Bug 462673 - Browser exits unexpectedly whan calling window.close() from an unload handler. r=gavin

This commit is contained in:
Dão Gottwald 2009-03-24 12:47:06 +01:00
Родитель 40af1731e8
Коммит 06b4581c23
4 изменённых файлов: 186 добавлений и 73 удалений

Просмотреть файл

@ -30,6 +30,7 @@
- Simon Bünzli <zeniko@gmail.com>
- Michael Ventnor <ventnor.bugzilla@yahoo.com.au>
- Mark Pilgrim <pilgrim@gmail.com>
- Dão Gottwald <dao@mozilla.com>
-
- Alternatively, the contents of this file may be used under the terms of
- either the GNU General Public License Version 2 or later (the "GPL"), or
@ -1482,7 +1483,7 @@
<method name="removeCurrentTab">
<body>
<![CDATA[
return this.removeTab(this.mCurrentTab);
this.removeTab(this.mCurrentTab);
]]>
</body>
</method>
@ -1501,6 +1502,10 @@
</body>
</method>
<field name="_removingTabs">
[]
</field>
<method name="removeTab">
<parameter name="aTab"/>
<body>
@ -1510,6 +1515,12 @@
</body>
</method>
<!-- Tab close requests are ignored if the window is closing anyway,
e.g. when holding Ctrl+W. -->
<field name="_windowIsClosing">
false
</field>
<!-- Returns everything that _endRemoveTab needs in an array. -->
<method name="_beginRemoveTab">
<parameter name="aTab"/>
@ -1517,14 +1528,19 @@
<parameter name="aCloseWindowWithLastTab"/>
<body>
<![CDATA[
if (this._removingTabs.indexOf(aTab) > -1 || this._windowIsClosing)
return null;
var browser = this.getBrowserForTab(aTab);
if (aFireBeforeUnload) {
let ds = this.getBrowserForTab(aTab).docShell;
let ds = browser.docShell;
if (ds.contentViewer && !ds.contentViewer.permitUnload())
return null;
}
var closeWindow = false;
var l = this.mTabs.length;
var l = this.mTabs.length - this._removingTabs.length;
if (l == 1) {
closeWindow = aCloseWindowWithLastTab != null ?
aCloseWindowWithLastTab :
@ -1539,22 +1555,15 @@
l++;
}
if (l == 2) {
var autohide = this.mPrefs.getBoolPref("browser.tabs.autoHide");
var tabStripHide = !window.toolbar.visible;
let autohide = this.mPrefs.getBoolPref("browser.tabs.autoHide");
let tabStripHide = !window.toolbar.visible;
if (autohide || tabStripHide)
this.setStripVisibilityTo(false);
}
if (!closeWindow) {
// see notes in addTab
let _delayedUpdate = function (aTabContainer) {
aTabContainer.adjustTabstrip();
aTabContainer.mTabstrip._updateScrollButtonsDisabledState();
};
setTimeout(_delayedUpdate, 0, this.mTabContainer);
}
this._removingTabs.push(aTab);
// We're committed to closing the tab now.
// We're committed to closing the tab now.
// Dispatch a notification.
// We dispatch it before any teardown so that event listeners can
// inspect the tab that's about to close.
@ -1562,29 +1571,23 @@
evt.initEvent("TabClose", true, false);
aTab.dispatchEvent(evt);
var index = aTab._tPos;
// Remove the tab's filter and progress listener.
const filter = this.mTabFilters[index];
var oldBrowser = this.getBrowserForTab(aTab);
oldBrowser.webProgress.removeProgressListener(filter);
filter.removeProgressListener(this.mTabListeners[index]);
this.mTabFilters.splice(index, 1);
this.mTabListeners.splice(index, 1);
const filter = this.mTabFilters[aTab._tPos];
browser.webProgress.removeProgressListener(filter);
filter.removeProgressListener(this.mTabListeners[aTab._tPos]);
// Remove our title change and blocking listeners
oldBrowser.removeEventListener("DOMTitleChanged", this.onTitleChanged, true);
browser.removeEventListener("DOMTitleChanged", this.onTitleChanged, true);
// We are no longer the primary content area.
oldBrowser.setAttribute("type", "content-targetable");
browser.setAttribute("type", "content-targetable");
// Remove this tab as the owner of any other tabs, since it's going away.
for (let i = 0; i < l; ++i) {
let tab = this.mTabs[i];
Array.forEach(this.mTabs, function (tab) {
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, closeWindow];
]]>
@ -1599,16 +1602,34 @@
return;
var [aTab, aCloseWindow] = args;
// update the UI early for responsiveness
aTab.collapsed = true;
this.tabContainer._fillTrailingGap();
this._blurTab(aTab);
this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
if (aCloseWindow) {
this._windowIsClosing = true;
while (this._removingTabs.length)
this._endRemoveTab([this._removingTabs[0], false]);
} else if (!this._windowIsClosing) {
// see notes in addTab
function _delayedUpdate(aTabContainer) {
aTabContainer.adjustTabstrip();
aTabContainer.mTabstrip._updateScrollButtonsDisabledState();
};
setTimeout(_delayedUpdate, 0, this.tabContainer);
}
// We're going to remove the tab and the browser now.
// Clean up mTabFilters and mTabListeners now rather than in
// _beginRemoveTab, so that their size is always in sync with the
// number of tabs and browsers (the xbl destructor depends on this).
this.mTabFilters.splice(aTab._tPos, 1);
this.mTabListeners.splice(aTab._tPos, 1);
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
@ -1625,53 +1646,72 @@
if (browser == this.mCurrentBrowser)
this.mCurrentBrowser = null;
// Remove the tab
this.mTabContainer.removeChild(aTab);
// Update our length
--length;
// invalidate cache, because mTabContainer is about to change
this._browsers = null;
this.mPanelContainer.removeChild(browser.parentNode);
// Invalidate browsers cache, as the tab is removed from the
// tab container.
this._browsers = null;
this.tabContainer._fillTrailingGap();
// Remove the tab ...
this.tabContainer.removeChild(aTab);
// Find the tab to select
var newIndex = -1;
if (currentIndex > index)
newIndex = currentIndex-1;
else if (currentIndex < index)
newIndex = currentIndex;
else {
if ("owner" in aTab && aTab.owner &&
this.mPrefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
for (let i = 0; i < length; ++i) {
if (this.mTabs[i] == aTab.owner) {
newIndex = i;
break;
}
}
}
if (newIndex == -1)
newIndex = (index == length) ? index - 1 : index;
}
// Select the new tab
this.selectedTab = this.mTabs[newIndex];
for (let i = aTab._tPos; i < length; i++)
// ... and fix up the _tPos properties immediately.
for (let i = aTab._tPos; i < this.mTabs.length; i++)
this.mTabs[i]._tPos = i;
this.mTabBox.selectedPanel = this.getBrowserForTab(this.mCurrentTab).parentNode;
this.mCurrentTab._selected = true;
// update first-tab/last-tab/beforeselected/afterselected attributes
this.selectedTab._selected = true;
this.updateCurrentBrowser();
// This will unload the document. An unload handler could remove
// dependant tabs, so it's important that the tabbrowser is now in
// a consistent state (tab removed, tab positions updated, etc.).
// Also, it's important that another tab has been selected before
// the panel is removed; otherwise, a random sibling panel can flash.
this.mPanelContainer.removeChild(browser.parentNode);
// see comment above destroy above
// As the panel is removed, the removal of a dependent document can
// cause the whole window to close. So at this point, it's possible
// that the binding is destructed.
if (this.mTabBox)
this.mTabBox.selectedPanel = this.getBrowserForTab(this.mCurrentTab).parentNode;
// see comment about destroy above
browser.focusedWindow = null;
browser.focusedElement = null;
if (aCloseWindow)
closeWindow(true);
this._windowIsClosing = closeWindow(true);
]]>
</body>
</method>
<method name="_blurTab">
<parameter name="aTab"/>
<body>
<![CDATA[
if (this.mCurrentTab != aTab)
return;
if (aTab.owner &&
this._removingTabs.indexOf(aTab.owner) == -1 &&
this.mPrefs.getBoolPref("browser.tabs.selectOwnerOnClose")) {
this.selectedTab = aTab.owner;
return;
}
var tab = aTab;
do {
tab = tab.nextSibling;
} while (tab && this._removingTabs.indexOf(tab) != -1);
if (!tab) {
tab = aTab;
do {
tab = tab.previousSibling;
} while (tab && this._removingTabs.indexOf(tab) != -1);
}
this.selectedTab = tab;
]]>
</body>
</method>

