Bug 691740 - Update thumbnails separately in their own queue r=tim

This commit is contained in:
Raymond Lee 2011-11-29 16:55:53 +08:00
Родитель 7d24f33b08
Коммит 4fd6e30e27
14 изменённых файлов: 515 добавлений и 437 удалений

Просмотреть файл

@ -0,0 +1,237 @@
/* ***** 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 delayedTabQueue.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 ***** */
// **********
// Title: delayedTabQueue.js
// ##########
// Class: DelayedTabQueue
// A queue that delays calls to a given callback specific for tabs. Tabs are
// sorted by priority.
//
// Parameters:
// callback - the callback that is called with the tab to process as the first
// argument
// options - various options for this tab queue (see below)
//
// Possible options:
// interval - interval between the heart beats in msecs
// cap - maximum time in msecs to be used by one heart beat
function DelayedTabQueue(callback, options) {
this._callback = callback;
this._heartbeatInterval = (options && options.interval) || 500;
this._heartbeatCap = (options && options.cap) || this._heartbeatInterval / 2;
this._entries = [];
this._tabPriorities = new WeakMap();
}
DelayedTabQueue.prototype = {
_callback: null,
_entries: null,
_tabPriorities: null,
_isPaused: false,
_heartbeat: null,
_heartbeatCap: 0,
_heartbeatInterval: 0,
_lastExecutionTime: 0,
// ----------
// Function: pause
// Pauses the heartbeat.
pause: function DQ_pause() {
this._isPaused = true;
this._stopHeartbeat();
},
// ----------
// Function: resume
// Resumes the heartbeat.
resume: function DQ_resume() {
this._isPaused = false;
this._startHeartbeat();
},
// ----------
// Function: push
// Pushes a new tab onto the queue.
//
// Parameters:
// tab - the tab to be added to the queue
push: function DQ_push(tab) {
let prio = this._getTabPriority(tab);
if (this._tabPriorities.has(tab)) {
let oldPrio = this._tabPriorities.get(tab);
// re-sort entries if the tab's priority has changed
if (prio != oldPrio) {
this._tabPriorities.set(tab, prio);
this._sortEntries();
}
return;
}
let shouldDefer = this._isPaused || this._entries.length ||
Date.now() - this._lastExecutionTime < this._heartbeatInterval;
if (shouldDefer) {
this._tabPriorities.set(tab, prio);
// create the new entry
this._entries.push(tab);
this._sortEntries();
this._startHeartbeat();
} else {
// execute immediately if there's no reason to defer
this._executeCallback(tab);
}
},
// ----------
// Function: _sortEntries
// Sorts all entries in the queue by their priorities.
_sortEntries: function DQ__sortEntries() {
let self = this;
this._entries.sort(function (left, right) {
let leftPrio = self._tabPriorities.get(left);
let rightPrio = self._tabPriorities.get(right);
return leftPrio - rightPrio;
});
},
// ----------
// Function: _getTabPriority
// Determines the priority for a given tab.
//
// Parameters:
// tab - the tab for which we want to get the priority
_getTabPriority: function DQ__getTabPriority(tab) {
if (this.parent && (this.parent.isStacked() &&
!this.parent.isTopOfStack(this) &&
!this.parent.expanded))
return 0;
return 1;
},
// ----------
// Function: _startHeartbeat
// Starts the heartbeat.
_startHeartbeat: function DQ__startHeartbeat() {
if (!this._heartbeat) {
this._heartbeat = setTimeout(this._checkHeartbeat.bind(this),
this._heartbeatInterval);
}
},
// ----------
// Function: _checkHeartbeat
// Checks the hearbeat and processes as many items from queue as possible.
_checkHeartbeat: function DQ__checkHeartbeat() {
this._heartbeat = null;
// return if processing is paused or there are no entries
if (this._isPaused || !this._entries.length)
return;
// process entries only if the UI is idle
if (UI.isIdle()) {
let startTime = Date.now();
let timeElapsed = 0;
do {
// remove the tab from the list of entries and execute the callback
let tab = this._entries.shift();
this._tabPriorities.delete(tab);
this._executeCallback(tab);
// track for how long we've been processing entries and make sure we
// dont't do it longer than {_heartbeatCap} msecs
timeElapsed = this._lastExecutionTime - startTime;
} while (this._entries.length && timeElapsed < this._heartbeatCap);
}
// keep the heartbeat active until all entries have been processed
if (this._entries.length)
this._startHeartbeat();
},
_executeCallback: function DQ__executeCallback(tab) {
this._lastExecutionTime = Date.now();
this._callback(tab);
},
// ----------
// Function: _stopHeartbeat
// Stops the heartbeat.
_stopHeartbeat: function DQ__stopHeartbeat() {
if (this._heartbeat)
this._heartbeat = clearTimeout(this._heartbeat);
},
// ----------
// Function: remove
// Removes a given tab from the queue.
//
// Parameters:
// tab - the tab to remove
remove: function DQ_remove(tab) {
if (!this._tabPriorities.has(tab))
return;
this._tabPriorities.delete(tab);
let index = this._entries.indexOf(tab);
if (index > -1)
this._entries.splice(index, 1);
},
// ----------
// Function: clear
// Removes all entries from the queue.
clear: function DQ_clear() {
let tab;
while (tab = this._entries.shift())
this._tabPriorities.delete(tab);
}
};

