Skip to content

Commit

Permalink
feat: support chrome_url_overrides for custom newtab page
Browse files Browse the repository at this point in the history
  • Loading branch information
samuelmaddock committed Dec 17, 2024
1 parent 946c25b commit 420d08f
Show file tree
Hide file tree
Showing 7 changed files with 110 additions and 16 deletions.
21 changes: 21 additions & 0 deletions packages/electron-chrome-extensions/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,19 @@ Notify the extension system that a tab has been selected as the active tab.
Returns [`Electron.MenuItem[]`](https://www.electronjs.org/docs/api/menu-item#class-menuitem) -
An array of all extension context menu items given the context.

##### `extensions.getURLOverrides()`

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
supported URL types.

Example:

```
{
newtab: 'chrome-extension://<id>/newtab.html'
}
```

#### Instance Events

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

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

##### Event: 'url-overrides-updated'

Returns:

- `urlOverrides` Object - A map of url types to extension URLs.

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.

### Element: `<browser-action-list>`

<img src="https://raw.githubusercontent.com/samuelmaddock/electron-browser-shell/master/packages/electron-chrome-extensions/screenshot-browser-action.png" width="438">
Expand Down
17 changes: 17 additions & 0 deletions packages/electron-chrome-extensions/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { CommandsAPI } from './api/commands'
import { ExtensionContext } from './context'
import { ExtensionRouter } from './router'
import { checkLicense, License } from './license'
import { readLoadedExtensionManifest } from './manifest'

export interface ChromeExtensionOptions extends ChromeExtensionImpl {
/**
Expand Down Expand Up @@ -98,9 +99,16 @@ export class ElectronChromeExtensions extends EventEmitter {
windows: new WindowsAPI(this.ctx),
}

this.listenForExtensions()
this.prependPreload()
}

private listenForExtensions() {
this.ctx.session.addListener('extension-loaded', (_event, extension) => {
readLoadedExtensionManifest(this.ctx, extension)
})
}

private async prependPreload() {
const { session } = this.ctx

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

/**
* Gets map of special pages to extension override URLs.
*
* @see https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json/chrome_url_overrides
*/
getURLOverrides(): Record<string, string> {
return this.ctx.store.urlOverrides
}

/**
* Add extensions to be visible as an extension action button.
*
Expand Down
35 changes: 35 additions & 0 deletions packages/electron-chrome-extensions/src/browser/manifest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { getExtensionUrl, validateExtensionResource } from './api/common'
import { ExtensionContext } from './context'

export async function readUrlOverrides(ctx: ExtensionContext, extension: Electron.Extension) {
const manifest = extension.manifest as chrome.runtime.Manifest
const urlOverrides = ctx.store.urlOverrides
let updated = false

if (typeof manifest.chrome_url_overrides === 'object') {
for (const [name, uri] of Object.entries(manifest.chrome_url_overrides!)) {
const validatedPath = await validateExtensionResource(extension, uri)
if (!validatedPath) {
console.error(
`Extension ${extension.id} attempted to override ${name} with invalid resource: ${uri}`,
)
continue
}

const url = getExtensionUrl(extension, uri)!
const currentUrl = urlOverrides[name]
if (currentUrl !== url) {
urlOverrides[name] = url
updated = true
}
}
}

if (updated) {
ctx.emit('url-overrides-updated', urlOverrides)
}
}

export function readLoadedExtensionManifest(ctx: ExtensionContext, extension: Electron.Extension) {
readUrlOverrides(ctx, extension)
}
2 changes: 2 additions & 0 deletions packages/electron-chrome-extensions/src/browser/store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export class ExtensionStore extends EventEmitter {
tabDetailsCache = new Map<number, Partial<chrome.tabs.Tab>>()
windowDetailsCache = new Map<number, Partial<chrome.windows.Window>>()

urlOverrides: Record<string, string> = {}

constructor(public impl: ChromeExtensionImpl) {
super()
}
Expand Down
12 changes: 6 additions & 6 deletions packages/electron-chrome-web-store/src/browser/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@ interface ElectronChromeWebStoreOptions {
*
* @param options Chrome Web Store configuration options.
*/
export function installChromeWebStore(opts: ElectronChromeWebStoreOptions = {}) {
export async function installChromeWebStore(opts: ElectronChromeWebStoreOptions = {}) {
const session = opts.session || electronSession.defaultSession
const extensionsPath = opts.extensionsPath || path.join(app.getPath('userData'), 'Extensions')
const modulePath = opts.modulePath || __dirname
Expand Down Expand Up @@ -554,9 +554,9 @@ export function installChromeWebStore(opts: ElectronChromeWebStoreOptions = {})

addIpcListeners(webStoreState)

app.whenReady().then(() => {
if (loadExtensions) {
loadAllExtensions(session, extensionsPath, { allowUnpacked: allowUnpackedExtensions })
}
})
await app.whenReady()

if (loadExtensions) {
await loadAllExtensions(session, extensionsPath, { allowUnpacked: allowUnpackedExtensions })
}
}
34 changes: 25 additions & 9 deletions packages/shell/browser/main.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
const path = require('path')
const { promises: fs } = require('fs')
const { app, session, BrowserWindow } = require('electron')

const { Tabs } = require('./tabs')
Expand Down Expand Up @@ -51,7 +50,7 @@ class TabbedBrowserWindow {
const self = this

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

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

queueMicrotask(() => {
// Create initial tab
this.tabs.create()
const tab = this.tabs.create()

if (options.initialUrl) {
tab.loadURL(options.initialUrl)
}
})
}

Expand All @@ -80,6 +83,10 @@ class TabbedBrowserWindow {
class Browser {
windows = []

urls = {
newtab: 'about:blank',
}

constructor() {
app.whenReady().then(this.init.bind(this))

Expand Down Expand Up @@ -153,7 +160,7 @@ class Browser {

const tab = win.tabs.create()

if (details.url) tab.loadURL(details.url || newTabUrl)
if (details.url) tab.loadURL(details.url)
if (typeof details.active === 'boolean' ? details.active : true) win.tabs.select(tab.id)

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

createWindow: (details) => {
const win = this.createWindow({
initialUrl: details.url || newTabUrl,
initialUrl: details.url,
})
// if (details.active) tabs.select(tab.id)
return win.window
Expand All @@ -184,16 +191,25 @@ class Browser {
this.popup = popup
})

// Allow extensions to override new tab page
this.extensions.on('url-overrides-updated', (urlOverrides) => {
if (urlOverrides.newtab) {
this.urls.newtab = urlOverrides.newtab
}
})

const webuiExtension = await this.session.loadExtension(PATHS.WEBUI)
webuiExtensionId = webuiExtension.id

installChromeWebStore({
// Wait for web store extensions to finish loading as they may change the
// newtab URL.
await installChromeWebStore({
session: this.session,
modulePath: path.join(__dirname, 'electron-chrome-web-store'),
})

if (!app.isPackaged) {
loadAllExtensions(this.session, PATHS.LOCAL_EXTENSIONS, true)
await loadAllExtensions(this.session, PATHS.LOCAL_EXTENSIONS, true)
}

this.createInitialWindow()
Expand All @@ -213,6 +229,7 @@ class Browser {
createWindow(options) {
const win = new TabbedBrowserWindow({
...options,
urls: this.urls,
extensions: this.extensions,
window: {
width: 1280,
Expand Down Expand Up @@ -243,8 +260,7 @@ class Browser {
}

createInitialWindow() {
const newTabUrl = path.join('chrome-extension://', webuiExtensionId, 'new-tab.html')
this.createWindow({ initialUrl: newTabUrl })
this.createWindow()
}

async onWebContentsCreated(event, webContents) {
Expand Down
5 changes: 4 additions & 1 deletion packages/shell/browser/ui/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,8 @@
"name": "WebUI",
"version": "1.0.0",
"manifest_version": 3,
"permissions": []
"permissions": [],
"chrome_url_overrides": {
"newtab": "new-tab.html"
}
}

0 comments on commit 420d08f

Please sign in to comment.