diff options
Diffstat (limited to '')
-rw-r--r-- | docshell/base/nsDocShell.cpp | 13882 |
1 files changed, 13882 insertions, 0 deletions
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp new file mode 100644 index 0000000000..bc67480311 --- /dev/null +++ b/docshell/base/nsDocShell.cpp @@ -0,0 +1,13882 @@ +/* -*- 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 <algorithm> + +#ifdef XP_WIN +# include <process.h> +# define getpid _getpid +#else +# include <unistd.h> // 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/Tuple.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), + mIsAppTab(false), + mDeviceSizeIsPageSize(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); + + mDeviceSizeIsPageSize = Preferences::GetBool( + "docshell.device_size_is_page_size", mDeviceSizeIsPageSize); + + if (nsCOMPtr<nsIObserverService> 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> nsDocShell::Create( + BrowsingContext* aBrowsingContext, uint64_t aContentWindowID) { + MOZ_ASSERT(aBrowsingContext, "DocShell without a BrowsingContext!"); + + nsresult rv; + RefPtr<nsDocShell> 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<nsIInterfaceRequestor> 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<WindowContext> 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<Element> 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<nsIDocShell> 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<nsIDocShellTreeItem> 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<nsICommandManager*>(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<Document> 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<nsIWindowWatcher> 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<nsIDocShellTreeOwner> 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<nsIBrowserChild> browserChild = GetBrowserChild(); + static_cast<BrowserChild*>(browserChild.get()) + ->SetCancelContentJSEpoch(aEpoch); + return NS_OK; +} + +nsresult nsDocShell::CheckDisallowedJavascriptLoad( + nsDocShellLoadState* aLoadState) { + if (!net::SchemeIsJavascript(aLoadState->URI())) { + return NS_OK; + } + + if (nsCOMPtr<nsIPrincipal> 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()) { + return LoadHistoryEntry(aLoadState->SHEntry(), 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<nsIPrincipal> 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<nsISHEntry> 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<nsIDocShellTreeItem> parentAsItem; + GetInProcessSameTypeParent(getter_AddRefs(parentAsItem)); + nsCOMPtr<nsIDocShell> parentDS(do_QueryInterface(parentAsItem)); + + if (!parentDS || parentDS == static_cast<nsIDocShell*>(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<nsILoadGroup> loadGroup; + GetLoadGroup(getter_AddRefs(loadGroup)); + if (contentChild && loadGroup && !mCheckingSessionHistory) { + RefPtr<Document> parentDoc = parentDS->GetDocument(); + parentDoc->BlockOnload(); + RefPtr<BrowsingContext> browsingContext = mBrowsingContext; + Maybe<uint64_t> currentLoadIdentifier = + mBrowsingContext->GetCurrentLoadIdentifier(); + RefPtr<nsDocShellLoadState> loadState = aLoadState; + bool isNavigating = mIsNavigating; + RefPtr<StopDetector> 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<LoadingSessionHistoryInfo>&& aResult) { + RefPtr<nsDocShell> docShell = + static_cast<nsDocShell*>(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<nsDocShell> docShell = + static_cast<nsDocShell*>(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<LoadingSessionHistoryInfo> 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<nsISHEntry> 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<nsISHEntry> 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<nsISHEntry> 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<nsIContentViewer> contentViewer(mContentViewer); + mFiredUnloadEvent = true; + + if (mTiming) { + mTiming->NotifyUnloadEventStart(); + } + + contentViewer->PageHide(aIsUnload); + + if (mTiming) { + mTiming->NotifyUnloadEventEnd(); + } + + AutoTArray<nsCOMPtr<nsIDocShell>, 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<nsDocShell> child = static_cast<nsDocShell*>(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<ChildSHistory> 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<nsGlobalWindowInner> 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<nsIContentViewer> contentViewer(mContentViewer); + if (aShow) { + contentViewer->SetIsHidden(false); + mRefreshURIList = std::move(mBFCachedRefreshURIList); + RefreshURIFromQueue(); + mFiredUnloadEvent = false; + RefPtr<Document> doc = contentViewer->GetDocument(); + if (doc) { + doc->NotifyActivityChanged(); + RefPtr<nsGlobalWindowInner> inner = + mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() + : nullptr; + if (mBrowsingContext->IsTop()) { + doc->NotifyPossibleTitleChange(false); + 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<nsIChannel> 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> 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> presShell = GetPresShell(); + if (presShell) { + presShell->Freeze(false); + } + } +} + +nsresult nsDocShell::Dispatch(TaskCategory aCategory, + already_AddRefed<nsIRunnable>&& aRunnable) { + nsCOMPtr<nsIRunnable> runnable(aRunnable); + nsCOMPtr<nsPIDOMWindowOuter> 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<nsPIDOMWindowOuter> 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; +} + +// +// Bug 13871: Prevent frameset spoofing +// +// This routine answers: 'Is origin's document from same domain as +// target's document?' +// +// file: uris are considered the same domain for the purpose of +// frame navigation regardless of script accessibility (bug 420425) +// +/* static */ +bool nsDocShell::ValidateOrigin(BrowsingContext* aOrigin, + BrowsingContext* aTarget) { + nsIDocShell* originDocShell = aOrigin->GetDocShell(); + MOZ_ASSERT(originDocShell, "originDocShell must not be null"); + Document* originDocument = originDocShell->GetDocument(); + NS_ENSURE_TRUE(originDocument, false); + + nsIDocShell* targetDocShell = aTarget->GetDocShell(); + MOZ_ASSERT(targetDocShell, "targetDocShell must not be null"); + Document* targetDocument = targetDocShell->GetDocument(); + NS_ENSURE_TRUE(targetDocument, false); + + bool equal; + nsresult rv = originDocument->NodePrincipal()->Equals( + targetDocument->NodePrincipal(), &equal); + if (NS_SUCCEEDED(rv) && equal) { + return true; + } + // Not strictly equal, special case if both are file: uris + nsCOMPtr<nsIURI> originURI; + nsCOMPtr<nsIURI> targetURI; + nsCOMPtr<nsIURI> innerOriginURI; + nsCOMPtr<nsIURI> innerTargetURI; + + // Casting to BasePrincipal, as we can't get InnerMost URI otherwise + auto* originDocumentBasePrincipal = + BasePrincipal::Cast(originDocument->NodePrincipal()); + + rv = originDocumentBasePrincipal->GetURI(getter_AddRefs(originURI)); + if (NS_SUCCEEDED(rv) && originURI) { + innerOriginURI = NS_GetInnermostURI(originURI); + } + + auto* targetDocumentBasePrincipal = + BasePrincipal::Cast(targetDocument->NodePrincipal()); + + rv = targetDocumentBasePrincipal->GetURI(getter_AddRefs(targetURI)); + if (NS_SUCCEEDED(rv) && targetURI) { + innerTargetURI = NS_GetInnermostURI(targetURI); + } + + return innerOriginURI && innerTargetURI && SchemeIsFile(innerOriginURI) && + SchemeIsFile(innerTargetURI); +} + +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<EventTarget> 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<nsIContentViewer> 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<Document> doc(GetDocument()); + RefPtr<Promise> 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<Document::GetContentBlockingEventsPromise> 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<nsWeakPtr>::ForwardIterator iter(mPrivacyObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr<nsIPrivacyTransitionObserver> 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<nsWeakPtr>::ForwardIterator iter(mReflowObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr<nsIReflowObserver> 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<RefPtr<nsIDocShell>>& 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<nsIWebBrowserChromeFocus> 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<nsILoadURIDelegate> delegate = GetLoadURIDelegate(); + delegate.forget(aLoadURIDelegate); + return NS_OK; +} + +already_AddRefed<nsILoadURIDelegate> nsDocShell::GetLoadURIDelegate() { + if (nsCOMPtr<nsILoadURIDelegate> 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<nsIDocShell> shell = do_QueryObject(child); + if (shell) { + shell->HistoryPurged(aNumEntries); + } + } + + return NS_OK; +} + +void nsDocShell::TriggerParentCheckDocShellIsEmpty() { + if (RefPtr<nsDocShell> 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<nsIDocShell> shell = do_QueryObject(child); + if (shell) { + static_cast<nsDocShell*>(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<JS::Value> aOut) { + nsTArray<dom::ProfileTimelineMarker> store; + SequenceRooter<dom::ProfileTimelineMarker> 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<nsDocShell> 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<nsDocShell> 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<nsWeakPtr>::ForwardIterator iter(mScrollObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref); + if (obs) { + obs->AsyncPanZoomStarted(); + } else { + iter.Remove(); + } + } +} + +void nsDocShell::NotifyAsyncPanZoomStopped() { + nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref); + if (obs) { + obs->AsyncPanZoomStopped(); + } else { + iter.Remove(); + } + } +} + +NS_IMETHODIMP +nsDocShell::NotifyScrollObservers() { + nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr<nsIScrollObserver> 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<nsGlobalWindowInner> win = + mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + if (win) { + Navigator* navigator = win->Navigator(); + if (navigator) { + navigator->ClearPlatformCache(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::ClearCachedUserAgent() { + RefPtr<nsGlobalWindowInner> 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> 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> nsDocShell::GetInProcessParentDocshell() { + nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(GetAsSupports(mParent)); + return docshell.forget().downcast<nsDocShell>(); +} + +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<nsPIDOMWindowOuter> 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<nsIDocShell> parent = GetInProcessParentDocshell(); + nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr; + nsPIDOMWindowInner* parentInner = + parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr; + if (!parentInner) { + return; + } + + nsCOMPtr<nsIURI> 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<ServiceWorkerDescriptor> controller(parentInner->GetController()); + if (controller.isNothing() || + !ServiceWorkerAllowedToControlWindow(principal, uri)) { + return; + } + + mInitialClientSource->InheritController(controller.ref()); +} + +Maybe<ClientInfo> nsDocShell::GetInitialClientInfo() const { + if (mInitialClientSource) { + Maybe<ClientInfo> 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<ClientInfo>(); + } + + return innerWindow->GetClientInfo(); +} + +nsresult nsDocShell::SetDocLoaderParent(nsDocLoader* aParent) { + bool wasFrame = IsSubframe(); + + nsresult rv = nsDocLoader::SetDocLoaderParent(aParent); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsPriority> 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<nsIDocShell> 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<nsIURIContentListener> 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<nsIDocShellTreeItem> parent = + do_QueryInterface(GetAsSupports(mParent)); + if (!parent) { + return NS_OK; + } + + if (parent->ItemType() == mItemType) { + nsCOMPtr<nsIDocShell> parentDS = do_QueryInterface(parent); + parentDS.forget(aParent); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetInProcessRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) { + NS_ENSURE_ARG_POINTER(aRootTreeItem); + + RefPtr<nsDocShell> root = this; + RefPtr<nsDocShell> 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<nsIDocShellTreeItem*>(this); + + nsCOMPtr<nsIDocShellTreeItem> 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<nsIWebProgress> webProgress = + do_QueryInterface(GetAsSupports(this)); + + if (webProgress) { + nsCOMPtr<nsIWebProgressListener> oldListener = + do_QueryInterface(mTreeOwner); + nsCOMPtr<nsIWebProgressListener> 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<nsIDocShellTreeItem> 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<nsIBrowserChild> newBrowserChild = do_GetInterface(mTreeOwner); + MOZ_ASSERT(newBrowserChild, + "No BrowserChild actor for tree owner in Content!"); + + if (mBrowserChild) { + nsCOMPtr<nsIBrowserChild> 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<nsDocLoader> 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<nsIDocShell> 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<nsDocLoader> 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<nsDocShell> 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<uint32_t>(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<nsDocShell*>(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<ChildSHistory> 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<ChildSHistory> 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<nsIDocShell> 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<nsGlobalWindowOuter> window = mScriptGlobal; + window.forget(aWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetMessageManager(ContentFrameMessageManager** aMessageManager) { + RefPtr<ContentFrameMessageManager> mm; + if (RefPtr<BrowserChild> 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; +} + +NS_IMETHODIMP +nsDocShell::SetDeviceSizeIsPageSize(bool aValue) { + if (mDeviceSizeIsPageSize != aValue) { + mDeviceSizeIsPageSize = aValue; + RefPtr<nsPresContext> presContext = GetPresContext(); + if (presContext) { + presContext->MediaFeatureValuesChanged( + {MediaFeatureChangeReason::DeviceSizeIsPageSizeChange}, + MediaFeatureChangePropagation::JustThisDocument); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDeviceSizeIsPageSize(bool* aValue) { + *aValue = mDeviceSizeIsPageSize; + return NS_OK; +} + +void nsDocShell::ClearFrameHistory(nsISHEntry* aEntry) { + MOZ_ASSERT(!mozilla::SessionHistoryInParent()); + RefPtr<ChildSHistory> 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<ChildSHistory> 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<ChildSHistory> 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<ChildSHistory> 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<ChildSHistory> 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<ChildSHistory> 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(const nsAString& aURI, + const LoadURIOptions& aLoadURIOptions) { + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + + RefPtr<nsDocShellLoadState> loadState; + nsresult rv = nsDocShellLoadState::CreateFromLoadURIOptions( + mBrowsingContext, aURI, 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(aURI).get())); + + // We need to store a session history entry. We don't have a valid URI, so + // we use about:blank instead. + nsCOMPtr<nsIURI> uri; + MOZ_ALWAYS_SUCCEEDS(NS_NewURI(getter_AddRefs(uri), "about:blank"_ns)); + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + if (aLoadURIOptions.mTriggeringPrincipal) { + triggeringPrincipal = aLoadURIOptions.mTriggeringPrincipal; + } else { + triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); + } + if (mozilla::SessionHistoryInParent()) { + mActiveEntry = MakeUnique<SessionHistoryInfo>( + 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(aURI).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::LoadURIFromScript(const nsAString& aURI, + JS::Handle<JS::Value> 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); +} + +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> element = mBrowsingContext->GetEmbedderElement(); + if (element) { + if (aFireFrameErrorEvent) { + if (RefPtr<nsFrameLoaderOwner> flo = do_QueryObject(element)) { + if (RefPtr<nsFrameLoader> 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 = 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<nsIPrompt> prompter; + nsCOMPtr<nsIStringBundle> 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<nsString, 3> 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<nsINestedURI> nestedURI = do_QueryInterface(aURI); + while (nestedURI) { + nsCOMPtr<nsIURI> 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<nsIURI> 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<nsINSSErrorsService> 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<nsITransportSecurityInfo> 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<nsISiteSecurityService> 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<nsIPublicKeyPinningService> 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<EventTarget> handler = mChromeEventHandler; + if (handler) { + nsCOMPtr<Element> 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<nsILoadURIDelegate> loadURIDelegate = GetLoadURIDelegate()) { + nsCOMPtr<nsIURI> 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<nsITextToSubURI> 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) { + MOZ_ASSERT(!mIsBeingDestroyed); + +#if defined(DEBUG) + if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { + nsAutoCString chanName; + if (aFailedChannel) { + aFailedChannel->GetName(chanName); + } else { + chanName.AssignLiteral("<no channel>"); + } + + 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<nsICaptivePortalService> 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<nsIURI> 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<nsDocShellLoadState> 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<LoadingSessionHistoryInfo>(*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<ChildSHistory> rootSH = GetRootSessionHistory(); + if (mozilla::SessionHistoryInParent()) { + MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p Reload", this)); + bool forceReload = IsForceReloadType(loadType); + if (!XRE_IsParentProcess()) { + RefPtr<nsDocShell> docShell(this); + nsCOMPtr<nsIContentViewer> cv(mContentViewer); + NS_ENSURE_STATE(cv); + + bool okToUnload = true; + MOZ_TRY(cv->PermitUnload(&okToUnload)); + if (!okToUnload) { + return NS_OK; + } + + RefPtr<Document> doc(GetDocument()); + RefPtr<BrowsingContext> browsingContext(mBrowsingContext); + nsCOMPtr<nsIURI> currentURI(mCurrentURI); + nsCOMPtr<nsIReferrerInfo> referrerInfo(mReferrerInfo); + RefPtr<StopDetector> stopDetector = new StopDetector(); + nsCOMPtr<nsILoadGroup> 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]( + Tuple<bool, Maybe<RefPtr<nsDocShellLoadState>>, Maybe<bool>>&& + aResult) { + auto scopeExit = MakeScopeExit([loadGroup, stopDetector]() { + if (loadGroup) { + loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK); + } + }); + if (stopDetector->Canceled()) { + return; + } + bool canReload; + Maybe<RefPtr<nsDocShellLoadState>> loadState; + Maybe<bool> reloadingActiveEntry; + + 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<RefPtr<nsDocShellLoadState>> loadState; + Maybe<bool> 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)); + ReloadDocument(this, GetDocument(), loadType, mBrowsingContext, + mCurrentURI, mReferrerInfo); + } + } + } + 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) { + return LoadHistoryEntry( + mOSHE, loadType, + aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION); + } + + if (mLSHE) { // In case a reload happened before the current load is done + return LoadHistoryEntry( + mLSHE, loadType, + aReloadFlags & nsIWebNavigation::LOAD_FLAGS_USER_ACTIVATION); + } + + return ReloadDocument(this, GetDocument(), loadType, mBrowsingContext, + mCurrentURI, mReferrerInfo); +} + +/* 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<nsIURI> originalURI; + nsCOMPtr<nsIURI> resultPrincipalURI; + bool loadReplace = false; + + nsIPrincipal* triggeringPrincipal = aDocument->NodePrincipal(); + nsCOMPtr<nsIContentSecurityPolicy> 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<nsIChannel> chan = aDocument->GetChannel(); + if (chan) { + uint32_t loadFlags; + chan->GetLoadFlags(&loadFlags); + loadReplace = loadFlags & nsIChannel::LOAD_REPLACE; + nsCOMPtr<nsIHttpChannel> httpChan(do_QueryInterface(chan)); + if (httpChan) { + httpChan->GetOriginalURI(getter_AddRefs(originalURI)); + } + + nsCOMPtr<nsILoadInfo> 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<nsIURI> currentURI = aCurrentURI; + + // Reload always rewrites result principal URI. + Maybe<nsCOMPtr<nsIURI>> emplacedResultPrincipalURI; + emplacedResultPrincipalURI.emplace(std::move(resultPrincipalURI)); + + RefPtr<WindowContext> context = aBrowsingContext->GetCurrentWindowContext(); + RefPtr<nsDocShellLoadState> 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<nsISHEntry*>(mLSHE)); + } + mActiveEntryIsLoadingFromSessionHistory = false; + + mFailedChannel = nullptr; + mFailedURI = nullptr; + } + + if (nsIWebNavigation::STOP_CONTENT & aStopFlags) { + // Stop the document loading and animations + if (mContentViewer) { + nsCOMPtr<nsIContentViewer> cv = mContentViewer; + cv->Stop(); + } + } else if (nsIWebNavigation::STOP_NETWORK & aStopFlags) { + // Stop the document loading only + if (mContentViewer) { + RefPtr<Document> 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<nsIWebNavigation> 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<Document> 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<nsIURI> uri = mCurrentURI; + uri.forget(aURI); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSessionHistoryXPCOM(nsISupports** aSessionHistory) { + NS_ENSURE_ARG_POINTER(aSessionHistory); + RefPtr<ChildSHistory> 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<nsIURI> newURI; + nsresult rv = NS_NewURI(getter_AddRefs(newURI), aURI); + if (NS_FAILED(rv)) { + return rv; + } + + RefPtr<nsDocShellLoadState> 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<nsISHEntry> 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<nsISHEntry> 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<nsIInputStream> nsDocShell::GetPostDataFromCurrentEntry() + const { + nsCOMPtr<nsIInputStream> 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<uint32_t> 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<nsIObserverService> 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<nsIDocShellTreeItem> 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<nsIBaseWindow> 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<nsIBaseWindow> 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<nsIBaseWindow> 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<nsIContentViewer> 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<Document> 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<nsViewManager> 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() == nsViewVisibility_kHide) { + 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<nsDocShell> docShell = this; + RefPtr<nsDocShell> 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<nsIBaseWindow> 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> presShell = GetPresShell()) { + presShell->ActivenessMaybeChanged(); + } + + // Tell the window about it + if (mScriptGlobal) { + mScriptGlobal->SetIsBackground(!isActive); + if (RefPtr<Document> 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<nsDOMNavigationTiming> 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::SetIsAppTab(bool aIsAppTab) { + mIsAppTab = aIsAppTab; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsAppTab(bool* aIsAppTab) { + *aIsAppTab = mIsAppTab; + return NS_OK; +} + +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<nsIContentViewer> 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<nsIBaseWindow> 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> 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<nsITimerCallback> 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<nsPIDOMWindowOuter> win = GetWindow(); + NS_ENSURE_TRUE(win, NS_ERROR_FAILURE); + + nsCOMPtr<nsITimer> 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<nsITimer> 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<nsDocShellLoadState> 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<nsIPrincipal> principal = aPrincipal; + RefPtr<Document> 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<nsIReferrerInfo> 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<ReferrerInfo*>(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 Tuple<const char16_t*, const char16_t*> 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 MakeTuple(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 MakeTuple(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 MakeTuple(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<char> 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<char16_t>::find( + urlStart, std::distance(urlStart, aEnd), quote.value()))) { + urlEnd = quotePos; + } + + return MakeTuple(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<uint32_t> 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<nsIURI> 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. + 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<nsIScriptSecurityManager> 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<nsITimer> 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<nsITimer> timer = do_QueryElementAt(mRefreshURIList, i); + if (!timer) { + continue; // this must be a nsRefreshURI already + } + + // Replace this timer object with a nsRefreshTimer object. + nsCOMPtr<nsITimerCallback> 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<nsIDocShell> 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<nsIDocShell> 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<nsITimerCallback> 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<nsRefreshTimer*>( + static_cast<nsITimerCallback*>(refreshInfo)) + ->GetDelay(); + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + if (win) { + nsCOMPtr<nsITimer> 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<nsIMultiPartChannel> 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) { + // 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<nsISHEntry*>(mLSHE)); + } + + if (!aIsTransientAboutBlank && mozilla::SessionHistoryInParent() && + !IsFollowupPartOfMultipart(aRequest)) { + bool expired = false; + uint32_t cacheKey = 0; + nsCOMPtr<nsICacheInfoChannel> 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); + } + + 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<nsIChannel> channel(do_QueryInterface(aRequest)); + nsCOMPtr<nsIURI> 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<DocumentChannel> 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<nsIWidget> 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<nsIWidget> mainWidget; + GetMainWidget(getter_AddRefs(mainWidget)); + if (mainWidget) { + mainWidget->SetCursor(nsIWidget::Cursor{eCursor_standard}); + } + } + } + + if ((~aStateFlags & (STATE_IS_DOCUMENT | STATE_STOP)) == 0) { + nsCOMPtr<nsIWebProgress> webProgress = + do_QueryInterface(GetAsSupports(this)); + // Is the document stop notification for this document? + if (aProgress == webProgress.get()) { + nsCOMPtr<nsIChannel> 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<nsIURI> 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<DocumentChannel> 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<nsIURI> 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<nsIHttpChannel> 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<nsIURIFixupInfo> nsDocShell::KeywordToURI( + const nsACString& aKeyword, bool aIsPrivateContext) { + nsCOMPtr<nsIURIFixupInfo> info; + if (!XRE_IsContentProcess()) { + nsCOMPtr<nsIURIFixup> uriFixup = components::URIFixup::Service(); + if (uriFixup) { + uriFixup->KeywordToURI(aKeyword, aIsPrivateContext, getter_AddRefs(info)); + } + } + return info.forget(); +} + +/* static */ +already_AddRefed<nsIURI> 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<nsILoadInfo> 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<nsITransportSecurityInfo> tsi; + rv = aChannel->GetSecurityInfo(getter_AddRefs(tsi)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + if (NS_WARN_IF(!tsi)) { + return nullptr; + } + + nsCOMPtr<nsIX509Cert> cert; + rv = tsi->GetServerCert(getter_AddRefs(cert)); + if (NS_WARN_IF(NS_FAILED(rv) || !cert)) { + return nullptr; + } + + nsTArray<uint8_t> 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<const uint8_t*, const char*>(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<nsIURI> newURI; + Unused << NS_MutateURI(aUrl).SetHost(newHost).Finalize( + getter_AddRefs(newURI)); + + return newURI.forget(); +} + +/* static */ +already_AddRefed<nsIURI> nsDocShell::AttemptURIFixup( + nsIChannel* aChannel, nsresult aStatus, + const mozilla::Maybe<nsCString>& 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<nsIURI> 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<nsIURI> newURI; + nsCOMPtr<nsIInputStream> 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<nsIEffectiveTLDService> tldService = + do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID); + if (tldService) { + nsAutoCString suffix; + attemptFixup = + NS_SUCCEEDED(tldService->GetKnownPublicSuffix(url, suffix)) && + suffix.IsEmpty(); + } + } + if (attemptFixup) { + nsCOMPtr<nsIURIFixupInfo> 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<nsIIDNService> 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<nsILoadInfo> 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<nsIURIFixup> uriFixup = components::URIFixup::Service(); + if (uriFixup) { + nsCOMPtr<nsIURIFixupInfo> 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<nsILoadInfo> 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<uint32_t>(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<nsIConsoleReportCollector> reporter = do_QueryInterface(aChannel); + if (reporter) { + nsCOMPtr<nsILoadGroup> loadGroup; + aChannel->GetLoadGroup(getter_AddRefs(loadGroup)); + if (loadGroup) { + reporter->FlushConsoleReports(loadGroup); + } else { + reporter->FlushConsoleReports(GetDocument()); + } + } + + nsCOMPtr<nsIURI> url; + nsresult rv = aChannel->GetURI(getter_AddRefs(url)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsITimedChannel> 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<nsISHEntry> 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<nsIDocShell> 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<nsIContentViewer> 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<nsIHttpChannel> 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<nsString> 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<nsILoadInfo> 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>(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<nsIContentSecurityPolicy> cspToInheritForAboutBlank; + nsCOMPtr<nsIURI> baseURI; + nsIPrincipal* principal = GetInheritedPrincipal(false); + nsIPrincipal* partitionedPrincipal = GetInheritedPrincipal(false, true); + + nsCOMPtr<nsIDocShellTreeItem> parentItem; + GetInProcessSameTypeParent(getter_AddRefs(parentItem)); + if (parentItem) { + if (nsCOMPtr<nsPIDOMWindowOuter> domWin = GetWindow()) { + nsCOMPtr<Element> 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<Document> 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<nsILoadInfo::CrossOriginEmbedderPolicy>& aCOEP, + bool aTryToSaveOldPresentation, bool aCheckPermitUnload, + WindowGlobalChild* aActor) { + RefPtr<Document> blankDoc; + nsCOMPtr<nsIContentViewer> 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<nsIDocShell> kungFuDeathGrip(this); + + AutoRestore<bool> 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<nsIDocumentLoaderFactory> docFactory = + nsContentUtils::FindInternalContentViewer("text/html"_ns); + + if (docFactory) { + nsCOMPtr<nsIPrincipal> 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<nsCSPContext> cspToInherit = new nsCSPContext(); + cspToInherit->InitFromOther(static_cast<nsCSPContext*>(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); + 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<Document> 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<nsIContentViewer> 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<Document> 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<RefPtr<BrowsingContext>>& 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<nsISupports> windowState = mScriptGlobal->SaveWindowState(); + NS_ENSURE_TRUE(windowState, NS_ERROR_FAILURE); + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) { + nsAutoCString spec; + nsCOMPtr<nsIURI> 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<nsIDocShellTreeItem> 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<Document> 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<nsIDocShell> 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<nsIDocShell> child = do_QueryObject(childDocLoader); + if (child) { + child->FinishRestore(); + } + } + + if (mOSHE && mOSHE->HasDetachedEditor()) { + ReattachEditorToWindow(mOSHE); + } + + RefPtr<Document> 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<nsIContentViewer> viewer = aSHEntry->GetContentViewer(); + + nsAutoCString spec; + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gPageCacheLog, LogLevel::Debug))) { + nsCOMPtr<nsIURI> 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<nsIDocShell> 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<RestorePresentationEvent> 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<nsDocShell::RestorePresentationEvent>& + aRestorePresentationEvent) + : mRestorePresentationEvent(aRestorePresentationEvent), + mEvent(aRestorePresentationEvent.get()) {} + + ~PresentationEventForgetter() { Forget(); } + + void Forget() { + if (mRestorePresentationEvent.get() == mEvent) { + mRestorePresentationEvent.Forget(); + mEvent = nullptr; + } + } + + private: + nsRevocableEventPtr<nsDocShell::RestorePresentationEvent>& + mRestorePresentationEvent; + RefPtr<nsDocShell::RestorePresentationEvent> 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<nsIContentViewer> 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<Document> 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<nsISHEntry> 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<RestorePresentationEvent> 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<ChildSHistory> 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<nsIContentViewer> 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<nsIContent> container; + RefPtr<Document> 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<nsISupports> windowState = mLSHE->GetWindowState(); + mLSHE->SetWindowState(nullptr); + + bool sticky = mLSHE->GetSticky(); + + RefPtr<Document> document = mContentViewer->GetDocument(); + + nsCOMArray<nsIDocShellTreeItem> childShells; + int32_t i = 0; + nsCOMPtr<nsIDocShellTreeItem> 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<nsIMutableArray> 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<nsDocShellEditorData> data(mLSHE->ForgetEditorData()); + + // Now remove it from the cached presentation. + mLSHE->SetContentViewer(nullptr); + mEODForCurrentDocument = false; + + mLSHE->SetEditorData(data.release()); + +#ifdef DEBUG + { + nsCOMPtr<nsIMutableArray> refreshURIs = mLSHE->GetRefreshURIList(); + nsCOMPtr<nsIDocShellTreeItem> 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<nsISHEntry*>(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<nsDocShell> parent = GetInProcessParentDocshell(); + if (parent) { + RefPtr<Document> 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<nsIURI> 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<nsPIDOMWindowOuter> privWin = GetWindow(); + NS_ASSERTION(privWin, "could not get nsPIDOMWindow interface"); + + // Now, dispatch a title change event which would happen as the + // <head> 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<nsIDocShell> 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> 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<nsPresContext> 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<nsPIDOMWindowInner> 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<nsIDocShell> 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<nsIContentViewer> 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<Document> doc = viewer->GetDocument(); + mSavingOldViewer = CanSavePresentation( + mLoadType, aRequest, doc, /* aReportBFCacheComboTelemetry */ false); + } + + NS_ASSERTION(!mLoadingURI, "Re-entering unload?"); + + nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest); + if (aOpenedChannel) { + aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI)); + } + 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<nsIChannel> failedChannel = mFailedChannel; + nsCOMPtr<nsIURI> 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<nsIPrincipal> 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<nsIURI> 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<nsILoadGroup> 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<nsILoadInfo> 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<Document> newDoc = viewer->GetDocument(); + + RefPtr<nsDocShell> parent = GetInProcessParentDocshell(); + nsCOMPtr<nsIPrincipal> parentPrincipal = + parent->GetDocument()->NodePrincipal(); + nsCOMPtr<nsIPrincipal> 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<nsIURI> prinURI; + BasePrincipal::Cast(thisPrincipal)->GetURI(getter_AddRefs(prinURI)); + nsPrintfCString marker("Iframe loaded in background: %s", + prinURI->GetSpecOrDefault().get()); + PROFILER_MARKER_TEXT("Background Iframe", DOM, {}, marker); + } + SetBackgroundLoadIframe(); + } + } + } + + NS_ENSURE_SUCCESS(Embed(viewer, nullptr, false, + ShouldAddToSessionHistory(finalURI, aOpenedChannel), + aOpenedChannel), + NS_ERROR_FAILURE); + + if (!mBrowsingContext->GetHasLoadedNonInitialDocument()) { + MOZ_ALWAYS_SUCCEEDS(mBrowsingContext->SetHasLoadedNonInitialDocument(true)); + } + + if (TreatAsBackgroundLoad()) { + nsCOMPtr<nsIRunnable> 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<nsIMultiPartChannel> 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<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest); + + nsCOMPtr<nsIDocumentLoaderFactory> 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 <SCRIPT>s and event handlers fail! (bug 20315) + // + // In this block of code, if we get an error result, we return it + // but if we get a null pointer, that's perfectly legal for parent + // and parentContentViewer. + // + + int32_t x = 0; + int32_t y = 0; + int32_t cx = 0; + int32_t cy = 0; + + // This will get the size from the current content viewer or from the + // Init settings + DoGetPositionAndSize(&x, &y, &cx, &cy); + + nsCOMPtr<nsIDocShellTreeItem> parentAsItem; + NS_ENSURE_SUCCESS(GetInProcessSameTypeParent(getter_AddRefs(parentAsItem)), + NS_ERROR_FAILURE); + nsCOMPtr<nsIDocShell> parent(do_QueryInterface(parentAsItem)); + + const Encoding* reloadEncoding = nullptr; + int32_t reloadEncodingSource = kCharsetUninitialized; + // |newMUDV| also serves as a flag to set the data from the above vars + nsCOMPtr<nsIContentViewer> newCv; + + if (mContentViewer || parent) { + nsCOMPtr<nsIContentViewer> oldCv; + if (mContentViewer) { + // Get any interesting state from old content viewer + // XXX: it would be far better to just reuse the document viewer , + // since we know we're just displaying the same document as before + oldCv = mContentViewer; + + // Tell the old content viewer to hibernate in session history when + // it is destroyed. + + if (mSavingOldViewer && NS_FAILED(CaptureState())) { + if (mOSHE) { + mOSHE->SyncPresentationState(); + } + mSavingOldViewer = false; + } + } else { + // No old content viewer, so get state from parent's content viewer + parent->GetContentViewer(getter_AddRefs(oldCv)); + } + + if (oldCv) { + newCv = aNewViewer; + if (newCv) { + reloadEncoding = + oldCv->GetReloadEncodingAndSource(&reloadEncodingSource); + } + } + } + + nscolor bgcolor = NS_RGBA(0, 0, 0, 0); + bool isUnderHiddenEmbedderElement = false; + // Ensure that the content viewer is destroyed *after* the GC - bug 71515 + nsCOMPtr<nsIContentViewer> contentViewer = mContentViewer; + if (contentViewer) { + // Stop any activity that may be happening in the old document before + // releasing it... + contentViewer->Stop(); + + // Try to extract the canvas background color from the old + // presentation shell, so we can use it for the next document. + if (PresShell* presShell = contentViewer->GetPresShell()) { + bgcolor = presShell->GetCanvasBackground(); + isUnderHiddenEmbedderElement = presShell->IsUnderHiddenEmbedderElement(); + } + + contentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr); + aNewViewer->SetPreviousViewer(contentViewer); + } + 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(); + } + + 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 = aNewViewer; + + nsCOMPtr<nsIWidget> widget; + NS_ENSURE_SUCCESS(GetMainWidget(getter_AddRefs(widget)), NS_ERROR_FAILURE); + + nsIntRect bounds(x, y, cx, cy); + + mContentViewer->SetNavigationTiming(mTiming); + + if (NS_FAILED(mContentViewer->Init(widget, bounds, aWindowActor))) { + nsCOMPtr<nsIContentViewer> viewer = mContentViewer; + viewer->Close(nullptr); + viewer->Destroy(); + mContentViewer = nullptr; + mCurrentURI = nullptr; + NS_WARNING("ContentViewer Initialization failed"); + return NS_ERROR_FAILURE; + } + + // If we have old state to copy, set the old state onto the new content + // viewer + if (newCv) { + newCv->SetReloadEncodingAndSource(reloadEncoding, reloadEncodingSource); + } + + NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE); + + // Stuff the bgcolor from the old pres shell into the new + // pres shell. This improves page load continuity. + if (RefPtr<PresShell> presShell = mContentViewer->GetPresShell()) { + presShell->SetCanvasBackground(bgcolor); + presShell->ActivenessMaybeChanged(); + if (isUnderHiddenEmbedderElement) { + presShell->SetIsUnderHiddenEmbedderElement(isUnderHiddenEmbedderElement); + } + } + + // XXX: It looks like the LayoutState gets restored again in Embed() + // right after the call to SetupNewViewer(...) + + // We don't show the mContentViewer yet, since we want to draw the old page + // until we have enough of the new page to show. Just return with the new + // viewer still set to hidden. + + return NS_OK; +} + +void nsDocShell::SetDocCurrentStateObj(nsISHEntry* aShEntry, + SessionHistoryInfo* aInfo) { + NS_ENSURE_TRUE_VOID(mContentViewer); + + RefPtr<Document> document = GetDocument(); + NS_ENSURE_TRUE_VOID(document); + + nsCOMPtr<nsIStructuredCloneContainer> scContainer; + if (mozilla::SessionHistoryInParent()) { + // If aInfo is null, just set the document's state object to null. + if (aInfo) { + scContainer = aInfo->GetStateData(); + } + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p SetCurrentDocState %p", this, scContainer.get())); + } else { + if (aShEntry) { + scContainer = aShEntry->GetStateData(); + + // If aShEntry is null, just set the document's state object to null. + } + } + + // It's OK for scContainer too be null here; that just means there's no + // state data associated with this history entry. + document->SetStateObject(scContainer); +} + +nsresult nsDocShell::CheckLoadingPermissions() { + // This method checks whether the caller may load content into + // this docshell. Even though we've done our best to hide windows + // from code that doesn't have the right to access them, it's + // still possible for an evil site to open a window and access + // frames in the new window through window.frames[] (which is + // allAccess for historic reasons), so we still need to do this + // check on load. + nsresult rv = NS_OK; + + if (!IsSubframe()) { + // We're not a frame. Permit all loads. + return rv; + } + + // Note - The check for a current JSContext here isn't necessarily sensical. + // It's just designed to preserve the old semantics during a mass-conversion + // patch. + if (!nsContentUtils::GetCurrentJSContext()) { + return NS_OK; + } + + // Check if the caller is from the same origin as this docshell, + // or any of its ancestors. + for (RefPtr<BrowsingContext> bc = mBrowsingContext; bc; + bc = bc->GetParent()) { + // If the BrowsingContext is not in process, then it + // is true by construction that its principal will not + // subsume the current docshell principal. + if (!bc->IsInProcess()) { + continue; + } + + nsCOMPtr<nsIScriptGlobalObject> sgo = + bc->GetDocShell()->GetScriptGlobalObject(); + nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(sgo)); + + nsIPrincipal* p; + if (!sop || !(p = sop->GetPrincipal())) { + return NS_ERROR_UNEXPECTED; + } + + if (nsContentUtils::SubjectPrincipal()->Subsumes(p)) { + // Same origin, permit load + return NS_OK; + } + } + + return NS_ERROR_DOM_PROP_ACCESS_DENIED; +} + +//***************************************************************************** +// nsDocShell: Site Loading +//***************************************************************************** + +void nsDocShell::CopyFavicon(nsIURI* aOldURI, nsIURI* aNewURI, + bool aInPrivateBrowsing) { + if (XRE_IsContentProcess()) { + dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); + if (contentChild) { + contentChild->SendCopyFavicon(aOldURI, aNewURI, aInPrivateBrowsing); + } + return; + } + +#ifdef MOZ_PLACES + nsCOMPtr<nsIFaviconService> favSvc = + do_GetService("@mozilla.org/browser/favicon-service;1"); + if (favSvc) { + favSvc->CopyFavicons(aOldURI, aNewURI, + aInPrivateBrowsing + ? nsIFaviconService::FAVICON_LOAD_PRIVATE + : nsIFaviconService::FAVICON_LOAD_NON_PRIVATE, + nullptr); + } +#endif +} + +class InternalLoadEvent : public Runnable { + public: + InternalLoadEvent(nsDocShell* aDocShell, nsDocShellLoadState* aLoadState) + : mozilla::Runnable("InternalLoadEvent"), + mDocShell(aDocShell), + mLoadState(aLoadState) { + // For events, both target and filename should be the version of "null" they + // expect. By the time the event is fired, both window targeting and file + // downloading have been handled, so we should never have an internal load + // event that retargets or had a download. + mLoadState->SetTarget(u""_ns); + mLoadState->SetFileName(VoidString()); + } + + NS_IMETHOD + Run() override { +#ifndef ANDROID + MOZ_ASSERT(mLoadState->TriggeringPrincipal(), + "InternalLoadEvent: Should always have a principal here"); +#endif + return mDocShell->InternalLoad(mLoadState); + } + + private: + RefPtr<nsDocShell> mDocShell; + RefPtr<nsDocShellLoadState> mLoadState; +}; + +/** + * Returns true if we started an asynchronous load (i.e., from the network), but + * the document we're loading there hasn't yet become this docshell's active + * document. + * + * When JustStartedNetworkLoad is true, you should be careful about modifying + * mLoadType and mLSHE. These are both set when the asynchronous load first + * starts, and the load expects that, when it eventually runs InternalLoad, + * mLoadType and mLSHE will have their original values. + */ +bool nsDocShell::JustStartedNetworkLoad() { + return mDocumentRequest && mDocumentRequest != GetCurrentDocChannel(); +} + +// The contentType will be INTERNAL_(I)FRAME if this docshell is for a +// non-toplevel browsing context in spec terms. (frame, iframe, <object>, +// <embed>, etc) +// +// This return value will be used when we call NS_CheckContentLoadPolicy, and +// later when we call DoURILoad. +nsContentPolicyType nsDocShell::DetermineContentType() { + if (!IsSubframe()) { + return nsIContentPolicy::TYPE_DOCUMENT; + } + + const auto& maybeEmbedderElementType = + GetBrowsingContext()->GetEmbedderElementType(); + if (!maybeEmbedderElementType) { + // If the EmbedderElementType hasn't been set yet, just assume we're + // an iframe since that's more common. + return nsIContentPolicy::TYPE_INTERNAL_IFRAME; + } + + return maybeEmbedderElementType->EqualsLiteral("iframe") + ? nsIContentPolicy::TYPE_INTERNAL_IFRAME + : nsIContentPolicy::TYPE_INTERNAL_FRAME; +} + +bool nsDocShell::NoopenerForceEnabled() { + // If current's top-level browsing context's active document's + // cross-origin-opener-policy is "same-origin" or "same-origin + COEP" then + // if currentDoc's origin is not same origin with currentDoc's top-level + // origin, noopener is force enabled, and name is cleared to "_blank". + auto topPolicy = mBrowsingContext->Top()->GetOpenerPolicy(); + return (topPolicy == nsILoadInfo::OPENER_POLICY_SAME_ORIGIN || + topPolicy == + nsILoadInfo:: + OPENER_POLICY_SAME_ORIGIN_EMBEDDER_POLICY_REQUIRE_CORP) && + !mBrowsingContext->SameOriginWithTop(); +} + +nsresult nsDocShell::PerformRetargeting(nsDocShellLoadState* aLoadState) { + MOZ_ASSERT(aLoadState, "need a load state!"); + MOZ_ASSERT(!aLoadState->Target().IsEmpty(), "should have a target here!"); + MOZ_ASSERT(aLoadState->TargetBrowsingContext().IsNull(), + "should not have picked target yet"); + + nsresult rv = NS_OK; + RefPtr<BrowsingContext> targetContext; + + // Only _self, _parent, and _top are supported in noopener case. But we + // have to be careful to not apply that to the noreferrer case. See bug + // 1358469. + bool allowNamedTarget = + !aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_NO_OPENER) || + aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER); + if (allowNamedTarget || + aLoadState->Target().LowerCaseEqualsLiteral("_self") || + aLoadState->Target().LowerCaseEqualsLiteral("_parent") || + aLoadState->Target().LowerCaseEqualsLiteral("_top")) { + targetContext = mBrowsingContext->FindWithName( + aLoadState->Target(), /* aUseEntryGlobalForAccessCheck */ false); + } + + if (!targetContext) { + // If the targetContext doesn't exist, then this is a new docShell and we + // should consider this a TYPE_DOCUMENT load + // + // For example, when target="_blank" + + // If there's no targetContext, that means we are about to create a new + // window. Perform a content policy check before creating the window. Please + // note for all other docshell loads content policy checks are performed + // within the contentSecurityManager when the channel is about to be + // openend. + nsISupports* requestingContext = nullptr; + if (XRE_IsContentProcess()) { + // In e10s the child process doesn't have access to the element that + // contains the browsing context (because that element is in the chrome + // process). So we just pass mScriptGlobal. + requestingContext = ToSupports(mScriptGlobal); + } else { + // This is for loading non-e10s tabs and toplevel windows of various + // sorts. + // For the toplevel window cases, requestingElement will be null. + nsCOMPtr<Element> requestingElement = + mScriptGlobal->GetFrameElementInternal(); + requestingContext = requestingElement; + } + + // Ideally we should use the same loadinfo as within DoURILoad which + // should match this one when both are applicable. + nsCOMPtr<nsILoadInfo> secCheckLoadInfo = + new LoadInfo(mScriptGlobal, aLoadState->URI(), + aLoadState->TriggeringPrincipal(), requestingContext, + nsILoadInfo::SEC_ONLY_FOR_EXPLICIT_CONTENTSEC_CHECK, 0); + + // Since Content Policy checks are performed within docShell as well as + // the ContentSecurityManager we need a reliable way to let certain + // nsIContentPolicy consumers ignore duplicate calls. + secCheckLoadInfo->SetSkipContentPolicyCheckForWebRequest(true); + + int16_t shouldLoad = nsIContentPolicy::ACCEPT; + rv = NS_CheckContentLoadPolicy(aLoadState->URI(), secCheckLoadInfo, + ""_ns, // mime guess + &shouldLoad); + + if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { + if (NS_SUCCEEDED(rv)) { + if (shouldLoad == nsIContentPolicy::REJECT_TYPE) { + return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT; + } + if (shouldLoad == nsIContentPolicy::REJECT_POLICY) { + return NS_ERROR_BLOCKED_BY_POLICY; + } + } + + return NS_ERROR_CONTENT_BLOCKED; + } + } + + // + // Resolve the window target before going any further... + // If the load has been targeted to another DocShell, then transfer the + // load to it... + // + + // We've already done our owner-inheriting. Mask out that bit, so we + // don't try inheriting an owner from the target window if we came up + // with a null owner above. + aLoadState->UnsetInternalLoadFlag(INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL); + + if (!targetContext) { + // If the docshell's document is sandboxed, only open a new window + // if the document's SANDBOXED_AUXILLARY_NAVIGATION flag is not set. + // (i.e. if allow-popups is specified) + NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE); + Document* doc = mContentViewer->GetDocument(); + + const bool isDocumentAuxSandboxed = + doc && (doc->GetSandboxFlags() & SANDBOXED_AUXILIARY_NAVIGATION); + + if (isDocumentAuxSandboxed) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + NS_ENSURE_TRUE(win, NS_ERROR_NOT_AVAILABLE); + + RefPtr<BrowsingContext> newBC; + nsAutoCString spec; + aLoadState->URI()->GetSpec(spec); + + // If we are a noopener load, we just hand the whole thing over to our + // window. + if (aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_NO_OPENER) || + NoopenerForceEnabled()) { + // Various asserts that we know to hold because NO_OPENER loads can only + // happen for links. + MOZ_ASSERT(!aLoadState->LoadReplace()); + MOZ_ASSERT(aLoadState->PrincipalToInherit() == + aLoadState->TriggeringPrincipal()); + MOZ_ASSERT(!(aLoadState->InternalLoadFlags() & + ~(INTERNAL_LOAD_FLAGS_NO_OPENER | + INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER)), + "Only INTERNAL_LOAD_FLAGS_NO_OPENER and " + "INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER can be set"); + MOZ_ASSERT_IF(aLoadState->PostDataStream(), + aLoadState->IsFormSubmission()); + MOZ_ASSERT(!aLoadState->HeadersStream()); + // If OnLinkClickSync was invoked inside the onload handler, the load + // type would be set to LOAD_NORMAL_REPLACE; otherwise it should be + // LOAD_LINK. + MOZ_ASSERT(aLoadState->LoadType() == LOAD_LINK || + aLoadState->LoadType() == LOAD_NORMAL_REPLACE); + MOZ_ASSERT(!aLoadState->LoadIsFromSessionHistory()); + MOZ_ASSERT(aLoadState->FirstParty()); // Windowwatcher will assume this. + + RefPtr<nsDocShellLoadState> loadState = + new nsDocShellLoadState(aLoadState->URI()); + + // Set up our loadinfo so it will do the load as much like we would have + // as possible. + loadState->SetReferrerInfo(aLoadState->GetReferrerInfo()); + loadState->SetOriginalURI(aLoadState->OriginalURI()); + + Maybe<nsCOMPtr<nsIURI>> resultPrincipalURI; + aLoadState->GetMaybeResultPrincipalURI(resultPrincipalURI); + + loadState->SetMaybeResultPrincipalURI(resultPrincipalURI); + loadState->SetKeepResultPrincipalURIIfSet( + aLoadState->KeepResultPrincipalURIIfSet()); + // LoadReplace will always be false due to asserts above, skip setting + // it. + loadState->SetTriggeringPrincipal(aLoadState->TriggeringPrincipal()); + loadState->SetTriggeringSandboxFlags( + aLoadState->TriggeringSandboxFlags()); + loadState->SetCsp(aLoadState->Csp()); + loadState->SetInheritPrincipal(aLoadState->HasInternalLoadFlags( + INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL)); + // Explicit principal because we do not want any guesses as to what the + // principal to inherit is: it should be aTriggeringPrincipal. + loadState->SetPrincipalIsExplicit(true); + loadState->SetLoadType(LOAD_LINK); + loadState->SetForceAllowDataURI(aLoadState->HasInternalLoadFlags( + INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI)); + + loadState->SetHasValidUserGestureActivation( + aLoadState->HasValidUserGestureActivation()); + + // Propagate POST data to the new load. + loadState->SetPostDataStream(aLoadState->PostDataStream()); + loadState->SetIsFormSubmission(aLoadState->IsFormSubmission()); + + rv = win->Open(NS_ConvertUTF8toUTF16(spec), + aLoadState->Target(), // window name + u""_ns, // Features + loadState, + true, // aForceNoOpener + getter_AddRefs(newBC)); + MOZ_ASSERT(!newBC); + return rv; + } + + rv = win->OpenNoNavigate(NS_ConvertUTF8toUTF16(spec), + aLoadState->Target(), // window name + u""_ns, // Features + getter_AddRefs(newBC)); + + // In some cases the Open call doesn't actually result in a new + // window being opened. We can detect these cases by examining the + // document in |newBC|, if any. + nsCOMPtr<nsPIDOMWindowOuter> piNewWin = + newBC ? newBC->GetDOMWindow() : nullptr; + if (piNewWin) { + RefPtr<Document> newDoc = piNewWin->GetExtantDoc(); + if (!newDoc || newDoc->IsInitialDocument()) { + aLoadState->SetInternalLoadFlag(INTERNAL_LOAD_FLAGS_FIRST_LOAD); + } + } + + if (newBC) { + targetContext = newBC; + } + } + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(targetContext, rv); + + // If our target BrowsingContext is still pending initialization, ignore the + // navigation request targeting it. + if (NS_WARN_IF(targetContext->GetPendingInitialization())) { + return NS_OK; + } + + aLoadState->SetTargetBrowsingContext(targetContext); + // + // Transfer the load to the target BrowsingContext... Clear the window target + // name to the empty string to prevent recursive retargeting! + // + // No window target + aLoadState->SetTarget(u""_ns); + // No forced download + aLoadState->SetFileName(VoidString()); + return targetContext->InternalLoad(aLoadState); +} + +static nsAutoCString RefMaybeNull(nsIURI* aURI) { + nsAutoCString result; + if (NS_FAILED(aURI->GetRef(result))) { + result.SetIsVoid(true); + } + return result; +} + +uint32_t nsDocShell::GetSameDocumentNavigationFlags(nsIURI* aNewURI) { + uint32_t flags = LOCATION_CHANGE_SAME_DOCUMENT; + + bool equal = false; + if (mCurrentURI && + NS_SUCCEEDED(mCurrentURI->EqualsExceptRef(aNewURI, &equal)) && equal && + RefMaybeNull(mCurrentURI) != RefMaybeNull(aNewURI)) { + flags |= LOCATION_CHANGE_HASHCHANGE; + } + + return flags; +} + +bool nsDocShell::IsSameDocumentNavigation(nsDocShellLoadState* aLoadState, + SameDocumentNavigationState& aState) { + MOZ_ASSERT(aLoadState); + if (!(aLoadState->LoadType() == LOAD_NORMAL || + aLoadState->LoadType() == LOAD_STOP_CONTENT || + LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(), + LOAD_FLAGS_REPLACE_HISTORY) || + aLoadState->LoadType() == LOAD_HISTORY || + aLoadState->LoadType() == LOAD_LINK)) { + return false; + } + + nsCOMPtr<nsIURI> currentURI = mCurrentURI; + + nsresult rvURINew = aLoadState->URI()->GetRef(aState.mNewHash); + if (NS_SUCCEEDED(rvURINew)) { + rvURINew = aLoadState->URI()->GetHasRef(&aState.mNewURIHasRef); + } + + if (currentURI && NS_SUCCEEDED(rvURINew)) { + nsresult rvURIOld = currentURI->GetRef(aState.mCurrentHash); + if (NS_SUCCEEDED(rvURIOld)) { + rvURIOld = currentURI->GetHasRef(&aState.mCurrentURIHasRef); + } + if (NS_SUCCEEDED(rvURIOld)) { + if (NS_FAILED(currentURI->EqualsExceptRef(aLoadState->URI(), + &aState.mSameExceptHashes))) { + aState.mSameExceptHashes = false; + } + } + } + + if (!aState.mSameExceptHashes && currentURI && NS_SUCCEEDED(rvURINew)) { + // Maybe aLoadState->URI() came from the exposable form of currentURI? + nsCOMPtr<nsIURI> currentExposableURI = + nsIOService::CreateExposableURI(currentURI); + nsresult rvURIOld = currentExposableURI->GetRef(aState.mCurrentHash); + if (NS_SUCCEEDED(rvURIOld)) { + rvURIOld = currentExposableURI->GetHasRef(&aState.mCurrentURIHasRef); + } + if (NS_SUCCEEDED(rvURIOld)) { + if (NS_FAILED(currentExposableURI->EqualsExceptRef( + aLoadState->URI(), &aState.mSameExceptHashes))) { + aState.mSameExceptHashes = false; + } + // HTTPS-Only Mode upgrades schemes from http to https in Necko, hence we + // have to perform a special check here to avoid an actual navigation. If + // HTTPS-Only Mode is enabled and the two URIs are same-origin (modulo the + // fact that the new URI is currently http), then set mSameExceptHashes to + // true and only perform a fragment navigation. + if (!aState.mSameExceptHashes) { + nsCOMPtr<nsIChannel> docChannel = GetCurrentDocChannel(); + if (docChannel) { + nsCOMPtr<nsILoadInfo> docLoadInfo = docChannel->LoadInfo(); + if (!docLoadInfo->GetLoadErrorPage()) { + if (nsHTTPSOnlyUtils::IsEqualURIExceptSchemeAndRef( + currentExposableURI, aLoadState->URI(), docLoadInfo)) { + aState.mSameExceptHashes = true; + } + } + } + } + } + } + + if (mozilla::SessionHistoryInParent()) { + if (mActiveEntry && aLoadState->LoadIsFromSessionHistory()) { + aState.mHistoryNavBetweenSameDoc = mActiveEntry->SharesDocumentWith( + aLoadState->GetLoadingSessionHistoryInfo()->mInfo); + } + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell::IsSameDocumentNavigation %p NavBetweenSameDoc=%d", + this, aState.mHistoryNavBetweenSameDoc)); + } else { + if (mOSHE && aLoadState->LoadIsFromSessionHistory()) { + // We're doing a history load. + + mOSHE->SharesDocumentWith(aLoadState->SHEntry(), + &aState.mHistoryNavBetweenSameDoc); + } + } + + // A same document navigation happens when we navigate between two SHEntries + // for the same document. We do a same document navigation under two + // circumstances. Either + // + // a) we're navigating between two different SHEntries which share a + // document, or + // + // b) we're navigating to a new shentry whose URI differs from the + // current URI only in its hash, the new hash is non-empty, and + // we're not doing a POST. + // + // The restriction that the SHEntries in (a) must be different ensures + // that history.go(0) and the like trigger full refreshes, rather than + // same document navigations. + if (!mozilla::SessionHistoryInParent()) { + bool doSameDocumentNavigation = + (aState.mHistoryNavBetweenSameDoc && mOSHE != aLoadState->SHEntry()) || + (!aLoadState->SHEntry() && !aLoadState->PostDataStream() && + aState.mSameExceptHashes && aState.mNewURIHasRef); + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p NavBetweenSameDoc=%d is same doc = %d", this, + aState.mHistoryNavBetweenSameDoc, doSameDocumentNavigation)); + return doSameDocumentNavigation; + } + + if (aState.mHistoryNavBetweenSameDoc && + !aLoadState->GetLoadingSessionHistoryInfo()->mLoadingCurrentEntry) { + return true; + } + + MOZ_LOG( + gSHLog, LogLevel::Debug, + ("nsDocShell::IsSameDocumentNavigation %p !LoadIsFromSessionHistory=%s " + "!PostDataStream: %s mSameExceptHashes: %s mNewURIHasRef: %s", + this, !aLoadState->LoadIsFromSessionHistory() ? "true" : "false", + !aLoadState->PostDataStream() ? "true" : "false", + aState.mSameExceptHashes ? "true" : "false", + aState.mNewURIHasRef ? "true" : "false")); + return !aLoadState->LoadIsFromSessionHistory() && + !aLoadState->PostDataStream() && aState.mSameExceptHashes && + aState.mNewURIHasRef; +} + +nsresult nsDocShell::HandleSameDocumentNavigation( + nsDocShellLoadState* aLoadState, SameDocumentNavigationState& aState) { +#ifdef DEBUG + SameDocumentNavigationState state; + MOZ_ASSERT(IsSameDocumentNavigation(aLoadState, state)); +#endif + + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell::HandleSameDocumentNavigation %p %s -> %s", this, + mCurrentURI->GetSpecOrDefault().get(), + aLoadState->URI()->GetSpecOrDefault().get())); + + RefPtr<Document> doc = GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + doc->DoNotifyPossibleTitleChange(); + + nsCOMPtr<nsIURI> currentURI = mCurrentURI; + + // Save the position of the scrollers. + nsPoint scrollPos = GetCurScrollPos(); + + // Reset mLoadType to its original value once we exit this block, because this + // same document navigation might have started after a normal, network load, + // and we don't want to clobber its load type. See bug 737307. + AutoRestore<uint32_t> loadTypeResetter(mLoadType); + + // If a non-same-document-navigation (i.e., a network load) is pending, make + // this a replacement load, so that we don't add a SHEntry here and the + // network load goes into the SHEntry it expects to. + if (JustStartedNetworkLoad() && (aLoadState->LoadType() & LOAD_CMD_NORMAL)) { + mLoadType = LOAD_NORMAL_REPLACE; + } else { + mLoadType = aLoadState->LoadType(); + } + + mURIResultedInDocument = true; + + nsCOMPtr<nsISHEntry> oldLSHE = mLSHE; + + // we need to assign aLoadState->SHEntry() to mLSHE right here, so that on + // History loads, SetCurrentURI() called from OnNewURI() will send proper + // onLocationChange() notifications to the browser to update back/forward + // buttons. + SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(aLoadState->SHEntry()), + Nothing()); + UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> oldLoadingEntry; + mLoadingEntry.swap(oldLoadingEntry); + if (aLoadState->GetLoadingSessionHistoryInfo()) { + mLoadingEntry = MakeUnique<LoadingSessionHistoryInfo>( + *aLoadState->GetLoadingSessionHistoryInfo()); + mNeedToReportActiveAfterLoadingBecomesActive = false; + } + + // Set the doc's URI according to the new history entry's URI. + doc->SetDocumentURI(aLoadState->URI()); + + /* This is a anchor traversal within the same page. + * call OnNewURI() so that, this traversal will be + * recorded in session and global history. + */ + nsCOMPtr<nsIPrincipal> newURITriggeringPrincipal, newURIPrincipalToInherit, + newURIPartitionedPrincipalToInherit; + nsCOMPtr<nsIContentSecurityPolicy> newCsp; + if (mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) { + if (mozilla::SessionHistoryInParent()) { + newURITriggeringPrincipal = mActiveEntry->GetTriggeringPrincipal(); + newURIPrincipalToInherit = mActiveEntry->GetPrincipalToInherit(); + newURIPartitionedPrincipalToInherit = + mActiveEntry->GetPartitionedPrincipalToInherit(); + newCsp = mActiveEntry->GetCsp(); + } else { + newURITriggeringPrincipal = mOSHE->GetTriggeringPrincipal(); + newURIPrincipalToInherit = mOSHE->GetPrincipalToInherit(); + newURIPartitionedPrincipalToInherit = + mOSHE->GetPartitionedPrincipalToInherit(); + newCsp = mOSHE->GetCsp(); + } + } else { + newURITriggeringPrincipal = aLoadState->TriggeringPrincipal(); + newURIPrincipalToInherit = doc->NodePrincipal(); + newURIPartitionedPrincipalToInherit = doc->PartitionedPrincipal(); + newCsp = doc->GetCsp(); + } + + uint32_t locationChangeFlags = + GetSameDocumentNavigationFlags(aLoadState->URI()); + + // Pass true for aCloneSHChildren, since we're not + // changing documents here, so all of our subframes are + // still relevant to the new session history entry. + // + // It also makes OnNewURI(...) set LOCATION_CHANGE_SAME_DOCUMENT + // flag on firing onLocationChange(...). + // Anyway, aCloneSHChildren param is simply reflecting + // doSameDocumentNavigation in this scope. + // + // Note: we'll actually fire onLocationChange later, in order to preserve + // ordering of HistoryCommit() in the parent vs onLocationChange (bug + // 1668126) + bool locationChangeNeeded = + OnNewURI(aLoadState->URI(), nullptr, newURITriggeringPrincipal, + newURIPrincipalToInherit, newURIPartitionedPrincipalToInherit, + newCsp, false, true, true); + + nsCOMPtr<nsIInputStream> postData; + uint32_t cacheKey = 0; + + bool scrollRestorationIsManual = false; + if (!mozilla::SessionHistoryInParent()) { + if (mOSHE) { + /* save current position of scroller(s) (bug 59774) */ + mOSHE->SetScrollPosition(scrollPos.x, scrollPos.y); + scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual(); + // Get the postdata and page ident from the current page, if + // the new load is being done via normal means. Note that + // "normal means" can be checked for just by checking for + // LOAD_CMD_NORMAL, given the loadType and allowScroll check + // above -- it filters out some LOAD_CMD_NORMAL cases that we + // wouldn't want here. + if (aLoadState->LoadType() & LOAD_CMD_NORMAL) { + postData = mOSHE->GetPostData(); + cacheKey = mOSHE->GetCacheKey(); + } + + // Link our new SHEntry to the old SHEntry's back/forward + // cache data, since the two SHEntries correspond to the + // same document. + if (mLSHE) { + if (!aLoadState->LoadIsFromSessionHistory()) { + // If we're not doing a history load, scroll restoration + // should be inherited from the previous session history entry. + SetScrollRestorationIsManualOnHistoryEntry(mLSHE, + scrollRestorationIsManual); + } + mLSHE->AdoptBFCacheEntry(mOSHE); + } + } + } else { + if (mActiveEntry) { + mActiveEntry->SetScrollPosition(scrollPos.x, scrollPos.y); + if (mBrowsingContext) { + CollectWireframe(); + if (XRE_IsParentProcess()) { + SessionHistoryEntry* entry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetScrollPosition(scrollPos.x, scrollPos.y); + } + } else { + mozilla::Unused << ContentChild::GetSingleton() + ->SendSessionHistoryEntryScrollPosition( + mBrowsingContext, scrollPos.x, + scrollPos.y); + } + } + } + if (mLoadingEntry) { + if (!mLoadingEntry->mLoadIsFromSessionHistory) { + // If we're not doing a history load, scroll restoration + // should be inherited from the previous session history entry. + // XXX This needs most probably tweaks once fragment navigation is + // fixed to work with session-history-in-parent. + SetScrollRestorationIsManualOnHistoryEntry(nullptr, + scrollRestorationIsManual); + } + } + } + + // If we're doing a history load, use its scroll restoration state. + if (aLoadState->LoadIsFromSessionHistory()) { + if (mozilla::SessionHistoryInParent()) { + scrollRestorationIsManual = aLoadState->GetLoadingSessionHistoryInfo() + ->mInfo.GetScrollRestorationIsManual(); + } else { + scrollRestorationIsManual = + aLoadState->SHEntry()->GetScrollRestorationIsManual(); + } + } + + /* Assign mLSHE to mOSHE. This will either be a new entry created + * by OnNewURI() for normal loads or aLoadState->SHEntry() for history + * loads. + */ + if (!mozilla::SessionHistoryInParent()) { + if (mLSHE) { + SetHistoryEntryAndUpdateBC(Nothing(), Some<nsISHEntry*>(mLSHE)); + // Save the postData obtained from the previous page + // in to the session history entry created for the + // anchor page, so that any history load of the anchor + // page will restore the appropriate postData. + if (postData) { + mOSHE->SetPostData(postData); + } + + // Make sure we won't just repost without hitting the + // cache first + if (cacheKey != 0) { + mOSHE->SetCacheKey(cacheKey); + } + } + + /* Set the title for the SH entry for this target url so that + * SH menus in go/back/forward buttons won't be empty for this. + * Note, this happens on mOSHE (and mActiveEntry in the future) because of + * the code above. + * Note, when session history lives in the parent process, this does not + * update the title there. + */ + SetTitleOnHistoryEntry(false); + } else { + if (aLoadState->LoadIsFromSessionHistory()) { + MOZ_LOG( + gSHLog, LogLevel::Debug, + ("Moving the loading entry to the active entry on nsDocShell %p to " + "%s", + this, mLoadingEntry->mInfo.GetURI()->GetSpecOrDefault().get())); + bool hadActiveEntry = !!mActiveEntry; + + nsCOMPtr<nsILayoutHistoryState> currentLayoutHistoryState; + if (mActiveEntry) { + currentLayoutHistoryState = mActiveEntry->GetLayoutHistoryState(); + } + mActiveEntry = MakeUnique<SessionHistoryInfo>(mLoadingEntry->mInfo); + if (currentLayoutHistoryState) { + // Restore the existing nsILayoutHistoryState object, since it is + // possibly being used by the layout. When doing a new load, the + // shared state is copied from the existing active entry, so this + // special case is needed only with the history loads. + mActiveEntry->SetLayoutHistoryState(currentLayoutHistoryState); + } + + if (cacheKey != 0) { + mActiveEntry->SetCacheKey(cacheKey); + } + // We're passing in mCurrentURI, which could be null. SessionHistoryCommit + // does require a non-null uri if this is for a refresh load of the same + // URI, but in that case mCurrentURI won't be null here. + mBrowsingContext->SessionHistoryCommit( + *mLoadingEntry, mLoadType, mCurrentURI, hadActiveEntry, true, true, + /* No expiration update on the same document loads*/ + false, cacheKey); + // FIXME Need to set postdata. + + // Set the title for the SH entry for this target url so that + // SH menus in go/back/forward buttons won't be empty for this. + // Note, when session history lives in the parent process, this does not + // update the title there. + SetTitleOnHistoryEntry(false); + } else { + Maybe<bool> scrollRestorationIsManual; + if (mActiveEntry) { + scrollRestorationIsManual.emplace( + mActiveEntry->GetScrollRestorationIsManual()); + + // Get the postdata and page ident from the current page, if the new + // load is being done via normal means. Note that "normal means" can be + // checked for just by checking for LOAD_CMD_NORMAL, given the loadType + // and allowScroll check above -- it filters out some LOAD_CMD_NORMAL + // cases that we wouldn't want here. + if (aLoadState->LoadType() & LOAD_CMD_NORMAL) { + postData = mActiveEntry->GetPostData(); + cacheKey = mActiveEntry->GetCacheKey(); + } + } + + MOZ_LOG(gSHLog, LogLevel::Debug, + ("Creating an active entry on nsDocShell %p to %s", this, + aLoadState->URI()->GetSpecOrDefault().get())); + if (mActiveEntry) { + mActiveEntry = + MakeUnique<SessionHistoryInfo>(*mActiveEntry, aLoadState->URI()); + } else { + mActiveEntry = MakeUnique<SessionHistoryInfo>( + aLoadState->URI(), newURITriggeringPrincipal, + newURIPrincipalToInherit, newURIPartitionedPrincipalToInherit, + newCsp, mContentTypeHint); + } + + // Save the postData obtained from the previous page in to the session + // history entry created for the anchor page, so that any history load of + // the anchor page will restore the appropriate postData. + if (postData) { + mActiveEntry->SetPostData(postData); + } + + // Make sure we won't just repost without hitting the + // cache first + if (cacheKey != 0) { + mActiveEntry->SetCacheKey(cacheKey); + } + + // Set the title for the SH entry for this target url so that + // SH menus in go/back/forward buttons won't be empty for this. + mActiveEntry->SetTitle(mTitle); + + if (scrollRestorationIsManual.isSome()) { + mActiveEntry->SetScrollRestorationIsManual( + scrollRestorationIsManual.value()); + } + + if (LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) { + mBrowsingContext->ReplaceActiveSessionHistoryEntry(mActiveEntry.get()); + } else { + mBrowsingContext->IncrementHistoryEntryCountForBrowsingContext(); + // FIXME We should probably just compute mChildOffset in the parent + // instead of passing it over IPC here. + mBrowsingContext->SetActiveSessionHistoryEntry( + Some(scrollPos), mActiveEntry.get(), mLoadType, cacheKey); + // FIXME Do we need to update mPreviousEntryIndex and mLoadedEntryIndex? + } + } + } + + if (locationChangeNeeded) { + FireOnLocationChange(this, nullptr, aLoadState->URI(), locationChangeFlags); + } + + /* Restore the original LSHE if we were loading something + * while same document navigation was initiated. + */ + SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(oldLSHE), Nothing()); + mLoadingEntry.swap(oldLoadingEntry); + + /* Set the title for the Global History entry for this anchor url. + */ + UpdateGlobalHistoryTitle(aLoadState->URI()); + + SetDocCurrentStateObj(mOSHE, mActiveEntry.get()); + + // Inform the favicon service that the favicon for oldURI also + // applies to aLoadState->URI(). + CopyFavicon(currentURI, aLoadState->URI(), UsePrivateBrowsing()); + + RefPtr<nsGlobalWindowOuter> scriptGlobal = mScriptGlobal; + RefPtr<nsGlobalWindowInner> win = + scriptGlobal ? scriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + + // ScrollToAnchor doesn't necessarily cause us to scroll the window; + // the function decides whether a scroll is appropriate based on the + // arguments it receives. But even if we don't end up scrolling, + // ScrollToAnchor performs other important tasks, such as informing + // the presShell that we have a new hash. See bug 680257. + nsresult rv = ScrollToAnchor(aState.mCurrentURIHasRef, aState.mNewURIHasRef, + aState.mNewHash, aLoadState->LoadType()); + NS_ENSURE_SUCCESS(rv, rv); + + /* restore previous position of scroller(s), if we're moving + * back in history (bug 59774) + */ + nscoord bx = 0; + nscoord by = 0; + bool needsScrollPosUpdate = false; + if ((mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) && + (aLoadState->LoadType() == LOAD_HISTORY || + aLoadState->LoadType() == LOAD_RELOAD_NORMAL) && + !scrollRestorationIsManual) { + needsScrollPosUpdate = true; + if (mozilla::SessionHistoryInParent()) { + mActiveEntry->GetScrollPosition(&bx, &by); + } else { + mOSHE->GetScrollPosition(&bx, &by); + } + } + + // Dispatch the popstate and hashchange events, as appropriate. + // + // The event dispatch below can cause us to re-enter script and + // destroy the docshell, nulling out mScriptGlobal. Hold a stack + // reference to avoid null derefs. See bug 914521. + if (win) { + // Fire a hashchange event URIs differ, and only in their hashes. + bool doHashchange = aState.mSameExceptHashes && + (aState.mCurrentURIHasRef != aState.mNewURIHasRef || + !aState.mCurrentHash.Equals(aState.mNewHash)); + + if (aState.mHistoryNavBetweenSameDoc || doHashchange) { + win->DispatchSyncPopState(); + } + + if (needsScrollPosUpdate && win->HasActiveDocument()) { + SetCurScrollPosEx(bx, by); + } + + if (doHashchange) { + // Note that currentURI hasn't changed because it's on the + // stack, so we can just use it directly as the old URI. + win->DispatchAsyncHashchange(currentURI, aLoadState->URI()); + } + } + + return NS_OK; +} + +static bool NavigationShouldTakeFocus(nsDocShell* aDocShell, + nsDocShellLoadState* aLoadState) { + if (!aLoadState->AllowFocusMove()) { + return false; + } + if (!aLoadState->HasValidUserGestureActivation()) { + return false; + } + const auto& sourceBC = aLoadState->SourceBrowsingContext(); + if (!sourceBC || !sourceBC->IsActive()) { + // If the navigation didn't come from a foreground tab, then we don't steal + // focus. + return false; + } + auto* bc = aDocShell->GetBrowsingContext(); + if (sourceBC.get() == bc) { + // If it comes from the same tab / frame, don't steal focus either. + return false; + } + auto* fm = nsFocusManager::GetFocusManager(); + if (fm && bc->IsActive() && fm->IsInActiveWindow(bc)) { + // If we're already on the foreground tab of the foreground window, then we + // don't need to do this. This helps to e.g. not steal focus from the + // browser chrome unnecessarily. + return false; + } + if (auto* doc = aDocShell->GetExtantDocument()) { + if (doc->IsInitialDocument()) { + // If we're the initial load for the browsing context, the browser + // chrome determines what to focus. This is important because the + // browser chrome may want to e.g focus the url-bar + return false; + } + } + // Take loadDivertedInBackground into account so the behavior would be the + // same as how the tab first opened. + return !Preferences::GetBool("browser.tabs.loadDivertedInBackground", false); +} + +nsresult nsDocShell::InternalLoad(nsDocShellLoadState* aLoadState, + Maybe<uint32_t> aCacheKey) { + MOZ_ASSERT(aLoadState, "need a load state!"); + MOZ_ASSERT(aLoadState->TriggeringPrincipal(), + "need a valid TriggeringPrincipal"); + + if (!aLoadState->TriggeringPrincipal()) { + MOZ_ASSERT(false, "InternalLoad needs a valid triggeringPrincipal"); + return NS_ERROR_FAILURE; + } + if (NS_WARN_IF(mBrowsingContext->GetPendingInitialization())) { + return NS_ERROR_NOT_AVAILABLE; + } + + const bool shouldTakeFocus = NavigationShouldTakeFocus(this, aLoadState); + + mOriginalUriString.Truncate(); + + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, + ("DOCSHELL %p InternalLoad %s\n", this, + aLoadState->URI()->GetSpecOrDefault().get())); + + NS_ENSURE_TRUE(IsValidLoadType(aLoadState->LoadType()), NS_ERROR_INVALID_ARG); + + // Cancel loads coming from Docshells that are being destroyed. + if (mIsBeingDestroyed) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = EnsureScriptEnvironment(); + if (NS_FAILED(rv)) { + return rv; + } + + // If we have a target to move to, do that now. + if (!aLoadState->Target().IsEmpty()) { + return PerformRetargeting(aLoadState); + } + + if (aLoadState->TargetBrowsingContext().IsNull()) { + aLoadState->SetTargetBrowsingContext(GetBrowsingContext()); + } + + MOZ_DIAGNOSTIC_ASSERT( + aLoadState->TargetBrowsingContext() == GetBrowsingContext(), + "Load must be targeting this BrowsingContext"); + + MOZ_TRY(CheckDisallowedJavascriptLoad(aLoadState)); + + // If we don't have a target, we're loading into ourselves, and our load + // delegate may want to intercept that load. + SameDocumentNavigationState sameDocumentNavigationState; + bool sameDocument = + IsSameDocumentNavigation(aLoadState, sameDocumentNavigationState) && + !aLoadState->GetPendingRedirectedChannel(); + + // Note: We do this check both here and in BrowsingContext:: + // LoadURI/InternalLoad, since document-specific sandbox flags are only + // available in the process triggering the load, and we don't want the target + // process to have to trust the triggering process to do the appropriate + // checks for the BrowsingContext's sandbox flags. + MOZ_TRY(mBrowsingContext->CheckSandboxFlags(aLoadState)); + + NS_ENSURE_STATE(!HasUnloadedParent()); + + rv = CheckLoadingPermissions(); + if (NS_FAILED(rv)) { + return rv; + } + + if (mFiredUnloadEvent) { + if (IsOKToLoadURI(aLoadState->URI())) { + MOZ_ASSERT(aLoadState->Target().IsEmpty(), + "Shouldn't have a window target here!"); + + // If this is a replace load, make whatever load triggered + // the unload event also a replace load, so we don't + // create extra history entries. + if (LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(), + LOAD_FLAGS_REPLACE_HISTORY)) { + mLoadType = LOAD_NORMAL_REPLACE; + } + + // Do this asynchronously + nsCOMPtr<nsIRunnable> ev = new InternalLoadEvent(this, aLoadState); + return Dispatch(TaskCategory::Other, ev.forget()); + } + + // Just ignore this load attempt + return NS_OK; + } + + // If we are loading a URI that should inherit a security context (basically + // javascript: at this point), and the caller has said that principal + // inheritance is allowed, there are a few possible cases: + // + // 1) We are provided with the principal to inherit. In that case, we just use + // it. + // + // 2) The load is coming from some other application. In this case we don't + // want to inherit from whatever document we have loaded now, since the + // load is unrelated to it. + // + // 3) It's a load from our application, but does not provide an explicit + // principal to inherit. In that case, we want to inherit the principal of + // our current document, or of our parent document (if any) if we don't + // have a current document. + { + bool inherits; + + if (!aLoadState->HasLoadFlags(LOAD_FLAGS_FROM_EXTERNAL) && + !aLoadState->PrincipalToInherit() && + (aLoadState->HasInternalLoadFlags( + INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL)) && + NS_SUCCEEDED(nsContentUtils::URIInheritsSecurityContext( + aLoadState->URI(), &inherits)) && + inherits) { + aLoadState->SetPrincipalToInherit(GetInheritedPrincipal(true)); + } + // If principalToInherit is still null (e.g. if some of the conditions of + // were not satisfied), then no inheritance of any sort will happen: the + // load will just get a principal based on the URI being loaded. + } + + // If this docshell is owned by a frameloader, make sure to cancel + // possible frameloader initialization before loading a new page. + nsCOMPtr<nsIDocShellTreeItem> parent = GetInProcessParentDocshell(); + if (parent) { + RefPtr<Document> doc = parent->GetDocument(); + if (doc) { + doc->TryCancelFrameLoaderInitialization(this); + } + } + + // Before going any further vet loads initiated by external programs. + if (aLoadState->HasLoadFlags(LOAD_FLAGS_FROM_EXTERNAL)) { + MOZ_DIAGNOSTIC_ASSERT(aLoadState->LoadType() == LOAD_NORMAL); + + // Disallow external chrome: loads targetted at content windows + if (SchemeIsChrome(aLoadState->URI())) { + NS_WARNING("blocked external chrome: url -- use '--chrome' option"); + return NS_ERROR_FAILURE; + } + + // clear the decks to prevent context bleed-through (bug 298255) + rv = CreateAboutBlankContentViewer(nullptr, nullptr, nullptr, nullptr, + /* aIsInitialDocument */ false); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + } + + mAllowKeywordFixup = aLoadState->HasInternalLoadFlags( + INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP); + mURIResultedInDocument = false; // reset the clock... + + // See if this is actually a load between two history entries for the same + // document. If the process fails, or if we successfully navigate within the + // same document, return. + if (sameDocument) { + nsresult rv = + HandleSameDocumentNavigation(aLoadState, sameDocumentNavigationState); + NS_ENSURE_SUCCESS(rv, rv); + if (shouldTakeFocus) { + mBrowsingContext->Focus(CallerType::System, IgnoreErrors()); + } + return rv; + } + + // mContentViewer->PermitUnload can destroy |this| docShell, which + // causes the next call of CanSavePresentation to crash. + // Hold onto |this| until we return, to prevent a crash from happening. + // (bug#331040) + nsCOMPtr<nsIDocShell> kungFuDeathGrip(this); + + // Don't init timing for javascript:, since it generally doesn't + // actually start a load or anything. If it does, we'll init + // timing then, from OnStateChange. + + // XXXbz mTiming should know what channel it's for, so we don't + // need this hackery. + bool toBeReset = false; + bool isJavaScript = SchemeIsJavascript(aLoadState->URI()); + + if (!isJavaScript) { + toBeReset = MaybeInitTiming(); + } + bool isNotDownload = aLoadState->FileName().IsVoid(); + if (mTiming && isNotDownload) { + mTiming->NotifyBeforeUnload(); + } + // Check if the page doesn't want to be unloaded. The javascript: + // protocol handler deals with this for javascript: URLs. + if (!isJavaScript && isNotDownload && + !aLoadState->NotifiedBeforeUnloadListeners() && mContentViewer) { + bool okToUnload; + + // Check if request is exempted from HTTPSOnlyMode and if https-first is + // enabled, if so it means: + // * https-first failed to upgrade request to https + // * we already asked for permission to unload and the user accepted + // otherwise we wouldn't be here. + bool isPrivateWin = GetOriginAttributes().mPrivateBrowsingId > 0; + bool isHistoryOrReload = false; + uint32_t loadType = aLoadState->LoadType(); + + // Check if request is a reload. + if (loadType == LOAD_RELOAD_NORMAL || + loadType == LOAD_RELOAD_BYPASS_CACHE || + loadType == LOAD_RELOAD_BYPASS_PROXY || + loadType == LOAD_RELOAD_BYPASS_PROXY_AND_CACHE || + loadType == LOAD_HISTORY) { + isHistoryOrReload = true; + } + + // If it isn't a reload, the request already failed to be upgraded and + // https-first is enabled then don't ask the user again for permission to + // unload and just unload. + if (!isHistoryOrReload && aLoadState->IsExemptFromHTTPSOnlyMode() && + nsHTTPSOnlyUtils::IsHttpsFirstModeEnabled(isPrivateWin)) { + rv = mContentViewer->PermitUnload( + nsIContentViewer::PermitUnloadAction::eDontPromptAndUnload, + &okToUnload); + } else { + rv = mContentViewer->PermitUnload(&okToUnload); + } + + if (NS_SUCCEEDED(rv) && !okToUnload) { + // The user chose not to unload the page, interrupt the + // load. + MaybeResetInitTiming(toBeReset); + return NS_OK; + } + } + + if (mTiming && isNotDownload) { + mTiming->NotifyUnloadAccepted(mCurrentURI); + } + + // In e10s, in the parent process, we refuse to load anything other than + // "safe" resources that we ship or trust enough to give "special" URLs. + // Similar check will be performed by the ParentProcessDocumentChannel if in + // use. + if (XRE_IsE10sParentProcess() && + !DocumentChannel::CanUseDocumentChannel(aLoadState->URI()) && + !CanLoadInParentProcess(aLoadState->URI())) { + return NS_ERROR_FAILURE; + } + + // Whenever a top-level browsing context is navigated, the user agent MUST + // lock the orientation of the document to the document's default + // orientation. We don't explicitly check for a top-level browsing context + // here because orientation is only set on top-level browsing contexts. + if (mBrowsingContext->GetOrientationLock() != hal::ScreenOrientation::None) { + MOZ_ASSERT(mBrowsingContext->IsTop()); + MOZ_ALWAYS_SUCCEEDS( + mBrowsingContext->SetOrientationLock(hal::ScreenOrientation::None)); + if (mBrowsingContext->IsActive()) { + ScreenOrientation::UpdateActiveOrientationLock( + hal::ScreenOrientation::None); + } + } + + // Check for saving the presentation here, before calling Stop(). + // This is necessary so that we can catch any pending requests. + // Since the new request has not been created yet, we pass null for the + // new request parameter. + // Also pass nullptr for the document, since it doesn't affect the return + // value for our purposes here. + bool savePresentation = + CanSavePresentation(aLoadState->LoadType(), nullptr, nullptr, + /* aReportBFCacheComboTelemetry */ true); + + // nsDocShell::CanSavePresentation is for non-SHIP version only. Do a + // separate check for SHIP so that we know if there are ongoing requests + // before calling Stop() below. + if (mozilla::SessionHistoryInParent()) { + Document* document = GetDocument(); + uint32_t flags = 0; + if (document && !document->CanSavePresentation(nullptr, flags, true)) { + // This forces some flags into the WindowGlobalParent's mBFCacheStatus, + // which we'll then use in CanonicalBrowsingContext::AllowedInBFCache, + // and in particular we'll store BFCacheStatus::REQUEST if needed. + // Also, we want to report all the flags to the parent process here (and + // not just BFCacheStatus::NOT_ALLOWED), so that it can update the + // telemetry data correctly. + document->DisallowBFCaching(flags); + } + } + + // Don't stop current network activity for javascript: URL's since + // they might not result in any data, and thus nothing should be + // stopped in those cases. In the case where they do result in + // data, the javascript: URL channel takes care of stopping + // current network activity. + if (!isJavaScript && isNotDownload) { + // Stop any current network activity. + // Also stop content if this is a zombie doc. otherwise + // the onload will be delayed by other loads initiated in the + // background by the first document that + // didn't fully load before the next load was initiated. + // If not a zombie, don't stop content until data + // starts arriving from the new URI... + + if ((mContentViewer && mContentViewer->GetPreviousViewer()) || + LOAD_TYPE_HAS_FLAGS(aLoadState->LoadType(), LOAD_FLAGS_STOP_CONTENT)) { + rv = Stop(nsIWebNavigation::STOP_ALL); + } else { + rv = Stop(nsIWebNavigation::STOP_NETWORK); + } + + if (NS_FAILED(rv)) { + return rv; + } + } + + mLoadType = aLoadState->LoadType(); + + // aLoadState->SHEntry() should be assigned to mLSHE, only after Stop() has + // been called. But when loading an error page, do not clear the + // mLSHE for the real page. + if (mLoadType != LOAD_ERROR_PAGE) { + SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(aLoadState->SHEntry()), + Nothing()); + if (aLoadState->LoadIsFromSessionHistory() && + !mozilla::SessionHistoryInParent()) { + // We're making history navigation or a reload. Make sure our history ID + // points to the same ID as SHEntry's docshell ID. + nsID historyID = {}; + aLoadState->SHEntry()->GetDocshellID(historyID); + + Unused << mBrowsingContext->SetHistoryID(historyID); + } + } + + mSavingOldViewer = savePresentation; + + // If we have a saved content viewer in history, restore and show it now. + if (aLoadState->LoadIsFromSessionHistory() && + (mLoadType & LOAD_CMD_HISTORY)) { + // https://html.spec.whatwg.org/#history-traversal: + // To traverse the history + // "If entry has a different Document object than the current entry, then + // run the following substeps: Remove any tasks queued by the history + // traversal task source..." + // Same document object case was handled already above with + // HandleSameDocumentNavigation call. + RefPtr<ChildSHistory> shistory = GetRootSessionHistory(); + if (shistory) { + shistory->RemovePendingHistoryNavigations(); + } + if (!mozilla::SessionHistoryInParent()) { + // It's possible that the previous viewer of mContentViewer is the + // viewer that will end up in aLoadState->SHEntry() when it gets closed. + // If that's the case, we need to go ahead and force it into its shentry + // so we can restore it. + if (mContentViewer) { + nsCOMPtr<nsIContentViewer> prevViewer = + mContentViewer->GetPreviousViewer(); + if (prevViewer) { +#ifdef DEBUG + nsCOMPtr<nsIContentViewer> prevPrevViewer = + prevViewer->GetPreviousViewer(); + NS_ASSERTION(!prevPrevViewer, "Should never have viewer chain here"); +#endif + nsCOMPtr<nsISHEntry> viewerEntry; + prevViewer->GetHistoryEntry(getter_AddRefs(viewerEntry)); + if (viewerEntry == aLoadState->SHEntry()) { + // Make sure this viewer ends up in the right place + mContentViewer->SetPreviousViewer(nullptr); + prevViewer->Destroy(); + } + } + } + nsCOMPtr<nsISHEntry> oldEntry = mOSHE; + bool restoring; + rv = RestorePresentation(aLoadState->SHEntry(), &restoring); + if (restoring) { + Telemetry::Accumulate(Telemetry::BFCACHE_PAGE_RESTORED, true); + return rv; + } + Telemetry::Accumulate(Telemetry::BFCACHE_PAGE_RESTORED, false); + + // We failed to restore the presentation, so clean up. + // Both the old and new history entries could potentially be in + // an inconsistent state. + if (NS_FAILED(rv)) { + if (oldEntry) { + oldEntry->SyncPresentationState(); + } + + aLoadState->SHEntry()->SyncPresentationState(); + } + } + } + + bool isTopLevelDoc = mBrowsingContext->IsTopContent(); + + OriginAttributes attrs = GetOriginAttributes(); + attrs.SetFirstPartyDomain(isTopLevelDoc, aLoadState->URI()); + + PredictorLearn(aLoadState->URI(), nullptr, + nsINetworkPredictor::LEARN_LOAD_TOPLEVEL, attrs); + PredictorPredict(aLoadState->URI(), nullptr, + nsINetworkPredictor::PREDICT_LOAD, attrs, nullptr); + + nsCOMPtr<nsIRequest> req; + rv = DoURILoad(aLoadState, aCacheKey, getter_AddRefs(req)); + + if (NS_SUCCEEDED(rv)) { + if (shouldTakeFocus) { + mBrowsingContext->Focus(CallerType::System, IgnoreErrors()); + } + } + + if (NS_FAILED(rv)) { + nsCOMPtr<nsIChannel> chan(do_QueryInterface(req)); + UnblockEmbedderLoadEventForFailure(); + if (DisplayLoadError(rv, aLoadState->URI(), nullptr, chan) && + // FIXME: At this point code was using internal load flags, but checking + // non-internal load flags? + aLoadState->HasLoadFlags(LOAD_FLAGS_ERROR_LOAD_CHANGES_RV)) { + return NS_ERROR_LOAD_SHOWED_ERRORPAGE; + } + + // We won't report any error if this is an unknown protocol error. The + // reason behind this is that it will allow enumeration of external + // protocols if we report an error for each unknown protocol. + if (NS_ERROR_UNKNOWN_PROTOCOL == rv) { + return NS_OK; + } + } + + return rv; +} + +/* static */ +bool nsDocShell::CanLoadInParentProcess(nsIURI* aURI) { + nsCOMPtr<nsIURI> uri = aURI; + // In e10s, in the parent process, we refuse to load anything other than + // "safe" resources that we ship or trust enough to give "special" URLs. + bool canLoadInParent = false; + if (NS_SUCCEEDED(NS_URIChainHasFlags( + uri, nsIProtocolHandler::URI_IS_UI_RESOURCE, &canLoadInParent)) && + canLoadInParent) { + // We allow UI resources. + return true; + } + // For about: and extension-based URIs, which don't get + // URI_IS_UI_RESOURCE, first remove layers of view-source:, if present. + while (uri && uri->SchemeIs("view-source")) { + nsCOMPtr<nsINestedURI> nested = do_QueryInterface(uri); + if (nested) { + nested->GetInnerURI(getter_AddRefs(uri)); + } else { + break; + } + } + // Allow about: URIs, and allow moz-extension ones if we're running + // extension content in the parent process. + if (!uri || uri->SchemeIs("about") || + (!StaticPrefs::extensions_webextensions_remote() && + uri->SchemeIs("moz-extension"))) { + return true; + } +#ifdef MOZ_THUNDERBIRD + if (uri->SchemeIs("imap") || uri->SchemeIs("mailbox") || + uri->SchemeIs("news") || uri->SchemeIs("nntp") || + uri->SchemeIs("snews")) { + return true; + } +#endif + nsAutoCString scheme; + uri->GetScheme(scheme); + // Allow ext+foo URIs (extension-registered custom protocols). See + // https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/protocol_handlers + if (StringBeginsWith(scheme, "ext+"_ns) && + !StaticPrefs::extensions_webextensions_remote()) { + return true; + } + // Final exception for some legacy automated tests: + if (xpc::IsInAutomation() && + StaticPrefs::security_allow_unsafe_parent_loads()) { + return true; + } + return false; +} + +nsIPrincipal* nsDocShell::GetInheritedPrincipal( + bool aConsiderCurrentDocument, bool aConsiderPartitionedPrincipal) { + RefPtr<Document> document; + bool inheritedFromCurrent = false; + + if (aConsiderCurrentDocument && mContentViewer) { + document = mContentViewer->GetDocument(); + inheritedFromCurrent = true; + } + + if (!document) { + nsCOMPtr<nsIDocShellTreeItem> parentItem; + GetInProcessSameTypeParent(getter_AddRefs(parentItem)); + if (parentItem) { + document = parentItem->GetDocument(); + } + } + + if (!document) { + if (!aConsiderCurrentDocument) { + return nullptr; + } + + // Make sure we end up with _something_ as the principal no matter + // what.If this fails, we'll just get a null docViewer and bail. + EnsureContentViewer(); + if (!mContentViewer) { + return nullptr; + } + document = mContentViewer->GetDocument(); + } + + //-- Get the document's principal + if (document) { + nsIPrincipal* docPrincipal = aConsiderPartitionedPrincipal + ? document->PartitionedPrincipal() + : document->NodePrincipal(); + + // Don't allow loads in typeContent docShells to inherit the system + // principal from existing documents. + if (inheritedFromCurrent && mItemType == typeContent && + docPrincipal->IsSystemPrincipal()) { + return nullptr; + } + + return docPrincipal; + } + + return nullptr; +} + +/* static */ nsresult nsDocShell::CreateRealChannelForDocument( + nsIChannel** aChannel, nsIURI* aURI, nsILoadInfo* aLoadInfo, + nsIInterfaceRequestor* aCallbacks, nsLoadFlags aLoadFlags, + const nsAString& aSrcdoc, nsIURI* aBaseURI) { + nsCOMPtr<nsIChannel> channel; + if (aSrcdoc.IsVoid()) { + MOZ_TRY(NS_NewChannelInternal(getter_AddRefs(channel), aURI, aLoadInfo, + nullptr, // PerformanceStorage + nullptr, // loadGroup + aCallbacks, aLoadFlags)); + + if (aBaseURI) { + nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(channel); + if (vsc) { + MOZ_ALWAYS_SUCCEEDS(vsc->SetBaseURI(aBaseURI)); + } + } + } else if (SchemeIsViewSource(aURI)) { + // Instantiate view source handler protocol, if it doesn't exist already. + nsCOMPtr<nsIIOService> io(do_GetIOService()); + MOZ_ASSERT(io); + nsCOMPtr<nsIProtocolHandler> handler; + nsresult rv = + io->GetProtocolHandler("view-source", getter_AddRefs(handler)); + if (NS_FAILED(rv)) { + return rv; + } + + nsViewSourceHandler* vsh = nsViewSourceHandler::GetInstance(); + if (!vsh) { + return NS_ERROR_FAILURE; + } + + MOZ_TRY(vsh->NewSrcdocChannel(aURI, aBaseURI, aSrcdoc, aLoadInfo, + getter_AddRefs(channel))); + } else { + MOZ_TRY(NS_NewInputStreamChannelInternal(getter_AddRefs(channel), aURI, + aSrcdoc, "text/html"_ns, aLoadInfo, + true)); + nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(channel); + MOZ_ASSERT(isc); + isc->SetBaseURI(aBaseURI); + } + + if (aLoadFlags != nsIRequest::LOAD_NORMAL) { + nsresult rv = channel->SetLoadFlags(aLoadFlags); + NS_ENSURE_SUCCESS(rv, rv); + } + + channel.forget(aChannel); + return NS_OK; +} + +/* static */ bool nsDocShell::CreateAndConfigureRealChannelForLoadState( + BrowsingContext* aBrowsingContext, nsDocShellLoadState* aLoadState, + LoadInfo* aLoadInfo, nsIInterfaceRequestor* aCallbacks, + nsDocShell* aDocShell, const OriginAttributes& aOriginAttributes, + nsLoadFlags aLoadFlags, uint32_t aCacheKey, nsresult& aRv, + nsIChannel** aChannel) { + MOZ_ASSERT(aLoadInfo); + + nsString srcdoc = VoidString(); + bool isSrcdoc = + aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_IS_SRCDOC); + if (isSrcdoc) { + srcdoc = aLoadState->SrcdocData(); + } + + aLoadInfo->SetTriggeringRemoteType( + aLoadState->GetEffectiveTriggeringRemoteType()); + + if (aLoadState->PrincipalToInherit()) { + aLoadInfo->SetPrincipalToInherit(aLoadState->PrincipalToInherit()); + } + aLoadInfo->SetLoadTriggeredFromExternal( + aLoadState->HasLoadFlags(LOAD_FLAGS_FROM_EXTERNAL)); + aLoadInfo->SetForceAllowDataURI(aLoadState->HasInternalLoadFlags( + INTERNAL_LOAD_FLAGS_FORCE_ALLOW_DATA_URI)); + aLoadInfo->SetOriginalFrameSrcLoad( + aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_ORIGINAL_FRAME_SRC)); + + bool inheritAttrs = false; + if (aLoadState->PrincipalToInherit()) { + inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal( + aLoadState->PrincipalToInherit(), aLoadState->URI(), + true, // aInheritForAboutBlank + isSrcdoc); + } + + // Strip the target query parameters before creating the channel. + aLoadState->MaybeStripTrackerQueryStrings(aBrowsingContext); + + OriginAttributes attrs; + + // Inherit origin attributes from PrincipalToInherit if inheritAttrs is + // true. Otherwise we just use the origin attributes from docshell. + if (inheritAttrs) { + MOZ_ASSERT(aLoadState->PrincipalToInherit(), + "We should have PrincipalToInherit here."); + attrs = aLoadState->PrincipalToInherit()->OriginAttributesRef(); + // If firstPartyIsolation is not enabled, then PrincipalToInherit should + // have the same origin attributes with docshell. + MOZ_ASSERT_IF(!OriginAttributes::IsFirstPartyEnabled(), + attrs == aOriginAttributes); + } else { + attrs = aOriginAttributes; + attrs.SetFirstPartyDomain(IsTopLevelDoc(aBrowsingContext, aLoadInfo), + aLoadState->URI()); + } + + aRv = aLoadInfo->SetOriginAttributes(attrs); + if (NS_WARN_IF(NS_FAILED(aRv))) { + return false; + } + + if (aLoadState->GetIsFromProcessingFrameAttributes()) { + aLoadInfo->SetIsFromProcessingFrameAttributes(); + } + + // Propagate the IsFormSubmission flag to the loadInfo. + if (aLoadState->IsFormSubmission()) { + aLoadInfo->SetIsFormSubmission(true); + } + + aLoadInfo->SetUnstrippedURI(aLoadState->GetUnstrippedURI()); + + nsCOMPtr<nsIChannel> channel; + aRv = CreateRealChannelForDocument(getter_AddRefs(channel), aLoadState->URI(), + aLoadInfo, aCallbacks, aLoadFlags, srcdoc, + aLoadState->BaseURI()); + NS_ENSURE_SUCCESS(aRv, false); + + if (!channel) { + return false; + } + + // If the HTTPS-Only mode is enabled, every insecure request gets upgraded to + // HTTPS by default. This behavior can be disabled through the loadinfo flag + // HTTPS_ONLY_EXEMPT. + nsHTTPSOnlyUtils::TestSitePermissionAndPotentiallyAddExemption(channel); + + // hack + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal( + do_QueryInterface(channel)); + nsCOMPtr<nsIURI> referrer; + nsIReferrerInfo* referrerInfo = aLoadState->GetReferrerInfo(); + if (referrerInfo) { + referrerInfo->GetOriginalReferrer(getter_AddRefs(referrer)); + } + if (httpChannelInternal) { + if (aLoadState->HasInternalLoadFlags( + INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES)) { + aRv = httpChannelInternal->SetThirdPartyFlags( + nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW); + MOZ_ASSERT(NS_SUCCEEDED(aRv)); + } + if (aLoadState->FirstParty()) { + aRv = httpChannelInternal->SetDocumentURI(aLoadState->URI()); + MOZ_ASSERT(NS_SUCCEEDED(aRv)); + } else { + aRv = httpChannelInternal->SetDocumentURI(referrer); + MOZ_ASSERT(NS_SUCCEEDED(aRv)); + } + aRv = httpChannelInternal->SetRedirectMode( + nsIHttpChannelInternal::REDIRECT_MODE_MANUAL); + MOZ_ASSERT(NS_SUCCEEDED(aRv)); + } + + if (httpChannel) { + if (aLoadState->HeadersStream()) { + aRv = AddHeadersToChannel(aLoadState->HeadersStream(), httpChannel); + } + // Set the referrer explicitly + // Referrer is currenly only set for link clicks here. + if (referrerInfo) { + aRv = httpChannel->SetReferrerInfo(referrerInfo); + MOZ_ASSERT(NS_SUCCEEDED(aRv)); + } + + // Mark the http channel as UrgentStart for top level document loading in + // active tab. + if (IsUrgentStart(aBrowsingContext, aLoadInfo, aLoadState->LoadType())) { + nsCOMPtr<nsIClassOfService> cos(do_QueryInterface(channel)); + if (cos) { + cos->AddClassFlags(nsIClassOfService::UrgentStart); + } + } + } + + channel->SetOriginalURI(aLoadState->OriginalURI() ? aLoadState->OriginalURI() + : aLoadState->URI()); + + const nsACString& typeHint = aLoadState->TypeHint(); + if (!typeHint.IsVoid()) { + channel->SetContentType(typeHint); + } + + const nsAString& fileName = aLoadState->FileName(); + if (!fileName.IsVoid()) { + aRv = channel->SetContentDisposition(nsIChannel::DISPOSITION_ATTACHMENT); + NS_ENSURE_SUCCESS(aRv, false); + if (!fileName.IsEmpty()) { + aRv = channel->SetContentDispositionFilename(fileName); + NS_ENSURE_SUCCESS(aRv, false); + } + } + + if (nsCOMPtr<nsIWritablePropertyBag2> props = do_QueryInterface(channel)) { + nsCOMPtr<nsIURI> referrer; + nsIReferrerInfo* referrerInfo = aLoadState->GetReferrerInfo(); + if (referrerInfo) { + referrerInfo->GetOriginalReferrer(getter_AddRefs(referrer)); + } + // save true referrer for those who need it (e.g. xpinstall whitelisting) + // Currently only http and ftp channels support this. + props->SetPropertyAsInterface(u"docshell.internalReferrer"_ns, referrer); + + if (aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_FIRST_LOAD)) { + props->SetPropertyAsBool(u"docshell.newWindowTarget"_ns, true); + } + } + + nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel)); + auto loadType = aLoadState->LoadType(); + + if (loadType == LOAD_RELOAD_NORMAL && + StaticPrefs:: + browser_soft_reload_only_force_validate_top_level_document()) { + nsCOMPtr<nsICacheInfoChannel> cachingChannel = do_QueryInterface(channel); + if (cachingChannel) { + cachingChannel->SetForceValidateCacheContent(true); + } + } + + // figure out if we need to set the post data stream on the channel... + if (aLoadState->PostDataStream()) { + if (nsCOMPtr<nsIFormPOSTActionChannel> postChannel = + do_QueryInterface(channel)) { + // XXX it's a bit of a hack to rewind the postdata stream here but + // it has to be done in case the post data is being reused multiple + // times. + nsCOMPtr<nsISeekableStream> postDataSeekable = + do_QueryInterface(aLoadState->PostDataStream()); + if (postDataSeekable) { + aRv = postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); + NS_ENSURE_SUCCESS(aRv, false); + } + + // we really need to have a content type associated with this stream!! + postChannel->SetUploadStream(aLoadState->PostDataStream(), ""_ns, -1); + + // Ownership of the stream has transferred to the channel, clear our + // reference. + aLoadState->SetPostDataStream(nullptr); + } + + /* If there is a valid postdata *and* it is a History Load, + * set up the cache key on the channel, to retrieve the + * data *only* from the cache. If it is a normal reload, the + * cache is free to go to the server for updated postdata. + */ + if (cacheChannel && aCacheKey != 0) { + if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_CHARSET_CHANGE) { + cacheChannel->SetCacheKey(aCacheKey); + uint32_t loadFlags; + if (NS_SUCCEEDED(channel->GetLoadFlags(&loadFlags))) { + channel->SetLoadFlags(loadFlags | + nsICachingChannel::LOAD_ONLY_FROM_CACHE); + } + } else if (loadType == LOAD_RELOAD_NORMAL) { + cacheChannel->SetCacheKey(aCacheKey); + } + } + } else { + /* If there is no postdata, set the cache key on the channel, and + * do not set the LOAD_ONLY_FROM_CACHE flag, so that the channel + * will be free to get it from net if it is not found in cache. + * New cache may use it creatively on CGI pages with GET + * method and even on those that say "no-cache" + */ + if (loadType == LOAD_HISTORY || loadType == LOAD_RELOAD_NORMAL || + loadType == LOAD_RELOAD_CHARSET_CHANGE || + loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_CACHE || + loadType == LOAD_RELOAD_CHARSET_CHANGE_BYPASS_PROXY_AND_CACHE) { + if (cacheChannel && aCacheKey != 0) { + cacheChannel->SetCacheKey(aCacheKey); + } + } + } + + if (nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(channel)) { + // Allow execution against our context if the principals match + scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL); + } + + if (nsCOMPtr<nsITimedChannel> timedChannel = do_QueryInterface(channel)) { + timedChannel->SetTimingEnabled(true); + + nsString initiatorType; + switch (aLoadInfo->InternalContentPolicyType()) { + case nsIContentPolicy::TYPE_INTERNAL_EMBED: + initiatorType = u"embed"_ns; + break; + case nsIContentPolicy::TYPE_INTERNAL_OBJECT: + initiatorType = u"object"_ns; + break; + default: { + const auto& embedderElementType = + aBrowsingContext->GetEmbedderElementType(); + if (embedderElementType) { + initiatorType = *embedderElementType; + } + break; + } + } + + if (!initiatorType.IsEmpty()) { + timedChannel->SetInitiatorType(initiatorType); + } + } + + nsCOMPtr<nsIURI> rpURI; + aLoadInfo->GetResultPrincipalURI(getter_AddRefs(rpURI)); + Maybe<nsCOMPtr<nsIURI>> originalResultPrincipalURI; + aLoadState->GetMaybeResultPrincipalURI(originalResultPrincipalURI); + if (originalResultPrincipalURI && + (!aLoadState->KeepResultPrincipalURIIfSet() || !rpURI)) { + // Unconditionally override, we want the replay to be equal to what has + // been captured. + aLoadInfo->SetResultPrincipalURI(originalResultPrincipalURI.ref()); + } + + if (aLoadState->OriginalURI() && aLoadState->LoadReplace()) { + // The LOAD_REPLACE flag and its handling here will be removed as part + // of bug 1319110. For now preserve its restoration here to not break + // any code expecting it being set specially on redirected channels. + // If the flag has originally been set to change result of + // NS_GetFinalChannelURI it won't have any effect and also won't cause + // any harm. + uint32_t loadFlags; + aRv = channel->GetLoadFlags(&loadFlags); + NS_ENSURE_SUCCESS(aRv, false); + channel->SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE); + } + + nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp(); + if (csp) { + // Navigational requests that are same origin need to be upgraded in case + // upgrade-insecure-requests is present. Please note that for document + // navigations that bit is re-computed in case we encounter a server + // side redirect so the navigation is not same-origin anymore. + bool upgradeInsecureRequests = false; + csp->GetUpgradeInsecureRequests(&upgradeInsecureRequests); + if (upgradeInsecureRequests) { + // only upgrade if the navigation is same origin + nsCOMPtr<nsIPrincipal> resultPrincipal; + aRv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( + channel, getter_AddRefs(resultPrincipal)); + NS_ENSURE_SUCCESS(aRv, false); + if (nsContentSecurityUtils::IsConsideredSameOriginForUIR( + aLoadState->TriggeringPrincipal(), resultPrincipal)) { + aLoadInfo->SetUpgradeInsecureRequests(true); + } + } + + // For document loads we store the CSP that potentially needs to + // be inherited by the new document, e.g. in case we are loading + // an opaque origin like a data: URI. The actual inheritance + // check happens within Document::InitCSP(). + // Please create an actual copy of the CSP (do not share the same + // reference) otherwise a Meta CSP of an opaque origin will + // incorrectly be propagated to the embedding document. + RefPtr<nsCSPContext> cspToInherit = new nsCSPContext(); + cspToInherit->InitFromOther(static_cast<nsCSPContext*>(csp.get())); + aLoadInfo->SetCSPToInherit(cspToInherit); + } + + channel.forget(aChannel); + return true; +} + +bool nsDocShell::IsAboutBlankLoadOntoInitialAboutBlank( + nsIURI* aURI, bool aInheritPrincipal, nsIPrincipal* aPrincipalToInherit) { + return NS_IsAboutBlank(aURI) && aInheritPrincipal && + (aPrincipalToInherit == GetInheritedPrincipal(false)) && + (!mContentViewer || !mContentViewer->GetDocument() || + mContentViewer->GetDocument()->IsInitialDocument()); +} + +nsresult nsDocShell::DoURILoad(nsDocShellLoadState* aLoadState, + Maybe<uint32_t> aCacheKey, + nsIRequest** aRequest) { + // Double-check that we're still around to load this URI. + if (mIsBeingDestroyed) { + // Return NS_OK despite not doing anything to avoid throwing exceptions + // from nsLocation::SetHref if the unload handler of the existing page + // tears us down. + return NS_OK; + } + + nsCOMPtr<nsIURILoader> uriLoader = components::URILoader::Service(); + if (NS_WARN_IF(!uriLoader)) { + return NS_ERROR_UNEXPECTED; + } + + // Persist and sync layout history state before we load a new uri, as this + // might be our last chance to do so, in the content process. + PersistLayoutHistoryState(); + SynchronizeLayoutHistoryState(); + + nsresult rv; + nsContentPolicyType contentPolicyType = DetermineContentType(); + + if (IsSubframe()) { + MOZ_ASSERT(contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IFRAME || + contentPolicyType == nsIContentPolicy::TYPE_INTERNAL_FRAME, + "DoURILoad thinks this is a frame and InternalLoad does not"); + + if (StaticPrefs::dom_block_external_protocol_in_iframes()) { + // Only allow URLs able to return data in iframes. + bool doesNotReturnData = false; + NS_URIChainHasFlags(aLoadState->URI(), + nsIProtocolHandler::URI_DOES_NOT_RETURN_DATA, + &doesNotReturnData); + if (doesNotReturnData) { + // The context to check user-interaction with for the purposes of + // popup-blocking. + // + // We generally want to check the context that initiated the navigation. + WindowContext* sourceWindowContext = [&] { + const MaybeDiscardedBrowsingContext& sourceBC = + aLoadState->SourceBrowsingContext(); + if (!sourceBC.IsNullOrDiscarded()) { + if (WindowContext* wc = sourceBC.get()->GetCurrentWindowContext()) { + return wc; + } + } + return mBrowsingContext->GetParentWindowContext(); + }(); + + MOZ_ASSERT(sourceWindowContext); + // FIXME: We can't check user-interaction against an OOP window. This is + // the next best thing we can really do. The load state keeps whether + // the navigation had a user interaction in process + // (aLoadState->HasValidUserGestureActivation()), but we can't really + // consume it, which we want to prevent popup-spamming from the same + // click event. + WindowContext* context = + sourceWindowContext->IsInProcess() + ? sourceWindowContext + : mBrowsingContext->GetCurrentWindowContext(); + const bool popupBlocked = [&] { + const bool active = mBrowsingContext->IsActive(); + + // For same-origin-with-top windows, we grant a single free popup + // without user activation, see bug 1680721. + // + // We consume the flag now even if there's no user activation. + const bool hasFreePass = [&] { + if (!active || + !(context->IsInProcess() && context->SameOriginWithTop())) { + return false; + } + nsGlobalWindowInner* win = + context->TopWindowContext()->GetInnerWindow(); + return win && win->TryOpenExternalProtocolIframe(); + }(); + + if (context->IsInProcess() && + context->ConsumeTransientUserGestureActivation()) { + // If the user has interacted with the page, consume it. + return false; + } + + // TODO(emilio): Can we remove this check? It seems like what prompted + // this code (bug 1514547) should be covered by transient user + // activation, see bug 1514547. + if (active && + PopupBlocker::ConsumeTimerTokenForExternalProtocolIframe()) { + return false; + } + + if (sourceWindowContext->CanShowPopup()) { + return false; + } + + if (hasFreePass) { + return false; + } + + return true; + }(); + + // No error must be returned when iframes are blocked. + if (popupBlocked) { + nsAutoString message; + nsresult rv = nsContentUtils::GetLocalizedString( + nsContentUtils::eDOM_PROPERTIES, + "ExternalProtocolFrameBlockedNoUserActivation", message); + if (NS_SUCCEEDED(rv)) { + nsContentUtils::ReportToConsoleByWindowID( + message, nsIScriptError::warningFlag, "DOM"_ns, + context->InnerWindowId()); + } + return NS_OK; + } + } + } + + // Only allow view-source scheme in top-level docshells. view-source is + // the only scheme to which this applies at the moment due to potential + // timing attacks to read data from cross-origin iframes. If this widens + // we should add a protocol flag for whether the scheme is allowed in + // frames and use something like nsNetUtil::NS_URIChainHasFlags. + nsCOMPtr<nsIURI> tempURI = aLoadState->URI(); + nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(tempURI); + while (nestedURI) { + // view-source should always be an nsINestedURI, loop and check the + // scheme on this and all inner URIs that are also nested URIs. + if (SchemeIsViewSource(tempURI)) { + return NS_ERROR_UNKNOWN_PROTOCOL; + } + nestedURI->GetInnerURI(getter_AddRefs(tempURI)); + nestedURI = do_QueryInterface(tempURI); + } + } else { + MOZ_ASSERT(contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT, + "DoURILoad thinks this is a document and InternalLoad does not"); + } + + // We want to inherit aLoadState->PrincipalToInherit() when: + // 1. ChannelShouldInheritPrincipal returns true. + // 2. aLoadState->URI() is not data: URI, or data: URI is not + // configured as unique opaque origin. + bool inheritPrincipal = false; + + if (aLoadState->PrincipalToInherit()) { + bool isSrcdoc = + aLoadState->HasInternalLoadFlags(INTERNAL_LOAD_FLAGS_IS_SRCDOC); + bool inheritAttrs = nsContentUtils::ChannelShouldInheritPrincipal( + aLoadState->PrincipalToInherit(), aLoadState->URI(), + true, // aInheritForAboutBlank + isSrcdoc); + + inheritPrincipal = inheritAttrs && !SchemeIsData(aLoadState->URI()); + } + + // See https://bugzilla.mozilla.org/show_bug.cgi?id=1736570 + const bool isAboutBlankLoadOntoInitialAboutBlank = + IsAboutBlankLoadOntoInitialAboutBlank(aLoadState->URI(), inheritPrincipal, + aLoadState->PrincipalToInherit()); + + // FIXME We still have a ton of codepaths that don't pass through + // DocumentLoadListener, so probably need to create session history info + // in more places. + if (aLoadState->GetLoadingSessionHistoryInfo()) { + SetLoadingSessionHistoryInfo(*aLoadState->GetLoadingSessionHistoryInfo()); + } else if (isAboutBlankLoadOntoInitialAboutBlank && + mozilla::SessionHistoryInParent()) { + // Materialize LoadingSessionHistoryInfo here, because DocumentChannel + // loads have it, and later history behavior depends on it existing. + UniquePtr<SessionHistoryInfo> entry = MakeUnique<SessionHistoryInfo>( + aLoadState->URI(), aLoadState->TriggeringPrincipal(), + aLoadState->PrincipalToInherit(), + aLoadState->PartitionedPrincipalToInherit(), aLoadState->Csp(), + mContentTypeHint); + mozilla::dom::LoadingSessionHistoryInfo info(*entry); + SetLoadingSessionHistoryInfo(info, true); + } + + // open a channel for the url + + // If we have a pending channel, use the channel we've already created here. + // We don't need to set up load flags for our channel, as it has already been + // created. + + if (nsCOMPtr<nsIChannel> channel = + aLoadState->GetPendingRedirectedChannel()) { + // If we have a request outparameter, shove our channel into it. + if (aRequest) { + nsCOMPtr<nsIRequest> outRequest = channel; + outRequest.forget(aRequest); + } + + return OpenRedirectedChannel(aLoadState); + } + + // There are two cases we care about: + // * Top-level load: In this case, loadingNode is null, but loadingWindow + // is our mScriptGlobal. We pass null for loadingPrincipal in this case. + // * Subframe load: loadingWindow is null, but loadingNode is the frame + // element for the load. loadingPrincipal is the NodePrincipal of the + // frame element. + nsCOMPtr<nsINode> loadingNode; + nsCOMPtr<nsPIDOMWindowOuter> loadingWindow; + nsCOMPtr<nsIPrincipal> loadingPrincipal; + nsCOMPtr<nsISupports> topLevelLoadingContext; + + if (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) { + loadingNode = nullptr; + loadingPrincipal = nullptr; + loadingWindow = mScriptGlobal; + if (XRE_IsContentProcess()) { + // In e10s the child process doesn't have access to the element that + // contains the browsing context (because that element is in the chrome + // process). + nsCOMPtr<nsIBrowserChild> browserChild = GetBrowserChild(); + topLevelLoadingContext = ToSupports(browserChild); + } else { + // This is for loading non-e10s tabs and toplevel windows of various + // sorts. + // For the toplevel window cases, requestingElement will be null. + nsCOMPtr<Element> requestingElement = + loadingWindow->GetFrameElementInternal(); + topLevelLoadingContext = requestingElement; + } + } else { + loadingWindow = nullptr; + loadingNode = mScriptGlobal->GetFrameElementInternal(); + if (loadingNode) { + // If we have a loading node, then use that as our loadingPrincipal. + loadingPrincipal = loadingNode->NodePrincipal(); +#ifdef DEBUG + // Get the docshell type for requestingElement. + RefPtr<Document> requestingDoc = loadingNode->OwnerDoc(); + nsCOMPtr<nsIDocShell> elementDocShell = requestingDoc->GetDocShell(); + // requestingElement docshell type = current docshell type. + MOZ_ASSERT( + mItemType == elementDocShell->ItemType(), + "subframes should have the same docshell type as their parent"); +#endif + } else { + if (mIsBeingDestroyed) { + // If this isn't a top-level load and mScriptGlobal's frame element is + // null, then the element got removed from the DOM while we were trying + // to load this resource. This docshell is scheduled for destruction + // already, so bail out here. + return NS_OK; + } + // If we are not being destroyed and we do not have access to the loading + // node, then we are a remote subframe. Set the loading principal + // to be a null principal and then set it correctly in the parent. + loadingPrincipal = NullPrincipal::Create(GetOriginAttributes(), nullptr); + } + } + + if (!aLoadState->TriggeringPrincipal()) { + MOZ_ASSERT(false, "DoURILoad needs a valid triggeringPrincipal"); + return NS_ERROR_FAILURE; + } + + uint32_t sandboxFlags = mBrowsingContext->GetSandboxFlags(); + nsSecurityFlags securityFlags = + nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL; + + if (mLoadType == LOAD_ERROR_PAGE) { + securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE; + } + + if (inheritPrincipal) { + securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + + // Must never have a parent for TYPE_DOCUMENT loads + MOZ_ASSERT_IF(contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT, + !mBrowsingContext->GetParent()); + // Subdocuments must have a parent + MOZ_ASSERT_IF(contentPolicyType == nsIContentPolicy::TYPE_SUBDOCUMENT, + mBrowsingContext->GetParent()); + mBrowsingContext->SetTriggeringAndInheritPrincipals( + aLoadState->TriggeringPrincipal(), aLoadState->PrincipalToInherit(), + aLoadState->GetLoadIdentifier()); + RefPtr<LoadInfo> loadInfo = + (contentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) + ? new LoadInfo(loadingWindow, aLoadState->URI(), + aLoadState->TriggeringPrincipal(), + topLevelLoadingContext, securityFlags, sandboxFlags) + : new LoadInfo(loadingPrincipal, aLoadState->TriggeringPrincipal(), + loadingNode, securityFlags, contentPolicyType, + Maybe<mozilla::dom::ClientInfo>(), + Maybe<mozilla::dom::ServiceWorkerDescriptor>(), + sandboxFlags); + RefPtr<WindowContext> context = mBrowsingContext->GetCurrentWindowContext(); + + if (isAboutBlankLoadOntoInitialAboutBlank) { + // Match the DocumentChannel case where the default for third-partiness + // differs from the default in LoadInfo construction here. + // toolkit/components/antitracking/test/browser/browser_aboutblank.js + // fails without this. + BrowsingContext* top = mBrowsingContext->Top(); + if (top == mBrowsingContext) { + // If we're at the top, this must be a window.open()ed + // window, and we can't be third-party relative to ourselves. + loadInfo->SetIsThirdPartyContextToTopWindow(false); + } else { + if (Document* topDoc = top->GetDocument()) { + bool thirdParty = false; + mozilla::Unused << topDoc->GetPrincipal()->IsThirdPartyPrincipal( + aLoadState->PrincipalToInherit(), &thirdParty); + loadInfo->SetIsThirdPartyContextToTopWindow(thirdParty); + } else { + // If top is in a different process, we have to be third-party relative + // to it. + loadInfo->SetIsThirdPartyContextToTopWindow(true); + } + } + } + + if (mLoadType != LOAD_ERROR_PAGE && context && context->IsInProcess() && + context->HasValidTransientUserGestureActivation()) { + aLoadState->SetHasValidUserGestureActivation(true); + } + + // in case this docshell load was triggered by a valid transient user gesture, + // or also the load originates from external, then we pass that information on + // to the loadinfo, which allows e.g. setting Sec-Fetch-User request headers. + if (aLoadState->HasValidUserGestureActivation() || + aLoadState->HasLoadFlags(LOAD_FLAGS_FROM_EXTERNAL)) { + loadInfo->SetHasValidUserGestureActivation(true); + } + loadInfo->SetTriggeringSandboxFlags(aLoadState->TriggeringSandboxFlags()); + loadInfo->SetIsMetaRefresh(aLoadState->IsMetaRefresh()); + + uint32_t cacheKey = 0; + if (aCacheKey) { + cacheKey = *aCacheKey; + } else if (mozilla::SessionHistoryInParent()) { + if (mLoadingEntry) { + cacheKey = mLoadingEntry->mInfo.GetCacheKey(); + } else if (mActiveEntry) { // for reload cases + cacheKey = mActiveEntry->GetCacheKey(); + } + } else { + if (mLSHE) { + cacheKey = mLSHE->GetCacheKey(); + } else if (mOSHE) { // for reload cases + cacheKey = mOSHE->GetCacheKey(); + } + } + + bool uriModified; + if (mLSHE || mLoadingEntry) { + if (mLoadingEntry) { + uriModified = mLoadingEntry->mInfo.GetURIWasModified(); + } else { + uriModified = mLSHE->GetURIWasModified(); + } + } else { + uriModified = false; + } + + bool isXFOError = false; + if (mFailedChannel) { + nsresult status; + mFailedChannel->GetStatus(&status); + isXFOError = status == NS_ERROR_XFO_VIOLATION; + } + + nsLoadFlags loadFlags = aLoadState->CalculateChannelLoadFlags( + mBrowsingContext, Some(uriModified), Some(isXFOError)); + + nsCOMPtr<nsIChannel> channel; + if (DocumentChannel::CanUseDocumentChannel(aLoadState->URI()) && + !isAboutBlankLoadOntoInitialAboutBlank) { + channel = DocumentChannel::CreateForDocument(aLoadState, loadInfo, + loadFlags, this, cacheKey, + uriModified, isXFOError); + MOZ_ASSERT(channel); + + // Disable keyword fixup when using DocumentChannel, since + // DocumentLoadListener will handle this for us (in the parent process). + mAllowKeywordFixup = false; + } else if (!CreateAndConfigureRealChannelForLoadState( + mBrowsingContext, aLoadState, loadInfo, this, this, + GetOriginAttributes(), loadFlags, cacheKey, rv, + getter_AddRefs(channel))) { + return rv; + } + + // Make sure to give the caller a channel if we managed to create one + // This is important for correct error page/session history interaction + if (aRequest) { + NS_ADDREF(*aRequest = channel); + } + + nsCOMPtr<nsIContentSecurityPolicy> csp = aLoadState->Csp(); + if (csp) { + // Check CSP navigate-to + bool allowsNavigateTo = false; + rv = csp->GetAllowsNavigateTo(aLoadState->URI(), + aLoadState->IsFormSubmission(), + false, /* aWasRedirected */ + false, /* aEnforceWhitelist */ + &allowsNavigateTo); + NS_ENSURE_SUCCESS(rv, rv); + + if (!allowsNavigateTo) { + return NS_ERROR_CSP_NAVIGATE_TO_VIOLATION; + } + } + + const nsACString& typeHint = aLoadState->TypeHint(); + if (!typeHint.IsVoid()) { + mContentTypeHint = typeHint; + } else { + mContentTypeHint.Truncate(); + } + + // Load attributes depend on load type... + if (mLoadType == LOAD_RELOAD_CHARSET_CHANGE) { + // Use SetAllowStaleCacheContent (not LOAD_FROM_CACHE flag) since we + // only want to force cache load for this channel, not the whole + // loadGroup. + nsCOMPtr<nsICacheInfoChannel> cachingChannel = do_QueryInterface(channel); + if (cachingChannel) { + cachingChannel->SetAllowStaleCacheContent(true); + } + } + + uint32_t openFlags = + nsDocShell::ComputeURILoaderFlags(mBrowsingContext, mLoadType); + return OpenInitializedChannel(channel, uriLoader, openFlags); +} + +static nsresult AppendSegmentToString(nsIInputStream* aIn, void* aClosure, + const char* aFromRawSegment, + uint32_t aToOffset, uint32_t aCount, + uint32_t* aWriteCount) { + // aFromSegment now contains aCount bytes of data. + + nsAutoCString* buf = static_cast<nsAutoCString*>(aClosure); + buf->Append(aFromRawSegment, aCount); + + // Indicate that we have consumed all of aFromSegment + *aWriteCount = aCount; + return NS_OK; +} + +/* static */ nsresult nsDocShell::AddHeadersToChannel( + nsIInputStream* aHeadersData, nsIChannel* aGenericChannel) { + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aGenericChannel); + NS_ENSURE_STATE(httpChannel); + + uint32_t numRead; + nsAutoCString headersString; + nsresult rv = aHeadersData->ReadSegments( + AppendSegmentToString, &headersString, UINT32_MAX, &numRead); + NS_ENSURE_SUCCESS(rv, rv); + + // used during the manipulation of the String from the InputStream + nsAutoCString headerName; + nsAutoCString headerValue; + int32_t crlf; + int32_t colon; + + // + // Iterate over the headersString: for each "\r\n" delimited chunk, + // add the value as a header to the nsIHttpChannel + // + + static const char kWhitespace[] = "\b\t\r\n "; + while (true) { + crlf = headersString.Find("\r\n"); + if (crlf == kNotFound) { + return NS_OK; + } + + const nsACString& oneHeader = StringHead(headersString, crlf); + + colon = oneHeader.FindChar(':'); + if (colon == kNotFound) { + return NS_ERROR_UNEXPECTED; + } + + headerName = StringHead(oneHeader, colon); + headerValue = Substring(oneHeader, colon + 1); + + headerName.Trim(kWhitespace); + headerValue.Trim(kWhitespace); + + headersString.Cut(0, crlf + 2); + + // + // FINALLY: we can set the header! + // + + rv = httpChannel->SetRequestHeader(headerName, headerValue, true); + NS_ENSURE_SUCCESS(rv, rv); + } + + MOZ_ASSERT_UNREACHABLE("oops"); + return NS_ERROR_UNEXPECTED; +} + +/* static */ uint32_t nsDocShell::ComputeURILoaderFlags( + BrowsingContext* aBrowsingContext, uint32_t aLoadType) { + MOZ_ASSERT(aBrowsingContext); + + uint32_t openFlags = 0; + if (aLoadType == LOAD_LINK) { + openFlags |= nsIURILoader::IS_CONTENT_PREFERRED; + } + if (!aBrowsingContext->GetAllowContentRetargeting()) { + openFlags |= nsIURILoader::DONT_RETARGET; + } + + return openFlags; +} + +nsresult nsDocShell::OpenInitializedChannel(nsIChannel* aChannel, + nsIURILoader* aURILoader, + uint32_t aOpenFlags) { + nsresult rv = NS_OK; + + // If anything fails here, make sure to clear our initial ClientSource. + auto cleanupInitialClient = + MakeScopeExit([&] { mInitialClientSource.reset(); }); + + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + NS_ENSURE_TRUE(win, NS_ERROR_FAILURE); + + MaybeCreateInitialClientSource(); + + // Let the client channel helper know if we are using DocumentChannel, + // since redirects get handled in the parent process in that case. + RefPtr<net::DocumentChannel> docChannel = do_QueryObject(aChannel); + if (docChannel && XRE_IsContentProcess()) { + // Tell the content process nsDocumentOpenInfo to not try to do + // any sort of targeting. + aOpenFlags |= nsIURILoader::DONT_RETARGET; + } + + // Since we are loading a document we need to make sure the proper reserved + // and initial client data is stored on the nsILoadInfo. The + // ClientChannelHelper does this and ensures that it is propagated properly + // on redirects. We pass no reserved client here so that the helper will + // create the reserved ClientSource if necessary. + Maybe<ClientInfo> noReservedClient; + if (docChannel) { + // When using DocumentChannel, all redirect handling is done in the parent, + // so we just need the child variant to watch for the internal redirect + // to the final channel. + rv = AddClientChannelHelperInChild( + aChannel, win->EventTargetFor(TaskCategory::Other)); + docChannel->SetInitialClientInfo(GetInitialClientInfo()); + } else { + rv = AddClientChannelHelper(aChannel, std::move(noReservedClient), + GetInitialClientInfo(), + win->EventTargetFor(TaskCategory::Other)); + } + NS_ENSURE_SUCCESS(rv, rv); + + rv = aURILoader->OpenURI(aChannel, aOpenFlags, this); + NS_ENSURE_SUCCESS(rv, rv); + + // We're about to load a new page and it may take time before necko + // gives back any data, so main thread might have a chance to process a + // collector slice + nsJSContext::MaybeRunNextCollectorSlice(this, JS::GCReason::DOCSHELL); + + // Success. Keep the initial ClientSource if it exists. + cleanupInitialClient.release(); + + return NS_OK; +} + +nsresult nsDocShell::OpenRedirectedChannel(nsDocShellLoadState* aLoadState) { + nsCOMPtr<nsIChannel> channel = aLoadState->GetPendingRedirectedChannel(); + MOZ_ASSERT(channel); + + // If anything fails here, make sure to clear our initial ClientSource. + auto cleanupInitialClient = + MakeScopeExit([&] { mInitialClientSource.reset(); }); + + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + NS_ENSURE_TRUE(win, NS_ERROR_FAILURE); + + MaybeCreateInitialClientSource(); + + nsCOMPtr<nsILoadInfo> loadInfo = channel->LoadInfo(); + + LoadInfo* li = static_cast<LoadInfo*>(loadInfo.get()); + if (loadInfo->GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_DOCUMENT) { + li->UpdateBrowsingContextID(mBrowsingContext->Id()); + } else if (loadInfo->GetExternalContentPolicyType() == + ExtContentPolicy::TYPE_SUBDOCUMENT) { + li->UpdateFrameBrowsingContextID(mBrowsingContext->Id()); + } + // TODO: more attributes need to be updated on the LoadInfo (bug 1561706) + + // If we did a process switch, then we should have an existing allocated + // ClientInfo, so we just need to allocate a corresponding ClientSource. + CreateReservedSourceIfNeeded(channel, + win->EventTargetFor(TaskCategory::Other)); + + RefPtr<nsDocumentOpenInfo> loader = + new nsDocumentOpenInfo(this, nsIURILoader::DONT_RETARGET, nullptr); + channel->SetLoadGroup(mLoadGroup); + + MOZ_ALWAYS_SUCCEEDS(loader->Prepare()); + + nsresult rv = NS_OK; + if (XRE_IsParentProcess()) { + // If we're in the parent, the we don't have an nsIChildChannel, just + // the original channel, which is already open in this process. + + // DocumentLoadListener expects to get an nsIParentChannel, so + // we create a wrapper around the channel and nsIStreamListener + // that forwards functionality as needed, and then we register + // it under the provided identifier. + RefPtr<ParentChannelWrapper> wrapper = + new ParentChannelWrapper(channel, loader); + wrapper->Register(aLoadState->GetPendingRedirectChannelRegistrarId()); + + mLoadGroup->AddRequest(channel, nullptr); + } else if (nsCOMPtr<nsIChildChannel> childChannel = + do_QueryInterface(channel)) { + // Our channel was redirected from another process, so doesn't need to + // be opened again. However, it does need its listener hooked up + // correctly. + rv = childChannel->CompleteRedirectSetup(loader); + } else { + // It's possible for the redirected channel to not implement + // nsIChildChannel and be entirely local (like srcdoc). In that case we + // can just open the local instance and it will work. + rv = channel->AsyncOpen(loader); + } + if (rv == NS_ERROR_NO_CONTENT) { + return NS_OK; + } + NS_ENSURE_SUCCESS(rv, rv); + + // Success. Keep the initial ClientSource if it exists. + cleanupInitialClient.release(); + return NS_OK; +} + +nsresult nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef, + nsACString& aNewHash, uint32_t aLoadType) { + if (!mCurrentURI) { + return NS_OK; + } + + RefPtr<PresShell> presShell = GetPresShell(); + if (!presShell) { + // If we failed to get the shell, or if there is no shell, + // nothing left to do here. + return NS_OK; + } + + nsIScrollableFrame* rootScroll = presShell->GetRootScrollFrameAsScrollable(); + if (rootScroll) { + rootScroll->ClearDidHistoryRestore(); + } + + // If we have no new anchor, we do not want to scroll, unless there is a + // current anchor and we are doing a history load. So return if we have no + // new anchor, and there is no current anchor or the load is not a history + // load. + if ((!aCurHasRef || aLoadType != LOAD_HISTORY) && !aNewHasRef) { + return NS_OK; + } + + // Both the new and current URIs refer to the same page. We can now + // browse to the hash stored in the new URI. + + if (!aNewHash.IsEmpty()) { + // anchor is there, but if it's a load from history, + // we don't have any anchor jumping to do + bool scroll = aLoadType != LOAD_HISTORY && aLoadType != LOAD_RELOAD_NORMAL; + + // We assume that the bytes are in UTF-8, as it says in the + // spec: + // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1 + + // We try the UTF-8 string first, and then try the document's + // charset (see below). If the string is not UTF-8, + // conversion will fail and give us an empty Unicode string. + // In that case, we should just fall through to using the + // page's charset. + nsresult rv = NS_ERROR_FAILURE; + NS_ConvertUTF8toUTF16 uStr(aNewHash); + if (!uStr.IsEmpty()) { + rv = presShell->GoToAnchor(uStr, scroll, ScrollFlags::ScrollSmoothAuto); + } + + if (NS_FAILED(rv)) { + char* str = ToNewCString(aNewHash, mozilla::fallible); + if (!str) { + return NS_ERROR_OUT_OF_MEMORY; + } + nsUnescape(str); + NS_ConvertUTF8toUTF16 utf16Str(str); + if (!utf16Str.IsEmpty()) { + rv = presShell->GoToAnchor(utf16Str, scroll, + ScrollFlags::ScrollSmoothAuto); + } + free(str); + } + + // Above will fail if the anchor name is not UTF-8. Need to + // convert from document charset to unicode. + if (NS_FAILED(rv)) { + // Get a document charset + NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE); + Document* doc = mContentViewer->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + nsAutoCString charset; + doc->GetDocumentCharacterSet()->Name(charset); + + nsCOMPtr<nsITextToSubURI> textToSubURI = + do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Unescape and convert to unicode + nsAutoString uStr; + + rv = textToSubURI->UnEscapeAndConvert(charset, aNewHash, uStr); + NS_ENSURE_SUCCESS(rv, rv); + + // Ignore return value of GoToAnchor, since it will return an error + // if there is no such anchor in the document, which is actually a + // success condition for us (we want to update the session history + // with the new URI no matter whether we actually scrolled + // somewhere). + // + // When aNewHash contains "%00", unescaped string may be empty. + // And GoToAnchor asserts if we ask it to scroll to an empty ref. + presShell->GoToAnchor(uStr, scroll && !uStr.IsEmpty(), + ScrollFlags::ScrollSmoothAuto); + } + } else { + // Tell the shell it's at an anchor, without scrolling. + presShell->GoToAnchor(u""_ns, false); + + // An empty anchor was found, but if it's a load from history, + // we don't have to jump to the top of the page. Scrollbar + // position will be restored by the caller, based on positions + // stored in session history. + if (aLoadType == LOAD_HISTORY || aLoadType == LOAD_RELOAD_NORMAL) { + return NS_OK; + } + // An empty anchor. Scroll to the top of the page. Ignore the + // return value; failure to scroll here (e.g. if there is no + // root scrollframe) is not grounds for canceling the load! + SetCurScrollPosEx(0, 0); + } + + return NS_OK; +} + +bool nsDocShell::OnNewURI(nsIURI* aURI, nsIChannel* aChannel, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, + bool aFireOnLocationChange, bool aAddToGlobalHistory, + bool aCloneSHChildren) { + MOZ_ASSERT(aURI, "uri is null"); + MOZ_ASSERT(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set"); + + MOZ_ASSERT(!aPrincipalToInherit || + (aPrincipalToInherit && aTriggeringPrincipal)); + +#if defined(DEBUG) + if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { + nsAutoCString chanName; + if (aChannel) { + aChannel->GetName(chanName); + } else { + chanName.AssignLiteral("<no channel>"); + } + + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]::OnNewURI(\"%s\", [%s], 0x%x)\n", this, + aURI->GetSpecOrDefault().get(), chanName.get(), mLoadType)); + } +#endif + + bool equalUri = false; + + // Get the post data and the HTTP response code from the channel. + uint32_t responseStatus = 0; + nsCOMPtr<nsIInputStream> inputStream; + if (aChannel) { + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + + // Check if the HTTPChannel is hiding under a multiPartChannel + if (!httpChannel) { + GetHttpChannel(aChannel, getter_AddRefs(httpChannel)); + } + + if (httpChannel) { + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel)); + if (uploadChannel) { + uploadChannel->GetUploadStream(getter_AddRefs(inputStream)); + } + + // If the response status indicates an error, unlink this session + // history entry from any entries sharing its document. + nsresult rv = httpChannel->GetResponseStatus(&responseStatus); + if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) { + mLSHE->AbandonBFCacheEntry(); + // FIXME Do the same for mLoadingEntry + } + } + } + + // Determine if this type of load should update history. + bool updateGHistory = ShouldUpdateGlobalHistory(mLoadType); + + // We don't update session history on reload unless we're loading + // an iframe in shift-reload case. + bool updateSHistory = mBrowsingContext->ShouldUpdateSessionHistory(mLoadType); + + // Create SH Entry (mLSHE) only if there is a SessionHistory object in the + // root browsing context. + // FIXME If session history in the parent is enabled then we only do this if + // the session history object is in process, otherwise we can't really + // use the mLSHE anyway. Once session history is only stored in the + // parent then this code will probably be removed anyway. + RefPtr<ChildSHistory> rootSH = GetRootSessionHistory(); + if (!rootSH) { + updateSHistory = false; + updateGHistory = false; // XXX Why global history too? + } + + // Check if the url to be loaded is the same as the one already loaded. + if (mCurrentURI) { + aURI->Equals(mCurrentURI, &equalUri); + } + +#ifdef DEBUG + bool shAvailable = (rootSH != nullptr); + + // XXX This log message is almost useless because |updateSHistory| + // and |updateGHistory| are not correct at this point. + + MOZ_LOG(gDocShellLog, LogLevel::Debug, + (" shAvailable=%i updateSHistory=%i updateGHistory=%i" + " equalURI=%i\n", + shAvailable, updateSHistory, updateGHistory, equalUri)); +#endif + + /* If the url to be loaded is the same as the one already there, + * and the original loadType is LOAD_NORMAL, LOAD_LINK, or + * LOAD_STOP_CONTENT, set loadType to LOAD_NORMAL_REPLACE so that + * AddToSessionHistory() won't mess with the current SHEntry and + * if this page has any frame children, it also will be handled + * properly. see bug 83684 + * + * NB: If mOSHE is null but we have a current URI, then it probably + * means that we must be at the transient about:blank content viewer; + * we should let the normal load continue, since there's nothing to + * replace. Sometimes this happens after a session restore (eg process + * switch) and mCurrentURI is not about:blank; we assume we can let the load + * continue (Bug 1301399). + * + * XXX Hopefully changing the loadType at this time will not hurt + * anywhere. The other way to take care of sequentially repeating + * frameset pages is to add new methods to nsIDocShellTreeItem. + * Hopefully I don't have to do that. + */ + if (equalUri && + (mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) && + (mLoadType == LOAD_NORMAL || mLoadType == LOAD_LINK || + mLoadType == LOAD_STOP_CONTENT) && + !inputStream) { + mLoadType = LOAD_NORMAL_REPLACE; + } + + // If this is a refresh to the currently loaded url, we don't + // have to update session or global history. + if (mLoadType == LOAD_REFRESH && !inputStream && equalUri) { + SetHistoryEntryAndUpdateBC(Some<nsISHEntry*>(mOSHE), Nothing()); + } + + /* If the user pressed shift-reload, cache will create a new cache key + * for the page. Save the new cacheKey in Session History. + * see bug 90098 + */ + if (aChannel && IsForceReloadType(mLoadType)) { + MOZ_ASSERT(!updateSHistory || IsSubframe(), + "We shouldn't be updating session history for forced" + " reloads unless we're in a newly created iframe!"); + + nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(aChannel)); + uint32_t cacheKey = 0; + // Get the Cache Key and store it in SH. + if (cacheChannel) { + cacheChannel->GetCacheKey(&cacheKey); + } + // If we already have a loading history entry, store the new cache key + // in it. Otherwise, since we're doing a reload and won't be updating + // our history entry, store the cache key in our current history entry. + SetCacheKeyOnHistoryEntry(mLSHE ? mLSHE : mOSHE, cacheKey); + + if (!mozilla::SessionHistoryInParent()) { + // Since we're force-reloading, clear all the sub frame history. + ClearFrameHistory(mLSHE); + ClearFrameHistory(mOSHE); + } + } + + if (!mozilla::SessionHistoryInParent()) { + // Clear subframe history on refresh. + // XXX: history.go(0) won't go this path as mLoadType is LOAD_HISTORY in + // this case. One should re-validate after bug 1331865 fixed. + if (mLoadType == LOAD_REFRESH) { + ClearFrameHistory(mLSHE); + ClearFrameHistory(mOSHE); + } + + if (updateSHistory) { + // Update session history if necessary... + if (!mLSHE && (mItemType == typeContent) && mURIResultedInDocument) { + /* This is a fresh page getting loaded for the first time + *.Create a Entry for it and add it to SH, if this is the + * rootDocShell + */ + (void)AddToSessionHistory(aURI, aChannel, aTriggeringPrincipal, + aPrincipalToInherit, + aPartitionedPrincipalToInherit, aCsp, + aCloneSHChildren, getter_AddRefs(mLSHE)); + } + } else if (GetSessionHistory() && mLSHE && mURIResultedInDocument) { + // Even if we don't add anything to SHistory, ensure the current index + // points to the same SHEntry as our mLSHE. + + GetSessionHistory()->LegacySHistory()->EnsureCorrectEntryAtCurrIndex( + mLSHE); + } + } + + // If this is a POST request, we do not want to include this in global + // history. + if (ShouldAddURIVisit(aChannel) && updateGHistory && aAddToGlobalHistory && + !net::ChannelIsPost(aChannel)) { + nsCOMPtr<nsIURI> previousURI; + uint32_t previousFlags = 0; + + if (mLoadType & LOAD_CMD_RELOAD) { + // On a reload request, we don't set redirecting flags. + previousURI = aURI; + } else { + ExtractLastVisit(aChannel, getter_AddRefs(previousURI), &previousFlags); + } + + AddURIVisit(aURI, previousURI, previousFlags, responseStatus); + } + + // If this was a history load or a refresh, or it was a history load but + // later changed to LOAD_NORMAL_REPLACE due to redirection, update the index + // in session history. + if (!mozilla::SessionHistoryInParent() && rootSH && + ((mLoadType & (LOAD_CMD_HISTORY | LOAD_CMD_RELOAD)) || + mLoadType == LOAD_NORMAL_REPLACE || mLoadType == LOAD_REFRESH_REPLACE)) { + mPreviousEntryIndex = rootSH->Index(); + if (!mozilla::SessionHistoryInParent()) { + rootSH->LegacySHistory()->UpdateIndex(); + } + mLoadedEntryIndex = rootSH->Index(); + MOZ_LOG(gPageCacheLog, LogLevel::Verbose, + ("Previous index: %d, Loaded index: %d", mPreviousEntryIndex, + mLoadedEntryIndex)); + } + + // aCloneSHChildren exactly means "we are not loading a new document". + uint32_t locationFlags = + aCloneSHChildren ? uint32_t(LOCATION_CHANGE_SAME_DOCUMENT) : 0; + + bool onLocationChangeNeeded = + SetCurrentURI(aURI, aChannel, aFireOnLocationChange, + /* aIsInitialAboutBlank */ false, locationFlags); + // Make sure to store the referrer from the channel, if any + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + if (httpChannel) { + mReferrerInfo = httpChannel->GetReferrerInfo(); + } + return onLocationChangeNeeded; +} + +Maybe<Wireframe> nsDocShell::GetWireframe() { + const bool collectWireFrame = + mozilla::SessionHistoryInParent() && + StaticPrefs::browser_history_collectWireframes() && + mBrowsingContext->IsTopContent() && mActiveEntry; + + if (!collectWireFrame) { + return Nothing(); + } + + RefPtr<Document> doc = mContentViewer->GetDocument(); + Nullable<Wireframe> wireframe; + doc->GetWireframeWithoutFlushing(false, wireframe); + if (wireframe.IsNull()) { + return Nothing(); + } + return Some(wireframe.Value()); +} + +bool nsDocShell::CollectWireframe() { + Maybe<Wireframe> wireframe = GetWireframe(); + if (wireframe.isNothing()) { + return false; + } + + if (XRE_IsParentProcess()) { + SessionHistoryEntry* entry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetWireframe(wireframe); + } + } else { + mozilla::Unused + << ContentChild::GetSingleton()->SendSessionHistoryEntryWireframe( + mBrowsingContext, wireframe.ref()); + } + + return true; +} + +//***************************************************************************** +// nsDocShell: Session History +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle, + const nsAString& aURL, bool aReplace, JSContext* aCx) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell[%p]: AddState(..., %s, %s, %d)", this, + NS_ConvertUTF16toUTF8(aTitle).get(), + NS_ConvertUTF16toUTF8(aURL).get(), aReplace)); + // Implements History.pushState and History.replaceState + + // Here's what we do, roughly in the order specified by HTML5. The specific + // steps we are executing are at + // <https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate> + // and + // <https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps>. + // This function basically implements #dom-history-pushstate and + // UpdateURLAndHistory implements #url-and-history-update-steps. + // + // A. Serialize aData using structured clone. This is #dom-history-pushstate + // step 5. + // B. If the third argument is present, #dom-history-pushstate step 7. + // 7.1. Resolve the url, relative to our document. + // 7.2. If (a) fails, raise a SECURITY_ERR + // 7.4. Compare the resulting absolute URL to the document's address. If + // any part of the URLs difer other than the <path>, <query>, and + // <fragment> components, raise a SECURITY_ERR and abort. + // C. If !aReplace, #url-and-history-update-steps steps 2.1-2.3: + // Remove from the session history all entries after the current entry, + // as we would after a regular navigation, and save the current + // entry's scroll position (bug 590573). + // D. #url-and-history-update-steps step 2.4 or step 3. As apropriate, + // either add a state object entry to the session history after the + // current entry with the following properties, or modify the current + // session history entry to set + // a. cloned data as the state object, + // b. if the third argument was present, the absolute URL found in + // step 2 + // Also clear the new history entry's POST data (see bug 580069). + // E. If aReplace is false (i.e. we're doing a pushState instead of a + // replaceState), notify bfcache that we've navigated to a new page. + // F. If the third argument is present, set the document's current address + // to the absolute URL found in step B. This is + // #url-and-history-update-steps step 4. + // + // It's important that this function not run arbitrary scripts after step A + // and before completing step E. For example, if a script called + // history.back() before we completed step E, bfcache might destroy an + // active content viewer. Since EvictOutOfRangeContentViewers at the end of + // step E might run script, we can't just put a script blocker around the + // critical section. + // + // Note that we completely ignore the aTitle parameter. + + nsresult rv; + + // Don't clobber the load type of an existing network load. + AutoRestore<uint32_t> loadTypeResetter(mLoadType); + + // pushState effectively becomes replaceState when we've started a network + // load but haven't adopted its document yet. This mirrors what we do with + // changes to the hash at this stage of the game. + if (JustStartedNetworkLoad()) { + aReplace = true; + } + + RefPtr<Document> document = GetDocument(); + NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); + + // Step A: Serialize aData using structured clone. + // https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate + // step 5. + nsCOMPtr<nsIStructuredCloneContainer> scContainer; + + // scContainer->Init might cause arbitrary JS to run, and this code might + // navigate the page we're on, potentially to a different origin! (bug + // 634834) To protect against this, we abort if our principal changes due + // to the InitFromJSVal() call. + { + RefPtr<Document> origDocument = GetDocument(); + if (!origDocument) { + return NS_ERROR_DOM_SECURITY_ERR; + } + nsCOMPtr<nsIPrincipal> origPrincipal = origDocument->NodePrincipal(); + + scContainer = new nsStructuredCloneContainer(); + rv = scContainer->InitFromJSVal(aData, aCx); + NS_ENSURE_SUCCESS(rv, rv); + + RefPtr<Document> newDocument = GetDocument(); + if (!newDocument) { + return NS_ERROR_DOM_SECURITY_ERR; + } + nsCOMPtr<nsIPrincipal> newPrincipal = newDocument->NodePrincipal(); + + bool principalsEqual = false; + origPrincipal->Equals(newPrincipal, &principalsEqual); + NS_ENSURE_TRUE(principalsEqual, NS_ERROR_DOM_SECURITY_ERR); + } + + // Check that the state object isn't too long. + int32_t maxStateObjSize = StaticPrefs::browser_history_maxStateObjectSize(); + if (maxStateObjSize < 0) { + maxStateObjSize = 0; + } + + uint64_t scSize; + rv = scContainer->GetSerializedNBytes(&scSize); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(scSize <= (uint32_t)maxStateObjSize, NS_ERROR_ILLEGAL_VALUE); + + // Step B: Resolve aURL. + // https://html.spec.whatwg.org/multipage/history.html#dom-history-pushstate + // step 7. + bool equalURIs = true; + nsCOMPtr<nsIURI> currentURI; + if (mCurrentURI) { + currentURI = nsIOService::CreateExposableURI(mCurrentURI); + } else { + currentURI = mCurrentURI; + } + nsCOMPtr<nsIURI> newURI; + if (aURL.Length() == 0) { + newURI = currentURI; + } else { + // 7.1: Resolve aURL relative to mURI + + nsIURI* docBaseURI = document->GetDocBaseURI(); + if (!docBaseURI) { + return NS_ERROR_FAILURE; + } + + nsAutoCString spec; + docBaseURI->GetSpec(spec); + + rv = NS_NewURI(getter_AddRefs(newURI), aURL, + document->GetDocumentCharacterSet(), docBaseURI); + + // 7.2: If 2a fails, raise a SECURITY_ERR + if (NS_FAILED(rv)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + // 7.4 and 7.5: Same-origin check. + if (!nsContentUtils::URIIsLocalFile(newURI)) { + // In addition to checking that the security manager says that + // the new URI has the same origin as our current URI, we also + // check that the two URIs have the same userpass. (The + // security manager says that |http://foo.com| and + // |http://me@foo.com| have the same origin.) currentURI + // won't contain the password part of the userpass, so this + // means that it's never valid to specify a password in a + // pushState or replaceState URI. + + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE); + + // It's very important that we check that newURI is of the same + // origin as currentURI, not docBaseURI, because a page can + // set docBaseURI arbitrarily to any domain. + nsAutoCString currentUserPass, newUserPass; + NS_ENSURE_SUCCESS(currentURI->GetUserPass(currentUserPass), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newURI->GetUserPass(newUserPass), NS_ERROR_FAILURE); + bool isPrivateWin = + document->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId > + 0; + if (NS_FAILED(secMan->CheckSameOriginURI(currentURI, newURI, true, + isPrivateWin)) || + !currentUserPass.Equals(newUserPass)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + } else { + // It's a file:// URI + nsCOMPtr<nsIPrincipal> principal = document->GetPrincipal(); + + if (!principal || NS_FAILED(principal->CheckMayLoadWithReporting( + newURI, false, document->InnerWindowID()))) { + return NS_ERROR_DOM_SECURITY_ERR; + } + } + + if (currentURI) { + currentURI->Equals(newURI, &equalURIs); + } else { + equalURIs = false; + } + + } // end of same-origin check + + // Step 8: call "URL and history update steps" + rv = UpdateURLAndHistory(document, newURI, scContainer, aTitle, aReplace, + currentURI, equalURIs); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult nsDocShell::UpdateURLAndHistory(Document* aDocument, nsIURI* aNewURI, + nsIStructuredCloneContainer* aData, + const nsAString& aTitle, bool aReplace, + nsIURI* aCurrentURI, bool aEqualURIs) { + // Implements + // https://html.spec.whatwg.org/multipage/history.html#url-and-history-update-steps + + // If we have a pending title change, handle it before creating a new entry. + aDocument->DoNotifyPossibleTitleChange(); + + // Step 2, if aReplace is false: Create a new entry in the session + // history. This will erase all SHEntries after the new entry and make this + // entry the current one. This operation may modify mOSHE, which we need + // later, so we keep a reference here. + NS_ENSURE_TRUE(mOSHE || mActiveEntry || aReplace, NS_ERROR_FAILURE); + nsCOMPtr<nsISHEntry> oldOSHE = mOSHE; + + // If this push/replaceState changed the document's current URI and the new + // URI differs from the old URI in more than the hash, or if the old + // SHEntry's URI was modified in this way by a push/replaceState call + // set URIWasModified to true for the current SHEntry (bug 669671). + bool sameExceptHashes = true; + aNewURI->EqualsExceptRef(aCurrentURI, &sameExceptHashes); + bool uriWasModified; + if (sameExceptHashes) { + if (mozilla::SessionHistoryInParent()) { + uriWasModified = mActiveEntry && mActiveEntry->GetURIWasModified(); + } else { + uriWasModified = oldOSHE && oldOSHE->GetURIWasModified(); + } + } else { + uriWasModified = true; + } + + mLoadType = LOAD_PUSHSTATE; + + nsCOMPtr<nsISHEntry> newSHEntry; + if (!aReplace) { + // Step 2. + + // Step 2.2, "Remove any tasks queued by the history traversal task + // source that are associated with any Document objects in the + // top-level browsing context's document family." This is very hard in + // SessionHistoryInParent since we can't synchronously access the + // pending navigations that are already sent to the parent. We can + // abort any AsyncGo navigations that are waiting to be sent. If we + // send a message to the parent, it would be processed after any + // navigations previously sent. So long as we consider the "history + // traversal task source" to be the list in this process we match the + // spec. If we move the entire list to the parent, we can handle the + // aborting of loads there, but we don't have a way to synchronously + // remove entries as we do here for non-SHIP. + RefPtr<ChildSHistory> shistory = GetRootSessionHistory(); + if (shistory) { + shistory->RemovePendingHistoryNavigations(); + } + + nsPoint scrollPos = GetCurScrollPos(); + + bool scrollRestorationIsManual; + if (mozilla::SessionHistoryInParent()) { + // FIXME Need to save the current scroll position on mActiveEntry. + scrollRestorationIsManual = mActiveEntry->GetScrollRestorationIsManual(); + } else { + // Save the current scroll position (bug 590573). Step 2.3. + mOSHE->SetScrollPosition(scrollPos.x, scrollPos.y); + + scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual(); + } + + nsCOMPtr<nsIContentSecurityPolicy> csp = aDocument->GetCsp(); + + if (mozilla::SessionHistoryInParent()) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p UpdateActiveEntry (not replacing)", this)); + nsString title(mActiveEntry->GetTitle()); + UpdateActiveEntry(false, + /* aPreviousScrollPos = */ Some(scrollPos), aNewURI, + /* aOriginalURI = */ nullptr, + /* aReferrerInfo = */ nullptr, + /* aTriggeringPrincipal = */ aDocument->NodePrincipal(), + csp, title, scrollRestorationIsManual, aData, + uriWasModified); + } else { + // Since we're not changing which page we have loaded, pass + // true for aCloneChildren. + nsresult rv = AddToSessionHistory( + aNewURI, nullptr, + aDocument->NodePrincipal(), // triggeringPrincipal + nullptr, nullptr, csp, true, getter_AddRefs(newSHEntry)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE); + + // Session history entries created by pushState inherit scroll restoration + // mode from the current entry. + newSHEntry->SetScrollRestorationIsManual(scrollRestorationIsManual); + + nsString title; + mOSHE->GetTitle(title); + + // Set the new SHEntry's title (bug 655273). + newSHEntry->SetTitle(title); + + // Link the new SHEntry to the old SHEntry's BFCache entry, since the + // two entries correspond to the same document. + NS_ENSURE_SUCCESS(newSHEntry->AdoptBFCacheEntry(oldOSHE), + NS_ERROR_FAILURE); + + // AddToSessionHistory may not modify mOSHE. In case it doesn't, + // we'll just set mOSHE here. + mOSHE = newSHEntry; + } + } else if (mozilla::SessionHistoryInParent()) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p UpdateActiveEntry (replacing) mActiveEntry %p", + this, mActiveEntry.get())); + // Setting the resultPrincipalURI to nullptr is fine here: it will cause + // NS_GetFinalChannelURI to use the originalURI as the URI, which is aNewURI + // in our case. We could also set it to aNewURI, with the same result. + // We don't use aTitle here, see bug 544535. + nsString title; + nsCOMPtr<nsIReferrerInfo> referrerInfo; + if (mActiveEntry) { + title = mActiveEntry->GetTitle(); + referrerInfo = mActiveEntry->GetReferrerInfo(); + } else { + referrerInfo = nullptr; + } + UpdateActiveEntry( + true, /* aPreviousScrollPos = */ Nothing(), aNewURI, aNewURI, + /* aReferrerInfo = */ referrerInfo, aDocument->NodePrincipal(), + aDocument->GetCsp(), title, + mActiveEntry && mActiveEntry->GetScrollRestorationIsManual(), aData, + uriWasModified); + } else { + // Step 3. + newSHEntry = mOSHE; + + MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p step 3", this)); + // Since we're not changing which page we have loaded, pass + // true for aCloneChildren. + if (!newSHEntry) { + nsresult rv = AddToSessionHistory( + aNewURI, nullptr, + aDocument->NodePrincipal(), // triggeringPrincipal + nullptr, nullptr, aDocument->GetCsp(), true, + getter_AddRefs(newSHEntry)); + NS_ENSURE_SUCCESS(rv, rv); + mOSHE = newSHEntry; + } + + newSHEntry->SetURI(aNewURI); + newSHEntry->SetOriginalURI(aNewURI); + // We replaced the URI of the entry, clear the unstripped URI as it + // shouldn't be used for reloads anymore. + newSHEntry->SetUnstrippedURI(nullptr); + // Setting the resultPrincipalURI to nullptr is fine here: it will cause + // NS_GetFinalChannelURI to use the originalURI as the URI, which is aNewURI + // in our case. We could also set it to aNewURI, with the same result. + newSHEntry->SetResultPrincipalURI(nullptr); + newSHEntry->SetLoadReplace(false); + } + + if (!mozilla::SessionHistoryInParent()) { + // Step 2.4 and 3: Modify new/original session history entry and clear its + // POST data, if there is any. + newSHEntry->SetStateData(aData); + newSHEntry->SetPostData(nullptr); + + newSHEntry->SetURIWasModified(uriWasModified); + + // Step E as described at the top of AddState: If aReplace is false, + // indicating that we're doing a pushState rather than a replaceState, + // notify bfcache that we've added a page to the history so it can evict + // content viewers if appropriate. Otherwise call ReplaceEntry so that we + // notify nsIHistoryListeners that an entry was replaced. We may not have a + // root session history if this call is coming from a document.open() in a + // docshell subtree that disables session history. + RefPtr<ChildSHistory> rootSH = GetRootSessionHistory(); + if (rootSH) { + rootSH->LegacySHistory()->EvictContentViewersOrReplaceEntry(newSHEntry, + aReplace); + } + } + + // Step 4: If the document's URI changed, update document's URI and update + // global history. + // + // We need to call FireOnLocationChange so that the browser's address bar + // gets updated and the back button is enabled, but we only need to + // explicitly call FireOnLocationChange if we're not calling SetCurrentURI, + // since SetCurrentURI will call FireOnLocationChange for us. + // + // Both SetCurrentURI(...) and FireDummyOnLocationChange() pass + // nullptr for aRequest param to FireOnLocationChange(...). Such an update + // notification is allowed only when we know docshell is not loading a new + // document and it requires LOCATION_CHANGE_SAME_DOCUMENT flag. Otherwise, + // FireOnLocationChange(...) breaks security UI. + // + // If the docshell is shutting down, don't update the document URI, as we + // can't load into a docshell that is being destroyed. + if (!aEqualURIs && !mIsBeingDestroyed) { + aDocument->SetDocumentURI(aNewURI); + SetCurrentURI(aNewURI, nullptr, /* aFireLocationChange */ true, + /* aIsInitialAboutBlank */ false, + GetSameDocumentNavigationFlags(aNewURI)); + + AddURIVisit(aNewURI, aCurrentURI, 0); + + // AddURIVisit doesn't set the title for the new URI in global history, + // so do that here. + UpdateGlobalHistoryTitle(aNewURI); + + // Inform the favicon service that our old favicon applies to this new + // URI. + CopyFavicon(aCurrentURI, aNewURI, UsePrivateBrowsing()); + } else { + FireDummyOnLocationChange(); + } + aDocument->SetStateObject(aData); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCurrentScrollRestorationIsManual(bool* aIsManual) { + if (mozilla::SessionHistoryInParent()) { + *aIsManual = mActiveEntry && mActiveEntry->GetScrollRestorationIsManual(); + return NS_OK; + } + + *aIsManual = false; + if (mOSHE) { + return mOSHE->GetScrollRestorationIsManual(aIsManual); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCurrentScrollRestorationIsManual(bool aIsManual) { + SetScrollRestorationIsManualOnHistoryEntry(mOSHE, aIsManual); + + return NS_OK; +} + +void nsDocShell::SetScrollRestorationIsManualOnHistoryEntry( + nsISHEntry* aSHEntry, bool aIsManual) { + if (aSHEntry) { + aSHEntry->SetScrollRestorationIsManual(aIsManual); + } + + if (mActiveEntry && mBrowsingContext) { + mActiveEntry->SetScrollRestorationIsManual(aIsManual); + if (XRE_IsParentProcess()) { + SessionHistoryEntry* entry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetScrollRestorationIsManual(aIsManual); + } + } else { + mozilla::Unused << ContentChild::GetSingleton() + ->SendSessionHistoryEntryScrollRestorationIsManual( + mBrowsingContext, aIsManual); + } + } +} + +void nsDocShell::SetCacheKeyOnHistoryEntry(nsISHEntry* aSHEntry, + uint32_t aCacheKey) { + if (aSHEntry) { + aSHEntry->SetCacheKey(aCacheKey); + } + + if (mActiveEntry && mBrowsingContext) { + mActiveEntry->SetCacheKey(aCacheKey); + if (XRE_IsParentProcess()) { + SessionHistoryEntry* entry = + mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry(); + if (entry) { + entry->SetCacheKey(aCacheKey); + } + } else { + mozilla::Unused + << ContentChild::GetSingleton()->SendSessionHistoryEntryCacheKey( + mBrowsingContext, aCacheKey); + } + } +} + +/* static */ +bool nsDocShell::ShouldAddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel) { + // I believe none of the about: urls should go in the history. But then + // that could just be me... If the intent is only deny about:blank then we + // should just do a spec compare, rather than two gets of the scheme and + // then the path. -Gagan + nsresult rv; + nsAutoCString buf; + + rv = aURI->GetScheme(buf); + if (NS_FAILED(rv)) { + return false; + } + + if (buf.EqualsLiteral("about")) { + rv = aURI->GetPathQueryRef(buf); + if (NS_FAILED(rv)) { + return false; + } + + if (buf.EqualsLiteral("blank")) { + return false; + } + // We only want to add about:newtab if it's not privileged, and + // if it is not configured to show the blank page. + if (buf.EqualsLiteral("newtab")) { + if (!StaticPrefs::browser_newtabpage_enabled()) { + return false; + } + + NS_ENSURE_TRUE(aChannel, false); + nsCOMPtr<nsIPrincipal> resultPrincipal; + rv = nsContentUtils::GetSecurityManager()->GetChannelResultPrincipal( + aChannel, getter_AddRefs(resultPrincipal)); + NS_ENSURE_SUCCESS(rv, false); + return !resultPrincipal->IsSystemPrincipal(); + } + } + + return true; +} + +nsresult nsDocShell::AddToSessionHistory( + nsIURI* aURI, nsIChannel* aChannel, nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + nsIPrincipal* aPartitionedPrincipalToInherit, + nsIContentSecurityPolicy* aCsp, bool aCloneChildren, + nsISHEntry** aNewEntry) { + MOZ_ASSERT(aURI, "uri is null"); + MOZ_ASSERT(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set"); + MOZ_DIAGNOSTIC_ASSERT(!mozilla::SessionHistoryInParent()); + +#if defined(DEBUG) + if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { + nsAutoCString chanName; + if (aChannel) { + aChannel->GetName(chanName); + } else { + chanName.AssignLiteral("<no channel>"); + } + + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]::AddToSessionHistory(\"%s\", [%s])\n", this, + aURI->GetSpecOrDefault().get(), chanName.get())); + } +#endif + + nsresult rv = NS_OK; + nsCOMPtr<nsISHEntry> entry; + + /* + * If this is a LOAD_FLAGS_REPLACE_HISTORY in a subframe, we use + * the existing SH entry in the page and replace the url and + * other vitalities. + */ + if (LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY) && + !mBrowsingContext->IsTop()) { + // This is a subframe + entry = mOSHE; + if (entry) { + entry->ClearEntry(); + } + } + + // Create a new entry if necessary. + if (!entry) { + entry = new nsSHEntry(); + } + + // Get the post data & referrer + nsCOMPtr<nsIInputStream> inputStream; + nsCOMPtr<nsIURI> originalURI; + nsCOMPtr<nsIURI> resultPrincipalURI; + nsCOMPtr<nsIURI> unstrippedURI; + bool loadReplace = false; + nsCOMPtr<nsIReferrerInfo> referrerInfo; + uint32_t cacheKey = 0; + nsCOMPtr<nsIPrincipal> triggeringPrincipal = aTriggeringPrincipal; + nsCOMPtr<nsIPrincipal> principalToInherit = aPrincipalToInherit; + nsCOMPtr<nsIPrincipal> partitionedPrincipalToInherit = + aPartitionedPrincipalToInherit; + nsCOMPtr<nsIContentSecurityPolicy> csp = aCsp; + bool expired = false; // by default the page is not expired + bool discardLayoutState = false; + nsCOMPtr<nsICacheInfoChannel> cacheChannel; + bool userActivation = false; + + if (aChannel) { + cacheChannel = do_QueryInterface(aChannel); + + /* If there is a caching channel, get the Cache Key and store it + * in SH. + */ + if (cacheChannel) { + cacheChannel->GetCacheKey(&cacheKey); + } + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + + // Check if the httpChannel is hiding under a multipartChannel + if (!httpChannel) { + GetHttpChannel(aChannel, getter_AddRefs(httpChannel)); + } + if (httpChannel) { + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel)); + if (uploadChannel) { + uploadChannel->GetUploadStream(getter_AddRefs(inputStream)); + } + httpChannel->GetOriginalURI(getter_AddRefs(originalURI)); + uint32_t loadFlags; + aChannel->GetLoadFlags(&loadFlags); + loadReplace = loadFlags & nsIChannel::LOAD_REPLACE; + rv = httpChannel->GetReferrerInfo(getter_AddRefs(referrerInfo)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + + discardLayoutState = ShouldDiscardLayoutState(httpChannel); + } + + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->LoadInfo(); + if (!triggeringPrincipal) { + triggeringPrincipal = loadInfo->TriggeringPrincipal(); + } + if (!csp) { + csp = loadInfo->GetCspToInherit(); + } + + loadInfo->GetResultPrincipalURI(getter_AddRefs(resultPrincipalURI)); + + loadInfo->GetUnstrippedURI(getter_AddRefs(unstrippedURI)); + + userActivation = loadInfo->GetHasValidUserGestureActivation(); + + // For now keep storing just the principal in the SHEntry. + if (!principalToInherit) { + if (loadInfo->GetLoadingSandboxed()) { + if (loadInfo->GetLoadingPrincipal()) { + principalToInherit = NullPrincipal::CreateWithInheritedAttributes( + loadInfo->GetLoadingPrincipal()); + } else { + // get the OriginAttributes + OriginAttributes attrs; + loadInfo->GetOriginAttributes(&attrs); + principalToInherit = NullPrincipal::Create(attrs); + } + } else { + principalToInherit = loadInfo->PrincipalToInherit(); + } + } + + if (!partitionedPrincipalToInherit) { + // XXXehsan is it correct to fall back to the principal to inherit in all + // cases? For example, what about the cases where we are using the load + // info's principal to inherit? Do we need to add a similar concept to + // load info for partitioned principal? + partitionedPrincipalToInherit = principalToInherit; + } + } + + nsAutoString srcdoc; + bool srcdocEntry = false; + nsCOMPtr<nsIURI> baseURI; + + nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(aChannel); + if (inStrmChan) { + bool isSrcdocChannel; + inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel); + if (isSrcdocChannel) { + inStrmChan->GetSrcdocData(srcdoc); + srcdocEntry = true; + inStrmChan->GetBaseURI(getter_AddRefs(baseURI)); + } else { + srcdoc.SetIsVoid(true); + } + } + /* If cache got a 'no-store', ask SH not to store + * HistoryLayoutState. By default, SH will set this + * flag to true and save HistoryLayoutState. + */ + bool saveLayoutState = !discardLayoutState; + + 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; + } + } + + // Title is set in nsDocShell::SetTitle() + entry->Create(aURI, // uri + u""_ns, // Title + inputStream, // Post data stream + cacheKey, // CacheKey + mContentTypeHint, // Content-type + triggeringPrincipal, // Channel or provided principal + principalToInherit, partitionedPrincipalToInherit, csp, + HistoryID(), GetCreatedDynamically(), originalURI, + resultPrincipalURI, unstrippedURI, loadReplace, referrerInfo, + srcdoc, srcdocEntry, baseURI, saveLayoutState, expired, + userActivation); + + if (mBrowsingContext->IsTop() && GetSessionHistory()) { + bool shouldPersist = ShouldAddToSessionHistory(aURI, aChannel); + Maybe<int32_t> previousEntryIndex; + Maybe<int32_t> loadedEntryIndex; + rv = GetSessionHistory()->LegacySHistory()->AddToRootSessionHistory( + aCloneChildren, mOSHE, mBrowsingContext, entry, mLoadType, + shouldPersist, &previousEntryIndex, &loadedEntryIndex); + + MOZ_ASSERT(NS_SUCCEEDED(rv), "Could not add entry to root session history"); + if (previousEntryIndex.isSome()) { + mPreviousEntryIndex = previousEntryIndex.value(); + } + if (loadedEntryIndex.isSome()) { + mLoadedEntryIndex = loadedEntryIndex.value(); + } + + // aCloneChildren implies that we are retaining the same document, thus we + // need to signal to the top WC that the new SHEntry may receive a fresh + // user interaction flag. + if (aCloneChildren) { + WindowContext* topWc = mBrowsingContext->GetTopWindowContext(); + if (topWc && !topWc->IsDiscarded()) { + MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false)); + } + } + } else { + // This is a subframe, make sure that this new SHEntry will be + // marked with user interaction. + WindowContext* topWc = mBrowsingContext->GetTopWindowContext(); + if (topWc && !topWc->IsDiscarded()) { + MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(false)); + } + if (!mOSHE || !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) { + rv = AddChildSHEntryToParent(entry, mBrowsingContext->ChildOffset(), + aCloneChildren); + } + } + + // Return the new SH entry... + if (aNewEntry) { + *aNewEntry = nullptr; + if (NS_SUCCEEDED(rv)) { + entry.forget(aNewEntry); + } + } + + return rv; +} + +void nsDocShell::UpdateActiveEntry( + bool aReplace, const Maybe<nsPoint>& aPreviousScrollPos, nsIURI* aURI, + nsIURI* aOriginalURI, nsIReferrerInfo* aReferrerInfo, + nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp, + const nsAString& aTitle, bool aScrollRestorationIsManual, + nsIStructuredCloneContainer* aData, bool aURIWasModified) { + MOZ_ASSERT(mozilla::SessionHistoryInParent()); + MOZ_ASSERT(aURI, "uri is null"); + MOZ_ASSERT(mLoadType == LOAD_PUSHSTATE, + "This code only deals with pushState"); + MOZ_ASSERT_IF(aPreviousScrollPos.isSome(), !aReplace); + + MOZ_LOG(gSHLog, LogLevel::Debug, + ("Creating an active entry on nsDocShell %p to %s", this, + aURI->GetSpecOrDefault().get())); + + // Even if we're replacing an existing entry we create new a + // SessionHistoryInfo. In the parent process we'll keep the existing + // SessionHistoryEntry, but just replace its SessionHistoryInfo, that way the + // entry keeps identity but its data is replaced. + bool replace = aReplace && mActiveEntry; + + if (!replace) { + CollectWireframe(); + } + + if (mActiveEntry) { + // Link this entry to the previous active entry. + mActiveEntry = MakeUnique<SessionHistoryInfo>(*mActiveEntry, aURI); + } else { + mActiveEntry = MakeUnique<SessionHistoryInfo>( + aURI, aTriggeringPrincipal, nullptr, nullptr, aCsp, mContentTypeHint); + } + mActiveEntry->SetOriginalURI(aOriginalURI); + mActiveEntry->SetUnstrippedURI(nullptr); + mActiveEntry->SetReferrerInfo(aReferrerInfo); + mActiveEntry->SetTitle(aTitle); + mActiveEntry->SetStateData(static_cast<nsStructuredCloneContainer*>(aData)); + mActiveEntry->SetURIWasModified(aURIWasModified); + mActiveEntry->SetScrollRestorationIsManual(aScrollRestorationIsManual); + + if (replace) { + mBrowsingContext->ReplaceActiveSessionHistoryEntry(mActiveEntry.get()); + } else { + mBrowsingContext->IncrementHistoryEntryCountForBrowsingContext(); + // FIXME We should probably just compute mChildOffset in the parent + // instead of passing it over IPC here. + mBrowsingContext->SetActiveSessionHistoryEntry( + aPreviousScrollPos, mActiveEntry.get(), mLoadType, + /* aCacheKey = */ 0); + // FIXME Do we need to update mPreviousEntryIndex and mLoadedEntryIndex? + } +} + +nsresult nsDocShell::LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType, + bool aUserActivation) { + NS_ENSURE_TRUE(aEntry, NS_ERROR_FAILURE); + + nsresult rv; + RefPtr<nsDocShellLoadState> loadState; + rv = aEntry->CreateLoadInfo(getter_AddRefs(loadState)); + NS_ENSURE_SUCCESS(rv, rv); + + // Calling CreateAboutBlankContentViewer can set mOSHE to null, and if + // that's the only thing holding a ref to aEntry that will cause aEntry to + // die while we're loading it. So hold a strong ref to aEntry here, just + // in case. + nsCOMPtr<nsISHEntry> kungFuDeathGrip(aEntry); + + loadState->SetHasValidUserGestureActivation( + loadState->HasValidUserGestureActivation() || aUserActivation); + + return LoadHistoryEntry(loadState, aLoadType, aEntry == mOSHE); +} + +nsresult nsDocShell::LoadHistoryEntry(const LoadingSessionHistoryInfo& aEntry, + uint32_t aLoadType, + bool aUserActivation) { + RefPtr<nsDocShellLoadState> loadState = aEntry.CreateLoadInfo(); + loadState->SetHasValidUserGestureActivation( + loadState->HasValidUserGestureActivation() || aUserActivation); + + return LoadHistoryEntry(loadState, aLoadType, aEntry.mLoadingCurrentEntry); +} + +nsresult nsDocShell::LoadHistoryEntry(nsDocShellLoadState* aLoadState, + uint32_t aLoadType, + bool aLoadingCurrentEntry) { + if (!IsNavigationAllowed()) { + return NS_OK; + } + + // We are setting load type afterwards so we don't have to + // send it in an IPC message + aLoadState->SetLoadType(aLoadType); + + nsresult rv; + if (SchemeIsJavascript(aLoadState->URI())) { + // We're loading a URL that will execute script from inside asyncOpen. + // Replace the current document with about:blank now to prevent + // anything from the current document from leaking into any JavaScript + // code in the URL. + // Don't cache the presentation if we're going to just reload the + // current entry. Caching would lead to trying to save the different + // content viewers in the same nsISHEntry object. + rv = CreateAboutBlankContentViewer( + aLoadState->PrincipalToInherit(), + aLoadState->PartitionedPrincipalToInherit(), nullptr, nullptr, + /* aIsInitialDocument */ false, Nothing(), !aLoadingCurrentEntry); + + if (NS_FAILED(rv)) { + // The creation of the intermittent about:blank content + // viewer failed for some reason (potentially because the + // user prevented it). Interrupt the history load. + return NS_OK; + } + + if (!aLoadState->TriggeringPrincipal()) { + // Ensure that we have a triggeringPrincipal. Otherwise javascript: + // URIs will pick it up from the about:blank page we just loaded, + // and we don't really want even that in this case. + nsCOMPtr<nsIPrincipal> principal = + NullPrincipal::Create(GetOriginAttributes()); + aLoadState->SetTriggeringPrincipal(principal); + } + } + + /* If there is a valid postdata *and* the user pressed + * reload or shift-reload, take user's permission before we + * repost the data to the server. + */ + if ((aLoadType & LOAD_CMD_RELOAD) && aLoadState->PostDataStream()) { + bool repost; + rv = ConfirmRepost(&repost); + if (NS_FAILED(rv)) { + return rv; + } + + // If the user pressed cancel in the dialog, return. We're done here. + if (!repost) { + return NS_BINDING_ABORTED; + } + } + + // If there is no valid triggeringPrincipal, we deny the load + MOZ_ASSERT(aLoadState->TriggeringPrincipal(), + "need a valid triggeringPrincipal to load from history"); + if (!aLoadState->TriggeringPrincipal()) { + return NS_ERROR_FAILURE; + } + + return InternalLoad(aLoadState); // No nsIRequest +} + +NS_IMETHODIMP +nsDocShell::PersistLayoutHistoryState() { + nsresult rv = NS_OK; + + if (mozilla::SessionHistoryInParent() ? !!mActiveEntry : !!mOSHE) { + bool scrollRestorationIsManual; + if (mozilla::SessionHistoryInParent()) { + scrollRestorationIsManual = mActiveEntry->GetScrollRestorationIsManual(); + } else { + scrollRestorationIsManual = mOSHE->GetScrollRestorationIsManual(); + } + nsCOMPtr<nsILayoutHistoryState> layoutState; + if (RefPtr<PresShell> presShell = GetPresShell()) { + rv = presShell->CaptureHistoryState(getter_AddRefs(layoutState)); + } else if (scrollRestorationIsManual) { + // Even if we don't have layout anymore, we may want to reset the + // current scroll state in layout history. + GetLayoutHistoryState(getter_AddRefs(layoutState)); + } + + if (scrollRestorationIsManual && layoutState) { + layoutState->ResetScrollState(); + } + } + + return rv; +} + +void nsDocShell::SwapHistoryEntries(nsISHEntry* aOldEntry, + nsISHEntry* aNewEntry) { + if (aOldEntry == mOSHE) { + mOSHE = aNewEntry; + } + + if (aOldEntry == mLSHE) { + mLSHE = aNewEntry; + } +} + +void nsDocShell::SetHistoryEntryAndUpdateBC(const Maybe<nsISHEntry*>& aLSHE, + const Maybe<nsISHEntry*>& aOSHE) { + // We want to hold on to the reference in mLSHE before we update it. + // Otherwise, SetHistoryEntry could release the last reference to + // the entry while aOSHE is pointing to it. + nsCOMPtr<nsISHEntry> deathGripOldLSHE; + if (aLSHE.isSome()) { + deathGripOldLSHE = SetHistoryEntry(&mLSHE, aLSHE.value()); + MOZ_ASSERT(mLSHE.get() == aLSHE.value()); + } + nsCOMPtr<nsISHEntry> deathGripOldOSHE; + if (aOSHE.isSome()) { + deathGripOldOSHE = SetHistoryEntry(&mOSHE, aOSHE.value()); + MOZ_ASSERT(mOSHE.get() == aOSHE.value()); + } +} + +already_AddRefed<nsISHEntry> nsDocShell::SetHistoryEntry( + nsCOMPtr<nsISHEntry>* aPtr, nsISHEntry* aEntry) { + // We need to sync up the docshell and session history trees for + // subframe navigation. If the load was in a subframe, we forward up to + // the root docshell, which will then recursively sync up all docshells + // to their corresponding entries in the new session history tree. + // If we don't do this, then we can cache a content viewer on the wrong + // cloned entry, and subsequently restore it at the wrong time. + RefPtr<BrowsingContext> topBC = mBrowsingContext->Top(); + if (topBC->IsDiscarded()) { + topBC = nullptr; + } + RefPtr<BrowsingContext> currBC = + mBrowsingContext->IsDiscarded() ? nullptr : mBrowsingContext; + if (topBC && *aPtr) { + (*aPtr)->SyncTreesForSubframeNavigation(aEntry, topBC, currBC); + } + nsCOMPtr<nsISHEntry> entry(aEntry); + entry.swap(*aPtr); + return entry.forget(); +} + +already_AddRefed<ChildSHistory> nsDocShell::GetRootSessionHistory() { + RefPtr<ChildSHistory> childSHistory = + mBrowsingContext->Top()->GetChildSessionHistory(); + return childSHistory.forget(); +} + +nsresult nsDocShell::GetHttpChannel(nsIChannel* aChannel, + nsIHttpChannel** aReturn) { + NS_ENSURE_ARG_POINTER(aReturn); + if (!aChannel) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aChannel)); + if (multiPartChannel) { + nsCOMPtr<nsIChannel> baseChannel; + multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel)); + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(baseChannel)); + *aReturn = httpChannel; + NS_IF_ADDREF(*aReturn); + } + return NS_OK; +} + +bool nsDocShell::ShouldDiscardLayoutState(nsIHttpChannel* aChannel) { + // By default layout State will be saved. + if (!aChannel) { + return false; + } + + // figure out if SH should be saving layout state + bool noStore = false; + Unused << aChannel->IsNoStoreResponse(&noStore); + return noStore; +} + +NS_IMETHODIMP +nsDocShell::GetEditor(nsIEditor** aEditor) { + NS_ENSURE_ARG_POINTER(aEditor); + RefPtr<HTMLEditor> htmlEditor = GetHTMLEditorInternal(); + htmlEditor.forget(aEditor); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetEditor(nsIEditor* aEditor) { + HTMLEditor* htmlEditor = aEditor ? aEditor->GetAsHTMLEditor() : nullptr; + // If TextEditor comes, throw an error. + if (aEditor && !htmlEditor) { + return NS_ERROR_INVALID_ARG; + } + return SetHTMLEditorInternal(htmlEditor); +} + +HTMLEditor* nsDocShell::GetHTMLEditorInternal() { + return mEditorData ? mEditorData->GetHTMLEditor() : nullptr; +} + +nsresult nsDocShell::SetHTMLEditorInternal(HTMLEditor* aHTMLEditor) { + if (!aHTMLEditor && !mEditorData) { + return NS_OK; + } + + nsresult rv = EnsureEditorData(); + if (NS_FAILED(rv)) { + return rv; + } + + return mEditorData->SetHTMLEditor(aHTMLEditor); +} + +NS_IMETHODIMP +nsDocShell::GetEditable(bool* aEditable) { + NS_ENSURE_ARG_POINTER(aEditable); + *aEditable = mEditorData && mEditorData->GetEditable(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasEditingSession(bool* aHasEditingSession) { + NS_ENSURE_ARG_POINTER(aHasEditingSession); + + if (mEditorData) { + *aHasEditingSession = !!mEditorData->GetEditingSession(); + } else { + *aHasEditingSession = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::MakeEditable(bool aInWaitForUriLoad) { + nsresult rv = EnsureEditorData(); + if (NS_FAILED(rv)) { + return rv; + } + + return mEditorData->MakeEditable(aInWaitForUriLoad); +} + +/* static */ bool nsDocShell::ShouldAddURIVisit(nsIChannel* aChannel) { + bool needToAddURIVisit = true; + nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel)); + if (props) { + mozilla::Unused << props->GetPropertyAsBool( + u"docshell.needToAddURIVisit"_ns, &needToAddURIVisit); + } + + return needToAddURIVisit; +} + +/* static */ void nsDocShell::ExtractLastVisit( + nsIChannel* aChannel, nsIURI** aURI, uint32_t* aChannelRedirectFlags) { + nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel)); + if (!props) { + return; + } + + nsresult rv; + nsCOMPtr<nsIURI> uri(do_GetProperty(props, u"docshell.previousURI"_ns, &rv)); + if (NS_SUCCEEDED(rv)) { + uri.forget(aURI); + + rv = props->GetPropertyAsUint32(u"docshell.previousFlags"_ns, + aChannelRedirectFlags); + + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Could not fetch previous flags, URI will be treated like referrer"); + + } else { + // There is no last visit for this channel, so this must be the first + // link. Link the visit to the referrer of this request, if any. + // Treat referrer as null if there is an error getting it. + NS_GetReferrerFromChannel(aChannel, aURI); + } +} + +void nsDocShell::SaveLastVisit(nsIChannel* aChannel, nsIURI* aURI, + uint32_t aChannelRedirectFlags) { + nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel)); + if (!props || !aURI) { + return; + } + + props->SetPropertyAsInterface(u"docshell.previousURI"_ns, aURI); + props->SetPropertyAsUint32(u"docshell.previousFlags"_ns, + aChannelRedirectFlags); +} + +/* static */ void nsDocShell::InternalAddURIVisit( + nsIURI* aURI, nsIURI* aPreviousURI, uint32_t aChannelRedirectFlags, + uint32_t aResponseStatus, BrowsingContext* aBrowsingContext, + nsIWidget* aWidget, uint32_t aLoadType) { + MOZ_ASSERT(aURI, "Visited URI is null!"); + MOZ_ASSERT(aLoadType != LOAD_ERROR_PAGE && aLoadType != LOAD_BYPASS_HISTORY, + "Do not add error or bypass pages to global history"); + + bool usePrivateBrowsing = false; + aBrowsingContext->GetUsePrivateBrowsing(&usePrivateBrowsing); + + // Only content-type docshells save URI visits. Also don't do + // anything here if we're not supposed to use global history. + if (!aBrowsingContext->IsContent() || + !aBrowsingContext->GetUseGlobalHistory() || usePrivateBrowsing) { + return; + } + + nsCOMPtr<IHistory> history = components::History::Service(); + + if (history) { + uint32_t visitURIFlags = 0; + + if (aBrowsingContext->IsTop()) { + visitURIFlags |= IHistory::TOP_LEVEL; + } + + if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) { + visitURIFlags |= IHistory::REDIRECT_TEMPORARY; + } else if (aChannelRedirectFlags & + nsIChannelEventSink::REDIRECT_PERMANENT) { + visitURIFlags |= IHistory::REDIRECT_PERMANENT; + } else { + MOZ_ASSERT(!aChannelRedirectFlags, + "One of REDIRECT_TEMPORARY or REDIRECT_PERMANENT must be set " + "if any flags in aChannelRedirectFlags is set."); + } + + if (aResponseStatus >= 300 && aResponseStatus < 400) { + visitURIFlags |= IHistory::REDIRECT_SOURCE; + if (aResponseStatus == 301 || aResponseStatus == 308) { + visitURIFlags |= IHistory::REDIRECT_SOURCE_PERMANENT; + } + } + // Errors 400-501 and 505 are considered unrecoverable, in the sense a + // simple retry attempt by the user is unlikely to solve them. + // 408 is special cased, since may actually indicate a temporary + // connection problem. + else if (aResponseStatus != 408 && + ((aResponseStatus >= 400 && aResponseStatus <= 501) || + aResponseStatus == 505)) { + visitURIFlags |= IHistory::UNRECOVERABLE_ERROR; + } + + mozilla::Unused << history->VisitURI(aWidget, aURI, aPreviousURI, + visitURIFlags, + aBrowsingContext->BrowserId()); + } +} + +void nsDocShell::AddURIVisit(nsIURI* aURI, nsIURI* aPreviousURI, + uint32_t aChannelRedirectFlags, + uint32_t aResponseStatus) { + nsPIDOMWindowOuter* outer = GetWindow(); + nsCOMPtr<nsIWidget> widget = widget::WidgetUtils::DOMWindowToWidget(outer); + + InternalAddURIVisit(aURI, aPreviousURI, aChannelRedirectFlags, + aResponseStatus, mBrowsingContext, widget, mLoadType); +} + +//***************************************************************************** +// nsDocShell: Helper Routines +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::SetLoadType(uint32_t aLoadType) { + mLoadType = aLoadType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetLoadType(uint32_t* aLoadType) { + *aLoadType = mLoadType; + return NS_OK; +} + +nsresult nsDocShell::ConfirmRepost(bool* aRepost) { + if (StaticPrefs::dom_confirm_repost_testing_always_accept()) { + *aRepost = true; + return NS_OK; + } + + nsCOMPtr<nsIPromptCollection> prompter = + do_GetService("@mozilla.org/embedcomp/prompt-collection;1"); + if (!prompter) { + return NS_ERROR_NOT_AVAILABLE; + } + + return prompter->ConfirmRepost(mBrowsingContext, aRepost); +} + +nsresult nsDocShell::GetPromptAndStringBundle(nsIPrompt** aPrompt, + nsIStringBundle** aStringBundle) { + NS_ENSURE_SUCCESS(GetInterface(NS_GET_IID(nsIPrompt), (void**)aPrompt), + NS_ERROR_FAILURE); + + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::components::StringBundle::Service(); + NS_ENSURE_TRUE(stringBundleService, NS_ERROR_FAILURE); + + NS_ENSURE_SUCCESS( + stringBundleService->CreateBundle(kAppstringsBundleURL, aStringBundle), + NS_ERROR_FAILURE); + + return NS_OK; +} + +nsIScrollableFrame* nsDocShell::GetRootScrollFrame() { + PresShell* presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, nullptr); + + return presShell->GetRootScrollFrameAsScrollable(); +} + +nsresult nsDocShell::EnsureScriptEnvironment() { + if (mScriptGlobal) { + return NS_OK; + } + + if (mIsBeingDestroyed) { + return NS_ERROR_NOT_AVAILABLE; + } + +#ifdef DEBUG + NS_ASSERTION(!mInEnsureScriptEnv, + "Infinite loop! Calling EnsureScriptEnvironment() from " + "within EnsureScriptEnvironment()!"); + + // Yeah, this isn't re-entrant safe, but that's ok since if we + // re-enter this method, we'll infinitely loop... + AutoRestore<bool> boolSetter(mInEnsureScriptEnv); + mInEnsureScriptEnv = true; +#endif + + nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner)); + NS_ENSURE_TRUE(browserChrome, NS_ERROR_NOT_AVAILABLE); + + uint32_t chromeFlags; + browserChrome->GetChromeFlags(&chromeFlags); + + // If our window is modal and we're not opened as chrome, make + // this window a modal content window. + mScriptGlobal = nsGlobalWindowOuter::Create(this, mItemType == typeChrome); + MOZ_ASSERT(mScriptGlobal); + + // Ensure the script object is set up to run script. + return mScriptGlobal->EnsureScriptEnvironment(); +} + +nsresult nsDocShell::EnsureEditorData() { + MOZ_ASSERT(!mIsBeingDestroyed); + + bool openDocHasDetachedEditor = mOSHE && mOSHE->HasDetachedEditor(); + if (!mEditorData && !mIsBeingDestroyed && !openDocHasDetachedEditor) { + // We shouldn't recreate the editor data if it already exists, or + // we're shutting down, or we already have a detached editor data + // stored in the session history. We should only have one editordata + // per docshell. + mEditorData = MakeUnique<nsDocShellEditorData>(this); + } + + return mEditorData ? NS_OK : NS_ERROR_NOT_AVAILABLE; +} + +nsresult nsDocShell::EnsureFind() { + if (!mFind) { + mFind = new nsWebBrowserFind(); + } + + // we promise that the nsIWebBrowserFind that we return has been set + // up to point to the focused, or content window, so we have to + // set that up each time. + + nsIScriptGlobalObject* scriptGO = GetScriptGlobalObject(); + NS_ENSURE_TRUE(scriptGO, NS_ERROR_UNEXPECTED); + + // default to our window + nsCOMPtr<nsPIDOMWindowOuter> ourWindow = do_QueryInterface(scriptGO); + nsCOMPtr<nsPIDOMWindowOuter> windowToSearch; + nsFocusManager::GetFocusedDescendant(ourWindow, + nsFocusManager::eIncludeAllDescendants, + getter_AddRefs(windowToSearch)); + + nsCOMPtr<nsIWebBrowserFindInFrames> findInFrames = do_QueryInterface(mFind); + if (!findInFrames) { + return NS_ERROR_NO_INTERFACE; + } + + nsresult rv = findInFrames->SetRootSearchFrame(ourWindow); + if (NS_FAILED(rv)) { + return rv; + } + rv = findInFrames->SetCurrentSearchFrame(windowToSearch); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::IsBeingDestroyed(bool* aDoomed) { + NS_ENSURE_ARG(aDoomed); + *aDoomed = mIsBeingDestroyed; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsExecutingOnLoadHandler(bool* aResult) { + NS_ENSURE_ARG(aResult); + *aResult = mIsExecutingOnLoadHandler; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetLayoutHistoryState(nsILayoutHistoryState** aLayoutHistoryState) { + nsCOMPtr<nsILayoutHistoryState> state; + if (mozilla::SessionHistoryInParent()) { + if (mActiveEntry) { + state = mActiveEntry->GetLayoutHistoryState(); + } + } else { + if (mOSHE) { + state = mOSHE->GetLayoutHistoryState(); + } + } + state.forget(aLayoutHistoryState); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetLayoutHistoryState(nsILayoutHistoryState* aLayoutHistoryState) { + if (mOSHE) { + mOSHE->SetLayoutHistoryState(aLayoutHistoryState); + } + if (mActiveEntry) { + mActiveEntry->SetLayoutHistoryState(aLayoutHistoryState); + } + return NS_OK; +} + +nsDocShell::InterfaceRequestorProxy::InterfaceRequestorProxy( + nsIInterfaceRequestor* aRequestor) { + if (aRequestor) { + mWeakPtr = do_GetWeakReference(aRequestor); + } +} + +nsDocShell::InterfaceRequestorProxy::~InterfaceRequestorProxy() { + mWeakPtr = nullptr; +} + +NS_IMPL_ISUPPORTS(nsDocShell::InterfaceRequestorProxy, nsIInterfaceRequestor) + +NS_IMETHODIMP +nsDocShell::InterfaceRequestorProxy::GetInterface(const nsIID& aIID, + void** aSink) { + NS_ENSURE_ARG_POINTER(aSink); + nsCOMPtr<nsIInterfaceRequestor> ifReq = do_QueryReferent(mWeakPtr); + if (ifReq) { + return ifReq->GetInterface(aIID, aSink); + } + *aSink = nullptr; + return NS_NOINTERFACE; +} + +//***************************************************************************** +// nsDocShell::nsIAuthPromptProvider +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetAuthPrompt(uint32_t aPromptReason, const nsIID& aIID, + void** aResult) { + // a priority prompt request will override a false mAllowAuth setting + bool priorityPrompt = (aPromptReason == PROMPT_PROXY); + + if (!mAllowAuth && !priorityPrompt) { + return NS_ERROR_NOT_AVAILABLE; + } + + // we're either allowing auth, or it's a proxy request + nsresult rv; + nsCOMPtr<nsIPromptFactory> wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnsureScriptEnvironment(); + 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. + + return wwatch->GetPrompt(mScriptGlobal, aIID, + reinterpret_cast<void**>(aResult)); +} + +//***************************************************************************** +// nsDocShell::nsILoadContext +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetAssociatedWindow(mozIDOMWindowProxy** aWindow) { + CallGetInterface(this, aWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetTopWindow(mozIDOMWindowProxy** aWindow) { + return mBrowsingContext->GetTopWindow(aWindow); +} + +NS_IMETHODIMP +nsDocShell::GetTopFrameElement(Element** aElement) { + return mBrowsingContext->GetTopFrameElement(aElement); +} + +NS_IMETHODIMP +nsDocShell::GetUseTrackingProtection(bool* aUseTrackingProtection) { + return mBrowsingContext->GetUseTrackingProtection(aUseTrackingProtection); +} + +NS_IMETHODIMP +nsDocShell::SetUseTrackingProtection(bool aUseTrackingProtection) { + return mBrowsingContext->SetUseTrackingProtection(aUseTrackingProtection); +} + +NS_IMETHODIMP +nsDocShell::GetIsContent(bool* aIsContent) { + *aIsContent = (mItemType == typeContent); + return NS_OK; +} + +bool nsDocShell::IsOKToLoadURI(nsIURI* aURI) { + MOZ_ASSERT(aURI, "Must have a URI!"); + + if (!mFiredUnloadEvent) { + return true; + } + + if (!mLoadingURI) { + return false; + } + + bool isPrivateWin = false; + Document* doc = GetDocument(); + if (doc) { + isPrivateWin = + doc->NodePrincipal()->OriginAttributesRef().mPrivateBrowsingId > 0; + } + + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + return secMan && NS_SUCCEEDED(secMan->CheckSameOriginURI( + aURI, mLoadingURI, false, isPrivateWin)); +} + +// +// Routines for selection and clipboard +// +nsresult nsDocShell::GetControllerForCommand(const char* aCommand, + nsIController** aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + NS_ENSURE_TRUE(mScriptGlobal, NS_ERROR_FAILURE); + + nsCOMPtr<nsPIWindowRoot> root = mScriptGlobal->GetTopWindowRoot(); + NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); + + return root->GetControllerForCommand(aCommand, false /* for any window */, + aResult); +} + +NS_IMETHODIMP +nsDocShell::IsCommandEnabled(const char* aCommand, bool* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsIController> controller; + rv = GetControllerForCommand(aCommand, getter_AddRefs(controller)); + if (controller) { + rv = controller->IsCommandEnabled(aCommand, aResult); + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::DoCommand(const char* aCommand) { + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsIController> controller; + rv = GetControllerForCommand(aCommand, getter_AddRefs(controller)); + if (controller) { + rv = controller->DoCommand(aCommand); + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::DoCommandWithParams(const char* aCommand, + nsICommandParams* aParams) { + nsCOMPtr<nsIController> controller; + nsresult rv = GetControllerForCommand(aCommand, getter_AddRefs(controller)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsICommandController> commandController = + do_QueryInterface(controller, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return commandController->DoCommandWithParams(aCommand, aParams); +} + +nsresult nsDocShell::EnsureCommandHandler() { + if (!mCommandManager) { + if (nsCOMPtr<nsPIDOMWindowOuter> domWindow = GetWindow()) { + mCommandManager = new nsCommandManager(domWindow); + } + } + return mCommandManager ? NS_OK : NS_ERROR_FAILURE; +} + +// link handling + +class OnLinkClickEvent : public Runnable { + public: + OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent, + nsDocShellLoadState* aLoadState, bool aNoOpenerImplied, + bool aIsTrusted, nsIPrincipal* aTriggeringPrincipal); + + NS_IMETHOD Run() override { + AutoPopupStatePusher popupStatePusher(mPopupState); + + // We need to set up an AutoJSAPI here for the following reason: When we + // do OnLinkClickSync we'll eventually end up in + // nsGlobalWindow::OpenInternal which only does popup blocking if + // !LegacyIsCallerChromeOrNativeCode(). So we need to fake things so that + // we don't look like native code as far as LegacyIsCallerNativeCode() is + // concerned. + AutoJSAPI jsapi; + if (mIsTrusted || jsapi.Init(mContent->OwnerDoc()->GetScopeObject())) { + mHandler->OnLinkClickSync(mContent, mLoadState, mNoOpenerImplied, + mTriggeringPrincipal); + } + return NS_OK; + } + + private: + RefPtr<nsDocShell> mHandler; + nsCOMPtr<nsIContent> mContent; + RefPtr<nsDocShellLoadState> mLoadState; + nsCOMPtr<nsIPrincipal> mTriggeringPrincipal; + PopupBlocker::PopupControlState mPopupState; + bool mNoOpenerImplied; + bool mIsTrusted; +}; + +OnLinkClickEvent::OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent, + nsDocShellLoadState* aLoadState, + bool aNoOpenerImplied, bool aIsTrusted, + nsIPrincipal* aTriggeringPrincipal) + : mozilla::Runnable("OnLinkClickEvent"), + mHandler(aHandler), + mContent(aContent), + mLoadState(aLoadState), + mTriggeringPrincipal(aTriggeringPrincipal), + mPopupState(PopupBlocker::GetPopupControlState()), + mNoOpenerImplied(aNoOpenerImplied), + mIsTrusted(aIsTrusted) {} + +nsresult nsDocShell::OnLinkClick( + nsIContent* aContent, nsIURI* aURI, const nsAString& aTargetSpec, + const nsAString& aFileName, nsIInputStream* aPostDataStream, + nsIInputStream* aHeadersDataStream, bool aIsUserTriggered, bool aIsTrusted, + nsIPrincipal* aTriggeringPrincipal, nsIContentSecurityPolicy* aCsp) { +#ifndef ANDROID + MOZ_ASSERT(aTriggeringPrincipal, "Need a valid triggeringPrincipal"); +#endif + NS_ASSERTION(NS_IsMainThread(), "wrong thread"); + + if (!IsNavigationAllowed() || !IsOKToLoadURI(aURI)) { + return NS_OK; + } + + // On history navigation through Back/Forward buttons, don't execute + // automatic JavaScript redirection such as |anchorElement.click()| or + // |formElement.submit()|. + // + // XXX |formElement.submit()| bypasses this checkpoint because it calls + // nsDocShell::OnLinkClickSync(...) instead. + if (ShouldBlockLoadingForBackButton()) { + return NS_OK; + } + + if (aContent->IsEditable()) { + return NS_OK; + } + + bool noOpenerImplied = false; + nsAutoString target(aTargetSpec); + if (aFileName.IsVoid() && + ShouldOpenInBlankTarget(aTargetSpec, aURI, aContent, aIsUserTriggered)) { + target = u"_blank"; + if (!aTargetSpec.Equals(target)) { + noOpenerImplied = true; + } + } + + RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aURI); + loadState->SetTarget(target); + loadState->SetFileName(aFileName); + loadState->SetPostDataStream(aPostDataStream); + loadState->SetHeadersStream(aHeadersDataStream); + loadState->SetFirstParty(true); + loadState->SetTriggeringPrincipal( + aTriggeringPrincipal ? aTriggeringPrincipal : aContent->NodePrincipal()); + loadState->SetPrincipalToInherit(aContent->NodePrincipal()); + loadState->SetCsp(aCsp ? aCsp : aContent->GetCsp()); + loadState->SetAllowFocusMove(UserActivation::IsHandlingUserInput()); + + nsCOMPtr<nsIRunnable> ev = + new OnLinkClickEvent(this, aContent, loadState, noOpenerImplied, + aIsTrusted, aTriggeringPrincipal); + return Dispatch(TaskCategory::UI, ev.forget()); +} + +bool nsDocShell::ShouldOpenInBlankTarget(const nsAString& aOriginalTarget, + nsIURI* aLinkURI, nsIContent* aContent, + bool aIsUserTriggered) { + if (net::SchemeIsJavascript(aLinkURI)) { + return false; + } + + // External links from within app tabs should always open in new tabs + // instead of replacing the app tab's page (Bug 575561) + // nsIURI.host can throw for non-nsStandardURL nsIURIs. If we fail to + // get either host, just return false to use the original target. + nsAutoCString linkHost; + if (NS_FAILED(aLinkURI->GetHost(linkHost))) { + return false; + } + + // The targetTopLevelLinkClicksToBlank property on BrowsingContext allows + // privileged code to change the default targeting behaviour. In particular, + // if a user-initiated link click for the (or targetting the) top-level frame + // is detected, we default the target to "_blank" to give it a new + // top-level BrowsingContext. + if (mBrowsingContext->TargetTopLevelLinkClicksToBlank() && aIsUserTriggered && + ((aOriginalTarget.IsEmpty() && mBrowsingContext->IsTop()) || + aOriginalTarget == u"_top"_ns)) { + return true; + } + + // Don't modify non-default targets. + if (!aOriginalTarget.IsEmpty()) { + return false; + } + + // Only check targets that are in extension panels or app tabs. + // (isAppTab will be false for app tab subframes). + nsString mmGroup = mBrowsingContext->Top()->GetMessageManagerGroup(); + if (!mmGroup.EqualsLiteral("webext-browsers") && !mIsAppTab) { + return false; + } + + nsCOMPtr<nsIURI> docURI = aContent->OwnerDoc()->GetDocumentURIObject(); + if (!docURI) { + return false; + } + + nsAutoCString docHost; + if (NS_FAILED(docURI->GetHost(docHost))) { + return false; + } + + if (linkHost.Equals(docHost)) { + return false; + } + + // Special case: ignore "www" prefix if it is part of host string + return linkHost.Length() < docHost.Length() + ? !docHost.Equals("www."_ns + linkHost) + : !linkHost.Equals("www."_ns + docHost); +} + +static bool IsElementAnchorOrArea(nsIContent* aContent) { + // Make sure we are dealing with either an <A> or <AREA> element in the HTML + // or XHTML namespace. + return aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area); +} + +nsresult nsDocShell::OnLinkClickSync(nsIContent* aContent, + nsDocShellLoadState* aLoadState, + bool aNoOpenerImplied, + nsIPrincipal* aTriggeringPrincipal) { + if (!IsNavigationAllowed() || !IsOKToLoadURI(aLoadState->URI())) { + return NS_OK; + } + + // XXX When the linking node was HTMLFormElement, it is synchronous event. + // That is, the caller of this method is not |OnLinkClickEvent::Run()| + // but |HTMLFormElement::SubmitSubmission(...)|. + if (aContent->IsHTMLElement(nsGkAtoms::form) && + ShouldBlockLoadingForBackButton()) { + return NS_OK; + } + + if (aContent->IsEditable()) { + return NS_OK; + } + + // if the triggeringPrincipal is not passed explicitly, then we + // fall back to using doc->NodePrincipal() as the triggeringPrincipal. + nsCOMPtr<nsIPrincipal> triggeringPrincipal = + aTriggeringPrincipal ? aTriggeringPrincipal : aContent->NodePrincipal(); + + { + // defer to an external protocol handler if necessary... + nsCOMPtr<nsIExternalProtocolService> extProtService = + do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID); + if (extProtService) { + nsAutoCString scheme; + aLoadState->URI()->GetScheme(scheme); + if (!scheme.IsEmpty()) { + // if the URL scheme does not correspond to an exposed protocol, then + // we need to hand this link click over to the external protocol + // handler. + bool isExposed; + nsresult rv = + extProtService->IsExposedProtocol(scheme.get(), &isExposed); + if (NS_SUCCEEDED(rv) && !isExposed) { + return extProtService->LoadURI( + aLoadState->URI(), triggeringPrincipal, nullptr, mBrowsingContext, + /* aTriggeredExternally */ + false, + /* aHasValidUserGestureActivation */ + aContent->OwnerDoc()->HasValidTransientUserGestureActivation()); + } + } + } + } + uint32_t triggeringSandboxFlags = 0; + if (mBrowsingContext) { + triggeringSandboxFlags = aContent->OwnerDoc()->GetSandboxFlags(); + } + + uint32_t flags = INTERNAL_LOAD_FLAGS_NONE; + bool isElementAnchorOrArea = IsElementAnchorOrArea(aContent); + bool triggeringPrincipalIsSystemPrincipal = + aLoadState->TriggeringPrincipal()->IsSystemPrincipal(); + if (isElementAnchorOrArea) { + MOZ_ASSERT(aContent->IsHTMLElement()); + nsAutoString relString; + aContent->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, + relString); + nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok( + relString); + + bool targetBlank = aLoadState->Target().LowerCaseEqualsLiteral("_blank"); + bool explicitOpenerSet = false; + + // The opener behaviour follows a hierarchy, such that if a higher + // priority behaviour is specified, it always takes priority. That + // priority is currently: norefrerer > noopener > opener > default + + while (tok.hasMoreTokens()) { + const nsAString& token = tok.nextToken(); + if (token.LowerCaseEqualsLiteral("noreferrer")) { + flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER | + INTERNAL_LOAD_FLAGS_NO_OPENER; + // noreferrer cannot be overwritten by a 'rel=opener'. + explicitOpenerSet = true; + break; + } + + if (token.LowerCaseEqualsLiteral("noopener")) { + flags |= INTERNAL_LOAD_FLAGS_NO_OPENER; + explicitOpenerSet = true; + } + + if (targetBlank && StaticPrefs::dom_targetBlankNoOpener_enabled() && + token.LowerCaseEqualsLiteral("opener") && !explicitOpenerSet) { + explicitOpenerSet = true; + } + } + + if (targetBlank && StaticPrefs::dom_targetBlankNoOpener_enabled() && + !explicitOpenerSet && !triggeringPrincipalIsSystemPrincipal) { + flags |= INTERNAL_LOAD_FLAGS_NO_OPENER; + } + + if (aNoOpenerImplied) { + flags |= INTERNAL_LOAD_FLAGS_NO_OPENER; + } + } + + // Get the owner document of the link that was clicked, this will be + // the document that the link is in, or the last document that the + // link was in. From that document, we'll get the URI to use as the + // referrer, since the current URI in this docshell may be a + // new document that we're in the process of loading. + RefPtr<Document> referrerDoc = aContent->OwnerDoc(); + + // Now check that the referrerDoc's inner window is the current inner + // window for mScriptGlobal. If it's not, then we don't want to + // follow this link. + nsPIDOMWindowInner* referrerInner = referrerDoc->GetInnerWindow(); + if (!mScriptGlobal || !referrerInner || + mScriptGlobal->GetCurrentInnerWindow() != referrerInner) { + // We're no longer the current inner window + return NS_OK; + } + + // referrer could be null here in some odd cases, but that's ok, + // we'll just load the link w/o sending a referrer in those cases. + + // If this is an anchor element, grab its type property to use as a hint + nsAutoString typeHint; + RefPtr<HTMLAnchorElement> anchor = HTMLAnchorElement::FromNode(aContent); + if (anchor) { + anchor->GetType(typeHint); + NS_ConvertUTF16toUTF8 utf8Hint(typeHint); + nsAutoCString type, dummy; + NS_ParseRequestContentType(utf8Hint, type, dummy); + CopyUTF8toUTF16(type, typeHint); + } + + // Link click (or form submission) can be triggered inside an onload + // handler, and we don't want to add history entry in this case. + bool inOnLoadHandler = false; + GetIsExecutingOnLoadHandler(&inOnLoadHandler); + uint32_t loadType = inOnLoadHandler ? LOAD_NORMAL_REPLACE : LOAD_LINK; + + nsCOMPtr<nsIReferrerInfo> referrerInfo = + isElementAnchorOrArea ? new ReferrerInfo(*aContent->AsElement()) + : new ReferrerInfo(*referrerDoc); + RefPtr<WindowContext> context = mBrowsingContext->GetCurrentWindowContext(); + + aLoadState->SetTriggeringSandboxFlags(triggeringSandboxFlags); + aLoadState->SetReferrerInfo(referrerInfo); + aLoadState->SetInternalLoadFlags(flags); + aLoadState->SetTypeHint(NS_ConvertUTF16toUTF8(typeHint)); + aLoadState->SetLoadType(loadType); + aLoadState->SetSourceBrowsingContext(mBrowsingContext); + aLoadState->SetHasValidUserGestureActivation( + context && context->HasValidTransientUserGestureActivation()); + + nsresult rv = InternalLoad(aLoadState); + + if (NS_SUCCEEDED(rv)) { + nsPingListener::DispatchPings(this, aContent, aLoadState->URI(), + referrerInfo); + } + + return rv; +} + +nsresult nsDocShell::OnOverLink(nsIContent* aContent, nsIURI* aURI, + const nsAString& aTargetSpec) { + if (aContent->IsEditable()) { + return NS_OK; + } + + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsIWebBrowserChrome> browserChrome = do_GetInterface(mTreeOwner); + if (!browserChrome) { + return rv; + } + + nsCOMPtr<nsIURI> exposableURI = nsIOService::CreateExposableURI(aURI); + nsAutoCString spec; + rv = exposableURI->GetDisplaySpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ConvertUTF8toUTF16 uStr(spec); + + PredictorPredict(aURI, mCurrentURI, nsINetworkPredictor::PREDICT_LINK, + aContent->NodePrincipal()->OriginAttributesRef(), nullptr); + + rv = browserChrome->SetLinkStatus(uStr); + return rv; +} + +nsresult nsDocShell::OnLeaveLink() { + nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner)); + nsresult rv = NS_ERROR_FAILURE; + + if (browserChrome) { + rv = browserChrome->SetLinkStatus(u""_ns); + } + return rv; +} + +bool nsDocShell::ShouldBlockLoadingForBackButton() { + if (!(mLoadType & LOAD_CMD_HISTORY) || + UserActivation::IsHandlingUserInput() || + !Preferences::GetBool("accessibility.blockjsredirection")) { + return false; + } + + bool canGoForward = false; + GetCanGoForward(&canGoForward); + return canGoForward; +} + +bool nsDocShell::PluginsAllowedInCurrentDoc() { + if (!mContentViewer) { + return false; + } + + Document* doc = mContentViewer->GetDocument(); + if (!doc) { + return false; + } + + return doc->GetAllowPlugins(); +} + +//---------------------------------------------------------------------- +// Web Shell Services API + +// This functions is only called when a new charset is detected in loading a +// document. +nsresult nsDocShell::CharsetChangeReloadDocument( + mozilla::NotNull<const mozilla::Encoding*> aEncoding, int32_t aSource) { + // XXX hack. keep the aCharset and aSource wait to pick it up + nsCOMPtr<nsIContentViewer> cv; + NS_ENSURE_SUCCESS(GetContentViewer(getter_AddRefs(cv)), NS_ERROR_FAILURE); + if (cv) { + int32_t source; + Unused << cv->GetReloadEncodingAndSource(&source); + if (aSource > source) { + cv->SetReloadEncodingAndSource(aEncoding, aSource); + if (eCharsetReloadRequested != mCharsetReloadState) { + mCharsetReloadState = eCharsetReloadRequested; + switch (mLoadType) { + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + return Reload(LOAD_FLAGS_CHARSET_CHANGE | LOAD_FLAGS_BYPASS_CACHE | + LOAD_FLAGS_BYPASS_PROXY); + case LOAD_RELOAD_BYPASS_CACHE: + return Reload(LOAD_FLAGS_CHARSET_CHANGE | LOAD_FLAGS_BYPASS_CACHE); + default: + return Reload(LOAD_FLAGS_CHARSET_CHANGE); + } + } + } + } + // return failure if this request is not accepted due to mCharsetReloadState + return NS_ERROR_DOCSHELL_REQUEST_REJECTED; +} + +nsresult nsDocShell::CharsetChangeStopDocumentLoad() { + if (eCharsetReloadRequested != mCharsetReloadState) { + Stop(nsIWebNavigation::STOP_ALL); + return NS_OK; + } + // return failer if this request is not accepted due to mCharsetReloadState + return NS_ERROR_DOCSHELL_REQUEST_REJECTED; +} + +NS_IMETHODIMP nsDocShell::ExitPrintPreview() { +#if NS_PRINT_PREVIEW + nsCOMPtr<nsIWebBrowserPrint> viewer = do_QueryInterface(mContentViewer); + return viewer->ExitPrintPreview(); +#else + return NS_OK; +#endif +} + +/* [infallible] */ +NS_IMETHODIMP nsDocShell::GetIsTopLevelContentDocShell( + bool* aIsTopLevelContentDocShell) { + *aIsTopLevelContentDocShell = false; + + if (mItemType == typeContent) { + *aIsTopLevelContentDocShell = mBrowsingContext->IsTopContent(); + } + + return NS_OK; +} + +// Implements nsILoadContext.originAttributes +NS_IMETHODIMP +nsDocShell::GetScriptableOriginAttributes(JSContext* aCx, + JS::MutableHandle<JS::Value> aVal) { + return mBrowsingContext->GetScriptableOriginAttributes(aCx, aVal); +} + +// Implements nsIDocShell.GetOriginAttributes() +NS_IMETHODIMP +nsDocShell::GetOriginAttributes(JSContext* aCx, + JS::MutableHandle<JS::Value> aVal) { + return mBrowsingContext->GetScriptableOriginAttributes(aCx, aVal); +} + +bool nsDocShell::ServiceWorkerAllowedToControlWindow(nsIPrincipal* aPrincipal, + nsIURI* aURI) { + MOZ_ASSERT(aPrincipal); + MOZ_ASSERT(aURI); + + if (UsePrivateBrowsing() || mBrowsingContext->GetSandboxFlags()) { + return false; + } + + nsCOMPtr<nsIDocShellTreeItem> parent; + GetInProcessSameTypeParent(getter_AddRefs(parent)); + nsPIDOMWindowOuter* parentOuter = parent ? parent->GetWindow() : nullptr; + nsPIDOMWindowInner* parentInner = + parentOuter ? parentOuter->GetCurrentInnerWindow() : nullptr; + + StorageAccess storage = + StorageAllowedForNewWindow(aPrincipal, aURI, parentInner); + + // If the partitioned service worker is enabled, service worker is allowed to + // control the window if partition is enabled. + if (StaticPrefs::privacy_partition_serviceWorkers() && parentInner) { + RefPtr<Document> doc = parentInner->GetExtantDoc(); + + if (doc && StoragePartitioningEnabled(storage, doc->CookieJarSettings())) { + return true; + } + } + + return storage == StorageAccess::eAllow; +} + +nsresult nsDocShell::SetOriginAttributes(const OriginAttributes& aAttrs) { + MOZ_ASSERT(!mIsBeingDestroyed); + return mBrowsingContext->SetOriginAttributes(aAttrs); +} + +NS_IMETHODIMP +nsDocShell::ResumeRedirectedLoad(uint64_t aIdentifier, int32_t aHistoryIndex) { + RefPtr<nsDocShell> self = this; + RefPtr<ChildProcessChannelListener> cpcl = + ChildProcessChannelListener::GetSingleton(); + + // Call into InternalLoad with the pending channel when it is received. + cpcl->RegisterCallback( + aIdentifier, [self, aHistoryIndex]( + nsDocShellLoadState* aLoadState, + nsTArray<Endpoint<extensions::PStreamFilterParent>>&& + aStreamFilterEndpoints, + nsDOMNavigationTiming* aTiming) { + MOZ_ASSERT(aLoadState->GetPendingRedirectedChannel()); + if (NS_WARN_IF(self->mIsBeingDestroyed)) { + aLoadState->GetPendingRedirectedChannel()->CancelWithReason( + NS_BINDING_ABORTED, "nsDocShell::mIsBeingDestroyed"_ns); + return NS_BINDING_ABORTED; + } + + self->mLoadType = aLoadState->LoadType(); + nsCOMPtr<nsIURI> previousURI; + uint32_t previousFlags = 0; + ExtractLastVisit(aLoadState->GetPendingRedirectedChannel(), + getter_AddRefs(previousURI), &previousFlags); + self->SaveLastVisit(aLoadState->GetPendingRedirectedChannel(), + previousURI, previousFlags); + + if (aTiming) { + self->mTiming = new nsDOMNavigationTiming(self, aTiming); + self->mBlankTiming = false; + } + + // If we're performing a history load, locate the correct history entry, + // and set the relevant bits on our loadState. + if (aHistoryIndex >= 0 && self->GetSessionHistory() && + !mozilla::SessionHistoryInParent()) { + nsCOMPtr<nsISHistory> legacySHistory = + self->GetSessionHistory()->LegacySHistory(); + + nsCOMPtr<nsISHEntry> entry; + nsresult rv = legacySHistory->GetEntryAtIndex(aHistoryIndex, + getter_AddRefs(entry)); + if (NS_SUCCEEDED(rv)) { + legacySHistory->InternalSetRequestedIndex(aHistoryIndex); + aLoadState->SetLoadType(LOAD_HISTORY); + aLoadState->SetSHEntry(entry); + } + } + + self->InternalLoad(aLoadState); + + if (aLoadState->GetOriginalURIString().isSome()) { + // Save URI string in case it's needed later when + // sending to search engine service in EndPageLoad() + self->mOriginalUriString = *aLoadState->GetOriginalURIString(); + } + + for (auto& endpoint : aStreamFilterEndpoints) { + extensions::StreamFilterParent::Attach( + aLoadState->GetPendingRedirectedChannel(), std::move(endpoint)); + } + + // If the channel isn't pending, then it means that InternalLoad + // never connected it, and we shouldn't try to continue. This + // can happen even if InternalLoad returned NS_OK. + bool pending = false; + aLoadState->GetPendingRedirectedChannel()->IsPending(&pending); + NS_ASSERTION(pending, "We should have connected the pending channel!"); + if (!pending) { + return NS_BINDING_ABORTED; + } + return NS_OK; + }); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetOriginAttributes(JS::Handle<JS::Value> aOriginAttributes, + JSContext* aCx) { + OriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + return SetOriginAttributes(attrs); +} + +NS_IMETHODIMP +nsDocShell::GetAsyncPanZoomEnabled(bool* aOut) { + if (PresShell* presShell = GetPresShell()) { + *aOut = presShell->AsyncPanZoomEnabled(); + return NS_OK; + } + + // If we don't have a presShell, fall back to the default platform value of + // whether or not APZ is enabled. + *aOut = gfxPlatform::AsyncPanZoomEnabled(); + return NS_OK; +} + +bool nsDocShell::HasUnloadedParent() { + for (WindowContext* wc = GetBrowsingContext()->GetParentWindowContext(); wc; + wc = wc->GetParentWindowContext()) { + if (!wc->IsCurrent() || wc->IsDiscarded() || + wc->GetBrowsingContext()->IsDiscarded()) { + // If a parent is OOP and the parent WindowContext is no + // longer current, we can assume the parent was unloaded. + return true; + } + + if (wc->GetBrowsingContext()->IsInProcess() && + (!wc->GetBrowsingContext()->GetDocShell() || + wc->GetBrowsingContext()->GetDocShell()->GetIsInUnload())) { + return true; + } + } + return false; +} + +/* static */ +bool nsDocShell::ShouldUpdateGlobalHistory(uint32_t aLoadType) { + return !(aLoadType == LOAD_BYPASS_HISTORY || aLoadType == LOAD_ERROR_PAGE || + aLoadType & LOAD_CMD_HISTORY); +} + +void nsDocShell::UpdateGlobalHistoryTitle(nsIURI* aURI) { + if (!mBrowsingContext->GetUseGlobalHistory() || UsePrivateBrowsing()) { + return; + } + + // Global history is interested into sub-frame visits only for link-coloring + // purposes, thus title updates are skipped for those. + // + // Moreover, some iframe documents (such as the ones created via + // document.open()) inherit the document uri of the caller, which would cause + // us to override a previously set page title with one from the subframe. + if (IsSubframe()) { + return; + } + + if (nsCOMPtr<IHistory> history = components::History::Service()) { + history->SetURITitle(aURI, mTitle); + } +} + +bool nsDocShell::IsInvisible() { return mInvisible; } + +void nsDocShell::SetInvisible(bool aInvisible) { mInvisible = aInvisible; } + +// The caller owns |aAsyncCause| here. +void nsDocShell::NotifyJSRunToCompletionStart(const char* aReason, + const nsAString& aFunctionName, + const nsAString& aFilename, + const uint32_t aLineNumber, + JS::Handle<JS::Value> aAsyncStack, + const char* aAsyncCause) { + // If first start, mark interval start. + if (mJSRunToCompletionDepth == 0 && TimelineConsumers::HasConsumer(this)) { + TimelineConsumers::AddMarkerForDocShell( + this, mozilla::MakeUnique<JavascriptTimelineMarker>( + aReason, aFunctionName, aFilename, aLineNumber, + MarkerTracingType::START, aAsyncStack, aAsyncCause)); + } + + mJSRunToCompletionDepth++; +} + +void nsDocShell::NotifyJSRunToCompletionStop() { + mJSRunToCompletionDepth--; + + // If last stop, mark interval end. + if (mJSRunToCompletionDepth == 0 && TimelineConsumers::HasConsumer(this)) { + TimelineConsumers::AddMarkerForDocShell(this, "Javascript", + MarkerTracingType::END); + } +} + +/* static */ +void nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString& aProvider, + const nsString& aKeyword) { + if (aProvider.IsEmpty()) { + return; + } + nsresult rv; + nsCOMPtr<nsISupportsString> isupportsString = + do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID, &rv); + NS_ENSURE_SUCCESS_VOID(rv); + + rv = isupportsString->SetData(aProvider); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); + if (obsSvc) { + // Note that "keyword-search" refers to a search via the url + // bar, not a bookmarks keyword search. + obsSvc->NotifyObservers(isupportsString, "keyword-search", aKeyword.get()); + } +} + +NS_IMETHODIMP +nsDocShell::ShouldPrepareForIntercept(nsIURI* aURI, nsIChannel* aChannel, + bool* aShouldIntercept) { + return mInterceptController->ShouldPrepareForIntercept(aURI, aChannel, + aShouldIntercept); +} + +NS_IMETHODIMP +nsDocShell::ChannelIntercepted(nsIInterceptedChannel* aChannel) { + return mInterceptController->ChannelIntercepted(aChannel); +} + +bool nsDocShell::InFrameSwap() { + RefPtr<nsDocShell> shell = this; + do { + if (shell->mInFrameSwap) { + return true; + } + shell = shell->GetInProcessParentDocshell(); + } while (shell); + return false; +} + +UniquePtr<ClientSource> nsDocShell::TakeInitialClientSource() { + return std::move(mInitialClientSource); +} + +NS_IMETHODIMP +nsDocShell::GetEditingSession(nsIEditingSession** aEditSession) { + if (!NS_SUCCEEDED(EnsureEditorData())) { + return NS_ERROR_FAILURE; + } + + *aEditSession = do_AddRef(mEditorData->GetEditingSession()).take(); + return *aEditSession ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::GetScriptableBrowserChild(nsIBrowserChild** aBrowserChild) { + *aBrowserChild = GetBrowserChild().take(); + return *aBrowserChild ? NS_OK : NS_ERROR_FAILURE; +} + +already_AddRefed<nsIBrowserChild> nsDocShell::GetBrowserChild() { + nsCOMPtr<nsIBrowserChild> tc = do_QueryReferent(mBrowserChild); + return tc.forget(); +} + +nsCommandManager* nsDocShell::GetCommandManager() { + NS_ENSURE_SUCCESS(EnsureCommandHandler(), nullptr); + return mCommandManager; +} + +NS_IMETHODIMP_(void) +nsDocShell::GetOriginAttributes(mozilla::OriginAttributes& aAttrs) { + mBrowsingContext->GetOriginAttributes(aAttrs); +} + +HTMLEditor* nsIDocShell::GetHTMLEditor() { + nsDocShell* docShell = static_cast<nsDocShell*>(this); + return docShell->GetHTMLEditorInternal(); +} + +nsresult nsIDocShell::SetHTMLEditor(HTMLEditor* aHTMLEditor) { + nsDocShell* docShell = static_cast<nsDocShell*>(this); + return docShell->SetHTMLEditorInternal(aHTMLEditor); +} + +#define MATRIX_LENGTH 20 + +NS_IMETHODIMP +nsDocShell::SetColorMatrix(const nsTArray<float>& aMatrix) { + if (aMatrix.Length() == MATRIX_LENGTH) { + mColorMatrix.reset(new gfx::Matrix5x4()); + static_assert( + MATRIX_LENGTH * sizeof(float) == sizeof(mColorMatrix->components), + "Size mismatch for our memcpy"); + memcpy(mColorMatrix->components, aMatrix.Elements(), + sizeof(mColorMatrix->components)); + } else if (aMatrix.Length() == 0) { + mColorMatrix.reset(); + } else { + return NS_ERROR_INVALID_ARG; + } + + PresShell* presShell = GetPresShell(); + if (!presShell) { + return NS_ERROR_FAILURE; + } + + nsIFrame* frame = presShell->GetRootFrame(); + if (!frame) { + return NS_ERROR_FAILURE; + } + + frame->SchedulePaint(); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetColorMatrix(nsTArray<float>& aMatrix) { + if (mColorMatrix) { + aMatrix.SetLength(MATRIX_LENGTH); + static_assert( + MATRIX_LENGTH * sizeof(float) == sizeof(mColorMatrix->components), + "Size mismatch for our memcpy"); + memcpy(aMatrix.Elements(), mColorMatrix->components, + MATRIX_LENGTH * sizeof(float)); + } + + return NS_OK; +} + +#undef MATRIX_LENGTH + +NS_IMETHODIMP +nsDocShell::GetIsForceReloading(bool* aForceReload) { + *aForceReload = IsForceReloading(); + return NS_OK; +} + +bool nsDocShell::IsForceReloading() { return IsForceReloadType(mLoadType); } + +NS_IMETHODIMP +nsDocShell::GetBrowsingContextXPCOM(BrowsingContext** aBrowsingContext) { + *aBrowsingContext = do_AddRef(mBrowsingContext).take(); + return NS_OK; +} + +BrowsingContext* nsDocShell::GetBrowsingContext() { return mBrowsingContext; } + +bool nsDocShell::GetIsAttemptingToNavigate() { + // XXXbz the document.open spec says to abort even if there's just a + // queued navigation task, sort of. It's not clear whether browsers + // actually do that, and we didn't use to do it, so for now let's + // not do that. + // https://github.com/whatwg/html/issues/3447 tracks the spec side of this. + if (mDocumentRequest) { + // There's definitely a navigation in progress. + return true; + } + + // javascript: channels have slightly weird behavior: they're LOAD_BACKGROUND + // until the script runs, which means they're not sending loadgroup + // notifications and hence not getting set as mDocumentRequest. Look through + // our loadgroup for document-level javascript: loads. + if (!mLoadGroup) { + return false; + } + + nsCOMPtr<nsISimpleEnumerator> requests; + mLoadGroup->GetRequests(getter_AddRefs(requests)); + bool hasMore = false; + while (NS_SUCCEEDED(requests->HasMoreElements(&hasMore)) && hasMore) { + nsCOMPtr<nsISupports> elem; + requests->GetNext(getter_AddRefs(elem)); + nsCOMPtr<nsIScriptChannel> scriptChannel(do_QueryInterface(elem)); + if (!scriptChannel) { + continue; + } + + if (scriptChannel->GetIsDocumentLoad()) { + // This is a javascript: load that might lead to a new document, + // hence a navigation. + return true; + } + } + + return mCheckingSessionHistory; +} + +void nsDocShell::SetLoadingSessionHistoryInfo( + const mozilla::dom::LoadingSessionHistoryInfo& aLoadingInfo, + bool aNeedToReportActiveAfterLoadingBecomesActive) { + // FIXME Would like to assert this, but can't yet. + // MOZ_ASSERT(!mLoadingEntry); + MOZ_LOG(gSHLog, LogLevel::Debug, + ("Setting the loading entry on nsDocShell %p to %s", this, + aLoadingInfo.mInfo.GetURI()->GetSpecOrDefault().get())); + mLoadingEntry = MakeUnique<LoadingSessionHistoryInfo>(aLoadingInfo); + mNeedToReportActiveAfterLoadingBecomesActive = + aNeedToReportActiveAfterLoadingBecomesActive; +} + +void nsDocShell::MoveLoadingToActiveEntry(bool aPersist, bool aExpired, + uint32_t aCacheKey) { + MOZ_ASSERT(mozilla::SessionHistoryInParent()); + + MOZ_LOG(gSHLog, LogLevel::Debug, + ("nsDocShell %p MoveLoadingToActiveEntry", this)); + + bool hadActiveEntry = !!mActiveEntry; + mActiveEntry = nullptr; + mozilla::UniquePtr<mozilla::dom::LoadingSessionHistoryInfo> loadingEntry; + mActiveEntryIsLoadingFromSessionHistory = + mLoadingEntry && mLoadingEntry->mLoadIsFromSessionHistory; + if (mLoadingEntry) { + MOZ_LOG(gSHLog, LogLevel::Debug, + ("Moving the loading entry to the active entry on nsDocShell %p " + "to %s", + this, mLoadingEntry->mInfo.GetURI()->GetSpecOrDefault().get())); + mActiveEntry = MakeUnique<SessionHistoryInfo>(mLoadingEntry->mInfo); + mLoadingEntry.swap(loadingEntry); + if (!mActiveEntryIsLoadingFromSessionHistory) { + if (mNeedToReportActiveAfterLoadingBecomesActive) { + // Needed to pass various history length WPTs. + mBrowsingContext->SetActiveSessionHistoryEntry( + mozilla::Nothing(), mActiveEntry.get(), mLoadType, + /* aUpdatedCacheKey = */ 0, false); + } + mBrowsingContext->IncrementHistoryEntryCountForBrowsingContext(); + } + } + mNeedToReportActiveAfterLoadingBecomesActive = false; + + if (mActiveEntry) { + if (aCacheKey != 0) { + mActiveEntry->SetCacheKey(aCacheKey); + } + MOZ_ASSERT(loadingEntry); + uint32_t loadType = + mLoadType == LOAD_ERROR_PAGE ? mFailedLoadType : mLoadType; + + if (loadingEntry->mLoadId != UINT64_MAX) { + // We're passing in mCurrentURI, which could be null. SessionHistoryCommit + // does require a non-null uri if this is for a refresh load of the same + // URI, but in that case mCurrentURI won't be null here. + mBrowsingContext->SessionHistoryCommit( + *loadingEntry, loadType, mCurrentURI, hadActiveEntry, aPersist, false, + aExpired, aCacheKey); + } + } +} + +static bool IsFaviconLoad(nsIRequest* aRequest) { + nsCOMPtr<nsIChannel> channel = do_QueryInterface(aRequest); + if (!channel) { + return false; + } + + nsCOMPtr<nsILoadInfo> li = channel->LoadInfo(); + return li && li->InternalContentPolicyType() == + nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON; +} + +void nsDocShell::RecordSingleChannelId(bool aStartRequest, + nsIRequest* aRequest) { + // Ignore favicon loads, they don't need to block caching. + if (IsFaviconLoad(aRequest)) { + return; + } + + MOZ_ASSERT_IF(!aStartRequest, mRequestForBlockingFromBFCacheCount > 0); + + mRequestForBlockingFromBFCacheCount += aStartRequest ? 1 : -1; + + if (mBrowsingContext->GetCurrentWindowContext()) { + // We have three states: no request, one request with an id and + // eiher one request without an id or multiple requests. Nothing() is no + // request, Some(non-zero) is one request with an id and Some(0) is one + // request without an id or multiple requests. + Maybe<uint64_t> singleChannelId; + if (mRequestForBlockingFromBFCacheCount > 1) { + singleChannelId = Some(0); + } else if (mRequestForBlockingFromBFCacheCount == 1) { + nsCOMPtr<nsIIdentChannel> identChannel; + if (aStartRequest) { + identChannel = do_QueryInterface(aRequest); + } else { + // aChannel is the channel that's being removed, but we need to check if + // the remaining channel in the loadgroup has an id. + nsCOMPtr<nsISimpleEnumerator> requests; + mLoadGroup->GetRequests(getter_AddRefs(requests)); + for (const auto& request : SimpleEnumerator<nsIRequest>(requests)) { + if (!IsFaviconLoad(request) && + !!(identChannel = do_QueryInterface(request))) { + break; + } + } + } + + if (identChannel) { + singleChannelId = Some(identChannel->ChannelId()); + } else { + singleChannelId = Some(0); + } + } else { + MOZ_ASSERT(mRequestForBlockingFromBFCacheCount == 0); + singleChannelId = Nothing(); + } + + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Verbose))) { + nsAutoCString uri("[no uri]"); + if (mCurrentURI) { + uri = mCurrentURI->GetSpecOrDefault(); + } + if (singleChannelId.isNothing()) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Verbose, + ("Loadgroup for %s doesn't have any requests relevant for " + "blocking BFCache", + uri.get())); + } else if (singleChannelId.value() == 0) { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Verbose, + ("Loadgroup for %s has multiple requests relevant for blocking " + "BFCache", + uri.get())); + } else { + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Verbose, + ("Loadgroup for %s has one request with id %" PRIu64 + " relevant for blocking BFCache", + uri.get(), singleChannelId.value())); + } + } + + if (mSingleChannelId != singleChannelId) { + mSingleChannelId = singleChannelId; + WindowGlobalChild* wgc = + mBrowsingContext->GetCurrentWindowContext()->GetWindowGlobalChild(); + if (wgc) { + wgc->SendSetSingleChannelId(singleChannelId); + } + } + } +} + +NS_IMETHODIMP +nsDocShell::OnStartRequest(nsIRequest* aRequest) { + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Verbose))) { + nsAutoCString uri("[no uri]"); + if (mCurrentURI) { + uri = mCurrentURI->GetSpecOrDefault(); + } + nsAutoCString name; + aRequest->GetName(name); + MOZ_LOG(gSHIPBFCacheLog, LogLevel::Verbose, + ("Adding request %s to loadgroup for %s", name.get(), uri.get())); + } + RecordSingleChannelId(true, aRequest); + return nsDocLoader::OnStartRequest(aRequest); +} + +NS_IMETHODIMP +nsDocShell::OnStopRequest(nsIRequest* aRequest, nsresult aStatusCode) { + if (MOZ_UNLIKELY(MOZ_LOG_TEST(gSHIPBFCacheLog, LogLevel::Verbose))) { + nsAutoCString uri("[no uri]"); + if (mCurrentURI) { + uri = mCurrentURI->GetSpecOrDefault(); + } + nsAutoCString name; + aRequest->GetName(name); + MOZ_LOG( + gSHIPBFCacheLog, LogLevel::Verbose, + ("Removing request %s from loadgroup for %s", name.get(), uri.get())); + } + RecordSingleChannelId(false, aRequest); + return nsDocLoader::OnStopRequest(aRequest, aStatusCode); +} + +void nsDocShell::MaybeDisconnectChildListenersOnPageHide() { + MOZ_RELEASE_ASSERT(XRE_IsContentProcess()); + + if (mChannelToDisconnectOnPageHide != 0 && mLoadGroup) { + nsCOMPtr<nsISimpleEnumerator> requests; + mLoadGroup->GetRequests(getter_AddRefs(requests)); + for (const auto& request : SimpleEnumerator<nsIRequest>(requests)) { + RefPtr<DocumentChannel> channel = do_QueryObject(request); + if (channel && channel->ChannelId() == mChannelToDisconnectOnPageHide) { + static_cast<DocumentChannelChild*>(channel.get()) + ->DisconnectChildListeners(NS_BINDING_ABORTED, NS_BINDING_ABORTED); + } + } + mChannelToDisconnectOnPageHide = 0; + } +} |