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