Index: lib/crawler.js |
diff --git a/lib/crawler.js b/lib/crawler.js |
index 7bc28dbbe75187a1a752583c81d5c638a24c98ca..8e7144acee6667440528cb34e62d419bee7aa784 100644 |
--- a/lib/crawler.js |
+++ b/lib/crawler.js |
@@ -25,7 +25,7 @@ let {FilterNotifier} = abprequire("filterNotifier"); |
let {FilterStorage} = abprequire("filterStorage"); |
/** |
- * Creates a pool of tabs and allocates them to tasks on request. |
+ * Allocates tabs on request but not more than maxtabs at the same time. |
* |
* @param {tabbrowser} browser |
* The tabbed browser where tabs should be created |
@@ -35,33 +35,63 @@ let {FilterStorage} = abprequire("filterStorage"); |
*/ |
function TabAllocator(browser, maxtabs) |
{ |
- browser.removeAllTabsBut(browser.tabs[0]) |
- |
- this._tabs = []; |
- for (let i = 0; i < maxtabs; i++) |
- this._tabs.push(browser.addTab("about:blank")); |
- |
- browser.removeTab(browser.tabs[0]); |
- |
- this._deferred = []; |
+ this._browser = browser; |
+ this._tabs = 0; |
+ this._maxtabs = maxtabs; |
+ // The queue containing resolve functions of promises waiting for a tab. |
+ this._resolvers = []; |
+ // Keep at least one tab alive to prevent browser from closing itself. |
+ this._tabKeepingWindowAlive = this._browser.tabs[0]; |
+ this._browser.removeAllTabsBut(this._tabKeepingWindowAlive); |
} |
TabAllocator.prototype = { |
+ _removeTabKeepingWindowAlive: function() |
+ { |
+ if (!this._tabKeepingWindowAlive) |
+ return; |
+ this._browser.removeTab(this._tabKeepingWindowAlive); |
+ delete this._tabKeepingWindowAlive; |
+ }, |
+ |
/** |
- * Returns a promise that will resolve into a tab once a tab can be allocated. |
- * The tab cannot be used by other tasks until releaseTab() is called. |
+ * Creates a blank tab in this._browser. |
* |
- * @result {Promise} |
+ * @return {Promise.<tab>} promise which resolves once the tab is fully initialized. |
*/ |
- getTab: function() |
+ _createTab: function() |
{ |
- if (this._tabs.length) |
- return this._tabs.shift(); |
- else |
+ this._tabs++; |
+ let tab = this._browser.addTab("about:blank"); |
+ if (tab.linkedBrowser.outerWindowID) |
{ |
- let deferred = Promise.defer(); |
- this._deferred.push(deferred); |
- return deferred.promise; |
+ this._removeTabKeepingWindowAlive(); |
+ return Promise.resolve(tab); |
} |
+ return new Promise((resolve, reject) => |
+ { |
+ let onBrowserInit = (msg) => |
+ { |
+ tab.linkedBrowser.messageManager.removeMessageListener("Browser:Init", onBrowserInit); |
+ this._removeTabKeepingWindowAlive(); |
+ resolve(tab); |
+ }; |
+ // "Browser:Init" message is sent once the browser is ready, see |
+ // https://bugzil.la/1256602#c1 |
+ tab.linkedBrowser.messageManager.addMessageListener("Browser:Init", onBrowserInit); |
+ }); |
+ }, |
+ |
+ /** |
+ * Returns a promise that will resolve into a tab once a tab is allocated. |
+ * The tab cannot be used by other tasks until releaseTab() is called. |
+ * |
+ * @result {Promise.<tab>} |
+ */ |
+ getTab: function() |
+ { |
+ if (this._tabs < this._maxtabs) |
+ return this._createTab(); |
+ return new Promise((resolve, reject) => this._resolvers.push(resolve)); |
}, |
/** |
@@ -71,15 +101,23 @@ TabAllocator.prototype = { |
*/ |
releaseTab: function(tab) |
{ |
- let browser = tab.parentNode.tabbrowser; |
- browser.removeTab(tab); |
- tab = browser.addTab("about:blank"); |
- |
- if (this._deferred.length) |
- this._deferred.shift().resolve(tab); |
+ // If we are about to close last tab don't close it immediately to keep |
+ // the window alive. It will be closed when a new tab is created. |
+ if (this._tabs > 1) |
+ this._browser.removeTab(tab); |
else |
- this._tabs.push(tab); |
- } |
+ { |
+ // navigate away from early opened URL |
+ tab.linkedBrowser.loadURI('about:blank', null, null); |
+ this._tabKeepingWindowAlive = tab; |
+ } |
+ |
+ this._tabs--; |
+ if (this._resolvers.length && this._tabs < this._maxtabs) |
+ { |
+ this._resolvers.shift()(this._createTab()); |
+ } |
+ }, |
}; |
/** |
@@ -231,8 +269,8 @@ function run(window, urls, timeout, maxtabs, targetURL, onDone) |
exports.run = run; |
/** |
- * Spawns a {Task} task to crawl each url from `urls` argument and calls |
- * `onDone` when all tasks are finished. |
+ * Spawns a {Task} task to crawl each url from urls argument and calls |
+ * onDone when all tasks are finished. |
* @param {Window} window |
* The browser window we're operating in |
* @param {String[]} urls |