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
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
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.
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"
50 using namespace HTMLNames
;
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())
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
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())
90 HTMLFormControlElement
* control
= toHTMLFormControlElement(element
);
91 if (control
->isActivatedSubmit()) {
92 // There's a button that is already activated for submit, return
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
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
))
111 HTMLOptionElement
* optionElement
= toHTMLOptionElement(item
);
112 if (optionElement
->selected() != optionElement
->fastHasAttribute(selectedAttr
))
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
))
124 HTMLOptionElement
* optionElement
= toHTMLOptionElement(item
);
125 if (optionElement
->fastHasAttribute(selectedAttr
)) {
126 // The page specified the option to select.
127 initialSelected
= optionElement
;
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
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
));
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())
166 HTMLFormControlElement
& control
= toHTMLFormControlElement(*item
);
168 if (control
.isDisabledFormControl() || control
.name().isNull())
171 if (!isInDefaultState(control
) || isHTMLTextAreaElement(control
))
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
179 if (input
.type() == InputTypeNames::file
|| input
.type() == InputTypeNames::password
)
182 if (input
.isTextField()) {
184 // The auto-complete bar only knows how to fill in one
185 // value. This form has multiple fields; don't treat it as
189 textElement
= toHTMLInputElement(&control
);
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())
209 HTMLFormControlElement
& control
= toHTMLFormControlElement(*item
);
210 if (control
.isDisabledFormControl() || control
.name().isNull())
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;
225 FormDataEncoder::encodeStringAsFormData(*encodedString
, entry
->value());
229 return isElementFound
;
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
))
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.
254 // Look for a suitable search text field in the form when a
255 // selectedInputElement is not provided.
257 inputElement
= findSuitableSearchInputElement(*formElement
);
259 // Return if no suitable text element has been found.
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
)
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());
287 m_encoding
= String(encoding
.name());