/* -*- 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).
*/
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
staticvoid GetFrameName(Element* aOwnerContent, nsAString& aFrameName) {
int32_t namespaceID = aOwnerContent->GetNameSpaceID(); if (namespaceID == kNameSpaceID_XHTML && !aOwnerContent->IsInHTMLDocument()) {
aOwnerContent->GetAttr(nsGkAtoms::id, aFrameName);
} else {
aOwnerContent->GetAttr(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(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. staticbool IsTopContent(BrowsingContext* aParent, Element* aOwner) { if (XRE_IsContentProcess()) { returnfalse;
}
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;
}
}
}
// 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)) {
BrowsingContext::CreateDetachedOptions options; if (aOpenWindowInfo) {
options.topLevelCreatedByWebContent =
aOpenWindowInfo->GetIsTopLevelCreatedByWebContent();
}
options.windowless = parentBC->Windowless();
// Create toplevel context without a parent & as Type::Content. return BrowsingContext::CreateDetached(
nullptr, opener, aSpecificGroup, frameName,
BrowsingContext::Type::Content, options);
}
MOZ_ASSERT(!aOpenWindowInfo, "Can't have openWindowInfo for non-toplevel context");
staticbool InitialLoadIsRemote(Element* aOwner) { // 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()) { returnfalse;
}
// Otherwise, we're remote if we have "remote=true" and we're a XUL element. return (aOwner->GetNameSpaceID() == kNameSpaceID_XUL) &&
aOwner->AttrValueIs(kNameSpaceID_None, nsGkAtoms::remote,
nsGkAtoms::_true, eCaseMatters);
}
// 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;
}
// 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);
// 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(nsGkAtoms::RemoteType, remoteType) &&
!remoteType.IsEmpty()) {
CopyUTF16toUTF8(remoteType, fl->mRemoteType);
} else {
fl->mRemoteType = DEFAULT_REMOTE_TYPE;
}
} return fl.forget();
}
#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);
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;
}
// If we are being loaded by a lazy loaded iframe, use its base URI first // instead of the current base URI. auto* lazyBaseURI = GetLazyLoadFrameResumptionState().mBaseURI.get();
nsIURI* baseURI = lazyBaseURI ? lazyBaseURI : mOwnerContent->GetBaseURI();
// 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, baseURI);
}
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);
AUTO_PROFILER_LABEL("nsFrameLoader::ReallyStartLoadingInternal", OTHER);
RefPtr<nsDocShellLoadState> loadState; if (!mPendingSwitchID) {
loadState = new nsDocShellLoadState(mURIToLoad);
loadState->SetOriginalFrameSrc(mLoadingOriginalSrc);
loadState->SetShouldCheckForRecursion(mShouldCheckForRecursion);
// 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);
} elseif (!mTriggeringPrincipal) {
nsCOMPtr<nsIContentSecurityPolicy> csp = mOwnerContent->GetCsp();
loadState->SetCsp(csp);
}
Document* ownerDoc = mOwnerContent->OwnerDoc(); if (ownerDoc) {
loadState->SetTriggeringStorageAccess(ownerDoc->UsingStorageAccess());
loadState->SetTriggeringWindowId(ownerDoc->InnerWindowID());
}
// 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 (!mRemoteBrowserShown) { // This can fail if it's too early to show the frame, we will retry later.
Unused << ShowRemoteFrame( /* aFrame = */ do_QueryFrame(GetPrimaryFrameOfOwningContent()));
}
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);
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");
}
/** * 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");
nsView* view = aFrame->EnsureInnerView(); if (!view) { returnfalse;
}
// If we already have a pres shell (which can happen with <object> / <embed>) // then hook it up in the view tree. if (PresShell* presShell = ds->GetPresShell()) { // Ensure root scroll frame is reflowed in case margins have changed. if (marginsChanged) { if (nsIFrame* rootScrollContainerFrame =
presShell->GetRootScrollContainerFrame()) {
presShell->FrameNeedsReflow(rootScrollContainerFrame,
IntrinsicDirty::None, NS_FRAME_IS_DIRTY);
}
}
nsView* childView = presShell->GetViewManager()->GetRootView();
MOZ_DIAGNOSTIC_ASSERT(childView); if (childView->GetParent() == view) { // We were probably doing a docshell swap and succeeded before getting // here, hooray, nothing else to do. returntrue;
}
// We did layout before due to <object> or <embed> and now we need to fix // up our stuff and initialize our docshell below too.
MOZ_DIAGNOSTIC_ASSERT(!view->GetFirstChild());
MOZ_DIAGNOSTIC_ASSERT(!childView->GetParent(), "Stale view?");
nsSubDocumentFrame::InsertViewsInReverseOrder(childView, view);
nsSubDocumentFrame::EndSwapDocShellsForViews(view->GetFirstChild());
}
// 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 https://bugzilla.mozilla.org/show_bug.cgi?id=284245 // // FIXME(emilio): This is a massive hack, plus probably not the right place to // do it. Should probably be done in Document::CreatePresShell? 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());
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 (auto* body = HTMLBodyElement::FromNode(cur)) {
body->FrameMarginsChanged();
}
}
}
}
bool nsFrameLoader::ShowRemoteFrame(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."); returnfalse;
}
// 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()) { returnfalse;
}
// We never want to host remote frameloaders in simple popups, like menus.
nsIWidget* widget = nsContentUtils::WidgetForContent(mOwnerContent); if (!widget || static_cast<nsBaseWidget*>(widget)->IsSmallPopup()) { returnfalse;
}
if (BrowserHost* bh = mRemoteBrowser->AsBrowserHost()) {
RefPtr<BrowsingContext> bc = bh->GetBrowsingContext()->Top();
// Set to the current activation of the window.
bc->SetIsActiveBrowserWindow(bc->GetIsActiveBrowserWindow());
}
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);
}
}
}
#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
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;
}
// 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);
// 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;
}
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->EvictAllDocumentViewers();
}
}
};
evict(this);
evict(aOther);
}
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);
// 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()); // These might have moved to a new window, so make sure they have // the appropriate priority (bug 1896172)
browserParent->RecomputeProcessPriority();
otherBrowserParent->RecomputeProcessPriority(); return NS_OK;
}
// 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);
}
// 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();
}
}
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;
}
// 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(nsGkAtoms::srcdoc); bool otherHasSrcdoc = otherContent->IsHTMLElement(nsGkAtoms::iframe) &&
otherContent->HasAttr(nsGkAtoms::srcdoc); if (ourHasSrcdoc || otherHasSrcdoc) { // Ignore this case entirely for now, since we support XUL <-> HTML swapping 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;
}
RefPtr<nsDocShell> ourDocshell = GetExistingDocShell();
RefPtr<nsDocShell> otherDocshell = aOther->GetExistingDocShell(); if (!ourDocshell || !otherDocshell) { // How odd return NS_ERROR_NOT_IMPLEMENTED;
}
// To avoid having to mess with session history, avoid swapping // frameloaders that don't correspond to root same-type docshells, // unless both roots have session history disabled.
nsCOMPtr<nsIDocShellTreeItem> ourRootTreeItem, otherRootTreeItem;
ourDocshell->GetInProcessSameTypeRootTreeItem(
getter_AddRefs(ourRootTreeItem));
otherDocshell->GetInProcessSameTypeRootTreeItem(
getter_AddRefs(otherRootTreeItem));
nsCOMPtr<nsIWebNavigation> ourRootWebnav = do_QueryInterface(ourRootTreeItem);
nsCOMPtr<nsIWebNavigation> otherRootWebnav =
do_QueryInterface(otherRootTreeItem);
if (!ourRootWebnav || !otherRootWebnav) { return NS_ERROR_NOT_IMPLEMENTED;
}
// Also make sure that the two BrowsingContexts are the same type. Otherwise // swapping is certainly not safe. If this needs to be changed then // the code below needs to be audited as it assumes identical types. if (ourBc->GetType() != otherBc->GetType()) { return NS_ERROR_NOT_IMPLEMENTED;
}
// We ensure that BCs are either both top frames or both subframes. if (ourBc->IsTop() != otherBc->IsTop()) { return NS_ERROR_NOT_IMPLEMENTED;
}
// One more twist here. Setting up the right treeowners in a heterogeneous // tree is a bit of a pain. So make sure that if `ourBc->GetType()` is not // nsIDocShellTreeItem::typeContent then all of our descendants are the same // type as us. if (!ourBc->IsContent() &&
(!AllDescendantsOfType(ourBc, ourBc->GetType()) ||
!AllDescendantsOfType(otherBc, otherBc->GetType()))) { return NS_ERROR_NOT_IMPLEMENTED;
}
// Save off the tree owners, frame elements, chrome event handlers, and // docshell and document parents before doing anything else.
nsCOMPtr<nsIDocShellTreeOwner> ourOwner, otherOwner;
ourDocshell->GetTreeOwner(getter_AddRefs(ourOwner));
otherDocshell->GetTreeOwner(getter_AddRefs(otherOwner)); // Note: it's OK to have null treeowners.
// Make sure to swap docshells between the two frames.
Document* ourDoc = ourContent->GetComposedDoc();
Document* otherDoc = otherContent->GetComposedDoc(); if (!ourDoc || !otherDoc) { // Again, how odd, given that we had docshells return NS_ERROR_NOT_IMPLEMENTED;
}
// 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 = ourDocshell->GetOriginAttributes();
rv = PopulateOriginContextIdsFromAttributes(ourOriginAttributes);
NS_ENSURE_SUCCESS(rv, rv);
nsSubDocumentFrame* ourFrameFrame = do_QueryFrame(ourFrame); if (!ourFrameFrame) { return NS_ERROR_NOT_IMPLEMENTED;
}
// OK. First begin to swap the docshells in the two nsIFrames
rv = ourFrameFrame->BeginSwapDocShells(otherFrame); if (NS_FAILED(rv)) { return rv;
}
// Now move the docshells to the right docshell trees. Note that this // resets their treeowners to null.
ourParentItem->RemoveChild(ourDocshell);
otherParentItem->RemoveChild(otherDocshell); if (ourBc->IsContent()) {
ourOwner->ContentShellRemoved(ourDocshell);
otherOwner->ContentShellRemoved(otherDocshell);
}
// Restore the correct chrome event handlers.
ourDocshell->SetChromeEventHandler(otherChromeEventHandler);
otherDocshell->SetChromeEventHandler(ourChromeEventHandler); // Restore the correct treeowners // (and also chrome event handlers for content frames only).
SetTreeOwnerAndChromeEventHandlerOnDocshellTree(
ourDocshell, otherOwner,
ourBc->IsContent() ? otherChromeEventHandler.get() : nullptr);
SetTreeOwnerAndChromeEventHandlerOnDocshellTree(
otherDocshell, ourOwner,
ourBc->IsContent() ? ourChromeEventHandler.get() : nullptr);
// Switch the owner content before we start calling AddTreeItemToTreeOwner. // Note that we rely on this to deal with setting mObservingOwnerContent to // false and calling RemoveMutationObserver as needed.
SetOwnerContent(otherContent);
aOther->SetOwnerContent(ourContent);
// SetSubDocumentFor nulls out parent documents on the old child doc if a // new non-null document is passed in, so just go ahead and remove both // kids before reinserting in the parent subdoc maps, to avoid // complications.
ourParentDocument->SetSubDocumentFor(ourContent, nullptr);
otherParentDocument->SetSubDocumentFor(otherContent, nullptr);
ourParentDocument->SetSubDocumentFor(ourContent, otherChildDocument);
otherParentDocument->SetSubDocumentFor(otherContent, ourChildDocument);
RefPtr<nsFrameMessageManager> ourMessageManager = mMessageManager;
RefPtr<nsFrameMessageManager> otherMessageManager = aOther->mMessageManager; // Swap pointers in child message managers. if (mChildMessageManager) {
InProcessBrowserChildMessageManager* browserChild = mChildMessageManager;
browserChild->SetOwner(otherContent);
browserChild->SetChromeMessageManager(otherMessageManager);
} if (aOther->mChildMessageManager) {
InProcessBrowserChildMessageManager* otherBrowserChild =
aOther->mChildMessageManager;
otherBrowserChild->SetOwner(ourContent);
otherBrowserChild->SetChromeMessageManager(ourMessageManager);
} // Swap and setup things in parent message managers. if (mMessageManager) {
mMessageManager->SetCallback(aOther);
} if (aOther->mMessageManager) {
aOther->mMessageManager->SetCallback(this);
}
mMessageManager.swap(aOther->mMessageManager);
// 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);
// Drop any cached content viewers in the two session histories. if (ourHistory) {
--> --------------------
--> maximum size reached
--> --------------------
¤ Dauer der Verarbeitung: 0.44 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.