/* -*- 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/. */ #include "mozilla/ArrayUtils.h" #include "mozilla/DeclarationBlockInlines.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventListenerManager.h" #include "mozilla/EventStateManager.h" #include "mozilla/EventStates.h" #include "mozilla/GenericSpecifiedValuesInlines.h" #include "mozilla/Likely.h" #include "mozilla/MouseEvents.h" #include "mozilla/TextEditor.h" #include "nscore.h" #include "nsGenericHTMLElement.h" #include "nsAttrValueInlines.h" #include "nsCOMPtr.h" #include "nsIAtom.h" #include "nsQueryObject.h" #include "nsIContentInlines.h" #include "nsIContentViewer.h" #include "mozilla/css/Declaration.h" #include "nsIDocument.h" #include "nsIDocumentEncoder.h" #include "nsIDOMHTMLDocument.h" #include "nsIDOMAttr.h" #include "nsIDOMDocumentFragment.h" #include "nsIDOMHTMLElement.h" #include "nsIDOMWindow.h" #include "nsIDOMDocument.h" #include "nsMappedAttributes.h" #include "nsHTMLStyleSheet.h" #include "nsIHTMLDocument.h" #include "nsPIDOMWindow.h" #include "nsIURL.h" #include "nsEscape.h" #include "nsIFrameInlines.h" #include "nsIScrollableFrame.h" #include "nsView.h" #include "nsViewManager.h" #include "nsIWidget.h" #include "nsRange.h" #include "nsIPresShell.h" #include "nsPresContext.h" #include "nsIDocShell.h" #include "nsNameSpaceManager.h" #include "nsError.h" #include "nsIPrincipal.h" #include "nsContainerFrame.h" #include "nsStyleUtil.h" #include "nsPresState.h" #include "nsILayoutHistoryState.h" #include "nsHTMLParts.h" #include "nsContentUtils.h" #include "mozilla/dom/DirectionalityUtils.h" #include "nsString.h" #include "nsUnicharUtils.h" #include "nsGkAtoms.h" #include "nsIDOMEvent.h" #include "nsDOMCSSDeclaration.h" #include "nsITextControlFrame.h" #include "nsIForm.h" #include "nsIFormControl.h" #include "nsIDOMHTMLFormElement.h" #include "mozilla/dom/HTMLFormElement.h" #include "nsFocusManager.h" #include "nsAttrValueOrString.h" #include "mozilla/InternalMutationEvent.h" #include "nsDOMStringMap.h" #include "nsLayoutUtils.h" #include "mozAutoDocUpdate.h" #include "nsHtml5Module.h" #include "nsITextControlElement.h" #include "mozilla/dom/ElementInlines.h" #include "HTMLFieldSetElement.h" #include "nsTextNode.h" #include "HTMLBRElement.h" #include "HTMLMenuElement.h" #include "nsDOMMutationObserver.h" #include "mozilla/Preferences.h" #include "mozilla/dom/FromParser.h" #include "mozilla/dom/Link.h" #include "mozilla/BloomFilter.h" #include "mozilla/dom/ScriptLoader.h" #include "nsVariant.h" #include "nsDOMTokenList.h" #include "nsThreadUtils.h" #include "nsTextFragment.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/TouchEvent.h" #include "mozilla/ErrorResult.h" #include "nsHTMLDocument.h" #include "nsGlobalWindow.h" #include "mozilla/dom/HTMLBodyElement.h" #include "imgIContainer.h" #include "nsComputedDOMStyle.h" #include "mozilla/StyleSetHandle.h" #include "mozilla/StyleSetHandleInlines.h" #include "ReferrerPolicy.h" #include "mozilla/dom/HTMLLabelElement.h" #include "mozilla/dom/HTMLInputElement.h" using namespace mozilla; using namespace mozilla::dom; /** * nsAutoFocusEvent is used to dispatch a focus event when a * nsGenericHTMLFormElement is binded to the tree with the autofocus attribute * enabled. */ class nsAutoFocusEvent : public Runnable { public: explicit nsAutoFocusEvent(nsGenericHTMLFormElement* aElement) : mozilla::Runnable("nsAutoFocusEvent") , mElement(aElement) { } NS_IMETHOD Run() override { nsFocusManager* fm = nsFocusManager::GetFocusManager(); if (!fm) { return NS_ERROR_NULL_POINTER; } nsIDocument* document = mElement->OwnerDoc(); nsPIDOMWindowOuter* window = document->GetWindow(); if (!window) { return NS_OK; } // Trying to found the top window (equivalent to window.top). if (nsCOMPtr top = window->GetTop()) { window = top; } if (window->GetFocusedNode()) { return NS_OK; } nsCOMPtr topDoc = window->GetExtantDoc(); if (topDoc && topDoc->GetReadyStateEnum() == nsIDocument::READYSTATE_COMPLETE) { return NS_OK; } // If something is focused in the same document, ignore autofocus. if (!fm->GetFocusedContent() || fm->GetFocusedContent()->OwnerDoc() != document) { mozilla::ErrorResult rv; mElement->Focus(rv); return rv.StealNSResult(); } return NS_OK; } private: // NOTE: nsGenericHTMLFormElement is saved as a nsGenericHTMLElement // because AddRef/Release are ambiguous with nsGenericHTMLFormElement // and Focus() is declared (and defined) in nsGenericHTMLElement class. RefPtr mElement; }; NS_IMPL_ADDREF_INHERITED(nsGenericHTMLElement, nsGenericHTMLElementBase) NS_IMPL_RELEASE_INHERITED(nsGenericHTMLElement, nsGenericHTMLElementBase) NS_INTERFACE_MAP_BEGIN(nsGenericHTMLElement) NS_INTERFACE_MAP_ENTRY(nsIDOMHTMLElement) NS_INTERFACE_MAP_ENTRY(nsIDOMElement) NS_INTERFACE_MAP_ENTRY(nsIDOMNode) NS_INTERFACE_MAP_END_INHERITING(nsGenericHTMLElementBase) nsresult nsGenericHTMLElement::CopyInnerTo(Element* aDst, bool aPreallocateChildren) { MOZ_ASSERT(!aDst->GetUncomposedDoc(), "Should not CopyInnerTo an Element in a document"); nsresult rv; bool reparse = (aDst->OwnerDoc() != OwnerDoc()); rv = static_cast(aDst)->mAttrsAndChildren. EnsureCapacityToClone(mAttrsAndChildren, aPreallocateChildren); NS_ENSURE_SUCCESS(rv, rv); int32_t i, count = GetAttrCount(); for (i = 0; i < count; ++i) { const nsAttrName *name = mAttrsAndChildren.AttrNameAt(i); const nsAttrValue *value = mAttrsAndChildren.AttrAt(i); if (name->Equals(nsGkAtoms::style, kNameSpaceID_None) && value->Type() == nsAttrValue::eCSSDeclaration) { // We still clone CSS attributes, even in the cross-document case. // https://github.com/w3c/webappsec-csp/issues/212 // We can't just set this as a string, because that will fail // to reparse the string into style data until the node is // inserted into the document. Clone the Rule instead. nsAttrValue valueCopy(*value); rv = aDst->SetParsedAttr(name->NamespaceID(), name->LocalName(), name->GetPrefix(), valueCopy, false); NS_ENSURE_SUCCESS(rv, rv); DeclarationBlock* cssDeclaration = value->GetCSSDeclarationValue(); cssDeclaration->SetImmutable(); } else if (reparse) { nsAutoString valStr; value->ToString(valStr); rv = aDst->SetAttr(name->NamespaceID(), name->LocalName(), name->GetPrefix(), valStr, false); NS_ENSURE_SUCCESS(rv, rv); } else { nsAttrValue valueCopy(*value); rv = aDst->SetParsedAttr(name->NamespaceID(), name->LocalName(), name->GetPrefix(), valueCopy, false); NS_ENSURE_SUCCESS(rv, rv); } } return NS_OK; } static const nsAttrValue::EnumTable kDirTable[] = { { "ltr", eDir_LTR }, { "rtl", eDir_RTL }, { "auto", eDir_Auto }, { nullptr, 0 } }; void nsGenericHTMLElement::GetAccessKeyLabel(nsString& aLabel) { nsAutoString suffix; GetAccessKey(suffix); if (!suffix.IsEmpty()) { EventStateManager::GetAccessKeyLabelPrefix(this, aLabel); aLabel.Append(suffix); } } static bool IS_TABLE_CELL(LayoutFrameType frameType) { return LayoutFrameType::TableCell == frameType || LayoutFrameType::BCTableCell == frameType; } static bool IsOffsetParent(nsIFrame* aFrame) { LayoutFrameType frameType = aFrame->Type(); if (IS_TABLE_CELL(frameType) || frameType == LayoutFrameType::Table) { // Per the IDL for Element, only td, th, and table are acceptable offsetParents // apart from body or positioned elements; we need to check the content type as // well as the frame type so we ignore anonymous tables created by an element // with display: table-cell with no actual table nsIContent* content = aFrame->GetContent(); return content->IsAnyOfHTMLElements(nsGkAtoms::table, nsGkAtoms::td, nsGkAtoms::th); } return false; } Element* nsGenericHTMLElement::GetOffsetRect(CSSIntRect& aRect) { aRect = CSSIntRect(); nsIFrame* frame = GetStyledFrame(); if (!frame) { return nullptr; } nsIFrame* parent = frame->GetParent(); nsPoint origin(0, 0); if (parent && parent->IsTableWrapperFrame() && frame->IsTableFrame()) { origin = parent->GetPositionIgnoringScrolling(); parent = parent->GetParent(); } nsIContent* offsetParent = nullptr; Element* docElement = GetComposedDoc()->GetRootElement(); nsIContent* content = frame->GetContent(); if (content && (content->IsHTMLElement(nsGkAtoms::body) || content == docElement)) { parent = frame; } else { const bool isPositioned = frame->IsAbsPosContainingBlock(); const bool isAbsolutelyPositioned = frame->IsAbsolutelyPositioned(); origin += frame->GetPositionIgnoringScrolling(); for ( ; parent ; parent = parent->GetParent()) { content = parent->GetContent(); // Stop at the first ancestor that is positioned. if (parent->IsAbsPosContainingBlock()) { offsetParent = content; break; } // Add the parent's origin to our own to get to the // right coordinate system. const bool isOffsetParent = !isPositioned && IsOffsetParent(parent); if (!isOffsetParent) { origin += parent->GetPositionIgnoringScrolling(); } if (content) { // If we've hit the document element, break here. if (content == docElement) { break; } // Break if the ancestor frame type makes it suitable as offset parent // and this element is *not* positioned or if we found the body element. if (isOffsetParent || content->IsHTMLElement(nsGkAtoms::body)) { offsetParent = content; break; } } } if (isAbsolutelyPositioned && !offsetParent) { // If this element is absolutely positioned, but we don't have // an offset parent it means this element is an absolutely // positioned child that's not nested inside another positioned // element, in this case the element's frame's parent is the // frame for the HTML element so we fail to find the body in the // parent chain. We want the offset parent in this case to be // the body, so we just get the body element from the document. nsCOMPtr html_doc(do_QueryInterface(GetComposedDoc())); if (html_doc) { offsetParent = static_cast(html_doc.get())->GetBody(); } } } // Subtract the parent border unless it uses border-box sizing. if (parent && parent->StylePosition()->mBoxSizing != StyleBoxSizing::Border) { const nsStyleBorder* border = parent->StyleBorder(); origin.x -= border->GetComputedBorderWidth(eSideLeft); origin.y -= border->GetComputedBorderWidth(eSideTop); } // XXX We should really consider subtracting out padding for // content-box sizing, but we should see what IE does.... // Get the union of all rectangles in this and continuation frames. // It doesn't really matter what we use as aRelativeTo here, since // we only care about the size. We just have to use something non-null. nsRect rcFrame = nsLayoutUtils::GetAllInFlowRectsUnion(frame, frame); rcFrame.MoveTo(origin); aRect = CSSIntRect::FromAppUnitsRounded(rcFrame); return offsetParent ? offsetParent->AsElement() : nullptr; } bool nsGenericHTMLElement::Spellcheck() { // Has the state has been explicitly set? nsIContent* node; for (node = this; node; node = node->GetParent()) { if (node->IsHTMLElement()) { static nsIContent::AttrValuesArray strings[] = {&nsGkAtoms::_true, &nsGkAtoms::_false, nullptr}; switch (node->FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::spellcheck, strings, eCaseMatters)) { case 0: // spellcheck = "true" return true; case 1: // spellcheck = "false" return false; } } } // contenteditable/designMode are spellchecked by default if (IsEditable()) { return true; } // Is this a chrome element? if (nsContentUtils::IsChromeDoc(OwnerDoc())) { return false; // Not spellchecked by default } // Anything else that's not a form control is not spellchecked by default nsCOMPtr formControl = do_QueryObject(this); if (!formControl) { return false; // Not spellchecked by default } // Is this a multiline plaintext input? int32_t controlType = formControl->ControlType(); if (controlType == NS_FORM_TEXTAREA) { return true; // Spellchecked by default } // Is this anything other than an input text? // Other inputs are not spellchecked. if (controlType != NS_FORM_INPUT_TEXT) { return false; // Not spellchecked by default } // Does the user want input text spellchecked by default? // NOTE: Do not reflect a pref value of 0 back to the DOM getter. // The web page should not know if the user has disabled spellchecking. // We'll catch this in the editor itself. int32_t spellcheckLevel = Preferences::GetInt("layout.spellcheckDefault", 1); return spellcheckLevel == 2; // "Spellcheck multi- and single-line" } bool nsGenericHTMLElement::InNavQuirksMode(nsIDocument* aDoc) { return aDoc && aDoc->GetCompatibilityMode() == eCompatibility_NavQuirks; } void nsGenericHTMLElement::UpdateEditableState(bool aNotify) { // XXX Should we do this only when in a document? ContentEditableTristate value = GetContentEditableValue(); if (value != eInherit) { DoSetEditableFlag(!!value, aNotify); return; } nsStyledElement::UpdateEditableState(aNotify); } EventStates nsGenericHTMLElement::IntrinsicState() const { EventStates state = nsGenericHTMLElementBase::IntrinsicState(); if (GetDirectionality() == eDir_RTL) { state |= NS_EVENT_STATE_RTL; state &= ~NS_EVENT_STATE_LTR; } else { // at least for HTML, directionality is exclusively LTR or RTL NS_ASSERTION(GetDirectionality() == eDir_LTR, "HTML element's directionality must be either RTL or LTR"); state |= NS_EVENT_STATE_LTR; state &= ~NS_EVENT_STATE_RTL; } return state; } uint32_t nsGenericHTMLElement::EditableInclusiveDescendantCount() { bool isEditable = IsInUncomposedDoc() && HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue; return EditableDescendantCount() + isEditable; } nsresult nsGenericHTMLElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, bool aCompileEventHandlers) { nsresult rv = nsGenericHTMLElementBase::BindToTree(aDocument, aParent, aBindingParent, aCompileEventHandlers); NS_ENSURE_SUCCESS(rv, rv); if (aDocument) { RegAccessKey(); if (HasName() && CanHaveName(NodeInfo()->NameAtom())) { aDocument-> AddToNameTable(this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue()); } if (HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue) { nsCOMPtr htmlDocument = do_QueryInterface(aDocument); if (htmlDocument) { htmlDocument->ChangeContentEditableCount(this, +1); } } } // We need to consider a labels element is moved to another subtree // with different root, it needs to update labels list and its root // as well. nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); if (slots && slots->mLabelsList) { slots->mLabelsList->MaybeResetRoot(SubtreeRoot()); } return rv; } void nsGenericHTMLElement::UnbindFromTree(bool aDeep, bool aNullParent) { if (IsInUncomposedDoc()) { UnregAccessKey(); } RemoveFromNameTable(); if (GetContentEditableValue() == eTrue) { //XXXsmaug Fix this for Shadow DOM, bug 1066965. nsCOMPtr htmlDocument = do_QueryInterface(GetUncomposedDoc()); if (htmlDocument) { htmlDocument->ChangeContentEditableCount(this, -1); } } // We need to consider a labels element is removed from tree, // it needs to update labels list and its root as well. nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); if (slots && slots->mLabelsList) { slots->mLabelsList->MaybeResetRoot(SubtreeRoot()); } nsStyledElement::UnbindFromTree(aDeep, aNullParent); } HTMLFormElement* nsGenericHTMLElement::FindAncestorForm(HTMLFormElement* aCurrentForm) { NS_ASSERTION(!HasAttr(kNameSpaceID_None, nsGkAtoms::form) || IsHTMLElement(nsGkAtoms::img), "FindAncestorForm should not be called if @form is set!"); // Make sure we don't end up finding a form that's anonymous from // our point of view. See also nsGenericHTMLFormElement::UpdateFieldSet. nsIContent* bindingParent = GetBindingParent(); nsIContent* content = this; while (content != bindingParent && content) { // If the current ancestor is a form, return it as our form if (content->IsHTMLElement(nsGkAtoms::form)) { #ifdef DEBUG if (!nsContentUtils::IsInSameAnonymousTree(this, content)) { // It's possible that we started unbinding at |content| or // some ancestor of it, and |content| and |this| used to all be // anonymous. Check for this the hard way. for (nsIContent* child = this; child != content; child = child->GetParent()) { NS_ASSERTION(child->GetParent()->IndexOf(child) != -1, "Walked too far?"); } } #endif return static_cast(content); } nsIContent *prevContent = content; content = prevContent->GetParent(); if (!content && aCurrentForm) { // We got to the root of the subtree we're in, and we're being removed // from the DOM (the only time we get into this method with a non-null // aCurrentForm). Check whether aCurrentForm is in the same subtree. If // it is, we want to return aCurrentForm, since this case means that // we're one of those inputs-in-a-table that have a hacked mForm pointer // and a subtree containing both us and the form got removed from the // DOM. if (nsContentUtils::ContentIsDescendantOf(aCurrentForm, prevContent)) { return aCurrentForm; } } } return nullptr; } bool nsGenericHTMLElement::CheckHandleEventForAnchorsPreconditions( EventChainVisitor& aVisitor) { NS_PRECONDITION(nsCOMPtr(do_QueryObject(this)), "should be called only when |this| implements |Link|"); if (!aVisitor.mPresContext) { // We need a pres context to do link stuff. Some events (e.g. mutation // events) don't have one. // XXX: ideally, shouldn't we be able to do what we need without one? return false; } //Need to check if we hit an imagemap area and if so see if we're handling //the event on that map or on a link farther up the tree. If we're on a //link farther up, do nothing. nsCOMPtr target = aVisitor.mPresContext->EventStateManager()-> GetEventTargetContent(aVisitor.mEvent); return !target || !target->IsHTMLElement(nsGkAtoms::area) || IsHTMLElement(nsGkAtoms::area); } nsresult nsGenericHTMLElement::GetEventTargetParentForAnchors(EventChainPreVisitor& aVisitor) { nsresult rv = nsGenericHTMLElementBase::GetEventTargetParent(aVisitor); NS_ENSURE_SUCCESS(rv, rv); if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) { return NS_OK; } return GetEventTargetParentForLinks(aVisitor); } nsresult nsGenericHTMLElement::PostHandleEventForAnchors(EventChainPostVisitor& aVisitor) { if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) { return NS_OK; } return PostHandleEventForLinks(aVisitor); } bool nsGenericHTMLElement::IsHTMLLink(nsIURI** aURI) const { NS_PRECONDITION(aURI, "Must provide aURI out param"); *aURI = GetHrefURIForAnchors().take(); // We promise out param is non-null if we return true, so base rv on it return *aURI != nullptr; } already_AddRefed nsGenericHTMLElement::GetHrefURIForAnchors() const { // This is used by the three Link implementations and // nsHTMLStyleElement. // Get href= attribute (relative URI). // We use the nsAttrValue's copy of the URI string to avoid copying. nsCOMPtr uri; GetURIAttr(nsGkAtoms::href, nullptr, getter_AddRefs(uri)); return uri.forget(); } nsresult nsGenericHTMLElement::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName, const nsAttrValueOrString* aValue, bool aNotify) { if (aNamespaceID == kNameSpaceID_None) { if (aName == nsGkAtoms::accesskey) { // Have to unregister before clearing flag. See UnregAccessKey UnregAccessKey(); if (!aValue) { UnsetFlags(NODE_HAS_ACCESSKEY); } } else if (aName == nsGkAtoms::name) { // Have to do this before clearing flag. See RemoveFromNameTable RemoveFromNameTable(); if (!aValue || aValue->IsEmpty()) { ClearHasName(); } } else if (aName == nsGkAtoms::contenteditable) { if (aValue) { // Set this before the attribute is set so that any subclass code that // runs before the attribute is set won't think we're missing a // contenteditable attr when we actually have one. SetMayHaveContentEditableAttr(); } } if (!aValue && IsEventAttributeName(aName)) { if (EventListenerManager* manager = GetExistingListenerManager()) { manager->RemoveEventHandler(aName, EmptyString()); } } } return nsGenericHTMLElementBase::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify); } nsresult nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName, const nsAttrValue* aValue, const nsAttrValue* aOldValue, bool aNotify) { if (aNamespaceID == kNameSpaceID_None) { if (IsEventAttributeName(aName) && aValue) { MOZ_ASSERT(aValue->Type() == nsAttrValue::eString, "Expected string value for script body"); nsresult rv = SetEventHandler(aName, aValue->GetStringValue()); NS_ENSURE_SUCCESS(rv, rv); } else if (aNotify && aName == nsGkAtoms::spellcheck) { SyncEditorsOnSubtree(this); } else if (aName == nsGkAtoms::dir) { Directionality dir = eDir_LTR; // A boolean tracking whether we need to recompute our directionality. // This needs to happen after we update our internal "dir" attribute // state but before we call SetDirectionalityOnDescendants. bool recomputeDirectionality = false; // We don't want to have to keep getting the "dir" attribute in // IntrinsicState, so we manually recompute our dir-related event states // here and send the relevant update notifications. EventStates dirStates; if (aValue && aValue->Type() == nsAttrValue::eEnum) { SetHasValidDir(); dirStates |= NS_EVENT_STATE_HAS_DIR_ATTR; Directionality dirValue = (Directionality)aValue->GetEnumValue(); if (dirValue == eDir_Auto) { dirStates |= NS_EVENT_STATE_DIR_ATTR_LIKE_AUTO; } else { dir = dirValue; SetDirectionality(dir, aNotify); if (dirValue == eDir_LTR) { dirStates |= NS_EVENT_STATE_DIR_ATTR_LTR; } else { MOZ_ASSERT(dirValue == eDir_RTL); dirStates |= NS_EVENT_STATE_DIR_ATTR_RTL; } } } else { if (aValue) { // We have a value, just not a valid one. dirStates |= NS_EVENT_STATE_HAS_DIR_ATTR; } ClearHasValidDir(); if (NodeInfo()->Equals(nsGkAtoms::bdi)) { dirStates |= NS_EVENT_STATE_DIR_ATTR_LIKE_AUTO; } else { recomputeDirectionality = true; } } // Now figure out what's changed about our dir states. EventStates oldDirStates = State() & DIR_ATTR_STATES; EventStates changedStates = dirStates ^ oldDirStates; ToggleStates(changedStates, aNotify); if (recomputeDirectionality) { dir = RecomputeDirectionality(this, aNotify); } SetDirectionalityOnDescendants(this, dir, aNotify); } else if (aName == nsGkAtoms::contenteditable) { int32_t editableCountDelta = 0; if (aOldValue && (aOldValue->Equals(NS_LITERAL_STRING("true"), eIgnoreCase) || aOldValue->Equals(EmptyString(), eIgnoreCase))) { editableCountDelta = -1; } if (aValue && (aValue->Equals(NS_LITERAL_STRING("true"), eIgnoreCase) || aValue->Equals(EmptyString(), eIgnoreCase))) { ++editableCountDelta; } ChangeEditableState(editableCountDelta); } else if (aName == nsGkAtoms::accesskey) { if (aValue && !aValue->Equals(EmptyString(), eIgnoreCase)) { SetFlags(NODE_HAS_ACCESSKEY); RegAccessKey(); } } else if (aName == nsGkAtoms::name) { if (aValue && !aValue->Equals(EmptyString(), eIgnoreCase)) { // This may not be quite right because we can have subclass code run // before here. But in practice subclasses don't care about this flag, // and in particular selector matching does not care. Otherwise we'd // want to handle it like we handle id attributes (in PreIdMaybeChange // and PostIdMaybeChange). SetHasName(); if (CanHaveName(NodeInfo()->NameAtom())) { AddToNameTable(aValue->GetAtomValue()); } } } } return nsGenericHTMLElementBase::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue, aNotify); } EventListenerManager* nsGenericHTMLElement::GetEventListenerManagerForAttr(nsIAtom* aAttrName, bool* aDefer) { // Attributes on the body and frameset tags get set on the global object if ((mNodeInfo->Equals(nsGkAtoms::body) || mNodeInfo->Equals(nsGkAtoms::frameset)) && // We only forward some event attributes from body/frameset to window (0 #define EVENT(name_, id_, type_, struct_) /* nothing */ #define FORWARDED_EVENT(name_, id_, type_, struct_) \ || nsGkAtoms::on##name_ == aAttrName #define WINDOW_EVENT FORWARDED_EVENT #include "mozilla/EventNameList.h" // IWYU pragma: keep #undef WINDOW_EVENT #undef FORWARDED_EVENT #undef EVENT ) ) { nsPIDOMWindowInner *win; // If we have a document, and it has a window, add the event // listener on the window (the inner window). If not, proceed as // normal. // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc() here, // override BindToTree for those classes and munge event listeners there? nsIDocument *document = OwnerDoc(); *aDefer = false; if ((win = document->GetInnerWindow())) { nsCOMPtr piTarget(do_QueryInterface(win)); return piTarget->GetOrCreateListenerManager(); } return nullptr; } return nsGenericHTMLElementBase::GetEventListenerManagerForAttr(aAttrName, aDefer); } #define EVENT(name_, id_, type_, struct_) /* nothing; handled by nsINode */ #define FORWARDED_EVENT(name_, id_, type_, struct_) \ EventHandlerNonNull* \ nsGenericHTMLElement::GetOn##name_() \ { \ if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \ /* XXXbz note to self: add tests for this! */ \ if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \ nsGlobalWindow* globalWin = nsGlobalWindow::Cast(win); \ return globalWin->GetOn##name_(); \ } \ return nullptr; \ } \ \ return nsINode::GetOn##name_(); \ } \ void \ nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) \ { \ if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \ nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \ if (!win) { \ return; \ } \ \ nsGlobalWindow* globalWin = nsGlobalWindow::Cast(win); \ return globalWin->SetOn##name_(handler); \ } \ \ return nsINode::SetOn##name_(handler); \ } #define ERROR_EVENT(name_, id_, type_, struct_) \ already_AddRefed \ nsGenericHTMLElement::GetOn##name_() \ { \ if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \ /* XXXbz note to self: add tests for this! */ \ if (nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow()) { \ nsGlobalWindow* globalWin = nsGlobalWindow::Cast(win); \ OnErrorEventHandlerNonNull* errorHandler = globalWin->GetOn##name_(); \ if (errorHandler) { \ RefPtr handler = \ new EventHandlerNonNull(errorHandler); \ return handler.forget(); \ } \ } \ return nullptr; \ } \ \ RefPtr handler = nsINode::GetOn##name_(); \ return handler.forget(); \ } \ void \ nsGenericHTMLElement::SetOn##name_(EventHandlerNonNull* handler) \ { \ if (IsAnyOfHTMLElements(nsGkAtoms::body, nsGkAtoms::frameset)) { \ nsPIDOMWindowInner* win = OwnerDoc()->GetInnerWindow(); \ if (!win) { \ return; \ } \ \ nsGlobalWindow* globalWin = nsGlobalWindow::Cast(win); \ RefPtr errorHandler; \ if (handler) { \ errorHandler = new OnErrorEventHandlerNonNull(handler); \ } \ return globalWin->SetOn##name_(errorHandler); \ } \ \ return nsINode::SetOn##name_(handler); \ } #include "mozilla/EventNameList.h" // IWYU pragma: keep #undef ERROR_EVENT #undef FORWARDED_EVENT #undef EVENT void nsGenericHTMLElement::GetBaseTarget(nsAString& aBaseTarget) const { OwnerDoc()->GetBaseTarget(aBaseTarget); } //---------------------------------------------------------------------- bool nsGenericHTMLElement::ParseAttribute(int32_t aNamespaceID, nsIAtom* aAttribute, const nsAString& aValue, nsAttrValue& aResult) { if (aNamespaceID == kNameSpaceID_None) { if (aAttribute == nsGkAtoms::dir) { return aResult.ParseEnumValue(aValue, kDirTable, false); } if (aAttribute == nsGkAtoms::tabindex) { return aResult.ParseIntValue(aValue); } if (aAttribute == nsGkAtoms::referrerpolicy) { return ParseReferrerAttribute(aValue, aResult); } if (aAttribute == nsGkAtoms::name) { // Store name as an atom. name="" means that the element has no name, // not that it has an empty string as the name. if (aValue.IsEmpty()) { return false; } aResult.ParseAtom(aValue); return true; } if (aAttribute == nsGkAtoms::contenteditable) { aResult.ParseAtom(aValue); return true; } if (aAttribute == nsGkAtoms::rel) { aResult.ParseAtomArray(aValue); return true; } } return nsGenericHTMLElementBase::ParseAttribute(aNamespaceID, aAttribute, aValue, aResult); } bool nsGenericHTMLElement::ParseBackgroundAttribute(int32_t aNamespaceID, nsIAtom* aAttribute, const nsAString& aValue, nsAttrValue& aResult) { if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::background && !aValue.IsEmpty()) { // Resolve url to an absolute url nsIDocument* doc = OwnerDoc(); nsCOMPtr baseURI = GetBaseURI(); nsCOMPtr uri; nsresult rv = nsContentUtils::NewURIWithDocumentCharset( getter_AddRefs(uri), aValue, doc, baseURI); if (NS_FAILED(rv)) { return false; } mozilla::css::URLValue *url = new mozilla::css::URLValue(uri, aValue, baseURI, doc->GetDocumentURI(), NodePrincipal()); aResult.SetTo(url, &aValue); return true; } return false; } bool nsGenericHTMLElement::IsAttributeMapped(const nsIAtom* aAttribute) const { static const MappedAttributeEntry* const map[] = { sCommonAttributeMap }; return FindAttributeDependence(aAttribute, map); } nsMapRuleToAttributesFunc nsGenericHTMLElement::GetAttributeMappingFunction() const { return &MapCommonAttributesInto; } nsIFormControlFrame* nsGenericHTMLElement::GetFormControlFrame(bool aFlushFrames) { if (aFlushFrames && IsInComposedDoc()) { // Cause a flush of the frames, so we get up-to-date frame information GetComposedDoc()->FlushPendingNotifications(FlushType::Frames); } nsIFrame* frame = GetPrimaryFrame(); if (frame) { nsIFormControlFrame* form_frame = do_QueryFrame(frame); if (form_frame) { return form_frame; } // If we have generated content, the primary frame will be a // wrapper frame.. out real frame will be in its child list. for (frame = frame->PrincipalChildList().FirstChild(); frame; frame = frame->GetNextSibling()) { form_frame = do_QueryFrame(frame); if (form_frame) { return form_frame; } } } return nullptr; } nsPresContext* nsGenericHTMLElement::GetPresContext(PresContextFor aFor) { // Get the document nsIDocument* doc = (aFor == eForComposedDoc) ? GetComposedDoc() : GetUncomposedDoc(); if (doc) { // Get presentation shell. nsIPresShell *presShell = doc->GetShell(); if (presShell) { return presShell->GetPresContext(); } } return nullptr; } static const nsAttrValue::EnumTable kDivAlignTable[] = { { "left", NS_STYLE_TEXT_ALIGN_MOZ_LEFT }, { "right", NS_STYLE_TEXT_ALIGN_MOZ_RIGHT }, { "center", NS_STYLE_TEXT_ALIGN_MOZ_CENTER }, { "middle", NS_STYLE_TEXT_ALIGN_MOZ_CENTER }, { "justify", NS_STYLE_TEXT_ALIGN_JUSTIFY }, { nullptr, 0 } }; static const nsAttrValue::EnumTable kFrameborderTable[] = { { "yes", NS_STYLE_FRAME_YES }, { "no", NS_STYLE_FRAME_NO }, { "1", NS_STYLE_FRAME_1 }, { "0", NS_STYLE_FRAME_0 }, { nullptr, 0 } }; static const nsAttrValue::EnumTable kScrollingTable[] = { { "yes", NS_STYLE_FRAME_YES }, { "no", NS_STYLE_FRAME_NO }, { "on", NS_STYLE_FRAME_ON }, { "off", NS_STYLE_FRAME_OFF }, { "scroll", NS_STYLE_FRAME_SCROLL }, { "noscroll", NS_STYLE_FRAME_NOSCROLL }, { "auto", NS_STYLE_FRAME_AUTO }, { nullptr, 0 } }; static const nsAttrValue::EnumTable kTableVAlignTable[] = { { "top", NS_STYLE_VERTICAL_ALIGN_TOP }, { "middle", NS_STYLE_VERTICAL_ALIGN_MIDDLE }, { "bottom", NS_STYLE_VERTICAL_ALIGN_BOTTOM }, { "baseline",NS_STYLE_VERTICAL_ALIGN_BASELINE }, { nullptr, 0 } }; bool nsGenericHTMLElement::ParseAlignValue(const nsAString& aString, nsAttrValue& aResult) { static const nsAttrValue::EnumTable kAlignTable[] = { { "left", NS_STYLE_TEXT_ALIGN_LEFT }, { "right", NS_STYLE_TEXT_ALIGN_RIGHT }, { "top", NS_STYLE_VERTICAL_ALIGN_TOP }, { "middle", NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE }, { "bottom", NS_STYLE_VERTICAL_ALIGN_BASELINE }, { "center", NS_STYLE_VERTICAL_ALIGN_MIDDLE_WITH_BASELINE }, { "baseline", NS_STYLE_VERTICAL_ALIGN_BASELINE }, { "texttop", NS_STYLE_VERTICAL_ALIGN_TEXT_TOP }, { "absmiddle", NS_STYLE_VERTICAL_ALIGN_MIDDLE }, { "abscenter", NS_STYLE_VERTICAL_ALIGN_MIDDLE }, { "absbottom", NS_STYLE_VERTICAL_ALIGN_BOTTOM }, { nullptr, 0 } }; return aResult.ParseEnumValue(aString, kAlignTable, false); } //---------------------------------------- static const nsAttrValue::EnumTable kTableHAlignTable[] = { { "left", NS_STYLE_TEXT_ALIGN_LEFT }, { "right", NS_STYLE_TEXT_ALIGN_RIGHT }, { "center", NS_STYLE_TEXT_ALIGN_CENTER }, { "char", NS_STYLE_TEXT_ALIGN_CHAR }, { "justify",NS_STYLE_TEXT_ALIGN_JUSTIFY }, { nullptr, 0 } }; bool nsGenericHTMLElement::ParseTableHAlignValue(const nsAString& aString, nsAttrValue& aResult) { return aResult.ParseEnumValue(aString, kTableHAlignTable, false); } //---------------------------------------- // This table is used for td, th, tr, col, thead, tbody and tfoot. static const nsAttrValue::EnumTable kTableCellHAlignTable[] = { { "left", NS_STYLE_TEXT_ALIGN_MOZ_LEFT }, { "right", NS_STYLE_TEXT_ALIGN_MOZ_RIGHT }, { "center", NS_STYLE_TEXT_ALIGN_MOZ_CENTER }, { "char", NS_STYLE_TEXT_ALIGN_CHAR }, { "justify",NS_STYLE_TEXT_ALIGN_JUSTIFY }, { "middle", NS_STYLE_TEXT_ALIGN_MOZ_CENTER }, { "absmiddle", NS_STYLE_TEXT_ALIGN_CENTER }, { nullptr, 0 } }; bool nsGenericHTMLElement::ParseTableCellHAlignValue(const nsAString& aString, nsAttrValue& aResult) { return aResult.ParseEnumValue(aString, kTableCellHAlignTable, false); } //---------------------------------------- bool nsGenericHTMLElement::ParseTableVAlignValue(const nsAString& aString, nsAttrValue& aResult) { return aResult.ParseEnumValue(aString, kTableVAlignTable, false); } bool nsGenericHTMLElement::ParseDivAlignValue(const nsAString& aString, nsAttrValue& aResult) { return aResult.ParseEnumValue(aString, kDivAlignTable, false); } bool nsGenericHTMLElement::ParseImageAttribute(nsIAtom* aAttribute, const nsAString& aString, nsAttrValue& aResult) { if ((aAttribute == nsGkAtoms::width) || (aAttribute == nsGkAtoms::height)) { return aResult.ParseSpecialIntValue(aString); } if ((aAttribute == nsGkAtoms::hspace) || (aAttribute == nsGkAtoms::vspace) || (aAttribute == nsGkAtoms::border)) { return aResult.ParseIntWithBounds(aString, 0); } return false; } bool nsGenericHTMLElement::ParseReferrerAttribute(const nsAString& aString, nsAttrValue& aResult) { static const nsAttrValue::EnumTable kReferrerTable[] = { { net::kRPS_No_Referrer, static_cast(net::RP_No_Referrer) }, { net::kRPS_Origin, static_cast(net::RP_Origin) }, { net::kRPS_Origin_When_Cross_Origin, static_cast(net::RP_Origin_When_Crossorigin) }, { net::kRPS_No_Referrer_When_Downgrade, static_cast(net::RP_No_Referrer_When_Downgrade) }, { net::kRPS_Unsafe_URL, static_cast(net::RP_Unsafe_URL) }, { net::kRPS_Strict_Origin, static_cast(net::RP_Strict_Origin) }, { net::kRPS_Same_Origin, static_cast(net::RP_Same_Origin) }, { net::kRPS_Strict_Origin_When_Cross_Origin, static_cast(net::RP_Strict_Origin_When_Cross_Origin) }, { nullptr, 0 } }; return aResult.ParseEnumValue(aString, kReferrerTable, false); } bool nsGenericHTMLElement::ParseFrameborderValue(const nsAString& aString, nsAttrValue& aResult) { return aResult.ParseEnumValue(aString, kFrameborderTable, false); } bool nsGenericHTMLElement::ParseScrollingValue(const nsAString& aString, nsAttrValue& aResult) { return aResult.ParseEnumValue(aString, kScrollingTable, false); } static inline void MapLangAttributeInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { if (!aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Font) | NS_STYLE_INHERIT_BIT(Text))) { return; } const nsAttrValue* langValue = aAttributes->GetAttr(nsGkAtoms::lang); if (!langValue) { return; } MOZ_ASSERT(langValue->Type() == nsAttrValue::eAtom); if (aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Font))) { aData->SetIdentAtomValueIfUnset(eCSSProperty__x_lang, langValue->GetAtomValue()); } if (aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Text))) { if (!aData->PropertyIsSet(eCSSProperty_text_emphasis_position)) { const nsIAtom* lang = langValue->GetAtomValue(); if (nsStyleUtil::MatchesLanguagePrefix(lang, u"zh")) { aData->SetKeywordValue(eCSSProperty_text_emphasis_position, NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT_ZH); } else if (nsStyleUtil::MatchesLanguagePrefix(lang, u"ja") || nsStyleUtil::MatchesLanguagePrefix(lang, u"mn")) { // This branch is currently no part of the spec. // See bug 1040668 comment 69 and comment 75. aData->SetKeywordValue(eCSSProperty_text_emphasis_position, NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT); } } } } /** * Handle attributes common to all html elements */ void nsGenericHTMLElement::MapCommonAttributesIntoExceptHidden(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { if (aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(UserInterface))) { if (!aData->PropertyIsSet(eCSSProperty__moz_user_modify)) { const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::contenteditable); if (value) { if (value->Equals(nsGkAtoms::_empty, eCaseMatters) || value->Equals(nsGkAtoms::_true, eIgnoreCase)) { aData->SetKeywordValue(eCSSProperty__moz_user_modify, StyleUserModify::ReadWrite); } else if (value->Equals(nsGkAtoms::_false, eIgnoreCase)) { aData->SetKeywordValue(eCSSProperty__moz_user_modify, StyleUserModify::ReadOnly); } } } } MapLangAttributeInto(aAttributes, aData); } void nsGenericHTMLElement::MapCommonAttributesInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { MapCommonAttributesIntoExceptHidden(aAttributes, aData); if (aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Display))) { if (!aData->PropertyIsSet(eCSSProperty_display)) { if (aAttributes->IndexOfAttr(nsGkAtoms::hidden) >= 0) { aData->SetKeywordValue(eCSSProperty_display, StyleDisplay::None); } } } } /* static */ const nsGenericHTMLElement::MappedAttributeEntry nsGenericHTMLElement::sCommonAttributeMap[] = { { &nsGkAtoms::contenteditable }, { &nsGkAtoms::lang }, { &nsGkAtoms::hidden }, { nullptr } }; /* static */ const Element::MappedAttributeEntry nsGenericHTMLElement::sImageMarginSizeAttributeMap[] = { { &nsGkAtoms::width }, { &nsGkAtoms::height }, { &nsGkAtoms::hspace }, { &nsGkAtoms::vspace }, { nullptr } }; /* static */ const Element::MappedAttributeEntry nsGenericHTMLElement::sImageAlignAttributeMap[] = { { &nsGkAtoms::align }, { nullptr } }; /* static */ const Element::MappedAttributeEntry nsGenericHTMLElement::sDivAlignAttributeMap[] = { { &nsGkAtoms::align }, { nullptr } }; /* static */ const Element::MappedAttributeEntry nsGenericHTMLElement::sImageBorderAttributeMap[] = { { &nsGkAtoms::border }, { nullptr } }; /* static */ const Element::MappedAttributeEntry nsGenericHTMLElement::sBackgroundAttributeMap[] = { { &nsGkAtoms::background }, { &nsGkAtoms::bgcolor }, { nullptr } }; /* static */ const Element::MappedAttributeEntry nsGenericHTMLElement::sBackgroundColorAttributeMap[] = { { &nsGkAtoms::bgcolor }, { nullptr } }; void nsGenericHTMLElement::MapImageAlignAttributeInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { if (aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Display))) { const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align); if (value && value->Type() == nsAttrValue::eEnum) { int32_t align = value->GetEnumValue(); if (!aData->PropertyIsSet(eCSSProperty_float_)) { if (align == NS_STYLE_TEXT_ALIGN_LEFT) { aData->SetKeywordValue(eCSSProperty_float_, StyleFloat::Left); } else if (align == NS_STYLE_TEXT_ALIGN_RIGHT) { aData->SetKeywordValue(eCSSProperty_float_, StyleFloat::Right); } } if (!aData->PropertyIsSet(eCSSProperty_vertical_align)) { switch (align) { case NS_STYLE_TEXT_ALIGN_LEFT: case NS_STYLE_TEXT_ALIGN_RIGHT: break; default: aData->SetKeywordValue(eCSSProperty_vertical_align, align); break; } } } } } void nsGenericHTMLElement::MapDivAlignAttributeInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { if (aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Text))) { if (!aData->PropertyIsSet(eCSSProperty_text_align)) { // align: enum const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align); if (value && value->Type() == nsAttrValue::eEnum) aData->SetKeywordValue(eCSSProperty_text_align, value->GetEnumValue()); } } } void nsGenericHTMLElement::MapVAlignAttributeInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { if (aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Display))) { if (!aData->PropertyIsSet(eCSSProperty_vertical_align)) { // align: enum const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::valign); if (value && value->Type() == nsAttrValue::eEnum) aData->SetKeywordValue(eCSSProperty_vertical_align, value->GetEnumValue()); } } } void nsGenericHTMLElement::MapImageMarginAttributeInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { if (!aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Margin))) return; const nsAttrValue* value; // hspace: value value = aAttributes->GetAttr(nsGkAtoms::hspace); if (value) { if (value->Type() == nsAttrValue::eInteger) { aData->SetPixelValueIfUnset(eCSSProperty_margin_left, (float)value->GetIntegerValue()); aData->SetPixelValueIfUnset(eCSSProperty_margin_right, (float)value->GetIntegerValue()); } else if (value->Type() == nsAttrValue::ePercent) { aData->SetPercentValueIfUnset(eCSSProperty_margin_left, value->GetPercentValue()); aData->SetPercentValueIfUnset(eCSSProperty_margin_right, value->GetPercentValue()); } } // vspace: value value = aAttributes->GetAttr(nsGkAtoms::vspace); if (value) { if (value->Type() == nsAttrValue::eInteger) { aData->SetPixelValueIfUnset(eCSSProperty_margin_top, (float)value->GetIntegerValue()); aData->SetPixelValueIfUnset(eCSSProperty_margin_bottom, (float)value->GetIntegerValue()); } else if (value->Type() == nsAttrValue::ePercent) { aData->SetPercentValueIfUnset(eCSSProperty_margin_top, value->GetPercentValue()); aData->SetPercentValueIfUnset(eCSSProperty_margin_bottom, value->GetPercentValue()); } } } void nsGenericHTMLElement::MapWidthAttributeInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { if (!aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Position))) { return; } // width: value if (!aData->PropertyIsSet(eCSSProperty_width)) { const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width); if (value && value->Type() == nsAttrValue::eInteger) { aData->SetPixelValue(eCSSProperty_width, (float)value->GetIntegerValue()); } else if (value && value->Type() == nsAttrValue::ePercent) { aData->SetPercentValue(eCSSProperty_width, value->GetPercentValue()); } } } void nsGenericHTMLElement::MapHeightAttributeInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { if (!aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Position))) return; // height: value if (!aData->PropertyIsSet(eCSSProperty_height)) { const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::height); if (value && value->Type() == nsAttrValue::eInteger) { aData->SetPixelValue(eCSSProperty_height, (float)value->GetIntegerValue()); } else if (value && value->Type() == nsAttrValue::ePercent) { aData->SetPercentValue(eCSSProperty_height, value->GetPercentValue()); } } } void nsGenericHTMLElement::MapImageSizeAttributesInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { nsGenericHTMLElement::MapWidthAttributeInto(aAttributes, aData); nsGenericHTMLElement::MapHeightAttributeInto(aAttributes, aData); } void nsGenericHTMLElement::MapImageBorderAttributeInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { if (!(aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Border)))) return; // border: pixels const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::border); if (!value) return; nscoord val = 0; if (value->Type() == nsAttrValue::eInteger) val = value->GetIntegerValue(); aData->SetPixelValueIfUnset(eCSSProperty_border_top_width, (float)val); aData->SetPixelValueIfUnset(eCSSProperty_border_right_width, (float)val); aData->SetPixelValueIfUnset(eCSSProperty_border_bottom_width, (float)val); aData->SetPixelValueIfUnset(eCSSProperty_border_left_width, (float)val); aData->SetKeywordValueIfUnset(eCSSProperty_border_top_style, NS_STYLE_BORDER_STYLE_SOLID); aData->SetKeywordValueIfUnset(eCSSProperty_border_right_style, NS_STYLE_BORDER_STYLE_SOLID); aData->SetKeywordValueIfUnset(eCSSProperty_border_bottom_style, NS_STYLE_BORDER_STYLE_SOLID); aData->SetKeywordValueIfUnset(eCSSProperty_border_left_style, NS_STYLE_BORDER_STYLE_SOLID); aData->SetCurrentColorIfUnset(eCSSProperty_border_top_color); aData->SetCurrentColorIfUnset(eCSSProperty_border_right_color); aData->SetCurrentColorIfUnset(eCSSProperty_border_bottom_color); aData->SetCurrentColorIfUnset(eCSSProperty_border_left_color); } void nsGenericHTMLElement::MapBackgroundInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { if (!aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Background))) return; if (!aData->PropertyIsSet(eCSSProperty_background_image) && aData->PresContext()->UseDocumentColors()) { // background nsAttrValue* value = const_cast(aAttributes->GetAttr(nsGkAtoms::background)); if (value) { aData->SetBackgroundImage(*value); } } } void nsGenericHTMLElement::MapBGColorInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { if (!aData->ShouldComputeStyleStruct(NS_STYLE_INHERIT_BIT(Background))) return; if (!aData->PropertyIsSet(eCSSProperty_background_color) && aData->PresContext()->UseDocumentColors()) { const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::bgcolor); nscolor color; if (value && value->GetColorValue(color)) { aData->SetColorValue(eCSSProperty_background_color, color); } } } void nsGenericHTMLElement::MapBackgroundAttributesInto(const nsMappedAttributes* aAttributes, GenericSpecifiedValues* aData) { MapBackgroundInto(aAttributes, aData); MapBGColorInto(aAttributes, aData); } //---------------------------------------------------------------------- nsresult nsGenericHTMLElement::SetAttrHelper(nsIAtom* aAttr, const nsAString& aValue) { return SetAttr(kNameSpaceID_None, aAttr, aValue, true); } int32_t nsGenericHTMLElement::GetIntAttr(nsIAtom* aAttr, int32_t aDefault) const { const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(aAttr); if (attrVal && attrVal->Type() == nsAttrValue::eInteger) { return attrVal->GetIntegerValue(); } return aDefault; } nsresult nsGenericHTMLElement::SetIntAttr(nsIAtom* aAttr, int32_t aValue) { nsAutoString value; value.AppendInt(aValue); return SetAttr(kNameSpaceID_None, aAttr, value, true); } uint32_t nsGenericHTMLElement::GetUnsignedIntAttr(nsIAtom* aAttr, uint32_t aDefault) const { const nsAttrValue* attrVal = mAttrsAndChildren.GetAttr(aAttr); if (!attrVal || attrVal->Type() != nsAttrValue::eInteger) { return aDefault; } return attrVal->GetIntegerValue(); } void nsGenericHTMLElement::GetURIAttr(nsIAtom* aAttr, nsIAtom* aBaseAttr, nsAString& aResult) const { nsCOMPtr uri; bool hadAttr = GetURIAttr(aAttr, aBaseAttr, getter_AddRefs(uri)); if (!hadAttr) { aResult.Truncate(); return; } if (!uri) { // Just return the attr value GetAttr(kNameSpaceID_None, aAttr, aResult); return; } nsAutoCString spec; uri->GetSpec(spec); CopyUTF8toUTF16(spec, aResult); } bool nsGenericHTMLElement::GetURIAttr(nsIAtom* aAttr, nsIAtom* aBaseAttr, nsIURI** aURI) const { *aURI = nullptr; const nsAttrValue* attr = mAttrsAndChildren.GetAttr(aAttr); if (!attr) { return false; } nsCOMPtr baseURI = GetBaseURI(); if (aBaseAttr) { nsAutoString baseAttrValue; if (GetAttr(kNameSpaceID_None, aBaseAttr, baseAttrValue)) { nsCOMPtr baseAttrURI; nsresult rv = nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(baseAttrURI), baseAttrValue, OwnerDoc(), baseURI); if (NS_FAILED(rv)) { return true; } baseURI.swap(baseAttrURI); } } // Don't care about return value. If it fails, we still want to // return true, and *aURI will be null. nsContentUtils::NewURIWithDocumentCharset(aURI, attr->GetStringValue(), OwnerDoc(), baseURI); return true; } HTMLMenuElement* nsGenericHTMLElement::GetContextMenu() const { nsAutoString value; GetHTMLAttr(nsGkAtoms::contextmenu, value); if (!value.IsEmpty()) { //XXXsmaug How should this work in Shadow DOM? nsIDocument* doc = GetUncomposedDoc(); if (doc) { return HTMLMenuElement::FromContentOrNull(doc->GetElementById(value)); } } return nullptr; } bool nsGenericHTMLElement::IsLabelable() const { return IsAnyOfHTMLElements(nsGkAtoms::progress, nsGkAtoms::meter); } /* static */ bool nsGenericHTMLElement::MatchLabelsElement(Element* aElement, int32_t aNamespaceID, nsIAtom* aAtom, void* aData) { HTMLLabelElement* element = HTMLLabelElement::FromContent(aElement); return element && element->GetControl() == aData; } already_AddRefed nsGenericHTMLElement::Labels() { MOZ_ASSERT(IsLabelable(), "Labels() only allow labelable elements to use it."); nsExtendedDOMSlots* slots = ExtendedDOMSlots(); if (!slots->mLabelsList) { slots->mLabelsList = new nsLabelsNodeList(SubtreeRoot(), MatchLabelsElement, nullptr, this); } RefPtr labels = slots->mLabelsList; return labels.forget(); } bool nsGenericHTMLElement::IsInteractiveHTMLContent(bool aIgnoreTabindex) const { return IsAnyOfHTMLElements(nsGkAtoms::details, nsGkAtoms::embed, nsGkAtoms::keygen) || (!aIgnoreTabindex && HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)); } // static bool nsGenericHTMLElement::TouchEventsEnabled(JSContext* aCx, JSObject* aGlobal) { return TouchEvent::PrefEnabled(aCx, aGlobal); } //---------------------------------------------------------------------- nsGenericHTMLFormElement::nsGenericHTMLFormElement(already_AddRefed& aNodeInfo, uint8_t aType) : nsGenericHTMLElement(aNodeInfo) , nsIFormControl(aType) , mForm(nullptr) , mFieldSet(nullptr) { // We should add the NS_EVENT_STATE_ENABLED bit here as needed, but // that depends on our type, which is not initialized yet. So we // have to do this in subclasses. } nsGenericHTMLFormElement::~nsGenericHTMLFormElement() { if (mFieldSet) { mFieldSet->RemoveElement(this); } // Check that this element doesn't know anything about its form at this point. NS_ASSERTION(!mForm, "mForm should be null at this point!"); } NS_IMPL_ISUPPORTS_INHERITED(nsGenericHTMLFormElement, nsGenericHTMLElement, nsIFormControl) nsINode* nsGenericHTMLFormElement::GetScopeChainParent() const { return mForm ? mForm : nsGenericHTMLElement::GetScopeChainParent(); } bool nsGenericHTMLFormElement::IsNodeOfType(uint32_t aFlags) const { return !(aFlags & ~(eCONTENT | eHTML_FORM_CONTROL)); } void nsGenericHTMLFormElement::SaveSubtreeState() { SaveState(); nsGenericHTMLElement::SaveSubtreeState(); } void nsGenericHTMLFormElement::SetForm(nsIDOMHTMLFormElement* aForm) { NS_PRECONDITION(aForm, "Don't pass null here"); NS_ASSERTION(!mForm, "We don't support switching from one non-null form to another."); SetForm(static_cast(aForm), false); } void nsGenericHTMLFormElement::SetForm(HTMLFormElement* aForm, bool aBindToTree) { if (aForm) { BeforeSetForm(aBindToTree); } // keep a *weak* ref to the form here mForm = aForm; } void nsGenericHTMLFormElement::ClearForm(bool aRemoveFromForm, bool aUnbindOrDelete) { NS_ASSERTION((mForm != nullptr) == HasFlag(ADDED_TO_FORM), "Form control should have had flag set correctly"); if (!mForm) { return; } if (aRemoveFromForm) { nsAutoString nameVal, idVal; GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal); GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal); mForm->RemoveElement(this, true); if (!nameVal.IsEmpty()) { mForm->RemoveElementFromTable(this, nameVal); } if (!idVal.IsEmpty()) { mForm->RemoveElementFromTable(this, idVal); } } UnsetFlags(ADDED_TO_FORM); mForm = nullptr; AfterClearForm(aUnbindOrDelete); } Element* nsGenericHTMLFormElement::GetFormElement() { return mForm; } HTMLFieldSetElement* nsGenericHTMLFormElement::GetFieldSet() { return mFieldSet; } nsresult nsGenericHTMLFormElement::GetForm(nsIDOMHTMLFormElement** aForm) { NS_ENSURE_ARG_POINTER(aForm); NS_IF_ADDREF(*aForm = mForm); return NS_OK; } nsIContent::IMEState nsGenericHTMLFormElement::GetDesiredIMEState() { TextEditor* textEditor = GetTextEditorInternal(); if (!textEditor) { return nsGenericHTMLElement::GetDesiredIMEState(); } IMEState state; nsresult rv = textEditor->GetPreferredIMEState(&state); if (NS_FAILED(rv)) { return nsGenericHTMLElement::GetDesiredIMEState(); } return state; } nsresult nsGenericHTMLFormElement::BindToTree(nsIDocument* aDocument, nsIContent* aParent, nsIContent* aBindingParent, bool aCompileEventHandlers) { nsresult rv = nsGenericHTMLElement::BindToTree(aDocument, aParent, aBindingParent, aCompileEventHandlers); NS_ENSURE_SUCCESS(rv, rv); // An autofocus event has to be launched if the autofocus attribute is // specified and the element accept the autofocus attribute. In addition, // the document should not be already loaded and the "browser.autofocus" // preference should be 'true'. if (IsAutofocusable() && HasAttr(kNameSpaceID_None, nsGkAtoms::autofocus) && nsContentUtils::AutoFocusEnabled()) { nsCOMPtr event = new nsAutoFocusEvent(this); rv = NS_DispatchToCurrentThread(event); NS_ENSURE_SUCCESS(rv, rv); } // If @form is set, the element *has* to be in a document, otherwise it // wouldn't be possible to find an element with the corresponding id. // If @form isn't set, the element *has* to have a parent, otherwise it // wouldn't be possible to find a form ancestor. // We should not call UpdateFormOwner if none of these conditions are // fulfilled. if (HasAttr(kNameSpaceID_None, nsGkAtoms::form) ? !!GetUncomposedDoc() : !!aParent) { UpdateFormOwner(true, nullptr); } // Set parent fieldset which should be used for the disabled state. UpdateFieldSet(false); return NS_OK; } void nsGenericHTMLFormElement::UnbindFromTree(bool aDeep, bool aNullParent) { // Save state before doing anything SaveState(); if (mForm) { // Might need to unset mForm if (aNullParent) { // No more parent means no more form ClearForm(true, true); } else { // Recheck whether we should still have an mForm. if (HasAttr(kNameSpaceID_None, nsGkAtoms::form) || !FindAncestorForm(mForm)) { ClearForm(true, true); } else { UnsetFlags(MAYBE_ORPHAN_FORM_ELEMENT); } } if (!mForm) { // Our novalidate state might have changed UpdateState(false); } } // We have to remove the form id observer if there was one. // We will re-add one later if needed (during bind to tree). if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None, nsGkAtoms::form)) { RemoveFormIdObserver(); } nsGenericHTMLElement::UnbindFromTree(aDeep, aNullParent); // The element might not have a fieldset anymore. UpdateFieldSet(false); } nsresult nsGenericHTMLFormElement::BeforeSetAttr(int32_t aNameSpaceID, nsIAtom* aName, const nsAttrValueOrString* aValue, bool aNotify) { if (aNameSpaceID == kNameSpaceID_None) { nsAutoString tmp; // remove the control from the hashtable as needed if (mForm && (aName == nsGkAtoms::name || aName == nsGkAtoms::id)) { GetAttr(kNameSpaceID_None, aName, tmp); if (!tmp.IsEmpty()) { mForm->RemoveElementFromTable(this, tmp); } } if (mForm && aName == nsGkAtoms::type) { GetAttr(kNameSpaceID_None, nsGkAtoms::name, tmp); if (!tmp.IsEmpty()) { mForm->RemoveElementFromTable(this, tmp); } GetAttr(kNameSpaceID_None, nsGkAtoms::id, tmp); if (!tmp.IsEmpty()) { mForm->RemoveElementFromTable(this, tmp); } mForm->RemoveElement(this, false); } if (aName == nsGkAtoms::form) { // If @form isn't set or set to the empty string, there were no observer // so we don't have to remove it. if (nsContentUtils::HasNonEmptyAttr(this, kNameSpaceID_None, nsGkAtoms::form)) { // The current form id observer is no longer needed. // A new one may be added in AfterSetAttr. RemoveFormIdObserver(); } } } return nsGenericHTMLElement::BeforeSetAttr(aNameSpaceID, aName, aValue, aNotify); } nsresult nsGenericHTMLFormElement::AfterSetAttr(int32_t aNameSpaceID, nsIAtom* aName, const nsAttrValue* aValue, const nsAttrValue* aOldValue, bool aNotify) { if (aNameSpaceID == kNameSpaceID_None) { // add the control to the hashtable as needed if (mForm && (aName == nsGkAtoms::name || aName == nsGkAtoms::id) && aValue && !aValue->IsEmptyString()) { MOZ_ASSERT(aValue->Type() == nsAttrValue::eAtom, "Expected atom value for name/id"); mForm->AddElementToTable(this, nsDependentAtomString(aValue->GetAtomValue())); } if (mForm && aName == nsGkAtoms::type) { nsAutoString tmp; GetAttr(kNameSpaceID_None, nsGkAtoms::name, tmp); if (!tmp.IsEmpty()) { mForm->AddElementToTable(this, tmp); } GetAttr(kNameSpaceID_None, nsGkAtoms::id, tmp); if (!tmp.IsEmpty()) { mForm->AddElementToTable(this, tmp); } mForm->AddElement(this, false, aNotify); } if (aName == nsGkAtoms::form) { // We need a new form id observer. //XXXsmaug How should this work in Shadow DOM? nsIDocument* doc = GetUncomposedDoc(); if (doc) { Element* formIdElement = nullptr; if (aValue && !aValue->IsEmptyString()) { formIdElement = AddFormIdObserver(); } // Because we have a new @form value (or no more @form), we have to // update our form owner. UpdateFormOwner(false, formIdElement); } } } return nsGenericHTMLElement::AfterSetAttr(aNameSpaceID, aName, aValue, aOldValue, aNotify); } nsresult nsGenericHTMLFormElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { if (aVisitor.mEvent->IsTrusted() && (aVisitor.mEvent->mMessage == eFocus || aVisitor.mEvent->mMessage == eBlur)) { // We have to handle focus/blur event to change focus states in // PreHandleEvent to prevent it breaks event target chain creation. aVisitor.mWantsPreHandleEvent = true; } return nsGenericHTMLElement::GetEventTargetParent(aVisitor); } nsresult nsGenericHTMLFormElement::PreHandleEvent(EventChainVisitor& aVisitor) { if (aVisitor.mEvent->IsTrusted()) { switch (aVisitor.mEvent->mMessage) { case eFocus: { // Check to see if focus has bubbled up from a form control's // child textfield or button. If that's the case, don't focus // this parent file control -- leave focus on the child. nsIFormControlFrame* formControlFrame = GetFormControlFrame(true); if (formControlFrame && aVisitor.mEvent->mOriginalTarget == static_cast(this)) formControlFrame->SetFocus(true, true); break; } case eBlur: { nsIFormControlFrame* formControlFrame = GetFormControlFrame(true); if (formControlFrame) formControlFrame->SetFocus(false, false); break; } default: break; } } return nsGenericHTMLElement::PreHandleEvent(aVisitor); } /* virtual */ bool nsGenericHTMLFormElement::IsDisabled() const { return State().HasState(NS_EVENT_STATE_DISABLED); } void nsGenericHTMLFormElement::ForgetFieldSet(nsIContent* aFieldset) { if (mFieldSet == aFieldset) { mFieldSet = nullptr; } } bool nsGenericHTMLFormElement::CanBeDisabled() const { int32_t type = ControlType(); // It's easier to test the types that _cannot_ be disabled return type != NS_FORM_OBJECT && type != NS_FORM_OUTPUT; } bool nsGenericHTMLFormElement::IsHTMLFocusable(bool aWithMouse, bool* aIsFocusable, int32_t* aTabIndex) { if (nsGenericHTMLElement::IsHTMLFocusable(aWithMouse, aIsFocusable, aTabIndex)) { return true; } #ifdef XP_MACOSX *aIsFocusable = (!aWithMouse || nsFocusManager::sMouseFocusesFormControl) && *aIsFocusable; #endif return false; } EventStates nsGenericHTMLFormElement::IntrinsicState() const { // If you add attribute-dependent states here, you need to add them them to // AfterSetAttr too. And add them to AfterSetAttr for all subclasses that // implement IntrinsicState() and are affected by that attribute. EventStates state = nsGenericHTMLElement::IntrinsicState(); if (mForm && mForm->IsDefaultSubmitElement(this)) { NS_ASSERTION(IsSubmitControl(), "Default submit element that isn't a submit control."); // We are the default submit element (:default) state |= NS_EVENT_STATE_DEFAULT; } // Make the text controls read-write if (!state.HasState(NS_EVENT_STATE_MOZ_READWRITE) && IsTextOrNumberControl(/*aExcludePassword*/ false)) { bool roState = GetBoolAttr(nsGkAtoms::readonly); if (!roState) { state |= NS_EVENT_STATE_MOZ_READWRITE; state &= ~NS_EVENT_STATE_MOZ_READONLY; } } return state; } nsGenericHTMLFormElement::FocusTristate nsGenericHTMLFormElement::FocusState() { // We can't be focused if we aren't in a (composed) document nsIDocument* doc = GetComposedDoc(); if (!doc) return eUnfocusable; // first see if we are disabled or not. If disabled then do nothing. if (IsDisabled()) { return eUnfocusable; } // If the window is not active, do not allow the focus to bring the // window to the front. We update the focus controller, but do // nothing else. if (nsPIDOMWindowOuter* win = doc->GetWindow()) { nsCOMPtr rootWindow = win->GetPrivateRoot(); nsCOMPtr fm = do_GetService(FOCUSMANAGER_CONTRACTID); if (fm && rootWindow) { nsCOMPtr activeWindow; fm->GetActiveWindow(getter_AddRefs(activeWindow)); if (activeWindow == rootWindow) { return eActiveWindow; } } } return eInactiveWindow; } Element* nsGenericHTMLFormElement::AddFormIdObserver() { NS_ASSERTION(GetUncomposedDoc(), "When adding a form id observer, " "we should be in a document!"); nsAutoString formId; nsIDocument* doc = OwnerDoc(); GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId); NS_ASSERTION(!formId.IsEmpty(), "@form value should not be the empty string!"); RefPtr atom = NS_Atomize(formId); return doc->AddIDTargetObserver(atom, FormIdUpdated, this, false); } void nsGenericHTMLFormElement::RemoveFormIdObserver() { /** * We are using OwnerDoc() because we don't really care about having the * element actually being in the tree. If it is not and @form value changes, * this method will be called for nothing but removing an observer which does * not exist doesn't cost so much (no entry in the hash table) so having a * boolean for GetUncomposedDoc()/GetOwnerDoc() would make everything look * more complex for nothing. */ nsIDocument* doc = OwnerDoc(); // At this point, we may not have a document anymore. In that case, we can't // remove the observer. The document did that for us. if (!doc) { return; } nsAutoString formId; GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId); NS_ASSERTION(!formId.IsEmpty(), "@form value should not be the empty string!"); RefPtr atom = NS_Atomize(formId); doc->RemoveIDTargetObserver(atom, FormIdUpdated, this, false); } /* static */ bool nsGenericHTMLFormElement::FormIdUpdated(Element* aOldElement, Element* aNewElement, void* aData) { nsGenericHTMLFormElement* element = static_cast(aData); NS_ASSERTION(element->IsHTMLElement(), "aData should be an HTML element"); element->UpdateFormOwner(false, aNewElement); return true; } bool nsGenericHTMLFormElement::IsElementDisabledForEvents(EventMessage aMessage, nsIFrame* aFrame) { switch (aMessage) { case eMouseMove: case eMouseOver: case eMouseOut: case eMouseEnter: case eMouseLeave: case ePointerMove: case ePointerOver: case ePointerOut: case ePointerEnter: case ePointerLeave: case eWheel: case eLegacyMouseLineOrPageScroll: case eLegacyMousePixelScroll: return false; default: break; } bool disabled = IsDisabled(); if (!disabled && aFrame) { const nsStyleUserInterface* uiStyle = aFrame->StyleUserInterface(); disabled = uiStyle->mUserInput == StyleUserInput::None || uiStyle->mUserInput == StyleUserInput::Disabled; } return disabled; } void nsGenericHTMLFormElement::UpdateFormOwner(bool aBindToTree, Element* aFormIdElement) { NS_PRECONDITION(!aBindToTree || !aFormIdElement, "aFormIdElement shouldn't be set if aBindToTree is true!"); bool needStateUpdate = false; if (!aBindToTree) { needStateUpdate = mForm && mForm->IsDefaultSubmitElement(this); ClearForm(true, false); } HTMLFormElement *oldForm = mForm; if (!mForm) { // If @form is set, we have to use that to find the form. nsAutoString formId; if (GetAttr(kNameSpaceID_None, nsGkAtoms::form, formId)) { if (!formId.IsEmpty()) { Element* element = nullptr; if (aBindToTree) { element = AddFormIdObserver(); } else { element = aFormIdElement; } NS_ASSERTION(GetUncomposedDoc(), "The element should be in a document " "when UpdateFormOwner is called!"); NS_ASSERTION(!GetUncomposedDoc() || element == GetUncomposedDoc()->GetElementById(formId), "element should be equals to the current element " "associated with the id in @form!"); if (element && element->IsHTMLElement(nsGkAtoms::form) && nsContentUtils::IsInSameAnonymousTree(this, element)) { SetForm(static_cast(element), aBindToTree); } } } else { // We now have a parent, so we may have picked up an ancestor form. Search // for it. Note that if mForm is already set we don't want to do this, // because that means someone (probably the content sink) has already set // it to the right value. Also note that even if being bound here didn't // change our parent, we still need to search, since our parent chain // probably changed _somewhere_. SetForm(FindAncestorForm(), aBindToTree); } } if (mForm && !HasFlag(ADDED_TO_FORM)) { // Now we need to add ourselves to the form nsAutoString nameVal, idVal; GetAttr(kNameSpaceID_None, nsGkAtoms::name, nameVal); GetAttr(kNameSpaceID_None, nsGkAtoms::id, idVal); SetFlags(ADDED_TO_FORM); // Notify only if we just found this mForm. mForm->AddElement(this, true, oldForm == nullptr); if (!nameVal.IsEmpty()) { mForm->AddElementToTable(this, nameVal); } if (!idVal.IsEmpty()) { mForm->AddElementToTable(this, idVal); } } if (mForm != oldForm || needStateUpdate) { UpdateState(true); } } void nsGenericHTMLFormElement::UpdateFieldSet(bool aNotify) { nsIContent* parent = nullptr; nsIContent* prev = nullptr; // Don't walk out of anonymous subtrees. Note the similar code in // nsGenericHTMLElement::FindAncestorForm. nsIContent* bindingParent = GetBindingParent(); for (parent = GetParent(); parent && parent != bindingParent; prev = parent, parent = parent->GetParent()) { HTMLFieldSetElement* fieldset = HTMLFieldSetElement::FromContent(parent); if (fieldset && (!prev || fieldset->GetFirstLegend() != prev)) { if (mFieldSet == fieldset) { // We already have the right fieldset; return; } if (mFieldSet) { mFieldSet->RemoveElement(this); } mFieldSet = fieldset; fieldset->AddElement(this); // The disabled state may have changed FieldSetDisabledChanged(aNotify); return; } } // No fieldset found. if (mFieldSet) { mFieldSet->RemoveElement(this); mFieldSet = nullptr; // The disabled state may have changed FieldSetDisabledChanged(aNotify); } } void nsGenericHTMLFormElement::UpdateDisabledState(bool aNotify) { if (!CanBeDisabled()) { return; } bool isDisabled = HasAttr(kNameSpaceID_None, nsGkAtoms::disabled); if (!isDisabled && mFieldSet) { isDisabled = mFieldSet->IsDisabled(); } EventStates disabledStates; if (isDisabled) { disabledStates |= NS_EVENT_STATE_DISABLED; } else { disabledStates |= NS_EVENT_STATE_ENABLED; } EventStates oldDisabledStates = State() & DISABLED_STATES; EventStates changedStates = disabledStates ^ oldDisabledStates; if (!changedStates.IsEmpty()) { ToggleStates(changedStates, aNotify); } } void nsGenericHTMLFormElement::UpdateRequiredState(bool aIsRequired, bool aNotify) { #ifdef DEBUG int32_t type = ControlType(); #endif MOZ_ASSERT((type & NS_FORM_INPUT_ELEMENT) || type == NS_FORM_SELECT || type == NS_FORM_TEXTAREA, "This should be called only on types that @required applies"); #ifdef DEBUG HTMLInputElement* input = HTMLInputElement::FromContent(this); if (input) { MOZ_ASSERT(input->DoesRequiredApply(), "This should be called only on input types that @required applies"); } #endif EventStates requiredStates; if (aIsRequired) { requiredStates |= NS_EVENT_STATE_REQUIRED; } else { requiredStates |= NS_EVENT_STATE_OPTIONAL; } EventStates oldRequiredStates = State() & REQUIRED_STATES; EventStates changedStates = requiredStates ^ oldRequiredStates; if (!changedStates.IsEmpty()) { ToggleStates(changedStates, aNotify); } } void nsGenericHTMLFormElement::FieldSetDisabledChanged(bool aNotify) { UpdateDisabledState(aNotify); } bool nsGenericHTMLFormElement::IsLabelable() const { // TODO: keygen should be in that list, see bug 101019. uint32_t type = ControlType(); return (type & NS_FORM_INPUT_ELEMENT && type != NS_FORM_INPUT_HIDDEN) || type & NS_FORM_BUTTON_ELEMENT || // type == NS_FORM_KEYGEN || type == NS_FORM_OUTPUT || type == NS_FORM_SELECT || type == NS_FORM_TEXTAREA; } void nsGenericHTMLFormElement::GetFormAction(nsString& aValue) { uint32_t type = ControlType(); if (!(type & NS_FORM_INPUT_ELEMENT) && !(type & NS_FORM_BUTTON_ELEMENT)) { return; } if (!GetAttr(kNameSpaceID_None, nsGkAtoms::formaction, aValue) || aValue.IsEmpty()) { nsIDocument* document = OwnerDoc(); nsIURI* docURI = document->GetDocumentURI(); if (docURI) { nsAutoCString spec; nsresult rv = docURI->GetSpec(spec); if (NS_FAILED(rv)) { return; } CopyUTF8toUTF16(spec, aValue); } } else { GetURIAttr(nsGkAtoms::formaction, nullptr, aValue); } } //---------------------------------------------------------------------- void nsGenericHTMLElement::Click(CallerType aCallerType) { if (HandlingClick()) return; // Strong in case the event kills it nsCOMPtr doc = GetComposedDoc(); nsCOMPtr shell; RefPtr context; if (doc) { shell = doc->GetShell(); if (shell) { context = shell->GetPresContext(); } } SetHandlingClick(); // Mark this event trusted if Click() is called from system code. WidgetMouseEvent event(aCallerType == CallerType::System, eMouseClick, nullptr, WidgetMouseEvent::eReal); event.mFlags.mIsPositionless = true; event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN; EventDispatcher::Dispatch(static_cast(this), context, &event); ClearHandlingClick(); } bool nsGenericHTMLElement::IsHTMLFocusable(bool aWithMouse, bool *aIsFocusable, int32_t *aTabIndex) { nsIDocument* doc = GetComposedDoc(); if (!doc || doc->HasFlag(NODE_IS_EDITABLE)) { // In designMode documents we only allow focusing the document. if (aTabIndex) { *aTabIndex = -1; } *aIsFocusable = false; return true; } int32_t tabIndex = TabIndex(); bool disabled = false; bool disallowOverridingFocusability = true; if (IsEditableRoot()) { // Editable roots should always be focusable. disallowOverridingFocusability = true; // Ignore the disabled attribute in editable contentEditable/designMode // roots. if (!HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) { // The default value for tabindex should be 0 for editable // contentEditable roots. tabIndex = 0; } } else { disallowOverridingFocusability = false; // Just check for disabled attribute on form controls disabled = IsDisabled(); if (disabled) { tabIndex = -1; } } if (aTabIndex) { *aTabIndex = tabIndex; } // If a tabindex is specified at all, or the default tabindex is 0, we're focusable *aIsFocusable = (tabIndex >= 0 || (!disabled && HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex))); return disallowOverridingFocusability; } void nsGenericHTMLElement::RegUnRegAccessKey(bool aDoReg) { // first check to see if we have an access key nsAutoString accessKey; GetAttr(kNameSpaceID_None, nsGkAtoms::accesskey, accessKey); if (accessKey.IsEmpty()) { return; } // We have an access key, so get the ESM from the pres context. nsPresContext* presContext = GetPresContext(eForUncomposedDoc); if (presContext) { EventStateManager* esm = presContext->EventStateManager(); // Register or unregister as appropriate. if (aDoReg) { esm->RegisterAccessKey(this, (uint32_t)accessKey.First()); } else { esm->UnregisterAccessKey(this, (uint32_t)accessKey.First()); } } } bool nsGenericHTMLElement::PerformAccesskey(bool aKeyCausesActivation, bool aIsTrustedEvent) { nsPresContext* presContext = GetPresContext(eForUncomposedDoc); if (!presContext) { return false; } // It's hard to say what HTML4 wants us to do in all cases. bool focused = true; nsFocusManager* fm = nsFocusManager::GetFocusManager(); if (fm) { fm->SetFocus(this, nsIFocusManager::FLAG_BYKEY); // Return true if the element became the current focus within its window. nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow(); focused = (window && window->GetFocusedNode()); } if (aKeyCausesActivation) { // Click on it if the users prefs indicate to do so. nsAutoPopupStatePusher popupStatePusher(aIsTrustedEvent ? openAllowed : openAbused); DispatchSimulatedClick(this, aIsTrustedEvent, presContext); } return focused; } nsresult nsGenericHTMLElement::DispatchSimulatedClick(nsGenericHTMLElement* aElement, bool aIsTrusted, nsPresContext* aPresContext) { WidgetMouseEvent event(aIsTrusted, eMouseClick, nullptr, WidgetMouseEvent::eReal); event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD; event.mFlags.mIsPositionless = true; return EventDispatcher::Dispatch(ToSupports(aElement), aPresContext, &event); } already_AddRefed nsGenericHTMLElement::GetAssociatedEditor() { // If contenteditable is ever implemented, it might need to do something different here? RefPtr textEditor = GetTextEditorInternal(); return textEditor.forget(); } bool nsGenericHTMLElement::IsCurrentBodyElement() { // TODO Bug 698498: Should this handle the case where GetBody returns a // frameset? if (!IsHTMLElement(nsGkAtoms::body)) { return false; } nsCOMPtr htmlDocument = do_QueryInterface(GetUncomposedDoc()); if (!htmlDocument) { return false; } nsCOMPtr htmlElement; htmlDocument->GetBody(getter_AddRefs(htmlElement)); return htmlElement == static_cast(this); } // static void nsGenericHTMLElement::SyncEditorsOnSubtree(nsIContent* content) { /* Sync this node */ nsGenericHTMLElement* element = FromContent(content); if (element) { RefPtr textEditor = element->GetAssociatedEditor(); if (textEditor) { textEditor->SyncRealTimeSpell(); } } /* Sync all children */ for (nsIContent* child = content->GetFirstChild(); child; child = child->GetNextSibling()) { SyncEditorsOnSubtree(child); } } void nsGenericHTMLElement::RecompileScriptEventListeners() { int32_t i, count = mAttrsAndChildren.AttrCount(); for (i = 0; i < count; ++i) { const nsAttrName *name = mAttrsAndChildren.AttrNameAt(i); // Eventlistenener-attributes are always in the null namespace if (!name->IsAtom()) { continue; } nsIAtom *attr = name->Atom(); if (!IsEventAttributeName(attr)) { continue; } nsAutoString value; GetAttr(kNameSpaceID_None, attr, value); SetEventHandler(attr, value, true); } } bool nsGenericHTMLElement::IsEditableRoot() const { nsIDocument *document = GetComposedDoc(); if (!document) { return false; } if (document->HasFlag(NODE_IS_EDITABLE)) { return false; } if (GetContentEditableValue() != eTrue) { return false; } nsIContent *parent = GetParent(); return !parent || !parent->HasFlag(NODE_IS_EDITABLE); } static void MakeContentDescendantsEditable(nsIContent *aContent, nsIDocument *aDocument) { // If aContent is not an element, we just need to update its // internal editable state and don't need to notify anyone about // that. For elements, we need to send a ContentStateChanged // notification. if (!aContent->IsElement()) { aContent->UpdateEditableState(false); return; } Element *element = aContent->AsElement(); element->UpdateEditableState(true); for (nsIContent *child = aContent->GetFirstChild(); child; child = child->GetNextSibling()) { if (!child->HasAttr(kNameSpaceID_None, nsGkAtoms::contenteditable)) { MakeContentDescendantsEditable(child, aDocument); } } } void nsGenericHTMLElement::ChangeEditableState(int32_t aChange) { //XXXsmaug Fix this for Shadow DOM, bug 1066965. nsIDocument* document = GetUncomposedDoc(); if (!document) { return; } if (aChange != 0) { nsCOMPtr htmlDocument = do_QueryInterface(document); if (htmlDocument) { htmlDocument->ChangeContentEditableCount(this, aChange); } nsIContent* parent = GetParent(); while (parent) { parent->ChangeEditableDescendantCount(aChange); parent = parent->GetParent(); } } if (document->HasFlag(NODE_IS_EDITABLE)) { document = nullptr; } // MakeContentDescendantsEditable is going to call ContentStateChanged for // this element and all descendants if editable state has changed. // We might as well wrap it all in one script blocker. nsAutoScriptBlocker scriptBlocker; MakeContentDescendantsEditable(this, document); } //---------------------------------------------------------------------- nsGenericHTMLFormElementWithState::nsGenericHTMLFormElementWithState( already_AddRefed& aNodeInfo, uint8_t aType ) : nsGenericHTMLFormElement(aNodeInfo, aType) { mStateKey.SetIsVoid(true); } nsresult nsGenericHTMLFormElementWithState::GenerateStateKey() { // Keep the key if already computed if (!mStateKey.IsVoid()) { return NS_OK; } nsIDocument* doc = GetUncomposedDoc(); if (!doc) { return NS_OK; } // Generate the state key nsresult rv = nsContentUtils::GenerateStateKey(this, doc, mStateKey); if (NS_FAILED(rv)) { mStateKey.SetIsVoid(true); return rv; } // If the state key is blank, this is anonymous content or for whatever // reason we are not supposed to save/restore state: keep it as such. if (!mStateKey.IsEmpty()) { // Add something unique to content so layout doesn't muck us up. mStateKey += "-C"; } return NS_OK; } nsPresState* nsGenericHTMLFormElementWithState::GetPrimaryPresState() { if (mStateKey.IsEmpty()) { return nullptr; } nsCOMPtr history = GetLayoutHistory(false); if (!history) { return nullptr; } // Get the pres state for this key, if it doesn't exist, create one. nsPresState* result = history->GetState(mStateKey); if (!result) { result = new nsPresState(); history->AddState(mStateKey, result); } return result; } already_AddRefed nsGenericHTMLFormElementWithState::GetLayoutHistory(bool aRead) { nsCOMPtr doc = GetUncomposedDoc(); if (!doc) { return nullptr; } // // Get the history // nsCOMPtr history = doc->GetLayoutHistoryState(); if (!history) { return nullptr; } if (aRead && !history->HasStates()) { return nullptr; } return history.forget(); } bool nsGenericHTMLFormElementWithState::RestoreFormControlState() { if (mStateKey.IsEmpty()) { return false; } nsCOMPtr history = GetLayoutHistory(true); if (!history) { return false; } nsPresState *state; // Get the pres state for this key state = history->GetState(mStateKey); if (state) { bool result = RestoreState(state); history->RemoveState(mStateKey); return result; } return false; } void nsGenericHTMLFormElementWithState::NodeInfoChanged(nsIDocument* aOldDoc) { nsGenericHTMLElement::NodeInfoChanged(aOldDoc); mStateKey.SetIsVoid(true); } nsSize nsGenericHTMLElement::GetWidthHeightForImage(RefPtr& aImageRequest) { nsSize size(0,0); nsIFrame* frame = GetPrimaryFrame(FlushType::Layout); if (frame) { size = frame->GetContentRect().Size(); size.width = nsPresContext::AppUnitsToIntCSSPixels(size.width); size.height = nsPresContext::AppUnitsToIntCSSPixels(size.height); } else { const nsAttrValue* value; nsCOMPtr image; if (aImageRequest) { aImageRequest->GetImage(getter_AddRefs(image)); } if ((value = GetParsedAttr(nsGkAtoms::width)) && value->Type() == nsAttrValue::eInteger) { size.width = value->GetIntegerValue(); } else if (image) { image->GetWidth(&size.width); } if ((value = GetParsedAttr(nsGkAtoms::height)) && value->Type() == nsAttrValue::eInteger) { size.height = value->GetIntegerValue(); } else if (image) { image->GetHeight(&size.height); } } NS_ASSERTION(size.width >= 0, "negative width"); NS_ASSERTION(size.height >= 0, "negative height"); return size; } bool nsGenericHTMLElement::IsEventAttributeNameInternal(nsIAtom *aName) { return nsContentUtils::IsEventAttributeName(aName, EventNameType_HTML); } /** * Construct a URI from a string, as an element.src attribute * would be set to. Helper for the media elements. */ nsresult nsGenericHTMLElement::NewURIFromString(const nsAString& aURISpec, nsIURI** aURI) { NS_ENSURE_ARG_POINTER(aURI); *aURI = nullptr; nsCOMPtr doc = OwnerDoc(); nsCOMPtr baseURI = GetBaseURI(); nsresult rv = nsContentUtils::NewURIWithDocumentCharset(aURI, aURISpec, doc, baseURI); NS_ENSURE_SUCCESS(rv, rv); bool equal; if (aURISpec.IsEmpty() && doc->GetDocumentURI() && NS_SUCCEEDED(doc->GetDocumentURI()->Equals(*aURI, &equal)) && equal) { // Assume an element can't point to a fragment of its embedding // document. Fail here instead of returning the recursive URI // and waiting for the subsequent load to fail. NS_RELEASE(*aURI); return NS_ERROR_DOM_INVALID_STATE_ERR; } return NS_OK; } static bool IsOrHasAncestorWithDisplayNone(Element* aElement, nsIPresShell* aPresShell) { AutoTArray elementsToCheck; // Style and layout work on the flattened tree, so this is what we need to // check in order to figure out whether we're in a display: none subtree. for (Element* e = aElement; e; e = e->GetFlattenedTreeParentElement()) { if (e->GetPrimaryFrame()) { // e definitely isn't display:none and doesn't have a display:none // ancestor. break; } elementsToCheck.AppendElement(e); } if (elementsToCheck.IsEmpty()) { return false; } StyleSetHandle styleSet = aPresShell->StyleSet(); RefPtr sc; for (auto* element : Reversed(elementsToCheck)) { if (sc) { if (styleSet->IsGecko()) { sc = styleSet->ResolveStyleFor(element, sc, LazyComputeBehavior::Assert); } else { // Call ResolveStyleLazily to protect against stale element data in // the tree when styled by Servo. sc = styleSet->AsServo()->ResolveStyleLazily( element, CSSPseudoElementType::NotPseudo, nullptr); } } else { sc = nsComputedDOMStyle::GetStyleContextNoFlush(element, nullptr, aPresShell); } if (sc->StyleDisplay()->mDisplay == StyleDisplay::None) { return true; } } return false; } void nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue, mozilla::ErrorResult& aError) { if (!GetPrimaryFrame(FlushType::Layout)) { nsIPresShell* presShell = nsComputedDOMStyle::GetPresShellForContent(this); // NOTE(emilio): We need to check the presshell is initialized in order to // ensure the document is styled. if (!presShell || !presShell->DidInitialize() || IsOrHasAncestorWithDisplayNone(this, presShell)) { GetTextContentInternal(aValue, aError); return; } } nsRange::GetInnerTextNoFlush(aValue, aError, this, 0, this, GetChildCount()); } void nsGenericHTMLElement::SetInnerText(const nsAString& aValue) { // Batch possible DOMSubtreeModified events. mozAutoSubtreeModified subtree(OwnerDoc(), nullptr); FireNodeRemovedForChildren(); // Might as well stick a batch around this since we're performing several // mutations. mozAutoDocUpdate updateBatch(GetComposedDoc(), UPDATE_CONTENT_MODEL, true); nsAutoMutationBatch mb; uint32_t childCount = GetChildCount(); mb.Init(this, true, false); for (uint32_t i = 0; i < childCount; ++i) { RemoveChildAt(0, true); } mb.RemovalDone(); nsString str; const char16_t* s = aValue.BeginReading(); const char16_t* end = aValue.EndReading(); while (true) { if (s != end && *s == '\r' && s + 1 != end && s[1] == '\n') { // a \r\n pair should only generate one
, so just skip the \r ++s; } if (s == end || *s == '\r' || *s == '\n') { if (!str.IsEmpty()) { RefPtr textContent = new nsTextNode(NodeInfo()->NodeInfoManager()); textContent->SetText(str, true); AppendChildTo(textContent, true); } if (s == end) { break; } str.Truncate(); already_AddRefed ni = NodeInfo()->NodeInfoManager()->GetNodeInfo(nsGkAtoms::br, nullptr, kNameSpaceID_XHTML, nsIDOMNode::ELEMENT_NODE); RefPtr br = new HTMLBRElement(ni); AppendChildTo(br, true); } else { str.Append(*s); } ++s; } mb.NodesAdded(); }