Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / web / WebSearchableFormData.cpp
blob29459ad48775b959f6b6d479d3586fb1fe1c404e
1 /*
2 * Copyright (C) 2009 Google Inc. All rights reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions are
6 * met:
8 * * Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * * Redistributions in binary form must reproduce the above
11 * copyright notice, this list of conditions and the following disclaimer
12 * in the documentation and/or other materials provided with the
13 * distribution.
14 * * Neither the name of Google Inc. nor the names of its
15 * contributors may be used to endorse or promote products derived from
16 * this software without specific prior written permission.
18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31 #include "config.h"
32 #include "public/web/WebSearchableFormData.h"
34 #include "core/HTMLNames.h"
35 #include "core/InputTypeNames.h"
36 #include "core/dom/Document.h"
37 #include "core/html/FormData.h"
38 #include "core/html/HTMLFormControlElement.h"
39 #include "core/html/HTMLFormElement.h"
40 #include "core/html/HTMLInputElement.h"
41 #include "core/html/HTMLOptionElement.h"
42 #include "core/html/HTMLSelectElement.h"
43 #include "platform/network/FormDataEncoder.h"
44 #include "public/web/WebFormElement.h"
45 #include "public/web/WebInputElement.h"
46 #include "wtf/text/TextEncoding.h"
48 namespace blink {
50 using namespace HTMLNames;
52 namespace {
54 // Gets the encoding for the form.
55 // TODO(tkent): Use FormDataEncoder::encodingFromAcceptCharset().
56 void getFormEncoding(const HTMLFormElement& form, WTF::TextEncoding* encoding)
58 String str(form.fastGetAttribute(HTMLNames::accept_charsetAttr));
59 str.replace(',', ' ');
60 Vector<String> charsets;
61 str.split(' ', charsets);
62 for (const String& charset : charsets) {
63 *encoding = WTF::TextEncoding(charset);
64 if (encoding->isValid())
65 return;
67 if (form.document().loader())
68 *encoding = WTF::TextEncoding(form.document().encoding());
71 // Returns true if the submit request results in an HTTP URL.
72 bool isHTTPFormSubmit(const HTMLFormElement& form)
74 // FIXME: This function is insane. This is an overly complicated way to get
75 // this information.
76 String action(form.action());
77 // The isNull() check is trying to avoid completeURL returning KURL() when
78 // passed a null string.
79 return form.document().completeURL(action.isNull() ? "" : action).protocolIs("http");
82 // If the form does not have an activated submit button, the first submit
83 // button is returned.
84 HTMLFormControlElement* buttonToActivate(const HTMLFormElement& form)
86 HTMLFormControlElement* firstSubmitButton = nullptr;
87 for (auto& element : form.associatedElements()) {
88 if (!element->isFormControlElement())
89 continue;
90 HTMLFormControlElement* control = toHTMLFormControlElement(element);
91 if (control->isActivatedSubmit()) {
92 // There's a button that is already activated for submit, return
93 // nullptr.
94 return nullptr;
96 if (!firstSubmitButton && control->isSuccessfulSubmitButton())
97 firstSubmitButton = control;
99 return firstSubmitButton;
102 // Returns true if the selected state of all the options matches the default
103 // selected state.
104 bool isSelectInDefaultState(const HTMLSelectElement& select)
106 const WillBeHeapVector<RawPtrWillBeMember<HTMLElement>>& listItems = select.listItems();
107 if (select.multiple() || select.size() > 1) {
108 for (const auto& item : listItems) {
109 if (!isHTMLOptionElement(*item))
110 continue;
111 HTMLOptionElement* optionElement = toHTMLOptionElement(item);
112 if (optionElement->selected() != optionElement->fastHasAttribute(selectedAttr))
113 return false;
115 return true;
118 // The select is rendered as a combobox (called menulist in WebKit). At
119 // least one item is selected, determine which one.
120 HTMLOptionElement* initialSelected = nullptr;
121 for (const auto& item : listItems) {
122 if (!isHTMLOptionElement(*item))
123 continue;
124 HTMLOptionElement* optionElement = toHTMLOptionElement(item);
125 if (optionElement->fastHasAttribute(selectedAttr)) {
126 // The page specified the option to select.
127 initialSelected = optionElement;
128 break;
130 if (!initialSelected)
131 initialSelected = optionElement;
133 return !initialSelected || initialSelected->selected();
136 // Returns true if the form element is in its default state, false otherwise.
137 // The default state is the state of the form element on initial load of the
138 // page, and varies depending upon the form element. For example, a checkbox is
139 // in its default state if the checked state matches the state of the checked
140 // attribute.
141 bool isInDefaultState(const HTMLFormControlElement& formElement)
143 if (isHTMLInputElement(formElement)) {
144 const HTMLInputElement& inputElement = toHTMLInputElement(formElement);
145 if (inputElement.type() == InputTypeNames::checkbox || inputElement.type() == InputTypeNames::radio)
146 return inputElement.checked() == inputElement.fastHasAttribute(checkedAttr);
147 } else if (isHTMLSelectElement(formElement)) {
148 return isSelectInDefaultState(toHTMLSelectElement(formElement));
150 return true;
153 // Look for a suitable search text field in a given HTMLFormElement
154 // Return nothing if one of those items are found:
155 // - A text area field
156 // - A file upload field
157 // - A Password field
158 // - More than one text field
159 HTMLInputElement* findSuitableSearchInputElement(const HTMLFormElement& form)
161 HTMLInputElement* textElement = nullptr;
162 for (const auto& item : form.associatedElements()) {
163 if (!item->isFormControlElement())
164 continue;
166 HTMLFormControlElement& control = toHTMLFormControlElement(*item);
168 if (control.isDisabledFormControl() || control.name().isNull())
169 continue;
171 if (!isInDefaultState(control) || isHTMLTextAreaElement(control))
172 return nullptr;
174 if (isHTMLInputElement(control) && control.willValidate()) {
175 const HTMLInputElement& input = toHTMLInputElement(control);
177 // Return nothing if a file upload field or a password field are
178 // found.
179 if (input.type() == InputTypeNames::file || input.type() == InputTypeNames::password)
180 return nullptr;
182 if (input.isTextField()) {
183 if (textElement) {
184 // The auto-complete bar only knows how to fill in one
185 // value. This form has multiple fields; don't treat it as
186 // searchable.
187 return nullptr;
189 textElement = toHTMLInputElement(&control);
193 return textElement;
196 // Build a search string based on a given HTMLFormElement and HTMLInputElement
198 // Search string output example from www.google.com:
199 // "hl=en&source=hp&biw=1085&bih=854&q={searchTerms}&btnG=Google+Search&aq=f&aqi=&aql=&oq="
201 // Return false if the provided HTMLInputElement is not found in the form
202 bool buildSearchString(const HTMLFormElement& form, Vector<char>* encodedString, const WTF::TextEncoding& encoding, const HTMLInputElement* textElement)
204 bool isElementFound = false;
205 for (const auto& item : form.associatedElements()) {
206 if (!item->isFormControlElement())
207 continue;
209 HTMLFormControlElement& control = toHTMLFormControlElement(*item);
210 if (control.isDisabledFormControl() || control.name().isNull())
211 continue;
213 FormData* formData = FormData::create(encoding);
214 control.appendToFormData(*formData);
216 for (const auto& entry : formData->entries()) {
217 if (!encodedString->isEmpty())
218 encodedString->append('&');
219 FormDataEncoder::encodeStringAsFormData(*encodedString, entry->name());
220 encodedString->append('=');
221 if (&control == textElement) {
222 encodedString->append("{searchTerms}", 13);
223 isElementFound = true;
224 } else {
225 FormDataEncoder::encodeStringAsFormData(*encodedString, entry->value());
229 return isElementFound;
232 } // namespace
234 WebSearchableFormData::WebSearchableFormData(const WebFormElement& form, const WebInputElement& selectedInputElement)
236 RefPtrWillBeRawPtr<HTMLFormElement> formElement = static_cast<PassRefPtrWillBeRawPtr<HTMLFormElement>>(form);
237 HTMLInputElement* inputElement = static_cast<PassRefPtrWillBeRawPtr<HTMLInputElement>>(selectedInputElement).get();
239 // Only consider forms that GET data.
240 // Allow HTTPS only when an input element is provided.
241 if (equalIgnoringCase(formElement->getAttribute(methodAttr), "post")
242 || (!isHTTPFormSubmit(*formElement) && !inputElement))
243 return;
245 WTF::TextEncoding encoding;
246 getFormEncoding(*formElement, &encoding);
247 if (!encoding.isValid()) {
248 // Need a valid encoding to encode the form elements.
249 // If the encoding isn't found webkit ends up replacing the params with
250 // empty strings. So, we don't try to do anything here.
251 return;
254 // Look for a suitable search text field in the form when a
255 // selectedInputElement is not provided.
256 if (!inputElement) {
257 inputElement = findSuitableSearchInputElement(*formElement);
259 // Return if no suitable text element has been found.
260 if (!inputElement)
261 return;
264 HTMLFormControlElement* firstSubmitButton = buttonToActivate(*formElement);
265 if (firstSubmitButton) {
266 // The form does not have an active submit button, make the first button
267 // active. We need to do this, otherwise the URL will not contain the
268 // name of the submit button.
269 firstSubmitButton->setActivatedSubmit(true);
272 Vector<char> encodedString;
273 bool isValidSearchString = buildSearchString(*formElement, &encodedString, encoding, inputElement);
275 if (firstSubmitButton)
276 firstSubmitButton->setActivatedSubmit(false);
278 // Return if the search string is not valid.
279 if (!isValidSearchString)
280 return;
282 String action(formElement->action());
283 KURL url(formElement->document().completeURL(action.isNull() ? "" : action));
284 RefPtr<EncodedFormData> formData = EncodedFormData::create(encodedString);
285 url.setQuery(formData->flattenToString());
286 m_url = url;
287 m_encoding = String(encoding.name());
290 } // namespace blink