Bug 626455 - modal dialog in onbeforeunload: browser freeze after removing last tab group in panorama; r=dietrich
☠☠ backed out by 7a08fd89d10a ☠ ☠
authorTim Taubert <tim.taubert@gmx.de>
Mon, 15 Aug 2011 16:03:39 +0200
changeset 75371 1608aa3ce9d38ee758d4ac843e125fcdc14b804d
parent 75370 621138727ae41c530c3a9f812bbc93e1164263b8
child 75372 c762e01dd6dc27a90357a9d6388967d9412c2522
child 75380 7a08fd89d10aefbe28fc9a03c643b06650f1b412
push id2
push userbsmedberg@mozilla.com
push dateFri, 19 Aug 2011 14:38:13 +0000
reviewersdietrich
bugs626455
milestone8.0a1
Bug 626455 - modal dialog in onbeforeunload: browser freeze after removing last tab group in panorama; r=dietrich
browser/base/content/tabview/content.js
browser/base/content/tabview/groupitems.js
browser/base/content/tabview/tabitems.js
browser/base/content/tabview/ui.js
browser/base/content/test/tabview/Makefile.in
browser/base/content/test/tabview/browser_tabview_bug597980.js
browser/base/content/test/tabview/browser_tabview_bug599626.js
browser/base/content/test/tabview/browser_tabview_bug604098.js
browser/base/content/test/tabview/browser_tabview_bug624953.js
browser/base/content/test/tabview/browser_tabview_bug625195.js
browser/base/content/test/tabview/browser_tabview_bug626455.js
browser/base/content/test/tabview/browser_tabview_bug663421.js
browser/base/content/test/tabview/browser_tabview_dragdrop.js
browser/base/content/test/tabview/browser_tabview_launch.js
browser/base/jar.mn
new file mode 100644
--- /dev/null
+++ b/browser/base/content/tabview/content.js
@@ -0,0 +1,46 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is content.js.
+ *
+ * The Initial Developer of the Original Code is the Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Tim Taubert <ttaubert@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
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+addEventListener("DOMWillOpenModalDialog", function (event) {
+  // (event.isTrusted == true) when the event is generated by a user action
+  // and does not originate from a script.
+  if (event.isTrusted) {
+    // we're intentionally sending a synchronous message to handle this event
+    // as quick as possible, switch the selected tab and hide the tabview
+    // before the modal dialog is shown
+    sendSyncMessage("Panorama:DOMWillOpenModalDialog");
+  }
+}, true);
--- a/browser/base/content/tabview/groupitems.js
+++ b/browser/base/content/tabview/groupitems.js
@@ -739,40 +739,55 @@ GroupItem.prototype = Utils.extend(new I
       return true;
     }
     return false;
   },
 
   // ----------
   // Function: _unhide
   // Shows the hidden group.
