From 30c337afcc097c4df13fd9d0984638473eb35ca0 Mon Sep 17 00:00:00 2001 From: samuelmaddock Date: Fri, 9 Oct 2020 23:15:15 -0400 Subject: [PATCH] refactor: create PopupView class Depends on changes in electron/electron#25873 and electron/electron#25874 --- .../src/browser/api/browser-action.ts | 32 +++++++-- .../src/browser/index.ts | 15 ----- .../src/browser/popup.ts | 65 +++++++++++++++++++ .../src/browser/store.ts | 3 +- packages/shell/browser/main.js | 16 ----- 5 files changed, 94 insertions(+), 37 deletions(-) create mode 100644 packages/electron-chrome-extensions/src/browser/popup.ts 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 1adcf2d..ce75206 100644 --- a/packages/electron-chrome-extensions/src/browser/api/browser-action.ts +++ b/packages/electron-chrome-extensions/src/browser/api/browser-action.ts @@ -1,5 +1,6 @@ -import { session, ipcMain } from 'electron' +import { session, ipcMain, BrowserWindow } from 'electron' import { EventEmitter } from 'events' +import { PopupView } from '../popup' import { ExtensionStore } from '../store' import { getIconImage } from './common' @@ -22,7 +23,8 @@ interface ExtensionActionStore extends Partial { } export class BrowserActionAPI { - sessionActionMap = new Map>() + private sessionActionMap = new Map>() + private popup?: PopupView constructor(private store: ExtensionStore) { const setter = (propName: string) => ( @@ -70,9 +72,11 @@ export class BrowserActionAPI { return action } - getPopupPath(session: Electron.Session, extensionId: string, tabId: string) { + private getPopupUrl(session: Electron.Session, extensionId: string, tabId: number) { const action = this.getAction(session, extensionId) - return action.tabs[tabId] ? action.tabs[tabId].popup?.path : action.popup?.path + const popupPath = + (action.tabs[tabId] && action.tabs[tabId].popup?.path) || action.popup?.path || undefined + return popupPath && `chrome-extension://${extensionId}/${popupPath}` } processExtensions(session: Electron.Session, extensions: Electron.Extension[]) { @@ -105,6 +109,24 @@ export class BrowserActionAPI { } private onClicked(event: Electron.IpcMainInvokeEvent, extensionId: string) { - this.store.emit('action-clicked', event, extensionId) + if (this.popup) { + const toggleExtension = !this.popup.isDestroyed() && this.popup.extensionId === extensionId + this.popup.destroy() + this.popup = undefined + if (toggleExtension) return + } + + // TODO: activeTab needs to be refactored to support one active tab per window + const { activeTab } = this.store + if (!activeTab) return + + const popupUrl = this.getPopupUrl(activeTab.session, extensionId, activeTab.id) + + if (popupUrl) { + const win = BrowserWindow.fromWebContents(activeTab) + if (win) this.popup = new PopupView(extensionId, win, popupUrl) + } else { + // TODO: dispatch click action + } } } diff --git a/packages/electron-chrome-extensions/src/browser/index.ts b/packages/electron-chrome-extensions/src/browser/index.ts index 8647c72..7f1297b 100644 --- a/packages/electron-chrome-extensions/src/browser/index.ts +++ b/packages/electron-chrome-extensions/src/browser/index.ts @@ -120,19 +120,4 @@ export class Extensions extends EventEmitter { this.tabs.onActivated(tab.id) } } - - createPopup(win: Electron.BrowserWindow, tabId: string, extensionId: string) { - const popupPath = - this.browserAction.getPopupPath(win.webContents.session, extensionId, tabId) || `popup.html` - const popupUrl = `chrome-extension://${extensionId}/${popupPath}` - const popup = new BrowserView() - popup.setBounds({ x: win.getSize()[0] - 256, y: 62, width: 256, height: 400 }) - // popup.webContents.loadURL(`chrome-extension://${extension.id}/popup.html?tabId=${win.webContents.id}`) - console.log(`POPUP URL: ${popupUrl}`) - popup.webContents.loadURL(popupUrl) - popup.webContents.openDevTools({ mode: 'detach', activate: true }) - popup.setBackgroundColor('#ff0000') - win.addBrowserView(popup) - return popup - } } diff --git a/packages/electron-chrome-extensions/src/browser/popup.ts b/packages/electron-chrome-extensions/src/browser/popup.ts new file mode 100644 index 0000000..c3779bd --- /dev/null +++ b/packages/electron-chrome-extensions/src/browser/popup.ts @@ -0,0 +1,65 @@ +import { BrowserView, BrowserWindow } from 'electron' + +enum PopupGeometry { + // TODO: dynamically set offset based on action button position + OffsetY = 62, + MinWidth = 25, + MinHeight = 25, + MaxWidth = 800, + MaxHeight = 600, +} + +export class PopupView { + private view?: BrowserView + + constructor(public extensionId: string, private window: BrowserWindow, url: string) { + this.view = new BrowserView({ + webPreferences: { + contextIsolation: true, + preferredSizeMode: true, + } as any, + }) + + // Set default size where preferredSizeMode isn't supported + this.view.setBounds({ x: this.window.getSize()[0] - 256, y: 62, width: 256, height: 400 }) + + const untypedWebContents = this.view.webContents as any + untypedWebContents.on('preferred-size-changed', this.updatePreferredSize) + + this.view.setBackgroundColor('#ff0000') + this.view.webContents.loadURL(url) + + // this.view.webContents.openDevTools({ mode: 'detach', activate: true }) + + this.window.addBrowserView(this.view) + this.view.webContents.focus() + + // TODO: + this.view.webContents.once('blur' as any, this.destroy) + } + + destroy = () => { + if (!this.view) return + + this.window.removeBrowserView(this.view) + if (this.view.webContents.isDevToolsOpened()) { + this.view.webContents.closeDevTools() + } + + this.view = undefined + } + + isDestroyed() { + return !this.view + } + + private updatePreferredSize = (event: Electron.Event, size: Electron.Size) => { + const windowWidth = this.window.getSize()[0] + this.view?.setBounds({ + x: windowWidth - size.width, + y: PopupGeometry.OffsetY, + width: Math.min(PopupGeometry.MaxWidth, Math.max(size.width, PopupGeometry.MinWidth)), + height: Math.min(PopupGeometry.MaxHeight, Math.max(size.height, PopupGeometry.MinHeight)), + }) + } +} diff --git a/packages/electron-chrome-extensions/src/browser/store.ts b/packages/electron-chrome-extensions/src/browser/store.ts index 954ed7b..4c38841 100644 --- a/packages/electron-chrome-extensions/src/browser/store.ts +++ b/packages/electron-chrome-extensions/src/browser/store.ts @@ -15,7 +15,8 @@ export class ExtensionStore { } get activeTab(): Electron.WebContents | undefined { - return this.activeTabId ? this.getTabById(this.activeTabId) : undefined + const tab = this.activeTabId ? this.getTabById(this.activeTabId) : undefined + return tab && !tab.isDestroyed() ? tab : undefined } set activeTab(tab: Electron.WebContents | undefined) { const tabId = tab?.id diff --git a/packages/shell/browser/main.js b/packages/shell/browser/main.js index 73bc6a0..e807621 100644 --- a/packages/shell/browser/main.js +++ b/packages/shell/browser/main.js @@ -115,7 +115,6 @@ class TabbedBrowserWindow { class Browser { windows = [] - popupView = null constructor() { app.whenReady().then(this.init.bind(this)) @@ -204,21 +203,6 @@ class Browser { win.tabs.select(tab.id) }) - this.extensions.on('action-clicked', (event, extensionId) => { - const win = this.getWindowFromWebContents(event.sender) - const selectedId = win.tabs.selected ? win.tabs.selected.id : -1 - - if (this.popupView) { - win.window.removeBrowserView(this.popupView) - if (this.popupView.webContents.isDevToolsOpened()) { - this.popupView.webContents.closeDevTools() - } - this.popupView = undefined - } else { - this.popupView = this.extensions.createPopup(win.window, selectedId, extensionId) - } - }) - this.createWindow({ initialUrl: newTabUrl }) }