From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- dom/base/nsFrameLoader.cpp | 3490 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 3490 insertions(+) create mode 100644 dom/base/nsFrameLoader.cpp (limited to 'dom/base/nsFrameLoader.cpp') diff --git a/dom/base/nsFrameLoader.cpp b/dom/base/nsFrameLoader.cpp new file mode 100644 index 000000000..23067becd --- /dev/null +++ b/dom/base/nsFrameLoader.cpp @@ -0,0 +1,3490 @@ +/* -*- 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 "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 "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; + } + + // 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) + 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