| Index: lib/crawler.js |
| diff --git a/lib/crawler.js b/lib/crawler.js |
| index 7bc28dbbe75187a1a752583c81d5c638a24c98ca..f01f6f81851bec00f337718d7b46e9f1543a4add 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,66 @@ 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. |
| + let tabToRemove = this._browser.tabs[0]; |
| + this._browser.removeAllTabsBut(tabToRemove); |
| + // this._tab is a keep alive tab |
| + this._tab = this._createTab().then(tab => |
| + { |
| + // Starting from Firefox 48 (nightly) the sequence of calls addTab and |
| + // removeTab can cause a closing of the browser because a new tab is still |
| + // not here. Because of that we need to remove the previous tab only after |
| + // the new tab is ready. |
| + this._browser.removeTab(tabToRemove); |
| + return tab; |
| + }); |
|
Wladimir Palant
2016/09/14 15:00:13
There is little point pre-allocating a tab, you ca
sergei
2016/09/15 15:33:27
Done.
|
| } |
| TabAllocator.prototype = { |
| /** |
| - * Returns a promise that will resolve into a tab once a tab can be allocated. |
| + * Creates a blank tab in this._browser. |
| + * |
| + * @return {Promise.<tab>} promise which resolves once the tab is fully initialized. |
| + */ |
| + _createTab: function() |
| + { |
| + this._tabs++; |
| + let tab = this._browser.addTab("about:blank"); |
| + if (tab.linkedBrowser.outerWindowID) |
| + return Promise.resolve(tab); |
| + return new Promise((resolve, reject) => |
| + { |
| + let onBrowserInit = (msg) => |
| + { |
| + // https://bugzilla.mozilla.org/show_bug.cgi?id=1256602#c1 |
|
Wladimir Palant
2016/09/14 15:00:13
Please don't use URL-only comments, it should be o
sergei
2016/09/15 15:33:27
Done.
|
| + tab.linkedBrowser.messageManager.removeMessageListener("Browser:Init", onBrowserInit); |
| + resolve(tab); |
| + }; |
| + 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} |
| + * @result {Promise.<tab>} |
| */ |
| getTab: function() |
| { |
| - if (this._tabs.length) |
| - return this._tabs.shift(); |
| - else |
| + if (this._tab) |
| { |
| - let deferred = Promise.defer(); |
| - this._deferred.push(deferred); |
| - return deferred.promise; |
| + let tab = this._tab; |
| + delete this._tab; |
| + return tab; |
| } |
| + if (this._tabs < this._maxtabs) |
| + return this._createTab(); |
| + return new Promise((resolve, reject) => this._resolvers.push(resolve)); |
| }, |
| /** |
| @@ -71,15 +104,33 @@ TabAllocator.prototype = { |
| */ |
| releaseTab: function(tab) |
| { |
| - let browser = tab.parentNode.tabbrowser; |
| - browser.removeTab(tab); |
| - tab = browser.addTab("about:blank"); |
| + // If we are about to close last tab don't close it immediately rather |
| + // allocate a new blank tab and close the current one afterwards. |
| + if (this._tabs == 1) |
| + { |
| + this._tab = this._createTab().then((resultTab) => |
| + { |
| + this.releaseTab(tab); |
| + return resultTab; |
| + }); |
| + return; |
| + } |
| - if (this._deferred.length) |
| - this._deferred.shift().resolve(tab); |
| - else |
| - this._tabs.push(tab); |
| - } |
| + this._browser.removeTab(tab); |
| + this._tabs--; |
| + if (this._resolvers.length) |
| + { |
| + if (this._tab) |
| + { |
| + this._resolvers.shift()(this._tab); |
| + delete this._tab; |
| + } |
| + else if (this._tabs < this._maxtabs) |
| + { |
| + this._resolvers.shift()(this._createTab()); |
| + } |
| + } |
| + }, |
| }; |
| /** |
| @@ -231,8 +282,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 |