diff options
Diffstat (limited to 'docshell/base/nsDocShell.cpp')
-rw-r--r-- | docshell/base/nsDocShell.cpp | 14853 |
1 files changed, 14853 insertions, 0 deletions
diff --git a/docshell/base/nsDocShell.cpp b/docshell/base/nsDocShell.cpp new file mode 100644 index 000000000..ab119a016 --- /dev/null +++ b/docshell/base/nsDocShell.cpp @@ -0,0 +1,14853 @@ +/* -*- 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> + +#include "mozilla/ArrayUtils.h" +#include "mozilla/Attributes.h" +#include "mozilla/AutoRestore.h" +#include "mozilla/BasePrincipal.h" +#include "mozilla/Casting.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/ChromeUtils.h" +#include "mozilla/dom/Element.h" +#include "mozilla/dom/TabChild.h" +#include "mozilla/dom/ProfileTimelineMarkerBinding.h" +#include "mozilla/dom/ScreenOrientation.h" +#include "mozilla/dom/ToJSValue.h" +#include "mozilla/dom/PermissionMessageUtils.h" +#include "mozilla/dom/workers/ServiceWorkerManager.h" +#include "mozilla/EventStateManager.h" +#include "mozilla/LoadInfo.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "mozilla/StartupTimeline.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "Navigator.h" +#include "URIUtils.h" +#include "mozilla/dom/DocGroup.h" +#include "mozilla/dom/TabGroup.h" + +#include "nsIContent.h" +#include "nsIContentInlines.h" +#include "nsIDocument.h" +#include "nsIDOMDocument.h" +#include "nsIDOMElement.h" + +#include "nsArray.h" +#include "nsArrayUtils.h" +#include "nsICaptivePortalService.h" +#include "nsIDOMStorage.h" +#include "nsIContentViewer.h" +#include "nsIDocumentLoaderFactory.h" +#include "nsCURILoader.h" +#include "nsDocShellCID.h" +#include "nsDOMCID.h" +#include "nsNetCID.h" +#include "nsNetUtil.h" +#include "mozilla/net/ReferrerPolicy.h" +#include "nsRect.h" +#include "prenv.h" +#include "nsIDOMWindow.h" +#include "nsIGlobalObject.h" +#include "nsIViewSourceChannel.h" +#include "nsIWebBrowserChrome.h" +#include "nsPoint.h" +#include "nsIObserverService.h" +#include "nsIPrompt.h" +#include "nsIAuthPrompt.h" +#include "nsIAuthPrompt2.h" +#include "nsIChannelEventSink.h" +#include "nsIAsyncVerifyRedirectCallback.h" +#include "nsIScriptSecurityManager.h" +#include "nsIScriptObjectPrincipal.h" +#include "nsIScrollableFrame.h" +#include "nsContentPolicyUtils.h" // NS_CheckContentLoadPolicy(...) +#include "nsISeekableStream.h" +#include "nsAutoPtr.h" +#include "nsQueryObject.h" +#include "nsIWritablePropertyBag2.h" +#include "nsIAppShell.h" +#include "nsWidgetsCID.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsView.h" +#include "nsViewManager.h" +#include "nsIScriptChannel.h" +#include "nsITimedChannel.h" +#include "nsIPrivacyTransitionObserver.h" +#include "nsIReflowObserver.h" +#include "nsIScrollObserver.h" +#include "nsIDocShellTreeItem.h" +#include "nsIChannel.h" +#include "IHistory.h" +#include "nsViewSourceHandler.h" +#include "nsWhitespaceTokenizer.h" +#include "nsICookieService.h" +#include "nsIConsoleReportCollector.h" +#include "nsObjectLoadingContent.h" + +// we want to explore making the document own the load group +// so we can associate the document URI with the load group. +// until this point, we have an evil hack: +#include "nsIHttpChannelInternal.h" +#include "nsPILoadGroupInternal.h" + +// Local Includes +#include "nsDocShellLoadInfo.h" +#include "nsCDefaultURIFixup.h" +#include "nsDocShellEnumerator.h" +#include "nsSHistory.h" +#include "nsDocShellEditorData.h" +#include "GeckoProfiler.h" +#include "timeline/JavascriptTimelineMarker.h" + +// Helper Classes +#include "nsError.h" +#include "nsEscape.h" + +// Interfaces Needed +#include "nsIFormPOSTActionChannel.h" +#include "nsIUploadChannel.h" +#include "nsIUploadChannel2.h" +#include "nsIWebProgress.h" +#include "nsILayoutHistoryState.h" +#include "nsITimer.h" +#include "nsISHistoryInternal.h" +#include "nsIPrincipal.h" +#include "nsNullPrincipal.h" +#include "nsISHEntry.h" +#include "nsIWindowWatcher.h" +#include "nsIPromptFactory.h" +#include "nsITransportSecurityInfo.h" +#include "nsINode.h" +#include "nsINSSErrorsService.h" +#include "nsIApplicationCacheChannel.h" +#include "nsIApplicationCacheContainer.h" +#include "nsStreamUtils.h" +#include "nsIController.h" +#include "nsPICommandUpdater.h" +#include "nsIDOMHTMLAnchorElement.h" +#include "nsIWebBrowserChrome3.h" +#include "nsITabChild.h" +#include "nsISiteSecurityService.h" +#include "nsStructuredCloneContainer.h" +#include "nsIStructuredCloneContainer.h" +#ifdef MOZ_PLACES +#include "nsIFaviconService.h" +#include "mozIPlacesPendingOperation.h" +#include "mozIAsyncFavicons.h" +#endif +#include "nsINetworkPredictor.h" + +// Editor-related +#include "nsIEditingSession.h" + +#include "nsPIDOMWindow.h" +#include "nsGlobalWindow.h" +#include "nsPIWindowRoot.h" +#include "nsICachingChannel.h" +#include "nsIMultiPartChannel.h" +#include "nsIWyciwygChannel.h" + +// For reporting errors with the console service. +// These can go away if error reporting is propagated up past nsDocShell. +#include "nsIScriptError.h" + +// used to dispatch urls to default protocol handlers +#include "nsCExternalHandlerService.h" +#include "nsIExternalProtocolService.h" + +#include "nsFocusManager.h" + +#include "nsITextToSubURI.h" + +#include "nsIJARChannel.h" + +#include "mozilla/Logging.h" + +#include "nsISelectionDisplay.h" + +#include "nsIGlobalHistory2.h" + +#include "nsIFrame.h" +#include "nsSubDocumentFrame.h" + +// for embedding +#include "nsIWebBrowserChromeFocus.h" + +#if NS_PRINT_PREVIEW +#include "nsIDocumentViewerPrint.h" +#include "nsIWebBrowserPrint.h" +#endif + +#include "nsContentUtils.h" +#include "nsIContentSecurityPolicy.h" +#include "nsILoadInfo.h" +#include "nsSandboxFlags.h" +#include "nsXULAppAPI.h" +#include "nsDOMNavigationTiming.h" +#include "nsISecurityUITelemetry.h" +#include "nsIAppsService.h" +#include "nsDSURIContentListener.h" +#include "nsDocShellLoadTypes.h" +#include "nsDocShellTransferableHooks.h" +#include "nsICommandManager.h" +#include "nsIDOMNode.h" +#include "nsIDocShellTreeOwner.h" +#include "nsIHttpChannel.h" +#include "nsIIDNService.h" +#include "nsIInputStreamChannel.h" +#include "nsINestedURI.h" +#include "nsISHContainer.h" +#include "nsISHistory.h" +#include "nsISecureBrowserUI.h" +#include "nsISocketProvider.h" +#include "nsIStringBundle.h" +#include "nsIURIFixup.h" +#include "nsIURILoader.h" +#include "nsIURL.h" +#include "nsIWebBrowserFind.h" +#include "nsIWidget.h" +#include "mozilla/dom/EncodingUtils.h" +#include "mozilla/dom/PerformanceNavigation.h" +#include "mozilla/dom/ScriptSettings.h" + +#ifdef MOZ_TOOLKIT_SEARCH +#include "nsIBrowserSearchService.h" +#endif + +#include "mozIThirdPartyUtil.h" + +static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); + +#if defined(DEBUG_bryner) || defined(DEBUG_chb) +//#define DEBUG_DOCSHELL_FOCUS +#define DEBUG_PAGE_CACHE +#endif + +#ifdef XP_WIN +#include <process.h> +#define getpid _getpid +#else +#include <unistd.h> // for getpid() +#endif + +using namespace mozilla; +using namespace mozilla::dom; +using mozilla::dom::workers::ServiceWorkerManager; + +// True means sUseErrorPages has been added to +// preferences var cache. +static bool gAddedPreferencesVarCache = false; + +bool nsDocShell::sUseErrorPages = false; + +// Number of documents currently loading +static int32_t gNumberOfDocumentsLoading = 0; + +// Global count of existing docshells. +static int32_t gDocShellCount = 0; + +// Global count of docshells with the private attribute set +static uint32_t gNumberOfPrivateDocShells = 0; + +// Global reference to the URI fixup service. +nsIURIFixup* nsDocShell::sURIFixup = 0; + +// True means we validate window targets to prevent frameset +// spoofing. Initialize this to a non-bolean value so we know to check +// the pref on the creation of the first docshell. +static uint32_t gValidateOrigin = 0xffffffff; + +// 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 + +#ifdef DEBUG +static mozilla::LazyLogModule gDocShellLog("nsDocShell"); +#endif +static mozilla::LazyLogModule gDocShellLeakLog("nsDocShellLeak");; + +const char kBrandBundleURL[] = "chrome://branding/locale/brand.properties"; +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)); + } +} + +//***************************************************************************** +// <a ping> support +//***************************************************************************** + +#define PREF_PINGS_ENABLED "browser.send_pings" +#define PREF_PINGS_MAX_PER_LINK "browser.send_pings.max_per_link" +#define PREF_PINGS_REQUIRE_SAME_HOST "browser.send_pings.require_same_host" + +// Check prefs to see if pings are enabled and if so what restrictions might +// be applied. +// +// @param maxPerLink +// This parameter returns the number of pings that are allowed per link click +// +// @param requireSameHost +// This parameter returns true if pings are restricted to the same host as +// the document in which the click occurs. If the same host restriction is +// imposed, then we still allow for pings to cross over to different +// protocols and ports for flexibility and because it is not possible to send +// a ping via FTP. +// +// @returns +// true if pings are enabled and false otherwise. +// +static bool +PingsEnabled(int32_t* aMaxPerLink, bool* aRequireSameHost) +{ + bool allow = Preferences::GetBool(PREF_PINGS_ENABLED, false); + + *aMaxPerLink = 1; + *aRequireSameHost = true; + + if (allow) { + Preferences::GetInt(PREF_PINGS_MAX_PER_LINK, aMaxPerLink); + Preferences::GetBool(PREF_PINGS_REQUIRE_SAME_HOST, aRequireSameHost); + } + + return allow; +} + +typedef void (*ForEachPingCallback)(void* closure, nsIContent* content, + nsIURI* uri, nsIIOService* ios); + +static bool +IsElementAnchor(nsIContent* aContent) +{ + // Make sure we are dealing with either an <A> or <AREA> element in the HTML + // or XHTML namespace. + return aContent->IsAnyOfHTMLElements(nsGkAtoms::a, nsGkAtoms::area); +} + +static void +ForEachPing(nsIContent* aContent, ForEachPingCallback aCallback, void* aClosure) +{ + // NOTE: Using nsIDOMHTMLAnchorElement::GetPing isn't really worth it here + // since we'd still need to parse the resulting string. Instead, we + // just parse the raw attribute. It might be nice if the content node + // implemented an interface that exposed an enumeration of nsIURIs. + + // Make sure we are dealing with either an <A> or <AREA> element in the HTML + // or XHTML namespace. + if (!IsElementAnchor(aContent)) { + return; + } + + nsCOMPtr<nsIAtom> pingAtom = NS_Atomize("ping"); + if (!pingAtom) { + return; + } + + nsAutoString value; + aContent->GetAttr(kNameSpaceID_None, pingAtom, value); + if (value.IsEmpty()) { + return; + } + + nsCOMPtr<nsIIOService> ios = do_GetIOService(); + if (!ios) { + return; + } + + nsIDocument* doc = aContent->OwnerDoc(); + + nsWhitespaceTokenizer tokenizer(value); + + while (tokenizer.hasMoreTokens()) { + nsCOMPtr<nsIURI> uri, baseURI = aContent->GetBaseURI(); + ios->NewURI(NS_ConvertUTF16toUTF8(tokenizer.nextToken()), + doc->GetDocumentCharacterSet().get(), + baseURI, getter_AddRefs(uri)); + // if we can't generate a valid URI, then there is nothing to do + if (!uri) { + continue; + } + // Explicitly not allow loading data: URIs + bool isDataScheme = + (NS_SUCCEEDED(uri->SchemeIs("data", &isDataScheme)) && isDataScheme); + + if (!isDataScheme) { + aCallback(aClosure, aContent, uri, ios); + } + } +} + +//---------------------------------------------------------------------- + +// We wait this many milliseconds before killing the ping channel... +#define PING_TIMEOUT 10000 + +static void +OnPingTimeout(nsITimer* aTimer, void* aClosure) +{ + nsILoadGroup* loadGroup = static_cast<nsILoadGroup*>(aClosure); + if (loadGroup) { + loadGroup->Cancel(NS_ERROR_ABORT); + } +} + +class nsPingListener final + : public nsIStreamListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + nsPingListener() + { + } + + void SetLoadGroup(nsILoadGroup* aLoadGroup) { + mLoadGroup = aLoadGroup; + } + + nsresult StartTimeout(); + +private: + ~nsPingListener(); + + nsCOMPtr<nsILoadGroup> mLoadGroup; + nsCOMPtr<nsITimer> mTimer; +}; + +NS_IMPL_ISUPPORTS(nsPingListener, nsIStreamListener, nsIRequestObserver) + +nsPingListener::~nsPingListener() +{ + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } +} + +nsresult +nsPingListener::StartTimeout() +{ + nsCOMPtr<nsITimer> timer = do_CreateInstance(NS_TIMER_CONTRACTID); + + if (timer) { + nsresult rv = timer->InitWithFuncCallback(OnPingTimeout, mLoadGroup, + PING_TIMEOUT, + nsITimer::TYPE_ONE_SHOT); + if (NS_SUCCEEDED(rv)) { + mTimer = timer; + return NS_OK; + } + } + + return NS_ERROR_OUT_OF_MEMORY; +} + +NS_IMETHODIMP +nsPingListener::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) +{ + return NS_OK; +} + +NS_IMETHODIMP +nsPingListener::OnDataAvailable(nsIRequest* aRequest, nsISupports* aContext, + nsIInputStream* aStream, uint64_t aOffset, + uint32_t aCount) +{ + uint32_t result; + return aStream->ReadSegments(NS_DiscardSegment, nullptr, aCount, &result); +} + +NS_IMETHODIMP +nsPingListener::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, + nsresult aStatus) +{ + mLoadGroup = nullptr; + + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + return NS_OK; +} + +struct MOZ_STACK_CLASS SendPingInfo +{ + int32_t numPings; + int32_t maxPings; + bool requireSameHost; + nsIURI* target; + nsIURI* referrer; + nsIDocShell* docShell; + uint32_t referrerPolicy; +}; + +static void +SendPing(void* aClosure, nsIContent* aContent, nsIURI* aURI, + nsIIOService* aIOService) +{ + SendPingInfo* info = static_cast<SendPingInfo*>(aClosure); + if (info->maxPings > -1 && info->numPings >= info->maxPings) { + return; + } + + nsIDocument* doc = aContent->OwnerDoc(); + + nsCOMPtr<nsIChannel> chan; + NS_NewChannel(getter_AddRefs(chan), + aURI, + doc, + info->requireSameHost + ? nsILoadInfo::SEC_REQUIRE_SAME_ORIGIN_DATA_IS_BLOCKED + : nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL, + nsIContentPolicy::TYPE_PING, + nullptr, // aLoadGroup + nullptr, // aCallbacks + nsIRequest::LOAD_NORMAL, // aLoadFlags, + aIOService); + + if (!chan) { + return; + } + + // Don't bother caching the result of this URI load, but do not exempt + // it from Safe Browsing. + chan->SetLoadFlags(nsIRequest::INHIBIT_CACHING | nsIChannel::LOAD_CLASSIFY_URI); + + nsCOMPtr<nsIHttpChannel> httpChan = do_QueryInterface(chan); + if (!httpChan) { + return; + } + + // This is needed in order for 3rd-party cookie blocking to work. + nsCOMPtr<nsIHttpChannelInternal> httpInternal = do_QueryInterface(httpChan); + if (httpInternal) { + httpInternal->SetDocumentURI(doc->GetDocumentURI()); + } + + httpChan->SetRequestMethod(NS_LITERAL_CSTRING("POST")); + + // Remove extraneous request headers (to reduce request size) + httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept"), + EmptyCString(), false); + httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-language"), + EmptyCString(), false); + httpChan->SetRequestHeader(NS_LITERAL_CSTRING("accept-encoding"), + EmptyCString(), false); + + // Always send a Ping-To header. + nsAutoCString pingTo; + if (NS_SUCCEEDED(info->target->GetSpec(pingTo))) { + httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-To"), pingTo, false); + } + + nsCOMPtr<nsIScriptSecurityManager> sm = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + + if (sm && info->referrer) { + bool referrerIsSecure; + uint32_t flags = nsIProtocolHandler::URI_SAFE_TO_LOAD_IN_SECURE_CONTEXT; + nsresult rv = NS_URIChainHasFlags(info->referrer, flags, &referrerIsSecure); + + // Default to sending less data if NS_URIChainHasFlags() fails. + referrerIsSecure = NS_FAILED(rv) || referrerIsSecure; + + bool sameOrigin = + NS_SUCCEEDED(sm->CheckSameOriginURI(info->referrer, aURI, false)); + + // If both the address of the document containing the hyperlink being + // audited and "ping URL" have the same origin or the document containing + // the hyperlink being audited was not retrieved over an encrypted + // connection, send a Ping-From header. + if (sameOrigin || !referrerIsSecure) { + nsAutoCString pingFrom; + if (NS_SUCCEEDED(info->referrer->GetSpec(pingFrom))) { + httpChan->SetRequestHeader(NS_LITERAL_CSTRING("Ping-From"), pingFrom, + false); + } + } + + // If the document containing the hyperlink being audited was not retrieved + // over an encrypted connection and its address does not have the same + // origin as "ping URL", send a referrer. + if (!sameOrigin && !referrerIsSecure) { + httpChan->SetReferrerWithPolicy(info->referrer, info->referrerPolicy); + } + } + + nsCOMPtr<nsIUploadChannel2> uploadChan = do_QueryInterface(httpChan); + if (!uploadChan) { + return; + } + + NS_NAMED_LITERAL_CSTRING(uploadData, "PING"); + + nsCOMPtr<nsIInputStream> uploadStream; + NS_NewPostDataStream(getter_AddRefs(uploadStream), false, uploadData); + if (!uploadStream) { + return; + } + + uploadChan->ExplicitSetUploadStream(uploadStream, + NS_LITERAL_CSTRING("text/ping"), + uploadData.Length(), + NS_LITERAL_CSTRING("POST"), false); + + // The channel needs to have a loadgroup associated with it, so that we can + // cancel the channel and any redirected channels it may create. + nsCOMPtr<nsILoadGroup> loadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID); + if (!loadGroup) { + return; + } + nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(info->docShell); + loadGroup->SetNotificationCallbacks(callbacks); + chan->SetLoadGroup(loadGroup); + + RefPtr<nsPingListener> pingListener = new nsPingListener(); + chan->AsyncOpen2(pingListener); + + // Even if AsyncOpen failed, we still count this as a successful ping. It's + // possible that AsyncOpen may have failed after triggering some background + // process that may have written something to the network. + info->numPings++; + + // Prevent ping requests from stalling and never being garbage collected... + if (NS_FAILED(pingListener->StartTimeout())) { + // If we failed to setup the timer, then we should just cancel the channel + // because we won't be able to ensure that it goes away in a timely manner. + chan->Cancel(NS_ERROR_ABORT); + return; + } + // if the channel openend successfully, then make the pingListener hold + // a strong reference to the loadgroup which is released in ::OnStopRequest + pingListener->SetLoadGroup(loadGroup); +} + +// Spec: http://whatwg.org/specs/web-apps/current-work/#ping +static void +DispatchPings(nsIDocShell* aDocShell, + nsIContent* aContent, + nsIURI* aTarget, + nsIURI* aReferrer, + uint32_t aReferrerPolicy) +{ + SendPingInfo info; + + if (!PingsEnabled(&info.maxPings, &info.requireSameHost)) { + return; + } + if (info.maxPings == 0) { + return; + } + + info.numPings = 0; + info.target = aTarget; + info.referrer = aReferrer; + info.referrerPolicy = aReferrerPolicy; + info.docShell = aDocShell; + + ForEachPing(aContent, SendPing, &info); +} + +static nsDOMNavigationTiming::Type +ConvertLoadTypeToNavigationType(uint32_t aLoadType) +{ + // Not initialized, assume it's normal load. + if (aLoadType == 0) { + aLoadType = LOAD_NORMAL; + } + + auto result = nsDOMNavigationTiming::TYPE_RESERVED; + switch (aLoadType) { + case LOAD_NORMAL: + case LOAD_NORMAL_EXTERNAL: + case LOAD_NORMAL_BYPASS_CACHE: + case LOAD_NORMAL_BYPASS_PROXY: + case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE: + case LOAD_NORMAL_REPLACE: + case LOAD_NORMAL_ALLOW_MIXED_CONTENT: + case LOAD_LINK: + case LOAD_STOP_CONTENT: + case LOAD_REPLACE_BYPASS_CACHE: + result = nsDOMNavigationTiming::TYPE_NAVIGATE; + break; + case LOAD_HISTORY: + result = nsDOMNavigationTiming::TYPE_BACK_FORWARD; + break; + case LOAD_RELOAD_NORMAL: + case LOAD_RELOAD_CHARSET_CHANGE: + case LOAD_RELOAD_BYPASS_CACHE: + case LOAD_RELOAD_BYPASS_PROXY: + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + case LOAD_RELOAD_ALLOW_MIXED_CONTENT: + result = nsDOMNavigationTiming::TYPE_RELOAD; + break; + case LOAD_STOP_CONTENT_AND_REPLACE: + case LOAD_REFRESH: + case LOAD_BYPASS_HISTORY: + case LOAD_ERROR_PAGE: + case LOAD_PUSHSTATE: + result = nsDOMNavigationTiming::TYPE_RESERVED; + break; + default: + // NS_NOTREACHED("Unexpected load type value"); + result = nsDOMNavigationTiming::TYPE_RESERVED; + break; + } + + return result; +} + +static nsISHEntry* GetRootSHEntry(nsISHEntry* aEntry); + +static void +IncreasePrivateDocShellCount() +{ + gNumberOfPrivateDocShells++; + if (gNumberOfPrivateDocShells > 1 || + !XRE_IsContentProcess()) { + return; + } + + mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton(); + cc->SendPrivateDocShellsExist(true); +} + +static void +DecreasePrivateDocShellCount() +{ + MOZ_ASSERT(gNumberOfPrivateDocShells > 0); + gNumberOfPrivateDocShells--; + if (!gNumberOfPrivateDocShells) { + if (XRE_IsContentProcess()) { + dom::ContentChild* cc = dom::ContentChild::GetSingleton(); + cc->SendPrivateDocShellsExist(false); + return; + } + + nsCOMPtr<nsIObserverService> obsvc = services::GetObserverService(); + if (obsvc) { + obsvc->NotifyObservers(nullptr, "last-pb-context-exited", nullptr); + } + } +} + +static uint64_t gDocshellIDCounter = 0; + +nsDocShell::nsDocShell() + : nsDocLoader() + , mDefaultScrollbarPref(Scrollbar_Auto, Scrollbar_Auto) + , mTreeOwner(nullptr) + , mChromeEventHandler(nullptr) + , mCharsetReloadState(eCharsetReloadInit) + , mChildOffset(0) + , mBusyFlags(BUSY_FLAGS_NONE) + , mAppType(nsIDocShell::APP_TYPE_UNKNOWN) + , mLoadType(0) + , mMarginWidth(-1) + , mMarginHeight(-1) + , mItemType(typeContent) + , mPreviousTransIndex(-1) + , mLoadedTransIndex(-1) + , mSandboxFlags(0) + , mOrientationLock(eScreenOrientation_None) + , mFullscreenAllowed(CHECK_ATTRIBUTES) + , mCreated(false) + , mAllowSubframes(true) + , mAllowPlugins(true) + , mAllowJavascript(true) + , mAllowMetaRedirects(true) + , mAllowImages(true) + , mAllowMedia(true) + , mAllowDNSPrefetch(true) + , mAllowWindowControl(true) + , mAllowContentRetargeting(true) + , mAllowContentRetargetingOnChildren(true) + , mUseErrorPages(false) + , mObserveErrorPages(true) + , mAllowAuth(true) + , mAllowKeywordFixup(false) + , mIsOffScreenBrowser(false) + , mIsActive(true) + , mDisableMetaRefreshWhenInactive(false) + , mIsPrerendered(false) + , mIsAppTab(false) + , mUseGlobalHistory(false) + , mUseRemoteTabs(false) + , mDeviceSizeIsPageSize(false) + , mWindowDraggingAllowed(false) + , mInFrameSwap(false) + , mInheritPrivateBrowsingId(true) + , mCanExecuteScripts(false) + , mFiredUnloadEvent(false) + , mEODForCurrentDocument(false) + , mURIResultedInDocument(false) + , mIsBeingDestroyed(false) + , mIsExecutingOnLoadHandler(false) + , mIsPrintingOrPP(false) + , mSavingOldViewer(false) + , mAffectPrivateSessionLifetime(true) + , mInvisible(false) + , mHasLoadedNonBlankURI(false) + , mBlankTiming(false) + , mCreatingDocument(false) +#ifdef DEBUG + , mInEnsureScriptEnv(false) +#endif + , mDefaultLoadFlags(nsIRequest::LOAD_NORMAL) + , mFrameType(FRAME_TYPE_REGULAR) + , mPrivateBrowsingId(0) + , mParentCharsetSource(0) + , mJSRunToCompletionDepth(0) + , mTouchEventsOverride(nsIDocShell::TOUCHEVENTS_OVERRIDE_NONE) +{ + AssertOriginAttributesMatchPrivateBrowsing(); + mHistoryID = ++gDocshellIDCounter; + if (gDocShellCount++ == 0) { + NS_ASSERTION(sURIFixup == nullptr, + "Huh, sURIFixup not null in first nsDocShell ctor!"); + + CallGetService(NS_URIFIXUP_CONTRACTID, &sURIFixup); + } + + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p created\n", this)); + +#ifdef DEBUG + // We're counting the number of |nsDocShells| to help find leaks + ++gNumberOfDocShells; + if (!PR_GetEnv("MOZ_QUIET")) { + printf_stderr("++DOCSHELL %p == %ld [pid = %d] [id = %llu]\n", + (void*)this, + gNumberOfDocShells, + getpid(), + AssertedCast<unsigned long long>(mHistoryID)); + } +#endif +} + +nsDocShell::~nsDocShell() +{ + MOZ_ASSERT(!mObserved); + + // Avoid notifying observers while we're in the dtor. + mIsBeingDestroyed = true; + + Destroy(); + + nsCOMPtr<nsISHistoryInternal> shPrivate(do_QueryInterface(mSessionHistory)); + if (shPrivate) { + shPrivate->SetRootDocShell(nullptr); + } + + if (--gDocShellCount == 0) { + NS_IF_RELEASE(sURIFixup); + } + + if (gDocShellLeakLog) { + MOZ_LOG(gDocShellLeakLog, LogLevel::Debug, ("DOCSHELL %p destroyed\n", this)); + } + +#ifdef DEBUG + // We're counting the number of |nsDocShells| to help find leaks + --gNumberOfDocShells; + if (!PR_GetEnv("MOZ_QUIET")) { + printf_stderr("--DOCSHELL %p == %ld [pid = %d] [id = %llu]\n", + (void*)this, + gNumberOfDocShells, + getpid(), + AssertedCast<unsigned long long>(mHistoryID)); + } +#endif +} + +nsresult +nsDocShell::Init() +{ + nsresult rv = nsDocLoader::Init(); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(mLoadGroup, "Something went wrong!"); + + mContentListener = new nsDSURIContentListener(this); + rv = mContentListener->Init(); + NS_ENSURE_SUCCESS(rv, rv); + + // 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(static_cast<nsIInterfaceRequestor*>(this)); + mLoadGroup->SetNotificationCallbacks(proxy); + + rv = nsDocLoader::AddDocLoaderAsChildOfRoot(this); + NS_ENSURE_SUCCESS(rv, rv); + + // Add as |this| 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 ourselves, so it's ok. + return AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT | + nsIWebProgress::NOTIFY_STATE_NETWORK); +} + +void +nsDocShell::DestroyChildren() +{ + nsCOMPtr<nsIDocShellTreeItem> shell; + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + shell = do_QueryObject(iter.GetNext()); + NS_ASSERTION(shell, "docshell has null child"); + + if (shell) { + shell->SetTreeOwner(nullptr); + } + } + + nsDocLoader::DestroyChildren(); +} + +NS_IMPL_ADDREF_INHERITED(nsDocShell, nsDocLoader) +NS_IMPL_RELEASE_INHERITED(nsDocShell, nsDocLoader) + +NS_INTERFACE_MAP_BEGIN(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(nsIScrollable) + NS_INTERFACE_MAP_ENTRY(nsITextScroll) + NS_INTERFACE_MAP_ENTRY(nsIDocCharset) + NS_INTERFACE_MAP_ENTRY(nsIRefreshURI) + NS_INTERFACE_MAP_ENTRY(nsIWebProgressListener) + NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference) + NS_INTERFACE_MAP_ENTRY(nsIContentViewerContainer) + NS_INTERFACE_MAP_ENTRY(nsIWebPageDescriptor) + NS_INTERFACE_MAP_ENTRY(nsIAuthPromptProvider) + NS_INTERFACE_MAP_ENTRY(nsILoadContext) + NS_INTERFACE_MAP_ENTRY(nsIWebShellServices) + NS_INTERFACE_MAP_ENTRY(nsILinkHandler) + NS_INTERFACE_MAP_ENTRY(nsIClipboardCommands) + NS_INTERFACE_MAP_ENTRY(nsIDOMStorageManager) + NS_INTERFACE_MAP_ENTRY(nsINetworkInterceptController) + NS_INTERFACE_MAP_ENTRY(nsIDeprecationWarner) +NS_INTERFACE_MAP_END_INHERITING(nsDocLoader) + +NS_IMETHODIMP +nsDocShell::GetInterface(const nsIID& aIID, void** aSink) +{ + NS_PRECONDITION(aSink, "null out param"); + + *aSink = nullptr; + + if (aIID.Equals(NS_GET_IID(nsICommandManager))) { + NS_ENSURE_SUCCESS(EnsureCommandHandler(), NS_ERROR_FAILURE); + *aSink = mCommandManager; + } 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)) || + aIID.Equals(NS_GET_IID(nsIDOMWindowInternal))) && + NS_SUCCEEDED(EnsureScriptEnvironment())) { + return mScriptGlobal->QueryInterface(aIID, aSink); + } else if (aIID.Equals(NS_GET_IID(nsIDOMDocument)) && + NS_SUCCEEDED(EnsureContentViewer())) { + mContentViewer->GetDOMDocument((nsIDOMDocument**)aSink); + return *aSink ? NS_OK : NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsIDocument)) && + NS_SUCCEEDED(EnsureContentViewer())) { + nsCOMPtr<nsIDocument> doc = mContentViewer->GetDocument(); + doc.forget(aSink); + return *aSink ? NS_OK : NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsIApplicationCacheContainer))) { + *aSink = nullptr; + + // Return application cache associated with this docshell, if any + + nsCOMPtr<nsIContentViewer> contentViewer; + GetContentViewer(getter_AddRefs(contentViewer)); + if (!contentViewer) { + return NS_ERROR_NO_INTERFACE; + } + + nsCOMPtr<nsIDOMDocument> domDoc; + contentViewer->GetDOMDocument(getter_AddRefs(domDoc)); + NS_ASSERTION(domDoc, "Should have a document."); + if (!domDoc) { + return NS_ERROR_NO_INTERFACE; + } + +#if defined(DEBUG) + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]: returning app cache container %p", + this, domDoc.get())); +#endif + return domDoc->QueryInterface(aIID, aSink); + } 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->AsOuter(), &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))) { + nsCOMPtr<nsISHistory> shistory; + nsresult rv = GetSessionHistory(getter_AddRefs(shistory)); + if (NS_SUCCEEDED(rv) && shistory) { + shistory.forget(aSink); + return NS_OK; + } + 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(nsIEditingSession))) { + nsCOMPtr<nsIEditingSession> es; + GetEditingSession(getter_AddRefs(es)); + es.forget(aSink); + return *aSink ? NS_OK : NS_NOINTERFACE; + } else if (aIID.Equals(NS_GET_IID(nsIClipboardDragDropHookList)) && + NS_SUCCEEDED(EnsureTransferableHookData())) { + *aSink = mTransferableHookData; + NS_ADDREF((nsISupports*)*aSink); + return NS_OK; + } else if (aIID.Equals(NS_GET_IID(nsISelectionDisplay))) { + nsIPresShell* shell = GetPresShell(); + if (shell) { + return shell->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(nsITabChild))) { + *aSink = GetTabChild().take(); + return *aSink ? NS_OK : NS_ERROR_FAILURE; + } else if (aIID.Equals(NS_GET_IID(nsIContentFrameMessageManager))) { + nsCOMPtr<nsITabChild> tabChild = + do_GetInterface(static_cast<nsIDocShell*>(this)); + nsCOMPtr<nsIContentFrameMessageManager> mm; + if (tabChild) { + tabChild->GetMessageManager(getter_AddRefs(mm)); + } else { + if (nsPIDOMWindowOuter* win = GetWindow()) { + mm = do_QueryInterface(win->GetParentTarget()); + } + } + *aSink = mm.get(); + } else { + return nsDocLoader::GetInterface(aIID, aSink); + } + + NS_IF_ADDREF(((nsISupports*)*aSink)); + return *aSink ? NS_OK : NS_NOINTERFACE; +} + +uint32_t +nsDocShell::ConvertDocShellLoadInfoToLoadType( + nsDocShellInfoLoadType aDocShellLoadType) +{ + uint32_t loadType = LOAD_NORMAL; + + switch (aDocShellLoadType) { + case nsIDocShellLoadInfo::loadNormal: + loadType = LOAD_NORMAL; + break; + case nsIDocShellLoadInfo::loadNormalReplace: + loadType = LOAD_NORMAL_REPLACE; + break; + case nsIDocShellLoadInfo::loadNormalExternal: + loadType = LOAD_NORMAL_EXTERNAL; + break; + case nsIDocShellLoadInfo::loadHistory: + loadType = LOAD_HISTORY; + break; + case nsIDocShellLoadInfo::loadNormalBypassCache: + loadType = LOAD_NORMAL_BYPASS_CACHE; + break; + case nsIDocShellLoadInfo::loadNormalBypassProxy: + loadType = LOAD_NORMAL_BYPASS_PROXY; + break; + case nsIDocShellLoadInfo::loadNormalBypassProxyAndCache: + loadType = LOAD_NORMAL_BYPASS_PROXY_AND_CACHE; + break; + case nsIDocShellLoadInfo::loadNormalAllowMixedContent: + loadType = LOAD_NORMAL_ALLOW_MIXED_CONTENT; + break; + case nsIDocShellLoadInfo::loadReloadNormal: + loadType = LOAD_RELOAD_NORMAL; + break; + case nsIDocShellLoadInfo::loadReloadCharsetChange: + loadType = LOAD_RELOAD_CHARSET_CHANGE; + break; + case nsIDocShellLoadInfo::loadReloadBypassCache: + loadType = LOAD_RELOAD_BYPASS_CACHE; + break; + case nsIDocShellLoadInfo::loadReloadBypassProxy: + loadType = LOAD_RELOAD_BYPASS_PROXY; + break; + case nsIDocShellLoadInfo::loadReloadBypassProxyAndCache: + loadType = LOAD_RELOAD_BYPASS_PROXY_AND_CACHE; + break; + case nsIDocShellLoadInfo::loadLink: + loadType = LOAD_LINK; + break; + case nsIDocShellLoadInfo::loadRefresh: + loadType = LOAD_REFRESH; + break; + case nsIDocShellLoadInfo::loadBypassHistory: + loadType = LOAD_BYPASS_HISTORY; + break; + case nsIDocShellLoadInfo::loadStopContent: + loadType = LOAD_STOP_CONTENT; + break; + case nsIDocShellLoadInfo::loadStopContentAndReplace: + loadType = LOAD_STOP_CONTENT_AND_REPLACE; + break; + case nsIDocShellLoadInfo::loadPushState: + loadType = LOAD_PUSHSTATE; + break; + case nsIDocShellLoadInfo::loadReplaceBypassCache: + loadType = LOAD_REPLACE_BYPASS_CACHE; + break; + case nsIDocShellLoadInfo::loadReloadMixedContent: + loadType = LOAD_RELOAD_ALLOW_MIXED_CONTENT; + break; + default: + NS_NOTREACHED("Unexpected nsDocShellInfoLoadType value"); + } + + return loadType; +} + +nsDocShellInfoLoadType +nsDocShell::ConvertLoadTypeToDocShellLoadInfo(uint32_t aLoadType) +{ + nsDocShellInfoLoadType docShellLoadType = nsIDocShellLoadInfo::loadNormal; + switch (aLoadType) { + case LOAD_NORMAL: + docShellLoadType = nsIDocShellLoadInfo::loadNormal; + break; + case LOAD_NORMAL_REPLACE: + docShellLoadType = nsIDocShellLoadInfo::loadNormalReplace; + break; + case LOAD_NORMAL_EXTERNAL: + docShellLoadType = nsIDocShellLoadInfo::loadNormalExternal; + break; + case LOAD_NORMAL_BYPASS_CACHE: + docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassCache; + break; + case LOAD_NORMAL_BYPASS_PROXY: + docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassProxy; + break; + case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE: + docShellLoadType = nsIDocShellLoadInfo::loadNormalBypassProxyAndCache; + break; + case LOAD_NORMAL_ALLOW_MIXED_CONTENT: + docShellLoadType = nsIDocShellLoadInfo::loadNormalAllowMixedContent; + break; + case LOAD_HISTORY: + docShellLoadType = nsIDocShellLoadInfo::loadHistory; + break; + case LOAD_RELOAD_NORMAL: + docShellLoadType = nsIDocShellLoadInfo::loadReloadNormal; + break; + case LOAD_RELOAD_CHARSET_CHANGE: + docShellLoadType = nsIDocShellLoadInfo::loadReloadCharsetChange; + break; + case LOAD_RELOAD_BYPASS_CACHE: + docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassCache; + break; + case LOAD_RELOAD_BYPASS_PROXY: + docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassProxy; + break; + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + docShellLoadType = nsIDocShellLoadInfo::loadReloadBypassProxyAndCache; + break; + case LOAD_LINK: + docShellLoadType = nsIDocShellLoadInfo::loadLink; + break; + case LOAD_REFRESH: + docShellLoadType = nsIDocShellLoadInfo::loadRefresh; + break; + case LOAD_BYPASS_HISTORY: + case LOAD_ERROR_PAGE: + docShellLoadType = nsIDocShellLoadInfo::loadBypassHistory; + break; + case LOAD_STOP_CONTENT: + docShellLoadType = nsIDocShellLoadInfo::loadStopContent; + break; + case LOAD_STOP_CONTENT_AND_REPLACE: + docShellLoadType = nsIDocShellLoadInfo::loadStopContentAndReplace; + break; + case LOAD_PUSHSTATE: + docShellLoadType = nsIDocShellLoadInfo::loadPushState; + break; + case LOAD_REPLACE_BYPASS_CACHE: + docShellLoadType = nsIDocShellLoadInfo::loadReplaceBypassCache; + break; + case LOAD_RELOAD_ALLOW_MIXED_CONTENT: + docShellLoadType = nsIDocShellLoadInfo::loadReloadMixedContent; + break; + default: + NS_NOTREACHED("Unexpected load type value"); + } + + return docShellLoadType; +} + +NS_IMETHODIMP +nsDocShell::LoadURI(nsIURI* aURI, + nsIDocShellLoadInfo* aLoadInfo, + uint32_t aLoadFlags, + bool aFirstParty) +{ + NS_PRECONDITION(aLoadInfo || (aLoadFlags & EXTRA_LOAD_FLAGS) == 0, + "Unexpected flags"); + NS_PRECONDITION((aLoadFlags & 0xf) == 0, "Should not have these flags set"); + + // 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 + } + + nsCOMPtr<nsIURI> referrer; + nsCOMPtr<nsIURI> originalURI; + bool loadReplace = false; + nsCOMPtr<nsIInputStream> postStream; + nsCOMPtr<nsIInputStream> headersStream; + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + bool inheritPrincipal = false; + bool principalIsExplicit = false; + bool sendReferrer = true; + uint32_t referrerPolicy = mozilla::net::RP_Default; + bool isSrcdoc = false; + nsCOMPtr<nsISHEntry> shEntry; + nsXPIDLString target; + nsAutoString srcdoc; + nsCOMPtr<nsIDocShell> sourceDocShell; + nsCOMPtr<nsIURI> baseURI; + + uint32_t loadType = MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags); + + NS_ENSURE_ARG(aURI); + + if (!StartupTimeline::HasRecord(StartupTimeline::FIRST_LOAD_URI) && + mItemType == typeContent && !NS_IsAboutBlank(aURI)) { + StartupTimeline::RecordOnce(StartupTimeline::FIRST_LOAD_URI); + } + + // Extract the info from the DocShellLoadInfo struct... + if (aLoadInfo) { + aLoadInfo->GetReferrer(getter_AddRefs(referrer)); + aLoadInfo->GetOriginalURI(getter_AddRefs(originalURI)); + aLoadInfo->GetLoadReplace(&loadReplace); + nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal; + aLoadInfo->GetLoadType(<); + // Get the appropriate loadType from nsIDocShellLoadInfo type + loadType = ConvertDocShellLoadInfoToLoadType(lt); + + aLoadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal)); + aLoadInfo->GetInheritPrincipal(&inheritPrincipal); + aLoadInfo->GetPrincipalIsExplicit(&principalIsExplicit); + aLoadInfo->GetSHEntry(getter_AddRefs(shEntry)); + aLoadInfo->GetTarget(getter_Copies(target)); + aLoadInfo->GetPostDataStream(getter_AddRefs(postStream)); + aLoadInfo->GetHeadersStream(getter_AddRefs(headersStream)); + aLoadInfo->GetSendReferrer(&sendReferrer); + aLoadInfo->GetReferrerPolicy(&referrerPolicy); + aLoadInfo->GetIsSrcdocLoad(&isSrcdoc); + aLoadInfo->GetSrcdocData(srcdoc); + aLoadInfo->GetSourceDocShell(getter_AddRefs(sourceDocShell)); + aLoadInfo->GetBaseURI(getter_AddRefs(baseURI)); + } + +#if defined(DEBUG) + if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { + nsAutoCString uristr; + aURI->GetAsciiSpec(uristr); + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]: loading %s with flags 0x%08x", + this, uristr.get(), aLoadFlags)); + } +#endif + + if (!shEntry && + !LOAD_TYPE_HAS_FLAGS(loadType, LOAD_FLAGS_REPLACE_HISTORY)) { + // First verify if this is a subframe. + nsCOMPtr<nsIDocShellTreeItem> parentAsItem; + GetSameTypeParent(getter_AddRefs(parentAsItem)); + nsCOMPtr<nsIDocShell> parentDS(do_QueryInterface(parentAsItem)); + uint32_t parentLoadType; + + if (parentDS && parentDS != static_cast<nsIDocShell*>(this)) { + /* OK. It is a subframe. Checkout the + * parent's loadtype. If the parent was loaded thro' 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 + parentDS->GetLoadType(&parentLoadType); + + // Get the ShEntry for the child from the parent + nsCOMPtr<nsISHEntry> currentSH; + bool oshe = false; + parentDS->GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe); + bool dynamicallyAddedChild = mDynamicallyCreated; + if (!dynamicallyAddedChild && !oshe && currentSH) { + currentSH->HasDynamicallyAddedChild(&dynamicallyAddedChild); + } + if (!dynamicallyAddedChild) { + // Only use the old SHEntry, if we're sure enough that + // it wasn't originally for some other frame. + parentDS->GetChildSHEntry(mChildOffset, getter_AddRefs(shEntry)); + } + + // Make some decisions on the child frame's loadType based on the + // parent's loadType. + if (!mCurrentURI) { + // This is a newly created frame. Check for exception cases first. + // By default the subframe will inherit the parent's loadType. + if (shEntry && (parentLoadType == LOAD_NORMAL || + parentLoadType == LOAD_LINK || + parentLoadType == LOAD_NORMAL_EXTERNAL)) { + // 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) { + loadType = LOAD_NORMAL_REPLACE; + shEntry = nullptr; + } + } else if (parentLoadType == LOAD_REFRESH) { + // Clear shEntry. For refresh loads, we have to load + // what comes thro' the pipe, not what's in history. + shEntry = nullptr; + } else if ((parentLoadType == LOAD_BYPASS_HISTORY) || + (shEntry && + ((parentLoadType & LOAD_CMD_HISTORY) || + (parentLoadType == LOAD_RELOAD_NORMAL) || + (parentLoadType == LOAD_RELOAD_CHARSET_CHANGE)))) { + // 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. + loadType = 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. + loadType = 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 + loadType = parentLoadType; + } + } else { + // This is a pre-existing subframe. If the load was not originally + // initiated by session history, (if (!shEntry) condition succeeded) and + // mCurrentURI is not null, 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. + uint32_t parentBusy = BUSY_FLAGS_NONE; + uint32_t selfBusy = BUSY_FLAGS_NONE; + parentDS->GetBusyFlags(&parentBusy); + GetBusyFlags(&selfBusy); + if (parentBusy & BUSY_FLAGS_BUSY || + selfBusy & BUSY_FLAGS_BUSY) { + loadType = LOAD_NORMAL_REPLACE; + shEntry = nullptr; + } + } + } // parentDS + else { + // This is the root docshell. If we got here while + // executing an onLoad Handler,this load will not go + // into session history. + bool inOnLoadHandler = false; + GetIsExecutingOnLoadHandler(&inOnLoadHandler); + if (inOnLoadHandler) { + loadType = LOAD_NORMAL_REPLACE; + } + } + } // !shEntry + + if (shEntry) { +#ifdef DEBUG + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]: loading from session history", this)); +#endif + + return LoadHistoryEntry(shEntry, loadType); + } + + // 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 ((loadType == LOAD_NORMAL || loadType == LOAD_STOP_CONTENT) && + ShouldBlockLoadingForBackButton()) { + return NS_OK; + } + + // Perform the load... + + // We need a principalToInherit. + // + // If principalIsExplicit is not set there are 4 possibilities: + // (1) If the system principal or an expanded principal was passed + // in and we're a typeContent docshell, inherit the principal + // from the current document instead. + // (2) In all other cases when the principal passed in is not null, + // use that principal. + // (3) If the caller has allowed inheriting from the current document, + // or if we're being called from system code (eg chrome JS or pure + // C++) then inheritPrincipal should be true and InternalLoad will get + // a principal from the current document. If none of these things are + // true, then + // (4) we don't pass a principal into the channel, and a principal will be + // created later from the channel's internal data. + // + // If principalIsExplicit *is* set, there are 4 possibilities + // (1) If the system principal or an expanded principal was passed in + // and we're a typeContent docshell, return an error. + // (2) In all other cases when the principal passed in is not null, + // use that principal. + // (3) If the caller has allowed inheriting from the current document, + // then inheritPrincipal should be true and InternalLoad will get + // a principal from the current document. If none of these things are + // true, then + // (4) we dont' pass a principal into the channel, and a principal will be + // created later from the channel's internal data. + nsCOMPtr<nsIPrincipal> principalToInherit = triggeringPrincipal; + if (principalToInherit && mItemType != typeChrome) { + if (nsContentUtils::IsSystemPrincipal(principalToInherit)) { + if (principalIsExplicit) { + return NS_ERROR_DOM_SECURITY_ERR; + } + principalToInherit = nullptr; + inheritPrincipal = true; + } else if (nsContentUtils::IsExpandedPrincipal(principalToInherit)) { + if (principalIsExplicit) { + return NS_ERROR_DOM_SECURITY_ERR; + } + // Don't inherit from the current page. Just do the safe thing + // and pretend that we were loaded by a nullprincipal. + // + // We didn't inherit OriginAttributes here as ExpandedPrincipal doesn't + // have origin attributes. + principalToInherit = nsNullPrincipal::CreateWithInheritedAttributes(this); + inheritPrincipal = false; + } + } + if (!principalToInherit && !inheritPrincipal && !principalIsExplicit) { + // See if there's system or chrome JS code running + inheritPrincipal = nsContentUtils::LegacyIsCallerChromeOrNativeCode(); + } + + if (aLoadFlags & LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL) { + inheritPrincipal = false; + principalToInherit = nsNullPrincipal::CreateWithInheritedAttributes(this); + } + + // If the triggeringPrincipal is not passed explicitly, we first try to create + // a principal from the referrer, since the referrer URI reflects the web origin + // that triggered the load. If there is no referrer URI, we fall back to using + // the SystemPrincipal. It's safe to assume that no provided triggeringPrincipal + // and no referrer simulate a load that was triggered by the system. + // It's important to note that this block of code needs to appear *after* the block + // where we munge the principalToInherit, because otherwise we would never enter + // code blocks checking if the principalToInherit is null and we will end up with + // a wrong inheritPrincipal flag. + if (!triggeringPrincipal) { + if (referrer) { + nsresult rv = CreatePrincipalFromReferrer(referrer, + getter_AddRefs(triggeringPrincipal)); + NS_ENSURE_SUCCESS(rv, rv); + } + else { + triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); + } + } + + uint32_t flags = 0; + + if (inheritPrincipal) { + flags |= INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL; + } + + if (!sendReferrer) { + flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER; + } + + if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) { + flags |= INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; + } + + if (aLoadFlags & LOAD_FLAGS_FIRST_LOAD) { + flags |= INTERNAL_LOAD_FLAGS_FIRST_LOAD; + } + + if (aLoadFlags & LOAD_FLAGS_BYPASS_CLASSIFIER) { + flags |= INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER; + } + + if (aLoadFlags & LOAD_FLAGS_FORCE_ALLOW_COOKIES) { + flags |= INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES; + } + + if (isSrcdoc) { + flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC; + } + + return InternalLoad(aURI, + originalURI, + loadReplace, + referrer, + referrerPolicy, + triggeringPrincipal, + principalToInherit, + flags, + target, + nullptr, // No type hint + NullString(), // No forced download + postStream, + headersStream, + loadType, + nullptr, // No SHEntry + aFirstParty, + srcdoc, + sourceDocShell, + baseURI, + nullptr, // No nsIDocShell + nullptr); // No nsIRequest +} + +NS_IMETHODIMP +nsDocShell::LoadStream(nsIInputStream* aStream, nsIURI* aURI, + const nsACString& aContentType, + const nsACString& aContentCharset, + nsIDocShellLoadInfo* aLoadInfo) +{ + NS_ENSURE_ARG(aStream); + + mAllowKeywordFixup = false; + + // if the caller doesn't pass in a URI we need to create a dummy URI. necko + // currently requires a URI in various places during the load. Some consumers + // do as well. + nsCOMPtr<nsIURI> uri = aURI; + if (!uri) { + // HACK ALERT + nsresult rv = NS_OK; + uri = do_CreateInstance(NS_SIMPLEURI_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + // Make sure that the URI spec "looks" like a protocol and path... + // For now, just use a bogus protocol called "internal" + rv = uri->SetSpec(NS_LITERAL_CSTRING("internal:load-stream")); + if (NS_FAILED(rv)) { + return rv; + } + } + + uint32_t loadType = LOAD_NORMAL; + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + if (aLoadInfo) { + nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal; + (void)aLoadInfo->GetLoadType(<); + // Get the appropriate LoadType from nsIDocShellLoadInfo type + loadType = ConvertDocShellLoadInfoToLoadType(lt); + aLoadInfo->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal)); + } + + NS_ENSURE_SUCCESS(Stop(nsIWebNavigation::STOP_NETWORK), NS_ERROR_FAILURE); + + mLoadType = loadType; + + if (!triggeringPrincipal) { + triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); + } + + // build up a channel for this stream. + nsCOMPtr<nsIChannel> channel; + nsresult rv = NS_NewInputStreamChannel(getter_AddRefs(channel), + uri, + aStream, + triggeringPrincipal, + nsILoadInfo::SEC_NORMAL, + nsIContentPolicy::TYPE_OTHER, + aContentType, + aContentCharset); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + nsCOMPtr<nsIURILoader> uriLoader(do_GetService(NS_URI_LOADER_CONTRACTID)); + NS_ENSURE_TRUE(uriLoader, NS_ERROR_FAILURE); + + NS_ENSURE_SUCCESS(DoChannelLoad(channel, uriLoader, false), + NS_ERROR_FAILURE); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::CreateLoadInfo(nsIDocShellLoadInfo** aLoadInfo) +{ + nsDocShellLoadInfo* loadInfo = new nsDocShellLoadInfo(); + nsCOMPtr<nsIDocShellLoadInfo> localRef(loadInfo); + + localRef.forget(aLoadInfo); + return NS_OK; +} + +/* + * 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() +{ + mEODForCurrentDocument = false; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::FirePageHideNotification(bool aIsUnload) +{ + 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) { + if (kids[i]) { + kids[i]->FirePageHideNotification(aIsUnload); + } + } + // Now make sure our editor, if any, is detached before we go + // any farther. + DetachEditorFromWindow(); + } + + return NS_OK; +} + +void +nsDocShell::MaybeInitTiming() +{ + if (mTiming && !mBlankTiming) { + return; + } + + if (mScriptGlobal && mBlankTiming) { + nsPIDOMWindowInner* innerWin = + mScriptGlobal->AsOuter()->GetCurrentInnerWindow(); + if (innerWin && innerWin->GetPerformance()) { + mTiming = innerWin->GetPerformance()->GetDOMTiming(); + mBlankTiming = false; + } + } + + if (!mTiming) { + mTiming = new nsDOMNavigationTiming(); + } + + mTiming->NotifyNavigationStart( + mIsActive ? nsDOMNavigationTiming::DocShellState::eActive + : nsDOMNavigationTiming::DocShellState::eInactive); +} + +// +// 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(nsIDocShellTreeItem* aOriginTreeItem, + nsIDocShellTreeItem* aTargetTreeItem) +{ + // We want to bypass this check for chrome callers, but only if there's + // JS on the stack. System callers still need to do it. + if (nsContentUtils::GetCurrentJSContext() && + nsContentUtils::IsCallerChrome()) { + return true; + } + + MOZ_ASSERT(aOriginTreeItem && aTargetTreeItem, "need two docshells"); + + // Get origin document principal + nsCOMPtr<nsIDocument> originDocument = aOriginTreeItem->GetDocument(); + NS_ENSURE_TRUE(originDocument, false); + + // Get target principal + nsCOMPtr<nsIDocument> targetDocument = aTargetTreeItem->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 + bool originIsFile = false; + bool targetIsFile = false; + nsCOMPtr<nsIURI> originURI; + nsCOMPtr<nsIURI> targetURI; + nsCOMPtr<nsIURI> innerOriginURI; + nsCOMPtr<nsIURI> innerTargetURI; + + rv = originDocument->NodePrincipal()->GetURI(getter_AddRefs(originURI)); + if (NS_SUCCEEDED(rv) && originURI) { + innerOriginURI = NS_GetInnermostURI(originURI); + } + + rv = targetDocument->NodePrincipal()->GetURI(getter_AddRefs(targetURI)); + if (NS_SUCCEEDED(rv) && targetURI) { + innerTargetURI = NS_GetInnermostURI(targetURI); + } + + return innerOriginURI && innerTargetURI && + NS_SUCCEEDED(innerOriginURI->SchemeIs("file", &originIsFile)) && + NS_SUCCEEDED(innerTargetURI->SchemeIs("file", &targetIsFile)) && + originIsFile && targetIsFile; +} + +nsresult +nsDocShell::GetEldestPresContext(nsPresContext** aPresContext) +{ + NS_ENSURE_ARG_POINTER(aPresContext); + *aPresContext = nullptr; + + nsCOMPtr<nsIContentViewer> viewer = mContentViewer; + while (viewer) { + nsCOMPtr<nsIContentViewer> prevViewer; + viewer->GetPreviousViewer(getter_AddRefs(prevViewer)); + if (!prevViewer) { + return viewer->GetPresContext(aPresContext); + } + viewer = prevViewer; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetPresContext(nsPresContext** aPresContext) +{ + NS_ENSURE_ARG_POINTER(aPresContext); + *aPresContext = nullptr; + + if (!mContentViewer) { + return NS_OK; + } + + return mContentViewer->GetPresContext(aPresContext); +} + +NS_IMETHODIMP_(nsIPresShell*) +nsDocShell::GetPresShell() +{ + RefPtr<nsPresContext> presContext; + (void)GetPresContext(getter_AddRefs(presContext)); + return presContext ? presContext->GetPresShell() : nullptr; +} + +NS_IMETHODIMP +nsDocShell::GetEldestPresShell(nsIPresShell** aPresShell) +{ + nsresult rv = NS_OK; + + NS_ENSURE_ARG_POINTER(aPresShell); + *aPresShell = nullptr; + + RefPtr<nsPresContext> presContext; + (void)GetEldestPresContext(getter_AddRefs(presContext)); + + if (presContext) { + NS_IF_ADDREF(*aPresShell = presContext->GetPresShell()); + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetContentViewer(nsIContentViewer** aContentViewer) +{ + NS_ENSURE_ARG_POINTER(aContentViewer); + + *aContentViewer = mContentViewer; + NS_IF_ADDREF(*aContentViewer); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetChromeEventHandler(nsIDOMEventTarget* aChromeEventHandler) +{ + // Weak reference. Don't addref. + nsCOMPtr<EventTarget> handler = do_QueryInterface(aChromeEventHandler); + mChromeEventHandler = handler.get(); + + if (mScriptGlobal) { + mScriptGlobal->SetChromeEventHandler(mChromeEventHandler); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetChromeEventHandler(nsIDOMEventTarget** aChromeEventHandler) +{ + NS_ENSURE_ARG_POINTER(aChromeEventHandler); + nsCOMPtr<EventTarget> handler = mChromeEventHandler; + handler.forget(aChromeEventHandler); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCurrentURI(nsIURI* aURI) +{ + // Note that securityUI will set STATE_IS_INSECURE, even if + // the scheme of |aURI| is "https". + SetCurrentURI(aURI, nullptr, true, 0); + return NS_OK; +} + +bool +nsDocShell::SetCurrentURI(nsIURI* aURI, nsIRequest* aRequest, + bool aFireOnLocationChange, uint32_t aLocationFlags) +{ + if (gDocShellLeakLog && MOZ_LOG_TEST(gDocShellLeakLog, LogLevel::Debug)) { + PR_LogPrint("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; + } + + mCurrentURI = NS_TryToMakeImmutable(aURI); + + if (!NS_IsAboutBlank(mCurrentURI)) { + mHasLoadedNonBlankURI = true; + } + + bool isRoot = false; // Is this the root docshell + bool isSubFrame = false; // Is this a subframe navigation? + + nsCOMPtr<nsIDocShellTreeItem> root; + + GetSameTypeRootTreeItem(getter_AddRefs(root)); + if (root.get() == static_cast<nsIDocShellTreeItem*>(this)) { + // This is the root docshell + isRoot = true; + } + if (mLSHE) { + mLSHE->GetIsSubFrame(&isSubFrame); + } + + if (!isSubFrame && !isRoot) { + /* + * We don't want to send OnLocationChange notifications when + * a subframe is being loaded for the first time, while + * visiting a frameset page + */ + return false; + } + + if (aFireOnLocationChange) { + FireOnLocationChange(this, aRequest, aURI, aLocationFlags); + } + return !aFireOnLocationChange; +} + +NS_IMETHODIMP +nsDocShell::GetCharset(nsACString& aCharset) +{ + aCharset.Truncate(); + + nsIPresShell* presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + nsIDocument* doc = presShell->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + aCharset = doc->GetDocumentCharacterSet(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GatherCharsetMenuTelemetry() +{ + nsCOMPtr<nsIContentViewer> viewer; + GetContentViewer(getter_AddRefs(viewer)); + if (!viewer) { + return NS_OK; + } + + nsIDocument* doc = viewer->GetDocument(); + if (!doc || doc->WillIgnoreCharsetOverride()) { + return NS_OK; + } + + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_USED, true); + + bool isFileURL = false; + nsIURI* url = doc->GetOriginalURI(); + if (url) { + url->SchemeIs("file", &isFileURL); + } + + int32_t charsetSource = doc->GetDocumentCharacterSetSource(); + switch (charsetSource) { + case kCharsetFromTopLevelDomain: + // Unlabeled doc on a domain that we map to a fallback encoding + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 7); + break; + case kCharsetFromFallback: + case kCharsetFromDocTypeDefault: + case kCharsetFromCache: + case kCharsetFromParentFrame: + case kCharsetFromHintPrevDoc: + // Changing charset on an unlabeled doc. + if (isFileURL) { + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 0); + } else { + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 1); + } + break; + case kCharsetFromAutoDetection: + // Changing charset on unlabeled doc where chardet fired + if (isFileURL) { + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 2); + } else { + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 3); + } + break; + case kCharsetFromMetaPrescan: + case kCharsetFromMetaTag: + case kCharsetFromChannel: + // Changing charset on a doc that had a charset label. + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 4); + break; + case kCharsetFromParentForced: + case kCharsetFromUserForced: + // Changing charset on a document that already had an override. + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 5); + break; + case kCharsetFromIrreversibleAutoDetection: + case kCharsetFromOtherComponent: + case kCharsetFromByteOrderMark: + case kCharsetUninitialized: + default: + // Bug. This isn't supposed to happen. + Telemetry::Accumulate(Telemetry::CHARSET_OVERRIDE_SITUATION, 6); + break; + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCharset(const nsACString& aCharset) +{ + // set the charset override + return SetForcedCharset(aCharset); +} + +NS_IMETHODIMP +nsDocShell::SetForcedCharset(const nsACString& aCharset) +{ + if (aCharset.IsEmpty()) { + mForcedCharset.Truncate(); + return NS_OK; + } + nsAutoCString encoding; + if (!EncodingUtils::FindEncodingForLabel(aCharset, encoding)) { + // Reject unknown labels + return NS_ERROR_INVALID_ARG; + } + if (!EncodingUtils::IsAsciiCompatible(encoding)) { + // Reject XSS hazards + return NS_ERROR_INVALID_ARG; + } + mForcedCharset = encoding; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetForcedCharset(nsACString& aResult) +{ + aResult = mForcedCharset; + return NS_OK; +} + +void +nsDocShell::SetParentCharset(const nsACString& aCharset, + int32_t aCharsetSource, + nsIPrincipal* aPrincipal) +{ + mParentCharset = aCharset; + mParentCharsetSource = aCharsetSource; + mParentCharsetPrincipal = aPrincipal; +} + +void +nsDocShell::GetParentCharset(nsACString& aCharset, + int32_t* aCharsetSource, + nsIPrincipal** aPrincipal) +{ + aCharset = mParentCharset; + *aCharsetSource = mParentCharsetSource; + NS_IF_ADDREF(*aPrincipal = mParentCharsetPrincipal); +} + +NS_IMETHODIMP +nsDocShell::GetChannelIsUnsafe(bool* aUnsafe) +{ + *aUnsafe = false; + + nsIChannel* channel = GetCurrentDocChannel(); + if (!channel) { + return NS_OK; + } + + nsCOMPtr<nsIJARChannel> jarChannel = do_QueryInterface(channel); + if (!jarChannel) { + return NS_OK; + } + + return jarChannel->GetIsUnsafe(aUnsafe); +} + +NS_IMETHODIMP +nsDocShell::GetHasMixedActiveContentLoaded(bool* aHasMixedActiveContentLoaded) +{ + nsCOMPtr<nsIDocument> doc(GetDocument()); + *aHasMixedActiveContentLoaded = doc && doc->GetHasMixedActiveContentLoaded(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasMixedActiveContentBlocked(bool* aHasMixedActiveContentBlocked) +{ + nsCOMPtr<nsIDocument> doc(GetDocument()); + *aHasMixedActiveContentBlocked = + doc && doc->GetHasMixedActiveContentBlocked(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasMixedDisplayContentLoaded(bool* aHasMixedDisplayContentLoaded) +{ + nsCOMPtr<nsIDocument> doc(GetDocument()); + *aHasMixedDisplayContentLoaded = + doc && doc->GetHasMixedDisplayContentLoaded(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasMixedDisplayContentBlocked( + bool* aHasMixedDisplayContentBlocked) +{ + nsCOMPtr<nsIDocument> doc(GetDocument()); + *aHasMixedDisplayContentBlocked = + doc && doc->GetHasMixedDisplayContentBlocked(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasTrackingContentBlocked(bool* aHasTrackingContentBlocked) +{ + nsCOMPtr<nsIDocument> doc(GetDocument()); + *aHasTrackingContentBlocked = doc && doc->GetHasTrackingContentBlocked(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasTrackingContentLoaded(bool* aHasTrackingContentLoaded) +{ + nsCOMPtr<nsIDocument> doc(GetDocument()); + *aHasTrackingContentLoaded = doc && doc->GetHasTrackingContentLoaded(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowPlugins(bool* aAllowPlugins) +{ + NS_ENSURE_ARG_POINTER(aAllowPlugins); + + *aAllowPlugins = mAllowPlugins; + if (!mAllowPlugins) { + return NS_OK; + } + + bool unsafe; + *aAllowPlugins = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowPlugins(bool aAllowPlugins) +{ + mAllowPlugins = aAllowPlugins; + // XXX should enable or disable a plugin host + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowJavascript(bool* aAllowJavascript) +{ + NS_ENSURE_ARG_POINTER(aAllowJavascript); + + *aAllowJavascript = mAllowJavascript; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowJavascript(bool aAllowJavascript) +{ + mAllowJavascript = aAllowJavascript; + RecomputeCanExecuteScripts(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetUsePrivateBrowsing(bool* aUsePrivateBrowsing) +{ + NS_ENSURE_ARG_POINTER(aUsePrivateBrowsing); + AssertOriginAttributesMatchPrivateBrowsing(); + *aUsePrivateBrowsing = mPrivateBrowsingId > 0; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetUsePrivateBrowsing(bool aUsePrivateBrowsing) +{ + nsContentUtils::ReportToConsoleNonLocalized( + NS_LITERAL_STRING("Only internal code is allowed to set the usePrivateBrowsing attribute"), + nsIScriptError::warningFlag, + NS_LITERAL_CSTRING("Internal API Used"), + mContentViewer ? mContentViewer->GetDocument() : nullptr); + + if (!CanSetOriginAttributes()) { + bool changed = aUsePrivateBrowsing != (mPrivateBrowsingId > 0); + + return changed ? NS_ERROR_FAILURE : NS_OK; + } + + return SetPrivateBrowsing(aUsePrivateBrowsing); +} + +NS_IMETHODIMP +nsDocShell::SetPrivateBrowsing(bool aUsePrivateBrowsing) +{ + bool changed = aUsePrivateBrowsing != (mPrivateBrowsingId > 0); + if (changed) { + mPrivateBrowsingId = aUsePrivateBrowsing ? 1 : 0; + + if (mItemType != typeChrome) { + mOriginAttributes.SyncAttributesWithPrivateBrowsing(aUsePrivateBrowsing); + } + + if (mAffectPrivateSessionLifetime) { + if (aUsePrivateBrowsing) { + IncreasePrivateDocShellCount(); + } else { + DecreasePrivateDocShellCount(); + } + } + } + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsILoadContext> shell = do_QueryObject(iter.GetNext()); + if (shell) { + shell->SetPrivateBrowsing(aUsePrivateBrowsing); + } + } + + if (changed) { + nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mPrivacyObservers); + while (iter.HasMore()) { + nsWeakPtr ref = iter.GetNext(); + nsCOMPtr<nsIPrivacyTransitionObserver> obs = do_QueryReferent(ref); + if (!obs) { + mPrivacyObservers.RemoveElement(ref); + } else { + obs->PrivateModeChanged(aUsePrivateBrowsing); + } + } + } + + AssertOriginAttributesMatchPrivateBrowsing(); + return NS_OK; +} + +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); + + *aUseRemoteTabs = mUseRemoteTabs; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetRemoteTabs(bool aUseRemoteTabs) +{ +#ifdef MOZ_CRASHREPORTER + if (aUseRemoteTabs) { + CrashReporter::AnnotateCrashReport(NS_LITERAL_CSTRING("DOMIPCEnabled"), + NS_LITERAL_CSTRING("1")); + } +#endif + + mUseRemoteTabs = aUseRemoteTabs; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAffectPrivateSessionLifetime(bool aAffectLifetime) +{ + bool change = aAffectLifetime != mAffectPrivateSessionLifetime; + if (change && UsePrivateBrowsing()) { + AssertOriginAttributesMatchPrivateBrowsing(); + if (aAffectLifetime) { + IncreasePrivateDocShellCount(); + } else { + DecreasePrivateDocShellCount(); + } + } + mAffectPrivateSessionLifetime = aAffectLifetime; + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext()); + if (shell) { + shell->SetAffectPrivateSessionLifetime(aAffectLifetime); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAffectPrivateSessionLifetime(bool* aAffectLifetime) +{ + *aAffectLifetime = mAffectPrivateSessionLifetime; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::AddWeakPrivacyTransitionObserver( + nsIPrivacyTransitionObserver* aObserver) +{ + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_NOT_AVAILABLE; + } + return mPrivacyObservers.AppendElement(weakObs) ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::AddWeakReflowObserver(nsIReflowObserver* aObserver) +{ + nsWeakPtr weakObs = do_GetWeakReference(aObserver); + if (!weakObs) { + return NS_ERROR_FAILURE; + } + return mReflowObservers.AppendElement(weakObs) ? NS_OK : NS_ERROR_FAILURE; +} + +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) { + mReflowObservers.RemoveElement(ref); + } 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; + if (!mAllowMetaRedirects) { + return NS_OK; + } + + bool unsafe; + *aReturn = NS_SUCCEEDED(GetChannelIsUnsafe(&unsafe)) && !unsafe; + 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->AsOuter()->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 = mAllowContentRetargeting; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowContentRetargeting(bool aAllowContentRetargeting) +{ + mAllowContentRetargetingOnChildren = aAllowContentRetargeting; + mAllowContentRetargeting = aAllowContentRetargeting; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowContentRetargetingOnChildren( + bool* aAllowContentRetargetingOnChildren) +{ + *aAllowContentRetargetingOnChildren = mAllowContentRetargetingOnChildren; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAllowContentRetargetingOnChildren( + bool aAllowContentRetargetingOnChildren) +{ + mAllowContentRetargetingOnChildren = aAllowContentRetargetingOnChildren; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetInheritPrivateBrowsingId(bool* aInheritPrivateBrowsingId) +{ + *aInheritPrivateBrowsingId = mInheritPrivateBrowsingId; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetInheritPrivateBrowsingId(bool aInheritPrivateBrowsingId) +{ + mInheritPrivateBrowsingId = aInheritPrivateBrowsingId; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetFullscreenAllowed(bool* aFullscreenAllowed) +{ + NS_ENSURE_ARG_POINTER(aFullscreenAllowed); + + // Browsers and apps have their mFullscreenAllowed retrieved from their + // corresponding iframe in their parent upon creation. + if (mFullscreenAllowed != CHECK_ATTRIBUTES) { + *aFullscreenAllowed = (mFullscreenAllowed == PARENT_ALLOWS); + return NS_OK; + } + + // Assume false until we determine otherwise... + *aFullscreenAllowed = false; + + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + if (!win) { + return NS_OK; + } + if (nsCOMPtr<Element> frameElement = win->GetFrameElementInternal()) { + if (frameElement->IsXULElement()) { + if (frameElement->HasAttr(kNameSpaceID_None, + nsGkAtoms::disablefullscreen)) { + // Document inside this frame is explicitly disabled. + return NS_OK; + } + } else { + // We do not allow document inside any containing element other + // than iframe to enter fullscreen. + if (frameElement->IsHTMLElement(nsGkAtoms::iframe)) { + // If any ancestor iframe does not have allowfullscreen attribute + // set, then fullscreen is not allowed. + if (!frameElement->HasAttr(kNameSpaceID_None, + nsGkAtoms::allowfullscreen) && + !frameElement->HasAttr(kNameSpaceID_None, + nsGkAtoms::mozallowfullscreen)) { + return NS_OK; + } + } else if (frameElement->IsHTMLElement(nsGkAtoms::embed)) { + // Respect allowfullscreen only if this is a rewritten YouTube embed. + nsCOMPtr<nsIObjectLoadingContent> objectLoadingContent = + do_QueryInterface(frameElement); + if (!objectLoadingContent) { + return NS_OK; + } + nsObjectLoadingContent* olc = + static_cast<nsObjectLoadingContent*>(objectLoadingContent.get()); + if (!olc->IsRewrittenYoutubeEmbed()) { + return NS_OK; + } + // We don't have to check prefixed attributes because Flash does not + // support them. + if (!frameElement->HasAttr(kNameSpaceID_None, + nsGkAtoms::allowfullscreen)) { + return NS_OK; + } + } else { + // neither iframe nor embed + return NS_OK; + } + } + } + + // If we have no parent then we're the root docshell; no ancestor of the + // original docshell doesn't have a allowfullscreen attribute, so + // report fullscreen as allowed. + RefPtr<nsDocShell> parent = GetParentDocshell(); + if (!parent) { + *aFullscreenAllowed = true; + return NS_OK; + } + + // Otherwise, we have a parent, continue the checking for + // mozFullscreenAllowed in the parent docshell's ancestors. + return parent->GetFullscreenAllowed(aFullscreenAllowed); +} + +NS_IMETHODIMP +nsDocShell::SetFullscreenAllowed(bool aFullscreenAllowed) +{ + if (!nsIDocShell::GetIsMozBrowserOrApp()) { + // Only allow setting of fullscreenAllowed on content/process boundaries. + // At non-boundaries the fullscreenAllowed attribute is calculated based on + // whether all enclosing frames have the "mozFullscreenAllowed" attribute + // set to "true". fullscreenAllowed is set at the process boundaries to + // propagate the value of the parent's "mozFullscreenAllowed" attribute + // across process boundaries. + return NS_ERROR_UNEXPECTED; + } + mFullscreenAllowed = (aFullscreenAllowed ? PARENT_ALLOWS : PARENT_PROHIBITS); + return NS_OK; +} + +ScreenOrientationInternal +nsDocShell::OrientationLock() +{ + return mOrientationLock; +} + +void +nsDocShell::SetOrientationLock(ScreenOrientationInternal aOrientationLock) +{ + mOrientationLock = aOrientationLock; +} + +NS_IMETHODIMP +nsDocShell::GetMayEnableCharacterEncodingMenu( + bool* aMayEnableCharacterEncodingMenu) +{ + *aMayEnableCharacterEncodingMenu = false; + if (!mContentViewer) { + return NS_OK; + } + nsIDocument* doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_OK; + } + if (doc->WillIgnoreCharsetOverride()) { + return NS_OK; + } + + *aMayEnableCharacterEncodingMenu = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDocShellEnumerator(int32_t aItemType, int32_t aDirection, + nsISimpleEnumerator** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + RefPtr<nsDocShellEnumerator> docShellEnum; + if (aDirection == ENUMERATE_FORWARDS) { + docShellEnum = new nsDocShellForwardsEnumerator; + } else { + docShellEnum = new nsDocShellBackwardsEnumerator; + } + + nsresult rv = docShellEnum->SetEnumDocShellType(aItemType); + if (NS_FAILED(rv)) { + return rv; + } + + rv = docShellEnum->SetEnumerationRootItem((nsIDocShellTreeItem*)this); + if (NS_FAILED(rv)) { + return rv; + } + + rv = docShellEnum->First(); + if (NS_FAILED(rv)) { + return rv; + } + + rv = docShellEnum->QueryInterface(NS_GET_IID(nsISimpleEnumerator), + (void**)aResult); + + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetAppType(uint32_t* aAppType) +{ + *aAppType = mAppType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetAppType(uint32_t 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::GetMarginWidth(int32_t* aWidth) +{ + NS_ENSURE_ARG_POINTER(aWidth); + + *aWidth = mMarginWidth; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetMarginWidth(int32_t aWidth) +{ + mMarginWidth = aWidth; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetMarginHeight(int32_t* aHeight) +{ + NS_ENSURE_ARG_POINTER(aHeight); + + *aHeight = mMarginHeight; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetMarginHeight(int32_t aHeight) +{ + mMarginHeight = aHeight; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetBusyFlags(uint32_t* 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::GetSecurityUI(nsISecureBrowserUI** aSecurityUI) +{ + NS_IF_ADDREF(*aSecurityUI = mSecurityUI); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetSecurityUI(nsISecureBrowserUI* aSecurityUI) +{ + mSecurityUI = aSecurityUI; + mSecurityUI->SetDocShell(this); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetUseErrorPages(bool* aUseErrorPages) +{ + *aUseErrorPages = UseErrorPages(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetUseErrorPages(bool aUseErrorPages) +{ + // If mUseErrorPages is set explicitly, stop using sUseErrorPages. + if (mObserveErrorPages) { + mObserveErrorPages = false; + } + mUseErrorPages = aUseErrorPages; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetPreviousTransIndex(int32_t* aPreviousTransIndex) +{ + *aPreviousTransIndex = mPreviousTransIndex; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetLoadedTransIndex(int32_t* aLoadedTransIndex) +{ + *aLoadedTransIndex = mLoadedTransIndex; + 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. + mPreviousTransIndex = std::max(-1, mPreviousTransIndex - aNumEntries); + mLoadedTransIndex = std::max(0, mLoadedTransIndex - aNumEntries); + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext()); + if (shell) { + shell->HistoryPurged(aNumEntries); + } + } + + return NS_OK; +} + +nsresult +nsDocShell::HistoryTransactionRemoved(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 == mPreviousTransIndex) { + mPreviousTransIndex = -1; + } else if (aIndex < mPreviousTransIndex) { + --mPreviousTransIndex; + } + if (mLoadedTransIndex == aIndex) { + mLoadedTransIndex = 0; + } else if (aIndex < mLoadedTransIndex) { + --mLoadedTransIndex; + } + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext()); + if (shell) { + static_cast<nsDocShell*>(shell.get())->HistoryTransactionRemoved(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) +{ + bool ignore; + *aWhen = + (TimeStamp::Now() - TimeStamp::ProcessCreation(ignore)).ToMilliseconds(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetWindowDraggingAllowed(bool aValue) +{ + RefPtr<nsDocShell> parent = GetParentDocshell(); + 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 = GetParentDocshell(); + if (mItemType == typeChrome && !parent) { + // Top level chrome window + *aValue = true; + } else { + *aValue = mWindowDraggingAllowed; + } + return NS_OK; +} + +nsIDOMStorageManager* +nsDocShell::TopSessionStorageManager() +{ + nsresult rv; + + nsCOMPtr<nsIDocShellTreeItem> topItem; + rv = GetSameTypeRootTreeItem(getter_AddRefs(topItem)); + if (NS_FAILED(rv)) { + return nullptr; + } + + if (!topItem) { + return nullptr; + } + + nsDocShell* topDocShell = static_cast<nsDocShell*>(topItem.get()); + if (topDocShell != this) { + return topDocShell->TopSessionStorageManager(); + } + + if (!mSessionStorageManager) { + mSessionStorageManager = + do_CreateInstance("@mozilla.org/dom/sessionStorage-manager;1"); + } + + return mSessionStorageManager; +} + +NS_IMETHODIMP +nsDocShell::GetSessionStorageForPrincipal(nsIPrincipal* aPrincipal, + const nsAString& aDocumentURI, + bool aCreate, + nsIDOMStorage** aStorage) +{ + nsCOMPtr<nsIDOMStorageManager> manager = TopSessionStorageManager(); + if (!manager) { + return NS_ERROR_UNEXPECTED; + } + + nsCOMPtr<nsPIDOMWindowOuter> domWin = GetWindow(); + + AssertOriginAttributesMatchPrivateBrowsing(); + if (aCreate) { + return manager->CreateStorage(domWin->GetCurrentInnerWindow(), aPrincipal, + aDocumentURI, UsePrivateBrowsing(), aStorage); + } + + return manager->GetStorage(domWin->GetCurrentInnerWindow(), aPrincipal, + UsePrivateBrowsing(), aStorage); +} + +nsresult +nsDocShell::AddSessionStorage(nsIPrincipal* aPrincipal, nsIDOMStorage* aStorage) +{ + RefPtr<DOMStorage> storage = static_cast<DOMStorage*>(aStorage); + if (!storage) { + return NS_ERROR_UNEXPECTED; + } + + nsIPrincipal* storagePrincipal = storage->GetPrincipal(); + if (storagePrincipal != aPrincipal) { + NS_ERROR("Wanting to add a sessionStorage for different principal"); + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsCOMPtr<nsIDOMStorageManager> manager = TopSessionStorageManager(); + if (!manager) { + return NS_ERROR_UNEXPECTED; + } + + return manager->CloneStorage(aStorage); +} + +NS_IMETHODIMP +nsDocShell::GetCurrentDocumentChannel(nsIChannel** aResult) +{ + NS_IF_ADDREF(*aResult = GetCurrentDocChannel()); + return NS_OK; +} + +nsIChannel* +nsDocShell::GetCurrentDocChannel() +{ + if (mContentViewer) { + nsIDocument* 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; + } + return mScrollObservers.AppendElement(weakObs) ? NS_OK : NS_ERROR_FAILURE; +} + +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 { + mScrollObservers.RemoveElement(ref); + } + } +} + +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 { + mScrollObservers.RemoveElement(ref); + } + } +} + +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 { + mScrollObservers.RemoveElement(ref); + } + } + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIDocShellTreeItem +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetName(nsAString& aName) +{ + aName = mName; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetName(const nsAString& aName) +{ + mName = aName; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::NameEquals(const nsAString& aName, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = mName.Equals(aName); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCustomUserAgent(nsAString& aCustomUserAgent) +{ + aCustomUserAgent = mCustomUserAgent; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCustomUserAgent(const nsAString& aCustomUserAgent) +{ + mCustomUserAgent = aCustomUserAgent; + RefPtr<nsGlobalWindow> win = mScriptGlobal ? + mScriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + if (win) { + ErrorResult ignored; + Navigator* navigator = win->GetNavigator(ignored); + ignored.SuppressException(); + if (navigator) { + navigator->ClearUserAgentCache(); + } + } + + uint32_t childCount = mChildList.Length(); + for (uint32_t i = 0; i < childCount; ++i) { + nsCOMPtr<nsIDocShell> childShell = do_QueryInterface(ChildAt(i)); + if (childShell) { + childShell->SetCustomUserAgent(aCustomUserAgent); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetTouchEventsOverride(uint32_t* aTouchEventsOverride) +{ + NS_ENSURE_ARG_POINTER(aTouchEventsOverride); + + *aTouchEventsOverride = mTouchEventsOverride; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetTouchEventsOverride(uint32_t aTouchEventsOverride) +{ + if (!(aTouchEventsOverride == nsIDocShell::TOUCHEVENTS_OVERRIDE_NONE || + aTouchEventsOverride == nsIDocShell::TOUCHEVENTS_OVERRIDE_ENABLED || + aTouchEventsOverride == nsIDocShell::TOUCHEVENTS_OVERRIDE_DISABLED)) { + return NS_ERROR_INVALID_ARG; + } + + mTouchEventsOverride = aTouchEventsOverride; + + uint32_t childCount = mChildList.Length(); + for (uint32_t i = 0; i < childCount; ++i) { + nsCOMPtr<nsIDocShell> childShell = do_QueryInterface(ChildAt(i)); + if (childShell) { + childShell->SetTouchEventsOverride(aTouchEventsOverride); + } + } + return NS_OK; +} + +/* virtual */ int32_t +nsDocShell::ItemType() +{ + return mItemType; +} + +NS_IMETHODIMP +nsDocShell::GetItemType(int32_t* aItemType) +{ + NS_ENSURE_ARG_POINTER(aItemType); + + *aItemType = ItemType(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetItemType(int32_t aItemType) +{ + NS_ENSURE_ARG((aItemType == typeChrome) || (typeContent == aItemType)); + + // Only allow setting the type on root docshells. Those would be the ones + // that have the docloader service as mParent or have no mParent at all. + nsCOMPtr<nsIDocumentLoader> docLoaderService = + do_GetService(NS_DOCUMENTLOADER_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(docLoaderService, NS_ERROR_UNEXPECTED); + + NS_ENSURE_STATE(!mParent || mParent == docLoaderService); + + mItemType = aItemType; + + // disable auth prompting for anything but content + mAllowAuth = mItemType == typeContent; + + RefPtr<nsPresContext> presContext = nullptr; + GetPresContext(getter_AddRefs(presContext)); + if (presContext) { + presContext->UpdateIsChrome(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetParent(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; +} + +already_AddRefed<nsDocShell> +nsDocShell::GetParentDocshell() +{ + nsCOMPtr<nsIDocShell> docshell = do_QueryInterface(GetAsSupports(mParent)); + return docshell.forget().downcast<nsDocShell>(); +} + +void +nsDocShell::RecomputeCanExecuteScripts() +{ + bool old = mCanExecuteScripts; + RefPtr<nsDocShell> parent = GetParentDocshell(); + + // If we have no tree owner, that means that we've been detached from the + // docshell tree (this is distinct from having no parent dochshell, which + // is the case for root docshells). It would be nice to simply disallow + // script in detached docshells, but bug 986542 demonstrates that this + // behavior breaks at least one website. + // + // So instead, we use our previous value, unless mAllowJavascript has been + // explicitly set to false. + if (!mTreeOwner) { + mCanExecuteScripts = mCanExecuteScripts && mAllowJavascript; + // If scripting has been explicitly disabled on our docshell, we're done. + } else if (!mAllowJavascript) { + mCanExecuteScripts = false; + // If we have a parent, inherit. + } else if (parent) { + mCanExecuteScripts = parent->mCanExecuteScripts; + // Otherwise, we're the root of the tree, and we haven't explicitly disabled + // script. Allow. + } else { + mCanExecuteScripts = true; + } + + // Inform our active DOM window. + // + // This will pass the outer, which will be in the scope of the active inner. + if (mScriptGlobal && mScriptGlobal->GetGlobalJSObject()) { + xpc::Scriptability& scriptability = + xpc::Scriptability::Get(mScriptGlobal->GetGlobalJSObject()); + scriptability.SetDocShellAllowsScript(mCanExecuteScripts); + } + + // If our value has changed, our children might be affected. Recompute their + // value as well. + if (old != mCanExecuteScripts) { + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + static_cast<nsDocShell*>(iter.GetNext())->RecomputeCanExecuteScripts(); + } + } +} + +nsresult +nsDocShell::SetDocLoaderParent(nsDocLoader* aParent) +{ + bool wasFrame = IsFrame(); +#ifdef DEBUG + bool wasPrivate = UsePrivateBrowsing(); +#endif + + nsresult rv = nsDocLoader::SetDocLoaderParent(aParent); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsISupportsPriority> priorityGroup = do_QueryInterface(mLoadGroup); + if (wasFrame != IsFrame() && 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; + nsString customUserAgent; + nsCOMPtr<nsIDocShell> parentAsDocShell(do_QueryInterface(parent)); + if (parentAsDocShell) { + if (mAllowPlugins && NS_SUCCEEDED(parentAsDocShell->GetAllowPlugins(&value))) { + SetAllowPlugins(value); + } + if (mAllowJavascript && NS_SUCCEEDED(parentAsDocShell->GetAllowJavascript(&value))) { + SetAllowJavascript(value); + } + 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); + } + SetAllowContentRetargeting(mAllowContentRetargeting && + parentAsDocShell->GetAllowContentRetargetingOnChildren()); + if (parentAsDocShell->GetIsPrerendered()) { + SetIsPrerendered(); + } + if (NS_SUCCEEDED(parentAsDocShell->GetIsActive(&value))) { + // a prerendered docshell is not active yet + SetIsActive(value && !mIsPrerendered); + } + if (NS_SUCCEEDED(parentAsDocShell->GetCustomUserAgent(customUserAgent)) && + !customUserAgent.IsEmpty()) { + SetCustomUserAgent(customUserAgent); + } + if (NS_FAILED(parentAsDocShell->GetAllowDNSPrefetch(&value))) { + value = false; + } + SetAllowDNSPrefetch(mAllowDNSPrefetch && value); + if (mInheritPrivateBrowsingId) { + value = parentAsDocShell->GetAffectPrivateSessionLifetime(); + SetAffectPrivateSessionLifetime(value); + } + uint32_t flags; + if (NS_SUCCEEDED(parentAsDocShell->GetDefaultLoadFlags(&flags))) { + SetDefaultLoadFlags(flags); + } + uint32_t touchEventsOverride; + if (NS_SUCCEEDED(parentAsDocShell->GetTouchEventsOverride(&touchEventsOverride))) { + SetTouchEventsOverride(touchEventsOverride); + } + } + + nsCOMPtr<nsILoadContext> parentAsLoadContext(do_QueryInterface(parent)); + if (parentAsLoadContext && mInheritPrivateBrowsingId && + NS_SUCCEEDED(parentAsLoadContext->GetUsePrivateBrowsing(&value))) { + SetPrivateBrowsing(value); + } + + nsCOMPtr<nsIURIContentListener> parentURIListener(do_GetInterface(parent)); + if (parentURIListener) { + mContentListener->SetParentContentListener(parentURIListener); + } + + // Our parent has changed. Recompute scriptability. + RecomputeCanExecuteScripts(); + + NS_ASSERTION(mInheritPrivateBrowsingId || wasPrivate == UsePrivateBrowsing(), + "Private browsing state changed while inheritance was disabled"); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSameTypeParent(nsIDocShellTreeItem** aParent) +{ + NS_ENSURE_ARG_POINTER(aParent); + *aParent = nullptr; + + if (nsIDocShell::GetIsMozBrowserOrApp()) { + return NS_OK; + } + + nsCOMPtr<nsIDocShellTreeItem> parent = + do_QueryInterface(GetAsSupports(mParent)); + if (!parent) { + return NS_OK; + } + + if (parent->ItemType() == mItemType) { + parent.swap(*aParent); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSameTypeParentIgnoreBrowserAndAppBoundaries(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::GetRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) +{ + NS_ENSURE_ARG_POINTER(aRootTreeItem); + + RefPtr<nsDocShell> root = this; + RefPtr<nsDocShell> parent = root->GetParentDocshell(); + while (parent) { + root = parent; + parent = root->GetParentDocshell(); + } + + root.forget(aRootTreeItem); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSameTypeRootTreeItem(nsIDocShellTreeItem** aRootTreeItem) +{ + NS_ENSURE_ARG_POINTER(aRootTreeItem); + *aRootTreeItem = static_cast<nsIDocShellTreeItem*>(this); + + nsCOMPtr<nsIDocShellTreeItem> parent; + NS_ENSURE_SUCCESS(GetSameTypeParent(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + while (parent) { + *aRootTreeItem = parent; + NS_ENSURE_SUCCESS( + (*aRootTreeItem)->GetSameTypeParent(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + } + NS_ADDREF(*aRootTreeItem); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSameTypeRootTreeItemIgnoreBrowserAndAppBoundaries(nsIDocShell ** aRootTreeItem) +{ + NS_ENSURE_ARG_POINTER(aRootTreeItem); + *aRootTreeItem = static_cast<nsIDocShell *>(this); + + nsCOMPtr<nsIDocShell> parent; + NS_ENSURE_SUCCESS(GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + while (parent) { + *aRootTreeItem = parent; + NS_ENSURE_SUCCESS((*aRootTreeItem)-> + GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)), + NS_ERROR_FAILURE); + } + NS_ADDREF(*aRootTreeItem); + return NS_OK; +} + +/* static */ +bool +nsDocShell::CanAccessItem(nsIDocShellTreeItem* aTargetItem, + nsIDocShellTreeItem* aAccessingItem, + bool aConsiderOpener) +{ + NS_PRECONDITION(aTargetItem, "Must have target item!"); + + if (!gValidateOrigin || !aAccessingItem) { + // Good to go + return true; + } + + // XXXbz should we care if aAccessingItem or the document therein is + // chrome? Should those get extra privileges? + + // For historical context, see: + // + // Bug 13871: Prevent frameset spoofing + // Bug 103638: Targets with same name in different windows open in wrong + // window with javascript + // Bug 408052: Adopt "ancestor" frame navigation policy + + // Now do a security check. + // + // Disallow navigation if the two frames are not part of the same app, or if + // they have different is-in-browser-element states. + // + // Allow navigation if + // 1) aAccessingItem can script aTargetItem or one of its ancestors in + // the frame hierarchy or + // 2) aTargetItem is a top-level frame and aAccessingItem is its descendant + // 3) aTargetItem is a top-level frame and aAccessingItem can target + // its opener per rule (1) or (2). + + if (aTargetItem == aAccessingItem) { + // A frame is allowed to navigate itself. + return true; + } + + nsCOMPtr<nsIDocShell> targetDS = do_QueryInterface(aTargetItem); + nsCOMPtr<nsIDocShell> accessingDS = do_QueryInterface(aAccessingItem); + if (!targetDS || !accessingDS) { + // We must be able to convert both to nsIDocShell. + return false; + } + + if (targetDS->GetIsInIsolatedMozBrowserElement() != + accessingDS->GetIsInIsolatedMozBrowserElement() || + targetDS->GetAppId() != accessingDS->GetAppId()) { + return false; + } + + nsCOMPtr<nsIDocShellTreeItem> accessingRoot; + aAccessingItem->GetSameTypeRootTreeItem(getter_AddRefs(accessingRoot)); + nsCOMPtr<nsIDocShell> accessingRootDS = do_QueryInterface(accessingRoot); + + nsCOMPtr<nsIDocShellTreeItem> targetRoot; + aTargetItem->GetSameTypeRootTreeItem(getter_AddRefs(targetRoot)); + nsCOMPtr<nsIDocShell> targetRootDS = do_QueryInterface(targetRoot); + + DocShellOriginAttributes targetOA = + static_cast<nsDocShell*>(targetDS.get())->GetOriginAttributes(); + DocShellOriginAttributes accessingOA = + static_cast<nsDocShell*>(accessingDS.get())->GetOriginAttributes(); + + // When the first party isolation is on, the top-level docShell may not have + // the firstPartyDomain in its originAttributes, but its document will have + // it. So we get the firstPartyDomain from the nodePrincipal of the document + // before we compare the originAttributes. + if (OriginAttributes::IsFirstPartyEnabled()) { + if (accessingDS == accessingRootDS && + aAccessingItem->ItemType() == nsIDocShellTreeItem::typeContent && + !accessingDS->GetIsMozBrowserOrApp()) { + + nsCOMPtr<nsIDocument> accessingDoc = aAccessingItem->GetDocument(); + + if (accessingDoc) { + nsCOMPtr<nsIPrincipal> accessingPrincipal = accessingDoc->NodePrincipal(); + + accessingOA.mFirstPartyDomain = + BasePrincipal::Cast(accessingPrincipal)->OriginAttributesRef().mFirstPartyDomain; + } + } + + if (targetDS == targetRootDS && + aTargetItem->ItemType() == nsIDocShellTreeItem::typeContent && + !targetDS->GetIsMozBrowserOrApp()) { + + nsCOMPtr<nsIDocument> targetDoc = aAccessingItem->GetDocument(); + + if (targetDoc) { + nsCOMPtr<nsIPrincipal> targetPrincipal = targetDoc->NodePrincipal(); + + targetOA.mFirstPartyDomain = + BasePrincipal::Cast(targetPrincipal)->OriginAttributesRef().mFirstPartyDomain; + } + } + } + + if (targetOA != accessingOA) { + return false; + } + + // A private document can't access a non-private one, and vice versa. + if (static_cast<nsDocShell*>(targetDS.get())->UsePrivateBrowsing() != + static_cast<nsDocShell*>(accessingDS.get())->UsePrivateBrowsing()) { + return false; + } + + if (aTargetItem == accessingRoot) { + // A frame can navigate its root. + return true; + } + + // Check if aAccessingItem can navigate one of aTargetItem's ancestors. + nsCOMPtr<nsIDocShellTreeItem> target = aTargetItem; + do { + if (ValidateOrigin(aAccessingItem, target)) { + return true; + } + + nsCOMPtr<nsIDocShellTreeItem> parent; + target->GetSameTypeParent(getter_AddRefs(parent)); + parent.swap(target); + } while (target); + + if (aTargetItem != targetRoot) { + // target is a subframe, not in accessor's frame hierarchy, and all its + // ancestors have origins different from that of the accessor. Don't + // allow access. + return false; + } + + if (!aConsiderOpener) { + // All done here + return false; + } + + nsCOMPtr<nsPIDOMWindowOuter> targetWindow = aTargetItem->GetWindow(); + if (!targetWindow) { + NS_ERROR("This should not happen, really"); + return false; + } + + nsCOMPtr<mozIDOMWindowProxy> targetOpener = targetWindow->GetOpener(); + nsCOMPtr<nsIWebNavigation> openerWebNav(do_GetInterface(targetOpener)); + nsCOMPtr<nsIDocShellTreeItem> openerItem(do_QueryInterface(openerWebNav)); + + if (!openerItem) { + return false; + } + + return CanAccessItem(openerItem, aAccessingItem, false); +} + +static bool +ItemIsActive(nsIDocShellTreeItem* aItem) +{ + if (nsCOMPtr<nsPIDOMWindowOuter> window = aItem->GetWindow()) { + auto* win = nsGlobalWindow::Cast(window); + MOZ_ASSERT(win->IsOuterWindow()); + if (!win->GetClosedOuter()) { + return true; + } + } + + return false; +} + +NS_IMETHODIMP +nsDocShell::FindItemWithName(const nsAString& aName, + nsISupports* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + // If we don't find one, we return NS_OK and a null result + *aResult = nullptr; + + if (aName.IsEmpty()) { + return NS_OK; + } + + if (aRequestor) { + // If aRequestor is not null we don't need to check special names, so + // just hand straight off to the search by actual name function. + return DoFindItemWithName(aName, aRequestor, aOriginalRequestor, aResult); + } else { + // This is the entry point into the target-finding algorithm. Check + // for special names. This should only be done once, hence the check + // for a null aRequestor. + + nsCOMPtr<nsIDocShellTreeItem> foundItem; + if (aName.LowerCaseEqualsLiteral("_self")) { + foundItem = this; + } else if (aName.LowerCaseEqualsLiteral("_blank")) { + // Just return null. Caller must handle creating a new window with + // a blank name himself. + return NS_OK; + } else if (aName.LowerCaseEqualsLiteral("_parent")) { + GetSameTypeParent(getter_AddRefs(foundItem)); + if (!foundItem) { + foundItem = this; + } + } else if (aName.LowerCaseEqualsLiteral("_top")) { + GetSameTypeRootTreeItem(getter_AddRefs(foundItem)); + NS_ASSERTION(foundItem, "Must have this; worst case it's us!"); + } else { + // Do the search for item by an actual name. + DoFindItemWithName(aName, aRequestor, aOriginalRequestor, + getter_AddRefs(foundItem)); + } + + if (foundItem && !CanAccessItem(foundItem, aOriginalRequestor)) { + foundItem = nullptr; + } + + // DoFindItemWithName only returns active items and we don't check if + // the item is active for the special cases. + if (foundItem) { + foundItem.swap(*aResult); + } + return NS_OK; + } +} + +void +nsDocShell::AssertOriginAttributesMatchPrivateBrowsing() { + // Chrome docshells must not have a private browsing OriginAttribute + // Content docshells must maintain the equality: + // mOriginAttributes.mPrivateBrowsingId == mPrivateBrowsingId + if (mItemType == typeChrome) { + MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == 0); + } else { + MOZ_DIAGNOSTIC_ASSERT(mOriginAttributes.mPrivateBrowsingId == mPrivateBrowsingId); + } +} + +nsresult +nsDocShell::DoFindItemWithName(const nsAString& aName, + nsISupports* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aResult) +{ + // First we check our name. + if (mName.Equals(aName) && ItemIsActive(this) && + CanAccessItem(this, aOriginalRequestor)) { + NS_ADDREF(*aResult = this); + return NS_OK; + } + + // This QI may fail, but the places where we want to compare, comparing + // against nullptr serves the same purpose. + nsCOMPtr<nsIDocShellTreeItem> reqAsTreeItem(do_QueryInterface(aRequestor)); + + // Second we check our children making sure not to ask a child if + // it is the aRequestor. +#ifdef DEBUG + nsresult rv = +#endif + FindChildWithName(aName, true, true, reqAsTreeItem, aOriginalRequestor, + aResult); + NS_ASSERTION(NS_SUCCEEDED(rv), + "FindChildWithName should not be failing here."); + if (*aResult) { + return NS_OK; + } + + // Third if we have a parent and it isn't the requestor then we + // should ask it to do the search. If it is the requestor we + // should just stop here and let the parent do the rest. If we + // don't have a parent, then we should ask the + // docShellTreeOwner to do the search. + nsCOMPtr<nsIDocShellTreeItem> parentAsTreeItem = + do_QueryInterface(GetAsSupports(mParent)); + if (parentAsTreeItem) { + if (parentAsTreeItem == reqAsTreeItem) { + return NS_OK; + } + + // If we have a same-type parent, respecting browser and app boundaries. + // NOTE: Could use GetSameTypeParent if the issues described in bug 1310344 are fixed. + if (!GetIsMozBrowserOrApp() && parentAsTreeItem->ItemType() == mItemType) { + return parentAsTreeItem->FindItemWithName( + aName, + static_cast<nsIDocShellTreeItem*>(this), + aOriginalRequestor, + aResult); + } + } + + // If we have a null parent or the parent is not of the same type, we need to + // give up on finding it in our tree, and start looking in our TabGroup. + nsCOMPtr<nsPIDOMWindowOuter> window = GetWindow(); + if (window) { + RefPtr<mozilla::dom::TabGroup> tabGroup = window->TabGroup(); + // We don't want to make the request to our TabGroup if they are the ones + // which made a request to us. + if (tabGroup != aRequestor) { + tabGroup->FindItemWithName(aName, this, aOriginalRequestor, aResult); + } + } + + return NS_OK; +} + +bool +nsDocShell::IsSandboxedFrom(nsIDocShell* aTargetDocShell) +{ + // If no target then not sandboxed. + if (!aTargetDocShell) { + return false; + } + + // We cannot be sandboxed from ourselves. + if (aTargetDocShell == this) { + return false; + } + + // Default the sandbox flags to our flags, so that if we can't retrieve the + // active document, we will still enforce our own. + uint32_t sandboxFlags = mSandboxFlags; + if (mContentViewer) { + nsCOMPtr<nsIDocument> doc = mContentViewer->GetDocument(); + if (doc) { + sandboxFlags = doc->GetSandboxFlags(); + } + } + + // If no flags, we are not sandboxed at all. + if (!sandboxFlags) { + return false; + } + + // If aTargetDocShell has an ancestor, it is not top level. + nsCOMPtr<nsIDocShellTreeItem> ancestorOfTarget; + aTargetDocShell->GetSameTypeParent(getter_AddRefs(ancestorOfTarget)); + if (ancestorOfTarget) { + do { + // We are not sandboxed if we are an ancestor of target. + if (ancestorOfTarget == this) { + return false; + } + nsCOMPtr<nsIDocShellTreeItem> tempTreeItem; + ancestorOfTarget->GetSameTypeParent(getter_AddRefs(tempTreeItem)); + tempTreeItem.swap(ancestorOfTarget); + } while (ancestorOfTarget); + + // Otherwise, we are sandboxed from aTargetDocShell. + return true; + } + + // aTargetDocShell is top level, are we the "one permitted sandboxed + // navigator", i.e. did we open aTargetDocShell? + nsCOMPtr<nsIDocShell> permittedNavigator; + aTargetDocShell->GetOnePermittedSandboxedNavigator( + getter_AddRefs(permittedNavigator)); + if (permittedNavigator == this) { + return false; + } + + // If SANDBOXED_TOPLEVEL_NAVIGATION flag is not on, we are not sandboxed + // from our top. + if (!(sandboxFlags & SANDBOXED_TOPLEVEL_NAVIGATION)) { + nsCOMPtr<nsIDocShellTreeItem> rootTreeItem; + GetSameTypeRootTreeItem(getter_AddRefs(rootTreeItem)); + if (SameCOMIdentity(aTargetDocShell, rootTreeItem)) { + return false; + } + } + + // Otherwise, we are sandboxed from aTargetDocShell. + return true; +} + +NS_IMETHODIMP +nsDocShell::GetTreeOwner(nsIDocShellTreeOwner** aTreeOwner) +{ + NS_ENSURE_ARG_POINTER(aTreeOwner); + + *aTreeOwner = mTreeOwner; + NS_IF_ADDREF(*aTreeOwner); + return NS_OK; +} + +#ifdef DEBUG_DOCSHELL_FOCUS +static void +PrintDocTree(nsIDocShellTreeItem* aParentNode, int aLevel) +{ + for (int32_t i = 0; i < aLevel; i++) { + printf(" "); + } + + int32_t childWebshellCount; + aParentNode->GetChildCount(&childWebshellCount); + nsCOMPtr<nsIDocShell> parentAsDocShell(do_QueryInterface(aParentNode)); + int32_t type = aParentNode->ItemType(); + nsCOMPtr<nsIPresShell> presShell = parentAsDocShell->GetPresShell(); + RefPtr<nsPresContext> presContext; + parentAsDocShell->GetPresContext(getter_AddRefs(presContext)); + nsIDocument* doc = presShell->GetDocument(); + + nsCOMPtr<nsPIDOMWindowOuter> domwin(doc->GetWindow()); + + nsCOMPtr<nsIWidget> widget; + nsViewManager* vm = presShell->GetViewManager(); + if (vm) { + vm->GetWidget(getter_AddRefs(widget)); + } + dom::Element* rootElement = doc->GetRootElement(); + + printf("DS %p Ty %s Doc %p DW %p EM %p CN %p\n", + (void*)parentAsDocShell.get(), + type == nsIDocShellTreeItem::typeChrome ? "Chr" : "Con", + (void*)doc, (void*)domwin.get(), + (void*)presContext->EventStateManager(), (void*)rootElement); + + if (childWebshellCount > 0) { + for (int32_t i = 0; i < childWebshellCount; i++) { + nsCOMPtr<nsIDocShellTreeItem> child; + aParentNode->GetChildAt(i, getter_AddRefs(child)); + PrintDocTree(child, aLevel + 1); + } + } +} + +static void +PrintDocTree(nsIDocShellTreeItem* aParentNode) +{ + NS_ASSERTION(aParentNode, "Pointer is null!"); + + nsCOMPtr<nsIDocShellTreeItem> parentItem; + aParentNode->GetParent(getter_AddRefs(parentItem)); + while (parentItem) { + nsCOMPtr<nsIDocShellTreeItem> tmp; + parentItem->GetParent(getter_AddRefs(tmp)); + if (!tmp) { + break; + } + parentItem = tmp; + } + + if (!parentItem) { + parentItem = aParentNode; + } + + PrintDocTree(parentItem, 0); +} +#endif + +NS_IMETHODIMP +nsDocShell::SetTreeOwner(nsIDocShellTreeOwner* aTreeOwner) +{ +#ifdef DEBUG_DOCSHELL_FOCUS + nsCOMPtr<nsIDocShellTreeItem> item(do_QueryInterface(aTreeOwner)); + if (item) { + PrintDocTree(item); + } +#endif + + // Don't automatically set the progress based on the tree owner for frames + if (!IsFrame()) { + 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 + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShellTreeItem> child = do_QueryObject(iter.GetNext()); + NS_ENSURE_TRUE(child, NS_ERROR_FAILURE); + + if (child->ItemType() == mItemType) { + child->SetTreeOwner(aTreeOwner); + } + } + + // Our tree owner has changed. Recompute scriptability. + // + // Note that this is near-redundant with the recomputation in + // SetDocLoaderParent(), but not so for the root DocShell, where the call to + // SetTreeOwner() happens after the initial AddDocLoaderAsChildOfRoot(), + // and we never set another parent. Given that this is neither expensive nor + // performance-critical, let's be safe and unconditionally recompute this + // state whenever dependent state changes. + RecomputeCanExecuteScripts(); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetChildOffset(uint32_t aChildOffset) +{ + mChildOffset = aChildOffset; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHistoryID(uint64_t* aID) +{ + *aID = mHistoryID; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsInUnload(bool* aIsInUnload) +{ + *aIsInUnload = mFiredUnloadEvent; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetChildCount(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"); + + nsCOMPtr<nsIDocShell> childDocShell = do_QueryInterface(aChild); + bool dynamic = false; + childDocShell->GetCreatedDynamically(&dynamic); + if (!dynamic) { + nsCOMPtr<nsISHEntry> currentSH; + bool oshe = false; + GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe); + if (currentSH) { + currentSH->HasDynamicallyAddedChild(&dynamic); + } + } + childDocShell->SetChildOffset(dynamic ? -1 : mChildList.Length() - 1); + + /* Set the child's global history if the parent has one */ + if (mUseGlobalHistory) { + childDocShell->SetUseGlobalHistory(true); + } + + 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; + } + nsIDocument* doc = mContentViewer->GetDocument(); + if (!doc) { + return NS_OK; + } + + bool isWyciwyg = false; + + if (mCurrentURI) { + // Check if the url is wyciwyg + mCurrentURI->SchemeIs("wyciwyg", &isWyciwyg); + } + + if (!isWyciwyg) { + // If this docshell is loaded from a wyciwyg: URI, don't + // advertise our charset since it does not in any way reflect + // the actual source charset, which is what we're trying to + // expose here. + + const nsACString& 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::GetChildAt(int32_t aIndex, nsIDocShellTreeItem** aChild) +{ + NS_ENSURE_ARG_POINTER(aChild); + +#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); + NS_ENSURE_TRUE(child, NS_ERROR_UNEXPECTED); + + return CallQueryInterface(child, aChild); +} + +NS_IMETHODIMP +nsDocShell::FindChildWithName(const nsAString& aName, + bool aRecurse, bool aSameType, + nsIDocShellTreeItem* aRequestor, + nsIDocShellTreeItem* aOriginalRequestor, + nsIDocShellTreeItem** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + + // if we don't find one, we return NS_OK and a null result + *aResult = nullptr; + + if (aName.IsEmpty()) { + return NS_OK; + } + + nsXPIDLString childName; + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShellTreeItem> child = do_QueryObject(iter.GetNext()); + NS_ENSURE_TRUE(child, NS_ERROR_FAILURE); + int32_t childType = child->ItemType(); + + if (aSameType && (childType != mItemType)) { + continue; + } + + bool childNameEquals = false; + child->NameEquals(aName, &childNameEquals); + if (childNameEquals && ItemIsActive(child) && + CanAccessItem(child, aOriginalRequestor)) { + child.swap(*aResult); + break; + } + + // Only ask it to check children if it is same type + if (childType != mItemType) { + continue; + } + + // Only ask the child if it isn't the requestor + if (aRecurse && (aRequestor != child)) { + // See if child contains the shell with the given name +#ifdef DEBUG + nsresult rv = +#endif + child->FindChildWithName(aName, true, aSameType, + static_cast<nsIDocShellTreeItem*>(this), + aOriginalRequestor, aResult); + NS_ASSERTION(NS_SUCCEEDED(rv), "FindChildWithName should not fail here"); + if (*aResult) { + // found it + return NS_OK; + } + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetChildSHEntry(int32_t aChildOffset, nsISHEntry** aResult) +{ + nsresult rv = NS_OK; + + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + // A nsISHEntry for a child is *only* available when the parent is in + // the progress of loading a document too... + + if (mLSHE) { + /* Before looking for the subframe's url, check + * the expiration status of the parent. If the parent + * has expired from cache, then subframes will not be + * loaded from history in certain situations. + */ + bool parentExpired = false; + mLSHE->GetExpirationStatus(&parentExpired); + + /* Get the parent's Load Type so that it can be set on the child too. + * By default give a loadHistory value + */ + uint32_t loadType = nsIDocShellLoadInfo::loadHistory; + mLSHE->GetLoadType(&loadType); + // If the user did a shift-reload on this frameset page, + // we don't want to load the subframes from history. + if (loadType == nsIDocShellLoadInfo::loadReloadBypassCache || + loadType == nsIDocShellLoadInfo::loadReloadBypassProxy || + loadType == nsIDocShellLoadInfo::loadReloadBypassProxyAndCache || + loadType == nsIDocShellLoadInfo::loadRefresh) { + return rv; + } + + /* If the user pressed reload and the parent frame has expired + * from cache, we do not want to load the child frame from history. + */ + if (parentExpired && (loadType == nsIDocShellLoadInfo::loadReloadNormal)) { + // The parent has expired. Return null. + *aResult = nullptr; + return rv; + } + + nsCOMPtr<nsISHContainer> container(do_QueryInterface(mLSHE)); + if (container) { + // Get the child subframe from session history. + rv = container->GetChildAt(aChildOffset, aResult); + if (*aResult) { + (*aResult)->SetLoadType(loadType); + } + } + } + return rv; +} + +NS_IMETHODIMP +nsDocShell::AddChildSHEntry(nsISHEntry* aCloneRef, nsISHEntry* aNewEntry, + int32_t aChildOffset, uint32_t aLoadType, + bool aCloneChildren) +{ + 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 + */ + nsCOMPtr<nsISHContainer> container(do_QueryInterface(mLSHE, &rv)); + if (container) { + if (NS_FAILED(container->ReplaceChild(aNewEntry))) { + rv = container->AddChild(aNewEntry, aChildOffset); + } + } + } else if (!aCloneRef) { + /* This is an initial load in some subframe. Just append it if we can */ + nsCOMPtr<nsISHContainer> container(do_QueryInterface(mOSHE, &rv)); + if (container) { + rv = container->AddChild(aNewEntry, aChildOffset); + } + } else { + rv = AddChildSHEntryInternal(aCloneRef, aNewEntry, aChildOffset, + aLoadType, aCloneChildren); + } + return rv; +} + +nsresult +nsDocShell::AddChildSHEntryInternal(nsISHEntry* aCloneRef, + nsISHEntry* aNewEntry, + int32_t aChildOffset, + uint32_t aLoadType, + bool aCloneChildren) +{ + nsresult rv = NS_OK; + if (mSessionHistory) { + /* You are currently in the rootDocShell. + * You will get here when a subframe has a new url + * to load and you have walked up the tree all the + * way to the top to clone the current SHEntry hierarchy + * and replace the subframe where a new url was loaded with + * a new entry. + */ + int32_t index = -1; + nsCOMPtr<nsISHEntry> currentHE; + mSessionHistory->GetIndex(&index); + if (index < 0) { + return NS_ERROR_FAILURE; + } + + rv = mSessionHistory->GetEntryAtIndex(index, false, + getter_AddRefs(currentHE)); + NS_ENSURE_TRUE(currentHE, NS_ERROR_FAILURE); + + nsCOMPtr<nsISHEntry> currentEntry(do_QueryInterface(currentHE)); + if (currentEntry) { + uint32_t cloneID = 0; + nsCOMPtr<nsISHEntry> nextEntry; + aCloneRef->GetID(&cloneID); + rv = CloneAndReplace(currentEntry, this, cloneID, aNewEntry, + aCloneChildren, getter_AddRefs(nextEntry)); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsISHistoryInternal> shPrivate = + do_QueryInterface(mSessionHistory); + NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE); + rv = shPrivate->AddEntry(nextEntry, true); + } + } + } else { + /* Just pass this along */ + nsCOMPtr<nsIDocShell> parent = + do_QueryInterface(GetAsSupports(mParent), &rv); + if (parent) { + rv = static_cast<nsDocShell*>(parent.get())->AddChildSHEntryInternal( + aCloneRef, aNewEntry, aChildOffset, aLoadType, aCloneChildren); + } + } + return rv; +} + +nsresult +nsDocShell::AddChildSHEntryToParent(nsISHEntry* aNewEntry, int32_t aChildOffset, + bool aCloneChildren) +{ + /* 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 + nsCOMPtr<nsISHistory> rootSH; + GetRootSessionHistory(getter_AddRefs(rootSH)); + if (rootSH) { + rootSH->GetIndex(&mPreviousTransIndex); + } + + nsresult rv; + nsCOMPtr<nsIDocShell> parent = do_QueryInterface(GetAsSupports(mParent), &rv); + if (parent) { + rv = parent->AddChildSHEntry(mOSHE, aNewEntry, aChildOffset, mLoadType, + aCloneChildren); + } + + if (rootSH) { + rootSH->GetIndex(&mLoadedTransIndex); +#ifdef DEBUG_PAGE_CACHE + printf("Previous index: %d, Loaded index: %d\n\n", mPreviousTransIndex, + mLoadedTransIndex); +#endif + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::SetUseGlobalHistory(bool aUseGlobalHistory) +{ + nsresult rv; + + mUseGlobalHistory = aUseGlobalHistory; + + if (!aUseGlobalHistory) { + mGlobalHistory = nullptr; + return NS_OK; + } + + // No need to initialize mGlobalHistory if IHistory is available. + nsCOMPtr<IHistory> history = services::GetHistoryService(); + if (history) { + return NS_OK; + } + + if (mGlobalHistory) { + return NS_OK; + } + + mGlobalHistory = do_GetService(NS_GLOBALHISTORY2_CONTRACTID, &rv); + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetUseGlobalHistory(bool* aUseGlobalHistory) +{ + *aUseGlobalHistory = mUseGlobalHistory; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RemoveFromSessionHistory() +{ + nsCOMPtr<nsISHistoryInternal> internalHistory; + nsCOMPtr<nsISHistory> sessionHistory; + nsCOMPtr<nsIDocShellTreeItem> root; + GetSameTypeRootTreeItem(getter_AddRefs(root)); + if (root) { + nsCOMPtr<nsIWebNavigation> rootAsWebnav = do_QueryInterface(root); + if (rootAsWebnav) { + rootAsWebnav->GetSessionHistory(getter_AddRefs(sessionHistory)); + internalHistory = do_QueryInterface(sessionHistory); + } + } + if (!internalHistory) { + return NS_OK; + } + + int32_t index = 0; + sessionHistory->GetIndex(&index); + AutoTArray<uint64_t, 16> ids({mHistoryID}); + internalHistory->RemoveEntries(ids, index); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCreatedDynamically(bool aDynamic) +{ + mDynamicallyCreated = aDynamic; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCreatedDynamically(bool* aDynamic) +{ + *aDynamic = mDynamicallyCreated; + return NS_OK; +} + +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; +} + +nsIScriptGlobalObject* +nsDocShell::GetScriptGlobalObject() +{ + NS_ENSURE_SUCCESS(EnsureScriptEnvironment(), nullptr); + return mScriptGlobal; +} + +nsIDocument* +nsDocShell::GetDocument() +{ + NS_ENSURE_SUCCESS(EnsureContentViewer(), nullptr); + return mContentViewer->GetDocument(); +} + +nsPIDOMWindowOuter* +nsDocShell::GetWindow() +{ + if (NS_FAILED(EnsureScriptEnvironment())) { + return nullptr; + } + return mScriptGlobal->AsOuter(); +} + +NS_IMETHODIMP +nsDocShell::SetDeviceSizeIsPageSize(bool aValue) +{ + if (mDeviceSizeIsPageSize != aValue) { + mDeviceSizeIsPageSize = aValue; + RefPtr<nsPresContext> presContext; + GetPresContext(getter_AddRefs(presContext)); + if (presContext) { + presContext->MediaFeatureValuesChanged(nsRestyleHint(0)); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDeviceSizeIsPageSize(bool* aValue) +{ + *aValue = mDeviceSizeIsPageSize; + return NS_OK; +} + +void +nsDocShell::ClearFrameHistory(nsISHEntry* aEntry) +{ + nsCOMPtr<nsISHContainer> shcontainer = do_QueryInterface(aEntry); + nsCOMPtr<nsISHistory> rootSH; + GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsISHistoryInternal> history = do_QueryInterface(rootSH); + if (!history || !shcontainer) { + return; + } + + int32_t count = 0; + shcontainer->GetChildCount(&count); + AutoTArray<uint64_t, 16> ids; + for (int32_t i = 0; i < count; ++i) { + nsCOMPtr<nsISHEntry> child; + shcontainer->GetChildAt(i, getter_AddRefs(child)); + if (child) { + uint64_t id = 0; + child->GetDocshellID(&id); + ids.AppendElement(id); + } + } + int32_t index = 0; + rootSH->GetIndex(&index); + history->RemoveEntries(ids, index); +} + +//------------------------------------- +//-- Helper Method for Print discovery +//------------------------------------- +bool +nsDocShell::IsPrintingOrPP(bool aDisplayErrorDialog) +{ + if (mIsPrintingOrPP && aDisplayErrorDialog) { + DisplayLoadError(NS_ERROR_DOCUMENT_IS_PRINTMODE, nullptr, nullptr, nullptr); + } + + return mIsPrintingOrPP; +} + +bool +nsDocShell::IsNavigationAllowed(bool aDisplayPrintErrorDialog, + bool aCheckIfUnloadFired) +{ + bool isAllowed = !IsPrintingOrPP(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) +{ + if (!IsNavigationAllowed(false)) { + *aCanGoBack = false; + return NS_OK; // JS may not handle returning of an error code + } + nsresult rv; + nsCOMPtr<nsISHistory> rootSH; + rv = GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH)); + NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); + rv = webnav->GetCanGoBack(aCanGoBack); + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetCanGoForward(bool* aCanGoForward) +{ + if (!IsNavigationAllowed(false)) { + *aCanGoForward = false; + return NS_OK; // JS may not handle returning of an error code + } + nsresult rv; + nsCOMPtr<nsISHistory> rootSH; + rv = GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH)); + NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); + rv = webnav->GetCanGoForward(aCanGoForward); + return rv; +} + +NS_IMETHODIMP +nsDocShell::GoBack() +{ + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + nsresult rv; + nsCOMPtr<nsISHistory> rootSH; + rv = GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH)); + NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); + rv = webnav->GoBack(); + return rv; +} + +NS_IMETHODIMP +nsDocShell::GoForward() +{ + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + nsresult rv; + nsCOMPtr<nsISHistory> rootSH; + rv = GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH)); + NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); + rv = webnav->GoForward(); + return rv; +} + +NS_IMETHODIMP +nsDocShell::GotoIndex(int32_t aIndex) +{ + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + nsresult rv; + nsCOMPtr<nsISHistory> rootSH; + rv = GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsIWebNavigation> webnav(do_QueryInterface(rootSH)); + NS_ENSURE_TRUE(webnav, NS_ERROR_FAILURE); + rv = webnav->GotoIndex(aIndex); + return rv; +} + +NS_IMETHODIMP +nsDocShell::LoadURI(const char16_t* aURI, + uint32_t aLoadFlags, + nsIURI* aReferringURI, + nsIInputStream* aPostStream, + nsIInputStream* aHeaderStream) +{ + return LoadURIWithOptions(aURI, aLoadFlags, aReferringURI, + mozilla::net::RP_Default, aPostStream, + aHeaderStream, nullptr); +} + +NS_IMETHODIMP +nsDocShell::LoadURIWithOptions(const char16_t* aURI, + uint32_t aLoadFlags, + nsIURI* aReferringURI, + uint32_t aReferrerPolicy, + nsIInputStream* aPostStream, + nsIInputStream* aHeaderStream, + nsIURI* aBaseURI) +{ + NS_ASSERTION((aLoadFlags & 0xf) == 0, "Unexpected flags"); + + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIInputStream> postStream(aPostStream); + nsresult rv = NS_OK; + + // Create a URI from our string; if that succeeds, we want to + // change aLoadFlags to not include the ALLOW_THIRD_PARTY_FIXUP + // flag. + + NS_ConvertUTF16toUTF8 uriString(aURI); + // Cleanup the empty spaces that might be on each end. + uriString.Trim(" "); + // Eliminate embedded newlines, which single-line text fields now allow: + uriString.StripChars("\r\n"); + NS_ENSURE_TRUE(!uriString.IsEmpty(), NS_ERROR_FAILURE); + + rv = NS_NewURI(getter_AddRefs(uri), uriString); + if (uri) { + aLoadFlags &= ~LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP; + } + + nsCOMPtr<nsIURIFixupInfo> fixupInfo; + if (sURIFixup) { + // Call the fixup object. This will clobber the rv from NS_NewURI + // above, but that's fine with us. Note that we need to do this even + // if NS_NewURI returned a URI, because fixup handles nested URIs, etc + // (things like view-source:mozilla.org for example). + uint32_t fixupFlags = 0; + if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) { + fixupFlags |= nsIURIFixup::FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP; + } + if (aLoadFlags & LOAD_FLAGS_FIXUP_SCHEME_TYPOS) { + fixupFlags |= nsIURIFixup::FIXUP_FLAG_FIX_SCHEME_TYPOS; + } + nsCOMPtr<nsIInputStream> fixupStream; + rv = sURIFixup->GetFixupURIInfo(uriString, fixupFlags, + getter_AddRefs(fixupStream), + getter_AddRefs(fixupInfo)); + + if (NS_SUCCEEDED(rv)) { + fixupInfo->GetPreferredURI(getter_AddRefs(uri)); + fixupInfo->SetConsumer(GetAsSupports(this)); + } + + if (fixupStream) { + // GetFixupURIInfo only returns a post data stream if it succeeded + // and changed the URI, in which case we should override the + // passed-in post data. + postStream = fixupStream; + } + + if (aLoadFlags & LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) { + nsCOMPtr<nsIObserverService> serv = services::GetObserverService(); + if (serv) { + serv->NotifyObservers(fixupInfo, "keyword-uri-fixup", aURI); + } + } + } + // else no fixup service so just use the URI we created and see + // what happens + + if (NS_ERROR_MALFORMED_URI == rv) { + if (DisplayLoadError(rv, uri, aURI, nullptr) && + (aLoadFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) { + return NS_ERROR_LOAD_SHOWED_ERRORPAGE; + } + } + + if (NS_FAILED(rv) || !uri) { + return NS_ERROR_FAILURE; + } + + PopupControlState popupState; + if (aLoadFlags & LOAD_FLAGS_ALLOW_POPUPS) { + popupState = openAllowed; + aLoadFlags &= ~LOAD_FLAGS_ALLOW_POPUPS; + } else { + popupState = openOverridden; + } + nsAutoPopupStatePusher statePusher(popupState); + + // Don't pass certain flags that aren't needed and end up confusing + // ConvertLoadTypeToDocShellLoadInfo. We do need to ensure that they are + // passed to LoadURI though, since it uses them. + uint32_t extraFlags = (aLoadFlags & EXTRA_LOAD_FLAGS); + aLoadFlags &= ~EXTRA_LOAD_FLAGS; + + nsCOMPtr<nsIDocShellLoadInfo> loadInfo; + rv = CreateLoadInfo(getter_AddRefs(loadInfo)); + if (NS_FAILED(rv)) { + return rv; + } + + /* + * If the user "Disables Protection on This Page", we have to make sure to + * remember the users decision when opening links in child tabs [Bug 906190] + */ + uint32_t loadType; + if (aLoadFlags & LOAD_FLAGS_ALLOW_MIXED_CONTENT) { + loadType = MAKE_LOAD_TYPE(LOAD_NORMAL_ALLOW_MIXED_CONTENT, aLoadFlags); + } else { + loadType = MAKE_LOAD_TYPE(LOAD_NORMAL, aLoadFlags); + } + + loadInfo->SetLoadType(ConvertLoadTypeToDocShellLoadInfo(loadType)); + loadInfo->SetPostDataStream(postStream); + loadInfo->SetReferrer(aReferringURI); + loadInfo->SetReferrerPolicy(aReferrerPolicy); + loadInfo->SetHeadersStream(aHeaderStream); + loadInfo->SetBaseURI(aBaseURI); + + if (fixupInfo) { + nsAutoString searchProvider, keyword; + fixupInfo->GetKeywordProviderName(searchProvider); + fixupInfo->GetKeywordAsSent(keyword); + MaybeNotifyKeywordSearchLoading(searchProvider, keyword); + } + + rv = LoadURI(uri, loadInfo, extraFlags, true); + + // Save URI string in case it's needed later when + // sending to search engine service in EndPageLoad() + mOriginalUriString = uriString; + + return rv; +} + +NS_IMETHODIMP +nsDocShell::DisplayLoadError(nsresult aError, nsIURI* aURI, + const char16_t* aURL, + nsIChannel* aFailedChannel, + bool* aDisplayedErrorPage) +{ + *aDisplayedErrorPage = false; + // Get prompt and string bundle servcies + 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); + + nsAutoString error; + const uint32_t kMaxFormatStrArgs = 3; + nsAutoString formatStrs[kMaxFormatStrArgs]; + uint32_t formatStrCount = 0; + bool addHostPort = 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[0]); + 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); + } + formatStrCount = 1; + error.AssignLiteral("unknownProtocolFound"); + } else if (NS_ERROR_FILE_NOT_FOUND == aError) { + NS_ENSURE_ARG_POINTER(aURI); + error.AssignLiteral("fileNotFound"); + } else if (NS_ERROR_FILE_ACCESS_DENIED == aError) { + NS_ENSURE_ARG_POINTER(aURI); + error.AssignLiteral("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[0]); + formatStrCount = 1; + error.AssignLiteral("dnsNotFound"); + } else if (NS_ERROR_CONNECTION_REFUSED == aError) { + NS_ENSURE_ARG_POINTER(aURI); + addHostPort = true; + error.AssignLiteral("connectionFailure"); + } else if (NS_ERROR_NET_INTERRUPT == aError) { + NS_ENSURE_ARG_POINTER(aURI); + addHostPort = true; + error.AssignLiteral("netInterrupt"); + } else if (NS_ERROR_NET_TIMEOUT == aError) { + NS_ENSURE_ARG_POINTER(aURI); + // Get the host + nsAutoCString host; + aURI->GetHost(host); + CopyUTF8toUTF16(host, formatStrs[0]); + formatStrCount = 1; + error.AssignLiteral("netTimeout"); + } else if (NS_ERROR_CSP_FRAME_ANCESTOR_VIOLATION == aError || + NS_ERROR_CSP_FORM_ACTION_VIOLATION == aError) { + // CSP error + cssClass.AssignLiteral("neterror"); + error.AssignLiteral("cspBlocked"); + } 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.AssignLiteral("sslv3Used"); + addHostPort = true; + } else if (securityState & nsIWebProgressListener::STATE_USES_WEAK_CRYPTO) { + error.AssignLiteral("weakCryptoUsed"); + addHostPort = true; + } else { + // Usually we should have aFailedChannel and get a detailed message + tsi->GetErrorMessage(getter_Copies(messageStr)); + } + } else { + // No channel, let's obtain the generic error message + if (nsserr) { + nsserr->GetErrorMessage(aError, messageStr); + } + } + if (!messageStr.IsEmpty()) { + if (errorClass == nsINSSErrorsService::ERROR_CLASS_BAD_CERT) { + error.AssignLiteral("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, HPKP draft spec section 2.6). + uint32_t flags = + UsePrivateBrowsing() ? nsISocketProvider::NO_PERMANENT_STORAGE : 0; + bool isStsHost = false; + bool isPinnedHost = false; + if (XRE_IsParentProcess()) { + nsCOMPtr<nsISiteSecurityService> sss = + do_GetService(NS_SSSERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI, + flags, nullptr, &isStsHost); + NS_ENSURE_SUCCESS(rv, rv); + rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HPKP, aURI, + flags, nullptr, &isPinnedHost); + NS_ENSURE_SUCCESS(rv, rv); + } else { + mozilla::dom::ContentChild* cc = + mozilla::dom::ContentChild::GetSingleton(); + mozilla::ipc::URIParams uri; + SerializeURI(aURI, uri); + cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HSTS, uri, flags, + &isStsHost); + cc->SendIsSecureURI(nsISiteSecurityService::HEADER_HPKP, uri, flags, + &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) { + cssClass.AssignLiteral("badStsCert"); + } + + uint32_t bucketId; + if (isStsHost) { + // measuring STS separately allows us to measure click through + // rates easily + bucketId = nsISecurityUITelemetry::WARNING_BAD_CERT_TOP_STS; + } else { + bucketId = nsISecurityUITelemetry::WARNING_BAD_CERT_TOP; + } + + // See if an alternate cert error page is registered + nsAdoptingCString alternateErrorPage = + Preferences::GetCString("security.alternate_certificate_error_page"); + if (alternateErrorPage) { + errorPage.Assign(alternateErrorPage); + } + + if (!IsFrame() && errorPage.EqualsIgnoreCase("certerror")) { + Telemetry::Accumulate(mozilla::Telemetry::SECURITY_UI, bucketId); + } + + } else { + error.AssignLiteral("nssFailure2"); + } + } + } else if (NS_ERROR_PHISHING_URI == aError || + NS_ERROR_MALWARE_URI == aError || + NS_ERROR_UNWANTED_URI == aError) { + nsAutoCString host; + aURI->GetHost(host); + CopyUTF8toUTF16(host, formatStrs[0]); + formatStrCount = 1; + + // 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 + nsAdoptingCString alternateErrorPage = + Preferences::GetCString("urlclassifier.alternate_error_page"); + if (alternateErrorPage) { + errorPage.Assign(alternateErrorPage); + } + + uint32_t bucketId; + bool sendTelemetry = false; + if (NS_ERROR_PHISHING_URI == aError) { + sendTelemetry = true; + error.AssignLiteral("deceptiveBlocked"); + bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_PHISHING_PAGE_FRAME + : nsISecurityUITelemetry::WARNING_PHISHING_PAGE_TOP; + } else if (NS_ERROR_MALWARE_URI == aError) { + sendTelemetry = true; + error.AssignLiteral("malwareBlocked"); + bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_MALWARE_PAGE_FRAME + : nsISecurityUITelemetry::WARNING_MALWARE_PAGE_TOP; + } else if (NS_ERROR_UNWANTED_URI == aError) { + sendTelemetry = true; + error.AssignLiteral("unwantedBlocked"); + bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_FRAME + : nsISecurityUITelemetry::WARNING_UNWANTED_PAGE_TOP; + } + + if (sendTelemetry && errorPage.EqualsIgnoreCase("blocked")) { + Telemetry::Accumulate(Telemetry::SECURITY_UI, bucketId); + } + + cssClass.AssignLiteral("blacklist"); + } else if (NS_ERROR_CONTENT_CRASHED == aError) { + errorPage.AssignLiteral("tabcrashed"); + error.AssignLiteral("tabcrashed"); + + nsCOMPtr<EventTarget> handler = mChromeEventHandler; + if (handler) { + nsCOMPtr<Element> element = do_QueryInterface(handler); + element->GetAttribute(NS_LITERAL_STRING("crashedPageTitle"), 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 { + // Errors requiring simple formatting + switch (aError) { + case NS_ERROR_MALFORMED_URI: + // URI is malformed + error.AssignLiteral("malformedURI"); + break; + case NS_ERROR_REDIRECT_LOOP: + // Doc failed to load because the server generated too many redirects + error.AssignLiteral("redirectLoop"); + break; + case NS_ERROR_UNKNOWN_SOCKET_TYPE: + // Doc failed to load because PSM is not installed + error.AssignLiteral("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.AssignLiteral("netReset"); + break; + case NS_ERROR_DOCUMENT_NOT_CACHED: + // Doc failed to load because the cache does not contain a copy of + // the document. + error.AssignLiteral("notCached"); + break; + case NS_ERROR_OFFLINE: + // Doc failed to load because we are offline. + error.AssignLiteral("netOffline"); + break; + case NS_ERROR_DOCUMENT_IS_PRINTMODE: + // Doc navigation attempted while Printing or Print Preview + error.AssignLiteral("isprinting"); + break; + case NS_ERROR_PORT_ACCESS_NOT_ALLOWED: + // Port blocked for security reasons + addHostPort = true; + error.AssignLiteral("deniedPortAccess"); + break; + case NS_ERROR_UNKNOWN_PROXY_HOST: + // Proxy hostname could not be resolved. + error.AssignLiteral("proxyResolveFailure"); + break; + case NS_ERROR_PROXY_CONNECTION_REFUSED: + // Proxy connection was refused. + error.AssignLiteral("proxyConnectFailure"); + break; + case NS_ERROR_INVALID_CONTENT_ENCODING: + // Bad Content Encoding. + error.AssignLiteral("contentEncodingError"); + break; + case NS_ERROR_REMOTE_XUL: + error.AssignLiteral("remoteXUL"); + break; + case NS_ERROR_UNSAFE_CONTENT_TYPE: + // Channel refused to load from an unrecognized content type. + error.AssignLiteral("unsafeContentType"); + break; + case NS_ERROR_CORRUPTED_CONTENT: + // Broken Content Detected. e.g. Content-MD5 check failure. + error.AssignLiteral("corruptedContentErrorv2"); + break; + case NS_ERROR_INTERCEPTION_FAILED: + // ServiceWorker intercepted request, but something went wrong. + error.AssignLiteral("corruptedContentErrorv2"); + break; + case NS_ERROR_NET_INADEQUATE_SECURITY: + // Server negotiated bad TLS for HTTP/2. + error.AssignLiteral("inadequateSecurityError"); + addHostPort = true; + break; + default: + break; + } + } + + // Test if the error should be displayed + if (error.IsEmpty()) { + return NS_OK; + } + + // 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[formatStrCount++]); + } + + nsAutoCString spec; + rv = NS_ERROR_NOT_AVAILABLE; + if (aURI) { + // displaying "file://" is aesthetically unpleasing and could even be + // confusing to the user + bool isFileURI = false; + rv = aURI->SchemeIs("file", &isFileURI); + if (NS_SUCCEEDED(rv) && isFileURI) { + aURI->GetPath(spec); + } else { + aURI->GetSpec(spec); + } + + nsAutoCString charset; + // unescape and convert from origin charset + aURI->GetOriginCharset(charset); + nsCOMPtr<nsITextToSubURI> textToSubURI( + do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = textToSubURI->UnEscapeURIForUI(charset, spec, + formatStrs[formatStrCount]); + } + } else { + spec.Assign('?'); + } + if (NS_FAILED(rv)) { + CopyUTF8toUTF16(spec, formatStrs[formatStrCount]); + } + rv = NS_OK; + ++formatStrCount; + + const char16_t* strs[kMaxFormatStrArgs]; + for (uint32_t i = 0; i < formatStrCount; i++) { + strs[i] = formatStrs[i].get(); + } + nsXPIDLString str; + rv = stringBundle->FormatStringFromName(error.get(), strs, formatStrCount, + getter_Copies(str)); + NS_ENSURE_SUCCESS(rv, rv); + messageStr.Assign(str.get()); + } + + // 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) { + bool isSecureURI = false; + rv = aURI->SchemeIs("https", &isSecureURI); + if (NS_SUCCEEDED(rv) && isSecureURI) { + // Maybe TLS intolerant. Treat this as an SSL error. + error.AssignLiteral("nssFailure2"); + } + } + + if (UseErrorPages()) { + // Display an error page + nsresult loadedPage = LoadErrorPage(aURI, aURL, errorPage.get(), + error.get(), 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" + +NS_IMETHODIMP +nsDocShell::LoadErrorPage(nsIURI* aURI, const char16_t* aURL, + const char* aErrorPage, + const char16_t* aErrorType, + const char16_t* aDescription, + const char* aCSSClass, + nsIChannel* aFailedChannel) +{ +#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->GetSpecOrDefault().get(), NS_ConvertUTF16toUTF8(aURL).get(), + chanName.get())); + } +#endif + mFailedChannel = aFailedChannel; + mFailedURI = aURI; + 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(); + } + + nsAutoCString url; + nsAutoCString charset; + if (aURI) { + nsresult rv = aURI->GetSpec(url); + NS_ENSURE_SUCCESS(rv, rv); + rv = aURI->GetOriginCharset(charset); + NS_ENSURE_SUCCESS(rv, rv); + } else if (aURL) { + CopyUTF16toUTF8(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, escapedCharset, escapedError, escapedDescription, + escapedCSSClass; + SAFE_ESCAPE(escapedUrl, url, url_Path); + SAFE_ESCAPE(escapedCharset, charset, url_Path); + SAFE_ESCAPE(escapedError, NS_ConvertUTF16toUTF8(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="); + errorPageUrl.AppendASCII(escapedCharset.get()); + + nsAutoCString frameType(FrameTypeToString(mFrameType)); + errorPageUrl.AppendLiteral("&f="); + errorPageUrl.AppendASCII(frameType.get()); + + // Append the manifest URL if the error comes from an app. + nsString manifestURL; + nsresult rv = GetAppManifestURL(manifestURL); + if (manifestURL.Length() > 0) { + nsCString manifestParam; + SAFE_ESCAPE(manifestParam, NS_ConvertUTF16toUTF8(manifestURL), url_Path); + errorPageUrl.AppendLiteral("&m="); + errorPageUrl.AppendASCII(manifestParam.get()); + } + + 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; + rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl); + NS_ENSURE_SUCCESS(rv, rv); + + return InternalLoad(errorPageURI, nullptr, false, nullptr, + mozilla::net::RP_Default, + nsContentUtils::GetSystemPrincipal(), nullptr, + INTERNAL_LOAD_FLAGS_NONE, EmptyString(), + nullptr, NullString(), nullptr, nullptr, LOAD_ERROR_PAGE, + nullptr, true, NullString(), this, nullptr, nullptr, + nullptr); +} + +NS_IMETHODIMP +nsDocShell::Reload(uint32_t aReloadFlags) +{ + if (!IsNavigationAllowed()) { + return NS_OK; // JS may not handle returning of an error code + } + nsresult rv; + NS_ASSERTION(((aReloadFlags & 0xf) == 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 + nsCOMPtr<nsISHistory> rootSH; + rv = GetRootSessionHistory(getter_AddRefs(rootSH)); + nsCOMPtr<nsISHistoryInternal> shistInt(do_QueryInterface(rootSH)); + bool canReload = true; + if (rootSH) { + shistInt->NotifyOnHistoryReload(mCurrentURI, aReloadFlags, &canReload); + } + + if (!canReload) { + return NS_OK; + } + + /* If you change this part of code, make sure bug 45297 does not re-occur */ + if (mOSHE) { + rv = LoadHistoryEntry(mOSHE, loadType); + } else if (mLSHE) { // In case a reload happened before the current load is done + rv = LoadHistoryEntry(mLSHE, loadType); + } else { + nsCOMPtr<nsIDocument> doc(GetDocument()); + + if (!doc) { + return NS_OK; + } + + // Do not inherit owner from document + uint32_t flags = INTERNAL_LOAD_FLAGS_NONE; + nsAutoString srcdoc; + nsCOMPtr<nsIURI> baseURI; + nsCOMPtr<nsIURI> originalURI; + bool loadReplace = false; + + nsIPrincipal* triggeringPrincipal = doc->NodePrincipal(); + nsAutoString contentTypeHint; + doc->GetContentType(contentTypeHint); + + if (doc->IsSrcdocDocument()) { + doc->GetSrcdocData(srcdoc); + flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC; + baseURI = doc->GetBaseURI(); + } + nsCOMPtr<nsIChannel> chan = doc->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)); + } + } + + MOZ_ASSERT(triggeringPrincipal, "Need a valid triggeringPrincipal"); + + // Stack variables to ensure changes to the member variables don't affect to + // the call. + nsCOMPtr<nsIURI> currentURI = mCurrentURI; + nsCOMPtr<nsIURI> referrerURI = mReferrerURI; + uint32_t referrerPolicy = mReferrerPolicy; + rv = InternalLoad(currentURI, + originalURI, + loadReplace, + referrerURI, + referrerPolicy, + triggeringPrincipal, + triggeringPrincipal, + flags, + EmptyString(), // No window target + NS_LossyConvertUTF16toASCII(contentTypeHint).get(), + NullString(), // No forced download + nullptr, // No post data + nullptr, // No headers data + loadType, // Load type + nullptr, // No SHEntry + true, + srcdoc, // srcdoc argument for iframe + this, // For reloads we are the source + baseURI, + nullptr, // No nsIDocShell + nullptr); // No nsIRequest + } + + return rv; +} + +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 + SetHistoryEntry(&mOSHE, mLSHE); + SetHistoryEntry(&mLSHE, nullptr); + } + + mFailedChannel = nullptr; + mFailedURI = nullptr; + } + + if (nsIWebNavigation::STOP_CONTENT & aStopFlags) { + // Stop the document loading + if (mContentViewer) { + nsCOMPtr<nsIContentViewer> cv = mContentViewer; + cv->Stop(); + } + } + + 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(); + } + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIWebNavigation> shellAsNav(do_QueryObject(iter.GetNext())); + if (shellAsNav) { + shellAsNav->Stop(aStopFlags); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDocument(nsIDOMDocument** aDocument) +{ + NS_ENSURE_ARG_POINTER(aDocument); + NS_ENSURE_SUCCESS(EnsureContentViewer(), NS_ERROR_FAILURE); + + return mContentViewer->GetDOMDocument(aDocument); +} + +NS_IMETHODIMP +nsDocShell::GetCurrentURI(nsIURI** aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + + if (mCurrentURI) { + return NS_EnsureSafeToReturn(mCurrentURI, aURI); + } + + *aURI = nullptr; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetReferringURI(nsIURI** aURI) +{ + NS_ENSURE_ARG_POINTER(aURI); + + *aURI = mReferrerURI; + NS_IF_ADDREF(*aURI); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetSessionHistory(nsISHistory* aSessionHistory) +{ + NS_ENSURE_TRUE(aSessionHistory, NS_ERROR_FAILURE); + // make sure that we are the root docshell and + // set a handle to root docshell in SH. + + nsCOMPtr<nsIDocShellTreeItem> root; + /* Get the root docshell. If *this* is the root docshell + * then save a handle to *this* in SH. SH needs it to do + * traversions thro' its entries + */ + GetSameTypeRootTreeItem(getter_AddRefs(root)); + NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); + if (root.get() == static_cast<nsIDocShellTreeItem*>(this)) { + mSessionHistory = aSessionHistory; + nsCOMPtr<nsISHistoryInternal> shPrivate = + do_QueryInterface(mSessionHistory); + NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE); + shPrivate->SetRootDocShell(this); + return NS_OK; + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::GetSessionHistory(nsISHistory** aSessionHistory) +{ + NS_ENSURE_ARG_POINTER(aSessionHistory); + *aSessionHistory = mSessionHistory; + NS_IF_ADDREF(*aSessionHistory); + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIWebPageDescriptor +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::LoadPage(nsISupports* aPageDescriptor, uint32_t aDisplayType) +{ + nsCOMPtr<nsISHEntry> shEntryIn(do_QueryInterface(aPageDescriptor)); + + // Currently, the opaque 'page descriptor' is an nsISHEntry... + if (!shEntryIn) { + return NS_ERROR_INVALID_POINTER; + } + + // Now clone shEntryIn, since we might end up modifying it later on, and we + // want a page descriptor to be reusable. + nsCOMPtr<nsISHEntry> shEntry; + nsresult rv = shEntryIn->Clone(getter_AddRefs(shEntry)); + NS_ENSURE_SUCCESS(rv, rv); + + // Give our cloned shEntry a new bfcache entry so this load is independent + // of all other loads. (This is important, in particular, for bugs 582795 + // and 585298.) + rv = shEntry->AbandonBFCacheEntry(); + NS_ENSURE_SUCCESS(rv, rv); + + // + // load the page as view-source + // + if (nsIWebPageDescriptor::DISPLAY_AS_SOURCE == aDisplayType) { + nsCOMPtr<nsIURI> oldUri, newUri; + nsCString spec, newSpec; + + // Create a new view-source URI and replace the original. + rv = shEntry->GetURI(getter_AddRefs(oldUri)); + if (NS_FAILED(rv)) { + return rv; + } + + oldUri->GetSpec(spec); + newSpec.AppendLiteral("view-source:"); + newSpec.Append(spec); + + rv = NS_NewURI(getter_AddRefs(newUri), newSpec); + if (NS_FAILED(rv)) { + return rv; + } + shEntry->SetURI(newUri); + shEntry->SetOriginalURI(nullptr); + } + + rv = LoadHistoryEntry(shEntry, LOAD_HISTORY); + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetCurrentDescriptor(nsISupports** aPageDescriptor) +{ + NS_PRECONDITION(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; +} + +//***************************************************************************** +// 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); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::Create() +{ + if (mCreated) { + // We've already been created + return NS_OK; + } + + NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, + "Unexpected item type in docshell"); + + NS_ENSURE_TRUE(Preferences::GetRootBranch(), NS_ERROR_FAILURE); + mCreated = true; + + if (gValidateOrigin == 0xffffffff) { + // Check pref to see if we should prevent frameset spoofing + gValidateOrigin = + Preferences::GetBool("browser.frame.validate_origin", true); + } + + // Should we use XUL error pages instead of alerts if possible? + mUseErrorPages = + Preferences::GetBool("browser.xul.error_pages.enabled", mUseErrorPages); + + if (!gAddedPreferencesVarCache) { + Preferences::AddBoolVarCache(&sUseErrorPages, + "browser.xul.error_pages.enabled", + mUseErrorPages); + gAddedPreferencesVarCache = true; + } + + mDisableMetaRefreshWhenInactive = + Preferences::GetBool("browser.meta_refresh_when_inactive.disabled", + mDisableMetaRefreshWhenInactive); + + mDeviceSizeIsPageSize = + Preferences::GetBool("docshell.device_size_is_page_size", + mDeviceSizeIsPageSize); + + nsCOMPtr<nsIObserverService> serv = services::GetObserverService(); + if (serv) { + const char* msg = mItemType == typeContent ? + NS_WEBNAVIGATION_CREATE : NS_CHROME_WEBNAVIGATION_CREATE; + serv->NotifyObservers(GetAsSupports(this), msg, nullptr); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::Destroy() +{ + NS_ASSERTION(mItemType == typeContent || mItemType == typeChrome, + "Unexpected item type in docshell"); + + if (!mIsBeingDestroyed) { + 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; + + // Make sure we don't record profile timeline markers anymore + SetRecordProfileTimelineMarkers(false); + + // Remove our pref observers + if (mObserveErrorPages) { + mObserveErrorPages = 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; + + mTransferableHookData = 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(); + mScriptGlobal = nullptr; + } + + if (mSessionHistory) { + // 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) + nsCOMPtr<nsISHistoryInternal> shPrivate = + do_QueryInterface(mSessionHistory); + if (shPrivate) { + shPrivate->EvictAllContentViewers(); + } + mSessionHistory = nullptr; + } + + SetTreeOwner(nullptr); + + mOnePermittedSandboxedNavigator = nullptr; + + // required to break ref cycle + mSecurityUI = nullptr; + + // Cancel any timers that were set for this docshell; this is needed + // to break the cycle between us and the timers. + CancelRefreshURITimers(); + + if (UsePrivateBrowsing()) { + mPrivateBrowsingId = 0; + mOriginAttributes.SyncAttributesWithPrivateBrowsing(false); + if (mAffectPrivateSessionLifetime) { + DecreasePrivateDocShellCount(); + } + } + + 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.x = aX; + mBounds.y = 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.x = aX; + mBounds.y = aY; + mBounds.width = aWidth; + mBounds.height = 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. + nsCOMPtr<nsIDocument> doc(do_GetInterface(GetAsSupports(mParent))); + if (doc) { + doc->FlushPendingNotifications(Flush_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) +{ + nsCOMPtr<nsIPresShell> presShell = GetPresShell(); + NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE); + + 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) +{ + 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 nsIXULWindow + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +nsDocShell::GetVisibility(bool* aVisibility) +{ + NS_ENSURE_ARG_POINTER(aVisibility); + + *aVisibility = false; + + if (!mContentViewer) { + return NS_OK; + } + + nsCOMPtr<nsIPresShell> 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->GetParentDocshell(); + while (parentItem) { + presShell = docShell->GetPresShell(); + + nsCOMPtr<nsIPresShell> pPresShell = parentItem->GetPresShell(); + + // Null-check for crash in bug 267804 + if (!pPresShell) { + NS_NOTREACHED("parent docshell has null pres shell"); + return NS_OK; + } + + vm = presShell->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; + bool isDocShellOffScreen = false; + docShell->GetIsOffScreenBrowser(&isDocShellOffScreen); + if (frame && + !frame->IsVisibleConsideringAncestors( + nsIFrame::VISIBILITY_CROSS_CHROME_CONTENT_BOUNDARY) && + !isDocShellOffScreen) { + return NS_OK; + } + + docShell = parentItem; + parentItem = docShell->GetParentDocshell(); + } + + 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); +} + +NS_IMETHODIMP +nsDocShell::SetIsOffScreenBrowser(bool aIsOffScreen) +{ + mIsOffScreenBrowser = aIsOffScreen; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsOffScreenBrowser(bool* aIsOffScreen) +{ + *aIsOffScreen = mIsOffScreenBrowser; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetIsActive(bool aIsActive) +{ + // We disallow setting active on chrome docshells. + if (mItemType == nsIDocShellTreeItem::typeChrome) { + return NS_ERROR_INVALID_ARG; + } + + // Keep track ourselves. + mIsActive = aIsActive; + + // Clear prerender flag if necessary. + mIsPrerendered &= !aIsActive; + + // Tell the PresShell about it. + nsCOMPtr<nsIPresShell> pshell = GetPresShell(); + if (pshell) { + pshell->SetIsActive(aIsActive); + } + + // Tell the window about it + if (mScriptGlobal) { + mScriptGlobal->SetIsBackground(!aIsActive); + if (nsCOMPtr<nsIDocument> doc = mScriptGlobal->GetExtantDoc()) { + // Update orientation when the top-level browsing context becomes active. + // We make an exception for apps because they currently rely on + // orientation locks persisting across browsing contexts. + if (aIsActive && !GetIsApp()) { + nsCOMPtr<nsIDocShellTreeItem> parent; + GetSameTypeParent(getter_AddRefs(parent)); + if (!parent) { + // We only care about the top-level browsing context. + uint16_t orientation = OrientationLock(); + ScreenOrientation::UpdateActiveOrientationLock(orientation); + } + } + + doc->PostVisibilityUpdateEvent(); + } + } + + // Tell the nsDOMNavigationTiming about it + RefPtr<nsDOMNavigationTiming> timing = mTiming; + if (!timing && mContentViewer) { + nsIDocument* doc = mContentViewer->GetDocument(); + if (doc) { + timing = doc->GetNavigationTiming(); + } + } + if (timing) { + timing->NotifyDocShellStateChanged( + aIsActive ? nsDOMNavigationTiming::DocShellState::eActive + : nsDOMNavigationTiming::DocShellState::eInactive); + } + + // Recursively tell all of our children, but don't tell <iframe mozbrowser> + // children; they handle their state separately. + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> docshell = do_QueryObject(iter.GetNext()); + if (!docshell) { + continue; + } + + if (!docshell->GetIsMozBrowserOrApp()) { + docshell->SetIsActive(aIsActive); + } + } + + // Restart or stop meta refresh timers if necessary + if (mDisableMetaRefreshWhenInactive) { + if (mIsActive) { + ResumeRefreshURIs(); + } else { + SuspendRefreshURIs(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsActive(bool* aIsActive) +{ + *aIsActive = mIsActive; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetIsPrerendered() +{ + MOZ_ASSERT(!mIsPrerendered, + "SetIsPrerendered() called on already prerendered docshell"); + SetIsActive(false); + mIsPrerendered = true; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsPrerendered(bool* aIsPrerendered) +{ + *aIsPrerendered = mIsPrerendered; + return NS_OK; +} + +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::SetSandboxFlags(uint32_t aSandboxFlags) +{ + mSandboxFlags = aSandboxFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetSandboxFlags(uint32_t* aSandboxFlags) +{ + *aSandboxFlags = mSandboxFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetOnePermittedSandboxedNavigator(nsIDocShell* aSandboxedNavigator) +{ + if (mOnePermittedSandboxedNavigator) { + NS_ERROR("One Permitted Sandboxed Navigator should only be set once."); + return NS_OK; + } + + mOnePermittedSandboxedNavigator = do_GetWeakReference(aSandboxedNavigator); + NS_ASSERTION(mOnePermittedSandboxedNavigator, + "One Permitted Sandboxed Navigator must support weak references."); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetOnePermittedSandboxedNavigator(nsIDocShell** aSandboxedNavigator) +{ + NS_ENSURE_ARG_POINTER(aSandboxedNavigator); + nsCOMPtr<nsIDocShell> permittedNavigator = + do_QueryReferent(mOnePermittedSandboxedNavigator); + permittedNavigator.forget(aSandboxedNavigator); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetDefaultLoadFlags(uint32_t aDefaultLoadFlags) +{ + mDefaultLoadFlags = aDefaultLoadFlags; + + // Tell the load group to set these flags all requests in the group + if (mLoadGroup) { + mLoadGroup->SetDefaultLoadFlags(aDefaultLoadFlags); + } else { + NS_WARNING("nsDocShell::SetDefaultLoadFlags has no loadGroup to propagate the flags to"); + } + + // Recursively tell all of our children. We *do not* skip + // <iframe mozbrowser> children - if someone sticks custom flags in this + // docShell then they too get the same flags. + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> docshell = do_QueryObject(iter.GetNext()); + if (!docshell) { + continue; + } + docshell->SetDefaultLoadFlags(aDefaultLoadFlags); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetDefaultLoadFlags(uint32_t* aDefaultLoadFlags) +{ + *aDefaultLoadFlags = mDefaultLoadFlags; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetMixedContentChannel(nsIChannel* aMixedContentChannel) +{ +#ifdef DEBUG + // if the channel is non-null + if (aMixedContentChannel) { + // Get the root docshell. + nsCOMPtr<nsIDocShellTreeItem> root; + GetSameTypeRootTreeItem(getter_AddRefs(root)); + NS_WARNING_ASSERTION(root.get() == static_cast<nsIDocShellTreeItem*>(this), + "Setting mMixedContentChannel on a docshell that is " + "not the root docshell"); + } +#endif + mMixedContentChannel = aMixedContentChannel; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetFailedChannel(nsIChannel** aFailedChannel) +{ + NS_ENSURE_ARG_POINTER(aFailedChannel); + nsIDocument* doc = GetDocument(); + if (!doc) { + *aFailedChannel = nullptr; + return NS_OK; + } + NS_IF_ADDREF(*aFailedChannel = doc->GetFailedChannel()); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetMixedContentChannel(nsIChannel** aMixedContentChannel) +{ + NS_ENSURE_ARG_POINTER(aMixedContentChannel); + NS_IF_ADDREF(*aMixedContentChannel = mMixedContentChannel); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAllowMixedContentAndConnectionData(bool* aRootHasSecureConnection, + bool* aAllowMixedContent, + bool* aIsRootDocShell) +{ + *aRootHasSecureConnection = true; + *aAllowMixedContent = false; + *aIsRootDocShell = false; + + nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot; + GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); + NS_ASSERTION(sameTypeRoot, + "No document shell root tree item from document shell tree item!"); + *aIsRootDocShell = + sameTypeRoot.get() == static_cast<nsIDocShellTreeItem*>(this); + + // now get the document from sameTypeRoot + nsCOMPtr<nsIDocument> rootDoc = sameTypeRoot->GetDocument(); + if (rootDoc) { + nsCOMPtr<nsIPrincipal> rootPrincipal = rootDoc->NodePrincipal(); + + // For things with system principal (e.g. scratchpad) there is no uri + // aRootHasSecureConnection should be false. + nsCOMPtr<nsIURI> rootUri; + if (nsContentUtils::IsSystemPrincipal(rootPrincipal) || + NS_FAILED(rootPrincipal->GetURI(getter_AddRefs(rootUri))) || !rootUri || + NS_FAILED(rootUri->SchemeIs("https", aRootHasSecureConnection))) { + *aRootHasSecureConnection = false; + } + + // Check the root doc's channel against the root docShell's + // mMixedContentChannel to see if they are the same. If they are the same, + // the user has overriden the block. + nsCOMPtr<nsIDocShell> rootDocShell = do_QueryInterface(sameTypeRoot); + nsCOMPtr<nsIChannel> mixedChannel; + rootDocShell->GetMixedContentChannel(getter_AddRefs(mixedChannel)); + *aAllowMixedContent = + mixedChannel && (mixedChannel == rootDoc->GetChannel()); + } + + 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::SetFocus() +{ + return NS_OK; +} + +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(char16_t** aTitle) +{ + NS_ENSURE_ARG_POINTER(aTitle); + + *aTitle = ToNewUnicode(mTitle); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetTitle(const char16_t* aTitle) +{ + // Store local title + mTitle = aTitle; + + nsCOMPtr<nsIDocShellTreeItem> parent; + GetSameTypeParent(getter_AddRefs(parent)); + + // When title is set on the top object it should then be passed to the + // tree owner. + if (!parent) { + nsCOMPtr<nsIBaseWindow> treeOwnerAsWin(do_QueryInterface(mTreeOwner)); + if (treeOwnerAsWin) { + treeOwnerAsWin->SetTitle(aTitle); + } + } + + AssertOriginAttributesMatchPrivateBrowsing(); + if (mCurrentURI && mLoadType != LOAD_ERROR_PAGE && mUseGlobalHistory && + !UsePrivateBrowsing()) { + nsCOMPtr<IHistory> history = services::GetHistoryService(); + if (history) { + history->SetURITitle(mCurrentURI, mTitle); + } else if (mGlobalHistory) { + mGlobalHistory->SetPageTitle(mCurrentURI, nsString(mTitle)); + } + } + + // Update SessionHistory with the document's title. + if (mOSHE && mLoadType != LOAD_BYPASS_HISTORY && + mLoadType != LOAD_ERROR_PAGE) { + mOSHE->SetTitle(mTitle); + } + + return NS_OK; +} + +nsresult +nsDocShell::GetCurScrollPos(int32_t aScrollOrientation, int32_t* aCurPos) +{ + NS_ENSURE_ARG_POINTER(aCurPos); + + nsIScrollableFrame* sf = GetRootScrollFrame(); + if (!sf) { + return NS_ERROR_FAILURE; + } + + nsPoint pt = sf->GetScrollPosition(); + + switch (aScrollOrientation) { + case ScrollOrientation_X: + *aCurPos = pt.x; + return NS_OK; + + case ScrollOrientation_Y: + *aCurPos = pt.y; + return NS_OK; + + default: + NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG); + } +} + +nsresult +nsDocShell::SetCurScrollPosEx(int32_t aCurHorizontalPos, + int32_t aCurVerticalPos) +{ + nsIScrollableFrame* sf = GetRootScrollFrame(); + NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); + + sf->ScrollTo(nsPoint(aCurHorizontalPos, aCurVerticalPos), + nsIScrollableFrame::INSTANT); + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIScrollable +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetDefaultScrollbarPreferences(int32_t aScrollOrientation, + int32_t* aScrollbarPref) +{ + NS_ENSURE_ARG_POINTER(aScrollbarPref); + switch (aScrollOrientation) { + case ScrollOrientation_X: + *aScrollbarPref = mDefaultScrollbarPref.x; + return NS_OK; + + case ScrollOrientation_Y: + *aScrollbarPref = mDefaultScrollbarPref.y; + return NS_OK; + + default: + NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::SetDefaultScrollbarPreferences(int32_t aScrollOrientation, + int32_t aScrollbarPref) +{ + switch (aScrollOrientation) { + case ScrollOrientation_X: + mDefaultScrollbarPref.x = aScrollbarPref; + return NS_OK; + + case ScrollOrientation_Y: + mDefaultScrollbarPref.y = aScrollbarPref; + return NS_OK; + + default: + NS_ENSURE_TRUE(false, NS_ERROR_INVALID_ARG); + } + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::GetScrollbarVisibility(bool* aVerticalVisible, + bool* aHorizontalVisible) +{ + nsIScrollableFrame* sf = GetRootScrollFrame(); + NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); + + uint32_t scrollbarVisibility = sf->GetScrollbarVisibility(); + if (aVerticalVisible) { + *aVerticalVisible = + (scrollbarVisibility & nsIScrollableFrame::VERTICAL) != 0; + } + if (aHorizontalVisible) { + *aHorizontalVisible = + (scrollbarVisibility & nsIScrollableFrame::HORIZONTAL) != 0; + } + + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsITextScroll +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::ScrollByLines(int32_t aNumLines) +{ + nsIScrollableFrame* sf = GetRootScrollFrame(); + NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); + + sf->ScrollBy(nsIntPoint(0, aNumLines), nsIScrollableFrame::LINES, + nsIScrollableFrame::SMOOTH); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::ScrollByPages(int32_t aNumPages) +{ + nsIScrollableFrame* sf = GetRootScrollFrame(); + NS_ENSURE_TRUE(sf, NS_ERROR_FAILURE); + + sf->ScrollBy(nsIntPoint(0, aNumPages), nsIScrollableFrame::PAGES, + nsIScrollableFrame::SMOOTH); + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIRefreshURI +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::RefreshURI(nsIURI* aURI, + int32_t aDelay, bool aRepeat, + bool aMetaRefresh, + nsIPrincipal* aPrincipal) +{ + 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; + } + + nsRefreshTimer* refreshTimer = new nsRefreshTimer(); + uint32_t busyFlags = 0; + GetBusyFlags(&busyFlags); + + nsCOMPtr<nsISupports> dataRef = refreshTimer; // Get the ref count to 1 + + refreshTimer->mDocShell = this; + refreshTimer->mPrincipal = aPrincipal; + refreshTimer->mURI = aURI; + refreshTimer->mDelay = aDelay; + refreshTimer->mRepeat = aRepeat; + refreshTimer->mMetaRefresh = aMetaRefresh; + + if (!mRefreshURIList) { + mRefreshURIList = nsArray::Create(); + } + + if (busyFlags & BUSY_FLAGS_BUSY || (!mIsActive && 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, /*weak =*/ false); + } else { + // There is no page loading going on right now. Create the + // timer and fire it right away. + nsCOMPtr<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1"); + NS_ENSURE_TRUE(timer, NS_ERROR_FAILURE); + + mRefreshURIList->AppendElement(timer, /*weak =*/ false); // owning timer ref + timer->InitWithCallback(refreshTimer, aDelay, nsITimer::TYPE_ONE_SHOT); + } + return NS_OK; +} + +nsresult +nsDocShell::ForceRefreshURIFromTimer(nsIURI* aURI, + int32_t aDelay, + bool aMetaRefresh, + nsITimer* aTimer, + nsIPrincipal* aPrincipal) +{ + NS_PRECONDITION(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, aDelay, aMetaRefresh, aPrincipal); +} + +NS_IMETHODIMP +nsDocShell::ForceRefreshURI(nsIURI* aURI, int32_t aDelay, bool aMetaRefresh, nsIPrincipal* aPrincipal) +{ + NS_ENSURE_ARG(aURI); + + nsCOMPtr<nsIDocShellLoadInfo> loadInfo; + CreateLoadInfo(getter_AddRefs(loadInfo)); + NS_ENSURE_TRUE(loadInfo, NS_ERROR_OUT_OF_MEMORY); + + /* We do need to pass in a referrer, but we don't want it to + * be sent to the server. + */ + loadInfo->SetSendReferrer(false); + + /* for most refreshes the current URI is an appropriate + * internal referrer + */ + loadInfo->SetReferrer(mCurrentURI); + + /* Don't ever "guess" on which principal to use to avoid picking + * the current principal. + */ + loadInfo->SetPrincipalIsExplicit(true); + + /* Check if this META refresh causes a redirection + * to another site. + */ + bool equalUri = false; + nsresult rv = aURI->Equals(mCurrentURI, &equalUri); + if (NS_SUCCEEDED(rv) && (!equalUri) && aMetaRefresh && + 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(). + */ + loadInfo->SetLoadType(nsIDocShellLoadInfo::loadNormalReplace); + + /* for redirects we mimic HTTP, which passes the + * original referrer + */ + nsCOMPtr<nsIURI> internalReferrer; + GetReferringURI(getter_AddRefs(internalReferrer)); + if (internalReferrer) { + loadInfo->SetReferrer(internalReferrer); + } + } else { + loadInfo->SetLoadType(nsIDocShellLoadInfo::loadRefresh); + } + + // If the principal is null, the refresh will have a triggeringPrincipal + // derived from the referrer URI, or will be set to the system principal + // if there is no refererrer. See LoadURI() + if (aPrincipal) { + loadInfo->SetTriggeringPrincipal(aPrincipal); + } + + /* + * LoadURI(...) will cancel all refresh timers... This causes the + * Timer and its refreshData instance to be released... + */ + LoadURI(aURI, loadInfo, nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL, true); + + return NS_OK; +} + +nsresult +nsDocShell::SetupRefreshURIFromHeader(nsIURI* aBaseURI, + nsIPrincipal* aPrincipal, + const nsACString& aHeader) +{ + // Refresh headers are parsed with the following format in mind + // <META HTTP-EQUIV=REFRESH CONTENT="5; URL=http://uri"> + // By the time we are here, the following is true: + // header = "REFRESH" + // content = "5; URL=http://uri" // note the URL attribute is + // optional, if it is absent, the currently loaded url is used. + // Also note that the seconds and URL separator can be either + // a ';' or a ','. The ',' separator should be illegal but CNN + // is using it. + // + // We need to handle the following strings, where + // - X is a set of digits + // - URI is either a relative or absolute URI + // + // Note that URI should start with "url=" but we allow omission + // + // "" || ";" || "," + // empty string. use the currently loaded URI + // and refresh immediately. + // "X" || "X;" || "X," + // Refresh the currently loaded URI in X seconds. + // "X; URI" || "X, URI" + // Refresh using URI as the destination in X seconds. + // "URI" || "; URI" || ", URI" + // Refresh immediately using URI as the destination. + // + // Currently, anything immediately following the URI, if + // separated by any char in the set "'\"\t\r\n " will be + // ignored. So "10; url=go.html ; foo=bar" will work, + // and so will "10; url='go.html'; foo=bar". However, + // "10; url=go.html; foo=bar" will result in the uri + // "go.html;" since ';' and ',' are valid uri characters. + // + // Note that we need to remove any tokens wrapping the URI. + // These tokens currently include spaces, double and single + // quotes. + + // when done, seconds is 0 or the given number of seconds + // uriAttrib is empty or the URI specified + MOZ_ASSERT(aPrincipal); + + nsAutoCString uriAttrib; + int32_t seconds = 0; + bool specifiesSeconds = false; + + nsACString::const_iterator iter, tokenStart, doneIterating; + + aHeader.BeginReading(iter); + aHeader.EndReading(doneIterating); + + // skip leading whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + tokenStart = iter; + + // skip leading + and - + if (iter != doneIterating && (*iter == '-' || *iter == '+')) { + ++iter; + } + + // parse number + while (iter != doneIterating && (*iter >= '0' && *iter <= '9')) { + seconds = seconds * 10 + (*iter - '0'); + specifiesSeconds = true; + ++iter; + } + + if (iter != doneIterating) { + // if we started with a '-', number is negative + if (*tokenStart == '-') { + seconds = -seconds; + } + + // skip to next ';' or ',' + nsACString::const_iterator iterAfterDigit = iter; + while (iter != doneIterating && !(*iter == ';' || *iter == ',')) { + if (specifiesSeconds) { + // Non-whitespace characters here mean that the string is + // malformed but tolerate sites that specify a decimal point, + // even though meta refresh only works on whole seconds. + if (iter == iterAfterDigit && + !nsCRT::IsAsciiSpace(*iter) && *iter != '.') { + // The characters between the seconds and the next + // section are just garbage! + // e.g. content="2a0z+,URL=http://www.mozilla.org/" + // Just ignore this redirect. + return NS_ERROR_FAILURE; + } else if (nsCRT::IsAsciiSpace(*iter)) { + // We've had at least one whitespace so tolerate the mistake + // and drop through. + // e.g. content="10 foo" + ++iter; + break; + } + } + ++iter; + } + + // skip any remaining whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + // skip ';' or ',' + if (iter != doneIterating && (*iter == ';' || *iter == ',')) { + ++iter; + } + + // skip whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + } + + // possible start of URI + tokenStart = iter; + + // skip "url = " to real start of URI + if (iter != doneIterating && (*iter == 'u' || *iter == 'U')) { + ++iter; + if (iter != doneIterating && (*iter == 'r' || *iter == 'R')) { + ++iter; + if (iter != doneIterating && (*iter == 'l' || *iter == 'L')) { + ++iter; + + // skip whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + if (iter != doneIterating && *iter == '=') { + ++iter; + + // skip whitespace + while (iter != doneIterating && nsCRT::IsAsciiSpace(*iter)) { + ++iter; + } + + // found real start of URI + tokenStart = iter; + } + } + } + } + + // skip a leading '"' or '\''. + + bool isQuotedURI = false; + if (tokenStart != doneIterating && + (*tokenStart == '"' || *tokenStart == '\'')) { + isQuotedURI = true; + ++tokenStart; + } + + // set iter to start of URI + iter = tokenStart; + + // tokenStart here points to the beginning of URI + + // grab the rest of the URI + while (iter != doneIterating) { + if (isQuotedURI && (*iter == '"' || *iter == '\'')) { + break; + } + ++iter; + } + + // move iter one back if the last character is a '"' or '\'' + if (iter != tokenStart && isQuotedURI) { + --iter; + if (!(*iter == '"' || *iter == '\'')) { + ++iter; + } + } + + // URI is whatever's contained from tokenStart to iter. + // note: if tokenStart == doneIterating, so is iter. + + nsresult rv = NS_OK; + + nsCOMPtr<nsIURI> uri; + bool specifiesURI = false; + if (tokenStart == iter) { + uri = aBaseURI; + } else { + uriAttrib = Substring(tokenStart, iter); + // NS_NewURI takes care of any whitespace surrounding the URL + rv = NS_NewURI(getter_AddRefs(uri), uriAttrib, nullptr, aBaseURI); + specifiesURI = true; + } + + // No URI or seconds were specified + if (!specifiesSeconds && !specifiesURI) { + // Do nothing because the alternative is to spin around in a refresh + // loop forever! + return NS_ERROR_FAILURE; + } + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIScriptSecurityManager> securityManager( + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv)); + if (NS_SUCCEEDED(rv)) { + rv = securityManager->CheckLoadURIWithPrincipal( + aPrincipal, uri, + nsIScriptSecurityManager::LOAD_IS_AUTOMATIC_DOCUMENT_REPLACEMENT); + + if (NS_SUCCEEDED(rv)) { + bool isjs = true; + rv = NS_URIChainHasFlags( + uri, nsIProtocolHandler::URI_OPENING_EXECUTES_SCRIPT, &isjs); + NS_ENSURE_SUCCESS(rv, rv); + + if (isjs) { + return NS_ERROR_FAILURE; + } + } + + if (NS_SUCCEEDED(rv)) { + // Since we can't travel back in time yet, just pretend + // negative numbers do nothing at all. + if (seconds < 0) { + return NS_ERROR_FAILURE; + } + + rv = RefreshURI(uri, seconds * 1000, false, true, aPrincipal); + } + } + } + return rv; +} + +NS_IMETHODIMP +nsDocShell::SetupRefreshURI(nsIChannel* aChannel) +{ + nsresult rv; + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel, &rv)); + if (NS_SUCCEEDED(rv)) { + nsAutoCString refreshHeader; + rv = httpChannel->GetResponseHeader(NS_LITERAL_CSTRING("refresh"), + refreshHeader); + + if (!refreshHeader.IsEmpty()) { + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPrincipal> principal; + rv = secMan->GetChannelResultPrincipal(aChannel, + getter_AddRefs(principal)); + NS_ENSURE_SUCCESS(rv, rv); + + SetupReferrerFromChannel(aChannel); + rv = SetupRefreshURIFromHeader(mCurrentURI, principal, refreshHeader); + if (NS_SUCCEEDED(rv)) { + return NS_REFRESHURI_HEADER_FOUND; + } + } + } + return rv; +} + +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); + mRefreshURIList = nullptr; + mSavedRefreshURIList = 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; +} + +NS_IMETHODIMP +nsDocShell::SuspendRefreshURIs() +{ + 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(); + + nsCOMPtr<nsITimerCallback> rt = do_QueryInterface(callback); + NS_ASSERTION(rt, + "RefreshURIList timer callbacks should only be RefreshTimer objects"); + + mRefreshURIList->ReplaceElementAt(rt, i, /*weak =*/ false); + } + } + + // Suspend refresh URIs for our child shells as well. + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext()); + if (shell) { + shell->SuspendRefreshURIs(); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::ResumeRefreshURIs() +{ + RefreshURIFromQueue(); + + // Resume refresh URIs for our child shells as well. + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> shell = do_QueryObject(iter.GetNext()); + 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<nsITimer> timer = do_CreateInstance("@mozilla.org/timer;1"); + 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, /*weak =*/ false); + timer->InitWithCallback(refreshInfo, delay, nsITimer::TYPE_ONE_SHOT); + } + } + } + + return NS_OK; +} + +//***************************************************************************** +// nsDocShell::nsIContentViewerContainer +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::Embed(nsIContentViewer* aContentViewer, + const char* aCommand, nsISupports* aExtraInfo) +{ + // Save the LayoutHistoryState of the previous document, before + // setting up new document + PersistLayoutHistoryState(); + + nsresult rv = SetupNewViewer(aContentViewer); + NS_ENSURE_SUCCESS(rv, rv); + + // If we are loading a wyciwyg url from history, change the base URI for + // the document to the original http url that created the document.write(). + // This makes sure that all relative urls in a document.written page loaded + // via history work properly. + if (mCurrentURI && + (mLoadType & LOAD_CMD_HISTORY || + mLoadType == LOAD_RELOAD_NORMAL || + mLoadType == LOAD_RELOAD_CHARSET_CHANGE)) { + bool isWyciwyg = false; + // Check if the url is wyciwyg + rv = mCurrentURI->SchemeIs("wyciwyg", &isWyciwyg); + if (isWyciwyg && NS_SUCCEEDED(rv)) { + SetBaseUrlForWyciwyg(aContentViewer); + } + } + // XXX What if SetupNewViewer fails? + if (mLSHE) { + // Restore the editing state, if it's stored in session history. + if (mLSHE->HasDetachedEditor()) { + ReattachEditorToWindow(mLSHE); + } + // Set history.state + SetDocCurrentStateObj(mLSHE); + + SetHistoryEntry(&mOSHE, mLSHE); + } + + bool updateHistory = true; + + // Determine if this type of load should update history + switch (mLoadType) { + case LOAD_NORMAL_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; +} + +NS_IMETHODIMP +nsDocShell::SetIsPrinting(bool aIsPrinting) +{ + mIsPrintingOrPP = aIsPrinting; + 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); + + nsCOMPtr<nsIWyciwygChannel> wcwgChannel(do_QueryInterface(aRequest)); + nsCOMPtr<nsIWebProgress> webProgress = + do_QueryInterface(GetAsSupports(this)); + + // We don't update navigation timing for wyciwyg channels + if (this == aProgress && !wcwgChannel) { + MaybeInitTiming(); + mTiming->NotifyFetchStart(uri, + ConvertLoadTypeToNavigationType(mLoadType)); + } + + // Was the wyciwyg document loaded on this docshell? + if (wcwgChannel && !mLSHE && (mItemType == typeContent) && + aProgress == webProgress.get()) { + bool equalUri = true; + // Store the wyciwyg url in session history, only if it is + // being loaded fresh for the first time. We don't want + // multiple entries for successive loads + if (mCurrentURI && + NS_SUCCEEDED(uri->Equals(mCurrentURI, &equalUri)) && + !equalUri) { + nsCOMPtr<nsIDocShellTreeItem> parentAsItem; + GetSameTypeParent(getter_AddRefs(parentAsItem)); + nsCOMPtr<nsIDocShell> parentDS(do_QueryInterface(parentAsItem)); + bool inOnLoadHandler = false; + if (parentDS) { + parentDS->GetIsExecutingOnLoadHandler(&inOnLoadHandler); + } + if (inOnLoadHandler) { + // We're handling parent's load event listener, which causes + // document.write in a subdocument. + // Need to clear the session history for all child + // docshells so that we can handle them like they would + // all be added dynamically. + nsCOMPtr<nsIDocShell> parent = do_QueryInterface(parentAsItem); + if (parent) { + bool oshe = false; + nsCOMPtr<nsISHEntry> entry; + parent->GetCurrentSHEntry(getter_AddRefs(entry), &oshe); + static_cast<nsDocShell*>(parent.get())->ClearFrameHistory(entry); + } + } + + // This is a document.write(). Get the made-up url + // from the channel and store it in session history. + // Pass false for aCloneChildren, since we're creating + // a new DOM here. + AddToSessionHistory(uri, wcwgChannel, nullptr, nullptr, false, + getter_AddRefs(mLSHE)); + SetCurrentURI(uri, aRequest, true, 0); + // Save history state of the previous page + PersistLayoutHistoryState(); + // We'll never get an Embed() for this load, so just go ahead + // and SetHistoryEntry now. + SetHistoryEntry(&mOSHE, mLSHE); + } + } + // Page has begun to load + mBusyFlags = BUSY_FLAGS_BUSY | BUSY_FLAGS_BEFORE_PAGE_LOAD; + + if ((aStateFlags & STATE_RESTORING) == 0) { + // Show the progress cursor if the pref is set + if (nsContentUtils::UseActivityCursor()) { + nsCOMPtr<nsIWidget> mainWidget; + GetMainWidget(getter_AddRefs(mainWidget)); + if (mainWidget) { + mainWidget->SetCursor(eCursor_spinning); + } + } + } + } else if ((~aStateFlags & (STATE_TRANSFERRING | STATE_IS_DOCUMENT)) == 0) { + // Page is loading + mBusyFlags = 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 (nsContentUtils::UseActivityCursor()) { + nsCOMPtr<nsIWidget> mainWidget; + GetMainWidget(getter_AddRefs(mainWidget)); + if (mainWidget) { + mainWidget->SetCursor(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) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + 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 mixed content is allowed for the old channel, we forward + // the permission to the new channel if it has the same origin + // as the old one. + if (mMixedContentChannel && mMixedContentChannel == aOldChannel) { + nsresult rv = nsContentUtils::CheckSameOrigin(mMixedContentChannel, aNewChannel); + if (NS_SUCCEEDED(rv)) { + SetMixedContentChannel(aNewChannel); // Same origin: forward permission. + } else { + SetMixedContentChannel(nullptr); // Different origin: clear mMixedContentChannel. + } + } + + 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; + } + + // 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 || + 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 { + nsCOMPtr<nsIURI> referrer; + // Treat referrer as null if there is an error getting it. + (void)NS_GetReferrerFromChannel(aOldChannel, getter_AddRefs(referrer)); + + // Get the HTTP response code, if available. + uint32_t responseStatus = 0; + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aOldChannel); + if (httpChannel) { + (void)httpChannel->GetResponseStatus(&responseStatus); + } + + // Add visit N -1 => N + AddURIVisit(oldURI, referrer, 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); + } + + // check if the new load should go through the application cache. + nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel = + do_QueryInterface(aNewChannel); + if (appCacheChannel) { + if (GeckoProcessType_Default != XRE_GetProcessType()) { + // Permission will be checked in the parent process. + appCacheChannel->SetChooseApplicationCache(true); + } else { + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + + if (secMan) { + nsCOMPtr<nsIPrincipal> principal; + secMan->GetDocShellCodebasePrincipal(newURI, this, + getter_AddRefs(principal)); + appCacheChannel->SetChooseApplicationCache( + NS_ShouldCheckAppCache(principal, UsePrivateBrowsing())); + } + } + } + + if (!(aRedirectFlags & nsIChannelEventSink::REDIRECT_INTERNAL) && + mLoadType & (LOAD_CMD_RELOAD | LOAD_CMD_HISTORY)) { + mLoadType = LOAD_NORMAL_REPLACE; + SetHistoryEntry(&mLSHE, nullptr); + } +} + +NS_IMETHODIMP +nsDocShell::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, const char16_t* aMessage) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, uint32_t aState) +{ + NS_NOTREACHED("notification excluded in AddProgressListener(...)"); + return NS_OK; +} + +nsresult +nsDocShell::EndPageLoad(nsIWebProgress* aProgress, + nsIChannel* aChannel, nsresult aStatus) +{ + if (!aChannel) { + return NS_ERROR_NULL_POINTER; + } + + nsCOMPtr<nsIConsoleReportCollector> reporter = do_QueryInterface(aChannel); + if (reporter) { + reporter->FlushConsoleReports(GetDocument()); + } + + nsCOMPtr<nsIURI> url; + nsresult rv = aChannel->GetURI(getter_AddRefs(url)); + if (NS_FAILED(rv)) { + return rv; + } + + nsCOMPtr<nsITimedChannel> timingChannel = do_QueryInterface(aChannel); + if (timingChannel) { + TimeStamp channelCreationTime; + rv = timingChannel->GetChannelCreation(&channelCreationTime); + if (NS_SUCCEEDED(rv) && !channelCreationTime.IsNull()) { + Telemetry::AccumulateTimeDelta(Telemetry::TOTAL_CONTENT_PAGE_LOAD_TIME, + channelCreationTime); + nsCOMPtr<nsPILoadGroupInternal> internalLoadGroup = + do_QueryInterface(mLoadGroup); + if (internalLoadGroup) { + internalLoadGroup->OnEndPageLoad(aChannel); + } + } + } + + // Timing is picked up by the window, we don't need it anymore + mTiming = nullptr; + + // clean up reload state for meta charset + if (eCharsetReloadRequested == mCharsetReloadState) { + mCharsetReloadState = eCharsetReloadStopOrigional; + } else { + mCharsetReloadState = eCharsetReloadInit; + } + + // Save a pointer to the currently-loading history entry. + // nsDocShell::EndPageLoad will clear mLSHE, but we may need this history + // entry further down in this method. + nsCOMPtr<nsISHEntry> loadingSHE = mLSHE; + mozilla::Unused << loadingSHE; // XXX: Not sure if we need this anymore + + // + // one of many safeguards that prevent death and destruction if + // someone is so very very rude as to bring this window down + // during this load handler. + // + nsCOMPtr<nsIDocShell> kungFuDeathGrip(this); + + // Notify the ContentViewer that the Document has finished loading. This + // will cause any OnLoad(...) and PopState(...) handlers to fire. + if (!mEODForCurrentDocument && mContentViewer) { + mIsExecutingOnLoadHandler = true; + mContentViewer->LoadComplete(aStatus); + mIsExecutingOnLoadHandler = false; + + mEODForCurrentDocument = true; + + // If all documents have completed their loading + // favor native event dispatch priorities + // over performance + if (--gNumberOfDocumentsLoading == 0) { + // Hint to use normal native event dispatch priorities + FavorPerformanceHint(false); + } + } + /* Check if the httpChannel has any cache-control related response headers, + * like no-store, no-cache. If so, update SHEntry so that + * when a user goes back/forward to this page, we appropriately do + * form value restoration or load from server. + */ + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + if (!httpChannel) { + // HttpChannel could be hiding underneath a Multipart channel. + GetHttpChannel(aChannel, getter_AddRefs(httpChannel)); + } + + if (httpChannel) { + // figure out if SH should be saving layout state. + bool discardLayoutState = ShouldDiscardLayoutState(httpChannel); + if (mLSHE && discardLayoutState && (mLoadType & LOAD_CMD_NORMAL) && + (mLoadType != LOAD_BYPASS_HISTORY) && (mLoadType != LOAD_ERROR_PAGE)) { + mLSHE->SetSaveLayoutStateFlag(false); + } + } + + // Clear mLSHE after calling the onLoadHandlers. This way, if the + // onLoadHandler tries to load something different in + // itself or one of its children, we can deal with it appropriately. + if (mLSHE) { + mLSHE->SetLoadType(nsIDocShellLoadInfo::loadHistory); + + // Clear the mLSHE reference to indicate document loading is done one + // way or another. + SetHistoryEntry(&mLSHE, nullptr); + } + // if there's a refresh header in the channel, this method + // will set it up for us. + if (mIsActive || !mDisableMetaRefreshWhenInactive) + RefreshURIFromQueue(); + + // Test whether this is the top frame or a subframe + bool isTopFrame = true; + nsCOMPtr<nsIDocShellTreeItem> targetParentTreeItem; + rv = GetSameTypeParent(getter_AddRefs(targetParentTreeItem)); + if (NS_SUCCEEDED(rv) && targetParentTreeItem) { + isTopFrame = false; + } + + // + // If the page load failed, then deal with the error condition... + // Errors are handled as follows: + // 1. Check to see if it's a file not found error or bad content + // encoding error. + // 2. Send the URI to a keyword server (if enabled) + // 3. If the error was DNS failure, then add www and .com to the URI + // (if appropriate). + // 4. Throw an error dialog box... + // + if (url && NS_FAILED(aStatus)) { + if (aStatus == NS_ERROR_FILE_NOT_FOUND || + aStatus == NS_ERROR_FILE_ACCESS_DENIED || + aStatus == NS_ERROR_CORRUPTED_CONTENT || + aStatus == NS_ERROR_INVALID_CONTENT_ENCODING) { + DisplayLoadError(aStatus, url, nullptr, aChannel); + return NS_OK; + } else if (aStatus == NS_ERROR_INVALID_SIGNATURE) { + // NS_ERROR_INVALID_SIGNATURE indicates a content-signature error. + // This currently only happens in case a remote about page fails. + // We have to load a fallback in this case. + // XXX: We always load about blank here, firefox has to overwrite this if + // it wants to display something else. + return LoadURI(u"about:blank", // URI string + nsIChannel::LOAD_NORMAL, // Load flags + nullptr, // Referring URI + nullptr, // Post data stream + nullptr); // Headers stream + } + + // Handle iframe document not loading error because source was + // a tracking URL. We make a note of this iframe node by including + // it in a dedicated array of blocked tracking nodes under its parent + // document. (document of parent window of blocked document) + if (isTopFrame == false && aStatus == NS_ERROR_TRACKING_URI) { + // frameElement is our nsIContent to be annotated + nsCOMPtr<nsIDOMElement> frameElement; + nsPIDOMWindowOuter* thisWindow = GetWindow(); + if (!thisWindow) { + return NS_OK; + } + + frameElement = thisWindow->GetFrameElement(); + if (!frameElement) { + return NS_OK; + } + + // Parent window + nsCOMPtr<nsIDocShellTreeItem> parentItem; + GetSameTypeParent(getter_AddRefs(parentItem)); + if (!parentItem) { + return NS_OK; + } + + nsCOMPtr<nsIDocument> parentDoc; + parentDoc = parentItem->GetDocument(); + if (!parentDoc) { + return NS_OK; + } + + nsCOMPtr<nsIContent> cont = do_QueryInterface(frameElement); + parentDoc->AddBlockedTrackingNode(cont); + + return NS_OK; + } + + if (sURIFixup) { + // + // 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 && mAllowKeywordFixup) { + bool keywordsEnabled = Preferences::GetBool("keyword.enabled", false); + + nsAutoCString host; + url->GetHost(host); + + nsAutoCString scheme; + url->GetScheme(scheme); + + int32_t dotLoc = host.FindChar('.'); + + // 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... + if (keywordsEnabled && !scheme.IsEmpty() && + (scheme.Find("http") != 0)) { + keywordsEnabled = false; + } + + if (keywordsEnabled && (kNotFound == dotLoc)) { + nsCOMPtr<nsIURIFixupInfo> info; + // only send non-qualified hosts to the keyword server + if (!mOriginalUriString.IsEmpty()) { + sURIFixup->KeywordToURI(mOriginalUriString, + getter_AddRefs(newPostData), + getter_AddRefs(info)); + } 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))) { + sURIFixup->KeywordToURI(utf8Host, + getter_AddRefs(newPostData), + getter_AddRefs(info)); + } else { + sURIFixup->KeywordToURI(host, + getter_AddRefs(newPostData), + getter_AddRefs(info)); + } + } + + info->GetPreferredURI(getter_AddRefs(newURI)); + if (newURI) { + info->GetKeywordAsSent(keywordAsSent); + info->GetKeywordProviderName(keywordProviderName); + } + } // end keywordsEnabled + } + + // + // Now try change the address, e.g. turn http://foo into + // http://www.foo.com + // + if (aStatus == NS_ERROR_UNKNOWN_HOST || + aStatus == NS_ERROR_NET_RESET) { + bool doCreateAlternate = true; + + // Skip fixup for anything except a normal document load + // operation on the topframe. + + if (mLoadType != LOAD_NORMAL || !isTopFrame) { + doCreateAlternate = false; + } else { + // Test if keyword lookup produced a new URI or not + if (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) { + // Skip doing this if our channel was redirected, because we + // shouldn't be guessing things about the post-redirect URI. + nsLoadFlags loadFlags = 0; + if (NS_FAILED(aChannel->GetLoadFlags(&loadFlags)) || + (loadFlags & nsIChannel::LOAD_REPLACE)) { + doCreateAlternate = false; + } + } + } + if (doCreateAlternate) { + newURI = nullptr; + newPostData = nullptr; + keywordProviderName.Truncate(); + keywordAsSent.Truncate(); + sURIFixup->CreateFixupURI(oldSpec, + nsIURIFixup::FIXUP_FLAGS_MAKE_ALTERNATE_URI, + getter_AddRefs(newPostData), + getter_AddRefs(newURI)); + } + } + + // 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) { + nsAutoCString newSpec; + newURI->GetSpec(newSpec); + NS_ConvertUTF8toUTF16 newSpecW(newSpec); + + // This notification is meant for Firefox Health Report so it + // can increment counts from the search engine + MaybeNotifyKeywordSearchLoading(keywordProviderName, keywordAsSent); + + return LoadURI(newSpecW.get(), // URI string + LOAD_FLAGS_NONE, // Load flags + nullptr, // Referring URI + newPostData, // Post data stream + nullptr); // Headers stream + } + } + } + + // Well, fixup didn't work :-( + // It is time to throw an error dialog box, and be done with it... + + // Errors to be shown only on top-level frames + if ((aStatus == NS_ERROR_UNKNOWN_HOST || + aStatus == NS_ERROR_CONNECTION_REFUSED || + aStatus == NS_ERROR_UNKNOWN_PROXY_HOST || + aStatus == NS_ERROR_PROXY_CONNECTION_REFUSED) && + (isTopFrame || UseErrorPages())) { + DisplayLoadError(aStatus, url, nullptr, aChannel); + } else if (aStatus == NS_ERROR_NET_TIMEOUT || + aStatus == NS_ERROR_REDIRECT_LOOP || + aStatus == NS_ERROR_UNKNOWN_SOCKET_TYPE || + aStatus == NS_ERROR_NET_INTERRUPT || + aStatus == NS_ERROR_NET_RESET || + aStatus == NS_ERROR_OFFLINE || + aStatus == NS_ERROR_MALWARE_URI || + aStatus == NS_ERROR_PHISHING_URI || + aStatus == NS_ERROR_UNWANTED_URI || + aStatus == NS_ERROR_UNSAFE_CONTENT_TYPE || + aStatus == NS_ERROR_REMOTE_XUL || + aStatus == NS_ERROR_INTERCEPTION_FAILED || + aStatus == NS_ERROR_NET_INADEQUATE_SECURITY || + NS_ERROR_GET_MODULE(aStatus) == NS_ERROR_MODULE_SECURITY) { + // Errors to be shown for any frame + DisplayLoadError(aStatus, url, nullptr, aChannel); + } else if (aStatus == NS_ERROR_DOCUMENT_NOT_CACHED) { + // Non-caching channels will simply return NS_ERROR_OFFLINE. + // Caching channels would have to look at their flags to work + // out which error to return. Or we can fix up the error here. + if (!(mLoadType & LOAD_CMD_HISTORY)) { + aStatus = NS_ERROR_OFFLINE; + } + DisplayLoadError(aStatus, url, nullptr, aChannel); + } + } else if (url && NS_SUCCEEDED(aStatus)) { + // If we have a host + mozilla::net::PredictorLearnRedirect(url, aChannel, this); + } + + return NS_OK; +} + +//***************************************************************************** +// nsDocShell: Content Viewer Management +//***************************************************************************** + +nsresult +nsDocShell::EnsureContentViewer() +{ + if (mContentViewer) { + return NS_OK; + } + if (mIsBeingDestroyed) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIURI> baseURI; + nsIPrincipal* principal = GetInheritedPrincipal(false); + nsCOMPtr<nsIDocShellTreeItem> parentItem; + GetSameTypeParent(getter_AddRefs(parentItem)); + if (parentItem) { + if (nsCOMPtr<nsPIDOMWindowOuter> domWin = GetWindow()) { + nsCOMPtr<Element> parentElement = domWin->GetFrameElementInternal(); + if (parentElement) { + baseURI = parentElement->GetBaseURI(); + } + } + } + + nsresult rv = CreateAboutBlankContentViewer(principal, baseURI); + + NS_ENSURE_STATE(mContentViewer); + + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIDocument> doc(GetDocument()); + NS_ASSERTION(doc, + "Should have doc if CreateAboutBlankContentViewer " + "succeeded!"); + + doc->SetIsInitialDocument(true); + } + + return rv; +} + +nsresult +nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal, + nsIURI* aBaseURI, + bool aTryToSaveOldPresentation) +{ + nsCOMPtr<nsIDocument> blankDoc; + nsCOMPtr<nsIContentViewer> viewer; + nsresult rv = NS_ERROR_FAILURE; + + /* mCreatingDocument should never be true at this point. However, it's + a theoretical possibility. We want to know about it and make it stop, + and this sounds like a job for an assertion. */ + NS_ASSERTION(!mCreatingDocument, + "infinite(?) loop creating document averted"); + if (mCreatingDocument) { + return NS_ERROR_FAILURE; + } + + // mContentViewer->PermitUnload may release |this| docshell. + nsCOMPtr<nsIDocShell> kungFuDeathGrip(this); + + AutoRestore<bool> creatingDocument(mCreatingDocument); + mCreatingDocument = true; + + if (aPrincipal && !nsContentUtils::IsSystemPrincipal(aPrincipal) && + mItemType != typeChrome) { + MOZ_ASSERT(ChromeUtils::IsOriginAttributesEqualIgnoringAddonId( + BasePrincipal::Cast(aPrincipal)->OriginAttributesRef(), + mOriginAttributes)); + } + + // Make sure timing is created. But first record whether we had it + // already, so we don't clobber the timing for an in-progress load. + bool hadTiming = mTiming; + MaybeInitTiming(); + if (mContentViewer) { + // We've got a content viewer already. Make sure the user + // permits us to discard the current document and replace it + // with about:blank. And also ensure we fire the unload events + // in the current document. + + // Unload gets fired first for + // document loaded from the session history. + mTiming->NotifyBeforeUnload(); + + bool okToUnload; + rv = mContentViewer->PermitUnload(&okToUnload); + + if (NS_SUCCEEDED(rv) && !okToUnload) { + // The user chose not to unload the page, interrupt the load. + return NS_ERROR_FAILURE; + } + + mSavingOldViewer = aTryToSaveOldPresentation && + CanSavePresentation(LOAD_NORMAL, nullptr, nullptr); + + if (mTiming) { + mTiming->NotifyUnloadAccepted(mCurrentURI); + } + + // Make sure to blow away our mLoadingURI just in case. No loads + // from inside this pagehide. + mLoadingURI = nullptr; + + // Stop any in-progress loading, so that we don't accidentally trigger any + // PageShow notifications from Embed() interrupting our loading below. + Stop(); + + // Notify the current document that it is about to be unloaded!! + // + // It is important to fire the unload() notification *before* any state + // is changed within the DocShell - otherwise, javascript will get the + // wrong information :-( + // + (void)FirePageHideNotification(!mSavingOldViewer); + } + + // Now make sure we don't think we're in the middle of firing unload after + // this point. This will make us fire unload when the about:blank document + // unloads... but that's ok, more or less. Would be nice if it fired load + // too, of course. + mFiredUnloadEvent = false; + + nsCOMPtr<nsIDocumentLoaderFactory> docFactory = + nsContentUtils::FindInternalContentViewer(NS_LITERAL_CSTRING("text/html")); + + if (docFactory) { + nsCOMPtr<nsIPrincipal> principal; + if (mSandboxFlags & SANDBOXED_ORIGIN) { + if (aPrincipal) { + principal = nsNullPrincipal::CreateWithInheritedAttributes(aPrincipal); + } else { + principal = nsNullPrincipal::CreateWithInheritedAttributes(this); + } + } else { + principal = aPrincipal; + } + // generate (about:blank) document to load + docFactory->CreateBlankDocument(mLoadGroup, principal, + getter_AddRefs(blankDoc)); + if (blankDoc) { + // Hack: set the base URI manually, since this document never + // got Reset() with a channel. + blankDoc->SetBaseURI(aBaseURI); + + blankDoc->SetContainer(this); + + // Copy our sandbox flags to the document. These are immutable + // after being set here. + blankDoc->SetSandboxFlags(mSandboxFlags); + + // create a content viewer for us and the new document + docFactory->CreateInstanceForDocument( + NS_ISUPPORTS_CAST(nsIDocShell*, this), blankDoc, "view", + getter_AddRefs(viewer)); + + // hook 'em up + if (viewer) { + viewer->SetContainer(this); + rv = Embed(viewer, "", 0); + NS_ENSURE_SUCCESS(rv, rv); + + SetCurrentURI(blankDoc->GetDocumentURI(), nullptr, true, 0); + rv = mIsBeingDestroyed ? NS_ERROR_NOT_AVAILABLE : NS_OK; + } + } + } + + // The transient about:blank viewer doesn't have a session history entry. + SetHistoryEntry(&mOSHE, nullptr); + + // Clear out our mTiming like we would in EndPageLoad, if we didn't + // have one before entering this function. + if (!hadTiming) { + mTiming = nullptr; + mBlankTiming = true; + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::CreateAboutBlankContentViewer(nsIPrincipal* aPrincipal) +{ + return CreateAboutBlankContentViewer(aPrincipal, nullptr); +} + +bool +nsDocShell::CanSavePresentation(uint32_t aLoadType, + nsIRequest* aNewRequest, + nsIDocument* aNewDocument) +{ + if (!mOSHE) { + return false; // no entry to save into + } + + nsCOMPtr<nsIContentViewer> viewer; + mOSHE->GetContentViewer(getter_AddRefs(viewer)); + if (viewer) { + NS_WARNING("mOSHE already has a content viewer!"); + return false; + } + + // Only save presentation for "normal" loads and link loads. Anything else + // probably wants to refetch the page, so caching the old presentation + // would be incorrect. + if (aLoadType != LOAD_NORMAL && + aLoadType != LOAD_HISTORY && + aLoadType != LOAD_LINK && + aLoadType != LOAD_STOP_CONTENT && + aLoadType != LOAD_STOP_CONTENT_AND_REPLACE && + aLoadType != LOAD_ERROR_PAGE) { + return false; + } + + // If the session history entry has the saveLayoutState flag set to false, + // then we should not cache the presentation. + bool canSaveState; + mOSHE->GetSaveLayoutStateFlag(&canSaveState); + if (!canSaveState) { + return false; + } + + // If the document is not done loading, don't cache it. + if (!mScriptGlobal || mScriptGlobal->IsLoading()) { + return false; + } + + if (mScriptGlobal->WouldReuseInnerWindow(aNewDocument)) { + return false; + } + + // Avoid doing the work of saving the presentation state in the case where + // the content viewer cache is disabled. + if (nsSHistory::GetMaxTotalViewers() == 0) { + return false; + } + + // Don't cache the content viewer if we're in a subframe and the subframe + // pref is disabled. + bool cacheFrames = + Preferences::GetBool("browser.sessionhistory.cache_subframes", false); + if (!cacheFrames) { + nsCOMPtr<nsIDocShellTreeItem> root; + GetSameTypeParent(getter_AddRefs(root)); + if (root && root != this) { + return false; // this is a subframe load + } + } + + // If the document does not want its presentation cached, then don't. + nsCOMPtr<nsIDocument> doc = mScriptGlobal->GetExtantDoc(); + return doc && doc->CanSavePresentation(aNewRequest); +} + +void +nsDocShell::ReattachEditorToWindow(nsISHEntry* aSHEntry) +{ + NS_ASSERTION(!mEditorData, + "Why reattach an editor when we already have one?"); + NS_ASSERTION(aSHEntry && aSHEntry->HasDetachedEditor(), + "Reattaching when there's not a detached editor."); + + if (mEditorData || !aSHEntry) { + return; + } + + mEditorData = aSHEntry->ForgetEditorData(); + if (mEditorData) { +#ifdef DEBUG + nsresult rv = +#endif + mEditorData->ReattachToWindow(this); + NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to reattach editing session"); + } +} + +void +nsDocShell::DetachEditorFromWindow() +{ + if (!mEditorData || mEditorData->WaitingForLoad()) { + // If there's nothing to detach, or if the editor data is actually set + // up for the _new_ page that's coming in, don't detach. + return; + } + + NS_ASSERTION(!mOSHE || !mOSHE->HasDetachedEditor(), + "Detaching editor when it's already detached."); + + nsresult res = mEditorData->DetachFromWindow(); + NS_ASSERTION(NS_SUCCEEDED(res), "Failed to detach editor"); + + if (NS_SUCCEEDED(res)) { + // Make mOSHE hold the owning ref to the editor data. + if (mOSHE) { + mOSHE->SetEditorData(mEditorData.forget()); + } else { + mEditorData = nullptr; + } + } + +#ifdef DEBUG + { + bool isEditable; + GetEditable(&isEditable); + NS_ASSERTION(!isEditable, + "Window is still editable after detaching editor."); + } +#endif // DEBUG +} + +nsresult +nsDocShell::CaptureState() +{ + if (!mOSHE || mOSHE == mLSHE) { + // No entry to save into, or we're replacing the existing entry. + return NS_ERROR_FAILURE; + } + + if (!mScriptGlobal) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsISupports> windowState = mScriptGlobal->SaveWindowState(); + NS_ENSURE_TRUE(windowState, NS_ERROR_FAILURE); + +#ifdef DEBUG_PAGE_CACHE + nsCOMPtr<nsIURI> uri; + mOSHE->GetURI(getter_AddRefs(uri)); + nsAutoCString spec; + if (uri) { + uri->GetSpec(spec); + } + printf("Saving presentation into session history\n"); + printf(" SH URI: %s\n", spec.get()); +#endif + + nsresult rv = mOSHE->SetWindowState(windowState); + NS_ENSURE_SUCCESS(rv, rv); + + // Suspend refresh URIs and save off the timer queue + rv = mOSHE->SetRefreshURIList(mSavedRefreshURIList); + NS_ENSURE_SUCCESS(rv, rv); + + // Capture the current content viewer bounds. + if (mContentViewer) { + nsIntRect bounds; + mContentViewer->GetBounds(bounds); + rv = mOSHE->SetViewerBounds(bounds); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Capture the docshell hierarchy. + mOSHE->ClearChildShells(); + + uint32_t childCount = mChildList.Length(); + for (uint32_t i = 0; i < childCount; ++i) { + nsCOMPtr<nsIDocShellTreeItem> childShell = do_QueryInterface(ChildAt(i)); + NS_ASSERTION(childShell, "null child shell"); + + mOSHE->AddChildShell(childShell); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::RestorePresentationEvent::Run() +{ + if (mDocShell && NS_FAILED(mDocShell->RestoreFromHistory())) { + NS_WARNING("RestoreFromHistory failed"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::BeginRestore(nsIContentViewer* aContentViewer, bool aTop) +{ + nsresult rv; + if (!aContentViewer) { + rv = EnsureContentViewer(); + NS_ENSURE_SUCCESS(rv, rv); + + aContentViewer = mContentViewer; + } + + // Dispatch events for restoring the presentation. We try to simulate + // the progress notifications loading the document would cause, so we add + // the document's channel to the loadgroup to initiate stateChange + // notifications. + + nsCOMPtr<nsIDOMDocument> domDoc; + aContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); + if (doc) { + nsIChannel* channel = doc->GetChannel(); + if (channel) { + mEODForCurrentDocument = false; + mIsRestoringDocument = true; + mLoadGroup->AddRequest(channel, nullptr); + mIsRestoringDocument = false; + } + } + + if (!aTop) { + // This point corresponds to us having gotten OnStartRequest or + // STATE_START, so do the same thing that CreateContentViewer does at + // this point to ensure that unload/pagehide events for this document + // will fire when it's unloaded again. + mFiredUnloadEvent = false; + + // For non-top frames, there is no notion of making sure that the + // previous document is in the domwindow when STATE_START notifications + // happen. We can just call BeginRestore for all of the child shells + // now. + rv = BeginRestoreChildren(); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +nsresult +nsDocShell::BeginRestoreChildren() +{ + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> child = do_QueryObject(iter.GetNext()); + if (child) { + nsresult rv = child->BeginRestore(nullptr, false); + NS_ENSURE_SUCCESS(rv, rv); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::FinishRestore() +{ + // First we call finishRestore() on our children. In the simulated load, + // all of the child frames finish loading before the main document. + + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> child = do_QueryObject(iter.GetNext()); + if (child) { + child->FinishRestore(); + } + } + + if (mOSHE && mOSHE->HasDetachedEditor()) { + ReattachEditorToWindow(mOSHE); + } + + nsCOMPtr<nsIDocument> doc = GetDocument(); + if (doc) { + // Finally, we remove the request from the loadgroup. This will + // cause onStateChange(STATE_STOP) to fire, which will fire the + // pageshow event to the chrome. + + nsIChannel* channel = doc->GetChannel(); + if (channel) { + mIsRestoringDocument = true; + mLoadGroup->RemoveRequest(channel, nullptr, NS_OK); + mIsRestoringDocument = false; + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetRestoringDocument(bool* aRestoring) +{ + *aRestoring = mIsRestoringDocument; + return NS_OK; +} + +nsresult +nsDocShell::RestorePresentation(nsISHEntry* aSHEntry, bool* aRestoring) +{ + NS_ASSERTION(mLoadType & LOAD_CMD_HISTORY, + "RestorePresentation should only be called for history loads"); + + nsCOMPtr<nsIContentViewer> viewer; + aSHEntry->GetContentViewer(getter_AddRefs(viewer)); + +#ifdef DEBUG_PAGE_CACHE + nsCOMPtr<nsIURI> uri; + aSHEntry->GetURI(getter_AddRefs(uri)); + + nsAutoCString spec; + if (uri) { + uri->GetSpec(spec); + } +#endif + + *aRestoring = false; + + if (!viewer) { +#ifdef DEBUG_PAGE_CACHE + printf("no saved presentation for uri: %s\n", spec.get()); +#endif + return NS_OK; + } + + // We need to make sure the content viewer's container is this docshell. + // In subframe navigation, it's possible for the docshell that the + // content viewer was originally loaded into to be replaced with a + // different one. We don't currently support restoring the presentation + // in that case. + + nsCOMPtr<nsIDocShell> container; + viewer->GetContainer(getter_AddRefs(container)); + if (!::SameCOMIdentity(container, GetAsSupports(this))) { +#ifdef DEBUG_PAGE_CACHE + printf("No valid container, clearing presentation\n"); +#endif + aSHEntry->SetContentViewer(nullptr); + return NS_ERROR_FAILURE; + } + + NS_ASSERTION(mContentViewer != viewer, "Restoring existing presentation"); + +#ifdef DEBUG_PAGE_CACHE + printf("restoring presentation from session history: %s\n", spec.get()); +#endif + + SetHistoryEntry(&mLSHE, aSHEntry); + + // Post an event that will remove the request after we've returned + // to the event loop. This mimics the way it is called by nsIChannel + // implementations. + + // Revoke any pending restore (just in case) + NS_ASSERTION(!mRestorePresentationEvent.IsPending(), + "should only have one RestorePresentationEvent"); + mRestorePresentationEvent.Revoke(); + + RefPtr<RestorePresentationEvent> evt = new RestorePresentationEvent(this); + nsresult rv = NS_DispatchToCurrentThread(evt); + if (NS_SUCCEEDED(rv)) { + mRestorePresentationEvent = evt.get(); + // The rest of the restore processing will happen on our event + // callback. + *aRestoring = true; + } + + return rv; +} + +namespace { +class MOZ_STACK_CLASS PresentationEventForgetter +{ +public: + explicit PresentationEventForgetter( + nsRevocableEventPtr<nsDocShell::RestorePresentationEvent>& + aRestorePresentationEvent) + : mRestorePresentationEvent(aRestorePresentationEvent) + , mEvent(aRestorePresentationEvent.get()) + { + } + + ~PresentationEventForgetter() + { + Forget(); + } + + void Forget() + { + if (mRestorePresentationEvent.get() == mEvent) { + mRestorePresentationEvent.Forget(); + mEvent = nullptr; + } + } + +private: + nsRevocableEventPtr<nsDocShell::RestorePresentationEvent>& + mRestorePresentationEvent; + RefPtr<nsDocShell::RestorePresentationEvent> mEvent; +}; + +} // namespace + +nsresult +nsDocShell::RestoreFromHistory() +{ + MOZ_ASSERT(mRestorePresentationEvent.IsPending()); + PresentationEventForgetter forgetter(mRestorePresentationEvent); + + // This section of code follows the same ordering as CreateContentViewer. + if (!mLSHE) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIContentViewer> viewer; + mLSHE->GetContentViewer(getter_AddRefs(viewer)); + if (!viewer) { + return NS_ERROR_FAILURE; + } + + if (mSavingOldViewer) { + // We determined that it was safe to cache the document presentation + // at the time we initiated the new load. We need to check whether + // it's still safe to do so, since there may have been DOM mutations + // or new requests initiated. + nsCOMPtr<nsIDOMDocument> domDoc; + viewer->GetDOMDocument(getter_AddRefs(domDoc)); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); + nsIRequest* request = nullptr; + if (doc) { + request = doc->GetChannel(); + } + mSavingOldViewer = CanSavePresentation(mLoadType, request, doc); + } + + nsCOMPtr<nsIContentViewer> oldCv(mContentViewer); + nsCOMPtr<nsIContentViewer> newCv(viewer); + int32_t minFontSize = 0; + float textZoom = 1.0f; + float pageZoom = 1.0f; + float overrideDPPX = 0.0f; + + bool styleDisabled = false; + if (oldCv && newCv) { + oldCv->GetMinFontSize(&minFontSize); + oldCv->GetTextZoom(&textZoom); + oldCv->GetFullZoom(&pageZoom); + oldCv->GetOverrideDPPX(&overrideDPPX); + oldCv->GetAuthorStyleDisabled(&styleDisabled); + } + + // Protect against mLSHE going away via a load triggered from + // pagehide or unload. + nsCOMPtr<nsISHEntry> origLSHE = mLSHE; + + // Make sure to blow away our mLoadingURI just in case. No loads + // from inside this pagehide. + mLoadingURI = nullptr; + + // Notify the old content viewer that it's being hidden. + FirePageHideNotification(!mSavingOldViewer); + + // If mLSHE was changed as a result of the pagehide event, then + // something else was loaded. Don't finish restoring. + if (mLSHE != origLSHE) { + return NS_OK; + } + + // Add the request to our load group. We do this before swapping out + // the content viewers so that consumers of STATE_START can access + // the old document. We only deal with the toplevel load at this time -- + // to be consistent with normal document loading, subframes cannot start + // loading until after data arrives, which is after STATE_START completes. + + RefPtr<RestorePresentationEvent> currentPresentationRestoration = + mRestorePresentationEvent.get(); + Stop(); + // Make sure we're still restoring the same presentation. + // If we aren't, docshell is in process doing another load already. + NS_ENSURE_STATE(currentPresentationRestoration == + mRestorePresentationEvent.get()); + BeginRestore(viewer, true); + NS_ENSURE_STATE(currentPresentationRestoration == + mRestorePresentationEvent.get()); + forgetter.Forget(); + + // Set mFiredUnloadEvent = false so that the unload handler for the + // *new* document will fire. + mFiredUnloadEvent = false; + + mURIResultedInDocument = true; + nsCOMPtr<nsISHistory> rootSH; + GetRootSessionHistory(getter_AddRefs(rootSH)); + if (rootSH) { + nsCOMPtr<nsISHistoryInternal> hist = do_QueryInterface(rootSH); + rootSH->GetIndex(&mPreviousTransIndex); + hist->UpdateIndex(); + rootSH->GetIndex(&mLoadedTransIndex); +#ifdef DEBUG_PAGE_CACHE + printf("Previous index: %d, Loaded index: %d\n\n", mPreviousTransIndex, + mLoadedTransIndex); +#endif + } + + // Rather than call Embed(), we will retrieve the viewer from the session + // history entry and swap it in. + // XXX can we refactor this so that we can just call Embed()? + PersistLayoutHistoryState(); + nsresult rv; + if (mContentViewer) { + if (mSavingOldViewer && NS_FAILED(CaptureState())) { + if (mOSHE) { + mOSHE->SyncPresentationState(); + } + mSavingOldViewer = false; + } + } + + mSavedRefreshURIList = nullptr; + + // In cases where we use a transient about:blank viewer between loads, + // we never show the transient viewer, so _its_ previous viewer is never + // unhooked from the view hierarchy. Destroy any such previous viewer now, + // before we grab the root view sibling, so that we don't grab a view + // that's about to go away. + + if (mContentViewer) { + nsCOMPtr<nsIContentViewer> previousViewer; + mContentViewer->GetPreviousViewer(getter_AddRefs(previousViewer)); + if (previousViewer) { + mContentViewer->SetPreviousViewer(nullptr); + previousViewer->Destroy(); + } + } + + // Save off the root view's parent and sibling so that we can insert the + // new content viewer's root view at the same position. Also save the + // bounds of the root view's widget. + + nsView* rootViewSibling = nullptr; + nsView* rootViewParent = nullptr; + nsIntRect newBounds(0, 0, 0, 0); + + nsCOMPtr<nsIPresShell> oldPresShell = GetPresShell(); + if (oldPresShell) { + nsViewManager* vm = oldPresShell->GetViewManager(); + if (vm) { + nsView* oldRootView = vm->GetRootView(); + + if (oldRootView) { + rootViewSibling = oldRootView->GetNextSibling(); + rootViewParent = oldRootView->GetParent(); + + mContentViewer->GetBounds(newBounds); + } + } + } + + nsCOMPtr<nsIContent> container; + nsCOMPtr<nsIDocument> sibling; + if (rootViewParent && rootViewParent->GetParent()) { + nsIFrame* frame = rootViewParent->GetParent()->GetFrame(); + container = frame ? frame->GetContent() : nullptr; + } + if (rootViewSibling) { + nsIFrame* frame = rootViewSibling->GetFrame(); + sibling = + frame ? frame->PresContext()->PresShell()->GetDocument() : nullptr; + } + + // Transfer ownership to mContentViewer. By ensuring that either the + // docshell or the session history, but not both, have references to the + // content viewer, we prevent the viewer from being torn down after + // Destroy() is called. + + if (mContentViewer) { + mContentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr); + viewer->SetPreviousViewer(mContentViewer); + } + if (mOSHE && (!mContentViewer || !mSavingOldViewer)) { + // We don't plan to save a viewer in mOSHE; tell it to drop + // any other state it's holding. + mOSHE->SyncPresentationState(); + } + + // Order the mContentViewer setup just like Embed does. + mContentViewer = nullptr; + + // Now that we're about to switch documents, forget all of our children. + // Note that we cached them as needed up in CaptureState above. + DestroyChildren(); + + mContentViewer.swap(viewer); + + // Grab all of the related presentation from the SHEntry now. + // Clearing the viewer from the SHEntry will clear all of this state. + nsCOMPtr<nsISupports> windowState; + mLSHE->GetWindowState(getter_AddRefs(windowState)); + mLSHE->SetWindowState(nullptr); + + bool sticky; + mLSHE->GetSticky(&sticky); + + nsCOMPtr<nsIDOMDocument> domDoc; + mContentViewer->GetDOMDocument(getter_AddRefs(domDoc)); + + nsCOMArray<nsIDocShellTreeItem> childShells; + int32_t i = 0; + nsCOMPtr<nsIDocShellTreeItem> child; + while (NS_SUCCEEDED(mLSHE->ChildShellAt(i++, getter_AddRefs(child))) && + child) { + childShells.AppendObject(child); + } + + // get the previous content viewer size + nsIntRect oldBounds(0, 0, 0, 0); + mLSHE->GetViewerBounds(oldBounds); + + // Restore the refresh URI list. The refresh timers will be restarted + // when EndPageLoad() is called. + nsCOMPtr<nsIMutableArray> refreshURIList; + mLSHE->GetRefreshURIList(getter_AddRefs(refreshURIList)); + + // Reattach to the window object. + mIsRestoringDocument = true; // for MediaDocument::BecomeInteractive + rv = mContentViewer->Open(windowState, mLSHE); + mIsRestoringDocument = false; + + // Hack to keep nsDocShellEditorData alive across the + // SetContentViewer(nullptr) call below. + nsAutoPtr<nsDocShellEditorData> data(mLSHE->ForgetEditorData()); + + // Now remove it from the cached presentation. + mLSHE->SetContentViewer(nullptr); + mEODForCurrentDocument = false; + + mLSHE->SetEditorData(data.forget()); + +#ifdef DEBUG + { + nsCOMPtr<nsIMutableArray> refreshURIs; + mLSHE->GetRefreshURIList(getter_AddRefs(refreshURIs)); + nsCOMPtr<nsIDocShellTreeItem> childShell; + mLSHE->ChildShellAt(0, getter_AddRefs(childShell)); + NS_ASSERTION(!refreshURIs && !childShell, + "SHEntry should have cleared presentation state"); + } +#endif + + // Restore the sticky state of the viewer. The viewer has set this state + // on the history entry in Destroy() just before marking itself non-sticky, + // to avoid teardown of the presentation. + mContentViewer->SetSticky(sticky); + + NS_ENSURE_SUCCESS(rv, rv); + + // mLSHE is now our currently-loaded document. + SetHistoryEntry(&mOSHE, mLSHE); + + // XXX special wyciwyg handling in Embed()? + + // We aren't going to restore any items from the LayoutHistoryState, + // but we don't want them to stay around in case the page is reloaded. + SetLayoutHistoryState(nullptr); + + // This is the end of our Embed() replacement + + mSavingOldViewer = false; + mEODForCurrentDocument = false; + + // Tell the event loop to favor plevents over user events, see comments + // in CreateContentViewer. + if (++gNumberOfDocumentsLoading == 1) { + FavorPerformanceHint(true); + } + + if (oldCv && newCv) { + newCv->SetMinFontSize(minFontSize); + newCv->SetTextZoom(textZoom); + newCv->SetFullZoom(pageZoom); + newCv->SetOverrideDPPX(overrideDPPX); + newCv->SetAuthorStyleDisabled(styleDisabled); + } + + nsCOMPtr<nsIDocument> document = do_QueryInterface(domDoc); + if (document) { + RefPtr<nsDocShell> parent = GetParentDocshell(); + if (parent) { + nsCOMPtr<nsIDocument> d = parent->GetDocument(); + if (d) { + if (d->EventHandlingSuppressed()) { + document->SuppressEventHandling(nsIDocument::eEvents, + d->EventHandlingSuppressed()); + } + + // Ick, it'd be nicer to not rewalk all of the subdocs here. + if (d->AnimationsPaused()) { + document->SuppressEventHandling(nsIDocument::eAnimationsOnly, + d->AnimationsPaused()); + } + } + } + + // Use the uri from the mLSHE we had when we entered this function + // (which need not match the document's URI if anchors are involved), + // since that's the history entry we're loading. Note that if we use + // origLSHE we don't have to worry about whether the entry in question + // is still mLSHE or whether it's now mOSHE. + nsCOMPtr<nsIURI> uri; + origLSHE->GetURI(getter_AddRefs(uri)); + SetCurrentURI(uri, document->GetChannel(), true, 0); + } + + // This is the end of our CreateContentViewer() replacement. + // Now we simulate a load. First, we restore the state of the javascript + // window object. + nsCOMPtr<nsPIDOMWindowOuter> privWin = GetWindow(); + NS_ASSERTION(privWin, "could not get nsPIDOMWindow interface"); + + // Now, dispatch a title change event which would happen as the + // <head> is parsed. + document->NotifyPossibleTitleChange(false); + + // Now we simulate appending child docshells for subframes. + for (i = 0; i < childShells.Count(); ++i) { + nsIDocShellTreeItem* childItem = childShells.ObjectAt(i); + nsCOMPtr<nsIDocShell> childShell = do_QueryInterface(childItem); + + // Make sure to not clobber the state of the child. Since AddChild + // always clobbers it, save it off first. + bool allowPlugins; + childShell->GetAllowPlugins(&allowPlugins); + + bool allowJavascript; + childShell->GetAllowJavascript(&allowJavascript); + + bool allowRedirects; + childShell->GetAllowMetaRedirects(&allowRedirects); + + bool allowSubframes; + childShell->GetAllowSubframes(&allowSubframes); + + bool allowImages; + childShell->GetAllowImages(&allowImages); + + bool allowMedia = childShell->GetAllowMedia(); + + bool allowDNSPrefetch; + childShell->GetAllowDNSPrefetch(&allowDNSPrefetch); + + bool allowContentRetargeting = childShell->GetAllowContentRetargeting(); + bool allowContentRetargetingOnChildren = + childShell->GetAllowContentRetargetingOnChildren(); + + uint32_t defaultLoadFlags; + childShell->GetDefaultLoadFlags(&defaultLoadFlags); + + // this.AddChild(child) calls child.SetDocLoaderParent(this), meaning that + // the child inherits our state. Among other things, this means that the + // child inherits our mIsActive, mIsPrerendered and mPrivateBrowsingId, + // which is what we want. + AddChild(childItem); + + childShell->SetAllowPlugins(allowPlugins); + childShell->SetAllowJavascript(allowJavascript); + childShell->SetAllowMetaRedirects(allowRedirects); + childShell->SetAllowSubframes(allowSubframes); + childShell->SetAllowImages(allowImages); + childShell->SetAllowMedia(allowMedia); + childShell->SetAllowDNSPrefetch(allowDNSPrefetch); + childShell->SetAllowContentRetargeting(allowContentRetargeting); + childShell->SetAllowContentRetargetingOnChildren( + allowContentRetargetingOnChildren); + childShell->SetDefaultLoadFlags(defaultLoadFlags); + + rv = childShell->BeginRestore(nullptr, false); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Make sure to restore the window state after adding the child shells back + // to the tree. This is necessary for Thaw() and Resume() to propagate + // properly. + rv = privWin->RestoreWindowState(windowState); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIPresShell> shell = GetPresShell(); + + // We may be displayed on a different monitor (or in a different + // HiDPI mode) than when we got into the history list. So we need + // to check if this has happened. See bug 838239. + + // Because the prescontext normally handles resolution changes via + // a runnable (see nsPresContext::UIResolutionChanged), its device + // context won't be -immediately- updated as a result of calling + // shell->BackingScaleFactorChanged(). + + // But we depend on that device context when adjusting the view size + // via mContentViewer->SetBounds(newBounds) below. So we need to + // explicitly tell it to check for changed resolution here. + if (shell && shell->GetPresContext()->DeviceContext()->CheckDPIChange()) { + shell->BackingScaleFactorChanged(); + } + + nsViewManager* newVM = shell ? shell->GetViewManager() : nullptr; + nsView* newRootView = newVM ? newVM->GetRootView() : nullptr; + + // Insert the new root view at the correct location in the view tree. + if (container) { + nsSubDocumentFrame* subDocFrame = + do_QueryFrame(container->GetPrimaryFrame()); + rootViewParent = subDocFrame ? subDocFrame->EnsureInnerView() : nullptr; + } else { + rootViewParent = nullptr; + } + if (sibling && + sibling->GetShell() && + sibling->GetShell()->GetViewManager()) { + rootViewSibling = sibling->GetShell()->GetViewManager()->GetRootView(); + } else { + rootViewSibling = nullptr; + } + if (rootViewParent && newRootView && + newRootView->GetParent() != rootViewParent) { + nsViewManager* parentVM = rootViewParent->GetViewManager(); + if (parentVM) { + // InsertChild(parent, child, sib, true) inserts the child after + // sib in content order, which is before sib in view order. BUT + // when sib is null it inserts at the end of the the document + // order, i.e., first in view order. But when oldRootSibling is + // null, the old root as at the end of the view list --- last in + // content order --- and we want to call InsertChild(parent, child, + // nullptr, false) in that case. + parentVM->InsertChild(rootViewParent, newRootView, + rootViewSibling, + rootViewSibling ? true : false); + + NS_ASSERTION(newRootView->GetNextSibling() == rootViewSibling, + "error in InsertChild"); + } + } + + nsCOMPtr<nsPIDOMWindowInner> privWinInner = privWin->GetCurrentInnerWindow(); + + // If parent is suspended, increase suspension count. + // This can't be done as early as event suppression since this + // depends on docshell tree. + privWinInner->SyncStateFromParentWindow(); + + // Now that all of the child docshells have been put into place, we can + // restart the timers for the window and all of the child frames. + privWinInner->Resume(); + + // Restore the refresh URI list. The refresh timers will be restarted + // when EndPageLoad() is called. + mRefreshURIList = refreshURIList; + + // Meta-refresh timers have been restarted for this shell, but not + // for our children. Walk the child shells and restart their timers. + nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList); + while (iter.HasMore()) { + nsCOMPtr<nsIDocShell> child = do_QueryObject(iter.GetNext()); + if (child) { + child->ResumeRefreshURIs(); + } + } + + // Make sure this presentation is the same size as the previous + // presentation. If this is not the same size we showed it at last time, + // then we need to resize the widget. + + // XXXbryner This interacts poorly with Firefox's infobar. If the old + // presentation had the infobar visible, then we will resize the new + // presentation to that smaller size. However, firing the locationchanged + // event will hide the infobar, which will immediately resize the window + // back to the larger size. A future optimization might be to restore + // the presentation at the "wrong" size, then fire the locationchanged + // event and check whether the docshell's new size is the same as the + // cached viewer size (skipping the resize if they are equal). + + if (newRootView) { + if (!newBounds.IsEmpty() && !newBounds.IsEqualEdges(oldBounds)) { +#ifdef DEBUG_PAGE_CACHE + printf("resize widget(%d, %d, %d, %d)\n", newBounds.x, + newBounds.y, newBounds.width, newBounds.height); +#endif + mContentViewer->SetBounds(newBounds); + } else { + nsIScrollableFrame* rootScrollFrame = + shell->GetRootScrollFrameAsScrollableExternal(); + if (rootScrollFrame) { + rootScrollFrame->PostScrolledAreaEventForCurrentArea(); + } + } + } + + // The FinishRestore call below can kill these, null them out so we don't + // have invalid pointer lying around. + newRootView = rootViewSibling = rootViewParent = nullptr; + newVM = nullptr; + + // Simulate the completion of the load. + nsDocShell::FinishRestore(); + + // Restart plugins, and paint the content. + if (shell) { + shell->Thaw(); + } + + return privWin->FireDelayedDOMEvents(); +} + +nsresult +nsDocShell::CreateContentViewer(const nsACString& aContentType, + nsIRequest* aRequest, + nsIStreamListener** aContentHandler) +{ + *aContentHandler = nullptr; + + // Can we check the content type of the current content viewer + // and reuse it without destroying it and re-creating it? + + NS_ASSERTION(mLoadGroup, "Someone ignored return from Init()?"); + + // Instantiate the content viewer object + nsCOMPtr<nsIContentViewer> viewer; + nsresult rv = NewContentViewerObj(aContentType, aRequest, mLoadGroup, + aContentHandler, getter_AddRefs(viewer)); + + if (NS_FAILED(rv)) { + return rv; + } + + // Notify the current document that it is about to be unloaded!! + // + // It is important to fire the unload() notification *before* any state + // is changed within the DocShell - otherwise, javascript will get the + // wrong information :-( + // + + if (mSavingOldViewer) { + // We determined that it was safe to cache the document presentation + // at the time we initiated the new load. We need to check whether + // it's still safe to do so, since there may have been DOM mutations + // or new requests initiated. + nsCOMPtr<nsIDOMDocument> domDoc; + viewer->GetDOMDocument(getter_AddRefs(domDoc)); + nsCOMPtr<nsIDocument> doc = do_QueryInterface(domDoc); + mSavingOldViewer = CanSavePresentation(mLoadType, aRequest, doc); + } + + NS_ASSERTION(!mLoadingURI, "Re-entering unload?"); + + nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest); + if (aOpenedChannel) { + aOpenedChannel->GetURI(getter_AddRefs(mLoadingURI)); + } + FirePageHideNotification(!mSavingOldViewer); + mLoadingURI = nullptr; + + // Set mFiredUnloadEvent = false so that the unload handler for the + // *new* document will fire. + mFiredUnloadEvent = false; + + // we've created a new document so go ahead and call + // OnLoadingSite(), but don't fire OnLocationChange() + // notifications before we've called Embed(). See bug 284993. + mURIResultedInDocument = true; + + if (mLoadType == LOAD_ERROR_PAGE) { + // We need to set the SH entry and our current URI here and not + // at the moment we load the page. We want the same behavior + // of Stop() as for a normal page load. See bug 514232 for details. + + // Revert mLoadType to load type to state the page load failed, + // following function calls need it. + mLoadType = mFailedLoadType; + + nsCOMPtr<nsIChannel> failedChannel = mFailedChannel; + + nsIDocument* doc = viewer->GetDocument(); + if (doc) { + doc->SetFailedChannel(failedChannel); + } + + // Make sure we have a URI to set currentURI. + nsCOMPtr<nsIURI> failedURI; + if (failedChannel) { + NS_GetFinalChannelURI(failedChannel, getter_AddRefs(failedURI)); + } + + if (!failedURI) { + failedURI = mFailedURI; + } + if (!failedURI) { + // We need a URI object to store a session history entry, so make up a URI + NS_NewURI(getter_AddRefs(failedURI), "about:blank"); + } + + // When we don't have failedURI, something wrong will happen. See + // bug 291876. + MOZ_ASSERT(failedURI, "We don't have a URI for history APIs."); + + mFailedChannel = nullptr; + mFailedURI = nullptr; + + // Create an shistory entry for the old load. + if (failedURI) { + bool errorOnLocationChangeNeeded = OnNewURI( + failedURI, failedChannel, nullptr, nullptr, mLoadType, false, false, false); + + if (errorOnLocationChangeNeeded) { + FireOnLocationChange(this, failedChannel, failedURI, + LOCATION_CHANGE_ERROR_PAGE); + } + } + + // Be sure to have a correct mLSHE, it may have been cleared by + // EndPageLoad. See bug 302115. + if (mSessionHistory && !mLSHE) { + int32_t idx; + mSessionHistory->GetRequestedIndex(&idx); + if (idx == -1) { + mSessionHistory->GetIndex(&idx); + } + mSessionHistory->GetEntryAtIndex(idx, false, getter_AddRefs(mLSHE)); + } + + mLoadType = LOAD_ERROR_PAGE; + } + + bool onLocationChangeNeeded = OnLoadingSite(aOpenedChannel, false); + + // let's try resetting the load group if we need to... + nsCOMPtr<nsILoadGroup> currentLoadGroup; + NS_ENSURE_SUCCESS( + aOpenedChannel->GetLoadGroup(getter_AddRefs(currentLoadGroup)), + NS_ERROR_FAILURE); + + if (currentLoadGroup != mLoadGroup) { + nsLoadFlags loadFlags = 0; + + // Cancel any URIs that are currently loading... + // XXX: Need to do this eventually Stop(); + // + // Retarget the document to this loadgroup... + // + /* First attach the channel to the right loadgroup + * and then remove from the old loadgroup. This + * puts the notifications in the right order and + * we don't null-out mLSHE in OnStateChange() for + * all redirected urls + */ + aOpenedChannel->SetLoadGroup(mLoadGroup); + + // Mark the channel as being a document URI... + aOpenedChannel->GetLoadFlags(&loadFlags); + loadFlags |= nsIChannel::LOAD_DOCUMENT_URI; + + aOpenedChannel->SetLoadFlags(loadFlags); + + mLoadGroup->AddRequest(aRequest, nullptr); + if (currentLoadGroup) { + currentLoadGroup->RemoveRequest(aRequest, nullptr, NS_BINDING_RETARGETED); + } + + // Update the notification callbacks, so that progress and + // status information are sent to the right docshell... + aOpenedChannel->SetNotificationCallbacks(this); + } + + NS_ENSURE_SUCCESS(Embed(viewer, "", nullptr), NS_ERROR_FAILURE); + + mSavedRefreshURIList = nullptr; + mSavingOldViewer = false; + mEODForCurrentDocument = false; + + // if this document is part of a multipart document, + // the ID can be used to distinguish it from the other parts. + nsCOMPtr<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aRequest)); + if (multiPartChannel) { + nsCOMPtr<nsIPresShell> shell = GetPresShell(); + if (NS_SUCCEEDED(rv) && shell) { + nsIDocument* doc = shell->GetDocument(); + if (doc) { + uint32_t partID; + multiPartChannel->GetPartID(&partID); + doc->SetPartID(partID); + } + } + } + + // Give hint to native plevent dispatch mechanism. If a document + // is loading the native plevent dispatch mechanism should favor + // performance over normal native event dispatch priorities. + if (++gNumberOfDocumentsLoading == 1) { + // Hint to favor performance for the plevent notification mechanism. + // We want the pages to load as fast as possible even if its means + // native messages might be starved. + FavorPerformanceHint(true); + } + + if (onLocationChangeNeeded) { + FireOnLocationChange(this, aRequest, mCurrentURI, 0); + } + + return NS_OK; +} + +nsresult +nsDocShell::NewContentViewerObj(const nsACString& aContentType, + nsIRequest* aRequest, nsILoadGroup* aLoadGroup, + nsIStreamListener** aContentHandler, + nsIContentViewer** aViewer) +{ + nsCOMPtr<nsIChannel> aOpenedChannel = do_QueryInterface(aRequest); + + nsCOMPtr<nsIDocumentLoaderFactory> docLoaderFactory = + nsContentUtils::FindInternalContentViewer(aContentType); + if (!docLoaderFactory) { + return NS_ERROR_FAILURE; + } + + // Now create an instance of the content viewer nsLayoutDLF makes the + // determination if it should be a "view-source" instead of "view" + nsresult rv = docLoaderFactory->CreateInstance("view", + aOpenedChannel, + aLoadGroup, aContentType, + this, + nullptr, + aContentHandler, + aViewer); + NS_ENSURE_SUCCESS(rv, rv); + + (*aViewer)->SetContainer(this); + return NS_OK; +} + +nsresult +nsDocShell::SetupNewViewer(nsIContentViewer* aNewViewer) +{ + // + // Copy content viewer state from previous or parent content viewer. + // + // The following logic is mirrored in nsHTMLDocument::StartDocumentLoad! + // + // Do NOT to maintain a reference to the old content viewer outside + // of this "copying" block, or it will not be destroyed until the end of + // this routine and all <SCRIPT>s and event handlers fail! (bug 20315) + // + // In this block of code, if we get an error result, we return it + // but if we get a null pointer, that's perfectly legal for parent + // and parentContentViewer. + // + + int32_t x = 0; + int32_t y = 0; + int32_t cx = 0; + int32_t cy = 0; + + // This will get the size from the current content viewer or from the + // Init settings + DoGetPositionAndSize(&x, &y, &cx, &cy); + + nsCOMPtr<nsIDocShellTreeItem> parentAsItem; + NS_ENSURE_SUCCESS(GetSameTypeParent(getter_AddRefs(parentAsItem)), + NS_ERROR_FAILURE); + nsCOMPtr<nsIDocShell> parent(do_QueryInterface(parentAsItem)); + + nsAutoCString forceCharset; + nsAutoCString hintCharset; + int32_t hintCharsetSource; + int32_t minFontSize; + float textZoom; + float pageZoom; + float overrideDPPX; + bool styleDisabled; + // |newMUDV| also serves as a flag to set the data from the above vars + nsCOMPtr<nsIContentViewer> newCv; + + if (mContentViewer || parent) { + nsCOMPtr<nsIContentViewer> oldCv; + if (mContentViewer) { + // Get any interesting state from old content viewer + // XXX: it would be far better to just reuse the document viewer , + // since we know we're just displaying the same document as before + oldCv = mContentViewer; + + // Tell the old content viewer to hibernate in session history when + // it is destroyed. + + if (mSavingOldViewer && NS_FAILED(CaptureState())) { + if (mOSHE) { + mOSHE->SyncPresentationState(); + } + mSavingOldViewer = false; + } + } else { + // No old content viewer, so get state from parent's content viewer + parent->GetContentViewer(getter_AddRefs(oldCv)); + } + + if (oldCv) { + newCv = aNewViewer; + if (newCv) { + NS_ENSURE_SUCCESS(oldCv->GetForceCharacterSet(forceCharset), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetHintCharacterSet(hintCharset), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetHintCharacterSetSource(&hintCharsetSource), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetMinFontSize(&minFontSize), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetTextZoom(&textZoom), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetFullZoom(&pageZoom), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetOverrideDPPX(&overrideDPPX), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(oldCv->GetAuthorStyleDisabled(&styleDisabled), + NS_ERROR_FAILURE); + } + } + } + + nscolor bgcolor = NS_RGBA(0, 0, 0, 0); + // Ensure that the content viewer is destroyed *after* the GC - bug 71515 + nsCOMPtr<nsIContentViewer> contentViewer = mContentViewer; + if (contentViewer) { + // Stop any activity that may be happening in the old document before + // releasing it... + contentViewer->Stop(); + + // Try to extract the canvas background color from the old + // presentation shell, so we can use it for the next document. + nsCOMPtr<nsIPresShell> shell; + contentViewer->GetPresShell(getter_AddRefs(shell)); + + if (shell) { + bgcolor = shell->GetCanvasBackground(); + } + + contentViewer->Close(mSavingOldViewer ? mOSHE.get() : nullptr); + aNewViewer->SetPreviousViewer(contentViewer); + } + if (mOSHE && (!mContentViewer || !mSavingOldViewer)) { + // We don't plan to save a viewer in mOSHE; tell it to drop + // any other state it's holding. + mOSHE->SyncPresentationState(); + } + + mContentViewer = nullptr; + + // Now that we're about to switch documents, forget all of our children. + // Note that we cached them as needed up in CaptureState above. + DestroyChildren(); + + mContentViewer = aNewViewer; + + nsCOMPtr<nsIWidget> widget; + NS_ENSURE_SUCCESS(GetMainWidget(getter_AddRefs(widget)), NS_ERROR_FAILURE); + + nsIntRect bounds(x, y, cx, cy); + + mContentViewer->SetNavigationTiming(mTiming); + + if (NS_FAILED(mContentViewer->Init(widget, bounds))) { + mContentViewer = nullptr; + NS_WARNING("ContentViewer Initialization failed"); + return NS_ERROR_FAILURE; + } + + // If we have old state to copy, set the old state onto the new content + // viewer + if (newCv) { + NS_ENSURE_SUCCESS(newCv->SetForceCharacterSet(forceCharset), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetHintCharacterSet(hintCharset), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetHintCharacterSetSource(hintCharsetSource), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetMinFontSize(minFontSize), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetTextZoom(textZoom), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetFullZoom(pageZoom), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetOverrideDPPX(overrideDPPX), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newCv->SetAuthorStyleDisabled(styleDisabled), + NS_ERROR_FAILURE); + } + + // Stuff the bgcolor from the old pres shell into the new + // pres shell. This improves page load continuity. + nsCOMPtr<nsIPresShell> shell; + mContentViewer->GetPresShell(getter_AddRefs(shell)); + + if (shell) { + shell->SetCanvasBackground(bgcolor); + } + + // XXX: It looks like the LayoutState gets restored again in Embed() + // right after the call to SetupNewViewer(...) + + // We don't show the mContentViewer yet, since we want to draw the old page + // until we have enough of the new page to show. Just return with the new + // viewer still set to hidden. + + return NS_OK; +} + +nsresult +nsDocShell::SetDocCurrentStateObj(nsISHEntry* aShEntry) +{ + NS_ENSURE_STATE(mContentViewer); + nsCOMPtr<nsIDocument> document = GetDocument(); + NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); + + nsCOMPtr<nsIStructuredCloneContainer> scContainer; + if (aShEntry) { + nsresult rv = aShEntry->GetStateData(getter_AddRefs(scContainer)); + NS_ENSURE_SUCCESS(rv, rv); + + // If aShEntry is null, just set the document's state object to null. + } + + // It's OK for scContainer too be null here; that just means there's no + // state data associated with this history entry. + document->SetStateObject(scContainer); + + return NS_OK; +} + +nsresult +nsDocShell::CheckLoadingPermissions() +{ + // This method checks whether the caller may load content into + // this docshell. Even though we've done our best to hide windows + // from code that doesn't have the right to access them, it's + // still possible for an evil site to open a window and access + // frames in the new window through window.frames[] (which is + // allAccess for historic reasons), so we still need to do this + // check on load. + nsresult rv = NS_OK; + + if (!gValidateOrigin || !IsFrame()) { + // Origin validation was turned off, or we're not a frame. + // Permit all loads. + + return rv; + } + + // Note - The check for a current JSContext here isn't necessarily sensical. + // It's just designed to preserve the old semantics during a mass-conversion + // patch. + if (!nsContentUtils::GetCurrentJSContext()) { + return NS_OK; + } + + // Check if the caller is from the same origin as this docshell, + // or any of its ancestors. + nsCOMPtr<nsIDocShellTreeItem> item(this); + do { + nsCOMPtr<nsIScriptGlobalObject> sgo = do_GetInterface(item); + nsCOMPtr<nsIScriptObjectPrincipal> sop(do_QueryInterface(sgo)); + + nsIPrincipal* p; + if (!sop || !(p = sop->GetPrincipal())) { + return NS_ERROR_UNEXPECTED; + } + + if (nsContentUtils::SubjectPrincipal()->Subsumes(p)) { + // Same origin, permit load + return NS_OK; + } + + nsCOMPtr<nsIDocShellTreeItem> tmp; + item->GetSameTypeParent(getter_AddRefs(tmp)); + item.swap(tmp); + } while (item); + + return NS_ERROR_DOM_PROP_ACCESS_DENIED; +} + +//***************************************************************************** +// nsDocShell: Site Loading +//***************************************************************************** + +namespace { + +#ifdef MOZ_PLACES +// Callback used by CopyFavicon to inform the favicon service that one URI +// (mNewURI) has the same favicon URI (OnComplete's aFaviconURI) as another. +class nsCopyFaviconCallback final : public nsIFaviconDataCallback +{ +public: + NS_DECL_ISUPPORTS + + nsCopyFaviconCallback(mozIAsyncFavicons* aSvc, + nsIURI* aNewURI, + nsIPrincipal* aLoadingPrincipal, + bool aInPrivateBrowsing) + : mSvc(aSvc) + , mNewURI(aNewURI) + , mLoadingPrincipal(aLoadingPrincipal) + , mInPrivateBrowsing(aInPrivateBrowsing) + { + } + + NS_IMETHOD + OnComplete(nsIURI* aFaviconURI, uint32_t aDataLen, + const uint8_t* aData, const nsACString& aMimeType) override + { + // Continue only if there is an associated favicon. + if (!aFaviconURI) { + return NS_OK; + } + + MOZ_ASSERT(aDataLen == 0, + "We weren't expecting the callback to deliver data."); + + nsCOMPtr<mozIPlacesPendingOperation> po; + return mSvc->SetAndFetchFaviconForPage( + mNewURI, aFaviconURI, false, + mInPrivateBrowsing ? nsIFaviconService::FAVICON_LOAD_PRIVATE : + nsIFaviconService::FAVICON_LOAD_NON_PRIVATE, + nullptr, mLoadingPrincipal, getter_AddRefs(po)); + } + +private: + ~nsCopyFaviconCallback() {} + + nsCOMPtr<mozIAsyncFavicons> mSvc; + nsCOMPtr<nsIURI> mNewURI; + nsCOMPtr<nsIPrincipal> mLoadingPrincipal; + bool mInPrivateBrowsing; +}; + +NS_IMPL_ISUPPORTS(nsCopyFaviconCallback, nsIFaviconDataCallback) +#endif + +} // namespace + +void +nsDocShell::CopyFavicon(nsIURI* aOldURI, + nsIURI* aNewURI, + nsIPrincipal* aLoadingPrincipal, + bool aInPrivateBrowsing) +{ + if (XRE_IsContentProcess()) { + dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); + if (contentChild) { + mozilla::ipc::URIParams oldURI, newURI; + SerializeURI(aOldURI, oldURI); + SerializeURI(aNewURI, newURI); + contentChild->SendCopyFavicon(oldURI, newURI, + IPC::Principal(aLoadingPrincipal), + aInPrivateBrowsing); + } + return; + } + +#ifdef MOZ_PLACES + nsCOMPtr<mozIAsyncFavicons> favSvc = + do_GetService("@mozilla.org/browser/favicon-service;1"); + if (favSvc) { + nsCOMPtr<nsIFaviconDataCallback> callback = + new nsCopyFaviconCallback(favSvc, aNewURI, + aLoadingPrincipal, + aInPrivateBrowsing); + favSvc->GetFaviconURLForPage(aOldURI, callback); + } +#endif +} + +class InternalLoadEvent : public Runnable +{ +public: + InternalLoadEvent(nsDocShell* aDocShell, nsIURI* aURI, + nsIURI* aOriginalURI, bool aLoadReplace, + nsIURI* aReferrer, uint32_t aReferrerPolicy, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, uint32_t aFlags, + const char* aTypeHint, nsIInputStream* aPostData, + nsIInputStream* aHeadersData, uint32_t aLoadType, + nsISHEntry* aSHEntry, bool aFirstParty, + const nsAString& aSrcdoc, nsIDocShell* aSourceDocShell, + nsIURI* aBaseURI) + : mSrcdoc(aSrcdoc) + , mDocShell(aDocShell) + , mURI(aURI) + , mOriginalURI(aOriginalURI) + , mLoadReplace(aLoadReplace) + , mReferrer(aReferrer) + , mReferrerPolicy(aReferrerPolicy) + , mTriggeringPrincipal(aTriggeringPrincipal) + , mPrincipalToInherit(aPrincipalToInherit) + , mPostData(aPostData) + , mHeadersData(aHeadersData) + , mSHEntry(aSHEntry) + , mFlags(aFlags) + , mLoadType(aLoadType) + , mFirstParty(aFirstParty) + , mSourceDocShell(aSourceDocShell) + , mBaseURI(aBaseURI) + { + // Make sure to keep null things null as needed + if (aTypeHint) { + mTypeHint = aTypeHint; + } + } + + NS_IMETHOD + Run() override + { + return mDocShell->InternalLoad(mURI, mOriginalURI, + mLoadReplace, + mReferrer, + mReferrerPolicy, + mTriggeringPrincipal, mPrincipalToInherit, + mFlags, EmptyString(), mTypeHint.get(), + NullString(), mPostData, mHeadersData, + mLoadType, mSHEntry, mFirstParty, + mSrcdoc, mSourceDocShell, mBaseURI, + nullptr, nullptr); + } + +private: + // Use IDL strings so .get() returns null by default + nsXPIDLString mWindowTarget; + nsXPIDLCString mTypeHint; + nsString mSrcdoc; + + RefPtr<nsDocShell> mDocShell; + nsCOMPtr<nsIURI> mURI; + nsCOMPtr<nsIURI> mOriginalURI; + bool mLoadReplace; + nsCOMPtr<nsIURI> mReferrer; + uint32_t mReferrerPolicy; + nsCOMPtr<nsIPrincipal> mTriggeringPrincipal; + nsCOMPtr<nsIPrincipal> mPrincipalToInherit; + nsCOMPtr<nsIInputStream> mPostData; + nsCOMPtr<nsIInputStream> mHeadersData; + nsCOMPtr<nsISHEntry> mSHEntry; + uint32_t mFlags; + uint32_t mLoadType; + bool mFirstParty; + nsCOMPtr<nsIDocShell> mSourceDocShell; + nsCOMPtr<nsIURI> mBaseURI; +}; + +/** + * Returns true if we started an asynchronous load (i.e., from the network), but + * the document we're loading there hasn't yet become this docshell's active + * document. + * + * When JustStartedNetworkLoad is true, you should be careful about modifying + * mLoadType and mLSHE. These are both set when the asynchronous load first + * starts, and the load expects that, when it eventually runs InternalLoad, + * mLoadType and mLSHE will have their original values. + */ +bool +nsDocShell::JustStartedNetworkLoad() +{ + return mDocumentRequest && mDocumentRequest != GetCurrentDocChannel(); +} + +nsresult +nsDocShell::CreatePrincipalFromReferrer(nsIURI* aReferrer, + nsIPrincipal** aResult) +{ + PrincipalOriginAttributes attrs; + attrs.InheritFromDocShellToDoc(mOriginAttributes, aReferrer); + nsCOMPtr<nsIPrincipal> prin = + BasePrincipal::CreateCodebasePrincipal(aReferrer, attrs); + prin.forget(aResult); + + return *aResult ? NS_OK : NS_ERROR_FAILURE; +} + +bool +nsDocShell::IsAboutNewtab(nsIURI* aURI) +{ + if (!aURI) { + return false; + } + bool isAbout; + if (NS_WARN_IF(NS_FAILED(aURI->SchemeIs("about", &isAbout)))) { + return false; + } + if (!isAbout) { + return false; + } + + nsAutoCString module; + if (NS_WARN_IF(NS_FAILED(NS_GetAboutModuleName(aURI, module)))) { + return false; + } + return module.Equals("newtab"); +} + +NS_IMETHODIMP +nsDocShell::InternalLoad(nsIURI* aURI, + nsIURI* aOriginalURI, + bool aLoadReplace, + nsIURI* aReferrer, + uint32_t aReferrerPolicy, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + uint32_t aFlags, + const nsAString& aWindowTarget, + const char* aTypeHint, + const nsAString& aFileName, + nsIInputStream* aPostData, + nsIInputStream* aHeadersData, + uint32_t aLoadType, + nsISHEntry* aSHEntry, + bool aFirstParty, + const nsAString& aSrcdoc, + nsIDocShell* aSourceDocShell, + nsIURI* aBaseURI, + nsIDocShell** aDocShell, + nsIRequest** aRequest) +{ + MOZ_ASSERT(aTriggeringPrincipal, "need a valid TriggeringPrincipal"); + + nsresult rv = NS_OK; + mOriginalUriString.Truncate(); + + if (gDocShellLeakLog && MOZ_LOG_TEST(gDocShellLeakLog, LogLevel::Debug)) { + PR_LogPrint("DOCSHELL %p InternalLoad %s\n", + this, aURI ? aURI->GetSpecOrDefault().get() : ""); + } + // Initialize aDocShell/aRequest + if (aDocShell) { + *aDocShell = nullptr; + } + if (aRequest) { + *aRequest = nullptr; + } + + if (!aURI) { + return NS_ERROR_NULL_POINTER; + } + + NS_ENSURE_TRUE(IsValidLoadType(aLoadType), NS_ERROR_INVALID_ARG); + + NS_ENSURE_TRUE(!mIsBeingDestroyed, NS_ERROR_NOT_AVAILABLE); + + rv = EnsureScriptEnvironment(); + if (NS_FAILED(rv)) { + return rv; + } + + // wyciwyg urls can only be loaded through history. Any normal load of + // wyciwyg through docshell is illegal. Disallow such loads. + if (aLoadType & LOAD_CMD_NORMAL) { + bool isWyciwyg = false; + rv = aURI->SchemeIs("wyciwyg", &isWyciwyg); + if ((isWyciwyg && NS_SUCCEEDED(rv)) || NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + } + + bool isJavaScript = false; + if (NS_FAILED(aURI->SchemeIs("javascript", &isJavaScript))) { + isJavaScript = false; + } + + bool isTargetTopLevelDocShell = false; + nsCOMPtr<nsIDocShell> targetDocShell; + if (!aWindowTarget.IsEmpty()) { + // Locate the target DocShell. + nsCOMPtr<nsIDocShellTreeItem> targetItem; + // Only _self, _parent, and _top are supported in noopener case. But we + // have to be careful to not apply that to the noreferrer case. See bug + // 1358469. + bool allowNamedTarget = !(aFlags & INTERNAL_LOAD_FLAGS_NO_OPENER) || + (aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER); + if (allowNamedTarget || + aWindowTarget.LowerCaseEqualsLiteral("_self") || + aWindowTarget.LowerCaseEqualsLiteral("_parent") || + aWindowTarget.LowerCaseEqualsLiteral("_top")) { + rv = FindItemWithName(aWindowTarget, nullptr, this, + getter_AddRefs(targetItem)); + NS_ENSURE_SUCCESS(rv, rv); + } + + targetDocShell = do_QueryInterface(targetItem); + if (targetDocShell) { + // If the targetDocShell and the rootDocShell are the same, then the + // targetDocShell is the top level document and hence we should + // consider this TYPE_DOCUMENT + // + // For example: + // 1. target="_top" + // 2. target="_parent", where this docshell is in the 2nd level of + // docshell tree. + nsCOMPtr<nsIDocShellTreeItem> sameTypeRoot; + targetDocShell->GetSameTypeRootTreeItem(getter_AddRefs(sameTypeRoot)); + NS_ASSERTION(sameTypeRoot, + "No document shell root tree item from targetDocShell!"); + nsCOMPtr<nsIDocShell> rootShell = do_QueryInterface(sameTypeRoot); + NS_ASSERTION(rootShell, + "No root docshell from document shell root tree item."); + isTargetTopLevelDocShell = targetDocShell == rootShell; + } else { + // If the targetDocShell doesn't exist, then this is a new docShell + // and we should consider this a TYPE_DOCUMENT load + // + // For example, when target="_blank" + isTargetTopLevelDocShell = true; + } + } + + // The contentType will be INTERNAL_(I)FRAME if: + // 1. This docshell is for iframe. + // 2. AND aWindowTarget is not a new window, nor a top-level window. + // + // This variable will be used when we call NS_CheckContentLoadPolicy, and + // later when we call DoURILoad. + uint32_t contentType; + if (IsFrame() && !isTargetTopLevelDocShell) { + nsCOMPtr<Element> requestingElement = + mScriptGlobal->AsOuter()->GetFrameElementInternal(); + if (requestingElement) { + contentType = requestingElement->IsHTMLElement(nsGkAtoms::iframe) ? + nsIContentPolicy::TYPE_INTERNAL_IFRAME : nsIContentPolicy::TYPE_INTERNAL_FRAME; + } else { + // If we have lost our frame element by now, just assume we're + // an iframe since that's more common. + contentType = nsIContentPolicy::TYPE_INTERNAL_IFRAME; + } + } else { + contentType = nsIContentPolicy::TYPE_DOCUMENT; + } + + // If there's no targetDocShell, that means we are about to create a new window, + // perform a content policy check before creating the window. + if (!targetDocShell) { + nsCOMPtr<Element> requestingElement; + nsISupports* requestingContext = nullptr; + + if (contentType == nsIContentPolicy::TYPE_DOCUMENT) { + if (XRE_IsContentProcess()) { + // In e10s the child process doesn't have access to the element that + // contains the browsing context (because that element is in the chrome + // process). So we just pass mScriptGlobal. + requestingContext = ToSupports(mScriptGlobal); + } else { + // This is for loading non-e10s tabs and toplevel windows of various + // sorts. + // For the toplevel window cases, requestingElement will be null. + requestingElement = mScriptGlobal->AsOuter()->GetFrameElementInternal(); + requestingContext = requestingElement; + } + } else { + requestingElement = mScriptGlobal->AsOuter()->GetFrameElementInternal(); + requestingContext = requestingElement; + +#ifdef DEBUG + if (requestingElement) { + // Get the docshell type for requestingElement. + nsCOMPtr<nsIDocument> requestingDoc = requestingElement->OwnerDoc(); + nsCOMPtr<nsIDocShell> elementDocShell = requestingDoc->GetDocShell(); + + // requestingElement docshell type = current docshell type. + MOZ_ASSERT(mItemType == elementDocShell->ItemType(), + "subframes should have the same docshell type as their parent"); + } +#endif + } + + int16_t shouldLoad = nsIContentPolicy::ACCEPT; + rv = NS_CheckContentLoadPolicy(contentType, + aURI, + aTriggeringPrincipal, + requestingContext, + EmptyCString(), // mime guess + nullptr, // extra + &shouldLoad); + + if (NS_FAILED(rv) || NS_CP_REJECTED(shouldLoad)) { + if (NS_SUCCEEDED(rv) && shouldLoad == nsIContentPolicy::REJECT_TYPE) { + return NS_ERROR_CONTENT_BLOCKED_SHOW_ALT; + } + + return NS_ERROR_CONTENT_BLOCKED; + } + + // If HSTS priming was set by nsMixedContentBlocker::ShouldLoad, and we + // would block due to mixed content, go ahead and block here. If we try to + // proceed with priming, we will error out later on. + nsCOMPtr<nsIDocShell> docShell = NS_CP_GetDocShellFromContext(requestingContext); + // When loading toplevel windows, requestingContext can be null. We don't + // really care about HSTS in that situation, though; loads in toplevel + // windows should all be browser UI. + if (docShell) { + nsIDocument* document = docShell->GetDocument(); + NS_ENSURE_TRUE(document, NS_OK); + + HSTSPrimingState state = document->GetHSTSPrimingStateForLocation(aURI); + if (state == HSTSPrimingState::eHSTS_PRIMING_BLOCK) { + // HSTS Priming currently disabled for InternalLoad, so we need to clear + // the location that was added by nsMixedContentBlocker::ShouldLoad + // Bug 1269815 will address images loaded via InternalLoad + document->ClearHSTSPrimingLocation(aURI); + return NS_ERROR_CONTENT_BLOCKED; + } + } + } + + nsCOMPtr<nsIPrincipal> principalToInherit = aPrincipalToInherit; + // + // Get a principal from the current document if necessary. Note that we only + // do this for URIs that inherit a security context and local file URIs; + // in particular we do NOT do this for about:blank. This way, random + // about:blank loads that have no principal (which basically means they were + // done by someone from chrome manually messing with our nsIWebNavigation + // or by C++ setting document.location) don't get a funky principal. If + // callers want something interesting to happen with the about:blank + // principal in this case, they should pass aPrincipalToInherit in. + // + { + bool inherits; + // One more twist: Don't inherit the principal for external loads. + if (aLoadType != LOAD_NORMAL_EXTERNAL && !principalToInherit && + (aFlags & INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL) && + NS_SUCCEEDED(nsContentUtils::URIInheritsSecurityContext(aURI, + &inherits)) && + inherits) { + principalToInherit = GetInheritedPrincipal(true); + } + } + + // Don't allow loads that would inherit our security context + // if this document came from an unsafe channel. + { + bool willInherit; + // This condition needs to match the one in + // nsContentUtils::ChannelShouldInheritPrincipal. + // Except we reverse the rv check to be safe in case + // nsContentUtils::URIInheritsSecurityContext fails here and + // succeeds there. + rv = nsContentUtils::URIInheritsSecurityContext(aURI, &willInherit); + if (NS_FAILED(rv) || willInherit || NS_IsAboutBlank(aURI)) { + nsCOMPtr<nsIDocShellTreeItem> treeItem = this; + do { + nsCOMPtr<nsIDocShell> itemDocShell = do_QueryInterface(treeItem); + bool isUnsafe; + if (itemDocShell && + NS_SUCCEEDED(itemDocShell->GetChannelIsUnsafe(&isUnsafe)) && + isUnsafe) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsCOMPtr<nsIDocShellTreeItem> parent; + treeItem->GetSameTypeParent(getter_AddRefs(parent)); + parent.swap(treeItem); + } while (treeItem); + } + } + + // + // Resolve the window target before going any further... + // If the load has been targeted to another DocShell, then transfer the + // load to it... + // + if (!aWindowTarget.IsEmpty()) { + // We've already done our owner-inheriting. Mask out that bit, so we + // don't try inheriting an owner from the target window if we came up + // with a null owner above. + aFlags = aFlags & ~INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL; + + bool isNewWindow = false; + if (!targetDocShell) { + // If the docshell's document is sandboxed, only open a new window + // if the document's SANDBOXED_AUXILLARY_NAVIGATION flag is not set. + // (i.e. if allow-popups is specified) + NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE); + nsIDocument* doc = mContentViewer->GetDocument(); + uint32_t sandboxFlags = 0; + + if (doc) { + sandboxFlags = doc->GetSandboxFlags(); + if (sandboxFlags & SANDBOXED_AUXILIARY_NAVIGATION) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + } + + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + NS_ENSURE_TRUE(win, NS_ERROR_NOT_AVAILABLE); + + nsCOMPtr<nsPIDOMWindowOuter> newWin; + nsAutoCString spec; + if (aURI) { + aURI->GetSpec(spec); + } + // If we are a noopener load, we just hand the whole thing over to our + // window. + if (aFlags & INTERNAL_LOAD_FLAGS_NO_OPENER) { + // Various asserts that we know to hold because NO_OPENER loads can only + // happen for links. + MOZ_ASSERT(!aLoadReplace); + MOZ_ASSERT(aPrincipalToInherit == aTriggeringPrincipal); + MOZ_ASSERT(aFlags == INTERNAL_LOAD_FLAGS_NO_OPENER || + aFlags == (INTERNAL_LOAD_FLAGS_NO_OPENER | + INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER)); + MOZ_ASSERT(!aPostData); + MOZ_ASSERT(!aHeadersData); + MOZ_ASSERT(aLoadType == LOAD_LINK); + MOZ_ASSERT(!aSHEntry); + MOZ_ASSERT(aFirstParty); // Windowwatcher will assume this. + + nsCOMPtr<nsIDocShellLoadInfo> loadInfo; + rv = CreateLoadInfo(getter_AddRefs(loadInfo)); + if (NS_FAILED(rv)) { + return rv; + } + + // Set up our loadinfo so it will do the load as much like we would have + // as possible. + loadInfo->SetReferrer(aReferrer); + loadInfo->SetReferrerPolicy(aReferrerPolicy); + loadInfo->SetSendReferrer(!(aFlags & + INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER)); + loadInfo->SetOriginalURI(aOriginalURI); + loadInfo->SetLoadReplace(aLoadReplace); + loadInfo->SetTriggeringPrincipal(aTriggeringPrincipal); + loadInfo->SetInheritPrincipal( + aFlags & INTERNAL_LOAD_FLAGS_INHERIT_PRINCIPAL); + // Explicit principal because we do not want any guesses as to what the + // principal to inherit is: it should be aTriggeringPrincipal. + loadInfo->SetPrincipalIsExplicit(true); + loadInfo->SetLoadType(ConvertLoadTypeToDocShellLoadInfo(LOAD_LINK)); + + rv = win->Open(NS_ConvertUTF8toUTF16(spec), + aWindowTarget, // window name + EmptyString(), // Features + loadInfo, + true, // aForceNoOpener + getter_AddRefs(newWin)); + MOZ_ASSERT(!newWin); + return rv; + } + + rv = win->OpenNoNavigate(NS_ConvertUTF8toUTF16(spec), + aWindowTarget, // window name + EmptyString(), // Features + getter_AddRefs(newWin)); + + // In some cases the Open call doesn't actually result in a new + // window being opened. We can detect these cases by examining the + // document in |newWin|, if any. + nsCOMPtr<nsPIDOMWindowOuter> piNewWin = do_QueryInterface(newWin); + if (piNewWin) { + nsCOMPtr<nsIDocument> newDoc = piNewWin->GetExtantDoc(); + if (!newDoc || newDoc->IsInitialDocument()) { + isNewWindow = true; + aFlags |= INTERNAL_LOAD_FLAGS_FIRST_LOAD; + } + } + + nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(newWin); + targetDocShell = do_QueryInterface(webNav); + } + + // + // Transfer the load to the target DocShell... Pass nullptr as the + // window target name from to prevent recursive retargeting! + // + if (NS_SUCCEEDED(rv) && targetDocShell) { + rv = targetDocShell->InternalLoad(aURI, + aOriginalURI, + aLoadReplace, + aReferrer, + aReferrerPolicy, + aTriggeringPrincipal, + principalToInherit, + aFlags, + EmptyString(), // No window target + aTypeHint, + NullString(), // No forced download + aPostData, + aHeadersData, + aLoadType, + aSHEntry, + aFirstParty, + aSrcdoc, + aSourceDocShell, + aBaseURI, + aDocShell, + aRequest); + if (rv == NS_ERROR_NO_CONTENT) { + // XXXbz except we never reach this code! + if (isNewWindow) { + // + // At this point, a new window has been created, but the + // URI did not have any data associated with it... + // + // So, the best we can do, is to tear down the new window + // that was just created! + // + if (nsCOMPtr<nsPIDOMWindowOuter> domWin = targetDocShell->GetWindow()) { + domWin->Close(); + } + } + // + // NS_ERROR_NO_CONTENT should not be returned to the + // caller... This is an internal error code indicating that + // the URI had no data associated with it - probably a + // helper-app style protocol (ie. mailto://) + // + rv = NS_OK; + } else if (isNewWindow) { + // XXX: Once new windows are created hidden, the new + // window will need to be made visible... For now, + // do nothing. + } + } + + // Else we ran out of memory, or were a popup and got blocked, + // or something. + + return rv; + } + + // + // Load is being targetted at this docshell so return an error if the + // docshell is in the process of being destroyed. + // + if (mIsBeingDestroyed) { + return NS_ERROR_FAILURE; + } + + NS_ENSURE_STATE(!HasUnloadedParent()); + + rv = CheckLoadingPermissions(); + if (NS_FAILED(rv)) { + return rv; + } + + if (mFiredUnloadEvent) { + if (IsOKToLoadURI(aURI)) { + NS_PRECONDITION(aWindowTarget.IsEmpty(), + "Shouldn't have a window target here!"); + + // If this is a replace load, make whatever load triggered + // the unload event also a replace load, so we don't + // create extra history entries. + if (LOAD_TYPE_HAS_FLAGS(aLoadType, LOAD_FLAGS_REPLACE_HISTORY)) { + mLoadType = LOAD_NORMAL_REPLACE; + } + + // Do this asynchronously + nsCOMPtr<nsIRunnable> ev = + new InternalLoadEvent(this, aURI, aOriginalURI, aLoadReplace, + aReferrer, aReferrerPolicy, + aTriggeringPrincipal, principalToInherit, + aFlags, aTypeHint, aPostData, aHeadersData, + aLoadType, aSHEntry, aFirstParty, aSrcdoc, + aSourceDocShell, aBaseURI); + return NS_DispatchToCurrentThread(ev); + } + + // Just ignore this load attempt + return NS_OK; + } + + // If a source docshell has been passed, check to see if we are sandboxed + // from it as the result of an iframe or CSP sandbox. + if (aSourceDocShell && aSourceDocShell->IsSandboxedFrom(this)) { + return NS_ERROR_DOM_INVALID_ACCESS_ERR; + } + + // If this docshell is owned by a frameloader, make sure to cancel + // possible frameloader initialization before loading a new page. + nsCOMPtr<nsIDocShellTreeItem> parent = GetParentDocshell(); + if (parent) { + nsCOMPtr<nsIDocument> doc = parent->GetDocument(); + if (doc) { + doc->TryCancelFrameLoaderInitialization(this); + } + } + + // Before going any further vet loads initiated by external programs. + if (aLoadType == LOAD_NORMAL_EXTERNAL) { + // Disallow external chrome: loads targetted at content windows + bool isChrome = false; + if (NS_SUCCEEDED(aURI->SchemeIs("chrome", &isChrome)) && isChrome) { + NS_WARNING("blocked external chrome: url -- use '--chrome' option"); + return NS_ERROR_FAILURE; + } + + // clear the decks to prevent context bleed-through (bug 298255) + rv = CreateAboutBlankContentViewer(nullptr, nullptr); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + // reset loadType so we don't have to add lots of tests for + // LOAD_NORMAL_EXTERNAL after this point + aLoadType = LOAD_NORMAL; + } + + mAllowKeywordFixup = + (aFlags & INTERNAL_LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP) != 0; + mURIResultedInDocument = false; // reset the clock... + + if (aLoadType == LOAD_NORMAL || + aLoadType == LOAD_STOP_CONTENT || + LOAD_TYPE_HAS_FLAGS(aLoadType, LOAD_FLAGS_REPLACE_HISTORY) || + aLoadType == LOAD_HISTORY || + aLoadType == LOAD_LINK) { + nsCOMPtr<nsIURI> currentURI = mCurrentURI; + + nsAutoCString curHash, newHash; + bool curURIHasRef = false, newURIHasRef = false; + + nsresult rvURINew = aURI->GetRef(newHash); + if (NS_SUCCEEDED(rvURINew)) { + rvURINew = aURI->GetHasRef(&newURIHasRef); + } + + bool sameExceptHashes = false; + if (currentURI && NS_SUCCEEDED(rvURINew)) { + nsresult rvURIOld = currentURI->GetRef(curHash); + if (NS_SUCCEEDED(rvURIOld)) { + rvURIOld = currentURI->GetHasRef(&curURIHasRef); + } + if (NS_SUCCEEDED(rvURIOld)) { + if (NS_FAILED(currentURI->EqualsExceptRef(aURI, &sameExceptHashes))) { + sameExceptHashes = false; + } + } + } + + if (!sameExceptHashes && sURIFixup && currentURI && + NS_SUCCEEDED(rvURINew)) { + // Maybe aURI came from the exposable form of currentURI? + nsCOMPtr<nsIURI> currentExposableURI; + rv = sURIFixup->CreateExposableURI(currentURI, + getter_AddRefs(currentExposableURI)); + NS_ENSURE_SUCCESS(rv, rv); + nsresult rvURIOld = currentExposableURI->GetRef(curHash); + if (NS_SUCCEEDED(rvURIOld)) { + rvURIOld = currentExposableURI->GetHasRef(&curURIHasRef); + } + if (NS_SUCCEEDED(rvURIOld)) { + if (NS_FAILED(currentExposableURI->EqualsExceptRef(aURI, &sameExceptHashes))) { + sameExceptHashes = false; + } + } + } + + bool historyNavBetweenSameDoc = false; + if (mOSHE && aSHEntry) { + // We're doing a history load. + + mOSHE->SharesDocumentWith(aSHEntry, &historyNavBetweenSameDoc); + +#ifdef DEBUG + if (historyNavBetweenSameDoc) { + nsCOMPtr<nsIInputStream> currentPostData; + mOSHE->GetPostData(getter_AddRefs(currentPostData)); + NS_ASSERTION(currentPostData == aPostData, + "Different POST data for entries for the same page?"); + } +#endif + } + + // A short-circuited load happens when we navigate between two SHEntries + // for the same document. We do a short-circuited load under two + // circumstances. Either + // + // a) we're navigating between two different SHEntries which share a + // document, or + // + // b) we're navigating to a new shentry whose URI differs from the + // current URI only in its hash, the new hash is non-empty, and + // we're not doing a POST. + // + // The restriction tha the SHEntries in (a) must be different ensures + // that history.go(0) and the like trigger full refreshes, rather than + // short-circuited loads. + bool doShortCircuitedLoad = + (historyNavBetweenSameDoc && mOSHE != aSHEntry) || + (!aSHEntry && !aPostData && + sameExceptHashes && newURIHasRef); + + if (doShortCircuitedLoad) { + // Save the position of the scrollers. + nscoord cx = 0, cy = 0; + GetCurScrollPos(ScrollOrientation_X, &cx); + GetCurScrollPos(ScrollOrientation_Y, &cy); + + // Reset mLoadType to its original value once we exit this block, + // because this short-circuited load might have started after a + // normal, network load, and we don't want to clobber its load type. + // See bug 737307. + AutoRestore<uint32_t> loadTypeResetter(mLoadType); + + // If a non-short-circuit load (i.e., a network load) is pending, + // make this a replacement load, so that we don't add a SHEntry here + // and the network load goes into the SHEntry it expects to. + if (JustStartedNetworkLoad() && (aLoadType & LOAD_CMD_NORMAL)) { + mLoadType = LOAD_NORMAL_REPLACE; + } else { + mLoadType = aLoadType; + } + + mURIResultedInDocument = true; + + nsCOMPtr<nsISHEntry> oldLSHE = mLSHE; + + /* we need to assign mLSHE to aSHEntry right here, so that on History + * loads, SetCurrentURI() called from OnNewURI() will send proper + * onLocationChange() notifications to the browser to update + * back/forward buttons. + */ + SetHistoryEntry(&mLSHE, aSHEntry); + + // Set the doc's URI according to the new history entry's URI. + nsCOMPtr<nsIDocument> doc = GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + doc->SetDocumentURI(aURI); + + /* This is a anchor traversal with in the same page. + * call OnNewURI() so that, this traversal will be + * recorded in session and global history. + */ + nsCOMPtr<nsIPrincipal> triggeringPrincipal, principalToInherit; + if (mOSHE) { + mOSHE->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal)); + mOSHE->GetPrincipalToInherit(getter_AddRefs(principalToInherit)); + } + // Pass true for aCloneSHChildren, since we're not + // changing documents here, so all of our subframes are + // still relevant to the new session history entry. + // + // It also makes OnNewURI(...) set LOCATION_CHANGE_SAME_DOCUMENT + // flag on firing onLocationChange(...). + // Anyway, aCloneSHChildren param is simply reflecting + // doShortCircuitedLoad in this scope. + OnNewURI(aURI, nullptr, triggeringPrincipal, principalToInherit, + mLoadType, true, true, true); + + nsCOMPtr<nsIInputStream> postData; + nsCOMPtr<nsISupports> cacheKey; + + bool scrollRestorationIsManual = false; + if (mOSHE) { + /* save current position of scroller(s) (bug 59774) */ + mOSHE->SetScrollPosition(cx, cy); + mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual); + // Get the postdata and page ident from the current page, if + // the new load is being done via normal means. Note that + // "normal means" can be checked for just by checking for + // LOAD_CMD_NORMAL, given the loadType and allowScroll check + // above -- it filters out some LOAD_CMD_NORMAL cases that we + // wouldn't want here. + if (aLoadType & LOAD_CMD_NORMAL) { + mOSHE->GetPostData(getter_AddRefs(postData)); + mOSHE->GetCacheKey(getter_AddRefs(cacheKey)); + + // Link our new SHEntry to the old SHEntry's back/forward + // cache data, since the two SHEntries correspond to the + // same document. + if (mLSHE) { + if (!aSHEntry) { + // If we're not doing a history load, scroll restoration + // should be inherited from the previous session history entry. + mLSHE->SetScrollRestorationIsManual(scrollRestorationIsManual); + } + mLSHE->AdoptBFCacheEntry(mOSHE); + } + } + } + + // If we're doing a history load, use its scroll restoration state. + if (aSHEntry) { + aSHEntry->GetScrollRestorationIsManual(&scrollRestorationIsManual); + } + + /* Assign mOSHE to mLSHE. This will either be a new entry created + * by OnNewURI() for normal loads or aSHEntry for history loads. + */ + if (mLSHE) { + SetHistoryEntry(&mOSHE, mLSHE); + // Save the postData obtained from the previous page + // in to the session history entry created for the + // anchor page, so that any history load of the anchor + // page will restore the appropriate postData. + if (postData) { + mOSHE->SetPostData(postData); + } + + // Make sure we won't just repost without hitting the + // cache first + if (cacheKey) { + mOSHE->SetCacheKey(cacheKey); + } + } + + /* Restore the original LSHE if we were loading something + * while short-circuited load was initiated. + */ + SetHistoryEntry(&mLSHE, oldLSHE); + /* Set the title for the SH entry for this target url. so that + * SH menus in go/back/forward buttons won't be empty for this. + */ + if (mSessionHistory) { + int32_t index = -1; + mSessionHistory->GetIndex(&index); + nsCOMPtr<nsISHEntry> shEntry; + mSessionHistory->GetEntryAtIndex(index, false, getter_AddRefs(shEntry)); + NS_ENSURE_TRUE(shEntry, NS_ERROR_FAILURE); + shEntry->SetTitle(mTitle); + } + + /* Set the title for the Global History entry for this anchor url. + */ + if (mUseGlobalHistory && !UsePrivateBrowsing()) { + nsCOMPtr<IHistory> history = services::GetHistoryService(); + if (history) { + history->SetURITitle(aURI, mTitle); + } else if (mGlobalHistory) { + mGlobalHistory->SetPageTitle(aURI, mTitle); + } + } + + SetDocCurrentStateObj(mOSHE); + + // Inform the favicon service that the favicon for oldURI also + // applies to aURI. + CopyFavicon(currentURI, aURI, doc->NodePrincipal(), UsePrivateBrowsing()); + + RefPtr<nsGlobalWindow> scriptGlobal = mScriptGlobal; + RefPtr<nsGlobalWindow> win = scriptGlobal ? + scriptGlobal->GetCurrentInnerWindowInternal() : nullptr; + + // ScrollToAnchor doesn't necessarily cause us to scroll the window; + // the function decides whether a scroll is appropriate based on the + // arguments it receives. But even if we don't end up scrolling, + // ScrollToAnchor performs other important tasks, such as informing + // the presShell that we have a new hash. See bug 680257. + rv = ScrollToAnchor(curURIHasRef, newURIHasRef, newHash, aLoadType); + NS_ENSURE_SUCCESS(rv, rv); + + /* restore previous position of scroller(s), if we're moving + * back in history (bug 59774) + */ + nscoord bx = 0; + nscoord by = 0; + bool needsScrollPosUpdate = false; + if (mOSHE && (aLoadType == LOAD_HISTORY || + aLoadType == LOAD_RELOAD_NORMAL) && + !scrollRestorationIsManual) { + needsScrollPosUpdate = true; + mOSHE->GetScrollPosition(&bx, &by); + } + + // Dispatch the popstate and hashchange events, as appropriate. + // + // The event dispatch below can cause us to re-enter script and + // destroy the docshell, nulling out mScriptGlobal. Hold a stack + // reference to avoid null derefs. See bug 914521. + if (win) { + // Fire a hashchange event URIs differ, and only in their hashes. + bool doHashchange = sameExceptHashes && + (curURIHasRef != newURIHasRef || !curHash.Equals(newHash)); + + if (historyNavBetweenSameDoc || doHashchange) { + win->DispatchSyncPopState(); + } + + if (needsScrollPosUpdate && win->AsInner()->HasActiveDocument()) { + SetCurScrollPosEx(bx, by); + } + + if (doHashchange) { + // Note that currentURI hasn't changed because it's on the + // stack, so we can just use it directly as the old URI. + win->DispatchAsyncHashchange(currentURI, aURI); + } + } + + return NS_OK; + } + } + + // mContentViewer->PermitUnload can destroy |this| docShell, which + // causes the next call of CanSavePresentation to crash. + // Hold onto |this| until we return, to prevent a crash from happening. + // (bug#331040) + nsCOMPtr<nsIDocShell> kungFuDeathGrip(this); + + // Don't init timing for javascript:, since it generally doesn't + // actually start a load or anything. If it does, we'll init + // timing then, from OnStateChange. + + // XXXbz mTiming should know what channel it's for, so we don't + // need this hackery. Note that this is still broken in cases + // when we're loading something that's not javascript: and the + // beforeunload handler denies the load. That will screw up + // timing for the next load! + if (!isJavaScript) { + MaybeInitTiming(); + } + bool timeBeforeUnload = aFileName.IsVoid(); + if (mTiming && timeBeforeUnload) { + mTiming->NotifyBeforeUnload(); + } + // Check if the page doesn't want to be unloaded. The javascript: + // protocol handler deals with this for javascript: URLs. + if (!isJavaScript && aFileName.IsVoid() && mContentViewer) { + bool okToUnload; + rv = mContentViewer->PermitUnload(&okToUnload); + + if (NS_SUCCEEDED(rv) && !okToUnload) { + // The user chose not to unload the page, interrupt the + // load. + return NS_OK; + } + } + + if (mTiming && timeBeforeUnload) { + mTiming->NotifyUnloadAccepted(mCurrentURI); + } + + // Check if the webbrowser chrome wants the load to proceed; this can be + // used to cancel attempts to load URIs in the wrong process. + nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner); + if (browserChrome3) { + // In case this is a remote newtab load, set aURI to aOriginalURI (newtab). + // This ensures that the verifySignedContent flag is set on loadInfo in + // DoURILoad. + nsIURI* uriForShouldLoadCheck = aURI; + if (IsAboutNewtab(aOriginalURI)) { + uriForShouldLoadCheck = aOriginalURI; + } + bool shouldLoad; + rv = browserChrome3->ShouldLoadURI(this, uriForShouldLoadCheck, aReferrer, + &shouldLoad); + if (NS_SUCCEEDED(rv) && !shouldLoad) { + return NS_OK; + } + } + + // Whenever a top-level browsing context is navigated, the user agent MUST + // lock the orientation of the document to the document's default + // orientation. We don't explicitly check for a top-level browsing context + // here because orientation is only set on top-level browsing contexts. + // We make an exception for apps because they currently rely on + // orientation locks persisting across browsing contexts. + if (OrientationLock() != eScreenOrientation_None && !GetIsApp()) { +#ifdef DEBUG + nsCOMPtr<nsIDocShellTreeItem> parent; + GetSameTypeParent(getter_AddRefs(parent)); + MOZ_ASSERT(!parent); +#endif + SetOrientationLock(eScreenOrientation_None); + if (mIsActive) { + ScreenOrientation::UpdateActiveOrientationLock(eScreenOrientation_None); + } + } + + // Check for saving the presentation here, before calling Stop(). + // This is necessary so that we can catch any pending requests. + // Since the new request has not been created yet, we pass null for the + // new request parameter. + // Also pass nullptr for the document, since it doesn't affect the return + // value for our purposes here. + bool savePresentation = CanSavePresentation(aLoadType, nullptr, nullptr); + + // Don't stop current network activity for javascript: URL's since + // they might not result in any data, and thus nothing should be + // stopped in those cases. In the case where they do result in + // data, the javascript: URL channel takes care of stopping + // current network activity. + if (!isJavaScript && aFileName.IsVoid()) { + // Stop any current network activity. + // Also stop content if this is a zombie doc. otherwise + // the onload will be delayed by other loads initiated in the + // background by the first document that + // didn't fully load before the next load was initiated. + // If not a zombie, don't stop content until data + // starts arriving from the new URI... + + nsCOMPtr<nsIContentViewer> zombieViewer; + if (mContentViewer) { + mContentViewer->GetPreviousViewer(getter_AddRefs(zombieViewer)); + } + + if (zombieViewer || + LOAD_TYPE_HAS_FLAGS(aLoadType, LOAD_FLAGS_STOP_CONTENT)) { + rv = Stop(nsIWebNavigation::STOP_ALL); + } else { + rv = Stop(nsIWebNavigation::STOP_NETWORK); + } + + if (NS_FAILED(rv)) { + return rv; + } + } + + mLoadType = aLoadType; + + // mLSHE should be assigned to aSHEntry, only after Stop() has + // been called. But when loading an error page, do not clear the + // mLSHE for the real page. + if (mLoadType != LOAD_ERROR_PAGE) { + SetHistoryEntry(&mLSHE, aSHEntry); + } + + mSavingOldViewer = savePresentation; + + // If we have a saved content viewer in history, restore and show it now. + if (aSHEntry && (mLoadType & LOAD_CMD_HISTORY)) { + // Make sure our history ID points to the same ID as + // SHEntry's docshell ID. + aSHEntry->GetDocshellID(&mHistoryID); + + // It's possible that the previous viewer of mContentViewer is the + // viewer that will end up in aSHEntry when it gets closed. If that's + // the case, we need to go ahead and force it into its shentry so we + // can restore it. + if (mContentViewer) { + nsCOMPtr<nsIContentViewer> prevViewer; + mContentViewer->GetPreviousViewer(getter_AddRefs(prevViewer)); + if (prevViewer) { +#ifdef DEBUG + nsCOMPtr<nsIContentViewer> prevPrevViewer; + prevViewer->GetPreviousViewer(getter_AddRefs(prevPrevViewer)); + NS_ASSERTION(!prevPrevViewer, "Should never have viewer chain here"); +#endif + nsCOMPtr<nsISHEntry> viewerEntry; + prevViewer->GetHistoryEntry(getter_AddRefs(viewerEntry)); + if (viewerEntry == aSHEntry) { + // Make sure this viewer ends up in the right place + mContentViewer->SetPreviousViewer(nullptr); + prevViewer->Destroy(); + } + } + } + nsCOMPtr<nsISHEntry> oldEntry = mOSHE; + bool restoring; + rv = RestorePresentation(aSHEntry, &restoring); + if (restoring) { + return rv; + } + + // We failed to restore the presentation, so clean up. + // Both the old and new history entries could potentially be in + // an inconsistent state. + if (NS_FAILED(rv)) { + if (oldEntry) { + oldEntry->SyncPresentationState(); + } + + aSHEntry->SyncPresentationState(); + } + } + + nsAutoString srcdoc; + if (aFlags & INTERNAL_LOAD_FLAGS_IS_SRCDOC) { + srcdoc = aSrcdoc; + } else { + srcdoc = NullString(); + } + + net::PredictorLearn(aURI, nullptr, + nsINetworkPredictor::LEARN_LOAD_TOPLEVEL, this); + net::PredictorPredict(aURI, nullptr, + nsINetworkPredictor::PREDICT_LOAD, this, nullptr); + + nsCOMPtr<nsIRequest> req; + rv = DoURILoad(aURI, aOriginalURI, aLoadReplace, aReferrer, + !(aFlags & INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER), + aReferrerPolicy, + aTriggeringPrincipal, principalToInherit, aTypeHint, + aFileName, aPostData, aHeadersData, + aFirstParty, aDocShell, getter_AddRefs(req), + (aFlags & INTERNAL_LOAD_FLAGS_FIRST_LOAD) != 0, + (aFlags & INTERNAL_LOAD_FLAGS_BYPASS_CLASSIFIER) != 0, + (aFlags & INTERNAL_LOAD_FLAGS_FORCE_ALLOW_COOKIES) != 0, + srcdoc, aBaseURI, contentType); + if (req && aRequest) { + NS_ADDREF(*aRequest = req); + } + + if (NS_FAILED(rv)) { + nsCOMPtr<nsIChannel> chan(do_QueryInterface(req)); + if (DisplayLoadError(rv, aURI, nullptr, chan) && + (aFlags & LOAD_FLAGS_ERROR_LOAD_CHANGES_RV) != 0) { + return NS_ERROR_LOAD_SHOWED_ERRORPAGE; + } + } + + return rv; +} + +nsIPrincipal* +nsDocShell::GetInheritedPrincipal(bool aConsiderCurrentDocument) +{ + nsCOMPtr<nsIDocument> document; + bool inheritedFromCurrent = false; + + if (aConsiderCurrentDocument && mContentViewer) { + document = mContentViewer->GetDocument(); + inheritedFromCurrent = true; + } + + if (!document) { + nsCOMPtr<nsIDocShellTreeItem> parentItem; + GetSameTypeParent(getter_AddRefs(parentItem)); + if (parentItem) { + document = parentItem->GetDocument(); + } + } + + if (!document) { + if (!aConsiderCurrentDocument) { + return nullptr; + } + + // Make sure we end up with _something_ as the principal no matter + // what.If this fails, we'll just get a null docViewer and bail. + EnsureContentViewer(); + if (!mContentViewer) { + return nullptr; + } + document = mContentViewer->GetDocument(); + } + + //-- Get the document's principal + if (document) { + nsIPrincipal* docPrincipal = document->NodePrincipal(); + + // Don't allow loads in typeContent docShells to inherit the system + // principal from existing documents. + if (inheritedFromCurrent && + mItemType == typeContent && + nsContentUtils::IsSystemPrincipal(docPrincipal)) { + return nullptr; + } + + return docPrincipal; + } + + return nullptr; +} + +nsresult +nsDocShell::DoURILoad(nsIURI* aURI, + nsIURI* aOriginalURI, + bool aLoadReplace, + nsIURI* aReferrerURI, + bool aSendReferrer, + uint32_t aReferrerPolicy, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + const char* aTypeHint, + const nsAString& aFileName, + nsIInputStream* aPostData, + nsIInputStream* aHeadersData, + bool aFirstParty, + nsIDocShell** aDocShell, + nsIRequest** aRequest, + bool aIsNewWindowTarget, + bool aBypassClassifier, + bool aForceAllowCookies, + const nsAString& aSrcdoc, + nsIURI* aBaseURI, + nsContentPolicyType aContentPolicyType) +{ + // Double-check that we're still around to load this URI. + if (mIsBeingDestroyed) { + // Return NS_OK despite not doing anything to avoid throwing exceptions from + // nsLocation::SetHref if the unload handler of the existing page tears us + // down. + return NS_OK; + } + + nsresult rv; + nsCOMPtr<nsIURILoader> uriLoader = do_GetService(NS_URI_LOADER_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + if (IsFrame()) { + + MOZ_ASSERT(aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_IFRAME || + aContentPolicyType == nsIContentPolicy::TYPE_INTERNAL_FRAME, + "DoURILoad thinks this is a frame and InternalLoad does not"); + + // Only allow view-source scheme in top-level docshells. view-source is + // the only scheme to which this applies at the moment due to potential + // timing attacks to read data from cross-origin iframes. If this widens + // we should add a protocol flag for whether the scheme is allowed in + // frames and use something like nsNetUtil::NS_URIChainHasFlags. + nsCOMPtr<nsIURI> tempURI = aURI; + nsCOMPtr<nsINestedURI> nestedURI = do_QueryInterface(tempURI); + while (nestedURI) { + // view-source should always be an nsINestedURI, loop and check the + // scheme on this and all inner URIs that are also nested URIs. + bool isViewSource = false; + rv = tempURI->SchemeIs("view-source", &isViewSource); + if (NS_FAILED(rv) || isViewSource) { + return NS_ERROR_UNKNOWN_PROTOCOL; + } + nestedURI->GetInnerURI(getter_AddRefs(tempURI)); + nestedURI = do_QueryInterface(tempURI); + } + } else { + MOZ_ASSERT(aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT, + "DoURILoad thinks this is a document and InternalLoad does not"); + } + + // open a channel for the url + nsCOMPtr<nsIChannel> channel; + + bool isSrcdoc = !aSrcdoc.IsVoid(); + + // There are two cases we care about: + // * Top-level load: In this case, loadingNode is null, but loadingWindow + // is our mScriptGlobal. We pass null for loadingPrincipal in this case. + // * Subframe load: loadingWindow is null, but loadingNode is the frame + // element for the load. loadingPrincipal is the NodePrincipal of the frame + // element. + nsCOMPtr<nsINode> loadingNode; + nsCOMPtr<nsPIDOMWindowOuter> loadingWindow; + nsCOMPtr<nsIPrincipal> loadingPrincipal; + + if (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) { + loadingNode = nullptr; + loadingPrincipal = nullptr; + loadingWindow = mScriptGlobal->AsOuter(); + } else { + loadingWindow = nullptr; + loadingNode = mScriptGlobal->AsOuter()->GetFrameElementInternal(); + if (loadingNode) { + // If we have a loading node, then use that as our loadingPrincipal. + loadingPrincipal = loadingNode->NodePrincipal(); + } else { + // If this isn't a top-level load and mScriptGlobal's frame element is + // null, then the element got removed from the DOM while we were trying + // to load this resource. This docshell is scheduled for destruction + // already, so bail out here. + return NS_OK; + } + } + + // Getting the right triggeringPrincipal needs to be updated and is only + // ready for use once bug 1182569 landed. Until then, we cannot rely on + // the triggeringPrincipal for TYPE_DOCUMENT loads. + MOZ_ASSERT(aTriggeringPrincipal, "Need a valid triggeringPrincipal"); + + bool isSandBoxed = mSandboxFlags & SANDBOXED_ORIGIN; + // only inherit if we have a aPrincipalToInherit + bool inherit = false; + + if (aPrincipalToInherit) { + inherit = nsContentUtils::ChannelShouldInheritPrincipal( + aPrincipalToInherit, + aURI, + true, // aInheritForAboutBlank + isSrcdoc); + } + + nsLoadFlags loadFlags = mDefaultLoadFlags; + nsSecurityFlags securityFlags = nsILoadInfo::SEC_NORMAL; + + if (aFirstParty) { + // tag first party URL loads + loadFlags |= nsIChannel::LOAD_INITIAL_DOCUMENT_URI; + } + + if (mLoadType == LOAD_ERROR_PAGE) { + // Error pages are LOAD_BACKGROUND + loadFlags |= nsIChannel::LOAD_BACKGROUND; + securityFlags |= nsILoadInfo::SEC_LOAD_ERROR_PAGE; + } + + if (inherit) { + securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL; + } + if (isSandBoxed) { + securityFlags |= nsILoadInfo::SEC_SANDBOXED; + } + + nsCOMPtr<nsILoadInfo> loadInfo = + (aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT) ? + new LoadInfo(loadingWindow, aTriggeringPrincipal, + securityFlags) : + new LoadInfo(loadingPrincipal, aTriggeringPrincipal, loadingNode, + securityFlags, aContentPolicyType); + + if (aPrincipalToInherit) { + loadInfo->SetPrincipalToInherit(aPrincipalToInherit); + } + + // We have to do this in case our OriginAttributes are different from the + // OriginAttributes of the parent document. Or in case there isn't a + // parent document. + NeckoOriginAttributes neckoAttrs; + bool isTopLevelDoc = aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT && + mItemType == typeContent && + !GetIsMozBrowserOrApp(); + neckoAttrs.InheritFromDocShellToNecko(GetOriginAttributes(), isTopLevelDoc, aURI); + rv = loadInfo->SetOriginAttributes(neckoAttrs); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (!isSrcdoc) { + rv = NS_NewChannelInternal(getter_AddRefs(channel), + aURI, + loadInfo, + nullptr, // loadGroup + static_cast<nsIInterfaceRequestor*>(this), + loadFlags); + + if (NS_FAILED(rv)) { + if (rv == NS_ERROR_UNKNOWN_PROTOCOL) { + // This is a uri with a protocol scheme we don't know how + // to handle. Embedders might still be interested in + // handling the load, though, so we fire a notification + // before throwing the load away. + bool abort = false; + nsresult rv2 = mContentListener->OnStartURIOpen(aURI, &abort); + if (NS_SUCCEEDED(rv2) && abort) { + // Hey, they're handling the load for us! How convenient! + return NS_OK; + } + } + return rv; + } + + if (aBaseURI) { + nsCOMPtr<nsIViewSourceChannel> vsc = do_QueryInterface(channel); + if (vsc) { + vsc->SetBaseURI(aBaseURI); + } + } + } else { + nsAutoCString scheme; + rv = aURI->GetScheme(scheme); + NS_ENSURE_SUCCESS(rv, rv); + bool isViewSource; + aURI->SchemeIs("view-source", &isViewSource); + + if (isViewSource) { + nsViewSourceHandler* vsh = nsViewSourceHandler::GetInstance(); + NS_ENSURE_TRUE(vsh, NS_ERROR_FAILURE); + + rv = vsh->NewSrcdocChannel(aURI, aBaseURI, aSrcdoc, + loadInfo, getter_AddRefs(channel)); + } else { + rv = NS_NewInputStreamChannelInternal(getter_AddRefs(channel), + aURI, + aSrcdoc, + NS_LITERAL_CSTRING("text/html"), + loadInfo, + true); + NS_ENSURE_SUCCESS(rv, rv); + nsCOMPtr<nsIInputStreamChannel> isc = do_QueryInterface(channel); + MOZ_ASSERT(isc); + isc->SetBaseURI(aBaseURI); + } + } + + nsCOMPtr<nsIApplicationCacheChannel> appCacheChannel = + do_QueryInterface(channel); + if (appCacheChannel) { + // Any document load should not inherit application cache. + appCacheChannel->SetInheritApplicationCache(false); + + // Loads with the correct permissions should check for a matching + // application cache. + if (GeckoProcessType_Default != XRE_GetProcessType()) { + // Permission will be checked in the parent process + appCacheChannel->SetChooseApplicationCache(true); + } else { + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + + if (secMan) { + nsCOMPtr<nsIPrincipal> principal; + secMan->GetDocShellCodebasePrincipal(aURI, this, + getter_AddRefs(principal)); + appCacheChannel->SetChooseApplicationCache( + NS_ShouldCheckAppCache(principal, UsePrivateBrowsing())); + } + } + } + + // Make sure to give the caller a channel if we managed to create one + // This is important for correct error page/session history interaction + if (aRequest) { + NS_ADDREF(*aRequest = channel); + } + + if (aOriginalURI) { + channel->SetOriginalURI(aOriginalURI); + if (aLoadReplace) { + uint32_t loadFlags; + channel->GetLoadFlags(&loadFlags); + NS_ENSURE_SUCCESS(rv, rv); + channel->SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE); + } + } else { + channel->SetOriginalURI(aURI); + } + + if (aTypeHint && *aTypeHint) { + channel->SetContentType(nsDependentCString(aTypeHint)); + mContentTypeHint = aTypeHint; + } else { + mContentTypeHint.Truncate(); + } + + if (!aFileName.IsVoid()) { + rv = channel->SetContentDisposition(nsIChannel::DISPOSITION_ATTACHMENT); + NS_ENSURE_SUCCESS(rv, rv); + if (!aFileName.IsEmpty()) { + rv = channel->SetContentDispositionFilename(aFileName); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + if (mLoadType == LOAD_NORMAL_ALLOW_MIXED_CONTENT || + mLoadType == LOAD_RELOAD_ALLOW_MIXED_CONTENT) { + rv = SetMixedContentChannel(channel); + NS_ENSURE_SUCCESS(rv, rv); + } else if (mMixedContentChannel) { + /* + * If the user "Disables Protection on This Page", we call + * SetMixedContentChannel for the first time, otherwise + * mMixedContentChannel is still null. + * Later, if the new channel passes a same orign check, we remember the + * users decision by calling SetMixedContentChannel using the new channel. + * This way, the user does not have to click the disable protection button + * over and over for browsing the same site. + */ + rv = nsContentUtils::CheckSameOrigin(mMixedContentChannel, channel); + if (NS_FAILED(rv) || NS_FAILED(SetMixedContentChannel(channel))) { + SetMixedContentChannel(nullptr); + } + } + + // hack + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(channel)); + nsCOMPtr<nsIHttpChannelInternal> httpChannelInternal( + do_QueryInterface(channel)); + if (httpChannelInternal) { + if (aForceAllowCookies) { + httpChannelInternal->SetThirdPartyFlags( + nsIHttpChannelInternal::THIRD_PARTY_FORCE_ALLOW); + } + if (aFirstParty) { + httpChannelInternal->SetDocumentURI(aURI); + } else { + httpChannelInternal->SetDocumentURI(aReferrerURI); + } + httpChannelInternal->SetRedirectMode( + nsIHttpChannelInternal::REDIRECT_MODE_MANUAL); + } + + nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(channel)); + if (props) { + // save true referrer for those who need it (e.g. xpinstall whitelisting) + // Currently only http and ftp channels support this. + props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.internalReferrer"), + aReferrerURI); + } + + nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(channel)); + /* Get the cache Key from SH */ + nsCOMPtr<nsISupports> cacheKey; + if (cacheChannel) { + if (mLSHE) { + mLSHE->GetCacheKey(getter_AddRefs(cacheKey)); + } else if (mOSHE) { // for reload cases + mOSHE->GetCacheKey(getter_AddRefs(cacheKey)); + } + } + + // figure out if we need to set the post data stream on the channel... + if (aPostData) { + nsCOMPtr<nsIFormPOSTActionChannel> postChannel(do_QueryInterface(channel)); + if (postChannel) { + // XXX it's a bit of a hack to rewind the postdata stream here but + // it has to be done in case the post data is being reused multiple + // times. + nsCOMPtr<nsISeekableStream> postDataSeekable = + do_QueryInterface(aPostData); + if (postDataSeekable) { + rv = postDataSeekable->Seek(nsISeekableStream::NS_SEEK_SET, 0); + NS_ENSURE_SUCCESS(rv, rv); + } + + // we really need to have a content type associated with this stream!! + postChannel->SetUploadStream(aPostData, EmptyCString(), -1); + } + + /* If there is a valid postdata *and* it is a History Load, + * set up the cache key on the channel, to retrieve the + * data *only* from the cache. If it is a normal reload, the + * cache is free to go to the server for updated postdata. + */ + if (cacheChannel && cacheKey) { + if (mLoadType == LOAD_HISTORY || + mLoadType == LOAD_RELOAD_CHARSET_CHANGE) { + cacheChannel->SetCacheKey(cacheKey); + uint32_t loadFlags; + if (NS_SUCCEEDED(channel->GetLoadFlags(&loadFlags))) { + channel->SetLoadFlags( + loadFlags | nsICachingChannel::LOAD_ONLY_FROM_CACHE); + } + } else if (mLoadType == LOAD_RELOAD_NORMAL) { + cacheChannel->SetCacheKey(cacheKey); + } + } + } else { + /* If there is no postdata, set the cache key on the channel, and + * do not set the LOAD_ONLY_FROM_CACHE flag, so that the channel + * will be free to get it from net if it is not found in cache. + * New cache may use it creatively on CGI pages with GET + * method and even on those that say "no-cache" + */ + if (mLoadType == LOAD_HISTORY || + mLoadType == LOAD_RELOAD_NORMAL || + mLoadType == LOAD_RELOAD_CHARSET_CHANGE) { + if (cacheChannel && cacheKey) { + cacheChannel->SetCacheKey(cacheKey); + } + } + } + + if (httpChannel) { + if (aHeadersData) { + rv = AddHeadersToChannel(aHeadersData, httpChannel); + } + // Set the referrer explicitly + if (aReferrerURI && aSendReferrer) { + // Referrer is currenly only set for link clicks here. + httpChannel->SetReferrerWithPolicy(aReferrerURI, aReferrerPolicy); + } + // set Content-Signature enforcing bit if aOriginalURI == about:newtab + if (aOriginalURI && httpChannel) { + if (IsAboutNewtab(aOriginalURI)) { + nsCOMPtr<nsILoadInfo> loadInfo = httpChannel->GetLoadInfo(); + if (loadInfo) { + loadInfo->SetVerifySignedContent(true); + } + } + } + } + + nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(channel); + if (scriptChannel) { + // Allow execution against our context if the principals match + scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL); + } + + if (aIsNewWindowTarget) { + nsCOMPtr<nsIWritablePropertyBag2> props = do_QueryInterface(channel); + if (props) { + props->SetPropertyAsBool(NS_LITERAL_STRING("docshell.newWindowTarget"), + true); + } + } + + nsCOMPtr<nsITimedChannel> timedChannel(do_QueryInterface(channel)); + if (timedChannel) { + timedChannel->SetTimingEnabled(true); + + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + if (IsFrame() && win) { + nsCOMPtr<Element> frameElement = win->GetFrameElementInternal(); + if (frameElement) { + timedChannel->SetInitiatorType(frameElement->LocalName()); + } + } + } + + rv = DoChannelLoad(channel, uriLoader, aBypassClassifier); + + // + // If the channel load failed, we failed and nsIWebProgress just ain't + // gonna happen. + // + if (NS_SUCCEEDED(rv)) { + if (aDocShell) { + *aDocShell = this; + NS_ADDREF(*aDocShell); + } + } + + return rv; +} + +static nsresult +AppendSegmentToString(nsIInputStream* aIn, + void* aClosure, + const char* aFromRawSegment, + uint32_t aToOffset, + uint32_t aCount, + uint32_t* aWriteCount) +{ + // aFromSegment now contains aCount bytes of data. + + nsAutoCString* buf = static_cast<nsAutoCString*>(aClosure); + buf->Append(aFromRawSegment, aCount); + + // Indicate that we have consumed all of aFromSegment + *aWriteCount = aCount; + return NS_OK; +} + +nsresult +nsDocShell::AddHeadersToChannel(nsIInputStream* aHeadersData, + nsIChannel* aGenericChannel) +{ + nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aGenericChannel); + NS_ENSURE_STATE(httpChannel); + + uint32_t numRead; + nsAutoCString headersString; + nsresult rv = aHeadersData->ReadSegments(AppendSegmentToString, + &headersString, + UINT32_MAX, + &numRead); + NS_ENSURE_SUCCESS(rv, rv); + + // used during the manipulation of the String from the InputStream + nsAutoCString headerName; + nsAutoCString headerValue; + int32_t crlf; + int32_t colon; + + // + // Iterate over the headersString: for each "\r\n" delimited chunk, + // add the value as a header to the nsIHttpChannel + // + + static const char kWhitespace[] = "\b\t\r\n "; + while (true) { + crlf = headersString.Find("\r\n"); + if (crlf == kNotFound) { + return NS_OK; + } + + const nsCSubstring& oneHeader = StringHead(headersString, crlf); + + colon = oneHeader.FindChar(':'); + if (colon == kNotFound) { + return NS_ERROR_UNEXPECTED; + } + + headerName = StringHead(oneHeader, colon); + headerValue = Substring(oneHeader, colon + 1); + + headerName.Trim(kWhitespace); + headerValue.Trim(kWhitespace); + + headersString.Cut(0, crlf + 2); + + // + // FINALLY: we can set the header! + // + + rv = httpChannel->SetRequestHeader(headerName, headerValue, true); + NS_ENSURE_SUCCESS(rv, rv); + } + + NS_NOTREACHED("oops"); + return NS_ERROR_UNEXPECTED; +} + +nsresult +nsDocShell::DoChannelLoad(nsIChannel* aChannel, + nsIURILoader* aURILoader, + bool aBypassClassifier) +{ + nsresult rv; + // Mark the channel as being a document URI and allow content sniffing... + nsLoadFlags loadFlags = 0; + (void)aChannel->GetLoadFlags(&loadFlags); + loadFlags |= nsIChannel::LOAD_DOCUMENT_URI | + nsIChannel::LOAD_CALL_CONTENT_SNIFFERS; + + // Load attributes depend on load type... + switch (mLoadType) { + case LOAD_HISTORY: { + // Only send VALIDATE_NEVER if mLSHE's URI was never changed via + // push/replaceState (bug 669671). + bool uriModified = false; + if (mLSHE) { + mLSHE->GetURIWasModified(&uriModified); + } + + if (!uriModified) { + loadFlags |= nsIRequest::VALIDATE_NEVER; + } + break; + } + + case LOAD_RELOAD_CHARSET_CHANGE: { + // Use SetAllowStaleCacheContent (not LOAD_FROM_CACHE flag) since we only want + // to force cache load for this channel, not the whole loadGroup. + nsCOMPtr<nsICacheInfoChannel> cachingChannel = do_QueryInterface(aChannel); + if (cachingChannel) { + cachingChannel->SetAllowStaleCacheContent(true); + } + break; + } + + case LOAD_RELOAD_NORMAL: + case LOAD_REFRESH: + loadFlags |= nsIRequest::VALIDATE_ALWAYS; + break; + + case LOAD_NORMAL_BYPASS_CACHE: + case LOAD_NORMAL_BYPASS_PROXY: + case LOAD_NORMAL_BYPASS_PROXY_AND_CACHE: + case LOAD_NORMAL_ALLOW_MIXED_CONTENT: + case LOAD_RELOAD_BYPASS_CACHE: + case LOAD_RELOAD_BYPASS_PROXY: + case LOAD_RELOAD_BYPASS_PROXY_AND_CACHE: + case LOAD_RELOAD_ALLOW_MIXED_CONTENT: + case LOAD_REPLACE_BYPASS_CACHE: + loadFlags |= nsIRequest::LOAD_BYPASS_CACHE | + nsIRequest::LOAD_FRESH_CONNECTION; + break; + + case LOAD_NORMAL: + case LOAD_LINK: + // Set cache checking flags + switch (Preferences::GetInt("browser.cache.check_doc_frequency", -1)) { + case 0: + loadFlags |= nsIRequest::VALIDATE_ONCE_PER_SESSION; + break; + case 1: + loadFlags |= nsIRequest::VALIDATE_ALWAYS; + break; + case 2: + loadFlags |= nsIRequest::VALIDATE_NEVER; + break; + } + break; + } + + if (!aBypassClassifier) { + loadFlags |= nsIChannel::LOAD_CLASSIFY_URI; + } + + // If the user pressed shift-reload, then do not allow ServiceWorker + // interception to occur. See step 12.1 of the SW HandleFetch algorithm. + if (IsForceReloadType(mLoadType)) { + loadFlags |= nsIChannel::LOAD_BYPASS_SERVICE_WORKER; + } + + (void)aChannel->SetLoadFlags(loadFlags); + + uint32_t openFlags = 0; + if (mLoadType == LOAD_LINK) { + openFlags |= nsIURILoader::IS_CONTENT_PREFERRED; + } + if (!mAllowContentRetargeting) { + openFlags |= nsIURILoader::DONT_RETARGET; + } + rv = aURILoader->OpenURI(aChannel, openFlags, this); + NS_ENSURE_SUCCESS(rv, rv); + + return NS_OK; +} + +nsresult +nsDocShell::ScrollToAnchor(bool aCurHasRef, bool aNewHasRef, + nsACString& aNewHash, uint32_t aLoadType) +{ + if (!mCurrentURI) { + return NS_OK; + } + + nsCOMPtr<nsIPresShell> shell = GetPresShell(); + if (!shell) { + // If we failed to get the shell, or if there is no shell, + // nothing left to do here. + return NS_OK; + } + + nsIScrollableFrame* rootScroll = shell->GetRootScrollFrameAsScrollable(); + if (rootScroll) { + rootScroll->ClearDidHistoryRestore(); + } + + // If we have no new anchor, we do not want to scroll, unless there is a + // current anchor and we are doing a history load. So return if we have no + // new anchor, and there is no current anchor or the load is not a history + // load. + if ((!aCurHasRef || aLoadType != LOAD_HISTORY) && !aNewHasRef) { + return NS_OK; + } + + // Both the new and current URIs refer to the same page. We can now + // browse to the hash stored in the new URI. + + if (!aNewHash.IsEmpty()) { + // anchor is there, but if it's a load from history, + // we don't have any anchor jumping to do + bool scroll = aLoadType != LOAD_HISTORY && + aLoadType != LOAD_RELOAD_NORMAL; + + char* str = ToNewCString(aNewHash); + if (!str) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // nsUnescape modifies the string that is passed into it. + nsUnescape(str); + + // We assume that the bytes are in UTF-8, as it says in the + // spec: + // http://www.w3.org/TR/html4/appendix/notes.html#h-B.2.1 + + // We try the UTF-8 string first, and then try the document's + // charset (see below). If the string is not UTF-8, + // conversion will fail and give us an empty Unicode string. + // In that case, we should just fall through to using the + // page's charset. + nsresult rv = NS_ERROR_FAILURE; + NS_ConvertUTF8toUTF16 uStr(str); + if (!uStr.IsEmpty()) { + rv = shell->GoToAnchor(NS_ConvertUTF8toUTF16(str), scroll, + nsIPresShell::SCROLL_SMOOTH_AUTO); + } + free(str); + + // Above will fail if the anchor name is not UTF-8. Need to + // convert from document charset to unicode. + if (NS_FAILED(rv)) { + // Get a document charset + NS_ENSURE_TRUE(mContentViewer, NS_ERROR_FAILURE); + nsIDocument* doc = mContentViewer->GetDocument(); + NS_ENSURE_TRUE(doc, NS_ERROR_FAILURE); + const nsACString& aCharset = doc->GetDocumentCharacterSet(); + + nsCOMPtr<nsITextToSubURI> textToSubURI = + do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + // Unescape and convert to unicode + nsXPIDLString uStr; + + rv = textToSubURI->UnEscapeAndConvert(PromiseFlatCString(aCharset).get(), + PromiseFlatCString(aNewHash).get(), + getter_Copies(uStr)); + NS_ENSURE_SUCCESS(rv, rv); + + // Ignore return value of GoToAnchor, since it will return an error + // if there is no such anchor in the document, which is actually a + // success condition for us (we want to update the session history + // with the new URI no matter whether we actually scrolled + // somewhere). + // + // When aNewHash contains "%00", unescaped string may be empty. + // And GoToAnchor asserts if we ask it to scroll to an empty ref. + shell->GoToAnchor(uStr, scroll && !uStr.IsEmpty(), + nsIPresShell::SCROLL_SMOOTH_AUTO); + } + } else { + // Tell the shell it's at an anchor, without scrolling. + shell->GoToAnchor(EmptyString(), false); + + // An empty anchor was found, but if it's a load from history, + // we don't have to jump to the top of the page. Scrollbar + // position will be restored by the caller, based on positions + // stored in session history. + if (aLoadType == LOAD_HISTORY || aLoadType == LOAD_RELOAD_NORMAL) { + return NS_OK; + } + // An empty anchor. Scroll to the top of the page. Ignore the + // return value; failure to scroll here (e.g. if there is no + // root scrollframe) is not grounds for canceling the load! + SetCurScrollPosEx(0, 0); + } + + return NS_OK; +} + +void +nsDocShell::SetupReferrerFromChannel(nsIChannel* aChannel) +{ + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + if (httpChannel) { + nsCOMPtr<nsIURI> referrer; + nsresult rv = httpChannel->GetReferrer(getter_AddRefs(referrer)); + if (NS_SUCCEEDED(rv)) { + SetReferrerURI(referrer); + } + uint32_t referrerPolicy; + rv = httpChannel->GetReferrerPolicy(&referrerPolicy); + if (NS_SUCCEEDED(rv)) { + SetReferrerPolicy(referrerPolicy); + } + } +} + +bool +nsDocShell::OnNewURI(nsIURI* aURI, nsIChannel* aChannel, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + uint32_t aLoadType, bool aFireOnLocationChange, + bool aAddToGlobalHistory, bool aCloneSHChildren) +{ + NS_PRECONDITION(aURI, "uri is null"); + NS_PRECONDITION(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set"); + + MOZ_ASSERT(!aPrincipalToInherit || (aPrincipalToInherit && aTriggeringPrincipal)); + +#if defined(DEBUG) + if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { + nsAutoCString chanName; + if (aChannel) { + aChannel->GetName(chanName); + } else { + chanName.AssignLiteral("<no channel>"); + } + + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]::OnNewURI(\"%s\", [%s], 0x%x)\n", + this, aURI->GetSpecOrDefault().get(), chanName.get(), aLoadType)); + } +#endif + + bool equalUri = false; + + // Get the post data and the HTTP response code from the channel. + uint32_t responseStatus = 0; + nsCOMPtr<nsIInputStream> inputStream; + if (aChannel) { + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + + // Check if the HTTPChannel is hiding under a multiPartChannel + if (!httpChannel) { + GetHttpChannel(aChannel, getter_AddRefs(httpChannel)); + } + + if (httpChannel) { + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel)); + if (uploadChannel) { + uploadChannel->GetUploadStream(getter_AddRefs(inputStream)); + } + + // If the response status indicates an error, unlink this session + // history entry from any entries sharing its document. + nsresult rv = httpChannel->GetResponseStatus(&responseStatus); + if (mLSHE && NS_SUCCEEDED(rv) && responseStatus >= 400) { + mLSHE->AbandonBFCacheEntry(); + } + } + } + + // Determine if this type of load should update history. + bool updateGHistory = !(aLoadType == LOAD_BYPASS_HISTORY || + aLoadType == LOAD_ERROR_PAGE || + aLoadType & LOAD_CMD_HISTORY); + + // We don't update session history on reload unless we're loading + // an iframe in shift-reload case. + bool updateSHistory = updateGHistory && + (!(aLoadType & LOAD_CMD_RELOAD) || + (IsForceReloadType(aLoadType) && IsFrame())); + + // Create SH Entry (mLSHE) only if there is a SessionHistory object in the + // current frame or in the root docshell. + nsCOMPtr<nsISHistory> rootSH = mSessionHistory; + if (!rootSH) { + // Get the handle to SH from the root docshell + GetRootSessionHistory(getter_AddRefs(rootSH)); + if (!rootSH) { + updateSHistory = false; + updateGHistory = false; // XXX Why global history too? + } + } + + // Check if the url to be loaded is the same as the one already loaded. + if (mCurrentURI) { + aURI->Equals(mCurrentURI, &equalUri); + } + +#ifdef DEBUG + bool shAvailable = (rootSH != nullptr); + + // XXX This log message is almost useless because |updateSHistory| + // and |updateGHistory| are not correct at this point. + + MOZ_LOG(gDocShellLog, LogLevel::Debug, + (" shAvailable=%i updateSHistory=%i updateGHistory=%i" + " equalURI=%i\n", + shAvailable, updateSHistory, updateGHistory, equalUri)); + + if (shAvailable && mCurrentURI && !mOSHE && aLoadType != LOAD_ERROR_PAGE) { + NS_ASSERTION(NS_IsAboutBlank(mCurrentURI), + "no SHEntry for a non-transient viewer?"); + } +#endif + + /* If the url to be loaded is the same as the one already there, + * and the original loadType is LOAD_NORMAL, LOAD_LINK, or + * LOAD_STOP_CONTENT, set loadType to LOAD_NORMAL_REPLACE so that + * AddToSessionHistory() won't mess with the current SHEntry and + * if this page has any frame children, it also will be handled + * properly. see bug 83684 + * + * NB: If mOSHE is null but we have a current URI, then it means + * that we must be at the transient about:blank content viewer + * (asserted above) and we should let the normal load continue, + * since there's nothing to replace. + * + * XXX Hopefully changing the loadType at this time will not hurt + * anywhere. The other way to take care of sequentially repeating + * frameset pages is to add new methods to nsIDocShellTreeItem. + * Hopefully I don't have to do that. + */ + if (equalUri && + mOSHE && + (mLoadType == LOAD_NORMAL || + mLoadType == LOAD_LINK || + mLoadType == LOAD_STOP_CONTENT) && + !inputStream) { + mLoadType = LOAD_NORMAL_REPLACE; + } + + // If this is a refresh to the currently loaded url, we don't + // have to update session or global history. + if (mLoadType == LOAD_REFRESH && !inputStream && equalUri) { + SetHistoryEntry(&mLSHE, mOSHE); + } + + /* If the user pressed shift-reload, cache will create a new cache key + * for the page. Save the new cacheKey in Session History. + * see bug 90098 + */ + if (aChannel && IsForceReloadType(aLoadType)) { + MOZ_ASSERT(!updateSHistory || IsFrame(), + "We shouldn't be updating session history for forced" + " reloads unless we're in a newly created iframe!"); + + nsCOMPtr<nsICacheInfoChannel> cacheChannel(do_QueryInterface(aChannel)); + nsCOMPtr<nsISupports> cacheKey; + // Get the Cache Key and store it in SH. + if (cacheChannel) { + cacheChannel->GetCacheKey(getter_AddRefs(cacheKey)); + } + // If we already have a loading history entry, store the new cache key + // in it. Otherwise, since we're doing a reload and won't be updating + // our history entry, store the cache key in our current history entry. + if (mLSHE) { + mLSHE->SetCacheKey(cacheKey); + } else if (mOSHE) { + mOSHE->SetCacheKey(cacheKey); + } + + // Since we're force-reloading, clear all the sub frame history. + ClearFrameHistory(mLSHE); + ClearFrameHistory(mOSHE); + } + + if (aLoadType == LOAD_RELOAD_NORMAL) { + nsCOMPtr<nsISHEntry> currentSH; + bool oshe = false; + GetCurrentSHEntry(getter_AddRefs(currentSH), &oshe); + bool dynamicallyAddedChild = false; + if (currentSH) { + currentSH->HasDynamicallyAddedChild(&dynamicallyAddedChild); + } + if (dynamicallyAddedChild) { + ClearFrameHistory(currentSH); + } + } + + if (aLoadType == LOAD_REFRESH) { + ClearFrameHistory(mLSHE); + ClearFrameHistory(mOSHE); + } + + if (updateSHistory) { + // Update session history if necessary... + if (!mLSHE && (mItemType == typeContent) && mURIResultedInDocument) { + /* This is a fresh page getting loaded for the first time + *.Create a Entry for it and add it to SH, if this is the + * rootDocShell + */ + (void)AddToSessionHistory(aURI, aChannel, aTriggeringPrincipal, + aPrincipalToInherit, aCloneSHChildren, + getter_AddRefs(mLSHE)); + } + } else if (mSessionHistory && mLSHE && mURIResultedInDocument) { + // Even if we don't add anything to SHistory, ensure the current index + // points to the same SHEntry as our mLSHE. + int32_t index = 0; + mSessionHistory->GetRequestedIndex(&index); + if (index == -1) { + mSessionHistory->GetIndex(&index); + } + nsCOMPtr<nsISHEntry> currentSH; + mSessionHistory->GetEntryAtIndex(index, false, getter_AddRefs(currentSH)); + if (currentSH != mLSHE) { + nsCOMPtr<nsISHistoryInternal> shPrivate = + do_QueryInterface(mSessionHistory); + shPrivate->ReplaceEntry(index, mLSHE); + } + } + + // If this is a POST request, we do not want to include this in global + // history. + if (updateGHistory && aAddToGlobalHistory && !ChannelIsPost(aChannel)) { + nsCOMPtr<nsIURI> previousURI; + uint32_t previousFlags = 0; + + if (aLoadType & LOAD_CMD_RELOAD) { + // On a reload request, we don't set redirecting flags. + previousURI = aURI; + } else { + ExtractLastVisit(aChannel, getter_AddRefs(previousURI), &previousFlags); + } + + // Note: We don't use |referrer| when our global history is + // based on IHistory. + nsCOMPtr<nsIURI> referrer; + // Treat referrer as null if there is an error getting it. + (void)NS_GetReferrerFromChannel(aChannel, getter_AddRefs(referrer)); + + AddURIVisit(aURI, referrer, previousURI, previousFlags, responseStatus); + } + + // If this was a history load or a refresh, or it was a history load but + // later changed to LOAD_NORMAL_REPLACE due to redirection, update the index + // in session history. + if (rootSH && + ((mLoadType & (LOAD_CMD_HISTORY | LOAD_CMD_RELOAD)) || + mLoadType == LOAD_NORMAL_REPLACE)) { + nsCOMPtr<nsISHistoryInternal> shInternal(do_QueryInterface(rootSH)); + if (shInternal) { + rootSH->GetIndex(&mPreviousTransIndex); + shInternal->UpdateIndex(); + rootSH->GetIndex(&mLoadedTransIndex); +#ifdef DEBUG_PAGE_CACHE + printf("Previous index: %d, Loaded index: %d\n\n", + mPreviousTransIndex, mLoadedTransIndex); +#endif + } + } + + // aCloneSHChildren exactly means "we are not loading a new document". + uint32_t locationFlags = + aCloneSHChildren ? uint32_t(LOCATION_CHANGE_SAME_DOCUMENT) : 0; + + bool onLocationChangeNeeded = SetCurrentURI(aURI, aChannel, + aFireOnLocationChange, + locationFlags); + // Make sure to store the referrer from the channel, if any + SetupReferrerFromChannel(aChannel); + return onLocationChangeNeeded; +} + +bool +nsDocShell::OnLoadingSite(nsIChannel* aChannel, bool aFireOnLocationChange, + bool aAddToGlobalHistory) +{ + nsCOMPtr<nsIURI> uri; + // If this a redirect, use the final url (uri) + // else use the original url + // + // Note that this should match what documents do (see nsDocument::Reset). + NS_GetFinalChannelURI(aChannel, getter_AddRefs(uri)); + NS_ENSURE_TRUE(uri, false); + + // Pass false for aCloneSHChildren, since we're loading a new page here. + return OnNewURI(uri, aChannel, nullptr, nullptr, mLoadType, aFireOnLocationChange, + aAddToGlobalHistory, false); +} + +void +nsDocShell::SetReferrerURI(nsIURI* aURI) +{ + mReferrerURI = aURI; // This assigment addrefs +} + +void +nsDocShell::SetReferrerPolicy(uint32_t aReferrerPolicy) +{ + mReferrerPolicy = aReferrerPolicy; +} + +//***************************************************************************** +// nsDocShell: Session History +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::AddState(JS::Handle<JS::Value> aData, const nsAString& aTitle, + const nsAString& aURL, bool aReplace, JSContext* aCx) +{ + // Implements History.pushState and History.replaceState + + // Here's what we do, roughly in the order specified by HTML5: + // 1. Serialize aData using structured clone. + // 2. If the third argument is present, + // a. Resolve the url, relative to the first script's base URL + // b. If (a) fails, raise a SECURITY_ERR + // c. Compare the resulting absolute URL to the document's address. If + // any part of the URLs difer other than the <path>, <query>, and + // <fragment> components, raise a SECURITY_ERR and abort. + // 3. If !aReplace: + // Remove from the session history all entries after the current entry, + // as we would after a regular navigation, and save the current + // entry's scroll position (bug 590573). + // 4. As apropriate, either add a state object entry to the session history + // after the current entry with the following properties, or modify the + // current session history entry to set + // a. cloned data as the state object, + // b. if the third argument was present, the absolute URL found in + // step 2 + // Also clear the new history entry's POST data (see bug 580069). + // 5. If aReplace is false (i.e. we're doing a pushState instead of a + // replaceState), notify bfcache that we've navigated to a new page. + // 6. If the third argument is present, set the document's current address + // to the absolute URL found in step 2. + // + // It's important that this function not run arbitrary scripts after step 1 + // and before completing step 5. For example, if a script called + // history.back() before we completed step 5, bfcache might destroy an + // active content viewer. Since EvictOutOfRangeContentViewers at the end of + // step 5 might run script, we can't just put a script blocker around the + // critical section. + // + // Note that we completely ignore the aTitle parameter. + + nsresult rv; + + // Don't clobber the load type of an existing network load. + AutoRestore<uint32_t> loadTypeResetter(mLoadType); + + // pushState effectively becomes replaceState when we've started a network + // load but haven't adopted its document yet. This mirrors what we do with + // changes to the hash at this stage of the game. + if (JustStartedNetworkLoad()) { + aReplace = true; + } + + nsCOMPtr<nsIDocument> document = GetDocument(); + NS_ENSURE_TRUE(document, NS_ERROR_FAILURE); + + // Step 1: Serialize aData using structured clone. + nsCOMPtr<nsIStructuredCloneContainer> scContainer; + + // scContainer->Init might cause arbitrary JS to run, and this code might + // navigate the page we're on, potentially to a different origin! (bug + // 634834) To protect against this, we abort if our principal changes due + // to the InitFromJSVal() call. + { + nsCOMPtr<nsIDocument> origDocument = GetDocument(); + if (!origDocument) { + return NS_ERROR_DOM_SECURITY_ERR; + } + nsCOMPtr<nsIPrincipal> origPrincipal = origDocument->NodePrincipal(); + + scContainer = new nsStructuredCloneContainer(); + rv = scContainer->InitFromJSVal(aData, aCx); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocument> newDocument = GetDocument(); + if (!newDocument) { + return NS_ERROR_DOM_SECURITY_ERR; + } + nsCOMPtr<nsIPrincipal> newPrincipal = newDocument->NodePrincipal(); + + bool principalsEqual = false; + origPrincipal->Equals(newPrincipal, &principalsEqual); + NS_ENSURE_TRUE(principalsEqual, NS_ERROR_DOM_SECURITY_ERR); + } + + // Check that the state object isn't too long. + // Default max length: 640k bytes. + int32_t maxStateObjSize = + Preferences::GetInt("browser.history.maxStateObjectSize", 0xA0000); + if (maxStateObjSize < 0) { + maxStateObjSize = 0; + } + + uint64_t scSize; + rv = scContainer->GetSerializedNBytes(&scSize); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(scSize <= (uint32_t)maxStateObjSize, NS_ERROR_ILLEGAL_VALUE); + + // Step 2: Resolve aURL + bool equalURIs = true; + nsCOMPtr<nsIURI> currentURI; + if (sURIFixup && mCurrentURI) { + rv = sURIFixup->CreateExposableURI(mCurrentURI, getter_AddRefs(currentURI)); + NS_ENSURE_SUCCESS(rv, rv); + } else { + currentURI = mCurrentURI; + } + nsCOMPtr<nsIURI> oldURI = currentURI; + nsCOMPtr<nsIURI> newURI; + if (aURL.Length() == 0) { + newURI = currentURI; + } else { + // 2a: Resolve aURL relative to mURI + + nsIURI* docBaseURI = document->GetDocBaseURI(); + if (!docBaseURI) { + return NS_ERROR_FAILURE; + } + + nsAutoCString spec; + docBaseURI->GetSpec(spec); + + nsAutoCString charset; + rv = docBaseURI->GetOriginCharset(charset); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + rv = NS_NewURI(getter_AddRefs(newURI), aURL, charset.get(), docBaseURI); + + // 2b: If 2a fails, raise a SECURITY_ERR + if (NS_FAILED(rv)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + // 2c: Same-origin check. + if (!nsContentUtils::URIIsLocalFile(newURI)) { + // In addition to checking that the security manager says that + // the new URI has the same origin as our current URI, we also + // check that the two URIs have the same userpass. (The + // security manager says that |http://foo.com| and + // |http://me@foo.com| have the same origin.) currentURI + // won't contain the password part of the userpass, so this + // means that it's never valid to specify a password in a + // pushState or replaceState URI. + + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + NS_ENSURE_TRUE(secMan, NS_ERROR_FAILURE); + + // It's very important that we check that newURI is of the same + // origin as currentURI, not docBaseURI, because a page can + // set docBaseURI arbitrarily to any domain. + nsAutoCString currentUserPass, newUserPass; + NS_ENSURE_SUCCESS(currentURI->GetUserPass(currentUserPass), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(newURI->GetUserPass(newUserPass), NS_ERROR_FAILURE); + if (NS_FAILED(secMan->CheckSameOriginURI(currentURI, newURI, true)) || + !currentUserPass.Equals(newUserPass)) { + return NS_ERROR_DOM_SECURITY_ERR; + } + } else { + // It's a file:// URI + nsCOMPtr<nsIScriptObjectPrincipal> docScriptObj = + do_QueryInterface(document); + + if (!docScriptObj) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsCOMPtr<nsIPrincipal> principal = docScriptObj->GetPrincipal(); + + if (!principal || + NS_FAILED(principal->CheckMayLoad(newURI, true, false))) { + return NS_ERROR_DOM_SECURITY_ERR; + } + } + + if (currentURI) { + currentURI->Equals(newURI, &equalURIs); + } else { + equalURIs = false; + } + + } // end of same-origin check + + // Step 3: Create a new entry in the session history. This will erase + // all SHEntries after the new entry and make this entry the current + // one. This operation may modify mOSHE, which we need later, so we + // keep a reference here. + NS_ENSURE_TRUE(mOSHE, NS_ERROR_FAILURE); + nsCOMPtr<nsISHEntry> oldOSHE = mOSHE; + + mLoadType = LOAD_PUSHSTATE; + + nsCOMPtr<nsISHEntry> newSHEntry; + if (!aReplace) { + // Save the current scroll position (bug 590573). + nscoord cx = 0, cy = 0; + GetCurScrollPos(ScrollOrientation_X, &cx); + GetCurScrollPos(ScrollOrientation_Y, &cy); + mOSHE->SetScrollPosition(cx, cy); + + bool scrollRestorationIsManual = false; + mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual); + + // Since we're not changing which page we have loaded, pass + // true for aCloneChildren. + rv = AddToSessionHistory(newURI, nullptr, nullptr, nullptr, true, + getter_AddRefs(newSHEntry)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ENSURE_TRUE(newSHEntry, NS_ERROR_FAILURE); + + // Session history entries created by pushState inherit scroll restoration + // mode from the current entry. + newSHEntry->SetScrollRestorationIsManual(scrollRestorationIsManual); + + // Link the new SHEntry to the old SHEntry's BFCache entry, since the + // two entries correspond to the same document. + NS_ENSURE_SUCCESS(newSHEntry->AdoptBFCacheEntry(oldOSHE), NS_ERROR_FAILURE); + + // Set the new SHEntry's title (bug 655273). + nsString title; + mOSHE->GetTitle(getter_Copies(title)); + newSHEntry->SetTitle(title); + + // AddToSessionHistory may not modify mOSHE. In case it doesn't, + // we'll just set mOSHE here. + mOSHE = newSHEntry; + + } else { + newSHEntry = mOSHE; + newSHEntry->SetURI(newURI); + newSHEntry->SetOriginalURI(newURI); + newSHEntry->SetLoadReplace(false); + } + + // Step 4: Modify new/original session history entry and clear its POST + // data, if there is any. + newSHEntry->SetStateData(scContainer); + newSHEntry->SetPostData(nullptr); + + // If this push/replaceState changed the document's current URI and the new + // URI differs from the old URI in more than the hash, or if the old + // SHEntry's URI was modified in this way by a push/replaceState call + // set URIWasModified to true for the current SHEntry (bug 669671). + bool sameExceptHashes = true, oldURIWasModified = false; + newURI->EqualsExceptRef(currentURI, &sameExceptHashes); + oldOSHE->GetURIWasModified(&oldURIWasModified); + newSHEntry->SetURIWasModified(!sameExceptHashes || oldURIWasModified); + + // Step 5: If aReplace is false, indicating that we're doing a pushState + // rather than a replaceState, notify bfcache that we've added a page to + // the history so it can evict content viewers if appropriate. Otherwise + // call ReplaceEntry so that we notify nsIHistoryListeners that an entry + // was replaced. + nsCOMPtr<nsISHistory> rootSH; + GetRootSessionHistory(getter_AddRefs(rootSH)); + NS_ENSURE_TRUE(rootSH, NS_ERROR_UNEXPECTED); + + nsCOMPtr<nsISHistoryInternal> internalSH = do_QueryInterface(rootSH); + NS_ENSURE_TRUE(internalSH, NS_ERROR_UNEXPECTED); + + if (!aReplace) { + int32_t curIndex = -1; + rv = rootSH->GetIndex(&curIndex); + if (NS_SUCCEEDED(rv) && curIndex > -1) { + internalSH->EvictOutOfRangeContentViewers(curIndex); + } + } else { + nsCOMPtr<nsISHEntry> rootSHEntry = GetRootSHEntry(newSHEntry); + + int32_t index = -1; + rv = rootSH->GetIndexOfEntry(rootSHEntry, &index); + if (NS_SUCCEEDED(rv) && index > -1) { + internalSH->ReplaceEntry(index, rootSHEntry); + } + } + + // Step 6: If the document's URI changed, update document's URI and update + // global history. + // + // We need to call FireOnLocationChange so that the browser's address bar + // gets updated and the back button is enabled, but we only need to + // explicitly call FireOnLocationChange if we're not calling SetCurrentURI, + // since SetCurrentURI will call FireOnLocationChange for us. + // + // Both SetCurrentURI(...) and FireDummyOnLocationChange() pass + // nullptr for aRequest param to FireOnLocationChange(...). Such an update + // notification is allowed only when we know docshell is not loading a new + // document and it requires LOCATION_CHANGE_SAME_DOCUMENT flag. Otherwise, + // FireOnLocationChange(...) breaks security UI. + if (!equalURIs) { + document->SetDocumentURI(newURI); + // We can't trust SetCurrentURI to do always fire locationchange events + // when we expect it to, so we hack around that by doing it ourselves... + SetCurrentURI(newURI, nullptr, false, LOCATION_CHANGE_SAME_DOCUMENT); + if (mLoadType != LOAD_ERROR_PAGE) { + FireDummyOnLocationChange(); + } + + AddURIVisit(newURI, oldURI, oldURI, 0); + + // AddURIVisit doesn't set the title for the new URI in global history, + // so do that here. + if (mUseGlobalHistory && !UsePrivateBrowsing()) { + nsCOMPtr<IHistory> history = services::GetHistoryService(); + if (history) { + history->SetURITitle(newURI, mTitle); + } else if (mGlobalHistory) { + mGlobalHistory->SetPageTitle(newURI, mTitle); + } + } + + // Inform the favicon service that our old favicon applies to this new + // URI. + CopyFavicon(oldURI, newURI, document->NodePrincipal(), UsePrivateBrowsing()); + } else { + FireDummyOnLocationChange(); + } + document->SetStateObject(scContainer); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetCurrentScrollRestorationIsManual(bool* aIsManual) +{ + *aIsManual = false; + if (mOSHE) { + mOSHE->GetScrollRestorationIsManual(aIsManual); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetCurrentScrollRestorationIsManual(bool aIsManual) +{ + if (mOSHE) { + mOSHE->SetScrollRestorationIsManual(aIsManual); + } + + return NS_OK; +} + +bool +nsDocShell::ShouldAddToSessionHistory(nsIURI* aURI) +{ + // I believe none of the about: urls should go in the history. But then + // that could just be me... If the intent is only deny about:blank then we + // should just do a spec compare, rather than two gets of the scheme and + // then the path. -Gagan + nsresult rv; + nsAutoCString buf; + + rv = aURI->GetScheme(buf); + if (NS_FAILED(rv)) { + return false; + } + + if (buf.EqualsLiteral("about")) { + rv = aURI->GetPath(buf); + if (NS_FAILED(rv)) { + return false; + } + + if (buf.EqualsLiteral("blank") || buf.EqualsLiteral("newtab")) { + return false; + } + } + + return true; +} + +nsresult +nsDocShell::AddToSessionHistory(nsIURI* aURI, nsIChannel* aChannel, + nsIPrincipal* aTriggeringPrincipal, + nsIPrincipal* aPrincipalToInherit, + bool aCloneChildren, + nsISHEntry** aNewEntry) +{ + NS_PRECONDITION(aURI, "uri is null"); + NS_PRECONDITION(!aChannel || !aTriggeringPrincipal, "Shouldn't have both set"); + +#if defined(DEBUG) + if (MOZ_LOG_TEST(gDocShellLog, LogLevel::Debug)) { + nsAutoCString chanName; + if (aChannel) { + aChannel->GetName(chanName); + } else { + chanName.AssignLiteral("<no channel>"); + } + + MOZ_LOG(gDocShellLog, LogLevel::Debug, + ("nsDocShell[%p]::AddToSessionHistory(\"%s\", [%s])\n", + this, aURI->GetSpecOrDefault().get(), chanName.get())); + } +#endif + + nsresult rv = NS_OK; + nsCOMPtr<nsISHEntry> entry; + bool shouldPersist; + + shouldPersist = ShouldAddToSessionHistory(aURI); + + // Get a handle to the root docshell + nsCOMPtr<nsIDocShellTreeItem> root; + GetSameTypeRootTreeItem(getter_AddRefs(root)); + /* + * If this is a LOAD_FLAGS_REPLACE_HISTORY in a subframe, we use + * the existing SH entry in the page and replace the url and + * other vitalities. + */ + if (LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY) && + root != static_cast<nsIDocShellTreeItem*>(this)) { + // This is a subframe + entry = mOSHE; + nsCOMPtr<nsISHContainer> shContainer(do_QueryInterface(entry)); + if (shContainer) { + int32_t childCount = 0; + shContainer->GetChildCount(&childCount); + // Remove all children of this entry + for (int32_t i = childCount - 1; i >= 0; i--) { + nsCOMPtr<nsISHEntry> child; + shContainer->GetChildAt(i, getter_AddRefs(child)); + shContainer->RemoveChild(child); + } + entry->AbandonBFCacheEntry(); + } + } + + // Create a new entry if necessary. + if (!entry) { + entry = do_CreateInstance(NS_SHENTRY_CONTRACTID); + + if (!entry) { + return NS_ERROR_OUT_OF_MEMORY; + } + } + + // Get the post data & referrer + nsCOMPtr<nsIInputStream> inputStream; + nsCOMPtr<nsIURI> originalURI; + bool loadReplace = false; + nsCOMPtr<nsIURI> referrerURI; + uint32_t referrerPolicy = mozilla::net::RP_Default; + nsCOMPtr<nsISupports> cacheKey; + nsCOMPtr<nsIPrincipal> triggeringPrincipal = aTriggeringPrincipal; + nsCOMPtr<nsIPrincipal> principalToInherit = aPrincipalToInherit; + bool expired = false; + bool discardLayoutState = false; + nsCOMPtr<nsICacheInfoChannel> cacheChannel; + if (aChannel) { + cacheChannel = do_QueryInterface(aChannel); + + /* If there is a caching channel, get the Cache Key and store it + * in SH. + */ + if (cacheChannel) { + cacheChannel->GetCacheKey(getter_AddRefs(cacheKey)); + } + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + + // Check if the httpChannel is hiding under a multipartChannel + if (!httpChannel) { + GetHttpChannel(aChannel, getter_AddRefs(httpChannel)); + } + if (httpChannel) { + nsCOMPtr<nsIUploadChannel> uploadChannel(do_QueryInterface(httpChannel)); + if (uploadChannel) { + uploadChannel->GetUploadStream(getter_AddRefs(inputStream)); + } + httpChannel->GetOriginalURI(getter_AddRefs(originalURI)); + uint32_t loadFlags; + aChannel->GetLoadFlags(&loadFlags); + loadReplace = loadFlags & nsIChannel::LOAD_REPLACE; + httpChannel->GetReferrer(getter_AddRefs(referrerURI)); + httpChannel->GetReferrerPolicy(&referrerPolicy); + + discardLayoutState = ShouldDiscardLayoutState(httpChannel); + } + + // XXX Bug 1286838: Replace channel owner with loadInfo triggeringPrincipal + nsCOMPtr<nsISupports> owner; + aChannel->GetOwner(getter_AddRefs(owner)); + triggeringPrincipal = do_QueryInterface(owner); + + nsCOMPtr<nsILoadInfo> loadInfo = aChannel->GetLoadInfo(); + if (loadInfo) { + if (!triggeringPrincipal) { + triggeringPrincipal = loadInfo->TriggeringPrincipal(); + } + + // For now keep storing just the principal in the SHEntry. + if (!principalToInherit) { + if (loadInfo->GetLoadingSandboxed()) { + if (loadInfo->LoadingPrincipal()) { + principalToInherit = nsNullPrincipal::CreateWithInheritedAttributes( + loadInfo->LoadingPrincipal()); + } else { + // get the OriginAttributes + NeckoOriginAttributes nAttrs; + loadInfo->GetOriginAttributes(&nAttrs); + PrincipalOriginAttributes pAttrs; + pAttrs.InheritFromNecko(nAttrs); + principalToInherit = nsNullPrincipal::Create(pAttrs); + } + } else { + principalToInherit = loadInfo->PrincipalToInherit(); + } + } + } + } + + // Title is set in nsDocShell::SetTitle() + entry->Create(aURI, // uri + EmptyString(), // Title + inputStream, // Post data stream + nullptr, // LayoutHistory state + cacheKey, // CacheKey + mContentTypeHint, // Content-type + triggeringPrincipal, // Channel or provided principal + principalToInherit, + mHistoryID, + mDynamicallyCreated); + + entry->SetOriginalURI(originalURI); + entry->SetLoadReplace(loadReplace); + entry->SetReferrerURI(referrerURI); + entry->SetReferrerPolicy(referrerPolicy); + nsCOMPtr<nsIInputStreamChannel> inStrmChan = do_QueryInterface(aChannel); + if (inStrmChan) { + bool isSrcdocChannel; + inStrmChan->GetIsSrcdocChannel(&isSrcdocChannel); + if (isSrcdocChannel) { + nsAutoString srcdoc; + inStrmChan->GetSrcdocData(srcdoc); + entry->SetSrcdocData(srcdoc); + nsCOMPtr<nsIURI> baseURI; + inStrmChan->GetBaseURI(getter_AddRefs(baseURI)); + entry->SetBaseURI(baseURI); + } + } + /* If cache got a 'no-store', ask SH not to store + * HistoryLayoutState. By default, SH will set this + * flag to true and save HistoryLayoutState. + */ + if (discardLayoutState) { + entry->SetSaveLayoutStateFlag(false); + } + 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; + } + } + if (expired) { + entry->SetExpirationStatus(true); + } + + if (root == static_cast<nsIDocShellTreeItem*>(this) && mSessionHistory) { + // If we need to clone our children onto the new session + // history entry, do so now. + if (aCloneChildren && mOSHE) { + uint32_t cloneID; + mOSHE->GetID(&cloneID); + nsCOMPtr<nsISHEntry> newEntry; + CloneAndReplace(mOSHE, this, cloneID, entry, true, + getter_AddRefs(newEntry)); + NS_ASSERTION(entry == newEntry, + "The new session history should be in the new entry"); + } + + // This is the root docshell + bool addToSHistory = !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY); + if (!addToSHistory) { + // Replace current entry in session history; If the requested index is + // valid, it indicates the loading was triggered by a history load, and + // we should replace the entry at requested index instead. + int32_t index = 0; + mSessionHistory->GetRequestedIndex(&index); + if (index == -1) { + mSessionHistory->GetIndex(&index); + } + nsCOMPtr<nsISHistoryInternal> shPrivate = + do_QueryInterface(mSessionHistory); + // Replace the current entry with the new entry + if (index >= 0) { + if (shPrivate) { + rv = shPrivate->ReplaceEntry(index, entry); + } + } else { + // If we're trying to replace an inexistant shistory entry, append. + addToSHistory = true; + } + } + + if (addToSHistory) { + // Add to session history + nsCOMPtr<nsISHistoryInternal> shPrivate = + do_QueryInterface(mSessionHistory); + NS_ENSURE_TRUE(shPrivate, NS_ERROR_FAILURE); + mSessionHistory->GetIndex(&mPreviousTransIndex); + rv = shPrivate->AddEntry(entry, shouldPersist); + mSessionHistory->GetIndex(&mLoadedTransIndex); +#ifdef DEBUG_PAGE_CACHE + printf("Previous index: %d, Loaded index: %d\n\n", + mPreviousTransIndex, mLoadedTransIndex); +#endif + } + } else { + // This is a subframe. + if (!mOSHE || !LOAD_TYPE_HAS_FLAGS(mLoadType, LOAD_FLAGS_REPLACE_HISTORY)) { + rv = AddChildSHEntryToParent(entry, mChildOffset, aCloneChildren); + } + } + + // Return the new SH entry... + if (aNewEntry) { + *aNewEntry = nullptr; + if (NS_SUCCEEDED(rv)) { + entry.forget(aNewEntry); + } + } + + return rv; +} + +nsresult +nsDocShell::LoadHistoryEntry(nsISHEntry* aEntry, uint32_t aLoadType) +{ + if (!IsNavigationAllowed()) { + return NS_OK; + } + + nsCOMPtr<nsIURI> uri; + nsCOMPtr<nsIURI> originalURI; + bool loadReplace = false; + nsCOMPtr<nsIInputStream> postData; + nsCOMPtr<nsIURI> referrerURI; + uint32_t referrerPolicy; + nsAutoCString contentType; + nsCOMPtr<nsIPrincipal> triggeringPrincipal; + nsCOMPtr<nsIPrincipal> principalToInherit; + + NS_ENSURE_TRUE(aEntry, NS_ERROR_FAILURE); + + NS_ENSURE_SUCCESS(aEntry->GetURI(getter_AddRefs(uri)), NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetOriginalURI(getter_AddRefs(originalURI)), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetLoadReplace(&loadReplace), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetReferrerURI(getter_AddRefs(referrerURI)), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetReferrerPolicy(&referrerPolicy), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetPostData(getter_AddRefs(postData)), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetContentType(contentType), NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetTriggeringPrincipal(getter_AddRefs(triggeringPrincipal)), + NS_ERROR_FAILURE); + NS_ENSURE_SUCCESS(aEntry->GetPrincipalToInherit(getter_AddRefs(principalToInherit)), + NS_ERROR_FAILURE); + + // Calling CreateAboutBlankContentViewer can set mOSHE to null, and if + // that's the only thing holding a ref to aEntry that will cause aEntry to + // die while we're loading it. So hold a strong ref to aEntry here, just + // in case. + nsCOMPtr<nsISHEntry> kungFuDeathGrip(aEntry); + bool isJS; + nsresult rv = uri->SchemeIs("javascript", &isJS); + if (NS_FAILED(rv) || isJS) { + // We're loading a URL that will execute script from inside asyncOpen. + // Replace the current document with about:blank now to prevent + // anything from the current document from leaking into any JavaScript + // code in the URL. + // Don't cache the presentation if we're going to just reload the + // current entry. Caching would lead to trying to save the different + // content viewers in the same nsISHEntry object. + rv = CreateAboutBlankContentViewer(principalToInherit, nullptr, + aEntry != mOSHE); + + if (NS_FAILED(rv)) { + // The creation of the intermittent about:blank content + // viewer failed for some reason (potentially because the + // user prevented it). Interrupt the history load. + return NS_OK; + } + + if (!triggeringPrincipal) { + // Ensure that we have a triggeringPrincipal. Otherwise javascript: + // URIs will pick it up from the about:blank page we just loaded, + // and we don't really want even that in this case. + triggeringPrincipal = nsNullPrincipal::CreateWithInheritedAttributes(this); + } + } + + /* If there is a valid postdata *and* the user pressed + * reload or shift-reload, take user's permission before we + * repost the data to the server. + */ + if ((aLoadType & LOAD_CMD_RELOAD) && postData) { + bool repost; + rv = ConfirmRepost(&repost); + if (NS_FAILED(rv)) { + return rv; + } + + // If the user pressed cancel in the dialog, return. We're done here. + if (!repost) { + return NS_BINDING_ABORTED; + } + } + + // Do not inherit principal from document (security-critical!); + uint32_t flags = INTERNAL_LOAD_FLAGS_NONE; + + nsAutoString srcdoc; + bool isSrcdoc; + nsCOMPtr<nsIURI> baseURI; + aEntry->GetIsSrcdocEntry(&isSrcdoc); + if (isSrcdoc) { + aEntry->GetSrcdocData(srcdoc); + aEntry->GetBaseURI(getter_AddRefs(baseURI)); + flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC; + } else { + srcdoc = NullString(); + } + + // If there is no triggeringPrincipal we can fall back to using the + // SystemPrincipal as the triggeringPrincipal for loading the history + // entry, since the history entry can only end up in history if security + // checks passed in the initial loading phase. + if (!triggeringPrincipal) { + triggeringPrincipal = nsContentUtils::GetSystemPrincipal(); + } + + // Passing nullptr as aSourceDocShell gives the same behaviour as before + // aSourceDocShell was introduced. According to spec we should be passing + // the source browsing context that was used when the history entry was + // first created. bug 947716 has been created to address this issue. + rv = InternalLoad(uri, + originalURI, + loadReplace, + referrerURI, + referrerPolicy, + triggeringPrincipal, + principalToInherit, + flags, + EmptyString(), // No window target + contentType.get(), // Type hint + NullString(), // No forced file download + postData, // Post data stream + nullptr, // No headers stream + aLoadType, // Load type + aEntry, // SHEntry + true, + srcdoc, + nullptr, // Source docshell, see comment above + baseURI, + nullptr, // No nsIDocShell + nullptr); // No nsIRequest + return rv; +} + +NS_IMETHODIMP +nsDocShell::GetShouldSaveLayoutState(bool* aShould) +{ + *aShould = false; + if (mOSHE) { + // Don't capture historystate and save it in history + // if the page asked not to do so. + mOSHE->GetSaveLayoutStateFlag(aShould); + } + + return NS_OK; +} + +nsresult +nsDocShell::PersistLayoutHistoryState() +{ + nsresult rv = NS_OK; + + if (mOSHE) { + bool scrollRestorationIsManual = false; + mOSHE->GetScrollRestorationIsManual(&scrollRestorationIsManual); + + nsCOMPtr<nsIPresShell> shell = GetPresShell(); + nsCOMPtr<nsILayoutHistoryState> layoutState; + if (shell) { + rv = shell->CaptureHistoryState(getter_AddRefs(layoutState)); + } else if (scrollRestorationIsManual) { + // Even if we don't have layout anymore, we may want to reset the current + // scroll state in layout history. + GetLayoutHistoryState(getter_AddRefs(layoutState)); + } + + if (scrollRestorationIsManual && layoutState) { + layoutState->ResetScrollState(); + } + } + + return rv; +} + +/* static */ nsresult +nsDocShell::WalkHistoryEntries(nsISHEntry* aRootEntry, + nsDocShell* aRootShell, + WalkHistoryEntriesFunc aCallback, + void* aData) +{ + NS_ENSURE_TRUE(aRootEntry, NS_ERROR_FAILURE); + + nsCOMPtr<nsISHContainer> container(do_QueryInterface(aRootEntry)); + if (!container) { + return NS_ERROR_FAILURE; + } + + int32_t childCount; + container->GetChildCount(&childCount); + for (int32_t i = 0; i < childCount; i++) { + nsCOMPtr<nsISHEntry> childEntry; + container->GetChildAt(i, getter_AddRefs(childEntry)); + if (!childEntry) { + // childEntry can be null for valid reasons, for example if the + // docshell at index i never loaded anything useful. + // Remember to clone also nulls in the child array (bug 464064). + aCallback(nullptr, nullptr, i, aData); + continue; + } + + nsDocShell* childShell = nullptr; + if (aRootShell) { + // Walk the children of aRootShell and see if one of them + // has srcChild as a SHEntry. + nsTObserverArray<nsDocLoader*>::ForwardIterator iter( + aRootShell->mChildList); + while (iter.HasMore()) { + nsDocShell* child = static_cast<nsDocShell*>(iter.GetNext()); + + if (child->HasHistoryEntry(childEntry)) { + childShell = child; + break; + } + } + } + nsresult rv = aCallback(childEntry, childShell, i, aData); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +// callback data for WalkHistoryEntries +struct MOZ_STACK_CLASS CloneAndReplaceData +{ + CloneAndReplaceData(uint32_t aCloneID, nsISHEntry* aReplaceEntry, + bool aCloneChildren, nsISHEntry* aDestTreeParent) + : cloneID(aCloneID) + , cloneChildren(aCloneChildren) + , replaceEntry(aReplaceEntry) + , destTreeParent(aDestTreeParent) + { + } + + uint32_t cloneID; + bool cloneChildren; + nsISHEntry* replaceEntry; + nsISHEntry* destTreeParent; + nsCOMPtr<nsISHEntry> resultEntry; +}; + +/* static */ nsresult +nsDocShell::CloneAndReplaceChild(nsISHEntry* aEntry, nsDocShell* aShell, + int32_t aEntryIndex, void* aData) +{ + nsCOMPtr<nsISHEntry> dest; + + CloneAndReplaceData* data = static_cast<CloneAndReplaceData*>(aData); + uint32_t cloneID = data->cloneID; + nsISHEntry* replaceEntry = data->replaceEntry; + + nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent); + if (!aEntry) { + if (container) { + container->AddChild(nullptr, aEntryIndex); + } + return NS_OK; + } + + uint32_t srcID; + aEntry->GetID(&srcID); + + nsresult rv = NS_OK; + if (srcID == cloneID) { + // Replace the entry + dest = replaceEntry; + } else { + // Clone the SHEntry... + rv = aEntry->Clone(getter_AddRefs(dest)); + NS_ENSURE_SUCCESS(rv, rv); + } + dest->SetIsSubFrame(true); + + if (srcID != cloneID || data->cloneChildren) { + // Walk the children + CloneAndReplaceData childData(cloneID, replaceEntry, + data->cloneChildren, dest); + rv = WalkHistoryEntries(aEntry, aShell, + CloneAndReplaceChild, &childData); + NS_ENSURE_SUCCESS(rv, rv); + } + + if (srcID != cloneID && aShell) { + aShell->SwapHistoryEntries(aEntry, dest); + } + + if (container) { + container->AddChild(dest, aEntryIndex); + } + + data->resultEntry = dest; + return rv; +} + +/* static */ nsresult +nsDocShell::CloneAndReplace(nsISHEntry* aSrcEntry, + nsDocShell* aSrcShell, + uint32_t aCloneID, + nsISHEntry* aReplaceEntry, + bool aCloneChildren, + nsISHEntry** aResultEntry) +{ + NS_ENSURE_ARG_POINTER(aResultEntry); + NS_ENSURE_TRUE(aReplaceEntry, NS_ERROR_FAILURE); + + CloneAndReplaceData data(aCloneID, aReplaceEntry, aCloneChildren, nullptr); + nsresult rv = CloneAndReplaceChild(aSrcEntry, aSrcShell, 0, &data); + + data.resultEntry.swap(*aResultEntry); + return rv; +} + +void +nsDocShell::SwapHistoryEntries(nsISHEntry* aOldEntry, nsISHEntry* aNewEntry) +{ + if (aOldEntry == mOSHE) { + mOSHE = aNewEntry; + } + + if (aOldEntry == mLSHE) { + mLSHE = aNewEntry; + } +} + +struct SwapEntriesData +{ + nsDocShell* ignoreShell; // constant; the shell to ignore + nsISHEntry* destTreeRoot; // constant; the root of the dest tree + nsISHEntry* destTreeParent; // constant; the node under destTreeRoot + // whose children will correspond to aEntry +}; + +nsresult +nsDocShell::SetChildHistoryEntry(nsISHEntry* aEntry, nsDocShell* aShell, + int32_t aEntryIndex, void* aData) +{ + SwapEntriesData* data = static_cast<SwapEntriesData*>(aData); + nsDocShell* ignoreShell = data->ignoreShell; + + if (!aShell || aShell == ignoreShell) { + return NS_OK; + } + + nsISHEntry* destTreeRoot = data->destTreeRoot; + + nsCOMPtr<nsISHEntry> destEntry; + nsCOMPtr<nsISHContainer> container = do_QueryInterface(data->destTreeParent); + + if (container) { + // aEntry is a clone of some child of destTreeParent, but since the + // trees aren't necessarily in sync, we'll have to locate it. + // Note that we could set aShell's entry to null if we don't find a + // corresponding entry under destTreeParent. + + uint32_t targetID, id; + aEntry->GetID(&targetID); + + // First look at the given index, since this is the common case. + nsCOMPtr<nsISHEntry> entry; + container->GetChildAt(aEntryIndex, getter_AddRefs(entry)); + if (entry && NS_SUCCEEDED(entry->GetID(&id)) && id == targetID) { + destEntry.swap(entry); + } else { + int32_t childCount; + container->GetChildCount(&childCount); + for (int32_t i = 0; i < childCount; ++i) { + container->GetChildAt(i, getter_AddRefs(entry)); + if (!entry) { + continue; + } + + entry->GetID(&id); + if (id == targetID) { + destEntry.swap(entry); + break; + } + } + } + } else { + destEntry = destTreeRoot; + } + + aShell->SwapHistoryEntries(aEntry, destEntry); + + // Now handle the children of aEntry. + SwapEntriesData childData = { ignoreShell, destTreeRoot, destEntry }; + return WalkHistoryEntries(aEntry, aShell, SetChildHistoryEntry, &childData); +} + +static nsISHEntry* +GetRootSHEntry(nsISHEntry* aEntry) +{ + nsCOMPtr<nsISHEntry> rootEntry = aEntry; + nsISHEntry* result = nullptr; + while (rootEntry) { + result = rootEntry; + result->GetParent(getter_AddRefs(rootEntry)); + } + + return result; +} + +void +nsDocShell::SetHistoryEntry(nsCOMPtr<nsISHEntry>* aPtr, nsISHEntry* aEntry) +{ + // We need to sync up the docshell and session history trees for + // subframe navigation. If the load was in a subframe, we forward up to + // the root docshell, which will then recursively sync up all docshells + // to their corresponding entries in the new session history tree. + // If we don't do this, then we can cache a content viewer on the wrong + // cloned entry, and subsequently restore it at the wrong time. + + nsISHEntry* newRootEntry = GetRootSHEntry(aEntry); + if (newRootEntry) { + // newRootEntry is now the new root entry. + // Find the old root entry as well. + + // Need a strong ref. on |oldRootEntry| so it isn't destroyed when + // SetChildHistoryEntry() does SwapHistoryEntries() (bug 304639). + nsCOMPtr<nsISHEntry> oldRootEntry = GetRootSHEntry(*aPtr); + if (oldRootEntry) { + nsCOMPtr<nsIDocShellTreeItem> rootAsItem; + GetSameTypeRootTreeItem(getter_AddRefs(rootAsItem)); + nsCOMPtr<nsIDocShell> rootShell = do_QueryInterface(rootAsItem); + if (rootShell) { // if we're the root just set it, nothing to swap + SwapEntriesData data = { this, newRootEntry }; + nsIDocShell* rootIDocShell = static_cast<nsIDocShell*>(rootShell); + nsDocShell* rootDocShell = static_cast<nsDocShell*>(rootIDocShell); + +#ifdef DEBUG + nsresult rv = +#endif + SetChildHistoryEntry(oldRootEntry, rootDocShell, 0, &data); + NS_ASSERTION(NS_SUCCEEDED(rv), "SetChildHistoryEntry failed"); + } + } + } + + *aPtr = aEntry; +} + +nsresult +nsDocShell::GetRootSessionHistory(nsISHistory** aReturn) +{ + nsresult rv; + + nsCOMPtr<nsIDocShellTreeItem> root; + // Get the root docshell + rv = GetSameTypeRootTreeItem(getter_AddRefs(root)); + // QI to nsIWebNavigation + nsCOMPtr<nsIWebNavigation> rootAsWebnav(do_QueryInterface(root)); + if (rootAsWebnav) { + // Get the handle to SH from the root docshell + rv = rootAsWebnav->GetSessionHistory(aReturn); + } + return rv; +} + +nsresult +nsDocShell::GetHttpChannel(nsIChannel* aChannel, nsIHttpChannel** aReturn) +{ + NS_ENSURE_ARG_POINTER(aReturn); + if (!aChannel) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIMultiPartChannel> multiPartChannel(do_QueryInterface(aChannel)); + if (multiPartChannel) { + nsCOMPtr<nsIChannel> baseChannel; + multiPartChannel->GetBaseChannel(getter_AddRefs(baseChannel)); + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(baseChannel)); + *aReturn = httpChannel; + NS_IF_ADDREF(*aReturn); + } + return NS_OK; +} + +bool +nsDocShell::ShouldDiscardLayoutState(nsIHttpChannel* aChannel) +{ + // By default layout State will be saved. + if (!aChannel) { + return false; + } + + // figure out if SH should be saving layout state + bool noStore = false; + aChannel->IsNoStoreResponse(&noStore); + return noStore; +} + +NS_IMETHODIMP +nsDocShell::GetEditor(nsIEditor** aEditor) +{ + NS_ENSURE_ARG_POINTER(aEditor); + + if (!mEditorData) { + *aEditor = nullptr; + return NS_OK; + } + + return mEditorData->GetEditor(aEditor); +} + +NS_IMETHODIMP +nsDocShell::SetEditor(nsIEditor* aEditor) +{ + nsresult rv = EnsureEditorData(); + if (NS_FAILED(rv)) { + return rv; + } + + return mEditorData->SetEditor(aEditor); +} + +NS_IMETHODIMP +nsDocShell::GetEditable(bool* aEditable) +{ + NS_ENSURE_ARG_POINTER(aEditable); + *aEditable = mEditorData && mEditorData->GetEditable(); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetHasEditingSession(bool* aHasEditingSession) +{ + NS_ENSURE_ARG_POINTER(aHasEditingSession); + + if (mEditorData) { + nsCOMPtr<nsIEditingSession> editingSession; + mEditorData->GetEditingSession(getter_AddRefs(editingSession)); + *aHasEditingSession = (editingSession.get() != nullptr); + } else { + *aHasEditingSession = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::MakeEditable(bool aInWaitForUriLoad) +{ + nsresult rv = EnsureEditorData(); + if (NS_FAILED(rv)) { + return rv; + } + + return mEditorData->MakeEditable(aInWaitForUriLoad); +} + +bool +nsDocShell::ChannelIsPost(nsIChannel* aChannel) +{ + nsCOMPtr<nsIHttpChannel> httpChannel(do_QueryInterface(aChannel)); + if (!httpChannel) { + return false; + } + + nsAutoCString method; + httpChannel->GetRequestMethod(method); + return method.EqualsLiteral("POST"); +} + +void +nsDocShell::ExtractLastVisit(nsIChannel* aChannel, + nsIURI** aURI, + uint32_t* aChannelRedirectFlags) +{ + nsCOMPtr<nsIPropertyBag2> props(do_QueryInterface(aChannel)); + if (!props) { + return; + } + + nsresult rv = props->GetPropertyAsInterface( + NS_LITERAL_STRING("docshell.previousURI"), + NS_GET_IID(nsIURI), + reinterpret_cast<void**>(aURI)); + + if (NS_FAILED(rv)) { + // There is no last visit for this channel, so this must be the first + // link. Link the visit to the referrer of this request, if any. + // Treat referrer as null if there is an error getting it. + (void)NS_GetReferrerFromChannel(aChannel, aURI); + } else { + rv = props->GetPropertyAsUint32(NS_LITERAL_STRING("docshell.previousFlags"), + aChannelRedirectFlags); + + NS_WARNING_ASSERTION( + NS_SUCCEEDED(rv), + "Could not fetch previous flags, URI will be treated like referrer"); + } +} + +void +nsDocShell::SaveLastVisit(nsIChannel* aChannel, + nsIURI* aURI, + uint32_t aChannelRedirectFlags) +{ + nsCOMPtr<nsIWritablePropertyBag2> props(do_QueryInterface(aChannel)); + if (!props || !aURI) { + return; + } + + props->SetPropertyAsInterface(NS_LITERAL_STRING("docshell.previousURI"), + aURI); + props->SetPropertyAsUint32(NS_LITERAL_STRING("docshell.previousFlags"), + aChannelRedirectFlags); +} + +void +nsDocShell::AddURIVisit(nsIURI* aURI, + nsIURI* aReferrerURI, + nsIURI* aPreviousURI, + uint32_t aChannelRedirectFlags, + uint32_t aResponseStatus) +{ + MOZ_ASSERT(aURI, "Visited URI is null!"); + MOZ_ASSERT(mLoadType != LOAD_ERROR_PAGE && + mLoadType != LOAD_BYPASS_HISTORY, + "Do not add error or bypass pages to global history"); + + // Only content-type docshells save URI visits. Also don't do + // anything here if we're not supposed to use global history. + if (mItemType != typeContent || !mUseGlobalHistory || UsePrivateBrowsing()) { + return; + } + + nsCOMPtr<IHistory> history = services::GetHistoryService(); + + if (history) { + uint32_t visitURIFlags = 0; + + if (!IsFrame()) { + visitURIFlags |= IHistory::TOP_LEVEL; + } + + if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_TEMPORARY) { + visitURIFlags |= IHistory::REDIRECT_TEMPORARY; + } else if (aChannelRedirectFlags & nsIChannelEventSink::REDIRECT_PERMANENT) { + visitURIFlags |= IHistory::REDIRECT_PERMANENT; + } + + if (aResponseStatus >= 300 && aResponseStatus < 400) { + visitURIFlags |= IHistory::REDIRECT_SOURCE; + } + // Errors 400-501 and 505 are considered unrecoverable, in the sense a + // simple retry attempt by the user is unlikely to solve them. + // 408 is special cased, since may actually indicate a temporary + // connection problem. + else if (aResponseStatus != 408 && + ((aResponseStatus >= 400 && aResponseStatus <= 501) || + aResponseStatus == 505)) { + visitURIFlags |= IHistory::UNRECOVERABLE_ERROR; + } + + (void)history->VisitURI(aURI, aPreviousURI, visitURIFlags); + } else if (mGlobalHistory) { + // Falls back to sync global history interface. + (void)mGlobalHistory->AddURI(aURI, + !!aChannelRedirectFlags, + !IsFrame(), + aReferrerURI); + } +} + +//***************************************************************************** +// nsDocShell: Helper Routines +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::SetLoadType(uint32_t aLoadType) +{ + mLoadType = aLoadType; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetLoadType(uint32_t* aLoadType) +{ + *aLoadType = mLoadType; + return NS_OK; +} + +nsresult +nsDocShell::ConfirmRepost(bool* aRepost) +{ + nsCOMPtr<nsIPrompt> prompter; + CallGetInterface(this, static_cast<nsIPrompt**>(getter_AddRefs(prompter))); + if (!prompter) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + if (!stringBundleService) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIStringBundle> appBundle; + nsresult rv = stringBundleService->CreateBundle(kAppstringsBundleURL, + getter_AddRefs(appBundle)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIStringBundle> brandBundle; + rv = stringBundleService->CreateBundle(kBrandBundleURL, + getter_AddRefs(brandBundle)); + NS_ENSURE_SUCCESS(rv, rv); + + NS_ASSERTION(prompter && brandBundle && appBundle, + "Unable to set up repost prompter."); + + nsXPIDLString brandName; + rv = brandBundle->GetStringFromName(u"brandShortName", + getter_Copies(brandName)); + + nsXPIDLString msgString, button0Title; + if (NS_FAILED(rv)) { // No brand, use the generic version. + rv = appBundle->GetStringFromName(u"confirmRepostPrompt", + getter_Copies(msgString)); + } else { + // Brand available - if the app has an override file with formatting, the + // app name will be included. Without an override, the prompt will look + // like the generic version. + const char16_t* formatStrings[] = { brandName.get() }; + rv = appBundle->FormatStringFromName(u"confirmRepostPrompt", + formatStrings, + ArrayLength(formatStrings), + getter_Copies(msgString)); + } + if (NS_FAILED(rv)) { + return rv; + } + + rv = appBundle->GetStringFromName(u"resendButton.label", + getter_Copies(button0Title)); + if (NS_FAILED(rv)) { + return rv; + } + + int32_t buttonPressed; + // The actual value here is irrelevant, but we can't pass an invalid + // bool through XPConnect. + bool checkState = false; + rv = prompter->ConfirmEx( + nullptr, msgString.get(), + (nsIPrompt::BUTTON_POS_0 * nsIPrompt::BUTTON_TITLE_IS_STRING) + + (nsIPrompt::BUTTON_POS_1 * nsIPrompt::BUTTON_TITLE_CANCEL), + button0Title.get(), nullptr, nullptr, nullptr, &checkState, &buttonPressed); + if (NS_FAILED(rv)) { + return rv; + } + + *aRepost = (buttonPressed == 0); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetPromptAndStringBundle(nsIPrompt** aPrompt, + nsIStringBundle** aStringBundle) +{ + NS_ENSURE_SUCCESS(GetInterface(NS_GET_IID(nsIPrompt), (void**)aPrompt), + NS_ERROR_FAILURE); + + nsCOMPtr<nsIStringBundleService> stringBundleService = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(stringBundleService, NS_ERROR_FAILURE); + + NS_ENSURE_SUCCESS( + stringBundleService->CreateBundle(kAppstringsBundleURL, aStringBundle), + NS_ERROR_FAILURE); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetChildOffset(nsIDOMNode* aChild, nsIDOMNode* aParent, + int32_t* aOffset) +{ + NS_ENSURE_ARG_POINTER(aChild || aParent); + + nsCOMPtr<nsIDOMNodeList> childNodes; + NS_ENSURE_SUCCESS(aParent->GetChildNodes(getter_AddRefs(childNodes)), + NS_ERROR_FAILURE); + NS_ENSURE_TRUE(childNodes, NS_ERROR_FAILURE); + + int32_t i = 0; + + for (; true; i++) { + nsCOMPtr<nsIDOMNode> childNode; + NS_ENSURE_SUCCESS(childNodes->Item(i, getter_AddRefs(childNode)), + NS_ERROR_FAILURE); + NS_ENSURE_TRUE(childNode, NS_ERROR_FAILURE); + + if (childNode.get() == aChild) { + *aOffset = i; + return NS_OK; + } + } + + return NS_ERROR_FAILURE; +} + +nsIScrollableFrame* +nsDocShell::GetRootScrollFrame() +{ + nsCOMPtr<nsIPresShell> shell = GetPresShell(); + NS_ENSURE_TRUE(shell, nullptr); + + return shell->GetRootScrollFrameAsScrollableExternal(); +} + +NS_IMETHODIMP +nsDocShell::EnsureScriptEnvironment() +{ + if (mScriptGlobal) { + return NS_OK; + } + + if (mIsBeingDestroyed) { + return NS_ERROR_NOT_AVAILABLE; + } + +#ifdef DEBUG + NS_ASSERTION(!mInEnsureScriptEnv, + "Infinite loop! Calling EnsureScriptEnvironment() from " + "within EnsureScriptEnvironment()!"); + + // Yeah, this isn't re-entrant safe, but that's ok since if we + // re-enter this method, we'll infinitely loop... + AutoRestore<bool> boolSetter(mInEnsureScriptEnv); + mInEnsureScriptEnv = true; +#endif + + nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner)); + NS_ENSURE_TRUE(browserChrome, NS_ERROR_NOT_AVAILABLE); + + uint32_t chromeFlags; + browserChrome->GetChromeFlags(&chromeFlags); + + bool isModalContentWindow = + (mItemType == typeContent) && + (chromeFlags & nsIWebBrowserChrome::CHROME_MODAL_CONTENT_WINDOW); + // There can be various other content docshells associated with the + // top-level window, like sidebars. Make sure that we only create an + // nsGlobalModalWindow for the primary content shell. + if (isModalContentWindow) { + nsCOMPtr<nsIDocShellTreeItem> primaryItem; + nsresult rv = + mTreeOwner->GetPrimaryContentShell(getter_AddRefs(primaryItem)); + NS_ENSURE_SUCCESS(rv, rv); + isModalContentWindow = (primaryItem == this); + } + + // If our window is modal and we're not opened as chrome, make + // this window a modal content window. + mScriptGlobal = + NS_NewScriptGlobalObject(mItemType == typeChrome, isModalContentWindow); + MOZ_ASSERT(mScriptGlobal); + + mScriptGlobal->SetDocShell(this); + + // Ensure the script object is set up to run script. + return mScriptGlobal->EnsureScriptEnvironment(); +} + +NS_IMETHODIMP +nsDocShell::EnsureEditorData() +{ + bool openDocHasDetachedEditor = mOSHE && mOSHE->HasDetachedEditor(); + if (!mEditorData && !mIsBeingDestroyed && !openDocHasDetachedEditor) { + // We shouldn't recreate the editor data if it already exists, or + // we're shutting down, or we already have a detached editor data + // stored in the session history. We should only have one editordata + // per docshell. + mEditorData = new nsDocShellEditorData(this); + } + + return mEditorData ? NS_OK : NS_ERROR_NOT_AVAILABLE; +} + +nsresult +nsDocShell::EnsureTransferableHookData() +{ + if (!mTransferableHookData) { + mTransferableHookData = new nsTransferableHookData(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::EnsureFind() +{ + nsresult rv; + if (!mFind) { + mFind = do_CreateInstance("@mozilla.org/embedcomp/find;1", &rv); + if (NS_FAILED(rv)) { + return rv; + } + } + + // we promise that the nsIWebBrowserFind that we return has been set + // up to point to the focused, or content window, so we have to + // set that up each time. + + nsIScriptGlobalObject* scriptGO = GetScriptGlobalObject(); + NS_ENSURE_TRUE(scriptGO, NS_ERROR_UNEXPECTED); + + // default to our window + nsCOMPtr<nsPIDOMWindowOuter> ourWindow = do_QueryInterface(scriptGO); + nsCOMPtr<nsPIDOMWindowOuter> windowToSearch; + nsFocusManager::GetFocusedDescendant(ourWindow, true, + getter_AddRefs(windowToSearch)); + + nsCOMPtr<nsIWebBrowserFindInFrames> findInFrames = do_QueryInterface(mFind); + if (!findInFrames) { + return NS_ERROR_NO_INTERFACE; + } + + rv = findInFrames->SetRootSearchFrame(ourWindow); + if (NS_FAILED(rv)) { + return rv; + } + rv = findInFrames->SetCurrentSearchFrame(windowToSearch); + if (NS_FAILED(rv)) { + return rv; + } + + return NS_OK; +} + +bool +nsDocShell::IsFrame() +{ + nsCOMPtr<nsIDocShellTreeItem> parent; + GetSameTypeParent(getter_AddRefs(parent)); + return !!parent; +} + +NS_IMETHODIMP +nsDocShell::IsBeingDestroyed(bool* aDoomed) +{ + NS_ENSURE_ARG(aDoomed); + *aDoomed = mIsBeingDestroyed; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsExecutingOnLoadHandler(bool* aResult) +{ + NS_ENSURE_ARG(aResult); + *aResult = mIsExecutingOnLoadHandler; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetLayoutHistoryState(nsILayoutHistoryState** aLayoutHistoryState) +{ + if (mOSHE) { + mOSHE->GetLayoutHistoryState(aLayoutHistoryState); + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetLayoutHistoryState(nsILayoutHistoryState* aLayoutHistoryState) +{ + if (mOSHE) { + mOSHE->SetLayoutHistoryState(aLayoutHistoryState); + } + return NS_OK; +} + +nsRefreshTimer::nsRefreshTimer() + : mDelay(0), mRepeat(false), mMetaRefresh(false) +{ +} + +nsRefreshTimer::~nsRefreshTimer() +{ +} + +NS_IMPL_ADDREF(nsRefreshTimer) +NS_IMPL_RELEASE(nsRefreshTimer) + +NS_INTERFACE_MAP_BEGIN(nsRefreshTimer) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsITimerCallback) + NS_INTERFACE_MAP_ENTRY(nsITimerCallback) +NS_INTERFACE_MAP_END_THREADSAFE + +NS_IMETHODIMP +nsRefreshTimer::Notify(nsITimer* aTimer) +{ + NS_ASSERTION(mDocShell, "DocShell is somehow null"); + + if (mDocShell && aTimer) { + // Get the delay count to determine load type + uint32_t delay = 0; + aTimer->GetDelay(&delay); + mDocShell->ForceRefreshURIFromTimer(mURI, delay, mMetaRefresh, aTimer, mPrincipal); + } + return NS_OK; +} + +nsDocShell::InterfaceRequestorProxy::InterfaceRequestorProxy( + nsIInterfaceRequestor* aRequestor) +{ + if (aRequestor) { + mWeakPtr = do_GetWeakReference(aRequestor); + } +} + +nsDocShell::InterfaceRequestorProxy::~InterfaceRequestorProxy() +{ + mWeakPtr = nullptr; +} + +NS_IMPL_ISUPPORTS(nsDocShell::InterfaceRequestorProxy, nsIInterfaceRequestor) + +NS_IMETHODIMP +nsDocShell::InterfaceRequestorProxy::GetInterface(const nsIID& aIID, + void** aSink) +{ + NS_ENSURE_ARG_POINTER(aSink); + nsCOMPtr<nsIInterfaceRequestor> ifReq = do_QueryReferent(mWeakPtr); + if (ifReq) { + return ifReq->GetInterface(aIID, aSink); + } + *aSink = nullptr; + return NS_NOINTERFACE; +} + +nsresult +nsDocShell::SetBaseUrlForWyciwyg(nsIContentViewer* aContentViewer) +{ + if (!aContentViewer) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIURI> baseURI; + nsresult rv = NS_ERROR_NOT_AVAILABLE; + + if (sURIFixup) { + rv = sURIFixup->CreateExposableURI(mCurrentURI, getter_AddRefs(baseURI)); + } + + // Get the current document and set the base uri + if (baseURI) { + nsIDocument* document = aContentViewer->GetDocument(); + if (document) { + document->SetBaseURI(baseURI); + } + } + return rv; +} + +//***************************************************************************** +// nsDocShell::nsIAuthPromptProvider +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetAuthPrompt(uint32_t aPromptReason, const nsIID& aIID, + void** aResult) +{ + // a priority prompt request will override a false mAllowAuth setting + bool priorityPrompt = (aPromptReason == PROMPT_PROXY); + + if (!mAllowAuth && !priorityPrompt) { + return NS_ERROR_NOT_AVAILABLE; + } + + // we're either allowing auth, or it's a proxy request + nsresult rv; + nsCOMPtr<nsIPromptFactory> wwatch = + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnsureScriptEnvironment(); + NS_ENSURE_SUCCESS(rv, rv); + + // Get the an auth prompter for our window so that the parenting + // of the dialogs works as it should when using tabs. + + return wwatch->GetPrompt(mScriptGlobal->AsOuter(), aIID, + reinterpret_cast<void**>(aResult)); +} + +//***************************************************************************** +// nsDocShell::nsILoadContext +//***************************************************************************** + +NS_IMETHODIMP +nsDocShell::GetAssociatedWindow(mozIDOMWindowProxy** aWindow) +{ + CallGetInterface(this, aWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetTopWindow(mozIDOMWindowProxy** aWindow) +{ + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + if (win) { + win = win->GetTop(); + } + win.forget(aWindow); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetTopFrameElement(nsIDOMElement** aElement) +{ + *aElement = nullptr; + nsCOMPtr<nsPIDOMWindowOuter> win = GetWindow(); + if (!win) { + return NS_OK; + } + + nsCOMPtr<nsPIDOMWindowOuter> top = win->GetScriptableTop(); + NS_ENSURE_TRUE(top, NS_ERROR_FAILURE); + + // GetFrameElementInternal, /not/ GetScriptableFrameElement -- if |top| is + // inside <iframe mozbrowser>, we want to return the iframe, not null. + // And we want to cross the content/chrome boundary. + nsCOMPtr<nsIDOMElement> elt = + do_QueryInterface(top->GetFrameElementInternal()); + elt.forget(aElement); + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetNestedFrameId(uint64_t* aId) +{ + *aId = 0; + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::IsTrackingProtectionOn(bool* aIsTrackingProtectionOn) +{ + if (Preferences::GetBool("privacy.trackingprotection.enabled", false)) { + *aIsTrackingProtectionOn = true; + } else if (UsePrivateBrowsing() && + Preferences::GetBool("privacy.trackingprotection.pbmode.enabled", false)) { + *aIsTrackingProtectionOn = true; + } else { + *aIsTrackingProtectionOn = false; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetIsContent(bool* aIsContent) +{ + *aIsContent = (mItemType == typeContent); + return NS_OK; +} + +bool +nsDocShell::IsOKToLoadURI(nsIURI* aURI) +{ + NS_PRECONDITION(aURI, "Must have a URI!"); + + if (!mFiredUnloadEvent) { + return true; + } + + if (!mLoadingURI) { + return false; + } + + nsCOMPtr<nsIScriptSecurityManager> secMan = + do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID); + return secMan && + NS_SUCCEEDED(secMan->CheckSameOriginURI(aURI, mLoadingURI, false)); +} + +// +// Routines for selection and clipboard +// +nsresult +nsDocShell::GetControllerForCommand(const char* aCommand, + nsIController** aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = nullptr; + + NS_ENSURE_TRUE(mScriptGlobal, NS_ERROR_FAILURE); + + nsCOMPtr<nsPIWindowRoot> root = mScriptGlobal->GetTopWindowRoot(); + NS_ENSURE_TRUE(root, NS_ERROR_FAILURE); + + return root->GetControllerForCommand(aCommand, aResult); +} + +NS_IMETHODIMP +nsDocShell::IsCommandEnabled(const char* aCommand, bool* aResult) +{ + NS_ENSURE_ARG_POINTER(aResult); + *aResult = false; + + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsIController> controller; + rv = GetControllerForCommand(aCommand, getter_AddRefs(controller)); + if (controller) { + rv = controller->IsCommandEnabled(aCommand, aResult); + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::DoCommand(const char* aCommand) +{ + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsIController> controller; + rv = GetControllerForCommand(aCommand, getter_AddRefs(controller)); + if (controller) { + rv = controller->DoCommand(aCommand); + } + + return rv; +} + +NS_IMETHODIMP +nsDocShell::DoCommandWithParams(const char* aCommand, nsICommandParams* aParams) +{ + nsCOMPtr<nsIController> controller; + nsresult rv = GetControllerForCommand(aCommand, getter_AddRefs(controller)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr<nsICommandController> commandController = + do_QueryInterface(controller, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return commandController->DoCommandWithParams(aCommand, aParams); +} + +nsresult +nsDocShell::EnsureCommandHandler() +{ + if (!mCommandManager) { + nsCOMPtr<nsPICommandUpdater> commandUpdater = + do_CreateInstance("@mozilla.org/embedcomp/command-manager;1"); + if (!commandUpdater) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsCOMPtr<nsPIDOMWindowOuter> domWindow = GetWindow(); + nsresult rv = commandUpdater->Init(domWindow); + if (NS_SUCCEEDED(rv)) { + mCommandManager = do_QueryInterface(commandUpdater); + } + } + + return mCommandManager ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::CanCutSelection(bool* aResult) +{ + return IsCommandEnabled("cmd_cut", aResult); +} + +NS_IMETHODIMP +nsDocShell::CanCopySelection(bool* aResult) +{ + return IsCommandEnabled("cmd_copy", aResult); +} + +NS_IMETHODIMP +nsDocShell::CanCopyLinkLocation(bool* aResult) +{ + return IsCommandEnabled("cmd_copyLink", aResult); +} + +NS_IMETHODIMP +nsDocShell::CanCopyImageLocation(bool* aResult) +{ + return IsCommandEnabled("cmd_copyImageLocation", aResult); +} + +NS_IMETHODIMP +nsDocShell::CanCopyImageContents(bool* aResult) +{ + return IsCommandEnabled("cmd_copyImageContents", aResult); +} + +NS_IMETHODIMP +nsDocShell::CanPaste(bool* aResult) +{ + return IsCommandEnabled("cmd_paste", aResult); +} + +NS_IMETHODIMP +nsDocShell::CutSelection(void) +{ + return DoCommand("cmd_cut"); +} + +NS_IMETHODIMP +nsDocShell::CopySelection(void) +{ + return DoCommand("cmd_copy"); +} + +NS_IMETHODIMP +nsDocShell::CopyLinkLocation(void) +{ + return DoCommand("cmd_copyLink"); +} + +NS_IMETHODIMP +nsDocShell::CopyImageLocation(void) +{ + return DoCommand("cmd_copyImageLocation"); +} + +NS_IMETHODIMP +nsDocShell::CopyImageContents(void) +{ + return DoCommand("cmd_copyImageContents"); +} + +NS_IMETHODIMP +nsDocShell::Paste(void) +{ + return DoCommand("cmd_paste"); +} + +NS_IMETHODIMP +nsDocShell::SelectAll(void) +{ + return DoCommand("cmd_selectAll"); +} + +// +// SelectNone +// +// Collapses the current selection, insertion point ends up at beginning +// of previous selection. +// +NS_IMETHODIMP +nsDocShell::SelectNone(void) +{ + return DoCommand("cmd_selectNone"); +} + +// link handling + +class OnLinkClickEvent : public Runnable +{ +public: + OnLinkClickEvent(nsDocShell* aHandler, nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec, + const nsAString& aFileName, + nsIInputStream* aPostDataStream, + nsIInputStream* aHeadersDataStream, + bool aIsTrusted); + + NS_IMETHOD Run() override + { + nsAutoPopupStatePusher popupStatePusher(mPopupState); + + // We need to set up an AutoJSAPI here for the following reason: When we do + // OnLinkClickSync we'll eventually end up in nsGlobalWindow::OpenInternal + // which only does popup blocking if !LegacyIsCallerChromeOrNativeCode(). + // So we need to fake things so that we don't look like native code as far + // as LegacyIsCallerNativeCode() is concerned. + AutoJSAPI jsapi; + if (mIsTrusted || jsapi.Init(mContent->OwnerDoc()->GetScopeObject())) { + mHandler->OnLinkClickSync(mContent, mURI, + mTargetSpec.get(), mFileName, + mPostDataStream, mHeadersDataStream, + nullptr, nullptr); + } + return NS_OK; + } + +private: + RefPtr<nsDocShell> mHandler; + nsCOMPtr<nsIURI> mURI; + nsString mTargetSpec; + nsString mFileName; + nsCOMPtr<nsIInputStream> mPostDataStream; + nsCOMPtr<nsIInputStream> mHeadersDataStream; + nsCOMPtr<nsIContent> mContent; + PopupControlState mPopupState; + bool mIsTrusted; +}; + +OnLinkClickEvent::OnLinkClickEvent(nsDocShell* aHandler, + nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec, + const nsAString& aFileName, + nsIInputStream* aPostDataStream, + nsIInputStream* aHeadersDataStream, + bool aIsTrusted) + : mHandler(aHandler) + , mURI(aURI) + , mTargetSpec(aTargetSpec) + , mFileName(aFileName) + , mPostDataStream(aPostDataStream) + , mHeadersDataStream(aHeadersDataStream) + , mContent(aContent) + , mPopupState(mHandler->mScriptGlobal->GetPopupControlState()) + , mIsTrusted(aIsTrusted) +{ +} + +NS_IMETHODIMP +nsDocShell::OnLinkClick(nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec, + const nsAString& aFileName, + nsIInputStream* aPostDataStream, + nsIInputStream* aHeadersDataStream, + bool aIsTrusted) +{ + NS_ASSERTION(NS_IsMainThread(), "wrong thread"); + + if (!IsNavigationAllowed() || !IsOKToLoadURI(aURI)) { + return NS_OK; + } + + // On history navigation through Back/Forward buttons, don't execute + // automatic JavaScript redirection such as |anchorElement.click()| or + // |formElement.submit()|. + // + // XXX |formElement.submit()| bypasses this checkpoint because it calls + // nsDocShell::OnLinkClickSync(...) instead. + if (ShouldBlockLoadingForBackButton()) { + return NS_OK; + } + + if (aContent->IsEditable()) { + return NS_OK; + } + + nsresult rv = NS_ERROR_FAILURE; + nsAutoString target; + + nsCOMPtr<nsIWebBrowserChrome3> browserChrome3 = do_GetInterface(mTreeOwner); + if (browserChrome3) { + nsCOMPtr<nsIDOMNode> linkNode = do_QueryInterface(aContent); + nsAutoString oldTarget(aTargetSpec); + rv = browserChrome3->OnBeforeLinkTraversal(oldTarget, aURI, + linkNode, mIsAppTab, target); + } + + if (NS_FAILED(rv)) { + target = aTargetSpec; + } + + nsCOMPtr<nsIRunnable> ev = + new OnLinkClickEvent(this, aContent, aURI, target.get(), aFileName, + aPostDataStream, aHeadersDataStream, aIsTrusted); + return NS_DispatchToCurrentThread(ev); +} + +NS_IMETHODIMP +nsDocShell::OnLinkClickSync(nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec, + const nsAString& aFileName, + nsIInputStream* aPostDataStream, + nsIInputStream* aHeadersDataStream, + nsIDocShell** aDocShell, + nsIRequest** aRequest) +{ + // Initialize the DocShell / Request + if (aDocShell) { + *aDocShell = nullptr; + } + if (aRequest) { + *aRequest = nullptr; + } + + if (!IsNavigationAllowed() || !IsOKToLoadURI(aURI)) { + return NS_OK; + } + + // XXX When the linking node was HTMLFormElement, it is synchronous event. + // That is, the caller of this method is not |OnLinkClickEvent::Run()| + // but |HTMLFormElement::SubmitSubmission(...)|. + if (aContent->IsHTMLElement(nsGkAtoms::form) && + ShouldBlockLoadingForBackButton()) { + return NS_OK; + } + + if (aContent->IsEditable()) { + return NS_OK; + } + + { + // defer to an external protocol handler if necessary... + nsCOMPtr<nsIExternalProtocolService> extProtService = + do_GetService(NS_EXTERNALPROTOCOLSERVICE_CONTRACTID); + if (extProtService) { + nsAutoCString scheme; + aURI->GetScheme(scheme); + if (!scheme.IsEmpty()) { + // if the URL scheme does not correspond to an exposed protocol, then we + // need to hand this link click over to the external protocol handler. + bool isExposed; + nsresult rv = + extProtService->IsExposedProtocol(scheme.get(), &isExposed); + if (NS_SUCCEEDED(rv) && !isExposed) { + return extProtService->LoadURI(aURI, this); + } + } + } + } + + uint32_t flags = INTERNAL_LOAD_FLAGS_NONE; + if (IsElementAnchor(aContent)) { + MOZ_ASSERT(aContent->IsHTMLElement()); + nsAutoString referrer; + aContent->GetAttr(kNameSpaceID_None, nsGkAtoms::rel, referrer); + nsWhitespaceTokenizerTemplate<nsContentUtils::IsHTMLWhitespace> tok(referrer); + while (tok.hasMoreTokens()) { + const nsAString& token = tok.nextToken(); + if (token.LowerCaseEqualsLiteral("noreferrer")) { + flags |= INTERNAL_LOAD_FLAGS_DONT_SEND_REFERRER | + INTERNAL_LOAD_FLAGS_NO_OPENER; + // We now have all the flags we could possibly have, so just stop. + break; + } + if (token.LowerCaseEqualsLiteral("noopener")) { + flags |= INTERNAL_LOAD_FLAGS_NO_OPENER; + } + } + } + + // Get the owner document of the link that was clicked, this will be + // the document that the link is in, or the last document that the + // link was in. From that document, we'll get the URI to use as the + // referer, since the current URI in this docshell may be a + // new document that we're in the process of loading. + nsCOMPtr<nsIDocument> refererDoc = aContent->OwnerDoc(); + NS_ENSURE_TRUE(refererDoc, NS_ERROR_UNEXPECTED); + + // Now check that the refererDoc's inner window is the current inner + // window for mScriptGlobal. If it's not, then we don't want to + // follow this link. + nsPIDOMWindowInner* refererInner = refererDoc->GetInnerWindow(); + NS_ENSURE_TRUE(refererInner, NS_ERROR_UNEXPECTED); + if (!mScriptGlobal || + mScriptGlobal->AsOuter()->GetCurrentInnerWindow() != refererInner) { + // We're no longer the current inner window + return NS_OK; + } + + nsCOMPtr<nsIURI> referer = refererDoc->GetDocumentURI(); + uint32_t refererPolicy = refererDoc->GetReferrerPolicy(); + + // get referrer attribute from clicked link and parse it + // if per element referrer is enabled, the element referrer overrules + // the document wide referrer + if (IsElementAnchor(aContent)) { + net::ReferrerPolicy refPolEnum = aContent->AsElement()->GetReferrerPolicyAsEnum(); + if (refPolEnum != net::RP_Unset) { + refererPolicy = refPolEnum; + } + } + + // referer could be null here in some odd cases, but that's ok, + // we'll just load the link w/o sending a referer in those cases. + + nsAutoString target(aTargetSpec); + + // If this is an anchor element, grab its type property to use as a hint + nsAutoString typeHint; + nsCOMPtr<nsIDOMHTMLAnchorElement> anchor(do_QueryInterface(aContent)); + if (anchor) { + anchor->GetType(typeHint); + NS_ConvertUTF16toUTF8 utf8Hint(typeHint); + nsAutoCString type, dummy; + NS_ParseRequestContentType(utf8Hint, type, dummy); + CopyUTF8toUTF16(type, typeHint); + } + + // Clone the URI now, in case a content policy or something messes + // with it under InternalLoad; we do _not_ want to change the URI + // our caller passed in. + nsCOMPtr<nsIURI> clonedURI; + aURI->Clone(getter_AddRefs(clonedURI)); + if (!clonedURI) { + return NS_ERROR_OUT_OF_MEMORY; + } + + nsresult rv = InternalLoad(clonedURI, // New URI + nullptr, // Original URI + false, // LoadReplace + referer, // Referer URI + refererPolicy, // Referer policy + aContent->NodePrincipal(), // Triggering is our node's + // principal + aContent->NodePrincipal(), + flags, + target, // Window target + NS_LossyConvertUTF16toASCII(typeHint).get(), + aFileName, // Download as file + aPostDataStream, // Post data stream + aHeadersDataStream, // Headers stream + LOAD_LINK, // Load type + nullptr, // No SHEntry + true, // first party site + NullString(), // No srcdoc + this, // We are the source + nullptr, // baseURI not needed + aDocShell, // DocShell out-param + aRequest); // Request out-param + if (NS_SUCCEEDED(rv)) { + DispatchPings(this, aContent, aURI, referer, refererPolicy); + } + return rv; +} + +NS_IMETHODIMP +nsDocShell::OnOverLink(nsIContent* aContent, + nsIURI* aURI, + const char16_t* aTargetSpec) +{ + if (aContent->IsEditable()) { + return NS_OK; + } + + nsCOMPtr<nsIWebBrowserChrome2> browserChrome2 = do_GetInterface(mTreeOwner); + nsresult rv = NS_ERROR_FAILURE; + + nsCOMPtr<nsIWebBrowserChrome> browserChrome; + if (!browserChrome2) { + browserChrome = do_GetInterface(mTreeOwner); + if (!browserChrome) { + return rv; + } + } + + nsCOMPtr<nsITextToSubURI> textToSubURI = + do_GetService(NS_ITEXTTOSUBURI_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + return rv; + } + + // use url origin charset to unescape the URL + nsAutoCString charset; + rv = aURI->GetOriginCharset(charset); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString spec; + rv = aURI->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString uStr; + rv = textToSubURI->UnEscapeURIForUI(charset, spec, uStr); + NS_ENSURE_SUCCESS(rv, rv); + + mozilla::net::PredictorPredict(aURI, mCurrentURI, + nsINetworkPredictor::PREDICT_LINK, + this, nullptr); + + if (browserChrome2) { + nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aContent); + rv = browserChrome2->SetStatusWithContext(nsIWebBrowserChrome::STATUS_LINK, + uStr, element); + } else { + rv = browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_LINK, uStr.get()); + } + return rv; +} + +NS_IMETHODIMP +nsDocShell::OnLeaveLink() +{ + nsCOMPtr<nsIWebBrowserChrome> browserChrome(do_GetInterface(mTreeOwner)); + nsresult rv = NS_ERROR_FAILURE; + + if (browserChrome) { + rv = browserChrome->SetStatus(nsIWebBrowserChrome::STATUS_LINK, + EmptyString().get()); + } + return rv; +} + +bool +nsDocShell::ShouldBlockLoadingForBackButton() +{ + if (!(mLoadType & LOAD_CMD_HISTORY) || + EventStateManager::IsHandlingUserInput() || + !Preferences::GetBool("accessibility.blockjsredirection")) { + return false; + } + + bool canGoForward = false; + GetCanGoForward(&canGoForward); + return canGoForward; +} + +bool +nsDocShell::PluginsAllowedInCurrentDoc() +{ + bool pluginsAllowed = false; + + if (!mContentViewer) { + return false; + } + + nsIDocument* doc = mContentViewer->GetDocument(); + if (!doc) { + return false; + } + + doc->GetAllowPlugins(&pluginsAllowed); + return pluginsAllowed; +} + +//---------------------------------------------------------------------- +// Web Shell Services API + +// This functions is only called when a new charset is detected in loading a +// document. Its name should be changed to "CharsetReloadDocument" +NS_IMETHODIMP +nsDocShell::ReloadDocument(const char* aCharset, int32_t aSource) +{ + // XXX hack. keep the aCharset and aSource wait to pick it up + nsCOMPtr<nsIContentViewer> cv; + NS_ENSURE_SUCCESS(GetContentViewer(getter_AddRefs(cv)), NS_ERROR_FAILURE); + if (cv) { + int32_t hint; + cv->GetHintCharacterSetSource(&hint); + if (aSource > hint) { + nsCString charset(aCharset); + cv->SetHintCharacterSet(charset); + cv->SetHintCharacterSetSource(aSource); + if (eCharsetReloadRequested != mCharsetReloadState) { + mCharsetReloadState = eCharsetReloadRequested; + return Reload(LOAD_FLAGS_CHARSET_CHANGE); + } + } + } + // return failure if this request is not accepted due to mCharsetReloadState + return NS_ERROR_DOCSHELL_REQUEST_REJECTED; +} + +NS_IMETHODIMP +nsDocShell::StopDocumentLoad(void) +{ + if (eCharsetReloadRequested != mCharsetReloadState) { + Stop(nsIWebNavigation::STOP_ALL); + return NS_OK; + } + // return failer if this request is not accepted due to mCharsetReloadState + return NS_ERROR_DOCSHELL_REQUEST_REJECTED; +} + +NS_IMETHODIMP +nsDocShell::GetPrintPreview(nsIWebBrowserPrint** aPrintPreview) +{ + *aPrintPreview = nullptr; +#if NS_PRINT_PREVIEW + nsCOMPtr<nsIDocumentViewerPrint> print = do_QueryInterface(mContentViewer); + if (!print || !print->IsInitializedForPrintPreview()) { + Stop(nsIWebNavigation::STOP_ALL); + nsCOMPtr<nsIPrincipal> principal = nsNullPrincipal::CreateWithInheritedAttributes(this); + nsresult rv = CreateAboutBlankContentViewer(principal, nullptr); + NS_ENSURE_SUCCESS(rv, rv); + print = do_QueryInterface(mContentViewer); + NS_ENSURE_STATE(print); + print->InitializeForPrintPreview(); + } + nsCOMPtr<nsIWebBrowserPrint> result = do_QueryInterface(print); + result.forget(aPrintPreview); + return NS_OK; +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +#ifdef DEBUG +unsigned long nsDocShell::gNumberOfDocShells = 0; +#endif + +NS_IMETHODIMP +nsDocShell::GetCanExecuteScripts(bool* aResult) +{ + *aResult = mCanExecuteScripts; + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::SetFrameType(uint32_t aFrameType) +{ + mFrameType = aFrameType; + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetFrameType(uint32_t* aFrameType) +{ + *aFrameType = mFrameType; + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetIsApp(bool* aIsApp) +{ + *aIsApp = (mFrameType == FRAME_TYPE_APP); + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetIsMozBrowserOrApp(bool* aIsMozBrowserOrApp) +{ + *aIsMozBrowserOrApp = (mFrameType != FRAME_TYPE_REGULAR); + return NS_OK; +} + +uint32_t +nsDocShell::GetInheritedFrameType() +{ + if (mFrameType != FRAME_TYPE_REGULAR) { + return mFrameType; + } + + nsCOMPtr<nsIDocShellTreeItem> parentAsItem; + GetSameTypeParent(getter_AddRefs(parentAsItem)); + + nsCOMPtr<nsIDocShell> parent = do_QueryInterface(parentAsItem); + if (!parent) { + return FRAME_TYPE_REGULAR; + } + + return static_cast<nsDocShell*>(parent.get())->GetInheritedFrameType(); +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetIsIsolatedMozBrowserElement(bool* aIsIsolatedMozBrowserElement) +{ + bool result = mFrameType == FRAME_TYPE_BROWSER && + mOriginAttributes.mInIsolatedMozBrowser; + *aIsIsolatedMozBrowserElement = result; + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetIsInIsolatedMozBrowserElement(bool* aIsInIsolatedMozBrowserElement) +{ + MOZ_ASSERT(!mOriginAttributes.mInIsolatedMozBrowser || + (GetInheritedFrameType() == FRAME_TYPE_BROWSER), + "Isolated mozbrowser should only be true inside browser frames"); + bool result = (GetInheritedFrameType() == FRAME_TYPE_BROWSER) && + mOriginAttributes.mInIsolatedMozBrowser; + *aIsInIsolatedMozBrowserElement = result; + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetIsInMozBrowserOrApp(bool* aIsInMozBrowserOrApp) +{ + *aIsInMozBrowserOrApp = (GetInheritedFrameType() != FRAME_TYPE_REGULAR); + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetIsTopLevelContentDocShell(bool* aIsTopLevelContentDocShell) +{ + *aIsTopLevelContentDocShell = false; + + if (mItemType == typeContent) { + nsCOMPtr<nsIDocShellTreeItem> root; + GetSameTypeRootTreeItem(getter_AddRefs(root)); + *aIsTopLevelContentDocShell = root.get() == static_cast<nsIDocShellTreeItem*>(this); + } + + return NS_OK; +} + +/* [infallible] */ NS_IMETHODIMP +nsDocShell::GetAppId(uint32_t* aAppId) +{ + if (mOriginAttributes.mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID) { + *aAppId = mOriginAttributes.mAppId; + return NS_OK; + } + + nsCOMPtr<nsIDocShell> parent; + GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)); + + if (!parent) { + *aAppId = nsIScriptSecurityManager::NO_APP_ID; + return NS_OK; + } + + return parent->GetAppId(aAppId); +} + +// Implements nsILoadContext.originAttributes +NS_IMETHODIMP +nsDocShell::GetOriginAttributes(JS::MutableHandle<JS::Value> aVal) +{ + JSContext* cx = nsContentUtils::GetCurrentJSContext(); + MOZ_ASSERT(cx); + + return GetOriginAttributes(cx, aVal); +} + +// Implements nsIDocShell.GetOriginAttributes() +NS_IMETHODIMP +nsDocShell::GetOriginAttributes(JSContext* aCx, + JS::MutableHandle<JS::Value> aVal) +{ + bool ok = ToJSValue(aCx, mOriginAttributes, aVal); + NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE); + return NS_OK; +} + +bool +nsDocShell::CanSetOriginAttributes() +{ + MOZ_ASSERT(mChildList.IsEmpty()); + if (!mChildList.IsEmpty()) { + return false; + } + + // TODO: Bug 1273058 - mContentViewer should be null when setting origin + // attributes. + if (mContentViewer) { + nsIDocument* doc = mContentViewer->GetDocument(); + if (doc) { + nsIURI* uri = doc->GetDocumentURI(); + if (!uri) { + return false; + } + nsCString uriSpec = uri->GetSpecOrDefault(); + MOZ_ASSERT(uriSpec.EqualsLiteral("about:blank")); + if (!uriSpec.EqualsLiteral("about:blank")) { + return false; + } + } + } + + return true; +} + +nsresult +nsDocShell::SetOriginAttributes(const DocShellOriginAttributes& aAttrs) +{ + if (!CanSetOriginAttributes()) { + return NS_ERROR_FAILURE; + } + + AssertOriginAttributesMatchPrivateBrowsing(); + mOriginAttributes = aAttrs; + + bool isPrivate = mOriginAttributes.mPrivateBrowsingId > 0; + // Chrome docshell can not contain OriginAttributes.mPrivateBrowsingId + if (mItemType == typeChrome && isPrivate) { + mOriginAttributes.mPrivateBrowsingId = 0; + } + + SetPrivateBrowsing(isPrivate); + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::SetOriginAttributesBeforeLoading(JS::Handle<JS::Value> aOriginAttributes) +{ + if (!aOriginAttributes.isObject()) { + return NS_ERROR_INVALID_ARG; + } + + AutoJSAPI jsapi; + if (!jsapi.Init(&aOriginAttributes.toObject())) { + return NS_ERROR_UNEXPECTED; + } + + JSContext* cx = jsapi.cx(); + if (NS_WARN_IF(!cx)) { + return NS_ERROR_FAILURE; + } + + DocShellOriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(cx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + return SetOriginAttributes(attrs); +} + +NS_IMETHODIMP +nsDocShell::SetOriginAttributes(JS::Handle<JS::Value> aOriginAttributes, + JSContext* aCx) +{ + DocShellOriginAttributes attrs; + if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) { + return NS_ERROR_INVALID_ARG; + } + + return SetOriginAttributes(attrs); +} + +NS_IMETHODIMP +nsDocShell::GetAppManifestURL(nsAString& aAppManifestURL) +{ + uint32_t appId = nsIDocShell::GetAppId(); + if (appId != nsIScriptSecurityManager::NO_APP_ID && + appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) { + nsCOMPtr<nsIAppsService> appsService = + do_GetService(APPS_SERVICE_CONTRACTID); + NS_ASSERTION(appsService, "No AppsService available"); + appsService->GetManifestURLByLocalId(appId, aAppManifestURL); + } else { + aAppManifestURL.SetLength(0); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetAsyncPanZoomEnabled(bool* aOut) +{ + if (nsIPresShell* presShell = GetPresShell()) { + *aOut = presShell->AsyncPanZoomEnabled(); + return NS_OK; + } + + // If we don't have a presShell, fall back to the default platform value of + // whether or not APZ is enabled. + *aOut = gfxPlatform::AsyncPanZoomEnabled(); + return NS_OK; +} + +bool +nsDocShell::HasUnloadedParent() +{ + RefPtr<nsDocShell> parent = GetParentDocshell(); + while (parent) { + bool inUnload = false; + parent->GetIsInUnload(&inUnload); + if (inUnload) { + return true; + } + parent = parent->GetParentDocshell(); + } + return false; +} + +bool +nsDocShell::IsInvisible() +{ + return mInvisible; +} + +void +nsDocShell::SetInvisible(bool aInvisible) +{ + mInvisible = aInvisible; +} + +void +nsDocShell::SetOpener(nsITabParent* aOpener) +{ + mOpener = do_GetWeakReference(aOpener); +} + +nsITabParent* +nsDocShell::GetOpener() +{ + nsCOMPtr<nsITabParent> opener(do_QueryReferent(mOpener)); + return opener; +} + +// The caller owns |aAsyncCause| here. +void +nsDocShell::NotifyJSRunToCompletionStart(const char* aReason, + const char16_t* aFunctionName, + const char16_t* aFilename, + const uint32_t aLineNumber, + JS::Handle<JS::Value> aAsyncStack, + const char* aAsyncCause) +{ + // If first start, mark interval start. + if (mJSRunToCompletionDepth == 0) { + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (timelines && timelines->HasConsumer(this)) { + timelines->AddMarkerForDocShell(this, Move( + mozilla::MakeUnique<JavascriptTimelineMarker>( + aReason, aFunctionName, aFilename, aLineNumber, MarkerTracingType::START, + aAsyncStack, aAsyncCause))); + } + } + + mJSRunToCompletionDepth++; +} + +void +nsDocShell::NotifyJSRunToCompletionStop() +{ + mJSRunToCompletionDepth--; + + // If last stop, mark interval end. + if (mJSRunToCompletionDepth == 0) { + RefPtr<TimelineConsumers> timelines = TimelineConsumers::Get(); + if (timelines && timelines->HasConsumer(this)) { + timelines->AddMarkerForDocShell(this, "Javascript", MarkerTracingType::END); + } + } +} + +void +nsDocShell::MaybeNotifyKeywordSearchLoading(const nsString& aProvider, + const nsString& aKeyword) +{ + if (aProvider.IsEmpty()) { + return; + } + + if (XRE_IsContentProcess()) { + dom::ContentChild* contentChild = dom::ContentChild::GetSingleton(); + if (contentChild) { + contentChild->SendNotifyKeywordSearchLoading(aProvider, aKeyword); + } + return; + } + +#ifdef MOZ_TOOLKIT_SEARCH + nsCOMPtr<nsIBrowserSearchService> searchSvc = + do_GetService("@mozilla.org/browser/search-service;1"); + if (searchSvc) { + nsCOMPtr<nsISearchEngine> searchEngine; + searchSvc->GetEngineByName(aProvider, getter_AddRefs(searchEngine)); + if (searchEngine) { + nsCOMPtr<nsIObserverService> obsSvc = services::GetObserverService(); + if (obsSvc) { + // Note that "keyword-search" refers to a search via the url + // bar, not a bookmarks keyword search. + obsSvc->NotifyObservers(searchEngine, "keyword-search", aKeyword.get()); + } + } + } +#endif +} + +NS_IMETHODIMP +nsDocShell::ShouldPrepareForIntercept(nsIURI* aURI, bool aIsNonSubresourceRequest, + bool* aShouldIntercept) +{ + *aShouldIntercept = false; + // No in private browsing + if (UsePrivateBrowsing()) { + return NS_OK; + } + + if (mSandboxFlags) { + // If we're sandboxed, don't intercept. + return NS_OK; + } + + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + return NS_OK; + } + + nsresult result; + nsCOMPtr<mozIThirdPartyUtil> thirdPartyUtil = + do_GetService(THIRDPARTYUTIL_CONTRACTID, &result); + NS_ENSURE_SUCCESS(result, result); + + if (mCurrentURI && + nsContentUtils::CookiesBehavior() == nsICookieService::BEHAVIOR_REJECT_FOREIGN) { + nsAutoCString uriSpec; + if (!(mCurrentURI->GetSpecOrDefault().EqualsLiteral("about:blank"))) { + // Reject the interception of third-party iframes if the cookie behaviour + // is set to reject all third-party cookies (1). In case that this pref + // is not set or can't be read, we default to allow all cookies (0) as + // this is the default value in all.js. + bool isThirdPartyURI = true; + result = thirdPartyUtil->IsThirdPartyURI(mCurrentURI, aURI, + &isThirdPartyURI); + if (NS_FAILED(result)) { + return result; + } + + if (isThirdPartyURI) { + return NS_OK; + } + } + } + + if (aIsNonSubresourceRequest) { + PrincipalOriginAttributes attrs; + attrs.InheritFromDocShellToDoc(mOriginAttributes, aURI); + nsCOMPtr<nsIPrincipal> principal = + BasePrincipal::CreateCodebasePrincipal(aURI, attrs); + *aShouldIntercept = swm->IsAvailable(principal, aURI); + return NS_OK; + } + + nsCOMPtr<nsIDocument> doc = GetDocument(); + if (!doc) { + return NS_ERROR_NOT_AVAILABLE; + } + + ErrorResult rv; + *aShouldIntercept = swm->IsControlled(doc, rv); + if (NS_WARN_IF(rv.Failed())) { + return rv.StealNSResult(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::ChannelIntercepted(nsIInterceptedChannel* aChannel) +{ + RefPtr<ServiceWorkerManager> swm = ServiceWorkerManager::GetInstance(); + if (!swm) { + aChannel->Cancel(NS_ERROR_INTERCEPTION_FAILED); + return NS_OK; + } + + nsCOMPtr<nsIChannel> channel; + nsresult rv = aChannel->GetChannel(getter_AddRefs(channel)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIDocument> doc; + + bool isSubresourceLoad = !nsContentUtils::IsNonSubresourceRequest(channel); + if (isSubresourceLoad) { + doc = GetDocument(); + if (!doc) { + return NS_ERROR_NOT_AVAILABLE; + } + } else { + // For top-level navigations, save a document ID which will be passed to + // the FetchEvent as the clientId later on. + rv = nsIDocument::GenerateDocumentId(mInterceptedDocumentId); + NS_ENSURE_SUCCESS(rv, rv); + } + + bool isReload = mLoadType & LOAD_CMD_RELOAD; + + nsCOMPtr<nsIURI> uri; + rv = channel->GetURI(getter_AddRefs(uri)); + NS_ENSURE_SUCCESS(rv, rv); + + PrincipalOriginAttributes attrs; + attrs.InheritFromDocShellToDoc(mOriginAttributes, uri); + + ErrorResult error; + swm->DispatchFetchEvent(attrs, doc, mInterceptedDocumentId, aChannel, + isReload, isSubresourceLoad, error); + if (NS_WARN_IF(error.Failed())) { + return error.StealNSResult(); + } + + return NS_OK; +} + +bool +nsDocShell::InFrameSwap() +{ + RefPtr<nsDocShell> shell = this; + do { + if (shell->mInFrameSwap) { + return true; + } + shell = shell->GetParentDocshell(); + } while (shell); + return false; +} + +NS_IMETHODIMP +nsDocShell::IssueWarning(uint32_t aWarning, bool aAsError) +{ + if (mContentViewer) { + nsCOMPtr<nsIDocument> doc = mContentViewer->GetDocument(); + if (doc) { + doc->WarnOnceAbout(nsIDocument::DeprecatedOperations(aWarning), aAsError); + } + } + return NS_OK; +} + +NS_IMETHODIMP +nsDocShell::GetEditingSession(nsIEditingSession** aEditSession) +{ + if (!NS_SUCCEEDED(EnsureEditorData())) { + return NS_ERROR_FAILURE; + } + + mEditorData->GetEditingSession(aEditSession); + return *aEditSession ? NS_OK : NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +nsDocShell::GetScriptableTabChild(nsITabChild** aTabChild) +{ + *aTabChild = GetTabChild().take(); + return *aTabChild ? NS_OK : NS_ERROR_FAILURE; +} + +already_AddRefed<nsITabChild> +nsDocShell::GetTabChild() +{ + nsCOMPtr<nsIDocShellTreeOwner> owner(mTreeOwner); + nsCOMPtr<nsITabChild> tc = do_GetInterface(owner); + return tc.forget(); +} + +nsICommandManager* +nsDocShell::GetCommandManager() +{ + NS_ENSURE_SUCCESS(EnsureCommandHandler(), nullptr); + return mCommandManager; +} + +NS_IMETHODIMP +nsDocShell::GetProcessLockReason(uint32_t* aReason) +{ + MOZ_ASSERT(aReason); + + nsPIDOMWindowOuter* outer = GetWindow(); + MOZ_ASSERT(outer); + + // Check if we are a toplevel window + if (outer->GetScriptableParentOrNull()) { + *aReason = PROCESS_LOCK_IFRAME; + return NS_OK; + } + + // If we have any other toplevel windows in our tab group, then we cannot + // perform the navigation. + nsTArray<nsPIDOMWindowOuter*> toplevelWindows = + outer->TabGroup()->GetTopLevelWindows(); + if (toplevelWindows.Length() > 1) { + *aReason = PROCESS_LOCK_RELATED_CONTEXTS; + return NS_OK; + } + MOZ_ASSERT(toplevelWindows.Length() == 1); + MOZ_ASSERT(toplevelWindows[0] == outer); + + // If we aren't in a content process, we cannot perform a cross-process load. + if (!XRE_IsContentProcess()) { + *aReason = PROCESS_LOCK_NON_CONTENT; + return NS_OK; + } + + *aReason = PROCESS_LOCK_NONE; + return NS_OK; +} |