/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
for (constauto& element : aInvalidElements) { bool isFocusable = false; // MOZ_KnownLive because 'aInvalidElements' is guaranteed to keep it alive. // This can go away once bug 1620312 is fixed.
focusManager->ElementIsFocusable(MOZ_KnownLive(element), 0, &isFocusable); if (!isFocusable) {
nsTArray<nsString> params;
nsAutoCString messageName("InvalidFormControlUnfocusable");
// https://html.spec.whatwg.org/multipage/forms.html#concept-form-submit void HTMLFormElement::MaybeSubmit(Element* aSubmitter) { #ifdef DEBUG if (aSubmitter) { constauto* fc = nsIFormControl::FromNode(aSubmitter);
MOZ_ASSERT(fc);
MOZ_ASSERT(fc->IsSubmitControl(), "aSubmitter is not a submit control?");
} #endif
// 5.1. If form's firing submission events is true, then return. if (mIsFiringSubmissionEvents) { return;
}
// 5.2. Set form's firing submission events to true.
AutoRestore<bool> resetFiringSubmissionEventsFlag(mIsFiringSubmissionEvents);
mIsFiringSubmissionEvents = true;
// Flag elements as user-interacted. // FIXME: Should be specified, see: // https://github.com/whatwg/html/issues/10066
{ for (nsGenericHTMLFormElement* el : mControls->mElements.AsList()) {
el->SetUserInteracted(true);
} for (nsGenericHTMLFormElement* el : mControls->mNotInElements.AsList()) {
el->SetUserInteracted(true);
}
}
// 5.3. If the submitter element's no-validate state is false, then // interactively validate the constraints of form and examine the result. // If the result is negative (i.e., the constraint validation concluded // that there were invalid fields and probably informed the user of this) bool noValidateState =
HasAttr(nsGkAtoms::novalidate) ||
(aSubmitter && aSubmitter->HasAttr(nsGkAtoms::formnovalidate)); if (!noValidateState && !CheckValidFormSubmission()) { return;
}
RefPtr<PresShell> presShell = doc->GetPresShell(); if (!presShell) { // We need the nsPresContext for dispatching the submit event. In some // rare cases we need to flush notifications to force creation of the // nsPresContext here (for example when a script calls form.requestSubmit() // from script early during page load). We only flush the notifications // if the PresShell hasn't been created yet, to limit the performance // impact.
doc->FlushPendingNotifications(FlushType::EnsurePresShellInitAndFrames);
presShell = doc->GetPresShell();
}
// If |PresShell::Destroy| has been called due to handling the event the pres // context will return a null pres shell. See bug 125624. Using presShell to // dispatch the event. It makes sure that event is not handled if the window // is being destroyed. if (presShell) {
SubmitEventInit init;
init.mBubbles = true;
init.mCancelable = true;
init.mSubmitter =
aSubmitter ? nsGenericHTMLElement::FromNode(aSubmitter) : nullptr;
RefPtr<SubmitEvent> event =
SubmitEvent::Constructor(this, u"submit"_ns, init);
event->SetTrusted(true);
nsEventStatus status = nsEventStatus_eIgnore;
presShell->HandleDOMEventWithTarget(this, event, &status);
}
}
void HTMLFormElement::MaybeReset(Element* aSubmitter) { // If |PresShell::Destroy| has been called due to handling the event the pres // context will return a null pres shell. See bug 125624. Using presShell to // dispatch the event. It makes sure that event is not handled if the window // is being destroyed. if (RefPtr<PresShell> presShell = OwnerDoc()->GetPresShell()) {
InternalFormEvent event(true, eFormReset);
event.mOriginator = aSubmitter;
nsEventStatus status = nsEventStatus_eIgnore;
presShell->HandleDOMEventWithTarget(this, &event, &status);
}
}
void HTMLFormElement::Submit(ErrorResult& aRv) { aRv = DoSubmit(); }
// 1.1. If submitter is not a submit button, then throw a TypeError. if (!fc || !fc->IsSubmitControl()) {
aRv.ThrowTypeError("The submitter is not a submit button."); return;
}
// 1.2. If submitter's form owner is not this form element, then throw a // "NotFoundError" DOMException. if (fc->GetForm() != this) {
aRv.ThrowNotFoundError("The submitter is not owned by this form."); return;
}
}
// 2. Otherwise, set submitter to this form element. // 3. Submit this form element, from submitter.
MaybeSubmit(aSubmitter);
}
if (IsInUncomposedDoc() && aContext.OwnerDoc().IsHTMLOrXHTML()) {
aContext.OwnerDoc().AsHTMLDocument()->AddedForm();
}
return rv;
}
template <typename T> staticvoid MarkOrphans(const nsTArray<T*>& aArray) {
uint32_t length = aArray.Length(); for (uint32_t i = 0; i < length; ++i) {
aArray[i]->SetFlags(MAYBE_ORPHAN_FORM_ELEMENT);
}
}
staticvoid CollectOrphans(nsINode* aRemovalRoot, const nsTArray<nsGenericHTMLFormElement*>& aArray #ifdef DEBUG
,
HTMLFormElement* aThisForm #endif
) { // Put a script blocker around all the notifications we're about to do.
nsAutoScriptBlocker scriptBlocker;
// Walk backwards so that if we remove elements we can just keep iterating
uint32_t length = aArray.Length(); for (uint32_t i = length; i > 0; --i) {
nsGenericHTMLFormElement* node = aArray[i - 1];
// Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the // node is in fact a descendant of the form and hence should stay in the // form. If it _is_ set, then we need to check whether the node is a // descendant of aRemovalRoot. If it is, we leave it in the form. #ifdef DEBUG bool removed = false; #endif if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT); if (!node->IsInclusiveDescendantOf(aRemovalRoot)) {
nsCOMPtr<nsIFormControl> fc = nsIFormControl::FromNode(node);
MOZ_ASSERT(fc);
fc->ClearForm(true, false); #ifdef DEBUG
removed = true; #endif
}
}
#ifdef DEBUG if (!removed) { constauto* fc = nsIFormControl::FromNode(node);
MOZ_ASSERT(fc);
HTMLFormElement* form = fc->GetForm();
NS_ASSERTION(form == aThisForm, "How did that happen?");
} #endif/* DEBUG */
}
}
staticvoid CollectOrphans(nsINode* aRemovalRoot, const nsTArray<HTMLImageElement*>& aArray #ifdef DEBUG
,
HTMLFormElement* aThisForm #endif
) { // Walk backwards so that if we remove elements we can just keep iterating
uint32_t length = aArray.Length(); for (uint32_t i = length; i > 0; --i) {
HTMLImageElement* node = aArray[i - 1];
// Now if MAYBE_ORPHAN_FORM_ELEMENT is not set, that would mean that the // node is in fact a descendant of the form and hence should stay in the // form. If it _is_ set, then we need to check whether the node is a // descendant of aRemovalRoot. If it is, we leave it in the form. #ifdef DEBUG bool removed = false; #endif if (node->HasFlag(MAYBE_ORPHAN_FORM_ELEMENT)) {
node->UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT); if (!node->IsInclusiveDescendantOf(aRemovalRoot)) {
node->ClearForm(true);
#ifdef DEBUG
removed = true; #endif
}
}
#ifdef DEBUG if (!removed) {
HTMLFormElement* form = node->GetForm();
NS_ASSERTION(form == aThisForm, "How did that happen?");
} #endif/* DEBUG */
}
}
// Note, this is explicitly using uncomposed doc, since we count // only forms in document.
RefPtr<Document> oldDocument = GetUncomposedDoc();
// Mark all of our controls as maybe being orphans
MarkOrphans(mControls->mElements.AsList());
MarkOrphans(mControls->mNotInElements.AsList());
MarkOrphans(mImageElements.AsList());
nsGenericHTMLElement::UnbindFromTree(aContext);
nsINode* ancestor = this;
nsINode* cur; do {
cur = ancestor->GetParentNode(); if (!cur) { break;
}
ancestor = cur;
} while (true);
CollectOrphans(ancestor, mControls->mElements #ifdef DEBUG
, this #endif
);
CollectOrphans(ancestor, mControls->mNotInElements #ifdef DEBUG
, this #endif
);
CollectOrphans(ancestor, mImageElements #ifdef DEBUG
, this #endif
);
if (oldDocument && oldDocument->IsHTMLOrXHTML()) {
oldDocument->AsHTMLDocument()->RemovedForm();
}
ForgetCurrentSubmission();
}
staticbool CanSubmit(WidgetEvent& aEvent) { // According to the UI events spec section "Trusted events", we shouldn't // trigger UA default action with an untrusted event except click. // However, there are still some sites depending on sending untrusted event // to submit form, see Bug 1370630. return !StaticPrefs::dom_forms_submit_trusted_event_only() ||
aEvent.IsTrusted();
}
// XXXedgar, the untrusted event would trigger form submission, in this // case, form need to handle defer flag and flushing pending submission by // itself. This could be removed after Bug 1370630. if (!aVisitor.mEvent->IsTrusted()) { // let the form know that it needs to defer the submission, // that means that if there are scripted submissions, the // latest one will be deferred until after the exit point of the // handler.
mDeferSubmission = true;
}
} elseif (msg == eFormReset) { if (mGeneratingReset) {
aVisitor.mCanHandle = false; return;
}
mGeneratingReset = true;
}
}
nsGenericHTMLElement::GetEventTargetParent(aVisitor);
}
void HTMLFormElement::WillHandleEvent(EventChainPostVisitor& aVisitor) { // If this is the bubble stage and there is a nested form below us which // received a submit event we do *not* want to handle the submit event // for this form too. if ((aVisitor.mEvent->mMessage == eFormSubmit ||
aVisitor.mEvent->mMessage == eFormReset) &&
aVisitor.mEvent->mFlags.mInBubblingPhase &&
aVisitor.mEvent->mOriginalTarget != static_cast<nsIContent*>(this)) {
aVisitor.mEvent->StopPropagation();
}
}
nsresult HTMLFormElement::PostHandleEvent(EventChainPostVisitor& aVisitor) { if (aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) &&
CanSubmit(*aVisitor.mEvent)) {
EventMessage msg = aVisitor.mEvent->mMessage; if (aVisitor.mEventStatus == nsEventStatus_eIgnore) { switch (msg) { case eFormReset: {
DoReset(); break;
} case eFormSubmit: { if (!aVisitor.mEvent->IsTrusted()) { // Warning about the form submission is from untrusted event.
OwnerDoc()->WarnOnceAbout(
DeprecatedOperations::eFormSubmissionUntrustedEvent);
}
RefPtr<Event> event = aVisitor.mDOMEvent;
DoSubmit(event); break;
} default: break;
}
}
// XXXedgar, the untrusted event would trigger form submission, in this // case, form need to handle defer flag and flushing pending submission by // itself. This could be removed after Bug 1370630. if (msg == eFormSubmit && !aVisitor.mEvent->IsTrusted()) { // let the form know not to defer subsequent submissions
mDeferSubmission = false; // tell the form to flush a possible pending submission.
FlushPendingSubmission();
}
nsresult HTMLFormElement::DoReset() { // Make sure the presentation is up-to-date
Document* doc = GetComposedDoc(); if (doc) {
doc->FlushPendingNotifications(FlushType::ContentAndNotify);
}
// JBK walk the elements[] array instead of form frame controls - bug 34297
uint32_t numElements = mControls->Length(); for (uint32_t elementX = 0; elementX < numElements; ++elementX) { // Hold strong ref in case the reset does something weird
nsCOMPtr<nsIFormControl> controlNode = nsIFormControl::FromNodeOrNull(
mControls->mElements->SafeElementAt(elementX, nullptr)); if (controlNode) {
controlNode->Reset();
}
}
nsresult HTMLFormElement::DoSubmit(Event* aEvent) {
Document* doc = GetComposedDoc();
NS_ASSERTION(doc, "Should never get here without a current doc");
// Make sure the presentation is up-to-date if (doc) {
doc->FlushPendingNotifications(FlushType::ContentAndNotify);
}
// Don't submit if we're not in a document or if we're in // a sandboxed frame and form submit is disabled. if (mIsConstructingEntryList || !doc ||
(doc->GetSandboxFlags() & SANDBOXED_FORMS)) { return NS_OK;
}
if (IsSubmitting()) {
NS_WARNING("Preventing double form submission"); // XXX Should this return an error? return NS_OK;
}
// Don't raise an error if form cannot navigate. if (rv == NS_ERROR_NOT_AVAILABLE) { return NS_OK;
}
NS_ENSURE_SUCCESS(rv, rv);
// XXXbz if the script global is that for an sXBL/XBL2 doc, it won't // be a window...
nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow(); if (window) {
mSubmitPopupState = PopupBlocker::GetPopupControlState();
} else {
mSubmitPopupState = PopupBlocker::openAbused;
}
if (DialogFormSubmission* dialogSubmission =
submission->GetAsDialogSubmission()) { return SubmitDialog(dialogSubmission);
}
if (mDeferSubmission) { // we are in an event handler, JS submitted so we have to // defer this submission. let's remember it and return // without submitting
mPendingSubmission = std::move(submission); return NS_OK;
}
return SubmitSubmission(submission.get());
}
nsresult HTMLFormElement::BuildSubmission(HTMLFormSubmission** aFormSubmission,
Event* aEvent) { // Get the submitter element
nsGenericHTMLElement* submitter = nullptr; if (aEvent) {
SubmitEvent* submitEvent = aEvent->AsSubmitEvent(); if (submitEvent) {
submitter = submitEvent->GetSubmitter();
}
}
nsresult rv;
// // Walk over the form elements and call SubmitNamesValues() on them to get // their data. // auto encoding = GetSubmitEncoding()->OutputEncoding();
RefPtr<FormData> formData = new FormData(GetOwnerGlobal(), encoding, submitter);
rv = ConstructEntryList(formData);
NS_ENSURE_SUBMIT_SUCCESS(rv);
// // Get the submission object //
rv = HTMLFormSubmission::GetFromForm(this, submitter, encoding,
aFormSubmission);
NS_ENSURE_SUBMIT_SUCCESS(rv);
// // Dump the data into the submission object // if (!(*aFormSubmission)->GetAsDialogSubmission()) {
rv = formData->CopySubmissionDataTo(*aFormSubmission);
NS_ENSURE_SUBMIT_SUCCESS(rv);
}
nsCOMPtr<nsIURI> actionURI = aFormSubmission->GetActionURL(); if (!actionURI) { return NS_OK;
}
// If there is no link handler, then we won't actually be able to submit.
Document* doc = GetComposedDoc();
RefPtr<nsDocShell> container =
doc ? nsDocShell::Cast(doc->GetDocShell()) : nullptr; if (!container || IsEditable()) { return NS_OK;
}
// javascript URIs are not really submissions; they just call a function. // Also, they may synchronously call submit(), and we want them to be able to // do so while still disallowing other double submissions. (Bug 139798) // Note that any other URI types that are of equivalent type should also be // added here. // XXXbz this is a mess. The real issue here is that nsJSChannel sets the // LOAD_BACKGROUND flag, so doesn't notify us, compounded by the fact that // the JS executes before we forget the submission in OnStateChange on // STATE_STOP. As a result, we have to make sure that we simply pretend // we're not submitting when submitting to a JS URL. That's kinda bogus, but // there we are. bool schemeIsJavaScript = actionURI->SchemeIs("javascript");
// Even if the submit succeeds, it's possible for there to be no // browsing context; for example, if it's to a named anchor within // the same page the submit will not really do anything. if (mTargetContext && !mTargetContext->IsDiscarded() && !schemeIsJavaScript) {
mCurrentLoadId = Some(currentLoadId);
} else {
ForgetCurrentSubmission();
}
return rv;
}
// https://html.spec.whatwg.org/#concept-form-submit step 11
nsresult HTMLFormElement::SubmitDialog(DialogFormSubmission* aFormSubmission) { // Close the dialog subject. If there is a result, let that be the return // value.
HTMLDialogElement* dialog = aFormSubmission->DialogElement();
MOZ_ASSERT(dialog);
if (!StaticPrefs::security_warn_submit_secure_to_insecure()) { return NS_OK;
}
// Only ask the user about posting from a secure URI to an insecure URI if // this element is in the root document. When this is not the case, the mixed // content blocker will take care of security for us. if (!OwnerDoc()->IsTopLevelContentDocument()) { return NS_OK;
}
if (nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(aActionURL)) { return NS_OK;
}
if (nsMixedContentBlocker::URISafeToBeLoadedInSecureContext(aActionURL)) { return NS_OK;
}
if (nsMixedContentBlocker::IsPotentiallyTrustworthyOnion(aActionURL)) { return NS_OK;
}
nsCOMPtr<nsPIDOMWindowOuter> window = OwnerDoc()->GetWindow(); if (!window) { return NS_ERROR_FAILURE;
}
// Now that we know the action URI is insecure check if we're submitting from // a secure URI and if so fall thru and prompt user about posting. if (nsCOMPtr<nsPIDOMWindowInner> innerWindow = OwnerDoc()->GetInnerWindow()) { if (!innerWindow->IsSecureContext()) { return NS_OK;
}
}
// Bug 1351358: While file URIs are considered to be secure contexts we allow // submitting a form to an insecure URI from a file URI without an alert in an // attempt to avoid compatibility issues. if (window->GetDocumentURI()->SchemeIs("file")) { return NS_OK;
}
nsCOMPtr<nsIDocShell> docShell = window->GetDocShell(); if (!docShell) { return NS_ERROR_FAILURE;
}
nsresult HTMLFormElement::ConstructEntryList(FormData* aFormData) {
MOZ_ASSERT(aFormData, "Must have FormData!"); if (mIsConstructingEntryList) { // Step 2.2 of https://xhr.spec.whatwg.org/#dom-formdata. return NS_ERROR_DOM_INVALID_STATE_ERR;
}
AutoRestore<bool> resetConstructingEntryList(mIsConstructingEntryList);
mIsConstructingEntryList = true; // This shouldn't be called recursively, so use a rather large value // for the preallocated buffer.
AutoTArray<RefPtr<nsGenericHTMLFormElement>, 100> sortedControls;
nsresult rv = mControls->GetSortedControls(sortedControls);
NS_ENSURE_SUCCESS(rv, rv);
// Walk the list of nodes and call SubmitNamesValues() on the controls for (nsGenericHTMLFormElement* control : sortedControls) { // Disabled elements don't submit if (!control->IsDisabled()) {
nsCOMPtr<nsIFormControl> fc = nsIFormControl::FromNode(control);
MOZ_ASSERT(fc); // Tell the control to submit its name/value pairs to the submission
fc->SubmitNamesValues(aFormData);
}
}
int32_t charsetLen = acceptCharsetValue.Length(); if (charsetLen > 0) {
int32_t offset = 0;
int32_t spPos = 0; // get charset from charsets one by one do {
spPos = acceptCharsetValue.FindChar(char16_t(' '), offset);
int32_t cnt = ((-1 == spPos) ? (charsetLen - offset) : (spPos - offset)); if (cnt > 0) {
nsAutoString uCharset;
acceptCharsetValue.Mid(uCharset, offset, cnt);
auto encoding = Encoding::ForLabelNoReplacement(uCharset); if (encoding) { return WrapNotNull(encoding);
}
}
offset = spPos + 1;
} while (spPos != -1);
} // if there are no accept-charset or all the charset are not supported // Get the charset from document
Document* doc = GetComposedDoc(); if (doc) { return doc->GetDocumentCharacterSet();
} return UTF_8_ENCODING;
}
Element* HTMLFormElement::IndexedGetter(uint32_t aIndex, bool& aFound) {
Element* element = mControls->mElements->SafeElementAt(aIndex, nullptr);
aFound = element != nullptr; return element;
}
#ifdef DEBUG /** * Checks that all form elements are in document order. Asserts if any pair of * consecutive elements are not in increasing document order. * * @param aControls List of form controls to check. * @param aForm Parent form of the controls.
*/ /* static */ void HTMLFormElement::AssertDocumentOrder( const nsTArray<nsGenericHTMLFormElement*>& aControls, nsIContent* aForm) { // TODO: remove the if directive with bug 598468. // This is done to prevent asserts in some edge cases. # if 0 // Only iterate if aControls is not empty, since otherwise // |aControls.Length() - 1| will be a very large unsigned number... not what // we want here. if (!aControls.IsEmpty()) { for (uint32_t i = 0; i < aControls.Length() - 1; ++i) {
NS_ASSERTION(
CompareFormControlPosition(aControls[i], aControls[i + 1], aForm) < 0, "Form controls not ordered correctly");
}
} # endif
}
/** * Copy of the above function, but with RefPtrs. * * @param aControls List of form controls to check. * @param aForm Parent form of the controls.
*/ /* static */ void HTMLFormElement::AssertDocumentOrder( const nsTArray<RefPtr<nsGenericHTMLFormElement>>& aControls,
nsIContent* aForm) { // TODO: remove the if directive with bug 598468. // This is done to prevent asserts in some edge cases. # if 0 // Only iterate if aControls is not empty, since otherwise // |aControls.Length() - 1| will be a very large unsigned number... not what // we want here. if (!aControls.IsEmpty()) { for (uint32_t i = 0; i < aControls.Length() - 1; ++i) {
NS_ASSERTION(
CompareFormControlPosition(aControls[i], aControls[i + 1], aForm) < 0, "Form controls not ordered correctly");
}
} # endif
} #endif
nsresult HTMLFormElement::AddElement(nsGenericHTMLFormElement* aChild, bool aUpdateValidity, bool aNotify) { // If an element has a @form, we can assume it *might* be able to not have // a parent and still be in the form.
NS_ASSERTION(aChild->HasAttr(nsGkAtoms::form) || aChild->GetParent(), "Form control should have a parent");
nsCOMPtr<nsIFormControl> fc = nsIFormControl::FromNode(aChild);
MOZ_ASSERT(fc); // Determine whether to add the new element to the elements or // the not-in-elements list. bool childInElements = HTMLFormControlsCollection::ShouldBeInElements(fc);
TreeOrderedArray<nsGenericHTMLFormElement*>& controlList =
childInElements ? mControls->mElements : mControls->mNotInElements;
// The new child is the new first submit in its list if the firstSubmitSlot // is currently empty or if the child is before what's currently in the // slot. Note that if we already have a control in firstSubmitSlot and // we're appending this element can't possibly replace what's currently in // the slot. Also note that aChild can't become the mDefaultSubmitElement // unless it replaces what's in the slot. If it _does_ replace what's in // the slot, it becomes the default submit if either the default submit is // what's in the slot or the child is earlier than the default submit. if (!*firstSubmitSlot ||
(!lastElement && nsContentUtils::CompareTreePosition<TreeKind::DOM>(
aChild, *firstSubmitSlot, this) < 0)) { // Update mDefaultSubmitElement if it's currently in a valid state. // Valid state means either non-null or null because there are in fact // no submit elements around. if ((mDefaultSubmitElement ||
(!mFirstSubmitInElements && !mFirstSubmitNotInElements)) &&
(*firstSubmitSlot == mDefaultSubmitElement ||
nsContentUtils::CompareTreePosition<TreeKind::DOM>(
aChild, mDefaultSubmitElement, this) < 0)) {
SetDefaultSubmitElement(aChild);
}
*firstSubmitSlot = aChild;
}
// If the element is subject to constraint validaton and is invalid, we need // to update our internal counter. if (aUpdateValidity) {
nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aChild); if (cvElmt && cvElmt->IsCandidateForConstraintValidation() &&
!cvElmt->IsValid()) {
UpdateValidity(false);
}
}
// Notify the radio button it's been added to a group // This has to be done _after_ UpdateValidity() call to prevent the element // being count twice. if (type == FormControlType::InputRadio) {
RefPtr<HTMLInputElement> radio = static_cast<HTMLInputElement*>(aChild);
radio->AddToRadioGroup();
}
void HTMLFormElement::SetDefaultSubmitElement(
nsGenericHTMLFormElement* aElement) { if (mDefaultSubmitElement) { // It just so happens that a radio button or an <option> can't be our // default submit element, so we can just blindly remove the bit.
mDefaultSubmitElement->RemoveStates(ElementState::DEFAULT);
}
mDefaultSubmitElement = aElement; if (mDefaultSubmitElement) {
mDefaultSubmitElement->AddStates(ElementState::DEFAULT);
}
}
// // Remove it from the radio group if it's a radio button //
nsresult rv = NS_OK;
nsCOMPtr<nsIFormControl> fc = nsIFormControl::FromNode(aChild);
MOZ_ASSERT(fc); if (fc->ControlType() == FormControlType::InputRadio) {
RefPtr<HTMLInputElement> radio = static_cast<HTMLInputElement*>(aChild);
radio->RemoveFromRadioGroup();
}
// Determine whether to remove the child from the elements list // or the not in elements list. bool childInElements = HTMLFormControlsCollection::ShouldBeInElements(fc);
TreeOrderedArray<nsGenericHTMLFormElement*>& controls =
childInElements ? mControls->mElements : mControls->mNotInElements;
// Find the index of the child. This will be used later if necessary // to find the default submit.
size_t index = controls->IndexOf(aChild);
NS_ENSURE_STATE(index != controls.AsList().NoIndex);
// We are removing the first submit in this list, find the new first submit
uint32_t length = controls->Length(); for (uint32_t i = index; i < length; ++i) { constauto* currentControl =
nsIFormControl::FromNode(controls->ElementAt(i));
MOZ_ASSERT(currentControl); if (currentControl->IsSubmitControl()) {
*firstSubmitSlot = controls->ElementAt(i); break;
}
}
}
if (aChild == mDefaultSubmitElement) { // Need to reset mDefaultSubmitElement. Do this asynchronously so // that we're not doing it while the DOM is in flux.
SetDefaultSubmitElement(nullptr);
nsContentUtils::AddScriptRunner(new RemoveElementRunnable(this));
// Note that we don't need to notify on the old default submit (which is // being removed) because it's either being removed from the DOM or // changing attributes in a way that makes it responsible for sending its // own notifications.
}
// If the element was subject to constraint validation and is invalid, we need // to update our internal counter. if (aUpdateValidity) {
nsCOMPtr<nsIConstraintValidation> cvElmt = do_QueryObject(aChild); if (cvElmt && cvElmt->IsCandidateForConstraintValidation() &&
!cvElmt->IsValid()) {
UpdateValidity(true);
}
}
return rv;
}
void HTMLFormElement::HandleDefaultSubmitRemoval() { if (mDefaultSubmitElement) { // Already got reset somehow; nothing else to do here return;
}
nsGenericHTMLFormElement* newDefaultSubmit; if (!mFirstSubmitNotInElements) {
newDefaultSubmit = mFirstSubmitInElements;
} elseif (!mFirstSubmitInElements) {
newDefaultSubmit = mFirstSubmitNotInElements;
} else {
NS_ASSERTION(mFirstSubmitInElements != mFirstSubmitNotInElements, "How did that happen?"); // Have both; use the earlier one
newDefaultSubmit =
nsContentUtils::CompareTreePosition<TreeKind::DOM>(
mFirstSubmitInElements, mFirstSubmitNotInElements, this) < 0
? mFirstSubmitInElements
: mFirstSubmitNotInElements;
}
SetDefaultSubmitElement(newDefaultSubmit);
nsresult HTMLFormElement::RemoveElementFromTableInternal(
nsInterfaceHashtable<nsStringHashKey, nsISupports>& aTable,
nsIContent* aChild, const nsAString& aName) { auto entry = aTable.Lookup(aName); if (!entry) { return NS_OK;
} // Single element in the hash, just remove it if it's the one // we're trying to remove... if (entry.Data() == aChild) {
entry.Remove();
++mExpandoAndGeneration.generation; return NS_OK;
}
nsCOMPtr<nsIContent> content(do_QueryInterface(entry.Data())); if (content) { return NS_OK;
}
// If it's not a content node then it must be a RadioNodeList.
MOZ_ASSERT(nsCOMPtr<RadioNodeList>(do_QueryInterface(entry.Data()))); auto* list = static_cast<RadioNodeList*>(entry->get());
list->RemoveElement(aChild);
uint32_t length = list->Length();
if (!length) { // If the list is empty we remove if from our hash, this shouldn't // happen tho
entry.Remove();
++mExpandoAndGeneration.generation;
} elseif (length == 1) { // Only one element left, replace the list in the hash with the // single element.
nsIContent* node = list->Item(0); if (node) {
entry.Data() = node;
}
}
// Prepare to run NotifySubmitObservers early before the // scripts on the page get to modify the form data, possibly // throwing off any password manager. (bug 257781)
nsCOMPtr<nsIURI> actionURI;
nsresult rv;
rv = GetActionURL(getter_AddRefs(actionURI), aOriginatingElement); if (NS_FAILED(rv) || !actionURI) return;
// Notify observers of submit if the form is valid. // TODO: checking for mInvalidElementsCount is a temporary fix that should be // removed with bug 610402. if (mInvalidElementsCount == 0) { bool cancelSubmit = false;
rv = NotifySubmitObservers(actionURI, &cancelSubmit, true); if (NS_SUCCEEDED(rv)) {
mNotifiedObservers = true;
mNotifiedObserversResult = cancelSubmit;
}
}
}
if (mPendingSubmission) { // Transfer owning reference so that the submission doesn't get deleted // if we reenter
UniquePtr<HTMLFormSubmission> submission = std::move(mPendingSubmission);
// // Grab the URL string // // If the originating element is a submit control and has the formaction // attribute specified, it should be used. Otherwise, the action attribute // from the form element should be used. //
nsAutoString action;
if (aOriginatingElement &&
aOriginatingElement->HasAttr(nsGkAtoms::formaction)) { #ifdef DEBUG constauto* formControl = nsIFormControl::FromNode(aOriginatingElement);
NS_ASSERTION(formControl && formControl->IsSubmitControl(), "The originating element must be a submit form control!"); #endif// DEBUG
HTMLInputElement* inputElement =
HTMLInputElement::FromNode(aOriginatingElement); if (inputElement) {
inputElement->GetFormAction(action);
} else { auto buttonElement = HTMLButtonElement::FromNode(aOriginatingElement); if (buttonElement) {
buttonElement->GetFormAction(action);
} else {
NS_ERROR("Originating element must be an input or button element!"); return NS_ERROR_UNEXPECTED;
}
}
} else {
GetAction(action);
}
// // Form the full action URL //
// Get the document to form the URL. // We'll also need it later to get the DOM window when notifying form submit // observers (bug 33203) if (!IsInComposedDoc()) { return NS_OK; // No doc means don't submit, see Bug 28988
}
// Get base URL
Document* document = OwnerDoc();
nsIURI* docURI = document->GetDocumentURI();
NS_ENSURE_TRUE(docURI, NS_ERROR_UNEXPECTED);
// If an action is not specified and we are inside // a HTML document then reload the URL. This makes us // compatible with 4.x browsers. // If we are in some other type of document such as XML or // XUL, do nothing. This prevents undesirable reloading of // a document inside XUL.
nsCOMPtr<nsIURI> actionURL; if (action.IsEmpty()) { if (!document->IsHTMLOrXHTML()) { // Must be a XML, XUL or other non-HTML document type // so do nothing. return NS_OK;
}
actionURL = docURI;
} else {
nsIURI* baseURL = GetBaseURI();
NS_ASSERTION(baseURL, "No Base URL found in Form Submit!\n"); if (!baseURL) { return NS_OK; // No base URL -> exit early, see Bug 30721
}
rv = NS_NewURI(getter_AddRefs(actionURL), action, nullptr, baseURL);
NS_ENSURE_SUCCESS(rv, rv);
}
// // Verify the URL should be reached // // Get security manager, check to see if access to action URI is allowed. //
nsIScriptSecurityManager* securityManager =
nsContentUtils::GetSecurityManager();
rv = securityManager->CheckLoadURIWithPrincipal(
NodePrincipal(), actionURL, nsIScriptSecurityManager::STANDARD,
OwnerDoc()->InnerWindowID());
NS_ENSURE_SUCCESS(rv, rv);
// Potentially the page uses the CSP directive 'upgrade-insecure-requests'. In // such a case we have to upgrade the action url from http:// to https://. // The upgrade is only required if the actionURL is http and not a potentially // trustworthy loopback URI. bool needsUpgrade =
actionURL->SchemeIs("http") &&
!nsMixedContentBlocker::IsPotentiallyTrustworthyLoopbackURL(actionURL) &&
document->GetUpgradeInsecureRequests(false); if (needsUpgrade) { // let's use the old specification before the upgrade for logging
AutoTArray<nsString, 2> params;
nsAutoCString spec;
rv = actionURL->GetSpec(spec);
NS_ENSURE_SUCCESS(rv, rv);
CopyUTF8toUTF16(spec, *params.AppendElement());
// upgrade the actionURL from http:// to use https://
nsCOMPtr<nsIURI> upgradedActionURL;
rv = NS_GetSecureUpgradedURI(actionURL, getter_AddRefs(upgradedActionURL));
NS_ENSURE_SUCCESS(rv, rv);
actionURL = std::move(upgradedActionURL);
// let's log a message to the console that we are upgrading a request
nsAutoCString scheme;
rv = actionURL->GetScheme(scheme);
NS_ENSURE_SUCCESS(rv, rv);
CopyUTF8toUTF16(scheme, *params.AppendElement());
// // Assign to the output //
actionURL.forget(aActionURL);
return rv;
}
void HTMLFormElement::GetSubmissionTarget(nsGenericHTMLElement* aSubmitter,
nsAString& aTarget) { // https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#form-submission-algorithm // 19. If the submitter element is a submit button and it has a formtarget // attribute, then set formTarget to the formtarget attribute value. // 20. Let target be the result of getting an element's target given // submitter's form owner and formTarget. // // Note: Falling back to the base target is part of "get an element's target". if (!(aSubmitter && aSubmitter->GetAttr(nsGkAtoms::formtarget, aTarget)) &&
!GetAttr(nsGkAtoms::target, aTarget)) {
GetBaseTarget(aTarget);
}
SanitizeLinkOrFormTarget(aTarget);
}
for (auto* element : Reversed(mControls->mElements.AsList())) { constauto* fc = nsIFormControl::FromNode(element);
MOZ_ASSERT(fc); // XXX How about date/time control? if (fc->IsTextControl(false) && !element->IsDisabled()) { return element == aElement;
}
} returnfalse;
}
bool HTMLFormElement::CheckFormValidity(
nsTArray<RefPtr<Element>>* aInvalidElements) const { bool ret = true;
// This shouldn't be called recursively, so use a rather large value // for the preallocated buffer.
AutoTArray<RefPtr<nsGenericHTMLFormElement>, 100> sortedControls; if (NS_FAILED(mControls->GetSortedControls(sortedControls))) { returnfalse;
}
uint32_t len = sortedControls.Length();
for (uint32_t i = 0; i < len; ++i) {
nsCOMPtr<nsIConstraintValidation> cvElmt =
do_QueryObject(sortedControls[i]); bool defaultAction = true; if (cvElmt && !cvElmt->CheckValidity(*sortedControls[i], &defaultAction)) {
ret = false;
// Add all unhandled invalid controls to aInvalidElements if the caller // requested them. if (defaultAction && aInvalidElements) {
aInvalidElements->AppendElement(sortedControls[i]);
}
}
}
return ret;
}
bool HTMLFormElement::CheckValidFormSubmission() { /** * Check for form validity: do not submit a form if there are unhandled * invalid controls in the form. * This should not be done if the form has been submitted with .submit() or * has been submitted and novalidate/formnovalidate is used. * * NOTE: for the moment, we are also checking that whether the MozInvalidForm * event gets prevented default so it will prevent blocking form submission if * the browser does not have implemented a UI yet. * * TODO: the check for MozInvalidForm event should be removed later when HTML5 * Forms will be spread enough and authors will assume forms can't be * submitted when invalid. See bug 587671.
*/
AutoTArray<RefPtr<Element>, 32> invalidElements; if (CheckFormValidity(&invalidElements)) { returntrue;
}
AutoJSAPI jsapi; if (!jsapi.Init(GetOwnerGlobal())) { returnfalse;
}
JS::Rooted<JS::Value> detail(jsapi.cx()); if (!ToJSValue(jsapi.cx(), invalidElements, &detail)) { returnfalse;
}
NS_ASSERTION(mInvalidElementsCount >= 0, "Something went seriously wrong!");
// The form validity has just changed if: // - there are no more invalid elements ; // - or there is one invalid elmement and an element just became invalid. // If we have invalid elements and we used to before as well, do nothing. if (mInvalidElementsCount &&
(mInvalidElementsCount != 1 || aElementValidity)) { return;
}
nsresult HTMLFormElement::AddElementToTableInternal(
nsInterfaceHashtable<nsStringHashKey, nsISupports>& aTable,
nsIContent* aChild, const nsAString& aName) { return aTable.WithEntryHandle(aName, [&](auto&& entry) { if (!entry) { // No entry found, add the element
entry.Insert(aChild);
++mExpandoAndGeneration.generation;
} else { // Found something in the hash, check its type
nsCOMPtr<nsIContent> content = do_QueryInterface(entry.Data());
if (content) { // Check if the new content is the same as the one we found in the // hash, if it is then we leave it in the hash as it is, this will // happen if a form control has both a name and an id with the same // value if (content == aChild) { return NS_OK;
}
// Found an element, create a list, add the element to the list and put // the list in the hash
RadioNodeList* list = new RadioNodeList(this);
// If an element has a @form, we can assume it *might* be able to not // have a parent and still be in the form.
NS_ASSERTION(
(content->IsElement() && content->AsElement()->HasAttr(
kNameSpaceID_None, nsGkAtoms::form)) ||
content->GetParent(), "Item in list without parent");
// Determine the ordering between the new and old element. bool newFirst = nsContentUtils::PositionIsBefore(aChild, content);
// Replace the element with the list.
entry.Data() = listSupports;
} else { // There's already a list in the hash, add the child to the list.
MOZ_ASSERT(nsCOMPtr<RadioNodeList>(do_QueryInterface(entry.Data()))); auto* list = static_cast<RadioNodeList*>(entry->get());
NS_ASSERTION(
list->Length() > 1, "List should have been converted back to a single element");
// Fast-path appends; this check is ok even if the child is // already in the list, since if it tests true the child would // have come at the end of the list, and the PositionIsBefore // will test false. if (nsContentUtils::PositionIsBefore(list->Item(list->Length() - 1),
aChild)) {
list->AppendElement(aChild); return NS_OK;
}
// If a control has a name equal to its id, it could be in the // list already. if (list->IndexOf(aChild) != -1) { return NS_OK;
}
size_t idx;
DebugOnly<bool> found =
BinarySearchIf(RadioNodeListAdaptor(list), 0, list->Length(),
PositionComparator(aChild), &idx);
MOZ_ASSERT(!found, "should not have found an element");
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.