Bug 775965 - Ensure presentation persists across nsSubDocumentFrame reframes. r=roc

This commit is contained in:
Chris Pearce 2012-08-14 16:06:44 +12:00
parent f78206195e
commit 274d465a14
7 changed files with 196 additions and 36 deletions

View File

@ -288,6 +288,7 @@ NS_INTERFACE_MAP_END
nsFrameLoader::nsFrameLoader(Element* aOwner, bool aNetworkCreated)
: mOwnerContent(aOwner)
, mDetachedSubdocViews(nullptr)
, mDepthTooGreat(false)
, mIsTopLevelContent(false)
, mDestroyCalled(false)
@ -2377,3 +2378,18 @@ nsFrameLoader::SetRemoteBrowser(nsITabParent* aTabParent)
ShowRemoteFrame(nsIntSize(0, 0));
}
void
nsFrameLoader::SetDetachedSubdocView(nsIView* aDetachedViews,
nsIDocument* aContainerDoc)
{
mDetachedSubdocViews = aDetachedViews;
mContainerDocWhileDetached = aContainerDoc;
}
nsIView*
nsFrameLoader::GetDetachedSubdocView(nsIDocument** aContainerDoc) const
{
NS_IF_ADDREF(*aContainerDoc = mContainerDocWhileDetached);
return mDetachedSubdocViews;
}

View File

@ -268,6 +268,25 @@ public:
*/
void SetRemoteBrowser(nsITabParent* aTabParent);
/**
* Stashes a detached view on the frame loader. We do this when we're
* destroying the nsSubDocumentFrame. If the nsSubdocumentFrame is
* being reframed we'll restore the detached view when it's recreated,
* otherwise we'll discard the old presentation and set the detached
* subdoc view to null. aContainerDoc is the document containing the
* the subdoc frame. This enables us to detect when the containing
* document has changed during reframe, so we can discard the presentation
* in that case.
*/
void SetDetachedSubdocView(nsIView* aDetachedView,
nsIDocument* aContainerDoc);
/**
* Retrieves the detached view and the document containing the view,
* as set by SetDetachedSubdocView().
*/
nsIView* GetDetachedSubdocView(nsIDocument** aContainerDoc) const;
private:
void SetOwnerContent(mozilla::dom::Element* aContent);
@ -326,6 +345,16 @@ public:
nsRefPtr<nsFrameMessageManager> mMessageManager;
nsCOMPtr<nsIInProcessContentFrameMessageManager> mChildMessageManager;
private:
// Stores the root view of the subdocument while the subdocument is being
// reframed. Used to restore the presentation after reframing.
nsIView* mDetachedSubdocViews;
// Stores the containing document of the frame corresponding to this
// frame loader. This is reference is kept valid while the subframe's
// presentation is detached and stored in mDetachedSubdocViews. This
// enables us to detect whether the frame has moved documents during
// a reframe, so that we know not to restore the presentation.
nsCOMPtr<nsIDocument> mContainerDocWhileDetached;
bool mDepthTooGreat : 1;
bool mIsTopLevelContent : 1;
bool mDestroyCalled : 1;

View File

