Skip to content

Commit 7d3a1a2

Browse files
committed
feat: manifest v3
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
1 parent 808b120 commit 7d3a1a2

File tree

21 files changed

+887
-542
lines changed

21 files changed

+887
-542
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"build:shell": "yarn --cwd ./packages/shell build",
1818
"start": "yarn build:context-menu && yarn build:extensions && yarn --cwd ./packages/shell start",
1919
"start:debug": "cross-env SHELL_DEBUG=true DEBUG='electron-chrome-extensions*' yarn start",
20+
"start:electron-dev": "cross-env ELECTRON_OVERRIDE_DIST_PATH=$(e show out --path) ELECTRON_ENABLE_LOGGING=1 DEBUG='electron-chrome-extensions*' yarn start",
2021
"start:skip-build": "cross-env SHELL_DEBUG=true DEBUG='electron-chrome-extensions*' yarn --cwd ./packages/shell start",
2122
"test": "yarn test:extensions",
2223
"test:extensions": "yarn --cwd ./packages/electron-chrome-extensions test",

packages/electron-chrome-extensions/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
"@babel/preset-typescript": "^7.10.4",
2929
"@types/chai": "^4.2.14",
3030
"@types/chai-as-promised": "^7.1.3",
31-
"@types/chrome": "^0.0.122",
31+
"@types/chrome": "^0.0.272",
3232
"@types/mocha": "^8.0.4",
3333
"babel-loader": "^8.2.5",
3434
"chai": "^4.2.0",

