| /* |
| * Copyright (C) 2009 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following disclaimer |
| * in the documentation and/or other materials provided with the |
| * distribution. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
| * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
| * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
| * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
| * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
| * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
| * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "third_party/blink/public/web/web_searchable_form_data.h" |
| |
| #include "third_party/blink/public/web/web_form_element.h" |
| #include "third_party/blink/public/web/web_input_element.h" |
| #include "third_party/blink/renderer/core/dom/document.h" |
| #include "third_party/blink/renderer/core/html/forms/form_data.h" |
| #include "third_party/blink/renderer/core/html/forms/html_form_control_element.h" |
| #include "third_party/blink/renderer/core/html/forms/html_form_element.h" |
| #include "third_party/blink/renderer/core/html/forms/html_input_element.h" |
| #include "third_party/blink/renderer/core/html/forms/html_option_element.h" |
| #include "third_party/blink/renderer/core/html/forms/html_select_element.h" |
| #include "third_party/blink/renderer/core/html_names.h" |
| #include "third_party/blink/renderer/core/input_type_names.h" |
| #include "third_party/blink/renderer/platform/heap/heap.h" |
| #include "third_party/blink/renderer/platform/network/form_data_encoder.h" |
| #include "third_party/blink/renderer/platform/wtf/text/text_encoding.h" |
| |
| namespace blink { |
| |
| using namespace html_names; |
| |
| namespace { |
| |
| // Gets the encoding for the form. |
| // TODO(tkent): Use FormDataEncoder::encodingFromAcceptCharset(). |
| void GetFormEncoding(const HTMLFormElement& form, WTF::TextEncoding* encoding) { |
| String str(form.FastGetAttribute(html_names::kAcceptCharsetAttr)); |
| str.Replace(',', ' '); |
| Vector<String> charsets; |
| str.Split(' ', charsets); |
| for (const String& charset : charsets) { |
| *encoding = WTF::TextEncoding(charset); |
| if (encoding->IsValid()) |
| return; |
| } |
| if (form.GetDocument().Loader()) |
| *encoding = WTF::TextEncoding(form.GetDocument().Encoding()); |
| } |
| |
| // If the form does not have an activated submit button, the first submit |
| // button is returned. |
| HTMLFormControlElement* ButtonToActivate(const HTMLFormElement& form) { |
| HTMLFormControlElement* first_submit_button = nullptr; |
| for (auto& element : form.ListedElements()) { |
| if (!element->IsFormControlElement()) |
| continue; |
| HTMLFormControlElement* control = ToHTMLFormControlElement(element); |
| if (control->IsActivatedSubmit()) { |
| // There's a button that is already activated for submit, return |
| // nullptr. |
| return nullptr; |
| } |
| if (!first_submit_button && control->IsSuccessfulSubmitButton()) |
| first_submit_button = control; |
| } |
| return first_submit_button; |
| } |
| |
| // Returns true if the selected state of all the options matches the default |
| // selected state. |
| bool IsSelectInDefaultState(const HTMLSelectElement& select) { |
| if (select.IsMultiple() || select.size() > 1) { |
| for (auto* const option_element : select.GetOptionList()) { |
| if (option_element->Selected() != |
| option_element->FastHasAttribute(kSelectedAttr)) |
| return false; |
| } |
| return true; |
| } |
| |
| // The select is rendered as a combobox (called menulist in WebKit). At |
| // least one item is selected, determine which one. |
| HTMLOptionElement* initial_selected = nullptr; |
| for (auto* const option_element : select.GetOptionList()) { |
| if (option_element->FastHasAttribute(kSelectedAttr)) { |
| // The page specified the option to select. |
| initial_selected = option_element; |
| break; |
| } |
| if (!initial_selected) |
| initial_selected = option_element; |
| } |
| return !initial_selected || initial_selected->Selected(); |
| } |
| |
| // Returns true if the form element is in its default state, false otherwise. |
| // The default state is the state of the form element on initial load of the |
| // page, and varies depending upon the form element. For example, a checkbox is |
| // in its default state if the checked state matches the state of the checked |
| // attribute. |
| bool IsInDefaultState(const HTMLFormControlElement& form_element) { |
| if (auto* input = ToHTMLInputElementOrNull(form_element)) { |
| if (input->type() == input_type_names::kCheckbox || |
| input->type() == input_type_names::kRadio) |
| return input->checked() == input->FastHasAttribute(kCheckedAttr); |
| } else if (auto* select = ToHTMLSelectElementOrNull(form_element)) { |
| return IsSelectInDefaultState(*select); |
| } |
| return true; |
| } |
| |
| // Look for a suitable search text field in a given HTMLFormElement |
| // Return nothing if one of those items are found: |
| // - A text area field |
| // - A file upload field |
| // - A Password field |
| // - More than one text field |
| HTMLInputElement* FindSuitableSearchInputElement(const HTMLFormElement& form) { |
| HTMLInputElement* text_element = nullptr; |
| for (const auto& item : form.ListedElements()) { |
| if (!item->IsFormControlElement()) |
| continue; |
| |
| HTMLFormControlElement& control = ToHTMLFormControlElement(*item); |
| |
| if (control.IsDisabledFormControl() || control.GetName().IsNull()) |
| continue; |
| |
| if (!IsInDefaultState(control) || IsHTMLTextAreaElement(control)) |
| return nullptr; |
| |
| if (IsHTMLInputElement(control) && control.willValidate()) { |
| const HTMLInputElement& input = ToHTMLInputElement(control); |
| |
| // Return nothing if a file upload field or a password field are |
| // found. |
| if (input.type() == input_type_names::kFile || |
| input.type() == input_type_names::kPassword) |
| return nullptr; |
| |
| if (input.IsTextField()) { |
| if (text_element) { |
| // The auto-complete bar only knows how to fill in one |
| // value. This form has multiple fields; don't treat it as |
| // searchable. |
| return nullptr; |
| } |
| text_element = ToHTMLInputElement(&control); |
| } |
| } |
| } |
| return text_element; |
| } |
| |
| // Build a search string based on a given HTMLFormElement and HTMLInputElement |
| // |
| // Search string output example from www.google.com: |
| // "hl=en&source=hp&biw=1085&bih=854&q={searchTerms}&btnG=Google+Search&aq=f&aqi=&aql=&oq=" |
| // |
| // Return false if the provided HTMLInputElement is not found in the form |
| bool BuildSearchString(const HTMLFormElement& form, |
| Vector<char>* encoded_string, |
| const WTF::TextEncoding& encoding, |
| const HTMLInputElement* text_element) { |
| bool is_element_found = false; |
| for (const auto& item : form.ListedElements()) { |
| if (!item->IsFormControlElement()) |
| continue; |
| |
| HTMLFormControlElement& control = ToHTMLFormControlElement(*item); |
| if (control.IsDisabledFormControl() || control.GetName().IsNull()) |
| continue; |
| |
| auto* form_data = MakeGarbageCollected<FormData>(encoding); |
| control.AppendToFormData(*form_data); |
| |
| for (const auto& entry : form_data->Entries()) { |
| if (!encoded_string->IsEmpty()) |
| encoded_string->push_back('&'); |
| FormDataEncoder::EncodeStringAsFormData(*encoded_string, |
| form_data->Encode(entry->name()), |
| FormDataEncoder::kNormalizeCRLF); |
| encoded_string->push_back('='); |
| if (&control == text_element) { |
| encoded_string->Append("{searchTerms}", 13); |
| is_element_found = true; |
| } else { |
| FormDataEncoder::EncodeStringAsFormData( |
| *encoded_string, form_data->Encode(entry->Value()), |
| FormDataEncoder::kNormalizeCRLF); |
| } |
| } |
| } |
| return is_element_found; |
| } |
| |
| } // namespace |
| |
| WebSearchableFormData::WebSearchableFormData( |
| const WebFormElement& form, |
| const WebInputElement& selected_input_element) { |
| HTMLFormElement* form_element = static_cast<HTMLFormElement*>(form); |
| HTMLInputElement* input_element = |
| static_cast<HTMLInputElement*>(selected_input_element); |
| |
| // Only consider forms that GET data. |
| if (EqualIgnoringASCIICase(form_element->getAttribute(kMethodAttr), "post")) |
| return; |
| |
| WTF::TextEncoding encoding; |
| GetFormEncoding(*form_element, &encoding); |
| if (!encoding.IsValid()) { |
| // Need a valid encoding to encode the form elements. |
| // If the encoding isn't found webkit ends up replacing the params with |
| // empty strings. So, we don't try to do anything here. |
| return; |
| } |
| |
| // Look for a suitable search text field in the form when a |
| // selectedInputElement is not provided. |
| if (!input_element) { |
| input_element = FindSuitableSearchInputElement(*form_element); |
| |
| // Return if no suitable text element has been found. |
| if (!input_element) |
| return; |
| } |
| |
| HTMLFormControlElement* first_submit_button = ButtonToActivate(*form_element); |
| if (first_submit_button) { |
| // The form does not have an active submit button, make the first button |
| // active. We need to do this, otherwise the URL will not contain the |
| // name of the submit button. |
| first_submit_button->SetActivatedSubmit(true); |
| } |
| |
| Vector<char> encoded_string; |
| bool is_valid_search_string = BuildSearchString( |
| *form_element, &encoded_string, encoding, input_element); |
| |
| if (first_submit_button) |
| first_submit_button->SetActivatedSubmit(false); |
| |
| // Return if the search string is not valid. |
| if (!is_valid_search_string) |
| return; |
| |
| KURL url(form_element->action()); |
| scoped_refptr<EncodedFormData> form_data = |
| EncodedFormData::Create(encoded_string); |
| url.SetQuery(form_data->FlattenToString()); |
| url_ = url; |
| encoding_ = String(encoding.GetName()); |
| } |
| |
| } // namespace blink |