From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- dom/xul/nsXULElement.cpp | 2008 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2008 insertions(+) create mode 100644 dom/xul/nsXULElement.cpp (limited to 'dom/xul/nsXULElement.cpp') diff --git a/dom/xul/nsXULElement.cpp b/dom/xul/nsXULElement.cpp new file mode 100644 index 0000000000..66d6d0a20c --- /dev/null +++ b/dom/xul/nsXULElement.cpp @@ -0,0 +1,2008 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "nsXULElement.h" + +#include +#include +#include "AttrArray.h" +#include "MainThreadUtils.h" +#include "ReferrerInfo.h" +#include "Units.h" +#include "XULButtonElement.h" +#include "XULFrameElement.h" +#include "XULMenuElement.h" +#include "XULMenuBarElement.h" +#include "XULPopupElement.h" +#include "XULResizerElement.h" +#include "XULTextElement.h" +#include "XULTooltipElement.h" +#include "XULTreeElement.h" +#include "js/CompilationAndEvaluation.h" +#include "js/CompileOptions.h" +#include "js/experimental/JSStencil.h" +#include "js/OffThreadScriptCompilation.h" +#include "js/SourceText.h" +#include "js/Transcoding.h" +#include "js/Utility.h" +#include "jsapi.h" +#include "mozilla/Assertions.h" +#include "mozilla/ArrayIterator.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/DeclarationBlock.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/FlushType.h" +#include "mozilla/GlobalKeyListener.h" +#include "mozilla/HoldDropJSObjects.h" +#include "mozilla/MacroForEach.h" +#include "mozilla/Maybe.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/OwningNonNull.h" +#include "mozilla/PresShell.h" +#include "mozilla/RefPtr.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/StaticAnalysisFunctions.h" +#include "mozilla/StaticPtr.h" +#include "mozilla/URLExtraData.h" +#include "mozilla/dom/BindContext.h" +#include "mozilla/dom/BorrowedAttrInfo.h" +#include "mozilla/dom/CSSRuleBinding.h" +#include "mozilla/dom/Document.h" +#include "mozilla/dom/DocumentInlines.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Event.h" +#include "mozilla/dom/EventTarget.h" +#include "mozilla/dom/FragmentOrElement.h" +#include "mozilla/dom/FromParser.h" +#include "mozilla/dom/MouseEventBinding.h" +#include "mozilla/dom/MutationEventBinding.h" +#include "mozilla/dom/NodeInfo.h" +#include "mozilla/dom/ReferrerPolicyBinding.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/XULBroadcastManager.h" +#include "mozilla/dom/XULCommandEvent.h" +#include "mozilla/dom/XULElementBinding.h" +#include "mozilla/dom/nsCSPUtils.h" +#include "mozilla/fallible.h" +#include "nsAtom.h" +#include "nsAttrValueInlines.h" +#include "nsCaseTreatment.h" +#include "nsChangeHint.h" +#include "nsCOMPtr.h" +#include "nsCompatibility.h" +#include "nsContentCreatorFunctions.h" +#include "nsContentUtils.h" +#include "nsCycleCollectionNoteChild.h" +#include "nsCycleCollectionTraversalCallback.h" +#include "nsDebug.h" +#include "nsError.h" +#include "nsFocusManager.h" +#include "nsGkAtoms.h" +#include "nsIContent.h" +#include "nsIContentSecurityPolicy.h" +#include "nsIControllers.h" +#include "nsID.h" +#include "nsIDOMEventListener.h" +#include "nsIDOMXULControlElement.h" +#include "nsIDOMXULSelectCntrlItemEl.h" +#include "nsIDocShell.h" +#include "nsIFocusManager.h" +#include "nsIFrame.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIRunnable.h" +#include "nsIScriptContext.h" +#include "nsISupportsUtils.h" +#include "nsIURI.h" +#include "nsIXPConnect.h" +#include "nsMenuPopupFrame.h" +#include "nsNodeInfoManager.h" +#include "nsPIDOMWindow.h" +#include "nsPIDOMWindowInlines.h" +#include "nsPresContext.h" +#include "nsQueryFrame.h" +#include "nsString.h" +#include "nsStyledElement.h" +#include "nsThreadUtils.h" +#include "nsXULControllers.h" +#include "nsXULPopupListener.h" +#include "nsXULPopupManager.h" +#include "nsXULPrototypeCache.h" +#include "nsXULTooltipListener.h" +#include "xpcpublic.h" + +using namespace mozilla; +using namespace mozilla::dom; + +#ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING +uint32_t nsXULPrototypeAttribute::gNumElements; +uint32_t nsXULPrototypeAttribute::gNumAttributes; +uint32_t nsXULPrototypeAttribute::gNumCacheTests; +uint32_t nsXULPrototypeAttribute::gNumCacheHits; +uint32_t nsXULPrototypeAttribute::gNumCacheSets; +uint32_t nsXULPrototypeAttribute::gNumCacheFills; +#endif + +#define NS_DISPATCH_XUL_COMMAND (1 << 0) + +//---------------------------------------------------------------------- +// nsXULElement +// + +nsXULElement::nsXULElement(already_AddRefed&& aNodeInfo) + : nsStyledElement(std::move(aNodeInfo)) { + XUL_PROTOTYPE_ATTRIBUTE_METER(gNumElements); +} + +nsXULElement::~nsXULElement() = default; + +/* static */ +nsXULElement* NS_NewBasicXULElement( + already_AddRefed&& aNodeInfo) { + RefPtr nodeInfo(std::move(aNodeInfo)); + auto* nim = nodeInfo->NodeInfoManager(); + return new (nim) nsXULElement(nodeInfo.forget()); +} + +/* static */ +nsXULElement* nsXULElement::Construct( + already_AddRefed&& aNodeInfo) { + // NOTE: If you add elements here, you probably also want to change + // mozilla::dom::binding_detail::HTMLConstructor in BindingUtils.cpp to take + // them into account, otherwise you'll start getting "Illegal constructor" + // exceptions in chrome code. + RefPtr nodeInfo = aNodeInfo; + if (nodeInfo->Equals(nsGkAtoms::resizer)) { + return NS_NewXULResizerElement(nodeInfo.forget()); + } + + if (nodeInfo->Equals(nsGkAtoms::label) || + nodeInfo->Equals(nsGkAtoms::description)) { + auto* nim = nodeInfo->NodeInfoManager(); + return new (nim) XULTextElement(nodeInfo.forget()); + } + + if (nodeInfo->Equals(nsGkAtoms::menupopup) || + nodeInfo->Equals(nsGkAtoms::popup) || + nodeInfo->Equals(nsGkAtoms::panel)) { + return NS_NewXULPopupElement(nodeInfo.forget()); + } + + if (nodeInfo->Equals(nsGkAtoms::tooltip)) { + return NS_NewXULTooltipElement(nodeInfo.forget()); + } + + if (nodeInfo->Equals(nsGkAtoms::iframe) || + nodeInfo->Equals(nsGkAtoms::browser) || + nodeInfo->Equals(nsGkAtoms::editor)) { + auto* nim = nodeInfo->NodeInfoManager(); + return new (nim) XULFrameElement(nodeInfo.forget()); + } + + if (nodeInfo->Equals(nsGkAtoms::menubar)) { + auto* nim = nodeInfo->NodeInfoManager(); + return new (nim) XULMenuBarElement(nodeInfo.forget()); + } + + if (nodeInfo->Equals(nsGkAtoms::menu) || + nodeInfo->Equals(nsGkAtoms::menulist)) { + auto* nim = nodeInfo->NodeInfoManager(); + return new (nim) XULMenuElement(nodeInfo.forget()); + } + + if (nodeInfo->Equals(nsGkAtoms::tree)) { + auto* nim = nodeInfo->NodeInfoManager(); + return new (nim) XULTreeElement(nodeInfo.forget()); + } + + if (nodeInfo->Equals(nsGkAtoms::checkbox) || + nodeInfo->Equals(nsGkAtoms::radio) || + nodeInfo->Equals(nsGkAtoms::thumb) || + nodeInfo->Equals(nsGkAtoms::button) || + nodeInfo->Equals(nsGkAtoms::menuitem) || + nodeInfo->Equals(nsGkAtoms::toolbarbutton) || + nodeInfo->Equals(nsGkAtoms::toolbarpaletteitem) || + nodeInfo->Equals(nsGkAtoms::scrollbarbutton)) { + auto* nim = nodeInfo->NodeInfoManager(); + return new (nim) XULButtonElement(nodeInfo.forget()); + } + + return NS_NewBasicXULElement(nodeInfo.forget()); +} + +/* static */ +already_AddRefed nsXULElement::CreateFromPrototype( + nsXULPrototypeElement* aPrototype, mozilla::dom::NodeInfo* aNodeInfo, + bool aIsScriptable, bool aIsRoot) { + RefPtr ni = aNodeInfo; + nsCOMPtr baseElement; + NS_NewXULElement(getter_AddRefs(baseElement), ni.forget(), + dom::FROM_PARSER_NETWORK, aPrototype->mIsAtom); + + if (baseElement) { + nsXULElement* element = FromNode(baseElement); + + if (aPrototype->mHasIdAttribute) { + element->SetHasID(); + } + if (aPrototype->mHasClassAttribute) { + element->SetMayHaveClass(); + } + if (aPrototype->mHasStyleAttribute) { + element->SetMayHaveStyle(); + } + + element->MakeHeavyweight(aPrototype); + if (aIsScriptable) { + // Check each attribute on the prototype to see if we need to do + // any additional processing and hookup that would otherwise be + // done 'automagically' by SetAttr(). + for (const auto& attribute : aPrototype->mAttributes) { + element->AddListenerForAttributeIfNeeded(attribute.mName); + } + } + + return baseElement.forget().downcast(); + } + + return nullptr; +} + +nsresult nsXULElement::CreateFromPrototype(nsXULPrototypeElement* aPrototype, + Document* aDocument, + bool aIsScriptable, bool aIsRoot, + Element** aResult) { + // Create an nsXULElement from a prototype + MOZ_ASSERT(aPrototype != nullptr, "null ptr"); + if (!aPrototype) return NS_ERROR_NULL_POINTER; + + MOZ_ASSERT(aResult != nullptr, "null ptr"); + if (!aResult) return NS_ERROR_NULL_POINTER; + + RefPtr nodeInfo; + if (aDocument) { + mozilla::dom::NodeInfo* ni = aPrototype->mNodeInfo; + nodeInfo = aDocument->NodeInfoManager()->GetNodeInfo( + ni->NameAtom(), ni->GetPrefixAtom(), ni->NamespaceID(), ELEMENT_NODE); + } else { + nodeInfo = aPrototype->mNodeInfo; + } + + RefPtr element = + CreateFromPrototype(aPrototype, nodeInfo, aIsScriptable, aIsRoot); + element.forget(aResult); + + return NS_OK; +} + +nsresult NS_NewXULElement(Element** aResult, + already_AddRefed&& aNodeInfo, + FromParser aFromParser, nsAtom* aIsAtom, + mozilla::dom::CustomElementDefinition* aDefinition) { + RefPtr nodeInfo = aNodeInfo; + + MOZ_ASSERT(nodeInfo, "need nodeinfo for non-proto Create"); + + NS_ASSERTION( + nodeInfo->NamespaceEquals(kNameSpaceID_XUL), + "Trying to create XUL elements that don't have the XUL namespace"); + + Document* doc = nodeInfo->GetDocument(); + if (doc && !doc->AllowXULXBL()) { + return NS_ERROR_NOT_AVAILABLE; + } + + return nsContentUtils::NewXULOrHTMLElement(aResult, nodeInfo, aFromParser, + aIsAtom, aDefinition); +} + +void NS_TrustedNewXULElement( + Element** aResult, already_AddRefed&& aNodeInfo) { + RefPtr ni = aNodeInfo; + MOZ_ASSERT(ni, "need nodeinfo for non-proto Create"); + + // Create an nsXULElement with the specified namespace and tag. + NS_ADDREF(*aResult = nsXULElement::Construct(ni.forget())); +} + +//---------------------------------------------------------------------- +// nsISupports interface + +NS_IMPL_CYCLE_COLLECTION_INHERITED(nsXULElement, nsStyledElement) + +NS_IMPL_ADDREF_INHERITED(nsXULElement, nsStyledElement) +NS_IMPL_RELEASE_INHERITED(nsXULElement, nsStyledElement) + +NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsXULElement) + NS_ELEMENT_INTERFACE_TABLE_TO_MAP_SEGUE +NS_INTERFACE_MAP_END_INHERITING(nsStyledElement) + +//---------------------------------------------------------------------- +// nsINode interface + +nsresult nsXULElement::Clone(mozilla::dom::NodeInfo* aNodeInfo, + nsINode** aResult) const { + *aResult = nullptr; + + RefPtr ni = aNodeInfo; + RefPtr element = Construct(ni.forget()); + + nsresult rv = const_cast(this)->CopyInnerTo( + element, ReparseAttributes::No); + NS_ENSURE_SUCCESS(rv, rv); + + // Note that we're _not_ copying mControllers. + + element.forget(aResult); + return rv; +} + +//---------------------------------------------------------------------- + +EventListenerManager* nsXULElement::GetEventListenerManagerForAttr( + nsAtom* aAttrName, bool* aDefer) { + // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc() + // here, override BindToTree for those classes and munge event + // listeners there? + Document* doc = OwnerDoc(); + + nsPIDOMWindowInner* window; + Element* root = doc->GetRootElement(); + if ((!root || root == this) && (window = doc->GetInnerWindow())) { + nsCOMPtr piTarget = do_QueryInterface(window); + + *aDefer = false; + return piTarget->GetOrCreateListenerManager(); + } + + return nsStyledElement::GetEventListenerManagerForAttr(aAttrName, aDefer); +} + +// returns true if the element is not a list +static bool IsNonList(mozilla::dom::NodeInfo* aNodeInfo) { + return !aNodeInfo->Equals(nsGkAtoms::tree) && + !aNodeInfo->Equals(nsGkAtoms::richlistbox); +} + +bool nsXULElement::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) { + /* + * Returns true if an element may be focused, and false otherwise. The inout + * argument aTabIndex will be set to the tab order index to be used; -1 for + * elements that should not be part of the tab order and a greater value to + * indicate its tab order. + * + * Confusingly, the supplied value for the aTabIndex argument may indicate + * whether the element may be focused as a result of the -moz-user-focus + * property, where -1 means no and 0 means yes. + * + * For controls, the element cannot be focused and is not part of the tab + * order if it is disabled. + * + * -moz-user-focus is overridden if a tabindex (even -1) is specified. + * + * Specifically, the behaviour for all XUL elements is as follows: + * *aTabIndex = -1 no tabindex Not focusable or tabbable + * *aTabIndex = -1 tabindex="-1" Focusable but not tabbable + * *aTabIndex = -1 tabindex=">=0" Focusable and tabbable + * *aTabIndex >= 0 no tabindex Focusable and tabbable + * *aTabIndex >= 0 tabindex="-1" Focusable but not tabbable + * *aTabIndex >= 0 tabindex=">=0" Focusable and tabbable + * + * If aTabIndex is null, then the tabindex is not computed, and + * true is returned for non-disabled controls and false otherwise. + */ + + // elements are not focusable by default + bool shouldFocus = false; + +#ifdef XP_MACOSX + // on Mac, mouse interactions only focus the element if it's a list, + // or if it's a remote target, since the remote target must handle + // the focus. + if (aWithMouse && IsNonList(mNodeInfo) && + !EventStateManager::IsTopLevelRemoteTarget(this)) { + return false; + } +#endif + + nsCOMPtr xulControl = AsXULControl(); + if (xulControl) { + // a disabled element cannot be focused and is not part of the tab order + bool disabled; + xulControl->GetDisabled(&disabled); + if (disabled) { + if (aTabIndex) *aTabIndex = -1; + return false; + } + shouldFocus = true; + } + + if (aTabIndex) { + Maybe attrVal = GetTabIndexAttrValue(); + if (attrVal.isSome()) { + // The tabindex attribute was specified, so the element becomes + // focusable. + shouldFocus = true; + *aTabIndex = attrVal.value(); + } else { + // otherwise, if there is no tabindex attribute, just use the value of + // *aTabIndex to indicate focusability. Reset any supplied tabindex to 0. + shouldFocus = *aTabIndex >= 0; + if (shouldFocus) { + *aTabIndex = 0; + } + } + + if (xulControl && shouldFocus && sTabFocusModelAppliesToXUL && + !(sTabFocusModel & eTabFocus_formElementsMask)) { + // By default, the tab focus model doesn't apply to xul element on any + // system but OS X. on OS X we're following it for UI elements (XUL) as + // sTabFocusModel is based on "Full Keyboard Access" system setting (see + // mac/nsILookAndFeel). both textboxes and list elements (i.e. trees and + // list) should always be focusable (textboxes are handled as html:input) + // For compatibility, we only do this for controls, otherwise elements + // like cannot take this focus. + if (IsNonList(mNodeInfo)) { + *aTabIndex = -1; + } + } + } + + return shouldFocus; +} + +bool nsXULElement::HasMenu() { + if (auto* button = XULButtonElement::FromNode(this)) { + return button->IsMenu(); + } + return false; +} + +void nsXULElement::OpenMenu(bool aOpenFlag) { + // Flush frames first. It's not clear why this is needed, see bug 1704670. + if (Document* doc = GetComposedDoc()) { + doc->FlushPendingNotifications(FlushType::Frames); + } + + nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); + if (!pm) { + return; + } + + if (aOpenFlag) { + // Nothing will happen if this element isn't a menu. + pm->ShowMenu(this, false); + } else { + // Nothing will happen if this element isn't a menu. + pm->HideMenu(this); + } +} + +Result nsXULElement::PerformAccesskey(bool aKeyCausesActivation, + bool aIsTrustedEvent) { + if (IsXULElement(nsGkAtoms::label)) { + nsAutoString control; + GetAttr(kNameSpaceID_None, nsGkAtoms::control, control); + if (control.IsEmpty()) { + return Err(NS_ERROR_UNEXPECTED); + } + + // XXXsmaug Should we use ShadowRoot::GetElementById in case + // element is in Shadow DOM? + RefPtr document = GetUncomposedDoc(); + if (!document) { + return Err(NS_ERROR_UNEXPECTED); + } + + RefPtr element = document->GetElementById(control); + if (!element) { + return Err(NS_ERROR_UNEXPECTED); + } + + // XXXedgar, This is mainly for HTMLElement which doesn't do visible + // check in PerformAccesskey. We probably should always do visible + // check on HTMLElement even if the PerformAccesskey is not redirected from + // label XULelement per spec. + nsIFrame* frame = element->GetPrimaryFrame(); + if (!frame || !frame->IsVisibleConsideringAncestors()) { + return Err(NS_ERROR_UNEXPECTED); + } + + return element->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent); + } + + nsIFrame* frame = GetPrimaryFrame(); + if (!frame || !frame->IsVisibleConsideringAncestors()) { + return Err(NS_ERROR_UNEXPECTED); + } + + bool focused = false; + // Define behavior for each type of XUL element. + if (!IsXULElement(nsGkAtoms::toolbarbutton)) { + if (RefPtr fm = nsFocusManager::GetFocusManager()) { + RefPtr elementToFocus = this; + // for radio buttons, focus the radiogroup instead + if (IsXULElement(nsGkAtoms::radio)) { + if (nsCOMPtr controlItem = + AsXULSelectControlItem()) { + bool disabled; + controlItem->GetDisabled(&disabled); + if (!disabled) { + controlItem->GetControl(getter_AddRefs(elementToFocus)); + } + } + } + + if (elementToFocus) { + fm->SetFocus(elementToFocus, nsIFocusManager::FLAG_BYKEY); + + // Return true if the element became focused. + nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow(); + focused = (window && window->GetFocusedElement() == elementToFocus); + } + } + } + + if (aKeyCausesActivation && !IsXULElement(nsGkAtoms::menulist)) { + ClickWithInputSource(MouseEvent_Binding::MOZ_SOURCE_KEYBOARD, + aIsTrustedEvent); + return focused; + } + + // If the accesskey won't cause the activation and the focus isn't changed, + // either. Return error so EventStateManager would try to find next element + // to handle the accesskey. + return focused ? Result{focused} : Err(NS_ERROR_ABORT); +} + +//---------------------------------------------------------------------- + +void nsXULElement::AddListenerForAttributeIfNeeded(nsAtom* aLocalName) { + // If appropriate, add a popup listener and/or compile the event + // handler. Called when we change the element's document, create a + // new element, change an attribute's value, etc. + // Eventlistenener-attributes are always in the null namespace. + if (aLocalName == nsGkAtoms::menu || aLocalName == nsGkAtoms::contextmenu || + // XXXdwh popup and context are deprecated + aLocalName == nsGkAtoms::popup || aLocalName == nsGkAtoms::context) { + AddPopupListener(aLocalName); + } + if (nsContentUtils::IsEventAttributeName(aLocalName, EventNameType_XUL)) { + nsAutoString value; + GetAttr(kNameSpaceID_None, aLocalName, value); + SetEventHandler(aLocalName, value, true); + } +} + +void nsXULElement::AddListenerForAttributeIfNeeded(const nsAttrName& aName) { + if (aName.IsAtom()) { + AddListenerForAttributeIfNeeded(aName.Atom()); + } +} + +//---------------------------------------------------------------------- +// +// nsIContent interface +// +void nsXULElement::UpdateEditableState(bool aNotify) { + // Don't call through to Element here because the things + // it does don't work for cases when we're an editable control. + nsIContent* parent = GetParent(); + + SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE)); + UpdateState(aNotify); +} + +class XULInContentErrorReporter : public Runnable { + public: + explicit XULInContentErrorReporter(Document& aDocument) + : mozilla::Runnable("XULInContentErrorReporter"), mDocument(aDocument) {} + + NS_IMETHOD Run() override { + mDocument->WarnOnceAbout(DeprecatedOperations::eImportXULIntoContent, + false); + return NS_OK; + } + + private: + OwningNonNull mDocument; +}; + +static bool NeedTooltipSupport(const nsXULElement& aXULElement) { + if (aXULElement.NodeInfo()->Equals(nsGkAtoms::treechildren)) { + // treechildren always get tooltip support, since cropped tree cells show + // their full text in a tooltip. + return true; + } + + return aXULElement.GetBoolAttr(nsGkAtoms::tooltip) || + aXULElement.GetBoolAttr(nsGkAtoms::tooltiptext); +} + +nsresult nsXULElement::BindToTree(BindContext& aContext, nsINode& aParent) { + nsresult rv = nsStyledElement::BindToTree(aContext, aParent); + NS_ENSURE_SUCCESS(rv, rv); + + if (!IsInComposedDoc()) { + return rv; + } + + Document& doc = aContext.OwnerDoc(); + if (!IsInNativeAnonymousSubtree() && !doc.AllowXULXBL() && + !doc.HasWarnedAbout(DeprecatedOperations::eImportXULIntoContent)) { + nsContentUtils::AddScriptRunner(new XULInContentErrorReporter(doc)); + } + +#ifdef DEBUG + if (!doc.AllowXULXBL() && !doc.IsUnstyledDocument()) { + // To save CPU cycles and memory, we don't load xul.css for other elements + // except scrollbars. + // + // This assertion makes sure no other XUL element is used in a non-XUL + // document. + nsAtom* tag = NodeInfo()->NameAtom(); + MOZ_ASSERT(tag == nsGkAtoms::scrollbar || + tag == nsGkAtoms::scrollbarbutton || + tag == nsGkAtoms::scrollcorner || tag == nsGkAtoms::slider || + tag == nsGkAtoms::thumb || tag == nsGkAtoms::resizer, + "Unexpected XUL element in non-XUL doc"); + } +#endif + + // Within Bug 1492063 and its dependencies we started to apply a + // CSP to system privileged about pages. Since some about: pages + // are implemented in *.xul files we added this workaround to + // apply a CSP to them. To do so, we check the introduced custom + // attribute 'csp' on the root element. + if (doc.GetRootElement() == this) { + nsAutoString cspPolicyStr; + GetAttr(kNameSpaceID_None, nsGkAtoms::csp, cspPolicyStr); + +#ifdef DEBUG + { + nsCOMPtr docCSP = doc.GetCsp(); + uint32_t policyCount = 0; + if (docCSP) { + docCSP->GetPolicyCount(&policyCount); + } + MOZ_ASSERT(policyCount == 0, "how come we already have a policy?"); + } +#endif + + CSP_ApplyMetaCSPToDoc(doc, cspPolicyStr); + } + + if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) { + // Create our XUL key listener and hook it up. + XULKeySetGlobalKeyListener::AttachKeyHandler(this); + } + + RegUnRegAccessKey(true); + + if (NeedTooltipSupport(*this)) { + AddTooltipSupport(); + } + + if (XULBroadcastManager::MayNeedListener(*this)) { + if (!doc.HasXULBroadcastManager()) { + doc.InitializeXULBroadcastManager(); + } + XULBroadcastManager* broadcastManager = doc.GetXULBroadcastManager(); + broadcastManager->AddListener(this); + } + return rv; +} + +void nsXULElement::UnbindFromTree(bool aNullParent) { + if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) { + XULKeySetGlobalKeyListener::DetachKeyHandler(this); + } + + RegUnRegAccessKey(false); + + if (NeedTooltipSupport(*this)) { + RemoveTooltipSupport(); + } + + Document* doc = GetComposedDoc(); + if (doc && doc->HasXULBroadcastManager() && + XULBroadcastManager::MayNeedListener(*this)) { + RefPtr broadcastManager = + doc->GetXULBroadcastManager(); + broadcastManager->RemoveListener(this); + } + + // mControllers can own objects that are implemented + // in JavaScript (such as some implementations of + // nsIControllers. These objects prevent their global + // object's script object from being garbage collected, + // which means JS continues to hold an owning reference + // to the nsGlobalWindow, which owns the document, + // which owns this content. That's a cycle, so we break + // it here. (It might be better to break this by releasing + // mDocument in nsGlobalWindow::SetDocShell, but I'm not + // sure whether that would fix all possible cycles through + // mControllers.) + nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); + if (slots) { + slots->mControllers = nullptr; + } + + nsStyledElement::UnbindFromTree(aNullParent); +} + +void nsXULElement::DoneAddingChildren(bool aHaveNotified) { + if (IsXULElement(nsGkAtoms::linkset)) { + Document* doc = GetComposedDoc(); + if (doc) { + doc->OnL10nResourceContainerParsed(); + } + } +} + +void nsXULElement::RegUnRegAccessKey(bool aDoReg) { + // Don't try to register for unsupported elements + if (!SupportsAccessKey()) { + return; + } + + nsStyledElement::RegUnRegAccessKey(aDoReg); +} + +bool nsXULElement::SupportsAccessKey() const { + if (NodeInfo()->Equals(nsGkAtoms::label) && HasAttr(nsGkAtoms::control)) { + return true; + } + + // XXX(ntim): check if description[value] or description[accesskey] are + // actually used, remove `value` from {Before/After}SetAttr if not the case + if (NodeInfo()->Equals(nsGkAtoms::description) && HasAttr(nsGkAtoms::value) && + HasAttr(nsGkAtoms::control)) { + return true; + } + + return IsAnyOfXULElements(nsGkAtoms::button, nsGkAtoms::toolbarbutton, + nsGkAtoms::checkbox, nsGkAtoms::tab, + nsGkAtoms::radio); +} + +void nsXULElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, bool aNotify) { + if (aNamespaceID == kNameSpaceID_None) { + if (aName == nsGkAtoms::accesskey || aName == nsGkAtoms::control || + aName == nsGkAtoms::value) { + RegUnRegAccessKey(false); + } else if ((aName == nsGkAtoms::command || aName == nsGkAtoms::observes) && + IsInUncomposedDoc()) { + // XXX sXBL/XBL2 issue! Owner or current document? + // XXX Why does this not also remove broadcast listeners if the + // "element" attribute was changed on an ? + nsAutoString oldValue; + GetAttr(kNameSpaceID_None, nsGkAtoms::observes, oldValue); + if (oldValue.IsEmpty()) { + GetAttr(kNameSpaceID_None, nsGkAtoms::command, oldValue); + } + Document* doc = GetUncomposedDoc(); + if (!oldValue.IsEmpty() && doc->HasXULBroadcastManager()) { + RefPtr broadcastManager = + doc->GetXULBroadcastManager(); + broadcastManager->RemoveListener(this); + } +#ifdef DEBUG + } else if (aName == nsGkAtoms::usercontextid) { + const nsAttrValue* oldValue = GetParsedAttr(aName); + if (oldValue && (!aValue || !aValue->Equals(*oldValue))) { + MOZ_ASSERT(false, + "Changing usercontextid doesn't really work properly."); + } +#endif + } + } + + return nsStyledElement::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify); +} + +void nsXULElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName, + const nsAttrValue* aValue, + const nsAttrValue* aOldValue, + nsIPrincipal* aSubjectPrincipal, bool aNotify) { + if (aNamespaceID == kNameSpaceID_None) { + if (aValue) { + AddListenerForAttributeIfNeeded(aName); + } + + if (aName == nsGkAtoms::accesskey || aName == nsGkAtoms::control || + aName == nsGkAtoms::value) { + RegUnRegAccessKey(true); + } else if (aName == nsGkAtoms::tooltip || aName == nsGkAtoms::tooltiptext) { + if (!!aValue != !!aOldValue && IsInComposedDoc() && + !NodeInfo()->Equals(nsGkAtoms::treechildren)) { + if (aValue) { + AddTooltipSupport(); + } else { + RemoveTooltipSupport(); + } + } + } + Document* doc = GetComposedDoc(); + if (doc && doc->HasXULBroadcastManager()) { + RefPtr broadcastManager = + doc->GetXULBroadcastManager(); + broadcastManager->AttributeChanged(this, aNamespaceID, aName); + } + if (doc && XULBroadcastManager::MayNeedListener(*this)) { + if (!doc->HasXULBroadcastManager()) { + doc->InitializeXULBroadcastManager(); + } + XULBroadcastManager* broadcastManager = doc->GetXULBroadcastManager(); + broadcastManager->AddListener(this); + } + + // XXX need to check if they're changing an event handler: if + // so, then we need to unhook the old one. Or something. + } + + return nsStyledElement::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue, + aSubjectPrincipal, aNotify); +} + +void nsXULElement::AddTooltipSupport() { + nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance(); + if (!listener) { + return; + } + + listener->AddTooltipSupport(this); +} + +void nsXULElement::RemoveTooltipSupport() { + nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance(); + if (!listener) { + return; + } + + listener->RemoveTooltipSupport(this); +} + +bool nsXULElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute, + const nsAString& aValue, + nsIPrincipal* aMaybeScriptedPrincipal, + nsAttrValue& aResult) { + if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::tabindex) { + return aResult.ParseIntValue(aValue); + } + + // Parse into a nsAttrValue + if (!nsStyledElement::ParseAttribute(aNamespaceID, aAttribute, aValue, + aMaybeScriptedPrincipal, aResult)) { + // Fall back to parsing as atom for short values + aResult.ParseStringOrAtom(aValue); + } + + return true; +} + +void nsXULElement::DestroyContent() { + nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots(); + if (slots) { + slots->mControllers = nullptr; + } + + nsStyledElement::DestroyContent(); +} + +#ifdef MOZ_DOM_LIST +void nsXULElement::List(FILE* out, int32_t aIndent) const { + nsCString prefix("XUL"); + if (HasSlots()) { + prefix.Append('*'); + } + prefix.Append(' '); + + nsStyledElement::List(out, aIndent, prefix); +} +#endif + +bool nsXULElement::IsEventStoppedFromAnonymousScrollbar(EventMessage aMessage) { + return (IsRootOfNativeAnonymousSubtree() && + IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::scrollcorner) && + (aMessage == eMouseClick || aMessage == eMouseDoubleClick || + aMessage == eXULCommand || aMessage == eContextMenu || + aMessage == eDragStart || aMessage == eMouseAuxClick)); +} + +nsresult nsXULElement::DispatchXULCommand(const EventChainVisitor& aVisitor, + nsAutoString& aCommand) { + // XXX sXBL/XBL2 issue! Owner or current document? + nsCOMPtr doc = GetUncomposedDoc(); + NS_ENSURE_STATE(doc); + RefPtr commandElt = doc->GetElementById(aCommand); + if (commandElt) { + // Create a new command event to dispatch to the element + // pointed to by the command attribute. The new event's + // sourceEvent will be the original command event that we're + // handling. + RefPtr event = aVisitor.mDOMEvent; + uint16_t inputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN; + int16_t button = 0; + while (event) { + NS_ENSURE_STATE(event->GetOriginalTarget() != commandElt); + RefPtr commandEvent = event->AsXULCommandEvent(); + if (commandEvent) { + event = commandEvent->GetSourceEvent(); + inputSource = commandEvent->InputSource(); + button = commandEvent->Button(); + } else { + event = nullptr; + } + } + WidgetInputEvent* orig = aVisitor.mEvent->AsInputEvent(); + nsContentUtils::DispatchXULCommand( + commandElt, orig->IsTrusted(), MOZ_KnownLive(aVisitor.mDOMEvent), + nullptr, orig->IsControl(), orig->IsAlt(), orig->IsShift(), + orig->IsMeta(), inputSource, button); + } else { + NS_WARNING("A XUL element is attached to a command that doesn't exist!\n"); + } + return NS_OK; +} + +void nsXULElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) { + aVisitor.mForceContentDispatch = true; // FIXME! Bug 329119 + if (IsEventStoppedFromAnonymousScrollbar(aVisitor.mEvent->mMessage)) { + // Don't propagate these events from native anonymous scrollbar. + aVisitor.mCanHandle = true; + aVisitor.SetParentTarget(nullptr, false); + return; + } + if (aVisitor.mEvent->mMessage == eXULCommand && + aVisitor.mEvent->mClass == eInputEventClass && + aVisitor.mEvent->mOriginalTarget == static_cast(this) && + !IsXULElement(nsGkAtoms::command)) { + // Check that we really have an xul command event. That will be handled + // in a special way. + // See if we have a command elt. If so, we execute on the command + // instead of on our content element. + if (aVisitor.mDOMEvent && aVisitor.mDOMEvent->AsXULCommandEvent() && + HasNonEmptyAttr(nsGkAtoms::command)) { + // Stop building the event target chain for the original event. + // We don't want it to propagate to any DOM nodes. + aVisitor.mCanHandle = false; + aVisitor.mAutomaticChromeDispatch = false; + // Dispatch XUL command in PreHandleEvent to prevent it breaks event + // target chain creation + aVisitor.mWantsPreHandleEvent = true; + aVisitor.mItemFlags |= NS_DISPATCH_XUL_COMMAND; + return; + } + } + + nsStyledElement::GetEventTargetParent(aVisitor); +} + +nsresult nsXULElement::PreHandleEvent(EventChainVisitor& aVisitor) { + if (aVisitor.mItemFlags & NS_DISPATCH_XUL_COMMAND) { + nsAutoString command; + GetAttr(kNameSpaceID_None, nsGkAtoms::command, command); + MOZ_ASSERT(!command.IsEmpty()); + return DispatchXULCommand(aVisitor, command); + } + return nsStyledElement::PreHandleEvent(aVisitor); +} + +//---------------------------------------------------------------------- +// Implementation methods + +NS_IMETHODIMP_(bool) +nsXULElement::IsAttributeMapped(const nsAtom* aAttribute) const { + return false; +} + +nsIControllers* nsXULElement::GetControllers(ErrorResult& rv) { + if (!Controllers()) { + nsExtendedDOMSlots* slots = ExtendedDOMSlots(); + + slots->mControllers = new nsXULControllers(); + } + + return Controllers(); +} + +void nsXULElement::Click(CallerType aCallerType) { + ClickWithInputSource(MouseEvent_Binding::MOZ_SOURCE_UNKNOWN, + aCallerType == CallerType::System); +} + +void nsXULElement::ClickWithInputSource(uint16_t aInputSource, + bool aIsTrustedEvent) { + if (BoolAttrIsTrue(nsGkAtoms::disabled)) return; + + nsCOMPtr doc = GetComposedDoc(); // Strong just in case + if (doc) { + RefPtr context = doc->GetPresContext(); + if (context) { + // strong ref to PresContext so events don't destroy it + + WidgetMouseEvent eventDown(aIsTrustedEvent, eMouseDown, nullptr, + WidgetMouseEvent::eReal); + WidgetMouseEvent eventUp(aIsTrustedEvent, eMouseUp, nullptr, + WidgetMouseEvent::eReal); + // This helps to avoid commands being dispatched from + // XULButtonElement::PostHandleEventForMenu. + eventUp.mFlags.mMultipleActionsPrevented = true; + WidgetMouseEvent eventClick(aIsTrustedEvent, eMouseClick, nullptr, + WidgetMouseEvent::eReal); + eventDown.mInputSource = eventUp.mInputSource = eventClick.mInputSource = + aInputSource; + + // send mouse down + nsEventStatus status = nsEventStatus_eIgnore; + EventDispatcher::Dispatch(static_cast(this), context, + &eventDown, nullptr, &status); + + // send mouse up + status = nsEventStatus_eIgnore; // reset status + EventDispatcher::Dispatch(static_cast(this), context, + &eventUp, nullptr, &status); + + // send mouse click + status = nsEventStatus_eIgnore; // reset status + EventDispatcher::Dispatch(static_cast(this), context, + &eventClick, nullptr, &status); + + // If the click has been prevented, lets skip the command call + // this is how a physical click works + if (status == nsEventStatus_eConsumeNoDefault) { + return; + } + } + } + + // oncommand is fired when an element is clicked... + DoCommand(); +} + +void nsXULElement::DoCommand() { + nsCOMPtr doc = GetComposedDoc(); // strong just in case + if (doc) { + RefPtr self = this; + nsContentUtils::DispatchXULCommand(self, true); + } +} + +nsresult nsXULElement::AddPopupListener(nsAtom* aName) { + // Add a popup listener to the element + bool isContext = + (aName == nsGkAtoms::context || aName == nsGkAtoms::contextmenu); + uint32_t listenerFlag = isContext ? XUL_ELEMENT_HAS_CONTENTMENU_LISTENER + : XUL_ELEMENT_HAS_POPUP_LISTENER; + + if (HasFlag(listenerFlag)) { + return NS_OK; + } + + nsCOMPtr listener = + new nsXULPopupListener(this, isContext); + + // Add the popup as a listener on this element. + EventListenerManager* manager = GetOrCreateListenerManager(); + SetFlags(listenerFlag); + + if (isContext) { + manager->AddEventListenerByType(listener, u"contextmenu"_ns, + TrustedEventsAtSystemGroupBubble()); + } else { + manager->AddEventListenerByType(listener, u"mousedown"_ns, + TrustedEventsAtSystemGroupBubble()); + } + return NS_OK; +} + +//---------------------------------------------------------------------- + +nsresult nsXULElement::MakeHeavyweight(nsXULPrototypeElement* aPrototype) { + if (!aPrototype) { + return NS_OK; + } + + size_t i; + nsresult rv; + for (i = 0; i < aPrototype->mAttributes.Length(); ++i) { + nsXULPrototypeAttribute* protoattr = &aPrototype->mAttributes[i]; + nsAttrValue attrValue; + + // Style rules need to be cloned. + if (protoattr->mValue.Type() == nsAttrValue::eCSSDeclaration) { + DeclarationBlock* decl = protoattr->mValue.GetCSSDeclarationValue(); + RefPtr declClone = decl->Clone(); + + nsString stringValue; + protoattr->mValue.ToString(stringValue); + + attrValue.SetTo(declClone.forget(), &stringValue); + } else { + attrValue.SetTo(protoattr->mValue); + } + + bool oldValueSet; + // XXX we might wanna have a SetAndTakeAttr that takes an nsAttrName + if (protoattr->mName.IsAtom()) { + rv = mAttrs.SetAndSwapAttr(protoattr->mName.Atom(), attrValue, + &oldValueSet); + } else { + rv = mAttrs.SetAndSwapAttr(protoattr->mName.NodeInfo(), attrValue, + &oldValueSet); + } + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +bool nsXULElement::BoolAttrIsTrue(nsAtom* aName) const { + const nsAttrValue* attr = GetAttrInfo(kNameSpaceID_None, aName).mValue; + + return attr && attr->Type() == nsAttrValue::eAtom && + attr->GetAtomValue() == nsGkAtoms::_true; +} + +bool nsXULElement::IsEventAttributeNameInternal(nsAtom* aName) { + return nsContentUtils::IsEventAttributeName(aName, EventNameType_XUL); +} + +JSObject* nsXULElement::WrapNode(JSContext* aCx, + JS::Handle aGivenProto) { + return dom::XULElement_Binding::Wrap(aCx, this, aGivenProto); +} + +bool nsXULElement::IsInteractiveHTMLContent() const { + return IsXULElement(nsGkAtoms::menupopup) || + Element::IsInteractiveHTMLContent(); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULPrototypeNode) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULPrototypeNode) + if (tmp->mType == nsXULPrototypeNode::eType_Element) { + static_cast(tmp)->Unlink(); + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeNode) + if (tmp->mType == nsXULPrototypeNode::eType_Element) { + nsXULPrototypeElement* elem = static_cast(tmp); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mNodeInfo"); + cb.NoteNativeChild(elem->mNodeInfo, + NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo)); + size_t i; + for (i = 0; i < elem->mAttributes.Length(); ++i) { + const nsAttrName& name = elem->mAttributes[i].mName; + if (!name.IsAtom()) { + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, + "mAttributes[i].mName.NodeInfo()"); + cb.NoteNativeChild(name.NodeInfo(), + NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo)); + } + } + ImplCycleCollectionTraverse(cb, elem->mChildren, "mChildren"); + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXULPrototypeNode) +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +//---------------------------------------------------------------------- +// +// nsXULPrototypeAttribute +// + +nsXULPrototypeAttribute::~nsXULPrototypeAttribute() { + MOZ_COUNT_DTOR(nsXULPrototypeAttribute); +} + +//---------------------------------------------------------------------- +// +// nsXULPrototypeElement +// + +nsresult nsXULPrototypeElement::Serialize( + nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc, + const nsTArray>* aNodeInfos) { + nsresult rv; + + // Write basic prototype data + rv = aStream->Write32(mType); + + // Write Node Info + int32_t index = aNodeInfos->IndexOf(mNodeInfo); + NS_ASSERTION(index >= 0, "unknown mozilla::dom::NodeInfo index"); + nsresult tmp = aStream->Write32(index); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + // Write Attributes + tmp = aStream->Write32(mAttributes.Length()); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + nsAutoString attributeValue; + size_t i; + for (i = 0; i < mAttributes.Length(); ++i) { + RefPtr ni; + if (mAttributes[i].mName.IsAtom()) { + ni = mNodeInfo->NodeInfoManager()->GetNodeInfo( + mAttributes[i].mName.Atom(), nullptr, kNameSpaceID_None, + nsINode::ATTRIBUTE_NODE); + NS_ASSERTION(ni, "the nodeinfo should already exist"); + } else { + ni = mAttributes[i].mName.NodeInfo(); + } + + index = aNodeInfos->IndexOf(ni); + NS_ASSERTION(index >= 0, "unknown mozilla::dom::NodeInfo index"); + tmp = aStream->Write32(index); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + mAttributes[i].mValue.ToString(attributeValue); + tmp = aStream->WriteWStringZ(attributeValue.get()); + if (NS_FAILED(tmp)) { + rv = tmp; + } + } + + // Now write children + tmp = aStream->Write32(uint32_t(mChildren.Length())); + if (NS_FAILED(tmp)) { + rv = tmp; + } + for (i = 0; i < mChildren.Length(); i++) { + nsXULPrototypeNode* child = mChildren[i].get(); + switch (child->mType) { + case eType_Element: + case eType_Text: + case eType_PI: + tmp = child->Serialize(aStream, aProtoDoc, aNodeInfos); + if (NS_FAILED(tmp)) { + rv = tmp; + } + break; + case eType_Script: + tmp = aStream->Write32(child->mType); + if (NS_FAILED(tmp)) { + rv = tmp; + } + nsXULPrototypeScript* script = + static_cast(child); + + tmp = aStream->Write8(script->mOutOfLine); + if (NS_FAILED(tmp)) { + rv = tmp; + } + if (!script->mOutOfLine) { + tmp = script->Serialize(aStream, aProtoDoc, aNodeInfos); + if (NS_FAILED(tmp)) { + rv = tmp; + } + } else { + tmp = aStream->WriteCompoundObject(script->mSrcURI, + NS_GET_IID(nsIURI), true); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + if (script->HasStencil()) { + // This may return NS_OK without muxing script->mSrcURI's + // data into the cache file, in the case where that + // muxed document is already there (written by a prior + // session, or by an earlier cache episode during this + // session). + tmp = script->SerializeOutOfLine(aStream, aProtoDoc); + if (NS_FAILED(tmp)) { + rv = tmp; + } + } + } + break; + } + } + + return rv; +} + +nsresult nsXULPrototypeElement::Deserialize( + nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc, + nsIURI* aDocumentURI, + const nsTArray>* aNodeInfos) { + MOZ_ASSERT(aNodeInfos, "missing nodeinfo array"); + + // Read Node Info + uint32_t number = 0; + nsresult rv = aStream->Read32(&number); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + mNodeInfo = aNodeInfos->SafeElementAt(number, nullptr); + if (!mNodeInfo) { + return NS_ERROR_UNEXPECTED; + } + + // Read Attributes + rv = aStream->Read32(&number); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + int32_t attributes = int32_t(number); + + if (attributes > 0) { + mAttributes.AppendElements(attributes); + + nsAutoString attributeValue; + for (size_t i = 0; i < mAttributes.Length(); ++i) { + rv = aStream->Read32(&number); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + mozilla::dom::NodeInfo* ni = aNodeInfos->SafeElementAt(number, nullptr); + if (!ni) { + return NS_ERROR_UNEXPECTED; + } + + mAttributes[i].mName.SetTo(ni); + + rv = aStream->ReadString(attributeValue); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + rv = SetAttrAt(i, attributeValue, aDocumentURI); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + } + } + + rv = aStream->Read32(&number); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + uint32_t numChildren = int32_t(number); + + if (numChildren > 0) { + if (!mChildren.SetCapacity(numChildren, fallible)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + for (uint32_t i = 0; i < numChildren; i++) { + rv = aStream->Read32(&number); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + Type childType = (Type)number; + + RefPtr child; + + switch (childType) { + case eType_Element: + child = new nsXULPrototypeElement(); + rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + break; + case eType_Text: + child = new nsXULPrototypeText(); + rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + break; + case eType_PI: + child = new nsXULPrototypePI(); + rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + break; + case eType_Script: { + // language version/options obtained during deserialization. + RefPtr script = new nsXULPrototypeScript(0); + + rv = aStream->ReadBoolean(&script->mOutOfLine); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + if (!script->mOutOfLine) { + rv = script->Deserialize(aStream, aProtoDoc, aDocumentURI, + aNodeInfos); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + } else { + nsCOMPtr supports; + rv = aStream->ReadObject(true, getter_AddRefs(supports)); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + script->mSrcURI = do_QueryInterface(supports); + + rv = script->DeserializeOutOfLine(aStream, aProtoDoc); + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + } + + child = std::move(script); + break; + } + default: + MOZ_ASSERT(false, "Unexpected child type!"); + return NS_ERROR_UNEXPECTED; + } + + MOZ_ASSERT(child, "Don't append null to mChildren"); + MOZ_ASSERT(child->mType == childType); + mChildren.AppendElement(child); + + // Oh dear. Something failed during the deserialization. + // We don't know what. But likely consequences of failed + // deserializations included calls to |AbortCaching| which + // shuts down the cache and closes our streams. + // If that happens, next time through this loop, we die a messy + // death. So, let's just fail now, and propagate that failure + // upward so that the ChromeProtocolHandler knows it can't use + // a cached chrome channel for this. + if (NS_WARN_IF(NS_FAILED(rv))) return rv; + } + } + + return rv; +} + +nsresult nsXULPrototypeElement::SetAttrAt(uint32_t aPos, + const nsAString& aValue, + nsIURI* aDocumentURI) { + MOZ_ASSERT(aPos < mAttributes.Length(), "out-of-bounds"); + + // WARNING!! + // This code is largely duplicated in nsXULElement::SetAttr. + // Any changes should be made to both functions. + + if (!mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) { + if (mNodeInfo->NamespaceEquals(kNameSpaceID_XHTML) && + mAttributes[aPos].mName.Equals(nsGkAtoms::is)) { + // We still care about the is attribute set on HTML elements. + mAttributes[aPos].mValue.ParseAtom(aValue); + mIsAtom = mAttributes[aPos].mValue.GetAtomValue(); + + return NS_OK; + } + + mAttributes[aPos].mValue.ParseStringOrAtom(aValue); + + return NS_OK; + } + + if (mAttributes[aPos].mName.Equals(nsGkAtoms::id) && !aValue.IsEmpty()) { + mHasIdAttribute = true; + // Store id as atom. + // id="" means that the element has no id. Not that it has + // emptystring as id. + mAttributes[aPos].mValue.ParseAtom(aValue); + + return NS_OK; + } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::is)) { + // Store is as atom. + mAttributes[aPos].mValue.ParseAtom(aValue); + mIsAtom = mAttributes[aPos].mValue.GetAtomValue(); + + return NS_OK; + } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::_class)) { + mHasClassAttribute = true; + // Compute the element's class list + mAttributes[aPos].mValue.ParseAtomArray(aValue); + + return NS_OK; + } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::style)) { + mHasStyleAttribute = true; + // Parse the element's 'style' attribute + + // This is basically duplicating what nsINode::NodePrincipal() does + nsIPrincipal* principal = mNodeInfo->NodeInfoManager()->DocumentPrincipal(); + // XXX Get correct Base URI (need GetBaseURI on *prototype* element) + // TODO: If we implement Content Security Policy for chrome documents + // as has been discussed, the CSP should be checked here to see if + // inline styles are allowed to be applied. + // XXX No specific specs talk about xul and referrer policy, pass Unset + auto referrerInfo = + MakeRefPtr(aDocumentURI, ReferrerPolicy::_empty); + auto data = MakeRefPtr(aDocumentURI, referrerInfo, principal); + RefPtr declaration = DeclarationBlock::FromCssText( + aValue, data, eCompatibility_FullStandards, nullptr, + StyleCssRuleType::Style); + if (declaration) { + mAttributes[aPos].mValue.SetTo(declaration.forget(), &aValue); + + return NS_OK; + } + // Don't abort if parsing failed, it could just be malformed css. + } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::tabindex)) { + mAttributes[aPos].mValue.ParseIntValue(aValue); + + return NS_OK; + } + + mAttributes[aPos].mValue.ParseStringOrAtom(aValue); + + return NS_OK; +} + +void nsXULPrototypeElement::Unlink() { + mAttributes.Clear(); + mChildren.Clear(); +} + +//---------------------------------------------------------------------- +// +// nsXULPrototypeScript +// + +nsXULPrototypeScript::nsXULPrototypeScript(uint32_t aLineNo) + : nsXULPrototypeNode(eType_Script), + mLineNo(aLineNo), + mSrcLoading(false), + mOutOfLine(true), + mSrcLoadWaiters(nullptr), + mStencil(nullptr) {} + +static nsresult WriteStencil(nsIObjectOutputStream* aStream, JSContext* aCx, + JS::Stencil* aStencil) { + JS::TranscodeBuffer buffer; + JS::TranscodeResult code; + code = JS::EncodeStencil(aCx, aStencil, buffer); + + if (code != JS::TranscodeResult::Ok) { + if (code == JS::TranscodeResult::Throw) { + JS_ClearPendingException(aCx); + return NS_ERROR_OUT_OF_MEMORY; + } + + MOZ_ASSERT(IsTranscodeFailureResult(code)); + return NS_ERROR_FAILURE; + } + + size_t size = buffer.length(); + if (size > UINT32_MAX) { + return NS_ERROR_FAILURE; + } + nsresult rv = aStream->Write32(size); + if (NS_SUCCEEDED(rv)) { + // Ideally we could just pass "buffer" here. See bug 1566574. + rv = aStream->WriteBytes(Span(buffer.begin(), size)); + } + + return rv; +} + +static nsresult ReadStencil(nsIObjectInputStream* aStream, JSContext* aCx, + const JS::DecodeOptions& aOptions, + JS::Stencil** aStencilOut) { + // We don't serialize mutedError-ness of scripts, which is fine as long as + // we only serialize system and XUL-y things. We can detect this by checking + // where the caller wants us to deserialize. + // + // CompilationScope() could theoretically GC, so get that out of the way + // before comparing to the cx global. + JSObject* loaderGlobal = xpc::CompilationScope(); + MOZ_RELEASE_ASSERT(nsContentUtils::IsSystemCaller(aCx) || + JS::CurrentGlobalOrNull(aCx) == loaderGlobal); + + uint32_t size; + nsresult rv = aStream->Read32(&size); + if (NS_FAILED(rv)) { + return rv; + } + + char* data; + rv = aStream->ReadBytes(size, &data); + if (NS_FAILED(rv)) { + return rv; + } + + // The decoded stencil shouldn't borrow from the XDR buffer. + MOZ_ASSERT(!aOptions.borrowBuffer); + auto cleanupData = MakeScopeExit([&]() { free(data); }); + + JS::TranscodeRange range(reinterpret_cast(data), size); + + { + JS::TranscodeResult code; + RefPtr stencil; + code = JS::DecodeStencil(aCx, aOptions, range, getter_AddRefs(stencil)); + if (code != JS::TranscodeResult::Ok) { + if (code == JS::TranscodeResult::Throw) { + JS_ClearPendingException(aCx); + return NS_ERROR_OUT_OF_MEMORY; + } + + MOZ_ASSERT(IsTranscodeFailureResult(code)); + return NS_ERROR_FAILURE; + } + + stencil.forget(aStencilOut); + } + + return rv; +} + +void nsXULPrototypeScript::FillCompileOptions(JS::CompileOptions& options) { + // If the script was inline, tell the JS parser to save source for + // Function.prototype.toSource(). If it's out of line, we retrieve the + // source from the files on demand. + options.setSourceIsLazy(mOutOfLine); +} + +nsresult nsXULPrototypeScript::Serialize( + nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc, + const nsTArray>* aNodeInfos) { + NS_ENSURE_TRUE(aProtoDoc, NS_ERROR_UNEXPECTED); + + AutoJSAPI jsapi; + if (!jsapi.Init(xpc::CompilationScope())) { + return NS_ERROR_UNEXPECTED; + } + + NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mStencil, + "script source still loading when serializing?!"); + if (!mStencil) return NS_ERROR_FAILURE; + + // Write basic prototype data + nsresult rv; + rv = aStream->Write32(mLineNo); + if (NS_FAILED(rv)) return rv; + + JSContext* cx = jsapi.cx(); + MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx)); + + return WriteStencil(aStream, cx, mStencil); +} + +nsresult nsXULPrototypeScript::SerializeOutOfLine( + nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc) { + if (!mSrcURI->SchemeIs("chrome")) + // Don't cache scripts that don't come from chrome uris. + return NS_ERROR_NOT_IMPLEMENTED; + + nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance(); + if (!cache) return NS_ERROR_OUT_OF_MEMORY; + + NS_ASSERTION(cache->IsEnabled(), + "writing to the cache file, but the XUL cache is off?"); + bool exists; + cache->HasScript(mSrcURI, &exists); + + /* return will be NS_OK from GetAsciiSpec. + * that makes no sense. + * nor does returning NS_OK from HasMuxedDocument. + * XXX return something meaningful. + */ + if (exists) return NS_OK; + + nsCOMPtr oos; + nsresult rv = cache->GetScriptOutputStream(mSrcURI, getter_AddRefs(oos)); + NS_ENSURE_SUCCESS(rv, rv); + + nsresult tmp = Serialize(oos, aProtoDoc, nullptr); + if (NS_FAILED(tmp)) { + rv = tmp; + } + tmp = cache->FinishScriptOutputStream(mSrcURI); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + if (NS_FAILED(rv)) cache->AbortCaching(); + return rv; +} + +nsresult nsXULPrototypeScript::Deserialize( + nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc, + nsIURI* aDocumentURI, + const nsTArray>* aNodeInfos) { + nsresult rv; + NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mStencil, + "prototype script not well-initialized when deserializing?!"); + + // Read basic prototype data + rv = aStream->Read32(&mLineNo); + if (NS_FAILED(rv)) return rv; + + AutoJSAPI jsapi; + if (!jsapi.Init(xpc::CompilationScope())) { + return NS_ERROR_UNEXPECTED; + } + JSContext* cx = jsapi.cx(); + + JS::DecodeOptions options; + RefPtr newStencil; + rv = ReadStencil(aStream, cx, options, getter_AddRefs(newStencil)); + NS_ENSURE_SUCCESS(rv, rv); + Set(newStencil); + return NS_OK; +} + +nsresult nsXULPrototypeScript::DeserializeOutOfLine( + nsIObjectInputStream* aInput, nsXULPrototypeDocument* aProtoDoc) { + // Keep track of failure via rv, so we can + // AbortCaching if things look bad. + nsresult rv = NS_OK; + nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance(); + + nsCOMPtr objectInput = aInput; + if (cache) { + bool useXULCache = true; + if (mSrcURI) { + // NB: we must check the XUL script cache early, to avoid + // multiple deserialization attempts for a given script. + // Note that PrototypeDocumentContentSink::LoadScript + // checks the XUL script cache too, in order to handle the + // serialization case. + // + // We need do this only for