packages/electron-chrome-extensions/spec/chrome-browserAction-spec.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,11 @@ describe('chrome.browserAction', () => {
4242

4343
it('supports cross-session communication', async () => {
4444
const otherSession = session.fromPartition(`persist:crx-${uuid()}`)
45-
otherSession.setPreloads(browser.session.getPreloads())
45+
46+
// TODO(mv3): remove any
47+
;(browser.session as any).getPreloadScripts().forEach((script) => {
48+
;(otherSession as any).registerPreloadScript(script)
49+
})
4650

4751
const view = new BrowserView({
4852
webPreferences: { session: otherSession, nodeIntegration: false, contextIsolation: true },

packages/electron-chrome-extensions/spec/crx-helpers.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,12 @@ export const createCrxSession = () => {
1414
}
1515

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

2125
export const createCrxRemoteWindow = () => {

packages/electron-chrome-extensions/src/browser-action.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -401,12 +401,14 @@ export const injectBrowserAction = () => {
401401
customElements.define('browser-action-list', BrowserActionListElement)
402402
}
403403

404-
try {
404+
if (process.contextIsolated) {
405405
contextBridge.exposeInMainWorld('browserAction', __browserAction__)
406406

407407
// Must execute script in main world to modify custom component registry.
408-
webFrame.executeJavaScript(`(${mainWorldScript}());`)
409-
} catch {
408+
;(contextBridge as any).evaluateInMainWorld({
409+
func: mainWorldScript
410+
})
411+
} else {
410412
// When contextIsolation is disabled, contextBridge will throw an error.
411413
// If that's the case, we're in the main world so we can just execute our
412414
// function.

packages/electron-chrome-extensions/src/browser/api/browser-action.ts

Lines changed: 40 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,23 @@ interface ActivateDetails {
3838

3939
const getBrowserActionDefaults = (extension: Electron.Extension): ExtensionAction | undefined => {
4040
const manifest = getExtensionManifest(extension)
41-
const { browser_action } = manifest
42-
if (typeof browser_action === 'object') {
41+
const browserAction =
42+
manifest.manifest_version === 3
43+
? manifest.action
44+
: manifest.manifest_version === 2
45+
? manifest.browser_action
46+
: undefined
47+
if (typeof browserAction === 'object') {
48+
const manifestAction: chrome.runtime.ManifestAction = browserAction
4349
const action: ExtensionAction = {}
4450

45-
action.title = browser_action.default_title || manifest.name
51+
action.title = manifestAction.default_title || manifest.name
4652

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

50-
if (browser_action.default_popup) {
51-
action.popup = browser_action.default_popup
56+
if (manifestAction.default_popup) {
57+
action.popup = manifestAction.default_popup
5258
}
5359

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

66-
private observers: Set<Electron.WebContents> = new Set()
72+
// TODO(mv3): support SWs
73+
// private observers: Set<Electron.WebContents | Electron.ServiceWorkerMain> = new Set()
74+
private observers: Set<any> = new Set()
6775
private queuedUpdate: boolean = false
6876

6977
constructor(private ctx: ExtensionContext) {
@@ -96,7 +104,7 @@ export class BrowserActionAPI {
96104
propName: ExtensionActionKey,
97105
) => {
98106
const { tabId } = details
99-
let value = (details as any)[propName] || undefined
107+
let value = details[propName]
100108

101109
if (typeof value === 'undefined') {
102110
const defaults = getBrowserActionDefaults(extension)
@@ -131,6 +139,11 @@ export class BrowserActionAPI {
131139
handleProp('Title', 'title')
132140
handleProp('Popup', 'popup')
133141

142+
handle('browserAction.getUserSettings', (): chrome.action.UserSettings => {
143+
// TODO: allow extension pinning
144+
return { isOnToolbar: true }
145+
})
146+
134147
// setIcon is unique in that it can pass in a variety of properties. Here we normalize them
135148
// to use 'icon'.
136149
handle(
@@ -148,19 +161,20 @@ export class BrowserActionAPI {
148161
handle(
149162
'browserAction.addObserver',
150163
(event) => {
151-
const { sender: webContents } = event
152-
this.observers.add(webContents)
153-
webContents.once('destroyed', () => {
154-
this.observers.delete(webContents)
164+
const { sender: observer } = event
165+
this.observers.add(observer)
166+
// TODO(mv3): need a destroyed event on workers
167+
observer.once?.('destroyed', () => {
168+
this.observers.delete(observer)
155169
})
156170
},
157171
preloadOpts,
158172
)
159173
handle(
160174
'browserAction.removeObserver',
161175
(event) => {
162-
const { sender: webContents } = event
163-
this.observers.delete(webContents)
176+
const { sender: observer } = event
177+
this.observers.delete(observer)
164178
},
165179
preloadOpts,
166180
)
@@ -291,7 +305,16 @@ export class BrowserActionAPI {
291305

292306
private getPopupUrl(extensionId: string, tabId: number) {
293307
const action = this.getAction(extensionId)
294-
const popupPath = action.tabs[tabId]?.popup || action.popup || undefined
308+
const tabPopupValue = action.tabs[tabId]?.popup
309+
const actionPopupValue = action.popup
310+
311+
let popupPath: string | undefined
312+
313+
if (typeof tabPopupValue !== 'undefined') {
314+
popupPath = tabPopupValue
315+
} else if (typeof actionPopupValue !== 'undefined') {
316+
popupPath = actionPopupValue
317+
}
295318

296319
let url: string | undefined
297320

@@ -431,6 +454,7 @@ export class BrowserActionAPI {
431454

432455
appendSeparator()
433456

457+
// TODO(mv3): need to build 'action' menu items?
434458
const contextMenuItems: MenuItem[] = this.ctx.store.buildMenuItems(
435459
extensionId,
436460
'browser_action',
@@ -475,7 +499,8 @@ export class BrowserActionAPI {
475499
debug(`dispatching update to ${this.observers.size} observer(s)`)
476500
Array.from(this.observers).forEach((observer) => {
477501
if (!observer.isDestroyed()) {
478-
observer.send('browserAction.update')
502+
// TODO(mv3): support sending to SWs
503+
observer.send?.('browserAction.update')
479504
}
480505
})
481506
})

packages/electron-chrome-extensions/src/browser/api/common.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,8 +80,12 @@ export const getIconPath = (
8080
iconSize: number = 32,
8181
resizeType = ResizeType.Up,
8282
) => {
83-
const { browser_action, icons } = getExtensionManifest(extension)
84-
const { default_icon } = browser_action || {}
83+
const manifest = getExtensionManifest(extension)
84+
const { icons } = manifest
85+
86+
const default_icon: chrome.runtime.ManifestIcons | undefined = (
87+
manifest.manifest_version === 3 ? manifest.action : manifest.browser_action
88+
)?.default_icon
8589

8690
if (typeof default_icon === 'string') {
8791
const iconPath = default_icon

packages/electron-chrome-extensions/src/browser/api/context-menus.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ const matchesConditions = (
5454

5555
const { contextTypes, targetUrl, documentUrl } = conditions
5656

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

@@ -139,7 +139,7 @@ export class ContextMenusAPI {
139139
for (const item of menuItemTemplates) {
140140
const menuItem = itemMap.get(item.props.id)
141141
if (item.props.parentId) {
142-
const parentMenuItem = itemMap.get(item.props.parentId)
142+
const parentMenuItem = itemMap.get(`${item.props.parentId}`)
143143
if (parentMenuItem) {
144144
const submenu = (parentMenuItem.submenu || []) as Electron.MenuItemConstructorOptions[]
145145
submenu.push(menuItem!)
@@ -320,7 +320,8 @@ export class ContextMenusAPI {
320320
frameId: -1, // TODO: match frameURL with webFrameMain in Electron 12
321321
frameUrl: params?.frameURL,
322322
editable: params?.isEditable || false,
323-
mediaType: params?.mediaType,
323+
// TODO(mv3): limit possible string enums
324+
mediaType: params?.mediaType as any,
324325
wasChecked: false, // TODO
325326
pageUrl: params?.pageURL as any, // types are inaccurate
326327
linkUrl: params?.linkURL,

packages/electron-chrome-extensions/src/browser/api/tabs.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ export class TabsAPI {
106106
id: tabId,
107107
incognito: false,
108108
index: -1, // TODO
109+
groupId: -1, // TODO(mv3): implement?
109110
mutedInfo: { muted: tab.audioMuted },
110111
pinned: false,
111112
selected: true,

packages/electron-chrome-extensions/src/browser/api/web-navigation.ts

Lines changed: 59 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -4,29 +4,34 @@ import { ExtensionEvent } from '../router'
44

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

7-
// https://github.com/electron/electron/pull/25464
8-
const getFrame = (frameProcessId: number, frameRoutingId: number) => {
9-
return (
10-
('webFrameMain' in electron &&
11-
(electron as any).webFrameMain.fromId(frameProcessId, frameRoutingId)) ||
12-
null
13-
)
14-
}
7+
const getFrame = (frameProcessId: number, frameRoutingId: number) =>
8+
electron.webFrameMain.fromId(frameProcessId, frameRoutingId);
159

16-
const getFrameId = (frame: any) =>
17-
'webFrameMain' in electron ? (frame === frame.top ? 0 : frame.frameTreeNodeId) : -1
10+
const getFrameId = (frame: Electron.WebFrameMain) =>
11+
frame === frame.top ? 0 : frame.frameTreeNodeId
1812

19-
const getParentFrameId = (frame: any) => {
13+
const getParentFrameId = (frame: Electron.WebFrameMain) => {
2014
const parentFrame = frame?.parent
2115
return parentFrame ? getFrameId(parentFrame) : -1
2216
}
2317

24-
const getFrameDetails = (frame: any) => ({
25-
errorOccurred: false, // TODO
26-
processId: frame.processId,
27-
frameId: getFrameId(frame),
28-
parentFrameId: getParentFrameId(frame),
18+
// TODO(mv3): fenced_frame getter API needed
19+
const getFrameType = (frame: Electron.WebFrameMain) =>
20+
!frame.parent ? 'outermost_frame' : 'sub_frame';
21+
22+
// TODO(mv3): add WebFrameMain API to retrieve this
23+
const getDocumentLifecycle = (frame: Electron.WebFrameMain): DocumentLifecycle =>
24+
'active' as const;
25+
26+
const getFrameDetails = (frame: Electron.WebFrameMain): chrome.webNavigation.GetFrameResultDetails => ({
27+
// TODO(mv3): implement new properties
2928
url: frame.url,
29+
documentId: 'not-implemented',
30+
documentLifecycle: getDocumentLifecycle(frame),
31+
errorOccurred: false,
32+
frameType: getFrameType(frame),
33+
parentDocumentId: undefined,
34+
parentFrameId: getParentFrameId(frame),
3035
})
3136

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

71-
let targetFrame: any
76+
let targetFrame: Electron.WebFrameMain | undefined
7277

7378
if (typeof details.frameId === 'number') {
74-
// https://github.com/electron/electron/pull/25464
75-
if ('mainFrame' in tab) {
76-
const mainFrame = (tab as any).mainFrame
77-
targetFrame = mainFrame.framesInSubtree.find((frame: any) => {
78-
const isMainFrame = frame === frame.top
79-
return isMainFrame ? details.frameId === 0 : details.frameId === frame.frameTreeNodeId
80-
})
81-
}
79+
const mainFrame = tab.mainFrame
80+
targetFrame = mainFrame.framesInSubtree.find((frame: any) => {
81+
const isMainFrame = frame === frame.top
82+
return isMainFrame ? details.frameId === 0 : details.frameId === frame.frameTreeNodeId
83+
})
8284
}
8385

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

135137
const details: chrome.webNavigation.WebNavigationParentedCallbackDetails = {
136138
frameId: getFrameId(frame),
139+
frameType: getFrameType(frame),
140+
documentLifecycle: getDocumentLifecycle(frame),
137141
parentFrameId: getParentFrameId(frame),
138142
processId: frame ? frame.processId : -1,
139143
tabId: tab.id,
@@ -155,9 +159,21 @@ export class WebNavigationAPI {
155159
frameRoutingId: number,
156160
) => {
157161
const frame = getFrame(frameProcessId, frameRoutingId)
158-
const details: chrome.webNavigation.WebNavigationParentedCallbackDetails = {
162+
if (!frame) {
163+
// TODO(mv3): handle null return
164+
return;
165+
}
166+
167+
const details: chrome.webNavigation.WebNavigationTransitionCallbackDetails = {
159168
frameId: getFrameId(frame),
160-
parentFrameId: getParentFrameId(frame),
169+
// NOTE: workaround for property missing in type
170+
...({
171+
parentFrameId: getParentFrameId(frame),
172+
}),
173+
frameType: getFrameType(frame),
174+
transitionType: '', // TODO(mv3)
175+
transitionQualifiers: [], // TODO(mv3)
176+
documentLifecycle: getDocumentLifecycle(frame),
161177
processId: frameProcessId,
162178
tabId: tab.id,
163179
timeStamp: Date.now(),
@@ -175,13 +191,20 @@ export class WebNavigationAPI {
175191
frameRoutingId: number,
176192
) => {
177193
const frame = getFrame(frameProcessId, frameRoutingId)
194+
if (!frame) {
195+
// TODO(mv3): handle null return
196+
return;
197+
}
198+
178199
const details: chrome.webNavigation.WebNavigationTransitionCallbackDetails & {
179200
parentFrameId: number
180201
} = {
181202
transitionType: '', // TODO
182203
transitionQualifiers: [], // TODO
183204
frameId: getFrameId(frame),
184205
parentFrameId: getParentFrameId(frame),
206+
frameType: getFrameType(frame),
207+
documentLifecycle: getDocumentLifecycle(frame),
185208
processId: frameProcessId,
186209
tabId: tab.id,
187210
timeStamp: Date.now(),
@@ -194,6 +217,8 @@ export class WebNavigationAPI {
194217
const details: chrome.webNavigation.WebNavigationParentedCallbackDetails = {
195218
frameId: getFrameId(frame),
196219
parentFrameId: getParentFrameId(frame),
220+
frameType: getFrameType(frame),
221+
documentLifecycle: getDocumentLifecycle(frame),
197222
processId: frame.processId,
198223
tabId: tab.id,
199224
timeStamp: Date.now(),
@@ -214,10 +239,17 @@ export class WebNavigationAPI {
214239
frameRoutingId: number,
215240
) => {
216241
const frame = getFrame(frameProcessId, frameRoutingId)
242+
if (!frame) {
243+
// TODO(mv3): handle null return
244+
return;
245+
}
246+
217247
const url = tab.getURL()
218248
const details: chrome.webNavigation.WebNavigationParentedCallbackDetails = {
219249
frameId: getFrameId(frame),
220250
parentFrameId: getParentFrameId(frame),
251+
frameType: getFrameType(frame),
252+
documentLifecycle: getDocumentLifecycle(frame),
221253
processId: frameProcessId,
222254
tabId: tab.id,
223255
timeStamp: Date.now(),

0 commit comments

Comments
 (0)