/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/Assertions.h" #include "mozilla/ScopeExit.h" #include "nsGlobalWindow.h" #include #include "mozilla/MemoryReporting.h" // Local Includes #include "Navigator.h" #include "mozilla/Encoding.h" #include "nsContentSecurityManager.h" #include "nsScreen.h" #include "nsHistory.h" #include "nsDOMNavigationTiming.h" #include "nsIDOMStorageManager.h" #include "nsISecureBrowserUI.h" #include "nsIWebProgressListener.h" #include "mozilla/AntiTrackingUtils.h" #include "mozilla/ContentBlocking.h" #include "mozilla/dom/AutoPrintEventDispatcher.h" #include "mozilla/dom/BindingUtils.h" #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/BrowsingContextBinding.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ContentFrameMessageManager.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/EventTarget.h" #include "mozilla/dom/LocalStorage.h" #include "mozilla/dom/LSObject.h" #include "mozilla/dom/Storage.h" #include "mozilla/dom/MaybeCrossOriginObject.h" #include "mozilla/dom/Performance.h" #include "mozilla/dom/StorageEvent.h" #include "mozilla/dom/StorageEventBinding.h" #include "mozilla/dom/StorageNotifierService.h" #include "mozilla/dom/StorageUtils.h" #include "mozilla/dom/Timeout.h" #include "mozilla/dom/TimeoutHandler.h" #include "mozilla/dom/TimeoutManager.h" #include "mozilla/dom/WindowContext.h" #include "mozilla/dom/WindowFeatures.h" // WindowFeatures #include "mozilla/dom/WindowProxyHolder.h" #include "mozilla/IntegerPrintfMacros.h" #if defined(MOZ_WIDGET_ANDROID) # include "mozilla/dom/WindowOrientationObserver.h" #endif #include "nsBaseCommandController.h" #include "nsError.h" #include "nsICookieService.h" #include "nsISizeOfEventTarget.h" #include "nsDOMJSUtils.h" #include "nsArrayUtils.h" #include "nsIDocShellTreeOwner.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIPermissionManager.h" #include "nsIScriptContext.h" #include "nsWindowMemoryReporter.h" #include "nsWindowSizes.h" #include "WindowNamedPropertiesHandler.h" #include "nsFrameSelection.h" #include "nsNetUtil.h" #include "nsVariant.h" #include "nsPrintfCString.h" #include "mozilla/intl/LocaleService.h" #include "WindowDestroyedEvent.h" #include "nsDocShellLoadState.h" #include "mozilla/dom/WindowGlobalChild.h" // Helper Classes #include "nsJSUtils.h" #include "jsapi.h" #include "jsfriendapi.h" #include "js/friend/StackLimits.h" // js::CheckRecursionLimitConservativeDontReport #include "js/friend/WindowProxy.h" // js::IsWindowProxy, js::SetWindowProxy #include "js/PropertySpec.h" #include "js/Wrapper.h" #include "nsLayoutUtils.h" #include "nsReadableUtils.h" #include "nsJSEnvironment.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/Preferences.h" #include "mozilla/Likely.h" #include "mozilla/SpinEventLoopUntil.h" #include "mozilla/Sprintf.h" #include "mozilla/Unused.h" // Other Classes #include "mozilla/dom/BarProps.h" #include "nsContentCID.h" #include "nsLayoutStatics.h" #include "nsCCUncollectableMarker.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/ToJSValue.h" #include "nsJSPrincipals.h" #include "mozilla/Attributes.h" #include "mozilla/Components.h" #include "mozilla/Debug.h" #include "mozilla/EventListenerManager.h" #include "mozilla/EventStates.h" #include "mozilla/MouseEvents.h" #include "mozilla/PresShell.h" #include "mozilla/ProcessHangMonitor.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/StaticPrefs_print.h" #include "mozilla/StaticPrefs_fission.h" #include "mozilla/ThrottledEventQueue.h" #include "AudioChannelService.h" #include "nsAboutProtocolUtils.h" #include "nsCharTraits.h" // NS_IS_HIGH/LOW_SURROGATE #include "PostMessageEvent.h" #include "mozilla/dom/DocGroup.h" #include "mozilla/net/CookieJarSettings.h" // Interfaces Needed #include "nsIFrame.h" #include "nsCanvasFrame.h" #include "nsIWidget.h" #include "nsIWidgetListener.h" #include "nsIBaseWindow.h" #include "nsIDeviceSensors.h" #include "nsIContent.h" #include "nsIDocShell.h" #include "mozilla/dom/Document.h" #include "Crypto.h" #include "nsDOMString.h" #include "nsIEmbeddingSiteWindow.h" #include "nsThreadUtils.h" #include "nsILoadContext.h" #include "nsIScrollableFrame.h" #include "nsView.h" #include "nsViewManager.h" #include "nsIPrompt.h" #include "nsIPromptService.h" #include "nsIPromptFactory.h" #include "nsIWritablePropertyBag2.h" #include "nsIWebNavigation.h" #include "nsIWebBrowserChrome.h" #include "nsIWebBrowserFind.h" // For window.find() #include "nsComputedDOMStyle.h" #include "nsDOMCID.h" #include "nsDOMWindowUtils.h" #include "nsIWindowWatcher.h" #include "nsPIWindowWatcher.h" #include "nsIContentViewer.h" #include "nsIScriptError.h" #include "nsISHistory.h" #include "nsIControllers.h" #include "nsGlobalWindowCommands.h" #include "nsQueryObject.h" #include "nsContentUtils.h" #include "nsCSSProps.h" #include "nsIURIFixup.h" #include "nsIURIMutator.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventStateManager.h" #include "nsIObserverService.h" #include "nsFocusManager.h" #include "nsIAppWindow.h" #include "nsServiceManagerUtils.h" #include "mozilla/dom/CustomEvent.h" #include "nsIScreenManager.h" #include "nsIClassifiedChannel.h" #include "nsIXULRuntime.h" #include "xpcprivate.h" #ifdef NS_PRINTING # include "nsIPrintSettings.h" # include "nsIPrintSettingsService.h" # include "nsIWebBrowserPrint.h" #endif #include "nsWindowRoot.h" #include "nsNetCID.h" #include "nsIArray.h" #include "nsIDOMXULCommandDispatcher.h" #include "mozilla/GlobalKeyListener.h" #include "nsIDragService.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Selection.h" #include "nsFrameLoader.h" #include "nsFrameLoaderOwner.h" #include "nsXPCOMCID.h" #include "mozilla/Logging.h" #include "mozilla/ProfilerMarkers.h" #include "prenv.h" #include "mozilla/dom/IDBFactory.h" #include "mozilla/dom/MessageChannel.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/Gamepad.h" #include "mozilla/dom/GamepadManager.h" #include "gfxVR.h" #include "VRShMem.h" #include "FxRWindowManager.h" #include "mozilla/dom/VRDisplay.h" #include "mozilla/dom/VRDisplayEvent.h" #include "mozilla/dom/VRDisplayEventBinding.h" #include "mozilla/dom/VREventObserver.h" #include "nsRefreshDriver.h" #include "Layers.h" #include "mozilla/BasePrincipal.h" #include "mozilla/Services.h" #include "mozilla/Telemetry.h" #include "mozilla/dom/Location.h" #include "nsHTMLDocument.h" #include "nsWrapperCacheInlines.h" #include "mozilla/DOMEventTargetHelper.h" #include "prrng.h" #include "nsSandboxFlags.h" #include "nsBaseCommandController.h" #include "nsXULControllers.h" #include "mozilla/dom/AudioContext.h" #include "mozilla/dom/BrowserElementDictionariesBinding.h" #include "mozilla/dom/BrowsingContextGroup.h" #include "mozilla/dom/cache/CacheStorage.h" #include "mozilla/dom/Console.h" #include "mozilla/dom/Fetch.h" #include "mozilla/dom/FunctionBinding.h" #include "mozilla/dom/HashChangeEvent.h" #include "mozilla/dom/IntlUtils.h" #include "mozilla/dom/PopStateEvent.h" #include "mozilla/dom/PopupBlockedEvent.h" #include "mozilla/dom/PrimitiveConversions.h" #include "mozilla/dom/WindowBinding.h" #include "nsIBrowserChild.h" #include "mozilla/dom/MediaQueryList.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/NavigatorBinding.h" #include "mozilla/dom/ImageBitmap.h" #include "mozilla/dom/ImageBitmapBinding.h" #include "mozilla/dom/ServiceWorkerRegistration.h" #include "mozilla/dom/U2F.h" #include "mozilla/dom/WebIDLGlobalNameHash.h" #include "mozilla/dom/Worklet.h" #include "AccessCheck.h" #ifdef HAVE_SIDEBAR # include "mozilla/dom/ExternalBinding.h" #endif #ifdef MOZ_WEBSPEECH # include "mozilla/dom/SpeechSynthesis.h" #endif #ifdef ANDROID # include #endif #ifdef XP_WIN # include # define getpid _getpid #else # include // for getpid() #endif using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::dom::ipc; using mozilla::BasePrincipal; using mozilla::OriginAttributes; using mozilla::TimeStamp; #define FORWARD_TO_INNER(method, args, err_rval) \ PR_BEGIN_MACRO \ if (!mInnerWindow) { \ NS_WARNING("No inner window available!"); \ return err_rval; \ } \ return GetCurrentInnerWindowInternal()->method args; \ PR_END_MACRO #define FORWARD_TO_INNER_VOID(method, args) \ PR_BEGIN_MACRO \ if (!mInnerWindow) { \ NS_WARNING("No inner window available!"); \ return; \ } \ GetCurrentInnerWindowInternal()->method args; \ return; \ PR_END_MACRO // Same as FORWARD_TO_INNER, but this will create a fresh inner if an // inner doesn't already exists. #define FORWARD_TO_INNER_CREATE(method, args, err_rval) \ PR_BEGIN_MACRO \ if (!mInnerWindow) { \ if (mIsClosed) { \ return err_rval; \ } \ nsCOMPtr kungFuDeathGrip = GetDoc(); \ ::mozilla::Unused << kungFuDeathGrip; \ if (!mInnerWindow) { \ return err_rval; \ } \ } \ return GetCurrentInnerWindowInternal()->method args; \ PR_END_MACRO static LazyLogModule gDOMLeakPRLogOuter("DOMLeakOuter"); extern LazyLogModule gPageCacheLog; #ifdef DEBUG static LazyLogModule gDocShellAndDOMWindowLeakLogging( "DocShellAndDOMWindowLeak"); #endif nsGlobalWindowOuter::OuterWindowByIdTable* nsGlobalWindowOuter::sOuterWindowsById = nullptr; /* static */ nsPIDOMWindowOuter* nsPIDOMWindowOuter::GetFromCurrentInner( nsPIDOMWindowInner* aInner) { if (!aInner) { return nullptr; } nsPIDOMWindowOuter* outer = aInner->GetOuterWindow(); if (!outer || outer->GetCurrentInnerWindow() != aInner) { return nullptr; } return outer; } //***************************************************************************** // nsOuterWindowProxy: Outer Window Proxy //***************************************************************************** // Give OuterWindowProxyClass 2 reserved slots, like the other wrappers, so // JSObject::swap can swap it with CrossCompartmentWrappers without requiring // malloc. // // We store the nsGlobalWindowOuter* in our first slot. // // We store our holder weakmap in the second slot. const JSClass OuterWindowProxyClass = PROXY_CLASS_DEF( "Proxy", JSCLASS_HAS_RESERVED_SLOTS(2)); /* additional class flags */ static const size_t OUTER_WINDOW_SLOT = 0; static const size_t HOLDER_WEAKMAP_SLOT = 1; class nsOuterWindowProxy : public MaybeCrossOriginObject { typedef MaybeCrossOriginObject Base; public: constexpr nsOuterWindowProxy() : Base(0) {} bool finalizeInBackground(const JS::Value& priv) const override { return false; } // Standard internal methods /** * Implementation of [[GetOwnProperty]] as defined at * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty * * "proxy" is the WindowProxy object involved. It may not be same-compartment * with cx. */ bool getOwnPropertyDescriptor( JSContext* cx, JS::Handle proxy, JS::Handle id, JS::MutableHandle desc) const override; /* * Implementation of the same-origin case of * . */ bool definePropertySameOrigin(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::Handle desc, JS::ObjectOpResult& result) const override; /** * Implementation of [[OwnPropertyKeys]] as defined at * * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-ownpropertykeys * * "proxy" is the WindowProxy object involved. It may not be same-compartment * with cx. */ bool ownPropertyKeys(JSContext* cx, JS::Handle proxy, JS::MutableHandleVector props) const override; /** * Implementation of [[Delete]] as defined at * https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-delete * * "proxy" is the WindowProxy object involved. It may not be same-compartment * with cx. */ bool delete_(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::ObjectOpResult& result) const override; /** * Implementaton of hook for superclass getPrototype() method. */ JSObject* getSameOriginPrototype(JSContext* cx) const override; /** * Implementation of [[HasProperty]] internal method as defined at * https://tc39.github.io/ecma262/#sec-ordinary-object-internal-methods-and-internal-slots-hasproperty-p * * "proxy" is the WindowProxy object involved. It may not be same-compartment * with cx. * * Note that the HTML spec does not define an override for this internal * method, so we just want the "normal object" behavior. We have to override * it, because js::Wrapper also overrides, with "not normal" behavior. */ bool has(JSContext* cx, JS::Handle proxy, JS::Handle id, bool* bp) const override; /** * Implementation of [[Get]] internal method as defined at * . * * "proxy" is the WindowProxy object involved. It may or may not be * same-compartment with "cx". * * "receiver" is the receiver ("this") for the get. It will be * same-compartment with "cx". * * "vp" is the return value. It will be same-compartment with "cx". */ bool get(JSContext* cx, JS::Handle proxy, JS::Handle receiver, JS::Handle id, JS::MutableHandle vp) const override; /** * Implementation of [[Set]] internal method as defined at * . * * "proxy" is the WindowProxy object involved. It may or may not be * same-compartment with "cx". * * "v" is the value being set. It will be same-compartment with "cx". * * "receiver" is the receiver ("this") for the set. It will be * same-compartment with "cx". */ bool set(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::Handle v, JS::Handle receiver, JS::ObjectOpResult& result) const override; // SpiderMonkey extensions /** * Implementation of SpiderMonkey extension which just checks whether this * object has the property. Basically Object.getOwnPropertyDescriptor(obj, * prop) !== undefined. but does not require reifying the descriptor. * * We have to override this because js::Wrapper overrides it, but we want * different behavior from js::Wrapper. * * "proxy" is the WindowProxy object involved. It may not be same-compartment * with cx. */ bool hasOwn(JSContext* cx, JS::Handle proxy, JS::Handle id, bool* bp) const override; /** * Implementation of SpiderMonkey extension which is used as a fast path for * enumerating. * * We have to override this because js::Wrapper overrides it, but we want * different behavior from js::Wrapper. * * "proxy" is the WindowProxy object involved. It may not be same-compartment * with cx. */ bool getOwnEnumerablePropertyKeys( JSContext* cx, JS::Handle proxy, JS::MutableHandleVector props) const override; /** * Hook used by SpiderMonkey to implement Object.prototype.toString. */ const char* className(JSContext* cx, JS::Handle wrapper) const override; void finalize(JSFreeOp* fop, JSObject* proxy) const override; size_t objectMoved(JSObject* proxy, JSObject* old) const override; bool isCallable(JSObject* obj) const override { return false; } bool isConstructor(JSObject* obj) const override { return false; } static const nsOuterWindowProxy singleton; static nsGlobalWindowOuter* GetOuterWindow(JSObject* proxy) { nsGlobalWindowOuter* outerWindow = nsGlobalWindowOuter::FromSupports(static_cast( js::GetProxyReservedSlot(proxy, OUTER_WINDOW_SLOT).toPrivate())); return outerWindow; } protected: // False return value means we threw an exception. True return value // but false "found" means we didn't have a subframe at that index. bool GetSubframeWindow(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::MutableHandle vp, bool& found) const; // Returns a non-null window only if id is an index and we have a // window at that index. Nullable GetSubframeWindow(JSContext* cx, JS::Handle proxy, JS::Handle id) const; bool AppendIndexedPropertyNames(JSObject* proxy, JS::MutableHandleVector props) const; using MaybeCrossOriginObjectMixins::EnsureHolder; bool EnsureHolder(JSContext* cx, JS::Handle proxy, JS::MutableHandle holder) const override; // Helper method for creating a special "print" method that allows printing // our PDF-viewer documents even if you're not same-origin with them. // // aProxy must be our nsOuterWindowProxy. It will not be same-compartment // with aCx, since we only use this on the different-origin codepath! // // Can return true without filling in aDesc, which corresponds to not exposing // a "print" method. static bool MaybeGetPDFJSPrintMethod( JSContext* cx, JS::Handle proxy, JS::MutableHandle desc); // The actual "print" method we use for the PDFJS case. static bool PDFJSPrintMethod(JSContext* cx, unsigned argc, JS::Value* vp); // Helper method to get the pre-PDF-viewer-messing-with-it principal from an // inner window. Will return null if this is not a PDF-viewer inner or if the // principal could not be found for some reason. static already_AddRefed GetNoPDFJSPrincipal( nsGlobalWindowInner* inner); }; const char* nsOuterWindowProxy::className(JSContext* cx, JS::Handle proxy) const { MOZ_ASSERT(js::IsProxy(proxy)); if (!IsPlatformObjectSameOrigin(cx, proxy)) { return "Object"; } return "Window"; } void nsOuterWindowProxy::finalize(JSFreeOp* fop, JSObject* proxy) const { nsGlobalWindowOuter* outerWindow = GetOuterWindow(proxy); if (outerWindow) { outerWindow->ClearWrapper(proxy); BrowsingContext* bc = outerWindow->GetBrowsingContext(); if (bc) { bc->ClearWindowProxy(); } // Ideally we would use OnFinalize here, but it's possible that // EnsureScriptEnvironment will later be called on the window, and we don't // want to create a new script object in that case. Therefore, we need to // write a non-null value that will reliably crash when dereferenced. outerWindow->PoisonOuterWindowProxy(proxy); } } /** * IsNonConfigurableReadonlyPrimitiveGlobalProp returns true for * property names that fit the following criteria: * * 1) The ES spec defines a property with that name on globals. * 2) The property is non-configurable. * 3) The property is non-writable (readonly). * 4) The value of the property is a primitive (so doesn't change * observably on when navigation happens). * * Such properties can act as actual non-configurable properties on a * WindowProxy, because they are not affected by navigation. */ #ifndef RELEASE_OR_BETA static bool IsNonConfigurableReadonlyPrimitiveGlobalProp(JSContext* cx, JS::Handle id) { return id == GetJSIDByIndex(cx, XPCJSContext::IDX_NAN) || id == GetJSIDByIndex(cx, XPCJSContext::IDX_UNDEFINED) || id == GetJSIDByIndex(cx, XPCJSContext::IDX_INFINITY); } #endif bool nsOuterWindowProxy::getOwnPropertyDescriptor( JSContext* cx, JS::Handle proxy, JS::Handle id, JS::MutableHandle desc) const { // First check for indexed access. This is // https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty // step 2, mostly. bool found; if (!GetSubframeWindow(cx, proxy, id, desc.value(), found)) { return false; } if (found) { // Step 2.4. FillPropertyDescriptor(desc, proxy, true); return true; } bool isSameOrigin = IsPlatformObjectSameOrigin(cx, proxy); // If we did not find a subframe, we could still have an indexed property // access. In that case we should throw a SecurityError in the cross-origin // case. if (!isSameOrigin && IsArrayIndex(GetArrayIndexFromId(id))) { // Step 2.5.2. return ReportCrossOriginDenial(cx, id, "access"_ns); } // Step 2.5.1 is handled via the forwarding to js::Wrapper; it saves us an // IsArrayIndex(GetArrayIndexFromId(id)) here. We'll never have a property on // the Window whose name is an index, because our defineProperty doesn't pass // those on to the Window. // Step 3. if (isSameOrigin) { if (StaticPrefs::dom_missing_prop_counters_enabled() && JSID_IS_ATOM(id)) { Window_Binding::CountMaybeMissingProperty(proxy, id); } // Fall through to js::Wrapper. { // Scope for JSAutoRealm while we are dealing with js::Wrapper. // When forwarding to js::Wrapper, we should just enter the Realm of proxy // for now. That's what js::Wrapper expects, and since we're same-origin // anyway this is not changing any security behavior. JSAutoRealm ar(cx, proxy); JS_MarkCrossZoneId(cx, id); bool ok = js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc); if (!ok) { return false; } #ifndef RELEASE_OR_BETA // To be turned on in bug 1496510. if (!IsNonConfigurableReadonlyPrimitiveGlobalProp(cx, id)) { desc.setConfigurable(true); } #endif } // Now wrap our descriptor back into the Realm that asked for it. return JS_WrapPropertyDescriptor(cx, desc); } // Step 4. if (!CrossOriginGetOwnPropertyHelper(cx, proxy, id, desc)) { return false; } // Step 5 if (desc.object()) { return true; } // Non-spec step for the PDF viewer's window.print(). This comes before we // check for named subframes, because in the same-origin case print() would // shadow those. if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_PRINT)) { if (!MaybeGetPDFJSPrintMethod(cx, proxy, desc)) { return false; } if (desc.object()) { return true; } } // Step 6 -- check for named subframes. if (JSID_IS_STRING(id)) { nsAutoJSString name; if (!name.init(cx, JSID_TO_STRING(id))) { return false; } nsGlobalWindowOuter* win = GetOuterWindow(proxy); if (RefPtr childDOMWin = win->GetChildWindow(name)) { JS::Rooted childValue(cx); if (!ToJSValue(cx, WindowProxyHolder(childDOMWin), &childValue)) { return false; } FillPropertyDescriptor(desc, proxy, childValue, /* readonly = */ true, /* enumerable = */ false); return true; } } // And step 7. return CrossOriginPropertyFallback(cx, proxy, id, desc); } bool nsOuterWindowProxy::definePropertySameOrigin( JSContext* cx, JS::Handle proxy, JS::Handle id, JS::Handle desc, JS::ObjectOpResult& result) const { if (IsArrayIndex(GetArrayIndexFromId(id))) { // Spec says to Reject whether this is a supported index or not, // since we have no indexed setter or indexed creator. It is up // to the caller to decide whether to throw a TypeError. return result.failCantDefineWindowElement(); } JS::ObjectOpResult ourResult; bool ok = js::Wrapper::defineProperty(cx, proxy, id, desc, ourResult); if (!ok) { return false; } if (!ourResult.ok()) { // It's possible that this failed because the page got the existing // descriptor (which we force to claim to be configurable) and then tried to // redefine the property with the descriptor it got but a different value. // We want to allow this case to succeed, so check for it and if we're in // that case try again but now with an attempt to define a non-configurable // property. if (!desc.hasConfigurable() || !desc.configurable()) { // The incoming descriptor was not explicitly marked "configurable: true", // so it failed for some other reason. Just propagate that reason out. result = ourResult; return true; } JS::Rooted existingDesc(cx); ok = js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, &existingDesc); if (!ok) { return false; } if (!existingDesc.object() || existingDesc.configurable()) { // We have no existing property, or its descriptor is already configurable // (on the Window itself, where things really can be non-configurable). // So we failed for some other reason, which we should propagate out. result = ourResult; return true; } JS::Rooted updatedDesc(cx, desc); updatedDesc.setConfigurable(false); JS::ObjectOpResult ourNewResult; ok = js::Wrapper::defineProperty(cx, proxy, id, updatedDesc, ourNewResult); if (!ok) { return false; } if (!ourNewResult.ok()) { // Twiddling the configurable flag didn't help. Just return this failure // out to the caller. result = ourNewResult; return true; } } #ifndef RELEASE_OR_BETA // To be turned on in bug 1496510. if (desc.hasConfigurable() && !desc.configurable() && !IsNonConfigurableReadonlyPrimitiveGlobalProp(cx, id)) { // Give callers a way to detect that they failed to "really" define a // non-configurable property. result.failCantDefineWindowNonConfigurable(); return true; } #endif result.succeed(); return true; } bool nsOuterWindowProxy::ownPropertyKeys( JSContext* cx, JS::Handle proxy, JS::MutableHandleVector props) const { // Just our indexed stuff followed by our "normal" own property names. if (!AppendIndexedPropertyNames(proxy, props)) { return false; } if (IsPlatformObjectSameOrigin(cx, proxy)) { // When forwarding to js::Wrapper, we should just enter the Realm of proxy // for now. That's what js::Wrapper expects, and since we're same-origin // anyway this is not changing any security behavior. JS::RootedVector innerProps(cx); { // Scope for JSAutoRealm so we can mark the ids once we exit it JSAutoRealm ar(cx, proxy); if (!js::Wrapper::ownPropertyKeys(cx, proxy, &innerProps)) { return false; } } for (auto& id : innerProps) { JS_MarkCrossZoneId(cx, id); } return js::AppendUnique(cx, props, innerProps); } // In the cross-origin case we purposefully exclude subframe names from the // list of property names we report here. JS::Rooted holder(cx); if (!EnsureHolder(cx, proxy, &holder)) { return false; } JS::RootedVector crossOriginProps(cx); if (!js::GetPropertyKeys(cx, holder, JSITER_OWNONLY | JSITER_HIDDEN | JSITER_SYMBOLS, &crossOriginProps) || !js::AppendUnique(cx, props, crossOriginProps)) { return false; } // Add the "print" property if needed. nsGlobalWindowOuter* outer = GetOuterWindow(proxy); nsGlobalWindowInner* inner = nsGlobalWindowInner::Cast(outer->GetCurrentInnerWindow()); if (inner) { nsCOMPtr targetPrincipal = GetNoPDFJSPrincipal(inner); if (targetPrincipal && nsContentUtils::SubjectPrincipal(cx)->Equals(targetPrincipal)) { JS::RootedVector printProp(cx); if (!printProp.append(GetJSIDByIndex(cx, XPCJSContext::IDX_PRINT)) || !js::AppendUnique(cx, props, printProp)) { return false; } } } return xpc::AppendCrossOriginWhitelistedPropNames(cx, props); } bool nsOuterWindowProxy::delete_(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::ObjectOpResult& result) const { if (!IsPlatformObjectSameOrigin(cx, proxy)) { return ReportCrossOriginDenial(cx, id, "delete"_ns); } if (!GetSubframeWindow(cx, proxy, id).IsNull()) { // Fail (which means throw if strict, else return false). return result.failCantDeleteWindowElement(); } if (IsArrayIndex(GetArrayIndexFromId(id))) { // Indexed, but not supported. Spec says return true. return result.succeed(); } // We're same-origin, so it should be safe to enter the Realm of "proxy". // Let's do that, just in case, to avoid cross-compartment issues in our // js::Wrapper caller.. JSAutoRealm ar(cx, proxy); JS_MarkCrossZoneId(cx, id); return js::Wrapper::delete_(cx, proxy, id, result); } JSObject* nsOuterWindowProxy::getSameOriginPrototype(JSContext* cx) const { return Window_Binding::GetProtoObjectHandle(cx); } bool nsOuterWindowProxy::has(JSContext* cx, JS::Handle proxy, JS::Handle id, bool* bp) const { // We could just directly forward this method to js::BaseProxyHandler, but // that involves reifying the actual property descriptor, which might be more // work than we have to do for has() on the Window. if (!IsPlatformObjectSameOrigin(cx, proxy)) { // In the cross-origin case we only have own properties. Just call hasOwn // directly. return hasOwn(cx, proxy, id, bp); } if (!GetSubframeWindow(cx, proxy, id).IsNull()) { *bp = true; return true; } // Just to be safe in terms of compartment asserts, enter the Realm of // "proxy". We're same-origin with it, so this should be safe. JSAutoRealm ar(cx, proxy); JS_MarkCrossZoneId(cx, id); return js::Wrapper::has(cx, proxy, id, bp); } bool nsOuterWindowProxy::hasOwn(JSContext* cx, JS::Handle proxy, JS::Handle id, bool* bp) const { // We could just directly forward this method to js::BaseProxyHandler, but // that involves reifying the actual property descriptor, which might be more // work than we have to do for hasOwn() on the Window. if (!IsPlatformObjectSameOrigin(cx, proxy)) { // Avoiding reifying the property descriptor here would require duplicating // a bunch of "is this property exposed cross-origin" logic, which is // probably not worth it. Just forward this along to the base // implementation. // // It's very important to not forward this to js::Wrapper, because that will // not do the right security and cross-origin checks and will pass through // the call to the Window. // // The BaseProxyHandler code is OK with this happening without entering the // compartment of "proxy". return js::BaseProxyHandler::hasOwn(cx, proxy, id, bp); } if (!GetSubframeWindow(cx, proxy, id).IsNull()) { *bp = true; return true; } // Just to be safe in terms of compartment asserts, enter the Realm of // "proxy". We're same-origin with it, so this should be safe. JSAutoRealm ar(cx, proxy); JS_MarkCrossZoneId(cx, id); return js::Wrapper::hasOwn(cx, proxy, id, bp); } bool nsOuterWindowProxy::get(JSContext* cx, JS::Handle proxy, JS::Handle receiver, JS::Handle id, JS::MutableHandle vp) const { if (id == GetJSIDByIndex(cx, XPCJSContext::IDX_WRAPPED_JSOBJECT) && xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) { vp.set(JS::ObjectValue(*proxy)); return MaybeWrapValue(cx, vp); } if (!IsPlatformObjectSameOrigin(cx, proxy)) { return CrossOriginGet(cx, proxy, receiver, id, vp); } bool found; if (!GetSubframeWindow(cx, proxy, id, vp, found)) { return false; } if (found) { return true; } if (StaticPrefs::dom_missing_prop_counters_enabled() && JSID_IS_ATOM(id)) { Window_Binding::CountMaybeMissingProperty(proxy, id); } { // Scope for JSAutoRealm // Enter "proxy"'s Realm. We're in the same-origin case, so this should be // safe. JSAutoRealm ar(cx, proxy); JS_MarkCrossZoneId(cx, id); JS::Rooted wrappedReceiver(cx, receiver); if (!MaybeWrapValue(cx, &wrappedReceiver)) { return false; } // Fall through to js::Wrapper. if (!js::Wrapper::get(cx, proxy, wrappedReceiver, id, vp)) { return false; } } // Make sure our return value is in the caller compartment. return MaybeWrapValue(cx, vp); } bool nsOuterWindowProxy::set(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::Handle v, JS::Handle receiver, JS::ObjectOpResult& result) const { if (!IsPlatformObjectSameOrigin(cx, proxy)) { return CrossOriginSet(cx, proxy, id, v, receiver, result); } if (IsArrayIndex(GetArrayIndexFromId(id))) { // Reject the set. It's up to the caller to decide whether to throw a // TypeError. If the caller is strict mode JS code, it'll throw. return result.failReadOnly(); } // Do the rest in the Realm of "proxy", since we're in the same-origin case. JSAutoRealm ar(cx, proxy); JS::Rooted wrappedArg(cx, v); if (!MaybeWrapValue(cx, &wrappedArg)) { return false; } JS::Rooted wrappedReceiver(cx, receiver); if (!MaybeWrapValue(cx, &wrappedReceiver)) { return false; } JS_MarkCrossZoneId(cx, id); return js::Wrapper::set(cx, proxy, id, wrappedArg, wrappedReceiver, result); } bool nsOuterWindowProxy::getOwnEnumerablePropertyKeys( JSContext* cx, JS::Handle proxy, JS::MutableHandleVector props) const { // We could just stop overring getOwnEnumerablePropertyKeys and let our // superclasses deal (by falling back on the BaseProxyHandler implementation // that uses a combination of ownPropertyKeys and getOwnPropertyDescriptor to // only return the enumerable ones. But maybe there's value in having // somewhat faster for-in iteration on Window objects... // Like ownPropertyKeys, our indexed stuff followed by our "normal" enumerable // own property names. if (!AppendIndexedPropertyNames(proxy, props)) { return false; } if (!IsPlatformObjectSameOrigin(cx, proxy)) { // All the cross-origin properties other than the indexed props are // non-enumerable, so we're done here. return true; } // When forwarding to js::Wrapper, we should just enter the Realm of proxy // for now. That's what js::Wrapper expects, and since we're same-origin // anyway this is not changing any security behavior. JS::RootedVector innerProps(cx); { // Scope for JSAutoRealm so we can mark the ids once we exit it. JSAutoRealm ar(cx, proxy); if (!js::Wrapper::getOwnEnumerablePropertyKeys(cx, proxy, &innerProps)) { return false; } } for (auto& id : innerProps) { JS_MarkCrossZoneId(cx, id); } return js::AppendUnique(cx, props, innerProps); } bool nsOuterWindowProxy::GetSubframeWindow(JSContext* cx, JS::Handle proxy, JS::Handle id, JS::MutableHandle vp, bool& found) const { Nullable frame = GetSubframeWindow(cx, proxy, id); if (frame.IsNull()) { found = false; return true; } found = true; return WrapObject(cx, frame.Value(), vp); } Nullable nsOuterWindowProxy::GetSubframeWindow( JSContext* cx, JS::Handle proxy, JS::Handle id) const { uint32_t index = GetArrayIndexFromId(id); if (!IsArrayIndex(index)) { return nullptr; } nsGlobalWindowOuter* win = GetOuterWindow(proxy); return win->IndexedGetterOuter(index); } bool nsOuterWindowProxy::AppendIndexedPropertyNames( JSObject* proxy, JS::MutableHandleVector props) const { uint32_t length = GetOuterWindow(proxy)->Length(); MOZ_ASSERT(int32_t(length) >= 0); if (!props.reserve(props.length() + length)) { return false; } for (int32_t i = 0; i < int32_t(length); ++i) { if (!props.append(INT_TO_JSID(i))) { return false; } } return true; } bool nsOuterWindowProxy::EnsureHolder( JSContext* cx, JS::Handle proxy, JS::MutableHandle holder) const { return EnsureHolder(cx, proxy, HOLDER_WEAKMAP_SLOT, Window_Binding::sCrossOriginProperties, holder); } size_t nsOuterWindowProxy::objectMoved(JSObject* obj, JSObject* old) const { nsGlobalWindowOuter* outerWindow = GetOuterWindow(obj); if (outerWindow) { outerWindow->UpdateWrapper(obj, old); BrowsingContext* bc = outerWindow->GetBrowsingContext(); if (bc) { bc->UpdateWindowProxy(obj, old); } } return 0; } enum { PDFJS_SLOT_CALLEE = 0 }; // static bool nsOuterWindowProxy::MaybeGetPDFJSPrintMethod( JSContext* cx, JS::Handle proxy, JS::MutableHandle desc) { MOZ_ASSERT(proxy); nsGlobalWindowOuter* outer = GetOuterWindow(proxy); nsGlobalWindowInner* inner = nsGlobalWindowInner::Cast(outer->GetCurrentInnerWindow()); if (!inner) { // No print method to expose. return true; } nsCOMPtr targetPrincipal = GetNoPDFJSPrincipal(inner); if (!targetPrincipal) { // Nothing special to be done. return true; } if (!nsContentUtils::SubjectPrincipal(cx)->Equals(targetPrincipal)) { // Not our origin's PDF document. return true; } // Get the function we plan to actually call. JS::Rooted innerObj(cx, inner->GetGlobalJSObject()); if (!innerObj) { // Really should not happen, but ok, let's just return. return true; } JS::Rooted targetFunc(cx); { JSAutoRealm ar(cx, innerObj); if (!JS_GetProperty(cx, innerObj, "print", &targetFunc)) { return false; } } if (!targetFunc.isObject()) { // Who knows what's going on. Just return. return true; } // The Realm of cx is the realm our caller is in and the realm we // should create our function in. Note that we can't use the // standard XPConnect function forwarder machinery because our // "this" is cross-origin, so we have to do thus by hand. // Make sure targetFunc is wrapped into the right compartment. if (!MaybeWrapValue(cx, &targetFunc)) { return false; } JSFunction* fun = js::NewFunctionWithReserved(cx, PDFJSPrintMethod, 0, 0, "print"); if (!fun) { return false; } JS::Rooted funObj(cx, JS_GetFunctionObject(fun)); js::SetFunctionNativeReserved(funObj, PDFJS_SLOT_CALLEE, targetFunc); JS::Rooted funVal(cx, JS::ObjectValue(*funObj)); // JSPROP_ENUMERATE because that's what it would have been in the same-origin // case without the PDF viewer messing with things. desc.setDataDescriptor(funVal, JSPROP_ENUMERATE); desc.object().set(proxy); return true; } // static bool nsOuterWindowProxy::PDFJSPrintMethod(JSContext* cx, unsigned argc, JS::Value* vp) { JS::CallArgs args = CallArgsFromVp(argc, vp); JS::Rooted realCallee( cx, &js::GetFunctionNativeReserved(&args.callee(), PDFJS_SLOT_CALLEE) .toObject()); // Unchecked unwrap, because we want to extract the thing we really had // before. realCallee = js::UncheckedUnwrap(realCallee); JS::Rooted thisv(cx, args.thisv()); if (thisv.isNullOrUndefined()) { // Replace it with the global of our stashed callee, simulating the // global-assuming behavior of DOM methods. JS::Rooted global(cx, JS::GetNonCCWObjectGlobal(realCallee)); if (!MaybeWrapObject(cx, &global)) { return false; } thisv.setObject(*global); } else if (!thisv.isObject()) { return ThrowInvalidThis(cx, args, false, prototypes::id::Window); } // We want to do an UncheckedUnwrap here, because we're going to directly // examine the principal of the inner window, if we have an inner window. JS::Rooted unwrappedObj(cx, js::UncheckedUnwrap(&thisv.toObject())); nsGlobalWindowInner* inner = nullptr; { // Do the unwrap in the Realm of the object we're looking at. JSAutoRealm ar(cx, unwrappedObj); UNWRAP_MAYBE_CROSS_ORIGIN_OBJECT(Window, &unwrappedObj, inner, cx); } if (!inner) { return ThrowInvalidThis(cx, args, false, prototypes::id::Window); } nsIPrincipal* callerPrincipal = nsContentUtils::SubjectPrincipal(cx); if (!callerPrincipal->SubsumesConsideringDomain(inner->GetPrincipal())) { // Check whether it's a PDF viewer from our origin. nsCOMPtr pdfPrincipal = GetNoPDFJSPrincipal(inner); if (!pdfPrincipal || !callerPrincipal->Equals(pdfPrincipal)) { // Security error. return ThrowInvalidThis(cx, args, true, prototypes::id::Window); } } // Go ahead and enter the Realm of our real callee to call it. We'll pass it // our "thisv", just in case someone grabs a "print" method off one PDF // document and .call()s it on another one. { JSAutoRealm ar(cx, realCallee); if (!MaybeWrapValue(cx, &thisv)) { return false; } // Don't bother passing through the args; they will get ignored anyway. if (!JS::Call(cx, thisv, realCallee, JS::HandleValueArray::empty(), args.rval())) { return false; } } // Wrap the return value (not that there should be any!) into the right // compartment. return MaybeWrapValue(cx, args.rval()); } // static already_AddRefed nsOuterWindowProxy::GetNoPDFJSPrincipal( nsGlobalWindowInner* inner) { if (!nsContentUtils::IsPDFJS(inner->GetPrincipal())) { return nullptr; } Document* doc = inner->GetExtantDoc(); if (!doc) { return nullptr; } nsCOMPtr propBag(do_QueryInterface(doc->GetChannel())); if (!propBag) { return nullptr; } nsCOMPtr principal; propBag->GetPropertyAsInterface(u"noPDFJSPrincipal"_ns, NS_GET_IID(nsIPrincipal), getter_AddRefs(principal)); return principal.forget(); } const nsOuterWindowProxy nsOuterWindowProxy::singleton; class nsChromeOuterWindowProxy : public nsOuterWindowProxy { public: constexpr nsChromeOuterWindowProxy() : nsOuterWindowProxy() {} const char* className(JSContext* cx, JS::Handle wrapper) const override; static const nsChromeOuterWindowProxy singleton; }; const char* nsChromeOuterWindowProxy::className( JSContext* cx, JS::Handle proxy) const { MOZ_ASSERT(js::IsProxy(proxy)); return "ChromeWindow"; } const nsChromeOuterWindowProxy nsChromeOuterWindowProxy::singleton; static JSObject* NewOuterWindowProxy(JSContext* cx, JS::Handle global, bool isChrome) { MOZ_ASSERT(JS_IsGlobalObject(global)); JSAutoRealm ar(cx, global); js::WrapperOptions options; options.setClass(&OuterWindowProxyClass); JSObject* obj = js::Wrapper::NewSingleton(cx, global, isChrome ? &nsChromeOuterWindowProxy::singleton : &nsOuterWindowProxy::singleton, options); MOZ_ASSERT_IF(obj, js::IsWindowProxy(obj)); return obj; } //***************************************************************************** //*** nsGlobalWindowOuter: Object Management //***************************************************************************** nsGlobalWindowOuter::nsGlobalWindowOuter(uint64_t aWindowID) : nsPIDOMWindowOuter(aWindowID), mFullscreen(false), mFullscreenMode(false), mForceFullScreenInWidget(false), mIsClosed(false), mInClose(false), mHavePendingClose(false), mIsPopupSpam(false), mBlockScriptedClosingFlag(false), mWasOffline(false), mCreatingInnerWindow(false), mIsChrome(false), mAllowScriptsToClose(false), mTopLevelOuterContentWindow(false), mStorageAccessPermissionGranted(false), mDelayedPrintUntilAfterLoad(false), mDelayedCloseForPrinting(false), mShouldDelayPrintUntilAfterLoad(false), #ifdef DEBUG mSerial(0), mSetOpenerWindowCalled(false), #endif mCleanedUp(false), mCanSkipCCGeneration(0), mAutoActivateVRDisplayID(0) { AssertIsOnMainThread(); nsLayoutStatics::AddRef(); // Initialize the PRCList (this). PR_INIT_CLIST(this); // |this| is an outer window. Outer windows start out frozen and // remain frozen until they get an inner window. MOZ_ASSERT(IsFrozen()); // We could have failed the first time through trying // to create the entropy collector, so we should // try to get one until we succeed. #ifdef DEBUG mSerial = nsContentUtils::InnerOrOuterWindowCreated(); MOZ_LOG(gDocShellAndDOMWindowLeakLogging, LogLevel::Info, ("++DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p]\n", nsContentUtils::GetCurrentInnerOrOuterWindowCount(), static_cast(ToCanonicalSupports(this)), getpid(), mSerial, nullptr)); #endif MOZ_LOG(gDOMLeakPRLogOuter, LogLevel::Debug, ("DOMWINDOW %p created outer=nullptr", this)); // Add ourselves to the outer windows list. MOZ_ASSERT(sOuterWindowsById, "Outer Windows hash table must be created!"); // |this| is an outer window, add to the outer windows list. MOZ_ASSERT(!sOuterWindowsById->Get(mWindowID), "This window shouldn't be in the hash table yet!"); // We seem to see crashes in release builds because of null // |sOuterWindowsById|. if (sOuterWindowsById) { sOuterWindowsById->Put(mWindowID, this); } } #ifdef DEBUG /* static */ void nsGlobalWindowOuter::AssertIsOnMainThread() { MOZ_ASSERT(NS_IsMainThread()); } #endif // DEBUG /* static */ void nsGlobalWindowOuter::Init() { AssertIsOnMainThread(); NS_ASSERTION(gDOMLeakPRLogOuter, "gDOMLeakPRLogOuter should have been initialized!"); sOuterWindowsById = new OuterWindowByIdTable(); } nsGlobalWindowOuter::~nsGlobalWindowOuter() { AssertIsOnMainThread(); if (sOuterWindowsById) { sOuterWindowsById->Remove(mWindowID); } nsContentUtils::InnerOrOuterWindowDestroyed(); #ifdef DEBUG if (MOZ_LOG_TEST(gDocShellAndDOMWindowLeakLogging, LogLevel::Info)) { nsAutoCString url; if (mLastOpenedURI) { url = mLastOpenedURI->GetSpecOrDefault(); // Data URLs can be very long, so truncate to avoid flooding the log. const uint32_t maxURLLength = 1000; if (url.Length() > maxURLLength) { url.Truncate(maxURLLength); } } MOZ_LOG( gDocShellAndDOMWindowLeakLogging, LogLevel::Info, ("--DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p] [url = " "%s]\n", nsContentUtils::GetCurrentInnerOrOuterWindowCount(), static_cast(ToCanonicalSupports(this)), getpid(), mSerial, nullptr, url.get())); } #endif MOZ_LOG(gDOMLeakPRLogOuter, LogLevel::Debug, ("DOMWINDOW %p destroyed", this)); JSObject* proxy = GetWrapperMaybeDead(); if (proxy) { if (mBrowsingContext && mBrowsingContext->GetUnbarrieredWindowProxy()) { nsGlobalWindowOuter* outer = nsOuterWindowProxy::GetOuterWindow( mBrowsingContext->GetUnbarrieredWindowProxy()); // Check that the current WindowProxy object corresponds to this // nsGlobalWindowOuter, because we don't want to clear the WindowProxy if // we've replaced it with a cross-process WindowProxy. if (outer == this) { mBrowsingContext->ClearWindowProxy(); } } js::SetProxyReservedSlot(proxy, OUTER_WINDOW_SLOT, JS::PrivateValue(nullptr)); } // An outer window is destroyed with inner windows still possibly // alive, iterate through the inner windows and null out their // back pointer to this outer, and pull them out of the list of // inner windows. // // Our linked list of inner windows both contains (an nsGlobalWindowOuter), // and our inner windows (nsGlobalWindowInners). This means that we need to // use PRCList*. We can then compare that PRCList* to `this` to see if its an // inner or outer window. PRCList* w; while ((w = PR_LIST_HEAD(this)) != this) { PR_REMOVE_AND_INIT_LINK(w); } DropOuterWindowDocs(); // Outer windows are always supposed to call CleanUp before letting themselves // be destroyed. MOZ_ASSERT(mCleanedUp); nsCOMPtr ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID); if (ac) ac->RemoveWindowAsListener(this); nsLayoutStatics::Release(); } // static void nsGlobalWindowOuter::ShutDown() { AssertIsOnMainThread(); delete sOuterWindowsById; sOuterWindowsById = nullptr; } void nsGlobalWindowOuter::DropOuterWindowDocs() { MOZ_ASSERT_IF(mDoc, !mDoc->EventHandlingSuppressed()); mDoc = nullptr; mSuspendedDoc = nullptr; } void nsGlobalWindowOuter::CleanUp() { // Guarantee idempotence. if (mCleanedUp) return; mCleanedUp = true; StartDying(); mWindowUtils = nullptr; ClearControllers(); mContext = nullptr; // Forces Release mChromeEventHandler = nullptr; // Forces Release mParentTarget = nullptr; mMessageManager = nullptr; mArguments = nullptr; } void nsGlobalWindowOuter::ClearControllers() { if (mControllers) { uint32_t count; mControllers->GetControllerCount(&count); while (count--) { nsCOMPtr controller; mControllers->GetControllerAt(count, getter_AddRefs(controller)); nsCOMPtr context = do_QueryInterface(controller); if (context) context->SetCommandContext(nullptr); } mControllers = nullptr; } } //***************************************************************************** // nsGlobalWindowOuter::nsISupports //***************************************************************************** // QueryInterface implementation for nsGlobalWindowOuter NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindowOuter) NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, EventTarget) NS_INTERFACE_MAP_ENTRY(nsIDOMWindow) NS_INTERFACE_MAP_ENTRY(nsIGlobalObject) NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObject) NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal) NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget) NS_INTERFACE_MAP_ENTRY(nsPIDOMWindowOuter) NS_INTERFACE_MAP_ENTRY(mozIDOMWindowProxy) NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMChromeWindow, IsChromeWindow()) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) NS_INTERFACE_MAP_END NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindowOuter) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindowOuter) NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindowOuter) if (tmp->IsBlackForCC(false)) { if (nsCCUncollectableMarker::InGeneration(tmp->mCanSkipCCGeneration)) { return true; } tmp->mCanSkipCCGeneration = nsCCUncollectableMarker::sGeneration; if (EventListenerManager* elm = tmp->GetExistingListenerManager()) { elm->MarkForCC(); } return true; } NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsGlobalWindowOuter) return tmp->IsBlackForCC(true); NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGlobalWindowOuter) return tmp->IsBlackForCC(false); NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalWindowOuter) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowOuter) if (MOZ_UNLIKELY(cb.WantDebugInfo())) { char name[512]; nsAutoCString uri; if (tmp->mDoc && tmp->mDoc->GetDocumentURI()) { uri = tmp->mDoc->GetDocumentURI()->GetSpecOrDefault(); } SprintfLiteral(name, "nsGlobalWindowOuter # %" PRIu64 " outer %s", tmp->mWindowID, uri.get()); cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); } else { NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGlobalWindowOuter, tmp->mRefCnt.get()) } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArguments) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentStoragePrincipal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPartitionedPrincipal) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc) // Traverse stuff from nsPIDOMWindow NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameElement) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocShell) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext) tmp->TraverseObjectsInGlobal(cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mBrowserDOMWindow) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowOuter) NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE if (sOuterWindowsById) { sOuterWindowsById->Remove(tmp->mWindowID); } NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext) NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers) NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments) NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDoc) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentStoragePrincipal) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPartitionedPrincipal) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc) // Unlink stuff from nsPIDOMWindow NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler) NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget) NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameElement) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocShell) if (tmp->mBrowsingContext) { if (tmp->mBrowsingContext->GetUnbarrieredWindowProxy()) { nsGlobalWindowOuter* outer = nsOuterWindowProxy::GetOuterWindow( tmp->mBrowsingContext->GetUnbarrieredWindowProxy()); // Check that the current WindowProxy object corresponds to this // nsGlobalWindowOuter, because we don't want to clear the WindowProxy if // we've replaced it with a cross-process WindowProxy. if (outer == tmp) { tmp->mBrowsingContext->ClearWindowProxy(); } } tmp->mBrowsingContext = nullptr; } tmp->UnlinkObjectsInGlobal(); if (tmp->IsChromeWindow()) { NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mBrowserDOMWindow) } NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_UNLINK_END NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGlobalWindowOuter) NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_END bool nsGlobalWindowOuter::IsBlackForCC(bool aTracingNeeded) { if (!nsCCUncollectableMarker::sGeneration) { return false; } // Unlike most wrappers, the outer window wrapper is not a wrapper for // the outer window. Instead, the outer window wrapper holds the inner // window binding object, which in turn holds the nsGlobalWindowInner, which // has a strong reference to the nsGlobalWindowOuter. We're using the // mInnerWindow pointer as a flag for that whole chain. return (nsCCUncollectableMarker::InGeneration(GetMarkedCCGeneration()) || (mInnerWindow && HasKnownLiveWrapper())) && (!aTracingNeeded || HasNothingToTrace(ToSupports(this))); } //***************************************************************************** // nsGlobalWindowOuter::nsIScriptGlobalObject //***************************************************************************** nsresult nsGlobalWindowOuter::EnsureScriptEnvironment() { if (GetWrapperPreserveColor()) { return NS_OK; } NS_ENSURE_STATE(!mCleanedUp); NS_ASSERTION(!GetCurrentInnerWindowInternal(), "No cached wrapper, but we have an inner window?"); NS_ASSERTION(!mContext, "Will overwrite mContext!"); // If this window is an [i]frame, don't bother GC'ing when the frame's context // is destroyed since a GC will happen when the frameset or host document is // destroyed anyway. mContext = new nsJSContext(mBrowsingContext->IsTop(), this); return NS_OK; } nsIScriptContext* nsGlobalWindowOuter::GetScriptContext() { return mContext; } bool nsGlobalWindowOuter::WouldReuseInnerWindow(Document* aNewDocument) { // We reuse the inner window when: // a. We are currently at our original document. // b. At least one of the following conditions are true: // -- The new document is the same as the old document. This means that we're // getting called from document.open(). // -- The new document has the same origin as what we have loaded right now. if (!mDoc || !aNewDocument) { return false; } if (!mDoc->IsInitialDocument()) { return false; } #ifdef DEBUG { nsCOMPtr uri; NS_GetURIWithoutRef(mDoc->GetDocumentURI(), getter_AddRefs(uri)); NS_ASSERTION(NS_IsAboutBlank(uri), "How'd this happen?"); } #endif // Great, we're the original document, check for one of the other // conditions. if (mDoc == aNewDocument) { return true; } if (aNewDocument->IsStaticDocument()) { return false; } if (BasePrincipal::Cast(mDoc->NodePrincipal()) ->FastEqualsConsideringDomain(aNewDocument->NodePrincipal())) { // The origin is the same. return true; } return false; } void nsGlobalWindowOuter::SetInitialPrincipalToSubject( nsIContentSecurityPolicy* aCSP, const Maybe& aCOEP) { // First, grab the subject principal. nsCOMPtr newWindowPrincipal = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller(); // We should never create windows with an expanded principal. // If we have a system principal, make sure we're not using it for a content // docshell. // NOTE: Please keep this logic in sync with // nsAppShellService::JustCreateTopWindow if (nsContentUtils::IsExpandedPrincipal(newWindowPrincipal) || (newWindowPrincipal->IsSystemPrincipal() && GetBrowsingContext()->IsContent())) { newWindowPrincipal = nullptr; } // If there's an existing document, bail if it either: if (mDoc) { // (a) is not an initial about:blank document, or if (!mDoc->IsInitialDocument()) return; // (b) already has the correct principal. if (mDoc->NodePrincipal() == newWindowPrincipal) return; #ifdef DEBUG // If we have a document loaded at this point, it had better be about:blank. // Otherwise, something is really weird. An about:blank page has a // NullPrincipal. bool isNullPrincipal; MOZ_ASSERT(NS_SUCCEEDED(mDoc->NodePrincipal()->GetIsNullPrincipal( &isNullPrincipal)) && isNullPrincipal); #endif } // Use the subject (or system) principal as the storage principal too until // the new window finishes navigating and gets a real storage principal. nsDocShell::Cast(GetDocShell()) ->CreateAboutBlankContentViewer(newWindowPrincipal, newWindowPrincipal, aCSP, nullptr, aCOEP); if (mDoc) { mDoc->SetIsInitialDocument(true); } RefPtr presShell = GetDocShell()->GetPresShell(); if (presShell && !presShell->DidInitialize()) { // Ensure that if someone plays with this document they will get // layout happening. presShell->Initialize(); } } #define WINDOWSTATEHOLDER_IID \ { \ 0x0b917c3e, 0xbd50, 0x4683, { \ 0xaf, 0xc9, 0xc7, 0x81, 0x07, 0xae, 0x33, 0x26 \ } \ } class WindowStateHolder final : public nsISupports { public: NS_DECLARE_STATIC_IID_ACCESSOR(WINDOWSTATEHOLDER_IID) NS_DECL_ISUPPORTS explicit WindowStateHolder(nsGlobalWindowInner* aWindow); nsGlobalWindowInner* GetInnerWindow() { return mInnerWindow; } void DidRestoreWindow() { mInnerWindow = nullptr; mInnerWindowReflector = nullptr; } protected: ~WindowStateHolder(); nsGlobalWindowInner* mInnerWindow; // We hold onto this to make sure the inner window doesn't go away. The outer // window ends up recalculating it anyway. JS::PersistentRooted mInnerWindowReflector; }; NS_DEFINE_STATIC_IID_ACCESSOR(WindowStateHolder, WINDOWSTATEHOLDER_IID) WindowStateHolder::WindowStateHolder(nsGlobalWindowInner* aWindow) : mInnerWindow(aWindow), mInnerWindowReflector(RootingCx(), aWindow->GetWrapper()) { MOZ_ASSERT(aWindow, "null window"); aWindow->Suspend(); // When a global goes into the bfcache, we disable script. xpc::Scriptability::Get(mInnerWindowReflector).SetDocShellAllowsScript(false); } WindowStateHolder::~WindowStateHolder() { if (mInnerWindow) { // This window was left in the bfcache and is now going away. We need to // free it up. // Note that FreeInnerObjects may already have been called on the // inner window if its outer has already had SetDocShell(null) // called. mInnerWindow->FreeInnerObjects(); } } NS_IMPL_ISUPPORTS(WindowStateHolder, WindowStateHolder) bool nsGlobalWindowOuter::ComputeIsSecureContext(Document* aDocument, SecureContextFlags aFlags) { nsCOMPtr principal = aDocument->NodePrincipal(); if (principal->IsSystemPrincipal()) { return true; } // Implement https://w3c.github.io/webappsec-secure-contexts/#settings-object // With some modifications to allow for aFlags. bool hadNonSecureContextCreator = false; if (WindowContext* parentWindow = GetBrowsingContext()->GetParentWindowContext()) { hadNonSecureContextCreator = !parentWindow->GetIsSecureContext(); } if (hadNonSecureContextCreator) { return false; } if (nsContentUtils::HttpsStateIsModern(aDocument)) { return true; } if (principal->GetIsNullPrincipal()) { nsCOMPtr uri = aDocument->GetOriginalURI(); // IsOriginPotentiallyTrustworthy doesn't care about origin attributes so // it doesn't actually matter what we use here, but reusing the document // principal's attributes is convenient. const OriginAttributes& attrs = principal->OriginAttributesRef(); // CreateContentPrincipal correctly gets a useful principal for blob: and // other URI_INHERITS_SECURITY_CONTEXT URIs. principal = BasePrincipal::CreateContentPrincipal(uri, attrs); if (NS_WARN_IF(!principal)) { return false; } } return principal->GetIsOriginPotentiallyTrustworthy(); } static bool InitializeLegacyNetscapeObject(JSContext* aCx, JS::Handle aGlobal) { JSAutoRealm ar(aCx, aGlobal); // Note: MathJax depends on window.netscape being exposed. See bug 791526. JS::Rooted obj(aCx); obj = JS_DefineObject(aCx, aGlobal, "netscape", nullptr); NS_ENSURE_TRUE(obj, false); obj = JS_DefineObject(aCx, obj, "security", nullptr); NS_ENSURE_TRUE(obj, false); return true; } struct MOZ_STACK_CLASS CompartmentFinderState { explicit CompartmentFinderState(nsIPrincipal* aPrincipal) : principal(aPrincipal), compartment(nullptr) {} // Input: we look for a compartment which is same-origin with the // given principal. nsIPrincipal* principal; // Output: We set this member if we find a compartment. JS::Compartment* compartment; }; static JS::CompartmentIterResult FindSameOriginCompartment( JSContext* aCx, void* aData, JS::Compartment* aCompartment) { auto* data = static_cast(aData); MOZ_ASSERT(!data->compartment, "Why are we getting called?"); // If this compartment is not safe to share across globals, don't do // anything with it; in particular we should not be getting a // CompartmentPrivate from such a compartment, because it may be in // the middle of being collected and its CompartmentPrivate may no // longer be valid. if (!js::IsSharableCompartment(aCompartment)) { return JS::CompartmentIterResult::KeepGoing; } auto* compartmentPrivate = xpc::CompartmentPrivate::Get(aCompartment); if (!compartmentPrivate->CanShareCompartmentWith(data->principal)) { // Can't reuse this one, keep going. return JS::CompartmentIterResult::KeepGoing; } // We have a winner! data->compartment = aCompartment; return JS::CompartmentIterResult::Stop; } static JS::RealmCreationOptions& SelectZone( JSContext* aCx, nsIPrincipal* aPrincipal, nsGlobalWindowInner* aNewInner, JS::RealmCreationOptions& aOptions) { // Use the shared system compartment for chrome windows. if (aPrincipal->IsSystemPrincipal()) { return aOptions.setExistingCompartment(xpc::PrivilegedJunkScope()); } BrowsingContext* bc = aNewInner->GetBrowsingContext(); if (bc->IsTop()) { // We're a toplevel load. Use a new zone. This way, when we do // zone-based compartment sharing we won't share compartments // across navigations. return aOptions.setNewCompartmentAndZone(); } // Find the in-process ancestor highest in the hierarchy. nsGlobalWindowInner* ancestor = nullptr; for (WindowContext* wc = bc->GetParentWindowContext(); wc; wc = wc->GetParentWindowContext()) { if (nsGlobalWindowInner* win = wc->GetInnerWindow()) { ancestor = win; } } // If we have an ancestor window, use its zone. if (ancestor && ancestor->GetGlobalJSObject()) { JS::Zone* zone = JS::GetObjectZone(ancestor->GetGlobalJSObject()); // Now try to find an existing compartment that's same-origin // with our principal. CompartmentFinderState data(aPrincipal); JS_IterateCompartmentsInZone(aCx, zone, &data, FindSameOriginCompartment); if (data.compartment) { return aOptions.setExistingCompartment(data.compartment); } return aOptions.setNewCompartmentInExistingZone( ancestor->GetGlobalJSObject()); } return aOptions.setNewCompartmentAndZone(); } /** * Create a new global object that will be used for an inner window. * Return the native global and an nsISupports 'holder' that can be used * to manage the lifetime of it. */ static nsresult CreateNativeGlobalForInner( JSContext* aCx, nsGlobalWindowInner* aNewInner, nsIURI* aURI, nsIPrincipal* aPrincipal, JS::MutableHandle aGlobal, bool aIsSecureContext, bool aDefineSharedArrayBufferConstructor) { MOZ_ASSERT(aCx); MOZ_ASSERT(aNewInner); MOZ_ASSERT(aPrincipal); // DOMWindow with nsEP is not supported, we have to make sure // no one creates one accidentally. nsCOMPtr nsEP = do_QueryInterface(aPrincipal); MOZ_RELEASE_ASSERT(!nsEP, "DOMWindow with nsEP is not supported"); JS::RealmOptions options; JS::RealmCreationOptions& creationOptions = options.creationOptions(); SelectZone(aCx, aPrincipal, aNewInner, creationOptions); creationOptions.setSecureContext(aIsSecureContext); // Define the SharedArrayBuffer global constructor property only if shared // memory may be used and structured-cloned (e.g. through postMessage). // // When the global constructor property isn't defined, the SharedArrayBuffer // constructor can still be reached through Web Assembly. Omitting the global // property just prevents feature-tests from being misled. See bug 1624266. creationOptions.setDefineSharedArrayBufferConstructor( aDefineSharedArrayBufferConstructor); xpc::InitGlobalObjectOptions(options, aPrincipal); // Determine if we need the Components object. bool needComponents = aPrincipal->IsSystemPrincipal(); uint32_t flags = needComponents ? 0 : xpc::OMIT_COMPONENTS_OBJECT; flags |= xpc::DONT_FIRE_ONNEWGLOBALHOOK; if (!Window_Binding::Wrap(aCx, aNewInner, aNewInner, options, nsJSPrincipals::get(aPrincipal), false, aGlobal) || !xpc::InitGlobalObject(aCx, aGlobal, flags)) { return NS_ERROR_FAILURE; } MOZ_ASSERT(aNewInner->GetWrapperPreserveColor() == aGlobal); // Set the location information for the new global, so that tools like // about:memory may use that information xpc::SetLocationForGlobal(aGlobal, aURI); if (!InitializeLegacyNetscapeObject(aCx, aGlobal)) { return NS_ERROR_FAILURE; } return NS_OK; } nsresult nsGlobalWindowOuter::SetNewDocument(Document* aDocument, nsISupports* aState, bool aForceReuseInnerWindow, WindowGlobalChild* aActor) { MOZ_ASSERT(mDocumentPrincipal == nullptr, "mDocumentPrincipal prematurely set!"); MOZ_ASSERT(mDocumentStoragePrincipal == nullptr, "mDocumentStoragePrincipal prematurely set!"); MOZ_ASSERT(mDocumentPartitionedPrincipal == nullptr, "mDocumentPartitionedPrincipal prematurely set!"); MOZ_ASSERT(aDocument); // Bail out early if we're in process of closing down the window. NS_ENSURE_STATE(!mCleanedUp); NS_ASSERTION(!GetCurrentInnerWindow() || GetCurrentInnerWindow()->GetExtantDoc() == mDoc, "Uh, mDoc doesn't match the current inner window " "document!"); bool wouldReuseInnerWindow = WouldReuseInnerWindow(aDocument); if (aForceReuseInnerWindow && !wouldReuseInnerWindow && mDoc && mDoc->NodePrincipal() != aDocument->NodePrincipal()) { NS_ERROR("Attempted forced inner window reuse while changing principal"); return NS_ERROR_UNEXPECTED; } if (!mBrowsingContext->AncestorsAreCurrent()) { return NS_ERROR_NOT_AVAILABLE; } RefPtr oldDoc = mDoc; MOZ_RELEASE_ASSERT(oldDoc != aDocument); AutoJSAPI jsapi; jsapi.Init(); JSContext* cx = jsapi.cx(); // Check if we're anywhere near the stack limit before we reach the // transplanting code, since it has no good way to handle errors. This uses // the untrusted script limit, which is not strictly necessary since no // actual script should run. if (!js::CheckRecursionLimitConservativeDontReport(cx)) { NS_WARNING("Overrecursion in SetNewDocument"); return NS_ERROR_FAILURE; } if (!mDoc) { // First document load. // Get our private root. If it is equal to us, then we need to // attach our global key bindings that handles browser scrolling // and other browser commands. nsPIDOMWindowOuter* privateRoot = GetPrivateRoot(); if (privateRoot == this) { RootWindowGlobalKeyListener::AttachKeyHandler(mChromeEventHandler); } } MaybeResetWindowName(aDocument); /* No mDocShell means we're already been partially closed down. When that happens, setting status isn't a big requirement, so don't. (Doesn't happen under normal circumstances, but bug 49615 describes a case.) */ nsContentUtils::AddScriptRunner( NewRunnableMethod("nsGlobalWindowOuter::ClearStatus", this, &nsGlobalWindowOuter::ClearStatus)); // Sometimes, WouldReuseInnerWindow() returns true even if there's no inner // window (see bug 776497). Be safe. bool reUseInnerWindow = (aForceReuseInnerWindow || wouldReuseInnerWindow) && GetCurrentInnerWindowInternal(); nsresult rv; // We set mDoc even though this is an outer window to avoid // having to *always* reach into the inner window to find the // document. mDoc = aDocument; nsDocShell::Cast(mDocShell)->MaybeRestoreWindowName(); // We drop the print request for the old document on the floor, it never made // it. We don't close the window here either even if we were asked to. mShouldDelayPrintUntilAfterLoad = true; mDelayedCloseForPrinting = false; mDelayedPrintUntilAfterLoad = false; // Take this opportunity to clear mSuspendedDoc. Our old inner window is now // responsible for unsuspending it. mSuspendedDoc = nullptr; #ifdef DEBUG mLastOpenedURI = aDocument->GetDocumentURI(); #endif RefPtr currentInner = GetCurrentInnerWindowInternal(); if (currentInner && currentInner->mNavigator) { currentInner->mNavigator->OnNavigation(); } RefPtr newInnerWindow; bool createdInnerWindow = false; bool thisChrome = IsChromeWindow(); nsCOMPtr wsh = do_QueryInterface(aState); NS_ASSERTION(!aState || wsh, "What kind of weird state are you giving me here?"); bool doomCurrentInner = false; // Only non-gray (i.e. exposed to JS) objects should be assigned to // newInnerGlobal. JS::Rooted newInnerGlobal(cx); if (reUseInnerWindow) { // We're reusing the current inner window. NS_ASSERTION(!currentInner->IsFrozen(), "We should never be reusing a shared inner window"); newInnerWindow = currentInner; newInnerGlobal = currentInner->GetWrapper(); // We're reusing the inner window, but this still counts as a navigation, // so all expandos and such defined on the outer window should go away. // Force all Xray wrappers to be recomputed. JS::Rooted rootedObject(cx, GetWrapper()); if (!JS_RefreshCrossCompartmentWrappers(cx, rootedObject)) { return NS_ERROR_FAILURE; } // Inner windows are only reused for same-origin principals, but the // principals don't necessarily match exactly. Update the principal on the // realm to match the new document. NB: We don't just call // currentInner->RefreshRealmPrincipals() here because we haven't yet set // its mDoc to aDocument. JS::Realm* realm = js::GetNonCCWObjectRealm(newInnerGlobal); #ifdef DEBUG bool sameOrigin = false; nsIPrincipal* existing = nsJSPrincipals::get(JS::GetRealmPrincipals(realm)); aDocument->NodePrincipal()->Equals(existing, &sameOrigin); MOZ_ASSERT(sameOrigin); #endif JS::SetRealmPrincipals(realm, nsJSPrincipals::get(aDocument->NodePrincipal())); } else { if (aState) { newInnerWindow = wsh->GetInnerWindow(); newInnerGlobal = newInnerWindow->GetWrapper(); } else { newInnerWindow = nsGlobalWindowInner::Create(this, thisChrome, aActor); if (StaticPrefs::dom_timeout_defer_during_load()) { // ensure the initial loading state is known newInnerWindow->SetActiveLoadingState( aDocument->GetReadyStateEnum() == Document::ReadyState::READYSTATE_LOADING); } // The outer window is automatically treated as frozen when we // null out the inner window. As a result, initializing classes // on the new inner won't end up reaching into the old inner // window for classes etc. // // [This happens with Object.prototype when XPConnect creates // a temporary global while initializing classes; the reason // being that xpconnect creates the temp global w/o a parent // and proto, which makes the JS engine look up classes in // cx->globalObject, i.e. this outer window]. mInnerWindow = nullptr; mCreatingInnerWindow = true; // The SharedArrayBuffer global constructor property should not be present // in a fresh global object when shared memory objects aren't allowed // (because COOP/COEP support isn't enabled, or because COOP/COEP don't // act to isolate this page to a separate process). // Every script context we are initialized with must create a // new global. rv = CreateNativeGlobalForInner( cx, newInnerWindow, aDocument->GetDocumentURI(), aDocument->NodePrincipal(), &newInnerGlobal, ComputeIsSecureContext(aDocument), newInnerWindow->IsSharedMemoryAllowed()); NS_ASSERTION( NS_SUCCEEDED(rv) && newInnerGlobal && newInnerWindow->GetWrapperPreserveColor() == newInnerGlobal, "Failed to get script global"); mCreatingInnerWindow = false; createdInnerWindow = true; NS_ENSURE_SUCCESS(rv, rv); } if (currentInner && currentInner->GetWrapperPreserveColor()) { // Don't free objects on our current inner window if it's going to be // held in the bfcache. if (!currentInner->IsFrozen()) { doomCurrentInner = true; } } mInnerWindow = newInnerWindow; MOZ_ASSERT(mInnerWindow); mInnerWindow->TryToCacheTopInnerWindow(); if (!GetWrapperPreserveColor()) { JS::Rooted outer( cx, NewOuterWindowProxy(cx, newInnerGlobal, thisChrome)); NS_ENSURE_TRUE(outer, NS_ERROR_FAILURE); mBrowsingContext->CleanUpDanglingRemoteOuterWindowProxies(cx, &outer); MOZ_ASSERT(js::IsWindowProxy(outer)); js::SetProxyReservedSlot(outer, OUTER_WINDOW_SLOT, JS::PrivateValue(ToSupports(this))); // Inform the nsJSContext, which is the canonical holder of the outer. mContext->SetWindowProxy(outer); SetWrapper(mContext->GetWindowProxy()); } else { JS::Rooted outerObject( cx, NewOuterWindowProxy(cx, newInnerGlobal, thisChrome)); if (!outerObject) { NS_ERROR("out of memory"); return NS_ERROR_FAILURE; } JS::Rooted obj(cx, GetWrapper()); MOZ_ASSERT(js::IsWindowProxy(obj)); js::SetProxyReservedSlot(obj, OUTER_WINDOW_SLOT, JS::PrivateValue(nullptr)); js::SetProxyReservedSlot(outerObject, OUTER_WINDOW_SLOT, JS::PrivateValue(nullptr)); js::SetProxyReservedSlot(obj, HOLDER_WEAKMAP_SLOT, JS::UndefinedValue()); #ifdef NIGHTLY_BUILD outerObject = xpc::TransplantObjectNukingXrayWaiver(cx, obj, outerObject); #else outerObject = xpc::TransplantObject(cx, obj, outerObject); #endif if (!outerObject) { mBrowsingContext->ClearWindowProxy(); NS_ERROR("unable to transplant wrappers, probably OOM"); return NS_ERROR_FAILURE; } js::SetProxyReservedSlot(outerObject, OUTER_WINDOW_SLOT, JS::PrivateValue(ToSupports(this))); SetWrapper(outerObject); MOZ_ASSERT(JS::GetNonCCWObjectGlobal(outerObject) == newInnerGlobal); // Inform the nsJSContext, which is the canonical holder of the outer. mContext->SetWindowProxy(outerObject); } // Enter the new global's realm. JSAutoRealm ar(cx, GetWrapperPreserveColor()); { JS::Rooted outer(cx, GetWrapperPreserveColor()); js::SetWindowProxy(cx, newInnerGlobal, outer); mBrowsingContext->SetWindowProxy(outer); } // Set scriptability based on the state of the docshell. bool allow = GetDocShell()->GetCanExecuteScripts(); xpc::Scriptability::Get(GetWrapperPreserveColor()) .SetDocShellAllowsScript(allow); if (!aState) { // Get the "window" property once so it will be cached on our inner. We // have to do this here, not in binding code, because this has to happen // after we've created the outer window proxy and stashed it in the outer // nsGlobalWindowOuter, so GetWrapperPreserveColor() on that outer // nsGlobalWindowOuter doesn't return null and // nsGlobalWindowOuter::OuterObject works correctly. JS::Rooted unused(cx); if (!JS_GetProperty(cx, newInnerGlobal, "window", &unused)) { NS_ERROR("can't create the 'window' property"); return NS_ERROR_FAILURE; } // And same thing for the "self" property. if (!JS_GetProperty(cx, newInnerGlobal, "self", &unused)) { NS_ERROR("can't create the 'self' property"); return NS_ERROR_FAILURE; } } } JSAutoRealm ar(cx, GetWrapperPreserveColor()); if (!aState && !reUseInnerWindow) { // Loading a new page and creating a new inner window, *not* // restoring from session history. // Now that both the the inner and outer windows are initialized // let the script context do its magic to hook them together. MOZ_ASSERT(mContext->GetWindowProxy() == GetWrapperPreserveColor()); #ifdef DEBUG JS::Rooted rootedJSObject(cx, GetWrapperPreserveColor()); JS::Rooted proto1(cx), proto2(cx); JS_GetPrototype(cx, rootedJSObject, &proto1); JS_GetPrototype(cx, newInnerGlobal, &proto2); NS_ASSERTION(proto1 == proto2, "outer and inner globals should have the same prototype"); #endif mInnerWindow->SyncStateFromParentWindow(); } // Add an extra ref in case we release mContext during GC. nsCOMPtr kungFuDeathGrip(mContext); // Make sure the inner's document is set correctly before we call // SetScriptGlobalObject, because that might try to examine document-dependent // state. Unfortunately, we can't do some of the other clearing/resetting // work we do below until after SetScriptGlobalObject(), because it might // depend on the document having the right scope object. if (aState) { MOZ_RELEASE_ASSERT(newInnerWindow->mDoc == aDocument); } else { if (reUseInnerWindow) { MOZ_RELEASE_ASSERT(newInnerWindow->mDoc != aDocument); } newInnerWindow->mDoc = aDocument; } aDocument->SetScriptGlobalObject(newInnerWindow); MOZ_RELEASE_ASSERT(newInnerWindow->mDoc == aDocument); if (!aState) { if (reUseInnerWindow) { // The storage objects contain the URL of the window. We have to // recreate them when the innerWindow is reused. newInnerWindow->mLocalStorage = nullptr; newInnerWindow->mSessionStorage = nullptr; newInnerWindow->mPerformance = nullptr; // This must be called after nullifying the internal objects because // here we could recreate them, calling the getter methods, and store // them into the JS slots. If we nullify them after, the slot values and // the objects will be out of sync. newInnerWindow->ClearDocumentDependentSlots(cx); } else { newInnerWindow->InitDocumentDependentState(cx); // Initialize DOM classes etc on the inner window. JS::Rooted obj(cx, newInnerGlobal); rv = kungFuDeathGrip->InitClasses(obj); NS_ENSURE_SUCCESS(rv, rv); } // When replacing an initial about:blank document we call // ExecutionReady again to update the client creation URL. rv = newInnerWindow->ExecutionReady(); NS_ENSURE_SUCCESS(rv, rv); if (mArguments) { newInnerWindow->DefineArgumentsProperty(mArguments); mArguments = nullptr; } // Give the new inner window our chrome event handler (since it // doesn't have one). newInnerWindow->mChromeEventHandler = mChromeEventHandler; } if (!aState && reUseInnerWindow) { // Notify our WindowGlobalChild that it has a new document. If `aState` was // passed, we're restoring the window from the BFCache, so the document // hasn't changed. // If we didn't have a window global child before, then initializing // it will have set all the required state, so we don't need to do // it again. mInnerWindow->GetWindowGlobalChild()->OnNewDocument(aDocument); } // Update the current window for our BrowsingContext. RefPtr bc = GetBrowsingContext(); if (bc->IsOwnedByProcess()) { MOZ_ALWAYS_SUCCEEDS(bc->SetCurrentInnerWindowId(mInnerWindow->WindowID())); } // We no longer need the old inner window. Start its destruction if // its not being reused and clear our reference. if (doomCurrentInner) { currentInner->FreeInnerObjects(); } currentInner = nullptr; // We wait to fire the debugger hook until the window is all set up and hooked // up with the outer. See bug 969156. if (createdInnerWindow) { nsContentUtils::AddScriptRunner(NewRunnableMethod( "nsGlobalWindowInner::FireOnNewGlobalObject", newInnerWindow, &nsGlobalWindowInner::FireOnNewGlobalObject)); } if (newInnerWindow && !newInnerWindow->mHasNotifiedGlobalCreated && mDoc) { // We should probably notify. However if this is the, arguably bad, // situation when we're creating a temporary non-chrome-about-blank // document in a chrome docshell, don't notify just yet. Instead wait // until we have a real chrome doc. const bool isContentAboutBlankInChromeDocshell = [&] { if (!mDocShell) { return false; } RefPtr bc = mDocShell->GetBrowsingContext(); if (!bc || bc->GetType() != BrowsingContext::Type::Chrome) { return false; } return !mDoc->NodePrincipal()->IsSystemPrincipal(); }(); if (!isContentAboutBlankInChromeDocshell) { newInnerWindow->mHasNotifiedGlobalCreated = true; nsContentUtils::AddScriptRunner(NewRunnableMethod( "nsGlobalWindowOuter::DispatchDOMWindowCreated", this, &nsGlobalWindowOuter::DispatchDOMWindowCreated)); } } PreloadLocalStorage(); mStorageAccessPermissionGranted = ContentBlocking::ShouldAllowAccessFor( newInnerWindow, aDocument->GetDocumentURI(), nullptr); // Do this here rather than in say the Document constructor, since // we need a WindowContext available. mDoc->InitUseCounters(); return NS_OK; } /* static */ void nsGlobalWindowOuter::PrepareForProcessChange(JSObject* aProxy) { JS::Rooted localProxy(RootingCx(), aProxy); MOZ_ASSERT(js::IsWindowProxy(localProxy)); RefPtr outerWindow = nsOuterWindowProxy::GetOuterWindow(localProxy); if (!outerWindow) { return; } AutoJSAPI jsapi; jsapi.Init(); JSContext* cx = jsapi.cx(); JSAutoRealm ar(cx, localProxy); // Clear out existing references from the browsing context and outer window to // the proxy, and from the proxy to the outer window. These references will // become invalid once the proxy is transplanted. Clearing the window proxy // from the browsing context is also necessary to indicate that it is for an // out of process window. outerWindow->ClearWrapper(localProxy); RefPtr bc = outerWindow->GetBrowsingContext(); MOZ_ASSERT(bc); MOZ_ASSERT(bc->GetWindowProxy() == localProxy); bc->ClearWindowProxy(); js::SetProxyReservedSlot(localProxy, OUTER_WINDOW_SLOT, JS::PrivateValue(nullptr)); js::SetProxyReservedSlot(localProxy, HOLDER_WEAKMAP_SLOT, JS::UndefinedValue()); // Create a new remote outer window proxy, and transplant to it. JS::Rooted remoteProxy(cx); if (!mozilla::dom::GetRemoteOuterWindowProxy(cx, bc, localProxy, &remoteProxy)) { MOZ_CRASH("PrepareForProcessChange GetRemoteOuterWindowProxy"); } if (!xpc::TransplantObjectNukingXrayWaiver(cx, localProxy, remoteProxy)) { MOZ_CRASH("PrepareForProcessChange TransplantObject"); } } void nsGlobalWindowOuter::PreloadLocalStorage() { if (!Storage::StoragePrefIsEnabled()) { return; } if (IsChromeWindow()) { return; } nsIPrincipal* principal = GetPrincipal(); nsIPrincipal* storagePrincipal = GetEffectiveStoragePrincipal(); if (!principal || !storagePrincipal) { return; } nsresult rv; nsCOMPtr storageManager = do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv); if (NS_FAILED(rv)) { return; } // private browsing windows do not persist local storage to disk so we should // only try to precache storage when we're not a private browsing window. if (principal->GetPrivateBrowsingId() == 0) { RefPtr storage; rv = storageManager->PrecacheStorage(principal, storagePrincipal, getter_AddRefs(storage)); if (NS_SUCCEEDED(rv)) { mLocalStorage = storage; } } } void nsGlobalWindowOuter::DispatchDOMWindowCreated() { if (!mDoc) { return; } // Fire DOMWindowCreated at chrome event listeners nsContentUtils::DispatchChromeEvent(mDoc, ToSupports(mDoc), u"DOMWindowCreated"_ns, CanBubble::eYes, Cancelable::eNo); nsCOMPtr observerService = mozilla::services::GetObserverService(); // The event dispatching could possibly cause docshell destory, and // consequently cause mDoc to be set to nullptr by DropOuterWindowDocs(), // so check it again here. if (observerService && mDoc) { nsAutoString origin; nsIPrincipal* principal = mDoc->NodePrincipal(); nsContentUtils::GetUTFOrigin(principal, origin); observerService->NotifyObservers(static_cast(this), principal->IsSystemPrincipal() ? "chrome-document-global-created" : "content-document-global-created", origin.get()); } } void nsGlobalWindowOuter::ClearStatus() { SetStatusOuter(u""_ns); } void nsGlobalWindowOuter::SetDocShell(nsDocShell* aDocShell) { MOZ_ASSERT(aDocShell); if (aDocShell == mDocShell) { return; } mDocShell = aDocShell; mBrowsingContext = aDocShell->GetBrowsingContext(); RefPtr parentContext = mBrowsingContext->GetParent(); MOZ_RELEASE_ASSERT(!parentContext || GetBrowsingContextGroup() == parentContext->Group()); mTopLevelOuterContentWindow = mBrowsingContext->IsTopContent(); // Get our enclosing chrome shell and retrieve its global window impl, so // that we can do some forwarding to the chrome document. RefPtr chromeEventHandler; mDocShell->GetChromeEventHandler(getter_AddRefs(chromeEventHandler)); mChromeEventHandler = chromeEventHandler; if (!mChromeEventHandler) { // We have no chrome event handler. If we have a parent, // get our chrome event handler from the parent. If // we don't have a parent, then we need to make a new // window root object that will function as a chrome event // handler and receive all events that occur anywhere inside // our window. nsCOMPtr parentWindow = GetInProcessParent(); if (parentWindow.get() != this) { mChromeEventHandler = parentWindow->GetChromeEventHandler(); } else { mChromeEventHandler = NS_NewWindowRoot(this); mIsRootOuterWindow = true; } } SetIsBackgroundInternal(!mBrowsingContext->IsActive()); } void nsGlobalWindowOuter::DetachFromDocShell(bool aIsBeingDiscarded) { // DetachFromDocShell means the window is being torn down. Drop our // reference to the script context, allowing it to be deleted // later. Meanwhile, keep our weak reference to the script object // so that it can be retrieved later (until it is finalized by the JS GC). if (mDoc && DocGroup::TryToLoadIframesInBackground()) { DocGroup* docGroup = GetDocGroup(); RefPtr docShell = GetDocShell(); RefPtr dShell = nsDocShell::Cast(docShell); if (dShell) { docGroup->TryFlushIframePostMessages(dShell->GetOuterWindowID()); } } // Call FreeInnerObjects on all inner windows, not just the current // one, since some could be held by WindowStateHolder objects that // are GC-owned. RefPtr inner; for (PRCList* node = PR_LIST_HEAD(this); node != this; node = PR_NEXT_LINK(inner)) { // This cast is safe because `node != this`. Non-this nodes are inner // windows. inner = static_cast(node); MOZ_ASSERT(!inner->mOuterWindow || inner->mOuterWindow == this); inner->FreeInnerObjects(); } // Don't report that we were detached to the nsWindowMemoryReporter, as it // only tracks inner windows. NotifyWindowIDDestroyed("outer-window-destroyed"); nsGlobalWindowInner* currentInner = GetCurrentInnerWindowInternal(); if (currentInner) { NS_ASSERTION(mDoc, "Must have doc!"); // Remember the document's principal and URI. mDocumentPrincipal = mDoc->NodePrincipal(); mDocumentStoragePrincipal = mDoc->EffectiveStoragePrincipal(); mDocumentPartitionedPrincipal = mDoc->PartitionedPrincipal(); mDocumentURI = mDoc->GetDocumentURI(); // Release our document reference DropOuterWindowDocs(); } ClearControllers(); mChromeEventHandler = nullptr; // force release now if (mContext) { // When we're about to destroy a top level content window // (for example a tab), we trigger a full GC by passing null as the last // param. We also trigger a full GC for chrome windows. nsJSContext::PokeGC(JS::GCReason::SET_DOC_SHELL, (mTopLevelOuterContentWindow || mIsChrome) ? nullptr : GetWrapperPreserveColor()); mContext = nullptr; } if (aIsBeingDiscarded) { // If our BrowsingContext is being discarded, make a note that our current // inner window was active at the time it went away. if (GetCurrentInnerWindow()) { GetCurrentInnerWindowInternal()->SetWasCurrentInnerWindow(); } } mDocShell = nullptr; mBrowsingContext->ClearDocShell(); CleanUp(); } void nsGlobalWindowOuter::UpdateParentTarget() { // NOTE: This method is nearly identical to // nsGlobalWindowInner::UpdateParentTarget(). IF YOU UPDATE THIS METHOD, // UPDATE THE OTHER ONE TOO! The one difference is that this method updates // mMessageManager as well, which inner windows don't have. // Try to get our frame element's tab child global (its in-process message // manager). If that fails, fall back to the chrome event handler's tab // child global, and if it doesn't have one, just use the chrome event // handler itself. nsCOMPtr frameElement = GetFrameElementInternal(); mMessageManager = nsContentUtils::TryGetBrowserChildGlobal(frameElement); if (!mMessageManager) { nsGlobalWindowOuter* topWin = GetInProcessScriptableTopInternal(); if (topWin) { frameElement = topWin->GetFrameElementInternal(); mMessageManager = nsContentUtils::TryGetBrowserChildGlobal(frameElement); } } if (!mMessageManager) { mMessageManager = nsContentUtils::TryGetBrowserChildGlobal(mChromeEventHandler); } if (mMessageManager) { mParentTarget = mMessageManager; } else { mParentTarget = mChromeEventHandler; } } EventTarget* nsGlobalWindowOuter::GetTargetForEventTargetChain() { return GetCurrentInnerWindowInternal(); } void nsGlobalWindowOuter::GetEventTargetParent(EventChainPreVisitor& aVisitor) { MOZ_CRASH("The outer window should not be part of an event path"); } bool nsGlobalWindowOuter::ShouldPromptToBlockDialogs() { if (!nsContentUtils::GetCurrentJSContext()) { return false; // non-scripted caller. } nsGlobalWindowOuter* topWindowOuter = GetInProcessScriptableTopInternal(); if (!topWindowOuter) { NS_ASSERTION(!mDocShell, "ShouldPromptToBlockDialogs() called without a top window?"); return true; } nsGlobalWindowInner* topWindow = topWindowOuter->GetCurrentInnerWindowInternal(); if (!topWindow) { return true; } return topWindow->DialogsAreBeingAbused(); } bool nsGlobalWindowOuter::AreDialogsEnabled() { nsGlobalWindowOuter* topWindowOuter = GetInProcessScriptableTopInternal(); if (!topWindowOuter) { NS_ERROR("AreDialogsEnabled() called without a top window?"); return false; } // TODO: Warn if no top window? nsGlobalWindowInner* topWindow = topWindowOuter->GetCurrentInnerWindowInternal(); if (!topWindow) { return false; } // Dialogs are blocked if the content viewer is hidden if (mDocShell) { nsCOMPtr cv; mDocShell->GetContentViewer(getter_AddRefs(cv)); bool isHidden; cv->GetIsHidden(&isHidden); if (isHidden) { return false; } } // Dialogs are also blocked if the document is sandboxed with SANDBOXED_MODALS // (or if we have no document, of course). Which document? Who knows; the // spec is daft. See . For now // just go ahead and check mDoc, since in everything except edge cases in // which a frame is allow-same-origin but not allow-scripts and is being poked // at by some other window this should be the right thing anyway. if (!mDoc || (mDoc->GetSandboxFlags() & SANDBOXED_MODALS)) { return false; } return topWindow->mAreDialogsEnabled; } bool nsGlobalWindowOuter::ConfirmDialogIfNeeded() { NS_ENSURE_TRUE(mDocShell, false); nsCOMPtr promptSvc = do_GetService("@mozilla.org/embedcomp/prompt-service;1"); if (!promptSvc) { return true; } // Reset popup state while opening a modal dialog, and firing events // about the dialog, to prevent the current state from being active // the whole time a modal dialog is open. AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true); bool disableDialog = false; nsAutoString label, title; nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDialogLabel", label); nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDialogPreventTitle", title); promptSvc->Confirm(this, title.get(), label.get(), &disableDialog); if (disableDialog) { DisableDialogs(); return false; } return true; } void nsGlobalWindowOuter::DisableDialogs() { nsGlobalWindowOuter* topWindowOuter = GetInProcessScriptableTopInternal(); if (!topWindowOuter) { NS_ERROR("DisableDialogs() called without a top window?"); return; } nsGlobalWindowInner* topWindow = topWindowOuter->GetCurrentInnerWindowInternal(); // TODO: Warn if no top window? if (topWindow) { topWindow->mAreDialogsEnabled = false; } } void nsGlobalWindowOuter::EnableDialogs() { nsGlobalWindowOuter* topWindowOuter = GetInProcessScriptableTopInternal(); if (!topWindowOuter) { NS_ERROR("EnableDialogs() called without a top window?"); return; } // TODO: Warn if no top window? nsGlobalWindowInner* topWindow = topWindowOuter->GetCurrentInnerWindowInternal(); if (topWindow) { topWindow->mAreDialogsEnabled = true; } } nsresult nsGlobalWindowOuter::PostHandleEvent(EventChainPostVisitor& aVisitor) { MOZ_CRASH("The outer window should not be part of an event path"); } void nsGlobalWindowOuter::PoisonOuterWindowProxy(JSObject* aObject) { if (aObject == GetWrapperMaybeDead()) { PoisonWrapper(); } } nsresult nsGlobalWindowOuter::SetArguments(nsIArray* aArguments) { nsresult rv; // We've now mostly separated them, but the difference is still opaque to // nsWindowWatcher (the caller of SetArguments in this little back-and-forth // embedding waltz we do here). // // So we need to demultiplex the two cases here. nsGlobalWindowInner* currentInner = GetCurrentInnerWindowInternal(); mArguments = aArguments; rv = currentInner->DefineArgumentsProperty(aArguments); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } //***************************************************************************** // nsGlobalWindowOuter::nsIScriptObjectPrincipal //***************************************************************************** nsIPrincipal* nsGlobalWindowOuter::GetPrincipal() { if (mDoc) { // If we have a document, get the principal from the document return mDoc->NodePrincipal(); } if (mDocumentPrincipal) { return mDocumentPrincipal; } // If we don't have a principal and we don't have a document we // ask the parent window for the principal. This can happen when // loading a frameset that has a , in // that case the global window is used in JS before we've loaded // a document into the window. nsCOMPtr objPrincipal = do_QueryInterface(GetInProcessParentInternal()); if (objPrincipal) { return objPrincipal->GetPrincipal(); } return nullptr; } nsIPrincipal* nsGlobalWindowOuter::GetEffectiveStoragePrincipal() { if (mDoc) { // If we have a document, get the principal from the document return mDoc->EffectiveStoragePrincipal(); } if (mDocumentStoragePrincipal) { return mDocumentStoragePrincipal; } // If we don't have a storage principal and we don't have a document we ask // the parent window for the storage principal. nsCOMPtr objPrincipal = do_QueryInterface(GetInProcessParentInternal()); if (objPrincipal) { return objPrincipal->GetEffectiveStoragePrincipal(); } return nullptr; } nsIPrincipal* nsGlobalWindowOuter::PartitionedPrincipal() { if (mDoc) { // If we have a document, get the principal from the document return mDoc->PartitionedPrincipal(); } if (mDocumentPartitionedPrincipal) { return mDocumentPartitionedPrincipal; } // If we don't have a partitioned principal and we don't have a document we // ask the parent window for the partitioned principal. nsCOMPtr objPrincipal = do_QueryInterface(GetInProcessParentInternal()); if (objPrincipal) { return objPrincipal->PartitionedPrincipal(); } return nullptr; } //***************************************************************************** // nsGlobalWindowOuter::nsIDOMWindow //***************************************************************************** void nsPIDOMWindowOuter::SetInitialKeyboardIndicators( UIStateChangeType aShowFocusRings) { MOZ_ASSERT(!GetCurrentInnerWindow()); nsPIDOMWindowOuter* piWin = GetPrivateRoot(); if (!piWin) { return; } MOZ_ASSERT(piWin == this); // only change the flags that have been modified nsCOMPtr windowRoot = do_QueryInterface(mChromeEventHandler); if (!windowRoot) { return; } if (aShowFocusRings != UIStateChangeType_NoChange) { windowRoot->SetShowFocusRings(aShowFocusRings == UIStateChangeType_Set); } nsContentUtils::SetKeyboardIndicatorsOnRemoteChildren(this, aShowFocusRings); } Element* nsPIDOMWindowOuter::GetFrameElementInternal() const { return mFrameElement; } void nsPIDOMWindowOuter::SetFrameElementInternal(Element* aFrameElement) { mFrameElement = aFrameElement; } Navigator* nsGlobalWindowOuter::GetNavigator() { FORWARD_TO_INNER(Navigator, (), nullptr); } nsScreen* nsGlobalWindowOuter::GetScreen() { FORWARD_TO_INNER(GetScreen, (IgnoreErrors()), nullptr); } void nsPIDOMWindowOuter::MaybeActiveMediaComponents() { if (mMediaSuspend != nsISuspendedTypes::SUSPENDED_BLOCK) { return; } MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug, ("nsPIDOMWindowOuter, MaybeActiveMediaComponents, " "resume the window from blocked, this = %p\n", this)); SetMediaSuspend(nsISuspendedTypes::NONE_SUSPENDED); } SuspendTypes nsPIDOMWindowOuter::GetMediaSuspend() const { return mMediaSuspend; } void nsPIDOMWindowOuter::SetMediaSuspend(SuspendTypes aSuspend) { MaybeNotifyMediaResumedFromBlock(aSuspend); mMediaSuspend = aSuspend; RefreshMediaElementsSuspend(aSuspend); } void nsPIDOMWindowOuter::MaybeNotifyMediaResumedFromBlock( SuspendTypes aSuspend) { if (mMediaSuspend == nsISuspendedTypes::SUSPENDED_BLOCK && aSuspend == nsISuspendedTypes::NONE_SUSPENDED) { RefPtr service = AudioChannelService::GetOrCreate(); if (service) { service->NotifyMediaResumedFromBlock(this); } } } bool nsPIDOMWindowOuter::GetAudioMuted() const { BrowsingContext* bc = GetBrowsingContext(); return bc ? bc->Top()->GetMuted() : false; } float nsPIDOMWindowOuter::GetAudioVolume() const { return mAudioVolume; } nsresult nsPIDOMWindowOuter::SetAudioVolume(float aVolume) { if (aVolume < 0.0) { return NS_ERROR_DOM_INDEX_SIZE_ERR; } if (mAudioVolume == aVolume) { return NS_OK; } mAudioVolume = aVolume; RefreshMediaElementsVolume(); return NS_OK; } void nsPIDOMWindowOuter::RefreshMediaElementsVolume() { RefPtr service = AudioChannelService::GetOrCreate(); if (service) { service->RefreshAgentsVolume(this, GetAudioVolume(), GetAudioMuted()); } } void nsPIDOMWindowOuter::RefreshMediaElementsSuspend(SuspendTypes aSuspend) { RefPtr service = AudioChannelService::GetOrCreate(); if (service) { service->RefreshAgentsSuspend(this, aSuspend); } } void nsPIDOMWindowOuter::SetServiceWorkersTestingEnabled(bool aEnabled) { // Devtools should only be setting this on the top level window. Its // ok if devtools clears the flag on clean up of nested windows, though. // It will have no affect. #ifdef DEBUG nsCOMPtr topWindow = GetInProcessScriptableTop(); MOZ_ASSERT_IF(aEnabled, this == topWindow); #endif mServiceWorkersTestingEnabled = aEnabled; } bool nsPIDOMWindowOuter::GetServiceWorkersTestingEnabled() { // Automatically get this setting from the top level window so that nested // iframes get the correct devtools setting. nsCOMPtr topWindow = GetInProcessScriptableTop(); if (!topWindow) { return false; } return topWindow->mServiceWorkersTestingEnabled; } mozilla::dom::BrowsingContextGroup* nsPIDOMWindowOuter::GetBrowsingContextGroup() const { return mBrowsingContext ? mBrowsingContext->Group() : nullptr; } Nullable nsGlobalWindowOuter::GetParentOuter() { BrowsingContext* bc = GetBrowsingContext(); return bc ? bc->GetParent(IgnoreErrors()) : nullptr; } /** * GetInProcessScriptableParent used to be called when a script read * window.parent. Under Fission, that is now handled by * BrowsingContext::GetParent, and the result is a WindowProxyHolder rather than * an actual global window. This method still exists for legacy callers which * relied on the old logic, and require in-process windows. However, it only * works correctly when no out-of-process frames exist between this window and * the top-level window, so it should not be used in new code. * * In contrast to GetRealParent, GetInProcessScriptableParent respects