/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* 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 "mozIApplication.h" #include "nsDocShell.h" #include "nsIAppsService.h" #include "nsIDOMHTMLIFrameElement.h" #include "nsIDOMHTMLFrameElement.h" #include "nsIDOMMozBrowserFrame.h" #include "nsIDOMWindow.h" #include "nsIPresShell.h" #include "nsIContentInlines.h" #include "nsIContentViewer.h" #include "nsIDocument.h" #include "nsIDOMDocument.h" #include "nsPIDOMWindow.h" #include "nsIWebNavigation.h" #include "nsIWebProgress.h" #include "nsIDocShell.h" #include "nsIDocShellTreeOwner.h" #include "nsIDocShellLoadInfo.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 "nsIDOMEventTarget.h" #include "nsIFrame.h" #include "nsIScrollableFrame.h" #include "nsSubDocumentFrame.h" #include "nsError.h" #include "nsISHistory.h" #include "nsISHistoryInternal.h" #include "nsIDOMHTMLDocument.h" #include "nsIXULWindow.h" #include "nsIEditor.h" #include "nsIMozBrowserFrame.h" #include "nsISHistory.h" #include "nsNullPrincipal.h" #include "nsIScriptError.h" #include "nsGlobalWindow.h" #include "nsPIWindowRoot.h" #include "nsLayoutUtils.h" #include "nsMappedAttributes.h" #include "nsView.h" #include "GroupedSHistory.h" #include "PartialSHistory.h" #include "nsIURI.h" #include "nsIURL.h" #include "nsNetUtil.h" #include "nsGkAtoms.h" #include "nsNameSpaceManager.h" #include "nsThreadUtils.h" #include "nsIDOMChromeWindow.h" #include "nsInProcessTabChildGlobal.h" #include "Layers.h" #include "ClientLayerManager.h" #include "AppProcessChecker.h" #include "ContentParent.h" #include "TabParent.h" #include "mozilla/plugins/PPluginWidgetParent.h" #include "../plugins/ipc/PluginWidgetParent.h" #include "mozilla/AsyncEventDispatcher.h" #include "mozilla/BasePrincipal.h" #include "mozilla/GuardObjects.h" #include "mozilla/Preferences.h" #include "mozilla/Unused.h" #include "mozilla/dom/Element.h" #include "mozilla/jsipc/CrossProcessObjectWrappers.h" #include "mozilla/layout/RenderFrameParent.h" #include "nsIAppsService.h" #include "GeckoProfiler.h" #include "jsapi.h" #include "mozilla/dom/HTMLIFrameElement.h" #include "nsSandboxFlags.h" #include "mozilla/layers/CompositorBridgeChild.h" #include "mozilla/dom/ipc/StructuredCloneData.h" #include "mozilla/WebBrowserPersistLocalDocument.h" #include "nsPrincipal.h" #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 FrameMetrics::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. // Bug 228829: Limit this to 1, like IE does. #define MAX_SAME_URL_CONTENT_FRAMES 1 // 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(nsFrameLoader, mDocShell, mMessageManager, mChildMessageManager, mOpener, mPartialSessionHistory, mGroupedSessionHistory) NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameLoader) NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameLoader) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameLoader) NS_INTERFACE_MAP_ENTRY(nsIFrameLoader) NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFrameLoader) NS_INTERFACE_MAP_ENTRY(nsIWebBrowserPersistable) NS_INTERFACE_MAP_END nsFrameLoader::nsFrameLoader(Element* aOwner, nsPIDOMWindowOuter* aOpener, bool aNetworkCreated) : mOwnerContent(aOwner) , mDetachedSubdocFrame(nullptr) , mOpener(aOpener) , mRemoteBrowser(nullptr) , mChildID(0) , mEventMode(EVENT_MODE_NORMAL_DISPATCH) , mIsPrerendered(false) , mDepthTooGreat(false) , mIsTopLevelContent(false) , mDestroyCalled(false) , mNeedsAsyncDestroy(false) , mInSwap(false) , mInShow(false) , mHideCalled(false) , mNetworkCreated(aNetworkCreated) , mRemoteBrowserShown(false) , mRemoteFrame(false) , mClipSubdocument(true) , mClampScrollPosition(true) , mObservingOwnerContent(false) , mVisible(true) , mFreshProcess(false) { mRemoteFrame = ShouldUseRemoteProcess(); MOZ_ASSERT(!mRemoteFrame || !aOpener, "Cannot pass aOpener for a remote frame!"); // Check if we are supposed to load into a fresh process mFreshProcess = mOwnerContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::freshProcess, nsGkAtoms::_true, eCaseMatters); } nsFrameLoader::~nsFrameLoader() { if (mMessageManager) { mMessageManager->Disconnect(); } MOZ_RELEASE_ASSERT(mDestroyCalled); } nsFrameLoader* nsFrameLoader::Create(Element* aOwner, nsPIDOMWindowOuter* aOpener, bool aNetworkCreated) { NS_ENSURE_TRUE(aOwner, nullptr); nsIDocument* 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); } NS_IMETHODIMP nsFrameLoader::LoadFrame() { NS_ENSURE_TRUE(mOwnerContent, NS_ERROR_NOT_INITIALIZED); nsAutoString src; bool isSrcdoc = mOwnerContent->IsHTMLElement(nsGkAtoms::iframe) && mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc); if (isSrcdoc) { src.AssignLiteral("about:srcdoc"); } else { GetURL(src); 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 NS_OK; } src.AssignLiteral("about:blank"); } } nsIDocument* doc = mOwnerContent->OwnerDoc(); if (doc->IsStaticDocument()) { return NS_OK; } if (doc->IsLoadedAsInteractiveData()) { // XBL bindings doc shouldn't load sub-documents. return NS_OK; } nsCOMPtr base_uri = mOwnerContent->GetBaseURI(); const nsAFlatCString &doc_charset = doc->GetDocumentCharacterSet(); const char *charset = doc_charset.IsEmpty() ? nullptr : doc_charset.get(); nsCOMPtr uri; nsresult rv = NS_NewURI(getter_AddRefs(uri), src, charset, 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"), charset, base_uri); } if (NS_SUCCEEDED(rv)) { rv = LoadURI(uri); } if (NS_FAILED(rv)) { FireErrorEvent(); return rv; } return NS_OK; } void nsFrameLoader::FireErrorEvent() { if (!mOwnerContent) { return; } RefPtr loadBlockingAsyncDispatcher = new LoadBlockingAsyncEventDispatcher(mOwnerContent, NS_LITERAL_STRING("error"), false, false); loadBlockingAsyncDispatcher->PostDOMEvent(); } NS_IMETHODIMP nsFrameLoader::LoadURI(nsIURI* aURI) { if (!aURI) return NS_ERROR_INVALID_POINTER; NS_ENSURE_STATE(!mDestroyCalled && mOwnerContent); nsCOMPtr doc = mOwnerContent->OwnerDoc(); nsresult rv = CheckURILoad(aURI); NS_ENSURE_SUCCESS(rv, rv); mURIToLoad = aURI; rv = doc->InitializeFrameLoader(this); if (NS_FAILED(rv)) { mURIToLoad = nullptr; } return rv; } NS_IMETHODIMP nsFrameLoader::SetIsPrerendered() { MOZ_ASSERT(!mDocShell, "Please call SetIsPrerendered before docShell is created"); mIsPrerendered = true; return NS_OK; } NS_IMETHODIMP nsFrameLoader::MakePrerenderedLoaderActive() { MOZ_ASSERT(mIsPrerendered, "This frameloader was not in prerendered mode."); mIsPrerendered = false; if (IsRemoteFrame()) { if (!mRemoteBrowser) { NS_WARNING("Missing remote browser."); return NS_ERROR_FAILURE; } mRemoteBrowser->SetDocShellIsActive(true); } else { if (!mDocShell) { NS_WARNING("Missing docshell."); return NS_ERROR_FAILURE; } nsresult rv = mDocShell->SetIsActive(true); NS_ENSURE_SUCCESS(rv, rv); } return NS_OK; } NS_IMETHODIMP nsFrameLoader::GetPartialSessionHistory(nsIPartialSHistory** aResult) { if (mRemoteBrowser && !mPartialSessionHistory) { // For remote case we can lazy initialize PartialSHistory since // it doens't need to be registered as a listener to nsISHistory directly. mPartialSessionHistory = new PartialSHistory(this); } nsCOMPtr partialHistory(mPartialSessionHistory); partialHistory.forget(aResult); return NS_OK; } NS_IMETHODIMP nsFrameLoader::GetGroupedSessionHistory(nsIGroupedSHistory** aResult) { nsCOMPtr groupedHistory(mGroupedSessionHistory); groupedHistory.forget(aResult); return NS_OK; } NS_IMETHODIMP nsFrameLoader::AppendPartialSessionHistoryAndSwap(nsIFrameLoader* aOther) { if (!aOther) { return NS_ERROR_INVALID_POINTER; } nsCOMPtr otherGroupedHistory; aOther->GetGroupedSessionHistory(getter_AddRefs(otherGroupedHistory)); MOZ_ASSERT(!otherGroupedHistory, "Cannot append a GroupedSHistory owner to another."); if (otherGroupedHistory) { return NS_ERROR_UNEXPECTED; } // Append ourselves. nsresult rv; if (!mGroupedSessionHistory) { mGroupedSessionHistory = new GroupedSHistory(); rv = mGroupedSessionHistory->AppendPartialSessionHistory(mPartialSessionHistory); if (NS_FAILED(rv)) { return NS_ERROR_FAILURE; } } if (aOther == this) { return NS_OK; } // Append the other. RefPtr otherLoader = static_cast(aOther); rv = mGroupedSessionHistory-> AppendPartialSessionHistory(otherLoader->mPartialSessionHistory); if (NS_FAILED(rv)) { return NS_ERROR_FAILURE; } // Swap loaders through our owner, so the owner's listeners will be correctly // setup. nsCOMPtr ourBrowser = do_QueryInterface(mOwnerContent); nsCOMPtr otherBrowser = do_QueryInterface(otherLoader->mOwnerContent); if (!ourBrowser || !otherBrowser) { return NS_ERROR_FAILURE; } if (NS_FAILED(ourBrowser->SwapBrowsers(otherBrowser))) { return NS_ERROR_FAILURE; } mGroupedSessionHistory.swap(otherLoader->mGroupedSessionHistory); return NS_OK; } NS_IMETHODIMP nsFrameLoader::RequestGroupedHistoryNavigation(uint32_t aGlobalIndex) { if (!mGroupedSessionHistory) { return NS_ERROR_UNEXPECTED; } nsCOMPtr targetLoader; nsresult rv = mGroupedSessionHistory-> GotoIndex(aGlobalIndex, getter_AddRefs(targetLoader)); if (NS_FAILED(rv)) { return NS_ERROR_FAILURE; } RefPtr otherLoader = static_cast(targetLoader.get()); if (!targetLoader) { return NS_ERROR_FAILURE; } if (targetLoader == this) { return NS_OK; } nsCOMPtr ourBrowser = do_QueryInterface(mOwnerContent); nsCOMPtr otherBrowser = do_QueryInterface(otherLoader->mOwnerContent); if (!ourBrowser || !otherBrowser) { return NS_ERROR_FAILURE; } if (NS_FAILED(ourBrowser->SwapBrowsers(otherBrowser))) { return NS_ERROR_FAILURE; } mGroupedSessionHistory.swap(otherLoader->mGroupedSessionHistory); return NS_OK; } nsresult nsFrameLoader::ReallyStartLoading() { nsresult rv = ReallyStartLoadingInternal(); if (NS_FAILED(rv)) { FireErrorEvent(); } return rv; } nsresult nsFrameLoader::ReallyStartLoadingInternal() { NS_ENSURE_STATE(mURIToLoad && mOwnerContent && mOwnerContent->IsInComposedDoc()); PROFILER_LABEL("nsFrameLoader", "ReallyStartLoading", js::ProfileEntry::Category::OTHER); if (IsRemoteFrame()) { if (!mRemoteBrowser && !TryRemoteBrowser()) { NS_WARNING("Couldn't create child process for iframe."); return NS_ERROR_FAILURE; } // FIXME get error codes from child mRemoteBrowser->LoadURL(mURIToLoad); if (!mRemoteBrowserShown && !ShowRemoteFrame(ScreenIntSize(0, 0))) { NS_WARNING("[nsFrameLoader] ReallyStartLoadingInternal tried but couldn't show remote browser.\n"); } 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); NS_ENSURE_SUCCESS(rv, rv); nsCOMPtr loadInfo; mDocShell->CreateLoadInfo(getter_AddRefs(loadInfo)); NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE); // 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! loadInfo->SetTriggeringPrincipal(mOwnerContent->NodePrincipal()); 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); loadInfo->SetSrcdocData(srcdoc); nsCOMPtr baseURI = mOwnerContent->GetBaseURI(); loadInfo->SetBaseURI(baseURI); } else { rv = mOwnerContent->NodePrincipal()->GetURI(getter_AddRefs(referrer)); NS_ENSURE_SUCCESS(rv, rv); } // Use referrer as long as it is not an nsNullPrincipalURI. // 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) { loadInfo->SetReferrer(referrer); } } // 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::FromContent(mOwnerContent); if (iframe) { net::ReferrerPolicy iframeReferrerPolicy = iframe->GetReferrerPolicyAsEnum(); if (iframeReferrerPolicy != net::RP_Unset) { referrerPolicy = iframeReferrerPolicy; } } loadInfo->SetReferrerPolicy(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; } // Notify that this load resulted from attribute changes. loadInfo->SetIsFromProcessingFrameAttributes(true); // Kick off the load... bool tmpState = mNeedsAsyncDestroy; mNeedsAsyncDestroy = true; nsCOMPtr uriToLoad = mURIToLoad; rv = mDocShell->LoadURI(uriToLoad, loadInfo, flags, false); mNeedsAsyncDestroy = tmpState; mURIToLoad = nullptr; NS_ENSURE_SUCCESS(rv, rv); return NS_OK; } nsresult nsFrameLoader::CheckURILoad(nsIURI* aURI) { // 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 = 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); } NS_IMETHODIMP nsFrameLoader::GetDocShell(nsIDocShell **aDocShell) { *aDocShell = nullptr; nsresult rv = NS_OK; if (IsRemoteFrame()) { return rv; } // 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)) { return rv; } NS_ASSERTION(mDocShell, "MaybeCreateDocShell succeeded, but null mDocShell"); } *aDocShell = mDocShell; NS_IF_ADDREF(*aDocShell); return rv; } static void SetTreeOwnerAndChromeEventHandlerOnDocshellTree(nsIDocShellTreeItem* aItem, nsIDocShellTreeOwner* aOwner, EventTarget* aHandler) { NS_PRECONDITION(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); } } /** * Set the type of the treeitem and hook it up to the treeowner. * @param aItem the treeitem we're working with * @param aTreeOwner the relevant treeowner; might be null * @param aParentType the nsIDocShellTreeItem::GetType of our parent docshell * @param aParentNode if non-null, the docshell we should be added as a child to * * @return whether aItem is top-level content */ bool nsFrameLoader::AddTreeItemToTreeOwner(nsIDocShellTreeItem* aItem, nsIDocShellTreeOwner* aOwner, int32_t aParentType, nsIDocShell* aParentNode) { NS_PRECONDITION(aItem, "Must have docshell treeitem"); NS_PRECONDITION(mOwnerContent, "Must have owning content"); nsAutoString value; bool isContent = false; mOwnerContent->GetAttr(kNameSpaceID_None, TypeAttrName(), value); // we accept "content" and "content-xxx" values. // at time of writing, we expect "xxx" to be "primary" or "targetable", but // someday it might be an integer expressing priority or something else. isContent = value.LowerCaseEqualsLiteral("content") || StringBeginsWith(value, NS_LITERAL_STRING("content-"), nsCaseInsensitiveStringComparator()); // Force mozbrowser frames to always be typeContent, even if the // mozbrowser interfaces are disabled. nsCOMPtr mozbrowser = do_QueryInterface(mOwnerContent); if (mozbrowser) { bool isMozbrowser = false; mozbrowser->GetMozbrowser(&isMozbrowser); isContent |= isMozbrowser; } if (isContent) { // The web shell's type is content. aItem->SetItemType(nsIDocShellTreeItem::typeContent); } else { // Inherit our type from our parent docshell. If it is // chrome, we'll be chrome. If it is content, we'll be // content. aItem->SetItemType(aParentType); } // Now that we have our type set, add ourselves to the parent, as needed. if (aParentNode) { aParentNode->AddChild(aItem); } bool retval = false; if (aParentType == nsIDocShellTreeItem::typeChrome && isContent) { retval = true; bool is_primary = value.LowerCaseEqualsLiteral("content-primary"); if (aOwner) { bool is_targetable = is_primary || value.LowerCaseEqualsLiteral("content-targetable"); mOwnerContent->AddMutationObserver(this); mObservingOwnerContent = true; aOwner->ContentShellAdded(aItem, is_primary, is_targetable, value); } } return retval; } 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; } }; 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); nsCOMPtr sc = do_QueryInterface(mDocShell); if (sc) { sc->SetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_X, scrollbarPrefX); sc->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; nsCOMPtr baseWindow = do_QueryInterface(mDocShell); NS_ASSERTION(baseWindow, "Found a nsIDocShell that isn't a nsIBaseWindow."); 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) { nsCOMPtr doc = do_QueryInterface(presShell->GetDocument()); if (doc) { nsAutoString designMode; doc->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. nsCOMPtr editor; nsresult rv = mDocShell->GetEditor(getter_AddRefs(editor)); NS_ENSURE_SUCCESS(rv, false); doc->SetDesignMode(NS_LITERAL_STRING("off")); doc->SetDesignMode(NS_LITERAL_STRING("on")); } else { // Re-initialize the presentation for contenteditable documents bool editable = false, hasEditingSession = false; mDocShell->GetEditable(&editable); mDocShell->GetHasEditingSession(&hasEditingSession); nsCOMPtr editor; mDocShell->GetEditor(getter_AddRefs(editor)); if (editable && hasEditingSession && editor) { editor->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); // Trigger a restyle if there's a prescontext // FIXME: This could do something much less expensive. RefPtr presContext; mDocShell->GetPresContext(getter_AddRefs(presContext)); if (presContext) // rebuild, because now the same nsMappedAttributes* will produce // a different style presContext->RebuildAllStyleData(nsChangeHint(0), eRestyle_Subtree); } bool nsFrameLoader::ShowRemoteFrame(const ScreenIntSize& size, nsSubDocumentFrame *aFrame) { PROFILER_LABEL_FUNC(js::ProfileEntry::Category::GRAPHICS); NS_ASSERTION(IsRemoteFrame(), "ShowRemote only makes sense on remote frames."); if (!mRemoteBrowser && !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; } RefPtr layerManager = nsContentUtils::LayerManagerForDocument(mOwnerContent->GetComposedDoc()); if (!layerManager) { // This is just not going to work. return false; } nsPIDOMWindowOuter* win = mOwnerContent->OwnerDoc()->GetWindow(); bool parentIsActive = false; if (win) { nsCOMPtr windowRoot = nsGlobalWindow::Cast(win)->GetTopWindowRoot(); if (windowRoot) { nsPIDOMWindowOuter* topWin = windowRoot->GetWindow(); parentIsActive = topWin && topWin->IsActive(); } } mRemoteBrowser->Show(size, parentIsActive); mRemoteBrowserShown = true; nsCOMPtr os = services::GetObserverService(); if (os) { os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, 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)) { mRemoteBrowser->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); nsCOMPtr baseWin = do_QueryInterface(mDocShell); NS_ASSERTION(baseWin, "Found an nsIDocShell which doesn't implement nsIBaseWindow."); baseWin->SetVisibility(false); baseWin->SetParentWidget(nullptr); } nsresult nsFrameLoader::SwapWithOtherRemoteLoader(nsFrameLoader* aOther, nsIFrameLoaderOwner* aThisOwner, nsIFrameLoaderOwner* 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; } nsIDocument* ourDoc = ourContent->GetComposedDoc(); nsIDocument* 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; } if (!mRemoteBrowser || !aOther->mRemoteBrowser) { return NS_ERROR_NOT_IMPLEMENTED; } if (mRemoteBrowser->IsIsolatedMozBrowserElement() != aOther->mRemoteBrowser->IsIsolatedMozBrowserElement() || mRemoteBrowser->HasOwnApp() != aOther->mRemoteBrowser->HasOwnApp()) { 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 XULElement, 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. DocShellOriginAttributes ourOriginAttributes = mRemoteBrowser->OriginAttributesRef(); rv = PopulateUserContextIdFromAttribute(ourOriginAttributes); NS_ENSURE_SUCCESS(rv,rv); DocShellOriginAttributes 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 (OwnerIsMozBrowserOrAppFrame() && !aOther->OwnerIsMozBrowserOrAppFrame()) { DestroyBrowserFrameScripts(); } if (!OwnerIsMozBrowserOrAppFrame() && aOther->OwnerIsMozBrowserOrAppFrame()) { aOther->DestroyBrowserFrameScripts(); } aOther->mRemoteBrowser->SetBrowserDOMWindow(browserDOMWindow); mRemoteBrowser->SetBrowserDOMWindow(otherBrowserDOMWindow); // Native plugin windows used by this remote content need to be reparented. if (nsPIDOMWindowOuter* newWin = ourDoc->GetWindow()) { RefPtr newParent = nsGlobalWindow::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); } } MaybeUpdatePrimaryTabParent(eTabParentRemoved); aOther->MaybeUpdatePrimaryTabParent(eTabParentRemoved); SetOwnerContent(otherContent); aOther->SetOwnerContent(ourContent); mRemoteBrowser->SetOwnerElement(otherContent); aOther->mRemoteBrowser->SetOwnerElement(ourContent); 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. nsCOMPtr kungFuDeathGrip(this); aThisOwner->InternalSetFrameLoader(aOther); aOtherOwner->InternalSetFrameLoader(kungFuDeathGrip); ourFrameFrame->EndSwapDocShells(otherFrame); ourShell->BackingScaleFactorChanged(); otherShell->BackingScaleFactorChanged(); ourDoc->FlushPendingNotifications(Flush_Layout); otherDoc->FlushPendingNotifications(Flush_Layout); // 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, nsIFrameLoaderOwner* aThisOwner, nsIFrameLoaderOwner* 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() || (OwnerIsMozBrowserOrAppFrame() && (ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowfullscreen) || ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen))); bool otherFullscreenAllowed = otherContent->IsXULElement() || (aOther->OwnerIsMozBrowserOrAppFrame() && (otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowfullscreen) || otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen))); if (ourFullscreenAllowed != otherFullscreenAllowed) { 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; } nsCOMPtr ourHistory; nsCOMPtr otherHistory; ourRootWebnav->GetSessionHistory(getter_AddRefs(ourHistory)); otherRootWebnav->GetSessionHistory(getter_AddRefs(otherHistory)); 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 = do_QueryInterface(ourWindow->GetChromeEventHandler()); nsCOMPtr otherChromeEventHandler = do_QueryInterface(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. nsIDocument* ourDoc = ourContent->GetComposedDoc(); nsIDocument* 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() || ourDocshell->GetIsApp() != otherDocshell->GetIsApp()) { 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 XULElement, 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. DocShellOriginAttributes ourOriginAttributes = ourDocshell->GetOriginAttributes(); rv = PopulateUserContextIdFromAttribute(ourOriginAttributes); NS_ENSURE_SUCCESS(rv,rv); DocShellOriginAttributes 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 (OwnerIsMozBrowserOrAppFrame() && !aOther->OwnerIsMozBrowserOrAppFrame()) { DestroyBrowserFrameScripts(); } if (!OwnerIsMozBrowserOrAppFrame() && aOther->OwnerIsMozBrowserOrAppFrame()) { 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, otherParentType, nullptr); aOther->AddTreeItemToTreeOwner(otherDocshell, ourOwner, ourParentType, nullptr); // 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) { nsInProcessTabChildGlobal* tabChild = static_cast(mChildMessageManager.get()); tabChild->SetOwner(otherContent); tabChild->SetChromeMessageManager(otherMessageManager); } if (aOther->mChildMessageManager) { nsInProcessTabChildGlobal* otherTabChild = static_cast(aOther->mChildMessageManager.get()); 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. nsCOMPtr kungFuDeathGrip(this); aThisOwner->InternalSetFrameLoader(aOther); aOtherOwner->InternalSetFrameLoader(kungFuDeathGrip); // Drop any cached content viewers in the two session histories. nsCOMPtr ourInternalHistory = do_QueryInterface(ourHistory); nsCOMPtr otherInternalHistory = do_QueryInterface(otherHistory); if (ourInternalHistory) { ourInternalHistory->EvictAllContentViewers(); } if (otherInternalHistory) { otherInternalHistory->EvictAllContentViewers(); } 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(); ourParentDocument->FlushPendingNotifications(Flush_Layout); otherParentDocument->FlushPendingNotifications(Flush_Layout); // Initialize browser API if needed now that owner content has changed InitializeBrowserAPI(); aOther->InitializeBrowserAPI(); return NS_OK; } NS_IMETHODIMP nsFrameLoader::Destroy() { StartDestroy(); return NS_OK; } 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) : 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(); } // Fire the "unload" event if we're in-process. if (mChildMessageManager) { static_cast(mChildMessageManager.get())->FireUnloadEvent(); } // Destroy the docshell. nsCOMPtr base_win(do_QueryInterface(mDocShell)); if (base_win) { base_win->Destroy(); } mDocShell = nullptr; if (mChildMessageManager) { // Stop handling events in the in-process frame script. static_cast(mChildMessageManager.get())->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 (mMessageManager) { mMessageManager->Disconnect(); } if (mChildMessageManager) { static_cast(mChildMessageManager.get())->Disconnect(); } mMessageManager = nullptr; mChildMessageManager = nullptr; } NS_IMETHODIMP nsFrameLoader::GetDepthTooGreat(bool* aDepthTooGreat) { *aDepthTooGreat = mDepthTooGreat; return NS_OK; } void nsFrameLoader::SetOwnerContent(Element* aContent) { if (mObservingOwnerContent) { mObservingOwnerContent = false; mOwnerContent->RemoveMutationObserver(this); } mOwnerContent = aContent; if (RenderFrameParent* rfp = GetCurrentRenderFrame()) { rfp->OwnerContentChanged(aContent); } } bool nsFrameLoader::OwnerIsMozBrowserOrAppFrame() { nsCOMPtr browserFrame = do_QueryInterface(mOwnerContent); return browserFrame ? browserFrame->GetReallyIsBrowserOrApp() : false; } // The xpcom getter version NS_IMETHODIMP nsFrameLoader::GetOwnerIsMozBrowserOrAppFrame(bool* aResult) { *aResult = OwnerIsMozBrowserOrAppFrame(); return NS_OK; } bool nsFrameLoader::OwnerIsAppFrame() { nsCOMPtr browserFrame = do_QueryInterface(mOwnerContent); return browserFrame ? browserFrame->GetReallyIsApp() : false; } bool nsFrameLoader::OwnerIsMozBrowserFrame() { return OwnerIsMozBrowserOrAppFrame() && !OwnerIsAppFrame(); } 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; } void nsFrameLoader::GetOwnerAppManifestURL(nsAString& aOut) { aOut.Truncate(); nsCOMPtr browserFrame = do_QueryInterface(mOwnerContent); if (browserFrame) { browserFrame->GetAppManifestURL(aOut); } } already_AddRefed nsFrameLoader::GetOwnApp() { nsAutoString manifest; GetOwnerAppManifestURL(manifest); if (manifest.IsEmpty()) { return nullptr; } nsCOMPtr appsService = do_GetService(APPS_SERVICE_CONTRACTID); NS_ENSURE_TRUE(appsService, nullptr); nsCOMPtr app; appsService->GetAppByManifestURL(manifest, getter_AddRefs(app)); return app.forget(); } already_AddRefed nsFrameLoader::GetContainingApp() { // See if our owner content's principal has an associated app. uint32_t appId = mOwnerContent->NodePrincipal()->GetAppId(); MOZ_ASSERT(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID); if (appId == nsIScriptSecurityManager::NO_APP_ID || appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) { return nullptr; } nsCOMPtr appsService = do_GetService(APPS_SERVICE_CONTRACTID); NS_ENSURE_TRUE(appsService, nullptr); nsCOMPtr app; appsService->GetAppByLocalId(appId, getter_AddRefs(app)); return app.forget(); } 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; } if (XRE_IsContentProcess() && !(PR_GetEnv("MOZ_NESTED_OOP_TABS") || Preferences::GetBool("dom.ipc.tabs.nested.enabled", false))) { return false; } // If we're an