/* -*- 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 "base/basictypes.h" #include "prenv.h" #include "nsDocShell.h" #include "nsIDOMMozBrowserFrame.h" #include "nsIDOMWindow.h" #include "nsIPresShell.h" #include "nsIContentInlines.h" #include "nsIContentViewer.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 "nsIXPConnect.h" #include "nsUnicharUtils.h" #include "nsIScriptGlobalObject.h" #include "nsIScriptSecurityManager.h" #include "nsIScrollable.h" #include "nsFrameLoader.h" #include "nsFrameLoaderOwner.h" #include "nsIFrame.h" #include "nsIScrollableFrame.h" #include "nsSubDocumentFrame.h" #include "nsError.h" #include "nsISHistory.h" #include "nsIXULWindow.h" #include "nsIMozBrowserFrame.h" #include "nsISHistory.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 "nsIURI.h" #include "nsIURL.h" #include "nsNetUtil.h" #include "nsGkAtoms.h" #include "nsNameSpaceManager.h" #include "nsThreadUtils.h" #include "nsIDOMChromeWindow.h" #include "InProcessTabChildMessageManager.h" #include "Layers.h" #include "ClientLayerManager.h" #include "ContentParent.h" #include "TabParent.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/BasePrincipal.h" #include "mozilla/GuardObjects.h" #include "mozilla/HTMLEditor.h" #include "mozilla/NullPrincipal.h" #include "mozilla/Preferences.h" #include "mozilla/Unused.h" #include "mozilla/dom/ChromeMessageSender.h" #include "mozilla/dom/Element.h" #include "mozilla/dom/FrameLoaderBinding.h" #include "mozilla/dom/MozFrameLoaderOwnerBinding.h" #include "mozilla/gfx/CrossProcessPaint.h" #include "mozilla/jsipc/CrossProcessObjectWrappers.h" #include "mozilla/layout/RenderFrame.h" #include "mozilla/ServoCSSParser.h" #include "mozilla/ServoStyleSet.h" #include "nsGenericHTMLFrameElement.h" #include "GeckoProfiler.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/ParentSHistory.h" #include "mozilla/dom/ChildSHistory.h" #include "mozilla/dom/CanonicalBrowsingContext.h" #include "mozilla/dom/ContentChild.h" #include "mozilla/dom/BrowserBridgeChild.h" #include "mozilla/dom/HTMLBodyElement.h" #include "mozilla/ContentPrincipal.h" #ifdef XP_WIN # include "mozilla/plugins/PPluginWidgetParent.h" # include "../plugins/ipc/PluginWidgetParent.h" #endif #ifdef MOZ_XUL # include "nsXULPopupManager.h" #endif #ifdef NS_PRINTING # include "mozilla/embedding/printingui/PrintingParent.h" # include "nsIWebBrowserPrint.h" #endif using namespace mozilla; using namespace mozilla::hal; using namespace mozilla::dom; using namespace mozilla::dom::ipc; using namespace mozilla::layers; using namespace mozilla::layout; typedef ScrollableLayerGuid::ViewID ViewID; // Bug 136580: Limit to the number of nested content frames that can have the // same URL. This is to stop content that is recursively loading // itself. Note that "#foo" on the end of URL doesn't affect // whether it's considered identical, but "?foo" or ";foo" are // considered and compared. // Limit this to 2, like chromium does. #define MAX_SAME_URL_CONTENT_FRAMES 2 // 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, mDocShell, mMessageManager, mChildMessageManager, mOpener, mParentSHistory) 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, nsPIDOMWindowOuter* aOpener, bool aNetworkCreated) : mOwnerContent(aOwner), mDetachedSubdocFrame(nullptr), mOpener(aOpener), mRemoteBrowser(nullptr), mChildID(0), mDepthTooGreat(false), mIsTopLevelContent(false), mDestroyCalled(false), mNeedsAsyncDestroy(false), mInSwap(false), mInShow(false), mHideCalled(false), mNetworkCreated(aNetworkCreated), mLoadingOriginalSrc(false), mRemoteBrowserShown(false), mIsRemoteFrame(false), mObservingOwnerContent(false) { mIsRemoteFrame = ShouldUseRemoteProcess(); MOZ_ASSERT(!mIsRemoteFrame || !aOpener, "Cannot pass aOpener for a remote frame!"); } nsFrameLoader::nsFrameLoader(Element* aOwner, const mozilla::dom::RemotenessOptions& aOptions) : mOwnerContent(aOwner), mDetachedSubdocFrame(nullptr), mOpener(nullptr), mRemoteBrowser(nullptr), mChildID(0), mDepthTooGreat(false), mIsTopLevelContent(false), mDestroyCalled(false), mNeedsAsyncDestroy(false), mInSwap(false), mInShow(false), mHideCalled(false), mNetworkCreated(false), mLoadingOriginalSrc(false), mRemoteBrowserShown(false), mIsRemoteFrame(false), mObservingOwnerContent(false) { if (aOptions.mRemoteType.WasPassed() && (!aOptions.mRemoteType.Value().IsVoid())) { mIsRemoteFrame = true; } bool hasOpener = aOptions.mOpener.WasPassed() && !aOptions.mOpener.Value().IsNull(); MOZ_ASSERT(!mIsRemoteFrame || !hasOpener, "Cannot pass aOpener for a remote frame!"); if (hasOpener) { // This seems slightly unwieldy. mOpener = aOptions.mOpener.Value().Value().get()->GetDOMWindow(); } } nsFrameLoader::~nsFrameLoader() { if (mMessageManager) { mMessageManager->Disconnect(); } MOZ_RELEASE_ASSERT(mDestroyCalled); } /* static */ nsFrameLoader* nsFrameLoader::Create(Element* aOwner, nsPIDOMWindowOuter* aOpener, bool aNetworkCreated) { 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); return new nsFrameLoader(aOwner, aOpener, aNetworkCreated); } /* static */ nsFrameLoader* nsFrameLoader::Create( mozilla::dom::Element* aOwner, const mozilla::dom::RemotenessOptions& aOptions) { NS_ENSURE_TRUE(aOwner, nullptr); // 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. return new nsFrameLoader(aOwner, aOptions); } void nsFrameLoader::LoadFrame(bool aOriginalSrc) { if (NS_WARN_IF(!mOwnerContent)) { return; } nsAutoString src; nsCOMPtr principal; nsCOMPtr csp; bool isSrcdoc = mOwnerContent->IsHTMLElement(nsGkAtoms::iframe) && mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc); if (isSrcdoc) { src.AssignLiteral("about:srcdoc"); principal = mOwnerContent->NodePrincipal(); // Currently the NodePrincipal holds the CSP for a document. After // Bug 965637 we can query the CSP from mOwnerContent->OwnerDoc() // instead of mOwnerContent->NodePrincipal(). mOwnerContent->NodePrincipal()->GetCsp(getter_AddRefs(csp)); } 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(); // Currently the NodePrincipal holds the CSP for a document. After // Bug 965637 we can query the CSP from mOwnerContent->OwnerDoc() // instead of mOwnerContent->NodePrincipal(). mOwnerContent->NodePrincipal()->GetCsp(getter_AddRefs(csp)); } } Document* doc = mOwnerContent->OwnerDoc(); if (doc->IsStaticDocument()) { return; } if (doc->IsLoadedAsInteractiveData()) { // XBL bindings doc shouldn't load sub-documents. return; } nsCOMPtr base_uri = mOwnerContent->GetBaseURI(); auto encoding = doc->GetDocumentCharacterSet(); nsCOMPtr 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), NS_LITERAL_STRING("about:blank"), encoding, base_uri); } if (NS_SUCCEEDED(rv)) { rv = LoadURI(uri, principal, csp, aOriginalSrc); } if (NS_FAILED(rv)) { FireErrorEvent(); } } void nsFrameLoader::FireErrorEvent() { if (!mOwnerContent) { return; } RefPtr loadBlockingAsyncDispatcher = new LoadBlockingAsyncEventDispatcher( mOwnerContent, NS_LITERAL_STRING("error"), 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 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; } nsresult nsFrameLoader::ReallyStartLoading() { nsresult rv = ReallyStartLoadingInternal(); if (NS_FAILED(rv)) { FireErrorEvent(); } return rv; } nsresult nsFrameLoader::ReallyStartLoadingInternal() { NS_ENSURE_STATE(mURIToLoad && mOwnerContent && mOwnerContent->IsInComposedDoc()); AUTO_PROFILER_LABEL("nsFrameLoader::ReallyStartLoadingInternal", OTHER); if (IsRemoteFrame()) { if (!mRemoteBrowser && !mBrowserBridgeChild && !TryRemoteBrowser()) { NS_WARNING("Couldn't create child process for iframe."); return NS_ERROR_FAILURE; } if (mBrowserBridgeChild) { nsAutoCString spec; mURIToLoad->GetSpec(spec); Unused << mBrowserBridgeChild->SendLoadURL(spec); } else { // FIXME get error codes from child mRemoteBrowser->LoadURL(mURIToLoad); } 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; } NS_ASSERTION(mDocShell, "MaybeCreateDocShell succeeded with a null mDocShell"); // Just to be safe, recheck uri. rv = CheckURILoad(mURIToLoad, mTriggeringPrincipal); NS_ENSURE_SUCCESS(rv, rv); RefPtr loadState = new nsDocShellLoadState(mURIToLoad); loadState->SetOriginalFrameSrc(mLoadingOriginalSrc); mLoadingOriginalSrc = false; // If this frame is sandboxed with respect to origin we will set it up with // a null principal later in nsDocShell::DoURILoad. // We do it there to correctly sandbox content that was loaded into // the frame via other methods than the src attribute. // We'll use our principal, not that of the document loaded inside us. This // is very important; needed to prevent XSS attacks 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 NodePrincipal 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) { // Currently the NodePrincipal holds the CSP for a document. After // Bug 965637 we can query the CSP from mOwnerContent->OwnerDoc() // instead of mOwnerContent->NodePrincipal(). nsCOMPtr csp; mOwnerContent->NodePrincipal()->GetCsp(getter_AddRefs(csp)); loadState->SetCsp(csp); } nsCOMPtr referrer; nsAutoString srcdoc; bool isSrcdoc = mOwnerContent->IsHTMLElement(nsGkAtoms::iframe) && mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::srcdoc, srcdoc); if (isSrcdoc) { nsAutoString referrerStr; mOwnerContent->OwnerDoc()->GetReferrer(referrerStr); rv = NS_NewURI(getter_AddRefs(referrer), referrerStr); loadState->SetSrcdocData(srcdoc); nsCOMPtr baseURI = mOwnerContent->GetBaseURI(); loadState->SetBaseURI(baseURI); } else { rv = mOwnerContent->NodePrincipal()->GetURI(getter_AddRefs(referrer)); NS_ENSURE_SUCCESS(rv, rv); } // Use referrer as long as it is not an NullPrincipalURI. // We could add a method such as GetReferrerURI to principals to make this // cleaner, but given that we need to start using Source Browsing Context for // referrer (see Bug 960639) this may be wasted effort at this stage. if (referrer) { bool isNullPrincipalScheme; rv = referrer->SchemeIs(NS_NULLPRINCIPAL_SCHEME, &isNullPrincipalScheme); if (NS_SUCCEEDED(rv) && !isNullPrincipalScheme) { // get referrer policy for this iframe: // first load document wide policy, then // load iframe referrer attribute if enabled in preferences // per element referrer overrules document wide referrer if enabled net::ReferrerPolicy referrerPolicy = mOwnerContent->OwnerDoc()->GetReferrerPolicy(); HTMLIFrameElement* iframe = HTMLIFrameElement::FromNode(mOwnerContent); if (iframe) { net::ReferrerPolicy iframeReferrerPolicy = iframe->GetReferrerPolicyAsEnum(); if (iframeReferrerPolicy != net::RP_Unset) { referrerPolicy = iframeReferrerPolicy; } } loadState->SetReferrerInfo(new ReferrerInfo(referrer, referrerPolicy)); } } // 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->SetIsFromProcessingFrameAttributes(); // Kick off the load... bool tmpState = mNeedsAsyncDestroy; mNeedsAsyncDestroy = true; loadState->SetLoadFlags(flags); loadState->SetFirstParty(false); rv = mDocShell->LoadURI(loadState); 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); 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; } NS_ASSERTION(mDocShell, "MaybeCreateDocShell succeeded, but null mDocShell"); } return mDocShell; } static void SetTreeOwnerAndChromeEventHandlerOnDocshellTree( nsIDocShellTreeItem* aItem, nsIDocShellTreeOwner* aOwner, EventTarget* aHandler) { MOZ_ASSERT(aItem, "Must have item"); aItem->SetTreeOwner(aOwner); int32_t childCount = 0; aItem->GetChildCount(&childCount); for (int32_t i = 0; i < childCount; ++i) { nsCOMPtr item; aItem->GetChildAt(i, getter_AddRefs(item)); if (aHandler) { nsCOMPtr 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 mozbrowser = aOwnerContent->GetAsMozBrowserFrame(); if (mozbrowser) { mozbrowser->GetMozbrowser(&isContent); } } if (isContent) { return aDocShell->ItemType() == nsIDocShellTreeItem::typeContent; } nsCOMPtr parent; aDocShell->GetParent(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()), "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(nsIDocShellTreeItem* aParentItem, int32_t aType) { int32_t childCount = 0; aParentItem->GetChildCount(&childCount); for (int32_t i = 0; i < childCount; ++i) { nsCOMPtr kid; aParentItem->GetChildAt(i, getter_AddRefs(kid)); if (kid->ItemType() != aType || !AllDescendantsOfType(kid, aType)) { return false; } } return true; } /** * A class that automatically sets mInShow to false when it goes * out of scope. */ class MOZ_RAII AutoResetInShow { private: nsFrameLoader* mFrameLoader; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER public: explicit AutoResetInShow( nsFrameLoader* aFrameLoader MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : mFrameLoader(aFrameLoader) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; } ~AutoResetInShow() { mFrameLoader->mInShow = false; } }; static bool ParentWindowIsActive(Document* aDoc) { nsCOMPtr root = nsContentUtils::GetWindowRoot(aDoc); if (root) { nsPIDOMWindowOuter* rootWin = root->GetWindow(); return rootWin && rootWin->IsActive(); } return false; } void nsFrameLoader::MaybeShowFrame() { nsIFrame* frame = GetPrimaryFrameOfOwningContent(); if (frame) { nsSubDocumentFrame* subDocFrame = do_QueryFrame(frame); if (subDocFrame) { subDocFrame->MaybeShowViewer(); } } } bool nsFrameLoader::Show(int32_t marginWidth, int32_t marginHeight, int32_t scrollbarPrefX, int32_t scrollbarPrefY, nsSubDocumentFrame* frame) { if (mInShow) { return false; } // Reset mInShow if we exit early. AutoResetInShow resetInShow(this); mInShow = true; ScreenIntSize size = frame->GetSubdocumentSize(); if (IsRemoteFrame()) { return ShowRemoteFrame(size, frame); } nsresult rv = MaybeCreateDocShell(); if (NS_FAILED(rv)) { return false; } NS_ASSERTION(mDocShell, "MaybeCreateDocShell succeeded, but null mDocShell"); if (!mDocShell) { return false; } mDocShell->SetMarginWidth(marginWidth); mDocShell->SetMarginHeight(marginHeight); mDocShell->SetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_X, scrollbarPrefX); mDocShell->SetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_Y, scrollbarPrefY); nsCOMPtr presShell = mDocShell->GetPresShell(); if (presShell) { // Ensure root scroll frame is reflowed in case scroll preferences or // margins have changed nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); if (rootScrollFrame) { presShell->FrameNeedsReflow(rootScrollFrame, nsIPresShell::eResize, NS_FRAME_IS_DIRTY); } return true; } nsView* view = frame->EnsureInnerView(); if (!view) return false; RefPtr baseWindow = mDocShell; baseWindow->InitWindow(nullptr, view->GetWidget(), 0, 0, size.width, size.height); // This is kinda whacky, this "Create()" call doesn't really // create anything, one starts to wonder why this was named // "Create"... baseWindow->Create(); baseWindow->SetVisibility(true); NS_ENSURE_TRUE(mDocShell, 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 // https://bugzilla.mozilla.org/show_bug.cgi?id=284245 presShell = mDocShell->GetPresShell(); if (presShell) { 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 = mDocShell->GetHTMLEditor(); Unused << htmlEditor; htmlDoc->SetDesignMode(NS_LITERAL_STRING("off"), Nothing(), IgnoreErrors()); htmlDoc->SetDesignMode(NS_LITERAL_STRING("on"), Nothing(), IgnoreErrors()); } else { // Re-initialize the presentation for contenteditable documents bool editable = false, hasEditingSession = false; mDocShell->GetEditable(&editable); mDocShell->GetHasEditingSession(&hasEditingSession); RefPtr htmlEditor = mDocShell->GetHTMLEditor(); if (editable && hasEditingSession && htmlEditor) { htmlEditor->PostCreate(); } } } } mInShow = false; if (mHideCalled) { mHideCalled = false; Hide(); return false; } return true; } void nsFrameLoader::MarginsChanged(uint32_t aMarginWidth, uint32_t aMarginHeight) { // We assume that the margins are always zero for remote frames. if (IsRemoteFrame()) return; // If there's no docshell, we're probably not up and running yet. // nsFrameLoader::Show() will take care of setting the right // margins. if (!mDocShell) return; // Set the margins mDocShell->SetMarginWidth(aMarginWidth); mDocShell->SetMarginHeight(aMarginHeight); // There's a cached property declaration block // that needs to be updated if (Document* doc = mDocShell->GetDocument()) { for (nsINode* cur = doc; cur; cur = cur->GetNextNode()) { if (cur->IsHTMLElement(nsGkAtoms::body)) { static_cast(cur)->ClearMappedServoStyle(); } } } // Trigger a restyle if there's a prescontext // FIXME: This could do something much less expensive. if (nsPresContext* presContext = mDocShell->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 (!mRemoteBrowser && !mBrowserBridgeChild && !TryRemoteBrowser()) { 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(widget)->IsSmallPopup()) { return false; } if (mBrowserBridgeChild) { nsCOMPtr container = mOwnerContent->OwnerDoc()->GetContainer(); nsCOMPtr baseWindow = do_QueryInterface(container); nsCOMPtr mainWidget; baseWindow->GetMainWidget(getter_AddRefs(mainWidget)); nsSizeMode sizeMode = mainWidget ? mainWidget->SizeMode() : nsSizeMode_Normal; Unused << mBrowserBridgeChild->SendShow( size, ParentWindowIsActive(mOwnerContent->OwnerDoc()), sizeMode); mRemoteBrowserShown = true; return true; } RenderFrame* rf = mRemoteBrowser ? mRemoteBrowser->GetRenderFrame() : nullptr; if (!rf || !rf->AttachLayerManager()) { // This is just not going to work. return false; } mRemoteBrowser->Show(size, ParentWindowIsActive(mOwnerContent->OwnerDoc())); mRemoteBrowserShown = true; nsCOMPtr os = services::GetObserverService(); if (os) { os->NotifyObservers(ToSupports(this), "remote-browser-shown", nullptr); } } 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->GetStateBits() & NS_FRAME_FIRST_REFLOW)) { if (mRemoteBrowser) { mRemoteBrowser->UpdateDimensions(dimensions, size); } else if (mBrowserBridgeChild) { mBrowserBridgeChild->UpdateDimensions(dimensions, size); } } } return true; } void nsFrameLoader::Hide() { if (mHideCalled) { return; } if (mInShow) { mHideCalled = true; return; } if (!mDocShell) return; nsCOMPtr contentViewer; mDocShell->GetContentViewer(getter_AddRefs(contentViewer)); if (contentViewer) contentViewer->SetSticky(false); RefPtr baseWin = mDocShell; 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->GetStateBits() & NS_FRAME_FIRST_REFLOW) { if (nsCOMPtr shell = presContext->GetPresShell()) { shell->FlushPendingNotifications(FlushType::Layout); } } } nsresult nsFrameLoader::SwapWithOtherRemoteLoader( nsFrameLoader* aOther, nsFrameLoaderOwner* aThisOwner, nsFrameLoaderOwner* aOtherOwner) { MOZ_ASSERT(NS_IsMainThread()); #ifdef DEBUG RefPtr first = aThisOwner->GetFrameLoader(); RefPtr 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; } nsIPresShell* ourShell = ourDoc->GetShell(); nsIPresShell* otherShell = otherDoc->GetShell(); if (!ourShell || !otherShell) { return NS_ERROR_NOT_IMPLEMENTED; } // FIXME: Consider supporting FrameLoader swapping for remote sub frames. if (mBrowserBridgeChild) { return NS_ERROR_NOT_IMPLEMENTED; } if (!mRemoteBrowser || !aOther->mRemoteBrowser) { return NS_ERROR_NOT_IMPLEMENTED; } if (mRemoteBrowser->IsIsolatedMozBrowserElement() != aOther->mRemoteBrowser->IsIsolatedMozBrowserElement()) { 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 = mRemoteBrowser->OriginAttributesRef(); rv = PopulateUserContextIdFromAttribute(ourOriginAttributes); NS_ENSURE_SUCCESS(rv, rv); OriginAttributes otherOriginAttributes = aOther->mRemoteBrowser->OriginAttributesRef(); rv = aOther->PopulateUserContextIdFromAttribute(otherOriginAttributes); NS_ENSURE_SUCCESS(rv, rv); if (ourOriginAttributes != 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; 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 otherBrowserDOMWindow = aOther->mRemoteBrowser->GetBrowserDOMWindow(); nsCOMPtr browserDOMWindow = mRemoteBrowser->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(); } aOther->mRemoteBrowser->SetBrowserDOMWindow(browserDOMWindow); mRemoteBrowser->SetBrowserDOMWindow(otherBrowserDOMWindow); #ifdef XP_WIN // Native plugin windows used by this remote content need to be reparented. if (nsPIDOMWindowOuter* newWin = ourDoc->GetWindow()) { RefPtr newParent = nsGlobalWindowOuter::Cast(newWin)->GetMainWidget(); const ManagedContainer& plugins = aOther->mRemoteBrowser->ManagedPPluginWidgetParent(); for (auto iter = plugins.ConstIter(); !iter.Done(); iter.Next()) { static_cast(iter.Get()->GetKey()) ->SetParent(newParent); } } #endif // XP_WIN MaybeUpdatePrimaryTabParent(eTabParentRemoved); aOther->MaybeUpdatePrimaryTabParent(eTabParentRemoved); SetOwnerContent(otherContent); aOther->SetOwnerContent(ourContent); mRemoteBrowser->SetOwnerElement(otherContent); aOther->mRemoteBrowser->SetOwnerElement(ourContent); // Update window activation state for the swapped owner content. Unused << mRemoteBrowser->SendParentActivated( ParentWindowIsActive(otherContent->OwnerDoc())); Unused << aOther->mRemoteBrowser->SendParentActivated( ParentWindowIsActive(ourContent->OwnerDoc())); MaybeUpdatePrimaryTabParent(eTabParentChanged); aOther->MaybeUpdatePrimaryTabParent(eTabParentChanged); RefPtr ourMessageManager = mMessageManager; RefPtr 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); // 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 kungFuDeathGrip(this); aThisOwner->SetFrameLoader(aOther); aOtherOwner->SetFrameLoader(kungFuDeathGrip); ourFrameFrame->EndSwapDocShells(otherFrame); ourShell->BackingScaleFactorChanged(); otherShell->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 << mRemoteBrowser->SendSwappedWithOtherRemoteLoader( ourContext.AsIPCTabContext()); Unused << aOther->mRemoteBrowser->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 MOZ_GUARD_OBJECT_NOTIFIER_PARAM) : mThisFrameLoader(aThisFrameLoader), mOtherFrameLoader(aOtherFrameLoader), mThisDocShell(aThisDocShell), mOtherDocShell(aOtherDocShell), mThisEventTarget(aThisEventTarget), mOtherEventTarget(aOtherEventTarget) { MOZ_GUARD_OBJECT_NOTIFIER_INIT; 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::FirePageShowEvent(mThisDocShell, mThisEventTarget, false); nsContentUtils::FirePageShowEvent(mOtherDocShell, mOtherEventTarget, false); nsContentUtils::FirePageHideEvent(mThisDocShell, mThisEventTarget); nsContentUtils::FirePageHideEvent(mOtherDocShell, mOtherEventTarget); } ~AutoResetInFrameSwap() { nsContentUtils::FirePageShowEvent(mThisDocShell, mThisEventTarget, true); nsContentUtils::FirePageShowEvent(mOtherDocShell, mOtherEventTarget, true); mThisFrameLoader->mInSwap = false; mOtherFrameLoader->mInSwap = false; mThisDocShell->SetInFrameSwap(false); mOtherDocShell->SetInFrameSwap(false); } private: RefPtr mThisFrameLoader; RefPtr mOtherFrameLoader; RefPtr mThisDocShell; RefPtr mOtherDocShell; nsCOMPtr mThisEventTarget; nsCOMPtr mOtherEventTarget; MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER }; nsresult nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther, nsFrameLoaderOwner* aThisOwner, nsFrameLoaderOwner* aOtherOwner) { #ifdef DEBUG RefPtr first = aThisOwner->GetFrameLoader(); RefPtr 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; } Element* ourContent = mOwnerContent; Element* otherContent = aOther->mOwnerContent; if (!ourContent || !otherContent) { // Can't handle this 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(kNameSpaceID_None, nsGkAtoms::allowfullscreen) || ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen))); bool otherFullscreenAllowed = otherContent->IsXULElement() || (aOther->OwnerIsMozBrowserFrame() && (otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowfullscreen) || otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen))); if (ourFullscreenAllowed != otherFullscreenAllowed) { return NS_ERROR_NOT_IMPLEMENTED; } bool ourPaymentRequestAllowed = ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowpaymentrequest); bool otherPaymentRequestAllowed = otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowpaymentrequest); if (ourPaymentRequestAllowed != otherPaymentRequestAllowed) { 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 ourDocshell = static_cast(GetExistingDocShell()); RefPtr otherDocshell = static_cast(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 ourRootTreeItem, otherRootTreeItem; ourDocshell->GetSameTypeRootTreeItem(getter_AddRefs(ourRootTreeItem)); otherDocshell->GetSameTypeRootTreeItem(getter_AddRefs(otherRootTreeItem)); nsCOMPtr ourRootWebnav = do_QueryInterface(ourRootTreeItem); nsCOMPtr otherRootWebnav = do_QueryInterface(otherRootTreeItem); if (!ourRootWebnav || !otherRootWebnav) { return NS_ERROR_NOT_IMPLEMENTED; } RefPtr ourHistory = ourRootWebnav->GetSessionHistory(); RefPtr otherHistory = otherRootWebnav->GetSessionHistory(); if ((ourRootTreeItem != ourDocshell || otherRootTreeItem != otherDocshell) && (ourHistory || otherHistory)) { return NS_ERROR_NOT_IMPLEMENTED; } // Also make sure that the two docshells 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. int32_t ourType = ourDocshell->ItemType(); int32_t otherType = otherDocshell->ItemType(); if (ourType != otherType) { 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 ourType is not // nsIDocShellTreeItem::typeContent then all of our descendants are the same // type as us. if (ourType != nsIDocShellTreeItem::typeContent && (!AllDescendantsOfType(ourDocshell, ourType) || !AllDescendantsOfType(otherDocshell, otherType))) { 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 ourOwner, otherOwner; ourDocshell->GetTreeOwner(getter_AddRefs(ourOwner)); otherDocshell->GetTreeOwner(getter_AddRefs(otherOwner)); // Note: it's OK to have null treeowners. nsCOMPtr ourParentItem, otherParentItem; ourDocshell->GetParent(getter_AddRefs(ourParentItem)); otherDocshell->GetParent(getter_AddRefs(otherParentItem)); if (!ourParentItem || !otherParentItem) { return NS_ERROR_NOT_IMPLEMENTED; } // Make sure our parents are the same type too int32_t ourParentType = ourParentItem->ItemType(); int32_t otherParentType = otherParentItem->ItemType(); if (ourParentType != otherParentType) { return NS_ERROR_NOT_IMPLEMENTED; } nsCOMPtr ourWindow = ourDocshell->GetWindow(); nsCOMPtr otherWindow = otherDocshell->GetWindow(); nsCOMPtr ourFrameElement = ourWindow->GetFrameElementInternal(); nsCOMPtr otherFrameElement = otherWindow->GetFrameElementInternal(); nsCOMPtr ourChromeEventHandler = ourWindow->GetChromeEventHandler(); nsCOMPtr otherChromeEventHandler = otherWindow->GetChromeEventHandler(); nsCOMPtr ourEventTarget = ourWindow->GetParentTarget(); nsCOMPtr otherEventTarget = otherWindow->GetParentTarget(); NS_ASSERTION(SameCOMIdentity(ourFrameElement, ourContent) && SameCOMIdentity(otherFrameElement, otherContent) && SameCOMIdentity(ourChromeEventHandler, ourContent) && SameCOMIdentity(otherChromeEventHandler, otherContent), "How did that happen, exactly?"); nsCOMPtr ourChildDocument = ourWindow->GetExtantDoc(); nsCOMPtr otherChildDocument = otherWindow->GetExtantDoc(); if (!ourChildDocument || !otherChildDocument) { // This shouldn't be happening return NS_ERROR_NOT_IMPLEMENTED; } nsCOMPtr ourParentDocument = ourChildDocument->GetParentDocument(); nsCOMPtr otherParentDocument = otherChildDocument->GetParentDocument(); // 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; } NS_ASSERTION(ourDoc == ourParentDocument, "Unexpected parent document"); NS_ASSERTION(otherDoc == otherParentDocument, "Unexpected parent document"); nsIPresShell* ourShell = ourDoc->GetShell(); nsIPresShell* otherShell = otherDoc->GetShell(); if (!ourShell || !otherShell) { return NS_ERROR_NOT_IMPLEMENTED; } if (ourDocshell->GetIsIsolatedMozBrowserElement() != otherDocshell->GetIsIsolatedMozBrowserElement()) { 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 = PopulateUserContextIdFromAttribute(ourOriginAttributes); NS_ENSURE_SUCCESS(rv, rv); OriginAttributes otherOriginAttributes = otherDocshell->GetOriginAttributes(); rv = aOther->PopulateUserContextIdFromAttribute(otherOriginAttributes); NS_ENSURE_SUCCESS(rv, rv); if (ourOriginAttributes != otherOriginAttributes) { return NS_ERROR_NOT_IMPLEMENTED; } if (mInSwap || aOther->mInSwap) { return NS_ERROR_NOT_IMPLEMENTED; } AutoResetInFrameSwap autoFrameSwap(this, aOther, ourDocshell, otherDocshell, ourEventTarget, otherEventTarget); nsIFrame* ourFrame = ourContent->GetPrimaryFrame(); nsIFrame* otherFrame = otherContent->GetPrimaryFrame(); if (!ourFrame || !otherFrame) { return NS_ERROR_NOT_IMPLEMENTED; } 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; } // Destroy browser frame scripts for content leaving a frame with browser API if (OwnerIsMozBrowserFrame() && !aOther->OwnerIsMozBrowserFrame()) { DestroyBrowserFrameScripts(); } if (!OwnerIsMozBrowserFrame() && aOther->OwnerIsMozBrowserFrame()) { aOther->DestroyBrowserFrameScripts(); } // Now move the docshells to the right docshell trees. Note that this // resets their treeowners to null. ourParentItem->RemoveChild(ourDocshell); otherParentItem->RemoveChild(otherDocshell); if (ourType == nsIDocShellTreeItem::typeContent) { ourOwner->ContentShellRemoved(ourDocshell); otherOwner->ContentShellRemoved(otherDocshell); } ourParentItem->AddChild(otherDocshell); otherParentItem->AddChild(ourDocshell); // 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, ourType == nsIDocShellTreeItem::typeContent ? otherChromeEventHandler.get() : nullptr); SetTreeOwnerAndChromeEventHandlerOnDocshellTree( otherDocshell, ourOwner, ourType == nsIDocShellTreeItem::typeContent ? 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); AddTreeItemToTreeOwner(ourDocshell, otherOwner); aOther->AddTreeItemToTreeOwner(otherDocshell, ourOwner); // 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); ourWindow->SetFrameElementInternal(otherFrameElement); otherWindow->SetFrameElementInternal(ourFrameElement); RefPtr ourMessageManager = mMessageManager; RefPtr otherMessageManager = aOther->mMessageManager; // Swap pointers in child message managers. if (mChildMessageManager) { InProcessTabChildMessageManager* tabChild = mChildMessageManager; tabChild->SetOwner(otherContent); tabChild->SetChromeMessageManager(otherMessageManager); } if (aOther->mChildMessageManager) { InProcessTabChildMessageManager* otherTabChild = aOther->mChildMessageManager; otherTabChild->SetOwner(ourContent); otherTabChild->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 kungFuDeathGrip(this); aThisOwner->SetFrameLoader(aOther); aOtherOwner->SetFrameLoader(kungFuDeathGrip); // Drop any cached content viewers in the two session histories. if (ourHistory) { ourHistory->EvictLocalContentViewers(); } if (otherHistory) { otherHistory->EvictLocalContentViewers(); } NS_ASSERTION(ourFrame == ourContent->GetPrimaryFrame() && otherFrame == otherContent->GetPrimaryFrame(), "changed primary frame"); ourFrameFrame->EndSwapDocShells(otherFrame); // If the content being swapped came from windows on two screens with // incompatible backing resolution (e.g. dragging a tab between windows on // hi-dpi and low-dpi screens), it will have style data that is based on // the wrong appUnitsPerDevPixel value. So we tell the PresShells that their // backing scale factor may have changed. (Bug 822266) ourShell->BackingScaleFactorChanged(); otherShell->BackingScaleFactorChanged(); // Initialize browser API if needed now that owner content has changed InitializeBrowserAPI(); aOther->InitializeBrowserAPI(); return NS_OK; } void nsFrameLoader::Destroy() { StartDestroy(); } class nsFrameLoaderDestroyRunnable : public Runnable { enum DestroyPhase { // See the implementation of Run for an explanation of these phases. eDestroyDocShell, eWaitForUnloadMessage, eDestroyComplete }; RefPtr mFrameLoader; DestroyPhase mPhase; public: explicit nsFrameLoaderDestroyRunnable(nsFrameLoader* aFrameLoader) : mozilla::Runnable("nsFrameLoaderDestroyRunnable"), mFrameLoader(aFrameLoader), mPhase(eDestroyDocShell) {} NS_IMETHOD Run() override; }; void nsFrameLoader::StartDestroy() { // nsFrameLoader::StartDestroy is called just before the frameloader is // detached from the element. Destruction continues in phases via // the nsFrameLoaderDestroyRunnable. if (mDestroyCalled) { return; } mDestroyCalled = true; // After this point, we return an error when trying to send a message using // the message manager on the frame. if (mMessageManager) { mMessageManager->Close(); } // Retain references to the element and the frameloader in case we // receive any messages from the message manager on the frame. These // references are dropped in DestroyComplete. if (mChildMessageManager || mRemoteBrowser) { mOwnerContentStrong = mOwnerContent; if (mRemoteBrowser) { mRemoteBrowser->CacheFrameLoader(this); } if (mChildMessageManager) { mChildMessageManager->CacheFrameLoader(this); } } // If the TabParent has installed any event listeners on the window, this is // its last chance to remove them while we're still in the document. if (mRemoteBrowser) { mRemoteBrowser->RemoveWindowListeners(); } nsCOMPtr doc; bool dynamicSubframeRemoval = false; if (mOwnerContent) { doc = mOwnerContent->OwnerDoc(); dynamicSubframeRemoval = !mIsTopLevelContent && !doc->InUnlinkOrDeletion(); doc->SetSubDocumentFor(mOwnerContent, nullptr); MaybeUpdatePrimaryTabParent(eTabParentRemoved); SetOwnerContent(nullptr); } // Seems like this is a dynamic frame removal. if (dynamicSubframeRemoval) { if (mDocShell) { mDocShell->RemoveFromSessionHistory(); } } // Let the tree owner know we're gone. if (mIsTopLevelContent) { if (mDocShell) { nsCOMPtr parentItem; mDocShell->GetParent(getter_AddRefs(parentItem)); nsCOMPtr owner = do_GetInterface(parentItem); if (owner) { owner->ContentShellRemoved(mDocShell); } } } // Let our window know that we are gone if (mDocShell) { nsCOMPtr win_private(mDocShell->GetWindow()); if (win_private) { win_private->SetFrameElementInternal(nullptr); } } nsCOMPtr destroyRunnable = new nsFrameLoaderDestroyRunnable(this); if (mNeedsAsyncDestroy || !doc || NS_FAILED(doc->FinalizeFrameLoader(this, destroyRunnable))) { NS_DispatchToCurrentThread(destroyRunnable); } } nsresult nsFrameLoaderDestroyRunnable::Run() { switch (mPhase) { case eDestroyDocShell: mFrameLoader->DestroyDocShell(); // In the out-of-process case, TabParent will eventually call // DestroyComplete once it receives a __delete__ message from the child. // In the in-process case, we dispatch a series of runnables to ensure // that DestroyComplete gets called at the right time. The frame loader is // kept alive by mFrameLoader during this time. if (mFrameLoader->mChildMessageManager) { // When the docshell is destroyed, NotifyWindowIDDestroyed is called to // asynchronously notify {outer,inner}-window-destroyed via a runnable. // We don't want DestroyComplete to run until after those runnables have // run. Since we're enqueueing ourselves after the window-destroyed // runnables are enqueued, we're guaranteed to run after. mPhase = eWaitForUnloadMessage; NS_DispatchToCurrentThread(this); } break; case eWaitForUnloadMessage: // The *-window-destroyed observers have finished running at this // point. However, it's possible that a *-window-destroyed observer might // have sent a message using the message manager. These messages might not // have been processed yet. So we enqueue ourselves again to ensure that // DestroyComplete runs after all messages sent by *-window-destroyed // observers have been processed. mPhase = eDestroyComplete; NS_DispatchToCurrentThread(this); break; case eDestroyComplete: // Now that all messages sent by unload listeners and window destroyed // observers have been processed, we disconnect the message manager and // finish destruction. mFrameLoader->DestroyComplete(); break; } return NS_OK; } void nsFrameLoader::DestroyDocShell() { // This code runs after the frameloader has been detached from the // element. We postpone this work because we may not be allowed to run // script at that time. // Ask the TabChild to fire the frame script "unload" event, destroy its // docshell, and finally destroy the PBrowser actor. This eventually leads to // nsFrameLoader::DestroyComplete being called. if (mRemoteBrowser) { mRemoteBrowser->Destroy(); } if (mBrowserBridgeChild) { Unused << mBrowserBridgeChild->Send__delete__(mBrowserBridgeChild); mBrowserBridgeChild = nullptr; } // Fire the "unload" event if we're in-process. if (mChildMessageManager) { mChildMessageManager->FireUnloadEvent(); } // Destroy the docshell. if (mDocShell) { mDocShell->Destroy(); } mDocShell = nullptr; if (mChildMessageManager) { // Stop handling events in the in-process frame script. mChildMessageManager->DisconnectEventListeners(); } } void nsFrameLoader::DestroyComplete() { // We get here, as part of StartDestroy, after the docshell has been destroyed // and all message manager messages sent during docshell destruction have been // dispatched. We also get here if the child process crashes. In the latter // case, StartDestroy might not have been called. // Drop the strong references created in StartDestroy. if (mChildMessageManager || mRemoteBrowser) { mOwnerContentStrong = nullptr; if (mRemoteBrowser) { mRemoteBrowser->CacheFrameLoader(nullptr); } if (mChildMessageManager) { mChildMessageManager->CacheFrameLoader(nullptr); } } // Call TabParent::Destroy if we haven't already (in case of a crash). if (mRemoteBrowser) { mRemoteBrowser->SetOwnerElement(nullptr); mRemoteBrowser->Destroy(); mRemoteBrowser = nullptr; } if (mBrowserBridgeChild) { Unused << mBrowserBridgeChild->Send__delete__(mBrowserBridgeChild); mBrowserBridgeChild = nullptr; } if (mMessageManager) { mMessageManager->Disconnect(); } if (mChildMessageManager) { mChildMessageManager->Disconnect(); } mMessageManager = nullptr; mChildMessageManager = nullptr; } void nsFrameLoader::SetOwnerContent(Element* aContent) { if (mObservingOwnerContent) { mObservingOwnerContent = false; mOwnerContent->RemoveMutationObserver(this); } mOwnerContent = aContent; AutoJSAPI jsapi; jsapi.Init(); JS::RootedObject wrapper(jsapi.cx(), GetWrapper()); if (wrapper) { JSAutoRealm ar(jsapi.cx(), wrapper); IgnoredErrorResult rv; UpdateReflectorGlobal(jsapi.cx(), wrapper, rv); Unused << NS_WARN_IF(rv.Failed()); } } bool nsFrameLoader::OwnerIsMozBrowserFrame() { nsCOMPtr browserFrame = do_QueryInterface(mOwnerContent); return browserFrame ? browserFrame->GetReallyIsBrowser() : false; } bool nsFrameLoader::OwnerIsIsolatedMozBrowserFrame() { nsCOMPtr browserFrame = do_QueryInterface(mOwnerContent); if (!browserFrame) { return false; } if (!OwnerIsMozBrowserFrame()) { return false; } bool isolated = browserFrame->GetIsolated(); if (isolated) { return true; } return false; } bool nsFrameLoader::ShouldUseRemoteProcess() { if (PR_GetEnv("MOZ_DISABLE_OOP_TABS") || Preferences::GetBool("dom.ipc.tabs.disabled", false)) { return false; } // Don't try to launch nested children if we don't have OMTC. // They won't render! if (XRE_IsContentProcess() && !CompositorBridgeChild::ChildProcessHasCompositorBridge()) { return false; } // Check if the force fission test attribute is enabled. if (XRE_IsContentProcess() && Preferences::GetBool("fission.oopif.attribute", false) && mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::fission)) { return true; } if (XRE_IsContentProcess() && !(PR_GetEnv("MOZ_NESTED_OOP_TABS") || Preferences::GetBool("dom.ipc.tabs.nested.enabled", false))) { return false; } // If we're an