gecko-dev/dom/xul/nsXULElement.cpp

2436 строки
81 KiB
C++

/* -*- 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/. */
#include "nsCOMPtr.h"
#include "nsDOMCID.h"
#include "nsError.h"
#include "nsDOMString.h"
#include "nsAtom.h"
#include "nsIBaseWindow.h"
#include "nsIDOMEventListener.h"
#include "nsIDOMXULCommandDispatcher.h"
#include "nsIDOMXULSelectCntrlItemEl.h"
#include "nsIDocument.h"
#include "mozilla/ClearOnShutdown.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/EventStates.h"
#include "mozilla/DeclarationBlock.h"
#include "nsFocusManager.h"
#include "nsHTMLStyleSheet.h"
#include "nsNameSpaceManager.h"
#include "nsIObjectInputStream.h"
#include "nsIObjectOutputStream.h"
#include "nsIPresShell.h"
#include "nsIPrincipal.h"
#include "nsIScriptContext.h"
#include "nsIScriptError.h"
#include "nsIScriptSecurityManager.h"
#include "nsIServiceManager.h"
#include "nsIURL.h"
#include "nsViewManager.h"
#include "nsIWidget.h"
#include "nsLayoutCID.h"
#include "nsContentCID.h"
#include "mozilla/dom/Event.h"
#include "nsStyleConsts.h"
#include "nsString.h"
#include "nsXULControllers.h"
#include "nsIBoxObject.h"
#include "nsPIBoxObject.h"
#include "XULDocument.h"
#include "nsXULPopupListener.h"
#include "nsContentUtils.h"
#include "nsContentList.h"
#include "mozilla/InternalMutationEvent.h"
#include "mozilla/MouseEvents.h"
#include "nsPIDOMWindow.h"
#include "nsJSPrincipals.h"
#include "nsDOMAttributeMap.h"
#include "nsGkAtoms.h"
#include "nsNodeUtils.h"
#include "nsFrameLoader.h"
#include "mozilla/Logging.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"
#include "nsReadableUtils.h"
#include "nsIFrame.h"
#include "nsNodeInfoManager.h"
#include "nsXBLBinding.h"
#include "nsXULTooltipListener.h"
#include "mozilla/EventDispatcher.h"
#include "mozAutoDocUpdate.h"
#include "nsCCUncollectableMarker.h"
#include "nsICSSDeclaration.h"
#include "nsLayoutUtils.h"
#include "XULFrameElement.h"
#include "XULPopupElement.h"
#include "XULScrollElement.h"
#include "mozilla/dom/XULElementBinding.h"
#include "mozilla/dom/BoxObject.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/dom/MutationEventBinding.h"
#include "mozilla/dom/XULCommandEvent.h"
using namespace mozilla;
using namespace mozilla::dom;
#ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING
uint32_t nsXULPrototypeAttribute::gNumElements;
uint32_t nsXULPrototypeAttribute::gNumAttributes;
uint32_t nsXULPrototypeAttribute::gNumCacheTests;
uint32_t nsXULPrototypeAttribute::gNumCacheHits;
uint32_t nsXULPrototypeAttribute::gNumCacheSets;
uint32_t nsXULPrototypeAttribute::gNumCacheFills;
#endif
#define NS_DISPATCH_XUL_COMMAND (1 << 0)
//----------------------------------------------------------------------
// nsXULElement
//
nsXULElement::nsXULElement(already_AddRefed<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()
{
}
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 */
nsXULElement* NS_NewBasicXULElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
{
return new nsXULElement(aNodeInfo);
}
/* static */
nsXULElement* nsXULElement::Construct(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
{
RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo;
if (nodeInfo->Equals(nsGkAtoms::menupopup) ||
nodeInfo->Equals(nsGkAtoms::popup) ||
nodeInfo->Equals(nsGkAtoms::panel) ||
nodeInfo->Equals(nsGkAtoms::tooltip)) {
return NS_NewXULPopupElement(nodeInfo.forget());
}
if (nodeInfo->Equals(nsGkAtoms::iframe) ||
nodeInfo->Equals(nsGkAtoms::browser) ||
nodeInfo->Equals(nsGkAtoms::editor)) {
already_AddRefed<mozilla::dom::NodeInfo> frameni = nodeInfo.forget();
return new XULFrameElement(frameni);
}
if (nodeInfo->Equals(nsGkAtoms::scrollbox)) {
already_AddRefed<mozilla::dom::NodeInfo> scrollni = nodeInfo.forget();
return new XULScrollElement(scrollni);
}
return NS_NewBasicXULElement(nodeInfo.forget());
}
/* static */
already_AddRefed<nsXULElement>
nsXULElement::CreateFromPrototype(nsXULPrototypeElement* aPrototype,
mozilla::dom::NodeInfo *aNodeInfo,
bool aIsScriptable,
bool aIsRoot)
{
RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
nsCOMPtr<Element> baseElement;
NS_NewXULElement(getter_AddRefs(baseElement),
ni.forget(),
dom::FROM_PARSER_NETWORK,
aPrototype->mIsAtom);
if (baseElement) {
nsXULElement* element = FromNode(baseElement);
if (aPrototype->mHasIdAttribute) {
element->SetHasID();
}
if (aPrototype->mHasClassAttribute) {
element->SetMayHaveClass();
}
if (aPrototype->mHasStyleAttribute) {
element->SetMayHaveStyle();
}
element->MakeHeavyweight(aPrototype);
if (aIsScriptable) {
// Check each attribute on the prototype to see if we need to do
// any additional processing and hookup that would otherwise be
// done 'automagically' by SetAttr().
for (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 baseElement.forget().downcast<nsXULElement>();
}
return nullptr;
}
nsresult
nsXULElement::CreateFromPrototype(nsXULPrototypeElement* aPrototype,
nsIDocument* aDocument,
bool aIsScriptable,
bool aIsRoot,
Element** aResult)
{
// Create an nsXULElement from a prototype
MOZ_ASSERT(aPrototype != nullptr, "null ptr");
if (! aPrototype)
return NS_ERROR_NULL_POINTER;
MOZ_ASSERT(aResult != nullptr, "null ptr");
if (! aResult)
return NS_ERROR_NULL_POINTER;
RefPtr<mozilla::dom::NodeInfo> nodeInfo;
if (aDocument) {
mozilla::dom::NodeInfo* ni = aPrototype->mNodeInfo;
nodeInfo = aDocument->NodeInfoManager()->
GetNodeInfo(ni->NameAtom(), ni->GetPrefixAtom(), ni->NamespaceID(),
ELEMENT_NODE);
} else {
nodeInfo = aPrototype->mNodeInfo;
}
RefPtr<nsXULElement> element = CreateFromPrototype(aPrototype, nodeInfo,
aIsScriptable, aIsRoot);
element.forget(aResult);
return NS_OK;
}
nsresult
NS_NewXULElement(Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
FromParser aFromParser, nsAtom* aIsAtom,
mozilla::dom::CustomElementDefinition* aDefinition)
{
RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo;
MOZ_ASSERT(nodeInfo, "need nodeinfo for non-proto Create");
NS_ASSERTION(nodeInfo->NamespaceEquals(kNameSpaceID_XUL),
"Trying to create XUL elements that don't have the XUL namespace");
nsIDocument* doc = nodeInfo->GetDocument();
if (doc && !doc->AllowXULXBL()) {
return NS_ERROR_NOT_AVAILABLE;
}
return nsContentUtils::NewXULOrHTMLElement(aResult, nodeInfo, aFromParser, aIsAtom, aDefinition);
}
void
NS_TrustedNewXULElement(Element** aResult,
already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
{
RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
MOZ_ASSERT(ni, "need nodeinfo for non-proto Create");
// Create an nsXULElement with the specified namespace and tag.
NS_ADDREF(*aResult = nsXULElement::Construct(ni.forget()));
}
//----------------------------------------------------------------------
// nsISupports interface
NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULElement)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsXULElement,
nsStyledElement)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBindingParent);
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(mBindingParent);
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_ELEMENT_INTERFACE_TABLE_TO_MAP_SEGUE
NS_INTERFACE_MAP_END_INHERITING(nsStyledElement)
//----------------------------------------------------------------------
// nsINode interface
nsresult
nsXULElement::Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult,
bool aPreallocateChildren) const
{
*aResult = nullptr;
RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
RefPtr<nsXULElement> element = Construct(ni.forget());
nsresult rv = element->mAttrsAndChildren.EnsureCapacityToClone(mAttrsAndChildren,
aPreallocateChildren);
NS_ENSURE_SUCCESS(rv, rv);
// Note that we're _not_ copying mControllers.
uint32_t count = mAttrsAndChildren.AttrCount();
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<DeclarationBlock> declClone = decl->Clone();
nsString stringValue;
originalValue->ToString(stringValue);
attrValue.SetTo(declClone.forget(), &stringValue);
} else {
attrValue.SetTo(*originalValue);
}
bool oldValueSet;
if (originalName->IsAtom()) {
rv = element->mAttrsAndChildren.SetAndSwapAttr(originalName->Atom(),
attrValue,
&oldValueSet);
} else {
rv = element->mAttrsAndChildren.SetAndSwapAttr(originalName->NodeInfo(),
attrValue,
&oldValueSet);
}
NS_ENSURE_SUCCESS(rv, rv);
element->AddListenerFor(*originalName, true);
if (originalName->Equals(nsGkAtoms::id) &&
!originalValue->IsEmptyString()) {
element->SetHasID();
}
if (originalName->Equals(nsGkAtoms::_class)) {
element->SetMayHaveClass();
}
if (originalName->Equals(nsGkAtoms::style)) {
element->SetMayHaveStyle();
}
}
element.forget(aResult);
return rv;
}
//----------------------------------------------------------------------
already_AddRefed<nsINodeList>
nsXULElement::GetElementsByAttribute(const nsAString& aAttribute,
const nsAString& aValue)
{
RefPtr<nsAtom> 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();
}
already_AddRefed<nsINodeList>
nsXULElement::GetElementsByAttributeNS(const nsAString& aNamespaceURI,
const nsAString& aAttribute,
const nsAString& aValue,
ErrorResult& rv)
{
RefPtr<nsAtom> 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(nsAtom* 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) && (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::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)
{
RefPtr<Element> content(this);
if (IsXULElement(nsGkAtoms::label)) {
nsAutoString control;
GetAttr(kNameSpaceID_None, nsGkAtoms::control, control);
if (control.IsEmpty()) {
return false;
}
//XXXsmaug Should we use ShadowRoot::GetElementById in case
// content is in Shadow DOM?
nsCOMPtr<nsIDocument> document = content->GetUncomposedDoc();
if (!document) {
return false;
}
content = document->GetElementById(control);
if (!content) {
return false;
}
}
nsIFrame* frame = content->GetPrimaryFrame();
if (!frame || !frame->IsVisibleConsideringAncestors()) {
return false;
}
bool focused = false;
nsXULElement* elm = FromNode(content);
if (elm) {
// Define behavior for each type of XUL element.
if (!content->IsXULElement(nsGkAtoms::toolbarbutton)) {
nsIFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
nsCOMPtr<Element> 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 = content;
}
if (elementToFocus) {
fm->SetFocus(elementToFocus, nsIFocusManager::FLAG_BYKEY);
// Return true if the element became focused.
nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
focused = (window && window->GetFocusedElement());
}
}
}
if (aKeyCausesActivation &&
!content->IsAnyOfXULElements(nsGkAtoms::textbox, nsGkAtoms::menulist)) {
elm->ClickWithInputSource(MouseEvent_Binding::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()) {
nsAtom *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(nsAtom* 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);
}
#ifdef DEBUG
/**
* 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(nsAtom* aTag)
{
return // scrollbar parts:
aTag == nsGkAtoms::scrollbar ||
aTag == nsGkAtoms::scrollbarbutton ||
aTag == nsGkAtoms::scrollcorner ||
aTag == nsGkAtoms::slider ||
aTag == nsGkAtoms::thumb ||
// other
aTag == nsGkAtoms::datetimebox ||
aTag == nsGkAtoms::resizer ||
aTag == nsGkAtoms::label ||
aTag == nsGkAtoms::videocontrols;
}
#endif
class XULInContentErrorReporter : public Runnable
{
public:
explicit XULInContentErrorReporter(nsIDocument* aDocument)
: mozilla::Runnable("XULInContentErrorReporter")
, mDocument(aDocument)
{
}
NS_IMETHOD Run() override
{
mDocument->WarnOnceAbout(nsIDocument::eImportXULIntoContent, false);
return NS_OK;
}
private:
nsCOMPtr<nsIDocument> mDocument;
};
static bool
NeedTooltipSupport(const nsXULElement& aXULElement)
{
if (aXULElement.NodeInfo()->Equals(nsGkAtoms::treechildren)) {
// treechildren always get tooltip support, since cropped tree cells show
// their full text in a tooltip.
return true;
}
return aXULElement.GetBoolAttr(nsGkAtoms::tooltip) ||
aXULElement.GetBoolAttr(nsGkAtoms::tooltiptext);
}
nsresult
nsXULElement::BindToTree(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();
#ifdef DEBUG
if (doc && !doc->AllowXULXBL() && !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 assertion makes sure no other XUL element than the ones in the
// minimal XUL sheet is used in the bindings.
if (!XULElementsRulesInMinimalXULSheet(NodeInfo()->NameAtom())) {
NS_ERROR("Unexpected XUL element in non-XUL doc");
}
}
#endif
if (doc && NeedTooltipSupport(*this)) {
AddTooltipSupport();
}
return rv;
}
void
nsXULElement::UnbindFromTree(bool aDeep, bool aNullParent)
{
if (NeedTooltipSupport(*this)) {
RemoveTooltipSupport();
}
// mControllers can own objects that are implemented
// in JavaScript (such as some implementations of
// nsIControllers. These objects prevent their global
// object's script object from being garbage collected,
// which means JS continues to hold an owning reference
// to the nsGlobalWindow, which owns the document,
// which owns this content. That's a cycle, so we break
// it here. (It might be better to break this by releasing
// mDocument in nsGlobalWindow::SetDocShell, but I'm not
// sure whether that would fix all possible cycles through
// mControllers.)
nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
if (slots) {
slots->mControllers = nullptr;
}
nsStyledElement::UnbindFromTree(aDeep, aNullParent);
}
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) {
Element* element = 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?
nsIContent* bindingParent = GetBindingParent();
element = bindingParent ? bindingParent->AsElement() : nullptr;
}
if (element) {
shell->GetPresContext()->EventStateManager()->
UnregisterAccessKey(element, aOldValue.First());
}
}
}
}
nsresult
nsXULElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
const 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, nsAtom* aName,
const nsAttrValue* aValue,
const nsAttrValue* aOldValue,
nsIPrincipal* aSubjectPrincipal,
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 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::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
if (document->IsXULDocument()) {
document->AsXULDocument()->ResetDocumentDirection();
}
} else if (aName == nsGkAtoms::lwtheme ||
aName == nsGkAtoms::lwthemetextcolor) {
// if the lwtheme changed, make sure to reset the document lwtheme cache
if (document->IsXULDocument()) {
document->AsXULDocument()->ResetDocumentLWTheme();
UpdateBrightTitlebarForeground(document);
}
} else if (aName == nsGkAtoms::brighttitlebarforeground) {
UpdateBrightTitlebarForeground(document);
}
}
} 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::localedir) {
// if the localedir changed on the root element, reset the document direction
if (doc->IsXULDocument()) {
doc->AsXULDocument()->ResetDocumentDirection();
}
} else if ((aName == nsGkAtoms::lwtheme ||
aName == nsGkAtoms::lwthemetextcolor)) {
// if the lwtheme changed, make sure to restyle appropriately
if (doc->IsXULDocument()) {
doc->AsXULDocument()->ResetDocumentLWTheme();
UpdateBrightTitlebarForeground(doc);
}
} else if (aName == nsGkAtoms::brighttitlebarforeground) {
UpdateBrightTitlebarForeground(doc);
} else if (aName == nsGkAtoms::drawintitlebar) {
SetDrawsInTitlebar(false);
} else if (aName == nsGkAtoms::drawtitle) {
SetDrawsTitle(false);
}
}
}
if (aName == nsGkAtoms::tooltip || aName == nsGkAtoms::tooltiptext) {
if (!!aValue != !!aOldValue &&
IsInComposedDoc() &&
!NodeInfo()->Equals(nsGkAtoms::treechildren)) {
if (aValue) {
AddTooltipSupport();
} else {
RemoveTooltipSupport();
}
}
}
// XXX need to check if they're changing an event handler: if
// so, then we need to unhook the old one. Or something.
}
return nsStyledElement::AfterSetAttr(aNamespaceID, aName,
aValue, aOldValue, aSubjectPrincipal, aNotify);
}
void
nsXULElement::AddTooltipSupport()
{
nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance();
if (!listener) {
return;
}
listener->AddTooltipSupport(this);
}
void
nsXULElement::RemoveTooltipSupport()
{
nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance();
if (!listener) {
return;
}
listener->RemoveTooltipSupport(this);
}
bool
nsXULElement::ParseAttribute(int32_t aNamespaceID,
nsAtom* aAttribute,
const nsAString& aValue,
nsIPrincipal* aMaybeScriptedPrincipal,
nsAttrValue& aResult)
{
// Parse into a nsAttrValue
if (!nsStyledElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
aMaybeScriptedPrincipal, aResult)) {
// Fall back to parsing as atom for short values
aResult.ParseStringOrAtom(aValue);
}
return true;
}
void
nsXULElement::RemoveBroadcaster(const nsAString & broadcasterId)
{
nsIDocument* doc = OwnerDoc();
if (!doc->IsXULDocument()) {
return;
}
if (Element* broadcaster = doc->GetElementById(broadcasterId)) {
doc->AsXULDocument()->RemoveBroadcastListenerFor(
*broadcaster, *this, NS_LITERAL_STRING("*"));
}
}
void
nsXULElement::DestroyContent()
{
nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
if (slots) {
slots->mControllers = 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
bool
nsXULElement::IsEventStoppedFromAnonymousScrollbar(EventMessage aMessage)
{
return (IsRootOfNativeAnonymousSubtree() &&
IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::scrollcorner) &&
(aMessage == eMouseClick || aMessage == eMouseDoubleClick ||
aMessage == eXULCommand || aMessage == eContextMenu ||
aMessage == eDragStart || aMessage == eMouseAuxClick));
}
nsresult
nsXULElement::DispatchXULCommand(const EventChainVisitor& aVisitor,
nsAutoString& aCommand)
{
// XXX sXBL/XBL2 issue! Owner or current document?
nsCOMPtr<nsIDocument> doc = GetUncomposedDoc();
NS_ENSURE_STATE(doc);
RefPtr<Element> commandElt = doc->GetElementById(aCommand);
if (commandElt) {
// Create a new command event to dispatch to the element
// pointed to by the command attribute. The new event's
// sourceEvent will be the original command event that we're
// handling.
RefPtr<Event> event = aVisitor.mDOMEvent;
uint16_t inputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
while (event) {
NS_ENSURE_STATE(event->GetOriginalTarget() != commandElt);
RefPtr<XULCommandEvent> commandEvent = event->AsXULCommandEvent();
if (commandEvent) {
event = commandEvent->GetSourceEvent();
inputSource = commandEvent->InputSource();
} else {
event = nullptr;
}
}
WidgetInputEvent* orig = aVisitor.mEvent->AsInputEvent();
nsContentUtils::DispatchXULCommand(
commandElt,
orig->IsTrusted(),
aVisitor.mDOMEvent,
nullptr,
orig->IsControl(),
orig->IsAlt(),
orig->IsShift(),
orig->IsMeta(),
inputSource);
} else {
NS_WARNING("A XUL element is attached to a command that doesn't exist!\n");
}
return NS_OK;
}
void
nsXULElement::GetEventTargetParent(EventChainPreVisitor& aVisitor)
{
aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119
if (IsEventStoppedFromAnonymousScrollbar(aVisitor.mEvent->mMessage)) {
// Don't propagate these events from native anonymous scrollbar.
aVisitor.mCanHandle = true;
aVisitor.SetParentTarget(nullptr, false);
return;
}
if (aVisitor.mEvent->mMessage == eXULCommand &&
aVisitor.mEvent->mClass == eInputEventClass &&
aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) &&
!IsXULElement(nsGkAtoms::command)) {
// Check that we really have an xul command event. That will be handled
// in a special way.
// See if we have a command elt. If so, we execute on the command
// instead of on our content element.
nsAutoString command;
if (aVisitor.mDOMEvent &&
aVisitor.mDOMEvent->AsXULCommandEvent() &&
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;
// Dispatch XUL command in PreHandleEvent to prevent it breaks event
// target chain creation
aVisitor.mWantsPreHandleEvent = true;
aVisitor.mItemFlags |= NS_DISPATCH_XUL_COMMAND;
return;
}
}
nsStyledElement::GetEventTargetParent(aVisitor);
}
nsresult
nsXULElement::PreHandleEvent(EventChainVisitor& aVisitor)
{
if (aVisitor.mItemFlags & NS_DISPATCH_XUL_COMMAND) {
nsAutoString command;
GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
MOZ_ASSERT(!command.IsEmpty());
return DispatchXULCommand(aVisitor, command);
}
return nsStyledElement::PreHandleEvent(aVisitor);
}
//----------------------------------------------------------------------
// Implementation methods
nsChangeHint
nsXULElement::GetAttributeChangeHint(const nsAtom* aAttribute,
int32_t aModType) const
{
nsChangeHint retval(nsChangeHint(0));
if (aAttribute == nsGkAtoms::value &&
(aModType == MutationEvent_Binding::REMOVAL ||
aModType == MutationEvent_Binding::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 nsAtom* aAttribute) const
{
return false;
}
nsIControllers*
nsXULElement::GetControllers(ErrorResult& rv)
{
if (! Controllers()) {
nsExtendedDOMSlots* slots = ExtendedDOMSlots();
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();
}
already_AddRefed<BoxObject>
nsXULElement::GetBoxObject(ErrorResult& rv)
{
// XXX sXBL/XBL2 issue! Owner or current document?
return OwnerDoc()->GetBoxObjectFor(this, rv);
}
void
nsXULElement::Click(CallerType aCallerType)
{
ClickWithInputSource(MouseEvent_Binding::MOZ_SOURCE_UNKNOWN,
aCallerType == CallerType::System);
}
void
nsXULElement::ClickWithInputSource(uint16_t aInputSource, bool aIsTrustedEvent)
{
if (BoolAttrIsTrue(nsGkAtoms::disabled))
return;
nsCOMPtr<nsIDocument> doc = GetComposedDoc(); // Strong just in case
if (doc) {
RefPtr<nsPresContext> context = doc->GetPresContext();
if (context) {
// strong ref to PresContext so events don't destroy it
WidgetMouseEvent eventDown(aIsTrustedEvent, eMouseDown,
nullptr, WidgetMouseEvent::eReal);
WidgetMouseEvent eventUp(aIsTrustedEvent, eMouseUp,
nullptr, WidgetMouseEvent::eReal);
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;
}
}
}
// oncommand is fired when an element is clicked...
DoCommand();
}
void
nsXULElement::DoCommand()
{
nsCOMPtr<nsIDocument> doc = GetComposedDoc(); // strong just in case
if (doc) {
nsContentUtils::DispatchXULCommand(this, true);
}
}
bool
nsXULElement::IsNodeOfType(uint32_t aFlags) const
{
return false;
}
nsresult
nsXULElement::AddPopupListener(nsAtom* aName)
{
// Add a popup listener to the element
bool isContext = (aName == nsGkAtoms::context ||
aName == nsGkAtoms::contextmenu);
uint32_t listenerFlag = isContext ?
XUL_ELEMENT_HAS_CONTENTMENU_LISTENER :
XUL_ELEMENT_HAS_POPUP_LISTENER;
if (HasFlag(listenerFlag)) {
return NS_OK;
}
nsCOMPtr<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<DeclarationBlock> declClone = decl->Clone();
nsString stringValue;
protoattr->mValue.ToString(stringValue);
attrValue.SetTo(declClone.forget(), &stringValue);
} else {
attrValue.SetTo(protoattr->mValue);
}
bool oldValueSet;
// XXX we might wanna have a SetAndTakeAttr that takes an nsAttrName
if (protoattr->mName.IsAtom()) {
rv = mAttrsAndChildren.SetAndSwapAttr(protoattr->mName.Atom(),
attrValue, &oldValueSet);
} else {
rv = mAttrsAndChildren.SetAndSwapAttr(protoattr->mName.NodeInfo(),
attrValue, &oldValueSet);
}
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;
nsPresContext* presContext = doc->GetPresContext();
if (presContext && presContext->IsChrome()) {
nsIFrame* frame = GetPrimaryFrame();
if (frame) {
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;
}
class SetDrawInTitleBarEvent : public Runnable
{
public:
SetDrawInTitleBarEvent(nsIWidget* aWidget, bool aState)
: mozilla::Runnable("SetDrawInTitleBarEvent")
, 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)
: mozilla::Runnable("MarginSetter")
, mWidget(aWidget)
, mMargin(-1, -1, -1, -1)
{
}
MarginSetter(nsIWidget* aWidget, const LayoutDeviceIntMargin& aMargin)
: mozilla::Runnable("MarginSetter")
, 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(nsAtom* 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;
}
nsAtom *attr = name->Atom();
if (!nsContentUtils::IsEventAttributeName(attr, EventNameType_XUL)) {
continue;
}
nsAutoString value;
GetAttr(kNameSpaceID_None, attr, value);
SetEventHandler(attr, value, true);
}
}
bool
nsXULElement::IsEventAttributeNameInternal(nsAtom *aName)
{
return nsContentUtils::IsEventAttributeName(aName, EventNameType_XUL);
}
JSObject*
nsXULElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
{
return dom::XULElement_Binding::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_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, nsINode::ATTRIBUTE_NODE);
NS_ASSERTION(ni, "the nodeinfo should already exist");
} else {
ni = mAttributes[i].mName.NodeInfo();
}
index = aNodeInfos->IndexOf(ni);
NS_ASSERTION(index >= 0, "unknown mozilla::dom::NodeInfo index");
tmp = aStream->Write32(index);
if (NS_FAILED(tmp)) {
rv = tmp;
}
mAttributes[i].mValue.ToString(attributeValue);
tmp = aStream->WriteWStringZ(attributeValue.get());
if (NS_FAILED(tmp)) {
rv = tmp;
}
}
// Now write children
tmp = aStream->Write32(uint32_t(mChildren.Length()));
if (NS_FAILED(tmp)) {
rv = tmp;
}
for (i = 0; i < mChildren.Length(); i++) {
nsXULPrototypeNode* child = mChildren[i].get();
switch (child->mType) {
case eType_Element:
case eType_Text:
case eType_PI:
tmp = child->Serialize(aStream, aProtoDoc, aNodeInfos);
if (NS_FAILED(tmp)) {
rv = tmp;
}
break;
case eType_Script:
tmp = aStream->Write32(child->mType);
if (NS_FAILED(tmp)) {
rv = tmp;
}
nsXULPrototypeScript* script = static_cast<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)
{
MOZ_ASSERT(aNodeInfos, "missing nodeinfo array");
// Read Node Info
uint32_t number = 0;
nsresult rv = aStream->Read32(&number);
if (NS_WARN_IF(NS_FAILED(rv))) return rv;
mNodeInfo = aNodeInfos->SafeElementAt(number, nullptr);
if (!mNodeInfo) {
return NS_ERROR_UNEXPECTED;
}
// Read Attributes
rv = aStream->Read32(&number);
if (NS_WARN_IF(NS_FAILED(rv))) return rv;
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);
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)
{
MOZ_ASSERT(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::is)) {
// Store is as atom.
mAttributes[aPos].mValue.ParseAtom(aValue);
mIsAtom = mAttributes[aPos].mValue.GetAtomValue();
return NS_OK;
} else if (mAttributes[aPos].mName.Equals(nsGkAtoms::_class)) {
mHasClassAttribute = true;
// Compute the element's class list
mAttributes[aPos].mValue.ParseAtomArray(aValue);
return NS_OK;
} else if (mAttributes[aPos].mName.Equals(nsGkAtoms::style)) {
mHasStyleAttribute = true;
// Parse the element's 'style' attribute
// This is basically duplicating what nsINode::NodePrincipal() does
nsIPrincipal* principal =
mNodeInfo->NodeInfoManager()->DocumentPrincipal();
// XXX Get correct Base URI (need GetBaseURI on *prototype* element)
// TODO: If we implement Content Security Policy for chrome documents
// as has been discussed, the CSP should be checked here to see if
// inline styles are allowed to be applied.
RefPtr<URLExtraData> data =
new URLExtraData(aDocumentURI, aDocumentURI, principal);
RefPtr<DeclarationBlock> declaration =
DeclarationBlock::FromCssText(
aValue, data, eCompatibility_FullStandards, nullptr);
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)
: nsXULPrototypeNode(eType_Script),
mLineNo(aLineNo),
mSrcLoading(false),
mOutOfLine(true),
mSrcLoadWaiters(nullptr),
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(0); // See bug 1418294.
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;
uint32_t dummy;
rv = aStream->Read32(&dummy); // See bug 1418294.
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;
JS::OffThreadToken* mToken;
public:
NotifyOffThreadScriptCompletedRunnable(nsIOffThreadScriptReceiver* aReceiver,
JS::OffThreadToken* aToken)
: mozilla::Runnable("NotifyOffThreadScriptCompletedRunnable")
, 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(JS::OffThreadToken* 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!
JS::CompileOptions options(cx);
options.setIntroductionType("scriptElement")
.setFileAndLine(urlspec.get(), aLineNo);
// 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;
}