bool EventListenerManager::DispatchEvent(JSContext* aCx, const EventTarget& aTarget, JSObject* aEvent, ErrorResult& aRv) const { using namespace mozilla::dom::workers::events; if (!IsSupportedEventClass(aEvent)) { aRv.Throw(NS_ERROR_FAILURE); return false; } jsval val; if (!JS_GetProperty(aCx, aEvent, "target", &val)) { aRv.Throw(NS_ERROR_FAILURE); return false; } if (!JSVAL_IS_NULL(val)) { // Already has a target, must be recursively dispatched. Throw. aRv.Throw(NS_ERROR_FAILURE); return false; } if (PR_CLIST_IS_EMPTY(&mCollectionHead)) { return false; } JSString* eventType; JSBool eventIsTrusted; if (!JS_GetProperty(aCx, aEvent, "type", &val) || !(eventType = JS_ValueToString(aCx, val)) || !(eventType = JS_InternJSString(aCx, eventType))) { aRv.Throw(NS_ERROR_FAILURE); return false; } // We have already ensure that the event is one of our types of events so // there is no need to worry about this property being faked. if (!JS_GetProperty(aCx, aEvent, "isTrusted", &val) || !JS_ValueToBoolean(aCx, val, &eventIsTrusted)) { aRv.Throw(NS_ERROR_FAILURE); return false; } ListenerCollection* collection = GetCollectionForType(&mCollectionHead, INTERNED_STRING_TO_JSID(aCx, eventType)); if (!collection) { return false; } ContextAllocPolicy ap(aCx); // XXXbent There is no reason to use nsAutoJSValHolder here as we should be // able to use js::AutoValueVector. Worse, nsAutoJSValHolder is much // slower. However, js::AutoValueVector causes crashes on Android at // the moment so we don't have much choice. js::Vector<nsAutoJSValHolder, 10, ContextAllocPolicy> listeners(ap); for (PRCList* elem = PR_NEXT_LINK(&collection->mListenerHead); elem != &collection->mListenerHead; elem = PR_NEXT_LINK(elem)) { ListenerData* listenerData = static_cast<ListenerData*>(elem); // Listeners that don't want untrusted events will be skipped if this is an // untrusted event. if (eventIsTrusted || listenerData->mWantsUntrusted) { nsAutoJSValHolder holder; if (!holder.Hold(aCx)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return false; } holder = listenerData->mListener; if (!listeners.append(holder)) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return false; } } } if (listeners.empty()) { return false; } SetEventTarget(aEvent, aTarget.GetJSObject()); for (size_t index = 0; index < listeners.length(); index++) { if (EventImmediatePropagationStopped(aEvent)) { break; } // If anything fails in here we want to report the exception and continue on // to the next listener rather than bailing out. If something fails and // does not set an exception then we bail out entirely as we've either run // out of memory or the operation callback has indicated that we should // stop running. jsval listenerVal = listeners[index]; JSObject* listenerObj; if (!JS_ValueToObject(aCx, listenerVal, &listenerObj)) { if (!JS_ReportPendingException(aCx)) { aRv.Throw(NS_ERROR_FAILURE); return false; } continue; } static const char sHandleEventChars[] = "handleEvent"; JSObject* thisObj = aTarget.GetJSObject(); JSBool hasHandleEvent; if (!JS_HasProperty(aCx, listenerObj, sHandleEventChars, &hasHandleEvent)) { if (!JS_ReportPendingException(aCx)) { aRv.Throw(NS_ERROR_FAILURE); return false; } continue; } if (hasHandleEvent) { if (!JS_GetProperty(aCx, listenerObj, sHandleEventChars, &listenerVal)) { if (!JS_ReportPendingException(aCx)) { aRv.Throw(NS_ERROR_FAILURE); return false; } continue; } thisObj = listenerObj; } jsval argv[] = { OBJECT_TO_JSVAL(aEvent) }; jsval rval = JSVAL_VOID; if (!JS_CallFunctionValue(aCx, thisObj, listenerVal, ArrayLength(argv), argv, &rval)) { if (!JS_ReportPendingException(aCx)) { aRv.Throw(NS_ERROR_FAILURE); return false; } continue; } } return EventWasCanceled(aEvent); }
bool EventListenerManager::DispatchEvent(JSContext* aCx, const EventTarget& aTarget, JSObject* event, ErrorResult& aRv) const { JS::Rooted<JSObject*> aEvent(aCx, event); using namespace mozilla::dom::workers::events; if (!IsSupportedEventClass(aEvent)) { aRv.Throw(NS_ERROR_FAILURE); return false; } JS::Rooted<JS::Value> val(aCx); if (!JS_GetProperty(aCx, aEvent, "target", &val)) { aRv.Throw(NS_ERROR_FAILURE); return false; } if (!JSVAL_IS_NULL(val)) { // Already has a target, must be recursively dispatched. Throw. aRv.Throw(NS_ERROR_FAILURE); return false; } if (mCollections.isEmpty()) { return false; } JS::Rooted<JSString*> eventType(aCx); bool eventIsTrusted; if (!JS_GetProperty(aCx, aEvent, "type", &val) || !(eventType = JS_ValueToString(aCx, val)) || !(eventType = JS_InternJSString(aCx, eventType))) { aRv.Throw(NS_ERROR_FAILURE); return false; } // We have already ensure that the event is one of our types of events so // there is no need to worry about this property being faked. if (!JS_GetProperty(aCx, aEvent, "isTrusted", &val) || !JS_ValueToBoolean(aCx, val, &eventIsTrusted)) { aRv.Throw(NS_ERROR_FAILURE); return false; } ListenerCollection* collection = GetCollectionForType(mCollections, INTERNED_STRING_TO_JSID(aCx, eventType)); if (!collection) { return false; } JS::AutoValueVector listeners(aCx); for (ListenerData* listenerData = collection->mListeners.getFirst(); listenerData; listenerData = listenerData->getNext()) { // Listeners that don't want untrusted events will be skipped if this is an // untrusted event. if (eventIsTrusted || listenerData->mWantsUntrusted) { if (!listeners.append(OBJECT_TO_JSVAL(listenerData->mListener))) { aRv.Throw(NS_ERROR_OUT_OF_MEMORY); return false; } } } if (listeners.empty()) { return false; } SetEventTarget(aEvent, aTarget.GetJSObject()); for (size_t index = 0; index < listeners.length(); index++) { if (EventImmediatePropagationStopped(aEvent)) { break; } // If anything fails in here we want to report the exception and continue on // to the next listener rather than bailing out. If something fails and // does not set an exception then we bail out entirely as we've either run // out of memory or the operation callback has indicated that we should // stop running. JS::Rooted<JS::Value> listenerVal(aCx, listeners[index]); JS::Rooted<JSObject*> listenerObj(aCx); if (!JS_ValueToObject(aCx, listenerVal, &listenerObj)) { if (!JS_ReportPendingException(aCx)) { aRv.Throw(NS_ERROR_FAILURE); return false; } continue; } static const char sHandleEventChars[] = "handleEvent"; JS::Rooted<JSObject*> thisObj(aCx, aTarget.GetJSObject()); bool hasHandleEvent; if (!JS_HasProperty(aCx, listenerObj, sHandleEventChars, &hasHandleEvent)) { if (!JS_ReportPendingException(aCx)) { aRv.Throw(NS_ERROR_FAILURE); return false; } continue; } if (hasHandleEvent) { if (!JS_GetProperty(aCx, listenerObj, sHandleEventChars, &listenerVal)) { if (!JS_ReportPendingException(aCx)) { aRv.Throw(NS_ERROR_FAILURE); return false; } continue; } thisObj = listenerObj; } jsval argv[] = { OBJECT_TO_JSVAL(aEvent) }; JS::Rooted<JS::Value> rval(aCx); if (!JS_CallFunctionValue(aCx, thisObj, listenerVal, ArrayLength(argv), argv, rval.address())) { if (!JS_ReportPendingException(aCx)) { aRv.Throw(NS_ERROR_FAILURE); return false; } continue; } } return EventWasCanceled(aEvent); }