Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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 "nsGlobalWindowOuter.h"
#include "nsGlobalWindowInner.h"
#include <algorithm>
#include "mozilla/MemoryReporting.h"
// Local Includes
#include "Navigator.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/UserActivation.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/SchedulerGroup.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/Sprintf.h"
#include "mozilla/Unused.h"
// Other Classes
#include "mozilla/dom/BarProps.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 "nsIDocumentViewer.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;
static inline nsGlobalWindowInner* GetCurrentInnerWindowInternal(
const nsGlobalWindowOuter* aOuter) {
return nsGlobalWindowInner::Cast(aOuter->GetCurrentInnerWindow());
}
#define FORWARD_TO_INNER(method, args, err_rval) \
PR_BEGIN_MACRO \
if (!mInnerWindow) { \
NS_WARNING("No inner window available!"); \
return err_rval; \
} \
return GetCurrentInnerWindowInternal(this)->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(this)->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(this)->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
*
* "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
*/
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
*
*
* "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
*
* "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
*
* "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
*
* "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
*
* "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
// 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() = default;
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();
SetIsOnMainThread();
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(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(this),
"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;