-  _unhide: function GroupItem__unhide() {
-    let self = this;
-
+  //
+  // Parameters:
+  //   options - various options (see below)
+  //
+  // Possible options:
+  //   immediately - true when no animations should be used
+  _unhide: function GroupItem__unhide(options) {
     this._cancelFadeAwayUndoButtonTimer();
     this.hidden = false;
     this.$undoContainer.remove();
     this.$undoContainer = null;
     this.droppable(true);
     this.setTrenches(this.bounds);
 
-    iQ(this.container).show().animate({
-      "-moz-transform": "scale(1)",
-      "opacity": 1
-    }, {
-      duration: 170,
-      complete: function() {
-        self._children.forEach(function(child) {
-          iQ(child.container).show();
-        });
+    let self = this;
+
+    let finalize = function () {
+      self._children.forEach(function(child) {
+        iQ(child.container).show();
+      });
+
+      UI.setActive(self);
+      self._sendToSubscribers("groupShown", { groupItemId: self.id });
+    };
+
+    let $container = iQ(this.container).show();
 
-        UI.setActive(self);
-        self._sendToSubscribers("groupShown", { groupItemId: self.id });
-      }
-    });
+    if (!options || !options.immediately) {
+      $container.animate({
+        "-moz-transform": "scale(1)",
+        "opacity": 1
+      }, {
+        duration: 170,
+        complete: finalize
+      });
+    } else {
+      $container.css({"-moz-transform": "none", opacity: 1});
+      finalize();
+    }
 
     GroupItems.updateGroupCloseButtons();
   },
 
   // ----------
   // Function: closeHidden
   // Removes the group item, its children and its container.
   closeHidden: function GroupItem_closeHidden() {
@@ -780,69 +795,83 @@ GroupItem.prototype = Utils.extend(new I
 
     this._cancelFadeAwayUndoButtonTimer();
 
     // When the last non-empty groupItem is closed and there are no
     // pinned tabs then create a new group with a blank tab.
     let remainingGroups = GroupItems.groupItems.filter(function (groupItem) {
       return (groupItem != self && groupItem.getChildren().length);
     });
+
+    let tab = null;
+
     if (!gBrowser._numPinnedTabs && !remainingGroups.length) {
       let emptyGroups = GroupItems.groupItems.filter(function (groupItem) {
         return (groupItem != self && !groupItem.getChildren().length);
       });
       let group = (emptyGroups.length ? emptyGroups[0] : GroupItems.newGroup());
-      group.newTab(null, { closedLastTab: true });
+      tab = group.newTab(null, {dontZoomIn: true});
     }
 
-    this.destroy();
+    let closed = this.destroy();
+
+    if (!tab)
+      return;
+
+    if (closed) {
+      // Let's make the new tab the selected tab.
+      UI.goToTab(tab);
+    } else {
+      // Remove the new tab and group, if this group is no longer closed.
+      tab._tabViewTabItem.parent.destroy({immediately: true});
+    }
   },
 
   // ----------
   // Function: destroy
   // Close all tabs linked to children (tabItems), removes all children and 
   // close the groupItem.
   //
   // Parameters:
   //   options - An object with optional settings for this call.
   //
   // Options:
   //   immediately - (bool) if true, no animation will be used
+  //
+  // Returns true if the groupItem has been closed, or false otherwise. A group
+  // could not have been closed due to a tab with an onUnload handler (that
+  // waits for user interaction).
   destroy: function GroupItem_destroy(options) {
     let self = this;
 
     // when "TabClose" event is fired, the browser tab is about to close and our 
     // item "close" event is fired.  And then, the browser tab gets closed. 
     // In other words, the group "close" event is fired before all browser
     // tabs in the group are closed.  The below code would fire the group "close"
     // event only after all browser tabs in that group are closed.
-    let shouldRemoveTabItems = [];
-    let toClose = this._children.concat();
-    toClose.forEach(function(child) {
+    this._children.concat().forEach(function(child) {
       child.removeSubscriber("close", self._onChildClose);
 
-      let removed = child.close(true);
-      if (removed) {
-        shouldRemoveTabItems.push(child);
+      if (child.close(true)) {
+        self.remove(child, { dontArrange: true });
       } else {
         // child.removeSubscriber() must be called before child.close(), 
         // therefore we call child.addSubscriber() if the tab is not removed.
         child.addSubscriber("close", self._onChildClose);
       }
     });
 
-    if (shouldRemoveTabItems.length != toClose.length) {
-      // remove children without the assiciated tab and show the group item
-      shouldRemoveTabItems.forEach(function(child) {
-        self.remove(child, { dontArrange: true });
-      });
+    if (this._children.length) {
+      if (this.hidden)
+        this.$undoContainer.fadeOut(function() { self._unhide() });
 
-      this.$undoContainer.fadeOut(function() { self._unhide() });
+      return false;
     } else {
       this.close(options);
+      return true;
     }
   },
 
   // ----------
   // Function: _fadeAwayUndoButton
   // Fades away the undo button
   _fadeAwayUndoButton: function GroupItem__fadeAwayUdoButton() {
     let self = this;
@@ -1812,23 +1841,26 @@ GroupItem.prototype = Utils.extend(new I
   },
 
   // ----------
   // Function: newTab
   // Creates a new tab within this groupItem.
   // Parameters:
   //  url - the new tab should open this url as well
   //  options - the options object
+  //    dontZoomIn - set to true to not zoom into the newly created tab
   //    closedLastTab - boolean indicates the last tab has just been closed
   newTab: function GroupItem_newTab(url, options) {
     if (options && options.closedLastTab)
       UI.closedLastTabInTabView = true;
 
     UI.setActive(this, { dontSetActiveTabInGroup: true });
-    gBrowser.loadOneTab(url || "about:blank", { inBackground: false });
+
+    let dontZoomIn = !!(options && options.dontZoomIn);
+    return gBrowser.loadOneTab(url || "about:blank", { inBackground: dontZoomIn });
   },
 
   // ----------
   // Function: reorderTabItemsBasedOnTabOrder
   // Reorders the tabs in a groupItem based on the arrangment of the tabs
   // shown in the tab bar. It does it by sorting the children
   // of the groupItem by the positions of their respective tabs in the
   // tab bar.
--- a/browser/base/content/tabview/tabitems.js
+++ b/browser/base/content/tabview/tabitems.js
@@ -473,39 +473,32 @@ TabItem.prototype = Utils.extend(new Ite
   // Parameters:
   //   groupClose - true if this method is called by group close action.
   // Returns true if this tab is removed.
   close: function TabItem_close(groupClose) {
     // When the last tab is closed, put a new tab into closing tab's group. If
     // closing tab doesn't belong to a group and no empty group, create a new 
     // one for the new tab.
     if (!groupClose && gBrowser.tabs.length == 1) {
-      let group;
-      if (this.tab._tabViewTabItem.parent) {
-        group = this.tab._tabViewTabItem.parent;
-      } else {
-        let emptyGroups = GroupItems.groupItems.filter(function (groupItem) {
-          return (!groupItem.getChildren().length);
-        });
-        group = (emptyGroups.length ? emptyGroups[0] : GroupItems.newGroup());
-      }
+      let group = this.tab._tabViewTabItem.parent;
       group.newTab(null, { closedLastTab: true });
     }
+
     // when "TabClose" event is fired, the browser tab is about to close and our 
     // item "close" is fired before the browser tab actually get closed. 
     // Therefore, we need "tabRemoved" event below.
     gBrowser.removeTab(this.tab);
-    let tabNotClosed = 
-      Array.some(gBrowser.tabs, function(tab) { return tab == this.tab; }, this);
-    if (!tabNotClosed)
+    let tabClosed = !this.tab;
+
+    if (tabClosed)
       this._sendToSubscribers("tabRemoved");
 
     // No need to explicitly delete the tab data, becasue sessionstore data
     // associated with the tab will automatically go away
-    return !tabNotClosed;
+    return tabClosed;
   },
 
   // ----------
   // Function: addClass
   // Adds the specified CSS class to this item's container DOM element.
   addClass: function TabItem_addClass(className) {
     this.$container.addClass(className);
   },
--- a/browser/base/content/tabview/ui.js
+++ b/browser/base/content/tabview/ui.js
@@ -21,16 +21,17 @@
  * Contributor(s):
  * Ian Gilman <ian@iangilman.com>
  * Aza Raskin <aza@mozilla.com>
  * Michael Yoshitaka Erlewine <mitcho@mitcho.com>
  * Ehsan Akhgari <ehsan@mozilla.com>
  * Raymond Lee <raymond@appcoast.com>
  * Sean Dunn <seanedunn@yahoo.com>
  * Tim Taubert <tim.taubert@gmx.de>
+ * Mihai Sucan <mihai.sucan@gmail.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
  * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  * in which case the provisions of the GPL or the LGPL are applicable instead
  * of those above. If you wish to allow use of your version of this file only
  * under the terms of either the GPL or the LGPL, and not to allow others to
  * use your version of this file under the terms of the MPL, indicate your
@@ -75,16 +76,20 @@ let UI = {
   // Variable: _closedSelectedTabInTabView
   // If true, a select tab has just been closed in TabView.
   _closedSelectedTabInTabView: false,
 
   // Variable: restoredClosedTab
   // If true, a closed tab has just been restored.
   restoredClosedTab: false,
 
+  // Variable: _isChangingVisibility
+  // Tracks whether we're currently in the process of showing/hiding the tabview.
+  _isChangingVisibility: false,
+
   // Variable: _reorderTabItemsOnShow
   // Keeps track of the <GroupItem>s which their tab items' tabs have been moved
   // and re-orders the tab items when switching to TabView.
   _reorderTabItemsOnShow: [],
 
   // Variable: _reorderTabsOnHide
   // Keeps track of the <GroupItem>s which their tab items have been moved in
   // TabView UI and re-orders the tabs when switcing back to main browser.
@@ -225,16 +230,25 @@ let UI = {
           }
         }
       });
 
       iQ(window).bind("unload", function() {
         self.uninit();
       });
 
+      // ___ setup DOMWillOpenModalDialog message handler
+      let mm = gWindow.messageManager;
+      let callback = this._onDOMWillOpenModalDialog.bind(this);
+      mm.addMessageListener("Panorama:DOMWillOpenModalDialog", callback);
+
+      this._cleanupFunctions.push(function () {
+        mm.removeMessageListener("Panorama:DOMWillOpenModalDialog", callback);
+      });
+
       // ___ setup key handlers
       this._setTabViewFrameKeyHandlers();
 
       // ___ add tab action handlers
       this._addTabActionHandlers();
 
       // ___ groups
       GroupItems.init();
@@ -267,16 +281,20 @@ let UI = {
         if (self.isTabViewVisible())
           GroupItems.removeHiddenGroups();
 
         Storage.saveActiveGroupName(gWindow);
         TabItems.saveAll(true);
         self._save();
       }, false);
 
+      // ___ load frame script
+      let frameScript = "chrome://browser/content/tabview-content.js";
+      gWindow.messageManager.loadFrameScript(frameScript, true);
+
       // ___ Done
       this._frameInitialized = true;
       this._save();
 
       // fire an iframe initialized event so everyone knows tab view is 
       // initialized.
       let event = document.createEvent("Events");
       event.initEvent("tabviewframeinitialized", true, false);
@@ -472,19 +490,21 @@ let UI = {
   },
 
   // ----------
   // Function: showTabView
   // Shows TabView and hides the main browser UI.
   // Parameters:
   //   zoomOut - true for zoom out animation, false for nothing.
   showTabView: function UI_showTabView(zoomOut) {
-    if (this.isTabViewVisible())
+    if (this.isTabViewVisible() || this._isChangingVisibility)
       return;
 
+    this._isChangingVisibility = true;
+
     // initialize the direction of the page
     this._initPageDirection();
 
     var self = this;
     var currentTab = this._currentTab;
 
     this._reorderTabItemsOnShow.forEach(function(groupItem) {
       groupItem.reorderTabItemsBasedOnTabOrder();
@@ -517,44 +537,48 @@ let UI = {
       // Zoom out!
       item.zoomOut(function() {
         if (!currentTab._tabViewTabItem) // if the tab's been destroyed
           item = null;
 
         self.setActive(item);
 
         self._resize(true);
+        self._isChangingVisibility = false;
         dispatchEvent(event);
 
         // Flush pending updates
         GroupItems.flushAppTabUpdates();
 
         TabItems.resumePainting();
       });
     } else {
       self.clearActiveTab();
+      self._isChangingVisibility = false;
       dispatchEvent(event);
 
       // Flush pending updates
       GroupItems.flushAppTabUpdates();
 
       TabItems.resumePainting();
     }
 
     if (gTabView.firstUseExperienced)
       gTabView.enableSessionRestore();
   },
 
   // ----------
   // Function: hideTabView
   // Hides TabView and shows the main browser UI.
   hideTabView: function UI_hideTabView() {
-    if (!this.isTabViewVisible())
+    if (!this.isTabViewVisible() || this._isChangingVisibility)
       return;
 
+    this._isChangingVisibility = true;
+
     // another tab might be select if user decides to stay on a page when
     // a onclose confirmation prompts.
     GroupItems.removeHiddenGroups();
     TabItems.pausePainting();
 
     this._reorderTabsOnHide.forEach(function(groupItem) {
       groupItem.reorderTabsBasedOnTabItemOrder();
     });
@@ -571,16 +595,18 @@ let UI = {
     gBrowser.contentWindow.focus();
 
     gBrowser.updateTitlebar();
 #ifdef XP_MACOSX
     this.setTitlebarColors(false);
 #endif
     Storage.saveVisibilityData(gWindow, "false");
 
+    this._isChangingVisibility = false;
+
     let event = document.createEvent("Events");
     event.initEvent("tabviewhidden", true, false);
     dispatchEvent(event);
   },
 
 #ifdef XP_MACOSX
   // ----------
   // Function: setTitlebarColors
@@ -744,31 +770,24 @@ let UI = {
           var groupItem = GroupItems.getActiveGroupItem();
 
           // 1) Only go back to the TabView tab when there you close the last
           // tab of a groupItem.
           let closingLastOfGroup = (groupItem && 
               groupItem._children.length == 1 && 
               groupItem._children[0].tab == tab);
 
-          // 2) Take care of the case where you've closed the last tab in
-          // an un-named groupItem, which means that the groupItem is gone (null) and
-          // there are no visible tabs. 
-          let closingUnnamedGroup = (groupItem == null &&
-              gBrowser.visibleTabs.length <= 1); 
-
-          // 3) When a blank tab is active while restoring a closed tab the
+          // 2) When a blank tab is active while restoring a closed tab the
           // blank tab gets removed. The active group is not closed as this is
           // where the restored tab goes. So do not show the TabView.
           let tabItem = tab && tab._tabViewTabItem;
           let closingBlankTabAfterRestore =
             (tabItem && tabItem.isRemovedAfterRestore);
 
-          if ((closingLastOfGroup || closingUnnamedGroup) &&
-              !closingBlankTabAfterRestore) {
+          if (closingLastOfGroup && !closingBlankTabAfterRestore) {
             // for the tab focus event to pick up.
             self._closedLastVisibleTab = true;
             self.showTabView();
           }
         }
       }
     };
 
@@ -878,18 +897,24 @@ let UI = {
     this._closedLastVisibleTab = false;
     this._closedSelectedTabInTabView = false;
     this.closedLastTabInTabView = false;
     this.restoredClosedTab = false;
     this._lastOpenedTab = null;
 
     // if TabView is visible but we didn't just close the last tab or
     // selected tab, show chrome.
-    if (this.isTabViewVisible())
+    if (this.isTabViewVisible()) {
+      // Unhide the group of the tab the user is activating.
+      if (tab && tab._tabViewTabItem && tab._tabViewTabItem.parent &&
+          tab._tabViewTabItem.parent.hidden)
+        tab._tabViewTabItem.parent._unhide({immediately: true});
+
       this.hideTabView();
+    }
 
     // another tab might be selected when hideTabView() is invoked so a
     // validation is needed.
     if (this._currentTab != tab)
       return;
 
     let newItem = null;
     // update the tab bar for the new tab's group
@@ -914,16 +939,37 @@ let UI = {
       }
 
       if (GroupItems.getActiveGroupItem())
         GroupItems._updateTabBar();
     }
   },
 
   // ----------
+  // Function: _onDOMWillOpenModalDialog
+  // Called when a web page is about to show a modal dialog.
+  _onDOMWillOpenModalDialog: function UI__onDOMWillOpenModalDialog(cx) {
+    if (!this.isTabViewVisible())
+      return;
+
+    let index = gBrowser.browsers.indexOf(cx.target);
+    if (index == -1)
+      return;
+
+    let tab = gBrowser.tabs[index];
+
+    // When TabView is visible, we need to call onTabSelect to make sure that
+    // TabView is hidden and that the correct group is activated. When a modal
+    // dialog is shown for currently selected tab the onTabSelect event handler
+    // is not called, so we need to do it.
+    if (gBrowser.selectedTab == tab && this._currentTab == tab)
+      this.onTabSelect(tab);
+  },
+
+  // ----------
   // Function: setReorderTabsOnHide
   // Sets the groupItem which the tab items' tabs should be re-ordered when
   // switching to the main browser UI.
   // Parameters:
   //   groupItem - the groupItem which would be used for re-ordering tabs.
   setReorderTabsOnHide: function UI_setReorderTabsOnHide(groupItem) {
     if (this.isTabViewVisible()) {
       var index = this._reorderTabsOnHide.indexOf(groupItem);
--- a/browser/base/content/test/tabview/Makefile.in
+++ b/browser/base/content/test/tabview/Makefile.in
@@ -106,16 +106,17 @@ include $(topsrcdir)/config/rules.mk
                  browser_tabview_bug624727.js \
                  browser_tabview_bug624847.js \
                  browser_tabview_bug624931.js \
                  browser_tabview_bug624953.js \
                  browser_tabview_bug625195.js \
                  browser_tabview_bug625269.js \
                  browser_tabview_bug625424.js \
                  browser_tabview_bug626368.js \
+                 browser_tabview_bug626455.js \
                  browser_tabview_bug626525.js \
                  browser_tabview_bug626791.js \
                  browser_tabview_bug627239.js \
                  browser_tabview_bug627288.js \
                  browser_tabview_bug627736.js \
                  browser_tabview_bug628061.js \
                  browser_tabview_bug628165.js \
                  browser_tabview_bug628270.js \
--- a/browser/base/content/test/tabview/browser_tabview_bug597980.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug597980.js
@@ -5,17 +5,17 @@ function test() {
   waitForExplicitFinish();
 
   newWindowWithTabView(part1);
 }
 
 function part1(win) {
   registerCleanupFunction(function() win.close());
 
-  let contentWindow = win.document.getElementById("tab-view").contentWindow;
+  let contentWindow = win.TabView.getContentWindow();
   is(contentWindow.GroupItems.groupItems.length, 1, "Has only one group");
 
   let originalTab = win.gBrowser.selectedTab;
   let originalGroup = contentWindow.GroupItems.groupItems[0];
   let newTab = win.gBrowser.loadOneTab("about:blank", {inBackground: true});
   
   is(originalGroup.getChildren().length, 2, "The original group now has two tabs");
   
@@ -66,20 +66,17 @@ function part2(win) {
   let newTab = win.gBrowser.loadOneTab("about:blank", {inBackground: true});
   hideTabView(function() {
     let selectedTab = win.gBrowser.selectedTab;
     isnot(selectedTab, newTab, "They are different tabs");
 
     // switch the selected tab to new tab
     win.gBrowser.selectedTab = newTab;
 
-    whenTabViewIsHidden(function () {
-      is(win.gBrowser.selectedTab, newTab, "The seleted tab should be the same as before (new tab)");
-       win.close();
-       finish();
-    });
-
-    // show tabview
-    EventUtils.synthesizeKey("e", { accelKey: true, shiftKey: true }, win);
-    // hide tabview
-    EventUtils.synthesizeKey("e", { accelKey: true, shiftKey: true }, win);
-  })
+    showTabView(function () {
+      hideTabView(function () {
+        is(win.gBrowser.selectedTab, newTab,
+           "The selected tab should be the same as before (new tab)");
+        waitForFocus(finish);
+      }, win);
+    }, win);
+  }, win);
 }
--- a/browser/base/content/test/tabview/browser_tabview_bug599626.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug599626.js
@@ -16,19 +16,17 @@ function onTabViewShown() {
 
   afterAllTabsLoaded(function () {
     testStayOnPage(contentWindow, groupItemOne, groupItemTwo);
   });
 }
 
 function testStayOnPage(contentWindow, groupItemOne, groupItemTwo) {
   whenDialogOpened(function (dialog) {
-    groupItemTwo.addSubscriber("groupShown", function onShown() {
-      groupItemTwo.removeSubscriber("groupShown", onShown);
-
+    executeSoon(function () {
       is(gBrowser.tabs.length, 2, 
          "The total number of tab is 2 when staying on the page");
       is(contentWindow.TabItems.getItems().length, 2, 
          "The total number of tab items is 2 when staying on the page");
 
       showTabView(function () {
         // start the next test
         testLeavePage(contentWindow, groupItemOne, groupItemTwo);
--- a/browser/base/content/test/tabview/browser_tabview_bug604098.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug604098.js
@@ -20,16 +20,17 @@ function test() {
   });
 }
 
 function test1() {
   let groupItems = contentWindow.GroupItems.groupItems;
   is(groupItems.length, 1, "there is one groupItem");
 
   whenTabViewIsHidden(function() {
+    gBrowser.selectedTab = gBrowser.tabs[0];
     is(groupItems.length, 2, "there are two groupItems");
     closeGroupItem(groupItems[1], finish);
   });
 
   // first click
   mouseClick(contentElement, 0);
   // second click
   mouseClick(contentElement, 0);
--- a/browser/base/content/test/tabview/browser_tabview_bug624953.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug624953.js
@@ -13,11 +13,11 @@ function test() {
 
     groupItem.setSize(100, 100, true);
     groupItem.setUserSize();
     ok(!groupItem.isStacked(), 'groupItem is still not stacked');
 
     cw.GroupItems.resumeArrange();
     ok(groupItem.isStacked(), 'groupItem is now stacked');
 
-    closeGroupItem(groupItem, finish);
+    closeGroupItem(groupItem, function () hideTabView(finish));
   });
 }
--- a/browser/base/content/test/tabview/browser_tabview_bug625195.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug625195.js
@@ -29,17 +29,17 @@ function test() {
 
     whenTabViewIsHidden(function() {
       popup(gBrowser.tabs[0]);
 
       ok(!document.getElementById("context_closeTab").disabled, "The 'Close tab' menu item is enabled");
       ok(!document.getElementById("context_openTabInWindow").disabled, "The 'Move to New Window' menu item is enabled");
 
       let newTabTwo = gBrowser.selectedTab;
-      gBrowser.selected = originalTab;
+      gBrowser.selectedTab = originalTab;
 
       gBrowser.removeTab(newTabOne);
       gBrowser.removeTab(newTabTwo);
 
       finish();
     });
     let newGroup = contentWindow.GroupItems.newGroup();
     newGroup.newTab();
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/tabview/browser_tabview_bug626455.js
@@ -0,0 +1,127 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ *
+ * Contributor(s):
+ *   Mihai Sucan <mihai.sucan@gmail.com>
+ *   Raymond Lee <raymond@appcoast.com>
+ */
+
+const TEST_URL = 'data:text/html,<script>window.onbeforeunload=' +
+                 'function(e){e.returnValue="?"}</script><body ' +
+                 'onunload="alert(\'onunload\')" onpagehide="' +
+                 'alert(\'onpagehide\')"></body>';
+
+let contentWindow;
+let activeGroup;
+
+function test() {
+  waitForExplicitFinish();
+
+  showTabView(function () {
+    contentWindow = TabView.getContentWindow();
+    activeGroup = contentWindow.GroupItems.getActiveGroupItem();
+
+    gBrowser.browsers[0].contentWindow.location =
+      "data:text/html,<p>test for bug 626455, tab1";
+    gBrowser.addTab(TEST_URL);
+
+    afterAllTabsLoaded(testStayOnPage);
+  });
+}
+
+function testStayOnPage() {
+  whenDialogOpened(function (dialog) {
+    // stay on page
+    dialog.cancelDialog();
+
+    executeSoon(function () {
+      showTabView(function () {
+        is(gBrowser.tabs.length, 1,
+           "The total number of tab is 1 when staying on the page");
+
+        let location = gBrowser.browsers[0].contentWindow.location.toString();
+        isnot(location.indexOf("onbeforeunload"), -1,
+              "The open tab is the expected one");
+
+        is(contentWindow.GroupItems.getActiveGroupItem(), activeGroup,
+           "Active group is still the same");
+
+        is(contentWindow.GroupItems.groupItems.length, 1,
+           "Only one group is open");
+
+        // start the next test
+        testLeavePage();
+      });
+    });
+  });
+
+  closeGroupItem(activeGroup);
+}
+
+function testLeavePage() {
+  let dialogsAccepted = 0;
+
+  whenDialogOpened(function onDialogOpened(dialog) {
+    if (++dialogsAccepted < 3)
+      whenDialogOpened(onDialogOpened);
+
+    // Leave page
+    dialog.acceptDialog();
+  });
+
+  whenGroupClosed(activeGroup, finishTest);
+  closeGroupItem(activeGroup);
+}
+
+function finishTest() {
+  is(gBrowser.tabs.length, 1,
+     "The total number of tab is 1 after leaving the page");
+  is(contentWindow.TabItems.getItems().length, 1,
+     "The total number of tab items is 1 after leaving the page");
+
+  let location = gBrowser.browsers[0].contentWindow.location.toString();
+  is(location, "about:blank", "The open tab is the expected one");
+
+  isnot(contentWindow.GroupItems.getActiveGroupItem(), activeGroup,
+     "Active group is no longer the same");
+
+  is(contentWindow.GroupItems.groupItems.length, 1,
+     "Only one group is open");
+
+  finish();
+}
+
+// ----------
+function whenGroupClosed(group, callback) {
+  group.addSubscriber("close", function onClose() {
+    group.removeSubscriber("close", onClose);
+    callback();
+  });
+}
+
+// ----------
+function whenDialogOpened(callback) {
+  let wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+           .getService(Ci.nsIWindowMediator);
+
+  let listener = {
+    onCloseWindow: function () {},
+    onWindowTitleChange: function () {},
+
+    onOpenWindow: function (xulWin) {
+      let domWin = xulWin.QueryInterface(Ci.nsIInterfaceRequestor)
+                   .getInterface(Ci.nsIDOMWindow);
+
+      whenWindowLoaded(domWin, function () {
+        let dialog = domWin.document.querySelector("dialog");
+        if (dialog) {
+          wm.removeListener(listener);
+          callback(dialog);
+        }
+      });
+    }
+  };
+
+  wm.addListener(listener);
+}
--- a/browser/base/content/test/tabview/browser_tabview_bug663421.js
+++ b/browser/base/content/test/tabview/browser_tabview_bug663421.js
@@ -62,17 +62,17 @@ function test() {
     let target = tab._tabViewTabItem.container;
 
     waitForFocus(function () {
       EventUtils.synthesizeMouseAtCenter(target, {type: "mousedown"}, cw);
       EventUtils.synthesizeMouse(target, 600, 5, {type: "mousemove"}, cw);
       EventUtils.synthesizeMouse(target, 600, 5, {type: "mouseup"}, cw);
 
       checkNumberOfGroupItems(2);
-      next();
+      closeGroupItem(cw.GroupItems.groupItems[1], next);
     }, win);
   }
 
   let tests = [test1, test2, test3, test4];
 
   waitForExplicitFinish();
 
   newWindowWithTabView(function (aWin) {
--- a/browser/base/content/test/tabview/browser_tabview_dragdrop.js
+++ b/browser/base/content/test/tabview/browser_tabview_dragdrop.js
@@ -1,56 +1,39 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function test() {
   waitForExplicitFinish();
-
-  window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
-  TabView.toggle();
+  showTabView(onTabViewShown);
 }
 
-function onTabViewWindowLoaded() {
-  window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
-
+function onTabViewShown() {
   ok(TabView.isVisible(), "Tab View is visible");
 
-  let contentWindow = document.getElementById("tab-view").contentWindow;
+  let contentWindow = TabView.getContentWindow();
   let [originalTab] = gBrowser.visibleTabs;
 
+  let createGroupItem = function (left, top, width, height) {
+    let box = new contentWindow.Rect(left, top, width, height);
+    let groupItem = new contentWindow.GroupItem([], {bounds: box, immediately: true});
+
+    contentWindow.UI.setActive(groupItem);
+    gBrowser.loadOneTab("about:blank", {inBackground: true});
+
+    return groupItem;
+  };
+
   // create group one and two
-  let boxOne = new contentWindow.Rect(20, 20, 300, 300);
-  let groupOne = new contentWindow.GroupItem([], { bounds: boxOne });
-  ok(groupOne.isEmpty(), "This group is empty");
-
-  let boxTwo = new contentWindow.Rect(20, 400, 300, 300);
-  let groupTwo = new contentWindow.GroupItem([], { bounds: boxTwo });
-
-  groupOne.addSubscriber("childAdded", function onChildAdded() {
-    groupOne.removeSubscriber("childAdded", onChildAdded);
-    groupTwo.newTab();
-  });
+  let groupOne = createGroupItem(20, 20, 300, 300);
+  let groupTwo = createGroupItem(20, 400, 300, 300);
 
-  let count = 0;
-  let onTabViewShown = function() {
-    if (count == 2) {
-      window.removeEventListener("tabviewshown", onTabViewShown, false);
-      addTest(contentWindow, groupOne.id, groupTwo.id, originalTab);
-    }
-  };
-  let onTabViewHidden = function() {
-    TabView.toggle();
-    if (++count == 2)
-      window.removeEventListener("tabviewhidden", onTabViewHidden, false);
-  };
-  window.addEventListener("tabviewshown", onTabViewShown, false);
-  window.addEventListener("tabviewhidden", onTabViewHidden, false);
-
-  // open tab in group
-  groupOne.newTab();
+  waitForFocus(function () {
+    addTest(contentWindow, groupOne.id, groupTwo.id, originalTab);
+  });
 }
 
 function addTest(contentWindow, groupOneId, groupTwoId, originalTab) {
   let groupOne = contentWindow.GroupItems.groupItem(groupOneId);
   let groupTwo = contentWindow.GroupItems.groupItem(groupTwoId);
   let groupOneTabItemCount = groupOne.getChildren().length;
   let groupTwoTabItemCount = groupTwo.getChildren().length;
   is(groupOneTabItemCount, 1, "GroupItem one has one tab");
@@ -69,34 +52,23 @@ function addTest(contentWindow, groupOne
 
   function endGame() {
     groupTwo.removeSubscriber("childAdded", endGame);
 
     is(groupOne.getChildren().length, --groupOneTabItemCount,
        "The number of children in group one is decreased by 1");
     is(groupTwo.getChildren().length, ++groupTwoTabItemCount,
        "The number of children in group two is increased by 1");
-  
-    let onTabViewHidden = function() {
-      window.removeEventListener("tabviewhidden", onTabViewHidden, false);
-      groupTwo.closeAll();
-      // close undo group
-      let closeButton = groupTwo.$undoContainer.find(".close");
-      EventUtils.sendMouseEvent(
-        { type: "click" }, closeButton[0], contentWindow);
-    };
-    groupTwo.addSubscriber("close", function onClose() {
-      groupTwo.removeSubscriber("close", onClose);
-      finish(); 
+
+    closeGroupItem(groupOne, function () {
+      closeGroupItem(groupTwo, function () hideTabView(finish));
     });
-    window.addEventListener("tabviewhidden", onTabViewHidden, false);
-    gBrowser.selectedTab = originalTab;
   }
+
   groupTwo.addSubscriber("childAdded", endGame);
-  
   simulateDragDrop(tabItem.container, offsetX, offsetY, contentWindow);
 }
 
 function simulateDragDrop(element, offsetX, offsetY, contentWindow) {
   let rect = element.getBoundingClientRect();
   let startX = (rect.right - rect.left)/2;
   let startY = (rect.bottom - rect.top)/2;
   let incrementX = offsetX / 2;
--- a/browser/base/content/test/tabview/browser_tabview_launch.js
+++ b/browser/base/content/test/tabview/browser_tabview_launch.js
@@ -1,52 +1,54 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
-let timerId;
 let newWin;
 
 // ----------
 function test() {
   waitForExplicitFinish();
 
-  // launch tab view for the first time
-  newWindowWithTabView(function() {}, function(win) {
+  let panelSelected = false;
+  registerCleanupFunction(function () ok(panelSelected, "panel is selected"));
+
+  let onLoad = function (win) {
+    registerCleanupFunction(function () win.close());
+
     newWin = win;
 
     let onSelect = function(event) {
       if (deck != event.target)
         return;
 
       let iframe = win.document.getElementById("tab-view");
       if (deck.selectedPanel != iframe)
         return;
 
       deck.removeEventListener("select", onSelect, true);
-
-      whenTabViewIsShown(function() {
-        executeSoon(function() {
-          testMethodToHideAndShowTabView(function() {
-            newWin.document.getElementById("menu_tabview").doCommand();
-          }, function() {
-            testMethodToHideAndShowTabView(function() {
-              EventUtils.synthesizeKey("e", { accelKey: true, shiftKey: true }, newWin);
-            }, finish);
-          });
-        });
-      }, win);
+      panelSelected = true;
     };
 
     let deck = win.document.getElementById("tab-view-deck");
     deck.addEventListener("select", onSelect, true);
-  });
+  };
 
-  registerCleanupFunction(function () {
-    newWin.close();
-  });
+  let onShow = function (win) {
+    executeSoon(function() {
+      testMethodToHideAndShowTabView(function() {
+        newWin.document.getElementById("menu_tabview").doCommand();
+      }, function() {
+        testMethodToHideAndShowTabView(function() {
+          EventUtils.synthesizeKey("E", { accelKey: true, shiftKey: true }, newWin);
+        }, finish);
+      });
+    });
+  };
+
+  newWindowWithTabView(onShow, onLoad);
 }
 
 function testMethodToHideAndShowTabView(executeFunc, callback) {
   whenTabViewIsHidden(function() {
     ok(!newWin.TabView.isVisible(), "Tab View is not visible after executing the function");
     whenTabViewIsShown(function() {
       ok(newWin.TabView.isVisible(), "Tab View is visible after executing the function again");
       callback();
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -48,16 +48,17 @@ browser.jar:
 *       content/browser/sanitize.xul                  (content/sanitize.xul)
 *       content/browser/sanitizeDialog.js             (content/sanitizeDialog.js)
         content/browser/sanitizeDialog.css            (content/sanitizeDialog.css)
 *       content/browser/tabbrowser.css                (content/tabbrowser.css)
 *       content/browser/tabbrowser.xml                (content/tabbrowser.xml)
         content/browser/tabview.css                   (content/tabview/tabview.css)
 *       content/browser/tabview.js                    (content/tabview/tabview.js)
         content/browser/tabview.html                  (content/tabview/tabview.html)
+        content/browser/tabview-content.js            (content/tabview/content.js)
 *       content/browser/urlbarBindings.xml            (content/urlbarBindings.xml)
 *       content/browser/utilityOverlay.js             (content/utilityOverlay.js)
 *       content/browser/web-panels.js                 (content/web-panels.js)
 *       content/browser/web-panels.xul                (content/web-panels.xul)
 *       content/browser/baseMenuOverlay.xul           (content/baseMenuOverlay.xul)
 *       content/browser/nsContextMenu.js              (content/nsContextMenu.js)
 #ifdef MOZ_SERVICES_SYNC
 *       content/browser/aboutSyncTabs.xul             (content/aboutSyncTabs.xul)