Source code

Revision control

Copy as Markdown

Other Tools

/* -*- 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/. */
/*
* Class for managing loading of a subframe (creation of the docshell,
* handling of loads in it, recursion-checking).
*/
#include "nsFrameLoader.h"
#include "base/basictypes.h"
#include "prenv.h"
#include "nsDocShell.h"
#include "nsIContentInlines.h"
#include "nsIContentViewer.h"
#include "nsIPrintSettings.h"
#include "nsIPrintSettingsService.h"
#include "mozilla/dom/Document.h"
#include "nsPIDOMWindow.h"
#include "nsIWebNavigation.h"
#include "nsIWebProgress.h"
#include "nsIDocShell.h"
#include "nsIDocShellTreeOwner.h"
#include "nsDocShellLoadState.h"
#include "nsIBaseWindow.h"
#include "nsIBrowser.h"
#include "nsContentUtils.h"
#include "nsUnicharUtils.h"
#include "nsIScriptGlobalObject.h"
#include "nsIScriptSecurityManager.h"
#include "nsFrameLoaderOwner.h"
#include "nsIFrame.h"
#include "nsIScrollableFrame.h"
#include "nsSubDocumentFrame.h"
#include "nsError.h"
#include "nsIAppWindow.h"
#include "nsIMozBrowserFrame.h"
#include "nsIScriptError.h"
#include "nsGlobalWindow.h"
#include "nsHTMLDocument.h"
#include "nsPIWindowRoot.h"
#include "nsLayoutUtils.h"
#include "nsMappedAttributes.h"
#include "nsView.h"
#include "nsBaseWidget.h"
#include "nsQueryObject.h"
#include "ReferrerInfo.h"
#include "nsIOpenWindowInfo.h"
#include "nsISHistory.h"
#include "nsIURI.h"
#include "nsIXULRuntime.h"
#include "nsNetUtil.h"
#include "nsFocusManager.h"
#include "nsIINIParser.h"
#include "nsAppRunner.h"
#include "nsDirectoryService.h"
#include "nsDirectoryServiceDefs.h"
#include "nsGkAtoms.h"
#include "nsNameSpaceManager.h"
#include "nsThreadUtils.h"
#include "nsIDOMChromeWindow.h"
#include "InProcessBrowserChildMessageManager.h"
#include "ContentParent.h"
#include "BrowserParent.h"
#include "mozilla/AsyncEventDispatcher.h"
#include "mozilla/BasePrincipal.h"
#include "mozilla/ExpandedPrincipal.h"
#include "mozilla/FlushType.h"
#include "mozilla/HTMLEditor.h"
#include "mozilla/NullPrincipal.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/PresShellInlines.h"
#include "mozilla/ProcessPriorityManager.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_fission.h"
#include "mozilla/Unused.h"
#include "mozilla/dom/BrowsingContext.h"
#include "mozilla/dom/ChromeMessageSender.h"
#include "mozilla/dom/Element.h"
#include "mozilla/dom/FrameCrashedEvent.h"
#include "mozilla/dom/FrameLoaderBinding.h"
#include "mozilla/dom/InProcessChild.h"
#include "mozilla/dom/MozFrameLoaderOwnerBinding.h"
#include "mozilla/dom/PBrowser.h"
#include "mozilla/dom/SessionHistoryEntry.h"
#include "mozilla/dom/SessionStoreChild.h"
#include "mozilla/dom/SessionStoreParent.h"
#include "mozilla/dom/SessionStoreUtils.h"
#include "mozilla/dom/WindowGlobalParent.h"
#include "mozilla/dom/XULFrameElement.h"
#include "mozilla/gfx/CrossProcessPaint.h"
#include "mozilla/ProfilerLabels.h"
#include "nsGenericHTMLFrameElement.h"
#include "jsapi.h"
#include "mozilla/dom/HTMLIFrameElement.h"
#include "nsSandboxFlags.h"
#include "mozilla/layers/CompositorBridgeChild.h"
#include "mozilla/dom/CustomEvent.h"
#include "mozilla/dom/ipc/StructuredCloneData.h"
#include "mozilla/WebBrowserPersistLocalDocument.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseNativeHandler.h"
#include "mozilla/dom/ChildSHistory.h"
#include "mozilla/dom/CanonicalBrowsingContext.h"
#include "mozilla/dom/ContentChild.h"
#include "mozilla/dom/ContentProcessManager.h"
#include "mozilla/dom/BrowserBridgeChild.h"
#include "mozilla/dom/BrowserHost.h"
#include "mozilla/dom/BrowserBridgeHost.h"
#include "mozilla/dom/BrowsingContextGroup.h"
#include "mozilla/dom/SessionStorageManager.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/dom/PBackgroundSessionStorageCache.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/dom/HTMLBodyElement.h"
#include "mozilla/ContentPrincipal.h"
#include "nsXULPopupManager.h"
#ifdef NS_PRINTING
# include "nsIWebBrowserPrint.h"
#endif
#if defined(MOZ_TELEMETRY_REPORTING)
# include "mozilla/Telemetry.h"
#endif // defined(MOZ_TELEMETRY_REPORTING)
using namespace mozilla;
using namespace mozilla::hal;
using namespace mozilla::dom;
using namespace mozilla::dom::ipc;
using namespace mozilla::ipc;
using namespace mozilla::layers;
using namespace mozilla::layout;
using ViewID = ScrollableLayerGuid::ViewID;
using PrintPreviewResolver = std::function<void(const PrintPreviewResultInfo&)>;
// Bug 8065: Limit content frame depth to some reasonable level. This
// does not count chrome frames when determining depth, nor does it
// prevent chrome recursion. Number is fairly arbitrary, but meant to
// keep number of shells to a reasonable number on accidental recursion with a
// small (but not 1) branching factor. With large branching factors the number
// of shells can rapidly become huge and run us out of memory. To solve that,
// we'd need to re-institute a fixed version of bug 98158.
#define MAX_DEPTH_CONTENT_FRAMES 10
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(nsFrameLoader, mPendingBrowsingContext,
mMessageManager, mChildMessageManager,
mRemoteBrowser, mSessionStoreChild)
NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameLoader)
NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameLoader)
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameLoader)
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
NS_INTERFACE_MAP_ENTRY_CONCRETE(nsFrameLoader)
NS_INTERFACE_MAP_ENTRY(nsIMutationObserver)
NS_INTERFACE_MAP_ENTRY(nsISupports)
NS_INTERFACE_MAP_END
nsFrameLoader::nsFrameLoader(Element* aOwner, BrowsingContext* aBrowsingContext,
bool aIsRemoteFrame, bool aNetworkCreated)
: mPendingBrowsingContext(aBrowsingContext),
mOwnerContent(aOwner),
mDetachedSubdocFrame(nullptr),
mPendingSwitchID(0),
mChildID(0),
mRemoteType(NOT_REMOTE_TYPE),
mInitialized(false),
mDepthTooGreat(false),
mIsTopLevelContent(false),
mDestroyCalled(false),
mNeedsAsyncDestroy(false),
mInSwap(false),
mInShow(false),
mHideCalled(false),
mNetworkCreated(aNetworkCreated),
mLoadingOriginalSrc(false),
mRemoteBrowserShown(false),
mIsRemoteFrame(aIsRemoteFrame),
mWillChangeProcess(false),
mObservingOwnerContent(false),
mTabProcessCrashFired(false) {
nsCOMPtr<nsFrameLoaderOwner> owner = do_QueryInterface(aOwner);
owner->AttachFrameLoader(this);
}
nsFrameLoader::~nsFrameLoader() {
if (mMessageManager) {
mMessageManager->Disconnect();
}
MOZ_ASSERT(!mOwnerContent);
MOZ_RELEASE_ASSERT(mDestroyCalled);
}
static nsAtom* TypeAttrName(Element* aOwnerContent) {
return aOwnerContent->IsXULElement() ? nsGkAtoms::type
: nsGkAtoms::mozframetype;
}
static void GetFrameName(Element* aOwnerContent, nsAString& aFrameName) {
int32_t namespaceID = aOwnerContent->GetNameSpaceID();
if (namespaceID == kNameSpaceID_XHTML && !aOwnerContent->IsInHTMLDocument()) {
aOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, aFrameName);
} else {
aOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, aFrameName);
// XXX if no NAME then use ID, after a transition period this will be
// changed so that XUL only uses ID too (bug 254284).
if (aFrameName.IsEmpty() && namespaceID == kNameSpaceID_XUL) {
aOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::id, aFrameName);
}
}
}
// If this method returns true, the nsFrameLoader will act as a boundary, as is
// the case for <iframe mozbrowser> and <browser type="content"> elements.
//
// # Historical Notes (10 April 2019)
//
// In the past, this boundary was defined by the "typeContent" and "typeChrome"
// nsIDocShellTreeItem types. There was only ever a single split in the tree,
// and it occurred at the boundary between these two types of docshells. When
// <iframe mozbrowser> was introduced, it was given special casing to make it
// act like a second boundary, without having to change the existing code.
//
// The about:addons page, which is loaded within a content browser, then added a
// remote <browser type="content" remote="true"> element. When remote, this
// would also act as a mechanism for creating a disjoint tree, due to the
// process keeping the embedder and embedee separate.
//
// However, when initial out-of-process iframe support was implemented, this
// codepath became a risk, as it could've caused the oop iframe remote
// WindowProxy code to be activated for the addons page. This was fixed by
// extendng the isolation logic previously reserved to <iframe mozbrowser> to
// also cover <browser> elements with the explicit `remote` property loaded in
// content.
//
// To keep these boundaries clear, and allow them to work in a cross-process
// manner, they are no longer handled by typeContent and typeChrome. Instead,
// the actual BrowsingContext tree is broken at these edges.
static bool IsTopContent(BrowsingContext* aParent, Element* aOwner) {
if (XRE_IsContentProcess()) {
return false;
}
// If we have a (deprecated) mozbrowser element, we want to start a new
// BrowsingContext tree regardless of whether the parent is chrome or content.
nsCOMPtr<nsIMozBrowserFrame> mozbrowser = aOwner->GetAsMozBrowserFrame();
if (mozbrowser && mozbrowser->GetReallyIsBrowser()) {
return true;
}
if (aParent->IsContent()) {
// If we're already in content, we may still want to create a new
// BrowsingContext tree if our element is a xul browser element with a
// `remote="true"` marker.
return aOwner->IsXULElement() &&
aOwner->AttrValueIs(kNameSpaceID_None, nsGkAtoms::remote,
nsGkAtoms::_true, eCaseMatters);
}
// If we're in a chrome context, we want to start a new tree if we are an
// element with a `type="content"` marker.
return aOwner->AttrValueIs(kNameSpaceID_None, TypeAttrName(aOwner),
nsGkAtoms::content, eIgnoreCase);
}
static already_AddRefed<BrowsingContext> CreateBrowsingContext(
Element* aOwner, nsIOpenWindowInfo* aOpenWindowInfo,
BrowsingContextGroup* aSpecificGroup, bool aNetworkCreated = false) {
MOZ_ASSERT(!aOpenWindowInfo || !aSpecificGroup,
"Only one of SpecificGroup and OpenWindowInfo may be provided!");
// If we've got a pending BrowserParent from the content process, use the
// BrowsingContext which was created for it.
if (aOpenWindowInfo && aOpenWindowInfo->GetNextRemoteBrowser()) {
MOZ_ASSERT(XRE_IsParentProcess());
return do_AddRef(
aOpenWindowInfo->GetNextRemoteBrowser()->GetBrowsingContext());
}
RefPtr<BrowsingContext> opener;
if (aOpenWindowInfo && !aOpenWindowInfo->GetForceNoOpener()) {
opener = aOpenWindowInfo->GetParent();
if (opener) {
// Must create BrowsingContext with opener in-process.
MOZ_ASSERT(opener->IsInProcess());
// This can only happen when the opener was closed from a nested event
// loop in the window provider code, and only when the open was triggered
// by a non-e10s tab, and the new tab is being opened in a new browser
// window. Since it is a corner case among corner cases, and the opener
// window will appear to be null to consumers after it is discarded
// anyway, just drop the opener entirely.
if (opener->IsDiscarded()) {
NS_WARNING(
"Opener was closed from a nested event loop in the parent process. "
"Please fix this.");
opener = nullptr;
}
}
}
RefPtr<nsGlobalWindowInner> parentInner =
nsGlobalWindowInner::Cast(aOwner->OwnerDoc()->GetInnerWindow());
if (NS_WARN_IF(!parentInner) || parentInner->IsDying()) {
return nullptr;
}
BrowsingContext* parentBC = parentInner->GetBrowsingContext();
if (NS_WARN_IF(!parentBC) || parentBC->IsDiscarded()) {
return nullptr;
}
// Determine the frame name for the new browsing context.
nsAutoString frameName;
GetFrameName(aOwner, frameName);
// Create our BrowsingContext without immediately attaching it. It's possible
// that no DocShell or remote browser will ever be created for this
// FrameLoader, particularly if the document that we were created for is not
// currently active. And in that latter case, if we try to attach our BC now,
// it will wind up attached as a child of the currently active inner window
// for the BrowsingContext, and cause no end of trouble.
if (IsTopContent(parentBC, aOwner)) {
// Create toplevel context without a parent & as Type::Content.
return BrowsingContext::CreateDetached(
nullptr, opener, aSpecificGroup, frameName,
BrowsingContext::Type::Content, false);
}
MOZ_ASSERT(!aOpenWindowInfo,
"Can't have openWindowInfo for non-toplevel context");
MOZ_ASSERT(!aSpecificGroup,
"Can't force BrowsingContextGroup for non-toplevel context");
return BrowsingContext::CreateDetached(parentInner, nullptr, nullptr,
frameName, parentBC->GetType(), false,
!aNetworkCreated);
}
static bool InitialLoadIsRemote(Element* aOwner) {
if (PR_GetEnv("MOZ_DISABLE_OOP_TABS") ||
Preferences::GetBool("dom.ipc.tabs.disabled", false)) {
return false;
}
// The initial load in an content process iframe should never be made remote.
// Content process iframes always become remote due to navigation.
if (XRE_IsContentProcess()) {
return false;
}
// If we're an <iframe mozbrowser> and we don't have a "remote" attribute,
// fall back to the default.
nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aOwner);
bool isMozBrowserFrame = browserFrame && browserFrame->GetReallyIsBrowser();
if (isMozBrowserFrame &&
!aOwner->HasAttr(kNameSpaceID_None, nsGkAtoms::remote)) {
return Preferences::GetBool("dom.ipc.browser_frames.oop_by_default", false);
}
// Otherwise, we're remote if we have "remote=true" and we're either a
// browser frame or a XUL element.
return (isMozBrowserFrame || aOwner->GetNameSpaceID() == kNameSpaceID_XUL) &&
aOwner->AttrValueIs(kNameSpaceID_None, nsGkAtoms::remote,
nsGkAtoms::_true, eCaseMatters);
}
static already_AddRefed<BrowsingContextGroup> InitialBrowsingContextGroup(
Element* aOwner) {
nsAutoString attrString;
if (aOwner->GetNameSpaceID() != kNameSpaceID_XUL ||
!aOwner->GetAttr(nsGkAtoms::initialBrowsingContextGroupId, attrString)) {
return nullptr;
}
// It's OK to read the attribute using a signed 64-bit integer parse, as an ID
// generated using `nsContentUtils::GenerateProcessSpecificId` (like BCG IDs)
// will only ever use 53 bits of precision, so it can be round-tripped through
// a JS number.
nsresult rv = NS_OK;
int64_t signedGroupId = attrString.ToInteger64(&rv, 10);
if (NS_FAILED(rv) || signedGroupId <= 0) {
MOZ_DIAGNOSTIC_ASSERT(
false, "we intended to have a particular id, but failed to parse it!");
return nullptr;
}
return BrowsingContextGroup::GetOrCreate(uint64_t(signedGroupId));
}
already_AddRefed<nsFrameLoader> nsFrameLoader::Create(
Element* aOwner, bool aNetworkCreated, nsIOpenWindowInfo* aOpenWindowInfo) {
NS_ENSURE_TRUE(aOwner, nullptr);
Document* doc = aOwner->OwnerDoc();
// We never create nsFrameLoaders for elements in resource documents.
//
// We never create nsFrameLoaders for elements in data documents, unless the
// document is a static document.
// Static documents are an exception because any sub-documents need an
// nsFrameLoader to keep the relevant docShell alive, even though the
// nsFrameLoader isn't used to load anything (the sub-document is created by
// the static clone process).
//
// We never create nsFrameLoaders for elements that are not
// in-composed-document, unless the element belongs to a static document.
// Static documents are an exception because this method is called at a point
// in the static clone process before aOwner has been inserted into its
// document. For other types of documents this wouldn't be a problem since
// we'd create the nsFrameLoader as necessary after aOwner is inserted into a
// document, but the mechanisms that take care of that don't apply for static
// documents so we need to create the nsFrameLoader now. (This isn't wasteful
// since for a static document we know aOwner will end up in a document and
// the nsFrameLoader will be used for its docShell.)
//
NS_ENSURE_TRUE(!doc->IsResourceDoc() &&
((!doc->IsLoadedAsData() && aOwner->IsInComposedDoc()) ||
doc->IsStaticDocument()),
nullptr);
RefPtr<BrowsingContextGroup> group = InitialBrowsingContextGroup(aOwner);
RefPtr<BrowsingContext> context =
CreateBrowsingContext(aOwner, aOpenWindowInfo, group, aNetworkCreated);
NS_ENSURE_TRUE(context, nullptr);
if (XRE_IsParentProcess() && aOpenWindowInfo) {
MOZ_ASSERT(context->IsTopContent());
if (RefPtr<BrowsingContext> crossGroupOpener =
aOpenWindowInfo->GetParent()) {
context->Canonical()->SetCrossGroupOpenerId(crossGroupOpener->Id());
}
}
bool isRemoteFrame = InitialLoadIsRemote(aOwner);
RefPtr<nsFrameLoader> fl =
new nsFrameLoader(aOwner, context, isRemoteFrame, aNetworkCreated);
fl->mOpenWindowInfo = aOpenWindowInfo;
// If this is a toplevel initial remote frame, we're looking at a browser
// loaded in the parent process. Pull the remote type attribute off of the
// <browser> element to determine which remote type it should be loaded in, or
// use `DEFAULT_REMOTE_TYPE` if we can't tell.
if (isRemoteFrame) {
MOZ_ASSERT(XRE_IsParentProcess());
nsAutoString remoteType;
if (aOwner->GetAttr(kNameSpaceID_None, nsGkAtoms::RemoteType, remoteType) &&
!remoteType.IsEmpty()) {
CopyUTF16toUTF8(remoteType, fl->mRemoteType);
} else {
fl->mRemoteType = DEFAULT_REMOTE_TYPE;
}
}
return fl.forget();
}
/* static */
already_AddRefed<nsFrameLoader> nsFrameLoader::Recreate(
mozilla::dom::Element* aOwner, BrowsingContext* aContext,
BrowsingContextGroup* aSpecificGroup,
const NavigationIsolationOptions& aRemotenessOptions, bool aIsRemote,
bool aNetworkCreated, bool aPreserveContext) {
NS_ENSURE_TRUE(aOwner, nullptr);
#ifdef DEBUG
// This version of Create is only called for Remoteness updates, so we can
// assume we need a FrameLoader here and skip the check in the other Create.
Document* doc = aOwner->OwnerDoc();
MOZ_ASSERT(!doc->IsResourceDoc());
MOZ_ASSERT((!doc->IsLoadedAsData() && aOwner->IsInComposedDoc()) ||
doc->IsStaticDocument());
#endif
RefPtr<BrowsingContext> context = aContext;
if (!context || !aPreserveContext) {
context = CreateBrowsingContext(aOwner, /* openWindowInfo */ nullptr,
aSpecificGroup);
if (aContext) {
MOZ_ASSERT(
XRE_IsParentProcess(),
"Recreating browing contexts only supported in the parent process");
aContext->Canonical()->SynchronizeLayoutHistoryState();
aContext->Canonical()->ReplacedBy(context->Canonical(),
aRemotenessOptions);
}
}
NS_ENSURE_TRUE(context, nullptr);
RefPtr<nsFrameLoader> fl =
new nsFrameLoader(aOwner, context, aIsRemote, aNetworkCreated);
return fl.forget();
}
void nsFrameLoader::LoadFrame(bool aOriginalSrc) {
if (NS_WARN_IF(!mOwnerContent)) {
return;
}
nsAutoString src;
nsCOMPtr<nsIPrincipal> principal;
nsCOMPtr<nsIContentSecurityPolicy> csp;
bool isSrcdoc = mOwnerContent->IsHTMLElement(nsGkAtoms::iframe) &&
mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc);
if (isSrcdoc) {
src.AssignLiteral("about:srcdoc");
principal = mOwnerContent->NodePrincipal();
csp = mOwnerContent->GetCsp();
} else {
GetURL(src, getter_AddRefs(principal), getter_AddRefs(csp));
src.Trim(" \t\n\r");
if (src.IsEmpty()) {
// If the frame is a XUL element and has the attribute 'nodefaultsrc=true'
// then we will not use 'about:blank' as fallback but return early without
// starting a load if no 'src' attribute is given (or it's empty).
if (mOwnerContent->IsXULElement() &&
mOwnerContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::nodefaultsrc,
nsGkAtoms::_true, eCaseMatters)) {
return;
}
src.AssignLiteral("about:blank");
principal = mOwnerContent->NodePrincipal();
csp = mOwnerContent->GetCsp();
}
}
Document* doc = mOwnerContent->OwnerDoc();
if (doc->IsStaticDocument()) {
return;
}
nsIURI* base_uri = mOwnerContent->GetBaseURI();
auto encoding = doc->GetDocumentCharacterSet();
nsCOMPtr<nsIURI> uri;
nsresult rv = NS_NewURI(getter_AddRefs(uri), src, encoding, base_uri);
// If the URI was malformed, try to recover by loading about:blank.
if (rv == NS_ERROR_MALFORMED_URI) {
rv = NS_NewURI(getter_AddRefs(uri), u"about:blank"_ns, encoding, base_uri);
}
if (NS_SUCCEEDED(rv)) {
rv = LoadURI(uri, principal, csp, aOriginalSrc);
}
if (NS_FAILED(rv)) {
FireErrorEvent();
}
}
void nsFrameLoader::ConfigRemoteProcess(const nsACString& aRemoteType,
ContentParent* aContentParent) {
MOZ_DIAGNOSTIC_ASSERT(IsRemoteFrame(), "Must be a remote frame");
MOZ_DIAGNOSTIC_ASSERT(!mRemoteBrowser, "Must not have a browser yet");
MOZ_DIAGNOSTIC_ASSERT_IF(aContentParent,
aContentParent->GetRemoteType() == aRemoteType);
mRemoteType = aRemoteType;
mChildID = aContentParent ? aContentParent->ChildID() : 0;
}
void nsFrameLoader::FireErrorEvent() {
if (!mOwnerContent) {
return;
}
RefPtr<AsyncEventDispatcher> loadBlockingAsyncDispatcher =
new LoadBlockingAsyncEventDispatcher(
mOwnerContent, u"error"_ns, CanBubble::eNo, ChromeOnlyDispatch::eNo);
loadBlockingAsyncDispatcher->PostDOMEvent();
}
nsresult nsFrameLoader::LoadURI(nsIURI* aURI,
nsIPrincipal* aTriggeringPrincipal,
nsIContentSecurityPolicy* aCsp,
bool aOriginalSrc) {
if (!aURI) return NS_ERROR_INVALID_POINTER;
NS_ENSURE_STATE(!mDestroyCalled && mOwnerContent);
MOZ_ASSERT(
aTriggeringPrincipal,
"Must have an explicit triggeringPrincipal to nsFrameLoader::LoadURI.");
mLoadingOriginalSrc = aOriginalSrc;
nsCOMPtr<Document> doc = mOwnerContent->OwnerDoc();
nsresult rv;
rv = CheckURILoad(aURI, aTriggeringPrincipal);
NS_ENSURE_SUCCESS(rv, rv);
mURIToLoad = aURI;
mTriggeringPrincipal = aTriggeringPrincipal;
mCsp = aCsp;
rv = doc->InitializeFrameLoader(this);
if (NS_FAILED(rv)) {
mURIToLoad = nullptr;
mTriggeringPrincipal = nullptr;
mCsp = nullptr;
}
return rv;
}
void nsFrameLoader::ResumeLoad(uint64_t aPendingSwitchID) {
Document* doc = mOwnerContent->OwnerDoc();
if (doc->IsStaticDocument()) {
// Static doc shouldn't load sub-documents.
return;
}
if (NS_WARN_IF(mDestroyCalled || !mOwnerContent)) {
FireErrorEvent();
return;
}
mLoadingOriginalSrc = false;
mURIToLoad = nullptr;
mPendingSwitchID = aPendingSwitchID;
mTriggeringPrincipal = mOwnerContent->NodePrincipal();
mCsp = mOwnerContent->GetCsp();
nsresult rv = doc->InitializeFrameLoader(this);
if (NS_FAILED(rv)) {
mPendingSwitchID = 0;
mTriggeringPrincipal = nullptr;
mCsp = nullptr;
FireErrorEvent();
}
}
nsresult nsFrameLoader::ReallyStartLoading() {
nsresult rv = ReallyStartLoadingInternal();
if (NS_FAILED(rv)) {
FireErrorEvent();
}
return rv;
}
nsresult nsFrameLoader::ReallyStartLoadingInternal() {
NS_ENSURE_STATE((mURIToLoad || mPendingSwitchID) && mOwnerContent &&
mOwnerContent->IsInComposedDoc());
AUTO_PROFILER_LABEL("nsFrameLoader::ReallyStartLoadingInternal", OTHER);
RefPtr<nsDocShellLoadState> loadState;
if (!mPendingSwitchID) {
loadState = new nsDocShellLoadState(mURIToLoad);
loadState->SetOriginalFrameSrc(mLoadingOriginalSrc);
// The triggering principal could be null if the frame is loaded other
// than the src attribute, for example, the frame is sandboxed. In that
// case we use the principal of the owner content, which is needed to
// prevent XSS attaches on documents loaded in subframes.
if (mTriggeringPrincipal) {
loadState->SetTriggeringPrincipal(mTriggeringPrincipal);
} else {
loadState->SetTriggeringPrincipal(mOwnerContent->NodePrincipal());
}
// If we have an explicit CSP, we set it. If not, we only query it from
// the document in case there was no explicit triggeringPrincipal.
// Otherwise it's possible that the original triggeringPrincipal did not
// have a CSP which causes the CSP on the Principal and explicit CSP
// to be out of sync.
if (mCsp) {
loadState->SetCsp(mCsp);
} else if (!mTriggeringPrincipal) {
nsCOMPtr<nsIContentSecurityPolicy> csp = mOwnerContent->GetCsp();
loadState->SetCsp(csp);
}
nsAutoString srcdoc;
bool isSrcdoc =
mOwnerContent->IsHTMLElement(nsGkAtoms::iframe) &&
mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::srcdoc, srcdoc);
if (isSrcdoc) {
loadState->SetSrcdocData(srcdoc);
loadState->SetBaseURI(mOwnerContent->GetBaseURI());
}
auto referrerInfo = MakeRefPtr<ReferrerInfo>(*mOwnerContent);
loadState->SetReferrerInfo(referrerInfo);
loadState->SetIsFromProcessingFrameAttributes();
// Default flags:
int32_t flags = nsIWebNavigation::LOAD_FLAGS_NONE;
// Flags for browser frame:
if (OwnerIsMozBrowserFrame()) {
flags = nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL;
}
loadState->SetLoadFlags(flags);
loadState->SetFirstParty(false);
// If we're loading the default about:blank document in a <browser> element,
// prevent the load from causing a process switch by explicitly overriding
// remote type selection.
if (mPendingBrowsingContext->IsTopContent() &&
mOwnerContent->IsXULElement(nsGkAtoms::browser) &&
NS_IsAboutBlank(mURIToLoad) &&
loadState->TriggeringPrincipal()->IsSystemPrincipal()) {
loadState->SetRemoteTypeOverride(mRemoteType);
}
}
if (IsRemoteFrame()) {
if (!EnsureRemoteBrowser()) {
NS_WARNING("Couldn't create child process for iframe.");
return NS_ERROR_FAILURE;
}
if (mPendingSwitchID) {
mRemoteBrowser->ResumeLoad(mPendingSwitchID);
mPendingSwitchID = 0;
} else {
mRemoteBrowser->LoadURL(loadState);
}
if (!mRemoteBrowserShown) {
// This can fail if it's too early to show the frame, we will retry later.
Unused << ShowRemoteFrame(ScreenIntSize(0, 0));
}
return NS_OK;
}
nsresult rv = MaybeCreateDocShell();
if (NS_FAILED(rv)) {
return rv;
}
MOZ_ASSERT(GetDocShell(),
"MaybeCreateDocShell succeeded with a null docShell");
// If we have a pending switch, just resume our load.
if (mPendingSwitchID) {
bool tmpState = mNeedsAsyncDestroy;
mNeedsAsyncDestroy = true;
rv = GetDocShell()->ResumeRedirectedLoad(mPendingSwitchID, -1);
mNeedsAsyncDestroy = tmpState;
mPendingSwitchID = 0;
return rv;
}
// Just to be safe, recheck uri.
rv = CheckURILoad(mURIToLoad, mTriggeringPrincipal);
NS_ENSURE_SUCCESS(rv, rv);
mLoadingOriginalSrc = false;
// Kick off the load...
bool tmpState = mNeedsAsyncDestroy;
mNeedsAsyncDestroy = true;
rv = GetDocShell()->LoadURI(loadState, false);
mNeedsAsyncDestroy = tmpState;
mURIToLoad = nullptr;
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
nsresult nsFrameLoader::CheckURILoad(nsIURI* aURI,
nsIPrincipal* aTriggeringPrincipal) {
// Check for security. The fun part is trying to figure out what principals
// to use. The way I figure it, if we're doing a LoadFrame() accidentally
// (eg someone created a frame/iframe node, we're being parsed, XUL iframes
// are being reframed, etc.) then we definitely want to use the node
// principal of mOwnerContent for security checks. If, on the other hand,
// someone's setting the src on our owner content, or created it via script,
// or whatever, then they can clearly access it... and we should still use
// the principal of mOwnerContent. I don't think that leads to privilege
// escalation, and it's reasonably guaranteed to not lead to XSS issues
// (since caller can already access mOwnerContent in this case). So just use
// the principal of mOwnerContent no matter what. If script wants to run
// things with its own permissions, which differ from those of mOwnerContent
// (which means the script is privileged in some way) it should set
// window.location instead.
nsIScriptSecurityManager* secMan = nsContentUtils::GetSecurityManager();
// Get our principal
nsIPrincipal* principal =
(aTriggeringPrincipal ? aTriggeringPrincipal
: mOwnerContent->NodePrincipal());
// Check if we are allowed to load absURL
nsresult rv = secMan->CheckLoadURIWithPrincipal(
principal, aURI, nsIScriptSecurityManager::STANDARD,
mOwnerContent->OwnerDoc()->InnerWindowID());
if (NS_FAILED(rv)) {
return rv; // We're not
}
// Bail out if this is an infinite recursion scenario
if (IsRemoteFrame()) {
return NS_OK;
}
return CheckForRecursiveLoad(aURI);
}
nsDocShell* nsFrameLoader::GetDocShell(ErrorResult& aRv) {
if (IsRemoteFrame()) {
return nullptr;
}
// If we have an owner, make sure we have a docshell and return
// that. If not, we're most likely in the middle of being torn down,
// then we just return null.
if (mOwnerContent) {
nsresult rv = MaybeCreateDocShell();
if (NS_FAILED(rv)) {
aRv.Throw(rv);
return nullptr;
}
MOZ_ASSERT(GetDocShell(),
"MaybeCreateDocShell succeeded, but null docShell");
}
return GetDocShell();
}
static void SetTreeOwnerAndChromeEventHandlerOnDocshellTree(
nsIDocShellTreeItem* aItem, nsIDocShellTreeOwner* aOwner,
EventTarget* aHandler) {
MOZ_ASSERT(aItem, "Must have item");
aItem->SetTreeOwner(aOwner);
int32_t childCount = 0;
aItem->GetInProcessChildCount(&childCount);
for (int32_t i = 0; i < childCount; ++i) {
nsCOMPtr<nsIDocShellTreeItem> item;
aItem->GetInProcessChildAt(i, getter_AddRefs(item));
if (aHandler) {
nsCOMPtr<nsIDocShell> shell(do_QueryInterface(item));
shell->SetChromeEventHandler(aHandler);
}
SetTreeOwnerAndChromeEventHandlerOnDocshellTree(item, aOwner, aHandler);
}
}
#if defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
static bool CheckDocShellType(mozilla::dom::Element* aOwnerContent,
nsIDocShellTreeItem* aDocShell, nsAtom* aAtom) {
bool isContent = aOwnerContent->AttrValueIs(kNameSpaceID_None, aAtom,
nsGkAtoms::content, eIgnoreCase);
if (!isContent) {
nsCOMPtr<nsIMozBrowserFrame> mozbrowser =
aOwnerContent->GetAsMozBrowserFrame();
if (mozbrowser) {
mozbrowser->GetMozbrowser(&isContent);
}
}
if (isContent) {
return aDocShell->ItemType() == nsIDocShellTreeItem::typeContent;
}
nsCOMPtr<nsIDocShellTreeItem> parent;
aDocShell->GetInProcessParent(getter_AddRefs(parent));
return parent && parent->ItemType() == aDocShell->ItemType();
}
#endif // defined(MOZ_DIAGNOSTIC_ASSERT_ENABLED)
/**
* Hook up a given TreeItem to its tree owner. aItem's type must have already
* been set, and it should already be part of the DocShellTree.
* @param aItem the treeitem we're working with
* @param aTreeOwner the relevant treeowner; might be null
*/
void nsFrameLoader::AddTreeItemToTreeOwner(nsIDocShellTreeItem* aItem,
nsIDocShellTreeOwner* aOwner) {
MOZ_ASSERT(aItem, "Must have docshell treeitem");
MOZ_ASSERT(mOwnerContent, "Must have owning content");
MOZ_DIAGNOSTIC_ASSERT(
CheckDocShellType(mOwnerContent, aItem, TypeAttrName(mOwnerContent)),
"Correct ItemType should be set when creating BrowsingContext");
if (mIsTopLevelContent) {
bool is_primary = mOwnerContent->AttrValueIs(
kNameSpaceID_None, nsGkAtoms::primary, nsGkAtoms::_true, eIgnoreCase);
if (aOwner) {
mOwnerContent->AddMutationObserver(this);
mObservingOwnerContent = true;
aOwner->ContentShellAdded(aItem, is_primary);
}
}
}
static bool AllDescendantsOfType(BrowsingContext* aParent,
BrowsingContext::Type aType) {
for (auto& child : aParent->Children()) {
if (child->GetType() != aType || !AllDescendantsOfType(child, aType)) {
return false;
}
}
return true;
}
void nsFrameLoader::MaybeShowFrame() {
nsIFrame* frame = GetPrimaryFrameOfOwningContent();
if (frame) {
nsSubDocumentFrame* subDocFrame = do_QueryFrame(frame);
if (subDocFrame) {
subDocFrame->MaybeShowViewer();
}
}
}
static ScrollbarPreference GetScrollbarPreference(const Element* aOwner) {
if (!aOwner) {
return ScrollbarPreference::Auto;
}
const nsAttrValue* attrValue = aOwner->GetParsedAttr(nsGkAtoms::scrolling);
return nsGenericHTMLFrameElement::MapScrollingAttribute(attrValue);
}
static CSSIntSize GetMarginAttributes(const Element* aOwner) {
CSSIntSize result(-1, -1);
auto* content = nsGenericHTMLElement::FromNodeOrNull(aOwner);
if (!content) {
return result;
}
const nsAttrValue* attr = content->GetParsedAttr(nsGkAtoms::marginwidth);
if (attr && attr->Type() == nsAttrValue::eInteger) {
result.width = attr->GetIntegerValue();
}
attr = content->GetParsedAttr(nsGkAtoms::marginheight);
if (attr && attr->Type() == nsAttrValue::eInteger) {
result.height = attr->GetIntegerValue();
}
return result;
}
bool nsFrameLoader::Show(nsSubDocumentFrame* frame) {
if (mInShow) {
return false;
}
mInShow = true;
auto resetInShow = mozilla::MakeScopeExit([&] { mInShow = false; });
ScreenIntSize size = frame->GetSubdocumentSize();
if (IsRemoteFrame()) {
// FIXME(bug 1588791): For fission iframes we need to pass down the
// scrollbar preferences.
return ShowRemoteFrame(size, frame);
}
nsresult rv = MaybeCreateDocShell();
if (NS_FAILED(rv)) {
return false;
}
nsDocShell* ds = GetDocShell();
MOZ_ASSERT(ds, "MaybeCreateDocShell succeeded, but null docShell");
if (!ds) {
return false;
}
ds->SetScrollbarPreference(GetScrollbarPreference(mOwnerContent));
const bool marginsChanged =
ds->UpdateFrameMargins(GetMarginAttributes(mOwnerContent));
if (PresShell* presShell = ds->GetPresShell()) {
// Ensure root scroll frame is reflowed in case margins have changed
if (marginsChanged) {
if (nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame()) {
presShell->FrameNeedsReflow(rootScrollFrame, IntrinsicDirty::None,
NS_FRAME_IS_DIRTY);
}
}
return true;
}
nsView* view = frame->EnsureInnerView();
if (!view) return false;
RefPtr<nsDocShell> baseWindow = GetDocShell();
baseWindow->InitWindow(nullptr, view->GetWidget(), 0, 0, size.width,
size.height);
baseWindow->SetVisibility(true);
NS_ENSURE_TRUE(GetDocShell(), false);
// Trigger editor re-initialization if midas is turned on in the
// sub-document. This shouldn't be necessary, but given the way our
// editor works, it is. See
if (RefPtr<PresShell> presShell = GetDocShell()->GetPresShell()) {
Document* doc = presShell->GetDocument();
nsHTMLDocument* htmlDoc =
doc && doc->IsHTMLOrXHTML() ? doc->AsHTMLDocument() : nullptr;
if (htmlDoc) {
nsAutoString designMode;
htmlDoc->GetDesignMode(designMode);
if (designMode.EqualsLiteral("on")) {
// Hold on to the editor object to let the document reattach to the
// same editor object, instead of creating a new one.
RefPtr<HTMLEditor> htmlEditor = GetDocShell()->GetHTMLEditor();
Unused << htmlEditor;
htmlDoc->SetDesignMode(u"off"_ns, Nothing(), IgnoreErrors());
htmlDoc->SetDesignMode(u"on"_ns, Nothing(), IgnoreErrors());
} else {
// Re-initialize the presentation for contenteditable documents
bool editable = false, hasEditingSession = false;
GetDocShell()->GetEditable(&editable);
GetDocShell()->GetHasEditingSession(&hasEditingSession);
RefPtr<HTMLEditor> htmlEditor = GetDocShell()->GetHTMLEditor();
if (editable && hasEditingSession && htmlEditor) {
htmlEditor->PostCreate();
}
}
}
}
mInShow = false;
if (mHideCalled) {
mHideCalled = false;
Hide();
return false;
}
return true;
}
void nsFrameLoader::MarginsChanged() {
// We assume that the margins are always zero for remote frames.
if (IsRemoteFrame()) {
return;
}
nsDocShell* docShell = GetDocShell();
// If there's no docshell, we're probably not up and running yet.
// nsFrameLoader::Show() will take care of setting the right
// margins.
if (!docShell) {
return;
}
if (!docShell->UpdateFrameMargins(GetMarginAttributes(mOwnerContent))) {
return;
}
// There's a cached property declaration block
// that needs to be updated
if (Document* doc = docShell->GetDocument()) {
for (nsINode* cur = doc; cur; cur = cur->GetNextNode()) {
if (cur->IsHTMLElement(nsGkAtoms::body)) {
static_cast<HTMLBodyElement*>(cur)->ClearMappedServoStyle();
}
}
}
// Trigger a restyle if there's a prescontext
// FIXME: This could do something much less expensive.
if (nsPresContext* presContext = docShell->GetPresContext()) {
// rebuild, because now the same nsMappedAttributes* will produce
// a different style
presContext->RebuildAllStyleData(nsChangeHint(0),
RestyleHint::RestyleSubtree());
}
}
bool nsFrameLoader::ShowRemoteFrame(const ScreenIntSize& size,
nsSubDocumentFrame* aFrame) {
AUTO_PROFILER_LABEL("nsFrameLoader::ShowRemoteFrame", OTHER);
NS_ASSERTION(IsRemoteFrame(),
"ShowRemote only makes sense on remote frames.");
if (!EnsureRemoteBrowser()) {
NS_ERROR("Couldn't create child process.");
return false;
}
// FIXME/bug 589337: Show()/Hide() is pretty expensive for
// cross-process layers; need to figure out what behavior we really
// want here. For now, hack.
if (!mRemoteBrowserShown) {
if (!mOwnerContent || !mOwnerContent->GetComposedDoc()) {
return false;
}
// We never want to host remote frameloaders in simple popups, like menus.
nsIWidget* widget = nsContentUtils::WidgetForContent(mOwnerContent);
if (!widget || static_cast<nsBaseWidget*>(widget)->IsSmallPopup()) {
return false;
}
if (BrowserHost* bh = mRemoteBrowser->AsBrowserHost()) {
RefPtr<BrowsingContext> bc = bh->GetBrowsingContext()->Top();
// Set to the current activation of the window.
bc->SetIsActiveBrowserWindow(bc->GetIsActiveBrowserWindow());
}
nsCOMPtr<nsISupports> container = mOwnerContent->OwnerDoc()->GetContainer();
nsCOMPtr<nsIBaseWindow> baseWindow = do_QueryInterface(container);
nsCOMPtr<nsIWidget> mainWidget;
baseWindow->GetMainWidget(getter_AddRefs(mainWidget));
nsSizeMode sizeMode =
mainWidget ? mainWidget->SizeMode() : nsSizeMode_Normal;
OwnerShowInfo info(size, GetScrollbarPreference(mOwnerContent), sizeMode);
if (!mRemoteBrowser->Show(info)) {
return false;
}
mRemoteBrowserShown = true;
// This notification doesn't apply to fission, apparently.
if (!GetBrowserBridgeChild()) {
if (nsCOMPtr<nsIObserverService> os = services::GetObserverService()) {
os->NotifyObservers(ToSupports(this), "remote-browser-shown", nullptr);
}
ProcessPriorityManager::RemoteBrowserFrameShown(this);
}
} else {
nsIntRect dimensions;
NS_ENSURE_SUCCESS(GetWindowDimensions(dimensions), false);
// Don't show remote iframe if we are waiting for the completion of reflow.
if (!aFrame || !aFrame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
mRemoteBrowser->UpdateDimensions(dimensions, size);
}
}
return true;
}
void nsFrameLoader::Hide() {
if (mHideCalled) {
return;
}
if (mInShow) {
mHideCalled = true;
return;
}
if (!GetDocShell()) {
return;
}
nsCOMPtr<nsIContentViewer> contentViewer;
GetDocShell()->GetContentViewer(getter_AddRefs(contentViewer));
if (contentViewer) contentViewer->SetSticky(false);
RefPtr<nsDocShell> baseWin = GetDocShell();
baseWin->SetVisibility(false);
baseWin->SetParentWidget(nullptr);
}
void nsFrameLoader::ForceLayoutIfNecessary() {
nsIFrame* frame = GetPrimaryFrameOfOwningContent();
if (!frame) {
return;
}
nsPresContext* presContext = frame->PresContext();
if (!presContext) {
return;
}
// Only force the layout flush if the frameloader hasn't ever been
// run through layout.
if (frame->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
if (RefPtr<PresShell> presShell = presContext->GetPresShell()) {
presShell->FlushPendingNotifications(FlushType::Layout);
}
}
}
nsresult nsFrameLoader::SwapWithOtherRemoteLoader(
nsFrameLoader* aOther, nsFrameLoaderOwner* aThisOwner,
nsFrameLoaderOwner* aOtherOwner) {
MOZ_ASSERT(NS_IsMainThread());
#ifdef DEBUG
RefPtr<nsFrameLoader> first = aThisOwner->GetFrameLoader();
RefPtr<nsFrameLoader> second = aOtherOwner->GetFrameLoader();
MOZ_ASSERT(first == this, "aThisOwner must own this");
MOZ_ASSERT(second == aOther, "aOtherOwner must own aOther");
#endif
Element* ourContent = mOwnerContent;
Element* otherContent = aOther->mOwnerContent;
if (!ourContent || !otherContent) {
// Can't handle this
return NS_ERROR_NOT_IMPLEMENTED;
}
// Make sure there are no same-origin issues
bool equal;
nsresult rv = ourContent->NodePrincipal()->Equals(
otherContent->NodePrincipal(), &equal);
if (NS_FAILED(rv) || !equal) {
// Security problems loom. Just bail on it all
return NS_ERROR_DOM_SECURITY_ERR;
}
Document* ourDoc = ourContent->GetComposedDoc();
Document* otherDoc = otherContent->GetComposedDoc();
if (!ourDoc || !otherDoc) {
// Again, how odd, given that we had docshells
return NS_ERROR_NOT_IMPLEMENTED;
}
PresShell* ourPresShell = ourDoc->GetPresShell();
PresShell* otherPresShell = otherDoc->GetPresShell();
if (!ourPresShell || !otherPresShell) {
return NS_ERROR_NOT_IMPLEMENTED;
}
auto* browserParent = GetBrowserParent();
auto* otherBrowserParent = aOther->GetBrowserParent();
if (!browserParent || !otherBrowserParent) {
return NS_ERROR_NOT_IMPLEMENTED;
}
RefPtr<BrowsingContext> ourBc = browserParent->GetBrowsingContext();
RefPtr<BrowsingContext> otherBc = otherBrowserParent->GetBrowsingContext();
// When we swap docShells, maybe we have to deal with a new page created just
// for this operation. In this case, the browser code should already have set
// the correct userContextId attribute value in the owning element, but our
// docShell, that has been created way before) doesn't know that that
// happened.
// This is the reason why now we must retrieve the correct value from the
// usercontextid attribute before comparing our originAttributes with the
// other one.
OriginAttributes ourOriginAttributes = ourBc->OriginAttributesRef();
rv = PopulateOriginContextIdsFromAttributes(ourOriginAttributes);
NS_ENSURE_SUCCESS(rv, rv);
OriginAttributes otherOriginAttributes = otherBc->OriginAttributesRef();
rv = aOther->PopulateOriginContextIdsFromAttributes(otherOriginAttributes);
NS_ENSURE_SUCCESS(rv, rv);
if (!ourOriginAttributes.EqualsIgnoringFPD(otherOriginAttributes)) {
return NS_ERROR_NOT_IMPLEMENTED;
}
bool ourHasHistory =
mIsTopLevelContent && ourContent->IsXULElement(nsGkAtoms::browser) &&
!ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disablehistory);
bool otherHasHistory =
aOther->mIsTopLevelContent &&
otherContent->IsXULElement(nsGkAtoms::browser) &&
!otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::disablehistory);
if (ourHasHistory != otherHasHistory) {
return NS_ERROR_NOT_IMPLEMENTED;
}
if (mInSwap || aOther->mInSwap) {
return NS_ERROR_NOT_IMPLEMENTED;
}
mInSwap = aOther->mInSwap = true;
// NOTE(emilio): This doesn't have to flush because the caller does already.
nsIFrame* ourFrame = ourContent->GetPrimaryFrame();
nsIFrame* otherFrame = otherContent->GetPrimaryFrame();
if (!ourFrame || !otherFrame) {
mInSwap = aOther->mInSwap = false;
return NS_ERROR_NOT_IMPLEMENTED;
}
nsSubDocumentFrame* ourFrameFrame = do_QueryFrame(ourFrame);
if (!ourFrameFrame) {
mInSwap = aOther->mInSwap = false;
return NS_ERROR_NOT_IMPLEMENTED;
}
rv = ourFrameFrame->BeginSwapDocShells(otherFrame);
if (NS_FAILED(rv)) {
mInSwap = aOther->mInSwap = false;
return rv;
}
nsCOMPtr<nsIBrowserDOMWindow> otherBrowserDOMWindow =
otherBrowserParent->GetBrowserDOMWindow();
nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow =
browserParent->GetBrowserDOMWindow();
if (!!otherBrowserDOMWindow != !!browserDOMWindow) {
return NS_ERROR_NOT_IMPLEMENTED;
}
// Destroy browser frame scripts for content leaving a frame with browser API
if (OwnerIsMozBrowserFrame() && !aOther->OwnerIsMozBrowserFrame()) {
DestroyBrowserFrameScripts();
}
if (!OwnerIsMozBrowserFrame() && aOther->OwnerIsMozBrowserFrame()) {
aOther->DestroyBrowserFrameScripts();
}
otherBrowserParent->SetBrowserDOMWindow(browserDOMWindow);
browserParent->SetBrowserDOMWindow(otherBrowserDOMWindow);
MaybeUpdatePrimaryBrowserParent(eBrowserParentRemoved);
aOther->MaybeUpdatePrimaryBrowserParent(eBrowserParentRemoved);
if (mozilla::BFCacheInParent() && XRE_IsParentProcess()) {
// nsFrameLoaders in session history can't be moved to another owner since
// there are no corresponging message managers on which swap can be done.
// See the line mMessageManager.swap(aOther->mMessageManager); below.
auto evict = [](nsFrameLoader* aFrameLoader) {
if (BrowsingContext* bc =
aFrameLoader->GetMaybePendingBrowsingContext()) {
nsCOMPtr<nsISHistory> shistory = bc->Canonical()->GetSessionHistory();
if (shistory) {
shistory->EvictAllContentViewers();
}
}
};
evict(this);
evict(aOther);
}
SetOwnerContent(otherContent);
aOther->SetOwnerContent(ourContent);
browserParent->SetOwnerElement(otherContent);
otherBrowserParent->SetOwnerElement(ourContent);
// Update window activation state for the swapped owner content.
bool ourActive = otherBc->GetIsActiveBrowserWindow();
bool otherActive = ourBc->GetIsActiveBrowserWindow();
if (ourBc->IsTop()) {
ourBc->SetIsActiveBrowserWindow(otherActive);
}
if (otherBc->IsTop()) {
otherBc->SetIsActiveBrowserWindow(ourActive);
}
MaybeUpdatePrimaryBrowserParent(eBrowserParentChanged);
aOther->MaybeUpdatePrimaryBrowserParent(eBrowserParentChanged);
RefPtr<nsFrameMessageManager> ourMessageManager = mMessageManager;
RefPtr<nsFrameMessageManager> otherMessageManager = aOther->mMessageManager;
// Swap and setup things in parent message managers.
if (ourMessageManager) {
ourMessageManager->SetCallback(aOther);
}
if (otherMessageManager) {
otherMessageManager->SetCallback(this);
}
mMessageManager.swap(aOther->mMessageManager);
// XXXsmaug what should be done to JSWindowActorParent objects when swapping
// frameloaders? Currently they leak very easily, bug 1697918.
// Perform the actual swap of the internal refptrs. We keep a strong reference
// to ourselves to make sure we don't die while we overwrite our reference to
// ourself.
RefPtr<nsFrameLoader> kungFuDeathGrip(this);
aThisOwner->SetFrameLoader(aOther);
aOtherOwner->SetFrameLoader(kungFuDeathGrip);
ourFrameFrame->EndSwapDocShells(otherFrame);
ourPresShell->BackingScaleFactorChanged();
otherPresShell->BackingScaleFactorChanged();
// Initialize browser API if needed now that owner content has changed.
InitializeBrowserAPI();
aOther->InitializeBrowserAPI();
mInSwap = aOther->mInSwap = false;
// Send an updated tab context since owner content type may have changed.
MutableTabContext ourContext;
rv = GetNewTabContext(&ourContext);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
MutableTabContext otherContext;
rv = aOther->GetNewTabContext(&otherContext);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
Unused << browserParent->SendSwappedWithOtherRemoteLoader(
ourContext.AsIPCTabContext());
Unused << otherBrowserParent->SendSwappedWithOtherRemoteLoader(
otherContext.AsIPCTabContext());
return NS_OK;
}
class MOZ_RAII AutoResetInFrameSwap final {
public:
AutoResetInFrameSwap(nsFrameLoader* aThisFrameLoader,
nsFrameLoader* aOtherFrameLoader,
nsDocShell* aThisDocShell, nsDocShell* aOtherDocShell,
EventTarget* aThisEventTarget,
EventTarget* aOtherEventTarget)
: mThisFrameLoader(aThisFrameLoader),
mOtherFrameLoader(aOtherFrameLoader),
mThisDocShell(aThisDocShell),
mOtherDocShell(aOtherDocShell),
mThisEventTarget(aThisEventTarget),
mOtherEventTarget(aOtherEventTarget) {
mThisFrameLoader->mInSwap = true;
mOtherFrameLoader->mInSwap = true;
mThisDocShell->SetInFrameSwap(true);
mOtherDocShell->SetInFrameSwap(true);
// Fire pageshow events on still-loading pages, and then fire pagehide
// events. Note that we do NOT fire these in the normal way, but just fire
// them on the chrome event handlers.
nsContentUtils::FirePageShowEventForFrameLoaderSwap(
mThisDocShell, mThisEventTarget, false);
nsContentUtils::FirePageShowEventForFrameLoaderSwap(
mOtherDocShell, mOtherEventTarget, false);
nsContentUtils::FirePageHideEventForFrameLoaderSwap(mThisDocShell,
mThisEventTarget);
nsContentUtils::FirePageHideEventForFrameLoaderSwap(mOtherDocShell,
mOtherEventTarget);
}
~AutoResetInFrameSwap() {
nsContentUtils::FirePageShowEventForFrameLoaderSwap(mThisDocShell,
mThisEventTarget, true);
nsContentUtils::FirePageShowEventForFrameLoaderSwap(
mOtherDocShell, mOtherEventTarget, true);
mThisFrameLoader->mInSwap = false;
mOtherFrameLoader->mInSwap = false;
mThisDocShell->SetInFrameSwap(false);
mOtherDocShell->SetInFrameSwap(false);
// This is needed to get visibility state right in cases when we swapped a
// visible tab (foreground in visible window) with a non-visible tab.
if (RefPtr<Document> doc = mThisDocShell->GetDocument()) {
doc->UpdateVisibilityState();
}
if (RefPtr<Document> doc = mOtherDocShell->GetDocument()) {
doc->UpdateVisibilityState();
}
}
private:
RefPtr<nsFrameLoader> mThisFrameLoader;
RefPtr<nsFrameLoader> mOtherFrameLoader;
RefPtr<nsDocShell> mThisDocShell;
RefPtr<nsDocShell> mOtherDocShell;
nsCOMPtr<EventTarget> mThisEventTarget;
nsCOMPtr<EventTarget> mOtherEventTarget;
};
nsresult nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther,
nsFrameLoaderOwner* aThisOwner,
nsFrameLoaderOwner* aOtherOwner) {
#ifdef DEBUG
RefPtr<nsFrameLoader> first = aThisOwner->GetFrameLoader();
RefPtr<nsFrameLoader> second = aOtherOwner->GetFrameLoader();
MOZ_ASSERT(first == this, "aThisOwner must own this");
MOZ_ASSERT(second == aOther, "aOtherOwner must own aOther");
#endif
NS_ENSURE_STATE(!mInShow && !aOther->mInShow);
if (IsRemoteFrame() != aOther->IsRemoteFrame()) {
NS_WARNING(
"Swapping remote and non-remote frames is not currently supported");
return NS_ERROR_NOT_IMPLEMENTED;
}
RefPtr<Element> ourContent = mOwnerContent;
RefPtr<Element> otherContent = aOther->mOwnerContent;
if (!ourContent || !otherContent) {
// Can't handle this
return NS_ERROR_NOT_IMPLEMENTED;
}
nsIFrame* ourFrame = ourContent->GetPrimaryFrame(FlushType::Frames);
nsIFrame* otherFrame = otherContent->GetPrimaryFrame(FlushType::Frames);
if (!ourFrame || !otherFrame) {
return NS_ERROR_NOT_IMPLEMENTED;
}
// Ensure the flushes above haven't changed all the world.
if (ourContent != mOwnerContent || otherContent != aOther->mOwnerContent) {
return NS_ERROR_NOT_IMPLEMENTED;
}
bool ourHasSrcdoc = ourContent->IsHTMLElement(nsGkAtoms::iframe) &&
ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc);
bool otherHasSrcdoc =
otherContent->IsHTMLElement(nsGkAtoms::iframe) &&
otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc);
if (ourHasSrcdoc || otherHasSrcdoc) {
// Ignore this case entirely for now, since we support XUL <-> HTML swapping
return NS_ERROR_NOT_IMPLEMENTED;
}
bool ourFullscreenAllowed = ourContent->IsXULElement() ||
(OwnerIsMozBrowserFrame() &&
ourContent->HasAttr(nsGkAtoms::allowfullscreen));
bool otherFullscreenAllowed =
otherContent->IsXULElement() ||
(aOther->OwnerIsMozBrowserFrame() &&
otherContent->HasAttr(nsGkAtoms::allowfullscreen));
if (ourFullscreenAllowed != otherFullscreenAllowed) {
return NS_ERROR_NOT_IMPLEMENTED;
}
nsILoadContext* ourLoadContext = ourContent->OwnerDoc()->GetLoadContext();
nsILoadContext* otherLoadContext = otherContent->OwnerDoc()->GetLoadContext();
MOZ_ASSERT(ourLoadContext && otherLoadContext,
"Swapping frames within dead documents?");
if (ourLoadContext->UseRemoteTabs() != otherLoadContext->UseRemoteTabs()) {
NS_WARNING("Can't swap between e10s and non-e10s windows");
return NS_ERROR_NOT_IMPLEMENTED;
}
if (ourLoadContext->UseRemoteSubframes() !=
otherLoadContext->UseRemoteSubframes()) {
NS_WARNING("Can't swap between fission and non-fission windows");
return NS_ERROR_NOT_IMPLEMENTED;
}
// Divert to a separate path for the remaining steps in the remote case
if (IsRemoteFrame()) {
MOZ_ASSERT(aOther->IsRemoteFrame());
return SwapWithOtherRemoteLoader(aOther, aThisOwner, aOtherOwner);
}
// Make sure there are no same-origin issues
bool equal;
nsresult rv = ourContent->NodePrincipal()->Equals(
otherContent->NodePrincipal(), &equal);
if (NS_FAILED(rv) || !equal) {
// Security problems loom. Just bail on it all
return NS_ERROR_DOM_SECURITY_ERR;
}