☠☠ backed out by e8615e856867 ☠ ☠ | |
author | Raymond Lee <raymond@raysquare.com> |
Wed, 24 Nov 2010 01:13:29 +0800 | |
changeset 58102 | 5cdcba8db3a2130d4b50f56177cc9f9d567b4390 |
parent 58101 | 88cca1740749b379494db465135b23669ed9b5d4 |
child 58103 | 89d20196334387aecb5ef1c67382041b38c8a7e8 |
child 58117 | e8615e856867ec4803ad0c386d1a26e019f49840 |
push id | 17157 |
push user | ian@iangilman.com |
push date | Tue, 23 Nov 2010 22:33:58 +0000 |
treeherder | mozilla-central@597e4c5ded14 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | ian, benjamin |
bugs | 597248 |
milestone | 2.0b8pre |
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
|
--- a/browser/base/content/tabview/groupitems.js +++ b/browser/base/content/tabview/groupitems.js @@ -1873,17 +1873,17 @@ let GroupItems = { gBrowser.visibleTabs.some(function(tab) { if (!tab.pinned && tab != tabItem.tab) { otherTab = tab; return true; } return false; }); - if (otherTab) { + if (otherTab && otherTab.tabItem) { // the first visible tab belongs to a group, add the new tabItem into // that group if (otherTab.tabItem.parent) { let groupItem = otherTab.tabItem.parent; groupItem.add(tabItem); this.setActiveGroupItem(groupItem); return; }
--- a/browser/base/content/tabview/tabitems.js +++ b/browser/base/content/tabview/tabitems.js @@ -63,18 +63,19 @@ function TabItem(tab, options) { .addClass('tab') .html("<div class='thumb'>" + "<img class='cached-thumb' style='display:none'/><canvas/></div>" + "<div class='favicon'><img/></div>" + "<span class='tab-title'> </span>" ) .appendTo('body'); + this._cachedImageData = null; + this.shouldHideCachedData = false; this.canvasSizeForced = false; - this.isShowingCachedData = false; this.favEl = (iQ('.favicon', $div))[0]; this.favImgEl = (iQ('.favicon>img', $div))[0]; this.nameEl = (iQ('.tab-title', $div))[0]; this.thumbEl = (iQ('.thumb', $div))[0]; this.canvasEl = (iQ('.thumb canvas', $div))[0]; this.cachedThumbEl = (iQ('img.cached-thumb', $div))[0]; this.tabCanvas = new TabCanvas(this.tab, this.canvasEl); @@ -237,54 +238,85 @@ TabItem.prototype = Utils.extend(new Ite // size of thumbnail on screen. Note that this call does not nest, unlike // <TabItems.resumePainting>; if you call forceCanvasSize multiple // times, you just need a single unforce to clear them all. unforceCanvasSize: function TabItem_unforceCanvasSize() { this.canvasSizeForced = false; }, // ---------- + // Function: isShowingCachedData + // Returns a boolean indicates whether the cached data is being displayed or + // not. + isShowingCachedData: function() { + return (this._cachedImageData != null); + }, + + // ---------- // Function: showCachedData // Shows the cached data i.e. image and title. Note: this method should only // be called at browser startup with the cached data avaliable. + // + // Parameters: + // tabData - the tab data showCachedData: function TabItem_showCachedData(tabData) { - this.isShowingCachedData = true; - var $nameElement = iQ(this.nameEl); - var $canvasElement = iQ(this.canvasEl); - var $cachedThumbElement = iQ(this.cachedThumbEl); - $cachedThumbElement.attr("src", tabData.imageData).show(); + if (!this._cachedImageData) { + TabItems.cachedDataCounter++; + this.tab.linkedBrowser._tabViewTabItemWithCachedData = this; + if (TabItems.cachedDataCounter == 1) + gBrowser.addTabsProgressListener(TabItems.tabsProgressListener); + } + this._cachedImageData = tabData.imageData; + let $nameElement = iQ(this.nameEl); + let $canvasElement = iQ(this.canvasEl); + let $cachedThumbElement = iQ(this.cachedThumbEl); + $cachedThumbElement.attr("src", this._cachedImageData).show(); $canvasElement.css({opacity: 0.0}); $nameElement.text(tabData.title ? tabData.title : ""); }, // ---------- // Function: hideCachedData // Hides the cached data i.e. image and title and show the canvas. hideCachedData: function TabItem_hideCachedData() { - var $canvasElement = iQ(this.canvasEl); - var $cachedThumbElement = iQ(this.cachedThumbEl); + let $canvasElement = iQ(this.canvasEl); + let $cachedThumbElement = iQ(this.cachedThumbEl); $cachedThumbElement.hide(); $canvasElement.css({opacity: 1.0}); - this.isShowingCachedData = false; + if (this._cachedImageData) { + TabItems.cachedDataCounter--; + this._cachedImageData = null; + this.tab.linkedBrowser._tabViewTabItemWithCachedData = null; + if (TabItems.cachedDataCounter == 0) + gBrowser.removeTabsProgressListener(TabItems.tabsProgressListener); + } }, // ---------- // Function: getStorageData // Get data to be used for persistent storage of this object. // // Parameters: // getImageData - true to include thumbnail pixels (and page title as well); default false getStorageData: function TabItem_getStorageData(getImageData) { + let imageData = null; + + if (getImageData) { + if (this._cachedImageData) + imageData = this._cachedImageData; + else if (this.tabCanvas) + imageData = this.tabCanvas.toImageData(); + } + return { bounds: this.getBounds(), userSize: (Utils.isPoint(this.userSize) ? new Point(this.userSize) : null), url: this.tab.linkedBrowser.currentURI.spec, groupID: (this.parent ? this.parent.id : 0), - imageData: (getImageData && this.tabCanvas ? - this.tabCanvas.toImageData() : null), + imageData: imageData, title: getImageData && this.tab.label || null }; }, // ---------- // Function: save // Store persistent for this object. // @@ -697,39 +729,53 @@ TabItem.prototype = Utils.extend(new Ite // Singleton for managing <TabItem>s let TabItems = { minTabWidth: 40, tabWidth: 160, tabHeight: 120, fontSize: 9, items: [], paintingPaused: 0, + cachedDataCounter: 0, // total number of cached data being displayed. + tabsProgressListener: null, _tabsWaitingForUpdate: [], _heartbeatOn: false, // see explanation at startHeartbeat() below _heartbeatTiming: 100, // milliseconds between _checkHeartbeat() calls _lastUpdateTime: Date.now(), _eventListeners: [], tempCanvas: null, // ---------- // Function: init // Set up the necessary tracking to maintain the <TabItems>s. init: function TabItems_init() { Utils.assert(window.AllTabs, "AllTabs must be initialized first"); - var self = this; + let self = this; let $canvas = iQ("<canvas>"); $canvas.appendTo(iQ("body")); $canvas.hide(); this.tempCanvas = $canvas[0]; // 150 pixels is an empirical size, below which FF's drawWindow() // algorithm breaks down this.tempCanvas.width = 150; this.tempCanvas.height = 150; + this.tabsProgressListener = { + onStateChange: function(browser, webProgress, request, stateFlags, status) { + if ((stateFlags & Ci.nsIWebProgressListener.STATE_STOP) && + (stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW)) { + // browser would only has _tabViewTabItemWithCachedData if + // it's showing cached data. + if (browser._tabViewTabItemWithCachedData) + browser._tabViewTabItemWithCachedData.shouldHideCachedData = true; + } + } + }; + // When a tab is opened, create the TabItem this._eventListeners["open"] = function(tab) { if (tab.ownerDocument.defaultView != gWindow || tab.pinned) return; self.link(tab); } // When a tab's content is loaded, show the canvas and hide the cached data @@ -759,16 +805,19 @@ let TabItems = { self.link(tab, {immediately: true}); self.update(tab); }); }, // ---------- // Function: uninit uninit: function TabItems_uninit() { + if (this.tabsProgressListener) + gBrowser.removeTabsProgressListener(this.tabsProgressListener); + for (let name in this._eventListeners) { AllTabs.unregister(name, this._eventListeners[name]); } this.items.forEach(function(tabItem) { for (let x in tabItem) { if (typeof tabItem[x] == "object") tabItem[x] = null; } @@ -791,17 +840,17 @@ let TabItems = { let shouldDefer = ( this.isPaintingPaused() || this._tabsWaitingForUpdate.length || Date.now() - this._lastUpdateTime < this._heartbeatTiming ); let isCurrentTab = ( - !UI._isTabViewVisible() && + !UI.isTabViewVisible() && tab == gBrowser.selectedTab ); if (shouldDefer && !isCurrentTab) { if (this._tabsWaitingForUpdate.indexOf(tab) == -1) this._tabsWaitingForUpdate.push(tab); this.startHeartbeat(); } else @@ -845,17 +894,17 @@ let TabItems = { this.reconnect(tabItem); tabItem.save(); } // ___ label let label = tab.label; let $name = iQ(tabItem.nameEl); - if (!tabItem.isShowingCachedData && $name.text() != label) + if (!tabItem.isShowingCachedData() && $name.text() != label) $name.text(label); // ___ thumbnail let $canvas = iQ(tabItem.canvasEl); if (!tabItem.canvasSizeForced) { let w = $canvas.width(); let h = $canvas.height(); if (w != tabItem.canvasEl.width || h != tabItem.canvasEl.height) { @@ -865,18 +914,17 @@ let TabItems = { } this._lastUpdateTime = Date.now(); tabItem._lastTabUpdateTime = this._lastUpdateTime; tabItem.tabCanvas.paint(); // ___ cache - // TODO: this logic needs to be better; hiding too soon now - if (tabItem.isShowingCachedData && !tab.hasAttribute("busy")) + if (tabItem.isShowingCachedData() && tabItem.shouldHideCachedData) tabItem.hideCachedData(); } catch(e) { Utils.log(e); } }, // ---------- // Function: link @@ -1085,26 +1133,18 @@ let TabItems = { // if it matches the selected tab or no active tab and the browser // tab is hidden, the active group item would be set. if (item.tab == gBrowser.selectedTab || (!GroupItems.getActiveGroupItem() && !item.tab.hidden)) GroupItems.setActiveGroupItem(item.parent); } } - if (tabData.imageData) { + if (tabData.imageData) item.showCachedData(tabData); - // the code in the progress listener doesn't fire sometimes because - // tab is being restored so need to catch that. - setTimeout(function() { - if (item && item.isShowingCachedData) { - item.hideCachedData(); - } - }, 15000); - } item.reconnected = true; found = {addedToGroup: tabData.groupID}; } else { // We should never have any orphaned tabs. Therefore, item is not // connected if it has no parent and GroupItems.newTab() would handle // the group creation. item.reconnected = (item.parent != null);
--- a/browser/base/content/tabview/ui.js +++ b/browser/base/content/tabview/ui.js @@ -205,17 +205,17 @@ let UI = { iQ(window).resize(function() { self._resize(); }); // ___ setup observer to save canvas images var observer = { observe : function(subject, topic, data) { if (topic == "quit-application-requested") { - if (self._isTabViewVisible()) { + if (self.isTabViewVisible()) { GroupItems.removeHiddenGroups(); TabItems.saveAll(true); } self._save(); } } }; Services.obs.addObserver(observer, "quit-application-requested", false); @@ -374,19 +374,19 @@ let UI = { self._activeTab = null; }); this._activeTab.makeActive(); } }, // ---------- - // Function: _isTabViewVisible + // Function: isTabViewVisible // Returns true if the TabView UI is currently shown. - _isTabViewVisible: function UI__isTabViewVisible() { + isTabViewVisible: function UI_isTabViewVisible() { return gTabViewDeck.selectedIndex == 1; }, // --------- // Function: _initPageDirection // Initializes the page base direction _initPageDirection: function UI__initPageDirection() { let chromeReg = Cc["@mozilla.org/chrome/chrome-registry;1"]. @@ -397,17 +397,17 @@ 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()) return; // initialize the direction of the page this._initPageDirection(); var self = this; var currentTab = this._currentTab; var item = null; @@ -463,17 +463,17 @@ let UI = { TabItems.resumePainting(); }, // ---------- // Function: hideTabView // Hides TabView and shows the main browser UI. hideTabView: function UI_hideTabView() { - if (!this._isTabViewVisible()) + if (!this.isTabViewVisible()) return; // 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) { @@ -562,18 +562,18 @@ let UI = { // don't reenter Panorama due to all of the session restore tab // manipulation (which otherwise we might). When transitioning away from // PB, we reenter Panorama if we had been there directly before PB. function pbObserver(aSubject, aTopic, aData) { if (aTopic == "private-browsing") { self._privateBrowsing.transitionStage = 3; if (aData == "enter") { // If we are in Tab View, exit. - self._privateBrowsing.wasInTabView = self._isTabViewVisible(); - if (self._isTabViewVisible()) + self._privateBrowsing.wasInTabView = self.isTabViewVisible(); + if (self.isTabViewVisible()) self.goToTab(gBrowser.selectedTab); } } else if (aTopic == "private-browsing-change-granted") { if (aData == "enter" || aData == "exit") { self._privateBrowsing.transitionStage = 1; self._privateBrowsing.transitionMode = aData; } } @@ -601,17 +601,17 @@ let UI = { this._eventListeners.close = function(tab) { if (tab.ownerDocument.defaultView != gWindow) return; // if it's an app tab, remove it from all the group items if (tab.pinned) GroupItems.removeAppTab(tab); - if (self._isTabViewVisible()) { + if (self.isTabViewVisible()) { // just closed the selected tab in the TabView interface. if (self._currentTab == tab) self._closedSelectedTabInTabView = true; } else { // If we're currently in the process of entering private browsing, // we don't want to go to the Tab View UI. if (self._privateBrowsing.transitionStage > 0) return; @@ -714,29 +714,29 @@ let UI = { // ---------- // Function: onTabSelect // Called when the user switches from one tab to another outside of the TabView UI. onTabSelect: function UI_onTabSelect(tab) { let currentTab = this._currentTab; this._currentTab = tab; // if the last visible tab has just been closed, don't show the chrome UI. - if (this._isTabViewVisible() && + if (this.isTabViewVisible() && (this._closedLastVisibleTab || this._closedSelectedTabInTabView)) { this._closedLastVisibleTab = false; this._closedSelectedTabInTabView = false; return; } // reset these vars, just in case. this._closedLastVisibleTab = false; this._closedSelectedTabInTabView = false; // if TabView is visible but we didn't just close the last tab or // selected tab, show chrome. - if (this._isTabViewVisible()) + if (this.isTabViewVisible()) this.hideTabView(); // another tab might be selected when hideTabView() is invoked so a // validation is needed. if (this._currentTab != tab) return; let oldItem = null; @@ -784,31 +784,31 @@ let UI = { // ---------- // 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()) { + if (this.isTabViewVisible()) { var index = this._reorderTabsOnHide.indexOf(groupItem); if (index == -1) this._reorderTabsOnHide.push(groupItem); } }, // ---------- // Function: setReorderTabItemsOnShow // Sets the groupItem which the tab items should be re-ordered when // switching to the tab view UI. // Parameters: // groupItem - the groupItem which would be used for re-ordering tab items. setReorderTabItemsOnShow: function UI_setReorderTabItemsOnShow(groupItem) { - if (!this._isTabViewVisible()) { + if (!this.isTabViewVisible()) { var index = this._reorderTabItemsOnShow.indexOf(groupItem); if (index == -1) this._reorderTabItemsOnShow.push(groupItem); } }, // ---------- updateTabButton: function UI__updateTabButton(){ @@ -1078,17 +1078,17 @@ let UI = { if (typeof force == "undefined") force = false; if (!this._pageBounds) return; // If TabView isn't focused and is not showing, don't perform a resize. // This resize really slows things down. - if (!force && !this._isTabViewVisible()) + if (!force && !this.isTabViewVisible()) return; var oldPageBounds = new Rect(this._pageBounds); var newPageBounds = Items.getPageBounds(); if (newPageBounds.equals(oldPageBounds)) return; var items = Items.getTopLevelItems();
--- a/browser/base/content/test/tabview/Makefile.in +++ b/browser/base/content/test/tabview/Makefile.in @@ -55,16 +55,17 @@ include $(topsrcdir)/config/rules.mk browser_tabview_bug591706.js \ browser_tabview_bug594176.js \ browser_tabview_bug595191.js \ browser_tabview_bug595518.js \ browser_tabview_bug595521.js \ browser_tabview_bug595804.js \ browser_tabview_bug595930.js \ browser_tabview_bug595943.js \ + browser_tabview_bug597248.js \ browser_tabview_bug597399.js \ browser_tabview_bug598600.js \ browser_tabview_bug599626.js \ browser_tabview_dragdrop.js \ browser_tabview_exit_button.js \ browser_tabview_group.js \ browser_tabview_launch.js \ browser_tabview_orphaned_tabs.js \
new file mode 100644 --- /dev/null +++ b/browser/base/content/test/tabview/browser_tabview_bug597248.js @@ -0,0 +1,192 @@ +/* ***** 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 tabview bug 597248 test. + * + * The Initial Developer of the Original Code is + * Mozilla Foundation. + * Portions created by the Initial Developer are Copyright (C) 2010 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * Raymond Lee <raymond@appcoast.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 ***** */ + +let newWin; +let restoredWin; +let newTabOne; +let newTabTwo; +let restoredNewTabOneLoaded = false; +let restoredNewTabTwoLoaded = false; +let frameInitialized = false; + +function test() { + waitForExplicitFinish(); + + // open a new window + newWin = openDialog(getBrowserURL(), "_blank", "chrome,all,dialog=no"); + newWin.addEventListener("load", function(event) { + newWin.removeEventListener("load", arguments.callee, false); + setupOne(); + }, false); +} + +function setupOne() { + let loadedCount = 0; + let allLoaded = function() { + if (++loadedCount == 2) { + newWin.addEventListener("tabviewshown", setupTwo, false); + newWin.TabView.toggle(); + } + } + + newTabOne = newWin.gBrowser.tabs[0]; + newTabTwo = newWin.gBrowser.addTab(); + load(newTabOne, "http://mochi.test:8888/", allLoaded); + load(newTabTwo, "http://mochi.test:8888/browser/browser/base/content/test/tabview/dummy_page.html", allLoaded); +} + +function setupTwo() { + newWin.removeEventListener("tabviewshown", setupTwo, false); + + let contentWindow = newWin.document.getElementById("tab-view").contentWindow; + + let tabItems = contentWindow.TabItems.getItems(); + is(tabItems.length, 2, "There should be 2 tab items before closing"); + + // force all canvas to update + tabItems.forEach(function(tabItem) { + contentWindow.TabItems._update(tabItem.tab); + }); + + // stimulate a quit application requested so the image data gets stored. + let quitRequestObserver = function(aSubject, aTopic, aData) { + ok(aTopic == "quit-application-requested" && + aSubject instanceof Ci.nsISupportsPRBool, + "Received a quit request and going to deny it"); + Services.obs.removeObserver(quitRequestObserver, "quit-application-requested", false); + + aSubject.data = true; + } + Services.obs.addObserver(quitRequestObserver, "quit-application-requested", false); + ok(!Application.quit(), "Tried to quit and canceled it"); + + // check the storage for stored image data. + tabItems.forEach(function(tabItem) { + let tabData = contentWindow.Storage.getTabData(tabItem.tab); + ok(tabData && tabData.imageData, "TabItem has stored image data before closing"); + }); + + // close the new window and restore it. + newWin.addEventListener("unload", function(event) { + newWin.removeEventListener("unload", arguments.callee, false); + newWin = null; + + // restore window and test it + restoredWin = undoCloseWindow(); + restoredWin.addEventListener("load", function(event) { + restoredWin.removeEventListener("load", arguments.callee, false); + + // setup tab variables and listen to the load progress. + newTabOne = restoredWin.gBrowser.tabs[0]; + newTabTwo = restoredWin.gBrowser.tabs[1]; + restoredWin.gBrowser.addTabsProgressListener(gTabsProgressListener); + + // execute code when the frame isninitialized. + restoredWin.addEventListener("tabviewframeinitialized", onTabViewFrameInitialized, false); + }, false); + }, false); + + newWin.close(); +} + +let gTabsProgressListener = { + onStateChange: function(browser, webProgress, request, stateFlags, status) { + if (stateFlags & Ci.nsIWebProgressListener.STATE_STOP && + stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) { + if (newTabOne.linkedBrowser == browser) + restoredNewTabOneLoaded = true; + else if (newTabTwo.linkedBrowser == browser) + restoredNewTabTwoLoaded = true; + + // since we are not sure whether the frame is initialized first or two tabs + // compete loading first so we need this. + if (restoredNewTabOneLoaded && restoredNewTabTwoLoaded) { + if (frameInitialized) { + // since a tabs progress listener is used in the code to set + // tabItem.shouldHideCachedData, executeSoon is used to avoid a racing + // condition. + executeSoon(updateAndCheck); + } + restoredWin.gBrowser.removeTabsProgressListener(gTabsProgressListener); + } + } + } +}; + +function onTabViewFrameInitialized() { + restoredWin.removeEventListener("tabviewframeinitialized", onTabViewFrameInitialized, false); + + let contentWindow = restoredWin.document.getElementById("tab-view").contentWindow; + + let tabItems = contentWindow.TabItems.getItems(); + tabItems.forEach(function(tabItem) { + ok(tabItem.isShowingCachedData(), "Tab item is showing cached data"); + }); + + // since we are not sure whether the frame is initialized first or two tabs + // compete loading first so we need this. + if (restoredNewTabOneLoaded && restoredNewTabTwoLoaded) { + // executeSoon is used to ensure tabItem.shouldHideCachedData is set + // because tabs progress listener might run at the same time as this test code. + executeSoon(updateAndCheck); + } else + frameInitialized = true; +} + +function updateAndCheck() { + // force all canvas to update + let contentWindow = restoredWin.document.getElementById("tab-view").contentWindow; + + let tabItems = contentWindow.TabItems.getItems(); + tabItems.forEach(function(tabItem) { + contentWindow.TabItems._update(tabItem.tab); + ok(!tabItem.isShowingCachedData(), "Tab item is not showing cached data anymore"); + }); + + // clean up and finish + restoredWin.close(); + finish(); +} + +function load(tab, url, callback) { + tab.linkedBrowser.addEventListener("load", function (event) { + tab.linkedBrowser.removeEventListener("load", arguments.callee, true); + callback(); + }, true); + tab.linkedBrowser.loadURI(url); +} +