diff options
-rw-r--r-- | chrome/renderer/autofill/form_autofill_browsertest.cc | 175 | ||||
-rw-r--r-- | chrome/test/data/autofill/heuristics/input/bug_555010.html | 282 | ||||
-rw-r--r-- | chrome/test/data/autofill/heuristics/output/bug_555010.out | 22 | ||||
-rw-r--r-- | components/autofill/content/renderer/form_autofill_util.cc | 71 | ||||
-rw-r--r-- | content/public/test/render_view_test.cc | 99 | ||||
-rw-r--r-- | content/public/test/render_view_test.h | 26 |
6 files changed, 615 insertions, 60 deletions
diff --git a/chrome/renderer/autofill/form_autofill_browsertest.cc b/chrome/renderer/autofill/form_autofill_browsertest.cc index 9985fc2..c88bdc2 100644 --- a/chrome/renderer/autofill/form_autofill_browsertest.cc +++ b/chrome/renderer/autofill/form_autofill_browsertest.cc @@ -104,8 +104,10 @@ const char kFormHtml[] = " <INPUT type='submit' name='reply-send' value='Send'/>" "</FORM>"; +// This constant uses a mixed-case title tag to be sure that the title match is +// not case-sensitive. Other tests in this file use an all-lower title tag. const char kUnownedFormHtml[] = - "<HEAD><TITLE>enter shipping info</TITLE></HEAD>" + "<HEAD><TITLE>Enter Shipping Info</TITLE></HEAD>" "<INPUT type='text' id='firstname'/>" "<INPUT type='text' id='lastname'/>" "<INPUT type='hidden' id='imhidden'/>" @@ -135,6 +137,72 @@ const char kUnownedFormHtml[] = "<TEXTAREA id='textarea-nonempty'>Go away!</TEXTAREA>" "<INPUT type='submit' name='reply-send' value='Send'/>"; +// This constant has no title tag, and should be passed to +// LoadHTMLWithURLOverride to test the detection of unowned forms by URL. +const char kUnownedUntitledFormHtml[] = + "<INPUT type='text' id='firstname'/>" + "<INPUT type='text' id='lastname'/>" + "<INPUT type='hidden' id='imhidden'/>" + "<INPUT type='text' id='notempty' value='Hi'/>" + "<INPUT type='text' autocomplete='off' id='noautocomplete'/>" + "<INPUT type='text' disabled='disabled' id='notenabled'/>" + "<INPUT type='text' readonly id='readonly'/>" + "<INPUT type='text' style='visibility: hidden'" + " id='invisible'/>" + "<INPUT type='text' style='display: none' id='displaynone'/>" + "<INPUT type='month' id='month'/>" + "<INPUT type='month' id='month-nonempty' value='2011-12'/>" + "<SELECT id='select'>" + " <OPTION></OPTION>" + " <OPTION value='CA'>California</OPTION>" + " <OPTION value='TX'>Texas</OPTION>" + "</SELECT>" + "<SELECT id='select-nonempty'>" + " <OPTION value='CA' selected>California</OPTION>" + " <OPTION value='TX'>Texas</OPTION>" + "</SELECT>" + "<SELECT id='select-unchanged'>" + " <OPTION value='CA' selected>California</OPTION>" + " <OPTION value='TX'>Texas</OPTION>" + "</SELECT>" + "<TEXTAREA id='textarea'></TEXTAREA>" + "<TEXTAREA id='textarea-nonempty'>Go away!</TEXTAREA>" + "<INPUT type='submit' name='reply-send' value='Send'/>"; + +// This constant does not have a title tag, but should match an unowned form +// anyway because it is not English. +const char kUnownedNonEnglishFormHtml[] = + "<HTML LANG='fr'>" + "<INPUT type='text' id='firstname'/>" + "<INPUT type='text' id='lastname'/>" + "<INPUT type='hidden' id='imhidden'/>" + "<INPUT type='text' id='notempty' value='Hi'/>" + "<INPUT type='text' autocomplete='off' id='noautocomplete'/>" + "<INPUT type='text' disabled='disabled' id='notenabled'/>" + "<INPUT type='text' readonly id='readonly'/>" + "<INPUT type='text' style='visibility: hidden'" + " id='invisible'/>" + "<INPUT type='text' style='display: none' id='displaynone'/>" + "<INPUT type='month' id='month'/>" + "<INPUT type='month' id='month-nonempty' value='2011-12'/>" + "<SELECT id='select'>" + " <OPTION></OPTION>" + " <OPTION value='CA'>California</OPTION>" + " <OPTION value='TX'>Texas</OPTION>" + "</SELECT>" + "<SELECT id='select-nonempty'>" + " <OPTION value='CA' selected>California</OPTION>" + " <OPTION value='TX'>Texas</OPTION>" + "</SELECT>" + "<SELECT id='select-unchanged'>" + " <OPTION value='CA' selected>California</OPTION>" + " <OPTION value='TX'>Texas</OPTION>" + "</SELECT>" + "<TEXTAREA id='textarea'></TEXTAREA>" + "<TEXTAREA id='textarea-nonempty'>Go away!</TEXTAREA>" + "<INPUT type='submit' name='reply-send' value='Send'/>" + "</HTML>"; + std::string RetrievalMethodToString( const WebElementDescriptor::RetrievalMethod& method) { switch (method) { @@ -263,11 +331,15 @@ class FormAutofillTest : public ChromeRenderViewTest { // Test FormFillxxx functions. void TestFormFillFunctions(const char* html, bool unowned, + const char* url_override, const AutofillFieldCase* field_cases, size_t number_of_field_cases, FillFormFunction fill_form_function, GetValueFunction get_value_function) { - LoadHTML(html); + if (url_override) + LoadHTMLWithUrlOverride(html, url_override); + else + LoadHTML(html); WebFrame* web_frame = GetMainFrame(); ASSERT_NE(nullptr, web_frame); @@ -362,7 +434,7 @@ class FormAutofillTest : public ChromeRenderViewTest { id).to<WebInputElement>(); } - void TestFillForm(const char* html, bool unowned) { + void TestFillForm(const char* html, bool unowned, const char* url_override) { static const AutofillFieldCase field_cases[] = { // fields: form_control_type, name, initial_value, autocomplete_attribute, // should_be_autofilled, autofill_value, expected_value @@ -423,7 +495,8 @@ class FormAutofillTest : public ChromeRenderViewTest { "some multi-\nline value", "Go\naway!"}, }; - TestFormFillFunctions(html, unowned, field_cases, arraysize(field_cases), + TestFormFillFunctions(html, unowned, url_override, + field_cases, arraysize(field_cases), FillForm, &GetValueWrapper); // Verify preview selection. WebInputElement firstname = GetInputElementById("firstname"); @@ -431,7 +504,8 @@ class FormAutofillTest : public ChromeRenderViewTest { EXPECT_EQ(16, firstname.selectionEnd()); } - void TestPreviewForm(const char* html, bool unowned) { + void TestPreviewForm(const char* html, bool unowned, + const char* url_override) { static const AutofillFieldCase field_cases[] = { // Normal empty fields should be previewed. {"text", @@ -495,7 +569,8 @@ class FormAutofillTest : public ChromeRenderViewTest { "suggested multi-\nline value", ""}, }; - TestFormFillFunctions(html, unowned, field_cases, arraysize(field_cases), + TestFormFillFunctions(html, unowned, url_override, + field_cases, arraysize(field_cases), &PreviewForm, &GetSuggestedValueWrapper); // Verify preview selection. @@ -504,6 +579,20 @@ class FormAutofillTest : public ChromeRenderViewTest { EXPECT_EQ(19, firstname.selectionEnd()); } + void TestUnmatchedUnownedForm(const char* html, const char* url_override) { + if (url_override) + LoadHTMLWithUrlOverride(html, url_override); + else + LoadHTML(html); + + WebFrame* web_frame = GetMainFrame(); + ASSERT_NE(nullptr, web_frame); + + FormCache form_cache(*web_frame); + std::vector<FormData> forms = form_cache.ExtractNewForms(); + ASSERT_EQ(0U, forms.size()); + } + void TestFindFormForInputElement(const char* html, bool unowned) { LoadHTML(html); WebFrame* web_frame = GetMainFrame(); @@ -2508,11 +2597,29 @@ TEST_F(FormAutofillTest, FindFormForTextAreaElementForUnownedForm) { // Test regular FillForm function. TEST_F(FormAutofillTest, FillForm) { - TestFillForm(kFormHtml, false); + TestFillForm(kFormHtml, false, nullptr); } TEST_F(FormAutofillTest, FillFormForUnownedForm) { - TestFillForm(kUnownedFormHtml, true); + TestFillForm(kUnownedFormHtml, true, nullptr); +} + +TEST_F(FormAutofillTest, FillFormForUnownedUntitledForm) { + TestFillForm(kUnownedUntitledFormHtml, true, + "http://example.test/checkout_flow"); +} + +TEST_F(FormAutofillTest, FillFormForUnownedNonEnglishForm) { + TestFillForm(kUnownedNonEnglishFormHtml, true, nullptr); +} + +TEST_F(FormAutofillTest, FillFormForUnownedNonASCIIForm) { + std::string html("<HEAD><TITLE>accented latin: \xC3\xA0, thai: \xE0\xB8\x81, " + "control: \x04, nbsp: \xEF\xBB\xBF, non-BMP: \xF0\x9F\x8C\x80; This " + "should match a CHECKOUT flow despite the non-ASCII chars" + "</TITLE></HEAD>"); + html.append(kUnownedUntitledFormHtml); + TestFillForm(html.c_str(), true, nullptr); } TEST_F(FormAutofillTest, FillFormIncludingNonFocusableElements) { @@ -2594,19 +2701,65 @@ TEST_F(FormAutofillTest, FillFormIncludingNonFocusableElements) { "some multi-\nline value", "some multi-\nline value"}, }; - TestFormFillFunctions(kFormHtml, false, field_cases, arraysize(field_cases), + TestFormFillFunctions(kFormHtml, false, nullptr, + field_cases, arraysize(field_cases), &FillFormIncludingNonFocusableElementsWrapper, &GetValueWrapper); } TEST_F(FormAutofillTest, PreviewForm) { - TestPreviewForm(kFormHtml, false); + TestPreviewForm(kFormHtml, false, nullptr); } TEST_F(FormAutofillTest, PreviewFormForUnownedForm) { - TestPreviewForm(kUnownedFormHtml, true); + TestPreviewForm(kUnownedFormHtml, true, nullptr); +} + +TEST_F(FormAutofillTest, PreviewFormForUnownedUntitledForm) { + // This test uses a mixed-case URL to be sure that the url match is not + // case-sensitive. + TestPreviewForm(kUnownedUntitledFormHtml, true, + "http://example.test/Enter_Shipping_Address/"); +} + +TEST_F(FormAutofillTest, PreviewFormForUnownedNonEnglishForm) { + TestPreviewForm(kUnownedNonEnglishFormHtml, true, nullptr); +} + +// Data that looks like an unowned form should NOT be matched unless an +// additional indicator is present, such as title tag or url, to prevent false +// positives. + +TEST_F(FormAutofillTest, UnmatchedFormNoURL) { + TestUnmatchedUnownedForm(kUnownedUntitledFormHtml, nullptr); +} + +TEST_F(FormAutofillTest, UnmatchedFormPathWithoutKeywords) { + TestUnmatchedUnownedForm(kUnownedUntitledFormHtml, + "http://example.test/path_without_keywords"); +} + +TEST_F(FormAutofillTest, UnmatchedFormKeywordInQueryOnly) { + TestUnmatchedUnownedForm(kUnownedUntitledFormHtml, + "http://example.test/search?q=checkout+in+query"); } +TEST_F(FormAutofillTest, UnmatchedFormTitleWithoutKeywords) { + std::string wrong_title_html( + "<TITLE>This title has nothing to do with autofill</TITLE>"); + wrong_title_html += kUnownedUntitledFormHtml; + TestUnmatchedUnownedForm(wrong_title_html.c_str(), nullptr); +} + +TEST_F(FormAutofillTest, UnmatchedFormNonASCII) { + std::string html("<HEAD><TITLE>Non-ASCII soft hyphen in the middle of " + "keyword prevents a match here: check\xC2\xADout" + "</TITLE></HEAD>"); + html.append(kUnownedUntitledFormHtml); + TestUnmatchedUnownedForm(html.c_str(), nullptr); +} + + TEST_F(FormAutofillTest, Labels) { ExpectJohnSmithLabels( "<FORM name='TestForm' action='http://cnn.com' method='post'>" diff --git a/chrome/test/data/autofill/heuristics/input/bug_555010.html b/chrome/test/data/autofill/heuristics/input/bug_555010.html new file mode 100644 index 0000000..bf026a0 --- /dev/null +++ b/chrome/test/data/autofill/heuristics/input/bug_555010.html @@ -0,0 +1,282 @@ +<HTML> + <HEAD> + <meta name="viewport" content="width=320, user-scalable=no, initial-scale=1.0, maximum-scale=1.0" /> + <meta http-equiv="x-ua-compatible" content="IE=edge" > + <title>Office Supplies, Technology, Ink Much More | Staples - add checkout tag because url is inaccessible from test</title> + <meta name="title" content="Office Supplies, Technology, Ink Much More | Staples" /> + <meta name="description" content="Your partner for office supplies, technology and cleaning & breakroom. Discover more about Staples product range today. FREE shipping on orders over $45"> + <meta charset="UTF-8" /> + </HEAD> + <BODY style="margin:0px; padding: 0px;"> + <div id="id_skPageContainer" class="cls_skCOPg"> + <div id="id_skStudio_Wrapper"> + <div id="id_scfHeader" class="scfHeader" align="center"></div> + <div id="studiop_31" class="scfPage skcWebPage sk-ui-layout-legacy" align="center" data-pageid="31" data-viewName = "mobile" data-pageUrl="office/supplies/StaplesCheckoutFlow" > + <div id="id_scfContent_" class="scfContent" align="left"> + <div id="old_checkout" class="lyt_cont_div page" style= "width: 100%; height: 480px; " flexiPage="true"> + <div id="skPageLayoutCell_31_id-center" class="skc_pageCellLayout cls_skWidget widget68 " widgetId = "306" widgetName = "widget68" widgetType = "6" style="width:100.0%;height:480px;float: left;position: relative; overflow: hidden;"> + <div class="cls_richTextWidget" id=skPageLayoutCell_31_id-center > + <div id="id_skPageContainer"> + <div id="id_skcontent" class="cls_skContent cls_skCntPanelClosed"> + <div id="id_skPageContent"> + <div class="testcheckout"> + <div class="cls_skCOHeaderCont"> + <div class="cls_skCOBackNav"></div> + <span class="cls_skManageAddrHeading cls_skHide">Manage Addresses</span> + <span class="cls_skEditAddrHeading cls_skHide">Edit Address</span> + <span class="cls_skAddAddrHeading cls_skHide">Add Address</span> + <span class="cls_skCOHeading cls_skHide">Checkout</span> + <span class="cls_skECOHeading cls_skHide">Guest Checkout</span> + <span class="cls_skStorePickUpHeading cls_skHide"></span> + <span class="cls_skAlternatePickUpHeading cls_skHide">Alternate Pick Up Person</span> + <span class="cls_skRCHeading cls_skHide">Returning Customer</span> + <span class="cls_skManageCardHeading cls_skHide">Manage Payment Methods</span> + <span class="cls_skEditCardHeading cls_skHide">Edit Payment Method</span> + <span class="cls_skAddCardHeading cls_skHide">Add Payment Method</span> + </div> + <div class="cls_skCOShippingText"></div> + <div class="cls_skLoginCont cls_skHide"> + <div class="cls_skAppleSkuCOCont cls_skHide"> + <b>Guest Checkout Not Available</b> + <br>Because of one or more items in your Cart, Guest Checkout is not available. Please sign in to continue. + </div> + <div class="cls_skGuestCOCont"> + <div class="cls_skSignInCont"> Returning customer? + <div class="cls_skSignInBtn">SIGN IN</div> + </div> + <div class="cls_skGuestCOTxt">Guest Checkout</div> + <div class="cls_skClear"></div> + </div> + </div> + <div class="cls_skSigninWrap cls_skHide"> + <form class="cls_skCOLogonForm"> + <div id="id_skSigninErr" class="cls_skSigninErr"></div> + <div id="id_skSignInputName" class="cls_skInput"> + <input id="id_skCOUsername" name="userName" class="cls_skInputTxt cls_skUsername scTrack scInput" sctype="scInput" scvalue="username" scinput="input" fieldtype="username" placeholder="Username or Email address" event="username field" tabindex="5" maxlength="55" autocomplete="on" autocorrect="off" autocapitalize="off" type="email"> + <div class="cls_skClearBut"></div> + </div> + <div id="id_skSignInputPwd" class="cls_skInput"> + <input id="id_skCOPwd" name="password" class="cls_skInputTxt cls_skPwd scTrack scInput" sctype="scInput" scvalue="password" scinput="input" fieldtype="pwd" placeholder="Password" event="password field" tabindex="5" maxlength="256" autocapitalize="off" autocomplete="on" type="password"> + <div class="cls_skClearBut"></div> + </div> + </form> + <div class="cls_skForgotPwd scTrack scButton" sctype="scButton" scvalue="forgetpassword">Forgot Password?</div> + <div class="cls_skSignIn scTrack scButton" sctype="scButton" scvalue="Signin">Sign In</div> + <div class="cls_skClear"></div> + </div> + <div class="cls_skUserCont"> + <div id="id_skCOErrorCont" class="cls_skCOErrorCont"></div> + <div id="id_skCOShippingCont" class="cls_skCOShippingCont cls_skHide"> + <div id="id_skCheckoutInputCont" class="cls_skCheckoutInputCont"> + <div id="id_skShipInputName" class="cls_skInput"> + <input id="id_skCOName" class="cls_skInputTxt cls_skName scTrack scInput" sctype="scInput" scvalue="editname" scinput="input" fieldtype="name" placeholder="Full Name (First Name Last Name)" event="shipping name" tabindex="1" maxlength="128" name="cardName" autocomplete="on" autocorrect="off" autocapitalize="off" type="text"> + <div class="cls_skClearBut"></div> + </div> + <div id="id_skShipInputAddress" class="cls_skInput"> + <div class="cls_skAddressWrapper"> + <div class="cls_skCOShipAddress cls_skAddressCont"> + <input id="id_skCOShipAddress" class="cls_skInputTxt cls_skAddress cls_skShipToAddress scTrack scInput" sctype="scInput" scvalue="editadd" scinput="input" fieldtype="address" placeholder="Shipping Address" event="shipping address" tabindex="2" maxlength="128" addresstype="ship" type="text"> + <div class="cls_skClearBut"></div> + </div> + <div class="cls_skCOShipAptBldg"> + <input id="id_skCOShipAptBldg" class="cls_skInputTxt cls_skInputOptionalTxt cls_skApartment scInput" scvalue="apartment" scinput="input" fieldtype="aptbldg" placeholder="Apt/Bldg (optional)" event="shipping apt/suite" tabindex="3" maxlength="35" autocomplete="off" type="text"> + <div class="cls_skClearBut"></div> + </div> + </div> + </div> + <div class="cls_skShipToStore cls_skHide"> + <div class="cls_skShipToStoreDetailTxt"></div> + </div> + <div class="cls_skStorePickUpBtn cls_skHide"> + <div class="cls_skStorePickUpBtnTxt"></div> + </div> + <div id="id_skShipInputPhone" class="cls_skInput"> + <input id="id_skCOPhoneno" class="cls_skInputTxt cls_skCOPhoneno cls_skPhoneNo scTrack scInput" sctype="scInput" scvalue="editphone" scinput="input" maxlength="16" fieldtype="phnumber" placeholder="Phone" event="shipping phone number" tabindex="4" autocomplete="off" type="tel"> + <div class="cls_skClearBut"></div> + </div> + <div id="id_skShipInputEmail" class="cls_skInput"> + <input id="id_skCOEmail" class="cls_skInputTxt cls_skEmail scTrack scInput" sctype="scInput" scvalue="email" scinput="input" fieldtype="email" placeholder="Email" event="shipping email" tabindex="5" maxlength="256" autocapitalize="off" type="email"> + <div class="cls_skClearBut"></div> + </div> + <div id="id_skShipInputCredit" class="cls_skInput"> + <div class="cls_skAddressWrapper"> + <div class="cls_skCrditCarDetailsCont">Credit Card Details</div> + <div class="cls_skCrditCardImg"></div> + <div class="cls_skCOCreditCardCont"> + <label class="cls_skAstrickSymbol">*</label> + <input id="id_skCOCreditCard" class="cls_skInputTxt cls_skCreditCardNo cls_skCreditCard scTrack scInput" sctype="scInput" scvalue="creditcard" scinput="input" maxlength="19" fieldtype="cardNumber" event="card number" placeholder="1234 5647 8901 2345" tabindex="6" name="cardNumber" autocomplete="off" type="tel"> + </div> + <div class="cls_skCOCreditYrMnthCont"> + <input class="cls_skInputTxt cls_skCreditYrMth cls_skCreditCard scInput" scvalue="month/year" scinput="input" fieldtype="mnthYr" placeholder="EXP" event="expire month/year" tabindex="7" name="expirationMonth" autocomplete="off" type="tel"> + </div> + <div class="cls_skCOCreditCcv"> + <input class="cls_skInputTxt cls_skCreditCcv cls_skCreditCard scInput" scvalue="CVV" scinput="input" maxlength="4" fieldtype="cvv" placeholder="CVV" event="card id" tabindex="8" name="cvv" autocomplete="off" type="tel"> + </div> + <div class="cls_skClearBut"></div> + </div> + </div> + </div> + </div> + <div class="cls_skCOSaveCCCont cls_skHide"> Save credit card + <div class="cls_skCOSaveCCtoggleCont"> + <div class="cls_skCOSaveCCtoggleYes scTrack scOption" sctype="scOption" and="" scvalue="No|Yes"></div> + <div class="cls_skCOSaveCCtoggleNo scTrack scOption" sctype="scOption" and="" scvalue="No|Yes"></div> + </div> + </div> + <div class="cls_skCOUseAsCont cls_skHide"> Use<span></span> as billing address + <div class="cls_skCOUseAstoggleCont"> + <div class="cls_skCOUseAstoggleYes scTrack scOption" sctype="scOption" and="" scvalue="No|Yes"></div> + <div class="cls_skCOUseAstoggleNo scTrack scOption" sctype="scOption" and="" scvalue="No|Yes"></div> + </div> + </div> + <div id="id_skCOBillingCont" class="cls_skCOBillingCont cls_skHide"> + <div id="id_skCheckoutInputCont" class="cls_skCheckoutInputCont"> + <div id="id_skBillInputName" class="cls_skInput"> + <input id="id_skCOName" class="cls_skInputTxt cls_skName scTrack scInput" sctype="scInput" scvalue="bname" scinput="input" fieldtype="name" placeholder="Card Holder Name" event="billing name" tabindex="9" maxlength="128" autocomplete="on" autocorrect="off" autocapitalize="off" type="text"> + <div class="cls_skClearBut"></div> + </div> + <div id="id_skBillInputAddress" class="cls_skInput"> + <div class="cls_skAddressWrapper"> + <div class="cls_skCOShipAddress cls_skAddressCont"> + <input id="id_skCOShipAddress" class="cls_skInputTxt cls_skAddress cls_skBillToAddress scTrack scInput" sctype="scInput" scvalue="baddress" scinput="input" fieldtype="address" placeholder="Billing Address" event="billing address" tabindex="10" maxlength="128" autocomplete="off" type="text"> + <div class="cls_skClearBut"></div> + </div> + <div class="cls_skCOShipAptBldg"> + <input id="id_skCOShipAptBldg" class="cls_skInputTxt cls_skInputOptionalTxt cls_skApartment scInput" scvalue="apartment" scinput="input" fieldtype="aptbldg" placeholder="Apt/Bldg (optional)" event="billing apt/suite" tabindex="11" maxlength="35" autocomplete="off" type="text"> + <div class="cls_skClearBut"></div> + </div> + </div> + </div> + <div id="id_skBillInputPhone" class="cls_skInput"> + <input id="id_skCOPhoneno" class="cls_skInputTxt cls_skCOPhoneno cls_skPhoneNo scTrack scInput" sctype="scInput" scvalue="bphone" scinput="input" maxlength="16" fieldtype="phnumber" placeholder="Phone" event="billing phone number" tabindex="12" type="tel"> + <div class="cls_skClearBut"></div> + </div> + </div> + </div> + <div id="id_skCartCouponsCont" class="cls_skCartCouponsCont cls_skHide"></div> + <div id="id_skSavedAddrWrap" class="cls_skHide"> + <div id="id_skSavedAddrCont" class="cls_skSavedAddrCont"> + </div> + </div> + <div id="id_skAddrWrap" class="cls_skHide"> + <div id="id_skAddrCont" class="cls_skAddrCont"> + <div id="id_skCheckoutInputCont" class="cls_skCheckoutInputCont"> + <div class="cls_skInput"> + <input id="id_skCOName" class="cls_skInputTxt cls_skName scTrack scInput" scvalue="name" scinput="input" fieldtype="name" placeholder="Name" event="shipping name" tabindex="9" maxlength="128" autocomplete="on" autocorrect="off" autocapitalize="off" type="text"> + <div class="cls_skClearBut"></div> + </div> + <div class="cls_skInput"> + <div class="cls_skCOShipAddress cls_skAddressCont"> + <input id="id_skCOShipAddress" class="cls_skInputTxt cls_skAddress scTrack scInput" scvalue="address" scinput="input" fieldtype="address" placeholder="Address" event="shipping address" tabindex="10" maxlength="128" autocomplete="off" type="text"> + <div class="cls_skClearBut"></div> + </div> + <div class="cls_skCOShipAptBldg"> + <input id="id_skCOShipAptBldg" class="cls_skInputTxt cls_skInputOptionalTxt cls_skApartment scInput" scvalue="apartment" scinput="input" fieldtype="aptbldg" placeholder="Apt/Bldg (optional)" event="shipping apt/suite" tabindex="11" maxlength="35" autocomplete="off" type="text"> + <div class="cls_skClearBut"></div> + </div> + </div> + <div class="cls_skInput"> + <input id="id_skCOPhoneno" class="cls_skInputTxt cls_skCOPhoneno cls_skPhoneNo scTrack scInput" scvalue="phone" scinput="input" maxlength="16" fieldtype="phnumber" placeholder="Phone" event="shipping phone number" tabindex="12" type="tel"> + <div class="cls_skClearBut"></div> + </div> + </div> + <div class="cls_skShipToAddr cls_skHide scTrack scButton" sctype="scButton" scvalue="shiptoaddress">SHIP TO THIS ADDRESS</div> + <div class="cls_skNewAddrContinue cls_skHide">CONTINUE</div> + </div> + </div> + <div id="id_skSavedCardWrap" class="cls_skHide"> + <div id="id_skSavedCardCont" class="cls_skSavedCardCont"></div> + </div> + <div id="id_skCardWrap" class="cls_skHide"> + <div id="id_skCardCont" class="cls_skCardCont"> + <div id="id_skManageCardInputCont" class="cls_skCheckoutInputCont"> + <div id="id_skManageInputCredit" class="cls_skInput"> + <div class="cls_skCrditCarDetailsCont">Credit Card Details</div> + <div class="cls_skCrditCardImg"></div> + <div class="cls_skCOCreditCardCont"> + <label class="cls_skAstrickSymbol">*</label> + <input id="id_skManageCreditCard" class="cls_skInputTxt cls_skCreditCardNo cls_skCreditCard scTrack scInput" scvalue="editcardno" scinput="input" maxlength="19" fieldtype="cardNumber" placeholder="1234 5647 8901 2345" tabindex="6" name="cardNumber" autocomplete="off" type="tel"> + </div> + <div class="cls_skCOCreditYrMnthCont"> + <input class="cls_skInputTxt cls_skCreditYrMth cls_skCreditCard scInput" scvalue="month/year" scinput="input" fieldtype="mnthYr" placeholder="EXP" tabindex="7" name="expirationMonth" autocomplete="off" type="tel"> + </div> + <div class="cls_skCOCreditCcv"> + <input class="cls_skInputTxt cls_skCreditCcv cls_skCreditCard scInput" scvalue="CVV" scinput="input" maxlength="4" fieldtype="cvv" placeholder="CVV" tabindex="8" name="cvv" autocomplete="off" type="tel"> + </div> + <div class="cls_skClearBut"></div> + </div> + </div> + <div class="cls_skPayBtn cls_skHide scTrack scButton" sctype="scButton" scvalue="selectthiscard">SELECT THIS CARD</div> + <div class="cls_skNewCardContinue cls_skHide scTrack scButton" sctype="scButton" scvalue="addcard">ADD CARD</div> + </div> + </div> + <div id="id_skStorePickUpWrap" class="cls_skStorePickUpWrap cls_skHide"> + <div class="cls_skCOaccContainer"></div> + </div> + <div id="id_skAltPickUpWrap" class="cls_skAltPickUpWrap cls_skHide"> + <div class="cls_skStorePickupAlternateCont"> + <div id="id_skAltPUInputPhone" class="cls_skInput"> + <input id="id_skAltPUName" class="cls_skInputTxt cls_skName scInput" scvalue="name" scinput="input" fieldtype="name" placeholder="Name" tabindex="1" maxlength="128" autocomplete="on" autocorrect="off" autocapitalize="off" type="text"> + <div class="cls_skClearBut"></div> + </div> + <div id="id_skAltPUInputPhone" class="cls_skInput"> + <input id="id_skAltPUPhoneno" class="cls_skInputTxt cls_skCOPhoneno cls_skPhoneNo scInput" scvalue="phone" scinput="input" maxlength="16" fieldtype="phnumber" placeholder="Phone" tabindex="2" autocomplete="off" type="tel"> + <div class="cls_skClearBut"></div> + </div> + <div id="id_skAltPUInputEmail" class="cls_skInput"> + <input id="id_skAltPUEmail" class="cls_skInputTxt cls_skEmail scInput" scvalue="email" scinput="input" fieldtype="email" placeholder="Email" tabindex="3" maxlength="256" autocapitalize="off" type="email"> + <div class="cls_skClearBut"></div> + </div> + <div class="cls_skAlternatePickupContinue">CONTINUE</div> + </div> + </div> + <div class="cls_skCheckoutVme cls_skHide"> + <div class="cls_skVmeErrorMsg hide"></div> + <div class="cls_skCardImg"></div> + </div> + </div> + </div> + <div class="cls_skCOOrderCont cls_skHide"> + <div class="cls_skCOOrderItemsCont"> + <div class="cls_skCOItems cls_skCODetails"> Product Total <b>$0.00</b></div> + <div class="cls_skCOShipping cls_skCODetails"> Shipping <b>Free</b></div> + <div class="cls_skCOOversize cls_skCODetails"> Oversize Shipping Fees <b>$0.00</b></div> + <div class="cls_skCOCoupons cls_skCODetails"> Coupons & Rewards <b>$0.00</b></div> + <div class="cls_skCOTax cls_skCODetails"> Estimated Tax <b>$0.00</b></div> + <div class="cls_skCOTotalCost cls_skCOTotAmt cls_skCODetails"> Total Amount Due <span>$0.00</span></div> + </div> + <div class="cls_skCOPayCont"> + <div class="cls_skCOPayNow scTrack scPay" sctype="scPay" tabindex="13">PLACE ORDER</div> + <div class="cls_skCOPayNowLoading"> + <div class="cls_skActivitySpinner"></div> + </div> + </div> + </div> + </div> + <div id="id_skTaxCalculationPopup" class="cls_skTaxCalculationPopup"> + <div class="cls_skTaxCalculationCont"> + <div class="cls_skLoadingImg"></div> + <div class="cls_skTaxCalculationMsg">Calculating Tax</div> + </div> + </div> + <div id="id_skMFLoadingGauge" class="cls_skPageLoadingGauge"> + <div class="cls_skActivitySpinner"></div> + </div> + <div id="id_skPanelCntWrap" class="cls_skPanelCntWrap"></div> + <div class="cls_skCORPValidationPopup"></div> + <div class="cls_skVPopupTop"></div> + <div id="id_skHeightEl" class="cls_skHeightEl" style="display:none"></div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + <div id="id_scfFooter" class="scfFooter"></div> + </div> + </div> + <div class="cls_skCouponCont"></div> + <div class="cls_skATBPopupMask"></div> + </BODY> +</HTML> diff --git a/chrome/test/data/autofill/heuristics/output/bug_555010.out b/chrome/test/data/autofill/heuristics/output/bug_555010.out new file mode 100644 index 0000000..32a2019 --- /dev/null +++ b/chrome/test/data/autofill/heuristics/output/bug_555010.out @@ -0,0 +1,22 @@ +CREDIT_CARD_NAME | cardName | Full Name (First Name Last Name) | | cardName_1-cc +ADDRESS_HOME_LINE1 | id_skCOShipAddress | Shipping Address | | cardName_1-default +UNKNOWN_TYPE | id_skCOShipAptBldg | Apt/Bldg (optional) | | cardName_1-default +PHONE_HOME_WHOLE_NUMBER | id_skCOPhoneno | Phone | | cardName_1-default +EMAIL_ADDRESS | id_skCOEmail | Email | | cardName_1-default +CREDIT_CARD_NUMBER | cardNumber | 1234 5647 8901 2345 | | cardName_1-cc +CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR | expirationMonth | EXP | | cardName_1-cc +CREDIT_CARD_VERIFICATION_CODE | cvv | CVV | | cardName_1-cc +CREDIT_CARD_NAME | id_skCOName | Card Holder Name | | id_skCOName_1-cc +ADDRESS_HOME_LINE1 | id_skCOShipAddress | Billing Address | | id_skCOName_1-default +UNKNOWN_TYPE | id_skCOShipAptBldg | Apt/Bldg (optional) | | id_skCOName_1-default +PHONE_HOME_WHOLE_NUMBER | id_skCOPhoneno | Phone | | id_skCOName_1-default +NAME_FULL | id_skCOName | Name | | id_skCOName_1-default +ADDRESS_HOME_LINE1 | id_skCOShipAddress | Address | | id_skCOShipAddress_3-default +UNKNOWN_TYPE | id_skCOShipAptBldg | Apt/Bldg (optional) | | id_skCOShipAddress_3-default +PHONE_HOME_WHOLE_NUMBER | id_skCOPhoneno | Phone | | id_skCOShipAddress_3-default +CREDIT_CARD_NUMBER | cardNumber | 1234 5647 8901 2345 | | id_skCOShipAddress_3-cc +CREDIT_CARD_EXP_DATE_4_DIGIT_YEAR | expirationMonth | EXP | | id_skCOShipAddress_3-cc +CREDIT_CARD_VERIFICATION_CODE | cvv | CVV | | id_skCOShipAddress_3-cc +CREDIT_CARD_NAME | id_skAltPUName | Name | | id_skCOShipAddress_3-cc +PHONE_HOME_WHOLE_NUMBER | id_skAltPUPhoneno | Phone | | id_skCOShipAddress_3-default +EMAIL_ADDRESS | id_skAltPUEmail | Email | | id_skCOShipAddress_3-default diff --git a/components/autofill/content/renderer/form_autofill_util.cc b/components/autofill/content/renderer/form_autofill_util.cc index b7bb401..40fbb03 100644 --- a/components/autofill/content/renderer/form_autofill_util.cc +++ b/components/autofill/content/renderer/form_autofill_util.cc @@ -1468,40 +1468,57 @@ bool UnownedCheckoutFormElementsAndFieldSetsToFormData( FormFieldData* field) { // Only attempt formless Autofill on checkout flows. This avoids the many // false positives found on the non-checkout web. See - // http://crbug.com/462375. For now this early abort only applies to - // English-language pages, because the regex is not translated. Note that - // an empty "lang" attribute counts as English. A potential problem is that - // this only checks document.title(), but should actually check the main - // frame's title. Thus it may make bad decisions for iframes. + // http://crbug.com/462375. WebElement html_element = document.documentElement(); + + // For now this restriction only applies to English-language pages, because + // the keywords are not translated. Note that an empty "lang" attribute + // counts as English. std::string lang; if (!html_element.isNull()) lang = html_element.getAttribute("lang").utf8(); - if (lang.empty() || - base::StartsWith(lang, "en", base::CompareCase::INSENSITIVE_ASCII)) { - std::string title(base::UTF16ToUTF8(base::string16(document.title()))); - const char* const kKeywords[] = { - "payment", - "checkout", - "address", - "delivery", - "shipping", - }; - - bool found = false; - for (const auto& keyword : kKeywords) { - if (title.find(keyword) != base::string16::npos) { - found = true; - break; - } + if (!lang.empty() && + !base::StartsWith(lang, "en", base::CompareCase::INSENSITIVE_ASCII)) { + return UnownedFormElementsAndFieldSetsToFormData( + fieldsets, control_elements, element, document, extract_mask, form, + field); + } + + // A potential problem is that this only checks document.title(), but should + // actually check the main frame's title. Thus it may make bad decisions for + // iframes. + base::string16 title(base::ToLowerASCII(base::string16(document.title()))); + + // Don't check the path for url's without a standard format path component, + // such as data:. + std::string path; + GURL url(document.url()); + if (url.IsStandard()) + path = base::ToLowerASCII(url.path()); + + const char* const kKeywords[] = { + "payment", + "checkout", + "address", + "delivery", + "shipping", + }; + + for (const auto& keyword : kKeywords) { + // Compare char16 elements of |title| with char elements of |keyword| using + // operator==. + auto title_pos = std::search(title.begin(), title.end(), + keyword, keyword + strlen(keyword)); + if (title_pos != title.end() || + path.find(keyword) != std::string::npos) { + // Found a keyword: treat this as an unowned form. + return UnownedFormElementsAndFieldSetsToFormData( + fieldsets, control_elements, element, document, extract_mask, form, + field); } - if (!found) - return false; } - return UnownedFormElementsAndFieldSetsToFormData( - fieldsets, control_elements, element, document, extract_mask, form, - field); + return false; } bool UnownedPasswordFormElementsAndFieldSetsToFormData( diff --git a/content/public/test/render_view_test.cc b/content/public/test/render_view_test.cc index 3eabdb9..c17e342 100644 --- a/content/public/test/render_view_test.cc +++ b/content/public/test/render_view_test.cc @@ -36,6 +36,7 @@ #include "content/test/test_content_client.h" #include "content/test/test_render_frame.h" #include "third_party/WebKit/public/platform/WebScreenInfo.h" +#include "third_party/WebKit/public/platform/WebURLLoader.h" #include "third_party/WebKit/public/platform/WebURLRequest.h" #include "third_party/WebKit/public/web/WebDocument.h" #include "third_party/WebKit/public/web/WebHistoryItem.h" @@ -64,6 +65,7 @@ using blink::WebLocalFrame; using blink::WebMouseEvent; using blink::WebScriptSource; using blink::WebString; +using blink::WebURLLoader; using blink::WebURLRequest; namespace { @@ -104,37 +106,102 @@ bool GetWindowsKeyCode(char ascii_character, int* key_code) { } } +WebURLRequest createDataRequest(const std::string& html) { + std::string url_str = "data:text/html;charset=utf-8,"; + url_str.append(html); + GURL url(url_str); + WebURLRequest request(url); + request.setCheckForBrowserSideNavigation(false); + return request; +} + } // namespace namespace content { -class RendererBlinkPlatformImplNoSandboxImpl +const char kWrappedHTMLDataHeader[] = "X-WrappedHTMLData"; + +// This loader checks all requests for the presence of the X-WrappedHTMLData +// header and, if it's found, substitutes a data: url with the header's value +// instead of loading the original request. It is used to implement +// LoadHTMLWithURLOverride. +class WebURLLoaderWrapper : public WebURLLoader { +public: + WebURLLoaderWrapper(WebURLLoader* wrapped_loader) + : wrapped_loader_(wrapped_loader) { } + + void loadSynchronously(const WebURLRequest& request, + blink::WebURLResponse& response, + blink::WebURLError& error, + blink::WebData& data) override { + std::string html = request.httpHeaderField(kWrappedHTMLDataHeader).utf8(); + wrapped_loader_->loadSynchronously( + html.empty() ? request : createDataRequest(html), + response, + error, + data); + } + + void loadAsynchronously(const WebURLRequest& request, + blink::WebURLLoaderClient* client) override { + std::string html = request.httpHeaderField(kWrappedHTMLDataHeader).utf8(); + wrapped_loader_->loadAsynchronously( + html.empty() ? request : createDataRequest(html), + client); + } + + void cancel() override { + wrapped_loader_->cancel(); + } + + void setDefersLoading(bool defer) override { + wrapped_loader_->setDefersLoading(defer); + } + + void setLoadingTaskRunner(blink::WebTaskRunner* runner) override { + wrapped_loader_->setLoadingTaskRunner(runner); + } + +private: + std::unique_ptr<WebURLLoader> wrapped_loader_; +}; + +class RendererBlinkPlatformImplTestOverrideImpl : public RendererBlinkPlatformImpl { public: - RendererBlinkPlatformImplNoSandboxImpl( + RendererBlinkPlatformImplTestOverrideImpl( scheduler::RendererScheduler* scheduler) : RendererBlinkPlatformImpl(scheduler) {} + // Get rid of the dependency to the sandbox, which is not available in + // RenderViewTest. blink::WebSandboxSupport* sandboxSupport() override { return NULL; } + + // Inject a WebURLLoader which rewrites requests that have the + // X-WrappedHTMLData header. + WebURLLoader* createURLLoader() override { + return new WebURLLoaderWrapper( + RendererBlinkPlatformImpl::createURLLoader()); + } }; -RenderViewTest::RendererBlinkPlatformImplNoSandbox:: - RendererBlinkPlatformImplNoSandbox() { +RenderViewTest::RendererBlinkPlatformImplTestOverride:: + RendererBlinkPlatformImplTestOverride() { renderer_scheduler_ = scheduler::RendererScheduler::Create(); blink_platform_impl_.reset( - new RendererBlinkPlatformImplNoSandboxImpl(renderer_scheduler_.get())); + new RendererBlinkPlatformImplTestOverrideImpl(renderer_scheduler_.get())); } -RenderViewTest::RendererBlinkPlatformImplNoSandbox:: - ~RendererBlinkPlatformImplNoSandbox() { +RenderViewTest::RendererBlinkPlatformImplTestOverride:: + ~RendererBlinkPlatformImplTestOverride() { } blink::Platform* - RenderViewTest::RendererBlinkPlatformImplNoSandbox::Get() const { + RenderViewTest::RendererBlinkPlatformImplTestOverride::Get() const { return blink_platform_impl_.get(); } -void RenderViewTest::RendererBlinkPlatformImplNoSandbox::Shutdown() { +void RenderViewTest::RendererBlinkPlatformImplTestOverride::Shutdown() { renderer_scheduler_->Shutdown(); blink_platform_impl_->Shutdown(); } @@ -177,11 +244,19 @@ bool RenderViewTest::ExecuteJavaScriptAndReturnIntValue( } void RenderViewTest::LoadHTML(const char* html) { - std::string url_str = "data:text/html;charset=utf-8,"; - url_str.append(html); - GURL url(url_str); + GetMainFrame()->loadRequest(createDataRequest(html)); + // The load actually happens asynchronously, so we pump messages to process + // the pending continuation. + FrameLoadWaiter(view_->GetMainRenderFrame()).Wait(); +} + +void RenderViewTest::LoadHTMLWithUrlOverride(const char* html, + const char* url_override) { + GURL url(url_override); WebURLRequest request(url); request.setCheckForBrowserSideNavigation(false); + request.addHTTPHeaderField(kWrappedHTMLDataHeader, WebString::fromUTF8(html)); + GetMainFrame()->loadRequest(request); // The load actually happens asynchronously, so we pump messages to process // the pending continuation. diff --git a/content/public/test/render_view_test.h b/content/public/test/render_view_test.h index 27cea17..6e351de 100644 --- a/content/public/test/render_view_test.h +++ b/content/public/test/render_view_test.h @@ -48,23 +48,23 @@ class FakeCompositorDependencies; class MockRenderProcess; class PageState; class RendererMainPlatformDelegate; -class RendererBlinkPlatformImplNoSandboxImpl; +class RendererBlinkPlatformImplTestOverrideImpl; class RenderView; class RenderViewTest : public testing::Test, blink::WebLeakDetectorClient { public: - // A special BlinkPlatformImpl class for getting rid off the dependency to the - // sandbox, which is not available in RenderViewTest. - class RendererBlinkPlatformImplNoSandbox { + // A special BlinkPlatformImpl class with overrides that are useful for + // RenderViewTest. + class RendererBlinkPlatformImplTestOverride { public: - RendererBlinkPlatformImplNoSandbox(); - ~RendererBlinkPlatformImplNoSandbox(); + RendererBlinkPlatformImplTestOverride(); + ~RendererBlinkPlatformImplTestOverride(); blink::Platform* Get() const; void Shutdown(); private: scoped_ptr<scheduler::RendererScheduler> renderer_scheduler_; - scoped_ptr<RendererBlinkPlatformImplNoSandboxImpl> blink_platform_impl_; + scoped_ptr<RendererBlinkPlatformImplTestOverrideImpl> blink_platform_impl_; }; RenderViewTest(); @@ -88,10 +88,16 @@ class RenderViewTest : public testing::Test, blink::WebLeakDetectorClient { bool ExecuteJavaScriptAndReturnIntValue(const base::string16& script, int* result); - // Loads the given HTML into the main frame as a data: URL and blocks until - // the navigation is committed. + // Loads |html| into the main frame as a data: URL and blocks until the + // navigation is committed. void LoadHTML(const char* html); + // Pretends to load |url| into the main frame, but substitutes |html| for the + // response body (and does not include any response headers). This can be used + // instead of LoadHTML for tests that cannot use a data: url (for example if + // document.location needs to be set to something specific.) + void LoadHTMLWithUrlOverride(const char* html, const char* url); + // Returns the current PageState. // In OOPIF enabled modes, this returns a PageState object for the main frame. PageState GetCurrentPageState(); @@ -188,7 +194,7 @@ class RenderViewTest : public testing::Test, blink::WebLeakDetectorClient { // We use a naked pointer because we don't want to expose RenderViewImpl in // the embedder's namespace. RenderView* view_; - RendererBlinkPlatformImplNoSandbox blink_platform_impl_; + RendererBlinkPlatformImplTestOverride blink_platform_impl_; scoped_ptr<ContentClient> content_client_; scoped_ptr<ContentBrowserClient> content_browser_client_; scoped_ptr<ContentRendererClient> content_renderer_client_; |