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