Skip to content

Commit

Permalink
feat: manifest v3
Browse files Browse the repository at this point in the history
fix: preload script error in mv3 sw

update @types/chrome

update ext browser APIs for mv3

handle workers in ext router

add initial mv3 changes for renderer preload

fix webcontentsview padding

load preload for mv2 and mv3

feat: mv3 action button support

fix: only show tab icon when loaded

start service worker when dispatching event

add i18n.getAcceptLanguages stub

use new process type

add dmg maker

allow using local electron build

feat: chrome.downloads stub

fix: missing props in webNavigation.onCommitted

refactor: use session.registerPreloadScript

LOCAL_ELECTRON

find packaged extensions

don't copy local extensions

add yarn start:electron-dev command

use running-status-changed

fix: error in offscreen documents

fix: error when sw host destroyed

fix: action.setPopup('') falling back to undefined

fix: 1password action button click

fix: extension.isAllowed*Access not invoking callback

remove rounded corners from popup window

add permission.site
  • Loading branch information
samuelmaddock committed Dec 4, 2024
1 parent 808b120 commit 7d3a1a2
Show file tree
Hide file tree
Showing 21 changed files with 887 additions and 542 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
"build:shell": "yarn --cwd ./packages/shell build",
"start": "yarn build:context-menu && yarn build:extensions && yarn --cwd ./packages/shell start",
"start:debug": "cross-env SHELL_DEBUG=true DEBUG='electron-chrome-extensions*' yarn start",
"start:electron-dev": "cross-env ELECTRON_OVERRIDE_DIST_PATH=$(e show out --path) ELECTRON_ENABLE_LOGGING=1 DEBUG='electron-chrome-extensions*' yarn start",
"start:skip-build": "cross-env SHELL_DEBUG=true DEBUG='electron-chrome-extensions*' yarn --cwd ./packages/shell start",
"test": "yarn test:extensions",
"test:extensions": "yarn --cwd ./packages/electron-chrome-extensions test",
Expand Down
2 changes: 1 addition & 1 deletion packages/electron-chrome-extensions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"@babel/preset-typescript": "^7.10.4",
"@types/chai": "^4.2.14",
"@types/chai-as-promised": "^7.1.3",
"@types/chrome": "^0.0.122",
"@types/chrome": "^0.0.272",
"@types/mocha": "^8.0.4",
"babel-loader": "^8.2.5",
"chai": "^4.2.0",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,11 @@ describe('chrome.browserAction', () => {

it('supports cross-session communication', async () => {
const otherSession = session.fromPartition(`persist:crx-${uuid()}`)
otherSession.setPreloads(browser.session.getPreloads())

// TODO(mv3): remove any
;(browser.session as any).getPreloadScripts().forEach((script) => {
;(otherSession as any).registerPreloadScript(script)
})

const view = new BrowserView({
webPreferences: { session: otherSession, nodeIntegration: false, contextIsolation: true },
Expand Down
8 changes: 6 additions & 2 deletions packages/electron-chrome-extensions/spec/crx-helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ export const createCrxSession = () => {
}

export const addCrxPreload = (session: Electron.Session) => {
const preload = path.join(__dirname, 'fixtures', 'crx-test-preload.js')
session.setPreloads([...session.getPreloads(), preload])
// TODO(mv3): remove any
(session as any).registerPreloadScript({
id: 'crx-test-preload',
type: 'frame',
filePath: path.join(__dirname, 'fixtures', 'crx-test-preload.js')
})
}

export const createCrxRemoteWindow = () => {
Expand Down
8 changes: 5 additions & 3 deletions packages/electron-chrome-extensions/src/browser-action.ts
Original file line number Diff line number Diff line change
Expand Up @@ -401,12 +401,14 @@ export const injectBrowserAction = () => {
customElements.define('browser-action-list', BrowserActionListElement)
}

try {
if (process.contextIsolated) {
contextBridge.exposeInMainWorld('browserAction', __browserAction__)

// Must execute script in main world to modify custom component registry.
webFrame.executeJavaScript(`(${mainWorldScript}());`)
} catch {
;(contextBridge as any).evaluateInMainWorld({
func: mainWorldScript
})
} else {
// When contextIsolation is disabled, contextBridge will throw an error.
// If that's the case, we're in the main world so we can just execute our
// function.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,17 +38,23 @@ interface ActivateDetails {

const getBrowserActionDefaults = (extension: Electron.Extension): ExtensionAction | undefined => {
const manifest = getExtensionManifest(extension)
const { browser_action } = manifest
if (typeof browser_action === 'object') {
const browserAction =
manifest.manifest_version === 3
? manifest.action
: manifest.manifest_version === 2
? manifest.browser_action
: undefined
if (typeof browserAction === 'object') {
const manifestAction: chrome.runtime.ManifestAction = browserAction
const action: ExtensionAction = {}

action.title = browser_action.default_title || manifest.name
action.title = manifestAction.default_title || manifest.name

const iconPath = getIconPath(extension)
if (iconPath) action.icon = { path: iconPath }

if (browser_action.default_popup) {
action.popup = browser_action.default_popup
if (manifestAction.default_popup) {
action.popup = manifestAction.default_popup
}

return action
Expand All @@ -63,7 +69,9 @@ export class BrowserActionAPI {
private actionMap = new Map</* extensionId */ string, ExtensionActionStore>()
private popup?: PopupView

private observers: Set<Electron.WebContents> = new Set()
// TODO(mv3): support SWs
// private observers: Set<Electron.WebContents | Electron.ServiceWorkerMain> = new Set()
private observers: Set<any> = new Set()
private queuedUpdate: boolean = false

constructor(private ctx: ExtensionContext) {
Expand Down Expand Up @@ -96,7 +104,7 @@ export class BrowserActionAPI {
propName: ExtensionActionKey,
) => {
const { tabId } = details
let value = (details as any)[propName] || undefined
let value = details[propName]

if (typeof value === 'undefined') {
const defaults = getBrowserActionDefaults(extension)
Expand Down Expand Up @@ -131,6 +139,11 @@ export class BrowserActionAPI {
handleProp('Title', 'title')
handleProp('Popup', 'popup')

handle('browserAction.getUserSettings', (): chrome.action.UserSettings => {
// TODO: allow extension pinning
return { isOnToolbar: true }
})

// setIcon is unique in that it can pass in a variety of properties. Here we normalize them
// to use 'icon'.
handle(
Expand All @@ -148,19 +161,20 @@ export class BrowserActionAPI {
handle(
'browserAction.addObserver',
(event) => {
const { sender: webContents } = event
this.observers.add(webContents)
webContents.once('destroyed', () => {
this.observers.delete(webContents)
const { sender: observer } = event
this.observers.add(observer)
// TODO(mv3): need a destroyed event on workers
observer.once?.('destroyed', () => {
this.observers.delete(observer)
})
},
preloadOpts,
)
handle(
'browserAction.removeObserver',
(event) => {
const { sender: webContents } = event
this.observers.delete(webContents)
const { sender: observer } = event
this.observers.delete(observer)
},
preloadOpts,
)
Expand Down Expand Up @@ -291,7 +305,16 @@ export class BrowserActionAPI {

private getPopupUrl(extensionId: string, tabId: number) {
const action = this.getAction(extensionId)
const popupPath = action.tabs[tabId]?.popup || action.popup || undefined
const tabPopupValue = action.tabs[tabId]?.popup
const actionPopupValue = action.popup

let popupPath: string | undefined

if (typeof tabPopupValue !== 'undefined') {
popupPath = tabPopupValue
} else if (typeof actionPopupValue !== 'undefined') {
popupPath = actionPopupValue
}

let url: string | undefined

Expand Down Expand Up @@ -431,6 +454,7 @@ export class BrowserActionAPI {

appendSeparator()

// TODO(mv3): need to build 'action' menu items?
const contextMenuItems: MenuItem[] = this.ctx.store.buildMenuItems(
extensionId,
'browser_action',
Expand Down Expand Up @@ -475,7 +499,8 @@ export class BrowserActionAPI {
debug(`dispatching update to ${this.observers.size} observer(s)`)
Array.from(this.observers).forEach((observer) => {
if (!observer.isDestroyed()) {
observer.send('browserAction.update')
// TODO(mv3): support sending to SWs
observer.send?.('browserAction.update')
}
})
})
Expand Down
8 changes: 6 additions & 2 deletions packages/electron-chrome-extensions/src/browser/api/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,12 @@ export const getIconPath = (
iconSize: number = 32,
resizeType = ResizeType.Up,
) => {
const { browser_action, icons } = getExtensionManifest(extension)
const { default_icon } = browser_action || {}
const manifest = getExtensionManifest(extension)
const { icons } = manifest

const default_icon: chrome.runtime.ManifestIcons | undefined = (
manifest.manifest_version === 3 ? manifest.action : manifest.browser_action
)?.default_icon

if (typeof default_icon === 'string') {
const iconPath = default_icon
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const matchesConditions = (

const { contextTypes, targetUrl, documentUrl } = conditions

const contexts = props.contexts || DEFAULT_CONTEXTS
const contexts = (Array.isArray(props.contexts) ? props.contexts : [props.contexts]) || DEFAULT_CONTEXTS
const inContext = contexts.some((context) => contextTypes.has(context as ContextMenuType))
if (!inContext) return false

Expand Down Expand Up @@ -139,7 +139,7 @@ export class ContextMenusAPI {
for (const item of menuItemTemplates) {
const menuItem = itemMap.get(item.props.id)
if (item.props.parentId) {
const parentMenuItem = itemMap.get(item.props.parentId)
const parentMenuItem = itemMap.get(`${item.props.parentId}`)
if (parentMenuItem) {
const submenu = (parentMenuItem.submenu || []) as Electron.MenuItemConstructorOptions[]
submenu.push(menuItem!)
Expand Down Expand Up @@ -320,7 +320,8 @@ export class ContextMenusAPI {
frameId: -1, // TODO: match frameURL with webFrameMain in Electron 12
frameUrl: params?.frameURL,
editable: params?.isEditable || false,
mediaType: params?.mediaType,
// TODO(mv3): limit possible string enums
mediaType: params?.mediaType as any,
wasChecked: false, // TODO
pageUrl: params?.pageURL as any, // types are inaccurate
linkUrl: params?.linkURL,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ export class TabsAPI {
id: tabId,
incognito: false,
index: -1, // TODO
groupId: -1, // TODO(mv3): implement?
mutedInfo: { muted: tab.audioMuted },
pinned: false,
selected: true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,34 @@ import { ExtensionEvent } from '../router'

const debug = require('debug')('electron-chrome-extensions:webNavigation')

// https://github.com/electron/electron/pull/25464
const getFrame = (frameProcessId: number, frameRoutingId: number) => {
return (
('webFrameMain' in electron &&
(electron as any).webFrameMain.fromId(frameProcessId, frameRoutingId)) ||
null
)
}
const getFrame = (frameProcessId: number, frameRoutingId: number) =>
electron.webFrameMain.fromId(frameProcessId, frameRoutingId);

const getFrameId = (frame: any) =>
'webFrameMain' in electron ? (frame === frame.top ? 0 : frame.frameTreeNodeId) : -1
const getFrameId = (frame: Electron.WebFrameMain) =>
frame === frame.top ? 0 : frame.frameTreeNodeId

const getParentFrameId = (frame: any) => {
const getParentFrameId = (frame: Electron.WebFrameMain) => {
const parentFrame = frame?.parent
return parentFrame ? getFrameId(parentFrame) : -1
}

const getFrameDetails = (frame: any) => ({
errorOccurred: false, // TODO
processId: frame.processId,
frameId: getFrameId(frame),
parentFrameId: getParentFrameId(frame),
// TODO(mv3): fenced_frame getter API needed
const getFrameType = (frame: Electron.WebFrameMain) =>
!frame.parent ? 'outermost_frame' : 'sub_frame';

// TODO(mv3): add WebFrameMain API to retrieve this
const getDocumentLifecycle = (frame: Electron.WebFrameMain): DocumentLifecycle =>
'active' as const;

const getFrameDetails = (frame: Electron.WebFrameMain): chrome.webNavigation.GetFrameResultDetails => ({
// TODO(mv3): implement new properties
url: frame.url,
documentId: 'not-implemented',
documentLifecycle: getDocumentLifecycle(frame),
errorOccurred: false,
frameType: getFrameType(frame),
parentDocumentId: undefined,
parentFrameId: getParentFrameId(frame),
})

export class WebNavigationAPI {
Expand Down Expand Up @@ -68,17 +73,14 @@ export class WebNavigationAPI {
const tab = this.ctx.store.getTabById(details.tabId)
if (!tab) return null

let targetFrame: any
let targetFrame: Electron.WebFrameMain | undefined

if (typeof details.frameId === 'number') {
// https://github.com/electron/electron/pull/25464
if ('mainFrame' in tab) {
const mainFrame = (tab as any).mainFrame
targetFrame = mainFrame.framesInSubtree.find((frame: any) => {
const isMainFrame = frame === frame.top
return isMainFrame ? details.frameId === 0 : details.frameId === frame.frameTreeNodeId
})
}
const mainFrame = tab.mainFrame
targetFrame = mainFrame.framesInSubtree.find((frame: any) => {
const isMainFrame = frame === frame.top
return isMainFrame ? details.frameId === 0 : details.frameId === frame.frameTreeNodeId
})
}

return targetFrame ? getFrameDetails(targetFrame) : null
Expand Down Expand Up @@ -134,6 +136,8 @@ export class WebNavigationAPI {

const details: chrome.webNavigation.WebNavigationParentedCallbackDetails = {
frameId: getFrameId(frame),
frameType: getFrameType(frame),
documentLifecycle: getDocumentLifecycle(frame),
parentFrameId: getParentFrameId(frame),
processId: frame ? frame.processId : -1,
tabId: tab.id,
Expand All @@ -155,9 +159,21 @@ export class WebNavigationAPI {
frameRoutingId: number,
) => {
const frame = getFrame(frameProcessId, frameRoutingId)
const details: chrome.webNavigation.WebNavigationParentedCallbackDetails = {
if (!frame) {
// TODO(mv3): handle null return
return;
}

const details: chrome.webNavigation.WebNavigationTransitionCallbackDetails = {
frameId: getFrameId(frame),
parentFrameId: getParentFrameId(frame),
// NOTE: workaround for property missing in type
...({
parentFrameId: getParentFrameId(frame),
}),
frameType: getFrameType(frame),
transitionType: '', // TODO(mv3)
transitionQualifiers: [], // TODO(mv3)
documentLifecycle: getDocumentLifecycle(frame),
processId: frameProcessId,
tabId: tab.id,
timeStamp: Date.now(),
Expand All @@ -175,13 +191,20 @@ export class WebNavigationAPI {
frameRoutingId: number,
) => {
const frame = getFrame(frameProcessId, frameRoutingId)
if (!frame) {
// TODO(mv3): handle null return
return;
}

const details: chrome.webNavigation.WebNavigationTransitionCallbackDetails & {
parentFrameId: number
} = {
transitionType: '', // TODO
transitionQualifiers: [], // TODO
frameId: getFrameId(frame),
parentFrameId: getParentFrameId(frame),
frameType: getFrameType(frame),
documentLifecycle: getDocumentLifecycle(frame),
processId: frameProcessId,
tabId: tab.id,
timeStamp: Date.now(),
Expand All @@ -194,6 +217,8 @@ export class WebNavigationAPI {
const details: chrome.webNavigation.WebNavigationParentedCallbackDetails = {
frameId: getFrameId(frame),
parentFrameId: getParentFrameId(frame),
frameType: getFrameType(frame),
documentLifecycle: getDocumentLifecycle(frame),
processId: frame.processId,
tabId: tab.id,
timeStamp: Date.now(),
Expand All @@ -214,10 +239,17 @@ export class WebNavigationAPI {
frameRoutingId: number,
) => {
const frame = getFrame(frameProcessId, frameRoutingId)
if (!frame) {
// TODO(mv3): handle null return
return;
}

const url = tab.getURL()
const details: chrome.webNavigation.WebNavigationParentedCallbackDetails = {
frameId: getFrameId(frame),
parentFrameId: getParentFrameId(frame),
frameType: getFrameType(frame),
documentLifecycle: getDocumentLifecycle(frame),
processId: frameProcessId,
tabId: tab.id,
timeStamp: Date.now(),
Expand Down
Loading

0 comments on commit 7d3a1a2

Please sign in to comment.