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 75369 1608aa3ce9d38ee758d4ac843e125fcdc14b804d
parent 75368 621138727ae41c530c3a9f812bbc93e1164263b8
child 75370 c762e01dd6dc27a90357a9d6388967d9412c2522
child 75378 7a08fd89d10aefbe28fc9a03c643b06650f1b412
push id21011
push userrcampbell@mozilla.com
push dateTue, 16 Aug 2011 15:35:10 +0000
treeherdermozilla-central@e765c8f565c6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdietrich
bugs626455
milestone8.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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)