michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ michael@0: /* vim: set ts=2 sw=2 et tw=78: */ michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: /* michael@0: * Class for managing loading of a subframe (creation of the docshell, michael@0: * handling of loads in it, recursion-checking). michael@0: */ michael@0: michael@0: #include "base/basictypes.h" michael@0: michael@0: #include "prenv.h" michael@0: michael@0: #include "mozIApplication.h" michael@0: #include "nsIDOMHTMLIFrameElement.h" michael@0: #include "nsIDOMHTMLFrameElement.h" michael@0: #include "nsIDOMMozBrowserFrame.h" michael@0: #include "nsIDOMWindow.h" michael@0: #include "nsIPresShell.h" michael@0: #include "nsIContentInlines.h" michael@0: #include "nsIContentViewer.h" michael@0: #include "nsIDocument.h" michael@0: #include "nsIDOMDocument.h" michael@0: #include "nsIDOMFile.h" michael@0: #include "nsPIDOMWindow.h" michael@0: #include "nsIWebNavigation.h" michael@0: #include "nsIWebProgress.h" michael@0: #include "nsIDocShell.h" michael@0: #include "nsIDocShellTreeOwner.h" michael@0: #include "nsIDocShellLoadInfo.h" michael@0: #include "nsIDOMApplicationRegistry.h" michael@0: #include "nsIBaseWindow.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsCxPusher.h" michael@0: #include "nsIXPConnect.h" michael@0: #include "nsUnicharUtils.h" michael@0: #include "nsIScriptGlobalObject.h" michael@0: #include "nsIScriptSecurityManager.h" michael@0: #include "nsIScrollable.h" michael@0: #include "nsFrameLoader.h" michael@0: #include "nsIDOMEventTarget.h" michael@0: #include "nsIFrame.h" michael@0: #include "nsIScrollableFrame.h" michael@0: #include "nsSubDocumentFrame.h" michael@0: #include "nsError.h" michael@0: #include "nsISHistory.h" michael@0: #include "nsISHistoryInternal.h" michael@0: #include "nsIDOMHTMLDocument.h" michael@0: #include "nsIXULWindow.h" michael@0: #include "nsIEditor.h" michael@0: #include "nsIMozBrowserFrame.h" michael@0: #include "nsIPermissionManager.h" michael@0: #include "nsISHistory.h" michael@0: #include "nsNullPrincipal.h" michael@0: michael@0: #include "nsLayoutUtils.h" michael@0: #include "nsView.h" michael@0: michael@0: #include "nsIURI.h" michael@0: #include "nsIURL.h" michael@0: #include "nsNetUtil.h" michael@0: michael@0: #include "nsGkAtoms.h" michael@0: #include "nsNameSpaceManager.h" michael@0: michael@0: #include "nsThreadUtils.h" michael@0: michael@0: #include "nsIDOMChromeWindow.h" michael@0: #include "nsInProcessTabChildGlobal.h" michael@0: michael@0: #include "Layers.h" michael@0: michael@0: #include "AppProcessChecker.h" michael@0: #include "ContentParent.h" michael@0: #include "TabParent.h" michael@0: #include "mozilla/AsyncEventDispatcher.h" michael@0: #include "mozilla/GuardObjects.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "mozilla/unused.h" michael@0: #include "mozilla/dom/Element.h" michael@0: #include "mozilla/layout/RenderFrameParent.h" michael@0: #include "nsIAppsService.h" michael@0: #include "GeckoProfiler.h" michael@0: michael@0: #include "jsapi.h" michael@0: #include "mozilla/dom/HTMLIFrameElement.h" michael@0: #include "nsSandboxFlags.h" michael@0: #include "JavaScriptParent.h" michael@0: michael@0: #include "mozilla/dom/StructuredCloneUtils.h" michael@0: michael@0: #ifdef MOZ_XUL michael@0: #include "nsXULPopupManager.h" michael@0: #endif michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::hal; michael@0: using namespace mozilla::dom; michael@0: using namespace mozilla::dom::ipc; michael@0: using namespace mozilla::layers; michael@0: using namespace mozilla::layout; michael@0: typedef FrameMetrics::ViewID ViewID; michael@0: michael@0: class nsAsyncDocShellDestroyer : public nsRunnable michael@0: { michael@0: public: michael@0: nsAsyncDocShellDestroyer(nsIDocShell* aDocShell) michael@0: : mDocShell(aDocShell) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: nsCOMPtr base_win(do_QueryInterface(mDocShell)); michael@0: if (base_win) { michael@0: base_win->Destroy(); michael@0: } michael@0: return NS_OK; michael@0: } michael@0: nsRefPtr mDocShell; michael@0: }; michael@0: michael@0: NS_IMPL_ISUPPORTS(nsContentView, nsIContentView) michael@0: michael@0: nsresult michael@0: nsContentView::Update(const ViewConfig& aConfig) michael@0: { michael@0: if (aConfig == mConfig) { michael@0: return NS_OK; michael@0: } michael@0: mConfig = aConfig; michael@0: michael@0: // View changed. Try to locate our subdoc frame and invalidate michael@0: // it if found. michael@0: if (!mFrameLoader) { michael@0: if (IsRoot()) { michael@0: // Oops, don't have a frame right now. That's OK; the view michael@0: // config persists and will apply to the next frame we get, if we michael@0: // ever get one. michael@0: return NS_OK; michael@0: } else { michael@0: // This view is no longer valid. michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: } michael@0: michael@0: if (RenderFrameParent* rfp = mFrameLoader->GetCurrentRemoteFrame()) { michael@0: rfp->ContentViewScaleChanged(this); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentView::ScrollTo(float aXpx, float aYpx) michael@0: { michael@0: ViewConfig config(mConfig); michael@0: config.mScrollOffset = nsPoint(nsPresContext::CSSPixelsToAppUnits(aXpx), michael@0: nsPresContext::CSSPixelsToAppUnits(aYpx)); michael@0: return Update(config); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentView::ScrollBy(float aDXpx, float aDYpx) michael@0: { michael@0: ViewConfig config(mConfig); michael@0: config.mScrollOffset.MoveBy(nsPresContext::CSSPixelsToAppUnits(aDXpx), michael@0: nsPresContext::CSSPixelsToAppUnits(aDYpx)); michael@0: return Update(config); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentView::SetScale(float aXScale, float aYScale) michael@0: { michael@0: ViewConfig config(mConfig); michael@0: config.mXScale = aXScale; michael@0: config.mYScale = aYScale; michael@0: return Update(config); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentView::GetScrollX(float* aViewScrollX) michael@0: { michael@0: *aViewScrollX = nsPresContext::AppUnitsToFloatCSSPixels( michael@0: mConfig.mScrollOffset.x); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentView::GetScrollY(float* aViewScrollY) michael@0: { michael@0: *aViewScrollY = nsPresContext::AppUnitsToFloatCSSPixels( michael@0: mConfig.mScrollOffset.y); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentView::GetViewportWidth(float* aWidth) michael@0: { michael@0: *aWidth = nsPresContext::AppUnitsToFloatCSSPixels(mViewportSize.width); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentView::GetViewportHeight(float* aHeight) michael@0: { michael@0: *aHeight = nsPresContext::AppUnitsToFloatCSSPixels(mViewportSize.height); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentView::GetContentWidth(float* aWidth) michael@0: { michael@0: *aWidth = nsPresContext::AppUnitsToFloatCSSPixels(mContentSize.width); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentView::GetContentHeight(float* aHeight) michael@0: { michael@0: *aHeight = nsPresContext::AppUnitsToFloatCSSPixels(mContentSize.height); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsContentView::GetId(nsContentViewId* aId) michael@0: { michael@0: NS_ASSERTION(sizeof(nsContentViewId) == sizeof(ViewID), michael@0: "ID size for XPCOM ID and internal ID type are not the same!"); michael@0: *aId = mScrollId; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Bug 136580: Limit to the number of nested content frames that can have the michael@0: // same URL. This is to stop content that is recursively loading michael@0: // itself. Note that "#foo" on the end of URL doesn't affect michael@0: // whether it's considered identical, but "?foo" or ";foo" are michael@0: // considered and compared. michael@0: // Bug 228829: Limit this to 1, like IE does. michael@0: #define MAX_SAME_URL_CONTENT_FRAMES 1 michael@0: michael@0: // Bug 8065: Limit content frame depth to some reasonable level. This michael@0: // does not count chrome frames when determining depth, nor does it michael@0: // prevent chrome recursion. Number is fairly arbitrary, but meant to michael@0: // keep number of shells to a reasonable number on accidental recursion with a michael@0: // small (but not 1) branching factor. With large branching factors the number michael@0: // of shells can rapidly become huge and run us out of memory. To solve that, michael@0: // we'd need to re-institute a fixed version of bug 98158. michael@0: #define MAX_DEPTH_CONTENT_FRAMES 10 michael@0: michael@0: NS_IMPL_CYCLE_COLLECTION(nsFrameLoader, mDocShell, mMessageManager, mChildMessageManager) michael@0: NS_IMPL_CYCLE_COLLECTING_ADDREF(nsFrameLoader) michael@0: NS_IMPL_CYCLE_COLLECTING_RELEASE(nsFrameLoader) michael@0: michael@0: NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsFrameLoader) michael@0: NS_INTERFACE_MAP_ENTRY(nsIFrameLoader) michael@0: NS_INTERFACE_MAP_ENTRY(nsIContentViewManager) michael@0: NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIFrameLoader) michael@0: NS_INTERFACE_MAP_END michael@0: michael@0: nsFrameLoader::nsFrameLoader(Element* aOwner, bool aNetworkCreated) michael@0: : mOwnerContent(aOwner) michael@0: , mAppIdSentToPermissionManager(nsIScriptSecurityManager::NO_APP_ID) michael@0: , mDetachedSubdocViews(nullptr) michael@0: , mDepthTooGreat(false) michael@0: , mIsTopLevelContent(false) michael@0: , mDestroyCalled(false) michael@0: , mNeedsAsyncDestroy(false) michael@0: , mInSwap(false) michael@0: , mInShow(false) michael@0: , mHideCalled(false) michael@0: , mNetworkCreated(aNetworkCreated) michael@0: , mRemoteBrowserShown(false) michael@0: , mRemoteFrame(false) michael@0: , mClipSubdocument(true) michael@0: , mClampScrollPosition(true) michael@0: , mRemoteBrowserInitialized(false) michael@0: , mObservingOwnerContent(false) michael@0: , mVisible(true) michael@0: , mCurrentRemoteFrame(nullptr) michael@0: , mRemoteBrowser(nullptr) michael@0: , mChildID(0) michael@0: , mRenderMode(RENDER_MODE_DEFAULT) michael@0: , mEventMode(EVENT_MODE_NORMAL_DISPATCH) michael@0: , mPendingFrameSent(false) michael@0: { michael@0: ResetPermissionManagerStatus(); michael@0: } michael@0: michael@0: nsFrameLoader::~nsFrameLoader() michael@0: { michael@0: mNeedsAsyncDestroy = true; michael@0: if (mMessageManager) { michael@0: mMessageManager->Disconnect(); michael@0: } michael@0: nsFrameLoader::Destroy(); michael@0: } michael@0: michael@0: nsFrameLoader* michael@0: nsFrameLoader::Create(Element* aOwner, bool aNetworkCreated) michael@0: { michael@0: NS_ENSURE_TRUE(aOwner, nullptr); michael@0: nsIDocument* doc = aOwner->OwnerDoc(); michael@0: NS_ENSURE_TRUE(!doc->IsResourceDoc() && michael@0: ((!doc->IsLoadedAsData() && aOwner->GetCurrentDoc()) || michael@0: doc->IsStaticDocument()), michael@0: nullptr); michael@0: michael@0: return new nsFrameLoader(aOwner, aNetworkCreated); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFrameLoader::LoadFrame() michael@0: { michael@0: NS_ENSURE_TRUE(mOwnerContent, NS_ERROR_NOT_INITIALIZED); michael@0: michael@0: nsAutoString src; michael@0: michael@0: bool isSrcdoc = mOwnerContent->IsHTML(nsGkAtoms::iframe) && michael@0: mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc); michael@0: if (isSrcdoc) { michael@0: src.AssignLiteral("about:srcdoc"); michael@0: } michael@0: else { michael@0: GetURL(src); michael@0: michael@0: src.Trim(" \t\n\r"); michael@0: michael@0: if (src.IsEmpty()) { michael@0: // If the frame is a XUL element and has the attribute 'nodefaultsrc=true' michael@0: // then we will not use 'about:blank' as fallback but return early without michael@0: // starting a load if no 'src' attribute is given (or it's empty). michael@0: if (mOwnerContent->IsXUL() && michael@0: mOwnerContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::nodefaultsrc, michael@0: nsGkAtoms::_true, eCaseMatters)) { michael@0: return NS_OK; michael@0: } michael@0: src.AssignLiteral("about:blank"); michael@0: } michael@0: } michael@0: michael@0: nsIDocument* doc = mOwnerContent->OwnerDoc(); michael@0: if (doc->IsStaticDocument()) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsCOMPtr base_uri = mOwnerContent->GetBaseURI(); michael@0: const nsAFlatCString &doc_charset = doc->GetDocumentCharacterSet(); michael@0: const char *charset = doc_charset.IsEmpty() ? nullptr : doc_charset.get(); michael@0: michael@0: nsCOMPtr uri; michael@0: nsresult rv = NS_NewURI(getter_AddRefs(uri), src, charset, base_uri); michael@0: michael@0: // If the URI was malformed, try to recover by loading about:blank. michael@0: if (rv == NS_ERROR_MALFORMED_URI) { michael@0: rv = NS_NewURI(getter_AddRefs(uri), NS_LITERAL_STRING("about:blank"), michael@0: charset, base_uri); michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = LoadURI(uri); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: FireErrorEvent(); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFrameLoader::FireErrorEvent() michael@0: { michael@0: if (!mOwnerContent) { michael@0: return; michael@0: } michael@0: nsRefPtr loadBlockingAsyncDispatcher = michael@0: new LoadBlockingAsyncEventDispatcher(mOwnerContent, michael@0: NS_LITERAL_STRING("error"), michael@0: false, false); michael@0: loadBlockingAsyncDispatcher->PostDOMEvent(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFrameLoader::LoadURI(nsIURI* aURI) michael@0: { michael@0: if (!aURI) michael@0: return NS_ERROR_INVALID_POINTER; michael@0: NS_ENSURE_STATE(!mDestroyCalled && mOwnerContent); michael@0: michael@0: nsCOMPtr doc = mOwnerContent->OwnerDoc(); michael@0: michael@0: nsresult rv = CheckURILoad(aURI); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mURIToLoad = aURI; michael@0: rv = doc->InitializeFrameLoader(this); michael@0: if (NS_FAILED(rv)) { michael@0: mURIToLoad = nullptr; michael@0: } michael@0: return rv; michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameLoader::ReallyStartLoading() michael@0: { michael@0: nsresult rv = ReallyStartLoadingInternal(); michael@0: if (NS_FAILED(rv)) { michael@0: FireErrorEvent(); michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: class DelayedStartLoadingRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: DelayedStartLoadingRunnable(nsFrameLoader* aFrameLoader) michael@0: : mFrameLoader(aFrameLoader) michael@0: { michael@0: } michael@0: michael@0: NS_IMETHOD Run() michael@0: { michael@0: // Retry the request. michael@0: mFrameLoader->ReallyStartLoading(); michael@0: michael@0: // We delayed nsFrameLoader::ReallyStartLoading() after the child process is michael@0: // ready and might not be able to notify the remote browser in michael@0: // UpdatePositionAndSize() when reflow finished. Retrigger reflow. michael@0: nsIFrame* frame = mFrameLoader->GetPrimaryFrameOfOwningContent(); michael@0: if (!frame) { michael@0: return NS_OK; michael@0: } michael@0: frame->InvalidateFrame(); michael@0: frame->PresContext()->PresShell()-> michael@0: FrameNeedsReflow(frame, nsIPresShell::eResize, NS_FRAME_IS_DIRTY); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: private: michael@0: nsRefPtr mFrameLoader; michael@0: }; michael@0: michael@0: nsresult michael@0: nsFrameLoader::ReallyStartLoadingInternal() michael@0: { michael@0: NS_ENSURE_STATE(mURIToLoad && mOwnerContent && mOwnerContent->IsInDoc()); michael@0: michael@0: PROFILER_LABEL("nsFrameLoader", "ReallyStartLoading"); michael@0: michael@0: nsresult rv = MaybeCreateDocShell(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: michael@0: if (mRemoteFrame) { michael@0: if (!mRemoteBrowser) { michael@0: if (!mPendingFrameSent) { michael@0: nsCOMPtr os = services::GetObserverService(); michael@0: if (os && !mRemoteBrowserInitialized) { michael@0: os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this), michael@0: "remote-browser-pending", nullptr); michael@0: mPendingFrameSent = true; michael@0: } michael@0: } michael@0: if (Preferences::GetBool("dom.ipc.processPrelaunch.enabled", false) && michael@0: !ContentParent::PreallocatedProcessReady()) { michael@0: michael@0: ContentParent::RunAfterPreallocatedProcessReady( michael@0: new DelayedStartLoadingRunnable(this)); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: TryRemoteBrowser(); michael@0: michael@0: if (!mRemoteBrowser) { michael@0: NS_WARNING("Couldn't create child process for iframe."); michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: if (mRemoteBrowserShown || ShowRemoteFrame(nsIntSize(0, 0))) { michael@0: // FIXME get error codes from child michael@0: mRemoteBrowser->LoadURL(mURIToLoad); michael@0: } else { michael@0: NS_WARNING("[nsFrameLoader] ReallyStartLoadingInternal tried but couldn't show remote browser.\n"); michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ASSERTION(mDocShell, michael@0: "MaybeCreateDocShell succeeded with a null mDocShell"); michael@0: michael@0: // Just to be safe, recheck uri. michael@0: rv = CheckURILoad(mURIToLoad); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr loadInfo; michael@0: mDocShell->CreateLoadInfo(getter_AddRefs(loadInfo)); michael@0: NS_ENSURE_TRUE(loadInfo, NS_ERROR_FAILURE); michael@0: michael@0: // If this frame is sandboxed with respect to origin we will set it up with michael@0: // a null principal later in nsDocShell::DoURILoad. michael@0: // We do it there to correctly sandbox content that was loaded into michael@0: // the frame via other methods than the src attribute. michael@0: // We'll use our principal, not that of the document loaded inside us. This michael@0: // is very important; needed to prevent XSS attacks on documents loaded in michael@0: // subframes! michael@0: loadInfo->SetOwner(mOwnerContent->NodePrincipal()); michael@0: michael@0: nsCOMPtr referrer; michael@0: michael@0: nsAutoString srcdoc; michael@0: bool isSrcdoc = mOwnerContent->IsHTML(nsGkAtoms::iframe) && michael@0: mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::srcdoc, michael@0: srcdoc); michael@0: michael@0: if (isSrcdoc) { michael@0: nsAutoString referrerStr; michael@0: mOwnerContent->OwnerDoc()->GetReferrer(referrerStr); michael@0: rv = NS_NewURI(getter_AddRefs(referrer), referrerStr); michael@0: michael@0: loadInfo->SetSrcdocData(srcdoc); michael@0: nsCOMPtr baseURI = mOwnerContent->GetBaseURI(); michael@0: loadInfo->SetBaseURI(baseURI); michael@0: } michael@0: else { michael@0: rv = mOwnerContent->NodePrincipal()->GetURI(getter_AddRefs(referrer)); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Use referrer as long as it is not an nsNullPrincipalURI. michael@0: // We could add a method such as GetReferrerURI to principals to make this michael@0: // cleaner, but given that we need to start using Source Browsing Context for michael@0: // referrer (see Bug 960639) this may be wasted effort at this stage. michael@0: if (referrer) { michael@0: bool isNullPrincipalScheme; michael@0: rv = referrer->SchemeIs(NS_NULLPRINCIPAL_SCHEME, &isNullPrincipalScheme); michael@0: if (NS_SUCCEEDED(rv) && !isNullPrincipalScheme) { michael@0: loadInfo->SetReferrer(referrer); michael@0: } michael@0: } michael@0: michael@0: // Default flags: michael@0: int32_t flags = nsIWebNavigation::LOAD_FLAGS_NONE; michael@0: michael@0: // Flags for browser frame: michael@0: if (OwnerIsBrowserFrame()) { michael@0: flags = nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP | michael@0: nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_OWNER; michael@0: } michael@0: michael@0: // Kick off the load... michael@0: bool tmpState = mNeedsAsyncDestroy; michael@0: mNeedsAsyncDestroy = true; michael@0: nsCOMPtr uriToLoad = mURIToLoad; michael@0: rv = mDocShell->LoadURI(uriToLoad, loadInfo, flags, false); michael@0: mNeedsAsyncDestroy = tmpState; michael@0: mURIToLoad = nullptr; michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameLoader::CheckURILoad(nsIURI* aURI) michael@0: { michael@0: // Check for security. The fun part is trying to figure out what principals michael@0: // to use. The way I figure it, if we're doing a LoadFrame() accidentally michael@0: // (eg someone created a frame/iframe node, we're being parsed, XUL iframes michael@0: // are being reframed, etc.) then we definitely want to use the node michael@0: // principal of mOwnerContent for security checks. If, on the other hand, michael@0: // someone's setting the src on our owner content, or created it via script, michael@0: // or whatever, then they can clearly access it... and we should still use michael@0: // the principal of mOwnerContent. I don't think that leads to privilege michael@0: // escalation, and it's reasonably guaranteed to not lead to XSS issues michael@0: // (since caller can already access mOwnerContent in this case). So just use michael@0: // the principal of mOwnerContent no matter what. If script wants to run michael@0: // things with its own permissions, which differ from those of mOwnerContent michael@0: // (which means the script is privileged in some way) it should set michael@0: // window.location instead. michael@0: nsIScriptSecurityManager *secMan = nsContentUtils::GetSecurityManager(); michael@0: michael@0: // Get our principal michael@0: nsIPrincipal* principal = mOwnerContent->NodePrincipal(); michael@0: michael@0: // Check if we are allowed to load absURL michael@0: nsresult rv = michael@0: secMan->CheckLoadURIWithPrincipal(principal, aURI, michael@0: nsIScriptSecurityManager::STANDARD); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; // We're not michael@0: } michael@0: michael@0: // Bail out if this is an infinite recursion scenario michael@0: rv = MaybeCreateDocShell(); michael@0: if (NS_FAILED(rv)) { michael@0: return rv; michael@0: } michael@0: if (mRemoteFrame) { michael@0: return NS_OK; michael@0: } michael@0: return CheckForRecursiveLoad(aURI); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFrameLoader::GetDocShell(nsIDocShell **aDocShell) michael@0: { michael@0: *aDocShell = nullptr; michael@0: nsresult rv = NS_OK; michael@0: michael@0: // If we have an owner, make sure we have a docshell and return michael@0: // that. If not, we're most likely in the middle of being torn down, michael@0: // then we just return null. michael@0: if (mOwnerContent) { michael@0: nsresult rv = MaybeCreateDocShell(); michael@0: if (NS_FAILED(rv)) michael@0: return rv; michael@0: if (mRemoteFrame) { michael@0: NS_WARNING("No docshells for remote frames!"); michael@0: return rv; michael@0: } michael@0: NS_ASSERTION(mDocShell, michael@0: "MaybeCreateDocShell succeeded, but null mDocShell"); michael@0: } michael@0: michael@0: *aDocShell = mDocShell; michael@0: NS_IF_ADDREF(*aDocShell); michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: void michael@0: nsFrameLoader::Finalize() michael@0: { michael@0: nsCOMPtr base_win(do_QueryInterface(mDocShell)); michael@0: if (base_win) { michael@0: base_win->Destroy(); michael@0: } michael@0: mDocShell = nullptr; michael@0: } michael@0: michael@0: static void michael@0: FirePageHideEvent(nsIDocShellTreeItem* aItem, michael@0: EventTarget* aChromeEventHandler) michael@0: { michael@0: nsCOMPtr internalDoc = do_GetInterface(aItem); michael@0: NS_ASSERTION(internalDoc, "What happened here?"); michael@0: internalDoc->OnPageHide(true, aChromeEventHandler); michael@0: michael@0: int32_t childCount = 0; michael@0: aItem->GetChildCount(&childCount); michael@0: nsAutoTArray, 8> kids; michael@0: kids.AppendElements(childCount); michael@0: for (int32_t i = 0; i < childCount; ++i) { michael@0: aItem->GetChildAt(i, getter_AddRefs(kids[i])); michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < kids.Length(); ++i) { michael@0: if (kids[i]) { michael@0: FirePageHideEvent(kids[i], aChromeEventHandler); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // The pageshow event is fired for a given document only if IsShowing() returns michael@0: // the same thing as aFireIfShowing. This gives us a way to fire pageshow only michael@0: // on documents that are still loading or only on documents that are already michael@0: // loaded. michael@0: static void michael@0: FirePageShowEvent(nsIDocShellTreeItem* aItem, michael@0: EventTarget* aChromeEventHandler, michael@0: bool aFireIfShowing) michael@0: { michael@0: int32_t childCount = 0; michael@0: aItem->GetChildCount(&childCount); michael@0: nsAutoTArray, 8> kids; michael@0: kids.AppendElements(childCount); michael@0: for (int32_t i = 0; i < childCount; ++i) { michael@0: aItem->GetChildAt(i, getter_AddRefs(kids[i])); michael@0: } michael@0: michael@0: for (uint32_t i = 0; i < kids.Length(); ++i) { michael@0: if (kids[i]) { michael@0: FirePageShowEvent(kids[i], aChromeEventHandler, aFireIfShowing); michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr internalDoc = do_GetInterface(aItem); michael@0: NS_ASSERTION(internalDoc, "What happened here?"); michael@0: if (internalDoc->IsShowing() == aFireIfShowing) { michael@0: internalDoc->OnPageShow(true, aChromeEventHandler); michael@0: } michael@0: } michael@0: michael@0: static void michael@0: SetTreeOwnerAndChromeEventHandlerOnDocshellTree(nsIDocShellTreeItem* aItem, michael@0: nsIDocShellTreeOwner* aOwner, michael@0: EventTarget* aHandler) michael@0: { michael@0: NS_PRECONDITION(aItem, "Must have item"); michael@0: michael@0: aItem->SetTreeOwner(aOwner); michael@0: michael@0: int32_t childCount = 0; michael@0: aItem->GetChildCount(&childCount); michael@0: for (int32_t i = 0; i < childCount; ++i) { michael@0: nsCOMPtr item; michael@0: aItem->GetChildAt(i, getter_AddRefs(item)); michael@0: if (aHandler) { michael@0: nsCOMPtr shell(do_QueryInterface(item)); michael@0: shell->SetChromeEventHandler(aHandler); michael@0: } michael@0: SetTreeOwnerAndChromeEventHandlerOnDocshellTree(item, aOwner, aHandler); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Set the type of the treeitem and hook it up to the treeowner. michael@0: * @param aItem the treeitem we're working with michael@0: * @param aTreeOwner the relevant treeowner; might be null michael@0: * @param aParentType the nsIDocShellTreeItem::GetType of our parent docshell michael@0: * @param aParentNode if non-null, the docshell we should be added as a child to michael@0: * michael@0: * @return whether aItem is top-level content michael@0: */ michael@0: bool michael@0: nsFrameLoader::AddTreeItemToTreeOwner(nsIDocShellTreeItem* aItem, michael@0: nsIDocShellTreeOwner* aOwner, michael@0: int32_t aParentType, michael@0: nsIDocShell* aParentNode) michael@0: { michael@0: NS_PRECONDITION(aItem, "Must have docshell treeitem"); michael@0: NS_PRECONDITION(mOwnerContent, "Must have owning content"); michael@0: michael@0: nsAutoString value; michael@0: bool isContent = false; michael@0: mOwnerContent->GetAttr(kNameSpaceID_None, TypeAttrName(), value); michael@0: michael@0: // we accept "content" and "content-xxx" values. michael@0: // at time of writing, we expect "xxx" to be "primary" or "targetable", but michael@0: // someday it might be an integer expressing priority or something else. michael@0: michael@0: isContent = value.LowerCaseEqualsLiteral("content") || michael@0: StringBeginsWith(value, NS_LITERAL_STRING("content-"), michael@0: nsCaseInsensitiveStringComparator()); michael@0: michael@0: // Force mozbrowser frames to always be typeContent, even if the michael@0: // mozbrowser interfaces are disabled. michael@0: nsCOMPtr mozbrowser = michael@0: do_QueryInterface(mOwnerContent); michael@0: if (mozbrowser) { michael@0: bool isMozbrowser = false; michael@0: mozbrowser->GetMozbrowser(&isMozbrowser); michael@0: isContent |= isMozbrowser; michael@0: } michael@0: michael@0: if (isContent) { michael@0: // The web shell's type is content. michael@0: michael@0: aItem->SetItemType(nsIDocShellTreeItem::typeContent); michael@0: } else { michael@0: // Inherit our type from our parent docshell. If it is michael@0: // chrome, we'll be chrome. If it is content, we'll be michael@0: // content. michael@0: michael@0: aItem->SetItemType(aParentType); michael@0: } michael@0: michael@0: // Now that we have our type set, add ourselves to the parent, as needed. michael@0: if (aParentNode) { michael@0: aParentNode->AddChild(aItem); michael@0: } michael@0: michael@0: bool retval = false; michael@0: if (aParentType == nsIDocShellTreeItem::typeChrome && isContent) { michael@0: retval = true; michael@0: michael@0: bool is_primary = value.LowerCaseEqualsLiteral("content-primary"); michael@0: michael@0: if (aOwner) { michael@0: bool is_targetable = is_primary || michael@0: value.LowerCaseEqualsLiteral("content-targetable"); michael@0: mOwnerContent->AddMutationObserver(this); michael@0: mObservingOwnerContent = true; michael@0: aOwner->ContentShellAdded(aItem, is_primary, is_targetable, value); michael@0: } michael@0: } michael@0: michael@0: return retval; michael@0: } michael@0: michael@0: static bool michael@0: AllDescendantsOfType(nsIDocShellTreeItem* aParentItem, int32_t aType) michael@0: { michael@0: int32_t childCount = 0; michael@0: aParentItem->GetChildCount(&childCount); michael@0: michael@0: for (int32_t i = 0; i < childCount; ++i) { michael@0: nsCOMPtr kid; michael@0: aParentItem->GetChildAt(i, getter_AddRefs(kid)); michael@0: michael@0: if (kid->ItemType() != aType || !AllDescendantsOfType(kid, aType)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /** michael@0: * A class that automatically sets mInShow to false when it goes michael@0: * out of scope. michael@0: */ michael@0: class MOZ_STACK_CLASS AutoResetInShow { michael@0: private: michael@0: nsFrameLoader* mFrameLoader; michael@0: MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER michael@0: public: michael@0: AutoResetInShow(nsFrameLoader* aFrameLoader MOZ_GUARD_OBJECT_NOTIFIER_PARAM) michael@0: : mFrameLoader(aFrameLoader) michael@0: { michael@0: MOZ_GUARD_OBJECT_NOTIFIER_INIT; michael@0: } michael@0: ~AutoResetInShow() { mFrameLoader->mInShow = false; } michael@0: }; michael@0: michael@0: michael@0: bool michael@0: nsFrameLoader::Show(int32_t marginWidth, int32_t marginHeight, michael@0: int32_t scrollbarPrefX, int32_t scrollbarPrefY, michael@0: nsSubDocumentFrame* frame) michael@0: { michael@0: if (mInShow) { michael@0: return false; michael@0: } michael@0: // Reset mInShow if we exit early. michael@0: AutoResetInShow resetInShow(this); michael@0: mInShow = true; michael@0: michael@0: nsresult rv = MaybeCreateDocShell(); michael@0: if (NS_FAILED(rv)) { michael@0: return false; michael@0: } michael@0: michael@0: if (!mRemoteFrame) { michael@0: if (!mDocShell) michael@0: return false; michael@0: michael@0: mDocShell->SetMarginWidth(marginWidth); michael@0: mDocShell->SetMarginHeight(marginHeight); michael@0: michael@0: nsCOMPtr sc = do_QueryInterface(mDocShell); michael@0: if (sc) { michael@0: sc->SetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_X, michael@0: scrollbarPrefX); michael@0: sc->SetDefaultScrollbarPreferences(nsIScrollable::ScrollOrientation_Y, michael@0: scrollbarPrefY); michael@0: } michael@0: michael@0: nsCOMPtr presShell = mDocShell->GetPresShell(); michael@0: if (presShell) { michael@0: // Ensure root scroll frame is reflowed in case scroll preferences or michael@0: // margins have changed michael@0: nsIFrame* rootScrollFrame = presShell->GetRootScrollFrame(); michael@0: if (rootScrollFrame) { michael@0: presShell->FrameNeedsReflow(rootScrollFrame, nsIPresShell::eResize, michael@0: NS_FRAME_IS_DIRTY); michael@0: } michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: nsIntSize size = frame->GetSubdocumentSize(); michael@0: if (mRemoteFrame) { michael@0: return ShowRemoteFrame(size, frame); michael@0: } michael@0: michael@0: nsView* view = frame->EnsureInnerView(); michael@0: if (!view) michael@0: return false; michael@0: michael@0: nsCOMPtr baseWindow = do_QueryInterface(mDocShell); michael@0: NS_ASSERTION(baseWindow, "Found a nsIDocShell that isn't a nsIBaseWindow."); michael@0: baseWindow->InitWindow(nullptr, view->GetWidget(), 0, 0, michael@0: size.width, size.height); michael@0: // This is kinda whacky, this "Create()" call doesn't really michael@0: // create anything, one starts to wonder why this was named michael@0: // "Create"... michael@0: baseWindow->Create(); michael@0: baseWindow->SetVisibility(true); michael@0: NS_ENSURE_TRUE(mDocShell, false); michael@0: michael@0: // Trigger editor re-initialization if midas is turned on in the michael@0: // sub-document. This shouldn't be necessary, but given the way our michael@0: // editor works, it is. See michael@0: // https://bugzilla.mozilla.org/show_bug.cgi?id=284245 michael@0: nsCOMPtr presShell = mDocShell->GetPresShell(); michael@0: if (presShell) { michael@0: nsCOMPtr doc = michael@0: do_QueryInterface(presShell->GetDocument()); michael@0: michael@0: if (doc) { michael@0: nsAutoString designMode; michael@0: doc->GetDesignMode(designMode); michael@0: michael@0: if (designMode.EqualsLiteral("on")) { michael@0: // Hold on to the editor object to let the document reattach to the michael@0: // same editor object, instead of creating a new one. michael@0: nsCOMPtr editor; michael@0: nsresult rv = mDocShell->GetEditor(getter_AddRefs(editor)); michael@0: NS_ENSURE_SUCCESS(rv, false); michael@0: michael@0: doc->SetDesignMode(NS_LITERAL_STRING("off")); michael@0: doc->SetDesignMode(NS_LITERAL_STRING("on")); michael@0: } else { michael@0: // Re-initialize the presentation for contenteditable documents michael@0: bool editable = false, michael@0: hasEditingSession = false; michael@0: mDocShell->GetEditable(&editable); michael@0: mDocShell->GetHasEditingSession(&hasEditingSession); michael@0: nsCOMPtr editor; michael@0: mDocShell->GetEditor(getter_AddRefs(editor)); michael@0: if (editable && hasEditingSession && editor) { michael@0: editor->PostCreate(); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: mInShow = false; michael@0: if (mHideCalled) { michael@0: mHideCalled = false; michael@0: Hide(); michael@0: return false; michael@0: } michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsFrameLoader::MarginsChanged(uint32_t aMarginWidth, michael@0: uint32_t aMarginHeight) michael@0: { michael@0: // We assume that the margins are always zero for remote frames. michael@0: if (mRemoteFrame) michael@0: return; michael@0: michael@0: // If there's no docshell, we're probably not up and running yet. michael@0: // nsFrameLoader::Show() will take care of setting the right michael@0: // margins. michael@0: if (!mDocShell) michael@0: return; michael@0: michael@0: // Set the margins michael@0: mDocShell->SetMarginWidth(aMarginWidth); michael@0: mDocShell->SetMarginHeight(aMarginHeight); michael@0: michael@0: // Trigger a restyle if there's a prescontext michael@0: nsRefPtr presContext; michael@0: mDocShell->GetPresContext(getter_AddRefs(presContext)); michael@0: if (presContext) michael@0: presContext->RebuildAllStyleData(nsChangeHint(0)); michael@0: } michael@0: michael@0: bool michael@0: nsFrameLoader::ShowRemoteFrame(const nsIntSize& size, michael@0: nsSubDocumentFrame *aFrame) michael@0: { michael@0: NS_ASSERTION(mRemoteFrame, "ShowRemote only makes sense on remote frames."); michael@0: michael@0: if (!mRemoteBrowser) { michael@0: TryRemoteBrowser(); michael@0: michael@0: if (!mRemoteBrowser) { michael@0: NS_ERROR("Couldn't create child process."); michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // FIXME/bug 589337: Show()/Hide() is pretty expensive for michael@0: // cross-process layers; need to figure out what behavior we really michael@0: // want here. For now, hack. michael@0: if (!mRemoteBrowserShown) { michael@0: if (!mOwnerContent || michael@0: !mOwnerContent->GetCurrentDoc()) { michael@0: return false; michael@0: } michael@0: michael@0: nsRefPtr layerManager = michael@0: nsContentUtils::LayerManagerForDocument(mOwnerContent->GetCurrentDoc()); michael@0: if (!layerManager) { michael@0: // This is just not going to work. michael@0: return false; michael@0: } michael@0: michael@0: mRemoteBrowser->Show(size); michael@0: mRemoteBrowserShown = true; michael@0: michael@0: EnsureMessageManager(); michael@0: michael@0: nsCOMPtr os = services::GetObserverService(); michael@0: if (os && !mRemoteBrowserInitialized) { michael@0: if (!mPendingFrameSent) { michael@0: os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this), michael@0: "remote-browser-pending", nullptr); michael@0: mPendingFrameSent = true; michael@0: } michael@0: os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this), michael@0: "remote-browser-shown", nullptr); michael@0: mRemoteBrowserInitialized = true; michael@0: } michael@0: } else { michael@0: nsRect dimensions; michael@0: NS_ENSURE_SUCCESS(GetWindowDimensions(dimensions), false); michael@0: michael@0: // Don't show remote iframe if we are waiting for the completion of reflow. michael@0: if (!aFrame || !(aFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW)) { michael@0: mRemoteBrowser->UpdateDimensions(dimensions, size); michael@0: } michael@0: } michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsFrameLoader::Hide() michael@0: { michael@0: if (mHideCalled) { michael@0: return; michael@0: } michael@0: if (mInShow) { michael@0: mHideCalled = true; michael@0: return; michael@0: } michael@0: michael@0: if (!mDocShell) michael@0: return; michael@0: michael@0: nsCOMPtr contentViewer; michael@0: mDocShell->GetContentViewer(getter_AddRefs(contentViewer)); michael@0: if (contentViewer) michael@0: contentViewer->SetSticky(false); michael@0: michael@0: nsCOMPtr baseWin = do_QueryInterface(mDocShell); michael@0: NS_ASSERTION(baseWin, michael@0: "Found an nsIDocShell which doesn't implement nsIBaseWindow."); michael@0: baseWin->SetVisibility(false); michael@0: baseWin->SetParentWidget(nullptr); michael@0: } michael@0: michael@0: nsresult michael@0: nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther, michael@0: nsRefPtr& aFirstToSwap, michael@0: nsRefPtr& aSecondToSwap) michael@0: { michael@0: NS_PRECONDITION((aFirstToSwap == this && aSecondToSwap == aOther) || michael@0: (aFirstToSwap == aOther && aSecondToSwap == this), michael@0: "Swapping some sort of random loaders?"); michael@0: NS_ENSURE_STATE(!mInShow && !aOther->mInShow); michael@0: michael@0: Element* ourContent = mOwnerContent; michael@0: Element* otherContent = aOther->mOwnerContent; michael@0: michael@0: if (!ourContent || !otherContent) { michael@0: // Can't handle this michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // Make sure there are no same-origin issues michael@0: bool equal; michael@0: nsresult rv = michael@0: ourContent->NodePrincipal()->Equals(otherContent->NodePrincipal(), &equal); michael@0: if (NS_FAILED(rv) || !equal) { michael@0: // Security problems loom. Just bail on it all michael@0: return NS_ERROR_DOM_SECURITY_ERR; michael@0: } michael@0: michael@0: nsCOMPtr ourDocshell = GetExistingDocShell(); michael@0: nsCOMPtr otherDocshell = aOther->GetExistingDocShell(); michael@0: if (!ourDocshell || !otherDocshell) { michael@0: // How odd michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // To avoid having to mess with session history, avoid swapping michael@0: // frameloaders that don't correspond to root same-type docshells, michael@0: // unless both roots have session history disabled. michael@0: nsCOMPtr ourRootTreeItem, otherRootTreeItem; michael@0: ourDocshell->GetSameTypeRootTreeItem(getter_AddRefs(ourRootTreeItem)); michael@0: otherDocshell->GetSameTypeRootTreeItem(getter_AddRefs(otherRootTreeItem)); michael@0: nsCOMPtr ourRootWebnav = michael@0: do_QueryInterface(ourRootTreeItem); michael@0: nsCOMPtr otherRootWebnav = michael@0: do_QueryInterface(otherRootTreeItem); michael@0: michael@0: if (!ourRootWebnav || !otherRootWebnav) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: nsCOMPtr ourHistory; michael@0: nsCOMPtr otherHistory; michael@0: ourRootWebnav->GetSessionHistory(getter_AddRefs(ourHistory)); michael@0: otherRootWebnav->GetSessionHistory(getter_AddRefs(otherHistory)); michael@0: michael@0: if ((ourRootTreeItem != ourDocshell || otherRootTreeItem != otherDocshell) && michael@0: (ourHistory || otherHistory)) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // Also make sure that the two docshells are the same type. Otherwise michael@0: // swapping is certainly not safe. If this needs to be changed then michael@0: // the code below needs to be audited as it assumes identical types. michael@0: int32_t ourType = ourDocshell->ItemType(); michael@0: int32_t otherType = otherDocshell->ItemType(); michael@0: if (ourType != otherType) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // One more twist here. Setting up the right treeowners in a heterogeneous michael@0: // tree is a bit of a pain. So make sure that if ourType is not michael@0: // nsIDocShellTreeItem::typeContent then all of our descendants are the same michael@0: // type as us. michael@0: if (ourType != nsIDocShellTreeItem::typeContent && michael@0: (!AllDescendantsOfType(ourDocshell, ourType) || michael@0: !AllDescendantsOfType(otherDocshell, otherType))) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // Save off the tree owners, frame elements, chrome event handlers, and michael@0: // docshell and document parents before doing anything else. michael@0: nsCOMPtr ourOwner, otherOwner; michael@0: ourDocshell->GetTreeOwner(getter_AddRefs(ourOwner)); michael@0: otherDocshell->GetTreeOwner(getter_AddRefs(otherOwner)); michael@0: // Note: it's OK to have null treeowners. michael@0: michael@0: nsCOMPtr ourParentItem, otherParentItem; michael@0: ourDocshell->GetParent(getter_AddRefs(ourParentItem)); michael@0: otherDocshell->GetParent(getter_AddRefs(otherParentItem)); michael@0: if (!ourParentItem || !otherParentItem) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // Make sure our parents are the same type too michael@0: int32_t ourParentType = ourParentItem->ItemType(); michael@0: int32_t otherParentType = otherParentItem->ItemType(); michael@0: if (ourParentType != otherParentType) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: nsCOMPtr ourWindow = do_GetInterface(ourDocshell); michael@0: nsCOMPtr otherWindow = do_GetInterface(otherDocshell); michael@0: michael@0: nsCOMPtr ourFrameElement = michael@0: ourWindow->GetFrameElementInternal(); michael@0: nsCOMPtr otherFrameElement = michael@0: otherWindow->GetFrameElementInternal(); michael@0: michael@0: nsCOMPtr ourChromeEventHandler = michael@0: do_QueryInterface(ourWindow->GetChromeEventHandler()); michael@0: nsCOMPtr otherChromeEventHandler = michael@0: do_QueryInterface(otherWindow->GetChromeEventHandler()); michael@0: michael@0: NS_ASSERTION(SameCOMIdentity(ourFrameElement, ourContent) && michael@0: SameCOMIdentity(otherFrameElement, otherContent) && michael@0: SameCOMIdentity(ourChromeEventHandler, ourContent) && michael@0: SameCOMIdentity(otherChromeEventHandler, otherContent), michael@0: "How did that happen, exactly?"); michael@0: michael@0: nsCOMPtr ourChildDocument = ourWindow->GetExtantDoc(); michael@0: nsCOMPtr otherChildDocument = otherWindow ->GetExtantDoc(); michael@0: if (!ourChildDocument || !otherChildDocument) { michael@0: // This shouldn't be happening michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: nsCOMPtr ourParentDocument = michael@0: ourChildDocument->GetParentDocument(); michael@0: nsCOMPtr otherParentDocument = michael@0: otherChildDocument->GetParentDocument(); michael@0: michael@0: // Make sure to swap docshells between the two frames. michael@0: nsIDocument* ourDoc = ourContent->GetCurrentDoc(); michael@0: nsIDocument* otherDoc = otherContent->GetCurrentDoc(); michael@0: if (!ourDoc || !otherDoc) { michael@0: // Again, how odd, given that we had docshells michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: NS_ASSERTION(ourDoc == ourParentDocument, "Unexpected parent document"); michael@0: NS_ASSERTION(otherDoc == otherParentDocument, "Unexpected parent document"); michael@0: michael@0: nsIPresShell* ourShell = ourDoc->GetShell(); michael@0: nsIPresShell* otherShell = otherDoc->GetShell(); michael@0: if (!ourShell || !otherShell) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: if (ourDocshell->GetIsBrowserElement() != michael@0: otherDocshell->GetIsBrowserElement() || michael@0: ourDocshell->GetIsApp() != otherDocshell->GetIsApp()) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: if (mInSwap || aOther->mInSwap) { michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: mInSwap = aOther->mInSwap = true; michael@0: michael@0: // Fire pageshow events on still-loading pages, and then fire pagehide michael@0: // events. Note that we do NOT fire these in the normal way, but just fire michael@0: // them on the chrome event handlers. michael@0: FirePageShowEvent(ourDocshell, ourChromeEventHandler, false); michael@0: FirePageShowEvent(otherDocshell, otherChromeEventHandler, false); michael@0: FirePageHideEvent(ourDocshell, ourChromeEventHandler); michael@0: FirePageHideEvent(otherDocshell, otherChromeEventHandler); michael@0: michael@0: nsIFrame* ourFrame = ourContent->GetPrimaryFrame(); michael@0: nsIFrame* otherFrame = otherContent->GetPrimaryFrame(); michael@0: if (!ourFrame || !otherFrame) { michael@0: mInSwap = aOther->mInSwap = false; michael@0: FirePageShowEvent(ourDocshell, ourChromeEventHandler, true); michael@0: FirePageShowEvent(otherDocshell, otherChromeEventHandler, true); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: nsSubDocumentFrame* ourFrameFrame = do_QueryFrame(ourFrame); michael@0: if (!ourFrameFrame) { michael@0: mInSwap = aOther->mInSwap = false; michael@0: FirePageShowEvent(ourDocshell, ourChromeEventHandler, true); michael@0: FirePageShowEvent(otherDocshell, otherChromeEventHandler, true); michael@0: return NS_ERROR_NOT_IMPLEMENTED; michael@0: } michael@0: michael@0: // OK. First begin to swap the docshells in the two nsIFrames michael@0: rv = ourFrameFrame->BeginSwapDocShells(otherFrame); michael@0: if (NS_FAILED(rv)) { michael@0: mInSwap = aOther->mInSwap = false; michael@0: FirePageShowEvent(ourDocshell, ourChromeEventHandler, true); michael@0: FirePageShowEvent(otherDocshell, otherChromeEventHandler, true); michael@0: return rv; michael@0: } michael@0: michael@0: // Now move the docshells to the right docshell trees. Note that this michael@0: // resets their treeowners to null. michael@0: ourParentItem->RemoveChild(ourDocshell); michael@0: otherParentItem->RemoveChild(otherDocshell); michael@0: if (ourType == nsIDocShellTreeItem::typeContent) { michael@0: ourOwner->ContentShellRemoved(ourDocshell); michael@0: otherOwner->ContentShellRemoved(otherDocshell); michael@0: } michael@0: michael@0: ourParentItem->AddChild(otherDocshell); michael@0: otherParentItem->AddChild(ourDocshell); michael@0: michael@0: // Restore the correct chrome event handlers. michael@0: ourDocshell->SetChromeEventHandler(otherChromeEventHandler); michael@0: otherDocshell->SetChromeEventHandler(ourChromeEventHandler); michael@0: // Restore the correct treeowners michael@0: // (and also chrome event handlers for content frames only). michael@0: SetTreeOwnerAndChromeEventHandlerOnDocshellTree(ourDocshell, otherOwner, michael@0: ourType == nsIDocShellTreeItem::typeContent ? otherChromeEventHandler : nullptr); michael@0: SetTreeOwnerAndChromeEventHandlerOnDocshellTree(otherDocshell, ourOwner, michael@0: ourType == nsIDocShellTreeItem::typeContent ? ourChromeEventHandler : nullptr); michael@0: michael@0: // Switch the owner content before we start calling AddTreeItemToTreeOwner. michael@0: // Note that we rely on this to deal with setting mObservingOwnerContent to michael@0: // false and calling RemoveMutationObserver as needed. michael@0: SetOwnerContent(otherContent); michael@0: aOther->SetOwnerContent(ourContent); michael@0: michael@0: AddTreeItemToTreeOwner(ourDocshell, otherOwner, otherParentType, nullptr); michael@0: aOther->AddTreeItemToTreeOwner(otherDocshell, ourOwner, ourParentType, michael@0: nullptr); michael@0: michael@0: // SetSubDocumentFor nulls out parent documents on the old child doc if a michael@0: // new non-null document is passed in, so just go ahead and remove both michael@0: // kids before reinserting in the parent subdoc maps, to avoid michael@0: // complications. michael@0: ourParentDocument->SetSubDocumentFor(ourContent, nullptr); michael@0: otherParentDocument->SetSubDocumentFor(otherContent, nullptr); michael@0: ourParentDocument->SetSubDocumentFor(ourContent, otherChildDocument); michael@0: otherParentDocument->SetSubDocumentFor(otherContent, ourChildDocument); michael@0: michael@0: ourWindow->SetFrameElementInternal(otherFrameElement); michael@0: otherWindow->SetFrameElementInternal(ourFrameElement); michael@0: michael@0: nsRefPtr ourMessageManager = mMessageManager; michael@0: nsRefPtr otherMessageManager = aOther->mMessageManager; michael@0: // Swap pointers in child message managers. michael@0: if (mChildMessageManager) { michael@0: nsInProcessTabChildGlobal* tabChild = michael@0: static_cast(mChildMessageManager.get()); michael@0: tabChild->SetOwner(otherContent); michael@0: tabChild->SetChromeMessageManager(otherMessageManager); michael@0: } michael@0: if (aOther->mChildMessageManager) { michael@0: nsInProcessTabChildGlobal* otherTabChild = michael@0: static_cast(aOther->mChildMessageManager.get()); michael@0: otherTabChild->SetOwner(ourContent); michael@0: otherTabChild->SetChromeMessageManager(ourMessageManager); michael@0: } michael@0: // Swap and setup things in parent message managers. michael@0: if (mMessageManager) { michael@0: mMessageManager->SetCallback(aOther); michael@0: } michael@0: if (aOther->mMessageManager) { michael@0: aOther->mMessageManager->SetCallback(this); michael@0: } michael@0: mMessageManager.swap(aOther->mMessageManager); michael@0: michael@0: aFirstToSwap.swap(aSecondToSwap); michael@0: michael@0: // Drop any cached content viewers in the two session histories. michael@0: nsCOMPtr ourInternalHistory = michael@0: do_QueryInterface(ourHistory); michael@0: nsCOMPtr otherInternalHistory = michael@0: do_QueryInterface(otherHistory); michael@0: if (ourInternalHistory) { michael@0: ourInternalHistory->EvictAllContentViewers(); michael@0: } michael@0: if (otherInternalHistory) { michael@0: otherInternalHistory->EvictAllContentViewers(); michael@0: } michael@0: michael@0: NS_ASSERTION(ourFrame == ourContent->GetPrimaryFrame() && michael@0: otherFrame == otherContent->GetPrimaryFrame(), michael@0: "changed primary frame"); michael@0: michael@0: ourFrameFrame->EndSwapDocShells(otherFrame); michael@0: michael@0: // If the content being swapped came from windows on two screens with michael@0: // incompatible backing resolution (e.g. dragging a tab between windows on michael@0: // hi-dpi and low-dpi screens), it will have style data that is based on michael@0: // the wrong appUnitsPerDevPixel value. So we tell the PresShells that their michael@0: // backing scale factor may have changed. (Bug 822266) michael@0: ourShell->BackingScaleFactorChanged(); michael@0: otherShell->BackingScaleFactorChanged(); michael@0: michael@0: ourParentDocument->FlushPendingNotifications(Flush_Layout); michael@0: otherParentDocument->FlushPendingNotifications(Flush_Layout); michael@0: michael@0: FirePageShowEvent(ourDocshell, otherChromeEventHandler, true); michael@0: FirePageShowEvent(otherDocshell, ourChromeEventHandler, true); michael@0: michael@0: mInSwap = aOther->mInSwap = false; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFrameLoader::DestroyChild() michael@0: { michael@0: if (mRemoteBrowser) { michael@0: mRemoteBrowser->SetOwnerElement(nullptr); michael@0: mRemoteBrowser->Destroy(); michael@0: mRemoteBrowser = nullptr; michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFrameLoader::Destroy() michael@0: { michael@0: if (mDestroyCalled) { michael@0: return NS_OK; michael@0: } michael@0: mDestroyCalled = true; michael@0: michael@0: if (mMessageManager) { michael@0: mMessageManager->Disconnect(); michael@0: } michael@0: if (mChildMessageManager) { michael@0: static_cast(mChildMessageManager.get())->Disconnect(); michael@0: } michael@0: michael@0: nsCOMPtr doc; michael@0: bool dynamicSubframeRemoval = false; michael@0: if (mOwnerContent) { michael@0: doc = mOwnerContent->OwnerDoc(); michael@0: dynamicSubframeRemoval = !mIsTopLevelContent && !doc->InUnlinkOrDeletion(); michael@0: doc->SetSubDocumentFor(mOwnerContent, nullptr); michael@0: michael@0: SetOwnerContent(nullptr); michael@0: } michael@0: DestroyChild(); michael@0: michael@0: // Seems like this is a dynamic frame removal. michael@0: if (dynamicSubframeRemoval) { michael@0: if (mDocShell) { michael@0: mDocShell->RemoveFromSessionHistory(); michael@0: } michael@0: } michael@0: michael@0: // Let the tree owner know we're gone. michael@0: if (mIsTopLevelContent) { michael@0: if (mDocShell) { michael@0: nsCOMPtr parentItem; michael@0: mDocShell->GetParent(getter_AddRefs(parentItem)); michael@0: nsCOMPtr owner = do_GetInterface(parentItem); michael@0: if (owner) { michael@0: owner->ContentShellRemoved(mDocShell); michael@0: } michael@0: } michael@0: } michael@0: michael@0: // Let our window know that we are gone michael@0: nsCOMPtr win_private(do_GetInterface(mDocShell)); michael@0: if (win_private) { michael@0: win_private->SetFrameElementInternal(nullptr); michael@0: } michael@0: michael@0: if ((mNeedsAsyncDestroy || !doc || michael@0: NS_FAILED(doc->FinalizeFrameLoader(this))) && mDocShell) { michael@0: nsCOMPtr event = new nsAsyncDocShellDestroyer(mDocShell); michael@0: NS_ENSURE_TRUE(event, NS_ERROR_OUT_OF_MEMORY); michael@0: NS_DispatchToCurrentThread(event); michael@0: michael@0: // Let go of our docshell now that the async destroyer holds on to michael@0: // the docshell. michael@0: michael@0: mDocShell = nullptr; michael@0: } michael@0: michael@0: // NOTE: 'this' may very well be gone by now. michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsFrameLoader::GetDepthTooGreat(bool* aDepthTooGreat) michael@0: { michael@0: *aDepthTooGreat = mDepthTooGreat; michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsFrameLoader::SetOwnerContent(Element* aContent) michael@0: { michael@0: if (mObservingOwnerContent) { michael@0: mObservingOwnerContent = false; michael@0: mOwnerContent->RemoveMutationObserver(this); michael@0: } michael@0: mOwnerContent = aContent; michael@0: if (RenderFrameParent* rfp = GetCurrentRemoteFrame()) { michael@0: rfp->OwnerContentChanged(aContent); michael@0: } michael@0: michael@0: ResetPermissionManagerStatus(); michael@0: } michael@0: michael@0: bool michael@0: nsFrameLoader::OwnerIsBrowserOrAppFrame() michael@0: { michael@0: nsCOMPtr browserFrame = do_QueryInterface(mOwnerContent); michael@0: return browserFrame ? browserFrame->GetReallyIsBrowserOrApp() : false; michael@0: } michael@0: michael@0: // The xpcom getter version michael@0: NS_IMETHODIMP michael@0: nsFrameLoader::GetOwnerIsBrowserOrAppFrame(bool* aResult) michael@0: { michael@0: *aResult = OwnerIsBrowserOrAppFrame(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsFrameLoader::OwnerIsAppFrame() michael@0: { michael@0: nsCOMPtr browserFrame = do_QueryInterface(mOwnerContent); michael@0: return browserFrame ? browserFrame->GetReallyIsApp() : false; michael@0: } michael@0: michael@0: bool michael@0: nsFrameLoader::OwnerIsBrowserFrame() michael@0: { michael@0: return OwnerIsBrowserOrAppFrame() && !OwnerIsAppFrame(); michael@0: } michael@0: michael@0: void michael@0: nsFrameLoader::GetOwnerAppManifestURL(nsAString& aOut) michael@0: { michael@0: aOut.Truncate(); michael@0: nsCOMPtr browserFrame = do_QueryInterface(mOwnerContent); michael@0: if (browserFrame) { michael@0: browserFrame->GetAppManifestURL(aOut); michael@0: } michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsFrameLoader::GetOwnApp() michael@0: { michael@0: nsAutoString manifest; michael@0: GetOwnerAppManifestURL(manifest); michael@0: if (manifest.IsEmpty()) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr appsService = do_GetService(APPS_SERVICE_CONTRACTID); michael@0: NS_ENSURE_TRUE(appsService, nullptr); michael@0: michael@0: nsCOMPtr app; michael@0: appsService->GetAppByManifestURL(manifest, getter_AddRefs(app)); michael@0: michael@0: return app.forget(); michael@0: } michael@0: michael@0: already_AddRefed michael@0: nsFrameLoader::GetContainingApp() michael@0: { michael@0: // See if our owner content's principal has an associated app. michael@0: uint32_t appId = mOwnerContent->NodePrincipal()->GetAppId(); michael@0: MOZ_ASSERT(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID); michael@0: michael@0: if (appId == nsIScriptSecurityManager::NO_APP_ID || michael@0: appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) { michael@0: return nullptr; michael@0: } michael@0: michael@0: nsCOMPtr appsService = do_GetService(APPS_SERVICE_CONTRACTID); michael@0: NS_ENSURE_TRUE(appsService, nullptr); michael@0: michael@0: nsCOMPtr app; michael@0: appsService->GetAppByLocalId(appId, getter_AddRefs(app)); michael@0: michael@0: return app.forget(); michael@0: } michael@0: michael@0: bool michael@0: nsFrameLoader::ShouldUseRemoteProcess() michael@0: { michael@0: if (PR_GetEnv("MOZ_DISABLE_OOP_TABS") || michael@0: Preferences::GetBool("dom.ipc.tabs.disabled", false)) { michael@0: return false; michael@0: } michael@0: michael@0: // If we're inside a content process, don't use a remote process for this michael@0: // frame; it won't work properly until bug 761935 is fixed. michael@0: if (XRE_GetProcessType() == GeckoProcessType_Content) { michael@0: return false; michael@0: } michael@0: michael@0: // If we're an