michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: #include "nsJSUtils.h" michael@0: #include "nsString.h" michael@0: #include "nsIServiceManager.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIScriptContext.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "nsIMutableArray.h" michael@0: #include "nsVariant.h" michael@0: #include "nsIDOMBeforeUnloadEvent.h" michael@0: #include "nsGkAtoms.h" michael@0: #include "xpcpublic.h" michael@0: #include "nsJSEnvironment.h" michael@0: #include "nsDOMJSUtils.h" michael@0: #include "WorkerPrivate.h" michael@0: #include "mozilla/ContentEvents.h" michael@0: #include "mozilla/CycleCollectedJSRuntime.h" michael@0: #include "mozilla/HoldDropJSObjects.h" michael@0: #include "mozilla/JSEventHandler.h" michael@0: #include "mozilla/Likely.h" michael@0: #include "mozilla/dom/ErrorEvent.h" michael@0: #include "mozilla/dom/UnionTypes.h" michael@0: michael@0: namespace mozilla { michael@0: michael@0: using namespace dom; michael@0: michael@0: JSEventHandler::JSEventHandler(nsISupports* aTarget, michael@0: nsIAtom* aType, michael@0: const TypedEventHandler& aTypedHandler) michael@0: : mEventName(aType) michael@0: , mTypedHandler(aTypedHandler) michael@0: { michael@0: nsCOMPtr base = do_QueryInterface(aTarget); michael@0: mTarget = base.get(); michael@0: // Note, we call HoldJSObjects to get CanSkip called before CC. michael@0: HoldJSObjects(this); michael@0: } michael@0: michael@0: JSEventHandler::~JSEventHandler() michael@0: { michael@0: NS_ASSERTION(!mTarget, "Should have called Disconnect()!"); michael@0: DropJSObjects(this); michael@0: } michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CLASS(JSEventHandler) michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(JSEventHandler) michael@0: tmp->mTypedHandler.ForgetHandler(); michael@0: NS_IMPL_CYCLE_COLLECTION_UNLINK_END michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(JSEventHandler) michael@0: if (MOZ_UNLIKELY(cb.WantDebugInfo()) && tmp->mEventName) { michael@0: nsAutoCString name; michael@0: name.AppendLiteral("JSEventHandler handlerName="); michael@0: name.Append( michael@0: NS_ConvertUTF16toUTF8(nsDependentAtomString(tmp->mEventName)).get()); michael@0: cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name.get()); michael@0: } else { michael@0: NS_IMPL_CYCLE_COLLECTION_DESCRIBE(JSEventHandler, tmp->mRefCnt.get()) michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mTypedHandler.Ptr()) michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS michael@0: NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(JSEventHandler) michael@0: if (tmp->IsBlackForCC()) { michael@0: return true; michael@0: } michael@0: // If we have a target, it is the one which has tmp as onfoo handler. michael@0: if (tmp->mTarget) { michael@0: nsXPCOMCycleCollectionParticipant* cp = nullptr; michael@0: CallQueryInterface(tmp->mTarget, &cp); michael@0: nsISupports* canonical = nullptr; michael@0: tmp->mTarget->QueryInterface(NS_GET_IID(nsCycleCollectionISupports), michael@0: reinterpret_cast(&canonical)); michael@0: // Usually CanSkip ends up unmarking the event listeners of mTarget, michael@0: // so tmp may become black. michael@0: if (cp && canonical && cp->CanSkip(canonical, true)) { michael@0: return tmp->IsBlackForCC(); michael@0: } michael@0: } michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(JSEventHandler) michael@0: return tmp->IsBlackForCC(); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(JSEventHandler) michael@0: return tmp->IsBlackForCC(); michael@0: NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSEventHandler) michael@0: NS_INTERFACE_MAP_ENTRY(nsIDOMEventListener) michael@0: NS_INTERFACE_MAP_ENTRY(nsISupports) michael@0: NS_INTERFACE_MAP_ENTRY(JSEventHandler) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(JSEventHandler) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(JSEventHandler) michael@0: michael@0: bool michael@0: JSEventHandler::IsBlackForCC() michael@0: { michael@0: // We can claim to be black if all the things we reference are michael@0: // effectively black already. michael@0: return !mTypedHandler.HasEventHandler() || michael@0: !mTypedHandler.Ptr()->HasGrayCallable(); michael@0: } michael@0: michael@0: nsresult michael@0: JSEventHandler::HandleEvent(nsIDOMEvent* aEvent) michael@0: { michael@0: nsCOMPtr target = do_QueryInterface(mTarget); michael@0: if (!target || !mTypedHandler.HasEventHandler() || michael@0: !GetTypedEventHandler().Ptr()->CallbackPreserveColor()) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: Event* event = aEvent->InternalDOMEvent(); michael@0: bool isMainThread = event->IsMainThreadEvent(); michael@0: bool isChromeHandler = michael@0: isMainThread ? michael@0: nsContentUtils::GetObjectPrincipal( michael@0: GetTypedEventHandler().Ptr()->CallbackPreserveColor()) == michael@0: nsContentUtils::GetSystemPrincipal() : michael@0: mozilla::dom::workers::IsCurrentThreadRunningChromeWorker(); michael@0: michael@0: if (mTypedHandler.Type() == TypedEventHandler::eOnError) { michael@0: MOZ_ASSERT_IF(mEventName, mEventName == nsGkAtoms::onerror); michael@0: michael@0: nsString errorMsg, file; michael@0: EventOrString msgOrEvent; michael@0: Optional fileName; michael@0: Optional lineNumber; michael@0: Optional columnNumber; michael@0: Optional> error; michael@0: michael@0: NS_ENSURE_TRUE(aEvent, NS_ERROR_UNEXPECTED); michael@0: ErrorEvent* scriptEvent = aEvent->InternalDOMEvent()->AsErrorEvent(); michael@0: if (scriptEvent) { michael@0: scriptEvent->GetMessage(errorMsg); michael@0: msgOrEvent.SetAsString().SetData(errorMsg.Data(), errorMsg.Length()); michael@0: michael@0: scriptEvent->GetFilename(file); michael@0: fileName = &file; michael@0: michael@0: lineNumber.Construct(); michael@0: lineNumber.Value() = scriptEvent->Lineno(); michael@0: michael@0: columnNumber.Construct(); michael@0: columnNumber.Value() = scriptEvent->Colno(); michael@0: michael@0: ThreadsafeAutoJSContext cx; michael@0: error.Construct(cx); michael@0: scriptEvent->GetError(cx, &error.Value()); michael@0: } else { michael@0: msgOrEvent.SetAsEvent() = aEvent->InternalDOMEvent(); michael@0: } michael@0: michael@0: nsRefPtr handler = michael@0: mTypedHandler.OnErrorEventHandler(); michael@0: ErrorResult rv; michael@0: bool handled = handler->Call(mTarget, msgOrEvent, fileName, lineNumber, michael@0: columnNumber, error, rv); michael@0: if (rv.Failed()) { michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: if (handled) { michael@0: event->PreventDefaultInternal(isChromeHandler); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: if (mTypedHandler.Type() == TypedEventHandler::eOnBeforeUnload) { michael@0: MOZ_ASSERT(mEventName == nsGkAtoms::onbeforeunload); michael@0: michael@0: nsRefPtr handler = michael@0: mTypedHandler.OnBeforeUnloadEventHandler(); michael@0: ErrorResult rv; michael@0: nsString retval; michael@0: handler->Call(mTarget, *(aEvent->InternalDOMEvent()), retval, rv); michael@0: if (rv.Failed()) { michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: nsCOMPtr beforeUnload = do_QueryInterface(aEvent); michael@0: NS_ENSURE_STATE(beforeUnload); michael@0: michael@0: if (!DOMStringIsNull(retval)) { michael@0: event->PreventDefaultInternal(isChromeHandler); michael@0: michael@0: nsAutoString text; michael@0: beforeUnload->GetReturnValue(text); michael@0: michael@0: // Set the text in the beforeUnload event as long as it wasn't michael@0: // already set (through event.returnValue, which takes michael@0: // precedence over a value returned from a JS function in IE) michael@0: if (text.IsEmpty()) { michael@0: beforeUnload->SetReturnValue(retval); michael@0: } michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: MOZ_ASSERT(mTypedHandler.Type() == TypedEventHandler::eNormal); michael@0: ErrorResult rv; michael@0: nsRefPtr handler = mTypedHandler.NormalEventHandler(); michael@0: JS::Rooted retval(CycleCollectedJSRuntime::Get()->Runtime()); michael@0: handler->Call(mTarget, *(aEvent->InternalDOMEvent()), &retval, rv); michael@0: if (rv.Failed()) { michael@0: return rv.ErrorCode(); michael@0: } michael@0: michael@0: // If the handler returned false and its sense is not reversed, michael@0: // or the handler returned true and its sense is reversed from michael@0: // the usual (false means cancel), then prevent default. michael@0: if (retval.isBoolean() && michael@0: retval.toBoolean() == (mEventName == nsGkAtoms::onerror || michael@0: mEventName == nsGkAtoms::onmouseover)) { michael@0: event->PreventDefaultInternal(isChromeHandler); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // namespace mozilla michael@0: michael@0: using namespace mozilla; michael@0: michael@0: /* michael@0: * Factory functions michael@0: */ michael@0: michael@0: nsresult michael@0: NS_NewJSEventHandler(nsISupports* aTarget, michael@0: nsIAtom* aEventType, michael@0: const TypedEventHandler& aTypedHandler, michael@0: JSEventHandler** aReturn) michael@0: { michael@0: NS_ENSURE_ARG(aEventType || !NS_IsMainThread()); michael@0: JSEventHandler* it = michael@0: new JSEventHandler(aTarget, aEventType, aTypedHandler); michael@0: NS_ADDREF(*aReturn = it); michael@0: michael@0: return NS_OK; michael@0: }