nsresult EventListenerManager::SetEventHandler(nsIAtom* aName, const nsAString& aBody, uint32_t aLanguage, bool aDeferCompilation, bool aPermitUntrustedEvents, Element* aElement) { NS_PRECONDITION(aLanguage != nsIProgrammingLanguage::UNKNOWN, "Must know the language for the script event listener"); // |aPermitUntrustedEvents| is set to False for chrome - events // *generated* from an unknown source are not allowed. // However, for script languages with no 'sandbox', we want to reject // such scripts based on the source of their code, not just the source // of the event. if (aPermitUntrustedEvents && aLanguage != nsIProgrammingLanguage::JAVASCRIPT) { NS_WARNING("Discarding non-JS event listener from untrusted source"); return NS_ERROR_FAILURE; } nsCOMPtr<nsIDocument> doc; nsCOMPtr<nsIScriptGlobalObject> global = GetScriptGlobalAndDocument(getter_AddRefs(doc)); if (!global) { // This can happen; for example this document might have been // loaded as data. return NS_OK; } #ifdef DEBUG nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(global); if (win) { MOZ_ASSERT(win->IsInnerWindow(), "We should not have an outer window here!"); } #endif nsresult rv = NS_OK; // return early preventing the event listener from being added // 'doc' is fetched above if (doc) { // Don't allow adding an event listener if the document is sandboxed // without 'allow-scripts'. if (doc->GetSandboxFlags() & SANDBOXED_SCRIPTS) { return NS_ERROR_DOM_SECURITY_ERR; } nsCOMPtr<nsIContentSecurityPolicy> csp; rv = doc->NodePrincipal()->GetCsp(getter_AddRefs(csp)); NS_ENSURE_SUCCESS(rv, rv); if (csp) { bool inlineOK = true; bool reportViolations = false; rv = csp->GetAllowsInlineScript(&reportViolations, &inlineOK); NS_ENSURE_SUCCESS(rv, rv); if (reportViolations) { // gather information to log with violation report nsIURI* uri = doc->GetDocumentURI(); nsAutoCString asciiSpec; if (uri) uri->GetAsciiSpec(asciiSpec); nsAutoString scriptSample, attr, tagName(NS_LITERAL_STRING("UNKNOWN")); aName->ToString(attr); nsCOMPtr<nsIDOMNode> domNode(do_QueryInterface(mTarget)); if (domNode) domNode->GetNodeName(tagName); // build a "script sample" based on what we know about this element scriptSample.Assign(attr); scriptSample.AppendLiteral(" attribute on "); scriptSample.Append(tagName); scriptSample.AppendLiteral(" element"); csp->LogViolationDetails(nsIContentSecurityPolicy::VIOLATION_TYPE_INLINE_SCRIPT, NS_ConvertUTF8toUTF16(asciiSpec), scriptSample, 0, EmptyString(), EmptyString()); } // return early if CSP wants us to block inline scripts if (!inlineOK) { return NS_OK; } } } // This might be the first reference to this language in the global // We must init the language before we attempt to fetch its context. if (NS_FAILED(global->EnsureScriptEnvironment())) { NS_WARNING("Failed to setup script environment for this language"); // but fall through and let the inevitable failure below handle it. } nsIScriptContext* context = global->GetScriptContext(); NS_ENSURE_TRUE(context, NS_ERROR_FAILURE); NS_ENSURE_STATE(global->GetGlobalJSObject()); JSAutoRequest ar(context->GetNativeContext()); JS::Rooted<JSObject*> scope(context->GetNativeContext(), global->GetGlobalJSObject()); Listener* listener = SetEventHandlerInternal(scope, aName, EmptyString(), nsEventHandler(), aPermitUntrustedEvents); if (!aDeferCompilation) { return CompileEventHandlerInternal(listener, &aBody, aElement); } return NS_OK; }
nsresult EventListenerManager::CompileEventHandlerInternal(Listener* aListener, const nsAString* aBody, Element* aElement) { NS_PRECONDITION(aListener->GetJSListener(), "Why do we not have a JS listener?"); NS_PRECONDITION(aListener->mHandlerIsString, "Why are we compiling a non-string JS listener?"); nsresult result = NS_OK; nsIJSEventListener* jsListener = aListener->GetJSListener(); NS_ASSERTION(!jsListener->GetHandler().HasEventHandler(), "What is there to compile?"); nsCOMPtr<nsIDocument> doc; nsCOMPtr<nsIScriptGlobalObject> global = GetScriptGlobalAndDocument(getter_AddRefs(doc)); NS_ENSURE_STATE(global); nsIScriptContext* context = global->GetScriptContext(); NS_ENSURE_STATE(context); // Push a context to make sure exceptions are reported in the right place. AutoPushJSContext cx(context->GetNativeContext()); JS::Rooted<JSObject*> handler(cx); JS::Rooted<JSObject*> scope(cx, jsListener->GetEventScope()); nsCOMPtr<nsIAtom> typeAtom = aListener->mTypeAtom; nsIAtom* attrName = typeAtom; if (aListener->mHandlerIsString) { // OK, we didn't find an existing compiled event handler. Flag us // as not a string so we don't keep trying to compile strings // which can't be compiled aListener->mHandlerIsString = false; // mTarget may not be an Element if it's a window and we're // getting an inline event listener forwarded from <html:body> or // <html:frameset> or <xul:window> or the like. // XXX I don't like that we have to reference content from // here. The alternative is to store the event handler string on // the nsIJSEventListener itself, and that still doesn't address // the arg names issue. nsCOMPtr<Element> element = do_QueryInterface(mTarget); MOZ_ASSERT(element || aBody, "Where will we get our body?"); nsAutoString handlerBody; const nsAString* body = aBody; if (!aBody) { if (aListener->mTypeAtom == nsGkAtoms::onSVGLoad) { attrName = nsGkAtoms::onload; } else if (aListener->mTypeAtom == nsGkAtoms::onSVGUnload) { attrName = nsGkAtoms::onunload; } else if (aListener->mTypeAtom == nsGkAtoms::onSVGResize) { attrName = nsGkAtoms::onresize; } else if (aListener->mTypeAtom == nsGkAtoms::onSVGScroll) { attrName = nsGkAtoms::onscroll; } else if (aListener->mTypeAtom == nsGkAtoms::onSVGZoom) { attrName = nsGkAtoms::onzoom; } else if (aListener->mTypeAtom == nsGkAtoms::onbeginEvent) { attrName = nsGkAtoms::onbegin; } else if (aListener->mTypeAtom == nsGkAtoms::onrepeatEvent) { attrName = nsGkAtoms::onrepeat; } else if (aListener->mTypeAtom == nsGkAtoms::onendEvent) { attrName = nsGkAtoms::onend; } element->GetAttr(kNameSpaceID_None, attrName, handlerBody); body = &handlerBody; aElement = element; } aListener = nullptr; uint32_t lineNo = 0; nsAutoCString url (NS_LITERAL_CSTRING("-moz-evil:lying-event-listener")); MOZ_ASSERT(body); MOZ_ASSERT(aElement); nsIURI *uri = aElement->OwnerDoc()->GetDocumentURI(); if (uri) { uri->GetSpec(url); lineNo = 1; } uint32_t argCount; const char **argNames; nsContentUtils::GetEventArgNames(aElement->GetNameSpaceID(), typeAtom, &argCount, &argNames); JSAutoCompartment ac(cx, context->GetWindowProxy()); JS::CompileOptions options(cx); options.setIntroductionType("eventHandler") .setFileAndLine(url.get(), lineNo) .setVersion(SCRIPTVERSION_DEFAULT); JS::Rooted<JS::Value> targetVal(cx); // Go ahead and wrap into the current compartment of cx directly. JS::Rooted<JSObject*> wrapScope(cx, JS::CurrentGlobalOrNull(cx)); if (WrapNewBindingObject(cx, wrapScope, aElement, &targetVal)) { MOZ_ASSERT(targetVal.isObject()); nsDependentAtomString str(attrName); // Most of our names are short enough that we don't even have to malloc // the JS string stuff, so don't worry about playing games with // refcounting XPCOM stringbuffers. JS::Rooted<JSString*> jsStr(cx, JS_NewUCStringCopyN(cx, str.BeginReading(), str.Length())); NS_ENSURE_TRUE(jsStr, NS_ERROR_OUT_OF_MEMORY); options.setElement(&targetVal.toObject()) .setElementAttributeName(jsStr); } JS::Rooted<JSObject*> handlerFun(cx); result = nsJSUtils::CompileFunction(cx, JS::NullPtr(), options, nsAtomCString(typeAtom), argCount, argNames, *body, handlerFun.address()); NS_ENSURE_SUCCESS(result, result); handler = handlerFun; NS_ENSURE_TRUE(handler, NS_ERROR_FAILURE); } else { aListener = nullptr; } if (handler) { nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(mTarget); // Bind it JS::Rooted<JSObject*> boundHandler(cx); context->BindCompiledEventHandler(mTarget, scope, handler, &boundHandler); // Note - We pass null for aIncumbentGlobal below. We could also pass the // compilation global, but since the handler is guaranteed to be scripted, // there's no need to use an override, since the JS engine will always give // us the right answer. if (!boundHandler) { jsListener->ForgetHandler(); } else if (jsListener->EventName() == nsGkAtoms::onerror && win) { nsRefPtr<OnErrorEventHandlerNonNull> handlerCallback = new OnErrorEventHandlerNonNull(boundHandler, /* aIncumbentGlobal = */ nullptr); jsListener->SetHandler(handlerCallback); } else if (jsListener->EventName() == nsGkAtoms::onbeforeunload && win) { nsRefPtr<OnBeforeUnloadEventHandlerNonNull> handlerCallback = new OnBeforeUnloadEventHandlerNonNull(boundHandler, /* aIncumbentGlobal = */ nullptr); jsListener->SetHandler(handlerCallback); } else { nsRefPtr<EventHandlerNonNull> handlerCallback = new EventHandlerNonNull(boundHandler, /* aIncumbentGlobal = */ nullptr); jsListener->SetHandler(handlerCallback); } } return result; }
nsresult EventListenerManager::CompileEventHandlerInternal(Listener* aListener, const nsAString* aBody, Element* aElement) { MOZ_ASSERT(aListener->GetJSEventHandler()); MOZ_ASSERT(aListener->mHandlerIsString, "Why are we compiling a non-string JS listener?"); JSEventHandler* jsEventHandler = aListener->GetJSEventHandler(); MOZ_ASSERT(!jsEventHandler->GetTypedEventHandler().HasEventHandler(), "What is there to compile?"); nsresult result = NS_OK; nsCOMPtr<nsIDocument> doc; nsCOMPtr<nsIScriptGlobalObject> global = GetScriptGlobalAndDocument(getter_AddRefs(doc)); NS_ENSURE_STATE(global); // Activate JSAPI, and make sure that exceptions are reported on the right // Window. AutoJSAPI jsapi; if (NS_WARN_IF(!jsapi.InitWithLegacyErrorReporting(global))) { return NS_ERROR_UNEXPECTED; } JSContext* cx = jsapi.cx(); nsCOMPtr<nsIAtom> typeAtom = aListener->mTypeAtom; nsIAtom* attrName = typeAtom; // Flag us as not a string so we don't keep trying to compile strings which // can't be compiled. aListener->mHandlerIsString = false; // mTarget may not be an Element if it's a window and we're // getting an inline event listener forwarded from <html:body> or // <html:frameset> or <xul:window> or the like. // XXX I don't like that we have to reference content from // here. The alternative is to store the event handler string on // the JSEventHandler itself, and that still doesn't address // the arg names issue. nsCOMPtr<Element> element = do_QueryInterface(mTarget); MOZ_ASSERT(element || aBody, "Where will we get our body?"); nsAutoString handlerBody; const nsAString* body = aBody; if (!aBody) { if (aListener->mTypeAtom == nsGkAtoms::onSVGLoad) { attrName = nsGkAtoms::onload; } else if (aListener->mTypeAtom == nsGkAtoms::onSVGUnload) { attrName = nsGkAtoms::onunload; } else if (aListener->mTypeAtom == nsGkAtoms::onSVGResize) { attrName = nsGkAtoms::onresize; } else if (aListener->mTypeAtom == nsGkAtoms::onSVGScroll) { attrName = nsGkAtoms::onscroll; } else if (aListener->mTypeAtom == nsGkAtoms::onSVGZoom) { attrName = nsGkAtoms::onzoom; } else if (aListener->mTypeAtom == nsGkAtoms::onbeginEvent) { attrName = nsGkAtoms::onbegin; } else if (aListener->mTypeAtom == nsGkAtoms::onrepeatEvent) { attrName = nsGkAtoms::onrepeat; } else if (aListener->mTypeAtom == nsGkAtoms::onendEvent) { attrName = nsGkAtoms::onend; } element->GetAttr(kNameSpaceID_None, attrName, handlerBody); body = &handlerBody; aElement = element; } aListener = nullptr; uint32_t lineNo = 0; nsAutoCString url (NS_LITERAL_CSTRING("-moz-evil:lying-event-listener")); MOZ_ASSERT(body); MOZ_ASSERT(aElement); nsIURI *uri = aElement->OwnerDoc()->GetDocumentURI(); if (uri) { uri->GetSpec(url); lineNo = 1; } nsCOMPtr<nsPIDOMWindow> win = do_QueryInterface(mTarget); uint32_t argCount; const char **argNames; nsContentUtils::GetEventArgNames(aElement->GetNameSpaceID(), typeAtom, win, &argCount, &argNames); JSAddonId *addonId = MapURIToAddonID(uri); // Wrap the event target, so that we can use it as the scope for the event // handler. Note that mTarget is different from aElement in the <body> case, // where mTarget is a Window. // // The wrapScope doesn't really matter here, because the target will create // its reflector in the proper scope, and then we'll enter that compartment. JS::Rooted<JSObject*> wrapScope(cx, global->GetGlobalJSObject()); JS::Rooted<JS::Value> v(cx); { JSAutoCompartment ac(cx, wrapScope); nsresult rv = nsContentUtils::WrapNative(cx, mTarget, &v, /* aAllowWrapping = */ false); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } } if (addonId) { JS::Rooted<JSObject*> vObj(cx, &v.toObject()); JS::Rooted<JSObject*> addonScope(cx, xpc::GetAddonScope(cx, vObj, addonId)); if (!addonScope) { return NS_ERROR_FAILURE; } JSAutoCompartment ac(cx, addonScope); if (!JS_WrapValue(cx, &v)) { return NS_ERROR_FAILURE; } } JS::Rooted<JSObject*> target(cx, &v.toObject()); JSAutoCompartment ac(cx, target); nsDependentAtomString str(attrName); // Most of our names are short enough that we don't even have to malloc // the JS string stuff, so don't worry about playing games with // refcounting XPCOM stringbuffers. JS::Rooted<JSString*> jsStr(cx, JS_NewUCStringCopyN(cx, str.BeginReading(), str.Length())); NS_ENSURE_TRUE(jsStr, NS_ERROR_OUT_OF_MEMORY); // Get the reflector for |aElement|, so that we can pass to setElement. if (NS_WARN_IF(!WrapNewBindingObject(cx, target, aElement, &v))) { return NS_ERROR_FAILURE; } JS::CompileOptions options(cx); options.setIntroductionType("eventHandler") .setFileAndLine(url.get(), lineNo) .setVersion(JSVERSION_DEFAULT) .setElement(&v.toObject()) .setElementAttributeName(jsStr) .setDefineOnScope(false); JS::Rooted<JSObject*> handler(cx); result = nsJSUtils::CompileFunction(cx, target, options, nsAtomCString(typeAtom), argCount, argNames, *body, handler.address()); NS_ENSURE_SUCCESS(result, result); NS_ENSURE_TRUE(handler, NS_ERROR_FAILURE); if (jsEventHandler->EventName() == nsGkAtoms::onerror && win) { nsRefPtr<OnErrorEventHandlerNonNull> handlerCallback = new OnErrorEventHandlerNonNull(handler, /* aIncumbentGlobal = */ nullptr); jsEventHandler->SetHandler(handlerCallback); } else if (jsEventHandler->EventName() == nsGkAtoms::onbeforeunload && win) { nsRefPtr<OnBeforeUnloadEventHandlerNonNull> handlerCallback = new OnBeforeUnloadEventHandlerNonNull(handler, /* aIncumbentGlobal = */ nullptr); jsEventHandler->SetHandler(handlerCallback); } else { nsRefPtr<EventHandlerNonNull> handlerCallback = new EventHandlerNonNull(handler, /* aIncumbentGlobal = */ nullptr); jsEventHandler->SetHandler(handlerCallback); } return result; }