forked from mirrors/gecko-dev
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:
parent
f714f056d0
commit
1d94b51b97
6 changed files with 98 additions and 47 deletions
|
|
@ -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() {
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
},
|
},
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue