Bug 1272774 - allow listTabs to return favicon data from PlacesUtils;r=ochameau

MozReview-Commit-ID: 8bkn3mG6YkL

--HG--
extra : rebase_source : 9f514f1adbc4e79022c8c18c5195d3e1d1298062
This commit is contained in:
Julian Descottes 2018-01-04 17:25:45 +01:00
parent f714f056d0
commit 1d94b51b97
6 changed files with 98 additions and 47 deletions

View file

@ -51,29 +51,22 @@ class TabsPanel extends Component {
client.removeListener("tabListChanged", this.update); client.removeListener("tabListChanged", this.update);
} }
update() { async update() {
this.props.client.mainRoot.listTabs().then(({ tabs }) => { let { tabs } = await this.props.client.mainRoot.listTabs({ favicons: true });
// Filter out closed tabs (represented as `null`).
tabs = tabs.filter(tab => !!tab); // Filter out closed tabs (represented as `null`).
tabs.forEach(tab => { tabs = tabs.filter(tab => !!tab);
// FIXME Also try to fetch low-res favicon. But we should use actor
// support for this to get the high-res one (bug 1061654). for (let tab of tabs) {
let url = new URL(tab.url); if (tab.favicon) {
if (url.protocol.startsWith("http")) { let base64Favicon = btoa(String.fromCharCode.apply(String, tab.favicon));
let prePath = url.origin; tab.icon = "data:image/png;base64," + base64Favicon;
let idx = url.pathname.lastIndexOf("/"); } else {
if (idx === -1) { tab.icon = "chrome://devtools/skin/images/globe.svg";
prePath += url.pathname; }
} else { }
prePath += url.pathname.substr(0, idx);
} this.setState({ tabs });
tab.icon = prePath + "/favicon.ico";
} else {
tab.icon = "chrome://devtools/skin/images/globe.svg";
}
});
this.setState({ tabs });
});
} }
render() { render() {

View file

@ -29,6 +29,23 @@ add_task(function* () {
return container.querySelector(".target-name").title === TAB_URL; return container.querySelector(".target-name").title === TAB_URL;
}, 100); }, 100);
let icon = container.querySelector(".target-icon");
ok(icon && icon.src, "Tab icon found and src attribute is not empty");
info("Check if the tab icon is a valid image");
yield new Promise(r => {
let image = new Image();
image.onload = () => {
ok(true, "Favicon is not a broken image");
r();
};
image.onerror = () => {
ok(false, "Favicon is a broken image");
r();
};
image.src = icon.src;
});
// Finally, close the tab // Finally, close the tab
yield removeTab(newTab); yield removeTab(newTab);

View file

