Skip to content

Commit 420d08f

Browse files
committed
feat: support chrome_url_overrides for custom newtab page
1 parent 946c25b commit 420d08f

File tree

7 files changed

+110
-16
lines changed

7 files changed

+110
-16
lines changed

packages/electron-chrome-extensions/README.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,19 @@ Notify the extension system that a tab has been selected as the active tab.
169169
Returns [`Electron.MenuItem[]`](https://www.electronjs.org/docs/api/menu-item#class-menuitem) -
170170
An array of all extension context menu items given the context.
171171

172+
##### `extensions.getURLOverrides()`
173+
174+
Returns `Object` which maps special URL types to an extension URL. See [chrome_urls_overrides](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides) for a list of
175+
supported URL types.
176+
177+
Example:
178+
179+
```
180+
{
181+
newtab: 'chrome-extension://<id>/newtab.html'
182+
}
183+
```
184+
172185
#### Instance Events
173186

174187
##### Event: 'browser-action-popup-created'
@@ -179,6 +192,14 @@ Returns:
179192

180193
Emitted when a popup is created by the `chrome.browserAction` API.
181194

195+
##### Event: 'url-overrides-updated'
196+
197+
Returns:
198+
199+
- `urlOverrides` Object - A map of url types to extension URLs.
200+
201+
Emitted after an extension is loaded with [chrome_urls_overrides](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides) set.
202+
182203
### Element: `<browser-action-list>`
183204

184205
<img src="https://raw.githubusercontent.com/samuelmaddock/electron-browser-shell/master/packages/electron-chrome-extensions/screenshot-browser-action.png" width="438">

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { CommandsAPI } from './api/commands'
1717
import { ExtensionContext } from './context'
1818
import { ExtensionRouter } from './router'
1919
import { checkLicense, License } from './license'
20+
import { readLoadedExtensionManifest } from './manifest'
2021

2122
export interface ChromeExtensionOptions extends ChromeExtensionImpl {
2223
/**
@@ -98,9 +99,16 @@ export class ElectronChromeExtensions extends EventEmitter {
9899
windows: new WindowsAPI(this.ctx),
99100
}
100101

102+
this.listenForExtensions()
101103
this.prependPreload()
102104
}
103105

106+
private listenForExtensions() {
107+
this.ctx.session.addListener('extension-loaded', (_event, extension) => {
108+
readLoadedExtensionManifest(this.ctx, extension)
109+
})
110+
}
111+
104112
private async prependPreload() {
105113
const { session } = this.ctx
106114

@@ -169,6 +177,15 @@ export class ElectronChromeExtensions extends EventEmitter {
169177
return this.api.contextMenus.buildMenuItemsForParams(webContents, params)
170178
}
171179

180+
/**
181+
* Gets map of special pages to extension override URLs.
182+
*
183+
* @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides
184+
*/
185+
getURLOverrides(): Record<string, string> {
186+
return this.ctx.store.urlOverrides
187+
}
188+
172189
/**
173190
* Add extensions to be visible as an extension action button.
174191
*
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { getExtensionUrl, validateExtensionResource } from './api/common'
2+
import { ExtensionContext } from './context'
3+
4+
export async function readUrlOverrides(ctx: ExtensionContext, extension: Electron.Extension) {
5+
const manifest = extension.manifest as chrome.runtime.Manifest
6+
const urlOverrides = ctx.store.urlOverrides
7+
let updated = false
8+
9+
if (typeof manifest.chrome_url_overrides === 'object') {
10+
for (const [name, uri] of Object.entries(manifest.chrome_url_overrides!)) {
11+
const validatedPath = await validateExtensionResource(extension, uri)
12+
if (!validatedPath) {
13+
console.error(
14+
`Extension ${extension.id} attempted to override ${name} with invalid resource: ${uri}`,
15+
)
16+
continue
17+
}
18+
19+
const url = getExtensionUrl(extension, uri)!
20+
const currentUrl = urlOverrides[name]
21+
if (currentUrl !== url) {
22+
urlOverrides[name] = url
23+
updated = true
24+
}
25+
}
26+
}
27+
28+
if (updated) {
29+
ctx.emit('url-overrides-updated', urlOverrides)
30+
}
31+
}
32+
33+
export function readLoadedExtensionManifest(ctx: ExtensionContext, extension: Electron.Extension) {
34+
readUrlOverrides(ctx, extension)
35+
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export class ExtensionStore extends EventEmitter {
2929
tabDetailsCache = new Map<number, Partial<chrome.tabs.Tab>>()
3030
windowDetailsCache = new Map<number, Partial<chrome.windows.Window>>()
3131

32+
urlOverrides: Record<string, string> = {}
33+
3234
constructor(public impl: ChromeExtensionImpl) {
3335
super()
3436
}

packages/electron-chrome-web-store/src/browser/index.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -522,7 +522,7 @@ interface ElectronChromeWebStoreOptions {
522522
*
523523
* @param options Chrome Web Store configuration options.
524524
*/
525-
export function installChromeWebStore(opts: ElectronChromeWebStoreOptions = {}) {
525+
export async function installChromeWebStore(opts: ElectronChromeWebStoreOptions = {}) {
526526
const session = opts.session || electronSession.defaultSession
527527
const extensionsPath = opts.extensionsPath || path.join(app.getPath('userData'), 'Extensions')
528528
const modulePath = opts.modulePath || __dirname
@@ -554,9 +554,9 @@ export function installChromeWebStore(opts: ElectronChromeWebStoreOptions = {})
554554

555555
addIpcListeners(webStoreState)
556556

557-
app.whenReady().then(() => {
558-
if (loadExtensions) {
559-
loadAllExtensions(session, extensionsPath, { allowUnpacked: allowUnpackedExtensions })
560-
}
561-
})
557+
await app.whenReady()
558+
559+
if (loadExtensions) {
560+
await loadAllExtensions(session, extensionsPath, { allowUnpacked: allowUnpackedExtensions })
561+
}
562562
}

packages/shell/browser/main.js

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
const path = require('path')
2-
const { promises: fs } = require('fs')
32
const { app, session, BrowserWindow } = require('electron')
43

54
const { Tabs } = require('./tabs')
@@ -51,7 +50,7 @@ class TabbedBrowserWindow {
5150
const self = this
5251

5352
this.tabs.on('tab-created', function onTabCreated(tab) {
54-
if (options.initialUrl) tab.webContents.loadURL(options.initialUrl)
53+
tab.loadURL(options.urls.newtab)
5554

5655
// Track tab that may have been created outside of the extensions API.
5756
self.extensions.addTab(tab.webContents, tab.window)
@@ -63,7 +62,11 @@ class TabbedBrowserWindow {
6362

6463
queueMicrotask(() => {
6564
// Create initial tab
66-
this.tabs.create()
65+
const tab = this.tabs.create()
66+
67+
if (options.initialUrl) {
68+
tab.loadURL(options.initialUrl)
69+
}
6770
})
6871
}
6972

@@ -80,6 +83,10 @@ class TabbedBrowserWindow {
8083
class Browser {
8184
windows = []
8285

86+
urls = {
87+
newtab: 'about:blank',
88+
}
89+
8390
constructor() {
8491
app.whenReady().then(this.init.bind(this))
8592

@@ -153,7 +160,7 @@ class Browser {
153160

154161
const tab = win.tabs.create()
155162

156-
if (details.url) tab.loadURL(details.url || newTabUrl)
163+
if (details.url) tab.loadURL(details.url)
157164
if (typeof details.active === 'boolean' ? details.active : true) win.tabs.select(tab.id)
158165

159166
return [tab.webContents, tab.window]
@@ -169,7 +176,7 @@ class Browser {
169176

170177
createWindow: (details) => {
171178
const win = this.createWindow({
172-
initialUrl: details.url || newTabUrl,
179+
initialUrl: details.url,
173180
})
174181
// if (details.active) tabs.select(tab.id)
175182
return win.window
@@ -184,16 +191,25 @@ class Browser {
184191
this.popup = popup
185192
})
186193

194+
// Allow extensions to override new tab page
195+
this.extensions.on('url-overrides-updated', (urlOverrides) => {
196+
if (urlOverrides.newtab) {
197+
this.urls.newtab = urlOverrides.newtab
198+
}
199+
})
200+
187201
const webuiExtension = await this.session.loadExtension(PATHS.WEBUI)
188202
webuiExtensionId = webuiExtension.id
189203

190-
installChromeWebStore({
204+
// Wait for web store extensions to finish loading as they may change the
205+
// newtab URL.
206+
await installChromeWebStore({
191207
session: this.session,
192208
modulePath: path.join(__dirname, 'electron-chrome-web-store'),
193209
})
194210

195211
if (!app.isPackaged) {
196-
loadAllExtensions(this.session, PATHS.LOCAL_EXTENSIONS, true)
212+
await loadAllExtensions(this.session, PATHS.LOCAL_EXTENSIONS, true)
197213
}
198214

199215
this.createInitialWindow()
@@ -213,6 +229,7 @@ class Browser {
213229
createWindow(options) {
214230
const win = new TabbedBrowserWindow({
215231
...options,
232+
urls: this.urls,
216233
extensions: this.extensions,
217234
window: {
218235
width: 1280,
@@ -243,8 +260,7 @@ class Browser {
243260
}
244261

245262
createInitialWindow() {
246-
const newTabUrl = path.join('chrome-extension://', webuiExtensionId, 'new-tab.html')
247-
this.createWindow({ initialUrl: newTabUrl })
263+
this.createWindow()
248264
}
249265

250266
async onWebContentsCreated(event, webContents) {

packages/shell/browser/ui/manifest.json

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22
"name": "WebUI",
33
"version": "1.0.0",
44
"manifest_version": 3,
5-
"permissions": []
5+
"permissions": [],
6+
"chrome_url_overrides": {
7+
"newtab": "new-tab.html"
8+
}
69
}

0 commit comments

Comments
 (0)