Source code

Revision control

Copy as Markdown

Other Tools

/* 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>
#include "mozilla/CycleCollectedJSContext.h"
#include "mozilla/dom/HTMLFormElement.h"
#ifdef XP_WIN
# include <process.h>
# define getpid _getpid
#else
# include <unistd.h> // for getpid()
#endif
#include "nsDeviceContext.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/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/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/SchedulerGroup.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/ScrollContainerFrame.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/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/FragmentDirective.h"
#include "mozilla/dom/HTMLAnchorElement.h"
#include "mozilla/dom/HTMLIFrameElement.h"
#include "mozilla/dom/Navigation.h"
#include "mozilla/dom/NavigationBinding.h"
#include "mozilla/dom/NavigationHistoryEntry.h"
#include "mozilla/dom/NavigationUtils.h"
#include "mozilla/dom/PerformanceNavigation.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/PolicyContainer.h"
#include "mozilla/dom/PopupBlocker.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/TrustedTypeUtils.h"
#include "mozilla/dom/TrustedTypesConstants.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/glean/DocshellMetrics.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 "nsIClassifiedChannel.h"
#include "nsIClassOfService.h"
#include "nsIConsoleReportCollector.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsIContentSecurityPolicy.h"
#include "nsIController.h"
#include "nsIDocShellTreeItem.h"
#include "nsIDocShellTreeOwner.h"
#include "nsIDocumentViewer.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 "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 "nsScriptSecurityManager.h"
#include "nsIScrollObserver.h"
#include "nsISupportsPrimitives.h"
#include "nsISecureBrowserUI.h"
#include "nsISeekableStream.h"
#include "nsISelectionDisplay.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 "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 "nsAboutProtocolUtils.h"
#include "nsArray.h"
#include "nsArrayUtils.h"
#include "nsBrowserStatusFilter.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 "nsDocElementCreatedNotificationRunner.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 "nsGlobalWindowInner.h"
#include "nsGlobalWindowOuter.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 "nsSHistory.h"
#include "nsStructuredCloneContainer.h"
#include "nsSubDocumentFrame.h"
#include "nsURILoader.h"
#include "nsURLHelper.h"
#include "nsViewSourceHandler.h"
#include "nsWebBrowserFind.h"
#include "nsWhitespaceTokenizer.h"
#include "nsWidgetsCID.h"
#include "nsXULAppAPI.h"
#include "CertVerifier.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 "nsDocShellTelemetryUtils.h"
#include "nsIOpenWindowInfo.h"
#ifdef MOZ_PLACES
# include "mozilla/places/nsFaviconService.h"
# include "mozIPlacesPendingOperation.h"
#endif
#ifdef NS_PRINTING
# 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");
static mozilla::LazyLogModule gDocShellLog("nsDocShell");
#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 gDocShellAndDOMWindowLeakLogging(
"DocShellAndDOMWindowLeak");
#endif
static mozilla::LazyLogModule gDocShellLeakLog("nsDocShellLeak");
extern mozilla::LazyLogModule gPageCacheLog;
extern mozilla::LazyLogModule gNavigationAPILog;
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),
mChannelToDisconnectOnPageHide(0),
mCreatingDocument(false),
#ifdef DEBUG
mInEnsureScriptEnv(false),
#endif
mInitialized(false),
mAllowSubframes(true),
mAllowMetaRedirects(true),
mAllowImages(true),
mAllowMedia(true),
mAllowDNSPrefetch(true),
mAllowWindowControl(true),
mCSSErrorReportingEnabled(false),
mAllowAuth(mItemType == typeContent),
mAllowKeywordFixup(false),
mDisableMetaRefreshWhenInactive(false),
mWindowDraggingAllowed(false),
mInFrameSwap(false),
mFiredUnloadEvent(false),
mEODForCurrentDocument(false),
mURIResultedInDocument(false),
mIsBeingDestroyed(false),
mIsExecutingOnLoadHandler(false),
mInvisible(false),
mHasLoadedNonBlankURI(false),
mHasStartedLoadingOtherThanInitialBlankURI(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
}
void nsDocShell::DestroyDocumentViewer() {
if (!mDocumentViewer) {
return;
}
mDocumentViewer->Close();
mDocumentViewer->Destroy();
mDocumentViewer = nullptr;
}
nsDocShell::~nsDocShell() {
// Avoid notifying observers while we're in the dtor.
mIsBeingDestroyed = true;
Destroy();
DestroyDocumentViewer();
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
}
nsresult nsDocShell::InitWindow(nsIWidget* aParentWidget, int32_t aX,
int32_t aY, int32_t aWidth, int32_t aHeight,
nsIOpenWindowInfo* aOpenWindowInfo,
mozilla::dom::WindowGlobalChild* aWindowActor) {
SetParentWidget(aParentWidget);
SetPositionAndSize(aX, aY, aWidth, aHeight, 0);
return Initialize(aOpenWindowInfo, aWindowActor);
}
nsresult nsDocShell::Initialize(nsIOpenWindowInfo* aOpenWindowInfo,
mozilla::dom::WindowGlobalChild* aWindowActor) {
if (mInitialized) {
// We've already been initialized.
MOZ_ASSERT(!aOpenWindowInfo,
"Tried to reinitialize with override principal");
MOZ_ASSERT(!aWindowActor, "Tried to reinitialize with a window actor");
return NS_OK;
}
MOZ_ASSERT(aOpenWindowInfo,
"Must have openwindowinfo if not already initialized.");
NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome,
"Unexpected item type in docshell");
NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_NOT_INITIALIZED);
mInitialized = true;
mDisableMetaRefreshWhenInactive =
Preferences::GetBool("browser.meta_refresh_when_inactive.disabled",
mDisableMetaRefreshWhenInactive);
nsresult rv = CreateInitialDocumentViewer(aOpenWindowInfo, aWindowActor);
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 rv;
}
/* 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;
}
uint32_t notifyMask =
nsIWebProgress::NOTIFY_STATE_ALL | nsIWebProgress::NOTIFY_LOCATION |
nsIWebProgress::NOTIFY_SECURITY | nsIWebProgress::NOTIFY_STATUS;
// NOTE: Only listen for NOTIFY_PROGRESS on toplevel BrowsingContexts, as
// listeners in the browser UI only cares about total progress on the toplevel
// context. Aggregation of the total progress is currently handled within
// `nsDocLoader`, and does not take out-of-process iframes into account.
if (aBrowsingContext->IsTop()) {
notifyMask |= nsIWebProgress::NOTIFY_PROGRESS;
}
// 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, notifyMask);
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());
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,
mBCWebProgressStatusFilter)
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)) && VerifyDocumentViewer()) {
RefPtr<Document> doc = mDocumentViewer->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 (!aLoadState->URI()->SchemeIs("javascript")) {
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));
// FIXME Null check aLoadState->GetLoadingSessionHistoryInfo()?
return LoadHistoryEntry(*aLoadState->GetLoadingSessionHistoryInfo(),
aLoadState->LoadType(),
aLoadState->HasValidUserGestureActivation(),
aLoadState->NotifiedBeforeUnloadListeners());
}
// 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()) {
WindowContext* topWc = mBrowsingContext->GetTopWindowContext();
if (topWc && !topWc->IsDiscarded()) {
MOZ_ALWAYS_SUCCEEDS(topWc->SetSHEntryHasUserInteraction(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() &&
!aLoadState->HistoryBehavior()) {
// 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!
if (aLoadState->IsFormSubmission()) {
#ifdef DEBUG
if (!mEODForCurrentDocument) {
const MaybeDiscarded<BrowsingContext>& targetBC =
aLoadState->TargetBrowsingContext();
MOZ_ASSERT_IF(GetBrowsingContext() == targetBC.get(),
aLoadState->LoadType() == LOAD_NORMAL_REPLACE);
}
#endif
} else {
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 (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 && !GetIsAttemptingToNavigate()) {
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);
}
}
}
}
// 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.
if (mCurrentURI &&
(!NS_IsAboutBlank(mCurrentURI) || mLoadingEntry || mActiveEntry) &&
!aLoadState->HistoryBehavior()) {
// 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() {
FirePageHideNotificationInternal(false);
return NS_OK;
}
void nsDocShell::FirePageHideNotificationInternal(
bool aSkipCheckingDynEntries) {
{
nsAutoMicroTask mt;
SetOngoingNavigation(Nothing());
}
if (mDocumentViewer && !mFiredUnloadEvent) {
// Keep an explicit reference since calling PageHide could release
// mDocumentViewer
nsCOMPtr<nsIDocumentViewer> viewer(mDocumentViewer);
mFiredUnloadEvent = true;
if (mTiming) {
mTiming->NotifyUnloadEventStart();
}
viewer->PageHide(true);
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(true);
}
}
// If the document is unloading, remove all dynamic subframe entries.
if (!aSkipCheckingDynEntries) {
RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
if (rootSH) {
MOZ_LOG(
gSHLog, LogLevel::Debug,
("nsDocShell %p unloading, remove dynamic subframe entries", this));
if (mActiveEntry) {
mBrowsingContext->RemoveDynEntriesFromActiveSessionHistoryEntry();
}
MOZ_LOG(gSHLog, LogLevel::Debug,
("nsDocShell %p unloading, no active entries", this));
}
}
// 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;
}
if (RefPtr<nsGlobalWindowInner> inner =
nsGlobalWindowInner::Cast(mScriptGlobal->GetCurrentInnerWindow())) {
if (aThaw) {
inner->Thaw(false);
} else {
inner->Freeze(false);
}
}
}
void nsDocShell::FirePageHideShowNonRecursive(bool aShow) {
MOZ_ASSERT(mozilla::BFCacheInParent());
if (!mDocumentViewer) {
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<nsIDocumentViewer> viewer(mDocumentViewer);
if (aShow) {
viewer->SetIsHidden(false);
mRefreshURIList = std::move(mBFCachedRefreshURIList);
RefreshURIFromQueue();
mFiredUnloadEvent = false;
RefPtr<Document> doc = viewer->GetDocument();
if (doc) {
doc->NotifyActivityChanged();
nsCOMPtr<nsPIDOMWindowInner> inner =
mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindow() : nullptr;
if (mBrowsingContext->IsTop()) {
doc->NotifyPossibleTitleChange(false);
doc->SetLoadingOrRestoredFromBFCacheTimeStampToNow();
if (inner) {
// Now that we have found the inner window of the page restored
// from the history, we have to make sure that
// performance.navigation.type is 2.
// Traditionally this type change has been done to the top level page
// only.
Performance* performance = inner->GetPerformance();
if (performance) {
performance->GetDOMTiming()->NotifyRestoreStart();
}
}
}
nsCOMPtr<nsIChannel> channel = doc->GetChannel();
if (channel) {
SetLoadType(LOAD_HISTORY);
mEODForCurrentDocument = false;
mIsRestoringDocument = true;
mLoadGroup->AddRequest(channel, nullptr);
nsCOMPtr<nsIURI> uri;
if (doc->FragmentDirective()) {
// If we have fragment directives, then we've mutated the document
// uri. Set the current URI from session history instead.
uri = mActiveEntry ? mActiveEntry->GetURI() : nullptr;
}
if (!uri) {
uri = doc->GetDocumentURI();
}
SetCurrentURI(uri, 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;
viewer->PageHide(false);
RefPtr<PresShell> presShell = GetPresShell();
if (presShell) {
presShell->Freeze(false);
}
}
}
nsresult nsDocShell::Dispatch(already_AddRefed<nsIRunnable>&& aRunnable) {
nsCOMPtr<nsIRunnable> runnable(aRunnable);
if (NS_WARN_IF(!GetWindow())) {
// Window should only be unavailable after destroyed.
MOZ_ASSERT(mIsBeingDestroyed);
return NS_ERROR_FAILURE;
}
return SchedulerGroup::Dispatch(runnable.forget());
}
NS_IMETHODIMP
nsDocShell::DispatchLocationChangeEvent() {
return Dispatch(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;
}
nsPresContext* nsDocShell::GetEldestPresContext() {
nsIDocumentViewer* viewer = mDocumentViewer;
while (viewer) {
nsIDocumentViewer* prevViewer = viewer->GetPreviousViewer();
if (!prevViewer) {
return viewer->GetPresContext();
}
viewer = prevViewer;
}
return nullptr;
}
nsPresContext* nsDocShell::GetPresContext() {
if (!mDocumentViewer) {
return nullptr;
}
return mDocumentViewer->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::GetDocViewer(nsIDocumentViewer** aDocumentViewer) {
NS_ENSURE_ARG_POINTER(aDocumentViewer);
*aDocumentViewer = mDocumentViewer;
NS_IF_ADDREF(*aDocumentViewer);
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;
}
SetCurrentURIInternal(aURI);
#ifdef DEBUG
mLastOpenedURI = aURI;
#endif
if (!NS_IsAboutBlankAllowQueryAndFragment(mCurrentURI)) {
mHasLoadedNonBlankURI = true;
}
// Don't fire onLocationChange when creating a the initial about:blank
// document, as this can happen when it's not safe for us to run script.
// Note that if this initial about:blank isn't immediately navigated
// away from, the onLocationChange will be fired as part of committing
// to keeping the initial about:blank as the actual first initial
// navigation destination.
if (aIsInitialAboutBlank) {
MOZ_ASSERT(!mHasLoadedNonBlankURI && !aRequest && aLocationFlags == 0);
return false;
}
MOZ_ASSERT(nsContentUtils::IsSafeToRunScript());
if (aFireOnLocationChange) {
FireOnLocationChange(this, aRequest, aURI, aLocationFlags);
}
return !aFireOnLocationChange;
}
void nsDocShell::SetCurrentURIInternal(nsIURI* aURI) {
mCurrentURI = aURI;
if (mBrowsingContext) {
mBrowsingContext->ClearCachedValuesOfLocations();
}
}
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<nsIDocumentViewer> viewer;
GetDocViewer(getter_AddRefs(viewer));
if (!viewer) {
return NS_OK;
}
Document* doc = viewer->GetDocument();
if (!doc || doc->WillIgnoreCharsetOverride()) {
return NS_OK;
}
mForcedAutodetection = true;
nsIURI* uri = doc->GetOriginalURI();
bool isFileURL = uri && uri->SchemeIs("file");
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"));
break;
case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic:
case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic:
case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII:
case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content:
case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content:
case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII:
LOGCHARSETMENU(("TEXT:UnlabeledNonUtf8"));
break;
case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII:
LOGCHARSETMENU(("TEXT:UnlabeledNonUtf8TLD"));
break;
case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8:
case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII:
LOGCHARSETMENU(("TEXT:UnlabeledUtf8"));
break;
case kCharsetFromChannel:
if (encoding == UTF_8_ENCODING) {
LOGCHARSETMENU(("TEXT:ChannelUtf8"));
} else {
LOGCHARSETMENU(("TEXT:ChannelNonUtf8"));
}
break;
default:
LOGCHARSETMENU(("TEXT:Bug"));
break;
}
} else {
switch (charsetSource) {
case kCharsetFromInitialAutoDetectionASCII:
// Deliberately no final version
LOGCHARSETMENU(("HTML:UnlabeledAscii"));
break;
case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Generic:
case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Generic:
case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8GenericInitialWasASCII:
case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8Content:
case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8Content:
case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8ContentInitialWasASCII:
LOGCHARSETMENU(("HTML:UnlabeledNonUtf8"));
break;
case kCharsetFromInitialAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLD:
case kCharsetFromFinalAutoDetectionWouldNotHaveBeenUTF8DependedOnTLDInitialWasASCII:
LOGCHARSETMENU(("HTML:UnlabeledNonUtf8TLD"));
break;
case kCharsetFromInitialAutoDetectionWouldHaveBeenUTF8:
case kCharsetFromFinalAutoDetectionWouldHaveBeenUTF8InitialWasASCII:
LOGCHARSETMENU(("HTML:UnlabeledUtf8"));
break;
case kCharsetFromChannel:
if (encoding == UTF_8_ENCODING) {
LOGCHARSETMENU(("HTML:ChannelUtf8"));
} else {
LOGCHARSETMENU(("HTML:ChannelNonUtf8"));
}
break;
case kCharsetFromXmlDeclaration:
case kCharsetFromMetaTag:
if (isFileURL) {
LOGCHARSETMENU(("HTML:LocalLabeled"));
} else if (encoding == UTF_8_ENCODING) {
LOGCHARSETMENU(("HTML:MetaUtf8"));
} else {
LOGCHARSETMENU(("HTML:MetaNonUtf8"));
}
break;
default:
LOGCHARSETMENU(("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->GetRelevantGlobal(), 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::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;
}
bool nsDocShell::HasStartedLoadingOtherThanInitialBlankURI() {
return mHasStartedLoadingOtherThanInitialBlankURI;
}
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 (!mDocumentViewer) {
return NS_OK;
}
Document* doc = mDocumentViewer->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::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;
}
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;
}
nsresult nsDocShell::Now(DOMHighResTimeStamp* aWhen) {
*aWhen = (TimeStamp::Now() - TimeStamp::ProcessCreation()).ToMilliseconds();
return NS_OK;
}
NS_IMETHODIMP
nsDocShell::SetWindowDraggingAllowed(bool aValue) {
RefPtr<nsDocShell> parent;
if (!aValue && mItemType == typeChrome &&
!(parent = GetInProcessParentDocshell())) {
// 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;
if (mItemType == typeChrome && !(parent = GetInProcessParentDocshell())) {
// 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 (mDocumentViewer) {
Document* doc = mDocumentViewer->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() {
nsCOMPtr<nsPIDOMWindowInner> win =
mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindow() : nullptr;
if (win) {
Navigator* navigator = win->Navigator();
if (navigator) {
navigator->ClearPlatformCache();
}
}
return NS_OK;
}
NS_IMETHODIMP
nsDocShell::ClearCachedUserAgent() {
nsCOMPtr<nsPIDOMWindowInner> win =
mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindow() : nullptr;
if (win) {
Navigator* navigator = win->Navigator();
if (navigator) {
navigator->ClearUserAgentCache();
}
}
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->GetCurrentInnerWindow() &&
mScriptGlobal->GetCurrentInnerWindow()->GetExtantDoc()) {
MOZ_DIAGNOSTIC_ASSERT(
mScriptGlobal->GetCurrentInnerWindow()->GetClientInfo().isSome());
MOZ_DIAGNOSTIC_ASSERT(!mInitialClientSource);
return;
}
// Don't recreate the initial client source. We call this multiple times
// when DoChannelLoad() is called before CreateAboutBlankDocumentViewer.
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
// CreateAboutBlankDocumentViewer. 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, GetMainThreadSerialEventTarget(), 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.
MaybeInheritController(mInitialClientSource.get(), principal);
}
void VerifyCientPrincipalInfosMatch(const mozilla::ipc::PrincipalInfo& aLeft,
const mozilla::ipc::PrincipalInfo& aRight) {
// Inheriting a controller when the principals don't match would cause a
// crash. Let's do the checks earlier to crash here already instead of
// ClientSource::SetController. And assert each condition separately. See bug
// 1880012.
MOZ_RELEASE_ASSERT(aLeft.type() == aRight.type());
switch (aLeft.type()) {
case mozilla::ipc::PrincipalInfo::TContentPrincipalInfo: {
const mozilla::ipc::ContentPrincipalInfo& leftContent =
aLeft.get_ContentPrincipalInfo();
const mozilla::ipc::ContentPrincipalInfo& rightContent =
aRight.get_ContentPrincipalInfo();
MOZ_RELEASE_ASSERT(leftContent.attrs() == rightContent.attrs() &&
leftContent.originNoSuffix() ==
rightContent.originNoSuffix());
return;
}
case mozilla::ipc::PrincipalInfo::TNullPrincipalInfo: {
// null principal never matches
MOZ_RELEASE_ASSERT(false, "Clients have null principals");
return;
}
default: {
break;
}
}
}
void nsDocShell::MaybeInheritController(
mozilla::dom::ClientSource* aClientSource, nsIPrincipal* aPrincipal) {
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(aPrincipal, uri)) {
return;
}
VerifyCientPrincipalInfosMatch(aClientSource->Info().PrincipalInfo(),
controller->PrincipalInfo());
aClientSource->InheritController(controller.ref());
}
Maybe<ClientInfo> nsDocShell::GetInitialClientInfo() const {
if (mInitialClientSource) {
Maybe<ClientInfo> result;
result.emplace(mInitialClientSource->Info());
return result;
}
nsPIDOMWindowInner* innerWindow =
mScriptGlobal ? mScriptGlobal->GetCurrentInnerWindow() : nullptr;
Document* doc = innerWindow ? innerWindow->GetExtantDoc() : nullptr;
if (!doc || !doc->IsUncommittedInitialDocument()) {
// We won't reuse the inner window so let the channel determine one
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);
}
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;
// Step 4.4. Check if the loading entry has a name.
if (mLoadingEntry) {
name = mLoadingEntry->mInfo.GetName();
}
if (name.IsEmpty()) {
return;
}
// Step 4.4.1. Set the name to the browsing context.
(void)mBrowsingContext->SetName(name);
// Step 4.4.2. Clear the name of all entries that are contiguous and
// same-origin with the loading entry.
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 (XRE_IsParentProcess()) {
SessionHistoryEntry* entry =
mBrowsingContext->Canonical()->GetActiveSessionHistoryEntry();
if (entry) {
nsSHistory::WalkContiguousEntries(
entry, [&](SessionHistoryEntry* aEntry) { aEntry->SetName(name); });
}
} else {
// Ask parent process to store the name in entries.
(void)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::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 (!mDocumentViewer) {
return NS_OK;
}
Document* doc = mDocumentViewer->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);
}
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_TRUE(VerifyDocumentViewer(), nullptr);
return mDocumentViewer->GetDocument();
}
Document* nsDocShell::GetExtantDocument() {
return mDocumentViewer ? mDocumentViewer->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;
}
//-------------------------------------
//-- 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 (!mDocumentViewer) {
return true;
}
bool firingBeforeUnload;
mDocumentViewer->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, StaticPrefs::browser_navigation_requireUserInteraction());
MOZ_LOG(gSHLog, LogLevel::Verbose,
("nsDocShell %p CanGoBack()->%d", this, *aCanGoBack));
return NS_OK;
}
return NS_ERROR_FAILURE;
}
NS_IMETHODIMP
nsDocShell::GetCanGoBackIgnoringUserInteraction(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, false);
MOZ_LOG(gSHLog, LogLevel::Verbose,
("nsDocShell %p CanGoBackIgnoringUserInteraction()->%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, StaticPrefs::browser_navigation_requireUserInteraction());
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(nsIURI* 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));
MOZ_ASSERT(rv != NS_ERROR_MALFORMED_URI);
if (NS_FAILED(rv) || !loadState) {
return NS_ERROR_FAILURE;
}
// Set the captive portal tab flag on the browsing context if requested
if (loadState->GetIsCaptivePortalTab()) {
(void)mBrowsingContext->SetIsCaptivePortalTab(true);
}
return LoadURI(loadState, true);
}
NS_IMETHODIMP
nsDocShell::LoadURIFromScript(nsIURI* 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);
}
nsresult nsDocShell::FixupAndLoadURIString(
const nsAString& aURIString, 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, aURIString, aLoadURIOptions, getter_AddRefs(loadState));
uint32_t loadFlags = aLoadURIOptions.mLoadFlags;
if (NS_ERROR_MALFORMED_URI == rv) {
MOZ_LOG(gSHLog, LogLevel::Debug,
("Creating an active entry on nsDocShell %p to %s (because "
"we're showing an error page)",
this, NS_ConvertUTF16toUTF8(aURIString).get()));
// We need to store a session history entry. We don't have a valid URI, so
// we use about:blank instead.
nsCOMPtr<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();
}
UniquePtr<SessionHistoryInfo> previousActiveEntry(mActiveEntry.release());
mActiveEntry = MakeUnique<SessionHistoryInfo>(
uri, triggeringPrincipal, nullptr, nullptr, nullptr,
nsLiteralCString("text/html"));
mBrowsingContext->SetActiveSessionHistoryEntry(
Nothing(), mActiveEntry.get(), previousActiveEntry.get(),
MAKE_LOAD_TYPE(LOAD_NORMAL, loadFlags),
/* aUpdatedCacheKey = */ 0);
if (DisplayLoadError(rv, nullptr, PromiseFlatString(aURIString).get(),
nullptr) &&
(loadFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) {
return NS_ERROR_LOAD_SHOWED_ERRORPAGE;
}
}
if (NS_FAILED(rv) || !loadState) {
return NS_ERROR_FAILURE;
}
// Set the captive portal tab flag on the browsing context if requested
if (loadState->GetIsCaptivePortalTab()) {
(void)mBrowsingContext->SetIsCaptivePortalTab(true);
}
return LoadURI(loadState, true);
}
NS_IMETHODIMP
nsDocShell::FixupAndLoadURIStringFromScript(
const nsAString& aURIString, 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 FixupAndLoadURIString(aURIString, loadURIOptions);
}
void nsDocShell::UnblockEmbedderLoadEventForFailure(bool aFireFrameErrorEvent) {
// If we're not in a content frame, or are at a BrowsingContext tree boundary,
// such as the content-chrome boundary, don't fire the error event.
if (mBrowsingContext->IsTopContent() || mBrowsingContext->IsChrome()) {
return;
}
// If embedder is same-process, then unblocking the load event is already
// handled by nsDocLoader. Fire the error event on our embedder element if
// requested.
//
// XXX: Bug 1440212 is looking into potentially changing this behaviour to act
// more like the remote case when in-process.
RefPtr<Element> 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 &&
!mBrowsingContext->GetParentWindowContext()->IsInProcess()) {
(void)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_NET_EMPTY_RESPONSE == aError) {
NS_ENSURE_ARG_POINTER(aURI);
error = "httpErrorPage";
} else if (NS_ERROR_NET_ERROR_RESPONSE == aError) {
NS_ENSURE_ARG_POINTER(aURI);
error = "serverError";
} 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) {
// 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");
}
errorPage.Assign("certerror");
} else {
error = "nssFailure2";
}
} else if (NS_ERROR_PHISHING_URI == aError ||
NS_ERROR_MALWARE_URI == aError ||
NS_ERROR_UNWANTED_URI == aError ||
NS_ERROR_HARMFULADDON_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";
} else if (NS_ERROR_HARMFULADDON_URI == aError) {
error = "addonBlocked";
}
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 if (aError == NS_ERROR_RESTRICTED_CONTENT) {
errorPage.AssignLiteral("restricted");
error = "restrictedcontent";
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 the client is misconfigured
error = "clientSocketMisconfiguration";
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:
// Page blocked by policy
error = "blockedByPolicy";
break;
case NS_ERROR_DOM_COOP_FAILED:
error = "blockedByCOOP";
errorDescriptionID = "blockedByCORP";
break;
case NS_ERROR_DOM_COEP_FAILED:
error = "blockedByCOEP";
errorDescriptionID = "blockedByCORP";
break;
case NS_ERROR_DOM_INVALID_HEADER_VALUE:
error = "invalidHeaderValue";
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;
case NS_ERROR_BASIC_HTTP_AUTH_DISABLED:
error = "basicHttpAuthDisabled";
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;
}
glean::page::load_error
.Get(IsSubframe() ? "frame"_ns : "top"_ns,
mozilla::dom::LoadErrorToTelemetryLabel(aError))
.Add();
// 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 (aURI->SchemeIs("file")) {
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) &&
aURI->SchemeIs("https")) {
// 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) {
(void)mScriptGlobal->GetDoc();
}
// Display a message box
prompter->Alert(nullptr, messageStr.get());
}
return NS_OK;
}
#define PREF_SAFEBROWSING_ALLOWOVERRIDE "browser.safebrowsing.allowOverride"
nsresult nsDocShell::LoadErrorPage(nsIURI* aURI, const char16_t* aURL,
const char* aErrorPage,
const char* aErrorType,
const char16_t* aDescription,
const char* aCSSClass,
nsIChannel* aFailedChannel) {
if (mIsBeingDestroyed) {
return NS_ERROR_NOT_AVAILABLE;
}
#if defined(DEBUG)
if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) {
nsAutoCString chanName;
if (aFailedChannel) {
aFailedChannel->GetName(chanName);
} else {
chanName.AssignLiteral("<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<nsIWritablePropertyBag2> props(do_QueryInterface(aFailedChannel));
if (props) {
nsAutoCString addonName;
props->GetPropertyAsACString(u"blockedExtension"_ns, addonName);
nsCString escapedAddonName;
SAFE_ESCAPE(escapedAddonName, addonName, url_Path);
errorPageUrl.AppendLiteral("&a=");
errorPageUrl.AppendASCII(escapedAddonName.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;
RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(aErrorURI);
loadState->SetTriggeringPrincipal(nsContentUtils::GetSystemPrincipal());
if (mBrowsingContext) {
loadState->SetTriggeringSandboxFlags(mBrowsingContext->GetSandboxFlags());
loadState->SetTriggeringWindowId(
mBrowsingContext->GetCurrentInnerWindowId());
nsPIDOMWindowInner* innerWin = mScriptGlobal->GetCurrentInnerWindow();
if (innerWin) {
loadState->SetTriggeringStorageAccess(innerWin->UsingStorageAccess());
}
}
loadState->SetLoadType(LOAD_ERROR_PAGE);
loadState->SetFirstParty(true);
loadState->SetSourceBrowsingContext(mBrowsingContext);
if (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));
}
// Prevent initial about:blank handling, as it's likely irrelevant and
// keeps us from needing to change GeckoView / NavigationDelegateTest.
// It also makes the load more consistent with non-about-blank cases.
loadState->ProhibitInitialAboutBlankHandling();
return InternalLoad(loadState);
}
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
nsDocShell::Reload(uint32_t aReloadFlags) {
return ReloadNavigable(Nothing(), aReloadFlags, nullptr,
UserNavigationInvolvement::BrowserUI);
}
// To reload a navigable navigable given an optional serialized state-or-null
// navigationAPIState (default null) and an optional user navigation
// involvement userInvolvement (default "none"):
nsresult nsDocShell::ReloadNavigable(
mozilla::Maybe<NotNull<JSContext*>> aCx, uint32_t aReloadFlags,
nsIStructuredCloneContainer* aNavigationAPIState,
UserNavigationInvolvement aUserInvolvement,
NavigationAPIMethodTracker* aNavigationAPIMethodTracker) {
AUTO_PROFILER_MARKER_UNTYPED(
"nsDocShell:ReloadNavigable", DOM,
MarkerOptions(MarkerStack::Capture(),
MarkerInnerWindowIdFromDocShell(this)));
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);
NS_ENSURE_TRUE(
aUserInvolvement == UserNavigationInvolvement::BrowserUI || aCx,
NS_ERROR_INVALID_ARG);
RefPtr<nsDocShell> docShell(this);
// 1. If userInvolvement is not "browser UI", then:
if (aUserInvolvement != UserNavigationInvolvement::BrowserUI) {
// 1.1 Let navigation be navigable's active window's navigation API.
nsPIDOMWindowOuter* windowOuter = GetWindow();
MOZ_DIAGNOSTIC_ASSERT(windowOuter);
nsPIDOMWindowInner* windowInner = windowOuter->GetCurrentInnerWindow();
MOZ_DIAGNOSTIC_ASSERT(windowInner);
RefPtr navigation = windowInner->Navigation();
// 1.2 Let destinationNavigationAPIState be navigable's active session
// history entry's navigation API state.
// 1.3 If navigationAPIState is not null, then set
// destinationNavigationAPIState to navigationAPIState.
RefPtr<nsIStructuredCloneContainer> destinationNavigationAPIState =
aNavigationAPIState;
if (!destinationNavigationAPIState) {
destinationNavigationAPIState =
mActiveEntry ? mActiveEntry->GetNavigationAPIState() : nullptr;
}
// 1.4 Let continue be the result of firing a push/replace/reload navigate
// event at navigation with navigationType set to "reload",
// isSameDocument set to false, userInvolvement set to userInvolvement,
// destinationURL set to navigable's active session history entry's URL,
// and navigationAPIState set to destinationNavigationAPIState.
// 1.5 If continue is false, then return.
RefPtr destinationURL = mActiveEntry ? mActiveEntry->GetURI() : nullptr;
if (navigation &&
!navigation->FirePushReplaceReloadNavigateEvent(
*aCx, NavigationType::Reload, destinationURL,
/* aIsSameDocument */ false, Some(aUserInvolvement),
/* aSourceElement*/ nullptr, /* aFormDataEntryList */ nullptr,
destinationNavigationAPIState,
/* aClassiCHistoryAPIState */ nullptr,
aNavigationAPIMethodTracker)) {
return NS_OK;
}
}
// The following steps are implemented by the remainder of ReloadNavigable.
// Send notifications to the HistoryListener if any, about the impending
// reload
RefPtr<ChildSHistory> rootSH = GetRootSessionHistory();
MOZ_LOG(gSHLog, LogLevel::Debug, ("nsDocShell %p Reload", this));
bool forceReload = IsForceReloadType(loadType);
if (!XRE_IsParentProcess()) {
++mPendingReloadCount;
nsCOMPtr<nsIDocumentViewer> viewer(mDocumentViewer);
NS_ENSURE_STATE(viewer);
bool okToUnload = true;
MOZ_TRY(viewer->PermitUnload(&okToUnload));
if (mIsBeingDestroyed) {
// unload handler destroyed this docshell.
return NS_ERROR_NOT_AVAILABLE;
}
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](
std::tuple<bool, Maybe<NotNull<RefPtr<nsDocShellLoadState>>>,
Maybe<bool>>&& aResult) {
auto scopeExit = MakeScopeExit([loadGroup, stopDetector]() {
if (loadGroup) {
loadGroup->RemoveRequest(stopDetector, nullptr, NS_OK);
}
});
// Decrease mPendingReloadCount before any other early returns!
if (--(docShell->mPendingReloadCount) > 0) {
return;
}
if (stopDetector->Canceled()) {
return;
}
bool canReload;
Maybe<NotNull<RefPtr<nsDocShellLoadState>>> loadState;
Maybe<bool> reloadingActiveEntry;
std::tie(canReload, loadState, reloadingActiveEntry) = aResult;
if (!canReload) {
return;
}
if (loadState.isSome()) {
MOZ_LOG(
gSHLog, LogLevel::Debug,
("nsDocShell %p Reload - LoadHistoryEntry", docShell.get()));
loadState.ref()->SetNotifiedBeforeUnloadListeners(true);
docShell->LoadHistoryEntry(loadState.ref(), loadType,
reloadingActiveEntry.ref());
} else {
MOZ_LOG(gSHLog, LogLevel::Debug,
("nsDocShell %p ReloadDocument", docShell.get()));
ReloadDocument(docShell, doc, loadType, browsingContext, currentURI,
referrerInfo,
/* aNotifiedBeforeUnloadListeners */ true);
}
},
[](mozilla::ipc::ResponseRejectReason) {});
} else {
// Parent process
bool canReload = false;
Maybe<NotNull<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));
RefPtr<Document> doc = GetDocument();
RefPtr<BrowsingContext> bc = mBrowsingContext;
nsCOMPtr<nsIURI> currentURI = mCurrentURI;
nsCOMPtr<nsIReferrerInfo> referrerInfo = mReferrerInfo;
ReloadDocument(this, doc, loadType, bc, currentURI, referrerInfo);
}
}
}
return NS_OK;
}
void nsDocShell::DisplayRestrictedContentError() {
bool didDisplayLoadError = false;
RefPtr<mozilla::dom::Document> doc = GetDocument();
if (!doc) {
return;
}
doc->TerminateParserAndDisableScripts();
DisplayLoadError(NS_ERROR_RESTRICTED_CONTENT, doc->GetDocumentURI(), nullptr,
nullptr, &didDisplayLoadError);
}
/* 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<nsIPolicyContainer> policyContainer =
aDocument->GetPolicyContainer();
uint32_t triggeringSandboxFlags = aDocument->GetSandboxFlags();
uint64_t triggeringWindowId = aDocument->InnerWindowID();
bool triggeringStorageAccess = aDocument->UsingStorageAccess();
net::ClassificationFlags triggeringClassificationFlags =
aDocument->GetScriptTrackingFlags();
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->SetTriggeringWindowId(triggeringWindowId);
loadState->SetTriggeringStorageAccess(triggeringStorageAccess);
loadState->SetTriggeringClassificationFlags(triggeringClassificationFlags);
loadState->SetPrincipalToInherit(triggeringPrincipal);
loadState->SetPolicyContainer(policyContainer);
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->SetTextDirectiveUserActivation(
aDocument->ConsumeTextDirectiveUserActivation() ||
loadState->HasValidUserGestureActivation());
loadState->SetNotifiedBeforeUnloadListeners(aNotifiedBeforeUnloadListeners);
return aDocShell->InternalLoad(loadState);
}
// TODO: Convert this to MOZ_CAN_RUN_SCRIPT (bug 1415230)
MOZ_CAN_RUN_SCRIPT_BOUNDARY NS_IMETHODIMP
nsDocShell::Stop(uint32_t aStopFlags) {
return StopInternal(aStopFlags, UnsetOngoingNavigation::Yes);
}
nsresult nsDocShell::StopInternal(
uint32_t aStopFlags, UnsetOngoingNavigation aUnsetOngoingNavigation) {
RefPtr kungFuDeathGrip = this;
if (RefPtr<Document> doc = GetExtantDocument();
aUnsetOngoingNavigation == UnsetOngoingNavigation::Yes && doc &&
!doc->ShouldIgnoreOpens() &&
mOngoingNavigation == Some(OngoingNavigation::NavigationID)) {
SetOngoingNavigation(Nothing());
}
if (mLoadType == LOAD_ERROR_PAGE) {
mActiveEntryIsLoadingFromSessionHistory = false;
mFailedChannel = nullptr;
mFailedURI = nullptr;
}
if (nsIWebNavigation::STOP_CONTENT & aStopFlags) {
// Stop the document loading and animations
if (mDocumentViewer) {
nsCOMPtr<nsIDocumentViewer> viewer = mDocumentViewer;
viewer->Stop();
}
} else if (nsIWebNavigation::STOP_NETWORK & aStopFlags) {
// Stop the document loading only
if (mDocumentViewer) {
RefPtr<Document> doc = mDocumentViewer->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 CreateDocumentViewer.
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!
if (aUnsetOngoingNavigation == UnsetOngoingNavigation::No && mLoadGroup) {
// Tag load group cancellation as navigation-caused so that XHR can
// suppress abort events (bug 1505389). SetCanceledReason is
// first-write-wins, so nsDocLoader::Stop's reason won't overwrite.
// XXX Consider using a flag on LoadGroup instead of CanceledReason
mLoadGroup->SetCanceledReason("navigation"_ns);
}
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_TRUE(VerifyDocumentViewer(), NS_ERROR_FAILURE);
RefPtr<Document> doc = mDocumentViewer->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;
}
auto* otherDocShell = nsDocShell::Cast(aOtherDocShell);
RefPtr<nsDocShellLoadState> loadState = new nsDocShellLoadState(newURI);
if (!otherDocShell->FillLoadStateFromCurrentEntry(*loadState)) {
return NS_ERROR_INVALID_POINTER;
}
uint32_t cacheKey = otherDocShell->GetCacheKeyFromCurrentEntry().valueOr(0);
// 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);
// Initial about:blank handling is probably irrelevant, but newURI shouldn't
// anyway be about:blank. Otherwise we should prohibit initial about blank
// handling.