Bug 1729121 [wpt PR 30345] - Improve media queries JS test, a=testonly
[gecko.git] / dom / xul / nsXULElement.cpp
blobaafc25d64323122b94d7a6280a26bf88923ed1dc
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsXULElement.h"
8 #include <new>
9 #include <utility>
10 #include "AttrArray.h"
11 #include "MainThreadUtils.h"
12 #include "ReferrerInfo.h"
13 #include "Units.h"
14 #include "XULFrameElement.h"
15 #include "XULMenuElement.h"
16 #include "XULPopupElement.h"
17 #include "XULTextElement.h"
18 #include "XULTooltipElement.h"
19 #include "XULTreeElement.h"
20 #include "js/CompilationAndEvaluation.h"
21 #include "js/CompileOptions.h"
22 #include "js/experimental/JSStencil.h"
23 #include "js/OffThreadScriptCompilation.h"
24 #include "js/SourceText.h"
25 #include "js/Transcoding.h"
26 #include "js/Utility.h"
27 #include "jsapi.h"
28 #include "mozilla/Assertions.h"
29 #include "mozilla/ArrayIterator.h"
30 #include "mozilla/ClearOnShutdown.h"
31 #include "mozilla/DeclarationBlock.h"
32 #include "mozilla/EventDispatcher.h"
33 #include "mozilla/EventListenerManager.h"
34 #include "mozilla/EventStateManager.h"
35 #include "mozilla/FlushType.h"
36 #include "mozilla/GlobalKeyListener.h"
37 #include "mozilla/HoldDropJSObjects.h"
38 #include "mozilla/MacroForEach.h"
39 #include "mozilla/Maybe.h"
40 #include "mozilla/MouseEvents.h"
41 #include "mozilla/OwningNonNull.h"
42 #include "mozilla/PresShell.h"
43 #include "mozilla/RefPtr.h"
44 #include "mozilla/ScopeExit.h"
45 #include "mozilla/StaticAnalysisFunctions.h"
46 #include "mozilla/StaticPtr.h"
47 #include "mozilla/URLExtraData.h"
48 #include "mozilla/dom/BindContext.h"
49 #include "mozilla/dom/BorrowedAttrInfo.h"
50 #include "mozilla/dom/CSSRuleBinding.h"
51 #include "mozilla/dom/Document.h"
52 #include "mozilla/dom/DocumentInlines.h"
53 #include "mozilla/dom/Element.h"
54 #include "mozilla/dom/Event.h"
55 #include "mozilla/dom/EventTarget.h"
56 #include "mozilla/dom/FragmentOrElement.h"
57 #include "mozilla/dom/FromParser.h"
58 #include "mozilla/dom/MouseEventBinding.h"
59 #include "mozilla/dom/MutationEventBinding.h"
60 #include "mozilla/dom/NodeInfo.h"
61 #include "mozilla/dom/ReferrerPolicyBinding.h"
62 #include "mozilla/dom/ScriptSettings.h"
63 #include "mozilla/dom/XULBroadcastManager.h"
64 #include "mozilla/dom/XULCommandEvent.h"
65 #include "mozilla/dom/XULElementBinding.h"
66 #include "mozilla/dom/nsCSPUtils.h"
67 #include "mozilla/fallible.h"
68 #include "nsAtom.h"
69 #include "nsAttrValueInlines.h"
70 #include "nsAttrValueOrString.h"
71 #include "nsCaseTreatment.h"
72 #include "nsChangeHint.h"
73 #include "nsCOMPtr.h"
74 #include "nsCompatibility.h"
75 #include "nsContentCreatorFunctions.h"
76 #include "nsContentUtils.h"
77 #include "nsCycleCollectionNoteChild.h"
78 #include "nsCycleCollectionTraversalCallback.h"
79 #include "nsDebug.h"
80 #include "nsError.h"
81 #include "nsFocusManager.h"
82 #include "nsGkAtoms.h"
83 #include "nsIContent.h"
84 #include "nsIContentSecurityPolicy.h"
85 #include "nsIControllers.h"
86 #include "nsID.h"
87 #include "nsIDOMEventListener.h"
88 #include "nsIDOMXULControlElement.h"
89 #include "nsIDOMXULSelectCntrlItemEl.h"
90 #include "nsIDocShell.h"
91 #include "nsIFocusManager.h"
92 #include "nsIFrame.h"
93 #include "nsIObjectInputStream.h"
94 #include "nsIObjectOutputStream.h"
95 #include "nsIRunnable.h"
96 #include "nsIScriptContext.h"
97 #include "nsISupportsUtils.h"
98 #include "nsIURI.h"
99 #include "nsIXPConnect.h"
100 #include "nsMenuFrame.h"
101 #include "nsMenuPopupFrame.h"
102 #include "nsNodeInfoManager.h"
103 #include "nsPIDOMWindow.h"
104 #include "nsPIDOMWindowInlines.h"
105 #include "nsPresContext.h"
106 #include "nsQueryFrame.h"
107 #include "nsString.h"
108 #include "nsStyledElement.h"
109 #include "nsThreadUtils.h"
110 #include "nsXULControllers.h"
111 #include "nsXULPopupListener.h"
112 #include "nsXULPopupManager.h"
113 #include "nsXULPrototypeCache.h"
114 #include "nsXULTooltipListener.h"
115 #include "xpcpublic.h"
117 using namespace mozilla;
118 using namespace mozilla::dom;
120 #ifdef XUL_PROTOTYPE_ATTRIBUTE_METERING
121 uint32_t nsXULPrototypeAttribute::gNumElements;
122 uint32_t nsXULPrototypeAttribute::gNumAttributes;
123 uint32_t nsXULPrototypeAttribute::gNumCacheTests;
124 uint32_t nsXULPrototypeAttribute::gNumCacheHits;
125 uint32_t nsXULPrototypeAttribute::gNumCacheSets;
126 uint32_t nsXULPrototypeAttribute::gNumCacheFills;
127 #endif
129 #define NS_DISPATCH_XUL_COMMAND (1 << 0)
131 //----------------------------------------------------------------------
132 // nsXULElement
135 nsXULElement::nsXULElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
136 : nsStyledElement(std::move(aNodeInfo)) {
137 XUL_PROTOTYPE_ATTRIBUTE_METER(gNumElements);
140 nsXULElement::~nsXULElement() = default;
142 /* static */
143 nsXULElement* NS_NewBasicXULElement(
144 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) {
145 RefPtr<mozilla::dom::NodeInfo> nodeInfo(std::move(aNodeInfo));
146 auto* nim = nodeInfo->NodeInfoManager();
147 return new (nim) nsXULElement(nodeInfo.forget());
150 /* static */
151 nsXULElement* nsXULElement::Construct(
152 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) {
153 // NOTE: If you add elements here, you probably also want to change
154 // mozilla::dom::binding_detail::HTMLConstructor in BindingUtils.cpp to take
155 // them into account, otherwise you'll start getting "Illegal constructor"
156 // exceptions in chrome code.
157 RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo;
158 if (nodeInfo->Equals(nsGkAtoms::label) ||
159 nodeInfo->Equals(nsGkAtoms::description)) {
160 auto* nim = nodeInfo->NodeInfoManager();
161 return new (nim) XULTextElement(nodeInfo.forget());
164 if (nodeInfo->Equals(nsGkAtoms::menupopup) ||
165 nodeInfo->Equals(nsGkAtoms::popup) ||
166 nodeInfo->Equals(nsGkAtoms::panel)) {
167 return NS_NewXULPopupElement(nodeInfo.forget());
170 if (nodeInfo->Equals(nsGkAtoms::tooltip)) {
171 return NS_NewXULTooltipElement(nodeInfo.forget());
174 if (nodeInfo->Equals(nsGkAtoms::iframe) ||
175 nodeInfo->Equals(nsGkAtoms::browser) ||
176 nodeInfo->Equals(nsGkAtoms::editor)) {
177 auto* nim = nodeInfo->NodeInfoManager();
178 return new (nim) XULFrameElement(nodeInfo.forget());
181 if (nodeInfo->Equals(nsGkAtoms::menu) ||
182 nodeInfo->Equals(nsGkAtoms::menulist)) {
183 auto* nim = nodeInfo->NodeInfoManager();
184 return new (nim) XULMenuElement(nodeInfo.forget());
187 if (nodeInfo->Equals(nsGkAtoms::tree)) {
188 auto* nim = nodeInfo->NodeInfoManager();
189 return new (nim) XULTreeElement(nodeInfo.forget());
192 return NS_NewBasicXULElement(nodeInfo.forget());
195 /* static */
196 already_AddRefed<nsXULElement> nsXULElement::CreateFromPrototype(
197 nsXULPrototypeElement* aPrototype, mozilla::dom::NodeInfo* aNodeInfo,
198 bool aIsScriptable, bool aIsRoot) {
199 RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
200 nsCOMPtr<Element> baseElement;
201 NS_NewXULElement(getter_AddRefs(baseElement), ni.forget(),
202 dom::FROM_PARSER_NETWORK, aPrototype->mIsAtom);
204 if (baseElement) {
205 nsXULElement* element = FromNode(baseElement);
207 if (aPrototype->mHasIdAttribute) {
208 element->SetHasID();
210 if (aPrototype->mHasClassAttribute) {
211 element->SetMayHaveClass();
213 if (aPrototype->mHasStyleAttribute) {
214 element->SetMayHaveStyle();
217 element->MakeHeavyweight(aPrototype);
218 if (aIsScriptable) {
219 // Check each attribute on the prototype to see if we need to do
220 // any additional processing and hookup that would otherwise be
221 // done 'automagically' by SetAttr().
222 for (const auto& attribute : aPrototype->mAttributes) {
223 element->AddListenerForAttributeIfNeeded(attribute.mName);
227 return baseElement.forget().downcast<nsXULElement>();
230 return nullptr;
233 nsresult nsXULElement::CreateFromPrototype(nsXULPrototypeElement* aPrototype,
234 Document* aDocument,
235 bool aIsScriptable, bool aIsRoot,
236 Element** aResult) {
237 // Create an nsXULElement from a prototype
238 MOZ_ASSERT(aPrototype != nullptr, "null ptr");
239 if (!aPrototype) return NS_ERROR_NULL_POINTER;
241 MOZ_ASSERT(aResult != nullptr, "null ptr");
242 if (!aResult) return NS_ERROR_NULL_POINTER;
244 RefPtr<mozilla::dom::NodeInfo> nodeInfo;
245 if (aDocument) {
246 mozilla::dom::NodeInfo* ni = aPrototype->mNodeInfo;
247 nodeInfo = aDocument->NodeInfoManager()->GetNodeInfo(
248 ni->NameAtom(), ni->GetPrefixAtom(), ni->NamespaceID(), ELEMENT_NODE);
249 } else {
250 nodeInfo = aPrototype->mNodeInfo;
253 RefPtr<nsXULElement> element =
254 CreateFromPrototype(aPrototype, nodeInfo, aIsScriptable, aIsRoot);
255 element.forget(aResult);
257 return NS_OK;
260 nsresult NS_NewXULElement(Element** aResult,
261 already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo,
262 FromParser aFromParser, nsAtom* aIsAtom,
263 mozilla::dom::CustomElementDefinition* aDefinition) {
264 RefPtr<mozilla::dom::NodeInfo> nodeInfo = aNodeInfo;
266 MOZ_ASSERT(nodeInfo, "need nodeinfo for non-proto Create");
268 NS_ASSERTION(
269 nodeInfo->NamespaceEquals(kNameSpaceID_XUL),
270 "Trying to create XUL elements that don't have the XUL namespace");
272 Document* doc = nodeInfo->GetDocument();
273 if (doc && !doc->AllowXULXBL()) {
274 return NS_ERROR_NOT_AVAILABLE;
277 return nsContentUtils::NewXULOrHTMLElement(aResult, nodeInfo, aFromParser,
278 aIsAtom, aDefinition);
281 void NS_TrustedNewXULElement(
282 Element** aResult, already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo) {
283 RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
284 MOZ_ASSERT(ni, "need nodeinfo for non-proto Create");
286 // Create an nsXULElement with the specified namespace and tag.
287 NS_ADDREF(*aResult = nsXULElement::Construct(ni.forget()));
290 //----------------------------------------------------------------------
291 // nsISupports interface
293 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsXULElement, nsStyledElement)
295 NS_IMPL_ADDREF_INHERITED(nsXULElement, nsStyledElement)
296 NS_IMPL_RELEASE_INHERITED(nsXULElement, nsStyledElement)
298 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsXULElement)
299 NS_ELEMENT_INTERFACE_TABLE_TO_MAP_SEGUE
300 NS_INTERFACE_MAP_END_INHERITING(nsStyledElement)
302 //----------------------------------------------------------------------
303 // nsINode interface
305 nsresult nsXULElement::Clone(mozilla::dom::NodeInfo* aNodeInfo,
306 nsINode** aResult) const {
307 *aResult = nullptr;
309 RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
310 RefPtr<nsXULElement> element = Construct(ni.forget());
312 nsresult rv = const_cast<nsXULElement*>(this)->CopyInnerTo(
313 element, ReparseAttributes::No);
314 NS_ENSURE_SUCCESS(rv, rv);
316 // Note that we're _not_ copying mControllers.
318 element.forget(aResult);
319 return rv;
322 //----------------------------------------------------------------------
324 EventListenerManager* nsXULElement::GetEventListenerManagerForAttr(
325 nsAtom* aAttrName, bool* aDefer) {
326 // XXXbz sXBL/XBL2 issue: should we instead use GetComposedDoc()
327 // here, override BindToTree for those classes and munge event
328 // listeners there?
329 Document* doc = OwnerDoc();
331 nsPIDOMWindowInner* window;
332 Element* root = doc->GetRootElement();
333 if ((!root || root == this) && (window = doc->GetInnerWindow())) {
334 nsCOMPtr<EventTarget> piTarget = do_QueryInterface(window);
336 *aDefer = false;
337 return piTarget->GetOrCreateListenerManager();
340 return nsStyledElement::GetEventListenerManagerForAttr(aAttrName, aDefer);
343 // returns true if the element is not a list
344 static bool IsNonList(mozilla::dom::NodeInfo* aNodeInfo) {
345 return !aNodeInfo->Equals(nsGkAtoms::tree) &&
346 !aNodeInfo->Equals(nsGkAtoms::richlistbox);
349 bool nsXULElement::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) {
351 * Returns true if an element may be focused, and false otherwise. The inout
352 * argument aTabIndex will be set to the tab order index to be used; -1 for
353 * elements that should not be part of the tab order and a greater value to
354 * indicate its tab order.
356 * Confusingly, the supplied value for the aTabIndex argument may indicate
357 * whether the element may be focused as a result of the -moz-user-focus
358 * property, where -1 means no and 0 means yes.
360 * For controls, the element cannot be focused and is not part of the tab
361 * order if it is disabled.
363 * -moz-user-focus is overridden if a tabindex (even -1) is specified.
365 * Specifically, the behaviour for all XUL elements is as follows:
366 * *aTabIndex = -1 no tabindex Not focusable or tabbable
367 * *aTabIndex = -1 tabindex="-1" Focusable but not tabbable
368 * *aTabIndex = -1 tabindex=">=0" Focusable and tabbable
369 * *aTabIndex >= 0 no tabindex Focusable and tabbable
370 * *aTabIndex >= 0 tabindex="-1" Focusable but not tabbable
371 * *aTabIndex >= 0 tabindex=">=0" Focusable and tabbable
373 * If aTabIndex is null, then the tabindex is not computed, and
374 * true is returned for non-disabled controls and false otherwise.
377 // elements are not focusable by default
378 bool shouldFocus = false;
380 #ifdef XP_MACOSX
381 // on Mac, mouse interactions only focus the element if it's a list,
382 // or if it's a remote target, since the remote target must handle
383 // the focus.
384 if (aWithMouse && IsNonList(mNodeInfo) &&
385 !EventStateManager::IsTopLevelRemoteTarget(this)) {
386 return false;
388 #endif
390 nsCOMPtr<nsIDOMXULControlElement> xulControl = AsXULControl();
391 if (xulControl) {
392 // a disabled element cannot be focused and is not part of the tab order
393 bool disabled;
394 xulControl->GetDisabled(&disabled);
395 if (disabled) {
396 if (aTabIndex) *aTabIndex = -1;
397 return false;
399 shouldFocus = true;
402 if (aTabIndex) {
403 Maybe<int32_t> attrVal = GetTabIndexAttrValue();
404 if (attrVal.isSome()) {
405 // The tabindex attribute was specified, so the element becomes
406 // focusable.
407 shouldFocus = true;
408 *aTabIndex = attrVal.value();
409 } else {
410 // otherwise, if there is no tabindex attribute, just use the value of
411 // *aTabIndex to indicate focusability. Reset any supplied tabindex to 0.
412 shouldFocus = *aTabIndex >= 0;
413 if (shouldFocus) {
414 *aTabIndex = 0;
418 if (xulControl && shouldFocus && sTabFocusModelAppliesToXUL &&
419 !(sTabFocusModel & eTabFocus_formElementsMask)) {
420 // By default, the tab focus model doesn't apply to xul element on any
421 // system but OS X. on OS X we're following it for UI elements (XUL) as
422 // sTabFocusModel is based on "Full Keyboard Access" system setting (see
423 // mac/nsILookAndFeel). both textboxes and list elements (i.e. trees and
424 // list) should always be focusable (textboxes are handled as html:input)
425 // For compatibility, we only do this for controls, otherwise elements
426 // like <browser> cannot take this focus.
427 if (IsNonList(mNodeInfo)) {
428 *aTabIndex = -1;
433 return shouldFocus;
436 int32_t nsXULElement::ScreenX() {
437 nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
438 return frame ? frame->GetScreenRect().x : 0;
441 int32_t nsXULElement::ScreenY() {
442 nsIFrame* frame = GetPrimaryFrame(FlushType::Layout);
443 return frame ? frame->GetScreenRect().y : 0;
446 bool nsXULElement::HasMenu() {
447 nsMenuFrame* menu = do_QueryFrame(GetPrimaryFrame(FlushType::Frames));
448 return !!menu;
451 void nsXULElement::OpenMenu(bool aOpenFlag) {
452 // Flush frames first. It's not clear why this is needed, see bug 1704670.
453 if (Document* doc = GetComposedDoc()) {
454 doc->FlushPendingNotifications(FlushType::Frames);
457 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
458 if (pm) {
459 if (aOpenFlag) {
460 // Nothing will happen if this element isn't a menu.
461 pm->ShowMenu(this, false, false);
462 } else {
463 // Nothing will happen if this element isn't a menu.
464 pm->HideMenu(this);
469 Result<bool, nsresult> nsXULElement::PerformAccesskey(bool aKeyCausesActivation,
470 bool aIsTrustedEvent) {
471 if (IsXULElement(nsGkAtoms::label)) {
472 nsAutoString control;
473 GetAttr(kNameSpaceID_None, nsGkAtoms::control, control);
474 if (control.IsEmpty()) {
475 return Err(NS_ERROR_UNEXPECTED);
478 // XXXsmaug Should we use ShadowRoot::GetElementById in case
479 // element is in Shadow DOM?
480 RefPtr<Document> document = GetUncomposedDoc();
481 if (!document) {
482 return Err(NS_ERROR_UNEXPECTED);
485 RefPtr<Element> element = document->GetElementById(control);
486 if (!element) {
487 return Err(NS_ERROR_UNEXPECTED);
490 // XXXedgar, This is mainly for HTMLElement which doesn't do visible
491 // check in PerformAccesskey. We probably should always do visible
492 // check on HTMLElement even if the PerformAccesskey is not redirected from
493 // label XULelement per spec.
494 nsIFrame* frame = element->GetPrimaryFrame();
495 if (!frame || !frame->IsVisibleConsideringAncestors()) {
496 return Err(NS_ERROR_UNEXPECTED);
499 return element->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent);
502 nsIFrame* frame = GetPrimaryFrame();
503 if (!frame || !frame->IsVisibleConsideringAncestors()) {
504 return Err(NS_ERROR_UNEXPECTED);
507 bool focused = false;
508 // Define behavior for each type of XUL element.
509 if (!IsXULElement(nsGkAtoms::toolbarbutton)) {
510 if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
511 RefPtr<Element> elementToFocus = this;
512 // for radio buttons, focus the radiogroup instead
513 if (IsXULElement(nsGkAtoms::radio)) {
514 if (nsCOMPtr<nsIDOMXULSelectControlItemElement> controlItem =
515 AsXULSelectControlItem()) {
516 bool disabled;
517 controlItem->GetDisabled(&disabled);
518 if (!disabled) {
519 controlItem->GetControl(getter_AddRefs(elementToFocus));
524 if (elementToFocus) {
525 fm->SetFocus(elementToFocus, nsIFocusManager::FLAG_BYKEY);
527 // Return true if the element became focused.
528 nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
529 focused = (window && window->GetFocusedElement() == elementToFocus);
534 if (aKeyCausesActivation && !IsXULElement(nsGkAtoms::menulist)) {
535 ClickWithInputSource(MouseEvent_Binding::MOZ_SOURCE_KEYBOARD,
536 aIsTrustedEvent);
537 return focused;
540 // If the accesskey won't cause the activation and the focus isn't changed,
541 // either. Return error so EventStateManager would try to find next element
542 // to handle the accesskey.
543 return focused ? Result<bool, nsresult>{focused} : Err(NS_ERROR_ABORT);
546 //----------------------------------------------------------------------
548 void nsXULElement::AddListenerForAttributeIfNeeded(nsAtom* aLocalName) {
549 // If appropriate, add a popup listener and/or compile the event
550 // handler. Called when we change the element's document, create a
551 // new element, change an attribute's value, etc.
552 // Eventlistenener-attributes are always in the null namespace.
553 if (aLocalName == nsGkAtoms::menu || aLocalName == nsGkAtoms::contextmenu ||
554 // XXXdwh popup and context are deprecated
555 aLocalName == nsGkAtoms::popup || aLocalName == nsGkAtoms::context) {
556 AddPopupListener(aLocalName);
558 if (nsContentUtils::IsEventAttributeName(aLocalName, EventNameType_XUL)) {
559 nsAutoString value;
560 GetAttr(kNameSpaceID_None, aLocalName, value);
561 SetEventHandler(aLocalName, value, true);
565 void nsXULElement::AddListenerForAttributeIfNeeded(const nsAttrName& aName) {
566 if (aName.IsAtom()) {
567 AddListenerForAttributeIfNeeded(aName.Atom());
571 //----------------------------------------------------------------------
573 // nsIContent interface
575 void nsXULElement::UpdateEditableState(bool aNotify) {
576 // Don't call through to Element here because the things
577 // it does don't work for cases when we're an editable control.
578 nsIContent* parent = GetParent();
580 SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE));
581 UpdateState(aNotify);
584 class XULInContentErrorReporter : public Runnable {
585 public:
586 explicit XULInContentErrorReporter(Document& aDocument)
587 : mozilla::Runnable("XULInContentErrorReporter"), mDocument(aDocument) {}
589 NS_IMETHOD Run() override {
590 mDocument->WarnOnceAbout(DeprecatedOperations::eImportXULIntoContent,
591 false);
592 return NS_OK;
595 private:
596 OwningNonNull<Document> mDocument;
599 static bool NeedTooltipSupport(const nsXULElement& aXULElement) {
600 if (aXULElement.NodeInfo()->Equals(nsGkAtoms::treechildren)) {
601 // treechildren always get tooltip support, since cropped tree cells show
602 // their full text in a tooltip.
603 return true;
606 return aXULElement.GetBoolAttr(nsGkAtoms::tooltip) ||
607 aXULElement.GetBoolAttr(nsGkAtoms::tooltiptext);
610 nsresult nsXULElement::BindToTree(BindContext& aContext, nsINode& aParent) {
611 nsresult rv = nsStyledElement::BindToTree(aContext, aParent);
612 NS_ENSURE_SUCCESS(rv, rv);
614 if (!IsInComposedDoc()) {
615 return rv;
618 Document& doc = aContext.OwnerDoc();
619 if (!IsInNativeAnonymousSubtree() && !doc.AllowXULXBL() &&
620 !doc.HasWarnedAbout(DeprecatedOperations::eImportXULIntoContent)) {
621 nsContentUtils::AddScriptRunner(new XULInContentErrorReporter(doc));
624 #ifdef DEBUG
625 if (!doc.AllowXULXBL() && !doc.IsUnstyledDocument()) {
626 // To save CPU cycles and memory, non-XUL documents only load the user
627 // agent style sheet rules for a minimal set of XUL elements such as
628 // 'scrollbar' that may be created implicitly for their content (those
629 // rules being in minimal-xul.css).
631 // This assertion makes sure no other XUL element is used in a non-XUL
632 // document.
633 nsAtom* tag = NodeInfo()->NameAtom();
634 MOZ_ASSERT(
635 // scrollbar parts
636 tag == nsGkAtoms::scrollbar || tag == nsGkAtoms::scrollbarbutton ||
637 tag == nsGkAtoms::scrollcorner || tag == nsGkAtoms::slider ||
638 tag == nsGkAtoms::thumb ||
639 // other
640 tag == nsGkAtoms::resizer || tag == nsGkAtoms::label,
641 "Unexpected XUL element in non-XUL doc");
643 #endif
645 // Within Bug 1492063 and its dependencies we started to apply a
646 // CSP to system privileged about pages. Since some about: pages
647 // are implemented in *.xul files we added this workaround to
648 // apply a CSP to them. To do so, we check the introduced custom
649 // attribute 'csp' on the root element.
650 if (doc.GetRootElement() == this) {
651 nsAutoString cspPolicyStr;
652 GetAttr(kNameSpaceID_None, nsGkAtoms::csp, cspPolicyStr);
654 #ifdef DEBUG
656 nsCOMPtr<nsIContentSecurityPolicy> docCSP = doc.GetCsp();
657 uint32_t policyCount = 0;
658 if (docCSP) {
659 docCSP->GetPolicyCount(&policyCount);
661 MOZ_ASSERT(policyCount == 0, "how come we already have a policy?");
663 #endif
665 CSP_ApplyMetaCSPToDoc(doc, cspPolicyStr);
668 if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
669 // Create our XUL key listener and hook it up.
670 XULKeySetGlobalKeyListener::AttachKeyHandler(this);
673 RegUnRegAccessKey(true);
675 if (NeedTooltipSupport(*this)) {
676 AddTooltipSupport();
679 if (XULBroadcastManager::MayNeedListener(*this)) {
680 if (!doc.HasXULBroadcastManager()) {
681 doc.InitializeXULBroadcastManager();
683 XULBroadcastManager* broadcastManager = doc.GetXULBroadcastManager();
684 broadcastManager->AddListener(this);
686 return rv;
689 void nsXULElement::UnbindFromTree(bool aNullParent) {
690 if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
691 XULKeySetGlobalKeyListener::DetachKeyHandler(this);
694 RegUnRegAccessKey(false);
696 if (NeedTooltipSupport(*this)) {
697 RemoveTooltipSupport();
700 Document* doc = GetComposedDoc();
701 if (doc && doc->HasXULBroadcastManager() &&
702 XULBroadcastManager::MayNeedListener(*this)) {
703 RefPtr<XULBroadcastManager> broadcastManager =
704 doc->GetXULBroadcastManager();
705 broadcastManager->RemoveListener(this);
708 // mControllers can own objects that are implemented
709 // in JavaScript (such as some implementations of
710 // nsIControllers. These objects prevent their global
711 // object's script object from being garbage collected,
712 // which means JS continues to hold an owning reference
713 // to the nsGlobalWindow, which owns the document,
714 // which owns this content. That's a cycle, so we break
715 // it here. (It might be better to break this by releasing
716 // mDocument in nsGlobalWindow::SetDocShell, but I'm not
717 // sure whether that would fix all possible cycles through
718 // mControllers.)
719 nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
720 if (slots) {
721 slots->mControllers = nullptr;
724 nsStyledElement::UnbindFromTree(aNullParent);
727 void nsXULElement::DoneAddingChildren(bool aHaveNotified) {
728 if (IsXULElement(nsGkAtoms::linkset)) {
729 Document* doc = GetComposedDoc();
730 if (doc) {
731 doc->OnL10nResourceContainerParsed();
736 void nsXULElement::RegUnRegAccessKey(bool aDoReg) {
737 // Don't try to register for unsupported elements
738 if (!SupportsAccessKey()) {
739 return;
742 nsStyledElement::RegUnRegAccessKey(aDoReg);
745 bool nsXULElement::SupportsAccessKey() const {
746 if (NodeInfo()->Equals(nsGkAtoms::label) && HasAttr(nsGkAtoms::control)) {
747 return true;
750 // XXX(ntim): check if description[value] or description[accesskey] are
751 // actually used, remove `value` from {Before/After}SetAttr if not the case
752 if (NodeInfo()->Equals(nsGkAtoms::description) && HasAttr(nsGkAtoms::value) &&
753 HasAttr(nsGkAtoms::control)) {
754 return true;
757 return IsAnyOfXULElements(nsGkAtoms::button, nsGkAtoms::toolbarbutton,
758 nsGkAtoms::checkbox, nsGkAtoms::tab,
759 nsGkAtoms::radio);
762 nsresult nsXULElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
763 const nsAttrValueOrString* aValue,
764 bool aNotify) {
765 if (aNamespaceID == kNameSpaceID_None &&
766 (aName == nsGkAtoms::accesskey || aName == nsGkAtoms::control ||
767 aName == nsGkAtoms::value)) {
768 RegUnRegAccessKey(false);
769 } else if (aNamespaceID == kNameSpaceID_None &&
770 (aName == nsGkAtoms::command || aName == nsGkAtoms::observes) &&
771 IsInUncomposedDoc()) {
772 // XXX sXBL/XBL2 issue! Owner or current document?
773 // XXX Why does this not also remove broadcast listeners if the
774 // "element" attribute was changed on an <observer>?
775 nsAutoString oldValue;
776 GetAttr(kNameSpaceID_None, nsGkAtoms::observes, oldValue);
777 if (oldValue.IsEmpty()) {
778 GetAttr(kNameSpaceID_None, nsGkAtoms::command, oldValue);
781 Document* doc = GetUncomposedDoc();
782 if (!oldValue.IsEmpty() && doc->HasXULBroadcastManager()) {
783 RefPtr<XULBroadcastManager> broadcastManager =
784 doc->GetXULBroadcastManager();
785 broadcastManager->RemoveListener(this);
787 } else if (aNamespaceID == kNameSpaceID_None && aValue &&
788 mNodeInfo->Equals(nsGkAtoms::window) &&
789 aName == nsGkAtoms::chromemargin) {
790 nsAttrValue attrValue;
791 // Make sure the margin format is valid first
792 if (!attrValue.ParseIntMarginValue(aValue->String())) {
793 return NS_ERROR_INVALID_ARG;
795 } else if (aNamespaceID == kNameSpaceID_None &&
796 aName == nsGkAtoms::usercontextid) {
797 nsAutoString oldValue;
798 bool hasAttribute =
799 GetAttr(kNameSpaceID_None, nsGkAtoms::usercontextid, oldValue);
800 if (hasAttribute && (!aValue || !aValue->String().Equals(oldValue))) {
801 MOZ_ASSERT(false, "Changing usercontextid is not allowed.");
802 return NS_ERROR_INVALID_ARG;
806 return nsStyledElement::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify);
809 nsresult nsXULElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
810 const nsAttrValue* aValue,
811 const nsAttrValue* aOldValue,
812 nsIPrincipal* aSubjectPrincipal,
813 bool aNotify) {
814 if (aNamespaceID == kNameSpaceID_None) {
815 if (aValue) {
816 AddListenerForAttributeIfNeeded(aName);
819 if (aName == nsGkAtoms::accesskey || aName == nsGkAtoms::control ||
820 aName == nsGkAtoms::value) {
821 RegUnRegAccessKey(true);
822 } else if (aName == nsGkAtoms::tooltip || aName == nsGkAtoms::tooltiptext) {
823 if (!!aValue != !!aOldValue && IsInComposedDoc() &&
824 !NodeInfo()->Equals(nsGkAtoms::treechildren)) {
825 if (aValue) {
826 AddTooltipSupport();
827 } else {
828 RemoveTooltipSupport();
832 Document* doc = GetComposedDoc();
833 if (doc && doc->HasXULBroadcastManager()) {
834 RefPtr<XULBroadcastManager> broadcastManager =
835 doc->GetXULBroadcastManager();
836 broadcastManager->AttributeChanged(this, aNamespaceID, aName);
838 if (doc && XULBroadcastManager::MayNeedListener(*this)) {
839 if (!doc->HasXULBroadcastManager()) {
840 doc->InitializeXULBroadcastManager();
842 XULBroadcastManager* broadcastManager = doc->GetXULBroadcastManager();
843 broadcastManager->AddListener(this);
846 // XXX need to check if they're changing an event handler: if
847 // so, then we need to unhook the old one. Or something.
850 return nsStyledElement::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue,
851 aSubjectPrincipal, aNotify);
854 void nsXULElement::AddTooltipSupport() {
855 nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance();
856 if (!listener) {
857 return;
860 listener->AddTooltipSupport(this);
863 void nsXULElement::RemoveTooltipSupport() {
864 nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance();
865 if (!listener) {
866 return;
869 listener->RemoveTooltipSupport(this);
872 bool nsXULElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
873 const nsAString& aValue,
874 nsIPrincipal* aMaybeScriptedPrincipal,
875 nsAttrValue& aResult) {
876 if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::tabindex) {
877 return aResult.ParseIntValue(aValue);
880 // Parse into a nsAttrValue
881 if (!nsStyledElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
882 aMaybeScriptedPrincipal, aResult)) {
883 // Fall back to parsing as atom for short values
884 aResult.ParseStringOrAtom(aValue);
887 return true;
890 void nsXULElement::DestroyContent() {
891 nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
892 if (slots) {
893 slots->mControllers = nullptr;
896 nsStyledElement::DestroyContent();
899 #ifdef MOZ_DOM_LIST
900 void nsXULElement::List(FILE* out, int32_t aIndent) const {
901 nsCString prefix("XUL");
902 if (HasSlots()) {
903 prefix.Append('*');
905 prefix.Append(' ');
907 nsStyledElement::List(out, aIndent, prefix);
909 #endif
911 bool nsXULElement::IsEventStoppedFromAnonymousScrollbar(EventMessage aMessage) {
912 return (IsRootOfNativeAnonymousSubtree() &&
913 IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::scrollcorner) &&
914 (aMessage == eMouseClick || aMessage == eMouseDoubleClick ||
915 aMessage == eXULCommand || aMessage == eContextMenu ||
916 aMessage == eDragStart || aMessage == eMouseAuxClick));
919 nsresult nsXULElement::DispatchXULCommand(const EventChainVisitor& aVisitor,
920 nsAutoString& aCommand) {
921 // XXX sXBL/XBL2 issue! Owner or current document?
922 nsCOMPtr<Document> doc = GetUncomposedDoc();
923 NS_ENSURE_STATE(doc);
924 RefPtr<Element> commandElt = doc->GetElementById(aCommand);
925 if (commandElt) {
926 // Create a new command event to dispatch to the element
927 // pointed to by the command attribute. The new event's
928 // sourceEvent will be the original command event that we're
929 // handling.
930 RefPtr<Event> event = aVisitor.mDOMEvent;
931 uint16_t inputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
932 int16_t button = 0;
933 while (event) {
934 NS_ENSURE_STATE(event->GetOriginalTarget() != commandElt);
935 RefPtr<XULCommandEvent> commandEvent = event->AsXULCommandEvent();
936 if (commandEvent) {
937 event = commandEvent->GetSourceEvent();
938 inputSource = commandEvent->InputSource();
939 button = commandEvent->Button();
940 } else {
941 event = nullptr;
944 WidgetInputEvent* orig = aVisitor.mEvent->AsInputEvent();
945 nsContentUtils::DispatchXULCommand(
946 commandElt, orig->IsTrusted(), MOZ_KnownLive(aVisitor.mDOMEvent),
947 nullptr, orig->IsControl(), orig->IsAlt(), orig->IsShift(),
948 orig->IsMeta(), inputSource, button);
949 } else {
950 NS_WARNING("A XUL element is attached to a command that doesn't exist!\n");
952 return NS_OK;
955 void nsXULElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
956 aVisitor.mForceContentDispatch = true; // FIXME! Bug 329119
957 if (IsEventStoppedFromAnonymousScrollbar(aVisitor.mEvent->mMessage)) {
958 // Don't propagate these events from native anonymous scrollbar.
959 aVisitor.mCanHandle = true;
960 aVisitor.SetParentTarget(nullptr, false);
961 return;
963 if (aVisitor.mEvent->mMessage == eXULCommand &&
964 aVisitor.mEvent->mClass == eInputEventClass &&
965 aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) &&
966 !IsXULElement(nsGkAtoms::command)) {
967 // Check that we really have an xul command event. That will be handled
968 // in a special way.
969 // See if we have a command elt. If so, we execute on the command
970 // instead of on our content element.
971 if (aVisitor.mDOMEvent && aVisitor.mDOMEvent->AsXULCommandEvent() &&
972 HasNonEmptyAttr(nsGkAtoms::command)) {
973 // Stop building the event target chain for the original event.
974 // We don't want it to propagate to any DOM nodes.
975 aVisitor.mCanHandle = false;
976 aVisitor.mAutomaticChromeDispatch = false;
977 // Dispatch XUL command in PreHandleEvent to prevent it breaks event
978 // target chain creation
979 aVisitor.mWantsPreHandleEvent = true;
980 aVisitor.mItemFlags |= NS_DISPATCH_XUL_COMMAND;
981 return;
985 nsStyledElement::GetEventTargetParent(aVisitor);
988 nsresult nsXULElement::PreHandleEvent(EventChainVisitor& aVisitor) {
989 if (aVisitor.mItemFlags & NS_DISPATCH_XUL_COMMAND) {
990 nsAutoString command;
991 GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
992 MOZ_ASSERT(!command.IsEmpty());
993 return DispatchXULCommand(aVisitor, command);
995 return nsStyledElement::PreHandleEvent(aVisitor);
998 //----------------------------------------------------------------------
999 // Implementation methods
1001 nsChangeHint nsXULElement::GetAttributeChangeHint(const nsAtom* aAttribute,
1002 int32_t aModType) const {
1003 if (aAttribute == nsGkAtoms::value &&
1004 (aModType == MutationEvent_Binding::REMOVAL ||
1005 aModType == MutationEvent_Binding::ADDITION) &&
1006 IsAnyOfXULElements(nsGkAtoms::label, nsGkAtoms::description)) {
1007 // Label and description dynamically morph between a normal
1008 // block and a cropping single-line XUL text frame. If the
1009 // value attribute is being added or removed, then we need to
1010 // return a hint of frame change. (See bugzilla bug 95475 for
1011 // details.)
1012 return nsChangeHint_ReconstructFrame;
1015 if (aAttribute == nsGkAtoms::type &&
1016 IsAnyOfXULElements(nsGkAtoms::toolbarbutton, nsGkAtoms::button)) {
1017 // type=menu switches from a button frame to a menu frame.
1018 return nsChangeHint_ReconstructFrame;
1021 return nsChangeHint(0);
1024 NS_IMETHODIMP_(bool)
1025 nsXULElement::IsAttributeMapped(const nsAtom* aAttribute) const {
1026 return false;
1029 nsIControllers* nsXULElement::GetControllers(ErrorResult& rv) {
1030 if (!Controllers()) {
1031 nsExtendedDOMSlots* slots = ExtendedDOMSlots();
1033 slots->mControllers = new nsXULControllers();
1036 return Controllers();
1039 void nsXULElement::Click(CallerType aCallerType) {
1040 ClickWithInputSource(MouseEvent_Binding::MOZ_SOURCE_UNKNOWN,
1041 aCallerType == CallerType::System);
1044 void nsXULElement::ClickWithInputSource(uint16_t aInputSource,
1045 bool aIsTrustedEvent) {
1046 if (BoolAttrIsTrue(nsGkAtoms::disabled)) return;
1048 nsCOMPtr<Document> doc = GetComposedDoc(); // Strong just in case
1049 if (doc) {
1050 RefPtr<nsPresContext> context = doc->GetPresContext();
1051 if (context) {
1052 // strong ref to PresContext so events don't destroy it
1054 WidgetMouseEvent eventDown(aIsTrustedEvent, eMouseDown, nullptr,
1055 WidgetMouseEvent::eReal);
1056 WidgetMouseEvent eventUp(aIsTrustedEvent, eMouseUp, nullptr,
1057 WidgetMouseEvent::eReal);
1058 WidgetMouseEvent eventClick(aIsTrustedEvent, eMouseClick, nullptr,
1059 WidgetMouseEvent::eReal);
1060 eventDown.mInputSource = eventUp.mInputSource = eventClick.mInputSource =
1061 aInputSource;
1063 // send mouse down
1064 nsEventStatus status = nsEventStatus_eIgnore;
1065 EventDispatcher::Dispatch(static_cast<nsIContent*>(this), context,
1066 &eventDown, nullptr, &status);
1068 // send mouse up
1069 status = nsEventStatus_eIgnore; // reset status
1070 EventDispatcher::Dispatch(static_cast<nsIContent*>(this), context,
1071 &eventUp, nullptr, &status);
1073 // send mouse click
1074 status = nsEventStatus_eIgnore; // reset status
1075 EventDispatcher::Dispatch(static_cast<nsIContent*>(this), context,
1076 &eventClick, nullptr, &status);
1078 // If the click has been prevented, lets skip the command call
1079 // this is how a physical click works
1080 if (status == nsEventStatus_eConsumeNoDefault) {
1081 return;
1086 // oncommand is fired when an element is clicked...
1087 DoCommand();
1090 void nsXULElement::DoCommand() {
1091 nsCOMPtr<Document> doc = GetComposedDoc(); // strong just in case
1092 if (doc) {
1093 RefPtr<nsXULElement> self = this;
1094 nsContentUtils::DispatchXULCommand(self, true);
1098 bool nsXULElement::IsNodeOfType(uint32_t aFlags) const { return false; }
1100 nsresult nsXULElement::AddPopupListener(nsAtom* aName) {
1101 // Add a popup listener to the element
1102 bool isContext =
1103 (aName == nsGkAtoms::context || aName == nsGkAtoms::contextmenu);
1104 uint32_t listenerFlag = isContext ? XUL_ELEMENT_HAS_CONTENTMENU_LISTENER
1105 : XUL_ELEMENT_HAS_POPUP_LISTENER;
1107 if (HasFlag(listenerFlag)) {
1108 return NS_OK;
1111 nsCOMPtr<nsIDOMEventListener> listener =
1112 new nsXULPopupListener(this, isContext);
1114 // Add the popup as a listener on this element.
1115 EventListenerManager* manager = GetOrCreateListenerManager();
1116 SetFlags(listenerFlag);
1118 if (isContext) {
1119 manager->AddEventListenerByType(listener, u"contextmenu"_ns,
1120 TrustedEventsAtSystemGroupBubble());
1121 } else {
1122 manager->AddEventListenerByType(listener, u"mousedown"_ns,
1123 TrustedEventsAtSystemGroupBubble());
1125 return NS_OK;
1128 //----------------------------------------------------------------------
1130 nsresult nsXULElement::MakeHeavyweight(nsXULPrototypeElement* aPrototype) {
1131 if (!aPrototype) {
1132 return NS_OK;
1135 size_t i;
1136 nsresult rv;
1137 for (i = 0; i < aPrototype->mAttributes.Length(); ++i) {
1138 nsXULPrototypeAttribute* protoattr = &aPrototype->mAttributes[i];
1139 nsAttrValue attrValue;
1141 // Style rules need to be cloned.
1142 if (protoattr->mValue.Type() == nsAttrValue::eCSSDeclaration) {
1143 DeclarationBlock* decl = protoattr->mValue.GetCSSDeclarationValue();
1144 RefPtr<DeclarationBlock> declClone = decl->Clone();
1146 nsString stringValue;
1147 protoattr->mValue.ToString(stringValue);
1149 attrValue.SetTo(declClone.forget(), &stringValue);
1150 } else {
1151 attrValue.SetTo(protoattr->mValue);
1154 bool oldValueSet;
1155 // XXX we might wanna have a SetAndTakeAttr that takes an nsAttrName
1156 if (protoattr->mName.IsAtom()) {
1157 rv = mAttrs.SetAndSwapAttr(protoattr->mName.Atom(), attrValue,
1158 &oldValueSet);
1159 } else {
1160 rv = mAttrs.SetAndSwapAttr(protoattr->mName.NodeInfo(), attrValue,
1161 &oldValueSet);
1163 NS_ENSURE_SUCCESS(rv, rv);
1165 return NS_OK;
1168 bool nsXULElement::BoolAttrIsTrue(nsAtom* aName) const {
1169 const nsAttrValue* attr = GetAttrInfo(kNameSpaceID_None, aName).mValue;
1171 return attr && attr->Type() == nsAttrValue::eAtom &&
1172 attr->GetAtomValue() == nsGkAtoms::_true;
1175 void nsXULElement::RecompileScriptEventListeners() {
1176 int32_t i, count = mAttrs.AttrCount();
1177 for (i = 0; i < count; ++i) {
1178 const nsAttrName* name = mAttrs.AttrNameAt(i);
1180 // Eventlistenener-attributes are always in the null namespace
1181 if (!name->IsAtom()) {
1182 continue;
1185 nsAtom* attr = name->Atom();
1186 if (!nsContentUtils::IsEventAttributeName(attr, EventNameType_XUL)) {
1187 continue;
1190 nsAutoString value;
1191 GetAttr(kNameSpaceID_None, attr, value);
1192 SetEventHandler(attr, value, true);
1196 bool nsXULElement::IsEventAttributeNameInternal(nsAtom* aName) {
1197 return nsContentUtils::IsEventAttributeName(aName, EventNameType_XUL);
1200 JSObject* nsXULElement::WrapNode(JSContext* aCx,
1201 JS::Handle<JSObject*> aGivenProto) {
1202 return dom::XULElement_Binding::Wrap(aCx, this, aGivenProto);
1205 bool nsXULElement::IsInteractiveHTMLContent() const {
1206 return IsXULElement(nsGkAtoms::menupopup) ||
1207 Element::IsInteractiveHTMLContent();
1210 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULPrototypeNode)
1212 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULPrototypeNode)
1213 if (tmp->mType == nsXULPrototypeNode::eType_Element) {
1214 static_cast<nsXULPrototypeElement*>(tmp)->Unlink();
1216 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1217 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeNode)
1218 if (tmp->mType == nsXULPrototypeNode::eType_Element) {
1219 nsXULPrototypeElement* elem = static_cast<nsXULPrototypeElement*>(tmp);
1220 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mNodeInfo");
1221 cb.NoteNativeChild(elem->mNodeInfo,
1222 NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
1223 size_t i;
1224 for (i = 0; i < elem->mAttributes.Length(); ++i) {
1225 const nsAttrName& name = elem->mAttributes[i].mName;
1226 if (!name.IsAtom()) {
1227 NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
1228 "mAttributes[i].mName.NodeInfo()");
1229 cb.NoteNativeChild(name.NodeInfo(),
1230 NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
1233 ImplCycleCollectionTraverse(cb, elem->mChildren, "mChildren");
1235 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
1236 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXULPrototypeNode)
1237 NS_IMPL_CYCLE_COLLECTION_TRACE_END
1239 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsXULPrototypeNode, AddRef)
1240 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsXULPrototypeNode, Release)
1242 //----------------------------------------------------------------------
1244 // nsXULPrototypeAttribute
1247 nsXULPrototypeAttribute::~nsXULPrototypeAttribute() {
1248 MOZ_COUNT_DTOR(nsXULPrototypeAttribute);
1251 //----------------------------------------------------------------------
1253 // nsXULPrototypeElement
1256 nsresult nsXULPrototypeElement::Serialize(
1257 nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
1258 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
1259 nsresult rv;
1261 // Write basic prototype data
1262 rv = aStream->Write32(mType);
1264 // Write Node Info
1265 int32_t index = aNodeInfos->IndexOf(mNodeInfo);
1266 NS_ASSERTION(index >= 0, "unknown mozilla::dom::NodeInfo index");
1267 nsresult tmp = aStream->Write32(index);
1268 if (NS_FAILED(tmp)) {
1269 rv = tmp;
1272 // Write Attributes
1273 tmp = aStream->Write32(mAttributes.Length());
1274 if (NS_FAILED(tmp)) {
1275 rv = tmp;
1278 nsAutoString attributeValue;
1279 size_t i;
1280 for (i = 0; i < mAttributes.Length(); ++i) {
1281 RefPtr<mozilla::dom::NodeInfo> ni;
1282 if (mAttributes[i].mName.IsAtom()) {
1283 ni = mNodeInfo->NodeInfoManager()->GetNodeInfo(
1284 mAttributes[i].mName.Atom(), nullptr, kNameSpaceID_None,
1285 nsINode::ATTRIBUTE_NODE);
1286 NS_ASSERTION(ni, "the nodeinfo should already exist");
1287 } else {
1288 ni = mAttributes[i].mName.NodeInfo();
1291 index = aNodeInfos->IndexOf(ni);
1292 NS_ASSERTION(index >= 0, "unknown mozilla::dom::NodeInfo index");
1293 tmp = aStream->Write32(index);
1294 if (NS_FAILED(tmp)) {
1295 rv = tmp;
1298 mAttributes[i].mValue.ToString(attributeValue);
1299 tmp = aStream->WriteWStringZ(attributeValue.get());
1300 if (NS_FAILED(tmp)) {
1301 rv = tmp;
1305 // Now write children
1306 tmp = aStream->Write32(uint32_t(mChildren.Length()));
1307 if (NS_FAILED(tmp)) {
1308 rv = tmp;
1310 for (i = 0; i < mChildren.Length(); i++) {
1311 nsXULPrototypeNode* child = mChildren[i].get();
1312 switch (child->mType) {
1313 case eType_Element:
1314 case eType_Text:
1315 case eType_PI:
1316 tmp = child->Serialize(aStream, aProtoDoc, aNodeInfos);
1317 if (NS_FAILED(tmp)) {
1318 rv = tmp;
1320 break;
1321 case eType_Script:
1322 tmp = aStream->Write32(child->mType);
1323 if (NS_FAILED(tmp)) {
1324 rv = tmp;
1326 nsXULPrototypeScript* script =
1327 static_cast<nsXULPrototypeScript*>(child);
1329 tmp = aStream->Write8(script->mOutOfLine);
1330 if (NS_FAILED(tmp)) {
1331 rv = tmp;
1333 if (!script->mOutOfLine) {
1334 tmp = script->Serialize(aStream, aProtoDoc, aNodeInfos);
1335 if (NS_FAILED(tmp)) {
1336 rv = tmp;
1338 } else {
1339 tmp = aStream->WriteCompoundObject(script->mSrcURI,
1340 NS_GET_IID(nsIURI), true);
1341 if (NS_FAILED(tmp)) {
1342 rv = tmp;
1345 if (script->HasStencil()) {
1346 // This may return NS_OK without muxing script->mSrcURI's
1347 // data into the cache file, in the case where that
1348 // muxed document is already there (written by a prior
1349 // session, or by an earlier cache episode during this
1350 // session).
1351 tmp = script->SerializeOutOfLine(aStream, aProtoDoc);
1352 if (NS_FAILED(tmp)) {
1353 rv = tmp;
1357 break;
1361 return rv;
1364 nsresult nsXULPrototypeElement::Deserialize(
1365 nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
1366 nsIURI* aDocumentURI,
1367 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
1368 MOZ_ASSERT(aNodeInfos, "missing nodeinfo array");
1370 // Read Node Info
1371 uint32_t number = 0;
1372 nsresult rv = aStream->Read32(&number);
1373 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1374 mNodeInfo = aNodeInfos->SafeElementAt(number, nullptr);
1375 if (!mNodeInfo) {
1376 return NS_ERROR_UNEXPECTED;
1379 // Read Attributes
1380 rv = aStream->Read32(&number);
1381 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1382 int32_t attributes = int32_t(number);
1384 if (attributes > 0) {
1385 mAttributes.AppendElements(attributes);
1387 nsAutoString attributeValue;
1388 for (size_t i = 0; i < mAttributes.Length(); ++i) {
1389 rv = aStream->Read32(&number);
1390 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1391 mozilla::dom::NodeInfo* ni = aNodeInfos->SafeElementAt(number, nullptr);
1392 if (!ni) {
1393 return NS_ERROR_UNEXPECTED;
1396 mAttributes[i].mName.SetTo(ni);
1398 rv = aStream->ReadString(attributeValue);
1399 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1400 rv = SetAttrAt(i, attributeValue, aDocumentURI);
1401 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1405 rv = aStream->Read32(&number);
1406 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1407 uint32_t numChildren = int32_t(number);
1409 if (numChildren > 0) {
1410 if (!mChildren.SetCapacity(numChildren, fallible)) {
1411 return NS_ERROR_OUT_OF_MEMORY;
1414 for (uint32_t i = 0; i < numChildren; i++) {
1415 rv = aStream->Read32(&number);
1416 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1417 Type childType = (Type)number;
1419 RefPtr<nsXULPrototypeNode> child;
1421 switch (childType) {
1422 case eType_Element:
1423 child = new nsXULPrototypeElement();
1424 rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos);
1425 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1426 break;
1427 case eType_Text:
1428 child = new nsXULPrototypeText();
1429 rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos);
1430 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1431 break;
1432 case eType_PI:
1433 child = new nsXULPrototypePI();
1434 rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos);
1435 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1436 break;
1437 case eType_Script: {
1438 // language version/options obtained during deserialization.
1439 RefPtr<nsXULPrototypeScript> script = new nsXULPrototypeScript(0);
1441 rv = aStream->ReadBoolean(&script->mOutOfLine);
1442 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1443 if (!script->mOutOfLine) {
1444 rv = script->Deserialize(aStream, aProtoDoc, aDocumentURI,
1445 aNodeInfos);
1446 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1447 } else {
1448 nsCOMPtr<nsISupports> supports;
1449 rv = aStream->ReadObject(true, getter_AddRefs(supports));
1450 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1451 script->mSrcURI = do_QueryInterface(supports);
1453 rv = script->DeserializeOutOfLine(aStream, aProtoDoc);
1454 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1457 child = std::move(script);
1458 break;
1460 default:
1461 MOZ_ASSERT(false, "Unexpected child type!");
1462 return NS_ERROR_UNEXPECTED;
1465 MOZ_ASSERT(child, "Don't append null to mChildren");
1466 MOZ_ASSERT(child->mType == childType);
1467 mChildren.AppendElement(child);
1469 // Oh dear. Something failed during the deserialization.
1470 // We don't know what. But likely consequences of failed
1471 // deserializations included calls to |AbortCaching| which
1472 // shuts down the cache and closes our streams.
1473 // If that happens, next time through this loop, we die a messy
1474 // death. So, let's just fail now, and propagate that failure
1475 // upward so that the ChromeProtocolHandler knows it can't use
1476 // a cached chrome channel for this.
1477 if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1481 return rv;
1484 nsresult nsXULPrototypeElement::SetAttrAt(uint32_t aPos,
1485 const nsAString& aValue,
1486 nsIURI* aDocumentURI) {
1487 MOZ_ASSERT(aPos < mAttributes.Length(), "out-of-bounds");
1489 // WARNING!!
1490 // This code is largely duplicated in nsXULElement::SetAttr.
1491 // Any changes should be made to both functions.
1493 if (!mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) {
1494 if (mNodeInfo->NamespaceEquals(kNameSpaceID_XHTML) &&
1495 mAttributes[aPos].mName.Equals(nsGkAtoms::is)) {
1496 // We still care about the is attribute set on HTML elements.
1497 mAttributes[aPos].mValue.ParseAtom(aValue);
1498 mIsAtom = mAttributes[aPos].mValue.GetAtomValue();
1500 return NS_OK;
1503 mAttributes[aPos].mValue.ParseStringOrAtom(aValue);
1505 return NS_OK;
1508 if (mAttributes[aPos].mName.Equals(nsGkAtoms::id) && !aValue.IsEmpty()) {
1509 mHasIdAttribute = true;
1510 // Store id as atom.
1511 // id="" means that the element has no id. Not that it has
1512 // emptystring as id.
1513 mAttributes[aPos].mValue.ParseAtom(aValue);
1515 return NS_OK;
1516 } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::is)) {
1517 // Store is as atom.
1518 mAttributes[aPos].mValue.ParseAtom(aValue);
1519 mIsAtom = mAttributes[aPos].mValue.GetAtomValue();
1521 return NS_OK;
1522 } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::_class)) {
1523 mHasClassAttribute = true;
1524 // Compute the element's class list
1525 mAttributes[aPos].mValue.ParseAtomArray(aValue);
1527 return NS_OK;
1528 } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::style)) {
1529 mHasStyleAttribute = true;
1530 // Parse the element's 'style' attribute
1532 // This is basically duplicating what nsINode::NodePrincipal() does
1533 nsIPrincipal* principal = mNodeInfo->NodeInfoManager()->DocumentPrincipal();
1534 // XXX Get correct Base URI (need GetBaseURI on *prototype* element)
1535 // TODO: If we implement Content Security Policy for chrome documents
1536 // as has been discussed, the CSP should be checked here to see if
1537 // inline styles are allowed to be applied.
1538 // XXX No specific specs talk about xul and referrer policy, pass Unset
1539 auto referrerInfo =
1540 MakeRefPtr<ReferrerInfo>(aDocumentURI, ReferrerPolicy::_empty);
1541 auto data = MakeRefPtr<URLExtraData>(aDocumentURI, referrerInfo, principal);
1542 RefPtr<DeclarationBlock> declaration = DeclarationBlock::FromCssText(
1543 aValue, data, eCompatibility_FullStandards, nullptr,
1544 StyleCssRuleType::Style);
1545 if (declaration) {
1546 mAttributes[aPos].mValue.SetTo(declaration.forget(), &aValue);
1548 return NS_OK;
1550 // Don't abort if parsing failed, it could just be malformed css.
1551 } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::tabindex)) {
1552 mAttributes[aPos].mValue.ParseIntValue(aValue);
1554 return NS_OK;
1557 mAttributes[aPos].mValue.ParseStringOrAtom(aValue);
1559 return NS_OK;
1562 void nsXULPrototypeElement::Unlink() {
1563 mAttributes.Clear();
1564 mChildren.Clear();
1567 //----------------------------------------------------------------------
1569 // nsXULPrototypeScript
1572 nsXULPrototypeScript::nsXULPrototypeScript(uint32_t aLineNo)
1573 : nsXULPrototypeNode(eType_Script),
1574 mLineNo(aLineNo),
1575 mSrcLoading(false),
1576 mOutOfLine(true),
1577 mSrcLoadWaiters(nullptr),
1578 mStencil(nullptr) {}
1580 static nsresult WriteStencil(nsIObjectOutputStream* aStream, JSContext* aCx,
1581 const JS::ReadOnlyCompileOptions& aOptions,
1582 JS::Stencil* aStencil) {
1583 uint8_t flags = 0; // We don't have flags anymore.
1584 nsresult rv = aStream->Write8(flags);
1585 if (NS_FAILED(rv)) {
1586 return rv;
1589 JS::TranscodeBuffer buffer;
1590 JS::TranscodeResult code;
1591 code = JS::EncodeStencil(aCx, aOptions, aStencil, buffer);
1593 if (code != JS::TranscodeResult::Ok) {
1594 if (code == JS::TranscodeResult::Throw) {
1595 JS_ClearPendingException(aCx);
1596 return NS_ERROR_OUT_OF_MEMORY;
1599 MOZ_ASSERT(IsTranscodeFailureResult(code));
1600 return NS_ERROR_FAILURE;
1603 size_t size = buffer.length();
1604 if (size > UINT32_MAX) {
1605 return NS_ERROR_FAILURE;
1607 rv = aStream->Write32(size);
1608 if (NS_SUCCEEDED(rv)) {
1609 // Ideally we could just pass "buffer" here. See bug 1566574.
1610 rv = aStream->WriteBytes(Span(buffer.begin(), size));
1613 return rv;
1616 static nsresult ReadStencil(nsIObjectInputStream* aStream, JSContext* aCx,
1617 const JS::ReadOnlyCompileOptions& aOptions,
1618 JS::Stencil** aStencilOut) {
1619 uint8_t flags;
1620 nsresult rv = aStream->Read8(&flags);
1621 if (NS_FAILED(rv)) {
1622 return rv;
1625 // We don't serialize mutedError-ness of scripts, which is fine as long as
1626 // we only serialize system and XUL-y things. We can detect this by checking
1627 // where the caller wants us to deserialize.
1629 // CompilationScope() could theoretically GC, so get that out of the way
1630 // before comparing to the cx global.
1631 JSObject* loaderGlobal = xpc::CompilationScope();
1632 MOZ_RELEASE_ASSERT(nsContentUtils::IsSystemCaller(aCx) ||
1633 JS::CurrentGlobalOrNull(aCx) == loaderGlobal);
1635 uint32_t size;
1636 rv = aStream->Read32(&size);
1637 if (NS_FAILED(rv)) {
1638 return rv;
1641 char* data;
1642 rv = aStream->ReadBytes(size, &data);
1643 if (NS_FAILED(rv)) {
1644 return rv;
1647 // The decoded stencil shouldn't borrow from the XDR buffer.
1648 MOZ_ASSERT(!aOptions.borrowBuffer);
1649 auto cleanupData = MakeScopeExit([&]() { free(data); });
1651 JS::TranscodeRange range(reinterpret_cast<uint8_t*>(data), size);
1654 JS::TranscodeResult code;
1655 RefPtr<JS::Stencil> stencil;
1656 code = JS::DecodeStencil(aCx, aOptions, range, getter_AddRefs(stencil));
1657 if (code != JS::TranscodeResult::Ok) {
1658 if (code == JS::TranscodeResult::Throw) {
1659 JS_ClearPendingException(aCx);
1660 return NS_ERROR_OUT_OF_MEMORY;
1663 MOZ_ASSERT(IsTranscodeFailureResult(code));
1664 return NS_ERROR_FAILURE;
1667 stencil.forget(aStencilOut);
1670 return rv;
1673 void nsXULPrototypeScript::FillCompileOptions(JS::CompileOptions& options) {
1674 // If the script was inline, tell the JS parser to save source for
1675 // Function.prototype.toSource(). If it's out of line, we retrieve the
1676 // source from the files on demand.
1677 options.setSourceIsLazy(mOutOfLine);
1680 nsresult nsXULPrototypeScript::Serialize(
1681 nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
1682 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
1683 NS_ENSURE_TRUE(aProtoDoc, NS_ERROR_UNEXPECTED);
1685 AutoJSAPI jsapi;
1686 if (!jsapi.Init(xpc::CompilationScope())) {
1687 return NS_ERROR_UNEXPECTED;
1690 NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mStencil,
1691 "script source still loading when serializing?!");
1692 if (!mStencil) return NS_ERROR_FAILURE;
1694 // Write basic prototype data
1695 nsresult rv;
1696 rv = aStream->Write32(mLineNo);
1697 if (NS_FAILED(rv)) return rv;
1698 rv = aStream->Write32(0); // See bug 1418294.
1699 if (NS_FAILED(rv)) return rv;
1701 JSContext* cx = jsapi.cx();
1702 MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx));
1704 JS::CompileOptions options(cx);
1705 FillCompileOptions(options);
1707 return WriteStencil(aStream, cx, options, mStencil);
1710 nsresult nsXULPrototypeScript::SerializeOutOfLine(
1711 nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc) {
1712 if (!mSrcURI->SchemeIs("chrome"))
1713 // Don't cache scripts that don't come from chrome uris.
1714 return NS_ERROR_NOT_IMPLEMENTED;
1716 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
1717 if (!cache) return NS_ERROR_OUT_OF_MEMORY;
1719 NS_ASSERTION(cache->IsEnabled(),
1720 "writing to the cache file, but the XUL cache is off?");
1721 bool exists;
1722 cache->HasData(mSrcURI, &exists);
1724 /* return will be NS_OK from GetAsciiSpec.
1725 * that makes no sense.
1726 * nor does returning NS_OK from HasMuxedDocument.
1727 * XXX return something meaningful.
1729 if (exists) return NS_OK;
1731 nsCOMPtr<nsIObjectOutputStream> oos;
1732 nsresult rv = cache->GetOutputStream(mSrcURI, getter_AddRefs(oos));
1733 NS_ENSURE_SUCCESS(rv, rv);
1735 nsresult tmp = Serialize(oos, aProtoDoc, nullptr);
1736 if (NS_FAILED(tmp)) {
1737 rv = tmp;
1739 tmp = cache->FinishOutputStream(mSrcURI);
1740 if (NS_FAILED(tmp)) {
1741 rv = tmp;
1744 if (NS_FAILED(rv)) cache->AbortCaching();
1745 return rv;
1748 nsresult nsXULPrototypeScript::Deserialize(
1749 nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
1750 nsIURI* aDocumentURI,
1751 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
1752 nsresult rv;
1753 NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mStencil,
1754 "prototype script not well-initialized when deserializing?!");
1756 // Read basic prototype data
1757 rv = aStream->Read32(&mLineNo);
1758 if (NS_FAILED(rv)) return rv;
1759 uint32_t dummy;
1760 rv = aStream->Read32(&dummy); // See bug 1418294.
1761 if (NS_FAILED(rv)) return rv;
1763 AutoJSAPI jsapi;
1764 if (!jsapi.Init(xpc::CompilationScope())) {
1765 return NS_ERROR_UNEXPECTED;
1767 JSContext* cx = jsapi.cx();
1769 JS::CompileOptions options(cx);
1770 FillCompileOptions(options);
1772 RefPtr<JS::Stencil> newStencil;
1773 rv = ReadStencil(aStream, cx, options, getter_AddRefs(newStencil));
1774 NS_ENSURE_SUCCESS(rv, rv);
1775 Set(newStencil);
1776 return NS_OK;
1779 nsresult nsXULPrototypeScript::DeserializeOutOfLine(
1780 nsIObjectInputStream* aInput, nsXULPrototypeDocument* aProtoDoc) {
1781 // Keep track of failure via rv, so we can
1782 // AbortCaching if things look bad.
1783 nsresult rv = NS_OK;
1784 nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
1786 nsCOMPtr<nsIObjectInputStream> objectInput = aInput;
1787 if (cache) {
1788 bool useXULCache = true;
1789 if (mSrcURI) {
1790 // NB: we must check the XUL script cache early, to avoid
1791 // multiple deserialization attempts for a given script.
1792 // Note that PrototypeDocumentContentSink::LoadScript
1793 // checks the XUL script cache too, in order to handle the
1794 // serialization case.
1796 // We need do this only for <script src='strres.js'> and the
1797 // like, i.e., out-of-line scripts that are included by several
1798 // different XUL documents stored in the cache file.
1799 useXULCache = cache->IsEnabled();
1801 if (useXULCache) {
1802 RefPtr<JS::Stencil> newStencil = cache->GetStencil(mSrcURI);
1803 if (newStencil) {
1804 Set(newStencil);
1809 if (!mStencil) {
1810 if (mSrcURI) {
1811 rv = cache->GetInputStream(mSrcURI, getter_AddRefs(objectInput));
1813 // If !mSrcURI, we have an inline script. We shouldn't have
1814 // to do anything else in that case, I think.
1816 // We do reflect errors into rv, but our caller may want to
1817 // ignore our return value, because mStencil will be null
1818 // after any error, and that suffices to cause the script to
1819 // be reloaded (from the src= URI, if any) and recompiled.
1820 // We're better off slow-loading than bailing out due to a
1821 // error.
1822 if (NS_SUCCEEDED(rv))
1823 rv = Deserialize(objectInput, aProtoDoc, nullptr, nullptr);
1825 if (NS_SUCCEEDED(rv)) {
1826 if (useXULCache && mSrcURI && mSrcURI->SchemeIs("chrome")) {
1827 cache->PutStencil(mSrcURI, GetStencil());
1829 cache->FinishInputStream(mSrcURI);
1830 } else {
1831 // If mSrcURI is not in the cache,
1832 // rv will be NS_ERROR_NOT_AVAILABLE and we'll try to
1833 // update the cache file to hold a serialization of
1834 // this script, once it has finished loading.
1835 if (rv != NS_ERROR_NOT_AVAILABLE) cache->AbortCaching();
1839 return rv;
1842 class NotifyOffThreadScriptCompletedRunnable : public Runnable {
1843 // An array of all outstanding script receivers. All reference counting of
1844 // these objects happens on the main thread. When we return to the main
1845 // thread from script compilation we make sure our receiver is still in
1846 // this array (still alive) before proceeding. This array is cleared during
1847 // shutdown, potentially before all outstanding script compilations have
1848 // finished. We do not need to worry about pointer replay here, because
1849 // a) we should not be starting script compilation after clearing this
1850 // array and b) in all other cases the receiver will still be alive.
1851 static StaticAutoPtr<nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>>
1852 sReceivers;
1853 static bool sSetupClearOnShutdown;
1855 nsIOffThreadScriptReceiver* mReceiver;
1856 JS::OffThreadToken* mToken;
1858 public:
1859 NotifyOffThreadScriptCompletedRunnable(nsIOffThreadScriptReceiver* aReceiver,
1860 JS::OffThreadToken* aToken)
1861 : mozilla::Runnable("NotifyOffThreadScriptCompletedRunnable"),
1862 mReceiver(aReceiver),
1863 mToken(aToken) {}
1865 static void NoteReceiver(nsIOffThreadScriptReceiver* aReceiver) {
1866 if (!sSetupClearOnShutdown) {
1867 ClearOnShutdown(&sReceivers);
1868 sSetupClearOnShutdown = true;
1869 sReceivers = new nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>();
1872 // If we ever crash here, it's because we tried to lazy compile script
1873 // too late in shutdown.
1874 sReceivers->AppendElement(aReceiver);
1877 NS_DECL_NSIRUNNABLE
1880 StaticAutoPtr<nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>>
1881 NotifyOffThreadScriptCompletedRunnable::sReceivers;
1882 bool NotifyOffThreadScriptCompletedRunnable::sSetupClearOnShutdown = false;
1884 NS_IMETHODIMP
1885 NotifyOffThreadScriptCompletedRunnable::Run() {
1886 MOZ_ASSERT(NS_IsMainThread());
1888 RefPtr<JS::Stencil> stencil;
1890 AutoJSAPI jsapi;
1891 if (!jsapi.Init(xpc::CompilationScope())) {
1892 // Now what? I guess we just leak... this should probably never
1893 // happen.
1894 return NS_ERROR_UNEXPECTED;
1896 JSContext* cx = jsapi.cx();
1897 stencil = JS::FinishOffThreadCompileToStencil(cx, mToken);
1900 if (!sReceivers) {
1901 // We've already shut down.
1902 return NS_OK;
1905 auto index = sReceivers->IndexOf(mReceiver);
1906 MOZ_RELEASE_ASSERT(index != sReceivers->NoIndex);
1907 nsCOMPtr<nsIOffThreadScriptReceiver> receiver =
1908 std::move((*sReceivers)[index]);
1909 sReceivers->RemoveElementAt(index);
1911 return receiver->OnScriptCompileComplete(stencil,
1912 stencil ? NS_OK : NS_ERROR_FAILURE);
1915 static void OffThreadScriptReceiverCallback(JS::OffThreadToken* aToken,
1916 void* aCallbackData) {
1917 // Be careful not to adjust the refcount on the receiver, as this callback
1918 // may be invoked off the main thread.
1919 nsIOffThreadScriptReceiver* aReceiver =
1920 static_cast<nsIOffThreadScriptReceiver*>(aCallbackData);
1921 RefPtr<NotifyOffThreadScriptCompletedRunnable> notify =
1922 new NotifyOffThreadScriptCompletedRunnable(aReceiver, aToken);
1923 NS_DispatchToMainThread(notify);
1926 nsresult nsXULPrototypeScript::Compile(
1927 const char16_t* aText, size_t aTextLength, JS::SourceOwnership aOwnership,
1928 nsIURI* aURI, uint32_t aLineNo, Document* aDocument,
1929 nsIOffThreadScriptReceiver* aOffThreadReceiver /* = nullptr */) {
1930 // We'll compile the script in the compilation scope.
1931 AutoJSAPI jsapi;
1932 if (!jsapi.Init(xpc::CompilationScope())) {
1933 if (aOwnership == JS::SourceOwnership::TakeOwnership) {
1934 // In this early-exit case -- before the |srcBuf.init| call will
1935 // own |aText| -- we must relinquish ownership manually.
1936 js_free(const_cast<char16_t*>(aText));
1939 return NS_ERROR_UNEXPECTED;
1941 JSContext* cx = jsapi.cx();
1943 JS::SourceText<char16_t> srcBuf;
1944 if (NS_WARN_IF(!srcBuf.init(cx, aText, aTextLength, aOwnership))) {
1945 return NS_ERROR_FAILURE;
1948 nsAutoCString urlspec;
1949 nsresult rv = aURI->GetSpec(urlspec);
1950 if (NS_WARN_IF(NS_FAILED(rv))) {
1951 return rv;
1954 // Ok, compile it to create a prototype script object!
1955 JS::CompileOptions options(cx);
1956 FillCompileOptions(options);
1957 options.setIntroductionType(mOutOfLine ? "srcScript" : "inlineScript")
1958 .setFileAndLine(urlspec.get(), mOutOfLine ? 1 : aLineNo);
1960 JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
1962 if (aOffThreadReceiver && JS::CanCompileOffThread(cx, options, aTextLength)) {
1963 if (!JS::CompileToStencilOffThread(
1964 cx, options, srcBuf, OffThreadScriptReceiverCallback,
1965 static_cast<void*>(aOffThreadReceiver))) {
1966 JS_ClearPendingException(cx);
1967 return NS_ERROR_OUT_OF_MEMORY;
1969 NotifyOffThreadScriptCompletedRunnable::NoteReceiver(aOffThreadReceiver);
1970 } else {
1971 RefPtr<JS::Stencil> stencil =
1972 JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
1973 if (!stencil) {
1974 return NS_ERROR_OUT_OF_MEMORY;
1976 Set(stencil);
1978 return NS_OK;
1981 nsresult nsXULPrototypeScript::InstantiateScript(
1982 JSContext* aCx, JS::MutableHandleScript aScript) {
1983 MOZ_ASSERT(mStencil);
1985 JS::CompileOptions options(aCx);
1986 FillCompileOptions(options);
1987 // We don't need setIntroductionType and setFileAndLine here, unlike
1988 // nsXULPrototypeScript::Compile.
1989 // mStencil already contains the information.
1990 aScript.set(JS::InstantiateGlobalStencil(aCx, options, mStencil));
1991 if (!aScript) {
1992 JS_ClearPendingException(aCx);
1993 return NS_ERROR_OUT_OF_MEMORY;
1996 return NS_OK;
1999 void nsXULPrototypeScript::Set(JS::Stencil* aStencil) { mStencil = aStencil; }
2001 //----------------------------------------------------------------------
2003 // nsXULPrototypeText
2006 nsresult nsXULPrototypeText::Serialize(
2007 nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
2008 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
2009 nsresult rv;
2011 // Write basic prototype data
2012 rv = aStream->Write32(mType);
2014 nsresult tmp = aStream->WriteWStringZ(mValue.get());
2015 if (NS_FAILED(tmp)) {
2016 rv = tmp;
2019 return rv;
2022 nsresult nsXULPrototypeText::Deserialize(
2023 nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
2024 nsIURI* aDocumentURI,
2025 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
2026 nsresult rv = aStream->ReadString(mValue);
2027 if (NS_WARN_IF(NS_FAILED(rv))) {
2028 return rv;
2030 return NS_OK;
2033 //----------------------------------------------------------------------
2035 // nsXULPrototypePI
2038 nsresult nsXULPrototypePI::Serialize(
2039 nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
2040 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
2041 nsresult rv;
2043 // Write basic prototype data
2044 rv = aStream->Write32(mType);
2046 nsresult tmp = aStream->WriteWStringZ(mTarget.get());
2047 if (NS_FAILED(tmp)) {
2048 rv = tmp;
2050 tmp = aStream->WriteWStringZ(mData.get());
2051 if (NS_FAILED(tmp)) {
2052 rv = tmp;
2055 return rv;
2058 nsresult nsXULPrototypePI::Deserialize(
2059 nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
2060 nsIURI* aDocumentURI,
2061 const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
2062 nsresult rv;
2064 rv = aStream->ReadString(mTarget);
2065 if (NS_FAILED(rv)) return rv;
2066 rv = aStream->ReadString(mData);
2067 if (NS_FAILED(rv)) return rv;
2069 return rv;