diff --git a/caps/nsScriptSecurityManager.cpp b/caps/nsScriptSecurityManager.cpp index 0d5e4b0fd32..228d501282c 100644 --- a/caps/nsScriptSecurityManager.cpp +++ b/caps/nsScriptSecurityManager.cpp @@ -988,7 +988,7 @@ bool nsScriptSecurityManager::ScriptAllowed(JSObject *aGlobal) { MOZ_ASSERT(aGlobal); - MOZ_ASSERT(JS_IsGlobalObject(aGlobal) || js::IsOuterObject(aGlobal)); + MOZ_ASSERT(JS_IsGlobalObject(aGlobal) || js::IsWindowProxy(aGlobal)); // Check the bits on the compartment private. return xpc::Scriptability::Get(aGlobal).Allowed(); diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp index a7abf3f13ea..d86d609e56b 100644 --- a/dom/base/nsGlobalWindow.cpp +++ b/dom/base/nsGlobalWindow.cpp @@ -1102,6 +1102,7 @@ NewOuterWindowProxy(JSContext *cx, JS::Handle global, bool isChrome) isChrome ? &nsChromeOuterWindowProxy::singleton : &nsOuterWindowProxy::singleton, options); + MOZ_ASSERT_IF(obj, js::IsWindowProxy(obj)); NS_ASSERTION(js::GetObjectClass(obj)->ext.innerObject, "bad class"); return obj; @@ -2659,6 +2660,11 @@ nsGlobalWindow::SetNewDocument(nsIDocument* aDocument, // Enter the new global's compartment. JSAutoCompartment ac(cx, GetWrapperPreserveColor()); + { + JS::Rooted outer(cx, GetWrapperPreserveColor()); + js::SetWindowProxy(cx, newInnerGlobal, outer); + } + // Set scriptability based on the state of the docshell. bool allow = GetDocShell()->GetCanExecuteScripts(); xpc::Scriptability::Get(GetWrapperPreserveColor()).SetDocShellAllowsScript(allow); diff --git a/dom/bindings/BindingUtils.cpp b/dom/bindings/BindingUtils.cpp index f4a23d97ebc..b96e3a1258c 100644 --- a/dom/bindings/BindingUtils.cpp +++ b/dom/bindings/BindingUtils.cpp @@ -3124,7 +3124,7 @@ DeprecationWarning(JSContext* aCx, JSObject* aObject, bool ObjectToOuterObjectValue(JSContext* cx, JS::Handle obj, JS::MutableHandle vp) { - JSObject* outer = JS_ObjectToOuterObject(cx, obj); + JSObject* outer = js::ToWindowProxyIfWindow(obj); vp.setObject(*outer); return true; } diff --git a/dom/bindings/BindingUtils.h b/dom/bindings/BindingUtils.h index 927f02bd68d..e844d4de772 100644 --- a/dom/bindings/BindingUtils.h +++ b/dom/bindings/BindingUtils.h @@ -776,13 +776,9 @@ CouldBeDOMBinding(nsWrapperCache* aCache) inline bool TryToOuterize(JSContext* cx, JS::MutableHandle rval) { - if (js::IsInnerObject(&rval.toObject())) { - JS::Rooted obj(cx, &rval.toObject()); - obj = JS_ObjectToOuterObject(cx, obj); - if (!obj) { - return false; - } - + if (js::IsWindow(&rval.toObject())) { + JSObject* obj = js::ToWindowProxyIfWindow(&rval.toObject()); + MOZ_ASSERT(obj); rval.set(JS::ObjectValue(*obj)); } diff --git a/dom/plugins/base/nsNPAPIPlugin.cpp b/dom/plugins/base/nsNPAPIPlugin.cpp index 7caab1bc04e..067f1245539 100644 --- a/dom/plugins/base/nsNPAPIPlugin.cpp +++ b/dom/plugins/base/nsNPAPIPlugin.cpp @@ -1350,10 +1350,8 @@ _evaluate(NPP npp, NPObject* npobj, NPString *script, NPVariant *result) return false; } - obj = JS_ObjectToInnerObject(cx, obj); - MOZ_ASSERT(obj, - "JS_ObjectToInnerObject should never return null with non-null " - "input."); + obj = js::ToWindowIfWindowProxy(obj); + MOZ_ASSERT(obj, "ToWindowIfWindowProxy should never return null"); if (result) { // Initialize the out param to void diff --git a/js/ipc/JavaScriptShared.cpp b/js/ipc/JavaScriptShared.cpp index 729f48f155b..2ba8a4f5d9c 100644 --- a/js/ipc/JavaScriptShared.cpp +++ b/js/ipc/JavaScriptShared.cpp @@ -537,9 +537,8 @@ JavaScriptShared::findObjectById(JSContext* cx, const ObjectId& objId) if (objId.hasXrayWaiver()) { { JSAutoCompartment ac2(cx, obj); - obj = JS_ObjectToOuterObject(cx, obj); - if (!obj) - return nullptr; + obj = js::ToWindowProxyIfWindow(obj); + MOZ_ASSERT(obj); } if (!xpc::WrapperFactory::WaiveXrayAndWrap(cx, &obj)) return nullptr; diff --git a/js/public/Class.h b/js/public/Class.h index e23b8c3d462..9a9d562e331 100644 --- a/js/public/Class.h +++ b/js/public/Class.h @@ -737,7 +737,7 @@ struct JSClass { // application. #define JSCLASS_GLOBAL_APPLICATION_SLOTS 5 #define JSCLASS_GLOBAL_SLOT_COUNT \ - (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 3 + 36) + (JSCLASS_GLOBAL_APPLICATION_SLOTS + JSProto_LIMIT * 3 + 37) #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n) \ (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n))) #define JSCLASS_GLOBAL_FLAGS \ diff --git a/js/src/builtin/Eval.cpp b/js/src/builtin/Eval.cpp index f40a9b0fdda..0fa77245640 100644 --- a/js/src/builtin/Eval.cpp +++ b/js/src/builtin/Eval.cpp @@ -33,7 +33,7 @@ AssertInnerizedScopeChain(JSContext* cx, JSObject& scopeobj) #ifdef DEBUG RootedObject obj(cx); for (obj = &scopeobj; obj; obj = obj->enclosingScope()) - MOZ_ASSERT(GetInnerObject(obj) == obj); + MOZ_ASSERT(!IsWindowProxy(obj)); #endif } diff --git a/js/src/jit/BaselineIC.cpp b/js/src/jit/BaselineIC.cpp index 79f7e2853da..370fd9dc506 100644 --- a/js/src/jit/BaselineIC.cpp +++ b/js/src/jit/BaselineIC.cpp @@ -5551,13 +5551,15 @@ TryAttachNativeGetAccessorPropStub(JSContext* cx, HandleScript script, jsbytecod const Class* outerClass = nullptr; if (!isDOMProxy && !obj->isNative()) { outerClass = obj->getClass(); - DebugOnly outer = obj.get(); - obj = GetInnerObject(obj); - MOZ_ASSERT(script->global().isNative()); - if (obj != &script->global()) + if (!IsWindowProxy(obj)) return true; - // ICGetProp_CallNative*::Compiler::generateStubCode depends on this. - MOZ_ASSERT(&((GetProxyDataLayout(outer)->values->privateSlot).toObject()) == obj); + + // This must be a WindowProxy for the current Window/global. Else it'd + // be a cross-compartment wrapper and IsWindowProxy returns false for + // those. + MOZ_ASSERT(ToWindowIfWindowProxy(obj) == cx->global()); + obj = cx->global(); + if (!EffectlesslyLookupProperty(cx, obj, id, &holder, &shape, &isDOMProxy, &domProxyShadowsResult, &domProxyHasGeneration)) { @@ -6471,13 +6473,9 @@ ICGetPropCallNativeCompiler::generateStubCode(MacroAssembler& masm) masm.branchTestObject(Assembler::NotEqual, R0, &failure); objReg = masm.extractObject(R0, ExtractTemp0); if (outerClass_) { - ValueOperand val = regs.takeAnyValue(); Register tmp = regs.takeAny(); masm.branchTestObjClass(Assembler::NotEqual, objReg, tmp, outerClass_, &failure); - masm.loadPtr(Address(objReg, ProxyDataOffset + offsetof(ProxyDataLayout, values)), tmp); - masm.loadValue(Address(tmp, offsetof(ProxyValueArray, privateSlot)), val); - masm.movePtr(masm.extractObject(val, ExtractTemp0), objReg); - regs.add(val); + masm.movePtr(ImmGCPtr(cx->global()), objReg); regs.add(tmp); } } diff --git a/js/src/jit/IonBuilder.cpp b/js/src/jit/IonBuilder.cpp index 15a07daad25..925fad292d8 100644 --- a/js/src/jit/IonBuilder.cpp +++ b/js/src/jit/IonBuilder.cpp @@ -11847,11 +11847,14 @@ IonBuilder::tryInnerizeWindow(MDefinition* obj) if (!singleton) return obj; - JSObject* inner = GetInnerObject(singleton); - if (inner == singleton || inner != &script()->global()) + if (!IsWindowProxy(singleton)) return obj; - // When we navigate, the outer object is brain transplanted and we'll mark + // This must be a WindowProxy for the current Window/global. Else it'd be + // a cross-compartment wrapper and IsWindowProxy returns false for those. + MOZ_ASSERT(ToWindowIfWindowProxy(singleton) == &script()->global()); + + // When we navigate, the WindowProxy is brain transplanted and we'll mark // its ObjectGroup as having unknown properties. The type constraint we add // here will invalidate JIT code when this happens. TypeSet::ObjectKey* key = TypeSet::ObjectKey::get(singleton); diff --git a/js/src/jsapi-tests/testBug604087.cpp b/js/src/jsapi-tests/testBug604087.cpp index c9c41509a64..ffbdf2a24a2 100644 --- a/js/src/jsapi-tests/testBug604087.cpp +++ b/js/src/jsapi-tests/testBug604087.cpp @@ -56,6 +56,8 @@ static const JSWrapObjectCallbacks WrapObjectCallbacks = { BEGIN_TEST(testBug604087) { + js::SetWindowProxyClass(cx->runtime(), &OuterWrapperClass); + js::WrapperOptions options; options.setClass(&OuterWrapperClass); options.setSingleton(true); diff --git a/js/src/jscompartment.cpp b/js/src/jscompartment.cpp index 63867500bb1..f1899bfb0af 100644 --- a/js/src/jscompartment.cpp +++ b/js/src/jscompartment.cpp @@ -403,7 +403,7 @@ JSCompartment::wrap(JSContext* cx, MutableHandleObject obj, HandleObject existin const JSWrapObjectCallbacks* cb = cx->runtime()->wrapObjectCallbacks; if (obj->compartment() == this) { - obj.set(GetOuterObject(cx, obj)); + obj.set(ToWindowProxyIfWindow(obj)); return true; } @@ -419,7 +419,7 @@ JSCompartment::wrap(JSContext* cx, MutableHandleObject obj, HandleObject existin obj.set(UncheckedUnwrap(obj, /* stopAtOuter = */ true)); if (obj->compartment() == this) { - MOZ_ASSERT(obj == GetOuterObject(cx, obj)); + MOZ_ASSERT(!IsWindow(obj)); return true; } @@ -442,7 +442,7 @@ JSCompartment::wrap(JSContext* cx, MutableHandleObject obj, HandleObject existin if (!obj) return false; } - MOZ_ASSERT(obj == GetOuterObject(cx, obj)); + MOZ_ASSERT(!IsWindow(obj)); if (obj->compartment() == this) return true; diff --git a/js/src/jsfriendapi.cpp b/js/src/jsfriendapi.cpp index 0ee94f548fc..f918839ae18 100644 --- a/js/src/jsfriendapi.cpp +++ b/js/src/jsfriendapi.cpp @@ -82,10 +82,10 @@ JS_FindCompilationScope(JSContext* cx, HandleObject objArg) obj = UncheckedUnwrap(obj); /* - * Innerize the target_obj so that we compile in the correct (inner) - * scope. + * Get the Window if `obj` is a WindowProxy so that we compile in the + * correct (global) scope. */ - return GetInnerObject(obj); + return ToWindowIfWindowProxy(obj); } JS_FRIEND_API(JSFunction*) @@ -1282,3 +1282,53 @@ js::GetPropertyNameFromPC(JSScript* script, jsbytecode* pc) return nullptr; return script->getName(pc); } + +JS_FRIEND_API(void) +js::SetWindowProxyClass(JSRuntime* rt, const js::Class* clasp) +{ + MOZ_ASSERT(!rt->maybeWindowProxyClass()); + rt->setWindowProxyClass(clasp); +} + +JS_FRIEND_API(void) +js::SetWindowProxy(JSContext* cx, HandleObject global, HandleObject windowProxy) +{ + AssertHeapIsIdle(cx); + CHECK_REQUEST(cx); + + assertSameCompartment(cx, global, windowProxy); + + MOZ_ASSERT(IsWindowProxy(windowProxy)); + global->as().setWindowProxy(windowProxy); +} + +JS_FRIEND_API(JSObject*) +js::ToWindowIfWindowProxy(JSObject* obj) +{ + if (IsWindowProxy(obj)) + return &obj->global(); + return obj; +} + +JS_FRIEND_API(JSObject*) +js::ToWindowProxyIfWindow(JSObject* obj) +{ + if (IsWindow(obj)) + return obj->as().windowProxy(); + return obj; +} + +JS_FRIEND_API(bool) +js::IsWindowProxy(JSObject* obj) +{ + // Note: simply checking `obj == obj->global().windowProxy()` is not + // sufficient: we may have transplanted the window proxy with a CCW. + // Check the Class to ensure we really have a window proxy. + return obj->getClass() == obj->runtimeFromAnyThread()->maybeWindowProxyClass(); +} + +JS_FRIEND_API(bool) +js::detail::IsWindowSlow(JSObject* obj) +{ + return obj->as().maybeWindowProxy(); +} diff --git a/js/src/jsfriendapi.h b/js/src/jsfriendapi.h index e0304c780a1..81fb1338f47 100644 --- a/js/src/jsfriendapi.h +++ b/js/src/jsfriendapi.h @@ -148,14 +148,6 @@ JS_GetScriptPrincipals(JSScript* script); extern JS_FRIEND_API(bool) JS_ScriptHasMutedErrors(JSScript* script); -/* Safe to call with input obj == nullptr. Returns non-nullptr iff obj != nullptr. */ -extern JS_FRIEND_API(JSObject*) -JS_ObjectToInnerObject(JSContext* cx, JS::HandleObject obj); - -/* Requires obj != nullptr. */ -extern JS_FRIEND_API(JSObject*) -JS_ObjectToOuterObject(JSContext* cx, JS::HandleObject obj); - extern JS_FRIEND_API(JSObject*) JS_CloneObject(JSContext* cx, JS::HandleObject obj, JS::HandleObject proto); @@ -676,16 +668,6 @@ ParentKeyForStandardClass(JSProtoKey key) return JSProto_Object; } -inline bool -IsInnerObject(JSObject* obj) { - return !!GetObjectClass(obj)->ext.outerObject; -} - -inline bool -IsOuterObject(JSObject* obj) { - return !!GetObjectClass(obj)->ext.innerObject; -} - JS_FRIEND_API(bool) IsFunctionObject(JSObject* obj); @@ -2859,6 +2841,77 @@ ReportIsNotFunction(JSContext* cx, JS::HandleValue v); extern JS_FRIEND_API(JSObject*) ConvertArgsToArray(JSContext* cx, const JS::CallArgs& args); +/** + * Window and WindowProxy + * + * The functions below have to do with Windows and WindowProxies. There's an + * invariant that actual Window objects (the global objects of web pages) are + * never directly exposed to script. Instead we often substitute a WindowProxy. + * + * The scope chain, on the other hand, contains the Window and never its + * WindowProxy. + * + * As a result, we have calls to these "substitute-this-object-for-that-object" + * functions sprinkled at apparently arbitrary (but actually *very* carefully + * and nervously selected) places throughout the engine and indeed the + * universe. + */ + +/** + * Tell the JS engine which Class is used for WindowProxy objects. Used by the + * functions below. + */ +extern JS_FRIEND_API(void) +SetWindowProxyClass(JSRuntime* rt, const Class* clasp); + +/** + * Associates a WindowProxy with a Window (global object). `windowProxy` must + * have the Class set by SetWindowProxyClass. + */ +extern JS_FRIEND_API(void) +SetWindowProxy(JSContext* cx, JS::HandleObject global, JS::HandleObject windowProxy); + +namespace detail { + +JS_FRIEND_API(bool) +IsWindowSlow(JSObject* obj); + +} // namespace detail + +/** + * Returns true iff `obj` is a global object with an associated WindowProxy, + * see SetWindowProxy. + */ +inline bool +IsWindow(JSObject* obj) +{ + if (GetObjectClass(obj)->flags & JSCLASS_IS_GLOBAL) + return detail::IsWindowSlow(obj); + return false; +} + +/** + * Returns true iff `obj` has the WindowProxy Class (see SetWindowProxyClass). + */ +JS_FRIEND_API(bool) +IsWindowProxy(JSObject* obj); + +/** + * If `obj` is a Window, get its associated WindowProxy (or a CCW if the + * page was navigated away from), else return `obj`. This function is + * infallible and never returns nullptr. + */ +extern JS_FRIEND_API(JSObject*) +ToWindowProxyIfWindow(JSObject* obj); + +/** + * If `obj` is a WindowProxy, get its associated Window (the compartment's + * global), else return `obj`. This function is infallible and never returns + * nullptr. + */ +extern JS_FRIEND_API(JSObject*) +ToWindowIfWindowProxy(JSObject* obj); + } /* namespace js */ extern JS_FRIEND_API(void) diff --git a/js/src/jsobj.cpp b/js/src/jsobj.cpp index c231fb282dd..6e4f1afea77 100644 --- a/js/src/jsobj.cpp +++ b/js/src/jsobj.cpp @@ -76,23 +76,6 @@ using mozilla::DebugOnly; using mozilla::Maybe; using mozilla::UniquePtr; -JS_FRIEND_API(JSObject*) -JS_ObjectToInnerObject(JSContext* cx, HandleObject obj) -{ - if (!obj) { - JS_ReportErrorNumber(cx, GetErrorMessage, nullptr, JSMSG_INACTIVE); - return nullptr; - } - return GetInnerObject(obj); -} - -JS_FRIEND_API(JSObject*) -JS_ObjectToOuterObject(JSContext* cx, HandleObject obj) -{ - assertSameCompartment(cx, obj); - return GetOuterObject(cx, obj); -} - void js::ReportNotObject(JSContext* cx, const Value& v) { @@ -2484,14 +2467,14 @@ js::SetPrototype(JSContext* cx, HandleObject obj, HandleObject proto, JS::Object /* * ES6 9.1.2 step 6 forbids generating cyclical prototype chains. But we - * have to do this comparison on the observable outer objects, not on the - * possibly-inner object we're setting the proto on. + * have to do this comparison on the observable WindowProxy, not on the + * possibly-Window object we're setting the proto on. */ - RootedObject outerObj(cx, GetOuterObject(cx, obj)); + RootedObject objMaybeWindowProxy(cx, ToWindowProxyIfWindow(obj)); RootedObject obj2(cx); for (obj2 = proto; obj2; ) { - MOZ_ASSERT(GetOuterObject(cx, obj2) == obj2); - if (obj2 == outerObj) + MOZ_ASSERT(!IsWindow(obj2)); + if (obj2 == objMaybeWindowProxy) return result.fail(JSMSG_CANT_SET_PROTO_CYCLE); if (!GetPrototype(cx, obj2, &obj2)) @@ -2767,7 +2750,7 @@ js::GetPropertyDescriptor(JSContext* cx, HandleObject obj, HandleId id, bool js::WatchGuts(JSContext* cx, JS::HandleObject origObj, JS::HandleId id, JS::HandleObject callable) { - RootedObject obj(cx, GetInnerObject(origObj)); + RootedObject obj(cx, ToWindowIfWindowProxy(origObj)); if (obj->isNative()) { // Use sparse indexes for watched objects, as dense elements can be // written to without checking the watchpoint map. @@ -2796,7 +2779,7 @@ js::UnwatchGuts(JSContext* cx, JS::HandleObject origObj, JS::HandleId id) { // Looking in the map for an unsupported object will never hit, so we don't // need to check for nativeness or watchable-ness here. - RootedObject obj(cx, GetInnerObject(origObj)); + RootedObject obj(cx, ToWindowIfWindowProxy(origObj)); if (WatchpointMap* wpmap = cx->compartment()->watchpointMap) wpmap->unwatch(obj, id, nullptr, nullptr); return true; diff --git a/js/src/jsobj.h b/js/src/jsobj.h index 5256b3e5db8..dc33b8d02bb 100644 --- a/js/src/jsobj.h +++ b/js/src/jsobj.h @@ -1035,59 +1035,6 @@ ToPrimitive(JSContext* cx, JSType preferredType, MutableHandleValue vp) extern const char* GetObjectClassName(JSContext* cx, HandleObject obj); -/* - * Inner and outer objects - * - * GetInnerObject and GetOuterObject (and also GetThisValue, somewhat) have to - * do with Windows and WindowProxies. There's a screwy invariant that actual - * Window objects (the global objects of web pages) are never directly exposed - * to script. Instead we often substitute a WindowProxy. - * - * As a result, we have calls to these three "substitute-this-object-for-that- - * object" functions sprinkled at apparently arbitrary (but actually *very* - * carefully and nervously selected) places throughout the engine and indeed - * the universe. - */ - -/* - * If obj is a WindowProxy, return its current inner Window. Otherwise return - * obj. This function can't fail and never returns nullptr. - * - * GetInnerObject is called when we need a scope chain; you never want a - * WindowProxy on a scope chain. - * - * It's also called in a few places where an object comes in from script, and - * the user probably intends to operate on the Window, not the - * WindowProxy. Object.prototype.watch and various Debugger features do - * this. (Users can't simply pass the Window, because the Window isn't exposed - * to scripts.) - */ -inline JSObject* -GetInnerObject(JSObject* obj) -{ - if (InnerObjectOp op = obj->getClass()->ext.innerObject) { - JS::AutoSuppressGCAnalysis nogc; - return op(obj); - } - return obj; -} - -/* - * If obj is a Window object, return the WindowProxy. Otherwise return obj. - * This function can't fail; it never sets an exception or returns nullptr. - * - * This must be called before passing an object to script, if the object might - * be a Window. (But usually those cases involve scope objects, and for those, - * it is better to call GetThisValue instead.) - */ -inline JSObject* -GetOuterObject(JSContext* cx, HandleObject obj) -{ - if (ObjectOp op = obj->getClass()->ext.outerObject) - return op(cx, obj); - return obj; -} - /* * Return an object that may be used as `this` in place of obj. For most * objects this just returns obj. diff --git a/js/src/proxy/Proxy.cpp b/js/src/proxy/Proxy.cpp index a0126a1ee62..fda0442cb22 100644 --- a/js/src/proxy/Proxy.cpp +++ b/js/src/proxy/Proxy.cpp @@ -261,12 +261,10 @@ Proxy::hasOwn(JSContext* cx, HandleObject proxy, HandleId id, bool* bp) } static Value -OuterizeValue(JSContext* cx, HandleValue v) +ValueToWindowProxyIfWindow(Value v) { - if (v.isObject()) { - RootedObject obj(cx, &v.toObject()); - return ObjectValue(*GetOuterObject(cx, obj)); - } + if (v.isObject()) + return ObjectValue(*ToWindowProxyIfWindow(&v.toObject())); return v; } @@ -281,9 +279,9 @@ Proxy::get(JSContext* cx, HandleObject proxy, HandleValue receiver_, HandleId id if (!policy.allowed()) return policy.returnValue(); - // Outerize the receiver. Proxy handlers shouldn't have to know about - // the Window/WindowProxy distinction. - RootedValue receiver(cx, OuterizeValue(cx, receiver_)); + // Use the WindowProxy as receiver if receiver_ is a Window. Proxy handlers + // shouldn't have to know about the Window/WindowProxy distinction. + RootedValue receiver(cx, ValueToWindowProxyIfWindow(receiver_)); if (handler->hasPrototype()) { bool own; @@ -315,9 +313,9 @@ Proxy::set(JSContext* cx, HandleObject proxy, HandleId id, HandleValue v, Handle return result.succeed(); } - // Outerize the receiver. Proxy handlers shouldn't have to know about - // the Window/WindowProxy distinction. - RootedValue receiver(cx, OuterizeValue(cx, receiver_)); + // Use the WindowProxy as receiver if receiver_ is a Window. Proxy handlers + // shouldn't have to know about the Window/WindowProxy distinction. + RootedValue receiver(cx, ValueToWindowProxyIfWindow(receiver_)); // Special case. See the comment on BaseProxyHandler::mHasPrototype. if (handler->hasPrototype()) diff --git a/js/src/shell/js.cpp b/js/src/shell/js.cpp index f7841a8159f..11823b285d3 100644 --- a/js/src/shell/js.cpp +++ b/js/src/shell/js.cpp @@ -2553,9 +2553,8 @@ EvalInContext(JSContext* cx, unsigned argc, Value* vp) ac.emplace(cx, sobj); } - sobj = GetInnerObject(sobj); - if (!sobj) - return false; + sobj = ToWindowIfWindowProxy(sobj); + if (!(sobj->getClass()->flags & JSCLASS_IS_GLOBAL)) { JS_ReportError(cx, "Invalid scope argument to evalcx"); return false; diff --git a/js/src/vm/Debugger.cpp b/js/src/vm/Debugger.cpp index a9e16a93c33..12d07608289 100644 --- a/js/src/vm/Debugger.cpp +++ b/js/src/vm/Debugger.cpp @@ -3064,10 +3064,8 @@ Debugger::unwrapDebuggeeArgument(JSContext* cx, const Value& v) return nullptr; } - /* If that produced an outer window, innerize it. */ - obj = GetInnerObject(obj); - if (!obj) - return nullptr; + /* If that produced a WindowProxy, get the Window (global). */ + obj = ToWindowIfWindowProxy(obj); /* If that didn't produce a global object, it's an error. */ if (!obj->is()) { @@ -7696,8 +7694,8 @@ RequireGlobalObject(JSContext* cx, HandleValue dbgobj, HandleObject referent) } /* ... and WindowProxies around Windows. */ - if (IsOuterObject(obj)) { - obj = JS_ObjectToInnerObject(cx, obj); + if (IsWindowProxy(obj)) { + obj = ToWindowIfWindowProxy(obj); isWindowProxy = "a WindowProxy referring to "; } @@ -7799,8 +7797,8 @@ DebuggerObject_unsafeDereference(JSContext* cx, unsigned argc, Value* vp) if (!cx->compartment()->wrap(cx, args.rval())) return false; - // Wrapping should outerize inner objects. - MOZ_ASSERT(!IsInnerObject(&args.rval().toObject())); + // Wrapping should return the WindowProxy. + MOZ_ASSERT(!IsWindow(&args.rval().toObject())); return true; } diff --git a/js/src/vm/GlobalObject.h b/js/src/vm/GlobalObject.h index 242aa43d7bb..8f4cb76c465 100644 --- a/js/src/vm/GlobalObject.h +++ b/js/src/vm/GlobalObject.h @@ -117,6 +117,7 @@ class GlobalObject : public NativeObject INT32X4_TYPE_DESCR, FOR_OF_PIC_CHAIN, MODULE_RESOLVE_HOOK, + WINDOW_PROXY, /* Total reserved-slot count for global objects. */ RESERVED_SLOTS @@ -747,6 +748,18 @@ class GlobalObject : public NativeObject } static NativeObject* getOrCreateForOfPICObject(JSContext* cx, Handle global); + JSObject* windowProxy() const { + return &getReservedSlot(WINDOW_PROXY).toObject(); + } + JSObject* maybeWindowProxy() const { + Value v = getReservedSlot(WINDOW_PROXY); + MOZ_ASSERT(v.isObject() || v.isUndefined()); + return v.isObject() ? &v.toObject() : nullptr; + } + void setWindowProxy(JSObject* windowProxy) { + setReservedSlot(WINDOW_PROXY, ObjectValue(*windowProxy)); + } + void setModuleResolveHook(HandleFunction hook) { MOZ_ASSERT(hook); setSlot(MODULE_RESOLVE_HOOK, ObjectValue(*hook)); diff --git a/js/src/vm/Interpreter.cpp b/js/src/vm/Interpreter.cpp index 7b47cfebec0..8cfc50c917b 100644 --- a/js/src/vm/Interpreter.cpp +++ b/js/src/vm/Interpreter.cpp @@ -570,11 +570,7 @@ js::ExecuteKernel(JSContext* cx, HandleScript script, JSObject& scopeChainArg, c MOZ_ASSERT_IF(type == EXECUTE_GLOBAL, IsGlobalLexicalScope(&scopeChainArg) || !IsSyntacticScope(&scopeChainArg)); #ifdef DEBUG - if (thisv.isObject()) { - RootedObject thisObj(cx, &thisv.toObject()); - AutoSuppressGC nogc(cx); - MOZ_ASSERT(GetOuterObject(cx, thisObj) == thisObj); - } + MOZ_ASSERT_IF(thisv.isObject(), !IsWindow(&thisv.toObject())); RootedObject terminatingScope(cx, &scopeChainArg); while (IsSyntacticScope(terminatingScope)) terminatingScope = terminatingScope->enclosingScope(); @@ -613,7 +609,7 @@ js::Execute(JSContext* cx, HandleScript script, JSObject& scopeChainArg, Value* /* The scope chain is something we control, so we know it can't have any outer objects on it. */ RootedObject scopeChain(cx, &scopeChainArg); - MOZ_ASSERT(scopeChain == GetInnerObject(scopeChain)); + MOZ_ASSERT(!IsWindowProxy(scopeChain)); if (script->module()) { MOZ_RELEASE_ASSERT(scopeChain == script->module()->environment(), diff --git a/js/src/vm/Runtime.cpp b/js/src/vm/Runtime.cpp index 45629efd0e6..a1481e80c37 100644 --- a/js/src/vm/Runtime.cpp +++ b/js/src/vm/Runtime.cpp @@ -226,6 +226,7 @@ JSRuntime::JSRuntime(JSRuntime* parentRuntime) ionPcScriptCache(nullptr), scriptEnvironmentPreparer(nullptr), ctypesActivityCallback(nullptr), + windowProxyClass_(nullptr), offthreadIonCompilationEnabled_(true), parallelParsingEnabled_(true), autoWritableJitCodeActive_(false), diff --git a/js/src/vm/Runtime.h b/js/src/vm/Runtime.h index bce003128ba..971c68c6817 100644 --- a/js/src/vm/Runtime.h +++ b/js/src/vm/Runtime.h @@ -1399,6 +1399,7 @@ struct JSRuntime : public JS::shadow::Runtime, private: JS::RuntimeOptions options_; + const js::Class* windowProxyClass_; // Settings for how helper threads can be used. bool offthreadIonCompilationEnabled_; @@ -1436,6 +1437,13 @@ struct JSRuntime : public JS::shadow::Runtime, return options_; } + const js::Class* maybeWindowProxyClass() const { + return windowProxyClass_; + } + void setWindowProxyClass(const js::Class* clasp) { + windowProxyClass_ = clasp; + } + #ifdef DEBUG public: js::AutoEnterPolicy* enteredPolicy; diff --git a/js/xpconnect/src/XPCJSRuntime.cpp b/js/xpconnect/src/XPCJSRuntime.cpp index 8e21cd8c8cf..e51c5d01f8a 100644 --- a/js/xpconnect/src/XPCJSRuntime.cpp +++ b/js/xpconnect/src/XPCJSRuntime.cpp @@ -3461,6 +3461,7 @@ XPCJSRuntime::XPCJSRuntime(nsXPConnect* aXPConnect) js::SetScriptEnvironmentPreparer(runtime, &mEnvironmentPreparer); js::SetActivityCallback(runtime, ActivityCallback, this); JS_SetInterruptCallback(runtime, InterruptCallback); + js::SetWindowProxyClass(runtime, &OuterWindowProxyClass); // The JS engine needs to keep the source code around in order to implement // Function.prototype.toSource(). It'd be nice to not have to do this for diff --git a/js/xpconnect/src/XPCVariant.cpp b/js/xpconnect/src/XPCVariant.cpp index de121abb629..9abfc410ccc 100644 --- a/js/xpconnect/src/XPCVariant.cpp +++ b/js/xpconnect/src/XPCVariant.cpp @@ -42,8 +42,7 @@ XPCVariant::XPCVariant(JSContext* cx, Value aJSVal) // thing, but I'm saving the cleanup here for another day. Blake thinks // that we should just not store the WN if we're creating a variant for // an outer window. - JS::RootedObject obj(cx, &mJSVal.toObject()); - obj = JS_ObjectToInnerObject(cx, obj); + JSObject* obj = js::ToWindowIfWindowProxy(&mJSVal.toObject()); mJSVal = JS::ObjectValue(*obj); JSObject* unwrapped = js::CheckedUnwrap(obj, /* stopAtOuter = */ false); diff --git a/js/xpconnect/wrappers/WrapperFactory.cpp b/js/xpconnect/wrappers/WrapperFactory.cpp index 942b52d9e14..c8f493711e4 100644 --- a/js/xpconnect/wrappers/WrapperFactory.cpp +++ b/js/xpconnect/wrappers/WrapperFactory.cpp @@ -98,7 +98,7 @@ WrapperFactory::WaiveXray(JSContext* cx, JSObject* objArg) { RootedObject obj(cx, objArg); obj = UncheckedUnwrap(obj); - MOZ_ASSERT(!js::IsInnerObject(obj)); + MOZ_ASSERT(!js::IsWindow(obj)); JSObject* waiver = GetXrayWaiver(obj); if (waiver) @@ -155,20 +155,20 @@ WrapperFactory::PrepareForWrapping(JSContext* cx, HandleObject scope, RootedObject obj(cx, objArg); // Outerize any raw inner objects at the entry point here, so that we don't // have to worry about them for the rest of the wrapping code. - if (js::IsInnerObject(obj)) { + if (js::IsWindow(obj)) { JSAutoCompartment ac(cx, obj); - obj = JS_ObjectToOuterObject(cx, obj); - NS_ENSURE_TRUE(obj, nullptr); - // The outerization hook wraps, which means that we can end up with a - // CCW here if |obj| was a navigated-away-from inner. Strip any CCWs. + obj = js::ToWindowProxyIfWindow(obj); + MOZ_ASSERT(obj); + // ToWindowProxyIfWindow can return a CCW if |obj| was a + // navigated-away-from Window. Strip any CCWs. obj = js::UncheckedUnwrap(obj); - MOZ_ASSERT(js::IsOuterObject(obj)); + MOZ_ASSERT(js::IsWindowProxy(obj)); } - // If we've got an outer window, there's nothing special that needs to be + // If we've got a WindowProxy, there's nothing special that needs to be // done here, and we can move on to the next phase of wrapping. We handle // this case first to allow us to assert against wrappers below. - if (js::IsOuterObject(obj)) + if (js::IsWindowProxy(obj)) return waive ? WaiveXray(cx, obj) : obj; // Here are the rules for wrapping: @@ -400,7 +400,7 @@ WrapperFactory::Rewrap(JSContext* cx, HandleObject existing, HandleObject obj) js::GetObjectClass(obj)->ext.innerObject, "wrapped object passed to rewrap"); MOZ_ASSERT(!XrayUtils::IsXPCWNHolderClass(JS_GetClass(obj)), "trying to wrap a holder"); - MOZ_ASSERT(!js::IsInnerObject(obj)); + MOZ_ASSERT(!js::IsWindow(obj)); // We sometimes end up here after nsContentUtils has been shut down but before // XPConnect has been shut down, so check the context stack the roundabout way. MOZ_ASSERT(XPCJSRuntime::Get()->GetJSContextStack()->Peek() == cx); @@ -542,7 +542,7 @@ WrapperFactory::WaiveXrayAndWrap(JSContext* cx, MutableHandleObject argObj) { MOZ_ASSERT(argObj); RootedObject obj(cx, js::UncheckedUnwrap(argObj)); - MOZ_ASSERT(!js::IsInnerObject(obj)); + MOZ_ASSERT(!js::IsWindow(obj)); if (js::IsObjectInContextCompartment(obj, cx)) { argObj.set(obj); return true; diff --git a/xpcom/base/CycleCollectedJSRuntime.cpp b/xpcom/base/CycleCollectedJSRuntime.cpp index d39b5b5c6ca..d68a0cb5a9f 100644 --- a/xpcom/base/CycleCollectedJSRuntime.cpp +++ b/xpcom/base/CycleCollectedJSRuntime.cpp @@ -970,9 +970,9 @@ CycleCollectedJSRuntime::UsefulToMergeZones() const if (!obj) { continue; } - MOZ_ASSERT(js::IsOuterObject(obj)); - // Grab the inner from the outer. - obj = JS_ObjectToInnerObject(cx, obj); + MOZ_ASSERT(js::IsWindowProxy(obj)); + // Grab the global from the WindowProxy. + obj = js::ToWindowIfWindowProxy(obj); MOZ_ASSERT(JS_IsGlobalObject(obj)); if (JS::ObjectIsMarkedGray(obj) && !js::IsSystemCompartment(js::GetObjectCompartment(obj))) {