gecko-dev/dom/base/nsGlobalWindowInner.cpp

7716 строки
251 KiB
C++

/* -*- 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 "nsGlobalWindowInner.h"
#include <inttypes.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <cstdint>
#include <new>
#include <type_traits>
#include <utility>
#include "AudioChannelService.h"
#include "AutoplayPolicy.h"
#include "Crypto.h"
#include "GeckoProfiler.h"
#include "MainThreadUtils.h"
#include "Navigator.h"
#include "PaintWorkletImpl.h"
#include "SessionStorageCache.h"
#include "Units.h"
#include "VRManagerChild.h"
#include "WindowDestroyedEvent.h"
#include "WindowNamedPropertiesHandler.h"
#include "js/ComparisonOperators.h"
#include "js/CompileOptions.h"
#include "js/Id.h"
#include "js/PropertyAndElement.h" // JS_DefineProperty, JS_GetProperty
#include "js/PropertyDescriptor.h"
#include "js/RealmOptions.h"
#include "js/RootingAPI.h"
#include "js/TypeDecls.h"
#include "js/Value.h"
#include "js/Warnings.h"
#include "js/shadow/String.h"
#include "jsapi.h"
#include "jsfriendapi.h"
#include "mozIDOMWindow.h"
#include "moz_external_vr.h"
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/ArrayIterator.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Attributes.h"
#include "mozilla/BaseProfilerMarkersPrerequisites.h"
#include "mozilla/BasicEvents.h"
#include "mozilla/CallState.h"
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/DOMEventTargetHelper.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/EventDispatcher.h"
#include "mozilla/EventListenerManager.h"
#include "mozilla/EventQueue.h"
#include "mozilla/ExtensionPolicyService.h"
#include "mozilla/FloatingPoint.h"
#include "mozilla/FlushType.h"
#include "mozilla/Likely.h"
#include "mozilla/LinkedList.h"
#include "mozilla/Logging.h"
#include "mozilla/MacroForEach.h"
#include "mozilla/Maybe.h"
#include "mozilla/OwningNonNull.h"
#include "mozilla/PermissionDelegateHandler.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/ProcessHangMonitor.h"
#include "mozilla/RefPtr.h"
#include "mozilla/Result.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/ScrollOrigin.h"
#include "mozilla/ScrollTypes.h"
#include "mozilla/Components.h"
#include "mozilla/SizeOfState.h"
#include "mozilla/Span.h"
#include "mozilla/SpinEventLoopUntil.h"
#include "mozilla/Sprintf.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StorageAccess.h"
#include "mozilla/TaskCategory.h"
#include "mozilla/Telemetry.h"
#include "mozilla/TelemetryHistogramEnums.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/AudioContext.h"
#include "mozilla/dom/AutoEntryScript.h"
#include "mozilla/dom/BarProps.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/BindingUtils.h"
#include "mozilla/dom/BrowserChild.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/CSPEvalChecker.h"
#include "mozilla/dom/CallbackDebuggerNotification.h"
#include "mozilla/dom/ChromeMessageBroadcaster.h"
#include "mozilla/dom/ClientInfo.h"
#include "mozilla/dom/ClientManager.h"
#include "mozilla/dom/ClientSource.h"
#include "mozilla/dom/ClientState.h"
#include "mozilla/dom/ClientsBinding.h"
#include "mozilla/dom/Console.h"
#include "mozilla/dom/ContentFrameMessageManager.h"
#include "mozilla/dom/ContentMediaController.h"
#include "mozilla/dom/CustomElementRegistry.h"
#include "mozilla/dom/DOMJSProxyHandler.h"
#include "mozilla/dom/DebuggerNotification.h"
#include "mozilla/dom/DebuggerNotificationBinding.h"
#include "mozilla/dom/DebuggerNotificationManager.h"
#include "mozilla/dom/DispatcherTrait.h"
#include "mozilla/dom/DocGroup.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/DocumentInlines.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/Event.h"
#include "mozilla/dom/EventTarget.h"
#include "mozilla/dom/External.h"
#include "mozilla/dom/Fetch.h"
#include "mozilla/dom/Gamepad.h"
#include "mozilla/dom/GamepadHandle.h"
#include "mozilla/dom/GamepadManager.h"
#include "mozilla/dom/HashChangeEvent.h"
#include "mozilla/dom/HashChangeEventBinding.h"
#include "mozilla/dom/IDBFactory.h"
#include "mozilla/dom/IdleRequest.h"
#include "mozilla/dom/ImageBitmap.h"
#include "mozilla/dom/ImageBitmapSource.h"
#include "mozilla/dom/InstallTriggerBinding.h"
#include "mozilla/dom/IntlUtils.h"
#include "mozilla/dom/JSExecutionContext.h"
#include "mozilla/dom/LSObject.h"
#include "mozilla/dom/LoadedScript.h"
#include "mozilla/dom/LocalStorage.h"
#include "mozilla/dom/LocalStorageCommon.h"
#include "mozilla/dom/Location.h"
#include "mozilla/dom/MediaKeys.h"
#include "mozilla/dom/NavigatorBinding.h"
#include "mozilla/dom/Nullable.h"
#include "mozilla/dom/PartitionedLocalStorage.h"
#include "mozilla/dom/Performance.h"
#include "mozilla/dom/PopStateEvent.h"
#include "mozilla/dom/PopStateEventBinding.h"
#include "mozilla/dom/PopupBlocker.h"
#include "mozilla/dom/PrimitiveConversions.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/RootedDictionary.h"
#include "mozilla/dom/ScriptLoader.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/ServiceWorker.h"
#include "mozilla/dom/ServiceWorkerDescriptor.h"
#include "mozilla/dom/ServiceWorkerRegistration.h"
#include "mozilla/dom/SessionStorageManager.h"
#include "mozilla/dom/SharedWorker.h"
#include "mozilla/dom/Storage.h"
#include "mozilla/dom/StorageEvent.h"
#include "mozilla/dom/StorageEventBinding.h"
#include "mozilla/dom/StorageNotifierService.h"
#include "mozilla/dom/StorageUtils.h"
#include "mozilla/dom/TabMessageTypes.h"
#include "mozilla/dom/Timeout.h"
#include "mozilla/dom/TimeoutHandler.h"
#include "mozilla/dom/TimeoutManager.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/dom/U2F.h"
#include "mozilla/dom/VRDisplay.h"
#include "mozilla/dom/VRDisplayEvent.h"
#include "mozilla/dom/VRDisplayEventBinding.h"
#include "mozilla/dom/VREventObserver.h"
#include "mozilla/dom/VisualViewport.h"
#include "mozilla/dom/WakeLock.h"
#include "mozilla/dom/WebIDLGlobalNameHash.h"
#include "mozilla/dom/WindowBinding.h"
#include "mozilla/dom/WindowContext.h"
#include "mozilla/dom/WindowGlobalChild.h"
#include "mozilla/dom/WindowProxyHolder.h"
#include "mozilla/dom/WorkerCommon.h"
#include "mozilla/dom/Worklet.h"
#include "mozilla/dom/XRPermissionRequest.h"
#include "mozilla/dom/cache/CacheStorage.h"
#include "mozilla/dom/cache/Types.h"
#include "mozilla/extensions/WebExtensionPolicy.h"
#include "mozilla/fallible.h"
#include "mozilla/gfx/BasePoint.h"
#include "mozilla/gfx/BaseRect.h"
#include "mozilla/gfx/BaseSize.h"
#include "mozilla/gfx/Rect.h"
#include "mozilla/gfx/Types.h"
#include "mozilla/intl/LocaleService.h"
#include "mozilla/net/CookieJarSettings.h"
#include "nsAtom.h"
#include "nsBaseHashtable.h"
#include "nsCCUncollectableMarker.h"
#include "nsCOMPtr.h"
#include "nsCRT.h"
#include "nsCRTGlue.h"
#include "nsCanvasFrame.h"
#include "nsCharTraits.h"
#include "nsCheapSets.h"
#include "nsContentUtils.h"
#include "nsCoord.h"
#include "nsCycleCollectionNoteChild.h"
#include "nsCycleCollectionTraversalCallback.h"
#include "nsDOMNavigationTiming.h"
#include "nsDOMOfflineResourceList.h"
#include "nsDebug.h"
#include "nsDocShell.h"
#include "nsFocusManager.h"
#include "nsFrameMessageManager.h"
#include "nsGkAtoms.h"
#include "nsGlobalWindowOuter.h"
#include "nsHashKeys.h"
#include "nsHistory.h"
#include "nsIAddonPolicyService.h"
#include "nsIArray.h"
#include "nsIBaseWindow.h"
#include "nsIBrowserChild.h"
#include "nsICancelableRunnable.h"
#include "nsIChannel.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIControllers.h"
#include "nsICookieJarSettings.h"
#include "nsICookieService.h"
#include "nsID.h"
#include "nsIDOMStorageManager.h"
#include "nsIDeviceSensors.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeItem.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIDocumentLoader.h"
#include "nsIDragService.h"
#include "nsIFocusManager.h"
#include "nsIFrame.h"
#include "nsIGlobalObject.h"
#include "nsIIOService.h"
#include "nsIIdleRunnable.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsILoadContext.h"
#include "nsILoadGroup.h"
#include "nsILoadInfo.h"
#include "nsINamed.h"
#include "nsINode.h"
#include "nsIObserver.h"
#include "nsIObserverService.h"
#include "nsIPermission.h"
#include "nsIPermissionManager.h"
#include "nsIPrefBranch.h"
#include "nsIPrincipal.h"
#include "nsIPrompt.h"
#include "nsIRunnable.h"
#include "nsIScreen.h"
#include "nsIScreenManager.h"
#include "nsIScriptContext.h"
#include "nsIScriptGlobalObject.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIScrollableFrame.h"
#include "nsISerialEventTarget.h"
#include "nsISimpleEnumerator.h"
#include "nsISizeOfEventTarget.h"
#include "nsISlowScriptDebug.h"
#include "nsISupportsUtils.h"
#include "nsIThread.h"
#include "nsITimedChannel.h"
#include "nsIURI.h"
#include "nsIVariant.h"
#include "nsIWeakReference.h"
#include "nsIWebBrowserChrome.h"
#include "nsIWebNavigation.h"
#include "nsIWebProgressListener.h"
#include "nsIWidget.h"
#include "nsIWidgetListener.h"
#include "nsJSPrincipals.h"
#include "nsJSUtils.h"
#include "nsLayoutStatics.h"
#include "nsLiteralString.h"
#include "nsNetUtil.h"
#include "nsPIDOMWindow.h"
#include "nsPIDOMWindowInlines.h"
#include "nsPIWindowRoot.h"
#include "nsPoint.h"
#include "nsPresContext.h"
#include "nsQueryObject.h"
#include "nsRefPtrHashtable.h"
#include "nsSandboxFlags.h"
#include "nsScreen.h"
#include "nsServiceManagerUtils.h"
#include "nsString.h"
#include "nsStringFlags.h"
#include "nsStringFwd.h"
#include "nsTArray.h"
#include "nsTLiteralString.h"
#include "nsTObserverArray.h"
#include "nsTStringRepr.h"
#include "nsThreadUtils.h"
#include "nsWeakReference.h"
#include "nsWindowMemoryReporter.h"
#include "nsWindowSizes.h"
#include "nsWrapperCache.h"
#include "nsWrapperCacheInlines.h"
#include "nsXULAppAPI.h"
#include "nsrootidl.h"
#include "prclist.h"
#include "prtypes.h"
#include "xpcprivate.h"
#include "xpcpublic.h"
#ifdef MOZ_XUL
# include "nsIDOMXULControlElement.h"
# include "nsMenuPopupFrame.h"
#endif
#ifdef NS_PRINTING
# include "nsIPrintSettings.h"
# include "nsIPrintSettingsService.h"
# include "nsIWebBrowserPrint.h"
#endif
#ifdef MOZ_WEBSPEECH
# include "mozilla/dom/SpeechSynthesis.h"
#endif
#ifdef ANDROID
# include <android/log.h>
#endif
#ifdef MOZ_WIDGET_ANDROID
# include "mozilla/dom/WindowOrientationObserver.h"
#endif
#ifdef XP_WIN
# include "mozilla/Debug.h"
# include <process.h>
# define getpid _getpid
#else
# include <unistd.h> // for getpid()
#endif
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::dom::ipc;
using mozilla::TimeDuration;
using mozilla::TimeStamp;
using mozilla::dom::GamepadHandle;
using mozilla::dom::cache::CacheStorage;
#define FORWARD_TO_OUTER(method, args, err_rval) \
PR_BEGIN_MACRO \
nsGlobalWindowOuter* outer = GetOuterWindowInternal(); \
if (!HasActiveDocument()) { \
NS_WARNING(outer ? "Inner window does not have active document." \
: "No outer window available!"); \
return err_rval; \
} \
return outer->method args; \
PR_END_MACRO
static nsGlobalWindowOuter* GetOuterWindowForForwarding(
nsGlobalWindowInner* aInner, ErrorResult& aError) {
nsGlobalWindowOuter* outer = aInner->GetOuterWindowInternal();
if (MOZ_LIKELY(aInner->HasActiveDocument())) {
return outer;
}
if (!outer) {
NS_WARNING("No outer window available!");
aError.Throw(NS_ERROR_NOT_INITIALIZED);
} else {
aError.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO);
}
return nullptr;
}
#define FORWARD_TO_OUTER_OR_THROW(method, args, errorresult, err_rval) \
PR_BEGIN_MACRO \
nsGlobalWindowOuter* outer = GetOuterWindowForForwarding(this, errorresult); \
if (MOZ_LIKELY(outer)) { \
return outer->method args; \
} \
return err_rval; \
PR_END_MACRO
#define FORWARD_TO_OUTER_VOID(method, args) \
PR_BEGIN_MACRO \
nsGlobalWindowOuter* outer = GetOuterWindowInternal(); \
if (!HasActiveDocument()) { \
NS_WARNING(outer ? "Inner window does not have active document." \
: "No outer window available!"); \
return; \
} \
outer->method args; \
return; \
PR_END_MACRO
#define ENSURE_ACTIVE_DOCUMENT(errorresult, err_rval) \
PR_BEGIN_MACRO \
if (MOZ_UNLIKELY(!HasActiveDocument())) { \
aError.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO); \
return err_rval; \
} \
PR_END_MACRO
#define DOM_TOUCH_LISTENER_ADDED "dom-touch-listener-added"
#define MEMORY_PRESSURE_OBSERVER_TOPIC "memory-pressure"
#define PERMISSION_CHANGED_TOPIC "perm-changed"
static LazyLogModule gDOMLeakPRLogInner("DOMLeakInner");
extern mozilla::LazyLogModule gTimeoutLog;
#ifdef DEBUG
static LazyLogModule gDocShellAndDOMWindowLeakLogging(
"DocShellAndDOMWindowLeak");
#endif
static FILE* gDumpFile = nullptr;
nsGlobalWindowInner::InnerWindowByIdTable*
nsGlobalWindowInner::sInnerWindowsById = nullptr;
bool nsGlobalWindowInner::sDragServiceDisabled = false;
bool nsGlobalWindowInner::sMouseDown = false;
/**
* 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 StorageNotificationObserver {
public:
explicit nsGlobalWindowObserver(nsGlobalWindowInner* 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;
}
void ObserveStorageNotification(StorageEvent* aEvent,
const char16_t* aStorageType,
bool aPrivateBrowsing) override {
if (mWindow) {
mWindow->ObserveStorageNotification(aEvent, aStorageType,
aPrivateBrowsing);
}
}
nsIPrincipal* GetEffectiveStoragePrincipal() const override {
return mWindow ? mWindow->GetEffectiveStoragePrincipal() : nullptr;
}
bool IsPrivateBrowsing() const override {
return mWindow ? mWindow->IsPrivateBrowsing() : false;
}
nsIEventTarget* GetEventTarget() const override {
return mWindow ? mWindow->EventTargetFor(TaskCategory::Other) : nullptr;
}
private:
~nsGlobalWindowObserver() = default;
// This reference is non-owning and safe because it's cleared by
// nsGlobalWindowInner::FreeInnerObjects().
nsGlobalWindowInner* MOZ_NON_OWNING_REF mWindow;
};
NS_IMPL_ISUPPORTS(nsGlobalWindowObserver, nsIObserver, nsIInterfaceRequestor)
class IdleRequestExecutor;
class IdleRequestExecutorTimeoutHandler final : public TimeoutHandler {
public:
explicit IdleRequestExecutorTimeoutHandler(IdleRequestExecutor* aExecutor)
: mExecutor(aExecutor) {}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(IdleRequestExecutorTimeoutHandler)
bool Call(const char* /* unused */) override;
private:
~IdleRequestExecutorTimeoutHandler() override = default;
RefPtr<IdleRequestExecutor> mExecutor;
};
NS_IMPL_CYCLE_COLLECTION(IdleRequestExecutorTimeoutHandler, mExecutor)
NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequestExecutorTimeoutHandler)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequestExecutorTimeoutHandler)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutorTimeoutHandler)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
class IdleRequestExecutor final : public nsIRunnable,
public nsICancelableRunnable,
public nsINamed,
public nsIIdleRunnable {
public:
explicit IdleRequestExecutor(nsGlobalWindowInner* aWindow)
: mDispatched(false), mDeadline(TimeStamp::Now()), mWindow(aWindow) {
MOZ_DIAGNOSTIC_ASSERT(mWindow);
mIdlePeriodLimit = {mDeadline, mWindow->LastIdleRequestHandle()};
mDelayedExecutorDispatcher = new IdleRequestExecutorTimeoutHandler(this);
}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(IdleRequestExecutor, nsIRunnable)
NS_DECL_NSIRUNNABLE
NS_DECL_NSINAMED
nsresult Cancel() override;
void SetDeadline(TimeStamp aDeadline) override;
bool IsCancelled() const { return !mWindow || mWindow->IsDying(); }
// Checks if aRequest shouldn't execute in the current idle period
// since it has been queued from a chained call to
// requestIdleCallback from within a running idle callback.
bool IneligibleForCurrentIdlePeriod(IdleRequest* aRequest) const {
return aRequest->Handle() >= mIdlePeriodLimit.mLastRequestIdInIdlePeriod &&
TimeStamp::Now() <= mIdlePeriodLimit.mEndOfIdlePeriod;
}
void MaybeUpdateIdlePeriodLimit();
// Maybe dispatch the IdleRequestExecutor. MabyeDispatch will
// schedule a delayed dispatch if the associated window is in the
// background or if given a time to wait until dispatching.
void MaybeDispatch(TimeStamp aDelayUntil = TimeStamp());
void ScheduleDispatch();
private:
struct IdlePeriodLimit {
TimeStamp mEndOfIdlePeriod;
uint32_t mLastRequestIdInIdlePeriod;
};
void DelayedDispatch(uint32_t aDelay);
~IdleRequestExecutor() override = default;
bool mDispatched;
TimeStamp mDeadline;
IdlePeriodLimit mIdlePeriodLimit;
RefPtr<nsGlobalWindowInner> mWindow;
// The timeout handler responsible for dispatching this executor in
// the case of immediate dispatch to the idle queue isn't
// desirable. This is used if we've dispatched all idle callbacks
// that are allowed to run in the current idle period, or if the
// associated window is currently in the background.
RefPtr<TimeoutHandler> mDelayedExecutorDispatcher;
// If not Nothing() then this value is the handle to the currently
// scheduled delayed executor dispatcher. This is needed to be able
// to cancel the timeout handler in case of the executor being
// cancelled.
Maybe<int32_t> mDelayedExecutorHandle;
};
NS_IMPL_CYCLE_COLLECTION_CLASS(IdleRequestExecutor)
NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequestExecutor)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequestExecutor)
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IdleRequestExecutor)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDelayedExecutorDispatcher)
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IdleRequestExecutor)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDelayedExecutorDispatcher)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutor)
NS_INTERFACE_MAP_ENTRY(nsIRunnable)
NS_INTERFACE_MAP_ENTRY(nsICancelableRunnable)
NS_INTERFACE_MAP_ENTRY(nsINamed)
NS_INTERFACE_MAP_ENTRY(nsIIdleRunnable)
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIRunnable)
NS_INTERFACE_MAP_END
NS_IMETHODIMP
IdleRequestExecutor::GetName(nsACString& aName) {
aName.AssignLiteral("IdleRequestExecutor");
return NS_OK;
}
// MOZ_CAN_RUN_SCRIPT_BOUNDARY until nsIRunnable::Run is MOZ_CAN_RUN_SCRIPT.
// See bug 1535398.
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP IdleRequestExecutor::Run() {
MOZ_ASSERT(NS_IsMainThread());
mDispatched = false;
if (mWindow) {
RefPtr<nsGlobalWindowInner> window(mWindow);
window->ExecuteIdleRequest(mDeadline);
}
return NS_OK;
}
nsresult IdleRequestExecutor::Cancel() {
MOZ_ASSERT(NS_IsMainThread());
if (mDelayedExecutorHandle && mWindow) {
mWindow->TimeoutManager().ClearTimeout(
mDelayedExecutorHandle.value(), Timeout::Reason::eIdleCallbackTimeout);
}
mWindow = nullptr;
return NS_OK;
}
void IdleRequestExecutor::SetDeadline(TimeStamp aDeadline) {
MOZ_ASSERT(NS_IsMainThread());
if (!mWindow) {
return;
}
mDeadline = aDeadline;
}
void IdleRequestExecutor::MaybeUpdateIdlePeriodLimit() {
if (TimeStamp::Now() > mIdlePeriodLimit.mEndOfIdlePeriod) {
mIdlePeriodLimit = {mDeadline, mWindow->LastIdleRequestHandle()};
}
}
void IdleRequestExecutor::MaybeDispatch(TimeStamp aDelayUntil) {
// If we've already dispatched the executor we don't want to do it
// again. Also, if we've called IdleRequestExecutor::Cancel mWindow
// will be null, which indicates that we shouldn't dispatch this
// executor either.
if (mDispatched || IsCancelled()) {
return;
}
mDispatched = true;
nsPIDOMWindowOuter* outer = mWindow->GetOuterWindow();
if (outer && outer->IsBackground()) {
// Set a timeout handler with a timeout of 0 ms to throttle idle
// callback requests coming from a backround window using
// background timeout throttling.
DelayedDispatch(0);
return;
}
TimeStamp now = TimeStamp::Now();
if (!aDelayUntil || aDelayUntil < now) {
ScheduleDispatch();
return;
}
TimeDuration delay = aDelayUntil - now;
DelayedDispatch(static_cast<uint32_t>(delay.ToMilliseconds()));
}
void IdleRequestExecutor::ScheduleDispatch() {
MOZ_ASSERT(mWindow);
mDelayedExecutorHandle = Nothing();
RefPtr<IdleRequestExecutor> request = this;
NS_DispatchToCurrentThreadQueue(request.forget(), EventQueuePriority::Idle);
}
void IdleRequestExecutor::DelayedDispatch(uint32_t aDelay) {
MOZ_ASSERT(mWindow);
MOZ_ASSERT(mDelayedExecutorHandle.isNothing());
int32_t handle;
mWindow->TimeoutManager().SetTimeout(
mDelayedExecutorDispatcher, aDelay, false,
Timeout::Reason::eIdleCallbackTimeout, &handle);
mDelayedExecutorHandle = Some(handle);
}
bool IdleRequestExecutorTimeoutHandler::Call(const char* /* unused */) {
if (!mExecutor->IsCancelled()) {
mExecutor->ScheduleDispatch();
}
return true;
}
void nsGlobalWindowInner::ScheduleIdleRequestDispatch() {
AssertIsOnMainThread();
if (!mIdleRequestExecutor) {
mIdleRequestExecutor = new IdleRequestExecutor(this);
}
mIdleRequestExecutor->MaybeDispatch();
}
void nsGlobalWindowInner::SuspendIdleRequests() {
if (mIdleRequestExecutor) {
mIdleRequestExecutor->Cancel();
mIdleRequestExecutor = nullptr;
}
}
void nsGlobalWindowInner::ResumeIdleRequests() {
MOZ_ASSERT(!mIdleRequestExecutor);
ScheduleIdleRequestDispatch();
}
void nsGlobalWindowInner::RemoveIdleCallback(
mozilla::dom::IdleRequest* aRequest) {
AssertIsOnMainThread();
if (aRequest->HasTimeout()) {
mTimeoutManager->ClearTimeout(aRequest->GetTimeoutHandle(),
Timeout::Reason::eIdleCallbackTimeout);
}
aRequest->removeFrom(mIdleRequestCallbacks);
}
void nsGlobalWindowInner::RunIdleRequest(IdleRequest* aRequest,
DOMHighResTimeStamp aDeadline,
bool aDidTimeout) {
AssertIsOnMainThread();
// XXXbz Do we still need this RefPtr? MOZ_CAN_RUN_SCRIPT should
// guarantee that caller is holding a strong ref on the stack.
RefPtr<IdleRequest> request(aRequest);
RemoveIdleCallback(request);
request->IdleRun(this, aDeadline, aDidTimeout);
}
void nsGlobalWindowInner::ExecuteIdleRequest(TimeStamp aDeadline) {
AssertIsOnMainThread();
RefPtr<IdleRequest> request = mIdleRequestCallbacks.getFirst();
if (!request) {
// There are no more idle requests, so stop scheduling idle
// request callbacks.
return;
}
// If the request that we're trying to execute has been queued
// during the current idle period, then dispatch it again at the end
// of the idle period.
if (mIdleRequestExecutor->IneligibleForCurrentIdlePeriod(request)) {
mIdleRequestExecutor->MaybeDispatch(aDeadline);
return;
}
DOMHighResTimeStamp deadline = 0.0;
if (Performance* perf = GetPerformance()) {
deadline = perf->GetDOMTiming()->TimeStampToDOMHighRes(aDeadline);
}
mIdleRequestExecutor->MaybeUpdateIdlePeriodLimit();
RunIdleRequest(request, deadline, false);
// Running the idle callback could've suspended the window, in which
// case mIdleRequestExecutor will be null.
if (mIdleRequestExecutor) {
mIdleRequestExecutor->MaybeDispatch();
}
}
class IdleRequestTimeoutHandler final : public TimeoutHandler {
public:
IdleRequestTimeoutHandler(JSContext* aCx, IdleRequest* aIdleRequest,
nsPIDOMWindowInner* aWindow)
: TimeoutHandler(aCx), mIdleRequest(aIdleRequest), mWindow(aWindow) {}
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
NS_DECL_CYCLE_COLLECTION_CLASS(IdleRequestTimeoutHandler)
MOZ_CAN_RUN_SCRIPT bool Call(const char* /* unused */) override {
RefPtr<nsGlobalWindowInner> window(nsGlobalWindowInner::Cast(mWindow));
RefPtr<IdleRequest> request(mIdleRequest);
window->RunIdleRequest(request, 0.0, true);
return true;
}
private:
~IdleRequestTimeoutHandler() override = default;
RefPtr<IdleRequest> mIdleRequest;
nsCOMPtr<nsPIDOMWindowInner> mWindow;
};
NS_IMPL_CYCLE_COLLECTION(IdleRequestTimeoutHandler, mIdleRequest, mWindow)
NS_IMPL_CYCLE_COLLECTING_ADDREF(IdleRequestTimeoutHandler)
NS_IMPL_CYCLE_COLLECTING_RELEASE(IdleRequestTimeoutHandler)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestTimeoutHandler)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
uint32_t nsGlobalWindowInner::RequestIdleCallback(
JSContext* aCx, IdleRequestCallback& aCallback,
const IdleRequestOptions& aOptions, ErrorResult& aError) {
AssertIsOnMainThread();
if (IsDying()) {
return 0;
}
uint32_t handle = mIdleRequestCallbackCounter++;
RefPtr<IdleRequest> request = new IdleRequest(&aCallback, handle);
if (aOptions.mTimeout.WasPassed()) {
int32_t timeoutHandle;
RefPtr<TimeoutHandler> handler(
new IdleRequestTimeoutHandler(aCx, request, this));
nsresult rv = mTimeoutManager->SetTimeout(
handler, aOptions.mTimeout.Value(), false,
Timeout::Reason::eIdleCallbackTimeout, &timeoutHandle);
if (NS_WARN_IF(NS_FAILED(rv))) {
return 0;
}
request->SetTimeoutHandle(timeoutHandle);
}
mIdleRequestCallbacks.insertBack(request);
if (!IsSuspended()) {
ScheduleIdleRequestDispatch();
}
return handle;
}
void nsGlobalWindowInner::CancelIdleCallback(uint32_t aHandle) {
for (IdleRequest* r : mIdleRequestCallbacks) {
if (r->Handle() == aHandle) {
RemoveIdleCallback(r);
break;
}
}
}
void nsGlobalWindowInner::DisableIdleCallbackRequests() {
if (mIdleRequestExecutor) {
mIdleRequestExecutor->Cancel();
mIdleRequestExecutor = nullptr;
}
while (!mIdleRequestCallbacks.isEmpty()) {
RefPtr<IdleRequest> request = mIdleRequestCallbacks.getFirst();
RemoveIdleCallback(request);
}
}
bool nsGlobalWindowInner::IsBackgroundInternal() const {
return !mOuterWindow || mOuterWindow->IsBackground();
}
class PromiseDocumentFlushedResolver final {
public:
PromiseDocumentFlushedResolver(Promise* aPromise,
PromiseDocumentFlushedCallback& aCallback)
: mPromise(aPromise), mCallback(&aCallback) {}
virtual ~PromiseDocumentFlushedResolver() = default;
void Call() {
nsMutationGuard guard;
ErrorResult error;
JS::Rooted<JS::Value> returnVal(RootingCx());
mCallback->Call(&returnVal, error);
if (error.Failed()) {
mPromise->MaybeReject(std::move(error));
} else if (guard.Mutated(0)) {
// Something within the callback mutated the DOM.
mPromise->MaybeReject(NS_ERROR_DOM_NO_MODIFICATION_ALLOWED_ERR);
} else {
mPromise->MaybeResolve(returnVal);
}
}
RefPtr<Promise> mPromise;
RefPtr<PromiseDocumentFlushedCallback> mCallback;
};
//*****************************************************************************
//*** nsGlobalWindowInner: Object Management
//*****************************************************************************
nsGlobalWindowInner::nsGlobalWindowInner(nsGlobalWindowOuter* aOuterWindow,
WindowGlobalChild* aActor)
: nsPIDOMWindowInner(aOuterWindow, aActor),
mWasOffline(false),
mHasHadSlowScript(false),
mIsChrome(false),
mCleanMessageManager(false),
mNeedsFocus(true),
mHasFocus(false),
mFocusByKeyOccurred(false),
mDidFireDocElemInserted(false),
mHasGamepad(false),
mHasXRSession(false),
mHasVRDisplayActivateEvents(false),
mXRRuntimeDetectionInFlight(false),
mXRPermissionRequestInFlight(false),
mXRPermissionGranted(false),
mWasCurrentInnerWindow(false),
mHasSeenGamepadInput(false),
mHintedWasLoading(false),
mHasOpenedExternalProtocolFrame(false),
mSuspendDepth(0),
mFreezeDepth(0),
#ifdef DEBUG
mSerial(0),
#endif
mFocusMethod(0),
mIdleRequestCallbackCounter(1),
mIdleRequestExecutor(nullptr),
mObservingRefresh(false),
mIteratingDocumentFlushedResolvers(false),
mCanSkipCCGeneration(0) {
mIsInnerWindow = true;
AssertIsOnMainThread();
nsLayoutStatics::AddRef();
// Initialize the PRCList (this).
PR_INIT_CLIST(this);
// add this inner window to the outer window list of inners.
PR_INSERT_AFTER(this, aOuterWindow);
mTimeoutManager = MakeUnique<dom::TimeoutManager>(
*this, StaticPrefs::dom_timeout_max_idle_defer_ms());
mObserver = new nsGlobalWindowObserver(this);
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);
os->AddObserver(mObserver, MEMORY_PRESSURE_OBSERVER_TOPIC, false);
os->AddObserver(mObserver, PERMISSION_CHANGED_TOPIC, false);
}
Preferences::AddStrongObserver(mObserver, "intl.accept_languages");
// Watch for storage notifications so we can fire storage events.
RefPtr<StorageNotifierService> sns = StorageNotifierService::GetOrCreate();
if (sns) {
sns->Register(mObserver);
}
if (XRE_IsContentProcess()) {
nsCOMPtr<nsIDocShell> docShell = GetDocShell();
if (docShell) {
mBrowserChild = docShell->GetBrowserChild();
}
}
if (gDumpFile == nullptr) {
nsAutoCString fname;
Preferences::GetCString("browser.dom.window.dump.file", fname);
if (!fname.IsEmpty()) {
// If this fails to open, Dump() knows to just go to stdout on null.
gDumpFile = fopen(fname.get(), "wb+");
} else {
gDumpFile = stdout;
}
}
#ifdef DEBUG
mSerial = nsContentUtils::InnerOrOuterWindowCreated();
MOZ_LOG(gDocShellAndDOMWindowLeakLogging, LogLevel::Info,
("++DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p]\n",
nsContentUtils::GetCurrentInnerOrOuterWindowCount(),
static_cast<void*>(ToCanonicalSupports(this)), getpid(), mSerial,
static_cast<void*>(ToCanonicalSupports(aOuterWindow))));
#endif
MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
("DOMWINDOW %p created outer=%p", this, aOuterWindow));
// Add ourselves to the inner windows list.
MOZ_ASSERT(sInnerWindowsById, "Inner Windows hash table must be created!");
MOZ_ASSERT(!sInnerWindowsById->Contains(mWindowID),
"This window shouldn't be in the hash table yet!");
// We seem to see crashes in release builds because of null
// |sInnerWindowsById|.
if (sInnerWindowsById) {
sInnerWindowsById->InsertOrUpdate(mWindowID, this);
}
}
#ifdef DEBUG
/* static */
void nsGlobalWindowInner::AssertIsOnMainThread() {
MOZ_ASSERT(NS_IsMainThread());
}
#endif // DEBUG
/* static */
void nsGlobalWindowInner::Init() {
AssertIsOnMainThread();
NS_ASSERTION(gDOMLeakPRLogInner,
"gDOMLeakPRLogInner should have been initialized!");
sInnerWindowsById = new InnerWindowByIdTable();
}
nsGlobalWindowInner::~nsGlobalWindowInner() {
AssertIsOnMainThread();
MOZ_ASSERT(!mHintedWasLoading);
if (IsChromeWindow()) {
MOZ_ASSERT(mCleanMessageManager,
"chrome windows may always disconnect the msg manager");
DisconnectAndClearGroupMessageManagers();
if (mChromeFields.mMessageManager) {
static_cast<nsFrameMessageManager*>(mChromeFields.mMessageManager.get())
->Disconnect();
}
mCleanMessageManager = false;
}
// In most cases this should already have been called, but call it again
// here to catch any corner cases.
FreeInnerObjects();
if (sInnerWindowsById) {
sInnerWindowsById->Remove(mWindowID);
}
nsContentUtils::InnerOrOuterWindowDestroyed();
#ifdef DEBUG
if (MOZ_LOG_TEST(gDocShellAndDOMWindowLeakLogging, LogLevel::Info)) {
nsAutoCString url;
if (mLastOpenedURI) {
url = mLastOpenedURI->GetSpecOrDefault();
// Data URLs can be very long, so truncate to avoid flooding the log.
const uint32_t maxURLLength = 1000;
if (url.Length() > maxURLLength) {
url.Truncate(maxURLLength);
}
}
nsGlobalWindowOuter* outer = nsGlobalWindowOuter::Cast(mOuterWindow);
MOZ_LOG(
gDocShellAndDOMWindowLeakLogging, LogLevel::Info,
("--DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p] [url = "
"%s]\n",
nsContentUtils::GetCurrentInnerOrOuterWindowCount(),
static_cast<void*>(ToCanonicalSupports(this)), getpid(), mSerial,
static_cast<void*>(ToCanonicalSupports(outer)), url.get()));
}
#endif
MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
("DOMWINDOW %p destroyed", this));
Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
mMutationBits ? 1 : 0);
// 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.
nsGlobalWindowOuter* outer = GetOuterWindowInternal();
if (outer) {
outer->MaybeClearInnerWindow(this);
}
// We don't have to leave the tab group if we are an inner window.
nsCOMPtr<nsIDeviceSensors> ac = do_GetService(NS_DEVICE_SENSORS_CONTRACTID);
if (ac) ac->RemoveWindowAsListener(this);
nsLayoutStatics::Release();
}
// static
void nsGlobalWindowInner::ShutDown() {
AssertIsOnMainThread();
if (gDumpFile && gDumpFile != stdout) {
fclose(gDumpFile);
}
gDumpFile = nullptr;
delete sInnerWindowsById;
sInnerWindowsById = nullptr;
}
void nsGlobalWindowInner::FreeInnerObjects() {
if (IsDying()) {
return;
}
StartDying();
if (mDoc && mDoc->GetWindowContext()) {
// The document is about to lose its window, so this is a good time to send
// our page use counters.
//
// (We also do this in Document::SetScriptGlobalObject(nullptr), which
// catches most cases of documents losing their window, but not all.)
mDoc->SendPageUseCounters();
}
// 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);
}
// Kill all of the workers for this window.
CancelWorkersForWindow(*this);
for (RefPtr<mozilla::dom::SharedWorker> pinnedWorker :
mSharedWorkers.ForwardRange()) {
pinnedWorker->Close();
}
if (mTimeoutManager) {
mTimeoutManager->ClearAllTimeouts();
}
DisableIdleCallbackRequests();
mChromeEventHandler = nullptr;
if (mListenerManager) {
mListenerManager->RemoveAllListeners();
mListenerManager->Disconnect();
mListenerManager = nullptr;
}
mHistory = nullptr;
if (mNavigator) {
mNavigator->OnNavigation();
mNavigator->Invalidate();
mNavigator = nullptr;
}
mScreen = nullptr;
#if defined(MOZ_WIDGET_ANDROID)
mOrientationChangeObserver = nullptr;
#endif
if (mDoc) {
// Remember the document's principal, URI, and CSP.
mDocumentPrincipal = mDoc->NodePrincipal();
mDocumentStoragePrincipal = mDoc->EffectiveStoragePrincipal();
mDocumentPartitionedPrincipal = mDoc->PartitionedPrincipal();
mDocumentURI = mDoc->GetDocumentURI();
mDocBaseURI = mDoc->GetDocBaseURI();
mDocumentCsp = mDoc->GetCsp();
while (mDoc->EventHandlingSuppressed()) {
mDoc->UnsuppressEventHandlingAndFireEvents(false);
}
}
// Remove our reference to the document and the document principal.
mFocusedElement = nullptr;
if (mApplicationCache) {
static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())
->Disconnect();
mApplicationCache = nullptr;
}
if (mIndexedDB) {
mIndexedDB->DisconnectFromGlobal(this);
mIndexedDB = nullptr;
}
nsIGlobalObject::UnlinkObjectsInGlobal();
NotifyWindowIDDestroyed("inner-window-destroyed");
for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
mAudioContexts[i]->OnWindowDestroy();
}
mAudioContexts.Clear();
for (MediaKeys* mediaKeys : mMediaKeysInstances) {
mediaKeys->OnInnerWindowDestroy();
}
mMediaKeysInstances.Clear();
DisableGamepadUpdates();
mHasGamepad = false;
mGamepads.Clear();
DisableVRUpdates();
mHasXRSession = false;
mHasVRDisplayActivateEvents = false;
mXRRuntimeDetectionInFlight = false;
mXRPermissionRequestInFlight = false;
mXRPermissionGranted = false;
mVRDisplays.Clear();
// This breaks a cycle between the window and the ClientSource object.
mClientSource.reset();
if (mWindowGlobalChild) {
// Remove any remaining listeners.
int64_t nListeners = mWindowGlobalChild->BeforeUnloadListeners();
for (int64_t i = 0; i < nListeners; ++i) {
mWindowGlobalChild->BeforeUnloadRemoved();
}
MOZ_ASSERT(mWindowGlobalChild->BeforeUnloadListeners() == 0);
}
// If we have any promiseDocumentFlushed callbacks, fire them now so
// that the Promises can resolve.
CallDocumentFlushedResolvers(/* aUntilExhaustion = */ true);
DisconnectEventTargetObjects();
if (mObserver) {
nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
if (os) {
os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC);
os->RemoveObserver(mObserver, MEMORY_PRESSURE_OBSERVER_TOPIC);
os->RemoveObserver(mObserver, PERMISSION_CHANGED_TOPIC);
}
RefPtr<StorageNotifierService> sns = StorageNotifierService::GetOrCreate();
if (sns) {
sns->Unregister(mObserver);
}
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();
}
mMenubar = nullptr;
mToolbar = nullptr;
mLocationbar = nullptr;
mPersonalbar = nullptr;
mStatusbar = nullptr;
mScrollbars = nullptr;
mConsole = nullptr;
mPaintWorklet = nullptr;
mExternal = nullptr;
mInstallTrigger = nullptr;
if (mLocalStorage) {
mLocalStorage->Disconnect();
mLocalStorage = nullptr;
}
mSessionStorage = nullptr;
mPerformance = nullptr;
mContentMediaController = nullptr;
mSharedWorkers.Clear();
#ifdef MOZ_WEBSPEECH
mSpeechSynthesis = nullptr;
#endif
mGlean = nullptr;
mGleanPings = nullptr;
mParentTarget = nullptr;
if (mCleanMessageManager) {
MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned");
if (mChromeFields.mMessageManager) {
mChromeFields.mMessageManager->Disconnect();
}
}
if (mWindowGlobalChild && !mWindowGlobalChild->IsClosed()) {
mWindowGlobalChild->Destroy();
}
mWindowGlobalChild = nullptr;
mIntlUtils = nullptr;
HintIsLoading(false);
}
//*****************************************************************************
// nsGlobalWindowInner::nsISupports
//*****************************************************************************
// QueryInterface implementation for nsGlobalWindowInner
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsGlobalWindowInner)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, EventTarget)
NS_INTERFACE_MAP_ENTRY(nsIDOMWindow)
NS_INTERFACE_MAP_ENTRY(nsIGlobalObject)
NS_INTERFACE_MAP_ENTRY(nsIScriptGlobalObject)
NS_INTERFACE_MAP_ENTRY(nsIScriptObjectPrincipal)
NS_INTERFACE_MAP_ENTRY(mozilla::dom::EventTarget)
NS_INTERFACE_MAP_ENTRY(nsPIDOMWindowInner)
NS_INTERFACE_MAP_ENTRY(mozIDOMWindow)
NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIDOMChromeWindow, IsChromeWindow())
NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
NS_INTERFACE_MAP_END
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsGlobalWindowInner)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsGlobalWindowInner)
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsGlobalWindowInner)
if (tmp->IsBlackForCC(false)) {
if (nsCCUncollectableMarker::InGeneration(tmp->mCanSkipCCGeneration)) {
return true;
}
tmp->mCanSkipCCGeneration = nsCCUncollectableMarker::sGeneration;
if (EventListenerManager* elm = tmp->GetExistingListenerManager()) {
elm->MarkForCC();
}
if (tmp->mTimeoutManager) {
tmp->mTimeoutManager->UnmarkGrayTimers();
}
return true;
}
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsGlobalWindowInner)
return tmp->IsBlackForCC(true);
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsGlobalWindowInner)
return tmp->IsBlackForCC(false);
NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
NS_IMPL_CYCLE_COLLECTION_CLASS(nsGlobalWindowInner)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INTERNAL(nsGlobalWindowInner)
if (MOZ_UNLIKELY(cb.WantDebugInfo())) {
char name[512];
nsAutoCString uri;
if (tmp->mDoc && tmp->mDoc->GetDocumentURI()) {
uri = tmp->mDoc->GetDocumentURI()->GetSpecOrDefault();
}
SprintfLiteral(name, "nsGlobalWindowInner # %" PRIu64 " inner %s",
tmp->mWindowID, uri.get());
cb.DescribeRefCountedNode(tmp->mRefCnt.get(), name);
} else {
NS_IMPL_CYCLE_COLLECTION_DESCRIBE(nsGlobalWindowInner, tmp->mRefCnt.get())
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNavigator)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPerformance)
#ifdef MOZ_WEBSPEECH
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSpeechSynthesis)
#endif
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGlean)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGleanPings)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOuterWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTopInnerWindow)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mListenerManager)
if (tmp->mTimeoutManager) {
tmp->mTimeoutManager->ForEachUnorderedTimeout([&cb](Timeout* timeout) {
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(mSharedWorkers)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mLocalStorage)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSessionStorage)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mApplicationCache)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIndexedDB)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentStoragePrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentPartitionedPrincipal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentCsp)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowserChild)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleRequestExecutor)
for (IdleRequest* request : tmp->mIdleRequestCallbacks) {
cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest));
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClientSource)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRDisplays)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDebuggerNotificationManager)
// Traverse stuff from nsPIDOMWindow
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedElement)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBrowsingContext)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindowGlobalChild)
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(mPaintWorklet)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExternal)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mInstallTrigger)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIntlUtils)
tmp->TraverseObjectsInGlobal(cb);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mMessageManager)
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mGroupMessageManagers)
for (size_t i = 0; i < tmp->mDocumentFlushedResolvers.Length(); i++) {
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentFlushedResolvers[i]->mPromise);
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDocumentFlushedResolvers[i]->mCallback);
}
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner)
NS_IMPL_CYCLE_COLLECTION_UNLINK_WEAK_REFERENCE
if (sInnerWindowsById) {
sInnerWindowsById->Remove(tmp->mWindowID);
}
JSObject* wrapper = tmp->GetWrapperPreserveColor();
if (wrapper) {
// Mark our realm as dead, so the JS engine won't hand out our
// global after this point.
JS::SetRealmNonLive(js::GetNonCCWObjectRealm(wrapper));
}
NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance)
#ifdef MOZ_WEBSPEECH
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechSynthesis)
#endif
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGlean)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGleanPings)
if (tmp->mOuterWindow) {
nsGlobalWindowOuter::Cast(tmp->mOuterWindow)->MaybeClearInnerWindow(tmp);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOuterWindow)
}
if (tmp->mListenerManager) {
tmp->mListenerManager->Disconnect();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mListenerManager)
}
// Here the Timeouts list would've been unlinked, but we rely on
// that Timeout objects have been traced and will remove themselves
// while unlinking.
tmp->UpdateTopInnerWindow();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mTopInnerWindow)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mLocation)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mHistory)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCustomElements)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mSharedWorkers)
if (tmp->mLocalStorage) {
tmp->mLocalStorage->Disconnect();
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)
}
if (tmp->mIndexedDB) {
tmp->mIndexedDB->DisconnectFromGlobal(tmp);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB)
}
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentStoragePrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPartitionedPrincipal)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentCsp)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowserChild)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mVRDisplays)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDebuggerNotificationManager)
// Unlink stuff from nsPIDOMWindow
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mFocusedElement)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mBrowsingContext)
MOZ_DIAGNOSTIC_ASSERT(
!tmp->mWindowGlobalChild || tmp->mWindowGlobalChild->IsClosed(),
"How are we unlinking a window before its actor has been destroyed?");
NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindowGlobalChild)
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(mPaintWorklet)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mExternal)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mInstallTrigger)
NS_IMPL_CYCLE_COLLECTION_UNLINK(mIntlUtils)
tmp->UnlinkObjectsInGlobal();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleRequestExecutor)
// Here the IdleRequest list would've been unlinked, but we rely on
// that IdleRequest objects have been traced and will remove
// themselves while unlinking.
NS_IMPL_CYCLE_COLLECTION_UNLINK(mClientSource)
if (tmp->IsChromeWindow()) {
if (tmp->mChromeFields.mMessageManager) {
static_cast<nsFrameMessageManager*>(
tmp->mChromeFields.mMessageManager.get())
->Disconnect();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mMessageManager)
}
tmp->DisconnectAndClearGroupMessageManagers();
NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mGroupMessageManagers)
}
for (size_t i = 0; i < tmp->mDocumentFlushedResolvers.Length(); i++) {
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentFlushedResolvers[i]->mPromise);
NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentFlushedResolvers[i]->mCallback);
}
tmp->mDocumentFlushedResolvers.Clear();
NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
#ifdef DEBUG
void nsGlobalWindowInner::RiskyUnlink() {
NS_CYCLE_COLLECTION_INNERNAME.Unlink(this);
}
#endif
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(nsGlobalWindowInner)
NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
NS_IMPL_CYCLE_COLLECTION_TRACE_END
bool nsGlobalWindowInner::IsBlackForCC(bool aTracingNeeded) {
if (!nsCCUncollectableMarker::sGeneration) {
return false;
}
return (nsCCUncollectableMarker::InGeneration(GetMarkedCCGeneration()) ||
HasKnownLiveWrapper()) &&
(!aTracingNeeded || HasNothingToTrace(ToSupports(this)));
}
//*****************************************************************************
// nsGlobalWindowInner::nsIScriptGlobalObject
//*****************************************************************************
nsresult nsGlobalWindowInner::EnsureScriptEnvironment() {
// NOTE: We can't use FORWARD_TO_OUTER here because we don't want to fail if
// we're called on an inactive inner window.
nsGlobalWindowOuter* outer = GetOuterWindowInternal();
if (!outer) {
NS_WARNING("No outer window available!");
return NS_ERROR_FAILURE;
}
return outer->EnsureScriptEnvironment();
}
nsIScriptContext* nsGlobalWindowInner::GetScriptContext() {
nsGlobalWindowOuter* outer = GetOuterWindowInternal();
if (!outer) {
return nullptr;
}
return outer->GetScriptContext();
}
void nsGlobalWindowInner::TraceGlobalJSObject(JSTracer* aTrc) {
TraceWrapper(aTrc, "active window global");
}
void nsGlobalWindowInner::UpdateAutoplayPermission() {
if (!GetWindowContext()) {
return;
}
uint32_t perm = AutoplayPolicy::GetSiteAutoplayPermission(GetPrincipal());
if (GetWindowContext()->GetAutoplayPermission() == perm) {
return;
}
// Setting autoplay permission on a discarded context has no effect.
Unused << GetWindowContext()->SetAutoplayPermission(perm);
}
void nsGlobalWindowInner::UpdateShortcutsPermission() {
if (!GetWindowContext() ||
!GetWindowContext()->GetBrowsingContext()->IsTop()) {
// We only cache the shortcuts permission on top-level WindowContexts
// since we always check the top-level principal for the permission.
return;
}
uint32_t perm = GetShortcutsPermission(GetPrincipal());
if (GetWindowContext()->GetShortcutsPermission() == perm) {
return;
}
// If the WindowContext is discarded this has no effect.
Unused << GetWindowContext()->SetShortcutsPermission(perm);
}
/* static */
uint32_t nsGlobalWindowInner::GetShortcutsPermission(nsIPrincipal* aPrincipal) {
uint32_t perm = nsIPermissionManager::DENY_ACTION;
nsCOMPtr<nsIPermissionManager> permMgr =
mozilla::components::PermissionManager::Service();
if (aPrincipal && permMgr) {
permMgr->TestExactPermissionFromPrincipal(aPrincipal, "shortcuts"_ns,
&perm);
}
return perm;
}
void nsGlobalWindowInner::UpdatePopupPermission() {
if (!GetWindowContext()) {
return;
}
uint32_t perm = PopupBlocker::GetPopupPermission(GetPrincipal());
if (GetWindowContext()->GetPopupPermission() == perm) {
return;
}
// If the WindowContext is discarded this has no effect.
Unused << GetWindowContext()->SetPopupPermission(perm);
}
void nsGlobalWindowInner::UpdatePermissions() {
if (!GetWindowContext()) {
return;
}
nsCOMPtr<nsIPrincipal> principal = GetPrincipal();
RefPtr<WindowContext> windowContext = GetWindowContext();
WindowContext::Transaction txn;
txn.SetAutoplayPermission(
AutoplayPolicy::GetSiteAutoplayPermission(principal));
txn.SetPopupPermission(PopupBlocker::GetPopupPermission(principal));
if (windowContext->IsTop()) {
txn.SetShortcutsPermission(GetShortcutsPermission(principal));
}
// Setting permissions on a discarded WindowContext has no effect
Unused << txn.Commit(windowContext);
}
void nsGlobalWindowInner::InitDocumentDependentState(JSContext* aCx) {
MOZ_ASSERT(mDoc);
if (MOZ_LOG_TEST(gDOMLeakPRLogInner, LogLevel::Debug)) {
nsIURI* uri = mDoc->GetDocumentURI();
MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
("DOMWINDOW %p SetNewDocument %s", this,
uri ? uri->GetSpecOrDefault().get() : ""));
}
mFocusedElement = nullptr;
mLocalStorage = nullptr;
mSessionStorage = nullptr;
mPerformance = nullptr;
// This must be called after nullifying the internal objects because here we
// could recreate them, calling the getter methods, and store them into the JS
// slots. If we nullify them after, the slot values and the objects will be
// out of sync.
ClearDocumentDependentSlots(aCx);
if (!mWindowGlobalChild) {
mWindowGlobalChild = WindowGlobalChild::Create(this);
}
MOZ_ASSERT(!GetWindowContext()->HasBeenUserGestureActivated(),
"WindowContext should always not have user gesture activation at "
"this point.");
UpdatePermissions();
RefPtr<PermissionDelegateHandler> permDelegateHandler =
mDoc->GetPermissionDelegateHandler();
if (permDelegateHandler) {
permDelegateHandler->PopulateAllDelegatedPermissions();
}
#if defined(MOZ_WIDGET_ANDROID)
// When we insert the new document to the window in the top-level browsing
// context, we should reset the status of the request which is used for the
// previous document.
if (mWindowGlobalChild && GetBrowsingContext() &&
!GetBrowsingContext()->GetParent()) {
// Return value of setting synced field should be checked. See bug 1656492.
Unused << GetBrowsingContext()->ResetGVAutoplayRequestStatus();
}
#endif
#ifdef DEBUG
mLastOpenedURI = mDoc->GetDocumentURI();
#endif
Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS,
mMutationBits ? 1 : 0);
// Clear our mutation bitfield.
mMutationBits = 0;
}
nsresult nsGlobalWindowInner::EnsureClientSource() {
MOZ_DIAGNOSTIC_ASSERT(mDoc);
bool newClientSource = false;
// Get the load info for the document if we performed a load. Be careful not
// to look at local URLs, though. Local URLs are those that have a scheme of:
// * about:
// * data:
// * blob:
// We also do an additional check here so that we only treat about:blank
// and about:srcdoc as local URLs. Other internal firefox about: URLs should
// not be treated this way.
nsCOMPtr<nsILoadInfo> loadInfo;
nsCOMPtr<nsIChannel> channel = mDoc->GetChannel();
if (channel) {
nsCOMPtr<nsIURI> uri;
Unused << channel->GetURI(getter_AddRefs(uri));
bool ignoreLoadInfo = false;
// Note, this is mostly copied from NS_IsAboutBlank(). Its duplicated
// here so we can efficiently check about:srcdoc as well.
if (uri->SchemeIs("about")) {
nsCString spec = uri->GetSpecOrDefault();
ignoreLoadInfo = spec.EqualsLiteral("about:blank") ||
spec.EqualsLiteral("about:srcdoc");
} else {
// Its not an about: URL, so now check for our other URL types.
ignoreLoadInfo = uri->SchemeIs("data") || uri->SchemeIs("blob");
}
if (!ignoreLoadInfo) {
loadInfo = channel->LoadInfo();
}
}
// Take the initial client source from the docshell immediately. Even if we
// don't end up using it here we should consume it.
UniquePtr<ClientSource> initialClientSource;
nsIDocShell* docshell = GetDocShell();
if (docshell) {
initialClientSource = docshell->TakeInitialClientSource();
}
// Try to get the reserved client from the LoadInfo. A Client is
// reserved at the start of the channel load if there is not an
// initial about:blank document that will be reused. It is also
// created if the channel load encounters a cross-origin redirect.
if (loadInfo) {
UniquePtr<ClientSource> reservedClient =
loadInfo->TakeReservedClientSource();
if (reservedClient) {
mClientSource.reset();
mClientSource = std::move(reservedClient);
newClientSource = true;
}
}
// We don't have a LoadInfo reserved client, but maybe we should
// be inheriting an initial one from the docshell. This means
// that the docshell started the channel load before creating the
// initial about:blank document. This is an optimization, though,
// and it created an initial Client as a placeholder for the document.
// In this case we want to inherit this placeholder Client here.
if (!mClientSource) {
mClientSource = std::move(initialClientSource);
if (mClientSource) {
newClientSource = true;
}
}
// Verify the final ClientSource principal matches the final document
// principal. The ClientChannelHelper handles things like network
// redirects, but there are other ways the document principal can change.
// For example, if something sets the nsIChannel.owner property, then
// the final channel principal can be anything. Unfortunately there is
// no good way to detect this until after the channel completes loading.
//
// For now we handle this just by reseting the ClientSource. This will
// result in a new ClientSource with the correct principal being created.
// To APIs like ServiceWorker and Clients API it will look like there was
// an initial content page created that was then immediately replaced.
// This is pretty close to what we are actually doing.
if (mClientSource) {
auto principalOrErr = mClientSource->Info().GetPrincipal();
nsCOMPtr<nsIPrincipal> clientPrincipal =
principalOrErr.isOk() ? principalOrErr.unwrap() : nullptr;
if (!clientPrincipal || !clientPrincipal->Equals(mDoc->NodePrincipal())) {
mClientSource.reset();
}
}
// If we don't have a reserved client or an initial client, then create
// one now. This can happen in certain cases where we avoid preallocating
// the client in the docshell. This mainly occurs in situations where
// the principal is not clearly inherited from the parent; e.g. sandboxed
// iframes, window.open(), etc.
//
// We also do this late ClientSource creation if the final document ended
// up with a different principal.
//
// TODO: We may not be marking initial about:blank documents created
// this way as controlled by a service worker properly. The
// controller should be coming from the same place as the inheritted
// principal. We do this in docshell, but as mentioned we aren't
// smart enough to handle all cases yet. For example, a
// window.open() with new URL should inherit the controller from
// the opener, but we probably don't handle that yet.
if (!mClientSource) {
mClientSource = ClientManager::CreateSource(
ClientType::Window, EventTargetFor(TaskCategory::Other),
mDoc->NodePrincipal());
MOZ_DIAGNOSTIC_ASSERT(mClientSource);
newClientSource = true;
// Note, we don't apply the loadinfo controller below if we create
// the ClientSource here.
}
// The load may have started controlling the Client as well. If
// so, mark it as controlled immediately here. The actor may
// or may not have been notified by the parent side about being
// controlled yet.
//
// Note: We should be careful not to control a client that was created late.
// These clients were not seen by the ServiceWorkerManager when it
// marked the LoadInfo controlled and it won't know about them. Its
// also possible we are creating the client late due to the final
// principal changing and these clients should definitely not be
// controlled by a service worker with a different principal.
else if (loadInfo) {
const Maybe<ServiceWorkerDescriptor> controller = loadInfo->GetController();
if (controller.isSome()) {
mClientSource->SetController(controller.ref());
}
// We also have to handle the case where te initial about:blank is
// controlled due to inheritting the service worker from its parent,
// but the actual nsIChannel load is not covered by any service worker.
// In this case we want the final page to be uncontrolled. There is
// an open spec issue about how exactly this should be handled, but for
// now we just force creation of a new ClientSource to clear the
// controller.
//
// https://github.com/w3c/ServiceWorker/issues/1232
//
else if (mClientSource->GetController().isSome()) {
mClientSource.reset();
mClientSource = ClientManager::CreateSource(
ClientType::Window, EventTargetFor(TaskCategory::Other),
mDoc->NodePrincipal());
MOZ_DIAGNOSTIC_ASSERT(mClientSource);
newClientSource = true;
}
}
if (mClientSource) {
// Generally the CSP is stored within the Client and cached on the document.
// At the time of CSP parsing however, the Client has not been created yet,
// hence we store the CSP on the document and propagate/sync the CSP with
// Client here when we create the Client.
mClientSource->SetCsp(mDoc->GetCsp());
DocGroup* docGroup = GetDocGroup();
MOZ_DIAGNOSTIC_ASSERT(docGroup);
mClientSource->SetAgentClusterId(docGroup->AgentClusterId());
if (mWindowGlobalChild) {
mWindowGlobalChild->SendSetClientInfo(mClientSource->Info().ToIPC());
}
}
// Its possible that we got a client just after being frozen in
// the bfcache. In that case freeze the client immediately.
if (newClientSource && IsFrozen()) {
mClientSource->Freeze();
}
return NS_OK;
}
nsresult nsGlobalWindowInner::ExecutionReady() {
nsresult rv = EnsureClientSource();
NS_ENSURE_SUCCESS(rv, rv);
rv = mClientSource->WindowExecutionReady(this);
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
void nsGlobalWindowInner::UpdateParentTarget() {
// NOTE: This method is identical to
// nsGlobalWindowOuter::UpdateParentTarget(). IF YOU UPDATE THIS METHOD,
// UPDATE THE OTHER ONE TOO!
// 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.
nsPIDOMWindowOuter* outer = GetOuterWindow();
if (!outer) {
return;
}
nsCOMPtr<Element> frameElement = outer->GetFrameElementInternal();
nsCOMPtr<EventTarget> eventTarget =
nsContentUtils::TryGetBrowserChildGlobal(frameElement);
if (!eventTarget) {
nsGlobalWindowOuter* topWin = GetInProcessScriptableTopInternal();
if (topWin) {
frameElement = topWin->GetFrameElementInternal();
eventTarget = nsContentUtils::TryGetBrowserChildGlobal(frameElement);
}
}
if (!eventTarget) {
eventTarget = nsContentUtils::TryGetBrowserChildGlobal(mChromeEventHandler);
}
if (!eventTarget) {
eventTarget = mChromeEventHandler;
}
mParentTarget = eventTarget;
}
EventTarget* nsGlobalWindowInner::GetTargetForDOMEvent() {
return GetOuterWindowInternal();
}
void nsGlobalWindowInner::GetEventTargetParent(EventChainPreVisitor& aVisitor) {
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()) {
sMouseDown = true;
} else if ((msg == eMouseUp || msg == eDragEnd) &&
aVisitor.mEvent->IsTrusted()) {
sMouseDown = false;
if (sDragServiceDisabled) {
nsCOMPtr<nsIDragService> ds =
do_GetService("@mozilla.org/widget/dragservice;1");
if (ds) {
sDragServiceDisabled = false;
ds->Unsuppress();
}
}
}
aVisitor.SetParentTarget(GetParentTarget(), true);
}
void nsGlobalWindowInner::FireFrameLoadEvent() {
// If we're not in a content frame, or are at a BrowsingContext tree boundary,
// such as the content-chrome boundary, don't fire the "load" event.
if (GetBrowsingContext()->IsTopContent() ||
GetBrowsingContext()->IsChrome()) {
return;
}
// If embedder is same-process, fire the event on our embedder element.
//
// XXX: Bug 1440212 is looking into potentially changing this behaviour to act
// more like the remote case when in-process.
RefPtr<Element> element = GetBrowsingContext()->GetEmbedderElement();
if (element) {
nsEventStatus status = nsEventStatus_eIgnore;
WidgetEvent event(/* aIsTrusted = */ true, eLoad);
event.mFlags.mBubbles = false;
event.mFlags.mCancelable = false;
if (mozilla::dom::DocGroup::TryToLoadIframesInBackground()) {
nsDocShell* ds = nsDocShell::Cast(GetDocShell());
if (ds && !ds->HasFakeOnLoadDispatched()) {
EventDispatcher::Dispatch(element, nullptr, &event, nullptr, &status);
}
} else {
// 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;
}
// We don't have an in-process embedder. Try to get our `BrowserChild` actor
// to send a message to that embedder. We want to double-check that our outer
// window is actually the one at the root of this browserChild though, just in
// case.
RefPtr<BrowserChild> browserChild =
BrowserChild::GetFrom(static_cast<nsPIDOMWindowInner*>(this));
if (browserChild) {
// Double-check that our outer window is actually at the root of this
// `BrowserChild`, in case we're in an odd maybe-unhosted situation like a
// print preview dialog.
nsCOMPtr<nsPIDOMWindowOuter> rootOuter =
do_GetInterface(browserChild->WebNavigation());
if (!rootOuter || rootOuter != GetOuterWindow()) {
return;
}
mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents(
EmbedderElementEventType::LoadEvent);
}
}
nsresult nsGlobalWindowInner::PostHandleEvent(EventChainPostVisitor& aVisitor) {
// 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. */
RefPtr<EventTarget> 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()) {
// If any VR display presentation is active at unload, the next page
// will receive a vrdisplayactive event to indicate that it should
// immediately begin vr presentation. This should occur when navigating
// forwards, navigating backwards, and on page reload.
for (const auto& display : mVRDisplays) {
if (display->IsPresenting()) {
display->StartVRNavigation();
// Save this VR display ID to trigger vrdisplayactivate event
// after the next load event.
nsGlobalWindowOuter* outer = GetOuterWindowInternal();
if (outer) {
outer->SetAutoActivateVRDisplayID(display->DisplayId());
}
// XXX The WebVR 1.1 spec does not define which of multiple VR
// presenting VR displays will be chosen during navigation.
// As the underlying platform VR API's currently only allow a single
// VR display, it is safe to choose the first VR display for now.
break;
}
}
mIsDocumentLoaded = false;
// Tell the parent process that the document is not loaded.
if (mWindowGlobalChild) {
mWindowGlobalChild->SendUpdateDocumentHasLoaded(mIsDocumentLoaded);
}
} else if (aVisitor.mEvent->mMessage == eLoad &&
aVisitor.mEvent->IsTrusted()) {
// This is page load event since load events don't propagate to |window|.
// @see Document::GetEventTargetParent.
mIsDocumentLoaded = true;
// Tell the parent process that the document is loaded.
if (mWindowGlobalChild) {
mWindowGlobalChild->SendUpdateDocumentHasLoaded(mIsDocumentLoaded);
}
mTimeoutManager->OnDocumentLoaded();
MOZ_ASSERT(aVisitor.mEvent->IsTrusted());
FireFrameLoadEvent();
if (mVREventObserver) {
mVREventObserver->NotifyAfterLoad();
}
uint32_t autoActivateVRDisplayID = 0;
nsGlobalWindowOuter* outer = GetOuterWindowInternal();
if (outer) {
autoActivateVRDisplayID = outer->GetAutoActivateVRDisplayID();
}
if (autoActivateVRDisplayID) {
DispatchVRDisplayActivate(autoActivateVRDisplayID,
VRDisplayEventReason::Navigation);
}
}
return NS_OK;
}
nsresult nsGlobalWindowInner::DefineArgumentsProperty(nsIArray* aArguments) {
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);
}
//*****************************************************************************
// nsGlobalWindowInner::nsIScriptObjectPrincipal
//*****************************************************************************
nsIPrincipal* nsGlobalWindowInner::GetPrincipal() {
if (mDoc) {
// If we have a document, get the principal from the document
return mDoc->NodePrincipal();
}
if (mDocumentPrincipal) {
return mDocumentPrincipal;
}
// If we don't have a principal and we don't have a document we
// ask the parent window for the principal. This can happen when
// loading a frameset that has a <frame src="javascript:xxx">, in
// that case the global window is used in JS before we've loaded
// a document into the window.
nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
do_QueryInterface(GetInProcessParentInternal());
if (objPrincipal) {
return objPrincipal->GetPrincipal();
}
return nullptr;
}
nsIPrincipal* nsGlobalWindowInner::GetEffectiveStoragePrincipal() {
if (mDoc) {
// If we have a document, get the principal from the document
return mDoc->EffectiveStoragePrincipal();
}
if (mDocumentStoragePrincipal) {
return mDocumentStoragePrincipal;
}
// If we don't have a storage principal and we don't have a document we ask
// the parent window for the storage principal.
nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
do_QueryInterface(GetInProcessParentInternal());
if (objPrincipal) {
return objPrincipal->GetEffectiveStoragePrincipal();
}
return nullptr;
}
nsIPrincipal* nsGlobalWindowInner::PartitionedPrincipal() {
if (mDoc) {
// If we have a document, get the principal from the document
return mDoc->PartitionedPrincipal();
}
if (mDocumentPartitionedPrincipal) {
return mDocumentPartitionedPrincipal;
}
// If we don't have a partitioned principal and we don't have a document we
// ask the parent window for the partitioned principal.
nsCOMPtr<nsIScriptObjectPrincipal> objPrincipal =
do_QueryInterface(GetInProcessParentInternal());
if (objPrincipal) {
return objPrincipal->PartitionedPrincipal();
}
return nullptr;
}
//*****************************************************************************
// nsGlobalWindowInner::nsIDOMWindow
//*****************************************************************************
bool nsPIDOMWindowInner::AddAudioContext(AudioContext* aAudioContext) {
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) {
mAudioContexts.RemoveElement(aAudioContext);
}
void nsPIDOMWindowInner::MuteAudioContexts() {
for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
if (!mAudioContexts[i]->IsOffline()) {
mAudioContexts[i]->Mute();
}
}
}
void nsPIDOMWindowInner::UnmuteAudioContexts() {
for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
if (!mAudioContexts[i]->IsOffline()) {
mAudioContexts[i]->Unmute();
}
}
}
WindowProxyHolder nsGlobalWindowInner::Window() {
return WindowProxyHolder(GetBrowsingContext());
}
Navigator* nsPIDOMWindowInner::Navigator() {
if (!mNavigator) {
mNavigator = new mozilla::dom::Navigator(this);
}
return mNavigator;
}
VisualViewport* nsGlobalWindowInner::VisualViewport() {
if (!mVisualViewport) {
mVisualViewport = new mozilla::dom::VisualViewport(this);
}
return mVisualViewport;
}
nsScreen* nsGlobalWindowInner::GetScreen(ErrorResult& aError) {
if (!mScreen) {
mScreen = nsScreen::Create(this);
if (!mScreen) {
aError.Throw(NS_ERROR_UNEXPECTED);
return nullptr;
}
}
return mScreen;
}
nsHistory* nsGlobalWindowInner::GetHistory(ErrorResult& aError) {
if (!mHistory) {
mHistory = new nsHistory(this);
}
return mHistory;
}
CustomElementRegistry* nsGlobalWindowInner::CustomElements() {
if (!mCustomElements) {
mCustomElements = new CustomElementRegistry(this);
}
return mCustomElements;
}
CustomElementRegistry* nsGlobalWindowInner::GetExistingCustomElements() {
return mCustomElements;
}
Performance* nsPIDOMWindowInner::GetPerformance() {
CreatePerformanceObjectIfNeeded();
return mPerformance;
}
void nsPIDOMWindowInner::QueuePerformanceNavigationTiming() {
CreatePerformanceObjectIfNeeded();
if (mPerformance) {
mPerformance->QueueNavigationTimingEntry();
}
}
void nsPIDOMWindowInner::CreatePerformanceObjectIfNeeded() {
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) {
mPerformance = Performance::CreateForMainThread(this, mDoc->NodePrincipal(),
timing, timedChannel);
}
}
bool nsPIDOMWindowInner::IsSecureContext() const {
return nsGlobalWindowInner::Cast(this)->IsSecureContext();
}
void nsPIDOMWindowInner::Suspend(bool aIncludeSubWindows) {
nsGlobalWindowInner::Cast(this)->Suspend(aIncludeSubWindows);
}
void nsPIDOMWindowInner::Resume(bool aIncludeSubWindows) {
nsGlobalWindowInner::Cast(this)->Resume(aIncludeSubWindows);
}
void nsPIDOMWindowInner::SyncStateFromParentWindow() {
nsGlobalWindowInner::Cast(this)->SyncStateFromParentWindow();
}
Maybe<ClientInfo> nsPIDOMWindowInner::GetClientInfo() const {
return nsGlobalWindowInner::Cast(this)->GetClientInfo();
}
Maybe<ClientState> nsPIDOMWindowInner::GetClientState() const {
return nsGlobalWindowInner::Cast(this)->GetClientState();
}
Maybe<ServiceWorkerDescriptor> nsPIDOMWindowInner::GetController() const {
return nsGlobalWindowInner::Cast(this)->GetController();
}
void nsPIDOMWindowInner::SetCsp(nsIContentSecurityPolicy* aCsp) {
return nsGlobalWindowInner::Cast(this)->SetCsp(aCsp);
}
void nsPIDOMWindowInner::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCsp) {
return nsGlobalWindowInner::Cast(this)->SetPreloadCsp(aPreloadCsp);
}
nsIContentSecurityPolicy* nsPIDOMWindowInner::GetCsp() {
return nsGlobalWindowInner::Cast(this)->GetCsp();
}
void nsPIDOMWindowInner::NoteCalledRegisterForServiceWorkerScope(
const nsACString& aScope) {
nsGlobalWindowInner::Cast(this)->NoteCalledRegisterForServiceWorkerScope(
aScope);
}
void nsPIDOMWindowInner::NoteDOMContentLoaded() {
nsGlobalWindowInner::Cast(this)->NoteDOMContentLoaded();
}
bool nsGlobalWindowInner::ShouldReportForServiceWorkerScope(
const nsAString& aScope) {
bool result = false;
nsPIDOMWindowOuter* topOuter = GetInProcessScriptableTop();
NS_ENSURE_TRUE(topOuter, false);
nsGlobalWindowInner* topInner =
nsGlobalWindowInner::Cast(topOuter->GetCurrentInnerWindow());
NS_ENSURE_TRUE(topInner, false);
topInner->ShouldReportForServiceWorkerScopeInternal(
NS_ConvertUTF16toUTF8(aScope), &result);
return result;
}
InstallTriggerImpl* nsGlobalWindowInner::GetInstallTrigger() {
if (!mInstallTrigger) {
ErrorResult rv;
mInstallTrigger = ConstructJSImplementation<InstallTriggerImpl>(
"@mozilla.org/addons/installtrigger;1", this, rv);
if (rv.Failed()) {
rv.SuppressException();
return nullptr;
}
}
return mInstallTrigger;
}
nsIDOMWindowUtils* nsGlobalWindowInner::GetWindowUtils(ErrorResult& aRv) {
FORWARD_TO_OUTER_OR_THROW(WindowUtils, (), aRv, nullptr);
}
CallState nsGlobalWindowInner::ShouldReportForServiceWorkerScopeInternal(
const nsACString& aScope, bool* aResultOut) {
MOZ_DIAGNOSTIC_ASSERT(aResultOut);
// First check to see if this window is controlled. If so, then we have
// found a match and are done.
const Maybe<ServiceWorkerDescriptor> swd = GetController();
if (swd.isSome() && swd.ref().Scope() == aScope) {
*aResultOut = true;
return CallState::Stop;
}
// Next, check to see if this window has called
// navigator.serviceWorker.register() for this scope. If so, then treat this
// as a match so console reports appear in the devtools console.
if (mClientSource &&
mClientSource->CalledRegisterForServiceWorkerScope(aScope)) {
*aResultOut = true;
return CallState::Stop;
}
// Finally check the current docshell nsILoadGroup to see if there are any
// outstanding navigation requests. If so, match the scope against the
// channel's URL. We want to show console reports during the FetchEvent
// intercepting the navigation itself.
nsCOMPtr<nsIDocumentLoader> loader(do_QueryInterface(GetDocShell()));
if (loader) {
nsCOMPtr<nsILoadGroup> loadgroup;
Unused << loader->GetLoadGroup(getter_AddRefs(loadgroup));
if (loadgroup) {
nsCOMPtr<nsISimpleEnumerator> iter;
Unused << loadgroup->GetRequests(getter_AddRefs(iter));
if (iter) {
nsCOMPtr<nsISupports> tmp;
bool hasMore = true;
// Check each network request in the load group.
while (NS_SUCCEEDED(iter->HasMoreElements(&hasMore)) && hasMore) {
iter->GetNext(getter_AddRefs(tmp));
nsCOMPtr<nsIChannel> loadingChannel(do_QueryInterface(tmp));
// Ignore subresource requests. Logging for a subresource
// FetchEvent should be handled above since the client is
// already controlled.
if (!loadingChannel ||
!nsContentUtils::IsNonSubresourceRequest(loadingChannel)) {
continue;
}
nsCOMPtr<nsIURI> loadingURL;
Unused << loadingChannel->GetURI(getter_AddRefs(loadingURL));
if (!loadingURL) {
continue;
}
nsAutoCString loadingSpec;
Unused << loadingURL->GetSpec(loadingSpec);
// Perform a simple substring comparison to match the scope
// against the channel URL.
if (StringBeginsWith(loadingSpec, aScope)) {
*aResultOut = true;
return CallState::Stop;
}
}
}
}
}
// The current window doesn't care about this service worker, but maybe
// one of our child frames does.
return CallOnInProcessChildren(
&nsGlobalWindowInner::ShouldReportForServiceWorkerScopeInternal, aScope,
aResultOut);
}
void nsGlobalWindowInner::NoteCalledRegisterForServiceWorkerScope(
const nsACString& aScope) {
if (!mClientSource) {
return;
}
mClientSource->NoteCalledRegisterForServiceWorkerScope(aScope);
}
void nsGlobalWindowInner::NoteDOMContentLoaded() {
if (!mClientSource) {
return;
}
mClientSource->NoteDOMContentLoaded();
}
void nsGlobalWindowInner::UpdateTopInnerWindow() {
if (IsTopInnerWindow() || !mTopInnerWindow) {
return;
}
mTopInnerWindow->UpdateWebSocketCount(-(int32_t)mNumOfOpenWebSockets);
}
bool nsGlobalWindowInner::IsInSyncOperation() {
return GetExtantDoc() && GetExtantDoc()->IsInSyncOperation();
}
bool nsGlobalWindowInner::IsSharedMemoryAllowedInternal(
nsIPrincipal* aPrincipal) const {
MOZ_ASSERT(NS_IsMainThread());
if (StaticPrefs::
dom_postMessage_sharedArrayBuffer_bypassCOOP_COEP_insecure_enabled()) {
return true;
}
if (ExtensionPolicyService::GetSingleton().IsExtensionProcess()) {
if (auto* basePrincipal = BasePrincipal::Cast(aPrincipal)) {
if (auto* policy = basePrincipal->AddonPolicy()) {
return policy->IsPrivileged();
}
}
}
return CrossOriginIsolated();
}
bool nsGlobalWindowInner::CrossOriginIsolated() const {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<BrowsingContext> bc = GetBrowsingContext();
MOZ_DIAGNOSTIC_ASSERT(bc);
return bc->CrossOriginIsolated();
}
void nsPIDOMWindowInner::AddPeerConnection() {
MOZ_ASSERT(NS_IsMainThread());
mTopInnerWindow ? mTopInnerWindow->mTotalActivePeerConnections++
: mTotalActivePeerConnections++;
++mActivePeerConnections;
if (mActivePeerConnections == 1 && mWindowGlobalChild) {
mWindowGlobalChild->BlockBFCacheFor(BFCacheStatus::ACTIVE_PEER_CONNECTION);
}
}
void nsPIDOMWindowInner::RemovePeerConnection() {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mTopInnerWindow ? mTopInnerWindow->mTotalActivePeerConnections
: mTotalActivePeerConnections);
MOZ_ASSERT(mActivePeerConnections > 0);
mTopInnerWindow ? mTopInnerWindow->mTotalActivePeerConnections--
: mTotalActivePeerConnections--;
--mActivePeerConnections;
if (mActivePeerConnections == 0 && mWindowGlobalChild) {
mWindowGlobalChild->UnblockBFCacheFor(
BFCacheStatus::ACTIVE_PEER_CONNECTION);
}
}
bool nsPIDOMWindowInner::HasActivePeerConnections() {
MOZ_ASSERT(NS_IsMainThread());
return mTopInnerWindow ? mTopInnerWindow->mTotalActivePeerConnections
: mTotalActivePeerConnections;
}
void nsPIDOMWindowInner::AddMediaKeysInstance(MediaKeys* aMediaKeys) {
MOZ_ASSERT(NS_IsMainThread());
mMediaKeysInstances.AppendElement(aMediaKeys);
if (mWindowGlobalChild && mMediaKeysInstances.Length() == 1) {
mWindowGlobalChild->BlockBFCacheFor(BFCacheStatus::CONTAINS_EME_CONTENT);
}
}
void nsPIDOMWindowInner::RemoveMediaKeysInstance(MediaKeys* aMediaKeys) {
MOZ_ASSERT(NS_IsMainThread());
mMediaKeysInstances.RemoveElement(aMediaKeys);
if (mWindowGlobalChild && mMediaKeysInstances.IsEmpty()) {
mWindowGlobalChild->UnblockBFCacheFor(BFCacheStatus::CONTAINS_EME_CONTENT);
}
}
bool nsPIDOMWindowInner::HasActiveMediaKeysInstance() {
MOZ_ASSERT(NS_IsMainThread());
return !mMediaKeysInstances.IsEmpty();
}
bool nsPIDOMWindowInner::IsPlayingAudio() {
for (uint32_t i = 0; i < mAudioContexts.Length(); i++) {
if (mAudioContexts[i]->IsRunning()) {
return true;
}
}
RefPtr<AudioChannelService> acs = AudioChannelService::Get();
if (!acs) {
return false;
}
auto outer = GetOuterWindow();
if (!outer) {
// We've been unlinked and are about to die. Not a good time to pretend to
// be playing audio.
return false;
}
return acs->IsWindowActive(outer);
}
bool nsPIDOMWindowInner::IsDocumentLoaded() const { return mIsDocumentLoaded; }
mozilla::dom::TimeoutManager& nsPIDOMWindowInner::TimeoutManager() {
return *mTimeoutManager;
}
bool nsPIDOMWindowInner::IsRunningTimeout() {
return TimeoutManager().IsRunningTimeout();
}
void nsPIDOMWindowInner::TryToCacheTopInnerWindow() {
if (mHasTriedToCacheTopInnerWindow) {
return;
}
nsGlobalWindowInner* window = nsGlobalWindowInner::Cast(this);
MOZ_ASSERT(!window->IsDying());
mHasTriedToCacheTopInnerWindow = true;
MOZ_ASSERT(window);
if (nsCOMPtr<nsPIDOMWindowOuter> topOutter =
window->GetInProcessScriptableTop()) {
mTopInnerWindow = topOutter->GetCurrentInnerWindow();
}
}
void nsPIDOMWindowInner::UpdateActiveIndexedDBDatabaseCount(int32_t aDelta) {
MOZ_ASSERT(NS_IsMainThread());
if (aDelta == 0) {
return;
}
// We count databases but not transactions because only active databases
// could block throttling.
uint32_t& counter = mTopInnerWindow
? mTopInnerWindow->mNumOfIndexedDBDatabases
: mNumOfIndexedDBDatabases;
counter += aDelta;
}
bool nsPIDOMWindowInner::HasActiveIndexedDBDatabases() {
MOZ_ASSERT(NS_IsMainThread());
return mTopInnerWindow ? mTopInnerWindow->mNumOfIndexedDBDatabases > 0
: mNumOfIndexedDBDatabases > 0;
}
void nsPIDOMWindowInner::UpdateWebSocketCount(int32_t aDelta) {
MOZ_ASSERT(NS_IsMainThread());
if (aDelta == 0) {
return;
}
if (mTopInnerWindow && !IsTopInnerWindow()) {
mTopInnerWindow->UpdateWebSocketCount(aDelta);
}
MOZ_DIAGNOSTIC_ASSERT(
aDelta > 0 || ((aDelta + mNumOfOpenWebSockets) < mNumOfOpenWebSockets));
mNumOfOpenWebSockets += aDelta;
}
bool nsPIDOMWindowInner::HasOpenWebSockets() const {
MOZ_ASSERT(NS_IsMainThread());
return mNumOfOpenWebSockets ||
(mTopInnerWindow && mTopInnerWindow->mNumOfOpenWebSockets);
}
bool nsPIDOMWindowInner::IsCurrentInnerWindow() const {
if (mBrowsingContext && mBrowsingContext->IsInBFCache()) {
return false;
}
if (!mBrowsingContext || mBrowsingContext->IsDiscarded()) {
// If our BrowsingContext has been discarded, we consider ourselves
// still-current if we were current at the time it was discarded.
return mOuterWindow && WasCurrentInnerWindow();
}
nsPIDOMWindowOuter* outer = mBrowsingContext->GetDOMWindow();
return outer && outer->GetCurrentInnerWindow() == this;
}
bool nsPIDOMWindowInner::IsFullyActive() const {
WindowContext* wc = GetWindowContext();
if (!wc || wc->IsDiscarded() || wc->IsCached()) {
return false;
}
return GetBrowsingContext()->AncestorsAreCurrent();
}
void nsPIDOMWindowInner::SetAudioCapture(bool aCapture) {
RefPtr<AudioChannelService> service = AudioChannelService::GetOrCreate();
if (service) {
service->SetWindowAudioCaptured(GetOuterWindow(), mWindowID, aCapture);
}
}
void nsGlobalWindowInner::SetActiveLoadingState(bool aIsLoading) {
MOZ_LOG(
gTimeoutLog, mozilla::LogLevel::Debug,
("SetActiveLoadingState innerwindow %p: %d", (void*)this, aIsLoading));
if (GetBrowsingContext()) {
// Setting loading on a discarded context has no effect.
Unused << GetBrowsingContext()->SetLoading(aIsLoading);
}
if (!nsGlobalWindowInner::Cast(this)->IsChromeWindow()) {
mTimeoutManager->SetLoading(aIsLoading);
}
HintIsLoading(aIsLoading);
}
void nsGlobalWindowInner::HintIsLoading(bool aIsLoading) {
// Hint to tell the JS GC to use modified triggers during pageload.
if (mHintedWasLoading != aIsLoading) {
using namespace js::gc;
SetPerformanceHint(danger::GetJSContext(), aIsLoading
? PerformanceHint::InPageLoad
: PerformanceHint::Normal);
mHintedWasLoading = aIsLoading;
}
}
// nsISpeechSynthesisGetter
#ifdef MOZ_WEBSPEECH
SpeechSynthesis* nsGlobalWindowInner::GetSpeechSynthesis(ErrorResult& aError) {
if (!mSpeechSynthesis) {
mSpeechSynthesis = new SpeechSynthesis(this);
}
return mSpeechSynthesis;
}
bool nsGlobalWindowInner::HasActiveSpeechSynthesis() {
if (mSpeechSynthesis) {
return !mSpeechSynthesis->HasEmptyQueue();
}
return false;
}
#endif
mozilla::glean::Glean* nsGlobalWindowInner::Glean() {
if (!mGlean) {
mGlean = new mozilla::glean::Glean();
}
return mGlean;
}
mozilla::glean::GleanPings* nsGlobalWindowInner::GleanPings() {
if (!mGleanPings) {
mGleanPings = new mozilla::glean::GleanPings();
}
return mGleanPings;
}
Nullable<WindowProxyHolder> nsGlobalWindowInner::GetParent(
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetParentOuter, (), aError, nullptr);
}
/**
* GetInProcessScriptableParent used to be called when a script read
* window.parent. Under Fission, that is now handled by
* BrowsingContext::GetParent, and the result is a WindowProxyHolder rather than
* an actual global window. This method still exists for legacy callers which
* relied on the old logic, and require in-process windows. However, it only
* works correctly when no out-of-process frames exist between this window and
* the top-level window, so it should not be used in new code.
*
* In contrast to GetRealParent, GetInProcessScriptableParent respects <iframe
* mozbrowser> boundaries, so if |this| is contained by an <iframe
* mozbrowser>, we will return |this| as its own parent.
*/
nsPIDOMWindowOuter* nsGlobalWindowInner::GetInProcessScriptableParent() {
FORWARD_TO_OUTER(GetInProcessScriptableParent, (), nullptr);
}
/**
* GetInProcessScriptableTop used to be called when a script read window.top.
* Under Fission, that is now handled by BrowsingContext::Top, and the result is
* a WindowProxyHolder rather than an actual global window. This method still
* exists for legacy callers which relied on the old logic, and require
* in-process windows. However, it only works correctly when no out-of-process
* frames exist between this window and the top-level window, so it should not
* be used in new code.
*
* In contrast to GetRealTop, GetInProcessScriptableTop respects <iframe
* mozbrowser> boundaries. If we encounter a window owned by an <iframe
* mozbrowser> while walking up the window hierarchy, we'll stop and return that
* window.
*/
nsPIDOMWindowOuter* nsGlobalWindowInner::GetInProcessScriptableTop() {
FORWARD_TO_OUTER(GetInProcessScriptableTop, (), nullptr);
}
void nsGlobalWindowInner::GetContent(JSContext* aCx,
JS::MutableHandle<JSObject*> aRetval,
CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetContentOuter,
(aCx, aRetval, aCallerType, aError), aError, );
}
BarProp* nsGlobalWindowInner::GetMenubar(ErrorResult& aError) {
if (!mMenubar) {
mMenubar = new MenubarProp(this);
}
return mMenubar;
}
BarProp* nsGlobalWindowInner::GetToolbar(ErrorResult& aError) {
if (!mToolbar) {
mToolbar = new ToolbarProp(this);
}
return mToolbar;
}
BarProp* nsGlobalWindowInner::GetLocationbar(ErrorResult& aError) {
if (!mLocationbar) {
mLocationbar = new LocationbarProp(this);
}
return mLocationbar;
}
BarProp* nsGlobalWindowInner::GetPersonalbar(ErrorResult& aError) {
if (!mPersonalbar) {
mPersonalbar = new PersonalbarProp(this);
}
return mPersonalbar;
}
BarProp* nsGlobalWindowInner::GetStatusbar(ErrorResult& aError) {
if (!mStatusbar) {
mStatusbar = new StatusbarProp(this);
}
return mStatusbar;
}
BarProp* nsGlobalWindowInner::GetScrollbars(ErrorResult& aError) {
if (!mScrollbars) {
mScrollbars = new ScrollbarsProp(this);
}
return mScrollbars;
}
bool nsGlobalWindowInner::GetClosed(ErrorResult& aError) {
// If we're called from JS (which is the only way we should be getting called
// here) and we reach this point, that means our JS global is the current
// target of the WindowProxy, which means that we are the "current inner"
// of our outer. So if FORWARD_TO_OUTER fails to forward, that means the
// outer is already torn down, which corresponds to the closed state.
FORWARD_TO_OUTER(GetClosedOuter, (), true);
}
Nullable<WindowProxyHolder> nsGlobalWindowInner::IndexedGetter(
uint32_t aIndex) {
FORWARD_TO_OUTER(IndexedGetterOuter, (aIndex), nullptr);
}
namespace {
struct InterfaceShimEntry {
const char* geckoName;
const char* domName;
};
} // anonymous namespace
// We add shims from Components.interfaces.nsIDOMFoo to window.Foo for each
// interface that has interface constants that sites might be getting off
// of Ci.
const InterfaceShimEntry kInterfaceShimMap[] = {
{"nsIXMLHttpRequest", "XMLHttpRequest"},
{"nsIDOMDOMException", "DOMException"},
{"nsIDOMNode", "Node"},
{"nsIDOMCSSRule", "CSSRule"},
{"nsIDOMEvent", "Event"},
{"nsIDOMNSEvent", "Event"},
{"nsIDOMKeyEvent", "KeyEvent"},
{"nsIDOMMouseEvent", "MouseEvent"},
{"nsIDOMMouseScrollEvent", "MouseScrollEvent"},
{"nsIDOMMutationEvent", "MutationEvent"},
{"nsIDOMUIEvent", "UIEvent"},
{"nsIDOMHTMLMediaElement", "HTMLMediaElement"},
{"nsIDOMRange", "Range"},
// Think about whether Ci.nsINodeFilter can just go away for websites!
{"nsIDOMNodeFilter", "NodeFilter"},
{"nsIDOMXPathResult", "XPathResult"}};
bool nsGlobalWindowInner::ResolveComponentsShim(
JSContext* aCx, JS::Handle<JSObject*> aGlobal,
JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc) {
// Keep track of how often this happens.
Telemetry::Accumulate(Telemetry::COMPONENTS_SHIM_ACCESSED_BY_CONTENT, true);
// Warn once.
nsCOMPtr<Document> doc = GetExtantDoc();
if (doc) {
doc->WarnOnceAbout(DeprecatedOperations::eComponents, /* asError = */ true);
}
// Create a fake Components object.
AssertSameCompartment(aCx, aGlobal);
JS::Rooted<JSObject*> components(aCx, JS_NewPlainObject(aCx));
if (NS_WARN_IF(!components)) {
return false;
}
// Create a fake interfaces object.
JS::Rooted<JSObject*> interfaces(aCx, JS_NewPlainObject(aCx));
if (NS_WARN_IF(!interfaces)) {
return false;
}
bool ok =
JS_DefineProperty(aCx, components, "interfaces", interfaces,
JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
if (NS_WARN_IF(!ok)) {
return false;
}
// Define a bunch of shims from the Ci.nsIDOMFoo to window.Foo for DOM
// interfaces with constants.
for (uint32_t i = 0; i < ArrayLength(kInterfaceShimMap); ++i) {
// Grab the names from the table.
const char* geckoName = kInterfaceShimMap[i].geckoName;
const char* domName = kInterfaceShimMap[i].domName;
// Look up the appopriate interface object on the global.
JS::Rooted<JS::Value> v(aCx, JS::UndefinedValue());
ok = JS_GetProperty(aCx, aGlobal, domName, &v);
if (NS_WARN_IF(!ok)) {
return false;
}
if (!v.isObject()) {
NS_WARNING("Unable to find interface object on global");
continue;
}
// Define the shim on the interfaces object.
ok = JS_DefineProperty(
aCx, interfaces, geckoName, v,
JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY);
if (NS_WARN_IF(!ok)) {
return false;
}
}
aDesc.set(mozilla::Some(JS::PropertyDescriptor::Data(
JS::ObjectValue(*components),
{JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Enumerable,
JS::PropertyAttribute::Writable})));
return true;
}
#ifdef RELEASE_OR_BETA
# define USE_CONTROLLERS_SHIM
#endif
#ifdef USE_CONTROLLERS_SHIM
static const JSClass ControllersShimClass = {"Controllers", 0};
static const JSClass XULControllersShimClass = {"XULControllers", 0};
#endif
bool nsGlobalWindowInner::DoResolve(
JSContext* aCx, JS::Handle<JSObject*> aObj, JS::Handle<jsid> aId,
JS::MutableHandle<mozilla::Maybe<JS::PropertyDescriptor>> aDesc) {
// 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;
}
// We support a cut-down Components.interfaces in case websites are
// using Components.interfaces.nsIFoo.CONSTANT_NAME for the ones
// that have constants.
if (StaticPrefs::dom_use_components_shim() &&
aId == XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)) {
return ResolveComponentsShim(aCx, aObj, aDesc);
}
// We also support a "window.controllers" thing; apparently some
// sites use it for browser-sniffing. See bug 1010577.
#ifdef USE_CONTROLLERS_SHIM
// Note: We use |aObj| rather than |this| to get the principal here, because
// this is called during Window setup when the Document isn't necessarily
// hooked up yet.
if ((aId == XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_CONTROLLERS) ||
aId == XPCJSRuntime::Get()->GetStringID(
XPCJSContext::IDX_CONTROLLERS_CLASS)) &&
!xpc::IsXrayWrapper(aObj) &&
!nsContentUtils::ObjectPrincipal(aObj)->IsSystemPrincipal()) {
if (GetExtantDoc()) {
GetExtantDoc()->WarnOnceAbout(
DeprecatedOperations::eWindow_Cc_ontrollers);
}
const JSClass* clazz;
if (aId ==
XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_CONTROLLERS)) {
clazz = &XULControllersShimClass;
} else {
clazz = &ControllersShimClass;
}
MOZ_ASSERT(JS_IsGlobalObject(aObj));
JS::Rooted<JSObject*> shim(aCx, JS_NewObject(aCx, clazz));
if (NS_WARN_IF(!shim)) {
return false;
}
aDesc.set(mozilla::Some(JS::PropertyDescriptor::Data(
JS::ObjectValue(*shim),
{JS::PropertyAttribute::Configurable, JS::PropertyAttribute::Enumerable,
JS::PropertyAttribute::Writable})));
return true;
}
#endif
return true;
}
/* static */
bool nsGlobalWindowInner::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 == XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_COMPONENTS)) {
return true;
}
if (aId == XPCJSRuntime::Get()->GetStringID(XPCJSContext::IDX_CONTROLLERS) ||
aId == XPCJSRuntime::Get()->GetStringID(
XPCJSContext::IDX_CONTROLLERS_CLASS)) {
// We only resolve .controllers/.Controllers in release builds and on
// non-chrome windows, but let's not worry about any of that stuff.
return true;
}
return WebIDLGlobalNameHash::MayResolve(aId);
}
void nsGlobalWindowInner::GetOwnPropertyNames(
JSContext* aCx, JS::MutableHandleVector<jsid> aNames, bool aEnumerableOnly,
ErrorResult& aRv) {
if (aEnumerableOnly) {
// The names we would return from here get defined on the window via one of
// two codepaths. The ones coming from the WebIDLGlobalNameHash will end up
// in the DefineConstructor function in BindingUtils, which always defines
// things as non-enumerable. The ones coming from the script namespace
// manager get defined by our resolve hook using FillPropertyDescriptor with
// 0 for the property attributes, so non-enumerable as well.
//
// So in the aEnumerableOnly case we have nothing to do.
return;
}
// "Components" is marked as enumerable but only resolved on demand :-/.
// aNames.AppendElement(u"Components"_ns);
JS::Rooted<JSObject*> wrapper(aCx, GetWrapper());
// There are actually two ways we can get called here: For normal
// enumeration or for Xray enumeration. In the latter case, we want to
// return all possible WebIDL names, because we don't really support
// deleting these names off our Xray; trying to resolve them will just make
// them come back. In the former case, we want to avoid returning deleted
// names. But the JS engine already knows about the non-deleted
// already-resolved names, so we can just return the so-far-unresolved ones.
//
// We can tell which case we're in by whether aCx is in our wrapper's
// compartment. If not, we're in the Xray case.
WebIDLGlobalNameHash::NameType nameType =
js::IsObjectInContextCompartment(wrapper, aCx)
? WebIDLGlobalNameHash::UnresolvedNamesOnly
: WebIDLGlobalNameHash::AllNames;
if (!WebIDLGlobalNameHash::GetNames(aCx, wrapper, nameType, aNames)) {
aRv.NoteJSContextException(aCx);
}
}
/* static */
bool nsGlobalWindowInner::IsPrivilegedChromeWindow(JSContext*, JSObject* aObj) {
// For now, have to deal with XPConnect objects here.
nsGlobalWindowInner* win = xpc::WindowOrNull(aObj);
return win && win->IsChromeWindow() &&
nsContentUtils::ObjectPrincipal(aObj) ==
nsContentUtils::GetSystemPrincipal();
}
/* static */
bool nsGlobalWindowInner::IsRequestIdleCallbackEnabled(JSContext* aCx,
JSObject*) {
// The requestIdleCallback should always be enabled for system code.
return StaticPrefs::dom_requestIdleCallback_enabled() ||
nsContentUtils::IsSystemCaller(aCx);
}
/* static */
bool nsGlobalWindowInner::DeviceSensorsEnabled(JSContext*, JSObject*) {
return Preferences::GetBool("device.sensors.enabled");
}
/* static */
bool nsGlobalWindowInner::ContentPropertyEnabled(JSContext* aCx, JSObject*) {
return StaticPrefs::dom_window_content_untrusted_enabled() ||
nsContentUtils::IsSystemCaller(aCx);
}
nsDOMOfflineResourceList* nsGlobalWindowInner::GetApplicationCache(
ErrorResult& aError) {
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(),
this);
mApplicationCache = applicationCache;
}
return mApplicationCache;
}
nsDOMOfflineResourceList* nsGlobalWindowInner::GetApplicationCache() {
return GetApplicationCache(IgnoreErrors());
}
Crypto* nsGlobalWindowInner::GetCrypto(ErrorResult& aError) {
if (!mCrypto) {
mCrypto = new Crypto(this);
}
return mCrypto;
}
mozilla::dom::U2F* nsGlobalWindowInner::GetU2f(ErrorResult& aError) {
if (!mU2F) {
RefPtr<U2F> u2f = new U2F(this);
u2f->Init(aError);
if (NS_WARN_IF(aError.Failed())) {
return nullptr;
}
mU2F = u2f;
}
return mU2F;
}
nsIControllers* nsGlobalWindowInner::GetControllers(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetControllersOuter, (aError), aError, nullptr);
}
nsresult nsGlobalWindowInner::GetControllers(nsIControllers** aResult) {
ErrorResult rv;
nsCOMPtr<nsIControllers> controllers = GetControllers(rv);
controllers.forget(aResult);
return rv.StealNSResult();
}
Nullable<WindowProxyHolder> nsGlobalWindowInner::GetOpenerWindow(
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetOpenerWindowOuter, (), aError, nullptr);
}
void nsGlobalWindowInner::GetOpener(JSContext* aCx,
JS::MutableHandle<JS::Value> aRetval,
ErrorResult& aError) {
Nullable<WindowProxyHolder> opener = GetOpenerWindow(aError);
if (aError.Failed() || opener.IsNull()) {
aRetval.setNull();
return;
}
if (!ToJSValue(aCx, opener.Value(), aRetval)) {
aError.NoteJSContextException(aCx);
}
}
void nsGlobalWindowInner::SetOpener(JSContext* aCx,
JS::Handle<JS::Value> aOpener,
ErrorResult& aError) {
if (aOpener.isNull()) {
RefPtr<BrowsingContext> bc(GetBrowsingContext());
if (!bc->IsDiscarded()) {
bc->SetOpener(nullptr);
}
return;
}
// If something other than null is passed, 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.
RedefineProperty(aCx, "opener", aOpener, aError);
}
void nsGlobalWindowInner::GetEvent(JSContext* aCx,
JS::MutableHandle<JS::Value> aRetval) {
if (mEvent) {
Unused << nsContentUtils::WrapNative(aCx, mEvent, aRetval);
} else {
aRetval.setUndefined();
}
}
void nsGlobalWindowInner::GetStatus(nsAString& aStatus, ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetStatusOuter, (aStatus), aError, );
}
void nsGlobalWindowInner::SetStatus(const nsAString& aStatus,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SetStatusOuter, (aStatus), aError, );
}
void nsGlobalWindowInner::GetName(nsAString& aName, ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetNameOuter, (aName), aError, );
}
void nsGlobalWindowInner::SetName(const nsAString& aName,
mozilla::ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SetNameOuter, (aName, aError), aError, );
}
double nsGlobalWindowInner::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 nsGlobalWindowInner::GetInnerWidth(double* aInnerWidth)
// to actually take a useful CallerType and pass it in here.
FORWARD_TO_OUTER_OR_THROW(GetInnerWidthOuter, (aError), aError, 0);
}
void nsGlobalWindowInner::GetInnerWidth(JSContext* aCx,
JS::MutableHandle<JS::Value> aValue,
CallerType aCallerType,
ErrorResult& aError) {
GetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::GetInnerWidth, aValue,
aCallerType, aError);
}
nsresult nsGlobalWindowInner::GetInnerWidth(double* aInnerWidth) {
ErrorResult rv;
// Callee doesn't care about the caller type, but play it safe.
*aInnerWidth = GetInnerWidth(CallerType::NonSystem, rv);
return rv.StealNSResult();
}
void nsGlobalWindowInner::SetInnerWidth(double aInnerWidth,
CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SetInnerWidthOuter,
(aInnerWidth, aCallerType, aError), aError, );
}
void nsGlobalWindowInner::SetInnerWidth(JSContext* aCx,
JS::Handle<JS::Value> aValue,
CallerType aCallerType,
ErrorResult& aError) {
SetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::SetInnerWidth, aValue,
"innerWidth", aCallerType, aError);
}
double nsGlobalWindowInner::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 nsGlobalWindowInner::GetInnerHeight(double* aInnerWidth)
// to actually take a useful CallerType and pass it in here.
FORWARD_TO_OUTER_OR_THROW(GetInnerHeightOuter, (aError), aError, 0);
}
void nsGlobalWindowInner::GetInnerHeight(JSContext* aCx,
JS::MutableHandle<JS::Value> aValue,
CallerType aCallerType,
ErrorResult& aError) {
GetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::GetInnerHeight, aValue,
aCallerType, aError);
}
nsresult nsGlobalWindowInner::GetInnerHeight(double* aInnerHeight) {
ErrorResult rv;
// Callee doesn't care about the caller type, but play it safe.
*aInnerHeight = GetInnerHeight(CallerType::NonSystem, rv);
return rv.StealNSResult();
}
void nsGlobalWindowInner::SetInnerHeight(double aInnerHeight,
CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SetInnerHeightOuter,
(aInnerHeight, aCallerType, aError), aError, );
}
void nsGlobalWindowInner::SetInnerHeight(JSContext* aCx,
JS::Handle<JS::Value> aValue,
CallerType aCallerType,
ErrorResult& aError) {
SetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::SetInnerHeight, aValue,
"innerHeight", aCallerType, aError);
}
int32_t nsGlobalWindowInner::GetOuterWidth(CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetOuterWidthOuter, (aCallerType, aError), aError,
0);
}
void nsGlobalWindowInner::GetOuterWidth(JSContext* aCx,
JS::MutableHandle<JS::Value> aValue,
CallerType aCallerType,
ErrorResult& aError) {
GetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::GetOuterWidth, aValue,
aCallerType, aError);
}
int32_t nsGlobalWindowInner::GetOuterHeight(CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetOuterHeightOuter, (aCallerType, aError), aError,
0);
}
void nsGlobalWindowInner::GetOuterHeight(JSContext* aCx,
JS::MutableHandle<JS::Value> aValue,
CallerType aCallerType,
ErrorResult& aError) {
GetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::GetOuterHeight, aValue,
aCallerType, aError);
}
void nsGlobalWindowInner::SetOuterWidth(int32_t aOuterWidth,
CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SetOuterWidthOuter,
(aOuterWidth, aCallerType, aError), aError, );
}
void nsGlobalWindowInner::SetOuterWidth(JSContext* aCx,
JS::Handle<JS::Value> aValue,
CallerType aCallerType,
ErrorResult& aError) {
SetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::SetOuterWidth, aValue,
"outerWidth", aCallerType, aError);
}
void nsGlobalWindowInner::SetOuterHeight(int32_t aOuterHeight,
CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SetOuterHeightOuter,
(aOuterHeight, aCallerType, aError), aError, );
}
void nsGlobalWindowInner::SetOuterHeight(JSContext* aCx,
JS::Handle<JS::Value> aValue,
CallerType aCallerType,
ErrorResult& aError) {
SetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::SetOuterHeight, aValue,
"outerHeight", aCallerType, aError);
}
int32_t nsGlobalWindowInner::GetScreenX(CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetScreenXOuter, (aCallerType, aError), aError, 0);
}
void nsGlobalWindowInner::GetScreenX(JSContext* aCx,
JS::MutableHandle<JS::Value> aValue,
CallerType aCallerType,
ErrorResult& aError) {
GetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::GetScreenX, aValue,
aCallerType, aError);
}
float nsGlobalWindowInner::GetMozInnerScreenX(CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenXOuter, (aCallerType), aError, 0);
}
float nsGlobalWindowInner::GetMozInnerScreenY(CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetMozInnerScreenYOuter, (aCallerType), aError, 0);
}
double nsGlobalWindowInner::GetDevicePixelRatio(CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetDevicePixelRatioOuter, (aCallerType), aError,
0.0);
}
uint64_t nsGlobalWindowInner::GetMozPaintCount(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetMozPaintCountOuter, (), aError, 0);
}
int32_t nsGlobalWindowInner::RequestAnimationFrame(
FrameRequestCallback& aCallback, ErrorResult& aError) {
if (!mDoc) {
return 0;
}
if (GetWrapperPreserveColor()) {
js::NotifyAnimationActivity(GetWrapperPreserveColor());
}
DebuggerNotificationDispatch(this,
DebuggerNotificationType::RequestAnimationFrame);
int32_t handle;
aError = mDoc->ScheduleFrameRequestCallback(aCallback, &handle);
return handle;
}
void nsGlobalWindowInner::CancelAnimationFrame(int32_t aHandle,
ErrorResult& aError) {
if (!mDoc) {
return;
}
DebuggerNotificationDispatch(this,
DebuggerNotificationType::CancelAnimationFrame);
mDoc->CancelFrameRequestCallback(aHandle);
}
already_AddRefed<MediaQueryList> nsGlobalWindowInner::MatchMedia(
const nsACString& aMediaQueryList, CallerType aCallerType,
ErrorResult& aError) {
ENSURE_ACTIVE_DOCUMENT(aError, nullptr);
return mDoc->MatchMedia(aMediaQueryList, aCallerType);
}
void nsGlobalWindowInner::SetScreenX(int32_t aScreenX, CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SetScreenXOuter, (aScreenX, aCallerType, aError),
aError, );
}
void nsGlobalWindowInner::SetScreenX(JSContext* aCx,
JS::Handle<JS::Value> aValue,
CallerType aCallerType,
ErrorResult& aError) {
SetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::SetScreenX, aValue,
"screenX", aCallerType, aError);
}
int32_t nsGlobalWindowInner::GetScreenY(CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetScreenYOuter, (aCallerType, aError), aError, 0);
}
void nsGlobalWindowInner::GetScreenY(JSContext* aCx,
JS::MutableHandle<JS::Value> aValue,
CallerType aCallerType,
ErrorResult& aError) {
GetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::GetScreenY, aValue,
aCallerType, aError);
}
void nsGlobalWindowInner::SetScreenY(int32_t aScreenY, CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SetScreenYOuter, (aScreenY, aCallerType, aError),
aError, );
}
void nsGlobalWindowInner::SetScreenY(JSContext* aCx,
JS::Handle<JS::Value> aValue,
CallerType aCallerType,
ErrorResult& aError) {
SetReplaceableWindowCoord(aCx, &nsGlobalWindowInner::SetScreenY, aValue,
"screenY", aCallerType, aError);
}
int32_t nsGlobalWindowInner::GetScrollMinX(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideLeft), aError, 0);
}
int32_t nsGlobalWindowInner::GetScrollMinY(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideTop), aError, 0);
}
int32_t nsGlobalWindowInner::GetScrollMaxX(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideRight), aError, 0);
}
int32_t nsGlobalWindowInner::GetScrollMaxY(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetScrollBoundaryOuter, (eSideBottom), aError, 0);
}
double nsGlobalWindowInner::GetScrollX(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetScrollXOuter, (), aError, 0);
}
double nsGlobalWindowInner::GetScrollY(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetScrollYOuter, (), aError, 0);
}
uint32_t nsGlobalWindowInner::Length() { FORWARD_TO_OUTER(Length, (), 0); }
Nullable<WindowProxyHolder> nsGlobalWindowInner::GetTop(
mozilla::ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetTopOuter, (), aError, nullptr);
}
already_AddRefed<BrowsingContext> nsGlobalWindowInner::GetChildWindow(
const nsAString& aName) {
if (GetOuterWindowInternal()) {
return GetOuterWindowInternal()->GetChildWindow(aName);
}
return nullptr;
}
void nsGlobalWindowInner::RefreshRealmPrincipal() {
JS::SetRealmPrincipals(js::GetNonCCWObjectRealm(GetWrapperPreserveColor()),
nsJSPrincipals::get(mDoc->NodePrincipal()));
}
already_AddRefed<nsIWidget> nsGlobalWindowInner::GetMainWidget() {
FORWARD_TO_OUTER(GetMainWidget, (), nullptr);
}
nsIWidget* nsGlobalWindowInner::GetNearestWidget() const {
if (GetOuterWindowInternal()) {
return GetOuterWindowInternal()->GetNearestWidget();
}
return nullptr;
}
void nsGlobalWindowInner::SetFullScreen(bool aFullscreen,
mozilla::ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SetFullscreenOuter, (aFullscreen, aError), aError,
/* void */);
}
bool nsGlobalWindowInner::GetFullScreen(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetFullscreenOuter, (), aError, false);
}
bool nsGlobalWindowInner::GetFullScreen() {
ErrorResult dummy;
bool fullscreen = GetFullScreen(dummy);
dummy.SuppressException();
return fullscreen;
}
void nsGlobalWindowInner::Dump(const nsAString& aStr) {
if (!nsJSUtils::DumpEnabled()) {
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 nsGlobalWindowInner::Alert(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError) {
Alert(u""_ns, aSubjectPrincipal, aError);
}
void nsGlobalWindowInner::Alert(const nsAString& aMessage,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(AlertOuter, (aMessage, aSubjectPrincipal, aError),
aError, );
}
bool nsGlobalWindowInner::Confirm(const nsAString& aMessage,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(ConfirmOuter, (aMessage, aSubjectPrincipal, aError),
aError, false);
}
already_AddRefed<Promise> nsGlobalWindowInner::Fetch(
const RequestOrUSVString& aInput, const RequestInit& aInit,
CallerType aCallerType, ErrorResult& aRv) {
return FetchRequest(this, aInput, aInit, aCallerType, aRv);
}
void nsGlobalWindowInner::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 nsGlobalWindowInner::Focus(CallerType aCallerType, ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(FocusOuter,
(aCallerType, /* aFromOtherProcess */ false,
nsFocusManager::GenerateFocusActionId()),
aError, );
}
nsresult nsGlobalWindowInner::Focus(CallerType aCallerType) {
ErrorResult rv;
Focus(aCallerType, rv);
return rv.StealNSResult();
}
void nsGlobalWindowInner::Blur(CallerType aCallerType, ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(BlurOuter, (aCallerType), aError, );
}
void nsGlobalWindowInner::Stop(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(StopOuter, (aError), aError, );
}
void nsGlobalWindowInner::Print(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(PrintOuter, (aError), aError, );
}
Nullable<WindowProxyHolder> nsGlobalWindowInner::PrintPreview(
nsIPrintSettings* aSettings, nsIWebProgressListener* aListener,
nsIDocShell* aDocShellToCloneInto, ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(Print,
(aSettings, aListener, aDocShellToCloneInto,
nsGlobalWindowOuter::IsPreview::Yes,
nsGlobalWindowOuter::IsForWindowDotPrint::No,
/* aPrintPreviewCallback = */ nullptr, aError),
aError, nullptr);
}
void nsGlobalWindowInner::MoveTo(int32_t aXPos, int32_t aYPos,
CallerType aCallerType, ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(MoveToOuter, (aXPos, aYPos, aCallerType, aError),
aError, );
}
void nsGlobalWindowInner::MoveBy(int32_t aXDif, int32_t aYDif,
CallerType aCallerType, ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(MoveByOuter, (aXDif, aYDif, aCallerType, aError),
aError, );
}
void nsGlobalWindowInner::ResizeTo(int32_t aWidth, int32_t aHeight,
CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(ResizeToOuter,
(aWidth, aHeight, aCallerType, aError), aError, );
}
void nsGlobalWindowInner::ResizeBy(int32_t aWidthDif, int32_t aHeightDif,
CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(
ResizeByOuter, (aWidthDif, aHeightDif, aCallerType, aError), aError, );
}
void nsGlobalWindowInner::SizeToContent(CallerType aCallerType,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SizeToContentOuter, (aCallerType, aError),
aError, );
}
already_AddRefed<nsPIWindowRoot> nsGlobalWindowInner::GetTopWindowRoot() {
nsGlobalWindowOuter* outer = GetOuterWindowInternal();
if (!outer) {
return nullptr;
}
return outer->GetTopWindowRoot();
}
void nsGlobalWindowInner::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 nsGlobalWindowInner::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 nsGlobalWindowInner::ScrollTo(const ScrollToOptions& aOptions) {
// When scrolling to a non-zero offset, we need to determine whether that
// position is within our scrollable range, so we need updated layout
// information which requires a layout flush, otherwise all we need is to
// flush frames to be able to access our scrollable frame here.
FlushType flushType =
((aOptions.mLeft.WasPassed() && aOptions.mLeft.Value() > 0) ||
(aOptions.mTop.WasPassed() && aOptions.mTop.Value() > 0))
? FlushType::Layout
: FlushType::Frames;
FlushPendingNotifications(flushType);
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 nsGlobalWindowInner::Scroll(const ScrollToOptions& aOptions) {
ScrollTo(aOptions);
}
void nsGlobalWindowInner::ScrollTo(const CSSIntPoint& aScroll,
const ScrollOptions& aOptions) {
// When scrolling to a non-zero offset, we need to determine whether that
// position is within our scrollable range, so we need updated layout
// information which requires a layout flush, otherwise all we need is to
// flush frames to be able to access our scrollable frame here.
FlushType flushType =
(aScroll.x || aScroll.y) ? FlushType::Layout : FlushType::Frames;
FlushPendingNotifications(flushType);
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;
}
ScrollMode scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
? ScrollMode::SmoothMsd
: ScrollMode::Instant;
sf->ScrollToCSSPixels(scroll, scrollMode);
}
}
void nsGlobalWindowInner::ScrollBy(double aXScrollDif, double aYScrollDif) {
FlushPendingNotifications(FlushType::Layout);
nsIScrollableFrame* sf = GetScrollFrame();
if (sf) {
// 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.
ScrollToOptions options;
options.mLeft.Construct(aXScrollDif);
options.mTop.Construct(aYScrollDif);
ScrollBy(options);
}
}
void nsGlobalWindowInner::ScrollBy(const ScrollToOptions& aOptions) {
FlushPendingNotifications(FlushType::Layout);
nsIScrollableFrame* sf = GetScrollFrame();
if (sf) {
CSSIntPoint scrollDelta;
if (aOptions.mLeft.WasPassed()) {
scrollDelta.x = mozilla::ToZeroIfNonfinite(aOptions.mLeft.Value());
}
if (aOptions.mTop.WasPassed()) {
scrollDelta.y = mozilla::ToZeroIfNonfinite(aOptions.mTop.Value());
}
ScrollMode scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
? ScrollMode::SmoothMsd
: ScrollMode::Instant;
sf->ScrollByCSSPixels(scrollDelta, scrollMode,
mozilla::ScrollOrigin::Relative);
}
}
void nsGlobalWindowInner::ScrollByLines(int32_t numLines,
const ScrollOptions& aOptions) {
FlushPendingNotifications(FlushType::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.
ScrollMode scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
? ScrollMode::SmoothMsd
: ScrollMode::Instant;
sf->ScrollBy(nsIntPoint(0, numLines), ScrollUnit::LINES, scrollMode);
}
}
void nsGlobalWindowInner::ScrollByPages(int32_t numPages,
const ScrollOptions& aOptions) {
FlushPendingNotifications(FlushType::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.
ScrollMode scrollMode = sf->IsSmoothScroll(aOptions.mBehavior)
? ScrollMode::SmoothMsd
: ScrollMode::Instant;
sf->ScrollBy(nsIntPoint(0, numPages), ScrollUnit::PAGES, scrollMode);
}
}
void nsGlobalWindowInner::MozScrollSnap() {
FlushPendingNotifications(FlushType::Layout);
nsIScrollableFrame* sf = GetScrollFrame();
if (sf) {
sf->ScrollSnap();
}
}
void nsGlobalWindowInner::ClearTimeout(int32_t aHandle) {
DebuggerNotificationDispatch(this, DebuggerNotificationType::ClearTimeout);
if (aHandle > 0) {
mTimeoutManager->ClearTimeout(aHandle, Timeout::Reason::eTimeoutOrInterval);
}
}
void nsGlobalWindowInner::ClearInterval(int32_t aHandle) {
DebuggerNotificationDispatch(this, DebuggerNotificationType::ClearInterval);
if (aHandle > 0) {
mTimeoutManager->ClearTimeout(aHandle, Timeout::Reason::eTimeoutOrInterval);
}
}
void nsGlobalWindowInner::SetResizable(bool aResizable) const {
// nop
}
void nsGlobalWindowInner::CaptureEvents() {
if (mDoc) {
mDoc->WarnOnceAbout(DeprecatedOperations::eUseOfCaptureEvents);
}
}
void nsGlobalWindowInner::ReleaseEvents() {
if (mDoc) {
mDoc->WarnOnceAbout(DeprecatedOperations::eUseOfReleaseEvents);
}
}
Nullable<WindowProxyHolder> nsGlobalWindowInner::Open(const nsAString& aUrl,
const nsAString& aName,
const nsAString& aOptions,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(OpenOuter, (aUrl, aName, aOptions, aError), aError,
nullptr);
}
Nullable<WindowProxyHolder> nsGlobalWindowInner::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);
}
WindowProxyHolder nsGlobalWindowInner::GetFrames(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetFramesOuter, (), aError, Window());
}
void nsGlobalWindowInner::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 nsGlobalWindowInner::PostMessageMoz(JSContext* aCx,
JS::Handle<JS::Value> aMessage,
const nsAString& aTargetOrigin,
const Sequence<JSObject*>& aTransfer,
nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv) {
JS::Rooted<JS::Value> transferArray(aCx, JS::UndefinedValue());
aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(aCx, aTransfer,
&transferArray);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
PostMessageMoz(aCx, aMessage, aTargetOrigin, transferArray, aSubjectPrincipal,
aRv);
}
void nsGlobalWindowInner::PostMessageMoz(
JSContext* aCx, JS::Handle<JS::Value> aMessage,
const WindowPostMessageOptions& aOptions, nsIPrincipal& aSubjectPrincipal,
ErrorResult& aRv) {
JS::Rooted<JS::Value> transferArray(aCx, JS::UndefinedValue());
aRv = nsContentUtils::CreateJSValueFromSequenceOfObject(
aCx, aOptions.mTransfer, &transferArray);
if (NS_WARN_IF(aRv.Failed())) {
return;
}
PostMessageMoz(aCx, aMessage, aOptions.mTargetOrigin, transferArray,
aSubjectPrincipal, aRv);
}
void nsGlobalWindowInner::Close(CallerType aCallerType, ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(CloseOuter, (aCallerType == CallerType::System),
aError, );
}
nsresult nsGlobalWindowInner::Close() {
FORWARD_TO_OUTER(Close, (), NS_ERROR_UNEXPECTED);
}
bool nsGlobalWindowInner::IsInModalState() {
FORWARD_TO_OUTER(IsInModalState, (), false);
}
// static
void nsGlobalWindowInner::NotifyDOMWindowDestroyed(
nsGlobalWindowInner* aWindow) {
nsCOMPtr<nsIObserverService> observerService = services::GetObserverService();
if (observerService) {
observerService->NotifyObservers(ToSupports(aWindow),
DOM_WINDOW_DESTROYED_TOPIC, nullptr);
}
}
void nsGlobalWindowInner::NotifyWindowIDDestroyed(const char* aTopic) {
nsCOMPtr<nsIRunnable> runnable =
new WindowDestroyedEvent(this, mWindowID, aTopic);
Dispatch(TaskCategory::Other, runnable.forget());
}
// static
void nsGlobalWindowInner::NotifyDOMWindowFrozen(nsGlobalWindowInner* aWindow) {
if (aWindow) {
nsCOMPtr<nsIObserverService> observerService =
services::GetObserverService();
if (observerService) {
observerService->NotifyObservers(ToSupports(aWindow),
DOM_WINDOW_FROZEN_TOPIC, nullptr);
}
}
}
// static
void nsGlobalWindowInner::NotifyDOMWindowThawed(nsGlobalWindowInner* aWindow) {
if (aWindow) {
nsCOMPtr<nsIObserverService> observerService =
services::GetObserverService();
if (observerService) {
observerService->NotifyObservers(ToSupports(aWindow),
DOM_WINDOW_THAWED_TOPIC, nullptr);
}
}
}
Element* nsGlobalWindowInner::GetFrameElement(nsIPrincipal& aSubjectPrincipal,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetFrameElement, (aSubjectPrincipal), aError,
nullptr);
}
Element* nsGlobalWindowInner::GetRealFrameElement(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetFrameElement, (), aError, nullptr);
}
void nsGlobalWindowInner::UpdateCommands(const nsAString& anAction,
Selection* aSel, int16_t aReason) {
if (GetOuterWindowInternal()) {
GetOuterWindowInternal()->UpdateCommands(anAction, aSel, aReason);
}
}
Selection* nsGlobalWindowInner::GetSelection(ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetSelectionOuter, (), aError, nullptr);
}
bool nsGlobalWindowInner::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 nsGlobalWindowInner::GetOrigin(nsAString& aOrigin) {
nsContentUtils::GetUTFOrigin(GetPrincipal(), aOrigin);
}
void nsGlobalWindowInner::Atob(const nsAString& aAsciiBase64String,
nsAString& aBinaryData, ErrorResult& aError) {
aError = nsContentUtils::Atob(aAsciiBase64String, aBinaryData);
}
void nsGlobalWindowInner::Btoa(const nsAString& aBinaryData,
nsAString& aAsciiBase64String,
ErrorResult& aError) {
aError = nsContentUtils::Btoa(aBinaryData, aAsciiBase64String);
}
//*****************************************************************************
// EventTarget
//*****************************************************************************
nsPIDOMWindowOuter* nsGlobalWindowInner::GetOwnerGlobalForBindingsInternal() {
return nsPIDOMWindowOuter::GetFromCurrentInner(this);
}
bool nsGlobalWindowInner::DispatchEvent(Event& aEvent, CallerType aCallerType,
ErrorResult& aRv) {
if (!IsCurrentInnerWindow()) {
NS_WARNING(
"DispatchEvent called on non-current inner window, dropping. "
"Please check the window in the caller instead.");
aRv.Throw(NS_ERROR_FAILURE);
return false;
}
if (!mDoc) {
aRv.Throw(NS_ERROR_FAILURE);
return false;
}
// Obtain a presentation shell
RefPtr<nsPresContext> presContext = mDoc->GetPresContext();
nsEventStatus status = nsEventStatus_eIgnore;
nsresult rv = EventDispatcher::DispatchDOMEvent(
ToSupports(this), nullptr, &aEvent, presContext, &status);
bool retval = !aEvent.DefaultPrevented(aCallerType);
if (NS_FAILED(rv)) {
aRv.Throw(rv);
}
return retval;
}
mozilla::Maybe<mozilla::dom::EventCallbackDebuggerNotificationType>
nsGlobalWindowInner::GetDebuggerNotificationType() const {
return mozilla::Some(
mozilla::dom::EventCallbackDebuggerNotificationType::Global);
}
bool nsGlobalWindowInner::ComputeDefaultWantsUntrusted(ErrorResult& aRv) {
return !nsContentUtils::IsChromeDoc(mDoc);
}
EventListenerManager* nsGlobalWindowInner::GetOrCreateListenerManager() {
if (!mListenerManager) {
mListenerManager =
new EventListenerManager(static_cast<EventTarget*>(this));
}
return mListenerManager;
}
EventListenerManager* nsGlobalWindowInner::GetExistingListenerManager() const {
return mListenerManager;
}
mozilla::dom::DebuggerNotificationManager*
nsGlobalWindowInner::GetOrCreateDebuggerNotificationManager() {
if (!mDebuggerNotificationManager) {
mDebuggerNotificationManager = new DebuggerNotificationManager(this);
}
return mDebuggerNotificationManager;
}
mozilla::dom::DebuggerNotificationManager*
nsGlobalWindowInner::GetExistingDebuggerNotificationManager() {
return mDebuggerNotificationManager;
}
//*****************************************************************************
// nsGlobalWindowInner::nsPIDOMWindow
//*****************************************************************************
Location* nsGlobalWindowInner::Location() {
if (!mLocation) {
mLocation = new dom::Location(this, GetBrowsingContext());
}
return mLocation;
}
void nsGlobalWindowInner::MaybeUpdateTouchState() {
if (mMayHaveTouchEventListener) {
nsCOMPtr<nsIObserverService> observerService =
services::GetObserverService();
if (observerService) {
observerService->NotifyObservers(static_cast<nsIDOMWindow*>(this),
DOM_TOUCH_LISTENER_ADDED, nullptr);
}
}
}
void nsGlobalWindowInner::EnableGamepadUpdates() {
if (mHasGamepad) {
RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
if (gamepadManager) {
gamepadManager->AddListener(this);
}
}
}
void nsGlobalWindowInner::DisableGamepadUpdates() {
if (mHasGamepad) {
RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
if (gamepadManager) {
gamepadManager->RemoveListener(this);
}
}
}
void nsGlobalWindowInner::EnableVRUpdates() {
// We need to create a VREventObserver before we can either detect XR runtimes
// or start an XR session
if (!mVREventObserver && (mHasXRSession || mXRRuntimeDetectionInFlight)) {
// Assert that we are not creating the observer while IsDying() as
// that would result in a leak. VREventObserver holds a RefPtr to
// this nsGlobalWindowInner and would prevent it from being deallocated.
MOZ_ASSERT(!IsDying(),
"Creating a VREventObserver for an nsGlobalWindow that is "
"dying would cause it to leak.");
mVREventObserver = new VREventObserver(this);
}
// If the content has an XR session, then we need to tell
// VREventObserver that there is VR activity.
if (mHasXRSession) {
nsPIDOMWindowOuter* outer = GetOuterWindow();
if (outer && !outer->IsBackground()) {
StartVRActivity();
}
}
}
void nsGlobalWindowInner::DisableVRUpdates() {
if (mVREventObserver) {
mVREventObserver->DisconnectFromOwner();
mVREventObserver = nullptr;
}
}
void nsGlobalWindowInner::ResetVRTelemetry(bool aUpdate) {
if (mVREventObserver) {
mVREventObserver->UpdateSpentTimeIn2DTelemetry(aUpdate);
}
}
void nsGlobalWindowInner::StartVRActivity() {
/**
* If the content has an XR session, tell
* the VREventObserver that the window is accessing
* VR devices.
*
* It's possible to have a VREventObserver without
* and XR session, if we are using it to get updates
* about XR runtime enumeration. In this case,
* we would not tell the VREventObserver that
* we are accessing VR devices.
*/
if (mVREventObserver && mHasXRSession) {
mVREventObserver->StartActivity();
}
}
void nsGlobalWindowInner::StopVRActivity() {
/**
* If the content has an XR session, tell
* the VReventObserver that the window is no longer
* accessing VR devices. This does not stop the
* XR session itself, which may be resumed with
* EnableVRUpdates.
* It's possible to have a VREventObserver without
* and XR session, if we are using it to get updates
* about XR runtime enumeration. In this case,
* we would not tell the VREventObserver that
* we ending an activity that accesses VR devices.
*/
if (mVREventObserver && mHasXRSession) {
mVREventObserver->StopActivity();
}
}
void nsGlobalWindowInner::SetFocusedElement(Element* aElement,
uint32_t aFocusMethod,
bool aNeedsFocus) {
if (aElement && aElement->GetComposedDoc() != mDoc) {
NS_WARNING("Trying to set focus to a node from a wrong document");
return;
}
if (IsDying()) {
NS_ASSERTION(!aElement, "Trying to focus cleaned up window!");
aElement = nullptr;
aNeedsFocus = false;
}
if (mFocusedElement != aElement) {
UpdateCanvasFocus(false, aElement);
mFocusedElement = aElement;
// TODO: Maybe this should be set on refocus too?
mFocusMethod = aFocusMethod & nsIFocusManager::METHOD_MASK;
}
if (mFocusedElement) {
// if a node was focused by a keypress, turn on focus rings for the
// window.
if (mFocusMethod & nsIFocusManager::FLAG_BYKEY) {
mUnknownFocusMethodShouldShowOutline = true;
mFocusByKeyOccurred = true;
} else if (nsFocusManager::GetFocusMoveActionCause(mFocusMethod) !=
widget::InputContextAction::CAUSE_UNKNOWN) {
mUnknownFocusMethodShouldShowOutline = false;
} else if (aFocusMethod & nsIFocusManager::FLAG_NOSHOWRING) {
// If we get focused via script, and script has explicitly opted out of
// outlines via FLAG_NOSHOWRING, we don't want to make a refocus start
// showing outlines.
mUnknownFocusMethodShouldShowOutline = false;
}
}
if (aNeedsFocus) {
mNeedsFocus = aNeedsFocus;
}
}
uint32_t nsGlobalWindowInner::GetFocusMethod() { return mFocusMethod; }
bool nsGlobalWindowInner::ShouldShowFocusRing() {
if (mFocusByKeyOccurred &&
StaticPrefs::browser_display_always_show_rings_after_key_focus()) {
return true;
}
nsCOMPtr<nsPIWindowRoot> root = GetTopWindowRoot();
return root && root->ShowFocusRings();
}
bool nsGlobalWindowInner::TakeFocus(bool aFocus, uint32_t aFocusMethod) {
if (IsDying()) {
return false;
}
if (aFocus) {
mFocusMethod = aFocusMethod & nsIFocusManager::METHOD_MASK;
}
if (mHasFocus != aFocus) {
mHasFocus = aFocus;
UpdateCanvasFocus(true, mFocusedElement);
}
// 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 nsGlobalWindowInner::SetReadyForFocus() {
bool oldNeedsFocus = mNeedsFocus;
mNeedsFocus = false;
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
fm->WindowShown(GetOuterWindow(), oldNeedsFocus);
}
}
void nsGlobalWindowInner::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.
nsFocusManager* fm = nsFocusManager::GetFocusManager();
if (fm) {
fm->WindowHidden(GetOuterWindow(), nsFocusManager::GenerateFocusActionId());
}
mNeedsFocus = true;
}
class HashchangeCallback : public Runnable {
public:
HashchangeCallback(const nsAString& aOldURL, const nsAString& aNewURL,
nsGlobalWindowInner* aWindow)
: mozilla::Runnable("HashchangeCallback"), mWindow(aWindow) {
MOZ_ASSERT(mWindow);
mOldURL.Assign(aOldURL);
mNewURL.Assign(aNewURL);
}
NS_IMETHOD Run() override {
MOZ_ASSERT(NS_IsMainThread(), "Should be called on the main thread.");
return mWindow->FireHashchange(mOldURL, mNewURL);
}
private:
nsString mOldURL;
nsString mNewURL;
RefPtr<nsGlobalWindowInner> mWindow;
};
nsresult nsGlobalWindowInner::DispatchAsyncHashchange(nsIURI* aOldURI,
nsIURI* aNewURI) {
// 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 Dispatch(TaskCategory::Other, callback.forget());
}
nsresult nsGlobalWindowInner::FireHashchange(const nsAString& aOldURL,
const nsAString& aNewURL) {
// 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(IsCurrentInnerWindow());
HashChangeEventInit init;
init.mNewURL = aNewURL;
init.mOldURL = aOldURL;
RefPtr<HashChangeEvent> event =
HashChangeEvent::Constructor(this, u"hashchange"_ns, init);
event->SetTrusted(true);
ErrorResult rv;
DispatchEvent(*event, rv);
return rv.StealNSResult();
}
nsresult nsGlobalWindowInner::DispatchSyncPopState() {
NS_ASSERTION(nsContentUtils::IsSafeToRunScript(),
"Must be safe to run script here.");
// 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;
nsresult rv = mDoc->GetStateObject(getter_AddRefs(stateObj));
NS_ENSURE_SUCCESS(rv, rv);
AutoJSAPI jsapi;
bool result = jsapi.Init(this);
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.mState = stateJSValue;
RefPtr<PopStateEvent> event =
PopStateEvent::Constructor(this, u"popstate"_ns, init);
event->SetTrusted(true);
event->SetTarget(this);
ErrorResult err;
DispatchEvent(*event, err);
return err.StealNSResult();
}
//-------------------------------------------------------
// Tells the HTMLFrame/CanvasFrame that is now has focus
void nsGlobalWindowInner::UpdateCanvasFocus(bool aFocusChanged,
nsIContent* aNewContent) {
// this is called from the inner window so use GetDocShell
nsIDocShell* docShell = GetDocShell();
if (!docShell) return;
bool editable;
docShell->GetEditable(&editable);
if (editable) return;
PresShell* presShell = docShell->GetPresShell();
if (!presShell || !mDoc) {
return;
}
Element* rootElement = mDoc->GetRootElement();
if (rootElement) {
if ((mHasFocus || aFocusChanged) &&
(mFocusedElement == rootElement || aNewContent == rootElement)) {
nsCanvasFrame* canvasFrame = presShell->GetCanvasFrame();
if (canvasFrame) {
canvasFrame->SetHasFocus(mHasFocus && rootElement == aNewContent);
}
}
} else {
// XXXbz I would expect that there is never a canvasFrame in this case...
nsCanvasFrame* canvasFrame = presShell->GetCanvasFrame();
if (canvasFrame) {
canvasFrame->SetHasFocus(false);
}
}
}
already_AddRefed<nsICSSDeclaration> nsGlobalWindowInner::GetComputedStyle(
Element& aElt, const nsAString& aPseudoElt, ErrorResult& aError) {
return GetComputedStyleHelper(aElt, aPseudoElt, false, aError);
}
already_AddRefed<nsICSSDeclaration>
nsGlobalWindowInner::GetDefaultComputedStyle(Element& aElt,
const nsAString& aPseudoElt,
ErrorResult& aError) {
return GetComputedStyleHelper(aElt, aPseudoElt, true, aError);
}
already_AddRefed<nsICSSDeclaration> nsGlobalWindowInner::GetComputedStyleHelper(
Element& aElt, const nsAString& aPseudoElt, bool aDefaultStylesOnly,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetComputedStyleHelperOuter,
(aElt, aPseudoElt, aDefaultStylesOnly), aError,
nullptr);
}
Storage* nsGlobalWindowInner::GetSessionStorage(ErrorResult& aError) {
nsIPrincipal* principal = GetPrincipal();
nsIPrincipal* storagePrincipal = GetEffectiveStoragePrincipal();
BrowsingContext* browsingContext = GetBrowsingContext();
if (!principal || !storagePrincipal || !browsingContext ||
!Storage::StoragePrefIsEnabled()) {
return nullptr;
}
if (mSessionStorage) {
MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
("nsGlobalWindowInner %p has %p sessionStorage", this,
mSessionStorage.get()));
bool canAccess =
principal->Subsumes(mSessionStorage->Principal()) &&
storagePrincipal->Subsumes(mSessionStorage->StoragePrincipal());
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;
}
uint32_t rejectedReason = 0;
StorageAccess access = StorageAllowedForWindow(this, &rejectedReason);
// SessionStorage is an ephemeral per-tab per-origin storage that only lives
// as long as the tab is open, although it may survive browser restarts
// thanks to the session store. So we interpret storage access differently
// than we would for persistent per-origin storage like LocalStorage and so
// it may be okay to provide SessionStorage even when we receive a value of
// eDeny.
//
// ContentBlocking::ShouldAllowAccessFor will return false for 3 main
// reasons.
//
// 1. Cookies are entirely blocked due to a per-origin permission
// (nsICookiePermission::ACCESS_DENY for the top-level principal or this
// window's principal) or the very broad BEHAVIOR_REJECT. This will return
// eDeny with a reason of STATE_COOKIES_BLOCKED_BY_PERMISSION or
// STATE_COOKIES_BLOCKED_ALL.
//
// 2. Third-party cookies are limited via BEHAVIOR_REJECT_FOREIGN and
// BEHAVIOR_LIMIT_FOREIGN and this is a third-party window. This will return
// eDeny with a reason of STATE_COOKIES_BLOCKED_FOREIGN.
//
// 3. Tracking protection (BEHAVIOR_REJECT_TRACKER and
// BEHAVIOR_REJECT_TRACKER_AND_PARTITION_FOREIGN) is in effect and
// IsThirdPartyTrackingResourceWindow() returned true and there wasn't a
// permission that allows it. This will return ePartitionTrackersOrDeny with
// a reason of STATE_COOKIES_BLOCKED_TRACKER or
// STATE_COOKIES_BLOCKED_SOCIALTRACKER.
//
// In the 1st case, the user has explicitly indicated that they don't want
// to allow any storage to the origin or all origins and so we throw an
// error and deny access to SessionStorage. In the 2nd case, a legacy
// decision reasoned that there's no harm in providing SessionStorage
// because the information is not durable and cannot escape the current tab.
// The rationale is similar for the 3rd case.
if (access == StorageAccess::eDeny &&
rejectedReason !=
nsIWebProgressListener::STATE_COOKIES_BLOCKED_FOREIGN) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
const RefPtr<SessionStorageManager> storageManager =
browsingContext->GetSessionStorageManager();
if (!storageManager) {
aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
RefPtr<Storage> storage;
aError = storageManager->CreateStorage(this, principal, storagePrincipal,
documentURI, IsPrivateBrowsing(),
getter_AddRefs(storage));
if (aError.Failed()) {
return nullptr;
}
mSessionStorage = storage;
MOZ_ASSERT(mSessionStorage);
MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
("nsGlobalWindowInner %p tried to get a new sessionStorage %p",
this, mSessionStorage.get()));
if (!mSessionStorage) {
aError.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
return nullptr;
}
}
MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug,
("nsGlobalWindowInner %p returns %p sessionStorage", this,
mSessionStorage.get()));
return mSessionStorage;
}
Storage* nsGlobalWindowInner::GetLocalStorage(ErrorResult& aError) {
if (!Storage::StoragePrefIsEnabled()) {
return nullptr;
}
// LocalStorage needs to be exposed in every context except for sandboxes and
// NullPrincipals (data: URLs, for instance). But we need to keep data
// separate in some scenarios: private-browsing and partitioned trackers.
// In private-browsing, LocalStorage keeps data in memory, and it shares
// StorageEvents just with other origins in the same private-browsing
// environment.
// For Partitioned Trackers, we expose a partitioned LocalStorage, which
// doesn't share data with other contexts, and it's just in memory.
// Partitioned localStorage is available only for trackers listed in the
// privacy.restrict3rdpartystorage.partitionedHosts pref. See
// nsContentUtils::IsURIInPrefList to know the syntax for the pref value.
// This is a temporary web-compatibility hack.
StorageAccess access = StorageAllowedForWindow(this);
// We allow partitioned localStorage only to some hosts.
bool isolated = false;
if (ShouldPartitionStorage(access)) {
if (!mDoc) {
access = StorageAccess::eDeny;
} else if (!StoragePartitioningEnabled(access, mDoc->CookieJarSettings())) {
static const char* kPrefName =
"privacy.restrict3rdpartystorage.partitionedHosts";
bool isInList = false;
mDoc->NodePrincipal()->IsURIInPrefList(kPrefName, &isInList);
if (!isInList) {
access = StorageAccess::eDeny;
} else {
isolated = true;
}
}
}
if (access == StorageAccess::eDeny) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
nsCOMPtr<nsICookieJarSettings> cookieJarSettings;
if (mDoc) {
cookieJarSettings = mDoc->CookieJarSettings();
} else {
cookieJarSettings = net::CookieJarSettings::GetBlockingAll();
}
// Note that this behavior is observable: if we grant storage permission to a
// tracker, we pass from the partitioned LocalStorage (or a partitioned cookie
// jar) to the 'normal' one. The previous data is lost and the 2
// window.localStorage objects, before and after the permission granted, will
// be different.
if (mLocalStorage) {
if ((mLocalStorage->Type() == (isolated ? Storage::ePartitionedLocalStorage
: Storage::eLocalStorage)) &&
(mLocalStorage->StoragePrincipal() == GetEffectiveStoragePrincipal())) {
return mLocalStorage;
}
// storage needs change
mLocalStorage = nullptr;
}
MOZ_ASSERT(!mLocalStorage);
if (!isolated) {
RefPtr<Storage> storage;
if (NextGenLocalStorageEnabled()) {
aError = LSObject::CreateForWindow(this, getter_AddRefs(storage));
} else {
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;
}
}
nsIPrincipal* principal = GetPrincipal();
if (!principal) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
nsIPrincipal* storagePrincipal = GetEffectiveStoragePrincipal();
if (!storagePrincipal) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
aError = storageManager->CreateStorage(this, principal, storagePrincipal,
documentURI, IsPrivateBrowsing(),
getter_AddRefs(storage));
}
if (aError.Failed()) {
return nullptr;
}
mLocalStorage = storage;
} else {
nsresult rv;
nsCOMPtr<nsIDOMSessionStorageManager> storageManager =
do_GetService("@mozilla.org/dom/sessionStorage-manager;1", &rv);
if (NS_FAILED(rv)) {
aError.Throw(rv);
return nullptr;
}
nsIPrincipal* principal = GetPrincipal();
if (!principal) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
nsIPrincipal* storagePrincipal = GetEffectiveStoragePrincipal();
if (!storagePrincipal) {
aError.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
RefPtr<SessionStorageCache> cache;
if (isolated) {
cache = new SessionStorageCache();
} else {
// This will clone the session storage if it exists.
rv = storageManager->GetSessionStorageCache(principal, storagePrincipal,
&cache);
if (NS_FAILED(rv)) {
aError.Throw(rv);
return nullptr;
}
}
mLocalStorage =
new PartitionedLocalStorage(this, principal, storagePrincipal, cache);
}
MOZ_ASSERT(mLocalStorage);
MOZ_ASSERT(
mLocalStorage->Type() ==
(isolated ? Storage::ePartitionedLocalStorage : Storage::eLocalStorage));
return mLocalStorage;
}
IDBFactory* nsGlobalWindowInner::GetIndexedDB(ErrorResult& aError) {
if (!mIndexedDB) {
// This may keep mIndexedDB null without setting an error.
auto res = IDBFactory::CreateForWindow(this);
if (res.isErr()) {
aError = res.unwrapErr();
} else {
mIndexedDB = res.unwrap();
}
}
return mIndexedDB;
}
//*****************************************************************************
// nsGlobalWindowInner::nsIInterfaceRequestor
//*****************************************************************************
NS_IMETHODIMP
nsGlobalWindowInner::GetInterface(const nsIID& aIID, void** aSink) {
nsGlobalWindowOuter* outer = GetOuterWindowInternal();
NS_ENSURE_TRUE(outer, NS_ERROR_NOT_INITIALIZED);
nsresult rv = outer->GetInterfaceInternal(aIID, aSink);
if (rv == NS_ERROR_NO_INTERFACE) {
return QueryInterface(aIID, aSink);
}
return rv;
}
void nsGlobalWindowInner::GetInterface(JSContext* aCx,
JS::Handle<JS::Value> aIID,
JS::MutableHandle<JS::Value> aRetval,
ErrorResult& aError) {
dom::GetInterface(aCx, this, aIID, aRetval, aError);
}
already_AddRefed<CacheStorage> nsGlobalWindowInner::GetCaches(
ErrorResult& aRv) {
if (!mCacheStorage) {
bool forceTrustedOrigin =
GetBrowsingContext() &&
GetBrowsingContext()->Top()->GetServiceWorkersTestingEnabled();
mCacheStorage = CacheStorage::CreateOnMainThread(
cache::DEFAULT_NAMESPACE, this, GetEffectiveStoragePrincipal(),
forceTrustedOrigin, aRv);
}
RefPtr<CacheStorage> ref = mCacheStorage;
return ref.forget();
}
void nsGlobalWindowInner::FireOfflineStatusEventIfChanged() {
if (!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");
}
nsContentUtils::DispatchTrustedEvent(mDoc, static_cast<EventTarget*>(this),
name, CanBubble::eNo, Cancelable::eNo);
}
nsGlobalWindowInner::SlowScriptResponse
nsGlobalWindowInner::ShowSlowScriptDialog(JSContext* aCx,
const nsString& aAddonId,
const double aDuration) {
nsresult rv;
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::WarnASCII(aCx, "A long running script was terminated");
return KillSlowScript;
}
// If our document is not active, just kill the script: we've been unloaded
if (!HasActiveDocument()) {
return KillSlowScript;
}
// Check if we should offer the option to debug
JS::AutoFilename filename;
unsigned lineno;
// Computing the line number can be very expensive (see bug 1330231 for
// example), and we don't use the line number anywhere except than in the
// parent process, so we avoid computing it elsewhere. This gives us most of
// the wins we are interested in, since the source of the slowness here is
// minified scripts which is more common in Web content that is loaded in the
// content process.
unsigned* linenop = XRE_IsParentProcess() ? &lineno : nullptr;
bool hasFrame = JS::DescribeScriptedCaller(aCx, &filename, linenop);
// 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;
// Override the cursor to something that we're sure the user can see.
SetCursor("auto"_ns, IgnoreErrors());
if (XRE_IsContentProcess() && ProcessHangMonitor::Get()) {
ProcessHangMonitor::SlowScriptAction action;
RefPtr<ProcessHangMonitor> monitor = ProcessHangMonitor::Get();
nsIDocShell* docShell = GetDocShell();
nsCOMPtr<nsIBrowserChild> child =
docShell ? docShell->GetBrowserChild() : nullptr;
action =
monitor->NotifySlowScript(child, filename.get(), aAddonId, aDuration);
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<nsGlobalWindowOuter> outer = GetOuterWindowInternal();
outer->EnterModalState();
SpinEventLoopUntil(
[&]() { return monitor->IsDebuggerStartupComplete(); });
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 failed = false;
auto getString = [&](const char* name,
nsContentUtils::PropertiesFile propFile =
nsContentUtils::eDOM_PROPERTIES) {
nsAutoString result;
nsresult rv = nsContentUtils::GetLocalizedString(propFile, name, result);
// GetStringFromName can return NS_OK and still give nullptr string
failed = failed || NS_FAILED(rv) || result.IsEmpty();
return result;
};
bool isAddonScript = !aAddonId.IsEmpty();
bool showDebugButton = debugCallback && !isAddonScript;
// Get localizable strings
nsAutoString title, checkboxMsg, debugButton, msg;
if (isAddonScript) {
title = getString("KillAddonScriptTitle");
checkboxMsg = getString("KillAddonScriptGlobalMessage");
auto appName =
getString("brandShortName", nsContentUtils::eBRAND_PROPERTIES);
nsCOMPtr<nsIAddonPolicyService> aps =
do_GetService("@mozilla.org/addons/policy-service;1");
nsString addonName;
if (!aps || NS_FAILED(aps->GetExtensionName(aAddonId, addonName))) {
addonName = aAddonId;
}
rv = nsContentUtils::FormatLocalizedString(
msg, nsContentUtils::eDOM_PROPERTIES, "KillAddonScriptMessage",
addonName, appName);
failed = failed || NS_FAILED(rv);
} else {
title = getString("KillScriptTitle");
checkboxMsg = getString("DontAskAgain");
if (showDebugButton) {
debugButton = getString("DebugScriptButton");
msg = getString("KillScriptWithDebugMessage");
} else {
msg = getString("KillScriptMessage");
}
}
auto stopButton = getString("StopScriptButton");
auto waitButton = getString("WaitForScriptButton");
if (failed) {
NS_ERROR("Failed to get localized strings.");
return ContinueSlowScript;
}
// Append file and line number information, if available
if (filename.get()) {
nsAutoString 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.ReplaceLiteral(cutStart, cutLength, u"\x2026");
}
rv = nsContentUtils::FormatLocalizedString(
scriptLocation, nsContentUtils::eDOM_PROPERTIES, "KillScriptLocation",
filenameUTF16);
if (NS_SUCCEEDED(rv)) {
msg.AppendLiteral("\n\n");
msg.Append(scriptLocation);
msg.Append(':');
msg.AppendInt(lineno);
}
}
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;
bool checkboxValue = false;
int32_t buttonPressed = 0; // In case the user exits dialog by clicking X.
{
// Null out the operation callback while we're re-entering JS here.
AutoDisableJSInterruptCallback disabler(aCx);
// Open the dialog.
rv = prompt->ConfirmEx(
title.get(), msg.get(), buttonFlags, waitButton.get(), stopButton.get(),
debugButton.get(), checkboxMsg.get(), &checkboxValue, &buttonPressed);
}
if (buttonPressed == 0) {
if (checkboxValue && !isAddonScript && NS_SUCCEEDED(rv))
return AlwaysContinueSlowScript;
return ContinueSlowScript;
}
if (buttonPressed == 2) {
MOZ_RELEASE_ASSERT(debugCallback);
rv = debugCallback->HandleSlowScriptDebug(this);
return NS_SUCCEEDED(rv) ? ContinueSlowScript : KillSlowScript;
}
JS_ClearPendingException(aCx);
return KillSlowScript;
}
nsresult nsGlobalWindowInner::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, MEMORY_PRESSURE_OBSERVER_TOPIC)) {
if (mPerformance) {
mPerformance->MemoryPressure();
}
RemoveReportRecords();
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<nsIObserver> observer = GetApplicationCache();
if (observer) observer->Observe(aSubject, aTopic, aData);
return NS_OK;
}
if (!nsCRT::strcmp(aTopic, PERMISSION_CHANGED_TOPIC)) {
nsCOMPtr<nsIPermission> perm(do_QueryInterface(aSubject));
if (!perm) {
// A null permission indicates that the entire permission list
// was cleared.
MOZ_ASSERT(!nsCRT::strcmp(aData, u"cleared"));
UpdatePermissions();
return NS_OK;
}
nsAutoCString type;
perm->GetType(type);
if (type == "autoplay-media"_ns) {
UpdateAutoplayPermission();
} else if (type == "shortcuts"_ns) {
UpdateShortcutsPermission();
} else if (type == "popup"_ns) {
UpdatePopupPermission();
}
if (!mDoc) {
return NS_OK;
}
RefPtr<PermissionDelegateHandler> permDelegateHandler =
mDoc->GetPermissionDelegateHandler();
if (permDelegateHandler) {
permDelegateHandler->UpdateDelegatedPermission(type);
}
return NS_OK;
}
if (!nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID)) {
MOZ_ASSERT(!NS_strcmp(aData, u"intl.accept_languages"));
// 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) {
Navigator_Binding::ClearCachedLanguageValue(mNavigator);
Navigator_Binding::ClearCachedLanguagesValue(mNavigator);
}
// The event has to be dispatched only to the current inner window.
if (!IsCurrentInnerWindow()) {
return NS_OK;
}
RefPtr<Event> event = NS_NewDOMEvent(this, nullptr, nullptr);
event->InitEvent(u"languagechange"_ns, false, false);
event->SetTrusted(true);
ErrorResult rv;
DispatchEvent(*event, rv);
return rv.StealNSResult();
}
NS_WARNING("unrecognized topic in nsGlobalWindowInner::Observe");
return NS_ERROR_FAILURE;
}
void nsGlobalWindowInner::ObserveStorageNotification(
StorageEvent* aEvent, const char16_t* aStorageType, bool aPrivateBrowsing) {
MOZ_ASSERT(aEvent);
// The private browsing check must be done here again because this window
// could have changed its state before the notification check and now. This
// happens in case this window did have a docShell at that time.
if (aPrivateBrowsing != IsPrivateBrowsing()) {
return;
}
// LocalStorage can only exist on an inner window, and we don't want to
// generate events on frozen or otherwise-navigated-away from windows.
// (Actually, this code used to try and buffer events for frozen windows,
// but it never worked, so we've removed it. See bug 1285898.)
if (!IsCurrentInnerWindow() || IsFrozen()) {
return;
}
nsIPrincipal* principal = GetPrincipal();
if (!principal) {
return;
}
bool fireMozStorageChanged = false;
nsAutoString eventType;
eventType.AssignLiteral("storage");
if (!NS_strcmp(aStorageType, u"sessionStorage")) {
RefPtr<Storage> changingStorage = aEvent->GetStorageArea();
MOZ_ASSERT(changingStorage);
bool check = false;
if (const RefPtr<SessionStorageManager> storageManager =
GetBrowsingContext()->GetSessionStorageManager()) {
nsresult rv = storageManager->CheckStorage(GetEffectiveStoragePrincipal(),
changingStorage, &check);
if (NS_FAILED(rv)) {
return;
}
}
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;
}
MOZ_LOG(
gDOMLeakPRLogInner, LogLevel::Debug,
("nsGlobalWindowInner %p with sessionStorage %p passing event from %p",
this, mSessionStorage.get(), changingStorage.get()));
fireMozStorageChanged = mSessionStorage == changingStorage;
if (fireMozStorageChanged) {
eventType.AssignLiteral("MozSessionStorageChanged");
}
}
else {
MOZ_ASSERT(!NS_strcmp(aStorageType, u"localStorage"));
nsIPrincipal* storagePrincipal = GetEffectiveStoragePrincipal();
if (!storagePrincipal) {
return;
}
MOZ_DIAGNOSTIC_ASSERT(StorageUtils::PrincipalsEqual(aEvent->GetPrincipal(),
storagePrincipal));
fireMozStorageChanged =
mLocalStorage && mLocalStorage == aEvent->GetStorageArea();
if (fireMozStorageChanged) {
eventType.AssignLiteral("MozLocalStorageChanged");
}
}
// Clone the storage event included in the observer notification. We want
// to dispatch clones rather than the original event.
IgnoredErrorResult error;
RefPtr<StorageEvent> clonedEvent =
CloneStorageEvent(eventType, aEvent, error);
if (error.Failed() || !clonedEvent) {
return;
}
clonedEvent->SetTrusted(true);
if (fireMozStorageChanged) {
WidgetEvent* internalEvent = clonedEvent->WidgetEventPtr();
internalEvent->mFlags.mOnlyChromeDispatch = true;
}
DispatchEvent(*clonedEvent);
}
already_AddRefed<StorageEvent> nsGlobalWindowInner::CloneStorageEvent(
const nsAString& aType, const RefPtr<StorageEvent>& aEvent,
ErrorResult& aRv) {
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<Storage> storageArea = aEvent->GetStorageArea();
RefPtr<Storage> storage;
// If null, this is a localStorage event received by IPC.
if (!storageArea) {
storage = GetLocalStorage(aRv);
if (!NextGenLocalStorageEnabled()) {
if (aRv.Failed() || !storage) {
return nullptr;
}
if (storage->Type() == Storage::eLocalStorage) {
RefPtr<LocalStorage> localStorage =
static_cast<LocalStorage*>(storage.get());
// We must apply the current change to the 'local' localStorage.
localStorage->ApplyEvent(aEvent);
}
}
} else if (storageArea->Type() == Storage::eSessionStorage) {
storage = GetSessionStorage(aRv);
} else {
MOZ_ASSERT(storageArea->Type() == Storage::eLocalStorage);
storage = GetLocalStorage(aRv);
}
if (aRv.Failed() || !storage) {
return nullptr;
}
if (storage->Type() == Storage::ePartitionedLocalStorage) {
// This error message is not exposed.
aRv.Throw(NS_ERROR_DOM_SECURITY_ERR);
return nullptr;
}
MOZ_ASSERT(storage);
MOZ_ASSERT_IF(storageArea, storage->IsForkOf(storageArea));
dict.mStorageArea = storage;
RefPtr<StorageEvent> event = StorageEvent::Constructor(this, aType, dict);
return event.forget();
}
void nsGlobalWindowInner::Suspend(bool aIncludeSubWindows) {
MOZ_ASSERT(NS_IsMainThread());
// 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 (!IsCurrentInnerWindow()) {
return;
}
// All in-process descendants are also suspended. This ensure mSuspendDepth
// is set properly and the timers are properly canceled for each in-process
// descendant.
if (aIncludeSubWindows) {
CallOnInProcessDescendants(&nsGlobalWindowInner::Suspend, false);
}
mSuspendDepth += 1;
if (mSuspendDepth != 1) {
return;
}
if (mWindowGlobalChild) {
mWindowGlobalChild->BlockBFCacheFor(BFCacheStatus::SUSPENDED);
}
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();
SuspendWorkersForWindow(*this);
for (RefPtr<mozilla::dom::SharedWorker> pinnedWorker :
mSharedWorkers.ForwardRange()) {
pinnedWorker->Suspend();
}
SuspendIdleRequests();
mTimeoutManager->Suspend();
// Suspend all of the AudioContexts for this window
for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) {
mAudioContexts[i]->SuspendFromChrome();
}
}
void nsGlobalWindowInner::Resume(bool aIncludeSubWindows) {
MOZ_ASSERT(NS_IsMainThread());
// 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 (!IsCurrentInnerWindow()) {
return;
}
// Resume all in-process descendants. This restores timers recursively
// canceled in Suspend() and ensures all in-process descendants have the
// correct mSuspendDepth.
if (aIncludeSubWindows) {
CallOnInProcessDescendants(&nsGlobalWindowInner::Resume, false);
}
if (mSuspendDepth == 0) {
// Ignore if the window is not suspended.
return;
}
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) {
mAudioContexts[i]->ResumeFromChrome();
}
mTimeoutManager->Resume();
ResumeIdleRequests();
// 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().
ResumeWorkersForWindow(*this);
for (RefPtr<mozilla::dom::SharedWorker> pinnedWorker :
mSharedWorkers.ForwardRange()) {
pinnedWorker->Resume();
}
if (mWindowGlobalChild) {
mWindowGlobalChild->UnblockBFCacheFor(BFCacheStatus::SUSPENDED);
}
}
bool nsGlobalWindowInner::IsSuspended() const {
MOZ_ASSERT(NS_IsMainThread());
return mSuspendDepth != 0;
}
void nsGlobalWindowInner::Freeze(bool aIncludeSubWindows) {
MOZ_ASSERT(NS_IsMainThread());
Suspend(aIncludeSubWindows);
FreezeInternal(aIncludeSubWindows);
}
void nsGlobalWindowInner::FreezeInternal(bool aIncludeSubWindows) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(IsCurrentInnerWindow());
MOZ_DIAGNOSTIC_ASSERT(IsSuspended());
HintIsLoading(false);
if (aIncludeSubWindows) {
CallOnInProcessChildren(&nsGlobalWindowInner::FreezeInternal,
aIncludeSubWindows);
}
mFreezeDepth += 1;
MOZ_ASSERT(mSuspendDepth >= mFreezeDepth);
if (mFreezeDepth != 1) {
return;
}
FreezeWorkersForWindow(*this);
for (RefPtr<mozilla::dom::SharedWorker> pinnedWorker :
mSharedWorkers.ForwardRange()) {
pinnedWorker->Freeze();
}
mTimeoutManager->Freeze();
if (mClientSource) {
mClientSource->Freeze();
}
NotifyDOMWindowFrozen(this);
}
void nsGlobalWindowInner::Thaw(bool aIncludeSubWindows) {
MOZ_ASSERT(NS_IsMainThread());
ThawInternal(aIncludeSubWindows);
Resume(aIncludeSubWindows);
}
void nsGlobalWindowInner::ThawInternal(bool aIncludeSubWindows) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_DIAGNOSTIC_ASSERT(IsCurrentInnerWindow());
MOZ_DIAGNOSTIC_ASSERT(IsSuspended());
if (aIncludeSubWindows) {
CallOnInProcessChildren(&nsGlobalWindowInner::ThawInternal,
aIncludeSubWindows);
}
MOZ_ASSERT(mFreezeDepth != 0);
mFreezeDepth -= 1;
MOZ_ASSERT(mSuspendDepth >= mFreezeDepth);
if (mFreezeDepth != 0) {
return;
}
if (mClientSource) {
mClientSource->Thaw();
}
mTimeoutManager->Thaw();
ThawWorkersForWindow(*this);
for (RefPtr<mozilla::dom::SharedWorker> pinnedWorker :
mSharedWorkers.ForwardRange()) {
pinnedWorker->Thaw();
}
NotifyDOMWindowThawed(this);
}
bool nsGlobalWindowInner::IsFrozen() const {
MOZ_ASSERT(NS_IsMainThread());
bool frozen = mFreezeDepth != 0;
MOZ_ASSERT_IF(frozen, IsSuspended());
return frozen;
}
void nsGlobalWindowInner::SyncStateFromParentWindow() {
// This method should only be called on an inner window that has been
// assigned to an outer window already.
MOZ_ASSERT(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;
nsGlobalWindowInner* parentInner =
parentOuter
? nsGlobalWindowInner::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, typename... Args>
CallState nsGlobalWindowInner::CallOnInProcessDescendantsInternal(
BrowsingContext* aBrowsingContext, bool aChildOnly, Method aMethod,
Args&&... aArgs) {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(aBrowsingContext);
CallState state = CallState::Continue;
for (const RefPtr<BrowsingContext>& bc : aBrowsingContext->Children()) {
if (nsCOMPtr<nsPIDOMWindowOuter> pWin = bc->GetDOMWindow()) {
auto* win = nsGlobalWindowOuter::Cast(pWin);
if (nsGlobalWindowInner* inner = win->GetCurrentInnerWindowInternal()) {
// Call the descendant method using our helper CallDescendant() template
// method. This allows us to handle both void returning methods and
// methods that return CallState explicitly. For void returning methods
// we assume CallState::Continue.
typedef decltype((inner->*aMethod)(aArgs...)) returnType;
state = CallDescendant<returnType>(inner, aMethod, aArgs...);
if (state == CallState::Stop) {
return state;
}
}
}
if (!aChildOnly) {
state = CallOnInProcessDescendantsInternal(bc.get(), aChildOnly, aMethod,
aArgs...);
if (state == CallState::Stop) {
return state;
}
}
}
return state;
}
Maybe<ClientInfo> nsGlobalWindowInner::GetClientInfo() const {
MOZ_ASSERT(NS_IsMainThread());
if (mDoc && mDoc->IsStaticDocument()) {
if (Maybe<ClientInfo> info = mDoc->GetOriginalDocument()->GetClientInfo()) {
return info;
}
}
Maybe<ClientInfo> clientInfo;
if (mClientSource) {
clientInfo.emplace(mClientSource->Info());
}
return clientInfo;
}
Maybe<ClientState> nsGlobalWindowInner::GetClientState() const {
MOZ_ASSERT(NS_IsMainThread());
if (mDoc && mDoc->IsStaticDocument()) {
if (Maybe<ClientState> state =
mDoc->GetOriginalDocument()->GetClientState()) {
return state;
}
}
Maybe<ClientState> clientState;
if (mClientSource) {
Result<ClientState, ErrorResult> res = mClientSource->SnapshotState();
if (res.isOk()) {
clientState.emplace(res.unwrap());
} else {
res.unwrapErr().SuppressException();
}
}
return clientState;
}
Maybe<ServiceWorkerDescriptor> nsGlobalWindowInner::GetController() const {
MOZ_ASSERT(NS_IsMainThread());
if (mDoc && mDoc->IsStaticDocument()) {
if (Maybe<ServiceWorkerDescriptor> controller =
mDoc->GetOriginalDocument()->GetController()) {
return controller;
}
}
Maybe<ServiceWorkerDescriptor> controller;
if (mClientSource) {
controller = mClientSource->GetController();
}
return controller;
}
void nsGlobalWindowInner::SetCsp(nsIContentSecurityPolicy* aCsp) {
if (!mClientSource) {
return;
}
mClientSource->SetCsp(aCsp);
// Also cache the CSP within the document
mDoc->SetCsp(aCsp);
if (mWindowGlobalChild) {
mWindowGlobalChild->SendSetClientInfo(mClientSource->Info().ToIPC());
}
}
void nsGlobalWindowInner::SetPreloadCsp(nsIContentSecurityPolicy* aPreloadCsp) {
if (!mClientSource) {
return;
}
mClientSource->SetPreloadCsp(aPreloadCsp);
// Also cache the preload CSP within the document
mDoc->SetPreloadCsp(aPreloadCsp);
if (mWindowGlobalChild) {
mWindowGlobalChild->SendSetClientInfo(mClientSource->Info().ToIPC());
}
}
nsIContentSecurityPolicy* nsGlobalWindowInner::GetCsp() {
if (mDoc) {
return mDoc->GetCsp();
}
// If the window is partially torn down and has its document nulled out,
// we query the CSP we snapshot in FreeInnerObjects.
if (mDocumentCsp) {
return mDocumentCsp;
}
return nullptr;
}
RefPtr<ServiceWorker> nsGlobalWindowInner::GetOrCreateServiceWorker(
const ServiceWorkerDescriptor& aDescriptor) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<ServiceWorker> ref;
ForEachEventTargetObject([&](DOMEventTargetHelper* aTarget, bool* aDoneOut) {
RefPtr<ServiceWorker> sw = do_QueryObject(aTarget);
if (!sw || !sw->Descriptor().Matches(aDescriptor)) {
return;
}
ref = std::move(sw);
*aDoneOut = true;
});
if (!ref) {
ref = ServiceWorker::Create(this, aDescriptor);
}
return ref;
}
RefPtr<mozilla::dom::ServiceWorkerRegistration>
nsGlobalWindowInner::GetServiceWorkerRegistration(
const mozilla::dom::ServiceWorkerRegistrationDescriptor& aDescriptor)
const {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<ServiceWorkerRegistration> ref;
ForEachEventTargetObject([&](DOMEventTargetHelper* aTarget, bool* aDoneOut) {
RefPtr<ServiceWorkerRegistration> swr = do_QueryObject(aTarget);
if (!swr || !swr->MatchesDescriptor(aDescriptor)) {
return;
}
ref = std::move(swr);
*aDoneOut = true;
});
return ref;
}
RefPtr<ServiceWorkerRegistration>
nsGlobalWindowInner::GetOrCreateServiceWorkerRegistration(
const ServiceWorkerRegistrationDescriptor& aDescriptor) {
MOZ_ASSERT(NS_IsMainThread());
RefPtr<ServiceWorkerRegistration> ref =
GetServiceWorkerRegistration(aDescriptor);
if (!ref) {
ref = ServiceWorkerRegistration::CreateForMainThread(this, aDescriptor);
}
return ref;
}
nsresult nsGlobalWindowInner::FireDelayedDOMEvents(bool aIncludeSubWindows) {
if (mApplicationCache) {
static_cast<nsDOMOfflineResourceList*>(mApplicationCache.get())
->FirePendingEvents();
}
// Fires an offline status event if the offline status has changed
FireOfflineStatusEventIfChanged();
if (!aIncludeSubWindows) {
return NS_OK;
}
nsCOMPtr<nsIDocShell> docShell = GetDocShell();
if (docShell) {
int32_t childCount = 0;
docShell->GetInProcessChildCount(&childCount);
// Take a copy of the current children so that modifications to
// the child list don't affect to the iteration.
AutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> children;
for (int32_t i = 0; i < childCount; ++i) {
nsCOMPtr<nsIDocShellTreeItem> childShell;
docShell->GetInProcessChildAt(i, getter_AddRefs(childShell));
if (childShell) {
children.AppendElement(childShell);
}
}
for (nsCOMPtr<nsIDocShellTreeItem> childShell : children) {
if (nsCOMPtr<nsPIDOMWindowOuter> pWin = childShell->GetWindow()) {
auto* win = nsGlobalWindowOuter::Cast(pWin);
win->FireDelayedDOMEvents(true);
}
}
}
return NS_OK;
}
//*****************************************************************************
// nsGlobalWindowInner: Window Control Functions
//*****************************************************************************
nsPIDOMWindowOuter* nsGlobalWindowInner::GetInProcessParentInternal() {
nsGlobalWindowOuter* outer = GetOuterWindowInternal();
if (!outer) {
// No outer window available!
return nullptr;
}
return outer->GetInProcessParentInternal();
}
nsIPrincipal* nsGlobalWindowInner::GetTopLevelAntiTrackingPrincipal() {
nsPIDOMWindowOuter* outerWindow = GetOuterWindowInternal();
if (!outerWindow) {
return nullptr;
}
nsPIDOMWindowOuter* topLevelOuterWindow =
GetBrowsingContext()->Top()->GetDOMWindow();
if (!topLevelOuterWindow) {
return nullptr;
}
bool stopAtOurLevel =
mDoc && mDoc->CookieJarSettings()->GetCookieBehavior() ==
nsICookieService::BEHAVIOR_REJECT_TRACKER;
if (stopAtOurLevel && topLevelOuterWindow == outerWindow) {
return nullptr;
}
nsPIDOMWindowInner* topLevelInnerWindow =
topLevelOuterWindow->GetCurrentInnerWindow();
if (NS_WARN_IF(!topLevelInnerWindow)) {
return nullptr;
}
nsIPrincipal* topLevelPrincipal =
nsGlobalWindowInner::Cast(topLevelInnerWindow)->GetPrincipal();
if (NS_WARN_IF(!topLevelPrincipal)) {
return nullptr;
}
return topLevelPrincipal;
}
nsIPrincipal* nsGlobalWindowInner::GetTopLevelStorageAreaPrincipal() {
if (mDoc && (mDoc->StorageAccessSandboxed())) {
// Storage access is disabled
return nullptr;
}
BrowsingContext* parent = GetBrowsingContext()->GetParent();
nsPIDOMWindowOuter* outerWindow = parent ? parent->GetDOMWindow() : nullptr;
if (!outerWindow) {
// No outer window available!
return nullptr;
}
if (!outerWindow->GetBrowsingContext()->IsTop()) {
return nullptr;
}
nsPIDOMWindowInner* innerWindow = outerWindow->GetCurrentInnerWindow();
if (NS_WARN_IF(!innerWindow)) {
return nullptr;
}
nsIPrincipal* parentPrincipal =
nsGlobalWindowInner::Cast(innerWindow)->GetPrincipal();
if (NS_WARN_IF(!parentPrincipal)) {
return nullptr;
}
return parentPrincipal;
}
//*****************************************************************************
// nsGlobalWindowInner: Timeout Functions
//*****************************************************************************
class WindowScriptTimeoutHandler final : public ScriptTimeoutHandler {
public:
NS_DECL_ISUPPORTS_INHERITED
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(WindowScriptTimeoutHandler,
ScriptTimeoutHandler)
WindowScriptTimeoutHandler(JSContext* aCx, nsIGlobalObject* aGlobal,
const nsAString& aExpression)
: ScriptTimeoutHandler(aCx, aGlobal, aExpression),
mInitiatingScript(ScriptLoader::GetActiveScript(aCx)) {}
MOZ_CAN_RUN_SCRIPT virtual bool Call(const char* aExecutionReason) override;
private:
virtual ~WindowScriptTimeoutHandler() = default;
// Initiating script for use when evaluating mExpr on the main thread.
RefPtr<LoadedScript> mInitiatingScript;
};
NS_IMPL_CYCLE_COLLECTION_INHERITED(WindowScriptTimeoutHandler,
ScriptTimeoutHandler, mInitiatingScript)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WindowScriptTimeoutHandler)
NS_INTERFACE_MAP_END_INHERITING(ScriptTimeoutHandler)
NS_IMPL_ADDREF_INHERITED(WindowScriptTimeoutHandler, ScriptTimeoutHandler)
NS_IMPL_RELEASE_INHERITED(WindowScriptTimeoutHandler, ScriptTimeoutHandler)
bool WindowScriptTimeoutHandler::Call(const char* aExecutionReason) {
// 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(mGlobal, aExecutionReason, true);
JS::CompileOptions options(aes.cx());
options.setFileAndLine(mFileName.get(), mLineNo);
options.setNoScriptRval(true);
options.setIntroductionType("domTimer");
JS::Rooted<JSObject*> global(aes.cx(), mGlobal->GetGlobalJSObject());
{
JSExecutionContext exec(aes.cx(), global, options);
nsresult rv = exec.Compile(mExpr);
JS::Rooted<JSScript*> script(aes.cx(), exec.MaybeGetScript());
if (script) {
if (mInitiatingScript) {
mInitiatingScript->AssociateWithScript(script);
}
rv = exec.ExecScript();
}
if (rv == NS_SUCCESS_DOM_SCRIPT_EVALUATION_THREW_UNCATCHABLE) {
return false;
}
}
return true;
};
nsGlobalWindowInner* nsGlobalWindowInner::InnerForSetTimeoutOrInterval(
ErrorResult& aError) {
nsGlobalWindowOuter* outer = GetOuterWindowInternal();
nsGlobalWindowInner* currentInner =
outer ? outer->GetCurrentInnerWindowInternal() : this;
// 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 HasActiveDocument() ? currentInner : nullptr;
}
int32_t nsGlobalWindowInner::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 nsGlobalWindowInner::SetTimeout(JSContext* aCx,
const nsAString& aHandler,
int32_t aTimeout,
const Sequence<JS::Value>& /* unused */,
ErrorResult& aError) {
return SetTimeoutOrInterval(aCx, aHandler, aTimeout, false, aError);
}
int32_t nsGlobalWindowInner::SetInterval(JSContext* aCx, Function& aFunction,
const int32_t aTimeout,
const Sequence<JS::Value>& aArguments,
ErrorResult& aError) {
return SetTimeoutOrInterval(aCx, aFunction, aTimeout, aArguments, true,
aError);
}
int32_t nsGlobalWindowInner::SetInterval(
JSContext* aCx, const nsAString& aHandler, const int32_t aTimeout,
const Sequence<JS::Value>& /* unused */, ErrorResult& aError) {
return SetTimeoutOrInterval(aCx, aHandler, aTimeout, true, aError);
}
int32_t nsGlobalWindowInner::SetTimeoutOrInterval(
JSContext* aCx, Function& aFunction, int32_t aTimeout,
const Sequence<JS::Value>& aArguments, bool aIsInterval,
ErrorResult& aError) {
nsGlobalWindowInner* inner = InnerForSetTimeoutOrInterval(aError);
if (!inner) {
return -1;
}
if (inner != this) {
RefPtr<nsGlobalWindowInner> innerRef(inner);
return innerRef->SetTimeoutOrInterval(aCx, aFunction, aTimeout, aArguments,
aIsInterval, aError);
}
DebuggerNotificationDispatch(
this, aIsInterval ? DebuggerNotificationType::SetInterval
: DebuggerNotificationType::SetTimeout);
if (!GetContextInternal() || !HasJSGlobal()) {
// This window was already closed, or never properly initialized,
// don't let a timer be scheduled on such a window.
aError.Throw(NS_ERROR_NOT_INITIALIZED);
return 0;
}
nsTArray<JS::Heap<JS::Value>> args;
if (!args.AppendElements(aArguments, fallible)) {
aError.Throw(NS_ERROR_OUT_OF_MEMORY);
return 0;
}
RefPtr<TimeoutHandler> handler =
new CallbackTimeoutHandler(aCx, this, &aFunction, std::move(args));
int32_t result;
aError =
mTimeoutManager->SetTimeout(handler, aTimeout, aIsInterval,
Timeout::Reason::eTimeoutOrInterval, &result);
return result;
}
int32_t nsGlobalWindowInner::SetTimeoutOrInterval(JSContext* aCx,
const nsAString& aHandler,
int32_t aTimeout,
bool aIsInterval,
ErrorResult& aError) {
nsGlobalWindowInner* inner = InnerForSetTimeoutOrInterval(aError);
if (!inner) {
return -1;
}
if (inner != this) {
RefPtr<nsGlobalWindowInner> innerRef(inner);
return innerRef->SetTimeoutOrInterval(aCx, aHandler, aTimeout, aIsInterval,
aError);
}
DebuggerNotificationDispatch(
this, aIsInterval ? DebuggerNotificationType::SetInterval
: DebuggerNotificationType::SetTimeout);
if (!GetContextInternal() || !HasJSGlobal()) {
// This window was already closed, or never properly initialized,
// don't let a timer be scheduled on such a window.
aError.Throw(NS_ERROR_NOT_INITIALIZED);
return 0;
}
bool allowEval = false;
aError = CSPEvalChecker::CheckForWindow(aCx, this, aHandler, &allowEval);
if (NS_WARN_IF(aError.Failed()) || !allowEval) {
return 0;
}
RefPtr<TimeoutHandler> handler =
new WindowScriptTimeoutHandler(aCx, this, aHandler);
int32_t result;
aError =
mTimeoutManager->SetTimeout(handler, aTimeout, aIsInterval,
Timeout::Reason::eTimeoutOrInterval, &result);
return result;
}
static const char* GetTimeoutReasonString(Timeout* aTimeout) {
switch (aTimeout->mReason) {
case Timeout::Reason::eTimeoutOrInterval:
if (aTimeout->mIsInterval) {
return "setInterval handler";
}
return "setTimeout handler";
case Timeout::Reason::eIdleCallbackTimeout:
return "setIdleCallback handler (timed out)";
default:
MOZ_CRASH("Unexpected enum value");
return "";
}
}
bool nsGlobalWindowInner::RunTimeoutHandler(Timeout* aTimeout,
nsIScriptContext* aScx) {
// Hold on to the timeout in case mExpr or mFunObj releases its
// doc.
// XXXbz Our caller guarantees it'll hold on to the timeout (because
// we're MOZ_CAN_RUN_SCRIPT), so we can probably stop doing that...
RefPtr<Timeout> timeout = aTimeout;
Timeout* last_running_timeout = mTimeoutManager->BeginRunningTimeout(timeout);
timeout->mRunning = true;
// Push this timeout's popup control state, which should only be
// enabled the first time a timeout fires that was created while
// popups were enabled and with a delay less than
// "dom.disable_open_click_delay".
AutoPopupStatePusher popupStatePusher(timeout->mPopupState);
// Clear the timeout's popup state, if any, to prevent interval
// timeouts from repeatedly opening poups.
timeout->mPopupState = PopupBlocker::openAbused;
uint32_t nestingLevel = TimeoutManager::GetNestingLevel();
TimeoutManager::SetNestingLevel(timeout->mNestingLevel);
const char* reason = GetTimeoutReasonString(timeout);
nsCString str;
if (profiler_can_accept_markers()) {
TimeDuration originalInterval = timeout->When() - timeout->SubmitTime();
str.Append(reason);
str.Append(" with interval ");
str.AppendInt(int(originalInterval.ToMilliseconds()));
str.Append("ms: ");
nsCString handlerDescription;
timeout->mScriptHandler->GetDescription(handlerDescription);
str.Append(handlerDescription);
}
AUTO_PROFILER_MARKER_TEXT("setTimeout callback", DOM,
MarkerOptions(MarkerStack::TakeBacktrace(
timeout->TakeProfilerBacktrace()),
MarkerInnerWindowId(mWindowID)),
str);
bool abortIntervalHandler;
{
RefPtr<TimeoutHandler> handler(timeout->mScriptHandler);
CallbackDebuggerNotificationGuard guard(
this, timeout->mIsInterval
? DebuggerNotificationType::SetIntervalCallback
: DebuggerNotificationType::SetTimeoutCallback);
abortIntervalHandler = !handler->Call(reason);
}
// 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.
TimeoutManager::SetNestingLevel(nestingLevel);
mTimeoutManager->EndRunningTimeout(last_running_timeout);
timeout->mRunning = false;
return timeout->mCleared;
}
//*****************************************************************************
// nsGlobalWindowInner: Helper Functions
//*****************************************************************************
already_AddRefed<nsIDocShellTreeOwner> nsGlobalWindowInner::GetTreeOwner() {
FORWARD_TO_OUTER(GetTreeOwner, (), nullptr);
}
already_AddRefed<nsIWebBrowserChrome>
nsGlobalWindowInner::GetWebBrowserChrome() {
nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(treeOwner);
return browserChrome.forget();
}
nsIScrollableFrame* nsGlobalWindowInner::GetScrollFrame() {
FORWARD_TO_OUTER(GetScrollFrame, (), nullptr);
}
bool nsGlobalWindowInner::IsPrivateBrowsing() {
nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(GetDocShell());
return loadContext && loadContext->UsePrivateBrowsing();
}
void nsGlobalWindowInner::FlushPendingNotifications(FlushType aType) {
if (mDoc) {
mDoc->FlushPendingNotifications(aType);
}
}
void nsGlobalWindowInner::EnableDeviceSensor(uint32_t aType) {
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 nsGlobalWindowInner::DisableDeviceSensor(uint32_t aType) {
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)
void nsGlobalWindowInner::EnableOrientationChangeListener() {
if (!nsContentUtils::ShouldResistFingerprinting(GetDocShell()) &&
!mOrientationChangeObserver) {
mOrientationChangeObserver = MakeUnique<WindowOrientationObserver>(this);
}
}
void nsGlobalWindowInner::DisableOrientationChangeListener() {
mOrientationChangeObserver = nullptr;
}
#endif
void nsGlobalWindowInner::SetHasGamepadEventListener(
bool aHasGamepad /* = true*/) {
mHasGamepad = aHasGamepad;
if (aHasGamepad) {
EnableGamepadUpdates();
}
}
void nsGlobalWindowInner::NotifyDetectXRRuntimesCompleted() {
if (!mXRRuntimeDetectionInFlight) {
return;
}
mXRRuntimeDetectionInFlight = false;
if (mXRPermissionRequestInFlight) {
return;
}
gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
bool supported = vm->RuntimeSupportsVR();
if (!supported) {
// A VR runtime was not installed; we can suppress
// the permission prompt
OnXRPermissionRequestCancel();
return;
}
// A VR runtime was found. Display a permission prompt before
// allowing it to be accessed.
// Connect to the VRManager in order to receive the runtime
// detection results.
mXRPermissionRequestInFlight = true;
RefPtr<XRPermissionRequest> request =
new XRPermissionRequest(this, WindowID());
Unused << NS_WARN_IF(NS_FAILED(request->Start()));
}
void nsGlobalWindowInner::RequestXRPermission() {
if (IsDying()) {
// Do not proceed if the window is dying, as that will result
// in leaks of objects that get re-allocated after FreeInnerObjects
// has been called, including mVREventObserver.
return;
}
if (mXRPermissionGranted) {
// Don't prompt redundantly once permission to
// access XR devices has been granted.
OnXRPermissionRequestAllow();
return;
}
if (mXRRuntimeDetectionInFlight || mXRPermissionRequestInFlight) {
// Don't allow multiple simultaneous permissions requests;
return;
}
// Before displaying a permission prompt, detect
// if there is any VR runtime installed.
gfx::VRManagerChild* vm = gfx::VRManagerChild::Get();
mXRRuntimeDetectionInFlight = true;
EnableVRUpdates();
vm->DetectRuntimes();
}
void nsGlobalWindowInner::OnXRPermissionRequestAllow() {
mXRPermissionRequestInFlight = false;
if (IsDying()) {
// The window may have started dying while the permission request
// is in flight.
// Do not proceed if the window is dying, as that will result
// in leaks of objects that get re-allocated after FreeInnerObjects
// has been called, including mNavigator.
return;
}
mXRPermissionGranted = true;
NotifyHasXRSession();
dom::Navigator* nav = Navigator();
MOZ_ASSERT(nav != nullptr);
nav->OnXRPermissionRequestAllow();
}
void nsGlobalWindowInner::OnXRPermissionRequestCancel() {
mXRPermissionRequestInFlight = false;
if (IsDying()) {
// The window may have started dying while the permission request
// is in flight.
// Do not proceed if the window is dying, as that will result
// in leaks of objects that get re-allocated after FreeInnerObjects
// has been called, including mNavigator.
return;
}
dom::Navigator* nav = Navigator();
MOZ_ASSERT(nav != nullptr);
nav->OnXRPermissionRequestCancel();
}
void nsGlobalWindowInner::EventListenerAdded(nsAtom* aType) {
if (aType == nsGkAtoms::onvrdisplayactivate ||
aType == nsGkAtoms::onvrdisplayconnect ||
aType == nsGkAtoms::onvrdisplaydeactivate ||
aType == nsGkAtoms::onvrdisplaydisconnect ||
aType == nsGkAtoms::onvrdisplaypresentchange) {
RequestXRPermission();
}
if (aType == nsGkAtoms::onvrdisplayactivate) {
mHasVRDisplayActivateEvents = true;
}
if ((aType == nsGkAtoms::onunload || aType == nsGkAtoms::onbeforeunload) &&
mWindowGlobalChild) {
if (++mUnloadOrBeforeUnloadListenerCount == 1) {
mWindowGlobalChild->BlockBFCacheFor(BFCacheStatus::UNLOAD_LISTENER);
}
if (aType == nsGkAtoms::onbeforeunload &&
(!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) {
mWindowGlobalChild->BeforeUnloadAdded();
MOZ_ASSERT(mWindowGlobalChild->BeforeUnloadListeners() > 0);
}
}
// We need to initialize localStorage in order to receive notifications.
if (aType == nsGkAtoms::onstorage) {
ErrorResult rv;
GetLocalStorage(rv);
rv.SuppressException();
if (NextGenLocalStorageEnabled() && mLocalStorage &&
mLocalStorage->Type() == Storage::eLocalStorage) {
auto object = static_cast<LSObject*>(mLocalStorage.get());
Unused << NS_WARN_IF(NS_FAILED(object->EnsureObserver()));
}
}
}
void nsGlobalWindowInner::EventListenerRemoved(nsAtom* aType) {
if ((aType == nsGkAtoms::onunload || aType == nsGkAtoms::onbeforeunload) &&
mWindowGlobalChild) {
MOZ_ASSERT(mUnloadOrBeforeUnloadListenerCount > 0);
if (--mUnloadOrBeforeUnloadListenerCount == 0) {
mWindowGlobalChild->UnblockBFCacheFor(BFCacheStatus::UNLOAD_LISTENER);
}
if (aType == nsGkAtoms::onbeforeunload &&
(!mDoc || !(mDoc->GetSandboxFlags() & SANDBOXED_MODALS))) {
mWindowGlobalChild->BeforeUnloadRemoved();
MOZ_ASSERT(mWindowGlobalChild->BeforeUnloadListeners() >= 0);
}
}
if (aType == nsGkAtoms::onstorage) {
if (NextGenLocalStorageEnabled() && mLocalStorage &&
mLocalStorage->Type() == Storage::eLocalStorage &&
// The remove event is fired even if this isn't the last listener, so
// only remove if there are no other listeners left.
mListenerManager &&
!mListenerManager->HasListenersFor(nsGkAtoms::onstorage)) {
auto object = static_cast<LSObject*>(mLocalStorage.get());
object->DropObserver();
}
}
}
void nsGlobalWindowInner::NotifyHasXRSession() {
if (IsDying()) {
// Do not proceed if the window is dying, as that will result
// in leaks of objects that get re-allocated after FreeInnerObjects
// has been called, including mVREventObserver.
return;
}
if (mWindowGlobalChild && !mHasXRSession) {
mWindowGlobalChild->BlockBFCacheFor(BFCacheStatus::HAS_USED_VR);
}
mHasXRSession = true;
EnableVRUpdates();
}
bool nsGlobalWindowInner::HasUsedVR() const {
// Returns true only if content has enumerated and activated
// XR devices. Detection of XR runtimes without activation
// will not cause true to be returned.
return mHasXRSession;
}
bool nsGlobalWindowInner::IsVRContentDetected() const {
// Returns true only if the content will respond to
// the VRDisplayActivate event.
return mHasVRDisplayActivateEvents;
}
bool nsGlobalWindowInner::IsVRContentPresenting() const {
for (const auto& display : mVRDisplays) {
if (display->IsAnyPresenting(gfx::kVRGroupAll)) {
return true;
}
}
return false;
}
void nsGlobalWindowInner::AddSizeOfIncludingThis(
nsWindowSizes& aWindowSizes) const {
aWindowSizes.mDOMSizes.mDOMOtherSize +=
aWindowSizes.mState.mMallocSizeOf(this);
aWindowSizes.mDOMSizes.mDOMOtherSize +=
nsIGlobalObject::ShallowSizeOfExcludingThis(
aWindowSizes.mState.mMallocSizeOf);
EventListenerManager* elm = GetExistingListenerManager();
if (elm) {
aWindowSizes.mDOMSizes.mDOMOtherSize +=
elm->SizeOfIncludingThis(aWindowSizes.mState.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() == this) {
mDoc->DocAddSizeOfIncludingThis(aWindowSizes);
}
}
if (mNavigator) {
aWindowSizes.mDOMSizes.mDOMOtherSize +=
mNavigator->SizeOfIncludingThis(aWindowSizes.mState.mMallocSizeOf);
}
ForEachEventTargetObject([&](DOMEventTargetHelper* et, bool* aDoneOut) {
if (nsCOMPtr<nsISizeOfEventTarget> iSizeOf = do_QueryObject(et)) {
aWindowSizes.mDOMSizes.mDOMEventTargetsSize +=
iSizeOf->SizeOfEventTargetIncludingThis(
aWindowSizes.mState.mMallocSizeOf);
}
if (EventListenerManager* elm = et->GetExistingListenerManager()) {
aWindowSizes.mDOMEventListenersCount += elm->ListenerCount();
}
++aWindowSizes.mDOMEventTargetsCount;
});
if (mPerformance) {
aWindowSizes.mDOMSizes.mDOMPerformanceUserEntries =
mPerformance->SizeOfUserEntries(aWindowSizes.mState.mMallocSizeOf);
aWindowSizes.mDOMSizes.mDOMPerformanceResourceEntries =
mPerformance->SizeOfResourceEntries(aWindowSizes.mState.mMallocSizeOf);
aWindowSizes.mDOMSizes.mDOMPerformanceEventEntries =
mPerformance->SizeOfEventEntries(aWindowSizes.mState.mMallocSizeOf);
}
}
void nsGlobalWindowInner::RegisterDataDocumentForMemoryReporting(
Document* aDocument) {
aDocument->SetAddedToMemoryReportAsDataDocument();
mDataDocumentsForMemoryReporting.AppendElement(
do_GetWeakReference(aDocument));
}
void nsGlobalWindowInner::UnregisterDataDocumentForMemoryReporting(
Document* aDocument) {
nsWeakPtr doc = do_GetWeakReference(aDocument);
MOZ_ASSERT(mDataDocumentsForMemoryReporting.Contains(doc));
mDataDocumentsForMemoryReporting.RemoveElement(doc);
}
void nsGlobalWindowInner::CollectDOMSizesForDataDocuments(
nsWindowSizes& aSize) const {
for (const nsWeakPtr& ptr : mDataDocumentsForMemoryReporting) {
if (nsCOMPtr<Document> doc = do_QueryReferent(ptr)) {
doc->DocAddSizeOfIncludingThis(aSize);
}
}
}
void nsGlobalWindowInner::AddGamepad(GamepadHandle aHandle, Gamepad* aGamepad) {
// 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.InsertOrUpdate(aHandle, RefPtr{aGamepad});
}
void nsGlobalWindowInner::RemoveGamepad(GamepadHandle aHandle) {
RefPtr<Gamepad> gamepad;
if (!mGamepads.Get(aHandle, getter_AddRefs(gamepad))) {
return;
}
// Free up the index we were using so it can be reused
mGamepadIndexSet.Remove(gamepad->Index());
mGamepads.Remove(aHandle);
}
void nsGlobalWindowInner::GetGamepads(nsTArray<RefPtr<Gamepad>>& aGamepads) {
aGamepads.Clear();
// navigator.getGamepads() always returns an empty array when
// privacy.resistFingerprinting is true.
if (nsContentUtils::ShouldResistFingerprinting(GetDocShell())) {
return;
}
// mGamepads.Count() may not be sufficient, but it's not harmful.
aGamepads.SetCapacity(mGamepads.Count());
for (const auto& entry : mGamepads) {
Gamepad* gamepad = entry.GetWeak();
aGamepads.EnsureLengthAtLeast(gamepad->Index() + 1);
aGamepads[gamepad->Index()] = gamepad;
}
}
already_AddRefed<Gamepad> nsGlobalWindowInner::GetGamepad(
GamepadHandle aHandle) {
RefPtr<Gamepad> gamepad;
if (mGamepads.Get(aHandle, getter_AddRefs(gamepad))) {
return gamepad.forget();
}
return nullptr;
}
void nsGlobalWindowInner::SetHasSeenGamepadInput(bool aHasSeen) {
mHasSeenGamepadInput = aHasSeen;
}
bool nsGlobalWindowInner::HasSeenGamepadInput() { return mHasSeenGamepadInput; }
void nsGlobalWindowInner::SyncGamepadState() {
if (mHasSeenGamepadInput) {
RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
for (const auto& entry : mGamepads) {
gamepadManager->SyncGamepadState(entry.GetKey(), this, entry.GetWeak());
}
}
}
void nsGlobalWindowInner::StopGamepadHaptics() {
if (mHasSeenGamepadInput) {
RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
gamepadManager->StopHaptics();
}
}
bool nsGlobalWindowInner::UpdateVRDisplays(
nsTArray<RefPtr<mozilla::dom::VRDisplay>>& aDevices) {
VRDisplay::UpdateVRDisplays(mVRDisplays, this);
aDevices = mVRDisplays.Clone();
return true;
}
void nsGlobalWindowInner::NotifyActiveVRDisplaysChanged() {
if (mNavigator) {
mNavigator->NotifyActiveVRDisplaysChanged();
}
}
void nsGlobalWindowInner::NotifyPresentationGenerationChanged(
uint32_t aDisplayID) {
for (const auto& display : mVRDisplays) {
if (display->DisplayId() == aDisplayID) {
display->OnPresentationGenerationChanged();
}
}
}
void nsGlobalWindowInner::DispatchVRDisplayActivate(
uint32_t aDisplayID, mozilla::dom::VRDisplayEventReason aReason) {
// Ensure that our list of displays is up to date
VRDisplay::UpdateVRDisplays(mVRDisplays, this);
// Search for the display identified with aDisplayID and fire the
// event if found.
for (const auto& display : mVRDisplays) {
if (display->DisplayId() == aDisplayID) {
if (aReason != VRDisplayEventReason::Navigation &&
display->IsAnyPresenting(gfx::kVRGroupContent)) {
// We only want to trigger this event if nobody is presenting to the
// display already or when a page is loaded by navigating away
// from a page with an active VR Presentation.
continue;
}
VRDisplayEventInit init;
init.mBubbles = false;
init.mCancelable = false;
init.mDisplay = display;
init.mReason.Construct(aReason);
RefPtr<VRDisplayEvent> event =
VRDisplayEvent::Constructor(this, u"vrdisplayactivate"_ns, init);
// vrdisplayactivate is a trusted event, allowing VRDisplay.requestPresent
// to be used in response to link traversal, user request (chrome UX), and
// HMD mounting detection sensors.
event->SetTrusted(true);
// VRDisplay.requestPresent normally requires a user gesture; however, an
// exception is made to allow it to be called in response to
// vrdisplayactivate during VR link traversal.
display->StartHandlingVRNavigationEvent();
DispatchEvent(*event);
display->StopHandlingVRNavigationEvent();
// Once we dispatch the event, we must not access any members as an event
// listener can do anything, including closing windows.
return;
}
}
}
void nsGlobalWindowInner::DispatchVRDisplayDeactivate(
uint32_t aDisplayID, mozilla::dom::VRDisplayEventReason aReason) {
// Ensure that our list of displays is up to date
VRDisplay::UpdateVRDisplays(mVRDisplays, this);
// Search for the display identified with aDisplayID and fire the
// event if found.
for (const auto& display : mVRDisplays) {
if (display->DisplayId() == aDisplayID && display->IsPresenting()) {
// We only want to trigger this event to content that is presenting to
// the display already.
VRDisplayEventInit init;
init.mBubbles = false;
init.mCancelable = false;
init.mDisplay = display;
init.mReason.Construct(aReason);
RefPtr<VRDisplayEvent> event =
VRDisplayEvent::Constructor(this, u"vrdisplaydeactivate"_ns, init);
event->SetTrusted(true);
DispatchEvent(*event);
// Once we dispatch the event, we must not access any members as an event
// listener can do anything, including closing windows.
return;
}
}
}
void nsGlobalWindowInner::DispatchVRDisplayConnect(uint32_t aDisplayID) {
// Ensure that our list of displays is up to date
VRDisplay::UpdateVRDisplays(mVRDisplays, this);
// Search for the display identified with aDisplayID and fire the
// event if found.
for (const auto& display : mVRDisplays) {
if (display->DisplayId() == aDisplayID) {
// Fire event even if not presenting to the display.
VRDisplayEventInit init;
init.mBubbles = false;
init.mCancelable = false;
init.mDisplay = display;
// VRDisplayEvent.reason is not set for vrdisplayconnect
RefPtr<VRDisplayEvent> event =
VRDisplayEvent::Constructor(this, u"vrdisplayconnect"_ns, init);
event->SetTrusted(true);
DispatchEvent(*event);
// Once we dispatch the event, we must not access any members as an event
// listener can do anything, including closing windows.
return;
}
}
}
void nsGlobalWindowInner::DispatchVRDisplayDisconnect(uint32_t aDisplayID) {
// Ensure that our list of displays is up to date
VRDisplay::UpdateVRDisplays(mVRDisplays, this);
// Search for the display identified with aDisplayID and fire the
// event if found.
for (const auto& display : mVRDisplays) {
if (display->DisplayId() == aDisplayID) {
// Fire event even if not presenting to the display.
VRDisplayEventInit init;
init.mBubbles = false;
init.mCancelable = false;
init.mDisplay = display;
// VRDisplayEvent.reason is not set for vrdisplaydisconnect
RefPtr<VRDisplayEvent> event =
VRDisplayEvent::Constructor(this, u"vrdisplaydisconnect"_ns, init);
event->SetTrusted(true);
DispatchEvent(*event);
// Once we dispatch the event, we must not access any members as an event
// listener can do anything, including closing windows.
return;
}
}
}
void nsGlobalWindowInner::DispatchVRDisplayPresentChange(uint32_t aDisplayID) {
// Ensure that our list of displays is up to date
VRDisplay::UpdateVRDisplays(mVRDisplays, this);
// Search for the display identified with aDisplayID and fire the
// event if found.
for (const auto& display : mVRDisplays) {
if (display->DisplayId() == aDisplayID) {
// Fire event even if not presenting to the display.
VRDisplayEventInit init;
init.mBubbles = false;
init.mCancelable = false;
init.mDisplay = display;
// VRDisplayEvent.reason is not set for vrdisplaypresentchange
RefPtr<VRDisplayEvent> event =
VRDisplayEvent::Constructor(this, u"vrdisplaypresentchange"_ns, init);
event->SetTrusted(true);
DispatchEvent(*event);
// Once we dispatch the event, we must not access any members as an event
// listener can do anything, including closing windows.
return;
}
}
}
enum WindowState {
// These constants need to match the constants in Window.webidl
STATE_MAXIMIZED = 1,
STATE_MINIMIZED = 2,
STATE_NORMAL = 3,
STATE_FULLSCREEN = 4
};
uint16_t nsGlobalWindowInner::WindowState() {
nsCOMPtr<nsIWidget> widget = GetMainWidget();
int32_t mode = widget ? widget->SizeMode() : 0;
switch (mode) {
case nsSizeMode_Minimized:
return STATE_MINIMIZED;
case nsSizeMode_Maximized:
return STATE_MAXIMIZED;
case nsSizeMode_Fullscreen:
return STATE_FULLSCREEN;
case nsSizeMode_Normal:
return STATE_NORMAL;
default:
NS_WARNING("Illegal window state for this chrome window");
break;
}
return STATE_NORMAL;
}
bool nsGlobalWindowInner::IsFullyOccluded() {
nsCOMPtr<nsIWidget> widget = GetMainWidget();
return widget && widget->IsFullyOccluded();
}
void nsGlobalWindowInner::Maximize() {
nsCOMPtr<nsIWidget> widget = GetMainWidget();
if (widget) {
widget->SetSizeMode(nsSizeMode_Maximized);
}
}
void nsGlobalWindowInner::Minimize() {
nsCOMPtr<nsIWidget> widget = GetMainWidget();
if (widget) {
widget->SetSizeMode(nsSizeMode_Minimized);
}
}
void nsGlobalWindowInner::Restore() {
nsCOMPtr<nsIWidget> widget = GetMainWidget();
if (widget) {
widget->SetSizeMode(nsSizeMode_Normal);
}
}
void nsGlobalWindowInner::GetWorkspaceID(nsAString& workspaceID) {
nsCOMPtr<nsIWidget> widget = GetMainWidget();
workspaceID.Truncate();
if (widget) {
return widget->GetWorkspaceID(workspaceID);
}
}
void nsGlobalWindowInner::MoveToWorkspace(const nsAString& workspaceID) {
nsCOMPtr<nsIWidget> widget = GetMainWidget();
if (widget) {
widget->MoveToWorkspace(workspaceID);
}
}
void nsGlobalWindowInner::GetAttention(ErrorResult& aResult) {
return GetAttentionWithCycleCount(-1, aResult);
}
void nsGlobalWindowInner::GetAttentionWithCycleCount(int32_t aCycleCount,
ErrorResult& aError) {
nsCOMPtr<nsIWidget> widget = GetMainWidget();
if (widget) {
aError = widget->GetAttention(aCycleCount);
}
}
already_AddRefed<Promise> nsGlobalWindowInner::PromiseDocumentFlushed(
PromiseDocumentFlushedCallback& aCallback, ErrorResult& aError) {
MOZ_RELEASE_ASSERT(IsChromeWindow());
if (!IsCurrentInnerWindow()) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
if (!mDoc || mIteratingDocumentFlushedResolvers) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
PresShell* presShell = mDoc->GetPresShell();
if (!presShell) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
// We need to associate the lifetime of the Promise to the lifetime
// of the caller's global. That way, if the window we're observing
// refresh driver ticks on goes away before our observer is fired,
// we can still resolve the Promise.
nsIGlobalObject* global = GetIncumbentGlobal();
if (!global) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
RefPtr<Promise> resultPromise = Promise::Create(global, aError);
if (aError.Failed()) {
return nullptr;
}
UniquePtr<PromiseDocumentFlushedResolver> flushResolver(
new PromiseDocumentFlushedResolver(resultPromise, aCallback));
if (!presShell->NeedStyleFlush() && !presShell->NeedLayoutFlush()) {
flushResolver->Call();
return resultPromise.forget();
}
if (!TryToObserveRefresh()) {
aError.Throw(NS_ERROR_FAILURE);
return nullptr;
}
mDocumentFlushedResolvers.AppendElement(std::move(flushResolver));
return resultPromise.forget();
}
bool nsGlobalWindowInner::TryToObserveRefresh() {
if (mObservingRefresh) {
return true;
}
if (!mDoc) {
return false;
}
nsPresContext* pc = mDoc->GetPresContext();
if (!pc) {
return false;
}
mObservingRefresh = true;
auto observer = MakeRefPtr<ManagedPostRefreshObserver>(
pc, [win = RefPtr{this}](bool aWasCanceled) {
if (win->MaybeCallDocumentFlushedResolvers(
/* aUntilExhaustion = */ aWasCanceled)) {
return ManagedPostRefreshObserver::Unregister::No;
}
win->mObservingRefresh = false;
return ManagedPostRefreshObserver::Unregister::Yes;
});
pc->RegisterManagedPostRefreshObserver(observer.get());
return mObservingRefresh;
}
void nsGlobalWindowInner::CallDocumentFlushedResolvers(bool aUntilExhaustion) {
while (true) {
{
// To coalesce MicroTask checkpoints inside callback call, enclose the
// inner loop with nsAutoMicroTask, and perform a MicroTask checkpoint
// after the loop.
nsAutoMicroTask mt;
mIteratingDocumentFlushedResolvers = true;
auto resolvers = std::move(mDocumentFlushedResolvers);
for (const auto& resolver : resolvers) {
resolver->Call();
}
mIteratingDocumentFlushedResolvers = false;
}
// Leaving nsAutoMicroTask above will perform MicroTask checkpoint, and
// Promise callbacks there may create mDocumentFlushedResolvers items.
// If there's no new resolvers, or we're not exhausting the queue, there's
// nothing to do (we'll keep observing if there's any new observer).
//
// Otherwise, keep looping to call all promises. This case can happen while
// destroying the window. This violates the constraint that the
// promiseDocumentFlushed callback only ever run when no flush is needed,
// but it's necessary to resolve the Promise returned by that.
if (!aUntilExhaustion || mDocumentFlushedResolvers.IsEmpty()) {
break;
}
}
}
bool nsGlobalWindowInner::MaybeCallDocumentFlushedResolvers(
bool aUntilExhaustion) {
MOZ_ASSERT(mDoc);
PresShell* presShell = mDoc->GetPresShell();
if (!presShell || aUntilExhaustion) {
CallDocumentFlushedResolvers(/* aUntilExhaustion = */ true);
return false;
}
if (presShell->NeedStyleFlush() || presShell->NeedLayoutFlush()) {
// By the time our observer fired, something has already invalidated
// style or layout - or perhaps we're still in the middle of a flush that
// was interrupted. In either case, we'll wait until the next refresh driver
// tick instead and try again.
return true;
}
CallDocumentFlushedResolvers(/* aUntilExhaustion = */ false);
return !mDocumentFlushedResolvers.IsEmpty();
}
already_AddRefed<nsWindowRoot> nsGlobalWindowInner::GetWindowRoot(
mozilla::ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetWindowRootOuter, (), aError, nullptr);
}
void nsGlobalWindowInner::SetCursor(const nsACString& aCursor,
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SetCursorOuter, (aCursor, aError), aError, );
}
NS_IMETHODIMP
nsGlobalWindowInner::GetBrowserDOMWindow(nsIBrowserDOMWindow** aBrowserWindow) {
MOZ_RELEASE_ASSERT(IsChromeWindow());
ErrorResult rv;
NS_IF_ADDREF(*aBrowserWindow = GetBrowserDOMWindow(rv));
return rv.StealNSResult();
}
nsIBrowserDOMWindow* nsGlobalWindowInner::GetBrowserDOMWindow(
ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(GetBrowserDOMWindowOuter, (), aError, nullptr);
}
void nsGlobalWindowInner::SetBrowserDOMWindow(
nsIBrowserDOMWindow* aBrowserWindow, ErrorResult& aError) {
FORWARD_TO_OUTER_OR_THROW(SetBrowserDOMWindowOuter, (aBrowserWindow),
aError, );
}
void nsGlobalWindowInner::NotifyDefaultButtonLoaded(Element& aDefaultButton,
ErrorResult& aError) {
#ifdef MOZ_XUL
// Don't snap to a disabled button.
nsCOMPtr<nsIDOMXULControlElement> xulControl = aDefaultButton.AsXULControl();
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::FromAppUnitsToNearest(
frame->GetScreenRectInAppUnits(),
frame->PresContext()->AppUnitsPerDevPixel());
// 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
}
ChromeMessageBroadcaster* nsGlobalWindowInner::MessageManager() {
MOZ_ASSERT(IsChromeWindow());
if (!mChromeFields.mMessageManager) {
RefPtr<ChromeMessageBroadcaster> globalMM =
nsFrameMessageManager::GetGlobalMessageManager();
mChromeFields.mMessageManager = new ChromeMessageBroadcaster(globalMM);
}
return mChromeFields.mMessageManager;
}
ChromeMessageBroadcaster* nsGlobalWindowInner::GetGroupMessageManager(
const nsAString& aGroup) {
MOZ_ASSERT(IsChromeWindow());
return mChromeFields.mGroupMessageManagers
.LookupOrInsertWith(
aGroup,
[&] {
return MakeAndAddRef<ChromeMessageBroadcaster>(MessageManager());
})
.get();
}
void nsGlobalWindowInner::InitWasOffline() { mWasOffline = NS_IsOffline(); }
#if defined(MOZ_WIDGET_ANDROID)
int16_t nsGlobalWindowInner::Orientation(CallerType aCallerType) const {
return nsContentUtils::ResistFingerprinting(aCallerType)
? 0
: WindowOrientationObserver::OrientationAngle();
}
#endif
already_AddRefed<Console> nsGlobalWindowInner::GetConsole(JSContext* aCx,
ErrorResult& aRv) {
if (!mConsole) {
mConsole = Console::Create(aCx, this, aRv);
if (NS_WARN_IF(aRv.Failed())) {
return nullptr;
}
}
RefPtr<Console> console = mConsole;
return console.forget();
}
bool nsGlobalWindowInner::IsSecureContext() const {
JS::Realm* realm = js::GetNonCCWObjectRealm(GetWrapperPreserveColor());
return JS::GetIsSecureContext(realm);
}
External* nsGlobalWindowInner::GetExternal(ErrorResult& aRv) {
if (!mExternal) {
mExternal = new dom::External(ToSupports(this));
}
return mExternal;
}
void nsGlobalWindowInner::GetSidebar(OwningExternalOrWindowProxy& aResult,
ErrorResult& aRv) {
// First check for a named frame named "sidebar"
RefPtr<BrowsingContext> domWindow = GetChildWindow(u"sidebar"_ns);
if (domWindow) {
aResult.SetAsWindowProxy() = std::move(domWindow);
return;
}
RefPtr<External> external = GetExternal(aRv);
if (external) {
aResult.SetAsExternal() = external;
}
}
void nsGlobalWindowInner::ClearDocumentDependentSlots(JSContext* aCx) {
// If JSAPI OOMs here, there is basically nothing we can do to recover safely.
if (!Window_Binding::ClearCachedDocumentValue(aCx, this) ||
!Window_Binding::ClearCachedPerformanceValue(aCx, this)) {
MOZ_CRASH("Unhandlable OOM while clearing document dependent slots.");
}
}
/* static */
JSObject* nsGlobalWindowInner::CreateNamedPropertiesObject(
JSContext* aCx, JS::Handle<JSObject*> aProto) {
return WindowNamedPropertiesHandler::Create(aCx, aProto);
}
void nsGlobalWindowInner::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)) {
aError.Throw(NS_ERROR_FAILURE);
}
}
template <typename T>
void nsGlobalWindowInner::GetReplaceableWindowCoord(
JSContext* aCx, nsGlobalWindowInner::WindowCoordGetter<T> aGetter,
JS::MutableHandle<JS::Value> aRetval, CallerType aCallerType,
ErrorResult& aError) {
T coord = (this->*aGetter)(aCallerType, aError);
if (!aError.Failed() && !ToJSValue(aCx, coord, aRetval)) {
aError.Throw(NS_ERROR_FAILURE);
}
}
template <typename T>
void nsGlobalWindowInner::SetReplaceableWindowCoord(
JSContext* aCx, nsGlobalWindowInner::WindowCoordSetter<T> aSetter,
JS::Handle<JS::Value> aValue, const char* aPropName, CallerType aCallerType,
ErrorResult& aError) {
/*
* 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.
*/
nsGlobalWindowOuter* outer = GetOuterWindowInternal();
if (!outer || !outer->CanMoveResizeWindows(aCallerType) ||
mBrowsingContext->IsFrame()) {
RedefineProperty(aCx, aPropName, aValue, aError);
return;
}
T value;
if (!ValueToPrimitive<T, eDefault>(aCx, aValue, aPropName, &value)) {
aError.Throw(NS_ERROR_UNEXPECTED);
return;
}
if (nsContentUtils::ShouldResistFingerprinting(GetDocShell())) {
bool innerWidthSpecified = false;
bool innerHeightSpecified = false;
bool outerWidthSpecified = false;
bool outerHeightSpecified = false;
if (strcmp(aPropName, "innerWidth") == 0) {
innerWidthSpecified = true;
} else if (strcmp(aPropName, "innerHeight") == 0) {
innerHeightSpecified = true;
} else if (strcmp(aPropName, "outerWidth") == 0) {
outerWidthSpecified = true;
} else if (strcmp(aPropName, "outerHeight") == 0) {
outerHeightSpecified = true;
}
if (innerWidthSpecified || innerHeightSpecified || outerWidthSpecified ||
outerHeightSpecified) {
nsCOMPtr<nsIBaseWindow> treeOwnerAsWin = outer->GetTreeOwnerWindow();
nsCOMPtr<nsIScreen> screen;
nsCOMPtr<nsIScreenManager> screenMgr(
do_GetService("@mozilla.org/gfx/screenmanager;1"));
int32_t winLeft = 0;
int32_t winTop = 0;
int32_t winWidth = 0;
int32_t winHeight = 0;
double scale = 1.0;
if (treeOwnerAsWin && screenMgr) {
// Acquire current window size.
treeOwnerAsWin->GetUnscaledDevicePixelsPerCSSPixel(&scale);
treeOwnerAsWin->GetPositionAndSize(&winLeft, &winTop, &winWidth,
&winHeight);
winLeft = NSToIntRound(winHeight / scale);
winTop = NSToIntRound(winWidth / scale);
winWidth = NSToIntRound(winWidth / scale);
winHeight = NSToIntRound(winHeight / scale);
// Acquire content window size.
CSSSize contentSize;
outer->GetInnerSize(contentSize);
screenMgr->ScreenForRect(winLeft, winTop, winWidth, winHeight,
getter_AddRefs(screen));
if (screen) {
int32_t roundedValue = std::round(value);
int32_t* targetContentWidth = nullptr;
int32_t* targetContentHeight = nullptr;
int32_t screenWidth = 0;
int32_t screenHeight = 0;
int32_t chromeWidth = 0;
int32_t chromeHeight = 0;
int32_t inputWidth = 0;
int32_t inputHeight = 0;
int32_t unused = 0;
// Get screen dimensions (in device pixels)
screen->GetAvailRect(&unused, &unused, &screenWidth, &screenHeight);
// Convert them to CSS pixels
screenWidth = NSToIntRound(screenWidth / scale);
screenHeight = NSToIntRound(screenHeight / scale);
// Calculate the chrome UI size.
chromeWidth = winWidth - contentSize.width;
chromeHeight = winHeight - contentSize.height;
if (innerWidthSpecified || outerWidthSpecified) {
inputWidth = value;
targetContentWidth = &roundedValue;
targetContentHeight = &unused;
} else if (innerHeightSpecified || outerHeightSpecified) {
inputHeight = value;
targetContentWidth = &unused;
targetContentHeight = &roundedValue;
}
nsContentUtils::CalcRoundedWindowSizeForResistingFingerprinting(
chromeWidth, chromeHeight, screenWidth, screenHeight, inputWidth,
inputHeight, outerWidthSpecified, outerHeightSpecified,
targetContentWidth, targetContentHeight);
value = T(roundedValue);
}
}
}
}
(this->*aSetter)(value, aCallerType, aError);
}
void nsGlobalWindowInner::FireOnNewGlobalObject() {
// AutoEntryScript required to invoke debugger hook, which is a
// Gecko-specific concept at present.
AutoEntryScript aes(this, "nsGlobalWindowInner report new global");
JS::Rooted<JSObject*> global(aes.cx(), GetWrapper());
JS_FireOnNewGlobalObject(aes.cx(), global);
}
#if defined(_WINDOWS_) && !defined(MOZ_WRAPPED_WINDOWS_H)
# pragma message( \
"wrapper failure reason: " MOZ_WINDOWS_WRAPPER_DISABLED_REASON)
# error "Never include unwrapped windows.h in this file!"
#endif
already_AddRefed<Promise> nsGlobalWindowInner::CreateImageBitmap(
const ImageBitmapSource& aImage, ErrorResult& aRv) {
return ImageBitmap::Create(this, aImage, Nothing(), aRv);
}
already_AddRefed<Promise> nsGlobalWindowInner::CreateImageBitmap(
const ImageBitmapSource& aImage, int32_t aSx, int32_t aSy, int32_t aSw,
int32_t aSh, ErrorResult& aRv) {
return ImageBitmap::Create(this, aImage,
Some(gfx::IntRect(aSx, aSy, aSw, aSh)), aRv);
}
nsresult nsGlobalWindowInner::Dispatch(
TaskCategory aCategory, already_AddRefed<nsIRunnable>&& aRunnable) {
MOZ_RELEASE_ASSERT(NS_IsMainThread());
if (GetDocGroup()) {
return GetDocGroup()->Dispatch(aCategory, std::move(aRunnable));
}
return DispatcherTrait::Dispatch(aCategory, std::move(aRunnable));
}
nsISerialEventTarget* nsGlobalWindowInner::EventTargetFor(
TaskCategory aCategory) const {
MOZ_RELEASE_ASSERT(NS_IsMainThread());
if (GetDocGroup()) {
return GetDocGroup()->EventTargetFor(aCategory);
}
return DispatcherTrait::EventTargetFor(aCategory);
}
AbstractThread* nsGlobalWindowInner::AbstractMainThreadFor(
TaskCategory aCategory) {
MOZ_RELEASE_ASSERT(NS_IsMainThread());
if (GetDocGroup()) {
return GetDocGroup()->AbstractMainThreadFor(aCategory);
}
return DispatcherTrait::AbstractMainThreadFor(aCategory);
}
Worklet* nsGlobalWindowInner::GetPaintWorklet(ErrorResult& aRv) {
if (!mPaintWorklet) {
nsIPrincipal* principal = GetPrincipal();
if (!principal) {
aRv.Throw(NS_ERROR_FAILURE);
return nullptr;
}
mPaintWorklet = PaintWorkletImpl::CreateWorklet(this, principal);
}
return mPaintWorklet;
}
void nsGlobalWindowInner::GetRegionalPrefsLocales(
nsTArray<nsString>& aLocales) {
MOZ_ASSERT(mozilla::intl::LocaleService::GetInstance());
AutoTArray<nsCString, 10> rpLocales;
mozilla::intl::LocaleService::GetInstance()->GetRegionalPrefsLocales(
rpLocales);
for (const auto& loc : rpLocales) {
aLocales.AppendElement(NS_ConvertUTF8toUTF16(loc));
}
}
void nsGlobalWindowInner::GetWebExposedLocales(nsTArray<nsString>& aLocales) {
MOZ_ASSERT(mozilla::intl::LocaleService::GetInstance());
AutoTArray<nsCString, 10> rpLocales;
mozilla::intl::LocaleService::GetInstance()->GetWebExposedLocales(rpLocales);
for (const auto& loc : rpLocales) {
aLocales.AppendElement(NS_ConvertUTF8toUTF16(loc));
}
}
IntlUtils* nsGlobalWindowInner::GetIntlUtils(ErrorResult& aError) {
if (!mIntlUtils) {
mIntlUtils = new IntlUtils(this);
}
return mIntlUtils;
}
void nsGlobalWindowInner::StoreSharedWorker(SharedWorker* aSharedWorker) {
MOZ_ASSERT(aSharedWorker);
MOZ_ASSERT(!mSharedWorkers.Contains(aSharedWorker));
mSharedWorkers.AppendElement(aSharedWorker);
}
void nsGlobalWindowInner::ForgetSharedWorker(SharedWorker* aSharedWorker) {
MOZ_ASSERT(aSharedWorker);
MOZ_ASSERT(mSharedWorkers.Contains(aSharedWorker));
mSharedWorkers.RemoveElement(aSharedWorker);
}
void nsGlobalWindowInner::StorageAccessPermissionGranted() {
PropagateStorageAccessPermissionGrantedToWorkers(*this);
// If we have a partitioned localStorage, it's time to replace it with a real
// one in order to receive notifications.
if (mLocalStorage) {
IgnoredErrorResult error;
GetLocalStorage(error);
if (NS_WARN_IF(error.Failed())) {
return;
}
MOZ_ASSERT(mLocalStorage &&
mLocalStorage->Type() == Storage::eLocalStorage);
if (NextGenLocalStorageEnabled() && mListenerManager &&
mListenerManager->HasListenersFor(nsGkAtoms::onstorage)) {
auto object = static_cast<LSObject*>(mLocalStorage.get());
object->EnsureObserver();
}
}
// Reset the IndexedDB factory.
mIndexedDB = nullptr;
// Reset DOM Cache
mCacheStorage = nullptr;
// Reset the active storage principal
if (mDoc) {
mDoc->ClearActiveStoragePrincipal();
if (mWindowGlobalChild) {
// XXX(farre): This is a bit backwards, but clearing the storage
// principal might make us end up with a new effective storage
// principal on the child side than on the parent side, which
// means that we need to sync it. See bug 1705359.
mWindowGlobalChild->SetDocumentPrincipal(
mDoc->NodePrincipal(), mDoc->EffectiveStoragePrincipal());
}
}
}
ContentMediaController* nsGlobalWindowInner::GetContentMediaController() {
if (mContentMediaController) {
return mContentMediaController;
}
if (!mBrowsingContext) {
return nullptr;
}
mContentMediaController = new ContentMediaController(mBrowsingContext->Id());
return mContentMediaController;
}
void nsGlobalWindowInner::SetScrollMarks(
const nsTArray<uint32_t>& aScrollMarks) {
mScrollMarks.Assign(aScrollMarks);
// Mark the scrollbar for repainting.
if (mDoc) {
PresShell* presShell = mDoc->GetPresShell();
if (presShell) {
nsIScrollableFrame* sf = presShell->GetRootScrollFrameAsScrollable();
if (sf) {
sf->InvalidateVerticalScrollbar();
}
}
}
}
/* static */
already_AddRefed<nsGlobalWindowInner> nsGlobalWindowInner::Create(
nsGlobalWindowOuter* aOuterWindow, bool aIsChrome,
WindowGlobalChild* aActor) {
RefPtr<nsGlobalWindowInner> window =
new nsGlobalWindowInner(aOuterWindow, aActor);
if (aIsChrome) {
window->mIsChrome = true;
window->mCleanMessageManager = true;
}
if (aActor) {
aActor->InitWindowGlobal(window);
}
window->InitWasOffline();
return window.forget();
}
nsIURI* nsPIDOMWindowInner::GetDocumentURI() const {
return mDoc ? mDoc->GetDocumentURI() : mDocumentURI.get();
}
nsIURI* nsPIDOMWindowInner::GetDocBaseURI() const {
return mDoc ? mDoc->GetDocBaseURI() : mDocBaseURI.get();
}
mozilla::dom::WindowContext* nsPIDOMWindowInner::GetWindowContext() const {
return mWindowGlobalChild ? mWindowGlobalChild->WindowContext() : nullptr;
}
bool nsPIDOMWindowInner::RemoveFromBFCacheSync() {
if (Document* doc = GetExtantDoc()) {
return doc->RemoveFromBFCacheSync();
}
return false;
}
void nsPIDOMWindowInner::MaybeCreateDoc() {
// XXX: Forward to outer?
MOZ_ASSERT(!mDoc);
if (nsIDocShell* docShell = GetDocShell()) {
// Note that |document| here is the same thing as our mDoc, but we
// don't have to explicitly set the member variable because the docshell
// has already called SetNewDocument().
nsCOMPtr<Document> document = docShell->GetDocument();
Unused << document;
}
}
mozilla::dom::DocGroup* nsPIDOMWindowInner::GetDocGroup() const {
Document* doc = GetExtantDoc();
if (doc) {
return doc->GetDocGroup();
}
return nullptr;
}
mozilla::dom::BrowsingContextGroup*
nsPIDOMWindowInner::GetBrowsingContextGroup() const {
return mBrowsingContext ? mBrowsingContext->Group() : nullptr;
}
nsIGlobalObject* nsPIDOMWindowInner::AsGlobal() {
return nsGlobalWindowInner::Cast(this);
}
const nsIGlobalObject* nsPIDOMWindowInner::AsGlobal() const {
return nsGlobalWindowInner::Cast(this);
}
void nsPIDOMWindowInner::SaveStorageAccessPermissionGranted() {
mStorageAccessPermissionGranted = true;
nsGlobalWindowInner::Cast(this)->StorageAccessPermissionGranted();
}
bool nsPIDOMWindowInner::HasStorageAccessPermissionGranted() {
return mStorageAccessPermissionGranted;
}
nsPIDOMWindowInner::nsPIDOMWindowInner(nsPIDOMWindowOuter* aOuterWindow,
WindowGlobalChild* aActor)
: mMutationBits(0),
mIsDocumentLoaded(false),
mIsHandlingResizeEvent(false),
mMayHavePaintEventListener(false),
mMayHaveTouchEventListener(false),
mMayHaveSelectionChangeEventListener(false),
mMayHaveMouseEnterLeaveEventListener(false),
mMayHavePointerEnterLeaveEventListener(false),
mMayHaveBeforeInputEventListenerForTelemetry(false),
mMutationObserverHasObservedNodeForTelemetry(false),
mOuterWindow(aOuterWindow),
mWindowID(0),
mHasNotifiedGlobalCreated(false),
mMarkedCCGeneration(0),
mHasTriedToCacheTopInnerWindow(false),
mNumOfIndexedDBDatabases(0),
mNumOfOpenWebSockets(0),
mEvent(nullptr),
mStorageAccessPermissionGranted(false),
mWindowGlobalChild(aActor),
mWasSuspendedByGroup(false) {
MOZ_ASSERT(aOuterWindow);
mBrowsingContext = aOuterWindow->GetBrowsingContext();
if (mWindowGlobalChild) {
mWindowID = aActor->InnerWindowId();
MOZ_ASSERT(mWindowGlobalChild->BrowsingContext() == mBrowsingContext);
} else {
mWindowID = nsContentUtils::GenerateWindowId();
}
}
nsPIDOMWindowInner::~nsPIDOMWindowInner() = default;
#undef FORWARD_TO_OUTER
#undef FORWARD_TO_OUTER_OR_THROW
#undef FORWARD_TO_OUTER_VOID