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