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);
}
update() {
this.props.client.mainRoot.listTabs().then(({ tabs }) => {
// Filter out closed tabs (represented as `null`).
tabs = tabs.filter(tab => !!tab);
tabs.forEach(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).
let url = new URL(tab.url);
if (url.protocol.startsWith("http")) {
let prePath = url.origin;
let idx = url.pathname.lastIndexOf("/");
if (idx === -1) {
prePath += url.pathname;
} else {
prePath += url.pathname.substr(0, idx);
}
tab.icon = prePath + "/favicon.ico";
} else {
tab.icon = "chrome://devtools/skin/images/globe.svg";
}
});
this.setState({ tabs });
});
async update() {
let { tabs } = await this.props.client.mainRoot.listTabs({ favicons: true });
// Filter out closed tabs (represented as `null`).
tabs = tabs.filter(tab => !!tab);
for (let tab of tabs) {
if (tab.favicon) {
let base64Favicon = btoa(String.fromCharCode.apply(String, tab.favicon));
tab.icon = "data:image/png;base64," + base64Favicon;
} else {
tab.icon = "chrome://devtools/skin/images/globe.svg";
}
}
this.setState({ tabs });
}
render() {

View File

@ -29,6 +29,23 @@ add_task(function* () {
return container.querySelector(".target-name").title === TAB_URL;
}, 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
yield removeTab(newTab);

View File

@ -286,7 +286,7 @@ RootActor.prototype = {
* would trigger any lazy tabs to be loaded, greatly increasing resource usage. Avoid
* this method whenever possible.
*/
onListTabs: async function () {
onListTabs: async function (request) {
let tabList = this._parameters.tabList;
if (!tabList) {
return { from: this.actorID, error: "noTabs",
@ -305,7 +305,8 @@ RootActor.prototype = {
let tabActorList = [];
let selected;
let tabActors = await tabList.getList();
let options = request.options || {};
let tabActors = await tabList.getList(options);
for (let tabActor of tabActors) {
if (tabActor.exited) {
// 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, "ProcessActorList", "devtools/server/actors/process", true);
loader.lazyImporter(this, "AddonManager", "resource://gre/modules/AddonManager.jsm");
loader.lazyImporter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
/**
* 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(
DebuggerServer.chromeWindowType);
let selectedBrowser = null;
@ -279,7 +280,7 @@ BrowserTabList.prototype.getList = function () {
for (let browser of this._getBrowsers()) {
let selected = browser === selectedBrowser;
actorPromises.push(
this._getActorForBrowser(browser)
this._getActorForBrowser(browser, browserActorOptions)
.then(actor => {
// Set the 'selected' properties on all actors correctly.
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.
let actor = this._actorByBrowser.get(browser);
if (actor) {
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._checkListening();
return actor.connect();
@ -695,16 +699,19 @@ exports.BrowserTabList = BrowserTabList;
*
* @param connection The main RDP connection.
* @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._browser = browser;
this._form = null;
this.exited = false;
this.options = options;
}
BrowserTabActor.prototype = {
connect() {
async connect() {
let onDestroy = () => {
if (this._deferredUpdate) {
// Reject the update promise if the tab was destroyed while requesting an update
@ -716,10 +723,14 @@ BrowserTabActor.prototype = {
this.exit();
};
let connect = DebuggerServer.connectToChild(this._conn, this._browser, onDestroy);
return connect.then(form => {
this._form = form;
return this;
});
let form = await connect;
this._form = form;
if (this.options.favicons) {
this._form.favicon = await this.getFaviconData();
}
return this;
},
get _tabbrowser() {
@ -736,27 +747,52 @@ BrowserTabActor.prototype = {
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,
// so only request form update if some code is still listening on the other
// side.
if (!this.exited) {
this._deferredUpdate = defer();
if (this.exited) {
return this.connect();
}
let form = await new Promise(resolve => {
let onFormUpdate = msg => {
// There may be more than just one childtab.js up and running
if (this._form.actor != msg.json.actor) {
return;
}
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.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) {
form.url = this.url;
}
return form;
},

View File

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

View File

@ -5,7 +5,7 @@
"use strict";
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
@ -48,10 +48,13 @@ RootClient.prototype = {
/**
* List the open tabs.
*
* @param object options
* Optional flags for listTabs:
* - boolean favicons: return favicon data
* @param function onResponse
* Called with the response packet.
*/
listTabs: DebuggerClient.requester({ type: "listTabs" }),
listTabs: DebuggerClient.requester({ type: "listTabs", options: arg(0) }),
/**
* List the installed addons.