Bug 1363301: Always provide live wrappers for ScriptSourceObjects. r?shu,bholley
MozReview-Commit-ID: LTNN66FywU4
--- a/js/src/jsfriendapi.cpp
+++ b/js/src/jsfriendapi.cpp
@@ -570,16 +570,22 @@ JS_IsDeadWrapper(JSObject* obj)
}
JS_FRIEND_API(JSObject*)
JS_NewDeadWrapper(JSContext* cx, JSObject* origObj)
{
return NewDeadProxyObject(cx, origObj);
}
+JS_FRIEND_API(bool)
+JS_IsScriptSourceObject(JSObject* obj)
+{
+ return obj->is<ScriptSourceObject>();
+}
+
void
js::TraceWeakMaps(WeakMapTracer* trc)
{
WeakMapBase::traceAllMappings(trc);
WatchpointMap::traceAll(trc);
}
extern JS_FRIEND_API(bool)
--- a/js/src/jsfriendapi.h
+++ b/js/src/jsfriendapi.h
@@ -101,16 +101,22 @@ JS_IsDeadWrapper(JSObject* obj);
* attempting to wrap objects from scopes which are already dead.
*
* If origObject is passed, it must be an proxy object, and will be
* used to determine the characteristics of the new dead wrapper.
*/
extern JS_FRIEND_API(JSObject*)
JS_NewDeadWrapper(JSContext* cx, JSObject* origObject = nullptr);
+/**
+ * Determine whether the given object is a ScriptSourceObject.
+ */
+extern JS_FRIEND_API(bool)
+JS_IsScriptSourceObject(JSObject* obj);
+
/*
* Used by the cycle collector to trace through a shape or object group and
* all cycle-participating data it reaches, using bounded stack space.
*/
extern JS_FRIEND_API(void)
JS_TraceShapeCycleCollectorChildren(JS::CallbackTracer* trc, JS::GCCellPtr shape);
extern JS_FRIEND_API(void)
JS_TraceObjectGroupCycleCollectorChildren(JS::CallbackTracer* trc, JS::GCCellPtr group);
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/tests/unit/test_subScriptLoader.js
@@ -0,0 +1,20 @@
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+add_task(function* test_executeScriptAfterNuked() {
+ let scriptUrl = Services.io.newFileURI(do_get_file("file_simple_script.js")).spec;
+
+ // Load the script for the first time into a sandbox, and then nuke
+ // that sandbox.
+ let sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal());
+ Services.scriptloader.loadSubScript(scriptUrl, sandbox);
+ Cu.nukeSandbox(sandbox);
+
+ // Load the script again into a new sandbox, and make sure it
+ // succeeds.
+ sandbox = Cu.Sandbox(Services.scriptSecurityManager.getSystemPrincipal());
+ Services.scriptloader.loadSubScript(scriptUrl, sandbox);
+});
--- a/js/xpconnect/tests/unit/xpcshell.ini
+++ b/js/xpconnect/tests/unit/xpcshell.ini
@@ -94,16 +94,17 @@ skip-if = toolkit == "android" # bug 134
[test_tearoffs.js]
[test_want_components.js]
[test_components.js]
[test_allowedDomains.js]
[test_allowedDomainsXHR.js]
[test_nuke_sandbox.js]
[test_nuke_sandbox_event_listeners.js]
[test_nuke_webextension_wrappers.js]
+[test_subScriptLoader.js]
[test_rewrap_dead_wrapper.js]
[test_sandbox_metadata.js]
[test_exportFunction.js]
[test_promise.js]
[test_returncode.js]
[test_textDecoder.js]
[test_url.js]
[test_URLSearchParams.js]
--- a/js/xpconnect/wrappers/WrapperFactory.cpp
+++ b/js/xpconnect/wrappers/WrapperFactory.cpp
@@ -186,20 +186,24 @@ WrapperFactory::PrepareForWrapping(JSCon
if (JS_IsDeadWrapper(obj)) {
retObj.set(JS_NewDeadWrapper(cx, obj));
return;
}
// If we've somehow gotten to this point after either the source or target
// compartment has been nuked, return a DeadObjectProxy to prevent further
// access.
+ // However, we always need to provide live wrappers for ScriptSourceObjects,
+ // since they're used for cross-compartment cloned scripts which assume the
+ // type of the unwrapped target.
JSCompartment* origin = js::GetObjectCompartment(obj);
JSCompartment* target = js::GetObjectCompartment(scope);
- if (CompartmentPrivate::Get(origin)->wasNuked ||
- CompartmentPrivate::Get(target)->wasNuked) {
+ if (!JS_IsScriptSourceObject(obj) &&
+ (CompartmentPrivate::Get(origin)->wasNuked ||
+ CompartmentPrivate::Get(target)->wasNuked)) {
NS_WARNING("Trying to create a wrapper into or out of a nuked compartment");
retObj.set(JS_NewDeadWrapper(cx));
return;
}
// If we've got a WindowProxy, there's nothing special that needs to be
@@ -347,17 +351,18 @@ WrapperFactory::PrepareForWrapping(JSCon
retObj.set(waive ? WaiveXray(cx, obj) : obj);
}
#ifdef DEBUG
static void
DEBUG_CheckUnwrapSafety(HandleObject obj, const js::Wrapper* handler,
JSCompartment* origin, JSCompartment* target)
{
- if (CompartmentPrivate::Get(origin)->wasNuked || CompartmentPrivate::Get(target)->wasNuked) {
+ if (!JS_IsScriptSourceObject(obj) &&
+ (CompartmentPrivate::Get(origin)->wasNuked || CompartmentPrivate::Get(target)->wasNuked)) {
// If either compartment has already been nuked, we should have returned
// a dead wrapper from our prewrap callback, and this function should
// not be called.
MOZ_ASSERT_UNREACHABLE("CheckUnwrapSafety called for a dead wrapper");
} else if (AccessCheck::isChrome(target) || xpc::IsUniversalXPConnectEnabled(target)) {
// If the caller is chrome (or effectively so), unwrap should always be allowed.
MOZ_ASSERT(!handler->hasSecurityPolicy());
} else if (CompartmentPrivate::Get(origin)->forcePermissiveCOWs) {