/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsGlobalWindow.h" #include #include "mozilla/MemoryReporting.h" // Local Includes #include "Navigator.h" #include "nsContentSecurityManager.h" #include "nsScreen.h" #include "nsHistory.h" #include "nsDOMNavigationTiming.h" #include "nsIDOMStorageManager.h" #include "mozilla/dom/AutoplayRequest.h" #include "mozilla/dom/DOMJSProxyHandler.h" #include "mozilla/dom/DOMPrefs.h" #include "mozilla/dom/EventTarget.h" #include "mozilla/dom/LocalStorage.h" #include "mozilla/dom/Storage.h" #include "mozilla/dom/IdleRequest.h" #include "mozilla/dom/Performance.h" #include "mozilla/dom/StorageEvent.h" #include "mozilla/dom/StorageEventBinding.h" #include "mozilla/dom/StorageNotifierService.h" #include "mozilla/dom/StorageUtils.h" #include "mozilla/dom/Timeout.h" #include "mozilla/dom/TimeoutHandler.h" #include "mozilla/dom/TimeoutManager.h" #include "mozilla/IntegerPrintfMacros.h" #if defined(MOZ_WIDGET_ANDROID) #include "mozilla/dom/WindowOrientationObserver.h" #endif #include "nsDOMOfflineResourceList.h" #include "nsError.h" #include "nsIIdleService.h" #include "nsISizeOfEventTarget.h" #include "nsDOMJSUtils.h" #include "nsArrayUtils.h" #include "nsDOMWindowList.h" #include "mozilla/dom/WakeLock.h" #include "mozilla/dom/power/PowerManagerService.h" #include "nsIDocShellTreeOwner.h" #include "nsIDocumentLoader.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIPermissionManager.h" #include "nsIScriptContext.h" #include "nsIScriptTimeoutHandler.h" #include "nsITimeoutHandler.h" #include "nsIController.h" #include "nsISlowScriptDebug.h" #include "nsWindowMemoryReporter.h" #include "nsWindowSizes.h" #include "WindowNamedPropertiesHandler.h" #include "nsFrameSelection.h" #include "nsNetUtil.h" #include "nsVariant.h" #include "nsPrintfCString.h" #include "mozilla/intl/LocaleService.h" #include "WindowDestroyedEvent.h" // Helper Classes #include "nsJSUtils.h" #include "jsapi.h" // for JSAutoRequest #include "js/Wrapper.h" #include "nsCharSeparatedTokenizer.h" #include "nsReadableUtils.h" #include "nsJSEnvironment.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/Preferences.h" #include "mozilla/Likely.h" #include "mozilla/Sprintf.h" #include "mozilla/Unused.h" // Other Classes #include "mozilla/dom/BarProps.h" #include "nsContentCID.h" #include "nsLayoutStatics.h" #include "nsCCUncollectableMarker.h" #include "mozilla/dom/WorkerCommon.h" #include "mozilla/dom/ToJSValue.h" #include "nsJSPrincipals.h" #include "mozilla/Attributes.h" #include "mozilla/Debug.h" #include "mozilla/EventListenerManager.h" #include "mozilla/EventStates.h" #include "mozilla/MouseEvents.h" #include "mozilla/ProcessHangMonitor.h" #include "mozilla/ThrottledEventQueue.h" #include "AudioChannelService.h" #include "nsAboutProtocolUtils.h" #include "nsCharTraits.h" // NS_IS_HIGH/LOW_SURROGATE #include "PostMessageEvent.h" #include "mozilla/dom/DocGroup.h" #include "mozilla/dom/TabGroup.h" // Interfaces Needed #include "nsIFrame.h" #include "nsCanvasFrame.h" #include "nsIWidget.h" #include "nsIWidgetListener.h" #include "nsIBaseWindow.h" #include "nsIDeviceSensors.h" #include "nsIContent.h" #include "nsIDocShell.h" #include "nsIDocument.h" #include "Crypto.h" #include "nsIDOMOfflineResourceList.h" #include "nsDOMString.h" #include "nsIEmbeddingSiteWindow.h" #include "nsThreadUtils.h" #include "nsILoadContext.h" #include "nsIPresShell.h" #include "nsIScrollableFrame.h" #include "nsView.h" #include "nsViewManager.h" #include "nsISelectionController.h" #include "nsIPrompt.h" #include "nsIPromptService.h" #include "nsIPromptFactory.h" #include "nsIAddonPolicyService.h" #include "nsIWritablePropertyBag2.h" #include "nsIWebNavigation.h" #include "nsIWebBrowserChrome.h" #include "nsIWebBrowserFind.h" // For window.find() #include "nsIWindowMediator.h" // For window.find() #include "nsDOMCID.h" #include "nsDOMWindowUtils.h" #include "nsIWindowWatcher.h" #include "nsPIWindowWatcher.h" #include "nsIContentViewer.h" #include "nsIScriptError.h" #include "nsIControllers.h" #include "nsIControllerContext.h" #include "nsGlobalWindowCommands.h" #include "nsQueryObject.h" #include "nsContentUtils.h" #include "nsCSSProps.h" #include "nsIURIFixup.h" #ifndef DEBUG #include "nsIAppStartup.h" #include "nsToolkitCompsCID.h" #endif #include "nsCDefaultURIFixup.h" #include "mozilla/EventDispatcher.h" #include "mozilla/EventStateManager.h" #include "nsIObserverService.h" #include "nsFocusManager.h" #include "nsIXULWindow.h" #include "nsITimedChannel.h" #include "nsServiceManagerUtils.h" #ifdef MOZ_XUL #include "nsIDOMXULControlElement.h" #include "nsMenuPopupFrame.h" #endif #include "mozilla/dom/CustomEvent.h" #include "nsIJARChannel.h" #include "nsIScreenManager.h" #include "nsIEffectiveTLDService.h" #include "nsICSSDeclaration.h" #include "xpcprivate.h" #ifdef NS_PRINTING #include "nsIPrintSettings.h" #include "nsIPrintSettingsService.h" #include "nsIWebBrowserPrint.h" #endif #include "nsWindowRoot.h" #include "nsNetCID.h" #include "nsIArray.h" #include "nsBindingManager.h" #include "nsXBLService.h" #include "nsIDragService.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/Selection.h" #include "nsFrameLoader.h" #include "nsISupportsPrimitives.h" #include "nsXPCOMCID.h" #include "mozilla/Logging.h" #include "prenv.h" #include "mozilla/dom/IDBFactory.h" #include "mozilla/dom/MessageChannel.h" #include "mozilla/dom/Promise.h" #include "mozilla/dom/Gamepad.h" #include "mozilla/dom/GamepadManager.h" #include "gfxVR.h" #include "mozilla/dom/VRDisplay.h" #include "mozilla/dom/VRDisplayEvent.h" #include "mozilla/dom/VRDisplayEventBinding.h" #include "mozilla/dom/VREventObserver.h" #include "nsRefreshDriver.h" #include "Layers.h" #include "mozilla/BasePrincipal.h" #include "mozilla/Services.h" #include "mozilla/Telemetry.h" #include "mozilla/dom/Location.h" #include "nsHTMLDocument.h" #include "nsWrapperCacheInlines.h" #include "mozilla/DOMEventTargetHelper.h" #include "prrng.h" #include "nsSandboxFlags.h" #include "mozilla/dom/AudioContext.h" #include "mozilla/dom/BrowserElementDictionariesBinding.h" #include "mozilla/dom/cache/CacheStorage.h" #include "mozilla/dom/Console.h" #include "mozilla/dom/Fetch.h" #include "mozilla/dom/FunctionBinding.h" #include "mozilla/dom/HashChangeEvent.h" #include "mozilla/dom/IntlUtils.h" #include "mozilla/dom/PopStateEvent.h" #include "mozilla/dom/PopupBlockedEvent.h" #include "mozilla/dom/PrimitiveConversions.h" #include "mozilla/dom/WindowBinding.h" #include "nsITabChild.h" #include "mozilla/dom/MediaQueryList.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/NavigatorBinding.h" #include "mozilla/dom/ImageBitmap.h" #include "mozilla/dom/ImageBitmapBinding.h" #include "mozilla/dom/InstallTriggerBinding.h" #include "mozilla/dom/ServiceWorker.h" #include "mozilla/dom/ServiceWorkerRegistration.h" #include "mozilla/dom/ServiceWorkerRegistrationDescriptor.h" #include "mozilla/dom/U2F.h" #include "mozilla/dom/WebIDLGlobalNameHash.h" #include "mozilla/dom/Worklet.h" #ifdef HAVE_SIDEBAR #include "mozilla/dom/ExternalBinding.h" #endif #ifdef MOZ_WEBSPEECH #include "mozilla/dom/SpeechSynthesis.h" #endif #include "mozilla/dom/ClientManager.h" #include "mozilla/dom/ClientSource.h" #include "mozilla/dom/ClientState.h" // Apple system headers seem to have a check() macro. #ifdef check class nsIScriptTimeoutHandler; #undef check #endif // check #include "AccessCheck.h" #ifdef ANDROID #include #endif #ifdef XP_WIN #include #define getpid _getpid #else #include // for getpid() #endif using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::dom::ipc; using mozilla::TimeStamp; using mozilla::TimeDuration; 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 #define FORWARD_TO_OUTER_OR_THROW(method, args, errorresult, err_rval) \ PR_BEGIN_MACRO \ nsGlobalWindowOuter *outer = GetOuterWindowInternal(); \ if (MOZ_LIKELY(HasActiveDocument())) { \ return outer->method args; \ } \ if (!outer) { \ NS_WARNING("No outer window available!"); \ errorresult.Throw(NS_ERROR_NOT_INITIALIZED); \ } else { \ errorresult.Throw(NS_ERROR_XPC_SECURITY_MANAGER_VETO); \ } \ return err_rval; \ PR_END_MACRO #define FORWARD_TO_OUTER_VOID(method, args) \ PR_BEGIN_MACRO \ 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 DOM_TOUCH_LISTENER_ADDED "dom-touch-listener-added" #define MEMORY_PRESSURE_OBSERVER_TOPIC "memory-pressure" // Amount of time allowed between alert/prompt/confirm before enabling // the stop dialog checkbox. #define DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT 3 // 3 sec // Maximum number of successive dialogs before we prompt users to disable // dialogs for this window. #define MAX_SUCCESSIVE_DIALOG_COUNT 5 // Idle fuzz time upper limit #define MAX_IDLE_FUZZ_TIME_MS 90000 // Min idle notification time in seconds. #define MIN_IDLE_NOTIFICATION_TIME_S 1 static LazyLogModule gDOMLeakPRLogInner("DOMLeakInner"); static bool gIdleObserversAPIFuzzTimeDisabled = false; 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* GetPrincipal() const override { return mWindow ? mWindow->GetPrincipal() : 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_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IdleRequestExecutorTimeoutHandler, TimeoutHandler) nsresult Call() override; private: ~IdleRequestExecutorTimeoutHandler() override {} RefPtr mExecutor; }; NS_IMPL_CYCLE_COLLECTION_INHERITED(IdleRequestExecutorTimeoutHandler, TimeoutHandler, mExecutor) NS_IMPL_ADDREF_INHERITED(IdleRequestExecutorTimeoutHandler, TimeoutHandler) NS_IMPL_RELEASE_INHERITED(IdleRequestExecutorTimeoutHandler, TimeoutHandler) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestExecutorTimeoutHandler) NS_INTERFACE_MAP_END_INHERITING(TimeoutHandler) 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 {} bool mDispatched; TimeStamp mDeadline; IdlePeriodLimit mIdlePeriodLimit; RefPtr 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. nsCOMPtr 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 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.AssignASCII("IdleRequestExecutor"); return NS_OK; } NS_IMETHODIMP IdleRequestExecutor::Run() { MOZ_ASSERT(NS_IsMainThread()); mDispatched = false; if (mWindow) { return mWindow->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->AsOuter()->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(delay.ToMilliseconds())); } void IdleRequestExecutor::ScheduleDispatch() { MOZ_ASSERT(mWindow); mDelayedExecutorHandle = Nothing(); RefPtr request = this; NS_IdleDispatchToCurrentThread(request.forget()); } 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); } nsresult IdleRequestExecutorTimeoutHandler::Call() { if (!mExecutor->IsCancelled()) { mExecutor->ScheduleDispatch(); } return NS_OK; } 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); } nsresult nsGlobalWindowInner::RunIdleRequest(IdleRequest* aRequest, DOMHighResTimeStamp aDeadline, bool aDidTimeout) { AssertIsOnMainThread(); RefPtr request(aRequest); RemoveIdleCallback(request); return request->IdleRun(this, aDeadline, aDidTimeout); } nsresult nsGlobalWindowInner::ExecuteIdleRequest(TimeStamp aDeadline) { AssertIsOnMainThread(); RefPtr request = mIdleRequestCallbacks.getFirst(); if (!request) { // There are no more idle requests, so stop scheduling idle // request callbacks. return NS_OK; } // 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 NS_OK; } DOMHighResTimeStamp deadline = 0.0; if (Performance* perf = GetPerformance()) { deadline = perf->GetDOMTiming()->TimeStampToDOMHighRes(aDeadline); } mIdleRequestExecutor->MaybeUpdateIdlePeriodLimit(); nsresult result = RunIdleRequest(request, deadline, false); // Running the idle callback could've suspended the window, in which // case mIdleRequestExecutor will be null. if (mIdleRequestExecutor) { mIdleRequestExecutor->MaybeDispatch(); } return result; } class IdleRequestTimeoutHandler final : public TimeoutHandler { public: IdleRequestTimeoutHandler(JSContext* aCx, IdleRequest* aIdleRequest, nsPIDOMWindowInner* aWindow) : TimeoutHandler(aCx) , mIdleRequest(aIdleRequest) , mWindow(aWindow) { } NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(IdleRequestTimeoutHandler, TimeoutHandler) nsresult Call() override { return nsGlobalWindowInner::Cast(mWindow)->RunIdleRequest(mIdleRequest, 0.0, true); } private: ~IdleRequestTimeoutHandler() override {} RefPtr mIdleRequest; nsCOMPtr mWindow; }; NS_IMPL_CYCLE_COLLECTION_INHERITED(IdleRequestTimeoutHandler, TimeoutHandler, mIdleRequest, mWindow) NS_IMPL_ADDREF_INHERITED(IdleRequestTimeoutHandler, TimeoutHandler) NS_IMPL_RELEASE_INHERITED(IdleRequestTimeoutHandler, TimeoutHandler) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(IdleRequestTimeoutHandler) NS_INTERFACE_MAP_END_INHERITING(TimeoutHandler) uint32_t nsGlobalWindowInner::RequestIdleCallback(JSContext* aCx, IdleRequestCallback& aCallback, const IdleRequestOptions& aOptions, ErrorResult& aError) { AssertIsOnMainThread(); if (IsDying()) { return 0; } uint32_t handle = mIdleRequestCallbackCounter++; RefPtr request = new IdleRequest(&aCallback, handle); if (aOptions.mTimeout.WasPassed()) { int32_t timeoutHandle; nsCOMPtr 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 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() { ErrorResult error; JS::Rooted returnVal(RootingCx()); mCallback->Call(&returnVal, error); if (error.Failed()) { mPromise->MaybeReject(error); } else { mPromise->MaybeResolve(returnVal); } } void Cancel() { mPromise->MaybeReject(NS_ERROR_ABORT); } RefPtr mPromise; RefPtr mCallback; }; //***************************************************************************** //*** nsGlobalWindowInner: Object Management //***************************************************************************** nsGlobalWindowInner::nsGlobalWindowInner(nsGlobalWindowOuter *aOuterWindow) : nsPIDOMWindowInner(aOuterWindow->AsOuter()), mozilla::webgpu::InstanceProvider(this), mIdleFuzzFactor(0), mIdleCallbackIndex(-1), mCurrentlyIdle(false), mAddActiveEventFuzzTime(true), mWasOffline(false), mHasHadSlowScript(false), mNotifyIdleObserversIdleOnThaw(false), mNotifyIdleObserversActiveOnThaw(false), mIsChrome(false), mCleanMessageManager(false), mNeedsFocus(true), mHasFocus(false), mShowFocusRingForContent(false), mFocusByKeyOccurred(false), mHasGamepad(false), mHasVREvents(false), mHasVRDisplayActivateEvents(false), mHasSeenGamepadInput(false), mSuspendDepth(0), mFreezeDepth(0), mFocusMethod(0), mSerial(0), mIdleRequestCallbackCounter(1), mIdleRequestExecutor(nullptr), mDialogAbuseCount(0), mAreDialogsEnabled(true), mObservingDidRefresh(false), mIteratingDocumentFlushedResolvers(false), mCanSkipCCGeneration(0), mBeforeUnloadListenerCount(0) { AssertIsOnMainThread(); nsLayoutStatics::AddRef(); // Initialize the PRCList (this). PR_INIT_CLIST(this); if (aOuterWindow) { // |this| is an inner window, add this inner window to the outer // window list of inners. PR_INSERT_AFTER(this, aOuterWindow); mTimeoutManager = MakeUnique(*nsGlobalWindowInner::Cast(AsInner())); mObserver = new nsGlobalWindowObserver(this); if (mObserver) { nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { // Watch for online/offline status changes so we can fire events. Use // a strong reference. os->AddObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC, false); os->AddObserver(mObserver, MEMORY_PRESSURE_OBSERVER_TOPIC, false); if (aOuterWindow->IsTopLevelWindow()) { os->AddObserver(mObserver, "clear-site-data-reload-needed", false); } } Preferences::AddStrongObserver(mObserver, "intl.accept_languages"); // Watch for storage notifications so we can fire storage events. RefPtr sns = StorageNotifierService::GetOrCreate(); if (sns) { sns->Register(mObserver); } } } else { // |this| is an outer window. Outer windows start out frozen and // remain frozen until they get an inner window. MOZ_ASSERT(IsFrozen()); } if (XRE_IsContentProcess()) { nsCOMPtr docShell = GetDocShell(); if (docShell) { mTabChild = docShell->GetTabChild(); } } // We could have failed the first time through trying // to create the entropy collector, so we should // try to get one until we succeed. mSerial = nsContentUtils::InnerOrOuterWindowCreated(); static bool sFirstTime = true; if (sFirstTime) { sFirstTime = false; TimeoutManager::Initialize(); Preferences::AddBoolVarCache(&gIdleObserversAPIFuzzTimeDisabled, "dom.idle-observers-api.fuzz_time.disabled", false); } 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 if (!PR_GetEnv("MOZ_QUIET")) { printf_stderr("++DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p]\n", nsContentUtils::GetCurrentInnerOrOuterWindowCount(), static_cast(ToCanonicalSupports(this)), getpid(), mSerial, static_cast(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->Get(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->Put(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(); if (IsChromeWindow()) { MOZ_ASSERT(mCleanMessageManager, "chrome windows may always disconnect the msg manager"); DisconnectAndClearGroupMessageManagers(); if (mChromeFields.mMessageManager) { static_cast( 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) { MOZ_ASSERT(sInnerWindowsById->Get(mWindowID), "This window should be in the hash table"); sInnerWindowsById->Remove(mWindowID); } nsContentUtils::InnerOrOuterWindowDestroyed(); #ifdef DEBUG if (!PR_GetEnv("MOZ_QUIET")) { nsAutoCString url; if (mLastOpenedURI) { url = mLastOpenedURI->GetSpecOrDefault(); // Data URLs can be very long, so truncate to avoid flooding the log. const uint32_t maxURLLength = 1000; if (url.Length() > maxURLLength) { url.Truncate(maxURLLength); } } nsGlobalWindowOuter* outer = nsGlobalWindowOuter::Cast(mOuterWindow); printf_stderr("--DOMWINDOW == %d (%p) [pid = %d] [serial = %d] [outer = %p] [url = %s]\n", nsContentUtils::GetCurrentInnerOrOuterWindowCount(), static_cast(ToCanonicalSupports(this)), getpid(), mSerial, static_cast(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 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; } // static void nsGlobalWindowInner::CleanupCachedXBLHandlers() { if (mCachedXBLPrototypeHandlers && mCachedXBLPrototypeHandlers->Count() > 0) { mCachedXBLPrototypeHandlers->Clear(); } } void nsGlobalWindowInner::FreeInnerObjects() { if (IsDying()) { return; } StartDying(); // 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); if (mTimeoutManager) { mTimeoutManager->ClearAllTimeouts(); } if (mIdleTimer) { mIdleTimer->Cancel(); mIdleTimer = nullptr; } mIdleObservers.Clear(); DisableIdleCallbackRequests(); mChromeEventHandler = nullptr; if (mListenerManager) { 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 and URI. mDocumentPrincipal = mDoc->NodePrincipal(); mDocumentURI = mDoc->GetDocumentURI(); mDocBaseURI = mDoc->GetDocBaseURI(); while (mDoc->EventHandlingSuppressed()) { mDoc->UnsuppressEventHandlingAndFireEvents(false); } if (mObservingDidRefresh) { nsIPresShell* shell = mDoc->GetShell(); if (shell) { Unused << shell->RemovePostRefreshObserver(this); } } } // Remove our reference to the document and the document principal. mFocusedElement = nullptr; if (mApplicationCache) { static_cast(mApplicationCache.get())->Disconnect(); mApplicationCache = nullptr; } if (mIndexedDB) { mIndexedDB->DisconnectFromWindow(this); mIndexedDB = nullptr; } UnlinkHostObjectURIs(); NotifyWindowIDDestroyed("inner-window-destroyed"); CleanupCachedXBLHandlers(); for (uint32_t i = 0; i < mAudioContexts.Length(); ++i) { mAudioContexts[i]->Shutdown(); } mAudioContexts.Clear(); DisableGamepadUpdates(); mHasGamepad = false; mGamepads.Clear(); DisableVRUpdates(); mHasVREvents = false; mHasVRDisplayActivateEvents = false; mVRDisplays.Clear(); // This breaks a cycle between the window and the ClientSource object. mClientSource.reset(); if (mTabChild) { // Remove any remaining listeners, and reset mBeforeUnloadListenerCount. for (int i = 0; i < mBeforeUnloadListenerCount; ++i) { mTabChild->BeforeUnloadRemoved(); } mBeforeUnloadListenerCount = 0; } // If we have any promiseDocumentFlushed callbacks, fire them now so // that the Promises can resolve. CallDocumentFlushedResolvers(); mObservingDidRefresh = false; DisconnectEventTargetObjects(); if (mObserver) { nsCOMPtr os = mozilla::services::GetObserverService(); if (os) { os->RemoveObserver(mObserver, NS_IOSERVICE_OFFLINE_STATUS_TOPIC); os->RemoveObserver(mObserver, MEMORY_PRESSURE_OBSERVER_TOPIC); if (GetOuterWindowInternal() && GetOuterWindowInternal()->IsTopLevelWindow()) { os->RemoveObserver(mObserver, "clear-site-data-reload-needed"); } } RefPtr sns = StorageNotifierService::GetOrCreate(); if (sns) { sns->Unregister(mObserver); } if (mIdleService) { mIdleService->RemoveIdleObserver(mObserver, MIN_IDLE_NOTIFICATION_TIME_S); } Preferences::RemoveObserver(mObserver, "intl.accept_languages"); // Drop its reference to this dying window, in case for some bogus reason // the object stays around. mObserver->Forget(); } mMenubar = nullptr; mToolbar = nullptr; mLocationbar = nullptr; mPersonalbar = nullptr; mStatusbar = nullptr; mScrollbars = nullptr; mConsole = nullptr; mPaintWorklet = nullptr; mExternal = nullptr; mInstallTrigger = nullptr; mPerformance = nullptr; #ifdef MOZ_WEBSPEECH mSpeechSynthesis = nullptr; #endif mParentTarget = nullptr; if (mCleanMessageManager) { MOZ_ASSERT(mIsChrome, "only chrome should have msg manager cleaned"); if (mChromeFields.mMessageManager) { mChromeFields.mMessageManager->Disconnect(); } } mIntlUtils = nullptr; } //***************************************************************************** // 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 (tmp->mCachedXBLPrototypeHandlers) { for (auto iter = tmp->mCachedXBLPrototypeHandlers->Iter(); !iter.Done(); iter.Next()) { iter.Data().exposeToActiveJS(); } } 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(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(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(mTabChild) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDoc) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleService) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleRequestExecutor) for (IdleRequest* request : tmp->mIdleRequestCallbacks) { cb.NoteNativeChild(request, NS_CYCLE_COLLECTION_PARTICIPANT(IdleRequest)); } NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mIdleObservers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mClientSource) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepads) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCacheStorage) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRDisplays) // Traverse stuff from nsPIDOMWindow NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeEventHandler) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mParentTarget) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mFocusedElement) 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->TraverseHostObjectURIs(cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mMessageManager) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mChromeFields.mGroupMessageManagers) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingPromises) 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); } static_cast(tmp)->CcTraverse(cb); NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(nsGlobalWindowInner) tmp->CleanupCachedXBLHandlers(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mNavigator) NS_IMPL_CYCLE_COLLECTION_UNLINK(mPerformance) #ifdef MOZ_WEBSPEECH NS_IMPL_CYCLE_COLLECTION_UNLINK(mSpeechSynthesis) #endif if (tmp->mOuterWindow) { 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(mLocalStorage) NS_IMPL_CYCLE_COLLECTION_UNLINK(mSessionStorage) if (tmp->mApplicationCache) { static_cast(tmp->mApplicationCache.get())->Disconnect(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mApplicationCache) } if (tmp->mIndexedDB) { tmp->mIndexedDB->DisconnectFromWindow(tmp); NS_IMPL_CYCLE_COLLECTION_UNLINK(mIndexedDB) } NS_IMPL_CYCLE_COLLECTION_UNLINK(mDocumentPrincipal) NS_IMPL_CYCLE_COLLECTION_UNLINK(mTabChild) NS_IMPL_CYCLE_COLLECTION_UNLINK(mDoc) NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleService) NS_IMPL_CYCLE_COLLECTION_UNLINK(mIdleObservers) NS_IMPL_CYCLE_COLLECTION_UNLINK(mGamepads) NS_IMPL_CYCLE_COLLECTION_UNLINK(mCacheStorage) NS_IMPL_CYCLE_COLLECTION_UNLINK(mVRDisplays) // Unlink stuff from nsPIDOMWindow NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeEventHandler) NS_IMPL_CYCLE_COLLECTION_UNLINK(mParentTarget) NS_IMPL_CYCLE_COLLECTION_UNLINK(mFocusedElement) 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->UnlinkHostObjectURIs(); 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( tmp->mChromeFields.mMessageManager.get())->Disconnect(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mMessageManager) } tmp->DisconnectAndClearGroupMessageManagers(); NS_IMPL_CYCLE_COLLECTION_UNLINK(mChromeFields.mGroupMessageManagers) } NS_IMPL_CYCLE_COLLECTION_UNLINK(mPendingPromises) 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(); static_cast(tmp)->CcUnlink(); 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) if (tmp->mCachedXBLPrototypeHandlers) { for (auto iter = tmp->mCachedXBLPrototypeHandlers->Iter(); !iter.Done(); iter.Next()) { aCallbacks.Trace(&iter.Data(), "Cached XBL prototype handler", aClosure); } } NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER NS_IMPL_CYCLE_COLLECTION_TRACE_END bool 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(); } JSObject * nsGlobalWindowInner::GetGlobalJSObject() { return FastGetGlobalJSObject(); } void nsGlobalWindowInner::TraceGlobalJSObject(JSTracer* aTrc) { TraceWrapper(aTrc, "active window global"); } PopupControlState nsGlobalWindowInner::GetPopupControlState() const { return nsContentUtils::GetPopupControlState(); } nsresult nsGlobalWindowInner::SetNewDocument(nsIDocument* aDocument, nsISupports* aState, bool aForceReuseInnerWindow) { MOZ_ASSERT(mDocumentPrincipal == nullptr, "mDocumentPrincipal prematurely set!"); MOZ_ASSERT(aDocument); if (!mOuterWindow) { return NS_ERROR_NOT_INITIALIZED; } // Refuse to set a new document if the call came from an inner // window that's not the current inner window. if (mOuterWindow->GetCurrentInnerWindow() != this) { return NS_ERROR_NOT_AVAILABLE; } return GetOuterWindowInternal()->SetNewDocument(aDocument, aState, aForceReuseInnerWindow); } void nsGlobalWindowInner::InnerSetNewDocument(JSContext* aCx, nsIDocument* aDocument) { MOZ_ASSERT(aDocument); if (MOZ_LOG_TEST(gDOMLeakPRLogInner, LogLevel::Debug)) { nsIURI *uri = aDocument->GetDocumentURI(); MOZ_LOG(gDOMLeakPRLogInner, LogLevel::Debug, ("DOMWINDOW %p SetNewDocument %s", this, uri ? uri->GetSpecOrDefault().get() : "")); } mDoc = aDocument; ClearDocumentDependentSlots(aCx); mFocusedElement = nullptr; mLocalStorage = nullptr; mSessionStorage = nullptr; #ifdef DEBUG mLastOpenedURI = aDocument->GetDocumentURI(); #endif Telemetry::Accumulate(Telemetry::INNERWINDOWS_WITH_MUTATION_LISTENERS, mMutationBits ? 1 : 0); // Clear our mutation bitfield. mMutationBits = 0; } 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 loadInfo; nsCOMPtr channel = mDoc->GetChannel(); if (channel) { nsCOMPtr 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. bool isAbout = false; if (NS_SUCCEEDED(uri->SchemeIs("about", &isAbout)) && isAbout) { 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. bool isData = false; bool isBlob = false; ignoreLoadInfo = (NS_SUCCEEDED(uri->SchemeIs("data", &isData)) && isData) || (NS_SUCCEEDED(uri->SchemeIs("blob", &isBlob)) && isBlob); } if (!ignoreLoadInfo) { loadInfo = channel->GetLoadInfo(); } } // Take the initial client source from the docshell immediately. Even if we // don't end up using it here we should consume it. UniquePtr 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 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) { nsCOMPtr clientPrincipal(mClientSource->Info().GetPrincipal()); 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 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; } } // 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(AsInner()); NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } void nsGlobalWindowInner::SetOpenerWindow(nsPIDOMWindowOuter* aOpener, bool aOriginalOpener) { FORWARD_TO_OUTER_VOID(SetOpenerWindow, (aOpener, aOriginalOpener)); } 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. nsCOMPtr frameElement = GetOuterWindow()->GetFrameElementInternal(); nsCOMPtr eventTarget = nsContentUtils::TryGetTabChildGlobalAsEventTarget(frameElement); if (!eventTarget) { nsGlobalWindowOuter* topWin = GetScriptableTopInternal(); if (topWin) { frameElement = topWin->AsOuter()->GetFrameElementInternal(); eventTarget = nsContentUtils::TryGetTabChildGlobalAsEventTarget(frameElement); } } if (!eventTarget) { eventTarget = nsContentUtils::TryGetTabChildGlobalAsEventTarget(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 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 ds = do_GetService("@mozilla.org/widget/dragservice;1"); if (ds) { sDragServiceDisabled = false; ds->Unsuppress(); } } } aVisitor.SetParentTarget(GetParentTarget(), true); // Handle 'active' event. if (!mIdleObservers.IsEmpty() && aVisitor.mEvent->IsTrusted() && (aVisitor.mEvent->HasMouseEventMessage() || aVisitor.mEvent->HasDragEventMessage())) { mAddActiveEventFuzzTime = false; } } bool nsGlobalWindowInner::DialogsAreBeingAbused() { NS_ASSERTION(GetScriptableTopInternal() && GetScriptableTopInternal()->GetCurrentInnerWindowInternal() == this, "DialogsAreBeingAbused called with invalid window"); if (mLastDialogQuitTime.IsNull() || nsContentUtils::IsCallerChrome()) { return false; } TimeDuration dialogInterval(TimeStamp::Now() - mLastDialogQuitTime); if (dialogInterval.ToSeconds() < Preferences::GetInt("dom.successive_dialog_time_limit", DEFAULT_SUCCESSIVE_DIALOG_TIME_LIMIT)) { mDialogAbuseCount++; return GetPopupControlState() > openAllowed || mDialogAbuseCount > MAX_SUCCESSIVE_DIALOG_COUNT; } // Reset the abuse counter mDialogAbuseCount = 0; return false; } void nsGlobalWindowInner::DisableDialogs() { FORWARD_TO_OUTER_VOID(DisableDialogs, ()); } void nsGlobalWindowInner::EnableDialogs() { FORWARD_TO_OUTER_VOID(EnableDialogs, ()); } 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 kungFuDeathGrip1(mChromeEventHandler); mozilla::Unused << kungFuDeathGrip1; // These aren't referred to through the function nsCOMPtr kungFuDeathGrip2(GetContextInternal()); mozilla::Unused << kungFuDeathGrip2; // These aren't referred to through the function if (aVisitor.mEvent->mMessage == eResize) { mIsHandlingResizeEvent = false; } else if (aVisitor.mEvent->mMessage == eUnload && aVisitor.mEvent->IsTrusted()) { // 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()) { // 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; } } // Execute bindingdetached handlers before we tear ourselves // down. if (mDoc) { mDoc->BindingManager()->ExecuteDetachedHandlers(); } mIsDocumentLoaded = false; } else if (aVisitor.mEvent->mMessage == eLoad && aVisitor.mEvent->IsTrusted()) { // This is page load event since load events don't propagate to |window|. // @see nsDocument::GetEventTargetParent. mIsDocumentLoaded = true; mTimeoutManager->OnDocumentLoaded(); nsCOMPtr element = GetOuterWindow()->GetFrameElementInternal(); nsIDocShell* docShell = GetDocShell(); if (element && GetParentInternal() && docShell && docShell->ItemType() != nsIDocShellTreeItem::typeChrome) { // If we're not in chrome, or at a chrome boundary, fire the // onload event for the frame element. nsEventStatus status = nsEventStatus_eIgnore; WidgetEvent event(aVisitor.mEvent->IsTrusted(), eLoad); event.mFlags.mBubbles = false; event.mFlags.mCancelable = false; // Most of the time we could get a pres context to pass in here, // but not always (i.e. if this window is not shown there won't // be a pres context available). Since we're not firing a GUI // event we don't need a pres context anyway so we just pass // null as the pres context all the time here. EventDispatcher::Dispatch(element, nullptr, &event, nullptr, &status); } 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 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 , in // that case the global window is used in JS before we've loaded // a document into the window. nsCOMPtr objPrincipal = do_QueryInterface(GetParentInternal()); if (objPrincipal) { return objPrincipal->GetPrincipal(); } return nullptr; } //***************************************************************************** // 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(); } } } nsGlobalWindowInner* nsGlobalWindowInner::Window() { return this; } nsGlobalWindowInner* nsGlobalWindowInner::Self() { return this; } Navigator* nsPIDOMWindowInner::Navigator() { if (!mNavigator) { mNavigator = new mozilla::dom::Navigator(this); } return mNavigator; } 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; } Performance* nsPIDOMWindowInner::GetPerformance() { CreatePerformanceObjectIfNeeded(); return mPerformance; } void nsPIDOMWindowInner::CreatePerformanceObjectIfNeeded() { if (mPerformance || !mDoc) { return; } RefPtr timing = mDoc->GetNavigationTiming(); nsCOMPtr timedChannel(do_QueryInterface(mDoc->GetChannel())); bool timingEnabled = false; if (!timedChannel || !NS_SUCCEEDED(timedChannel->GetTimingEnabled(&timingEnabled)) || !timingEnabled) { timedChannel = nullptr; } if (timing) { mPerformance = Performance::CreateForMainThread(this, mDoc->NodePrincipal(), timing, timedChannel); } } bool nsPIDOMWindowInner::IsSecureContext() const { return nsGlobalWindowInner::Cast(this)->IsSecureContext(); } void nsPIDOMWindowInner::Suspend() { nsGlobalWindowInner::Cast(this)->Suspend(); } void nsPIDOMWindowInner::Resume() { nsGlobalWindowInner::Cast(this)->Resume(); } void nsPIDOMWindowInner::Freeze() { nsGlobalWindowInner::Cast(this)->Freeze(); } void nsPIDOMWindowInner::Thaw() { nsGlobalWindowInner::Cast(this)->Thaw(); } void nsPIDOMWindowInner::SyncStateFromParentWindow() { nsGlobalWindowInner::Cast(this)->SyncStateFromParentWindow(); } Maybe nsPIDOMWindowInner::GetClientInfo() const { return nsGlobalWindowInner::Cast(this)->GetClientInfo(); } Maybe nsPIDOMWindowInner::GetClientState() const { return nsGlobalWindowInner::Cast(this)->GetClientState(); } Maybe nsPIDOMWindowInner::GetController() const { return nsGlobalWindowInner::Cast(this)->GetController(); } RefPtr nsPIDOMWindowInner::GetOrCreateServiceWorker(const mozilla::dom::ServiceWorkerDescriptor& aDescriptor) { return nsGlobalWindowInner::Cast(this)->GetOrCreateServiceWorker(aDescriptor); } 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 = GetScriptableTop(); NS_ENSURE_TRUE(topOuter, false); nsGlobalWindowInner* topInner = nsGlobalWindowInner::Cast(topOuter->GetCurrentInnerWindow()); NS_ENSURE_TRUE(topInner, false); topInner->ShouldReportForServiceWorkerScopeInternal(NS_ConvertUTF16toUTF8(aScope), &result); return result; } already_AddRefed nsGlobalWindowInner::GetInstallTrigger() { if (!mInstallTrigger) { JS::Rooted jsImplObj(RootingCx()); ErrorResult rv; ConstructJSImplementation("@mozilla.org/addons/installtrigger;1", this, &jsImplObj, rv); if (rv.Failed()) { rv.SuppressException(); return nullptr; } mInstallTrigger = new InstallTriggerImpl(jsImplObj, this); } return do_AddRef(mInstallTrigger); } nsGlobalWindowInner::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 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 loader(do_QueryInterface(GetDocShell())); if (loader) { nsCOMPtr loadgroup; Unused << loader->GetLoadGroup(getter_AddRefs(loadgroup)); if (loadgroup) { nsCOMPtr iter; Unused << loadgroup->GetRequests(getter_AddRefs(iter)); if (iter) { nsCOMPtr 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 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 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 CallOnChildren(&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::MigrateStateForDocumentOpen(nsGlobalWindowInner* aOldInner) { MOZ_DIAGNOSTIC_ASSERT(aOldInner); MOZ_DIAGNOSTIC_ASSERT(aOldInner != this); MOZ_DIAGNOSTIC_ASSERT(mDoc); // Rebind DETH objects to the new global created by document.open(). // XXX: Is this correct? We should consider if the spec and our // implementation should change to match other browsers by // just reusing the current window. (Bug 1449992) aOldInner->ForEachEventTargetObject( [&] (DOMEventTargetHelper* aDETH, bool* aDoneOut) { aDETH->BindToOwner(this->AsInner()); }); // Move the old Performance object from the old window to the new window. // The Performance object was also rebound in the DETH loop above. mPerformance = aOldInner->mPerformance.forget(); if (aOldInner->mIndexedDB) { aOldInner->mIndexedDB->RebindToNewWindow(this); mIndexedDB = aOldInner->mIndexedDB.forget(); } } void nsGlobalWindowInner::UpdateTopInnerWindow() { if (IsTopInnerWindow() || !mTopInnerWindow) { return; } mTopInnerWindow->UpdateWebSocketCount(-(int32_t)mNumOfOpenWebSockets); } void nsPIDOMWindowInner::AddPeerConnection() { MOZ_ASSERT(NS_IsMainThread()); mTopInnerWindow ? mTopInnerWindow->mActivePeerConnections++ : mActivePeerConnections++; } void nsPIDOMWindowInner::RemovePeerConnection() { MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(mTopInnerWindow ? mTopInnerWindow->mActivePeerConnections : mActivePeerConnections); mTopInnerWindow ? mTopInnerWindow->mActivePeerConnections-- : mActivePeerConnections--; } bool nsPIDOMWindowInner::HasActivePeerConnections() { MOZ_ASSERT(NS_IsMainThread()); return mTopInnerWindow ? mTopInnerWindow->mActivePeerConnections : mActivePeerConnections; } bool nsPIDOMWindowInner::IsPlayingAudio() { for (uint32_t i = 0; i < mAudioContexts.Length(); i++) { if (mAudioContexts[i]->IsRunning()) { return true; } } RefPtr 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 topOutter = window->GetScriptableTop()) { mTopInnerWindow = topOutter->GetCurrentInnerWindow(); } } void nsPIDOMWindowInner::UpdateActiveIndexedDBTransactionCount(int32_t aDelta) { MOZ_ASSERT(NS_IsMainThread()); if (aDelta == 0) { return; } TabGroup()->IndexedDBTransactionCounter() += aDelta; } 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; TabGroup()->IndexedDBDatabaseCounter() += 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::GetAudioCaptured() const { return mAudioCaptured; } nsresult nsPIDOMWindowInner::SetAudioCapture(bool aCapture) { mAudioCaptured = aCapture; RefPtr service = AudioChannelService::GetOrCreate(); if (service) { service->SetWindowAudioCaptured(GetOuterWindow(), mWindowID, aCapture); } return NS_OK; } // 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 already_AddRefed nsGlobalWindowInner::GetParent(ErrorResult& aError) { FORWARD_TO_OUTER_OR_THROW(GetParentOuter, (), aError, nullptr); } /** * GetScriptableParent is called when script reads window.parent. * * In contrast to GetRealParent, GetScriptableParent respects