@ -286,7 +286,7 @@ RootActor.prototype = {
* would trigger any lazy tabs to be loaded, greatly increasing resource usage. Avoid * would trigger any lazy tabs to be loaded, greatly increasing resource usage. Avoid
* this method whenever possible. * this method whenever possible.
*/ */
onListTabs: async function () { onListTabs: async function (request) {
let tabList = this._parameters.tabList; let tabList = this._parameters.tabList;
if (!tabList) { if (!tabList) {
return { from: this.actorID, error: "noTabs", return { from: this.actorID, error: "noTabs",
@ -305,7 +305,8 @@ RootActor.prototype = {
let tabActorList = []; let tabActorList = [];
let selected; let selected;
let tabActors = await tabList.getList(); let options = request.options || {};
let tabActors = await tabList.getList(options);
for (let tabActor of tabActors) { for (let tabActor of tabActors) {
if (tabActor.exited) { if (tabActor.exited) {
// Tab actor may have exited while we were gathering the list. // Tab actor may have exited while we were gathering the list.

View file

@ -20,6 +20,7 @@ loader.lazyRequireGetter(this, "WorkerActorList", "devtools/server/actors/worker
loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActorList", "devtools/server/actors/worker-list", true); loader.lazyRequireGetter(this, "ServiceWorkerRegistrationActorList", "devtools/server/actors/worker-list", true);
loader.lazyRequireGetter(this, "ProcessActorList", "devtools/server/actors/process", true); loader.lazyRequireGetter(this, "ProcessActorList", "devtools/server/actors/process", true);
loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm"); loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
loader.lazyImporter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
/** /**
* Browser-specific actors. * Browser-specific actors.
@ -256,7 +257,7 @@ BrowserTabList.prototype._getChildren = function (window) {
}); });
}; };
BrowserTabList.prototype.getList = function () { BrowserTabList.prototype.getList = function (browserActorOptions) {
let topXULWindow = Services.wm.getMostRecentWindow( let topXULWindow = Services.wm.getMostRecentWindow(
DebuggerServer.chromeWindowType); DebuggerServer.chromeWindowType);
let selectedBrowser = null; let selectedBrowser = null;
@ -279,7 +280,7 @@ BrowserTabList.prototype.getList = function () {
for (let browser of this._getBrowsers()) { for (let browser of this._getBrowsers()) {
let selected = browser === selectedBrowser; let selected = browser === selectedBrowser;
actorPromises.push( actorPromises.push(
this._getActorForBrowser(browser) this._getActorForBrowser(browser, browserActorOptions)
.then(actor => { .then(actor => {
// Set the 'selected' properties on all actors correctly. // Set the 'selected' properties on all actors correctly.
actor.selected = selected; actor.selected = selected;
@ -309,15 +310,18 @@ BrowserTabList.prototype.getList = function () {
}); });
}; };
BrowserTabList.prototype._getActorForBrowser = function (browser) { /**
* @param browserActorOptions see options argument of BrowserTabActor constructor.
*/
BrowserTabList.prototype._getActorForBrowser = function (browser, browserActorOptions) {
// Do we have an existing actor for this browser? If not, create one. // Do we have an existing actor for this browser? If not, create one.
let actor = this._actorByBrowser.get(browser); let actor = this._actorByBrowser.get(browser);
if (actor) { if (actor) {
this._foundCount++; this._foundCount++;
return actor.update(); return actor.update(browserActorOptions);
} }
actor = new BrowserTabActor(this._connection, browser); actor = new BrowserTabActor(this._connection, browser, browserActorOptions);
this._actorByBrowser.set(browser, actor); this._actorByBrowser.set(browser, actor);
this._checkListening(); this._checkListening();
return actor.connect(); return actor.connect();
@ -695,16 +699,19 @@ exports.BrowserTabList = BrowserTabList;
* *
* @param connection The main RDP connection. * @param connection The main RDP connection.
* @param browser <xul:browser> or <iframe mozbrowser> element to connect to. * @param browser <xul:browser> or <iframe mozbrowser> element to connect to.
* @param options
* - {Boolean} favicons: true if the form should include the favicon for the tab.
*/ */
function BrowserTabActor(connection, browser) { function BrowserTabActor(connection, browser, options = {}) {
this._conn = connection; this._conn = connection;
this._browser = browser; this._browser = browser;
this._form = null; this._form = null;
this.exited = false; this.exited = false;
this.options = options;
} }
BrowserTabActor.prototype = { BrowserTabActor.prototype = {
connect() { async connect() {
let onDestroy = () => { let onDestroy = () => {
if (this._deferredUpdate) { if (this._deferredUpdate) {
// Reject the update promise if the tab was destroyed while requesting an update // Reject the update promise if the tab was destroyed while requesting an update
@ -716,10 +723,14 @@ BrowserTabActor.prototype = {
this.exit(); this.exit();
}; };
let connect = DebuggerServer.connectToChild(this._conn, this._browser, onDestroy); let connect = DebuggerServer.connectToChild(this._conn, this._browser, onDestroy);
return connect.then(form => { let form = await connect;
this._form = form;
return this; this._form = form;
}); if (this.options.favicons) {
this._form.favicon = await this.getFaviconData();
}
return this;
}, },
get _tabbrowser() { get _tabbrowser() {
@ -736,27 +747,52 @@ BrowserTabActor.prototype = {
this._browser.frameLoader.messageManager; this._browser.frameLoader.messageManager;
}, },
update() { async getFaviconData() {
try {
let { data } = await PlacesUtils.promiseFaviconData(this._form.url);
return data;
} catch (e) {
// Favicon unavailable for this url.
return null;
}
},
/**
* @param {Object} options
* See BrowserTabActor constructor.
*/
async update(options = {}) {
// Update the BrowserTabActor options.
this.options = options;
// If the child happens to be crashed/close/detach, it won't have _form set, // If the child happens to be crashed/close/detach, it won't have _form set,
// so only request form update if some code is still listening on the other // so only request form update if some code is still listening on the other
// side. // side.
if (!this.exited) { if (this.exited) {
this._deferredUpdate = defer(); return this.connect();
}
let form = await new Promise(resolve => {
let onFormUpdate = msg => { let onFormUpdate = msg => {
// There may be more than just one childtab.js up and running // There may be more than just one childtab.js up and running
if (this._form.actor != msg.json.actor) { if (this._form.actor != msg.json.actor) {
return; return;
} }
this._mm.removeMessageListener("debug:form", onFormUpdate); this._mm.removeMessageListener("debug:form", onFormUpdate);
this._form = msg.json;
this._deferredUpdate.resolve(this); resolve(msg.json);
}; };
this._mm.addMessageListener("debug:form", onFormUpdate); this._mm.addMessageListener("debug:form", onFormUpdate);
this._mm.sendAsyncMessage("debug:form"); this._mm.sendAsyncMessage("debug:form");
return this._deferredUpdate.promise; });
this._form = form;
if (this.options.favicons) {
this._form.favicon = await this.getFaviconData();
} }
return this.connect(); return this;
}, },
/** /**
@ -809,6 +845,7 @@ BrowserTabActor.prototype = {
if (!form.url) { if (!form.url) {
form.url = this.url; form.url = this.url;
} }
return form; return form;
}, },

View file

@ -316,8 +316,8 @@ DebuggerClient.prototype = {
* This function exists only to preserve DebuggerClient's interface; * This function exists only to preserve DebuggerClient's interface;
* new code should say 'client.mainRoot.listTabs()'. * new code should say 'client.mainRoot.listTabs()'.
*/ */
listTabs: function (onResponse) { listTabs: function (options, onResponse) {
return this.mainRoot.listTabs(onResponse); return this.mainRoot.listTabs(options, onResponse);
}, },
/* /*

View file

@ -5,7 +5,7 @@
"use strict"; "use strict";
const { Ci } = require("chrome"); const { Ci } = require("chrome");
const {DebuggerClient} = require("devtools/shared/client/debugger-client"); const { arg, DebuggerClient } = require("devtools/shared/client/debugger-client");
/** /**
* A RootClient object represents a root actor on the server. Each * A RootClient object represents a root actor on the server. Each
@ -48,10 +48,13 @@ RootClient.prototype = {
/** /**
* List the open tabs. * List the open tabs.
* *
* @param object options
* Optional flags for listTabs:
* - boolean favicons: return favicon data
* @param function onResponse * @param function onResponse
* Called with the response packet. * Called with the response packet.
*/ */
listTabs: DebuggerClient.requester({ type: "listTabs" }), listTabs: DebuggerClient.requester({ type: "listTabs", options: arg(0) }),
/** /**
* List the installed addons. * List the installed addons.