Просмотреть файл

@ -72,9 +72,11 @@ _BROWSER_FILES = browser_sanitize-timespans.js \
browser_bug432599.js \
browser_bug441778.js \
browser_bug455852.js \
browser_bug462673.js \
browser_discovery.js \
discovery.html \
moz.png \
test_bug462673.html \
browser_getshortcutoruri.js \
browser_page_style_menu.js \
page_style_sample.html \

Просмотреть файл

@ -0,0 +1,53 @@
var runs = [
function (win, tabbrowser, tab) {
is(tabbrowser.browsers.length, 2, "test_bug462673.html has opened a second tab");
is(tabbrowser.selectedTab, tab.nextSibling, "dependent tab is selected");
tabbrowser.removeTab(tab);
ok(win.closed, "Window is closed");
},
function (win, tabbrowser, tab) {
var newTab = tabbrowser.addTab();
var newBrowser = newTab.linkedBrowser;
tabbrowser.removeTab(tab);
ok(!win.closed, "Window stays open");
if (!win.closed) {
is(tabbrowser.tabContainer.childElementCount, 1, "Window has one tab");
is(tabbrowser.browsers.length, 1, "Window has one browser");
is(tabbrowser.selectedTab, newTab, "Remaining tab is selected");
is(tabbrowser.selectedBrowser, newBrowser, "Browser for remaining tab is selected");
is(tabbrowser.mTabBox.selectedPanel, newBrowser.parentNode, "Panel for remaining tab is selected");
}
}
];
function test() {
waitForExplicitFinish();
runOneTest();
}
function runOneTest() {
var win = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no");
win.addEventListener("load", function () {
win.removeEventListener("load", arguments.callee, false);
var tab = win.gBrowser.tabContainer.firstChild;
var browser = tab.linkedBrowser;
browser.addEventListener("load", function () {
browser.removeEventListener("load", arguments.callee, true);
executeSoon(function () {
runs.shift()(win, win.gBrowser, tab);
win.close();
if (runs.length)
runOneTest();
else
finish();
});
}, true);
browser.contentWindow.location =
"chrome://mochikit/content/browser/browser/base/content/test/test_bug462673.html";
}, false);
}

Просмотреть файл

@ -0,0 +1,18 @@
<html>
<head>
<script>
var w;
function openIt() {
w = window.open("", "window2");
}
function closeIt() {
if (w) {
w.close();
w = null;
}
}
</script>
</head>
<body onload="openIt();" onunload="closeIt();">
</body>
</html>