Просмотреть файл

@ -110,8 +110,6 @@ function TabItem(tab, options) {
this.bounds = new Rect(0,0,1,1);
this._lastTabUpdateTime = Date.now();
// ___ superclass setup
this._init(div);
@ -491,7 +489,7 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
}
if (css.width) {
TabItems.update(this.tab);
TabItems.addToThumbnailUpdateQueue(this.tab);
let widthRange, proportion;
@ -630,7 +628,7 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
Search.hide();
UI.setActive(this);
TabItems._update(this.tab, {force: true});
TabItems.addToThumbnailUpdateQueue(this.tab, {dontDelay: true});
// Zoom in!
let tab = this.tab;
@ -701,7 +699,7 @@ TabItem.prototype = Utils.extend(new Item(), new Subscribable(), {
};
UI.setActive(this);
TabItems._update(this.tab, {force: true});
TabItems.addToThumbnailUpdateQueue(this.tab, {dontDelay: true});
$tab.addClass("front");
@ -784,17 +782,13 @@ let TabItems = {
_fragment: null,
items: [],
paintingPaused: 0,
_tabsWaitingForUpdate: null,
_heartbeat: null, // see explanation at startHeartbeat() below
_heartbeatTiming: 200, // milliseconds between calls
_maxTimeForUpdating: 200, // milliseconds that consecutive updates can take
_lastUpdateTime: Date.now(),
_eventListeners: [],
_pauseUpdateForTest: false,
tempCanvas: null,
_reconnectingPaused: false,
tabItemPadding: {},
_mozAfterPaintHandler: null,
_delayedTabQueue: null,
_delayedTabQueueThumbnails: null,
// ----------
// Function: toString
@ -809,9 +803,12 @@ let TabItems = {
init: function TabItems_init() {
Utils.assert(window.AllTabs, "AllTabs must be initialized first");
let self = this;
// Set up tab priority queue
this._tabsWaitingForUpdate = new TabPriorityQueue();
// set up delayed tab queues
this._delayedTabQueue = new DelayedTabQueue(this._update.bind(this));
this._delayedTabQueueThumbnails =
new DelayedTabQueue(this._updateThumbnail.bind(this));
this.minTabHeight = this.minTabWidth * this.tabHeight / this.tabWidth;
this.tabAspect = this.tabHeight / this.tabWidth;
this.invTabAspect = 1 / this.tabAspect;
@ -843,7 +840,7 @@ let TabItems = {
let tab = event.target;
if (!tab.pinned)
self.update(tab);
self.addToUpdateQueue(tab);
}
// When a tab is closed, unlink.
this._eventListeners.close = function (event) {
@ -872,7 +869,7 @@ let TabItems = {
if (!tab.hidden && activeGroupItemId)
options.groupItemId = activeGroupItemId;
self.link(tab, options);
self.update(tab);
self.addToUpdateQueue(tab);
});
},
@ -894,8 +891,11 @@ let TabItems = {
this.items = null;
this._eventListeners = null;
this._lastUpdateTime = null;
this._tabsWaitingForUpdate.clear();
this._delayedTabQueue.clear();
this._delayedTabQueue = null;
this._delayedTabQueueThumbnails.clear();
this._delayedTabQueueThumbnails = null;
},
// ----------
@ -946,32 +946,31 @@ let TabItems = {
let tab = gBrowser.tabs[index];
if (!tab.pinned)
this.update(tab);
this.addToThumbnailUpdateQueue(tab);
},
// ----------
// Function: update
// Function: _isTabRestored
// Check whether a given tab need restoring or not.
//
// Parameters:
// tab - the xul tab
_isTabToBeRestored: function TabItems__isTabToBeRestored(tab) {
let browser = tab.linkedBrowser;
return ("__SS_restoreState" in browser && browser.__SS_restoreState == 1);
},
// ----------
// Function: addToUpdateQueue
// Takes in a xul:tab.
update: function TabItems_update(tab) {
try {
Utils.assertThrow(tab, "tab");
Utils.assertThrow(!tab.pinned, "shouldn't be an app tab");
Utils.assertThrow(tab._tabViewTabItem, "should already be linked");
addToUpdateQueue: function TabItems_addToUpdateQueue(tab) {
Utils.assertThrow(tab, "tab");
Utils.assertThrow(!tab.pinned, "shouldn't be an app tab");
Utils.assertThrow(tab._tabViewTabItem, "should already be linked");
let shouldDefer = (
this.isPaintingPaused() ||
this._tabsWaitingForUpdate.hasItems() ||
Date.now() - this._lastUpdateTime < this._heartbeatTiming
);
if (shouldDefer) {
this._tabsWaitingForUpdate.push(tab);
this.startHeartbeat();
} else
this._update(tab);
} catch(e) {
Utils.log(e);
}
// don't update if the tab hasn't been restored, yet
if (!this._isTabToBeRestored(tab))
this._delayedTabQueue.push(tab);
},
// ----------
@ -980,94 +979,97 @@ let TabItems = {
//
// Parameters:
// tab - a xul tab to update
// options - an object with additional parameters, see below
//
// Possible options:
// force - true to always update the tab item even if it's incomplete
_update: function TabItems__update(tab, options) {
try {
if (this._pauseUpdateForTest)
return;
_update: function TabItems__update(tab) {
let tabItem = tab._tabViewTabItem;
Utils.assertThrow(tab, "tab");
// Even if the page hasn't loaded, display the favicon and title
// ___ get the TabItem
Utils.assertThrow(tab._tabViewTabItem, "must already be linked");
let tabItem = tab._tabViewTabItem;
// ___ icon
if (UI.shouldLoadFavIcon(tab.linkedBrowser)) {
let iconUrl = UI.getFavIconUrlForTab(tab);
// Even if the page hasn't loaded, display the favicon and title
if (tabItem.$favImage[0].src != iconUrl)
tabItem.$favImage[0].src = iconUrl;
// ___ icon
if (UI.shouldLoadFavIcon(tab.linkedBrowser)) {
let iconUrl = UI.getFavIconUrlForTab(tab);
if (tabItem.$favImage[0].src != iconUrl)
tabItem.$favImage[0].src = iconUrl;
iQ(tabItem.$fav[0]).show();
} else {
if (tabItem.$favImage[0].hasAttribute("src"))
tabItem.$favImage[0].removeAttribute("src");
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) {
let oldURL = tabItem.url;
tabItem.url = tabUrl;
tabItem.save();
}
// ___ Make sure the tab is complete and ready for updating.
let self = this;
let updateCanvas = function TabItems__update_updateCanvas(tabItem) {
// ___ thumbnail
let $canvas = tabItem.$canvas;
if (!tabItem.canvasSizeForced) {
let w = $canvas.width();
let h = $canvas.height();
if (w != tabItem.$canvas[0].width || h != tabItem.$canvas[0].height) {
tabItem.$canvas[0].width = w;
tabItem.$canvas[0].height = h;
}
}
self._lastUpdateTime = Date.now();
tabItem._lastTabUpdateTime = self._lastUpdateTime;
tabItem.tabCanvas.paint();
tabItem.saveThumbnail();
// ___ cache
if (tabItem.isShowingCachedData())
tabItem.hideCachedData();
// ___ notify subscribers that a full update has completed.
tabItem._sendToSubscribers("updated");
};
if (options && options.force)
updateCanvas(tabItem);
else
this._isComplete(tab, function TabItems__update_isComplete(isComplete) {
if (isComplete)
updateCanvas(tabItem);
else
self._tabsWaitingForUpdate.push(tab);
});
} catch(e) {
Utils.log(e);
iQ(tabItem.$fav[0]).show();
} else {
if (tabItem.$favImage[0].hasAttribute("src"))
tabItem.$favImage[0].removeAttribute("src");
iQ(tabItem.$fav[0]).hide();
}
// ___ label
let label = tab.label;
let $name = tabItem.$tabTitle;
if ($name.text() != label)
$name.text(label);
// ___ URL
let tabUrl = tab.linkedBrowser.currentURI.spec;
if (tabUrl != tabItem.url) {
let oldURL = tabItem.url;
tabItem.url = tabUrl;
tabItem.save();
}
// ___ notify subscribers that a full update has completed.
tabItem._sendToSubscribers("updated");
},
// ----------
// Function: addToThumbnailUpdateQueue
// Determines to update the thumbnail of a given tab or put it into a delay
// queue.
//
// Parameters:
// tab - the tab who's thumbnail will be updated
// options - possible options:
// dontDelay - set to true to force an immediate update of the given
// tab's thumbnail
addToThumbnailUpdateQueue: function TabItems_addToThumbnailUpdateQueue(tab, options) {
Utils.assertThrow(tab, "tab");
Utils.assertThrow(!tab.pinned, "shouldn't be an app tab");
Utils.assertThrow(tab._tabViewTabItem, "should already be linked");
// don't update the thumbnail if the tab hasn't been restored, yet
if (this._isTabToBeRestored(tab))
return;
if (options && options.dontDelay)
this._updateThumbnail(tab);
else
this._delayedTabQueueThumbnails.push(tab);
},
// ----------
// Function: _updateThumbnail
// Updates the thumbnail of a given tab.
//
// Parameters:
// tab - the tab who's thumbnail will be updated
_updateThumbnail: function TabItems__updateThumbnail(tab) {
let tabItem = tab._tabViewTabItem;
let $canvas = tabItem.$canvas;
if (!tabItem.canvasSizeForced) {
let w = $canvas.width();
let h = $canvas.height();
if (w != tabItem.$canvas[0].width || h != tabItem.$canvas[0].height) {
tabItem.$canvas[0].width = w;
tabItem.$canvas[0].height = h;
}
}
tabItem.tabCanvas.paint();
tabItem.saveThumbnail();
// ___ cache
if (tabItem.isShowingCachedData())
tabItem.hideCachedData();
// ___ notify subscribers that a full update has completed.
tabItem._sendToSubscribers("thumbnailUpdated");
},
// ----------
@ -1105,7 +1107,8 @@ let TabItems = {
tab._tabViewTabItem = null;
Storage.saveTab(tab, null);
this._tabsWaitingForUpdate.remove(tab);
this._delayedTabQueue.remove(tab);
this._delayedTabQueueThumbnails.remove(tab);
} catch(e) {
Utils.log(e);
}
@ -1121,60 +1124,7 @@ let TabItems = {
// when a tab becomes unpinned, create a TabItem for it
handleTabUnpin: function TabItems_handleTabUnpin(xulTab) {
this.link(xulTab);
this.update(xulTab);
},
// ----------
// Function: startHeartbeat
// Start a new heartbeat if there isn't one already started.
// The heartbeat is a chain of setTimeout calls that allows us to spread
// out update calls over a period of time.
// _heartbeat is used to make sure that we don't add multiple
// setTimeout chains.
startHeartbeat: function TabItems_startHeartbeat() {
if (!this._heartbeat) {
let self = this;
this._heartbeat = setTimeout(function() {
self._checkHeartbeat();
}, this._heartbeatTiming);
}
},
// ----------
// Function: _checkHeartbeat
// This periodically checks for tabs waiting to be updated, and calls
// _update on them.
// Should only be called by startHeartbeat and resumePainting.
_checkHeartbeat: function TabItems__checkHeartbeat() {
this._heartbeat = null;
if (this.isPaintingPaused())
return;
// restart the heartbeat to update all waiting tabs once the UI becomes idle
if (!UI.isIdle()) {
this.startHeartbeat();
return;
}
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();
this.addToUpdateQueue(xulTab);
},
// ----------
@ -1184,11 +1134,8 @@ let TabItems = {
// 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;
}
if (0 == this.paintingPaused++)
this._delayedTabQueueThumbnails.pause();
},
// ----------
@ -1197,10 +1144,10 @@ let TabItems = {
// 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");
Utils.assert(--this.paintingPaused > -1, "paintingPaused should not go below zero");
if (!this.isPaintingPaused())
this.startHeartbeat();
this._delayedTabQueueThumbnails.resume();
},
// ----------
@ -1352,118 +1299,6 @@ let TabItems = {
}
};
// ##########
// Class: TabPriorityQueue
// Container that returns tab items in a priority order
// Current implementation assigns tab to either a high priority
// or low priority queue, and toggles which queue items are popped
// from. This guarantees that high priority items which are constantly
// being added will not eclipse changes for lower priority items.
function TabPriorityQueue() {
};
TabPriorityQueue.prototype = {
_low: [], // low priority queue
_high: [], // high priority queue
// ----------
// Function: toString
// Prints [TabPriorityQueue count=count] for debug use
toString: function TabPriorityQueue_toString() {
return "[TabPriorityQueue count=" + (this._low.length + this._high.length) + "]";
},
// ----------
// Function: clear
// Empty the update queue
clear: function TabPriorityQueue_clear() {
this._low = [];
this._high = [];
},
// ----------
// Function: hasItems
// Return whether pending items exist
hasItems: function TabPriorityQueue_hasItems() {
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
push: function TabPriorityQueue_push(tab) {
// Push onto correct priority queue.
// It's only low priority if it's in a stack, and isn't the top,
// and the stack isn't expanded.
// If it already exists in the destination queue,
// leave it. If it exists in a different queue, remove it first and push
// onto new queue.
let item = tab._tabViewTabItem;
if (item.parent && (item.parent.isStacked() &&
!item.parent.isTopOfStack(item) &&
!item.parent.expanded)) {
let idx = this._high.indexOf(tab);
if (idx != -1) {
this._high.splice(idx, 1);
this._low.unshift(tab);
} else if (this._low.indexOf(tab) == -1)
this._low.unshift(tab);
} else {
let idx = this._low.indexOf(tab);
if (idx != -1) {
this._low.splice(idx, 1);
this._high.unshift(tab);
} else if (this._high.indexOf(tab) == -1)
this._high.unshift(tab);
}
},
// ----------
// Function: pop
// Remove and return the next item in priority order
pop: function TabPriorityQueue_pop() {
let ret = null;
if (this._high.length)
ret = this._high.pop();
else if (this._low.length)
ret = this._low.pop();
return ret;
},
// ----------
// Function: peek
// Return the next item in priority order, without removing it
peek: function TabPriorityQueue_peek() {
let ret = null;
if (this._high.length)
ret = this._high[this._high.length-1];
else if (this._low.length)
ret = this._low[this._low.length-1];
return ret;
},
// ----------
// Function: remove
// Remove the passed item
remove: function TabPriorityQueue_remove(tab) {
let index = this._high.indexOf(tab);
if (index != -1)
this._high.splice(index, 1);
else {
index = this._low.indexOf(tab);
if (index != -1)
this._low.splice(index, 1);
}
}
};
// ##########
// Class: TabCanvas
// Takes care of the actual canvas for the tab thumbnail

Просмотреть файл

@ -77,6 +77,7 @@ let AllTabs = {
#include tabitems.js
#include drag.js
#include trench.js
#include delayedTabQueue.js
#include thumbnailStorage.js
#include search.js
#include ui.js

Просмотреть файл

@ -12,15 +12,13 @@ function test() {
// create new tab
testTab = gBrowser.addTab("about:blank");
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");
contentWindow = document.getElementById("tab-view").contentWindow;
contentWindow = TabView.getContentWindow();
// create group
let testGroupRect = new contentWindow.Rect(20, 20, 300, 300);
@ -38,8 +36,13 @@ function onTabViewWindowLoaded() {
ok(testTab._tabViewTabItem, "tab item exists after adding to group");
// record last update time of tab canvas
let initialUpdateTime = testTabItem._lastTabUpdateTime;
// keep track of last thumbnail update time
let thumbnailUpdateCount = 0;
function onUpdate() thumbnailUpdateCount++;
testTabItem.addSubscriber("thumbnailUpdated", onUpdate);
registerCleanupFunction(function () {
testTabItem.removeSubscriber("thumbnailUpdated", onUpdate)
});
// simulate resize
let resizer = contentWindow.iQ('.iq-resizable-handle', testGroup.container)[0];
@ -69,9 +72,7 @@ function onTabViewWindowLoaded() {
});
funcChain.push(function() {
// verify that update time has changed after last update
let lastTime = testTabItem._lastTabUpdateTime;
let hbTiming = contentWindow.TabItems._heartbeatTiming;
ok((lastTime - initialUpdateTime) > hbTiming, "Tab has been updated:"+lastTime+"-"+initialUpdateTime+">"+hbTiming);
ok(thumbnailUpdateCount > 0, "Tab has been updated");
// clean up
testGroup.remove(testTab._tabViewTabItem);

Просмотреть файл

@ -60,8 +60,8 @@ function test() {
mm.removeMessageListener(cx.name, onLoad);
let tabItem = tab._tabViewTabItem;
tabItem.addSubscriber("updated", function onUpdated() {
tabItem.removeSubscriber("updated", onUpdated);
tabItem.addSubscriber("thumbnailUpdated", function onUpdated() {
tabItem.removeSubscriber("thumbnailUpdated", onUpdated);
checkUrl(test);
});
});

Просмотреть файл

@ -6,8 +6,6 @@ let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore
const TAB_STATE_NEEDS_RESTORE = 1;
const TAB_STATE_RESTORING = 2;
let stateBackup = ss.getBrowserState();
let state = {windows:[{tabs:[
// first group
{entries:[{url:"http://example.com#1"}],extData:{"tabview-tab":"{\"bounds\":{\"left\":20,\"top\":20,\"width\":20,\"height\":20},\"url\":\"http://example.com#1\",\"groupID\":2}"}},
@ -25,24 +23,31 @@ let state = {windows:[{tabs:[
"tabview-ui":"{\"pageBounds\":{\"left\":0,\"top\":0,\"width\":940,\"height\":1075}}"
}}]};
let win;
let cw;
function test() {
waitForExplicitFinish();
Services.prefs.setBoolPref("browser.sessionstore.restore_hidden_tabs", false);
TabsProgressListener.init();
newWindowWithTabView(
function(newWin) {
cw = win.TabView.getContentWindow();
TabsProgressListener.init();
registerCleanupFunction(function () {
TabsProgressListener.uninit();
testRestoreWithHiddenTabs();
},
function(newWin) {
win = newWin;
Services.prefs.clearUserPref("browser.sessionstore.restore_hidden_tabs");
ss.setBrowserState(stateBackup);
});
TabView._initFrame(function () {
executeSoon(testRestoreWithHiddenTabs);
});
registerCleanupFunction(function () {
TabsProgressListener.uninit();
Services.prefs.clearUserPref("browser.sessionstore.restore_hidden_tabs");
win.close();
});
}
);
}
function testRestoreWithHiddenTabs() {
@ -50,22 +55,21 @@ function testRestoreWithHiddenTabs() {
let ssReady = false;
let tabsRestored = false;
let check = function () {
function check() {
if (checked || !ssReady || !tabsRestored)
return;
checked = true;
is(gBrowser.tabs.length, 8, "there are now eight tabs");
is(gBrowser.visibleTabs.length, 4, "four visible tabs");
is(win.gBrowser.tabs.length, 8, "there are now eight tabs");
is(win.gBrowser.visibleTabs.length, 4, "four visible tabs");
let cw = TabView.getContentWindow();
is(cw.GroupItems.groupItems.length, 2, "there are now two groupItems");
testSwitchToInactiveGroup();
}
whenSessionStoreReady(function () {
whenWindowStateReady(win, function () {
ssReady = true;
check();
});
@ -81,7 +85,7 @@ function testRestoreWithHiddenTabs() {
check();
});
ss.setBrowserState(JSON.stringify(state));
ss.setWindowState(win, JSON.stringify(state), true);
}
function testSwitchToInactiveGroup() {
@ -100,31 +104,19 @@ function testSwitchToInactiveGroup() {
TabsProgressListener.unsetCallback();
is(gBrowser.visibleTabs.length, 4, "four visible tabs");
waitForFocus(finish);
is(win.gBrowser.visibleTabs.length, 4, "four visible tabs");
waitForFocus(finish, win);
});
gBrowser.selectedTab = gBrowser.tabs[4];
}
function whenSessionStoreReady(callback) {
window.addEventListener("SSWindowStateReady", function onReady() {
window.removeEventListener("SSWindowStateReady", onReady, false);
executeSoon(callback);
}, false);
win.gBrowser.selectedTab = win.gBrowser.tabs[4];
}
function countTabs() {
let needsRestore = 0, isRestoring = 0;
let windowsEnum = Services.wm.getEnumerator("navigator:browser");
while (windowsEnum.hasMoreElements()) {
let window = windowsEnum.getNext();
if (window.closed)
continue;
for (let i = 0; i < window.gBrowser.tabs.length; i++) {
let browser = window.gBrowser.tabs[i].linkedBrowser;
for (let i = 0; i < win.gBrowser.tabs.length; i++) {
let browser = win.gBrowser.tabs[i].linkedBrowser;
if (browser.__SS_restoreState) {
if (browser.__SS_restoreState == TAB_STATE_RESTORING)
isRestoring++;
else if (browser.__SS_restoreState == TAB_STATE_NEEDS_RESTORE)
@ -137,12 +129,12 @@ function countTabs() {
let TabsProgressListener = {
init: function () {
gBrowser.addTabsProgressListener(this);
win.gBrowser.addTabsProgressListener(this);
},
uninit: function () {
this.unsetCallback();
gBrowser.removeTabsProgressListener(this);
win.gBrowser.removeTabsProgressListener(this);
},
setCallback: function (callback) {
@ -161,14 +153,15 @@ let TabsProgressListener = {
return;
let self = this;
let finalize = function () {
function finalize() {
if (wasRestoring)
delete aBrowser.__wasRestoring;
self.callback.apply(null, countTabs());
};
let isRestoring = aBrowser.__SS_restoreState == TAB_STATE_RESTORING;
let isRestoring = (aBrowser.__SS_restoreState &&
aBrowser.__SS_restoreState == TAB_STATE_RESTORING);
let wasRestoring = !aBrowser.__SS_restoreState && aBrowser.__wasRestoring;
let hasStopped = aStateFlags & Ci.nsIWebProgressListener.STATE_STOP;

Просмотреть файл

@ -67,8 +67,8 @@ function setupTwo(win) {
"tabviewframeinitialized", onTabViewFrameInitialized, false);
let restoredContentWindow = restoredWin.TabView.getContentWindow();
// prevent TabItems._update being called before checking cached images
restoredContentWindow.TabItems._pauseUpdateForTest = true;
// prevent thumbnails from being updated before checking cached images
restoredContentWindow.TabItems.pausePainting();
let nextStep = function() {
// since we are not sure whether the frame is initialized first or two tabs
@ -130,17 +130,17 @@ function updateAndCheck() {
// force all canvas to update
let contentWindow = restoredWin.TabView.getContentWindow();
contentWindow.TabItems._pauseUpdateForTest = false;
contentWindow.TabItems.resumePainting();
let tabItems = contentWindow.TabItems.getItems();
tabItems.forEach(function(tabItem) {
tabItem.addSubscriber("updated", function onUpdated() {
tabItem.removeSubscriber("updated", onUpdated);
tabItem.addSubscriber("thumbnailUpdated", function onUpdated() {
tabItem.removeSubscriber("thumbnailUpdated", onUpdated);
ok(!tabItem.isShowingCachedData(),
"Tab item is not showing cached data anymore. " +
tabItem.tab.linkedBrowser.currentURI.spec);
});
contentWindow.TabItems.update(tabItem.tab);
contentWindow.TabItems.addToUpdateQueue(tabItem.tab);
});
// clean up and finish

Просмотреть файл

@ -26,9 +26,9 @@ function test() {
cw.TabItems.pausePainting();
groupItem.getChildren().forEach(function (tabItem) {
tabItem.addSubscriber("updated", function onUpdated() {
tabItem.removeSubscriber("updated", onUpdated);
tabItem._testLastTabUpdateTime = tabItem._lastTabUpdateTime;
tabItem.addSubscriber("thumbnailUpdated", function onUpdated() {
tabItem.removeSubscriber("thumbnailUpdated", onUpdated);
tabItem._testLastTabUpdateTime = Date.now();
if (--numTabsToUpdate)
return;
@ -37,7 +37,7 @@ function test() {
finish();
});
cw.TabItems.update(tabItem.tab);
cw.TabItems.addToUpdateQueue(tabItem.tab);
});
cw.TabItems.resumePainting();

Просмотреть файл

@ -3,66 +3,72 @@
function test() {
let cw;
let prefix;
let timestamp;
let thumbnailUpdateCount = 0;
let storeTimestamp = function () {
timestamp = cw.TabItems._lastUpdateTime;
}
let checkTimestamp = function () {
is(timestamp, cw.TabItems._lastUpdateTime, prefix +
": tabs were not updated");
}
let actionAddTab = function () {
storeTimestamp();
gBrowser.addTab("about:home");
function actionAddTab() {
let count = thumbnailUpdateCount;
addUpdateListener(gBrowser.addTab("about:home"));
afterAllTabsLoaded(function () {
checkTimestamp();
is(thumbnailUpdateCount, count, "add-tab: tabs were not updated");
next();
});
}
let actionMoveTab = function () {
storeTimestamp();
function actionMoveTab() {
let count = thumbnailUpdateCount;
gBrowser.moveTabTo(gBrowser.tabs[0], 1);
gBrowser.moveTabTo(gBrowser.tabs[1], 0);
checkTimestamp();
is(thumbnailUpdateCount, count, "move-tab: tabs were not updated");
next();
}
let actionSelectTab = function () {
storeTimestamp();
function actionSelectTab() {
let count = thumbnailUpdateCount;
gBrowser.selectedTab = gBrowser.tabs[1]
gBrowser.selectedTab = gBrowser.tabs[0]
checkTimestamp();
is(thumbnailUpdateCount, count, "select-tab: tabs were not updated");
next();
}
let actionRemoveTab = function () {
storeTimestamp();
function actionRemoveTab() {
let count = thumbnailUpdateCount;
gBrowser.removeTab(gBrowser.tabs[1]);
checkTimestamp();
is(thumbnailUpdateCount, count, "remove-tab: tabs were not updated");
next();
}
function addUpdateListener(tab) {
let tabItem = tab._tabViewTabItem;
function onUpdate() thumbnailUpdateCount++;
tabItem.addSubscriber("thumbnailUpdated", onUpdate);
registerCleanupFunction(function () {
tabItem.removeSubscriber("thumbnailUpdated", onUpdate)
});
}
function finishTest() {
let count = thumbnailUpdateCount;
showTabView(function () {
isnot(thumbnailUpdateCount, count, "finish: tabs were updated");
hideTabView(finish);
});
}
let actions = [
{name: "add", func: actionAddTab},
{name: "move", func: actionMoveTab},
{name: "select", func: actionSelectTab},
{name: "remove", func: actionRemoveTab}
actionAddTab, actionMoveTab, actionSelectTab, actionRemoveTab
];
let next = function () {
function next() {
let action = actions.shift();
if (action) {
prefix = action.name;
action.func();
action();
} else {
finish();
finishTest();
}
}
@ -70,6 +76,9 @@ function test() {
showTabView(function () {
cw = TabView.getContentWindow();
hideTabView(next);
hideTabView(function () {
addUpdateListener(gBrowser.tabs[0]);
next();
});
});
}

Просмотреть файл

@ -21,13 +21,13 @@ function test() {
cw.TabItems.pausePainting();
tabItem.addSubscriber("updated", function onUpdated() {
tabItem.removeSubscriber("updated", onUpdated);
tabItem.addSubscriber("thumbnailUpdated", function onUpdated() {
tabItem.removeSubscriber("thumbnailUpdated", onUpdated);
ok(isIdle, "tabItem is updated only when UI is idle");
finish();
});
cw.TabItems.update(tab);
cw.TabItems.addToUpdateQueue(tab);
cw.TabItems.resumePainting();
});
}

Просмотреть файл

@ -2,18 +2,28 @@
http://creativecommons.org/publicdomain/zero/1.0/ */
let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
let stateBackup = ss.getBrowserState();
let win;
let cw;
let stateBackup;
function test() {
waitForExplicitFinish();
registerCleanupFunction(function () {
ss.setBrowserState(stateBackup);
});
newWindowWithTabView(
function(newWin) {
cw = win.TabView.getContentWindow();
hideTabView(testRestoreNormal, win);
},
function(newWin) {
win = newWin;
TabView._initFrame(function() {
executeSoon(testRestoreNormal);
});
stateBackup = ss.getWindowState(win);
registerCleanupFunction(function () {
win.close();
});
}
);
}
function testRestoreNormal() {
@ -23,8 +33,8 @@ function testRestoreNormal() {
}
function testRestorePinned() {
gBrowser.loadOneTab("about:blank", {inBackground: true});
gBrowser.pinTab(gBrowser.tabs[0]);
win.gBrowser.loadOneTab("about:blank", {inBackground: true});
win.gBrowser.pinTab(win.gBrowser.tabs[0]);
testRestore("pinned", function () {
waitForBrowserState(JSON.parse(stateBackup), testRestoreHidden);
@ -32,22 +42,23 @@ function testRestorePinned() {
}
function testRestoreHidden() {
let groupItem = createGroupItemWithBlankTabs(window, 20, 20, 20, 1);
let tabItem = groupItem.getChild(0);
showTabView(function() {
let groupItem = createGroupItemWithBlankTabs(win, 200, 200, 20, 1);
let tabItem = groupItem.getChild(0);
hideGroupItem(groupItem, function () {
testRestore("hidden", function () {
isnot(tabItem.container.style.display, "none", "tabItem is visible");
waitForFocus(finish);
hideGroupItem(groupItem, function () {
testRestore("hidden", function () {
isnot(tabItem.container.style.display, "none", "tabItem is visible");
waitForFocus(finish);
});
});
});
}, win);
}
function testRestore(prefix, callback) {
waitForBrowserState(createBrowserState(), function () {
is(gBrowser.tabs.length, 2, prefix + ": two tabs restored");
is(win.gBrowser.tabs.length, 2, prefix + ": two tabs restored");
let cw = TabView.getContentWindow();
is(cw.GroupItems.groupItems.length, 2, prefix + ": we have two groupItems");
let [groupItem1, groupItem2] = cw.GroupItems.groupItems;
@ -62,30 +73,29 @@ function testRestore(prefix, callback) {
}
function waitForBrowserState(state, callback) {
window.addEventListener("SSWindowStateReady", function onReady() {
window.removeEventListener("SSWindowStateReady", onReady, false);
executeSoon(callback);
}, false);
whenWindowStateReady(win, function () {
afterAllTabsLoaded(callback, win);
});
ss.setBrowserState(JSON.stringify(state));
executeSoon(function() {
ss.setWindowState(win, JSON.stringify(state), true);
});
}
function createBrowserState() {
let bounds = {left: 20, top: 20, width: 20, height: 20};
let tabViewGroups = {nextID: 99, activeGroupId: 1};
let tabViewGroup = {
"1st-group-id": {bounds: bounds, title: "new group 1", id: "1st-group-id"},
"2nd-group-id": {bounds: bounds, title: "new group 2", id: "2nd-group-id"}
"1st-group-id": {bounds: {left: 20, top: 20, width: 200, height: 200}, title: "new group 1", id: "1st-group-id"},
"2nd-group-id": {bounds: {left: 240, top: 20, width: 200, height: 200}, title: "new group 2", id: "2nd-group-id"}
};
let tab1Data = {bounds: bounds, url: "about:robots", groupID: "2nd-group-id"};
let tab1Data = {bounds: {left: 240, top: 20, width: 20, height: 20}, url: "about:robots", groupID: "2nd-group-id"};
let tab1 = {
entries: [{url: "about:robots"}],
extData: {"tabview-tab": JSON.stringify(tab1Data)}
};
let tab2Data = {bounds: bounds, url: "about:mozilla", groupID: "1st-group-id"};
let tab2Data = {bounds: {left: 20, top: 20, width: 20, height: 20}, url: "about:mozilla", groupID: "1st-group-id"};
let tab2 = {
entries: [{url: "about:mozilla"}],
extData: {"tabview-tab": JSON.stringify(tab2Data)}

Просмотреть файл

@ -12,13 +12,13 @@ function test() {
let groupItem = contentWindow.GroupItems.groupItems[0];
groupItem.getChildren().forEach(function(tabItem) {
tabItem.addSubscriber("updated", function onUpdated() {
tabItem.removeSubscriber("updated", onUpdated);
tabItem.addSubscriber("thumbnailUpdated", function onUpdated() {
tabItem.removeSubscriber("thumbnailUpdated", onUpdated);
if (--numTabsToUpdate == 0)
finish();
});
contentWindow.TabItems.update(tabItem.tab);
contentWindow.TabItems.addToUpdateQueue(tabItem.tab);
});
}, win);
}, function(win) {

Просмотреть файл

@ -81,7 +81,7 @@ function afterAllTabItemsUpdated(callback, win) {
if (--counter == 0)
callback();
});
tabItems.update(tab);
tabItems.addToUpdateQueue(tab);
}
}
if (counter == 0)

Просмотреть файл

@ -893,14 +893,6 @@ let UI = {
if (this._closedLastVisibleTab ||
(this._closedSelectedTabInTabView && !this.closedLastTabInTabView) ||
this.restoredClosedTab) {
if (this.restoredClosedTab) {
// when the tab view UI is being displayed, update the thumb for the
// restored closed tab after the page load
tab.linkedBrowser.addEventListener("load", function onLoad(event) {
tab.linkedBrowser.removeEventListener("load", onLoad, true);
TabItems._update(tab);
}, true);
}
this._closedLastVisibleTab = false;
this._closedSelectedTabInTabView = false;
this.closedLastTabInTabView = false;