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/. */
5 
6 #include "nsXULElement.h"
7 
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"
116 
117 using namespace mozilla;
118 using namespace mozilla::dom;
119 
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
128 
129 #define NS_DISPATCH_XUL_COMMAND (1 << 0)
130 
131 //----------------------------------------------------------------------
132 // nsXULElement
133 //
134 
nsXULElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo)135 nsXULElement::nsXULElement(already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
136     : nsStyledElement(std::move(aNodeInfo)) {
137   XUL_PROTOTYPE_ATTRIBUTE_METER(gNumElements);
138 }
139 
140 nsXULElement::~nsXULElement() = default;
141 
142 /* static */
NS_NewBasicXULElement(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo)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());
148 }
149 
150 /* static */
Construct(already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo)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());
162   }
163 
164   if (nodeInfo->Equals(nsGkAtoms::menupopup) ||
165       nodeInfo->Equals(nsGkAtoms::popup) ||
166       nodeInfo->Equals(nsGkAtoms::panel)) {
167     return NS_NewXULPopupElement(nodeInfo.forget());
168   }
169 
170   if (nodeInfo->Equals(nsGkAtoms::tooltip)) {
171     return NS_NewXULTooltipElement(nodeInfo.forget());
172   }
173 
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());
179   }
180 
181   if (nodeInfo->Equals(nsGkAtoms::menu) ||
182       nodeInfo->Equals(nsGkAtoms::menulist)) {
183     auto* nim = nodeInfo->NodeInfoManager();
184     return new (nim) XULMenuElement(nodeInfo.forget());
185   }
186 
187   if (nodeInfo->Equals(nsGkAtoms::tree)) {
188     auto* nim = nodeInfo->NodeInfoManager();
189     return new (nim) XULTreeElement(nodeInfo.forget());
190   }
191 
192   return NS_NewBasicXULElement(nodeInfo.forget());
193 }
194 
195 /* static */
CreateFromPrototype(nsXULPrototypeElement * aPrototype,mozilla::dom::NodeInfo * aNodeInfo,bool aIsScriptable,bool aIsRoot)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);
203 
204   if (baseElement) {
205     nsXULElement* element = FromNode(baseElement);
206 
207     if (aPrototype->mHasIdAttribute) {
208       element->SetHasID();
209     }
210     if (aPrototype->mHasClassAttribute) {
211       element->SetMayHaveClass();
212     }
213     if (aPrototype->mHasStyleAttribute) {
214       element->SetMayHaveStyle();
215     }
216 
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);
224       }
225     }
226 
227     return baseElement.forget().downcast<nsXULElement>();
228   }
229 
230   return nullptr;
231 }
232 
CreateFromPrototype(nsXULPrototypeElement * aPrototype,Document * aDocument,bool aIsScriptable,bool aIsRoot,Element ** aResult)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;
240 
241   MOZ_ASSERT(aResult != nullptr, "null ptr");
242   if (!aResult) return NS_ERROR_NULL_POINTER;
243 
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;
251   }
252 
253   RefPtr<nsXULElement> element =
254       CreateFromPrototype(aPrototype, nodeInfo, aIsScriptable, aIsRoot);
255   element.forget(aResult);
256 
257   return NS_OK;
258 }
259 
NS_NewXULElement(Element ** aResult,already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo,FromParser aFromParser,nsAtom * aIsAtom,mozilla::dom::CustomElementDefinition * aDefinition)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;
265 
266   MOZ_ASSERT(nodeInfo, "need nodeinfo for non-proto Create");
267 
268   NS_ASSERTION(
269       nodeInfo->NamespaceEquals(kNameSpaceID_XUL),
270       "Trying to create XUL elements that don't have the XUL namespace");
271 
272   Document* doc = nodeInfo->GetDocument();
273   if (doc && !doc->AllowXULXBL()) {
274     return NS_ERROR_NOT_AVAILABLE;
275   }
276 
277   return nsContentUtils::NewXULOrHTMLElement(aResult, nodeInfo, aFromParser,
278                                              aIsAtom, aDefinition);
279 }
280 
NS_TrustedNewXULElement(Element ** aResult,already_AddRefed<mozilla::dom::NodeInfo> && aNodeInfo)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");
285 
286   // Create an nsXULElement with the specified namespace and tag.
287   NS_ADDREF(*aResult = nsXULElement::Construct(ni.forget()));
288 }
289 
290 //----------------------------------------------------------------------
291 // nsISupports interface
292 
NS_IMPL_CYCLE_COLLECTION_INHERITED(nsXULElement,nsStyledElement)293 NS_IMPL_CYCLE_COLLECTION_INHERITED(nsXULElement, nsStyledElement)
294 
295 NS_IMPL_ADDREF_INHERITED(nsXULElement, nsStyledElement)
296 NS_IMPL_RELEASE_INHERITED(nsXULElement, nsStyledElement)
297 
298 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(nsXULElement)
299   NS_ELEMENT_INTERFACE_TABLE_TO_MAP_SEGUE
300 NS_INTERFACE_MAP_END_INHERITING(nsStyledElement)
301 
302 //----------------------------------------------------------------------
303 // nsINode interface
304 
305 nsresult nsXULElement::Clone(mozilla::dom::NodeInfo* aNodeInfo,
306                              nsINode** aResult) const {
307   *aResult = nullptr;
308 
309   RefPtr<mozilla::dom::NodeInfo> ni = aNodeInfo;
310   RefPtr<nsXULElement> element = Construct(ni.forget());
311 
312   nsresult rv = const_cast<nsXULElement*>(this)->CopyInnerTo(
313       element, ReparseAttributes::No);
314   NS_ENSURE_SUCCESS(rv, rv);
315 
316   // Note that we're _not_ copying mControllers.
317 
318   element.forget(aResult);
319   return rv;
320 }
321 
322 //----------------------------------------------------------------------
323 
GetEventListenerManagerForAttr(nsAtom * aAttrName,bool * aDefer)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();
330 
331   nsPIDOMWindowInner* window;
332   Element* root = doc->GetRootElement();
333   if ((!root || root == this) && (window = doc->GetInnerWindow())) {
334     nsCOMPtr<EventTarget> piTarget = do_QueryInterface(window);
335 
336     *aDefer = false;
337     return piTarget->GetOrCreateListenerManager();
338   }
339 
340   return nsStyledElement::GetEventListenerManagerForAttr(aAttrName, aDefer);
341 }
342 
343 // returns true if the element is not a list
IsNonList(mozilla::dom::NodeInfo * aNodeInfo)344 static bool IsNonList(mozilla::dom::NodeInfo* aNodeInfo) {
345   return !aNodeInfo->Equals(nsGkAtoms::tree) &&
346          !aNodeInfo->Equals(nsGkAtoms::richlistbox);
347 }
348 
IsFocusableInternal(int32_t * aTabIndex,bool aWithMouse)349 bool nsXULElement::IsFocusableInternal(int32_t* aTabIndex, bool aWithMouse) {
350   /*
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.
355    *
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.
359    *
360    * For controls, the element cannot be focused and is not part of the tab
361    * order if it is disabled.
362    *
363    * -moz-user-focus is overridden if a tabindex (even -1) is specified.
364    *
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
372    *
373    * If aTabIndex is null, then the tabindex is not computed, and
374    * true is returned for non-disabled controls and false otherwise.
375    */
376 
377   // elements are not focusable by default
378   bool shouldFocus = false;
379 
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;
387   }
388 #endif
389 
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;
398     }
399     shouldFocus = true;
400   }
401 
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;
415       }
416     }
417 
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;
429       }
430     }
431   }
432 
433   return shouldFocus;
434 }
435 
HasMenu()436 bool nsXULElement::HasMenu() {
437   nsMenuFrame* menu = do_QueryFrame(GetPrimaryFrame(FlushType::Frames));
438   return !!menu;
439 }
440 
OpenMenu(bool aOpenFlag)441 void nsXULElement::OpenMenu(bool aOpenFlag) {
442   // Flush frames first. It's not clear why this is needed, see bug 1704670.
443   if (Document* doc = GetComposedDoc()) {
444     doc->FlushPendingNotifications(FlushType::Frames);
445   }
446 
447   nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
448   if (pm) {
449     if (aOpenFlag) {
450       // Nothing will happen if this element isn't a menu.
451       pm->ShowMenu(this, false);
452     } else {
453       // Nothing will happen if this element isn't a menu.
454       pm->HideMenu(this);
455     }
456   }
457 }
458 
PerformAccesskey(bool aKeyCausesActivation,bool aIsTrustedEvent)459 Result<bool, nsresult> nsXULElement::PerformAccesskey(bool aKeyCausesActivation,
460                                                       bool aIsTrustedEvent) {
461   if (IsXULElement(nsGkAtoms::label)) {
462     nsAutoString control;
463     GetAttr(kNameSpaceID_None, nsGkAtoms::control, control);
464     if (control.IsEmpty()) {
465       return Err(NS_ERROR_UNEXPECTED);
466     }
467 
468     // XXXsmaug Should we use ShadowRoot::GetElementById in case
469     //          element is in Shadow DOM?
470     RefPtr<Document> document = GetUncomposedDoc();
471     if (!document) {
472       return Err(NS_ERROR_UNEXPECTED);
473     }
474 
475     RefPtr<Element> element = document->GetElementById(control);
476     if (!element) {
477       return Err(NS_ERROR_UNEXPECTED);
478     }
479 
480     // XXXedgar, This is mainly for HTMLElement which doesn't do visible
481     // check in PerformAccesskey. We probably should always do visible
482     // check on HTMLElement even if the PerformAccesskey is not redirected from
483     // label XULelement per spec.
484     nsIFrame* frame = element->GetPrimaryFrame();
485     if (!frame || !frame->IsVisibleConsideringAncestors()) {
486       return Err(NS_ERROR_UNEXPECTED);
487     }
488 
489     return element->PerformAccesskey(aKeyCausesActivation, aIsTrustedEvent);
490   }
491 
492   nsIFrame* frame = GetPrimaryFrame();
493   if (!frame || !frame->IsVisibleConsideringAncestors()) {
494     return Err(NS_ERROR_UNEXPECTED);
495   }
496 
497   bool focused = false;
498   // Define behavior for each type of XUL element.
499   if (!IsXULElement(nsGkAtoms::toolbarbutton)) {
500     if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
501       RefPtr<Element> elementToFocus = this;
502       // for radio buttons, focus the radiogroup instead
503       if (IsXULElement(nsGkAtoms::radio)) {
504         if (nsCOMPtr<nsIDOMXULSelectControlItemElement> controlItem =
505                 AsXULSelectControlItem()) {
506           bool disabled;
507           controlItem->GetDisabled(&disabled);
508           if (!disabled) {
509             controlItem->GetControl(getter_AddRefs(elementToFocus));
510           }
511         }
512       }
513 
514       if (elementToFocus) {
515         fm->SetFocus(elementToFocus, nsIFocusManager::FLAG_BYKEY);
516 
517         // Return true if the element became focused.
518         nsPIDOMWindowOuter* window = OwnerDoc()->GetWindow();
519         focused = (window && window->GetFocusedElement() == elementToFocus);
520       }
521     }
522   }
523 
524   if (aKeyCausesActivation && !IsXULElement(nsGkAtoms::menulist)) {
525     ClickWithInputSource(MouseEvent_Binding::MOZ_SOURCE_KEYBOARD,
526                          aIsTrustedEvent);
527     return focused;
528   }
529 
530   // If the accesskey won't cause the activation and the focus isn't changed,
531   // either. Return error so EventStateManager would try to find next element
532   // to handle the accesskey.
533   return focused ? Result<bool, nsresult>{focused} : Err(NS_ERROR_ABORT);
534 }
535 
536 //----------------------------------------------------------------------
537 
AddListenerForAttributeIfNeeded(nsAtom * aLocalName)538 void nsXULElement::AddListenerForAttributeIfNeeded(nsAtom* aLocalName) {
539   // If appropriate, add a popup listener and/or compile the event
540   // handler. Called when we change the element's document, create a
541   // new element, change an attribute's value, etc.
542   // Eventlistenener-attributes are always in the null namespace.
543   if (aLocalName == nsGkAtoms::menu || aLocalName == nsGkAtoms::contextmenu ||
544       // XXXdwh popup and context are deprecated
545       aLocalName == nsGkAtoms::popup || aLocalName == nsGkAtoms::context) {
546     AddPopupListener(aLocalName);
547   }
548   if (nsContentUtils::IsEventAttributeName(aLocalName, EventNameType_XUL)) {
549     nsAutoString value;
550     GetAttr(kNameSpaceID_None, aLocalName, value);
551     SetEventHandler(aLocalName, value, true);
552   }
553 }
554 
AddListenerForAttributeIfNeeded(const nsAttrName & aName)555 void nsXULElement::AddListenerForAttributeIfNeeded(const nsAttrName& aName) {
556   if (aName.IsAtom()) {
557     AddListenerForAttributeIfNeeded(aName.Atom());
558   }
559 }
560 
561 //----------------------------------------------------------------------
562 //
563 // nsIContent interface
564 //
UpdateEditableState(bool aNotify)565 void nsXULElement::UpdateEditableState(bool aNotify) {
566   // Don't call through to Element here because the things
567   // it does don't work for cases when we're an editable control.
568   nsIContent* parent = GetParent();
569 
570   SetEditableFlag(parent && parent->HasFlag(NODE_IS_EDITABLE));
571   UpdateState(aNotify);
572 }
573 
574 class XULInContentErrorReporter : public Runnable {
575  public:
XULInContentErrorReporter(Document & aDocument)576   explicit XULInContentErrorReporter(Document& aDocument)
577       : mozilla::Runnable("XULInContentErrorReporter"), mDocument(aDocument) {}
578 
Run()579   NS_IMETHOD Run() override {
580     mDocument->WarnOnceAbout(DeprecatedOperations::eImportXULIntoContent,
581                              false);
582     return NS_OK;
583   }
584 
585  private:
586   OwningNonNull<Document> mDocument;
587 };
588 
NeedTooltipSupport(const nsXULElement & aXULElement)589 static bool NeedTooltipSupport(const nsXULElement& aXULElement) {
590   if (aXULElement.NodeInfo()->Equals(nsGkAtoms::treechildren)) {
591     // treechildren always get tooltip support, since cropped tree cells show
592     // their full text in a tooltip.
593     return true;
594   }
595 
596   return aXULElement.GetBoolAttr(nsGkAtoms::tooltip) ||
597          aXULElement.GetBoolAttr(nsGkAtoms::tooltiptext);
598 }
599 
BindToTree(BindContext & aContext,nsINode & aParent)600 nsresult nsXULElement::BindToTree(BindContext& aContext, nsINode& aParent) {
601   nsresult rv = nsStyledElement::BindToTree(aContext, aParent);
602   NS_ENSURE_SUCCESS(rv, rv);
603 
604   if (!IsInComposedDoc()) {
605     return rv;
606   }
607 
608   Document& doc = aContext.OwnerDoc();
609   if (!IsInNativeAnonymousSubtree() && !doc.AllowXULXBL() &&
610       !doc.HasWarnedAbout(DeprecatedOperations::eImportXULIntoContent)) {
611     nsContentUtils::AddScriptRunner(new XULInContentErrorReporter(doc));
612   }
613 
614 #ifdef DEBUG
615   if (!doc.AllowXULXBL() && !doc.IsUnstyledDocument()) {
616     // To save CPU cycles and memory, non-XUL documents only load the user
617     // agent style sheet rules for a minimal set of XUL elements such as
618     // 'scrollbar' that may be created implicitly for their content (those
619     // rules being in minimal-xul.css).
620     //
621     // This assertion makes sure no other XUL element is used in a non-XUL
622     // document.
623     nsAtom* tag = NodeInfo()->NameAtom();
624     MOZ_ASSERT(
625         // scrollbar parts
626         tag == nsGkAtoms::scrollbar || tag == nsGkAtoms::scrollbarbutton ||
627             tag == nsGkAtoms::scrollcorner || tag == nsGkAtoms::slider ||
628             tag == nsGkAtoms::thumb ||
629             // other
630             tag == nsGkAtoms::resizer || tag == nsGkAtoms::label,
631         "Unexpected XUL element in non-XUL doc");
632   }
633 #endif
634 
635   // Within Bug 1492063 and its dependencies we started to apply a
636   // CSP to system privileged about pages. Since some about: pages
637   // are implemented in *.xul files we added this workaround to
638   // apply a CSP to them. To do so, we check the introduced custom
639   // attribute 'csp' on the root element.
640   if (doc.GetRootElement() == this) {
641     nsAutoString cspPolicyStr;
642     GetAttr(kNameSpaceID_None, nsGkAtoms::csp, cspPolicyStr);
643 
644 #ifdef DEBUG
645     {
646       nsCOMPtr<nsIContentSecurityPolicy> docCSP = doc.GetCsp();
647       uint32_t policyCount = 0;
648       if (docCSP) {
649         docCSP->GetPolicyCount(&policyCount);
650       }
651       MOZ_ASSERT(policyCount == 0, "how come we already have a policy?");
652     }
653 #endif
654 
655     CSP_ApplyMetaCSPToDoc(doc, cspPolicyStr);
656   }
657 
658   if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
659     // Create our XUL key listener and hook it up.
660     XULKeySetGlobalKeyListener::AttachKeyHandler(this);
661   }
662 
663   RegUnRegAccessKey(true);
664 
665   if (NeedTooltipSupport(*this)) {
666     AddTooltipSupport();
667   }
668 
669   if (XULBroadcastManager::MayNeedListener(*this)) {
670     if (!doc.HasXULBroadcastManager()) {
671       doc.InitializeXULBroadcastManager();
672     }
673     XULBroadcastManager* broadcastManager = doc.GetXULBroadcastManager();
674     broadcastManager->AddListener(this);
675   }
676   return rv;
677 }
678 
UnbindFromTree(bool aNullParent)679 void nsXULElement::UnbindFromTree(bool aNullParent) {
680   if (NodeInfo()->Equals(nsGkAtoms::keyset, kNameSpaceID_XUL)) {
681     XULKeySetGlobalKeyListener::DetachKeyHandler(this);
682   }
683 
684   RegUnRegAccessKey(false);
685 
686   if (NeedTooltipSupport(*this)) {
687     RemoveTooltipSupport();
688   }
689 
690   Document* doc = GetComposedDoc();
691   if (doc && doc->HasXULBroadcastManager() &&
692       XULBroadcastManager::MayNeedListener(*this)) {
693     RefPtr<XULBroadcastManager> broadcastManager =
694         doc->GetXULBroadcastManager();
695     broadcastManager->RemoveListener(this);
696   }
697 
698   // mControllers can own objects that are implemented
699   // in JavaScript (such as some implementations of
700   // nsIControllers.  These objects prevent their global
701   // object's script object from being garbage collected,
702   // which means JS continues to hold an owning reference
703   // to the nsGlobalWindow, which owns the document,
704   // which owns this content.  That's a cycle, so we break
705   // it here.  (It might be better to break this by releasing
706   // mDocument in nsGlobalWindow::SetDocShell, but I'm not
707   // sure whether that would fix all possible cycles through
708   // mControllers.)
709   nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
710   if (slots) {
711     slots->mControllers = nullptr;
712   }
713 
714   nsStyledElement::UnbindFromTree(aNullParent);
715 }
716 
DoneAddingChildren(bool aHaveNotified)717 void nsXULElement::DoneAddingChildren(bool aHaveNotified) {
718   if (IsXULElement(nsGkAtoms::linkset)) {
719     Document* doc = GetComposedDoc();
720     if (doc) {
721       doc->OnL10nResourceContainerParsed();
722     }
723   }
724 }
725 
RegUnRegAccessKey(bool aDoReg)726 void nsXULElement::RegUnRegAccessKey(bool aDoReg) {
727   // Don't try to register for unsupported elements
728   if (!SupportsAccessKey()) {
729     return;
730   }
731 
732   nsStyledElement::RegUnRegAccessKey(aDoReg);
733 }
734 
SupportsAccessKey() const735 bool nsXULElement::SupportsAccessKey() const {
736   if (NodeInfo()->Equals(nsGkAtoms::label) && HasAttr(nsGkAtoms::control)) {
737     return true;
738   }
739 
740   // XXX(ntim): check if description[value] or description[accesskey] are
741   // actually used, remove `value` from {Before/After}SetAttr if not the case
742   if (NodeInfo()->Equals(nsGkAtoms::description) && HasAttr(nsGkAtoms::value) &&
743       HasAttr(nsGkAtoms::control)) {
744     return true;
745   }
746 
747   return IsAnyOfXULElements(nsGkAtoms::button, nsGkAtoms::toolbarbutton,
748                             nsGkAtoms::checkbox, nsGkAtoms::tab,
749                             nsGkAtoms::radio);
750 }
751 
BeforeSetAttr(int32_t aNamespaceID,nsAtom * aName,const nsAttrValueOrString * aValue,bool aNotify)752 nsresult nsXULElement::BeforeSetAttr(int32_t aNamespaceID, nsAtom* aName,
753                                      const nsAttrValueOrString* aValue,
754                                      bool aNotify) {
755   if (aNamespaceID == kNameSpaceID_None &&
756       (aName == nsGkAtoms::accesskey || aName == nsGkAtoms::control ||
757        aName == nsGkAtoms::value)) {
758     RegUnRegAccessKey(false);
759   } else if (aNamespaceID == kNameSpaceID_None &&
760              (aName == nsGkAtoms::command || aName == nsGkAtoms::observes) &&
761              IsInUncomposedDoc()) {
762     //         XXX sXBL/XBL2 issue! Owner or current document?
763     // XXX Why does this not also remove broadcast listeners if the
764     // "element" attribute was changed on an <observer>?
765     nsAutoString oldValue;
766     GetAttr(kNameSpaceID_None, nsGkAtoms::observes, oldValue);
767     if (oldValue.IsEmpty()) {
768       GetAttr(kNameSpaceID_None, nsGkAtoms::command, oldValue);
769     }
770 
771     Document* doc = GetUncomposedDoc();
772     if (!oldValue.IsEmpty() && doc->HasXULBroadcastManager()) {
773       RefPtr<XULBroadcastManager> broadcastManager =
774           doc->GetXULBroadcastManager();
775       broadcastManager->RemoveListener(this);
776     }
777   } else if (aNamespaceID == kNameSpaceID_None && aValue &&
778              mNodeInfo->Equals(nsGkAtoms::window) &&
779              aName == nsGkAtoms::chromemargin) {
780     nsAttrValue attrValue;
781     // Make sure the margin format is valid first
782     if (!attrValue.ParseIntMarginValue(aValue->String())) {
783       return NS_ERROR_INVALID_ARG;
784     }
785   } else if (aNamespaceID == kNameSpaceID_None &&
786              aName == nsGkAtoms::usercontextid) {
787     nsAutoString oldValue;
788     bool hasAttribute =
789         GetAttr(kNameSpaceID_None, nsGkAtoms::usercontextid, oldValue);
790     if (hasAttribute && (!aValue || !aValue->String().Equals(oldValue))) {
791       MOZ_ASSERT(false, "Changing usercontextid is not allowed.");
792       return NS_ERROR_INVALID_ARG;
793     }
794   }
795 
796   return nsStyledElement::BeforeSetAttr(aNamespaceID, aName, aValue, aNotify);
797 }
798 
AfterSetAttr(int32_t aNamespaceID,nsAtom * aName,const nsAttrValue * aValue,const nsAttrValue * aOldValue,nsIPrincipal * aSubjectPrincipal,bool aNotify)799 nsresult nsXULElement::AfterSetAttr(int32_t aNamespaceID, nsAtom* aName,
800                                     const nsAttrValue* aValue,
801                                     const nsAttrValue* aOldValue,
802                                     nsIPrincipal* aSubjectPrincipal,
803                                     bool aNotify) {
804   if (aNamespaceID == kNameSpaceID_None) {
805     if (aValue) {
806       AddListenerForAttributeIfNeeded(aName);
807     }
808 
809     if (aName == nsGkAtoms::accesskey || aName == nsGkAtoms::control ||
810         aName == nsGkAtoms::value) {
811       RegUnRegAccessKey(true);
812     } else if (aName == nsGkAtoms::tooltip || aName == nsGkAtoms::tooltiptext) {
813       if (!!aValue != !!aOldValue && IsInComposedDoc() &&
814           !NodeInfo()->Equals(nsGkAtoms::treechildren)) {
815         if (aValue) {
816           AddTooltipSupport();
817         } else {
818           RemoveTooltipSupport();
819         }
820       }
821     }
822     Document* doc = GetComposedDoc();
823     if (doc && doc->HasXULBroadcastManager()) {
824       RefPtr<XULBroadcastManager> broadcastManager =
825           doc->GetXULBroadcastManager();
826       broadcastManager->AttributeChanged(this, aNamespaceID, aName);
827     }
828     if (doc && XULBroadcastManager::MayNeedListener(*this)) {
829       if (!doc->HasXULBroadcastManager()) {
830         doc->InitializeXULBroadcastManager();
831       }
832       XULBroadcastManager* broadcastManager = doc->GetXULBroadcastManager();
833       broadcastManager->AddListener(this);
834     }
835 
836     // XXX need to check if they're changing an event handler: if
837     // so, then we need to unhook the old one.  Or something.
838   }
839 
840   return nsStyledElement::AfterSetAttr(aNamespaceID, aName, aValue, aOldValue,
841                                        aSubjectPrincipal, aNotify);
842 }
843 
AddTooltipSupport()844 void nsXULElement::AddTooltipSupport() {
845   nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance();
846   if (!listener) {
847     return;
848   }
849 
850   listener->AddTooltipSupport(this);
851 }
852 
RemoveTooltipSupport()853 void nsXULElement::RemoveTooltipSupport() {
854   nsXULTooltipListener* listener = nsXULTooltipListener::GetInstance();
855   if (!listener) {
856     return;
857   }
858 
859   listener->RemoveTooltipSupport(this);
860 }
861 
ParseAttribute(int32_t aNamespaceID,nsAtom * aAttribute,const nsAString & aValue,nsIPrincipal * aMaybeScriptedPrincipal,nsAttrValue & aResult)862 bool nsXULElement::ParseAttribute(int32_t aNamespaceID, nsAtom* aAttribute,
863                                   const nsAString& aValue,
864                                   nsIPrincipal* aMaybeScriptedPrincipal,
865                                   nsAttrValue& aResult) {
866   if (aNamespaceID == kNameSpaceID_None && aAttribute == nsGkAtoms::tabindex) {
867     return aResult.ParseIntValue(aValue);
868   }
869 
870   // Parse into a nsAttrValue
871   if (!nsStyledElement::ParseAttribute(aNamespaceID, aAttribute, aValue,
872                                        aMaybeScriptedPrincipal, aResult)) {
873     // Fall back to parsing as atom for short values
874     aResult.ParseStringOrAtom(aValue);
875   }
876 
877   return true;
878 }
879 
DestroyContent()880 void nsXULElement::DestroyContent() {
881   nsExtendedDOMSlots* slots = GetExistingExtendedDOMSlots();
882   if (slots) {
883     slots->mControllers = nullptr;
884   }
885 
886   nsStyledElement::DestroyContent();
887 }
888 
889 #ifdef MOZ_DOM_LIST
List(FILE * out,int32_t aIndent) const890 void nsXULElement::List(FILE* out, int32_t aIndent) const {
891   nsCString prefix("XUL");
892   if (HasSlots()) {
893     prefix.Append('*');
894   }
895   prefix.Append(' ');
896 
897   nsStyledElement::List(out, aIndent, prefix);
898 }
899 #endif
900 
IsEventStoppedFromAnonymousScrollbar(EventMessage aMessage)901 bool nsXULElement::IsEventStoppedFromAnonymousScrollbar(EventMessage aMessage) {
902   return (IsRootOfNativeAnonymousSubtree() &&
903           IsAnyOfXULElements(nsGkAtoms::scrollbar, nsGkAtoms::scrollcorner) &&
904           (aMessage == eMouseClick || aMessage == eMouseDoubleClick ||
905            aMessage == eXULCommand || aMessage == eContextMenu ||
906            aMessage == eDragStart || aMessage == eMouseAuxClick));
907 }
908 
DispatchXULCommand(const EventChainVisitor & aVisitor,nsAutoString & aCommand)909 nsresult nsXULElement::DispatchXULCommand(const EventChainVisitor& aVisitor,
910                                           nsAutoString& aCommand) {
911   // XXX sXBL/XBL2 issue! Owner or current document?
912   nsCOMPtr<Document> doc = GetUncomposedDoc();
913   NS_ENSURE_STATE(doc);
914   RefPtr<Element> commandElt = doc->GetElementById(aCommand);
915   if (commandElt) {
916     // Create a new command event to dispatch to the element
917     // pointed to by the command attribute. The new event's
918     // sourceEvent will be the original command event that we're
919     // handling.
920     RefPtr<Event> event = aVisitor.mDOMEvent;
921     uint16_t inputSource = MouseEvent_Binding::MOZ_SOURCE_UNKNOWN;
922     int16_t button = 0;
923     while (event) {
924       NS_ENSURE_STATE(event->GetOriginalTarget() != commandElt);
925       RefPtr<XULCommandEvent> commandEvent = event->AsXULCommandEvent();
926       if (commandEvent) {
927         event = commandEvent->GetSourceEvent();
928         inputSource = commandEvent->InputSource();
929         button = commandEvent->Button();
930       } else {
931         event = nullptr;
932       }
933     }
934     WidgetInputEvent* orig = aVisitor.mEvent->AsInputEvent();
935     nsContentUtils::DispatchXULCommand(
936         commandElt, orig->IsTrusted(), MOZ_KnownLive(aVisitor.mDOMEvent),
937         nullptr, orig->IsControl(), orig->IsAlt(), orig->IsShift(),
938         orig->IsMeta(), inputSource, button);
939   } else {
940     NS_WARNING("A XUL element is attached to a command that doesn't exist!\n");
941   }
942   return NS_OK;
943 }
944 
GetEventTargetParent(EventChainPreVisitor & aVisitor)945 void nsXULElement::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
946   aVisitor.mForceContentDispatch = true;  // FIXME! Bug 329119
947   if (IsEventStoppedFromAnonymousScrollbar(aVisitor.mEvent->mMessage)) {
948     // Don't propagate these events from native anonymous scrollbar.
949     aVisitor.mCanHandle = true;
950     aVisitor.SetParentTarget(nullptr, false);
951     return;
952   }
953   if (aVisitor.mEvent->mMessage == eXULCommand &&
954       aVisitor.mEvent->mClass == eInputEventClass &&
955       aVisitor.mEvent->mOriginalTarget == static_cast<nsIContent*>(this) &&
956       !IsXULElement(nsGkAtoms::command)) {
957     // Check that we really have an xul command event. That will be handled
958     // in a special way.
959     // See if we have a command elt.  If so, we execute on the command
960     // instead of on our content element.
961     if (aVisitor.mDOMEvent && aVisitor.mDOMEvent->AsXULCommandEvent() &&
962         HasNonEmptyAttr(nsGkAtoms::command)) {
963       // Stop building the event target chain for the original event.
964       // We don't want it to propagate to any DOM nodes.
965       aVisitor.mCanHandle = false;
966       aVisitor.mAutomaticChromeDispatch = false;
967       // Dispatch XUL command in PreHandleEvent to prevent it breaks event
968       // target chain creation
969       aVisitor.mWantsPreHandleEvent = true;
970       aVisitor.mItemFlags |= NS_DISPATCH_XUL_COMMAND;
971       return;
972     }
973   }
974 
975   nsStyledElement::GetEventTargetParent(aVisitor);
976 }
977 
PreHandleEvent(EventChainVisitor & aVisitor)978 nsresult nsXULElement::PreHandleEvent(EventChainVisitor& aVisitor) {
979   if (aVisitor.mItemFlags & NS_DISPATCH_XUL_COMMAND) {
980     nsAutoString command;
981     GetAttr(kNameSpaceID_None, nsGkAtoms::command, command);
982     MOZ_ASSERT(!command.IsEmpty());
983     return DispatchXULCommand(aVisitor, command);
984   }
985   return nsStyledElement::PreHandleEvent(aVisitor);
986 }
987 
988 //----------------------------------------------------------------------
989 // Implementation methods
990 
GetAttributeChangeHint(const nsAtom * aAttribute,int32_t aModType) const991 nsChangeHint nsXULElement::GetAttributeChangeHint(const nsAtom* aAttribute,
992                                                   int32_t aModType) const {
993   if (aAttribute == nsGkAtoms::value &&
994       (aModType == MutationEvent_Binding::REMOVAL ||
995        aModType == MutationEvent_Binding::ADDITION) &&
996       IsAnyOfXULElements(nsGkAtoms::label, nsGkAtoms::description)) {
997     // Label and description dynamically morph between a normal
998     // block and a cropping single-line XUL text frame.  If the
999     // value attribute is being added or removed, then we need to
1000     // return a hint of frame change.  (See bugzilla bug 95475 for
1001     // details.)
1002     return nsChangeHint_ReconstructFrame;
1003   }
1004 
1005   if (aAttribute == nsGkAtoms::type &&
1006       IsAnyOfXULElements(nsGkAtoms::toolbarbutton, nsGkAtoms::button)) {
1007     // type=menu switches from a button frame to a menu frame.
1008     return nsChangeHint_ReconstructFrame;
1009   }
1010 
1011   return nsChangeHint(0);
1012 }
1013 
NS_IMETHODIMP_(bool)1014 NS_IMETHODIMP_(bool)
1015 nsXULElement::IsAttributeMapped(const nsAtom* aAttribute) const {
1016   return false;
1017 }
1018 
GetControllers(ErrorResult & rv)1019 nsIControllers* nsXULElement::GetControllers(ErrorResult& rv) {
1020   if (!Controllers()) {
1021     nsExtendedDOMSlots* slots = ExtendedDOMSlots();
1022 
1023     slots->mControllers = new nsXULControllers();
1024   }
1025 
1026   return Controllers();
1027 }
1028 
Click(CallerType aCallerType)1029 void nsXULElement::Click(CallerType aCallerType) {
1030   ClickWithInputSource(MouseEvent_Binding::MOZ_SOURCE_UNKNOWN,
1031                        aCallerType == CallerType::System);
1032 }
1033 
ClickWithInputSource(uint16_t aInputSource,bool aIsTrustedEvent)1034 void nsXULElement::ClickWithInputSource(uint16_t aInputSource,
1035                                         bool aIsTrustedEvent) {
1036   if (BoolAttrIsTrue(nsGkAtoms::disabled)) return;
1037 
1038   nsCOMPtr<Document> doc = GetComposedDoc();  // Strong just in case
1039   if (doc) {
1040     RefPtr<nsPresContext> context = doc->GetPresContext();
1041     if (context) {
1042       // strong ref to PresContext so events don't destroy it
1043 
1044       WidgetMouseEvent eventDown(aIsTrustedEvent, eMouseDown, nullptr,
1045                                  WidgetMouseEvent::eReal);
1046       WidgetMouseEvent eventUp(aIsTrustedEvent, eMouseUp, nullptr,
1047                                WidgetMouseEvent::eReal);
1048       WidgetMouseEvent eventClick(aIsTrustedEvent, eMouseClick, nullptr,
1049                                   WidgetMouseEvent::eReal);
1050       eventDown.mInputSource = eventUp.mInputSource = eventClick.mInputSource =
1051           aInputSource;
1052 
1053       // send mouse down
1054       nsEventStatus status = nsEventStatus_eIgnore;
1055       EventDispatcher::Dispatch(static_cast<nsIContent*>(this), context,
1056                                 &eventDown, nullptr, &status);
1057 
1058       // send mouse up
1059       status = nsEventStatus_eIgnore;  // reset status
1060       EventDispatcher::Dispatch(static_cast<nsIContent*>(this), context,
1061                                 &eventUp, nullptr, &status);
1062 
1063       // send mouse click
1064       status = nsEventStatus_eIgnore;  // reset status
1065       EventDispatcher::Dispatch(static_cast<nsIContent*>(this), context,
1066                                 &eventClick, nullptr, &status);
1067 
1068       // If the click has been prevented, lets skip the command call
1069       // this is how a physical click works
1070       if (status == nsEventStatus_eConsumeNoDefault) {
1071         return;
1072       }
1073     }
1074   }
1075 
1076   // oncommand is fired when an element is clicked...
1077   DoCommand();
1078 }
1079 
DoCommand()1080 void nsXULElement::DoCommand() {
1081   nsCOMPtr<Document> doc = GetComposedDoc();  // strong just in case
1082   if (doc) {
1083     RefPtr<nsXULElement> self = this;
1084     nsContentUtils::DispatchXULCommand(self, true);
1085   }
1086 }
1087 
IsNodeOfType(uint32_t aFlags) const1088 bool nsXULElement::IsNodeOfType(uint32_t aFlags) const { return false; }
1089 
AddPopupListener(nsAtom * aName)1090 nsresult nsXULElement::AddPopupListener(nsAtom* aName) {
1091   // Add a popup listener to the element
1092   bool isContext =
1093       (aName == nsGkAtoms::context || aName == nsGkAtoms::contextmenu);
1094   uint32_t listenerFlag = isContext ? XUL_ELEMENT_HAS_CONTENTMENU_LISTENER
1095                                     : XUL_ELEMENT_HAS_POPUP_LISTENER;
1096 
1097   if (HasFlag(listenerFlag)) {
1098     return NS_OK;
1099   }
1100 
1101   nsCOMPtr<nsIDOMEventListener> listener =
1102       new nsXULPopupListener(this, isContext);
1103 
1104   // Add the popup as a listener on this element.
1105   EventListenerManager* manager = GetOrCreateListenerManager();
1106   SetFlags(listenerFlag);
1107 
1108   if (isContext) {
1109     manager->AddEventListenerByType(listener, u"contextmenu"_ns,
1110                                     TrustedEventsAtSystemGroupBubble());
1111   } else {
1112     manager->AddEventListenerByType(listener, u"mousedown"_ns,
1113                                     TrustedEventsAtSystemGroupBubble());
1114   }
1115   return NS_OK;
1116 }
1117 
1118 //----------------------------------------------------------------------
1119 
MakeHeavyweight(nsXULPrototypeElement * aPrototype)1120 nsresult nsXULElement::MakeHeavyweight(nsXULPrototypeElement* aPrototype) {
1121   if (!aPrototype) {
1122     return NS_OK;
1123   }
1124 
1125   size_t i;
1126   nsresult rv;
1127   for (i = 0; i < aPrototype->mAttributes.Length(); ++i) {
1128     nsXULPrototypeAttribute* protoattr = &aPrototype->mAttributes[i];
1129     nsAttrValue attrValue;
1130 
1131     // Style rules need to be cloned.
1132     if (protoattr->mValue.Type() == nsAttrValue::eCSSDeclaration) {
1133       DeclarationBlock* decl = protoattr->mValue.GetCSSDeclarationValue();
1134       RefPtr<DeclarationBlock> declClone = decl->Clone();
1135 
1136       nsString stringValue;
1137       protoattr->mValue.ToString(stringValue);
1138 
1139       attrValue.SetTo(declClone.forget(), &stringValue);
1140     } else {
1141       attrValue.SetTo(protoattr->mValue);
1142     }
1143 
1144     bool oldValueSet;
1145     // XXX we might wanna have a SetAndTakeAttr that takes an nsAttrName
1146     if (protoattr->mName.IsAtom()) {
1147       rv = mAttrs.SetAndSwapAttr(protoattr->mName.Atom(), attrValue,
1148                                  &oldValueSet);
1149     } else {
1150       rv = mAttrs.SetAndSwapAttr(protoattr->mName.NodeInfo(), attrValue,
1151                                  &oldValueSet);
1152     }
1153     NS_ENSURE_SUCCESS(rv, rv);
1154   }
1155   return NS_OK;
1156 }
1157 
BoolAttrIsTrue(nsAtom * aName) const1158 bool nsXULElement::BoolAttrIsTrue(nsAtom* aName) const {
1159   const nsAttrValue* attr = GetAttrInfo(kNameSpaceID_None, aName).mValue;
1160 
1161   return attr && attr->Type() == nsAttrValue::eAtom &&
1162          attr->GetAtomValue() == nsGkAtoms::_true;
1163 }
1164 
IsEventAttributeNameInternal(nsAtom * aName)1165 bool nsXULElement::IsEventAttributeNameInternal(nsAtom* aName) {
1166   return nsContentUtils::IsEventAttributeName(aName, EventNameType_XUL);
1167 }
1168 
WrapNode(JSContext * aCx,JS::Handle<JSObject * > aGivenProto)1169 JSObject* nsXULElement::WrapNode(JSContext* aCx,
1170                                  JS::Handle<JSObject*> aGivenProto) {
1171   return dom::XULElement_Binding::Wrap(aCx, this, aGivenProto);
1172 }
1173 
IsInteractiveHTMLContent() const1174 bool nsXULElement::IsInteractiveHTMLContent() const {
1175   return IsXULElement(nsGkAtoms::menupopup) ||
1176          Element::IsInteractiveHTMLContent();
1177 }
1178 
1179 NS_IMPL_CYCLE_COLLECTION_CLASS(nsXULPrototypeNode)
1180 
1181 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsXULPrototypeNode)
1182   if (tmp->mType == nsXULPrototypeNode::eType_Element) {
1183     static_cast<nsXULPrototypeElement*>(tmp)->Unlink();
1184   }
1185 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
1186 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsXULPrototypeNode)
1187   if (tmp->mType == nsXULPrototypeNode::eType_Element) {
1188     nsXULPrototypeElement* elem = static_cast<nsXULPrototypeElement*>(tmp);
1189     NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb, "mNodeInfo");
1190     cb.NoteNativeChild(elem->mNodeInfo,
1191                        NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
1192     size_t i;
1193     for (i = 0; i < elem->mAttributes.Length(); ++i) {
1194       const nsAttrName& name = elem->mAttributes[i].mName;
1195       if (!name.IsAtom()) {
1196         NS_CYCLE_COLLECTION_NOTE_EDGE_NAME(cb,
1197                                            "mAttributes[i].mName.NodeInfo()");
1198         cb.NoteNativeChild(name.NodeInfo(),
1199                            NS_CYCLE_COLLECTION_PARTICIPANT(NodeInfo));
1200       }
1201     }
1202     ImplCycleCollectionTraverse(cb, elem->mChildren, "mChildren");
1203   }
1204 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXULPrototypeNode)1205 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsXULPrototypeNode)
1206 NS_IMPL_CYCLE_COLLECTION_TRACE_END
1207 
1208 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsXULPrototypeNode, AddRef)
1209 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsXULPrototypeNode, Release)
1210 
1211 //----------------------------------------------------------------------
1212 //
1213 // nsXULPrototypeAttribute
1214 //
1215 
1216 nsXULPrototypeAttribute::~nsXULPrototypeAttribute() {
1217   MOZ_COUNT_DTOR(nsXULPrototypeAttribute);
1218 }
1219 
1220 //----------------------------------------------------------------------
1221 //
1222 // nsXULPrototypeElement
1223 //
1224 
Serialize(nsIObjectOutputStream * aStream,nsXULPrototypeDocument * aProtoDoc,const nsTArray<RefPtr<mozilla::dom::NodeInfo>> * aNodeInfos)1225 nsresult nsXULPrototypeElement::Serialize(
1226     nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
1227     const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
1228   nsresult rv;
1229 
1230   // Write basic prototype data
1231   rv = aStream->Write32(mType);
1232 
1233   // Write Node Info
1234   int32_t index = aNodeInfos->IndexOf(mNodeInfo);
1235   NS_ASSERTION(index >= 0, "unknown mozilla::dom::NodeInfo index");
1236   nsresult tmp = aStream->Write32(index);
1237   if (NS_FAILED(tmp)) {
1238     rv = tmp;
1239   }
1240 
1241   // Write Attributes
1242   tmp = aStream->Write32(mAttributes.Length());
1243   if (NS_FAILED(tmp)) {
1244     rv = tmp;
1245   }
1246 
1247   nsAutoString attributeValue;
1248   size_t i;
1249   for (i = 0; i < mAttributes.Length(); ++i) {
1250     RefPtr<mozilla::dom::NodeInfo> ni;
1251     if (mAttributes[i].mName.IsAtom()) {
1252       ni = mNodeInfo->NodeInfoManager()->GetNodeInfo(
1253           mAttributes[i].mName.Atom(), nullptr, kNameSpaceID_None,
1254           nsINode::ATTRIBUTE_NODE);
1255       NS_ASSERTION(ni, "the nodeinfo should already exist");
1256     } else {
1257       ni = mAttributes[i].mName.NodeInfo();
1258     }
1259 
1260     index = aNodeInfos->IndexOf(ni);
1261     NS_ASSERTION(index >= 0, "unknown mozilla::dom::NodeInfo index");
1262     tmp = aStream->Write32(index);
1263     if (NS_FAILED(tmp)) {
1264       rv = tmp;
1265     }
1266 
1267     mAttributes[i].mValue.ToString(attributeValue);
1268     tmp = aStream->WriteWStringZ(attributeValue.get());
1269     if (NS_FAILED(tmp)) {
1270       rv = tmp;
1271     }
1272   }
1273 
1274   // Now write children
1275   tmp = aStream->Write32(uint32_t(mChildren.Length()));
1276   if (NS_FAILED(tmp)) {
1277     rv = tmp;
1278   }
1279   for (i = 0; i < mChildren.Length(); i++) {
1280     nsXULPrototypeNode* child = mChildren[i].get();
1281     switch (child->mType) {
1282       case eType_Element:
1283       case eType_Text:
1284       case eType_PI:
1285         tmp = child->Serialize(aStream, aProtoDoc, aNodeInfos);
1286         if (NS_FAILED(tmp)) {
1287           rv = tmp;
1288         }
1289         break;
1290       case eType_Script:
1291         tmp = aStream->Write32(child->mType);
1292         if (NS_FAILED(tmp)) {
1293           rv = tmp;
1294         }
1295         nsXULPrototypeScript* script =
1296             static_cast<nsXULPrototypeScript*>(child);
1297 
1298         tmp = aStream->Write8(script->mOutOfLine);
1299         if (NS_FAILED(tmp)) {
1300           rv = tmp;
1301         }
1302         if (!script->mOutOfLine) {
1303           tmp = script->Serialize(aStream, aProtoDoc, aNodeInfos);
1304           if (NS_FAILED(tmp)) {
1305             rv = tmp;
1306           }
1307         } else {
1308           tmp = aStream->WriteCompoundObject(script->mSrcURI,
1309                                              NS_GET_IID(nsIURI), true);
1310           if (NS_FAILED(tmp)) {
1311             rv = tmp;
1312           }
1313 
1314           if (script->HasStencil()) {
1315             // This may return NS_OK without muxing script->mSrcURI's
1316             // data into the cache file, in the case where that
1317             // muxed document is already there (written by a prior
1318             // session, or by an earlier cache episode during this
1319             // session).
1320             tmp = script->SerializeOutOfLine(aStream, aProtoDoc);
1321             if (NS_FAILED(tmp)) {
1322               rv = tmp;
1323             }
1324           }
1325         }
1326         break;
1327     }
1328   }
1329 
1330   return rv;
1331 }
1332 
Deserialize(nsIObjectInputStream * aStream,nsXULPrototypeDocument * aProtoDoc,nsIURI * aDocumentURI,const nsTArray<RefPtr<mozilla::dom::NodeInfo>> * aNodeInfos)1333 nsresult nsXULPrototypeElement::Deserialize(
1334     nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
1335     nsIURI* aDocumentURI,
1336     const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
1337   MOZ_ASSERT(aNodeInfos, "missing nodeinfo array");
1338 
1339   // Read Node Info
1340   uint32_t number = 0;
1341   nsresult rv = aStream->Read32(&number);
1342   if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1343   mNodeInfo = aNodeInfos->SafeElementAt(number, nullptr);
1344   if (!mNodeInfo) {
1345     return NS_ERROR_UNEXPECTED;
1346   }
1347 
1348   // Read Attributes
1349   rv = aStream->Read32(&number);
1350   if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1351   int32_t attributes = int32_t(number);
1352 
1353   if (attributes > 0) {
1354     mAttributes.AppendElements(attributes);
1355 
1356     nsAutoString attributeValue;
1357     for (size_t i = 0; i < mAttributes.Length(); ++i) {
1358       rv = aStream->Read32(&number);
1359       if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1360       mozilla::dom::NodeInfo* ni = aNodeInfos->SafeElementAt(number, nullptr);
1361       if (!ni) {
1362         return NS_ERROR_UNEXPECTED;
1363       }
1364 
1365       mAttributes[i].mName.SetTo(ni);
1366 
1367       rv = aStream->ReadString(attributeValue);
1368       if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1369       rv = SetAttrAt(i, attributeValue, aDocumentURI);
1370       if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1371     }
1372   }
1373 
1374   rv = aStream->Read32(&number);
1375   if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1376   uint32_t numChildren = int32_t(number);
1377 
1378   if (numChildren > 0) {
1379     if (!mChildren.SetCapacity(numChildren, fallible)) {
1380       return NS_ERROR_OUT_OF_MEMORY;
1381     }
1382 
1383     for (uint32_t i = 0; i < numChildren; i++) {
1384       rv = aStream->Read32(&number);
1385       if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1386       Type childType = (Type)number;
1387 
1388       RefPtr<nsXULPrototypeNode> child;
1389 
1390       switch (childType) {
1391         case eType_Element:
1392           child = new nsXULPrototypeElement();
1393           rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos);
1394           if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1395           break;
1396         case eType_Text:
1397           child = new nsXULPrototypeText();
1398           rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos);
1399           if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1400           break;
1401         case eType_PI:
1402           child = new nsXULPrototypePI();
1403           rv = child->Deserialize(aStream, aProtoDoc, aDocumentURI, aNodeInfos);
1404           if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1405           break;
1406         case eType_Script: {
1407           // language version/options obtained during deserialization.
1408           RefPtr<nsXULPrototypeScript> script = new nsXULPrototypeScript(0);
1409 
1410           rv = aStream->ReadBoolean(&script->mOutOfLine);
1411           if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1412           if (!script->mOutOfLine) {
1413             rv = script->Deserialize(aStream, aProtoDoc, aDocumentURI,
1414                                      aNodeInfos);
1415             if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1416           } else {
1417             nsCOMPtr<nsISupports> supports;
1418             rv = aStream->ReadObject(true, getter_AddRefs(supports));
1419             if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1420             script->mSrcURI = do_QueryInterface(supports);
1421 
1422             rv = script->DeserializeOutOfLine(aStream, aProtoDoc);
1423             if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1424           }
1425 
1426           child = std::move(script);
1427           break;
1428         }
1429         default:
1430           MOZ_ASSERT(false, "Unexpected child type!");
1431           return NS_ERROR_UNEXPECTED;
1432       }
1433 
1434       MOZ_ASSERT(child, "Don't append null to mChildren");
1435       MOZ_ASSERT(child->mType == childType);
1436       mChildren.AppendElement(child);
1437 
1438       // Oh dear. Something failed during the deserialization.
1439       // We don't know what.  But likely consequences of failed
1440       // deserializations included calls to |AbortCaching| which
1441       // shuts down the cache and closes our streams.
1442       // If that happens, next time through this loop, we die a messy
1443       // death. So, let's just fail now, and propagate that failure
1444       // upward so that the ChromeProtocolHandler knows it can't use
1445       // a cached chrome channel for this.
1446       if (NS_WARN_IF(NS_FAILED(rv))) return rv;
1447     }
1448   }
1449 
1450   return rv;
1451 }
1452 
SetAttrAt(uint32_t aPos,const nsAString & aValue,nsIURI * aDocumentURI)1453 nsresult nsXULPrototypeElement::SetAttrAt(uint32_t aPos,
1454                                           const nsAString& aValue,
1455                                           nsIURI* aDocumentURI) {
1456   MOZ_ASSERT(aPos < mAttributes.Length(), "out-of-bounds");
1457 
1458   // WARNING!!
1459   // This code is largely duplicated in nsXULElement::SetAttr.
1460   // Any changes should be made to both functions.
1461 
1462   if (!mNodeInfo->NamespaceEquals(kNameSpaceID_XUL)) {
1463     if (mNodeInfo->NamespaceEquals(kNameSpaceID_XHTML) &&
1464         mAttributes[aPos].mName.Equals(nsGkAtoms::is)) {
1465       // We still care about the is attribute set on HTML elements.
1466       mAttributes[aPos].mValue.ParseAtom(aValue);
1467       mIsAtom = mAttributes[aPos].mValue.GetAtomValue();
1468 
1469       return NS_OK;
1470     }
1471 
1472     mAttributes[aPos].mValue.ParseStringOrAtom(aValue);
1473 
1474     return NS_OK;
1475   }
1476 
1477   if (mAttributes[aPos].mName.Equals(nsGkAtoms::id) && !aValue.IsEmpty()) {
1478     mHasIdAttribute = true;
1479     // Store id as atom.
1480     // id="" means that the element has no id. Not that it has
1481     // emptystring as id.
1482     mAttributes[aPos].mValue.ParseAtom(aValue);
1483 
1484     return NS_OK;
1485   } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::is)) {
1486     // Store is as atom.
1487     mAttributes[aPos].mValue.ParseAtom(aValue);
1488     mIsAtom = mAttributes[aPos].mValue.GetAtomValue();
1489 
1490     return NS_OK;
1491   } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::_class)) {
1492     mHasClassAttribute = true;
1493     // Compute the element's class list
1494     mAttributes[aPos].mValue.ParseAtomArray(aValue);
1495 
1496     return NS_OK;
1497   } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::style)) {
1498     mHasStyleAttribute = true;
1499     // Parse the element's 'style' attribute
1500 
1501     // This is basically duplicating what nsINode::NodePrincipal() does
1502     nsIPrincipal* principal = mNodeInfo->NodeInfoManager()->DocumentPrincipal();
1503     // XXX Get correct Base URI (need GetBaseURI on *prototype* element)
1504     // TODO: If we implement Content Security Policy for chrome documents
1505     // as has been discussed, the CSP should be checked here to see if
1506     // inline styles are allowed to be applied.
1507     // XXX No specific specs talk about xul and referrer policy, pass Unset
1508     auto referrerInfo =
1509         MakeRefPtr<ReferrerInfo>(aDocumentURI, ReferrerPolicy::_empty);
1510     auto data = MakeRefPtr<URLExtraData>(aDocumentURI, referrerInfo, principal);
1511     RefPtr<DeclarationBlock> declaration = DeclarationBlock::FromCssText(
1512         aValue, data, eCompatibility_FullStandards, nullptr,
1513         StyleCssRuleType::Style);
1514     if (declaration) {
1515       mAttributes[aPos].mValue.SetTo(declaration.forget(), &aValue);
1516 
1517       return NS_OK;
1518     }
1519     // Don't abort if parsing failed, it could just be malformed css.
1520   } else if (mAttributes[aPos].mName.Equals(nsGkAtoms::tabindex)) {
1521     mAttributes[aPos].mValue.ParseIntValue(aValue);
1522 
1523     return NS_OK;
1524   }
1525 
1526   mAttributes[aPos].mValue.ParseStringOrAtom(aValue);
1527 
1528   return NS_OK;
1529 }
1530 
Unlink()1531 void nsXULPrototypeElement::Unlink() {
1532   mAttributes.Clear();
1533   mChildren.Clear();
1534 }
1535 
1536 //----------------------------------------------------------------------
1537 //
1538 // nsXULPrototypeScript
1539 //
1540 
nsXULPrototypeScript(uint32_t aLineNo)1541 nsXULPrototypeScript::nsXULPrototypeScript(uint32_t aLineNo)
1542     : nsXULPrototypeNode(eType_Script),
1543       mLineNo(aLineNo),
1544       mSrcLoading(false),
1545       mOutOfLine(true),
1546       mSrcLoadWaiters(nullptr),
1547       mStencil(nullptr) {}
1548 
WriteStencil(nsIObjectOutputStream * aStream,JSContext * aCx,JS::Stencil * aStencil)1549 static nsresult WriteStencil(nsIObjectOutputStream* aStream, JSContext* aCx,
1550                              JS::Stencil* aStencil) {
1551   JS::TranscodeBuffer buffer;
1552   JS::TranscodeResult code;
1553   code = JS::EncodeStencil(aCx, aStencil, buffer);
1554 
1555   if (code != JS::TranscodeResult::Ok) {
1556     if (code == JS::TranscodeResult::Throw) {
1557       JS_ClearPendingException(aCx);
1558       return NS_ERROR_OUT_OF_MEMORY;
1559     }
1560 
1561     MOZ_ASSERT(IsTranscodeFailureResult(code));
1562     return NS_ERROR_FAILURE;
1563   }
1564 
1565   size_t size = buffer.length();
1566   if (size > UINT32_MAX) {
1567     return NS_ERROR_FAILURE;
1568   }
1569   nsresult rv = aStream->Write32(size);
1570   if (NS_SUCCEEDED(rv)) {
1571     // Ideally we could just pass "buffer" here.  See bug 1566574.
1572     rv = aStream->WriteBytes(Span(buffer.begin(), size));
1573   }
1574 
1575   return rv;
1576 }
1577 
ReadStencil(nsIObjectInputStream * aStream,JSContext * aCx,const JS::DecodeOptions & aOptions,JS::Stencil ** aStencilOut)1578 static nsresult ReadStencil(nsIObjectInputStream* aStream, JSContext* aCx,
1579                             const JS::DecodeOptions& aOptions,
1580                             JS::Stencil** aStencilOut) {
1581   // We don't serialize mutedError-ness of scripts, which is fine as long as
1582   // we only serialize system and XUL-y things. We can detect this by checking
1583   // where the caller wants us to deserialize.
1584   //
1585   // CompilationScope() could theoretically GC, so get that out of the way
1586   // before comparing to the cx global.
1587   JSObject* loaderGlobal = xpc::CompilationScope();
1588   MOZ_RELEASE_ASSERT(nsContentUtils::IsSystemCaller(aCx) ||
1589                      JS::CurrentGlobalOrNull(aCx) == loaderGlobal);
1590 
1591   uint32_t size;
1592   nsresult rv = aStream->Read32(&size);
1593   if (NS_FAILED(rv)) {
1594     return rv;
1595   }
1596 
1597   char* data;
1598   rv = aStream->ReadBytes(size, &data);
1599   if (NS_FAILED(rv)) {
1600     return rv;
1601   }
1602 
1603   // The decoded stencil shouldn't borrow from the XDR buffer.
1604   MOZ_ASSERT(!aOptions.borrowBuffer);
1605   auto cleanupData = MakeScopeExit([&]() { free(data); });
1606 
1607   JS::TranscodeRange range(reinterpret_cast<uint8_t*>(data), size);
1608 
1609   {
1610     JS::TranscodeResult code;
1611     RefPtr<JS::Stencil> stencil;
1612     code = JS::DecodeStencil(aCx, aOptions, range, getter_AddRefs(stencil));
1613     if (code != JS::TranscodeResult::Ok) {
1614       if (code == JS::TranscodeResult::Throw) {
1615         JS_ClearPendingException(aCx);
1616         return NS_ERROR_OUT_OF_MEMORY;
1617       }
1618 
1619       MOZ_ASSERT(IsTranscodeFailureResult(code));
1620       return NS_ERROR_FAILURE;
1621     }
1622 
1623     stencil.forget(aStencilOut);
1624   }
1625 
1626   return rv;
1627 }
1628 
FillCompileOptions(JS::CompileOptions & options)1629 void nsXULPrototypeScript::FillCompileOptions(JS::CompileOptions& options) {
1630   // If the script was inline, tell the JS parser to save source for
1631   // Function.prototype.toSource(). If it's out of line, we retrieve the
1632   // source from the files on demand.
1633   options.setSourceIsLazy(mOutOfLine);
1634 }
1635 
Serialize(nsIObjectOutputStream * aStream,nsXULPrototypeDocument * aProtoDoc,const nsTArray<RefPtr<mozilla::dom::NodeInfo>> * aNodeInfos)1636 nsresult nsXULPrototypeScript::Serialize(
1637     nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
1638     const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
1639   NS_ENSURE_TRUE(aProtoDoc, NS_ERROR_UNEXPECTED);
1640 
1641   AutoJSAPI jsapi;
1642   if (!jsapi.Init(xpc::CompilationScope())) {
1643     return NS_ERROR_UNEXPECTED;
1644   }
1645 
1646   NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mStencil,
1647                "script source still loading when serializing?!");
1648   if (!mStencil) return NS_ERROR_FAILURE;
1649 
1650   // Write basic prototype data
1651   nsresult rv;
1652   rv = aStream->Write32(mLineNo);
1653   if (NS_FAILED(rv)) return rv;
1654   rv = aStream->Write32(0);  // See bug 1418294.
1655   if (NS_FAILED(rv)) return rv;
1656 
1657   JSContext* cx = jsapi.cx();
1658   MOZ_ASSERT(xpc::CompilationScope() == JS::CurrentGlobalOrNull(cx));
1659 
1660   return WriteStencil(aStream, cx, mStencil);
1661 }
1662 
SerializeOutOfLine(nsIObjectOutputStream * aStream,nsXULPrototypeDocument * aProtoDoc)1663 nsresult nsXULPrototypeScript::SerializeOutOfLine(
1664     nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc) {
1665   if (!mSrcURI->SchemeIs("chrome"))
1666     // Don't cache scripts that don't come from chrome uris.
1667     return NS_ERROR_NOT_IMPLEMENTED;
1668 
1669   nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
1670   if (!cache) return NS_ERROR_OUT_OF_MEMORY;
1671 
1672   NS_ASSERTION(cache->IsEnabled(),
1673                "writing to the cache file, but the XUL cache is off?");
1674   bool exists;
1675   cache->HasData(mSrcURI, &exists);
1676 
1677   /* return will be NS_OK from GetAsciiSpec.
1678    * that makes no sense.
1679    * nor does returning NS_OK from HasMuxedDocument.
1680    * XXX return something meaningful.
1681    */
1682   if (exists) return NS_OK;
1683 
1684   nsCOMPtr<nsIObjectOutputStream> oos;
1685   nsresult rv = cache->GetOutputStream(mSrcURI, getter_AddRefs(oos));
1686   NS_ENSURE_SUCCESS(rv, rv);
1687 
1688   nsresult tmp = Serialize(oos, aProtoDoc, nullptr);
1689   if (NS_FAILED(tmp)) {
1690     rv = tmp;
1691   }
1692   tmp = cache->FinishOutputStream(mSrcURI);
1693   if (NS_FAILED(tmp)) {
1694     rv = tmp;
1695   }
1696 
1697   if (NS_FAILED(rv)) cache->AbortCaching();
1698   return rv;
1699 }
1700 
Deserialize(nsIObjectInputStream * aStream,nsXULPrototypeDocument * aProtoDoc,nsIURI * aDocumentURI,const nsTArray<RefPtr<mozilla::dom::NodeInfo>> * aNodeInfos)1701 nsresult nsXULPrototypeScript::Deserialize(
1702     nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
1703     nsIURI* aDocumentURI,
1704     const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
1705   nsresult rv;
1706   NS_ASSERTION(!mSrcLoading || mSrcLoadWaiters != nullptr || !mStencil,
1707                "prototype script not well-initialized when deserializing?!");
1708 
1709   // Read basic prototype data
1710   rv = aStream->Read32(&mLineNo);
1711   if (NS_FAILED(rv)) return rv;
1712   uint32_t dummy;
1713   rv = aStream->Read32(&dummy);  // See bug 1418294.
1714   if (NS_FAILED(rv)) return rv;
1715 
1716   AutoJSAPI jsapi;
1717   if (!jsapi.Init(xpc::CompilationScope())) {
1718     return NS_ERROR_UNEXPECTED;
1719   }
1720   JSContext* cx = jsapi.cx();
1721 
1722   JS::DecodeOptions options;
1723   RefPtr<JS::Stencil> newStencil;
1724   rv = ReadStencil(aStream, cx, options, getter_AddRefs(newStencil));
1725   NS_ENSURE_SUCCESS(rv, rv);
1726   Set(newStencil);
1727   return NS_OK;
1728 }
1729 
DeserializeOutOfLine(nsIObjectInputStream * aInput,nsXULPrototypeDocument * aProtoDoc)1730 nsresult nsXULPrototypeScript::DeserializeOutOfLine(
1731     nsIObjectInputStream* aInput, nsXULPrototypeDocument* aProtoDoc) {
1732   // Keep track of failure via rv, so we can
1733   // AbortCaching if things look bad.
1734   nsresult rv = NS_OK;
1735   nsXULPrototypeCache* cache = nsXULPrototypeCache::GetInstance();
1736 
1737   nsCOMPtr<nsIObjectInputStream> objectInput = aInput;
1738   if (cache) {
1739     bool useXULCache = true;
1740     if (mSrcURI) {
1741       // NB: we must check the XUL script cache early, to avoid
1742       // multiple deserialization attempts for a given script.
1743       // Note that PrototypeDocumentContentSink::LoadScript
1744       // checks the XUL script cache too, in order to handle the
1745       // serialization case.
1746       //
1747       // We need do this only for <script src='strres.js'> and the
1748       // like, i.e., out-of-line scripts that are included by several
1749       // different XUL documents stored in the cache file.
1750       useXULCache = cache->IsEnabled();
1751 
1752       if (useXULCache) {
1753         RefPtr<JS::Stencil> newStencil = cache->GetStencil(mSrcURI);
1754         if (newStencil) {
1755           Set(newStencil);
1756         }
1757       }
1758     }
1759 
1760     if (!mStencil) {
1761       if (mSrcURI) {
1762         rv = cache->GetInputStream(mSrcURI, getter_AddRefs(objectInput));
1763       }
1764       // If !mSrcURI, we have an inline script. We shouldn't have
1765       // to do anything else in that case, I think.
1766 
1767       // We do reflect errors into rv, but our caller may want to
1768       // ignore our return value, because mStencil will be null
1769       // after any error, and that suffices to cause the script to
1770       // be reloaded (from the src= URI, if any) and recompiled.
1771       // We're better off slow-loading than bailing out due to a
1772       // error.
1773       if (NS_SUCCEEDED(rv))
1774         rv = Deserialize(objectInput, aProtoDoc, nullptr, nullptr);
1775 
1776       if (NS_SUCCEEDED(rv)) {
1777         if (useXULCache && mSrcURI && mSrcURI->SchemeIs("chrome")) {
1778           cache->PutStencil(mSrcURI, GetStencil());
1779         }
1780         cache->FinishInputStream(mSrcURI);
1781       } else {
1782         // If mSrcURI is not in the cache,
1783         // rv will be NS_ERROR_NOT_AVAILABLE and we'll try to
1784         // update the cache file to hold a serialization of
1785         // this script, once it has finished loading.
1786         if (rv != NS_ERROR_NOT_AVAILABLE) cache->AbortCaching();
1787       }
1788     }
1789   }
1790   return rv;
1791 }
1792 
1793 class NotifyOffThreadScriptCompletedRunnable : public Runnable {
1794   // An array of all outstanding script receivers. All reference counting of
1795   // these objects happens on the main thread. When we return to the main
1796   // thread from script compilation we make sure our receiver is still in
1797   // this array (still alive) before proceeding. This array is cleared during
1798   // shutdown, potentially before all outstanding script compilations have
1799   // finished. We do not need to worry about pointer replay here, because
1800   // a) we should not be starting script compilation after clearing this
1801   // array and b) in all other cases the receiver will still be alive.
1802   static StaticAutoPtr<nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>>
1803       sReceivers;
1804   static bool sSetupClearOnShutdown;
1805 
1806   nsIOffThreadScriptReceiver* mReceiver;
1807   JS::OffThreadToken* mToken;
1808 
1809  public:
NotifyOffThreadScriptCompletedRunnable(nsIOffThreadScriptReceiver * aReceiver,JS::OffThreadToken * aToken)1810   NotifyOffThreadScriptCompletedRunnable(nsIOffThreadScriptReceiver* aReceiver,
1811                                          JS::OffThreadToken* aToken)
1812       : mozilla::Runnable("NotifyOffThreadScriptCompletedRunnable"),
1813         mReceiver(aReceiver),
1814         mToken(aToken) {}
1815 
NoteReceiver(nsIOffThreadScriptReceiver * aReceiver)1816   static void NoteReceiver(nsIOffThreadScriptReceiver* aReceiver) {
1817     if (!sSetupClearOnShutdown) {
1818       ClearOnShutdown(&sReceivers);
1819       sSetupClearOnShutdown = true;
1820       sReceivers = new nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>();
1821     }
1822 
1823     // If we ever crash here, it's because we tried to lazy compile script
1824     // too late in shutdown.
1825     sReceivers->AppendElement(aReceiver);
1826   }
1827 
1828   NS_DECL_NSIRUNNABLE
1829 };
1830 
1831 StaticAutoPtr<nsTArray<nsCOMPtr<nsIOffThreadScriptReceiver>>>
1832     NotifyOffThreadScriptCompletedRunnable::sReceivers;
1833 bool NotifyOffThreadScriptCompletedRunnable::sSetupClearOnShutdown = false;
1834 
1835 NS_IMETHODIMP
Run()1836 NotifyOffThreadScriptCompletedRunnable::Run() {
1837   MOZ_ASSERT(NS_IsMainThread());
1838 
1839   RefPtr<JS::Stencil> stencil;
1840   {
1841     AutoJSAPI jsapi;
1842     if (!jsapi.Init(xpc::CompilationScope())) {
1843       // Now what?  I guess we just leak... this should probably never
1844       // happen.
1845       return NS_ERROR_UNEXPECTED;
1846     }
1847     JSContext* cx = jsapi.cx();
1848     stencil = JS::FinishCompileToStencilOffThread(cx, mToken);
1849   }
1850 
1851   if (!sReceivers) {
1852     // We've already shut down.
1853     return NS_OK;
1854   }
1855 
1856   auto index = sReceivers->IndexOf(mReceiver);
1857   MOZ_RELEASE_ASSERT(index != sReceivers->NoIndex);
1858   nsCOMPtr<nsIOffThreadScriptReceiver> receiver =
1859       std::move((*sReceivers)[index]);
1860   sReceivers->RemoveElementAt(index);
1861 
1862   return receiver->OnScriptCompileComplete(stencil,
1863                                            stencil ? NS_OK : NS_ERROR_FAILURE);
1864 }
1865 
OffThreadScriptReceiverCallback(JS::OffThreadToken * aToken,void * aCallbackData)1866 static void OffThreadScriptReceiverCallback(JS::OffThreadToken* aToken,
1867                                             void* aCallbackData) {
1868   // Be careful not to adjust the refcount on the receiver, as this callback
1869   // may be invoked off the main thread.
1870   nsIOffThreadScriptReceiver* aReceiver =
1871       static_cast<nsIOffThreadScriptReceiver*>(aCallbackData);
1872   RefPtr<NotifyOffThreadScriptCompletedRunnable> notify =
1873       new NotifyOffThreadScriptCompletedRunnable(aReceiver, aToken);
1874   NS_DispatchToMainThread(notify);
1875 }
1876 
Compile(const char16_t * aText,size_t aTextLength,JS::SourceOwnership aOwnership,nsIURI * aURI,uint32_t aLineNo,Document * aDocument,nsIOffThreadScriptReceiver * aOffThreadReceiver)1877 nsresult nsXULPrototypeScript::Compile(
1878     const char16_t* aText, size_t aTextLength, JS::SourceOwnership aOwnership,
1879     nsIURI* aURI, uint32_t aLineNo, Document* aDocument,
1880     nsIOffThreadScriptReceiver* aOffThreadReceiver /* = nullptr */) {
1881   // We'll compile the script in the compilation scope.
1882   AutoJSAPI jsapi;
1883   if (!jsapi.Init(xpc::CompilationScope())) {
1884     if (aOwnership == JS::SourceOwnership::TakeOwnership) {
1885       // In this early-exit case -- before the |srcBuf.init| call will
1886       // own |aText| -- we must relinquish ownership manually.
1887       js_free(const_cast<char16_t*>(aText));
1888     }
1889 
1890     return NS_ERROR_UNEXPECTED;
1891   }
1892   JSContext* cx = jsapi.cx();
1893 
1894   JS::SourceText<char16_t> srcBuf;
1895   if (NS_WARN_IF(!srcBuf.init(cx, aText, aTextLength, aOwnership))) {
1896     return NS_ERROR_FAILURE;
1897   }
1898 
1899   nsAutoCString urlspec;
1900   nsresult rv = aURI->GetSpec(urlspec);
1901   if (NS_WARN_IF(NS_FAILED(rv))) {
1902     return rv;
1903   }
1904 
1905   // Ok, compile it to create a prototype script object!
1906   JS::CompileOptions options(cx);
1907   FillCompileOptions(options);
1908   options.setIntroductionType(mOutOfLine ? "srcScript" : "inlineScript")
1909       .setFileAndLine(urlspec.get(), mOutOfLine ? 1 : aLineNo);
1910 
1911   JS::Rooted<JSObject*> scope(cx, JS::CurrentGlobalOrNull(cx));
1912 
1913   if (aOffThreadReceiver && JS::CanCompileOffThread(cx, options, aTextLength)) {
1914     if (!JS::CompileToStencilOffThread(
1915             cx, options, srcBuf, OffThreadScriptReceiverCallback,
1916             static_cast<void*>(aOffThreadReceiver))) {
1917       JS_ClearPendingException(cx);
1918       return NS_ERROR_OUT_OF_MEMORY;
1919     }
1920     NotifyOffThreadScriptCompletedRunnable::NoteReceiver(aOffThreadReceiver);
1921   } else {
1922     RefPtr<JS::Stencil> stencil =
1923         JS::CompileGlobalScriptToStencil(cx, options, srcBuf);
1924     if (!stencil) {
1925       return NS_ERROR_OUT_OF_MEMORY;
1926     }
1927     Set(stencil);
1928   }
1929   return NS_OK;
1930 }
1931 
InstantiateScript(JSContext * aCx,JS::MutableHandleScript aScript)1932 nsresult nsXULPrototypeScript::InstantiateScript(
1933     JSContext* aCx, JS::MutableHandleScript aScript) {
1934   MOZ_ASSERT(mStencil);
1935 
1936   JS::CompileOptions options(aCx);
1937   FillCompileOptions(options);
1938   JS::InstantiateOptions instantiateOptions(options);
1939   aScript.set(JS::InstantiateGlobalStencil(aCx, instantiateOptions, mStencil));
1940   if (!aScript) {
1941     JS_ClearPendingException(aCx);
1942     return NS_ERROR_OUT_OF_MEMORY;
1943   }
1944 
1945   return NS_OK;
1946 }
1947 
Set(JS::Stencil * aStencil)1948 void nsXULPrototypeScript::Set(JS::Stencil* aStencil) { mStencil = aStencil; }
1949 
1950 //----------------------------------------------------------------------
1951 //
1952 // nsXULPrototypeText
1953 //
1954 
Serialize(nsIObjectOutputStream * aStream,nsXULPrototypeDocument * aProtoDoc,const nsTArray<RefPtr<mozilla::dom::NodeInfo>> * aNodeInfos)1955 nsresult nsXULPrototypeText::Serialize(
1956     nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
1957     const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
1958   nsresult rv;
1959 
1960   // Write basic prototype data
1961   rv = aStream->Write32(mType);
1962 
1963   nsresult tmp = aStream->WriteWStringZ(mValue.get());
1964   if (NS_FAILED(tmp)) {
1965     rv = tmp;
1966   }
1967 
1968   return rv;
1969 }
1970 
Deserialize(nsIObjectInputStream * aStream,nsXULPrototypeDocument * aProtoDoc,nsIURI * aDocumentURI,const nsTArray<RefPtr<mozilla::dom::NodeInfo>> * aNodeInfos)1971 nsresult nsXULPrototypeText::Deserialize(
1972     nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
1973     nsIURI* aDocumentURI,
1974     const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
1975   nsresult rv = aStream->ReadString(mValue);
1976   if (NS_WARN_IF(NS_FAILED(rv))) {
1977     return rv;
1978   }
1979   return NS_OK;
1980 }
1981 
1982 //----------------------------------------------------------------------
1983 //
1984 // nsXULPrototypePI
1985 //
1986 
Serialize(nsIObjectOutputStream * aStream,nsXULPrototypeDocument * aProtoDoc,const nsTArray<RefPtr<mozilla::dom::NodeInfo>> * aNodeInfos)1987 nsresult nsXULPrototypePI::Serialize(
1988     nsIObjectOutputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
1989     const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
1990   nsresult rv;
1991 
1992   // Write basic prototype data
1993   rv = aStream->Write32(mType);
1994 
1995   nsresult tmp = aStream->WriteWStringZ(mTarget.get());
1996   if (NS_FAILED(tmp)) {
1997     rv = tmp;
1998   }
1999   tmp = aStream->WriteWStringZ(mData.get());
2000   if (NS_FAILED(tmp)) {
2001     rv = tmp;
2002   }
2003 
2004   return rv;
2005 }
2006 
Deserialize(nsIObjectInputStream * aStream,nsXULPrototypeDocument * aProtoDoc,nsIURI * aDocumentURI,const nsTArray<RefPtr<mozilla::dom::NodeInfo>> * aNodeInfos)2007 nsresult nsXULPrototypePI::Deserialize(
2008     nsIObjectInputStream* aStream, nsXULPrototypeDocument* aProtoDoc,
2009     nsIURI* aDocumentURI,
2010     const nsTArray<RefPtr<mozilla::dom::NodeInfo>>* aNodeInfos) {
2011   nsresult rv;
2012 
2013   rv = aStream->ReadString(mTarget);
2014   if (NS_FAILED(rv)) return rv;
2015   rv = aStream->ReadString(mData);
2016   if (NS_FAILED(rv)) return rv;
2017 
2018   return rv;
2019 }
2020