1- import { Menu , MenuItem } from 'electron'
1+ import { Menu , MenuItem , protocol , nativeImage , app } from 'electron'
22import { ExtensionContext } from '../context'
33import { PopupView } from '../popup'
44import { ExtensionEvent } from '../router'
5- import { getIconImage , getExtensionUrl , getExtensionManifest } from './common'
5+ import {
6+ getExtensionUrl ,
7+ getExtensionManifest ,
8+ getIconPath ,
9+ resolveExtensionPath ,
10+ matchSize ,
11+ ResizeType ,
12+ } from './common'
613
714const debug = require ( 'debug' ) ( 'electron-chrome-extensions:browserAction' )
815
16+ if ( ! app . isReady ( ) ) {
17+ protocol . registerSchemesAsPrivileged ( [ { scheme : 'crx' , privileges : { bypassCSP : true } } ] )
18+ }
19+
920interface ExtensionAction {
1021 color ?: string
1122 text ?: string
1223 title ?: string
13- icon ?:
14- | string
15- | {
16- path : string
17- }
24+ icon ?: chrome . browserAction . TabIconDetails
1825 popup ?: string
1926}
2027
@@ -35,8 +42,8 @@ const getBrowserActionDefaults = (extension: Electron.Extension): ExtensionActio
3542
3643 action . title = browser_action . default_title || manifest . name
3744
38- const iconImage = getIconImage ( extension )
39- if ( iconImage ) action . icon = iconImage . toDataURL ( )
45+ const iconPath = getIconPath ( extension )
46+ if ( iconPath ) action . icon = { path : iconPath }
4047
4148 if ( browser_action . default_popup ) {
4249 action . popup = browser_action . default_popup
@@ -114,7 +121,18 @@ export class BrowserActionAPI {
114121 handleProp ( 'BadgeText' , 'text' )
115122 handleProp ( 'Title' , 'title' )
116123 handleProp ( 'Popup' , 'popup' )
117- handle ( 'browserAction.setIcon' , setter ( 'icon' ) )
124+
125+ const iconSetter = setter ( 'icon' )
126+
127+ // setIcon is unique in that it can pass in a variety of properties. Here we normalize them
128+ // to use 'icon'.
129+ handle (
130+ 'browserAction.setIcon' ,
131+ ( event , { tabId, ...details } : chrome . browserAction . TabIconDetails ) => {
132+ const iconDetails = { tabId, icon : details }
133+ iconSetter ( event , iconDetails )
134+ }
135+ )
118136
119137 // browserAction preload API
120138 const preloadOpts = { allowRemote : true , extensionContext : false }
@@ -151,6 +169,7 @@ export class BrowserActionAPI {
151169 delete actionDetails . tabs [ tabId ]
152170 }
153171 }
172+ this . onUpdate ( )
154173 } )
155174
156175 this . setupSession ( this . ctx . session )
@@ -164,6 +183,83 @@ export class BrowserActionAPI {
164183 session . on ( 'extension-unloaded' , ( event , extension ) => {
165184 this . removeActions ( extension . id )
166185 } )
186+
187+ session . protocol . registerBufferProtocol ( 'crx' , this . handleCrxRequest )
188+ }
189+
190+ private handleCrxRequest = (
191+ request : Electron . ProtocolRequest ,
192+ callback : ( response : Electron . ProtocolResponse ) => void
193+ ) => {
194+ debug ( '%s' , request . url )
195+
196+ let response : Electron . ProtocolResponse
197+
198+ try {
199+ const url = new URL ( request . url )
200+ const { hostname : requestType } = url
201+
202+ switch ( requestType ) {
203+ case 'extension-icon' : {
204+ const tabId = url . searchParams . get ( 'tabId' )
205+
206+ const fragments = url . pathname . split ( '/' )
207+ const extensionId = fragments [ 1 ]
208+ const imageSize = parseInt ( fragments [ 2 ] , 10 )
209+ const resizeType = parseInt ( fragments [ 3 ] , 10 ) || ResizeType . Up
210+
211+ const extension = this . ctx . session . getExtension ( extensionId )
212+
213+ let iconDetails : chrome . browserAction . TabIconDetails | undefined
214+
215+ const action = this . actionMap . get ( extensionId )
216+ if ( action ) {
217+ iconDetails = ( tabId && action . tabs [ tabId ] ?. icon ) || action . icon
218+ }
219+
220+ let iconImage
221+
222+ if ( extension && iconDetails ) {
223+ if ( typeof iconDetails . path === 'string' ) {
224+ const iconAbsPath = resolveExtensionPath ( extension , iconDetails . path )
225+ if ( iconAbsPath ) iconImage = nativeImage . createFromPath ( iconAbsPath )
226+ } else if ( typeof iconDetails . path === 'object' ) {
227+ const imagePath = matchSize ( iconDetails . path , imageSize , resizeType )
228+ const iconAbsPath = imagePath && resolveExtensionPath ( extension , imagePath )
229+ if ( iconAbsPath ) iconImage = nativeImage . createFromPath ( iconAbsPath )
230+ } else if ( typeof iconDetails . imageData === 'string' ) {
231+ iconImage = nativeImage . createFromDataURL ( iconDetails . imageData )
232+ } else if ( typeof iconDetails . imageData === 'object' ) {
233+ const imageData = matchSize ( iconDetails . imageData as any , imageSize , resizeType )
234+ iconImage = imageData ? nativeImage . createFromDataURL ( imageData ) : undefined
235+ }
236+ }
237+
238+ if ( iconImage ) {
239+ response = {
240+ statusCode : 200 ,
241+ mimeType : 'image/png' ,
242+ data : iconImage . toPNG ( ) ,
243+ }
244+ } else {
245+ response = { statusCode : 400 }
246+ }
247+
248+ break
249+ }
250+ default : {
251+ response = { statusCode : 400 }
252+ }
253+ }
254+ } catch ( e ) {
255+ console . error ( e )
256+
257+ response = {
258+ statusCode : 500 ,
259+ }
260+ }
261+
262+ callback ( response )
167263 }
168264
169265 private getAction ( extensionId : string ) {
@@ -201,11 +297,25 @@ export class BrowserActionAPI {
201297 }
202298 }
203299
204- private getState ( event : ExtensionEvent ) {
205- const actions = Array . from ( this . actionMap . entries ( ) ) . map ( ( val : any ) => ( {
206- id : val [ 0 ] ,
207- ...val [ 1 ] ,
208- } ) )
300+ private getState ( ) {
301+ // Get state without icon data.
302+ const actions = Array . from ( this . actionMap . entries ( ) ) . map ( ( [ id , details ] ) => {
303+ const { icon, tabs, ...rest } = details
304+
305+ const tabsInfo : { [ key : string ] : any } = { }
306+
307+ for ( const tabId of Object . keys ( tabs ) ) {
308+ const { icon, ...rest } = tabs [ tabId ]
309+ tabsInfo [ tabId ] = rest
310+ }
311+
312+ return {
313+ id,
314+ tabs : tabsInfo ,
315+ ...rest ,
316+ }
317+ } )
318+
209319 const activeTab = this . ctx . store . getActiveTabOfCurrentWindow ( )
210320 return { activeTabId : activeTab ?. id , actions }
211321 }
0 commit comments