Bug 1769952 - Fix running raptor on a Win10-64 VM r=sparky
[gecko.git] / dom / xul / XULBroadcastManager.cpp
blob7347036f1c54404252e11268ec959ddd7376da2c
1 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=4 sw=2 et tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "XULBroadcastManager.h"
8 #include "nsCOMPtr.h"
9 #include "nsContentUtils.h"
10 #include "mozilla/EventDispatcher.h"
11 #include "mozilla/Logging.h"
12 #include "mozilla/dom/DocumentInlines.h"
13 #include "nsXULElement.h"
15 struct BroadcastListener {
16 nsWeakPtr mListener;
17 RefPtr<nsAtom> mAttribute;
20 struct BroadcasterMapEntry : public PLDHashEntryHdr {
21 mozilla::dom::Element* mBroadcaster; // [WEAK]
22 nsTArray<BroadcastListener*>
23 mListeners; // [OWNING] of BroadcastListener objects
26 struct nsAttrNameInfo {
27 nsAttrNameInfo(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix)
28 : mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {}
29 nsAttrNameInfo(const nsAttrNameInfo& aOther) = delete;
30 nsAttrNameInfo(nsAttrNameInfo&& aOther) = default;
32 int32_t mNamespaceID;
33 RefPtr<nsAtom> mName;
34 RefPtr<nsAtom> mPrefix;
37 static void ClearBroadcasterMapEntry(PLDHashTable* aTable,
38 PLDHashEntryHdr* aEntry) {
39 BroadcasterMapEntry* entry = static_cast<BroadcasterMapEntry*>(aEntry);
40 for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
41 delete entry->mListeners[i];
43 entry->mListeners.Clear();
45 // N.B. that we need to manually run the dtor because we
46 // constructed the nsTArray object in-place.
47 entry->mListeners.~nsTArray<BroadcastListener*>();
50 static bool CanBroadcast(int32_t aNameSpaceID, nsAtom* aAttribute) {
51 // Don't push changes to the |id|, |persist|, |command| or
52 // |observes| attribute.
53 if (aNameSpaceID == kNameSpaceID_None) {
54 if ((aAttribute == nsGkAtoms::id) || (aAttribute == nsGkAtoms::persist) ||
55 (aAttribute == nsGkAtoms::command) ||
56 (aAttribute == nsGkAtoms::observes)) {
57 return false;
60 return true;
63 namespace mozilla::dom {
64 static LazyLogModule sXULBroadCastManager("XULBroadcastManager");
66 class XULBroadcastManager::nsDelayedBroadcastUpdate {
67 public:
68 nsDelayedBroadcastUpdate(Element* aBroadcaster, Element* aListener,
69 const nsAString& aAttr)
70 : mBroadcaster(aBroadcaster),
71 mListener(aListener),
72 mAttr(aAttr),
73 mSetAttr(false),
74 mNeedsAttrChange(false) {}
76 nsDelayedBroadcastUpdate(Element* aBroadcaster, Element* aListener,
77 nsAtom* aAttrName, const nsAString& aAttr,
78 bool aSetAttr, bool aNeedsAttrChange)
79 : mBroadcaster(aBroadcaster),
80 mListener(aListener),
81 mAttr(aAttr),
82 mAttrName(aAttrName),
83 mSetAttr(aSetAttr),
84 mNeedsAttrChange(aNeedsAttrChange) {}
86 nsDelayedBroadcastUpdate(const nsDelayedBroadcastUpdate& aOther) = delete;
87 nsDelayedBroadcastUpdate(nsDelayedBroadcastUpdate&& aOther) = default;
89 RefPtr<Element> mBroadcaster;
90 RefPtr<Element> mListener;
91 // Note if mAttrName isn't used, this is the name of the attr, otherwise
92 // this is the value of the attribute.
93 nsString mAttr;
94 RefPtr<nsAtom> mAttrName;
95 bool mSetAttr;
96 bool mNeedsAttrChange;
98 class Comparator {
99 public:
100 static bool Equals(const nsDelayedBroadcastUpdate& a,
101 const nsDelayedBroadcastUpdate& b) {
102 return a.mBroadcaster == b.mBroadcaster && a.mListener == b.mListener &&
103 a.mAttrName == b.mAttrName;
108 /* static */
109 bool XULBroadcastManager::MayNeedListener(const Element& aElement) {
110 if (aElement.NodeInfo()->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
111 return true;
113 if (aElement.HasAttr(nsGkAtoms::observes)) {
114 return true;
116 if (aElement.HasAttr(nsGkAtoms::command) &&
117 !(aElement.NodeInfo()->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
118 aElement.NodeInfo()->Equals(nsGkAtoms::key, kNameSpaceID_XUL))) {
119 return true;
121 return false;
124 XULBroadcastManager::XULBroadcastManager(Document* aDocument)
125 : mDocument(aDocument),
126 mBroadcasterMap(nullptr),
127 mHandlingDelayedAttrChange(false),
128 mHandlingDelayedBroadcasters(false) {}
130 XULBroadcastManager::~XULBroadcastManager() { delete mBroadcasterMap; }
132 void XULBroadcastManager::DropDocumentReference(void) { mDocument = nullptr; }
134 void XULBroadcastManager::SynchronizeBroadcastListener(Element* aBroadcaster,
135 Element* aListener,
136 const nsAString& aAttr) {
137 if (!nsContentUtils::IsSafeToRunScript()) {
138 mDelayedBroadcasters.EmplaceBack(aBroadcaster, aListener, aAttr);
139 MaybeBroadcast();
140 return;
142 bool notify = mHandlingDelayedBroadcasters;
144 if (aAttr.EqualsLiteral("*")) {
145 uint32_t count = aBroadcaster->GetAttrCount();
146 nsTArray<nsAttrNameInfo> attributes(count);
147 for (uint32_t i = 0; i < count; ++i) {
148 const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i);
149 int32_t nameSpaceID = attrName->NamespaceID();
150 nsAtom* name = attrName->LocalName();
152 // _Don't_ push the |id|, |ref|, or |persist| attribute's value!
153 if (!CanBroadcast(nameSpaceID, name)) continue;
155 attributes.AppendElement(
156 nsAttrNameInfo(nameSpaceID, name, attrName->GetPrefix()));
159 count = attributes.Length();
160 while (count-- > 0) {
161 int32_t nameSpaceID = attributes[count].mNamespaceID;
162 nsAtom* name = attributes[count].mName;
163 nsAutoString value;
164 if (aBroadcaster->GetAttr(nameSpaceID, name, value)) {
165 aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix, value,
166 notify);
169 #if 0
170 // XXX we don't fire the |onbroadcast| handler during
171 // initial hookup: doing so would potentially run the
172 // |onbroadcast| handler before the |onload| handler,
173 // which could define JS properties that mask XBL
174 // properties, etc.
175 ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
176 #endif
178 } else {
179 // Find out if the attribute is even present at all.
180 RefPtr<nsAtom> name = NS_Atomize(aAttr);
182 nsAutoString value;
183 if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) {
184 aListener->SetAttr(kNameSpaceID_None, name, value, notify);
185 } else {
186 aListener->UnsetAttr(kNameSpaceID_None, name, notify);
189 #if 0
190 // XXX we don't fire the |onbroadcast| handler during initial
191 // hookup: doing so would potentially run the |onbroadcast|
192 // handler before the |onload| handler, which could define JS
193 // properties that mask XBL properties, etc.
194 ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
195 #endif
199 void XULBroadcastManager::AddListenerFor(Element& aBroadcaster,
200 Element& aListener,
201 const nsAString& aAttr,
202 ErrorResult& aRv) {
203 if (!mDocument) {
204 aRv.Throw(NS_ERROR_FAILURE);
205 return;
208 nsresult rv = nsContentUtils::CheckSameOrigin(mDocument, &aBroadcaster);
210 if (NS_FAILED(rv)) {
211 aRv.Throw(rv);
212 return;
215 rv = nsContentUtils::CheckSameOrigin(mDocument, &aListener);
217 if (NS_FAILED(rv)) {
218 aRv.Throw(rv);
219 return;
222 static const PLDHashTableOps gOps = {
223 PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
224 PLDHashTable::MoveEntryStub, ClearBroadcasterMapEntry, nullptr};
226 if (!mBroadcasterMap) {
227 mBroadcasterMap = new PLDHashTable(&gOps, sizeof(BroadcasterMapEntry));
230 auto entry =
231 static_cast<BroadcasterMapEntry*>(mBroadcasterMap->Search(&aBroadcaster));
232 if (!entry) {
233 entry = static_cast<BroadcasterMapEntry*>(
234 mBroadcasterMap->Add(&aBroadcaster, fallible));
236 if (!entry) {
237 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
238 return;
241 entry->mBroadcaster = &aBroadcaster;
243 // N.B. placement new to construct the nsTArray object in-place
244 new (&entry->mListeners) nsTArray<BroadcastListener*>();
247 // Only add the listener if it's not there already!
248 RefPtr<nsAtom> attr = NS_Atomize(aAttr);
250 for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
251 BroadcastListener* bl = entry->mListeners[i];
252 nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
254 if (blListener == &aListener && bl->mAttribute == attr) return;
257 BroadcastListener* bl = new BroadcastListener;
258 bl->mListener = do_GetWeakReference(&aListener);
259 bl->mAttribute = attr;
261 entry->mListeners.AppendElement(bl);
263 SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr);
266 void XULBroadcastManager::RemoveListenerFor(Element& aBroadcaster,
267 Element& aListener,
268 const nsAString& aAttr) {
269 // If we haven't added any broadcast listeners, then there sure
270 // aren't any to remove.
271 if (!mBroadcasterMap) return;
273 auto entry =
274 static_cast<BroadcasterMapEntry*>(mBroadcasterMap->Search(&aBroadcaster));
275 if (entry) {
276 RefPtr<nsAtom> attr = NS_Atomize(aAttr);
277 for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
278 BroadcastListener* bl = entry->mListeners[i];
279 nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
281 if (blListener == &aListener && bl->mAttribute == attr) {
282 entry->mListeners.RemoveElementAt(i);
283 delete bl;
285 if (entry->mListeners.IsEmpty()) mBroadcasterMap->RemoveEntry(entry);
287 break;
293 nsresult XULBroadcastManager::ExecuteOnBroadcastHandlerFor(
294 Element* aBroadcaster, Element* aListener, nsAtom* aAttr) {
295 if (!mDocument) {
296 return NS_OK;
298 // Now we execute the onchange handler in the context of the
299 // observer. We need to find the observer in order to
300 // execute the handler.
302 for (nsCOMPtr<nsIContent> child = aListener->GetFirstChild(); child;
303 child = child->GetNextSibling()) {
304 // Look for an <observes> element beneath the listener. This
305 // ought to have an |element| attribute that refers to
306 // aBroadcaster, and an |attribute| element that tells us what
307 // attriubtes we're listening for.
308 if (!child->IsXULElement(nsGkAtoms::observes)) continue;
310 // Is this the element that was listening to us?
311 nsAutoString listeningToID;
312 child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::element,
313 listeningToID);
315 nsAutoString broadcasterID;
316 aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID);
318 if (listeningToID != broadcasterID) continue;
320 // We are observing the broadcaster, but is this the right
321 // attribute?
322 nsAutoString listeningToAttribute;
323 child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute,
324 listeningToAttribute);
326 if (!aAttr->Equals(listeningToAttribute) &&
327 !listeningToAttribute.EqualsLiteral("*")) {
328 continue;
331 // This is the right <observes> element. Execute the
332 // |onbroadcast| event handler
333 WidgetEvent event(true, eXULBroadcast);
335 if (RefPtr<nsPresContext> presContext = mDocument->GetPresContext()) {
336 // Handle the DOM event
337 nsEventStatus status = nsEventStatus_eIgnore;
338 EventDispatcher::Dispatch(child, presContext, &event, nullptr, &status);
342 return NS_OK;
345 void XULBroadcastManager::AttributeChanged(Element* aElement,
346 int32_t aNameSpaceID,
347 nsAtom* aAttribute) {
348 if (!mDocument) {
349 return;
351 NS_ASSERTION(aElement->OwnerDoc() == mDocument, "unexpected doc");
353 // Synchronize broadcast listeners
354 if (mBroadcasterMap && CanBroadcast(aNameSpaceID, aAttribute)) {
355 auto entry =
356 static_cast<BroadcasterMapEntry*>(mBroadcasterMap->Search(aElement));
358 if (entry) {
359 // We've got listeners: push the value.
360 nsAutoString value;
361 bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value);
363 for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
364 BroadcastListener* bl = entry->mListeners[i];
365 if ((bl->mAttribute == aAttribute) ||
366 (bl->mAttribute == nsGkAtoms::_asterisk)) {
367 nsCOMPtr<Element> listenerEl = do_QueryReferent(bl->mListener);
368 if (listenerEl) {
369 nsAutoString currentValue;
370 bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None, aAttribute,
371 currentValue);
372 // We need to update listener only if we're
373 // (1) removing an existing attribute,
374 // (2) adding a new attribute or
375 // (3) changing the value of an attribute.
376 bool needsAttrChange =
377 attrSet != hasAttr || !value.Equals(currentValue);
378 nsDelayedBroadcastUpdate delayedUpdate(aElement, listenerEl,
379 aAttribute, value, attrSet,
380 needsAttrChange);
382 size_t index = mDelayedAttrChangeBroadcasts.IndexOf(
383 delayedUpdate, 0, nsDelayedBroadcastUpdate::Comparator());
384 if (index != mDelayedAttrChangeBroadcasts.NoIndex) {
385 if (mHandlingDelayedAttrChange) {
386 NS_WARNING("Broadcasting loop!");
387 continue;
389 mDelayedAttrChangeBroadcasts.RemoveElementAt(index);
392 mDelayedAttrChangeBroadcasts.AppendElement(
393 std::move(delayedUpdate));
401 void XULBroadcastManager::MaybeBroadcast() {
402 // Only broadcast when not in an update and when safe to run scripts.
403 if (mDocument && mDocument->UpdateNestingLevel() == 0 &&
404 (mDelayedAttrChangeBroadcasts.Length() ||
405 mDelayedBroadcasters.Length())) {
406 if (!nsContentUtils::IsSafeToRunScript()) {
407 if (mDocument) {
408 nsContentUtils::AddScriptRunner(
409 NewRunnableMethod("dom::XULBroadcastManager::MaybeBroadcast", this,
410 &XULBroadcastManager::MaybeBroadcast));
412 return;
414 if (!mHandlingDelayedAttrChange) {
415 mHandlingDelayedAttrChange = true;
416 for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) {
417 RefPtr<nsAtom> attrName = mDelayedAttrChangeBroadcasts[i].mAttrName;
418 RefPtr<Element> listener = mDelayedAttrChangeBroadcasts[i].mListener;
419 if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) {
420 const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr;
421 if (mDelayedAttrChangeBroadcasts[i].mSetAttr) {
422 listener->SetAttr(kNameSpaceID_None, attrName, value, true);
423 } else {
424 listener->UnsetAttr(kNameSpaceID_None, attrName, true);
427 RefPtr<Element> broadcaster =
428 mDelayedAttrChangeBroadcasts[i].mBroadcaster;
429 ExecuteOnBroadcastHandlerFor(broadcaster, listener, attrName);
431 mDelayedAttrChangeBroadcasts.Clear();
432 mHandlingDelayedAttrChange = false;
435 uint32_t length = mDelayedBroadcasters.Length();
436 if (length) {
437 bool oldValue = mHandlingDelayedBroadcasters;
438 mHandlingDelayedBroadcasters = true;
439 nsTArray<nsDelayedBroadcastUpdate> delayedBroadcasters =
440 std::move(mDelayedBroadcasters);
441 for (uint32_t i = 0; i < length; ++i) {
442 SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster,
443 delayedBroadcasters[i].mListener,
444 delayedBroadcasters[i].mAttr);
446 mHandlingDelayedBroadcasters = oldValue;
451 nsresult XULBroadcastManager::FindBroadcaster(Element* aElement,
452 Element** aListener,
453 nsString& aBroadcasterID,
454 nsString& aAttribute,
455 Element** aBroadcaster) {
456 NodeInfo* ni = aElement->NodeInfo();
457 *aListener = nullptr;
458 *aBroadcaster = nullptr;
460 if (ni->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
461 // It's an <observes> element, which means that the actual
462 // listener is the _parent_ node. This element should have an
463 // 'element' attribute that specifies the ID of the
464 // broadcaster element, and an 'attribute' element, which
465 // specifies the name of the attribute to observe.
466 nsIContent* parent = aElement->GetParent();
467 if (!parent) {
468 // <observes> is the root element
469 return NS_FINDBROADCASTER_NOT_FOUND;
472 *aListener = Element::FromNode(parent);
473 NS_IF_ADDREF(*aListener);
475 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, aBroadcasterID);
476 if (aBroadcasterID.IsEmpty()) {
477 return NS_FINDBROADCASTER_NOT_FOUND;
479 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, aAttribute);
480 } else {
481 // It's a generic element, which means that we'll use the
482 // value of the 'observes' attribute to determine the ID of
483 // the broadcaster element, and we'll watch _all_ of its
484 // values.
485 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, aBroadcasterID);
487 // Bail if there's no aBroadcasterID
488 if (aBroadcasterID.IsEmpty()) {
489 // Try the command attribute next.
490 aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, aBroadcasterID);
491 if (!aBroadcasterID.IsEmpty()) {
492 // We've got something in the command attribute. We
493 // only treat this as a normal broadcaster if we are
494 // not a menuitem or a key.
496 if (ni->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
497 ni->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
498 return NS_FINDBROADCASTER_NOT_FOUND;
500 } else {
501 return NS_FINDBROADCASTER_NOT_FOUND;
505 *aListener = aElement;
506 NS_ADDREF(*aListener);
508 aAttribute.Assign('*');
511 // Make sure we got a valid listener.
512 NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED);
514 // Try to find the broadcaster element in the document.
515 Document* doc = aElement->GetComposedDoc();
516 if (doc) {
517 *aBroadcaster = doc->GetElementById(aBroadcasterID);
520 // The broadcaster element is missing.
521 if (!*aBroadcaster) {
522 return NS_FINDBROADCASTER_NOT_FOUND;
525 NS_ADDREF(*aBroadcaster);
527 return NS_FINDBROADCASTER_FOUND;
530 nsresult XULBroadcastManager::UpdateListenerHookup(Element* aElement,
531 HookupAction aAction) {
532 // Resolve a broadcaster hookup. Look at the element that we're
533 // trying to resolve: it could be an '<observes>' element, or just
534 // a vanilla element with an 'observes' attribute on it.
535 nsresult rv;
537 nsCOMPtr<Element> listener;
538 nsAutoString broadcasterID;
539 nsAutoString attribute;
540 nsCOMPtr<Element> broadcaster;
542 rv = FindBroadcaster(aElement, getter_AddRefs(listener), broadcasterID,
543 attribute, getter_AddRefs(broadcaster));
544 switch (rv) {
545 case NS_FINDBROADCASTER_NOT_FOUND:
546 return NS_OK;
547 case NS_FINDBROADCASTER_FOUND:
548 break;
549 default:
550 return rv;
553 NS_ENSURE_ARG(broadcaster && listener);
554 if (aAction == eHookupAdd) {
555 ErrorResult domRv;
556 AddListenerFor(*broadcaster, *listener, attribute, domRv);
557 if (domRv.Failed()) {
558 return domRv.StealNSResult();
560 } else {
561 RemoveListenerFor(*broadcaster, *listener, attribute);
564 // Tell the world we succeeded
565 if (MOZ_LOG_TEST(sXULBroadCastManager, LogLevel::Debug)) {
566 nsCOMPtr<nsIContent> content = listener;
567 NS_ASSERTION(content != nullptr, "not an nsIContent");
568 if (!content) {
569 return rv;
572 nsAutoCString attributeC, broadcasteridC;
573 LossyCopyUTF16toASCII(attribute, attributeC);
574 LossyCopyUTF16toASCII(broadcasterID, broadcasteridC);
575 MOZ_LOG(sXULBroadCastManager, LogLevel::Debug,
576 ("xul: broadcaster hookup <%s attribute='%s'> to %s",
577 nsAtomCString(content->NodeInfo()->NameAtom()).get(),
578 attributeC.get(), broadcasteridC.get()));
581 return NS_OK;
584 nsresult XULBroadcastManager::AddListener(Element* aElement) {
585 return UpdateListenerHookup(aElement, eHookupAdd);
588 nsresult XULBroadcastManager::RemoveListener(Element* aElement) {
589 return UpdateListenerHookup(aElement, eHookupRemove);
592 } // namespace mozilla::dom