diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /dom/base/nsGlobalWindow.cpp | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | uxp-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz |
Add m-esr52 at 52.6.0
Diffstat (limited to 'dom/base/nsGlobalWindow.cpp')
-rw-r--r-- | dom/base/nsGlobalWindow.cpp | 15189 |
1 files changed, 15189 insertions, 0 deletions
diff --git a/dom/base/nsGlobalWindow.cpp b/dom/base/nsGlobalWindow.cpp new file mode 100644 index 0000000000..8ff4b84ce3 --- /dev/null +++ b/dom/base/nsGlobalWindow.cpp @@ -0,0 +1,15189 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set ts=8 sts=2 et sw=2 tw=80: */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsGlobalWindow.h" + +#include <algorithm> + +#include "mozilla/MemoryReporting.h" + +// Local Includes +#include "Navigator.h" +#include "nsContentSecurityManager.h" +#include "nsScreen.h" +#include "nsHistory.h" +#include "nsDOMNavigationTiming.h" +#include "nsIDOMStorageManager.h" +#include "mozilla/dom/DOMStorage.h" +#include "mozilla/dom/IdleRequest.h" +#include "mozilla/dom/Performance.h" +#include "mozilla/dom/StorageEvent.h" +#include "mozilla/dom/StorageEventBinding.h" +#include "mozilla/dom/Timeout.h" +#include "mozilla/IntegerPrintfMacros.h" +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) +#include "mozilla/dom/WindowOrientationObserver.h" +#endif +#include "nsDOMOfflineResourceList.h" +#include "nsError.h" +#include "nsIIdleService.h" +#include "nsISizeOfEventTarget.h" +#include "nsDOMJSUtils.h" +#include "nsArrayUtils.h" +#include "nsIDOMWindowCollection.h" +#include "nsDOMWindowList.h" +#include "mozilla/dom/WakeLock.h" +#include "mozilla/dom/power/PowerManagerService.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIPermissionManager.h" +#include "nsIScriptContext.h" +#include "nsIScriptTimeoutHandler.h" +#include "nsITimeoutHandler.h" +#include "nsIController.h" +#include "nsScriptNameSpaceManager.h" +#include "nsISlowScriptDebug.h" +#include "nsWindowMemoryReporter.h" +#include "WindowNamedPropertiesHandler.h" +#include "nsFrameSelection.h" +#include "nsNetUtil.h" +#include "nsVariant.h" +#include "nsPrintfCString.h" + +// Helper Classes +#include "nsJSUtils.h" +#include "jsapi.h" // for JSAutoRequest +#include "jswrapper.h" +#include "nsCharSeparatedTokenizer.h" +#include "nsReadableUtils.h" +#include "nsDOMClassInfo.h" +#include "nsJSEnvironment.h" +#include "ScriptSettings.h" +#include "mozilla/Preferences.h" +#include "mozilla/Likely.h" +#include "mozilla/Sprintf.h" +#include "mozilla/Unused.h" + +// Other Classes +#include "mozilla/dom/BarProps.h" +#include "nsContentCID.h" +#include "nsLayoutStatics.h" +#include "nsCCUncollectableMarker.h" +#include "mozilla/dom/workers/Workers.h" +#include "mozilla/dom/ToJSValue.h" +#include "nsJSPrincipals.h" +#include "mozilla/Attributes.h" +#include "mozilla/Debug.h" +#include "mozilla/EventListenerManager.h" +#include "mozilla/EventStates.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/ProcessHangMonitor.h" +#include "mozilla/ThrottledEventQueue.h" +#include "AudioChannelService.h" +#include "nsAboutProtocolUtils.h" +#include "nsCharTraits.h" // NS_IS_HIGH/LOW_SURROGATE +#include "PostMessageEvent.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/TabGroup.h" + +// Interfaces Needed +#include "nsIFrame.h" +#include "nsCanvasFrame.h" +#include "nsIWidget.h" +#include "nsIWidgetListener.h" +#include "nsIBaseWindow.h" +#include "nsIDeviceSensors.h" +#include "nsIContent.h" +#include "nsIDocShell.h" +#include "nsIDocCharset.h" +#include "nsIDocument.h" +#include "Crypto.h" +#include "nsIDOMDocument.h" +#include "nsIDOMElement.h" +#include "nsIDOMEvent.h" +#include "nsIDOMOfflineResourceList.h" +#include "nsDOMString.h" +#include "nsIEmbeddingSiteWindow.h" +#include "nsThreadUtils.h" +#include "nsILoadContext.h" +#include "nsIPresShell.h" +#include "nsIScrollableFrame.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsISelectionController.h" +#include "nsISelection.h" +#include "nsIPrompt.h" +#include "nsIPromptService.h" +#include "nsIPromptFactory.h" +#include "nsIWritablePropertyBag2.h" +#include "nsIWebNavigation.h" +#include "nsIWebBrowserChrome.h" +#include "nsIWebBrowserFind.h" // For window.find() +#include "nsIWindowMediator.h" // For window.find() +#include "nsComputedDOMStyle.h" +#include "nsDOMCID.h" +#include "nsDOMWindowUtils.h" +#include "nsIWindowWatcher.h" +#include "nsPIWindowWatcher.h" +#include "nsIContentViewer.h" +#include "nsIScriptError.h" +#include "nsIControllers.h" +#include "nsIControllerContext.h" +#include "nsGlobalWindowCommands.h" +#include "nsQueryObject.h" +#include "nsContentUtils.h" +#include "nsCSSProps.h" +#include "nsIDOMFileList.h" +#include "nsIURIFixup.h" +#ifndef DEBUG +#include "nsIAppStartup.h" +#include "nsToolkitCompsCID.h" +#endif +#include "nsCDefaultURIFixup.h" +#include "mozilla/EventDispatcher.h" +#include "mozilla/EventStateManager.h" +#include "nsIObserverService.h" +#include "nsFocusManager.h" +#include "nsIXULWindow.h" +#include "nsITimedChannel.h" +#include "nsServiceManagerUtils.h" +#ifdef MOZ_XUL +#include "nsIDOMXULControlElement.h" +#include "nsMenuPopupFrame.h" +#endif +#include "mozilla/dom/CustomEvent.h" +#include "nsIJARChannel.h" +#include "nsIScreenManager.h" +#include "nsIEffectiveTLDService.h" + +#include "xpcprivate.h" + +#ifdef NS_PRINTING +#include "nsIPrintSettings.h" +#include "nsIPrintSettingsService.h" +#include "nsIWebBrowserPrint.h" +#endif + +#include "nsWindowRoot.h" +#include "nsNetCID.h" +#include "nsIArray.h" + +// XXX An unfortunate dependency exists here (two XUL files). +#include "nsIDOMXULDocument.h" +#include "nsIDOMXULCommandDispatcher.h" + +#include "nsBindingManager.h" +#include "nsXBLService.h" + +// used for popup blocking, needs to be converted to something +// belonging to the back-end like nsIContentPolicy +#include "nsIPopupWindowManager.h" + +#include "nsIDragService.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/Selection.h" +#include "nsFrameLoader.h" +#include "nsISupportsPrimitives.h" +#include "nsXPCOMCID.h" +#include "mozilla/Logging.h" +#include "prenv.h" +#include "prprf.h" + +#include "mozilla/dom/IDBFactory.h" +#include "mozilla/dom/MessageChannel.h" +#include "mozilla/dom/Promise.h" + +#ifdef MOZ_GAMEPAD +#include "mozilla/dom/Gamepad.h" +#include "mozilla/dom/GamepadManager.h" +#endif + +#include "mozilla/dom/VRDisplay.h" +#include "mozilla/dom/VREventObserver.h" + +#include "nsRefreshDriver.h" +#include "Layers.h" + +#include "mozilla/AddonPathService.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "mozilla/dom/Location.h" +#include "nsHTMLDocument.h" +#include "nsWrapperCacheInlines.h" +#include "mozilla/DOMEventTargetHelper.h" +#include "prrng.h" +#include "nsSandboxFlags.h" +#include "TimeChangeObserver.h" +#include "mozilla/dom/AudioContext.h" +#include "mozilla/dom/BrowserElementDictionariesBinding.h" +#include "mozilla/dom/cache/CacheStorage.h" +#include "mozilla/dom/Console.h" +#include "mozilla/dom/Fetch.h" +#include "mozilla/dom/FunctionBinding.h" +#include "mozilla/dom/HashChangeEvent.h" +#include "mozilla/dom/MozSelfSupportBinding.h" +#include "mozilla/dom/PopStateEvent.h" +#include "mozilla/dom/PopupBlockedEvent.h" +#include "mozilla/dom/PrimitiveConversions.h" +#include "mozilla/dom/WindowBinding.h" +#include "nsITabChild.h" +#include "mozilla/dom/MediaQueryList.h" +#include "mozilla/dom/ScriptSettings.h" +#include "mozilla/dom/NavigatorBinding.h" +#include "mozilla/dom/ImageBitmap.h" +#include "mozilla/dom/ImageBitmapBinding.h" +#include "mozilla/dom/ServiceWorkerRegistration.h" +#include "mozilla/dom/U2F.h" +#include "mozilla/dom/WebIDLGlobalNameHash.h" +#include "mozilla/dom/Worklet.h" +#ifdef HAVE_SIDEBAR +#include "mozilla/dom/ExternalBinding.h" +#endif + +#ifdef MOZ_WEBSPEECH +#include "mozilla/dom/SpeechSynthesis.h" +#endif + +#ifdef MOZ_B2G +#include "nsPISocketTransportService.h" +#endif + +// Apple system headers seem to have a check() macro. <sigh> +#ifdef check +class nsIScriptTimeoutHandler; +#undef check +#endif // check +#include "AccessCheck.h" + +#ifdef ANDROID +#include <android/log.h> +#endif + +#ifdef XP_WIN +#include <process.h> +#define getpid _getpid +#else +#include <unistd.h> // for getpid() +#endif + +static const char kStorageEnabled[] = "dom.storage.enabled"; + +using namespace mozilla; +using namespace mozilla::dom; +using namespace mozilla::dom::ipc; +using mozilla::BasePrincipal; +using mozilla::PrincipalOriginAttributes; +using mozilla::TimeStamp; +using mozilla::TimeDuration; +using mozilla::dom::cache::CacheStorage; + +static LazyLogModule gDOMLeakPRLog("DOMLeak"); + +nsGlobalWindow::WindowByIdTable *nsGlobalWindow::sWindowsById = nullptr; +bool nsGlobalWindow::sWarnedAboutWindowInternal = false; +bool nsGlobalWindow::sIdleObserversAPIFuzzTimeDisabled = false; + +static int32_t gRefCnt = 0; +static int32_t gOpenPopupSpamCount = 0; +static PopupControlState gPopupControlState = openAbused; +static int32_t gRunningTimeoutDepth = 0; +static bool gMouseDown = false; +static bool gDragServiceDisabled = false; +static FILE *gDumpFile = nullptr; +static uint32_t gSerialCounter = 0; +static TimeStamp gLastRecordedRecentTimeouts; +#define STATISTICS_INTERVAL (30 * PR_MSEC_PER_SEC) + +#ifdef DEBUG_jst +int32_t gTimeoutCnt = 0; +#endif + +#if defined(DEBUG_bryner) || defined(DEBUG_chb) +#define DEBUG_PAGE_CACHE +#endif + +#define DOM_TOUCH_LISTENER_ADDED "dom-touch-listener-added" + +// The default shortest interval/timeout we permit +#define DEFAULT_MIN_TIMEOUT_VALUE 4 // 4ms +#define DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE 1000 // 1000ms +static int32_t gMinTimeoutValue; +static int32_t gMinBackgroundTimeoutValue; +inline int32_t +nsGlobalWindow::DOMMinTimeoutValue() const { + // First apply any back pressure delay that might be in effect. + int32_t value = std::max(mBackPressureDelayMS, 0); + // Don't use the background timeout value when there are audio contexts + // present, so that baackground audio can keep running smoothly. (bug 1181073) + bool isBackground = mAudioContexts.IsEmpty() && + (!mOuterWindow || mOuterWindow->IsBackground()); + return + std::max(isBackground ? gMinBackgroundTimeoutValue : gMinTimeoutValue, value); +} + +// The number of nested timeouts before we start clamping. HTML5 says 1, WebKit +// uses 5. +#define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5 + +// The longest interval (as PRIntervalTime) we permit, or that our +// timer code can handle, really. See DELAY_INTERVAL_LIMIT in +// nsTimerImpl.h for details. +#define DOM_MAX_TIMEOUT_VALUE DELAY_INTERVAL_LIMIT + +// The interval at which we execute idle callbacks +static uint32_t gThrottledIdlePeriodLength; + +#define DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH 10000 + +#define FORWARD_TO_OUTER(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsInnerWindow()) { \ + nsGlobalWindow *outer = GetOuterWindowInternal(); \ + if (!AsInner()->HasActiveDocument()) { \ + NS_WARNING(outer ? \ + "Inner window does not have active document." : \ + "No outer window available!"); \ + return err_rval; \ + } \ + return outer->method args; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_OUTER_OR_THROW(method, args, errorresult, err_rval) \ + PR_BEGIN_MACRO \ + MOZ_RELEASE_ASSERT(IsInnerWindow()); \ + nsGlobalWindow *outer = GetOuterWindowInternal(); \ + if (MOZ_LIKELY(AsInner()->HasActiveDocument())) { \ + return outer->method args; \ + } \ + if (!outer) { \ + NS_WARNING("No outer window available!"); \ + errorresult.Throw(NS_ERROR_NOT_INITIALIZED); \ + } else { \ + errorresult.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO); \ + } \ + return err_rval; \ + PR_END_MACRO + +#define FORWARD_TO_OUTER_VOID(method, args) \ + PR_BEGIN_MACRO \ + if (IsInnerWindow()) { \ + nsGlobalWindow *outer = GetOuterWindowInternal(); \ + if (!AsInner()->HasActiveDocument()) { \ + NS_WARNING(outer ? \ + "Inner window does not have active document." : \ + "No outer window available!"); \ + return; \ + } \ + outer->method args; \ + return; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_OUTER_CHROME(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsInnerWindow()) { \ + nsGlobalWindow *outer = GetOuterWindowInternal(); \ + if (!AsInner()->HasActiveDocument()) { \ + NS_WARNING(outer ? \ + "Inner window does not have active document." : \ + "No outer window available!"); \ + return err_rval; \ + } \ + return ((nsGlobalChromeWindow *)outer)->method args; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_INNER_CHROME(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsOuterWindow()) { \ + if (!mInnerWindow) { \ + NS_WARNING("No inner window available!"); \ + return err_rval; \ + } \ + return ((nsGlobalChromeWindow *)nsGlobalWindow::Cast(mInnerWindow))->method args; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsInnerWindow()) { \ + nsGlobalWindow *outer = GetOuterWindowInternal(); \ + if (!AsInner()->HasActiveDocument()) { \ + NS_WARNING(outer ? \ + "Inner window does not have active document." : \ + "No outer window available!"); \ + return err_rval; \ + } \ + return ((nsGlobalModalWindow *)outer)->method args; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_INNER(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsOuterWindow()) { \ + if (!mInnerWindow) { \ + NS_WARNING("No inner window available!"); \ + return err_rval; \ + } \ + return GetCurrentInnerWindowInternal()->method args; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_INNER_MODAL_CONTENT_WINDOW(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsOuterWindow()) { \ + if (!mInnerWindow) { \ + NS_WARNING("No inner window available!"); \ + return err_rval; \ + } \ + return ((nsGlobalModalWindow*)GetCurrentInnerWindowInternal())->method args; \ + } \ + PR_END_MACRO + +#define FORWARD_TO_INNER_VOID(method, args) \ + PR_BEGIN_MACRO \ + if (IsOuterWindow()) { \ + if (!mInnerWindow) { \ + NS_WARNING("No inner window available!"); \ + return; \ + } \ + GetCurrentInnerWindowInternal()->method args; \ + return; \ + } \ + PR_END_MACRO + +// Same as FORWARD_TO_INNER, but this will create a fresh inner if an +// inner doesn't already exists. +#define FORWARD_TO_INNER_CREATE(method, args, err_rval) \ + PR_BEGIN_MACRO \ + if (IsOuterWindow()) { \ + if (!mInnerWindow) { \ + if (mIsClosed) { \ + return err_rval; \ + } \ + nsCOMPtr<nsIDocument> kungFuDeathGrip = GetDoc(); \ + ::mozilla::Unused << kungFuDeathGrip; \ + if (!mInnerWindow) { \ + return err_rval; \ + } \ + } \ + return GetCurrentInnerWindowInternal()->method args; \ + } \ + PR_END_MACRO + +// CIDs +static NS_DEFINE_CID(kXULControllersCID, NS_XULCONTROLLERS_CID); + +#define NETWORK_UPLOAD_EVENT_NAME NS_LITERAL_STRING("moznetworkupload") +#define NETWORK_DOWNLOAD_EVENT_NAME NS_LITERAL_STRING("moznetworkdownload") + +/** + * An indirect observer object that means we don't have to implement nsIObserver + * on nsGlobalWindow, where any script could see it. + */ +class nsGlobalWindowObserver final : public nsIObserver, + public nsIInterfaceRequestor +{ +public: + explicit nsGlobalWindowObserver(nsGlobalWindow* aWindow) : mWindow(aWindow) {} + NS_DECL_ISUPPORTS + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) override + { + if (!mWindow) + return NS_OK; + return mWindow->Observe(aSubject, aTopic, aData); + } + void Forget() { mWindow = nullptr; } + NS_IMETHOD GetInterface(const nsIID& aIID, void** aResult) override + { + if (mWindow && aIID.Equals(NS_GET_IID(nsIDOMWindow)) && mWindow) { + return mWindow->QueryInterface(aIID, aResult); + } + return NS_NOINTERFACE; + } + +private: + ~nsGlobalWindowObserver() {} + + // This reference is non-owning and safe because it's cleared by + // nsGlobalWindow::CleanUp(). + nsGlobalWindow* MOZ_NON_OWNING_REF mWindow; +}; + +NS_IMPL_ISUPPORTS(nsGlobalWindowObserver, nsIObserver, nsIInterfaceRequestor) + +static already_AddRefed<nsIVariant> +CreateVoidVariant() +{ + RefPtr<nsVariantCC> writable = new nsVariantCC(); + writable->SetAsVoid(); + return writable.forget(); +} + +nsresult +DialogValueHolder::Get(nsIPrincipal* aSubject, nsIVariant** aResult) +{ + nsCOMPtr<nsIVariant> result; + if (aSubject->SubsumesConsideringDomain(mOrigin)) { + result = mValue; + } else { + result = CreateVoidVariant(); + } + result.forget(aResult); + return NS_OK; +} + +void +DialogValueHolder::Get(JSContext* aCx, JS::Handle<JSObject*> aScope, + nsIPrincipal* aSubject, + JS::MutableHandle<JS::Value> aResult, + mozilla::ErrorResult& aError) +{ + if (aSubject->Subsumes(mOrigin)) { + aError = nsContentUtils::XPConnect()->VariantToJS(aCx, aScope, + mValue, aResult); + } else { + aResult.setUndefined(); + } +} + +void +nsGlobalWindow::PostThrottledIdleCallback() +{ + AssertIsOnMainThread(); + + if (mThrottledIdleRequestCallbacks.isEmpty()) + return; + + RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst()); + // ownership transferred from mThrottledIdleRequestCallbacks to + // mIdleRequestCallbacks + mIdleRequestCallbacks.insertBack(request); + NS_IdleDispatchToCurrentThread(request.forget()); +} + +/* static */ void +nsGlobalWindow::InsertIdleCallbackIntoList(IdleRequest* aRequest, + IdleRequests& aList) +{ + aList.insertBack(aRequest); + aRequest->AddRef(); +} + +uint32_t +nsGlobalWindow::RequestIdleCallback(JSContext* aCx, + IdleRequestCallback& aCallback, + const IdleRequestOptions& aOptions, + ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + AssertIsOnMainThread(); + + uint32_t handle = ++mIdleRequestCallbackCounter; + + RefPtr<IdleRequest> request = + new IdleRequest(aCx, AsInner(), aCallback, handle); + + if (aOptions.mTimeout.WasPassed()) { + aError = request->SetTimeout(aOptions.mTimeout.Value()); + if (NS_WARN_IF(aError.Failed())) { + return 0; + } + } + + nsGlobalWindow* outer = GetOuterWindowInternal(); + if (outer && outer->AsOuter()->IsBackground()) { + // mThrottledIdleRequestCallbacks now owns request + InsertIdleCallbackIntoList(request, mThrottledIdleRequestCallbacks); + + NS_DelayedDispatchToCurrentThread( + NewRunnableMethod(this, &nsGlobalWindow::PostThrottledIdleCallback), + 10000); + } else { + MOZ_ASSERT(mThrottledIdleRequestCallbacks.isEmpty()); + + // mIdleRequestCallbacks now owns request + InsertIdleCallbackIntoList(request, mIdleRequestCallbacks); + + NS_IdleDispatchToCurrentThread(request.forget()); + } + + return handle; +} + +void +nsGlobalWindow::CancelIdleCallback(uint32_t aHandle) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + for (IdleRequest* r : mIdleRequestCallbacks) { + if (r->Handle() == aHandle) { + r->Cancel(); + break; + } + } +} + +void +nsGlobalWindow::DisableIdleCallbackRequests() +{ + while (!mIdleRequestCallbacks.isEmpty()) { + RefPtr<IdleRequest> request = mIdleRequestCallbacks.popFirst(); + request->Cancel(); + } + + while (!mThrottledIdleRequestCallbacks.isEmpty()) { + RefPtr<IdleRequest> request = mThrottledIdleRequestCallbacks.popFirst(); + request->Cancel(); + } +} + +void nsGlobalWindow::UnthrottleIdleCallbackRequests() +{ + AssertIsOnMainThread(); + + while (!mThrottledIdleRequestCallbacks.isEmpty()) { + RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst()); + mIdleRequestCallbacks.insertBack(request); + NS_IdleDispatchToCurrentThread(request.forget()); + } +} + + +namespace mozilla { +namespace dom { +extern uint64_t +NextWindowID(); +} // namespace dom +} // namespace mozilla + +template<class T> +nsPIDOMWindow<T>::nsPIDOMWindow(nsPIDOMWindowOuter *aOuterWindow) +: mFrameElement(nullptr), mDocShell(nullptr), mModalStateDepth(0), + mRunningTimeout(nullptr), mMutationBits(0), mIsDocumentLoaded(false), + mIsHandlingResizeEvent(false), mIsInnerWindow(aOuterWindow != nullptr), + mMayHavePaintEventListener(false), mMayHaveTouchEventListener(false), + mMayHaveMouseEnterLeaveEventListener(false), + mMayHavePointerEnterLeaveEventListener(false), + mInnerObjectsFreed(false), + mIsModalContentWindow(false), + mIsActive(false), mIsBackground(false), + mMediaSuspend(Preferences::GetBool("media.block-autoplay-until-in-foreground", true) ? + nsISuspendedTypes::SUSPENDED_BLOCK : nsISuspendedTypes::NONE_SUSPENDED), + mAudioMuted(false), mAudioVolume(1.0), mAudioCaptured(false), + mDesktopModeViewport(false), mIsRootOuterWindow(false), mInnerWindow(nullptr), + mOuterWindow(aOuterWindow), + // Make sure no actual window ends up with mWindowID == 0 + mWindowID(NextWindowID()), mHasNotifiedGlobalCreated(false), + mMarkedCCGeneration(0), mServiceWorkersTestingEnabled(false) + {} + +template<class T> +nsPIDOMWindow<T>::~nsPIDOMWindow() {} + +/* static */ +nsPIDOMWindowOuter* +nsPIDOMWindowOuter::GetFromCurrentInner(nsPIDOMWindowInner* aInner) +{ + if (!aInner) { + return nullptr; + } + + nsPIDOMWindowOuter* outer = aInner->GetOuterWindow(); + if (!outer || outer->GetCurrentInnerWindow() != aInner) { + return nullptr; + } + + return outer; +} + +// DialogValueHolder CC goop. +NS_IMPL_CYCLE_COLLECTION(DialogValueHolder, mValue) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(DialogValueHolder) + NS_INTERFACE_MAP_ENTRY(nsISupports) +NS_INTERFACE_MAP_END + +NS_IMPL_CYCLE_COLLECTING_ADDREF(DialogValueHolder) +NS_IMPL_CYCLE_COLLECTING_RELEASE(DialogValueHolder) + +//***************************************************************************** +// nsOuterWindowProxy: Outer Window Proxy +//***************************************************************************** + +class nsOuterWindowProxy : public js::Wrapper +{ +public: + constexpr nsOuterWindowProxy() : js::Wrapper(0) { } + + virtual bool finalizeInBackground(const JS::Value& priv) const override { + return false; + } + + // Standard internal methods + virtual bool getOwnPropertyDescriptor(JSContext* cx, + JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, + JS::MutableHandle<JS::PropertyDescriptor> desc) + const override; + virtual bool defineProperty(JSContext* cx, + JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, + JS::Handle<JS::PropertyDescriptor> desc, + JS::ObjectOpResult &result) const override; + virtual bool ownPropertyKeys(JSContext *cx, + JS::Handle<JSObject*> proxy, + JS::AutoIdVector &props) const override; + virtual bool delete_(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, + JS::ObjectOpResult &result) const override; + + virtual bool getPrototypeIfOrdinary(JSContext* cx, + JS::Handle<JSObject*> proxy, + bool* isOrdinary, + JS::MutableHandle<JSObject*> protop) const override; + + virtual bool enumerate(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::MutableHandle<JSObject*> vp) const override; + virtual bool preventExtensions(JSContext* cx, + JS::Handle<JSObject*> proxy, + JS::ObjectOpResult& result) const override; + virtual bool isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, bool *extensible) + const override; + virtual bool has(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, bool *bp) const override; + virtual bool get(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<JS::Value> receiver, + JS::Handle<jsid> id, + JS::MutableHandle<JS::Value> vp) const override; + virtual bool set(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, JS::Handle<JS::Value> v, + JS::Handle<JS::Value> receiver, + JS::ObjectOpResult &result) const override; + + // SpiderMonkey extensions + virtual bool getPropertyDescriptor(JSContext* cx, + JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, + JS::MutableHandle<JS::PropertyDescriptor> desc) + const override; + virtual bool hasOwn(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, bool *bp) const override; + virtual bool getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::AutoIdVector &props) const override; + virtual const char *className(JSContext *cx, + JS::Handle<JSObject*> wrapper) const override; + + virtual void finalize(JSFreeOp *fop, JSObject *proxy) const override; + + virtual bool isCallable(JSObject *obj) const override { + return false; + } + virtual bool isConstructor(JSObject *obj) const override { + return false; + } + + virtual bool watch(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, JS::Handle<JSObject*> callable) const override; + virtual bool unwatch(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id) const override; + + static void ObjectMoved(JSObject *obj, const JSObject *old); + + static const nsOuterWindowProxy singleton; + +protected: + static nsGlobalWindow* GetOuterWindow(JSObject *proxy) + { + nsGlobalWindow* outerWindow = nsGlobalWindow::FromSupports( + static_cast<nsISupports*>(js::GetProxyExtra(proxy, 0).toPrivate())); + MOZ_ASSERT_IF(outerWindow, outerWindow->IsOuterWindow()); + return outerWindow; + } + + // False return value means we threw an exception. True return value + // but false "found" means we didn't have a subframe at that index. + bool GetSubframeWindow(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, + JS::MutableHandle<JS::Value> vp, + bool &found) const; + + // Returns a non-null window only if id is an index and we have a + // window at that index. + already_AddRefed<nsPIDOMWindowOuter> + GetSubframeWindow(JSContext *cx, + JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id) const; + + bool AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy, + JS::AutoIdVector &props) const; +}; + +static const js::ClassExtension OuterWindowProxyClassExtension = PROXY_MAKE_EXT( + nsOuterWindowProxy::ObjectMoved +); + +const js::Class OuterWindowProxyClass = PROXY_CLASS_WITH_EXT( + "Proxy", + 0, /* additional class flags */ + &OuterWindowProxyClassExtension); + +const char * +nsOuterWindowProxy::className(JSContext *cx, JS::Handle<JSObject*> proxy) const +{ + MOZ_ASSERT(js::IsProxy(proxy)); + + return "Window"; +} + +void +nsOuterWindowProxy::finalize(JSFreeOp *fop, JSObject *proxy) const +{ + nsGlobalWindow* outerWindow = GetOuterWindow(proxy); + if (outerWindow) { + outerWindow->ClearWrapper(); + + // Ideally we would use OnFinalize here, but it's possible that + // EnsureScriptEnvironment will later be called on the window, and we don't + // want to create a new script object in that case. Therefore, we need to + // write a non-null value that will reliably crash when dereferenced. + outerWindow->PoisonOuterWindowProxy(proxy); + } +} + +bool +nsOuterWindowProxy::getPropertyDescriptor(JSContext* cx, + JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, + JS::MutableHandle<JS::PropertyDescriptor> desc) const +{ + // The only thing we can do differently from js::Wrapper is shadow stuff with + // our indexed properties, so we can just try getOwnPropertyDescriptor and if + // that gives us nothing call on through to js::Wrapper. + desc.object().set(nullptr); + if (!getOwnPropertyDescriptor(cx, proxy, id, desc)) { + return false; + } + + if (desc.object()) { + return true; + } + + return js::Wrapper::getPropertyDescriptor(cx, proxy, id, desc); +} + +bool +nsOuterWindowProxy::getOwnPropertyDescriptor(JSContext* cx, + JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, + JS::MutableHandle<JS::PropertyDescriptor> desc) + const +{ + bool found; + if (!GetSubframeWindow(cx, proxy, id, desc.value(), found)) { + return false; + } + if (found) { + FillPropertyDescriptor(desc, proxy, true); + return true; + } + // else fall through to js::Wrapper + + // When we change this to always claim the property is configurable (bug + // 1178639), update the comments in nsOuterWindowProxy::defineProperty + // accordingly. + return js::Wrapper::getOwnPropertyDescriptor(cx, proxy, id, desc); +} + +bool +nsOuterWindowProxy::defineProperty(JSContext* cx, + JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, + JS::Handle<JS::PropertyDescriptor> desc, + JS::ObjectOpResult &result) const +{ + if (IsArrayIndex(GetArrayIndexFromId(cx, id))) { + // Spec says to Reject whether this is a supported index or not, + // since we have no indexed setter or indexed creator. It is up + // to the caller to decide whether to throw a TypeError. + return result.failCantDefineWindowElement(); + } + +#ifndef RELEASE_OR_BETA // To be turned on in bug 1178638. + // For now, allow chrome code to define non-configurable properties + // on windows, until we sort out what exactly the addon SDK is + // doing. In the meantime, this still allows us to test web compat + // behavior. + if (desc.hasConfigurable() && !desc.configurable() && + !nsContentUtils::IsCallerChrome()) { + return ThrowErrorMessage(cx, MSG_DEFINE_NON_CONFIGURABLE_PROP_ON_WINDOW); + } + + // Note that if hasConfigurable() is false we do NOT want to + // setConfigurable(true). That would make this code: + // + // var x; + // window.x = 5; + // + // fail, because the JS engine ends up converting the assignment into a define + // with !hasConfigurable(), but the var actually declared a non-configurable + // property on our underlying Window object, so the set would fail if we + // forced setConfigurable(true) here. What we want to do instead is change + // getOwnPropertyDescriptor to always claim configurable. See bug 1178639. +#endif + + return js::Wrapper::defineProperty(cx, proxy, id, desc, result); +} + +bool +nsOuterWindowProxy::ownPropertyKeys(JSContext *cx, + JS::Handle<JSObject*> proxy, + JS::AutoIdVector &props) const +{ + // Just our indexed stuff followed by our "normal" own property names. + if (!AppendIndexedPropertyNames(cx, proxy, props)) { + return false; + } + + JS::AutoIdVector innerProps(cx); + if (!js::Wrapper::ownPropertyKeys(cx, proxy, innerProps)) { + return false; + } + return js::AppendUnique(cx, props, innerProps); +} + +bool +nsOuterWindowProxy::delete_(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, JS::ObjectOpResult &result) const +{ + if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) { + // Fail (which means throw if strict, else return false). + return result.failCantDeleteWindowElement(); + } + + if (IsArrayIndex(GetArrayIndexFromId(cx, id))) { + // Indexed, but not supported. Spec says return true. + return result.succeed(); + } + + return js::Wrapper::delete_(cx, proxy, id, result); +} + +bool +nsOuterWindowProxy::getPrototypeIfOrdinary(JSContext* cx, + JS::Handle<JSObject*> proxy, + bool* isOrdinary, + JS::MutableHandle<JSObject*> protop) const +{ + // Window's [[GetPrototypeOf]] trap isn't the ordinary definition: + // + // https://html.spec.whatwg.org/multipage/browsers.html#windowproxy-getprototypeof + // + // We nonetheless can implement it with a static [[Prototype]], because + // wrapper-class handlers (particularly, XOW in FilteringWrapper.cpp) supply + // all non-ordinary behavior. + // + // But from a spec point of view, it's the exact same object in both cases -- + // only the observer's changed. So this getPrototypeIfOrdinary trap on the + // non-wrapper object *must* report non-ordinary, even if static [[Prototype]] + // usually means ordinary. + *isOrdinary = false; + return true; +} + +bool +nsOuterWindowProxy::preventExtensions(JSContext* cx, + JS::Handle<JSObject*> proxy, + JS::ObjectOpResult& result) const +{ + // If [[Extensible]] could be false, then navigating a window could navigate + // to a window that's [[Extensible]] after being at one that wasn't: an + // invariant violation. So never change a window's extensibility. + return result.failCantPreventExtensions(); +} + +bool +nsOuterWindowProxy::isExtensible(JSContext *cx, JS::Handle<JSObject*> proxy, + bool *extensible) const +{ + // See above. + *extensible = true; + return true; +} + +bool +nsOuterWindowProxy::has(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, bool *bp) const +{ + if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) { + *bp = true; + return true; + } + + return js::Wrapper::has(cx, proxy, id, bp); +} + +bool +nsOuterWindowProxy::hasOwn(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, bool *bp) const +{ + if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) { + *bp = true; + return true; + } + + return js::Wrapper::hasOwn(cx, proxy, id, bp); +} + +bool +nsOuterWindowProxy::get(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<JS::Value> receiver, + JS::Handle<jsid> id, + JS::MutableHandle<JS::Value> vp) const +{ + if (id == 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<JSObject*> proxy, + JS::Handle<jsid> id, + JS::Handle<JS::Value> v, + JS::Handle<JS::Value> receiver, + JS::ObjectOpResult &result) const +{ + if (IsArrayIndex(GetArrayIndexFromId(cx, id))) { + // Reject the set. It's up to the caller to decide whether to throw a + // TypeError. If the caller is strict mode JS code, it'll throw. + return result.failReadOnly(); + } + + return js::Wrapper::set(cx, proxy, id, v, receiver, result); +} + +bool +nsOuterWindowProxy::getOwnEnumerablePropertyKeys(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::AutoIdVector &props) const +{ + // BaseProxyHandler::keys seems to do what we want here: call + // ownPropertyKeys and then filter out the non-enumerable properties. + return js::BaseProxyHandler::getOwnEnumerablePropertyKeys(cx, proxy, props); +} + +bool +nsOuterWindowProxy::enumerate(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::MutableHandle<JSObject*> objp) const +{ + // BaseProxyHandler::enumerate seems to do what we want here: fall + // back on the property names returned from js::GetPropertyKeys() + return js::BaseProxyHandler::enumerate(cx, proxy, objp); +} + +bool +nsOuterWindowProxy::GetSubframeWindow(JSContext *cx, + JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, + JS::MutableHandle<JS::Value> vp, + bool& found) const +{ + nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id); + if (!frame) { + found = false; + return true; + } + + found = true; + // Just return the window's global + nsGlobalWindow* global = nsGlobalWindow::Cast(frame); + frame->EnsureInnerWindow(); + JSObject* obj = global->FastGetGlobalJSObject(); + // This null check fixes a hard-to-reproduce crash that occurs when we + // get here when we're mid-call to nsDocShell::Destroy. See bug 640904 + // comment 105. + if (MOZ_UNLIKELY(!obj)) { + return xpc::Throw(cx, NS_ERROR_FAILURE); + } + JS::ExposeObjectToActiveJS(obj); + vp.setObject(*obj); + return JS_WrapValue(cx, vp); +} + +already_AddRefed<nsPIDOMWindowOuter> +nsOuterWindowProxy::GetSubframeWindow(JSContext *cx, + JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id) const +{ + uint32_t index = GetArrayIndexFromId(cx, id); + if (!IsArrayIndex(index)) { + return nullptr; + } + + nsGlobalWindow* win = GetOuterWindow(proxy); + MOZ_ASSERT(win->IsOuterWindow()); + return win->IndexedGetterOuter(index); +} + +bool +nsOuterWindowProxy::AppendIndexedPropertyNames(JSContext *cx, JSObject *proxy, + JS::AutoIdVector &props) const +{ + uint32_t length = GetOuterWindow(proxy)->Length(); + MOZ_ASSERT(int32_t(length) >= 0); + if (!props.reserve(props.length() + length)) { + return false; + } + for (int32_t i = 0; i < int32_t(length); ++i) { + if (!props.append(INT_TO_JSID(i))) { + return false; + } + } + + return true; +} + +bool +nsOuterWindowProxy::watch(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id, JS::Handle<JSObject*> callable) const +{ + return js::WatchGuts(cx, proxy, id, callable); +} + +bool +nsOuterWindowProxy::unwatch(JSContext *cx, JS::Handle<JSObject*> proxy, + JS::Handle<jsid> id) const +{ + return js::UnwatchGuts(cx, proxy, id); +} + +void +nsOuterWindowProxy::ObjectMoved(JSObject *obj, const JSObject *old) +{ + nsGlobalWindow* outerWindow = GetOuterWindow(obj); + if (outerWindow) { + outerWindow->UpdateWrapper(obj, old); + } +} + +const nsOuterWindowProxy +nsOuterWindowProxy::singleton; + +class nsChromeOuterWindowProxy : public nsOuterWindowProxy +{ +public: + constexpr nsChromeOuterWindowProxy() : nsOuterWindowProxy() { } + + virtual const char *className(JSContext *cx, JS::Handle<JSObject*> wrapper) const override; + + static const nsChromeOuterWindowProxy singleton; +}; + +const char * +nsChromeOuterWindowProxy::className(JSContext *cx, + JS::Handle<JSObject*> proxy) const +{ + MOZ_ASSERT(js::IsProxy(proxy)); + + return "ChromeWindow"; +} + +const nsChromeOuterWindowProxy +nsChromeOuterWindowProxy::singleton; + +static JSObject* +NewOuterWindowProxy(JSContext *cx, JS::Handle<JSObject*> global, bool isChrome) +{ + JSAutoCompartment ac(cx, global); + MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(global) == global); + + js::WrapperOptions options; + options.setClass(&OuterWindowProxyClass); + options.setSingleton(true); + JSObject *obj = js::Wrapper::New(cx, global, + isChrome ? &nsChromeOuterWindowProxy::singleton + : &nsOuterWindowProxy::singleton, + options); + MOZ_ASSERT_IF(obj, js::IsWindowProxy(obj)); + return obj; +} + +namespace { + +// The maximum number of timer callbacks we will try to run in a single event +// loop runnable. +#define DEFAULT_TARGET_MAX_CONSECUTIVE_CALLBACKS 5 +uint32_t gTargetMaxConsecutiveCallbacks; + +// The number of queued runnables within the TabGroup ThrottledEventQueue +// at which to begin applying back pressure to the window. +#define DEFAULT_THROTTLED_EVENT_QUEUE_BACK_PRESSURE 5000 +static uint32_t gThrottledEventQueueBackPressure; + +// The amount of delay to apply to timers when back pressure is triggered. +// As the length of the ThrottledEventQueue grows delay is increased. The +// delay is scaled such that every kThrottledEventQueueBackPressure runnables +// in the queue equates to an additional kBackPressureDelayMS. +#define DEFAULT_BACK_PRESSURE_DELAY_MS 250 +static uint32_t gBackPressureDelayMS; + +// This defines a limit for how much the delay must drop before we actually +// reduce back pressure throttle amount. This makes the throttle delay +// a bit "sticky" once we enter back pressure. +#define DEFAULT_BACK_PRESSURE_DELAY_REDUCTION_THRESHOLD_MS 1000 +static uint32_t gBackPressureDelayReductionThresholdMS; + +// The minimum delay we can reduce back pressure to before we just floor +// the value back to zero. This allows us to ensure that we can exit +// back pressure event if there are always a small number of runnables +// queued up. +#define DEFAULT_BACK_PRESSURE_DELAY_MINIMUM_MS 100 +static uint32_t gBackPressureDelayMinimumMS; + +} // anonymous namespace + +//***************************************************************************** +//*** nsGlobalWindow: Object Management +//***************************************************************************** + +nsGlobalWindow::nsGlobalWindow(nsGlobalWindow *aOuterWindow) + : nsPIDOMWindow<nsISupports>(aOuterWindow ? aOuterWindow->AsOuter() : nullptr), + mIdleFuzzFactor(0), + mIdleCallbackIndex(-1), + mCurrentlyIdle(false), + mAddActiveEventFuzzTime(true), + mFullScreen(false), + mFullscreenMode(false), + mIsClosed(false), + mInClose(false), + mHavePendingClose(false), + mHadOriginalOpener(false), + mOriginalOpenerWasSecureContext(false), + mIsPopupSpam(false), + mBlockScriptedClosingFlag(false), + mWasOffline(false), + mHasHadSlowScript(false), + mNotifyIdleObserversIdleOnThaw(false), + mNotifyIdleObserversActiveOnThaw(false), + mCreatingInnerWindow(false), + mIsChrome(false), + mCleanMessageManager(false), + mNeedsFocus(true), + mHasFocus(false), + mShowFocusRingForContent(false), + mFocusByKeyOccurred(false), + mHasGamepad(false), + mHasVREvents(false), +#ifdef MOZ_GAMEPAD + mHasSeenGamepadInput(false), +#endif + mNotifiedIDDestroyed(false), + mAllowScriptsToClose(false), + mTimeoutInsertionPoint(nullptr), + mTimeoutIdCounter(1), + mTimeoutFiringDepth(0), + mSuspendDepth(0), + mFreezeDepth(0), + mBackPressureDelayMS(0), + mFocusMethod(0), + mSerial(0), + mIdleCallbackTimeoutCounter(1), + mIdleRequestCallbackCounter(1), +#ifdef DEBUG + mSetOpenerWindowCalled(false), +#endif +#ifdef MOZ_B2G + mNetworkUploadObserverEnabled(false), + mNetworkDownloadObserverEnabled(false), +#endif + mCleanedUp(false), + mDialogAbuseCount(0), + mAreDialogsEnabled(true), +#ifdef DEBUG + mIsValidatingTabGroup(false), +#endif + mCanSkipCCGeneration(0) +{ + AssertIsOnMainThread(); + + nsLayoutStatics::AddRef(); + + // Initialize the PRCList (this). + PR_INIT_CLIST(this); + + if (aOuterWindow) { + // |this| is an inner window, add this inner window to the outer + // window list of inners. + PR_INSERT_AFTER(this, aOuterWindow); + + mObserver = new nsGlobalWindowObserver(this); + if (mObserver) { + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + // Watch for online/offline status changes so we can fire events. Use + // a strong reference. + os->AddObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, + false); + + // Watch for dom-storage2-changed so we can fire storage + // events. Use a strong reference. + os->AddObserver(mObserver, "dom-storage2-changed", false); + } + + Preferences::AddStrongObserver(mObserver, "intl.accept_languages"); + } + } else { + // |this| is an outer window. Outer windows start out frozen and + // remain frozen until they get an inner window. + MOZ_ASSERT(IsFrozen()); + } + + // We could have failed the first time through trying + // to create the entropy collector, so we should + // try to get one until we succeed. + + gRefCnt++; + + static bool sFirstTime = true; + if (sFirstTime) { + Preferences::AddIntVarCache(&gMinTimeoutValue, + "dom.min_timeout_value", + DEFAULT_MIN_TIMEOUT_VALUE); + Preferences::AddIntVarCache(&gMinBackgroundTimeoutValue, + "dom.min_background_timeout_value", + DEFAULT_MIN_BACKGROUND_TIMEOUT_VALUE); + Preferences::AddBoolVarCache(&sIdleObserversAPIFuzzTimeDisabled, + "dom.idle-observers-api.fuzz_time.disabled", + false); + + Preferences::AddUintVarCache(&gThrottledIdlePeriodLength, + "dom.idle_period.throttled_length", + DEFAULT_THROTTLED_IDLE_PERIOD_LENGTH); + + Preferences::AddUintVarCache(&gThrottledEventQueueBackPressure, + "dom.timeout.throttled_event_queue_back_pressure", + DEFAULT_THROTTLED_EVENT_QUEUE_BACK_PRESSURE); + Preferences::AddUintVarCache(&gBackPressureDelayMS, + "dom.timeout.back_pressure_delay_ms", + DEFAULT_BACK_PRESSURE_DELAY_MS); + Preferences::AddUintVarCache(&gBackPressureDelayReductionThresholdMS, + "dom.timeout.back_pressure_delay_reduction_threshold_ms", + DEFAULT_BACK_PRESSURE_DELAY_REDUCTION_THRESHOLD_MS); + Preferences::AddUintVarCache(&gBackPressureDelayMinimumMS, + "dom.timeout.back_pressure_delay_minimum_ms", + DEFAULT_BACK_PRESSURE_DELAY_MINIMUM_MS); + + Preferences::AddUintVarCache(&gTargetMaxConsecutiveCallbacks, + "dom.timeout.max_consecutive_callbacks", + DEFAULT_TARGET_MAX_CONSECUTIVE_CALLBACKS); + + sFirstTime = false; + } + + if (gDumpFile == nullptr) { + const nsAdoptingCString& fname = + Preferences::GetCString("browser.dom.window.dump.file"); + if (!fname.IsEmpty()) { + // if this fails to open, Dump() knows to just go to stdout + // on null. + gDumpFile = fopen(fname, "wb+"); + } else { + gDumpFile = stdout; + } + } + + mSerial = ++gSerialCounter; + +#ifdef DEBUG + if (!PR_GetEnv("MOZ_QUIET")) { + printf_stderr("++DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p]\n", + gRefCnt, + static_cast<void*>(ToCanonicalSupports(this)), + getpid(), + gSerialCounter, + static_cast<void*>(ToCanonicalSupports(aOuterWindow))); + } +#endif + + if (gDOMLeakPRLog) + MOZ_LOG(gDOMLeakPRLog, LogLevel::Debug, + ("DOMWINDOW %p created outer=%p", this, aOuterWindow)); + + NS_ASSERTION(sWindowsById, "Windows hash table must be created!"); + NS_ASSERTION(!sWindowsById->Get(mWindowID), + "This window shouldn't be in the hash table yet!"); + // We seem to see crashes in release builds because of null |sWindowsById|. + if (sWindowsById) { + sWindowsById->Put(mWindowID, this); + } +} + +#ifdef DEBUG + +/* static */ +void +nsGlobalWindow::AssertIsOnMainThread() +{ + MOZ_ASSERT(NS_IsMainThread()); +} + +#endif // DEBUG + +/* static */ +void +nsGlobalWindow::Init() +{ + AssertIsOnMainThread(); + + NS_ASSERTION(gDOMLeakPRLog, "gDOMLeakPRLog should have been initialized!"); + + sWindowsById = new WindowByIdTable(); +} + +nsGlobalWindow::~nsGlobalWindow() +{ + AssertIsOnMainThread(); + + DisconnectEventTargetObjects(); + + // We have to check if sWindowsById isn't null because ::Shutdown might have + // been called. + if (sWindowsById) { + NS_ASSERTION(sWindowsById->Get(mWindowID), + "This window should be in the hash table"); + sWindowsById->Remove(mWindowID); + } + + --gRefCnt; + +#ifdef DEBUG + if (!PR_GetEnv("MOZ_QUIET")) { + nsAutoCString url; + if (mLastOpenedURI) { + url = mLastOpenedURI->GetSpecOrDefault(); + + // Data URLs can be very long, so truncate to avoid flooding the log. + const uint32_t maxURLLength = 1000; + if (url.Length() > maxURLLength) { + url.Truncate(maxURLLength); + } + } + + nsGlobalWindow* outer = nsGlobalWindow::Cast(mOuterWindow); + printf_stderr("--DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p] [url = %s]\n", + gRefCnt, + static_cast<void*>(ToCanonicalSupports(this)), + getpid(), + mSerial, + static_cast<void*>(ToCanonicalSupports(outer)), + url.get()); + } +#endif + + if (gDOMLeakPRLog) + MOZ_LOG(gDOMLeakPRLog, LogLevel::Debug, + ("DOMWINDOW %p destroyed", this)); + + if (IsOuterWindow()) { + JSObject *proxy = GetWrapperPreserveColor(); + if (proxy) { + js::SetProxyExtra(proxy, 0, js::PrivateValue(nullptr)); + } + + // An outer window is destroyed with inner windows still possibly + // alive, iterate through the inner windows and null out their + // back pointer to this outer, and pull them out of the list of + // inner windows. + + nsGlobalWindow *w; + while ((w = (nsGlobalWindow *)PR_LIST_HEAD(this)) != this) { + PR_REMOVE_AND_INIT_LINK(w); + } + + DropOuterWindowDocs(); + } else { + Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS, + mMutationBits ? 1 : 0); + + if (mListenerManager) { + mListenerManager->Disconnect(); + mListenerManager = nullptr; + } + + // An inner window is destroyed, pull it out of the outer window's + // list if inner windows. + + PR_REMOVE_LINK(this); + + // If our outer window's inner window is this window, null out the + // outer window's reference to this window that's being deleted. + nsGlobalWindow *outer = GetOuterWindowInternal(); + if (outer) { + outer->MaybeClearInnerWindow(this); + } + } + + // We don't have to leave the tab group if we are an inner window. + if (mTabGroup && IsOuterWindow()) { + mTabGroup->Leave(AsOuter()); + } + + // Outer windows are always supposed to call CleanUp before letting themselves + // be destroyed. And while CleanUp generally seems to be intended to clean up + // outers, we've historically called it for both. Changing this would probably + // involve auditing all of the references that inners and outers can have, and + // separating the handling into CleanUp() and FreeInnerObjects. + if (IsInnerWindow()) { + CleanUp(); + } else { + MOZ_ASSERT(mCleanedUp); + } + + nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID); + if (ac) + ac->RemoveWindowAsListener(this); + + nsLayoutStatics::Release(); +} + +void +nsGlobalWindow::AddEventTargetObject(DOMEventTargetHelper* aObject) +{ + MOZ_ASSERT(IsInnerWindow()); + mEventTargetObjects.PutEntry(aObject); +} + +void +nsGlobalWindow::RemoveEventTargetObject(DOMEventTargetHelper* aObject) +{ + MOZ_ASSERT(IsInnerWindow()); + mEventTargetObjects.RemoveEntry(aObject); +} + +void +nsGlobalWindow::DisconnectEventTargetObjects() +{ + for (auto iter = mEventTargetObjects.ConstIter(); !iter.Done(); + iter.Next()) { + RefPtr<DOMEventTargetHelper> target = iter.Get()->GetKey(); + target->DisconnectFromOwner(); + } + mEventTargetObjects.Clear(); +} + +// static +void +nsGlobalWindow::ShutDown() +{ + AssertIsOnMainThread(); + + if (gDumpFile && gDumpFile != stdout) { + fclose(gDumpFile); + } + gDumpFile = nullptr; + + delete sWindowsById; + sWindowsById = nullptr; +} + +// static +void +nsGlobalWindow::CleanupCachedXBLHandlers(nsGlobalWindow* aWindow) +{ + if (aWindow->mCachedXBLPrototypeHandlers && + aWindow->mCachedXBLPrototypeHandlers->Count() > 0) { + aWindow->mCachedXBLPrototypeHandlers->Clear(); + } +} + +void +nsGlobalWindow::MaybeForgiveSpamCount() +{ + if (IsOuterWindow() && + IsPopupSpamWindow()) { + SetIsPopupSpamWindow(false); + } +} + +void +nsGlobalWindow::SetIsPopupSpamWindow(bool aIsPopupSpam) +{ + MOZ_ASSERT(IsOuterWindow()); + + mIsPopupSpam = aIsPopupSpam; + if (aIsPopupSpam) { + ++gOpenPopupSpamCount; + } else { + --gOpenPopupSpamCount; + NS_ASSERTION(gOpenPopupSpamCount >= 0, + "Unbalanced decrement of gOpenPopupSpamCount"); + } +} + +void +nsGlobalWindow::DropOuterWindowDocs() +{ + MOZ_ASSERT(IsOuterWindow()); + MOZ_ASSERT_IF(mDoc, !mDoc->EventHandlingSuppressed()); + mDoc = nullptr; + mSuspendedDoc = nullptr; +} + +void +nsGlobalWindow::CleanUp() +{ + // Guarantee idempotence. + if (mCleanedUp) + return; + mCleanedUp = true; + + StartDying(); + + DisconnectEventTargetObjects(); + + if (mObserver) { + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (os) { + os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC); + os->RemoveObserver(mObserver, "dom-storage2-changed"); + } + +#ifdef MOZ_B2G + DisableNetworkEvent(eNetworkUpload); + DisableNetworkEvent(eNetworkDownload); +#endif // MOZ_B2G + + if (mIdleService) { + mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S); + } + + Preferences::RemoveObserver(mObserver, "intl.accept_languages"); + + // Drop its reference to this dying window, in case for some bogus reason + // the object stays around. + mObserver->Forget(); + } + + if (mNavigator) { + mNavigator->Invalidate(); + mNavigator = nullptr; + } + + mScreen = nullptr; + mMenubar = nullptr; + mToolbar = nullptr; + mLocationbar = nullptr; + mPersonalbar = nullptr; + mStatusbar = nullptr; + mScrollbars = nullptr; + mLocation = nullptr; + mHistory = nullptr; + mCustomElements = nullptr; + mFrames = nullptr; + mWindowUtils = nullptr; + mApplicationCache = nullptr; + mIndexedDB = nullptr; + + mConsole = nullptr; + + mExternal = nullptr; + + mMozSelfSupport = nullptr; + + mPerformance = nullptr; + +#ifdef MOZ_WEBSPEECH + mSpeechSynthesis = nullptr; +#endif + +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) + mOrientationChangeObserver = nullptr; +#endif + + ClearControllers(); + + mOpener = nullptr; // Forces Release + if (mContext) { + mContext = nullptr; // Forces Release + } + mChromeEventHandler = nullptr; // Forces Release + mParentTarget = nullptr; + + if (IsOuterWindow()) { + nsGlobalWindow* inner = GetCurrentInnerWindowInternal(); + if (inner) { + inner->CleanUp(); + } + } + + if (IsInnerWindow()) { + DisableGamepadUpdates(); + mHasGamepad = false; + DisableVRUpdates(); + mHasVREvents = false; +#ifdef MOZ_B2G + DisableTimeChangeNotifications(); +#endif + DisableIdleCallbackRequests(); + } else { + MOZ_ASSERT(!mHasGamepad); + MOZ_ASSERT(!mHasVREvents); + } + + if (mCleanMessageManager) { + MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned"); + nsGlobalChromeWindow *asChrome = static_cast<nsGlobalChromeWindow*>(this); + if (asChrome->mMessageManager) { + static_cast<nsFrameMessageManager*>( + asChrome->mMessageManager.get())->Disconnect(); + } + } + + mArguments = nullptr; + mDialogArguments = nullptr; + + CleanupCachedXBLHandlers(this); + + for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { + mAudioContexts[i]->Shutdown(); + } + mAudioContexts.Clear(); + + if (mIdleTimer) { + mIdleTimer->Cancel(); + mIdleTimer = nullptr; + } + + mServiceWorkerRegistrationTable.Clear(); +} + +void +nsGlobalWindow::ClearControllers() +{ + if (mControllers) { + uint32_t count; + mControllers->GetControllerCount(&count); + + while (count--) { + nsCOMPtr<nsIController> controller; + mControllers->GetControllerAt(count, getter_AddRefs(controller)); + + nsCOMPtr<nsIControllerContext> context = do_QueryInterface(controller); + if (context) + context->SetCommandContext(nullptr); + } + + mControllers = nullptr; + } +} + +void +nsGlobalWindow::FreeInnerObjects() +{ + NS_ASSERTION(IsInnerWindow(), "Don't free inner objects on an outer window"); + + // Make sure that this is called before we null out the document and + // other members that the window destroyed observers could + // re-create. + NotifyDOMWindowDestroyed(this); + if (auto* reporter = nsWindowMemoryReporter::Get()) { + reporter->ObserveDOMWindowDetached(this); + } + + mInnerObjectsFreed = true; + + // Kill all of the workers for this window. + mozilla::dom::workers::CancelWorkersForWindow(AsInner()); + + ClearAllTimeouts(); + + if (mIdleTimer) { + mIdleTimer->Cancel(); + mIdleTimer = nullptr; + } + + mIdleObservers.Clear(); + + DisableIdleCallbackRequests(); + + mChromeEventHandler = nullptr; + + if (mListenerManager) { + mListenerManager->Disconnect(); + mListenerManager = nullptr; + } + + mLocation = nullptr; + mHistory = nullptr; + mCustomElements = nullptr; + + if (mNavigator) { + mNavigator->OnNavigation(); + mNavigator->Invalidate(); + mNavigator = nullptr; + } + + if (mScreen) { + mScreen = nullptr; + } + +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) + mOrientationChangeObserver = nullptr; +#endif + + if (mDoc) { + // Remember the document's principal and URI. + mDocumentPrincipal = mDoc->NodePrincipal(); + mDocumentURI = mDoc->GetDocumentURI(); + mDocBaseURI = mDoc->GetDocBaseURI(); + + while (mDoc->EventHandlingSuppressed()) { + mDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eEvents, false); + } + + // Note: we don't have to worry about eAnimationsOnly suppressions because + // they won't leak. + } + + // Remove our reference to the document and the document principal. + mFocusedNode = nullptr; + + if (mApplicationCache) { + static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->Disconnect(); + mApplicationCache = nullptr; + } + + mIndexedDB = nullptr; + + UnlinkHostObjectURIs(); + + NotifyWindowIDDestroyed("inner-window-destroyed"); + + CleanupCachedXBLHandlers(this); + + for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { + mAudioContexts[i]->Shutdown(); + } + mAudioContexts.Clear(); + +#ifdef MOZ_GAMEPAD + DisableGamepadUpdates(); + mHasGamepad = false; + mGamepads.Clear(); +#endif + DisableVRUpdates(); + mHasVREvents = false; + mVRDisplays.Clear(); +} + +//***************************************************************************** +// nsGlobalWindow::nsISupports +//***************************************************************************** + +// QueryInterface implementation for nsGlobalWindow +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindow) + NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY + // Make sure this matches the cast in nsGlobalWindow::FromWrapper() + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIDOMEventTarget) + NS_INTERFACE_MAP_ENTRY(nsIDOMWindow) + if (aIID.Equals(NS_GET_IID(nsIDOMWindowInternal))) { + foundInterface = static_cast<nsIDOMWindowInternal*>(this); + if (!sWarnedAboutWindowInternal) { + sWarnedAboutWindowInternal = true; + nsContentUtils::ReportToConsole(nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Extensions"), mDoc, + nsContentUtils::eDOM_PROPERTIES, + "nsIDOMWindowInternalWarning"); + } + } else + NS_INTERFACE_MAP_ENTRY(nsIGlobalObject) + NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObject) + NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal) + NS_INTERFACE_MAP_ENTRY(nsIDOMEventTarget) + NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget) + if (aIID.Equals(NS_GET_IID(nsPIDOMWindowInner))) { + foundInterface = AsInner(); + } else + if (aIID.Equals(NS_GET_IID(mozIDOMWindow)) && IsInnerWindow()) { + foundInterface = AsInner(); + } else + if (aIID.Equals(NS_GET_IID(nsPIDOMWindowOuter))) { + foundInterface = AsOuter(); + } else + if (aIID.Equals(NS_GET_IID(mozIDOMWindowProxy)) && IsOuterWindow()) { + foundInterface = AsOuter(); + } else + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor) +NS_INTERFACE_MAP_END + + +NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindow) +NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindow) + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindow) + if (tmp->IsBlackForCC(false)) { + if (nsCCUncollectableMarker::InGeneration(tmp->mCanSkipCCGeneration)) { + return true; + } + tmp->mCanSkipCCGeneration = nsCCUncollectableMarker::sGeneration; + if (tmp->mCachedXBLPrototypeHandlers) { + for (auto iter = tmp->mCachedXBLPrototypeHandlers->Iter(); + !iter.Done(); + iter.Next()) { + iter.Data().exposeToActiveJS(); + } + } + if (EventListenerManager* elm = tmp->GetExistingListenerManager()) { + elm->MarkForCC(); + } + tmp->UnmarkGrayTimers(); + return true; + } +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsGlobalWindow) + return tmp->IsBlackForCC(true); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END + +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGlobalWindow) + return tmp->IsBlackForCC(false); +NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END + +inline void +ImplCycleCollectionTraverse(nsCycleCollectionTraversalCallback& aCallback, + IdleObserverHolder& aField, + const char* aName, + unsigned aFlags) +{ + CycleCollectionNoteChild(aCallback, aField.mIdleObserver.get(), aName, aFlags); +} + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalWindow) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindow) + if (MOZ_UNLIKELY(cb.WantDebugInfo())) { + char name[512]; + nsAutoCString uri; + if (tmp->mDoc && tmp->mDoc->GetDocumentURI()) { + uri = tmp->mDoc->GetDocumentURI()->GetSpecOrDefault(); + } + SprintfLiteral(name, "nsGlobalWindow # %" PRIu64 " %s %s", tmp->mWindowID, + tmp->IsInnerWindow() ? "inner" : "outer", uri.get()); + cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name); + } else { + NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGlobalWindow, tmp->mRefCnt.get()) + } + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mContext) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mControllers) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArguments) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDialogArguments) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mReturnValue) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerRegistrationTable) + +#ifdef MOZ_WEBSPEECH + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechSynthesis) +#endif + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOuterWindow) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager) + + for (Timeout* timeout = tmp->mTimeouts.getFirst(); + timeout; + timeout = timeout->getNext()) { + cb.NoteNativeChild(timeout, NS_CYCLE_COLLECTION_PARTICIPANT(Timeout)); + } + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocation) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mHistory) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCustomElements) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStorage) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplicationCache) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSuspendedDoc) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWakeLock) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingStorageEvents) + + for (IdleRequest* request : tmp->mIdleRequestCallbacks) { + cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest)); + } + + for (IdleRequest* request : tmp->mThrottledIdleRequestCallbacks) { + cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest)); + } + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers) + +#ifdef MOZ_GAMEPAD + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads) +#endif + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRDisplays) + + // Traverse stuff from nsPIDOMWindow + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFrameElement) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedNode) + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMenubar) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mToolbar) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocationbar) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPersonalbar) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStatusbar) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mScrollbars) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCrypto) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mU2F) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsole) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExternal) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMozSelfSupport) + + tmp->TraverseHostObjectURIs(cb); + + NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindow) + nsGlobalWindow::CleanupCachedXBLHandlers(tmp); + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext) + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mControllers) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mArguments) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDialogArguments) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mReturnValue) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator) + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance) + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mServiceWorkerRegistrationTable) + +#ifdef MOZ_WEBSPEECH + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechSynthesis) +#endif + + if (tmp->mOuterWindow) { + nsGlobalWindow::Cast(tmp->mOuterWindow)->MaybeClearInnerWindow(tmp); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOuterWindow) + } + + if (tmp->mListenerManager) { + tmp->mListenerManager->Disconnect(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager) + } + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistory) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomElements) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocalStorage) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStorage) + if (tmp->mApplicationCache) { + static_cast<nsDOMOfflineResourceList*>(tmp->mApplicationCache.get())->Disconnect(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplicationCache) + } + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSuspendedDoc) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleService) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mWakeLock) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingStorageEvents) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleObservers) + +#ifdef MOZ_GAMEPAD + NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads) +#endif + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mVRDisplays) + + // Unlink stuff from nsPIDOMWindow + NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFrameElement) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mFocusedNode) + + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMenubar) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mToolbar) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocationbar) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mPersonalbar) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mStatusbar) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mScrollbars) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mCrypto) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mU2F) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsole) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMozSelfSupport) + + tmp->UnlinkHostObjectURIs(); + + tmp->DisableIdleCallbackRequests(); + + NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +#ifdef DEBUG +void +nsGlobalWindow::RiskyUnlink() +{ + NS_CYCLE_COLLECTION_INNERNAME.Unlink(this); +} +#endif + +NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGlobalWindow) + if (tmp->mCachedXBLPrototypeHandlers) { + for (auto iter = tmp->mCachedXBLPrototypeHandlers->Iter(); + !iter.Done(); + iter.Next()) { + aCallbacks.Trace(&iter.Data(), "Cached XBL prototype handler", aClosure); + } + } + NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER +NS_IMPL_CYCLE_COLLECTION_TRACE_END + +bool +nsGlobalWindow::IsBlackForCC(bool aTracingNeeded) +{ + if (!nsCCUncollectableMarker::sGeneration) { + return false; + } + + return (nsCCUncollectableMarker::InGeneration(GetMarkedCCGeneration()) || + IsBlack()) && + (!aTracingNeeded || + HasNothingToTrace(static_cast<nsIDOMEventTarget*>(this))); +} + +void +nsGlobalWindow::UnmarkGrayTimers() +{ + for (Timeout* timeout = mTimeouts.getFirst(); + timeout; + timeout = timeout->getNext()) { + if (timeout->mScriptHandler) { + timeout->mScriptHandler->MarkForCC(); + } + } +} + +//***************************************************************************** +// nsGlobalWindow::nsIScriptGlobalObject +//***************************************************************************** + +nsresult +nsGlobalWindow::EnsureScriptEnvironment() +{ + nsGlobalWindow* outer = GetOuterWindowInternal(); + if (!outer) { + NS_WARNING("No outer window available!"); + return NS_ERROR_FAILURE; + } + + if (outer->GetWrapperPreserveColor()) { + return NS_OK; + } + + NS_ASSERTION(!outer->GetCurrentInnerWindowInternal(), + "No cached wrapper, but we have an inner window?"); + + // If this window is a [i]frame, don't bother GC'ing when the frame's context + // is destroyed since a GC will happen when the frameset or host document is + // destroyed anyway. + nsCOMPtr<nsIScriptContext> context = new nsJSContext(!IsFrame(), outer); + + NS_ASSERTION(!outer->mContext, "Will overwrite mContext!"); + + // should probably assert the context is clean??? + context->WillInitializeContext(); + + nsresult rv = context->InitContext(); + NS_ENSURE_SUCCESS(rv, rv); + + outer->mContext = context; + return NS_OK; +} + +nsIScriptContext * +nsGlobalWindow::GetScriptContext() +{ + nsGlobalWindow* outer = GetOuterWindowInternal(); + if (!outer) { + return nullptr; + } + return outer->mContext; +} + +JSObject * +nsGlobalWindow::GetGlobalJSObject() +{ + return FastGetGlobalJSObject(); +} + +void +nsGlobalWindow::TraceGlobalJSObject(JSTracer* aTrc) +{ + TraceWrapper(aTrc, "active window global"); +} + +bool +nsGlobalWindow::WouldReuseInnerWindow(nsIDocument* aNewDocument) +{ + MOZ_ASSERT(IsOuterWindow()); + + // We reuse the inner window when: + // a. We are currently at our original document. + // b. At least one of the following conditions are true: + // -- The new document is the same as the old document. This means that we're + // getting called from document.open(). + // -- The new document has the same origin as what we have loaded right now. + + if (!mDoc || !aNewDocument) { + return false; + } + + if (!mDoc->IsInitialDocument()) { + return false; + } + + NS_ASSERTION(NS_IsAboutBlank(mDoc->GetDocumentURI()), + "How'd this happen?"); + + // Great, we're the original document, check for one of the other + // conditions. + + if (mDoc == aNewDocument) { + return true; + } + + bool equal; + if (NS_SUCCEEDED(mDoc->NodePrincipal()->Equals(aNewDocument->NodePrincipal(), + &equal)) && + equal) { + // The origin is the same. + return true; + } + + return false; +} + +void +nsGlobalWindow::SetInitialPrincipalToSubject() +{ + MOZ_ASSERT(IsOuterWindow()); + + // First, grab the subject principal. + nsCOMPtr<nsIPrincipal> newWindowPrincipal = nsContentUtils::SubjectPrincipalOrSystemIfNativeCaller(); + + // We should never create windows with an expanded principal. + // If we have a system principal, make sure we're not using it for a content + // docshell. + // NOTE: Please keep this logic in sync with nsWebShellWindow::Initialize(). + if (nsContentUtils::IsExpandedPrincipal(newWindowPrincipal) || + (nsContentUtils::IsSystemPrincipal(newWindowPrincipal) && + GetDocShell()->ItemType() != nsIDocShellTreeItem::typeChrome)) { + newWindowPrincipal = nullptr; + } + + // If there's an existing document, bail if it either: + if (mDoc) { + // (a) is not an initial about:blank document, or + if (!mDoc->IsInitialDocument()) + return; + // (b) already has the correct principal. + if (mDoc->NodePrincipal() == newWindowPrincipal) + return; + +#ifdef DEBUG + // If we have a document loaded at this point, it had better be about:blank. + // Otherwise, something is really weird. + nsCOMPtr<nsIURI> 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<nsIPresShell> shell = GetDocShell()->GetPresShell(); + + if (shell && !shell->DidInitialize()) { + // Ensure that if someone plays with this document they will get + // layout happening. + nsRect r = shell->GetPresContext()->GetVisibleArea(); + shell->Initialize(r.width, r.height); + } +} + +PopupControlState +PushPopupControlState(PopupControlState aState, bool aForce) +{ + MOZ_ASSERT(NS_IsMainThread()); + + PopupControlState oldState = gPopupControlState; + + if (aState < gPopupControlState || aForce) { + gPopupControlState = aState; + } + + return oldState; +} + +void +PopPopupControlState(PopupControlState aState) +{ + MOZ_ASSERT(NS_IsMainThread()); + + gPopupControlState = aState; +} + +PopupControlState +nsGlobalWindow::PushPopupControlState(PopupControlState aState, + bool aForce) const +{ + return ::PushPopupControlState(aState, aForce); +} + +void +nsGlobalWindow::PopPopupControlState(PopupControlState aState) const +{ + ::PopPopupControlState(aState); +} + +PopupControlState +nsGlobalWindow::GetPopupControlState() const +{ + MOZ_ASSERT(NS_IsMainThread()); + return gPopupControlState; +} + +#define WINDOWSTATEHOLDER_IID \ +{0x0b917c3e, 0xbd50, 0x4683, {0xaf, 0xc9, 0xc7, 0x81, 0x07, 0xae, 0x33, 0x26}} + +class WindowStateHolder final : public nsISupports +{ +public: + NS_DECLARE_STATIC_IID_ACCESSOR(WINDOWSTATEHOLDER_IID) + NS_DECL_ISUPPORTS + + explicit WindowStateHolder(nsGlobalWindow *aWindow); + + nsGlobalWindow* GetInnerWindow() { return mInnerWindow; } + + void DidRestoreWindow() + { + mInnerWindow = nullptr; + mInnerWindowReflector = nullptr; + } + +protected: + ~WindowStateHolder(); + + nsGlobalWindow *mInnerWindow; + // We hold onto this to make sure the inner window doesn't go away. The outer + // window ends up recalculating it anyway. + JS::PersistentRooted<JSObject*> mInnerWindowReflector; +}; + +NS_DEFINE_STATIC_IID_ACCESSOR(WindowStateHolder, WINDOWSTATEHOLDER_IID) + +WindowStateHolder::WindowStateHolder(nsGlobalWindow* aWindow) + : mInnerWindow(aWindow), + mInnerWindowReflector(RootingCx(), aWindow->GetWrapper()) +{ + NS_PRECONDITION(aWindow, "null window"); + NS_PRECONDITION(aWindow->IsInnerWindow(), "Saving an outer window"); + + aWindow->Suspend(); + + // When a global goes into the bfcache, we disable script. + xpc::Scriptability::Get(mInnerWindowReflector).SetDocShellAllowsScript(false); +} + +WindowStateHolder::~WindowStateHolder() +{ + if (mInnerWindow) { + // This window was left in the bfcache and is now going away. We need to + // free it up. + // Note that FreeInnerObjects may already have been called on the + // inner window if its outer has already had SetDocShell(null) + // called. + mInnerWindow->FreeInnerObjects(); + } +} + +NS_IMPL_ISUPPORTS(WindowStateHolder, WindowStateHolder) + +// We need certain special behavior for remote XUL whitelisted domains, but we +// don't want that behavior to take effect in automation, because we whitelist +// all the mochitest domains. So we need to check a pref here. +static bool +TreatAsRemoteXUL(nsIPrincipal* aPrincipal) +{ + MOZ_ASSERT(!nsContentUtils::IsSystemPrincipal(aPrincipal)); + return nsContentUtils::AllowXULXBLForPrincipal(aPrincipal) && + !Preferences::GetBool("dom.use_xbl_scopes_for_remote_xul", false); +} + +static bool +EnablePrivilege(JSContext* cx, unsigned argc, JS::Value* vp) +{ + Telemetry::Accumulate(Telemetry::ENABLE_PRIVILEGE_EVER_CALLED, true); + return xpc::EnableUniversalXPConnect(cx); +} + +static const JSFunctionSpec EnablePrivilegeSpec[] = { + JS_FS("enablePrivilege", EnablePrivilege, 1, 0), + JS_FS_END +}; + +static bool +InitializeLegacyNetscapeObject(JSContext* aCx, JS::Handle<JSObject*> aGlobal) +{ + JSAutoCompartment ac(aCx, aGlobal); + + // Note: MathJax depends on window.netscape being exposed. See bug 791526. + JS::Rooted<JSObject*> obj(aCx); + obj = JS_DefineObject(aCx, aGlobal, "netscape", nullptr); + NS_ENSURE_TRUE(obj, false); + + obj = JS_DefineObject(aCx, obj, "security", nullptr); + NS_ENSURE_TRUE(obj, false); + + // We hide enablePrivilege behind a pref because it has been altered in a + // way that makes it fundamentally insecure to use in production. Mozilla + // uses this pref during automated testing to support legacy test code that + // uses enablePrivilege. If you're not doing test automation, you _must_ not + // flip this pref, or you will be exposing all your users to security + // vulnerabilities. + if (!xpc::IsInAutomation()) { + return true; + } + + /* Define PrivilegeManager object with the necessary "static" methods. */ + obj = JS_DefineObject(aCx, obj, "PrivilegeManager", nullptr); + NS_ENSURE_TRUE(obj, false); + + return JS_DefineFunctions(aCx, obj, EnablePrivilegeSpec); +} + +bool +nsGlobalWindow::ComputeIsSecureContext(nsIDocument* aDocument, SecureContextFlags aFlags) +{ + MOZ_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsIPrincipal> principal = aDocument->NodePrincipal(); + if (nsContentUtils::IsSystemPrincipal(principal)) { + return true; + } + + // Implement https://w3c.github.io/webappsec-secure-contexts/#settings-object + // With some modifications to allow for aFlags. + + bool hadNonSecureContextCreator = false; + + nsPIDOMWindowOuter* parentOuterWin = GetScriptableParent(); + MOZ_ASSERT(parentOuterWin, "How can we get here? No docShell somehow?"); + if (nsGlobalWindow::Cast(parentOuterWin) != this) { + // There may be a small chance that parentOuterWin has navigated in + // the time that it took us to start loading this sub-document. If that + // were the case then parentOuterWin->GetCurrentInnerWindow() wouldn't + // return the window for the document that is embedding us. For this + // reason we only use the GetScriptableParent call above to check that we + // have a same-type parent, but actually get the inner window via the + // document that we know is embedding us. + nsIDocument* creatorDoc = aDocument->GetParentDocument(); + if (!creatorDoc) { + return false; // we must be tearing down + } + nsGlobalWindow* parentWin = + nsGlobalWindow::Cast(creatorDoc->GetInnerWindow()); + if (!parentWin) { + return false; // we must be tearing down + } + MOZ_ASSERT(parentWin == + nsGlobalWindow::Cast(parentOuterWin->GetCurrentInnerWindow()), + "Creator window mismatch while setting Secure Context state"); + if (aFlags != SecureContextFlags::eIgnoreOpener) { + hadNonSecureContextCreator = !parentWin->IsSecureContext(); + } else { + hadNonSecureContextCreator = !parentWin->IsSecureContextIfOpenerIgnored(); + } + } else if (mHadOriginalOpener) { + if (aFlags != SecureContextFlags::eIgnoreOpener) { + hadNonSecureContextCreator = !mOriginalOpenerWasSecureContext; + } + } + + if (hadNonSecureContextCreator) { + return false; + } + + if (nsContentUtils::HttpsStateIsModern(aDocument)) { + return true; + } + + if (principal->GetIsNullPrincipal()) { + nsCOMPtr<nsIURI> uri = aDocument->GetOriginalURI(); + // IsOriginPotentiallyTrustworthy doesn't care about origin attributes so + // it doesn't actually matter what we use here, but reusing the document + // principal's attributes is convenient. + const PrincipalOriginAttributes& attrs = + BasePrincipal::Cast(principal)->OriginAttributesRef(); + // CreateCodebasePrincipal correctly gets a useful principal for blob: and + // other URI_INHERITS_SECURITY_CONTEXT URIs. + principal = BasePrincipal::CreateCodebasePrincipal(uri, attrs); + if (NS_WARN_IF(!principal)) { + return false; + } + } + + nsCOMPtr<nsIContentSecurityManager> csm = + do_GetService(NS_CONTENTSECURITYMANAGER_CONTRACTID); + NS_WARNING_ASSERTION(csm, "csm is null"); + if (csm) { + bool isTrustworthyOrigin = false; + csm->IsOriginPotentiallyTrustworthy(principal, &isTrustworthyOrigin); + if (isTrustworthyOrigin) { + return true; + } + } + + return false; +} + +/** + * Create a new global object that will be used for an inner window. + * Return the native global and an nsISupports 'holder' that can be used + * to manage the lifetime of it. + */ +static nsresult +CreateNativeGlobalForInner(JSContext* aCx, + nsGlobalWindow* aNewInner, + nsIURI* aURI, + nsIPrincipal* aPrincipal, + JS::MutableHandle<JSObject*> aGlobal, + bool aIsSecureContext) +{ + MOZ_ASSERT(aCx); + MOZ_ASSERT(aNewInner); + MOZ_ASSERT(aNewInner->IsInnerWindow()); + MOZ_ASSERT(aPrincipal); + + // DOMWindow with nsEP is not supported, we have to make sure + // no one creates one accidentally. + nsCOMPtr<nsIExpandedPrincipal> nsEP = do_QueryInterface(aPrincipal); + MOZ_RELEASE_ASSERT(!nsEP, "DOMWindow with nsEP is not supported"); + + nsGlobalWindow *top = nullptr; + if (aNewInner->GetOuterWindow()) { + top = aNewInner->GetTopInternal(); + } + + JS::CompartmentOptions options; + + // Sometimes add-ons load their own XUL windows, either as separate top-level + // windows or inside a browser element. In such cases we want to tag the + // window's compartment with the add-on ID. See bug 1092156. + if (nsContentUtils::IsSystemPrincipal(aPrincipal)) { + options.creationOptions().setAddonId(MapURIToAddonID(aURI)); + } + + if (top && top->GetGlobalJSObject()) { + options.creationOptions().setSameZoneAs(top->GetGlobalJSObject()); + } + + options.creationOptions().setSecureContext(aIsSecureContext); + + xpc::InitGlobalObjectOptions(options, aPrincipal); + + // Determine if we need the Components object. + bool needComponents = nsContentUtils::IsSystemPrincipal(aPrincipal) || + TreatAsRemoteXUL(aPrincipal); + uint32_t flags = needComponents ? 0 : nsIXPConnect::OMIT_COMPONENTS_OBJECT; + flags |= nsIXPConnect::DONT_FIRE_ONNEWGLOBALHOOK; + + if (!WindowBinding::Wrap(aCx, aNewInner, aNewInner, options, + nsJSPrincipals::get(aPrincipal), false, aGlobal) || + !xpc::InitGlobalObject(aCx, aGlobal, flags)) { + return NS_ERROR_FAILURE; + } + + MOZ_ASSERT(aNewInner->GetWrapperPreserveColor() == aGlobal); + + // Set the location information for the new global, so that tools like + // about:memory may use that information + xpc::SetLocationForGlobal(aGlobal, aURI); + + if (!InitializeLegacyNetscapeObject(aCx, aGlobal)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +nsresult +nsGlobalWindow::SetNewDocument(nsIDocument* aDocument, + nsISupports* aState, + bool aForceReuseInnerWindow) +{ + NS_PRECONDITION(mDocumentPrincipal == nullptr, + "mDocumentPrincipal prematurely set!"); + MOZ_ASSERT(aDocument); + + if (IsInnerWindow()) { + if (!mOuterWindow) { + return NS_ERROR_NOT_INITIALIZED; + } + + // Refuse to set a new document if the call came from an inner + // window that's not the current inner window. + if (mOuterWindow->GetCurrentInnerWindow() != AsInner()) { + return NS_ERROR_NOT_AVAILABLE; + } + + return GetOuterWindowInternal()->SetNewDocument(aDocument, aState, + aForceReuseInnerWindow); + } + + NS_PRECONDITION(IsOuterWindow(), "Must only be called on outer windows"); + + // Bail out early if we're in process of closing down the window. + NS_ENSURE_STATE(!mCleanedUp); + + NS_ASSERTION(!AsOuter()->GetCurrentInnerWindow() || + AsOuter()->GetCurrentInnerWindow()->GetExtantDoc() == mDoc, + "Uh, mDoc doesn't match the current inner window " + "document!"); + + bool wouldReuseInnerWindow = WouldReuseInnerWindow(aDocument); + if (aForceReuseInnerWindow && + !wouldReuseInnerWindow && + mDoc && + mDoc->NodePrincipal() != aDocument->NodePrincipal()) { + NS_ERROR("Attempted forced inner window reuse while changing principal"); + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsIDocument> oldDoc = mDoc; + + AutoJSAPI jsapi; + jsapi.Init(); + JSContext *cx = jsapi.cx(); + + // Check if we're anywhere near the stack limit before we reach the + // transplanting code, since it has no good way to handle errors. This uses + // the untrusted script limit, which is not strictly necessary since no + // actual script should run. + bool overrecursed = false; + JS_CHECK_RECURSION_CONSERVATIVE_DONT_REPORT(cx, overrecursed = true); + if (overrecursed) { + NS_WARNING("Overrecursion in SetNewDocument"); + return NS_ERROR_FAILURE; + } + + if (!mDoc) { + // First document load. + + // Get our private root. If it is equal to us, then we need to + // attach our global key bindings that handles browser scrolling + // and other browser commands. + nsPIDOMWindowOuter* privateRoot = nsGlobalWindow::GetPrivateRoot(); + + if (privateRoot == AsOuter()) { + nsXBLService::AttachGlobalKeyHandler(mChromeEventHandler); + } + } + + /* No mDocShell means we're already been partially closed down. When that + happens, setting status isn't a big requirement, so don't. (Doesn't happen + under normal circumstances, but bug 49615 describes a case.) */ + + nsContentUtils::AddScriptRunner( + NewRunnableMethod(this, &nsGlobalWindow::ClearStatus)); + + // Sometimes, WouldReuseInnerWindow() returns true even if there's no inner + // window (see bug 776497). Be safe. + bool reUseInnerWindow = (aForceReuseInnerWindow || wouldReuseInnerWindow) && + GetCurrentInnerWindowInternal(); + + nsresult rv = NS_OK; + + // We set mDoc even though this is an outer window to avoid + // having to *always* reach into the inner window to find the + // document. + mDoc = aDocument; + + // Take this opportunity to clear mSuspendedDoc. Our old inner window is now + // responsible for unsuspending it. + mSuspendedDoc = nullptr; + +#ifdef DEBUG + mLastOpenedURI = aDocument->GetDocumentURI(); +#endif + + mContext->WillInitializeContext(); + + nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal(); + + if (currentInner && currentInner->mNavigator) { + currentInner->mNavigator->OnNavigation(); + } + + RefPtr<nsGlobalWindow> newInnerWindow; + bool createdInnerWindow = false; + + bool thisChrome = IsChromeWindow(); + + nsCOMPtr<WindowStateHolder> wsh = do_QueryInterface(aState); + NS_ASSERTION(!aState || wsh, "What kind of weird state are you giving me here?"); + + JS::Rooted<JSObject*> newInnerGlobal(cx); + if (reUseInnerWindow) { + // We're reusing the current inner window. + NS_ASSERTION(!currentInner->IsFrozen(), + "We should never be reusing a shared inner window"); + newInnerWindow = currentInner; + newInnerGlobal = currentInner->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<JSObject*> rootedObject(cx, GetWrapper()); + if (!JS_RefreshCrossCompartmentWrappers(cx, rootedObject)) { + return NS_ERROR_FAILURE; + } + + // Inner windows are only reused for same-origin principals, but the principals + // don't necessarily match exactly. Update the principal on the compartment to + // match the new document. + // NB: We don't just call currentInner->RefreshCompartmentPrincipals() here + // because we haven't yet set its mDoc to aDocument. + JSCompartment *compartment = js::GetObjectCompartment(newInnerGlobal); +#ifdef DEBUG + bool sameOrigin = false; + nsIPrincipal *existing = + nsJSPrincipals::get(JS_GetCompartmentPrincipals(compartment)); + aDocument->NodePrincipal()->Equals(existing, &sameOrigin); + MOZ_ASSERT(sameOrigin); +#endif + MOZ_ASSERT_IF(aDocument == oldDoc, + xpc::GetCompartmentPrincipal(compartment) == + aDocument->NodePrincipal()); + if (aDocument != oldDoc) { + JS_SetCompartmentPrincipals(compartment, + nsJSPrincipals::get(aDocument->NodePrincipal())); + // Make sure we clear out the old content XBL scope, so the new one will + // get created with a principal that subsumes our new principal. + xpc::ClearContentXBLScope(newInnerGlobal); + } + } else { + if (aState) { + newInnerWindow = wsh->GetInnerWindow(); + newInnerGlobal = newInnerWindow->GetWrapperPreserveColor(); + } else { + if (thisChrome) { + newInnerWindow = nsGlobalChromeWindow::Create(this); + } else if (mIsModalContentWindow) { + newInnerWindow = nsGlobalModalWindow::Create(this); + } else { + newInnerWindow = nsGlobalWindow::Create(this); + } + + // The outer window is automatically treated as frozen when we + // null out the inner window. As a result, initializing classes + // on the new inner won't end up reaching into the old inner + // window for classes etc. + // + // [This happens with Object.prototype when XPConnect creates + // a temporary global while initializing classes; the reason + // being that xpconnect creates the temp global w/o a parent + // and proto, which makes the JS engine look up classes in + // cx->globalObject, i.e. this outer window]. + + mInnerWindow = nullptr; + + mCreatingInnerWindow = true; + // Every script context we are initialized with must create a + // new global. + rv = CreateNativeGlobalForInner(cx, newInnerWindow, + aDocument->GetDocumentURI(), + aDocument->NodePrincipal(), + &newInnerGlobal, + ComputeIsSecureContext(aDocument)); + NS_ASSERTION(NS_SUCCEEDED(rv) && newInnerGlobal && + newInnerWindow->GetWrapperPreserveColor() == newInnerGlobal, + "Failed to get script global"); + newInnerWindow->mIsSecureContextIfOpenerIgnored = + ComputeIsSecureContext(aDocument, SecureContextFlags::eIgnoreOpener); + + mCreatingInnerWindow = false; + createdInnerWindow = true; + + NS_ENSURE_SUCCESS(rv, rv); + } + + if (currentInner && currentInner->GetWrapperPreserveColor()) { + if (oldDoc == aDocument) { + // Move the navigator from the old inner window to the new one since + // this is a document.write. This is safe from a same-origin point of + // view because document.write can only be used by the same origin. + newInnerWindow->mNavigator = currentInner->mNavigator; + currentInner->mNavigator = nullptr; + if (newInnerWindow->mNavigator) { + newInnerWindow->mNavigator->SetWindow(newInnerWindow->AsInner()); + } + + // Make a copy of the old window's performance object on document.open. + // Note that we have to force eager creation of it here, because we need + // to grab the current document channel and whatnot before that changes. + currentInner->AsInner()->CreatePerformanceObjectIfNeeded(); + if (currentInner->mPerformance) { + newInnerWindow->mPerformance = + Performance::CreateForMainThread(newInnerWindow->AsInner(), + currentInner->mPerformance->GetDOMTiming(), + currentInner->mPerformance->GetChannel(), + currentInner->mPerformance->GetParentPerformance()); + } + } + + // Don't free objects on our current inner window if it's going to be + // held in the bfcache. + if (!currentInner->IsFrozen()) { + currentInner->FreeInnerObjects(); + } + } + + mInnerWindow = newInnerWindow->AsInner(); + + if (!GetWrapperPreserveColor()) { + JS::Rooted<JSObject*> 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<JSObject*> outerObject(cx, + NewOuterWindowProxy(cx, newInnerGlobal, thisChrome)); + if (!outerObject) { + NS_ERROR("out of memory"); + return NS_ERROR_FAILURE; + } + + JS::Rooted<JSObject*> obj(cx, GetWrapperPreserveColor()); + + js::SetProxyExtra(obj, 0, js::PrivateValue(nullptr)); + js::SetProxyExtra(outerObject, 0, js::PrivateValue(nullptr)); + + outerObject = xpc::TransplantObject(cx, obj, outerObject); + if (!outerObject) { + NS_ERROR("unable to transplant wrappers, probably OOM"); + return NS_ERROR_FAILURE; + } + + js::SetProxyExtra(outerObject, 0, js::PrivateValue(ToSupports(this))); + + SetWrapper(outerObject); + + MOZ_ASSERT(js::GetGlobalForObjectCrossCompartment(outerObject) == newInnerGlobal); + + // Inform the nsJSContext, which is the canonical holder of the outer. + mContext->SetWindowProxy(outerObject); + } + + // Enter the new global's compartment. + JSAutoCompartment ac(cx, GetWrapperPreserveColor()); + + { + JS::Rooted<JSObject*> outer(cx, GetWrapperPreserveColor()); + js::SetWindowProxy(cx, newInnerGlobal, outer); + } + + // Set scriptability based on the state of the docshell. + bool allow = GetDocShell()->GetCanExecuteScripts(); + xpc::Scriptability::Get(GetWrapperPreserveColor()).SetDocShellAllowsScript(allow); + + if (!aState) { + // Get the "window" property once so it will be cached on our inner. We + // have to do this here, not in binding code, because this has to happen + // after we've created the outer window proxy and stashed it in the outer + // nsGlobalWindow, so GetWrapperPreserveColor() on that outer + // nsGlobalWindow doesn't return null and nsGlobalWindow::OuterObject + // works correctly. + JS::Rooted<JS::Value> unused(cx); + if (!JS_GetProperty(cx, newInnerGlobal, "window", &unused)) { + NS_ERROR("can't create the 'window' property"); + return NS_ERROR_FAILURE; + } + + // And same thing for the "self" property. + if (!JS_GetProperty(cx, newInnerGlobal, "self", &unused)) { + NS_ERROR("can't create the 'self' property"); + return NS_ERROR_FAILURE; + } + } + } + + 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<JSObject*> rootedJSObject(cx, GetWrapperPreserveColor()); + JS::Rooted<JSObject*> proto1(cx), proto2(cx); + JS_GetPrototype(cx, rootedJSObject, &proto1); + JS_GetPrototype(cx, newInnerGlobal, &proto2); + NS_ASSERTION(proto1 == proto2, + "outer and inner globals should have the same prototype"); +#endif + + mInnerWindow->SyncStateFromParentWindow(); + } + + // Add an extra ref in case we release mContext during GC. + nsCOMPtr<nsIScriptContext> kungFuDeathGrip(mContext); + + aDocument->SetScriptGlobalObject(newInnerWindow); + MOZ_ASSERT(newInnerWindow->mTabGroup, + "We must have a TabGroup cached at this point"); + + if (!aState) { + if (reUseInnerWindow) { + + if (newInnerWindow->mDoc != aDocument) { + newInnerWindow->mDoc = aDocument; + + // The storage objects contain the URL of the window. We have to + // recreate them when the innerWindow is reused. + newInnerWindow->mLocalStorage = nullptr; + newInnerWindow->mSessionStorage = nullptr; + + newInnerWindow->ClearDocumentDependentSlots(cx); + } + } else { + newInnerWindow->InnerSetNewDocument(cx, aDocument); + + // Initialize DOM classes etc on the inner window. + JS::Rooted<JSObject*> obj(cx, newInnerGlobal); + rv = kungFuDeathGrip->InitClasses(obj); + NS_ENSURE_SUCCESS(rv, rv); + } + + // If the document comes from a JAR, check if the channel was determined + // to be unsafe. If so, permanently disable script on the compartment by + // calling Block() and throwing away the key. + nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(aDocument->GetChannel()); + if (jarChannel && jarChannel->GetIsUnsafe()) { + xpc::Scriptability::Get(newInnerGlobal).Block(); + } + + if (mArguments) { + newInnerWindow->DefineArgumentsProperty(mArguments); + mArguments = nullptr; + } + + // Give the new inner window our chrome event handler (since it + // doesn't have one). + newInnerWindow->mChromeEventHandler = mChromeEventHandler; + } + + nsJSContext::PokeGC(JS::gcreason::SET_NEW_DOCUMENT); + kungFuDeathGrip->DidInitializeContext(); + + // We wait to fire the debugger hook until the window is all set up and hooked + // up with the outer. See bug 969156. + if (createdInnerWindow) { + nsContentUtils::AddScriptRunner( + NewRunnableMethod(newInnerWindow, + &nsGlobalWindow::FireOnNewGlobalObject)); + } + + if (newInnerWindow && !newInnerWindow->mHasNotifiedGlobalCreated && mDoc) { + // We should probably notify. However if this is the, arguably bad, + // situation when we're creating a temporary non-chrome-about-blank + // document in a chrome docshell, don't notify just yet. Instead wait + // until we have a real chrome doc. + if (!mDocShell || + mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome || + nsContentUtils::IsSystemPrincipal(mDoc->NodePrincipal())) { + newInnerWindow->mHasNotifiedGlobalCreated = true; + nsContentUtils::AddScriptRunner( + NewRunnableMethod(this, &nsGlobalWindow::DispatchDOMWindowCreated)); + } + } + + PreloadLocalStorage(); + + return NS_OK; +} + +void +nsGlobalWindow::PreloadLocalStorage() +{ + MOZ_ASSERT(IsOuterWindow()); + + if (!Preferences::GetBool(kStorageEnabled)) { + return; + } + + if (IsChromeWindow()) { + return; + } + + nsIPrincipal* principal = GetPrincipal(); + if (!principal) { + return; + } + + nsresult rv; + + nsCOMPtr<nsIDOMStorageManager> storageManager = + do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv); + if (NS_FAILED(rv)) { + return; + } + + storageManager->PrecacheStorage(principal); +} + +void +nsGlobalWindow::DispatchDOMWindowCreated() +{ + MOZ_ASSERT(IsOuterWindow()); + + if (!mDoc) { + return; + } + + // Fire DOMWindowCreated at chrome event listeners + nsContentUtils::DispatchChromeEvent(mDoc, mDoc, NS_LITERAL_STRING("DOMWindowCreated"), + true /* bubbles */, + false /* not cancellable */); + + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + + // The event dispatching could possibly cause docshell destory, and + // consequently cause mDoc to be set to nullptr by DropOuterWindowDocs(), + // so check it again here. + if (observerService && mDoc) { + nsAutoString origin; + nsIPrincipal* principal = mDoc->NodePrincipal(); + nsContentUtils::GetUTFOrigin(principal, origin); + observerService-> + NotifyObservers(static_cast<nsIDOMWindow*>(this), + nsContentUtils::IsSystemPrincipal(principal) ? + "chrome-document-global-created" : + "content-document-global-created", + origin.get()); + } +} + +void +nsGlobalWindow::ClearStatus() +{ + SetStatusOuter(EmptyString()); +} + +void +nsGlobalWindow::InnerSetNewDocument(JSContext* aCx, nsIDocument* aDocument) +{ + NS_PRECONDITION(IsInnerWindow(), "Must only be called on inner windows"); + MOZ_ASSERT(aDocument); + + if (gDOMLeakPRLog && MOZ_LOG_TEST(gDOMLeakPRLog, LogLevel::Debug)) { + nsIURI *uri = aDocument->GetDocumentURI(); + PR_LogPrint("DOMWINDOW %p SetNewDocument %s", + this, uri ? uri->GetSpecOrDefault().get() : ""); + } + + mDoc = aDocument; + ClearDocumentDependentSlots(aCx); + mFocusedNode = nullptr; + mLocalStorage = nullptr; + mSessionStorage = nullptr; + +#ifdef DEBUG + mLastOpenedURI = aDocument->GetDocumentURI(); +#endif + + Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS, + mMutationBits ? 1 : 0); + + // Clear our mutation bitfield. + mMutationBits = 0; +} + +void +nsGlobalWindow::SetDocShell(nsIDocShell* aDocShell) +{ + NS_ASSERTION(IsOuterWindow(), "Uh, SetDocShell() called on inner window!"); + MOZ_ASSERT(aDocShell); + + if (aDocShell == mDocShell) { + return; + } + + mDocShell = aDocShell; // Weak Reference + + nsCOMPtr<nsPIDOMWindowOuter> parentWindow = GetScriptableParentOrNull(); + MOZ_RELEASE_ASSERT(!parentWindow || !mTabGroup || mTabGroup == Cast(parentWindow)->mTabGroup); + + NS_ASSERTION(!mNavigator, "Non-null mNavigator in outer window!"); + + if (mFrames) { + mFrames->SetDocShell(aDocShell); + } + + // Get our enclosing chrome shell and retrieve its global window impl, so + // that we can do some forwarding to the chrome document. + nsCOMPtr<nsIDOMEventTarget> 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<nsPIDOMWindowOuter> parentWindow = GetParent(); + if (parentWindow.get() != AsOuter()) { + mChromeEventHandler = parentWindow->GetChromeEventHandler(); + } + else { + mChromeEventHandler = NS_NewWindowRoot(AsOuter()); + mIsRootOuterWindow = true; + } + } + + bool docShellActive; + mDocShell->GetIsActive(&docShellActive); + mIsBackground = !docShellActive; +} + +void +nsGlobalWindow::DetachFromDocShell() +{ + NS_ASSERTION(IsOuterWindow(), "Uh, DetachFromDocShell() called on inner window!"); + + // DetachFromDocShell means the window is being torn down. Drop our + // reference to the script context, allowing it to be deleted + // later. Meanwhile, keep our weak reference to the script object + // so that it can be retrieved later (until it is finalized by the JS GC). + + NS_ASSERTION(mTimeouts.isEmpty(), "Uh, outer window holds timeouts!"); + + // Call FreeInnerObjects on all inner windows, not just the current + // one, since some could be held by WindowStateHolder objects that + // are GC-owned. + for (RefPtr<nsGlobalWindow> inner = (nsGlobalWindow *)PR_LIST_HEAD(this); + inner != this; + inner = (nsGlobalWindow*)PR_NEXT_LINK(inner)) { + MOZ_ASSERT(!inner->mOuterWindow || inner->mOuterWindow == AsOuter()); + inner->FreeInnerObjects(); + } + + if (auto* reporter = nsWindowMemoryReporter::Get()) { + reporter->ObserveDOMWindowDetached(this); + } + + NotifyWindowIDDestroyed("outer-window-destroyed"); + + nsGlobalWindow *currentInner = GetCurrentInnerWindowInternal(); + + if (currentInner) { + NS_ASSERTION(mDoc, "Must have doc!"); + + // Remember the document's principal and URI. + mDocumentPrincipal = mDoc->NodePrincipal(); + mDocumentURI = mDoc->GetDocumentURI(); + mDocBaseURI = mDoc->GetDocBaseURI(); + + // Release our document reference + DropOuterWindowDocs(); + mFocusedNode = nullptr; + } + + ClearControllers(); + + mChromeEventHandler = nullptr; // force release now + + if (mContext) { + nsJSContext::PokeGC(JS::gcreason::SET_DOC_SHELL); + mContext = nullptr; + } + + mDocShell = nullptr; // Weak Reference + + NS_ASSERTION(!mNavigator, "Non-null mNavigator in outer window!"); + + if (mFrames) { + mFrames->SetDocShell(nullptr); + } + + MaybeForgiveSpamCount(); + CleanUp(); +} + +void +nsGlobalWindow::SetOpenerWindow(nsPIDOMWindowOuter* aOpener, + bool aOriginalOpener) +{ + FORWARD_TO_OUTER_VOID(SetOpenerWindow, (aOpener, aOriginalOpener)); + + nsWeakPtr opener = do_GetWeakReference(aOpener); + if (opener == mOpener) { + return; + } + + NS_ASSERTION(!aOriginalOpener || !mSetOpenerWindowCalled, + "aOriginalOpener is true, but not first call to " + "SetOpenerWindow!"); + NS_ASSERTION(aOpener || !aOriginalOpener, + "Shouldn't set mHadOriginalOpener if aOpener is null"); + + mOpener = opener.forget(); + NS_ASSERTION(mOpener || !aOpener, "Opener must support weak references!"); + + if (aOriginalOpener) { + MOZ_ASSERT(!mHadOriginalOpener, + "Probably too late to call ComputeIsSecureContext again"); + mHadOriginalOpener = true; + mOriginalOpenerWasSecureContext = + aOpener->GetCurrentInnerWindow()->IsSecureContext(); + } + +#ifdef DEBUG + mSetOpenerWindowCalled = true; +#endif +} + +static +already_AddRefed<EventTarget> +TryGetTabChildGlobalAsEventTarget(nsISupports *aFrom) +{ + nsCOMPtr<nsIFrameLoaderOwner> frameLoaderOwner = do_QueryInterface(aFrom); + if (!frameLoaderOwner) { + return nullptr; + } + + RefPtr<nsFrameLoader> frameLoader = frameLoaderOwner->GetFrameLoader(); + if (!frameLoader) { + return nullptr; + } + + nsCOMPtr<EventTarget> 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<Element> frameElement = GetOuterWindow()->GetFrameElementInternal(); + nsCOMPtr<EventTarget> eventTarget = + TryGetTabChildGlobalAsEventTarget(frameElement); + + if (!eventTarget) { + nsGlobalWindow* topWin = GetScriptableTopInternal(); + if (topWin) { + frameElement = topWin->AsOuter()->GetFrameElementInternal(); + eventTarget = TryGetTabChildGlobalAsEventTarget(frameElement); + } + } + + if (!eventTarget) { + eventTarget = TryGetTabChildGlobalAsEventTarget(mChromeEventHandler); + } + + if (!eventTarget) { + eventTarget = mChromeEventHandler; + } + + mParentTarget = eventTarget; +} + +EventTarget* +nsGlobalWindow::GetTargetForDOMEvent() +{ + return GetOuterWindowInternal(); +} + +EventTarget* +nsGlobalWindow::GetTargetForEventTargetChain() +{ + return IsInnerWindow() ? this : GetCurrentInnerWindowInternal(); +} + +nsresult +nsGlobalWindow::WillHandleEvent(EventChainPostVisitor& aVisitor) +{ + return NS_OK; +} + +nsresult +nsGlobalWindow::PreHandleEvent(EventChainPreVisitor& aVisitor) +{ + NS_PRECONDITION(IsInnerWindow(), "PreHandleEvent is used on outer window!?"); + EventMessage msg = aVisitor.mEvent->mMessage; + + aVisitor.mCanHandle = true; + aVisitor.mForceContentDispatch = true; //FIXME! Bug 329119 + if (msg == eResize && aVisitor.mEvent->IsTrusted()) { + // QIing to window so that we can keep the old behavior also in case + // a child window is handling resize. + nsCOMPtr<nsPIDOMWindowInner> window = + do_QueryInterface(aVisitor.mEvent->mOriginalTarget); + if (window) { + mIsHandlingResizeEvent = true; + } + } else if (msg == eMouseDown && aVisitor.mEvent->IsTrusted()) { + gMouseDown = true; + } else if ((msg == eMouseUp || msg == eDragEnd) && + aVisitor.mEvent->IsTrusted()) { + gMouseDown = false; + if (gDragServiceDisabled) { + nsCOMPtr<nsIDragService> ds = + do_GetService("@mozilla.org/widget/dragservice;1"); + if (ds) { + gDragServiceDisabled = false; + ds->Unsuppress(); + } + } + } + + aVisitor.mParentTarget = GetParentTarget(); + + // Handle 'active' event. + if (!mIdleObservers.IsEmpty() && + aVisitor.mEvent->IsTrusted() && + (aVisitor.mEvent->HasMouseEventMessage() || + aVisitor.mEvent->HasDragEventMessage())) { + mAddActiveEventFuzzTime = false; + } + + return NS_OK; +} + +bool +nsGlobalWindow::ShouldPromptToBlockDialogs() +{ + MOZ_ASSERT(IsOuterWindow()); + + nsGlobalWindow *topWindow = GetScriptableTopInternal(); + if (!topWindow) { + NS_ASSERTION(!mDocShell, "ShouldPromptToBlockDialogs() called without a top window?"); + return true; + } + + topWindow = topWindow->GetCurrentInnerWindowInternal(); + if (!topWindow) { + return true; + } + + return topWindow->DialogsAreBeingAbused(); +} + +bool +nsGlobalWindow::AreDialogsEnabled() +{ + MOZ_ASSERT(IsOuterWindow()); + + nsGlobalWindow *topWindow = GetScriptableTopInternal(); + if (!topWindow) { + NS_ERROR("AreDialogsEnabled() called without a top window?"); + return false; + } + + // TODO: Warn if no top window? + topWindow = topWindow->GetCurrentInnerWindowInternal(); + if (!topWindow) { + return false; + } + + // Dialogs are blocked if the content viewer is hidden + if (mDocShell) { + nsCOMPtr<nsIContentViewer> cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + + bool isHidden; + cv->GetIsHidden(&isHidden); + if (isHidden) { + return false; + } + } + + // Dialogs are also blocked if the document is sandboxed with SANDBOXED_MODALS + // (or if we have no document, of course). Which document? Who knows; the + // spec is daft. See <https://github.com/whatwg/html/issues/1206>. For now + // just go ahead and check mDoc, since in everything except edge cases in + // which a frame is allow-same-origin but not allow-scripts and is being poked + // at by some other window this should be the right thing anyway. + if (!mDoc || (mDoc->GetSandboxFlags() & SANDBOXED_MODALS)) { + return false; + } + + return topWindow->mAreDialogsEnabled; +} + +bool +nsGlobalWindow::DialogsAreBeingAbused() +{ + MOZ_ASSERT(IsInnerWindow()); + NS_ASSERTION(GetScriptableTopInternal() && + GetScriptableTopInternal()->GetCurrentInnerWindowInternal() == this, + "DialogsAreBeingAbused called with invalid window"); + + if (mLastDialogQuitTime.IsNull() || + nsContentUtils::IsCallerChrome()) { + return false; + } + + TimeDuration dialogInterval(TimeStamp::Now() - mLastDialogQuitTime); + if (dialogInterval.ToSeconds() < + Preferences::GetInt("dom.successive_dialog_time_limit", + DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT)) { + mDialogAbuseCount++; + + return GetPopupControlState() > openAllowed || + mDialogAbuseCount > MAX_SUCCESSIVE_DIALOG_COUNT; + } + + // Reset the abuse counter + mDialogAbuseCount = 0; + + return false; +} + +bool +nsGlobalWindow::ConfirmDialogIfNeeded() +{ + MOZ_ASSERT(IsOuterWindow()); + + NS_ENSURE_TRUE(mDocShell, false); + nsCOMPtr<nsIPromptService> promptSvc = + do_GetService("@mozilla.org/embedcomp/prompt-service;1"); + + if (!promptSvc) { + return true; + } + + // Reset popup state while opening a modal dialog, and firing events + // about the dialog, to prevent the current state from being active + // the whole time a modal dialog is open. + nsAutoPopupStatePusher popupStatePusher(openAbused, true); + + bool disableDialog = false; + nsXPIDLString label, title; + nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES, + "ScriptDialogLabel", label); + nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES, + "ScriptDialogPreventTitle", title); + promptSvc->Confirm(AsOuter(), title.get(), label.get(), &disableDialog); + if (disableDialog) { + DisableDialogs(); + return false; + } + + return true; +} + +void +nsGlobalWindow::DisableDialogs() +{ + nsGlobalWindow *topWindow = GetScriptableTopInternal(); + if (!topWindow) { + NS_ERROR("DisableDialogs() called without a top window?"); + return; + } + + topWindow = topWindow->GetCurrentInnerWindowInternal(); + // TODO: Warn if no top window? + if (topWindow) { + topWindow->mAreDialogsEnabled = false; + } +} + +void +nsGlobalWindow::EnableDialogs() +{ + nsGlobalWindow *topWindow = GetScriptableTopInternal(); + if (!topWindow) { + NS_ERROR("EnableDialogs() called without a top window?"); + return; + } + + // TODO: Warn if no top window? + topWindow = topWindow->GetCurrentInnerWindowInternal(); + if (topWindow) { + topWindow->mAreDialogsEnabled = true; + } +} + +nsresult +nsGlobalWindow::PostHandleEvent(EventChainPostVisitor& aVisitor) +{ + NS_PRECONDITION(IsInnerWindow(), "PostHandleEvent is used on outer window!?"); + + // Return early if there is nothing to do. + switch (aVisitor.mEvent->mMessage) { + case eResize: + case eUnload: + case eLoad: + break; + default: + return NS_OK; + } + + /* mChromeEventHandler and mContext go dangling in the middle of this + function under some circumstances (events that destroy the window) + without this addref. */ + nsCOMPtr<nsIDOMEventTarget> kungFuDeathGrip1(mChromeEventHandler); + mozilla::Unused << kungFuDeathGrip1; // These aren't referred to through the function + nsCOMPtr<nsIScriptContext> kungFuDeathGrip2(GetContextInternal()); + mozilla::Unused << kungFuDeathGrip2; // These aren't referred to through the function + + + if (aVisitor.mEvent->mMessage == eResize) { + mIsHandlingResizeEvent = false; + } else if (aVisitor.mEvent->mMessage == eUnload && + aVisitor.mEvent->IsTrusted()) { + // Execute bindingdetached handlers before we tear ourselves + // down. + if (mDoc) { + mDoc->BindingManager()->ExecuteDetachedHandlers(); + } + mIsDocumentLoaded = false; + } else if (aVisitor.mEvent->mMessage == eLoad && + aVisitor.mEvent->IsTrusted()) { + // This is page load event since load events don't propagate to |window|. + // @see nsDocument::PreHandleEvent. + mIsDocumentLoaded = true; + + nsCOMPtr<Element> element = GetOuterWindow()->GetFrameElementInternal(); + nsIDocShell* docShell = GetDocShell(); + if (element && GetParentInternal() && + docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) { + // If we're not in chrome, or at a chrome boundary, fire the + // onload event for the frame element. + + nsEventStatus status = nsEventStatus_eIgnore; + WidgetEvent event(aVisitor.mEvent->IsTrusted(), eLoad); + event.mFlags.mBubbles = false; + event.mFlags.mCancelable = false; + + // Most of the time we could get a pres context to pass in here, + // but not always (i.e. if this window is not shown there won't + // be a pres context available). Since we're not firing a GUI + // event we don't need a pres context anyway so we just pass + // null as the pres context all the time here. + EventDispatcher::Dispatch(element, nullptr, &event, nullptr, &status); + } + } + + return NS_OK; +} + +nsresult +nsGlobalWindow::DispatchDOMEvent(WidgetEvent* aEvent, + nsIDOMEvent* aDOMEvent, + nsPresContext* aPresContext, + nsEventStatus* aEventStatus) +{ + return EventDispatcher::DispatchDOMEvent(static_cast<nsPIDOMWindow*>(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<nsISupports> supports = do_QueryElementAt(aArguments, 0, &rv); + NS_ENSURE_SUCCESS(rv, rv); + mDialogArguments = static_cast<DialogValueHolder*>(supports.get()); + } else { + mArguments = aArguments; + rv = currentInner->DefineArgumentsProperty(aArguments); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +nsGlobalWindow::DefineArgumentsProperty(nsIArray *aArguments) +{ + MOZ_ASSERT(IsInnerWindow()); + MOZ_ASSERT(!mIsModalContentWindow); // Handled separately. + + nsIScriptContext *ctx = GetOuterWindowInternal()->mContext; + NS_ENSURE_TRUE(aArguments && ctx, NS_ERROR_NOT_INITIALIZED); + + JS::Rooted<JSObject*> obj(RootingCx(), GetWrapperPreserveColor()); + return ctx->SetProperty(obj, "arguments", aArguments); +} + +namespace { + +// Convert a ThrottledEventQueue length to a timer delay in milliseconds. +// This will return a value between 0 and INT32_MAX. +int32_t +CalculateNewBackPressureDelayMS(uint32_t aBacklogDepth) +{ + double multiplier = static_cast<double>(aBacklogDepth) / + static_cast<double>(gThrottledEventQueueBackPressure); + double value = static_cast<double>(gBackPressureDelayMS) * multiplier; + // Avoid overflow + if (value > INT32_MAX) { + value = INT32_MAX; + } + + // Once we get close to an empty queue just floor the delay back to zero. + // We want to ensure we don't get stuck in a condition where there is a + // small amount of delay remaining due to an active, but reasonable, queue. + else if (value < static_cast<double>(gBackPressureDelayMinimumMS)) { + value = 0; + } + return static_cast<int32_t>(value); +} + +} // anonymous namespace + +void +nsGlobalWindow::MaybeApplyBackPressure() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // If we are already in back pressure then we don't need to apply back + // pressure again. We also shouldn't need to apply back pressure while + // the window is suspended. + if (mBackPressureDelayMS > 0 || IsSuspended()) { + return; + } + + RefPtr<ThrottledEventQueue> queue = TabGroup()->GetThrottledEventQueue(); + if (!queue) { + return; + } + + // Only begin back pressure if the window has greatly fallen behind the main + // thread. This is a somewhat arbitrary threshold chosen such that it should + // rarely fire under normaly circumstances. Its low enough, though, + // that we should have time to slow new runnables from being added before an + // OOM occurs. + if (queue->Length() < gThrottledEventQueueBackPressure) { + return; + } + + // First attempt to dispatch a runnable to update our back pressure state. We + // do this first in order to verify we can dispatch successfully before + // entering the back pressure state. + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod(this, &nsGlobalWindow::CancelOrUpdateBackPressure); + nsresult rv = queue->Dispatch(r.forget(), NS_DISPATCH_NORMAL); + NS_ENSURE_SUCCESS_VOID(rv); + + // Since the callback was scheduled successfully we can now persist the + // backpressure value. + mBackPressureDelayMS = CalculateNewBackPressureDelayMS(queue->Length()); +} + +void +nsGlobalWindow::CancelOrUpdateBackPressure() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(mBackPressureDelayMS > 0); + + // First, re-calculate the back pressure delay. + RefPtr<ThrottledEventQueue> queue = TabGroup()->GetThrottledEventQueue(); + int32_t newBackPressureDelayMS = + CalculateNewBackPressureDelayMS(queue ? queue->Length() : 0); + + // If the delay has increased, then simply apply it. Increasing the delay + // does not risk re-ordering timers. + if (newBackPressureDelayMS > mBackPressureDelayMS) { + mBackPressureDelayMS = newBackPressureDelayMS; + } + + // If the delay has decreased, though, we only apply the new value if it has + // reduced significantly. This hysteresis avoids thrashing the back pressure + // value back and forth rapidly. This is important because reducing the + // backpressure delay requires calling ResetTimerForThrottleReduction() which + // can be quite expensive. We only want to call that method if the back log + // is really clearing. + else if (newBackPressureDelayMS == 0 || + (static_cast<uint32_t>(mBackPressureDelayMS) > + (newBackPressureDelayMS + gBackPressureDelayReductionThresholdMS))) { + int32_t oldBackPressureDelayMS = mBackPressureDelayMS; + mBackPressureDelayMS = newBackPressureDelayMS; + + // If the back pressure delay has gone down we must reset any existing + // timers to use the new value. Otherwise we run the risk of executing + // timer callbacks out-of-order. + ResetTimersForThrottleReduction(oldBackPressureDelayMS); + } + + // If all of the back pressure delay has been removed then we no longer need + // to check back pressure updates. We can simply return without scheduling + // another update runnable. + if (!mBackPressureDelayMS) { + return; + } + + // Otherwise, if there is a back pressure delay still in effect we need + // queue a runnable to check if it can be reduced in the future. Note + // that this runnable is dispatched to the ThrottledEventQueue. This + // means we will not check for a new value until the current back log + // has been processed. The next update will only keep back pressure if + // more runnables continue to be dispatched to the queue. + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod(this, &nsGlobalWindow::CancelOrUpdateBackPressure); + MOZ_ALWAYS_SUCCEEDS(queue->Dispatch(r.forget(), NS_DISPATCH_NORMAL)); +} + +//***************************************************************************** +// nsGlobalWindow::nsIScriptObjectPrincipal +//***************************************************************************** + +nsIPrincipal* +nsGlobalWindow::GetPrincipal() +{ + if (mDoc) { + // If we have a document, get the principal from the document + return mDoc->NodePrincipal(); + } + + if (mDocumentPrincipal) { + return mDocumentPrincipal; + } + + // If we don't have a principal and we don't have a document we + // ask the parent window for the principal. This can happen when + // loading a frameset that has a <frame src="javascript:xxx">, in + // that case the global window is used in JS before we've loaded + // a document into the window. + + nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal = + do_QueryInterface(GetParentInternal()); + + if (objPrincipal) { + return objPrincipal->GetPrincipal(); + } + + return nullptr; +} + +//***************************************************************************** +// nsGlobalWindow::nsIDOMWindow +//***************************************************************************** + +template <class T> +nsIURI* +nsPIDOMWindow<T>::GetDocumentURI() const +{ + return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get(); +} + +template <class T> +nsIURI* +nsPIDOMWindow<T>::GetDocBaseURI() const +{ + return mDoc ? mDoc->GetDocBaseURI() : mDocBaseURI.get(); +} + +template <class T> +void +nsPIDOMWindow<T>::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<nsIDocument> document = docShell->GetDocument(); + Unused << document; + } +} + +void +nsPIDOMWindowOuter::SetInitialKeyboardIndicators( + UIStateChangeType aShowAccelerators, UIStateChangeType aShowFocusRings) +{ + MOZ_ASSERT(IsOuterWindow()); + MOZ_ASSERT(!GetCurrentInnerWindow()); + + nsPIDOMWindowOuter* piWin = GetPrivateRoot(); + if (!piWin) { + return; + } + + MOZ_ASSERT(piWin == AsOuter()); + + // only change the flags that have been modified + nsCOMPtr<nsPIWindowRoot> windowRoot = do_QueryInterface(mChromeEventHandler); + if (!windowRoot) { + return; + } + + if (aShowAccelerators != UIStateChangeType_NoChange) { + windowRoot->SetShowAccelerators(aShowAccelerators == UIStateChangeType_Set); + } + if (aShowFocusRings != UIStateChangeType_NoChange) { + windowRoot->SetShowFocusRings(aShowFocusRings == UIStateChangeType_Set); + } + + nsContentUtils::SetKeyboardIndicatorsOnRemoteChildren(GetOuterWindow(), + aShowAccelerators, + aShowFocusRings); +} + +Element* +nsPIDOMWindowOuter::GetFrameElementInternal() const +{ + MOZ_ASSERT(IsOuterWindow()); + return mFrameElement; +} + +void +nsPIDOMWindowOuter::SetFrameElementInternal(Element* aFrameElement) +{ + MOZ_ASSERT(IsOuterWindow()); + mFrameElement = aFrameElement; +} + +bool +nsPIDOMWindowInner::AddAudioContext(AudioContext* aAudioContext) +{ + MOZ_ASSERT(IsInnerWindow()); + + mAudioContexts.AppendElement(aAudioContext); + + // Return true if the context should be muted and false if not. + nsIDocShell* docShell = GetDocShell(); + return docShell && !docShell->GetAllowMedia() && !aAudioContext->IsOffline(); +} + +void +nsPIDOMWindowInner::RemoveAudioContext(AudioContext* aAudioContext) +{ + MOZ_ASSERT(IsInnerWindow()); + + mAudioContexts.RemoveElement(aAudioContext); +} + +void +nsPIDOMWindowInner::MuteAudioContexts() +{ + MOZ_ASSERT(IsInnerWindow()); + + for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { + if (!mAudioContexts[i]->IsOffline()) { + mAudioContexts[i]->Mute(); + } + } +} + +void +nsPIDOMWindowInner::UnmuteAudioContexts() +{ + MOZ_ASSERT(IsInnerWindow()); + + for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { + if (!mAudioContexts[i]->IsOffline()) { + mAudioContexts[i]->Unmute(); + } + } +} + +nsGlobalWindow* +nsGlobalWindow::Window() +{ + return this; +} + +nsGlobalWindow* +nsGlobalWindow::Self() +{ + return this; +} + +Navigator* +nsGlobalWindow::GetNavigator(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mNavigator) { + mNavigator = new Navigator(AsInner()); + } + + return mNavigator; +} + +nsIDOMNavigator* +nsGlobalWindow::GetNavigator() +{ + FORWARD_TO_INNER(GetNavigator, (), nullptr); + + ErrorResult dummy; + nsIDOMNavigator* navigator = GetNavigator(dummy); + dummy.SuppressException(); + return navigator; +} + +nsScreen* +nsGlobalWindow::GetScreen(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mScreen) { + mScreen = nsScreen::Create(AsInner()); + if (!mScreen) { + aError.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + } + + return mScreen; +} + +nsIDOMScreen* +nsGlobalWindow::GetScreen() +{ + FORWARD_TO_INNER(GetScreen, (), nullptr); + + ErrorResult dummy; + nsIDOMScreen* screen = GetScreen(dummy); + dummy.SuppressException(); + return screen; +} + +nsHistory* +nsGlobalWindow::GetHistory(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mHistory) { + mHistory = new nsHistory(AsInner()); + } + + return mHistory; +} + +CustomElementRegistry* +nsGlobalWindow::CustomElements() +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + if (!mCustomElements) { + mCustomElements = CustomElementRegistry::Create(AsInner()); + } + + return mCustomElements; +} + +Performance* +nsPIDOMWindowInner::GetPerformance() +{ + MOZ_ASSERT(IsInnerWindow()); + CreatePerformanceObjectIfNeeded(); + return mPerformance; +} + +Performance* +nsGlobalWindow::GetPerformance() +{ + return AsInner()->GetPerformance(); +} + +void +nsPIDOMWindowInner::CreatePerformanceObjectIfNeeded() +{ + MOZ_ASSERT(IsInnerWindow()); + + if (mPerformance || !mDoc) { + return; + } + RefPtr<nsDOMNavigationTiming> timing = mDoc->GetNavigationTiming(); + nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(mDoc->GetChannel())); + bool timingEnabled = false; + if (!timedChannel || + !NS_SUCCEEDED(timedChannel->GetTimingEnabled(&timingEnabled)) || + !timingEnabled) { + timedChannel = nullptr; + } + if (timing) { + // If we are dealing with an iframe, we will need the parent's performance + // object (so we can add the iframe as a resource of that page). + Performance* parentPerformance = nullptr; + nsCOMPtr<nsPIDOMWindowOuter> parentWindow = GetScriptableParentOrNull(); + if (parentWindow) { + nsPIDOMWindowInner* parentInnerWindow = nullptr; + if (parentWindow) { + parentInnerWindow = parentWindow->GetCurrentInnerWindow(); + } + if (parentInnerWindow) { + parentPerformance = parentInnerWindow->GetPerformance(); + } + } + mPerformance = + Performance::CreateForMainThread(this, timing, timedChannel, + parentPerformance); + } +} + +bool +nsPIDOMWindowInner::IsSecureContext() const +{ + return nsGlobalWindow::Cast(this)->IsSecureContext(); +} + +bool +nsPIDOMWindowInner::IsSecureContextIfOpenerIgnored() const +{ + return nsGlobalWindow::Cast(this)->IsSecureContextIfOpenerIgnored(); +} + +void +nsPIDOMWindowInner::Suspend() +{ + nsGlobalWindow::Cast(this)->Suspend(); +} + +void +nsPIDOMWindowInner::Resume() +{ + nsGlobalWindow::Cast(this)->Resume(); +} + +void +nsPIDOMWindowInner::Freeze() +{ + nsGlobalWindow::Cast(this)->Freeze(); +} + +void +nsPIDOMWindowInner::Thaw() +{ + nsGlobalWindow::Cast(this)->Thaw(); +} + +void +nsPIDOMWindowInner::SyncStateFromParentWindow() +{ + nsGlobalWindow::Cast(this)->SyncStateFromParentWindow(); +} + +SuspendTypes +nsPIDOMWindowOuter::GetMediaSuspend() const +{ + if (IsInnerWindow()) { + return mOuterWindow->GetMediaSuspend(); + } + + return mMediaSuspend; +} + +void +nsPIDOMWindowOuter::SetMediaSuspend(SuspendTypes aSuspend) +{ + if (IsInnerWindow()) { + mOuterWindow->SetMediaSuspend(aSuspend); + return; + } + + if (!IsDisposableSuspend(aSuspend)) { + mMediaSuspend = aSuspend; + } + + RefreshMediaElementsSuspend(aSuspend); +} + +bool +nsPIDOMWindowOuter::GetAudioMuted() const +{ + if (IsInnerWindow()) { + return mOuterWindow->GetAudioMuted(); + } + + return mAudioMuted; +} + +void +nsPIDOMWindowOuter::SetAudioMuted(bool aMuted) +{ + if (IsInnerWindow()) { + mOuterWindow->SetAudioMuted(aMuted); + return; + } + + if (mAudioMuted == aMuted) { + return; + } + + mAudioMuted = aMuted; + RefreshMediaElementsVolume(); +} + +float +nsPIDOMWindowOuter::GetAudioVolume() const +{ + if (IsInnerWindow()) { + return mOuterWindow->GetAudioVolume(); + } + + return mAudioVolume; +} + +nsresult +nsPIDOMWindowOuter::SetAudioVolume(float aVolume) +{ + if (IsInnerWindow()) { + return mOuterWindow->SetAudioVolume(aVolume); + } + + if (aVolume < 0.0) { + return NS_ERROR_DOM_INDEX_SIZE_ERR; + } + + if (mAudioVolume == aVolume) { + return NS_OK; + } + + mAudioVolume = aVolume; + RefreshMediaElementsVolume(); + return NS_OK; +} + +void +nsPIDOMWindowOuter::RefreshMediaElementsVolume() +{ + RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate(); + if (service) { + service->RefreshAgentsVolume(GetOuterWindow()); + } +} + +void +nsPIDOMWindowOuter::RefreshMediaElementsSuspend(SuspendTypes aSuspend) +{ + RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate(); + if (service) { + service->RefreshAgentsSuspend(GetOuterWindow(), aSuspend); + } +} + +bool +nsPIDOMWindowOuter::IsDisposableSuspend(SuspendTypes aSuspend) const +{ + return (aSuspend == nsISuspendedTypes::SUSPENDED_PAUSE_DISPOSABLE || + aSuspend == nsISuspendedTypes::SUSPENDED_STOP_DISPOSABLE); +} + +void +nsPIDOMWindowOuter::SetServiceWorkersTestingEnabled(bool aEnabled) +{ + // Devtools should only be setting this on the top level window. Its + // ok if devtools clears the flag on clean up of nested windows, though. + // It will have no affect. +#ifdef DEBUG + nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetScriptableTop(); + MOZ_ASSERT_IF(aEnabled, this == topWindow); +#endif + mServiceWorkersTestingEnabled = aEnabled; +} + +bool +nsPIDOMWindowOuter::GetServiceWorkersTestingEnabled() +{ + // Automatically get this setting from the top level window so that nested + // iframes get the correct devtools setting. + nsCOMPtr<nsPIDOMWindowOuter> topWindow = GetScriptableTop(); + if (!topWindow) { + return false; + } + return topWindow->mServiceWorkersTestingEnabled; +} + +bool +nsPIDOMWindowInner::GetAudioCaptured() const +{ + MOZ_ASSERT(IsInnerWindow()); + return mAudioCaptured; +} + +nsresult +nsPIDOMWindowInner::SetAudioCapture(bool aCapture) +{ + MOZ_ASSERT(IsInnerWindow()); + + mAudioCaptured = aCapture; + + RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate(); + if (service) { + service->SetWindowAudioCaptured(GetOuterWindow(), mWindowID, aCapture); + } + + return NS_OK; +} + +// nsISpeechSynthesisGetter + +#ifdef MOZ_WEBSPEECH +SpeechSynthesis* +nsGlobalWindow::GetSpeechSynthesis(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mSpeechSynthesis) { + mSpeechSynthesis = new SpeechSynthesis(AsInner()); + } + + return mSpeechSynthesis; +} + +bool +nsGlobalWindow::HasActiveSpeechSynthesis() +{ + MOZ_ASSERT(IsInnerWindow()); + + if (mSpeechSynthesis) { + return !mSpeechSynthesis->HasEmptyQueue(); + } + + return false; +} + +#endif + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::GetParentOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDocShell) { + return nullptr; + } + + nsCOMPtr<nsPIDOMWindowOuter> parent; + if (mDocShell->GetIsMozBrowserOrApp()) { + parent = AsOuter(); + } else { + parent = GetParent(); + } + + return parent.forget(); +} + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::GetParent(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetParentOuter, (), aError, nullptr); +} + +/** + * GetScriptableParent is called when script reads window.parent. + * + * In contrast to GetRealParent, GetScriptableParent respects <iframe + * mozbrowser> boundaries, so if |this| is contained by an <iframe + * mozbrowser>, we will return |this| as its own parent. + */ +nsPIDOMWindowOuter* +nsGlobalWindow::GetScriptableParent() +{ + FORWARD_TO_OUTER(GetScriptableParent, (), nullptr); + + nsCOMPtr<nsPIDOMWindowOuter> parent = GetParentOuter(); + return parent.get(); +} + +/** + * Behavies identically to GetScriptableParent extept that it returns null + * if GetScriptableParent would return this window. + */ +nsPIDOMWindowOuter* +nsGlobalWindow::GetScriptableParentOrNull() +{ + FORWARD_TO_OUTER(GetScriptableParentOrNull, (), nullptr); + + nsPIDOMWindowOuter* parent = GetScriptableParent(); + return (Cast(parent) == this) ? nullptr : parent; +} + +/** + * nsPIDOMWindow::GetParent (when called from C++) is just a wrapper around + * GetRealParent. + */ +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::GetParent() +{ + MOZ_ASSERT(IsOuterWindow()); + + if (!mDocShell) { + return nullptr; + } + + nsCOMPtr<nsIDocShell> parent; + mDocShell->GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)); + + if (parent) { + nsCOMPtr<nsPIDOMWindowOuter> win = parent->GetWindow(); + return win.forget(); + } + + nsCOMPtr<nsPIDOMWindowOuter> win(AsOuter()); + return win.forget(); +} + +static nsresult +GetTopImpl(nsGlobalWindow* aWin, nsPIDOMWindowOuter** aTop, bool aScriptable) +{ + *aTop = nullptr; + + // Walk up the parent chain. + + nsCOMPtr<nsPIDOMWindowOuter> prevParent = aWin->AsOuter(); + nsCOMPtr<nsPIDOMWindowOuter> parent = aWin->AsOuter(); + do { + if (!parent) { + break; + } + + prevParent = parent; + + nsCOMPtr<nsPIDOMWindowOuter> newParent; + if (aScriptable) { + newParent = parent->GetScriptableParent(); + } + else { + newParent = parent->GetParent(); + } + + parent = newParent; + + } while (parent != prevParent); + + if (parent) { + parent.swap(*aTop); + } + + return NS_OK; +} + +/** + * GetScriptableTop is called when script reads window.top. + * + * In contrast to GetRealTop, GetScriptableTop respects <iframe mozbrowser> + * boundaries. If we encounter a window owned by an <iframe mozbrowser> while + * walking up the window hierarchy, we'll stop and return that window. + */ +nsPIDOMWindowOuter* +nsGlobalWindow::GetScriptableTop() +{ + FORWARD_TO_OUTER(GetScriptableTop, (), nullptr); + nsCOMPtr<nsPIDOMWindowOuter> window; + GetTopImpl(this, getter_AddRefs(window), /* aScriptable = */ true); + return window.get(); +} + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::GetTop() +{ + MOZ_ASSERT(IsOuterWindow()); + nsCOMPtr<nsPIDOMWindowOuter> window; + GetTopImpl(this, getter_AddRefs(window), /* aScriptable = */ false); + return window.forget(); +} + +void +nsGlobalWindow::GetContentOuter(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsPIDOMWindowOuter> content = + GetContentInternal(aError, !nsContentUtils::IsCallerChrome()); + if (aError.Failed()) { + return; + } + + if (content) { + JS::Rooted<JS::Value> val(aCx); + aError = nsContentUtils::WrapNative(aCx, content, &val); + if (aError.Failed()) { + return; + } + + aRetval.set(&val.toObject()); + return; + } + + aRetval.set(nullptr); + return; +} + +void +nsGlobalWindow::GetContent(JSContext* aCx, + JS::MutableHandle<JSObject*> aRetval, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetContentOuter, (aCx, aRetval, aError), aError, ); +} + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::GetContentInternal(ErrorResult& aError, bool aUnprivilegedCaller) +{ + MOZ_ASSERT(IsOuterWindow()); + + // First check for a named frame named "content" + nsCOMPtr<nsPIDOMWindowOuter> domWindow = + GetChildWindow(NS_LITERAL_STRING("content")); + if (domWindow) { + return domWindow.forget(); + } + + // If we're contained in <iframe mozbrowser> or <iframe mozapp>, then + // GetContent is the same as window.top. + if (mDocShell && mDocShell->GetIsInMozBrowserOrApp()) { + return GetTopOuter(); + } + + nsCOMPtr<nsIDocShellTreeItem> primaryContent; + if (aUnprivilegedCaller) { + // If we're called by non-chrome code, make sure we don't return + // the primary content window if the calling tab is hidden. In + // such a case we return the same-type root in the hidden tab, + // which is "good enough", for now. + nsCOMPtr<nsIBaseWindow> baseWin(do_QueryInterface(mDocShell)); + + if (baseWin) { + bool visible = false; + baseWin->GetVisibility(&visible); + + if (!visible) { + mDocShell->GetSameTypeRootTreeItem(getter_AddRefs(primaryContent)); + } + } + } + + if (!primaryContent) { + nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner(); + if (!treeOwner) { + aError.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + treeOwner->GetPrimaryContentShell(getter_AddRefs(primaryContent)); + } + + if (!primaryContent) { + return nullptr; + } + + domWindow = primaryContent->GetWindow(); + return domWindow.forget(); +} + +MozSelfSupport* +nsGlobalWindow::GetMozSelfSupport(ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + + if (mMozSelfSupport) { + return mMozSelfSupport; + } + + // We're called from JS and want to use out existing JSContext (and, + // importantly, its compartment!) here. + AutoJSContext cx; + GlobalObject global(cx, FastGetGlobalJSObject()); + mMozSelfSupport = MozSelfSupport::Constructor(global, cx, aError); + return mMozSelfSupport; +} + +nsresult +nsGlobalWindow::GetScriptableContent(JSContext* aCx, JS::MutableHandle<JS::Value> aVal) +{ + ErrorResult rv; + JS::Rooted<JSObject*> content(aCx); + GetContent(aCx, &content, rv); + if (!rv.Failed()) { + aVal.setObjectOrNull(content); + } + + return rv.StealNSResult(); +} + +nsresult +nsGlobalWindow::GetPrompter(nsIPrompt** aPrompt) +{ + if (IsInnerWindow()) { + nsGlobalWindow* outer = GetOuterWindowInternal(); + if (!outer) { + NS_WARNING("No outer window available!"); + return NS_ERROR_NOT_INITIALIZED; + } + return outer->GetPrompter(aPrompt); + } + + if (!mDocShell) + return NS_ERROR_FAILURE; + + nsCOMPtr<nsIPrompt> prompter(do_GetInterface(mDocShell)); + NS_ENSURE_TRUE(prompter, NS_ERROR_NO_INTERFACE); + + prompter.forget(aPrompt); + return NS_OK; +} + +BarProp* +nsGlobalWindow::GetMenubar(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mMenubar) { + mMenubar = new MenubarProp(this); + } + + return mMenubar; +} + +BarProp* +nsGlobalWindow::GetToolbar(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mToolbar) { + mToolbar = new ToolbarProp(this); + } + + return mToolbar; +} + +BarProp* +nsGlobalWindow::GetLocationbar(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mLocationbar) { + mLocationbar = new LocationbarProp(this); + } + return mLocationbar; +} + +BarProp* +nsGlobalWindow::GetPersonalbar(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mPersonalbar) { + mPersonalbar = new PersonalbarProp(this); + } + return mPersonalbar; +} + +BarProp* +nsGlobalWindow::GetStatusbar(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mStatusbar) { + mStatusbar = new StatusbarProp(this); + } + return mStatusbar; +} + +BarProp* +nsGlobalWindow::GetScrollbars(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mScrollbars) { + mScrollbars = new ScrollbarsProp(this); + } + + return mScrollbars; +} + +bool +nsGlobalWindow::GetClosedOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + // If someone called close(), or if we don't have a docshell, we're closed. + return mIsClosed || !mDocShell; +} + +bool +nsGlobalWindow::GetClosed(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetClosedOuter, (), aError, false); +} + +bool +nsGlobalWindow::Closed() +{ + MOZ_ASSERT(IsOuterWindow()); + + return GetClosedOuter(); +} + +nsDOMWindowList* +nsGlobalWindow::GetWindowList() +{ + MOZ_ASSERT(IsOuterWindow()); + + if (!mFrames && mDocShell) { + mFrames = new nsDOMWindowList(mDocShell); + } + + return mFrames; +} + +already_AddRefed<nsIDOMWindowCollection> +nsGlobalWindow::GetFrames() +{ + FORWARD_TO_OUTER(GetFrames, (), nullptr); + + nsCOMPtr<nsIDOMWindowCollection> frames = GetWindowList(); + return frames.forget(); +} + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::IndexedGetterOuter(uint32_t aIndex) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + nsDOMWindowList* windows = GetWindowList(); + NS_ENSURE_TRUE(windows, nullptr); + + return windows->IndexedGetter(aIndex); +} + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::IndexedGetter(uint32_t aIndex) +{ + FORWARD_TO_OUTER(IndexedGetterOuter, (aIndex), nullptr); + MOZ_CRASH(); +} + +bool +nsGlobalWindow::DoResolve(JSContext* aCx, JS::Handle<JSObject*> aObj, + JS::Handle<jsid> aId, + JS::MutableHandle<JS::PropertyDescriptor> aDesc) +{ + MOZ_ASSERT(IsInnerWindow()); + + // Note: Keep this in sync with MayResolve. + + // Note: The infallibleInit call in GlobalResolve depends on this check. + if (!JSID_IS_STRING(aId)) { + return true; + } + + bool found; + if (!WebIDLGlobalNameHash::DefineIfEnabled(aCx, aObj, aId, aDesc, &found)) { + return false; + } + + if (found) { + return true; + } + + nsresult rv = nsWindowSH::GlobalResolve(this, aCx, aObj, aId, aDesc); + if (NS_FAILED(rv)) { + return Throw(aCx, rv); + } + + return true; +} + +/* static */ +bool +nsGlobalWindow::MayResolve(jsid aId) +{ + // Note: This function does not fail and may not have any side-effects. + // Note: Keep this in sync with DoResolve. + if (!JSID_IS_STRING(aId)) { + return false; + } + + if (aId == XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)) { + return true; + } + + if (aId == XPCJSContext::Get()->GetStringID(XPCJSContext::IDX_CONTROLLERS)) { + // We only resolve .controllers in release builds and on non-chrome windows, + // but let's not worry about any of that stuff. + return true; + } + + if (WebIDLGlobalNameHash::MayResolve(aId)) { + return true; + } + + nsScriptNameSpaceManager *nameSpaceManager = PeekNameSpaceManager(); + if (!nameSpaceManager) { + // Really shouldn't happen. Fail safe. + return true; + } + + nsAutoString name; + AssignJSFlatString(name, JSID_TO_FLAT_STRING(aId)); + + return nameSpaceManager->LookupName(name); +} + +void +nsGlobalWindow::GetOwnPropertyNames(JSContext* aCx, nsTArray<nsString>& aNames, + ErrorResult& aRv) +{ + MOZ_ASSERT(IsInnerWindow()); + // "Components" is marked as enumerable but only resolved on demand :-/. + //aNames.AppendElement(NS_LITERAL_STRING("Components")); + + nsScriptNameSpaceManager* nameSpaceManager = GetNameSpaceManager(); + if (nameSpaceManager) { + JS::Rooted<JSObject*> wrapper(aCx, GetWrapper()); + + WebIDLGlobalNameHash::GetNames(aCx, wrapper, aNames); + + for (auto i = nameSpaceManager->GlobalNameIter(); !i.Done(); i.Next()) { + const GlobalNameMapEntry* entry = i.Get(); + if (nsWindowSH::NameStructEnabled(aCx, this, entry->mKey, + entry->mGlobalName)) { + aNames.AppendElement(entry->mKey); + } + } + } +} + +/* static */ bool +nsGlobalWindow::IsPrivilegedChromeWindow(JSContext* aCx, JSObject* aObj) +{ + // For now, have to deal with XPConnect objects here. + return xpc::WindowOrNull(aObj)->IsChromeWindow() && + nsContentUtils::ObjectPrincipal(aObj) == nsContentUtils::GetSystemPrincipal(); +} + +/* static */ bool +nsGlobalWindow::IsShowModalDialogEnabled(JSContext*, JSObject*) +{ + static bool sAddedPrefCache = false; + static bool sIsDisabled; + static const char sShowModalDialogPref[] = "dom.disable_window_showModalDialog"; + + if (!sAddedPrefCache) { + Preferences::AddBoolVarCache(&sIsDisabled, sShowModalDialogPref, false); + sAddedPrefCache = true; + } + + return !sIsDisabled && !XRE_IsContentProcess(); +} + +nsIDOMOfflineResourceList* +nsGlobalWindow::GetApplicationCache(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mApplicationCache) { + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(GetDocShell())); + if (!webNav || !mDoc) { + aError.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + nsCOMPtr<nsIURI> uri; + aError = webNav->GetCurrentURI(getter_AddRefs(uri)); + if (aError.Failed()) { + return nullptr; + } + + nsCOMPtr<nsIURI> manifestURI; + nsContentUtils::GetOfflineAppManifest(mDoc, getter_AddRefs(manifestURI)); + + RefPtr<nsDOMOfflineResourceList> applicationCache = + new nsDOMOfflineResourceList(manifestURI, uri, mDoc->NodePrincipal(), + AsInner()); + + applicationCache->Init(); + + mApplicationCache = applicationCache; + } + + return mApplicationCache; +} + +already_AddRefed<nsIDOMOfflineResourceList> +nsGlobalWindow::GetApplicationCache() +{ + FORWARD_TO_INNER(GetApplicationCache, (), nullptr); + + ErrorResult dummy; + nsCOMPtr<nsIDOMOfflineResourceList> applicationCache = + GetApplicationCache(dummy); + dummy.SuppressException(); + return applicationCache.forget(); +} + +Crypto* +nsGlobalWindow::GetCrypto(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mCrypto) { + mCrypto = new Crypto(); + mCrypto->Init(this); + } + return mCrypto; +} + +mozilla::dom::U2F* +nsGlobalWindow::GetU2f(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mU2F) { + RefPtr<U2F> u2f = new U2F(); + u2f->Init(AsInner(), aError); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } + + mU2F = u2f; + } + return mU2F; +} + +nsIControllers* +nsGlobalWindow::GetControllersOuter(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mControllers) { + nsresult rv; + mControllers = do_CreateInstance(kXULControllersCID, &rv); + if (NS_FAILED(rv)) { + aError.Throw(rv); + return nullptr; + } + + // Add in the default controller + nsCOMPtr<nsIController> controller = do_CreateInstance( + NS_WINDOWCONTROLLER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + aError.Throw(rv); + return nullptr; + } + + mControllers->InsertControllerAt(0, controller); + nsCOMPtr<nsIControllerContext> controllerContext = do_QueryInterface(controller); + if (!controllerContext) { + aError.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + controllerContext->SetCommandContext(static_cast<nsIDOMWindow*>(this)); + } + + return mControllers; +} + +nsIControllers* +nsGlobalWindow::GetControllers(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetControllersOuter, (aError), aError, nullptr); +} + +nsresult +nsGlobalWindow::GetControllers(nsIControllers** aResult) +{ + FORWARD_TO_INNER(GetControllers, (aResult), NS_ERROR_UNEXPECTED); + + ErrorResult rv; + nsCOMPtr<nsIControllers> controllers = GetControllers(rv); + controllers.forget(aResult); + + return rv.StealNSResult(); +} + +nsPIDOMWindowOuter* +nsGlobalWindow::GetSanitizedOpener(nsPIDOMWindowOuter* aOpener) +{ + if (!aOpener) { + return nullptr; + } + + nsGlobalWindow* win = nsGlobalWindow::Cast(aOpener); + + // First, ensure that we're not handing back a chrome window to content: + if (win->IsChromeWindow()) { + return nullptr; + } + + // We don't want to reveal the opener if the opener is a mail window, + // because opener can be used to spoof the contents of a message (bug 105050). + // So, we look in the opener's root docshell to see if it's a mail window. + nsCOMPtr<nsIDocShell> openerDocShell = aOpener->GetDocShell(); + + if (openerDocShell) { + nsCOMPtr<nsIDocShellTreeItem> openerRootItem; + openerDocShell->GetRootTreeItem(getter_AddRefs(openerRootItem)); + nsCOMPtr<nsIDocShell> openerRootDocShell(do_QueryInterface(openerRootItem)); + if (openerRootDocShell) { + uint32_t appType; + nsresult rv = openerRootDocShell->GetAppType(&appType); + if (NS_SUCCEEDED(rv) && appType != nsIDocShell::APP_TYPE_MAIL) { + return aOpener; + } + } + } + + return nullptr; +} + +nsPIDOMWindowOuter* +nsGlobalWindow::GetOpenerWindowOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsPIDOMWindowOuter> opener = do_QueryReferent(mOpener); + + if (!opener) { + return nullptr; + } + + // First, check if we were called from a privileged chrome script + if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { + // Catch the case where we're chrome but the opener is not... + if (GetPrincipal() == nsContentUtils::GetSystemPrincipal() && + nsGlobalWindow::Cast(opener)->GetPrincipal() != nsContentUtils::GetSystemPrincipal()) { + return nullptr; + } + return opener; + } + + return GetSanitizedOpener(opener); +} + +nsPIDOMWindowOuter* +nsGlobalWindow::GetOpenerWindow(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetOpenerWindowOuter, (), aError, nullptr); +} + +void +nsGlobalWindow::GetOpener(JSContext* aCx, JS::MutableHandle<JS::Value> aRetval, + ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + + nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpenerWindow(aError); + if (aError.Failed() || !opener) { + aRetval.setNull(); + return; + } + + aError = nsContentUtils::WrapNative(aCx, opener, aRetval); +} + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::GetOpener() +{ + FORWARD_TO_OUTER(GetOpener, (), nullptr); + + nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpenerWindowOuter(); + return opener.forget(); +} + +void +nsGlobalWindow::SetOpener(JSContext* aCx, JS::Handle<JS::Value> aOpener, + ErrorResult& aError) +{ + // Check if we were called from a privileged chrome script. If not, and if + // aOpener is not null, just define aOpener on our inner window's JS object, + // wrapped into the current compartment so that for Xrays we define on the + // Xray expando object, but don't set it on the outer window, so that it'll + // get reset on navigation. This is just like replaceable properties, but + // we're not quite readonly. + if (!aOpener.isNull() && !nsContentUtils::IsCallerChrome()) { + RedefineProperty(aCx, "opener", aOpener, aError); + return; + } + + if (!aOpener.isObjectOrNull()) { + // Chrome code trying to set some random value as opener + aError.Throw(NS_ERROR_INVALID_ARG); + return; + } + + nsPIDOMWindowInner* win = nullptr; + if (aOpener.isObject()) { + JSObject* unwrapped = js::CheckedUnwrap(&aOpener.toObject(), + /* stopAtWindowProxy = */ false); + if (!unwrapped) { + aError.Throw(NS_ERROR_DOM_SECURITY_ERR); + return; + } + + auto* globalWindow = xpc::WindowOrNull(unwrapped); + if (!globalWindow) { + // Wasn't a window + aError.Throw(NS_ERROR_INVALID_ARG); + return; + } + + win = globalWindow->AsInner(); + } + + nsPIDOMWindowOuter* outer = nullptr; + if (win) { + if (!win->IsCurrentInnerWindow()) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + outer = win->GetOuterWindow(); + } + + SetOpenerWindow(outer, false); +} + +void +nsGlobalWindow::GetStatusOuter(nsAString& aStatus) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + aStatus = mStatus; +} + +void +nsGlobalWindow::GetStatus(nsAString& aStatus, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetStatusOuter, (aStatus), aError, ); +} + +void +nsGlobalWindow::SetStatusOuter(const nsAString& aStatus) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + mStatus = aStatus; + + /* + * If caller is not chrome and dom.disable_window_status_change is true, + * prevent propagating window.status to the UI by exiting early + */ + + if (!CanSetProperty("dom.disable_window_status_change")) { + return; + } + + nsCOMPtr<nsIWebBrowserChrome> browserChrome = GetWebBrowserChrome(); + if (browserChrome) { + browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_SCRIPT, + PromiseFlatString(aStatus).get()); + } +} + +void +nsGlobalWindow::SetStatus(const nsAString& aStatus, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(SetStatusOuter, (aStatus), aError, ); +} + +void +nsGlobalWindow::GetNameOuter(nsAString& aName) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (mDocShell) { + mDocShell->GetName(aName); + } +} + +void +nsGlobalWindow::GetName(nsAString& aName, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetNameOuter, (aName), aError, ); +} + +void +nsGlobalWindow::SetNameOuter(const nsAString& aName, mozilla::ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (mDocShell) { + aError = mDocShell->SetName(aName); + } +} + +void +nsGlobalWindow::SetName(const nsAString& aName, mozilla::ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(SetNameOuter, (aName, aError), aError, ); +} + +// Helper functions used by many methods below. +int32_t +nsGlobalWindow::DevToCSSIntPixels(int32_t px) +{ + if (!mDocShell) + return px; // assume 1:1 + + RefPtr<nsPresContext> presContext; + mDocShell->GetPresContext(getter_AddRefs(presContext)); + if (!presContext) + return px; + + return presContext->DevPixelsToIntCSSPixels(px); +} + +int32_t +nsGlobalWindow::CSSToDevIntPixels(int32_t px) +{ + if (!mDocShell) + return px; // assume 1:1 + + RefPtr<nsPresContext> presContext; + mDocShell->GetPresContext(getter_AddRefs(presContext)); + if (!presContext) + return px; + + return presContext->CSSPixelsToDevPixels(px); +} + +nsIntSize +nsGlobalWindow::DevToCSSIntPixels(nsIntSize px) +{ + if (!mDocShell) + return px; // assume 1:1 + + RefPtr<nsPresContext> presContext; + mDocShell->GetPresContext(getter_AddRefs(presContext)); + if (!presContext) + return px; + + return nsIntSize( + presContext->DevPixelsToIntCSSPixels(px.width), + presContext->DevPixelsToIntCSSPixels(px.height)); +} + +nsIntSize +nsGlobalWindow::CSSToDevIntPixels(nsIntSize px) +{ + if (!mDocShell) + return px; // assume 1:1 + + RefPtr<nsPresContext> presContext; + mDocShell->GetPresContext(getter_AddRefs(presContext)); + if (!presContext) + return px; + + return nsIntSize( + presContext->CSSPixelsToDevPixels(px.width), + presContext->CSSPixelsToDevPixels(px.height)); +} + +nsresult +nsGlobalWindow::GetInnerSize(CSSIntSize& aSize) +{ + MOZ_ASSERT(IsOuterWindow()); + + EnsureSizeUpToDate(); + + NS_ENSURE_STATE(mDocShell); + + RefPtr<nsPresContext> presContext; + mDocShell->GetPresContext(getter_AddRefs(presContext)); + RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell(); + + if (!presContext || !presShell) { + aSize = CSSIntSize(0, 0); + return NS_OK; + } + + /* + * On platforms with resolution-based zooming, the CSS viewport + * and visual viewport may not be the same. The inner size should + * be the visual viewport, but we fall back to the CSS viewport + * if it is not set. + */ + if (presShell->IsScrollPositionClampingScrollPortSizeSet()) { + aSize = CSSIntRect::FromAppUnitsRounded( + presShell->GetScrollPositionClampingScrollPortSize()); + } else { + RefPtr<nsViewManager> viewManager = presShell->GetViewManager(); + if (viewManager) { + viewManager->FlushDelayedResize(false); + } + + aSize = CSSIntRect::FromAppUnitsRounded( + presContext->GetVisibleArea().Size()); + } + return NS_OK; +} + +int32_t +nsGlobalWindow::GetInnerWidthOuter(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + CSSIntSize size; + aError = GetInnerSize(size); + return size.width; +} + +int32_t +nsGlobalWindow::GetInnerWidth(CallerType aCallerType, ErrorResult& aError) +{ + // We ignore aCallerType; we only have that argument because some other things + // called by GetReplaceableWindowCoord need it. If this ever changes, fix + // nsresult nsGlobalWindow::GetInnerWidth(int32_t* aInnerWidth) + // to actually take a useful CallerType and pass it in here. + FORWARD_TO_OUTER_OR_THROW(GetInnerWidthOuter, (aError), aError, 0); +} + +void +nsGlobalWindow::GetInnerWidth(JSContext* aCx, + JS::MutableHandle<JS::Value> aValue, + CallerType aCallerType, + ErrorResult& aError) +{ + GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetInnerWidth, aValue, + aCallerType, aError); +} + +nsresult +nsGlobalWindow::GetInnerWidth(int32_t* aInnerWidth) +{ + FORWARD_TO_INNER(GetInnerWidth, (aInnerWidth), NS_ERROR_UNEXPECTED); + + ErrorResult rv; + // Callee doesn't care about the caller type, but play it safe. + *aInnerWidth = GetInnerWidth(CallerType::NonSystem, rv); + + return rv.StealNSResult(); +} + +void +nsGlobalWindow::SetInnerWidthOuter(int32_t aInnerWidth, ErrorResult& aError, bool aCallerIsChrome) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDocShell) { + aError.Throw(NS_ERROR_UNEXPECTED); + return; + } + + CheckSecurityWidthAndHeight(&aInnerWidth, nullptr, aCallerIsChrome); + + RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell(); + + if (presShell && presShell->GetIsViewportOverridden()) + { + nscoord height = 0; + + RefPtr<nsPresContext> presContext; + presContext = presShell->GetPresContext(); + + nsRect shellArea = presContext->GetVisibleArea(); + height = shellArea.height; + SetCSSViewportWidthAndHeight(nsPresContext::CSSPixelsToAppUnits(aInnerWidth), + height); + return; + } + + int32_t height = 0; + int32_t unused = 0; + + nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell)); + docShellAsWin->GetSize(&unused, &height); + aError = SetDocShellWidthAndHeight(CSSToDevIntPixels(aInnerWidth), height); +} + +void +nsGlobalWindow::SetInnerWidth(int32_t aInnerWidth, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(SetInnerWidthOuter, (aInnerWidth, aError, nsContentUtils::IsCallerChrome()), aError, ); +} + +void +nsGlobalWindow::SetInnerWidth(JSContext* aCx, JS::Handle<JS::Value> aValue, + CallerType aCallerType, + ErrorResult& aError) +{ + SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetInnerWidth, + aValue, "innerWidth", aError); +} + +int32_t +nsGlobalWindow::GetInnerHeightOuter(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + CSSIntSize size; + aError = GetInnerSize(size); + return size.height; +} + +int32_t +nsGlobalWindow::GetInnerHeight(CallerType aCallerType, ErrorResult& aError) +{ + // We ignore aCallerType; we only have that argument because some other things + // called by GetReplaceableWindowCoord need it. If this ever changes, fix + // nsresult nsGlobalWindow::GetInnerHeight(int32_t* aInnerWidth) + // to actually take a useful CallerType and pass it in here. + FORWARD_TO_OUTER_OR_THROW(GetInnerHeightOuter, (aError), aError, 0); +} + +void +nsGlobalWindow::GetInnerHeight(JSContext* aCx, + JS::MutableHandle<JS::Value> aValue, + CallerType aCallerType, + ErrorResult& aError) +{ + GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetInnerHeight, aValue, + aCallerType, aError); +} + +nsresult +nsGlobalWindow::GetInnerHeight(int32_t* aInnerHeight) +{ + FORWARD_TO_INNER(GetInnerHeight, (aInnerHeight), NS_ERROR_UNEXPECTED); + + ErrorResult rv; + // Callee doesn't care about the caller type, but play it safe. + *aInnerHeight = GetInnerHeight(CallerType::NonSystem, rv); + + return rv.StealNSResult(); +} + +void +nsGlobalWindow::SetInnerHeightOuter(int32_t aInnerHeight, ErrorResult& aError, bool aCallerIsChrome) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDocShell) { + aError.Throw(NS_ERROR_UNEXPECTED); + return; + } + + RefPtr<nsIPresShell> presShell = mDocShell->GetPresShell(); + + if (presShell && presShell->GetIsViewportOverridden()) + { + RefPtr<nsPresContext> presContext; + presContext = presShell->GetPresContext(); + + nsRect shellArea = presContext->GetVisibleArea(); + nscoord height = aInnerHeight; + nscoord width = shellArea.width; + CheckSecurityWidthAndHeight(nullptr, &height, aCallerIsChrome); + SetCSSViewportWidthAndHeight(width, + nsPresContext::CSSPixelsToAppUnits(height)); + return; + } + + int32_t height = 0; + int32_t width = 0; + + nsCOMPtr<nsIBaseWindow> docShellAsWin(do_QueryInterface(mDocShell)); + docShellAsWin->GetSize(&width, &height); + CheckSecurityWidthAndHeight(nullptr, &aInnerHeight, aCallerIsChrome); + aError = SetDocShellWidthAndHeight(width, CSSToDevIntPixels(aInnerHeight)); +} + +void +nsGlobalWindow::SetInnerHeight(int32_t aInnerHeight, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(SetInnerHeightOuter, (aInnerHeight, aError, nsContentUtils::IsCallerChrome()), aError, ); +} + +void +nsGlobalWindow::SetInnerHeight(JSContext* aCx, JS::Handle<JS::Value> aValue, + CallerType aCallerType, ErrorResult& aError) +{ + SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetInnerHeight, + aValue, "innerHeight", aError); +} + +nsIntSize +nsGlobalWindow::GetOuterSize(CallerType aCallerType, ErrorResult& aError) +{ + MOZ_ASSERT(IsOuterWindow()); + + if (nsContentUtils::ResistFingerprinting(aCallerType)) { + CSSIntSize size; + aError = GetInnerSize(size); + return nsIntSize(size.width, size.height); + } + + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow(); + if (!treeOwnerAsWin) { + aError.Throw(NS_ERROR_FAILURE); + return nsIntSize(0, 0); + } + + nsGlobalWindow* rootWindow = nsGlobalWindow::Cast(GetPrivateRoot()); + if (rootWindow) { + rootWindow->FlushPendingNotifications(Flush_Layout); + } + + nsIntSize sizeDevPixels; + aError = treeOwnerAsWin->GetSize(&sizeDevPixels.width, &sizeDevPixels.height); + if (aError.Failed()) { + return nsIntSize(); + } + + return DevToCSSIntPixels(sizeDevPixels); +} + +int32_t +nsGlobalWindow::GetOuterWidthOuter(CallerType aCallerType, ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + return GetOuterSize(aCallerType, aError).width; +} + +int32_t +nsGlobalWindow::GetOuterWidth(CallerType aCallerType, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetOuterWidthOuter, (aCallerType, aError), + aError, 0); +} + +void +nsGlobalWindow::GetOuterWidth(JSContext* aCx, + JS::MutableHandle<JS::Value> aValue, + CallerType aCallerType, + ErrorResult& aError) +{ + GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetOuterWidth, aValue, + aCallerType, aError); +} + +int32_t +nsGlobalWindow::GetOuterHeightOuter(CallerType aCallerType, ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + return GetOuterSize(aCallerType, aError).height; +} + +int32_t +nsGlobalWindow::GetOuterHeight(CallerType aCallerType, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetOuterHeightOuter, (aCallerType, aError), + aError, 0); +} + +void +nsGlobalWindow::GetOuterHeight(JSContext* aCx, + JS::MutableHandle<JS::Value> aValue, + CallerType aCallerType, + ErrorResult& aError) +{ + GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetOuterHeight, aValue, + aCallerType, aError); +} + +void +nsGlobalWindow::SetOuterSize(int32_t aLengthCSSPixels, bool aIsWidth, + ErrorResult& aError, bool aCallerIsChrome) +{ + MOZ_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow(); + if (!treeOwnerAsWin) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + CheckSecurityWidthAndHeight(aIsWidth ? &aLengthCSSPixels : nullptr, + aIsWidth ? nullptr : &aLengthCSSPixels, + aCallerIsChrome); + + int32_t width, height; + aError = treeOwnerAsWin->GetSize(&width, &height); + if (aError.Failed()) { + return; + } + + int32_t lengthDevPixels = CSSToDevIntPixels(aLengthCSSPixels); + if (aIsWidth) { + width = lengthDevPixels; + } else { + height = lengthDevPixels; + } + aError = treeOwnerAsWin->SetSize(width, height, true); + + CheckForDPIChange(); +} + +void +nsGlobalWindow::SetOuterWidthOuter(int32_t aOuterWidth, ErrorResult& aError, bool aCallerIsChrome) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + SetOuterSize(aOuterWidth, true, aError, aCallerIsChrome); +} + +void +nsGlobalWindow::SetOuterWidth(int32_t aOuterWidth, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(SetOuterWidthOuter, (aOuterWidth, aError, nsContentUtils::IsCallerChrome()), aError, ); +} + +void +nsGlobalWindow::SetOuterWidth(JSContext* aCx, JS::Handle<JS::Value> aValue, + CallerType aCallerType, + ErrorResult& aError) +{ + SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetOuterWidth, + aValue, "outerWidth", aError); +} + +void +nsGlobalWindow::SetOuterHeightOuter(int32_t aOuterHeight, ErrorResult& aError, bool aCallerIsChrome) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + SetOuterSize(aOuterHeight, false, aError, aCallerIsChrome); +} + +void +nsGlobalWindow::SetOuterHeight(int32_t aOuterHeight, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(SetOuterHeightOuter, (aOuterHeight, aError, nsContentUtils::IsCallerChrome()), aError, ); +} + +void +nsGlobalWindow::SetOuterHeight(JSContext* aCx, JS::Handle<JS::Value> aValue, + CallerType aCallerType, + ErrorResult& aError) +{ + SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetOuterHeight, + aValue, "outerHeight", aError); +} + +CSSIntPoint +nsGlobalWindow::GetScreenXY(CallerType aCallerType, ErrorResult& aError) +{ + MOZ_ASSERT(IsOuterWindow()); + + // When resisting fingerprinting, always return (0,0) + if (nsContentUtils::ResistFingerprinting(aCallerType)) { + return CSSIntPoint(0, 0); + } + + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow(); + if (!treeOwnerAsWin) { + aError.Throw(NS_ERROR_FAILURE); + return CSSIntPoint(0, 0); + } + + int32_t x = 0, y = 0; + aError = treeOwnerAsWin->GetPosition(&x, &y); // LayoutDevice px values + + RefPtr<nsPresContext> presContext; + mDocShell->GetPresContext(getter_AddRefs(presContext)); + if (!presContext) { + return CSSIntPoint(x, y); + } + + // Find the global desktop coordinate of the top-left of the screen. + // We'll use this as a "fake origin" when converting to CSS px units, + // to avoid overlapping coordinates in cases such as a hi-dpi screen + // placed to the right of a lo-dpi screen on Windows. (Instead, there + // may be "gaps" in the resulting CSS px coordinates in some cases.) + nsDeviceContext *dc = presContext->DeviceContext(); + nsRect screenRect; + dc->GetRect(screenRect); + LayoutDeviceRect screenRectDev = + LayoutDevicePixel::FromAppUnits(screenRect, dc->AppUnitsPerDevPixel()); + + DesktopToLayoutDeviceScale scale = dc->GetDesktopToDeviceScale(); + DesktopRect screenRectDesk = screenRectDev / scale; + + CSSPoint cssPt = + LayoutDevicePoint(x - screenRectDev.x, y - screenRectDev.y) / + presContext->CSSToDevPixelScale(); + cssPt.x += screenRectDesk.x; + cssPt.y += screenRectDesk.y; + + return CSSIntPoint(NSToIntRound(cssPt.x), NSToIntRound(cssPt.y)); +} + +int32_t +nsGlobalWindow::GetScreenXOuter(CallerType aCallerType, ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + return GetScreenXY(aCallerType, aError).x; +} + +int32_t +nsGlobalWindow::GetScreenX(CallerType aCallerType, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetScreenXOuter, (aCallerType, aError), aError, 0); +} + +void +nsGlobalWindow::GetScreenX(JSContext* aCx, + JS::MutableHandle<JS::Value> aValue, + CallerType aCallerType, + ErrorResult& aError) +{ + GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetScreenX, aValue, + aCallerType, aError); +} + +nsRect +nsGlobalWindow::GetInnerScreenRect() +{ + MOZ_ASSERT(IsOuterWindow()); + + if (!mDocShell) { + return nsRect(); + } + + nsGlobalWindow* rootWindow = nsGlobalWindow::Cast(GetPrivateRoot()); + if (rootWindow) { + rootWindow->FlushPendingNotifications(Flush_Layout); + } + + if (!mDocShell) { + return nsRect(); + } + + nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell(); + if (!presShell) { + return nsRect(); + } + nsIFrame* rootFrame = presShell->GetRootFrame(); + if (!rootFrame) { + return nsRect(); + } + + return rootFrame->GetScreenRectInAppUnits(); +} + +float +nsGlobalWindow::GetMozInnerScreenXOuter(CallerType aCallerType) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + // When resisting fingerprinting, always return 0. + if (nsContentUtils::ResistFingerprinting(aCallerType)) { + return 0.0; + } + + nsRect r = GetInnerScreenRect(); + return nsPresContext::AppUnitsToFloatCSSPixels(r.x); +} + +float +nsGlobalWindow::GetMozInnerScreenX(CallerType aCallerType, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenXOuter, (aCallerType), aError, 0); +} + +float +nsGlobalWindow::GetMozInnerScreenYOuter(CallerType aCallerType) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + // Return 0 to prevent fingerprinting. + if (nsContentUtils::ResistFingerprinting(aCallerType)) { + return 0.0; + } + + nsRect r = GetInnerScreenRect(); + return nsPresContext::AppUnitsToFloatCSSPixels(r.y); +} + +float +nsGlobalWindow::GetMozInnerScreenY(CallerType aCallerType, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenYOuter, (aCallerType), aError, 0); +} + +float +nsGlobalWindow::GetDevicePixelRatioOuter(CallerType aCallerType) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDocShell) { + return 1.0; + } + + RefPtr<nsPresContext> presContext; + mDocShell->GetPresContext(getter_AddRefs(presContext)); + if (!presContext) { + return 1.0; + } + + if (nsContentUtils::ResistFingerprinting(aCallerType)) { + return 1.0; + } + + float overrideDPPX = presContext->GetOverrideDPPX(); + + if (overrideDPPX > 0) { + return overrideDPPX; + } + + return float(nsPresContext::AppUnitsPerCSSPixel())/ + presContext->AppUnitsPerDevPixel(); +} + +float +nsGlobalWindow::GetDevicePixelRatio(CallerType aCallerType, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetDevicePixelRatioOuter, (aCallerType), aError, 0.0); +} + +float +nsPIDOMWindowOuter::GetDevicePixelRatio(CallerType aCallerType) +{ + return nsGlobalWindow::Cast(this)->GetDevicePixelRatioOuter(aCallerType); +} + +uint64_t +nsGlobalWindow::GetMozPaintCountOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDocShell) { + return 0; + } + + nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell(); + return presShell ? presShell->GetPaintCount() : 0; +} + +uint64_t +nsGlobalWindow::GetMozPaintCount(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetMozPaintCountOuter, (), aError, 0); +} + +int32_t +nsGlobalWindow::RequestAnimationFrame(FrameRequestCallback& aCallback, + ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mDoc) { + return 0; + } + + if (GetWrapperPreserveColor()) { + js::NotifyAnimationActivity(GetWrapperPreserveColor()); + } + + int32_t handle; + aError = mDoc->ScheduleFrameRequestCallback(aCallback, &handle); + return handle; +} + +void +nsGlobalWindow::CancelAnimationFrame(int32_t aHandle, ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mDoc) { + return; + } + + mDoc->CancelFrameRequestCallback(aHandle); +} + +already_AddRefed<MediaQueryList> +nsGlobalWindow::MatchMediaOuter(const nsAString& aMediaQueryList) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDoc) { + return nullptr; + } + + return mDoc->MatchMedia(aMediaQueryList); +} + +already_AddRefed<MediaQueryList> +nsGlobalWindow::MatchMedia(const nsAString& aMediaQueryList, + ErrorResult& aError) +{ + // FIXME: This whole forward-to-outer and then get a pres + // shell/context off the docshell dance is sort of silly; it'd make + // more sense to forward to the inner, but it's what everyone else + // (GetSelection, GetScrollXY, etc.) does around here. + FORWARD_TO_OUTER_OR_THROW(MatchMediaOuter, (aMediaQueryList), aError, nullptr); +} + +void +nsGlobalWindow::SetScreenXOuter(int32_t aScreenX, ErrorResult& aError, bool aCallerIsChrome) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow(); + if (!treeOwnerAsWin) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + int32_t x, y; + aError = treeOwnerAsWin->GetPosition(&x, &y); + if (aError.Failed()) { + return; + } + + CheckSecurityLeftAndTop(&aScreenX, nullptr, aCallerIsChrome); + x = CSSToDevIntPixels(aScreenX); + + aError = treeOwnerAsWin->SetPosition(x, y); + + CheckForDPIChange(); +} + +void +nsGlobalWindow::SetScreenX(int32_t aScreenX, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(SetScreenXOuter, (aScreenX, aError, nsContentUtils::IsCallerChrome()), aError, ); +} + +void +nsGlobalWindow::SetScreenX(JSContext* aCx, JS::Handle<JS::Value> aValue, + CallerType aCallerType, ErrorResult& aError) +{ + SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetScreenX, + aValue, "screenX", aError); +} + +int32_t +nsGlobalWindow::GetScreenYOuter(CallerType aCallerType, ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + return GetScreenXY(aCallerType, aError).y; +} + +int32_t +nsGlobalWindow::GetScreenY(CallerType aCallerType, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetScreenYOuter, (aCallerType, aError), aError, 0); +} + +void +nsGlobalWindow::GetScreenY(JSContext* aCx, + JS::MutableHandle<JS::Value> aValue, + CallerType aCallerType, ErrorResult& aError) +{ + GetReplaceableWindowCoord(aCx, &nsGlobalWindow::GetScreenY, aValue, + aCallerType, aError); +} + +void +nsGlobalWindow::SetScreenYOuter(int32_t aScreenY, ErrorResult& aError, bool aCallerIsChrome) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow(); + if (!treeOwnerAsWin) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + int32_t x, y; + aError = treeOwnerAsWin->GetPosition(&x, &y); + if (aError.Failed()) { + return; + } + + CheckSecurityLeftAndTop(nullptr, &aScreenY, aCallerIsChrome); + y = CSSToDevIntPixels(aScreenY); + + aError = treeOwnerAsWin->SetPosition(x, y); + + CheckForDPIChange(); +} + +void +nsGlobalWindow::SetScreenY(int32_t aScreenY, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(SetScreenYOuter, (aScreenY, aError, nsContentUtils::IsCallerChrome()), aError, ); +} + +void +nsGlobalWindow::SetScreenY(JSContext* aCx, JS::Handle<JS::Value> aValue, + CallerType aCallerType, + ErrorResult& aError) +{ + SetReplaceableWindowCoord(aCx, &nsGlobalWindow::SetScreenY, + aValue, "screenY", aError); +} + +// NOTE: Arguments to this function should have values scaled to +// CSS pixels, not device pixels. +void +nsGlobalWindow::CheckSecurityWidthAndHeight(int32_t* aWidth, int32_t* aHeight, bool aCallerIsChrome) +{ + MOZ_ASSERT(IsOuterWindow()); + +#ifdef MOZ_XUL + if (!aCallerIsChrome) { + // if attempting to resize the window, hide any open popups + nsContentUtils::HidePopupsInDocument(mDoc); + } +#endif + + // This one is easy. Just ensure the variable is greater than 100; + if ((aWidth && *aWidth < 100) || (aHeight && *aHeight < 100)) { + // Check security state for use in determing window dimensions + + if (!nsContentUtils::IsCallerChrome()) { + //sec check failed + if (aWidth && *aWidth < 100) { + *aWidth = 100; + } + if (aHeight && *aHeight < 100) { + *aHeight = 100; + } + } + } +} + +// NOTE: Arguments to this function should have values in device pixels +nsresult +nsGlobalWindow::SetDocShellWidthAndHeight(int32_t aInnerWidth, int32_t aInnerHeight) +{ + MOZ_ASSERT(IsOuterWindow()); + + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + mDocShell->GetTreeOwner(getter_AddRefs(treeOwner)); + NS_ENSURE_TRUE(treeOwner, NS_ERROR_FAILURE); + + NS_ENSURE_SUCCESS(treeOwner->SizeShellTo(mDocShell, aInnerWidth, aInnerHeight), + NS_ERROR_FAILURE); + + return NS_OK; +} + +// NOTE: Arguments to this function should have values in app units +void +nsGlobalWindow::SetCSSViewportWidthAndHeight(nscoord aInnerWidth, nscoord aInnerHeight) +{ + MOZ_ASSERT(IsOuterWindow()); + + RefPtr<nsPresContext> presContext; + mDocShell->GetPresContext(getter_AddRefs(presContext)); + + nsRect shellArea = presContext->GetVisibleArea(); + shellArea.height = aInnerHeight; + shellArea.width = aInnerWidth; + + presContext->SetVisibleArea(shellArea); +} + +// NOTE: Arguments to this function should have values scaled to +// CSS pixels, not device pixels. +void +nsGlobalWindow::CheckSecurityLeftAndTop(int32_t* aLeft, int32_t* aTop, bool aCallerIsChrome) +{ + MOZ_ASSERT(IsOuterWindow()); + + // This one is harder. We have to get the screen size and window dimensions. + + // Check security state for use in determing window dimensions + + if (!aCallerIsChrome) { +#ifdef MOZ_XUL + // if attempting to move the window, hide any open popups + nsContentUtils::HidePopupsInDocument(mDoc); +#endif + + if (nsGlobalWindow* rootWindow = nsGlobalWindow::Cast(GetPrivateRoot())) { + rootWindow->FlushPendingNotifications(Flush_Layout); + } + + nsCOMPtr<nsIBaseWindow> treeOwner = GetTreeOwnerWindow(); + + nsCOMPtr<nsIDOMScreen> screen = GetScreen(); + + if (treeOwner && screen) { + int32_t screenLeft, screenTop, screenWidth, screenHeight; + int32_t winLeft, winTop, winWidth, winHeight; + + // Get the window size + treeOwner->GetPositionAndSize(&winLeft, &winTop, &winWidth, &winHeight); + + // convert those values to CSS pixels + // XXX four separate retrievals of the prescontext + winLeft = DevToCSSIntPixels(winLeft); + winTop = DevToCSSIntPixels(winTop); + winWidth = DevToCSSIntPixels(winWidth); + winHeight = DevToCSSIntPixels(winHeight); + + // Get the screen dimensions + // XXX This should use nsIScreenManager once it's fully fleshed out. + screen->GetAvailLeft(&screenLeft); + screen->GetAvailWidth(&screenWidth); + screen->GetAvailHeight(&screenHeight); +#if defined(XP_MACOSX) + /* The mac's coordinate system is different from the assumed Windows' + system. It offsets by the height of the menubar so that a window + placed at (0,0) will be entirely visible. Unfortunately that + correction is made elsewhere (in Widget) and the meaning of + the Avail... coordinates is overloaded. Here we allow a window + to be placed at (0,0) because it does make sense to do so. + */ + screen->GetTop(&screenTop); +#else + screen->GetAvailTop(&screenTop); +#endif + + if (aLeft) { + if (screenLeft+screenWidth < *aLeft+winWidth) + *aLeft = screenLeft+screenWidth - winWidth; + if (screenLeft > *aLeft) + *aLeft = screenLeft; + } + if (aTop) { + if (screenTop+screenHeight < *aTop+winHeight) + *aTop = screenTop+screenHeight - winHeight; + if (screenTop > *aTop) + *aTop = screenTop; + } + } else { + if (aLeft) + *aLeft = 0; + if (aTop) + *aTop = 0; + } + } +} + +int32_t +nsGlobalWindow::GetScrollBoundaryOuter(Side aSide) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + FlushPendingNotifications(Flush_Layout); + if (nsIScrollableFrame *sf = GetScrollFrame()) { + return nsPresContext:: + AppUnitsToIntCSSPixels(sf->GetScrollRange().Edge(aSide)); + } + return 0; +} + +int32_t +nsGlobalWindow::GetScrollMinX(ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideLeft), aError, 0); +} + +int32_t +nsGlobalWindow::GetScrollMinY(ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideTop), aError, 0); +} + +int32_t +nsGlobalWindow::GetScrollMaxX(ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideRight), aError, 0); +} + +int32_t +nsGlobalWindow::GetScrollMaxY(ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideBottom), aError, 0); +} + +CSSIntPoint +nsGlobalWindow::GetScrollXY(bool aDoFlush) +{ + MOZ_ASSERT(IsOuterWindow()); + + if (aDoFlush) { + FlushPendingNotifications(Flush_Layout); + } else { + EnsureSizeUpToDate(); + } + + nsIScrollableFrame *sf = GetScrollFrame(); + if (!sf) { + return CSSIntPoint(0, 0); + } + + nsPoint scrollPos = sf->GetScrollPosition(); + if (scrollPos != nsPoint(0,0) && !aDoFlush) { + // Oh, well. This is the expensive case -- the window is scrolled and we + // didn't actually flush yet. Repeat, but with a flush, since the content + // may get shorter and hence our scroll position may decrease. + return GetScrollXY(true); + } + + return sf->GetScrollPositionCSSPixels(); +} + +int32_t +nsGlobalWindow::GetScrollXOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + return GetScrollXY(false).x; +} + +int32_t +nsGlobalWindow::GetScrollX(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetScrollXOuter, (), aError, 0); +} + +int32_t +nsGlobalWindow::GetScrollYOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + return GetScrollXY(false).y; +} + +int32_t +nsGlobalWindow::GetScrollY(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetScrollYOuter, (), aError, 0); +} + +uint32_t +nsGlobalWindow::Length() +{ + FORWARD_TO_OUTER(Length, (), 0); + + nsDOMWindowList* windows = GetWindowList(); + + return windows ? windows->GetLength() : 0; +} + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::GetTopOuter() +{ + MOZ_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsPIDOMWindowOuter> top = GetScriptableTop(); + return top.forget(); +} + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::GetTop(mozilla::ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetTopOuter, (), aError, nullptr); +} + +nsPIDOMWindowOuter* +nsGlobalWindow::GetChildWindow(const nsAString& aName) +{ + nsCOMPtr<nsIDocShell> docShell(GetDocShell()); + NS_ENSURE_TRUE(docShell, nullptr); + + nsCOMPtr<nsIDocShellTreeItem> child; + docShell->FindChildWithName(aName, false, true, nullptr, nullptr, + getter_AddRefs(child)); + + return child ? child->GetWindow() : nullptr; +} + +bool +nsGlobalWindow::DispatchCustomEvent(const nsAString& aEventName) +{ + MOZ_ASSERT(IsOuterWindow()); + + bool defaultActionEnabled = true; + nsContentUtils::DispatchTrustedEvent(mDoc, ToSupports(this), aEventName, + true, true, &defaultActionEnabled); + + return defaultActionEnabled; +} + +bool +nsGlobalWindow::DispatchResizeEvent(const CSSIntSize& aSize) +{ + MOZ_ASSERT(IsOuterWindow()); + + ErrorResult res; + RefPtr<Event> domEvent = + mDoc->CreateEvent(NS_LITERAL_STRING("CustomEvent"), res); + if (res.Failed()) { + return false; + } + + // We don't init the AutoJSAPI with ourselves because we don't want it + // reporting errors to our onerror handlers. + AutoJSAPI jsapi; + jsapi.Init(); + JSContext* cx = jsapi.cx(); + JSAutoCompartment ac(cx, GetWrapperPreserveColor()); + + DOMWindowResizeEventDetail detail; + detail.mWidth = aSize.width; + detail.mHeight = aSize.height; + JS::Rooted<JS::Value> detailValue(cx); + if (!ToJSValue(cx, detail, &detailValue)) { + return false; + } + + CustomEvent* customEvent = static_cast<CustomEvent*>(domEvent.get()); + customEvent->InitCustomEvent(cx, + NS_LITERAL_STRING("DOMWindowResize"), + /* aCanBubble = */ true, + /* aCancelable = */ true, + detailValue, + res); + if (res.Failed()) { + return false; + } + + domEvent->SetTrusted(true); + domEvent->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + + nsCOMPtr<EventTarget> target = do_QueryInterface(GetOuterWindow()); + domEvent->SetTarget(target); + + bool defaultActionEnabled = true; + target->DispatchEvent(domEvent, &defaultActionEnabled); + + return defaultActionEnabled; +} + +void +nsGlobalWindow::RefreshCompartmentPrincipal() +{ + MOZ_ASSERT(IsInnerWindow()); + + JS_SetCompartmentPrincipals(js::GetObjectCompartment(GetWrapperPreserveColor()), + nsJSPrincipals::get(mDoc->NodePrincipal())); +} + +static already_AddRefed<nsIDocShellTreeItem> +GetCallerDocShellTreeItem() +{ + nsCOMPtr<nsIWebNavigation> callerWebNav = do_GetInterface(GetEntryGlobal()); + nsCOMPtr<nsIDocShellTreeItem> callerItem = do_QueryInterface(callerWebNav); + + return callerItem.forget(); +} + +bool +nsGlobalWindow::WindowExists(const nsAString& aName, + bool aForceNoOpener, + bool aLookForCallerOnJSStack) +{ + NS_PRECONDITION(IsOuterWindow(), "Must be outer window"); + NS_PRECONDITION(mDocShell, "Must have docshell"); + + if (aForceNoOpener) { + return aName.LowerCaseEqualsLiteral("_self") || + aName.LowerCaseEqualsLiteral("_top") || + aName.LowerCaseEqualsLiteral("_parent"); + } + + nsCOMPtr<nsIDocShellTreeItem> caller; + if (aLookForCallerOnJSStack) { + caller = GetCallerDocShellTreeItem(); + } + + if (!caller) { + caller = mDocShell; + } + + nsCOMPtr<nsIDocShellTreeItem> namedItem; + mDocShell->FindItemWithName(aName, nullptr, caller, + getter_AddRefs(namedItem)); + return namedItem != nullptr; +} + +already_AddRefed<nsIWidget> +nsGlobalWindow::GetMainWidget() +{ + FORWARD_TO_OUTER(GetMainWidget, (), nullptr); + + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow(); + + nsCOMPtr<nsIWidget> widget; + + if (treeOwnerAsWin) { + treeOwnerAsWin->GetMainWidget(getter_AddRefs(widget)); + } + + return widget.forget(); +} + +nsIWidget* +nsGlobalWindow::GetNearestWidget() const +{ + nsIDocShell* docShell = GetDocShell(); + NS_ENSURE_TRUE(docShell, nullptr); + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + NS_ENSURE_TRUE(presShell, nullptr); + nsIFrame* rootFrame = presShell->GetRootFrame(); + NS_ENSURE_TRUE(rootFrame, nullptr); + return rootFrame->GetView()->GetNearestWidget(nullptr); +} + +void +nsGlobalWindow::SetFullScreenOuter(bool aFullScreen, mozilla::ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + aError = SetFullscreenInternal(FullscreenReason::ForFullscreenMode, aFullScreen); +} + +void +nsGlobalWindow::SetFullScreen(bool aFullScreen, mozilla::ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(SetFullScreenOuter, (aFullScreen, aError), aError, /* void */); +} + +nsresult +nsGlobalWindow::SetFullScreen(bool aFullScreen) +{ + FORWARD_TO_OUTER(SetFullScreen, (aFullScreen), NS_ERROR_NOT_INITIALIZED); + + return SetFullscreenInternal(FullscreenReason::ForFullscreenMode, aFullScreen); +} + +static void +FinishDOMFullscreenChange(nsIDocument* aDoc, bool aInDOMFullscreen) +{ + if (aInDOMFullscreen) { + // Ask the document to handle any pending DOM fullscreen change. + if (!nsIDocument::HandlePendingFullscreenRequests(aDoc)) { + // If we don't end up having anything in fullscreen, + // async request exiting fullscreen. + nsIDocument::AsyncExitFullscreen(aDoc); + } + } else { + // If the window is leaving fullscreen state, also ask the document + // to exit from DOM Fullscreen. + nsIDocument::ExitFullscreenInDocTree(aDoc); + } +} + +struct FullscreenTransitionDuration +{ + // The unit of the durations is millisecond + uint16_t mFadeIn = 0; + uint16_t mFadeOut = 0; + bool IsSuppressed() const + { + return mFadeIn == 0 && mFadeOut == 0; + } +}; + +static void +GetFullscreenTransitionDuration(bool aEnterFullscreen, + FullscreenTransitionDuration* aDuration) +{ + const char* pref = aEnterFullscreen ? + "full-screen-api.transition-duration.enter" : + "full-screen-api.transition-duration.leave"; + nsAdoptingCString prefValue = Preferences::GetCString(pref); + if (!prefValue.IsEmpty()) { + sscanf(prefValue.get(), "%hu%hu", + &aDuration->mFadeIn, &aDuration->mFadeOut); + } +} + +class FullscreenTransitionTask : public Runnable +{ +public: + FullscreenTransitionTask(const FullscreenTransitionDuration& aDuration, + nsGlobalWindow* aWindow, bool aFullscreen, + nsIWidget* aWidget, nsIScreen* aScreen, + nsISupports* aTransitionData) + : mWindow(aWindow) + , mWidget(aWidget) + , mScreen(aScreen) + , mTransitionData(aTransitionData) + , mDuration(aDuration) + , mStage(eBeforeToggle) + , mFullscreen(aFullscreen) + { + MOZ_COUNT_CTOR(FullscreenTransitionTask); + } + + NS_IMETHOD Run() override; + +private: + virtual ~FullscreenTransitionTask() + { + MOZ_COUNT_DTOR(FullscreenTransitionTask); + } + + /** + * The flow of fullscreen transition: + * + * parent process | child process + * ---------------------------------------------------------------- + * + * | request/exit fullscreen + * <-----| + * BeforeToggle stage | + * | + * ToggleFullscreen stage *1 |-----> + * | HandleFullscreenRequests + * | + * <-----| MozAfterPaint event + * AfterToggle stage *2 | + * | + * End stage | + * + * Note we also start a timer at *1 so that if we don't get MozAfterPaint + * from the child process in time, we continue going to *2. + */ + enum Stage { + // BeforeToggle stage happens before we enter or leave fullscreen + // state. In this stage, the task triggers the pre-toggle fullscreen + // transition on the widget. + eBeforeToggle, + // ToggleFullscreen stage actually executes the fullscreen toggle, + // and wait for the next paint on the content to continue. + eToggleFullscreen, + // AfterToggle stage happens after we toggle the fullscreen state. + // In this stage, the task triggers the post-toggle fullscreen + // transition on the widget. + eAfterToggle, + // End stage is triggered after the final transition finishes. + eEnd + }; + + class Observer final : public nsIObserver + { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + + explicit Observer(FullscreenTransitionTask* aTask) + : mTask(aTask) { } + + private: + ~Observer() {} + + RefPtr<FullscreenTransitionTask> mTask; + }; + + static const char* const kPaintedTopic; + + RefPtr<nsGlobalWindow> mWindow; + nsCOMPtr<nsIWidget> mWidget; + nsCOMPtr<nsIScreen> mScreen; + nsCOMPtr<nsITimer> mTimer; + nsCOMPtr<nsISupports> mTransitionData; + + TimeStamp mFullscreenChangeStartTime; + FullscreenTransitionDuration mDuration; + Stage mStage; + bool mFullscreen; +}; + +const char* const +FullscreenTransitionTask::kPaintedTopic = "fullscreen-painted"; + +NS_IMETHODIMP +FullscreenTransitionTask::Run() +{ + Stage stage = mStage; + mStage = Stage(mStage + 1); + if (MOZ_UNLIKELY(mWidget->Destroyed())) { + // If the widget has been destroyed before we get here, don't try to + // do anything more. Just let it go and release ourselves. + NS_WARNING("The widget to fullscreen has been destroyed"); + return NS_OK; + } + if (stage == eBeforeToggle) { + PROFILER_MARKER("Fullscreen transition start"); + mWidget->PerformFullscreenTransition(nsIWidget::eBeforeFullscreenToggle, + mDuration.mFadeIn, mTransitionData, + this); + } else if (stage == eToggleFullscreen) { + PROFILER_MARKER("Fullscreen toggle start"); + mFullscreenChangeStartTime = TimeStamp::Now(); + if (MOZ_UNLIKELY(mWindow->mFullScreen != mFullscreen)) { + // This could happen in theory if several fullscreen requests in + // different direction happen continuously in a short time. We + // need to ensure the fullscreen state matches our target here, + // otherwise the widget would change the window state as if we + // toggle for Fullscreen Mode instead of Fullscreen API. + NS_WARNING("The fullscreen state of the window does not match"); + mWindow->mFullScreen = mFullscreen; + } + // Toggle the fullscreen state on the widget + if (!mWindow->SetWidgetFullscreen(FullscreenReason::ForFullscreenAPI, + mFullscreen, mWidget, mScreen)) { + // Fail to setup the widget, call FinishFullscreenChange to + // complete fullscreen change directly. + mWindow->FinishFullscreenChange(mFullscreen); + } + // Set observer for the next content paint. + nsCOMPtr<nsIObserver> observer = new Observer(this); + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + obs->AddObserver(observer, kPaintedTopic, false); + // There are several edge cases where we may never get the paint + // notification, including: + // 1. the window/tab is closed before the next paint; + // 2. the user has switched to another tab before we get here. + // Completely fixing those cases seems to be tricky, and since they + // should rarely happen, it probably isn't worth to fix. Hence we + // simply add a timeout here to ensure we never hang forever. + // In addition, if the page is complicated or the machine is less + // powerful, layout could take a long time, in which case, staying + // in black screen for that long could hurt user experience even + // more than exposing an intermediate state. + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID); + uint32_t timeout = + Preferences::GetUint("full-screen-api.transition.timeout", 1000); + mTimer->Init(observer, timeout, nsITimer::TYPE_ONE_SHOT); + } else if (stage == eAfterToggle) { + Telemetry::AccumulateTimeDelta(Telemetry::FULLSCREEN_TRANSITION_BLACK_MS, + mFullscreenChangeStartTime); + mWidget->PerformFullscreenTransition(nsIWidget::eAfterFullscreenToggle, + mDuration.mFadeOut, mTransitionData, + this); + } else if (stage == eEnd) { + PROFILER_MARKER("Fullscreen transition end"); + } + return NS_OK; +} + +NS_IMPL_ISUPPORTS(FullscreenTransitionTask::Observer, nsIObserver) + +NS_IMETHODIMP +FullscreenTransitionTask::Observer::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + bool shouldContinue = false; + if (strcmp(aTopic, FullscreenTransitionTask::kPaintedTopic) == 0) { + nsCOMPtr<nsPIDOMWindowInner> win(do_QueryInterface(aSubject)); + nsCOMPtr<nsIWidget> widget = win ? + nsGlobalWindow::Cast(win)->GetMainWidget() : nullptr; + if (widget == mTask->mWidget) { + // The paint notification arrives first. Cancel the timer. + mTask->mTimer->Cancel(); + shouldContinue = true; + PROFILER_MARKER("Fullscreen toggle end"); + } + } else { +#ifdef DEBUG + MOZ_ASSERT(strcmp(aTopic, NS_TIMER_CALLBACK_TOPIC) == 0, + "Should only get fullscreen-painted or timer-callback"); + nsCOMPtr<nsITimer> timer(do_QueryInterface(aSubject)); + MOZ_ASSERT(timer && timer == mTask->mTimer, + "Should only trigger this with the timer the task created"); +#endif + shouldContinue = true; + PROFILER_MARKER("Fullscreen toggle timeout"); + } + if (shouldContinue) { + nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService(); + obs->RemoveObserver(this, kPaintedTopic); + mTask->mTimer = nullptr; + mTask->Run(); + } + return NS_OK; +} + +static bool +MakeWidgetFullscreen(nsGlobalWindow* aWindow, FullscreenReason aReason, + bool aFullscreen) +{ + nsCOMPtr<nsIWidget> widget = aWindow->GetMainWidget(); + if (!widget) { + return false; + } + + FullscreenTransitionDuration duration; + bool performTransition = false; + nsCOMPtr<nsISupports> transitionData; + if (aReason == FullscreenReason::ForFullscreenAPI) { + GetFullscreenTransitionDuration(aFullscreen, &duration); + if (!duration.IsSuppressed()) { + performTransition = widget-> + PrepareForFullscreenTransition(getter_AddRefs(transitionData)); + } + } + // We pass nullptr as the screen to SetWidgetFullscreen + // and FullscreenTransitionTask, as we do not wish to override + // the default screen selection behavior. The screen containing + // most of the widget will be selected. + if (!performTransition) { + return aWindow->SetWidgetFullscreen(aReason, aFullscreen, widget, nullptr); + } else { + nsCOMPtr<nsIRunnable> task = + new FullscreenTransitionTask(duration, aWindow, aFullscreen, + widget, nullptr, transitionData); + task->Run(); + return true; + } +} + +nsresult +nsGlobalWindow::SetFullscreenInternal(FullscreenReason aReason, + bool aFullScreen) +{ + MOZ_ASSERT(IsOuterWindow()); + MOZ_ASSERT(nsContentUtils::IsSafeToRunScript(), + "Requires safe to run script as it " + "may call FinishDOMFullscreenChange"); + + NS_ENSURE_TRUE(mDocShell, NS_ERROR_FAILURE); + + MOZ_ASSERT(aReason != FullscreenReason::ForForceExitFullscreen || !aFullScreen, + "FullscreenReason::ForForceExitFullscreen can " + "only be used with exiting fullscreen"); + + // Only chrome can change our fullscreen mode. Otherwise, the state + // can only be changed for DOM fullscreen. + if (aReason == FullscreenReason::ForFullscreenMode && + !nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { + return NS_OK; + } + + // SetFullScreen needs to be called on the root window, so get that + // via the DocShell tree, and if we are not already the root, + // call SetFullScreen on that window instead. + nsCOMPtr<nsIDocShellTreeItem> rootItem; + mDocShell->GetRootTreeItem(getter_AddRefs(rootItem)); + nsCOMPtr<nsPIDOMWindowOuter> window = rootItem ? rootItem->GetWindow() : nullptr; + if (!window) + return NS_ERROR_FAILURE; + if (rootItem != mDocShell) + return window->SetFullscreenInternal(aReason, aFullScreen); + + // make sure we don't try to set full screen on a non-chrome window, + // which might happen in embedding world + if (mDocShell->ItemType() != nsIDocShellTreeItem::typeChrome) + return NS_ERROR_FAILURE; + + // If we are already in full screen mode, just return. + if (mFullScreen == aFullScreen) + return NS_OK; + + // Note that although entering DOM fullscreen could also cause + // consequential calls to this method, those calls will be skipped + // at the condition above. + if (aReason == FullscreenReason::ForFullscreenMode) { + if (!aFullScreen && !mFullscreenMode) { + // If we are exiting fullscreen mode, but we actually didn't + // entered fullscreen mode, the fullscreen state was only for + // the Fullscreen API. Change the reason here so that we can + // perform transition for it. + aReason = FullscreenReason::ForFullscreenAPI; + } else { + mFullscreenMode = aFullScreen; + } + } else { + // If we are exiting from DOM fullscreen while we initially make + // the window fullscreen because of fullscreen mode, don't restore + // the window. But we still need to exit the DOM fullscreen state. + if (!aFullScreen && mFullscreenMode) { + FinishDOMFullscreenChange(mDoc, false); + return NS_OK; + } + } + + // Prevent chrome documents which are still loading from resizing + // the window after we set fullscreen mode. + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow(); + nsCOMPtr<nsIXULWindow> xulWin(do_GetInterface(treeOwnerAsWin)); + if (aFullScreen && xulWin) { + xulWin->SetIntrinsicallySized(false); + } + + // Set this before so if widget sends an event indicating its + // gone full screen, the state trap above works. + mFullScreen = aFullScreen; + + // Sometimes we don't want the top-level widget to actually go fullscreen, + // for example in the B2G desktop client, we don't want the emulated screen + // dimensions to appear to increase when entering fullscreen mode; we just + // want the content to fill the entire client area of the emulator window. + if (!Preferences::GetBool("full-screen-api.ignore-widgets", false)) { + if (MakeWidgetFullscreen(this, aReason, aFullScreen)) { + // The rest of code for switching fullscreen is in nsGlobalWindow:: + // FinishFullscreenChange() which will be called after sizemodechange + // event is dispatched. + return NS_OK; + } + } + + FinishFullscreenChange(aFullScreen); + return NS_OK; +} + +bool +nsGlobalWindow::SetWidgetFullscreen(FullscreenReason aReason, bool aIsFullscreen, + nsIWidget* aWidget, nsIScreen* aScreen) +{ + MOZ_ASSERT(IsOuterWindow()); + MOZ_ASSERT(this == GetTopInternal(), "Only topmost window should call this"); + MOZ_ASSERT(!AsOuter()->GetFrameElementInternal(), "Content window should not call this"); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + + if (!NS_WARN_IF(!IsChromeWindow())) { + auto chromeWin = static_cast<nsGlobalChromeWindow*>(this); + if (!NS_WARN_IF(chromeWin->mFullscreenPresShell)) { + if (nsIPresShell* shell = mDocShell->GetPresShell()) { + if (nsRefreshDriver* rd = shell->GetRefreshDriver()) { + chromeWin->mFullscreenPresShell = do_GetWeakReference(shell); + MOZ_ASSERT(chromeWin->mFullscreenPresShell); + rd->SetIsResizeSuppressed(); + rd->Freeze(); + } + } + } + } + nsresult rv = aReason == FullscreenReason::ForFullscreenMode ? + // If we enter fullscreen for fullscreen mode, we want + // the native system behavior. + aWidget->MakeFullScreenWithNativeTransition(aIsFullscreen, aScreen) : + aWidget->MakeFullScreen(aIsFullscreen, aScreen); + return NS_SUCCEEDED(rv); +} + +/* virtual */ void +nsGlobalWindow::FinishFullscreenChange(bool aIsFullscreen) +{ + MOZ_ASSERT(IsOuterWindow()); + + if (aIsFullscreen != mFullScreen) { + NS_WARNING("Failed to toggle fullscreen state of the widget"); + // We failed to make the widget enter fullscreen. + // Stop further changes and restore the state. + if (!aIsFullscreen) { + mFullScreen = false; + mFullscreenMode = false; + } else { + MOZ_ASSERT_UNREACHABLE("Failed to exit fullscreen?"); + mFullScreen = true; + // We don't know how code can reach here. Not sure + // what value should be set for fullscreen mode. + mFullscreenMode = false; + } + return; + } + + // Note that we must call this to toggle the DOM fullscreen state + // of the document before dispatching the "fullscreen" event, so + // that the chrome can distinguish between browser fullscreen mode + // and DOM fullscreen. + FinishDOMFullscreenChange(mDoc, mFullScreen); + + // dispatch a "fullscreen" DOM event so that XUL apps can + // respond visually if we are kicked into full screen mode + DispatchCustomEvent(NS_LITERAL_STRING("fullscreen")); + + if (!NS_WARN_IF(!IsChromeWindow())) { + auto chromeWin = static_cast<nsGlobalChromeWindow*>(this); + if (nsCOMPtr<nsIPresShell> shell = + do_QueryReferent(chromeWin->mFullscreenPresShell)) { + if (nsRefreshDriver* rd = shell->GetRefreshDriver()) { + rd->Thaw(); + } + chromeWin->mFullscreenPresShell = nullptr; + } + } + + if (!mWakeLock && mFullScreen) { + RefPtr<power::PowerManagerService> pmService = + power::PowerManagerService::GetInstance(); + if (!pmService) { + return; + } + + // XXXkhuey using the inner here, do we need to do something if it changes? + ErrorResult rv; + mWakeLock = pmService->NewWakeLock(NS_LITERAL_STRING("DOM_Fullscreen"), + AsOuter()->GetCurrentInnerWindow(), rv); + NS_WARNING_ASSERTION(!rv.Failed(), "Failed to lock the wakelock"); + rv.SuppressException(); + } else if (mWakeLock && !mFullScreen) { + ErrorResult rv; + mWakeLock->Unlock(rv); + mWakeLock = nullptr; + rv.SuppressException(); + } +} + +bool +nsGlobalWindow::FullScreen() const +{ + MOZ_ASSERT(IsOuterWindow()); + + NS_ENSURE_TRUE(mDocShell, mFullScreen); + + // Get the fullscreen value of the root window, to always have the value + // accurate, even when called from content. + nsCOMPtr<nsIDocShellTreeItem> rootItem; + mDocShell->GetRootTreeItem(getter_AddRefs(rootItem)); + if (rootItem == mDocShell) { + if (!XRE_IsContentProcess()) { + // We are the root window. Return our internal value. + return mFullScreen; + } + if (nsCOMPtr<nsIWidget> widget = GetNearestWidget()) { + // We are in content process, figure out the value from + // the sizemode of the puppet widget. + return widget->SizeMode() == nsSizeMode_Fullscreen; + } + return false; + } + + nsCOMPtr<nsPIDOMWindowOuter> window = rootItem->GetWindow(); + NS_ENSURE_TRUE(window, mFullScreen); + + return nsGlobalWindow::Cast(window)->FullScreen(); +} + +bool +nsGlobalWindow::GetFullScreenOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + return FullScreen(); +} + +bool +nsGlobalWindow::GetFullScreen(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetFullScreenOuter, (), aError, false); +} + +bool +nsGlobalWindow::GetFullScreen() +{ + FORWARD_TO_INNER(GetFullScreen, (), false); + + ErrorResult dummy; + bool fullscreen = GetFullScreen(dummy); + dummy.SuppressException(); + return fullscreen; +} + +void +nsGlobalWindow::Dump(const nsAString& aStr) +{ + if (!nsContentUtils::DOMWindowDumpEnabled()) { + return; + } + + char *cstr = ToNewUTF8String(aStr); + +#if defined(XP_MACOSX) + // have to convert \r to \n so that printing to the console works + char *c = cstr, *cEnd = cstr + strlen(cstr); + while (c < cEnd) { + if (*c == '\r') + *c = '\n'; + c++; + } +#endif + + if (cstr) { + MOZ_LOG(nsContentUtils::DOMDumpLog(), LogLevel::Debug, ("[Window.Dump] %s", cstr)); +#ifdef XP_WIN + PrintToDebugger(cstr); +#endif +#ifdef ANDROID + __android_log_write(ANDROID_LOG_INFO, "GeckoDump", cstr); +#endif + FILE *fp = gDumpFile ? gDumpFile : stdout; + fputs(cstr, fp); + fflush(fp); + free(cstr); + } +} + +void +nsGlobalWindow::EnsureReflowFlushAndPaint() +{ + MOZ_ASSERT(IsOuterWindow()); + NS_ASSERTION(mDocShell, "EnsureReflowFlushAndPaint() called with no " + "docshell!"); + + if (!mDocShell) + return; + + nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell(); + + if (!presShell) + return; + + // Flush pending reflows. + if (mDoc) { + mDoc->FlushPendingNotifications(Flush_Layout); + } + + // Unsuppress painting. + presShell->UnsuppressPainting(); +} + +// static +void +nsGlobalWindow::MakeScriptDialogTitle(nsAString& aOutTitle, + nsIPrincipal* aSubjectPrincipal) +{ + MOZ_ASSERT(aSubjectPrincipal); + + aOutTitle.Truncate(); + + // Try to get a host from the running principal -- this will do the + // right thing for javascript: and data: documents. + + nsCOMPtr<nsIURI> uri; + nsresult rv = aSubjectPrincipal->GetURI(getter_AddRefs(uri)); + // Note - The check for the current JSContext here isn't necessarily sensical. + // It's just designed to preserve existing behavior during a mass-conversion + // patch. + if (NS_SUCCEEDED(rv) && uri && nsContentUtils::GetCurrentJSContext()) { + // remove user:pass for privacy and spoof prevention + + nsCOMPtr<nsIURIFixup> fixup(do_GetService(NS_URIFIXUP_CONTRACTID)); + if (fixup) { + nsCOMPtr<nsIURI> fixedURI; + rv = fixup->CreateExposableURI(uri, getter_AddRefs(fixedURI)); + if (NS_SUCCEEDED(rv) && fixedURI) { + nsAutoCString host; + fixedURI->GetHost(host); + + if (!host.IsEmpty()) { + // if this URI has a host we'll show it. For other + // schemes (e.g. file:) we fall back to the localized + // generic string + + nsAutoCString prepath; + fixedURI->GetPrePath(prepath); + + NS_ConvertUTF8toUTF16 ucsPrePath(prepath); + const char16_t *formatStrings[] = { ucsPrePath.get() }; + nsXPIDLString tempString; + nsContentUtils::FormatLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES, + "ScriptDlgHeading", + formatStrings, + tempString); + aOutTitle = tempString; + } + } + } + } + + if (aOutTitle.IsEmpty()) { + // We didn't find a host so use the generic heading + nsXPIDLString tempString; + nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES, + "ScriptDlgGenericHeading", + tempString); + aOutTitle = tempString; + } + + // Just in case + if (aOutTitle.IsEmpty()) { + NS_WARNING("could not get ScriptDlgGenericHeading string from string bundle"); + aOutTitle.AssignLiteral("[Script]"); + } +} + +bool +nsGlobalWindow::CanMoveResizeWindows(bool aCallerIsChrome) +{ + MOZ_ASSERT(IsOuterWindow()); + + // When called from chrome, we can avoid the following checks. + if (!aCallerIsChrome) { + // Don't allow scripts to move or resize windows that were not opened by a + // script. + if (!mHadOriginalOpener) { + return false; + } + + if (!CanSetProperty("dom.disable_window_move_resize")) { + return false; + } + + // Ignore the request if we have more than one tab in the window. + uint32_t itemCount = 0; + if (XRE_IsContentProcess()) { + nsCOMPtr<nsIDocShell> docShell = GetDocShell(); + if (docShell) { + nsCOMPtr<nsITabChild> child = docShell->GetTabChild(); + if (child) { + child->SendGetTabCount(&itemCount); + } + } + } else { + nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner(); + if (treeOwner) { + treeOwner->GetTargetableShellCount(&itemCount); + } + } + if (itemCount > 1) { + return false; + } + } + + if (mDocShell) { + bool allow; + nsresult rv = mDocShell->GetAllowWindowControl(&allow); + if (NS_SUCCEEDED(rv) && !allow) + return false; + } + + if (gMouseDown && !gDragServiceDisabled) { + nsCOMPtr<nsIDragService> ds = + do_GetService("@mozilla.org/widget/dragservice;1"); + if (ds) { + gDragServiceDisabled = true; + ds->Suppress(); + } + } + return true; +} + +bool +nsGlobalWindow::AlertOrConfirm(bool aAlert, + const nsAString& aMessage, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + // XXX This method is very similar to nsGlobalWindow::Prompt, make + // sure any modifications here don't need to happen over there! + MOZ_ASSERT(IsOuterWindow()); + + if (!AreDialogsEnabled()) { + // Just silently return. In the case of alert(), the return value is + // ignored. In the case of confirm(), returning false is the same thing as + // would happen if the user cancels. + return false; + } + + // Reset popup state while opening a modal dialog, and firing events + // about the dialog, to prevent the current state from being active + // the whole time a modal dialog is open. + nsAutoPopupStatePusher popupStatePusher(openAbused, true); + + // Before bringing up the window, unsuppress painting and flush + // pending reflows. + EnsureReflowFlushAndPaint(); + + nsAutoString title; + MakeScriptDialogTitle(title, &aSubjectPrincipal); + + // Remove non-terminating null characters from the + // string. See bug #310037. + nsAutoString final; + nsContentUtils::StripNullChars(aMessage, final); + + nsresult rv; + nsCOMPtr<nsIPromptFactory> promptFac = + do_GetService("@mozilla.org/prompter;1", &rv); + if (NS_FAILED(rv)) { + aError.Throw(rv); + return false; + } + + nsCOMPtr<nsIPrompt> prompt; + aError = promptFac->GetPrompt(AsOuter(), NS_GET_IID(nsIPrompt), + getter_AddRefs(prompt)); + if (aError.Failed()) { + return false; + } + + // Always allow tab modal prompts for alert and confirm. + if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) { + promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"), true); + } + + bool result = false; + nsAutoSyncOperation sync(mDoc); + if (ShouldPromptToBlockDialogs()) { + bool disallowDialog = false; + nsXPIDLString label; + nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES, + "ScriptDialogLabel", label); + + aError = aAlert ? + prompt->AlertCheck(title.get(), final.get(), label.get(), + &disallowDialog) : + prompt->ConfirmCheck(title.get(), final.get(), label.get(), + &disallowDialog, &result); + + if (disallowDialog) + DisableDialogs(); + } else { + aError = aAlert ? + prompt->Alert(title.get(), final.get()) : + prompt->Confirm(title.get(), final.get(), &result); + } + + return result; +} + +void +nsGlobalWindow::Alert(nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + Alert(EmptyString(), aSubjectPrincipal, aError); +} + +void +nsGlobalWindow::AlertOuter(const nsAString& aMessage, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + AlertOrConfirm(/* aAlert = */ true, aMessage, aSubjectPrincipal, aError); +} + +void +nsGlobalWindow::Alert(const nsAString& aMessage, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(AlertOuter, (aMessage, aSubjectPrincipal, aError), + aError, ); +} + +bool +nsGlobalWindow::ConfirmOuter(const nsAString& aMessage, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + return AlertOrConfirm(/* aAlert = */ false, aMessage, aSubjectPrincipal, + aError); +} + +bool +nsGlobalWindow::Confirm(const nsAString& aMessage, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(ConfirmOuter, (aMessage, aSubjectPrincipal, aError), + aError, false); +} + +already_AddRefed<Promise> +nsGlobalWindow::Fetch(const RequestOrUSVString& aInput, + const RequestInit& aInit, ErrorResult& aRv) +{ + return FetchRequest(this, aInput, aInit, aRv); +} + +void +nsGlobalWindow::PromptOuter(const nsAString& aMessage, + const nsAString& aInitial, + nsAString& aReturn, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + // XXX This method is very similar to nsGlobalWindow::AlertOrConfirm, make + // sure any modifications here don't need to happen over there! + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + SetDOMStringToNull(aReturn); + + if (!AreDialogsEnabled()) { + // Return null, as if the user just canceled the prompt. + return; + } + + // Reset popup state while opening a modal dialog, and firing events + // about the dialog, to prevent the current state from being active + // the whole time a modal dialog is open. + nsAutoPopupStatePusher popupStatePusher(openAbused, true); + + // Before bringing up the window, unsuppress painting and flush + // pending reflows. + EnsureReflowFlushAndPaint(); + + nsAutoString title; + MakeScriptDialogTitle(title, &aSubjectPrincipal); + + // Remove non-terminating null characters from the + // string. See bug #310037. + nsAutoString fixedMessage, fixedInitial; + nsContentUtils::StripNullChars(aMessage, fixedMessage); + nsContentUtils::StripNullChars(aInitial, fixedInitial); + + nsresult rv; + nsCOMPtr<nsIPromptFactory> promptFac = + do_GetService("@mozilla.org/prompter;1", &rv); + if (NS_FAILED(rv)) { + aError.Throw(rv); + return; + } + + nsCOMPtr<nsIPrompt> prompt; + aError = promptFac->GetPrompt(AsOuter(), NS_GET_IID(nsIPrompt), + getter_AddRefs(prompt)); + if (aError.Failed()) { + return; + } + + // Always allow tab modal prompts for prompt. + if (nsCOMPtr<nsIWritablePropertyBag2> promptBag = do_QueryInterface(prompt)) { + promptBag->SetPropertyAsBool(NS_LITERAL_STRING("allowTabModal"), true); + } + + // Pass in the default value, if any. + char16_t *inoutValue = ToNewUnicode(fixedInitial); + bool disallowDialog = false; + + nsXPIDLString label; + if (ShouldPromptToBlockDialogs()) { + nsContentUtils::GetLocalizedString(nsContentUtils::eCOMMON_DIALOG_PROPERTIES, + "ScriptDialogLabel", label); + } + + nsAutoSyncOperation sync(mDoc); + bool ok; + aError = prompt->Prompt(title.get(), fixedMessage.get(), + &inoutValue, label.get(), &disallowDialog, &ok); + + if (disallowDialog) { + DisableDialogs(); + } + + if (aError.Failed()) { + return; + } + + nsAdoptingString outValue(inoutValue); + + if (ok && outValue) { + aReturn.Assign(outValue); + } +} + +void +nsGlobalWindow::Prompt(const nsAString& aMessage, const nsAString& aInitial, + nsAString& aReturn, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(PromptOuter, + (aMessage, aInitial, aReturn, aSubjectPrincipal, + aError), + aError, ); +} + +void +nsGlobalWindow::FocusOuter(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (!fm) { + return; + } + + nsCOMPtr<nsIBaseWindow> baseWin = do_QueryInterface(mDocShell); + + bool isVisible = false; + if (baseWin) { + baseWin->GetVisibility(&isVisible); + } + + if (!isVisible) { + // A hidden tab is being focused, ignore this call. + return; + } + + nsCOMPtr<nsPIDOMWindowInner> caller = do_QueryInterface(GetEntryGlobal()); + nsPIDOMWindowOuter* callerOuter = caller ? caller->GetOuterWindow() : nullptr; + nsCOMPtr<nsPIDOMWindowOuter> opener = GetOpener(); + + // Enforce dom.disable_window_flip (for non-chrome), but still allow the + // window which opened us to raise us at times when popups are allowed + // (bugs 355482 and 369306). + bool canFocus = CanSetProperty("dom.disable_window_flip") || + (opener == callerOuter && + RevisePopupAbuseLevel(gPopupControlState) < openAbused); + + nsCOMPtr<mozIDOMWindowProxy> activeDOMWindow; + fm->GetActiveWindow(getter_AddRefs(activeDOMWindow)); + + nsCOMPtr<nsIDocShellTreeItem> rootItem; + mDocShell->GetRootTreeItem(getter_AddRefs(rootItem)); + nsCOMPtr<nsPIDOMWindowOuter> rootWin = rootItem ? rootItem->GetWindow() : nullptr; + auto* activeWindow = nsPIDOMWindowOuter::From(activeDOMWindow); + bool isActive = (rootWin == activeWindow); + + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow(); + if (treeOwnerAsWin && (canFocus || isActive)) { + bool isEnabled = true; + if (NS_SUCCEEDED(treeOwnerAsWin->GetEnabled(&isEnabled)) && !isEnabled) { + NS_WARNING( "Should not try to set the focus on a disabled window" ); + return; + } + + // XXXndeakin not sure what this is for or if it should go somewhere else + nsCOMPtr<nsIEmbeddingSiteWindow> embeddingWin(do_GetInterface(treeOwnerAsWin)); + if (embeddingWin) + embeddingWin->SetFocus(); + } + + if (!mDocShell) { + return; + } + + nsCOMPtr<nsIPresShell> presShell; + // Don't look for a presshell if we're a root chrome window that's got + // about:blank loaded. We don't want to focus our widget in that case. + // XXXbz should we really be checking for IsInitialDocument() instead? + bool lookForPresShell = true; + if (mDocShell->ItemType() == nsIDocShellTreeItem::typeChrome && + GetPrivateRoot() == AsOuter() && mDoc) { + nsIURI* ourURI = mDoc->GetDocumentURI(); + if (ourURI) { + lookForPresShell = !NS_IsAboutBlank(ourURI); + } + } + + if (lookForPresShell) { + mDocShell->GetEldestPresShell(getter_AddRefs(presShell)); + } + + nsCOMPtr<nsIDocShellTreeItem> parentDsti; + mDocShell->GetParent(getter_AddRefs(parentDsti)); + + // set the parent's current focus to the frame containing this window. + nsCOMPtr<nsPIDOMWindowOuter> parent = + parentDsti ? parentDsti->GetWindow() : nullptr; + if (parent) { + nsCOMPtr<nsIDocument> parentdoc = parent->GetDoc(); + if (!parentdoc) { + return; + } + + nsIContent* frame = parentdoc->FindContentForSubDocument(mDoc); + nsCOMPtr<nsIDOMElement> frameElement = do_QueryInterface(frame); + if (frameElement) { + uint32_t flags = nsIFocusManager::FLAG_NOSCROLL; + if (canFocus) + flags |= nsIFocusManager::FLAG_RAISE; + aError = fm->SetFocus(frameElement, flags); + } + return; + } + + if (canFocus) { + // if there is no parent, this must be a toplevel window, so raise the + // window if canFocus is true. If this is a child process, the raise + // window request will get forwarded to the parent by the puppet widget. + aError = fm->SetActiveWindow(AsOuter()); + } +} + +void +nsGlobalWindow::Focus(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(FocusOuter, (aError), aError, ); +} + +nsresult +nsGlobalWindow::Focus() +{ + FORWARD_TO_INNER(Focus, (), NS_ERROR_UNEXPECTED); + + ErrorResult rv; + Focus(rv); + + return rv.StealNSResult(); +} + +void +nsGlobalWindow::BlurOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + // If dom.disable_window_flip == true, then content should not be allowed + // to call this function (this would allow popunders, bug 369306) + if (!CanSetProperty("dom.disable_window_flip")) { + return; + } + + // If embedding apps don't implement nsIEmbeddingSiteWindow, we + // shouldn't throw exceptions to web content. + + nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner(); + nsCOMPtr<nsIEmbeddingSiteWindow> siteWindow(do_GetInterface(treeOwner)); + if (siteWindow) { + // This method call may cause mDocShell to become nullptr. + siteWindow->Blur(); + + // if the root is focused, clear the focus + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm && mDoc) { + nsCOMPtr<nsIDOMElement> element; + fm->GetFocusedElementForWindow(AsOuter(), false, nullptr, getter_AddRefs(element)); + nsCOMPtr<nsIContent> content = do_QueryInterface(element); + if (content == mDoc->GetRootElement()) { + fm->ClearFocus(AsOuter()); + } + } + } +} + +void +nsGlobalWindow::Blur(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(BlurOuter, (), aError, ); +} + +void +nsGlobalWindow::BackOuter(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell)); + if (!webNav) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + aError = webNav->GoBack(); +} + +void +nsGlobalWindow::Back(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(BackOuter, (aError), aError, ); +} + +void +nsGlobalWindow::ForwardOuter(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell)); + if (!webNav) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + aError = webNav->GoForward(); +} + +void +nsGlobalWindow::Forward(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(ForwardOuter, (aError), aError, ); +} + +void +nsGlobalWindow::HomeOuter(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDocShell) { + return; + } + + nsAdoptingString homeURL = + Preferences::GetLocalizedString(PREF_BROWSER_STARTUP_HOMEPAGE); + + if (homeURL.IsEmpty()) { + // if all else fails, use this +#ifdef DEBUG_seth + printf("all else failed. using %s as the home page\n", DEFAULT_HOME_PAGE); +#endif + CopyASCIItoUTF16(DEFAULT_HOME_PAGE, homeURL); + } + +#ifdef MOZ_PHOENIX + { + // Firefox lets the user specify multiple home pages to open in + // individual tabs by separating them with '|'. Since we don't + // have the machinery in place to easily open new tabs from here, + // simply truncate the homeURL at the first '|' character to + // prevent any possibilities of leaking the users list of home + // pages to the first home page. + // + // Once bug https://bugzilla.mozilla.org/show_bug.cgi?id=221445 is + // fixed we can revisit this. + int32_t firstPipe = homeURL.FindChar('|'); + + if (firstPipe > 0) { + homeURL.Truncate(firstPipe); + } + } +#endif + + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell)); + if (!webNav) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + aError = webNav->LoadURI(homeURL.get(), + nsIWebNavigation::LOAD_FLAGS_NONE, + nullptr, + nullptr, + nullptr); +} + +void +nsGlobalWindow::Home(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(HomeOuter, (aError), aError, ); +} + +void +nsGlobalWindow::StopOuter(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(mDocShell)); + if (webNav) { + aError = webNav->Stop(nsIWebNavigation::STOP_ALL); + } +} + +void +nsGlobalWindow::Stop(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(StopOuter, (aError), aError, ); +} + +void +nsGlobalWindow::PrintOuter(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + +#ifdef NS_PRINTING + if (Preferences::GetBool("dom.disable_window_print", false)) { + aError.Throw(NS_ERROR_NOT_AVAILABLE); + return; + } + + if (!AreDialogsEnabled()) { + // We probably want to keep throwing here; silently doing nothing is a bit + // weird given the typical use cases of print(). + aError.Throw(NS_ERROR_NOT_AVAILABLE); + return; + } + + if (ShouldPromptToBlockDialogs() && !ConfirmDialogIfNeeded()) { + aError.Throw(NS_ERROR_NOT_AVAILABLE); + return; + } + + nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint; + if (NS_SUCCEEDED(GetInterface(NS_GET_IID(nsIWebBrowserPrint), + getter_AddRefs(webBrowserPrint)))) { + nsAutoSyncOperation sync(GetCurrentInnerWindowInternal() ? + GetCurrentInnerWindowInternal()->mDoc.get() : + nullptr); + + nsCOMPtr<nsIPrintSettingsService> printSettingsService = + do_GetService("@mozilla.org/gfx/printsettings-service;1"); + + nsCOMPtr<nsIPrintSettings> printSettings; + if (printSettingsService) { + bool printSettingsAreGlobal = + Preferences::GetBool("print.use_global_printsettings", false); + + if (printSettingsAreGlobal) { + printSettingsService->GetGlobalPrintSettings(getter_AddRefs(printSettings)); + + nsXPIDLString printerName; + printSettings->GetPrinterName(getter_Copies(printerName)); + if (printerName.IsEmpty()) { + printSettingsService->GetDefaultPrinterName(getter_Copies(printerName)); + printSettings->SetPrinterName(printerName); + } + printSettingsService->InitPrintSettingsFromPrinter(printerName, printSettings); + printSettingsService->InitPrintSettingsFromPrefs(printSettings, + true, + nsIPrintSettings::kInitSaveAll); + } else { + printSettingsService->GetNewPrintSettings(getter_AddRefs(printSettings)); + } + + EnterModalState(); + webBrowserPrint->Print(printSettings, nullptr); + LeaveModalState(); + + bool savePrintSettings = + Preferences::GetBool("print.save_print_settings", false); + if (printSettingsAreGlobal && savePrintSettings) { + printSettingsService-> + SavePrintSettingsToPrefs(printSettings, + true, + nsIPrintSettings::kInitSaveAll); + printSettingsService-> + SavePrintSettingsToPrefs(printSettings, + false, + nsIPrintSettings::kInitSavePrinterName); + } + } else { + webBrowserPrint->GetGlobalPrintSettings(getter_AddRefs(printSettings)); + webBrowserPrint->Print(printSettings, nullptr); + } + } +#endif //NS_PRINTING +} + +void +nsGlobalWindow::Print(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(PrintOuter, (aError), aError, ); +} + +void +nsGlobalWindow::MoveToOuter(int32_t aXPos, int32_t aYPos, ErrorResult& aError, bool aCallerIsChrome) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + /* + * If caller is not chrome and the user has not explicitly exempted the site, + * prevent window.moveTo() by exiting early + */ + + if (!CanMoveResizeWindows(aCallerIsChrome) || IsFrame()) { + return; + } + + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow(); + if (!treeOwnerAsWin) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + nsCOMPtr<nsIScreenManager> screenMgr = + do_GetService("@mozilla.org/gfx/screenmanager;1"); + nsCOMPtr<nsIScreen> screen; + if (screenMgr) { + CSSIntSize size; + GetInnerSize(size); + screenMgr->ScreenForRect(aXPos, aYPos, size.width, size.height, + getter_AddRefs(screen)); + } + + if (screen) { + // On secondary displays, the "CSS px" coordinates are offset so that they + // share their origin with global desktop pixels, to avoid ambiguities in + // the coordinate space when there are displays with different DPIs. + // (See the corresponding code in GetScreenXY() above.) + int32_t screenLeftDeskPx, screenTopDeskPx, w, h; + screen->GetRectDisplayPix(&screenLeftDeskPx, &screenTopDeskPx, &w, &h); + CSSIntPoint cssPos(aXPos - screenLeftDeskPx, aYPos - screenTopDeskPx); + CheckSecurityLeftAndTop(&cssPos.x, &cssPos.y, aCallerIsChrome); + + double scale; + screen->GetDefaultCSSScaleFactor(&scale); + LayoutDevicePoint devPos = cssPos * CSSToLayoutDeviceScale(scale); + + screen->GetContentsScaleFactor(&scale); + DesktopPoint deskPos = devPos / DesktopToLayoutDeviceScale(scale); + aError = treeOwnerAsWin->SetPositionDesktopPix(screenLeftDeskPx + deskPos.x, + screenTopDeskPx + deskPos.y); + } else { + // We couldn't find a screen? Just assume a 1:1 mapping. + CSSIntPoint cssPos(aXPos, aXPos); + CheckSecurityLeftAndTop(&cssPos.x, &cssPos.y, aCallerIsChrome); + LayoutDevicePoint devPos = cssPos * CSSToLayoutDeviceScale(1.0); + aError = treeOwnerAsWin->SetPosition(devPos.x, devPos.y); + } + + CheckForDPIChange(); +} + +void +nsGlobalWindow::MoveTo(int32_t aXPos, int32_t aYPos, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(MoveToOuter, (aXPos, aYPos, aError, nsContentUtils::IsCallerChrome()), aError, ); +} + +void +nsGlobalWindow::MoveByOuter(int32_t aXDif, int32_t aYDif, ErrorResult& aError, bool aCallerIsChrome) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + /* + * If caller is not chrome and the user has not explicitly exempted the site, + * prevent window.moveBy() by exiting early + */ + + if (!CanMoveResizeWindows(aCallerIsChrome) || IsFrame()) { + return; + } + + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow(); + if (!treeOwnerAsWin) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + // To do this correctly we have to convert what we get from GetPosition + // into CSS pixels, add the arguments, do the security check, and + // then convert back to device pixels for the call to SetPosition. + + int32_t x, y; + aError = treeOwnerAsWin->GetPosition(&x, &y); + if (aError.Failed()) { + return; + } + + // mild abuse of a "size" object so we don't need more helper functions + nsIntSize cssPos(DevToCSSIntPixels(nsIntSize(x, y))); + + cssPos.width += aXDif; + cssPos.height += aYDif; + + CheckSecurityLeftAndTop(&cssPos.width, &cssPos.height, aCallerIsChrome); + + nsIntSize newDevPos(CSSToDevIntPixels(cssPos)); + + aError = treeOwnerAsWin->SetPosition(newDevPos.width, newDevPos.height); + + CheckForDPIChange(); +} + +void +nsGlobalWindow::MoveBy(int32_t aXDif, int32_t aYDif, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(MoveByOuter, (aXDif, aYDif, aError, nsContentUtils::IsCallerChrome()), aError, ); +} + +nsresult +nsGlobalWindow::MoveBy(int32_t aXDif, int32_t aYDif) +{ + FORWARD_TO_OUTER(MoveBy, (aXDif, aYDif), NS_ERROR_UNEXPECTED); + + ErrorResult rv; + MoveByOuter(aXDif, aYDif, rv, /* aCallerIsChrome = */ true); + + return rv.StealNSResult(); +} + +void +nsGlobalWindow::ResizeToOuter(int32_t aWidth, int32_t aHeight, ErrorResult& aError, bool aCallerIsChrome) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + /* + * If caller is a browser-element then dispatch a resize event to + * the embedder. + */ + if (mDocShell && mDocShell->GetIsMozBrowserOrApp()) { + CSSIntSize size(aWidth, aHeight); + if (!DispatchResizeEvent(size)) { + // The embedder chose to prevent the default action for this + // event, so let's not resize this window after all... + return; + } + } + + /* + * If caller is not chrome and the user has not explicitly exempted the site, + * prevent window.resizeTo() by exiting early + */ + + if (!CanMoveResizeWindows(aCallerIsChrome) || IsFrame()) { + return; + } + + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow(); + if (!treeOwnerAsWin) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + nsIntSize cssSize(aWidth, aHeight); + CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerIsChrome); + + nsIntSize devSz(CSSToDevIntPixels(cssSize)); + + aError = treeOwnerAsWin->SetSize(devSz.width, devSz.height, true); + + CheckForDPIChange(); +} + +void +nsGlobalWindow::ResizeTo(int32_t aWidth, int32_t aHeight, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(ResizeToOuter, (aWidth, aHeight, aError, nsContentUtils::IsCallerChrome()), aError, ); +} + +void +nsGlobalWindow::ResizeByOuter(int32_t aWidthDif, int32_t aHeightDif, + ErrorResult& aError, bool aCallerIsChrome) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + /* + * If caller is a browser-element then dispatch a resize event to + * parent. + */ + if (mDocShell && mDocShell->GetIsMozBrowserOrApp()) { + CSSIntSize size; + if (NS_FAILED(GetInnerSize(size))) { + return; + } + + size.width += aWidthDif; + size.height += aHeightDif; + + if (!DispatchResizeEvent(size)) { + // The embedder chose to prevent the default action for this + // event, so let's not resize this window after all... + return; + } + } + + /* + * If caller is not chrome and the user has not explicitly exempted the site, + * prevent window.resizeBy() by exiting early + */ + + if (!CanMoveResizeWindows(aCallerIsChrome) || IsFrame()) { + return; + } + + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow(); + if (!treeOwnerAsWin) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + int32_t width, height; + aError = treeOwnerAsWin->GetSize(&width, &height); + if (aError.Failed()) { + return; + } + + // To do this correctly we have to convert what we got from GetSize + // into CSS pixels, add the arguments, do the security check, and + // then convert back to device pixels for the call to SetSize. + + nsIntSize cssSize(DevToCSSIntPixels(nsIntSize(width, height))); + + cssSize.width += aWidthDif; + cssSize.height += aHeightDif; + + CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerIsChrome); + + nsIntSize newDevSize(CSSToDevIntPixels(cssSize)); + + aError = treeOwnerAsWin->SetSize(newDevSize.width, newDevSize.height, true); + + CheckForDPIChange(); +} + +void +nsGlobalWindow::ResizeBy(int32_t aWidthDif, int32_t aHeightDif, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(ResizeByOuter, (aWidthDif, aHeightDif, aError, nsContentUtils::IsCallerChrome()), aError, ); +} + +void +nsGlobalWindow::SizeToContentOuter(ErrorResult& aError, bool aCallerIsChrome) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDocShell) { + return; + } + + /* + * If caller is not chrome and the user has not explicitly exempted the site, + * prevent window.sizeToContent() by exiting early + */ + + if (!CanMoveResizeWindows(aCallerIsChrome) || IsFrame()) { + return; + } + + // The content viewer does a check to make sure that it's a content + // viewer for a toplevel docshell. + nsCOMPtr<nsIContentViewer> cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + if (!cv) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + int32_t width, height; + aError = cv->GetContentSize(&width, &height); + if (aError.Failed()) { + return; + } + + // Make sure the new size is following the CheckSecurityWidthAndHeight + // rules. + nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner(); + if (!treeOwner) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + nsIntSize cssSize(DevToCSSIntPixels(nsIntSize(width, height))); + CheckSecurityWidthAndHeight(&cssSize.width, &cssSize.height, aCallerIsChrome); + + nsIntSize newDevSize(CSSToDevIntPixels(cssSize)); + + aError = treeOwner->SizeShellTo(mDocShell, newDevSize.width, + newDevSize.height); +} + +void +nsGlobalWindow::SizeToContent(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(SizeToContentOuter, (aError, nsContentUtils::IsCallerChrome()), aError, ); +} + +already_AddRefed<nsPIWindowRoot> +nsGlobalWindow::GetTopWindowRoot() +{ + nsPIDOMWindowOuter* piWin = GetPrivateRoot(); + if (!piWin) { + return nullptr; + } + + nsCOMPtr<nsPIWindowRoot> window = do_QueryInterface(piWin->GetChromeEventHandler()); + return window.forget(); +} + +void +nsGlobalWindow::Scroll(double aXScroll, double aYScroll) +{ + // Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast. + auto scrollPos = CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScroll), + mozilla::ToZeroIfNonfinite(aYScroll)); + ScrollTo(scrollPos, ScrollOptions()); +} + +void +nsGlobalWindow::ScrollTo(double aXScroll, double aYScroll) +{ + // Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast. + auto scrollPos = CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScroll), + mozilla::ToZeroIfNonfinite(aYScroll)); + ScrollTo(scrollPos, ScrollOptions()); +} + +void +nsGlobalWindow::ScrollTo(const ScrollToOptions& aOptions) +{ + FlushPendingNotifications(Flush_Layout); + nsIScrollableFrame *sf = GetScrollFrame(); + + if (sf) { + CSSIntPoint scrollPos = sf->GetScrollPositionCSSPixels(); + if (aOptions.mLeft.WasPassed()) { + scrollPos.x = mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value()); + } + if (aOptions.mTop.WasPassed()) { + scrollPos.y = mozilla::ToZeroIfNonfinite(aOptions.mTop.Value()); + } + + ScrollTo(scrollPos, aOptions); + } +} + +void +nsGlobalWindow::Scroll(const ScrollToOptions& aOptions) +{ + ScrollTo(aOptions); +} + +void +nsGlobalWindow::ScrollTo(const CSSIntPoint& aScroll, + const ScrollOptions& aOptions) +{ + FlushPendingNotifications(Flush_Layout); + nsIScrollableFrame *sf = GetScrollFrame(); + + if (sf) { + // Here we calculate what the max pixel value is that we can + // scroll to, we do this by dividing maxint with the pixel to + // twips conversion factor, and subtracting 4, the 4 comes from + // experimenting with this value, anything less makes the view + // code not scroll correctly, I have no idea why. -- jst + const int32_t maxpx = nsPresContext::AppUnitsToIntCSSPixels(0x7fffffff) - 4; + + CSSIntPoint scroll(aScroll); + if (scroll.x > maxpx) { + scroll.x = maxpx; + } + + if (scroll.y > maxpx) { + scroll.y = maxpx; + } + + bool smoothScroll = sf->GetScrollbarStyles().IsSmoothScroll(aOptions.mBehavior); + + sf->ScrollToCSSPixels(scroll, smoothScroll + ? nsIScrollableFrame::SMOOTH_MSD + : nsIScrollableFrame::INSTANT); + } +} + +void +nsGlobalWindow::ScrollBy(double aXScrollDif, double aYScrollDif) +{ + FlushPendingNotifications(Flush_Layout); + nsIScrollableFrame *sf = GetScrollFrame(); + + if (sf) { + // Convert -Inf, Inf, and NaN to 0; otherwise, convert by C-style cast. + auto scrollDif = CSSIntPoint::Truncate(mozilla::ToZeroIfNonfinite(aXScrollDif), + mozilla::ToZeroIfNonfinite(aYScrollDif)); + // It seems like it would make more sense for ScrollBy to use + // SMOOTH mode, but tests seem to depend on the synchronous behaviour. + // Perhaps Web content does too. + ScrollTo(sf->GetScrollPositionCSSPixels() + scrollDif, ScrollOptions()); + } +} + +void +nsGlobalWindow::ScrollBy(const ScrollToOptions& aOptions) +{ + FlushPendingNotifications(Flush_Layout); + nsIScrollableFrame *sf = GetScrollFrame(); + + if (sf) { + CSSIntPoint scrollPos = sf->GetScrollPositionCSSPixels(); + if (aOptions.mLeft.WasPassed()) { + scrollPos.x += mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value()); + } + if (aOptions.mTop.WasPassed()) { + scrollPos.y += mozilla::ToZeroIfNonfinite(aOptions.mTop.Value()); + } + + ScrollTo(scrollPos, aOptions); + } +} + +void +nsGlobalWindow::ScrollByLines(int32_t numLines, + const ScrollOptions& aOptions) +{ + MOZ_ASSERT(IsInnerWindow()); + + FlushPendingNotifications(Flush_Layout); + nsIScrollableFrame *sf = GetScrollFrame(); + if (sf) { + // It seems like it would make more sense for ScrollByLines to use + // SMOOTH mode, but tests seem to depend on the synchronous behaviour. + // Perhaps Web content does too. + bool smoothScroll = sf->GetScrollbarStyles().IsSmoothScroll(aOptions.mBehavior); + + sf->ScrollBy(nsIntPoint(0, numLines), nsIScrollableFrame::LINES, + smoothScroll + ? nsIScrollableFrame::SMOOTH_MSD + : nsIScrollableFrame::INSTANT); + } +} + +void +nsGlobalWindow::ScrollByPages(int32_t numPages, + const ScrollOptions& aOptions) +{ + MOZ_ASSERT(IsInnerWindow()); + + FlushPendingNotifications(Flush_Layout); + nsIScrollableFrame *sf = GetScrollFrame(); + if (sf) { + // It seems like it would make more sense for ScrollByPages to use + // SMOOTH mode, but tests seem to depend on the synchronous behaviour. + // Perhaps Web content does too. + bool smoothScroll = sf->GetScrollbarStyles().IsSmoothScroll(aOptions.mBehavior); + + sf->ScrollBy(nsIntPoint(0, numPages), nsIScrollableFrame::PAGES, + smoothScroll + ? nsIScrollableFrame::SMOOTH_MSD + : nsIScrollableFrame::INSTANT); + } +} + +void +nsGlobalWindow::MozScrollSnap() +{ + MOZ_ASSERT(IsInnerWindow()); + + FlushPendingNotifications(Flush_Layout); + nsIScrollableFrame *sf = GetScrollFrame(); + if (sf) { + sf->ScrollSnap(); + } +} + +void +nsGlobalWindow::MozRequestOverfill(OverfillCallback& aCallback, + mozilla::ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + + nsIWidget* widget = nsContentUtils::WidgetForDocument(mDoc); + if (widget) { + mozilla::layers::LayerManager* manager = widget->GetLayerManager(); + if (manager) { + manager->RequestOverfill(&aCallback); + return; + } + } + + aError.Throw(NS_ERROR_NOT_AVAILABLE); +} + +void +nsGlobalWindow::ClearTimeout(int32_t aHandle) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (aHandle > 0) { + ClearTimeoutOrInterval(aHandle, Timeout::Reason::eTimeoutOrInterval); + } +} + +void +nsGlobalWindow::ClearInterval(int32_t aHandle) +{ + if (aHandle > 0) { + ClearTimeoutOrInterval(aHandle, Timeout::Reason::eTimeoutOrInterval); + } +} + +void +nsGlobalWindow::SetResizable(bool aResizable) const +{ + // nop +} + +void +nsGlobalWindow::CaptureEvents() +{ + if (mDoc) { + mDoc->WarnOnceAbout(nsIDocument::eUseOfCaptureEvents); + } +} + +void +nsGlobalWindow::ReleaseEvents() +{ + if (mDoc) { + mDoc->WarnOnceAbout(nsIDocument::eUseOfReleaseEvents); + } +} + +static +bool IsPopupBlocked(nsIDocument* aDoc) +{ + nsCOMPtr<nsIPopupWindowManager> pm = + do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID); + + if (!pm) { + return false; + } + + if (!aDoc) { + return true; + } + + uint32_t permission = nsIPopupWindowManager::ALLOW_POPUP; + pm->TestPermission(aDoc->NodePrincipal(), &permission); + return permission == nsIPopupWindowManager::DENY_POPUP; +} + +void +nsGlobalWindow::FirePopupBlockedEvent(nsIDocument* aDoc, + nsIURI* aPopupURI, + const nsAString& aPopupWindowName, + const nsAString& aPopupWindowFeatures) +{ + MOZ_ASSERT(aDoc); + + // Fire a "DOMPopupBlocked" event so that the UI can hear about + // blocked popups. + PopupBlockedEventInit init; + init.mBubbles = true; + init.mCancelable = true; + init.mRequestingWindow = this; + init.mPopupWindowURI = aPopupURI; + init.mPopupWindowName = aPopupWindowName; + init.mPopupWindowFeatures = aPopupWindowFeatures; + + RefPtr<PopupBlockedEvent> event = + PopupBlockedEvent::Constructor(aDoc, + NS_LITERAL_STRING("DOMPopupBlocked"), + init); + + event->SetTrusted(true); + + bool defaultActionEnabled; + aDoc->DispatchEvent(event, &defaultActionEnabled); +} + +// static +bool +nsGlobalWindow::CanSetProperty(const char *aPrefName) +{ + // Chrome can set any property. + if (nsContentUtils::LegacyIsCallerChromeOrNativeCode()) { + return true; + } + + // If the pref is set to true, we can not set the property + // and vice versa. + return !Preferences::GetBool(aPrefName, true); +} + +bool +nsGlobalWindow::PopupWhitelisted() +{ + if (!IsPopupBlocked(mDoc)) + return true; + + nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent(); + if (parent == AsOuter()) + { + return false; + } + + return nsGlobalWindow::Cast(parent)->PopupWhitelisted(); +} + +/* + * Examine the current document state to see if we're in a way that is + * typically abused by web designers. The window.open code uses this + * routine to determine whether to allow the new window. + * Returns a value from the PopupControlState enum. + */ +PopupControlState +nsGlobalWindow::RevisePopupAbuseLevel(PopupControlState aControl) +{ + MOZ_ASSERT(IsOuterWindow()); + + NS_ASSERTION(mDocShell, "Must have docshell"); + + if (mDocShell->ItemType() != nsIDocShellTreeItem::typeContent) { + return openAllowed; + } + + PopupControlState abuse = aControl; + switch (abuse) { + case openControlled: + case openAbused: + case openOverridden: + if (PopupWhitelisted()) + abuse = PopupControlState(abuse - 1); + break; + case openAllowed: break; + default: + NS_WARNING("Strange PopupControlState!"); + } + + // limit the number of simultaneously open popups + if (abuse == openAbused || abuse == openControlled) { + int32_t popupMax = Preferences::GetInt("dom.popup_maximum", -1); + if (popupMax >= 0 && gOpenPopupSpamCount >= popupMax) + abuse = openOverridden; + } + + return abuse; +} + +/* If a window open is blocked, fire the appropriate DOM events. */ +void +nsGlobalWindow::FireAbuseEvents(const nsAString &aPopupURL, + const nsAString &aPopupWindowName, + const nsAString &aPopupWindowFeatures) +{ + // fetch the URI of the window requesting the opened window + + nsCOMPtr<nsPIDOMWindowOuter> window = GetTop(); + if (!window) { + return; + } + + nsCOMPtr<nsIDocument> topDoc = window->GetDoc(); + nsCOMPtr<nsIURI> popupURI; + + // build the URI of the would-have-been popup window + // (see nsWindowWatcher::URIfromURL) + + // first, fetch the opener's base URI + + nsIURI *baseURL = nullptr; + + nsCOMPtr<nsIDocument> doc = GetEntryDocument(); + if (doc) + baseURL = doc->GetDocBaseURI(); + + // use the base URI to build what would have been the popup's URI + nsCOMPtr<nsIIOService> ios(do_GetService(NS_IOSERVICE_CONTRACTID)); + if (ios) + ios->NewURI(NS_ConvertUTF16toUTF8(aPopupURL), 0, baseURL, + getter_AddRefs(popupURI)); + + // fire an event chock full of informative URIs + FirePopupBlockedEvent(topDoc, popupURI, aPopupWindowName, + aPopupWindowFeatures); +} + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::OpenOuter(const nsAString& aUrl, const nsAString& aName, + const nsAString& aOptions, ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + nsCOMPtr<nsPIDOMWindowOuter> window; + aError = OpenJS(aUrl, aName, aOptions, getter_AddRefs(window)); + return window.forget(); +} + + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::Open(const nsAString& aUrl, const nsAString& aName, + const nsAString& aOptions, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(OpenOuter, (aUrl, aName, aOptions, aError), aError, + nullptr); +} + +nsresult +nsGlobalWindow::Open(const nsAString& aUrl, const nsAString& aName, + const nsAString& aOptions, nsIDocShellLoadInfo* aLoadInfo, + bool aForceNoOpener, nsPIDOMWindowOuter **_retval) +{ + FORWARD_TO_OUTER(Open, (aUrl, aName, aOptions, aLoadInfo, aForceNoOpener, + _retval), + NS_ERROR_NOT_INITIALIZED); + return OpenInternal(aUrl, aName, aOptions, + false, // aDialog + false, // aContentModal + true, // aCalledNoScript + false, // aDoJSFixups + true, // aNavigate + nullptr, nullptr, // No args + aLoadInfo, + aForceNoOpener, + _retval); +} + +nsresult +nsGlobalWindow::OpenJS(const nsAString& aUrl, const nsAString& aName, + const nsAString& aOptions, nsPIDOMWindowOuter **_retval) +{ + MOZ_ASSERT(IsOuterWindow()); + return OpenInternal(aUrl, aName, aOptions, + false, // aDialog + false, // aContentModal + false, // aCalledNoScript + true, // aDoJSFixups + true, // aNavigate + nullptr, nullptr, // No args + nullptr, // aLoadInfo + false, // aForceNoOpener + _retval); +} + +// like Open, but attaches to the new window any extra parameters past +// [features] as a JS property named "arguments" +nsresult +nsGlobalWindow::OpenDialog(const nsAString& aUrl, const nsAString& aName, + const nsAString& aOptions, + nsISupports* aExtraArgument, + nsPIDOMWindowOuter** _retval) +{ + MOZ_ASSERT(IsOuterWindow()); + return OpenInternal(aUrl, aName, aOptions, + true, // aDialog + false, // aContentModal + true, // aCalledNoScript + false, // aDoJSFixups + true, // aNavigate + nullptr, aExtraArgument, // Arguments + nullptr, // aLoadInfo + false, // aForceNoOpener + _retval); +} + +// Like Open, but passes aNavigate=false. +/* virtual */ nsresult +nsGlobalWindow::OpenNoNavigate(const nsAString& aUrl, + const nsAString& aName, + const nsAString& aOptions, + nsPIDOMWindowOuter **_retval) +{ + MOZ_ASSERT(IsOuterWindow()); + return OpenInternal(aUrl, aName, aOptions, + false, // aDialog + false, // aContentModal + true, // aCalledNoScript + false, // aDoJSFixups + false, // aNavigate + nullptr, nullptr, // No args + nullptr, // aLoadInfo + false, // aForceNoOpener + _retval); + +} + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::OpenDialogOuter(JSContext* aCx, const nsAString& aUrl, + const nsAString& aName, const nsAString& aOptions, + const Sequence<JS::Value>& aExtraArgument, + ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsIJSArgArray> argvArray; + aError = NS_CreateJSArgv(aCx, aExtraArgument.Length(), + aExtraArgument.Elements(), + getter_AddRefs(argvArray)); + if (aError.Failed()) { + return nullptr; + } + + nsCOMPtr<nsPIDOMWindowOuter> dialog; + aError = OpenInternal(aUrl, aName, aOptions, + true, // aDialog + false, // aContentModal + false, // aCalledNoScript + false, // aDoJSFixups + true, // aNavigate + argvArray, nullptr, // Arguments + nullptr, // aLoadInfo + false, // aForceNoOpener + getter_AddRefs(dialog)); + return dialog.forget(); +} + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::OpenDialog(JSContext* aCx, const nsAString& aUrl, + const nsAString& aName, const nsAString& aOptions, + const Sequence<JS::Value>& aExtraArgument, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(OpenDialogOuter, + (aCx, aUrl, aName, aOptions, aExtraArgument, aError), + aError, nullptr); +} + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::GetFramesOuter() +{ + RefPtr<nsPIDOMWindowOuter> frames(AsOuter()); + FlushPendingNotifications(Flush_ContentAndNotify); + return frames.forget(); +} + +already_AddRefed<nsPIDOMWindowOuter> +nsGlobalWindow::GetFrames(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetFramesOuter, (), aError, nullptr); +} + +nsGlobalWindow* +nsGlobalWindow::CallerInnerWindow() +{ + JSContext *cx = nsContentUtils::GetCurrentJSContext(); + NS_ENSURE_TRUE(cx, nullptr); + nsIGlobalObject* global = GetIncumbentGlobal(); + NS_ENSURE_TRUE(global, nullptr); + JS::Rooted<JSObject*> scope(cx, global->GetGlobalJSObject()); + NS_ENSURE_TRUE(scope, nullptr); + + // When Jetpack runs content scripts inside a sandbox, it uses + // sandboxPrototype to make them appear as though they're running in the + // scope of the page. So when a content script invokes postMessage, it expects + // the |source| of the received message to be the window set as the + // sandboxPrototype. This used to work incidentally for unrelated reasons, but + // now we need to do some special handling to support it. + if (xpc::IsSandbox(scope)) { + JSAutoCompartment ac(cx, scope); + JS::Rooted<JSObject*> scopeProto(cx); + bool ok = JS_GetPrototype(cx, scope, &scopeProto); + NS_ENSURE_TRUE(ok, nullptr); + if (scopeProto && xpc::IsSandboxPrototypeProxy(scopeProto) && + (scopeProto = js::CheckedUnwrap(scopeProto, /* stopAtWindowProxy = */ false))) + { + global = xpc::NativeGlobal(scopeProto); + NS_ENSURE_TRUE(global, nullptr); + } + } + + // The calling window must be holding a reference, so we can return a weak + // pointer. + nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(global); + return nsGlobalWindow::Cast(win); +} + +void +nsGlobalWindow::PostMessageMozOuter(JSContext* aCx, JS::Handle<JS::Value> aMessage, + const nsAString& aTargetOrigin, + JS::Handle<JS::Value> aTransfer, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + // + // Window.postMessage is an intentional subversion of the same-origin policy. + // As such, this code must be particularly careful in the information it + // exposes to calling code. + // + // http://www.whatwg.org/specs/web-apps/current-work/multipage/section-crossDocumentMessages.html + // + + // First, get the caller's window + RefPtr<nsGlobalWindow> callerInnerWin = CallerInnerWindow(); + nsIPrincipal* callerPrin; + if (callerInnerWin) { + MOZ_ASSERT(callerInnerWin->IsInnerWindow(), + "should have gotten an inner window here"); + + // Compute the caller's origin either from its principal or, in the case the + // principal doesn't carry a URI (e.g. the system principal), the caller's + // document. We must get this now instead of when the event is created and + // dispatched, because ultimately it is the identity of the calling window + // *now* that determines who sent the message (and not an identity which might + // have changed due to intervening navigations). + callerPrin = callerInnerWin->GetPrincipal(); + } + else { + // In case the global is not a window, it can be a sandbox, and the sandbox's + // principal can be used for the security check. + nsIGlobalObject* global = GetIncumbentGlobal(); + NS_ASSERTION(global, "Why is there no global object?"); + callerPrin = global->PrincipalOrNull(); + } + if (!callerPrin) { + return; + } + + nsCOMPtr<nsIURI> callerOuterURI; + if (NS_FAILED(callerPrin->GetURI(getter_AddRefs(callerOuterURI)))) { + return; + } + + nsAutoString origin; + if (callerOuterURI) { + // if the principal has a URI, use that to generate the origin + nsContentUtils::GetUTFOrigin(callerPrin, origin); + } + else if (callerInnerWin) { + // otherwise use the URI of the document to generate origin + nsCOMPtr<nsIDocument> doc = callerInnerWin->GetExtantDoc(); + if (!doc) { + return; + } + callerOuterURI = doc->GetDocumentURI(); + // if the principal has a URI, use that to generate the origin + nsContentUtils::GetUTFOrigin(callerOuterURI, origin); + } + else { + // in case of a sandbox with a system principal origin can be empty + if (!nsContentUtils::IsSystemPrincipal(callerPrin)) { + return; + } + } + + // Convert the provided origin string into a URI for comparison purposes. + nsCOMPtr<nsIPrincipal> providedPrincipal; + + if (aTargetOrigin.EqualsASCII("/")) { + providedPrincipal = callerPrin; + } + // "*" indicates no specific origin is required. + else if (!aTargetOrigin.EqualsASCII("*")) { + nsCOMPtr<nsIURI> originURI; + if (NS_FAILED(NS_NewURI(getter_AddRefs(originURI), aTargetOrigin))) { + aError.Throw(NS_ERROR_DOM_SYNTAX_ERR); + return; + } + + if (NS_FAILED(originURI->SetUserPass(EmptyCString())) || + NS_FAILED(originURI->SetPath(EmptyCString()))) { + return; + } + + PrincipalOriginAttributes attrs = + BasePrincipal::Cast(&aSubjectPrincipal)->OriginAttributesRef(); + if (aSubjectPrincipal.GetIsSystemPrincipal()) { + auto principal = BasePrincipal::Cast(GetPrincipal()); + + if (attrs != principal->OriginAttributesRef()) { + nsCOMPtr<nsIURI> targetURI; + nsAutoCString targetURL; + nsAutoCString sourceOrigin; + nsAutoCString targetOrigin; + + if (NS_FAILED(principal->GetURI(getter_AddRefs(targetURI))) || + NS_FAILED(targetURI->GetAsciiSpec(targetURL)) || + NS_FAILED(principal->GetOrigin(targetOrigin)) || + NS_FAILED(aSubjectPrincipal.GetOrigin(sourceOrigin))) { + NS_WARNING("Failed to get source and target origins"); + return; + } + + nsContentUtils::LogSimpleConsoleError( + NS_ConvertUTF8toUTF16(nsPrintfCString( + "Attempting to post a message to window with url \"%s\" and " + "origin \"%s\" from a system principal scope with mismatched " + "origin \"%s\".", + targetURL.get(), targetOrigin.get(), sourceOrigin.get())), + "DOM"); + + attrs = principal->OriginAttributesRef(); + } + } + + // Create a nsIPrincipal inheriting the app/browser attributes from the + // caller. + providedPrincipal = BasePrincipal::CreateCodebasePrincipal(originURI, attrs); + if (NS_WARN_IF(!providedPrincipal)) { + return; + } + } + + // Create and asynchronously dispatch a runnable which will handle actual DOM + // event creation and dispatch. + RefPtr<PostMessageEvent> event = + new PostMessageEvent(nsContentUtils::IsCallerChrome() || !callerInnerWin + ? nullptr + : callerInnerWin->GetOuterWindowInternal(), + origin, + this, + providedPrincipal, + callerInnerWin + ? callerInnerWin->GetDoc() + : nullptr, + nsContentUtils::IsCallerChrome()); + + JS::Rooted<JS::Value> message(aCx, aMessage); + JS::Rooted<JS::Value> transfer(aCx, aTransfer); + + event->Write(aCx, message, transfer, JS::CloneDataPolicy(), aError); + if (NS_WARN_IF(aError.Failed())) { + return; + } + + aError = NS_DispatchToCurrentThread(event); +} + +void +nsGlobalWindow::PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage, + const nsAString& aTargetOrigin, + JS::Handle<JS::Value> aTransfer, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(PostMessageMozOuter, + (aCx, aMessage, aTargetOrigin, aTransfer, + aSubjectPrincipal, aError), + aError, ); +} + +void +nsGlobalWindow::PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage, + const nsAString& aTargetOrigin, + const Optional<Sequence<JS::Value>>& aTransfer, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + JS::Rooted<JS::Value> transferArray(aCx, JS::UndefinedValue()); + if (aTransfer.WasPassed()) { + const Sequence<JS::Value >& values = aTransfer.Value(); + + // The input sequence only comes from the generated bindings code, which + // ensures it is rooted. + JS::HandleValueArray elements = + JS::HandleValueArray::fromMarkedLocation(values.Length(), values.Elements()); + + transferArray = JS::ObjectOrNullValue(JS_NewArrayObject(aCx, elements)); + if (transferArray.isNull()) { + aError.Throw(NS_ERROR_OUT_OF_MEMORY); + return; + } + } + + PostMessageMoz(aCx, aMessage, aTargetOrigin, transferArray, + aSubjectPrincipal, aError); +} + +class nsCloseEvent : public Runnable { + + RefPtr<nsGlobalWindow> mWindow; + bool mIndirect; + + nsCloseEvent(nsGlobalWindow *aWindow, bool aIndirect) + : mWindow(aWindow) + , mIndirect(aIndirect) + {} + +public: + + static nsresult + PostCloseEvent(nsGlobalWindow* aWindow, bool aIndirect) { + nsCOMPtr<nsIRunnable> ev = new nsCloseEvent(aWindow, aIndirect); + nsresult rv = NS_DispatchToCurrentThread(ev); + if (NS_SUCCEEDED(rv)) + aWindow->MaybeForgiveSpamCount(); + return rv; + } + + NS_IMETHOD Run() override { + if (mWindow) { + if (mIndirect) { + return PostCloseEvent(mWindow, false); + } + mWindow->ReallyCloseWindow(); + } + return NS_OK; + } + +}; + +bool +nsGlobalWindow::CanClose() +{ + MOZ_ASSERT(IsOuterWindow()); + + if (mIsChrome) { + nsCOMPtr<nsIBrowserDOMWindow> bwin; + nsIDOMChromeWindow* chromeWin = static_cast<nsGlobalChromeWindow*>(this); + chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin)); + + bool canClose = true; + if (bwin && NS_SUCCEEDED(bwin->CanClose(&canClose))) { + return canClose; + } + } + + if (!mDocShell) { + return true; + } + + // Ask the content viewer whether the toplevel window can close. + // If the content viewer returns false, it is responsible for calling + // Close() as soon as it is possible for the window to close. + // This allows us to not close the window while printing is happening. + + nsCOMPtr<nsIContentViewer> cv; + mDocShell->GetContentViewer(getter_AddRefs(cv)); + if (cv) { + bool canClose; + nsresult rv = cv->PermitUnload(&canClose); + if (NS_SUCCEEDED(rv) && !canClose) + return false; + + rv = cv->RequestWindowClose(&canClose); + if (NS_SUCCEEDED(rv) && !canClose) + return false; + } + + return true; +} + +void +nsGlobalWindow::CloseOuter(bool aTrustedCaller) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDocShell || IsInModalState() || + (IsFrame() && !mDocShell->GetIsMozBrowserOrApp())) { + // window.close() is called on a frame in a frameset, on a window + // that's already closed, or on a window for which there's + // currently a modal dialog open. Ignore such calls. + return; + } + + if (mHavePendingClose) { + // We're going to be closed anyway; do nothing since we don't want + // to double-close + return; + } + + if (mBlockScriptedClosingFlag) + { + // A script's popup has been blocked and we don't want + // the window to be closed directly after this event, + // so the user can see that there was a blocked popup. + return; + } + + // Don't allow scripts from content to close non-app or non-neterror + // windows that were not opened by script. + nsAutoString url; + nsresult rv = mDoc->GetURL(url); + NS_ENSURE_SUCCESS_VOID(rv); + + if (!mDocShell->GetIsApp() && + !StringBeginsWith(url, NS_LITERAL_STRING("about:neterror")) && + !mHadOriginalOpener && !aTrustedCaller) { + bool allowClose = mAllowScriptsToClose || + Preferences::GetBool("dom.allow_scripts_to_close_windows", true); + if (!allowClose) { + // We're blocking the close operation + // report localized error msg in JS console + nsContentUtils::ReportToConsole( + nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("DOM Window"), mDoc, // Better name for the category? + nsContentUtils::eDOM_PROPERTIES, + "WindowCloseBlockedWarning"); + + return; + } + } + + if (!mInClose && !mIsClosed && !CanClose()) { + return; + } + + // Fire a DOM event notifying listeners that this window is about to + // be closed. The tab UI code may choose to cancel the default + // action for this event, if so, we won't actually close the window + // (since the tab UI code will close the tab in stead). Sure, this + // could be abused by content code, but do we care? I don't think + // so... + + bool wasInClose = mInClose; + mInClose = true; + + if (!DispatchCustomEvent(NS_LITERAL_STRING("DOMWindowClose"))) { + // Someone chose to prevent the default action for this event, if + // so, let's not close this window after all... + + mInClose = wasInClose; + return; + } + + FinalClose(); +} + +void +nsGlobalWindow::Close(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(CloseOuter, (nsContentUtils::IsCallerChrome()), aError, ); +} + +nsresult +nsGlobalWindow::Close() +{ + FORWARD_TO_OUTER(Close, (), NS_ERROR_UNEXPECTED); + CloseOuter(/* aTrustedCaller = */ true); + return NS_OK; +} + +void +nsGlobalWindow::ForceClose() +{ + MOZ_ASSERT(IsOuterWindow()); + MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Default); + + if (IsFrame() || !mDocShell) { + // This may be a frame in a frameset, or a window that's already closed. + // Ignore such calls. + return; + } + + if (mHavePendingClose) { + // We're going to be closed anyway; do nothing since we don't want + // to double-close + return; + } + + mInClose = true; + + DispatchCustomEvent(NS_LITERAL_STRING("DOMWindowClose")); + + FinalClose(); +} + +void +nsGlobalWindow::FinalClose() +{ + MOZ_ASSERT(IsOuterWindow()); + + // Flag that we were closed. + mIsClosed = true; + + // If we get here from CloseOuter then it means that the parent process is + // going to close our window for us. It's just important to set mIsClosed. + if (XRE_GetProcessType() == GeckoProcessType_Content) { + return; + } + + // This stuff is non-sensical but incredibly fragile. The reasons for the + // behavior here don't make sense today and may not have ever made sense, + // but various bits of frontend code break when you change them. If you need + // to fix up this behavior, feel free to. It's a righteous task, but involves + // wrestling with various download manager tests, frontend code, and possible + // broken addons. The chrome tests in toolkit/mozapps/downloads are a good + // testing ground. + // + // In particular, if some inner of |win| is the entry global, we must + // complete _two_ round-trips to the event loop before the call to + // ReallyCloseWindow. This allows setTimeout handlers that are set after + // FinalClose() is called to run before the window is torn down. + nsCOMPtr<nsPIDOMWindowInner> entryWindow = + do_QueryInterface(GetEntryGlobal()); + bool indirect = + entryWindow && entryWindow->GetOuterWindow() == this->AsOuter(); + if (NS_FAILED(nsCloseEvent::PostCloseEvent(this, indirect))) { + ReallyCloseWindow(); + } else { + mHavePendingClose = true; + } +} + + +void +nsGlobalWindow::ReallyCloseWindow() +{ + FORWARD_TO_OUTER_VOID(ReallyCloseWindow, ()); + + // Make sure we never reenter this method. + mHavePendingClose = true; + + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = GetTreeOwnerWindow(); + + // If there's no treeOwnerAsWin, this window must already be closed. + + if (treeOwnerAsWin) { + + // but if we're a browser window we could be in some nasty + // self-destroying cascade that we should mostly ignore + + if (mDocShell) { + nsCOMPtr<nsIBrowserDOMWindow> bwin; + nsCOMPtr<nsIDocShellTreeItem> rootItem; + mDocShell->GetRootTreeItem(getter_AddRefs(rootItem)); + nsCOMPtr<nsPIDOMWindowOuter> rootWin = + rootItem ? rootItem->GetWindow() : nullptr; + nsCOMPtr<nsIDOMChromeWindow> chromeWin(do_QueryInterface(rootWin)); + if (chromeWin) + chromeWin->GetBrowserDOMWindow(getter_AddRefs(bwin)); + + if (rootWin) { + /* Normally we destroy the entire window, but not if + this DOM window belongs to a tabbed browser and doesn't + correspond to a tab. This allows a well-behaved tab + to destroy the container as it should but is a final measure + to prevent an errant tab from doing so when it shouldn't. + This works because we reach this code when we shouldn't only + in the particular circumstance that we belong to a tab + that has just been closed (and is therefore already missing + from the list of browsers) (and has an unload handler + that closes the window). */ + // XXXbz now that we have mHavePendingClose, is this needed? + bool isTab = false; + if (rootWin == AsOuter() || + !bwin || (bwin->IsTabContentWindow(GetOuterWindowInternal(), + &isTab), isTab)) + treeOwnerAsWin->Destroy(); + } + } + + CleanUp(); + } +} + +void +nsGlobalWindow::EnterModalState() +{ + MOZ_ASSERT(IsOuterWindow(), "Modal state is maintained on outer windows"); + + // GetScriptableTop, not GetTop, so that EnterModalState works properly with + // <iframe mozbrowser>. + nsGlobalWindow* topWin = GetScriptableTopInternal(); + + if (!topWin) { + NS_ERROR("Uh, EnterModalState() called w/o a reachable top window?"); + return; + } + + // If there is an active ESM in this window, clear it. Otherwise, this can + // cause a problem if a modal state is entered during a mouseup event. + EventStateManager* activeESM = + static_cast<EventStateManager*>( + EventStateManager::GetActiveEventStateManager()); + if (activeESM && activeESM->GetPresContext()) { + nsIPresShell* activeShell = activeESM->GetPresContext()->GetPresShell(); + if (activeShell && ( + nsContentUtils::ContentIsCrossDocDescendantOf(activeShell->GetDocument(), mDoc) || + nsContentUtils::ContentIsCrossDocDescendantOf(mDoc, activeShell->GetDocument()))) { + EventStateManager::ClearGlobalActiveContent(activeESM); + + activeShell->SetCapturingContent(nullptr, 0); + + if (activeShell) { + RefPtr<nsFrameSelection> frameSelection = activeShell->FrameSelection(); + frameSelection->SetDragState(false); + } + } + } + + // If there are any drag and drop operations in flight, try to end them. + nsCOMPtr<nsIDragService> ds = + do_GetService("@mozilla.org/widget/dragservice;1"); + if (ds) { + ds->EndDragSession(true); + } + + // Clear the capturing content if it is under topDoc. + // Usually the activeESM check above does that, but there are cases when + // we don't have activeESM, or it is for different document. + nsIDocument* topDoc = topWin->GetExtantDoc(); + nsIContent* capturingContent = nsIPresShell::GetCapturingContent(); + if (capturingContent && topDoc && + nsContentUtils::ContentIsCrossDocDescendantOf(capturingContent, topDoc)) { + nsIPresShell::SetCapturingContent(nullptr, 0); + } + + if (topWin->mModalStateDepth == 0) { + NS_ASSERTION(!topWin->mSuspendedDoc, "Shouldn't have mSuspendedDoc here!"); + + topWin->mSuspendedDoc = topDoc; + if (topDoc) { + topDoc->SuppressEventHandling(nsIDocument::eAnimationsOnly); + } + + nsGlobalWindow* inner = topWin->GetCurrentInnerWindowInternal(); + if (inner) { + topWin->GetCurrentInnerWindowInternal()->Suspend(); + } + } + topWin->mModalStateDepth++; +} + +void +nsGlobalWindow::LeaveModalState() +{ + MOZ_ASSERT(IsOuterWindow(), "Modal state is maintained on outer windows"); + + nsGlobalWindow* topWin = GetScriptableTopInternal(); + + if (!topWin) { + NS_ERROR("Uh, LeaveModalState() called w/o a reachable top window?"); + return; + } + + MOZ_ASSERT(topWin->mModalStateDepth != 0); + MOZ_ASSERT(IsSuspended()); + MOZ_ASSERT(topWin->IsSuspended()); + topWin->mModalStateDepth--; + + nsGlobalWindow* inner = topWin->GetCurrentInnerWindowInternal(); + + if (topWin->mModalStateDepth == 0) { + if (inner) { + inner->Resume(); + } + + if (topWin->mSuspendedDoc) { + nsCOMPtr<nsIDocument> currentDoc = topWin->GetExtantDoc(); + topWin->mSuspendedDoc->UnsuppressEventHandlingAndFireEvents(nsIDocument::eAnimationsOnly, + currentDoc == topWin->mSuspendedDoc); + topWin->mSuspendedDoc = nullptr; + } + } + + // Remember the time of the last dialog quit. + if (inner) { + inner->mLastDialogQuitTime = TimeStamp::Now(); + } + + if (topWin->mModalStateDepth == 0) { + RefPtr<Event> event = NS_NewDOMEvent(inner, nullptr, nullptr); + event->InitEvent(NS_LITERAL_STRING("endmodalstate"), true, false); + event->SetTrusted(true); + event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = true; + bool dummy; + topWin->DispatchEvent(event, &dummy); + } +} + +bool +nsGlobalWindow::IsInModalState() +{ + nsGlobalWindow *topWin = GetScriptableTopInternal(); + + if (!topWin) { + // IsInModalState() getting called w/o a reachable top window is a bit + // iffy, but valid enough not to make noise about it. See bug 404828 + return false; + } + + return topWin->mModalStateDepth != 0; +} + +// static +void +nsGlobalWindow::NotifyDOMWindowDestroyed(nsGlobalWindow* aWindow) { + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + if (observerService) { + observerService-> + NotifyObservers(ToSupports(aWindow), + DOM_WINDOW_DESTROYED_TOPIC, nullptr); + } +} + +// Try to match compartments that are not web content by matching compartments +// with principals that are either the system principal or an expanded principal. +// This may not return true for all non-web-content compartments. +struct BrowserCompartmentMatcher : public js::CompartmentFilter { + virtual bool match(JSCompartment* c) const override + { + nsCOMPtr<nsIPrincipal> pc = nsJSPrincipals::get(JS_GetCompartmentPrincipals(c)); + return nsContentUtils::IsSystemOrExpandedPrincipal(pc); + } +}; + + +class WindowDestroyedEvent : public Runnable +{ +public: + WindowDestroyedEvent(nsIDOMWindow* aWindow, uint64_t aID, + const char* aTopic) : + mID(aID), mTopic(aTopic) + { + mWindow = do_GetWeakReference(aWindow); + } + + NS_IMETHOD Run() override + { + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + if (observerService) { + nsCOMPtr<nsISupportsPRUint64> wrapper = + do_CreateInstance(NS_SUPPORTS_PRUINT64_CONTRACTID); + if (wrapper) { + wrapper->SetData(mID); + observerService->NotifyObservers(wrapper, mTopic.get(), nullptr); + } + } + + bool skipNukeCrossCompartment = false; +#ifndef DEBUG + nsCOMPtr<nsIAppStartup> appStartup = + do_GetService(NS_APPSTARTUP_CONTRACTID); + + if (appStartup) { + appStartup->GetShuttingDown(&skipNukeCrossCompartment); + } +#endif + + nsCOMPtr<nsISupports> window = do_QueryReferent(mWindow); + if (!skipNukeCrossCompartment && window) { + nsGlobalWindow* win = nsGlobalWindow::FromSupports(window); + nsGlobalWindow* currentInner = win->IsInnerWindow() ? win : win->GetCurrentInnerWindowInternal(); + NS_ENSURE_TRUE(currentInner, NS_OK); + + AutoSafeJSContext cx; + JS::Rooted<JSObject*> obj(cx, currentInner->FastGetGlobalJSObject()); + // We only want to nuke wrappers for the chrome->content case + if (obj && !js::IsSystemCompartment(js::GetObjectCompartment(obj))) { + js::NukeCrossCompartmentWrappers(cx, + BrowserCompartmentMatcher(), + js::SingleCompartment(js::GetObjectCompartment(obj)), + win->IsInnerWindow() ? js::DontNukeWindowReferences + : js::NukeWindowReferences); + } + } + + return NS_OK; + } + +private: + uint64_t mID; + nsCString mTopic; + nsWeakPtr mWindow; +}; + +void +nsGlobalWindow::NotifyWindowIDDestroyed(const char* aTopic) +{ + nsCOMPtr<nsIRunnable> runnable = new WindowDestroyedEvent(this, mWindowID, aTopic); + nsresult rv = NS_DispatchToCurrentThread(runnable); + if (NS_SUCCEEDED(rv)) { + mNotifiedIDDestroyed = true; + } +} + +// static +void +nsGlobalWindow::NotifyDOMWindowFrozen(nsGlobalWindow* aWindow) { + if (aWindow && aWindow->IsInnerWindow()) { + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + if (observerService) { + observerService-> + NotifyObservers(ToSupports(aWindow), + DOM_WINDOW_FROZEN_TOPIC, nullptr); + } + } +} + +// static +void +nsGlobalWindow::NotifyDOMWindowThawed(nsGlobalWindow* aWindow) { + if (aWindow && aWindow->IsInnerWindow()) { + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + if (observerService) { + observerService-> + NotifyObservers(ToSupports(aWindow), + DOM_WINDOW_THAWED_TOPIC, nullptr); + } + } +} + +JSObject* +nsGlobalWindow::GetCachedXBLPrototypeHandler(nsXBLPrototypeHandler* aKey) +{ + JS::Rooted<JSObject*> handler(RootingCx()); + if (mCachedXBLPrototypeHandlers) { + mCachedXBLPrototypeHandlers->Get(aKey, handler.address()); + } + return handler; +} + +void +nsGlobalWindow::CacheXBLPrototypeHandler(nsXBLPrototypeHandler* aKey, + JS::Handle<JSObject*> aHandler) +{ + if (!mCachedXBLPrototypeHandlers) { + mCachedXBLPrototypeHandlers = new XBLPrototypeHandlerTable(); + PreserveWrapper(ToSupports(this)); + } + + mCachedXBLPrototypeHandlers->Put(aKey, aHandler); +} + +Element* +nsGlobalWindow::GetFrameElementOuter(nsIPrincipal& aSubjectPrincipal) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDocShell || mDocShell->GetIsMozBrowserOrApp()) { + return nullptr; + } + + // Per HTML5, the frameElement getter returns null in cross-origin situations. + Element* element = GetRealFrameElementOuter(); + if (!element) { + return nullptr; + } + + if (!aSubjectPrincipal.SubsumesConsideringDomain(element->NodePrincipal())) { + return nullptr; + } + + return element; +} + +Element* +nsGlobalWindow::GetFrameElement(nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetFrameElementOuter, (aSubjectPrincipal), aError, + nullptr); +} + +Element* +nsGlobalWindow::GetRealFrameElementOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDocShell) { + return nullptr; + } + + nsCOMPtr<nsIDocShell> parent; + mDocShell->GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)); + + if (!parent || parent == mDocShell) { + // We're at a chrome boundary, don't expose the chrome iframe + // element to content code. + return nullptr; + } + + return mFrameElement; +} + +Element* +nsGlobalWindow::GetRealFrameElement(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetRealFrameElementOuter, (), aError, nullptr); +} + +/** + * nsIGlobalWindow::GetFrameElement (when called from C++) is just a wrapper + * around GetRealFrameElement. + */ +already_AddRefed<nsIDOMElement> +nsGlobalWindow::GetFrameElement() +{ + FORWARD_TO_INNER(GetFrameElement, (), nullptr); + + ErrorResult dummy; + nsCOMPtr<nsIDOMElement> frameElement = + do_QueryInterface(GetRealFrameElement(dummy)); + dummy.SuppressException(); + return frameElement.forget(); +} + +/* static */ bool +nsGlobalWindow::TokenizeDialogOptions(nsAString& aToken, + nsAString::const_iterator& aIter, + nsAString::const_iterator aEnd) +{ + while (aIter != aEnd && nsCRT::IsAsciiSpace(*aIter)) { + ++aIter; + } + + if (aIter == aEnd) { + return false; + } + + if (*aIter == ';' || *aIter == ':' || *aIter == '=') { + aToken.Assign(*aIter); + ++aIter; + return true; + } + + nsAString::const_iterator start = aIter; + + // Skip characters until we find whitespace, ';', ':', or '=' + while (aIter != aEnd && !nsCRT::IsAsciiSpace(*aIter) && + *aIter != ';' && + *aIter != ':' && + *aIter != '=') { + ++aIter; + } + + aToken.Assign(Substring(start, aIter)); + return true; +} + +// Helper for converting window.showModalDialog() options (list of ';' +// separated name (:|=) value pairs) to a format that's parsable by +// our normal window opening code. + +/* static */ +void +nsGlobalWindow::ConvertDialogOptions(const nsAString& aOptions, + nsAString& aResult) +{ + nsAString::const_iterator end; + aOptions.EndReading(end); + + nsAString::const_iterator iter; + aOptions.BeginReading(iter); + + nsAutoString token; + nsAutoString name; + nsAutoString value; + + while (true) { + if (!TokenizeDialogOptions(name, iter, end)) { + break; + } + + // Invalid name. + if (name.EqualsLiteral("=") || + name.EqualsLiteral(":") || + name.EqualsLiteral(";")) { + break; + } + + if (!TokenizeDialogOptions(token, iter, end)) { + break; + } + + if (!token.EqualsLiteral(":") && !token.EqualsLiteral("=")) { + continue; + } + + // We found name followed by ':' or '='. Look for a value. + if (!TokenizeDialogOptions(value, iter, end)) { + break; + } + + if (name.LowerCaseEqualsLiteral("center")) { + if (value.LowerCaseEqualsLiteral("on") || + value.LowerCaseEqualsLiteral("yes") || + value.LowerCaseEqualsLiteral("1")) { + aResult.AppendLiteral(",centerscreen=1"); + } + } else if (name.LowerCaseEqualsLiteral("dialogwidth")) { + if (!value.IsEmpty()) { + aResult.AppendLiteral(",width="); + aResult.Append(value); + } + } else if (name.LowerCaseEqualsLiteral("dialogheight")) { + if (!value.IsEmpty()) { + aResult.AppendLiteral(",height="); + aResult.Append(value); + } + } else if (name.LowerCaseEqualsLiteral("dialogtop")) { + if (!value.IsEmpty()) { + aResult.AppendLiteral(",top="); + aResult.Append(value); + } + } else if (name.LowerCaseEqualsLiteral("dialogleft")) { + if (!value.IsEmpty()) { + aResult.AppendLiteral(",left="); + aResult.Append(value); + } + } else if (name.LowerCaseEqualsLiteral("resizable")) { + if (value.LowerCaseEqualsLiteral("on") || + value.LowerCaseEqualsLiteral("yes") || + value.LowerCaseEqualsLiteral("1")) { + aResult.AppendLiteral(",resizable=1"); + } + } else if (name.LowerCaseEqualsLiteral("scroll")) { + if (value.LowerCaseEqualsLiteral("off") || + value.LowerCaseEqualsLiteral("no") || + value.LowerCaseEqualsLiteral("0")) { + aResult.AppendLiteral(",scrollbars=0"); + } + } + + if (iter == end || + !TokenizeDialogOptions(token, iter, end) || + !token.EqualsLiteral(";")) { + break; + } + } +} + +already_AddRefed<nsIVariant> +nsGlobalWindow::ShowModalDialogOuter(const nsAString& aUrl, + nsIVariant* aArgument, + const nsAString& aOptions, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (mDoc) { + mDoc->WarnOnceAbout(nsIDocument::eShowModalDialog); + } + + if (!IsShowModalDialogEnabled()) { + aError.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + RefPtr<DialogValueHolder> argHolder = + new DialogValueHolder(&aSubjectPrincipal, aArgument); + + // Before bringing up the window/dialog, unsuppress painting and flush + // pending reflows. + EnsureReflowFlushAndPaint(); + + if (!AreDialogsEnabled()) { + // We probably want to keep throwing here; silently doing nothing is a bit + // weird given the typical use cases of showModalDialog(). + aError.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + if (ShouldPromptToBlockDialogs() && !ConfirmDialogIfNeeded()) { + aError.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + nsCOMPtr<nsPIDOMWindowOuter> dlgWin; + nsAutoString options(NS_LITERAL_STRING("-moz-internal-modal=1,status=1")); + + ConvertDialogOptions(aOptions, options); + + options.AppendLiteral(",scrollbars=1,centerscreen=1,resizable=0"); + + EnterModalState(); + uint32_t oldMicroTaskLevel = nsContentUtils::MicroTaskLevel(); + nsContentUtils::SetMicroTaskLevel(0); + aError = OpenInternal(aUrl, EmptyString(), options, + false, // aDialog + true, // aContentModal + true, // aCalledNoScript + true, // aDoJSFixups + true, // aNavigate + nullptr, argHolder, // args + nullptr, // aLoadInfo + false, // aForceNoOpener + getter_AddRefs(dlgWin)); + nsContentUtils::SetMicroTaskLevel(oldMicroTaskLevel); + LeaveModalState(); + if (aError.Failed()) { + return nullptr; + } + + nsCOMPtr<nsIDOMModalContentWindow> dialog = do_QueryInterface(dlgWin); + if (!dialog) { + return nullptr; + } + + nsCOMPtr<nsIVariant> retVal; + aError = dialog->GetReturnValue(getter_AddRefs(retVal)); + MOZ_ASSERT(!aError.Failed()); + + return retVal.forget(); +} + +already_AddRefed<nsIVariant> +nsGlobalWindow::ShowModalDialog(const nsAString& aUrl, nsIVariant* aArgument, + const nsAString& aOptions, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(ShowModalDialogOuter, + (aUrl, aArgument, aOptions, aSubjectPrincipal, + aError), aError, nullptr); +} + +void +nsGlobalWindow::ShowModalDialog(JSContext* aCx, const nsAString& aUrl, + JS::Handle<JS::Value> aArgument, + const nsAString& aOptions, + JS::MutableHandle<JS::Value> aRetval, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + + nsCOMPtr<nsIVariant> args; + aError = nsContentUtils::XPConnect()->JSToVariant(aCx, + aArgument, + getter_AddRefs(args)); + if (aError.Failed()) { + return; + } + + nsCOMPtr<nsIVariant> retVal = + ShowModalDialog(aUrl, args, aOptions, aSubjectPrincipal, aError); + if (aError.Failed()) { + return; + } + + JS::Rooted<JS::Value> result(aCx); + if (retVal) { + aError = nsContentUtils::XPConnect()->VariantToJS(aCx, + FastGetGlobalJSObject(), + retVal, aRetval); + } else { + aRetval.setNull(); + } +} + +class ChildCommandDispatcher : public Runnable +{ +public: + ChildCommandDispatcher(nsGlobalWindow* aWindow, + nsITabChild* aTabChild, + const nsAString& aAction) + : mWindow(aWindow), mTabChild(aTabChild), mAction(aAction) {} + + NS_IMETHOD Run() override + { + nsCOMPtr<nsPIWindowRoot> root = mWindow->GetTopWindowRoot(); + if (!root) { + return NS_OK; + } + + nsTArray<nsCString> enabledCommands, disabledCommands; + root->GetEnabledDisabledCommands(enabledCommands, disabledCommands); + if (enabledCommands.Length() || disabledCommands.Length()) { + mTabChild->EnableDisableCommands(mAction, enabledCommands, disabledCommands); + } + + return NS_OK; + } + +private: + RefPtr<nsGlobalWindow> mWindow; + nsCOMPtr<nsITabChild> mTabChild; + nsString mAction; +}; + +class CommandDispatcher : public Runnable +{ +public: + CommandDispatcher(nsIDOMXULCommandDispatcher* aDispatcher, + const nsAString& aAction) + : mDispatcher(aDispatcher), mAction(aAction) {} + + NS_IMETHOD Run() override + { + return mDispatcher->UpdateCommands(mAction); + } + + nsCOMPtr<nsIDOMXULCommandDispatcher> mDispatcher; + nsString mAction; +}; + +nsresult +nsGlobalWindow::UpdateCommands(const nsAString& anAction, nsISelection* aSel, int16_t aReason) +{ + // If this is a child process, redirect to the parent process. + if (nsIDocShell* docShell = GetDocShell()) { + if (nsCOMPtr<nsITabChild> child = docShell->GetTabChild()) { + nsContentUtils::AddScriptRunner(new ChildCommandDispatcher(this, child, + anAction)); + return NS_OK; + } + } + + nsPIDOMWindowOuter *rootWindow = nsGlobalWindow::GetPrivateRoot(); + if (!rootWindow) + return NS_OK; + + nsCOMPtr<nsIDOMXULDocument> xulDoc = + do_QueryInterface(rootWindow->GetExtantDoc()); + // See if we contain a XUL document. + // selectionchange action is only used for mozbrowser, not for XUL. So we bypass + // XUL command dispatch if anAction is "selectionchange". + if (xulDoc && !anAction.EqualsLiteral("selectionchange")) { + // Retrieve the command dispatcher and call updateCommands on it. + nsCOMPtr<nsIDOMXULCommandDispatcher> xulCommandDispatcher; + xulDoc->GetCommandDispatcher(getter_AddRefs(xulCommandDispatcher)); + if (xulCommandDispatcher) { + nsContentUtils::AddScriptRunner(new CommandDispatcher(xulCommandDispatcher, + anAction)); + } + } + + return NS_OK; +} + +ThrottledEventQueue* +nsGlobalWindow::GetThrottledEventQueue() +{ + // We must have an outer to access the TabGroup. + nsGlobalWindow* outer = GetOuterWindowInternal(); + if (!outer) { + return nullptr; + } + + return TabGroup()->GetThrottledEventQueue(); +} + +Selection* +nsGlobalWindow::GetSelectionOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDocShell) { + return nullptr; + } + + nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell(); + if (!presShell) { + return nullptr; + } + nsISelection* domSelection = + presShell->GetCurrentSelection(SelectionType::eNormal); + return domSelection ? domSelection->AsSelection() : nullptr; +} + +Selection* +nsGlobalWindow::GetSelection(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetSelectionOuter, (), aError, nullptr); +} + +already_AddRefed<nsISelection> +nsGlobalWindow::GetSelection() +{ + nsCOMPtr<nsISelection> selection = GetSelectionOuter(); + return selection.forget(); +} + +bool +nsGlobalWindow::FindOuter(const nsAString& aString, bool aCaseSensitive, + bool aBackwards, bool aWrapAround, bool aWholeWord, + bool aSearchInFrames, bool aShowDialog, + ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (Preferences::GetBool("dom.disable_window_find", false)) { + aError.Throw(NS_ERROR_NOT_AVAILABLE); + return false; + } + + nsCOMPtr<nsIWebBrowserFind> finder(do_GetInterface(mDocShell)); + if (!finder) { + aError.Throw(NS_ERROR_NOT_AVAILABLE); + return false; + } + + // Set the options of the search + aError = finder->SetSearchString(PromiseFlatString(aString).get()); + if (aError.Failed()) { + return false; + } + finder->SetMatchCase(aCaseSensitive); + finder->SetFindBackwards(aBackwards); + finder->SetWrapFind(aWrapAround); + finder->SetEntireWord(aWholeWord); + finder->SetSearchFrames(aSearchInFrames); + + // the nsIWebBrowserFind is initialized to use this window + // as the search root, but uses focus to set the current search + // frame. If we're being called from JS (as here), this window + // should be the current search frame. + nsCOMPtr<nsIWebBrowserFindInFrames> framesFinder(do_QueryInterface(finder)); + if (framesFinder) { + framesFinder->SetRootSearchFrame(AsOuter()); // paranoia + framesFinder->SetCurrentSearchFrame(AsOuter()); + } + + // The Find API does not accept empty strings. Launch the Find Dialog. + if (aString.IsEmpty() || aShowDialog) { + // See if the find dialog is already up using nsIWindowMediator + nsCOMPtr<nsIWindowMediator> windowMediator = + do_GetService(NS_WINDOWMEDIATOR_CONTRACTID); + + nsCOMPtr<mozIDOMWindowProxy> findDialog; + + if (windowMediator) { + windowMediator->GetMostRecentWindow(u"findInPage", + getter_AddRefs(findDialog)); + } + + if (findDialog) { + // The Find dialog is already open, bring it to the top. + auto* piFindDialog = nsPIDOMWindowOuter::From(findDialog); + aError = piFindDialog->Focus(); + } else if (finder) { + // Open a Find dialog + nsCOMPtr<nsPIDOMWindowOuter> dialog; + aError = OpenDialog(NS_LITERAL_STRING("chrome://global/content/finddialog.xul"), + NS_LITERAL_STRING("_blank"), + NS_LITERAL_STRING("chrome, resizable=no, dependent=yes"), + finder, getter_AddRefs(dialog)); + } + + return false; + } + + // Launch the search with the passed in search string + bool didFind = false; + aError = finder->FindNext(&didFind); + return didFind; +} + +bool +nsGlobalWindow::Find(const nsAString& aString, bool aCaseSensitive, + bool aBackwards, bool aWrapAround, bool aWholeWord, + bool aSearchInFrames, bool aShowDialog, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(FindOuter, + (aString, aCaseSensitive, aBackwards, aWrapAround, + aWholeWord, aSearchInFrames, aShowDialog, aError), + aError, false); +} + +void +nsGlobalWindow::Atob(const nsAString& aAsciiBase64String, + nsAString& aBinaryData, ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + aError = nsContentUtils::Atob(aAsciiBase64String, aBinaryData); +} + +void +nsGlobalWindow::Btoa(const nsAString& aBinaryData, + nsAString& aAsciiBase64String, ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + aError = nsContentUtils::Btoa(aBinaryData, aAsciiBase64String); +} + +//***************************************************************************** +// nsGlobalWindow::nsIDOMEventTarget +//***************************************************************************** + +nsPIDOMWindowOuter* +nsGlobalWindow::GetOwnerGlobalForBindings() +{ + if (IsOuterWindow()) { + return AsOuter(); + } + + return nsPIDOMWindowOuter::GetFromCurrentInner(AsInner()); +} + +NS_IMETHODIMP +nsGlobalWindow::RemoveEventListener(const nsAString& aType, + nsIDOMEventListener* aListener, + bool aUseCapture) +{ + if (RefPtr<EventListenerManager> elm = GetExistingListenerManager()) { + elm->RemoveEventListener(aType, aListener, aUseCapture); + } + return NS_OK; +} + +NS_IMPL_REMOVE_SYSTEM_EVENT_LISTENER(nsGlobalWindow) + +NS_IMETHODIMP +nsGlobalWindow::DispatchEvent(nsIDOMEvent* aEvent, bool* aRetVal) +{ + FORWARD_TO_INNER(DispatchEvent, (aEvent, aRetVal), NS_OK); + + if (!AsInner()->IsCurrentInnerWindow()) { + NS_WARNING("DispatchEvent called on non-current inner window, dropping. " + "Please check the window in the caller instead."); + return NS_ERROR_FAILURE; + } + + if (!mDoc) { + return NS_ERROR_FAILURE; + } + + // Obtain a presentation shell + nsIPresShell *shell = mDoc->GetShell(); + RefPtr<nsPresContext> presContext; + if (shell) { + // Retrieve the context + presContext = shell->GetPresContext(); + } + + nsEventStatus status = nsEventStatus_eIgnore; + nsresult rv = EventDispatcher::DispatchDOMEvent(AsInner(), nullptr, aEvent, + presContext, &status); + + *aRetVal = (status != nsEventStatus_eConsumeNoDefault); + return rv; +} + +NS_IMETHODIMP +nsGlobalWindow::AddEventListener(const nsAString& aType, + nsIDOMEventListener *aListener, + bool aUseCapture, bool aWantsUntrusted, + uint8_t aOptionalArgc) +{ + NS_ASSERTION(!aWantsUntrusted || aOptionalArgc > 1, + "Won't check if this is chrome, you want to set " + "aWantsUntrusted to false or make the aWantsUntrusted " + "explicit by making optional_argc non-zero."); + + if (!aWantsUntrusted && + (aOptionalArgc < 2 && !nsContentUtils::IsChromeDoc(mDoc))) { + aWantsUntrusted = true; + } + + EventListenerManager* manager = GetOrCreateListenerManager(); + NS_ENSURE_STATE(manager); + manager->AddEventListener(aType, aListener, aUseCapture, aWantsUntrusted); + return NS_OK; +} + +void +nsGlobalWindow::AddEventListener(const nsAString& aType, + EventListener* aListener, + const AddEventListenerOptionsOrBoolean& aOptions, + const Nullable<bool>& aWantsUntrusted, + ErrorResult& aRv) +{ + if (IsOuterWindow() && mInnerWindow && + !nsContentUtils::CanCallerAccess(mInnerWindow)) { + aRv.Throw(NS_ERROR_DOM_SECURITY_ERR); + return; + } + + bool wantsUntrusted; + if (aWantsUntrusted.IsNull()) { + wantsUntrusted = !nsContentUtils::IsChromeDoc(mDoc); + } else { + wantsUntrusted = aWantsUntrusted.Value(); + } + + EventListenerManager* manager = GetOrCreateListenerManager(); + if (!manager) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return; + } + + manager->AddEventListener(aType, aListener, aOptions, wantsUntrusted); +} + +NS_IMETHODIMP +nsGlobalWindow::AddSystemEventListener(const nsAString& aType, + nsIDOMEventListener *aListener, + bool aUseCapture, + bool aWantsUntrusted, + uint8_t aOptionalArgc) +{ + NS_ASSERTION(!aWantsUntrusted || aOptionalArgc > 1, + "Won't check if this is chrome, you want to set " + "aWantsUntrusted to false or make the aWantsUntrusted " + "explicit by making optional_argc non-zero."); + + if (IsOuterWindow() && mInnerWindow && + !nsContentUtils::LegacyIsCallerNativeCode() && + !nsContentUtils::CanCallerAccess(mInnerWindow)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + if (!aWantsUntrusted && + (aOptionalArgc < 2 && !nsContentUtils::IsChromeDoc(mDoc))) { + aWantsUntrusted = true; + } + + return NS_AddSystemEventListener(this, aType, aListener, aUseCapture, + aWantsUntrusted); +} + +EventListenerManager* +nsGlobalWindow::GetOrCreateListenerManager() +{ + FORWARD_TO_INNER_CREATE(GetOrCreateListenerManager, (), nullptr); + + if (!mListenerManager) { + mListenerManager = + new EventListenerManager(static_cast<EventTarget*>(this)); + } + + return mListenerManager; +} + +EventListenerManager* +nsGlobalWindow::GetExistingListenerManager() const +{ + FORWARD_TO_INNER(GetExistingListenerManager, (), nullptr); + + return mListenerManager; +} + +nsIScriptContext* +nsGlobalWindow::GetContextForEventHandlers(nsresult* aRv) +{ + *aRv = NS_ERROR_UNEXPECTED; + NS_ENSURE_TRUE(!IsInnerWindow() || AsInner()->IsCurrentInnerWindow(), nullptr); + + nsIScriptContext* scx; + if ((scx = GetContext())) { + *aRv = NS_OK; + return scx; + } + return nullptr; +} + +//***************************************************************************** +// nsGlobalWindow::nsPIDOMWindow +//***************************************************************************** + +nsPIDOMWindowOuter* +nsGlobalWindow::GetPrivateParent() +{ + MOZ_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent(); + + if (AsOuter() == parent) { + nsCOMPtr<nsIContent> chromeElement(do_QueryInterface(mChromeEventHandler)); + if (!chromeElement) + return nullptr; // This is ok, just means a null parent. + + nsIDocument* doc = chromeElement->GetComposedDoc(); + if (!doc) + return nullptr; // This is ok, just means a null parent. + + return doc->GetWindow(); + } + + return parent; +} + +nsPIDOMWindowOuter* +nsGlobalWindow::GetPrivateRoot() +{ + if (IsInnerWindow()) { + nsGlobalWindow* outer = GetOuterWindowInternal(); + if (!outer) { + NS_WARNING("No outer window available!"); + return nullptr; + } + return outer->GetPrivateRoot(); + } + + nsCOMPtr<nsPIDOMWindowOuter> top = GetTop(); + + nsCOMPtr<nsIContent> chromeElement(do_QueryInterface(mChromeEventHandler)); + if (chromeElement) { + nsIDocument* doc = chromeElement->GetComposedDoc(); + if (doc) { + nsCOMPtr<nsPIDOMWindowOuter> parent = doc->GetWindow(); + if (parent) { + top = parent->GetTop(); + } + } + } + + return top; +} + +Location* +nsGlobalWindow::GetLocation(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + nsIDocShell *docShell = GetDocShell(); + if (!mLocation && docShell) { + mLocation = new Location(AsInner(), docShell); + } + return mLocation; +} + +nsIDOMLocation* +nsGlobalWindow::GetLocation() +{ + FORWARD_TO_INNER(GetLocation, (), nullptr); + + ErrorResult dummy; + nsIDOMLocation* location = GetLocation(dummy); + dummy.SuppressException(); + return location; +} + +void +nsGlobalWindow::ActivateOrDeactivate(bool aActivate) +{ + MOZ_ASSERT(IsOuterWindow()); + + if (!mDoc) { + return; + } + + // Set / unset mIsActive on the top level window, which is used for the + // :-moz-window-inactive pseudoclass, and its sheet (if any). + nsCOMPtr<nsIWidget> mainWidget = GetMainWidget(); + nsCOMPtr<nsIWidget> topLevelWidget; + if (mainWidget) { + // Get the top level widget (if the main widget is a sheet, this will + // be the sheet's top (non-sheet) parent). + topLevelWidget = mainWidget->GetSheetWindowParent(); + if (!topLevelWidget) { + topLevelWidget = mainWidget; + } + } + + SetActive(aActivate); + + if (mainWidget != topLevelWidget) { + // This is a workaround for the following problem: + // When a window with an open sheet gains or loses focus, only the sheet + // window receives the NS_ACTIVATE/NS_DEACTIVATE event. However the + // styling of the containing top level window also needs to change. We + // get around this by calling nsPIDOMWindow::SetActive() on both windows. + + // Get the top level widget's nsGlobalWindow + nsCOMPtr<nsPIDOMWindowOuter> topLevelWindow; + + // widgetListener should be a nsXULWindow + nsIWidgetListener* listener = topLevelWidget->GetWidgetListener(); + if (listener) { + nsCOMPtr<nsIXULWindow> window = listener->GetXULWindow(); + nsCOMPtr<nsIInterfaceRequestor> req(do_QueryInterface(window)); + topLevelWindow = do_GetInterface(req); + } + + if (topLevelWindow) { + topLevelWindow->SetActive(aActivate); + } + } +} + +static bool +NotifyDocumentTree(nsIDocument* aDocument, void* aData) +{ + aDocument->EnumerateSubDocuments(NotifyDocumentTree, nullptr); + aDocument->DocumentStatesChanged(NS_DOCUMENT_STATE_WINDOW_INACTIVE); + return true; +} + +void +nsGlobalWindow::SetActive(bool aActive) +{ + nsPIDOMWindow::SetActive(aActive); + if (mDoc) { + NotifyDocumentTree(mDoc, nullptr); + } +} + +bool +nsGlobalWindow::IsTopLevelWindowActive() +{ + nsCOMPtr<nsIDocShellTreeItem> treeItem(GetDocShell()); + if (!treeItem) { + return false; + } + + nsCOMPtr<nsIDocShellTreeItem> rootItem; + treeItem->GetRootTreeItem(getter_AddRefs(rootItem)); + if (!rootItem) { + return false; + } + + nsCOMPtr<nsPIDOMWindowOuter> domWindow = rootItem->GetWindow(); + return domWindow && domWindow->IsActive(); +} + +void nsGlobalWindow::SetIsBackground(bool aIsBackground) +{ + MOZ_ASSERT(IsOuterWindow()); + + bool resetTimers = (!aIsBackground && AsOuter()->IsBackground()); + nsPIDOMWindow::SetIsBackground(aIsBackground); + if (resetTimers) { + ResetTimersForThrottleReduction(gMinBackgroundTimeoutValue); + } + + if (!aIsBackground) { + nsGlobalWindow* inner = GetCurrentInnerWindowInternal(); + if (inner) { + inner->UnthrottleIdleCallbackRequests(); + } + } +#ifdef MOZ_GAMEPAD + if (!aIsBackground) { + nsGlobalWindow* inner = GetCurrentInnerWindowInternal(); + if (inner) { + inner->SyncGamepadState(); + } + } +#endif +} + +void nsGlobalWindow::MaybeUpdateTouchState() +{ + FORWARD_TO_INNER_VOID(MaybeUpdateTouchState, ()); + + if (mMayHaveTouchEventListener) { + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + + if (observerService) { + observerService->NotifyObservers(static_cast<nsIDOMWindow*>(this), + DOM_TOUCH_LISTENER_ADDED, + nullptr); + } + } +} + +void +nsGlobalWindow::EnableGamepadUpdates() +{ + MOZ_ASSERT(IsInnerWindow()); + + if (mHasGamepad) { +#ifdef MOZ_GAMEPAD + RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService()); + if (gamepadManager) { + gamepadManager->AddListener(this); + } +#endif + } +} + +void +nsGlobalWindow::DisableGamepadUpdates() +{ + MOZ_ASSERT(IsInnerWindow()); + + if (mHasGamepad) { +#ifdef MOZ_GAMEPAD + RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService()); + if (gamepadManager) { + gamepadManager->RemoveListener(this); + } +#endif + } +} + +void +nsGlobalWindow::EnableVRUpdates() +{ + MOZ_ASSERT(IsInnerWindow()); + + if (mHasVREvents && !mVREventObserver) { + mVREventObserver = new VREventObserver(this); + } +} + +void +nsGlobalWindow::DisableVRUpdates() +{ + MOZ_ASSERT(IsInnerWindow()); + + mVREventObserver = nullptr; +} + +void +nsGlobalWindow::SetChromeEventHandler(EventTarget* aChromeEventHandler) +{ + MOZ_ASSERT(IsOuterWindow()); + + SetChromeEventHandlerInternal(aChromeEventHandler); + // update the chrome event handler on all our inner windows + for (nsGlobalWindow *inner = (nsGlobalWindow *)PR_LIST_HEAD(this); + inner != this; + inner = (nsGlobalWindow*)PR_NEXT_LINK(inner)) { + NS_ASSERTION(!inner->mOuterWindow || inner->mOuterWindow == AsOuter(), + "bad outer window pointer"); + inner->SetChromeEventHandlerInternal(aChromeEventHandler); + } +} + +static bool IsLink(nsIContent* aContent) +{ + return aContent && (aContent->IsHTMLElement(nsGkAtoms::a) || + aContent->AttrValueIs(kNameSpaceID_XLink, nsGkAtoms::type, + nsGkAtoms::simple, eCaseMatters)); +} + +static bool ShouldShowFocusRingIfFocusedByMouse(nsIContent* aNode) +{ + if (!aNode) { + return true; + } + return !IsLink(aNode) && + !aNode->IsAnyOfHTMLElements(nsGkAtoms::video, nsGkAtoms::audio); +} + +void +nsGlobalWindow::SetFocusedNode(nsIContent* aNode, + uint32_t aFocusMethod, + bool aNeedsFocus) +{ + FORWARD_TO_INNER_VOID(SetFocusedNode, (aNode, aFocusMethod, aNeedsFocus)); + + if (aNode && aNode->GetComposedDoc() != mDoc) { + NS_WARNING("Trying to set focus to a node from a wrong document"); + return; + } + + if (mCleanedUp) { + NS_ASSERTION(!aNode, "Trying to focus cleaned up window!"); + aNode = nullptr; + aNeedsFocus = false; + } + if (mFocusedNode != aNode) { + UpdateCanvasFocus(false, aNode); + mFocusedNode = aNode; + mFocusMethod = aFocusMethod & FOCUSMETHOD_MASK; + mShowFocusRingForContent = false; + } + + if (mFocusedNode) { + // if a node was focused by a keypress, turn on focus rings for the + // window. + if (mFocusMethod & nsIFocusManager::FLAG_BYKEY) { + mFocusByKeyOccurred = true; + } else if ( + // otherwise, we set mShowFocusRingForContent, as we don't want this to + // be permanent for the window. On Windows, focus rings are only shown + // when the FLAG_SHOWRING flag is used. On other platforms, focus rings + // are only visible on some elements. +#ifndef XP_WIN + !(mFocusMethod & nsIFocusManager::FLAG_BYMOUSE) || + ShouldShowFocusRingIfFocusedByMouse(aNode) || +#endif + aFocusMethod & nsIFocusManager::FLAG_SHOWRING) { + mShowFocusRingForContent = true; + } + } + + if (aNeedsFocus) + mNeedsFocus = aNeedsFocus; +} + +uint32_t +nsGlobalWindow::GetFocusMethod() +{ + FORWARD_TO_INNER(GetFocusMethod, (), 0); + + return mFocusMethod; +} + +bool +nsGlobalWindow::ShouldShowFocusRing() +{ + FORWARD_TO_INNER(ShouldShowFocusRing, (), false); + + if (mShowFocusRingForContent || mFocusByKeyOccurred) { + return true; + } + + nsCOMPtr<nsPIWindowRoot> root = GetTopWindowRoot(); + return root ? root->ShowFocusRings() : false; +} + +void +nsGlobalWindow::SetKeyboardIndicators(UIStateChangeType aShowAccelerators, + UIStateChangeType aShowFocusRings) +{ + MOZ_ASSERT(IsOuterWindow()); + + nsPIDOMWindowOuter* piWin = GetPrivateRoot(); + if (!piWin) { + return; + } + + MOZ_ASSERT(piWin == AsOuter()); + + bool oldShouldShowFocusRing = ShouldShowFocusRing(); + + // only change the flags that have been modified + nsCOMPtr<nsPIWindowRoot> windowRoot = do_QueryInterface(mChromeEventHandler); + if (!windowRoot) { + return; + } + + if (aShowAccelerators != UIStateChangeType_NoChange) { + windowRoot->SetShowAccelerators(aShowAccelerators == UIStateChangeType_Set); + } + if (aShowFocusRings != UIStateChangeType_NoChange) { + windowRoot->SetShowFocusRings(aShowFocusRings == UIStateChangeType_Set); + } + + nsContentUtils::SetKeyboardIndicatorsOnRemoteChildren(GetOuterWindow(), + aShowAccelerators, + aShowFocusRings); + + bool newShouldShowFocusRing = ShouldShowFocusRing(); + if (mHasFocus && mFocusedNode && + oldShouldShowFocusRing != newShouldShowFocusRing && + mFocusedNode->IsElement()) { + // Update mFocusedNode's state. + if (newShouldShowFocusRing) { + mFocusedNode->AsElement()->AddStates(NS_EVENT_STATE_FOCUSRING); + } else { + mFocusedNode->AsElement()->RemoveStates(NS_EVENT_STATE_FOCUSRING); + } + } +} + +bool +nsGlobalWindow::TakeFocus(bool aFocus, uint32_t aFocusMethod) +{ + FORWARD_TO_INNER(TakeFocus, (aFocus, aFocusMethod), false); + + if (mCleanedUp) { + return false; + } + + if (aFocus) + mFocusMethod = aFocusMethod & FOCUSMETHOD_MASK; + + if (mHasFocus != aFocus) { + mHasFocus = aFocus; + UpdateCanvasFocus(true, mFocusedNode); + } + + // if mNeedsFocus is true, then the document has not yet received a + // document-level focus event. If there is a root content node, then return + // true to tell the calling focus manager that a focus event is expected. If + // there is no root content node, the document hasn't loaded enough yet, or + // there isn't one and there is no point in firing a focus event. + if (aFocus && mNeedsFocus && mDoc && mDoc->GetRootElement() != nullptr) { + mNeedsFocus = false; + return true; + } + + mNeedsFocus = false; + return false; +} + +void +nsGlobalWindow::SetReadyForFocus() +{ + FORWARD_TO_INNER_VOID(SetReadyForFocus, ()); + + bool oldNeedsFocus = mNeedsFocus; + mNeedsFocus = false; + + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->WindowShown(GetOuterWindow(), oldNeedsFocus); + } +} + +void +nsGlobalWindow::PageHidden() +{ + FORWARD_TO_INNER_VOID(PageHidden, ()); + + // the window is being hidden, so tell the focus manager that the frame is + // no longer valid. Use the persisted field to determine if the document + // is being destroyed. + + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + fm->WindowHidden(GetOuterWindow()); + } + + mNeedsFocus = true; +} + +class HashchangeCallback : public Runnable +{ +public: + HashchangeCallback(const nsAString &aOldURL, + const nsAString &aNewURL, + nsGlobalWindow* aWindow) + : mWindow(aWindow) + { + MOZ_ASSERT(mWindow); + MOZ_ASSERT(mWindow->IsInnerWindow()); + mOldURL.Assign(aOldURL); + mNewURL.Assign(aNewURL); + } + + NS_IMETHOD Run() override + { + NS_PRECONDITION(NS_IsMainThread(), "Should be called on the main thread."); + return mWindow->FireHashchange(mOldURL, mNewURL); + } + +private: + nsString mOldURL; + nsString mNewURL; + RefPtr<nsGlobalWindow> mWindow; +}; + +nsresult +nsGlobalWindow::DispatchAsyncHashchange(nsIURI *aOldURI, nsIURI *aNewURI) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + // Make sure that aOldURI and aNewURI are identical up to the '#', and that + // their hashes are different. + bool equal = false; + NS_ENSURE_STATE(NS_SUCCEEDED(aOldURI->EqualsExceptRef(aNewURI, &equal)) && equal); + nsAutoCString oldHash, newHash; + bool oldHasHash, newHasHash; + NS_ENSURE_STATE(NS_SUCCEEDED(aOldURI->GetRef(oldHash)) && + NS_SUCCEEDED(aNewURI->GetRef(newHash)) && + NS_SUCCEEDED(aOldURI->GetHasRef(&oldHasHash)) && + NS_SUCCEEDED(aNewURI->GetHasRef(&newHasHash)) && + (oldHasHash != newHasHash || !oldHash.Equals(newHash))); + + nsAutoCString oldSpec, newSpec; + nsresult rv = aOldURI->GetSpec(oldSpec); + NS_ENSURE_SUCCESS(rv, rv); + rv = aNewURI->GetSpec(newSpec); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ConvertUTF8toUTF16 oldWideSpec(oldSpec); + NS_ConvertUTF8toUTF16 newWideSpec(newSpec); + + nsCOMPtr<nsIRunnable> callback = + new HashchangeCallback(oldWideSpec, newWideSpec, this); + return NS_DispatchToMainThread(callback); +} + +nsresult +nsGlobalWindow::FireHashchange(const nsAString &aOldURL, + const nsAString &aNewURL) +{ + MOZ_ASSERT(IsInnerWindow()); + + // Don't do anything if the window is frozen. + if (IsFrozen()) { + return NS_OK; + } + + // Get a presentation shell for use in creating the hashchange event. + NS_ENSURE_STATE(AsInner()->IsCurrentInnerWindow()); + + nsIPresShell *shell = mDoc->GetShell(); + RefPtr<nsPresContext> presContext; + if (shell) { + presContext = shell->GetPresContext(); + } + + HashChangeEventInit init; + init.mBubbles = true; + init.mCancelable = false; + init.mNewURL = aNewURL; + init.mOldURL = aOldURL; + + RefPtr<HashChangeEvent> event = + HashChangeEvent::Constructor(this, NS_LITERAL_STRING("hashchange"), + init); + + event->SetTrusted(true); + + bool dummy; + return DispatchEvent(event, &dummy); +} + +nsresult +nsGlobalWindow::DispatchSyncPopState() +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + NS_ASSERTION(nsContentUtils::IsSafeToRunScript(), + "Must be safe to run script here."); + + nsresult rv = NS_OK; + + // Bail if the window is frozen. + if (IsFrozen()) { + return NS_OK; + } + + // Get the document's pending state object -- it contains the data we're + // going to send along with the popstate event. The object is serialized + // using structured clone. + nsCOMPtr<nsIVariant> stateObj; + rv = mDoc->GetStateObject(getter_AddRefs(stateObj)); + NS_ENSURE_SUCCESS(rv, rv); + + // Obtain a presentation shell for use in creating a popstate event. + nsIPresShell *shell = mDoc->GetShell(); + RefPtr<nsPresContext> presContext; + if (shell) { + presContext = shell->GetPresContext(); + } + + bool result = true; + AutoJSAPI jsapi; + result = jsapi.Init(AsInner()); + NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); + + JSContext* cx = jsapi.cx(); + JS::Rooted<JS::Value> stateJSValue(cx, JS::NullValue()); + result = stateObj ? VariantToJsval(cx, stateObj, &stateJSValue) : true; + NS_ENSURE_TRUE(result, NS_ERROR_FAILURE); + + RootedDictionary<PopStateEventInit> init(cx); + init.mBubbles = true; + init.mCancelable = false; + init.mState = stateJSValue; + + RefPtr<PopStateEvent> event = + PopStateEvent::Constructor(this, NS_LITERAL_STRING("popstate"), + init); + event->SetTrusted(true); + event->SetTarget(this); + + bool dummy; // default action + return DispatchEvent(event, &dummy); +} + +// Find an nsICanvasFrame under aFrame. Only search the principal +// child lists. aFrame must be non-null. +static nsCanvasFrame* FindCanvasFrame(nsIFrame* aFrame) +{ + nsCanvasFrame* canvasFrame = do_QueryFrame(aFrame); + if (canvasFrame) { + return canvasFrame; + } + + for (nsIFrame* kid : aFrame->PrincipalChildList()) { + canvasFrame = FindCanvasFrame(kid); + if (canvasFrame) { + return canvasFrame; + } + } + + return nullptr; +} + +//------------------------------------------------------- +// Tells the HTMLFrame/CanvasFrame that is now has focus +void +nsGlobalWindow::UpdateCanvasFocus(bool aFocusChanged, nsIContent* aNewContent) +{ + MOZ_ASSERT(IsInnerWindow()); + + // this is called from the inner window so use GetDocShell + nsIDocShell* docShell = GetDocShell(); + if (!docShell) + return; + + bool editable; + docShell->GetEditable(&editable); + if (editable) + return; + + nsCOMPtr<nsIPresShell> presShell = docShell->GetPresShell(); + if (!presShell || !mDoc) + return; + + Element *rootElement = mDoc->GetRootElement(); + if (rootElement) { + if ((mHasFocus || aFocusChanged) && + (mFocusedNode == rootElement || aNewContent == rootElement)) { + nsIFrame* frame = rootElement->GetPrimaryFrame(); + if (frame) { + frame = frame->GetParent(); + nsCanvasFrame* canvasFrame = do_QueryFrame(frame); + if (canvasFrame) { + canvasFrame->SetHasFocus(mHasFocus && rootElement == aNewContent); + } + } + } + } else { + // Look for the frame the hard way + nsIFrame* frame = presShell->GetRootFrame(); + if (frame) { + nsCanvasFrame* canvasFrame = FindCanvasFrame(frame); + if (canvasFrame) { + canvasFrame->SetHasFocus(false); + } + } + } +} + +already_AddRefed<nsICSSDeclaration> +nsGlobalWindow::GetComputedStyle(Element& aElt, const nsAString& aPseudoElt, + ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + return GetComputedStyleHelper(aElt, aPseudoElt, false, aError); +} + +already_AddRefed<nsICSSDeclaration> +nsGlobalWindow::GetDefaultComputedStyle(Element& aElt, + const nsAString& aPseudoElt, + ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + return GetComputedStyleHelper(aElt, aPseudoElt, true, aError); +} + +nsresult +nsGlobalWindow::GetComputedStyleHelper(nsIDOMElement* aElt, + const nsAString& aPseudoElt, + bool aDefaultStylesOnly, + nsIDOMCSSStyleDeclaration** aReturn) +{ + MOZ_ASSERT(IsInnerWindow()); + + NS_ENSURE_ARG_POINTER(aReturn); + *aReturn = nullptr; + + nsCOMPtr<dom::Element> element = do_QueryInterface(aElt); + if (!element) { + return NS_ERROR_DOM_NOT_SUPPORTED_ERR; + } + + ErrorResult rv; + nsCOMPtr<nsIDOMCSSStyleDeclaration> declaration = + GetComputedStyleHelper(*element, aPseudoElt, aDefaultStylesOnly, rv); + declaration.forget(aReturn); + + return rv.StealNSResult(); +} + +already_AddRefed<nsICSSDeclaration> +nsGlobalWindow::GetComputedStyleHelperOuter(Element& aElt, + const nsAString& aPseudoElt, + bool aDefaultStylesOnly) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + if (!mDocShell) { + return nullptr; + } + + nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell(); + + if (!presShell) { + // Try flushing frames on our parent in case there's a pending + // style change that will create the presshell. + auto* parent = nsGlobalWindow::Cast(GetPrivateParent()); + if (!parent) { + return nullptr; + } + + parent->FlushPendingNotifications(Flush_Frames); + + // Might have killed mDocShell + if (!mDocShell) { + return nullptr; + } + + presShell = mDocShell->GetPresShell(); + if (!presShell) { + return nullptr; + } + } + + RefPtr<nsComputedDOMStyle> compStyle = + NS_NewComputedDOMStyle(&aElt, aPseudoElt, presShell, + aDefaultStylesOnly ? nsComputedDOMStyle::eDefaultOnly : + nsComputedDOMStyle::eAll); + + return compStyle.forget(); +} + +already_AddRefed<nsICSSDeclaration> +nsGlobalWindow::GetComputedStyleHelper(Element& aElt, + const nsAString& aPseudoElt, + bool aDefaultStylesOnly, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetComputedStyleHelperOuter, + (aElt, aPseudoElt, aDefaultStylesOnly), + aError, nullptr); +} + +DOMStorage* +nsGlobalWindow::GetSessionStorage(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + nsIPrincipal *principal = GetPrincipal(); + nsIDocShell* docShell = GetDocShell(); + + if (!principal || !docShell || !Preferences::GetBool(kStorageEnabled)) { + return nullptr; + } + + if (mSessionStorage) { + if (MOZ_LOG_TEST(gDOMLeakPRLog, LogLevel::Debug)) { + PR_LogPrint("nsGlobalWindow %p has %p sessionStorage", this, mSessionStorage.get()); + } + bool canAccess = mSessionStorage->CanAccess(principal); + NS_ASSERTION(canAccess, + "This window owned sessionStorage " + "that could not be accessed!"); + if (!canAccess) { + mSessionStorage = nullptr; + } + } + + if (!mSessionStorage) { + nsString documentURI; + if (mDoc) { + aError = mDoc->GetDocumentURI(documentURI); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } + } + + // If the document has the sandboxed origin flag set + // don't allow access to sessionStorage. + if (!mDoc) { + aError.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + if (mDoc->GetSandboxFlags() & SANDBOXED_ORIGIN) { + aError.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + nsresult rv; + + nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(docShell, &rv); + if (NS_FAILED(rv)) { + aError.Throw(rv); + return nullptr; + } + + nsCOMPtr<nsIDOMStorage> storage; + aError = storageManager->CreateStorage(AsInner(), principal, documentURI, + IsPrivateBrowsing(), + getter_AddRefs(storage)); + if (aError.Failed()) { + return nullptr; + } + + mSessionStorage = static_cast<DOMStorage*>(storage.get()); + MOZ_ASSERT(mSessionStorage); + + if (MOZ_LOG_TEST(gDOMLeakPRLog, LogLevel::Debug)) { + PR_LogPrint("nsGlobalWindow %p tried to get a new sessionStorage %p", this, mSessionStorage.get()); + } + + if (!mSessionStorage) { + aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR); + return nullptr; + } + } + + if (MOZ_LOG_TEST(gDOMLeakPRLog, LogLevel::Debug)) { + PR_LogPrint("nsGlobalWindow %p returns %p sessionStorage", this, mSessionStorage.get()); + } + + return mSessionStorage; +} + +DOMStorage* +nsGlobalWindow::GetLocalStorage(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!Preferences::GetBool(kStorageEnabled)) { + return nullptr; + } + + if (!mLocalStorage) { + if (nsContentUtils::StorageAllowedForWindow(AsInner()) == + nsContentUtils::StorageAccess::eDeny) { + aError.Throw(NS_ERROR_DOM_SECURITY_ERR); + return nullptr; + } + + nsIPrincipal *principal = GetPrincipal(); + if (!principal) { + return nullptr; + } + + nsresult rv; + nsCOMPtr<nsIDOMStorageManager> storageManager = + do_GetService("@mozilla.org/dom/localStorage-manager;1", &rv); + if (NS_FAILED(rv)) { + aError.Throw(rv); + return nullptr; + } + + nsString documentURI; + if (mDoc) { + aError = mDoc->GetDocumentURI(documentURI); + if (NS_WARN_IF(aError.Failed())) { + return nullptr; + } + } + + nsCOMPtr<nsIDOMStorage> storage; + aError = storageManager->CreateStorage(AsInner(), principal, documentURI, + IsPrivateBrowsing(), + getter_AddRefs(storage)); + if (aError.Failed()) { + return nullptr; + } + + mLocalStorage = static_cast<DOMStorage*>(storage.get()); + MOZ_ASSERT(mLocalStorage); + } + + return mLocalStorage; +} + +IDBFactory* +nsGlobalWindow::GetIndexedDB(ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + if (!mIndexedDB) { + // This may keep mIndexedDB null without setting an error. + aError = IDBFactory::CreateForWindow(AsInner(), + getter_AddRefs(mIndexedDB)); + } + + return mIndexedDB; +} + +//***************************************************************************** +// nsGlobalWindow::nsIInterfaceRequestor +//***************************************************************************** + +NS_IMETHODIMP +nsGlobalWindow::GetInterface(const nsIID & aIID, void **aSink) +{ + NS_ENSURE_ARG_POINTER(aSink); + *aSink = nullptr; + + if (aIID.Equals(NS_GET_IID(nsIDocCharset))) { + nsGlobalWindow* outer = GetOuterWindowInternal(); + NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED); + + NS_WARNING("Using deprecated nsIDocCharset: use nsIDocShell.GetCharset() instead "); + nsCOMPtr<nsIDocCharset> docCharset(do_QueryInterface(outer->mDocShell)); + docCharset.forget(aSink); + } + else if (aIID.Equals(NS_GET_IID(nsIWebNavigation))) { + nsGlobalWindow* outer = GetOuterWindowInternal(); + NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsIWebNavigation> webNav(do_QueryInterface(outer->mDocShell)); + webNav.forget(aSink); + } + else if (aIID.Equals(NS_GET_IID(nsIDocShell))) { + nsGlobalWindow* outer = GetOuterWindowInternal(); + NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsIDocShell> docShell = outer->mDocShell; + docShell.forget(aSink); + } +#ifdef NS_PRINTING + else if (aIID.Equals(NS_GET_IID(nsIWebBrowserPrint))) { + nsGlobalWindow* outer = GetOuterWindowInternal(); + NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED); + + if (outer->mDocShell) { + nsCOMPtr<nsIContentViewer> viewer; + outer->mDocShell->GetContentViewer(getter_AddRefs(viewer)); + if (viewer) { + nsCOMPtr<nsIWebBrowserPrint> webBrowserPrint(do_QueryInterface(viewer)); + webBrowserPrint.forget(aSink); + } + } + } +#endif + else if (aIID.Equals(NS_GET_IID(nsIDOMWindowUtils))) { + nsGlobalWindow* outer = GetOuterWindowInternal(); + NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED); + + if (!mWindowUtils) { + mWindowUtils = new nsDOMWindowUtils(outer); + } + + *aSink = mWindowUtils; + NS_ADDREF(((nsISupports *) *aSink)); + } + else if (aIID.Equals(NS_GET_IID(nsILoadContext))) { + nsGlobalWindow* outer = GetOuterWindowInternal(); + NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED); + + nsCOMPtr<nsILoadContext> loadContext(do_QueryInterface(outer->mDocShell)); + loadContext.forget(aSink); + } + else { + return QueryInterface(aIID, aSink); + } + + return *aSink ? NS_OK : NS_ERROR_NO_INTERFACE; +} + +void +nsGlobalWindow::GetInterface(JSContext* aCx, nsIJSID* aIID, + JS::MutableHandle<JS::Value> aRetval, + ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + dom::GetInterface(aCx, this, aIID, aRetval, aError); +} + +already_AddRefed<CacheStorage> +nsGlobalWindow::GetCaches(ErrorResult& aRv) +{ + MOZ_ASSERT(IsInnerWindow()); + + if (!mCacheStorage) { + bool forceTrustedOrigin = + GetOuterWindow()->GetServiceWorkersTestingEnabled(); + + nsContentUtils::StorageAccess access = + nsContentUtils::StorageAllowedForWindow(AsInner()); + + // We don't block the cache API when being told to only allow storage for the + // current session. + bool storageBlocked = access <= nsContentUtils::StorageAccess::ePrivateBrowsing; + + mCacheStorage = CacheStorage::CreateOnMainThread(cache::DEFAULT_NAMESPACE, + this, GetPrincipal(), + storageBlocked, + forceTrustedOrigin, aRv); + } + + RefPtr<CacheStorage> ref = mCacheStorage; + return ref.forget(); +} + +already_AddRefed<ServiceWorkerRegistration> +nsPIDOMWindowInner::GetServiceWorkerRegistration(const nsAString& aScope) +{ + RefPtr<ServiceWorkerRegistration> registration; + if (!mServiceWorkerRegistrationTable.Get(aScope, + getter_AddRefs(registration))) { + registration = + ServiceWorkerRegistration::CreateForMainThread(this, aScope); + mServiceWorkerRegistrationTable.Put(aScope, registration); + } + return registration.forget(); +} + +void +nsPIDOMWindowInner::InvalidateServiceWorkerRegistration(const nsAString& aScope) +{ + mServiceWorkerRegistrationTable.Remove(aScope); +} + +void +nsGlobalWindow::FireOfflineStatusEventIfChanged() +{ + if (!AsInner()->IsCurrentInnerWindow()) + return; + + // Don't fire an event if the status hasn't changed + if (mWasOffline == NS_IsOffline()) { + return; + } + + mWasOffline = !mWasOffline; + + nsAutoString name; + if (mWasOffline) { + name.AssignLiteral("offline"); + } else { + name.AssignLiteral("online"); + } + // The event is fired at the body element, or if there is no body element, + // at the document. + nsCOMPtr<EventTarget> eventTarget = mDoc.get(); + nsHTMLDocument* htmlDoc = mDoc->AsHTMLDocument(); + if (htmlDoc) { + Element* body = htmlDoc->GetBody(); + if (body) { + eventTarget = body; + } + } else { + Element* documentElement = mDoc->GetDocumentElement(); + if (documentElement) { + eventTarget = documentElement; + } + } + nsContentUtils::DispatchTrustedEvent(mDoc, eventTarget, name, true, false); +} + +class NotifyIdleObserverRunnable : public Runnable +{ +public: + NotifyIdleObserverRunnable(nsIIdleObserver* aIdleObserver, + uint32_t aTimeInS, + bool aCallOnidle, + nsGlobalWindow* aIdleWindow) + : mIdleObserver(aIdleObserver), mTimeInS(aTimeInS), mIdleWindow(aIdleWindow), + mCallOnidle(aCallOnidle) + { } + + NS_IMETHOD Run() override + { + if (mIdleWindow->ContainsIdleObserver(mIdleObserver, mTimeInS)) { + return mCallOnidle ? mIdleObserver->Onidle() : mIdleObserver->Onactive(); + } + return NS_OK; + } + +private: + nsCOMPtr<nsIIdleObserver> mIdleObserver; + uint32_t mTimeInS; + RefPtr<nsGlobalWindow> mIdleWindow; + + // If false then call on active + bool mCallOnidle; +}; + +void +nsGlobalWindow::NotifyIdleObserver(IdleObserverHolder* aIdleObserverHolder, + bool aCallOnidle) +{ + MOZ_ASSERT(aIdleObserverHolder); + aIdleObserverHolder->mPrevNotificationIdle = aCallOnidle; + + nsCOMPtr<nsIRunnable> caller = + new NotifyIdleObserverRunnable(aIdleObserverHolder->mIdleObserver, + aIdleObserverHolder->mTimeInS, + aCallOnidle, this); + if (NS_FAILED(NS_DispatchToCurrentThread(caller))) { + NS_WARNING("Failed to dispatch thread for idle observer notification."); + } +} + +bool +nsGlobalWindow::ContainsIdleObserver(nsIIdleObserver* aIdleObserver, uint32_t aTimeInS) +{ + MOZ_ASSERT(aIdleObserver, "Idle observer not instantiated."); + bool found = false; + nsTObserverArray<IdleObserverHolder>::ForwardIterator iter(mIdleObservers); + while (iter.HasMore()) { + IdleObserverHolder& idleObserver = iter.GetNext(); + if (idleObserver.mIdleObserver == aIdleObserver && + idleObserver.mTimeInS == aTimeInS) { + found = true; + break; + } + } + return found; +} + +void +IdleActiveTimerCallback(nsITimer* aTimer, void* aClosure) +{ + RefPtr<nsGlobalWindow> idleWindow = static_cast<nsGlobalWindow*>(aClosure); + MOZ_ASSERT(idleWindow, "Idle window has not been instantiated."); + idleWindow->HandleIdleActiveEvent(); +} + +void +IdleObserverTimerCallback(nsITimer* aTimer, void* aClosure) +{ + RefPtr<nsGlobalWindow> idleWindow = static_cast<nsGlobalWindow*>(aClosure); + MOZ_ASSERT(idleWindow, "Idle window has not been instantiated."); + idleWindow->HandleIdleObserverCallback(); +} + +void +nsGlobalWindow::HandleIdleObserverCallback() +{ + MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!"); + MOZ_ASSERT(static_cast<uint32_t>(mIdleCallbackIndex) < mIdleObservers.Length(), + "Idle callback index exceeds array bounds!"); + IdleObserverHolder& idleObserver = mIdleObservers.ElementAt(mIdleCallbackIndex); + NotifyIdleObserver(&idleObserver, true); + mIdleCallbackIndex++; + if (NS_FAILED(ScheduleNextIdleObserverCallback())) { + NS_WARNING("Failed to set next idle observer callback."); + } +} + +nsresult +nsGlobalWindow::ScheduleNextIdleObserverCallback() +{ + MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!"); + MOZ_ASSERT(mIdleService, "No idle service!"); + + if (mIdleCallbackIndex < 0 || + static_cast<uint32_t>(mIdleCallbackIndex) >= mIdleObservers.Length()) { + return NS_OK; + } + + IdleObserverHolder& idleObserver = + mIdleObservers.ElementAt(mIdleCallbackIndex); + + uint32_t userIdleTimeMS = 0; + nsresult rv = mIdleService->GetIdleTime(&userIdleTimeMS); + NS_ENSURE_SUCCESS(rv, rv); + + uint32_t callbackTimeMS = 0; + if (idleObserver.mTimeInS * 1000 + mIdleFuzzFactor > userIdleTimeMS) { + callbackTimeMS = idleObserver.mTimeInS * 1000 - userIdleTimeMS + mIdleFuzzFactor; + } + + mIdleTimer->Cancel(); + rv = mIdleTimer->InitWithFuncCallback(IdleObserverTimerCallback, + this, + callbackTimeMS, + nsITimer::TYPE_ONE_SHOT); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +uint32_t +nsGlobalWindow::GetFuzzTimeMS() +{ + MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!"); + + if (sIdleObserversAPIFuzzTimeDisabled) { + return 0; + } + + uint32_t randNum = MAX_IDLE_FUZZ_TIME_MS; + size_t nbytes = PR_GetRandomNoise(&randNum, sizeof(randNum)); + if (nbytes != sizeof(randNum)) { + NS_WARNING("PR_GetRandomNoise(...) Not implemented or no available noise!"); + return MAX_IDLE_FUZZ_TIME_MS; + } + + if (randNum > MAX_IDLE_FUZZ_TIME_MS) { + randNum %= MAX_IDLE_FUZZ_TIME_MS; + } + + return randNum; +} + +nsresult +nsGlobalWindow::ScheduleActiveTimerCallback() +{ + MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!"); + + if (!mAddActiveEventFuzzTime) { + return HandleIdleActiveEvent(); + } + + MOZ_ASSERT(mIdleTimer); + mIdleTimer->Cancel(); + + uint32_t fuzzFactorInMS = GetFuzzTimeMS(); + nsresult rv = mIdleTimer->InitWithFuncCallback(IdleActiveTimerCallback, + this, + fuzzFactorInMS, + nsITimer::TYPE_ONE_SHOT); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +nsresult +nsGlobalWindow::HandleIdleActiveEvent() +{ + MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!"); + + if (mCurrentlyIdle) { + mIdleCallbackIndex = 0; + mIdleFuzzFactor = GetFuzzTimeMS(); + nsresult rv = ScheduleNextIdleObserverCallback(); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; + } + + mIdleCallbackIndex = -1; + MOZ_ASSERT(mIdleTimer); + mIdleTimer->Cancel(); + nsTObserverArray<IdleObserverHolder>::ForwardIterator iter(mIdleObservers); + while (iter.HasMore()) { + IdleObserverHolder& idleObserver = iter.GetNext(); + if (idleObserver.mPrevNotificationIdle) { + NotifyIdleObserver(&idleObserver, false); + } + } + + return NS_OK; +} + +nsGlobalWindow::SlowScriptResponse +nsGlobalWindow::ShowSlowScriptDialog() +{ + MOZ_ASSERT(IsInnerWindow()); + + nsresult rv; + AutoJSContext cx; + + if (Preferences::GetBool("dom.always_stop_slow_scripts")) { + return KillSlowScript; + } + + // If it isn't safe to run script, then it isn't safe to bring up the prompt + // (since that spins the event loop). In that (rare) case, we just kill the + // script and report a warning. + if (!nsContentUtils::IsSafeToRunScript()) { + JS_ReportWarningASCII(cx, "A long running script was terminated"); + return KillSlowScript; + } + + // If our document is not active, just kill the script: we've been unloaded + if (!AsInner()->HasActiveDocument()) { + return KillSlowScript; + } + + // Check if we should offer the option to debug + JS::AutoFilename filename; + unsigned lineno; + bool hasFrame = JS::DescribeScriptedCaller(cx, &filename, &lineno); + + // Record the slow script event if we haven't done so already for this inner window + // (which represents a particular page to the user). + if (!mHasHadSlowScript) { + Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_PAGE_COUNT, 1); + } + mHasHadSlowScript = true; + + if (XRE_IsContentProcess() && + ProcessHangMonitor::Get()) { + ProcessHangMonitor::SlowScriptAction action; + RefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get(); + nsIDocShell* docShell = GetDocShell(); + nsCOMPtr<nsITabChild> child = docShell ? docShell->GetTabChild() : nullptr; + action = monitor->NotifySlowScript(child, + filename.get(), + lineno); + if (action == ProcessHangMonitor::Terminate) { + return KillSlowScript; + } + + if (action == ProcessHangMonitor::StartDebugger) { + // Spin a nested event loop so that the debugger in the parent can fetch + // any information it needs. Once the debugger has started, return to the + // script. + RefPtr<nsGlobalWindow> outer = GetOuterWindowInternal(); + outer->EnterModalState(); + while (!monitor->IsDebuggerStartupComplete()) { + NS_ProcessNextEvent(nullptr, true); + } + outer->LeaveModalState(); + return ContinueSlowScript; + } + + return ContinueSlowScriptAndKeepNotifying; + } + + // Reached only on non-e10s - once per slow script dialog. + // On e10s - we probe once at ProcessHangsMonitor.jsm + Telemetry::Accumulate(Telemetry::SLOW_SCRIPT_NOTICE_COUNT, 1); + + // Get the nsIPrompt interface from the docshell + nsCOMPtr<nsIDocShell> ds = GetDocShell(); + NS_ENSURE_TRUE(ds, KillSlowScript); + nsCOMPtr<nsIPrompt> prompt = do_GetInterface(ds); + NS_ENSURE_TRUE(prompt, KillSlowScript); + + // Prioritize the SlowScriptDebug interface over JSD1. + nsCOMPtr<nsISlowScriptDebugCallback> debugCallback; + + if (hasFrame) { + const char *debugCID = "@mozilla.org/dom/slow-script-debug;1"; + nsCOMPtr<nsISlowScriptDebug> debugService = do_GetService(debugCID, &rv); + if (NS_SUCCEEDED(rv)) { + debugService->GetActivationHandler(getter_AddRefs(debugCallback)); + } + } + + bool showDebugButton = !!debugCallback; + + // Get localizable strings + nsXPIDLString title, msg, stopButton, waitButton, debugButton, neverShowDlg; + + rv = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + "KillScriptTitle", + title); + + nsresult tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + "StopScriptButton", + stopButton); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + "WaitForScriptButton", + waitButton); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + "DontAskAgain", + neverShowDlg); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + + if (showDebugButton) { + tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + "DebugScriptButton", + debugButton); + if (NS_FAILED(tmp)) { + rv = tmp; + } + + tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + "KillScriptWithDebugMessage", + msg); + if (NS_FAILED(tmp)) { + rv = tmp; + } + } + else { + tmp = nsContentUtils::GetLocalizedString(nsContentUtils::eDOM_PROPERTIES, + "KillScriptMessage", + msg); + if (NS_FAILED(tmp)) { + rv = tmp; + } + } + + // GetStringFromName can return NS_OK and still give nullptr string + if (NS_FAILED(rv) || !title || !msg || !stopButton || !waitButton || + (!debugButton && showDebugButton) || !neverShowDlg) { + NS_ERROR("Failed to get localized strings."); + return ContinueSlowScript; + } + + // Append file and line number information, if available + if (filename.get()) { + nsXPIDLString scriptLocation; + // We want to drop the middle part of too-long locations. We'll + // define "too-long" as longer than 60 UTF-16 code units. Just + // have to be a bit careful about unpaired surrogates. + NS_ConvertUTF8toUTF16 filenameUTF16(filename.get()); + if (filenameUTF16.Length() > 60) { + // XXXbz Do we need to insert any bidi overrides here? + size_t cutStart = 30; + size_t cutLength = filenameUTF16.Length() - 60; + MOZ_ASSERT(cutLength > 0); + if (NS_IS_LOW_SURROGATE(filenameUTF16[cutStart])) { + // Don't truncate before the low surrogate, in case it's preceded by a + // high surrogate and forms a single Unicode character. Instead, just + // include the low surrogate. + ++cutStart; + --cutLength; + } + if (NS_IS_LOW_SURROGATE(filenameUTF16[cutStart + cutLength])) { + // Likewise, don't drop a trailing low surrogate here. We want to + // increase cutLength, since it might be 0 already so we can't very well + // decrease it. + ++cutLength; + } + + // Insert U+2026 HORIZONTAL ELLIPSIS + filenameUTF16.Replace(cutStart, cutLength, NS_LITERAL_STRING(u"\x2026")); + } + const char16_t *formatParams[] = { filenameUTF16.get() }; + rv = nsContentUtils::FormatLocalizedString(nsContentUtils::eDOM_PROPERTIES, + "KillScriptLocation", + formatParams, + scriptLocation); + + if (NS_SUCCEEDED(rv) && scriptLocation) { + msg.AppendLiteral("\n\n"); + msg.Append(scriptLocation); + msg.Append(':'); + msg.AppendInt(lineno); + } + } + + int32_t buttonPressed = 0; // In case the user exits dialog by clicking X. + bool neverShowDlgChk = false; + uint32_t buttonFlags = nsIPrompt::BUTTON_POS_1_DEFAULT + + (nsIPrompt::BUTTON_TITLE_IS_STRING * + (nsIPrompt::BUTTON_POS_0 + nsIPrompt::BUTTON_POS_1)); + + // Add a third button if necessary. + if (showDebugButton) + buttonFlags += nsIPrompt::BUTTON_TITLE_IS_STRING * nsIPrompt::BUTTON_POS_2; + + // Null out the operation callback while we're re-entering JS here. + bool old = JS_DisableInterruptCallback(cx); + + // Open the dialog. + rv = prompt->ConfirmEx(title, msg, buttonFlags, waitButton, stopButton, + debugButton, neverShowDlg, &neverShowDlgChk, + &buttonPressed); + + JS_ResetInterruptCallback(cx, old); + + if (NS_SUCCEEDED(rv) && (buttonPressed == 0)) { + return neverShowDlgChk ? AlwaysContinueSlowScript : ContinueSlowScript; + } + if (buttonPressed == 2) { + if (debugCallback) { + rv = debugCallback->HandleSlowScriptDebug(this); + return NS_SUCCEEDED(rv) ? ContinueSlowScript : KillSlowScript; + } + } + JS_ClearPendingException(cx); + return KillSlowScript; +} + +uint32_t +nsGlobalWindow::FindInsertionIndex(IdleObserverHolder* aIdleObserver) +{ + MOZ_ASSERT(IsInnerWindow()); + MOZ_ASSERT(aIdleObserver, "Idle observer not instantiated."); + + uint32_t i = 0; + nsTObserverArray<IdleObserverHolder>::ForwardIterator iter(mIdleObservers); + while (iter.HasMore()) { + IdleObserverHolder& idleObserver = iter.GetNext(); + if (idleObserver.mTimeInS > aIdleObserver->mTimeInS) { + break; + } + i++; + MOZ_ASSERT(i <= mIdleObservers.Length(), "Array index out of bounds error."); + } + + return i; +} + +nsresult +nsGlobalWindow::RegisterIdleObserver(nsIIdleObserver* aIdleObserver) +{ + MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!"); + + nsresult rv; + if (mIdleObservers.IsEmpty()) { + mIdleService = do_GetService("@mozilla.org/widget/idleservice;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = mIdleService->AddIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S); + NS_ENSURE_SUCCESS(rv, rv); + + if (!mIdleTimer) { + mIdleTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mIdleTimer->Cancel(); + } + } + + MOZ_ASSERT(mIdleService); + MOZ_ASSERT(mIdleTimer); + + IdleObserverHolder tmpIdleObserver; + tmpIdleObserver.mIdleObserver = aIdleObserver; + rv = aIdleObserver->GetTime(&tmpIdleObserver.mTimeInS); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_MAX(tmpIdleObserver.mTimeInS, UINT32_MAX / 1000); + NS_ENSURE_ARG_MIN(tmpIdleObserver.mTimeInS, MIN_IDLE_NOTIFICATION_TIME_S); + + uint32_t insertAtIndex = FindInsertionIndex(&tmpIdleObserver); + if (insertAtIndex == mIdleObservers.Length()) { + mIdleObservers.AppendElement(tmpIdleObserver); + } + else { + mIdleObservers.InsertElementAt(insertAtIndex, tmpIdleObserver); + } + + bool userIsIdle = false; + rv = nsContentUtils::IsUserIdle(MIN_IDLE_NOTIFICATION_TIME_S, &userIsIdle); + NS_ENSURE_SUCCESS(rv, rv); + + // Special case. First idle observer added to empty list while the user is idle. + // Haven't received 'idle' topic notification from slow idle service yet. + // Need to wait for the idle notification and then notify idle observers in the list. + if (userIsIdle && mIdleCallbackIndex == -1) { + return NS_OK; + } + + if (!mCurrentlyIdle) { + return NS_OK; + } + + MOZ_ASSERT(mIdleCallbackIndex >= 0); + + if (static_cast<int32_t>(insertAtIndex) < mIdleCallbackIndex) { + IdleObserverHolder& idleObserver = mIdleObservers.ElementAt(insertAtIndex); + NotifyIdleObserver(&idleObserver, true); + mIdleCallbackIndex++; + return NS_OK; + } + + if (static_cast<int32_t>(insertAtIndex) == mIdleCallbackIndex) { + mIdleTimer->Cancel(); + rv = ScheduleNextIdleObserverCallback(); + NS_ENSURE_SUCCESS(rv, rv); + } + return NS_OK; +} + +nsresult +nsGlobalWindow::FindIndexOfElementToRemove(nsIIdleObserver* aIdleObserver, + int32_t* aRemoveElementIndex) +{ + MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!"); + MOZ_ASSERT(aIdleObserver, "Idle observer not instantiated."); + + *aRemoveElementIndex = 0; + if (mIdleObservers.IsEmpty()) { + return NS_ERROR_FAILURE; + } + + uint32_t aIdleObserverTimeInS; + nsresult rv = aIdleObserver->GetTime(&aIdleObserverTimeInS); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_ARG_MIN(aIdleObserverTimeInS, MIN_IDLE_NOTIFICATION_TIME_S); + + nsTObserverArray<IdleObserverHolder>::ForwardIterator iter(mIdleObservers); + while (iter.HasMore()) { + IdleObserverHolder& idleObserver = iter.GetNext(); + if (idleObserver.mTimeInS == aIdleObserverTimeInS && + idleObserver.mIdleObserver == aIdleObserver ) { + break; + } + (*aRemoveElementIndex)++; + } + return static_cast<uint32_t>(*aRemoveElementIndex) >= mIdleObservers.Length() ? + NS_ERROR_FAILURE : NS_OK; +} + +nsresult +nsGlobalWindow::UnregisterIdleObserver(nsIIdleObserver* aIdleObserver) +{ + MOZ_ASSERT(IsInnerWindow(), "Must be an inner window!"); + + int32_t removeElementIndex; + nsresult rv = FindIndexOfElementToRemove(aIdleObserver, &removeElementIndex); + if (NS_FAILED(rv)) { + NS_WARNING("Idle observer not found in list of idle observers. No idle observer removed."); + return NS_OK; + } + mIdleObservers.RemoveElementAt(removeElementIndex); + + MOZ_ASSERT(mIdleTimer); + if (mIdleObservers.IsEmpty() && mIdleService) { + rv = mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S); + NS_ENSURE_SUCCESS(rv, rv); + mIdleService = nullptr; + + mIdleTimer->Cancel(); + mIdleCallbackIndex = -1; + return NS_OK; + } + + if (!mCurrentlyIdle) { + return NS_OK; + } + + if (removeElementIndex < mIdleCallbackIndex) { + mIdleCallbackIndex--; + return NS_OK; + } + + if (removeElementIndex != mIdleCallbackIndex) { + return NS_OK; + } + + mIdleTimer->Cancel(); + + // If the last element in the array had been notified then decrement + // mIdleCallbackIndex because an idle was removed from the list of + // idle observers. + // Example: add idle observer with time 1, 2, 3, + // Idle notifications for idle observers with time 1, 2, 3 are complete + // Remove idle observer with time 3 while the user is still idle. + // The user never transitioned to active state. + // Add an idle observer with idle time 4 + if (static_cast<uint32_t>(mIdleCallbackIndex) == mIdleObservers.Length()) { + mIdleCallbackIndex--; + } + rv = ScheduleNextIdleObserverCallback(); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +nsGlobalWindow::Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) +{ + if (!nsCRT::strcmp(aTopic, NS_IOSERVICE_OFFLINE_STATUS_TOPIC)) { + if (!IsFrozen()) { + // Fires an offline status event if the offline status has changed + FireOfflineStatusEventIfChanged(); + } + return NS_OK; + } + + if (!nsCRT::strcmp(aTopic, OBSERVER_TOPIC_IDLE)) { + mCurrentlyIdle = true; + if (IsFrozen()) { + // need to fire only one idle event while the window is frozen. + mNotifyIdleObserversIdleOnThaw = true; + mNotifyIdleObserversActiveOnThaw = false; + } else if (AsInner()->IsCurrentInnerWindow()) { + HandleIdleActiveEvent(); + } + return NS_OK; + } + + if (!nsCRT::strcmp(aTopic, OBSERVER_TOPIC_ACTIVE)) { + mCurrentlyIdle = false; + if (IsFrozen()) { + mNotifyIdleObserversActiveOnThaw = true; + mNotifyIdleObserversIdleOnThaw = false; + } else if (AsInner()->IsCurrentInnerWindow()) { + MOZ_ASSERT(IsInnerWindow()); + ScheduleActiveTimerCallback(); + } + return NS_OK; + } + + if (!nsCRT::strcmp(aTopic, "dom-storage2-changed")) { + if (!IsInnerWindow() || !AsInner()->IsCurrentInnerWindow()) { + return NS_OK; + } + + nsIPrincipal *principal; + nsresult rv; + + RefPtr<StorageEvent> event = static_cast<StorageEvent*>(aSubject); + if (!event) { + return NS_ERROR_FAILURE; + } + + RefPtr<DOMStorage> changingStorage = event->GetStorageArea(); + if (!changingStorage) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIDOMStorage> istorage = changingStorage.get(); + + bool fireMozStorageChanged = false; + nsAutoString eventType; + eventType.AssignLiteral("storage"); + principal = GetPrincipal(); + if (!principal) { + return NS_OK; + } + + if (changingStorage->IsPrivate() != IsPrivateBrowsing()) { + return NS_OK; + } + + switch (changingStorage->GetType()) + { + case DOMStorage::SessionStorage: + { + bool check = false; + + nsCOMPtr<nsIDOMStorageManager> storageManager = do_QueryInterface(GetDocShell()); + if (storageManager) { + rv = storageManager->CheckStorage(principal, istorage, &check); + if (NS_FAILED(rv)) { + return rv; + } + } + + if (!check) { + // This storage event is not coming from our storage or is coming + // from a different docshell, i.e. it is a clone, ignore this event. + return NS_OK; + } + + if (MOZ_LOG_TEST(gDOMLeakPRLog, LogLevel::Debug)) { + PR_LogPrint("nsGlobalWindow %p with sessionStorage %p passing event from %p", + this, mSessionStorage.get(), changingStorage.get()); + } + + fireMozStorageChanged = mSessionStorage == changingStorage; + if (fireMozStorageChanged) { + eventType.AssignLiteral("MozSessionStorageChanged"); + } + break; + } + + case DOMStorage::LocalStorage: + { + // Allow event fire only for the same principal storages + // XXX We have to use EqualsIgnoreDomain after bug 495337 lands + nsIPrincipal* storagePrincipal = changingStorage->GetPrincipal(); + + bool equals = false; + rv = storagePrincipal->Equals(principal, &equals); + NS_ENSURE_SUCCESS(rv, rv); + + if (!equals) + return NS_OK; + + fireMozStorageChanged = mLocalStorage == changingStorage; + if (fireMozStorageChanged) { + eventType.AssignLiteral("MozLocalStorageChanged"); + } + break; + } + default: + return NS_OK; + } + + // Clone the storage event included in the observer notification. We want + // to dispatch clones rather than the original event. + ErrorResult error; + RefPtr<StorageEvent> newEvent = CloneStorageEvent(eventType, event, error); + if (error.Failed()) { + return error.StealNSResult(); + } + + newEvent->SetTrusted(true); + + if (fireMozStorageChanged) { + WidgetEvent* internalEvent = newEvent->WidgetEventPtr(); + internalEvent->mFlags.mOnlyChromeDispatch = true; + } + + if (IsFrozen()) { + // This window is frozen, rather than firing the events here, + // store the domain in which the change happened and fire the + // events if we're ever thawed. + + mPendingStorageEvents.AppendElement(newEvent); + return NS_OK; + } + + bool defaultActionEnabled; + DispatchEvent(newEvent, &defaultActionEnabled); + + return NS_OK; + } + + if (!nsCRT::strcmp(aTopic, "offline-cache-update-added")) { + if (mApplicationCache) + return NS_OK; + + // Instantiate the application object now. It observes update belonging to + // this window's document and correctly updates the applicationCache object + // state. + nsCOMPtr<nsIDOMOfflineResourceList> applicationCache = GetApplicationCache(); + nsCOMPtr<nsIObserver> observer = do_QueryInterface(applicationCache); + if (observer) + observer->Observe(aSubject, aTopic, aData); + + return NS_OK; + } + +#ifdef MOZ_B2G + if (!nsCRT::strcmp(aTopic, NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC) || + !nsCRT::strcmp(aTopic, NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC)) { + MOZ_ASSERT(IsInnerWindow()); + if (!AsInner()->IsCurrentInnerWindow()) { + return NS_OK; + } + + RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr); + event->InitEvent( + !nsCRT::strcmp(aTopic, NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC) + ? NETWORK_UPLOAD_EVENT_NAME + : NETWORK_DOWNLOAD_EVENT_NAME, + false, false); + event->SetTrusted(true); + + bool dummy; + return DispatchEvent(event, &dummy); + } +#endif // MOZ_B2G + + if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) { + MOZ_ASSERT(!NS_strcmp(aData, u"intl.accept_languages")); + MOZ_ASSERT(IsInnerWindow()); + + // The user preferred languages have changed, we need to fire an event on + // Window object and invalidate the cache for navigator.languages. It is + // done for every change which can be a waste of cycles but those should be + // fairly rare. + // We MUST invalidate navigator.languages before sending the event in the + // very likely situation where an event handler will try to read its value. + + if (mNavigator) { + NavigatorBinding::ClearCachedLanguageValue(mNavigator); + NavigatorBinding::ClearCachedLanguagesValue(mNavigator); + } + + // The event has to be dispatched only to the current inner window. + if (!AsInner()->IsCurrentInnerWindow()) { + return NS_OK; + } + + RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr); + event->InitEvent(NS_LITERAL_STRING("languagechange"), false, false); + event->SetTrusted(true); + + bool dummy; + return DispatchEvent(event, &dummy); + } + + NS_WARNING("unrecognized topic in nsGlobalWindow::Observe"); + return NS_ERROR_FAILURE; +} + +already_AddRefed<StorageEvent> +nsGlobalWindow::CloneStorageEvent(const nsAString& aType, + const RefPtr<StorageEvent>& aEvent, + ErrorResult& aRv) +{ + MOZ_ASSERT(IsInnerWindow()); + + StorageEventInit dict; + + dict.mBubbles = aEvent->Bubbles(); + dict.mCancelable = aEvent->Cancelable(); + aEvent->GetKey(dict.mKey); + aEvent->GetOldValue(dict.mOldValue); + aEvent->GetNewValue(dict.mNewValue); + aEvent->GetUrl(dict.mUrl); + + RefPtr<DOMStorage> storageArea = aEvent->GetStorageArea(); + MOZ_ASSERT(storageArea); + + RefPtr<DOMStorage> storage; + if (storageArea->GetType() == DOMStorage::LocalStorage) { + storage = GetLocalStorage(aRv); + } else { + MOZ_ASSERT(storageArea->GetType() == DOMStorage::SessionStorage); + storage = GetSessionStorage(aRv); + } + + if (aRv.Failed() || !storage) { + return nullptr; + } + + MOZ_ASSERT(storage); + MOZ_ASSERT(storage->IsForkOf(storageArea)); + + dict.mStorageArea = storage; + + RefPtr<StorageEvent> event = StorageEvent::Constructor(this, aType, dict); + return event.forget(); +} + +void +nsGlobalWindow::Suspend() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(IsInnerWindow()); + + // We can only safely suspend windows that are the current inner window. If + // its not the current inner, then we are in one of two different cases. + // Either we are in the bfcache or we are doomed window that is going away. + // When a window becomes inactive we purposely avoid placing already suspended + // windows into the bfcache. It only expects windows suspended due to the + // Freeze() method which occurs while the window is still the current inner. + // So we must not call Suspend() on bfcache windows at this point or this + // invariant will be broken. If the window is doomed there is no point in + // suspending it since it will soon be gone. + if (!AsInner()->IsCurrentInnerWindow()) { + return; + } + + // All children are also suspended. This ensure mSuspendDepth is + // set properly and the timers are properly canceled for each child. + CallOnChildren(&nsGlobalWindow::Suspend); + + mSuspendDepth += 1; + if (mSuspendDepth != 1) { + return; + } + + nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID); + if (ac) { + for (uint32_t i = 0; i < mEnabledSensors.Length(); i++) + ac->RemoveWindowListener(mEnabledSensors[i], this); + } + DisableGamepadUpdates(); + DisableVRUpdates(); + + mozilla::dom::workers::SuspendWorkersForWindow(AsInner()); + + for (Timeout* t = mTimeouts.getFirst(); t; t = t->getNext()) { + // Leave the timers with the current time remaining. This will + // cause the timers to potentially fire when the window is + // Resume()'d. Time effectively passes while suspended. + + // Drop the XPCOM timer; we'll reschedule when restoring the state. + if (t->mTimer) { + t->mTimer->Cancel(); + t->mTimer = nullptr; + + // Drop the reference that the timer's closure had on this timeout, we'll + // add it back in Resume(). + t->Release(); + } + } + + // Suspend all of the AudioContexts for this window + for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { + ErrorResult dummy; + RefPtr<Promise> d = mAudioContexts[i]->Suspend(dummy); + } +} + +void +nsGlobalWindow::Resume() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(IsInnerWindow()); + + // We can only safely resume a window if its the current inner window. If + // its not the current inner, then we are in one of two different cases. + // Either we are in the bfcache or we are doomed window that is going away. + // If a window is suspended when it becomes inactive we purposely do not + // put it in the bfcache, so Resume should never be needed in that case. + // If the window is doomed then there is no point in resuming it. + if (!AsInner()->IsCurrentInnerWindow()) { + return; + } + + // Resume all children. This restores timers recursively canceled + // in Suspend() and ensures all children have the correct mSuspendDepth. + CallOnChildren(&nsGlobalWindow::Resume); + + MOZ_ASSERT(mSuspendDepth != 0); + mSuspendDepth -= 1; + if (mSuspendDepth != 0) { + return; + } + + // We should not be able to resume a frozen window. It must be Thaw()'d first. + MOZ_ASSERT(mFreezeDepth == 0); + + nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID); + if (ac) { + for (uint32_t i = 0; i < mEnabledSensors.Length(); i++) + ac->AddWindowListener(mEnabledSensors[i], this); + } + EnableGamepadUpdates(); + EnableVRUpdates(); + + // Resume all of the AudioContexts for this window + for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { + ErrorResult dummy; + RefPtr<Promise> d = mAudioContexts[i]->Resume(dummy); + } + + TimeStamp now = TimeStamp::Now(); + DebugOnly<bool> _seenDummyTimeout = false; + + for (Timeout* t = mTimeouts.getFirst(); t; t = t->getNext()) { + // There's a chance we're being called with RunTimeout on the stack in which + // case we have a dummy timeout in the list that *must not* be resumed. It + // can be identified by a null mWindow. + if (!t->mWindow) { + NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!"); + _seenDummyTimeout = true; + continue; + } + + MOZ_ASSERT(!t->mTimer); + + // The timeout mWhen is set to the absolute time when the timer should + // fire. Recalculate the delay from now until that deadline. If the + // the deadline has already passed or falls within our minimum delay + // deadline, then clamp the resulting value to the minimum delay. The + // mWhen will remain at its absolute time, but we won't fire the OS + // timer until our calculated delay has passed. + int32_t remaining = 0; + if (t->mWhen > now) { + remaining = static_cast<int32_t>((t->mWhen - now).ToMilliseconds()); + } + uint32_t delay = std::max(remaining, DOMMinTimeoutValue()); + + t->mTimer = do_CreateInstance("@mozilla.org/timer;1"); + if (!t->mTimer) { + t->remove(); + continue; + } + + nsresult rv = t->InitTimer(GetThrottledEventQueue(), delay); + if (NS_FAILED(rv)) { + t->mTimer = nullptr; + t->remove(); + continue; + } + + // Add a reference for the new timer's closure. + t->AddRef(); + } + + // Resume all of the workers for this window. We must do this + // after timeouts since workers may have queued events that can trigger + // a setTimeout(). + mozilla::dom::workers::ResumeWorkersForWindow(AsInner()); +} + +bool +nsGlobalWindow::IsSuspended() const +{ + MOZ_ASSERT(NS_IsMainThread()); + // No inner means we are effectively suspended + if (IsOuterWindow()) { + if (!mInnerWindow) { + return true; + } + return mInnerWindow->IsSuspended(); + } + return mSuspendDepth != 0; +} + +void +nsGlobalWindow::Freeze() +{ + MOZ_ASSERT(NS_IsMainThread()); + Suspend(); + FreezeInternal(); +} + +void +nsGlobalWindow::FreezeInternal() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(IsInnerWindow()); + MOZ_DIAGNOSTIC_ASSERT(AsInner()->IsCurrentInnerWindow()); + MOZ_DIAGNOSTIC_ASSERT(IsSuspended()); + + CallOnChildren(&nsGlobalWindow::FreezeInternal); + + mFreezeDepth += 1; + MOZ_ASSERT(mSuspendDepth >= mFreezeDepth); + if (mFreezeDepth != 1) { + return; + } + + mozilla::dom::workers::FreezeWorkersForWindow(AsInner()); + + TimeStamp now = TimeStamp::Now(); + for (Timeout *t = mTimeouts.getFirst(); t; t = t->getNext()) { + // Save the current remaining time for this timeout. We will + // re-apply it when the window is Thaw()'d. This effectively + // shifts timers to the right as if time does not pass while + // the window is frozen. + if (t->mWhen > now) { + t->mTimeRemaining = t->mWhen - now; + } else { + t->mTimeRemaining = TimeDuration(0); + } + + // Since we are suspended there should be no OS timer set for + // this timeout entry. + MOZ_ASSERT(!t->mTimer); + } + + NotifyDOMWindowFrozen(this); +} + +void +nsGlobalWindow::Thaw() +{ + MOZ_ASSERT(NS_IsMainThread()); + ThawInternal(); + Resume(); +} + +void +nsGlobalWindow::ThawInternal() +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_DIAGNOSTIC_ASSERT(IsInnerWindow()); + MOZ_DIAGNOSTIC_ASSERT(AsInner()->IsCurrentInnerWindow()); + MOZ_DIAGNOSTIC_ASSERT(IsSuspended()); + + CallOnChildren(&nsGlobalWindow::ThawInternal); + + MOZ_ASSERT(mFreezeDepth != 0); + mFreezeDepth -= 1; + MOZ_ASSERT(mSuspendDepth >= mFreezeDepth); + if (mFreezeDepth != 0) { + return; + } + + TimeStamp now = TimeStamp::Now(); + DebugOnly<bool> _seenDummyTimeout = false; + + for (Timeout *t = mTimeouts.getFirst(); t; t = t->getNext()) { + // There's a chance we're being called with RunTimeout on the stack in which + // case we have a dummy timeout in the list that *must not* be resumed. It + // can be identified by a null mWindow. + if (!t->mWindow) { + NS_ASSERTION(!_seenDummyTimeout, "More than one dummy timeout?!"); + _seenDummyTimeout = true; + continue; + } + + // Set mWhen back to the time when the timer is supposed to fire. + t->mWhen = now + t->mTimeRemaining; + + MOZ_ASSERT(!t->mTimer); + } + + mozilla::dom::workers::ThawWorkersForWindow(AsInner()); + + NotifyDOMWindowThawed(this); +} + +bool +nsGlobalWindow::IsFrozen() const +{ + MOZ_ASSERT(NS_IsMainThread()); + // No inner means we are effectively frozen + if (IsOuterWindow()) { + if (!mInnerWindow) { + return true; + } + return mInnerWindow->IsFrozen(); + } + bool frozen = mFreezeDepth != 0; + MOZ_ASSERT_IF(frozen, IsSuspended()); + return frozen; +} + +void +nsGlobalWindow::SyncStateFromParentWindow() +{ + // This method should only be called on an inner window that has been + // assigned to an outer window already. + MOZ_ASSERT(IsInnerWindow()); + MOZ_ASSERT(AsInner()->IsCurrentInnerWindow()); + nsPIDOMWindowOuter* outer = GetOuterWindow(); + MOZ_ASSERT(outer); + + // Attempt to find our parent windows. + nsCOMPtr<Element> frame = outer->GetFrameElementInternal(); + nsPIDOMWindowOuter* parentOuter = frame ? frame->OwnerDoc()->GetWindow() + : nullptr; + nsGlobalWindow* parentInner = + parentOuter ? nsGlobalWindow::Cast(parentOuter->GetCurrentInnerWindow()) + : nullptr; + + // If our outer is in a modal state, but our parent is not in a modal + // state, then we must apply the suspend directly. If our parent is + // in a modal state then we should get the suspend automatically + // via the parentSuspendDepth application below. + if ((!parentInner || !parentInner->IsInModalState()) && IsInModalState()) { + Suspend(); + } + + uint32_t parentFreezeDepth = parentInner ? parentInner->mFreezeDepth : 0; + uint32_t parentSuspendDepth = parentInner ? parentInner->mSuspendDepth : 0; + + // Since every Freeze() calls Suspend(), the suspend count must + // be equal or greater to the freeze count. + MOZ_ASSERT(parentFreezeDepth <= parentSuspendDepth); + + // First apply the Freeze() calls. + for (uint32_t i = 0; i < parentFreezeDepth; ++i) { + Freeze(); + } + + // Now apply only the number of Suspend() calls to reach the target + // suspend count after applying the Freeze() calls. + for (uint32_t i = 0; i < (parentSuspendDepth - parentFreezeDepth); ++i) { + Suspend(); + } +} + +template<typename Method> +void +nsGlobalWindow::CallOnChildren(Method aMethod) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(IsInnerWindow()); + MOZ_ASSERT(AsInner()->IsCurrentInnerWindow()); + + nsCOMPtr<nsIDocShell> docShell = GetDocShell(); + if (!docShell) { + return; + } + + int32_t childCount = 0; + docShell->GetChildCount(&childCount); + + for (int32_t i = 0; i < childCount; ++i) { + nsCOMPtr<nsIDocShellTreeItem> childShell; + docShell->GetChildAt(i, getter_AddRefs(childShell)); + NS_ASSERTION(childShell, "null child shell"); + + nsCOMPtr<nsPIDOMWindowOuter> pWin = childShell->GetWindow(); + if (!pWin) { + continue; + } + + auto* win = nsGlobalWindow::Cast(pWin); + nsGlobalWindow* inner = win->GetCurrentInnerWindowInternal(); + + // This is a bit hackish. Only freeze/suspend windows which are truly our + // subwindows. + nsCOMPtr<Element> frame = pWin->GetFrameElementInternal(); + if (!mDoc || !frame || mDoc != frame->OwnerDoc() || !inner) { + continue; + } + + (inner->*aMethod)(); + } +} + +nsresult +nsGlobalWindow::FireDelayedDOMEvents() +{ + FORWARD_TO_INNER(FireDelayedDOMEvents, (), NS_ERROR_UNEXPECTED); + + for (uint32_t i = 0, len = mPendingStorageEvents.Length(); i < len; ++i) { + Observe(mPendingStorageEvents[i], "dom-storage2-changed", nullptr); + } + + if (mApplicationCache) { + static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())->FirePendingEvents(); + } + + // Fires an offline status event if the offline status has changed + FireOfflineStatusEventIfChanged(); + + if (mNotifyIdleObserversIdleOnThaw) { + mNotifyIdleObserversIdleOnThaw = false; + HandleIdleActiveEvent(); + } + + if (mNotifyIdleObserversActiveOnThaw) { + mNotifyIdleObserversActiveOnThaw = false; + ScheduleActiveTimerCallback(); + } + + nsCOMPtr<nsIDocShell> docShell = GetDocShell(); + if (docShell) { + int32_t childCount = 0; + docShell->GetChildCount(&childCount); + + for (int32_t i = 0; i < childCount; ++i) { + nsCOMPtr<nsIDocShellTreeItem> childShell; + docShell->GetChildAt(i, getter_AddRefs(childShell)); + NS_ASSERTION(childShell, "null child shell"); + + if (nsCOMPtr<nsPIDOMWindowOuter> pWin = childShell->GetWindow()) { + auto* win = nsGlobalWindow::Cast(pWin); + win->FireDelayedDOMEvents(); + } + } + } + + return NS_OK; +} + +//***************************************************************************** +// nsGlobalWindow: Window Control Functions +//***************************************************************************** + +nsPIDOMWindowOuter* +nsGlobalWindow::GetParentInternal() +{ + if (IsInnerWindow()) { + nsGlobalWindow* outer = GetOuterWindowInternal(); + if (!outer) { + NS_WARNING("No outer window available!"); + return nullptr; + } + return outer->GetParentInternal(); + } + + nsCOMPtr<nsPIDOMWindowOuter> parent = GetParent(); + + if (parent && parent != AsOuter()) { + return parent; + } + + return nullptr; +} + +void +nsGlobalWindow::UnblockScriptedClosing() +{ + MOZ_ASSERT(IsOuterWindow()); + mBlockScriptedClosingFlag = false; +} + +class AutoUnblockScriptClosing +{ +private: + RefPtr<nsGlobalWindow> mWin; +public: + explicit AutoUnblockScriptClosing(nsGlobalWindow* aWin) + : mWin(aWin) + { + MOZ_ASSERT(mWin); + MOZ_ASSERT(mWin->IsOuterWindow()); + } + ~AutoUnblockScriptClosing() + { + void (nsGlobalWindow::*run)() = &nsGlobalWindow::UnblockScriptedClosing; + NS_DispatchToCurrentThread(NewRunnableMethod(mWin, run)); + } +}; + +nsresult +nsGlobalWindow::OpenInternal(const nsAString& aUrl, const nsAString& aName, + const nsAString& aOptions, bool aDialog, + bool aContentModal, bool aCalledNoScript, + bool aDoJSFixups, bool aNavigate, + nsIArray *argv, + nsISupports *aExtraArgument, + nsIDocShellLoadInfo* aLoadInfo, + bool aForceNoOpener, + nsPIDOMWindowOuter **aReturn) +{ + MOZ_ASSERT(IsOuterWindow()); + +#ifdef DEBUG + uint32_t argc = 0; + if (argv) + argv->GetLength(&argc); +#endif + NS_PRECONDITION(!aExtraArgument || (!argv && argc == 0), + "Can't pass in arguments both ways"); + NS_PRECONDITION(!aCalledNoScript || (!argv && argc == 0), + "Can't pass JS args when called via the noscript methods"); + + mozilla::Maybe<AutoUnblockScriptClosing> closeUnblocker; + + // Calls to window.open from script should navigate. + MOZ_ASSERT(aCalledNoScript || aNavigate); + + *aReturn = nullptr; + + nsCOMPtr<nsIWebBrowserChrome> chrome = GetWebBrowserChrome(); + if (!chrome) { + // No chrome means we don't want to go through with this open call + // -- see nsIWindowWatcher.idl + return NS_ERROR_NOT_AVAILABLE; + } + + NS_ASSERTION(mDocShell, "Must have docshell here"); + + // Popups from apps are never blocked. + bool isApp = false; + if (mDoc) { + isApp = mDoc->NodePrincipal()->GetAppStatus() >= + nsIPrincipal::APP_STATUS_INSTALLED; + } + + bool forceNoOpener = aForceNoOpener; + if (!forceNoOpener) { + // Unlike other window flags, "noopener" comes from splitting on commas with + // HTML whitespace trimming... + nsCharSeparatedTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok( + aOptions, ','); + while (tok.hasMoreTokens()) { + if (tok.nextToken().EqualsLiteral("noopener")) { + forceNoOpener = true; + break; + } + } + } + + // XXXbz When this gets fixed to not use LegacyIsCallerNativeCode() + // (indirectly) maybe we can nix the AutoJSAPI usage OnLinkClickEvent::Run. + // But note that if you change this to GetEntryGlobal(), say, then + // OnLinkClickEvent::Run will need a full-blown AutoEntryScript. + const bool checkForPopup = !nsContentUtils::LegacyIsCallerChromeOrNativeCode() && + !isApp && !aDialog && !WindowExists(aName, forceNoOpener, !aCalledNoScript); + + // Note: it's very important that this be an nsXPIDLCString, since we want + // .get() on it to return nullptr until we write stuff to it. The window + // watcher expects a null URL string if there is no URL to load. + nsXPIDLCString url; + nsresult rv = NS_OK; + + // It's important to do this security check before determining whether this + // window opening should be blocked, to ensure that we don't FireAbuseEvents + // for a window opening that wouldn't have succeeded in the first place. + if (!aUrl.IsEmpty()) { + AppendUTF16toUTF8(aUrl, url); + + // It's safe to skip the security check below if we're not a dialog + // because window.openDialog is not callable from content script. See bug + // 56851. + // + // If we're not navigating, we assume that whoever *does* navigate the + // window will do a security check of their own. + if (url.get() && !aDialog && aNavigate) + rv = SecurityCheckURL(url.get()); + } + + if (NS_FAILED(rv)) + return rv; + + PopupControlState abuseLevel = gPopupControlState; + if (checkForPopup) { + abuseLevel = RevisePopupAbuseLevel(abuseLevel); + if (abuseLevel >= openAbused) { + if (!aCalledNoScript) { + // If script in some other window is doing a window.open on us and + // it's being blocked, then it's OK to close us afterwards, probably. + // But if we're doing a window.open on ourselves and block the popup, + // prevent this window from closing until after this script terminates + // so that whatever popup blocker UI the app has will be visible. + nsCOMPtr<nsPIDOMWindowInner> entryWindow = + do_QueryInterface(GetEntryGlobal()); + // Note that entryWindow can be null here if some JS component was the + // place where script was entered for this JS execution. + if (entryWindow && + entryWindow->GetOuterWindow() == this->AsOuter()) { + mBlockScriptedClosingFlag = true; + closeUnblocker.emplace(this); + } + } + + FireAbuseEvents(aUrl, aName, aOptions); + return aDoJSFixups ? NS_OK : NS_ERROR_FAILURE; + } + } + + nsCOMPtr<mozIDOMWindowProxy> domReturn; + + nsCOMPtr<nsIWindowWatcher> wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_TRUE(wwatch, rv); + + NS_ConvertUTF16toUTF8 options(aOptions); + NS_ConvertUTF16toUTF8 name(aName); + + const char *options_ptr = aOptions.IsEmpty() ? nullptr : options.get(); + const char *name_ptr = aName.IsEmpty() ? nullptr : name.get(); + + nsCOMPtr<nsPIWindowWatcher> pwwatch(do_QueryInterface(wwatch)); + NS_ENSURE_STATE(pwwatch); + + MOZ_ASSERT_IF(checkForPopup, abuseLevel < openAbused); + // At this point we should know for a fact that if checkForPopup then + // abuseLevel < openAbused, so we could just check for abuseLevel == + // openControlled. But let's be defensive just in case and treat anything + // that fails the above assert as a spam popup too, if it ever happens. + bool isPopupSpamWindow = checkForPopup && (abuseLevel >= openControlled); + + { + // Reset popup state while opening a window to prevent the + // current state from being active the whole time a modal + // dialog is open. + nsAutoPopupStatePusher popupStatePusher(openAbused, true); + + if (!aCalledNoScript) { + // We asserted at the top of this function that aNavigate is true for + // !aCalledNoScript. + rv = pwwatch->OpenWindow2(AsOuter(), url.get(), name_ptr, + options_ptr, /* aCalledFromScript = */ true, + aDialog, aNavigate, argv, + isPopupSpamWindow, + forceNoOpener, + aLoadInfo, + getter_AddRefs(domReturn)); + } else { + // Force a system caller here so that the window watcher won't screw us + // up. We do NOT want this case looking at the JS context on the stack + // when searching. Compare comments on + // nsIDOMWindow::OpenWindow and nsIWindowWatcher::OpenWindow. + + // Note: Because nsWindowWatcher is so broken, it's actually important + // that we don't force a system caller here, because that screws it up + // when it tries to compute the caller principal to associate with dialog + // arguments. That whole setup just really needs to be rewritten. :-( + Maybe<AutoNoJSAPI> nojsapi; + if (!aContentModal) { + nojsapi.emplace(); + } + + rv = pwwatch->OpenWindow2(AsOuter(), url.get(), name_ptr, + options_ptr, /* aCalledFromScript = */ false, + aDialog, aNavigate, aExtraArgument, + isPopupSpamWindow, + forceNoOpener, + aLoadInfo, + getter_AddRefs(domReturn)); + + } + } + + NS_ENSURE_SUCCESS(rv, rv); + + // success! + + NS_ENSURE_TRUE(domReturn, NS_OK); + nsCOMPtr<nsPIDOMWindowOuter> outerReturn = + nsPIDOMWindowOuter::From(domReturn); + outerReturn.swap(*aReturn); + + if (aDoJSFixups) { + nsCOMPtr<nsIDOMChromeWindow> chrome_win(do_QueryInterface(*aReturn)); + if (!chrome_win) { + // A new non-chrome window was created from a call to + // window.open() from JavaScript, make sure there's a document in + // the new window. We do this by simply asking the new window for + // its document, this will synchronously create an empty document + // if there is no document in the window. + // XXXbz should this just use EnsureInnerWindow()? + + // Force document creation. + nsCOMPtr<nsIDocument> doc = (*aReturn)->GetDoc(); + Unused << doc; + } + } + + return rv; +} + +//***************************************************************************** +// nsGlobalWindow: Timeout Functions +//***************************************************************************** + +uint32_t sNestingLevel; + +uint32_t +nsGlobalWindow::GetTimeoutId(Timeout::Reason aReason) +{ + switch (aReason) { + case Timeout::Reason::eIdleCallbackTimeout: + return ++mIdleCallbackTimeoutCounter; + case Timeout::Reason::eTimeoutOrInterval: + default: + return ++mTimeoutIdCounter; + } +} + +nsGlobalWindow* +nsGlobalWindow::InnerForSetTimeoutOrInterval(ErrorResult& aError) +{ + nsGlobalWindow* currentInner; + nsGlobalWindow* forwardTo; + if (IsInnerWindow()) { + nsGlobalWindow* outer = GetOuterWindowInternal(); + currentInner = outer ? outer->GetCurrentInnerWindowInternal() : this; + + forwardTo = this; + } else { + currentInner = GetCurrentInnerWindowInternal(); + + // This needs to forward to the inner window, but since the current + // inner may not be the inner in the calling scope, we need to treat + // this specially here as we don't want timeouts registered in a + // dying inner window to get registered and run on the current inner + // window. To get this right, we need to forward this call to the + // inner window that's calling window.setTimeout(). + + forwardTo = CallerInnerWindow(); + if (!forwardTo && nsContentUtils::IsCallerChrome()) { + forwardTo = currentInner; + } + if (!forwardTo) { + aError.Throw(NS_ERROR_NOT_AVAILABLE); + return nullptr; + } + + // If the caller and the callee share the same outer window, forward to the + // caller inner. Else, we forward to the current inner (e.g. someone is + // calling setTimeout() on a reference to some other window). + if (forwardTo->GetOuterWindow() != AsOuter() || + !forwardTo->IsInnerWindow()) { + if (!currentInner) { + NS_WARNING("No inner window available!"); + aError.Throw(NS_ERROR_NOT_INITIALIZED); + return nullptr; + } + + return currentInner; + } + } + + // If forwardTo is not the window with an active document then we want the + // call to setTimeout/Interval to be a noop, so return null but don't set an + // error. + return forwardTo->AsInner()->HasActiveDocument() ? currentInner : nullptr; +} + +int32_t +nsGlobalWindow::SetTimeout(JSContext* aCx, Function& aFunction, + int32_t aTimeout, + const Sequence<JS::Value>& aArguments, + ErrorResult& aError) +{ + return SetTimeoutOrInterval(aCx, aFunction, aTimeout, aArguments, false, + aError); +} + +int32_t +nsGlobalWindow::SetTimeout(JSContext* aCx, const nsAString& aHandler, + int32_t aTimeout, + const Sequence<JS::Value>& /* unused */, + ErrorResult& aError) +{ + return SetTimeoutOrInterval(aCx, aHandler, aTimeout, false, aError); +} + +static bool +IsInterval(const Optional<int32_t>& aTimeout, int32_t& aResultTimeout) +{ + if (aTimeout.WasPassed()) { + aResultTimeout = aTimeout.Value(); + return true; + } + + // If no interval was specified, treat this like a timeout, to avoid setting + // an interval of 0 milliseconds. + aResultTimeout = 0; + return false; +} + +int32_t +nsGlobalWindow::SetInterval(JSContext* aCx, Function& aFunction, + const Optional<int32_t>& aTimeout, + const Sequence<JS::Value>& aArguments, + ErrorResult& aError) +{ + int32_t timeout; + bool isInterval = IsInterval(aTimeout, timeout); + return SetTimeoutOrInterval(aCx, aFunction, timeout, aArguments, isInterval, + aError); +} + +int32_t +nsGlobalWindow::SetInterval(JSContext* aCx, const nsAString& aHandler, + const Optional<int32_t>& aTimeout, + const Sequence<JS::Value>& /* unused */, + ErrorResult& aError) +{ + int32_t timeout; + bool isInterval = IsInterval(aTimeout, timeout); + return SetTimeoutOrInterval(aCx, aHandler, timeout, isInterval, aError); +} + +nsresult +nsGlobalWindow::SetTimeoutOrInterval(nsITimeoutHandler* aHandler, + int32_t interval, bool aIsInterval, + Timeout::Reason aReason, int32_t* aReturn) +{ + MOZ_ASSERT(IsInnerWindow()); + + // If we don't have a document (we could have been unloaded since + // the call to setTimeout was made), do nothing. + if (!mDoc) { + return NS_OK; + } + + // Disallow negative intervals. If aIsInterval also disallow 0, + // because we use that as a "don't repeat" flag. + interval = std::max(aIsInterval ? 1 : 0, interval); + + // Make sure we don't proceed with an interval larger than our timer + // code can handle. (Note: we already forced |interval| to be non-negative, + // so the uint32_t cast (to avoid compiler warnings) is ok.) + uint32_t maxTimeoutMs = PR_IntervalToMilliseconds(DOM_MAX_TIMEOUT_VALUE); + if (static_cast<uint32_t>(interval) > maxTimeoutMs) { + interval = maxTimeoutMs; + } + + RefPtr<Timeout> timeout = new Timeout(); + timeout->mIsInterval = aIsInterval; + timeout->mInterval = interval; + timeout->mScriptHandler = aHandler; + timeout->mReason = aReason; + + // Now clamp the actual interval we will use for the timer based on + uint32_t nestingLevel = sNestingLevel + 1; + uint32_t realInterval = interval; + if (aIsInterval || nestingLevel >= DOM_CLAMP_TIMEOUT_NESTING_LEVEL || + mBackPressureDelayMS > 0) { + // Don't allow timeouts less than DOMMinTimeoutValue() from + // now... + realInterval = std::max(realInterval, uint32_t(DOMMinTimeoutValue())); + } + + TimeDuration delta = TimeDuration::FromMilliseconds(realInterval); + + if (IsFrozen()) { + // If we are frozen simply set timeout->mTimeRemaining to be the + // "time remaining" in the timeout (i.e., the interval itself). This + // will be used to create a new mWhen time when the window is thawed. + // The end effect is that time does not appear to pass for frozen windows. + timeout->mTimeRemaining = delta; + } else { + // Since we are not frozen we must set a precise mWhen target wakeup + // time. Even if we are suspended we want to use this target time so + // that it appears time passes while suspended. + timeout->mWhen = TimeStamp::Now() + delta; + } + + // If we're not suspended, then set the timer. + if (!IsSuspended()) { + MOZ_ASSERT(!timeout->mWhen.IsNull()); + + nsresult rv; + timeout->mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + + RefPtr<Timeout> copy = timeout; + + rv = timeout->InitTimer(GetThrottledEventQueue(), realInterval); + if (NS_FAILED(rv)) { + return rv; + } + + // The timeout is now also held in the timer's closure. + Unused << copy.forget(); + } + + timeout->mWindow = this; + + if (!aIsInterval) { + timeout->mNestingLevel = nestingLevel; + } + + // No popups from timeouts by default + timeout->mPopupState = openAbused; + + if (gRunningTimeoutDepth == 0 && gPopupControlState < openAbused) { + // This timeout is *not* set from another timeout and it's set + // while popups are enabled. Propagate the state to the timeout if + // its delay (interval) is equal to or less than what + // "dom.disable_open_click_delay" is set to (in ms). + + int32_t delay = + Preferences::GetInt("dom.disable_open_click_delay"); + + // This is checking |interval|, not realInterval, on purpose, + // because our lower bound for |realInterval| could be pretty high + // in some cases. + if (interval <= delay) { + timeout->mPopupState = gPopupControlState; + } + } + + InsertTimeoutIntoList(timeout); + + timeout->mTimeoutId = GetTimeoutId(aReason); + *aReturn = timeout->mTimeoutId; + + return NS_OK; +} + +int32_t +nsGlobalWindow::SetTimeoutOrInterval(JSContext *aCx, Function& aFunction, + int32_t aTimeout, + const Sequence<JS::Value>& aArguments, + bool aIsInterval, ErrorResult& aError) +{ + nsGlobalWindow* inner = InnerForSetTimeoutOrInterval(aError); + if (!inner) { + return -1; + } + + if (inner != this) { + return inner->SetTimeoutOrInterval(aCx, aFunction, aTimeout, aArguments, + aIsInterval, aError); + } + + nsCOMPtr<nsIScriptTimeoutHandler> handler = + NS_CreateJSTimeoutHandler(aCx, this, aFunction, aArguments, aError); + if (!handler) { + return 0; + } + + int32_t result; + aError = SetTimeoutOrInterval(handler, aTimeout, aIsInterval, + Timeout::Reason::eTimeoutOrInterval, &result); + return result; +} + +int32_t +nsGlobalWindow::SetTimeoutOrInterval(JSContext* aCx, const nsAString& aHandler, + int32_t aTimeout, bool aIsInterval, + ErrorResult& aError) +{ + nsGlobalWindow* inner = InnerForSetTimeoutOrInterval(aError); + if (!inner) { + return -1; + } + + if (inner != this) { + return inner->SetTimeoutOrInterval(aCx, aHandler, aTimeout, aIsInterval, + aError); + } + + nsCOMPtr<nsIScriptTimeoutHandler> handler = + NS_CreateJSTimeoutHandler(aCx, this, aHandler, aError); + if (!handler) { + return 0; + } + + int32_t result; + aError = SetTimeoutOrInterval(handler, aTimeout, aIsInterval, + Timeout::Reason::eTimeoutOrInterval, &result); + return result; +} + +bool +nsGlobalWindow::RunTimeoutHandler(Timeout* aTimeout, + nsIScriptContext* aScx) +{ + // Hold on to the timeout in case mExpr or mFunObj releases its + // doc. + RefPtr<Timeout> timeout = aTimeout; + Timeout* last_running_timeout = mRunningTimeout; + mRunningTimeout = timeout; + timeout->mRunning = true; + + // Push this timeout's popup control state, which should only be + // eabled the first time a timeout fires that was created while + // popups were enabled and with a delay less than + // "dom.disable_open_click_delay". + nsAutoPopupStatePusher popupStatePusher(timeout->mPopupState); + + // Clear the timeout's popup state, if any, to prevent interval + // timeouts from repeatedly opening poups. + timeout->mPopupState = openAbused; + + ++gRunningTimeoutDepth; + ++mTimeoutFiringDepth; + + bool trackNestingLevel = !timeout->mIsInterval; + uint32_t nestingLevel; + if (trackNestingLevel) { + nestingLevel = sNestingLevel; + sNestingLevel = timeout->mNestingLevel; + } + + const char *reason; + if (timeout->mIsInterval) { + reason = "setInterval handler"; + } else { + reason = "setTimeout handler"; + } + + bool abortIntervalHandler = false; + nsCOMPtr<nsIScriptTimeoutHandler> handler(do_QueryInterface(timeout->mScriptHandler)); + if (handler) { + RefPtr<Function> callback = handler->GetCallback(); + + if (!callback) { + // Evaluate the timeout expression. + const nsAString& script = handler->GetHandlerText(); + + const char* filename = nullptr; + uint32_t lineNo = 0, dummyColumn = 0; + handler->GetLocation(&filename, &lineNo, &dummyColumn); + + // New script entry point required, due to the "Create a script" sub-step of + // http://www.whatwg.org/specs/web-apps/current-work/#timer-initialisation-steps + nsAutoMicroTask mt; + AutoEntryScript aes(this, reason, true); + JS::CompileOptions options(aes.cx()); + options.setFileAndLine(filename, lineNo).setVersion(JSVERSION_DEFAULT); + JS::Rooted<JSObject*> global(aes.cx(), FastGetGlobalJSObject()); + nsresult rv = + nsJSUtils::EvaluateString(aes.cx(), script, global, options); + if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) { + abortIntervalHandler = true; + } + } else { + // Hold strong ref to ourselves while we call the callback. + nsCOMPtr<nsISupports> me(static_cast<nsIDOMWindow*>(this)); + ErrorResult rv; + JS::Rooted<JS::Value> ignoredVal(RootingCx()); + callback->Call(me, handler->GetArgs(), &ignoredVal, rv, reason); + if (rv.IsUncatchableException()) { + abortIntervalHandler = true; + } + + rv.SuppressException(); + } + } else { + nsCOMPtr<nsITimeoutHandler> basicHandler(timeout->mScriptHandler); + nsCOMPtr<nsISupports> kungFuDeathGrip(static_cast<nsIDOMWindow*>(this)); + mozilla::Unused << kungFuDeathGrip; + basicHandler->Call(); + } + + // If we received an uncatchable exception, do not schedule the timeout again. + // This allows the slow script dialog to break easy DoS attacks like + // setInterval(function() { while(1); }, 100); + if (abortIntervalHandler) { + // If it wasn't an interval timer to begin with, this does nothing. If it + // was, we'll treat it as a timeout that we just ran and discard it when + // we return. + timeout->mIsInterval = false; + } + + // We ignore any failures from calling EvaluateString() on the context or + // Call() on a Function here since we're in a loop + // where we're likely to be running timeouts whose OS timers + // didn't fire in time and we don't want to not fire those timers + // now just because execution of one timer failed. We can't + // propagate the error to anyone who cares about it from this + // point anyway, and the script context should have already reported + // the script error in the usual way - so we just drop it. + + // Since we might be processing more timeouts, go ahead and flush the promise + // queue now before we do that. We need to do that while we're still in our + // "running JS is safe" state (e.g. mRunningTimeout is set, timeout->mRunning + // is false). + Promise::PerformMicroTaskCheckpoint(); + + if (trackNestingLevel) { + sNestingLevel = nestingLevel; + } + + --mTimeoutFiringDepth; + --gRunningTimeoutDepth; + + mRunningTimeout = last_running_timeout; + timeout->mRunning = false; + + return timeout->mCleared; +} + +bool +nsGlobalWindow::RescheduleTimeout(Timeout* aTimeout, const TimeStamp& now, + bool aRunningPendingTimeouts) +{ + if (!aTimeout->mIsInterval) { + if (aTimeout->mTimer) { + // The timeout still has an OS timer, and it's not an interval, + // that means that the OS timer could still fire; cancel the OS + // timer and release its reference to the timeout. + aTimeout->mTimer->Cancel(); + aTimeout->mTimer = nullptr; + aTimeout->Release(); + } + return false; + } + + // Compute time to next timeout for interval timer. + // Make sure nextInterval is at least DOMMinTimeoutValue(). + TimeDuration nextInterval = + TimeDuration::FromMilliseconds(std::max(aTimeout->mInterval, + uint32_t(DOMMinTimeoutValue()))); + + // If we're running pending timeouts, set the next interval to be + // relative to "now", and not to when the timeout that was pending + // should have fired. + TimeStamp firingTime; + if (aRunningPendingTimeouts) { + firingTime = now + nextInterval; + } else { + firingTime = aTimeout->mWhen + nextInterval; + } + + TimeStamp currentNow = TimeStamp::Now(); + TimeDuration delay = firingTime - currentNow; + + // And make sure delay is nonnegative; that might happen if the timer + // thread is firing our timers somewhat early or if they're taking a long + // time to run the callback. + if (delay < TimeDuration(0)) { + delay = TimeDuration(0); + } + + if (!aTimeout->mTimer) { + if (IsFrozen()) { + aTimeout->mTimeRemaining = delay; + } else if (IsSuspended()) { + aTimeout->mWhen = currentNow + delay; + } else { + MOZ_ASSERT_UNREACHABLE("Window should be frozen or suspended."); + } + return true; + } + + aTimeout->mWhen = currentNow + delay; + + // Reschedule the OS timer. Don't bother returning any error codes if + // this fails since the callers of this method don't care about them. + nsresult rv = aTimeout->InitTimer(GetThrottledEventQueue(), + delay.ToMilliseconds()); + + if (NS_FAILED(rv)) { + NS_ERROR("Error initializing timer for DOM timeout!"); + + // We failed to initialize the new OS timer, this timer does + // us no good here so we just cancel it (just in case) and + // null out the pointer to the OS timer, this will release the + // OS timer. As we continue executing the code below we'll end + // up deleting the timeout since it's not an interval timeout + // any more (since timeout->mTimer == nullptr). + aTimeout->mTimer->Cancel(); + aTimeout->mTimer = nullptr; + + // Now that the OS timer no longer has a reference to the + // timeout we need to drop that reference. + aTimeout->Release(); + + return false; + } + + return true; +} + +void +nsGlobalWindow::RunTimeout(Timeout* aTimeout) +{ + if (IsSuspended()) { + return; + } + + NS_ASSERTION(IsInnerWindow(), "Timeout running on outer window!"); + NS_ASSERTION(!IsFrozen(), "Timeout running on a window in the bfcache!"); + + Timeout* nextTimeout; + Timeout* last_expired_timeout; + Timeout* last_insertion_point; + uint32_t firingDepth = mTimeoutFiringDepth + 1; + + // Make sure that the window and the script context don't go away as + // a result of running timeouts + nsCOMPtr<nsIScriptGlobalObject> windowKungFuDeathGrip(this); + + // A native timer has gone off. See which of our timeouts need + // servicing + TimeStamp now = TimeStamp::Now(); + TimeStamp deadline; + + if (aTimeout && aTimeout->mWhen > now) { + // The OS timer fired early (which can happen due to the timers + // having lower precision than TimeStamp does). Set |deadline| to + // be the time when the OS timer *should* have fired so that any + // timers that *should* have fired before aTimeout *will* be fired + // now. + + deadline = aTimeout->mWhen; + } else { + deadline = now; + } + + uint32_t numTimersToRun = 0; + bool targetTimerSeen = false; + + + // The timeout list is kept in deadline order. Discover the latest timeout + // whose deadline has expired. On some platforms, native timeout events fire + // "early", but we handled that above by setting deadline to aTimeout->mWhen + // if the timer fired early. So we can stop walking if we get to timeouts + // whose mWhen is greater than deadline, since once that happens we know + // nothing past that point is expired. + last_expired_timeout = nullptr; + for (Timeout* timeout = mTimeouts.getFirst(); + timeout && timeout->mWhen <= deadline; + timeout = timeout->getNext()) { + if (timeout->mFiringDepth == 0) { + // Mark any timeouts that are on the list to be fired with the + // firing depth so that we can reentrantly run timeouts + timeout->mFiringDepth = firingDepth; + last_expired_timeout = timeout; + + // Note that we have seen our target timer. This means we can now + // stop processing timers once we hit our threshold below. + if (timeout == aTimeout) { + targetTimerSeen = true; + } + + // Run only a limited number of timers based on the configured + // maximum. Note, we must always run our target timer however. + // Further timers that are ready will get picked up by their own + // nsITimer runnables when they execute. + // + // For chrome windows, however, we do coalesce all timers and + // do not yield the main thread. This is partly because we + // trust chrome windows not to misbehave and partly because a + // number of browser chrome tests have races that depend on this + // coalescing. + if (targetTimerSeen && + numTimersToRun >= gTargetMaxConsecutiveCallbacks && + !IsChromeWindow()) { + break; + } + + numTimersToRun += 1; + } + } + + // Maybe the timeout that the event was fired for has been deleted + // and there are no others timeouts with deadlines that make them + // eligible for execution yet. Go away. + if (!last_expired_timeout) { + return; + } + + // Record telemetry information about timers set recently. + TimeDuration recordingInterval = TimeDuration::FromMilliseconds(STATISTICS_INTERVAL); + if (gLastRecordedRecentTimeouts.IsNull() || + now - gLastRecordedRecentTimeouts > recordingInterval) { + gLastRecordedRecentTimeouts = now; + } + + // Insert a dummy timeout into the list of timeouts between the + // portion of the list that we are about to process now and those + // timeouts that will be processed in a future call to + // win_run_timeout(). This dummy timeout serves as the head of the + // list for any timeouts inserted as a result of running a timeout. + RefPtr<Timeout> dummy_timeout = new Timeout(); + dummy_timeout->mFiringDepth = firingDepth; + dummy_timeout->mWhen = now; + last_expired_timeout->setNext(dummy_timeout); + RefPtr<Timeout> timeoutExtraRef(dummy_timeout); + + last_insertion_point = mTimeoutInsertionPoint; + // If we ever start setting mTimeoutInsertionPoint to a non-dummy timeout, + // the logic in ResetTimersForThrottleReduction will need to change. + mTimeoutInsertionPoint = dummy_timeout; + + for (Timeout* timeout = mTimeouts.getFirst(); + timeout != dummy_timeout && !IsFrozen(); + timeout = nextTimeout) { + nextTimeout = timeout->getNext(); + + if (timeout->mFiringDepth != firingDepth) { + // We skip the timeout since it's on the list to run at another + // depth. + + continue; + } + + if (IsSuspended()) { + // Some timer did suspend us. Make sure the + // rest of the timers get executed later. + timeout->mFiringDepth = 0; + continue; + } + + // The timeout is on the list to run at this depth, go ahead and + // process it. + + // Get the script context (a strong ref to prevent it going away) + // for this timeout and ensure the script language is enabled. + nsCOMPtr<nsIScriptContext> scx = GetContextInternal(); + + if (!scx) { + // No context means this window was closed or never properly + // initialized for this language. + continue; + } + + // This timeout is good to run + bool timeout_was_cleared = RunTimeoutHandler(timeout, scx); + + if (timeout_was_cleared) { + // The running timeout's window was cleared, this means that + // ClearAllTimeouts() was called from a *nested* call, possibly + // through a timeout that fired while a modal (to this window) + // dialog was open or through other non-obvious paths. + MOZ_ASSERT(dummy_timeout->HasRefCntOne(), "dummy_timeout may leak"); + Unused << timeoutExtraRef.forget().take(); + + mTimeoutInsertionPoint = last_insertion_point; + + return; + } + + // If we have a regular interval timer, we re-schedule the + // timeout, accounting for clock drift. + bool needsReinsertion = RescheduleTimeout(timeout, now, !aTimeout); + + // Running a timeout can cause another timeout to be deleted, so + // we need to reset the pointer to the following timeout. + nextTimeout = timeout->getNext(); + + timeout->remove(); + + if (needsReinsertion) { + // Insert interval timeout onto list sorted in deadline order. + // AddRefs timeout. + InsertTimeoutIntoList(timeout); + } + + // Release the timeout struct since it's possibly out of the list + timeout->Release(); + } + + // Take the dummy timeout off the head of the list + dummy_timeout->remove(); + timeoutExtraRef = nullptr; + MOZ_ASSERT(dummy_timeout->HasRefCntOne(), "dummy_timeout may leak"); + + mTimeoutInsertionPoint = last_insertion_point; + + MaybeApplyBackPressure(); +} + +void +nsGlobalWindow::ClearTimeoutOrInterval(int32_t aTimerId, Timeout::Reason aReason) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + uint32_t timerId = (uint32_t)aTimerId; + Timeout* timeout; + + for (timeout = mTimeouts.getFirst(); timeout; timeout = timeout->getNext()) { + if (timeout->mTimeoutId == timerId && timeout->mReason == aReason) { + if (timeout->mRunning) { + /* We're running from inside the timeout. Mark this + timeout for deferred deletion by the code in + RunTimeout() */ + timeout->mIsInterval = false; + } + else { + /* Delete the timeout from the pending timeout list */ + timeout->remove(); + + if (timeout->mTimer) { + timeout->mTimer->Cancel(); + timeout->mTimer = nullptr; + timeout->Release(); + } + timeout->Release(); + } + break; + } + } +} + +nsresult nsGlobalWindow::ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS) +{ + FORWARD_TO_INNER(ResetTimersForThrottleReduction, (aPreviousThrottleDelayMS), + NS_ERROR_NOT_INITIALIZED); + MOZ_ASSERT(aPreviousThrottleDelayMS > 0); + + if (IsFrozen() || IsSuspended()) { + return NS_OK; + } + + TimeStamp now = TimeStamp::Now(); + + // If mTimeoutInsertionPoint is non-null, we're in the middle of firing + // timers and the timers we're planning to fire all come before + // mTimeoutInsertionPoint; mTimeoutInsertionPoint itself is a dummy timeout + // with an mWhen that may be semi-bogus. In that case, we don't need to do + // anything with mTimeoutInsertionPoint or anything before it, so should + // start at the timer after mTimeoutInsertionPoint, if there is one. + // Otherwise, start at the beginning of the list. + for (Timeout* timeout = mTimeoutInsertionPoint ? + mTimeoutInsertionPoint->getNext() : mTimeouts.getFirst(); + timeout; ) { + // It's important that this check be <= so that we guarantee that + // taking std::max with |now| won't make a quantity equal to + // timeout->mWhen below. + if (timeout->mWhen <= now) { + timeout = timeout->getNext(); + continue; + } + + if (timeout->mWhen - now > + TimeDuration::FromMilliseconds(aPreviousThrottleDelayMS)) { + // No need to loop further. Timeouts are sorted in mWhen order + // and the ones after this point were all set up for at least + // gMinBackgroundTimeoutValue ms and hence were not clamped. + break; + } + + // We reduced our throttled delay. Re-init the timer appropriately. + // Compute the interval the timer should have had if it had not been set in a + // background window + TimeDuration interval = + TimeDuration::FromMilliseconds(std::max(timeout->mInterval, + uint32_t(DOMMinTimeoutValue()))); + uint32_t oldIntervalMillisecs = 0; + timeout->mTimer->GetDelay(&oldIntervalMillisecs); + TimeDuration oldInterval = TimeDuration::FromMilliseconds(oldIntervalMillisecs); + if (oldInterval > interval) { + // unclamp + TimeStamp firingTime = + std::max(timeout->mWhen - oldInterval + interval, now); + + NS_ASSERTION(firingTime < timeout->mWhen, + "Our firing time should strictly decrease!"); + + TimeDuration delay = firingTime - now; + timeout->mWhen = firingTime; + + // Since we reset mWhen we need to move |timeout| to the right + // place in the list so that it remains sorted by mWhen. + + // Get the pointer to the next timeout now, before we move the + // current timeout in the list. + Timeout* nextTimeout = timeout->getNext(); + + // Since we are only reducing intervals in this method we can + // make an optimization here. If the reduction does not cause us + // to fall before our previous timeout then we do not have to + // remove and re-insert the current timeout. This is important + // because re-insertion makes this algorithm O(n^2). Since we + // will typically be shifting a lot of timers at once this + // optimization saves us a lot of work. + Timeout* prevTimeout = timeout->getPrevious(); + if (prevTimeout && prevTimeout->mWhen > timeout->mWhen) { + // It is safe to remove and re-insert because mWhen is now + // strictly smaller than it used to be, so we know we'll insert + // |timeout| before nextTimeout. + NS_ASSERTION(!nextTimeout || + timeout->mWhen < nextTimeout->mWhen, "How did that happen?"); + timeout->remove(); + // InsertTimeoutIntoList will addref |timeout| and reset + // mFiringDepth. Make sure to undo that after calling it. + uint32_t firingDepth = timeout->mFiringDepth; + InsertTimeoutIntoList(timeout); + timeout->mFiringDepth = firingDepth; + timeout->Release(); + } + + nsresult rv = timeout->InitTimer(GetThrottledEventQueue(), + delay.ToMilliseconds()); + + if (NS_FAILED(rv)) { + NS_WARNING("Error resetting non background timer for DOM timeout!"); + return rv; + } + + timeout = nextTimeout; + } else { + timeout = timeout->getNext(); + } + } + + return NS_OK; +} + +void +nsGlobalWindow::ClearAllTimeouts() +{ + Timeout* timeout; + Timeout* nextTimeout; + + for (timeout = mTimeouts.getFirst(); timeout; timeout = nextTimeout) { + /* If RunTimeout() is higher up on the stack for this + window, e.g. as a result of document.write from a timeout, + then we need to reset the list insertion point for + newly-created timeouts in case the user adds a timeout, + before we pop the stack back to RunTimeout. */ + if (mRunningTimeout == timeout) + mTimeoutInsertionPoint = nullptr; + + nextTimeout = timeout->getNext(); + + if (timeout->mTimer) { + timeout->mTimer->Cancel(); + timeout->mTimer = nullptr; + + // Drop the count since the timer isn't going to hold on + // anymore. + timeout->Release(); + } + + // Set timeout->mCleared to true to indicate that the timeout was + // cleared and taken out of the list of timeouts + timeout->mCleared = true; + + // Drop the count since we're removing it from the list. + timeout->Release(); + } + + // Clear out our list + mTimeouts.clear(); +} + +void +nsGlobalWindow::InsertTimeoutIntoList(Timeout* aTimeout) +{ + NS_ASSERTION(IsInnerWindow(), + "InsertTimeoutIntoList() called on outer window!"); + + // Start at mLastTimeout and go backwards. Don't go further than + // mTimeoutInsertionPoint, though. This optimizes for the common case of + // insertion at the end. + Timeout* prevSibling; + for (prevSibling = mTimeouts.getLast(); + prevSibling && prevSibling != mTimeoutInsertionPoint && + // This condition needs to match the one in SetTimeoutOrInterval that + // determines whether to set mWhen or mTimeRemaining. + (IsFrozen() ? + prevSibling->mTimeRemaining > aTimeout->mTimeRemaining : + prevSibling->mWhen > aTimeout->mWhen); + prevSibling = prevSibling->getPrevious()) { + /* Do nothing; just searching */ + } + + // Now link in aTimeout after prevSibling. + if (prevSibling) { + prevSibling->setNext(aTimeout); + } else { + mTimeouts.insertFront(aTimeout); + } + + aTimeout->mFiringDepth = 0; + + // Increment the timeout's reference count since it's now held on to + // by the list + aTimeout->AddRef(); +} + +//***************************************************************************** +// nsGlobalWindow: Helper Functions +//***************************************************************************** + +already_AddRefed<nsIDocShellTreeOwner> +nsGlobalWindow::GetTreeOwner() +{ + FORWARD_TO_OUTER(GetTreeOwner, (), nullptr); + + // If there's no docShellAsItem, this window must have been closed, + // in that case there is no tree owner. + + if (!mDocShell) { + return nullptr; + } + + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + mDocShell->GetTreeOwner(getter_AddRefs(treeOwner)); + return treeOwner.forget(); +} + +already_AddRefed<nsIBaseWindow> +nsGlobalWindow::GetTreeOwnerWindow() +{ + MOZ_ASSERT(IsOuterWindow()); + + nsCOMPtr<nsIDocShellTreeOwner> treeOwner; + + // If there's no mDocShell, this window must have been closed, + // in that case there is no tree owner. + + if (mDocShell) { + mDocShell->GetTreeOwner(getter_AddRefs(treeOwner)); + } + + nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(treeOwner); + return baseWindow.forget(); +} + +already_AddRefed<nsIWebBrowserChrome> +nsGlobalWindow::GetWebBrowserChrome() +{ + nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner(); + + nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(treeOwner); + return browserChrome.forget(); +} + +nsIScrollableFrame * +nsGlobalWindow::GetScrollFrame() +{ + FORWARD_TO_OUTER(GetScrollFrame, (), nullptr); + + if (!mDocShell) { + return nullptr; + } + + nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell(); + if (presShell) { + return presShell->GetRootScrollFrameAsScrollable(); + } + return nullptr; +} + +nsresult +nsGlobalWindow::SecurityCheckURL(const char *aURL) +{ + nsCOMPtr<nsPIDOMWindowInner> sourceWindow = do_QueryInterface(GetEntryGlobal()); + if (!sourceWindow) { + sourceWindow = AsOuter()->GetCurrentInnerWindow(); + } + AutoJSContext cx; + nsGlobalWindow* sourceWin = nsGlobalWindow::Cast(sourceWindow); + JSAutoCompartment ac(cx, sourceWin->GetGlobalJSObject()); + + // Resolve the baseURI, which could be relative to the calling window. + // + // Note the algorithm to get the base URI should match the one + // used to actually kick off the load in nsWindowWatcher.cpp. + nsCOMPtr<nsIDocument> doc = sourceWindow->GetDoc(); + nsIURI* baseURI = nullptr; + nsAutoCString charset(NS_LITERAL_CSTRING("UTF-8")); // default to utf-8 + if (doc) { + baseURI = doc->GetDocBaseURI(); + charset = doc->GetDocumentCharacterSet(); + } + nsCOMPtr<nsIURI> uri; + nsresult rv = NS_NewURI(getter_AddRefs(uri), nsDependentCString(aURL), + charset.get(), baseURI); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (NS_FAILED(nsContentUtils::GetSecurityManager()-> + CheckLoadURIFromScript(cx, uri))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +bool +nsGlobalWindow::IsPrivateBrowsing() +{ + nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(GetDocShell()); + return loadContext && loadContext->UsePrivateBrowsing(); +} + +void +nsGlobalWindow::FlushPendingNotifications(mozFlushType aType) +{ + if (mDoc) { + mDoc->FlushPendingNotifications(aType); + } +} + +void +nsGlobalWindow::EnsureSizeUpToDate() +{ + MOZ_ASSERT(IsOuterWindow()); + + // If we're a subframe, make sure our size is up to date. It's OK that this + // crosses the content/chrome boundary, since chrome can have pending reflows + // too. + nsGlobalWindow *parent = nsGlobalWindow::Cast(GetPrivateParent()); + if (parent) { + parent->FlushPendingNotifications(Flush_Layout); + } +} + +already_AddRefed<nsISupports> +nsGlobalWindow::SaveWindowState() +{ + NS_PRECONDITION(IsOuterWindow(), "Can't save the inner window's state"); + + if (!mContext || !GetWrapperPreserveColor()) { + // The window may be getting torn down; don't bother saving state. + return nullptr; + } + + nsGlobalWindow *inner = GetCurrentInnerWindowInternal(); + NS_ASSERTION(inner, "No inner window to save"); + + // Don't do anything else to this inner window! After this point, all + // calls to SetTimeoutOrInterval will create entries in the timeout + // list that will only run after this window has come out of the bfcache. + // Also, while we're frozen, we won't dispatch online/offline events + // to the page. + inner->Freeze(); + + nsCOMPtr<nsISupports> state = new WindowStateHolder(inner); + +#ifdef DEBUG_PAGE_CACHE + printf("saving window state, state = %p\n", (void*)state); +#endif + + return state.forget(); +} + +nsresult +nsGlobalWindow::RestoreWindowState(nsISupports *aState) +{ + NS_ASSERTION(IsOuterWindow(), "Cannot restore an inner window"); + + if (!mContext || !GetWrapperPreserveColor()) { + // The window may be getting torn down; don't bother restoring state. + return NS_OK; + } + + nsCOMPtr<WindowStateHolder> holder = do_QueryInterface(aState); + NS_ENSURE_TRUE(holder, NS_ERROR_FAILURE); + +#ifdef DEBUG_PAGE_CACHE + printf("restoring window state, state = %p\n", (void*)holder); +#endif + + // And we're ready to go! + nsGlobalWindow *inner = GetCurrentInnerWindowInternal(); + + // if a link is focused, refocus with the FLAG_SHOWRING flag set. This makes + // it easy to tell which link was last clicked when going back a page. + nsIContent* focusedNode = inner->GetFocusedNode(); + if (IsLink(focusedNode)) { + nsIFocusManager* fm = nsFocusManager::GetFocusManager(); + if (fm) { + nsCOMPtr<nsIDOMElement> focusedElement(do_QueryInterface(focusedNode)); + fm->SetFocus(focusedElement, nsIFocusManager::FLAG_NOSCROLL | + nsIFocusManager::FLAG_SHOWRING); + } + } + + inner->Thaw(); + + holder->DidRestoreWindow(); + + return NS_OK; +} + +void +nsGlobalWindow::EnableDeviceSensor(uint32_t aType) +{ + MOZ_ASSERT(IsInnerWindow()); + + bool alreadyEnabled = false; + for (uint32_t i = 0; i < mEnabledSensors.Length(); i++) { + if (mEnabledSensors[i] == aType) { + alreadyEnabled = true; + break; + } + } + + mEnabledSensors.AppendElement(aType); + + if (alreadyEnabled) { + return; + } + + nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID); + if (ac) { + ac->AddWindowListener(aType, this); + } +} + +void +nsGlobalWindow::DisableDeviceSensor(uint32_t aType) +{ + MOZ_ASSERT(IsInnerWindow()); + + int32_t doomedElement = -1; + int32_t listenerCount = 0; + for (uint32_t i = 0; i < mEnabledSensors.Length(); i++) { + if (mEnabledSensors[i] == aType) { + doomedElement = i; + listenerCount++; + } + } + + if (doomedElement == -1) { + return; + } + + mEnabledSensors.RemoveElementAt(doomedElement); + + if (listenerCount > 1) { + return; + } + + nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID); + if (ac) { + ac->RemoveWindowListener(aType, this); + } +} + +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) +void +nsGlobalWindow::EnableOrientationChangeListener() +{ + MOZ_ASSERT(IsInnerWindow()); + if (!nsContentUtils::ShouldResistFingerprinting(mDocShell) && + !mOrientationChangeObserver) { + mOrientationChangeObserver = + new WindowOrientationObserver(this); + } +} + +void +nsGlobalWindow::DisableOrientationChangeListener() +{ + MOZ_ASSERT(IsInnerWindow()); + + mOrientationChangeObserver = nullptr; +} +#endif + +void +nsGlobalWindow::SetHasGamepadEventListener(bool aHasGamepad/* = true*/) +{ + MOZ_ASSERT(IsInnerWindow()); + mHasGamepad = aHasGamepad; + if (aHasGamepad) { + EnableGamepadUpdates(); + } +} + + +void +nsGlobalWindow::EventListenerAdded(nsIAtom* aType) +{ + if (aType == nsGkAtoms::onvrdisplayconnect || + aType == nsGkAtoms::onvrdisplaydisconnect || + aType == nsGkAtoms::onvrdisplaypresentchange) { + NotifyVREventListenerAdded(); + } +} + +void +nsGlobalWindow::NotifyVREventListenerAdded() +{ + MOZ_ASSERT(IsInnerWindow()); + mHasVREvents = true; + EnableVRUpdates(); +} + +void +nsGlobalWindow::EnableTimeChangeNotifications() +{ + mozilla::time::AddWindowListener(AsInner()); +} + +void +nsGlobalWindow::DisableTimeChangeNotifications() +{ + mozilla::time::RemoveWindowListener(AsInner()); +} + +void +nsGlobalWindow::AddSizeOfIncludingThis(nsWindowSizes* aWindowSizes) const +{ + aWindowSizes->mDOMOtherSize += aWindowSizes->mMallocSizeOf(this); + + if (IsInnerWindow()) { + EventListenerManager* elm = GetExistingListenerManager(); + if (elm) { + aWindowSizes->mDOMOtherSize += + elm->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf); + aWindowSizes->mDOMEventListenersCount += + elm->ListenerCount(); + } + if (mDoc) { + // Multiple global windows can share a document. So only measure the + // document if it (a) doesn't have a global window, or (b) it's the + // primary document for the window. + if (!mDoc->GetInnerWindow() || + mDoc->GetInnerWindow() == AsInner()) { + mDoc->DocAddSizeOfIncludingThis(aWindowSizes); + } + } + } + + if (mNavigator) { + aWindowSizes->mDOMOtherSize += + mNavigator->SizeOfIncludingThis(aWindowSizes->mMallocSizeOf); + } + + aWindowSizes->mDOMEventTargetsSize += + mEventTargetObjects.ShallowSizeOfExcludingThis(aWindowSizes->mMallocSizeOf); + + for (auto iter = mEventTargetObjects.ConstIter(); !iter.Done(); iter.Next()) { + DOMEventTargetHelper* et = iter.Get()->GetKey(); + if (nsCOMPtr<nsISizeOfEventTarget> iSizeOf = do_QueryObject(et)) { + aWindowSizes->mDOMEventTargetsSize += + iSizeOf->SizeOfEventTargetIncludingThis(aWindowSizes->mMallocSizeOf); + } + if (EventListenerManager* elm = et->GetExistingListenerManager()) { + aWindowSizes->mDOMEventListenersCount += elm->ListenerCount(); + } + ++aWindowSizes->mDOMEventTargetsCount; + } +} + + +#ifdef MOZ_GAMEPAD +void +nsGlobalWindow::AddGamepad(uint32_t aIndex, Gamepad* aGamepad) +{ + MOZ_ASSERT(IsInnerWindow()); + // Create the index we will present to content based on which indices are + // already taken, as required by the spec. + // https://w3c.github.io/gamepad/gamepad.html#widl-Gamepad-index + int index = 0; + while(mGamepadIndexSet.Contains(index)) { + ++index; + } + mGamepadIndexSet.Put(index); + aGamepad->SetIndex(index); + mGamepads.Put(aIndex, aGamepad); +} + +void +nsGlobalWindow::RemoveGamepad(uint32_t aIndex) +{ + MOZ_ASSERT(IsInnerWindow()); + RefPtr<Gamepad> gamepad; + if (!mGamepads.Get(aIndex, getter_AddRefs(gamepad))) { + return; + } + // Free up the index we were using so it can be reused + mGamepadIndexSet.Remove(gamepad->Index()); + mGamepads.Remove(aIndex); +} + +void +nsGlobalWindow::GetGamepads(nsTArray<RefPtr<Gamepad> >& aGamepads) +{ + MOZ_ASSERT(IsInnerWindow()); + aGamepads.Clear(); + // mGamepads.Count() may not be sufficient, but it's not harmful. + aGamepads.SetCapacity(mGamepads.Count()); + for (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) { + Gamepad* gamepad = iter.UserData(); + aGamepads.EnsureLengthAtLeast(gamepad->Index() + 1); + aGamepads[gamepad->Index()] = gamepad; + } +} + +already_AddRefed<Gamepad> +nsGlobalWindow::GetGamepad(uint32_t aIndex) +{ + MOZ_ASSERT(IsInnerWindow()); + RefPtr<Gamepad> gamepad; + + if (mGamepads.Get(aIndex, getter_AddRefs(gamepad))) { + return gamepad.forget(); + } + + return nullptr; +} + +void +nsGlobalWindow::SetHasSeenGamepadInput(bool aHasSeen) +{ + MOZ_ASSERT(IsInnerWindow()); + mHasSeenGamepadInput = aHasSeen; +} + +bool +nsGlobalWindow::HasSeenGamepadInput() +{ + MOZ_ASSERT(IsInnerWindow()); + return mHasSeenGamepadInput; +} + +void +nsGlobalWindow::SyncGamepadState() +{ + MOZ_ASSERT(IsInnerWindow()); + if (mHasSeenGamepadInput) { + RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService()); + for (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) { + gamepadManager->SyncGamepadState(iter.Key(), iter.UserData()); + } + } +} +#endif // MOZ_GAMEPAD + +bool +nsGlobalWindow::UpdateVRDisplays(nsTArray<RefPtr<mozilla::dom::VRDisplay>>& aDevices) +{ + FORWARD_TO_INNER(UpdateVRDisplays, (aDevices), false); + + VRDisplay::UpdateVRDisplays(mVRDisplays, AsInner()); + aDevices = mVRDisplays; + return true; +} + +void +nsGlobalWindow::NotifyActiveVRDisplaysChanged() +{ + MOZ_ASSERT(IsInnerWindow()); + + if (mNavigator) { + mNavigator->NotifyActiveVRDisplaysChanged(); + } +} + + +// nsGlobalChromeWindow implementation + +NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalChromeWindow) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsGlobalChromeWindow, + nsGlobalWindow) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserDOMWindow) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMessageManager) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGroupMessageManagers) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOpenerForInitialContentBrowser) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsGlobalChromeWindow, + nsGlobalWindow) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserDOMWindow) + if (tmp->mMessageManager) { + static_cast<nsFrameMessageManager*>( + tmp->mMessageManager.get())->Disconnect(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mMessageManager) + } + tmp->DisconnectAndClearGroupMessageManagers(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mGroupMessageManagers) + NS_IMPL_CYCLE_COLLECTION_UNLINK(mOpenerForInitialContentBrowser) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +// QueryInterface implementation for nsGlobalChromeWindow +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsGlobalChromeWindow) + NS_INTERFACE_MAP_ENTRY(nsIDOMChromeWindow) +NS_INTERFACE_MAP_END_INHERITING(nsGlobalWindow) + +NS_IMPL_ADDREF_INHERITED(nsGlobalChromeWindow, nsGlobalWindow) +NS_IMPL_RELEASE_INHERITED(nsGlobalChromeWindow, nsGlobalWindow) + +/* static */ already_AddRefed<nsGlobalChromeWindow> +nsGlobalChromeWindow::Create(nsGlobalWindow *aOuterWindow) +{ + RefPtr<nsGlobalChromeWindow> window = new nsGlobalChromeWindow(aOuterWindow); + window->InitWasOffline(); + return window.forget(); +} + +NS_IMETHODIMP +nsGlobalChromeWindow::GetWindowState(uint16_t* aWindowState) +{ + FORWARD_TO_INNER_CHROME(GetWindowState, (aWindowState), NS_ERROR_UNEXPECTED); + + *aWindowState = WindowState(); + return NS_OK; +} + +uint16_t +nsGlobalWindow::WindowState() +{ + MOZ_ASSERT(IsInnerWindow()); + nsCOMPtr<nsIWidget> widget = GetMainWidget(); + + int32_t mode = widget ? widget->SizeMode() : 0; + + switch (mode) { + case nsSizeMode_Minimized: + return nsIDOMChromeWindow::STATE_MINIMIZED; + case nsSizeMode_Maximized: + return nsIDOMChromeWindow::STATE_MAXIMIZED; + case nsSizeMode_Fullscreen: + return nsIDOMChromeWindow::STATE_FULLSCREEN; + case nsSizeMode_Normal: + return nsIDOMChromeWindow::STATE_NORMAL; + default: + NS_WARNING("Illegal window state for this chrome window"); + break; + } + + return nsIDOMChromeWindow::STATE_NORMAL; +} + +NS_IMETHODIMP +nsGlobalChromeWindow::Maximize() +{ + FORWARD_TO_INNER_CHROME(Maximize, (), NS_ERROR_UNEXPECTED); + + nsGlobalWindow::Maximize(); + return NS_OK; +} + +void +nsGlobalWindow::Maximize() +{ + MOZ_ASSERT(IsInnerWindow()); + + nsCOMPtr<nsIWidget> widget = GetMainWidget(); + + if (widget) { + widget->SetSizeMode(nsSizeMode_Maximized); + } +} + +NS_IMETHODIMP +nsGlobalChromeWindow::Minimize() +{ + FORWARD_TO_INNER_CHROME(Minimize, (), NS_ERROR_UNEXPECTED); + + nsGlobalWindow::Minimize(); + return NS_OK; +} + +void +nsGlobalWindow::Minimize() +{ + MOZ_ASSERT(IsInnerWindow()); + + nsCOMPtr<nsIWidget> widget = GetMainWidget(); + + if (widget) { + widget->SetSizeMode(nsSizeMode_Minimized); + } +} + +NS_IMETHODIMP +nsGlobalChromeWindow::Restore() +{ + FORWARD_TO_INNER_CHROME(Restore, (), NS_ERROR_UNEXPECTED); + + nsGlobalWindow::Restore(); + return NS_OK; +} + +void +nsGlobalWindow::Restore() +{ + MOZ_ASSERT(IsInnerWindow()); + + nsCOMPtr<nsIWidget> widget = GetMainWidget(); + + if (widget) { + widget->SetSizeMode(nsSizeMode_Normal); + } +} + +NS_IMETHODIMP +nsGlobalChromeWindow::GetAttention() +{ + FORWARD_TO_INNER_CHROME(GetAttention, (), NS_ERROR_UNEXPECTED); + + ErrorResult rv; + GetAttention(rv); + return rv.StealNSResult(); +} + +void +nsGlobalWindow::GetAttention(ErrorResult& aResult) +{ + MOZ_ASSERT(IsInnerWindow()); + return GetAttentionWithCycleCount(-1, aResult); +} + +NS_IMETHODIMP +nsGlobalChromeWindow::GetAttentionWithCycleCount(int32_t aCycleCount) +{ + FORWARD_TO_INNER_CHROME(GetAttentionWithCycleCount, (aCycleCount), NS_ERROR_UNEXPECTED); + + ErrorResult rv; + GetAttentionWithCycleCount(aCycleCount, rv); + return rv.StealNSResult(); +} + +void +nsGlobalWindow::GetAttentionWithCycleCount(int32_t aCycleCount, + ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + + nsCOMPtr<nsIWidget> widget = GetMainWidget(); + + if (widget) { + aError = widget->GetAttention(aCycleCount); + } +} + +NS_IMETHODIMP +nsGlobalChromeWindow::BeginWindowMove(nsIDOMEvent *aMouseDownEvent, nsIDOMElement* aPanel) +{ + FORWARD_TO_INNER_CHROME(BeginWindowMove, (aMouseDownEvent, aPanel), NS_ERROR_UNEXPECTED); + + NS_ENSURE_TRUE(aMouseDownEvent, NS_ERROR_FAILURE); + Event* mouseDownEvent = aMouseDownEvent->InternalDOMEvent(); + NS_ENSURE_TRUE(mouseDownEvent, NS_ERROR_FAILURE); + + nsCOMPtr<Element> panel = do_QueryInterface(aPanel); + NS_ENSURE_TRUE(panel || !aPanel, NS_ERROR_FAILURE); + + ErrorResult rv; + BeginWindowMove(*mouseDownEvent, panel, rv); + return rv.StealNSResult(); +} + +void +nsGlobalWindow::BeginWindowMove(Event& aMouseDownEvent, Element* aPanel, + ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + + nsCOMPtr<nsIWidget> widget; + + // if a panel was supplied, use its widget instead. +#ifdef MOZ_XUL + if (aPanel) { + nsIFrame* frame = aPanel->GetPrimaryFrame(); + if (!frame || frame->GetType() != nsGkAtoms::menuPopupFrame) { + return; + } + + widget = (static_cast<nsMenuPopupFrame*>(frame))->GetWidget(); + } + else { +#endif + widget = GetMainWidget(); +#ifdef MOZ_XUL + } +#endif + + if (!widget) { + return; + } + + WidgetMouseEvent* mouseEvent = + aMouseDownEvent.WidgetEventPtr()->AsMouseEvent(); + if (!mouseEvent || mouseEvent->mClass != eMouseEventClass) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + aError = widget->BeginMoveDrag(mouseEvent); +} + +already_AddRefed<nsWindowRoot> +nsGlobalWindow::GetWindowRootOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + nsCOMPtr<nsPIWindowRoot> root = GetTopWindowRoot(); + return root.forget().downcast<nsWindowRoot>(); +} + +already_AddRefed<nsWindowRoot> +nsGlobalWindow::GetWindowRoot(mozilla::ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetWindowRootOuter, (), aError, nullptr); +} + +//Note: This call will lock the cursor, it will not change as it moves. +//To unlock, the cursor must be set back to CURSOR_AUTO. +NS_IMETHODIMP +nsGlobalChromeWindow::SetCursor(const nsAString& aCursor) +{ + FORWARD_TO_INNER_CHROME(SetCursor, (aCursor), NS_ERROR_UNEXPECTED); + + ErrorResult rv; + SetCursor(aCursor, rv); + return rv.StealNSResult(); +} + +void +nsGlobalWindow::SetCursorOuter(const nsAString& aCursor, ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + int32_t cursor; + + if (aCursor.EqualsLiteral("auto")) + cursor = NS_STYLE_CURSOR_AUTO; + else { + nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(aCursor); + if (eCSSKeyword_UNKNOWN == keyword || + !nsCSSProps::FindKeyword(keyword, nsCSSProps::kCursorKTable, cursor)) { + return; + } + } + + RefPtr<nsPresContext> presContext; + if (mDocShell) { + mDocShell->GetPresContext(getter_AddRefs(presContext)); + } + + if (presContext) { + // Need root widget. + nsCOMPtr<nsIPresShell> presShell = mDocShell->GetPresShell(); + if (!presShell) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + nsViewManager* vm = presShell->GetViewManager(); + if (!vm) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + nsView* rootView = vm->GetRootView(); + if (!rootView) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + nsIWidget* widget = rootView->GetNearestWidget(nullptr); + if (!widget) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + + // Call esm and set cursor. + aError = presContext->EventStateManager()->SetCursor(cursor, nullptr, + false, 0.0f, 0.0f, + widget, true); + } +} + +void +nsGlobalWindow::SetCursor(const nsAString& aCursor, ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(SetCursorOuter, (aCursor, aError), aError, ); +} + +NS_IMETHODIMP +nsGlobalChromeWindow::GetBrowserDOMWindow(nsIBrowserDOMWindow **aBrowserWindow) +{ + FORWARD_TO_INNER_CHROME(GetBrowserDOMWindow, (aBrowserWindow), NS_ERROR_UNEXPECTED); + + ErrorResult rv; + NS_IF_ADDREF(*aBrowserWindow = GetBrowserDOMWindow(rv)); + return rv.StealNSResult(); +} + +nsIBrowserDOMWindow* +nsGlobalWindow::GetBrowserDOMWindowOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + MOZ_ASSERT(IsChromeWindow()); + return static_cast<nsGlobalChromeWindow*>(this)->mBrowserDOMWindow; +} + +nsIBrowserDOMWindow* +nsGlobalWindow::GetBrowserDOMWindow(ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetBrowserDOMWindowOuter, (), aError, nullptr); +} + +NS_IMETHODIMP +nsGlobalChromeWindow::SetBrowserDOMWindow(nsIBrowserDOMWindow *aBrowserWindow) +{ + FORWARD_TO_INNER_CHROME(SetBrowserDOMWindow, (aBrowserWindow), NS_ERROR_UNEXPECTED); + + ErrorResult rv; + SetBrowserDOMWindow(aBrowserWindow, rv); + return rv.StealNSResult(); +} + +void +nsGlobalWindow::SetBrowserDOMWindowOuter(nsIBrowserDOMWindow* aBrowserWindow) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + MOZ_ASSERT(IsChromeWindow()); + static_cast<nsGlobalChromeWindow*>(this)->mBrowserDOMWindow = aBrowserWindow; +} + +void +nsGlobalWindow::SetBrowserDOMWindow(nsIBrowserDOMWindow* aBrowserWindow, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(SetBrowserDOMWindowOuter, (aBrowserWindow), aError, ); +} + +NS_IMETHODIMP +nsGlobalChromeWindow::NotifyDefaultButtonLoaded(nsIDOMElement* aDefaultButton) +{ + FORWARD_TO_INNER_CHROME(NotifyDefaultButtonLoaded, + (aDefaultButton), NS_ERROR_UNEXPECTED); + + nsCOMPtr<Element> defaultButton = do_QueryInterface(aDefaultButton); + NS_ENSURE_ARG(defaultButton); + + ErrorResult rv; + NotifyDefaultButtonLoaded(*defaultButton, rv); + return rv.StealNSResult(); +} + +void +nsGlobalWindow::NotifyDefaultButtonLoaded(Element& aDefaultButton, + ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); +#ifdef MOZ_XUL + // Don't snap to a disabled button. + nsCOMPtr<nsIDOMXULControlElement> xulControl = + do_QueryInterface(&aDefaultButton); + if (!xulControl) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + bool disabled; + aError = xulControl->GetDisabled(&disabled); + if (aError.Failed() || disabled) { + return; + } + + // Get the button rect in screen coordinates. + nsIFrame *frame = aDefaultButton.GetPrimaryFrame(); + if (!frame) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + LayoutDeviceIntRect buttonRect = + LayoutDeviceIntRect::FromUnknownRect(frame->GetScreenRect()); + + // Get the widget rect in screen coordinates. + nsIWidget *widget = GetNearestWidget(); + if (!widget) { + aError.Throw(NS_ERROR_FAILURE); + return; + } + LayoutDeviceIntRect widgetRect = widget->GetScreenBounds(); + + // Convert the buttonRect coordinates from screen to the widget. + buttonRect -= widgetRect.TopLeft(); + nsresult rv = widget->OnDefaultButtonLoaded(buttonRect); + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_IMPLEMENTED) { + aError.Throw(rv); + } +#else + aError.Throw(NS_ERROR_NOT_IMPLEMENTED); +#endif +} + +NS_IMETHODIMP +nsGlobalChromeWindow::GetMessageManager(nsIMessageBroadcaster** aManager) +{ + FORWARD_TO_INNER_CHROME(GetMessageManager, (aManager), NS_ERROR_UNEXPECTED); + + ErrorResult rv; + NS_IF_ADDREF(*aManager = GetMessageManager(rv)); + return rv.StealNSResult(); +} + +nsIMessageBroadcaster* +nsGlobalWindow::GetMessageManager(ErrorResult& aError) +{ + MOZ_ASSERT(IsChromeWindow()); + MOZ_RELEASE_ASSERT(IsInnerWindow()); + nsGlobalChromeWindow* myself = static_cast<nsGlobalChromeWindow*>(this); + if (!myself->mMessageManager) { + nsCOMPtr<nsIMessageBroadcaster> globalMM = + do_GetService("@mozilla.org/globalmessagemanager;1"); + myself->mMessageManager = + new nsFrameMessageManager(nullptr, + static_cast<nsFrameMessageManager*>(globalMM.get()), + MM_CHROME | MM_BROADCASTER); + } + return myself->mMessageManager; +} + +NS_IMETHODIMP +nsGlobalChromeWindow::GetGroupMessageManager(const nsAString& aGroup, + nsIMessageBroadcaster** aManager) +{ + FORWARD_TO_INNER_CHROME(GetGroupMessageManager, (aGroup, aManager), NS_ERROR_UNEXPECTED); + + ErrorResult rv; + NS_IF_ADDREF(*aManager = GetGroupMessageManager(aGroup, rv)); + return rv.StealNSResult(); +} + +nsIMessageBroadcaster* +nsGlobalWindow::GetGroupMessageManager(const nsAString& aGroup, + ErrorResult& aError) +{ + MOZ_ASSERT(IsChromeWindow()); + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + nsGlobalChromeWindow* myself = static_cast<nsGlobalChromeWindow*>(this); + nsCOMPtr<nsIMessageBroadcaster> messageManager = + myself->mGroupMessageManagers.Get(aGroup); + + if (!messageManager) { + nsFrameMessageManager* parent = + static_cast<nsFrameMessageManager*>(GetMessageManager(aError)); + + messageManager = new nsFrameMessageManager(nullptr, + parent, + MM_CHROME | MM_BROADCASTER); + myself->mGroupMessageManagers.Put(aGroup, messageManager); + } + + return messageManager; +} + +nsresult +nsGlobalChromeWindow::SetOpenerForInitialContentBrowser(mozIDOMWindowProxy* aOpenerWindow) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + MOZ_ASSERT(!mOpenerForInitialContentBrowser); + mOpenerForInitialContentBrowser = aOpenerWindow; + return NS_OK; +} + +nsresult +nsGlobalChromeWindow::TakeOpenerForInitialContentBrowser(mozIDOMWindowProxy** aOpenerWindow) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + // Intentionally forget our own member + mOpenerForInitialContentBrowser.forget(aOpenerWindow); + return NS_OK; +} + +// nsGlobalModalWindow implementation + +// QueryInterface implementation for nsGlobalModalWindow +NS_INTERFACE_MAP_BEGIN(nsGlobalModalWindow) + NS_INTERFACE_MAP_ENTRY(nsIDOMModalContentWindow) +NS_INTERFACE_MAP_END_INHERITING(nsGlobalWindow) + +NS_IMPL_ADDREF_INHERITED(nsGlobalModalWindow, nsGlobalWindow) +NS_IMPL_RELEASE_INHERITED(nsGlobalModalWindow, nsGlobalWindow) + + +void +nsGlobalWindow::GetDialogArgumentsOuter(JSContext* aCx, + JS::MutableHandle<JS::Value> aRetval, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + MOZ_ASSERT(IsModalContentWindow(), + "This should only be called on modal windows!"); + + if (!mDialogArguments) { + MOZ_ASSERT(mIsClosed, "This window should be closed!"); + aRetval.setUndefined(); + return; + } + + // This does an internal origin check, and returns undefined if the subject + // does not subsumes the origin of the arguments. + JS::Rooted<JSObject*> wrapper(aCx, GetWrapper()); + JSAutoCompartment ac(aCx, wrapper); + mDialogArguments->Get(aCx, wrapper, &aSubjectPrincipal, aRetval, aError); +} + +void +nsGlobalWindow::GetDialogArguments(JSContext* aCx, + JS::MutableHandle<JS::Value> aRetval, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetDialogArgumentsOuter, + (aCx, aRetval, aSubjectPrincipal, aError), + aError, ); +} + +/* static */ already_AddRefed<nsGlobalModalWindow> +nsGlobalModalWindow::Create(nsGlobalWindow *aOuterWindow) +{ + RefPtr<nsGlobalModalWindow> window = new nsGlobalModalWindow(aOuterWindow); + window->InitWasOffline(); + return window.forget(); +} + +NS_IMETHODIMP +nsGlobalModalWindow::GetDialogArguments(nsIVariant **aArguments) +{ + FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(GetDialogArguments, (aArguments), + NS_ERROR_NOT_INITIALIZED); + + // This does an internal origin check, and returns undefined if the subject + // does not subsumes the origin of the arguments. + return mDialogArguments->Get(nsContentUtils::SubjectPrincipal(), aArguments); +} + +/* static */ already_AddRefed<nsGlobalWindow> +nsGlobalWindow::Create(nsGlobalWindow *aOuterWindow) +{ + RefPtr<nsGlobalWindow> window = new nsGlobalWindow(aOuterWindow); + window->InitWasOffline(); + return window.forget(); +} + +void +nsGlobalWindow::InitWasOffline() +{ + mWasOffline = NS_IsOffline(); +} + +void +nsGlobalWindow::GetReturnValueOuter(JSContext* aCx, + JS::MutableHandle<JS::Value> aReturnValue, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + MOZ_ASSERT(IsModalContentWindow(), + "This should only be called on modal windows!"); + + if (mReturnValue) { + JS::Rooted<JSObject*> wrapper(aCx, GetWrapper()); + JSAutoCompartment ac(aCx, wrapper); + mReturnValue->Get(aCx, wrapper, &aSubjectPrincipal, aReturnValue, aError); + } else { + aReturnValue.setUndefined(); + } +} + +void +nsGlobalWindow::GetReturnValue(JSContext* aCx, + JS::MutableHandle<JS::Value> aReturnValue, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(GetReturnValueOuter, + (aCx, aReturnValue, aSubjectPrincipal, aError), + aError, ); +} + +NS_IMETHODIMP +nsGlobalModalWindow::GetReturnValue(nsIVariant **aRetVal) +{ + FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(GetReturnValue, (aRetVal), NS_OK); + + if (!mReturnValue) { + nsCOMPtr<nsIVariant> variant = CreateVoidVariant(); + variant.forget(aRetVal); + return NS_OK; + } + return mReturnValue->Get(nsContentUtils::SubjectPrincipal(), aRetVal); +} + +void +nsGlobalWindow::SetReturnValueOuter(JSContext* aCx, + JS::Handle<JS::Value> aReturnValue, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + MOZ_ASSERT(IsModalContentWindow(), + "This should only be called on modal windows!"); + + nsCOMPtr<nsIVariant> returnValue; + aError = + nsContentUtils::XPConnect()->JSToVariant(aCx, aReturnValue, + getter_AddRefs(returnValue)); + if (!aError.Failed()) { + mReturnValue = new DialogValueHolder(&aSubjectPrincipal, returnValue); + } +} + +void +nsGlobalWindow::SetReturnValue(JSContext* aCx, + JS::Handle<JS::Value> aReturnValue, + nsIPrincipal& aSubjectPrincipal, + ErrorResult& aError) +{ + FORWARD_TO_OUTER_OR_THROW(SetReturnValueOuter, + (aCx, aReturnValue, aSubjectPrincipal, aError), + aError, ); +} + +NS_IMETHODIMP +nsGlobalModalWindow::SetReturnValue(nsIVariant *aRetVal) +{ + FORWARD_TO_OUTER_MODAL_CONTENT_WINDOW(SetReturnValue, (aRetVal), NS_OK); + + mReturnValue = new DialogValueHolder(nsContentUtils::SubjectPrincipal(), + aRetVal); + return NS_OK; +} + +/* static */ +bool +nsGlobalWindow::IsModalContentWindow(JSContext* aCx, JSObject* aGlobal) +{ + return xpc::WindowOrNull(aGlobal)->IsModalContentWindow(); +} + +#if defined(MOZ_WIDGET_ANDROID) || defined(MOZ_WIDGET_GONK) +int16_t +nsGlobalWindow::Orientation(CallerType aCallerType) const +{ + return nsContentUtils::ResistFingerprinting(aCallerType) ? + 0 : WindowOrientationObserver::OrientationAngle(); +} +#endif + +Console* +nsGlobalWindow::GetConsole(ErrorResult& aRv) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mConsole) { + mConsole = Console::Create(AsInner(), aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + } + + return mConsole; +} + +bool +nsGlobalWindow::IsSecureContext() const +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + return JS_GetIsSecureContext(js::GetObjectCompartment(GetWrapperPreserveColor())); +} + +bool +nsGlobalWindow::IsSecureContextIfOpenerIgnored() const +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + return mIsSecureContextIfOpenerIgnored; +} + +already_AddRefed<External> +nsGlobalWindow::GetExternal(ErrorResult& aRv) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + +#ifdef HAVE_SIDEBAR + if (!mExternal) { + AutoJSContext cx; + JS::Rooted<JSObject*> jsImplObj(cx); + ConstructJSImplementation("@mozilla.org/sidebar;1", this, &jsImplObj, aRv); + if (aRv.Failed()) { + return nullptr; + } + mExternal = new External(jsImplObj, this); + } + + RefPtr<External> external = static_cast<External*>(mExternal.get()); + return external.forget(); +#else + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return nullptr; +#endif +} + +void +nsGlobalWindow::GetSidebar(OwningExternalOrWindowProxy& aResult, + ErrorResult& aRv) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + +#ifdef HAVE_SIDEBAR + // First check for a named frame named "sidebar" + nsCOMPtr<nsPIDOMWindowOuter> domWindow = GetChildWindow(NS_LITERAL_STRING("sidebar")); + if (domWindow) { + aResult.SetAsWindowProxy() = domWindow.forget(); + return; + } + + RefPtr<External> external = GetExternal(aRv); + if (external) { + aResult.SetAsExternal() = external; + } +#else + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); +#endif +} + +void +nsGlobalWindow::ClearDocumentDependentSlots(JSContext* aCx) +{ + MOZ_ASSERT(IsInnerWindow()); + + // If JSAPI OOMs here, there is basically nothing we can do to recover safely. + if (!WindowBinding::ClearCachedDocumentValue(aCx, this) || + !WindowBinding::ClearCachedPerformanceValue(aCx, this)) { + MOZ_CRASH("Unhandlable OOM while clearing document dependent slots."); + } +} + +/* static */ +JSObject* +nsGlobalWindow::CreateNamedPropertiesObject(JSContext *aCx, + JS::Handle<JSObject*> aProto) +{ + return WindowNamedPropertiesHandler::Create(aCx, aProto); +} + +bool +nsGlobalWindow::GetIsPrerendered() +{ + nsIDocShell* docShell = GetDocShell(); + return docShell && docShell->GetIsPrerendered(); +} + +#ifdef MOZ_B2G +void +nsGlobalWindow::EnableNetworkEvent(EventMessage aEventMessage) +{ + MOZ_ASSERT(IsInnerWindow()); + + nsCOMPtr<nsIPermissionManager> permMgr = + services::GetPermissionManager(); + if (!permMgr) { + NS_ERROR("No PermissionManager available!"); + return; + } + + uint32_t permission = nsIPermissionManager::DENY_ACTION; + permMgr->TestExactPermissionFromPrincipal(GetPrincipal(), "network-events", + &permission); + + if (permission != nsIPermissionManager::ALLOW_ACTION) { + return; + } + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (!os) { + NS_ERROR("ObserverService should be available!"); + return; + } + + switch (aEventMessage) { + case eNetworkUpload: + if (!mNetworkUploadObserverEnabled) { + mNetworkUploadObserverEnabled = true; + os->AddObserver(mObserver, NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC, false); + } + break; + case eNetworkDownload: + if (!mNetworkDownloadObserverEnabled) { + mNetworkDownloadObserverEnabled = true; + os->AddObserver(mObserver, NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC, false); + } + break; + default: + break; + } +} + +void +nsGlobalWindow::DisableNetworkEvent(EventMessage aEventMessage) +{ + MOZ_ASSERT(IsInnerWindow()); + + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); + if (!os) { + return; + } + + switch (aEventMessage) { + case eNetworkUpload: + if (mNetworkUploadObserverEnabled) { + mNetworkUploadObserverEnabled = false; + os->RemoveObserver(mObserver, NS_NETWORK_ACTIVITY_BLIP_UPLOAD_TOPIC); + } + break; + case eNetworkDownload: + if (mNetworkDownloadObserverEnabled) { + mNetworkDownloadObserverEnabled = false; + os->RemoveObserver(mObserver, NS_NETWORK_ACTIVITY_BLIP_DOWNLOAD_TOPIC); + } + break; + default: + break; + } +} +#endif // MOZ_B2G + +void +nsGlobalWindow::RedefineProperty(JSContext* aCx, const char* aPropName, + JS::Handle<JS::Value> aValue, + ErrorResult& aError) +{ + JS::Rooted<JSObject*> thisObj(aCx, GetWrapperPreserveColor()); + if (!thisObj) { + aError.Throw(NS_ERROR_UNEXPECTED); + return; + } + + if (!JS_WrapObject(aCx, &thisObj) || + !JS_DefineProperty(aCx, thisObj, aPropName, aValue, JSPROP_ENUMERATE, + JS_STUBGETTER, JS_STUBSETTER)) { + aError.Throw(NS_ERROR_FAILURE); + } +} + +void +nsGlobalWindow::GetReplaceableWindowCoord(JSContext* aCx, + nsGlobalWindow::WindowCoordGetter aGetter, + JS::MutableHandle<JS::Value> aRetval, + CallerType aCallerType, + ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + + int32_t coord = (this->*aGetter)(aCallerType, aError); + if (!aError.Failed() && + !ToJSValue(aCx, coord, aRetval)) { + aError.Throw(NS_ERROR_FAILURE); + } +} + +void +nsGlobalWindow::SetReplaceableWindowCoord(JSContext* aCx, + nsGlobalWindow::WindowCoordSetter aSetter, + JS::Handle<JS::Value> aValue, + const char* aPropName, + ErrorResult& aError) +{ + MOZ_ASSERT(IsInnerWindow()); + + /* + * If caller is not chrome and the user has not explicitly exempted the site, + * just treat this the way we would an IDL replaceable property. + */ + nsGlobalWindow* outer = GetOuterWindowInternal(); + if (!outer || !outer->CanMoveResizeWindows(nsContentUtils::IsCallerChrome()) || outer->IsFrame()) { + RedefineProperty(aCx, aPropName, aValue, aError); + return; + } + + int32_t value; + if (!ValueToPrimitive<int32_t, eDefault>(aCx, aValue, &value)) { + aError.Throw(NS_ERROR_UNEXPECTED); + return; + } + + (this->*aSetter)(value, aError); +} + +void +nsGlobalWindow::FireOnNewGlobalObject() +{ + MOZ_ASSERT(IsInnerWindow()); + + // AutoEntryScript required to invoke debugger hook, which is a + // Gecko-specific concept at present. + AutoEntryScript aes(this, "nsGlobalWindow report new global"); + JS::Rooted<JSObject*> global(aes.cx(), GetWrapper()); + JS_FireOnNewGlobalObject(aes.cx(), global); +} + +#ifdef _WINDOWS_ +#error "Never include windows.h in this file!" +#endif + +already_AddRefed<Promise> +nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage, + ErrorResult& aRv) +{ + if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) { + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return nullptr; + } + + return ImageBitmap::Create(this, aImage, Nothing(), aRv); +} + +already_AddRefed<Promise> +nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage, + int32_t aSx, int32_t aSy, int32_t aSw, int32_t aSh, + ErrorResult& aRv) +{ + if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) { + aRv.Throw(NS_ERROR_NOT_IMPLEMENTED); + return nullptr; + } + + return ImageBitmap::Create(this, aImage, Some(gfx::IntRect(aSx, aSy, aSw, aSh)), aRv); +} + +already_AddRefed<mozilla::dom::Promise> +nsGlobalWindow::CreateImageBitmap(const ImageBitmapSource& aImage, + int32_t aOffset, int32_t aLength, + ImageBitmapFormat aFormat, + const Sequence<ChannelPixelLayout>& aLayout, + ErrorResult& aRv) +{ + if (!ImageBitmap::ExtensionsEnabled(nullptr, nullptr)) { + aRv.Throw(NS_ERROR_TYPE_ERR); + return nullptr; + } + if (aImage.IsArrayBuffer() || aImage.IsArrayBufferView()) { + return ImageBitmap::Create(this, aImage, aOffset, aLength, aFormat, aLayout, + aRv); + } else { + aRv.Throw(NS_ERROR_TYPE_ERR); + return nullptr; + } +} + +// Helper called by methods that move/resize the window, +// to ensure the presContext (if any) is aware of resolution +// change that may happen in multi-monitor configuration. +void +nsGlobalWindow::CheckForDPIChange() +{ + if (mDocShell) { + RefPtr<nsPresContext> presContext; + mDocShell->GetPresContext(getter_AddRefs(presContext)); + if (presContext) { + if (presContext->DeviceContext()->CheckDPIChange()) { + presContext->UIResolutionChanged(); + } + } + } +} + +mozilla::dom::TabGroup* +nsGlobalWindow::TabGroupOuter() +{ + MOZ_RELEASE_ASSERT(IsOuterWindow()); + + // This method is valid both on inner and outer windows, which is a + // Outer windows lazily join TabGroups when requested. This is usually done + // because a document is getting its NodePrincipal, and asking for the + // TabGroup to determine its DocGroup. + if (!mTabGroup) { + // Get mOpener ourselves, instead of relying on GetOpenerWindowOuter, + // because that way we dodge the LegacyIsCallerChromeOrNativeCode() call + // which we want to return false. + nsCOMPtr<nsPIDOMWindowOuter> piOpener = do_QueryReferent(mOpener); + nsPIDOMWindowOuter* opener = GetSanitizedOpener(piOpener); + nsPIDOMWindowOuter* parent = GetScriptableParentOrNull(); + MOZ_ASSERT(!parent || !opener, "Only one of parent and opener may be provided"); + + mozilla::dom::TabGroup* toJoin = nullptr; + if (GetDocShell()->ItemType() == nsIDocShellTreeItem::typeChrome) { + toJoin = TabGroup::GetChromeTabGroup(); + } else if (opener) { + toJoin = opener->TabGroup(); + } else if (parent) { + toJoin = parent->TabGroup(); + } + mTabGroup = mozilla::dom::TabGroup::Join(AsOuter(), toJoin); + } + MOZ_ASSERT(mTabGroup); + +#ifdef DEBUG + // Ensure that we don't recurse forever + if (!mIsValidatingTabGroup) { + mIsValidatingTabGroup = true; + // We only need to do this check if we aren't in the chrome tab group + if (mIsChrome) { + MOZ_ASSERT(mTabGroup == TabGroup::GetChromeTabGroup()); + } else { + // Sanity check that our tabgroup matches our opener or parent. + RefPtr<nsPIDOMWindowOuter> parent = GetScriptableParentOrNull(); + MOZ_ASSERT_IF(parent, parent->TabGroup() == mTabGroup); + nsCOMPtr<nsPIDOMWindowOuter> piOpener = do_QueryReferent(mOpener); + nsPIDOMWindowOuter* opener = GetSanitizedOpener(piOpener); + MOZ_ASSERT_IF(opener && Cast(opener) != this, opener->TabGroup() == mTabGroup); + } + mIsValidatingTabGroup = false; + } +#endif + + return mTabGroup; +} + +mozilla::dom::TabGroup* +nsGlobalWindow::TabGroupInner() +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + // If we don't have a TabGroup yet, try to get it from the outer window and + // cache it. + if (!mTabGroup) { + nsGlobalWindow* outer = GetOuterWindowInternal(); + // This will never be called without either an outer window, or a cached tab group. + // This is because of the following: + // * This method is only called on inner windows + // * This method is called as a document is attached to it's script global + // by the document + // * Inner windows are created in nsGlobalWindow::SetNewDocument, which + // immediately sets a document, which will call this method, causing + // the TabGroup to be cached. + MOZ_RELEASE_ASSERT(outer, "Inner window without outer window has no cached tab group!"); + mTabGroup = outer->TabGroup(); + } + MOZ_ASSERT(mTabGroup); + +#ifdef DEBUG + nsGlobalWindow* outer = GetOuterWindowInternal(); + MOZ_ASSERT_IF(outer, outer->TabGroup() == mTabGroup); +#endif + + return mTabGroup; +} + +template<typename T> +mozilla::dom::TabGroup* +nsPIDOMWindow<T>::TabGroup() +{ + nsGlobalWindow* globalWindow = + static_cast<nsGlobalWindow*>( + reinterpret_cast<nsPIDOMWindow<nsISupports>*>(this)); + + if (IsInnerWindow()) { + return globalWindow->TabGroupInner(); + } + return globalWindow->TabGroupOuter(); +} + +template<typename T> +mozilla::dom::DocGroup* +nsPIDOMWindow<T>::GetDocGroup() +{ + nsIDocument* doc = GetExtantDoc(); + if (doc) { + return doc->GetDocGroup(); + } + return nullptr; +} + +nsGlobalWindow::TemporarilyDisableDialogs::TemporarilyDisableDialogs( + nsGlobalWindow* aWindow MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL) +{ + MOZ_GUARD_OBJECT_NOTIFIER_INIT; + + MOZ_ASSERT(aWindow); + nsGlobalWindow* topWindow = aWindow->GetScriptableTopInternal(); + if (!topWindow) { + NS_ERROR("nsGlobalWindow::TemporarilyDisableDialogs used without a top " + "window?"); + return; + } + + // TODO: Warn if no top window? + topWindow = topWindow->GetCurrentInnerWindowInternal(); + if (topWindow) { + mTopWindow = topWindow; + mSavedDialogsEnabled = mTopWindow->mAreDialogsEnabled; + mTopWindow->mAreDialogsEnabled = false; + } +} + +nsGlobalWindow::TemporarilyDisableDialogs::~TemporarilyDisableDialogs() +{ + if (mTopWindow) { + mTopWindow->mAreDialogsEnabled = mSavedDialogsEnabled; + } +} + +already_AddRefed<Worklet> +nsGlobalWindow::CreateWorklet(ErrorResult& aRv) +{ + MOZ_RELEASE_ASSERT(IsInnerWindow()); + + if (!mDoc) { + aRv.Throw(NS_ERROR_FAILURE); + return nullptr; + } + + RefPtr<Worklet> worklet = new Worklet(AsInner(), mDoc->NodePrincipal()); + return worklet.forget(); +} + +template class nsPIDOMWindow<mozIDOMWindowProxy>; +template class nsPIDOMWindow<mozIDOMWindow>; +template class nsPIDOMWindow<nsISupports>; |