diff options
Diffstat (limited to 'dom/html/nsGenericHTMLElement.cpp')
-rw-r--r-- | dom/html/nsGenericHTMLElement.cpp | 3032 |
1 files changed, 3032 insertions, 0 deletions
diff --git a/dom/html/nsGenericHTMLElement.cpp b/dom/html/nsGenericHTMLElement.cpp new file mode 100644 index 000000000..d75001a83 --- /dev/null +++ b/dom/html/nsGenericHTMLElement.cpp @@ -0,0 +1,3032 @@ +/* -*- 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/MouseEvents.h" +#include "mozilla/Likely.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 "nsIDOMHTMLMenuElement.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 "nsScriptLoader.h" +#include "nsRuleData.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 "nsIEditor.h" +#include "nsIEditorIMESupport.h" +#include "nsLayoutUtils.h" +#include "mozAutoDocUpdate.h" +#include "nsHtml5Module.h" +#include "nsITextControlElement.h" +#include "mozilla/dom/Element.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 "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" + +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) : 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<nsPIDOMWindowOuter> top = window->GetTop()) { + window = top; + } + + if (window->GetFocusedNode()) { + return NS_OK; + } + + nsCOMPtr<nsIDocument> 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<nsGenericHTMLElement> 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) +{ + nsresult rv; + int32_t i, count = GetAttrCount(); + for (i = 0; i < count; ++i) { + const nsAttrName *name = mAttrsAndChildren.AttrNameAt(i); + const nsAttrValue *value = mAttrsAndChildren.AttrAt(i); + + nsAutoString valStr; + value->ToString(valStr); + + if (name->Equals(nsGkAtoms::style, kNameSpaceID_None) && + value->Type() == nsAttrValue::eCSSDeclaration) { + DeclarationBlock* decl = value->GetCSSDeclarationValue(); + // 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. + RefPtr<DeclarationBlock> declClone = decl->Clone(); + + rv = aDst->SetInlineStyleDeclaration(declClone, &valStr, false); + NS_ENSURE_SUCCESS(rv, rv); + + continue; + } + + rv = aDst->SetAttr(name->NamespaceID(), name->LocalName(), + name->GetPrefix(), valStr, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsGenericHTMLElement::GetDataset(nsISupports** aDataset) +{ + *aDataset = Dataset().take(); + 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(nsIAtom* frameType) { + return nsGkAtoms::tableCellFrame == frameType || + nsGkAtoms::bcTableCellFrame == frameType; +} + +static bool +IsOffsetParent(nsIFrame* aFrame) +{ + nsIAtom* frameType = aFrame->GetType(); + + if (IS_TABLE_CELL(frameType) || frameType == nsGkAtoms::tableFrame) { + // 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->GetType() == nsGkAtoms::tableWrapperFrame && + frame->GetType() == nsGkAtoms::tableFrame) { + 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 (!isAbsolutelyPositioned && !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<nsIDOMHTMLDocument> html_doc(do_QueryInterface(GetComposedDoc())); + + if (html_doc) { + offsetParent = static_cast<nsHTMLDocument*>(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(NS_SIDE_LEFT); + origin.y -= border->GetComputedBorderWidth(NS_SIDE_TOP); + } + + // 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; +} + +NS_IMETHODIMP +nsGenericHTMLElement::InsertAdjacentHTML(const nsAString& aPosition, + const nsAString& aText) +{ + ErrorResult rv; + Element::InsertAdjacentHTML(aPosition, aText, rv); + return rv.StealNSResult(); +} + +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<nsIFormControl> formControl = do_QueryObject(this); + if (!formControl) { + return false; // Not spellchecked by default + } + + // Is this a multiline plaintext input? + int32_t controlType = formControl->GetType(); + 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()) { + aDocument-> + AddToNameTable(this, GetParsedAttr(nsGkAtoms::name)->GetAtomValue()); + } + + if (HasFlag(NODE_IS_EDITABLE) && GetContentEditableValue() == eTrue) { + nsCOMPtr<nsIHTMLDocument> htmlDocument = do_QueryInterface(aDocument); + if (htmlDocument) { + htmlDocument->ChangeContentEditableCount(this, +1); + } + } + } + + 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<nsIHTMLDocument> htmlDocument = do_QueryInterface(GetUncomposedDoc()); + if (htmlDocument) { + htmlDocument->ChangeContentEditableCount(this, -1); + } + } + + 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. + 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<HTMLFormElement*>(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<Link>(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<nsIContent> target = aVisitor.mPresContext->EventStateManager()-> + GetEventTargetContent(aVisitor.mEvent); + + return !target || !target->IsHTMLElement(nsGkAtoms::area) || + IsHTMLElement(nsGkAtoms::area); +} + +nsresult +nsGenericHTMLElement::PreHandleEventForAnchors(EventChainPreVisitor& aVisitor) +{ + nsresult rv = nsGenericHTMLElementBase::PreHandleEvent(aVisitor); + NS_ENSURE_SUCCESS(rv, rv); + + if (!CheckHandleEventForAnchorsPreconditions(aVisitor)) { + return NS_OK; + } + + return PreHandleEventForLinks(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<nsIURI> +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<nsIURI> uri; + GetURIAttr(nsGkAtoms::href, nullptr, getter_AddRefs(uri)); + + return uri.forget(); +} + +nsresult +nsGenericHTMLElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName, + const nsAttrValue* aValue, 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; + if (aValue && aValue->Type() == nsAttrValue::eEnum) { + SetHasValidDir(); + Directionality dirValue = (Directionality)aValue->GetEnumValue(); + if (dirValue == eDir_Auto) { + SetHasDirAuto(); + ClearHasFixedDir(); + } else { + dir = dirValue; + SetDirectionality(dir, aNotify); + ClearHasDirAuto(); + SetHasFixedDir(); + } + } else { + ClearHasValidDir(); + ClearHasFixedDir(); + if (NodeInfo()->Equals(nsGkAtoms::bdi)) { + SetHasDirAuto(); + } else { + ClearHasDirAuto(); + dir = RecomputeDirectionality(this, aNotify); + } + } + SetDirectionalityOnDescendants(this, dir, aNotify); + } + } + + return nsGenericHTMLElementBase::AfterSetAttr(aNamespaceID, aName, + aValue, 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<EventTarget> 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<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); \ + OnErrorEventHandlerNonNull* errorHandler = globalWin->GetOn##name_(); \ + if (errorHandler) { \ + RefPtr<EventHandlerNonNull> handler = \ + new EventHandlerNonNull(errorHandler); \ + return handler.forget(); \ + } \ + } \ + return nullptr; \ + } \ + \ + RefPtr<EventHandlerNonNull> 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<OnErrorEventHandlerNonNull> 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 + +nsresult +nsGenericHTMLElement::SetAttr(int32_t aNameSpaceID, nsIAtom* aName, + nsIAtom* aPrefix, const nsAString& aValue, + bool aNotify) +{ + bool contentEditable = aNameSpaceID == kNameSpaceID_None && + aName == nsGkAtoms::contenteditable; + bool accessKey = aName == nsGkAtoms::accesskey && + aNameSpaceID == kNameSpaceID_None; + + int32_t change = 0; + if (contentEditable) { + change = GetContentEditableValue() == eTrue ? -1 : 0; + SetMayHaveContentEditableAttr(); + } + + if (accessKey) { + UnregAccessKey(); + } + + nsresult rv = nsStyledElement::SetAttr(aNameSpaceID, aName, aPrefix, aValue, + aNotify); + NS_ENSURE_SUCCESS(rv, rv); + + if (contentEditable) { + if (aValue.IsEmpty() || aValue.LowerCaseEqualsLiteral("true")) { + change += 1; + } + + ChangeEditableState(change); + } + + if (accessKey && !aValue.IsEmpty()) { + SetFlags(NODE_HAS_ACCESSKEY); + RegAccessKey(); + } + + return NS_OK; +} + +nsresult +nsGenericHTMLElement::UnsetAttr(int32_t aNameSpaceID, nsIAtom* aAttribute, + bool aNotify) +{ + bool contentEditable = false; + int32_t contentEditableChange = 0; + + // Check for event handlers + if (aNameSpaceID == kNameSpaceID_None) { + if (aAttribute == nsGkAtoms::name) { + // Have to do this before clearing flag. See RemoveFromNameTable + RemoveFromNameTable(); + ClearHasName(); + } + else if (aAttribute == nsGkAtoms::contenteditable) { + contentEditable = true; + contentEditableChange = GetContentEditableValue() == eTrue ? -1 : 0; + } + else if (aAttribute == nsGkAtoms::accesskey) { + // Have to unregister before clearing flag. See UnregAccessKey + UnregAccessKey(); + UnsetFlags(NODE_HAS_ACCESSKEY); + } + else if (IsEventAttributeName(aAttribute)) { + if (EventListenerManager* manager = GetExistingListenerManager()) { + manager->RemoveEventHandler(aAttribute, EmptyString()); + } + } + } + + nsresult rv = nsGenericHTMLElementBase::UnsetAttr(aNameSpaceID, aAttribute, + aNotify); + NS_ENSURE_SUCCESS(rv, rv); + + if (contentEditable) { + ChangeEditableState(contentEditableChange); + } + + return NS_OK; +} + +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 emptystring as the name. + RemoveFromNameTable(); + if (aValue.IsEmpty()) { + ClearHasName(); + return false; + } + + aResult.ParseAtom(aValue); + + if (CanHaveName(NodeInfo()->NameAtom())) { + SetHasName(); + AddToNameTable(aResult.GetAtomValue()); + } + + 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<nsIURI> baseURI = GetBaseURI(); + nsCOMPtr<nsIURI> uri; + nsresult rv = nsContentUtils::NewURIWithDocumentCharset( + getter_AddRefs(uri), aValue, doc, baseURI); + if (NS_FAILED(rv)) { + return false; + } + + nsString value(aValue); + RefPtr<nsStringBuffer> buffer = nsCSSValue::BufferFromString(value); + if (MOZ_UNLIKELY(!buffer)) { + return false; + } + + mozilla::css::URLValue *url = + new mozilla::css::URLValue(uri, buffer, 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(Flush_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<int16_t>(net::RP_No_Referrer) }, + { net::kRPS_Origin, static_cast<int16_t>(net::RP_Origin) }, + { net::kRPS_Origin_When_Cross_Origin, static_cast<int16_t>(net::RP_Origin_When_Crossorigin) }, + { net::kRPS_No_Referrer_When_Downgrade, static_cast<int16_t>(net::RP_No_Referrer_When_Downgrade) }, + { net::kRPS_Unsafe_URL, static_cast<int16_t>(net::RP_Unsafe_URL) }, + { net::kRPS_Strict_Origin, static_cast<int16_t>(net::RP_Strict_Origin) }, + { net::kRPS_Same_Origin, static_cast<int16_t>(net::RP_Same_Origin) }, + { net::kRPS_Strict_Origin_When_Cross_Origin, static_cast<int16_t>(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, nsRuleData* aData) +{ + if (!(aData->mSIDs & (NS_STYLE_INHERIT_BIT(Font) | + NS_STYLE_INHERIT_BIT(Text)))) { + return; + } + + const nsAttrValue* langValue = aAttributes->GetAttr(nsGkAtoms::lang); + if (!langValue || langValue->Type() != nsAttrValue::eString) { + return; + } + + if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Font)) { + nsCSSValue* lang = aData->ValueForLang(); + if (lang->GetUnit() == eCSSUnit_Null) { + lang->SetStringValue(langValue->GetStringValue(), eCSSUnit_Ident); + } + } + if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Text)) { + nsCSSValue* emphasisPos = aData->ValueForTextEmphasisPosition(); + if (emphasisPos->GetUnit() == eCSSUnit_Null) { + const nsAString& lang = langValue->GetStringValue(); + if (nsStyleUtil::MatchesLanguagePrefix(lang, u"zh")) { + emphasisPos->SetIntValue(NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT_ZH, + eCSSUnit_Enumerated); + } 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. + emphasisPos->SetIntValue(NS_STYLE_TEXT_EMPHASIS_POSITION_DEFAULT, + eCSSUnit_Enumerated); + } + } + } +} + +/** + * Handle attributes common to all html elements + */ +void +nsGenericHTMLElement::MapCommonAttributesIntoExceptHidden(const nsMappedAttributes* aAttributes, + nsRuleData* aData) +{ + if (aData->mSIDs & NS_STYLE_INHERIT_BIT(UserInterface)) { + nsCSSValue* userModify = aData->ValueForUserModify(); + if (userModify->GetUnit() == eCSSUnit_Null) { + const nsAttrValue* value = + aAttributes->GetAttr(nsGkAtoms::contenteditable); + if (value) { + if (value->Equals(nsGkAtoms::_empty, eCaseMatters) || + value->Equals(nsGkAtoms::_true, eIgnoreCase)) { + userModify->SetIntValue(StyleUserModify::ReadWrite, + eCSSUnit_Enumerated); + } + else if (value->Equals(nsGkAtoms::_false, eIgnoreCase)) { + userModify->SetIntValue(StyleUserModify::ReadOnly, + eCSSUnit_Enumerated); + } + } + } + } + + MapLangAttributeInto(aAttributes, aData); +} + +void +nsGenericHTMLElement::MapCommonAttributesInto(const nsMappedAttributes* aAttributes, + nsRuleData* aData) +{ + MapCommonAttributesIntoExceptHidden(aAttributes, aData); + + if (aData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) { + nsCSSValue* display = aData->ValueForDisplay(); + if (display->GetUnit() == eCSSUnit_Null) { + if (aAttributes->IndexOfAttr(nsGkAtoms::hidden) >= 0) { + display->SetIntValue(StyleDisplay::None, eCSSUnit_Enumerated); + } + } + } +} + +/* 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, + nsRuleData* aRuleData) +{ + if (aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Display)) { + const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align); + if (value && value->Type() == nsAttrValue::eEnum) { + int32_t align = value->GetEnumValue(); + nsCSSValue* cssFloat = aRuleData->ValueForFloat(); + if (cssFloat->GetUnit() == eCSSUnit_Null) { + if (align == NS_STYLE_TEXT_ALIGN_LEFT) { + cssFloat->SetIntValue(StyleFloat::Left, eCSSUnit_Enumerated); + } else if (align == NS_STYLE_TEXT_ALIGN_RIGHT) { + cssFloat->SetIntValue(StyleFloat::Right, eCSSUnit_Enumerated); + } + } + nsCSSValue* verticalAlign = aRuleData->ValueForVerticalAlign(); + if (verticalAlign->GetUnit() == eCSSUnit_Null) { + switch (align) { + case NS_STYLE_TEXT_ALIGN_LEFT: + case NS_STYLE_TEXT_ALIGN_RIGHT: + break; + default: + verticalAlign->SetIntValue(align, eCSSUnit_Enumerated); + break; + } + } + } + } +} + +void +nsGenericHTMLElement::MapDivAlignAttributeInto(const nsMappedAttributes* aAttributes, + nsRuleData* aRuleData) +{ + if (aRuleData->mSIDs & NS_STYLE_INHERIT_BIT(Text)) { + nsCSSValue* textAlign = aRuleData->ValueForTextAlign(); + if (textAlign->GetUnit() == eCSSUnit_Null) { + // align: enum + const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::align); + if (value && value->Type() == nsAttrValue::eEnum) + textAlign->SetIntValue(value->GetEnumValue(), eCSSUnit_Enumerated); + } + } +} + + +void +nsGenericHTMLElement::MapImageMarginAttributeInto(const nsMappedAttributes* aAttributes, + nsRuleData* aData) +{ + if (!(aData->mSIDs & NS_STYLE_INHERIT_BIT(Margin))) + return; + + const nsAttrValue* value; + + // hspace: value + value = aAttributes->GetAttr(nsGkAtoms::hspace); + if (value) { + nsCSSValue hval; + if (value->Type() == nsAttrValue::eInteger) + hval.SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel); + else if (value->Type() == nsAttrValue::ePercent) + hval.SetPercentValue(value->GetPercentValue()); + + if (hval.GetUnit() != eCSSUnit_Null) { + nsCSSValue* left = aData->ValueForMarginLeft(); + if (left->GetUnit() == eCSSUnit_Null) + *left = hval; + nsCSSValue* right = aData->ValueForMarginRight(); + if (right->GetUnit() == eCSSUnit_Null) + *right = hval; + } + } + + // vspace: value + value = aAttributes->GetAttr(nsGkAtoms::vspace); + if (value) { + nsCSSValue vval; + if (value->Type() == nsAttrValue::eInteger) + vval.SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel); + else if (value->Type() == nsAttrValue::ePercent) + vval.SetPercentValue(value->GetPercentValue()); + + if (vval.GetUnit() != eCSSUnit_Null) { + nsCSSValue* top = aData->ValueForMarginTop(); + if (top->GetUnit() == eCSSUnit_Null) + *top = vval; + nsCSSValue* bottom = aData->ValueForMarginBottom(); + if (bottom->GetUnit() == eCSSUnit_Null) + *bottom = vval; + } + } +} + +void +nsGenericHTMLElement::MapImageSizeAttributesInto(const nsMappedAttributes* aAttributes, + nsRuleData* aData) +{ + if (!(aData->mSIDs & NS_STYLE_INHERIT_BIT(Position))) + return; + + // width: value + nsCSSValue* width = aData->ValueForWidth(); + if (width->GetUnit() == eCSSUnit_Null) { + const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::width); + if (value && value->Type() == nsAttrValue::eInteger) + width->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel); + else if (value && value->Type() == nsAttrValue::ePercent) + width->SetPercentValue(value->GetPercentValue()); + } + + // height: value + nsCSSValue* height = aData->ValueForHeight(); + if (height->GetUnit() == eCSSUnit_Null) { + const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::height); + if (value && value->Type() == nsAttrValue::eInteger) + height->SetFloatValue((float)value->GetIntegerValue(), eCSSUnit_Pixel); + else if (value && value->Type() == nsAttrValue::ePercent) + height->SetPercentValue(value->GetPercentValue()); + } +} + +void +nsGenericHTMLElement::MapImageBorderAttributeInto(const nsMappedAttributes* aAttributes, + nsRuleData* aData) +{ + if (!(aData->mSIDs & 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(); + + nsCSSValue* borderLeftWidth = aData->ValueForBorderLeftWidth(); + if (borderLeftWidth->GetUnit() == eCSSUnit_Null) + borderLeftWidth->SetFloatValue((float)val, eCSSUnit_Pixel); + nsCSSValue* borderTopWidth = aData->ValueForBorderTopWidth(); + if (borderTopWidth->GetUnit() == eCSSUnit_Null) + borderTopWidth->SetFloatValue((float)val, eCSSUnit_Pixel); + nsCSSValue* borderRightWidth = aData->ValueForBorderRightWidth(); + if (borderRightWidth->GetUnit() == eCSSUnit_Null) + borderRightWidth->SetFloatValue((float)val, eCSSUnit_Pixel); + nsCSSValue* borderBottomWidth = aData->ValueForBorderBottomWidth(); + if (borderBottomWidth->GetUnit() == eCSSUnit_Null) + borderBottomWidth->SetFloatValue((float)val, eCSSUnit_Pixel); + + nsCSSValue* borderLeftStyle = aData->ValueForBorderLeftStyle(); + if (borderLeftStyle->GetUnit() == eCSSUnit_Null) + borderLeftStyle->SetIntValue(NS_STYLE_BORDER_STYLE_SOLID, eCSSUnit_Enumerated); + nsCSSValue* borderTopStyle = aData->ValueForBorderTopStyle(); + if (borderTopStyle->GetUnit() == eCSSUnit_Null) + borderTopStyle->SetIntValue(NS_STYLE_BORDER_STYLE_SOLID, eCSSUnit_Enumerated); + nsCSSValue* borderRightStyle = aData->ValueForBorderRightStyle(); + if (borderRightStyle->GetUnit() == eCSSUnit_Null) + borderRightStyle->SetIntValue(NS_STYLE_BORDER_STYLE_SOLID, eCSSUnit_Enumerated); + nsCSSValue* borderBottomStyle = aData->ValueForBorderBottomStyle(); + if (borderBottomStyle->GetUnit() == eCSSUnit_Null) + borderBottomStyle->SetIntValue(NS_STYLE_BORDER_STYLE_SOLID, eCSSUnit_Enumerated); + + nsCSSValue* borderLeftColor = aData->ValueForBorderLeftColor(); + if (borderLeftColor->GetUnit() == eCSSUnit_Null) + borderLeftColor->SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor); + nsCSSValue* borderTopColor = aData->ValueForBorderTopColor(); + if (borderTopColor->GetUnit() == eCSSUnit_Null) + borderTopColor->SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor); + nsCSSValue* borderRightColor = aData->ValueForBorderRightColor(); + if (borderRightColor->GetUnit() == eCSSUnit_Null) + borderRightColor->SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor); + nsCSSValue* borderBottomColor = aData->ValueForBorderBottomColor(); + if (borderBottomColor->GetUnit() == eCSSUnit_Null) + borderBottomColor->SetIntValue(NS_COLOR_CURRENTCOLOR, eCSSUnit_EnumColor); +} + +void +nsGenericHTMLElement::MapBackgroundInto(const nsMappedAttributes* aAttributes, + nsRuleData* aData) +{ + if (!(aData->mSIDs & NS_STYLE_INHERIT_BIT(Background))) + return; + + nsPresContext* presContext = aData->mPresContext; + nsCSSValue* backImage = aData->ValueForBackgroundImage(); + if (backImage->GetUnit() == eCSSUnit_Null && + presContext->UseDocumentColors()) { + // background + nsAttrValue* value = + const_cast<nsAttrValue*>(aAttributes->GetAttr(nsGkAtoms::background)); + // If the value is an image, or it is a URL and we attempted a load, + // put it in the style tree. + if (value) { + if (value->Type() == nsAttrValue::eURL) { + value->LoadImage(presContext->Document()); + } + if (value->Type() == nsAttrValue::eImage) { + nsCSSValueList* list = backImage->SetListValue(); + list->mValue.SetImageValue(value->GetImageValue()); + } + } + } +} + +void +nsGenericHTMLElement::MapBGColorInto(const nsMappedAttributes* aAttributes, + nsRuleData* aData) +{ + if (!(aData->mSIDs & NS_STYLE_INHERIT_BIT(Background))) + return; + + nsCSSValue* backColor = aData->ValueForBackgroundColor(); + if (backColor->GetUnit() == eCSSUnit_Null && + aData->mPresContext->UseDocumentColors()) { + const nsAttrValue* value = aAttributes->GetAttr(nsGkAtoms::bgcolor); + nscolor color; + if (value && value->GetColorValue(color)) { + backColor->SetColorValue(color); + } + } +} + +void +nsGenericHTMLElement::MapBackgroundAttributesInto(const nsMappedAttributes* aAttributes, + nsRuleData* 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<nsIURI> 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<nsIURI> baseURI = GetBaseURI(); + + if (aBaseAttr) { + nsAutoString baseAttrValue; + if (GetAttr(kNameSpaceID_None, aBaseAttr, baseAttrValue)) { + nsCOMPtr<nsIURI> 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; +} + +/* static */ bool +nsGenericHTMLElement::IsScrollGrabAllowed(JSContext*, JSObject*) +{ + // Only allow scroll grabbing in chrome + nsIPrincipal* prin = nsContentUtils::SubjectPrincipal(); + return nsContentUtils::IsSystemPrincipal(prin); +} + +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; +} + +NS_IMETHODIMP +nsGenericHTMLElement::GetContextMenu(nsIDOMHTMLMenuElement** aContextMenu) +{ + NS_IF_ADDREF(*aContextMenu = GetContextMenu()); + return NS_OK; +} + +bool +nsGenericHTMLElement::IsLabelable() const +{ + return IsAnyOfHTMLElements(nsGkAtoms::progress, nsGkAtoms::meter); +} + +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<mozilla::dom::NodeInfo>& aNodeInfo) + : nsGenericHTMLElement(aNodeInfo) + , 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."); + + // keep a *weak* ref to the form here + mForm = static_cast<HTMLFormElement*>(aForm); +} + +void +nsGenericHTMLFormElement::ClearForm(bool aRemoveFromForm) +{ + 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, + HTMLFormElement::ElementRemoved); + } + + if (!idVal.IsEmpty()) { + mForm->RemoveElementFromTable(this, idVal, + HTMLFormElement::ElementRemoved); + } + } + + UnsetFlags(ADDED_TO_FORM); + mForm = nullptr; +} + +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() +{ + nsIEditor* editor = GetEditorInternal(); + if (!editor) + return nsGenericHTMLElement::GetDesiredIMEState(); + nsCOMPtr<nsIEditorIMESupport> imeEditor = do_QueryInterface(editor); + if (!imeEditor) + return nsGenericHTMLElement::GetDesiredIMEState(); + IMEState state; + nsresult rv = imeEditor->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) && + Preferences::GetBool("browser.autofocus", true)) { + nsCOMPtr<nsIRunnable> 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); + } else { + // Recheck whether we should still have an mForm. + if (HasAttr(kNameSpaceID_None, nsGkAtoms::form) || + !FindAncestorForm(mForm)) { + ClearForm(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, + 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, + HTMLFormElement::AttributeUpdated); + } + } + + if (mForm && aName == nsGkAtoms::type) { + GetAttr(kNameSpaceID_None, nsGkAtoms::name, tmp); + + if (!tmp.IsEmpty()) { + mForm->RemoveElementFromTable(this, tmp, + HTMLFormElement::AttributeUpdated); + } + + GetAttr(kNameSpaceID_None, nsGkAtoms::id, tmp); + + if (!tmp.IsEmpty()) { + mForm->RemoveElementFromTable(this, tmp, + HTMLFormElement::AttributeUpdated); + } + + mForm->RemoveElement(this, false); + + // Removing the element from the form can make it not be the default + // control anymore. Go ahead and notify on that change, though we might + // end up readding and becoming the default control again in + // AfterSetAttr. + // FIXME: Bug 656197 + UpdateState(aNotify); + } + + 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, 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); + + // Adding the element to the form can make it be the default control . + // Go ahead and notify on that change. + // Note: no need to notify on CanBeDisabled(), since type attr + // changes can't affect that. + UpdateState(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, aNotify); +} + +nsresult +nsGenericHTMLFormElement::PreHandleEvent(EventChainPreVisitor& 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<nsINode*>(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 HasAttr(kNameSpaceID_None, nsGkAtoms::disabled) || + (mFieldSet && mFieldSet->IsDisabled()); +} + +void +nsGenericHTMLFormElement::ForgetFieldSet(nsIContent* aFieldset) +{ + if (mFieldSet == aFieldset) { + mFieldSet = nullptr; + } +} + +bool +nsGenericHTMLFormElement::CanBeDisabled() const +{ + int32_t type = GetType(); + // 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 (CanBeDisabled()) { + // :enabled/:disabled + if (IsDisabled()) { + state |= NS_EVENT_STATE_DISABLED; + state &= ~NS_EVENT_STATE_ENABLED; + } else { + state &= ~NS_EVENT_STATE_DISABLED; + state |= NS_EVENT_STATE_ENABLED; + } + } + + 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<nsPIDOMWindowOuter> rootWindow = win->GetPrivateRoot(); + + nsCOMPtr<nsIFocusManager> fm = do_GetService(FOCUSMANAGER_CONTRACTID); + if (fm && rootWindow) { + nsCOMPtr<mozIDOMWindowProxy> 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!"); + nsCOMPtr<nsIAtom> 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!"); + nsCOMPtr<nsIAtom> atom = NS_Atomize(formId); + + doc->RemoveIDTargetObserver(atom, FormIdUpdated, this, false); +} + + +/* static */ +bool +nsGenericHTMLFormElement::FormIdUpdated(Element* aOldElement, + Element* aNewElement, + void* aData) +{ + nsGenericHTMLFormElement* element = + static_cast<nsGenericHTMLFormElement*>(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); + } + + 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)) { + mForm = static_cast<HTMLFormElement*>(element); + } + } + } 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_. + mForm = FindAncestorForm(); + } + } + + 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; + + for (parent = GetParent(); parent; + 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::FieldSetDisabledChanged(bool aNotify) +{ + UpdateState(aNotify); +} + +bool +nsGenericHTMLFormElement::IsLabelable() const +{ + // TODO: keygen should be in that list, see bug 101019. + uint32_t type = GetType(); + 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 +nsGenericHTMLElement::Click() +{ + if (HandlingClick()) + return; + + // Strong in case the event kills it + nsCOMPtr<nsIDocument> doc = GetComposedDoc(); + + nsCOMPtr<nsIPresShell> shell; + RefPtr<nsPresContext> context; + if (doc) { + shell = doc->GetShell(); + if (shell) { + context = shell->GetPresContext(); + } + } + + SetHandlingClick(); + + // Click() is never called from native code, but it may be + // called from chrome JS. Mark this event trusted if Click() + // is called from chrome code. + WidgetMouseEvent event(nsContentUtils::IsCallerChrome(), + eMouseClick, nullptr, WidgetMouseEvent::eReal); + event.inputSource = nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN; + + EventDispatcher::Dispatch(static_cast<nsIContent*>(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; + return EventDispatcher::Dispatch(ToSupports(aElement), aPresContext, &event); +} + +nsresult +nsGenericHTMLElement::GetEditor(nsIEditor** aEditor) +{ + *aEditor = nullptr; + + // See also HTMLTextFieldAccessible::GetEditor. + if (!nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + NS_IF_ADDREF(*aEditor = GetEditorInternal()); + return NS_OK; +} + +already_AddRefed<nsIEditor> +nsGenericHTMLElement::GetAssociatedEditor() +{ + // If contenteditable is ever implemented, it might need to do something different here? + + nsCOMPtr<nsIEditor> editor = GetEditorInternal(); + return editor.forget(); +} + +bool +nsGenericHTMLElement::IsCurrentBodyElement() +{ + // TODO Bug 698498: Should this handle the case where GetBody returns a + // frameset? + if (!IsHTMLElement(nsGkAtoms::body)) { + return false; + } + + nsCOMPtr<nsIDOMHTMLDocument> htmlDocument = + do_QueryInterface(GetUncomposedDoc()); + if (!htmlDocument) { + return false; + } + + nsCOMPtr<nsIDOMHTMLElement> htmlElement; + htmlDocument->GetBody(getter_AddRefs(htmlElement)); + return htmlElement == static_cast<HTMLBodyElement*>(this); +} + +// static +void +nsGenericHTMLElement::SyncEditorsOnSubtree(nsIContent* content) +{ + /* Sync this node */ + nsGenericHTMLElement* element = FromContent(content); + if (element) { + nsCOMPtr<nsIEditor> editor = element->GetAssociatedEditor(); + if (editor) { + editor->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<nsIHTMLDocument> 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<mozilla::dom::NodeInfo>& aNodeInfo + ) + : nsGenericHTMLFormElement(aNodeInfo) +{ + 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<nsILayoutHistoryState> 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<nsILayoutHistoryState> +nsGenericHTMLFormElementWithState::GetLayoutHistory(bool aRead) +{ + nsCOMPtr<nsIDocument> doc = GetUncomposedDoc(); + if (!doc) { + return nullptr; + } + + // + // Get the history + // + nsCOMPtr<nsILayoutHistoryState> 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<nsILayoutHistoryState> 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() +{ + mStateKey.SetIsVoid(true); +} + +nsSize +nsGenericHTMLElement::GetWidthHeightForImage(RefPtr<imgRequestProxy>& aImageRequest) +{ + nsSize size(0,0); + + nsIFrame* frame = GetPrimaryFrame(Flush_Layout); + + if (frame) { + size = frame->GetContentRect().Size(); + + size.width = nsPresContext::AppUnitsToIntCSSPixels(size.width); + size.height = nsPresContext::AppUnitsToIntCSSPixels(size.height); + } else { + const nsAttrValue* value; + nsCOMPtr<imgIContainer> 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::IsEventAttributeName(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<nsIDocument> doc = OwnerDoc(); + + nsCOMPtr<nsIURI> 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) +{ + nsTArray<Element*> elementsToCheck; + for (Element* e = aElement; e; e = e->GetParentElement()) { + 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<nsStyleContext> sc; + for (int32_t i = elementsToCheck.Length() - 1; i >= 0; --i) { + if (sc) { + sc = styleSet->ResolveStyleFor(elementsToCheck[i], sc); + } else { + sc = nsComputedDOMStyle::GetStyleContextForElementNoFlush(elementsToCheck[i], + nullptr, aPresShell); + } + if (sc->StyleDisplay()->mDisplay == StyleDisplay::None) { + return true; + } + } + + return false; +} + +void +nsGenericHTMLElement::GetInnerText(mozilla::dom::DOMString& aValue, + mozilla::ErrorResult& aError) +{ + if (!GetPrimaryFrame(Flush_Layout)) { + nsIPresShell* presShell = nsComputedDOMStyle::GetPresShellForContent(this); + if (!presShell || 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 <br>, so just skip the \r + ++s; + } + if (s == end || *s == '\r' || *s == '\n') { + if (!str.IsEmpty()) { + RefPtr<nsTextNode> textContent = + new nsTextNode(NodeInfo()->NodeInfoManager()); + textContent->SetText(str, true); + AppendChildTo(textContent, true); + } + if (s == end) { + break; + } + str.Truncate(); + already_AddRefed<mozilla::dom::NodeInfo> ni = + NodeInfo()->NodeInfoManager()->GetNodeInfo(nsGkAtoms::br, + nullptr, kNameSpaceID_XHTML, nsIDOMNode::ELEMENT_NODE); + RefPtr<HTMLBRElement> br = new HTMLBRElement(ni); + AppendChildTo(br, true); + } else { + str.Append(*s); + } + ++s; + } + + mb.NodesAdded(); +} |