1 | /* |
2 | * Copyright (C) 1999 Lars Knoll (knoll@kde.org) |
3 | * (C) 1999 Antti Koivisto (koivisto@kde.org) |
4 | * (C) 2001 Dirk Mueller (mueller@kde.org) |
5 | * Copyright (C) 2004-2010, 2012-2016 Apple Inc. All rights reserved. |
6 | * (C) 2006 Alexey Proskuryakov (ap@nypop.com) |
7 | * |
8 | * This library is free software; you can redistribute it and/or |
9 | * modify it under the terms of the GNU Library General Public |
10 | * License as published by the Free Software Foundation; either |
11 | * version 2 of the License, or (at your option) any later version. |
12 | * |
13 | * This library is distributed in the hope that it will be useful, |
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
16 | * Library General Public License for more details. |
17 | * |
18 | * You should have received a copy of the GNU Library General Public License |
19 | * along with this library; see the file COPYING.LIB. If not, write to |
20 | * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, |
21 | * Boston, MA 02110-1301, USA. |
22 | * |
23 | */ |
24 | |
25 | #include "config.h" |
26 | #include "HTMLFormElement.h" |
27 | |
28 | #include "DOMFormData.h" |
29 | #include "DOMWindow.h" |
30 | #include "Document.h" |
31 | #include "ElementIterator.h" |
32 | #include "Event.h" |
33 | #include "EventNames.h" |
34 | #include "FormController.h" |
35 | #include "FormData.h" |
36 | #include "Frame.h" |
37 | #include "FrameLoader.h" |
38 | #include "FrameLoaderClient.h" |
39 | #include "HTMLFieldSetElement.h" |
40 | #include "HTMLFormControlsCollection.h" |
41 | #include "HTMLImageElement.h" |
42 | #include "HTMLInputElement.h" |
43 | #include "HTMLNames.h" |
44 | #include "HTMLObjectElement.h" |
45 | #include "HTMLTableElement.h" |
46 | #include "NodeRareData.h" |
47 | #include "Page.h" |
48 | #include "RadioNodeList.h" |
49 | #include "RenderTextControl.h" |
50 | #include "ScriptDisallowedScope.h" |
51 | #include "Settings.h" |
52 | #include "UserGestureIndicator.h" |
53 | #include <limits> |
54 | #include <wtf/IsoMallocInlines.h> |
55 | #include <wtf/Ref.h> |
56 | #include <wtf/SetForScope.h> |
57 | |
58 | namespace WebCore { |
59 | |
60 | WTF_MAKE_ISO_ALLOCATED_IMPL(HTMLFormElement); |
61 | |
62 | using namespace HTMLNames; |
63 | |
64 | HTMLFormElement::HTMLFormElement(const QualifiedName& tagName, Document& document) |
65 | : HTMLElement(tagName, document) |
66 | { |
67 | ASSERT(hasTagName(formTag)); |
68 | } |
69 | |
70 | Ref<HTMLFormElement> HTMLFormElement::create(Document& document) |
71 | { |
72 | return adoptRef(*new HTMLFormElement(formTag, document)); |
73 | } |
74 | |
75 | Ref<HTMLFormElement> HTMLFormElement::create(const QualifiedName& tagName, Document& document) |
76 | { |
77 | return adoptRef(*new HTMLFormElement(tagName, document)); |
78 | } |
79 | |
80 | HTMLFormElement::~HTMLFormElement() |
81 | { |
82 | document().formController().willDeleteForm(*this); |
83 | if (!shouldAutocomplete()) |
84 | document().unregisterForDocumentSuspensionCallbacks(*this); |
85 | |
86 | m_defaultButton = nullptr; |
87 | for (auto& associatedElement : m_associatedElements) |
88 | associatedElement->formWillBeDestroyed(); |
89 | for (auto& imageElement : m_imageElements) |
90 | imageElement->m_form = nullptr; |
91 | } |
92 | |
93 | bool HTMLFormElement::formWouldHaveSecureSubmission(const String& url) |
94 | { |
95 | return document().completeURL(url).protocolIs("https" ); |
96 | } |
97 | |
98 | bool HTMLFormElement::rendererIsNeeded(const RenderStyle& style) |
99 | { |
100 | if (!m_wasDemoted) |
101 | return HTMLElement::rendererIsNeeded(style); |
102 | |
103 | auto parent = parentNode(); |
104 | auto parentRenderer = parent->renderer(); |
105 | |
106 | if (!parentRenderer) |
107 | return false; |
108 | |
109 | // FIXME: Shouldn't we also check for table caption (see |formIsTablePart| below). |
110 | bool parentIsTableElementPart = (parentRenderer->isTable() && is<HTMLTableElement>(*parent)) |
111 | || (parentRenderer->isTableRow() && parent->hasTagName(trTag)) |
112 | || (parentRenderer->isTableSection() && parent->hasTagName(tbodyTag)) |
113 | || (parentRenderer->isRenderTableCol() && parent->hasTagName(colTag)) |
114 | || (parentRenderer->isTableCell() && parent->hasTagName(trTag)); |
115 | |
116 | if (!parentIsTableElementPart) |
117 | return true; |
118 | |
119 | DisplayType display = style.display(); |
120 | bool formIsTablePart = display == DisplayType::Table || display == DisplayType::InlineTable || display == DisplayType::TableRowGroup |
121 | || display == DisplayType::TableHeaderGroup || display == DisplayType::TableFooterGroup || display == DisplayType::TableRow |
122 | || display == DisplayType::TableColumnGroup || display == DisplayType::TableColumn || display == DisplayType::TableCell |
123 | || display == DisplayType::TableCaption; |
124 | |
125 | return formIsTablePart; |
126 | } |
127 | |
128 | Node::InsertedIntoAncestorResult HTMLFormElement::insertedIntoAncestor(InsertionType insertionType, ContainerNode& parentOfInsertedTree) |
129 | { |
130 | HTMLElement::insertedIntoAncestor(insertionType, parentOfInsertedTree); |
131 | if (insertionType.connectedToDocument) |
132 | document().didAssociateFormControl(*this); |
133 | return InsertedIntoAncestorResult::Done; |
134 | } |
135 | |
136 | void HTMLFormElement::removedFromAncestor(RemovalType removalType, ContainerNode& oldParentOfRemovedTree) |
137 | { |
138 | Node& root = traverseToRootNode(); // Do not rely on rootNode() because our IsInTreeScope is outdated. |
139 | Vector<FormAssociatedElement*> associatedElements(m_associatedElements); |
140 | for (auto& associatedElement : associatedElements) |
141 | associatedElement->formOwnerRemovedFromTree(root); |
142 | HTMLElement::removedFromAncestor(removalType, oldParentOfRemovedTree); |
143 | } |
144 | |
145 | void HTMLFormElement::handleLocalEvents(Event& event, EventInvokePhase phase) |
146 | { |
147 | if (event.eventPhase() != Event::CAPTURING_PHASE && is<Node>(event.target()) && event.target() != this && (event.type() == eventNames().submitEvent || event.type() == eventNames().resetEvent)) { |
148 | event.stopPropagation(); |
149 | return; |
150 | } |
151 | HTMLElement::handleLocalEvents(event, phase); |
152 | } |
153 | |
154 | unsigned HTMLFormElement::length() const |
155 | { |
156 | unsigned length = 0; |
157 | for (auto& associatedElement : m_associatedElements) { |
158 | if (associatedElement->isEnumeratable()) |
159 | ++length; |
160 | } |
161 | return length; |
162 | } |
163 | |
164 | HTMLElement* HTMLFormElement::item(unsigned index) |
165 | { |
166 | return elements()->item(index); |
167 | } |
168 | |
169 | Optional<Variant<RefPtr<RadioNodeList>, RefPtr<Element>>> HTMLFormElement::namedItem(const AtomicString& name) |
170 | { |
171 | auto namedItems = namedElements(name); |
172 | |
173 | if (namedItems.isEmpty()) |
174 | return WTF::nullopt; |
175 | if (namedItems.size() == 1) |
176 | return Variant<RefPtr<RadioNodeList>, RefPtr<Element>> { RefPtr<Element> { WTFMove(namedItems[0]) } }; |
177 | |
178 | return Variant<RefPtr<RadioNodeList>, RefPtr<Element>> { RefPtr<RadioNodeList> { radioNodeList(name) } }; |
179 | } |
180 | |
181 | Vector<AtomicString> HTMLFormElement::supportedPropertyNames() const |
182 | { |
183 | // FIXME: Should be implemented (only needed for enumeration with includeDontEnumProperties mode |
184 | // since this class is annotated with LegacyUnenumerableNamedProperties). |
185 | return { }; |
186 | } |
187 | |
188 | void HTMLFormElement::submitImplicitly(Event& event, bool fromImplicitSubmissionTrigger) |
189 | { |
190 | unsigned submissionTriggerCount = 0; |
191 | for (auto& formAssociatedElement : m_associatedElements) { |
192 | if (!is<HTMLFormControlElement>(*formAssociatedElement)) |
193 | continue; |
194 | HTMLFormControlElement& formElement = downcast<HTMLFormControlElement>(*formAssociatedElement); |
195 | if (formElement.isSuccessfulSubmitButton()) { |
196 | if (formElement.renderer()) { |
197 | formElement.dispatchSimulatedClick(&event); |
198 | return; |
199 | } |
200 | } else if (formElement.canTriggerImplicitSubmission()) |
201 | ++submissionTriggerCount; |
202 | } |
203 | |
204 | if (!submissionTriggerCount) |
205 | return; |
206 | |
207 | // Older iOS apps using WebViews expect the behavior of auto submitting multi-input forms. |
208 | if (fromImplicitSubmissionTrigger && (submissionTriggerCount == 1 || document().settings().allowMultiElementImplicitSubmission())) |
209 | prepareForSubmission(event); |
210 | } |
211 | |
212 | bool HTMLFormElement::validateInteractively() |
213 | { |
214 | for (auto& associatedElement : m_associatedElements) { |
215 | if (is<HTMLFormControlElement>(*associatedElement)) |
216 | downcast<HTMLFormControlElement>(*associatedElement).hideVisibleValidationMessage(); |
217 | } |
218 | |
219 | Vector<RefPtr<HTMLFormControlElement>> unhandledInvalidControls; |
220 | if (!checkInvalidControlsAndCollectUnhandled(unhandledInvalidControls)) |
221 | return true; |
222 | // Because the form has invalid controls, we abort the form submission and |
223 | // show a validation message on a focusable form control. |
224 | |
225 | // Make sure layout is up-to-date in case we call isFocusable() (which |
226 | // has !renderer()->needsLayout() assertion). |
227 | ASSERT(!document().view() || !document().view()->needsLayout()); |
228 | |
229 | Ref<HTMLFormElement> protectedThis(*this); |
230 | |
231 | // Focus on the first focusable control and show a validation message. |
232 | for (auto& control : unhandledInvalidControls) { |
233 | if (control->isConnected() && control->isFocusable()) { |
234 | control->focusAndShowValidationMessage(); |
235 | break; |
236 | } |
237 | } |
238 | |
239 | // Warn about all of unfocusable controls. |
240 | if (document().frame()) { |
241 | for (auto& control : unhandledInvalidControls) { |
242 | if (control->isConnected() && control->isFocusable()) |
243 | continue; |
244 | String message = makeString("An invalid form control with name='" , control->name(), "' is not focusable." ); |
245 | document().addConsoleMessage(MessageSource::Rendering, MessageLevel::Error, message); |
246 | } |
247 | } |
248 | |
249 | return false; |
250 | } |
251 | |
252 | void HTMLFormElement::prepareForSubmission(Event& event) |
253 | { |
254 | if (!isConnected()) |
255 | return; |
256 | |
257 | RefPtr<Frame> frame = document().frame(); |
258 | if (m_isSubmittingOrPreparingForSubmission || !frame) |
259 | return; |
260 | |
261 | m_isSubmittingOrPreparingForSubmission = true; |
262 | m_shouldSubmit = false; |
263 | |
264 | bool shouldValidate = document().page() && document().page()->settings().interactiveFormValidationEnabled() && !noValidate(); |
265 | |
266 | if (shouldValidate) { |
267 | auto submitElement = findSubmitButton(&event); |
268 | if (submitElement && submitElement->formNoValidate()) |
269 | shouldValidate = false; |
270 | } |
271 | |
272 | // Interactive validation must be done before dispatching the submit event. |
273 | if (shouldValidate && !validateInteractively()) { |
274 | m_isSubmittingOrPreparingForSubmission = false; |
275 | return; |
276 | } |
277 | |
278 | auto targetFrame = frame->loader().findFrameForNavigation(effectiveTarget(&event), &document()); |
279 | if (!targetFrame) |
280 | targetFrame = frame.get(); |
281 | auto formState = FormState::create(*this, textFieldValues(), document(), NotSubmittedByJavaScript); |
282 | targetFrame->loader().client().dispatchWillSendSubmitEvent(WTFMove(formState)); |
283 | |
284 | auto protectedThis = makeRef(*this); |
285 | |
286 | auto submitEvent = Event::create(eventNames().submitEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes); |
287 | dispatchEvent(submitEvent); |
288 | |
289 | // Event handling could have resulted in m_shouldSubmit becoming true as a side effect, too. |
290 | if (!submitEvent->defaultPrevented()) |
291 | m_shouldSubmit = true; |
292 | |
293 | m_isSubmittingOrPreparingForSubmission = false; |
294 | |
295 | if (m_shouldSubmit) |
296 | submit(&event, true, true, NotSubmittedByJavaScript); |
297 | } |
298 | |
299 | void HTMLFormElement::submit() |
300 | { |
301 | submit(nullptr, false, true, NotSubmittedByJavaScript); |
302 | } |
303 | |
304 | void HTMLFormElement::submitFromJavaScript() |
305 | { |
306 | submit(nullptr, false, UserGestureIndicator::processingUserGesture(), SubmittedByJavaScript); |
307 | } |
308 | |
309 | StringPairVector HTMLFormElement::textFieldValues() const |
310 | { |
311 | StringPairVector result; |
312 | result.reserveInitialCapacity(m_associatedElements.size()); |
313 | for (auto& associatedElement : m_associatedElements) { |
314 | auto& element = associatedElement->asHTMLElement(); |
315 | if (!is<HTMLInputElement>(element)) |
316 | continue; |
317 | auto& input = downcast<HTMLInputElement>(element); |
318 | if (!input.isTextField()) |
319 | continue; |
320 | result.uncheckedAppend({ input.name().string(), input.value() }); |
321 | } |
322 | return result; |
323 | } |
324 | |
325 | void HTMLFormElement::submit(Event* event, bool activateSubmitButton, bool processingUserGesture, FormSubmissionTrigger formSubmissionTrigger) |
326 | { |
327 | RefPtr<FrameView> view = document().view(); |
328 | RefPtr<Frame> frame = document().frame(); |
329 | if (!view || !frame) |
330 | return; |
331 | |
332 | if (m_isSubmittingOrPreparingForSubmission) { |
333 | m_shouldSubmit = true; |
334 | return; |
335 | } |
336 | |
337 | m_isSubmittingOrPreparingForSubmission = true; |
338 | m_wasUserSubmitted = processingUserGesture; |
339 | |
340 | RefPtr<HTMLFormControlElement> firstSuccessfulSubmitButton; |
341 | bool needButtonActivation = activateSubmitButton; // do we need to activate a submit button? |
342 | |
343 | for (auto& associatedElement : m_associatedElements) { |
344 | if (!is<HTMLFormControlElement>(*associatedElement)) |
345 | continue; |
346 | if (needButtonActivation) { |
347 | HTMLFormControlElement& control = downcast<HTMLFormControlElement>(*associatedElement); |
348 | if (control.isActivatedSubmit()) |
349 | needButtonActivation = false; |
350 | else if (!firstSuccessfulSubmitButton && control.isSuccessfulSubmitButton()) |
351 | firstSuccessfulSubmitButton = &control; |
352 | } |
353 | } |
354 | |
355 | if (needButtonActivation && firstSuccessfulSubmitButton) |
356 | firstSuccessfulSubmitButton->setActivatedSubmit(true); |
357 | |
358 | auto protectedThis = makeRef(*this); // Form submission can execute arbitary JavaScript. |
359 | |
360 | auto shouldLockHistory = processingUserGesture ? LockHistory::No : LockHistory::Yes; |
361 | frame->loader().submitForm(FormSubmission::create(*this, m_attributes, event, shouldLockHistory, formSubmissionTrigger)); |
362 | |
363 | if (needButtonActivation && firstSuccessfulSubmitButton) |
364 | firstSuccessfulSubmitButton->setActivatedSubmit(false); |
365 | |
366 | m_shouldSubmit = false; |
367 | m_isSubmittingOrPreparingForSubmission = false; |
368 | } |
369 | |
370 | void HTMLFormElement::reset() |
371 | { |
372 | if (m_isInResetFunction) |
373 | return; |
374 | |
375 | RefPtr<Frame> protectedFrame = document().frame(); |
376 | if (!protectedFrame) |
377 | return; |
378 | |
379 | Ref<HTMLFormElement> protectedThis(*this); |
380 | |
381 | SetForScope<bool> isInResetFunctionRestorer(m_isInResetFunction, true); |
382 | |
383 | auto event = Event::create(eventNames().resetEvent, Event::CanBubble::Yes, Event::IsCancelable::Yes); |
384 | dispatchEvent(event); |
385 | if (!event->defaultPrevented()) |
386 | resetAssociatedFormControlElements(); |
387 | } |
388 | |
389 | void HTMLFormElement::resetAssociatedFormControlElements() |
390 | { |
391 | // Event handling can cause associated elements to be added or deleted while iterating |
392 | // over this collection. Protect these elements until we are done notifying them of |
393 | // the reset operation. |
394 | Vector<Ref<HTMLFormControlElement>> associatedFormControlElements; |
395 | associatedFormControlElements.reserveInitialCapacity(m_associatedElements.size()); |
396 | for (auto* element : m_associatedElements) { |
397 | if (is<HTMLFormControlElement>(element)) |
398 | associatedFormControlElements.uncheckedAppend(*downcast<HTMLFormControlElement>(element)); |
399 | } |
400 | |
401 | for (auto& associatedFormControlElement : associatedFormControlElements) |
402 | associatedFormControlElement->reset(); |
403 | } |
404 | |
405 | #if ENABLE(IOS_AUTOCORRECT_AND_AUTOCAPITALIZE) |
406 | |
407 | // FIXME: We should look to share this code with class HTMLFormControlElement instead of duplicating the logic. |
408 | |
409 | bool HTMLFormElement::shouldAutocorrect() const |
410 | { |
411 | const AtomicString& autocorrectValue = attributeWithoutSynchronization(autocorrectAttr); |
412 | if (!autocorrectValue.isEmpty()) |
413 | return !equalLettersIgnoringASCIICase(autocorrectValue, "off" ); |
414 | if (RefPtr<HTMLFormElement> form = this->form()) |
415 | return form->shouldAutocorrect(); |
416 | return true; |
417 | } |
418 | |
419 | #endif |
420 | |
421 | void HTMLFormElement::parseAttribute(const QualifiedName& name, const AtomicString& value) |
422 | { |
423 | if (name == actionAttr) { |
424 | m_attributes.parseAction(value); |
425 | |
426 | if (!m_attributes.action().isEmpty()) { |
427 | if (RefPtr<Frame> f = document().frame()) { |
428 | Frame& topFrame = f->tree().top(); |
429 | topFrame.loader().mixedContentChecker().checkFormForMixedContent(topFrame.document()->securityOrigin(), document().completeURL(m_attributes.action())); |
430 | } |
431 | } |
432 | } else if (name == targetAttr) |
433 | m_attributes.setTarget(value); |
434 | else if (name == methodAttr) |
435 | m_attributes.updateMethodType(value); |
436 | else if (name == enctypeAttr) |
437 | m_attributes.updateEncodingType(value); |
438 | else if (name == accept_charsetAttr) |
439 | m_attributes.setAcceptCharset(value); |
440 | else if (name == autocompleteAttr) { |
441 | if (!shouldAutocomplete()) |
442 | document().registerForDocumentSuspensionCallbacks(*this); |
443 | else |
444 | document().unregisterForDocumentSuspensionCallbacks(*this); |
445 | } else |
446 | HTMLElement::parseAttribute(name, value); |
447 | } |
448 | |
449 | unsigned HTMLFormElement::formElementIndexWithFormAttribute(Element* element, unsigned rangeStart, unsigned rangeEnd) |
450 | { |
451 | if (m_associatedElements.isEmpty()) |
452 | return 0; |
453 | |
454 | ASSERT(rangeStart <= rangeEnd); |
455 | |
456 | if (rangeStart == rangeEnd) |
457 | return rangeStart; |
458 | |
459 | unsigned left = rangeStart; |
460 | unsigned right = rangeEnd - 1; |
461 | unsigned short position; |
462 | |
463 | // Does binary search on m_associatedElements in order to find the index |
464 | // to be inserted. |
465 | while (left != right) { |
466 | unsigned middle = left + ((right - left) / 2); |
467 | ASSERT(middle < m_associatedElementsBeforeIndex || middle >= m_associatedElementsAfterIndex); |
468 | position = element->compareDocumentPosition(m_associatedElements[middle]->asHTMLElement()); |
469 | if (position & DOCUMENT_POSITION_FOLLOWING) |
470 | right = middle; |
471 | else |
472 | left = middle + 1; |
473 | } |
474 | |
475 | ASSERT(left < m_associatedElementsBeforeIndex || left >= m_associatedElementsAfterIndex); |
476 | position = element->compareDocumentPosition(m_associatedElements[left]->asHTMLElement()); |
477 | if (position & DOCUMENT_POSITION_FOLLOWING) |
478 | return left; |
479 | return left + 1; |
480 | } |
481 | |
482 | unsigned HTMLFormElement::formElementIndex(FormAssociatedElement* associatedElement) |
483 | { |
484 | ASSERT(associatedElement); |
485 | |
486 | HTMLElement& associatedHTMLElement = associatedElement->asHTMLElement(); |
487 | |
488 | // Treats separately the case where this element has the form attribute |
489 | // for performance consideration. |
490 | if (associatedHTMLElement.hasAttributeWithoutSynchronization(formAttr) && associatedHTMLElement.isConnected()) { |
491 | unsigned short position = compareDocumentPosition(associatedHTMLElement); |
492 | ASSERT_WITH_SECURITY_IMPLICATION(!(position & DOCUMENT_POSITION_DISCONNECTED)); |
493 | if (position & DOCUMENT_POSITION_PRECEDING) { |
494 | ++m_associatedElementsBeforeIndex; |
495 | ++m_associatedElementsAfterIndex; |
496 | return HTMLFormElement::formElementIndexWithFormAttribute(&associatedHTMLElement, 0, m_associatedElementsBeforeIndex - 1); |
497 | } |
498 | if (position & DOCUMENT_POSITION_FOLLOWING && !(position & DOCUMENT_POSITION_CONTAINED_BY)) |
499 | return HTMLFormElement::formElementIndexWithFormAttribute(&associatedHTMLElement, m_associatedElementsAfterIndex, m_associatedElements.size()); |
500 | } |
501 | |
502 | unsigned currentAssociatedElementsAfterIndex = m_associatedElementsAfterIndex; |
503 | ++m_associatedElementsAfterIndex; |
504 | |
505 | if (!associatedHTMLElement.isDescendantOf(*this)) |
506 | return currentAssociatedElementsAfterIndex; |
507 | |
508 | // Check for the special case where this element is the very last thing in |
509 | // the form's tree of children; we don't want to walk the entire tree in that |
510 | // common case that occurs during parsing; instead we'll just return a value |
511 | // that says "add this form element to the end of the array". |
512 | auto descendants = descendantsOfType<HTMLElement>(*this); |
513 | auto it = descendants.beginAt(associatedHTMLElement); |
514 | auto end = descendants.end(); |
515 | if (++it == end) |
516 | return currentAssociatedElementsAfterIndex; |
517 | |
518 | unsigned i = m_associatedElementsBeforeIndex; |
519 | for (auto& element : descendants) { |
520 | if (&element == &associatedHTMLElement) |
521 | return i; |
522 | if (!is<HTMLFormControlElement>(element) && !is<HTMLObjectElement>(element)) |
523 | continue; |
524 | if (element.form() != this) |
525 | continue; |
526 | ++i; |
527 | } |
528 | return currentAssociatedElementsAfterIndex; |
529 | } |
530 | |
531 | void HTMLFormElement::registerFormElement(FormAssociatedElement* e) |
532 | { |
533 | m_associatedElements.insert(formElementIndex(e), e); |
534 | |
535 | if (is<HTMLFormControlElement>(e)) { |
536 | HTMLFormControlElement& control = downcast<HTMLFormControlElement>(*e); |
537 | if (control.isSuccessfulSubmitButton()) { |
538 | if (!m_defaultButton) |
539 | control.invalidateStyleForSubtree(); |
540 | else |
541 | resetDefaultButton(); |
542 | } |
543 | } |
544 | } |
545 | |
546 | void HTMLFormElement::removeFormElement(FormAssociatedElement* e) |
547 | { |
548 | unsigned index = m_associatedElements.find(e); |
549 | ASSERT_WITH_SECURITY_IMPLICATION(index < m_associatedElements.size()); |
550 | if (index < m_associatedElementsBeforeIndex) |
551 | --m_associatedElementsBeforeIndex; |
552 | if (index < m_associatedElementsAfterIndex) |
553 | --m_associatedElementsAfterIndex; |
554 | removeFromPastNamesMap(e); |
555 | m_associatedElements.remove(index); |
556 | |
557 | if (auto* nodeLists = this->nodeLists()) |
558 | nodeLists->invalidateCaches(); |
559 | |
560 | if (e == m_defaultButton) |
561 | resetDefaultButton(); |
562 | } |
563 | |
564 | void HTMLFormElement::registerInvalidAssociatedFormControl(const HTMLFormControlElement& formControlElement) |
565 | { |
566 | ASSERT_WITH_MESSAGE(!is<HTMLFieldSetElement>(formControlElement), "FieldSet are never candidates for constraint validation." ); |
567 | ASSERT(static_cast<const Element&>(formControlElement).matchesInvalidPseudoClass()); |
568 | |
569 | if (m_invalidAssociatedFormControls.computesEmpty()) |
570 | invalidateStyleForSubtree(); |
571 | m_invalidAssociatedFormControls.add(const_cast<HTMLFormControlElement&>(formControlElement)); |
572 | } |
573 | |
574 | void HTMLFormElement::removeInvalidAssociatedFormControlIfNeeded(const HTMLFormControlElement& formControlElement) |
575 | { |
576 | if (m_invalidAssociatedFormControls.remove(formControlElement)) { |
577 | if (m_invalidAssociatedFormControls.computesEmpty()) |
578 | invalidateStyleForSubtree(); |
579 | } |
580 | } |
581 | |
582 | bool HTMLFormElement::isURLAttribute(const Attribute& attribute) const |
583 | { |
584 | return attribute.name() == actionAttr || HTMLElement::isURLAttribute(attribute); |
585 | } |
586 | |
587 | void HTMLFormElement::registerImgElement(HTMLImageElement* e) |
588 | { |
589 | ASSERT(m_imageElements.find(e) == notFound); |
590 | m_imageElements.append(makeWeakPtr(e)); |
591 | } |
592 | |
593 | void HTMLFormElement::removeImgElement(HTMLImageElement* e) |
594 | { |
595 | removeFromPastNamesMap(e); |
596 | bool removed = m_imageElements.removeFirst(e); |
597 | ASSERT_UNUSED(removed, removed); |
598 | } |
599 | |
600 | Ref<HTMLFormControlsCollection> HTMLFormElement::elements() |
601 | { |
602 | return ensureRareData().ensureNodeLists().addCachedCollection<HTMLFormControlsCollection>(*this, FormControls); |
603 | } |
604 | |
605 | Ref<HTMLCollection> HTMLFormElement::elementsForNativeBindings() |
606 | { |
607 | return elements(); |
608 | } |
609 | |
610 | String HTMLFormElement::name() const |
611 | { |
612 | return getNameAttribute(); |
613 | } |
614 | |
615 | bool HTMLFormElement::noValidate() const |
616 | { |
617 | return hasAttributeWithoutSynchronization(novalidateAttr); |
618 | } |
619 | |
620 | // FIXME: This function should be removed because it does not do the same thing as the |
621 | // JavaScript binding for action, which treats action as a URL attribute. Last time I |
622 | // (Darin Adler) removed this, someone added it back, so I am leaving it in for now. |
623 | String HTMLFormElement::action() const |
624 | { |
625 | return attributeWithoutSynchronization(actionAttr); |
626 | } |
627 | |
628 | void HTMLFormElement::setAction(const String &value) |
629 | { |
630 | setAttributeWithoutSynchronization(actionAttr, value); |
631 | } |
632 | |
633 | void HTMLFormElement::setEnctype(const String &value) |
634 | { |
635 | setAttributeWithoutSynchronization(enctypeAttr, value); |
636 | } |
637 | |
638 | String HTMLFormElement::method() const |
639 | { |
640 | return FormSubmission::Attributes::methodString(m_attributes.method()); |
641 | } |
642 | |
643 | void HTMLFormElement::setMethod(const String &value) |
644 | { |
645 | setAttributeWithoutSynchronization(methodAttr, value); |
646 | } |
647 | |
648 | String HTMLFormElement::target() const |
649 | { |
650 | return attributeWithoutSynchronization(targetAttr); |
651 | } |
652 | |
653 | String HTMLFormElement::effectiveTarget(const Event* event) const |
654 | { |
655 | if (auto* submitButton = findSubmitButton(event)) { |
656 | auto targetValue = submitButton->attributeWithoutSynchronization(formtargetAttr); |
657 | if (!targetValue.isNull()) |
658 | return targetValue; |
659 | } |
660 | |
661 | auto targetValue = target(); |
662 | if (!targetValue.isNull()) |
663 | return targetValue; |
664 | |
665 | return document().baseTarget(); |
666 | } |
667 | |
668 | bool HTMLFormElement::wasUserSubmitted() const |
669 | { |
670 | return m_wasUserSubmitted; |
671 | } |
672 | |
673 | HTMLFormControlElement* HTMLFormElement::findSubmitButton(const Event* event) const |
674 | { |
675 | if (!event || !is<Node>(event->target())) |
676 | return nullptr; |
677 | auto& node = downcast<Node>(*event->target()); |
678 | auto* element = is<Element>(node) ? &downcast<Element>(node) : node.parentElement(); |
679 | return element ? lineageOfType<HTMLFormControlElement>(*element).first() : nullptr; |
680 | } |
681 | |
682 | HTMLFormControlElement* HTMLFormElement::defaultButton() const |
683 | { |
684 | if (m_defaultButton) |
685 | return m_defaultButton.get(); |
686 | for (auto& associatedElement : m_associatedElements) { |
687 | if (!is<HTMLFormControlElement>(*associatedElement)) |
688 | continue; |
689 | HTMLFormControlElement& control = downcast<HTMLFormControlElement>(*associatedElement); |
690 | if (control.isSuccessfulSubmitButton()) { |
691 | m_defaultButton = makeWeakPtr(control); |
692 | return &control; |
693 | } |
694 | } |
695 | return nullptr; |
696 | } |
697 | |
698 | void HTMLFormElement::resetDefaultButton() |
699 | { |
700 | if (!m_defaultButton) { |
701 | // Computing the default button is not cheap, we don't want to do it unless needed. |
702 | // If there was no default button set, the only style to invalidate is the element |
703 | // being added to the form. This is done explicitely in registerFormElement(). |
704 | return; |
705 | } |
706 | |
707 | ScriptDisallowedScope::InMainThread scriptDisallowedScope; |
708 | |
709 | auto oldDefault = WTFMove(m_defaultButton); |
710 | defaultButton(); |
711 | if (m_defaultButton != oldDefault) { |
712 | if (oldDefault) |
713 | oldDefault->invalidateStyleForSubtree(); |
714 | if (m_defaultButton) |
715 | m_defaultButton->invalidateStyleForSubtree(); |
716 | } |
717 | } |
718 | |
719 | bool HTMLFormElement::checkValidity() |
720 | { |
721 | Vector<RefPtr<HTMLFormControlElement>> controls; |
722 | return !checkInvalidControlsAndCollectUnhandled(controls); |
723 | } |
724 | |
725 | bool HTMLFormElement::checkInvalidControlsAndCollectUnhandled(Vector<RefPtr<HTMLFormControlElement>>& unhandledInvalidControls) |
726 | { |
727 | Ref<HTMLFormElement> protectedThis(*this); |
728 | // Copy m_associatedElements because event handlers called from |
729 | // HTMLFormControlElement::checkValidity() might change m_associatedElements. |
730 | Vector<RefPtr<FormAssociatedElement>> elements; |
731 | elements.reserveCapacity(m_associatedElements.size()); |
732 | for (auto& associatedElement : m_associatedElements) |
733 | elements.append(associatedElement); |
734 | bool hasInvalidControls = false; |
735 | for (auto& element : elements) { |
736 | if (element->form() == this && is<HTMLFormControlElement>(*element)) { |
737 | HTMLFormControlElement& control = downcast<HTMLFormControlElement>(*element); |
738 | if (!control.checkValidity(&unhandledInvalidControls) && control.form() == this) |
739 | hasInvalidControls = true; |
740 | } |
741 | } |
742 | return hasInvalidControls; |
743 | } |
744 | |
745 | bool HTMLFormElement::reportValidity() |
746 | { |
747 | Ref<HTMLFormElement> protectedThis(*this); |
748 | |
749 | // Update layout before processing form actions in case the style changes |
750 | // the Form or button relationships. |
751 | document().updateLayoutIgnorePendingStylesheets(); |
752 | |
753 | return validateInteractively(); |
754 | } |
755 | |
756 | #ifndef NDEBUG |
757 | void HTMLFormElement::assertItemCanBeInPastNamesMap(FormNamedItem* item) const |
758 | { |
759 | ASSERT_WITH_SECURITY_IMPLICATION(item); |
760 | HTMLElement& element = item->asHTMLElement(); |
761 | ASSERT_WITH_SECURITY_IMPLICATION(element.form() == this); |
762 | |
763 | if (item->isFormAssociatedElement()) { |
764 | ASSERT_WITH_SECURITY_IMPLICATION(m_associatedElements.find(static_cast<FormAssociatedElement*>(item)) != notFound); |
765 | return; |
766 | } |
767 | |
768 | ASSERT_WITH_SECURITY_IMPLICATION(element.hasTagName(imgTag)); |
769 | ASSERT_WITH_SECURITY_IMPLICATION(m_imageElements.find(&downcast<HTMLImageElement>(element)) != notFound); |
770 | } |
771 | #else |
772 | inline void HTMLFormElement::assertItemCanBeInPastNamesMap(FormNamedItem*) const |
773 | { |
774 | } |
775 | #endif |
776 | |
777 | RefPtr<HTMLElement> HTMLFormElement::elementFromPastNamesMap(const AtomicString& pastName) const |
778 | { |
779 | if (pastName.isEmpty() || !m_pastNamesMap) |
780 | return nullptr; |
781 | FormNamedItem* item = m_pastNamesMap->get(pastName.impl()); |
782 | if (!item) |
783 | return nullptr; |
784 | assertItemCanBeInPastNamesMap(item); |
785 | return &item->asHTMLElement(); |
786 | } |
787 | |
788 | void HTMLFormElement::addToPastNamesMap(FormNamedItem* item, const AtomicString& pastName) |
789 | { |
790 | assertItemCanBeInPastNamesMap(item); |
791 | if (pastName.isEmpty()) |
792 | return; |
793 | if (!m_pastNamesMap) |
794 | m_pastNamesMap = std::make_unique<PastNamesMap>(); |
795 | m_pastNamesMap->set(pastName.impl(), item); |
796 | } |
797 | |
798 | void HTMLFormElement::removeFromPastNamesMap(FormNamedItem* item) |
799 | { |
800 | ASSERT(item); |
801 | if (!m_pastNamesMap) |
802 | return; |
803 | |
804 | for (auto& pastName : m_pastNamesMap->values()) { |
805 | if (pastName == item) |
806 | pastName = nullptr; // Keep looping. Single element can have multiple names. |
807 | } |
808 | } |
809 | |
810 | bool HTMLFormElement::matchesValidPseudoClass() const |
811 | { |
812 | return m_invalidAssociatedFormControls.computesEmpty(); |
813 | } |
814 | |
815 | bool HTMLFormElement::matchesInvalidPseudoClass() const |
816 | { |
817 | return !matchesValidPseudoClass(); |
818 | } |
819 | |
820 | // FIXME: Use Ref<HTMLElement> for the function result since there are no non-HTML elements returned here. |
821 | Vector<Ref<Element>> HTMLFormElement::namedElements(const AtomicString& name) |
822 | { |
823 | // http://www.whatwg.org/specs/web-apps/current-work/multipage/forms.html#dom-form-nameditem |
824 | Vector<Ref<Element>> namedItems = elements()->namedItems(name); |
825 | |
826 | auto elementFromPast = elementFromPastNamesMap(name); |
827 | if (namedItems.size() == 1 && namedItems.first().ptr() != elementFromPast) |
828 | addToPastNamesMap(downcast<HTMLElement>(namedItems.first().get()).asFormNamedItem(), name); |
829 | else if (elementFromPast && namedItems.isEmpty()) |
830 | namedItems.append(*elementFromPast); |
831 | |
832 | return namedItems; |
833 | } |
834 | |
835 | void HTMLFormElement::resumeFromDocumentSuspension() |
836 | { |
837 | ASSERT(!shouldAutocomplete()); |
838 | |
839 | Ref<HTMLFormElement> protectedThis(*this); |
840 | |
841 | resetAssociatedFormControlElements(); |
842 | } |
843 | |
844 | void HTMLFormElement::didMoveToNewDocument(Document& oldDocument, Document& newDocument) |
845 | { |
846 | if (!shouldAutocomplete()) { |
847 | oldDocument.unregisterForDocumentSuspensionCallbacks(*this); |
848 | newDocument.registerForDocumentSuspensionCallbacks(*this); |
849 | } |
850 | |
851 | HTMLElement::didMoveToNewDocument(oldDocument, newDocument); |
852 | } |
853 | |
854 | bool HTMLFormElement::shouldAutocomplete() const |
855 | { |
856 | return !equalLettersIgnoringASCIICase(attributeWithoutSynchronization(autocompleteAttr), "off" ); |
857 | } |
858 | |
859 | void HTMLFormElement::finishParsingChildren() |
860 | { |
861 | HTMLElement::finishParsingChildren(); |
862 | document().formController().restoreControlStateIn(*this); |
863 | } |
864 | |
865 | const Vector<FormAssociatedElement*>& HTMLFormElement::unsafeAssociatedElements() const |
866 | { |
867 | ASSERT(ScriptDisallowedScope::InMainThread::hasDisallowedScope()); |
868 | return m_associatedElements; |
869 | } |
870 | |
871 | Vector<Ref<FormAssociatedElement>> HTMLFormElement::copyAssociatedElementsVector() const |
872 | { |
873 | return WTF::map(m_associatedElements, [] (auto* rawElement) { |
874 | return Ref<FormAssociatedElement>(*rawElement); |
875 | }); |
876 | } |
877 | |
878 | void HTMLFormElement::copyNonAttributePropertiesFromElement(const Element& source) |
879 | { |
880 | m_wasDemoted = static_cast<const HTMLFormElement&>(source).m_wasDemoted; |
881 | HTMLElement::copyNonAttributePropertiesFromElement(source); |
882 | } |
883 | |
884 | HTMLFormElement* HTMLFormElement::findClosestFormAncestor(const Element& startElement) |
885 | { |
886 | return const_cast<HTMLFormElement*>(ancestorsOfType<HTMLFormElement>(startElement).first()); |
887 | } |
888 | |
889 | void HTMLFormElement::setAutocomplete(const AtomicString& value) |
890 | { |
891 | setAttributeWithoutSynchronization(autocompleteAttr, value); |
892 | } |
893 | |
894 | const AtomicString& HTMLFormElement::autocomplete() const |
895 | { |
896 | static NeverDestroyed<AtomicString> on("on" , AtomicString::ConstructFromLiteral); |
897 | static NeverDestroyed<AtomicString> off("off" , AtomicString::ConstructFromLiteral); |
898 | |
899 | return equalIgnoringASCIICase(attributeWithoutSynchronization(autocompleteAttr), "off" ) ? off : on; |
900 | } |
901 | |
902 | } // namespace |
903 | |