From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- dom/base/nsGlobalWindow.cpp | 15189 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 15189 insertions(+) create mode 100644 dom/base/nsGlobalWindow.cpp (limited to 'dom/base/nsGlobalWindow.cpp') diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp new file mode 100644 index 0000000000..8ff4b84ce3 --- /dev/null +++ b/dom/base/nsGlobalWindow.cpp @@ -0,0 +1,15189 @@ +/* -*- 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 "nsGlobalWindow.h" + +#include + +#include "mozilla/MemoryReporting.h" + +// Local Includes +#include "Navigator.h" +#include "nsContentSecurityManager.h" +#include "nsScreen.h" +#include "nsHistory.h" +#include "nsDOMNavigationTiming.h" +#include "nsIDOMStorageManager.h" +#include "mozilla/dom/DOMStorage.h" +#include "mozilla/dom/IdleRequest.h" +#include "mozilla/dom/Performance.h" +#include "mozilla/dom/StorageEvent.h" +#include "mozilla/dom/StorageEventBinding.h" +#include "mozilla/dom/Timeout.h" +#include "mozilla/IntegerPrintfMacros.h" +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) +#include "mozilla/dom/WindowOrientationObserver.h" +#endif +#include "nsDOMOfflineResourceList.h" +#include "nsError.h" +#include "nsIIdleService.h" +#include "nsISizeOfEventTarget.h" +#include "nsDOMJSUtils.h" +#include "nsArrayUtils.h" +#include "nsIDOMWindowCollection.h" +#include "nsDOMWindowList.h" +#include "mozilla/dom/WakeLock.h" +#include "mozilla/dom/power/PowerManagerService.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIPermissionManager.h" +#include "nsIScriptContext.h" +#include "nsIScriptTimeoutHandler.h" +#include "nsITimeoutHandler.h" +#include "nsIController.h" +#include "nsScriptNameSpaceManager.h" +#include "nsISlowScriptDebug.h" +#include "nsWindowMemoryReporter.h" +#include "WindowNamedPropertiesHandler.h" +#include "nsFrameSelection.h" +#include "nsNetUtil.h" +#include "nsVariant.h" +#include "nsPrintfCString.h" + +// Helper Classes +#include "nsJSUtils.h" +#include "jsapi.h" // for JSAutoRequest +#include "jswrapper.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsReadableUtils.h" +#include "nsDOMClassInfo.h" +#include "nsJSEnvironment.h" +#include "ScriptSettings.h" +#include "mozilla/Preferences.h" +#include "mozilla/Likely.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/workers/Workers.h" +#include "mozilla/dom/ToJSValue.h" +#include "nsJSPrincipals.h" +#include "mozilla/Attributes.h" +#include "mozilla/Debug.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/EventStates.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/ProcessHangMonitor.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/dom/TabGroup.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 "nsIDocCharset.h" +#include "nsIDocument.h" +#include "Crypto.h" +#include "nsIDOMDocument.h" +#include "nsIDOMElement.h" +#include "nsIDOMEvent.h" +#include "nsIDOMOfflineResourceList.h" +#include "nsDOMString.h" +#include "nsIEmbeddingSiteWindow.h" +#include "nsThreadUtils.h" +#include "nsILoadContext.h" +#include "nsIPresShell.h" +#include "nsIScrollableFrame.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsISelectionController.h" +#include "nsISelection.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 "nsIWindowMediator.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 "nsIControllers.h" +#include "nsIControllerContext.h" +#include "nsGlobalWindowCommands.h" +#include "nsQueryObject.h" +#include "nsContentUtils.h" +#include "nsCSSProps.h" +#include "nsIDOMFileList.h" +#include "nsIURIFixup.h" +#ifndef DEBUG +#include "nsIAppStartup.h" +#include "nsToolkitCompsCID.h" +#endif +#include "nsCDefaultURIFixup.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventStateManager.h" +#include "nsIObserverService.h" +#include "nsFocusManager.h" +#include "nsIXULWindow.h" +#include "nsITimedChannel.h" +#include "nsServiceManagerUtils.h" +#ifdef MOZ_XUL +#include "nsIDOMXULControlElement.h" +#include "nsMenuPopupFrame.h" +#endif +#include "mozilla/dom/CustomEvent.h" +#include "nsIJARChannel.h" +#include "nsIScreenManager.h" +#include "nsIEffectiveTLDService.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" + +// XXX An unfortunate dependency exists here (two XUL files). +#include "nsIDOMXULDocument.h" +#include "nsIDOMXULCommandDispatcher.h" + +#include "nsBindingManager.h" +#include "nsXBLService.h" + +// used for popup blocking, needs to be converted to something +// belonging to the back-end like nsIContentPolicy +#include "nsIPopupWindowManager.h" + +#include "nsIDragService.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Selection.h" +#include "nsFrameLoader.h" +#include "nsISupportsPrimitives.h" +#include "nsXPCOMCID.h" +#include "mozilla/Logging.h" +#include "prenv.h" +#include "prprf.h" + +#include "mozilla/dom/IDBFactory.h" +#include "mozilla/dom/MessageChannel.h" +#include "mozilla/dom/Promise.h" + +#ifdef MOZ_GAMEPAD +#include "mozilla/dom/Gamepad.h" +#include "mozilla/dom/GamepadManager.h" +#endif + +#include "mozilla/dom/VRDisplay.h" +#include "mozilla/dom/VREventObserver.h" + +#include "nsRefreshDriver.h" +#include "Layers.h" + +#include "mozilla/AddonPathService.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 "TimeChangeObserver.h" +#include "mozilla/dom/AudioContext.h" +#include "mozilla/dom/BrowserElementDictionariesBinding.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/MozSelfSupportBinding.h" +#include "mozilla/dom/PopStateEvent.h" +#include "mozilla/dom/PopupBlockedEvent.h" +#include "mozilla/dom/PrimitiveConversions.h" +#include "mozilla/dom/WindowBinding.h" +#include "nsITabChild.h" +#include "mozilla/dom/MediaQueryList.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/NavigatorBinding.h" +#include "mozilla/dom/ImageBitmap.h" +#include "mozilla/dom/ImageBitmapBinding.h" +#include "mozilla/dom/ServiceWorkerRegistration.h" +#include "mozilla/dom/U2F.h" +#include "mozilla/dom/WebIDLGlobalNameHash.h" +#include "mozilla/dom/Worklet.h" +#ifdef HAVE_SIDEBAR +#include "mozilla/dom/ExternalBinding.h" +#endif + +#ifdef MOZ_WEBSPEECH +#include "mozilla/dom/SpeechSynthesis.h" +#endif + +#ifdef MOZ_B2G +#include "nsPISocketTransportService.h" +#endif + +// Apple system headers seem to have a check() macro. +#ifdef check +class nsIScriptTimeoutHandler; +#undef check +#endif // check +#include "AccessCheck.h" + +#ifdef ANDROID +#include +#endif + +#ifdef XP_WIN +#include +#define getpid _getpid +#else +#include // for getpid() +#endif + +static const char kStorageEnabled[] = "dom.storage.enabled"; + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::dom::ipc; +using mozilla::BasePrincipal; +using mozilla::PrincipalOriginAttributes; +using mozilla::TimeStamp; +using mozilla::TimeDuration; +using mozilla::dom::cache::CacheStorage; + +static LazyLogModule gDOMLeakPRLog("DOMLeak"); + +nsGlobalWindow::WindowByIdTable *nsGlobalWindow::sWindowsById = nullptr; +bool nsGlobalWindow::sWarnedAboutWindowInternal = false; +bool nsGlobalWindow::sIdleObserversAPIFuzzTimeDisabled = false; + +static int32_t gRefCnt = 0; +static int32_t gOpenPopupSpamCount = 0; +static PopupControlState gPopupControlState = openAbused; +static int32_t gRunningTimeoutDepth = 0; +static bool gMouseDown = false; +static bool gDragServiceDisabled = false; +static FILE *gDumpFile = nullptr; +static uint32_t gSerialCounter = 0; +static TimeStamp gLastRecordedRecentTimeouts; +#define STATISTICS_INTERVAL (30 * PR_MSEC_PER_SEC) + +#ifdef DEBUG_jst +int32_t gTimeoutCnt = 0; +#endif + +#if defined(DEBUG_bryner) || defined(DEBUG_chb) +#define DEBUG_PAGE_CACHE +#endif + +#define DOM_TOUCH_LISTENER_ADDED "dom-touch-listener-added" + +// The default shortest interval/timeout we permit +#define DEFAULT_MIN_TIMEOUT_VALUE 4 // 4ms +#define DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE 1000 // 1000ms +static int32_t gMinTimeoutValue; +static int32_t gMinBackgroundTimeoutValue; +inline int32_t +nsGlobalWindow::DOMMinTimeoutValue() const { + // First apply any back pressure delay that might be in effect. + int32_t value = std::max(mBackPressureDelayMS, 0); + // Don't use the background timeout value when there are audio contexts + // present, so that baackground audio can keep running smoothly. (bug 1181073) + bool isBackground = mAudioContexts.IsEmpty() && + (!mOuterWindow || mOuterWindow->IsBackground()); + return + std::max(isBackground ? gMinBackgroundTimeoutValue : gMinTimeoutValue, value); +} + +// The number of nested timeouts before we start clamping. HTML5 says 1, WebKit +// uses 5. +#define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5 + +// The longest interval (as PRIntervalTime) we permit, or that our +// timer code can handle, really. See DELAY_INTERVAL_LIMIT in +// nsTimerImpl.h for details. +#define DOM_MAX_TIMEOUT_VALUE DELAY_INTERVAL_LIMIT + +// The interval at which we execute idle callbacks +static uint32_t gThrottledIdlePeriodLength; + +#define DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH 10000 + +#define FORWARD_TO_OUTER(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsInnerWindow()) { \ + nsGlobalWindow *outer = GetOuterWindowInternal(); \ + if (!AsInner()->HasActiveDocument()) { \ + NS_WARNING(outer ? \ + "Inner window does not have active document." : \ + "No outer window available!"); \ + return err_rval; \ + } \ + return outer->method args; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_OUTER_OR_THROW(method, args, errorresult, err_rval) \ + PR_BEGIN_MACRO \ + MOZ_RELEASE_ASSERT(IsInnerWindow()); \ + nsGlobalWindow *outer = GetOuterWindowInternal(); \ + if (MOZ_LIKELY(AsInner()->HasActiveDocument())) { \ + return outer->method args; \ + } \ + if (!outer) { \ + NS_WARNING("No outer window available!"); \ + errorresult.Throw(NS_ERROR_NOT_INITIALIZED); \ + } else { \ + errorresult.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO); \ + } \ + return err_rval; \ + PR_END_MACRO + +#define FORWARD_TO_OUTER_VOID(method, args) \ + PR_BEGIN_MACRO \ + if (IsInnerWindow()) { \ + nsGlobalWindow *outer = GetOuterWindowInternal(); \ + if (!AsInner()->HasActiveDocument()) { \ + NS_WARNING(outer ? \ + "Inner window does not have active document." : \ + "No outer window available!"); \ + return; \ + } \ + outer->method args; \ + return; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_OUTER_CHROME(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsInnerWindow()) { \ + nsGlobalWindow *outer = GetOuterWindowInternal(); \ + if (!AsInner()->HasActiveDocument()) { \ + NS_WARNING(outer ? \ + "Inner window does not have active document." : \ + "No outer window available!"); \ + return err_rval; \ + } \ + return ((nsGlobalChromeWindow *)outer)->method args; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_INNER_CHROME(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsOuterWindow()) { \ + if (!mInnerWindow) { \ + NS_WARNING("No inner window available!"); \ + return err_rval; \ + } \ + return ((nsGlobalChromeWindow *)nsGlobalWindow::Cast(mInnerWindow))->method args; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsInnerWindow()) { \ + nsGlobalWindow *outer = GetOuterWindowInternal(); \ + if (!AsInner()->HasActiveDocument()) { \ + NS_WARNING(outer ? \ + "Inner window does not have active document." : \ + "No outer window available!"); \ + return err_rval; \ + } \ + return ((nsGlobalModalWindow *)outer)->method args; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_INNER(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsOuterWindow()) { \ + if (!mInnerWindow) { \ + NS_WARNING("No inner window available!"); \ + return err_rval; \ + } \ + return GetCurrentInnerWindowInternal()->method args; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_INNER_MODAL_CONTENT_WINDOW(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsOuterWindow()) { \ + if (!mInnerWindow) { \ + NS_WARNING("No inner window available!"); \ + return err_rval; \ + } \ + return ((nsGlobalModalWindow*)GetCurrentInnerWindowInternal())->method args; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_INNER_VOID(method, args) \ + PR_BEGIN_MACRO \ + if (IsOuterWindow()) { \ + 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 (IsOuterWindow()) { \ + if (!mInnerWindow) { \ + if (mIsClosed) { \ + return err_rval; \ + } \ + nsCOMPtr kungFuDeathGrip = GetDoc(); \ + ::mozilla::Unused << kungFuDeathGrip; \ + if (!mInnerWindow) { \ + return err_rval; \ + } \ + } \ + return GetCurrentInnerWindowInternal()->method args; \ + } \ + PR_END_MACRO + +// CIDs +static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID); + +#define NETWORK_UPLOAD_EVENT_NAME NS_LITERAL_STRING("moznetworkupload") +#define NETWORK_DOWNLOAD_EVENT_NAME NS_LITERAL_STRING("moznetworkdownload") + +/** + * An indirect observer object that means we don't have to implement nsIObserver + * on nsGlobalWindow, where any script could see it. + */ +class nsGlobalWindowObserver final : public nsIObserver, + public nsIInterfaceRequestor +{ +public: + explicit nsGlobalWindowObserver(nsGlobalWindow* aWindow) : mWindow(aWindow) {} + NS_DECL_ISUPPORTS + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) override + { + if (!mWindow) + return NS_OK; + return mWindow->Observe(aSubject, aTopic, aData); + } + void Forget() { mWindow = nullptr; } + NS_IMETHOD GetInterface(const nsIID& aIID, void** aResult) override + { + if (mWindow && aIID.Equals(NS_GET_IID(nsIDOMWindow)) && mWindow) { + return mWindow->QueryInterface(aIID, aResult); + } + return NS_NOINTERFACE; + } + +private: + ~nsGlobalWindowObserver() {} + + // This reference is non-owning and safe because it's cleared by + // nsGlobalWindow::CleanUp(). + nsGlobalWindow* MOZ_NON_OWNING_REF mWindow; +}; + +NS_IMPL_ISUPPORTS(nsGlobalWindowObserver, nsIObserver, nsIInterfaceRequestor) + +static already_AddRefed +CreateVoidVariant() +{ + RefPtr writable = new nsVariantCC(); + writable->SetAsVoid(); + return writable.forget(); +} + +nsresult +DialogValueHolder::Get(nsIPrincipal* aSubject, nsIVariant** aResult) +{ + nsCOMPtr result; + if (aSubject->SubsumesConsideringDomain(mOrigin)) { + result = mValue; + } else { + result = CreateVoidVariant(); + } + result.forget(aResult); + return NS_OK; +} + +void +DialogValueHolder::Get(JSContext* aCx, JS::Handle aScope, + nsIPrincipal* aSubject, + JS::MutableHandle aResult, + mozilla::ErrorResult& aError) +{ + if (aSubject->Subsumes(mOrigin)) { + aError = nsContentUtils::XPConnect()->VariantToJS(aCx, aScope, + mValue, aResult); + } else { + aResult.setUndefined(); + } +} + +void +nsGlobalWindow::PostThrottledIdleCallback() +{ + AssertIsOnMainThread(); + + if (mThrottledIdleRequestCallbacks.isEmpty()) + return; + + RefPtr request(mThrottledIdleRequestCallbacks.popFirst()); + // ownership transferred from mThrottledIdleRequestCallbacks to + // mIdleRequestCallbacks + mIdleRequestCallbacks.insertBack(request); + NS_IdleDispatchToCurrentThread(request.forget()); +} + +/* static */ void +nsGlobalWindow::InsertIdleCallbackIntoList(IdleRequest* aRequest, + IdleRequests& aList) +{ + aList.insertBack(aRequest); + aRequest->AddRef(); +} + +uint32_t +nsGlobalWindow::RequestIdleCallback(JSContext* aCx, + IdleRequestCallback& aCallback, + const IdleRequestOptions& aOptions, + ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + AssertIsOnMainThread(); + + uint32_t handle = ++mIdleRequestCallbackCounter; + + RefPtr request = + new IdleRequest(aCx, AsInner(), aCallback, handle); + + if (aOptions.mTimeout.WasPassed()) { + aError = request->SetTimeout(aOptions.mTimeout.Value()); + if (NS_WARN_IF(aError.Failed())) { + return 0; + } + } + + nsGlobalWindow* outer = GetOuterWindowInternal(); + if (outer && outer->AsOuter()->IsBackground()) { + // mThrottledIdleRequestCallbacks now owns request + InsertIdleCallbackIntoList(request, mThrottledIdleRequestCallbacks); + + NS_DelayedDispatchToCurrentThread( + NewRunnableMethod(this, &nsGlobalWindow::PostThrottledIdleCallback), + 10000); + } else { + MOZ_ASSERT(mThrottledIdleRequestCallbacks.isEmpty()); + + // mIdleRequestCallbacks now owns request + InsertIdleCallbackIntoList(request, mIdleRequestCallbacks); + + NS_IdleDispatchToCurrentThread(request.forget()); + } + + return handle; +} + +void +nsGlobalWindow::CancelIdleCallback(uint32_t aHandle) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + for (IdleRequest* r : mIdleRequestCallbacks) { + if (r->Handle() == aHandle) { + r->Cancel(); + break; + } + } +} + +void +nsGlobalWindow::DisableIdleCallbackRequests() +{ + while (!mIdleRequestCallbacks.isEmpty()) { + RefPtr request = mIdleRequestCallbacks.popFirst(); + request->Cancel(); + } + + while (!mThrottledIdleRequestCallbacks.isEmpty()) { + RefPtr request = mThrottledIdleRequestCallbacks.popFirst(); + request->Cancel(); + } +} + +void nsGlobalWindow::UnthrottleIdleCallbackRequests() +{ + AssertIsOnMainThread(); + + while (!mThrottledIdleRequestCallbacks.isEmpty()) { + RefPtr request(mThrottledIdleRequestCallbacks.popFirst()); + mIdleRequestCallbacks.insertBack(request); + NS_IdleDispatchToCurrentThread(request.forget()); + } +} + + +namespace mozilla { +namespace dom { +extern uint64_t +NextWindowID(); +} // namespace dom +} // namespace mozilla + +template +nsPIDOMWindow::nsPIDOMWindow(nsPIDOMWindowOuter *aOuterWindow) +: mFrameElement(nullptr), mDocShell(nullptr), mModalStateDepth(0), + mRunningTimeout(nullptr), mMutationBits(0), mIsDocumentLoaded(false), + mIsHandlingResizeEvent(false), mIsInnerWindow(aOuterWindow != nullptr), + mMayHavePaintEventListener(false), mMayHaveTouchEventListener(false), + mMayHaveMouseEnterLeaveEventListener(false), + mMayHavePointerEnterLeaveEventListener(false), + mInnerObjectsFreed(false), + mIsModalContentWindow(false), + mIsActive(false), mIsBackground(false), + mMediaSuspend(Preferences::GetBool("media.block-autoplay-until-in-foreground", true) ? + nsISuspendedTypes::SUSPENDED_BLOCK : nsISuspendedTypes::NONE_SUSPENDED), + mAudioMuted(false), mAudioVolume(1.0), mAudioCaptured(false), + mDesktopModeViewport(false), mIsRootOuterWindow(false), mInnerWindow(nullptr), + mOuterWindow(aOuterWindow), + // Make sure no actual window ends up with mWindowID == 0 + mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false), + mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false) + {} + +template +nsPIDOMWindow::~nsPIDOMWindow() {} + +/* static */ +nsPIDOMWindowOuter* +nsPIDOMWindowOuter::GetFromCurrentInner(nsPIDOMWindowInner* aInner) +{ + if (!aInner) { + return nullptr; + } + + nsPIDOMWindowOuter* outer = aInner->GetOuterWindow(); + if (!outer || outer->GetCurrentInnerWindow() != aInner) { + return nullptr; + } + + return outer; +} + +// DialogValueHolder CC goop. +NS_IMPL_CYCLE_COLLECTION(DialogValueHolder, mValue) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DialogValueHolder) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(DialogValueHolder) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DialogValueHolder) + +//***************************************************************************** +// nsOuterWindowProxy: Outer Window Proxy +//***************************************************************************** + +class nsOuterWindowProxy : public js::Wrapper +{ +public: + constexpr nsOuterWindowProxy() : js::Wrapper(0) { } + + virtual bool finalizeInBackground(const JS::Value& priv) const override { + return false; + } + + // Standard internal methods + virtual bool getOwnPropertyDescriptor(JSContext* cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc) + const override; + virtual bool defineProperty(JSContext* cx, + JS::Handle proxy, + JS::Handle id, + JS::Handle desc, + JS::ObjectOpResult &result) const override; + virtual bool ownPropertyKeys(JSContext *cx, + JS::Handle proxy, + JS::AutoIdVector &props) const override; + virtual bool delete_(JSContext *cx, JS::Handle proxy, + JS::Handle id, + JS::ObjectOpResult &result) const override; + + virtual bool getPrototypeIfOrdinary(JSContext* cx, + JS::Handle proxy, + bool* isOrdinary, + JS::MutableHandle protop) const override; + + virtual bool enumerate(JSContext *cx, JS::Handle proxy, + JS::MutableHandle vp) const override; + virtual bool preventExtensions(JSContext* cx, + JS::Handle proxy, + JS::ObjectOpResult& result) const override; + virtual bool isExtensible(JSContext *cx, JS::Handle proxy, bool *extensible) + const override; + virtual bool has(JSContext *cx, JS::Handle proxy, + JS::Handle id, bool *bp) const override; + virtual bool get(JSContext *cx, JS::Handle proxy, + JS::Handle receiver, + JS::Handle id, + JS::MutableHandle vp) const override; + virtual bool set(JSContext *cx, JS::Handle proxy, + JS::Handle id, JS::Handle v, + JS::Handle receiver, + JS::ObjectOpResult &result) const override; + + // SpiderMonkey extensions + virtual bool getPropertyDescriptor(JSContext* cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc) + const override; + virtual bool hasOwn(JSContext *cx, JS::Handle proxy, + JS::Handle id, bool *bp) const override; + virtual bool getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle proxy, + JS::AutoIdVector &props) const override; + virtual const char *className(JSContext *cx, + JS::Handle wrapper) const override; + + virtual void finalize(JSFreeOp *fop, JSObject *proxy) const override; + + virtual bool isCallable(JSObject *obj) const override { + return false; + } + virtual bool isConstructor(JSObject *obj) const override { + return false; + } + + virtual bool watch(JSContext *cx, JS::Handle proxy, + JS::Handle id, JS::Handle callable) const override; + virtual bool unwatch(JSContext *cx, JS::Handle proxy, + JS::Handle id) const override; + + static void ObjectMoved(JSObject *obj, const JSObject *old); + + static const nsOuterWindowProxy singleton; + +protected: + static nsGlobalWindow* GetOuterWindow(JSObject *proxy) + { + nsGlobalWindow* outerWindow = nsGlobalWindow::FromSupports( + static_cast(js::GetProxyExtra(proxy, 0).toPrivate())); + MOZ_ASSERT_IF(outerWindow, outerWindow->IsOuterWindow()); + return outerWindow; + } + + // False return value means we threw an exception. True return value + // but false "found" means we didn't have a subframe at that index. + bool GetSubframeWindow(JSContext *cx, JS::Handle proxy, + JS::Handle id, + JS::MutableHandle vp, + bool &found) const; + + // Returns a non-null window only if id is an index and we have a + // window at that index. + already_AddRefed + GetSubframeWindow(JSContext *cx, + JS::Handle proxy, + JS::Handle id) const; + + bool AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy, + JS::AutoIdVector &props) const; +}; + +static const js::ClassExtension OuterWindowProxyClassExtension = PROXY_MAKE_EXT( + nsOuterWindowProxy::ObjectMoved +); + +const js::Class OuterWindowProxyClass = PROXY_CLASS_WITH_EXT( + "Proxy", + 0, /* additional class flags */ + &OuterWindowProxyClassExtension); + +const char * +nsOuterWindowProxy::className(JSContext *cx, JS::Handle proxy) const +{ + MOZ_ASSERT(js::IsProxy(proxy)); + + return "Window"; +} + +void +nsOuterWindowProxy::finalize(JSFreeOp *fop, JSObject *proxy) const +{ + nsGlobalWindow* outerWindow = GetOuterWindow(proxy); + if (outerWindow) { + outerWindow->ClearWrapper(); + + // 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::getPropertyDescriptor(JSContext* cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc) const +{ + // The only thing we can do differently from js::Wrapper is shadow stuff with + // our indexed properties, so we can just try getOwnPropertyDescriptor and if + // that gives us nothing call on through to js::Wrapper. + desc.object().set(nullptr); + if (!getOwnPropertyDescriptor(cx, proxy, id, desc)) { + return false; + } + + if (desc.object()) { + return true; + } + + return js::Wrapper::getPropertyDescriptor(cx, proxy, id, desc); +} + +bool +nsOuterWindowProxy::getOwnPropertyDescriptor(JSContext* cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc) + const +{ + bool found; + if (!GetSubframeWindow(cx, proxy, id, desc.value(), found)) { + return false; + } + if (found) { + FillPropertyDescriptor(desc, proxy, true); + return true; + } + // else fall through to js::Wrapper + + // When we change this to always claim the property is configurable (bug + // 1178639), update the comments in nsOuterWindowProxy::defineProperty + // accordingly. + return js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc); +} + +bool +nsOuterWindowProxy::defineProperty(JSContext* cx, + JS::Handle proxy, + JS::Handle id, + JS::Handle desc, + JS::ObjectOpResult &result) const +{ + if (IsArrayIndex(GetArrayIndexFromId(cx, 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(); + } + +#ifndef RELEASE_OR_BETA // To be turned on in bug 1178638. + // For now, allow chrome code to define non-configurable properties + // on windows, until we sort out what exactly the addon SDK is + // doing. In the meantime, this still allows us to test web compat + // behavior. + if (desc.hasConfigurable() && !desc.configurable() && + !nsContentUtils::IsCallerChrome()) { + return ThrowErrorMessage(cx, MSG_DEFINE_NON_CONFIGURABLE_PROP_ON_WINDOW); + } + + // Note that if hasConfigurable() is false we do NOT want to + // setConfigurable(true). That would make this code: + // + // var x; + // window.x = 5; + // + // fail, because the JS engine ends up converting the assignment into a define + // with !hasConfigurable(), but the var actually declared a non-configurable + // property on our underlying Window object, so the set would fail if we + // forced setConfigurable(true) here. What we want to do instead is change + // getOwnPropertyDescriptor to always claim configurable. See bug 1178639. +#endif + + return js::Wrapper::defineProperty(cx, proxy, id, desc, result); +} + +bool +nsOuterWindowProxy::ownPropertyKeys(JSContext *cx, + JS::Handle proxy, + JS::AutoIdVector &props) const +{ + // Just our indexed stuff followed by our "normal" own property names. + if (!AppendIndexedPropertyNames(cx, proxy, props)) { + return false; + } + + JS::AutoIdVector innerProps(cx); + if (!js::Wrapper::ownPropertyKeys(cx, proxy, innerProps)) { + return false; + } + return js::AppendUnique(cx, props, innerProps); +} + +bool +nsOuterWindowProxy::delete_(JSContext *cx, JS::Handle proxy, + JS::Handle id, JS::ObjectOpResult &result) const +{ + if (nsCOMPtr frame = GetSubframeWindow(cx, proxy, id)) { + // Fail (which means throw if strict, else return false). + return result.failCantDeleteWindowElement(); + } + + if (IsArrayIndex(GetArrayIndexFromId(cx, id))) { + // Indexed, but not supported. Spec says return true. + return result.succeed(); + } + + return js::Wrapper::delete_(cx, proxy, id, result); +} + +bool +nsOuterWindowProxy::getPrototypeIfOrdinary(JSContext* cx, + JS::Handle proxy, + bool* isOrdinary, + JS::MutableHandle protop) const +{ + // Window's [[GetPrototypeOf]] trap isn't the ordinary definition: + // + // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-getprototypeof + // + // We nonetheless can implement it with a static [[Prototype]], because + // wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp) supply + // all non-ordinary behavior. + // + // But from a spec point of view, it's the exact same object in both cases -- + // only the observer's changed. So this getPrototypeIfOrdinary trap on the + // non-wrapper object *must* report non-ordinary, even if static [[Prototype]] + // usually means ordinary. + *isOrdinary = false; + return true; +} + +bool +nsOuterWindowProxy::preventExtensions(JSContext* cx, + JS::Handle proxy, + JS::ObjectOpResult& result) const +{ + // If [[Extensible]] could be false, then navigating a window could navigate + // to a window that's [[Extensible]] after being at one that wasn't: an + // invariant violation. So never change a window's extensibility. + return result.failCantPreventExtensions(); +} + +bool +nsOuterWindowProxy::isExtensible(JSContext *cx, JS::Handle proxy, + bool *extensible) const +{ + // See above. + *extensible = true; + return true; +} + +bool +nsOuterWindowProxy::has(JSContext *cx, JS::Handle proxy, + JS::Handle id, bool *bp) const +{ + if (nsCOMPtr frame = GetSubframeWindow(cx, proxy, id)) { + *bp = true; + return true; + } + + return js::Wrapper::has(cx, proxy, id, bp); +} + +bool +nsOuterWindowProxy::hasOwn(JSContext *cx, JS::Handle proxy, + JS::Handle id, bool *bp) const +{ + if (nsCOMPtr frame = GetSubframeWindow(cx, proxy, id)) { + *bp = true; + return true; + } + + return js::Wrapper::hasOwn(cx, proxy, id, bp); +} + +bool +nsOuterWindowProxy::get(JSContext *cx, JS::Handle proxy, + JS::Handle receiver, + JS::Handle id, + JS::MutableHandle vp) const +{ + if (id == nsDOMClassInfo::sWrappedJSObject_id && + xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) { + vp.set(JS::ObjectValue(*proxy)); + return true; + } + + bool found; + if (!GetSubframeWindow(cx, proxy, id, vp, found)) { + return false; + } + if (found) { + return true; + } + // Else fall through to js::Wrapper + + return js::Wrapper::get(cx, proxy, receiver, id, vp); +} + +bool +nsOuterWindowProxy::set(JSContext *cx, JS::Handle proxy, + JS::Handle id, + JS::Handle v, + JS::Handle receiver, + JS::ObjectOpResult &result) const +{ + if (IsArrayIndex(GetArrayIndexFromId(cx, 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(); + } + + return js::Wrapper::set(cx, proxy, id, v, receiver, result); +} + +bool +nsOuterWindowProxy::getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle proxy, + JS::AutoIdVector &props) const +{ + // BaseProxyHandler::keys seems to do what we want here: call + // ownPropertyKeys and then filter out the non-enumerable properties. + return js::BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props); +} + +bool +nsOuterWindowProxy::enumerate(JSContext *cx, JS::Handle proxy, + JS::MutableHandle objp) const +{ + // BaseProxyHandler::enumerate seems to do what we want here: fall + // back on the property names returned from js::GetPropertyKeys() + return js::BaseProxyHandler::enumerate(cx, proxy, objp); +} + +bool +nsOuterWindowProxy::GetSubframeWindow(JSContext *cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle vp, + bool& found) const +{ + nsCOMPtr frame = GetSubframeWindow(cx, proxy, id); + if (!frame) { + found = false; + return true; + } + + found = true; + // Just return the window's global + nsGlobalWindow* global = nsGlobalWindow::Cast(frame); + frame->EnsureInnerWindow(); + JSObject* obj = global->FastGetGlobalJSObject(); + // This null check fixes a hard-to-reproduce crash that occurs when we + // get here when we're mid-call to nsDocShell::Destroy. See bug 640904 + // comment 105. + if (MOZ_UNLIKELY(!obj)) { + return xpc::Throw(cx, NS_ERROR_FAILURE); + } + JS::ExposeObjectToActiveJS(obj); + vp.setObject(*obj); + return JS_WrapValue(cx, vp); +} + +already_AddRefed +nsOuterWindowProxy::GetSubframeWindow(JSContext *cx, + JS::Handle proxy, + JS::Handle id) const +{ + uint32_t index = GetArrayIndexFromId(cx, id); + if (!IsArrayIndex(index)) { + return nullptr; + } + + nsGlobalWindow* win = GetOuterWindow(proxy); + MOZ_ASSERT(win->IsOuterWindow()); + return win->IndexedGetterOuter(index); +} + +bool +nsOuterWindowProxy::AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy, + JS::AutoIdVector &props) const +{ + uint32_t length = GetOuterWindow(proxy)->Length(); + MOZ_ASSERT(int32_t(length) >= 0); + if (!props.reserve(props.length() + length)) { + return false; + } + for (int32_t i = 0; i < int32_t(length); ++i) { + if (!props.append(INT_TO_JSID(i))) { + return false; + } + } + + return true; +} + +bool +nsOuterWindowProxy::watch(JSContext *cx, JS::Handle proxy, + JS::Handle id, JS::Handle callable) const +{ + return js::WatchGuts(cx, proxy, id, callable); +} + +bool +nsOuterWindowProxy::unwatch(JSContext *cx, JS::Handle proxy, + JS::Handle id) const +{ + return js::UnwatchGuts(cx, proxy, id); +} + +void +nsOuterWindowProxy::ObjectMoved(JSObject *obj, const JSObject *old) +{ + nsGlobalWindow* outerWindow = GetOuterWindow(obj); + if (outerWindow) { + outerWindow->UpdateWrapper(obj, old); + } +} + +const nsOuterWindowProxy +nsOuterWindowProxy::singleton; + +class nsChromeOuterWindowProxy : public nsOuterWindowProxy +{ +public: + constexpr nsChromeOuterWindowProxy() : nsOuterWindowProxy() { } + + virtual const char *className(JSContext *cx, JS::Handle wrapper) const override; + + static const nsChromeOuterWindowProxy singleton; +}; + +const char * +nsChromeOuterWindowProxy::className(JSContext *cx, + JS::Handle proxy) const +{ + MOZ_ASSERT(js::IsProxy(proxy)); + + return "ChromeWindow"; +} + +const nsChromeOuterWindowProxy +nsChromeOuterWindowProxy::singleton; + +static JSObject* +NewOuterWindowProxy(JSContext *cx, JS::Handle global, bool isChrome) +{ + JSAutoCompartment ac(cx, global); + MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(global) == global); + + js::WrapperOptions options; + options.setClass(&OuterWindowProxyClass); + options.setSingleton(true); + JSObject *obj = js::Wrapper::New(cx, global, + isChrome ? &nsChromeOuterWindowProxy::singleton + : &nsOuterWindowProxy::singleton, + options); + MOZ_ASSERT_IF(obj, js::IsWindowProxy(obj)); + return obj; +} + +namespace { + +// The maximum number of timer callbacks we will try to run in a single event +// loop runnable. +#define DEFAULT_TARGET_MAX_CONSECUTIVE_CALLBACKS 5 +uint32_t gTargetMaxConsecutiveCallbacks; + +// The number of queued runnables within the TabGroup ThrottledEventQueue +// at which to begin applying back pressure to the window. +#define DEFAULT_THROTTLED_EVENT_QUEUE_BACK_PRESSURE 5000 +static uint32_t gThrottledEventQueueBackPressure; + +// The amount of delay to apply to timers when back pressure is triggered. +// As the length of the ThrottledEventQueue grows delay is increased. The +// delay is scaled such that every kThrottledEventQueueBackPressure runnables +// in the queue equates to an additional kBackPressureDelayMS. +#define DEFAULT_BACK_PRESSURE_DELAY_MS 250 +static uint32_t gBackPressureDelayMS; + +// This defines a limit for how much the delay must drop before we actually +// reduce back pressure throttle amount. This makes the throttle delay +// a bit "sticky" once we enter back pressure. +#define DEFAULT_BACK_PRESSURE_DELAY_REDUCTION_THRESHOLD_MS 1000 +static uint32_t gBackPressureDelayReductionThresholdMS; + +// The minimum delay we can reduce back pressure to before we just floor +// the value back to zero. This allows us to ensure that we can exit +// back pressure event if there are always a small number of runnables +// queued up. +#define DEFAULT_BACK_PRESSURE_DELAY_MINIMUM_MS 100 +static uint32_t gBackPressureDelayMinimumMS; + +} // anonymous namespace + +//***************************************************************************** +//*** nsGlobalWindow: Object Management +//***************************************************************************** + +nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow) + : nsPIDOMWindow(aOuterWindow ? aOuterWindow->AsOuter() : nullptr), + mIdleFuzzFactor(0), + mIdleCallbackIndex(-1), + mCurrentlyIdle(false), + mAddActiveEventFuzzTime(true), + mFullScreen(false), + mFullscreenMode(false), + mIsClosed(false), + mInClose(false), + mHavePendingClose(false), + mHadOriginalOpener(false), + mOriginalOpenerWasSecureContext(false), + mIsPopupSpam(false), + mBlockScriptedClosingFlag(false), + mWasOffline(false), + mHasHadSlowScript(false), + mNotifyIdleObserversIdleOnThaw(false), + mNotifyIdleObserversActiveOnThaw(false), + mCreatingInnerWindow(false), + mIsChrome(false), + mCleanMessageManager(false), + mNeedsFocus(true), + mHasFocus(false), + mShowFocusRingForContent(false), + mFocusByKeyOccurred(false), + mHasGamepad(false), + mHasVREvents(false), +#ifdef MOZ_GAMEPAD + mHasSeenGamepadInput(false), +#endif + mNotifiedIDDestroyed(false), + mAllowScriptsToClose(false), + mTimeoutInsertionPoint(nullptr), + mTimeoutIdCounter(1), + mTimeoutFiringDepth(0), + mSuspendDepth(0), + mFreezeDepth(0), + mBackPressureDelayMS(0), + mFocusMethod(0), + mSerial(0), + mIdleCallbackTimeoutCounter(1), + mIdleRequestCallbackCounter(1), +#ifdef DEBUG + mSetOpenerWindowCalled(false), +#endif +#ifdef MOZ_B2G + mNetworkUploadObserverEnabled(false), + mNetworkDownloadObserverEnabled(false), +#endif + mCleanedUp(false), + mDialogAbuseCount(0), + mAreDialogsEnabled(true), +#ifdef DEBUG + mIsValidatingTabGroup(false), +#endif + mCanSkipCCGeneration(0) +{ + AssertIsOnMainThread(); + + nsLayoutStatics::AddRef(); + + // Initialize the PRCList (this). + PR_INIT_CLIST(this); + + if (aOuterWindow) { + // |this| is an inner window, add this inner window to the outer + // window list of inners. + PR_INSERT_AFTER(this, aOuterWindow); + + mObserver = new nsGlobalWindowObserver(this); + if (mObserver) { + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + // Watch for online/offline status changes so we can fire events. Use + // a strong reference. + os->AddObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, + false); + + // Watch for dom-storage2-changed so we can fire storage + // events. Use a strong reference. + os->AddObserver(mObserver, "dom-storage2-changed", false); + } + + Preferences::AddStrongObserver(mObserver, "intl.accept_languages"); + } + } else { + // |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. + + gRefCnt++; + + static bool sFirstTime = true; + if (sFirstTime) { + Preferences::AddIntVarCache(&gMinTimeoutValue, + "dom.min_timeout_value", + DEFAULT_MIN_TIMEOUT_VALUE); + Preferences::AddIntVarCache(&gMinBackgroundTimeoutValue, + "dom.min_background_timeout_value", + DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE); + Preferences::AddBoolVarCache(&sIdleObserversAPIFuzzTimeDisabled, + "dom.idle-observers-api.fuzz_time.disabled", + false); + + Preferences::AddUintVarCache(&gThrottledIdlePeriodLength, + "dom.idle_period.throttled_length", + DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH); + + Preferences::AddUintVarCache(&gThrottledEventQueueBackPressure, + "dom.timeout.throttled_event_queue_back_pressure", + DEFAULT_THROTTLED_EVENT_QUEUE_BACK_PRESSURE); + Preferences::AddUintVarCache(&gBackPressureDelayMS, + "dom.timeout.back_pressure_delay_ms", + DEFAULT_BACK_PRESSURE_DELAY_MS); + Preferences::AddUintVarCache(&gBackPressureDelayReductionThresholdMS, + "dom.timeout.back_pressure_delay_reduction_threshold_ms", + DEFAULT_BACK_PRESSURE_DELAY_REDUCTION_THRESHOLD_MS); + Preferences::AddUintVarCache(&gBackPressureDelayMinimumMS, + "dom.timeout.back_pressure_delay_minimum_ms", + DEFAULT_BACK_PRESSURE_DELAY_MINIMUM_MS); + + Preferences::AddUintVarCache(&gTargetMaxConsecutiveCallbacks, + "dom.timeout.max_consecutive_callbacks", + DEFAULT_TARGET_MAX_CONSECUTIVE_CALLBACKS); + + sFirstTime = false; + } + + if (gDumpFile == nullptr) { + const nsAdoptingCString& fname = + Preferences::GetCString("browser.dom.window.dump.file"); + if (!fname.IsEmpty()) { + // if this fails to open, Dump() knows to just go to stdout + // on null. + gDumpFile = fopen(fname, "wb+"); + } else { + gDumpFile = stdout; + } + } + + mSerial = ++gSerialCounter; + +#ifdef DEBUG + if (!PR_GetEnv("MOZ_QUIET")) { + printf_stderr("++DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p]\n", + gRefCnt, + static_cast(ToCanonicalSupports(this)), + getpid(), + gSerialCounter, + static_cast(ToCanonicalSupports(aOuterWindow))); + } +#endif + + if (gDOMLeakPRLog) + MOZ_LOG(gDOMLeakPRLog, LogLevel::Debug, + ("DOMWINDOW %p created outer=%p", this, aOuterWindow)); + + NS_ASSERTION(sWindowsById, "Windows hash table must be created!"); + NS_ASSERTION(!sWindowsById->Get(mWindowID), + "This window shouldn't be in the hash table yet!"); + // We seem to see crashes in release builds because of null |sWindowsById|. + if (sWindowsById) { + sWindowsById->Put(mWindowID, this); + } +} + +#ifdef DEBUG + +/* static */ +void +nsGlobalWindow::AssertIsOnMainThread() +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +#endif // DEBUG + +/* static */ +void +nsGlobalWindow::Init() +{ + AssertIsOnMainThread(); + + NS_ASSERTION(gDOMLeakPRLog, "gDOMLeakPRLog should have been initialized!"); + + sWindowsById = new WindowByIdTable(); +} + +nsGlobalWindow::~nsGlobalWindow() +{ + AssertIsOnMainThread(); + + DisconnectEventTargetObjects(); + + // We have to check if sWindowsById isn't null because ::Shutdown might have + // been called. + if (sWindowsById) { + NS_ASSERTION(sWindowsById->Get(mWindowID), + "This window should be in the hash table"); + sWindowsById->Remove(mWindowID); + } + + --gRefCnt; + +#ifdef DEBUG + if (!PR_GetEnv("MOZ_QUIET")) { + 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); + } + } + + nsGlobalWindow* outer = nsGlobalWindow::Cast(mOuterWindow); + printf_stderr("--DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p] [url = %s]\n", + gRefCnt, + static_cast(ToCanonicalSupports(this)), + getpid(), + mSerial, + static_cast(ToCanonicalSupports(outer)), + url.get()); + } +#endif + + if (gDOMLeakPRLog) + MOZ_LOG(gDOMLeakPRLog, LogLevel::Debug, + ("DOMWINDOW %p destroyed", this)); + + if (IsOuterWindow()) { + JSObject *proxy = GetWrapperPreserveColor(); + if (proxy) { + js::SetProxyExtra(proxy, 0, 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. + + nsGlobalWindow *w; + while ((w = (nsGlobalWindow *)PR_LIST_HEAD(this)) != this) { + PR_REMOVE_AND_INIT_LINK(w); + } + + DropOuterWindowDocs(); + } else { + Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS, + mMutationBits ? 1 : 0); + + if (mListenerManager) { + mListenerManager->Disconnect(); + mListenerManager = nullptr; + } + + // An inner window is destroyed, pull it out of the outer window's + // list if inner windows. + + PR_REMOVE_LINK(this); + + // If our outer window's inner window is this window, null out the + // outer window's reference to this window that's being deleted. + nsGlobalWindow *outer = GetOuterWindowInternal(); + if (outer) { + outer->MaybeClearInnerWindow(this); + } + } + + // We don't have to leave the tab group if we are an inner window. + if (mTabGroup && IsOuterWindow()) { + mTabGroup->Leave(AsOuter()); + } + + // Outer windows are always supposed to call CleanUp before letting themselves + // be destroyed. And while CleanUp generally seems to be intended to clean up + // outers, we've historically called it for both. Changing this would probably + // involve auditing all of the references that inners and outers can have, and + // separating the handling into CleanUp() and FreeInnerObjects. + if (IsInnerWindow()) { + CleanUp(); + } else { + MOZ_ASSERT(mCleanedUp); + } + + nsCOMPtr ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID); + if (ac) + ac->RemoveWindowAsListener(this); + + nsLayoutStatics::Release(); +} + +void +nsGlobalWindow::AddEventTargetObject(DOMEventTargetHelper* aObject) +{ + MOZ_ASSERT(IsInnerWindow()); + mEventTargetObjects.PutEntry(aObject); +} + +void +nsGlobalWindow::RemoveEventTargetObject(DOMEventTargetHelper* aObject) +{ + MOZ_ASSERT(IsInnerWindow()); + mEventTargetObjects.RemoveEntry(aObject); +} + +void +nsGlobalWindow::DisconnectEventTargetObjects() +{ + for (auto iter = mEventTargetObjects.ConstIter(); !iter.Done(); + iter.Next()) { + RefPtr target = iter.Get()->GetKey(); + target->DisconnectFromOwner(); + } + mEventTargetObjects.Clear(); +} + +// static +void +nsGlobalWindow::ShutDown() +{ + AssertIsOnMainThread(); + + if (gDumpFile && gDumpFile != stdout) { + fclose(gDumpFile); + } + gDumpFile = nullptr; + + delete sWindowsById; + sWindowsById = nullptr; +} + +// static +void +nsGlobalWindow::CleanupCachedXBLHandlers(nsGlobalWindow* aWindow) +{ + if (aWindow->mCachedXBLPrototypeHandlers && + aWindow->mCachedXBLPrototypeHandlers->Count() > 0) { + aWindow->mCachedXBLPrototypeHandlers->Clear(); + } +} + +void +nsGlobalWindow::MaybeForgiveSpamCount() +{ + if (IsOuterWindow() && + IsPopupSpamWindow()) { + SetIsPopupSpamWindow(false); + } +} + +void +nsGlobalWindow::SetIsPopupSpamWindow(bool aIsPopupSpam) +{ + MOZ_ASSERT(IsOuterWindow()); + + mIsPopupSpam = aIsPopupSpam; + if (aIsPopupSpam) { + ++gOpenPopupSpamCount; + } else { + --gOpenPopupSpamCount; + NS_ASSERTION(gOpenPopupSpamCount >= 0, + "Unbalanced decrement of gOpenPopupSpamCount"); + } +} + +void +nsGlobalWindow::DropOuterWindowDocs() +{ + MOZ_ASSERT(IsOuterWindow()); + MOZ_ASSERT_IF(mDoc, !mDoc->EventHandlingSuppressed()); + mDoc = nullptr; + mSuspendedDoc = nullptr; +} + +void +nsGlobalWindow::CleanUp() +{ + // Guarantee idempotence. + if (mCleanedUp) + return; + mCleanedUp = true; + + StartDying(); + + DisconnectEventTargetObjects(); + + if (mObserver) { + nsCOMPtr os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC); + os->RemoveObserver(mObserver, "dom-storage2-changed"); + } + +#ifdef MOZ_B2G + DisableNetworkEvent(eNetworkUpload); + DisableNetworkEvent(eNetworkDownload); +#endif // MOZ_B2G + + if (mIdleService) { + mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S); + } + + Preferences::RemoveObserver(mObserver, "intl.accept_languages"); + + // Drop its reference to this dying window, in case for some bogus reason + // the object stays around. + mObserver->Forget(); + } + + if (mNavigator) { + mNavigator->Invalidate(); + mNavigator = nullptr; + } + + mScreen = nullptr; + mMenubar = nullptr; + mToolbar = nullptr; + mLocationbar = nullptr; + mPersonalbar = nullptr; + mStatusbar = nullptr; + mScrollbars = nullptr; + mLocation = nullptr; + mHistory = nullptr; + mCustomElements = nullptr; + mFrames = nullptr; + mWindowUtils = nullptr; + mApplicationCache = nullptr; + mIndexedDB = nullptr; + + mConsole = nullptr; + + mExternal = nullptr; + + mMozSelfSupport = nullptr; + + mPerformance = nullptr; + +#ifdef MOZ_WEBSPEECH + mSpeechSynthesis = nullptr; +#endif + +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) + mOrientationChangeObserver = nullptr; +#endif + + ClearControllers(); + + mOpener = nullptr; // Forces Release + if (mContext) { + mContext = nullptr; // Forces Release + } + mChromeEventHandler = nullptr; // Forces Release + mParentTarget = nullptr; + + if (IsOuterWindow()) { + nsGlobalWindow* inner = GetCurrentInnerWindowInternal(); + if (inner) { + inner->CleanUp(); + } + } + + if (IsInnerWindow()) { + DisableGamepadUpdates(); + mHasGamepad = false; + DisableVRUpdates(); + mHasVREvents = false; +#ifdef MOZ_B2G + DisableTimeChangeNotifications(); +#endif + DisableIdleCallbackRequests(); + } else { + MOZ_ASSERT(!mHasGamepad); + MOZ_ASSERT(!mHasVREvents); + } + + if (mCleanMessageManager) { + MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned"); + nsGlobalChromeWindow *asChrome = static_cast(this); + if (asChrome->mMessageManager) { + static_cast( + asChrome->mMessageManager.get())->Disconnect(); + } + } + + mArguments = nullptr; + mDialogArguments = nullptr; + + CleanupCachedXBLHandlers(this); + + for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { + mAudioContexts[i]->Shutdown(); + } + mAudioContexts.Clear(); + + if (mIdleTimer) { + mIdleTimer->Cancel(); + mIdleTimer = nullptr; + } + + mServiceWorkerRegistrationTable.Clear(); +} + +void +nsGlobalWindow::ClearControllers() +{ + if (mControllers) { + uint32_t count; + mControllers->GetControllerCount(&count); + + while (count--) { + nsCOMPtr controller; + mControllers->GetControllerAt(count, getter_AddRefs(controller)); + + nsCOMPtr context = do_QueryInterface(controller); + if (context) + context->SetCommandContext(nullptr); + } + + mControllers = nullptr; + } +} + +void +nsGlobalWindow::FreeInnerObjects() +{ + NS_ASSERTION(IsInnerWindow(), "Don't free inner objects on an outer window"); + + // Make sure that this is called before we null out the document and + // other members that the window destroyed observers could + // re-create. + NotifyDOMWindowDestroyed(this); + if (auto* reporter = nsWindowMemoryReporter::Get()) { + reporter->ObserveDOMWindowDetached(this); + } + + mInnerObjectsFreed = true; + + // Kill all of the workers for this window. + mozilla::dom::workers::CancelWorkersForWindow(AsInner()); + + ClearAllTimeouts(); + + if (mIdleTimer) { + mIdleTimer->Cancel(); + mIdleTimer = nullptr; + } + + mIdleObservers.Clear(); + + DisableIdleCallbackRequests(); + + mChromeEventHandler = nullptr; + + if (mListenerManager) { + mListenerManager->Disconnect(); + mListenerManager = nullptr; + } + + mLocation = nullptr; + mHistory = nullptr; + mCustomElements = nullptr; + + if (mNavigator) { + mNavigator->OnNavigation(); + mNavigator->Invalidate(); + mNavigator = nullptr; + } + + if (mScreen) { + mScreen = nullptr; + } + +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) + mOrientationChangeObserver = nullptr; +#endif + + if (mDoc) { + // Remember the document's principal and URI. + mDocumentPrincipal = mDoc->NodePrincipal(); + mDocumentURI = mDoc->GetDocumentURI(); + mDocBaseURI = mDoc->GetDocBaseURI(); + + while (mDoc->EventHandlingSuppressed()) { + mDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents, false); + } + + // Note: we don't have to worry about eAnimationsOnly suppressions because + // they won't leak. + } + + // Remove our reference to the document and the document principal. + mFocusedNode = nullptr; + + if (mApplicationCache) { + static_cast(mApplicationCache.get())->Disconnect(); + mApplicationCache = nullptr; + } + + mIndexedDB = nullptr; + + UnlinkHostObjectURIs(); + + NotifyWindowIDDestroyed("inner-window-destroyed"); + + CleanupCachedXBLHandlers(this); + + for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { + mAudioContexts[i]->Shutdown(); + } + mAudioContexts.Clear(); + +#ifdef MOZ_GAMEPAD + DisableGamepadUpdates(); + mHasGamepad = false; + mGamepads.Clear(); +#endif + DisableVRUpdates(); + mHasVREvents = false; + mVRDisplays.Clear(); +} + +//***************************************************************************** +// nsGlobalWindow::nsISupports +//***************************************************************************** + +// QueryInterface implementation for nsGlobalWindow +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindow) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + // Make sure this matches the cast in nsGlobalWindow::FromWrapper() + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventTarget) + NS_INTERFACE_MAP_ENTRY(nsIDOMWindow) + if (aIID.Equals(NS_GET_IID(nsIDOMWindowInternal))) { + foundInterface = static_cast(this); + if (!sWarnedAboutWindowInternal) { + sWarnedAboutWindowInternal = true; + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Extensions"), mDoc, + nsContentUtils::eDOM_PROPERTIES, + "nsIDOMWindowInternalWarning"); + } + } else + NS_INTERFACE_MAP_ENTRY(nsIGlobalObject) + NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObject) + NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget) + NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget) + if (aIID.Equals(NS_GET_IID(nsPIDOMWindowInner))) { + foundInterface = AsInner(); + } else + if (aIID.Equals(NS_GET_IID(mozIDOMWindow)) && IsInnerWindow()) { + foundInterface = AsInner(); + } else + if (aIID.Equals(NS_GET_IID(nsPIDOMWindowOuter))) { + foundInterface = AsOuter(); + } else + if (aIID.Equals(NS_GET_IID(mozIDOMWindowProxy)) && IsOuterWindow()) { + foundInterface = AsOuter(); + } else + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) +NS_INTERFACE_MAP_END + + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindow) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindow) + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindow) + if (tmp->IsBlackForCC(false)) { + if (nsCCUncollectableMarker::InGeneration(tmp->mCanSkipCCGeneration)) { + return true; + } + tmp->mCanSkipCCGeneration = nsCCUncollectableMarker::sGeneration; + if (tmp->mCachedXBLPrototypeHandlers) { + for (auto iter = tmp->mCachedXBLPrototypeHandlers->Iter(); + !iter.Done(); + iter.Next()) { + iter.Data().exposeToActiveJS(); + } + } + if (EventListenerManager* elm = tmp->GetExistingListenerManager()) { + elm->MarkForCC(); + } + tmp->UnmarkGrayTimers(); + return true; + } +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsGlobalWindow) + return tmp->IsBlackForCC(true); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGlobalWindow) + return tmp->IsBlackForCC(false); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END + +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + IdleObserverHolder& aField, + const char* aName, + unsigned aFlags) +{ + CycleCollectionNoteChild(aCallback, aField.mIdleObserver.get(), aName, aFlags); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalWindow) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow) + if (MOZ_UNLIKELY(cb.WantDebugInfo())) { + char name[512]; + nsAutoCString uri; + if (tmp->mDoc && tmp->mDoc->GetDocumentURI()) { + uri = tmp->mDoc->GetDocumentURI()->GetSpecOrDefault(); + } + SprintfLiteral(name, "nsGlobalWindow # %" PRIu64 " %s %s", tmp->mWindowID, + tmp->IsInnerWindow() ? "inner" : "outer", uri.get()); + cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); + } else { + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGlobalWindow, 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(mDialogArguments) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReturnValue) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerRegistrationTable) + +#ifdef MOZ_WEBSPEECH + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechSynthesis) +#endif + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOuterWindow) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager) + + for (Timeout* timeout = tmp->mTimeouts.getFirst(); + timeout; + timeout = timeout->getNext()) { + cb.NoteNativeChild(timeout, NS_CYCLE_COLLECTION_PARTICIPANT(Timeout)); + } + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHistory) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomElements) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStorage) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplicationCache) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWakeLock) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents) + + for (IdleRequest* request : tmp->mIdleRequestCallbacks) { + cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest)); + } + + for (IdleRequest* request : tmp->mThrottledIdleRequestCallbacks) { + cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest)); + } + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers) + +#ifdef MOZ_GAMEPAD + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads) +#endif + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRDisplays) + + // Traverse stuff from nsPIDOMWindow + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameElement) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedNode) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenubar) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mToolbar) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocationbar) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPersonalbar) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStatusbar) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollbars) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mU2F) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExternal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMozSelfSupport) + + tmp->TraverseHostObjectURIs(cb); + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow) + nsGlobalWindow::CleanupCachedXBLHandlers(tmp); + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext) + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDialogArguments) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mReturnValue) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator) + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance) + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mServiceWorkerRegistrationTable) + +#ifdef MOZ_WEBSPEECH + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechSynthesis) +#endif + + if (tmp->mOuterWindow) { + nsGlobalWindow::Cast(tmp->mOuterWindow)->MaybeClearInnerWindow(tmp); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOuterWindow) + } + + if (tmp->mListenerManager) { + tmp->mListenerManager->Disconnect(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager) + } + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistory) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomElements) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStorage) + if (tmp->mApplicationCache) { + static_cast(tmp->mApplicationCache.get())->Disconnect(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplicationCache) + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDoc) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleService) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWakeLock) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingStorageEvents) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleObservers) + +#ifdef MOZ_GAMEPAD + NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads) +#endif + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mVRDisplays) + + // Unlink stuff from nsPIDOMWindow + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameElement) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFocusedNode) + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMenubar) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mToolbar) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocationbar) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPersonalbar) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mStatusbar) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollbars) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mU2F) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMozSelfSupport) + + tmp->UnlinkHostObjectURIs(); + + tmp->DisableIdleCallbackRequests(); + + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +#ifdef DEBUG +void +nsGlobalWindow::RiskyUnlink() +{ + NS_CYCLE_COLLECTION_INNERNAME.Unlink(this); +} +#endif + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGlobalWindow) + if (tmp->mCachedXBLPrototypeHandlers) { + for (auto iter = tmp->mCachedXBLPrototypeHandlers->Iter(); + !iter.Done(); + iter.Next()) { + aCallbacks.Trace(&iter.Data(), "Cached XBL prototype handler", aClosure); + } + } + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +bool +nsGlobalWindow::IsBlackForCC(bool aTracingNeeded) +{ + if (!nsCCUncollectableMarker::sGeneration) { + return false; + } + + return (nsCCUncollectableMarker::InGeneration(GetMarkedCCGeneration()) || + IsBlack()) && + (!aTracingNeeded || + HasNothingToTrace(static_cast(this))); +} + +void +nsGlobalWindow::UnmarkGrayTimers() +{ + for (Timeout* timeout = mTimeouts.getFirst(); + timeout; + timeout = timeout->getNext()) { + if (timeout->mScriptHandler) { + timeout->mScriptHandler->MarkForCC(); + } + } +} + +//***************************************************************************** +// nsGlobalWindow::nsIScriptGlobalObject +//***************************************************************************** + +nsresult +nsGlobalWindow::EnsureScriptEnvironment() +{ + nsGlobalWindow* outer = GetOuterWindowInternal(); + if (!outer) { + NS_WARNING("No outer window available!"); + return NS_ERROR_FAILURE; + } + + if (outer->GetWrapperPreserveColor()) { + return NS_OK; + } + + NS_ASSERTION(!outer->GetCurrentInnerWindowInternal(), + "No cached wrapper, but we have an inner window?"); + + // If this window is a [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. + nsCOMPtr context = new nsJSContext(!IsFrame(), outer); + + NS_ASSERTION(!outer->mContext, "Will overwrite mContext!"); + + // should probably assert the context is clean??? + context->WillInitializeContext(); + + nsresult rv = context->InitContext(); + NS_ENSURE_SUCCESS(rv, rv); + + outer->mContext = context; + return NS_OK; +} + +nsIScriptContext * +nsGlobalWindow::GetScriptContext() +{ + nsGlobalWindow* outer = GetOuterWindowInternal(); + if (!outer) { + return nullptr; + } + return outer->mContext; +} + +JSObject * +nsGlobalWindow::GetGlobalJSObject() +{ + return FastGetGlobalJSObject(); +} + +void +nsGlobalWindow::TraceGlobalJSObject(JSTracer* aTrc) +{ + TraceWrapper(aTrc, "active window global"); +} + +bool +nsGlobalWindow::WouldReuseInnerWindow(nsIDocument* aNewDocument) +{ + MOZ_ASSERT(IsOuterWindow()); + + // 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; + } + + NS_ASSERTION(NS_IsAboutBlank(mDoc->GetDocumentURI()), + "How'd this happen?"); + + // Great, we're the original document, check for one of the other + // conditions. + + if (mDoc == aNewDocument) { + return true; + } + + bool equal; + if (NS_SUCCEEDED(mDoc->NodePrincipal()->Equals(aNewDocument->NodePrincipal(), + &equal)) && + equal) { + // The origin is the same. + return true; + } + + return false; +} + +void +nsGlobalWindow::SetInitialPrincipalToSubject() +{ + MOZ_ASSERT(IsOuterWindow()); + + // First, grab the subject principal. + nsCOMPtr newWindowPrincipal = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller(); + + // We should never create windows with an expanded principal. + // If we have a system principal, make sure we're not using it for a content + // docshell. + // NOTE: Please keep this logic in sync with nsWebShellWindow::Initialize(). + if (nsContentUtils::IsExpandedPrincipal(newWindowPrincipal) || + (nsContentUtils::IsSystemPrincipal(newWindowPrincipal) && + GetDocShell()->ItemType() != nsIDocShellTreeItem::typeChrome)) { + newWindowPrincipal = nullptr; + } + + // If there's an existing document, bail if it either: + if (mDoc) { + // (a) is not an initial about:blank document, or + if (!mDoc->IsInitialDocument()) + return; + // (b) already has the correct principal. + if (mDoc->NodePrincipal() == newWindowPrincipal) + return; + +#ifdef DEBUG + // If we have a document loaded at this point, it had better be about:blank. + // Otherwise, something is really weird. + nsCOMPtr uri; + mDoc->NodePrincipal()->GetURI(getter_AddRefs(uri)); + NS_ASSERTION(uri && NS_IsAboutBlank(uri) && + NS_IsAboutBlank(mDoc->GetDocumentURI()), + "Unexpected original document"); +#endif + } + + GetDocShell()->CreateAboutBlankContentViewer(newWindowPrincipal); + mDoc->SetIsInitialDocument(true); + + nsCOMPtr shell = GetDocShell()->GetPresShell(); + + if (shell && !shell->DidInitialize()) { + // Ensure that if someone plays with this document they will get + // layout happening. + nsRect r = shell->GetPresContext()->GetVisibleArea(); + shell->Initialize(r.width, r.height); + } +} + +PopupControlState +PushPopupControlState(PopupControlState aState, bool aForce) +{ + MOZ_ASSERT(NS_IsMainThread()); + + PopupControlState oldState = gPopupControlState; + + if (aState < gPopupControlState || aForce) { + gPopupControlState = aState; + } + + return oldState; +} + +void +PopPopupControlState(PopupControlState aState) +{ + MOZ_ASSERT(NS_IsMainThread()); + + gPopupControlState = aState; +} + +PopupControlState +nsGlobalWindow::PushPopupControlState(PopupControlState aState, + bool aForce) const +{ + return ::PushPopupControlState(aState, aForce); +} + +void +nsGlobalWindow::PopPopupControlState(PopupControlState aState) const +{ + ::PopPopupControlState(aState); +} + +PopupControlState +nsGlobalWindow::GetPopupControlState() const +{ + MOZ_ASSERT(NS_IsMainThread()); + return gPopupControlState; +} + +#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(nsGlobalWindow *aWindow); + + nsGlobalWindow* GetInnerWindow() { return mInnerWindow; } + + void DidRestoreWindow() + { + mInnerWindow = nullptr; + mInnerWindowReflector = nullptr; + } + +protected: + ~WindowStateHolder(); + + nsGlobalWindow *mInnerWindow; + // We hold onto this to make sure the inner window doesn't go away. The outer + // window ends up recalculating it anyway. + JS::PersistentRooted mInnerWindowReflector; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(WindowStateHolder, WINDOWSTATEHOLDER_IID) + +WindowStateHolder::WindowStateHolder(nsGlobalWindow* aWindow) + : mInnerWindow(aWindow), + mInnerWindowReflector(RootingCx(), aWindow->GetWrapper()) +{ + NS_PRECONDITION(aWindow, "null window"); + NS_PRECONDITION(aWindow->IsInnerWindow(), "Saving an outer window"); + + aWindow->Suspend(); + + // When a global goes into the bfcache, we disable script. + xpc::Scriptability::Get(mInnerWindowReflector).SetDocShellAllowsScript(false); +} + +WindowStateHolder::~WindowStateHolder() +{ + if (mInnerWindow) { + // This window was left in the bfcache and is now going away. We need to + // free it up. + // Note that FreeInnerObjects may already have been called on the + // inner window if its outer has already had SetDocShell(null) + // called. + mInnerWindow->FreeInnerObjects(); + } +} + +NS_IMPL_ISUPPORTS(WindowStateHolder, WindowStateHolder) + +// We need certain special behavior for remote XUL whitelisted domains, but we +// don't want that behavior to take effect in automation, because we whitelist +// all the mochitest domains. So we need to check a pref here. +static bool +TreatAsRemoteXUL(nsIPrincipal* aPrincipal) +{ + MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(aPrincipal)); + return nsContentUtils::AllowXULXBLForPrincipal(aPrincipal) && + !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false); +} + +static bool +EnablePrivilege(JSContext* cx, unsigned argc, JS::Value* vp) +{ + Telemetry::Accumulate(Telemetry::ENABLE_PRIVILEGE_EVER_CALLED, true); + return xpc::EnableUniversalXPConnect(cx); +} + +static const JSFunctionSpec EnablePrivilegeSpec[] = { + JS_FS("enablePrivilege", EnablePrivilege, 1, 0), + JS_FS_END +}; + +static bool +InitializeLegacyNetscapeObject(JSContext* aCx, JS::Handle aGlobal) +{ + JSAutoCompartment ac(aCx, aGlobal); + + // Note: MathJax depends on window.netscape being exposed. See bug 791526. + JS::Rooted obj(aCx); + obj = JS_DefineObject(aCx, aGlobal, "netscape", nullptr); + NS_ENSURE_TRUE(obj, false); + + obj = JS_DefineObject(aCx, obj, "security", nullptr); + NS_ENSURE_TRUE(obj, false); + + // We hide enablePrivilege behind a pref because it has been altered in a + // way that makes it fundamentally insecure to use in production. Mozilla + // uses this pref during automated testing to support legacy test code that + // uses enablePrivilege. If you're not doing test automation, you _must_ not + // flip this pref, or you will be exposing all your users to security + // vulnerabilities. + if (!xpc::IsInAutomation()) { + return true; + } + + /* Define PrivilegeManager object with the necessary "static" methods. */ + obj = JS_DefineObject(aCx, obj, "PrivilegeManager", nullptr); + NS_ENSURE_TRUE(obj, false); + + return JS_DefineFunctions(aCx, obj, EnablePrivilegeSpec); +} + +bool +nsGlobalWindow::ComputeIsSecureContext(nsIDocument* aDocument, SecureContextFlags aFlags) +{ + MOZ_ASSERT(IsOuterWindow()); + + nsCOMPtr principal = aDocument->NodePrincipal(); + if (nsContentUtils::IsSystemPrincipal(principal)) { + return true; + } + + // Implement https://w3c.github.io/webappsec-secure-contexts/#settings-object + // With some modifications to allow for aFlags. + + bool hadNonSecureContextCreator = false; + + nsPIDOMWindowOuter* parentOuterWin = GetScriptableParent(); + MOZ_ASSERT(parentOuterWin, "How can we get here? No docShell somehow?"); + if (nsGlobalWindow::Cast(parentOuterWin) != this) { + // There may be a small chance that parentOuterWin has navigated in + // the time that it took us to start loading this sub-document. If that + // were the case then parentOuterWin->GetCurrentInnerWindow() wouldn't + // return the window for the document that is embedding us. For this + // reason we only use the GetScriptableParent call above to check that we + // have a same-type parent, but actually get the inner window via the + // document that we know is embedding us. + nsIDocument* creatorDoc = aDocument->GetParentDocument(); + if (!creatorDoc) { + return false; // we must be tearing down + } + nsGlobalWindow* parentWin = + nsGlobalWindow::Cast(creatorDoc->GetInnerWindow()); + if (!parentWin) { + return false; // we must be tearing down + } + MOZ_ASSERT(parentWin == + nsGlobalWindow::Cast(parentOuterWin->GetCurrentInnerWindow()), + "Creator window mismatch while setting Secure Context state"); + if (aFlags != SecureContextFlags::eIgnoreOpener) { + hadNonSecureContextCreator = !parentWin->IsSecureContext(); + } else { + hadNonSecureContextCreator = !parentWin->IsSecureContextIfOpenerIgnored(); + } + } else if (mHadOriginalOpener) { + if (aFlags != SecureContextFlags::eIgnoreOpener) { + hadNonSecureContextCreator = !mOriginalOpenerWasSecureContext; + } + } + + if (hadNonSecureContextCreator) { + return false; + } + + if (nsContentUtils::HttpsStateIsModern(aDocument)) { + return true; + } + + if (principal->GetIsNullPrincipal()) { + nsCOMPtr uri = aDocument->GetOriginalURI(); + // IsOriginPotentiallyTrustworthy doesn't care about origin attributes so + // it doesn't actually matter what we use here, but reusing the document + // principal's attributes is convenient. + const PrincipalOriginAttributes& attrs = + BasePrincipal::Cast(principal)->OriginAttributesRef(); + // CreateCodebasePrincipal correctly gets a useful principal for blob: and + // other URI_INHERITS_SECURITY_CONTEXT URIs. + principal = BasePrincipal::CreateCodebasePrincipal(uri, attrs); + if (NS_WARN_IF(!principal)) { + return false; + } + } + + nsCOMPtr csm = + do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID); + NS_WARNING_ASSERTION(csm, "csm is null"); + if (csm) { + bool isTrustworthyOrigin = false; + csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin); + if (isTrustworthyOrigin) { + return true; + } + } + + return false; +} + +/** + * 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, + nsGlobalWindow* aNewInner, + nsIURI* aURI, + nsIPrincipal* aPrincipal, + JS::MutableHandle aGlobal, + bool aIsSecureContext) +{ + MOZ_ASSERT(aCx); + MOZ_ASSERT(aNewInner); + MOZ_ASSERT(aNewInner->IsInnerWindow()); + MOZ_ASSERT(aPrincipal); + + // DOMWindow with nsEP is not supported, we have to make sure + // no one creates one accidentally. + nsCOMPtr nsEP = do_QueryInterface(aPrincipal); + MOZ_RELEASE_ASSERT(!nsEP, "DOMWindow with nsEP is not supported"); + + nsGlobalWindow *top = nullptr; + if (aNewInner->GetOuterWindow()) { + top = aNewInner->GetTopInternal(); + } + + JS::CompartmentOptions options; + + // Sometimes add-ons load their own XUL windows, either as separate top-level + // windows or inside a browser element. In such cases we want to tag the + // window's compartment with the add-on ID. See bug 1092156. + if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { + options.creationOptions().setAddonId(MapURIToAddonID(aURI)); + } + + if (top && top->GetGlobalJSObject()) { + options.creationOptions().setSameZoneAs(top->GetGlobalJSObject()); + } + + options.creationOptions().setSecureContext(aIsSecureContext); + + xpc::InitGlobalObjectOptions(options, aPrincipal); + + // Determine if we need the Components object. + bool needComponents = nsContentUtils::IsSystemPrincipal(aPrincipal) || + TreatAsRemoteXUL(aPrincipal); + uint32_t flags = needComponents ? 0 : nsIXPConnect::OMIT_COMPONENTS_OBJECT; + flags |= nsIXPConnect::DONT_FIRE_ONNEWGLOBALHOOK; + + if (!WindowBinding::Wrap(aCx, aNewInner, aNewInner, options, + nsJSPrincipals::get(aPrincipal), false, aGlobal) || + !xpc::InitGlobalObject(aCx, aGlobal, flags)) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(aNewInner->GetWrapperPreserveColor() == aGlobal); + + // Set the location information for the new global, so that tools like + // about:memory may use that information + xpc::SetLocationForGlobal(aGlobal, aURI); + + if (!InitializeLegacyNetscapeObject(aCx, aGlobal)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +nsGlobalWindow::SetNewDocument(nsIDocument* aDocument, + nsISupports* aState, + bool aForceReuseInnerWindow) +{ + NS_PRECONDITION(mDocumentPrincipal == nullptr, + "mDocumentPrincipal prematurely set!"); + MOZ_ASSERT(aDocument); + + if (IsInnerWindow()) { + if (!mOuterWindow) { + return NS_ERROR_NOT_INITIALIZED; + } + + // Refuse to set a new document if the call came from an inner + // window that's not the current inner window. + if (mOuterWindow->GetCurrentInnerWindow() != AsInner()) { + return NS_ERROR_NOT_AVAILABLE; + } + + return GetOuterWindowInternal()->SetNewDocument(aDocument, aState, + aForceReuseInnerWindow); + } + + NS_PRECONDITION(IsOuterWindow(), "Must only be called on outer windows"); + + // Bail out early if we're in process of closing down the window. + NS_ENSURE_STATE(!mCleanedUp); + + NS_ASSERTION(!AsOuter()->GetCurrentInnerWindow() || + AsOuter()->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; + } + + nsCOMPtr oldDoc = mDoc; + + 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. + bool overrecursed = false; + JS_CHECK_RECURSION_CONSERVATIVE_DONT_REPORT(cx, overrecursed = true); + if (overrecursed) { + 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 = nsGlobalWindow::GetPrivateRoot(); + + if (privateRoot == AsOuter()) { + nsXBLService::AttachGlobalKeyHandler(mChromeEventHandler); + } + } + + /* 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(this, &nsGlobalWindow::ClearStatus)); + + // Sometimes, WouldReuseInnerWindow() returns true even if there's no inner + // window (see bug 776497). Be safe. + bool reUseInnerWindow = (aForceReuseInnerWindow || wouldReuseInnerWindow) && + GetCurrentInnerWindowInternal(); + + nsresult rv = NS_OK; + + // 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; + + // Take this opportunity to clear mSuspendedDoc. Our old inner window is now + // responsible for unsuspending it. + mSuspendedDoc = nullptr; + +#ifdef DEBUG + mLastOpenedURI = aDocument->GetDocumentURI(); +#endif + + mContext->WillInitializeContext(); + + nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal(); + + if (currentInner && currentInner->mNavigator) { + currentInner->mNavigator->OnNavigation(); + } + + RefPtr newInnerWindow; + bool createdInnerWindow = false; + + bool thisChrome = IsChromeWindow(); + + nsCOMPtr wsh = do_QueryInterface(aState); + NS_ASSERTION(!aState || wsh, "What kind of weird state are you giving me here?"); + + JS::Rooted newInnerGlobal(cx); + if (reUseInnerWindow) { + // We're reusing the current inner window. + NS_ASSERTION(!currentInner->IsFrozen(), + "We should never be reusing a shared inner window"); + newInnerWindow = currentInner; + newInnerGlobal = currentInner->GetWrapperPreserveColor(); + + if (aDocument != oldDoc) { + JS::ExposeObjectToActiveJS(newInnerGlobal); + } + + // We're reusing the inner window, but this still counts as a navigation, + // so all expandos and such defined on the outer window should go away. Force + // all Xray wrappers to be recomputed. + JS::Rooted rootedObject(cx, GetWrapper()); + if (!JS_RefreshCrossCompartmentWrappers(cx, rootedObject)) { + return NS_ERROR_FAILURE; + } + + // Inner windows are only reused for same-origin principals, but the principals + // don't necessarily match exactly. Update the principal on the compartment to + // match the new document. + // NB: We don't just call currentInner->RefreshCompartmentPrincipals() here + // because we haven't yet set its mDoc to aDocument. + JSCompartment *compartment = js::GetObjectCompartment(newInnerGlobal); +#ifdef DEBUG + bool sameOrigin = false; + nsIPrincipal *existing = + nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment)); + aDocument->NodePrincipal()->Equals(existing, &sameOrigin); + MOZ_ASSERT(sameOrigin); +#endif + MOZ_ASSERT_IF(aDocument == oldDoc, + xpc::GetCompartmentPrincipal(compartment) == + aDocument->NodePrincipal()); + if (aDocument != oldDoc) { + JS_SetCompartmentPrincipals(compartment, + nsJSPrincipals::get(aDocument->NodePrincipal())); + // Make sure we clear out the old content XBL scope, so the new one will + // get created with a principal that subsumes our new principal. + xpc::ClearContentXBLScope(newInnerGlobal); + } + } else { + if (aState) { + newInnerWindow = wsh->GetInnerWindow(); + newInnerGlobal = newInnerWindow->GetWrapperPreserveColor(); + } else { + if (thisChrome) { + newInnerWindow = nsGlobalChromeWindow::Create(this); + } else if (mIsModalContentWindow) { + newInnerWindow = nsGlobalModalWindow::Create(this); + } else { + newInnerWindow = nsGlobalWindow::Create(this); + } + + // 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; + // Every script context we are initialized with must create a + // new global. + rv = CreateNativeGlobalForInner(cx, newInnerWindow, + aDocument->GetDocumentURI(), + aDocument->NodePrincipal(), + &newInnerGlobal, + ComputeIsSecureContext(aDocument)); + NS_ASSERTION(NS_SUCCEEDED(rv) && newInnerGlobal && + newInnerWindow->GetWrapperPreserveColor() == newInnerGlobal, + "Failed to get script global"); + newInnerWindow->mIsSecureContextIfOpenerIgnored = + ComputeIsSecureContext(aDocument, SecureContextFlags::eIgnoreOpener); + + mCreatingInnerWindow = false; + createdInnerWindow = true; + + NS_ENSURE_SUCCESS(rv, rv); + } + + if (currentInner && currentInner->GetWrapperPreserveColor()) { + if (oldDoc == aDocument) { + // Move the navigator from the old inner window to the new one since + // this is a document.write. This is safe from a same-origin point of + // view because document.write can only be used by the same origin. + newInnerWindow->mNavigator = currentInner->mNavigator; + currentInner->mNavigator = nullptr; + if (newInnerWindow->mNavigator) { + newInnerWindow->mNavigator->SetWindow(newInnerWindow->AsInner()); + } + + // Make a copy of the old window's performance object on document.open. + // Note that we have to force eager creation of it here, because we need + // to grab the current document channel and whatnot before that changes. + currentInner->AsInner()->CreatePerformanceObjectIfNeeded(); + if (currentInner->mPerformance) { + newInnerWindow->mPerformance = + Performance::CreateForMainThread(newInnerWindow->AsInner(), + currentInner->mPerformance->GetDOMTiming(), + currentInner->mPerformance->GetChannel(), + currentInner->mPerformance->GetParentPerformance()); + } + } + + // Don't free objects on our current inner window if it's going to be + // held in the bfcache. + if (!currentInner->IsFrozen()) { + currentInner->FreeInnerObjects(); + } + } + + mInnerWindow = newInnerWindow->AsInner(); + + if (!GetWrapperPreserveColor()) { + JS::Rooted outer(cx, + NewOuterWindowProxy(cx, newInnerGlobal, thisChrome)); + NS_ENSURE_TRUE(outer, NS_ERROR_FAILURE); + + js::SetProxyExtra(outer, 0, js::PrivateValue(ToSupports(this))); + + // Inform the nsJSContext, which is the canonical holder of the outer. + mContext->SetWindowProxy(outer); + mContext->DidInitializeContext(); + + SetWrapper(mContext->GetWindowProxy()); + } else { + JS::ExposeObjectToActiveJS(newInnerGlobal); + JS::Rooted outerObject(cx, + NewOuterWindowProxy(cx, newInnerGlobal, thisChrome)); + if (!outerObject) { + NS_ERROR("out of memory"); + return NS_ERROR_FAILURE; + } + + JS::Rooted obj(cx, GetWrapperPreserveColor()); + + js::SetProxyExtra(obj, 0, js::PrivateValue(nullptr)); + js::SetProxyExtra(outerObject, 0, js::PrivateValue(nullptr)); + + outerObject = xpc::TransplantObject(cx, obj, outerObject); + if (!outerObject) { + NS_ERROR("unable to transplant wrappers, probably OOM"); + return NS_ERROR_FAILURE; + } + + js::SetProxyExtra(outerObject, 0, js::PrivateValue(ToSupports(this))); + + SetWrapper(outerObject); + + MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(outerObject) == newInnerGlobal); + + // Inform the nsJSContext, which is the canonical holder of the outer. + mContext->SetWindowProxy(outerObject); + } + + // Enter the new global's compartment. + JSAutoCompartment ac(cx, GetWrapperPreserveColor()); + + { + JS::Rooted outer(cx, GetWrapperPreserveColor()); + js::SetWindowProxy(cx, newInnerGlobal, outer); + } + + // Set scriptability based on the state of the docshell. + bool allow = GetDocShell()->GetCanExecuteScripts(); + xpc::Scriptability::Get(GetWrapperPreserveColor()).SetDocShellAllowsScript(allow); + + 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 + // nsGlobalWindow, so GetWrapperPreserveColor() on that outer + // nsGlobalWindow doesn't return null and nsGlobalWindow::OuterObject + // works correctly. + JS::Rooted unused(cx); + if (!JS_GetProperty(cx, newInnerGlobal, "window", &unused)) { + NS_ERROR("can't create the 'window' property"); + return NS_ERROR_FAILURE; + } + + // And same thing for the "self" property. + if (!JS_GetProperty(cx, newInnerGlobal, "self", &unused)) { + NS_ERROR("can't create the 'self' property"); + return NS_ERROR_FAILURE; + } + } + } + + JSAutoCompartment ac(cx, GetWrapperPreserveColor()); + + if (!aState && !reUseInnerWindow) { + // Loading a new page and creating a new inner window, *not* + // restoring from session history. + + // Now that both the the inner and outer windows are initialized + // let the script context do its magic to hook them together. + MOZ_ASSERT(mContext->GetWindowProxy() == GetWrapperPreserveColor()); +#ifdef DEBUG + JS::Rooted rootedJSObject(cx, GetWrapperPreserveColor()); + JS::Rooted proto1(cx), proto2(cx); + JS_GetPrototype(cx, rootedJSObject, &proto1); + JS_GetPrototype(cx, newInnerGlobal, &proto2); + NS_ASSERTION(proto1 == proto2, + "outer and inner globals should have the same prototype"); +#endif + + mInnerWindow->SyncStateFromParentWindow(); + } + + // Add an extra ref in case we release mContext during GC. + nsCOMPtr kungFuDeathGrip(mContext); + + aDocument->SetScriptGlobalObject(newInnerWindow); + MOZ_ASSERT(newInnerWindow->mTabGroup, + "We must have a TabGroup cached at this point"); + + if (!aState) { + if (reUseInnerWindow) { + + if (newInnerWindow->mDoc != aDocument) { + newInnerWindow->mDoc = aDocument; + + // 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->ClearDocumentDependentSlots(cx); + } + } else { + newInnerWindow->InnerSetNewDocument(cx, aDocument); + + // Initialize DOM classes etc on the inner window. + JS::Rooted obj(cx, newInnerGlobal); + rv = kungFuDeathGrip->InitClasses(obj); + NS_ENSURE_SUCCESS(rv, rv); + } + + // If the document comes from a JAR, check if the channel was determined + // to be unsafe. If so, permanently disable script on the compartment by + // calling Block() and throwing away the key. + nsCOMPtr jarChannel = do_QueryInterface(aDocument->GetChannel()); + if (jarChannel && jarChannel->GetIsUnsafe()) { + xpc::Scriptability::Get(newInnerGlobal).Block(); + } + + 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; + } + + nsJSContext::PokeGC(JS::gcreason::SET_NEW_DOCUMENT); + kungFuDeathGrip->DidInitializeContext(); + + // 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(newInnerWindow, + &nsGlobalWindow::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. + if (!mDocShell || + mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome || + nsContentUtils::IsSystemPrincipal(mDoc->NodePrincipal())) { + newInnerWindow->mHasNotifiedGlobalCreated = true; + nsContentUtils::AddScriptRunner( + NewRunnableMethod(this, &nsGlobalWindow::DispatchDOMWindowCreated)); + } + } + + PreloadLocalStorage(); + + return NS_OK; +} + +void +nsGlobalWindow::PreloadLocalStorage() +{ + MOZ_ASSERT(IsOuterWindow()); + + if (!Preferences::GetBool(kStorageEnabled)) { + return; + } + + if (IsChromeWindow()) { + return; + } + + nsIPrincipal* principal = GetPrincipal(); + if (!principal) { + return; + } + + nsresult rv; + + nsCOMPtr storageManager = + do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv); + if (NS_FAILED(rv)) { + return; + } + + storageManager->PrecacheStorage(principal); +} + +void +nsGlobalWindow::DispatchDOMWindowCreated() +{ + MOZ_ASSERT(IsOuterWindow()); + + if (!mDoc) { + return; + } + + // Fire DOMWindowCreated at chrome event listeners + nsContentUtils::DispatchChromeEvent(mDoc, mDoc, NS_LITERAL_STRING("DOMWindowCreated"), + true /* bubbles */, + false /* not cancellable */); + + nsCOMPtr observerService = + mozilla::services::GetObserverService(); + + // The event dispatching could possibly cause docshell destory, and + // consequently cause mDoc to be set to nullptr by DropOuterWindowDocs(), + // so check it again here. + if (observerService && mDoc) { + nsAutoString origin; + nsIPrincipal* principal = mDoc->NodePrincipal(); + nsContentUtils::GetUTFOrigin(principal, origin); + observerService-> + NotifyObservers(static_cast(this), + nsContentUtils::IsSystemPrincipal(principal) ? + "chrome-document-global-created" : + "content-document-global-created", + origin.get()); + } +} + +void +nsGlobalWindow::ClearStatus() +{ + SetStatusOuter(EmptyString()); +} + +void +nsGlobalWindow::InnerSetNewDocument(JSContext* aCx, nsIDocument* aDocument) +{ + NS_PRECONDITION(IsInnerWindow(), "Must only be called on inner windows"); + MOZ_ASSERT(aDocument); + + if (gDOMLeakPRLog && MOZ_LOG_TEST(gDOMLeakPRLog, LogLevel::Debug)) { + nsIURI *uri = aDocument->GetDocumentURI(); + PR_LogPrint("DOMWINDOW %p SetNewDocument %s", + this, uri ? uri->GetSpecOrDefault().get() : ""); + } + + mDoc = aDocument; + ClearDocumentDependentSlots(aCx); + mFocusedNode = nullptr; + mLocalStorage = nullptr; + mSessionStorage = nullptr; + +#ifdef DEBUG + mLastOpenedURI = aDocument->GetDocumentURI(); +#endif + + Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS, + mMutationBits ? 1 : 0); + + // Clear our mutation bitfield. + mMutationBits = 0; +} + +void +nsGlobalWindow::SetDocShell(nsIDocShell* aDocShell) +{ + NS_ASSERTION(IsOuterWindow(), "Uh, SetDocShell() called on inner window!"); + MOZ_ASSERT(aDocShell); + + if (aDocShell == mDocShell) { + return; + } + + mDocShell = aDocShell; // Weak Reference + + nsCOMPtr parentWindow = GetScriptableParentOrNull(); + MOZ_RELEASE_ASSERT(!parentWindow || !mTabGroup || mTabGroup == Cast(parentWindow)->mTabGroup); + + NS_ASSERTION(!mNavigator, "Non-null mNavigator in outer window!"); + + if (mFrames) { + mFrames->SetDocShell(aDocShell); + } + + // Get our enclosing chrome shell and retrieve its global window impl, so + // that we can do some forwarding to the chrome document. + nsCOMPtr chromeEventHandler; + mDocShell->GetChromeEventHandler(getter_AddRefs(chromeEventHandler)); + mChromeEventHandler = do_QueryInterface(chromeEventHandler); + if (!mChromeEventHandler) { + // We have no chrome event handler. If we have a parent, + // get our chrome event handler from the parent. If + // we don't have a parent, then we need to make a new + // window root object that will function as a chrome event + // handler and receive all events that occur anywhere inside + // our window. + nsCOMPtr parentWindow = GetParent(); + if (parentWindow.get() != AsOuter()) { + mChromeEventHandler = parentWindow->GetChromeEventHandler(); + } + else { + mChromeEventHandler = NS_NewWindowRoot(AsOuter()); + mIsRootOuterWindow = true; + } + } + + bool docShellActive; + mDocShell->GetIsActive(&docShellActive); + mIsBackground = !docShellActive; +} + +void +nsGlobalWindow::DetachFromDocShell() +{ + NS_ASSERTION(IsOuterWindow(), "Uh, DetachFromDocShell() called on inner window!"); + + // 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). + + NS_ASSERTION(mTimeouts.isEmpty(), "Uh, outer window holds timeouts!"); + + // Call FreeInnerObjects on all inner windows, not just the current + // one, since some could be held by WindowStateHolder objects that + // are GC-owned. + for (RefPtr inner = (nsGlobalWindow *)PR_LIST_HEAD(this); + inner != this; + inner = (nsGlobalWindow*)PR_NEXT_LINK(inner)) { + MOZ_ASSERT(!inner->mOuterWindow || inner->mOuterWindow == AsOuter()); + inner->FreeInnerObjects(); + } + + if (auto* reporter = nsWindowMemoryReporter::Get()) { + reporter->ObserveDOMWindowDetached(this); + } + + NotifyWindowIDDestroyed("outer-window-destroyed"); + + nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal(); + + if (currentInner) { + NS_ASSERTION(mDoc, "Must have doc!"); + + // Remember the document's principal and URI. + mDocumentPrincipal = mDoc->NodePrincipal(); + mDocumentURI = mDoc->GetDocumentURI(); + mDocBaseURI = mDoc->GetDocBaseURI(); + + // Release our document reference + DropOuterWindowDocs(); + mFocusedNode = nullptr; + } + + ClearControllers(); + + mChromeEventHandler = nullptr; // force release now + + if (mContext) { + nsJSContext::PokeGC(JS::gcreason::SET_DOC_SHELL); + mContext = nullptr; + } + + mDocShell = nullptr; // Weak Reference + + NS_ASSERTION(!mNavigator, "Non-null mNavigator in outer window!"); + + if (mFrames) { + mFrames->SetDocShell(nullptr); + } + + MaybeForgiveSpamCount(); + CleanUp(); +} + +void +nsGlobalWindow::SetOpenerWindow(nsPIDOMWindowOuter* aOpener, + bool aOriginalOpener) +{ + FORWARD_TO_OUTER_VOID(SetOpenerWindow, (aOpener, aOriginalOpener)); + + nsWeakPtr opener = do_GetWeakReference(aOpener); + if (opener == mOpener) { + return; + } + + NS_ASSERTION(!aOriginalOpener || !mSetOpenerWindowCalled, + "aOriginalOpener is true, but not first call to " + "SetOpenerWindow!"); + NS_ASSERTION(aOpener || !aOriginalOpener, + "Shouldn't set mHadOriginalOpener if aOpener is null"); + + mOpener = opener.forget(); + NS_ASSERTION(mOpener || !aOpener, "Opener must support weak references!"); + + if (aOriginalOpener) { + MOZ_ASSERT(!mHadOriginalOpener, + "Probably too late to call ComputeIsSecureContext again"); + mHadOriginalOpener = true; + mOriginalOpenerWasSecureContext = + aOpener->GetCurrentInnerWindow()->IsSecureContext(); + } + +#ifdef DEBUG + mSetOpenerWindowCalled = true; +#endif +} + +static +already_AddRefed +TryGetTabChildGlobalAsEventTarget(nsISupports *aFrom) +{ + nsCOMPtr frameLoaderOwner = do_QueryInterface(aFrom); + if (!frameLoaderOwner) { + return nullptr; + } + + RefPtr frameLoader = frameLoaderOwner->GetFrameLoader(); + if (!frameLoader) { + return nullptr; + } + + nsCOMPtr target = frameLoader->GetTabChildGlobalAsEventTarget(); + return target.forget(); +} + +void +nsGlobalWindow::UpdateParentTarget() +{ + // Try to get our frame element's tab child global (its in-process message + // manager). If that fails, fall back to the chrome event handler's tab + // child global, and if it doesn't have one, just use the chrome event + // handler itself. + + nsCOMPtr frameElement = GetOuterWindow()->GetFrameElementInternal(); + nsCOMPtr eventTarget = + TryGetTabChildGlobalAsEventTarget(frameElement); + + if (!eventTarget) { + nsGlobalWindow* topWin = GetScriptableTopInternal(); + if (topWin) { + frameElement = topWin->AsOuter()->GetFrameElementInternal(); + eventTarget = TryGetTabChildGlobalAsEventTarget(frameElement); + } + } + + if (!eventTarget) { + eventTarget = TryGetTabChildGlobalAsEventTarget(mChromeEventHandler); + } + + if (!eventTarget) { + eventTarget = mChromeEventHandler; + } + + mParentTarget = eventTarget; +} + +EventTarget* +nsGlobalWindow::GetTargetForDOMEvent() +{ + return GetOuterWindowInternal(); +} + +EventTarget* +nsGlobalWindow::GetTargetForEventTargetChain() +{ + return IsInnerWindow() ? this : GetCurrentInnerWindowInternal(); +} + +nsresult +nsGlobalWindow::WillHandleEvent(EventChainPostVisitor& aVisitor) +{ + return NS_OK; +} + +nsresult +nsGlobalWindow::PreHandleEvent(EventChainPreVisitor& aVisitor) +{ + NS_PRECONDITION(IsInnerWindow(), "PreHandleEvent is used on outer window!?"); + EventMessage msg = aVisitor.mEvent->mMessage; + + aVisitor.mCanHandle = true; + aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119 + if (msg == eResize && aVisitor.mEvent->IsTrusted()) { + // QIing to window so that we can keep the old behavior also in case + // a child window is handling resize. + nsCOMPtr window = + do_QueryInterface(aVisitor.mEvent->mOriginalTarget); + if (window) { + mIsHandlingResizeEvent = true; + } + } else if (msg == eMouseDown && aVisitor.mEvent->IsTrusted()) { + gMouseDown = true; + } else if ((msg == eMouseUp || msg == eDragEnd) && + aVisitor.mEvent->IsTrusted()) { + gMouseDown = false; + if (gDragServiceDisabled) { + nsCOMPtr ds = + do_GetService("@mozilla.org/widget/dragservice;1"); + if (ds) { + gDragServiceDisabled = false; + ds->Unsuppress(); + } + } + } + + aVisitor.mParentTarget = GetParentTarget(); + + // Handle 'active' event. + if (!mIdleObservers.IsEmpty() && + aVisitor.mEvent->IsTrusted() && + (aVisitor.mEvent->HasMouseEventMessage() || + aVisitor.mEvent->HasDragEventMessage())) { + mAddActiveEventFuzzTime = false; + } + + return NS_OK; +} + +bool +nsGlobalWindow::ShouldPromptToBlockDialogs() +{ + MOZ_ASSERT(IsOuterWindow()); + + nsGlobalWindow *topWindow = GetScriptableTopInternal(); + if (!topWindow) { + NS_ASSERTION(!mDocShell, "ShouldPromptToBlockDialogs() called without a top window?"); + return true; + } + + topWindow = topWindow->GetCurrentInnerWindowInternal(); + if (!topWindow) { + return true; + } + + return topWindow->DialogsAreBeingAbused(); +} + +bool +nsGlobalWindow::AreDialogsEnabled() +{ + MOZ_ASSERT(IsOuterWindow()); + + nsGlobalWindow *topWindow = GetScriptableTopInternal(); + if (!topWindow) { + NS_ERROR("AreDialogsEnabled() called without a top window?"); + return false; + } + + // TODO: Warn if no top window? + topWindow = topWindow->GetCurrentInnerWindowInternal(); + if (!topWindow) { + return false; + } + + // Dialogs are blocked if the content viewer is hidden + if (mDocShell) { + nsCOMPtr cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + + bool isHidden; + cv->GetIsHidden(&isHidden); + if (isHidden) { + return false; + } + } + + // Dialogs are also blocked if the document is sandboxed with SANDBOXED_MODALS + // (or if we have no document, of course). Which document? Who knows; the + // spec is daft. See . For now + // just go ahead and check mDoc, since in everything except edge cases in + // which a frame is allow-same-origin but not allow-scripts and is being poked + // at by some other window this should be the right thing anyway. + if (!mDoc || (mDoc->GetSandboxFlags() & SANDBOXED_MODALS)) { + return false; + } + + return topWindow->mAreDialogsEnabled; +} + +bool +nsGlobalWindow::DialogsAreBeingAbused() +{ + MOZ_ASSERT(IsInnerWindow()); + NS_ASSERTION(GetScriptableTopInternal() && + GetScriptableTopInternal()->GetCurrentInnerWindowInternal() == this, + "DialogsAreBeingAbused called with invalid window"); + + if (mLastDialogQuitTime.IsNull() || + nsContentUtils::IsCallerChrome()) { + return false; + } + + TimeDuration dialogInterval(TimeStamp::Now() - mLastDialogQuitTime); + if (dialogInterval.ToSeconds() < + Preferences::GetInt("dom.successive_dialog_time_limit", + DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT)) { + mDialogAbuseCount++; + + return GetPopupControlState() > openAllowed || + mDialogAbuseCount > MAX_SUCCESSIVE_DIALOG_COUNT; + } + + // Reset the abuse counter + mDialogAbuseCount = 0; + + return false; +} + +bool +nsGlobalWindow::ConfirmDialogIfNeeded() +{ + MOZ_ASSERT(IsOuterWindow()); + + NS_ENSURE_TRUE(mDocShell, false); + nsCOMPtr promptSvc = + do_GetService("@mozilla.org/embedcomp/prompt-service;1"); + + if (!promptSvc) { + return true; + } + + // Reset popup state while opening a modal dialog, and firing events + // about the dialog, to prevent the current state from being active + // the whole time a modal dialog is open. + nsAutoPopupStatePusher popupStatePusher(openAbused, true); + + bool disableDialog = false; + nsXPIDLString label, title; + nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES, + "ScriptDialogLabel", label); + nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES, + "ScriptDialogPreventTitle", title); + promptSvc->Confirm(AsOuter(), title.get(), label.get(), &disableDialog); + if (disableDialog) { + DisableDialogs(); + return false; + } + + return true; +} + +void +nsGlobalWindow::DisableDialogs() +{ + nsGlobalWindow *topWindow = GetScriptableTopInternal(); + if (!topWindow) { + NS_ERROR("DisableDialogs() called without a top window?"); + return; + } + + topWindow = topWindow->GetCurrentInnerWindowInternal(); + // TODO: Warn if no top window? + if (topWindow) { + topWindow->mAreDialogsEnabled = false; + } +} + +void +nsGlobalWindow::EnableDialogs() +{ + nsGlobalWindow *topWindow = GetScriptableTopInternal(); + if (!topWindow) { + NS_ERROR("EnableDialogs() called without a top window?"); + return; + } + + // TODO: Warn if no top window? + topWindow = topWindow->GetCurrentInnerWindowInternal(); + if (topWindow) { + topWindow->mAreDialogsEnabled = true; + } +} + +nsresult +nsGlobalWindow::PostHandleEvent(EventChainPostVisitor& aVisitor) +{ + NS_PRECONDITION(IsInnerWindow(), "PostHandleEvent is used on outer window!?"); + + // Return early if there is nothing to do. + switch (aVisitor.mEvent->mMessage) { + case eResize: + case eUnload: + case eLoad: + break; + default: + return NS_OK; + } + + /* mChromeEventHandler and mContext go dangling in the middle of this + function under some circumstances (events that destroy the window) + without this addref. */ + nsCOMPtr kungFuDeathGrip1(mChromeEventHandler); + mozilla::Unused << kungFuDeathGrip1; // These aren't referred to through the function + nsCOMPtr kungFuDeathGrip2(GetContextInternal()); + mozilla::Unused << kungFuDeathGrip2; // These aren't referred to through the function + + + if (aVisitor.mEvent->mMessage == eResize) { + mIsHandlingResizeEvent = false; + } else if (aVisitor.mEvent->mMessage == eUnload && + aVisitor.mEvent->IsTrusted()) { + // Execute bindingdetached handlers before we tear ourselves + // down. + if (mDoc) { + mDoc->BindingManager()->ExecuteDetachedHandlers(); + } + mIsDocumentLoaded = false; + } else if (aVisitor.mEvent->mMessage == eLoad && + aVisitor.mEvent->IsTrusted()) { + // This is page load event since load events don't propagate to |window|. + // @see nsDocument::PreHandleEvent. + mIsDocumentLoaded = true; + + nsCOMPtr element = GetOuterWindow()->GetFrameElementInternal(); + nsIDocShell* docShell = GetDocShell(); + if (element && GetParentInternal() && + docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) { + // If we're not in chrome, or at a chrome boundary, fire the + // onload event for the frame element. + + nsEventStatus status = nsEventStatus_eIgnore; + WidgetEvent event(aVisitor.mEvent->IsTrusted(), eLoad); + event.mFlags.mBubbles = false; + event.mFlags.mCancelable = false; + + // Most of the time we could get a pres context to pass in here, + // but not always (i.e. if this window is not shown there won't + // be a pres context available). Since we're not firing a GUI + // event we don't need a pres context anyway so we just pass + // null as the pres context all the time here. + EventDispatcher::Dispatch(element, nullptr, &event, nullptr, &status); + } + } + + return NS_OK; +} + +nsresult +nsGlobalWindow::DispatchDOMEvent(WidgetEvent* aEvent, + nsIDOMEvent* aDOMEvent, + nsPresContext* aPresContext, + nsEventStatus* aEventStatus) +{ + return EventDispatcher::DispatchDOMEvent(static_cast(this), + aEvent, aDOMEvent, aPresContext, + aEventStatus); +} + +void +nsGlobalWindow::PoisonOuterWindowProxy(JSObject *aObject) +{ + MOZ_ASSERT(IsOuterWindow()); + if (aObject == GetWrapperPreserveColor()) { + PoisonWrapper(); + } +} + +nsresult +nsGlobalWindow::SetArguments(nsIArray *aArguments) +{ + MOZ_ASSERT(IsOuterWindow()); + nsresult rv; + + // Historically, we've used the same machinery to handle openDialog arguments + // (exposed via window.arguments) and showModalDialog arguments (exposed via + // window.dialogArguments), even though the former is XUL-only and uses an XPCOM + // array while the latter is web-exposed and uses an arbitrary JS value. + // Moreover, per-spec |dialogArguments| is a property of the browsing context + // (outer), whereas |arguments| lives on the inner. + // + // 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. + nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal(); + if (mIsModalContentWindow) { + // nsWindowWatcher blindly converts the original nsISupports into an array + // of length 1. We need to recover it, and then cast it back to the concrete + // object we know it to be. + nsCOMPtr supports = do_QueryElementAt(aArguments, 0, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mDialogArguments = static_cast(supports.get()); + } else { + mArguments = aArguments; + rv = currentInner->DefineArgumentsProperty(aArguments); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +nsGlobalWindow::DefineArgumentsProperty(nsIArray *aArguments) +{ + MOZ_ASSERT(IsInnerWindow()); + MOZ_ASSERT(!mIsModalContentWindow); // Handled separately. + + nsIScriptContext *ctx = GetOuterWindowInternal()->mContext; + NS_ENSURE_TRUE(aArguments && ctx, NS_ERROR_NOT_INITIALIZED); + + JS::Rooted obj(RootingCx(), GetWrapperPreserveColor()); + return ctx->SetProperty(obj, "arguments", aArguments); +} + +namespace { + +// Convert a ThrottledEventQueue length to a timer delay in milliseconds. +// This will return a value between 0 and INT32_MAX. +int32_t +CalculateNewBackPressureDelayMS(uint32_t aBacklogDepth) +{ + double multiplier = static_cast(aBacklogDepth) / + static_cast(gThrottledEventQueueBackPressure); + double value = static_cast(gBackPressureDelayMS) * multiplier; + // Avoid overflow + if (value > INT32_MAX) { + value = INT32_MAX; + } + + // Once we get close to an empty queue just floor the delay back to zero. + // We want to ensure we don't get stuck in a condition where there is a + // small amount of delay remaining due to an active, but reasonable, queue. + else if (value < static_cast(gBackPressureDelayMinimumMS)) { + value = 0; + } + return static_cast(value); +} + +} // anonymous namespace + +void +nsGlobalWindow::MaybeApplyBackPressure() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // If we are already in back pressure then we don't need to apply back + // pressure again. We also shouldn't need to apply back pressure while + // the window is suspended. + if (mBackPressureDelayMS > 0 || IsSuspended()) { + return; + } + + RefPtr queue = TabGroup()->GetThrottledEventQueue(); + if (!queue) { + return; + } + + // Only begin back pressure if the window has greatly fallen behind the main + // thread. This is a somewhat arbitrary threshold chosen such that it should + // rarely fire under normaly circumstances. Its low enough, though, + // that we should have time to slow new runnables from being added before an + // OOM occurs. + if (queue->Length() < gThrottledEventQueueBackPressure) { + return; + } + + // First attempt to dispatch a runnable to update our back pressure state. We + // do this first in order to verify we can dispatch successfully before + // entering the back pressure state. + nsCOMPtr r = + NewRunnableMethod(this, &nsGlobalWindow::CancelOrUpdateBackPressure); + nsresult rv = queue->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS_VOID(rv); + + // Since the callback was scheduled successfully we can now persist the + // backpressure value. + mBackPressureDelayMS = CalculateNewBackPressureDelayMS(queue->Length()); +} + +void +nsGlobalWindow::CancelOrUpdateBackPressure() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mBackPressureDelayMS > 0); + + // First, re-calculate the back pressure delay. + RefPtr queue = TabGroup()->GetThrottledEventQueue(); + int32_t newBackPressureDelayMS = + CalculateNewBackPressureDelayMS(queue ? queue->Length() : 0); + + // If the delay has increased, then simply apply it. Increasing the delay + // does not risk re-ordering timers. + if (newBackPressureDelayMS > mBackPressureDelayMS) { + mBackPressureDelayMS = newBackPressureDelayMS; + } + + // If the delay has decreased, though, we only apply the new value if it has + // reduced significantly. This hysteresis avoids thrashing the back pressure + // value back and forth rapidly. This is important because reducing the + // backpressure delay requires calling ResetTimerForThrottleReduction() which + // can be quite expensive. We only want to call that method if the back log + // is really clearing. + else if (newBackPressureDelayMS == 0 || + (static_cast(mBackPressureDelayMS) > + (newBackPressureDelayMS + gBackPressureDelayReductionThresholdMS))) { + int32_t oldBackPressureDelayMS = mBackPressureDelayMS; + mBackPressureDelayMS = newBackPressureDelayMS; + + // If the back pressure delay has gone down we must reset any existing + // timers to use the new value. Otherwise we run the risk of executing + // timer callbacks out-of-order. + ResetTimersForThrottleReduction(oldBackPressureDelayMS); + } + + // If all of the back pressure delay has been removed then we no longer need + // to check back pressure updates. We can simply return without scheduling + // another update runnable. + if (!mBackPressureDelayMS) { + return; + } + + // Otherwise, if there is a back pressure delay still in effect we need + // queue a runnable to check if it can be reduced in the future. Note + // that this runnable is dispatched to the ThrottledEventQueue. This + // means we will not check for a new value until the current back log + // has been processed. The next update will only keep back pressure if + // more runnables continue to be dispatched to the queue. + nsCOMPtr r = + NewRunnableMethod(this, &nsGlobalWindow::CancelOrUpdateBackPressure); + MOZ_ALWAYS_SUCCEEDS(queue->Dispatch(r.forget(), NS_DISPATCH_NORMAL)); +} + +//***************************************************************************** +// nsGlobalWindow::nsIScriptObjectPrincipal +//***************************************************************************** + +nsIPrincipal* +nsGlobalWindow::GetPrincipal() +{ + if (mDoc) { + // If we have a document, get the principal from the document + return mDoc->NodePrincipal(); + } + + if (mDocumentPrincipal) { + return mDocumentPrincipal; + } + + // If we don't have a principal and we don't have a document we + // ask the parent window for the principal. This can happen when + // loading a frameset that has a , in + // that case the global window is used in JS before we've loaded + // a document into the window. + + nsCOMPtr objPrincipal = + do_QueryInterface(GetParentInternal()); + + if (objPrincipal) { + return objPrincipal->GetPrincipal(); + } + + return nullptr; +} + +//***************************************************************************** +// nsGlobalWindow::nsIDOMWindow +//***************************************************************************** + +template +nsIURI* +nsPIDOMWindow::GetDocumentURI() const +{ + return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get(); +} + +template +nsIURI* +nsPIDOMWindow::GetDocBaseURI() const +{ + return mDoc ? mDoc->GetDocBaseURI() : mDocBaseURI.get(); +} + +template +void +nsPIDOMWindow::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 = docShell->GetDocument(); + Unused << document; + } +} + +void +nsPIDOMWindowOuter::SetInitialKeyboardIndicators( + UIStateChangeType aShowAccelerators, UIStateChangeType aShowFocusRings) +{ + MOZ_ASSERT(IsOuterWindow()); + MOZ_ASSERT(!GetCurrentInnerWindow()); + + nsPIDOMWindowOuter* piWin = GetPrivateRoot(); + if (!piWin) { + return; + } + + MOZ_ASSERT(piWin == AsOuter()); + + // only change the flags that have been modified + nsCOMPtr windowRoot = do_QueryInterface(mChromeEventHandler); + if (!windowRoot) { + return; + } + + if (aShowAccelerators != UIStateChangeType_NoChange) { + windowRoot->SetShowAccelerators(aShowAccelerators == UIStateChangeType_Set); + } + if (aShowFocusRings != UIStateChangeType_NoChange) { + windowRoot->SetShowFocusRings(aShowFocusRings == UIStateChangeType_Set); + } + + nsContentUtils::SetKeyboardIndicatorsOnRemoteChildren(GetOuterWindow(), + aShowAccelerators, + aShowFocusRings); +} + +Element* +nsPIDOMWindowOuter::GetFrameElementInternal() const +{ + MOZ_ASSERT(IsOuterWindow()); + return mFrameElement; +} + +void +nsPIDOMWindowOuter::SetFrameElementInternal(Element* aFrameElement) +{ + MOZ_ASSERT(IsOuterWindow()); + mFrameElement = aFrameElement; +} + +bool +nsPIDOMWindowInner::AddAudioContext(AudioContext* aAudioContext) +{ + MOZ_ASSERT(IsInnerWindow()); + + mAudioContexts.AppendElement(aAudioContext); + + // Return true if the context should be muted and false if not. + nsIDocShell* docShell = GetDocShell(); + return docShell && !docShell->GetAllowMedia() && !aAudioContext->IsOffline(); +} + +void +nsPIDOMWindowInner::RemoveAudioContext(AudioContext* aAudioContext) +{ + MOZ_ASSERT(IsInnerWindow()); + + mAudioContexts.RemoveElement(aAudioContext); +} + +void +nsPIDOMWindowInner::MuteAudioContexts() +{ + MOZ_ASSERT(IsInnerWindow()); + + for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { + if (!mAudioContexts[i]->IsOffline()) { + mAudioContexts[i]->Mute(); + } + } +} + +void +nsPIDOMWindowInner::UnmuteAudioContexts() +{ + MOZ_ASSERT(IsInnerWindow()); + + for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { + if (!mAudioContexts[i]->IsOffline()) { + mAudioContexts[i]->Unmute(); + } + } +} + +nsGlobalWindow* +nsGlobalWindow::Window() +{ + return this; +} + +nsGlobalWindow* +nsGlobalWindow::Self() +{ + return this; +} + +Navigator* +nsGlobalWindow::GetNavigator(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mNavigator) { + mNavigator = new Navigator(AsInner()); + } + + return mNavigator; +} + +nsIDOMNavigator* +nsGlobalWindow::GetNavigator() +{ + FORWARD_TO_INNER(GetNavigator, (), nullptr); + + ErrorResult dummy; + nsIDOMNavigator* navigator = GetNavigator(dummy); + dummy.SuppressException(); + return navigator; +} + +nsScreen* +nsGlobalWindow::GetScreen(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mScreen) { + mScreen = nsScreen::Create(AsInner()); + if (!mScreen) { + aError.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + } + + return mScreen; +} + +nsIDOMScreen* +nsGlobalWindow::GetScreen() +{ + FORWARD_TO_INNER(GetScreen, (), nullptr); + + ErrorResult dummy; + nsIDOMScreen* screen = GetScreen(dummy); + dummy.SuppressException(); + return screen; +} + +nsHistory* +nsGlobalWindow::GetHistory(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mHistory) { + mHistory = new nsHistory(AsInner()); + } + + return mHistory; +} + +CustomElementRegistry* +nsGlobalWindow::CustomElements() +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + if (!mCustomElements) { + mCustomElements = CustomElementRegistry::Create(AsInner()); + } + + return mCustomElements; +} + +Performance* +nsPIDOMWindowInner::GetPerformance() +{ + MOZ_ASSERT(IsInnerWindow()); + CreatePerformanceObjectIfNeeded(); + return mPerformance; +} + +Performance* +nsGlobalWindow::GetPerformance() +{ + return AsInner()->GetPerformance(); +} + +void +nsPIDOMWindowInner::CreatePerformanceObjectIfNeeded() +{ + MOZ_ASSERT(IsInnerWindow()); + + if (mPerformance || !mDoc) { + return; + } + RefPtr timing = mDoc->GetNavigationTiming(); + nsCOMPtr timedChannel(do_QueryInterface(mDoc->GetChannel())); + bool timingEnabled = false; + if (!timedChannel || + !NS_SUCCEEDED(timedChannel->GetTimingEnabled(&timingEnabled)) || + !timingEnabled) { + timedChannel = nullptr; + } + if (timing) { + // If we are dealing with an iframe, we will need the parent's performance + // object (so we can add the iframe as a resource of that page). + Performance* parentPerformance = nullptr; + nsCOMPtr parentWindow = GetScriptableParentOrNull(); + if (parentWindow) { + nsPIDOMWindowInner* parentInnerWindow = nullptr; + if (parentWindow) { + parentInnerWindow = parentWindow->GetCurrentInnerWindow(); + } + if (parentInnerWindow) { + parentPerformance = parentInnerWindow->GetPerformance(); + } + } + mPerformance = + Performance::CreateForMainThread(this, timing, timedChannel, + parentPerformance); + } +} + +bool +nsPIDOMWindowInner::IsSecureContext() const +{ + return nsGlobalWindow::Cast(this)->IsSecureContext(); +} + +bool +nsPIDOMWindowInner::IsSecureContextIfOpenerIgnored() const +{ + return nsGlobalWindow::Cast(this)->IsSecureContextIfOpenerIgnored(); +} + +void +nsPIDOMWindowInner::Suspend() +{ + nsGlobalWindow::Cast(this)->Suspend(); +} + +void +nsPIDOMWindowInner::Resume() +{ + nsGlobalWindow::Cast(this)->Resume(); +} + +void +nsPIDOMWindowInner::Freeze() +{ + nsGlobalWindow::Cast(this)->Freeze(); +} + +void +nsPIDOMWindowInner::Thaw() +{ + nsGlobalWindow::Cast(this)->Thaw(); +} + +void +nsPIDOMWindowInner::SyncStateFromParentWindow() +{ + nsGlobalWindow::Cast(this)->SyncStateFromParentWindow(); +} + +SuspendTypes +nsPIDOMWindowOuter::GetMediaSuspend() const +{ + if (IsInnerWindow()) { + return mOuterWindow->GetMediaSuspend(); + } + + return mMediaSuspend; +} + +void +nsPIDOMWindowOuter::SetMediaSuspend(SuspendTypes aSuspend) +{ + if (IsInnerWindow()) { + mOuterWindow->SetMediaSuspend(aSuspend); + return; + } + + if (!IsDisposableSuspend(aSuspend)) { + mMediaSuspend = aSuspend; + } + + RefreshMediaElementsSuspend(aSuspend); +} + +bool +nsPIDOMWindowOuter::GetAudioMuted() const +{ + if (IsInnerWindow()) { + return mOuterWindow->GetAudioMuted(); + } + + return mAudioMuted; +} + +void +nsPIDOMWindowOuter::SetAudioMuted(bool aMuted) +{ + if (IsInnerWindow()) { + mOuterWindow->SetAudioMuted(aMuted); + return; + } + + if (mAudioMuted == aMuted) { + return; + } + + mAudioMuted = aMuted; + RefreshMediaElementsVolume(); +} + +float +nsPIDOMWindowOuter::GetAudioVolume() const +{ + if (IsInnerWindow()) { + return mOuterWindow->GetAudioVolume(); + } + + return mAudioVolume; +} + +nsresult +nsPIDOMWindowOuter::SetAudioVolume(float aVolume) +{ + if (IsInnerWindow()) { + return mOuterWindow->SetAudioVolume(aVolume); + } + + if (aVolume < 0.0) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + if (mAudioVolume == aVolume) { + return NS_OK; + } + + mAudioVolume = aVolume; + RefreshMediaElementsVolume(); + return NS_OK; +} + +void +nsPIDOMWindowOuter::RefreshMediaElementsVolume() +{ + RefPtr service = AudioChannelService::GetOrCreate(); + if (service) { + service->RefreshAgentsVolume(GetOuterWindow()); + } +} + +void +nsPIDOMWindowOuter::RefreshMediaElementsSuspend(SuspendTypes aSuspend) +{ + RefPtr service = AudioChannelService::GetOrCreate(); + if (service) { + service->RefreshAgentsSuspend(GetOuterWindow(), aSuspend); + } +} + +bool +nsPIDOMWindowOuter::IsDisposableSuspend(SuspendTypes aSuspend) const +{ + return (aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE || + aSuspend == nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE); +} + +void +nsPIDOMWindowOuter::SetServiceWorkersTestingEnabled(bool aEnabled) +{ + // Devtools should only be setting this on the top level window. Its + // ok if devtools clears the flag on clean up of nested windows, though. + // It will have no affect. +#ifdef DEBUG + nsCOMPtr topWindow = GetScriptableTop(); + MOZ_ASSERT_IF(aEnabled, this == topWindow); +#endif + mServiceWorkersTestingEnabled = aEnabled; +} + +bool +nsPIDOMWindowOuter::GetServiceWorkersTestingEnabled() +{ + // Automatically get this setting from the top level window so that nested + // iframes get the correct devtools setting. + nsCOMPtr topWindow = GetScriptableTop(); + if (!topWindow) { + return false; + } + return topWindow->mServiceWorkersTestingEnabled; +} + +bool +nsPIDOMWindowInner::GetAudioCaptured() const +{ + MOZ_ASSERT(IsInnerWindow()); + return mAudioCaptured; +} + +nsresult +nsPIDOMWindowInner::SetAudioCapture(bool aCapture) +{ + MOZ_ASSERT(IsInnerWindow()); + + mAudioCaptured = aCapture; + + RefPtr service = AudioChannelService::GetOrCreate(); + if (service) { + service->SetWindowAudioCaptured(GetOuterWindow(), mWindowID, aCapture); + } + + return NS_OK; +} + +// nsISpeechSynthesisGetter + +#ifdef MOZ_WEBSPEECH +SpeechSynthesis* +nsGlobalWindow::GetSpeechSynthesis(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mSpeechSynthesis) { + mSpeechSynthesis = new SpeechSynthesis(AsInner()); + } + + return mSpeechSynthesis; +} + +bool +nsGlobalWindow::HasActiveSpeechSynthesis() +{ + MOZ_ASSERT(IsInnerWindow()); + + if (mSpeechSynthesis) { + return !mSpeechSynthesis->HasEmptyQueue(); + } + + return false; +} + +#endif + +already_AddRefed +nsGlobalWindow::GetParentOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDocShell) { + return nullptr; + } + + nsCOMPtr parent; + if (mDocShell->GetIsMozBrowserOrApp()) { + parent = AsOuter(); + } else { + parent = GetParent(); + } + + return parent.forget(); +} + +already_AddRefed +nsGlobalWindow::GetParent(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetParentOuter, (), aError, nullptr); +} + +/** + * GetScriptableParent is called when script reads window.parent. + * + * In contrast to GetRealParent, GetScriptableParent respects