/* -*- 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 "nsDocShell.h" #include #ifdef XP_WIN # include # define getpid _getpid #else # include // for getpid() #endif #include "mozilla/ArrayUtils.h" #include "mozilla/Attributes.h" #include "mozilla/AutoRestore.h" #include "mozilla/BasePrincipal.h" #include "mozilla/Casting.h" #include "mozilla/CheckedInt.h" #include "mozilla/Components.h" #include "mozilla/DebugOnly.h" #include "mozilla/Encoding.h" #include "mozilla/EventStateManager.h" #include "mozilla/HTMLEditor.h" #include "mozilla/InputTaskManager.h" #include "mozilla/LoadInfo.h" #include "mozilla/Logging.h" #include "mozilla/MediaFeatureChange.h" #include "mozilla/ObservedDocShell.h" #include "mozilla/Preferences.h" #include "mozilla/PresShell.h" #include "mozilla/ResultExtensions.h" #include "mozilla/SchedulerGroup.h" #include "mozilla/ScopeExit.h" #include "mozilla/ScrollTypes.h" #include "mozilla/SimpleEnumerator.h" #include "mozilla/StaticPrefs_browser.h" #include "mozilla/StaticPrefs_docshell.h" #include "mozilla/StaticPrefs_dom.h" #include "mozilla/StaticPrefs_extensions.h" #include "mozilla/StaticPrefs_privacy.h" #include "mozilla/StaticPrefs_security.h" #include "mozilla/StaticPrefs_ui.h" #include "mozilla/StaticPrefs_fission.h" #include "mozilla/StartupTimeline.h" #include "mozilla/StorageAccess.h" #include "mozilla/StoragePrincipalHelper.h" #include "mozilla/Telemetry.h" #include "mozilla/Unused.h" #include "mozilla/WidgetUtils.h" #include "mozilla/dom/AutoEntryScript.h" #include "mozilla/dom/ChildProcessChannelListener.h" #include "mozilla/dom/ClientChannelHelper.h" #include "mozilla/dom/ClientHandle.h" #include "mozilla/dom/ClientInfo.h" #include "mozilla/dom/ClientManager.h" #include "mozilla/dom/ClientSource.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/ContentFrameMessageManager.h" #include "mozilla/dom/DocGroup.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/HTMLAnchorElement.h" #include "mozilla/dom/HTMLIFrameElement.h" #include "mozilla/dom/PerformanceNavigation.h" #include "mozilla/dom/PermissionMessageUtils.h" #include "mozilla/dom/PopupBlocker.h" #include "mozilla/dom/ProfileTimelineMarkerBinding.h" #include "mozilla/dom/ScreenOrientation.h" #include "mozilla/dom/ScriptSettings.h" #include "mozilla/dom/ServiceWorkerInterceptController.h" #include "mozilla/dom/ServiceWorkerUtils.h" #include "mozilla/dom/SessionHistoryEntry.h" #include "mozilla/dom/SessionStorageManager.h" #include "mozilla/dom/SessionStoreChangeListener.h" #include "mozilla/dom/SessionStoreChild.h" #include "mozilla/dom/SessionStoreUtils.h" #include "mozilla/dom/BrowserChild.h" #include "mozilla/dom/ToJSValue.h" #include "mozilla/dom/UserActivation.h" #include "mozilla/dom/ChildSHistory.h" #include "mozilla/dom/nsCSPContext.h" #include "mozilla/dom/nsHTTPSOnlyUtils.h" #include "mozilla/dom/LoadURIOptionsBinding.h" #include "mozilla/dom/JSWindowActorChild.h" #include "mozilla/dom/DocumentBinding.h" #include "mozilla/ipc/ProtocolUtils.h" #include "mozilla/net/DocumentChannel.h" #include "mozilla/net/DocumentChannelChild.h" #include "mozilla/net/ParentChannelWrapper.h" #include "mozilla/net/UrlClassifierFeatureFactory.h" #include "ReferrerInfo.h" #include "nsIAuthPrompt.h" #include "nsIAuthPrompt2.h" #include "nsICachingChannel.h" #include "nsICaptivePortalService.h" #include "nsIChannel.h" #include "nsIChannelEventSink.h" #include "nsIClassOfService.h" #include "nsIConsoleReportCollector.h" #include "nsIContent.h" #include "nsIContentInlines.h" #include "nsIContentSecurityPolicy.h" #include "nsIContentViewer.h" #include "nsIController.h" #include "nsIDocShellTreeItem.h" #include "nsIDocShellTreeOwner.h" #include "mozilla/dom/Document.h" #include "nsHTMLDocument.h" #include "nsIDocumentLoaderFactory.h" #include "nsIDOMWindow.h" #include "nsIEditingSession.h" #include "nsIEffectiveTLDService.h" #include "nsIExternalProtocolService.h" #include "nsIFormPOSTActionChannel.h" #include "nsIFrame.h" #include "nsIGlobalObject.h" #include "nsIHttpChannel.h" #include "nsIHttpChannelInternal.h" #include "nsIIDNService.h" #include "nsIInputStreamChannel.h" #include "nsIInterfaceRequestorUtils.h" #include "nsILayoutHistoryState.h" #include "nsILoadInfo.h" #include "nsILoadURIDelegate.h" #include "nsIMultiPartChannel.h" #include "nsINestedURI.h" #include "nsINetworkPredictor.h" #include "nsINode.h" #include "nsINSSErrorsService.h" #include "nsIObserverService.h" #include "nsIOService.h" #include "nsIPrincipal.h" #include "nsIPrivacyTransitionObserver.h" #include "nsIPrompt.h" #include "nsIPromptCollection.h" #include "nsIPromptFactory.h" #include "nsIPublicKeyPinningService.h" #include "nsIReflowObserver.h" #include "nsIScriptChannel.h" #include "nsIScriptObjectPrincipal.h" #include "nsIScriptSecurityManager.h" #include "nsIScrollableFrame.h" #include "nsIScrollObserver.h" #include "nsISupportsPrimitives.h" #include "nsISecureBrowserUI.h" #include "nsISeekableStream.h" #include "nsISelectionDisplay.h" #include "nsISHEntry.h" #include "nsISiteSecurityService.h" #include "nsISocketProvider.h" #include "nsIStringBundle.h" #include "nsIStructuredCloneContainer.h" #include "nsIBrowserChild.h" #include "nsITextToSubURI.h" #include "nsITimedChannel.h" #include "nsITimer.h" #include "nsITransportSecurityInfo.h" #include "nsIUploadChannel.h" #include "nsIURIFixup.h" #include "nsIURIMutator.h" #include "nsIURILoader.h" #include "nsIViewSourceChannel.h" #include "nsIWebBrowserChrome.h" #include "nsIWebBrowserChromeFocus.h" #include "nsIWebBrowserFind.h" #include "nsIWebProgress.h" #include "nsIWidget.h" #include "nsIWindowWatcher.h" #include "nsIWritablePropertyBag2.h" #include "nsIX509Cert.h" #include "nsIXULRuntime.h" #include "nsCommandManager.h" #include "nsPIDOMWindow.h" #include "nsPIWindowRoot.h" #include "IHistory.h" #include "IUrlClassifierUITelemetry.h" #include "nsArray.h" #include "nsArrayUtils.h" #include "nsCExternalHandlerService.h" #include "nsContentDLF.h" #include "nsContentPolicyUtils.h" // NS_CheckContentLoadPolicy(...) #include "nsContentSecurityManager.h" #include "nsContentSecurityUtils.h" #include "nsContentUtils.h" #include "nsCURILoader.h" #include "nsDocShellCID.h" #include "nsDocShellEditorData.h" #include "nsDocShellEnumerator.h" #include "nsDocShellLoadState.h" #include "nsDocShellLoadTypes.h" #include "nsDOMCID.h" #include "nsDOMNavigationTiming.h" #include "nsDSURIContentListener.h" #include "nsEditingSession.h" #include "nsError.h" #include "nsEscape.h" #include "nsFocusManager.h" #include "nsGlobalWindow.h" #include "nsJSEnvironment.h" #include "nsNetCID.h" #include "nsNetUtil.h" #include "nsObjectLoadingContent.h" #include "nsPingListener.h" #include "nsPoint.h" #include "nsQueryObject.h" #include "nsQueryActor.h" #include "nsRect.h" #include "nsRefreshTimer.h" #include "nsSandboxFlags.h" #include "nsSHEntry.h" #include "nsSHistory.h" #include "nsSHEntry.h" #include "nsStructuredCloneContainer.h" #include "nsSubDocumentFrame.h" #include "nsURILoader.h" #include "nsURLHelper.h" #include "nsView.h" #include "nsViewManager.h" #include "nsViewSourceHandler.h" #include "nsWebBrowserFind.h" #include "nsWhitespaceTokenizer.h" #include "nsWidgetsCID.h" #include "nsXULAppAPI.h" #include "ThirdPartyUtil.h" #include "GeckoProfiler.h" #include "mozilla/NullPrincipal.h" #include "Navigator.h" #include "prenv.h" #include "mozilla/ipc/URIUtils.h" #include "sslerr.h" #include "mozpkix/pkix.h" #include "NSSErrorsService.h" #include "timeline/JavascriptTimelineMarker.h" #include "nsDocShellTelemetryUtils.h" #ifdef MOZ_PLACES # include "nsIFaviconService.h" # include "mozIPlacesPendingOperation.h" #endif #if NS_PRINT_PREVIEW # include "nsIDocumentViewerPrint.h" # include "nsIWebBrowserPrint.h" #endif using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::net; using mozilla::ipc::Endpoint; // Threshold value in ms for META refresh based redirects #define REFRESH_REDIRECT_TIMER 15000 static mozilla::LazyLogModule gCharsetMenuLog("CharsetMenu"); #define LOGCHARSETMENU(args) \ MOZ_LOG(gCharsetMenuLog, mozilla::LogLevel::Debug, args) #ifdef DEBUG unsigned long nsDocShell::gNumberOfDocShells = 0; static uint64_t gDocshellIDCounter = 0; static mozilla::LazyLogModule gDocShellLog("nsDocShell"); static mozilla::LazyLogModule gDocShellAndDOMWindowLeakLogging( "DocShellAndDOMWindowLeak"); #endif static mozilla::LazyLogModule gDocShellLeakLog("nsDocShellLeak"); extern mozilla::LazyLogModule gPageCacheLog; mozilla::LazyLogModule gSHLog("SessionHistory"); extern mozilla::LazyLogModule gSHIPBFCacheLog; const char kAppstringsBundleURL[] = "chrome://global/locale/appstrings.properties"; static bool IsTopLevelDoc(BrowsingContext* aBrowsingContext, nsILoadInfo* aLoadInfo) { MOZ_ASSERT(aBrowsingContext); MOZ_ASSERT(aLoadInfo); if (aLoadInfo->GetExternalContentPolicyType() != ExtContentPolicy::TYPE_DOCUMENT) { return false; } return aBrowsingContext->IsTopContent(); } // True if loading for top level document loading in active tab. static bool IsUrgentStart(BrowsingContext* aBrowsingContext, nsILoadInfo* aLoadInfo, uint32_t aLoadType) { MOZ_ASSERT(aBrowsingContext); MOZ_ASSERT(aLoadInfo); if (!IsTopLevelDoc(aBrowsingContext, aLoadInfo)) { return false; } if (aLoadType & (nsIDocShell::LOAD_CMD_NORMAL | nsIDocShell::LOAD_CMD_HISTORY)) { return true; } return aBrowsingContext->IsActive(); } nsDocShell::nsDocShell(BrowsingContext* aBrowsingContext, uint64_t aContentWindowID) : nsDocLoader(true), mContentWindowID(aContentWindowID), mBrowsingContext(aBrowsingContext), mParentCharset(nullptr), mTreeOwner(nullptr), mScrollbarPref(ScrollbarPreference::Auto), mCharsetReloadState(eCharsetReloadInit), mParentCharsetSource(0), mFrameMargins(-1, -1), mItemType(aBrowsingContext->IsContent() ? typeContent : typeChrome), mPreviousEntryIndex(-1), mLoadedEntryIndex(-1), mBusyFlags(BUSY_FLAGS_NONE), mAppType(nsIDocShell::APP_TYPE_UNKNOWN), mLoadType(0), mFailedLoadType(0), mJSRunToCompletionDepth(0), mMetaViewportOverride(nsIDocShell::META_VIEWPORT_OVERRIDE_NONE), mChannelToDisconnectOnPageHide(0), mCreatingDocument(false), #ifdef DEBUG mInEnsureScriptEnv(false), #endif mInitialized(false), mAllowSubframes(true), mAllowMetaRedirects(true), mAllowImages(true), mAllowMedia(true), mAllowDNSPrefetch(true), mAllowWindowControl(true), mCSSErrorReportingEnabled(false), mAllowAuth(mItemType == typeContent), mAllowKeywordFixup(false), mDisableMetaRefreshWhenInactive(false), mWindowDraggingAllowed(false), mInFrameSwap(false), mFiredUnloadEvent(false), mEODForCurrentDocument(false), mURIResultedInDocument(false), mIsBeingDestroyed(false), mIsExecutingOnLoadHandler(false), mSavingOldViewer(false), mInvisible(false), mHasLoadedNonBlankURI(false), mBlankTiming(false), mTitleValidForCurrentURI(false), mWillChangeProcess(false), mIsNavigating(false), mForcedAutodetection(false), mCheckingSessionHistory(false), mNeedToReportActiveAfterLoadingBecomesActive(false) { // If no outer window ID was provided, generate a new one. if (aContentWindowID == 0) { mContentWindowID = nsContentUtils::GenerateWindowId(); } MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p created\n", this)); #ifdef DEBUG mDocShellID = gDocshellIDCounter++; // We're counting the number of |nsDocShells| to help find leaks ++gNumberOfDocShells; MOZ_LOG(gDocShellAndDOMWindowLeakLogging, LogLevel::Info, ("++DOCSHELL %p == %ld [pid = %d] [id = %" PRIu64 "]\n", (void*)this, gNumberOfDocShells, getpid(), mDocShellID)); #endif } nsDocShell::~nsDocShell() { MOZ_ASSERT(!mObserved); // Avoid notifying observers while we're in the dtor. mIsBeingDestroyed = true; Destroy(); if (mContentViewer) { mContentViewer->Close(nullptr); mContentViewer->Destroy(); mContentViewer = nullptr; } MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p destroyed\n", this)); #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); } } // We're counting the number of |nsDocShells| to help find leaks --gNumberOfDocShells; MOZ_LOG( gDocShellAndDOMWindowLeakLogging, LogLevel::Info, ("--DOCSHELL %p == %ld [pid = %d] [id = %" PRIu64 "] [url = %s]\n", (void*)this, gNumberOfDocShells, getpid(), mDocShellID, url.get())); } #endif } bool nsDocShell::Initialize() { if (mInitialized) { // We've already been initialized. return true; } NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, "Unexpected item type in docshell"); NS_ENSURE_TRUE(Preferences::GetRootBranch(), false); mInitialized = true; mDisableMetaRefreshWhenInactive = Preferences::GetBool("browser.meta_refresh_when_inactive.disabled", mDisableMetaRefreshWhenInactive); if (nsCOMPtr serv = services::GetObserverService()) { const char* msg = mItemType == typeContent ? NS_WEBNAVIGATION_CREATE : NS_CHROME_WEBNAVIGATION_CREATE; serv->NotifyWhenScriptSafe(GetAsSupports(this), msg, nullptr); } return true; } /* static */ already_AddRefed nsDocShell::Create( BrowsingContext* aBrowsingContext, uint64_t aContentWindowID) { MOZ_ASSERT(aBrowsingContext, "DocShell without a BrowsingContext!"); nsresult rv; RefPtr ds = new nsDocShell(aBrowsingContext, aContentWindowID); // Initialize the underlying nsDocLoader. rv = ds->nsDocLoader::InitWithBrowsingContext(aBrowsingContext); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } // Create our ContentListener ds->mContentListener = new nsDSURIContentListener(ds); // We enable if we're in the parent process in order to support non-e10s // configurations. // Note: This check is duplicated in SharedWorkerInterfaceRequestor's // constructor. if (XRE_IsParentProcess()) { ds->mInterceptController = new ServiceWorkerInterceptController(); } // We want to hold a strong ref to the loadgroup, so it better hold a weak // ref to us... use an InterfaceRequestorProxy to do this. nsCOMPtr proxy = new InterfaceRequestorProxy(ds); ds->mLoadGroup->SetNotificationCallbacks(proxy); // XXX(nika): We have our BrowsingContext, so we might be able to skip this. // It could be nice to directly set up our DocLoader tree? rv = nsDocLoader::AddDocLoaderAsChildOfRoot(ds); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } // Add |ds| as a progress listener to itself. A little weird, but simpler // than reproducing all the listener-notification logic in overrides of the // various methods via which nsDocLoader can be notified. Note that this // holds an nsWeakPtr to |ds|, so it's ok. rv = ds->AddProgressListener(ds, nsIWebProgress::NOTIFY_STATE_DOCUMENT | nsIWebProgress::NOTIFY_STATE_NETWORK | nsIWebProgress::NOTIFY_LOCATION); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } // If our BrowsingContext has private browsing enabled, update the number of // private browsing docshells. if (aBrowsingContext->UsePrivateBrowsing()) { ds->NotifyPrivateBrowsingChanged(); } // If our parent window is present in this process, set up our parent now. RefPtr parentWC = aBrowsingContext->GetParentWindowContext(); if (parentWC && parentWC->IsInProcess()) { // If we don't have a parent element anymore, we can't finish this load! // How'd we get here? RefPtr parentElement = aBrowsingContext->GetEmbedderElement(); if (!parentElement) { MOZ_ASSERT_UNREACHABLE("nsDocShell::Create() - !parentElement"); return nullptr; } // We have an in-process parent window, but don't have a parent nsDocShell? // How'd we get here! nsCOMPtr parentShell = parentElement->OwnerDoc()->GetDocShell(); if (!parentShell) { MOZ_ASSERT_UNREACHABLE("nsDocShell::Create() - !parentShell"); return nullptr; } parentShell->AddChild(ds); } // Make |ds| the primary DocShell for the given context. aBrowsingContext->SetDocShell(ds); // Set |ds| default load flags on load group. ds->SetLoadGroupDefaultLoadFlags(aBrowsingContext->GetDefaultLoadFlags()); if (XRE_IsParentProcess()) { aBrowsingContext->Canonical()->MaybeAddAsProgressListener(ds); } return ds.forget(); } void nsDocShell::DestroyChildren() { for (auto* child : mChildList.ForwardRange()) { nsCOMPtr shell = do_QueryObject(child); NS_ASSERTION(shell, "docshell has null child"); if (shell) { shell->SetTreeOwner(nullptr); } } nsDocLoader::DestroyChildren(); } NS_IMPL_CYCLE_COLLECTION_WEAK_PTR_INHERITED(nsDocShell, nsDocLoader, mScriptGlobal, mInitialClientSource, mBrowsingContext, mChromeEventHandler) NS_IMPL_ADDREF_INHERITED(nsDocShell, nsDocLoader) NS_IMPL_RELEASE_INHERITED(nsDocShell, nsDocLoader) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocShell) NS_INTERFACE_MAP_ENTRY(nsIDocShell) NS_INTERFACE_MAP_ENTRY(nsIDocShellTreeItem) NS_INTERFACE_MAP_ENTRY(nsIWebNavigation) NS_INTERFACE_MAP_ENTRY(nsIBaseWindow) NS_INTERFACE_MAP_ENTRY(nsIRefreshURI) NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) NS_INTERFACE_MAP_ENTRY(nsIWebPageDescriptor) NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider) NS_INTERFACE_MAP_ENTRY(nsILoadContext) NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsINetworkInterceptController, mInterceptController) NS_INTERFACE_MAP_END_INHERITING(nsDocLoader) NS_IMETHODIMP nsDocShell::GetInterface(const nsIID& aIID, void** aSink) { MOZ_ASSERT(aSink, "null out param"); *aSink = nullptr; if (aIID.Equals(NS_GET_IID(nsICommandManager))) { NS_ENSURE_SUCCESS(EnsureCommandHandler(), NS_ERROR_FAILURE); *aSink = static_cast(mCommandManager.get()); } else if (aIID.Equals(NS_GET_IID(nsIURIContentListener))) { *aSink = mContentListener; } else if ((aIID.Equals(NS_GET_IID(nsIScriptGlobalObject)) || aIID.Equals(NS_GET_IID(nsIGlobalObject)) || aIID.Equals(NS_GET_IID(nsPIDOMWindowOuter)) || aIID.Equals(NS_GET_IID(mozIDOMWindowProxy)) || aIID.Equals(NS_GET_IID(nsIDOMWindow))) && NS_SUCCEEDED(EnsureScriptEnvironment())) { return mScriptGlobal->QueryInterface(aIID, aSink); } else if (aIID.Equals(NS_GET_IID(Document)) && NS_SUCCEEDED(EnsureContentViewer())) { RefPtr doc = mContentViewer->GetDocument(); doc.forget(aSink); return *aSink ? NS_OK : NS_NOINTERFACE; } else if (aIID.Equals(NS_GET_IID(nsIPrompt)) && NS_SUCCEEDED(EnsureScriptEnvironment())) { nsresult rv; nsCOMPtr wwatch = do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); // Get the an auth prompter for our window so that the parenting // of the dialogs works as it should when using tabs. nsIPrompt* prompt; rv = wwatch->GetNewPrompter(mScriptGlobal, &prompt); NS_ENSURE_SUCCESS(rv, rv); *aSink = prompt; return NS_OK; } else if (aIID.Equals(NS_GET_IID(nsIAuthPrompt)) || aIID.Equals(NS_GET_IID(nsIAuthPrompt2))) { return NS_SUCCEEDED(GetAuthPrompt(PROMPT_NORMAL, aIID, aSink)) ? NS_OK : NS_NOINTERFACE; } else if (aIID.Equals(NS_GET_IID(nsISHistory))) { // This is deprecated, you should instead directly get // ChildSHistory from the browsing context. MOZ_DIAGNOSTIC_ASSERT( false, "Do not try to get a nsISHistory interface from nsIDocShell"); return NS_NOINTERFACE; } else if (aIID.Equals(NS_GET_IID(nsIWebBrowserFind))) { nsresult rv = EnsureFind(); if (NS_FAILED(rv)) { return rv; } *aSink = mFind; NS_ADDREF((nsISupports*)*aSink); return NS_OK; } else if (aIID.Equals(NS_GET_IID(nsISelectionDisplay))) { if (PresShell* presShell = GetPresShell()) { return presShell->QueryInterface(aIID, aSink); } } else if (aIID.Equals(NS_GET_IID(nsIDocShellTreeOwner))) { nsCOMPtr treeOwner; nsresult rv = GetTreeOwner(getter_AddRefs(treeOwner)); if (NS_SUCCEEDED(rv) && treeOwner) { return treeOwner->QueryInterface(aIID, aSink); } } else if (aIID.Equals(NS_GET_IID(nsIBrowserChild))) { *aSink = GetBrowserChild().take(); return *aSink ? NS_OK : NS_ERROR_FAILURE; } else { return nsDocLoader::GetInterface(aIID, aSink); } NS_IF_ADDREF(((nsISupports*)*aSink)); return *aSink ? NS_OK : NS_NOINTERFACE; } NS_IMETHODIMP nsDocShell::SetCancelContentJSEpoch(int32_t aEpoch) { // Note: this gets called fairly early (before a pageload actually starts). // We could probably defer this even longer. nsCOMPtr browserChild = GetBrowserChild(); static_cast(browserChild.get()) ->SetCancelContentJSEpoch(aEpoch); return NS_OK; } nsresult nsDocShell::CheckDisallowedJavascriptLoad( nsDocShellLoadState* aLoadState) { if (!net::SchemeIsJavascript(aLoadState->URI())) { return NS_OK; } if (nsCOMPtr targetPrincipal = GetInheritedPrincipal(/* aConsiderCurrentDocument */ true)) { if (!aLoadState->TriggeringPrincipal()->Subsumes(targetPrincipal)) { return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI; } return NS_OK; } return NS_ERROR_DOM_BAD_CROSS_ORIGIN_URI; } NS_IMETHODIMP nsDocShell::LoadURI(nsDocShellLoadState* aLoadState, bool aSetNavigating) { return LoadURI(aLoadState, aSetNavigating, false); } nsresult nsDocShell::LoadURI(nsDocShellLoadState* aLoadState, bool aSetNavigating, bool aContinueHandlingSubframeHistory) { MOZ_ASSERT(aLoadState, "Must have a valid load state!"); // NOTE: This comparison between what appears to be internal/external load // flags is intentional, as it's ensuring that the caller isn't using any of // the flags reserved for implementations by the `nsIWebNavigation` interface. // In the future, this check may be dropped. MOZ_ASSERT( (aLoadState->LoadFlags() & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0, "Should not have these flags set"); MOZ_ASSERT(aLoadState->TargetBrowsingContext().IsNull(), "Targeting doesn't occur until InternalLoad"); if (!aLoadState->TriggeringPrincipal()) { MOZ_ASSERT(false, "LoadURI must have a triggering principal"); return NS_ERROR_FAILURE; } MOZ_TRY(CheckDisallowedJavascriptLoad(aLoadState)); bool oldIsNavigating = mIsNavigating; auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = oldIsNavigating; }); if (aSetNavigating) { mIsNavigating = true; } PopupBlocker::PopupControlState popupState = PopupBlocker::openOverridden; if (aLoadState->HasLoadFlags(LOAD_FLAGS_ALLOW_POPUPS)) { popupState = PopupBlocker::openAllowed; // If we allow popups as part of the navigation, ensure we fake a user // interaction, so that popups can, in fact, be allowed to open. if (WindowContext* wc = mBrowsingContext->GetCurrentWindowContext()) { wc->NotifyUserGestureActivation(); } } AutoPopupStatePusher statePusher(popupState); if (aLoadState->GetCancelContentJSEpoch().isSome()) { SetCancelContentJSEpoch(*aLoadState->GetCancelContentJSEpoch()); } // Note: we allow loads to get through here even if mFiredUnloadEvent is // true; that case will get handled in LoadInternal or LoadHistoryEntry, // so we pass false as the second parameter to IsNavigationAllowed. // However, we don't allow the page to change location *in the middle of* // firing beforeunload, so we do need to check if *beforeunload* is currently // firing, so we call IsNavigationAllowed rather than just IsPrintingOrPP. if (!IsNavigationAllowed(true, false)) { return NS_OK; // JS may not handle returning of an error code } nsLoadFlags defaultLoadFlags = mBrowsingContext->GetDefaultLoadFlags(); if (aLoadState->HasLoadFlags(LOAD_FLAGS_FORCE_TRR)) { defaultLoadFlags |= nsIRequest::LOAD_TRR_ONLY_MODE; } else if (aLoadState->HasLoadFlags(LOAD_FLAGS_DISABLE_TRR)) { defaultLoadFlags |= nsIRequest::LOAD_TRR_DISABLED_MODE; } MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetDefaultLoadFlags(defaultLoadFlags)); if (!StartupTimeline::HasRecord(StartupTimeline::FIRST_LOAD_URI) && mItemType == typeContent && !NS_IsAboutBlank(aLoadState->URI())) { StartupTimeline::RecordOnce(StartupTimeline::FIRST_LOAD_URI); } // LoadType used to be set to a default value here, if no LoadInfo/LoadState // object was passed in. That functionality has been removed as of bug // 1492648. LoadType should now be set up by the caller at the time they // create their nsDocShellLoadState object to pass into LoadURI. MOZ_LOG( gDocShellLeakLog, LogLevel::Debug, ("nsDocShell[%p]: loading %s with flags 0x%08x", this, aLoadState->URI()->GetSpecOrDefault().get(), aLoadState->LoadFlags())); if ((!aLoadState->LoadIsFromSessionHistory() && !LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(), LOAD_FLAGS_REPLACE_HISTORY)) || aContinueHandlingSubframeHistory) { // This is possibly a subframe, so handle it accordingly. // // If history exists, it will be loaded into the aLoadState object, and the // LoadType will be changed. if (MaybeHandleSubframeHistory(aLoadState, aContinueHandlingSubframeHistory)) { // MaybeHandleSubframeHistory returns true if we need to continue loading // asynchronously. return NS_OK; } } if (aLoadState->LoadIsFromSessionHistory()) { MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell[%p]: loading from session history", this)); if (!mozilla::SessionHistoryInParent()) { nsCOMPtr entry = aLoadState->SHEntry(); return LoadHistoryEntry(entry, aLoadState->LoadType(), aLoadState->HasValidUserGestureActivation()); } // FIXME Null check aLoadState->GetLoadingSessionHistoryInfo()? return LoadHistoryEntry(*aLoadState->GetLoadingSessionHistoryInfo(), aLoadState->LoadType(), aLoadState->HasValidUserGestureActivation()); } // On history navigation via Back/Forward buttons, don't execute // automatic JavaScript redirection such as |location.href = ...| or // |window.open()| // // LOAD_NORMAL: window.open(...) etc. // LOAD_STOP_CONTENT: location.href = ..., location.assign(...) if ((aLoadState->LoadType() == LOAD_NORMAL || aLoadState->LoadType() == LOAD_STOP_CONTENT) && ShouldBlockLoadingForBackButton()) { return NS_OK; } BrowsingContext::Type bcType = mBrowsingContext->GetType(); // Set up the inheriting principal in LoadState. nsresult rv = aLoadState->SetupInheritingPrincipal( bcType, mBrowsingContext->OriginAttributesRef()); NS_ENSURE_SUCCESS(rv, rv); rv = aLoadState->SetupTriggeringPrincipal( mBrowsingContext->OriginAttributesRef()); NS_ENSURE_SUCCESS(rv, rv); aLoadState->CalculateLoadURIFlags(); MOZ_ASSERT(aLoadState->TypeHint().IsVoid(), "Typehint should be null when calling InternalLoad from LoadURI"); MOZ_ASSERT(aLoadState->FileName().IsVoid(), "FileName should be null when calling InternalLoad from LoadURI"); MOZ_ASSERT(!aLoadState->LoadIsFromSessionHistory(), "Shouldn't be loading from an entry when calling InternalLoad " "from LoadURI"); // If we have a system triggering principal, we can assume that this load was // triggered by some UI in the browser chrome, such as the URL bar or // bookmark bar. This should count as a user interaction for the current sh // entry, so that the user may navigate back to the current entry, from the // entry that is going to be added as part of this load. nsCOMPtr triggeringPrincipal = aLoadState->TriggeringPrincipal(); if (triggeringPrincipal && triggeringPrincipal->IsSystemPrincipal()) { if (mozilla::SessionHistoryInParent()) { WindowContext* topWc = mBrowsingContext->GetTopWindowContext(); if (topWc && !topWc->IsDiscarded()) { MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(true)); } } else { bool oshe = false; nsCOMPtr currentSHEntry; GetCurrentSHEntry(getter_AddRefs(currentSHEntry), &oshe); if (currentSHEntry) { currentSHEntry->SetHasUserInteraction(true); } } } rv = InternalLoad(aLoadState); NS_ENSURE_SUCCESS(rv, rv); if (aLoadState->GetOriginalURIString().isSome()) { // Save URI string in case it's needed later when // sending to search engine service in EndPageLoad() mOriginalUriString = *aLoadState->GetOriginalURIString(); } return NS_OK; } bool nsDocShell::IsLoadingFromSessionHistory() { return mActiveEntryIsLoadingFromSessionHistory; } // StopDetector is modeled similarly to OnloadBlocker; it is a rather // dummy nsIRequest implementation which can be added to an nsILoadGroup to // detect Cancel calls. class StopDetector final : public nsIRequest { public: StopDetector() = default; NS_DECL_ISUPPORTS NS_DECL_NSIREQUEST bool Canceled() { return mCanceled; } private: ~StopDetector() = default; bool mCanceled = false; }; NS_IMPL_ISUPPORTS(StopDetector, nsIRequest) NS_IMETHODIMP StopDetector::GetName(nsACString& aResult) { aResult.AssignLiteral("about:stop-detector"); return NS_OK; } NS_IMETHODIMP StopDetector::IsPending(bool* aRetVal) { *aRetVal = true; return NS_OK; } NS_IMETHODIMP StopDetector::GetStatus(nsresult* aStatus) { *aStatus = NS_OK; return NS_OK; } NS_IMETHODIMP StopDetector::SetCanceledReason(const nsACString& aReason) { return SetCanceledReasonImpl(aReason); } NS_IMETHODIMP StopDetector::GetCanceledReason(nsACString& aReason) { return GetCanceledReasonImpl(aReason); } NS_IMETHODIMP StopDetector::CancelWithReason(nsresult aStatus, const nsACString& aReason) { return CancelWithReasonImpl(aStatus, aReason); } NS_IMETHODIMP StopDetector::Cancel(nsresult aStatus) { mCanceled = true; return NS_OK; } NS_IMETHODIMP StopDetector::Suspend(void) { return NS_OK; } NS_IMETHODIMP StopDetector::Resume(void) { return NS_OK; } NS_IMETHODIMP StopDetector::GetLoadGroup(nsILoadGroup** aLoadGroup) { *aLoadGroup = nullptr; return NS_OK; } NS_IMETHODIMP StopDetector::SetLoadGroup(nsILoadGroup* aLoadGroup) { return NS_OK; } NS_IMETHODIMP StopDetector::GetLoadFlags(nsLoadFlags* aLoadFlags) { *aLoadFlags = nsIRequest::LOAD_NORMAL; return NS_OK; } NS_IMETHODIMP StopDetector::GetTRRMode(nsIRequest::TRRMode* aTRRMode) { return GetTRRModeImpl(aTRRMode); } NS_IMETHODIMP StopDetector::SetTRRMode(nsIRequest::TRRMode aTRRMode) { return SetTRRModeImpl(aTRRMode); } NS_IMETHODIMP StopDetector::SetLoadFlags(nsLoadFlags aLoadFlags) { return NS_OK; } bool nsDocShell::MaybeHandleSubframeHistory( nsDocShellLoadState* aLoadState, bool aContinueHandlingSubframeHistory) { // First, verify if this is a subframe. // Note, it is ok to rely on docshell here and not browsing context since when // an iframe is created, it has first in-process docshell. nsCOMPtr parentAsItem; GetInProcessSameTypeParent(getter_AddRefs(parentAsItem)); nsCOMPtr parentDS(do_QueryInterface(parentAsItem)); if (!parentDS || parentDS == static_cast(this)) { if (mBrowsingContext && mBrowsingContext->IsTop()) { // This is the root docshell. If we got here while // executing an onLoad Handler,this load will not go // into session history. // XXX Why is this code in a method which deals with iframes! bool inOnLoadHandler = false; GetIsExecutingOnLoadHandler(&inOnLoadHandler); if (inOnLoadHandler) { aLoadState->SetLoadType(LOAD_NORMAL_REPLACE); } } return false; } /* OK. It is a subframe. Checkout the parent's loadtype. If the parent was * loaded through a history mechanism, then get the SH entry for the child * from the parent. This is done to restore frameset navigation while going * back/forward. If the parent was loaded through any other loadType, set the * child's loadType too accordingly, so that session history does not get * confused. */ // Get the parent's load type uint32_t parentLoadType; parentDS->GetLoadType(&parentLoadType); if (!aContinueHandlingSubframeHistory) { if (mozilla::SessionHistoryInParent()) { if (nsDocShell::Cast(parentDS.get())->IsLoadingFromSessionHistory() && !GetCreatedDynamically()) { if (XRE_IsContentProcess()) { dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); nsCOMPtr loadGroup; GetLoadGroup(getter_AddRefs(loadGroup)); if (contentChild && loadGroup && !mCheckingSessionHistory) { RefPtr parentDoc = parentDS->GetDocument(); parentDoc->BlockOnload(); RefPtr browsingContext = mBrowsingContext; Maybe currentLoadIdentifier = mBrowsingContext->GetCurrentLoadIdentifier(); RefPtr loadState = aLoadState; bool isNavigating = mIsNavigating; RefPtr stopDetector = new StopDetector(); loadGroup->AddRequest(stopDetector, nullptr); // Need to set mCheckingSessionHistory so that // GetIsAttemptingToNavigate() returns true. mCheckingSessionHistory = true; auto resolve = [currentLoadIdentifier, browsingContext, parentDoc, loadState, isNavigating, loadGroup, stopDetector]( mozilla::Maybe&& aResult) { RefPtr docShell = static_cast(browsingContext->GetDocShell()); auto unblockParent = MakeScopeExit( [loadGroup, stopDetector, parentDoc, docShell]() { if (docShell) { docShell->mCheckingSessionHistory = false; } loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK); parentDoc->UnblockOnload(false); }); if (!docShell || !docShell->mCheckingSessionHistory) { return; } if (stopDetector->Canceled()) { return; } if (currentLoadIdentifier == browsingContext->GetCurrentLoadIdentifier() && aResult.isSome()) { loadState->SetLoadingSessionHistoryInfo(aResult.value()); // This is an initial subframe load from the session // history, index doesn't need to be updated. loadState->SetLoadIsFromSessionHistory(0, false); } // We got the results back from the parent process, call // LoadURI again with the possibly updated data. docShell->LoadURI(loadState, isNavigating, true); }; auto reject = [loadGroup, stopDetector, browsingContext, parentDoc](mozilla::ipc::ResponseRejectReason) { RefPtr docShell = static_cast(browsingContext->GetDocShell()); if (docShell) { docShell->mCheckingSessionHistory = false; } // In practise reject shouldn't be called ever. loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK); parentDoc->UnblockOnload(false); }; contentChild->SendGetLoadingSessionHistoryInfoFromParent( mBrowsingContext, std::move(resolve), std::move(reject)); return true; } } else { Maybe info; mBrowsingContext->Canonical()->GetLoadingSessionHistoryInfoFromParent( info); if (info.isSome()) { aLoadState->SetLoadingSessionHistoryInfo(info.value()); // This is an initial subframe load from the session // history, index doesn't need to be updated. aLoadState->SetLoadIsFromSessionHistory(0, false); } } } } else { // Get the ShEntry for the child from the parent nsCOMPtr currentSH; bool oshe = false; parentDS->GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe); bool dynamicallyAddedChild = GetCreatedDynamically(); if (!dynamicallyAddedChild && !oshe && currentSH) { // Only use the old SHEntry, if we're sure enough that // it wasn't originally for some other frame. nsCOMPtr shEntry; currentSH->GetChildSHEntryIfHasNoDynamicallyAddedChild( mBrowsingContext->ChildOffset(), getter_AddRefs(shEntry)); if (shEntry) { aLoadState->SetSHEntry(shEntry); } } } } // Make some decisions on the child frame's loadType based on the // parent's loadType, if the subframe hasn't loaded anything into it. // // In some cases privileged scripts may try to get the DOMWindow // reference of this docshell before the loading starts, causing the // initial about:blank content viewer being created and mCurrentURI being // set. To handle this case we check if mCurrentURI is about:blank and // currentSHEntry is null. bool oshe = false; nsCOMPtr currentChildEntry; GetCurrentSHEntry(getter_AddRefs(currentChildEntry), &oshe); if (mCurrentURI && (!NS_IsAboutBlank(mCurrentURI) || currentChildEntry || mLoadingEntry || mActiveEntry)) { // This is a pre-existing subframe. If // 1. The load of this frame was not originally initiated by session // history directly (i.e. (!shEntry) condition succeeded, but it can // still be a history load on parent which causes this frame being // loaded), which we checked with the above assert, and // 2. mCurrentURI is not null, nor the initial about:blank, // it is possible that a parent's onLoadHandler or even self's // onLoadHandler is loading a new page in this child. Check parent's and // self's busy flag and if it is set, we don't want this onLoadHandler // load to get in to session history. BusyFlags parentBusy = parentDS->GetBusyFlags(); BusyFlags selfBusy = GetBusyFlags(); if (parentBusy & BUSY_FLAGS_BUSY || selfBusy & BUSY_FLAGS_BUSY) { aLoadState->SetLoadType(LOAD_NORMAL_REPLACE); aLoadState->ClearLoadIsFromSessionHistory(); } return false; } // This is a newly created frame. Check for exception cases first. // By default the subframe will inherit the parent's loadType. if (aLoadState->LoadIsFromSessionHistory() && (parentLoadType == LOAD_NORMAL || parentLoadType == LOAD_LINK)) { // The parent was loaded normally. In this case, this *brand new* // child really shouldn't have a SHEntry. If it does, it could be // because the parent is replacing an existing frame with a new frame, // in the onLoadHandler. We don't want this url to get into session // history. Clear off shEntry, and set load type to // LOAD_BYPASS_HISTORY. bool inOnLoadHandler = false; parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler); if (inOnLoadHandler) { aLoadState->SetLoadType(LOAD_NORMAL_REPLACE); aLoadState->ClearLoadIsFromSessionHistory(); } } else if (parentLoadType == LOAD_REFRESH) { // Clear shEntry. For refresh loads, we have to load // what comes through the pipe, not what's in history. aLoadState->ClearLoadIsFromSessionHistory(); } else if ((parentLoadType == LOAD_BYPASS_HISTORY) || (aLoadState->LoadIsFromSessionHistory() && ((parentLoadType & LOAD_CMD_HISTORY) || (parentLoadType == LOAD_RELOAD_NORMAL) || (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE) || (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE) || (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE)))) { // If the parent url, bypassed history or was loaded from // history, pass on the parent's loadType to the new child // frame too, so that the child frame will also // avoid getting into history. aLoadState->SetLoadType(parentLoadType); } else if (parentLoadType == LOAD_ERROR_PAGE) { // If the parent document is an error page, we don't // want to update global/session history. However, // this child frame is not an error page. aLoadState->SetLoadType(LOAD_BYPASS_HISTORY); } else if ((parentLoadType == LOAD_RELOAD_BYPASS_CACHE) || (parentLoadType == LOAD_RELOAD_BYPASS_PROXY) || (parentLoadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE)) { // the new frame should inherit the parent's load type so that it also // bypasses the cache and/or proxy aLoadState->SetLoadType(parentLoadType); } return false; } /* * Reset state to a new content model within the current document and the * document viewer. Called by the document before initiating an out of band * document.write(). */ NS_IMETHODIMP nsDocShell::PrepareForNewContentModel() { // Clear out our form control state, because the state of controls // in the pre-open() document should not affect the state of // controls that are now going to be written. SetLayoutHistoryState(nullptr); mEODForCurrentDocument = false; return NS_OK; } NS_IMETHODIMP nsDocShell::FirePageHideNotification(bool aIsUnload) { FirePageHideNotificationInternal(aIsUnload, false); return NS_OK; } void nsDocShell::FirePageHideNotificationInternal( bool aIsUnload, bool aSkipCheckingDynEntries) { if (mContentViewer && !mFiredUnloadEvent) { // Keep an explicit reference since calling PageHide could release // mContentViewer nsCOMPtr contentViewer(mContentViewer); mFiredUnloadEvent = true; if (mTiming) { mTiming->NotifyUnloadEventStart(); } contentViewer->PageHide(aIsUnload); if (mTiming) { mTiming->NotifyUnloadEventEnd(); } AutoTArray, 8> kids; uint32_t n = mChildList.Length(); kids.SetCapacity(n); for (uint32_t i = 0; i < n; i++) { kids.AppendElement(do_QueryInterface(ChildAt(i))); } n = kids.Length(); for (uint32_t i = 0; i < n; ++i) { RefPtr child = static_cast(kids[i].get()); if (child) { // Skip checking dynamic subframe entries in our children. child->FirePageHideNotificationInternal(aIsUnload, true); } } // If the document is unloading, remove all dynamic subframe entries. if (aIsUnload && !aSkipCheckingDynEntries) { RefPtr rootSH = GetRootSessionHistory(); if (rootSH) { MOZ_LOG( gSHLog, LogLevel::Debug, ("nsDocShell %p unloading, remove dynamic subframe entries", this)); if (mozilla::SessionHistoryInParent()) { if (mActiveEntry) { mBrowsingContext->RemoveDynEntriesFromActiveSessionHistoryEntry(); } MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p unloading, no active entries", this)); } else if (mOSHE) { int32_t index = rootSH->Index(); rootSH->LegacySHistory()->RemoveDynEntries(index, mOSHE); } } } // Now make sure our editor, if any, is detached before we go // any farther. DetachEditorFromWindow(); } } void nsDocShell::ThawFreezeNonRecursive(bool aThaw) { MOZ_ASSERT(mozilla::BFCacheInParent()); if (!mScriptGlobal) { return; } RefPtr inner = mScriptGlobal->GetCurrentInnerWindowInternal(); if (inner) { if (aThaw) { inner->Thaw(false); } else { inner->Freeze(false); } } } void nsDocShell::FirePageHideShowNonRecursive(bool aShow) { MOZ_ASSERT(mozilla::BFCacheInParent()); if (!mContentViewer) { return; } // Emulate what non-SHIP BFCache does too. In pageshow case // add and remove a request and before that call SetCurrentURI to get // the location change notification. // For pagehide, set mFiredUnloadEvent to true, so that unload doesn't fire. nsCOMPtr contentViewer(mContentViewer); if (aShow) { contentViewer->SetIsHidden(false); mRefreshURIList = std::move(mBFCachedRefreshURIList); RefreshURIFromQueue(); mFiredUnloadEvent = false; RefPtr doc = contentViewer->GetDocument(); if (doc) { doc->NotifyActivityChanged(); RefPtr inner = mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; if (mBrowsingContext->IsTop()) { doc->NotifyPossibleTitleChange(false); doc->SetLoadingOrRestoredFromBFCacheTimeStampToNow(); if (inner) { // Now that we have found the inner window of the page restored // from the history, we have to make sure that // performance.navigation.type is 2. // Traditionally this type change has been done to the top level page // only. Performance* performance = inner->GetPerformance(); if (performance) { performance->GetDOMTiming()->NotifyRestoreStart(); } } } nsCOMPtr channel = doc->GetChannel(); if (channel) { SetLoadType(LOAD_HISTORY); mEODForCurrentDocument = false; mIsRestoringDocument = true; mLoadGroup->AddRequest(channel, nullptr); SetCurrentURI(doc->GetDocumentURI(), channel, /* aFireOnLocationChange */ true, /* aIsInitialAboutBlank */ false, /* aLocationFlags */ 0); mLoadGroup->RemoveRequest(channel, nullptr, NS_OK); mIsRestoringDocument = false; } RefPtr presShell = GetPresShell(); if (presShell) { presShell->Thaw(false); } if (inner) { inner->FireDelayedDOMEvents(false); } } } else if (!mFiredUnloadEvent) { // XXXBFCache check again that the page can enter bfcache. // XXXBFCache should mTiming->NotifyUnloadEventStart()/End() be called here? if (mRefreshURIList) { RefreshURIToQueue(); mBFCachedRefreshURIList = std::move(mRefreshURIList); } else { // If Stop was called, the list was moved to mSavedRefreshURIList after // calling SuspendRefreshURIs, which calls RefreshURIToQueue. mBFCachedRefreshURIList = std::move(mSavedRefreshURIList); } mFiredUnloadEvent = true; contentViewer->PageHide(false); RefPtr presShell = GetPresShell(); if (presShell) { presShell->Freeze(false); } } } nsresult nsDocShell::Dispatch(TaskCategory aCategory, already_AddRefed&& aRunnable) { nsCOMPtr runnable(aRunnable); nsCOMPtr win = GetWindow(); if (NS_WARN_IF(!win)) { // Window should only be unavailable after destroyed. MOZ_ASSERT(mIsBeingDestroyed); return NS_ERROR_FAILURE; } if (win->GetDocGroup()) { return win->GetDocGroup()->Dispatch(aCategory, runnable.forget()); } return SchedulerGroup::Dispatch(aCategory, runnable.forget()); } NS_IMETHODIMP nsDocShell::DispatchLocationChangeEvent() { return Dispatch( TaskCategory::Other, NewRunnableMethod("nsDocShell::FireDummyOnLocationChange", this, &nsDocShell::FireDummyOnLocationChange)); } NS_IMETHODIMP nsDocShell::StartDelayedAutoplayMediaComponents() { RefPtr outerWindow = GetWindow(); if (outerWindow) { outerWindow->ActivateMediaComponents(); } return NS_OK; } bool nsDocShell::MaybeInitTiming() { if (mTiming && !mBlankTiming) { return false; } bool canBeReset = false; if (mScriptGlobal && mBlankTiming) { nsPIDOMWindowInner* innerWin = mScriptGlobal->GetCurrentInnerWindow(); if (innerWin && innerWin->GetPerformance()) { mTiming = innerWin->GetPerformance()->GetDOMTiming(); mBlankTiming = false; } } if (!mTiming) { mTiming = new nsDOMNavigationTiming(this); canBeReset = true; } mTiming->NotifyNavigationStart( mBrowsingContext->IsActive() ? nsDOMNavigationTiming::DocShellState::eActive : nsDOMNavigationTiming::DocShellState::eInactive); return canBeReset; } void nsDocShell::MaybeResetInitTiming(bool aReset) { if (aReset) { mTiming = nullptr; } } nsDOMNavigationTiming* nsDocShell::GetNavigationTiming() const { return mTiming; } nsPresContext* nsDocShell::GetEldestPresContext() { nsIContentViewer* viewer = mContentViewer; while (viewer) { nsIContentViewer* prevViewer = viewer->GetPreviousViewer(); if (!prevViewer) { return viewer->GetPresContext(); } viewer = prevViewer; } return nullptr; } nsPresContext* nsDocShell::GetPresContext() { if (!mContentViewer) { return nullptr; } return mContentViewer->GetPresContext(); } PresShell* nsDocShell::GetPresShell() { nsPresContext* presContext = GetPresContext(); return presContext ? presContext->GetPresShell() : nullptr; } PresShell* nsDocShell::GetEldestPresShell() { nsPresContext* presContext = GetEldestPresContext(); if (presContext) { return presContext->GetPresShell(); } return nullptr; } NS_IMETHODIMP nsDocShell::GetContentViewer(nsIContentViewer** aContentViewer) { NS_ENSURE_ARG_POINTER(aContentViewer); *aContentViewer = mContentViewer; NS_IF_ADDREF(*aContentViewer); return NS_OK; } NS_IMETHODIMP nsDocShell::GetOuterWindowID(uint64_t* aWindowID) { *aWindowID = mContentWindowID; return NS_OK; } NS_IMETHODIMP nsDocShell::SetChromeEventHandler(EventTarget* aChromeEventHandler) { mChromeEventHandler = aChromeEventHandler; if (mScriptGlobal) { mScriptGlobal->SetChromeEventHandler(mChromeEventHandler); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetChromeEventHandler(EventTarget** aChromeEventHandler) { NS_ENSURE_ARG_POINTER(aChromeEventHandler); RefPtr handler = mChromeEventHandler; handler.forget(aChromeEventHandler); return NS_OK; } NS_IMETHODIMP nsDocShell::SetCurrentURIForSessionStore(nsIURI* aURI) { // Note that securityUI will set STATE_IS_INSECURE, even if // the scheme of |aURI| is "https". SetCurrentURI(aURI, nullptr, /* aFireOnLocationChange */ true, /* aIsInitialAboutBlank */ false, /* aLocationFlags */ nsIWebProgressListener::LOCATION_CHANGE_SESSION_STORE); return NS_OK; } bool nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest, bool aFireOnLocationChange, bool aIsInitialAboutBlank, uint32_t aLocationFlags) { MOZ_ASSERT(!mIsBeingDestroyed); MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p SetCurrentURI %s\n", this, aURI ? aURI->GetSpecOrDefault().get() : "")); // We don't want to send a location change when we're displaying an error // page, and we don't want to change our idea of "current URI" either if (mLoadType == LOAD_ERROR_PAGE) { return false; } bool uriIsEqual = false; if (!mCurrentURI || !aURI || NS_FAILED(mCurrentURI->Equals(aURI, &uriIsEqual)) || !uriIsEqual) { mTitleValidForCurrentURI = false; } mCurrentURI = aURI; #ifdef DEBUG mLastOpenedURI = aURI; #endif if (!NS_IsAboutBlank(mCurrentURI)) { mHasLoadedNonBlankURI = true; } // Don't fire onLocationChange when creating a subframe's initial about:blank // document, as this can happen when it's not safe for us to run script. if (aIsInitialAboutBlank && !mHasLoadedNonBlankURI && !mBrowsingContext->IsTop()) { MOZ_ASSERT(!aRequest && aLocationFlags == 0); return false; } MOZ_ASSERT(nsContentUtils::IsSafeToRunScript()); if (aFireOnLocationChange) { FireOnLocationChange(this, aRequest, aURI, aLocationFlags); } return !aFireOnLocationChange; } NS_IMETHODIMP nsDocShell::GetCharset(nsACString& aCharset) { aCharset.Truncate(); PresShell* presShell = GetPresShell(); NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); Document* doc = presShell->GetDocument(); NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); doc->GetDocumentCharacterSet()->Name(aCharset); return NS_OK; } NS_IMETHODIMP nsDocShell::ForceEncodingDetection() { nsCOMPtr viewer; GetContentViewer(getter_AddRefs(viewer)); if (!viewer) { return NS_OK; } Document* doc = viewer->GetDocument(); if (!doc || doc->WillIgnoreCharsetOverride()) { return NS_OK; } mForcedAutodetection = true; nsIURI* url = doc->GetOriginalURI(); bool isFileURL = url && SchemeIsFile(url); int32_t charsetSource = doc->GetDocumentCharacterSetSource(); auto encoding = doc->GetDocumentCharacterSet(); // AsHTMLDocument is valid, because we called // WillIgnoreCharsetOverride() above. if (doc->AsHTMLDocument()->IsPlainText()) { switch (charsetSource) { case kCharsetFromInitialAutoDetectionASCII: // Deliberately no final version LOGCHARSETMENU(("TEXT:UnlabeledAscii")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::UnlabeledAscii); break; case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic: case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic: case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII: case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content: case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content: case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII: LOGCHARSETMENU(("TEXT:UnlabeledNonUtf8")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT:: UnlabeledNonUtf8); break; case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII: LOGCHARSETMENU(("TEXT:UnlabeledNonUtf8TLD")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT:: UnlabeledNonUtf8TLD); break; case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8: case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII: LOGCHARSETMENU(("TEXT:UnlabeledUtf8")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::UnlabeledUtf8); break; case kCharsetFromChannel: if (encoding == UTF_8_ENCODING) { LOGCHARSETMENU(("TEXT:ChannelUtf8")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::ChannelUtf8); } else { LOGCHARSETMENU(("TEXT:ChannelNonUtf8")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT:: ChannelNonUtf8); } break; default: LOGCHARSETMENU(("TEXT:Bug")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_TEXT::Bug); break; } } else { switch (charsetSource) { case kCharsetFromInitialAutoDetectionASCII: // Deliberately no final version LOGCHARSETMENU(("HTML:UnlabeledAscii")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::UnlabeledAscii); break; case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic: case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic: case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII: case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content: case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content: case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII: LOGCHARSETMENU(("HTML:UnlabeledNonUtf8")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML:: UnlabeledNonUtf8); break; case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD: case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII: LOGCHARSETMENU(("HTML:UnlabeledNonUtf8TLD")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML:: UnlabeledNonUtf8TLD); break; case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8: case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII: LOGCHARSETMENU(("HTML:UnlabeledUtf8")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::UnlabeledUtf8); break; case kCharsetFromChannel: if (encoding == UTF_8_ENCODING) { LOGCHARSETMENU(("HTML:ChannelUtf8")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::ChannelUtf8); } else { LOGCHARSETMENU(("HTML:ChannelNonUtf8")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML:: ChannelNonUtf8); } break; case kCharsetFromXmlDeclaration: case kCharsetFromMetaTag: if (isFileURL) { LOGCHARSETMENU(("HTML:LocalLabeled")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::LocalLabeled); } else if (encoding == UTF_8_ENCODING) { LOGCHARSETMENU(("HTML:MetaUtf8")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::InternalUtf8); } else { LOGCHARSETMENU(("HTML:MetaNonUtf8")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML:: InternalNonUtf8); } break; default: LOGCHARSETMENU(("HTML:Bug")); Telemetry::AccumulateCategorical( Telemetry::LABELS_ENCODING_OVERRIDE_SITUATION_HTML::Bug); break; } } return NS_OK; } void nsDocShell::SetParentCharset(const Encoding*& aCharset, int32_t aCharsetSource, nsIPrincipal* aPrincipal) { mParentCharset = aCharset; mParentCharsetSource = aCharsetSource; mParentCharsetPrincipal = aPrincipal; } void nsDocShell::GetParentCharset(const Encoding*& aCharset, int32_t* aCharsetSource, nsIPrincipal** aPrincipal) { aCharset = mParentCharset; *aCharsetSource = mParentCharsetSource; NS_IF_ADDREF(*aPrincipal = mParentCharsetPrincipal); } NS_IMETHODIMP nsDocShell::GetHasTrackingContentBlocked(Promise** aPromise) { MOZ_ASSERT(aPromise); ErrorResult rv; RefPtr doc(GetDocument()); RefPtr retPromise = Promise::Create(doc->GetOwnerGlobal(), rv); if (NS_WARN_IF(rv.Failed())) { return rv.StealNSResult(); } // Retrieve the document's content blocking events from the parent process. RefPtr promise = doc->GetContentBlockingEvents(); if (promise) { promise->Then( GetCurrentSerialEventTarget(), __func__, [retPromise](const Document::GetContentBlockingEventsPromise:: ResolveOrRejectValue& aValue) { if (aValue.IsResolve()) { bool has = aValue.ResolveValue() & nsIWebProgressListener::STATE_BLOCKED_TRACKING_CONTENT; retPromise->MaybeResolve(has); } else { retPromise->MaybeResolve(false); } }); } else { retPromise->MaybeResolve(false); } retPromise.forget(aPromise); return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowPlugins(bool* aAllowPlugins) { NS_ENSURE_ARG_POINTER(aAllowPlugins); *aAllowPlugins = mBrowsingContext->GetAllowPlugins(); return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowPlugins(bool aAllowPlugins) { // XXX should enable or disable a plugin host return mBrowsingContext->SetAllowPlugins(aAllowPlugins); } NS_IMETHODIMP nsDocShell::GetCssErrorReportingEnabled(bool* aEnabled) { MOZ_ASSERT(aEnabled); *aEnabled = mCSSErrorReportingEnabled; return NS_OK; } NS_IMETHODIMP nsDocShell::SetCssErrorReportingEnabled(bool aEnabled) { mCSSErrorReportingEnabled = aEnabled; return NS_OK; } NS_IMETHODIMP nsDocShell::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) { NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing); return mBrowsingContext->GetUsePrivateBrowsing(aUsePrivateBrowsing); } void nsDocShell::NotifyPrivateBrowsingChanged() { MOZ_ASSERT(!mIsBeingDestroyed); nsTObserverArray::ForwardIterator iter(mPrivacyObservers); while (iter.HasMore()) { nsWeakPtr ref = iter.GetNext(); nsCOMPtr obs = do_QueryReferent(ref); if (!obs) { iter.Remove(); } else { obs->PrivateModeChanged(UsePrivateBrowsing()); } } } NS_IMETHODIMP nsDocShell::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) { return mBrowsingContext->SetUsePrivateBrowsing(aUsePrivateBrowsing); } NS_IMETHODIMP nsDocShell::SetPrivateBrowsing(bool aUsePrivateBrowsing) { return mBrowsingContext->SetPrivateBrowsing(aUsePrivateBrowsing); } NS_IMETHODIMP nsDocShell::GetHasLoadedNonBlankURI(bool* aResult) { NS_ENSURE_ARG_POINTER(aResult); *aResult = mHasLoadedNonBlankURI; return NS_OK; } NS_IMETHODIMP nsDocShell::GetUseRemoteTabs(bool* aUseRemoteTabs) { NS_ENSURE_ARG_POINTER(aUseRemoteTabs); return mBrowsingContext->GetUseRemoteTabs(aUseRemoteTabs); } NS_IMETHODIMP nsDocShell::SetRemoteTabs(bool aUseRemoteTabs) { return mBrowsingContext->SetRemoteTabs(aUseRemoteTabs); } NS_IMETHODIMP nsDocShell::GetUseRemoteSubframes(bool* aUseRemoteSubframes) { NS_ENSURE_ARG_POINTER(aUseRemoteSubframes); return mBrowsingContext->GetUseRemoteSubframes(aUseRemoteSubframes); } NS_IMETHODIMP nsDocShell::SetRemoteSubframes(bool aUseRemoteSubframes) { return mBrowsingContext->SetRemoteSubframes(aUseRemoteSubframes); } NS_IMETHODIMP nsDocShell::AddWeakPrivacyTransitionObserver( nsIPrivacyTransitionObserver* aObserver) { nsWeakPtr weakObs = do_GetWeakReference(aObserver); if (!weakObs) { return NS_ERROR_NOT_AVAILABLE; } mPrivacyObservers.AppendElement(weakObs); return NS_OK; } NS_IMETHODIMP nsDocShell::AddWeakReflowObserver(nsIReflowObserver* aObserver) { nsWeakPtr weakObs = do_GetWeakReference(aObserver); if (!weakObs) { return NS_ERROR_FAILURE; } mReflowObservers.AppendElement(weakObs); return NS_OK; } NS_IMETHODIMP nsDocShell::RemoveWeakReflowObserver(nsIReflowObserver* aObserver) { nsWeakPtr obs = do_GetWeakReference(aObserver); return mReflowObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE; } NS_IMETHODIMP nsDocShell::NotifyReflowObservers(bool aInterruptible, DOMHighResTimeStamp aStart, DOMHighResTimeStamp aEnd) { nsTObserverArray::ForwardIterator iter(mReflowObservers); while (iter.HasMore()) { nsWeakPtr ref = iter.GetNext(); nsCOMPtr obs = do_QueryReferent(ref); if (!obs) { iter.Remove(); } else if (aInterruptible) { obs->ReflowInterruptible(aStart, aEnd); } else { obs->Reflow(aStart, aEnd); } } return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowMetaRedirects(bool* aReturn) { NS_ENSURE_ARG_POINTER(aReturn); *aReturn = mAllowMetaRedirects; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowMetaRedirects(bool aValue) { mAllowMetaRedirects = aValue; return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowSubframes(bool* aAllowSubframes) { NS_ENSURE_ARG_POINTER(aAllowSubframes); *aAllowSubframes = mAllowSubframes; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowSubframes(bool aAllowSubframes) { mAllowSubframes = aAllowSubframes; return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowImages(bool* aAllowImages) { NS_ENSURE_ARG_POINTER(aAllowImages); *aAllowImages = mAllowImages; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowImages(bool aAllowImages) { mAllowImages = aAllowImages; return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowMedia(bool* aAllowMedia) { *aAllowMedia = mAllowMedia; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowMedia(bool aAllowMedia) { mAllowMedia = aAllowMedia; // Mute or unmute audio contexts attached to the inner window. if (mScriptGlobal) { if (nsPIDOMWindowInner* innerWin = mScriptGlobal->GetCurrentInnerWindow()) { if (aAllowMedia) { innerWin->UnmuteAudioContexts(); } else { innerWin->MuteAudioContexts(); } } } return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowDNSPrefetch(bool* aAllowDNSPrefetch) { *aAllowDNSPrefetch = mAllowDNSPrefetch; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowDNSPrefetch(bool aAllowDNSPrefetch) { mAllowDNSPrefetch = aAllowDNSPrefetch; return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowWindowControl(bool* aAllowWindowControl) { *aAllowWindowControl = mAllowWindowControl; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowWindowControl(bool aAllowWindowControl) { mAllowWindowControl = aAllowWindowControl; return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowContentRetargeting(bool* aAllowContentRetargeting) { *aAllowContentRetargeting = mBrowsingContext->GetAllowContentRetargeting(); return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowContentRetargeting(bool aAllowContentRetargeting) { BrowsingContext::Transaction txn; txn.SetAllowContentRetargeting(aAllowContentRetargeting); txn.SetAllowContentRetargetingOnChildren(aAllowContentRetargeting); return txn.Commit(mBrowsingContext); } NS_IMETHODIMP nsDocShell::GetAllowContentRetargetingOnChildren( bool* aAllowContentRetargetingOnChildren) { *aAllowContentRetargetingOnChildren = mBrowsingContext->GetAllowContentRetargetingOnChildren(); return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowContentRetargetingOnChildren( bool aAllowContentRetargetingOnChildren) { return mBrowsingContext->SetAllowContentRetargetingOnChildren( aAllowContentRetargetingOnChildren); } NS_IMETHODIMP nsDocShell::GetMayEnableCharacterEncodingMenu( bool* aMayEnableCharacterEncodingMenu) { *aMayEnableCharacterEncodingMenu = false; if (!mContentViewer) { return NS_OK; } Document* doc = mContentViewer->GetDocument(); if (!doc) { return NS_OK; } if (doc->WillIgnoreCharsetOverride()) { return NS_OK; } *aMayEnableCharacterEncodingMenu = true; return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllDocShellsInSubtree(int32_t aItemType, DocShellEnumeratorDirection aDirection, nsTArray>& aResult) { aResult.Clear(); nsDocShellEnumerator docShellEnum( (aDirection == ENUMERATE_FORWARDS) ? nsDocShellEnumerator::EnumerationDirection::Forwards : nsDocShellEnumerator::EnumerationDirection::Backwards, aItemType, *this); nsresult rv = docShellEnum.BuildDocShellArray(aResult); if (NS_FAILED(rv)) { return rv; } return NS_OK; } NS_IMETHODIMP nsDocShell::GetAppType(AppType* aAppType) { *aAppType = mAppType; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAppType(AppType aAppType) { mAppType = aAppType; return NS_OK; } NS_IMETHODIMP nsDocShell::GetAllowAuth(bool* aAllowAuth) { *aAllowAuth = mAllowAuth; return NS_OK; } NS_IMETHODIMP nsDocShell::SetAllowAuth(bool aAllowAuth) { mAllowAuth = aAllowAuth; return NS_OK; } NS_IMETHODIMP nsDocShell::GetZoom(float* aZoom) { NS_ENSURE_ARG_POINTER(aZoom); *aZoom = 1.0f; return NS_OK; } NS_IMETHODIMP nsDocShell::SetZoom(float aZoom) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDocShell::GetBusyFlags(BusyFlags* aBusyFlags) { NS_ENSURE_ARG_POINTER(aBusyFlags); *aBusyFlags = mBusyFlags; return NS_OK; } NS_IMETHODIMP nsDocShell::TabToTreeOwner(bool aForward, bool aForDocumentNavigation, bool* aTookFocus) { NS_ENSURE_ARG_POINTER(aTookFocus); nsCOMPtr chromeFocus = do_GetInterface(mTreeOwner); if (chromeFocus) { if (aForward) { *aTookFocus = NS_SUCCEEDED(chromeFocus->FocusNextElement(aForDocumentNavigation)); } else { *aTookFocus = NS_SUCCEEDED(chromeFocus->FocusPrevElement(aForDocumentNavigation)); } } else { *aTookFocus = false; } return NS_OK; } NS_IMETHODIMP nsDocShell::GetLoadURIDelegate(nsILoadURIDelegate** aLoadURIDelegate) { nsCOMPtr delegate = GetLoadURIDelegate(); delegate.forget(aLoadURIDelegate); return NS_OK; } already_AddRefed nsDocShell::GetLoadURIDelegate() { if (nsCOMPtr result = do_QueryActor("LoadURIDelegate", GetDocument())) { return result.forget(); } return nullptr; } NS_IMETHODIMP nsDocShell::GetUseErrorPages(bool* aUseErrorPages) { *aUseErrorPages = mBrowsingContext->GetUseErrorPages(); return NS_OK; } NS_IMETHODIMP nsDocShell::SetUseErrorPages(bool aUseErrorPages) { return mBrowsingContext->SetUseErrorPages(aUseErrorPages); } NS_IMETHODIMP nsDocShell::GetPreviousEntryIndex(int32_t* aPreviousEntryIndex) { *aPreviousEntryIndex = mPreviousEntryIndex; return NS_OK; } NS_IMETHODIMP nsDocShell::GetLoadedEntryIndex(int32_t* aLoadedEntryIndex) { *aLoadedEntryIndex = mLoadedEntryIndex; return NS_OK; } NS_IMETHODIMP nsDocShell::HistoryPurged(int32_t aNumEntries) { // These indices are used for fastback cache eviction, to determine // which session history entries are candidates for content viewer // eviction. We need to adjust by the number of entries that we // just purged from history, so that we look at the right session history // entries during eviction. mPreviousEntryIndex = std::max(-1, mPreviousEntryIndex - aNumEntries); mLoadedEntryIndex = std::max(0, mLoadedEntryIndex - aNumEntries); for (auto* child : mChildList.ForwardRange()) { nsCOMPtr shell = do_QueryObject(child); if (shell) { shell->HistoryPurged(aNumEntries); } } return NS_OK; } void nsDocShell::TriggerParentCheckDocShellIsEmpty() { if (RefPtr parent = GetInProcessParentDocshell()) { parent->DocLoaderIsEmpty(true); } if (GetBrowsingContext()->IsContentSubframe() && !GetBrowsingContext()->GetParent()->IsInProcess()) { if (BrowserChild* browserChild = BrowserChild::GetFrom(this)) { mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents( EmbedderElementEventType::NoEvent); } } } nsresult nsDocShell::HistoryEntryRemoved(int32_t aIndex) { // These indices are used for fastback cache eviction, to determine // which session history entries are candidates for content viewer // eviction. We need to adjust by the number of entries that we // just purged from history, so that we look at the right session history // entries during eviction. if (aIndex == mPreviousEntryIndex) { mPreviousEntryIndex = -1; } else if (aIndex < mPreviousEntryIndex) { --mPreviousEntryIndex; } if (mLoadedEntryIndex == aIndex) { mLoadedEntryIndex = 0; } else if (aIndex < mLoadedEntryIndex) { --mLoadedEntryIndex; } for (auto* child : mChildList.ForwardRange()) { nsCOMPtr shell = do_QueryObject(child); if (shell) { static_cast(shell.get())->HistoryEntryRemoved(aIndex); } } return NS_OK; } NS_IMETHODIMP nsDocShell::SetRecordProfileTimelineMarkers(bool aValue) { bool currentValue = nsIDocShell::GetRecordProfileTimelineMarkers(); if (currentValue == aValue) { return NS_OK; } if (aValue) { MOZ_ASSERT(!TimelineConsumers::HasConsumer(this)); TimelineConsumers::AddConsumer(this); MOZ_ASSERT(TimelineConsumers::HasConsumer(this)); UseEntryScriptProfiling(); } else { MOZ_ASSERT(TimelineConsumers::HasConsumer(this)); TimelineConsumers::RemoveConsumer(this); MOZ_ASSERT(!TimelineConsumers::HasConsumer(this)); UnuseEntryScriptProfiling(); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetRecordProfileTimelineMarkers(bool* aValue) { *aValue = !!mObserved; return NS_OK; } nsresult nsDocShell::PopProfileTimelineMarkers( JSContext* aCx, JS::MutableHandle aOut) { nsTArray store; SequenceRooter rooter(aCx, &store); TimelineConsumers::PopMarkers(this, aCx, store); if (!ToJSValue(aCx, store, aOut)) { JS_ClearPendingException(aCx); return NS_ERROR_UNEXPECTED; } return NS_OK; } nsresult nsDocShell::Now(DOMHighResTimeStamp* aWhen) { *aWhen = (TimeStamp::Now() - TimeStamp::ProcessCreation()).ToMilliseconds(); return NS_OK; } NS_IMETHODIMP nsDocShell::SetWindowDraggingAllowed(bool aValue) { RefPtr parent = GetInProcessParentDocshell(); if (!aValue && mItemType == typeChrome && !parent) { // Window dragging is always allowed for top level // chrome docshells. return NS_ERROR_FAILURE; } mWindowDraggingAllowed = aValue; return NS_OK; } NS_IMETHODIMP nsDocShell::GetWindowDraggingAllowed(bool* aValue) { // window dragging regions in CSS (-moz-window-drag:drag) // can be slow. Default behavior is to only allow it for // chrome top level windows. RefPtr parent = GetInProcessParentDocshell(); if (mItemType == typeChrome && !parent) { // Top level chrome window *aValue = true; } else { *aValue = mWindowDraggingAllowed; } return NS_OK; } NS_IMETHODIMP nsDocShell::GetCurrentDocumentChannel(nsIChannel** aResult) { NS_IF_ADDREF(*aResult = GetCurrentDocChannel()); return NS_OK; } nsIChannel* nsDocShell::GetCurrentDocChannel() { if (mContentViewer) { Document* doc = mContentViewer->GetDocument(); if (doc) { return doc->GetChannel(); } } return nullptr; } NS_IMETHODIMP nsDocShell::AddWeakScrollObserver(nsIScrollObserver* aObserver) { nsWeakPtr weakObs = do_GetWeakReference(aObserver); if (!weakObs) { return NS_ERROR_FAILURE; } mScrollObservers.AppendElement(weakObs); return NS_OK; } NS_IMETHODIMP nsDocShell::RemoveWeakScrollObserver(nsIScrollObserver* aObserver) { nsWeakPtr obs = do_GetWeakReference(aObserver); return mScrollObservers.RemoveElement(obs) ? NS_OK : NS_ERROR_FAILURE; } void nsDocShell::NotifyAsyncPanZoomStarted() { nsTObserverArray::ForwardIterator iter(mScrollObservers); while (iter.HasMore()) { nsWeakPtr ref = iter.GetNext(); nsCOMPtr obs = do_QueryReferent(ref); if (obs) { obs->AsyncPanZoomStarted(); } else { iter.Remove(); } } } void nsDocShell::NotifyAsyncPanZoomStopped() { nsTObserverArray::ForwardIterator iter(mScrollObservers); while (iter.HasMore()) { nsWeakPtr ref = iter.GetNext(); nsCOMPtr obs = do_QueryReferent(ref); if (obs) { obs->AsyncPanZoomStopped(); } else { iter.Remove(); } } } NS_IMETHODIMP nsDocShell::NotifyScrollObservers() { nsTObserverArray::ForwardIterator iter(mScrollObservers); while (iter.HasMore()) { nsWeakPtr ref = iter.GetNext(); nsCOMPtr obs = do_QueryReferent(ref); if (obs) { obs->ScrollPositionChanged(); } else { iter.Remove(); } } return NS_OK; } //***************************************************************************** // nsDocShell::nsIDocShellTreeItem //***************************************************************************** NS_IMETHODIMP nsDocShell::GetName(nsAString& aName) { aName = mBrowsingContext->Name(); return NS_OK; } NS_IMETHODIMP nsDocShell::SetName(const nsAString& aName) { return mBrowsingContext->SetName(aName); } NS_IMETHODIMP nsDocShell::NameEquals(const nsAString& aName, bool* aResult) { NS_ENSURE_ARG_POINTER(aResult); *aResult = mBrowsingContext->NameEquals(aName); return NS_OK; } NS_IMETHODIMP nsDocShell::GetCustomUserAgent(nsAString& aCustomUserAgent) { mBrowsingContext->GetCustomUserAgent(aCustomUserAgent); return NS_OK; } NS_IMETHODIMP nsDocShell::SetCustomUserAgent(const nsAString& aCustomUserAgent) { if (mWillChangeProcess) { NS_WARNING("SetCustomUserAgent: Process is changing. Ignoring set"); return NS_ERROR_FAILURE; } return mBrowsingContext->SetCustomUserAgent(aCustomUserAgent); } NS_IMETHODIMP nsDocShell::ClearCachedPlatform() { RefPtr win = mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; if (win) { Navigator* navigator = win->Navigator(); if (navigator) { navigator->ClearPlatformCache(); } } return NS_OK; } NS_IMETHODIMP nsDocShell::ClearCachedUserAgent() { RefPtr win = mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; if (win) { Navigator* navigator = win->Navigator(); if (navigator) { navigator->ClearUserAgentCache(); } } return NS_OK; } NS_IMETHODIMP nsDocShell::GetMetaViewportOverride( MetaViewportOverride* aMetaViewportOverride) { NS_ENSURE_ARG_POINTER(aMetaViewportOverride); *aMetaViewportOverride = mMetaViewportOverride; return NS_OK; } NS_IMETHODIMP nsDocShell::SetMetaViewportOverride( MetaViewportOverride aMetaViewportOverride) { // We don't have a way to verify this coming from Javascript, so this check is // still needed. if (!(aMetaViewportOverride == META_VIEWPORT_OVERRIDE_NONE || aMetaViewportOverride == META_VIEWPORT_OVERRIDE_ENABLED || aMetaViewportOverride == META_VIEWPORT_OVERRIDE_DISABLED)) { return NS_ERROR_INVALID_ARG; } mMetaViewportOverride = aMetaViewportOverride; // Inform our presShell that it needs to re-check its need for a viewport // override. if (RefPtr presShell = GetPresShell()) { presShell->MaybeRecreateMobileViewportManager(true); } return NS_OK; } /* virtual */ int32_t nsDocShell::ItemType() { return mItemType; } NS_IMETHODIMP nsDocShell::GetItemType(int32_t* aItemType) { NS_ENSURE_ARG_POINTER(aItemType); MOZ_DIAGNOSTIC_ASSERT( (mBrowsingContext->IsContent() ? typeContent : typeChrome) == mItemType); *aItemType = mItemType; return NS_OK; } NS_IMETHODIMP nsDocShell::GetInProcessParent(nsIDocShellTreeItem** aParent) { if (!mParent) { *aParent = nullptr; } else { CallQueryInterface(mParent, aParent); } // Note that in the case when the parent is not an nsIDocShellTreeItem we // don't want to throw; we just want to return null. return NS_OK; } // With Fission, related nsDocShell objects may exist in a different process. In // that case, this method will return `nullptr`, despite a parent nsDocShell // object existing. // // Prefer using `BrowsingContext::Parent()`, which will succeed even if the // parent entry is not in the current process, and handle the case where the // parent nsDocShell is inaccessible. already_AddRefed nsDocShell::GetInProcessParentDocshell() { nsCOMPtr docshell = do_QueryInterface(GetAsSupports(mParent)); return docshell.forget().downcast(); } void nsDocShell::MaybeCreateInitialClientSource(nsIPrincipal* aPrincipal) { MOZ_ASSERT(!mIsBeingDestroyed); // If there is an existing document then there is no need to create // a client for a future initial about:blank document. if (mScriptGlobal && mScriptGlobal->GetCurrentInnerWindowInternal() && mScriptGlobal->GetCurrentInnerWindowInternal()->GetExtantDoc()) { MOZ_DIAGNOSTIC_ASSERT(mScriptGlobal->GetCurrentInnerWindowInternal() ->GetClientInfo() .isSome()); MOZ_DIAGNOSTIC_ASSERT(!mInitialClientSource); return; } // Don't recreate the initial client source. We call this multiple times // when DoChannelLoad() is called before CreateAboutBlankContentViewer. if (mInitialClientSource) { return; } // Don't pre-allocate the client when we are sandboxed. The inherited // principal does not take sandboxing into account. // TODO: Refactor sandboxing principal code out so we can use it here. if (!aPrincipal && mBrowsingContext->GetSandboxFlags()) { return; } // We cannot get inherited foreign partitioned principal here. Instead, we // directly check which principal we want to inherit for the service worker. nsIPrincipal* principal = aPrincipal ? aPrincipal : GetInheritedPrincipal( false, StoragePrincipalHelper:: ShouldUsePartitionPrincipalForServiceWorker(this)); // Sometimes there is no principal available when we are called from // CreateAboutBlankContentViewer. For example, sometimes the principal // is only extracted from the load context after the document is created // in Document::ResetToURI(). Ideally we would do something similar // here, but for now lets just avoid the issue by not preallocating the // client. if (!principal) { return; } nsCOMPtr win = GetWindow(); if (!win) { return; } mInitialClientSource = ClientManager::CreateSource( ClientType::Window, win->EventTargetFor(TaskCategory::Other), principal); MOZ_DIAGNOSTIC_ASSERT(mInitialClientSource); // Mark the initial client as execution ready, but owned by the docshell. // If the client is actually used this will cause ClientSource to force // the creation of the initial about:blank by calling // nsDocShell::GetDocument(). mInitialClientSource->DocShellExecutionReady(this); // Next, check to see if the parent is controlled. nsCOMPtr parent = GetInProcessParentDocshell(); nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr; nsPIDOMWindowInner* parentInner = parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr; if (!parentInner) { return; } nsCOMPtr uri; MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), "about:blank"_ns)); // We're done if there is no parent controller or if this docshell // is not permitted to control for some reason. Maybe controller(parentInner->GetController()); if (controller.isNothing() || !ServiceWorkerAllowedToControlWindow(principal, uri)) { return; } mInitialClientSource->InheritController(controller.ref()); } Maybe nsDocShell::GetInitialClientInfo() const { if (mInitialClientSource) { Maybe result; result.emplace(mInitialClientSource->Info()); return result; } nsGlobalWindowInner* innerWindow = mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr; if (!doc || !doc->IsInitialDocument()) { return Maybe(); } return innerWindow->GetClientInfo(); } nsresult nsDocShell::SetDocLoaderParent(nsDocLoader* aParent) { bool wasFrame = IsSubframe(); nsresult rv = nsDocLoader::SetDocLoaderParent(aParent); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr priorityGroup = do_QueryInterface(mLoadGroup); if (wasFrame != IsSubframe() && priorityGroup) { priorityGroup->AdjustPriority(wasFrame ? -1 : 1); } // Curse ambiguous nsISupports inheritance! nsISupports* parent = GetAsSupports(aParent); // If parent is another docshell, we inherit all their flags for // allowing plugins, scripting etc. bool value; nsCOMPtr parentAsDocShell(do_QueryInterface(parent)); if (parentAsDocShell) { if (mAllowMetaRedirects && NS_SUCCEEDED(parentAsDocShell->GetAllowMetaRedirects(&value))) { SetAllowMetaRedirects(value); } if (mAllowSubframes && NS_SUCCEEDED(parentAsDocShell->GetAllowSubframes(&value))) { SetAllowSubframes(value); } if (mAllowImages && NS_SUCCEEDED(parentAsDocShell->GetAllowImages(&value))) { SetAllowImages(value); } SetAllowMedia(parentAsDocShell->GetAllowMedia() && mAllowMedia); if (mAllowWindowControl && NS_SUCCEEDED(parentAsDocShell->GetAllowWindowControl(&value))) { SetAllowWindowControl(value); } if (NS_FAILED(parentAsDocShell->GetAllowDNSPrefetch(&value))) { value = false; } SetAllowDNSPrefetch(mAllowDNSPrefetch && value); // We don't need to inherit metaViewportOverride, because the viewport // is only relevant for the outermost nsDocShell, not for any iframes // like this that might be embedded within it. } nsCOMPtr parentURIListener(do_GetInterface(parent)); if (parentURIListener) { mContentListener->SetParentContentListener(parentURIListener); } return NS_OK; } void nsDocShell::MaybeRestoreWindowName() { if (!StaticPrefs::privacy_window_name_update_enabled()) { return; } // We only restore window.name for the top-level content. if (!mBrowsingContext->IsTopContent()) { return; } nsAutoString name; // Following implements https://html.spec.whatwg.org/#history-traversal: // Step 4.4. Check if the loading entry has a name. if (mLSHE) { mLSHE->GetName(name); } if (mLoadingEntry) { name = mLoadingEntry->mInfo.GetName(); } if (name.IsEmpty()) { return; } // Step 4.4.1. Set the name to the browsing context. Unused << mBrowsingContext->SetName(name); // Step 4.4.2. Clear the name of all entries that are contiguous and // same-origin with the loading entry. if (mLSHE) { nsSHistory::WalkContiguousEntries( mLSHE, [](nsISHEntry* aEntry) { aEntry->SetName(EmptyString()); }); } if (mLoadingEntry) { // Clear the name of the session entry in the child side. For parent side, // the clearing will be done when we commit the history to the parent. mLoadingEntry->mInfo.SetName(EmptyString()); } } void nsDocShell::StoreWindowNameToSHEntries() { MOZ_ASSERT(mBrowsingContext->IsTopContent()); nsAutoString name; mBrowsingContext->GetName(name); if (mOSHE) { nsSHistory::WalkContiguousEntries( mOSHE, [&](nsISHEntry* aEntry) { aEntry->SetName(name); }); } if (mozilla::SessionHistoryInParent()) { if (XRE_IsParentProcess()) { SessionHistoryEntry* entry = mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); if (entry) { nsSHistory::WalkContiguousEntries( entry, [&](nsISHEntry* aEntry) { aEntry->SetName(name); }); } } else { // Ask parent process to store the name in entries. mozilla::Unused << ContentChild::GetSingleton() ->SendSessionHistoryEntryStoreWindowNameInContiguousEntries( mBrowsingContext, name); } } } NS_IMETHODIMP nsDocShell::GetInProcessSameTypeParent(nsIDocShellTreeItem** aParent) { if (BrowsingContext* parentBC = mBrowsingContext->GetParent()) { *aParent = do_AddRef(parentBC->GetDocShell()).take(); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetSameTypeInProcessParentIgnoreBrowserBoundaries( nsIDocShell** aParent) { NS_ENSURE_ARG_POINTER(aParent); *aParent = nullptr; nsCOMPtr parent = do_QueryInterface(GetAsSupports(mParent)); if (!parent) { return NS_OK; } if (parent->ItemType() == mItemType) { nsCOMPtr parentDS = do_QueryInterface(parent); parentDS.forget(aParent); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetInProcessRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) { NS_ENSURE_ARG_POINTER(aRootTreeItem); RefPtr root = this; RefPtr parent = root->GetInProcessParentDocshell(); while (parent) { root = parent; parent = root->GetInProcessParentDocshell(); } root.forget(aRootTreeItem); return NS_OK; } NS_IMETHODIMP nsDocShell::GetInProcessSameTypeRootTreeItem( nsIDocShellTreeItem** aRootTreeItem) { NS_ENSURE_ARG_POINTER(aRootTreeItem); *aRootTreeItem = static_cast(this); nsCOMPtr parent; NS_ENSURE_SUCCESS(GetInProcessSameTypeParent(getter_AddRefs(parent)), NS_ERROR_FAILURE); while (parent) { *aRootTreeItem = parent; NS_ENSURE_SUCCESS( (*aRootTreeItem)->GetInProcessSameTypeParent(getter_AddRefs(parent)), NS_ERROR_FAILURE); } NS_ADDREF(*aRootTreeItem); return NS_OK; } NS_IMETHODIMP nsDocShell::GetTreeOwner(nsIDocShellTreeOwner** aTreeOwner) { NS_ENSURE_ARG_POINTER(aTreeOwner); *aTreeOwner = mTreeOwner; NS_IF_ADDREF(*aTreeOwner); return NS_OK; } NS_IMETHODIMP nsDocShell::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) { if (mIsBeingDestroyed && aTreeOwner) { return NS_ERROR_FAILURE; } // Don't automatically set the progress based on the tree owner for frames if (!IsSubframe()) { nsCOMPtr webProgress = do_QueryInterface(GetAsSupports(this)); if (webProgress) { nsCOMPtr oldListener = do_QueryInterface(mTreeOwner); nsCOMPtr newListener = do_QueryInterface(aTreeOwner); if (oldListener) { webProgress->RemoveProgressListener(oldListener); } if (newListener) { webProgress->AddProgressListener(newListener, nsIWebProgress::NOTIFY_ALL); } } } mTreeOwner = aTreeOwner; // Weak reference per API for (auto* childDocLoader : mChildList.ForwardRange()) { nsCOMPtr child = do_QueryObject(childDocLoader); NS_ENSURE_TRUE(child, NS_ERROR_FAILURE); if (child->ItemType() == mItemType) { child->SetTreeOwner(aTreeOwner); } } // If we're in the content process and have had a TreeOwner set on us, extract // our BrowserChild actor. If we've already had our BrowserChild set, assert // that it hasn't changed. if (mTreeOwner && XRE_IsContentProcess()) { nsCOMPtr newBrowserChild = do_GetInterface(mTreeOwner); MOZ_ASSERT(newBrowserChild, "No BrowserChild actor for tree owner in Content!"); if (mBrowserChild) { nsCOMPtr oldBrowserChild = do_QueryReferent(mBrowserChild); MOZ_RELEASE_ASSERT( oldBrowserChild == newBrowserChild, "Cannot change BrowserChild during nsDocShell lifetime!"); } else { mBrowserChild = do_GetWeakReference(newBrowserChild); } } return NS_OK; } NS_IMETHODIMP nsDocShell::GetHistoryID(nsID& aID) { aID = mBrowsingContext->GetHistoryID(); return NS_OK; } const nsID& nsDocShell::HistoryID() { return mBrowsingContext->GetHistoryID(); } NS_IMETHODIMP nsDocShell::GetIsInUnload(bool* aIsInUnload) { *aIsInUnload = mFiredUnloadEvent; return NS_OK; } NS_IMETHODIMP nsDocShell::GetInProcessChildCount(int32_t* aChildCount) { NS_ENSURE_ARG_POINTER(aChildCount); *aChildCount = mChildList.Length(); return NS_OK; } NS_IMETHODIMP nsDocShell::AddChild(nsIDocShellTreeItem* aChild) { NS_ENSURE_ARG_POINTER(aChild); RefPtr childAsDocLoader = GetAsDocLoader(aChild); NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED); // Make sure we're not creating a loop in the docshell tree nsDocLoader* ancestor = this; do { if (childAsDocLoader == ancestor) { return NS_ERROR_ILLEGAL_VALUE; } ancestor = ancestor->GetParent(); } while (ancestor); // Make sure to remove the child from its current parent. nsDocLoader* childsParent = childAsDocLoader->GetParent(); if (childsParent) { nsresult rv = childsParent->RemoveChildLoader(childAsDocLoader); NS_ENSURE_SUCCESS(rv, rv); } // Make sure to clear the treeowner in case this child is a different type // from us. aChild->SetTreeOwner(nullptr); nsresult res = AddChildLoader(childAsDocLoader); NS_ENSURE_SUCCESS(res, res); NS_ASSERTION(!mChildList.IsEmpty(), "child list must not be empty after a successful add"); /* Set the child's global history if the parent has one */ if (mBrowsingContext->GetUseGlobalHistory()) { // childDocShell->SetUseGlobalHistory(true); // this should be set through BC inherit MOZ_ASSERT(aChild->GetBrowsingContext()->GetUseGlobalHistory()); } if (aChild->ItemType() != mItemType) { return NS_OK; } aChild->SetTreeOwner(mTreeOwner); nsCOMPtr childAsDocShell(do_QueryInterface(aChild)); if (!childAsDocShell) { return NS_OK; } // charset, style-disabling, and zoom will be inherited in SetupNewViewer() // Now take this document's charset and set the child's parentCharset field // to it. We'll later use that field, in the loading process, for the // charset choosing algorithm. // If we fail, at any point, we just return NS_OK. // This code has some performance impact. But this will be reduced when // the current charset will finally be stored as an Atom, avoiding the // alias resolution extra look-up. // we are NOT going to propagate the charset is this Chrome's docshell if (mItemType == nsIDocShellTreeItem::typeChrome) { return NS_OK; } // get the parent's current charset if (!mContentViewer) { return NS_OK; } Document* doc = mContentViewer->GetDocument(); if (!doc) { return NS_OK; } const Encoding* parentCS = doc->GetDocumentCharacterSet(); int32_t charsetSource = doc->GetDocumentCharacterSetSource(); // set the child's parentCharset childAsDocShell->SetParentCharset(parentCS, charsetSource, doc->NodePrincipal()); // printf("### 1 >>> Adding child. Parent CS = %s. ItemType = %d.\n", // NS_LossyConvertUTF16toASCII(parentCS).get(), mItemType); return NS_OK; } NS_IMETHODIMP nsDocShell::RemoveChild(nsIDocShellTreeItem* aChild) { NS_ENSURE_ARG_POINTER(aChild); RefPtr childAsDocLoader = GetAsDocLoader(aChild); NS_ENSURE_TRUE(childAsDocLoader, NS_ERROR_UNEXPECTED); nsresult rv = RemoveChildLoader(childAsDocLoader); NS_ENSURE_SUCCESS(rv, rv); aChild->SetTreeOwner(nullptr); return nsDocLoader::AddDocLoaderAsChildOfRoot(childAsDocLoader); } NS_IMETHODIMP nsDocShell::GetInProcessChildAt(int32_t aIndex, nsIDocShellTreeItem** aChild) { NS_ENSURE_ARG_POINTER(aChild); RefPtr child = GetInProcessChildAt(aIndex); NS_ENSURE_TRUE(child, NS_ERROR_UNEXPECTED); child.forget(aChild); return NS_OK; } nsDocShell* nsDocShell::GetInProcessChildAt(int32_t aIndex) { #ifdef DEBUG if (aIndex < 0) { NS_WARNING("Negative index passed to GetChildAt"); } else if (static_cast(aIndex) >= mChildList.Length()) { NS_WARNING("Too large an index passed to GetChildAt"); } #endif nsIDocumentLoader* child = ChildAt(aIndex); // child may be nullptr here. return static_cast(child); } nsresult nsDocShell::AddChildSHEntry(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry, int32_t aChildOffset, uint32_t aLoadType, bool aCloneChildren) { MOZ_ASSERT(!mozilla::SessionHistoryInParent()); nsresult rv = NS_OK; if (mLSHE && aLoadType != LOAD_PUSHSTATE) { /* You get here if you are currently building a * hierarchy ie.,you just visited a frameset page */ if (NS_FAILED(mLSHE->ReplaceChild(aNewEntry))) { rv = mLSHE->AddChild(aNewEntry, aChildOffset); } } else if (!aCloneRef) { /* This is an initial load in some subframe. Just append it if we can */ if (mOSHE) { rv = mOSHE->AddChild(aNewEntry, aChildOffset, UseRemoteSubframes()); } } else { RefPtr shistory = GetRootSessionHistory(); if (shistory) { rv = shistory->LegacySHistory()->AddChildSHEntryHelper( aCloneRef, aNewEntry, mBrowsingContext->Top(), aCloneChildren); } } return rv; } nsresult nsDocShell::AddChildSHEntryToParent(nsISHEntry* aNewEntry, int32_t aChildOffset, bool aCloneChildren) { MOZ_ASSERT(!mozilla::SessionHistoryInParent()); /* You will get here when you are in a subframe and * a new url has been loaded on you. * The mOSHE in this subframe will be the previous url's * mOSHE. This mOSHE will be used as the identification * for this subframe in the CloneAndReplace function. */ // In this case, we will end up calling AddEntry, which increases the // current index by 1 RefPtr rootSH = GetRootSessionHistory(); if (rootSH) { mPreviousEntryIndex = rootSH->Index(); } nsresult rv; // XXX(farre): this is not Fission safe, expect errors. This never // get's executed once session history in the parent is enabled. nsCOMPtr parent = do_QueryInterface(GetAsSupports(mParent), &rv); NS_WARNING_ASSERTION( parent || !UseRemoteSubframes(), "Failed to add child session history entry! This will be resolved once " "session history in the parent is enabled."); if (parent) { rv = nsDocShell::Cast(parent)->AddChildSHEntry( mOSHE, aNewEntry, aChildOffset, mLoadType, aCloneChildren); } if (rootSH) { mLoadedEntryIndex = rootSH->Index(); if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Verbose))) { MOZ_LOG(gPageCacheLog, LogLevel::Verbose, ("Previous index: %d, Loaded index: %d", mPreviousEntryIndex, mLoadedEntryIndex)); } } return rv; } NS_IMETHODIMP nsDocShell::GetCurrentSHEntry(nsISHEntry** aEntry, bool* aOSHE) { *aOSHE = false; *aEntry = nullptr; if (mLSHE) { NS_ADDREF(*aEntry = mLSHE); } else if (mOSHE) { NS_ADDREF(*aEntry = mOSHE); *aOSHE = true; } return NS_OK; } NS_IMETHODIMP nsDocShell::SynchronizeLayoutHistoryState() { if (mActiveEntry && mActiveEntry->GetLayoutHistoryState() && mBrowsingContext) { if (XRE_IsContentProcess()) { dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); if (contentChild) { contentChild->SendSynchronizeLayoutHistoryState( mBrowsingContext, mActiveEntry->GetLayoutHistoryState()); } } else { SessionHistoryEntry* entry = mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); if (entry) { entry->SetLayoutHistoryState(mActiveEntry->GetLayoutHistoryState()); } } if (mLoadingEntry && mLoadingEntry->mInfo.SharedId() == mActiveEntry->SharedId()) { mLoadingEntry->mInfo.SetLayoutHistoryState( mActiveEntry->GetLayoutHistoryState()); } } return NS_OK; } void nsDocShell::SetLoadGroupDefaultLoadFlags(nsLoadFlags aLoadFlags) { if (mLoadGroup) { mLoadGroup->SetDefaultLoadFlags(aLoadFlags); } else { NS_WARNING( "nsDocShell::SetLoadGroupDefaultLoadFlags has no loadGroup to " "propagate the mode to"); } } nsIScriptGlobalObject* nsDocShell::GetScriptGlobalObject() { NS_ENSURE_SUCCESS(EnsureScriptEnvironment(), nullptr); return mScriptGlobal; } Document* nsDocShell::GetDocument() { NS_ENSURE_SUCCESS(EnsureContentViewer(), nullptr); return mContentViewer->GetDocument(); } Document* nsDocShell::GetExtantDocument() { return mContentViewer ? mContentViewer->GetDocument() : nullptr; } nsPIDOMWindowOuter* nsDocShell::GetWindow() { if (NS_FAILED(EnsureScriptEnvironment())) { return nullptr; } return mScriptGlobal; } NS_IMETHODIMP nsDocShell::GetDomWindow(mozIDOMWindowProxy** aWindow) { NS_ENSURE_ARG_POINTER(aWindow); nsresult rv = EnsureScriptEnvironment(); NS_ENSURE_SUCCESS(rv, rv); RefPtr window = mScriptGlobal; window.forget(aWindow); return NS_OK; } NS_IMETHODIMP nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) { RefPtr mm; if (RefPtr browserChild = BrowserChild::GetFrom(this)) { mm = browserChild->GetMessageManager(); } else if (nsPIDOMWindowOuter* win = GetWindow()) { mm = win->GetMessageManager(); } mm.forget(aMessageManager); return NS_OK; } NS_IMETHODIMP nsDocShell::GetIsNavigating(bool* aOut) { *aOut = mIsNavigating; return NS_OK; } void nsDocShell::ClearFrameHistory(nsISHEntry* aEntry) { MOZ_ASSERT(!mozilla::SessionHistoryInParent()); RefPtr rootSH = GetRootSessionHistory(); if (!rootSH || !aEntry) { return; } rootSH->LegacySHistory()->RemoveFrameEntries(aEntry); } //------------------------------------- //-- Helper Method for Print discovery //------------------------------------- bool nsDocShell::NavigationBlockedByPrinting(bool aDisplayErrorDialog) { if (!mBrowsingContext->Top()->GetIsPrinting()) { return false; } if (aDisplayErrorDialog) { DisplayLoadError(NS_ERROR_DOCUMENT_IS_PRINTMODE, nullptr, nullptr, nullptr); } return true; } bool nsDocShell::IsNavigationAllowed(bool aDisplayPrintErrorDialog, bool aCheckIfUnloadFired) { bool isAllowed = !NavigationBlockedByPrinting(aDisplayPrintErrorDialog) && (!aCheckIfUnloadFired || !mFiredUnloadEvent); if (!isAllowed) { return false; } if (!mContentViewer) { return true; } bool firingBeforeUnload; mContentViewer->GetBeforeUnloadFiring(&firingBeforeUnload); return !firingBeforeUnload; } //***************************************************************************** // nsDocShell::nsIWebNavigation //***************************************************************************** NS_IMETHODIMP nsDocShell::GetCanGoBack(bool* aCanGoBack) { *aCanGoBack = false; if (!IsNavigationAllowed(false)) { return NS_OK; // JS may not handle returning of an error code } RefPtr rootSH = GetRootSessionHistory(); if (rootSH) { *aCanGoBack = rootSH->CanGo(-1); MOZ_LOG(gSHLog, LogLevel::Verbose, ("nsDocShell %p CanGoBack()->%d", this, *aCanGoBack)); return NS_OK; } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsDocShell::GetCanGoForward(bool* aCanGoForward) { *aCanGoForward = false; if (!IsNavigationAllowed(false)) { return NS_OK; // JS may not handle returning of an error code } RefPtr rootSH = GetRootSessionHistory(); if (rootSH) { *aCanGoForward = rootSH->CanGo(1); MOZ_LOG(gSHLog, LogLevel::Verbose, ("nsDocShell %p CanGoForward()->%d", this, *aCanGoForward)); return NS_OK; } return NS_ERROR_FAILURE; } NS_IMETHODIMP nsDocShell::GoBack(bool aRequireUserInteraction, bool aUserActivation) { if (!IsNavigationAllowed()) { return NS_OK; // JS may not handle returning of an error code } auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; }); mIsNavigating = true; RefPtr rootSH = GetRootSessionHistory(); NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE); ErrorResult rv; rootSH->Go(-1, aRequireUserInteraction, aUserActivation, rv); return rv.StealNSResult(); } NS_IMETHODIMP nsDocShell::GoForward(bool aRequireUserInteraction, bool aUserActivation) { if (!IsNavigationAllowed()) { return NS_OK; // JS may not handle returning of an error code } auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; }); mIsNavigating = true; RefPtr rootSH = GetRootSessionHistory(); NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE); ErrorResult rv; rootSH->Go(1, aRequireUserInteraction, aUserActivation, rv); return rv.StealNSResult(); } // XXX(nika): We may want to stop exposing this API in the child process? Going // to a specific index from multiple different processes could definitely race. NS_IMETHODIMP nsDocShell::GotoIndex(int32_t aIndex, bool aUserActivation) { if (!IsNavigationAllowed()) { return NS_OK; // JS may not handle returning of an error code } auto cleanupIsNavigating = MakeScopeExit([&]() { mIsNavigating = false; }); mIsNavigating = true; RefPtr rootSH = GetRootSessionHistory(); NS_ENSURE_TRUE(rootSH, NS_ERROR_FAILURE); ErrorResult rv; rootSH->GotoIndex(aIndex, aIndex - rootSH->Index(), false, aUserActivation, rv); return rv.StealNSResult(); } nsresult nsDocShell::LoadURI(nsIURI* aURI, const LoadURIOptions& aLoadURIOptions) { if (!IsNavigationAllowed()) { return NS_OK; // JS may not handle returning of an error code } RefPtr loadState; nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions( mBrowsingContext, aURI, aLoadURIOptions, getter_AddRefs(loadState)); MOZ_ASSERT(rv != NS_ERROR_MALFORMED_URI); if (NS_FAILED(rv) || !loadState) { return NS_ERROR_FAILURE; } return LoadURI(loadState, true); } NS_IMETHODIMP nsDocShell::LoadURIFromScript(nsIURI* aURI, JS::Handle aLoadURIOptions, JSContext* aCx) { // generate dictionary for aLoadURIOptions and forward call LoadURIOptions loadURIOptions; if (!loadURIOptions.Init(aCx, aLoadURIOptions)) { return NS_ERROR_INVALID_ARG; } return LoadURI(aURI, loadURIOptions); } nsresult nsDocShell::FixupAndLoadURIString( const nsAString& aURIString, const LoadURIOptions& aLoadURIOptions) { if (!IsNavigationAllowed()) { return NS_OK; // JS may not handle returning of an error code } RefPtr loadState; nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions( mBrowsingContext, aURIString, aLoadURIOptions, getter_AddRefs(loadState)); uint32_t loadFlags = aLoadURIOptions.mLoadFlags; if (NS_ERROR_MALFORMED_URI == rv) { MOZ_LOG(gSHLog, LogLevel::Debug, ("Creating an active entry on nsDocShell %p to %s (because " "we're showing an error page)", this, NS_ConvertUTF16toUTF8(aURIString).get())); // We need to store a session history entry. We don't have a valid URI, so // we use about:blank instead. nsCOMPtr uri; MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), "about:blank"_ns)); nsCOMPtr triggeringPrincipal; if (aLoadURIOptions.mTriggeringPrincipal) { triggeringPrincipal = aLoadURIOptions.mTriggeringPrincipal; } else { triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); } if (mozilla::SessionHistoryInParent()) { mActiveEntry = MakeUnique( uri, triggeringPrincipal, nullptr, nullptr, nullptr, nsLiteralCString("text/html")); mBrowsingContext->SetActiveSessionHistoryEntry( Nothing(), mActiveEntry.get(), MAKE_LOAD_TYPE(LOAD_NORMAL, loadFlags), /* aUpdatedCacheKey = */ 0); } if (DisplayLoadError(rv, nullptr, PromiseFlatString(aURIString).get(), nullptr) && (loadFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) { return NS_ERROR_LOAD_SHOWED_ERRORPAGE; } } if (NS_FAILED(rv) || !loadState) { return NS_ERROR_FAILURE; } return LoadURI(loadState, true); } NS_IMETHODIMP nsDocShell::FixupAndLoadURIStringFromScript( const nsAString& aURIString, JS::Handle aLoadURIOptions, JSContext* aCx) { // generate dictionary for aLoadURIOptions and forward call LoadURIOptions loadURIOptions; if (!loadURIOptions.Init(aCx, aLoadURIOptions)) { return NS_ERROR_INVALID_ARG; } return FixupAndLoadURIString(aURIString, loadURIOptions); } void nsDocShell::UnblockEmbedderLoadEventForFailure(bool aFireFrameErrorEvent) { // 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 error event. if (mBrowsingContext->IsTopContent() || mBrowsingContext->IsChrome()) { return; } // If embedder is same-process, then unblocking the load event is already // handled by nsDocLoader. Fire the error event on our embedder element if // requested. // // XXX: Bug 1440212 is looking into potentially changing this behaviour to act // more like the remote case when in-process. RefPtr element = mBrowsingContext->GetEmbedderElement(); if (element) { if (aFireFrameErrorEvent) { if (RefPtr flo = do_QueryObject(element)) { if (RefPtr fl = flo->GetFrameLoader()) { fl->FireErrorEvent(); } } } return; } // If we have a cross-process parent document, we must notify it that we no // longer block its load event. This is necessary for OOP sub-documents // because error documents do not result in a call to // SendMaybeFireEmbedderLoadEvents via any of the normal call paths. // (Obviously, we must do this before any of the returns below.) RefPtr browserChild = BrowserChild::GetFrom(this); if (browserChild) { mozilla::Unused << browserChild->SendMaybeFireEmbedderLoadEvents( aFireFrameErrorEvent ? EmbedderElementEventType::ErrorEvent : EmbedderElementEventType::NoEvent); } } NS_IMETHODIMP nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, const char16_t* aURL, nsIChannel* aFailedChannel, bool* aDisplayedErrorPage) { MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p DisplayLoadError %s\n", this, aURI ? aURI->GetSpecOrDefault().get() : "")); *aDisplayedErrorPage = false; // Get prompt and string bundle services nsCOMPtr prompter; nsCOMPtr stringBundle; GetPromptAndStringBundle(getter_AddRefs(prompter), getter_AddRefs(stringBundle)); NS_ENSURE_TRUE(stringBundle, NS_ERROR_FAILURE); NS_ENSURE_TRUE(prompter, NS_ERROR_FAILURE); const char* error = nullptr; // The key used to select the appropriate error message from the properties // file. const char* errorDescriptionID = nullptr; AutoTArray formatStrs; bool addHostPort = false; bool isBadStsCertError = false; nsresult rv = NS_OK; nsAutoString messageStr; nsAutoCString cssClass; nsAutoCString errorPage; errorPage.AssignLiteral("neterror"); // Turn the error code into a human readable error message. if (NS_ERROR_UNKNOWN_PROTOCOL == aError) { NS_ENSURE_ARG_POINTER(aURI); // Extract the schemes into a comma delimited list. nsAutoCString scheme; aURI->GetScheme(scheme); CopyASCIItoUTF16(scheme, *formatStrs.AppendElement()); nsCOMPtr nestedURI = do_QueryInterface(aURI); while (nestedURI) { nsCOMPtr tempURI; nsresult rv2; rv2 = nestedURI->GetInnerURI(getter_AddRefs(tempURI)); if (NS_SUCCEEDED(rv2) && tempURI) { tempURI->GetScheme(scheme); formatStrs[0].AppendLiteral(", "); AppendASCIItoUTF16(scheme, formatStrs[0]); } nestedURI = do_QueryInterface(tempURI); } error = "unknownProtocolFound"; } else if (NS_ERROR_FILE_NOT_FOUND == aError) { NS_ENSURE_ARG_POINTER(aURI); error = "fileNotFound"; } else if (NS_ERROR_FILE_ACCESS_DENIED == aError) { NS_ENSURE_ARG_POINTER(aURI); error = "fileAccessDenied"; } else if (NS_ERROR_UNKNOWN_HOST == aError) { NS_ENSURE_ARG_POINTER(aURI); // Get the host nsAutoCString host; nsCOMPtr innermostURI = NS_GetInnermostURI(aURI); innermostURI->GetHost(host); CopyUTF8toUTF16(host, *formatStrs.AppendElement()); errorDescriptionID = "dnsNotFound2"; error = "dnsNotFound"; } else if (NS_ERROR_CONNECTION_REFUSED == aError || NS_ERROR_PROXY_BAD_GATEWAY == aError) { NS_ENSURE_ARG_POINTER(aURI); addHostPort = true; error = "connectionFailure"; } else if (NS_ERROR_NET_INTERRUPT == aError) { NS_ENSURE_ARG_POINTER(aURI); addHostPort = true; error = "netInterrupt"; } else if (NS_ERROR_NET_TIMEOUT == aError || NS_ERROR_PROXY_GATEWAY_TIMEOUT == aError || NS_ERROR_NET_TIMEOUT_EXTERNAL == aError) { NS_ENSURE_ARG_POINTER(aURI); // Get the host nsAutoCString host; aURI->GetHost(host); CopyUTF8toUTF16(host, *formatStrs.AppendElement()); error = "netTimeout"; } else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError || NS_ERROR_CSP_FORM_ACTION_VIOLATION == aError || NS_ERROR_CSP_NAVIGATE_TO_VIOLATION == aError) { // CSP error cssClass.AssignLiteral("neterror"); error = "cspBlocked"; } else if (NS_ERROR_XFO_VIOLATION == aError) { // XFO error cssClass.AssignLiteral("neterror"); error = "xfoBlocked"; } else if (NS_ERROR_GET_MODULE(aError) == NS_ERROR_MODULE_SECURITY) { nsCOMPtr nsserr = do_GetService(NS_NSS_ERRORS_SERVICE_CONTRACTID); uint32_t errorClass; if (!nsserr || NS_FAILED(nsserr->GetErrorClass(aError, &errorClass))) { errorClass = nsINSSErrorsService::ERROR_CLASS_SSL_PROTOCOL; } nsCOMPtr tsi; if (aFailedChannel) { aFailedChannel->GetSecurityInfo(getter_AddRefs(tsi)); } if (tsi) { uint32_t securityState; tsi->GetSecurityState(&securityState); if (securityState & nsIWebProgressListener::STATE_USES_SSL_3) { error = "sslv3Used"; addHostPort = true; } else if (securityState & nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) { error = "weakCryptoUsed"; addHostPort = true; } } else { // No channel, let's obtain the generic error message if (nsserr) { nsserr->GetErrorMessage(aError, messageStr); } } // We don't have a message string here anymore but DisplayLoadError // requires a non-empty messageStr. messageStr.Truncate(); messageStr.AssignLiteral(u" "); if (errorClass == nsINSSErrorsService::ERROR_CLASS_BAD_CERT) { error = "nssBadCert"; // If this is an HTTP Strict Transport Security host or a pinned host // and the certificate is bad, don't allow overrides (RFC 6797 section // 12.1). bool isStsHost = false; bool isPinnedHost = false; OriginAttributes attrsForHSTS; if (aFailedChannel) { StoragePrincipalHelper::GetOriginAttributesForHSTS(aFailedChannel, attrsForHSTS); } else { attrsForHSTS = GetOriginAttributes(); } if (XRE_IsParentProcess()) { nsCOMPtr sss = do_GetService(NS_SSSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = sss->IsSecureURI(aURI, attrsForHSTS, &isStsHost); NS_ENSURE_SUCCESS(rv, rv); } else { mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton(); cc->SendIsSecureURI(aURI, attrsForHSTS, &isStsHost); } nsCOMPtr pkps = do_GetService(NS_PKPSERVICE_CONTRACTID, &rv); NS_ENSURE_SUCCESS(rv, rv); rv = pkps->HostHasPins(aURI, &isPinnedHost); if (Preferences::GetBool("browser.xul.error_pages.expert_bad_cert", false)) { cssClass.AssignLiteral("expertBadCert"); } // HSTS/pinning takes precedence over the expert bad cert pref. We // never want to show the "Add Exception" button for these sites. // In the future we should differentiate between an HSTS host and a // pinned host and display a more informative message to the user. if (isStsHost || isPinnedHost) { isBadStsCertError = true; cssClass.AssignLiteral("badStsCert"); } // See if an alternate cert error page is registered nsAutoCString alternateErrorPage; nsresult rv = Preferences::GetCString( "security.alternate_certificate_error_page", alternateErrorPage); if (NS_SUCCEEDED(rv)) { errorPage.Assign(alternateErrorPage); } } else { error = "nssFailure2"; } } else if (NS_ERROR_PHISHING_URI == aError || NS_ERROR_MALWARE_URI == aError || NS_ERROR_UNWANTED_URI == aError || NS_ERROR_HARMFUL_URI == aError) { nsAutoCString host; aURI->GetHost(host); CopyUTF8toUTF16(host, *formatStrs.AppendElement()); // Malware and phishing detectors may want to use an alternate error // page, but if the pref's not set, we'll fall back on the standard page nsAutoCString alternateErrorPage; nsresult rv = Preferences::GetCString("urlclassifier.alternate_error_page", alternateErrorPage); if (NS_SUCCEEDED(rv)) { errorPage.Assign(alternateErrorPage); } if (NS_ERROR_PHISHING_URI == aError) { error = "deceptiveBlocked"; } else if (NS_ERROR_MALWARE_URI == aError) { error = "malwareBlocked"; } else if (NS_ERROR_UNWANTED_URI == aError) { error = "unwantedBlocked"; } else if (NS_ERROR_HARMFUL_URI == aError) { error = "harmfulBlocked"; } cssClass.AssignLiteral("blacklist"); } else if (NS_ERROR_CONTENT_CRASHED == aError) { errorPage.AssignLiteral("tabcrashed"); error = "tabcrashed"; RefPtr handler = mChromeEventHandler; if (handler) { nsCOMPtr element = do_QueryInterface(handler); element->GetAttribute(u"crashedPageTitle"_ns, messageStr); } // DisplayLoadError requires a non-empty messageStr to proceed and call // LoadErrorPage. If the page doesn't have a title, we will use a blank // space which will be trimmed and thus treated as empty by the front-end. if (messageStr.IsEmpty()) { messageStr.AssignLiteral(u" "); } } else if (NS_ERROR_FRAME_CRASHED == aError) { errorPage.AssignLiteral("framecrashed"); error = "framecrashed"; messageStr.AssignLiteral(u" "); } else if (NS_ERROR_BUILDID_MISMATCH == aError) { errorPage.AssignLiteral("restartrequired"); error = "restartrequired"; // DisplayLoadError requires a non-empty messageStr to proceed and call // LoadErrorPage. If the page doesn't have a title, we will use a blank // space which will be trimmed and thus treated as empty by the front-end. if (messageStr.IsEmpty()) { messageStr.AssignLiteral(u" "); } } else { // Errors requiring simple formatting switch (aError) { case NS_ERROR_MALFORMED_URI: // URI is malformed error = "malformedURI"; errorDescriptionID = "malformedURI2"; break; case NS_ERROR_REDIRECT_LOOP: // Doc failed to load because the server generated too many redirects error = "redirectLoop"; break; case NS_ERROR_UNKNOWN_SOCKET_TYPE: // Doc failed to load because PSM is not installed error = "unknownSocketType"; break; case NS_ERROR_NET_RESET: // Doc failed to load because the server kept reseting the connection // before we could read any data from it error = "netReset"; break; case NS_ERROR_DOCUMENT_NOT_CACHED: // Doc failed to load because the cache does not contain a copy of // the document. error = "notCached"; break; case NS_ERROR_OFFLINE: // Doc failed to load because we are offline. error = "netOffline"; break; case NS_ERROR_DOCUMENT_IS_PRINTMODE: // Doc navigation attempted while Printing or Print Preview error = "isprinting"; break; case NS_ERROR_PORT_ACCESS_NOT_ALLOWED: // Port blocked for security reasons addHostPort = true; error = "deniedPortAccess"; break; case NS_ERROR_UNKNOWN_PROXY_HOST: // Proxy hostname could not be resolved. error = "proxyResolveFailure"; break; case NS_ERROR_PROXY_CONNECTION_REFUSED: case NS_ERROR_PROXY_FORBIDDEN: case NS_ERROR_PROXY_NOT_IMPLEMENTED: case NS_ERROR_PROXY_AUTHENTICATION_FAILED: case NS_ERROR_PROXY_TOO_MANY_REQUESTS: // Proxy connection was refused. error = "proxyConnectFailure"; break; case NS_ERROR_INVALID_CONTENT_ENCODING: // Bad Content Encoding. error = "contentEncodingError"; break; case NS_ERROR_UNSAFE_CONTENT_TYPE: // Channel refused to load from an unrecognized content type. error = "unsafeContentType"; break; case NS_ERROR_CORRUPTED_CONTENT: // Broken Content Detected. e.g. Content-MD5 check failure. error = "corruptedContentErrorv2"; break; case NS_ERROR_INTERCEPTION_FAILED: // ServiceWorker intercepted request, but something went wrong. error = "corruptedContentErrorv2"; break; case NS_ERROR_NET_INADEQUATE_SECURITY: // Server negotiated bad TLS for HTTP/2. error = "inadequateSecurityError"; addHostPort = true; break; case NS_ERROR_BLOCKED_BY_POLICY: case NS_ERROR_DOM_COOP_FAILED: case NS_ERROR_DOM_COEP_FAILED: // Page blocked by policy error = "blockedByPolicy"; break; case NS_ERROR_NET_HTTP2_SENT_GOAWAY: case NS_ERROR_NET_HTTP3_PROTOCOL_ERROR: // HTTP/2 or HTTP/3 stack detected a protocol error error = "networkProtocolError"; break; default: break; } } nsresult delegateErrorCode = aError; // If the HTTPS-Only Mode upgraded this request and the upgrade might have // caused this error, we replace the error-page with about:httpsonlyerror if (nsHTTPSOnlyUtils::CouldBeHttpsOnlyError(aFailedChannel, aError)) { errorPage.AssignLiteral("httpsonlyerror"); delegateErrorCode = NS_ERROR_HTTPS_ONLY; } else if (isBadStsCertError) { delegateErrorCode = NS_ERROR_BAD_HSTS_CERT; } if (nsCOMPtr loadURIDelegate = GetLoadURIDelegate()) { nsCOMPtr errorPageURI; rv = loadURIDelegate->HandleLoadError( aURI, delegateErrorCode, NS_ERROR_GET_MODULE(delegateErrorCode), getter_AddRefs(errorPageURI)); // If the docshell is going away there's no point in showing an error page. if (NS_FAILED(rv) || mIsBeingDestroyed) { *aDisplayedErrorPage = false; return NS_OK; } if (errorPageURI) { *aDisplayedErrorPage = NS_SUCCEEDED(LoadErrorPage(errorPageURI, aURI, aFailedChannel)); return NS_OK; } } // Test if the error should be displayed if (!error) { return NS_OK; } if (!errorDescriptionID) { errorDescriptionID = error; } Telemetry::AccumulateCategoricalKeyed( IsSubframe() ? "frame"_ns : "top"_ns, mozilla::dom::LoadErrorToTelemetryLabel(aError)); // Test if the error needs to be formatted if (!messageStr.IsEmpty()) { // already obtained message } else { if (addHostPort) { // Build up the host:port string. nsAutoCString hostport; if (aURI) { aURI->GetHostPort(hostport); } else { hostport.Assign('?'); } CopyUTF8toUTF16(hostport, *formatStrs.AppendElement()); } nsAutoCString spec; rv = NS_ERROR_NOT_AVAILABLE; auto& nextFormatStr = *formatStrs.AppendElement(); if (aURI) { // displaying "file://" is aesthetically unpleasing and could even be // confusing to the user if (SchemeIsFile(aURI)) { aURI->GetPathQueryRef(spec); } else { aURI->GetSpec(spec); } nsCOMPtr textToSubURI( do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv)); if (NS_SUCCEEDED(rv)) { rv = textToSubURI->UnEscapeURIForUI(spec, nextFormatStr); } } else { spec.Assign('?'); } if (NS_FAILED(rv)) { CopyUTF8toUTF16(spec, nextFormatStr); } rv = NS_OK; nsAutoString str; rv = stringBundle->FormatStringFromName(errorDescriptionID, formatStrs, str); NS_ENSURE_SUCCESS(rv, rv); messageStr.Assign(str); } // Display the error as a page or an alert prompt NS_ENSURE_FALSE(messageStr.IsEmpty(), NS_ERROR_FAILURE); if ((NS_ERROR_NET_INTERRUPT == aError || NS_ERROR_NET_RESET == aError) && SchemeIsHTTPS(aURI)) { // Maybe TLS intolerant. Treat this as an SSL error. error = "nssFailure2"; } if (mBrowsingContext->GetUseErrorPages()) { // Display an error page nsresult loadedPage = LoadErrorPage(aURI, aURL, errorPage.get(), error, messageStr.get(), cssClass.get(), aFailedChannel); *aDisplayedErrorPage = NS_SUCCEEDED(loadedPage); } else { // The prompter reqires that our private window has a document (or it // asserts). Satisfy that assertion now since GetDoc will force // creation of one if it hasn't already been created. if (mScriptGlobal) { Unused << mScriptGlobal->GetDoc(); } // Display a message box prompter->Alert(nullptr, messageStr.get()); } return NS_OK; } #define PREF_SAFEBROWSING_ALLOWOVERRIDE "browser.safebrowsing.allowOverride" nsresult nsDocShell::LoadErrorPage(nsIURI* aURI, const char16_t* aURL, const char* aErrorPage, const char* aErrorType, const char16_t* aDescription, const char* aCSSClass, nsIChannel* aFailedChannel) { if (mIsBeingDestroyed) { return NS_ERROR_NOT_AVAILABLE; } #if defined(DEBUG) if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { nsAutoCString chanName; if (aFailedChannel) { aFailedChannel->GetName(chanName); } else { chanName.AssignLiteral(""); } MOZ_LOG(gDocShellLog, LogLevel::Debug, ("nsDocShell[%p]::LoadErrorPage(\"%s\", \"%s\", {...}, [%s])\n", this, aURI ? aURI->GetSpecOrDefault().get() : "", NS_ConvertUTF16toUTF8(aURL).get(), chanName.get())); } #endif nsAutoCString url; if (aURI) { nsresult rv = aURI->GetSpec(url); NS_ENSURE_SUCCESS(rv, rv); } else if (aURL) { CopyUTF16toUTF8(MakeStringSpan(aURL), url); } else { return NS_ERROR_INVALID_POINTER; } // Create a URL to pass all the error information through to the page. #undef SAFE_ESCAPE #define SAFE_ESCAPE(output, input, params) \ if (NS_WARN_IF(!NS_Escape(input, output, params))) { \ return NS_ERROR_OUT_OF_MEMORY; \ } nsCString escapedUrl, escapedError, escapedDescription, escapedCSSClass; SAFE_ESCAPE(escapedUrl, url, url_Path); SAFE_ESCAPE(escapedError, nsDependentCString(aErrorType), url_Path); SAFE_ESCAPE(escapedDescription, NS_ConvertUTF16toUTF8(aDescription), url_Path); if (aCSSClass) { nsCString cssClass(aCSSClass); SAFE_ESCAPE(escapedCSSClass, cssClass, url_Path); } nsCString errorPageUrl("about:"); errorPageUrl.AppendASCII(aErrorPage); errorPageUrl.AppendLiteral("?e="); errorPageUrl.AppendASCII(escapedError.get()); errorPageUrl.AppendLiteral("&u="); errorPageUrl.AppendASCII(escapedUrl.get()); if ((strcmp(aErrorPage, "blocked") == 0) && Preferences::GetBool(PREF_SAFEBROWSING_ALLOWOVERRIDE, true)) { errorPageUrl.AppendLiteral("&o=1"); } if (!escapedCSSClass.IsEmpty()) { errorPageUrl.AppendLiteral("&s="); errorPageUrl.AppendASCII(escapedCSSClass.get()); } errorPageUrl.AppendLiteral("&c=UTF-8"); nsCOMPtr cps = do_GetService(NS_CAPTIVEPORTAL_CID); int32_t cpsState; if (cps && NS_SUCCEEDED(cps->GetState(&cpsState)) && cpsState == nsICaptivePortalService::LOCKED_PORTAL) { errorPageUrl.AppendLiteral("&captive=true"); } errorPageUrl.AppendLiteral("&d="); errorPageUrl.AppendASCII(escapedDescription.get()); nsCOMPtr errorPageURI; nsresult rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl); NS_ENSURE_SUCCESS(rv, rv); return LoadErrorPage(errorPageURI, aURI, aFailedChannel); } nsresult nsDocShell::LoadErrorPage(nsIURI* aErrorURI, nsIURI* aFailedURI, nsIChannel* aFailedChannel) { mFailedChannel = aFailedChannel; mFailedURI = aFailedURI; mFailedLoadType = mLoadType; if (mLSHE) { // Abandon mLSHE's BFCache entry and create a new one. This way, if // we go back or forward to another SHEntry with the same doc // identifier, the error page won't persist. mLSHE->AbandonBFCacheEntry(); } RefPtr loadState = new nsDocShellLoadState(aErrorURI); loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal()); if (mBrowsingContext) { loadState->SetTriggeringSandboxFlags(mBrowsingContext->GetSandboxFlags()); } loadState->SetLoadType(LOAD_ERROR_PAGE); loadState->SetFirstParty(true); loadState->SetSourceBrowsingContext(mBrowsingContext); if (mozilla::SessionHistoryInParent() && mLoadingEntry) { // We keep the loading entry for the load that failed here. If the user // reloads we want to try to reload the original load, not the error page. loadState->SetLoadingSessionHistoryInfo( MakeUnique(*mLoadingEntry)); } return InternalLoad(loadState); } NS_IMETHODIMP nsDocShell::Reload(uint32_t aReloadFlags) { if (!IsNavigationAllowed()) { return NS_OK; // JS may not handle returning of an error code } NS_ASSERTION(((aReloadFlags & INTERNAL_LOAD_FLAGS_LOADURI_SETUP_FLAGS) == 0), "Reload command not updated to use load flags!"); NS_ASSERTION((aReloadFlags & EXTRA_LOAD_FLAGS) == 0, "Don't pass these flags to Reload"); uint32_t loadType = MAKE_LOAD_TYPE(LOAD_RELOAD_NORMAL, aReloadFlags); NS_ENSURE_TRUE(IsValidLoadType(loadType), NS_ERROR_INVALID_ARG); // Send notifications to the HistoryListener if any, about the impending // reload RefPtr rootSH = GetRootSessionHistory(); if (mozilla::SessionHistoryInParent()) { MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p Reload", this)); bool forceReload = IsForceReloadType(loadType); if (!XRE_IsParentProcess()) { ++mPendingReloadCount; RefPtr docShell(this); nsCOMPtr cv(mContentViewer); NS_ENSURE_STATE(cv); bool okToUnload = true; MOZ_TRY(cv->PermitUnload(&okToUnload)); if (!okToUnload) { return NS_OK; } RefPtr doc(GetDocument()); RefPtr browsingContext(mBrowsingContext); nsCOMPtr currentURI(mCurrentURI); nsCOMPtr referrerInfo(mReferrerInfo); RefPtr stopDetector = new StopDetector(); nsCOMPtr loadGroup; GetLoadGroup(getter_AddRefs(loadGroup)); if (loadGroup) { // loadGroup may be null in theory. In that case stopDetector just // doesn't do anything. loadGroup->AddRequest(stopDetector, nullptr); } ContentChild::GetSingleton()->SendNotifyOnHistoryReload( mBrowsingContext, forceReload, [docShell, doc, loadType, browsingContext, currentURI, referrerInfo, loadGroup, stopDetector]( std::tuple>>, Maybe>&& aResult) { auto scopeExit = MakeScopeExit([loadGroup, stopDetector]() { if (loadGroup) { loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK); } }); // Decrease mPendingReloadCount before any other early returns! if (--(docShell->mPendingReloadCount) > 0) { return; } if (stopDetector->Canceled()) { return; } bool canReload; Maybe>> loadState; Maybe reloadingActiveEntry; std::tie(canReload, loadState, reloadingActiveEntry) = aResult; if (!canReload) { return; } if (loadState.isSome()) { MOZ_LOG( gSHLog, LogLevel::Debug, ("nsDocShell %p Reload - LoadHistoryEntry", docShell.get())); loadState.ref()->SetNotifiedBeforeUnloadListeners(true); docShell->LoadHistoryEntry(loadState.ref(), loadType, reloadingActiveEntry.ref()); } else { MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p ReloadDocument", docShell.get())); ReloadDocument(docShell, doc, loadType, browsingContext, currentURI, referrerInfo, /* aNotifiedBeforeUnloadListeners */ true); } }, [](mozilla::ipc::ResponseRejectReason) {}); } else { // Parent process bool canReload = false; Maybe>> loadState; Maybe reloadingActiveEntry; if (!mBrowsingContext->IsDiscarded()) { mBrowsingContext->Canonical()->NotifyOnHistoryReload( forceReload, canReload, loadState, reloadingActiveEntry); } if (canReload) { if (loadState.isSome()) { MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p Reload - LoadHistoryEntry", this)); LoadHistoryEntry(loadState.ref(), loadType, reloadingActiveEntry.ref()); } else { MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p ReloadDocument", this)); RefPtr doc = GetDocument(); RefPtr bc = mBrowsingContext; nsCOMPtr currentURI = mCurrentURI; nsCOMPtr referrerInfo = mReferrerInfo; ReloadDocument(this, doc, loadType, bc, currentURI, referrerInfo); } } } return NS_OK; } bool canReload = true; if (rootSH) { rootSH->LegacySHistory()->NotifyOnHistoryReload(&canReload); } if (!canReload) { return NS_OK; } /* If you change this part of code, make sure bug 45297 does not re-occur */ if (mOSHE) { nsCOMPtr oshe = mOSHE; return LoadHistoryEntry( oshe, loadType, aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION); } if (mLSHE) { // In case a reload happened before the current load is done nsCOMPtr lshe = mLSHE; return LoadHistoryEntry( lshe, loadType, aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION); } RefPtr doc = GetDocument(); RefPtr bc = mBrowsingContext; nsCOMPtr currentURI = mCurrentURI; nsCOMPtr referrerInfo = mReferrerInfo; return ReloadDocument(this, doc, loadType, bc, currentURI, referrerInfo); } /* static */ nsresult nsDocShell::ReloadDocument(nsDocShell* aDocShell, Document* aDocument, uint32_t aLoadType, BrowsingContext* aBrowsingContext, nsIURI* aCurrentURI, nsIReferrerInfo* aReferrerInfo, bool aNotifiedBeforeUnloadListeners) { if (!aDocument) { return NS_OK; } // Do not inherit owner from document uint32_t flags = INTERNAL_LOAD_FLAGS_NONE; nsAutoString srcdoc; nsIURI* baseURI = nullptr; nsCOMPtr originalURI; nsCOMPtr resultPrincipalURI; bool loadReplace = false; nsIPrincipal* triggeringPrincipal = aDocument->NodePrincipal(); nsCOMPtr csp = aDocument->GetCsp(); uint32_t triggeringSandboxFlags = aDocument->GetSandboxFlags(); nsAutoString contentTypeHint; aDocument->GetContentType(contentTypeHint); if (aDocument->IsSrcdocDocument()) { aDocument->GetSrcdocData(srcdoc); flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC; baseURI = aDocument->GetBaseURI(); } else { srcdoc = VoidString(); } nsCOMPtr chan = aDocument->GetChannel(); if (chan) { uint32_t loadFlags; chan->GetLoadFlags(&loadFlags); loadReplace = loadFlags & nsIChannel::LOAD_REPLACE; nsCOMPtr httpChan(do_QueryInterface(chan)); if (httpChan) { httpChan->GetOriginalURI(getter_AddRefs(originalURI)); } nsCOMPtr loadInfo = chan->LoadInfo(); loadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI)); } if (!triggeringPrincipal) { MOZ_ASSERT(false, "Reload needs a valid triggeringPrincipal"); return NS_ERROR_FAILURE; } // Stack variables to ensure changes to the member variables don't affect to // the call. nsCOMPtr currentURI = aCurrentURI; // Reload always rewrites result principal URI. Maybe> emplacedResultPrincipalURI; emplacedResultPrincipalURI.emplace(std::move(resultPrincipalURI)); RefPtr context = aBrowsingContext->GetCurrentWindowContext(); RefPtr loadState = new nsDocShellLoadState(currentURI); loadState->SetReferrerInfo(aReferrerInfo); loadState->SetOriginalURI(originalURI); loadState->SetMaybeResultPrincipalURI(emplacedResultPrincipalURI); loadState->SetLoadReplace(loadReplace); loadState->SetTriggeringPrincipal(triggeringPrincipal); loadState->SetTriggeringSandboxFlags(triggeringSandboxFlags); loadState->SetPrincipalToInherit(triggeringPrincipal); loadState->SetCsp(csp); loadState->SetInternalLoadFlags(flags); loadState->SetTypeHint(NS_ConvertUTF16toUTF8(contentTypeHint)); loadState->SetLoadType(aLoadType); loadState->SetFirstParty(true); loadState->SetSrcdocData(srcdoc); loadState->SetSourceBrowsingContext(aBrowsingContext); loadState->SetBaseURI(baseURI); loadState->SetHasValidUserGestureActivation( context && context->HasValidTransientUserGestureActivation()); loadState->SetNotifiedBeforeUnloadListeners(aNotifiedBeforeUnloadListeners); return aDocShell->InternalLoad(loadState); } NS_IMETHODIMP nsDocShell::Stop(uint32_t aStopFlags) { // Revoke any pending event related to content viewer restoration mRestorePresentationEvent.Revoke(); if (mLoadType == LOAD_ERROR_PAGE) { if (mLSHE) { // Since error page loads never unset mLSHE, do so now SetHistoryEntryAndUpdateBC(Some(nullptr), Some(mLSHE)); } mActiveEntryIsLoadingFromSessionHistory = false; mFailedChannel = nullptr; mFailedURI = nullptr; } if (nsIWebNavigation::STOP_CONTENT & aStopFlags) { // Stop the document loading and animations if (mContentViewer) { nsCOMPtr cv = mContentViewer; cv->Stop(); } } else if (nsIWebNavigation::STOP_NETWORK & aStopFlags) { // Stop the document loading only if (mContentViewer) { RefPtr doc = mContentViewer->GetDocument(); if (doc) { doc->StopDocumentLoad(); } } } if (nsIWebNavigation::STOP_NETWORK & aStopFlags) { // Suspend any timers that were set for this loader. We'll clear // them out for good in CreateContentViewer. if (mRefreshURIList) { SuspendRefreshURIs(); mSavedRefreshURIList.swap(mRefreshURIList); mRefreshURIList = nullptr; } // XXXbz We could also pass |this| to nsIURILoader::Stop. That will // just call Stop() on us as an nsIDocumentLoader... We need fewer // redundant apis! Stop(); // Clear out mChannelToDisconnectOnPageHide. This page won't go in the // BFCache now, and the Stop above will have removed the DocumentChannel // from the loadgroup. mChannelToDisconnectOnPageHide = 0; } for (auto* child : mChildList.ForwardRange()) { nsCOMPtr shellAsNav(do_QueryObject(child)); if (shellAsNav) { shellAsNav->Stop(aStopFlags); } } return NS_OK; } NS_IMETHODIMP nsDocShell::GetDocument(Document** aDocument) { NS_ENSURE_ARG_POINTER(aDocument); NS_ENSURE_SUCCESS(EnsureContentViewer(), NS_ERROR_FAILURE); RefPtr doc = mContentViewer->GetDocument(); if (!doc) { return NS_ERROR_NOT_AVAILABLE; } doc.forget(aDocument); return NS_OK; } NS_IMETHODIMP nsDocShell::GetCurrentURI(nsIURI** aURI) { NS_ENSURE_ARG_POINTER(aURI); nsCOMPtr uri = mCurrentURI; uri.forget(aURI); return NS_OK; } NS_IMETHODIMP nsDocShell::GetSessionHistoryXPCOM(nsISupports** aSessionHistory) { NS_ENSURE_ARG_POINTER(aSessionHistory); RefPtr shistory = GetSessionHistory(); shistory.forget(aSessionHistory); return NS_OK; } //***************************************************************************** // nsDocShell::nsIWebPageDescriptor //***************************************************************************** NS_IMETHODIMP nsDocShell::LoadPageAsViewSource(nsIDocShell* aOtherDocShell, const nsAString& aURI) { if (!aOtherDocShell) { return NS_ERROR_INVALID_POINTER; } nsCOMPtr newURI; nsresult rv = NS_NewURI(getter_AddRefs(newURI), aURI); if (NS_FAILED(rv)) { return rv; } RefPtr loadState; uint32_t cacheKey; auto* otherDocShell = nsDocShell::Cast(aOtherDocShell); if (mozilla::SessionHistoryInParent()) { loadState = new nsDocShellLoadState(newURI); if (!otherDocShell->FillLoadStateFromCurrentEntry(*loadState)) { return NS_ERROR_INVALID_POINTER; } cacheKey = otherDocShell->GetCacheKeyFromCurrentEntry().valueOr(0); } else { nsCOMPtr entry; bool isOriginalSHE; otherDocShell->GetCurrentSHEntry(getter_AddRefs(entry), &isOriginalSHE); if (!entry) { return NS_ERROR_INVALID_POINTER; } rv = entry->CreateLoadInfo(getter_AddRefs(loadState)); NS_ENSURE_SUCCESS(rv, rv); entry->GetCacheKey(&cacheKey); loadState->SetURI(newURI); loadState->SetSHEntry(nullptr); } // We're doing a load of the page, via an API that // is only exposed to system code. The triggering principal for this load // should be the system principal. loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal()); loadState->SetOriginalURI(nullptr); loadState->SetResultPrincipalURI(nullptr); return InternalLoad(loadState, Some(cacheKey)); } NS_IMETHODIMP nsDocShell::GetCurrentDescriptor(nsISupports** aPageDescriptor) { MOZ_ASSERT(aPageDescriptor, "Null out param?"); *aPageDescriptor = nullptr; nsISHEntry* src = mOSHE ? mOSHE : mLSHE; if (src) { nsCOMPtr dest; nsresult rv = src->Clone(getter_AddRefs(dest)); if (NS_FAILED(rv)) { return rv; } // null out inappropriate cloned attributes... dest->SetParent(nullptr); dest->SetIsSubFrame(false); return CallQueryInterface(dest, aPageDescriptor); } return NS_ERROR_NOT_AVAILABLE; } already_AddRefed nsDocShell::GetPostDataFromCurrentEntry() const { nsCOMPtr postData; if (mozilla::SessionHistoryInParent()) { if (mActiveEntry) { postData = mActiveEntry->GetPostData(); } else if (mLoadingEntry) { postData = mLoadingEntry->mInfo.GetPostData(); } } else { if (mOSHE) { postData = mOSHE->GetPostData(); } else if (mLSHE) { postData = mLSHE->GetPostData(); } } return postData.forget(); } Maybe nsDocShell::GetCacheKeyFromCurrentEntry() const { if (mozilla::SessionHistoryInParent()) { if (mActiveEntry) { return Some(mActiveEntry->GetCacheKey()); } if (mLoadingEntry) { return Some(mLoadingEntry->mInfo.GetCacheKey()); } } else { if (mOSHE) { return Some(mOSHE->GetCacheKey()); } if (mLSHE) { return Some(mLSHE->GetCacheKey()); } } return Nothing(); } bool nsDocShell::FillLoadStateFromCurrentEntry( nsDocShellLoadState& aLoadState) { if (mLoadingEntry) { mLoadingEntry->mInfo.FillLoadInfo(aLoadState); return true; } if (mActiveEntry) { mActiveEntry->FillLoadInfo(aLoadState); return true; } return false; } //***************************************************************************** // nsDocShell::nsIBaseWindow //***************************************************************************** NS_IMETHODIMP nsDocShell::InitWindow(nativeWindow aParentNativeWindow, nsIWidget* aParentWidget, int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight) { SetParentWidget(aParentWidget); SetPositionAndSize(aX, aY, aWidth, aHeight, 0); NS_ENSURE_TRUE(Initialize(), NS_ERROR_FAILURE); return NS_OK; } NS_IMETHODIMP nsDocShell::Destroy() { // XXX: We allow this function to be called just once. If you are going to // reset new variables in this function, please make sure the variables will // never be re-initialized. Adding assertions to check |mIsBeingDestroyed| // in the setter functions for the variables would be enough. if (mIsBeingDestroyed) { return NS_ERROR_DOCSHELL_DYING; } NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, "Unexpected item type in docshell"); nsCOMPtr serv = services::GetObserverService(); if (serv) { const char* msg = mItemType == typeContent ? NS_WEBNAVIGATION_DESTROY : NS_CHROME_WEBNAVIGATION_DESTROY; serv->NotifyObservers(GetAsSupports(this), msg, nullptr); } mIsBeingDestroyed = true; // Brak the cycle with the initial client, if present. mInitialClientSource.reset(); // Make sure we don't record profile timeline markers anymore SetRecordProfileTimelineMarkers(false); // Make sure to blow away our mLoadingURI just in case. No loads // from inside this pagehide. mLoadingURI = nullptr; // Fire unload event before we blow anything away. (void)FirePageHideNotification(true); // Clear pointers to any detached nsEditorData that's lying // around in shistory entries. Breaks cycle. See bug 430921. if (mOSHE) { mOSHE->SetEditorData(nullptr); } if (mLSHE) { mLSHE->SetEditorData(nullptr); } // Note: mContentListener can be null if Init() failed and we're being // called from the destructor. if (mContentListener) { mContentListener->DropDocShellReference(); mContentListener->SetParentContentListener(nullptr); // Note that we do NOT set mContentListener to null here; that // way if someone tries to do a load in us after this point // the nsDSURIContentListener will block it. All of which // means that we should do this before calling Stop(), of // course. } // Stop any URLs that are currently being loaded... Stop(nsIWebNavigation::STOP_ALL); mEditorData = nullptr; // Save the state of the current document, before destroying the window. // This is needed to capture the state of a frameset when the new document // causes the frameset to be destroyed... PersistLayoutHistoryState(); // Remove this docshell from its parent's child list nsCOMPtr docShellParentAsItem = do_QueryInterface(GetAsSupports(mParent)); if (docShellParentAsItem) { docShellParentAsItem->RemoveChild(this); } if (mContentViewer) { mContentViewer->Close(nullptr); mContentViewer->Destroy(); mContentViewer = nullptr; } nsDocLoader::Destroy(); mParentWidget = nullptr; mCurrentURI = nullptr; if (mScriptGlobal) { mScriptGlobal->DetachFromDocShell(!mWillChangeProcess); mScriptGlobal = nullptr; } if (GetSessionHistory()) { // We want to destroy these content viewers now rather than // letting their destruction wait for the session history // entries to get garbage collected. (Bug 488394) GetSessionHistory()->EvictLocalContentViewers(); } if (mWillChangeProcess && !mBrowsingContext->IsDiscarded()) { mBrowsingContext->PrepareForProcessChange(); } SetTreeOwner(nullptr); mBrowserChild = nullptr; mChromeEventHandler = nullptr; // Cancel any timers that were set for this docshell; this is needed // to break the cycle between us and the timers. CancelRefreshURITimers(); return NS_OK; } double nsDocShell::GetWidgetCSSToDeviceScale() { if (mParentWidget) { return mParentWidget->GetDefaultScale().scale; } if (nsCOMPtr ownerWindow = do_QueryInterface(mTreeOwner)) { return ownerWindow->GetWidgetCSSToDeviceScale(); } return 1.0; } NS_IMETHODIMP nsDocShell::GetDevicePixelsPerDesktopPixel(double* aScale) { if (mParentWidget) { *aScale = mParentWidget->GetDesktopToDeviceScale().scale; return NS_OK; } nsCOMPtr ownerWindow(do_QueryInterface(mTreeOwner)); if (ownerWindow) { return ownerWindow->GetDevicePixelsPerDesktopPixel(aScale); } *aScale = 1.0; return NS_OK; } NS_IMETHODIMP nsDocShell::SetPosition(int32_t aX, int32_t aY) { mBounds.MoveTo(aX, aY); if (mContentViewer) { NS_ENSURE_SUCCESS(mContentViewer->Move(aX, aY), NS_ERROR_FAILURE); } return NS_OK; } NS_IMETHODIMP nsDocShell::SetPositionDesktopPix(int32_t aX, int32_t aY) { nsCOMPtr ownerWindow(do_QueryInterface(mTreeOwner)); if (ownerWindow) { return ownerWindow->SetPositionDesktopPix(aX, aY); } double scale = 1.0; GetDevicePixelsPerDesktopPixel(&scale); return SetPosition(NSToIntRound(aX * scale), NSToIntRound(aY * scale)); } NS_IMETHODIMP nsDocShell::GetPosition(int32_t* aX, int32_t* aY) { return GetPositionAndSize(aX, aY, nullptr, nullptr); } NS_IMETHODIMP nsDocShell::SetSize(int32_t aWidth, int32_t aHeight, bool aRepaint) { int32_t x = 0, y = 0; GetPosition(&x, &y); return SetPositionAndSize(x, y, aWidth, aHeight, aRepaint ? nsIBaseWindow::eRepaint : 0); } NS_IMETHODIMP nsDocShell::GetSize(int32_t* aWidth, int32_t* aHeight) { return GetPositionAndSize(nullptr, nullptr, aWidth, aHeight); } NS_IMETHODIMP nsDocShell::SetPositionAndSize(int32_t aX, int32_t aY, int32_t aWidth, int32_t aHeight, uint32_t aFlags) { mBounds.SetRect(aX, aY, aWidth, aHeight); // Hold strong ref, since SetBounds can make us null out mContentViewer nsCOMPtr viewer = mContentViewer; if (viewer) { uint32_t cvflags = (aFlags & nsIBaseWindow::eDelayResize) ? nsIContentViewer::eDelayResize : 0; // XXX Border figured in here or is that handled elsewhere? nsresult rv = viewer->SetBoundsWithFlags(mBounds, cvflags); NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth, int32_t* aHeight) { if (mParentWidget) { // ensure size is up-to-date if window has changed resolution LayoutDeviceIntRect r = mParentWidget->GetClientBounds(); SetPositionAndSize(mBounds.X(), mBounds.Y(), r.Width(), r.Height(), 0); } // We should really consider just getting this information from // our window instead of duplicating the storage and code... if (aWidth || aHeight) { // Caller wants to know our size; make sure to give them up to // date information. RefPtr doc(do_GetInterface(GetAsSupports(mParent))); if (doc) { doc->FlushPendingNotifications(FlushType::Layout); } } DoGetPositionAndSize(aX, aY, aWidth, aHeight); return NS_OK; } void nsDocShell::DoGetPositionAndSize(int32_t* aX, int32_t* aY, int32_t* aWidth, int32_t* aHeight) { if (aX) { *aX = mBounds.X(); } if (aY) { *aY = mBounds.Y(); } if (aWidth) { *aWidth = mBounds.Width(); } if (aHeight) { *aHeight = mBounds.Height(); } } NS_IMETHODIMP nsDocShell::SetDimensions(DimensionRequest&& aRequest) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDocShell::GetDimensions(DimensionKind aDimensionKind, int32_t* aX, int32_t* aY, int32_t* aCX, int32_t* aCY) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDocShell::Repaint(bool aForce) { PresShell* presShell = GetPresShell(); NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); RefPtr viewManager = presShell->GetViewManager(); NS_ENSURE_TRUE(viewManager, NS_ERROR_FAILURE); viewManager->InvalidateAllViews(); return NS_OK; } NS_IMETHODIMP nsDocShell::GetParentWidget(nsIWidget** aParentWidget) { NS_ENSURE_ARG_POINTER(aParentWidget); *aParentWidget = mParentWidget; NS_IF_ADDREF(*aParentWidget); return NS_OK; } NS_IMETHODIMP nsDocShell::SetParentWidget(nsIWidget* aParentWidget) { MOZ_ASSERT(!mIsBeingDestroyed); mParentWidget = aParentWidget; return NS_OK; } NS_IMETHODIMP nsDocShell::GetParentNativeWindow(nativeWindow* aParentNativeWindow) { NS_ENSURE_ARG_POINTER(aParentNativeWindow); if (mParentWidget) { *aParentNativeWindow = mParentWidget->GetNativeData(NS_NATIVE_WIDGET); } else { *aParentNativeWindow = nullptr; } return NS_OK; } NS_IMETHODIMP nsDocShell::SetParentNativeWindow(nativeWindow aParentNativeWindow) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDocShell::GetNativeHandle(nsAString& aNativeHandle) { // the nativeHandle should be accessed from nsIAppWindow return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDocShell::GetVisibility(bool* aVisibility) { NS_ENSURE_ARG_POINTER(aVisibility); *aVisibility = false; if (!mContentViewer) { return NS_OK; } PresShell* presShell = GetPresShell(); if (!presShell) { return NS_OK; } // get the view manager nsViewManager* vm = presShell->GetViewManager(); NS_ENSURE_TRUE(vm, NS_ERROR_FAILURE); // get the root view nsView* view = vm->GetRootView(); // views are not ref counted NS_ENSURE_TRUE(view, NS_ERROR_FAILURE); // if our root view is hidden, we are not visible if (view->GetVisibility() == ViewVisibility::Hide) { return NS_OK; } // otherwise, we must walk up the document and view trees checking // for a hidden view, unless we're an off screen browser, which // would make this test meaningless. RefPtr docShell = this; RefPtr parentItem = docShell->GetInProcessParentDocshell(); while (parentItem) { // Null-check for crash in bug 267804 if (!parentItem->GetPresShell()) { MOZ_ASSERT_UNREACHABLE("parent docshell has null pres shell"); return NS_OK; } vm = docShell->GetPresShell()->GetViewManager(); if (vm) { view = vm->GetRootView(); } if (view) { view = view->GetParent(); // anonymous inner view if (view) { view = view->GetParent(); // subdocumentframe's view } } nsIFrame* frame = view ? view->GetFrame() : nullptr; if (frame && !frame->IsVisibleConsideringAncestors( nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY)) { return NS_OK; } docShell = parentItem; parentItem = docShell->GetInProcessParentDocshell(); } nsCOMPtr treeOwnerAsWin(do_QueryInterface(mTreeOwner)); if (!treeOwnerAsWin) { *aVisibility = true; return NS_OK; } // Check with the tree owner as well to give embedders a chance to // expose visibility as well. nsresult rv = treeOwnerAsWin->GetVisibility(aVisibility); if (rv == NS_ERROR_NOT_IMPLEMENTED) { // The tree owner had no opinion on our visibility. *aVisibility = true; return NS_OK; } return rv; } void nsDocShell::ActivenessMaybeChanged() { const bool isActive = mBrowsingContext->IsActive(); if (RefPtr presShell = GetPresShell()) { presShell->ActivenessMaybeChanged(); } // Tell the window about it if (mScriptGlobal) { mScriptGlobal->SetIsBackground(!isActive); if (RefPtr doc = mScriptGlobal->GetExtantDoc()) { // Update orientation when the top-level browsing context becomes active. if (isActive && mBrowsingContext->IsTop()) { // We only care about the top-level browsing context. auto orientation = mBrowsingContext->GetOrientationLock(); ScreenOrientation::UpdateActiveOrientationLock(orientation); } doc->PostVisibilityUpdateEvent(); } } // Tell the nsDOMNavigationTiming about it RefPtr timing = mTiming; if (!timing && mContentViewer) { if (Document* doc = mContentViewer->GetDocument()) { timing = doc->GetNavigationTiming(); } } if (timing) { timing->NotifyDocShellStateChanged( isActive ? nsDOMNavigationTiming::DocShellState::eActive : nsDOMNavigationTiming::DocShellState::eInactive); } // Restart or stop meta refresh timers if necessary if (mDisableMetaRefreshWhenInactive) { if (isActive) { ResumeRefreshURIs(); } else { SuspendRefreshURIs(); } } if (InputTaskManager::CanSuspendInputEvent()) { mBrowsingContext->Group()->UpdateInputTaskManagerIfNeeded(isActive); } } NS_IMETHODIMP nsDocShell::SetDefaultLoadFlags(uint32_t aDefaultLoadFlags) { if (!mWillChangeProcess) { // Intentionally ignoring handling discarded browsing contexts. Unused << mBrowsingContext->SetDefaultLoadFlags(aDefaultLoadFlags); } else { // Bug 1623565: DevTools tries to clean up defaultLoadFlags on // shutdown. Sorry DevTools, your DocShell is in another process. NS_WARNING("nsDocShell::SetDefaultLoadFlags called on Zombie DocShell"); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetDefaultLoadFlags(uint32_t* aDefaultLoadFlags) { *aDefaultLoadFlags = mBrowsingContext->GetDefaultLoadFlags(); return NS_OK; } NS_IMETHODIMP nsDocShell::GetFailedChannel(nsIChannel** aFailedChannel) { NS_ENSURE_ARG_POINTER(aFailedChannel); Document* doc = GetDocument(); if (!doc) { *aFailedChannel = nullptr; return NS_OK; } NS_IF_ADDREF(*aFailedChannel = doc->GetFailedChannel()); return NS_OK; } NS_IMETHODIMP nsDocShell::SetVisibility(bool aVisibility) { // Show()/Hide() may change mContentViewer. nsCOMPtr cv = mContentViewer; if (!cv) { return NS_OK; } if (aVisibility) { cv->Show(); } else { cv->Hide(); } return NS_OK; } NS_IMETHODIMP nsDocShell::GetEnabled(bool* aEnabled) { NS_ENSURE_ARG_POINTER(aEnabled); *aEnabled = true; return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDocShell::SetEnabled(bool aEnabled) { return NS_ERROR_NOT_IMPLEMENTED; } NS_IMETHODIMP nsDocShell::GetMainWidget(nsIWidget** aMainWidget) { // We don't create our own widget, so simply return the parent one. return GetParentWidget(aMainWidget); } NS_IMETHODIMP nsDocShell::GetTitle(nsAString& aTitle) { aTitle = mTitle; return NS_OK; } NS_IMETHODIMP nsDocShell::SetTitle(const nsAString& aTitle) { // Avoid unnecessary updates of the title if the URI and the title haven't // changed. if (mTitleValidForCurrentURI && mTitle == aTitle) { return NS_OK; } // Store local title mTitle = aTitle; mTitleValidForCurrentURI = true; // When title is set on the top object it should then be passed to the // tree owner. if (mBrowsingContext->IsTop()) { nsCOMPtr treeOwnerAsWin(do_QueryInterface(mTreeOwner)); if (treeOwnerAsWin) { treeOwnerAsWin->SetTitle(aTitle); } } if (mCurrentURI && mLoadType != LOAD_ERROR_PAGE) { UpdateGlobalHistoryTitle(mCurrentURI); } // Update SessionHistory with the document's title. if (mLoadType != LOAD_BYPASS_HISTORY && mLoadType != LOAD_ERROR_PAGE) { SetTitleOnHistoryEntry(true); } return NS_OK; } void nsDocShell::SetTitleOnHistoryEntry(bool aUpdateEntryInSessionHistory) { if (mOSHE) { mOSHE->SetTitle(mTitle); } if (mActiveEntry && mBrowsingContext) { mActiveEntry->SetTitle(mTitle); if (aUpdateEntryInSessionHistory) { if (XRE_IsParentProcess()) { SessionHistoryEntry* entry = mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); if (entry) { entry->SetTitle(mTitle); } } else { mozilla::Unused << ContentChild::GetSingleton()->SendSessionHistoryEntryTitle( mBrowsingContext, mTitle); } } } } nsPoint nsDocShell::GetCurScrollPos() { nsPoint scrollPos; if (nsIScrollableFrame* sf = GetRootScrollFrame()) { scrollPos = sf->GetVisualViewportOffset(); } return scrollPos; } nsresult nsDocShell::SetCurScrollPosEx(int32_t aCurHorizontalPos, int32_t aCurVerticalPos) { nsIScrollableFrame* sf = GetRootScrollFrame(); NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); ScrollMode scrollMode = sf->IsSmoothScroll() ? ScrollMode::SmoothMsd : ScrollMode::Instant; nsPoint targetPos(aCurHorizontalPos, aCurVerticalPos); sf->ScrollTo(targetPos, scrollMode); // Set the visual viewport offset as well. RefPtr presShell = GetPresShell(); NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); nsPresContext* presContext = presShell->GetPresContext(); NS_ENSURE_TRUE(presContext, NS_ERROR_FAILURE); // Only the root content document can have a distinct visual viewport offset. if (!presContext->IsRootContentDocumentCrossProcess()) { return NS_OK; } // Not on a platform with a distinct visual viewport - don't bother setting // the visual viewport offset. if (!presShell->IsVisualViewportSizeSet()) { return NS_OK; } presShell->ScrollToVisual(targetPos, layers::FrameMetrics::eMainThread, scrollMode); return NS_OK; } void nsDocShell::SetScrollbarPreference(mozilla::ScrollbarPreference aPref) { if (mScrollbarPref == aPref) { return; } mScrollbarPref = aPref; auto* ps = GetPresShell(); if (!ps) { return; } nsIFrame* scrollFrame = ps->GetRootScrollFrame(); if (!scrollFrame) { return; } ps->FrameNeedsReflow(scrollFrame, IntrinsicDirty::FrameAncestorsAndDescendants, NS_FRAME_IS_DIRTY); } //***************************************************************************** // nsDocShell::nsIRefreshURI //***************************************************************************** NS_IMETHODIMP nsDocShell::RefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal, uint32_t aDelay) { MOZ_ASSERT(!mIsBeingDestroyed); NS_ENSURE_ARG(aURI); /* Check if Meta refresh/redirects are permitted. Some * embedded applications may not want to do this. * Must do this before sending out NOTIFY_REFRESH events * because listeners may have side effects (e.g. displaying a * button to manually trigger the refresh later). */ bool allowRedirects = true; GetAllowMetaRedirects(&allowRedirects); if (!allowRedirects) { return NS_OK; } // If any web progress listeners are listening for NOTIFY_REFRESH events, // give them a chance to block this refresh. bool sameURI; nsresult rv = aURI->Equals(mCurrentURI, &sameURI); if (NS_FAILED(rv)) { sameURI = false; } if (!RefreshAttempted(this, aURI, aDelay, sameURI)) { return NS_OK; } nsCOMPtr refreshTimer = new nsRefreshTimer(this, aURI, aPrincipal, aDelay); BusyFlags busyFlags = GetBusyFlags(); if (!mRefreshURIList) { mRefreshURIList = nsArray::Create(); } if (busyFlags & BUSY_FLAGS_BUSY || (!mBrowsingContext->IsActive() && mDisableMetaRefreshWhenInactive)) { // We don't want to create the timer right now. Instead queue up the // request and trigger the timer in EndPageLoad() or whenever we become // active. mRefreshURIList->AppendElement(refreshTimer); } else { // There is no page loading going on right now. Create the // timer and fire it right away. nsCOMPtr win = GetWindow(); NS_ENSURE_TRUE(win, NS_ERROR_FAILURE); nsCOMPtr timer; MOZ_TRY_VAR(timer, NS_NewTimerWithCallback(refreshTimer, aDelay, nsITimer::TYPE_ONE_SHOT)); mRefreshURIList->AppendElement(timer); // owning timer ref } return NS_OK; } nsresult nsDocShell::ForceRefreshURIFromTimer(nsIURI* aURI, nsIPrincipal* aPrincipal, uint32_t aDelay, nsITimer* aTimer) { MOZ_ASSERT(aTimer, "Must have a timer here"); // Remove aTimer from mRefreshURIList if needed if (mRefreshURIList) { uint32_t n = 0; mRefreshURIList->GetLength(&n); for (uint32_t i = 0; i < n; ++i) { nsCOMPtr timer = do_QueryElementAt(mRefreshURIList, i); if (timer == aTimer) { mRefreshURIList->RemoveElementAt(i); break; } } } return ForceRefreshURI(aURI, aPrincipal, aDelay); } NS_IMETHODIMP nsDocShell::ForceRefreshURI(nsIURI* aURI, nsIPrincipal* aPrincipal, uint32_t aDelay) { NS_ENSURE_ARG(aURI); RefPtr loadState = new nsDocShellLoadState(aURI); loadState->SetOriginalURI(mCurrentURI); loadState->SetResultPrincipalURI(aURI); loadState->SetResultPrincipalURIIsSome(true); loadState->SetKeepResultPrincipalURIIfSet(true); loadState->SetIsMetaRefresh(true); // Set the triggering pricipal to aPrincipal if available, or current // document's principal otherwise. nsCOMPtr principal = aPrincipal; RefPtr doc = GetDocument(); if (!principal) { if (!doc) { return NS_ERROR_FAILURE; } principal = doc->NodePrincipal(); } loadState->SetTriggeringPrincipal(principal); if (doc) { loadState->SetCsp(doc->GetCsp()); loadState->SetHasValidUserGestureActivation( doc->HasValidTransientUserGestureActivation()); loadState->SetTriggeringSandboxFlags(doc->GetSandboxFlags()); } loadState->SetPrincipalIsExplicit(true); /* Check if this META refresh causes a redirection * to another site. */ bool equalUri = false; nsresult rv = aURI->Equals(mCurrentURI, &equalUri); nsCOMPtr referrerInfo; if (NS_SUCCEEDED(rv) && !equalUri && aDelay <= REFRESH_REDIRECT_TIMER) { /* It is a META refresh based redirection within the threshold time * we have in mind (15000 ms as defined by REFRESH_REDIRECT_TIMER). * Pass a REPLACE flag to LoadURI(). */ loadState->SetLoadType(LOAD_REFRESH_REPLACE); /* For redirects we mimic HTTP, which passes the * original referrer. * We will pass in referrer but will not send to server */ if (mReferrerInfo) { referrerInfo = static_cast(mReferrerInfo.get()) ->CloneWithNewSendReferrer(false); } } else { loadState->SetLoadType(LOAD_REFRESH); /* We do need to pass in a referrer, but we don't want it to * be sent to the server. * For most refreshes the current URI is an appropriate * internal referrer. */ referrerInfo = new ReferrerInfo(mCurrentURI, ReferrerPolicy::_empty, false); } loadState->SetReferrerInfo(referrerInfo); loadState->SetLoadFlags( nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL); loadState->SetFirstParty(true); /* * LoadURI(...) will cancel all refresh timers... This causes the * Timer and its refreshData instance to be released... */ LoadURI(loadState, false); return NS_OK; } static const char16_t* SkipASCIIWhitespace(const char16_t* aStart, const char16_t* aEnd) { const char16_t* iter = aStart; while (iter != aEnd && mozilla::IsAsciiWhitespace(*iter)) { ++iter; } return iter; } static std::tuple ExtractURLString( const char16_t* aPosition, const char16_t* aEnd) { MOZ_ASSERT(aPosition != aEnd); // 1. Let urlString be the substring of input from the code point at // position to the end of the string. const char16_t* urlStart = aPosition; const char16_t* urlEnd = aEnd; // 2. If the code point in input pointed to by position is U+0055 (U) or // U+0075 (u), then advance position to the next code point. // Otherwise, jump to the step labeled skip quotes. if (*aPosition == 'U' || *aPosition == 'u') { ++aPosition; // 3. If the code point in input pointed to by position is U+0052 (R) or // U+0072 (r), then advance position to the next code point. // Otherwise, jump to the step labeled parse. if (aPosition == aEnd || (*aPosition != 'R' && *aPosition != 'r')) { return std::make_tuple(urlStart, urlEnd); } ++aPosition; // 4. If the code point in input pointed to by position is U+004C (L) or // U+006C (l), then advance position to the next code point. // Otherwise, jump to the step labeled parse. if (aPosition == aEnd || (*aPosition != 'L' && *aPosition != 'l')) { return std::make_tuple(urlStart, urlEnd); } ++aPosition; // 5. Skip ASCII whitespace within input given position. aPosition = SkipASCIIWhitespace(aPosition, aEnd); // 6. If the code point in input pointed to by position is U+003D (=), // then advance position to the next code point. Otherwise, jump to // the step labeled parse. if (aPosition == aEnd || *aPosition != '=') { return std::make_tuple(urlStart, urlEnd); } ++aPosition; // 7. Skip ASCII whitespace within input given position. aPosition = SkipASCIIWhitespace(aPosition, aEnd); } // 8. Skip quotes: If the code point in input pointed to by position is // U+0027 (') or U+0022 ("), then let quote be that code point, and // advance position to the next code point. Otherwise, let quote be // the empty string. Maybe quote; if (aPosition != aEnd && (*aPosition == '\'' || *aPosition == '"')) { quote.emplace(*aPosition); ++aPosition; } // 9. Set urlString to the substring of input from the code point at // position to the end of the string. urlStart = aPosition; urlEnd = aEnd; // 10. If quote is not the empty string, and there is a code point in // urlString equal to quote, then truncate urlString at that code // point, so that it and all subsequent code points are removed. const char16_t* quotePos; if (quote.isSome() && (quotePos = nsCharTraits::find( urlStart, std::distance(urlStart, aEnd), quote.value()))) { urlEnd = quotePos; } return std::make_tuple(urlStart, urlEnd); } void nsDocShell::SetupRefreshURIFromHeader(Document* aDocument, const nsAString& aHeader) { if (mIsBeingDestroyed) { return; } const char16_t* position = aHeader.BeginReading(); const char16_t* end = aHeader.EndReading(); // See // https://html.spec.whatwg.org/#pragma-directives:shared-declarative-refresh-steps. // 3. Skip ASCII whitespace position = SkipASCIIWhitespace(position, end); // 4. Let time be 0. CheckedInt milliSeconds; // 5. Collect a sequence of code points that are ASCII digits const char16_t* digitsStart = position; while (position != end && mozilla::IsAsciiDigit(*position)) { ++position; } if (position == digitsStart) { // 6. If timeString is the empty string, then: // 1. If the code point in input pointed to by position is not U+002E // (.), then return. if (position == end || *position != '.') { return; } } else { // 7. Otherwise, set time to the result of parsing timeString using the // rules for parsing non-negative integers. nsContentUtils::ParseHTMLIntegerResultFlags result; uint32_t seconds = nsContentUtils::ParseHTMLInteger(digitsStart, position, &result); MOZ_ASSERT(!(result & nsContentUtils::eParseHTMLInteger_Negative)); if (result & nsContentUtils::eParseHTMLInteger_Error) { // The spec assumes no errors here (since we only pass ASCII digits in), // but we can still overflow, so this block should deal with that (and // only that). MOZ_ASSERT(!(result & nsContentUtils::eParseHTMLInteger_ErrorOverflow)); return; } MOZ_ASSERT( !(result & nsContentUtils::eParseHTMLInteger_DidNotConsumeAllInput)); milliSeconds = seconds; milliSeconds *= 1000; if (!milliSeconds.isValid()) { return; } } // 8. Collect a sequence of code points that are ASCII digits and U+002E FULL // STOP characters (.) from input given position. Ignore any collected // characters. while (position != end && (mozilla::IsAsciiDigit(*position) || *position == '.')) { ++position; } // 9. Let urlRecord be document's URL. nsCOMPtr urlRecord(aDocument->GetDocumentURI()); // 10. If position is not past the end of input if (position != end) { // 1. If the code point in input pointed to by position is not U+003B (;), // U+002C (,), or ASCII whitespace, then return. if (*position != ';' && *position != ',' && !mozilla::IsAsciiWhitespace(*position)) { return; } // 2. Skip ASCII whitespace within input given position. position = SkipASCIIWhitespace(position, end); // 3. If the code point in input pointed to by position is U+003B (;) or // U+002C (,), then advance position to the next code point. if (position != end && (*position == ';' || *position == ',')) { ++position; // 4. Skip ASCII whitespace within input given position. position = SkipASCIIWhitespace(position, end); } // 11. If position is not past the end of input, then: if (position != end) { const char16_t* urlStart; const char16_t* urlEnd; // 1-10. See ExtractURLString. std::tie(urlStart, urlEnd) = ExtractURLString(position, end); // 11. Parse: Parse urlString relative to document. If that fails, return. // Otherwise, set urlRecord to the resulting URL record. nsresult rv = NS_NewURI(getter_AddRefs(urlRecord), Substring(urlStart, std::distance(urlStart, urlEnd)), /* charset = */ nullptr, aDocument->GetDocBaseURI()); NS_ENSURE_SUCCESS_VOID(rv); } } nsIPrincipal* principal = aDocument->NodePrincipal(); nsCOMPtr securityManager = nsContentUtils::GetSecurityManager(); nsresult rv = securityManager->CheckLoadURIWithPrincipal( principal, urlRecord, nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT, aDocument->InnerWindowID()); NS_ENSURE_SUCCESS_VOID(rv); bool isjs = true; rv = NS_URIChainHasFlags( urlRecord, nsIProtocolHandler::URI_OPENING_EXECUTES_SCRIPT, &isjs); NS_ENSURE_SUCCESS_VOID(rv); if (isjs) { return; } RefreshURI(urlRecord, principal, milliSeconds.value()); } static void DoCancelRefreshURITimers(nsIMutableArray* aTimerList) { if (!aTimerList) { return; } uint32_t n = 0; aTimerList->GetLength(&n); while (n) { nsCOMPtr timer(do_QueryElementAt(aTimerList, --n)); aTimerList->RemoveElementAt(n); // bye bye owning timer ref if (timer) { timer->Cancel(); } } } NS_IMETHODIMP nsDocShell::CancelRefreshURITimers() { DoCancelRefreshURITimers(mRefreshURIList); DoCancelRefreshURITimers(mSavedRefreshURIList); DoCancelRefreshURITimers(mBFCachedRefreshURIList); mRefreshURIList = nullptr; mSavedRefreshURIList = nullptr; mBFCachedRefreshURIList = nullptr; return NS_OK; } NS_IMETHODIMP nsDocShell::GetRefreshPending(bool* aResult) { if (!mRefreshURIList) { *aResult = false; return NS_OK; } uint32_t count; nsresult rv = mRefreshURIList->GetLength(&count); if (NS_SUCCEEDED(rv)) { *aResult = (count != 0); } return rv; } void nsDocShell::RefreshURIToQueue() { if (mRefreshURIList) { uint32_t n = 0; mRefreshURIList->GetLength(&n); for (uint32_t i = 0; i < n; ++i) { nsCOMPtr timer = do_QueryElementAt(mRefreshURIList, i); if (!timer) { continue; // this must be a nsRefreshURI already } // Replace this timer object with a nsRefreshTimer object. nsCOMPtr callback; timer->GetCallback(getter_AddRefs(callback)); timer->Cancel(); mRefreshURIList->ReplaceElementAt(callback, i); } } } NS_IMETHODIMP nsDocShell::SuspendRefreshURIs() { RefreshURIToQueue(); // Suspend refresh URIs for our child shells as well. for (auto* child : mChildList.ForwardRange()) { nsCOMPtr shell = do_QueryObject(child); if (shell) { shell->SuspendRefreshURIs(); } } return NS_OK; } NS_IMETHODIMP nsDocShell::ResumeRefreshURIs() { RefreshURIFromQueue(); // Resume refresh URIs for our child shells as well. for (auto* child : mChildList.ForwardRange()) { nsCOMPtr shell = do_QueryObject(child); if (shell) { shell->ResumeRefreshURIs(); } } return NS_OK; } nsresult nsDocShell::RefreshURIFromQueue() { if (!mRefreshURIList) { return NS_OK; } uint32_t n = 0; mRefreshURIList->GetLength(&n); while (n) { nsCOMPtr refreshInfo = do_QueryElementAt(mRefreshURIList, --n); if (refreshInfo) { // This is the nsRefreshTimer object, waiting to be // setup in a timer object and fired. // Create the timer and trigger it. uint32_t delay = static_cast( static_cast(refreshInfo)) ->GetDelay(); nsCOMPtr win = GetWindow(); if (win) { nsCOMPtr timer; NS_NewTimerWithCallback(getter_AddRefs(timer), refreshInfo, delay, nsITimer::TYPE_ONE_SHOT); if (timer) { // Replace the nsRefreshTimer element in the queue with // its corresponding timer object, so that in case another // load comes through before the timer can go off, the timer will // get cancelled in CancelRefreshURITimer() mRefreshURIList->ReplaceElementAt(timer, n); } } } } return NS_OK; } static bool IsFollowupPartOfMultipart(nsIRequest* aRequest) { nsCOMPtr multiPartChannel = do_QueryInterface(aRequest); bool firstPart = false; return multiPartChannel && NS_SUCCEEDED(multiPartChannel->GetIsFirstPart(&firstPart)) && !firstPart; } nsresult nsDocShell::Embed(nsIContentViewer* aContentViewer, WindowGlobalChild* aWindowActor, bool aIsTransientAboutBlank, bool aPersist, nsIRequest* aRequest, nsIURI* aPreviousURI) { // Save the LayoutHistoryState of the previous document, before // setting up new document PersistLayoutHistoryState(); nsresult rv = SetupNewViewer(aContentViewer, aWindowActor); NS_ENSURE_SUCCESS(rv, rv); // XXX What if SetupNewViewer fails? if (mozilla::SessionHistoryInParent() ? !!mLoadingEntry : !!mLSHE) { // Set history.state SetDocCurrentStateObj(mLSHE, mLoadingEntry ? &mLoadingEntry->mInfo : nullptr); } if (mLSHE) { // Restore the editing state, if it's stored in session history. if (mLSHE->HasDetachedEditor()) { ReattachEditorToWindow(mLSHE); } SetHistoryEntryAndUpdateBC(Nothing(), Some(mLSHE)); } if (!aIsTransientAboutBlank && mozilla::SessionHistoryInParent() && !IsFollowupPartOfMultipart(aRequest)) { bool expired = false; uint32_t cacheKey = 0; nsCOMPtr cacheChannel = do_QueryInterface(aRequest); if (cacheChannel) { // Check if the page has expired from cache uint32_t expTime = 0; cacheChannel->GetCacheTokenExpirationTime(&expTime); uint32_t now = PRTimeToSeconds(PR_Now()); if (expTime <= now) { expired = true; } // The checks for updating cache key are similar to the old session // history in OnNewURI. Try to update the cache key if // - we should update session history and aren't doing a session // history load. // - we're doing a forced reload. if (((!mLoadingEntry || !mLoadingEntry->mLoadIsFromSessionHistory) && mBrowsingContext->ShouldUpdateSessionHistory(mLoadType)) || IsForceReloadType(mLoadType)) { cacheChannel->GetCacheKey(&cacheKey); } } MOZ_LOG(gSHLog, LogLevel::Debug, ("document %p Embed", this)); MoveLoadingToActiveEntry(aPersist, expired, cacheKey, aPreviousURI); } bool updateHistory = true; // Determine if this type of load should update history switch (mLoadType) { case LOAD_NORMAL_REPLACE: case LOAD_REFRESH_REPLACE: case LOAD_STOP_CONTENT_AND_REPLACE: case LOAD_RELOAD_BYPASS_CACHE: case LOAD_RELOAD_BYPASS_PROXY: case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: case LOAD_REPLACE_BYPASS_CACHE: updateHistory = false; break; default: break; } if (!updateHistory) { SetLayoutHistoryState(nullptr); } return NS_OK; } //***************************************************************************** // nsDocShell::nsIWebProgressListener //***************************************************************************** NS_IMETHODIMP nsDocShell::OnProgressChange(nsIWebProgress* aProgress, nsIRequest* aRequest, int32_t aCurSelfProgress, int32_t aMaxSelfProgress, int32_t aCurTotalProgress, int32_t aMaxTotalProgress) { return NS_OK; } NS_IMETHODIMP nsDocShell::OnStateChange(nsIWebProgress* aProgress, nsIRequest* aRequest, uint32_t aStateFlags, nsresult aStatus) { if ((~aStateFlags & (STATE_START | STATE_IS_NETWORK)) == 0) { // Save timing statistics. nsCOMPtr channel(do_QueryInterface(aRequest)); nsCOMPtr uri; channel->GetURI(getter_AddRefs(uri)); nsAutoCString aURI; uri->GetAsciiSpec(aURI); if (this == aProgress) { mozilla::Unused << MaybeInitTiming(); mTiming->NotifyFetchStart(uri, ConvertLoadTypeToNavigationType(mLoadType)); // If we are starting a DocumentChannel, we need to pass the timing // statistics so that should a process switch occur, the starting type can // be passed to the new DocShell running in the other content process. if (RefPtr docChannel = do_QueryObject(aRequest)) { docChannel->SetNavigationTiming(mTiming); } } // Page has begun to load mBusyFlags = (BusyFlags)(BUSY_FLAGS_BUSY | BUSY_FLAGS_BEFORE_PAGE_LOAD); if ((aStateFlags & STATE_RESTORING) == 0) { // Show the progress cursor if the pref is set if (StaticPrefs::ui_use_activity_cursor()) { nsCOMPtr mainWidget; GetMainWidget(getter_AddRefs(mainWidget)); if (mainWidget) { mainWidget->SetCursor(nsIWidget::Cursor{eCursor_spinning}); } } if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { if (IsForceReloadType(mLoadType)) { if (WindowContext* windowContext = mBrowsingContext->GetCurrentWindowContext()) { SessionStoreChild::From(windowContext->GetWindowGlobalChild()) ->ResetSessionStore(mBrowsingContext, mBrowsingContext->GetSessionStoreEpoch()); } } } } } else if ((~aStateFlags & (STATE_TRANSFERRING | STATE_IS_DOCUMENT)) == 0) { // Page is loading mBusyFlags = (BusyFlags)(BUSY_FLAGS_BUSY | BUSY_FLAGS_PAGE_LOADING); } else if ((aStateFlags & STATE_STOP) && (aStateFlags & STATE_IS_NETWORK)) { // Page has finished loading mBusyFlags = BUSY_FLAGS_NONE; // Hide the progress cursor if the pref is set if (StaticPrefs::ui_use_activity_cursor()) { nsCOMPtr mainWidget; GetMainWidget(getter_AddRefs(mainWidget)); if (mainWidget) { mainWidget->SetCursor(nsIWidget::Cursor{eCursor_standard}); } } } if ((~aStateFlags & (STATE_IS_DOCUMENT | STATE_STOP)) == 0) { nsCOMPtr webProgress = do_QueryInterface(GetAsSupports(this)); // Is the document stop notification for this document? if (aProgress == webProgress.get()) { nsCOMPtr channel(do_QueryInterface(aRequest)); EndPageLoad(aProgress, channel, aStatus); } } // note that redirect state changes will go through here as well, but it // is better to handle those in OnRedirectStateChange where more // information is available. return NS_OK; } NS_IMETHODIMP nsDocShell::OnLocationChange(nsIWebProgress* aProgress, nsIRequest* aRequest, nsIURI* aURI, uint32_t aFlags) { // Since we've now changed Documents, notify the BrowsingContext that we've // changed. Ideally we'd just let the BrowsingContext do this when it // changes the current window global, but that happens before this and we // have a lot of tests that depend on the specific ordering of messages. bool isTopLevel = false; if (XRE_IsParentProcess() && !(aFlags & nsIWebProgressListener::LOCATION_CHANGE_SAME_DOCUMENT) && NS_SUCCEEDED(aProgress->GetIsTopLevel(&isTopLevel)) && isTopLevel) { GetBrowsingContext()->Canonical()->UpdateSecurityState(); } return NS_OK; } void nsDocShell::OnRedirectStateChange(nsIChannel* aOldChannel, nsIChannel* aNewChannel, uint32_t aRedirectFlags, uint32_t aStateFlags) { NS_ASSERTION(aStateFlags & STATE_REDIRECTING, "Calling OnRedirectStateChange when there is no redirect"); if (!(aStateFlags & STATE_IS_DOCUMENT)) { return; // not a toplevel document } nsCOMPtr oldURI, newURI; aOldChannel->GetURI(getter_AddRefs(oldURI)); aNewChannel->GetURI(getter_AddRefs(newURI)); if (!oldURI || !newURI) { return; } // DocumentChannel adds redirect chain to global history in the parent // process. The redirect chain can't be queried from the content process, so // there's no need to update global history here. RefPtr docChannel = do_QueryObject(aOldChannel); if (!docChannel) { // Below a URI visit is saved (see AddURIVisit method doc). // The visit chain looks something like: // ... // Site N - 1 // => Site N // (redirect to =>) Site N + 1 (we are here!) // Get N - 1 and transition type nsCOMPtr previousURI; uint32_t previousFlags = 0; ExtractLastVisit(aOldChannel, getter_AddRefs(previousURI), &previousFlags); if (aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL || net::ChannelIsPost(aOldChannel)) { // 1. Internal redirects are ignored because they are specific to the // channel implementation. // 2. POSTs are not saved by global history. // // Regardless, we need to propagate the previous visit to the new // channel. SaveLastVisit(aNewChannel, previousURI, previousFlags); } else { // Get the HTTP response code, if available. uint32_t responseStatus = 0; nsCOMPtr httpChannel = do_QueryInterface(aOldChannel); if (httpChannel) { Unused << httpChannel->GetResponseStatus(&responseStatus); } // Add visit N -1 => N AddURIVisit(oldURI, previousURI, previousFlags, responseStatus); // Since N + 1 could be the final destination, we will not save N => N + 1 // here. OnNewURI will do that, so we will cache it. SaveLastVisit(aNewChannel, oldURI, aRedirectFlags); } } if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) && mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) { mLoadType = LOAD_NORMAL_REPLACE; SetHistoryEntryAndUpdateBC(Some(nullptr), Nothing()); } } NS_IMETHODIMP nsDocShell::OnStatusChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, nsresult aStatus, const char16_t* aMessage) { MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); return NS_OK; } NS_IMETHODIMP nsDocShell::OnSecurityChange(nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aState) { MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); return NS_OK; } NS_IMETHODIMP nsDocShell::OnContentBlockingEvent(nsIWebProgress* aWebProgress, nsIRequest* aRequest, uint32_t aEvent) { MOZ_ASSERT_UNREACHABLE("notification excluded in AddProgressListener(...)"); return NS_OK; } already_AddRefed nsDocShell::KeywordToURI( const nsACString& aKeyword, bool aIsPrivateContext) { nsCOMPtr info; if (!XRE_IsContentProcess()) { nsCOMPtr uriFixup = components::URIFixup::Service(); if (uriFixup) { uriFixup->KeywordToURI(aKeyword, aIsPrivateContext, getter_AddRefs(info)); } } return info.forget(); } /* static */ already_AddRefed nsDocShell::MaybeFixBadCertDomainErrorURI( nsIChannel* aChannel, nsIURI* aUrl) { if (!aChannel) { return nullptr; } nsresult rv = NS_OK; nsAutoCString host; rv = aUrl->GetAsciiHost(host); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } // No point in going further if "www." is included in the hostname // already. That is the only hueristic we're applying in this function. if (StringBeginsWith(host, "www."_ns)) { return nullptr; } // Return if fixup enable pref is turned off. if (!mozilla::StaticPrefs::security_bad_cert_domain_error_url_fix_enabled()) { return nullptr; } // Return if scheme is not HTTPS. if (!SchemeIsHTTPS(aUrl)) { return nullptr; } nsCOMPtr info = aChannel->LoadInfo(); if (!info) { return nullptr; } // Skip doing the fixup if our channel was redirected, because we // shouldn't be guessing things about the post-redirect URI. if (!info->RedirectChain().IsEmpty()) { return nullptr; } int32_t port = 0; rv = aUrl->GetPort(&port); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } // Don't fix up hosts with ports. if (port != -1) { return nullptr; } // Don't fix up localhost url. if (host == "localhost") { return nullptr; } // Don't fix up hostnames with IP address. if (net_IsValidIPv4Addr(host) || net_IsValidIPv6Addr(host)) { return nullptr; } nsAutoCString userPass; rv = aUrl->GetUserPass(userPass); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } // Security - URLs with user / password info should NOT be modified. if (!userPass.IsEmpty()) { return nullptr; } nsCOMPtr tsi; rv = aChannel->GetSecurityInfo(getter_AddRefs(tsi)); if (NS_WARN_IF(NS_FAILED(rv))) { return nullptr; } if (NS_WARN_IF(!tsi)) { return nullptr; } nsCOMPtr cert; rv = tsi->GetServerCert(getter_AddRefs(cert)); if (NS_WARN_IF(NS_FAILED(rv) || !cert)) { return nullptr; } nsTArray certBytes; rv = cert->GetRawDER(certBytes); if (NS_FAILED(rv)) { return nullptr; } mozilla::pkix::Input serverCertInput; mozilla::pkix::Result rv1 = serverCertInput.Init(certBytes.Elements(), certBytes.Length()); if (rv1 != mozilla::pkix::Success) { return nullptr; } nsAutoCString newHost("www."_ns); newHost.Append(host); mozilla::pkix::Input newHostInput; rv1 = newHostInput.Init( BitwiseCast(newHost.BeginReading()), newHost.Length()); if (rv1 != mozilla::pkix::Success) { return nullptr; } // Check if adding a "www." prefix to the request's hostname will // cause the response's certificate to match. rv1 = mozilla::pkix::CheckCertHostname(serverCertInput, newHostInput); if (rv1 != mozilla::pkix::Success) { return nullptr; } nsCOMPtr newURI; Unused << NS_MutateURI(aUrl).SetHost(newHost).Finalize( getter_AddRefs(newURI)); return newURI.forget(); } /* static */ already_AddRefed nsDocShell::AttemptURIFixup( nsIChannel* aChannel, nsresult aStatus, const mozilla::Maybe& aOriginalURIString, uint32_t aLoadType, bool aIsTopFrame, bool aAllowKeywordFixup, bool aUsePrivateBrowsing, bool aNotifyKeywordSearchLoading, nsIInputStream** aNewPostData) { if (aStatus != NS_ERROR_UNKNOWN_HOST && aStatus != NS_ERROR_NET_RESET && aStatus != NS_ERROR_CONNECTION_REFUSED && aStatus != mozilla::psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERT_DOMAIN)) { return nullptr; } if (!(aLoadType == LOAD_NORMAL && aIsTopFrame) && !aAllowKeywordFixup) { return nullptr; } nsCOMPtr url; nsresult rv = aChannel->GetURI(getter_AddRefs(url)); if (NS_FAILED(rv)) { return nullptr; } // // Try and make an alternative URI from the old one // nsCOMPtr newURI; nsCOMPtr newPostData; nsAutoCString oldSpec; url->GetSpec(oldSpec); // // First try keyword fixup // nsAutoString keywordProviderName, keywordAsSent; if (aStatus == NS_ERROR_UNKNOWN_HOST && aAllowKeywordFixup) { // we should only perform a keyword search under the following // conditions: // (0) Pref keyword.enabled is true // (1) the url scheme is http (or https) // (2) the url does not have a protocol scheme // If we don't enforce such a policy, then we end up doing // keyword searchs on urls we don't intend like imap, file, // mailbox, etc. This could lead to a security problem where we // send data to the keyword server that we shouldn't be. // Someone needs to clean up keywords in general so we can // determine on a per url basis if we want keywords // enabled...this is just a bandaid... nsAutoCString scheme; Unused << url->GetScheme(scheme); if (Preferences::GetBool("keyword.enabled", false) && StringBeginsWith(scheme, "http"_ns)) { bool attemptFixup = false; nsAutoCString host; Unused << url->GetHost(host); if (host.FindChar('.') == kNotFound) { attemptFixup = true; } else { // For domains with dots, we check the public suffix validity. nsCOMPtr tldService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); if (tldService) { nsAutoCString suffix; attemptFixup = NS_SUCCEEDED(tldService->GetKnownPublicSuffix(url, suffix)) && suffix.IsEmpty(); } } if (attemptFixup) { nsCOMPtr info; // only send non-qualified hosts to the keyword server if (aOriginalURIString && !aOriginalURIString->IsEmpty()) { info = KeywordToURI(*aOriginalURIString, aUsePrivateBrowsing); } else { // // If this string was passed through nsStandardURL by // chance, then it may have been converted from UTF-8 to // ACE, which would result in a completely bogus keyword // query. Here we try to recover the original Unicode // value, but this is not 100% correct since the value may // have been normalized per the IDN normalization rules. // // Since we don't have access to the exact original string // that was entered by the user, this will just have to do. bool isACE; nsAutoCString utf8Host; nsCOMPtr idnSrv = do_GetService(NS_IDNSERVICE_CONTRACTID); if (idnSrv && NS_SUCCEEDED(idnSrv->IsACE(host, &isACE)) && isACE && NS_SUCCEEDED(idnSrv->ConvertACEtoUTF8(host, utf8Host))) { info = KeywordToURI(utf8Host, aUsePrivateBrowsing); } else { info = KeywordToURI(host, aUsePrivateBrowsing); } } if (info) { info->GetPreferredURI(getter_AddRefs(newURI)); if (newURI) { info->GetKeywordAsSent(keywordAsSent); info->GetKeywordProviderName(keywordProviderName); info->GetPostData(getter_AddRefs(newPostData)); } } } } } // // Now try change the address, e.g. turn http://foo into // http://www.foo.com, and if that doesn't work try https with // https://foo and https://www.foo.com. // if (aStatus == NS_ERROR_UNKNOWN_HOST || aStatus == NS_ERROR_NET_RESET) { // Skip fixup for anything except a normal document load // operation on the topframe. bool doCreateAlternate = aLoadType == LOAD_NORMAL && aIsTopFrame; if (doCreateAlternate) { nsCOMPtr loadInfo = aChannel->LoadInfo(); nsIPrincipal* principal = loadInfo->TriggeringPrincipal(); // Only do this if our channel was loaded directly by the user from the // URL bar or similar (system principal) and not redirected, because we // shouldn't be guessing things about links from other sites, or a // post-redirect URI. doCreateAlternate = principal && principal->IsSystemPrincipal() && loadInfo->RedirectChain().IsEmpty(); } // Test if keyword lookup produced a new URI or not if (doCreateAlternate && newURI) { bool sameURI = false; url->Equals(newURI, &sameURI); if (!sameURI) { // Keyword lookup made a new URI so no need to try // an alternate one. doCreateAlternate = false; } } if (doCreateAlternate) { newURI = nullptr; newPostData = nullptr; keywordProviderName.Truncate(); keywordAsSent.Truncate(); nsCOMPtr uriFixup = components::URIFixup::Service(); if (uriFixup) { nsCOMPtr fixupInfo; uriFixup->GetFixupURIInfo(oldSpec, nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI, getter_AddRefs(fixupInfo)); if (fixupInfo) { fixupInfo->GetPreferredURI(getter_AddRefs(newURI)); } } } } else if (aStatus == NS_ERROR_CONNECTION_REFUSED && Preferences::GetBool("browser.fixup.fallback-to-https", false)) { // Try HTTPS, since http didn't work if (SchemeIsHTTP(url)) { int32_t port = 0; url->GetPort(&port); // Fall back to HTTPS only if port is default if (port == -1) { newURI = nullptr; newPostData = nullptr; Unused << NS_MutateURI(url) .SetScheme("https"_ns) .Finalize(getter_AddRefs(newURI)); } } } // If we have a SSL_ERROR_BAD_CERT_DOMAIN error, try prefixing the domain name // with www. to see if we can avoid showing the cert error page. For example, // https://example.com -> https://www.example.com. if (aStatus == mozilla::psm::GetXPCOMFromNSSError(SSL_ERROR_BAD_CERT_DOMAIN)) { newPostData = nullptr; newURI = MaybeFixBadCertDomainErrorURI(aChannel, url); } // Did we make a new URI that is different to the old one? If so // load it. // if (newURI) { // Make sure the new URI is different from the old one, // otherwise there's little point trying to load it again. bool sameURI = false; url->Equals(newURI, &sameURI); if (!sameURI) { if (aNewPostData) { newPostData.forget(aNewPostData); } if (aNotifyKeywordSearchLoading) { // This notification is meant for Firefox Health Report so it // can increment counts from the search engine MaybeNotifyKeywordSearchLoading(keywordProviderName, keywordAsSent); } return newURI.forget(); } } return nullptr; } nsresult nsDocShell::FilterStatusForErrorPage( nsresult aStatus, nsIChannel* aChannel, uint32_t aLoadType, bool aIsTopFrame, bool aUseErrorPages, bool aIsInitialDocument, bool* aSkippedUnknownProtocolNavigation) { // Errors to be shown only on top-level frames if ((aStatus == NS_ERROR_UNKNOWN_HOST || aStatus == NS_ERROR_CONNECTION_REFUSED || aStatus == NS_ERROR_UNKNOWN_PROXY_HOST || aStatus == NS_ERROR_PROXY_CONNECTION_REFUSED || aStatus == NS_ERROR_PROXY_FORBIDDEN || aStatus == NS_ERROR_PROXY_NOT_IMPLEMENTED || aStatus == NS_ERROR_PROXY_AUTHENTICATION_FAILED || aStatus == NS_ERROR_PROXY_TOO_MANY_REQUESTS || aStatus == NS_ERROR_MALFORMED_URI || aStatus == NS_ERROR_BLOCKED_BY_POLICY || aStatus == NS_ERROR_DOM_COOP_FAILED || aStatus == NS_ERROR_DOM_COEP_FAILED) && (aIsTopFrame || aUseErrorPages)) { return aStatus; } if (aStatus == NS_ERROR_NET_TIMEOUT || aStatus == NS_ERROR_NET_TIMEOUT_EXTERNAL || aStatus == NS_ERROR_PROXY_GATEWAY_TIMEOUT || aStatus == NS_ERROR_REDIRECT_LOOP || aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE || aStatus == NS_ERROR_NET_INTERRUPT || aStatus == NS_ERROR_NET_RESET || aStatus == NS_ERROR_PROXY_BAD_GATEWAY || aStatus == NS_ERROR_OFFLINE || aStatus == NS_ERROR_MALWARE_URI || aStatus == NS_ERROR_PHISHING_URI || aStatus == NS_ERROR_UNWANTED_URI || aStatus == NS_ERROR_HARMFUL_URI || aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE || aStatus == NS_ERROR_INTERCEPTION_FAILED || aStatus == NS_ERROR_NET_INADEQUATE_SECURITY || aStatus == NS_ERROR_NET_HTTP2_SENT_GOAWAY || aStatus == NS_ERROR_NET_HTTP3_PROTOCOL_ERROR || aStatus == NS_ERROR_DOM_BAD_URI || aStatus == NS_ERROR_FILE_NOT_FOUND || aStatus == NS_ERROR_FILE_ACCESS_DENIED || aStatus == NS_ERROR_CORRUPTED_CONTENT || aStatus == NS_ERROR_INVALID_CONTENT_ENCODING || NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) { // Errors to be shown for any frame return aStatus; } if (aStatus == NS_ERROR_UNKNOWN_PROTOCOL) { // For unknown protocols we only display an error if the load is triggered // by the browser itself, or we're replacing the initial document (and // nothing else). Showing the error for page-triggered navigations causes // annoying behavior for users, see bug 1528305. // // We could, maybe, try to detect if this is in response to some user // interaction (like clicking a link, or something else) and maybe show // the error page in that case. But this allows for ctrl+clicking and such // to see the error page. nsCOMPtr info = aChannel->LoadInfo(); if (!info->TriggeringPrincipal()->IsSystemPrincipal() && StaticPrefs::dom_no_unknown_protocol_error_enabled() && !aIsInitialDocument) { if (aSkippedUnknownProtocolNavigation) { *aSkippedUnknownProtocolNavigation = true; } return NS_OK; } return aStatus; } if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) { // Non-caching channels will simply return NS_ERROR_OFFLINE. // Caching channels would have to look at their flags to work // out which error to return. Or we can fix up the error here. if (!(aLoadType & LOAD_CMD_HISTORY)) { return NS_ERROR_OFFLINE; } return aStatus; } return NS_OK; } nsresult nsDocShell::EndPageLoad(nsIWebProgress* aProgress, nsIChannel* aChannel, nsresult aStatus) { MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p EndPageLoad status: %" PRIx32 "\n", this, static_cast(aStatus))); if (!aChannel) { return NS_ERROR_NULL_POINTER; } // Make sure to discard the initial client if we never created the initial // about:blank document. Do this before possibly returning from the method // due to an error. mInitialClientSource.reset(); nsCOMPtr reporter = do_QueryInterface(aChannel); if (reporter) { nsCOMPtr loadGroup; aChannel->GetLoadGroup(getter_AddRefs(loadGroup)); if (loadGroup) { reporter->FlushConsoleReports(loadGroup); } else { reporter->FlushConsoleReports(GetDocument()); } } nsCOMPtr url; nsresult rv = aChannel->GetURI(getter_AddRefs(url)); if (NS_FAILED(rv)) { return rv; } nsCOMPtr timingChannel = do_QueryInterface(aChannel); if (timingChannel) { TimeStamp channelCreationTime; rv = timingChannel->GetChannelCreation(&channelCreationTime); if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) { Telemetry::AccumulateTimeDelta(Telemetry::TOTAL_CONTENT_PAGE_LOAD_TIME, channelCreationTime); } } // Timing is picked up by the window, we don't need it anymore mTiming = nullptr; // clean up reload state for meta charset if (eCharsetReloadRequested == mCharsetReloadState) { mCharsetReloadState = eCharsetReloadStopOrigional; } else { mCharsetReloadState = eCharsetReloadInit; } // Save a pointer to the currently-loading history entry. // nsDocShell::EndPageLoad will clear mLSHE, but we may need this history // entry further down in this method. nsCOMPtr loadingSHE = mLSHE; mozilla::Unused << loadingSHE; // XXX: Not sure if we need this anymore // // one of many safeguards that prevent death and destruction if // someone is so very very rude as to bring this window down // during this load handler. // nsCOMPtr kungFuDeathGrip(this); // Notify the ContentViewer that the Document has finished loading. This // will cause any OnLoad(...) and PopState(...) handlers to fire. if (!mEODForCurrentDocument && mContentViewer) { mIsExecutingOnLoadHandler = true; nsCOMPtr contentViewer = mContentViewer; contentViewer->LoadComplete(aStatus); mIsExecutingOnLoadHandler = false; mEODForCurrentDocument = true; } /* Check if the httpChannel has any cache-control related response headers, * like no-store, no-cache. If so, update SHEntry so that * when a user goes back/forward to this page, we appropriately do * form value restoration or load from server. */ nsCOMPtr httpChannel(do_QueryInterface(aChannel)); if (!httpChannel) { // HttpChannel could be hiding underneath a Multipart channel. GetHttpChannel(aChannel, getter_AddRefs(httpChannel)); } if (httpChannel) { // figure out if SH should be saving layout state. bool discardLayoutState = ShouldDiscardLayoutState(httpChannel); if (mLSHE && discardLayoutState && (mLoadType & LOAD_CMD_NORMAL) && (mLoadType != LOAD_BYPASS_HISTORY) && (mLoadType != LOAD_ERROR_PAGE)) { mLSHE->SetSaveLayoutStateFlag(false); } } // Clear mLSHE after calling the onLoadHandlers. This way, if the // onLoadHandler tries to load something different in // itself or one of its children, we can deal with it appropriately. if (mLSHE) { mLSHE->SetLoadType(LOAD_HISTORY); // Clear the mLSHE reference to indicate document loading is done one // way or another. SetHistoryEntryAndUpdateBC(Some(nullptr), Nothing()); } mActiveEntryIsLoadingFromSessionHistory = false; // if there's a refresh header in the channel, this method // will set it up for us. if (mBrowsingContext->IsActive() || !mDisableMetaRefreshWhenInactive) RefreshURIFromQueue(); // Test whether this is the top frame or a subframe bool isTopFrame = mBrowsingContext->IsTop(); bool hadErrorStatus = false; // If status code indicates an error it means that DocumentChannel already // tried to fixup the uri and failed. Throw an error dialog box here. if (NS_FAILED(aStatus)) { // If we got CONTENT_BLOCKED from EndPageLoad, then we need to fire // the error event to our embedder, since tests are relying on this. // The error event is usually fired by the caller of InternalLoad, but // this particular error can happen asynchronously. // Bug 1629201 is filed for having much clearer decision making around // which cases need error events. bool fireFrameErrorEvent = (aStatus == NS_ERROR_CONTENT_BLOCKED_SHOW_ALT || aStatus == NS_ERROR_CONTENT_BLOCKED); UnblockEmbedderLoadEventForFailure(fireFrameErrorEvent); bool isInitialDocument = !GetExtantDocument() || GetExtantDocument()->IsInitialDocument(); bool skippedUnknownProtocolNavigation = false; aStatus = FilterStatusForErrorPage(aStatus, aChannel, mLoadType, isTopFrame, mBrowsingContext->GetUseErrorPages(), isInitialDocument, &skippedUnknownProtocolNavigation); hadErrorStatus = true; if (NS_FAILED(aStatus)) { if (!mIsBeingDestroyed) { DisplayLoadError(aStatus, url, nullptr, aChannel); } } else if (skippedUnknownProtocolNavigation) { nsTArray params; if (NS_FAILED( NS_GetSanitizedURIStringFromURI(url, *params.AppendElement()))) { params.LastElement().AssignLiteral(u"(unknown uri)"); } nsContentUtils::ReportToConsole( nsIScriptError::warningFlag, "DOM"_ns, GetExtantDocument(), nsContentUtils::eDOM_PROPERTIES, "UnknownProtocolNavigationPrevented", params); } } else { // If we have a host nsCOMPtr loadInfo = aChannel->LoadInfo(); PredictorLearnRedirect(url, aChannel, loadInfo->GetOriginAttributes()); } if (hadErrorStatus) { // Don't send session store updates if the reason EndPageLoad was called is // because we are process switching. Sometimes the update takes too long and // incorrectly overrides session store data from the following load. return NS_OK; } if (StaticPrefs::browser_sessionstore_platform_collection_AtStartup()) { if (WindowContext* windowContext = mBrowsingContext->GetCurrentWindowContext()) { using Change = SessionStoreChangeListener::Change; // We've finished loading the page and now we want to collect all the // session store state that the page is initialized with. SessionStoreChangeListener::CollectSessionStoreData( windowContext, EnumSet(Change::Input, Change::Scroll, Change::SessionHistory, Change::WireFrame)); } } return NS_OK; } //***************************************************************************** // nsDocShell: Content Viewer Management //***************************************************************************** nsresult nsDocShell::EnsureContentViewer() { if (mContentViewer) { return NS_OK; } if (mIsBeingDestroyed) { return NS_ERROR_FAILURE; } nsCOMPtr cspToInheritForAboutBlank; nsCOMPtr baseURI; nsIPrincipal* principal = GetInheritedPrincipal(false); nsIPrincipal* partitionedPrincipal = GetInheritedPrincipal(false, true); nsCOMPtr parentItem; GetInProcessSameTypeParent(getter_AddRefs(parentItem)); if (parentItem) { if (nsCOMPtr domWin = GetWindow()) { nsCOMPtr parentElement = domWin->GetFrameElementInternal(); if (parentElement) { baseURI = parentElement->GetBaseURI(); cspToInheritForAboutBlank = parentElement->GetCsp(); } } } nsresult rv = CreateAboutBlankContentViewer( principal, partitionedPrincipal, cspToInheritForAboutBlank, baseURI, /* aIsInitialDocument */ true); NS_ENSURE_STATE(mContentViewer); if (NS_SUCCEEDED(rv)) { RefPtr doc(GetDocument()); MOZ_ASSERT(doc, "Should have doc if CreateAboutBlankContentViewer " "succeeded!"); MOZ_ASSERT(doc->IsInitialDocument(), "Document should be initial document"); // Documents created using EnsureContentViewer may be transient // placeholders created by framescripts before content has a // chance to load. In some cases, window.open(..., "noopener") // will create such a document and then synchronously tear it // down, firing a "pagehide" event. Doing so violates our // assertions about DocGroups. It's easier to silence the // assertion here than to avoid creating the extra document. doc->IgnoreDocGroupMismatches(); } return rv; } nsresult nsDocShell::CreateAboutBlankContentViewer( nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal, nsIContentSecurityPolicy* aCSP, nsIURI* aBaseURI, bool aIsInitialDocument, const Maybe& aCOEP, bool aTryToSaveOldPresentation, bool aCheckPermitUnload, WindowGlobalChild* aActor) { RefPtr blankDoc; nsCOMPtr viewer; nsresult rv = NS_ERROR_FAILURE; MOZ_ASSERT_IF(aActor, aActor->DocumentPrincipal() == aPrincipal); /* mCreatingDocument should never be true at this point. However, it's a theoretical possibility. We want to know about it and make it stop, and this sounds like a job for an assertion. */ NS_ASSERTION(!mCreatingDocument, "infinite(?) loop creating document averted"); if (mCreatingDocument) { return NS_ERROR_FAILURE; } if (!mBrowsingContext->AncestorsAreCurrent() || mBrowsingContext->IsInBFCache()) { mBrowsingContext->RemoveRootFromBFCacheSync(); return NS_ERROR_NOT_AVAILABLE; } // mContentViewer->PermitUnload may release |this| docshell. nsCOMPtr kungFuDeathGrip(this); AutoRestore creatingDocument(mCreatingDocument); mCreatingDocument = true; if (aPrincipal && !aPrincipal->IsSystemPrincipal() && mItemType != typeChrome) { MOZ_ASSERT(aPrincipal->OriginAttributesRef() == mBrowsingContext->OriginAttributesRef()); } // Make sure timing is created. But first record whether we had it // already, so we don't clobber the timing for an in-progress load. bool hadTiming = mTiming; bool toBeReset = MaybeInitTiming(); if (mContentViewer) { if (aCheckPermitUnload) { // We've got a content viewer already. Make sure the user // permits us to discard the current document and replace it // with about:blank. And also ensure we fire the unload events // in the current document. // Unload gets fired first for // document loaded from the session history. mTiming->NotifyBeforeUnload(); bool okToUnload; rv = mContentViewer->PermitUnload(&okToUnload); if (NS_SUCCEEDED(rv) && !okToUnload) { // The user chose not to unload the page, interrupt the load. MaybeResetInitTiming(toBeReset); return NS_ERROR_FAILURE; } if (mTiming) { mTiming->NotifyUnloadAccepted(mCurrentURI); } } mSavingOldViewer = aTryToSaveOldPresentation && CanSavePresentation(LOAD_NORMAL, nullptr, nullptr, /* aReportBFCacheComboTelemetry */ true); // Make sure to blow away our mLoadingURI just in case. No loads // from inside this pagehide. mLoadingURI = nullptr; // Stop any in-progress loading, so that we don't accidentally trigger any // PageShow notifications from Embed() interrupting our loading below. Stop(); // Notify the current document that it is about to be unloaded!! // // It is important to fire the unload() notification *before* any state // is changed within the DocShell - otherwise, javascript will get the // wrong information :-( // (void)FirePageHideNotification(!mSavingOldViewer); // pagehide notification might destroy this docshell. if (mIsBeingDestroyed) { return NS_ERROR_DOCSHELL_DYING; } } // Now make sure we don't think we're in the middle of firing unload after // this point. This will make us fire unload when the about:blank document // unloads... but that's ok, more or less. Would be nice if it fired load // too, of course. mFiredUnloadEvent = false; nsCOMPtr docFactory = nsContentUtils::FindInternalContentViewer("text/html"_ns); if (docFactory) { nsCOMPtr principal, partitionedPrincipal; const uint32_t sandboxFlags = mBrowsingContext->GetHasLoadedNonInitialDocument() ? mBrowsingContext->GetSandboxFlags() : mBrowsingContext->GetInitialSandboxFlags(); // If we're sandboxed, then create a new null principal. We skip // this if we're being created from WindowGlobalChild, since in // that case we already have a null principal if required. // We can't compare againt the BrowsingContext sandbox flag, since // the value was taken when the load initiated and may have since // changed. if ((sandboxFlags & SANDBOXED_ORIGIN) && !aActor) { if (aPrincipal) { principal = NullPrincipal::CreateWithInheritedAttributes(aPrincipal); } else { principal = NullPrincipal::Create(GetOriginAttributes()); } partitionedPrincipal = principal; } else { principal = aPrincipal; partitionedPrincipal = aPartitionedPrincipal; } // We cannot get the foreign partitioned prinicpal for the initial // about:blank page. So, we change to check if we need to use the // partitioned principal for the service worker here. MaybeCreateInitialClientSource( StoragePrincipalHelper::ShouldUsePartitionPrincipalForServiceWorker( this) ? partitionedPrincipal : principal); // generate (about:blank) document to load blankDoc = nsContentDLF::CreateBlankDocument(mLoadGroup, principal, partitionedPrincipal, this); if (blankDoc) { // Hack: manually set the CSP for the new document // Please create an actual copy of the CSP (do not share the same // reference) otherwise appending a new policy within the new // document will be incorrectly propagated to the opening doc. if (aCSP) { RefPtr cspToInherit = new nsCSPContext(); cspToInherit->InitFromOther(static_cast(aCSP)); blankDoc->SetCsp(cspToInherit); } blankDoc->SetIsInitialDocument(aIsInitialDocument); blankDoc->SetEmbedderPolicy(aCOEP); // Hack: set the base URI manually, since this document never // got Reset() with a channel. blankDoc->SetBaseURI(aBaseURI); // Copy our sandbox flags to the document. These are immutable // after being set here. blankDoc->SetSandboxFlags(sandboxFlags); blankDoc->InitFeaturePolicy(); // create a content viewer for us and the new document docFactory->CreateInstanceForDocument( NS_ISUPPORTS_CAST(nsIDocShell*, this), blankDoc, "view", getter_AddRefs(viewer)); // hook 'em up if (viewer) { viewer->SetContainer(this); rv = Embed(viewer, aActor, true, false, nullptr, mCurrentURI); NS_ENSURE_SUCCESS(rv, rv); SetCurrentURI(blankDoc->GetDocumentURI(), nullptr, /* aFireLocationChange */ true, /* aIsInitialAboutBlank */ true, /* aLocationFlags */ 0); rv = mIsBeingDestroyed ? NS_ERROR_NOT_AVAILABLE : NS_OK; } } } // The transient about:blank viewer doesn't have a session history entry. SetHistoryEntryAndUpdateBC(Nothing(), Some(nullptr)); // Clear out our mTiming like we would in EndPageLoad, if we didn't // have one before entering this function. if (!hadTiming) { mTiming = nullptr; mBlankTiming = true; } return rv; } NS_IMETHODIMP nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal, nsIPrincipal* aPartitionedPrincipal, nsIContentSecurityPolicy* aCSP) { return CreateAboutBlankContentViewer(aPrincipal, aPartitionedPrincipal, aCSP, nullptr, /* aIsInitialDocument */ false); } nsresult nsDocShell::CreateContentViewerForActor( WindowGlobalChild* aWindowActor) { MOZ_ASSERT(aWindowActor); // FIXME: WindowGlobalChild should provide the PartitionedPrincipal. // FIXME: We may want to support non-initial documents here. nsresult rv = CreateAboutBlankContentViewer( aWindowActor->DocumentPrincipal(), aWindowActor->DocumentPrincipal(), /* aCsp */ nullptr, /* aBaseURI */ nullptr, /* aIsInitialDocument */ true, /* aCOEP */ Nothing(), /* aTryToSaveOldPresentation */ true, /* aCheckPermitUnload */ true, aWindowActor); #ifdef DEBUG if (NS_SUCCEEDED(rv)) { RefPtr doc(GetDocument()); MOZ_ASSERT( doc, "Should have a document if CreateAboutBlankContentViewer succeeded"); MOZ_ASSERT(doc->GetOwnerGlobal() == aWindowActor->GetWindowGlobal(), "New document should be in the same global as our actor"); MOZ_ASSERT(doc->IsInitialDocument(), "New document should be an initial document"); } #endif return rv; } bool nsDocShell::CanSavePresentation(uint32_t aLoadType, nsIRequest* aNewRequest, Document* aNewDocument, bool aReportBFCacheComboTelemetry) { if (!mOSHE) { return false; // no entry to save into } MOZ_ASSERT(!mozilla::SessionHistoryInParent(), "mOSHE cannot be non-null with SHIP"); nsCOMPtr viewer = mOSHE->GetContentViewer(); if (viewer) { NS_WARNING("mOSHE already has a content viewer!"); return false; } // Only save presentation for "normal" loads and link loads. Anything else // probably wants to refetch the page, so caching the old presentation // would be incorrect. if (aLoadType != LOAD_NORMAL && aLoadType != LOAD_HISTORY && aLoadType != LOAD_LINK && aLoadType != LOAD_STOP_CONTENT && aLoadType != LOAD_STOP_CONTENT_AND_REPLACE && aLoadType != LOAD_ERROR_PAGE) { return false; } // If the session history entry has the saveLayoutState flag set to false, // then we should not cache the presentation. if (!mOSHE->GetSaveLayoutStateFlag()) { return false; } // If the document is not done loading, don't cache it. if (!mScriptGlobal || mScriptGlobal->IsLoading()) { MOZ_LOG(gPageCacheLog, mozilla::LogLevel::Verbose, ("Blocked due to document still loading")); return false; } if (mScriptGlobal->WouldReuseInnerWindow(aNewDocument)) { return false; } // Avoid doing the work of saving the presentation state in the case where // the content viewer cache is disabled. if (nsSHistory::GetMaxTotalViewers() == 0) { return false; } // Don't cache the content viewer if we're in a subframe. if (mBrowsingContext->GetParent()) { return false; // this is a subframe load } // If the document does not want its presentation cached, then don't. RefPtr doc = mScriptGlobal->GetExtantDoc(); uint32_t bfCacheCombo = 0; bool canSavePresentation = doc->CanSavePresentation(aNewRequest, bfCacheCombo, true); MOZ_ASSERT_IF(canSavePresentation, bfCacheCombo == 0); if (canSavePresentation && doc->IsTopLevelContentDocument()) { auto* browsingContextGroup = mBrowsingContext->Group(); nsTArray>& topLevelContext = browsingContextGroup->Toplevels(); for (const auto& browsingContext : topLevelContext) { if (browsingContext != mBrowsingContext) { if (StaticPrefs::docshell_shistory_bfcache_require_no_opener()) { canSavePresentation = false; } bfCacheCombo |= BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG; break; } } } if (aReportBFCacheComboTelemetry) { ReportBFCacheComboTelemetry(bfCacheCombo); } return doc && canSavePresentation; } /* static */ void nsDocShell::ReportBFCacheComboTelemetry(uint32_t aCombo) { // There are 11 possible reasons to make a request fails to use BFCache // (see BFCacheStatus in dom/base/Document.h), and we'd like to record // the common combinations for reasons which make requests fail to use // BFCache. These combinations are generated based on some local browsings, // we need to adjust them when necessary. enum BFCacheStatusCombo : uint32_t { BFCACHE_SUCCESS, NOT_ONLY_TOPLEVEL = mozilla::dom::BFCacheStatus::NOT_ONLY_TOPLEVEL_IN_BCG, // If both unload and beforeunload listeners are presented, it'll be // recorded as unload UNLOAD = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER, UNLOAD_REQUEST = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | mozilla::dom::BFCacheStatus::REQUEST, REQUEST = mozilla::dom::BFCacheStatus::REQUEST, UNLOAD_REQUEST_PEER = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | mozilla::dom::BFCacheStatus::REQUEST | mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION, UNLOAD_REQUEST_PEER_MSE = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | mozilla::dom::BFCacheStatus::REQUEST | mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION | mozilla::dom::BFCacheStatus::CONTAINS_MSE_CONTENT, UNLOAD_REQUEST_MSE = mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | mozilla::dom::BFCacheStatus::REQUEST | mozilla::dom::BFCacheStatus::CONTAINS_MSE_CONTENT, SUSPENDED_UNLOAD_REQUEST_PEER = mozilla::dom::BFCacheStatus::SUSPENDED | mozilla::dom::BFCacheStatus::UNLOAD_LISTENER | mozilla::dom::BFCacheStatus::REQUEST | mozilla::dom::BFCacheStatus::ACTIVE_PEER_CONNECTION, REMOTE_SUBFRAMES = mozilla::dom::BFCacheStatus::CONTAINS_REMOTE_SUBFRAMES, BEFOREUNLOAD = mozilla::dom::BFCacheStatus::BEFOREUNLOAD_LISTENER, }; // Beforeunload is recorded as a blocker only if it is the only one to block // bfcache. if (aCombo != mozilla::dom::BFCacheStatus::BEFOREUNLOAD_LISTENER) { aCombo &= ~mozilla::dom::BFCacheStatus::BEFOREUNLOAD_LISTENER; } switch (aCombo) { case BFCACHE_SUCCESS: Telemetry::AccumulateCategorical( Telemetry::LABELS_BFCACHE_COMBO::BFCache_Success); break; case NOT_ONLY_TOPLEVEL: if (StaticPrefs::docshell_shistory_bfcache_require_no_opener()) { Telemetry::AccumulateCategorical( Telemetry::LABELS_BFCACHE_COMBO::Other); break; } Telemetry::AccumulateCategorical( Telemetry::LABELS_BFCACHE_COMBO::BFCache_Success); Telemetry::AccumulateCategorical( Telemetry::LABELS_BFCACHE_COMBO::Success_Not_Toplevel); break; case UNLOAD: Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Unload); break; case BEFOREUNLOAD: Telemetry::AccumulateCategorical( Telemetry::LABELS_BFCACHE_COMBO::Beforeunload); break; case UNLOAD_REQUEST: Telemetry::AccumulateCategorical( Telemetry::LABELS_BFCACHE_COMBO::Unload_Req); break; case REQUEST: Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Req); break; case UNLOAD_REQUEST_PEER: Telemetry::AccumulateCategorical( Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_Peer); break; case UNLOAD_REQUEST_PEER_MSE: Telemetry::AccumulateCategorical( Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_Peer_MSE); break; case UNLOAD_REQUEST_MSE: Telemetry::AccumulateCategorical( Telemetry::LABELS_BFCACHE_COMBO::Unload_Req_MSE); break; case SUSPENDED_UNLOAD_REQUEST_PEER: Telemetry::AccumulateCategorical( Telemetry::LABELS_BFCACHE_COMBO::SPD_Unload_Req_Peer); break; case REMOTE_SUBFRAMES: Telemetry::AccumulateCategorical( Telemetry::LABELS_BFCACHE_COMBO::Remote_Subframes); break; default: Telemetry::AccumulateCategorical(Telemetry::LABELS_BFCACHE_COMBO::Other); break; } }; void nsDocShell::ReattachEditorToWindow(nsISHEntry* aSHEntry) { MOZ_ASSERT(!mozilla::SessionHistoryInParent()); MOZ_ASSERT(!mIsBeingDestroyed); NS_ASSERTION(!mEditorData, "Why reattach an editor when we already have one?"); NS_ASSERTION(aSHEntry && aSHEntry->HasDetachedEditor(), "Reattaching when there's not a detached editor."); if (mEditorData || !aSHEntry) { return; } mEditorData = WrapUnique(aSHEntry->ForgetEditorData()); if (mEditorData) { #ifdef DEBUG nsresult rv = #endif mEditorData->ReattachToWindow(this); NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to reattach editing session"); } } void nsDocShell::DetachEditorFromWindow() { if (!mEditorData || mEditorData->WaitingForLoad()) { // If there's nothing to detach, or if the editor data is actually set // up for the _new_ page that's coming in, don't detach. return; } NS_ASSERTION(!mOSHE || !mOSHE->HasDetachedEditor(), "Detaching editor when it's already detached."); nsresult res = mEditorData->DetachFromWindow(); NS_ASSERTION(NS_SUCCEEDED(res), "Failed to detach editor"); if (NS_SUCCEEDED(res)) { // Make mOSHE hold the owning ref to the editor data. if (mOSHE) { MOZ_ASSERT(!mIsBeingDestroyed || !mOSHE->HasDetachedEditor(), "We should not set the editor data again once after we " "detached the editor data during destroying this docshell"); mOSHE->SetEditorData(mEditorData.release()); } else { mEditorData = nullptr; } } #ifdef DEBUG { bool isEditable; GetEditable(&isEditable); NS_ASSERTION(!isEditable, "Window is still editable after detaching editor."); } #endif // DEBUG } nsresult nsDocShell::CaptureState() { MOZ_ASSERT(!mozilla::SessionHistoryInParent()); if (!mOSHE || mOSHE == mLSHE) { // No entry to save into, or we're replacing the existing entry. return NS_ERROR_FAILURE; } if (!mScriptGlobal) { return NS_ERROR_FAILURE; } nsCOMPtr windowState = mScriptGlobal->SaveWindowState(); NS_ENSURE_TRUE(windowState, NS_ERROR_FAILURE); if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) { nsAutoCString spec; nsCOMPtr uri = mOSHE->GetURI(); if (uri) { uri->GetSpec(spec); } MOZ_LOG(gPageCacheLog, LogLevel::Debug, ("Saving presentation into session history, URI: %s", spec.get())); } mOSHE->SetWindowState(windowState); // Suspend refresh URIs and save off the timer queue mOSHE->SetRefreshURIList(mSavedRefreshURIList); // Capture the current content viewer bounds. if (mContentViewer) { nsIntRect bounds; mContentViewer->GetBounds(bounds); mOSHE->SetViewerBounds(bounds); } // Capture the docshell hierarchy. mOSHE->ClearChildShells(); uint32_t childCount = mChildList.Length(); for (uint32_t i = 0; i < childCount; ++i) { nsCOMPtr childShell = do_QueryInterface(ChildAt(i)); NS_ASSERTION(childShell, "null child shell"); mOSHE->AddChildShell(childShell); } return NS_OK; } NS_IMETHODIMP nsDocShell::RestorePresentationEvent::Run() { MOZ_ASSERT(!mozilla::SessionHistoryInParent()); if (mDocShell && NS_FAILED(mDocShell->RestoreFromHistory())) { NS_WARNING("RestoreFromHistory failed"); } return NS_OK; } NS_IMETHODIMP nsDocShell::BeginRestore(nsIContentViewer* aContentViewer, bool aTop) { MOZ_ASSERT(!mozilla::SessionHistoryInParent()); nsresult rv; if (!aContentViewer) { rv = EnsureContentViewer(); NS_ENSURE_SUCCESS(rv, rv); aContentViewer = mContentViewer; } // Dispatch events for restoring the presentation. We try to simulate // the progress notifications loading the document would cause, so we add // the document's channel to the loadgroup to initiate stateChange // notifications. RefPtr doc = aContentViewer->GetDocument(); if (doc) { nsIChannel* channel = doc->GetChannel(); if (channel) { mEODForCurrentDocument = false; mIsRestoringDocument = true; mLoadGroup->AddRequest(channel, nullptr); mIsRestoringDocument = false; } } if (!aTop) { // This point corresponds to us having gotten OnStartRequest or // STATE_START, so do the same thing that CreateContentViewer does at // this point to ensure that unload/pagehide events for this document // will fire when it's unloaded again. mFiredUnloadEvent = false; // For non-top frames, there is no notion of making sure that the // previous document is in the domwindow when STATE_START notifications // happen. We can just call BeginRestore for all of the child shells // now. rv = BeginRestoreChildren(); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } nsresult nsDocShell::BeginRestoreChildren() { MOZ_ASSERT(!mozilla::SessionHistoryInParent()); for (auto* childDocLoader : mChildList.ForwardRange()) { nsCOMPtr child = do_QueryObject(childDocLoader); if (child) { nsresult rv = child->BeginRestore(nullptr, false); NS_ENSURE_SUCCESS(rv, rv); } } return NS_OK; } NS_IMETHODIMP nsDocShell::FinishRestore() { MOZ_ASSERT(!mozilla::SessionHistoryInParent()); // First we call finishRestore() on our children. In the simulated load, // all of the child frames finish loading before the main document. for (auto* childDocLoader : mChildList.ForwardRange()) { nsCOMPtr child = do_QueryObject(childDocLoader); if (child) { child->FinishRestore(); } } if (mOSHE && mOSHE->HasDetachedEditor()) { ReattachEditorToWindow(mOSHE); } RefPtr doc = GetDocument(); if (doc) { // Finally, we remove the request from the loadgroup. This will // cause onStateChange(STATE_STOP) to fire, which will fire the // pageshow event to the chrome. nsIChannel* channel = doc->GetChannel(); if (channel) { mIsRestoringDocument = true; mLoadGroup->RemoveRequest(channel, nullptr, NS_OK); mIsRestoringDocument = false; } } return NS_OK; } NS_IMETHODIMP nsDocShell::GetRestoringDocument(bool* aRestoring) { *aRestoring = mIsRestoringDocument; return NS_OK; } nsresult nsDocShell::RestorePresentation(nsISHEntry* aSHEntry, bool* aRestoring) { MOZ_ASSERT(!mozilla::SessionHistoryInParent()); MOZ_ASSERT(!mIsBeingDestroyed); NS_ASSERTION(mLoadType & LOAD_CMD_HISTORY, "RestorePresentation should only be called for history loads"); nsCOMPtr viewer = aSHEntry->GetContentViewer(); nsAutoCString spec; if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) { nsCOMPtr uri = aSHEntry->GetURI(); if (uri) { uri->GetSpec(spec); } } *aRestoring = false; if (!viewer) { MOZ_LOG(gPageCacheLog, LogLevel::Debug, ("no saved presentation for uri: %s", spec.get())); return NS_OK; } // We need to make sure the content viewer's container is this docshell. // In subframe navigation, it's possible for the docshell that the // content viewer was originally loaded into to be replaced with a // different one. We don't currently support restoring the presentation // in that case. nsCOMPtr container; viewer->GetContainer(getter_AddRefs(container)); if (!::SameCOMIdentity(container, GetAsSupports(this))) { MOZ_LOG(gPageCacheLog, LogLevel::Debug, ("No valid container, clearing presentation")); aSHEntry->SetContentViewer(nullptr); return NS_ERROR_FAILURE; } NS_ASSERTION(mContentViewer != viewer, "Restoring existing presentation"); MOZ_LOG(gPageCacheLog, LogLevel::Debug, ("restoring presentation from session history: %s", spec.get())); SetHistoryEntryAndUpdateBC(Some(aSHEntry), Nothing()); // Post an event that will remove the request after we've returned // to the event loop. This mimics the way it is called by nsIChannel // implementations. // Revoke any pending restore (just in case). NS_ASSERTION(!mRestorePresentationEvent.IsPending(), "should only have one RestorePresentationEvent"); mRestorePresentationEvent.Revoke(); RefPtr evt = new RestorePresentationEvent(this); nsresult rv = Dispatch(TaskCategory::Other, do_AddRef(evt)); if (NS_SUCCEEDED(rv)) { mRestorePresentationEvent = evt.get(); // The rest of the restore processing will happen on our event // callback. *aRestoring = true; } return rv; } namespace { class MOZ_STACK_CLASS PresentationEventForgetter { public: explicit PresentationEventForgetter( nsRevocableEventPtr& aRestorePresentationEvent) : mRestorePresentationEvent(aRestorePresentationEvent), mEvent(aRestorePresentationEvent.get()) {} ~PresentationEventForgetter() { Forget(); } void Forget() { if (mRestorePresentationEvent.get() == mEvent) { mRestorePresentationEvent.Forget(); mEvent = nullptr; } } private: nsRevocableEventPtr& mRestorePresentationEvent; RefPtr mEvent; }; } // namespace bool nsDocShell::SandboxFlagsImplyCookies(const uint32_t& aSandboxFlags) { return (aSandboxFlags & (SANDBOXED_ORIGIN | SANDBOXED_SCRIPTS)) == 0; } nsresult nsDocShell::RestoreFromHistory() { MOZ_ASSERT(!mozilla::SessionHistoryInParent()); MOZ_ASSERT(mRestorePresentationEvent.IsPending()); PresentationEventForgetter forgetter(mRestorePresentationEvent); // This section of code follows the same ordering as CreateContentViewer. if (!mLSHE) { return NS_ERROR_FAILURE; } nsCOMPtr viewer = mLSHE->GetContentViewer(); if (!viewer) { return NS_ERROR_FAILURE; } if (mSavingOldViewer) { // We determined that it was safe to cache the document presentation // at the time we initiated the new load. We need to check whether // it's still safe to do so, since there may have been DOM mutations // or new requests initiated. RefPtr doc = viewer->GetDocument(); nsIRequest* request = nullptr; if (doc) { request = doc->GetChannel(); } mSavingOldViewer = CanSavePresentation( mLoadType, request, doc, /* aReportBFCacheComboTelemetry */ false); } // Protect against mLSHE going away via a load triggered from // pagehide or unload. nsCOMPtr origLSHE = mLSHE; // Make sure to blow away our mLoadingURI just in case. No loads // from inside this pagehide. mLoadingURI = nullptr; // Notify the old content viewer that it's being hidden. FirePageHideNotification(!mSavingOldViewer); // pagehide notification might destroy this docshell. if (mIsBeingDestroyed) { return NS_ERROR_DOCSHELL_DYING; } // If mLSHE was changed as a result of the pagehide event, then // something else was loaded. Don't finish restoring. if (mLSHE != origLSHE) { return NS_OK; } // Add the request to our load group. We do this before swapping out // the content viewers so that consumers of STATE_START can access // the old document. We only deal with the toplevel load at this time -- // to be consistent with normal document loading, subframes cannot start // loading until after data arrives, which is after STATE_START completes. RefPtr currentPresentationRestoration = mRestorePresentationEvent.get(); Stop(); // Make sure we're still restoring the same presentation. // If we aren't, docshell is in process doing another load already. NS_ENSURE_STATE(currentPresentationRestoration == mRestorePresentationEvent.get()); BeginRestore(viewer, true); NS_ENSURE_STATE(currentPresentationRestoration == mRestorePresentationEvent.get()); forgetter.Forget(); // Set mFiredUnloadEvent = false so that the unload handler for the // *new* document will fire. mFiredUnloadEvent = false; mURIResultedInDocument = true; RefPtr rootSH = GetRootSessionHistory(); if (rootSH) { mPreviousEntryIndex = rootSH->Index(); rootSH->LegacySHistory()->UpdateIndex(); mLoadedEntryIndex = rootSH->Index(); MOZ_LOG(gPageCacheLog, LogLevel::Verbose, ("Previous index: %d, Loaded index: %d", mPreviousEntryIndex, mLoadedEntryIndex)); } // Rather than call Embed(), we will retrieve the viewer from the session // history entry and swap it in. // XXX can we refactor this so that we can just call Embed()? PersistLayoutHistoryState(); nsresult rv; if (mContentViewer) { if (mSavingOldViewer && NS_FAILED(CaptureState())) { if (mOSHE) { mOSHE->SyncPresentationState(); } mSavingOldViewer = false; } } mSavedRefreshURIList = nullptr; // In cases where we use a transient about:blank viewer between loads, // we never show the transient viewer, so _its_ previous viewer is never // unhooked from the view hierarchy. Destroy any such previous viewer now, // before we grab the root view sibling, so that we don't grab a view // that's about to go away. if (mContentViewer) { // Make sure to hold a strong ref to previousViewer here while we // drop the reference to it from mContentViewer. nsCOMPtr previousViewer = mContentViewer->GetPreviousViewer(); if (previousViewer) { mContentViewer->SetPreviousViewer(nullptr); previousViewer->Destroy(); } } // Save off the root view's parent and sibling so that we can insert the // new content viewer's root view at the same position. Also save the // bounds of the root view's widget. nsView* rootViewSibling = nullptr; nsView* rootViewParent = nullptr; nsIntRect newBounds(0, 0, 0, 0); PresShell* oldPresShell = GetPresShell(); if (oldPresShell) { nsViewManager* vm = oldPresShell->GetViewManager(); if (vm) { nsView* oldRootView = vm->GetRootView(); if (oldRootView) { rootViewSibling = oldRootView->GetNextSibling(); rootViewParent = oldRootView->GetParent(); mContentViewer->GetBounds(newBounds); } } } nsCOMPtr container; RefPtr sibling; if (rootViewParent && rootViewParent->GetParent()) { nsIFrame* frame = rootViewParent->GetParent()->GetFrame(); container = frame ? frame->GetContent() : nullptr; } if (rootViewSibling) { nsIFrame* frame = rootViewSibling->GetFrame(); sibling = frame ? frame->PresShell()->GetDocument() : nullptr; } // Transfer ownership to mContentViewer. By ensuring that either the // docshell or the session history, but not both, have references to the // content viewer, we prevent the viewer from being torn down after // Destroy() is called. if (mContentViewer) { mContentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr); viewer->SetPreviousViewer(mContentViewer); } if (mOSHE && (!mContentViewer || !mSavingOldViewer)) { // We don't plan to save a viewer in mOSHE; tell it to drop // any other state it's holding. mOSHE->SyncPresentationState(); } // Order the mContentViewer setup just like Embed does. mContentViewer = nullptr; // Now that we're about to switch documents, forget all of our children. // Note that we cached them as needed up in CaptureState above. DestroyChildren(); mContentViewer.swap(viewer); // Grab all of the related presentation from the SHEntry now. // Clearing the viewer from the SHEntry will clear all of this state. nsCOMPtr windowState = mLSHE->GetWindowState(); mLSHE->SetWindowState(nullptr); bool sticky = mLSHE->GetSticky(); RefPtr document = mContentViewer->GetDocument(); nsCOMArray childShells; int32_t i = 0; nsCOMPtr child; while (NS_SUCCEEDED(mLSHE->ChildShellAt(i++, getter_AddRefs(child))) && child) { childShells.AppendObject(child); } // get the previous content viewer size nsIntRect oldBounds(0, 0, 0, 0); mLSHE->GetViewerBounds(oldBounds); // Restore the refresh URI list. The refresh timers will be restarted // when EndPageLoad() is called. nsCOMPtr refreshURIList = mLSHE->GetRefreshURIList(); // Reattach to the window object. mIsRestoringDocument = true; // for MediaDocument::BecomeInteractive rv = mContentViewer->Open(windowState, mLSHE); mIsRestoringDocument = false; // Hack to keep nsDocShellEditorData alive across the // SetContentViewer(nullptr) call below. UniquePtr data(mLSHE->ForgetEditorData()); // Now remove it from the cached presentation. mLSHE->SetContentViewer(nullptr); mEODForCurrentDocument = false; mLSHE->SetEditorData(data.release()); #ifdef DEBUG { nsCOMPtr refreshURIs = mLSHE->GetRefreshURIList(); nsCOMPtr childShell; mLSHE->ChildShellAt(0, getter_AddRefs(childShell)); NS_ASSERTION(!refreshURIs && !childShell, "SHEntry should have cleared presentation state"); } #endif // Restore the sticky state of the viewer. The viewer has set this state // on the history entry in Destroy() just before marking itself non-sticky, // to avoid teardown of the presentation. mContentViewer->SetSticky(sticky); NS_ENSURE_SUCCESS(rv, rv); // mLSHE is now our currently-loaded document. SetHistoryEntryAndUpdateBC(Nothing(), Some(mLSHE)); // We aren't going to restore any items from the LayoutHistoryState, // but we don't want them to stay around in case the page is reloaded. SetLayoutHistoryState(nullptr); // This is the end of our Embed() replacement mSavingOldViewer = false; mEODForCurrentDocument = false; if (document) { RefPtr parent = GetInProcessParentDocshell(); if (parent) { RefPtr d = parent->GetDocument(); if (d) { if (d->EventHandlingSuppressed()) { document->SuppressEventHandling(d->EventHandlingSuppressed()); } } } // Use the uri from the mLSHE we had when we entered this function // (which need not match the document's URI if anchors are involved), // since that's the history entry we're loading. Note that if we use // origLSHE we don't have to worry about whether the entry in question // is still mLSHE or whether it's now mOSHE. nsCOMPtr uri = origLSHE->GetURI(); SetCurrentURI(uri, document->GetChannel(), /* aFireLocationChange */ true, /* aIsInitialAboutBlank */ false, /* aLocationFlags */ 0); } // This is the end of our CreateContentViewer() replacement. // Now we simulate a load. First, we restore the state of the javascript // window object. nsCOMPtr privWin = GetWindow(); NS_ASSERTION(privWin, "could not get nsPIDOMWindow interface"); // Now, dispatch a title change event which would happen as the // is parsed. document->NotifyPossibleTitleChange(false); // Now we simulate appending child docshells for subframes. for (i = 0; i < childShells.Count(); ++i) { nsIDocShellTreeItem* childItem = childShells.ObjectAt(i); nsCOMPtr childShell = do_QueryInterface(childItem); // Make sure to not clobber the state of the child. Since AddChild // always clobbers it, save it off first. bool allowRedirects; childShell->GetAllowMetaRedirects(&allowRedirects); bool allowSubframes; childShell->GetAllowSubframes(&allowSubframes); bool allowImages; childShell->GetAllowImages(&allowImages); bool allowMedia = childShell->GetAllowMedia(); bool allowDNSPrefetch; childShell->GetAllowDNSPrefetch(&allowDNSPrefetch); bool allowContentRetargeting = childShell->GetAllowContentRetargeting(); bool allowContentRetargetingOnChildren = childShell->GetAllowContentRetargetingOnChildren(); // this.AddChild(child) calls child.SetDocLoaderParent(this), meaning that // the child inherits our state. Among other things, this means that the // child inherits our mPrivateBrowsingId, which is what we want. AddChild(childItem); childShell->SetAllowMetaRedirects(allowRedirects); childShell->SetAllowSubframes(allowSubframes); childShell->SetAllowImages(allowImages); childShell->SetAllowMedia(allowMedia); childShell->SetAllowDNSPrefetch(allowDNSPrefetch); childShell->SetAllowContentRetargeting(allowContentRetargeting); childShell->SetAllowContentRetargetingOnChildren( allowContentRetargetingOnChildren); rv = childShell->BeginRestore(nullptr, false); NS_ENSURE_SUCCESS(rv, rv); } // Make sure to restore the window state after adding the child shells back // to the tree. This is necessary for Thaw() and Resume() to propagate // properly. rv = privWin->RestoreWindowState(windowState); NS_ENSURE_SUCCESS(rv, rv); RefPtr presShell = GetPresShell(); // We may be displayed on a different monitor (or in a different // HiDPI mode) than when we got into the history list. So we need // to check if this has happened. See bug 838239. // Because the prescontext normally handles resolution changes via // a runnable (see nsPresContext::UIResolutionChanged), its device // context won't be -immediately- updated as a result of calling // presShell->BackingScaleFactorChanged(). // But we depend on that device context when adjusting the view size // via mContentViewer->SetBounds(newBounds) below. So we need to // explicitly tell it to check for changed resolution here. if (presShell) { RefPtr pc = presShell->GetPresContext(); if (pc->DeviceContext()->CheckDPIChange()) { presShell->BackingScaleFactorChanged(); } // Recompute zoom and text-zoom and such. pc->RecomputeBrowsingContextDependentData(); } nsViewManager* newVM = presShell ? presShell->GetViewManager() : nullptr; nsView* newRootView = newVM ? newVM->GetRootView() : nullptr; // Insert the new root view at the correct location in the view tree. if (container) { nsSubDocumentFrame* subDocFrame = do_QueryFrame(container->GetPrimaryFrame()); rootViewParent = subDocFrame ? subDocFrame->EnsureInnerView() : nullptr; } else { rootViewParent = nullptr; } if (sibling && sibling->GetPresShell() && sibling->GetPresShell()->GetViewManager()) { rootViewSibling = sibling->GetPresShell()->GetViewManager()->GetRootView(); } else { rootViewSibling = nullptr; } if (rootViewParent && newRootView && newRootView->GetParent() != rootViewParent) { nsViewManager* parentVM = rootViewParent->GetViewManager(); if (parentVM) { // InsertChild(parent, child, sib, true) inserts the child after // sib in content order, which is before sib in view order. BUT // when sib is null it inserts at the end of the the document // order, i.e., first in view order. But when oldRootSibling is // null, the old root as at the end of the view list --- last in // content order --- and we want to call InsertChild(parent, child, // nullptr, false) in that case. parentVM->InsertChild(rootViewParent, newRootView, rootViewSibling, rootViewSibling ? true : false); NS_ASSERTION(newRootView->GetNextSibling() == rootViewSibling, "error in InsertChild"); } } nsCOMPtr privWinInner = privWin->GetCurrentInnerWindow(); // If parent is suspended, increase suspension count. // This can't be done as early as event suppression since this // depends on docshell tree. privWinInner->SyncStateFromParentWindow(); // Now that all of the child docshells have been put into place, we can // restart the timers for the window and all of the child frames. privWinInner->Resume(); // Now that we have found the inner window of the page restored // from the history, we have to make sure that // performance.navigation.type is 2. Performance* performance = privWinInner->GetPerformance(); if (performance) { performance->GetDOMTiming()->NotifyRestoreStart(); } // Restore the refresh URI list. The refresh timers will be restarted // when EndPageLoad() is called. mRefreshURIList = refreshURIList; // Meta-refresh timers have been restarted for this shell, but not // for our children. Walk the child shells and restart their timers. for (auto* childDocLoader : mChildList.ForwardRange()) { nsCOMPtr child = do_QueryObject(childDocLoader); if (child) { child->ResumeRefreshURIs(); } } // Make sure this presentation is the same size as the previous // presentation. If this is not the same size we showed it at last time, // then we need to resize the widget. // XXXbryner This interacts poorly with Firefox's infobar. If the old // presentation had the infobar visible, then we will resize the new // presentation to that smaller size. However, firing the locationchanged // event will hide the infobar, which will immediately resize the window // back to the larger size. A future optimization might be to restore // the presentation at the "wrong" size, then fire the locationchanged // event and check whether the docshell's new size is the same as the // cached viewer size (skipping the resize if they are equal). if (newRootView) { if (!newBounds.IsEmpty() && !newBounds.IsEqualEdges(oldBounds)) { MOZ_LOG(gPageCacheLog, LogLevel::Debug, ("resize widget(%d, %d, %d, %d)", newBounds.x, newBounds.y, newBounds.width, newBounds.height)); mContentViewer->SetBounds(newBounds); } else { nsIScrollableFrame* rootScrollFrame = presShell->GetRootScrollFrameAsScrollable(); if (rootScrollFrame) { rootScrollFrame->PostScrolledAreaEventForCurrentArea(); } } } // The FinishRestore call below can kill these, null them out so we don't // have invalid pointer lying around. newRootView = rootViewSibling = rootViewParent = nullptr; newVM = nullptr; // If the IsUnderHiddenEmbedderElement() state has been changed, we need to // update it. if (oldPresShell && presShell && presShell->IsUnderHiddenEmbedderElement() != oldPresShell->IsUnderHiddenEmbedderElement()) { presShell->SetIsUnderHiddenEmbedderElement( oldPresShell->IsUnderHiddenEmbedderElement()); } // Simulate the completion of the load. nsDocShell::FinishRestore(); // Restart plugins, and paint the content. if (presShell) { presShell->Thaw(); } return privWin->FireDelayedDOMEvents(true); } nsresult nsDocShell::CreateContentViewer(const nsACString& aContentType, nsIRequest* aRequest, nsIStreamListener** aContentHandler) { if (DocGroup::TryToLoadIframesInBackground()) { ResetToFirstLoad(); } *aContentHandler = nullptr; if (!mTreeOwner || mIsBeingDestroyed) { // If we don't have a tree owner, then we're in the process of being // destroyed. Rather than continue trying to load something, just give up. return NS_ERROR_DOCSHELL_DYING; } if (!mBrowsingContext->AncestorsAreCurrent() || mBrowsingContext->IsInBFCache()) { mBrowsingContext->RemoveRootFromBFCacheSync(); return NS_ERROR_NOT_AVAILABLE; } // Can we check the content type of the current content viewer // and reuse it without destroying it and re-creating it? NS_ASSERTION(mLoadGroup, "Someone ignored return from Init()?"); // Instantiate the content viewer object nsCOMPtr viewer; nsresult rv = NewContentViewerObj(aContentType, aRequest, mLoadGroup, aContentHandler, getter_AddRefs(viewer)); if (NS_FAILED(rv)) { return rv; } // Notify the current document that it is about to be unloaded!! // // It is important to fire the unload() notification *before* any state // is changed within the DocShell - otherwise, javascript will get the // wrong information :-( // if (mSavingOldViewer) { // We determined that it was safe to cache the document presentation // at the time we initiated the new load. We need to check whether // it's still safe to do so, since there may have been DOM mutations // or new requests initiated. RefPtr doc = viewer->GetDocument(); mSavingOldViewer = CanSavePresentation( mLoadType, aRequest, doc, /* aReportBFCacheComboTelemetry */ false); } NS_ASSERTION(!mLoadingURI, "Re-entering unload?"); nsCOMPtr aOpenedChannel = do_QueryInterface(aRequest); if (aOpenedChannel) { aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI)); } // Grab the current URI, we need to pass it to Embed, and OnNewURI will reset // it before we do call Embed. nsCOMPtr previousURI = mCurrentURI; FirePageHideNotification(!mSavingOldViewer); if (mIsBeingDestroyed) { // Force to stop the newly created orphaned viewer. viewer->Stop(); return NS_ERROR_DOCSHELL_DYING; } mLoadingURI = nullptr; // Set mFiredUnloadEvent = false so that the unload handler for the // *new* document will fire. mFiredUnloadEvent = false; // we've created a new document so go ahead and call // OnNewURI(), but don't fire OnLocationChange() // notifications before we've called Embed(). See bug 284993. mURIResultedInDocument = true; bool errorOnLocationChangeNeeded = false; nsCOMPtr failedChannel = mFailedChannel; nsCOMPtr failedURI; if (mLoadType == LOAD_ERROR_PAGE) { // We need to set the SH entry and our current URI here and not // at the moment we load the page. We want the same behavior // of Stop() as for a normal page load. See bug 514232 for details. // Revert mLoadType to load type to state the page load failed, // following function calls need it. mLoadType = mFailedLoadType; Document* doc = viewer->GetDocument(); if (doc) { doc->SetFailedChannel(failedChannel); } nsCOMPtr triggeringPrincipal; if (failedChannel) { // Make sure we have a URI to set currentURI. NS_GetFinalChannelURI(failedChannel, getter_AddRefs(failedURI)); } else { // if there is no failed channel we have to explicitly provide // a triggeringPrincipal for the history entry. triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); } if (!failedURI) { failedURI = mFailedURI; } if (!failedURI) { // We need a URI object to store a session history entry, so make up a URI NS_NewURI(getter_AddRefs(failedURI), "about:blank"); } // When we don't have failedURI, something wrong will happen. See // bug 291876. MOZ_ASSERT(failedURI, "We don't have a URI for history APIs."); mFailedChannel = nullptr; mFailedURI = nullptr; // Create an shistory entry for the old load. if (failedURI) { errorOnLocationChangeNeeded = OnNewURI(failedURI, failedChannel, triggeringPrincipal, nullptr, nullptr, nullptr, false, false, false); } // Be sure to have a correct mLSHE, it may have been cleared by // EndPageLoad. See bug 302115. ChildSHistory* shistory = GetSessionHistory(); if (!mozilla::SessionHistoryInParent() && shistory && !mLSHE) { int32_t idx = shistory->LegacySHistory()->GetRequestedIndex(); if (idx == -1) { idx = shistory->Index(); } shistory->LegacySHistory()->GetEntryAtIndex(idx, getter_AddRefs(mLSHE)); } mLoadType = LOAD_ERROR_PAGE; } nsCOMPtr finalURI; // If this a redirect, use the final url (uri) // else use the original url // // Note that this should match what documents do (see Document::Reset). NS_GetFinalChannelURI(aOpenedChannel, getter_AddRefs(finalURI)); bool onLocationChangeNeeded = false; if (finalURI) { // Pass false for aCloneSHChildren, since we're loading a new page here. onLocationChangeNeeded = OnNewURI(finalURI, aOpenedChannel, nullptr, nullptr, nullptr, nullptr, false, true, false); } // let's try resetting the load group if we need to... nsCOMPtr currentLoadGroup; NS_ENSURE_SUCCESS( aOpenedChannel->GetLoadGroup(getter_AddRefs(currentLoadGroup)), NS_ERROR_FAILURE); if (currentLoadGroup != mLoadGroup) { nsLoadFlags loadFlags = 0; // Cancel any URIs that are currently loading... // XXX: Need to do this eventually Stop(); // // Retarget the document to this loadgroup... // /* First attach the channel to the right loadgroup * and then remove from the old loadgroup. This * puts the notifications in the right order and * we don't null-out mLSHE in OnStateChange() for * all redirected urls */ aOpenedChannel->SetLoadGroup(mLoadGroup); // Mark the channel as being a document URI... aOpenedChannel->GetLoadFlags(&loadFlags); loadFlags |= nsIChannel::LOAD_DOCUMENT_URI; nsCOMPtr loadInfo = aOpenedChannel->LoadInfo(); if (SandboxFlagsImplyCookies(loadInfo->GetSandboxFlags())) { loadFlags |= nsIRequest::LOAD_DOCUMENT_NEEDS_COOKIE; } aOpenedChannel->SetLoadFlags(loadFlags); mLoadGroup->AddRequest(aRequest, nullptr); if (currentLoadGroup) { currentLoadGroup->RemoveRequest(aRequest, nullptr, NS_BINDING_RETARGETED); } // Update the notification callbacks, so that progress and // status information are sent to the right docshell... aOpenedChannel->SetNotificationCallbacks(this); } if (DocGroup::TryToLoadIframesInBackground()) { if ((!mContentViewer || GetDocument()->IsInitialDocument()) && IsSubframe()) { // At this point, we know we just created a new iframe document based on // the response from the server, and we check if it's a cross-domain // iframe RefPtr newDoc = viewer->GetDocument(); RefPtr parent = GetInProcessParentDocshell(); nsCOMPtr parentPrincipal = parent->GetDocument()->NodePrincipal(); nsCOMPtr thisPrincipal = newDoc->NodePrincipal(); SiteIdentifier parentSite; SiteIdentifier thisSite; nsresult rv = BasePrincipal::Cast(parentPrincipal)->GetSiteIdentifier(parentSite); NS_ENSURE_SUCCESS(rv, rv); rv = BasePrincipal::Cast(thisPrincipal)->GetSiteIdentifier(thisSite); NS_ENSURE_SUCCESS(rv, rv); if (!parentSite.Equals(thisSite)) { if (profiler_thread_is_being_profiled_for_markers()) { nsCOMPtr prinURI; BasePrincipal::Cast(thisPrincipal)->GetURI(getter_AddRefs(prinURI)); nsPrintfCString marker( "Iframe loaded in background: %s", nsContentUtils::TruncatedURLForDisplay(prinURI).get()); PROFILER_MARKER_TEXT("Background Iframe", DOM, {}, marker); } SetBackgroundLoadIframe(); } } } NS_ENSURE_SUCCESS(Embed(viewer, nullptr, false, ShouldAddToSessionHistory(finalURI, aOpenedChannel), aOpenedChannel, previousURI), NS_ERROR_FAILURE); if (!mBrowsingContext->GetHasLoadedNonInitialDocument()) { MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetHasLoadedNonInitialDocument(true)); } if (TreatAsBackgroundLoad()) { nsCOMPtr triggerParentCheckDocShell = NewRunnableMethod("nsDocShell::TriggerParentCheckDocShellIsEmpty", this, &nsDocShell::TriggerParentCheckDocShellIsEmpty); nsresult rv = NS_DispatchToCurrentThread(triggerParentCheckDocShell); NS_ENSURE_SUCCESS(rv, rv); } mSavedRefreshURIList = nullptr; mSavingOldViewer = false; mEODForCurrentDocument = false; // if this document is part of a multipart document, // the ID can be used to distinguish it from the other parts. nsCOMPtr multiPartChannel(do_QueryInterface(aRequest)); if (multiPartChannel) { if (PresShell* presShell = GetPresShell()) { if (Document* doc = presShell->GetDocument()) { uint32_t partID; multiPartChannel->GetPartID(&partID); doc->SetPartID(partID); } } } if (errorOnLocationChangeNeeded) { FireOnLocationChange(this, failedChannel, failedURI, LOCATION_CHANGE_ERROR_PAGE); } else if (onLocationChangeNeeded) { uint32_t locationFlags = (mLoadType & LOAD_CMD_RELOAD) ? uint32_t(LOCATION_CHANGE_RELOAD) : 0; FireOnLocationChange(this, aRequest, mCurrentURI, locationFlags); } return NS_OK; } nsresult nsDocShell::NewContentViewerObj(const nsACString& aContentType, nsIRequest* aRequest, nsILoadGroup* aLoadGroup, nsIStreamListener** aContentHandler, nsIContentViewer** aViewer) { nsCOMPtr aOpenedChannel = do_QueryInterface(aRequest); nsCOMPtr docLoaderFactory = nsContentUtils::FindInternalContentViewer(aContentType); if (!docLoaderFactory) { return NS_ERROR_FAILURE; } // Now create an instance of the content viewer nsLayoutDLF makes the // determination if it should be a "view-source" instead of "view" nsresult rv = docLoaderFactory->CreateInstance( "view", aOpenedChannel, aLoadGroup, aContentType, this, nullptr, aContentHandler, aViewer); NS_ENSURE_SUCCESS(rv, rv); (*aViewer)->SetContainer(this); return NS_OK; } nsresult nsDocShell::SetupNewViewer(nsIContentViewer* aNewViewer, WindowGlobalChild* aWindowActor) { MOZ_ASSERT(!mIsBeingDestroyed); // // Copy content viewer state from previous or parent content viewer. // // The following logic is mirrored in nsHTMLDocument::StartDocumentLoad! // // Do NOT to maintain a reference to the old content viewer outside // of this "copying" block, or it will not be destroyed until the end of // this routine and all