Bug 628701 - Panorama doesn't render thumbnails for many tabs [r=ian, a=blocking2.0:betaN+]

This commit is contained in:
Sean Dunn 2011-02-16 23:05:59 +01:00
parent 54ed54c516
commit 071232a5b8
6 changed files with 126 additions and 110 deletions

View File

@ -97,6 +97,10 @@ let Storage = {
this._sessionStore.setTabValue(tab, this.TAB_DATA_IDENTIFIER,
JSON.stringify(data));
// Notify subscribers
if (data && data.imageData && tab._tabViewTabItem)
tab._tabViewTabItem._sendToSubscribers("savedImageData");
},
// ----------

View File

@ -69,7 +69,6 @@ function TabItem(tab, options) {
let $div = iQ(div);
this._cachedImageData = null;
this.shouldHideCachedData = false;
this.canvasSizeForced = false;
this.$thumb = iQ('.thumb', $div);
this.$fav = iQ('.favicon', $div);
@ -88,6 +87,7 @@ function TabItem(tab, options) {
this._hasBeenDrawn = false;
this._reconnected = false;
this.isStacked = false;
this.url = "";
var self = this;
@ -259,12 +259,6 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
// Parameters:
// tabData - the tab data
showCachedData: function TabItem_showCachedData(tabData) {
if (!this._cachedImageData) {
TabItems.cachedDataCounter++;
this.tab.linkedBrowser._tabViewTabItemWithCachedData = this;
if (TabItems.cachedDataCounter == 1)
gBrowser.addTabsProgressListener(TabItems.tabsProgressListener);
}
this._cachedImageData = tabData.imageData;
this.$cachedThumb.attr("src", this._cachedImageData).show();
this.$canvas.css({opacity: 0.0});
@ -277,13 +271,8 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
hideCachedData: function TabItem_hideCachedData() {
this.$cachedThumb.hide();
this.$canvas.css({opacity: 1.0});
if (this._cachedImageData) {
TabItems.cachedDataCounter--;
if (this._cachedImageData)
this._cachedImageData = null;
this.tab.linkedBrowser._tabViewTabItemWithCachedData = null;
if (TabItems.cachedDataCounter == 0)
gBrowser.removeTabsProgressListener(TabItems.tabsProgressListener);
}
},
// ----------
@ -338,7 +327,7 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
_reconnect: function TabItem__reconnect() {
Utils.assertThrow(!this._reconnected, "shouldn't already be reconnected");
Utils.assertThrow(this.tab, "should have a xul:tab");
let tabData = Storage.getTabData(this.tab);
if (tabData && TabItems.storageSanity(tabData)) {
if (this.parent)
@ -363,7 +352,12 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
}
let currentUrl = this.tab.linkedBrowser.currentURI.spec;
if (tabData.imageData && tabData.url == currentUrl)
// If we have a cached image, then show it if the loaded URL matches
// what the cache is from, OR the loaded URL is blank, which means
// that the page hasn't loaded yet.
if (tabData.imageData && (tabData.url == currentUrl ||
currentUrl == 'about:blank'))
this.showCachedData(tabData);
} else {
// create tab by double click is handled in UI_init().
@ -778,11 +772,10 @@ let TabItems = {
_fragment: null,
items: [],
paintingPaused: 0,
cachedDataCounter: 0, // total number of cached data being displayed.
tabsProgressListener: null,
_tabsWaitingForUpdate: null,
_heartbeat: null, // see explanation at startHeartbeat() below
_heartbeatTiming: 100, // milliseconds between calls
_heartbeatTiming: 200, // milliseconds between calls
_maxTimeForUpdating: 200, // milliseconds that consecutive updates can take
_lastUpdateTime: Date.now(),
_eventListeners: [],
_pauseUpdateForTest: false,
@ -814,18 +807,6 @@ let TabItems = {
this.tempCanvas.width = 150;
this.tempCanvas.height = 112;
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)
@ -865,9 +846,6 @@ let TabItems = {
// ----------
// 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]);
}
@ -907,6 +885,22 @@ let TabItems = {
return this._fragment;
},
// ----------
// Function: isComplete
// Return whether the xul:tab has fully loaded.
isComplete: function TabItems_update(tab) {
// If our readyState is complete, but we're showing about:blank,
// and we're not loading about:blank, it means we haven't really
// started loading. This can happen to the first few tabs in a
// page.
Utils.assertThrow(tab, "tab");
return (
tab.linkedBrowser.contentDocument.readyState == 'complete' &&
!(tab.linkedBrowser.contentDocument.URL == 'about:blank' &&
tab._tabViewTabItem.url != 'about:blank')
);
},
// ----------
// Function: update
// Takes in a xul:tab.
@ -942,13 +936,12 @@ let TabItems = {
Utils.assertThrow(tab, "tab");
// ___ remove from waiting list if needed
this._tabsWaitingForUpdate.remove(tab);
// ___ get the TabItem
Utils.assertThrow(tab._tabViewTabItem, "must already be linked");
let tabItem = tab._tabViewTabItem;
// Even if the page hasn't loaded, display the favicon and title
// ___ icon
if (this.shouldLoadFavIcon(tab.linkedBrowser)) {
let iconUrl = tab.image;
@ -965,6 +958,16 @@ let TabItems = {
iQ(tabItem.$fav[0]).hide();
}
// ___ label
let label = tab.label;
let $name = tabItem.$tabTitle;
if ($name.text() != label)
$name.text(label);
// ___ remove from waiting list now that we have no other
// early returns
this._tabsWaitingForUpdate.remove(tab);
// ___ URL
let tabUrl = tab.linkedBrowser.currentURI.spec;
if (tabUrl != tabItem.url) {
@ -973,13 +976,12 @@ let TabItems = {
tabItem.save();
}
// ___ label
let label = tab.label;
let $name = tabItem.$tabTitle;
let isLabelUpdateAllowed = !tabItem.isShowingCachedData() ||
tabItem.shouldHideCachedData;
if (isLabelUpdateAllowed && $name.text() != label)
$name.text(label);
// ___ Make sure the tab is complete and ready for updating.
if (!this.isComplete(tab)) {
// If it's incomplete, stick it on the end of the queue
this._tabsWaitingForUpdate.push(tab);
return;
}
// ___ thumbnail
let $canvas = tabItem.$canvas;
@ -998,7 +1000,7 @@ let TabItems = {
tabItem.tabCanvas.paint();
// ___ cache
if (tabItem.isShowingCachedData() && tabItem.shouldHideCachedData)
if (tabItem.isShowingCachedData())
tabItem.hideCachedData();
// ___ notify subscribers that a full update has completed.
@ -1094,41 +1096,54 @@ let TabItems = {
_checkHeartbeat: function TabItems__checkHeartbeat() {
this._heartbeat = null;
if (this.isPaintingPaused())
if (this.isPaintingPaused() || !UI.isIdle)
return;
if (UI.isIdle())
this._update(this._tabsWaitingForUpdate.peek());
let accumTime = 0;
let items = this._tabsWaitingForUpdate.getItems();
// Do as many updates as we can fit into a "perceived" amount
// of time, which is tunable.
while (accumTime < this._maxTimeForUpdating && items.length) {
let updateBegin = Date.now();
this._update(items.pop());
let updateEnd = Date.now();
// Maintain a simple average of time for each tabitem update
// We can use this as a base by which to delay things like
// tab zooming, so there aren't any hitches.
let deltaTime = updateEnd - updateBegin;
accumTime += deltaTime;
}
if (this._tabsWaitingForUpdate.hasItems())
this.startHeartbeat();
},
// ----------
// Function: pausePainting
// Tells TabItems to stop updating thumbnails (so you can do
// animations without thumbnail paints causing stutters).
// pausePainting can be called multiple times, but every call to
// pausePainting needs to be mirrored with a call to <resumePainting>.
pausePainting: function TabItems_pausePainting() {
this.paintingPaused++;
if (this._heartbeat) {
clearTimeout(this._heartbeat);
this._heartbeat = null;
}
},
// ----------
// Function: pausePainting
// Tells TabItems to stop updating thumbnails (so you can do
// animations without thumbnail paints causing stutters).
// pausePainting can be called multiple times, but every call to
// pausePainting needs to be mirrored with a call to <resumePainting>.
pausePainting: function TabItems_pausePainting() {
this.paintingPaused++;
if (this._heartbeat) {
clearTimeout(this._heartbeat);
this._heartbeat = null;
}
},
// ----------
// Function: resumePainting
// Undoes a call to <pausePainting>. For instance, if you called
// pausePainting three times in a row, you'll need to call resumePainting
// three times before TabItems will start updating thumbnails again.
resumePainting: function TabItems_resumePainting() {
this.paintingPaused--;
Utils.assert(this.paintingPaused > -1, "paintingPaused should not go below zero");
if (!this.isPaintingPaused())
this.startHeartbeat();
},
// ----------
// Function: resumePainting
// Undoes a call to <pausePainting>. For instance, if you called
// pausePainting three times in a row, you'll need to call resumePainting
// three times before TabItems will start updating thumbnails again.
resumePainting: function TabItems_resumePainting() {
this.paintingPaused--;
Utils.assert(this.paintingPaused > -1, "paintingPaused should not go below zero");
if (!this.isPaintingPaused())
this.startHeartbeat();
},
// ----------
// Function: isPaintingPaused
@ -1285,7 +1300,6 @@ function TabPriorityQueue() {
};
TabPriorityQueue.prototype = {
_popToggle: false,
_low: [], // low priority queue
_high: [], // high priority queue
@ -1304,6 +1318,13 @@ TabPriorityQueue.prototype = {
return (this._low.length > 0) || (this._high.length > 0);
},
// ----------
// Function: getItems
// Returns all queued items, ordered from low to high priority
getItems: function TabPriorityQueue_getItems() {
return this._low.concat(this._high);
},
// ----------
// Function: push
// Add an item to be prioritized

View File

@ -243,19 +243,18 @@ let UI = {
});
// ___ setup observer to save canvas images
function quitObserver(subject, topic, data) {
if (topic == "quit-application-requested") {
function domWinClosedObserver(subject, topic, data) {
if (topic == "domwindowclosed" && subject == gWindow) {
if (self.isTabViewVisible())
GroupItems.removeHiddenGroups();
TabItems.saveAll(true);
self._save();
}
}
Services.obs.addObserver(
quitObserver, "quit-application-requested", false);
domWinClosedObserver, "domwindowclosed", false);
this._cleanupFunctions.push(function() {
Services.obs.removeObserver(quitObserver, "quit-application-requested");
Services.obs.removeObserver(domWinClosedObserver, "domwindowclosed");
});
// ___ Done

View File

@ -42,10 +42,9 @@ let contentWindow;
function test() {
waitForExplicitFinish();
contentWindow = document.getElementById("tab-view").contentWindow;
// create new tab
testTab = gBrowser.addTab("http://mochi.test:8888/");
testTab = gBrowser.addTab("about:blank");
window.addEventListener("tabviewshown", onTabViewWindowLoaded, false);
TabView.toggle();
@ -55,11 +54,15 @@ function onTabViewWindowLoaded() {
window.removeEventListener("tabviewshown", onTabViewWindowLoaded, false);
ok(TabView.isVisible(), "Tab View is visible");
contentWindow = document.getElementById("tab-view").contentWindow;
// create group
let testGroupRect = new contentWindow.Rect(20, 20, 300, 300);
testGroup = new contentWindow.GroupItem([], { bounds: testGroupRect });
ok(testGroup.isEmpty(), "This group is empty");
ok(testTab._tabViewTabItem, "tab item exists");
// place tab in group
let testTabItem = testTab._tabViewTabItem;
@ -67,6 +70,8 @@ function onTabViewWindowLoaded() {
testTabItem.parent.remove(testTabItem);
testGroup.add(testTabItem);
ok(testTab._tabViewTabItem, "tab item exists after adding to group");
// record last update time of tab canvas
let initialUpdateTime = testTabItem._lastTabUpdateTime;

View File

@ -117,8 +117,11 @@ function test() {
let checkPixelColors = function (url, colors, callback) {
let tab = win.gBrowser.tabs[0];
let $canvas = tab._tabViewTabItem.$canvas;
let width = $canvas.width();
let height = $canvas.height();
// Use the direct canvas sizes instead of querying
// iQ bounds, which may not be current.
let width = $canvas[0].width;
let height = $canvas[0].height;
let ctx = $canvas[0].getContext("2d");
afterAllTabItemsUpdated(function () {

View File

@ -77,9 +77,15 @@ function setupTwo() {
let tabItems = contentWindow.TabItems.getItems();
is(tabItems.length, 2, "There should be 2 tab items before closing");
// force all canvas to update
let numTabsToSave = tabItems.length;
// force all canvases to update, and hook in imageData save detection
tabItems.forEach(function(tabItem) {
contentWindow.TabItems._update(tabItem.tab);
tabItem.addSubscriber(tabItem, "savedImageData", function(item) {
item.removeSubscriber(item, "savedImageData");
--numTabsToSave;
});
});
// after the window is closed, restore it.
@ -95,6 +101,9 @@ function setupTwo() {
restoredWin.addEventListener("load", function(event) {
restoredWin.removeEventListener("load", arguments.callee, false);
// ensure that closed tabs have been saved
is(numTabsToSave, 0, "All tabs were saved when window was closed.");
// execute code when the frame is initialized.
let onTabViewFrameInitialized = function() {
restoredWin.removeEventListener(
@ -128,35 +137,10 @@ function setupTwo() {
});
};
// check the storage for stored image data
let checkDataAndCloseWindow = function() {
tabItems.forEach(function(tabItem) {
let tabData = contentWindow.Storage.getTabData(tabItem.tab);
ok(tabData && tabData.imageData,
"TabItem has stored image data before closing");
});
Services.obs.addObserver(
xulWindowDestory, "xul-window-destroyed", false);
newWin.close();
}
// 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);
// cancel the shut down
aSubject.data = true;
// save all images is execuated when "quit-application-requested" topic is
// announced so executeSoon is used to avoid racing condition.
executeSoon(checkDataAndCloseWindow);
}
Services.obs.addObserver(
quitRequestObserver, "quit-application-requested", false);
ok(!Application.quit(), "Tried to quit and canceled it");
xulWindowDestory, "xul-window-destroyed", false);
newWin.close();
}
let gTabsProgressListener = {