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-11 09:38:43 +01:00
parent 48f4d946e8
commit f6213f8f02
16 changed files with 723 additions and 120 deletions

View File

@ -278,7 +278,6 @@
mTab: aTab,
mBrowser: aBrowser,
mBlank: aStartsBlank,
mLastURI: null,
// cache flags for correct status bar update after tab switching
mStateFlags: 0,
@ -1389,6 +1388,18 @@
<method name="removeTab">
<parameter name="aTab"/>
<body>
<![CDATA[
this._endRemoveTab(this._beginRemoveTab(aTab, true));
]]>
</body>
</method>
<!-- Returns the tab being removed. This might not be the same as aTab,
in cases when aTab is not actually a tab -->
<method name="_beginRemoveTab">
<parameter name="aTab"/>
<parameter name="aFireBeforeUnload"/>
<body>
<![CDATA[
this._browsers = null; // invalidate cache
@ -1403,9 +1414,11 @@
return;
}
var ds = this.getBrowserForTab(aTab).docShell;
if (ds.contentViewer && !ds.contentViewer.permitUnload())
return;
if (aFireBeforeUnload) {
var ds = this.getBrowserForTab(aTab).docShell;
if (ds.contentViewer && !ds.contentViewer.permitUnload())
return;
}
// see notes in addTab
var _delayedUpdate = function(aTabContainer) {
@ -1435,15 +1448,7 @@
evt.initEvent("TabClose", true, false);
aTab.dispatchEvent(evt);
var index = -1;
if (this.mCurrentTab == aTab)
index = this.mTabContainer.selectedIndex;
else {
// Find and locate the tab in our list.
for (var i = 0; i < l; i++)
if (this.mTabContainer.childNodes[i] == aTab)
index = i;
}
var index = aTab._tPos;
// Remove the tab's filter and progress listener.
const filter = this.mTabFilters[index];
@ -1459,22 +1464,34 @@
// We are no longer the primary content area.
oldBrowser.setAttribute("type", "content-targetable");
// Get the index of the tab we're removing before unselecting it
var currentIndex = this.mTabContainer.selectedIndex;
var oldTab = aTab;
// clean up the before/afterselected attributes before removing the tab
oldTab._selected = false;
// Remove this tab as the owner of any other tabs, since it's going away.
for (i = 0; i < this.mTabContainer.childNodes.length; ++i) {
for (var i = 0; i < this.mTabs.length; ++i) {
var tab = this.mTabContainer.childNodes[i];
if ("owner" in tab && tab.owner == oldTab)
if ("owner" in tab && tab.owner == aTab)
// |tab| is a child of the tab we're removing, make it an orphan
tab.owner = null;
}
return aTab;
]]>
</body>
</method>
<method name="_endRemoveTab">
<parameter name="aTab"/>
<body>
<![CDATA[
var browser = this.getBrowserForTab(aTab);
var length = this.mTabs.length;
// Get the index of the tab we're removing before unselecting it
var currentIndex = this.mTabContainer.selectedIndex;
var index = aTab._tPos;
// clean up the before/afterselected attributes before removing the
// tab. But make sure this happens after we grab currentIndex.
aTab._selected = false;
// Because of the way XBL works (fields just set JS
// properties on the element) and the code we have in place
// to preserve the JS objects for any elements that have
@ -1485,16 +1502,18 @@
// XBL implementation of nsIObserver still works. But
// clearing focusedWindow happens below because it gets
// reset by updateCurrentBrowser.
oldBrowser.destroy();
browser.destroy();
if (oldBrowser == this.mCurrentBrowser)
if (browser == this.mCurrentBrowser)
this.mCurrentBrowser = null;
// Remove the tab
this.mTabContainer.removeChild(oldTab);
this.mTabContainer.removeChild(aTab);
// Update our length
--length;
// invalidate cache, because mTabContainer is about to change
this._browsers = null;
this.mPanelContainer.removeChild(oldBrowser.parentNode);
this.mPanelContainer.removeChild(browser.parentNode);
try {
// if we're at the right side (and not the logical end,
@ -1523,24 +1542,24 @@
else if (currentIndex < index)
newIndex = currentIndex;
else {
if ("owner" in oldTab && oldTab.owner &&
if ("owner" in aTab && aTab.owner &&
this.mPrefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
for (i = 0; i < this.mTabContainer.childNodes.length; ++i) {
for (var i = 0; i < length; ++i) {
tab = this.mTabContainer.childNodes[i];
if (tab == oldTab.owner) {
if (tab == aTab.owner) {
newIndex = i;
break;
}
}
}
if (newIndex == -1)
newIndex = (index == l - 1) ? index - 1 : index;
newIndex = (index == length) ? index - 1 : index;
}
// Select the new tab
this.selectedTab = this.mTabContainer.childNodes[newIndex];
this.selectedTab = this.mTabs[newIndex];
for (i = oldTab._tPos; i < this.mTabContainer.childNodes.length; i++) {
for (i = aTab._tPos; i < length; i++) {
this.mTabContainer.childNodes[i]._tPos = i;
}
this.mTabBox.selectedPanel = this.getBrowserForTab(this.mCurrentTab).parentNode;
@ -1549,8 +1568,53 @@
this.updateCurrentBrowser();
// see comment above destroy above
oldBrowser.focusedWindow = null;
oldBrowser.focusedElement = null;
browser.focusedWindow = null;
browser.focusedElement = null;
]]>
</body>
</method>
<method name="swapBrowsersAndCloseOther">
<parameter name="aOurTab"/>
<parameter name="aOtherTab"/>
<body>
<![CDATA[
var remoteBrowser =
aOtherTab.ownerDocument.defaultView.getBrowser();
var tabCount = remoteBrowser.mTabs.length;
// First, start teardown of the other browser. Make sure to not
// fire the beforeunload event in the process.
var tabToRemove = remoteBrowser._beginRemoveTab(aOtherTab, false);
// Unhook our progress listener
var ourIndex = aOurTab._tPos;
const filter = this.mTabFilters[ourIndex];
var tabListener = this.mTabListeners[ourIndex];
var ourBrowser = this.getBrowserForTab(aOurTab);
ourBrowser.webProgress.removeProgressListener(filter);
filter.removeProgressListener(tabListener);
var tabListenerBlank = tabListener.mBlank;
// Swap the docshells
ourBrowser.swapDocShells(remoteBrowser.getBrowserForTab(aOtherTab));
// Finish tearing down the tab that's going away.
remoteBrowser._endRemoveTab(tabToRemove);
// Restore the progress listener
tabListener = this.mTabProgressListener(aOurTab, ourBrowser,
tabListenerBlank);
this.mTabListeners[ourIndex] = tabListener;
filter.addProgressListener(tabListener,
Components.interfaces.nsIWebProgress.NOTIFY_ALL);
ourBrowser.webProgress.addProgressListener(filter,
Components.interfaces.nsIWebProgress.NOTIFY_ALL);
// close the other window if this was its last tab
if (tabCount == 1)
aOtherTab.ownerDocument.defaultView.close();
]]>
</body>
</method>
@ -1879,19 +1943,26 @@
}
}
else if (draggedTab) {
// copy the dropped tab and remove it from the other window
// (making it seem to have moved between windows)
// swap the dropped tab with a new one we create and then close
// it in the other window (making it seem to have moved between
// windows)
newIndex = this.getNewIndex(aEvent);
newTab = this.duplicateTab(draggedTab);
this.moveTabTo(newTab, newIndex);
this.selectedTab = newTab;
newTab = this.addTab("about:blank");
var newBrowser = this.getBrowserForTab(newTab);
// Stop the about:blank load
newBrowser.stop();
// make sure it has a docshell
newBrowser.docShell;
var remoteBrowser = draggedTab.ownerDocument.defaultView.getBrowser();
var tabCount = remoteBrowser.tabContainer.childNodes.length;
remoteBrowser.removeTab(draggedTab);
// close the other window if this was its last tab
if (tabCount == 1)
draggedTab.ownerDocument.defaultView.close();
this.moveTabTo(newTab, newIndex);
this.swapBrowsersAndCloseOther(newTab, draggedTab);
// We need to set selectedTab after we've done
// swapBrowsersAndCloseOther, so that the updateCurrentBrowser
// it triggers will correctly update our URL bar.
this.selectedTab = newTab;
this.setTabTitle(newTab);
}
else {
var url = transferUtils.retrieveURLFromData(aXferData.data, aXferData.flavour.contentType);

View File

@ -76,8 +76,23 @@ interface nsIFrameLoader : nsISupports
readonly attribute boolean depthTooGreat;
};
[scriptable, uuid(feaf9285-05ac-4898-a69f-c3bd350767e4)]
[scriptable, uuid(641c2d90-4ada-4367-bdb1-80831614161d)]
interface nsIFrameLoaderOwner : nsISupports
{
/**
* The frame loader owned by this nsIFrameLoaderOwner
*/
readonly attribute nsIFrameLoader frameLoader;
/**
* Swap frame loaders with the given nsIFrameLoaderOwner. This may
* only be posible in a very limited range of circumstances, or
* never, depending on the object implementing this interface.
*
* @throws NS_ERROR_NOT_IMPLEMENTED if the swapping logic is not
* implemented for the two given frame loader owners.
* @throws NS_ERROR_DOM_SECURITY_ERR if the swap is not allowed on
* security grounds.
*/
void swapFrameLoaders(in nsIFrameLoaderOwner aOtherOwner);
};

View File

@ -58,6 +58,11 @@ public:
}
return shell;
}
PRBool HasMoreThanOneShell() {
return mDoc->mPresShells.Length() > 1;
}
private:
static void* operator new(size_t) CPP_THROW_NEW { return 0; }
static void operator delete(void*, size_t) {}

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.

View File

@ -60,7 +60,8 @@ public:
mDepthTooGreat(PR_FALSE),
mIsTopLevelContent(PR_FALSE),
mDestroyCalled(PR_FALSE),
mInDestructor(PR_FALSE)
mInDestructor(PR_FALSE),
mInSwap(PR_FALSE)
{}
~nsFrameLoader() {
@ -75,6 +76,13 @@ public:
nsresult ReallyStartLoading();
void Finalize();
nsIDocShell* GetExistingDocShell() { return mDocShell; }
// The guts of an nsIFrameLoaderOwner::SwapFrameLoader implementation. A
// frame loader owner needs to call this, and pass in the two references to
// nsRefPtrs for frame loaders that need to be swapped.
nsresult SwapWithOtherLoader(nsFrameLoader* aOther,
nsRefPtr<nsFrameLoader>& aFirstToSwap,
nsRefPtr<nsFrameLoader>& aSecondToSwap);
private:
NS_HIDDEN_(nsresult) EnsureDocShell();
@ -84,10 +92,11 @@ private:
nsCOMPtr<nsIDocShell> mDocShell;
nsCOMPtr<nsIURI> mURIToLoad;
nsIContent *mOwnerContent; // WEAK
PRPackedBool mDepthTooGreat;
PRPackedBool mIsTopLevelContent;
PRPackedBool mDestroyCalled;
PRPackedBool mInDestructor;
PRPackedBool mDepthTooGreat : 1;
PRPackedBool mIsTopLevelContent : 1;
PRPackedBool mDestroyCalled : 1;
PRPackedBool mInDestructor : 1;
PRPackedBool mInSwap : 1;
};
#endif

View File

@ -670,6 +670,12 @@ nsObjectLoadingContent::GetFrameLoader(nsIFrameLoader** aFrameLoader)
return NS_OK;
}
NS_IMETHODIMP
nsObjectLoadingContent::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherLoader)
{
return NS_ERROR_NOT_IMPLEMENTED;
}
// nsIObjectLoadingContent
NS_IMETHODIMP
nsObjectLoadingContent::GetActualType(nsACString& aType)

