/* -*- 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/Casting.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/TabChild.h"
#include "mozilla/dom/ProfileTimelineMarkerBinding.h"
#include "mozilla/dom/ToJSValue.h"
#include "mozilla/EventStateManager.h"
#include "mozilla/Preferences.h"
#include "mozilla/Services.h"
#include "mozilla/StartupTimeline.h"
#include "mozilla/Telemetry.h"
#include "mozilla/unused.h"
#include "mozilla/VisualEventTracer.h"
#include "URIUtils.h"
#include "nsIContent.h"
#include "nsIContentInlines.h"
#include "nsIDocument.h"
#include "nsIDOMDocument.h"
#include "nsIDOMElement.h"
#include "nsIDOMStorage.h"
#include "nsIContentViewer.h"
#include "nsIDocumentLoaderFactory.h"
#include "nsCURILoader.h"
#include "nsDocShellCID.h"
#include "nsDOMCID.h"
#include "nsNetUtil.h"
#include "mozilla/net/ReferrerPolicy.h"
#include "nsRect.h"
#include "prenv.h"
#include "nsIDOMWindow.h"
#include "nsIGlobalObject.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 "nsIServiceWorkerManager.h"
#include "nsIScriptSecurityManager.h"
#include "nsIScriptObjectPrincipal.h"
#include "nsIScrollableFrame.h"
#include "nsContentPolicyUtils.h" // NS_CheckContentLoadPolicy(...)
#include "nsISeekableStream.h"
#include "nsAutoPtr.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"
// 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"
// Helper Classes
#include "nsError.h"
#include "nsEscape.h"
// Interfaces Needed
#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 "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 "prlog.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 "nsISHContainer.h"
#include "nsISHistory.h"
#include "nsISecureBrowserUI.h"
#include "nsIStringBundle.h"
#include "nsISupportsArray.h"
#include "nsIURIFixup.h"
#include "nsIURILoader.h"
#include "nsIWebBrowserFind.h"
#include "nsIWidget.h"
#include "mozilla/dom/EncodingUtils.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/dom/URLSearchParams.h"
#include "nsPerformance.h"
#ifdef MOZ_TOOLKIT_SEARCH
#include "nsIBrowserSearchService.h"
#endif
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;
// 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 PR_LOGGING
#ifdef DEBUG
static PRLogModuleInfo* gDocShellLog;
#endif
static PRLogModuleInfo* gDocShellLeakLog;
#endif
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;
}
static bool
CheckPingURI(nsIURI* aURI, nsIContent* aContent)
{
if (!aURI) {
return false;
}
// Check with nsIScriptSecurityManager
nsCOMPtr<nsIScriptSecurityManager> ssmgr =
do_GetService(NS_SCRIPTSECURITYMANAGER_CONTRACTID);
NS_ENSURE_TRUE(ssmgr, false);
nsresult rv = ssmgr->CheckLoadURIWithPrincipal(
aContent->NodePrincipal(), aURI, nsIScriptSecurityManager::STANDARD);
if (NS_FAILED(rv)) {
return false;
}
// Ignore non-HTTP(S)
bool match;
if ((NS_FAILED(aURI->SchemeIs("http", &match)) || !match) &&
(NS_FAILED(aURI->SchemeIs("https", &match)) || !match)) {
return false;
}
// Check with contentpolicy
int16_t shouldLoad = nsIContentPolicy::ACCEPT;
rv = NS_CheckContentLoadPolicy(nsIContentPolicy::TYPE_PING,
aURI,
aContent->NodePrincipal(),
aContent,
EmptyCString(), // mime hint
nullptr, // extra
&shouldLoad);
return NS_SUCCEEDED(rv) && NS_CP_ACCEPTED(shouldLoad);
}
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.
if (!aContent->IsHTML()) {
return false;
}
nsIAtom* nameAtom = aContent->Tag();
return nameAtom == nsGkAtoms::a || nameAtom == 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 = do_GetAtom("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 (CheckPingURI(uri, aContent)) {
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);
}
}
// Check to see if two URIs have the same host or not
static bool
IsSameHost(nsIURI* aUri1, nsIURI* aUri2)
{
nsAutoCString host1, host2;
aUri1->GetAsciiHost(host1);
aUri2->GetAsciiHost(host2);
return host1.Equals(host2);
}
class nsPingListener final
: public nsIStreamListener
, public nsIInterfaceRequestor
, public nsIChannelEventSink
{
public:
NS_DECL_ISUPPORTS
NS_DECL_NSIREQUESTOBSERVER
NS_DECL_NSISTREAMLISTENER
NS_DECL_NSIINTERFACEREQUESTOR
NS_DECL_NSICHANNELEVENTSINK
nsPingListener(bool aRequireSameHost, nsIContent* aContent,
nsILoadGroup* aLoadGroup)
: mRequireSameHost(aRequireSameHost)
, mContent(aContent)
, mLoadGroup(aLoadGroup)
{
}
nsresult StartTimeout();
private:
~nsPingListener();
bool mRequireSameHost;
nsCOMPtr<nsIContent> mContent;
nsCOMPtr<nsILoadGroup> mLoadGroup;
nsCOMPtr<nsITimer> mTimer;
};
NS_IMPL_ISUPPORTS(nsPingListener, nsIStreamListener, nsIRequestObserver,
nsIInterfaceRequestor, nsIChannelEventSink)
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;
}
NS_IMETHODIMP
nsPingListener::GetInterface(const nsIID& aIID, void** aResult)
{
if (aIID.Equals(NS_GET_IID(nsIChannelEventSink))) {
NS_ADDREF_THIS();
*aResult = (nsIChannelEventSink*)this;
return NS_OK;
}
return NS_ERROR_NO_INTERFACE;
}
NS_IMETHODIMP
nsPingListener::AsyncOnChannelRedirect(nsIChannel* aOldChan,
nsIChannel* aNewChan,
uint32_t aFlags,
nsIAsyncVerifyRedirectCallback* aCallback)
{
nsCOMPtr<nsIURI> newURI;
aNewChan->GetURI(getter_AddRefs(newURI));
if (!CheckPingURI(newURI, mContent)) {
return NS_ERROR_ABORT;
}
if (!mRequireSameHost) {
aCallback->OnRedirectVerifyCallback(NS_OK);
return NS_OK;
}
// XXXbz should this be using something more like the nsContentUtils
// same-origin checker?
nsCOMPtr<nsIURI> oldURI;
aOldChan->GetURI(getter_AddRefs(oldURI));
NS_ENSURE_STATE(oldURI && newURI);
if (!IsSameHost(oldURI, newURI)) {
return NS_ERROR_ABORT;
}
aCallback->OnRedirectVerifyCallback(NS_OK);
return NS_OK;
}
struct SendPingInfo
{
int32_t numPings;
int32_t maxPings;
bool requireSameHost;
nsIURI* target;
nsIURI* referrer;
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;
}
if (info->requireSameHost) {
// Make sure the referrer and the given uri share the same origin. We
// only require the same hostname. The scheme and port may differ.
if (!IsSameHost(aURI, info->referrer)) {
return;
}
}
nsIDocument* doc = aContent->OwnerDoc();
nsCOMPtr<nsIChannel> chan;
NS_NewChannel(getter_AddRefs(chan),
aURI,
doc,
nsILoadInfo::SEC_NORMAL,
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.
chan->SetLoadFlags(nsIRequest::INHIBIT_CACHING);
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;
}
chan->SetLoadGroup(loadGroup);
// Construct a listener that merely discards any response. If successful at
// opening the channel, then it is not necessary to hold a reference to the
// channel. The networking subsystem will take care of that for us.
nsPingListener* pingListener =
new nsPingListener(info->requireSameHost, aContent, loadGroup);
nsCOMPtr<nsIStreamListener> listener(pingListener);
// Observe redirects as well:
nsCOMPtr<nsIInterfaceRequestor> callbacks = do_QueryInterface(listener);
NS_ASSERTION(callbacks, "oops");
loadGroup->SetNotificationCallbacks(callbacks);
chan->AsyncOpen(listener, nullptr);
// 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);
}
}
// Spec: http://whatwg.org/specs/web-apps/current-work/#ping
static void
DispatchPings(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;
ForEachPing(aContent, SendPing, &info);
}
static nsDOMPerformanceNavigationType
ConvertLoadTypeToNavigationType(uint32_t aLoadType)
{
// Not initialized, assume it's normal load.
if (aLoadType == 0) {
aLoadType = LOAD_NORMAL;
}
auto result = dom::PerformanceNavigation::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 = dom::PerformanceNavigation::TYPE_NAVIGATE;
break;
case LOAD_HISTORY:
result = dom::PerformanceNavigation::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 = dom::PerformanceNavigation::TYPE_RELOAD;
break;
case LOAD_STOP_CONTENT_AND_REPLACE:
case LOAD_REFRESH:
case LOAD_BYPASS_HISTORY:
case LOAD_ERROR_PAGE:
case LOAD_PUSHSTATE:
result = dom::PerformanceNavigation::TYPE_RESERVED;
break;
default:
// NS_NOTREACHED("Unexpected load type value");
result = dom::PerformanceNavigation::TYPE_RESERVED;
break;
}
return result;
}
static nsISHEntry* GetRootSHEntry(nsISHEntry* aEntry);
static void
IncreasePrivateDocShellCount()
{
gNumberOfPrivateDocShells++;
if (gNumberOfPrivateDocShells > 1 ||
XRE_GetProcessType() != GeckoProcessType_Content) {
return;
}
mozilla::dom::ContentChild* cc = mozilla::dom::ContentChild::GetSingleton();
cc->SendPrivateDocShellsExist(true);
}
static void
DecreasePrivateDocShellCount()
{
MOZ_ASSERT(gNumberOfPrivateDocShells > 0);
gNumberOfPrivateDocShells--;
if (!gNumberOfPrivateDocShells) {
if (XRE_GetProcessType() == GeckoProcessType_Content) {
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);
}
}
}
//*****************************************************************************
//*** nsDocShell: Object Management
//*****************************************************************************
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)
, 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)
, mCreatingDocument(false)
, mUseErrorPages(false)
, mObserveErrorPages(true)
, mAllowAuth(true)
, mAllowKeywordFixup(false)
, mIsOffScreenBrowser(false)
, mIsActive(true)
, mIsPrerendered(false)
, mIsAppTab(false)
, mUseGlobalHistory(false)
, mInPrivateBrowsing(false)
, mUseRemoteTabs(false)
, mDeviceSizeIsPageSize(false)
, mWindowDraggingAllowed(false)
, mCanExecuteScripts(false)
, mFiredUnloadEvent(false)
, mEODForCurrentDocument(false)
, mURIResultedInDocument(false)
, mIsBeingDestroyed(false)
, mIsExecutingOnLoadHandler(false)
, mIsPrintingOrPP(false)
, mSavingOldViewer(false)
#ifdef DEBUG
, mInEnsureScriptEnv(false)
#endif
, mAffectPrivateSessionLifetime(true)
, mInvisible(false)
, mHasLoadedNonBlankURI(false)
, mDefaultLoadFlags(nsIRequest::LOAD_NORMAL)
, mBlankTiming(false)
, mFrameType(eFrameTypeRegular)
, mOwnOrContainingAppId(nsIScriptSecurityManager::UNKNOWN_APP_ID)
, mParentCharsetSource(0)
, mJSRunToCompletionDepth(0)
{
mHistoryID = ++gDocshellIDCounter;
if (gDocShellCount++ == 0) {
NS_ASSERTION(sURIFixup == nullptr,
"Huh, sURIFixup not null in first nsDocShell ctor!");
CallGetService(NS_URIFIXUP_CONTRACTID, &sURIFixup);
}
#ifdef PR_LOGGING
#ifdef DEBUG
if (!gDocShellLog) {
gDocShellLog = PR_NewLogModule("nsDocShell");
}
#endif
if (!gDocShellLeakLog) {
gDocShellLeakLog = PR_NewLogModule("nsDocShellLeak");
}
if (gDocShellLeakLog) {
PR_LOG(gDocShellLeakLog, PR_LOG_DEBUG, ("DOCSHELL %p created\n", this));
}
#endif
#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(!mProfileTimelineRecording);
Destroy();
nsCOMPtr<nsISHistoryInternal> shPrivate(do_QueryInterface(mSessionHistory));
if (shPrivate) {
shPrivate->SetRootDocShell(nullptr);
}
if (--gDocShellCount == 0) {
NS_IF_RELEASE(sURIFixup);
}
#ifdef PR_LOGGING
if (gDocShellLeakLog) {
PR_LOG(gDocShellLeakLog, PR_LOG_DEBUG, ("DOCSHELL %p destroyed\n", this));
}
#endif
#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);
NS_ENSURE_TRUE(mContentListener, NS_ERROR_OUT_OF_MEMORY);
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));
NS_ENSURE_TRUE(proxy, NS_ERROR_OUT_OF_MEMORY);
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();
}
//*****************************************************************************
// nsDocShell::nsISupports
//*****************************************************************************
NS_IMPL_ADDREF_INHERITED(nsDocShell, nsDocLoader)
NS_IMPL_RELEASE_INHERITED(nsDocShell, nsDocLoader)
NS_INTERFACE_MAP_BEGIN(nsDocShell)
NS_INTERFACE_MAP_ENTRY(nsIDocShell_ESR38)
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_END_INHERITING(nsDocLoader)
///*****************************************************************************
// nsDocShell::nsIInterfaceRequestor
//*****************************************************************************
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(nsPIDOMWindow)) ||
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(PR_LOGGING) && defined(DEBUG)
PR_LOG(gDocShellLog, PR_LOG_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, &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) {
*aSink = shistory;
NS_ADDREF((nsISupports*)*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)) &&
NS_SUCCEEDED(EnsureEditorData())) {
nsCOMPtr<nsIEditingSession> editingSession;
mEditorData->GetEditingSession(getter_AddRefs(editingSession));
if (editingSession) {
*aSink = editingSession;
NS_ADDREF((nsISupports*)*aSink);
return NS_OK;
}
return 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))) {
nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
nsresult rv = GetTreeOwner(getter_AddRefs(treeOwner));
if (NS_SUCCEEDED(rv) && treeOwner) {
nsCOMPtr<nsIInterfaceRequestor> ir = do_QueryInterface(treeOwner);
if (ir) {
return ir->GetInterface(aIID, aSink);
}
}
} 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 {
nsCOMPtr<nsPIDOMWindow> win = GetWindow();
if (win) {
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;
}
//*****************************************************************************
// nsDocShell::nsIDocShell
//*****************************************************************************
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
}
if (DoAppRedirectIfNeeded(aURI, aLoadInfo, aFirstParty)) {
return NS_OK;
}
nsCOMPtr<nsIURI> referrer;
nsCOMPtr<nsIInputStream> postStream;
nsCOMPtr<nsIInputStream> headersStream;
nsCOMPtr<nsISupports> owner;
bool inheritOwner = false;
bool ownerIsExplicit = 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));
nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal;
aLoadInfo->GetLoadType(<);
// Get the appropriate loadType from nsIDocShellLoadInfo type
loadType = ConvertDocShellLoadInfoToLoadType(lt);
aLoadInfo->GetOwner(getter_AddRefs(owner));
aLoadInfo->GetInheritOwner(&inheritOwner);
aLoadInfo->GetOwnerIsExplicit(&ownerIsExplicit);
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(PR_LOGGING) && defined(DEBUG)
if (PR_LOG_TEST(gDocShellLog, PR_LOG_DEBUG)) {
nsAutoCString uristr;
aURI->GetAsciiSpec(uristr);
PR_LOG(gDocShellLog, PR_LOG_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
PR_LOG(gDocShellLog, PR_LOG_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 an owner (a referring principal).
//
// If ownerIsExplicit 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 inheritOwner should be true and InternalLoad will get
// an owner from the current document. If none of these things are
// true, then
// (4) we pass a null owner into the channel, and an owner will be
// created later from the channel's internal data.
//
// If ownerIsExplicit *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 inheritOwner should be true and InternalLoad will get an owner
// from the current document. If none of these things are true, then
// (4) we pass a null owner into the channel, and an owner will be
// created later from the channel's internal data.
//
// NOTE: This all only works because the only thing the owner is used
// for in InternalLoad is data:, javascript:, and about:blank
// URIs. For other URIs this would all be dead wrong!
if (owner && mItemType != typeChrome) {
nsCOMPtr<nsIPrincipal> ownerPrincipal = do_QueryInterface(owner);
if (nsContentUtils::IsSystemPrincipal(ownerPrincipal)) {
if (ownerIsExplicit) {
return NS_ERROR_DOM_SECURITY_ERR;
}
owner = nullptr;
inheritOwner = true;
} else if (nsContentUtils::IsExpandedPrincipal(ownerPrincipal)) {
if (ownerIsExplicit) {
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.
owner = do_CreateInstance("@mozilla.org/nullprincipal;1");
NS_ENSURE_TRUE(owner, NS_ERROR_FAILURE);
inheritOwner = false;
}
}
if (!owner && !inheritOwner && !ownerIsExplicit) {
// See if there's system or chrome JS code running
inheritOwner = nsContentUtils::IsCallerChrome();
}
if (aLoadFlags & LOAD_FLAGS_DISALLOW_INHERIT_OWNER) {
inheritOwner = false;
owner = do_CreateInstance("@mozilla.org/nullprincipal;1");
}
uint32_t flags = 0;
if (inheritOwner) {
flags |= INTERNAL_LOAD_FLAGS_INHERIT_OWNER;
}
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,
referrer,
referrerPolicy,
owner,
flags,
target.get(),
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;
if (aLoadInfo) {
nsDocShellInfoLoadType lt = nsIDocShellLoadInfo::loadNormal;
(void)aLoadInfo->GetLoadType(<);
// Get the appropriate LoadType from nsIDocShellLoadInfo type
loadType = ConvertDocShellLoadInfoToLoadType(lt);
}
NS_ENSURE_SUCCESS(Stop(nsIWebNavigation::STOP_NETWORK), NS_ERROR_FAILURE);
mLoadType = loadType;
nsCOMPtr<nsISupports> owner;
aLoadInfo->GetOwner(getter_AddRefs(owner));
nsCOMPtr<nsIPrincipal> requestingPrincipal = do_QueryInterface(owner);
if (!requestingPrincipal) {
requestingPrincipal = nsContentUtils::GetSystemPrincipal();
}
// build up a channel for this stream.
nsCOMPtr<nsIChannel> channel;
nsresult rv = NS_NewInputStreamChannel(getter_AddRefs(channel),
uri,
aStream,
requestingPrincipal,
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();
NS_ENSURE_TRUE(loadInfo, NS_ERROR_OUT_OF_MEMORY);
nsCOMPtr<nsIDocShellLoadInfo> localRef(loadInfo);
*aLoadInfo = localRef;
NS_ADDREF(*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> kungFuDeathGrip(mContentViewer);
mFiredUnloadEvent = true;
if (mTiming) {
mTiming->NotifyUnloadEventStart();
}
mContentViewer->PageHide(aIsUnload);
if (mTiming) {
mTiming->NotifyUnloadEventEnd();
}
nsAutoTArray<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) {
nsPIDOMWindow* innerWin = mScriptGlobal->GetCurrentInnerWindow();
if (innerWin && innerWin->GetPerformance()) {
mTiming = innerWin->GetPerformance()->GetDOMTiming();
mBlankTiming = false;
}
}
if (!mTiming) {
mTiming = new nsDOMNavigationTiming();
}
mTiming->NotifyNavigationStart();
}
//
// 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()
{
nsRefPtr<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;
nsRefPtr<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;
}
/* void setCurrentURI (in nsIURI uri); */
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)
{
#ifdef PR_LOGGING
if (gDocShellLeakLog && PR_LOG_TEST(gDocShellLeakLog, PR_LOG_DEBUG)) {
nsAutoCString spec;
if (aURI) {
aURI->GetSpec(spec);
}
PR_LogPrint("DOCSHELL %p SetCurrentURI %s\n", this, spec.get());
}
#endif
// 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);
}
// nsDocShell owns a URLSearchParams that is used by
// window.location.searchParams to be in sync with the current location.
if (!mURLSearchParams) {
mURLSearchParams = new URLSearchParams();
}
nsAutoCString search;
nsCOMPtr<nsIURL> url(do_QueryInterface(mCurrentURI));
if (url) {
nsresult rv = url->GetQuery(search);
if (NS_FAILED(rv)) {
NS_WARNING("Failed to get the query from a nsIURL.");
}
}
mURLSearchParams->ParseInput(search, nullptr);
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);
*aUsePrivateBrowsing = mInPrivateBrowsing;
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);
return SetPrivateBrowsing(aUsePrivateBrowsing);
}
NS_IMETHODIMP
nsDocShell::SetPrivateBrowsing(bool aUsePrivateBrowsing)
{
bool changed = aUsePrivateBrowsing != mInPrivateBrowsing;
if (changed) {
mInPrivateBrowsing = 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);
}
}
}
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 && mInPrivateBrowsing) {
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) {
nsPIDOMWindow* innerWin = mScriptGlobal->GetCurrentInnerWindow();
if (innerWin) {
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::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;
// For non-browsers/apps, check that the enclosing iframe element
// has the allowfullscreen attribute set to true. If any ancestor
// iframe does not have mozallowfullscreen=true, then fullscreen is
// prohibited.
nsCOMPtr<nsPIDOMWindow> win = GetWindow();
if (!win) {
return NS_OK;
}
nsCOMPtr<Element> frameElement = win->GetFrameElementInternal();
if (frameElement &&
frameElement->IsHTML(nsGkAtoms::iframe) &&
!frameElement->HasAttr(kNameSpaceID_None, nsGkAtoms::allowfullscreen) &&
!frameElement->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen)) {
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.
nsRefPtr<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::GetIsBrowserOrApp()) {
// 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;
}
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;
nsRefPtr<nsDocShellEnumerator> docShellEnum;
if (aDirection == ENUMERATE_FORWARDS) {
docShellEnum = new nsDocShellForwardsEnumerator;
} else {
docShellEnum = new nsDocShellBackwardsEnumerator;
}
if (!docShellEnum) {
return NS_ERROR_OUT_OF_MEMORY;
}
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* aTookFocus)
{
NS_ENSURE_ARG_POINTER(aTookFocus);
nsCOMPtr<nsIWebBrowserChromeFocus> chromeFocus = do_GetInterface(mTreeOwner);
if (chromeFocus) {
if (aForward) {
*aTookFocus = NS_SUCCEEDED(chromeFocus->FocusNextElement());
} else {
*aTookFocus = NS_SUCCEEDED(chromeFocus->FocusPrevElement());
}
} 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;
}
unsigned long nsDocShell::gProfileTimelineRecordingsCount = 0;
NS_IMETHODIMP
nsDocShell::SetRecordProfileTimelineMarkers(bool aValue)
{
bool currentValue = nsIDocShell::GetRecordProfileTimelineMarkers();
if (currentValue != aValue) {
if (aValue) {
++gProfileTimelineRecordingsCount;
UseEntryScriptProfiling();
mProfileTimelineRecording = true;
} else {
--gProfileTimelineRecordingsCount;
UnuseEntryScriptProfiling();
mProfileTimelineRecording = false;
ClearProfileTimelineMarkers();
}
}
return NS_OK;
}
NS_IMETHODIMP
nsDocShell::GetRecordProfileTimelineMarkers(bool* aValue)
{
*aValue = mProfileTimelineRecording;
return NS_OK;
}
nsresult
nsDocShell::PopProfileTimelineMarkers(
JSContext* aCx,
JS::MutableHandle<JS::Value> aProfileTimelineMarkers)
{
// Looping over all markers gathered so far at the docShell level, whenever a
// START marker is found, look for the corresponding END marker and build a
// {name,start,end} JS object.
// Paint markers are different because paint is handled at root docShell level
// in the information that a paint was done is then stored at each sub
// docShell level but we can only be sure that a paint did happen in a
// docShell if an Layer marker type was recorded too.
nsTArray<mozilla::dom::ProfileTimelineMarker> profileTimelineMarkers;
SequenceRooter<mozilla::dom::ProfileTimelineMarker> rooter(
aCx, &profileTimelineMarkers);
// If we see an unpaired START, we keep it around for the next call
// to PopProfileTimelineMarkers. We store the kept START objects in
// this array.
nsTArray<TimelineMarker*> keptMarkers;
for (uint32_t i = 0; i < mProfileTimelineMarkers.Length(); ++i) {
TimelineMarker* startPayload = mProfileTimelineMarkers[i];
const char* startMarkerName = startPayload->GetName();
bool hasSeenPaintedLayer = false;
bool isPaint = strcmp(startMarkerName, "Paint") == 0;
// If we are processing a Paint marker, we append information from
// all the embedded Layer markers to this array.
dom::Sequence<dom::ProfileTimelineLayerRect> layerRectangles;
if (startPayload->GetMetaData() == TRACING_INTERVAL_START) {
bool hasSeenEnd = false;
// DOM events can be nested, so we must take care when searching
// for the matching end. It doesn't hurt to apply this logic to
// all event types.
uint32_t markerDepth = 0;
// The assumption is that the devtools timeline flushes markers frequently
// enough for the amount of markers to always be small enough that the
// nested for loop isn't going to be a performance problem.
for (uint32_t j = i + 1; j < mProfileTimelineMarkers.Length(); ++j) {
TimelineMarker* endPayload = mProfileTimelineMarkers[j];
const char* endMarkerName = endPayload->GetName();
// Look for Layer markers to stream out paint markers.
if (isPaint && strcmp(endMarkerName, "Layer") == 0) {
hasSeenPaintedLayer = true;
endPayload->AddLayerRectangles(layerRectangles);
}
if (!startPayload->Equals(endPayload)) {
continue;
}
// Pair start and end markers.
if (endPayload->GetMetaData() == TRACING_INTERVAL_START) {
++markerDepth;
} else if (endPayload->GetMetaData() == TRACING_INTERVAL_END) {
if (markerDepth > 0) {
--markerDepth;
} else {
// But ignore paint start/end if no layer has been painted.
if (!isPaint || (isPaint && hasSeenPaintedLayer)) {
mozilla::dom::ProfileTimelineMarker* marker =
profileTimelineMarkers.AppendElement();
marker->mName = NS_ConvertUTF8toUTF16(startPayload->GetName());
marker->mStart = startPayload->GetTime();
marker->mEnd = endPayload->GetTime();
marker->mStack = startPayload->GetStack();
if (isPaint) {
marker->mRectangles.Construct(layerRectangles);
}
startPayload->AddDetails(*marker);
endPayload->AddDetails(*marker);
}
// We want the start to be dropped either way.
hasSeenEnd = true;
break;
}
}
}
// If we did not see the corresponding END, keep the START.
if (!hasSeenEnd) {
keptMarkers.AppendElement(mProfileTimelineMarkers[i]);
mProfileTimelineMarkers.RemoveElementAt(i);
--i;
}
}
}
ClearProfileTimelineMarkers();
mProfileTimelineMarkers.SwapElements(keptMarkers);
if (!ToJSValue(aCx, profileTimelineMarkers, aProfileTimelineMarkers)) {
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;
}
void
nsDocShell::AddProfileTimelineMarker(const char* aName,
TracingMetadata aMetaData)
{
if (mProfileTimelineRecording) {
TimelineMarker* marker = new TimelineMarker(this, aName, aMetaData);
mProfileTimelineMarkers.AppendElement(marker);
}
}
void
nsDocShell::AddProfileTimelineMarker(UniquePtr<TimelineMarker>& aMarker)
{
if (mProfileTimelineRecording) {
mProfileTimelineMarkers.AppendElement(aMarker.release());
}
}
NS_IMETHODIMP
nsDocShell::SetWindowDraggingAllowed(bool aValue)
{
nsRefPtr<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.
nsRefPtr<nsDocShell> parent = GetParentDocshell();
if (mItemType == typeChrome && !parent) {
// Top level chrome window
*aValue = true;
} else {
*aValue = mWindowDraggingAllowed;
}
return NS_OK;
}
void
nsDocShell::ClearProfileTimelineMarkers()
{
for (uint32_t i = 0; i < mProfileTimelineMarkers.Length(); ++i) {
delete mProfileTimelineMarkers[i];
}
mProfileTimelineMarkers.Clear();
}
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<nsIDOMWindow> domWin = do_GetInterface(GetAsSupports(this));
if (aCreate) {
return manager->CreateStorage(domWin, aPrincipal, aDocumentURI,
mInPrivateBrowsing, aStorage);
}
return manager->GetStorage(domWin, aPrincipal, mInPrivateBrowsing, aStorage);
}
nsresult
nsDocShell::AddSessionStorage(nsIPrincipal* aPrincipal, nsIDOMStorage* aStorage)
{
nsRefPtr<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(const mozilla::CSSIntPoint aScrollPos)
{
nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
while (iter.HasMore()) {
nsWeakPtr ref = iter.GetNext();
nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
if (obs) {
obs->AsyncPanZoomStarted(aScrollPos);
} else {
mScrollObservers.RemoveElement(ref);
}
}
// Also notify child docshell
for (uint32_t i = 0; i < mChildList.Length(); ++i) {
nsCOMPtr<nsIDocShell> kid = do_QueryInterface(ChildAt(i));
if (kid) {
nsDocShell* docShell = static_cast<nsDocShell*>(kid.get());
docShell->NotifyAsyncPanZoomStarted(aScrollPos);
}
}
}
void
nsDocShell::NotifyAsyncPanZoomStopped(const mozilla::CSSIntPoint aScrollPos)
{
nsTObserverArray<nsWeakPtr>::ForwardIterator iter(mScrollObservers);
while (iter.HasMore()) {
nsWeakPtr ref = iter.GetNext();
nsCOMPtr<nsIScrollObserver> obs = do_QueryReferent(ref);
if (obs) {
obs->AsyncPanZoomStopped(aScrollPos);
} else {
mScrollObservers.RemoveElement(ref);
}
}
// Also notify child docshell
for (uint32_t i = 0; i < mChildList.Length(); ++i) {
nsCOMPtr<nsIDocShell> kid = do_QueryInterface(ChildAt(i));
if (kid) {
nsDocShell* docShell = static_cast<nsDocShell*>(kid.get());
docShell->NotifyAsyncPanZoomStopped(aScrollPos);
}
}
}
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 char16_t* aName, bool* aResult)
{
NS_ENSURE_ARG_POINTER(aName);
NS_ENSURE_ARG_POINTER(aResult);
*aResult = mName.Equals(aName);
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;
nsRefPtr<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;
nsRefPtr<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();
nsDocLoader::SetDocLoaderParent(aParent);
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;
nsCOMPtr<nsIDocShell_ESR38> parentAsDocShell(do_QueryInterface(parent));
if (parentAsDocShell) {
if (NS_SUCCEEDED(parentAsDocShell->GetAllowPlugins(&value))) {
SetAllowPlugins(value);
}
if (NS_SUCCEEDED(parentAsDocShell->GetAllowJavascript(&value))) {
SetAllowJavascript(value);
}
if (NS_SUCCEEDED(parentAsDocShell->GetAllowMetaRedirects(&value))) {
SetAllowMetaRedirects(value);
}
if (NS_SUCCEEDED(parentAsDocShell->GetAllowSubframes(&value))) {
SetAllowSubframes(value);
}
if (NS_SUCCEEDED(parentAsDocShell->GetAllowImages(&value))) {
SetAllowImages(value);
}
SetAllowMedia(parentAsDocShell->GetAllowMedia());
if (NS_SUCCEEDED(parentAsDocShell->GetAllowWindowControl(&value))) {
SetAllowWindowControl(value);
}
SetAllowContentRetargeting(parentAsDocShell->GetAllowContentRetargetingOnChildren());
if (NS_SUCCEEDED(parentAsDocShell->GetIsActive(&value))) {
SetIsActive(value);
}
if (parentAsDocShell->GetIsPrerendered()) {
SetIsPrerendered(true);
}
if (NS_FAILED(parentAsDocShell->GetAllowDNSPrefetch(&value))) {
value = false;
}
SetAllowDNSPrefetch(value);
value = parentAsDocShell->GetAffectPrivateSessionLifetime();
SetAffectPrivateSessionLifetime(value);
uint32_t flags;
if (NS_SUCCEEDED(parentAsDocShell->GetDefaultLoadFlags(&flags))) {
SetDefaultLoadFlags(flags);
}
}
nsCOMPtr<nsILoadContext> parentAsLoadContext(do_QueryInterface(parent));
if (parentAsLoadContext &&
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();
return NS_OK;
}
NS_IMETHODIMP
nsDocShell::GetSameTypeParent(nsIDocShellTreeItem** aParent)
{
NS_ENSURE_ARG_POINTER(aParent);
*aParent = nullptr;
if (nsIDocShell::GetIsBrowserOrApp()) {
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);
nsRefPtr<nsDocShell> root = this;
nsRefPtr<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;
}
/* 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 or neither to nsIDocShell.
return false;
}
if (targetDS && accessingDS &&
(targetDS->GetIsInBrowserElement() !=
accessingDS->GetIsInBrowserElement() ||
targetDS->GetAppId() != accessingDS->GetAppId())) {
return false;
}
nsCOMPtr<nsIDocShellTreeItem> accessingRoot;
aAccessingItem->GetSameTypeRootTreeItem(getter_AddRefs(accessingRoot));
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);
nsCOMPtr<nsIDocShellTreeItem> targetRoot;
aTargetItem->GetSameTypeRootTreeItem(getter_AddRefs(targetRoot));
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<nsIDOMWindow> targetWindow = aTargetItem->GetWindow();
if (!targetWindow) {
NS_ERROR("This should not happen, really");
return false;
}
nsCOMPtr<nsIDOMWindow> targetOpener;
targetWindow->GetOpener(getter_AddRefs(targetOpener));
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)
{
nsCOMPtr<nsIDOMWindow> window = aItem->GetWindow();
if (window) {
bool isClosed;
if (NS_SUCCEEDED(window->GetClosed(&isClosed)) && !isClosed) {
return true;
}
}
return false;
}
NS_IMETHODIMP
nsDocShell::FindItemWithName(const char16_t* aName,
nsISupports* aRequestor,
nsIDocShellTreeItem* aOriginalRequestor,
nsIDocShellTreeItem** aResult)
{
NS_ENSURE_ARG(aName);
NS_ENSURE_ARG_POINTER(aResult);
// If we don't find one, we return NS_OK and a null result
*aResult = nullptr;
if (!*aName) {
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;
nsDependentString name(aName);
if (name.LowerCaseEqualsLiteral("_self")) {
foundItem = this;
} else if (name.LowerCaseEqualsLiteral("_blank")) {
// Just return null. Caller must handle creating a new window with
// a blank name himself.
return NS_OK;
} else if (name.LowerCaseEqualsLiteral("_parent")) {
GetSameTypeParent(getter_AddRefs(foundItem));
if (!foundItem) {
foundItem = this;
}
} else if (name.LowerCaseEqualsLiteral("_top")) {
GetSameTypeRootTreeItem(getter_AddRefs(foundItem));
NS_ASSERTION(foundItem, "Must have this; worst case it's us!");
}
// _main is an IE target which should be case-insensitive but isn't
// see bug 217886 for details
else if (name.LowerCaseEqualsLiteral("_content") ||
name.EqualsLiteral("_main")) {
// Must pass our same type root as requestor to the
// treeowner to make sure things work right.
nsCOMPtr<nsIDocShellTreeItem> root;
GetSameTypeRootTreeItem(getter_AddRefs(root));
if (mTreeOwner) {
NS_ASSERTION(root, "Must have this; worst case it's us!");
mTreeOwner->FindItemWithName(aName, root, aOriginalRequestor,
getter_AddRefs(foundItem));
}
#ifdef DEBUG
else {
NS_ERROR("Someone isn't setting up the tree owner. "
"You might like to try that. "
"Things will.....you know, work.");
// Note: _content should always exist. If we don't have one
// hanging off the treeowner, just create a named window....
// so don't return here, in case we did that and can now find
// it.
// XXXbz should we be using |root| instead of creating
// a new window?
}
#endif
} 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;
}
}
nsresult
nsDocShell::DoFindItemWithName(const char16_t* 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 (parentAsTreeItem->ItemType() == mItemType) {
return parentAsTreeItem->FindItemWithName(
aName,
static_cast<nsIDocShellTreeItem*>(this),
aOriginalRequestor,
aResult);
}
}
// If the parent is null or not of the same type fall through and ask tree
// owner.
// This may fail, but comparing against null serves the same purpose
nsCOMPtr<nsIDocShellTreeOwner> reqAsTreeOwner(do_QueryInterface(aRequestor));
if (mTreeOwner && mTreeOwner != reqAsTreeOwner) {
return mTreeOwner->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();
nsRefPtr<nsPresContext> presContext;
parentAsDocShell->GetPresContext(getter_AddRefs(presContext));
nsIDocument* doc = presShell->GetDocument();
nsCOMPtr<nsIDOMWindow> 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);
nsRefPtr<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) {
childsParent->RemoveChildLoader(childAsDocLoader);
}
// 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);
nsRefPtr<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 char16_t* aName,
bool aRecurse, bool aSameType,
nsIDocShellTreeItem* aRequestor,
nsIDocShellTreeItem* aOriginalRequestor,
nsIDocShellTreeItem** aResult)
{
NS_ENSURE_ARG(aName);
NS_ENSURE_ARG_POINTER(aResult);
// if we don't find one, we return NS_OK and a null result
*aResult = nullptr;
if (!*aName) {
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);
nsAutoTArray<uint64_t, 16> ids;
ids.AppendElement(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();
}
nsPIDOMWindow*
nsDocShell::GetWindow()
{
NS_ENSURE_SUCCESS(EnsureScriptEnvironment(), nullptr);
return mScriptGlobal;
}
NS_IMETHODIMP
nsDocShell::SetDeviceSizeIsPageSize(bool aValue)
{
if (mDeviceSizeIsPageSize != aValue) {
mDeviceSizeIsPageSize = aValue;
nsRefPtr<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);
nsAutoTArray<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) {
DisplayLoadError(rv, uri, aURI, nullptr);
}
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)
{
// 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_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 {
// 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 =
mInPrivateBrowsing ? nsISocketProvider::NO_PERMANENT_STORAGE : 0;
bool isStsHost = false;
bool isPinnedHost = false;
if (XRE_GetProcessType() == GeckoProcessType_Default) {
nsCOMPtr<nsISiteSecurityService> sss =
do_GetService(NS_SSSERVICE_CONTRACTID, &rv);
NS_ENSURE_SUCCESS(rv, rv);
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HSTS, aURI,
flags, &isStsHost);
NS_ENSURE_SUCCESS(rv, rv);
rv = sss->IsSecureURI(nsISiteSecurityService::HEADER_HPKP, aURI,
flags, &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) {
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;
if (NS_ERROR_PHISHING_URI == aError) {
error.AssignLiteral("phishingBlocked");
bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_PHISHING_PAGE_FRAME
: nsISecurityUITelemetry::WARNING_PHISHING_PAGE_TOP;
} else {
error.AssignLiteral("malwareBlocked");
bucketId = IsFrame() ? nsISecurityUITelemetry::WARNING_MALWARE_PAGE_FRAME
: nsISecurityUITelemetry::WARNING_MALWARE_PAGE_TOP;
}
if (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(MOZ_UTF16(" "));
}
} 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("corruptedContentError");
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
LoadErrorPage(aURI, aURL, errorPage.get(), error.get(),
messageStr.get(), cssClass.get(), aFailedChannel);
} 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;
}
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(PR_LOGGING) && defined(DEBUG)
if (PR_LOG_TEST(gDocShellLog, PR_LOG_DEBUG)) {
nsAutoCString spec;
aURI->GetSpec(spec);
nsAutoCString chanName;
if (aFailedChannel) {
aFailedChannel->GetName(chanName);
} else {
chanName.AssignLiteral("<no channel>");
}
PR_LOG(gDocShellLog, PR_LOG_DEBUG,
("nsDocShell[%p]::LoadErrorPage(\"%s\", \"%s\", {...}, [%s])\n", this,
spec.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(cstring, escArg1, escArg2) \
{ \
char* s = nsEscape(escArg1, escArg2); \
if (!s) \
return NS_ERROR_OUT_OF_MEMORY; \
cstring.Adopt(s); \
}
nsCString escapedUrl, escapedCharset, escapedError, escapedDescription,
escapedCSSClass;
SAFE_ESCAPE(escapedUrl, url.get(), url_Path);
SAFE_ESCAPE(escapedCharset, charset.get(), url_Path);
SAFE_ESCAPE(escapedError,
NS_ConvertUTF16toUTF8(aErrorType).get(), url_Path);
SAFE_ESCAPE(escapedDescription,
NS_ConvertUTF16toUTF8(aDescription).get(), url_Path);
if (aCSSClass) {
SAFE_ESCAPE(escapedCSSClass, aCSSClass, url_Path);
}
nsCString errorPageUrl("about:");
errorPageUrl.AppendASCII(aErrorPage);
errorPageUrl.AppendLiteral("?e=");
errorPageUrl.AppendASCII(escapedError.get());
errorPageUrl.AppendLiteral("&u=");
errorPageUrl.AppendASCII(escapedUrl.get());
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).get(),
url_Path);
errorPageUrl.AppendLiteral("&m=");
errorPageUrl.AppendASCII(manifestParam.get());
}
// 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, mozilla::net::RP_Default,
nullptr, INTERNAL_LOAD_FLAGS_INHERIT_OWNER, nullptr,
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());
// Do not inherit owner from document
uint32_t flags = INTERNAL_LOAD_FLAGS_NONE;
nsAutoString srcdoc;
nsIPrincipal* principal = nullptr;
nsAutoString contentTypeHint;
nsCOMPtr<nsIURI> baseURI;
if (doc) {
principal = doc->NodePrincipal();
doc->GetContentType(contentTypeHint);
if (doc->IsSrcdocDocument()) {
doc->GetSrcdocData(srcdoc);
flags |= INTERNAL_LOAD_FLAGS_IS_SRCDOC;
baseURI = doc->GetBaseURI();
}
}
rv = InternalLoad(mCurrentURI,
mReferrerURI,
mReferrerPolicy,
principal,
flags,
nullptr, // 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);
}
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, false);
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;
}
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 (mURLSearchParams) {
mURLSearchParams->RemoveObservers();
mURLSearchParams = 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 (mInPrivateBrowsing) {
mInPrivateBrowsing = 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::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::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);
}
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, bool aFRepaint)
{
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) {
// XXX Border figured in here or is that handled elsewhere?
NS_ENSURE_SUCCESS(viewer->SetBounds(mBounds), 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
nsIntRect r;
mParentWidget->GetClientBounds(r);
SetPositionAndSize(mBounds.x, mBounds.y, r.width, r.height, false);
}
// 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.
nsRefPtr<nsDocShell> docShell = this;
nsRefPtr<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;
// 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()) {
doc->PostVisibilityUpdateEvent();
}
}
// 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->GetIsBrowserOrApp()) {
docshell->SetIsActive(aIsActive);
}
}
return NS_OK;
}
NS_IMETHODIMP
nsDocShell::GetIsActive(bool* aIsActive)
{
*aIsActive = mIsActive;
return NS_OK;
}
NS_IMETHODIMP
nsDocShell::SetIsPrerendered(bool aPrerendered)
{
MOZ_ASSERT(!aPrerendered || !mIsPrerendered,
"SetIsPrerendered(true) called on already prerendered docshell");
mIsPrerendered = aPrerendered;
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);
NS_IF_ADDREF(*aSandboxedNavigator = permittedNavigator);
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_WARN_IF_FALSE(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);
}
}
if (mCurrentURI && mLoadType != LOAD_ERROR_PAGE && mUseGlobalHistory &&
!mInPrivateBrowsing) {
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();
NS_ENSURE_TRUE(sf, 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)
{
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();
NS_ENSURE_TRUE(refreshTimer, NS_ERROR_OUT_OF_MEMORY);
uint32_t busyFlags = 0;
GetBusyFlags(&busyFlags);
nsCOMPtr<nsISupports> dataRef = refreshTimer; // Get the ref count to 1
refreshTimer->mDocShell = this;
refreshTimer->mURI = aURI;
refreshTimer->mDelay = aDelay;
refreshTimer->mRepeat = aRepeat;
refreshTimer->mMetaRefresh = aMetaRefresh;
if (!mRefreshURIList) {
NS_ENSURE_SUCCESS(NS_NewISupportsArray(getter_AddRefs(mRefreshURIList)),
NS_ERROR_FAILURE);
}
if (busyFlags & BUSY_FLAGS_BUSY) {
// We are busy loading another page. Don't create the
// timer right now. Instead queue up the request and trigger the
// timer in EndPageLoad().
mRefreshURIList->AppendElement(refreshTimer);
} 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