@ -738,32 +738,26 @@ void
nsObjectFrame::SetInstanceOwner(nsPluginInstanceOwner* aOwner)
{
mInstanceOwner = aOwner;
if (!mInstanceOwner) {
nsRootPresContext* rpc = PresContext()->GetRootPresContext();
if (rpc) {
if (mWidget) {
if (mInnerView) {
mInnerView->DetachWidgetEventHandler(mWidget);
if (mInstanceOwner) {
return;
}
nsRootPresContext* rpc = PresContext()->GetRootPresContext();
if (rpc) {
rpc->UnregisterPluginForGeometryUpdates(mContent);
}
if (mWidget && mInnerView) {
mInnerView->DetachWidgetEventHandler(mWidget);
// Make sure the plugin is hidden in case an update of plugin geometry
// hasn't happened since this plugin became hidden.
nsIWidget* parent = mWidget->GetParent();
if (parent) {
nsTArray<nsIWidget::Configuration> configurations;
this->GetEmptyClipConfiguration(&configurations);
parent->ConfigureChildren(configurations);
rpc->UnregisterPluginForGeometryUpdates(mContent);
// Make sure the plugin is hidden in case an update of plugin geometry
// hasn't happened since this plugin became hidden.
nsIWidget* parent = mWidget->GetParent();
if (parent) {
nsTArray<nsIWidget::Configuration> configurations;
this->GetEmptyClipConfiguration(&configurations);
parent->ConfigureChildren(configurations);
mWidget->Show(false);
mWidget->Enable(false);
mWidget->SetParent(nullptr);
}
}
} else {
#ifndef XP_MACOSX
rpc->UnregisterPluginForGeometryUpdates(mContent);
#endif
}
mWidget->Show(false);
mWidget->Enable(false);
mWidget->SetParent(nullptr);
}
}
}

View File

@ -117,6 +117,12 @@ private:
nsWeakFrame mFrame;
};
static void
InsertViewsInReverseOrder(nsIView* aSibling, nsIView* aParent);
static void
EndSwapDocShellsForViews(nsIView* aView);
NS_IMETHODIMP
nsSubDocumentFrame::Init(nsIContent* aContent,
nsIFrame* aParent,
@ -146,6 +152,28 @@ nsSubDocumentFrame::Init(nsIContent* aContent,
}
EnsureInnerView();
// If we have a detached subdoc's root view on our frame loader, re-insert
// it into the view tree. This happens when we've been reframed, and
// ensures the presentation persists across reframes. If the frame element
// has changed documents however, we blow away the presentation.
nsRefPtr<nsFrameLoader> frameloader = FrameLoader();
if (frameloader) {
nsCOMPtr<nsIDocument> oldContainerDoc;
nsIView* detachedViews =
frameloader->GetDetachedSubdocView(getter_AddRefs(oldContainerDoc));
if (detachedViews) {
if (oldContainerDoc == aContent->OwnerDoc()) {
// Restore stashed presentation.
::InsertViewsInReverseOrder(detachedViews, mInnerView);
::EndSwapDocShellsForViews(mInnerView->GetFirstChild());
} else {
// Presentation is for a different document, don't restore it.
frameloader->Hide();
}
}
frameloader->SetDetachedSubdocView(nullptr, nullptr);
}
// Set the primary frame now so that
// DocumentViewerImpl::FindContainerView called by ShowViewer below
// can find it if necessary.
@ -758,6 +786,49 @@ NS_NewSubDocumentFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
NS_IMPL_FRAMEARENA_HELPERS(nsSubDocumentFrame)
class nsHideViewer : public nsRunnable {
public:
nsHideViewer(nsIContent* aFrameElement,
nsFrameLoader* aFrameLoader,
nsIPresShell* aPresShell,
bool aHideViewerIfFrameless)
: mFrameElement(aFrameElement),
mFrameLoader(aFrameLoader),
mPresShell(aPresShell),
mHideViewerIfFrameless(aHideViewerIfFrameless)
{
NS_ASSERTION(mFrameElement, "Must have a frame element");
NS_ASSERTION(mFrameLoader, "Must have a frame loader");
NS_ASSERTION(mPresShell, "Must have a presshell");
}
NS_IMETHOD Run()
{
// Flush frames, to ensure any pending display:none changes are made.
// Note it can be unsafe to flush if we've destroyed the presentation
// for some other reason, like if we're shutting down.
if (!mPresShell->IsDestroying()) {
mPresShell->FlushPendingNotifications(Flush_Frames);
}
nsIFrame* frame = mFrameElement->GetPrimaryFrame();
if (!frame && mHideViewerIfFrameless) {
// The frame element has no nsIFrame. Hide the nsFrameLoader,
// which destroys the presentation.
mFrameLoader->SetDetachedSubdocView(nullptr, nullptr);
mFrameLoader->Hide();
}
return NS_OK;
}
private:
nsCOMPtr<nsIContent> mFrameElement;
nsRefPtr<nsFrameLoader> mFrameLoader;
nsCOMPtr<nsIPresShell> mPresShell;
bool mHideViewerIfFrameless;
};
static nsIView*
BeginSwapDocShellsForViews(nsIView* aSibling);
void
nsSubDocumentFrame::DestroyFrom(nsIFrame* aDestructRoot)
{
@ -765,19 +836,27 @@ nsSubDocumentFrame::DestroyFrom(nsIFrame* aDestructRoot)
PresContext()->PresShell()->CancelReflowCallback(this);
mPostedReflowCallback = false;
}
HideViewer();
// Detach the subdocument's views and stash them in the frame loader.
// We can then reattach them if we're being reframed (for example if
// the frame has been made position:fixed).
nsFrameLoader* frameloader = FrameLoader();
if (frameloader) {
nsIView* detachedViews = ::BeginSwapDocShellsForViews(mInnerView->GetFirstChild());
frameloader->SetDetachedSubdocView(detachedViews, mContent->OwnerDoc());
// We call nsFrameLoader::HideViewer() in a script runner so that we can
// safely determine whether the frame is being reframed or destroyed.
nsContentUtils::AddScriptRunner(
new nsHideViewer(mContent,
mFrameLoader,
PresContext()->PresShell(),
(mDidCreateDoc || mCallingShow)));
}
nsLeafFrame::DestroyFrom(aDestructRoot);
}
void
nsSubDocumentFrame::HideViewer()
{
if (mFrameLoader && (mDidCreateDoc || mCallingShow))
mFrameLoader->Hide();
}
nsIntSize
nsSubDocumentFrame::GetMarginAttributes()
{

View File

@ -119,8 +119,9 @@ protected:
virtual int GetSkipSides() const;
// Hide or show our document viewer
void HideViewer();
// Show our document viewer. The document viewer is hidden via a script
// runner, so that we can save and restore the presentation if we're
// being reframed.
void ShowViewer();
/* Obtains the frame we should use for intrinsic size information if we are

View File

@ -14,6 +14,7 @@ include $(DEPTH)/config/autoconf.mk
# in the list below, we make sure that the tests that require focus
# run before test_plugin_clipping, which can steal focus for its window.
MOCHITEST_FILES = \
test_contained_plugin_transplant.html \
bug344830_testembed.svg \
plugin_clipping_helper.xhtml \
plugin_clipping_helper2.xhtml \

View File

@ -0,0 +1,40 @@
<!DOCTYPE HTML>
<html>
<!--
https://bugzilla.mozilla.org/show_bug.cgi?id=775965
-->
<head>
<meta charset="utf-8">
<title>Test for Bug 775965</title>
<script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body onload="run();">
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=775965">Mozilla Bug 775965</a>
<p id="display"></p>
<div id="content">
<iframe id="iframe1" src="data:text/html,<embed type='application/x-test' width='200' height='200'></embed>"></iframe>
<iframe id="iframe2"></iframe>
</div>
<pre id="test">
<script type="application/javascript">
/** Test for Bug 775965 **/
SimpleTest.waitForExplicitFinish();
function run() {
var iframe1 = document.getElementById("iframe1");
var iframe2 = document.getElementById("iframe2");
iframe1.parentNode.removeChild(iframe1);
iframe1.style.position = "fixed";
iframe2.contentDocument.body.appendChild(iframe1);
setTimeout(function() { ok(true, "We didn't crash!"); SimpleTest.finish(); }, 0);
}
</script>
</pre>
</body>
</html>