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);
}