tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit fa35c395d66b02787a6eef65e478a37f34e7fc9d
parent 8fd0424527173fd491a269921ecf8e5357378ba1
Author: Matthew Gaudet <mgaudet@mozilla.com>
Date:   Wed,  1 Oct 2025 21:38:49 +0000

Bug 1983154 - Refactor CallSetup r=smaug

This refactoring splits the CallSetup constructor into some recyclable pieces.

Differential Revision: https://phabricator.services.mozilla.com/D265352

Diffstat:
Mdom/bindings/CallbackObject.cpp | 209+++++++++++++++++++++++++++++++++++++++++++++++++------------------------------
Mdom/bindings/CallbackObject.h | 31+++++++++++++++++++++++++------
2 files changed, 155 insertions(+), 85 deletions(-)

diff --git a/dom/bindings/CallbackObject.cpp b/dom/bindings/CallbackObject.cpp @@ -189,102 +189,83 @@ void CallbackObjectBase::GetDescription(nsACString& aOutString) { aOutString.Append(")"); } -CallSetup::CallSetup(CallbackObjectBase* aCallback, ErrorResult& aRv, - const char* aExecutionReason, - CallbackObjectBase::ExceptionHandling aExceptionHandling, - JS::Realm* aRealm, bool aIsJSImplementedWebIDL) - : mCx(nullptr), - mRealm(aRealm), - mErrorResult(aRv), - mExceptionHandling(aExceptionHandling), - mIsMainThread(NS_IsMainThread()) { - MOZ_ASSERT_IF( - aExceptionHandling == CallbackObjectBase::eReportExceptions || - aExceptionHandling == CallbackObjectBase::eRethrowExceptions, - !aRealm); - - CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); - if (ccjs) { - ccjs->EnterMicroTask(); +// Get the global for this callback: Note that this can return nullptr +// if it doesn't make sense to invoke the callback or global here . +// +// Note that for the case of JS-implemented WebIDL we never have a window here. +nsIGlobalObject* CallSetup::GetActiveGlobalObjectForCall( + JS::Handle<JSObject*> callbackOrGlobal, bool aIsMainThread, + bool aIsJSImplementedWebIDL, ErrorResult& aRv) { + nsGlobalWindowInner* win = aIsMainThread && !aIsJSImplementedWebIDL + ? xpc::WindowGlobalOrNull(callbackOrGlobal) + : nullptr; + if (win) { + // We don't want to run script in windows that have been navigated away + // from. + if (!win->HasActiveDocument()) { + aRv.ThrowNotSupportedError( + "Refusing to execute function from window whose document is no " + "longer active."); + return nullptr; + } + return win; } - // Compute the caller's subject principal (if necessary) early, before we - // do anything that might perturb the relevant state. - nsIPrincipal* webIDLCallerPrincipal = nullptr; - if (aIsJSImplementedWebIDL) { - webIDLCallerPrincipal = - nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller(); - } + // No DOM Window. Store the global. + auto* globalObject = xpc::NativeGlobal(callbackOrGlobal); + MOZ_ASSERT(globalObject); + return globalObject; +} - JSObject* wrappedCallback = aCallback->CallbackPreserveColor(); - if (!wrappedCallback) { +// Check that it's OK & possible to execute script in the given global, and if +// not return false and fill in aRv. +bool CallSetup::CheckBeforeExecution(nsIGlobalObject* aGlobalObject, + JSObject* aCallbackOrGlobal, + bool aIsJSImplementedWebIDL, + ErrorResult& aRv) { + if (aGlobalObject->IsScriptForbidden(aCallbackOrGlobal, + aIsJSImplementedWebIDL)) { aRv.ThrowNotSupportedError( - "Cannot execute callback from a nuked compartment."); - return; - } - - nsIGlobalObject* globalObject = nullptr; - - { - // First, find the real underlying callback. - JS::Rooted<JSObject*> realCallback(ccjs->RootingCx(), - js::UncheckedUnwrap(wrappedCallback)); - - // Get the global for this callback. Note that for the case of - // JS-implemented WebIDL we never have a window here. - nsGlobalWindowInner* win = mIsMainThread && !aIsJSImplementedWebIDL - ? xpc::WindowGlobalOrNull(realCallback) - : nullptr; - if (win) { - // We don't want to run script in windows that have been navigated away - // from. - if (!win->HasActiveDocument()) { - aRv.ThrowNotSupportedError( - "Refusing to execute function from window whose document is no " - "longer active."); - return; - } - globalObject = win; - } else { - // No DOM Window. Store the global. - globalObject = xpc::NativeGlobal(realCallback); - MOZ_ASSERT(globalObject); - } - - // Make sure to use realCallback to get the global of the callback - // object, not the wrapper. - if (globalObject->IsScriptForbidden(realCallback, aIsJSImplementedWebIDL)) { - aRv.ThrowNotSupportedError( - "Refusing to execute function from global in which script is " - "disabled."); - return; - } + "Refusing to execute function from global in which script is " + "disabled."); + return false; } // Bail out if there's no useful global. - if (!globalObject->HasJSGlobal()) { + if (!aGlobalObject->HasJSGlobal()) { aRv.ThrowNotSupportedError( "Refusing to execute function from global which is being torn down."); - return; + return false; } + return true; +} + +void CallSetup::SetupForExecution(nsIGlobalObject* aGlobalObject, + nsIGlobalObject* aIncumbentGlobal, + JS::Handle<JSObject*> aCallbackOrGlobal, + JS::Handle<JSObject*> aCallbackGlobal, + JS::Handle<JSObject*> aCreationStack, + nsIPrincipal* aWebIDLCallerPrincipal, + const char* aExecutionReason, + ErrorResult& aRv) { AutoAllowLegacyScriptExecution exemption; - mAutoEntryScript.emplace(globalObject, aExecutionReason, mIsMainThread); - mAutoEntryScript->SetWebIDLCallerPrincipal(webIDLCallerPrincipal); - nsIGlobalObject* incumbent = aCallback->IncumbentGlobalOrNull(); - if (incumbent) { + mAutoEntryScript.emplace(aGlobalObject, aExecutionReason, mIsMainThread); + mAutoEntryScript->SetWebIDLCallerPrincipal(aWebIDLCallerPrincipal); + + if (aIncumbentGlobal) { // The callback object traces its incumbent JS global, so in general it // should be alive here. However, it's possible that we could run afoul // of the same IPC global weirdness described above, wherein the // nsIGlobalObject has severed its reference to the JS global. Let's just // be safe here, so that nobody has to waste a day debugging gaia-ui tests. - if (!incumbent->HasJSGlobal()) { + if (!aIncumbentGlobal->HasJSGlobal()) { aRv.ThrowNotSupportedError( "Refusing to execute function because our incumbent global is being " "torn down."); return; } - mAutoIncumbentScript.emplace(incumbent); + mAutoIncumbentScript.emplace(aIncumbentGlobal); } JSContext* cx = mAutoEntryScript->cx(); @@ -296,10 +277,10 @@ CallSetup::CallSetup(CallbackObjectBase* aCallback, ErrorResult& aRv, // until here it do the unmark. This allows us to construct mRootedCallable // with the cx from mAutoEntryScript, avoiding the cost of finding another // JSContext. (Rooted<> does not care about requests or compartments.) - mRootedCallable.emplace(cx, aCallback->CallbackOrNull()); - JSObject* asyncStack = aCallback->GetCreationStack(); - if (asyncStack) { - mAsyncStackSetter.emplace(cx, asyncStack, aExecutionReason); + mRootedCallable.emplace(cx, aCallbackOrGlobal); + + if (aCreationStack) { + mAsyncStackSetter.emplace(cx, aCreationStack, aExecutionReason); } // Enter the realm of our callback, so we can actually work with it. @@ -307,7 +288,7 @@ CallSetup::CallSetup(CallbackObjectBase* aCallback, ErrorResult& aRv, // Note that if the callback is a wrapper, this will not be the same // realm that we ended up in with mAutoEntryScript above, because the // entry point is based off of the unwrapped callback (realCallback). - mAr.emplace(cx, aCallback->CallbackGlobalOrNull()); + mAr.emplace(cx, aCallbackGlobal); // And now we're ready to go. mCx = cx; @@ -317,6 +298,76 @@ CallSetup::CallSetup(CallbackObjectBase* aCallback, ErrorResult& aRv, mCallContext.emplace(cx, nullptr); } +CallSetup::CallSetup(CallbackObjectBase* aCallback, ErrorResult& aRv, + const char* aExecutionReason, + CallbackObjectBase::ExceptionHandling aExceptionHandling, + JS::Realm* aRealm, bool aIsJSImplementedWebIDL) + : mCx(nullptr), + mRealm(aRealm), + mErrorResult(aRv), + mExceptionHandling(aExceptionHandling), + mIsMainThread(NS_IsMainThread()) { + MOZ_ASSERT_IF( + aExceptionHandling == CallbackObjectBase::eReportExceptions || + aExceptionHandling == CallbackObjectBase::eRethrowExceptions, + !aRealm); + + CycleCollectedJSContext* ccjs = CycleCollectedJSContext::Get(); + MOZ_ASSERT(ccjs); + ccjs->EnterMicroTask(); + JS::RootedTuple<JSObject*, JSObject*, JSObject*> roots(ccjs->RootingCx()); + + // Compute the caller's subject principal (if necessary) early, before we + // do anything that might perturb the relevant state. + nsIPrincipal* webIDLCallerPrincipal = nullptr; + if (aIsJSImplementedWebIDL) { + webIDLCallerPrincipal = + nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller(); + } + + JSObject* wrappedCallback = aCallback->CallbackPreserveColor(); + if (!wrappedCallback) { + aRv.ThrowNotSupportedError( + "Cannot execute callback from a nuked compartment."); + return; + } + + nsIGlobalObject* globalObject = nullptr; + + { + // First, find the real underlying callback. + JS::RootedField<JSObject*, 0> realCallback( + roots, js::UncheckedUnwrap(wrappedCallback)); + + globalObject = GetActiveGlobalObjectForCall(realCallback, mIsMainThread, + aIsJSImplementedWebIDL, aRv); + if (!globalObject) { + MOZ_ASSERT(aRv.Failed()); + return; + } + + // Make sure to use realCallback to get the global of the callback + // object, not the wrapper. + if (!CheckBeforeExecution(globalObject, realCallback, + aIsJSImplementedWebIDL, aRv)) { + return; + } + } + + nsIGlobalObject* incumbent = aCallback->IncumbentGlobalOrNull(); + + // Start the execution setup -- if it succeeds, this will set mCx + JS::RootedField<JSObject*, 0> rootedCallback(roots, + aCallback->CallbackOrNull()); + JS::RootedField<JSObject*, 1> rootedCallbackGlobal( + roots, aCallback->CallbackGlobalOrNull()); + JS::RootedField<JSObject*, 2> rootedCreationStack( + roots, aCallback->GetCreationStack()); + SetupForExecution(globalObject, incumbent, rootedCallback, + rootedCallbackGlobal, rootedCreationStack, + webIDLCallerPrincipal, aExecutionReason, aRv); +} + bool CallSetup::ShouldRethrowException(JS::Handle<JS::Value> aException) { if (mExceptionHandling == CallbackObjectBase::eRethrowExceptions) { MOZ_ASSERT(!mRealm); diff --git a/dom/bindings/CallbackObject.h b/dom/bindings/CallbackObject.h @@ -238,13 +238,13 @@ class CallbackObjectBase { JS::TenuredHeap<JSObject*> mIncumbentJSGlobal; }; +/** + * A class that performs whatever setup we need to safely make a + * call while this class is on the stack, After the constructor + * returns, the call is safe to make if GetContext() returns + * non-null. + */ class MOZ_STACK_CLASS CallSetup { - /** - * A class that performs whatever setup we need to safely make a - * call while this class is on the stack, After the constructor - * returns, the call is safe to make if GetContext() returns - * non-null. - */ public: // If aExceptionHandling == eRethrowContentExceptions then aRealm // needs to be set to the realm in which exceptions will be rethrown. @@ -271,6 +271,25 @@ class MOZ_STACK_CLASS CallSetup { bool ShouldRethrowException(JS::Handle<JS::Value> aException); + static nsIGlobalObject* GetActiveGlobalObjectForCall( + JS::Handle<JSObject*> callbackOrGlobal, bool aIsMainThread, + bool aIsJSImplementedWebIDL, ErrorResult& aRv); + + static bool CheckBeforeExecution(nsIGlobalObject* aGlobalObject, + JSObject* aCallbackOrGlobal, + bool aIsJSImplementedWebIDL, + ErrorResult& aRv); + + // Perform the final setup work. If this succeeds, mCx is set and we are able + // to run the callback with the appropriate environment. + void SetupForExecution(nsIGlobalObject* aGlobalObject, + nsIGlobalObject* aIncumbentGlobal, + JS::Handle<JSObject*> aCallbackOrGlobal, + JS::Handle<JSObject*> aCallbackGlobal, + JS::Handle<JSObject*> aCreationStack, + nsIPrincipal* aWebIDLCallerPrincipal, + const char* aExecutionReason, ErrorResult& aRv); + // Members which can go away whenever JSContext* mCx;