Bug 113934. Backend and some minimal front end for dragging tabs between windows. r=gavin, r+sr=jst

This commit is contained in:
Boris Zbarsky
2008-08-07 19:15:40 -04:00
parent c3aef812ca
commit e9a90efb27
16 changed files with 723 additions and 120 deletions

View File

@@ -22,6 +22,7 @@
*
* Contributor(s):
* Johnny Stenback <jst@netscape.com> (original author)
* Boris Zbarsky <bzbarsky@mit.edu>
*
* 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"),
@@ -65,6 +66,12 @@
#include "nsIScriptSecurityManager.h"
#include "nsFrameLoader.h"
#include "nsIDOMEventTarget.h"
#include "nsIFrame.h"
#include "nsIFrameFrame.h"
#include "nsDOMError.h"
#include "nsPresShellIterator.h"
#include "nsGUIEvent.h"
#include "nsEventDispatcher.h"
#include "nsIURI.h"
#include "nsIURL.h"
@@ -281,6 +288,367 @@ nsFrameLoader::Finalize()
mDocShell = nsnull;
}
static void
FirePageHideEvent(nsIDocShellTreeItem* aItem,
nsIDOMEventTarget* aChromeEventHandler)
{
nsPageTransitionEvent event(PR_TRUE, NS_PAGE_HIDE, PR_TRUE);
nsCOMPtr<nsIDOMDocument> doc = do_GetInterface(aItem);
event.target = do_QueryInterface(doc);
nsEventDispatcher::Dispatch(aChromeEventHandler, nsnull, &event);
PRInt32 childCount = 0;
aItem->GetChildCount(&childCount);
nsAutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> kids;
kids.AppendElements(childCount);
for (PRInt32 i = 0; i < childCount; ++i) {
aItem->GetChildAt(i, getter_AddRefs(kids[i]));
}
for (PRUint32 i = 0; i < kids.Length(); ++i) {
if (kids[i]) {
FirePageHideEvent(kids[i], aChromeEventHandler);
}
}
}
static void
FirePageShowEvent(nsIDocShellTreeItem* aItem,
nsIDOMEventTarget* aChromeEventHandler)
{
PRInt32 childCount = 0;
aItem->GetChildCount(&childCount);
nsAutoTArray<nsCOMPtr<nsIDocShellTreeItem>, 8> kids;
kids.AppendElements(childCount);
for (PRInt32 i = 0; i < childCount; ++i) {
aItem->GetChildAt(i, getter_AddRefs(kids[i]));
}
for (PRUint32 i = 0; i < kids.Length(); ++i) {
if (kids[i]) {
FirePageShowEvent(kids[i], aChromeEventHandler);
}
}
nsPageTransitionEvent event(PR_TRUE, NS_PAGE_SHOW, PR_TRUE);
nsCOMPtr<nsIDOMDocument> doc = do_GetInterface(aItem);
event.target = do_QueryInterface(doc);
nsEventDispatcher::Dispatch(aChromeEventHandler, nsnull, &event);
}
static void
SetTreeOwnerAndChromeEventHandlerOnDocshellTree(nsIDocShellTreeItem* aItem,
nsIDocShellTreeOwner* aOwner,
nsIDOMEventTarget* aHandler)
{
NS_PRECONDITION(aItem, "Must have item");
#ifdef DEBUG
PRInt32 itemType;
aItem->GetItemType(&itemType);
NS_ASSERTION(itemType == nsIDocShellTreeItem::typeContent,
"How did something else get in here?");
#endif
aItem->SetTreeOwner(aOwner);
nsCOMPtr<nsIDocShell> shell(do_QueryInterface(aItem));
shell->SetChromeEventHandler(aHandler);
PRInt32 childCount = 0;
aItem->GetChildCount(&childCount);
for (PRInt32 i = 0; i < childCount; ++i) {
nsCOMPtr<nsIDocShellTreeItem> item;
aItem->GetChildAt(i, getter_AddRefs(item));
SetTreeOwnerAndChromeEventHandlerOnDocshellTree(item, aOwner, aHandler);
}
}
/**
* Set the type of the treeitem and hook it up to the treeowner.
* @param aItem the treeitem we're wrking working with
* @param aOwningContent the content node that owns aItem
* @param aTreeOwner the relevant treeowner; might be null
* @param aParentType the nsIDocShellTreeItem::GetType of our parent docshell
* @param aParentNode if non-null, the docshell we should be added as a child to
*
* @return whether aItem is top-level content
*/
static PRBool
AddTreeItemToTreeOwner(nsIDocShellTreeItem* aItem, nsIContent* aOwningContent,
nsIDocShellTreeOwner* aOwner, PRInt32 aParentType,
nsIDocShellTreeNode* aParentNode)
{
NS_PRECONDITION(aItem, "Must have docshell treeitem");
NS_PRECONDITION(aOwningContent, "Must have owning content");
nsAutoString value;
PRBool isContent = PR_FALSE;
if (aOwningContent->IsNodeOfType(nsINode::eXUL)) {
aOwningContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value);
}
// we accept "content" and "content-xxx" values.
// at time of writing, we expect "xxx" to be "primary" or "targetable", but
// someday it might be an integer expressing priority or something else.
isContent = value.LowerCaseEqualsLiteral("content") ||
StringBeginsWith(value, NS_LITERAL_STRING("content-"),
nsCaseInsensitiveStringComparator());
if (isContent) {
// The web shell's type is content.
aItem->SetItemType(nsIDocShellTreeItem::typeContent);
} else {
// Inherit our type from our parent webshell. If it is
// chrome, we'll be chrome. If it is content, we'll be
// content.
aItem->SetItemType(aParentType);
}
// Now that we have our type set, add ourselves to the parent, as needed.
if (aParentNode) {
aParentNode->AddChild(aItem);
}
PRBool retval = PR_FALSE;
if (aParentType == nsIDocShellTreeItem::typeChrome && isContent) {
retval = PR_TRUE;
PRBool is_primary = value.LowerCaseEqualsLiteral("content-primary");
if (aOwner) {
PRBool is_targetable = is_primary ||
value.LowerCaseEqualsLiteral("content-targetable");
aOwner->ContentShellAdded(aItem, is_primary, is_targetable, value);
}
}
return retval;
}
nsresult
nsFrameLoader::SwapWithOtherLoader(nsFrameLoader* aOther,
nsRefPtr<nsFrameLoader>& aFirstToSwap,
nsRefPtr<nsFrameLoader>& aSecondToSwap)
{
NS_PRECONDITION((aFirstToSwap == this && aSecondToSwap == aOther) ||
(aFirstToSwap == aOther && aSecondToSwap == this),
"Swapping some sort of random loaders?");
nsIContent* ourContent = mOwnerContent;
nsIContent* otherContent = aOther->mOwnerContent;
if (!ourContent || !otherContent) {
// Can't handle this
return NS_ERROR_NOT_IMPLEMENTED;
}
// Make sure there are no same-origin issues
PRBool equal;
nsresult rv =
ourContent->NodePrincipal()->Equals(otherContent->NodePrincipal(), &equal);
if (NS_FAILED(rv) || !equal) {
// Security problems loom. Just bail on it all
return NS_ERROR_DOM_SECURITY_ERR;
}
nsCOMPtr<nsIDocShell> ourDochell = GetExistingDocShell();
nsCOMPtr<nsIDocShell> otherDocshell = aOther->GetExistingDocShell();
if (!ourDochell || !otherDocshell) {
// How odd
return NS_ERROR_NOT_IMPLEMENTED;
}
// To avoid having to mess with session history, avoid swapping
// frameloaders that don't correspond to root same-type docshells.
nsCOMPtr<nsIDocShellTreeItem> ourTreeItem = do_QueryInterface(ourDochell);
nsCOMPtr<nsIDocShellTreeItem> otherTreeItem =
do_QueryInterface(otherDocshell);
nsCOMPtr<nsIDocShellTreeItem> ourRootTreeItem, otherRootTreeItem;
ourTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(ourRootTreeItem));
otherTreeItem->GetSameTypeRootTreeItem(getter_AddRefs(otherRootTreeItem));
if (ourRootTreeItem != ourTreeItem || otherRootTreeItem != otherTreeItem) {
return NS_ERROR_NOT_IMPLEMENTED;
}
// Also make sure that the two docshells are the same type. Otherwise
// swapping is certainly not safe. As far as that goes, make sure we only
// swap typeContent docshells, since otherwise it's hard to get treeowners
// right.
PRInt32 ourType = nsIDocShellTreeItem::typeChrome;
PRInt32 otherType = nsIDocShellTreeItem::typeChrome;
ourTreeItem->GetItemType(&ourType);
otherTreeItem->GetItemType(&otherType);
if (ourType != nsIDocShellTreeItem::typeContent ||
otherType != nsIDocShellTreeItem::typeContent) {
return NS_ERROR_NOT_IMPLEMENTED;
}
// Save off the tree owners, frame elements, chrome event handlers, and
// docshell and document parents before doing anything else.
nsCOMPtr<nsIDocShellTreeOwner> ourOwner, otherOwner;
ourTreeItem->GetTreeOwner(getter_AddRefs(ourOwner));
otherTreeItem->GetTreeOwner(getter_AddRefs(otherOwner));
// Note: it's OK to have null treeowners.
nsCOMPtr<nsIDocShellTreeItem> ourParentItem, otherParentItem;
ourTreeItem->GetParent(getter_AddRefs(ourParentItem));
otherTreeItem->GetParent(getter_AddRefs(otherParentItem));
if (!ourParentItem || !otherParentItem) {
return NS_ERROR_NOT_IMPLEMENTED;
}
nsCOMPtr<nsPIDOMWindow> ourWindow = do_GetInterface(ourDochell);
nsCOMPtr<nsPIDOMWindow> otherWindow = do_GetInterface(otherDocshell);
nsCOMPtr<nsIDOMElement> ourFrameElement =
ourWindow->GetFrameElementInternal();
nsCOMPtr<nsIDOMElement> otherFrameElement =
otherWindow->GetFrameElementInternal();
nsCOMPtr<nsIDOMEventTarget> ourChromeEventHandler =
do_QueryInterface(ourWindow->GetChromeEventHandler());
nsCOMPtr<nsIDOMEventTarget> otherChromeEventHandler =
do_QueryInterface(otherWindow->GetChromeEventHandler());
NS_ASSERTION(SameCOMIdentity(ourFrameElement, ourContent) &&
SameCOMIdentity(otherFrameElement, otherContent) &&
SameCOMIdentity(ourChromeEventHandler, ourContent) &&
SameCOMIdentity(otherChromeEventHandler, otherContent),
"How did that happen, exactly?");
nsCOMPtr<nsIDocument> ourChildDocument =
do_QueryInterface(ourWindow->GetExtantDocument());
nsCOMPtr<nsIDocument> otherChildDocument =
do_QueryInterface(otherWindow->GetExtantDocument());
if (!ourChildDocument || !otherChildDocument) {
// This shouldn't be happening
return NS_ERROR_NOT_IMPLEMENTED;
}
nsCOMPtr<nsIDocument> ourParentDocument =
ourChildDocument->GetParentDocument();
nsCOMPtr<nsIDocument> otherParentDocument =
otherChildDocument->GetParentDocument();
// Make sure to swap docshells between the two frames.
nsIDocument* ourDoc = ourContent->GetCurrentDoc();
nsIDocument* otherDoc = otherContent->GetCurrentDoc();
if (!ourDoc || !otherDoc) {
// Again, how odd, given that we had docshells
return NS_ERROR_NOT_IMPLEMENTED;
}
NS_ASSERTION(ourDoc == ourParentDocument, "Unexpected parent document");
NS_ASSERTION(otherDoc == otherParentDocument, "Unexpected parent document");
nsPresShellIterator iter1(ourDoc);
nsPresShellIterator iter2(otherDoc);
if (iter1.HasMoreThanOneShell() || iter2.HasMoreThanOneShell()) {
return NS_ERROR_NOT_IMPLEMENTED;
}
nsIPresShell* ourShell = ourDoc->GetPrimaryShell();
nsIPresShell* otherShell = otherDoc->GetPrimaryShell();
if (!ourShell || !otherShell) {
return NS_ERROR_NOT_IMPLEMENTED;
}
if (mInSwap || aOther->mInSwap) {
return NS_ERROR_NOT_IMPLEMENTED;
}
mInSwap = aOther->mInSwap = PR_TRUE;
// Fire pagehide events. Note that we do NOT fire these in the normal way,
// but just fire them on the chrome event handlers.
FirePageHideEvent(ourTreeItem, ourChromeEventHandler);
FirePageHideEvent(otherTreeItem, otherChromeEventHandler);
nsIFrame* ourFrame = ourShell->GetPrimaryFrameFor(ourContent);
nsIFrame* otherFrame = otherShell->GetPrimaryFrameFor(otherContent);
if (!ourFrame || !otherFrame) {
mInSwap = aOther->mInSwap = PR_FALSE;
FirePageShowEvent(ourTreeItem, ourChromeEventHandler);
FirePageShowEvent(otherTreeItem, otherChromeEventHandler);
return NS_ERROR_NOT_IMPLEMENTED;
}
nsIFrameFrame* ourFrameFrame = nsnull;
CallQueryInterface(ourFrame, &ourFrameFrame);
if (!ourFrameFrame) {
mInSwap = aOther->mInSwap = PR_FALSE;
FirePageShowEvent(ourTreeItem, ourChromeEventHandler);
FirePageShowEvent(otherTreeItem, otherChromeEventHandler);
return NS_ERROR_NOT_IMPLEMENTED;
}
// OK. First begin to swap the docshells in the two nsIFrames
rv = ourFrameFrame->BeginSwapDocShells(otherFrame);
if (NS_FAILED(rv)) {
mInSwap = aOther->mInSwap = PR_FALSE;
FirePageShowEvent(ourTreeItem, ourChromeEventHandler);
FirePageShowEvent(otherTreeItem, otherChromeEventHandler);
return rv;
}
// Now move the docshells to the right docshell trees. Note that this
// resets their treeowners to null.
ourParentItem->RemoveChild(ourTreeItem);
otherParentItem->RemoveChild(otherTreeItem);
if (ourType == nsIDocShellTreeItem::typeContent) {
ourOwner->ContentShellRemoved(ourTreeItem);
otherOwner->ContentShellRemoved(otherTreeItem);
}
ourParentItem->AddChild(otherTreeItem);
otherParentItem->AddChild(ourTreeItem);
// Restore the correct treeowners
SetTreeOwnerAndChromeEventHandlerOnDocshellTree(ourTreeItem, otherOwner,
otherChromeEventHandler);
SetTreeOwnerAndChromeEventHandlerOnDocshellTree(otherTreeItem, ourOwner,
ourChromeEventHandler);
AddTreeItemToTreeOwner(ourTreeItem, otherContent, otherOwner,
nsIDocShellTreeItem::typeChrome, nsnull);
AddTreeItemToTreeOwner(otherTreeItem, ourContent, ourOwner,
nsIDocShellTreeItem::typeChrome, nsnull);
// SetSubDocumentFor nulls out parent documents on the old child doc if a
// new non-null document is passed in, so just go ahead and remove both
// kids before reinserting in the parent subdoc maps, to avoid
// complications.
ourParentDocument->SetSubDocumentFor(ourContent, nsnull);
otherParentDocument->SetSubDocumentFor(otherContent, nsnull);
ourParentDocument->SetSubDocumentFor(ourContent, otherChildDocument);
otherParentDocument->SetSubDocumentFor(otherContent, ourChildDocument);
ourWindow->SetFrameElementInternal(otherFrameElement);
otherWindow->SetFrameElementInternal(ourFrameElement);
mOwnerContent = otherContent;
aOther->mOwnerContent = ourContent;
aFirstToSwap.swap(aSecondToSwap);
// We shouldn't have changed frames, but be really careful about it
if (ourFrame == ourShell->GetPrimaryFrameFor(ourContent) &&
otherFrame == otherShell->GetPrimaryFrameFor(otherContent)) {
ourFrameFrame->EndSwapDocShells(otherFrame);
}
ourParentDocument->FlushPendingNotifications(Flush_Layout);
otherParentDocument->FlushPendingNotifications(Flush_Layout);
FirePageShowEvent(ourTreeItem, otherChromeEventHandler);
FirePageShowEvent(otherTreeItem, ourChromeEventHandler);
mInSwap = aOther->mInSwap = PR_FALSE;
return NS_OK;
}
NS_IMETHODIMP
nsFrameLoader::Destroy()
{
@@ -402,52 +770,13 @@ nsFrameLoader::EnsureDocShell()
PRInt32 parentType;
parentAsItem->GetItemType(&parentType);
nsAutoString value;
PRBool isContent = PR_FALSE;
if (mOwnerContent->IsNodeOfType(nsINode::eXUL)) {
mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::type, value);
}
// we accept "content" and "content-xxx" values.
// at time of writing, we expect "xxx" to be "primary" or "targetable", but
// someday it might be an integer expressing priority or something else.
isContent = value.LowerCaseEqualsLiteral("content") ||
StringBeginsWith(value, NS_LITERAL_STRING("content-"),
nsCaseInsensitiveStringComparator());
if (isContent) {
// The web shell's type is content.
docShellAsItem->SetItemType(nsIDocShellTreeItem::typeContent);
} else {
// Inherit our type from our parent webshell. If it is
// chrome, we'll be chrome. If it is content, we'll be
// content.
docShellAsItem->SetItemType(parentType);
}
parentAsNode->AddChild(docShellAsItem);
if (parentType == nsIDocShellTreeItem::typeChrome && isContent) {
mIsTopLevelContent = PR_TRUE;
// XXXbz why is this in content code, exactly? We should handle
// this some other way..... Not sure how yet.
nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner;
parentAsItem->GetTreeOwner(getter_AddRefs(parentTreeOwner));
PRBool is_primary = value.LowerCaseEqualsLiteral("content-primary");
if (parentTreeOwner) {
PRBool is_targetable = is_primary ||
value.LowerCaseEqualsLiteral("content-targetable");
parentTreeOwner->ContentShellAdded(docShellAsItem, is_primary,
is_targetable, value);
}
}
// XXXbz why is this in content code, exactly? We should handle
// this some other way..... Not sure how yet.
nsCOMPtr<nsIDocShellTreeOwner> parentTreeOwner;
parentAsItem->GetTreeOwner(getter_AddRefs(parentTreeOwner));
mIsTopLevelContent =
AddTreeItemToTreeOwner(docShellAsItem, mOwnerContent, parentTreeOwner,
parentType, parentAsNode);
// Make sure all shells have links back to the content element
// in the nearest enclosing chrome shell.