diff -r 000000000000 -r 6474c204b198 dom/base/nsGlobalWindow.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dom/base/nsGlobalWindow.cpp Wed Dec 31 06:09:35 2014 +0100 @@ -0,0 +1,13913 @@ +/* -*- Mode: C++; tab-width: 2; 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 "nsScreen.h" +#include "nsHistory.h" +#include "nsPerformance.h" +#include "nsDOMNavigationTiming.h" +#include "nsIDOMStorage.h" +#include "nsIDOMStorageManager.h" +#include "DOMStorage.h" +#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 "nsIPermissionManager.h" +#include "nsIScriptContext.h" +#include "nsIScriptTimeoutHandler.h" +#include "nsIController.h" +#include "nsScriptNameSpaceManager.h" +#include "nsWindowMemoryReporter.h" + +// Helper Classes +#include "nsJSUtils.h" +#include "jsapi.h" // for JSAutoRequest +#include "js/OldDebugAPI.h" // for JS_ClearWatchPointsForObject +#include "jswrapper.h" +#include "nsReadableUtils.h" +#include "nsDOMClassInfo.h" +#include "nsJSEnvironment.h" +#include "ScriptSettings.h" +#include "mozilla/Preferences.h" +#include "mozilla/Likely.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/MessagePortList.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 "AudioChannelService.h" +#include "MessageEvent.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" +#ifndef MOZ_DISABLE_CRYPTOLEGACY +#include "nsIDOMCryptoLegacy.h" +#endif +#include "nsIDOMDocument.h" +#include "nsIDOMElement.h" +#include "nsIDOMEvent.h" +#include "nsIDOMPopupBlockedEvent.h" +#include "nsIDOMPopStateEvent.h" +#include "nsIDOMHashChangeEvent.h" +#include "nsIDOMOfflineResourceList.h" +#include "nsPIDOMStorage.h" +#include "nsDOMString.h" +#include "nsIEmbeddingSiteWindow.h" +#include "nsThreadUtils.h" +#include "nsILoadContext.h" +#include "nsIMarkupDocumentViewer.h" +#include "nsIPresShell.h" +#include "nsIScriptSecurityManager.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 "nsIEntropyCollector.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 "nsAutoPtr.h" +#include "nsContentUtils.h" +#include "nsCxPusher.h" +#include "nsCSSProps.h" +#include "nsIDOMFile.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 "nsIDOMCustomEvent.h" +#include "nsIFrameRequestCallback.h" +#include "nsIJARChannel.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 "GeneratedEvents.h" +#include "GeneratedEventClasses.h" +#include "mozIThirdPartyUtil.h" +#ifdef MOZ_LOGGING +// so we can get logging even in release builds +#define FORCE_PR_LOG 1 +#endif +#include "prlog.h" +#include "prenv.h" +#include "prprf.h" + +#include "mozilla/dom/MessageChannel.h" +#include "mozilla/dom/MessagePort.h" +#include "mozilla/dom/MessagePortBinding.h" +#include "mozilla/dom/indexedDB/IDBFactory.h" +#include "mozilla/dom/quota/QuotaManager.h" + +#include "mozilla/dom/StructuredCloneTags.h" + +#ifdef MOZ_GAMEPAD +#include "mozilla/dom/GamepadService.h" +#endif + +#include "nsRefreshDriver.h" + +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "nsLocation.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/Console.h" +#include "mozilla/dom/FunctionBinding.h" +#include "mozilla/dom/WindowBinding.h" +#include "nsITabChild.h" +#include "mozilla/dom/MediaQueryList.h" +#include "mozilla/dom/ScriptSettings.h" +#ifdef HAVE_SIDEBAR +#include "mozilla/dom/ExternalBinding.h" +#endif + +#ifdef MOZ_WEBSPEECH +#include "mozilla/dom/SpeechSynthesis.h" +#endif + +#ifdef MOZ_JSDEBUGGER +#include "jsdIDebuggerService.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 PR_LOGGING +static PRLogModuleInfo* gDOMLeakPRLog; +#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::TimeStamp; +using mozilla::TimeDuration; + +nsGlobalWindow::WindowByIdTable *nsGlobalWindow::sWindowsById = nullptr; +bool nsGlobalWindow::sWarnedAboutWindowInternal = false; +bool nsGlobalWindow::sIdleObserversAPIFuzzTimeDisabled = false; + +static nsIEntropyCollector *gEntropyCollector = nullptr; +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 uint64_t gNextWindowID = 0; +static uint32_t gSerialCounter = 0; +static uint32_t gTimeoutsRecentlySet = 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 { + bool isBackground = !mOuterWindow || mOuterWindow->IsBackground(); + return + std::max(isBackground ? gMinBackgroundTimeoutValue : gMinTimeoutValue, 0); +} + +// 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 + +#define FORWARD_TO_OUTER(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsInnerWindow()) { \ + nsGlobalWindow *outer = GetOuterWindowInternal(); \ + if (!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 \ + if (IsInnerWindow()) { \ + nsGlobalWindow *outer = GetOuterWindowInternal(); \ + if (!HasActiveDocument()) { \ + if (!outer) { \ + NS_WARNING("No outer window available!"); \ + errorresult.Throw(NS_ERROR_NOT_INITIALIZED); \ + } else { \ + errorresult.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO); \ + } \ + } else { \ + return outer->method args; \ + } \ + return err_rval; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_OUTER_VOID(method, args) \ + PR_BEGIN_MACRO \ + if (IsInnerWindow()) { \ + nsGlobalWindow *outer = GetOuterWindowInternal(); \ + if (!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 (!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 *)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 (!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_OR_THROW(method, args, errorresult, err_rval) \ + PR_BEGIN_MACRO \ + if (IsOuterWindow()) { \ + if (!mInnerWindow) { \ + NS_WARNING("No inner window available!"); \ + errorresult.Throw(NS_ERROR_NOT_INITIALIZED); \ + 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 doc; \ + nsresult fwdic_nr = GetDocument(getter_AddRefs(doc)); \ + NS_ENSURE_SUCCESS(fwdic_nr, err_rval); \ + if (!mInnerWindow) { \ + return err_rval; \ + } \ + } \ + return GetCurrentInnerWindowInternal()->method args; \ + } \ + PR_END_MACRO + +// CIDs +static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID); + +static const char sPopStatePrefStr[] = "browser.history.allowPopState"; + +#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 MOZ_FINAL : public nsIObserver, + public nsIInterfaceRequestor +{ +public: + nsGlobalWindowObserver(nsGlobalWindow* aWindow) : mWindow(aWindow) {} + NS_DECL_ISUPPORTS + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) + { + if (!mWindow) + return NS_OK; + return mWindow->Observe(aSubject, aTopic, aData); + } + void Forget() { mWindow = nullptr; } + NS_IMETHODIMP GetInterface(const nsIID& aIID, void** aResult) + { + if (mWindow && aIID.Equals(NS_GET_IID(nsIDOMWindow)) && mWindow) { + return mWindow->QueryInterface(aIID, aResult); + } + return NS_NOINTERFACE; + } + +private: + nsGlobalWindow* mWindow; +}; + +NS_IMPL_ISUPPORTS(nsGlobalWindowObserver, nsIObserver, nsIInterfaceRequestor) + +nsTimeout::nsTimeout() + : mCleared(false), + mRunning(false), + mIsInterval(false), + mPublicId(0), + mInterval(0), + mFiringDepth(0), + mNestingLevel(0), + mPopupState(openAllowed) +{ +#ifdef DEBUG_jst + { + extern int gTimeoutCnt; + + ++gTimeoutCnt; + } +#endif + + MOZ_COUNT_CTOR(nsTimeout); +} + +nsTimeout::~nsTimeout() +{ +#ifdef DEBUG_jst + { + extern int gTimeoutCnt; + + --gTimeoutCnt; + } +#endif + + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + MOZ_COUNT_DTOR(nsTimeout); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsTimeout) + +NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsTimeout) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsTimeout) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPrincipal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScriptHandler) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END +NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsTimeout, AddRef) +NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsTimeout, Release) + +// Return true if this timeout has a refcount of 1. This is used to check +// that dummy_timeout doesn't leak from nsGlobalWindow::RunTimeout. +bool +nsTimeout::HasRefCntOne() +{ + return mRefCnt.get() == 1; +} + +nsPIDOMWindow::nsPIDOMWindow(nsPIDOMWindow *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), + mIsModalContentWindow(false), + mIsActive(false), mIsBackground(false), + mAudioMuted(false), mAudioVolume(1.0), + mInnerWindow(nullptr), mOuterWindow(aOuterWindow), + // Make sure no actual window ends up with mWindowID == 0 + mWindowID(++gNextWindowID), mHasNotifiedGlobalCreated(false), + mMarkedCCGeneration(0) + {} + +nsPIDOMWindow::~nsPIDOMWindow() {} + +// 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: + nsOuterWindowProxy() : js::Wrapper(0) { } + + virtual bool finalizeInBackground(JS::Value priv) { + return false; + } + + virtual const char *className(JSContext *cx, + JS::Handle wrapper) MOZ_OVERRIDE; + virtual void finalize(JSFreeOp *fop, JSObject *proxy) MOZ_OVERRIDE; + + // Fundamental traps + virtual bool isExtensible(JSContext *cx, JS::Handle proxy, bool *extensible) + MOZ_OVERRIDE; + virtual bool preventExtensions(JSContext *cx, + JS::Handle proxy) MOZ_OVERRIDE; + virtual bool getPropertyDescriptor(JSContext* cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc) MOZ_OVERRIDE; + virtual bool getOwnPropertyDescriptor(JSContext* cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc) MOZ_OVERRIDE; + virtual bool defineProperty(JSContext* cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc) MOZ_OVERRIDE; + virtual bool getOwnPropertyNames(JSContext *cx, + JS::Handle proxy, + JS::AutoIdVector &props) MOZ_OVERRIDE; + virtual bool delete_(JSContext *cx, JS::Handle proxy, + JS::Handle id, + bool *bp) MOZ_OVERRIDE; + virtual bool enumerate(JSContext *cx, JS::Handle proxy, + JS::AutoIdVector &props) MOZ_OVERRIDE; + + virtual bool watch(JSContext *cx, JS::Handle proxy, + JS::Handle id, JS::Handle callable) MOZ_OVERRIDE; + virtual bool unwatch(JSContext *cx, JS::Handle proxy, + JS::Handle id) MOZ_OVERRIDE; + + // Derived traps + virtual bool has(JSContext *cx, JS::Handle proxy, + JS::Handle id, bool *bp) MOZ_OVERRIDE; + virtual bool hasOwn(JSContext *cx, JS::Handle proxy, + JS::Handle id, bool *bp) MOZ_OVERRIDE; + virtual bool get(JSContext *cx, JS::Handle proxy, + JS::Handle receiver, + JS::Handle id, + JS::MutableHandle vp) MOZ_OVERRIDE; + virtual bool set(JSContext *cx, JS::Handle proxy, + JS::Handle receiver, + JS::Handle id, + bool strict, + JS::MutableHandle vp) MOZ_OVERRIDE; + virtual bool keys(JSContext *cx, JS::Handle proxy, + JS::AutoIdVector &props) MOZ_OVERRIDE; + virtual bool iterate(JSContext *cx, JS::Handle proxy, + unsigned flags, + JS::MutableHandle vp) MOZ_OVERRIDE; + + static nsOuterWindowProxy singleton; + +protected: + nsGlobalWindow* GetWindow(JSObject *proxy) + { + return nsGlobalWindow::FromSupports( + static_cast(js::GetProxyExtra(proxy, 0).toPrivate())); + } + + // 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); + + // 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); + + bool AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy, + JS::AutoIdVector &props); +}; + +const js::Class OuterWindowProxyClass = + PROXY_CLASS_WITH_EXT( + "Proxy", + 0, /* additional slots */ + 0, /* additional class flags */ + nullptr, /* call */ + nullptr, /* construct */ + PROXY_MAKE_EXT( + nullptr, /* outerObject */ + js::proxy_innerObject, + nullptr, /* iteratorObject */ + false /* isWrappedNative */ + )); + +bool +nsOuterWindowProxy::isExtensible(JSContext *cx, JS::Handle proxy, + bool *extensible) +{ + // 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 always report true for this. + *extensible = true; + return true; +} + +bool +nsOuterWindowProxy::preventExtensions(JSContext *cx, + JS::Handle proxy) +{ + // See above. + JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, + JSMSG_CANT_CHANGE_EXTENSIBILITY); + return false; +} + +const char * +nsOuterWindowProxy::className(JSContext *cx, JS::Handle proxy) +{ + MOZ_ASSERT(js::IsProxy(proxy)); + + return "Window"; +} + +void +nsOuterWindowProxy::finalize(JSFreeOp *fop, JSObject *proxy) +{ + nsGlobalWindow* global = GetWindow(proxy); + if (global) { + global->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. + global->PoisonOuterWindowProxy(proxy); + } +} + +bool +nsOuterWindowProxy::getPropertyDescriptor(JSContext* cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc) +{ + // 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) +{ + 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 + + return js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc); +} + +bool +nsOuterWindowProxy::defineProperty(JSContext* cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle desc) +{ + int32_t index = GetArrayIndexFromId(cx, id); + if (IsArrayIndex(index)) { + // Spec says to Reject whether this is a supported index or not, + // since we have no indexed setter or indexed creator. That means + // throwing in strict mode (FIXME: Bug 828137), doing nothing in + // non-strict mode. + return true; + } + + return js::Wrapper::defineProperty(cx, proxy, id, desc); +} + +bool +nsOuterWindowProxy::getOwnPropertyNames(JSContext *cx, + JS::Handle proxy, + JS::AutoIdVector &props) +{ + // 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::getOwnPropertyNames(cx, proxy, innerProps)) { + return false; + } + return js::AppendUnique(cx, props, innerProps); +} + +bool +nsOuterWindowProxy::delete_(JSContext *cx, JS::Handle proxy, + JS::Handle id, bool *bp) +{ + if (nsCOMPtr frame = GetSubframeWindow(cx, proxy, id)) { + // Reject (which means throw if strict, else return false) the delete. + // Except we don't even know whether we're strict. See bug 803157. + *bp = false; + return true; + } + + int32_t index = GetArrayIndexFromId(cx, id); + if (IsArrayIndex(index)) { + // Indexed, but not supported. Spec says return true. + *bp = true; + return true; + } + + return js::Wrapper::delete_(cx, proxy, id, bp); +} + +bool +nsOuterWindowProxy::enumerate(JSContext *cx, JS::Handle proxy, + JS::AutoIdVector &props) +{ + // 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::enumerate(cx, proxy, innerProps)) { + return false; + } + return js::AppendUnique(cx, props, innerProps); +} + +bool +nsOuterWindowProxy::has(JSContext *cx, JS::Handle proxy, + JS::Handle id, bool *bp) +{ + 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) +{ + 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) +{ + 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 receiver, + JS::Handle id, + bool strict, + JS::MutableHandle vp) +{ + int32_t index = GetArrayIndexFromId(cx, id); + if (IsArrayIndex(index)) { + // Reject (which means throw if and only if strict) the set. + if (strict) { + // XXXbz This needs to throw, but see bug 828137. + } + return true; + } + + return js::Wrapper::set(cx, proxy, receiver, id, strict, vp); +} + +bool +nsOuterWindowProxy::keys(JSContext *cx, JS::Handle proxy, + JS::AutoIdVector &props) +{ + // BaseProxyHandler::keys seems to do what we want here: call + // getOwnPropertyNames and then filter out the non-enumerable properties. + return js::BaseProxyHandler::keys(cx, proxy, props); +} + +bool +nsOuterWindowProxy::iterate(JSContext *cx, JS::Handle proxy, + unsigned flags, JS::MutableHandle vp) +{ + // BaseProxyHandler::iterate seems to do what we want here: fall + // back on the property names returned from keys() and enumerate(). + return js::BaseProxyHandler::iterate(cx, proxy, flags, vp); +} + +bool +nsOuterWindowProxy::GetSubframeWindow(JSContext *cx, + JS::Handle proxy, + JS::Handle id, + JS::MutableHandle vp, + bool& found) +{ + nsCOMPtr frame = GetSubframeWindow(cx, proxy, id); + if (!frame) { + found = false; + return true; + } + + found = true; + // Just return the window's global + nsGlobalWindow* global = static_cast(frame.get()); + global->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); + } + + vp.setObject(*obj); + return JS_WrapValue(cx, vp); +} + +already_AddRefed +nsOuterWindowProxy::GetSubframeWindow(JSContext *cx, + JS::Handle proxy, + JS::Handle id) +{ + int32_t index = GetArrayIndexFromId(cx, id); + if (!IsArrayIndex(index)) { + return nullptr; + } + + nsGlobalWindow* win = GetWindow(proxy); + bool unused; + return win->IndexedGetter(index, unused); +} + +bool +nsOuterWindowProxy::AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy, + JS::AutoIdVector &props) +{ + uint32_t length = GetWindow(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) { + props.append(INT_TO_JSID(i)); + } + + return true; +} + +bool +nsOuterWindowProxy::watch(JSContext *cx, JS::Handle proxy, + JS::Handle id, JS::Handle callable) +{ + return js::WatchGuts(cx, proxy, id, callable); +} + +bool +nsOuterWindowProxy::unwatch(JSContext *cx, JS::Handle proxy, + JS::Handle id) +{ + return js::UnwatchGuts(cx, proxy, id); +} + +nsOuterWindowProxy +nsOuterWindowProxy::singleton; + +class nsChromeOuterWindowProxy : public nsOuterWindowProxy +{ +public: + nsChromeOuterWindowProxy() : nsOuterWindowProxy() {} + + virtual const char *className(JSContext *cx, JS::Handle wrapper) MOZ_OVERRIDE; + + static nsChromeOuterWindowProxy singleton; +}; + +const char * +nsChromeOuterWindowProxy::className(JSContext *cx, + JS::Handle proxy) +{ + MOZ_ASSERT(js::IsProxy(proxy)); + + return "ChromeWindow"; +} + +nsChromeOuterWindowProxy +nsChromeOuterWindowProxy::singleton; + +static JSObject* +NewOuterWindowProxy(JSContext *cx, JS::Handle parent, bool isChrome) +{ + JSAutoCompartment ac(cx, parent); + js::WrapperOptions options; + options.setClass(&OuterWindowProxyClass); + options.setSingleton(true); + JSObject *obj = js::Wrapper::New(cx, parent, parent, + isChrome ? &nsChromeOuterWindowProxy::singleton + : &nsOuterWindowProxy::singleton, + &options); + + NS_ASSERTION(js::GetObjectClass(obj)->ext.innerObject, "bad class"); + return obj; +} + +//***************************************************************************** +//*** nsGlobalWindow: Object Management +//***************************************************************************** + +nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow) + : nsPIDOMWindow(aOuterWindow), + mIdleFuzzFactor(0), + mIdleCallbackIndex(-1), + mCurrentlyIdle(false), + mAddActiveEventFuzzTime(true), + mIsFrozen(false), + mFullScreen(false), + mIsClosed(false), + mInClose(false), + mHavePendingClose(false), + mHadOriginalOpener(false), + mIsPopupSpam(false), + mBlockScriptedClosingFlag(false), + mFireOfflineStatusChangeEventOnThaw(false), + mNotifyIdleObserversIdleOnThaw(false), + mNotifyIdleObserversActiveOnThaw(false), + mCreatingInnerWindow(false), + mIsChrome(false), + mCleanMessageManager(false), + mNeedsFocus(true), + mHasFocus(false), +#if defined(XP_MACOSX) + mShowAccelerators(false), + mShowFocusRings(false), +#else + mShowAccelerators(true), + mShowFocusRings(true), +#endif + mShowFocusRingForContent(false), + mFocusByKeyOccurred(false), + mInnerObjectsFreed(false), + mHasGamepad(false), +#ifdef MOZ_GAMEPAD + mHasSeenGamepadInput(false), +#endif + mNotifiedIDDestroyed(false), + mAllowScriptsToClose(false), + mTimeoutInsertionPoint(nullptr), + mTimeoutPublicIdCounter(1), + mTimeoutFiringDepth(0), + mTimeoutsSuspendDepth(0), + mFocusMethod(0), + mSerial(0), +#ifdef DEBUG + mSetOpenerWindowCalled(false), +#endif +#ifdef MOZ_B2G + mNetworkUploadObserverEnabled(false), + mNetworkDownloadObserverEnabled(false), +#endif + mCleanedUp(false), + mDialogAbuseCount(0), + mAreDialogsEnabled(true) +{ + 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) { + NS_ADDREF(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); + } + } + } else { + // |this| is an outer window. Outer windows start out frozen and + // remain frozen until they get an inner window, so freeze this + // outer window here. + Freeze(); + + mObserver = nullptr; + SetIsDOMBinding(); + } + + // 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++; + + if (gRefCnt == 1) { + 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); + } + + 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 + +#ifdef PR_LOGGING + if (gDOMLeakPRLog) + PR_LOG(gDOMLeakPRLog, PR_LOG_DEBUG, + ("DOMWINDOW %p created outer=%p", this, aOuterWindow)); +#endif + + 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); + } +} + +/* static */ +void +nsGlobalWindow::Init() +{ + CallGetService(NS_ENTROPYCOLLECTOR_CONTRACTID, &gEntropyCollector); + NS_ASSERTION(gEntropyCollector, + "gEntropyCollector should have been initialized!"); + +#ifdef PR_LOGGING + gDOMLeakPRLog = PR_NewLogModule("DOMLeak"); + NS_ASSERTION(gDOMLeakPRLog, "gDOMLeakPRLog should have been initialized!"); +#endif + + sWindowsById = new WindowByIdTable(); +} + +static PLDHashOperator +DisconnectEventTargetObjects(nsPtrHashKey* aKey, + void* aClosure) +{ + nsRefPtr target = aKey->GetKey(); + target->DisconnectFromOwner(); + return PL_DHASH_NEXT; +} + +nsGlobalWindow::~nsGlobalWindow() +{ + mEventTargetObjects.EnumerateEntries(DisconnectEventTargetObjects, nullptr); + mEventTargetObjects.Clear(); + + // 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) { + mLastOpenedURI->GetSpec(url); + + // 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 = static_cast(mOuterWindow.get()); + 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 + +#ifdef PR_LOGGING + if (gDOMLeakPRLog) + PR_LOG(gDOMLeakPRLog, PR_LOG_DEBUG, + ("DOMWINDOW %p destroyed", this)); +#endif + + 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); + } + } + + // 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) +{ + mEventTargetObjects.PutEntry(aObject); +} + +void +nsGlobalWindow::RemoveEventTargetObject(DOMEventTargetHelper* aObject) +{ + mEventTargetObjects.RemoveEntry(aObject); +} + +// static +void +nsGlobalWindow::ShutDown() +{ + if (gDumpFile && gDumpFile != stdout) { + fclose(gDumpFile); + } + gDumpFile = nullptr; + + NS_IF_RELEASE(gEntropyCollector); + + 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()) + { + SetPopupSpamWindow(false); + --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; + + mEventTargetObjects.EnumerateEntries(DisconnectEventTargetObjects, nullptr); + mEventTargetObjects.Clear(); + + 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(NS_NETWORK_UPLOAD_EVENT); + DisableNetworkEvent(NS_NETWORK_DOWNLOAD_EVENT); +#endif // MOZ_B2G + + if (mIdleService) { + mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S); + } + + // Drop its reference to this dying window, in case for some bogus reason + // the object stays around. + mObserver->Forget(); + NS_RELEASE(mObserver); + } + + if (mNavigator) { + mNavigator->Invalidate(); + mNavigator = nullptr; + } + + mScreen = nullptr; + mMenubar = nullptr; + mToolbar = nullptr; + mLocationbar = nullptr; + mPersonalbar = nullptr; + mStatusbar = nullptr; + mScrollbars = nullptr; + mLocation = nullptr; + mHistory = nullptr; + mFrames = nullptr; + mWindowUtils = nullptr; + mApplicationCache = nullptr; + mIndexedDB = nullptr; + + mConsole = nullptr; + + mExternal = nullptr; + + mPerformance = nullptr; + +#ifdef MOZ_WEBSPEECH + mSpeechSynthesis = nullptr; +#endif + + ClearControllers(); + + mOpener = nullptr; // Forces Release + if (mContext) { + mContext = nullptr; // Forces Release + } + mChromeEventHandler = nullptr; // Forces Release + mParentTarget = nullptr; + + nsGlobalWindow *inner = GetCurrentInnerWindowInternal(); + + if (inner) { + inner->CleanUp(); + } + + DisableGamepadUpdates(); + mHasGamepad = false; + + if (mCleanMessageManager) { + NS_ABORT_IF_FALSE(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; + } + + DisableTimeChangeNotifications(); +} + +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); + + mInnerObjectsFreed = true; + + // Kill all of the workers for this window. + mozilla::dom::workers::CancelWorkersForWindow(this); + + // Close all offline storages for this window. + quota::QuotaManager* quotaManager = quota::QuotaManager::Get(); + if (quotaManager) { + quotaManager->AbortCloseStoragesForWindow(this); + } + + ClearAllTimeouts(); + + if (mIdleTimer) { + mIdleTimer->Cancel(); + mIdleTimer = nullptr; + } + + mIdleObservers.Clear(); + + mChromeEventHandler = nullptr; + + if (mListenerManager) { + mListenerManager->Disconnect(); + mListenerManager = nullptr; + } + + mLocation = nullptr; + mHistory = nullptr; + + if (mNavigator) { + mNavigator->OnNavigation(); + mNavigator->Invalidate(); + mNavigator = nullptr; + } + + if (mScreen) { + mScreen = nullptr; + } + + 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; + + 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 +} + +//***************************************************************************** +// nsGlobalWindow::nsISupports +//***************************************************************************** + +DOMCI_DATA(Window, nsGlobalWindow) + +// 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) +#ifdef MOZ_B2G + NS_INTERFACE_MAP_ENTRY(nsIDOMWindowB2G) +#endif // MOZ_B2G +#ifdef MOZ_WEBSPEECH + NS_INTERFACE_MAP_ENTRY(nsISpeechSynthesisGetter) +#endif // MOZ_B2G + NS_INTERFACE_MAP_ENTRY(nsIDOMJSWindow) + 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) + NS_INTERFACE_MAP_ENTRY(nsPIDOMWindow) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) + NS_INTERFACE_MAP_ENTRY(nsIDOMWindowPerformance) + NS_INTERFACE_MAP_ENTRY(nsITouchEventReceiver) + NS_INTERFACE_MAP_ENTRY(nsIInlineEventHandlers) + NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(Window) +NS_INTERFACE_MAP_END + + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindow) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindow) + +static PLDHashOperator +MarkXBLHandlers(nsXBLPrototypeHandler* aKey, JS::Heap& aData, void* aClosure) +{ + JS::ExposeObjectToActiveJS(aData); + return PL_DHASH_NEXT; +} + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindow) + if (tmp->IsBlackForCC(false)) { + if (tmp->mCachedXBLPrototypeHandlers) { + tmp->mCachedXBLPrototypeHandlers->Enumerate(MarkXBLHandlers, nullptr); + } + 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]; + PR_snprintf(name, sizeof(name), "nsGlobalWindow #%ld", tmp->mWindowID); + 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) + +#ifdef MOZ_WEBSPEECH + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechSynthesis) +#endif + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOuterWindow) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager) + + for (nsTimeout* timeout = tmp->mTimeouts.getFirst(); + timeout; + timeout = timeout->getNext()) { + cb.NoteNativeChild(timeout, NS_CYCLE_COLLECTION_PARTICIPANT(nsTimeout)); + } + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStorage) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplicationCache) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers) + +#ifdef MOZ_GAMEPAD + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads) +#endif + + // 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(mConsole) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExternal) + 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) + +#ifdef MOZ_WEBSPEECH + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechSynthesis) +#endif + + if (tmp->mOuterWindow) { + static_cast(tmp->mOuterWindow.get())->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(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(mDocumentPrincipal) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleService) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingStorageEvents) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleObservers) + +#ifdef MOZ_GAMEPAD + NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads) +#endif + + // 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(mConsole) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal) + 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 + +struct TraceData +{ + const TraceCallbacks& callbacks; + void* closure; +}; + +static PLDHashOperator +TraceXBLHandlers(nsXBLPrototypeHandler* aKey, JS::Heap& aData, void* aClosure) +{ + TraceData* data = static_cast(aClosure); + data->callbacks.Trace(&aData, "Cached XBL prototype handler", data->closure); + return PL_DHASH_NEXT; +} + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGlobalWindow) + if (tmp->mCachedXBLPrototypeHandlers) { + TraceData data = { aCallbacks, aClosure }; + tmp->mCachedXBLPrototypeHandlers->Enumerate(TraceXBLHandlers, &data); + } + 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 (nsTimeout* timeout = mTimeouts.getFirst(); + timeout; + timeout = timeout->getNext()) { + if (timeout->mScriptHandler) { + Function* f = timeout->mScriptHandler->GetCallback(); + if (f) { + // Callable() already does xpc_UnmarkGrayObject. + DebugOnly > o = f->Callable(); + MOZ_ASSERT(!xpc_IsGrayGCThing(o.value), "Should have been unmarked"); + } + } + } +} + +//***************************************************************************** +// 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(); + return outer ? outer->mContext : nullptr; +} + +JSObject * +nsGlobalWindow::GetGlobalJSObject() +{ + return FastGetGlobalJSObject(); +} + +void +nsGlobalWindow::TraceGlobalJSObject(JSTracer* aTrc) +{ + TraceWrapper(aTrc, "active window global"); +} + +/* static */ +JSObject* +nsGlobalWindow::OuterObject(JSContext* aCx, JS::Handle aObj) +{ + nsGlobalWindow* origWin; + UNWRAP_OBJECT(Window, aObj, origWin); + nsGlobalWindow* win = origWin->GetOuterWindowInternal(); + + if (!win) { + // If we no longer have an outer window. No code should ever be + // running on a window w/o an outer, which means this hook should + // never be called when we have no outer. But just in case, return + // null to prevent leaking an inner window to code in a different + // window. + NS_WARNING("nsGlobalWindow::OuterObject shouldn't fail!"); + return nullptr; + } + + JS::Rooted winObj(aCx, win->FastGetGlobalJSObject()); + MOZ_ASSERT(winObj); + + // Note that while |wrapper| is same-compartment with cx, the outer window + // might not be. If we're running script in an inactive scope and evalute + // |this|, the outer window is actually a cross-compartment wrapper. So we + // need to wrap here. + if (!JS_WrapObject(aCx, &winObj)) { + NS_WARNING("nsGlobalWindow::OuterObject shouldn't fail!"); + return nullptr; + } + + return winObj; +} + +bool +nsGlobalWindow::WouldReuseInnerWindow(nsIDocument *aNewDocument) +{ + // We reuse the inner window when: + // a. We are currently at our original document. + // b. At least one of the following conditions are true: + // -- The new document is the same as the old document. This means that we're + // getting called from document.open(). + // -- The new document has the same origin as what we have loaded right now. + + if (!mDoc || !aNewDocument) { + return false; + } + + if (!mDoc->IsInitialDocument()) { + return false; + } + + 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() +{ + FORWARD_TO_OUTER_VOID(SetInitialPrincipalToSubject, ()); + + // First, grab the subject principal. + nsCOMPtr newWindowPrincipal = nsContentUtils::GetSubjectPrincipal(); + if (!newWindowPrincipal) { + newWindowPrincipal = nsContentUtils::GetSystemPrincipal(); + } + + // Now, if we're about to use the system principal or an nsExpandedPrincipal, + // make sure we're not using it for a content docshell. + if (nsContentUtils::IsSystemOrExpandedPrincipal(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 MOZ_FINAL : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(WINDOWSTATEHOLDER_IID) + NS_DECL_ISUPPORTS + + WindowStateHolder(nsGlobalWindow *aWindow); + + nsGlobalWindow* GetInnerWindow() { return mInnerWindow; } + + void DidRestoreWindow() + { + mInnerWindow = nullptr; + } + +protected: + ~WindowStateHolder(); + + nsRefPtr mInnerWindow; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(WindowStateHolder, WINDOWSTATEHOLDER_IID) + +WindowStateHolder::WindowStateHolder(nsGlobalWindow* aWindow) + : mInnerWindow(aWindow) +{ + NS_PRECONDITION(aWindow, "null window"); + NS_PRECONDITION(aWindow->IsInnerWindow(), "Saving an outer window"); + + // We hold onto this to make sure the inner window doesn't go away. The outer + // window ends up recalculating it anyway. + mInnerWindow->PreserveWrapper(ToSupports(mInnerWindow)); + + aWindow->SuspendTimeouts(); + + // When a global goes into the bfcache, we disable script. + xpc::Scriptability::Get(aWindow->GetWrapperPreserveColor()).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); +} + +/** + * 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) +{ + 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->GetTop(); + } + JS::CompartmentOptions options; + if (top) { + if (top->GetGlobalJSObject()) { + options.setSameZoneAs(top->GetGlobalJSObject()); + } + } + + nsIXPConnect* xpc = nsContentUtils::XPConnect(); + + // 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; + + nsCOMPtr holder; + nsresult rv = xpc->InitClassesWithNewWrappedGlobal( + aCx, ToSupports(aNewInner), + aPrincipal, flags, options, getter_AddRefs(holder)); + NS_ENSURE_SUCCESS(rv, rv); + + aGlobal.set(holder->GetJSObject()); + + // Set the location information for the new global, so that tools like + // about:memory may use that information + MOZ_ASSERT(aGlobal); + MOZ_ASSERT(aNewInner->GetWrapperPreserveColor() == aGlobal); + xpc::SetLocationForGlobal(aGlobal, aURI); + + 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() != this) { + return NS_ERROR_NOT_AVAILABLE; + } + + return GetOuterWindowInternal()->SetNewDocument(aDocument, aState, + aForceReuseInnerWindow); + } + + NS_PRECONDITION(IsOuterWindow(), "Must only be called on outer windows"); + + if (IsFrozen()) { + // This outer is now getting its first inner, thaw the outer now + // that it's ready and is getting an inner window. + + Thaw(); + } + + NS_ASSERTION(!GetCurrentInnerWindow() || + GetCurrentInnerWindow()->GetExtantDoc() == mDoc, + "Uh, mDoc doesn't match the current inner window " + "document!"); + + bool wouldReuseInnerWindow = WouldReuseInnerWindow(aDocument); + if (aForceReuseInnerWindow && + !wouldReuseInnerWindow && + mDoc && + mDoc->NodePrincipal() != aDocument->NodePrincipal()) { + NS_ERROR("Attempted forced inner window reuse while changing principal"); + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr oldDoc = mDoc; + + nsIScriptContext *scx = GetContextInternal(); + NS_ENSURE_TRUE(scx, NS_ERROR_NOT_INITIALIZED); + + JSContext *cx = scx->GetNativeContext(); + +#ifndef MOZ_DISABLE_CRYPTOLEGACY + // clear smartcard events, our document has gone away. + if (mCrypto && XRE_GetProcessType() != GeckoProcessType_Content) { + nsresult rv = mCrypto->SetEnableSmartCardEvents(false); + NS_ENSURE_SUCCESS(rv, rv); + } +#endif + + 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. + nsIDOMWindow* privateRoot = nsGlobalWindow::GetPrivateRoot(); + + if (privateRoot == static_cast(this)) { + 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( + NS_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; + + // Set mDoc even if this is an outer window to avoid + // having to *always* reach into the inner window to find the + // document. + mDoc = aDocument; + if (IsInnerWindow() && IsDOMBinding()) { + WindowBinding::ClearCachedDocumentValue(cx, this); + } + + // 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(); + } + + nsRefPtr newInnerWindow; + bool createdInnerWindow = false; + + bool thisChrome = IsChromeWindow(); + + nsCxPusher cxPusher; + cxPusher.Push(cx); + + // Check if we're near the stack limit before we get anywhere near the + // transplanting code. + JS_CHECK_RECURSION(cx, return NS_ERROR_FAILURE); + + 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, GetWrapperPreserveColor()); + JS::ExposeObjectToActiveJS(rootedObject); + 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 + JS_SetCompartmentPrincipals(compartment, + nsJSPrincipals::get(aDocument->NodePrincipal())); + } else { + if (aState) { + newInnerWindow = wsh->GetInnerWindow(); + newInnerGlobal = newInnerWindow->GetWrapperPreserveColor(); + } else { + if (thisChrome) { + newInnerWindow = new nsGlobalChromeWindow(this); + } else if (mIsModalContentWindow) { + newInnerWindow = new nsGlobalModalWindow(this); + } else { + newInnerWindow = new nsGlobalWindow(this); + } + + // Freeze the outer window and null out the inner window so + // that initializing classes on the new inner doesn'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; + + Freeze(); + mCreatingInnerWindow = true; + // Every script context we are initialized with must create a + // new global. + rv = CreateNativeGlobalForInner(cx, newInnerWindow, + aDocument->GetDocumentURI(), + aDocument->NodePrincipal(), + &newInnerGlobal); + NS_ASSERTION(NS_SUCCEEDED(rv) && newInnerGlobal && + newInnerWindow->GetWrapperPreserveColor() == newInnerGlobal, + "Failed to get script global"); + + mCreatingInnerWindow = false; + createdInnerWindow = true; + Thaw(); + + 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); + } + + // 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->CreatePerformanceObjectIfNeeded(); + if (currentInner->mPerformance) { + newInnerWindow->mPerformance = + new nsPerformance(newInnerWindow, + 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; + + 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)); + + 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); + + { + JSAutoCompartment ac(cx, outerObject); + + JS_SetParent(cx, outerObject, newInnerGlobal); + + // Inform the nsJSContext, which is the canonical holder of the outer. + mContext->SetWindowProxy(outerObject); + + NS_ASSERTION(!JS_IsExceptionPending(cx), + "We might overwrite a pending exception!"); + XPCWrappedNativeScope* scope = xpc::GetObjectScope(outerObject); + if (scope->mWaiverWrapperMap) { + scope->mWaiverWrapperMap->Reparent(cx, newInnerGlobal); + } + } + } + + // Enter the new global's compartment. + JSAutoCompartment ac(cx, GetWrapperPreserveColor()); + + // Set scriptability based on the state of the docshell. + bool allow = GetDocShell()->GetCanExecuteScripts(); + xpc::Scriptability::Get(GetWrapperPreserveColor()).SetDocShellAllowsScript(allow); + + // If we created a new inner window above, we need to do the last little bit + // of initialization now that the dust has settled. + if (createdInnerWindow) { + nsIXPConnect *xpc = nsContentUtils::XPConnect(); + nsCOMPtr wrapper; + nsresult rv = xpc->GetWrappedNativeOfJSObject(cx, newInnerGlobal, + getter_AddRefs(wrapper)); + NS_ENSURE_SUCCESS(rv, rv); + NS_ABORT_IF_FALSE(wrapper, "bad wrapper"); + rv = wrapper->FinishInitForWrappedGlobal(); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (!aState) { + JS::Rooted rootedWrapper(cx, GetWrapperPreserveColor()); + if (!JS_DefineProperty(cx, newInnerGlobal, "window", rootedWrapper, + JSPROP_ENUMERATE | JSPROP_READONLY | JSPROP_PERMANENT, + JS_PropertyStub, JS_StrictPropertyStub)) { + NS_ERROR("can't create the 'window' 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 + + nsCOMPtr frame = GetFrameElementInternal(); + if (frame) { + nsPIDOMWindow* parentWindow = frame->OwnerDoc()->GetWindow(); + if (parentWindow && parentWindow->TimeoutSuspendCount()) { + SuspendTimeouts(parentWindow->TimeoutSuspendCount()); + } + } + } + + // Add an extra ref in case we release mContext during GC. + nsCOMPtr kungFuDeathGrip(mContext); + + aDocument->SetScriptGlobalObject(newInnerWindow); + + if (!aState) { + if (reUseInnerWindow) { + if (newInnerWindow->mDoc != aDocument) { + newInnerWindow->mDoc = aDocument; + + if (newInnerWindow->IsDOMBinding()) { + WindowBinding::ClearCachedDocumentValue(cx, newInnerWindow); + } else { + // We're reusing the inner window for a new document. In this + // case we don't clear the inner window's scope, but we must + // make sure the cached document property gets updated. + + JS::Rooted obj(cx, + currentInner->GetWrapperPreserveColor()); + ::JS_DeleteProperty(cx, obj, "document"); + } + } + } else { + newInnerWindow->InnerSetNewDocument(cx, aDocument); + + // Initialize DOM classes etc on the inner window. + JS::Rooted obj(cx, newInnerGlobal); + rv = mContext->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; + } + + mContext->GC(JS::gcreason::SET_NEW_DOCUMENT); + mContext->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) { + JS::Rooted global(cx, newInnerWindow->GetWrapper()); + JS_FireOnNewGlobalObject(cx, global); + } + + 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( + NS_NewRunnableMethod(this, &nsGlobalWindow::DispatchDOMWindowCreated)); + } + } + + PreloadLocalStorage(); + + return NS_OK; +} + +void +nsGlobalWindow::PreloadLocalStorage() +{ + if (!Preferences::GetBool(kStorageEnabled)) { + return; + } + + if (IsChromeWindow()) { + return; + } + + nsIPrincipal* principal = GetPrincipal(); + if (!principal) { + return; + } + + nsresult rv; + nsCOMPtr firstPartyIsolationURI; + rv = GetFirstPartyIsolationURI(getter_AddRefs(firstPartyIsolationURI)); + if (NS_FAILED(rv)) { + return; + } + + nsCOMPtr storageManager = + do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv); + if (NS_FAILED(rv)) { + return; + } + + storageManager->PrecacheStorageForFirstParty(firstPartyIsolationURI, principal); +} + +void +nsGlobalWindow::DispatchDOMWindowCreated() +{ + 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(); + if (observerService) { + 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() +{ + SetStatus(EmptyString()); +} + +void +nsGlobalWindow::InnerSetNewDocument(JSContext* aCx, nsIDocument* aDocument) +{ + NS_PRECONDITION(IsInnerWindow(), "Must only be called on inner windows"); + MOZ_ASSERT(aDocument); + +#ifdef PR_LOGGING + if (gDOMLeakPRLog && PR_LOG_TEST(gDOMLeakPRLog, PR_LOG_DEBUG)) { + nsIURI *uri = aDocument->GetDocumentURI(); + nsAutoCString spec; + if (uri) + uri->GetSpec(spec); + PR_LogPrint("DOMWINDOW %p SetNewDocument %s", this, spec.get()); + } +#endif + + mDoc = aDocument; + if (IsDOMBinding()) { + WindowBinding::ClearCachedDocumentValue(aCx, this); + } + 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 + + 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(getter_AddRefs(parentWindow)); + if (parentWindow.get() != static_cast(this)) { + nsCOMPtr piWindow(do_QueryInterface(parentWindow)); + mChromeEventHandler = piWindow->GetChromeEventHandler(); + } + else { + mChromeEventHandler = NS_NewWindowRoot(this); + } + } + + 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 (nsRefPtr inner = (nsGlobalWindow *)PR_LIST_HEAD(this); + inner != this; + inner = (nsGlobalWindow*)PR_NEXT_LINK(inner)) { + NS_ASSERTION(!inner->mOuterWindow || inner->mOuterWindow == this, + "bad outer window pointer"); + inner->FreeInnerObjects(); + } + + // Make sure that this is called before we null out the document. + NotifyDOMWindowDestroyed(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) { + mContext->GC(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(nsIDOMWindow* aOpener, + bool aOriginalOpener) +{ + FORWARD_TO_OUTER_VOID(SetOpenerWindow, (aOpener, aOriginalOpener)); + + 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 = do_GetWeakReference(aOpener); + NS_ASSERTION(mOpener || !aOpener, "Opener must support weak references!"); + + if (aOriginalOpener) { + mHadOriginalOpener = true; + } + +#ifdef DEBUG + mSetOpenerWindowCalled = true; +#endif +} + +static +already_AddRefed +TryGetTabChildGlobalAsEventTarget(nsISupports *aFrom) +{ + nsCOMPtr frameLoaderOwner = do_QueryInterface(aFrom); + if (!frameLoaderOwner) { + return nullptr; + } + + nsRefPtr 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 = GetFrameElementInternal(); + nsCOMPtr eventTarget = + TryGetTabChildGlobalAsEventTarget(frameElement); + + if (!eventTarget) { + nsGlobalWindow* topWin = GetScriptableTop(); + if (topWin) { + frameElement = topWin->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; +} + +JSContext* +nsGlobalWindow::GetJSContextForEventHandlers() +{ + return nullptr; +} + +nsresult +nsGlobalWindow::PreHandleEvent(EventChainPreVisitor& aVisitor) +{ + NS_PRECONDITION(IsInnerWindow(), "PreHandleEvent is used on outer window!?"); + static uint32_t count = 0; + uint32_t msg = aVisitor.mEvent->message; + + aVisitor.mCanHandle = true; + aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119 + if ((msg == NS_MOUSE_MOVE) && gEntropyCollector) { + //Chances are this counter will overflow during the life of the + //process, but that's OK for our case. Means we get a little + //more entropy. + if (count++ % 100 == 0) { + //Since the high bits seem to be zero's most of the time, + //let's only take the lowest half of the point structure. + int16_t myCoord[2]; + + myCoord[0] = aVisitor.mEvent->refPoint.x; + myCoord[1] = aVisitor.mEvent->refPoint.y; + gEntropyCollector->RandomUpdate((void*)myCoord, sizeof(myCoord)); + gEntropyCollector->RandomUpdate((void*)&(aVisitor.mEvent->time), + sizeof(uint32_t)); + } + } else if (msg == NS_RESIZE_EVENT) { + mIsHandlingResizeEvent = true; + } else if (msg == NS_MOUSE_BUTTON_DOWN && + aVisitor.mEvent->mFlags.mIsTrusted) { + gMouseDown = true; + } else if ((msg == NS_MOUSE_BUTTON_UP || + msg == NS_DRAGDROP_END) && + aVisitor.mEvent->mFlags.mIsTrusted) { + 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->mFlags.mIsTrusted && + (aVisitor.mEvent->HasMouseEventMessage() || + aVisitor.mEvent->HasDragEventMessage())) { + mAddActiveEventFuzzTime = false; + } + + return NS_OK; +} + +bool +nsGlobalWindow::ShouldPromptToBlockDialogs() +{ + MOZ_ASSERT(IsOuterWindow()); + + nsGlobalWindow *topWindow = GetScriptableTop(); + 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() +{ + nsGlobalWindow *topWindow = GetScriptableTop(); + 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; + } + } + + return topWindow->mAreDialogsEnabled; +} + +bool +nsGlobalWindow::DialogsAreBeingAbused() +{ + MOZ_ASSERT(IsInnerWindow()); + NS_ASSERTION(GetScriptableTop() && + GetScriptableTop()->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(this, title.get(), label.get(), &disableDialog); + if (disableDialog) { + DisableDialogs(); + return false; + } + + return true; +} + +void +nsGlobalWindow::DisableDialogs() +{ + nsGlobalWindow *topWindow = GetScriptableTop(); + 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 = GetScriptableTop(); + 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->message) { + case NS_RESIZE_EVENT: + case NS_PAGE_UNLOAD: + case NS_LOAD: + 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); + nsCOMPtr kungFuDeathGrip2(GetContextInternal()); + + if (aVisitor.mEvent->message == NS_RESIZE_EVENT) { + mIsHandlingResizeEvent = false; + } else if (aVisitor.mEvent->message == NS_PAGE_UNLOAD && + aVisitor.mEvent->mFlags.mIsTrusted) { + // Execute bindingdetached handlers before we tear ourselves + // down. + if (mDoc) { + mDoc->BindingManager()->ExecuteDetachedHandlers(); + } + mIsDocumentLoaded = false; + } else if (aVisitor.mEvent->message == NS_LOAD && + aVisitor.mEvent->mFlags.mIsTrusted) { + // This is page load event since load events don't propagate to |window|. + // @see nsDocument::PreHandleEvent. + mIsDocumentLoaded = true; + + nsCOMPtr element = 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->mFlags.mIsTrusted, NS_LOAD); + event.mFlags.mBubbles = 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(!mIsModalContentWindow); // Handled separately. + nsIScriptContext *ctx = GetOuterWindowInternal()->mContext; + NS_ENSURE_TRUE(aArguments && ctx, NS_ERROR_NOT_INITIALIZED); + AutoJSContext cx; + + JS::Rooted obj(cx, GetWrapperPreserveColor()); + return ctx->SetProperty(obj, "arguments", aArguments); +} + +//***************************************************************************** +// 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 +//***************************************************************************** + +nsIURI* +nsPIDOMWindow::GetDocumentURI() const +{ + return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get(); +} + +nsIURI* +nsPIDOMWindow::GetDocBaseURI() const +{ + return mDoc ? mDoc->GetDocBaseURI() : mDocBaseURI.get(); +} + +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 = do_GetInterface(docShell); + } +} + +Element* +nsPIDOMWindow::GetFrameElementInternal() const +{ + if (mOuterWindow) { + return mOuterWindow->GetFrameElementInternal(); + } + + NS_ASSERTION(!IsInnerWindow(), + "GetFrameElementInternal() called on orphan inner window"); + + return mFrameElement; +} + +void +nsPIDOMWindow::SetFrameElementInternal(Element* aFrameElement) +{ + if (IsOuterWindow()) { + mFrameElement = aFrameElement; + + return; + } + + if (!mOuterWindow) { + NS_ERROR("frameElement set on inner window with no outer!"); + + return; + } + + mOuterWindow->SetFrameElementInternal(aFrameElement); +} + +void +nsPIDOMWindow::AddAudioContext(AudioContext* aAudioContext) +{ + mAudioContexts.AppendElement(aAudioContext); + + nsIDocShell* docShell = GetDocShell(); + if (docShell && !docShell->GetAllowMedia() && !aAudioContext->IsOffline()) { + aAudioContext->Mute(); + } +} + +void +nsPIDOMWindow::RemoveAudioContext(AudioContext* aAudioContext) +{ + mAudioContexts.RemoveElement(aAudioContext); +} + +void +nsPIDOMWindow::MuteAudioContexts() +{ + for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { + if (!mAudioContexts[i]->IsOffline()) { + mAudioContexts[i]->Mute(); + } + } +} + +void +nsPIDOMWindow::UnmuteAudioContexts() +{ + for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { + if (!mAudioContexts[i]->IsOffline()) { + mAudioContexts[i]->Unmute(); + } + } +} + +NS_IMETHODIMP +nsGlobalWindow::GetDocument(nsIDOMDocument** aDocument) +{ + nsCOMPtr document = do_QueryInterface(GetDocument()); + document.forget(aDocument); + return NS_OK; +} + +nsIDOMWindow* +nsGlobalWindow::GetWindow(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetWindow, (aError), aError, nullptr); + + return this; +} + +NS_IMETHODIMP +nsGlobalWindow::GetWindow(nsIDOMWindow** aWindow) +{ + ErrorResult rv; + nsCOMPtr window = GetWindow(rv); + window.forget(aWindow); + + return rv.ErrorCode(); +} + +nsIDOMWindow* +nsGlobalWindow::GetSelf(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetSelf, (aError), aError, nullptr); + + return this; +} + +NS_IMETHODIMP +nsGlobalWindow::GetSelf(nsIDOMWindow** aWindow) +{ + ErrorResult rv; + nsCOMPtr window = GetSelf(rv); + window.forget(aWindow); + + return rv.ErrorCode(); +} + +Navigator* +nsGlobalWindow::GetNavigator(ErrorResult& aError) +{ + FORWARD_TO_INNER_OR_THROW(GetNavigator, (aError), aError, nullptr); + + if (!mNavigator) { + mNavigator = new Navigator(this); + } + + return mNavigator; +} + +NS_IMETHODIMP +nsGlobalWindow::GetNavigator(nsIDOMNavigator** aNavigator) +{ + ErrorResult rv; + nsCOMPtr navigator = GetNavigator(rv); + navigator.forget(aNavigator); + + return rv.ErrorCode(); +} + +nsScreen* +nsGlobalWindow::GetScreen(ErrorResult& aError) +{ + FORWARD_TO_INNER_OR_THROW(GetScreen, (aError), aError, nullptr); + + if (!mScreen) { + mScreen = nsScreen::Create(this); + if (!mScreen) { + aError.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + } + + return mScreen; +} + +NS_IMETHODIMP +nsGlobalWindow::GetScreen(nsIDOMScreen** aScreen) +{ + ErrorResult rv; + nsRefPtr screen = GetScreen(rv); + screen.forget(aScreen); + + return rv.ErrorCode(); +} + +nsHistory* +nsGlobalWindow::GetHistory(ErrorResult& aError) +{ + FORWARD_TO_INNER_OR_THROW(GetHistory, (aError), aError, nullptr); + + if (!mHistory) { + mHistory = new nsHistory(this); + } + + return mHistory; +} + +NS_IMETHODIMP +nsGlobalWindow::GetHistory(nsISupports** aHistory) +{ + ErrorResult rv; + nsCOMPtr history = GetHistory(rv); + history.forget(aHistory); + + return rv.ErrorCode(); +} + +nsPerformance* +nsGlobalWindow::GetPerformance(ErrorResult& aError) +{ + FORWARD_TO_INNER_OR_THROW(GetPerformance, (aError), aError, nullptr); + + nsPerformance* p = nsPIDOMWindow::GetPerformance(); + if (!p) { + aError.Throw(NS_ERROR_FAILURE); + } + return p; +} + +NS_IMETHODIMP +nsGlobalWindow::GetPerformance(nsISupports** aPerformance) +{ + ErrorResult rv; + nsCOMPtr performance = GetPerformance(rv); + performance.forget(aPerformance); + + return rv.ErrorCode(); +} + +nsPerformance* +nsPIDOMWindow::GetPerformance() +{ + MOZ_ASSERT(IsInnerWindow()); + CreatePerformanceObjectIfNeeded(); + return mPerformance; +} + +void +nsPIDOMWindow::CreatePerformanceObjectIfNeeded() +{ + if (mPerformance || !mDoc) { + return; + } + nsRefPtr 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). + nsPerformance* parentPerformance = nullptr; + nsCOMPtr parentWindow; + GetScriptableParent(getter_AddRefs(parentWindow)); + nsCOMPtr parentPWindow = do_GetInterface(parentWindow); + if (GetOuterWindow() != parentPWindow) { + if (parentPWindow && !parentPWindow->IsInnerWindow()) { + parentPWindow = parentPWindow->GetCurrentInnerWindow(); + } + if (parentPWindow) { + parentPerformance = parentPWindow->GetPerformance(); + } + } + mPerformance = + new nsPerformance(this, timing, timedChannel, parentPerformance); + } +} + +bool +nsPIDOMWindow::GetAudioMuted() const +{ + if (!IsInnerWindow()) { + return mInnerWindow->GetAudioMuted(); + } + + return mAudioMuted; +} + +void +nsPIDOMWindow::SetAudioMuted(bool aMuted) +{ + if (!IsInnerWindow()) { + mInnerWindow->SetAudioMuted(aMuted); + return; + } + + if (mAudioMuted == aMuted) { + return; + } + + mAudioMuted = aMuted; + RefreshMediaElements(); +} + +float +nsPIDOMWindow::GetAudioVolume() const +{ + if (!IsInnerWindow()) { + return mInnerWindow->GetAudioVolume(); + } + + return mAudioVolume; +} + +nsresult +nsPIDOMWindow::SetAudioVolume(float aVolume) +{ + if (!IsInnerWindow()) { + return mInnerWindow->SetAudioVolume(aVolume); + } + + if (aVolume < 0.0) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + if (mAudioVolume == aVolume) { + return NS_OK; + } + + mAudioVolume = aVolume; + RefreshMediaElements(); + return NS_OK; +} + +float +nsPIDOMWindow::GetAudioGlobalVolume() +{ + float globalVolume = 1.0; + nsCOMPtr window = this; + + do { + if (window->GetAudioMuted()) { + return 0; + } + + globalVolume *= window->GetAudioVolume(); + + nsCOMPtr win; + window->GetParent(getter_AddRefs(win)); + if (window == win) { + break; + } + + window = do_QueryInterface(win); + + // If there is not parent, or we are the toplevel or the volume is + // already 0.0, we don't continue. + } while (window && window != this && globalVolume); + + return globalVolume; +} + +void +nsPIDOMWindow::RefreshMediaElements() +{ + nsRefPtr service = + AudioChannelService::GetAudioChannelService(); + if (service) { + service->RefreshAgentsVolume(this); + } +} + +// nsISpeechSynthesisGetter + +#ifdef MOZ_WEBSPEECH +SpeechSynthesis* +nsGlobalWindow::GetSpeechSynthesis(ErrorResult& aError) +{ + FORWARD_TO_INNER_OR_THROW(GetSpeechSynthesis, (aError), aError, nullptr); + + if (!mSpeechSynthesis) { + mSpeechSynthesis = new SpeechSynthesis(this); + } + + return mSpeechSynthesis; +} + +NS_IMETHODIMP +nsGlobalWindow::GetSpeechSynthesis(nsISupports** aSpeechSynthesis) +{ + ErrorResult rv; + nsCOMPtr speechSynthesis; + if (Preferences::GetBool("media.webspeech.synth.enabled")) { + speechSynthesis = GetSpeechSynthesis(rv); + } + speechSynthesis.forget(aSpeechSynthesis); + + return rv.ErrorCode(); +} +#endif + +already_AddRefed +nsGlobalWindow::GetParent(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetParent, (aError), aError, nullptr); + + if (!mDocShell) { + return nullptr; + } + + nsCOMPtr parent; + if (mDocShell->GetIsBrowserOrApp()) { + parent = this; + } else { + aError = GetRealParent(getter_AddRefs(parent)); + } + + return parent.forget(); +} + +/** + * GetScriptableParent is called when script reads window.parent. + * + * In contrast to GetRealParent, GetScriptableParent respects