mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 07:42:04 +00:00
Bug 449734 part 1 - Preserve presentation when dragging a tab between browser windows. r=roc a=blocking2.0:betaN
This commit is contained in:
parent
2ec6264e84
commit
bdfed9190d
@ -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 \
|
||||
|
114
browser/base/content/test/browser_tab_dragdrop.js
Normal file
114
browser/base/content/test/browser_tab_dragdrop.js
Normal 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];
|
||||
|
||||
}
|
@ -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);
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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)
|
||||
{
|
||||
|
@ -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();
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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 =
|
||||
|
Loading…
Reference in New Issue
Block a user