/* -*- 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 "nsIDOMElementCSSInlineStyle.h" #include "nsIDOMXULSelectCntrlItemEl.h" #include "nsIDocument.h" #include "nsLayoutStylesheetCache.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/EventListenerManager.h" #include "mozilla/EventStateManager.h" #include "mozilla/EventStates.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 "nsIStyleSheet.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 // 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" 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 nsIDOMElementCSSInlineStyle, public nsIFrameLoaderOwner { ~nsXULElementTearoff() {} public: NS_DECL_CYCLE_COLLECTING_ISUPPORTS NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(nsXULElementTearoff, nsIDOMElementCSSInlineStyle) explicit nsXULElementTearoff(nsXULElement* aElement) : mElement(aElement) { } NS_IMETHOD GetStyle(nsIDOMCSSStyleDeclaration** aStyle) override { nsXULElement* element = static_cast(mElement.get()); NS_ADDREF(*aStyle = element->Style()); return NS_OK; } NS_FORWARD_NSIFRAMELOADEROWNER(static_cast(mElement.get())->) private: nsCOMPtr 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_ENTRY(nsIDOMElementCSSInlineStyle) NS_INTERFACE_MAP_END_AGGREGATED(mElement) //---------------------------------------------------------------------- // nsXULElement // nsXULElement::nsXULElement(already_AddRefed 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 if (mFrameLoader) { mFrameLoader->Destroy(); } } void nsXULElement::nsXULSlots::Traverse(nsCycleCollectionTraversalCallback &cb) { NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mSlots->mFrameLoader"); cb.NoteXPCOMChild(NS_ISUPPORTS_CAST(nsIFrameLoader*, mFrameLoader)); } nsINode::nsSlots* nsXULElement::CreateSlots() { return new nsXULSlots(); } void nsXULElement::MaybeUpdatePrivateLifetime() { if (AttrValueIs(kNameSpaceID_None, nsGkAtoms::windowtype, NS_LITERAL_STRING("navigator:browser"), eCaseMatters)) { return; } nsPIDOMWindow* win = OwnerDoc()->GetWindow(); nsCOMPtr docShell = win ? win->GetDocShell() : nullptr; if (docShell) { docShell->SetAffectPrivateSessionLifetime(false); } } /* static */ already_AddRefed nsXULElement::Create(nsXULPrototypeElement* aPrototype, mozilla::dom::NodeInfo *aNodeInfo, bool aIsScriptable, bool aIsRoot) { nsRefPtr ni = aNodeInfo; nsRefPtr 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; nsRefPtr 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; } nsRefPtr element = Create(aPrototype, nodeInfo, aIsScriptable, aIsRoot); element.forget(aResult); return NS_OK; } nsresult NS_NewXULElement(Element** aResult, already_AddRefed&& aNodeInfo) { nsRefPtr 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&& aNodeInfo) { nsRefPtr 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(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(nsIDOMElementCSSInlineStyle, new nsXULElementTearoff(this)) 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; nsRefPtr ni = aNodeInfo; nsRefPtr 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::eCSSStyleRule) { nsRefPtr ruleClone = originalValue->GetCSSStyleRuleValue()->Clone(); nsString stringValue; originalValue->ToString(stringValue); nsRefPtr styleRule = do_QueryObject(ruleClone); attrValue.SetTo(styleRule, &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 nsXULElement::GetElementsByAttribute(const nsAString& aAttribute, const nsAString& aValue) { nsCOMPtr attrAtom(do_GetAtom(aAttribute)); void* attrValue = new nsString(aValue); nsRefPtr 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 nsXULElement::GetElementsByAttributeNS(const nsAString& aNamespaceURI, const nsAString& aAttribute, const nsAString& aValue, ErrorResult& rv) { nsCOMPtr attrAtom(do_GetAtom(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); nsRefPtr 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(); nsPIDOMWindow *window; Element *root = doc->GetRootElement(); if ((!root || root == this) && !mNodeInfo->Equals(nsGkAtoms::overlay) && (window = doc->GetInnerWindow())) { nsCOMPtr piTarget = do_QueryInterface(window); *aDefer = false; return piTarget->GetOrCreateListenerManager(); } return nsStyledElement::GetEventListenerManagerForAttr(aAttrName, aDefer); } // returns true if the element is not a list static bool IsNonList(mozilla::dom::NodeInfo* aNodeInfo) { return !aNodeInfo->Equals(nsGkAtoms::tree) && !aNodeInfo->Equals(nsGkAtoms::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 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 // cannot take this focus. if (IsNonList(mNodeInfo)) *aTabIndex = -1; } } else { shouldFocus = *aTabIndex >= 0; } } return shouldFocus; } bool nsXULElement::PerformAccesskey(bool aKeyCausesActivation, bool aIsTrustedEvent) { nsCOMPtr content(this); if (IsXULElement(nsGkAtoms::label)) { nsCOMPtr 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 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 elementToFocus; // for radio buttons, focus the radiogroup instead if (content->IsXULElement(nsGkAtoms::radio)) { nsCOMPtr controlItem(do_QueryInterface(content)); if (controlItem) { bool disabled; controlItem->GetDisabled(&disabled); if (!disabled) { nsCOMPtr 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. nsPIDOMWindow* window = OwnerDoc()->GetWindow(); focused = (window && window->GetFocusedNode()); } } } if (aKeyCausesActivation && !content->IsAnyOfXULElements(nsGkAtoms::textbox, nsGkAtoms::menulist)) { elm->ClickWithInputSource(nsIDOMMouseEvent::MOZ_SOURCE_KEYBOARD); } } 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