1 : /* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is Web Workers.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * The Mozilla Foundation.
19 : * Portions created by the Initial Developer are Copyright (C) 2011
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Ben Turner <bent.mozilla@gmail.com> (Original Author)
24 : *
25 : * Alternatively, the contents of this file may be used under the terms of
26 : * either the GNU General Public License Version 2 or later (the "GPL"), or
27 : * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
28 : * in which case the provisions of the GPL or the LGPL are applicable instead
29 : * of those above. If you wish to allow use of your version of this file only
30 : * under the terms of either the GPL or the LGPL, and not to allow others to
31 : * use your version of this file under the terms of the MPL, indicate your
32 : * decision by deleting the provisions above and replace them with the notice
33 : * and other provisions required by the GPL or the LGPL. If you do not delete
34 : * the provisions above, a recipient may use your version of this file under
35 : * the terms of any one of the MPL, the GPL or the LGPL.
36 : *
37 : * ***** END LICENSE BLOCK ***** */
38 :
39 : #include "mozilla/Util.h"
40 :
41 : #include "ListenerManager.h"
42 :
43 : #include "jsalloc.h"
44 : #include "jsapi.h"
45 : #include "jsfriendapi.h"
46 : #include "js/Vector.h"
47 :
48 : #include "Events.h"
49 :
50 : using namespace mozilla;
51 : using dom::workers::events::ListenerManager;
52 :
53 : namespace {
54 :
55 : struct Listener;
56 :
57 : struct ListenerCollection : PRCList
58 : {
59 : static ListenerCollection*
60 0 : Add(JSContext* aCx, ListenerCollection* aCollectionHead, jsid aTypeId)
61 : {
62 : ListenerCollection* collection =
63 : static_cast<ListenerCollection*>(JS_malloc(aCx,
64 0 : sizeof(ListenerCollection)));
65 0 : if (!collection) {
66 0 : return NULL;
67 : }
68 :
69 0 : PR_APPEND_LINK(collection, aCollectionHead);
70 :
71 0 : collection->mTypeId = aTypeId;
72 0 : PR_INIT_CLIST(&collection->mListenerHead);
73 0 : return collection;
74 : }
75 :
76 : static void
77 0 : Remove(JSContext* aCx, ListenerCollection* aCollection)
78 : {
79 0 : PR_REMOVE_LINK(aCollection);
80 0 : JS_free(aCx, aCollection);
81 0 : }
82 :
83 : jsid mTypeId;
84 : PRCList mListenerHead;
85 : };
86 :
87 : struct Listener : PRCList
88 : {
89 : static Listener*
90 0 : Add(JSContext* aCx, Listener* aListenerHead, jsval aListenerVal,
91 : ListenerManager::Phase aPhase, bool aWantsUntrusted)
92 : {
93 : Listener* listener =
94 0 : static_cast<Listener*>(JS_malloc(aCx, sizeof(Listener)));
95 0 : if (!listener) {
96 0 : return NULL;
97 : }
98 :
99 0 : PR_APPEND_LINK(listener, aListenerHead);
100 :
101 0 : listener->mListenerVal = aListenerVal;
102 0 : listener->mPhase = aPhase;
103 0 : listener->mWantsUntrusted = aWantsUntrusted;
104 0 : return listener;
105 : }
106 :
107 : static void
108 0 : Remove(JSContext* aCx, Listener* aListener)
109 : {
110 0 : if (js::IsIncrementalBarrierNeeded(aCx))
111 0 : js::IncrementalValueBarrier(aListener->mListenerVal);
112 :
113 0 : PR_REMOVE_LINK(aListener);
114 0 : JS_free(aCx, aListener);
115 0 : }
116 :
117 : jsval mListenerVal;
118 : ListenerManager::Phase mPhase;
119 : bool mWantsUntrusted;
120 : };
121 :
122 : void
123 0 : DestroyList(JSContext* aCx, PRCList* aListHead)
124 : {
125 0 : for (PRCList* elem = PR_NEXT_LINK(aListHead); elem != aListHead; ) {
126 0 : PRCList* nextElem = PR_NEXT_LINK(elem);
127 0 : JS_free(aCx, elem);
128 0 : elem = nextElem;
129 : }
130 0 : }
131 :
132 : ListenerCollection*
133 0 : GetCollectionForType(PRCList* aHead, jsid aTypeId)
134 : {
135 0 : for (PRCList* elem = PR_NEXT_LINK(aHead);
136 : elem != aHead;
137 : elem = PR_NEXT_LINK(elem)) {
138 0 : ListenerCollection* collection = static_cast<ListenerCollection*>(elem);
139 0 : if (collection->mTypeId == aTypeId) {
140 0 : return collection;
141 : }
142 : }
143 0 : return NULL;
144 : }
145 :
146 : } // anonymous namespace
147 :
148 : #ifdef DEBUG
149 0 : ListenerManager::~ListenerManager()
150 : {
151 0 : JS_ASSERT(PR_CLIST_IS_EMPTY(&mCollectionHead));
152 0 : }
153 : #endif
154 :
155 : void
156 0 : ListenerManager::TraceInternal(JSTracer* aTrc)
157 : {
158 0 : JS_ASSERT(!PR_CLIST_IS_EMPTY(&mCollectionHead));
159 :
160 0 : for (PRCList* collectionElem = PR_NEXT_LINK(&mCollectionHead);
161 : collectionElem != &mCollectionHead;
162 : collectionElem = PR_NEXT_LINK(collectionElem)) {
163 : ListenerCollection* collection =
164 0 : static_cast<ListenerCollection*>(collectionElem);
165 :
166 0 : for (PRCList* listenerElem = PR_NEXT_LINK(&collection->mListenerHead);
167 : listenerElem != &collection->mListenerHead;
168 : listenerElem = PR_NEXT_LINK(listenerElem)) {
169 0 : JS_CALL_VALUE_TRACER(aTrc,
170 : static_cast<Listener*>(listenerElem)->mListenerVal,
171 : "EventListenerManager listener value");
172 : }
173 : }
174 0 : }
175 :
176 : void
177 0 : ListenerManager::FinalizeInternal(JSContext* aCx)
178 : {
179 0 : JS_ASSERT(!PR_CLIST_IS_EMPTY(&mCollectionHead));
180 :
181 0 : for (PRCList* elem = PR_NEXT_LINK(&mCollectionHead);
182 : elem != &mCollectionHead;
183 : elem = PR_NEXT_LINK(elem)) {
184 0 : DestroyList(aCx, &static_cast<ListenerCollection*>(elem)->mListenerHead);
185 : }
186 :
187 0 : DestroyList(aCx, &mCollectionHead);
188 :
189 : #ifdef DEBUG
190 0 : PR_INIT_CLIST(&mCollectionHead);
191 : #endif
192 0 : }
193 :
194 : bool
195 0 : ListenerManager::Add(JSContext* aCx, JSString* aType, jsval aListenerVal,
196 : Phase aPhase, bool aWantsUntrusted)
197 : {
198 0 : aType = JS_InternJSString(aCx, aType);
199 0 : if (!aType) {
200 0 : return false;
201 : }
202 :
203 0 : if (JSVAL_IS_PRIMITIVE(aListenerVal)) {
204 0 : JS_ReportError(aCx, "Bad listener!");
205 0 : return false;
206 : }
207 :
208 0 : jsid typeId = INTERNED_STRING_TO_JSID(aCx, aType);
209 :
210 : ListenerCollection* collection =
211 0 : GetCollectionForType(&mCollectionHead, typeId);
212 0 : if (!collection) {
213 : ListenerCollection* head =
214 0 : static_cast<ListenerCollection*>(&mCollectionHead);
215 0 : collection = ListenerCollection::Add(aCx, head, typeId);
216 0 : if (!collection) {
217 0 : return false;
218 : }
219 : }
220 :
221 0 : for (PRCList* elem = PR_NEXT_LINK(&collection->mListenerHead);
222 : elem != &collection->mListenerHead;
223 : elem = PR_NEXT_LINK(elem)) {
224 0 : Listener* listener = static_cast<Listener*>(elem);
225 0 : if (listener->mListenerVal == aListenerVal && listener->mPhase == aPhase) {
226 0 : return true;
227 : }
228 : }
229 :
230 : Listener* listener =
231 : Listener::Add(aCx, static_cast<Listener*>(&collection->mListenerHead),
232 0 : aListenerVal, aPhase, aWantsUntrusted);
233 0 : if (!listener) {
234 0 : return false;
235 : }
236 :
237 0 : return true;
238 : }
239 :
240 : bool
241 0 : ListenerManager::Remove(JSContext* aCx, JSString* aType, jsval aListenerVal,
242 : Phase aPhase, bool aClearEmpty)
243 : {
244 0 : aType = JS_InternJSString(aCx, aType);
245 0 : if (!aType) {
246 0 : return false;
247 : }
248 :
249 : ListenerCollection* collection =
250 0 : GetCollectionForType(&mCollectionHead, INTERNED_STRING_TO_JSID(aCx, aType));
251 0 : if (!collection) {
252 0 : return true;
253 : }
254 :
255 0 : for (PRCList* elem = PR_NEXT_LINK(&collection->mListenerHead);
256 : elem != &collection->mListenerHead;
257 : elem = PR_NEXT_LINK(elem)) {
258 0 : Listener* listener = static_cast<Listener*>(elem);
259 0 : if (listener->mListenerVal == aListenerVal && listener->mPhase == aPhase) {
260 0 : Listener::Remove(aCx, listener);
261 0 : if (aClearEmpty && PR_CLIST_IS_EMPTY(&collection->mListenerHead)) {
262 0 : ListenerCollection::Remove(aCx, collection);
263 : }
264 0 : break;
265 : }
266 : }
267 :
268 0 : return true;
269 : }
270 :
271 : bool
272 0 : ListenerManager::SetEventListener(JSContext* aCx, JSString* aType,
273 : jsval aListener)
274 : {
275 : jsval existing;
276 0 : if (!GetEventListener(aCx, aType, &existing)) {
277 0 : return false;
278 : }
279 :
280 0 : if (!JSVAL_IS_VOID(existing) &&
281 0 : !Remove(aCx, aType, existing, Onfoo, false)) {
282 0 : return false;
283 : }
284 :
285 0 : return JSVAL_IS_VOID(aListener) || JSVAL_IS_NULL(aListener) ?
286 : true :
287 0 : Add(aCx, aType, aListener, Onfoo, false);
288 : }
289 :
290 : bool
291 0 : ListenerManager::GetEventListener(JSContext* aCx, JSString* aType,
292 : jsval* aListenerVal)
293 : {
294 0 : if (PR_CLIST_IS_EMPTY(&mCollectionHead)) {
295 0 : *aListenerVal = JSVAL_VOID;
296 0 : return true;
297 : }
298 :
299 0 : aType = JS_InternJSString(aCx, aType);
300 0 : if (!aType) {
301 0 : return false;
302 : }
303 :
304 0 : jsid typeId = INTERNED_STRING_TO_JSID(aCx, aType);
305 :
306 : ListenerCollection* collection =
307 0 : GetCollectionForType(&mCollectionHead, typeId);
308 0 : if (collection) {
309 0 : for (PRCList* elem = PR_PREV_LINK(&collection->mListenerHead);
310 : elem != &collection->mListenerHead;
311 : elem = PR_NEXT_LINK(elem)) {
312 0 : Listener* listener = static_cast<Listener*>(elem);
313 0 : if (listener->mPhase == Onfoo) {
314 0 : *aListenerVal = listener->mListenerVal;
315 0 : return true;
316 : }
317 : }
318 : }
319 0 : *aListenerVal = JSVAL_VOID;
320 0 : return true;
321 : }
322 :
323 : class ExternallyUsableContextAllocPolicy
324 : {
325 : JSContext *const cx;
326 :
327 : public:
328 0 : ExternallyUsableContextAllocPolicy(JSContext *cx) : cx(cx) {}
329 : JSContext *context() const { return cx; }
330 0 : void *malloc_(size_t bytes) {
331 0 : JSAutoRequest ar(cx);
332 0 : return JS_malloc(cx, bytes);
333 : }
334 : void *realloc_(void *p, size_t oldBytes, size_t bytes) {
335 : JSAutoRequest ar(cx);
336 : return JS_realloc(cx, p, bytes);
337 : }
338 0 : void free_(void *p) { JS_free(cx, p); }
339 0 : void reportAllocOverflow() const { JS_ReportAllocationOverflow(cx); }
340 : };
341 :
342 : bool
343 0 : ListenerManager::DispatchEvent(JSContext* aCx, JSObject* aTarget,
344 : JSObject* aEvent, bool* aPreventDefaultCalled)
345 : {
346 0 : if (!events::IsSupportedEventClass(aEvent)) {
347 : JS_ReportErrorNumber(aCx, js_GetErrorMessage, NULL,
348 : JSMSG_INCOMPATIBLE_METHOD,
349 0 : "EventTarget", "dispatchEvent", "Event object");
350 0 : return false;
351 : }
352 :
353 : jsval val;
354 0 : if (!JS_GetProperty(aCx, aEvent, "target", &val)) {
355 0 : return false;
356 : }
357 :
358 0 : if (!JSVAL_IS_NULL(val)) {
359 : // Already has a target, must be recursively dispatched. Throw.
360 0 : JS_ReportError(aCx, "Cannot recursively dispatch the same event!");
361 0 : return false;
362 : }
363 :
364 0 : if (PR_CLIST_IS_EMPTY(&mCollectionHead)) {
365 0 : *aPreventDefaultCalled = false;
366 0 : return true;
367 : }
368 :
369 : JSString* eventType;
370 : JSBool eventIsTrusted;
371 :
372 0 : if (!JS_GetProperty(aCx, aEvent, "type", &val) ||
373 : !(eventType = JS_ValueToString(aCx, val)) ||
374 : !(eventType = JS_InternJSString(aCx, eventType))) {
375 0 : return false;
376 : }
377 :
378 : // We have already ensure that the event is one of our types of events so
379 : // there is no need to worry about this property being faked.
380 0 : if (!JS_GetProperty(aCx, aEvent, "isTrusted", &val) ||
381 0 : !JS_ValueToBoolean(aCx, val, &eventIsTrusted)) {
382 0 : return false;
383 : }
384 :
385 : ListenerCollection* collection =
386 : GetCollectionForType(&mCollectionHead,
387 0 : INTERNED_STRING_TO_JSID(aCx, eventType));
388 0 : if (!collection) {
389 0 : *aPreventDefaultCalled = false;
390 0 : return true;
391 : }
392 :
393 0 : ExternallyUsableContextAllocPolicy ap(aCx);
394 0 : js::Vector<jsval, 10, ExternallyUsableContextAllocPolicy> listeners(ap);
395 :
396 0 : for (PRCList* elem = PR_NEXT_LINK(&collection->mListenerHead);
397 : elem != &collection->mListenerHead;
398 : elem = PR_NEXT_LINK(elem)) {
399 0 : Listener* listener = static_cast<Listener*>(elem);
400 :
401 : // Listeners that don't want untrusted events will be skipped if this is an
402 : // untrusted event.
403 0 : if ((eventIsTrusted || listener->mWantsUntrusted) &&
404 0 : !listeners.append(listener->mListenerVal)) {
405 0 : return false;
406 : }
407 : }
408 :
409 0 : if (listeners.empty()) {
410 0 : return true;
411 : }
412 :
413 0 : events::SetEventTarget(aEvent, aTarget);
414 :
415 0 : for (size_t index = 0; index < listeners.length(); index++) {
416 0 : if (events::EventImmediatePropagationStopped(aEvent)) {
417 0 : break;
418 : }
419 :
420 : // If anything fails in here we want to report the exception and continue on
421 : // to the next listener rather than bailing out. If something fails and
422 : // does not set an exception then we bail out entirely as we've either run
423 : // out of memory or the operation callback has indicated that we should
424 : // stop running.
425 :
426 0 : jsval listenerVal = listeners[index];
427 :
428 : JSObject* listenerObj;
429 0 : if (!JS_ValueToObject(aCx, listenerVal, &listenerObj)) {
430 0 : if (!JS_ReportPendingException(aCx)) {
431 0 : return false;
432 : }
433 0 : continue;
434 : }
435 :
436 : static const char sHandleEventChars[] = "handleEvent";
437 :
438 0 : JSObject* thisObj = aTarget;
439 :
440 : JSBool hasHandleEvent;
441 0 : if (!JS_HasProperty(aCx, listenerObj, sHandleEventChars, &hasHandleEvent)) {
442 0 : if (!JS_ReportPendingException(aCx)) {
443 0 : return false;
444 : }
445 0 : continue;
446 : }
447 :
448 0 : if (hasHandleEvent) {
449 0 : if (!JS_GetProperty(aCx, listenerObj, sHandleEventChars, &listenerVal)) {
450 0 : if (!JS_ReportPendingException(aCx)) {
451 0 : return false;
452 : }
453 0 : continue;
454 : }
455 :
456 0 : thisObj = listenerObj;
457 : }
458 :
459 0 : jsval argv[] = { OBJECT_TO_JSVAL(aEvent) };
460 0 : jsval rval = JSVAL_VOID;
461 0 : if (!JS_CallFunctionValue(aCx, thisObj, listenerVal, ArrayLength(argv),
462 0 : argv, &rval)) {
463 0 : if (!JS_ReportPendingException(aCx)) {
464 0 : return false;
465 : }
466 0 : continue;
467 : }
468 : }
469 :
470 0 : events::SetEventTarget(aEvent, NULL);
471 :
472 0 : *aPreventDefaultCalled = events::EventWasCanceled(aEvent);
473 0 : return true;
474 : }
475 :
476 : bool
477 0 : ListenerManager::HasListenersForTypeInternal(JSContext* aCx, JSString* aType)
478 : {
479 0 : JS_ASSERT(!PR_CLIST_IS_EMPTY(&mCollectionHead));
480 :
481 0 : aType = JS_InternJSString(aCx, aType);
482 0 : if (!aType) {
483 0 : return false;
484 : }
485 :
486 0 : jsid typeId = INTERNED_STRING_TO_JSID(aCx, aType);
487 0 : return !!GetCollectionForType(&mCollectionHead, typeId);
488 : }
|