From fdadc28a174bfb65cd2070340b9702701878529a Mon Sep 17 00:00:00 2001 From: Parham Negahdar Date: Fri, 31 Dec 2021 21:10:15 -0500 Subject: [PATCH] Add changes necessary to delegate popup/icons handling --- .../electron-chrome-extensions/package.json | 3 +- .../src/browser/api/browser-action.ts | 121 +++++++++++------- .../src/browser/impl.ts | 7 + .../src/browser/index.ts | 6 +- .../src/browser/popup.ts | 3 +- .../src/browser/store.ts | 19 +++ yarn.lock | 2 +- 7 files changed, 111 insertions(+), 50 deletions(-) diff --git a/packages/electron-chrome-extensions/package.json b/packages/electron-chrome-extensions/package.json index d811952..87f126d 100644 --- a/packages/electron-chrome-extensions/package.json +++ b/packages/electron-chrome-extensions/package.json @@ -41,10 +41,9 @@ "typescript": "^4.0.2", "uuid": "^8.3.1", "walkdir": "^0.4.1", - "webpack": "^4.44.1", + "webpack": "^4.46.0", "webpack-cli": "^3.3.12" }, - "peerDependencies": {}, "babel": { "presets": [ [ diff --git a/packages/electron-chrome-extensions/src/browser/api/browser-action.ts b/packages/electron-chrome-extensions/src/browser/api/browser-action.ts index d84a096..eab809a 100644 --- a/packages/electron-chrome-extensions/src/browser/api/browser-action.ts +++ b/packages/electron-chrome-extensions/src/browser/api/browser-action.ts @@ -1,14 +1,14 @@ -import { Menu, MenuItem, protocol, nativeImage, app } from 'electron' -import { ExtensionContext } from '../context' -import { PopupView } from '../popup' -import { ExtensionEvent } from '../router' +import {app, Menu, MenuItem, nativeImage, NativeImage, protocol, WebContents} from 'electron' +import {ExtensionContext} from '../context' +import {PopupView} from '../popup' +import {ExtensionEvent} from '../router' import { - getExtensionUrl, getExtensionManifest, + getExtensionUrl, getIconPath, - resolveExtensionPath, matchSize, ResizeType, + resolveExtensionPath, } from './common' const debug = require('debug')('electron-chrome-extensions:browserAction') @@ -29,13 +29,27 @@ interface ExtensionAction { type ExtensionActionKey = keyof ExtensionAction -interface ActivateDetails { - eventType: string +export interface ActivateDetails { + eventType: 'click' | 'contextmenu' extensionId: string tabId: number anchorRect: { x: number; y: number; width: number; height: number } } +interface BrowserActionItem { + id: string, + tabs?: { [key: string]: any }, + title?: string, + iconModified?: number, + popup?: string + iconDataURI?: string +} + +export interface BrowserActionState { + activeTabId?: number + actions: BrowserActionItem[] +} + const getBrowserActionDefaults = (extension: Electron.Extension): ExtensionAction | undefined => { const manifest = getExtensionManifest(extension) const { browser_action } = manifest @@ -194,8 +208,39 @@ export class BrowserActionAPI { session.protocol.registerBufferProtocol('crx', this.handleCrxRequest) } + private makeIconImage = (extensionId: string, tabId?:string | null, imageSize: number = 32, resizeType: ResizeType = ResizeType.Up) => { + const extension = this.ctx.session.getExtension(extensionId) + if(!extension){ + return null + } + let iconDetails: chrome.browserAction.TabIconDetails | undefined + const action = this.actionMap.get(extensionId) + if (action) { + iconDetails = (tabId && action.tabs[tabId]?.icon) || action.icon + } + if(!iconDetails){ + return null + } + + let iconImage: NativeImage | null = null + if (typeof iconDetails.path === 'string') { + const iconAbsPath = resolveExtensionPath(extension, iconDetails.path) + if (iconAbsPath) iconImage = nativeImage.createFromPath(iconAbsPath) + } else if (typeof iconDetails.path === 'object') { + const imagePath = matchSize(iconDetails.path, imageSize, resizeType) + const iconAbsPath = imagePath && resolveExtensionPath(extension, imagePath) + if (iconAbsPath) iconImage = nativeImage.createFromPath(iconAbsPath) + } else if (typeof iconDetails.imageData === 'string') { + iconImage = nativeImage.createFromDataURL(iconDetails.imageData) + } else if (typeof iconDetails.imageData === 'object') { + const imageData = matchSize(iconDetails.imageData as any, imageSize, resizeType) + iconImage = imageData ? nativeImage.createFromDataURL(imageData) : null + } + return iconImage + } + private handleCrxRequest = ( - request: Electron.ProtocolRequest, + request: {url: string } | Electron.ProtocolRequest, callback: (response: Electron.ProtocolResponse) => void ) => { debug('%s', request.url) @@ -215,33 +260,7 @@ export class BrowserActionAPI { const imageSize = parseInt(fragments[2], 10) const resizeType = parseInt(fragments[3], 10) || ResizeType.Up - const extension = this.ctx.session.getExtension(extensionId) - - let iconDetails: chrome.browserAction.TabIconDetails | undefined - - const action = this.actionMap.get(extensionId) - if (action) { - iconDetails = (tabId && action.tabs[tabId]?.icon) || action.icon - } - - let iconImage - - if (extension && iconDetails) { - if (typeof iconDetails.path === 'string') { - const iconAbsPath = resolveExtensionPath(extension, iconDetails.path) - if (iconAbsPath) iconImage = nativeImage.createFromPath(iconAbsPath) - } else if (typeof iconDetails.path === 'object') { - const imagePath = matchSize(iconDetails.path, imageSize, resizeType) - const iconAbsPath = imagePath && resolveExtensionPath(extension, imagePath) - if (iconAbsPath) iconImage = nativeImage.createFromPath(iconAbsPath) - } else if (typeof iconDetails.imageData === 'string') { - iconImage = nativeImage.createFromDataURL(iconDetails.imageData) - } else if (typeof iconDetails.imageData === 'object') { - const imageData = matchSize(iconDetails.imageData as any, imageSize, resizeType) - iconImage = imageData ? nativeImage.createFromDataURL(imageData) : undefined - } - } - + const iconImage = this.makeIconImage(extensionId, tabId, imageSize, resizeType) if (iconImage) { response = { statusCode: 200, @@ -304,7 +323,7 @@ export class BrowserActionAPI { } } - private getState() { + private getState(): BrowserActionState { // Get state without icon data. const actions = Array.from(this.actionMap.entries()).map(([id, details]) => { const { icon, tabs, ...rest } = details @@ -327,7 +346,16 @@ export class BrowserActionAPI { return { activeTabId: activeTab?.id, actions } } - private activate({ sender }: ExtensionEvent, details: ActivateDetails) { + private getStateWithIcons(): BrowserActionState { + const state = this.getState() + state.actions = state.actions.map(s => ({ + ...s, + iconDataURI: this.makeIconImage(s.id)?.toDataURL() || "", + })) + return state + } + + public activate({ sender }: {sender: WebContents}, details: ActivateDetails) { const { eventType, extensionId, tabId } = details debug( @@ -373,17 +401,19 @@ export class BrowserActionAPI { throw new Error('Unable to get BrowserWindow from active tab') } - this.popup = new PopupView({ + this.ctx.store.createPopup({ extensionId, - session: this.ctx.session, - parent: win, - url: popupUrl, - anchorRect, - }) + session: this.ctx.session, + parent: win, + url: popupUrl, + anchorRect, + tabId: tab.id, + }) debug(`opened popup: ${popupUrl}`) - this.ctx.emit('browser-action-popup-created', this.popup) + // TODO: what are the key parts of this message? Seems to only be used in tests. + // this.ctx.emit('browser-action-popup-created', this.popup) } else { debug(`dispatching onClicked for ${extensionId}`) @@ -448,6 +478,7 @@ export class BrowserActionAPI { queueMicrotask(() => { this.queuedUpdate = false debug(`dispatching update to ${this.observers.size} observer(s)`) + this.ctx.store.onBrowserActionUpdate(this.getStateWithIcons()) Array.from(this.observers).forEach((observer) => { if (!observer.isDestroyed()) { observer.send('browserAction.update') diff --git a/packages/electron-chrome-extensions/src/browser/impl.ts b/packages/electron-chrome-extensions/src/browser/impl.ts index b87ef9b..de8c7ec 100644 --- a/packages/electron-chrome-extensions/src/browser/impl.ts +++ b/packages/electron-chrome-extensions/src/browser/impl.ts @@ -1,3 +1,6 @@ +import {BrowserActionState} from "./api/browser-action" +import {PopupViewOptions} from "./popup" + /** App-specific implementation details for extensions. */ export interface ChromeExtensionImpl { createTab?( @@ -14,4 +17,8 @@ export interface ChromeExtensionImpl { createWindow?(details: chrome.windows.CreateData): Promise removeWindow?(window: Electron.BrowserWindow): void + + createPopup?(details: PopupViewOptions): void + + onBrowserActionUpdate?(details: BrowserActionState): void } diff --git a/packages/electron-chrome-extensions/src/browser/index.ts b/packages/electron-chrome-extensions/src/browser/index.ts index 2cf13d9..aec5c79 100644 --- a/packages/electron-chrome-extensions/src/browser/index.ts +++ b/packages/electron-chrome-extensions/src/browser/index.ts @@ -3,7 +3,7 @@ import { EventEmitter } from 'events' import path from 'path' import { promises as fs } from 'fs' -import { BrowserActionAPI } from './api/browser-action' +import {ActivateDetails, BrowserActionAPI} from './api/browser-action' import { TabsAPI } from './api/tabs' import { WindowsAPI } from './api/windows' import { WebNavigationAPI } from './api/web-navigation' @@ -130,6 +130,10 @@ export class ElectronChromeExtensions extends EventEmitter { } } + activateExtension(tab: Electron.WebContents, details: ActivateDetails) { + this.api.browserAction.activate({sender: tab}, details) + } + /** * Add webContents to be tracked as an extension host which will receive * extension events when a chrome-extension:// resource is loaded. diff --git a/packages/electron-chrome-extensions/src/browser/popup.ts b/packages/electron-chrome-extensions/src/browser/popup.ts index 9a25ee9..85b04c6 100644 --- a/packages/electron-chrome-extensions/src/browser/popup.ts +++ b/packages/electron-chrome-extensions/src/browser/popup.ts @@ -9,12 +9,13 @@ export interface PopupAnchorRect { height: number } -interface PopupViewOptions { +export interface PopupViewOptions { extensionId: string session: Session parent: BrowserWindow url: string anchorRect: PopupAnchorRect + tabId: number } export class PopupView { diff --git a/packages/electron-chrome-extensions/src/browser/store.ts b/packages/electron-chrome-extensions/src/browser/store.ts index 5b4a14c..111bd43 100644 --- a/packages/electron-chrome-extensions/src/browser/store.ts +++ b/packages/electron-chrome-extensions/src/browser/store.ts @@ -1,8 +1,10 @@ import { BrowserWindow, webContents } from 'electron' import { EventEmitter } from 'events' +import {BrowserActionState} from "./api/browser-action" import { ContextMenuType } from './api/common' import { ChromeExtensionImpl } from './impl' import { ExtensionEvent } from './router' +import {PopupView, PopupViewOptions} from "./popup" const debug = require('debug')('electron-chrome-extensions:store') @@ -189,6 +191,23 @@ export class ExtensionStore extends EventEmitter { } } + // TODO: this probably doesn't belong in store.ts + createPopup(opts: PopupViewOptions){ + if(this.impl.createPopup){ + return this.impl.createPopup(opts) + } else { + return new PopupView(opts) + } + + } + + // TODO: this probably doesn't belong in store.ts + onBrowserActionUpdate(opts: BrowserActionState){ + if(this.impl.onBrowserActionUpdate){ + this.impl.onBrowserActionUpdate(opts) + } + } + buildMenuItems(extensionId: string, menuType: ContextMenuType): Electron.MenuItem[] { // This function is overwritten by ContextMenusAPI return [] diff --git a/yarn.lock b/yarn.lock index 22f242c..3b97674 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4658,7 +4658,7 @@ webpack-sources@^1.4.0, webpack-sources@^1.4.1: source-list-map "^2.0.0" source-map "~0.6.1" -webpack@^4.44.1: +webpack@^4.46.0: version "4.46.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.46.0.tgz#bf9b4404ea20a073605e0a011d188d77cb6ad542" integrity sha512-6jJuJjg8znb/xRItk7bkT0+Q7AHCYjjFnvKIWQPkNIOyRqoCGvkOs0ipeQzrqz4l5FtN5ZI/ukEHroeX/o1/5Q==