Source code

Revision control

Other Tools

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/. */
6
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"
14
15
struct BroadcastListener {
16
nsWeakPtr mListener;
17
RefPtr<nsAtom> mAttribute;
18
};
19
20
struct BroadcasterMapEntry : public PLDHashEntryHdr {
21
mozilla::dom::Element* mBroadcaster; // [WEAK]
22
nsTArray<BroadcastListener*>
23
mListeners; // [OWNING] of BroadcastListener objects
24
};
25
26
struct nsAttrNameInfo {
27
nsAttrNameInfo(int32_t aNamespaceID, nsAtom* aName, nsAtom* aPrefix)
28
: mNamespaceID(aNamespaceID), mName(aName), mPrefix(aPrefix) {}
29
nsAttrNameInfo(const nsAttrNameInfo& aOther)
30
: mNamespaceID(aOther.mNamespaceID),
31
mName(aOther.mName),
32
mPrefix(aOther.mPrefix) {}
33
int32_t mNamespaceID;
34
RefPtr<nsAtom> mName;
35
RefPtr<nsAtom> mPrefix;
36
};
37
38
static void ClearBroadcasterMapEntry(PLDHashTable* aTable,
39
PLDHashEntryHdr* aEntry) {
40
BroadcasterMapEntry* entry = static_cast<BroadcasterMapEntry*>(aEntry);
41
for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
42
delete entry->mListeners[i];
43
}
44
entry->mListeners.Clear();
45
46
// N.B. that we need to manually run the dtor because we
47
// constructed the nsTArray object in-place.
48
entry->mListeners.~nsTArray<BroadcastListener*>();
49
}
50
51
static bool CanBroadcast(int32_t aNameSpaceID, nsAtom* aAttribute) {
52
// Don't push changes to the |id|, |persist|, |command| or
53
// |observes| attribute.
54
if (aNameSpaceID == kNameSpaceID_None) {
55
if ((aAttribute == nsGkAtoms::id) || (aAttribute == nsGkAtoms::persist) ||
56
(aAttribute == nsGkAtoms::command) ||
57
(aAttribute == nsGkAtoms::observes)) {
58
return false;
59
}
60
}
61
return true;
62
}
63
64
namespace mozilla {
65
namespace dom {
66
static LazyLogModule sXULBroadCastManager("XULBroadcastManager");
67
68
/* static */
69
bool XULBroadcastManager::MayNeedListener(const Element& aElement) {
70
if (aElement.NodeInfo()->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
71
return true;
72
}
73
if (aElement.HasAttr(nsGkAtoms::observes)) {
74
return true;
75
}
76
if (aElement.HasAttr(nsGkAtoms::command) &&
77
!(aElement.NodeInfo()->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
78
aElement.NodeInfo()->Equals(nsGkAtoms::key, kNameSpaceID_XUL))) {
79
return true;
80
}
81
return false;
82
}
83
84
XULBroadcastManager::XULBroadcastManager(Document* aDocument)
85
: mDocument(aDocument),
86
mBroadcasterMap(nullptr),
87
mHandlingDelayedAttrChange(false),
88
mHandlingDelayedBroadcasters(false) {}
89
90
XULBroadcastManager::~XULBroadcastManager() { delete mBroadcasterMap; }
91
92
void XULBroadcastManager::DropDocumentReference(void) { mDocument = nullptr; }
93
94
void XULBroadcastManager::SynchronizeBroadcastListener(Element* aBroadcaster,
95
Element* aListener,
96
const nsAString& aAttr) {
97
if (!nsContentUtils::IsSafeToRunScript()) {
98
nsDelayedBroadcastUpdate delayedUpdate(aBroadcaster, aListener, aAttr);
99
mDelayedBroadcasters.AppendElement(delayedUpdate);
100
MaybeBroadcast();
101
return;
102
}
103
bool notify = mHandlingDelayedBroadcasters;
104
105
if (aAttr.EqualsLiteral("*")) {
106
uint32_t count = aBroadcaster->GetAttrCount();
107
nsTArray<nsAttrNameInfo> attributes(count);
108
for (uint32_t i = 0; i < count; ++i) {
109
const nsAttrName* attrName = aBroadcaster->GetAttrNameAt(i);
110
int32_t nameSpaceID = attrName->NamespaceID();
111
nsAtom* name = attrName->LocalName();
112
113
// _Don't_ push the |id|, |ref|, or |persist| attribute's value!
114
if (!CanBroadcast(nameSpaceID, name)) continue;
115
116
attributes.AppendElement(
117
nsAttrNameInfo(nameSpaceID, name, attrName->GetPrefix()));
118
}
119
120
count = attributes.Length();
121
while (count-- > 0) {
122
int32_t nameSpaceID = attributes[count].mNamespaceID;
123
nsAtom* name = attributes[count].mName;
124
nsAutoString value;
125
if (aBroadcaster->GetAttr(nameSpaceID, name, value)) {
126
aListener->SetAttr(nameSpaceID, name, attributes[count].mPrefix, value,
127
notify);
128
}
129
130
#if 0
131
// XXX we don't fire the |onbroadcast| handler during
132
// initial hookup: doing so would potentially run the
133
// |onbroadcast| handler before the |onload| handler,
134
// which could define JS properties that mask XBL
135
// properties, etc.
136
ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
137
#endif
138
}
139
} else {
140
// Find out if the attribute is even present at all.
141
RefPtr<nsAtom> name = NS_Atomize(aAttr);
142
143
nsAutoString value;
144
if (aBroadcaster->GetAttr(kNameSpaceID_None, name, value)) {
145
aListener->SetAttr(kNameSpaceID_None, name, value, notify);
146
} else {
147
aListener->UnsetAttr(kNameSpaceID_None, name, notify);
148
}
149
150
#if 0
151
// XXX we don't fire the |onbroadcast| handler during initial
152
// hookup: doing so would potentially run the |onbroadcast|
153
// handler before the |onload| handler, which could define JS
154
// properties that mask XBL properties, etc.
155
ExecuteOnBroadcastHandlerFor(aBroadcaster, aListener, name);
156
#endif
157
}
158
}
159
160
void XULBroadcastManager::AddListenerFor(Element& aBroadcaster,
161
Element& aListener,
162
const nsAString& aAttr,
163
ErrorResult& aRv) {
164
if (!mDocument) {
165
aRv.Throw(NS_ERROR_FAILURE);
166
return;
167
}
168
169
nsresult rv = nsContentUtils::CheckSameOrigin(mDocument, &aBroadcaster);
170
171
if (NS_FAILED(rv)) {
172
aRv.Throw(rv);
173
return;
174
}
175
176
rv = nsContentUtils::CheckSameOrigin(mDocument, &aListener);
177
178
if (NS_FAILED(rv)) {
179
aRv.Throw(rv);
180
return;
181
}
182
183
static const PLDHashTableOps gOps = {
184
PLDHashTable::HashVoidPtrKeyStub, PLDHashTable::MatchEntryStub,
185
PLDHashTable::MoveEntryStub, ClearBroadcasterMapEntry, nullptr};
186
187
if (!mBroadcasterMap) {
188
mBroadcasterMap = new PLDHashTable(&gOps, sizeof(BroadcasterMapEntry));
189
}
190
191
auto entry =
192
static_cast<BroadcasterMapEntry*>(mBroadcasterMap->Search(&aBroadcaster));
193
if (!entry) {
194
entry = static_cast<BroadcasterMapEntry*>(
195
mBroadcasterMap->Add(&aBroadcaster, fallible));
196
197
if (!entry) {
198
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
199
return;
200
}
201
202
entry->mBroadcaster = &aBroadcaster;
203
204
// N.B. placement new to construct the nsTArray object in-place
205
new (&entry->mListeners) nsTArray<BroadcastListener*>();
206
}
207
208
// Only add the listener if it's not there already!
209
RefPtr<nsAtom> attr = NS_Atomize(aAttr);
210
211
for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
212
BroadcastListener* bl = entry->mListeners[i];
213
nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
214
215
if (blListener == &aListener && bl->mAttribute == attr) return;
216
}
217
218
BroadcastListener* bl = new BroadcastListener;
219
bl->mListener = do_GetWeakReference(&aListener);
220
bl->mAttribute = attr;
221
222
entry->mListeners.AppendElement(bl);
223
224
SynchronizeBroadcastListener(&aBroadcaster, &aListener, aAttr);
225
}
226
227
void XULBroadcastManager::RemoveListenerFor(Element& aBroadcaster,
228
Element& aListener,
229
const nsAString& aAttr) {
230
// If we haven't added any broadcast listeners, then there sure
231
// aren't any to remove.
232
if (!mBroadcasterMap) return;
233
234
auto entry =
235
static_cast<BroadcasterMapEntry*>(mBroadcasterMap->Search(&aBroadcaster));
236
if (entry) {
237
RefPtr<nsAtom> attr = NS_Atomize(aAttr);
238
for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
239
BroadcastListener* bl = entry->mListeners[i];
240
nsCOMPtr<Element> blListener = do_QueryReferent(bl->mListener);
241
242
if (blListener == &aListener && bl->mAttribute == attr) {
243
entry->mListeners.RemoveElementAt(i);
244
delete bl;
245
246
if (entry->mListeners.IsEmpty()) mBroadcasterMap->RemoveEntry(entry);
247
248
break;
249
}
250
}
251
}
252
}
253
254
nsresult XULBroadcastManager::ExecuteOnBroadcastHandlerFor(
255
Element* aBroadcaster, Element* aListener, nsAtom* aAttr) {
256
if (!mDocument) {
257
return NS_OK;
258
}
259
// Now we execute the onchange handler in the context of the
260
// observer. We need to find the observer in order to
261
// execute the handler.
262
263
for (nsIContent* child = aListener->GetFirstChild(); child;
264
child = child->GetNextSibling()) {
265
// Look for an <observes> element beneath the listener. This
266
// ought to have an |element| attribute that refers to
267
// aBroadcaster, and an |attribute| element that tells us what
268
// attriubtes we're listening for.
269
if (!child->IsXULElement(nsGkAtoms::observes)) continue;
270
271
// Is this the element that was listening to us?
272
nsAutoString listeningToID;
273
child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::element,
274
listeningToID);
275
276
nsAutoString broadcasterID;
277
aBroadcaster->GetAttr(kNameSpaceID_None, nsGkAtoms::id, broadcasterID);
278
279
if (listeningToID != broadcasterID) continue;
280
281
// We are observing the broadcaster, but is this the right
282
// attribute?
283
nsAutoString listeningToAttribute;
284
child->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute,
285
listeningToAttribute);
286
287
if (!aAttr->Equals(listeningToAttribute) &&
288
!listeningToAttribute.EqualsLiteral("*")) {
289
continue;
290
}
291
292
// This is the right <observes> element. Execute the
293
// |onbroadcast| event handler
294
WidgetEvent event(true, eXULBroadcast);
295
296
RefPtr<nsPresContext> presContext = mDocument->GetPresContext();
297
if (presContext) {
298
// Handle the DOM event
299
nsEventStatus status = nsEventStatus_eIgnore;
300
EventDispatcher::Dispatch(child, presContext, &event, nullptr, &status);
301
}
302
}
303
304
return NS_OK;
305
}
306
307
void XULBroadcastManager::AttributeChanged(Element* aElement,
308
int32_t aNameSpaceID,
309
nsAtom* aAttribute) {
310
if (!mDocument) {
311
return;
312
}
313
NS_ASSERTION(aElement->OwnerDoc() == mDocument, "unexpected doc");
314
315
// Synchronize broadcast listeners
316
if (mBroadcasterMap && CanBroadcast(aNameSpaceID, aAttribute)) {
317
auto entry =
318
static_cast<BroadcasterMapEntry*>(mBroadcasterMap->Search(aElement));
319
320
if (entry) {
321
// We've got listeners: push the value.
322
nsAutoString value;
323
bool attrSet = aElement->GetAttr(kNameSpaceID_None, aAttribute, value);
324
325
for (size_t i = entry->mListeners.Length() - 1; i != (size_t)-1; --i) {
326
BroadcastListener* bl = entry->mListeners[i];
327
if ((bl->mAttribute == aAttribute) ||
328
(bl->mAttribute == nsGkAtoms::_asterisk)) {
329
nsCOMPtr<Element> listenerEl = do_QueryReferent(bl->mListener);
330
if (listenerEl) {
331
nsAutoString currentValue;
332
bool hasAttr = listenerEl->GetAttr(kNameSpaceID_None, aAttribute,
333
currentValue);
334
// We need to update listener only if we're
335
// (1) removing an existing attribute,
336
// (2) adding a new attribute or
337
// (3) changing the value of an attribute.
338
bool needsAttrChange =
339
attrSet != hasAttr || !value.Equals(currentValue);
340
nsDelayedBroadcastUpdate delayedUpdate(aElement, listenerEl,
341
aAttribute, value, attrSet,
342
needsAttrChange);
343
344
size_t index = mDelayedAttrChangeBroadcasts.IndexOf(
345
delayedUpdate, 0, nsDelayedBroadcastUpdate::Comparator());
346
if (index != mDelayedAttrChangeBroadcasts.NoIndex) {
347
if (mHandlingDelayedAttrChange) {
348
NS_WARNING("Broadcasting loop!");
349
continue;
350
}
351
mDelayedAttrChangeBroadcasts.RemoveElementAt(index);
352
}
353
354
mDelayedAttrChangeBroadcasts.AppendElement(delayedUpdate);
355
}
356
}
357
}
358
}
359
}
360
}
361
362
void XULBroadcastManager::MaybeBroadcast() {
363
// Only broadcast when not in an update and when safe to run scripts.
364
if (mDocument && mDocument->UpdateNestingLevel() == 0 &&
365
(mDelayedAttrChangeBroadcasts.Length() ||
366
mDelayedBroadcasters.Length())) {
367
if (!nsContentUtils::IsSafeToRunScript()) {
368
if (mDocument) {
369
nsContentUtils::AddScriptRunner(
370
NewRunnableMethod("dom::XULBroadcastManager::MaybeBroadcast", this,
371
&XULBroadcastManager::MaybeBroadcast));
372
}
373
return;
374
}
375
if (!mHandlingDelayedAttrChange) {
376
mHandlingDelayedAttrChange = true;
377
for (uint32_t i = 0; i < mDelayedAttrChangeBroadcasts.Length(); ++i) {
378
nsAtom* attrName = mDelayedAttrChangeBroadcasts[i].mAttrName;
379
if (mDelayedAttrChangeBroadcasts[i].mNeedsAttrChange) {
380
nsCOMPtr<Element> listener =
381
mDelayedAttrChangeBroadcasts[i].mListener;
382
const nsString& value = mDelayedAttrChangeBroadcasts[i].mAttr;
383
if (mDelayedAttrChangeBroadcasts[i].mSetAttr) {
384
listener->SetAttr(kNameSpaceID_None, attrName, value, true);
385
} else {
386
listener->UnsetAttr(kNameSpaceID_None, attrName, true);
387
}
388
}
389
ExecuteOnBroadcastHandlerFor(
390
mDelayedAttrChangeBroadcasts[i].mBroadcaster,
391
mDelayedAttrChangeBroadcasts[i].mListener, attrName);
392
}
393
mDelayedAttrChangeBroadcasts.Clear();
394
mHandlingDelayedAttrChange = false;
395
}
396
397
uint32_t length = mDelayedBroadcasters.Length();
398
if (length) {
399
bool oldValue = mHandlingDelayedBroadcasters;
400
mHandlingDelayedBroadcasters = true;
401
nsTArray<nsDelayedBroadcastUpdate> delayedBroadcasters;
402
mDelayedBroadcasters.SwapElements(delayedBroadcasters);
403
for (uint32_t i = 0; i < length; ++i) {
404
SynchronizeBroadcastListener(delayedBroadcasters[i].mBroadcaster,
405
delayedBroadcasters[i].mListener,
406
delayedBroadcasters[i].mAttr);
407
}
408
mHandlingDelayedBroadcasters = oldValue;
409
}
410
}
411
}
412
413
nsresult XULBroadcastManager::FindBroadcaster(Element* aElement,
414
Element** aListener,
415
nsString& aBroadcasterID,
416
nsString& aAttribute,
417
Element** aBroadcaster) {
418
NodeInfo* ni = aElement->NodeInfo();
419
*aListener = nullptr;
420
*aBroadcaster = nullptr;
421
422
if (ni->Equals(nsGkAtoms::observes, kNameSpaceID_XUL)) {
423
// It's an <observes> element, which means that the actual
424
// listener is the _parent_ node. This element should have an
425
// 'element' attribute that specifies the ID of the
426
// broadcaster element, and an 'attribute' element, which
427
// specifies the name of the attribute to observe.
428
nsIContent* parent = aElement->GetParent();
429
if (!parent) {
430
// <observes> is the root element
431
return NS_FINDBROADCASTER_NOT_FOUND;
432
}
433
434
*aListener = Element::FromNode(parent);
435
NS_IF_ADDREF(*aListener);
436
437
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::element, aBroadcasterID);
438
if (aBroadcasterID.IsEmpty()) {
439
return NS_FINDBROADCASTER_NOT_FOUND;
440
}
441
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::attribute, aAttribute);
442
} else {
443
// It's a generic element, which means that we'll use the
444
// value of the 'observes' attribute to determine the ID of
445
// the broadcaster element, and we'll watch _all_ of its
446
// values.
447
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::observes, aBroadcasterID);
448
449
// Bail if there's no aBroadcasterID
450
if (aBroadcasterID.IsEmpty()) {
451
// Try the command attribute next.
452
aElement->GetAttr(kNameSpaceID_None, nsGkAtoms::command, aBroadcasterID);
453
if (!aBroadcasterID.IsEmpty()) {
454
// We've got something in the command attribute. We
455
// only treat this as a normal broadcaster if we are
456
// not a menuitem or a key.
457
458
if (ni->Equals(nsGkAtoms::menuitem, kNameSpaceID_XUL) ||
459
ni->Equals(nsGkAtoms::key, kNameSpaceID_XUL)) {
460
return NS_FINDBROADCASTER_NOT_FOUND;
461
}
462
} else {
463
return NS_FINDBROADCASTER_NOT_FOUND;
464
}
465
}
466
467
*aListener = aElement;
468
NS_ADDREF(*aListener);
469
470
aAttribute.Assign('*');
471
}
472
473
// Make sure we got a valid listener.
474
NS_ENSURE_TRUE(*aListener, NS_ERROR_UNEXPECTED);
475
476
// Try to find the broadcaster element in the document.
477
Document* doc = aElement->GetComposedDoc();
478
if (doc) {
479
*aBroadcaster = doc->GetElementById(aBroadcasterID);
480
}
481
482
// The broadcaster element is missing.
483
if (!*aBroadcaster) {
484
return NS_FINDBROADCASTER_NOT_FOUND;
485
}
486
487
NS_ADDREF(*aBroadcaster);
488
489
return NS_FINDBROADCASTER_FOUND;
490
}
491
492
nsresult XULBroadcastManager::UpdateListenerHookup(Element* aElement,
493
HookupAction aAction) {
494
// Resolve a broadcaster hookup. Look at the element that we're
495
// trying to resolve: it could be an '<observes>' element, or just
496
// a vanilla element with an 'observes' attribute on it.
497
nsresult rv;
498
499
nsCOMPtr<Element> listener;
500
nsAutoString broadcasterID;
501
nsAutoString attribute;
502
nsCOMPtr<Element> broadcaster;
503
504
rv = FindBroadcaster(aElement, getter_AddRefs(listener), broadcasterID,
505
attribute, getter_AddRefs(broadcaster));
506
switch (rv) {
507
case NS_FINDBROADCASTER_NOT_FOUND:
508
return NS_OK;
509
case NS_FINDBROADCASTER_FOUND:
510
break;
511
default:
512
return rv;
513
}
514
515
NS_ENSURE_ARG(broadcaster && listener);
516
if (aAction == eHookupAdd) {
517
ErrorResult domRv;
518
AddListenerFor(*broadcaster, *listener, attribute, domRv);
519
if (domRv.Failed()) {
520
return domRv.StealNSResult();
521
}
522
} else {
523
RemoveListenerFor(*broadcaster, *listener, attribute);
524
}
525
526
// Tell the world we succeeded
527
if (MOZ_LOG_TEST(sXULBroadCastManager, LogLevel::Debug)) {
528
nsCOMPtr<nsIContent> content = listener;
529
NS_ASSERTION(content != nullptr, "not an nsIContent");
530
if (!content) {
531
return rv;
532
}
533
534
nsAutoCString attributeC, broadcasteridC;
535
LossyCopyUTF16toASCII(attribute, attributeC);
536
LossyCopyUTF16toASCII(broadcasterID, broadcasteridC);
537
MOZ_LOG(sXULBroadCastManager, LogLevel::Debug,
538
("xul: broadcaster hookup <%s attribute='%s'> to %s",
539
nsAtomCString(content->NodeInfo()->NameAtom()).get(),
540
attributeC.get(), broadcasteridC.get()));
541
}
542
543
return NS_OK;
544
}
545
546
nsresult XULBroadcastManager::AddListener(Element* aElement) {
547
return UpdateListenerHookup(aElement, eHookupAdd);
548
}
549
550
nsresult XULBroadcastManager::RemoveListener(Element* aElement) {
551
return UpdateListenerHookup(aElement, eHookupRemove);
552
}
553
554
} // namespace dom
555
} // namespace mozilla