зеркало из https://github.com/mozilla/gecko-dev.git
Bug 691740 - Update thumbnails separately in their own queue r=tim
This commit is contained in:
Родитель
7d24f33b08
Коммит
4fd6e30e27
|
@ -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;
|
||||
|
|
Загрузка…
Ссылка в новой задаче