Bug 449734 part 1 - Preserve presentation when dragging a tab between browser windows. r=roc a=blocking2.0:betaN

This commit is contained in:
Mats Palmgren 2010-09-18 13:28:49 +02:00
parent 2ec6264e84
commit bdfed9190d
9 changed files with 316 additions and 13 deletions

View File

@ -184,6 +184,7 @@ _BROWSER_FILES = \
browser_sanitizeDialog.js \
browser_scope.js \
browser_selectTabAtIndex.js \
browser_tab_dragdrop.js \
browser_tabfocus.js \
browser_tabs_owner.js \
browser_visibleTabs.js \

View File

@ -0,0 +1,114 @@
function test()
{
var embed = '<embed type="application/x-test" allowscriptaccess="always" allowfullscreen="true" wmode="window" width="640" height="480"></embed>'
waitForExplicitFinish();
// create a few tabs
var tabs = [
gBrowser.tabs[0],
gBrowser.addTab("about:blank", {skipAnimation: true}),
gBrowser.addTab("about:blank", {skipAnimation: true}),
gBrowser.addTab("about:blank", {skipAnimation: true}),
gBrowser.addTab("about:blank", {skipAnimation: true})
];
function setLocation(i, url) {
gBrowser.getBrowserForTab(tabs[i]).contentWindow.location = url;
}
function moveTabTo(a, b) {
gBrowser.swapBrowsersAndCloseOther(gBrowser.tabs[b], gBrowser.tabs[a]);
}
function clickTest(doc, win) {
var clicks = doc.defaultView.clicks;
EventUtils.synthesizeMouse(doc.body, 100, 600, {}, win);
is(doc.defaultView.clicks, clicks+1, "adding 1 more click on BODY");
}
function test1() {
moveTabTo(2, 3); // now: 0 1 2 4
is(gBrowser.tabs[1], tabs[1], "tab1");
is(gBrowser.tabs[2], tabs[3], "tab3");
var plugin = gBrowser.getBrowserForTab(tabs[4]).docShell.contentViewer.DOMDocument.wrappedJSObject.body.firstChild;
var tab4_plugin_object = plugin.getObjectValue();
gBrowser.selectedTab = gBrowser.tabs[2];
moveTabTo(3, 2); // now: 0 1 4
gBrowser.selectedTab = tabs[4];
var doc = gBrowser.getBrowserForTab(gBrowser.tabs[2]).docShell.contentViewer.DOMDocument.wrappedJSObject;
plugin = doc.body.firstChild;
ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
is(gBrowser.tabs[1], tabs[1], "tab1");
is(gBrowser.tabs[2], tabs[3], "tab4");
is(doc.defaultView.clicks, 0, "no click on BODY so far");
clickTest(doc, window);
moveTabTo(2, 1); // now: 0 4
is(gBrowser.tabs[1], tabs[1], "tab1");
doc = gBrowser.getBrowserForTab(gBrowser.tabs[1]).docShell.contentViewer.DOMDocument.wrappedJSObject;
plugin = doc.body.firstChild;
ok(plugin && plugin.checkObjectValue(tab4_plugin_object), "same plugin instance");
clickTest(doc, window);
// Load a new document (about:blank) in tab4, then detach that tab into a new window.
// In the new window, navigate back to the original document and click on its <body>,
// verify that its onclick was called.
var t = tabs[1];
var b = gBrowser.getBrowserForTab(t);
gBrowser.selectedTab = t;
b.addEventListener("load", function() {
b.removeEventListener("load", arguments.callee, true);
executeSoon(function () {
var win = gBrowser.replaceTabWithWindow(t);
win.addEventListener("load", function () {
win.removeEventListener("load", arguments.callee, true);
// Verify that the original window now only has the initial tab left in it.
is(gBrowser.tabs[0], tabs[0], "tab0");
is(gBrowser.getBrowserForTab(gBrowser.tabs[0]).contentWindow.location, "about:blank", "tab0 uri");
executeSoon(function () {
win.gBrowser.addEventListener("pageshow", function () {
win.gBrowser.removeEventListener("pageshow", arguments.callee, false);
executeSoon(function () {
t = win.gBrowser.tabs[0];
b = win.gBrowser.getBrowserForTab(t);
var doc = b.docShell.contentViewer.DOMDocument.wrappedJSObject;
clickTest(doc, win);
win.close();
finish();
});
}, false);
win.gBrowser.goBack();
});
}, true);
});
}, true);
b.loadURI("about:blank");
}
var loads = 0;
function waitForLoad(tab) {
gBrowser.getBrowserForTab(gBrowser.tabs[tab]).removeEventListener("load", arguments.callee, true);
++loads;
if (loads == tabs.length - 1) {
executeSoon(test1);
}
}
function fn(f, arg) {
return function () { return f(arg); };
}
for (var i = 1; i < tabs.length; ++i) {
gBrowser.getBrowserForTab(tabs[i]).addEventListener("load", fn(waitForLoad,i), true);
}
setLocation(1, "data:text/html,<title>tab1</title><body>tab1<iframe>");
setLocation(2, "data:text/plain,tab2");
setLocation(3, "data:text/html,<title>tab3</title><body>tab3<iframe>");
setLocation(4, "data:text/html,<body onload='clicks=0' onclick='++clicks'>"+embed);
gBrowser.selectedTab = tabs[3];
}

