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`).
 |     // Filter out closed tabs (represented as `null`).
 | ||||||
|     tabs = tabs.filter(tab => !!tab); |     tabs = tabs.filter(tab => !!tab); | ||||||
|       tabs.forEach(tab => { | 
 | ||||||
|         // FIXME Also try to fetch low-res favicon. But we should use actor
 |     for (let tab of tabs) { | ||||||
|         // support for this to get the high-res one (bug 1061654).
 |       if (tab.favicon) { | ||||||
|         let url = new URL(tab.url); |         let base64Favicon = btoa(String.fromCharCode.apply(String, tab.favicon)); | ||||||
|         if (url.protocol.startsWith("http")) { |         tab.icon = "data:image/png;base64," + base64Favicon; | ||||||
|           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 { |       } else { | ||||||
|         tab.icon = "chrome://devtools/skin/images/globe.svg"; |         tab.icon = "chrome://devtools/skin/images/globe.svg"; | ||||||
|       } |       } | ||||||
|       }); |     } | ||||||
|  | 
 | ||||||
|     this.setState({ tabs }); |     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; |     this._form = form; | ||||||
|  |     if (this.options.favicons) { | ||||||
|  |       this._form.favicon = await this.getFaviconData(); | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     return this; |     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
	
	 Julian Descottes
						Julian Descottes