summaryrefslogtreecommitdiffstats
path: root/dom/base/nsGlobalWindowOuter.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /dom/base/nsGlobalWindowOuter.cpp
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'dom/base/nsGlobalWindowOuter.cpp')
-rw-r--r--dom/base/nsGlobalWindowOuter.cpp7600
1 files changed, 7600 insertions, 0 deletions
diff --git a/dom/base/nsGlobalWindowOuter.cpp b/dom/base/nsGlobalWindowOuter.cpp
new file mode 100644
index 0000000000..1dc31e8c3b
--- /dev/null
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -0,0 +1,7600 @@
+/* -*- 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 <algorithm>
+
+#include "mozilla/MemoryReporting.h"
+
+// Local Includes
+#include "Navigator.h"
+#include "mozilla/Encoding.h"
+#include "nsContentSecurityManager.h"
+#include "nsGlobalWindowOuter.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/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/HTMLIFrameElement.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/ProxyHandlerUtils.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"
+#include "mozilla/StorageAccessAPIHelper.h"
+#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/CallAndConstruct.h" // JS::Call
+#include "js/friend/StackLimits.h" // js::AutoCheckRecursionLimit
+#include "js/friend/WindowProxy.h" // js::IsWindowProxy, js::SetWindowProxy
+#include "js/PropertyAndElement.h" // JS_DefineObject, JS_GetProperty
+#include "js/PropertySpec.h"
+#include "js/RealmIterators.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/MouseEvents.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProcessHangMonitor.h"
+#include "mozilla/StaticPrefs_dom.h"
+#include "mozilla/StaticPrefs_full_screen_api.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 "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 "mozilla/extensions/WebExtensionPolicy.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 "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/NavigatorBinding.h"
+#include "mozilla/dom/ImageBitmap.h"
+#include "mozilla/dom/ImageBitmapBinding.h"
+#include "mozilla/dom/ServiceWorkerRegistration.h"
+#include "mozilla/dom/WebIDLGlobalNameHash.h"
+#include "mozilla/dom/Worklet.h"
+#include "AccessCheck.h"
+
+#ifdef MOZ_WEBSPEECH
+# include "mozilla/dom/SpeechSynthesis.h"
+#endif
+
+#ifdef ANDROID
+# include <android/log.h>
+#endif
+
+#ifdef XP_WIN
+# include <process.h>
+# define getpid _getpid
+#else
+# include <unistd.h> // for getpid()
+#endif
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::dom::ipc;
+using mozilla::BasePrincipal;
+using mozilla::OriginAttributes;
+using mozilla::TimeStamp;
+using mozilla::layout::RemotePrintJobChild;
+
+#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<Document> 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<js::Wrapper> {
+ using Base = MaybeCrossOriginObject<js::Wrapper>;
+
+ 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<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const override;
+
+ /*
+ * Implementation of the same-origin case of
+ * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty>.
+ */
+ bool definePropertySameOrigin(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> 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<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> 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<JSObject*> proxy, JS::Handle<jsid> 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<JSObject*> proxy, JS::Handle<jsid> id,
+ bool* bp) const override;
+
+ /**
+ * Implementation of [[Get]] internal method as defined at
+ * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-get>.
+ *
+ * "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<JSObject*> proxy,
+ JS::Handle<JS::Value> receiver, JS::Handle<jsid> id,
+ JS::MutableHandle<JS::Value> vp) const override;
+
+ /**
+ * Implementation of [[Set]] internal method as defined at
+ * <https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-set>.
+ *
+ * "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<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::Handle<JS::Value> v, JS::Handle<JS::Value> 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<JSObject*> proxy, JS::Handle<jsid> 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<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> props) const override;
+
+ /**
+ * Hook used by SpiderMonkey to implement Object.prototype.toString.
+ */
+ const char* className(JSContext* cx,
+ JS::Handle<JSObject*> wrapper) const override;
+
+ void finalize(JS::GCContext* gcx, 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<nsISupports*>(
+ 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<JSObject*> proxy,
+ JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp,
+ bool& found) const;
+
+ // Returns a non-null window only if id is an index and we have a
+ // window at that index.
+ Nullable<WindowProxyHolder> GetSubframeWindow(JSContext* cx,
+ JS::Handle<JSObject*> proxy,
+ JS::Handle<jsid> id) const;
+
+ bool AppendIndexedPropertyNames(JSObject* proxy,
+ JS::MutableHandleVector<jsid> props) const;
+
+ using MaybeCrossOriginObjectMixins::EnsureHolder;
+ bool EnsureHolder(JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandle<JSObject*> 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<JSObject*> proxy,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> 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<nsIPrincipal> GetNoPDFJSPrincipal(
+ nsGlobalWindowInner* inner);
+};
+
+const char* nsOuterWindowProxy::className(JSContext* cx,
+ JS::Handle<JSObject*> proxy) const {
+ MOZ_ASSERT(js::IsProxy(proxy));
+
+ if (!IsPlatformObjectSameOrigin(cx, proxy)) {
+ return "Object";
+ }
+
+ return "Window";
+}
+
+void nsOuterWindowProxy::finalize(JS::GCContext* gcx, 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);
+ }
+}
+
+bool nsOuterWindowProxy::getOwnPropertyDescriptor(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) const {
+ // First check for indexed access. This is
+ // https://html.spec.whatwg.org/multipage/window-object.html#windowproxy-getownproperty
+ // step 2, mostly.
+ JS::Rooted<JS::Value> subframe(cx);
+ bool found;
+ if (!GetSubframeWindow(cx, proxy, id, &subframe, found)) {
+ return false;
+ }
+ if (found) {
+ // Step 2.4.
+
+ desc.set(Some(JS::PropertyDescriptor::Data(
+ subframe, {
+ JS::PropertyAttribute::Configurable,
+ JS::PropertyAttribute::Enumerable,
+ })));
+ 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() && id.isAtom()) {
+ 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;
+ }
+
+#if 0
+ // See https://github.com/tc39/ecma262/issues/672 for more information.
+ if (desc.isSome() &&
+ !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.isSome()) {
+ 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.isSome()) {
+ return true;
+ }
+ }
+
+ // Step 6 -- check for named subframes.
+ if (id.isString()) {
+ nsAutoJSString name;
+ if (!name.init(cx, id.toString())) {
+ return false;
+ }
+ nsGlobalWindowOuter* win = GetOuterWindow(proxy);
+ if (RefPtr<BrowsingContext> childDOMWin = win->GetChildWindow(name)) {
+ JS::Rooted<JS::Value> childValue(cx);
+ if (!ToJSValue(cx, WindowProxyHolder(childDOMWin), &childValue)) {
+ return false;
+ }
+ desc.set(Some(JS::PropertyDescriptor::Data(
+ childValue, {JS::PropertyAttribute::Configurable})));
+ return true;
+ }
+ }
+
+ // And step 7.
+ return CrossOriginPropertyFallback(cx, proxy, id, desc);
+}
+
+bool nsOuterWindowProxy::definePropertySameOrigin(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id,
+ JS::Handle<JS::PropertyDescriptor> 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<Maybe<JS::PropertyDescriptor>> existingDesc(cx);
+ ok = js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, &existingDesc);
+ if (!ok) {
+ return false;
+ }
+ if (existingDesc.isNothing() || 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<JS::PropertyDescriptor> 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;
+ }
+ }
+
+#if 0
+ // See https://github.com/tc39/ecma262/issues/672 for more information.
+ 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<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> 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<jsid> 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<JSObject*> holder(cx);
+ if (!EnsureHolder(cx, proxy, &holder)) {
+ return false;
+ }
+
+ JS::RootedVector<jsid> 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<nsIPrincipal> targetPrincipal = GetNoPDFJSPrincipal(inner);
+ if (targetPrincipal &&
+ nsContentUtils::SubjectPrincipal(cx)->Equals(targetPrincipal)) {
+ JS::RootedVector<jsid> 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<JSObject*> proxy,
+ JS::Handle<jsid> 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<JSObject*> proxy,
+ JS::Handle<jsid> 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<JSObject*> proxy,
+ JS::Handle<jsid> 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<JSObject*> proxy,
+ JS::Handle<JS::Value> receiver,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<JS::Value> 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() && id.isAtom()) {
+ 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<JS::Value> 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<JSObject*> proxy,
+ JS::Handle<jsid> id, JS::Handle<JS::Value> v,
+ JS::Handle<JS::Value> 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<JS::Value> wrappedArg(cx, v);
+ if (!MaybeWrapValue(cx, &wrappedArg)) {
+ return false;
+ }
+ JS::Rooted<JS::Value> 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<JSObject*> proxy,
+ JS::MutableHandleVector<jsid> 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<jsid> 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<JSObject*> proxy,
+ JS::Handle<jsid> id,
+ JS::MutableHandle<JS::Value> vp,
+ bool& found) const {
+ Nullable<WindowProxyHolder> frame = GetSubframeWindow(cx, proxy, id);
+ if (frame.IsNull()) {
+ found = false;
+ return true;
+ }
+
+ found = true;
+ return WrapObject(cx, frame.Value(), vp);
+}
+
+Nullable<WindowProxyHolder> nsOuterWindowProxy::GetSubframeWindow(
+ JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> 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<jsid> 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(JS::PropertyKey::Int(i))) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+bool nsOuterWindowProxy::EnsureHolder(
+ JSContext* cx, JS::Handle<JSObject*> proxy,
+ JS::MutableHandle<JSObject*> 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<JSObject*> proxy,
+ JS::MutableHandle<Maybe<JS::PropertyDescriptor>> desc) {
+ MOZ_ASSERT(proxy);
+ MOZ_ASSERT(!desc.isSome());
+
+ nsGlobalWindowOuter* outer = GetOuterWindow(proxy);
+ nsGlobalWindowInner* inner =
+ nsGlobalWindowInner::Cast(outer->GetCurrentInnerWindow());
+ if (!inner) {
+ // No print method to expose.
+ return true;
+ }
+
+ nsCOMPtr<nsIPrincipal> 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<JSObject*> innerObj(cx, inner->GetGlobalJSObject());
+ if (!innerObj) {
+ // Really should not happen, but ok, let's just return.
+ return true;
+ }
+
+ JS::Rooted<JS::Value> 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<JSObject*> funObj(cx, JS_GetFunctionObject(fun));
+ js::SetFunctionNativeReserved(funObj, PDFJS_SLOT_CALLEE, targetFunc);
+
+ // { value: <print>, writable: true, enumerable: true, configurable: true }
+ // because that's what it would have been in the same-origin case without
+ // the PDF viewer messing with things.
+ desc.set(Some(JS::PropertyDescriptor::Data(
+ JS::ObjectValue(*funObj),
+ {JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Enumerable,
+ JS::PropertyAttribute::Writable})));
+ return true;
+}
+
+// static
+bool nsOuterWindowProxy::PDFJSPrintMethod(JSContext* cx, unsigned argc,
+ JS::Value* vp) {
+ JS::CallArgs args = CallArgsFromVp(argc, vp);
+
+ JS::Rooted<JSObject*> 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<JS::Value> 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<JSObject*> 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<JSObject*> 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<nsIPrincipal> 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<nsIPrincipal> nsOuterWindowProxy::GetNoPDFJSPrincipal(
+ nsGlobalWindowInner* inner) {
+ if (!nsContentUtils::IsPDFJS(inner->GetPrincipal())) {
+ return nullptr;
+ }
+
+ if (Document* doc = inner->GetExtantDoc()) {
+ if (nsCOMPtr<nsIPropertyBag2> propBag =
+ do_QueryInterface(doc->GetChannel())) {
+ nsCOMPtr<nsIPrincipal> principal(
+ do_GetProperty(propBag, u"noPDFJSPrincipal"_ns));
+ return principal.forget();
+ }
+ }
+ return nullptr;
+}
+
+const nsOuterWindowProxy nsOuterWindowProxy::singleton;
+
+class nsChromeOuterWindowProxy : public nsOuterWindowProxy {
+ public:
+ constexpr nsChromeOuterWindowProxy() : nsOuterWindowProxy() {}
+
+ const char* className(JSContext* cx,
+ JS::Handle<JSObject*> wrapper) const override;
+
+ static const nsChromeOuterWindowProxy singleton;
+};
+
+const char* nsChromeOuterWindowProxy::className(
+ JSContext* cx, JS::Handle<JSObject*> proxy) const {
+ MOZ_ASSERT(js::IsProxy(proxy));
+
+ return "ChromeWindow";
+}
+
+const nsChromeOuterWindowProxy nsChromeOuterWindowProxy::singleton;
+
+static JSObject* NewOuterWindowProxy(JSContext* cx,
+ JS::Handle<JSObject*> global,
+ bool isChrome) {
+ MOZ_ASSERT(JS_IsGlobalObject(global));
+
+ JSAutoRealm ar(cx, global);
+
+ js::WrapperOptions options;
+ options.setClass(&OuterWindowProxyClass);
+ JSObject* obj =
+ js::Wrapper::New(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),
+ mFullscreenHasChangedDuringProcessing(false),
+ mForceFullScreenInWidget(false),
+ mIsClosed(false),
+ mInClose(false),
+ mHavePendingClose(false),
+ mBlockScriptedClosingFlag(false),
+ mWasOffline(false),
+ mCreatingInnerWindow(false),
+ mIsChrome(false),
+ mAllowScriptsToClose(false),
+ mTopLevelOuterContentWindow(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<void*>(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->Contains(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->InsertOrUpdate(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<void*>(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<nsIDeviceSensors> 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;
+ mSuspendedDocs.Clear();
+}
+
+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<nsIController> controller;
+ mControllers->GetControllerAt(count, getter_AddRefs(controller));
+
+ nsCOMPtr<nsIControllerContext> 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(mSuspendedDocs)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentCookiePrincipal)
+ 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(mSuspendedDocs)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal)
+ NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentCookiePrincipal)
+ 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
+//*****************************************************************************
+
+bool nsGlobalWindowOuter::ShouldResistFingerprinting(RFPTarget aTarget) const {
+ if (mDoc) {
+ return mDoc->ShouldResistFingerprinting(aTarget);
+ }
+ return nsContentUtils::ShouldResistFingerprinting(
+ "If we do not have a document then we do not have any context"
+ "to make an informed RFP choice, so we fall back to the global pref",
+ aTarget);
+}
+
+OriginTrials nsGlobalWindowOuter::Trials() const {
+ return mInnerWindow ? nsGlobalWindowInner::Cast(mInnerWindow)->Trials()
+ : OriginTrials();
+}
+
+FontFaceSet* nsGlobalWindowOuter::GetFonts() {
+ if (mDoc) {
+ return mDoc->Fonts();
+ }
+ return nullptr;
+}
+
+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<nsIURI> 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::SetInitialPrincipal(
+ nsIPrincipal* aNewWindowPrincipal, nsIContentSecurityPolicy* aCSP,
+ const Maybe<nsILoadInfo::CrossOriginEmbedderPolicy>& aCOEP) {
+ // 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(aNewWindowPrincipal) ||
+ (aNewWindowPrincipal->IsSystemPrincipal() &&
+ GetBrowsingContext()->IsContent())) {
+ aNewWindowPrincipal = 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() == aNewWindowPrincipal) 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(aNewWindowPrincipal, aNewWindowPrincipal,
+ aCSP, nullptr,
+ /* aIsInitialDocument */ true, aCOEP);
+
+ if (mDoc) {
+ MOZ_ASSERT(mDoc->IsInitialDocument(),
+ "document should be initial document");
+ }
+
+ RefPtr<PresShell> 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<JSObject*> 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).SetWindowAllowsScript(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<nsIPrincipal> 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()) {
+ // If the NullPrincipal has a valid precursor URI we want to use it to
+ // construct the principal otherwise we fall back to the original document
+ // URI.
+ nsCOMPtr<nsIPrincipal> precursorPrin = principal->GetPrecursorPrincipal();
+ nsCOMPtr<nsIURI> uri = precursorPrin ? precursorPrin->GetURI() : nullptr;
+ if (!uri) {
+ 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<JSObject*> aGlobal) {
+ JSAutoRealm ar(aCx, aGlobal);
+
+ // Note: MathJax depends on window.netscape being exposed. See bug 791526.
+ JS::Rooted<JSObject*> 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<CompartmentFinderState*>(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, Document* aDocument,
+ JS::MutableHandle<JSObject*> aGlobal, bool aIsSecureContext,
+ bool aDefineSharedArrayBufferConstructor) {
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aNewInner);
+
+ nsCOMPtr<nsIURI> uri = aDocument->GetDocumentURI();
+ nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal();
+ MOZ_ASSERT(principal);
+
+ // DOMWindow with nsEP is not supported, we have to make sure
+ // no one creates one accidentally.
+ nsCOMPtr<nsIExpandedPrincipal> nsEP = do_QueryInterface(principal);
+ MOZ_RELEASE_ASSERT(!nsEP, "DOMWindow with nsEP is not supported");
+
+ JS::RealmOptions options;
+ JS::RealmCreationOptions& creationOptions = options.creationOptions();
+
+ SelectZone(aCx, principal, 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);
+
+ // TODO(bug 1834744) we will need some way of passing different targets to the
+ // JS engine
+ xpc::InitGlobalObjectOptions(options, principal->IsSystemPrincipal(),
+ aDocument->ShouldResistFingerprinting(
+ RFPTarget::IsAlwaysEnabledForPrecompute));
+
+ // Determine if we need the Components object.
+ bool needComponents = principal->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(principal), 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, uri);
+
+ 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(mDocumentCookiePrincipal == nullptr,
+ "mDocumentCookiePrincipal 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<Document> 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.
+ js::AutoCheckRecursionLimit recursion(cx);
+ if (!recursion.checkConservativeDontReport(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 mSuspendedDocs. Our old inner window is now
+ // responsible for unsuspending it.
+ mSuspendedDocs.Clear();
+
+#ifdef DEBUG
+ mLastOpenedURI = aDocument->GetDocumentURI();
+#endif
+
+ RefPtr<nsGlobalWindowInner> currentInner = GetCurrentInnerWindowInternal();
+
+ if (currentInner && currentInner->mNavigator) {
+ currentInner->mNavigator->OnNavigation();
+ }
+
+ RefPtr<nsGlobalWindowInner> newInnerWindow;
+ bool createdInnerWindow = false;
+
+ bool thisChrome = IsChromeWindow();
+
+ nsCOMPtr<WindowStateHolder> 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<JSObject*> 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<JSObject*> 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, &newInnerGlobal,
+ ComputeIsSecureContext(aDocument),
+ newInnerWindow->IsSharedMemoryAllowedInternal(
+ aDocument->NodePrincipal()));
+ 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<JSObject*> 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<JSObject*> outerObject(
+ cx, NewOuterWindowProxy(cx, newInnerGlobal, thisChrome));
+ if (!outerObject) {
+ NS_ERROR("out of memory");
+ return NS_ERROR_FAILURE;
+ }
+
+ JS::Rooted<JSObject*> 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());
+
+ outerObject = xpc::TransplantObjectNukingXrayWaiver(cx, obj, outerObject);
+
+ 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<JSObject*> outer(cx, GetWrapperPreserveColor());
+ js::SetWindowProxy(cx, newInnerGlobal, outer);
+ mBrowsingContext->SetWindowProxy(outer);
+ }
+
+ // Set scriptability based on the state of the WindowContext.
+ WindowContext* wc = mInnerWindow->GetWindowContext();
+ bool allow =
+ wc ? wc->CanExecuteScripts() : mBrowsingContext->CanExecuteScripts();
+ xpc::Scriptability::Get(GetWrapperPreserveColor())
+ .SetWindowAllowsScript(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<JS::Value> 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<JSObject*> rootedJSObject(cx, GetWrapperPreserveColor());
+ JS::Rooted<JSObject*> 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<nsIScriptContext> 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 StorageAccess state may have changed. Invalidate the cached
+ // StorageAllowed field, so that the next call to StorageAllowedForWindow
+ // recomputes it.
+ newInnerWindow->ClearStorageAllowedCache();
+
+ // 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<JSObject*> 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<BrowsingContext> 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<BrowsingContext> 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();
+
+ // 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<JSObject*> localProxy(RootingCx(), aProxy);
+ MOZ_ASSERT(js::IsWindowProxy(localProxy));
+
+ RefPtr<nsGlobalWindowOuter> 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<BrowsingContext> 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<JSObject*> 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<nsIDOMStorageManager> 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> 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<nsIObserverService> 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<nsIDOMWindow*>(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<BrowsingContext> 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<EventTarget> 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<nsPIDOMWindowOuter> 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<nsIDocShell> docShell = GetDocShell();
+ RefPtr<nsDocShell> 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<nsGlobalWindowInner> 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<nsGlobalWindowInner*>(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();
+ mDocumentCookiePrincipal = mDoc->EffectiveCookiePrincipal();
+ 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<Element> 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.
+ }
+
+ BrowsingContextGroup* group = GetBrowsingContextGroup();
+ if (!group) {
+ return true;
+ }
+
+ return group->DialogsAreBeingAbused();
+}
+
+bool nsGlobalWindowOuter::AreDialogsEnabled() {
+ BrowsingContextGroup* group = mBrowsingContext->Group();
+ if (!group) {
+ NS_ERROR("AreDialogsEnabled() called without a browsing context group?");
+ return false;
+ }
+
+ // Dialogs are blocked if the content viewer is hidden
+ if (mDocShell) {
+ nsCOMPtr<nsIContentViewer> 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 <https://github.com/whatwg/html/issues/1206>. 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 group->GetAreDialogsEnabled();
+}
+
+bool nsGlobalWindowOuter::ConfirmDialogIfNeeded() {
+ NS_ENSURE_TRUE(mDocShell, false);
+ nsCOMPtr<nsIPromptService> promptSvc =
+ do_GetService("@mozilla.org/prompter;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() {
+ BrowsingContextGroup* group = mBrowsingContext->Group();
+ if (!group) {
+ NS_ERROR("DisableDialogs() called without a browsing context group?");
+ return;
+ }
+
+ if (group) {
+ group->SetAreDialogsEnabled(false);
+ }
+}
+
+void nsGlobalWindowOuter::EnableDialogs() {
+ BrowsingContextGroup* group = mBrowsingContext->Group();
+ if (!group) {
+ NS_ERROR("EnableDialogs() called without a browsing context group?");
+ return;
+ }
+
+ if (group) {
+ group->SetAreDialogsEnabled(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 <frame src="javascript:xxx">, in
+ // that case the global window is used in JS before we've loaded
+ // a document into the window.
+
+ nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
+ do_QueryInterface(GetInProcessParentInternal());
+
+ if (objPrincipal) {
+ return objPrincipal->GetPrincipal();
+ }
+
+ return nullptr;
+}
+
+nsIPrincipal* nsGlobalWindowOuter::GetEffectiveCookiePrincipal() {
+ if (mDoc) {
+ // If we have a document, get the principal from the document
+ return mDoc->EffectiveCookiePrincipal();
+ }
+
+ if (mDocumentCookiePrincipal) {
+ return mDocumentCookiePrincipal;
+ }
+
+ // If we don't have a cookie principal and we don't have a document we ask
+ // the parent window for the cookie principal.
+
+ nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
+ do_QueryInterface(GetInProcessParentInternal());
+
+ if (objPrincipal) {
+ return objPrincipal->GetEffectiveCookiePrincipal();
+ }
+
+ 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<nsIScriptObjectPrincipal> 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<nsIScriptObjectPrincipal> objPrincipal =
+ do_QueryInterface(GetInProcessParentInternal());
+
+ if (objPrincipal) {
+ return objPrincipal->PartitionedPrincipal();
+ }
+
+ return nullptr;
+}
+
+//*****************************************************************************
+// nsGlobalWindowOuter::nsIDOMWindow
+//*****************************************************************************
+
+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::ActivateMediaComponents() {
+ if (!ShouldDelayMediaFromStart()) {
+ return;
+ }
+ MOZ_LOG(AudioChannelService::GetAudioChannelLog(), LogLevel::Debug,
+ ("nsPIDOMWindowOuter, ActiveMediaComponents, "
+ "no longer to delay media from start, this = %p\n",
+ this));
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ Unused << bc->Top()->SetShouldDelayMediaFromStart(false);
+ }
+ NotifyResumingDelayedMedia();
+}
+
+bool nsPIDOMWindowOuter::ShouldDelayMediaFromStart() const {
+ BrowsingContext* bc = GetBrowsingContext();
+ return bc && bc->Top()->GetShouldDelayMediaFromStart();
+}
+
+void nsPIDOMWindowOuter::NotifyResumingDelayedMedia() {
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ if (service) {
+ service->NotifyResumingDelayedMedia(this);
+ }
+}
+
+bool nsPIDOMWindowOuter::GetAudioMuted() const {
+ BrowsingContext* bc = GetBrowsingContext();
+ return bc && bc->Top()->GetMuted();
+}
+
+void nsPIDOMWindowOuter::RefreshMediaElementsVolume() {
+ RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
+ if (service) {
+ // TODO: RefreshAgentsVolume can probably be simplified further.
+ service->RefreshAgentsVolume(this, 1.0f, GetAudioMuted());
+ }
+}
+
+mozilla::dom::BrowsingContextGroup*
+nsPIDOMWindowOuter::GetBrowsingContextGroup() const {
+ return mBrowsingContext ? mBrowsingContext->Group() : nullptr;
+}
+
+Nullable<WindowProxyHolder> 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 <iframe
+ * mozbrowser> boundaries, so if |this| is contained by an <iframe
+ * mozbrowser>, we will return |this| as its own parent.
+ */
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetInProcessScriptableParent() {
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ if (BrowsingContext* parentBC = GetBrowsingContext()->GetParent()) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> parent = parentBC->GetDOMWindow()) {
+ return parent;
+ }
+ }
+ return this;
+}
+
+/**
+ * Behavies identically to GetInProcessScriptableParent extept that it returns
+ * null if GetInProcessScriptableParent would return this window.
+ */
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetInProcessScriptableParentOrNull() {
+ nsPIDOMWindowOuter* parent = GetInProcessScriptableParent();
+ return (nsGlobalWindowOuter::Cast(parent) == this) ? nullptr : parent;
+}
+
+/**
+ * nsPIDOMWindow::GetParent (when called from C++) is just a wrapper around
+ * GetRealParent.
+ */
+already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetInProcessParent() {
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShell> parent;
+ mDocShell->GetSameTypeInProcessParentIgnoreBrowserBoundaries(
+ getter_AddRefs(parent));
+
+ if (parent) {
+ nsCOMPtr<nsPIDOMWindowOuter> win = parent->GetWindow();
+ return win.forget();
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> win(this);
+ return win.forget();
+}
+
+static nsresult GetTopImpl(nsGlobalWindowOuter* aWin, nsIURI* aURIBeingLoaded,
+ nsPIDOMWindowOuter** aTop, bool aScriptable,
+ bool aExcludingExtensionAccessibleContentFrames) {
+ *aTop = nullptr;
+
+ MOZ_ASSERT_IF(aExcludingExtensionAccessibleContentFrames, !aScriptable);
+
+ // Walk up the parent chain.
+
+ nsCOMPtr<nsPIDOMWindowOuter> prevParent = aWin;
+ nsCOMPtr<nsPIDOMWindowOuter> parent = aWin;
+ do {
+ if (!parent) {
+ break;
+ }
+
+ prevParent = parent;
+
+ if (aScriptable) {
+ parent = parent->GetInProcessScriptableParent();
+ } else {
+ parent = parent->GetInProcessParent();
+ }
+
+ if (aExcludingExtensionAccessibleContentFrames) {
+ if (auto* p = nsGlobalWindowOuter::Cast(parent)) {
+ nsGlobalWindowInner* currentInner = p->GetCurrentInnerWindowInternal();
+ nsIURI* uri = prevParent->GetDocumentURI();
+ if (!uri) {
+ // If our parent doesn't have a URI yet, we have a document that is in
+ // the process of being loaded. In that case, our caller is
+ // responsible for passing in the URI for the document that is being
+ // loaded, so we fall back to using that URI here.
+ uri = aURIBeingLoaded;
+ }
+
+ if (currentInner && uri) {
+ // If we find an inner window, we better find the uri for the current
+ // window we're looking at. If we can't find it directly, it is the
+ // responsibility of our caller to provide it to us.
+ MOZ_DIAGNOSTIC_ASSERT(uri);
+
+ // If the new parent has permission to load the current page, we're
+ // at a moz-extension:// frame which has a host permission that allows
+ // it to load the document that we've loaded. In that case, stop at
+ // this frame and consider it the top-level frame.
+ //
+ // Note that it's possible for the set of URIs accepted by
+ // AddonAllowsLoad() to change at runtime, but we don't need to cache
+ // the result of this check, since the important consumer of this code
+ // (which is nsIHttpChannelInternal.topWindowURI) already caches the
+ // result after computing it the first time.
+ if (BasePrincipal::Cast(p->GetPrincipal())
+ ->AddonAllowsLoad(uri, true)) {
+ parent = prevParent;
+ break;
+ }
+ }
+ }
+ }
+
+ } while (parent != prevParent);
+
+ if (parent) {
+ parent.swap(*aTop);
+ }
+
+ return NS_OK;
+}
+
+/**
+ * GetInProcessScriptableTop used to be called when a script read window.top.
+ * Under Fission, that is now handled by BrowsingContext::Top, 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 GetRealTop, GetInProcessScriptableTop respects <iframe
+ * mozbrowser> boundaries. If we encounter a window owned by an <iframe
+ * mozbrowser> while walking up the window hierarchy, we'll stop and return that
+ * window.
+ */
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetInProcessScriptableTop() {
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ GetTopImpl(this, /* aURIBeingLoaded = */ nullptr, getter_AddRefs(window),
+ /* aScriptable = */ true,
+ /* aExcludingExtensionAccessibleContentFrames = */ false);
+ return window.get();
+}
+
+already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::GetInProcessTop() {
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ GetTopImpl(this, /* aURIBeingLoaded = */ nullptr, getter_AddRefs(window),
+ /* aScriptable = */ false,
+ /* aExcludingExtensionAccessibleContentFrames = */ false);
+ return window.forget();
+}
+
+already_AddRefed<nsPIDOMWindowOuter>
+nsGlobalWindowOuter::GetTopExcludingExtensionAccessibleContentFrames(
+ nsIURI* aURIBeingLoaded) {
+ // There is a parent-process equivalent of this in DocumentLoadListener.cpp
+ // GetTopWindowExcludingExtensionAccessibleContentFrames
+ nsCOMPtr<nsPIDOMWindowOuter> window;
+ GetTopImpl(this, aURIBeingLoaded, getter_AddRefs(window),
+ /* aScriptable = */ false,
+ /* aExcludingExtensionAccessibleContentFrames = */ true);
+ return window.forget();
+}
+
+void nsGlobalWindowOuter::GetContentOuter(JSContext* aCx,
+ JS::MutableHandle<JSObject*> aRetval,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ RefPtr<BrowsingContext> content = GetContentInternal(aCallerType, aError);
+ if (aError.Failed()) {
+ return;
+ }
+
+ if (!content) {
+ aRetval.set(nullptr);
+ return;
+ }
+
+ JS::Rooted<JS::Value> val(aCx);
+ if (!ToJSValue(aCx, WindowProxyHolder{content}, &val)) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ MOZ_ASSERT(val.isObjectOrNull());
+ aRetval.set(val.toObjectOrNull());
+}
+
+already_AddRefed<BrowsingContext> nsGlobalWindowOuter::GetContentInternal(
+ CallerType aCallerType, ErrorResult& aError) {
+ // First check for a named frame named "content"
+ if (RefPtr<BrowsingContext> named = GetChildWindow(u"content"_ns)) {
+ return named.forget();
+ }
+
+ // If we're in the parent process, and being called by system code, `content`
+ // should return the current primary content frame (if it's in-process).
+ //
+ // We return `nullptr` if the current primary content frame is out-of-process,
+ // rather than a remote window proxy, as that is the existing behaviour as of
+ // bug 1597437.
+ if (XRE_IsParentProcess() && aCallerType == CallerType::System) {
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
+ if (!treeOwner) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShellTreeItem> primaryContent;
+ treeOwner->GetPrimaryContentShell(getter_AddRefs(primaryContent));
+ if (!primaryContent) {
+ return nullptr;
+ }
+
+ return do_AddRef(primaryContent->GetBrowsingContext());
+ }
+
+ // For legacy untrusted callers we always return the same value as
+ // `window.top`
+ if (mDoc && aCallerType != CallerType::System) {
+ mDoc->WarnOnceAbout(DeprecatedOperations::eWindowContentUntrusted);
+ }
+
+ MOZ_ASSERT(mBrowsingContext->IsContent());
+ return do_AddRef(mBrowsingContext->Top());
+}
+
+nsresult nsGlobalWindowOuter::GetPrompter(nsIPrompt** aPrompt) {
+ if (!mDocShell) return NS_ERROR_FAILURE;
+
+ nsCOMPtr<nsIPrompt> prompter(do_GetInterface(mDocShell));
+ NS_ENSURE_TRUE(prompter, NS_ERROR_NO_INTERFACE);
+
+ prompter.forget(aPrompt);
+ return NS_OK;
+}
+
+bool nsGlobalWindowOuter::GetClosedOuter() {
+ // If someone called close(), or if we don't have a docshell, we're closed.
+ return mIsClosed || !mDocShell;
+}
+
+bool nsGlobalWindowOuter::Closed() { return GetClosedOuter(); }
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::IndexedGetterOuter(
+ uint32_t aIndex) {
+ BrowsingContext* bc = GetBrowsingContext();
+ NS_ENSURE_TRUE(bc, nullptr);
+
+ Span<RefPtr<BrowsingContext>> children = bc->NonSyntheticChildren();
+
+ if (aIndex < children.Length()) {
+ return WindowProxyHolder(children[aIndex]);
+ }
+ return nullptr;
+}
+
+nsIControllers* nsGlobalWindowOuter::GetControllersOuter(ErrorResult& aError) {
+ if (!mControllers) {
+ mControllers = new nsXULControllers();
+ if (!mControllers) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ // Add in the default controller
+ RefPtr<nsBaseCommandController> commandController =
+ nsBaseCommandController::CreateWindowController();
+ if (!commandController) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ mControllers->InsertControllerAt(0, commandController);
+ commandController->SetCommandContext(static_cast<nsIDOMWindow*>(this));
+ }
+
+ return mControllers;
+}
+
+nsresult nsGlobalWindowOuter::GetControllers(nsIControllers** aResult) {
+ FORWARD_TO_INNER(GetControllers, (aResult), NS_ERROR_UNEXPECTED);
+}
+
+already_AddRefed<BrowsingContext>
+nsGlobalWindowOuter::GetOpenerBrowsingContext() {
+ RefPtr<BrowsingContext> opener = GetBrowsingContext()->GetOpener();
+ MOZ_DIAGNOSTIC_ASSERT(!opener ||
+ opener->Group() == GetBrowsingContext()->Group());
+ if (!opener || opener->Group() != GetBrowsingContext()->Group()) {
+ return nullptr;
+ }
+
+ // Catch the case where we're chrome but the opener is not...
+ if (nsContentUtils::LegacyIsCallerChromeOrNativeCode() &&
+ GetPrincipal() == nsContentUtils::GetSystemPrincipal()) {
+ auto* openerWin = nsGlobalWindowOuter::Cast(opener->GetDOMWindow());
+ if (!openerWin ||
+ openerWin->GetPrincipal() != nsContentUtils::GetSystemPrincipal()) {
+ return nullptr;
+ }
+ }
+
+ return opener.forget();
+}
+
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetSameProcessOpener() {
+ if (RefPtr<BrowsingContext> opener = GetOpenerBrowsingContext()) {
+ return opener->GetDOMWindow();
+ }
+ return nullptr;
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetOpenerWindowOuter() {
+ if (RefPtr<BrowsingContext> opener = GetOpenerBrowsingContext()) {
+ return WindowProxyHolder(std::move(opener));
+ }
+ return nullptr;
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetOpener() {
+ return GetOpenerWindowOuter();
+}
+
+void nsGlobalWindowOuter::GetStatusOuter(nsAString& aStatus) {
+ aStatus = mStatus;
+}
+
+void nsGlobalWindowOuter::SetStatusOuter(const nsAString& aStatus) {
+ mStatus = aStatus;
+
+ // We don't support displaying window.status in the UI, so there's nothing
+ // left to do here.
+}
+
+void nsGlobalWindowOuter::GetNameOuter(nsAString& aName) {
+ if (mDocShell) {
+ mDocShell->GetName(aName);
+ }
+}
+
+void nsGlobalWindowOuter::SetNameOuter(const nsAString& aName,
+ mozilla::ErrorResult& aError) {
+ if (mDocShell) {
+ aError = mDocShell->SetName(aName);
+ }
+}
+
+// NOTE: The idea of this function is that it should return the same as
+// nsPresContext::CSSToDeviceScale() if it was in aWindow synchronously. For
+// that, we use the UnscaledDevicePixelsPerCSSPixel() (which contains the device
+// scale and the OS zoom scale) and then account for the browsing context full
+// zoom. See the declaration of this function for context about why this is
+// needed.
+CSSToLayoutDeviceScale nsGlobalWindowOuter::CSSToDevScaleForBaseWindow(
+ nsIBaseWindow* aWindow) {
+ MOZ_ASSERT(aWindow);
+ auto scale = aWindow->UnscaledDevicePixelsPerCSSPixel();
+ if (mBrowsingContext) {
+ scale.scale *= mBrowsingContext->FullZoom();
+ }
+ return scale;
+}
+
+nsresult nsGlobalWindowOuter::GetInnerSize(CSSSize& aSize) {
+ EnsureSizeAndPositionUpToDate();
+
+ NS_ENSURE_STATE(mDocShell);
+
+ RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
+ PresShell* presShell = mDocShell->GetPresShell();
+
+ if (!presContext || !presShell) {
+ aSize = {};
+ return NS_OK;
+ }
+
+ // Whether or not the css viewport has been overridden, we can get the
+ // correct value by looking at the visible area of the presContext.
+ if (RefPtr<nsViewManager> viewManager = presShell->GetViewManager()) {
+ viewManager->FlushDelayedResize();
+ }
+
+ // FIXME: Bug 1598487 - Return the layout viewport instead of the ICB.
+ nsSize viewportSize = presContext->GetVisibleArea().Size();
+ if (presContext->GetDynamicToolbarState() == DynamicToolbarState::Collapsed) {
+ viewportSize =
+ nsLayoutUtils::ExpandHeightForViewportUnits(presContext, viewportSize);
+ }
+
+ aSize = CSSPixel::FromAppUnits(viewportSize);
+
+ if (StaticPrefs::dom_innerSize_rounded()) {
+ aSize.width = std::roundf(aSize.width);
+ aSize.height = std::roundf(aSize.height);
+ }
+
+ return NS_OK;
+}
+
+double nsGlobalWindowOuter::GetInnerWidthOuter(ErrorResult& aError) {
+ CSSSize size;
+ aError = GetInnerSize(size);
+ return size.width;
+}
+
+nsresult nsGlobalWindowOuter::GetInnerWidth(double* aInnerWidth) {
+ FORWARD_TO_INNER(GetInnerWidth, (aInnerWidth), NS_ERROR_UNEXPECTED);
+}
+
+void nsGlobalWindowOuter::SetInnerSize(int32_t aLengthCSSPixels, bool aIsWidth,
+ mozilla::dom::CallerType aCallerType,
+ mozilla::ErrorResult& aError) {
+ if (!mDocShell) {
+ aError.Throw(NS_ERROR_UNEXPECTED);
+ return;
+ }
+
+ CSSIntCoord length(aLengthCSSPixels);
+
+ CheckSecurityWidthAndHeight((aIsWidth ? &length.value : nullptr),
+ (aIsWidth ? nullptr : &length.value),
+ aCallerType);
+
+ RefPtr<PresShell> presShell = mDocShell->GetPresShell();
+
+ // Setting inner size should set the CSS viewport. If the CSS viewport
+ // has been overridden, change the override.
+ if (presShell && presShell->UsesMobileViewportSizing()) {
+ RefPtr<nsPresContext> presContext;
+ presContext = presShell->GetPresContext();
+
+ nsRect shellArea = presContext->GetVisibleArea();
+ if (aIsWidth) {
+ shellArea.width = CSSPixel::ToAppUnits(CSSCoord(length));
+ } else {
+ shellArea.height = CSSPixel::ToAppUnits(CSSCoord(length));
+ }
+
+ SetCSSViewportWidthAndHeight(shellArea.Width(), shellArea.Height());
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ auto scale = CSSToDevScaleForBaseWindow(treeOwnerAsWin);
+ LayoutDeviceIntCoord valueDev = (CSSCoord(length) * scale).Rounded();
+
+ Maybe<LayoutDeviceIntCoord> width, height;
+ if (aIsWidth) {
+ width.emplace(valueDev);
+ } else {
+ height.emplace(valueDev);
+ }
+
+ aError = treeOwnerAsWin->SetDimensions(
+ {DimensionKind::Inner, Nothing(), Nothing(), width, height});
+
+ CheckForDPIChange();
+}
+
+void nsGlobalWindowOuter::SetInnerWidthOuter(double aInnerWidth,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetInnerSize(NSToIntRound(ToZeroIfNonfinite(aInnerWidth)),
+ /* aIsWidth */ true, aCallerType, aError);
+}
+
+double nsGlobalWindowOuter::GetInnerHeightOuter(ErrorResult& aError) {
+ CSSSize size;
+ aError = GetInnerSize(size);
+ return size.height;
+}
+
+nsresult nsGlobalWindowOuter::GetInnerHeight(double* aInnerHeight) {
+ FORWARD_TO_INNER(GetInnerHeight, (aInnerHeight), NS_ERROR_UNEXPECTED);
+}
+
+void nsGlobalWindowOuter::SetInnerHeightOuter(double aInnerHeight,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetInnerSize(NSToIntRound(ToZeroIfNonfinite(aInnerHeight)),
+ /* aIsWidth */ false, aCallerType, aError);
+}
+
+CSSIntSize nsGlobalWindowOuter::GetOuterSize(CallerType aCallerType,
+ ErrorResult& aError) {
+ if (nsIGlobalObject::ShouldResistFingerprinting(aCallerType,
+ RFPTarget::Unknown)) {
+ CSSSize size;
+ aError = GetInnerSize(size);
+ return RoundedToInt(size);
+ }
+
+ // Windows showing documents in RDM panes and any subframes within them
+ // return the simulated device size.
+ if (mDoc) {
+ Maybe<CSSIntSize> deviceSize = GetRDMDeviceSize(*mDoc);
+ if (deviceSize.isSome()) {
+ return *deviceSize;
+ }
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return {};
+ }
+
+ return RoundedToInt(treeOwnerAsWin->GetSize() /
+ CSSToDevScaleForBaseWindow(treeOwnerAsWin));
+}
+
+int32_t nsGlobalWindowOuter::GetOuterWidthOuter(CallerType aCallerType,
+ ErrorResult& aError) {
+ return GetOuterSize(aCallerType, aError).width;
+}
+
+int32_t nsGlobalWindowOuter::GetOuterHeightOuter(CallerType aCallerType,
+ ErrorResult& aError) {
+ return GetOuterSize(aCallerType, aError).height;
+}
+
+void nsGlobalWindowOuter::SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ CheckSecurityWidthAndHeight(aIsWidth ? &aLengthCSSPixels : nullptr,
+ aIsWidth ? nullptr : &aLengthCSSPixels,
+ aCallerType);
+
+ auto scale = CSSToDevScaleForBaseWindow(treeOwnerAsWin);
+ LayoutDeviceIntCoord value =
+ (CSSCoord(CSSIntCoord(aLengthCSSPixels)) * scale).Rounded();
+
+ Maybe<LayoutDeviceIntCoord> width, height;
+ if (aIsWidth) {
+ width.emplace(value);
+ } else {
+ height.emplace(value);
+ }
+
+ aError = treeOwnerAsWin->SetDimensions(
+ {DimensionKind::Outer, Nothing(), Nothing(), width, height});
+
+ CheckForDPIChange();
+}
+
+void nsGlobalWindowOuter::SetOuterWidthOuter(int32_t aOuterWidth,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetOuterSize(aOuterWidth, true, aCallerType, aError);
+}
+
+void nsGlobalWindowOuter::SetOuterHeightOuter(int32_t aOuterHeight,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetOuterSize(aOuterHeight, false, aCallerType, aError);
+}
+
+CSSPoint nsGlobalWindowOuter::ScreenEdgeSlop() {
+ if (NS_WARN_IF(!mDocShell)) {
+ return {};
+ }
+ RefPtr<nsPresContext> pc = mDocShell->GetPresContext();
+ if (NS_WARN_IF(!pc)) {
+ return {};
+ }
+ nsCOMPtr<nsIWidget> widget = GetMainWidget();
+ if (NS_WARN_IF(!widget)) {
+ return {};
+ }
+ LayoutDeviceIntPoint pt = widget->GetScreenEdgeSlop();
+ auto auPoint =
+ LayoutDeviceIntPoint::ToAppUnits(pt, pc->AppUnitsPerDevPixel());
+ return CSSPoint::FromAppUnits(auPoint);
+}
+
+CSSIntPoint nsGlobalWindowOuter::GetScreenXY(CallerType aCallerType,
+ ErrorResult& aError) {
+ // When resisting fingerprinting, always return (0,0)
+ if (nsIGlobalObject::ShouldResistFingerprinting(aCallerType,
+ RFPTarget::Unknown)) {
+ return CSSIntPoint(0, 0);
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return CSSIntPoint(0, 0);
+ }
+
+ LayoutDeviceIntPoint windowPos;
+ aError = treeOwnerAsWin->GetPosition(&windowPos.x.value, &windowPos.y.value);
+
+ RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
+ if (!presContext) {
+ // XXX Fishy LayoutDevice to CSS conversion?
+ return CSSIntPoint(windowPos.x, windowPos.y);
+ }
+
+ nsDeviceContext* context = presContext->DeviceContext();
+ auto windowPosAppUnits = LayoutDeviceIntPoint::ToAppUnits(
+ windowPos, context->AppUnitsPerDevPixel());
+ return CSSIntPoint::FromAppUnitsRounded(windowPosAppUnits);
+}
+
+int32_t nsGlobalWindowOuter::GetScreenXOuter(CallerType aCallerType,
+ ErrorResult& aError) {
+ return GetScreenXY(aCallerType, aError).x;
+}
+
+nsRect nsGlobalWindowOuter::GetInnerScreenRect() {
+ if (!mDocShell) {
+ return nsRect();
+ }
+
+ EnsureSizeAndPositionUpToDate();
+
+ if (!mDocShell) {
+ return nsRect();
+ }
+
+ PresShell* presShell = mDocShell->GetPresShell();
+ if (!presShell) {
+ return nsRect();
+ }
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ if (!rootFrame) {
+ return nsRect();
+ }
+
+ return rootFrame->GetScreenRectInAppUnits();
+}
+
+Maybe<CSSIntSize> nsGlobalWindowOuter::GetRDMDeviceSize(
+ const Document& aDocument) {
+ // RDM device size should reflect the simulated device resolution, and
+ // be independent of any full zoom or resolution zoom applied to the
+ // content. To get this value, we get the "unscaled" browser child size,
+ // and divide by the full zoom. "Unscaled" in this case means unscaled
+ // from device to screen but it has been affected (multiplied) by the
+ // full zoom and we need to compensate for that.
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ // Bug 1576256: This does not work for cross-process subframes.
+ const Document* topInProcessContentDoc =
+ aDocument.GetTopLevelContentDocumentIfSameProcess();
+ BrowsingContext* bc = topInProcessContentDoc
+ ? topInProcessContentDoc->GetBrowsingContext()
+ : nullptr;
+ if (bc && bc->InRDMPane()) {
+ nsIDocShell* docShell = topInProcessContentDoc->GetDocShell();
+ if (docShell) {
+ nsPresContext* presContext = docShell->GetPresContext();
+ if (presContext) {
+ nsCOMPtr<nsIBrowserChild> child = docShell->GetBrowserChild();
+ if (child) {
+ // We intentionally use GetFullZoom here instead of
+ // GetDeviceFullZoom, because the unscaledInnerSize is based
+ // on the full zoom and not the device full zoom (which is
+ // rounded to result in integer device pixels).
+ float zoom = presContext->GetFullZoom();
+ BrowserChild* bc = static_cast<BrowserChild*>(child.get());
+ CSSSize unscaledSize = bc->GetUnscaledInnerSize();
+ return Some(CSSIntSize(gfx::RoundedToInt(unscaledSize / zoom)));
+ }
+ }
+ }
+ }
+ return Nothing();
+}
+
+float nsGlobalWindowOuter::GetMozInnerScreenXOuter(CallerType aCallerType) {
+ // When resisting fingerprinting, always return 0.
+ if (nsIGlobalObject::ShouldResistFingerprinting(aCallerType,
+ RFPTarget::Unknown)) {
+ return 0.0;
+ }
+
+ nsRect r = GetInnerScreenRect();
+ return nsPresContext::AppUnitsToFloatCSSPixels(r.x);
+}
+
+float nsGlobalWindowOuter::GetMozInnerScreenYOuter(CallerType aCallerType) {
+ // Return 0 to prevent fingerprinting.
+ if (nsIGlobalObject::ShouldResistFingerprinting(aCallerType,
+ RFPTarget::Unknown)) {
+ return 0.0;
+ }
+
+ nsRect r = GetInnerScreenRect();
+ return nsPresContext::AppUnitsToFloatCSSPixels(r.y);
+}
+
+void nsGlobalWindowOuter::SetScreenCoord(int32_t aCoordCSSPixels, bool aIsX,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ CheckSecurityLeftAndTop(aIsX ? &aCoordCSSPixels : nullptr,
+ aIsX ? nullptr : &aCoordCSSPixels, aCallerType);
+
+ auto scale = CSSToDevScaleForBaseWindow(treeOwnerAsWin);
+ LayoutDeviceIntCoord coord =
+ (CSSCoord(CSSIntCoord(aCoordCSSPixels)) * scale).Rounded();
+
+ Maybe<LayoutDeviceIntCoord> x, y;
+ if (aIsX) {
+ x.emplace(coord);
+ } else {
+ y.emplace(coord);
+ }
+
+ aError = treeOwnerAsWin->SetDimensions(
+ {DimensionKind::Outer, x, y, Nothing(), Nothing()});
+
+ CheckForDPIChange();
+}
+
+void nsGlobalWindowOuter::SetScreenXOuter(int32_t aScreenX,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetScreenCoord(aScreenX, /* aIsX */ true, aCallerType, aError);
+}
+
+int32_t nsGlobalWindowOuter::GetScreenYOuter(CallerType aCallerType,
+ ErrorResult& aError) {
+ return GetScreenXY(aCallerType, aError).y;
+}
+
+void nsGlobalWindowOuter::SetScreenYOuter(int32_t aScreenY,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ SetScreenCoord(aScreenY, /* aIsX */ false, aCallerType, aError);
+}
+
+// NOTE: Arguments to this function should have values scaled to
+// CSS pixels, not device pixels.
+void nsGlobalWindowOuter::CheckSecurityWidthAndHeight(int32_t* aWidth,
+ int32_t* aHeight,
+ CallerType aCallerType) {
+ if (aCallerType != CallerType::System) {
+ // if attempting to resize the window, hide any open popups
+ nsContentUtils::HidePopupsInDocument(mDoc);
+ }
+
+ // This one is easy. Just ensure the variable is greater than 100;
+ if ((aWidth && *aWidth < 100) || (aHeight && *aHeight < 100)) {
+ // Check security state for use in determing window dimensions
+
+ if (aCallerType != CallerType::System) {
+ // sec check failed
+ if (aWidth && *aWidth < 100) {
+ *aWidth = 100;
+ }
+ if (aHeight && *aHeight < 100) {
+ *aHeight = 100;
+ }
+ }
+ }
+}
+
+// NOTE: Arguments to this function should have values in app units
+void nsGlobalWindowOuter::SetCSSViewportWidthAndHeight(nscoord aInnerWidth,
+ nscoord aInnerHeight) {
+ RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
+
+ nsRect shellArea = presContext->GetVisibleArea();
+ shellArea.SetHeight(aInnerHeight);
+ shellArea.SetWidth(aInnerWidth);
+
+ // FIXME(emilio): This doesn't seem to be ok, this doesn't reflow or
+ // anything... Should go through PresShell::ResizeReflow.
+ //
+ // But I don't think this can be reached by content, as we don't allow to set
+ // inner{Width,Height}.
+ presContext->SetVisibleArea(shellArea);
+}
+
+// NOTE: Arguments to this function should have values scaled to
+// CSS pixels, not device pixels.
+void nsGlobalWindowOuter::CheckSecurityLeftAndTop(int32_t* aLeft, int32_t* aTop,
+ CallerType aCallerType) {
+ // This one is harder. We have to get the screen size and window dimensions.
+
+ // Check security state for use in determing window dimensions
+
+ if (aCallerType != CallerType::System) {
+ // if attempting to move the window, hide any open popups
+ nsContentUtils::HidePopupsInDocument(mDoc);
+
+ if (nsGlobalWindowOuter* rootWindow =
+ nsGlobalWindowOuter::Cast(GetPrivateRoot())) {
+ rootWindow->FlushPendingNotifications(FlushType::Layout);
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+
+ RefPtr<nsScreen> screen = GetScreen();
+
+ if (treeOwnerAsWin && screen) {
+ CSSToLayoutDeviceScale scale = CSSToDevScaleForBaseWindow(treeOwnerAsWin);
+ CSSIntRect winRect =
+ CSSIntRect::Round(treeOwnerAsWin->GetPositionAndSize() / scale);
+
+ // Get the screen dimensions
+ // XXX This should use nsIScreenManager once it's fully fleshed out.
+ int32_t screenLeft = screen->GetAvailLeft(IgnoreErrors());
+ int32_t screenWidth = screen->GetAvailWidth(IgnoreErrors());
+ int32_t screenHeight = screen->GetAvailHeight(IgnoreErrors());
+#if defined(XP_MACOSX)
+ /* The mac's coordinate system is different from the assumed Windows'
+ system. It offsets by the height of the menubar so that a window
+ placed at (0,0) will be entirely visible. Unfortunately that
+ correction is made elsewhere (in Widget) and the meaning of
+ the Avail... coordinates is overloaded. Here we allow a window
+ to be placed at (0,0) because it does make sense to do so.
+ */
+ int32_t screenTop = screen->GetTop(IgnoreErrors());
+#else
+ int32_t screenTop = screen->GetAvailTop(IgnoreErrors());
+#endif
+
+ if (aLeft) {
+ if (screenLeft + screenWidth < *aLeft + winRect.width)
+ *aLeft = screenLeft + screenWidth - winRect.width;
+ if (screenLeft > *aLeft) *aLeft = screenLeft;
+ }
+ if (aTop) {
+ if (screenTop + screenHeight < *aTop + winRect.height)
+ *aTop = screenTop + screenHeight - winRect.height;
+ if (screenTop > *aTop) *aTop = screenTop;
+ }
+ } else {
+ if (aLeft) *aLeft = 0;
+ if (aTop) *aTop = 0;
+ }
+ }
+}
+
+int32_t nsGlobalWindowOuter::GetScrollBoundaryOuter(Side aSide) {
+ FlushPendingNotifications(FlushType::Layout);
+ if (nsIScrollableFrame* sf = GetScrollFrame()) {
+ return nsPresContext::AppUnitsToIntCSSPixels(
+ sf->GetScrollRange().Edge(aSide));
+ }
+ return 0;
+}
+
+CSSPoint nsGlobalWindowOuter::GetScrollXY(bool aDoFlush) {
+ if (aDoFlush) {
+ FlushPendingNotifications(FlushType::Layout);
+ } else {
+ EnsureSizeAndPositionUpToDate();
+ }
+
+ nsIScrollableFrame* sf = GetScrollFrame();
+ if (!sf) {
+ return CSSIntPoint(0, 0);
+ }
+
+ nsPoint scrollPos = sf->GetScrollPosition();
+ if (scrollPos != nsPoint(0, 0) && !aDoFlush) {
+ // Oh, well. This is the expensive case -- the window is scrolled and we
+ // didn't actually flush yet. Repeat, but with a flush, since the content
+ // may get shorter and hence our scroll position may decrease.
+ return GetScrollXY(true);
+ }
+
+ return CSSPoint::FromAppUnits(scrollPos);
+}
+
+double nsGlobalWindowOuter::GetScrollXOuter() { return GetScrollXY(false).x; }
+
+double nsGlobalWindowOuter::GetScrollYOuter() { return GetScrollXY(false).y; }
+
+uint32_t nsGlobalWindowOuter::Length() {
+ BrowsingContext* bc = GetBrowsingContext();
+ return bc ? bc->NonSyntheticChildren().Length() : 0;
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetTopOuter() {
+ BrowsingContext* bc = GetBrowsingContext();
+ return bc ? bc->GetTop(IgnoreErrors()) : nullptr;
+}
+
+already_AddRefed<BrowsingContext> nsGlobalWindowOuter::GetChildWindow(
+ const nsAString& aName) {
+ NS_ENSURE_TRUE(mBrowsingContext, nullptr);
+ NS_ENSURE_TRUE(mInnerWindow, nullptr);
+ NS_ENSURE_TRUE(mInnerWindow->GetWindowGlobalChild(), nullptr);
+
+ return do_AddRef(mBrowsingContext->FindChildWithName(
+ aName, *mInnerWindow->GetWindowGlobalChild()));
+}
+
+bool nsGlobalWindowOuter::DispatchCustomEvent(
+ const nsAString& aEventName, ChromeOnlyDispatch aChromeOnlyDispatch) {
+ bool defaultActionEnabled = true;
+
+ if (aChromeOnlyDispatch == ChromeOnlyDispatch::eYes) {
+ nsContentUtils::DispatchEventOnlyToChrome(
+ mDoc, ToSupports(this), aEventName, CanBubble::eYes, Cancelable::eYes,
+ &defaultActionEnabled);
+ } else {
+ nsContentUtils::DispatchTrustedEvent(mDoc, ToSupports(this), aEventName,
+ CanBubble::eYes, Cancelable::eYes,
+ &defaultActionEnabled);
+ }
+
+ return defaultActionEnabled;
+}
+
+bool nsGlobalWindowOuter::DispatchResizeEvent(const CSSIntSize& aSize) {
+ ErrorResult res;
+ RefPtr<Event> domEvent =
+ mDoc->CreateEvent(u"CustomEvent"_ns, CallerType::System, res);
+ if (res.Failed()) {
+ return false;
+ }
+
+ // We don't init the AutoJSAPI with ourselves because we don't want it
+ // reporting errors to our onerror handlers.
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JSAutoRealm ar(cx, GetWrapperPreserveColor());
+
+ DOMWindowResizeEventDetail detail;
+ detail.mWidth = aSize.width;
+ detail.mHeight = aSize.height;
+ JS::Rooted<JS::Value> detailValue(cx);
+ if (!ToJSValue(cx, detail, &detailValue)) {
+ return false;
+ }
+
+ CustomEvent* customEvent = static_cast<CustomEvent*>(domEvent.get());
+ customEvent->InitCustomEvent(cx, u"DOMWindowResize"_ns,
+ /* aCanBubble = */ true,
+ /* aCancelable = */ true, detailValue);
+
+ domEvent->SetTrusted(true);
+ domEvent->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ nsCOMPtr<EventTarget> target = this;
+ domEvent->SetTarget(target);
+
+ return target->DispatchEvent(*domEvent, CallerType::System, IgnoreErrors());
+}
+
+bool nsGlobalWindowOuter::WindowExists(const nsAString& aName,
+ bool aForceNoOpener,
+ bool aLookForCallerOnJSStack) {
+ MOZ_ASSERT(mDocShell, "Must have docshell");
+
+ if (aForceNoOpener) {
+ return aName.LowerCaseEqualsLiteral("_self") ||
+ aName.LowerCaseEqualsLiteral("_top") ||
+ aName.LowerCaseEqualsLiteral("_parent");
+ }
+
+ if (WindowGlobalChild* wgc = mInnerWindow->GetWindowGlobalChild()) {
+ return wgc->FindBrowsingContextWithName(aName, aLookForCallerOnJSStack);
+ }
+ return false;
+}
+
+already_AddRefed<nsIWidget> nsGlobalWindowOuter::GetMainWidget() {
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+
+ nsCOMPtr<nsIWidget> widget;
+
+ if (treeOwnerAsWin) {
+ treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget));
+ }
+
+ return widget.forget();
+}
+
+nsIWidget* nsGlobalWindowOuter::GetNearestWidget() const {
+ nsIDocShell* docShell = GetDocShell();
+ if (!docShell) {
+ return nullptr;
+ }
+ PresShell* presShell = docShell->GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+ nsIFrame* rootFrame = presShell->GetRootFrame();
+ if (!rootFrame) {
+ return nullptr;
+ }
+ return rootFrame->GetView()->GetNearestWidget(nullptr);
+}
+
+void nsGlobalWindowOuter::SetFullscreenOuter(bool aFullscreen,
+ mozilla::ErrorResult& aError) {
+ aError =
+ SetFullscreenInternal(FullscreenReason::ForFullscreenMode, aFullscreen);
+}
+
+nsresult nsGlobalWindowOuter::SetFullScreen(bool aFullscreen) {
+ return SetFullscreenInternal(FullscreenReason::ForFullscreenMode,
+ aFullscreen);
+}
+
+static void FinishDOMFullscreenChange(Document* aDoc, bool aInDOMFullscreen) {
+ if (aInDOMFullscreen) {
+ // Ask the document to handle any pending DOM fullscreen change.
+ if (!Document::HandlePendingFullscreenRequests(aDoc)) {
+ // If we don't end up having anything in fullscreen,
+ // async request exiting fullscreen.
+ Document::AsyncExitFullscreen(aDoc);
+ }
+ } else {
+ // If the window is leaving fullscreen state, also ask the document
+ // to exit from DOM Fullscreen.
+ Document::ExitFullscreenInDocTree(aDoc);
+ }
+}
+
+struct FullscreenTransitionDuration {
+ // The unit of the durations is millisecond
+ uint16_t mFadeIn = 0;
+ uint16_t mFadeOut = 0;
+ bool IsSuppressed() const { return mFadeIn == 0 && mFadeOut == 0; }
+};
+
+static void GetFullscreenTransitionDuration(
+ bool aEnterFullscreen, FullscreenTransitionDuration* aDuration) {
+ const char* pref = aEnterFullscreen
+ ? "full-screen-api.transition-duration.enter"
+ : "full-screen-api.transition-duration.leave";
+ nsAutoCString prefValue;
+ Preferences::GetCString(pref, prefValue);
+ if (!prefValue.IsEmpty()) {
+ sscanf(prefValue.get(), "%hu%hu", &aDuration->mFadeIn,
+ &aDuration->mFadeOut);
+ }
+}
+
+class FullscreenTransitionTask : public Runnable {
+ public:
+ FullscreenTransitionTask(const FullscreenTransitionDuration& aDuration,
+ nsGlobalWindowOuter* aWindow, bool aFullscreen,
+ nsIWidget* aWidget, nsISupports* aTransitionData)
+ : mozilla::Runnable("FullscreenTransitionTask"),
+ mWindow(aWindow),
+ mWidget(aWidget),
+ mTransitionData(aTransitionData),
+ mDuration(aDuration),
+ mStage(eBeforeToggle),
+ mFullscreen(aFullscreen) {}
+
+ NS_IMETHOD Run() override;
+
+ private:
+ ~FullscreenTransitionTask() override = default;
+
+ /**
+ * The flow of fullscreen transition:
+ *
+ * parent process | child process
+ * ----------------------------------------------------------------
+ *
+ * | request/exit fullscreen
+ * <-----|
+ * BeforeToggle stage |
+ * |
+ * ToggleFullscreen stage *1 |----->
+ * | HandleFullscreenRequests
+ * |
+ * <-----| MozAfterPaint event
+ * AfterToggle stage *2 |
+ * |
+ * End stage |
+ *
+ * Note we also start a timer at *1 so that if we don't get MozAfterPaint
+ * from the child process in time, we continue going to *2.
+ */
+ enum Stage {
+ // BeforeToggle stage happens before we enter or leave fullscreen
+ // state. In this stage, the task triggers the pre-toggle fullscreen
+ // transition on the widget.
+ eBeforeToggle,
+ // ToggleFullscreen stage actually executes the fullscreen toggle,
+ // and wait for the next paint on the content to continue.
+ eToggleFullscreen,
+ // AfterToggle stage happens after we toggle the fullscreen state.
+ // In this stage, the task triggers the post-toggle fullscreen
+ // transition on the widget.
+ eAfterToggle,
+ // End stage is triggered after the final transition finishes.
+ eEnd
+ };
+
+ class Observer final : public nsIObserver, public nsINamed {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSINAMED
+
+ explicit Observer(FullscreenTransitionTask* aTask) : mTask(aTask) {}
+
+ private:
+ ~Observer() = default;
+
+ RefPtr<FullscreenTransitionTask> mTask;
+ };
+
+ static const char* const kPaintedTopic;
+
+ RefPtr<nsGlobalWindowOuter> mWindow;
+ nsCOMPtr<nsIWidget> mWidget;
+ nsCOMPtr<nsITimer> mTimer;
+ nsCOMPtr<nsISupports> mTransitionData;
+
+ TimeStamp mFullscreenChangeStartTime;
+ FullscreenTransitionDuration mDuration;
+ Stage mStage;
+ bool mFullscreen;
+};
+
+const char* const FullscreenTransitionTask::kPaintedTopic =
+ "fullscreen-painted";
+
+NS_IMETHODIMP
+FullscreenTransitionTask::Run() {
+ Stage stage = mStage;
+ mStage = Stage(mStage + 1);
+ if (MOZ_UNLIKELY(mWidget->Destroyed())) {
+ // If the widget has been destroyed before we get here, don't try to
+ // do anything more. Just let it go and release ourselves.
+ NS_WARNING("The widget to fullscreen has been destroyed");
+ mWindow->mIsInFullScreenTransition = false;
+ return NS_OK;
+ }
+ if (stage == eBeforeToggle) {
+ PROFILER_MARKER_UNTYPED("Fullscreen transition start", DOM);
+
+ mWindow->mIsInFullScreenTransition = true;
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
+ obs->NotifyObservers(nullptr, "fullscreen-transition-start", nullptr);
+
+ mWidget->PerformFullscreenTransition(nsIWidget::eBeforeFullscreenToggle,
+ mDuration.mFadeIn, mTransitionData,
+ this);
+ } else if (stage == eToggleFullscreen) {
+ PROFILER_MARKER_UNTYPED("Fullscreen toggle start", DOM);
+ mFullscreenChangeStartTime = TimeStamp::Now();
+ // Toggle the fullscreen state on the widget
+ if (!mWindow->SetWidgetFullscreen(FullscreenReason::ForFullscreenAPI,
+ mFullscreen, mWidget)) {
+ // Fail to setup the widget, call FinishFullscreenChange to
+ // complete fullscreen change directly.
+ mWindow->FinishFullscreenChange(mFullscreen);
+ }
+ // Set observer for the next content paint.
+ nsCOMPtr<nsIObserver> observer = new Observer(this);
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->AddObserver(observer, kPaintedTopic, false);
+ // There are several edge cases where we may never get the paint
+ // notification, including:
+ // 1. the window/tab is closed before the next paint;
+ // 2. the user has switched to another tab before we get here.
+ // Completely fixing those cases seems to be tricky, and since they
+ // should rarely happen, it probably isn't worth to fix. Hence we
+ // simply add a timeout here to ensure we never hang forever.
+ // In addition, if the page is complicated or the machine is less
+ // powerful, layout could take a long time, in which case, staying
+ // in black screen for that long could hurt user experience even
+ // more than exposing an intermediate state.
+ uint32_t timeout =
+ Preferences::GetUint("full-screen-api.transition.timeout", 1000);
+ NS_NewTimerWithObserver(getter_AddRefs(mTimer), observer, timeout,
+ nsITimer::TYPE_ONE_SHOT);
+ } else if (stage == eAfterToggle) {
+ Telemetry::AccumulateTimeDelta(Telemetry::FULLSCREEN_TRANSITION_BLACK_MS,
+ mFullscreenChangeStartTime);
+ mWidget->PerformFullscreenTransition(nsIWidget::eAfterFullscreenToggle,
+ mDuration.mFadeOut, mTransitionData,
+ this);
+ } else if (stage == eEnd) {
+ PROFILER_MARKER_UNTYPED("Fullscreen transition end", DOM);
+
+ mWindow->mIsInFullScreenTransition = false;
+
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ NS_ENSURE_TRUE(obs, NS_ERROR_FAILURE);
+ obs->NotifyObservers(nullptr, "fullscreen-transition-end", nullptr);
+
+ mWidget->CleanupFullscreenTransition();
+ }
+ return NS_OK;
+}
+
+NS_IMPL_ISUPPORTS(FullscreenTransitionTask::Observer, nsIObserver, nsINamed)
+
+NS_IMETHODIMP
+FullscreenTransitionTask::Observer::Observe(nsISupports* aSubject,
+ const char* aTopic,
+ const char16_t* aData) {
+ bool shouldContinue = false;
+ if (strcmp(aTopic, FullscreenTransitionTask::kPaintedTopic) == 0) {
+ nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aSubject));
+ nsCOMPtr<nsIWidget> widget =
+ win ? nsGlobalWindowInner::Cast(win)->GetMainWidget() : nullptr;
+ if (widget == mTask->mWidget) {
+ // The paint notification arrives first. Cancel the timer.
+ mTask->mTimer->Cancel();
+ shouldContinue = true;
+ PROFILER_MARKER_UNTYPED("Fullscreen toggle end", DOM);
+ }
+ } else {
+#ifdef DEBUG
+ MOZ_ASSERT(strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0,
+ "Should only get fullscreen-painted or timer-callback");
+ nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject));
+ MOZ_ASSERT(timer && timer == mTask->mTimer,
+ "Should only trigger this with the timer the task created");
+#endif
+ shouldContinue = true;
+ PROFILER_MARKER_UNTYPED("Fullscreen toggle timeout", DOM);
+ }
+ if (shouldContinue) {
+ nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+ obs->RemoveObserver(this, kPaintedTopic);
+ mTask->mTimer = nullptr;
+ mTask->Run();
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+FullscreenTransitionTask::Observer::GetName(nsACString& aName) {
+ aName.AssignLiteral("FullscreenTransitionTask");
+ return NS_OK;
+}
+
+static bool MakeWidgetFullscreen(nsGlobalWindowOuter* aWindow,
+ FullscreenReason aReason, bool aFullscreen) {
+ nsCOMPtr<nsIWidget> widget = aWindow->GetMainWidget();
+ if (!widget) {
+ return false;
+ }
+
+ FullscreenTransitionDuration duration;
+ bool performTransition = false;
+ nsCOMPtr<nsISupports> transitionData;
+ if (aReason == FullscreenReason::ForFullscreenAPI) {
+ GetFullscreenTransitionDuration(aFullscreen, &duration);
+ if (!duration.IsSuppressed()) {
+ performTransition = widget->PrepareForFullscreenTransition(
+ getter_AddRefs(transitionData));
+ }
+ }
+
+ if (!performTransition) {
+ return aWindow->SetWidgetFullscreen(aReason, aFullscreen, widget);
+ }
+
+ nsCOMPtr<nsIRunnable> task = new FullscreenTransitionTask(
+ duration, aWindow, aFullscreen, widget, transitionData);
+ task->Run();
+ return true;
+}
+
+nsresult nsGlobalWindowOuter::ProcessWidgetFullscreenRequest(
+ FullscreenReason aReason, bool aFullscreen) {
+ mInProcessFullscreenRequest.emplace(aReason, aFullscreen);
+
+ // Prevent chrome documents which are still loading from resizing
+ // the window after we set fullscreen mode.
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ nsCOMPtr<nsIAppWindow> appWin(do_GetInterface(treeOwnerAsWin));
+ if (aFullscreen && appWin) {
+ appWin->SetIntrinsicallySized(false);
+ }
+
+ // Sometimes we don't want the top-level widget to actually go fullscreen:
+ // - in the B2G desktop client, we don't want the emulated screen dimensions
+ // to appear to increase when entering fullscreen mode; we just want the
+ // content to fill the entire client area of the emulator window.
+ // - in FxR Desktop, we don't want fullscreen to take over the monitor, but
+ // instead we want fullscreen to fill the FxR window in the the headset.
+ if (!StaticPrefs::full_screen_api_ignore_widgets() &&
+ !mForceFullScreenInWidget) {
+ if (MakeWidgetFullscreen(this, aReason, aFullscreen)) {
+ // The rest of code for switching fullscreen is in nsGlobalWindowOuter::
+ // FinishFullscreenChange() which will be called after sizemodechange
+ // event is dispatched.
+ return NS_OK;
+ }
+ }
+
+#if defined(NIGHTLY_BUILD) && defined(XP_WIN)
+ if (FxRWindowManager::GetInstance()->IsFxRWindow(mWindowID)) {
+ mozilla::gfx::VRShMem shmem(nullptr, true /*aRequiresMutex*/);
+ shmem.SendFullscreenState(mWindowID, aFullscreen);
+ }
+#endif // NIGHTLY_BUILD && XP_WIN
+ FinishFullscreenChange(aFullscreen);
+ return NS_OK;
+}
+
+nsresult nsGlobalWindowOuter::SetFullscreenInternal(FullscreenReason aReason,
+ bool aFullscreen) {
+ MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(),
+ "Requires safe to run script as it "
+ "may call FinishDOMFullscreenChange");
+
+ NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE);
+
+ MOZ_ASSERT(
+ aReason != FullscreenReason::ForForceExitFullscreen || !aFullscreen,
+ "FullscreenReason::ForForceExitFullscreen can "
+ "only be used with exiting fullscreen");
+
+ // Only chrome can change our fullscreen mode. Otherwise, the state
+ // can only be changed for DOM fullscreen.
+ if (aReason == FullscreenReason::ForFullscreenMode &&
+ !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ return NS_OK;
+ }
+
+ // SetFullscreen needs to be called on the root window, so get that
+ // via the DocShell tree, and if we are not already the root,
+ // call SetFullscreen on that window instead.
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ mDocShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
+ nsCOMPtr<nsPIDOMWindowOuter> window =
+ rootItem ? rootItem->GetWindow() : nullptr;
+ if (!window) return NS_ERROR_FAILURE;
+ if (rootItem != mDocShell)
+ return window->SetFullscreenInternal(aReason, aFullscreen);
+
+ // make sure we don't try to set full screen on a non-chrome window,
+ // which might happen in embedding world
+ if (mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome)
+ return NS_ERROR_FAILURE;
+
+ // FullscreenReason::ForForceExitFullscreen can only be used with exiting
+ // fullscreen
+ MOZ_ASSERT_IF(
+ mFullscreen.isSome(),
+ mFullscreen.value() != FullscreenReason::ForForceExitFullscreen);
+
+ // If we are already in full screen mode, just return, we don't care about the
+ // reason here, because,
+ // - If we are in fullscreen mode due to browser fullscreen mode, requesting
+ // DOM fullscreen does not change anything.
+ // - If we are in fullscreen mode due to DOM fullscreen, requesting browser
+ // fullscreen should not change anything, either. Note that we should not
+ // update reason to ForFullscreenMode, otherwise the subsequent DOM
+ // fullscreen exit will be ignored and user will be confused. And ideally
+ // this should never happen as `window.fullscreen` returns `true` for DOM
+ // fullscreen as well.
+ if (mFullscreen.isSome() == aFullscreen) {
+ // How come we get browser fullscreen request while we are already in DOM
+ // fullscreen?
+ MOZ_ASSERT_IF(aFullscreen && aReason == FullscreenReason::ForFullscreenMode,
+ mFullscreen.value() != FullscreenReason::ForFullscreenAPI);
+ return NS_OK;
+ }
+
+ // Note that although entering DOM fullscreen could also cause
+ // consequential calls to this method, those calls will be skipped
+ // at the condition above.
+ if (aReason == FullscreenReason::ForFullscreenMode) {
+ if (!aFullscreen && mFullscreen &&
+ mFullscreen.value() == FullscreenReason::ForFullscreenAPI) {
+ // If we are exiting fullscreen mode, but we actually didn't
+ // entered browser fullscreen mode, the fullscreen state was only for
+ // the Fullscreen API. Change the reason here so that we can
+ // perform transition for it.
+ aReason = FullscreenReason::ForFullscreenAPI;
+ }
+ } else {
+ // If we are exiting from DOM fullscreen while we initially make
+ // the window fullscreen because of browser fullscreen mode, don't restore
+ // the window. But we still need to exit the DOM fullscreen state.
+ if (!aFullscreen && mFullscreen &&
+ mFullscreen.value() == FullscreenReason::ForFullscreenMode) {
+ // If there is a in-process fullscreen request, FinishDOMFullscreenChange
+ // will be called when the request is finished.
+ if (!mInProcessFullscreenRequest.isSome()) {
+ FinishDOMFullscreenChange(mDoc, false);
+ }
+ return NS_OK;
+ }
+ }
+
+ // Set this before so if widget sends an event indicating its
+ // gone full screen, the state trap above works.
+ if (aFullscreen) {
+ mFullscreen.emplace(aReason);
+ } else {
+ mFullscreen.reset();
+ }
+
+ // If we are in process of fullscreen request, only keep the latest fullscreen
+ // state, we will sync up later while the processing request is finished.
+ if (mInProcessFullscreenRequest.isSome()) {
+ mFullscreenHasChangedDuringProcessing = true;
+ return NS_OK;
+ }
+
+ return ProcessWidgetFullscreenRequest(aReason, aFullscreen);
+}
+
+// Support a per-window, dynamic equivalent of enabling
+// full-screen-api.ignore-widgets
+void nsGlobalWindowOuter::ForceFullScreenInWidget() {
+ MOZ_DIAGNOSTIC_ASSERT(XRE_IsParentProcess());
+
+ mForceFullScreenInWidget = true;
+}
+
+bool nsGlobalWindowOuter::SetWidgetFullscreen(FullscreenReason aReason,
+ bool aIsFullscreen,
+ nsIWidget* aWidget) {
+ MOZ_ASSERT(this == GetInProcessTopInternal(),
+ "Only topmost window should call this");
+ MOZ_ASSERT(!GetFrameElementInternal(), "Content window should not call this");
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+ if (!NS_WARN_IF(!IsChromeWindow())) {
+ if (!NS_WARN_IF(mChromeFields.mFullscreenPresShell)) {
+ if (PresShell* presShell = mDocShell->GetPresShell()) {
+ if (nsRefreshDriver* rd = presShell->GetRefreshDriver()) {
+ mChromeFields.mFullscreenPresShell = do_GetWeakReference(presShell);
+ MOZ_ASSERT(mChromeFields.mFullscreenPresShell);
+ rd->SetIsResizeSuppressed();
+ rd->Freeze();
+ }
+ }
+ }
+ }
+ nsresult rv = aReason == FullscreenReason::ForFullscreenMode
+ ?
+ // If we enter fullscreen for fullscreen mode, we want
+ // the native system behavior.
+ aWidget->MakeFullScreenWithNativeTransition(aIsFullscreen)
+ : aWidget->MakeFullScreen(aIsFullscreen);
+ return NS_SUCCEEDED(rv);
+}
+
+/* virtual */
+void nsGlobalWindowOuter::FullscreenWillChange(bool aIsFullscreen) {
+ if (!mInProcessFullscreenRequest.isSome()) {
+ // If there is no in-process fullscreen request, the fullscreen state change
+ // is triggered from the OS directly, e.g. user use built-in window button
+ // to enter/exit fullscreen on macOS.
+ mInProcessFullscreenRequest.emplace(FullscreenReason::ForFullscreenMode,
+ aIsFullscreen);
+ if (mFullscreen.isSome() != aIsFullscreen) {
+ if (aIsFullscreen) {
+ mFullscreen.emplace(FullscreenReason::ForFullscreenMode);
+ } else {
+ mFullscreen.reset();
+ }
+ } else {
+ // It is possible that FullscreenWillChange is notified with current
+ // fullscreen state, e.g. browser goes into fullscreen when widget
+ // fullscreen is prevented, and then user triggers fullscreen from the OS
+ // directly again.
+ MOZ_ASSERT(StaticPrefs::full_screen_api_ignore_widgets() ||
+ mForceFullScreenInWidget,
+ "This should only happen when widget fullscreen is prevented");
+ }
+ }
+ if (aIsFullscreen) {
+ DispatchCustomEvent(u"willenterfullscreen"_ns, ChromeOnlyDispatch::eYes);
+ } else {
+ DispatchCustomEvent(u"willexitfullscreen"_ns, ChromeOnlyDispatch::eYes);
+ }
+}
+
+/* virtual */
+void nsGlobalWindowOuter::FinishFullscreenChange(bool aIsFullscreen) {
+ mozilla::Maybe<FullscreenRequest> currentInProcessRequest =
+ std::move(mInProcessFullscreenRequest);
+ if (!mFullscreenHasChangedDuringProcessing &&
+ aIsFullscreen != mFullscreen.isSome()) {
+ NS_WARNING("Failed to toggle fullscreen state of the widget");
+ // We failed to make the widget enter fullscreen.
+ // Stop further changes and restore the state.
+ if (!aIsFullscreen) {
+ mFullscreen.reset();
+ } else {
+#ifndef XP_MACOSX
+ MOZ_ASSERT_UNREACHABLE("Failed to exit fullscreen?");
+#endif
+ // Restore fullscreen state with FullscreenReason::ForFullscreenAPI reason
+ // in order to make subsequent DOM fullscreen exit request can exit
+ // browser fullscreen mode.
+ mFullscreen.emplace(FullscreenReason::ForFullscreenAPI);
+ }
+ return;
+ }
+
+ // Note that we must call this to toggle the DOM fullscreen state
+ // of the document before dispatching the "fullscreen" event, so
+ // that the chrome can distinguish between browser fullscreen mode
+ // and DOM fullscreen.
+ FinishDOMFullscreenChange(mDoc, aIsFullscreen);
+
+ // dispatch a "fullscreen" DOM event so that XUL apps can
+ // respond visually if we are kicked into full screen mode
+ DispatchCustomEvent(u"fullscreen"_ns, ChromeOnlyDispatch::eYes);
+
+ if (!NS_WARN_IF(!IsChromeWindow())) {
+ if (RefPtr<PresShell> presShell =
+ do_QueryReferent(mChromeFields.mFullscreenPresShell)) {
+ if (nsRefreshDriver* rd = presShell->GetRefreshDriver()) {
+ rd->Thaw();
+ }
+ mChromeFields.mFullscreenPresShell = nullptr;
+ }
+ }
+
+ // If fullscreen state has changed during processing fullscreen request, we
+ // need to ensure widget matches our latest fullscreen state here.
+ if (mFullscreenHasChangedDuringProcessing) {
+ mFullscreenHasChangedDuringProcessing = false;
+ // Widget doesn't care about the reason that makes it entering/exiting
+ // fullscreen, so here we just need to ensure the fullscreen state is
+ // matched.
+ if (aIsFullscreen != mFullscreen.isSome()) {
+ // If we end up need to exit fullscreen, use the same reason that brings
+ // us into fullscreen mode, so that we will perform the same fullscreen
+ // transistion effect for exiting.
+ ProcessWidgetFullscreenRequest(
+ mFullscreen.isSome() ? mFullscreen.value()
+ : currentInProcessRequest.value().mReason,
+ mFullscreen.isSome());
+ }
+ }
+}
+
+/* virtual */
+void nsGlobalWindowOuter::MacFullscreenMenubarOverlapChanged(
+ mozilla::DesktopCoord aOverlapAmount) {
+ ErrorResult res;
+ RefPtr<Event> domEvent =
+ mDoc->CreateEvent(u"CustomEvent"_ns, CallerType::System, res);
+ if (res.Failed()) {
+ return;
+ }
+
+ AutoJSAPI jsapi;
+ jsapi.Init();
+ JSContext* cx = jsapi.cx();
+ JSAutoRealm ar(cx, GetWrapperPreserveColor());
+
+ JS::Rooted<JS::Value> detailValue(cx);
+ if (!ToJSValue(cx, aOverlapAmount, &detailValue)) {
+ return;
+ }
+
+ CustomEvent* customEvent = static_cast<CustomEvent*>(domEvent.get());
+ customEvent->InitCustomEvent(cx, u"MacFullscreenMenubarRevealUpdate"_ns,
+ /* aCanBubble = */ true,
+ /* aCancelable = */ true, detailValue);
+ domEvent->SetTrusted(true);
+ domEvent->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+
+ nsCOMPtr<EventTarget> target = this;
+ domEvent->SetTarget(target);
+
+ target->DispatchEvent(*domEvent, CallerType::System, IgnoreErrors());
+}
+
+bool nsGlobalWindowOuter::Fullscreen() const {
+ NS_ENSURE_TRUE(mDocShell, mFullscreen.isSome());
+
+ // Get the fullscreen value of the root window, to always have the value
+ // accurate, even when called from content.
+ nsCOMPtr<nsIDocShellTreeItem> rootItem;
+ mDocShell->GetInProcessRootTreeItem(getter_AddRefs(rootItem));
+ if (rootItem == mDocShell) {
+ if (!XRE_IsContentProcess()) {
+ // We are the root window. Return our internal value.
+ return mFullscreen.isSome();
+ }
+ if (nsCOMPtr<nsIWidget> widget = GetNearestWidget()) {
+ // We are in content process, figure out the value from
+ // the sizemode of the puppet widget.
+ return widget->SizeMode() == nsSizeMode_Fullscreen;
+ }
+ return false;
+ }
+
+ nsCOMPtr<nsPIDOMWindowOuter> window = rootItem->GetWindow();
+ NS_ENSURE_TRUE(window, mFullscreen.isSome());
+
+ return nsGlobalWindowOuter::Cast(window)->Fullscreen();
+}
+
+bool nsGlobalWindowOuter::GetFullscreenOuter() { return Fullscreen(); }
+
+bool nsGlobalWindowOuter::GetFullScreen() {
+ FORWARD_TO_INNER(GetFullScreen, (), false);
+}
+
+void nsGlobalWindowOuter::EnsureReflowFlushAndPaint() {
+ NS_ASSERTION(mDocShell,
+ "EnsureReflowFlushAndPaint() called with no "
+ "docshell!");
+
+ if (!mDocShell) return;
+
+ RefPtr<PresShell> presShell = mDocShell->GetPresShell();
+ if (!presShell) {
+ return;
+ }
+
+ // Flush pending reflows.
+ if (mDoc) {
+ mDoc->FlushPendingNotifications(FlushType::Layout);
+ }
+
+ // Unsuppress painting.
+ presShell->UnsuppressPainting();
+}
+
+// static
+void nsGlobalWindowOuter::MakeMessageWithPrincipal(
+ nsAString& aOutMessage, nsIPrincipal* aSubjectPrincipal, bool aUseHostPort,
+ const char* aNullMessage, const char* aContentMessage,
+ const char* aFallbackMessage) {
+ MOZ_ASSERT(aSubjectPrincipal);
+
+ aOutMessage.Truncate();
+
+ // Try to get a host from the running principal -- this will do the
+ // right thing for javascript: and data: documents.
+
+ nsAutoCString contentDesc;
+
+ if (aSubjectPrincipal->GetIsNullPrincipal()) {
+ nsContentUtils::GetLocalizedString(
+ nsContentUtils::eCOMMON_DIALOG_PROPERTIES, aNullMessage, aOutMessage);
+ } else {
+ auto* addonPolicy = BasePrincipal::Cast(aSubjectPrincipal)->AddonPolicy();
+ if (addonPolicy) {
+ nsContentUtils::FormatLocalizedString(
+ aOutMessage, nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
+ aContentMessage, addonPolicy->Name());
+ } else {
+ nsresult rv = NS_ERROR_FAILURE;
+ if (aUseHostPort) {
+ nsCOMPtr<nsIURI> uri = aSubjectPrincipal->GetURI();
+ if (uri) {
+ rv = uri->GetDisplayHostPort(contentDesc);
+ }
+ }
+ if (!aUseHostPort || NS_FAILED(rv)) {
+ rv = aSubjectPrincipal->GetExposablePrePath(contentDesc);
+ }
+ if (NS_SUCCEEDED(rv) && !contentDesc.IsEmpty()) {
+ NS_ConvertUTF8toUTF16 ucsPrePath(contentDesc);
+ nsContentUtils::FormatLocalizedString(
+ aOutMessage, nsContentUtils::eCOMMON_DIALOG_PROPERTIES,
+ aContentMessage, ucsPrePath);
+ }
+ }
+ }
+
+ if (aOutMessage.IsEmpty()) {
+ // We didn't find a host so use the generic heading
+ nsContentUtils::GetLocalizedString(
+ nsContentUtils::eCOMMON_DIALOG_PROPERTIES, aFallbackMessage,
+ aOutMessage);
+ }
+
+ // Just in case
+ if (aOutMessage.IsEmpty()) {
+ NS_WARNING(
+ "could not get ScriptDlgGenericHeading string from string bundle");
+ aOutMessage.AssignLiteral("[Script]");
+ }
+}
+
+bool nsGlobalWindowOuter::CanMoveResizeWindows(CallerType aCallerType) {
+ // When called from chrome, we can avoid the following checks.
+ if (aCallerType != CallerType::System) {
+ // Don't allow scripts to move or resize windows that were not opened by a
+ // script.
+ if (!mBrowsingContext->HadOriginalOpener()) {
+ return false;
+ }
+
+ if (!CanSetProperty("dom.disable_window_move_resize")) {
+ return false;
+ }
+
+ // Ignore the request if we have more than one tab in the window.
+ if (mBrowsingContext->Top()->HasSiblings()) {
+ return false;
+ }
+ }
+
+ if (mDocShell) {
+ bool allow;
+ nsresult rv = mDocShell->GetAllowWindowControl(&allow);
+ if (NS_SUCCEEDED(rv) && !allow) return false;
+ }
+
+ if (nsGlobalWindowInner::sMouseDown &&
+ !nsGlobalWindowInner::sDragServiceDisabled) {
+ nsCOMPtr<nsIDragService> ds =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (ds) {
+ nsGlobalWindowInner::sDragServiceDisabled = true;
+ ds->Suppress();
+ }
+ }
+ return true;
+}
+
+bool nsGlobalWindowOuter::AlertOrConfirm(bool aAlert, const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ // XXX This method is very similar to nsGlobalWindowOuter::Prompt, make
+ // sure any modifications here don't need to happen over there!
+ if (!AreDialogsEnabled()) {
+ // Just silently return. In the case of alert(), the return value is
+ // ignored. In the case of confirm(), returning false is the same thing as
+ // would happen if the user cancels.
+ return false;
+ }
+
+ // 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);
+
+ // Before bringing up the window, unsuppress painting and flush
+ // pending reflows.
+ EnsureReflowFlushAndPaint();
+
+ nsAutoString title;
+ MakeMessageWithPrincipal(title, &aSubjectPrincipal, false,
+ "ScriptDlgNullPrincipalHeading", "ScriptDlgHeading",
+ "ScriptDlgGenericHeading");
+
+ // Remove non-terminating null characters from the
+ // string. See bug #310037.
+ nsAutoString final;
+ nsContentUtils::StripNullChars(aMessage, final);
+ nsContentUtils::PlatformToDOMLineBreaks(final);
+
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> promptFac =
+ do_GetService("@mozilla.org/prompter;1", &rv);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return false;
+ }
+
+ nsCOMPtr<nsIPrompt> prompt;
+ aError =
+ promptFac->GetPrompt(this, NS_GET_IID(nsIPrompt), getter_AddRefs(prompt));
+ if (aError.Failed()) {
+ return false;
+ }
+
+ // Always allow content modal prompts for alert and confirm.
+ if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) {
+ promptBag->SetPropertyAsUint32(u"modalType"_ns,
+ nsIPrompt::MODAL_TYPE_CONTENT);
+ }
+
+ bool result = false;
+ nsAutoSyncOperation sync(mDoc, SyncOperationBehavior::eSuspendInput);
+ if (ShouldPromptToBlockDialogs()) {
+ bool disallowDialog = false;
+ nsAutoString label;
+ MakeMessageWithPrincipal(
+ label, &aSubjectPrincipal, true, "ScriptDialogLabelNullPrincipal",
+ "ScriptDialogLabelContentPrincipal", "ScriptDialogLabelNullPrincipal");
+
+ aError = aAlert
+ ? prompt->AlertCheck(title.get(), final.get(), label.get(),
+ &disallowDialog)
+ : prompt->ConfirmCheck(title.get(), final.get(), label.get(),
+ &disallowDialog, &result);
+
+ if (disallowDialog) {
+ DisableDialogs();
+ }
+ } else {
+ aError = aAlert ? prompt->Alert(title.get(), final.get())
+ : prompt->Confirm(title.get(), final.get(), &result);
+ }
+
+ return result;
+}
+
+void nsGlobalWindowOuter::AlertOuter(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ AlertOrConfirm(/* aAlert = */ true, aMessage, aSubjectPrincipal, aError);
+}
+
+bool nsGlobalWindowOuter::ConfirmOuter(const nsAString& aMessage,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ return AlertOrConfirm(/* aAlert = */ false, aMessage, aSubjectPrincipal,
+ aError);
+}
+
+void nsGlobalWindowOuter::PromptOuter(const nsAString& aMessage,
+ const nsAString& aInitial,
+ nsAString& aReturn,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ // XXX This method is very similar to nsGlobalWindowOuter::AlertOrConfirm,
+ // make sure any modifications here don't need to happen over there!
+ SetDOMStringToNull(aReturn);
+
+ if (!AreDialogsEnabled()) {
+ // Return null, as if the user just canceled the prompt.
+ return;
+ }
+
+ // 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);
+
+ // Before bringing up the window, unsuppress painting and flush
+ // pending reflows.
+ EnsureReflowFlushAndPaint();
+
+ nsAutoString title;
+ MakeMessageWithPrincipal(title, &aSubjectPrincipal, false,
+ "ScriptDlgNullPrincipalHeading", "ScriptDlgHeading",
+ "ScriptDlgGenericHeading");
+
+ // Remove non-terminating null characters from the
+ // string. See bug #310037.
+ nsAutoString fixedMessage, fixedInitial;
+ nsContentUtils::StripNullChars(aMessage, fixedMessage);
+ nsContentUtils::PlatformToDOMLineBreaks(fixedMessage);
+ nsContentUtils::StripNullChars(aInitial, fixedInitial);
+
+ nsresult rv;
+ nsCOMPtr<nsIPromptFactory> promptFac =
+ do_GetService("@mozilla.org/prompter;1", &rv);
+ if (NS_FAILED(rv)) {
+ aError.Throw(rv);
+ return;
+ }
+
+ nsCOMPtr<nsIPrompt> prompt;
+ aError =
+ promptFac->GetPrompt(this, NS_GET_IID(nsIPrompt), getter_AddRefs(prompt));
+ if (aError.Failed()) {
+ return;
+ }
+
+ // Always allow content modal prompts for prompt.
+ if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) {
+ promptBag->SetPropertyAsUint32(u"modalType"_ns,
+ nsIPrompt::MODAL_TYPE_CONTENT);
+ }
+
+ // Pass in the default value, if any.
+ char16_t* inoutValue = ToNewUnicode(fixedInitial);
+ bool disallowDialog = false;
+
+ nsAutoString label;
+ label.SetIsVoid(true);
+ if (ShouldPromptToBlockDialogs()) {
+ nsContentUtils::GetLocalizedString(
+ nsContentUtils::eCOMMON_DIALOG_PROPERTIES, "ScriptDialogLabel", label);
+ }
+
+ nsAutoSyncOperation sync(mDoc, SyncOperationBehavior::eSuspendInput);
+ bool ok;
+ aError = prompt->Prompt(title.get(), fixedMessage.get(), &inoutValue,
+ label.IsVoid() ? nullptr : label.get(),
+ &disallowDialog, &ok);
+
+ if (disallowDialog) {
+ DisableDialogs();
+ }
+
+ // XXX Doesn't this leak inoutValue?
+ if (aError.Failed()) {
+ return;
+ }
+
+ nsString outValue;
+ outValue.Adopt(inoutValue);
+ if (ok && inoutValue) {
+ aReturn = std::move(outValue);
+ }
+}
+
+void nsGlobalWindowOuter::FocusOuter(CallerType aCallerType,
+ bool aFromOtherProcess,
+ uint64_t aActionId) {
+ RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager();
+ if (MOZ_UNLIKELY(!fm)) {
+ return;
+ }
+
+ auto [canFocus, isActive] = GetBrowsingContext()->CanFocusCheck(aCallerType);
+ if (aFromOtherProcess) {
+ // We trust that the check passed in a process that's, in principle,
+ // untrusted, because we don't have the required caller context available
+ // here. Also, the worst that the other process can do in this case is to
+ // raise a window it's not supposed to be allowed to raise.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1677899
+ MOZ_ASSERT(XRE_IsContentProcess(),
+ "Parent should not trust other processes.");
+ canFocus = true;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (treeOwnerAsWin && (canFocus || isActive)) {
+ bool isEnabled = true;
+ if (NS_SUCCEEDED(treeOwnerAsWin->GetEnabled(&isEnabled)) && !isEnabled) {
+ NS_WARNING("Should not try to set the focus on a disabled window");
+ return;
+ }
+ }
+
+ if (!mDocShell) {
+ return;
+ }
+
+ // If the window has a child frame focused, clear the focus. This
+ // ensures that focus will be in this frame and not in a child.
+ if (nsIContent* content = GetFocusedElement()) {
+ if (HTMLIFrameElement::FromNode(content)) {
+ fm->ClearFocus(this);
+ }
+ }
+
+ RefPtr<BrowsingContext> parent;
+ BrowsingContext* bc = GetBrowsingContext();
+ if (bc) {
+ parent = bc->GetParent();
+ if (!parent && XRE_IsParentProcess()) {
+ parent = bc->Canonical()->GetParentCrossChromeBoundary();
+ }
+ }
+ if (parent) {
+ if (!parent->IsInProcess()) {
+ if (isActive) {
+ OwningNonNull<nsGlobalWindowOuter> kungFuDeathGrip(*this);
+ fm->WindowRaised(kungFuDeathGrip, aActionId);
+ } else {
+ ContentChild* contentChild = ContentChild::GetSingleton();
+ MOZ_ASSERT(contentChild);
+ contentChild->SendFinalizeFocusOuter(bc, canFocus, aCallerType);
+ }
+ return;
+ }
+
+ MOZ_ASSERT(mDoc, "Call chain should have ensured document creation.");
+ if (mDoc) {
+ if (Element* frame = mDoc->GetEmbedderElement()) {
+ nsContentUtils::RequestFrameFocus(*frame, canFocus, aCallerType);
+ }
+ }
+ return;
+ }
+
+ if (canFocus) {
+ // if there is no parent, this must be a toplevel window, so raise the
+ // window if canFocus is true. If this is a child process, the raise
+ // window request will get forwarded to the parent by the puppet widget.
+ OwningNonNull<nsGlobalWindowOuter> kungFuDeathGrip(*this);
+ fm->RaiseWindow(kungFuDeathGrip, aCallerType, aActionId);
+ }
+}
+
+nsresult nsGlobalWindowOuter::Focus(CallerType aCallerType) {
+ FORWARD_TO_INNER(Focus, (aCallerType), NS_ERROR_UNEXPECTED);
+}
+
+void nsGlobalWindowOuter::BlurOuter(CallerType aCallerType) {
+ if (!GetBrowsingContext()->CanBlurCheck(aCallerType)) {
+ return;
+ }
+
+ nsCOMPtr<nsIWebBrowserChrome> chrome = GetWebBrowserChrome();
+ if (chrome) {
+ chrome->Blur();
+ }
+}
+
+void nsGlobalWindowOuter::StopOuter(ErrorResult& aError) {
+ // IsNavigationAllowed checks are usually done in nsDocShell directly,
+ // however nsDocShell::Stop has a bunch of internal users that would fail
+ // the IsNavigationAllowed check.
+ if (!mDocShell || !nsDocShell::Cast(mDocShell)->IsNavigationAllowed()) {
+ return;
+ }
+
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
+ if (webNav) {
+ aError = webNav->Stop(nsIWebNavigation::STOP_ALL);
+ }
+}
+
+void nsGlobalWindowOuter::PrintOuter(ErrorResult& aError) {
+ if (!AreDialogsEnabled()) {
+ // Per spec, silently return. https://github.com/whatwg/html/commit/21a1de1
+ return;
+ }
+
+ // Printing is disabled, silently return.
+ if (!StaticPrefs::print_enabled()) {
+ return;
+ }
+
+ // If we're loading, queue the print for later. This is a special-case that
+ // only applies to the window.print() call, for compat with other engines and
+ // pre-existing behavior.
+ if (mShouldDelayPrintUntilAfterLoad) {
+ if (nsIDocShell* docShell = GetDocShell()) {
+ if (docShell->GetBusyFlags() & nsIDocShell::BUSY_FLAGS_PAGE_LOADING) {
+ mDelayedPrintUntilAfterLoad = true;
+ return;
+ }
+ }
+ }
+
+#ifdef NS_PRINTING
+ RefPtr<BrowsingContext> top =
+ mBrowsingContext ? mBrowsingContext->Top() : nullptr;
+ if (NS_WARN_IF(top && top->GetIsPrinting())) {
+ return;
+ }
+
+ if (top) {
+ Unused << top->SetIsPrinting(true);
+ }
+
+ auto unset = MakeScopeExit([&] {
+ if (top) {
+ Unused << top->SetIsPrinting(false);
+ }
+ });
+
+ const bool forPreview = !StaticPrefs::print_always_print_silent();
+ Print(nullptr, nullptr, nullptr, nullptr, IsPreview(forPreview),
+ IsForWindowDotPrint::Yes, nullptr, aError);
+#endif
+}
+
+class MOZ_RAII AutoModalState {
+ public:
+ explicit AutoModalState(nsGlobalWindowOuter& aWin)
+ : mModalStateWin(aWin.EnterModalState()) {}
+
+ ~AutoModalState() {
+ if (mModalStateWin) {
+ mModalStateWin->LeaveModalState();
+ }
+ }
+
+ RefPtr<nsGlobalWindowOuter> mModalStateWin;
+};
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::Print(
+ nsIPrintSettings* aPrintSettings, RemotePrintJobChild* aRemotePrintJob,
+ nsIWebProgressListener* aListener, nsIDocShell* aDocShellToCloneInto,
+ IsPreview aIsPreview, IsForWindowDotPrint aForWindowDotPrint,
+ PrintPreviewResolver&& aPrintPreviewCallback, ErrorResult& aError) {
+#ifdef NS_PRINTING
+ nsCOMPtr<nsIPrintSettingsService> printSettingsService =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ if (!printSettingsService) {
+ // we currently return here in headless mode - should we?
+ aError.ThrowNotSupportedError("No print settings service");
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIPrintSettings> ps = aPrintSettings;
+ if (!ps) {
+ // We shouldn't need this once bug 1776169 is fixed.
+ printSettingsService->GetDefaultPrintSettingsForPrinting(
+ getter_AddRefs(ps));
+ }
+
+ RefPtr<Document> docToPrint = mDoc;
+ if (NS_WARN_IF(!docToPrint)) {
+ aError.ThrowNotSupportedError("Document is gone");
+ return nullptr;
+ }
+
+ RefPtr<BrowsingContext> sourceBC = docToPrint->GetBrowsingContext();
+ MOZ_DIAGNOSTIC_ASSERT(sourceBC);
+ if (!sourceBC) {
+ aError.ThrowNotSupportedError("No browsing context for source document");
+ return nullptr;
+ }
+
+ nsAutoSyncOperation sync(docToPrint, SyncOperationBehavior::eAllowInput);
+ AutoModalState modalState(*this);
+
+ nsCOMPtr<nsIContentViewer> cv;
+ RefPtr<BrowsingContext> bc;
+ bool hasPrintCallbacks = false;
+ if (docToPrint->IsStaticDocument()) {
+ if (aForWindowDotPrint == IsForWindowDotPrint::Yes) {
+ aError.ThrowNotSupportedError(
+ "Calling print() from a print preview is unsupported, did you intend "
+ "to call printPreview() instead?");
+ return nullptr;
+ }
+ // We're already a print preview window, just reuse our browsing context /
+ // content viewer.
+ bc = sourceBC;
+ nsCOMPtr<nsIDocShell> docShell = bc->GetDocShell();
+ if (!docShell) {
+ aError.ThrowNotSupportedError("No docshell");
+ return nullptr;
+ }
+ // We could handle this if needed.
+ if (aDocShellToCloneInto && aDocShellToCloneInto != docShell) {
+ aError.ThrowNotSupportedError(
+ "We don't handle cloning a print preview doc into a different "
+ "docshell");
+ return nullptr;
+ }
+ docShell->GetContentViewer(getter_AddRefs(cv));
+ MOZ_DIAGNOSTIC_ASSERT(cv);
+ } else {
+ if (aDocShellToCloneInto) {
+ // Ensure the content viewer is created if needed.
+ Unused << aDocShellToCloneInto->GetDocument();
+ bc = aDocShellToCloneInto->GetBrowsingContext();
+ } else {
+ AutoNoJSAPI nojsapi;
+ auto printKind = aForWindowDotPrint == IsForWindowDotPrint::Yes
+ ? PrintKind::WindowDotPrint
+ : PrintKind::InternalPrint;
+ // For PrintKind::WindowDotPrint, this call will not only make the parent
+ // process create a CanonicalBrowsingContext for the returned `bc`, but
+ // it will also make the parent process initiate the print/print preview.
+ // See the handling of OPEN_PRINT_BROWSER in browser.js.
+ aError = OpenInternal(u""_ns, u""_ns, u""_ns,
+ false, // aDialog
+ false, // aContentModal
+ true, // aCalledNoScript
+ false, // aDoJSFixups
+ true, // aNavigate
+ nullptr, nullptr, // No args
+ nullptr, // aLoadState
+ false, // aForceNoOpener
+ printKind, getter_AddRefs(bc));
+ if (NS_WARN_IF(aError.Failed())) {
+ return nullptr;
+ }
+ }
+ if (!bc) {
+ aError.ThrowNotAllowedError("No browsing context");
+ return nullptr;
+ }
+
+ Unused << bc->Top()->SetIsPrinting(true);
+ nsCOMPtr<nsIDocShell> cloneDocShell = bc->GetDocShell();
+ MOZ_DIAGNOSTIC_ASSERT(cloneDocShell);
+ cloneDocShell->GetContentViewer(getter_AddRefs(cv));
+ MOZ_DIAGNOSTIC_ASSERT(cv);
+ if (!cv) {
+ aError.ThrowNotSupportedError("Didn't end up with a content viewer");
+ return nullptr;
+ }
+
+ if (bc != sourceBC) {
+ MOZ_ASSERT(bc->IsTopContent());
+ // If we are cloning from a document in a different BrowsingContext, we
+ // need to make sure to copy over our opener policy information from that
+ // BrowsingContext. In the case where the source is an iframe, this
+ // information needs to be copied from the toplevel source
+ // BrowsingContext, as we may be making a static clone of a single
+ // subframe.
+ MOZ_ALWAYS_SUCCEEDS(
+ bc->SetOpenerPolicy(sourceBC->Top()->GetOpenerPolicy()));
+ MOZ_DIAGNOSTIC_ASSERT(bc->Group() == sourceBC->Group());
+ }
+
+ if (RefPtr<Document> doc = cv->GetDocument()) {
+ if (doc->IsShowing()) {
+ // We're going to drop this document on the floor, in the SetDocument
+ // call below. Make sure to run OnPageHide() to keep state consistent
+ // and avoids assertions in the document destructor.
+ doc->OnPageHide(false, nullptr);
+ }
+ }
+
+ AutoPrintEventDispatcher dispatcher(*docToPrint);
+
+ nsAutoScriptBlocker blockScripts;
+ RefPtr<Document> clone = docToPrint->CreateStaticClone(
+ cloneDocShell, cv, ps, &hasPrintCallbacks);
+ if (!clone) {
+ aError.ThrowNotSupportedError("Clone operation for printing failed");
+ return nullptr;
+ }
+ }
+
+ nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint = do_QueryInterface(cv);
+ if (!webBrowserPrint) {
+ aError.ThrowNotSupportedError(
+ "Content viewer didn't implement nsIWebBrowserPrint");
+ return nullptr;
+ }
+
+ // For window.print(), we postpone making these calls until the round-trip to
+ // the parent process (triggered by the OpenInternal call above) calls us
+ // again. Only a call from the parent can provide a valid nsPrintSettings
+ // object and RemotePrintJobChild object.
+ if (aForWindowDotPrint == IsForWindowDotPrint::No) {
+ if (aIsPreview == IsPreview::Yes) {
+ aError = webBrowserPrint->PrintPreview(ps, aListener,
+ std::move(aPrintPreviewCallback));
+ if (aError.Failed()) {
+ return nullptr;
+ }
+ } else {
+ // Historically we've eaten this error.
+ webBrowserPrint->Print(ps, aRemotePrintJob, aListener);
+ }
+ }
+
+ // When using window.print() with the new UI, we usually want to block until
+ // the print dialog is hidden. But we can't really do that if we have print
+ // callbacks, because we are inside a sync operation, and we want to run
+ // microtasks / etc that the print callbacks may create. It is really awkward
+ // to have this subtle behavior difference...
+ //
+ // We also want to do this for fuzzing, so that they can test window.print().
+ const bool shouldBlock = [&] {
+ if (aForWindowDotPrint == IsForWindowDotPrint::No) {
+ return false;
+ }
+ if (aIsPreview == IsPreview::Yes) {
+ return !hasPrintCallbacks;
+ }
+ return StaticPrefs::dom_window_print_fuzzing_block_while_printing();
+ }();
+
+ if (shouldBlock) {
+ SpinEventLoopUntil("nsGlobalWindowOuter::Print"_ns,
+ [&] { return bc->IsDiscarded(); });
+ }
+
+ return WindowProxyHolder(std::move(bc));
+#else
+ return nullptr;
+#endif // NS_PRINTING
+}
+
+void nsGlobalWindowOuter::MoveToOuter(int32_t aXPos, int32_t aYPos,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.moveTo() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerType) || mBrowsingContext->IsSubframe()) {
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // We need to do the same transformation GetScreenXY does.
+ RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
+ if (!presContext) {
+ return;
+ }
+
+ CSSIntPoint cssPos(aXPos, aYPos);
+ CheckSecurityLeftAndTop(&cssPos.x.value, &cssPos.y.value, aCallerType);
+
+ nsDeviceContext* context = presContext->DeviceContext();
+
+ auto devPos = LayoutDeviceIntPoint::FromAppUnitsRounded(
+ CSSIntPoint::ToAppUnits(cssPos), context->AppUnitsPerDevPixel());
+
+ aError = treeOwnerAsWin->SetPosition(devPos.x, devPos.y);
+ CheckForDPIChange();
+}
+
+void nsGlobalWindowOuter::MoveByOuter(int32_t aXDif, int32_t aYDif,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.moveBy() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerType) || mBrowsingContext->IsSubframe()) {
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // To do this correctly we have to convert what we get from GetPosition
+ // into CSS pixels, add the arguments, do the security check, and
+ // then convert back to device pixels for the call to SetPosition.
+
+ int32_t x, y;
+ aError = treeOwnerAsWin->GetPosition(&x, &y);
+ if (aError.Failed()) {
+ return;
+ }
+
+ auto cssScale = CSSToDevScaleForBaseWindow(treeOwnerAsWin);
+ CSSIntPoint cssPos = RoundedToInt(treeOwnerAsWin->GetPosition() / cssScale);
+
+ cssPos.x += aXDif;
+ cssPos.y += aYDif;
+
+ CheckSecurityLeftAndTop(&cssPos.x.value, &cssPos.y.value, aCallerType);
+
+ LayoutDeviceIntPoint newDevPos = RoundedToInt(cssPos * cssScale);
+ aError = treeOwnerAsWin->SetPosition(newDevPos.x, newDevPos.y);
+
+ CheckForDPIChange();
+}
+
+nsresult nsGlobalWindowOuter::MoveBy(int32_t aXDif, int32_t aYDif) {
+ ErrorResult rv;
+ MoveByOuter(aXDif, aYDif, CallerType::System, rv);
+
+ return rv.StealNSResult();
+}
+
+void nsGlobalWindowOuter::ResizeToOuter(int32_t aWidth, int32_t aHeight,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.resizeTo() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerType) || mBrowsingContext->IsSubframe()) {
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ CSSIntSize cssSize(aWidth, aHeight);
+ CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerType);
+
+ LayoutDeviceIntSize devSize =
+ RoundedToInt(cssSize * CSSToDevScaleForBaseWindow(treeOwnerAsWin));
+ aError = treeOwnerAsWin->SetSize(devSize.width, devSize.height, true);
+
+ CheckForDPIChange();
+}
+
+void nsGlobalWindowOuter::ResizeByOuter(int32_t aWidthDif, int32_t aHeightDif,
+ CallerType aCallerType,
+ ErrorResult& aError) {
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.resizeBy() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerType) || mBrowsingContext->IsSubframe()) {
+ return;
+ }
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ LayoutDeviceIntSize size = treeOwnerAsWin->GetSize();
+
+ // To do this correctly we have to convert what we got from GetSize
+ // into CSS pixels, add the arguments, do the security check, and
+ // then convert back to device pixels for the call to SetSize.
+
+ auto scale = CSSToDevScaleForBaseWindow(treeOwnerAsWin);
+ CSSIntSize cssSize = RoundedToInt(size / scale);
+
+ cssSize.width += aWidthDif;
+ cssSize.height += aHeightDif;
+
+ CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerType);
+
+ LayoutDeviceIntSize newDevSize = RoundedToInt(cssSize * scale);
+
+ aError = treeOwnerAsWin->SetSize(newDevSize.width, newDevSize.height, true);
+
+ CheckForDPIChange();
+}
+
+void nsGlobalWindowOuter::SizeToContentOuter(
+ CallerType aCallerType, const SizeToContentConstraints& aConstraints,
+ ErrorResult& aError) {
+ if (!mDocShell) {
+ return;
+ }
+
+ /*
+ * If caller is not chrome and the user has not explicitly exempted the site,
+ * prevent window.sizeToContent() by exiting early
+ */
+
+ if (!CanMoveResizeWindows(aCallerType) || mBrowsingContext->IsSubframe()) {
+ return;
+ }
+
+ // The content viewer does a check to make sure that it's a content
+ // viewer for a toplevel docshell.
+ nsCOMPtr<nsIContentViewer> cv;
+ mDocShell->GetContentViewer(getter_AddRefs(cv));
+ if (!cv) {
+ return aError.Throw(NS_ERROR_FAILURE);
+ }
+
+ auto contentSize = cv->GetContentSize(
+ aConstraints.mMaxWidth, aConstraints.mMaxHeight, aConstraints.mPrefWidth);
+ if (!contentSize) {
+ return aError.Throw(NS_ERROR_FAILURE);
+ }
+
+ // Make sure the new size is following the CheckSecurityWidthAndHeight
+ // rules.
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
+ if (!treeOwner) {
+ return aError.Throw(NS_ERROR_FAILURE);
+ }
+
+ // Don't use DevToCSSIntPixelsForBaseWindow() nor
+ // CSSToDevIntPixelsForBaseWindow() here because contentSize is comes from
+ // nsIContentViewer::GetContentSize() and it's computed with nsPresContext so
+ // that we need to work with nsPresContext here too.
+ RefPtr<nsPresContext> presContext = cv->GetPresContext();
+ MOZ_ASSERT(
+ presContext,
+ "Should be non-nullptr if nsIContentViewer::GetContentSize() succeeded");
+ CSSIntSize cssSize = *contentSize;
+ CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerType);
+
+ LayoutDeviceIntSize newDevSize(
+ presContext->CSSPixelsToDevPixels(cssSize.width),
+ presContext->CSSPixelsToDevPixels(cssSize.height));
+
+ nsCOMPtr<nsIDocShell> docShell = mDocShell;
+ aError =
+ treeOwner->SizeShellTo(docShell, newDevSize.width, newDevSize.height);
+}
+
+already_AddRefed<nsPIWindowRoot> nsGlobalWindowOuter::GetTopWindowRoot() {
+ nsPIDOMWindowOuter* piWin = GetPrivateRoot();
+ if (!piWin) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIWindowRoot> window =
+ do_QueryInterface(piWin->GetChromeEventHandler());
+ return window.forget();
+}
+
+void nsGlobalWindowOuter::FirePopupBlockedEvent(
+ Document* aDoc, nsIURI* aPopupURI, const nsAString& aPopupWindowName,
+ const nsAString& aPopupWindowFeatures) {
+ MOZ_ASSERT(aDoc);
+
+ // Fire a "DOMPopupBlocked" event so that the UI can hear about
+ // blocked popups.
+ PopupBlockedEventInit init;
+ init.mBubbles = true;
+ init.mCancelable = true;
+ // XXX: This is a different object, but webidl requires an inner window here
+ // now.
+ init.mRequestingWindow = GetCurrentInnerWindowInternal();
+ init.mPopupWindowURI = aPopupURI;
+ init.mPopupWindowName = aPopupWindowName;
+ init.mPopupWindowFeatures = aPopupWindowFeatures;
+
+ RefPtr<PopupBlockedEvent> event =
+ PopupBlockedEvent::Constructor(aDoc, u"DOMPopupBlocked"_ns, init);
+
+ event->SetTrusted(true);
+
+ aDoc->DispatchEvent(*event);
+}
+
+// static
+bool nsGlobalWindowOuter::CanSetProperty(const char* aPrefName) {
+ // Chrome can set any property.
+ if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) {
+ return true;
+ }
+
+ // If the pref is set to true, we can not set the property
+ // and vice versa.
+ return !Preferences::GetBool(aPrefName, true);
+}
+
+/* If a window open is blocked, fire the appropriate DOM events. */
+void nsGlobalWindowOuter::FireAbuseEvents(
+ const nsAString& aPopupURL, const nsAString& aPopupWindowName,
+ const nsAString& aPopupWindowFeatures) {
+ // fetch the URI of the window requesting the opened window
+ nsCOMPtr<Document> currentDoc = GetDoc();
+ nsCOMPtr<nsIURI> popupURI;
+
+ // build the URI of the would-have-been popup window
+ // (see nsWindowWatcher::URIfromURL)
+
+ // first, fetch the opener's base URI
+
+ nsIURI* baseURL = nullptr;
+
+ nsCOMPtr<Document> doc = GetEntryDocument();
+ if (doc) baseURL = doc->GetDocBaseURI();
+
+ // use the base URI to build what would have been the popup's URI
+ nsCOMPtr<nsIIOService> ios(do_GetService(NS_IOSERVICE_CONTRACTID));
+ if (ios)
+ ios->NewURI(NS_ConvertUTF16toUTF8(aPopupURL), nullptr, baseURL,
+ getter_AddRefs(popupURI));
+
+ // fire an event block full of informative URIs
+ FirePopupBlockedEvent(currentDoc, popupURI, aPopupWindowName,
+ aPopupWindowFeatures);
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::OpenOuter(
+ const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
+ ErrorResult& aError) {
+ RefPtr<BrowsingContext> bc;
+ nsresult rv = OpenJS(aUrl, aName, aOptions, getter_AddRefs(bc));
+ if (rv == NS_ERROR_MALFORMED_URI) {
+ aError.ThrowSyntaxError("Unable to open a window with invalid URL '"_ns +
+ NS_ConvertUTF16toUTF8(aUrl) + "'."_ns);
+ return nullptr;
+ }
+
+ // XXX Is it possible that some internal errors are thrown here?
+ aError = rv;
+
+ if (!bc) {
+ return nullptr;
+ }
+ return WindowProxyHolder(std::move(bc));
+}
+
+nsresult nsGlobalWindowOuter::Open(const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ nsDocShellLoadState* aLoadState,
+ bool aForceNoOpener,
+ BrowsingContext** _retval) {
+ return OpenInternal(aUrl, aName, aOptions,
+ false, // aDialog
+ false, // aContentModal
+ true, // aCalledNoScript
+ false, // aDoJSFixups
+ true, // aNavigate
+ nullptr, nullptr, // No args
+ aLoadState, aForceNoOpener, PrintKind::None, _retval);
+}
+
+nsresult nsGlobalWindowOuter::OpenJS(const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ BrowsingContext** _retval) {
+ return OpenInternal(aUrl, aName, aOptions,
+ false, // aDialog
+ false, // aContentModal
+ false, // aCalledNoScript
+ true, // aDoJSFixups
+ true, // aNavigate
+ nullptr, nullptr, // No args
+ nullptr, // aLoadState
+ false, // aForceNoOpener
+ PrintKind::None, _retval);
+}
+
+// like Open, but attaches to the new window any extra parameters past
+// [features] as a JS property named "arguments"
+nsresult nsGlobalWindowOuter::OpenDialog(const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ nsISupports* aExtraArgument,
+ BrowsingContext** _retval) {
+ return OpenInternal(aUrl, aName, aOptions,
+ true, // aDialog
+ false, // aContentModal
+ true, // aCalledNoScript
+ false, // aDoJSFixups
+ true, // aNavigate
+ nullptr, aExtraArgument, // Arguments
+ nullptr, // aLoadState
+ false, // aForceNoOpener
+ PrintKind::None, _retval);
+}
+
+// Like Open, but passes aNavigate=false.
+/* virtual */
+nsresult nsGlobalWindowOuter::OpenNoNavigate(const nsAString& aUrl,
+ const nsAString& aName,
+ const nsAString& aOptions,
+ BrowsingContext** _retval) {
+ return OpenInternal(aUrl, aName, aOptions,
+ false, // aDialog
+ false, // aContentModal
+ true, // aCalledNoScript
+ false, // aDoJSFixups
+ false, // aNavigate
+ nullptr, nullptr, // No args
+ nullptr, // aLoadState
+ false, // aForceNoOpener
+ PrintKind::None, _retval);
+}
+
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::OpenDialogOuter(
+ JSContext* aCx, const nsAString& aUrl, const nsAString& aName,
+ const nsAString& aOptions, const Sequence<JS::Value>& aExtraArgument,
+ ErrorResult& aError) {
+ nsCOMPtr<nsIJSArgArray> argvArray;
+ aError =
+ NS_CreateJSArgv(aCx, aExtraArgument.Length(), aExtraArgument.Elements(),
+ getter_AddRefs(argvArray));
+ if (aError.Failed()) {
+ return nullptr;
+ }
+
+ RefPtr<BrowsingContext> dialog;
+ aError = OpenInternal(aUrl, aName, aOptions,
+ true, // aDialog
+ false, // aContentModal
+ false, // aCalledNoScript
+ false, // aDoJSFixups
+ true, // aNavigate
+ argvArray, nullptr, // Arguments
+ nullptr, // aLoadState
+ false, // aForceNoOpener
+ PrintKind::None, getter_AddRefs(dialog));
+ if (!dialog) {
+ return nullptr;
+ }
+ return WindowProxyHolder(std::move(dialog));
+}
+
+WindowProxyHolder nsGlobalWindowOuter::GetFramesOuter() {
+ RefPtr<nsPIDOMWindowOuter> frames(this);
+ FlushPendingNotifications(FlushType::ContentAndNotify);
+ return WindowProxyHolder(mBrowsingContext);
+}
+
+/* static */
+bool nsGlobalWindowOuter::GatherPostMessageData(
+ JSContext* aCx, const nsAString& aTargetOrigin, BrowsingContext** aSource,
+ nsAString& aOrigin, nsIURI** aTargetOriginURI,
+ nsIPrincipal** aCallerPrincipal, nsGlobalWindowInner** aCallerInnerWindow,
+ nsIURI** aCallerURI, Maybe<nsID>* aCallerAgentClusterId,
+ nsACString* aScriptLocation, ErrorResult& aError) {
+ //
+ // Window.postMessage is an intentional subversion of the same-origin policy.
+ // As such, this code must be particularly careful in the information it
+ // exposes to calling code.
+ //
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-crossDocumentMessages.html
+ //
+
+ // First, get the caller's window
+ RefPtr<nsGlobalWindowInner> callerInnerWin =
+ nsContentUtils::IncumbentInnerWindow();
+ nsIPrincipal* callerPrin;
+ if (callerInnerWin) {
+ RefPtr<Document> doc = callerInnerWin->GetExtantDoc();
+ if (!doc) {
+ return false;
+ }
+ NS_IF_ADDREF(*aCallerURI = doc->GetDocumentURI());
+
+ // Compute the caller's origin either from its principal or, in the case the
+ // principal doesn't carry a URI (e.g. the system principal), the caller's
+ // document. We must get this now instead of when the event is created and
+ // dispatched, because ultimately it is the identity of the calling window
+ // *now* that determines who sent the message (and not an identity which
+ // might have changed due to intervening navigations).
+ callerPrin = callerInnerWin->GetPrincipal();
+ } else {
+ // In case the global is not a window, it can be a sandbox, and the
+ // sandbox's principal can be used for the security check.
+ nsIGlobalObject* global = GetIncumbentGlobal();
+ NS_ASSERTION(global, "Why is there no global object?");
+ callerPrin = global->PrincipalOrNull();
+ if (callerPrin) {
+ BasePrincipal::Cast(callerPrin)->GetScriptLocation(*aScriptLocation);
+ }
+ }
+ if (!callerPrin) {
+ return false;
+ }
+
+ // if the principal has a URI, use that to generate the origin
+ if (!callerPrin->IsSystemPrincipal()) {
+ nsAutoCString asciiOrigin;
+ callerPrin->GetAsciiOrigin(asciiOrigin);
+ CopyUTF8toUTF16(asciiOrigin, aOrigin);
+ } else if (callerInnerWin) {
+ if (!*aCallerURI) {
+ return false;
+ }
+ // otherwise use the URI of the document to generate origin
+ nsContentUtils::GetUTFOrigin(*aCallerURI, aOrigin);
+ } else {
+ // in case of a sandbox with a system principal origin can be empty
+ if (!callerPrin->IsSystemPrincipal()) {
+ return false;
+ }
+ }
+ NS_IF_ADDREF(*aCallerPrincipal = callerPrin);
+
+ // "/" indicates same origin as caller, "*" indicates no specific origin is
+ // required.
+ if (!aTargetOrigin.EqualsASCII("/") && !aTargetOrigin.EqualsASCII("*")) {
+ nsCOMPtr<nsIURI> targetOriginURI;
+ if (NS_FAILED(NS_NewURI(getter_AddRefs(targetOriginURI), aTargetOrigin))) {
+ aError.Throw(NS_ERROR_DOM_SYNTAX_ERR);
+ return false;
+ }
+
+ nsresult rv = NS_MutateURI(targetOriginURI)
+ .SetUserPass(""_ns)
+ .SetPathQueryRef(""_ns)
+ .Finalize(aTargetOriginURI);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+ }
+
+ if (!nsContentUtils::IsCallerChrome() && callerInnerWin &&
+ callerInnerWin->GetOuterWindowInternal()) {
+ NS_ADDREF(*aSource = callerInnerWin->GetOuterWindowInternal()
+ ->GetBrowsingContext());
+ } else {
+ *aSource = nullptr;
+ }
+
+ if (aCallerAgentClusterId && callerInnerWin &&
+ callerInnerWin->GetDocGroup()) {
+ *aCallerAgentClusterId =
+ Some(callerInnerWin->GetDocGroup()->AgentClusterId());
+ }
+
+ callerInnerWin.forget(aCallerInnerWindow);
+
+ return true;
+}
+
+bool nsGlobalWindowOuter::GetPrincipalForPostMessage(
+ const nsAString& aTargetOrigin, nsIURI* aTargetOriginURI,
+ nsIPrincipal* aCallerPrincipal, nsIPrincipal& aSubjectPrincipal,
+ nsIPrincipal** aProvidedPrincipal) {
+ //
+ // Window.postMessage is an intentional subversion of the same-origin policy.
+ // As such, this code must be particularly careful in the information it
+ // exposes to calling code.
+ //
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-crossDocumentMessages.html
+ //
+
+ // Convert the provided origin string into a URI for comparison purposes.
+ nsCOMPtr<nsIPrincipal> providedPrincipal;
+
+ if (aTargetOrigin.EqualsASCII("/")) {
+ providedPrincipal = aCallerPrincipal;
+ }
+ // "*" indicates no specific origin is required.
+ else if (!aTargetOrigin.EqualsASCII("*")) {
+ OriginAttributes attrs = aSubjectPrincipal.OriginAttributesRef();
+ if (aSubjectPrincipal.IsSystemPrincipal()) {
+ auto principal = BasePrincipal::Cast(GetPrincipal());
+
+ if (attrs != principal->OriginAttributesRef()) {
+ nsAutoCString targetURL;
+ nsAutoCString sourceOrigin;
+ nsAutoCString targetOrigin;
+
+ if (NS_FAILED(principal->GetAsciiSpec(targetURL)) ||
+ NS_FAILED(principal->GetOrigin(targetOrigin)) ||
+ NS_FAILED(aSubjectPrincipal.GetOrigin(sourceOrigin))) {
+ NS_WARNING("Failed to get source and target origins");
+ return false;
+ }
+
+ nsContentUtils::LogSimpleConsoleError(
+ NS_ConvertUTF8toUTF16(nsPrintfCString(
+ R"(Attempting to post a message to window with url "%s" and )"
+ R"(origin "%s" from a system principal scope with mismatched )"
+ R"(origin "%s".)",
+ targetURL.get(), targetOrigin.get(), sourceOrigin.get())),
+ "DOM"_ns, !!principal->PrivateBrowsingId(),
+ principal->IsSystemPrincipal());
+
+ attrs = principal->OriginAttributesRef();
+ }
+ }
+
+ // Create a nsIPrincipal inheriting the app/browser attributes from the
+ // caller.
+ providedPrincipal =
+ BasePrincipal::CreateContentPrincipal(aTargetOriginURI, attrs);
+ if (NS_WARN_IF(!providedPrincipal)) {
+ return false;
+ }
+ } else {
+ // We still need to check the originAttributes if the target origin is '*'.
+ // But we will ingore the FPD here since the FPDs are possible to be
+ // different.
+ auto principal = BasePrincipal::Cast(GetPrincipal());
+ NS_ENSURE_TRUE(principal, false);
+
+ OriginAttributes targetAttrs = principal->OriginAttributesRef();
+ OriginAttributes sourceAttrs = aSubjectPrincipal.OriginAttributesRef();
+ // We have to exempt the check of OA if the subject prioncipal is a system
+ // principal since there are many tests try to post messages to content from
+ // chrome with a mismatch OA. For example, using the ContentTask.spawn() to
+ // post a message into a private browsing window. The injected code in
+ // ContentTask.spawn() will be executed under the system principal and the
+ // OA of the system principal mismatches with the OA of a private browsing
+ // window.
+ MOZ_DIAGNOSTIC_ASSERT(aSubjectPrincipal.IsSystemPrincipal() ||
+ sourceAttrs.EqualsIgnoringFPD(targetAttrs));
+
+ // If 'privacy.firstparty.isolate.block_post_message' is true, we will block
+ // postMessage across different first party domains.
+ if (OriginAttributes::IsBlockPostMessageForFPI() &&
+ !aSubjectPrincipal.IsSystemPrincipal() &&
+ sourceAttrs.mFirstPartyDomain != targetAttrs.mFirstPartyDomain) {
+ return false;
+ }
+ }
+
+ providedPrincipal.forget(aProvidedPrincipal);
+ return true;
+}
+
+void nsGlobalWindowOuter::PostMessageMozOuter(JSContext* aCx,
+ JS::Handle<JS::Value> aMessage,
+ const nsAString& aTargetOrigin,
+ JS::Handle<JS::Value> aTransfer,
+ nsIPrincipal& aSubjectPrincipal,
+ ErrorResult& aError) {
+ RefPtr<BrowsingContext> sourceBc;
+ nsAutoString origin;
+ nsCOMPtr<nsIURI> targetOriginURI;
+ nsCOMPtr<nsIPrincipal> callerPrincipal;
+ RefPtr<nsGlobalWindowInner> callerInnerWindow;
+ nsCOMPtr<nsIURI> callerURI;
+ Maybe<nsID> callerAgentClusterId = Nothing();
+ nsAutoCString scriptLocation;
+ if (!GatherPostMessageData(
+ aCx, aTargetOrigin, getter_AddRefs(sourceBc), origin,
+ getter_AddRefs(targetOriginURI), getter_AddRefs(callerPrincipal),
+ getter_AddRefs(callerInnerWindow), getter_AddRefs(callerURI),
+ &callerAgentClusterId, &scriptLocation, aError)) {
+ return;
+ }
+
+ nsCOMPtr<nsIPrincipal> providedPrincipal;
+ if (!GetPrincipalForPostMessage(aTargetOrigin, targetOriginURI,
+ callerPrincipal, aSubjectPrincipal,
+ getter_AddRefs(providedPrincipal))) {
+ return;
+ }
+
+ // Create and asynchronously dispatch a runnable which will handle actual DOM
+ // event creation and dispatch.
+ RefPtr<PostMessageEvent> event = new PostMessageEvent(
+ sourceBc, origin, this, providedPrincipal,
+ callerInnerWindow ? callerInnerWindow->WindowID() : 0, callerURI,
+ scriptLocation, callerAgentClusterId);
+
+ JS::CloneDataPolicy clonePolicy;
+
+ if (GetDocGroup() && callerAgentClusterId.isSome() &&
+ GetDocGroup()->AgentClusterId().Equals(callerAgentClusterId.value())) {
+ clonePolicy.allowIntraClusterClonableSharedObjects();
+ }
+
+ if (callerInnerWindow && callerInnerWindow->IsSharedMemoryAllowed()) {
+ clonePolicy.allowSharedMemoryObjects();
+ }
+
+ event->Write(aCx, aMessage, aTransfer, clonePolicy, aError);
+ if (NS_WARN_IF(aError.Failed())) {
+ return;
+ }
+
+ event->DispatchToTargetThread(aError);
+}
+
+class nsCloseEvent : public Runnable {
+ RefPtr<nsGlobalWindowOuter> mWindow;
+ bool mIndirect;
+
+ nsCloseEvent(nsGlobalWindowOuter* aWindow, bool aIndirect)
+ : mozilla::Runnable("nsCloseEvent"),
+ mWindow(aWindow),
+ mIndirect(aIndirect) {}
+
+ public:
+ static nsresult PostCloseEvent(nsGlobalWindowOuter* aWindow, bool aIndirect) {
+ nsCOMPtr<nsIRunnable> ev = new nsCloseEvent(aWindow, aIndirect);
+ nsresult rv = aWindow->Dispatch(TaskCategory::Other, ev.forget());
+ return rv;
+ }
+
+ NS_IMETHOD Run() override {
+ if (mWindow) {
+ if (mIndirect) {
+ return PostCloseEvent(mWindow, false);
+ }
+ mWindow->ReallyCloseWindow();
+ }
+ return NS_OK;
+ }
+};
+
+bool nsGlobalWindowOuter::CanClose() {
+ if (mIsChrome) {
+ nsCOMPtr<nsIBrowserDOMWindow> bwin;
+ GetBrowserDOMWindow(getter_AddRefs(bwin));
+
+ bool canClose = true;
+ if (bwin && NS_SUCCEEDED(bwin->CanClose(&canClose))) {
+ return canClose;
+ }
+ }
+
+ if (!mDocShell) {
+ return true;
+ }
+
+ nsCOMPtr<nsIContentViewer> cv;
+ mDocShell->GetContentViewer(getter_AddRefs(cv));
+ if (cv) {
+ bool canClose;
+ nsresult rv = cv->PermitUnload(&canClose);
+ if (NS_SUCCEEDED(rv) && !canClose) return false;
+ }
+
+ // If we still have to print, we delay the closing until print has happened.
+ if (mShouldDelayPrintUntilAfterLoad && mDelayedPrintUntilAfterLoad) {
+ mDelayedCloseForPrinting = true;
+ return false;
+ }
+
+ return true;
+}
+
+void nsGlobalWindowOuter::CloseOuter(bool aTrustedCaller) {
+ if (!mDocShell || IsInModalState() || mBrowsingContext->IsSubframe()) {
+ // window.close() is called on a frame in a frameset, on a window
+ // that's already closed, or on a window for which there's
+ // currently a modal dialog open. Ignore such calls.
+ return;
+ }
+
+ if (mHavePendingClose) {
+ // We're going to be closed anyway; do nothing since we don't want
+ // to double-close
+ return;
+ }
+
+ if (mBlockScriptedClosingFlag) {
+ // A script's popup has been blocked and we don't want
+ // the window to be closed directly after this event,
+ // so the user can see that there was a blocked popup.
+ return;
+ }
+
+ // Don't allow scripts from content to close non-neterror windows that
+ // were not opened by script.
+ if (mDoc) {
+ nsAutoString url;
+ nsresult rv = mDoc->GetURL(url);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ if (!StringBeginsWith(url, u"about:neterror"_ns) &&
+ !mBrowsingContext->HadOriginalOpener() && !aTrustedCaller &&
+ !IsOnlyTopLevelDocumentInSHistory()) {
+ bool allowClose =
+ mAllowScriptsToClose ||
+ Preferences::GetBool("dom.allow_scripts_to_close_windows", true);
+ if (!allowClose) {
+ // We're blocking the close operation
+ // report localized error msg in JS console
+ nsContentUtils::ReportToConsole(
+ nsIScriptError::warningFlag, "DOM Window"_ns,
+ mDoc, // Better name for the category?
+ nsContentUtils::eDOM_PROPERTIES, "WindowCloseBlockedWarning");
+
+ return;
+ }
+ }
+ }
+
+ if (!mInClose && !mIsClosed && !CanClose()) {
+ return;
+ }
+
+ // Fire a DOM event notifying listeners that this window is about to
+ // be closed. The tab UI code may choose to cancel the default
+ // action for this event, if so, we won't actually close the window
+ // (since the tab UI code will close the tab in stead). Sure, this
+ // could be abused by content code, but do we care? I don't think
+ // so...
+
+ bool wasInClose = mInClose;
+ mInClose = true;
+
+ if (!DispatchCustomEvent(u"DOMWindowClose"_ns, ChromeOnlyDispatch::eYes)) {
+ // Someone chose to prevent the default action for this event, if
+ // so, let's not close this window after all...
+
+ mInClose = wasInClose;
+ return;
+ }
+
+ FinalClose();
+}
+
+bool nsGlobalWindowOuter::IsOnlyTopLevelDocumentInSHistory() {
+ NS_ENSURE_TRUE(mDocShell && mBrowsingContext, false);
+ // Disabled since IsFrame() is buggy in Fission
+ // MOZ_ASSERT(mBrowsingContext->IsTop());
+
+ if (mozilla::SessionHistoryInParent()) {
+ return mBrowsingContext->GetIsSingleToplevelInHistory();
+ }
+
+ RefPtr<ChildSHistory> csh = nsDocShell::Cast(mDocShell)->GetSessionHistory();
+ if (csh && csh->LegacySHistory()) {
+ return csh->LegacySHistory()->IsEmptyOrHasEntriesForSingleTopLevelPage();
+ }
+
+ return false;
+}
+
+nsresult nsGlobalWindowOuter::Close() {
+ CloseOuter(/* aTrustedCaller = */ true);
+ return NS_OK;
+}
+
+void nsGlobalWindowOuter::ForceClose() {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default);
+
+ if (mBrowsingContext->IsSubframe() || !mDocShell) {
+ // This may be a frame in a frameset, or a window that's already closed.
+ // Ignore such calls.
+ return;
+ }
+
+ if (mHavePendingClose) {
+ // We're going to be closed anyway; do nothing since we don't want
+ // to double-close
+ return;
+ }
+
+ mInClose = true;
+
+ DispatchCustomEvent(u"DOMWindowClose"_ns, ChromeOnlyDispatch::eYes);
+
+ FinalClose();
+}
+
+void nsGlobalWindowOuter::FinalClose() {
+ // Flag that we were closed.
+ mIsClosed = true;
+
+ if (!mBrowsingContext->IsDiscarded()) {
+ MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetClosed(true));
+ }
+
+ // If we get here from CloseOuter then it means that the parent process is
+ // going to close our window for us. It's just important to set mIsClosed.
+ if (XRE_GetProcessType() == GeckoProcessType_Content) {
+ return;
+ }
+
+ // This stuff is non-sensical but incredibly fragile. The reasons for the
+ // behavior here don't make sense today and may not have ever made sense,
+ // but various bits of frontend code break when you change them. If you need
+ // to fix up this behavior, feel free to. It's a righteous task, but involves
+ // wrestling with various download manager tests, frontend code, and possible
+ // broken addons. The chrome tests in toolkit/mozapps/downloads are a good
+ // testing ground.
+ //
+ // In particular, if some inner of |win| is the entry global, we must
+ // complete _two_ round-trips to the event loop before the call to
+ // ReallyCloseWindow. This allows setTimeout handlers that are set after
+ // FinalClose() is called to run before the window is torn down.
+ nsCOMPtr<nsPIDOMWindowInner> entryWindow =
+ do_QueryInterface(GetEntryGlobal());
+ bool indirect = entryWindow && entryWindow->GetOuterWindow() == this;
+ if (NS_FAILED(nsCloseEvent::PostCloseEvent(this, indirect))) {
+ ReallyCloseWindow();
+ } else {
+ mHavePendingClose = true;
+ }
+}
+
+void nsGlobalWindowOuter::ReallyCloseWindow() {
+ // Make sure we never reenter this method.
+ mHavePendingClose = true;
+
+ nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow();
+ if (!treeOwnerAsWin) {
+ return;
+ }
+
+ treeOwnerAsWin->Destroy();
+ CleanUp();
+}
+
+void nsGlobalWindowOuter::SuppressEventHandling() {
+ if (mSuppressEventHandlingDepth == 0) {
+ if (BrowsingContext* bc = GetBrowsingContext()) {
+ bc->PreOrderWalk([&](BrowsingContext* aBC) {
+ if (nsCOMPtr<nsPIDOMWindowOuter> win = aBC->GetDOMWindow()) {
+ if (RefPtr<Document> doc = win->GetExtantDoc()) {
+ mSuspendedDocs.AppendElement(doc);
+ // Note: Document::SuppressEventHandling will also automatically
+ // suppress event handling for any in-process sub-documents.
+ // However, since we need to deal with cases where remote
+ // BrowsingContexts may be interleaved with in-process ones, we
+ // still need to walk the entire tree ourselves. This may be
+ // slightly redundant in some cases, but since event handling
+ // suppressions maintain a count of current blockers, it does not
+ // cause any problems.
+ doc->SuppressEventHandling();
+ }
+ }
+ });
+ }
+ }
+ mSuppressEventHandlingDepth++;
+}
+
+void nsGlobalWindowOuter::UnsuppressEventHandling() {
+ MOZ_ASSERT(mSuppressEventHandlingDepth != 0);
+ mSuppressEventHandlingDepth--;
+
+ if (mSuppressEventHandlingDepth == 0 && mSuspendedDocs.Length()) {
+ RefPtr<Document> currentDoc = GetExtantDoc();
+ bool fireEvent = currentDoc == mSuspendedDocs[0];
+ nsTArray<RefPtr<Document>> suspendedDocs = std::move(mSuspendedDocs);
+ for (const auto& doc : suspendedDocs) {
+ doc->UnsuppressEventHandlingAndFireEvents(fireEvent);
+ }
+ }
+}
+
+nsGlobalWindowOuter* nsGlobalWindowOuter::EnterModalState() {
+ // GetInProcessScriptableTop, not GetInProcessTop, so that EnterModalState
+ // works properly with <iframe mozbrowser>.
+ nsGlobalWindowOuter* topWin = GetInProcessScriptableTopInternal();
+
+ if (!topWin) {
+ NS_ERROR("Uh, EnterModalState() called w/o a reachable top window?");
+ return nullptr;
+ }
+
+ // If there is an active ESM in this window, clear it. Otherwise, this can
+ // cause a problem if a modal state is entered during a mouseup event.
+ EventStateManager* activeESM = static_cast<EventStateManager*>(
+ EventStateManager::GetActiveEventStateManager());
+ if (activeESM && activeESM->GetPresContext()) {
+ PresShell* activePresShell = activeESM->GetPresContext()->GetPresShell();
+ if (activePresShell && (nsContentUtils::ContentIsCrossDocDescendantOf(
+ activePresShell->GetDocument(), mDoc) ||
+ nsContentUtils::ContentIsCrossDocDescendantOf(
+ mDoc, activePresShell->GetDocument()))) {
+ EventStateManager::ClearGlobalActiveContent(activeESM);
+
+ PresShell::ReleaseCapturingContent();
+
+ if (activePresShell) {
+ RefPtr<nsFrameSelection> frameSelection =
+ activePresShell->FrameSelection();
+ frameSelection->SetDragState(false);
+ }
+ }
+ }
+
+ // If there are any drag and drop operations in flight, try to end them.
+ nsCOMPtr<nsIDragService> ds =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (ds) {
+ ds->EndDragSession(true, 0);
+ }
+
+ // Clear the capturing content if it is under topDoc.
+ // Usually the activeESM check above does that, but there are cases when
+ // we don't have activeESM, or it is for different document.
+ Document* topDoc = topWin->GetExtantDoc();
+ nsIContent* capturingContent = PresShell::GetCapturingContent();
+ if (capturingContent && topDoc &&
+ nsContentUtils::ContentIsCrossDocDescendantOf(capturingContent, topDoc)) {
+ PresShell::ReleaseCapturingContent();
+ }
+
+ if (topWin->mModalStateDepth == 0) {
+ topWin->SuppressEventHandling();
+
+ if (nsGlobalWindowInner* inner = topWin->GetCurrentInnerWindowInternal()) {
+ inner->Suspend();
+ }
+ }
+ topWin->mModalStateDepth++;
+ return topWin;
+}
+
+void nsGlobalWindowOuter::LeaveModalState() {
+ {
+ nsGlobalWindowOuter* topWin = GetInProcessScriptableTopInternal();
+ if (!topWin) {
+ NS_WARNING("Uh, LeaveModalState() called w/o a reachable top window?");
+ return;
+ }
+
+ if (topWin != this) {
+ MOZ_ASSERT(IsSuspended());
+ return topWin->LeaveModalState();
+ }
+ }
+
+ MOZ_ASSERT(mModalStateDepth != 0);
+ MOZ_ASSERT(IsSuspended());
+ mModalStateDepth--;
+
+ nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
+ if (mModalStateDepth == 0) {
+ if (inner) {
+ inner->Resume();
+ }
+
+ UnsuppressEventHandling();
+ }
+
+ // Remember the time of the last dialog quit.
+ if (auto* bcg = GetBrowsingContextGroup()) {
+ bcg->SetLastDialogQuitTime(TimeStamp::Now());
+ }
+
+ if (mModalStateDepth == 0) {
+ RefPtr<Event> event = NS_NewDOMEvent(inner, nullptr, nullptr);
+ event->InitEvent(u"endmodalstate"_ns, true, false);
+ event->SetTrusted(true);
+ event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true;
+ DispatchEvent(*event);
+ }
+}
+
+bool nsGlobalWindowOuter::IsInModalState() {
+ nsGlobalWindowOuter* topWin = GetInProcessScriptableTopInternal();
+
+ if (!topWin) {
+ // IsInModalState() getting called w/o a reachable top window is a bit
+ // iffy, but valid enough not to make noise about it. See bug 404828
+ return false;
+ }
+
+ return topWin->mModalStateDepth != 0;
+}
+
+void nsGlobalWindowOuter::NotifyWindowIDDestroyed(const char* aTopic) {
+ nsCOMPtr<nsIRunnable> runnable =
+ new WindowDestroyedEvent(this, mWindowID, aTopic);
+ Dispatch(TaskCategory::Other, runnable.forget());
+}
+
+Element* nsGlobalWindowOuter::GetFrameElement(nsIPrincipal& aSubjectPrincipal) {
+ // Per HTML5, the frameElement getter returns null in cross-origin situations.
+ Element* element = GetFrameElement();
+ if (!element) {
+ return nullptr;
+ }
+
+ if (!aSubjectPrincipal.SubsumesConsideringDomain(element->NodePrincipal())) {
+ return nullptr;
+ }
+
+ return element;
+}
+
+Element* nsGlobalWindowOuter::GetFrameElement() {
+ if (!mBrowsingContext || mBrowsingContext->IsTop()) {
+ return nullptr;
+ }
+ return mBrowsingContext->GetEmbedderElement();
+}
+
+namespace {
+class ChildCommandDispatcher : public Runnable {
+ public:
+ ChildCommandDispatcher(nsPIWindowRoot* aRoot, nsIBrowserChild* aBrowserChild,
+ nsPIDOMWindowOuter* aWindow, const nsAString& aAction)
+ : mozilla::Runnable("ChildCommandDispatcher"),
+ mRoot(aRoot),
+ mBrowserChild(aBrowserChild),
+ mWindow(aWindow),
+ mAction(aAction) {}
+
+ NS_IMETHOD Run() override {
+ AutoTArray<nsCString, 70> enabledCommands, disabledCommands;
+ mRoot->GetEnabledDisabledCommands(enabledCommands, disabledCommands);
+ if (enabledCommands.Length() || disabledCommands.Length()) {
+ BrowserChild* bc = static_cast<BrowserChild*>(mBrowserChild.get());
+ bc->SendEnableDisableCommands(mWindow->GetBrowsingContext(), mAction,
+ enabledCommands, disabledCommands);
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ nsCOMPtr<nsPIWindowRoot> mRoot;
+ nsCOMPtr<nsIBrowserChild> mBrowserChild;
+ nsCOMPtr<nsPIDOMWindowOuter> mWindow;
+ nsString mAction;
+};
+
+class CommandDispatcher : public Runnable {
+ public:
+ CommandDispatcher(nsIDOMXULCommandDispatcher* aDispatcher,
+ const nsAString& aAction)
+ : mozilla::Runnable("CommandDispatcher"),
+ mDispatcher(aDispatcher),
+ mAction(aAction) {}
+
+ // TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230, bug 1535398)
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHOD Run() override {
+ return mDispatcher->UpdateCommands(mAction);
+ }
+
+ const nsCOMPtr<nsIDOMXULCommandDispatcher> mDispatcher;
+ nsString mAction;
+};
+} // anonymous namespace
+
+void nsGlobalWindowOuter::UpdateCommands(const nsAString& anAction,
+ Selection* aSel, int16_t aReason) {
+ // If this is a child process, redirect to the parent process.
+ if (nsIDocShell* docShell = GetDocShell()) {
+ if (nsCOMPtr<nsIBrowserChild> child = docShell->GetBrowserChild()) {
+ nsCOMPtr<nsPIWindowRoot> root = GetTopWindowRoot();
+ if (root) {
+ nsContentUtils::AddScriptRunner(
+ new ChildCommandDispatcher(root, child, this, anAction));
+ }
+ return;
+ }
+ }
+
+ nsPIDOMWindowOuter* rootWindow = GetPrivateRoot();
+ if (!rootWindow) {
+ return;
+ }
+
+ Document* doc = rootWindow->GetExtantDoc();
+
+ if (!doc) {
+ return;
+ }
+ // selectionchange action is only used for mozbrowser, not for XUL. So we
+ // bypass XUL command dispatch if anAction is "selectionchange".
+ if (!anAction.EqualsLiteral("selectionchange")) {
+ // Retrieve the command dispatcher and call updateCommands on it.
+ nsIDOMXULCommandDispatcher* xulCommandDispatcher =
+ doc->GetCommandDispatcher();
+ if (xulCommandDispatcher) {
+ nsContentUtils::AddScriptRunner(
+ new CommandDispatcher(xulCommandDispatcher, anAction));
+ }
+ }
+}
+
+Selection* nsGlobalWindowOuter::GetSelectionOuter() {
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ PresShell* presShell = mDocShell->GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+ return presShell->GetCurrentSelection(SelectionType::eNormal);
+}
+
+already_AddRefed<Selection> nsGlobalWindowOuter::GetSelection() {
+ RefPtr<Selection> selection = GetSelectionOuter();
+ return selection.forget();
+}
+
+bool nsGlobalWindowOuter::FindOuter(const nsAString& aString,
+ bool aCaseSensitive, bool aBackwards,
+ bool aWrapAround, bool aWholeWord,
+ bool aSearchInFrames, bool aShowDialog,
+ ErrorResult& aError) {
+ Unused << aShowDialog;
+
+ nsCOMPtr<nsIWebBrowserFind> finder(do_GetInterface(mDocShell));
+ if (!finder) {
+ aError.Throw(NS_ERROR_NOT_AVAILABLE);
+ return false;
+ }
+
+ // Set the options of the search
+ aError = finder->SetSearchString(aString);
+ if (aError.Failed()) {
+ return false;
+ }
+ finder->SetMatchCase(aCaseSensitive);
+ finder->SetFindBackwards(aBackwards);
+ finder->SetWrapFind(aWrapAround);
+ finder->SetEntireWord(aWholeWord);
+ finder->SetSearchFrames(aSearchInFrames);
+
+ // the nsIWebBrowserFind is initialized to use this window
+ // as the search root, but uses focus to set the current search
+ // frame. If we're being called from JS (as here), this window
+ // should be the current search frame.
+ nsCOMPtr<nsIWebBrowserFindInFrames> framesFinder(do_QueryInterface(finder));
+ if (framesFinder) {
+ framesFinder->SetRootSearchFrame(this); // paranoia
+ framesFinder->SetCurrentSearchFrame(this);
+ }
+
+ if (aString.IsEmpty()) {
+ return false;
+ }
+
+ // Launch the search with the passed in search string
+ bool didFind = false;
+ aError = finder->FindNext(&didFind);
+ return didFind;
+}
+
+//*****************************************************************************
+// EventTarget
+//*****************************************************************************
+
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetOwnerGlobalForBindingsInternal() {
+ return this;
+}
+
+bool nsGlobalWindowOuter::DispatchEvent(Event& aEvent, CallerType aCallerType,
+ ErrorResult& aRv) {
+ FORWARD_TO_INNER(DispatchEvent, (aEvent, aCallerType, aRv), false);
+}
+
+bool nsGlobalWindowOuter::ComputeDefaultWantsUntrusted(ErrorResult& aRv) {
+ // It's OK that we just return false here on failure to create an
+ // inner. GetOrCreateListenerManager() will likewise fail, and then
+ // we won't be adding any listeners anyway.
+ FORWARD_TO_INNER_CREATE(ComputeDefaultWantsUntrusted, (aRv), false);
+}
+
+EventListenerManager* nsGlobalWindowOuter::GetOrCreateListenerManager() {
+ FORWARD_TO_INNER_CREATE(GetOrCreateListenerManager, (), nullptr);
+}
+
+EventListenerManager* nsGlobalWindowOuter::GetExistingListenerManager() const {
+ FORWARD_TO_INNER(GetExistingListenerManager, (), nullptr);
+}
+
+//*****************************************************************************
+// nsGlobalWindowOuter::nsPIDOMWindow
+//*****************************************************************************
+
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetPrivateParent() {
+ nsCOMPtr<nsPIDOMWindowOuter> parent = GetInProcessParent();
+
+ if (this == parent) {
+ nsCOMPtr<nsIContent> chromeElement(do_QueryInterface(mChromeEventHandler));
+ if (!chromeElement)
+ return nullptr; // This is ok, just means a null parent.
+
+ Document* doc = chromeElement->GetComposedDoc();
+ if (!doc) return nullptr; // This is ok, just means a null parent.
+
+ return doc->GetWindow();
+ }
+
+ return parent;
+}
+
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetPrivateRoot() {
+ nsCOMPtr<nsPIDOMWindowOuter> top = GetInProcessTop();
+
+ nsCOMPtr<nsIContent> chromeElement(do_QueryInterface(mChromeEventHandler));
+ if (chromeElement) {
+ Document* doc = chromeElement->GetComposedDoc();
+ if (doc) {
+ nsCOMPtr<nsPIDOMWindowOuter> parent = doc->GetWindow();
+ if (parent) {
+ top = parent->GetInProcessTop();
+ }
+ }
+ }
+
+ return top;
+}
+
+// This has a caller in Windows-only code (nsNativeAppSupportWin).
+Location* nsGlobalWindowOuter::GetLocation() {
+ // This method can be called on the outer window as well.
+ FORWARD_TO_INNER(Location, (), nullptr);
+}
+
+void nsGlobalWindowOuter::SetIsBackground(bool aIsBackground) {
+ bool changed = aIsBackground != IsBackground();
+ SetIsBackgroundInternal(aIsBackground);
+
+ nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
+
+ if (inner && changed) {
+ inner->UpdateBackgroundState();
+ }
+
+ if (aIsBackground) {
+ // Notify gamepadManager we are at the background window,
+ // we need to stop vibrate.
+ // Stop the vr telemery time spent when it switches to
+ // the background window.
+ if (inner && changed) {
+ inner->StopGamepadHaptics();
+ inner->StopVRActivity();
+ // true is for asking to set the delta time to
+ // the telemetry.
+ inner->ResetVRTelemetry(true);
+ }
+ return;
+ }
+
+ if (inner) {
+ // When switching to be as a top tab, restart the telemetry.
+ // false is for only resetting the timestamp.
+ inner->ResetVRTelemetry(false);
+ inner->SyncGamepadState();
+ inner->StartVRActivity();
+ }
+}
+
+void nsGlobalWindowOuter::SetIsBackgroundInternal(bool aIsBackground) {
+ mIsBackground = aIsBackground;
+}
+
+void nsGlobalWindowOuter::SetChromeEventHandler(
+ EventTarget* aChromeEventHandler) {
+ SetChromeEventHandlerInternal(aChromeEventHandler);
+ // update the chrome event handler on all our inner windows
+ RefPtr<nsGlobalWindowInner> inner;
+ for (PRCList* node = PR_LIST_HEAD(this); node != this;
+ node = PR_NEXT_LINK(inner)) {
+ // This cast is only safe if `node != this`, as nsGlobalWindowOuter is also
+ // in the list.
+ inner = static_cast<nsGlobalWindowInner*>(node);
+ NS_ASSERTION(!inner->mOuterWindow || inner->mOuterWindow == this,
+ "bad outer window pointer");
+ inner->SetChromeEventHandlerInternal(aChromeEventHandler);
+ }
+}
+
+void nsGlobalWindowOuter::SetFocusedElement(Element* aElement,
+ uint32_t aFocusMethod,
+ bool aNeedsFocus) {
+ FORWARD_TO_INNER_VOID(SetFocusedElement,
+ (aElement, aFocusMethod, aNeedsFocus));
+}
+
+uint32_t nsGlobalWindowOuter::GetFocusMethod() {
+ FORWARD_TO_INNER(GetFocusMethod, (), 0);
+}
+
+bool nsGlobalWindowOuter::ShouldShowFocusRing() {
+ FORWARD_TO_INNER(ShouldShowFocusRing, (), false);
+}
+
+bool nsGlobalWindowOuter::TakeFocus(bool aFocus, uint32_t aFocusMethod) {
+ FORWARD_TO_INNER(TakeFocus, (aFocus, aFocusMethod), false);
+}
+
+void nsGlobalWindowOuter::SetReadyForFocus() {
+ FORWARD_TO_INNER_VOID(SetReadyForFocus, ());
+}
+
+void nsGlobalWindowOuter::PageHidden() {
+ FORWARD_TO_INNER_VOID(PageHidden, ());
+}
+
+already_AddRefed<nsICSSDeclaration>
+nsGlobalWindowOuter::GetComputedStyleHelperOuter(Element& aElt,
+ const nsAString& aPseudoElt,
+ bool aDefaultStylesOnly,
+ ErrorResult& aRv) {
+ if (!mDoc) {
+ return nullptr;
+ }
+
+ RefPtr<nsICSSDeclaration> compStyle = NS_NewComputedDOMStyle(
+ &aElt, aPseudoElt, mDoc,
+ aDefaultStylesOnly ? nsComputedDOMStyle::StyleType::DefaultOnly
+ : nsComputedDOMStyle::StyleType::All,
+ aRv);
+
+ return compStyle.forget();
+}
+
+//*****************************************************************************
+// nsGlobalWindowOuter::nsIInterfaceRequestor
+//*****************************************************************************
+
+nsresult nsGlobalWindowOuter::GetInterfaceInternal(const nsIID& aIID,
+ void** aSink) {
+ NS_ENSURE_ARG_POINTER(aSink);
+ *aSink = nullptr;
+
+ if (aIID.Equals(NS_GET_IID(nsIWebNavigation))) {
+ nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell));
+ webNav.forget(aSink);
+ } else if (aIID.Equals(NS_GET_IID(nsIDocShell))) {
+ nsCOMPtr<nsIDocShell> docShell = mDocShell;
+ docShell.forget(aSink);
+ }
+#ifdef NS_PRINTING
+ else if (aIID.Equals(NS_GET_IID(nsIWebBrowserPrint))) {
+ if (mDocShell) {
+ nsCOMPtr<nsIContentViewer> viewer;
+ mDocShell->GetContentViewer(getter_AddRefs(viewer));
+ if (viewer) {
+ nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint(do_QueryInterface(viewer));
+ webBrowserPrint.forget(aSink);
+ }
+ }
+ }
+#endif
+ else if (aIID.Equals(NS_GET_IID(nsILoadContext))) {
+ nsCOMPtr<nsILoadContext> loadContext(do_QueryInterface(mDocShell));
+ loadContext.forget(aSink);
+ }
+
+ return *aSink ? NS_OK : NS_ERROR_NO_INTERFACE;
+}
+
+NS_IMETHODIMP
+nsGlobalWindowOuter::GetInterface(const nsIID& aIID, void** aSink) {
+ nsresult rv = GetInterfaceInternal(aIID, aSink);
+ if (rv == NS_ERROR_NO_INTERFACE) {
+ return QueryInterface(aIID, aSink);
+ }
+ return rv;
+}
+
+bool nsGlobalWindowOuter::IsSuspended() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ // No inner means we are effectively suspended
+ if (!mInnerWindow) {
+ return true;
+ }
+ return mInnerWindow->IsSuspended();
+}
+
+bool nsGlobalWindowOuter::IsFrozen() const {
+ MOZ_ASSERT(NS_IsMainThread());
+ // No inner means we are effectively frozen
+ if (!mInnerWindow) {
+ return true;
+ }
+ return mInnerWindow->IsFrozen();
+}
+
+nsresult nsGlobalWindowOuter::FireDelayedDOMEvents(bool aIncludeSubWindows) {
+ FORWARD_TO_INNER(FireDelayedDOMEvents, (aIncludeSubWindows),
+ NS_ERROR_UNEXPECTED);
+}
+
+//*****************************************************************************
+// nsGlobalWindowOuter: Window Control Functions
+//*****************************************************************************
+
+nsPIDOMWindowOuter* nsGlobalWindowOuter::GetInProcessParentInternal() {
+ nsCOMPtr<nsPIDOMWindowOuter> parent = GetInProcessParent();
+
+ if (parent && parent != this) {
+ return parent;
+ }
+
+ return nullptr;
+}
+
+void nsGlobalWindowOuter::UnblockScriptedClosing() {
+ mBlockScriptedClosingFlag = false;
+}
+
+class AutoUnblockScriptClosing {
+ private:
+ RefPtr<nsGlobalWindowOuter> mWin;
+
+ public:
+ explicit AutoUnblockScriptClosing(nsGlobalWindowOuter* aWin) : mWin(aWin) {
+ MOZ_ASSERT(mWin);
+ }
+ ~AutoUnblockScriptClosing() {
+ void (nsGlobalWindowOuter::*run)() =
+ &nsGlobalWindowOuter::UnblockScriptedClosing;
+ nsCOMPtr<nsIRunnable> caller = NewRunnableMethod(
+ "AutoUnblockScriptClosing::~AutoUnblockScriptClosing", mWin, run);
+ mWin->Dispatch(TaskCategory::Other, caller.forget());
+ }
+};
+
+nsresult nsGlobalWindowOuter::OpenInternal(
+ const nsAString& aUrl, const nsAString& aName, const nsAString& aOptions,
+ bool aDialog, bool aContentModal, bool aCalledNoScript, bool aDoJSFixups,
+ bool aNavigate, nsIArray* argv, nsISupports* aExtraArgument,
+ nsDocShellLoadState* aLoadState, bool aForceNoOpener, PrintKind aPrintKind,
+ BrowsingContext** aReturn) {
+#ifdef DEBUG
+ uint32_t argc = 0;
+ if (argv) argv->GetLength(&argc);
+#endif
+
+ MOZ_ASSERT(!aExtraArgument || (!argv && argc == 0),
+ "Can't pass in arguments both ways");
+ MOZ_ASSERT(!aCalledNoScript || (!argv && argc == 0),
+ "Can't pass JS args when called via the noscript methods");
+
+ mozilla::Maybe<AutoUnblockScriptClosing> closeUnblocker;
+
+ // Calls to window.open from script should navigate.
+ MOZ_ASSERT(aCalledNoScript || aNavigate);
+
+ *aReturn = nullptr;
+
+ nsCOMPtr<nsIWebBrowserChrome> chrome = GetWebBrowserChrome();
+ if (!chrome) {
+ // No chrome means we don't want to go through with this open call
+ // -- see nsIWindowWatcher.idl
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ NS_ASSERTION(mDocShell, "Must have docshell here");
+
+ NS_ConvertUTF16toUTF8 optionsUtf8(aOptions);
+
+ WindowFeatures features;
+ if (!features.Tokenize(optionsUtf8)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool forceNoOpener = aForceNoOpener;
+ if (features.Exists("noopener")) {
+ forceNoOpener = features.GetBool("noopener");
+ features.Remove("noopener");
+ }
+
+ bool forceNoReferrer = false;
+ if (features.Exists("noreferrer")) {
+ forceNoReferrer = features.GetBool("noreferrer");
+ if (forceNoReferrer) {
+ // noreferrer implies noopener
+ forceNoOpener = true;
+ }
+ features.Remove("noreferrer");
+ }
+
+ nsAutoCString options;
+ features.Stringify(options);
+
+ // If noopener is force-enabled for the current document, then set noopener to
+ // true, and clear the name to "_blank".
+ nsAutoString windowName(aName);
+ if (nsDocShell::Cast(GetDocShell())->NoopenerForceEnabled()) {
+ // FIXME: Eventually bypass force-enabling noopener if `aPrintKind !=
+ // PrintKind::None`, so that we can print pages with noopener force-enabled.
+ // This will require relaxing assertions elsewhere.
+ if (aPrintKind != PrintKind::None) {
+ NS_WARNING(
+ "printing frames with noopener force-enabled isn't supported yet");
+ return NS_ERROR_FAILURE;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(aNavigate,
+ "cannot OpenNoNavigate if noopener is force-enabled");
+
+ forceNoOpener = true;
+ windowName = u"_blank"_ns;
+ }
+
+ bool windowExists = WindowExists(windowName, forceNoOpener, !aCalledNoScript);
+
+ // XXXbz When this gets fixed to not use LegacyIsCallerNativeCode()
+ // (indirectly) maybe we can nix the AutoJSAPI usage OnLinkClickEvent::Run.
+ // But note that if you change this to GetEntryGlobal(), say, then
+ // OnLinkClickEvent::Run will need a full-blown AutoEntryScript.
+ const bool checkForPopup =
+ !nsContentUtils::LegacyIsCallerChromeOrNativeCode() && !aDialog &&
+ !windowExists;
+
+ // Note: the Void handling here is very important, because the window watcher
+ // expects a null URL string (not an empty string) if there is no URL to load.
+ nsCString url;
+ url.SetIsVoid(true);
+ nsresult rv = NS_OK;
+
+ nsCOMPtr<nsIURI> uri;
+
+ // It's important to do this security check before determining whether this
+ // window opening should be blocked, to ensure that we don't FireAbuseEvents
+ // for a window opening that wouldn't have succeeded in the first place.
+ if (!aUrl.IsEmpty()) {
+ AppendUTF16toUTF8(aUrl, url);
+
+ // It's safe to skip the security check below if we're not a dialog
+ // because window.openDialog is not callable from content script. See bug
+ // 56851.
+ //
+ // If we're not navigating, we assume that whoever *does* navigate the
+ // window will do a security check of their own.
+ if (!url.IsVoid() && !aDialog && aNavigate)
+ rv = SecurityCheckURL(url.get(), getter_AddRefs(uri));
+ } else if (mDoc) {
+ mDoc->SetUseCounter(eUseCounter_custom_WindowOpenEmptyUrl);
+ }
+
+ if (NS_FAILED(rv)) return rv;
+
+ PopupBlocker::PopupControlState abuseLevel =
+ PopupBlocker::GetPopupControlState();
+ if (checkForPopup) {
+ abuseLevel = mBrowsingContext->RevisePopupAbuseLevel(abuseLevel);
+ if (abuseLevel >= PopupBlocker::openBlocked) {
+ if (!aCalledNoScript) {
+ // If script in some other window is doing a window.open on us and
+ // it's being blocked, then it's OK to close us afterwards, probably.
+ // But if we're doing a window.open on ourselves and block the popup,
+ // prevent this window from closing until after this script terminates
+ // so that whatever popup blocker UI the app has will be visible.
+ nsCOMPtr<nsPIDOMWindowInner> entryWindow =
+ do_QueryInterface(GetEntryGlobal());
+ // Note that entryWindow can be null here if some JS component was the
+ // place where script was entered for this JS execution.
+ if (entryWindow && entryWindow->GetOuterWindow() == this) {
+ mBlockScriptedClosingFlag = true;
+ closeUnblocker.emplace(this);
+ }
+ }
+
+ FireAbuseEvents(aUrl, windowName, aOptions);
+ return aDoJSFixups ? NS_OK : NS_ERROR_FAILURE;
+ }
+ }
+
+ RefPtr<BrowsingContext> domReturn;
+
+ nsCOMPtr<nsIWindowWatcher> wwatch =
+ do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv);
+ NS_ENSURE_TRUE(wwatch, rv);
+
+ NS_ConvertUTF16toUTF8 name(windowName);
+
+ nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch));
+ NS_ENSURE_STATE(pwwatch);
+
+ MOZ_ASSERT_IF(checkForPopup, abuseLevel < PopupBlocker::openBlocked);
+ // At this point we should know for a fact that if checkForPopup then
+ // abuseLevel < PopupBlocker::openBlocked, so we could just check for
+ // abuseLevel == PopupBlocker::openControlled. But let's be defensive just in
+ // case and treat anything that fails the above assert as a spam popup too, if
+ // it ever happens.
+ bool isPopupSpamWindow =
+ checkForPopup && (abuseLevel >= PopupBlocker::openControlled);
+
+ const auto wwPrintKind = [&] {
+ switch (aPrintKind) {
+ case PrintKind::None:
+ return nsPIWindowWatcher::PRINT_NONE;
+ case PrintKind::InternalPrint:
+ return nsPIWindowWatcher::PRINT_INTERNAL;
+ case PrintKind::WindowDotPrint:
+ return nsPIWindowWatcher::PRINT_WINDOW_DOT_PRINT;
+ }
+ MOZ_ASSERT_UNREACHABLE("Wat");
+ return nsPIWindowWatcher::PRINT_NONE;
+ }();
+
+ {
+ // Reset popup state while opening a window to prevent the
+ // current state from being active the whole time a modal
+ // dialog is open.
+ AutoPopupStatePusher popupStatePusher(PopupBlocker::openAbused, true);
+
+ if (!aCalledNoScript) {
+ // We asserted at the top of this function that aNavigate is true for
+ // !aCalledNoScript.
+ rv = pwwatch->OpenWindow2(this, url, name, options,
+ /* aCalledFromScript = */ true, aDialog,
+ aNavigate, argv, isPopupSpamWindow,
+ forceNoOpener, forceNoReferrer, wwPrintKind,
+ aLoadState, getter_AddRefs(domReturn));
+ } else {
+ // Force a system caller here so that the window watcher won't screw us
+ // up. We do NOT want this case looking at the JS context on the stack
+ // when searching. Compare comments on
+ // nsIDOMWindow::OpenWindow and nsIWindowWatcher::OpenWindow.
+
+ // Note: Because nsWindowWatcher is so broken, it's actually important
+ // that we don't force a system caller here, because that screws it up
+ // when it tries to compute the caller principal to associate with dialog
+ // arguments. That whole setup just really needs to be rewritten. :-(
+ Maybe<AutoNoJSAPI> nojsapi;
+ if (!aContentModal) {
+ nojsapi.emplace();
+ }
+
+ rv = pwwatch->OpenWindow2(this, url, name, options,
+ /* aCalledFromScript = */ false, aDialog,
+ aNavigate, aExtraArgument, isPopupSpamWindow,
+ forceNoOpener, forceNoReferrer, wwPrintKind,
+ aLoadState, getter_AddRefs(domReturn));
+ }
+ }
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // success!
+
+ if (!aCalledNoScript && !windowExists && uri && !forceNoOpener) {
+ MaybeAllowStorageForOpenedWindow(uri);
+ }
+
+ if (domReturn && aDoJSFixups) {
+ nsCOMPtr<nsIDOMChromeWindow> chrome_win(
+ do_QueryInterface(domReturn->GetDOMWindow()));
+ if (!chrome_win) {
+ // A new non-chrome window was created from a call to
+ // window.open() from JavaScript, make sure there's a document in
+ // the new window. We do this by simply asking the new window for
+ // its document, this will synchronously create an empty document
+ // if there is no document in the window.
+ // XXXbz should this just use EnsureInnerWindow()?
+
+ // Force document creation.
+ if (nsPIDOMWindowOuter* win = domReturn->GetDOMWindow()) {
+ nsCOMPtr<Document> doc = win->GetDoc();
+ Unused << doc;
+ }
+ }
+ }
+
+ domReturn.forget(aReturn);
+ return NS_OK;
+}
+
+void nsGlobalWindowOuter::MaybeAllowStorageForOpenedWindow(nsIURI* aURI) {
+ nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
+ if (NS_WARN_IF(!inner)) {
+ return;
+ }
+
+ // No 3rd party URL/window.
+ if (!AntiTrackingUtils::IsThirdPartyWindow(inner, aURI)) {
+ return;
+ }
+
+ Document* doc = inner->GetDoc();
+ if (!doc) {
+ return;
+ }
+ nsCOMPtr<nsIPrincipal> principal = BasePrincipal::CreateContentPrincipal(
+ aURI, doc->NodePrincipal()->OriginAttributesRef());
+
+ // We don't care when the asynchronous work finishes here.
+ Unused << StorageAccessAPIHelper::AllowAccessFor(
+ principal, GetBrowsingContext(), ContentBlockingNotifier::eOpener);
+}
+
+//*****************************************************************************
+// nsGlobalWindowOuter: Helper Functions
+//*****************************************************************************
+
+already_AddRefed<nsIDocShellTreeOwner> nsPIDOMWindowOuter::GetTreeOwner() {
+ // If there's no docShellAsItem, this window must have been closed,
+ // in that case there is no tree owner.
+
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+ mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
+ return treeOwner.forget();
+}
+
+already_AddRefed<nsIBaseWindow> nsPIDOMWindowOuter::GetTreeOwnerWindow() {
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+
+ // If there's no mDocShell, this window must have been closed,
+ // in that case there is no tree owner.
+
+ if (mDocShell) {
+ mDocShell->GetTreeOwner(getter_AddRefs(treeOwner));
+ }
+
+ nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner);
+ return baseWindow.forget();
+}
+
+already_AddRefed<nsIWebBrowserChrome>
+nsPIDOMWindowOuter::GetWebBrowserChrome() {
+ nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
+
+ nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(treeOwner);
+ return browserChrome.forget();
+}
+
+nsIScrollableFrame* nsGlobalWindowOuter::GetScrollFrame() {
+ if (!mDocShell) {
+ return nullptr;
+ }
+
+ PresShell* presShell = mDocShell->GetPresShell();
+ if (presShell) {
+ return presShell->GetRootScrollFrameAsScrollable();
+ }
+ return nullptr;
+}
+
+nsresult nsGlobalWindowOuter::SecurityCheckURL(const char* aURL,
+ nsIURI** aURI) {
+ nsCOMPtr<nsPIDOMWindowInner> sourceWindow =
+ do_QueryInterface(GetEntryGlobal());
+ if (!sourceWindow) {
+ sourceWindow = GetCurrentInnerWindow();
+ }
+ AutoJSContext cx;
+ nsGlobalWindowInner* sourceWin = nsGlobalWindowInner::Cast(sourceWindow);
+ JSAutoRealm ar(cx, sourceWin->GetGlobalJSObject());
+
+ // Resolve the baseURI, which could be relative to the calling window.
+ //
+ // Note the algorithm to get the base URI should match the one
+ // used to actually kick off the load in nsWindowWatcher.cpp.
+ nsCOMPtr<Document> doc = sourceWindow->GetDoc();
+ nsIURI* baseURI = nullptr;
+ auto encoding = UTF_8_ENCODING; // default to utf-8
+ if (doc) {
+ baseURI = doc->GetDocBaseURI();
+ encoding = doc->GetDocumentCharacterSet();
+ }
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = NS_NewURI(getter_AddRefs(uri), nsDependentCString(aURL),
+ encoding, baseURI);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ return NS_ERROR_DOM_SYNTAX_ERR;
+ }
+
+ if (NS_FAILED(nsContentUtils::GetSecurityManager()->CheckLoadURIFromScript(
+ cx, uri))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ uri.forget(aURI);
+ return NS_OK;
+}
+
+void nsGlobalWindowOuter::FlushPendingNotifications(FlushType aType) {
+ if (mDoc) {
+ mDoc->FlushPendingNotifications(aType);
+ }
+}
+
+void nsGlobalWindowOuter::EnsureSizeAndPositionUpToDate() {
+ // If we're a subframe, make sure our size is up to date. Make sure to go
+ // through the document chain rather than the window chain to not flush on
+ // detached iframes, see bug 1545516.
+ if (mDoc && mDoc->StyleOrLayoutObservablyDependsOnParentDocumentLayout()) {
+ RefPtr<Document> parent = mDoc->GetInProcessParentDocument();
+ parent->FlushPendingNotifications(FlushType::Layout);
+ }
+}
+
+already_AddRefed<nsISupports> nsGlobalWindowOuter::SaveWindowState() {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ if (!mContext || !GetWrapperPreserveColor()) {
+ // The window may be getting torn down; don't bother saving state.
+ return nullptr;
+ }
+
+ nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
+ NS_ASSERTION(inner, "No inner window to save");
+
+ if (WindowContext* wc = inner->GetWindowContext()) {
+ MOZ_ASSERT(!wc->GetWindowStateSaved());
+ Unused << wc->SetWindowStateSaved(true);
+ }
+
+ // Don't do anything else to this inner window! After this point, all
+ // calls to SetTimeoutOrInterval will create entries in the timeout
+ // list that will only run after this window has come out of the bfcache.
+ // Also, while we're frozen, we won't dispatch online/offline events
+ // to the page.
+ inner->Freeze();
+
+ nsCOMPtr<nsISupports> state = new WindowStateHolder(inner);
+
+ MOZ_LOG(gPageCacheLog, LogLevel::Debug,
+ ("saving window state, state = %p", (void*)state));
+
+ return state.forget();
+}
+
+nsresult nsGlobalWindowOuter::RestoreWindowState(nsISupports* aState) {
+ MOZ_ASSERT(!mozilla::SessionHistoryInParent());
+
+ if (!mContext || !GetWrapperPreserveColor()) {
+ // The window may be getting torn down; don't bother restoring state.
+ return NS_OK;
+ }
+
+ nsCOMPtr<WindowStateHolder> holder = do_QueryInterface(aState);
+ NS_ENSURE_TRUE(holder, NS_ERROR_FAILURE);
+
+ MOZ_LOG(gPageCacheLog, LogLevel::Debug,
+ ("restoring window state, state = %p", (void*)holder));
+
+ // And we're ready to go!
+ nsGlobalWindowInner* inner = GetCurrentInnerWindowInternal();
+
+ // if a link is focused, refocus with the FLAG_SHOWRING flag set. This makes
+ // it easy to tell which link was last clicked when going back a page.
+ RefPtr<Element> focusedElement = inner->GetFocusedElement();
+ if (nsContentUtils::ContentIsLink(focusedElement)) {
+ if (RefPtr<nsFocusManager> fm = nsFocusManager::GetFocusManager()) {
+ fm->SetFocus(focusedElement, nsIFocusManager::FLAG_NOSCROLL |
+ nsIFocusManager::FLAG_SHOWRING);
+ }
+ }
+
+ if (WindowContext* wc = inner->GetWindowContext()) {
+ MOZ_ASSERT(wc->GetWindowStateSaved());
+ Unused << wc->SetWindowStateSaved(false);
+ }
+
+ inner->Thaw();
+
+ holder->DidRestoreWindow();
+
+ return NS_OK;
+}
+
+void nsGlobalWindowOuter::AddSizeOfIncludingThis(
+ nsWindowSizes& aWindowSizes) const {
+ aWindowSizes.mDOMSizes.mDOMOtherSize +=
+ aWindowSizes.mState.mMallocSizeOf(this);
+}
+
+uint32_t nsGlobalWindowOuter::GetAutoActivateVRDisplayID() {
+ uint32_t retVal = mAutoActivateVRDisplayID;
+ mAutoActivateVRDisplayID = 0;
+ return retVal;
+}
+
+void nsGlobalWindowOuter::SetAutoActivateVRDisplayID(
+ uint32_t aAutoActivateVRDisplayID) {
+ mAutoActivateVRDisplayID = aAutoActivateVRDisplayID;
+}
+
+already_AddRefed<nsWindowRoot> nsGlobalWindowOuter::GetWindowRootOuter() {
+ nsCOMPtr<nsPIWindowRoot> root = GetTopWindowRoot();
+ return root.forget().downcast<nsWindowRoot>();
+}
+
+nsIDOMWindowUtils* nsGlobalWindowOuter::WindowUtils() {
+ if (!mWindowUtils) {
+ mWindowUtils = new nsDOMWindowUtils(this);
+ }
+ return mWindowUtils;
+}
+
+bool nsGlobalWindowOuter::IsInSyncOperation() {
+ return GetExtantDoc() && GetExtantDoc()->IsInSyncOperation();
+}
+
+// Note: This call will lock the cursor, it will not change as it moves.
+// To unlock, the cursor must be set back to Auto.
+void nsGlobalWindowOuter::SetCursorOuter(const nsACString& aCursor,
+ ErrorResult& aError) {
+ auto cursor = StyleCursorKind::Auto;
+ if (!Servo_CursorKind_Parse(&aCursor, &cursor)) {
+ // FIXME: It's a bit weird that this doesn't throw but stuff below does, but
+ // matches previous behavior so...
+ return;
+ }
+
+ RefPtr<nsPresContext> presContext;
+ if (mDocShell) {
+ presContext = mDocShell->GetPresContext();
+ }
+
+ if (presContext) {
+ // Need root widget.
+ PresShell* presShell = mDocShell->GetPresShell();
+ if (!presShell) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsViewManager* vm = presShell->GetViewManager();
+ if (!vm) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsView* rootView = vm->GetRootView();
+ if (!rootView) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ nsIWidget* widget = rootView->GetNearestWidget(nullptr);
+ if (!widget) {
+ aError.Throw(NS_ERROR_FAILURE);
+ return;
+ }
+
+ // Call esm and set cursor.
+ aError = presContext->EventStateManager()->SetCursor(
+ cursor, nullptr, {}, Nothing(), widget, true);
+ }
+}
+
+NS_IMETHODIMP
+nsGlobalWindowOuter::GetBrowserDOMWindow(nsIBrowserDOMWindow** aBrowserWindow) {
+ MOZ_RELEASE_ASSERT(IsChromeWindow());
+ FORWARD_TO_INNER(GetBrowserDOMWindow, (aBrowserWindow), NS_ERROR_UNEXPECTED);
+}
+
+nsIBrowserDOMWindow* nsGlobalWindowOuter::GetBrowserDOMWindowOuter() {
+ MOZ_ASSERT(IsChromeWindow());
+ return mChromeFields.mBrowserDOMWindow;
+}
+
+void nsGlobalWindowOuter::SetBrowserDOMWindowOuter(
+ nsIBrowserDOMWindow* aBrowserWindow) {
+ MOZ_ASSERT(IsChromeWindow());
+ mChromeFields.mBrowserDOMWindow = aBrowserWindow;
+}
+
+ChromeMessageBroadcaster* nsGlobalWindowOuter::GetMessageManager() {
+ if (!mInnerWindow) {
+ NS_WARNING("No inner window available!");
+ return nullptr;
+ }
+ return GetCurrentInnerWindowInternal()->MessageManager();
+}
+
+ChromeMessageBroadcaster* nsGlobalWindowOuter::GetGroupMessageManager(
+ const nsAString& aGroup) {
+ if (!mInnerWindow) {
+ NS_WARNING("No inner window available!");
+ return nullptr;
+ }
+ return GetCurrentInnerWindowInternal()->GetGroupMessageManager(aGroup);
+}
+
+void nsGlobalWindowOuter::InitWasOffline() { mWasOffline = NS_IsOffline(); }
+
+#if defined(_WINDOWS_) && !defined(MOZ_WRAPPED_WINDOWS_H)
+# pragma message( \
+ "wrapper failure reason: " MOZ_WINDOWS_WRAPPER_DISABLED_REASON)
+# error "Never include unwrapped windows.h in this file!"
+#endif
+
+// Helper called by methods that move/resize the window,
+// to ensure the presContext (if any) is aware of resolution
+// change that may happen in multi-monitor configuration.
+void nsGlobalWindowOuter::CheckForDPIChange() {
+ if (mDocShell) {
+ RefPtr<nsPresContext> presContext = mDocShell->GetPresContext();
+ if (presContext) {
+ if (presContext->DeviceContext()->CheckDPIChange()) {
+ presContext->UIResolutionChanged();
+ }
+ }
+ }
+}
+
+nsresult nsGlobalWindowOuter::Dispatch(
+ TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (GetDocGroup()) {
+ return GetDocGroup()->Dispatch(aCategory, std::move(aRunnable));
+ }
+ return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
+}
+
+nsISerialEventTarget* nsGlobalWindowOuter::EventTargetFor(
+ TaskCategory aCategory) const {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (GetDocGroup()) {
+ return GetDocGroup()->EventTargetFor(aCategory);
+ }
+ return DispatcherTrait::EventTargetFor(aCategory);
+}
+
+AbstractThread* nsGlobalWindowOuter::AbstractMainThreadFor(
+ TaskCategory aCategory) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (GetDocGroup()) {
+ return GetDocGroup()->AbstractMainThreadFor(aCategory);
+ }
+ return DispatcherTrait::AbstractMainThreadFor(aCategory);
+}
+
+void nsGlobalWindowOuter::MaybeResetWindowName(Document* aNewDocument) {
+ MOZ_ASSERT(aNewDocument);
+
+ if (!StaticPrefs::privacy_window_name_update_enabled()) {
+ return;
+ }
+
+ const LoadingSessionHistoryInfo* info =
+ nsDocShell::Cast(mDocShell)->GetLoadingSessionHistoryInfo();
+ if (!info || info->mForceMaybeResetName.isNothing()) {
+ // We only reset the window name for the top-level content as well as
+ // storing in session entries.
+ if (!GetBrowsingContext()->IsTopContent()) {
+ return;
+ }
+
+ // Following implements https://html.spec.whatwg.org/#history-traversal:
+ // Step 4.2. Check if the loading document has a different origin than the
+ // previous document.
+
+ // We don't need to do anything if we haven't loaded a non-initial document.
+ if (!GetBrowsingContext()->GetHasLoadedNonInitialDocument()) {
+ return;
+ }
+
+ // If we have an existing document, directly check the document prinicpals
+ // with the new document to know if it is cross-origin.
+ //
+ // Note that there will be an issue of initial document handling in Fission
+ // when running the WPT unset_context_name-1.html. In the test, the first
+ // about:blank page would be loaded with the principal of the testing domain
+ // in Fission and the window.name will be set there. Then, The window.name
+ // won't be reset after navigating to the testing page because the principal
+ // is the same. But, it won't be the case for non-Fission mode that the
+ // first about:blank will be loaded with a null principal and the
+ // window.name will be reset when loading the test page.
+ if (mDoc && mDoc->NodePrincipal()->Equals(aNewDocument->NodePrincipal())) {
+ return;
+ }
+
+ // If we don't have an existing document, and if it's not the initial
+ // about:blank, we could be loading a document because of the
+ // process-switching. In this case, this should be a cross-origin
+ // navigation.
+ } else if (!info->mForceMaybeResetName.ref()) {
+ return;
+ }
+
+ // Step 4.2.2 Store the window.name into all session history entries that have
+ // the same origin as the previous document.
+ nsDocShell::Cast(mDocShell)->StoreWindowNameToSHEntries();
+
+ // Step 4.2.3 Clear the window.name if the browsing context is the top-level
+ // content and doesn't have an opener.
+
+ // We need to reset the window name in case of a cross-origin navigation,
+ // without an opener.
+ RefPtr<BrowsingContext> opener = GetOpenerBrowsingContext();
+ if (opener) {
+ return;
+ }
+
+ Unused << mBrowsingContext->SetName(EmptyString());
+}
+
+nsGlobalWindowOuter::TemporarilyDisableDialogs::TemporarilyDisableDialogs(
+ BrowsingContext* aBC) {
+ BrowsingContextGroup* group = aBC->Group();
+ if (!group) {
+ NS_ERROR(
+ "nsGlobalWindowOuter::TemporarilyDisableDialogs called without a "
+ "browsing context group?");
+ return;
+ }
+
+ if (group) {
+ mGroup = group;
+ mSavedDialogsEnabled = group->GetAreDialogsEnabled();
+ group->SetAreDialogsEnabled(false);
+ }
+}
+
+nsGlobalWindowOuter::TemporarilyDisableDialogs::~TemporarilyDisableDialogs() {
+ if (mGroup) {
+ mGroup->SetAreDialogsEnabled(mSavedDialogsEnabled);
+ }
+}
+
+/* static */
+already_AddRefed<nsGlobalWindowOuter> nsGlobalWindowOuter::Create(
+ nsDocShell* aDocShell, bool aIsChrome) {
+ uint64_t outerWindowID = aDocShell->GetOuterWindowID();
+ RefPtr<nsGlobalWindowOuter> window = new nsGlobalWindowOuter(outerWindowID);
+ if (aIsChrome) {
+ window->mIsChrome = true;
+ }
+ window->SetDocShell(aDocShell);
+
+ window->InitWasOffline();
+ return window.forget();
+}
+
+nsIURI* nsPIDOMWindowOuter::GetDocumentURI() const {
+ return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get();
+}
+
+void nsPIDOMWindowOuter::MaybeCreateDoc() {
+ MOZ_ASSERT(!mDoc);
+ if (nsIDocShell* docShell = GetDocShell()) {
+ // Note that |document| here is the same thing as our mDoc, but we
+ // don't have to explicitly set the member variable because the docshell
+ // has already called SetNewDocument().
+ nsCOMPtr<Document> document = docShell->GetDocument();
+ Unused << document;
+ }
+}
+
+void nsPIDOMWindowOuter::SetChromeEventHandlerInternal(
+ EventTarget* aChromeEventHandler) {
+ // Out-of-line so we don't need to include ContentFrameMessageManager.h in
+ // nsPIDOMWindow.h.
+ mChromeEventHandler = aChromeEventHandler;
+
+ // mParentTarget and mMessageManager will be set when the next event is
+ // dispatched or someone asks for our message manager.
+ mParentTarget = nullptr;
+ mMessageManager = nullptr;
+}
+
+mozilla::dom::DocGroup* nsPIDOMWindowOuter::GetDocGroup() const {
+ Document* doc = GetExtantDoc();
+ if (doc) {
+ return doc->GetDocGroup();
+ }
+ return nullptr;
+}
+
+nsPIDOMWindowOuter::nsPIDOMWindowOuter(uint64_t aWindowID)
+ : mFrameElement(nullptr),
+ mModalStateDepth(0),
+ mSuppressEventHandlingDepth(0),
+ mIsBackground(false),
+ mIsRootOuterWindow(false),
+ mInnerWindow(nullptr),
+ mWindowID(aWindowID),
+ mMarkedCCGeneration(0) {}
+
+nsPIDOMWindowOuter::~nsPIDOMWindowOuter() = default;