| 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 | |