View File

@ -3001,6 +3001,13 @@ nsGenericHTMLFrameElement::GetFrameLoader(nsIFrameLoader **aFrameLoader)
return NS_OK;
}
NS_IMETHODIMP
nsGenericHTMLFrameElement::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherOwner)
{
// We don't support this yet
return NS_ERROR_NOT_IMPLEMENTED;
}
nsresult
nsGenericHTMLFrameElement::LoadSrc()
{

View File

@ -167,8 +167,6 @@
#define XUL_ELEMENT_CONTAINER_CONTENTS_BUILT \
(nsXULElement::eContainerContentsBuilt << XUL_ELEMENT_LAZY_STATE_OFFSET)
class nsIDocShell;
// Global object maintenance
nsICSSParser* nsXULPrototypeElement::sCSSParser = nsnull;
nsIXBLService * nsXULElement::gXBLService = nsnull;
@ -2118,6 +2116,36 @@ nsXULElement::GetFrameLoader(nsIFrameLoader **aFrameLoader)
return NS_OK;
}
nsresult
nsXULElement::SwapFrameLoaders(nsIFrameLoaderOwner* aOtherOwner)
{
nsCOMPtr<nsIContent> otherContent(do_QueryInterface(aOtherOwner));
NS_ENSURE_TRUE(otherContent, NS_ERROR_NOT_IMPLEMENTED);
nsXULElement* otherEl = FromContent(otherContent);
NS_ENSURE_TRUE(otherEl, NS_ERROR_NOT_IMPLEMENTED);
if (otherEl == this) {
// nothing to do
return NS_OK;
}
nsXULSlots *ourSlots = static_cast<nsXULSlots*>(GetExistingDOMSlots());
nsXULSlots *otherSlots =
static_cast<nsXULSlots*>(otherEl->GetExistingDOMSlots());
if (!ourSlots || !ourSlots->mFrameLoader ||
!otherSlots || !otherSlots->mFrameLoader) {
// Can't handle swapping when there is nothing to swap... yet.
return NS_ERROR_NOT_IMPLEMENTED;
}
return
ourSlots->mFrameLoader->SwapWithOtherLoader(otherSlots->mFrameLoader,
ourSlots->mFrameLoader,
otherSlots->mFrameLoader);
}
NS_IMETHODIMP
nsXULElement::GetParentTree(nsIDOMXULMultiSelectControlElement** aTreeElement)
{

View File

@ -625,8 +625,8 @@ public:
nsresult GetStyle(nsIDOMCSSStyleDeclaration** aStyle);
nsresult GetFrameLoader(nsIFrameLoader** aFrameLoader);
nsresult SwapFrameLoaders(nsIFrameLoaderOwner* aOtherOwner);
virtual void RecompileScriptEventListeners();
@ -664,7 +664,7 @@ protected:
nsXULSlots(PtrBits aFlags);
virtual ~nsXULSlots();
nsCOMPtr<nsIFrameLoader> mFrameLoader;
nsRefPtr<nsFrameLoader> mFrameLoader;
};
virtual nsINode::nsSlots* CreateSlots();

