Bug 597248 - Make sure Panorama's thumbnail cache is solid [r=ian, a=benjamin]

This commit is contained in:
Raymond Lee 2010-11-24 01:13:29 +08:00
parent bf2c269162
commit 3cc05bb646
5 changed files with 272 additions and 39 deletions

View File

@ -1878,7 +1878,7 @@ let GroupItems = {
return false; return false;
}); });
if (otherTab) { if (otherTab && otherTab.tabItem) {
// the first visible tab belongs to a group, add the new tabItem into // the first visible tab belongs to a group, add the new tabItem into
// that group // that group
if (otherTab.tabItem.parent) { if (otherTab.tabItem.parent) {

View File

@ -68,8 +68,9 @@ function TabItem(tab, options) {
) )
.appendTo('body'); .appendTo('body');
this._cachedImageData = null;
this.shouldHideCachedData = false;
this.canvasSizeForced = false; this.canvasSizeForced = false;
this.isShowingCachedData = false;
this.favEl = (iQ('.favicon', $div))[0]; this.favEl = (iQ('.favicon', $div))[0];
this.favImgEl = (iQ('.favicon>img', $div))[0]; this.favImgEl = (iQ('.favicon>img', $div))[0];
this.nameEl = (iQ('.tab-title', $div))[0]; this.nameEl = (iQ('.tab-title', $div))[0];
@ -241,16 +242,33 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
this.canvasSizeForced = false; 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 // Function: showCachedData
// Shows the cached data i.e. image and title. Note: this method should only // Shows the cached data i.e. image and title. Note: this method should only
// be called at browser startup with the cached data avaliable. // be called at browser startup with the cached data avaliable.
//
// Parameters:
// tabData - the tab data
showCachedData: function TabItem_showCachedData(tabData) { showCachedData: function TabItem_showCachedData(tabData) {
this.isShowingCachedData = true; if (!this._cachedImageData) {
var $nameElement = iQ(this.nameEl); TabItems.cachedDataCounter++;
var $canvasElement = iQ(this.canvasEl); this.tab.linkedBrowser._tabViewTabItemWithCachedData = this;
var $cachedThumbElement = iQ(this.cachedThumbEl); if (TabItems.cachedDataCounter == 1)
$cachedThumbElement.attr("src", tabData.imageData).show(); 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}); $canvasElement.css({opacity: 0.0});
$nameElement.text(tabData.title ? tabData.title : ""); $nameElement.text(tabData.title ? tabData.title : "");
}, },
@ -259,11 +277,17 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
// Function: hideCachedData // Function: hideCachedData
// Hides the cached data i.e. image and title and show the canvas. // Hides the cached data i.e. image and title and show the canvas.
hideCachedData: function TabItem_hideCachedData() { hideCachedData: function TabItem_hideCachedData() {
var $canvasElement = iQ(this.canvasEl); let $canvasElement = iQ(this.canvasEl);
var $cachedThumbElement = iQ(this.cachedThumbEl); let $cachedThumbElement = iQ(this.cachedThumbEl);
$cachedThumbElement.hide(); $cachedThumbElement.hide();
$canvasElement.css({opacity: 1.0}); $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);
}
}, },
// ---------- // ----------
@ -273,13 +297,21 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
// Parameters: // Parameters:
// getImageData - true to include thumbnail pixels (and page title as well); default false // getImageData - true to include thumbnail pixels (and page title as well); default false
getStorageData: function TabItem_getStorageData(getImageData) { 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 { return {
bounds: this.getBounds(), bounds: this.getBounds(),
userSize: (Utils.isPoint(this.userSize) ? new Point(this.userSize) : null), userSize: (Utils.isPoint(this.userSize) ? new Point(this.userSize) : null),
url: this.tab.linkedBrowser.currentURI.spec, url: this.tab.linkedBrowser.currentURI.spec,
groupID: (this.parent ? this.parent.id : 0), groupID: (this.parent ? this.parent.id : 0),
imageData: (getImageData && this.tabCanvas ? imageData: imageData,
this.tabCanvas.toImageData() : null),
title: getImageData && this.tab.label || null title: getImageData && this.tab.label || null
}; };
}, },
@ -702,6 +734,8 @@ let TabItems = {
fontSize: 9, fontSize: 9,
items: [], items: [],
paintingPaused: 0, paintingPaused: 0,
cachedDataCounter: 0, // total number of cached data being displayed.
tabsProgressListener: null,
_tabsWaitingForUpdate: [], _tabsWaitingForUpdate: [],
_heartbeatOn: false, // see explanation at startHeartbeat() below _heartbeatOn: false, // see explanation at startHeartbeat() below
_heartbeatTiming: 100, // milliseconds between _checkHeartbeat() calls _heartbeatTiming: 100, // milliseconds between _checkHeartbeat() calls
@ -714,7 +748,7 @@ let TabItems = {
// Set up the necessary tracking to maintain the <TabItems>s. // Set up the necessary tracking to maintain the <TabItems>s.
init: function TabItems_init() { init: function TabItems_init() {
Utils.assert(window.AllTabs, "AllTabs must be initialized first"); Utils.assert(window.AllTabs, "AllTabs must be initialized first");
var self = this; let self = this;
let $canvas = iQ("<canvas>"); let $canvas = iQ("<canvas>");
$canvas.appendTo(iQ("body")); $canvas.appendTo(iQ("body"));
@ -725,6 +759,18 @@ let TabItems = {
this.tempCanvas.width = 150; this.tempCanvas.width = 150;
this.tempCanvas.height = 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 // When a tab is opened, create the TabItem
this._eventListeners["open"] = function(tab) { this._eventListeners["open"] = function(tab) {
if (tab.ownerDocument.defaultView != gWindow || tab.pinned) if (tab.ownerDocument.defaultView != gWindow || tab.pinned)
@ -764,6 +810,9 @@ let TabItems = {
// ---------- // ----------
// Function: uninit // Function: uninit
uninit: function TabItems_uninit() { uninit: function TabItems_uninit() {
if (this.tabsProgressListener)
gBrowser.removeTabsProgressListener(this.tabsProgressListener);
for (let name in this._eventListeners) { for (let name in this._eventListeners) {
AllTabs.unregister(name, this._eventListeners[name]); AllTabs.unregister(name, this._eventListeners[name]);
} }
@ -796,7 +845,7 @@ let TabItems = {
); );
let isCurrentTab = ( let isCurrentTab = (
!UI._isTabViewVisible() && !UI.isTabViewVisible() &&
tab == gBrowser.selectedTab tab == gBrowser.selectedTab
); );
@ -850,7 +899,7 @@ let TabItems = {
// ___ label // ___ label
let label = tab.label; let label = tab.label;
let $name = iQ(tabItem.nameEl); let $name = iQ(tabItem.nameEl);
if (!tabItem.isShowingCachedData && $name.text() != label) if (!tabItem.isShowingCachedData() && $name.text() != label)
$name.text(label); $name.text(label);
// ___ thumbnail // ___ thumbnail
@ -870,8 +919,7 @@ let TabItems = {
tabItem.tabCanvas.paint(); tabItem.tabCanvas.paint();
// ___ cache // ___ cache
// TODO: this logic needs to be better; hiding too soon now if (tabItem.isShowingCachedData() && tabItem.shouldHideCachedData)
if (tabItem.isShowingCachedData && !tab.hasAttribute("busy"))
tabItem.hideCachedData(); tabItem.hideCachedData();
} catch(e) { } catch(e) {
Utils.log(e); Utils.log(e);
@ -1090,16 +1138,8 @@ let TabItems = {
} }
} }
if (tabData.imageData) { if (tabData.imageData)
item.showCachedData(tabData); 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; item.reconnected = true;
found = {addedToGroup: tabData.groupID}; found = {addedToGroup: tabData.groupID};

View File

@ -210,7 +210,7 @@ let UI = {
var observer = { var observer = {
observe : function(subject, topic, data) { observe : function(subject, topic, data) {
if (topic == "quit-application-requested") { if (topic == "quit-application-requested") {
if (self._isTabViewVisible()) { if (self.isTabViewVisible()) {
GroupItems.removeHiddenGroups(); GroupItems.removeHiddenGroups();
TabItems.saveAll(true); TabItems.saveAll(true);
} }
@ -379,9 +379,9 @@ let UI = {
}, },
// ---------- // ----------
// Function: _isTabViewVisible // Function: isTabViewVisible
// Returns true if the TabView UI is currently shown. // Returns true if the TabView UI is currently shown.
_isTabViewVisible: function UI__isTabViewVisible() { isTabViewVisible: function UI_isTabViewVisible() {
return gTabViewDeck.selectedIndex == 1; return gTabViewDeck.selectedIndex == 1;
}, },
@ -402,7 +402,7 @@ let UI = {
// Parameters: // Parameters:
// zoomOut - true for zoom out animation, false for nothing. // zoomOut - true for zoom out animation, false for nothing.
showTabView: function UI_showTabView(zoomOut) { showTabView: function UI_showTabView(zoomOut) {
if (this._isTabViewVisible()) if (this.isTabViewVisible())
return; return;
// initialize the direction of the page // initialize the direction of the page
@ -468,7 +468,7 @@ let UI = {
// Function: hideTabView // Function: hideTabView
// Hides TabView and shows the main browser UI. // Hides TabView and shows the main browser UI.
hideTabView: function UI_hideTabView() { hideTabView: function UI_hideTabView() {
if (!this._isTabViewVisible()) if (!this.isTabViewVisible())
return; return;
// another tab might be select if user decides to stay on a page when // another tab might be select if user decides to stay on a page when
@ -567,8 +567,8 @@ let UI = {
self._privateBrowsing.transitionStage = 3; self._privateBrowsing.transitionStage = 3;
if (aData == "enter") { if (aData == "enter") {
// If we are in Tab View, exit. // If we are in Tab View, exit.
self._privateBrowsing.wasInTabView = self._isTabViewVisible(); self._privateBrowsing.wasInTabView = self.isTabViewVisible();
if (self._isTabViewVisible()) if (self.isTabViewVisible())
self.goToTab(gBrowser.selectedTab); self.goToTab(gBrowser.selectedTab);
} }
} else if (aTopic == "private-browsing-change-granted") { } else if (aTopic == "private-browsing-change-granted") {
@ -606,7 +606,7 @@ let UI = {
if (tab.pinned) if (tab.pinned)
GroupItems.removeAppTab(tab); GroupItems.removeAppTab(tab);
if (self._isTabViewVisible()) { if (self.isTabViewVisible()) {
// just closed the selected tab in the TabView interface. // just closed the selected tab in the TabView interface.
if (self._currentTab == tab) if (self._currentTab == tab)
self._closedSelectedTabInTabView = true; self._closedSelectedTabInTabView = true;
@ -719,7 +719,7 @@ let UI = {
this._currentTab = tab; this._currentTab = tab;
// if the last visible tab has just been closed, don't show the chrome UI. // 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 || this._closedSelectedTabInTabView)) {
this._closedLastVisibleTab = false; this._closedLastVisibleTab = false;
this._closedSelectedTabInTabView = false; this._closedSelectedTabInTabView = false;
@ -731,7 +731,7 @@ let UI = {
// if TabView is visible but we didn't just close the last tab or // if TabView is visible but we didn't just close the last tab or
// selected tab, show chrome. // selected tab, show chrome.
if (this._isTabViewVisible()) if (this.isTabViewVisible())
this.hideTabView(); this.hideTabView();
// another tab might be selected when hideTabView() is invoked so a // another tab might be selected when hideTabView() is invoked so a
@ -789,7 +789,7 @@ let UI = {
// Parameters: // Parameters:
// groupItem - the groupItem which would be used for re-ordering tabs. // groupItem - the groupItem which would be used for re-ordering tabs.
setReorderTabsOnHide: function UI_setReorderTabsOnHide(groupItem) { setReorderTabsOnHide: function UI_setReorderTabsOnHide(groupItem) {
if (this._isTabViewVisible()) { if (this.isTabViewVisible()) {
var index = this._reorderTabsOnHide.indexOf(groupItem); var index = this._reorderTabsOnHide.indexOf(groupItem);
if (index == -1) if (index == -1)
this._reorderTabsOnHide.push(groupItem); this._reorderTabsOnHide.push(groupItem);
@ -803,7 +803,7 @@ let UI = {
// Parameters: // Parameters:
// groupItem - the groupItem which would be used for re-ordering tab items. // groupItem - the groupItem which would be used for re-ordering tab items.
setReorderTabItemsOnShow: function UI_setReorderTabItemsOnShow(groupItem) { setReorderTabItemsOnShow: function UI_setReorderTabItemsOnShow(groupItem) {
if (!this._isTabViewVisible()) { if (!this.isTabViewVisible()) {
var index = this._reorderTabItemsOnShow.indexOf(groupItem); var index = this._reorderTabItemsOnShow.indexOf(groupItem);
if (index == -1) if (index == -1)
this._reorderTabItemsOnShow.push(groupItem); this._reorderTabItemsOnShow.push(groupItem);
@ -1083,7 +1083,7 @@ let UI = {
// If TabView isn't focused and is not showing, don't perform a resize. // If TabView isn't focused and is not showing, don't perform a resize.
// This resize really slows things down. // This resize really slows things down.
if (!force && !this._isTabViewVisible()) if (!force && !this.isTabViewVisible())
return; return;
var oldPageBounds = new Rect(this._pageBounds); var oldPageBounds = new Rect(this._pageBounds);

View File

@ -60,6 +60,7 @@ _BROWSER_FILES = \
browser_tabview_bug595804.js \ browser_tabview_bug595804.js \
browser_tabview_bug595930.js \ browser_tabview_bug595930.js \
browser_tabview_bug595943.js \ browser_tabview_bug595943.js \
browser_tabview_bug597248.js \
browser_tabview_bug597399.js \ browser_tabview_bug597399.js \
browser_tabview_bug598600.js \ browser_tabview_bug598600.js \
browser_tabview_bug599626.js \ browser_tabview_bug599626.js \

View File

@ -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);
}