View File

@ -1047,15 +1047,15 @@ nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther,
otherInternalHistory->EvictAllContentViewers();
}
// We shouldn't have changed frames, but be really careful about it
if (ourFrame == ourContent->GetPrimaryFrame() &&
otherFrame == otherContent->GetPrimaryFrame()) {
ourFrameFrame->EndSwapDocShells(otherFrame);
}
NS_ASSERTION(ourFrame == ourContent->GetPrimaryFrame() &&
otherFrame == otherContent->GetPrimaryFrame(),
"changed primary frame");
ourFrameFrame->EndSwapDocShells(otherFrame);
ourParentDocument->FlushPendingNotifications(Flush_Layout);
otherParentDocument->FlushPendingNotifications(Flush_Layout);
FirePageShowEvent(ourTreeItem, otherChromeEventHandler, PR_TRUE);
FirePageShowEvent(otherTreeItem, ourChromeEventHandler, PR_TRUE);

View File

@ -270,6 +270,14 @@ public:
nscolor FindOpaqueColorCovering(nsDisplayListBuilder* aBuilder,
ThebesLayer* aLayer, const nsRect& aRect);
/**
* Destroy any stored DisplayItemDataProperty for aFrame.
*/
static void DestroyDisplayItemDataFor(nsIFrame* aFrame)
{
aFrame->Properties().Delete(DisplayItemDataProperty());
}
/**
* Clip represents the intersection of an optional rectangle with a
* list of rounded rectangles.

View File

@ -28,7 +28,7 @@
* Robert O'Callahan <roc+moz@cs.cmu.edu>
* Christian Biesinger <cbiesinger@web.de>
* Josh Aas <josh@mozilla.com>
* Mats Palmgren <mats.palmgren@bredband.net>
* Mats Palmgren <matspal@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
@ -2634,6 +2634,53 @@ nsObjectFrame::GetNextObjectFrame(nsPresContext* aPresContext, nsIFrame* aRoot)
return nsnull;
}
/*static*/ void
nsObjectFrame::BeginSwapDocShells(nsIContent* aContent, void*)
{
NS_PRECONDITION(aContent, "");
// This function is called from a document content enumerator so we need
// to filter out the nsObjectFrames and ignore the rest.
nsIObjectFrame* obj = do_QueryFrame(aContent->GetPrimaryFrame());
if (!obj)
return;
nsObjectFrame* objectFrame = static_cast<nsObjectFrame*>(obj);
NS_ASSERTION(!objectFrame->mWidget || objectFrame->mWidget->GetParent(),
"Plugin windows must not be toplevel");
nsRootPresContext* rootPC = objectFrame->PresContext()->GetRootPresContext();
NS_ASSERTION(rootPC, "unable to unregister the plugin frame");
rootPC->UnregisterPluginForGeometryUpdates(objectFrame);
}
/*static*/ void
nsObjectFrame::EndSwapDocShells(nsIContent* aContent, void*)
{
NS_PRECONDITION(aContent, "");
// This function is called from a document content enumerator so we need
// to filter out the nsObjectFrames and ignore the rest.
nsIObjectFrame* obj = do_QueryFrame(aContent->GetPrimaryFrame());
if (!obj)
return;
nsObjectFrame* objectFrame = static_cast<nsObjectFrame*>(obj);
nsRootPresContext* rootPC = objectFrame->PresContext()->GetRootPresContext();
NS_ASSERTION(rootPC, "unable to register the plugin frame");
nsIWidget* widget = objectFrame->GetWidget();
if (widget) {
// Reparent the widget.
nsIWidget* parent =
rootPC->PresShell()->GetRootFrame()->GetNearestWidget();
widget->SetParent(parent);
objectFrame->CallSetWindow();
// Register for geometry updates and make a request.
rootPC->RegisterPluginForGeometryUpdates(objectFrame);
rootPC->RequestUpdatePluginGeometry(objectFrame);
}
}
nsIFrame*
NS_NewObjectFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
{

View File

@ -185,6 +185,19 @@ public:
ImageContainer* GetImageContainer();
/**
* If aContent has a nsObjectFrame, then prepare it for a DocShell swap.
* @see nsSubDocumentFrame::BeginSwapDocShells.
* There will be a call to EndSwapDocShells after we were moved to the
* new view tree.
*/
static void BeginSwapDocShells(nsIContent* aContent, void*);
/**
* If aContent has a nsObjectFrame, then set it up after a DocShell swap.
* @see nsSubDocumentFrame::EndSwapDocShells.
*/
static void EndSwapDocShells(nsIContent* aContent, void*);
protected:
nsObjectFrame(nsStyleContext* aContext);
virtual ~nsObjectFrame();

View File

@ -22,6 +22,7 @@
* Contributor(s):
* Travis Bogard <travis@netscape.com>
* HÂkan Waara <hwaara@chello.se>
* Mats Palmgren <matspal@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either of the GNU General Public License Version 2 or later (the "GPL"),
@ -92,6 +93,8 @@ using mozilla::layout::RenderFrameParent;
#include "nsIScrollableFrame.h"
#include "nsIObjectLoadingContent.h"
#include "nsLayoutUtils.h"
#include "FrameLayerBuilder.h"
#include "nsObjectFrame.h"
#ifdef MOZ_XUL
#include "nsXULPopupManager.h"
@ -103,6 +106,18 @@ using mozilla::layout::RenderFrameParent;
#endif
#include "nsIServiceManager.h"
using namespace mozilla;
static nsIDocument*
GetDocumentFromView(nsIView* aView)
{
NS_PRECONDITION(aView, "");
nsIFrame* f = static_cast<nsIFrame*>(aView->GetClientData());
nsIPresShell* ps = f ? f->PresContext()->PresShell() : nsnull;
return ps ? ps->GetDocument() : nsnull;
}
class AsyncFrameInit;
nsSubDocumentFrame::nsSubDocumentFrame(nsStyleContext* aContext)
@ -823,6 +838,75 @@ nsSubDocumentFrame::GetDocShell(nsIDocShell **aDocShell)
return mFrameLoader->GetDocShell(aDocShell);
}
static void
DestroyDisplayItemDataForFrames(nsIFrame* aFrame)
{
FrameLayerBuilder::DestroyDisplayItemDataFor(aFrame);
PRInt32 listIndex = 0;
nsIAtom* childList = nsnull;
do {
nsIFrame* child = aFrame->GetFirstChild(childList);
while (child) {
DestroyDisplayItemDataForFrames(child);
child = child->GetNextSibling();
}
childList = aFrame->GetAdditionalChildListName(listIndex++);
} while (childList);
}
static PRBool
BeginSwapDocShellsForDocument(nsIDocument* aDocument, void*)
{
NS_PRECONDITION(aDocument, "");
nsIPresShell* shell = aDocument->GetShell();
nsIFrame* rootFrame = shell ? shell->GetRootFrame() : nsnull;
if (rootFrame) {
::DestroyDisplayItemDataForFrames(rootFrame);
}
aDocument->EnumerateFreezableElements(
nsObjectFrame::BeginSwapDocShells, nsnull);
aDocument->EnumerateSubDocuments(BeginSwapDocShellsForDocument, nsnull);
return PR_TRUE;
}
static nsIView*
BeginSwapDocShellsForViews(nsIView* aSibling)
{
// Collect the removed sibling views in reverse order in 'removedViews'.
nsIView* removedViews = nsnull;
while (aSibling) {
nsIDocument* doc = ::GetDocumentFromView(aSibling);
if (doc) {
::BeginSwapDocShellsForDocument(doc, nsnull);
}
nsIView* next = aSibling->GetNextSibling();
aSibling->GetViewManager()->RemoveChild(aSibling);
aSibling->SetNextSibling(removedViews);
removedViews = aSibling;
aSibling = next;
}
return removedViews;
}
static void
InsertViewsInReverseOrder(nsIView* aSibling, nsIView* aParent)
{
NS_PRECONDITION(aParent, "");
NS_PRECONDITION(!aParent->GetFirstChild(), "inserting into non-empty list");
nsIViewManager* vm = aParent->GetViewManager();
while (aSibling) {
nsIView* next = aSibling->GetNextSibling();
aSibling->SetNextSibling(nsnull);
// PR_TRUE means 'after' in document order which is 'before' in view order,
// so this call prepends the child, thus reversing the siblings as we go.
vm->InsertChild(aParent, aSibling, nsnull, PR_TRUE);
aSibling = next;
}
}
nsresult
nsSubDocumentFrame::BeginSwapDocShells(nsIFrame* aOther)
{
@ -836,21 +920,54 @@ nsSubDocumentFrame::BeginSwapDocShells(nsIFrame* aOther)
return NS_ERROR_NOT_IMPLEMENTED;
}
HideViewer();
other->HideViewer();
if (mInnerView && other->mInnerView) {
nsIView* ourSubdocViews = mInnerView->GetFirstChild();
nsIView* ourRemovedViews = ::BeginSwapDocShellsForViews(ourSubdocViews);
nsIView* otherSubdocViews = other->mInnerView->GetFirstChild();
nsIView* otherRemovedViews = ::BeginSwapDocShellsForViews(otherSubdocViews);
::InsertViewsInReverseOrder(ourRemovedViews, other->mInnerView);
::InsertViewsInReverseOrder(otherRemovedViews, mInnerView);
}
mFrameLoader.swap(other->mFrameLoader);
return NS_OK;
}
static PRBool
EndSwapDocShellsForDocument(nsIDocument* aDocument, void*)
{
NS_PRECONDITION(aDocument, "");
aDocument->EnumerateFreezableElements(
nsObjectFrame::EndSwapDocShells, nsnull);
aDocument->EnumerateSubDocuments(EndSwapDocShellsForDocument, nsnull);
return PR_TRUE;
}
static void
EndSwapDocShellsForViews(nsIView* aSibling)
{
for ( ; aSibling; aSibling = aSibling->GetNextSibling()) {
nsIDocument* doc = ::GetDocumentFromView(aSibling);
if (doc) {
::EndSwapDocShellsForDocument(doc, nsnull);
}
}
}
void
nsSubDocumentFrame::EndSwapDocShells(nsIFrame* aOther)
{
nsSubDocumentFrame* other = static_cast<nsSubDocumentFrame*>(aOther);
nsWeakFrame weakThis(this);
nsWeakFrame weakOther(aOther);
ShowViewer();
other->ShowViewer();
if (mInnerView) {
::EndSwapDocShellsForViews(mInnerView->GetFirstChild());
}
if (other->mInnerView) {
::EndSwapDocShellsForViews(other->mInnerView->GetFirstChild());
}
// Now make sure we reflow both frames, in case their contents
// determine their size.

View File

@ -248,6 +248,9 @@ public:
* @result view's next sibling
*/
nsIView* GetNextSibling() const { return reinterpret_cast<nsIView*>(mNextSibling); }
void SetNextSibling(nsIView *aSibling) {
mNextSibling = reinterpret_cast<nsView*>(aSibling);
}
/**
* Set the view's link to client owned data.

View File

@ -21,7 +21,7 @@
* are Copyright (C) 2001 the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Mats Palmgren <mats.palmgren@bredband.net>
* Mats Palmgren <matspal@gmail.com>
* Masayuki Nakano <masayuki@d-toybox.com>
*
* Alternatively, the contents of this file may be used under the terms of
@ -890,7 +890,7 @@ nsWindow::SetParent(nsIWidget *aNewParent)
SetWidgetForHierarchy(mGdkWindow, oldContainer, newContainer);
}
gdk_window_reparent(mGdkWindow, newParentWindow, 0, 0);
gdk_window_reparent(mGdkWindow, newParentWindow, mBounds.x, mBounds.y);
}
PRBool parentHasMappedToplevel =