View File

@ -1190,12 +1190,10 @@ nsDocShell::SetChromeEventHandler(nsIDOMEventTarget* aChromeEventHandler)
// Weak reference. Don't addref.
mChromeEventHandler = piTarget;
NS_ASSERTION(!mScriptGlobal,
"SetChromeEventHandler() called after the script global "
"object was created! This means that the script global "
"object in this docshell won't get the right chrome event "
"handler. You really don't want to see this assert, FIX "
"YOUR CODE!");
nsCOMPtr<nsPIDOMWindow> win(do_QueryInterface(mScriptGlobal));
if (win) {
win->SetChromeEventHandler(piTarget);
}
return NS_OK;
}

View File

@ -96,6 +96,8 @@ public:
return mChromeEventHandler;
}
virtual void SetChromeEventHandler(nsPIDOMEventTarget* aChromeEventHandler) = 0;
PRBool HasMutationListeners(PRUint32 aMutationEventType) const
{
const nsPIDOMWindow *win;
@ -401,6 +403,10 @@ protected:
{
}
void SetChromeEventHandlerInternal(nsPIDOMEventTarget* aChromeEventHandler) {
mChromeEventHandler = aChromeEventHandler;
}
// These two variables are special in that they're set to the same
// value on both the outer window and the current inner window. Make
// sure you keep them in sync!

View File

@ -6810,6 +6810,27 @@ nsGlobalWindow::Deactivate()
return FireWidgetEvent(mDocShell, NS_DEACTIVATE);
}
void
nsGlobalWindow::SetChromeEventHandler(nsPIDOMEventTarget* aChromeEventHandler)
{
SetChromeEventHandlerInternal(aChromeEventHandler);
if (IsOuterWindow()) {
// update the chrome event handler on all our inner windows
for (nsGlobalWindow *inner = (nsGlobalWindow *)PR_LIST_HEAD(this);
inner != this;
inner = (nsGlobalWindow*)PR_NEXT_LINK(inner)) {
NS_ASSERTION(inner->mOuterWindow == this, "bad outer window pointer");
inner->SetChromeEventHandlerInternal(aChromeEventHandler);
}
} else if (mOuterWindow) {
// Need the cast to be able to call the protected method on a
// superclass. We could make the method public instead, but it's really
// better this way.
static_cast<nsGlobalWindow*>(mOuterWindow)->
SetChromeEventHandlerInternal(aChromeEventHandler);
}
}
nsIFocusController*
nsGlobalWindow::GetRootFocusController()
{

View File

@ -290,6 +290,7 @@ public:
virtual NS_HIDDEN_(nsPIDOMWindow*) GetPrivateRoot();
virtual NS_HIDDEN_(nsresult) Activate();
virtual NS_HIDDEN_(nsresult) Deactivate();
virtual NS_HIDDEN_(void) SetChromeEventHandler(nsPIDOMEventTarget* aChromeEventHandler);
virtual NS_HIDDEN_(nsIFocusController*) GetRootFocusController();
virtual NS_HIDDEN_(void) SetOpenerScriptPrincipal(nsIPrincipal* aPrincipal);

View File

@ -181,6 +181,8 @@ public:
// nsIFrameFrame
NS_IMETHOD GetDocShell(nsIDocShell **aDocShell);
NS_IMETHOD BeginSwapDocShells(nsIFrame* aOther);
virtual void EndSwapDocShells(nsIFrame* aOther);
NS_IMETHOD VerifyTree() const;
@ -199,6 +201,10 @@ protected:
virtual PRIntn GetSkipSides() const;
// Hide or show our document viewer
void HideViewer();
void ShowViewer();
/* Obtains the frame we should use for intrinsic size information if we are
* an HTML <object>, <embed> or <applet> (a replaced element - not <iframe>)
* and our sub-document has an intrinsic size. The frame returned is the
@ -303,18 +309,27 @@ nsSubDocumentFrame::Init(nsIContent* aContent,
view->CreateWidget(kCChildCID);
}
if (!aPresContext->IsDynamic()) {
ShowViewer();
return NS_OK;
}
void
nsSubDocumentFrame::ShowViewer()
{
if (!PresContext()->IsDynamic()) {
// We let the printing code take care of loading the document; just
// create a widget for it to use
rv = CreateViewAndWidget(eContentTypeContent);
NS_ENSURE_SUCCESS(rv,rv);
nsresult rv = CreateViewAndWidget(eContentTypeContent);
if (NS_FAILED(rv)) {
return;
}
} else {
rv = ShowDocShell();
NS_ENSURE_SUCCESS(rv,rv);
nsresult rv = ShowDocShell();
if (NS_FAILED(rv)) {
return;
}
mDidCreateDoc = PR_TRUE;
}
return NS_OK;
}
PRIntn
@ -745,6 +760,14 @@ nsSubDocumentFrame::Destroy()
mPostedReflowCallback = PR_FALSE;
}
HideViewer();
nsLeafFrame::Destroy();
}
void
nsSubDocumentFrame::HideViewer()
{
if (mFrameLoader && mDidCreateDoc) {
// Get the content viewer through the docshell, but don't call
// GetDocShell() since we don't want to create one if we don't
@ -774,12 +797,10 @@ nsSubDocumentFrame::Destroy()
// Hide the content viewer now that the frame is going away...
baseWin->SetVisibility(PR_FALSE);
// Clear out the parentWidget, since it's about to die with us
// Clear out the parentWidget, since it might be about to die with us
baseWin->SetParentWidget(nsnull);
}
}
nsLeafFrame::Destroy();
}
nsSize nsSubDocumentFrame::GetMargin()
@ -824,6 +845,46 @@ nsSubDocumentFrame::GetDocShell(nsIDocShell **aDocShell)
return mFrameLoader->GetDocShell(aDocShell);
}
NS_IMETHODIMP
nsSubDocumentFrame::BeginSwapDocShells(nsIFrame* aOther)
{
if (!aOther || aOther->GetType() != nsGkAtoms::subDocumentFrame) {
return NS_ERROR_NOT_IMPLEMENTED;
}
nsSubDocumentFrame* other = static_cast<nsSubDocumentFrame*>(aOther);
if (!mFrameLoader || !mDidCreateDoc || !other->mFrameLoader ||
!other->mDidCreateDoc) {
return NS_ERROR_NOT_IMPLEMENTED;
}
HideViewer();
other->HideViewer();
mFrameLoader.swap(other->mFrameLoader);
return NS_OK;
}
void
nsSubDocumentFrame::EndSwapDocShells(nsIFrame* aOther)
{
nsSubDocumentFrame* other = static_cast<nsSubDocumentFrame*>(aOther);
ShowViewer();
other->ShowViewer();
// Now make sure we reflow both frames, in case their contents
// determine their size.
PresContext()->PresShell()->
FrameNeedsReflow(this, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY);
other->PresContext()->PresShell()->
FrameNeedsReflow(other, nsIPresShell::eTreeChange, NS_FRAME_IS_DIRTY);
// And repaint them, for good measure, in case there's nothing
// interesting that happens during reflow.
InvalidateOverflowRect();
other->InvalidateOverflowRect();
}
inline PRInt32 ConvertOverflow(PRUint8 aOverflow)
{
switch (aOverflow) {
@ -935,6 +996,11 @@ nsSubDocumentFrame::ShowDocShell()
nsresult
nsSubDocumentFrame::CreateViewAndWidget(nsContentType aContentType)
{
if (mInnerView) {
// Nothing to do here
return NS_OK;
}
// create, init, set the parent of the view
nsIView* outerView = GetView();
NS_ASSERTION(outerView, "Must have an outer view already");

View File

@ -46,8 +46,8 @@
class nsIDocShell;
#define NS_IFRAMEFRAME_IID \
{ 0xda876f25, 0x1cff, 0x4f0a, { \
0xbf, 0x7e, 0x83, 0xd7, 0x4f, 0xc5, 0x2a, 0x3b } }
{ 0x22e34108, 0xc24b, 0x40ea, { \
0xb9, 0x79, 0x50, 0x18, 0x38, 0x8d, 0xd5, 0x88 } }
class nsIFrameFrame : public nsISupports
{
@ -55,6 +55,14 @@ public:
NS_DECLARE_STATIC_IID_ACCESSOR(NS_IFRAMEFRAME_IID)
NS_IMETHOD GetDocShell(nsIDocShell **aDocShell) = 0;
/**
* Only allowed to fail if the other frame is not the same type as
* this one or if one of the frames has no docshell. Don't call
* EndSwapDocShells() unless BeginSwapDocShells() succeeds.
*/
NS_IMETHOD BeginSwapDocShells(nsIFrame* aOther) = 0;
virtual void EndSwapDocShells(nsIFrame* aOther) = 0;
};
NS_DEFINE_STATIC_IID_ACCESSOR(nsIFrameFrame, NS_IFRAMEFRAME_IID)

View File

@ -957,6 +957,39 @@
]]>
</body>
</method>
<method name="swapDocShells">
<parameter name="aOtherBrowser"/>
<body>
<![CDATA[
// We need to swap fields that are tied to our docshell
var fieldsToSwap = [ "_docShell", "_webBrowserFind" ];
var ourTabBrowser = this.getTabBrowser();
// _fastFind is tied to the docshell if we don't have a tabbrowser
if ((ourTabBrowser != null) !=
(aOtherBrowser.getTabBrowser() != null))
throw "Unable to perform swap on <browsers> if one is in a <tabbrowser> and one is not";
if (!ourTabBrowser)
fieldsToSwap.push("_fastFind");
var ourFieldValues = {};
var otherFieldValues = {};
for each (var field in fieldsToSwap) {
ourFieldValues[field] = this[field];
otherFieldValues[field] = aOtherBrowser[field];
}
this.QueryInterface(Components.interfaces.nsIFrameLoaderOwner)
.swapFrameLoaders(aOtherBrowser);
for each (var field in fieldsToSwap) {
this[field] = otherFieldValues[field];
aOtherBrowser[field] = ourFieldValues[field];
}
]]>
</body>
</method>
</implementation>
<handlers>