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