summaryrefslogtreecommitdiffstats
path: root/dom/xul/nsXULElement.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/xul/nsXULElement.cpp')
-rw-r--r--dom/xul/nsXULElement.cpp2975
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;
+}