diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/xul/nsXULElement.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/xul/nsXULElement.cpp')
-rw-r--r-- | dom/xul/nsXULElement.cpp | 2975 |
1 files changed, 2975 insertions, 0 deletions
diff --git a/dom/xul/nsXULElement.cpp b/dom/xul/nsXULElement.cpp new file mode 100644 index 000000000..14fa898ab --- /dev/null +++ b/dom/xul/nsXULElement.cpp @@ -0,0 +1,2975 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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/. + * + * This Original Code has been modified by IBM Corporation. + * Modifications made by IBM described herein are + * Copyright (c) International Business Machines + * Corporation, 2000 + * + * Modifications to Mozilla code or documentation + * identified per MPL Section 3.3 + * + * Date Modified by Description of modification + * 03/27/2000 IBM Corp. Added PR_CALLBACK for Optlink + * use in OS2 + */ + +#include "nsCOMPtr.h" +#include "nsDOMCID.h" +#include "nsError.h" +#include "nsDOMString.h" +#include "nsIDOMEvent.h" +#include "nsIAtom.h" +#include "nsIBaseWindow.h" +#include "nsIDOMAttr.h" +#include "nsIDOMDocument.h" +#include "nsIDOMElement.h" +#include "nsIDOMEventListener.h" +#include "nsIDOMNodeList.h" +#include "nsIDOMXULCommandDispatcher.h" +#include "nsIDOMXULElement.h" +#include "nsIDOMXULSelectCntrlItemEl.h" +#include "nsIDocument.h" +#include "nsLayoutStylesheetCache.h" +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/EventStates.h" +#include "mozilla/DeclarationBlockInlines.h" +#include "nsFocusManager.h" +#include "nsHTMLStyleSheet.h" +#include "nsNameSpaceManager.h" +#include "nsIObjectInputStream.h" +#include "nsIObjectOutputStream.h" +#include "nsIPresShell.h" +#include "nsIPrincipal.h" +#include "nsIRDFCompositeDataSource.h" +#include "nsIRDFNode.h" +#include "nsIRDFService.h" +#include "nsIScriptContext.h" +#include "nsIScriptError.h" +#include "nsIScriptSecurityManager.h" +#include "nsIServiceManager.h" +#include "mozilla/css/StyleRule.h" +#include "nsIURL.h" +#include "nsViewManager.h" +#include "nsIWidget.h" +#include "nsIXULDocument.h" +#include "nsIXULTemplateBuilder.h" +#include "nsLayoutCID.h" +#include "nsContentCID.h" +#include "mozilla/dom/Event.h" +#include "nsRDFCID.h" +#include "nsStyleConsts.h" +#include "nsXPIDLString.h" +#include "nsXULControllers.h" +#include "nsIBoxObject.h" +#include "nsPIBoxObject.h" +#include "XULDocument.h" +#include "nsXULPopupListener.h" +#include "nsRuleWalker.h" +#include "nsIDOMCSSStyleDeclaration.h" +#include "nsCSSParser.h" +#include "ListBoxObject.h" +#include "nsContentUtils.h" +#include "nsContentList.h" +#include "mozilla/InternalMutationEvent.h" +#include "mozilla/MouseEvents.h" +#include "nsIDOMMutationEvent.h" +#include "nsPIDOMWindow.h" +#include "nsJSPrincipals.h" +#include "nsDOMAttributeMap.h" +#include "nsGkAtoms.h" +#include "nsXULContentUtils.h" +#include "nsNodeUtils.h" +#include "nsFrameLoader.h" +#include "mozilla/Logging.h" +#include "rdf.h" +#include "nsIControllers.h" +#include "nsAttrValueOrString.h" +#include "nsAttrValueInlines.h" +#include "mozilla/Attributes.h" +#include "nsIController.h" +#include "nsQueryObject.h" +#include <algorithm> +#include "nsIDOMChromeWindow.h" + +// The XUL doc interface +#include "nsIDOMXULDocument.h" + +#include "nsReadableUtils.h" +#include "nsIFrame.h" +#include "nsNodeInfoManager.h" +#include "nsXBLBinding.h" +#include "mozilla/EventDispatcher.h" +#include "mozAutoDocUpdate.h" +#include "nsIDOMXULCommandEvent.h" +#include "nsCCUncollectableMarker.h" +#include "nsICSSDeclaration.h" + +#include "mozilla/dom/XULElementBinding.h" +#include "mozilla/dom/BoxObject.h" +#include "mozilla/dom/HTMLIFrameElement.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 + +class nsXULElementTearoff final : public nsIFrameLoaderOwner +{ + ~nsXULElementTearoff() {} + +public: + NS_DECL_CYCLE_COLLECTING_ISUPPORTS + NS_DECL_CYCLE_COLLECTION_CLASS(nsXULElementTearoff) + + explicit nsXULElementTearoff(nsXULElement* aElement) + : mElement(aElement) + { + } + + NS_FORWARD_NSIFRAMELOADEROWNER(static_cast<nsXULElement*>(mElement.get())->) +private: + nsCOMPtr<nsIDOMXULElement> mElement; +}; + +NS_IMPL_CYCLE_COLLECTION(nsXULElementTearoff, mElement) + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsXULElementTearoff) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsXULElementTearoff) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsXULElementTearoff) + NS_INTERFACE_MAP_ENTRY(nsIFrameLoaderOwner) +NS_INTERFACE_MAP_END_AGGREGATED(mElement) + +//---------------------------------------------------------------------- +// nsXULElement +// + +nsXULElement::nsXULElement(already_AddRefed<mozilla::dom::NodeInfo> aNodeInfo) + : nsStyledElement(aNodeInfo), + mBindingParent(nullptr) +{ + XUL_PROTOTYPE_ATTRIBUTE_METER(gNumElements); + + // We may be READWRITE by default; check. + if (IsReadWriteTextElement()) { + AddStatesSilently(NS_EVENT_STATE_MOZ_READWRITE); + RemoveStatesSilently(NS_EVENT_STATE_MOZ_READONLY); + } +} + +nsXULElement::~nsXULElement() +{ +} + +nsXULElement::nsXULSlots::nsXULSlots() + : nsXULElement::nsDOMSlots() +{ +} + +nsXULElement::nsXULSlots::~nsXULSlots() +{ + NS_IF_RELEASE(mControllers); // Forces release + nsCOMPtr<nsIFrameLoader> frameLoader = do_QueryInterface(mFrameLoaderOrOpener); + if (frameLoader) { + static_cast<nsFrameLoader*>(frameLoader.get())->Destroy(); + } +} + +void +nsXULElement::nsXULSlots::Traverse(nsCycleCollectionTraversalCallback &cb) +{ + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mFrameLoaderOrOpener"); + cb.NoteXPCOMChild(mFrameLoaderOrOpener); +} + +nsINode::nsSlots* +nsXULElement::CreateSlots() +{ + return new nsXULSlots(); +} + +void +nsXULElement::MaybeUpdatePrivateLifetime() +{ + if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::windowtype, + NS_LITERAL_STRING("navigator:browser"), + eCaseMatters)) { + return; + } + + nsPIDOMWindowOuter* win = OwnerDoc()->GetWindow(); + nsCOMPtr<nsIDocShell> docShell = win ? win->GetDocShell() : nullptr; + if (docShell) { + docShell->SetAffectPrivateSessionLifetime(false); + } +} + +/* static */ +already_AddRefed<nsXULElement> +nsXULElement::Create(nsXULPrototypeElement* aPrototype, mozilla::dom::NodeInfo *aNodeInfo, + bool aIsScriptable, bool aIsRoot) +{ + RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo; + RefPtr<nsXULElement> element = new nsXULElement(ni.forget()); + if (element) { + if (aPrototype->mHasIdAttribute) { + element->SetHasID(); + } + if (aPrototype->mHasClassAttribute) { + element->SetFlags(NODE_MAY_HAVE_CLASS); + } + 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 (uint32_t i = 0; i < aPrototype->mNumAttributes; ++i) { + element->AddListenerFor(aPrototype->mAttributes[i].mName, + true); + } + } + + if (aIsRoot && aPrototype->mNodeInfo->Equals(nsGkAtoms::window)) { + for (uint32_t i = 0; i < aPrototype->mNumAttributes; ++i) { + if (aPrototype->mAttributes[i].mName.Equals(nsGkAtoms::windowtype)) { + element->MaybeUpdatePrivateLifetime(); + } + } + } + } + + return element.forget(); +} + +nsresult +nsXULElement::Create(nsXULPrototypeElement* aPrototype, + nsIDocument* aDocument, + bool aIsScriptable, + bool aIsRoot, + Element** aResult) +{ + // Create an nsXULElement from a prototype + NS_PRECONDITION(aPrototype != nullptr, "null ptr"); + if (! aPrototype) + return NS_ERROR_NULL_POINTER; + + NS_PRECONDITION(aResult != nullptr, "null ptr"); + if (! aResult) + return NS_ERROR_NULL_POINTER; + + RefPtr<mozilla::dom::NodeInfo> nodeInfo; + if (aDocument) { + mozilla::dom::NodeInfo* ni = aPrototype->mNodeInfo; + nodeInfo = aDocument->NodeInfoManager()-> + GetNodeInfo(ni->NameAtom(), ni->GetPrefixAtom(), ni->NamespaceID(), + nsIDOMNode::ELEMENT_NODE); + } else { + nodeInfo = aPrototype->mNodeInfo; + } + + RefPtr<nsXULElement> element = Create(aPrototype, nodeInfo, + aIsScriptable, aIsRoot); + element.forget(aResult); + + return NS_OK; +} + +nsresult +NS_NewXULElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) +{ + RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo; + + NS_PRECONDITION(ni, "need nodeinfo for non-proto Create"); + + nsIDocument* doc = ni->GetDocument(); + if (doc && !doc->AllowXULXBL()) { + return NS_ERROR_NOT_AVAILABLE; + } + + NS_ADDREF(*aResult = new nsXULElement(ni.forget())); + + return NS_OK; +} + +void +NS_TrustedNewXULElement(nsIContent** aResult, + already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) +{ + RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo; + NS_PRECONDITION(ni, "need nodeinfo for non-proto Create"); + + // Create an nsXULElement with the specified namespace and tag. + NS_ADDREF(*aResult = new nsXULElement(ni.forget())); +} + +//---------------------------------------------------------------------- +// nsISupports interface + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULElement) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXULElement, + nsStyledElement) + { + nsXULSlots* slots = static_cast<nsXULSlots*>(tmp->GetExistingSlots()); + if (slots) { + slots->Traverse(cb); + } + } +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsXULElement, + nsStyledElement) + // Why aren't we unlinking the prototype? + tmp->ClearHasID(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_ADDREF_INHERITED(nsXULElement, nsStyledElement) +NS_IMPL_RELEASE_INHERITED(nsXULElement, nsStyledElement) + +NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsXULElement) + NS_INTERFACE_TABLE_INHERITED(nsXULElement, nsIDOMNode, nsIDOMElement, + nsIDOMXULElement) + NS_ELEMENT_INTERFACE_TABLE_TO_MAP_SEGUE + NS_INTERFACE_MAP_ENTRY_TEAROFF(nsIFrameLoaderOwner, + new nsXULElementTearoff(this)) +NS_INTERFACE_MAP_END_INHERITING(nsStyledElement) + +//---------------------------------------------------------------------- +// nsIDOMNode interface + +nsresult +nsXULElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const +{ + *aResult = nullptr; + + RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo; + RefPtr<nsXULElement> element = new nsXULElement(ni.forget()); + + // XXX TODO: set up RDF generic builder n' stuff if there is a + // 'datasources' attribute? This is really kind of tricky, + // because then we'd need to -selectively- copy children that + // -weren't- generated from RDF. Ugh. Forget it. + + // Note that we're _not_ copying mControllers. + + uint32_t count = mAttrsAndChildren.AttrCount(); + nsresult rv = NS_OK; + for (uint32_t i = 0; i < count; ++i) { + const nsAttrName* originalName = mAttrsAndChildren.AttrNameAt(i); + const nsAttrValue* originalValue = mAttrsAndChildren.AttrAt(i); + nsAttrValue attrValue; + + // Style rules need to be cloned. + if (originalValue->Type() == nsAttrValue::eCSSDeclaration) { + DeclarationBlock* decl = originalValue->GetCSSDeclarationValue(); + RefPtr<css::Declaration> + declClone = new css::Declaration(*decl->AsGecko()); + + nsString stringValue; + originalValue->ToString(stringValue); + + attrValue.SetTo(declClone.forget(), &stringValue); + } else { + attrValue.SetTo(*originalValue); + } + + if (originalName->IsAtom()) { + rv = element->mAttrsAndChildren.SetAndSwapAttr(originalName->Atom(), + attrValue); + } else { + rv = element->mAttrsAndChildren.SetAndSwapAttr(originalName->NodeInfo(), + attrValue); + } + NS_ENSURE_SUCCESS(rv, rv); + element->AddListenerFor(*originalName, true); + if (originalName->Equals(nsGkAtoms::id) && + !originalValue->IsEmptyString()) { + element->SetHasID(); + } + if (originalName->Equals(nsGkAtoms::_class)) { + element->SetFlags(NODE_MAY_HAVE_CLASS); + } + if (originalName->Equals(nsGkAtoms::style)) { + element->SetMayHaveStyle(); + } + } + + element.forget(aResult); + return rv; +} + +//---------------------------------------------------------------------- + +NS_IMETHODIMP +nsXULElement::GetElementsByAttribute(const nsAString& aAttribute, + const nsAString& aValue, + nsIDOMNodeList** aReturn) +{ + *aReturn = GetElementsByAttribute(aAttribute, aValue).take(); + return NS_OK; +} + +already_AddRefed<nsINodeList> +nsXULElement::GetElementsByAttribute(const nsAString& aAttribute, + const nsAString& aValue) +{ + nsCOMPtr<nsIAtom> attrAtom(NS_Atomize(aAttribute)); + void* attrValue = new nsString(aValue); + RefPtr<nsContentList> list = + new nsContentList(this, + XULDocument::MatchAttribute, + nsContentUtils::DestroyMatchString, + attrValue, + true, + attrAtom, + kNameSpaceID_Unknown); + return list.forget(); +} + +NS_IMETHODIMP +nsXULElement::GetElementsByAttributeNS(const nsAString& aNamespaceURI, + const nsAString& aAttribute, + const nsAString& aValue, + nsIDOMNodeList** aReturn) +{ + ErrorResult rv; + *aReturn = + GetElementsByAttributeNS(aNamespaceURI, aAttribute, aValue, rv).take(); + return rv.StealNSResult(); +} + +already_AddRefed<nsINodeList> +nsXULElement::GetElementsByAttributeNS(const nsAString& aNamespaceURI, + const nsAString& aAttribute, + const nsAString& aValue, + ErrorResult& rv) +{ + nsCOMPtr<nsIAtom> attrAtom(NS_Atomize(aAttribute)); + + int32_t nameSpaceId = kNameSpaceID_Wildcard; + if (!aNamespaceURI.EqualsLiteral("*")) { + rv = + nsContentUtils::NameSpaceManager()->RegisterNameSpace(aNamespaceURI, + nameSpaceId); + if (rv.Failed()) { + return nullptr; + } + } + + void* attrValue = new nsString(aValue); + RefPtr<nsContentList> list = + new nsContentList(this, + XULDocument::MatchAttribute, + nsContentUtils::DestroyMatchString, + attrValue, + true, + attrAtom, + nameSpaceId); + + return list.forget(); +} + +EventListenerManager* +nsXULElement::GetEventListenerManagerForAttr(nsIAtom* aAttrName, bool* aDefer) +{ + // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc() + // here, override BindToTree for those classes and munge event + // listeners there? + nsIDocument* doc = OwnerDoc(); + + nsPIDOMWindowInner *window; + Element *root = doc->GetRootElement(); + if ((!root || root == this) && !mNodeInfo->Equals(nsGkAtoms::overlay) && + (window = doc->GetInnerWindow())) { + + nsCOMPtr<EventTarget> 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::listbox) && + !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. + * + * Controls (those that implement nsIDOMXULControlElement): + * *aTabIndex = -1 no tabindex Not focusable or tabbable + * *aTabIndex = -1 tabindex="-1" Not focusable or 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 + * Non-controls: + * *aTabIndex = -1 Not focusable or tabbable + * *aTabIndex >= 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::IsRemoteTarget(this)) + { + return false; + } +#endif + + nsCOMPtr<nsIDOMXULControlElement> xulControl = do_QueryObject(this); + 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) { + if (xulControl) { + if (HasAttr(kNameSpaceID_None, nsGkAtoms::tabindex)) { + // if either the aTabIndex argument or a specified tabindex is non-negative, + // the element becomes focusable. + int32_t tabIndex = 0; + xulControl->GetTabIndex(&tabIndex); + shouldFocus = *aTabIndex >= 0 || tabIndex >= 0; + *aTabIndex = tabIndex; + } 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 (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 <browser> + // cannot take this focus. + if (IsNonList(mNodeInfo)) + *aTabIndex = -1; + } + } else { + shouldFocus = *aTabIndex >= 0; + } + } + + return shouldFocus; +} + +bool +nsXULElement::PerformAccesskey(bool aKeyCausesActivation, + bool aIsTrustedEvent) +{ + nsCOMPtr<nsIContent> content(this); + + if (IsXULElement(nsGkAtoms::label)) { + nsCOMPtr<nsIDOMElement> element; + + nsAutoString control; + GetAttr(kNameSpaceID_None, nsGkAtoms::control, control); + if (!control.IsEmpty()) { + //XXXsmaug Should we use ShadowRoot::GetElementById in case + // content is in Shadow DOM? + nsCOMPtr<nsIDOMDocument> domDocument = + do_QueryInterface(content->GetUncomposedDoc()); + if (domDocument) + domDocument->GetElementById(control, getter_AddRefs(element)); + } + // here we'll either change |content| to the element referenced by + // |element|, or clear it. + content = do_QueryInterface(element); + + if (!content) { + return false; + } + } + + nsIFrame* frame = content->GetPrimaryFrame(); + if (!frame || !frame->IsVisibleConsideringAncestors()) { + return false; + } + + bool focused = false; + nsXULElement* elm = FromContent(content); + if (elm) { + // Define behavior for each type of XUL element. + if (!content->IsXULElement(nsGkAtoms::toolbarbutton)) { + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + nsCOMPtr<nsIDOMElement> elementToFocus; + // for radio buttons, focus the radiogroup instead + if (content->IsXULElement(nsGkAtoms::radio)) { + nsCOMPtr<nsIDOMXULSelectControlItemElement> controlItem(do_QueryInterface(content)); + if (controlItem) { + bool disabled; + controlItem->GetDisabled(&disabled); + if (!disabled) { + nsCOMPtr<nsIDOMXULSelectControlElement> selectControl; + controlItem->GetControl(getter_AddRefs(selectControl)); + elementToFocus = do_QueryInterface(selectControl); + } + } + } else { + elementToFocus = do_QueryInterface(content); + } + if (elementToFocus) { + fm->SetFocus(elementToFocus, nsIFocusManager::FLAG_BYKEY); + + // Return true if the element became focused. + nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow(); + focused = (window && window->GetFocusedNode()); + } + } + } + if (aKeyCausesActivation && + !content->IsAnyOfXULElements(nsGkAtoms::textbox, nsGkAtoms::menulist)) { + elm->ClickWithInputSource(nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD, aIsTrustedEvent); + } + } else { + return content->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent); + } + + return focused; +} + +//---------------------------------------------------------------------- + +void +nsXULElement::AddListenerFor(const nsAttrName& aName, + bool aCompileEventHandlers) +{ + // 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 (aName.IsAtom()) { + nsIAtom *attr = aName.Atom(); + MaybeAddPopupListener(attr); + if (aCompileEventHandlers && + nsContentUtils::IsEventAttributeName(attr, EventNameType_XUL)) { + nsAutoString value; + GetAttr(kNameSpaceID_None, attr, value); + SetEventHandler(attr, value, true); + } + } +} + +void +nsXULElement::MaybeAddPopupListener(nsIAtom* aLocalName) +{ + // If appropriate, add a popup listener. Called when we change the + // element's document, create a new element, change an attribute's + // value, etc. + if (aLocalName == nsGkAtoms::menu || + aLocalName == nsGkAtoms::contextmenu || + // XXXdwh popup and context are deprecated + aLocalName == nsGkAtoms::popup || + aLocalName == nsGkAtoms::context) { + AddPopupListener(aLocalName); + } +} + +//---------------------------------------------------------------------- +// +// 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); +} + +/** + * Returns true if the user-agent style sheet rules for this XUL element are + * in minimal-xul.css instead of xul.css. + */ +static inline bool XULElementsRulesInMinimalXULSheet(nsIAtom* aTag) +{ + return // scrollbar parts: + aTag == nsGkAtoms::scrollbar || + aTag == nsGkAtoms::scrollbarbutton || + aTag == nsGkAtoms::scrollcorner || + aTag == nsGkAtoms::slider || + aTag == nsGkAtoms::thumb || + aTag == nsGkAtoms::scale || + // other + aTag == nsGkAtoms::resizer || + aTag == nsGkAtoms::label || + aTag == nsGkAtoms::videocontrols; +} + +#ifdef DEBUG +/** + * Returns true if aElement is a XUL element created by the video controls + * binding. HTML <video> and <audio> bindings pull in this binding. This + * binding creates lots of different types of XUL elements. + */ +static inline bool +IsInVideoControls(nsXULElement* aElement) +{ + nsIContent* ancestor = aElement->GetParent(); + while (ancestor) { + if (ancestor->NodeInfo()->Equals(nsGkAtoms::videocontrols, kNameSpaceID_XUL)) { + return true; + } + ancestor = ancestor->GetParent(); + } + return false; +} + +/** + * Returns true if aElement is an element created by the <binding + * id="feedreaderUI"> binding or one of the bindings bound to such an element. + * element in one of the binding for such an element. Only + * subscribe.xhtml#feedSubscribeLine pulls in the feedreaderUI binding. This + * binding creates lots of different types of XUL elements. + */ +bool +IsInFeedSubscribeLine(nsXULElement* aElement) +{ + nsIContent* bindingParent = aElement->GetBindingParent(); + if (bindingParent) { + while (bindingParent->GetBindingParent()) { + bindingParent = bindingParent->GetBindingParent(); + } + nsIAtom* idAtom = bindingParent->GetID(); + if (idAtom && idAtom->Equals(NS_LITERAL_STRING("feedSubscribeLine"))) { + return true; + } + } + return false; +} +#endif + +class XULInContentErrorReporter : public Runnable +{ +public: + explicit XULInContentErrorReporter(nsIDocument* aDocument) : mDocument(aDocument) {} + + NS_IMETHOD Run() override + { + mDocument->WarnOnceAbout(nsIDocument::eImportXULIntoContent, false); + return NS_OK; + } + +private: + nsCOMPtr<nsIDocument> mDocument; +}; + +nsresult +nsXULElement::BindToTree(nsIDocument* aDocument, + nsIContent* aParent, + nsIContent* aBindingParent, + bool aCompileEventHandlers) +{ + if (!aBindingParent && + aDocument && + !aDocument->IsLoadedAsInteractiveData() && + !aDocument->AllowXULXBL() && + !aDocument->HasWarnedAbout(nsIDocument::eImportXULIntoContent)) { + nsContentUtils::AddScriptRunner(new XULInContentErrorReporter(aDocument)); + } + + nsresult rv = nsStyledElement::BindToTree(aDocument, aParent, + aBindingParent, + aCompileEventHandlers); + NS_ENSURE_SUCCESS(rv, rv); + + nsIDocument* doc = GetComposedDoc(); + if (doc && + !doc->LoadsFullXULStyleSheetUpFront() && + !doc->IsUnstyledDocument()) { + + // To save CPU cycles and memory, non-XUL documents only load the user + // agent style sheet rules for a minimal set of XUL elements such as + // 'scrollbar' that may be created implicitly for their content (those + // rules being in minimal-xul.css). This is where we make sure that all + // the other XUL UA style sheet rules (xul.css) have been loaded if the + // minimal set is not sufficient. + // + // We do this during binding, not element construction, because elements + // can be moved from the document that creates them to another document. + + if (!XULElementsRulesInMinimalXULSheet(NodeInfo()->NameAtom())) { + auto cache = nsLayoutStylesheetCache::For(doc->GetStyleBackendType()); + doc->EnsureOnDemandBuiltInUASheet(cache->XULSheet()); + // To keep memory usage down it is important that we try and avoid + // pulling xul.css into non-XUL documents. That should be very rare, and + // for HTML we currently should only pull it in if the document contains + // an <audio> or <video> element. This assertion is here to make sure + // that we don't fail to notice if a change to bindings causes us to + // start pulling in xul.css much more frequently. If this assertion + // fails then we need to figure out why, and how we can continue to avoid + // pulling in xul.css. + // Note that add-ons may introduce bindings that cause this assertion to + // fire. + NS_ASSERTION(IsInVideoControls(this) || + IsInFeedSubscribeLine(this) || + IsXULElement(nsGkAtoms::datetimebox), + "Unexpected XUL element in non-XUL doc"); + } + } + + if (aDocument) { + NS_ASSERTION(!nsContentUtils::IsSafeToRunScript(), + "Missing a script blocker!"); + // We're in a document now. Kick off the frame load. + LoadSrc(); + } + + return rv; +} + +void +nsXULElement::UnbindFromTree(bool aDeep, bool aNullParent) +{ + // 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.) + nsXULSlots* slots = static_cast<nsXULSlots*>(GetExistingDOMSlots()); + if (slots) { + NS_IF_RELEASE(slots->mControllers); + RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); + if (frameLoader) { + frameLoader->Destroy(); + } + slots->mFrameLoaderOrOpener = nullptr; + } + + nsStyledElement::UnbindFromTree(aDeep, aNullParent); +} + +void +nsXULElement::RemoveChildAt(uint32_t aIndex, bool aNotify) +{ + nsCOMPtr<nsIContent> oldKid = mAttrsAndChildren.GetSafeChildAt(aIndex); + if (!oldKid) { + return; + } + + // On the removal of a <treeitem>, <treechildren>, or <treecell> element, + // the possibility exists that some of the items in the removed subtree + // are selected (and therefore need to be deselected). We need to account for this. + nsCOMPtr<nsIDOMXULMultiSelectControlElement> controlElement; + nsCOMPtr<nsIListBoxObject> listBox; + bool fireSelectionHandler = false; + + // -1 = do nothing, -2 = null out current item + // anything else = index to re-set as current + int32_t newCurrentIndex = -1; + + if (oldKid->NodeInfo()->Equals(nsGkAtoms::listitem, kNameSpaceID_XUL)) { + // This is the nasty case. We have (potentially) a slew of selected items + // and cells going away. + // First, retrieve the tree. + // Check first whether this element IS the tree + controlElement = do_QueryObject(this); + + // If it's not, look at our parent + if (!controlElement) + GetParentTree(getter_AddRefs(controlElement)); + nsCOMPtr<nsIDOMXULElement> xulElement(do_QueryInterface(controlElement)); + + nsCOMPtr<nsIDOMElement> oldKidElem = do_QueryInterface(oldKid); + if (xulElement && oldKidElem) { + // Iterate over all of the items and find out if they are contained inside + // the removed subtree. + int32_t length; + controlElement->GetSelectedCount(&length); + for (int32_t i = 0; i < length; i++) { + nsCOMPtr<nsIDOMXULSelectControlItemElement> node; + controlElement->MultiGetSelectedItem(i, getter_AddRefs(node)); + // we need to QI here to do an XPCOM-correct pointercompare + nsCOMPtr<nsIDOMElement> selElem = do_QueryInterface(node); + if (selElem == oldKidElem && + NS_SUCCEEDED(controlElement->RemoveItemFromSelection(node))) { + length--; + i--; + fireSelectionHandler = true; + } + } + + nsCOMPtr<nsIDOMXULSelectControlItemElement> curItem; + controlElement->GetCurrentItem(getter_AddRefs(curItem)); + nsCOMPtr<nsIContent> curNode = do_QueryInterface(curItem); + if (curNode && nsContentUtils::ContentIsDescendantOf(curNode, oldKid)) { + // Current item going away + nsCOMPtr<nsIBoxObject> box; + xulElement->GetBoxObject(getter_AddRefs(box)); + listBox = do_QueryInterface(box); + if (listBox && oldKidElem) { + listBox->GetIndexOfItem(oldKidElem, &newCurrentIndex); + } + + // If any of this fails, we'll just set the current item to null + if (newCurrentIndex == -1) + newCurrentIndex = -2; + } + } + } + + nsStyledElement::RemoveChildAt(aIndex, aNotify); + + if (newCurrentIndex == -2) { + controlElement->SetCurrentItem(nullptr); + } else if (newCurrentIndex > -1) { + // Make sure the index is still valid + int32_t treeRows; + listBox->GetRowCount(&treeRows); + if (treeRows > 0) { + newCurrentIndex = std::min((treeRows - 1), newCurrentIndex); + nsCOMPtr<nsIDOMElement> newCurrentItem; + listBox->GetItemAtIndex(newCurrentIndex, getter_AddRefs(newCurrentItem)); + nsCOMPtr<nsIDOMXULSelectControlItemElement> xulCurItem = do_QueryInterface(newCurrentItem); + if (xulCurItem) + controlElement->SetCurrentItem(xulCurItem); + } else { + controlElement->SetCurrentItem(nullptr); + } + } + + nsIDocument* doc; + if (fireSelectionHandler && (doc = GetComposedDoc())) { + nsContentUtils::DispatchTrustedEvent(doc, + static_cast<nsIContent*>(this), + NS_LITERAL_STRING("select"), + false, + true); + } +} + +void +nsXULElement::UnregisterAccessKey(const nsAString& aOldValue) +{ + // If someone changes the accesskey, unregister the old one + // + nsIDocument* doc = GetComposedDoc(); + if (doc && !aOldValue.IsEmpty()) { + nsIPresShell *shell = doc->GetShell(); + + if (shell) { + nsIContent *content = this; + + // find out what type of content node this is + if (mNodeInfo->Equals(nsGkAtoms::label)) { + // For anonymous labels the unregistering must + // occur on the binding parent control. + // XXXldb: And what if the binding parent is null? + content = GetBindingParent(); + } + + if (content) { + shell->GetPresContext()->EventStateManager()-> + UnregisterAccessKey(content, aOldValue.First()); + } + } + } +} + +nsresult +nsXULElement::BeforeSetAttr(int32_t aNamespaceID, nsIAtom* aName, + nsAttrValueOrString* aValue, bool aNotify) +{ + if (aNamespaceID == kNameSpaceID_None && aName == nsGkAtoms::accesskey && + IsInUncomposedDoc()) { + nsAutoString oldValue; + if (GetAttr(aNamespaceID, aName, oldValue)) { + UnregisterAccessKey(oldValue); + } + } else if (aNamespaceID == kNameSpaceID_None && + (aName == nsGkAtoms::command || aName == nsGkAtoms::observes) && + IsInUncomposedDoc()) { +// XXX sXBL/XBL2 issue! Owner or current document? + nsAutoString oldValue; + GetAttr(kNameSpaceID_None, nsGkAtoms::observes, oldValue); + if (oldValue.IsEmpty()) { + GetAttr(kNameSpaceID_None, nsGkAtoms::command, oldValue); + } + + if (!oldValue.IsEmpty()) { + RemoveBroadcaster(oldValue); + } + } else if (aNamespaceID == kNameSpaceID_None && + aValue && + mNodeInfo->Equals(nsGkAtoms::window) && + aName == nsGkAtoms::chromemargin) { + nsAttrValue attrValue; + // Make sure the margin format is valid first + if (!attrValue.ParseIntMarginValue(aValue->String())) { + return NS_ERROR_INVALID_ARG; + } + } else if (aNamespaceID == kNameSpaceID_None && + aName == nsGkAtoms::usercontextid) { + nsAutoString oldValue; + bool hasAttribute = GetAttr(kNameSpaceID_None, nsGkAtoms::usercontextid, oldValue); + if (hasAttribute && (!aValue || !aValue->String().Equals(oldValue))) { + MOZ_ASSERT(false, "Changing usercontextid is not allowed."); + return NS_ERROR_INVALID_ARG; + } + } + + return nsStyledElement::BeforeSetAttr(aNamespaceID, aName, + aValue, aNotify); +} + +nsresult +nsXULElement::AfterSetAttr(int32_t aNamespaceID, nsIAtom* aName, + const nsAttrValue* aValue, bool aNotify) +{ + if (aNamespaceID == kNameSpaceID_None) { + if (aValue) { + // Add popup and event listeners. We can't call AddListenerFor since + // the attribute isn't set yet. + MaybeAddPopupListener(aName); + if (nsContentUtils::IsEventAttributeName(aName, EventNameType_XUL)) { + if (aValue->Type() == nsAttrValue::eString) { + SetEventHandler(aName, aValue->GetStringValue(), true); + } else { + nsAutoString body; + aValue->ToString(body); + SetEventHandler(aName, body, true); + } + } + + nsIDocument* document = GetUncomposedDoc(); + + // Hide chrome if needed + if (mNodeInfo->Equals(nsGkAtoms::window)) { + if (aName == nsGkAtoms::hidechrome) { + HideWindowChrome( + aValue->Equals(NS_LITERAL_STRING("true"), eCaseMatters)); + } else if (aName == nsGkAtoms::chromemargin) { + SetChromeMargins(aValue); + } else if (aName == nsGkAtoms::windowtype && + document && document->GetRootElement() == this) { + MaybeUpdatePrivateLifetime(); + } + } + // title, (in)activetitlebarcolor and drawintitlebar are settable on + // any root node (windows, dialogs, etc) + if (document && document->GetRootElement() == this) { + if (aName == nsGkAtoms::title) { + document->NotifyPossibleTitleChange(false); + } else if ((aName == nsGkAtoms::activetitlebarcolor || + aName == nsGkAtoms::inactivetitlebarcolor)) { + nscolor color = NS_RGBA(0, 0, 0, 0); + if (aValue->Type() == nsAttrValue::eColor) { + aValue->GetColorValue(color); + } else { + nsAutoString tmp; + nsAttrValue attrValue; + aValue->ToString(tmp); + attrValue.ParseColor(tmp); + attrValue.GetColorValue(color); + } + SetTitlebarColor(color, aName == nsGkAtoms::activetitlebarcolor); + } else if (aName == nsGkAtoms::drawintitlebar) { + SetDrawsInTitlebar( + aValue->Equals(NS_LITERAL_STRING("true"), eCaseMatters)); + } else if (aName == nsGkAtoms::drawtitle) { + SetDrawsTitle( + aValue->Equals(NS_LITERAL_STRING("true"), eCaseMatters)); + } else if (aName == nsGkAtoms::localedir) { + // if the localedir changed on the root element, reset the document direction + nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(document); + if (xuldoc) { + xuldoc->ResetDocumentDirection(); + } + } else if (aName == nsGkAtoms::lwtheme || + aName == nsGkAtoms::lwthemetextcolor) { + // if the lwtheme changed, make sure to reset the document lwtheme cache + nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(document); + if (xuldoc) { + xuldoc->ResetDocumentLWTheme(); + UpdateBrightTitlebarForeground(document); + } + } else if (aName == nsGkAtoms::brighttitlebarforeground) { + UpdateBrightTitlebarForeground(document); + } + } + + if (aName == nsGkAtoms::src && document) { + LoadSrc(); + } + } else { + if (mNodeInfo->Equals(nsGkAtoms::window)) { + if (aName == nsGkAtoms::hidechrome) { + HideWindowChrome(false); + } else if (aName == nsGkAtoms::chromemargin) { + ResetChromeMargins(); + } + } + + nsIDocument* doc = GetUncomposedDoc(); + if (doc && doc->GetRootElement() == this) { + if ((aName == nsGkAtoms::activetitlebarcolor || + aName == nsGkAtoms::inactivetitlebarcolor)) { + // Use 0, 0, 0, 0 as the "none" color. + SetTitlebarColor(NS_RGBA(0, 0, 0, 0), aName == nsGkAtoms::activetitlebarcolor); + } else if (aName == nsGkAtoms::localedir) { + // if the localedir changed on the root element, reset the document direction + nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(doc); + if (xuldoc) { + xuldoc->ResetDocumentDirection(); + } + } else if ((aName == nsGkAtoms::lwtheme || + aName == nsGkAtoms::lwthemetextcolor)) { + // if the lwtheme changed, make sure to restyle appropriately + nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(doc); + if (xuldoc) { + xuldoc->ResetDocumentLWTheme(); + UpdateBrightTitlebarForeground(doc); + } + } else if (aName == nsGkAtoms::brighttitlebarforeground) { + UpdateBrightTitlebarForeground(doc); + } else if (aName == nsGkAtoms::drawintitlebar) { + SetDrawsInTitlebar(false); + } else if (aName == nsGkAtoms::drawtitle) { + SetDrawsTitle(false); + } + } + } + + // 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, aNotify); +} + +bool +nsXULElement::ParseAttribute(int32_t aNamespaceID, + nsIAtom* aAttribute, + const nsAString& aValue, + nsAttrValue& aResult) +{ + // Parse into a nsAttrValue + if (!nsStyledElement::ParseAttribute(aNamespaceID, aAttribute, aValue, + aResult)) { + // Fall back to parsing as atom for short values + aResult.ParseStringOrAtom(aValue); + } + + return true; +} + +void +nsXULElement::RemoveBroadcaster(const nsAString & broadcasterId) +{ + nsCOMPtr<nsIDOMXULDocument> xuldoc = do_QueryInterface(OwnerDoc()); + if (xuldoc) { + nsCOMPtr<nsIDOMElement> broadcaster; + nsCOMPtr<nsIDOMDocument> domDoc (do_QueryInterface(xuldoc)); + domDoc->GetElementById(broadcasterId, getter_AddRefs(broadcaster)); + if (broadcaster) { + xuldoc->RemoveBroadcastListenerFor(broadcaster, this, + NS_LITERAL_STRING("*")); + } + } +} + +void +nsXULElement::DestroyContent() +{ + nsXULSlots* slots = static_cast<nsXULSlots*>(GetExistingDOMSlots()); + if (slots) { + NS_IF_RELEASE(slots->mControllers); + RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); + if (frameLoader) { + frameLoader->Destroy(); + } + slots->mFrameLoaderOrOpener = nullptr; + } + + nsStyledElement::DestroyContent(); +} + +#ifdef DEBUG +void +nsXULElement::List(FILE* out, int32_t aIndent) const +{ + nsCString prefix("XUL"); + if (HasSlots()) { + prefix.Append('*'); + } + prefix.Append(' '); + + nsStyledElement::List(out, aIndent, prefix); +} +#endif + +nsresult +nsXULElement::PreHandleEvent(EventChainPreVisitor& aVisitor) +{ + aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119 + if (IsRootOfNativeAnonymousSubtree() && + (IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::scrollcorner)) && + (aVisitor.mEvent->mMessage == eMouseClick || + aVisitor.mEvent->mMessage == eMouseDoubleClick || + aVisitor.mEvent->mMessage == eXULCommand || + aVisitor.mEvent->mMessage == eContextMenu || + aVisitor.mEvent->mMessage == eDragStart)) { + // Don't propagate these events from native anonymous scrollbar. + aVisitor.mCanHandle = true; + aVisitor.mParentTarget = nullptr; + return NS_OK; + } + if (aVisitor.mEvent->mMessage == eXULCommand && + aVisitor.mEvent->mClass == eInputEventClass && + aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) && + !IsXULElement(nsGkAtoms::command)) { + // Check that we really have an xul command event. That will be handled + // in a special way. + nsCOMPtr<nsIDOMXULCommandEvent> xulEvent = + do_QueryInterface(aVisitor.mDOMEvent); + // See if we have a command elt. If so, we execute on the command + // instead of on our content element. + nsAutoString command; + if (xulEvent && GetAttr(kNameSpaceID_None, nsGkAtoms::command, command) && + !command.IsEmpty()) { + // 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; + + // XXX sXBL/XBL2 issue! Owner or current document? + nsCOMPtr<nsIDOMDocument> domDoc(do_QueryInterface(GetUncomposedDoc())); + NS_ENSURE_STATE(domDoc); + nsCOMPtr<nsIDOMElement> commandElt; + domDoc->GetElementById(command, getter_AddRefs(commandElt)); + nsCOMPtr<nsIContent> commandContent(do_QueryInterface(commandElt)); + if (commandContent) { + // 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. + nsCOMPtr<nsIDOMEvent> domEvent = aVisitor.mDOMEvent; + while (domEvent) { + Event* event = domEvent->InternalDOMEvent(); + NS_ENSURE_STATE(!SameCOMIdentity(event->GetOriginalTarget(), + commandContent)); + nsCOMPtr<nsIDOMXULCommandEvent> commandEvent = + do_QueryInterface(domEvent); + if (commandEvent) { + commandEvent->GetSourceEvent(getter_AddRefs(domEvent)); + } else { + domEvent = nullptr; + } + } + + WidgetInputEvent* orig = aVisitor.mEvent->AsInputEvent(); + nsContentUtils::DispatchXULCommand( + commandContent, + aVisitor.mEvent->IsTrusted(), + aVisitor.mDOMEvent, + nullptr, + orig->IsControl(), + orig->IsAlt(), + orig->IsShift(), + orig->IsMeta()); + } else { + NS_WARNING("A XUL element is attached to a command that doesn't exist!\n"); + } + return NS_OK; + } + } + + return nsStyledElement::PreHandleEvent(aVisitor); +} + +// XXX This _should_ be an implementation method, _not_ publicly exposed :-( +NS_IMETHODIMP +nsXULElement::GetResource(nsIRDFResource** aResource) +{ + ErrorResult rv; + *aResource = GetResource(rv).take(); + return rv.StealNSResult(); +} + +already_AddRefed<nsIRDFResource> +nsXULElement::GetResource(ErrorResult& rv) +{ + nsAutoString id; + GetAttr(kNameSpaceID_None, nsGkAtoms::ref, id); + if (id.IsEmpty()) { + GetAttr(kNameSpaceID_None, nsGkAtoms::id, id); + } + + if (id.IsEmpty()) { + return nullptr; + } + + nsCOMPtr<nsIRDFResource> resource; + rv = nsXULContentUtils::RDFService()-> + GetUnicodeResource(id, getter_AddRefs(resource)); + return resource.forget(); +} + +NS_IMETHODIMP +nsXULElement::GetDatabase(nsIRDFCompositeDataSource** aDatabase) +{ + *aDatabase = GetDatabase().take(); + return NS_OK; +} + +already_AddRefed<nsIRDFCompositeDataSource> +nsXULElement::GetDatabase() +{ + nsCOMPtr<nsIXULTemplateBuilder> builder = GetBuilder(); + if (!builder) { + return nullptr; + } + + nsCOMPtr<nsIRDFCompositeDataSource> database; + builder->GetDatabase(getter_AddRefs(database)); + return database.forget(); +} + + +NS_IMETHODIMP +nsXULElement::GetBuilder(nsIXULTemplateBuilder** aBuilder) +{ + *aBuilder = GetBuilder().take(); + return NS_OK; +} + +already_AddRefed<nsIXULTemplateBuilder> +nsXULElement::GetBuilder() +{ + // XXX sXBL/XBL2 issue! Owner or current document? + nsCOMPtr<nsIXULDocument> xuldoc = do_QueryInterface(GetUncomposedDoc()); + if (!xuldoc) { + return nullptr; + } + + nsCOMPtr<nsIXULTemplateBuilder> builder; + xuldoc->GetTemplateBuilderFor(this, getter_AddRefs(builder)); + return builder.forget(); +} + +//---------------------------------------------------------------------- +// Implementation methods + +NS_IMETHODIMP +nsXULElement::WalkContentStyleRules(nsRuleWalker* aRuleWalker) +{ + return NS_OK; +} + +nsChangeHint +nsXULElement::GetAttributeChangeHint(const nsIAtom* aAttribute, + int32_t aModType) const +{ + nsChangeHint retval(nsChangeHint(0)); + + if (aAttribute == nsGkAtoms::value && + (aModType == nsIDOMMutationEvent::REMOVAL || + aModType == nsIDOMMutationEvent::ADDITION)) { + if (IsAnyOfXULElements(nsGkAtoms::label, nsGkAtoms::description)) + // Label and description dynamically morph between a normal + // block and a cropping single-line XUL text frame. If the + // value attribute is being added or removed, then we need to + // return a hint of frame change. (See bugzilla bug 95475 for + // details.) + retval = nsChangeHint_ReconstructFrame; + } else { + // if left or top changes we reflow. This will happen in xul + // containers that manage positioned children such as a stack. + if (nsGkAtoms::left == aAttribute || nsGkAtoms::top == aAttribute || + nsGkAtoms::right == aAttribute || nsGkAtoms::bottom == aAttribute || + nsGkAtoms::start == aAttribute || nsGkAtoms::end == aAttribute) + retval = NS_STYLE_HINT_REFLOW; + } + + return retval; +} + +NS_IMETHODIMP_(bool) +nsXULElement::IsAttributeMapped(const nsIAtom* aAttribute) const +{ + return false; +} + +// Controllers Methods +NS_IMETHODIMP +nsXULElement::GetControllers(nsIControllers** aResult) +{ + ErrorResult rv; + NS_IF_ADDREF(*aResult = GetControllers(rv)); + return rv.StealNSResult(); +} + +nsIControllers* +nsXULElement::GetControllers(ErrorResult& rv) +{ + if (! Controllers()) { + nsDOMSlots* slots = DOMSlots(); + + rv = NS_NewXULControllers(nullptr, NS_GET_IID(nsIControllers), + reinterpret_cast<void**>(&slots->mControllers)); + + NS_ASSERTION(!rv.Failed(), "unable to create a controllers"); + if (rv.Failed()) { + return nullptr; + } + } + + return Controllers(); +} + +NS_IMETHODIMP +nsXULElement::GetBoxObject(nsIBoxObject** aResult) +{ + ErrorResult rv; + *aResult = GetBoxObject(rv).take(); + return rv.StealNSResult(); +} + +already_AddRefed<BoxObject> +nsXULElement::GetBoxObject(ErrorResult& rv) +{ + // XXX sXBL/XBL2 issue! Owner or current document? + return OwnerDoc()->GetBoxObjectFor(this, rv); +} + +// Methods for setting/getting attributes from nsIDOMXULElement +#define NS_IMPL_XUL_STRING_ATTR(_method, _atom) \ + NS_IMETHODIMP \ + nsXULElement::Get##_method(nsAString& aReturn) \ + { \ + GetAttr(kNameSpaceID_None, nsGkAtoms::_atom, aReturn); \ + return NS_OK; \ + } \ + NS_IMETHODIMP \ + nsXULElement::Set##_method(const nsAString& aValue) \ + { \ + return SetAttr(kNameSpaceID_None, nsGkAtoms::_atom, aValue, \ + true); \ + } + +#define NS_IMPL_XUL_BOOL_ATTR(_method, _atom) \ + NS_IMETHODIMP \ + nsXULElement::Get##_method(bool* aResult) \ + { \ + *aResult = _method(); \ + return NS_OK; \ + } \ + NS_IMETHODIMP \ + nsXULElement::Set##_method(bool aValue) \ + { \ + SetXULBoolAttr(nsGkAtoms::_atom, aValue); \ + return NS_OK; \ + } + + +NS_IMPL_XUL_STRING_ATTR(Align, align) +NS_IMPL_XUL_STRING_ATTR(Dir, dir) +NS_IMPL_XUL_STRING_ATTR(Flex, flex) +NS_IMPL_XUL_STRING_ATTR(FlexGroup, flexgroup) +NS_IMPL_XUL_STRING_ATTR(Ordinal, ordinal) +NS_IMPL_XUL_STRING_ATTR(Orient, orient) +NS_IMPL_XUL_STRING_ATTR(Pack, pack) +NS_IMPL_XUL_BOOL_ATTR(Hidden, hidden) +NS_IMPL_XUL_BOOL_ATTR(Collapsed, collapsed) +NS_IMPL_XUL_BOOL_ATTR(AllowEvents, allowevents) +NS_IMPL_XUL_STRING_ATTR(Observes, observes) +NS_IMPL_XUL_STRING_ATTR(Menu, menu) +NS_IMPL_XUL_STRING_ATTR(ContextMenu, contextmenu) +NS_IMPL_XUL_STRING_ATTR(Tooltip, tooltip) +NS_IMPL_XUL_STRING_ATTR(Width, width) +NS_IMPL_XUL_STRING_ATTR(Height, height) +NS_IMPL_XUL_STRING_ATTR(MinWidth, minwidth) +NS_IMPL_XUL_STRING_ATTR(MinHeight, minheight) +NS_IMPL_XUL_STRING_ATTR(MaxWidth, maxwidth) +NS_IMPL_XUL_STRING_ATTR(MaxHeight, maxheight) +NS_IMPL_XUL_STRING_ATTR(Persist, persist) +NS_IMPL_XUL_STRING_ATTR(Left, left) +NS_IMPL_XUL_STRING_ATTR(Top, top) +NS_IMPL_XUL_STRING_ATTR(Datasources, datasources) +NS_IMPL_XUL_STRING_ATTR(Ref, ref) +NS_IMPL_XUL_STRING_ATTR(TooltipText, tooltiptext) +NS_IMPL_XUL_STRING_ATTR(StatusText, statustext) + +nsresult +nsXULElement::LoadSrc() +{ + // Allow frame loader only on objects for which a container box object + // can be obtained. + if (!IsAnyOfXULElements(nsGkAtoms::browser, nsGkAtoms::editor, + nsGkAtoms::iframe)) { + return NS_OK; + } + if (!IsInUncomposedDoc() || + !OwnerDoc()->GetRootElement() || + OwnerDoc()->GetRootElement()-> + NodeInfo()->Equals(nsGkAtoms::overlay, kNameSpaceID_XUL)) { + return NS_OK; + } + RefPtr<nsFrameLoader> frameLoader = GetFrameLoader(); + if (!frameLoader) { + // Check if we have an opener we need to be setting + nsXULSlots* slots = static_cast<nsXULSlots*>(Slots()); + nsCOMPtr<nsPIDOMWindowOuter> opener = do_QueryInterface(slots->mFrameLoaderOrOpener); + if (!opener) { + // If we are a content-primary xul-browser, we want to take the opener property! + nsCOMPtr<nsIDOMChromeWindow> chromeWindow = do_QueryInterface(OwnerDoc()->GetWindow()); + if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::type, + NS_LITERAL_STRING("content-primary"), eIgnoreCase) && + chromeWindow) { + nsCOMPtr<mozIDOMWindowProxy> wp; + chromeWindow->TakeOpenerForInitialContentBrowser(getter_AddRefs(wp)); + opener = nsPIDOMWindowOuter::From(wp); + } + } + + // false as the last parameter so that xul:iframe/browser/editor + // session history handling works like dynamic html:iframes. + // Usually xul elements are used in chrome, which doesn't have + // session history at all. + frameLoader = nsFrameLoader::Create(this, opener, false); + slots->mFrameLoaderOrOpener = static_cast<nsIFrameLoader*>(frameLoader); + NS_ENSURE_TRUE(frameLoader, NS_OK); + + (new AsyncEventDispatcher(this, + NS_LITERAL_STRING("XULFrameLoaderCreated"), + /* aBubbles */ true))->RunDOMEventWhenSafe(); + + if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::prerendered, + NS_LITERAL_STRING("true"), eIgnoreCase)) { + nsresult rv = frameLoader->SetIsPrerendered(); + NS_ENSURE_SUCCESS(rv,rv); + } + } + + return frameLoader->LoadFrame(); +} + +nsresult +nsXULElement::GetFrameLoaderXPCOM(nsIFrameLoader **aFrameLoader) +{ + *aFrameLoader = GetFrameLoader().take(); + return NS_OK; +} + +already_AddRefed<nsFrameLoader> +nsXULElement::GetFrameLoader() +{ + nsXULSlots* slots = static_cast<nsXULSlots*>(GetExistingSlots()); + if (!slots) + return nullptr; + + nsCOMPtr<nsIFrameLoader> loader = do_QueryInterface(slots->mFrameLoaderOrOpener); + return already_AddRefed<nsFrameLoader>(static_cast<nsFrameLoader*>(loader.forget().take())); +} + +nsresult +nsXULElement::GetParentApplication(mozIApplication** aApplication) +{ + if (!aApplication) { + return NS_ERROR_FAILURE; + } + + *aApplication = nullptr; + return NS_OK; +} + +void +nsXULElement::PresetOpenerWindow(mozIDOMWindowProxy* aWindow, ErrorResult& aRv) +{ + nsXULSlots* slots = static_cast<nsXULSlots*>(Slots()); + MOZ_ASSERT(!slots->mFrameLoaderOrOpener, "A frameLoader or opener is present when calling PresetOpenerWindow"); + + slots->mFrameLoaderOrOpener = aWindow; +} + +nsresult +nsXULElement::SetIsPrerendered() +{ + return SetAttr(kNameSpaceID_None, nsGkAtoms::prerendered, nullptr, + NS_LITERAL_STRING("true"), true); +} + +void +nsXULElement::InternalSetFrameLoader(nsIFrameLoader* aNewFrameLoader) +{ + nsXULSlots* slots = static_cast<nsXULSlots*>(GetExistingDOMSlots()); + MOZ_ASSERT(slots); + + slots->mFrameLoaderOrOpener = aNewFrameLoader; +} + +void +nsXULElement::SwapFrameLoaders(HTMLIFrameElement& aOtherLoaderOwner, + ErrorResult& rv) +{ + if (!GetExistingDOMSlots()) { + rv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return; + } + + nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(static_cast<nsIDOMXULElement*>(this)); + aOtherLoaderOwner.SwapFrameLoaders(flo, rv); +} + +void +nsXULElement::SwapFrameLoaders(nsXULElement& aOtherLoaderOwner, + ErrorResult& rv) +{ + if (&aOtherLoaderOwner == this) { + // nothing to do + return; + } + + if (!GetExistingDOMSlots()) { + rv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return; + } + + nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(static_cast<nsIDOMXULElement*>(this)); + aOtherLoaderOwner.SwapFrameLoaders(flo, rv); +} + +void +nsXULElement::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoaderOwner, + mozilla::ErrorResult& rv) +{ + if (!GetExistingDOMSlots()) { + rv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return; + } + + RefPtr<nsFrameLoader> loader = GetFrameLoader(); + RefPtr<nsFrameLoader> otherLoader = aOtherLoaderOwner->GetFrameLoader(); + if (!loader || !otherLoader) { + rv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return; + } + + nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(static_cast<nsIDOMXULElement*>(this)); + rv = loader->SwapWithOtherLoader(otherLoader, flo, aOtherLoaderOwner); +} + +NS_IMETHODIMP +nsXULElement::GetParentTree(nsIDOMXULMultiSelectControlElement** aTreeElement) +{ + for (nsIContent* current = GetParent(); current; + current = current->GetParent()) { + if (current->NodeInfo()->Equals(nsGkAtoms::listbox, + kNameSpaceID_XUL)) { + CallQueryInterface(current, aTreeElement); + // XXX returning NS_OK because that's what the code used to do; + // is that the right thing, though? + + return NS_OK; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXULElement::Focus() +{ + ErrorResult rv; + Focus(rv); + return rv.StealNSResult(); +} + +NS_IMETHODIMP +nsXULElement::Blur() +{ + ErrorResult rv; + Blur(rv); + return rv.StealNSResult(); +} + +NS_IMETHODIMP +nsXULElement::Click() +{ + return ClickWithInputSource(nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN, /* aIsTrusted = */ true); +} + +void +nsXULElement::Click(ErrorResult& rv) +{ + rv = ClickWithInputSource(nsIDOMMouseEvent::MOZ_SOURCE_UNKNOWN, nsContentUtils::IsCallerChrome()); +} + +nsresult +nsXULElement::ClickWithInputSource(uint16_t aInputSource, bool aIsTrustedEvent) +{ + if (BoolAttrIsTrue(nsGkAtoms::disabled)) + return NS_OK; + + nsCOMPtr<nsIDocument> doc = GetComposedDoc(); // Strong just in case + if (doc) { + nsCOMPtr<nsIPresShell> shell = doc->GetShell(); + if (shell) { + // strong ref to PresContext so events don't destroy it + RefPtr<nsPresContext> context = shell->GetPresContext(); + + WidgetMouseEvent eventDown(aIsTrustedEvent, eMouseDown, + nullptr, WidgetMouseEvent::eReal); + WidgetMouseEvent eventUp(aIsTrustedEvent, eMouseUp, + nullptr, WidgetMouseEvent::eReal); + WidgetMouseEvent eventClick(aIsTrustedEvent, eMouseClick, nullptr, + WidgetMouseEvent::eReal); + eventDown.inputSource = eventUp.inputSource = eventClick.inputSource + = aInputSource; + + // send mouse down + nsEventStatus status = nsEventStatus_eIgnore; + EventDispatcher::Dispatch(static_cast<nsIContent*>(this), + context, &eventDown, nullptr, &status); + + // send mouse up + status = nsEventStatus_eIgnore; // reset status + EventDispatcher::Dispatch(static_cast<nsIContent*>(this), + context, &eventUp, nullptr, &status); + + // send mouse click + status = nsEventStatus_eIgnore; // reset status + EventDispatcher::Dispatch(static_cast<nsIContent*>(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 NS_OK; + } + } + } + + // oncommand is fired when an element is clicked... + return DoCommand(); +} + +NS_IMETHODIMP +nsXULElement::DoCommand() +{ + nsCOMPtr<nsIDocument> doc = GetComposedDoc(); // strong just in case + if (doc) { + nsContentUtils::DispatchXULCommand(this, true); + } + + return NS_OK; +} + +nsIContent * +nsXULElement::GetBindingParent() const +{ + return mBindingParent; +} + +bool +nsXULElement::IsNodeOfType(uint32_t aFlags) const +{ + return !(aFlags & ~eCONTENT); +} + +nsresult +nsXULElement::AddPopupListener(nsIAtom* 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<nsIDOMEventListener> listener = + new nsXULPopupListener(this, isContext); + + // Add the popup as a listener on this element. + EventListenerManager* manager = GetOrCreateListenerManager(); + SetFlags(listenerFlag); + + if (isContext) { + manager->AddEventListenerByType(listener, + NS_LITERAL_STRING("contextmenu"), + TrustedEventsAtSystemGroupBubble()); + } else { + manager->AddEventListenerByType(listener, + NS_LITERAL_STRING("mousedown"), + TrustedEventsAtSystemGroupBubble()); + } + return NS_OK; +} + +EventStates +nsXULElement::IntrinsicState() const +{ + EventStates state = nsStyledElement::IntrinsicState(); + + if (IsReadWriteTextElement()) { + state |= NS_EVENT_STATE_MOZ_READWRITE; + state &= ~NS_EVENT_STATE_MOZ_READONLY; + } + + return state; +} + +//---------------------------------------------------------------------- + +nsresult +nsXULElement::MakeHeavyweight(nsXULPrototypeElement* aPrototype) +{ + if (!aPrototype) { + return NS_OK; + } + + uint32_t i; + nsresult rv; + for (i = 0; i < aPrototype->mNumAttributes; ++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<css::Declaration> + declClone = new css::Declaration(*decl->AsGecko()); + + nsString stringValue; + protoattr->mValue.ToString(stringValue); + + attrValue.SetTo(declClone.forget(), &stringValue); + } else { + attrValue.SetTo(protoattr->mValue); + } + + // XXX we might wanna have a SetAndTakeAttr that takes an nsAttrName + if (protoattr->mName.IsAtom()) { + rv = mAttrsAndChildren.SetAndSwapAttr(protoattr->mName.Atom(), attrValue); + } else { + rv = mAttrsAndChildren.SetAndSwapAttr(protoattr->mName.NodeInfo(), + attrValue); + } + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +nsresult +nsXULElement::HideWindowChrome(bool aShouldHide) +{ + nsIDocument* doc = GetUncomposedDoc(); + if (!doc || doc->GetRootElement() != this) + return NS_ERROR_UNEXPECTED; + + // only top level chrome documents can hide the window chrome + if (!doc->IsRootDisplayDocument()) + return NS_OK; + + nsIPresShell *shell = doc->GetShell(); + + if (shell) { + nsIFrame* frame = GetPrimaryFrame(); + + nsPresContext *presContext = shell->GetPresContext(); + + if (frame && presContext && presContext->IsChrome()) { + nsView* view = frame->GetClosestView(); + + if (view) { + nsIWidget* w = view->GetWidget(); + NS_ENSURE_STATE(w); + w->HideWindowChrome(aShouldHide); + } + } + } + + return NS_OK; +} + +nsIWidget* +nsXULElement::GetWindowWidget() +{ + nsIDocument* doc = GetComposedDoc(); + + // only top level chrome documents can set the titlebar color + if (doc && doc->IsRootDisplayDocument()) { + nsCOMPtr<nsISupports> container = doc->GetContainer(); + nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container); + if (baseWindow) { + nsCOMPtr<nsIWidget> mainWidget; + baseWindow->GetMainWidget(getter_AddRefs(mainWidget)); + return mainWidget; + } + } + return nullptr; +} + +void +nsXULElement::SetTitlebarColor(nscolor aColor, bool aActive) +{ + nsIWidget* mainWidget = GetWindowWidget(); + if (mainWidget) { + mainWidget->SetWindowTitlebarColor(aColor, aActive); + } +} + +class SetDrawInTitleBarEvent : public Runnable +{ +public: + SetDrawInTitleBarEvent(nsIWidget* aWidget, bool aState) + : mWidget(aWidget) + , mState(aState) + {} + + NS_IMETHOD Run() override { + NS_ASSERTION(mWidget, "You shouldn't call this runnable with a null widget!"); + + mWidget->SetDrawsInTitlebar(mState); + return NS_OK; + } + +private: + nsCOMPtr<nsIWidget> mWidget; + bool mState; +}; + +void +nsXULElement::SetDrawsInTitlebar(bool aState) +{ + nsIWidget* mainWidget = GetWindowWidget(); + if (mainWidget) { + nsContentUtils::AddScriptRunner(new SetDrawInTitleBarEvent(mainWidget, aState)); + } +} + +void +nsXULElement::SetDrawsTitle(bool aState) +{ + nsIWidget* mainWidget = GetWindowWidget(); + if (mainWidget) { + // We can do this synchronously because SetDrawsTitle doesn't have any + // synchronous effects apart from a harmless invalidation. + mainWidget->SetDrawsTitle(aState); + } +} + +void +nsXULElement::UpdateBrightTitlebarForeground(nsIDocument* aDoc) +{ + nsIWidget* mainWidget = GetWindowWidget(); + if (mainWidget) { + // We can do this synchronously because SetBrightTitlebarForeground doesn't have any + // synchronous effects apart from a harmless invalidation. + mainWidget->SetUseBrightTitlebarForeground( + aDoc->GetDocumentLWTheme() == nsIDocument::Doc_Theme_Bright || + aDoc->GetRootElement()->AttrValueIs(kNameSpaceID_None, + nsGkAtoms::brighttitlebarforeground, + NS_LITERAL_STRING("true"), + eCaseMatters)); + } +} + +class MarginSetter : public Runnable +{ +public: + explicit MarginSetter(nsIWidget* aWidget) : + mWidget(aWidget), mMargin(-1, -1, -1, -1) + {} + MarginSetter(nsIWidget *aWidget, const LayoutDeviceIntMargin& aMargin) : + mWidget(aWidget), mMargin(aMargin) + {} + + NS_IMETHOD Run() override + { + // SetNonClientMargins can dispatch native events, hence doing + // it off a script runner. + mWidget->SetNonClientMargins(mMargin); + return NS_OK; + } + +private: + nsCOMPtr<nsIWidget> mWidget; + LayoutDeviceIntMargin mMargin; +}; + +void +nsXULElement::SetChromeMargins(const nsAttrValue* aValue) +{ + if (!aValue) + return; + + nsIWidget* mainWidget = GetWindowWidget(); + if (!mainWidget) + return; + + // top, right, bottom, left - see nsAttrValue + nsIntMargin margins; + bool gotMargins = false; + + if (aValue->Type() == nsAttrValue::eIntMarginValue) { + gotMargins = aValue->GetIntMarginValue(margins); + } else { + nsAutoString tmp; + aValue->ToString(tmp); + gotMargins = nsContentUtils::ParseIntMarginValue(tmp, margins); + } + if (gotMargins) { + nsContentUtils::AddScriptRunner( + new MarginSetter( + mainWidget, LayoutDeviceIntMargin::FromUnknownMargin(margins))); + } +} + +void +nsXULElement::ResetChromeMargins() +{ + nsIWidget* mainWidget = GetWindowWidget(); + if (!mainWidget) + return; + // See nsIWidget + nsContentUtils::AddScriptRunner(new MarginSetter(mainWidget)); +} + +bool +nsXULElement::BoolAttrIsTrue(nsIAtom* aName) const +{ + const nsAttrValue* attr = + GetAttrInfo(kNameSpaceID_None, aName).mValue; + + return attr && attr->Type() == nsAttrValue::eAtom && + attr->GetAtomValue() == nsGkAtoms::_true; +} + +void +nsXULElement::RecompileScriptEventListeners() +{ + int32_t i, count = mAttrsAndChildren.AttrCount(); + for (i = 0; i < count; ++i) { + const nsAttrName *name = mAttrsAndChildren.AttrNameAt(i); + + // Eventlistenener-attributes are always in the null namespace + if (!name->IsAtom()) { + continue; + } + + nsIAtom *attr = name->Atom(); + if (!nsContentUtils::IsEventAttributeName(attr, EventNameType_XUL)) { + continue; + } + + nsAutoString value; + GetAttr(kNameSpaceID_None, attr, value); + SetEventHandler(attr, value, true); + } +} + +bool +nsXULElement::IsEventAttributeName(nsIAtom *aName) +{ + return nsContentUtils::IsEventAttributeName(aName, EventNameType_XUL); +} + +JSObject* +nsXULElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) +{ + return dom::XULElementBinding::Wrap(aCx, this, aGivenProto); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULPrototypeNode) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULPrototypeNode) + if (tmp->mType == nsXULPrototypeNode::eType_Element) { + static_cast<nsXULPrototypeElement*>(tmp)->Unlink(); + } else if (tmp->mType == nsXULPrototypeNode::eType_Script) { + static_cast<nsXULPrototypeScript*>(tmp)->UnlinkJSObjects(); + } +NS_IMPL_CYCLE_COLLECTION_UNLINK_END +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeNode) + if (tmp->mType == nsXULPrototypeNode::eType_Element) { + nsXULPrototypeElement *elem = + static_cast<nsXULPrototypeElement*>(tmp); + NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mNodeInfo"); + cb.NoteNativeChild(elem->mNodeInfo, + NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo)); + uint32_t i; + for (i = 0; i < elem->mNumAttributes; ++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_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXULPrototypeNode) + if (tmp->mType == nsXULPrototypeNode::eType_Script) { + nsXULPrototypeScript *script = + static_cast<nsXULPrototypeScript*>(tmp); + script->Trace(aCallbacks, aClosure); + } +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsXULPrototypeNode, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsXULPrototypeNode, Release) + +//---------------------------------------------------------------------- +// +// nsXULPrototypeAttribute +// + +nsXULPrototypeAttribute::~nsXULPrototypeAttribute() +{ + MOZ_COUNT_DTOR(nsXULPrototypeAttribute); +} + + +//---------------------------------------------------------------------- +// +// nsXULPrototypeElement +// + +nsresult +nsXULPrototypeElement::Serialize(nsIObjectOutputStream* aStream, + nsXULPrototypeDocument* aProtoDoc, + const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *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(mNumAttributes); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + nsAutoString attributeValue; + uint32_t i; + for (i = 0; i < mNumAttributes; ++i) { + RefPtr<mozilla::dom::NodeInfo> ni; + if (mAttributes[i].mName.IsAtom()) { + ni = mNodeInfo->NodeInfoManager()-> + GetNodeInfo(mAttributes[i].mName.Atom(), nullptr, + kNameSpaceID_None, + nsIDOMNode::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<nsXULPrototypeScript*>(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->HasScriptObject()) { + // 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<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) +{ + NS_PRECONDITION(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; + mNumAttributes = int32_t(number); + + if (mNumAttributes > 0) { + mAttributes = new (fallible) nsXULPrototypeAttribute[mNumAttributes]; + if (!mAttributes) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsAutoString attributeValue; + for (uint32_t i = 0; i < mNumAttributes; ++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<nsXULPrototypeNode> 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<nsXULPrototypeScript> script = new nsXULPrototypeScript(0, 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<nsISupports> 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 = script.forget(); + 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) +{ + NS_PRECONDITION(aPos < mNumAttributes, "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)) { + 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::_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 + + nsCSSParser parser; + + // 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. + RefPtr<css::Declaration> declaration = + parser.ParseStyleAttribute(aValue, aDocumentURI, aDocumentURI, + // This is basically duplicating what + // nsINode::NodePrincipal() does + mNodeInfo->NodeInfoManager()-> + DocumentPrincipal()); + if (declaration) { + mAttributes[aPos].mValue.SetTo(declaration.forget(), &aValue); + + return NS_OK; + } + // Don't abort if parsing failed, it could just be malformed css. + } + + mAttributes[aPos].mValue.ParseStringOrAtom(aValue); + + return NS_OK; +} + +void +nsXULPrototypeElement::Unlink() +{ + mNumAttributes = 0; + delete[] mAttributes; + mAttributes = nullptr; + mChildren.Clear(); +} + +void +nsXULPrototypeElement::TraceAllScripts(JSTracer* aTrc) +{ + for (uint32_t i = 0; i < mChildren.Length(); ++i) { + nsXULPrototypeNode* child = mChildren[i]; + if (child->mType == nsXULPrototypeNode::eType_Element) { + static_cast<nsXULPrototypeElement*>(child)->TraceAllScripts(aTrc); + } else if (child->mType == nsXULPrototypeNode::eType_Script) { + static_cast<nsXULPrototypeScript*>(child)->TraceScriptObject(aTrc); + } + } +} + +//---------------------------------------------------------------------- +// +// nsXULPrototypeScript +// + +nsXULPrototypeScript::nsXULPrototypeScript(uint32_t aLineNo, uint32_t aVersion) + : nsXULPrototypeNode(eType_Script), + mLineNo(aLineNo), + mSrcLoading(false), + mOutOfLine(true), + mSrcLoadWaiters(nullptr), + mLangVersion(aVersion), + mScriptObject(nullptr) +{ +} + + +nsXULPrototypeScript::~nsXULPrototypeScript() +{ + UnlinkJSObjects(); +} + +nsresult +nsXULPrototypeScript::Serialize(nsIObjectOutputStream* aStream, + nsXULPrototypeDocument* aProtoDoc, + const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) +{ + NS_ENSURE_TRUE(aProtoDoc, NS_ERROR_UNEXPECTED); + + AutoJSAPI jsapi; + if (!jsapi.Init(xpc::CompilationScope())) { + return NS_ERROR_UNEXPECTED; + } + + NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || + !mScriptObject, + "script source still loading when serializing?!"); + if (!mScriptObject) + return NS_ERROR_FAILURE; + + // Write basic prototype data + nsresult rv; + rv = aStream->Write32(mLineNo); + if (NS_FAILED(rv)) return rv; + rv = aStream->Write32(mLangVersion); + if (NS_FAILED(rv)) return rv; + + JSContext* cx = jsapi.cx(); + JS::Rooted<JSScript*> script(cx, mScriptObject); + MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx)); + return nsContentUtils::XPConnect()->WriteScript(aStream, cx, script); +} + +nsresult +nsXULPrototypeScript::SerializeOutOfLine(nsIObjectOutputStream* aStream, + nsXULPrototypeDocument* aProtoDoc) +{ + nsresult rv = NS_ERROR_NOT_IMPLEMENTED; + + bool isChrome = false; + if (NS_FAILED(mSrcURI->SchemeIs("chrome", &isChrome)) || !isChrome) + // Don't cache scripts that don't come from chrome uris. + return rv; + + 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->HasData(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<nsIObjectOutputStream> oos; + rv = cache->GetOutputStream(mSrcURI, getter_AddRefs(oos)); + NS_ENSURE_SUCCESS(rv, rv); + + nsresult tmp = Serialize(oos, aProtoDoc, nullptr); + if (NS_FAILED(tmp)) { + rv = tmp; + } + tmp = cache->FinishOutputStream(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<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) +{ + nsresult rv; + NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || + !mScriptObject, + "prototype script not well-initialized when deserializing?!"); + + // Read basic prototype data + rv = aStream->Read32(&mLineNo); + if (NS_FAILED(rv)) return rv; + rv = aStream->Read32(&mLangVersion); + if (NS_FAILED(rv)) return rv; + + AutoJSAPI jsapi; + if (!jsapi.Init(xpc::CompilationScope())) { + return NS_ERROR_UNEXPECTED; + } + JSContext* cx = jsapi.cx(); + + JS::Rooted<JSScript*> newScriptObject(cx); + rv = nsContentUtils::XPConnect()->ReadScript(aStream, cx, + newScriptObject.address()); + NS_ENSURE_SUCCESS(rv, rv); + Set(newScriptObject); + 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<nsIObjectInputStream> 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 XULDocument::LoadScript + // checks the XUL script cache too, in order to handle the + // serialization case. + // + // We need do this only for <script src='strres.js'> and the + // like, i.e., out-of-line scripts that are included by several + // different XUL documents stored in the cache file. + useXULCache = cache->IsEnabled(); + + if (useXULCache) { + JSScript* newScriptObject = + cache->GetScript(mSrcURI); + if (newScriptObject) + Set(newScriptObject); + } + } + + if (!mScriptObject) { + if (mSrcURI) { + rv = cache->GetInputStream(mSrcURI, getter_AddRefs(objectInput)); + } + // If !mSrcURI, we have an inline script. We shouldn't have + // to do anything else in that case, I think. + + // We do reflect errors into rv, but our caller may want to + // ignore our return value, because mScriptObject will be null + // after any error, and that suffices to cause the script to + // be reloaded (from the src= URI, if any) and recompiled. + // We're better off slow-loading than bailing out due to a + // error. + if (NS_SUCCEEDED(rv)) + rv = Deserialize(objectInput, aProtoDoc, nullptr, nullptr); + + if (NS_SUCCEEDED(rv)) { + if (useXULCache && mSrcURI) { + bool isChrome = false; + mSrcURI->SchemeIs("chrome", &isChrome); + if (isChrome) { + JS::Rooted<JSScript*> script(RootingCx(), GetScriptObject()); + cache->PutScript(mSrcURI, script); + } + } + cache->FinishInputStream(mSrcURI); + } else { + // If mSrcURI is not in the cache, + // rv will be NS_ERROR_NOT_AVAILABLE and we'll try to + // update the cache file to hold a serialization of + // this script, once it has finished loading. + if (rv != NS_ERROR_NOT_AVAILABLE) + cache->AbortCaching(); + } + } + } + return rv; +} + +class NotifyOffThreadScriptCompletedRunnable : public Runnable +{ + // An array of all outstanding script receivers. All reference counting of + // these objects happens on the main thread. When we return to the main + // thread from script compilation we make sure our receiver is still in + // this array (still alive) before proceeding. This array is cleared during + // shutdown, potentially before all outstanding script compilations have + // finished. We do not need to worry about pointer replay here, because + // a) we should not be starting script compilation after clearing this + // array and b) in all other cases the receiver will still be alive. + static StaticAutoPtr<nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>> sReceivers; + static bool sSetupClearOnShutdown; + + nsIOffThreadScriptReceiver* mReceiver; + void *mToken; + +public: + NotifyOffThreadScriptCompletedRunnable(nsIOffThreadScriptReceiver* aReceiver, + void *aToken) + : mReceiver(aReceiver), mToken(aToken) + {} + + static void NoteReceiver(nsIOffThreadScriptReceiver* aReceiver) { + if (!sSetupClearOnShutdown) { + ClearOnShutdown(&sReceivers); + sSetupClearOnShutdown = true; + sReceivers = new nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>(); + } + + // If we ever crash here, it's because we tried to lazy compile script + // too late in shutdown. + sReceivers->AppendElement(aReceiver); + } + + NS_DECL_NSIRUNNABLE +}; + +StaticAutoPtr<nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>> NotifyOffThreadScriptCompletedRunnable::sReceivers; +bool NotifyOffThreadScriptCompletedRunnable::sSetupClearOnShutdown = false; + +NS_IMETHODIMP +NotifyOffThreadScriptCompletedRunnable::Run() +{ + MOZ_ASSERT(NS_IsMainThread()); + + JS::Rooted<JSScript*> script(RootingCx()); + { + AutoJSAPI jsapi; + if (!jsapi.Init(xpc::CompilationScope())) { + // Now what? I guess we just leak... this should probably never + // happen. + return NS_ERROR_UNEXPECTED; + } + JSContext* cx = jsapi.cx(); + script = JS::FinishOffThreadScript(cx, mToken); + } + + if (!sReceivers) { + // We've already shut down. + return NS_OK; + } + + auto index = sReceivers->IndexOf(mReceiver); + MOZ_RELEASE_ASSERT(index != sReceivers->NoIndex); + nsCOMPtr<nsIOffThreadScriptReceiver> receiver = (*sReceivers)[index].forget(); + sReceivers->RemoveElementAt(index); + + return receiver->OnScriptCompileComplete(script, script ? NS_OK : NS_ERROR_FAILURE); +} + +static void +OffThreadScriptReceiverCallback(void *aToken, void *aCallbackData) +{ + // Be careful not to adjust the refcount on the receiver, as this callback + // may be invoked off the main thread. + nsIOffThreadScriptReceiver* aReceiver = static_cast<nsIOffThreadScriptReceiver*>(aCallbackData); + RefPtr<NotifyOffThreadScriptCompletedRunnable> notify = + new NotifyOffThreadScriptCompletedRunnable(aReceiver, aToken); + NS_DispatchToMainThread(notify); +} + +nsresult +nsXULPrototypeScript::Compile(JS::SourceBufferHolder& aSrcBuf, + nsIURI* aURI, uint32_t aLineNo, + nsIDocument* aDocument, + nsIOffThreadScriptReceiver *aOffThreadReceiver /* = nullptr */) +{ + // We'll compile the script in the compilation scope. + AutoJSAPI jsapi; + if (!jsapi.Init(xpc::CompilationScope())) { + return NS_ERROR_UNEXPECTED; + } + JSContext* cx = jsapi.cx(); + + nsresult rv; + nsAutoCString urlspec; + nsContentUtils::GetWrapperSafeScriptFilename(aDocument, aURI, urlspec, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Ok, compile it to create a prototype script object! + NS_ENSURE_TRUE(JSVersion(mLangVersion) != JSVERSION_UNKNOWN, NS_OK); + JS::CompileOptions options(cx); + options.setIntroductionType("scriptElement") + .setFileAndLine(urlspec.get(), aLineNo) + .setVersion(JSVersion(mLangVersion)); + // 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); + JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx)); + if (scope) { + JS::ExposeObjectToActiveJS(scope); + } + + if (aOffThreadReceiver && JS::CanCompileOffThread(cx, options, aSrcBuf.length())) { + if (!JS::CompileOffThread(cx, options, + aSrcBuf.get(), aSrcBuf.length(), + OffThreadScriptReceiverCallback, + static_cast<void*>(aOffThreadReceiver))) { + return NS_ERROR_OUT_OF_MEMORY; + } + NotifyOffThreadScriptCompletedRunnable::NoteReceiver(aOffThreadReceiver); + } else { + JS::Rooted<JSScript*> script(cx); + if (!JS::Compile(cx, options, aSrcBuf, &script)) + return NS_ERROR_OUT_OF_MEMORY; + Set(script); + } + return NS_OK; +} + +nsresult +nsXULPrototypeScript::Compile(const char16_t* aText, + int32_t aTextLength, + nsIURI* aURI, + uint32_t aLineNo, + nsIDocument* aDocument, + nsIOffThreadScriptReceiver *aOffThreadReceiver /* = nullptr */) +{ + JS::SourceBufferHolder srcBuf(aText, aTextLength, + JS::SourceBufferHolder::NoOwnership); + return Compile(srcBuf, aURI, aLineNo, aDocument, aOffThreadReceiver); +} + +void +nsXULPrototypeScript::UnlinkJSObjects() +{ + if (mScriptObject) { + mScriptObject = nullptr; + mozilla::DropJSObjects(this); + } +} + +void +nsXULPrototypeScript::Set(JSScript* aObject) +{ + MOZ_ASSERT(!mScriptObject, "Leaking script object."); + if (!aObject) { + mScriptObject = nullptr; + return; + } + + mScriptObject = aObject; + mozilla::HoldJSObjects(this); +} + +//---------------------------------------------------------------------- +// +// nsXULPrototypeText +// + +nsresult +nsXULPrototypeText::Serialize(nsIObjectOutputStream* aStream, + nsXULPrototypeDocument* aProtoDoc, + const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) +{ + nsresult rv; + + // Write basic prototype data + rv = aStream->Write32(mType); + + nsresult tmp = aStream->WriteWStringZ(mValue.get()); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + return rv; +} + +nsresult +nsXULPrototypeText::Deserialize(nsIObjectInputStream* aStream, + nsXULPrototypeDocument* aProtoDoc, + nsIURI* aDocumentURI, + const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) +{ + nsresult rv = aStream->ReadString(mValue); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +//---------------------------------------------------------------------- +// +// nsXULPrototypePI +// + +nsresult +nsXULPrototypePI::Serialize(nsIObjectOutputStream* aStream, + nsXULPrototypeDocument* aProtoDoc, + const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) +{ + nsresult rv; + + // Write basic prototype data + rv = aStream->Write32(mType); + + nsresult tmp = aStream->WriteWStringZ(mTarget.get()); + if (NS_FAILED(tmp)) { + rv = tmp; + } + tmp = aStream->WriteWStringZ(mData.get()); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + return rv; +} + +nsresult +nsXULPrototypePI::Deserialize(nsIObjectInputStream* aStream, + nsXULPrototypeDocument* aProtoDoc, + nsIURI* aDocumentURI, + const nsTArray<RefPtr<mozilla::dom::NodeInfo>> *aNodeInfos) +{ + nsresult rv; + + rv = aStream->ReadString(mTarget); + if (NS_FAILED(rv)) return rv; + rv = aStream->ReadString(mData); + if (NS_FAILED(rv)) return rv; + + return rv; +} |