问题描述
作为用户,我希望能够在Electron应用程序中使用网络商店(如Chrome网络商店)中的浏览器扩展。
作为开发者,我想通过扩展原生Chrome扩展API(chrome.*
)来提供这个功能。
目前还没有一种机制可以让开发者将脚本注入到新创建的隔离世界脚本上下文中。预加载脚本提供了将脚本注入到顶层框架的主要世界或通过contextIsolation
注入到顶层框架的隔离世界的能力。或者(1)或(2)在所有框架中都可以通过nodeIntegrationInSubFrames
实现。Chrome extension content scripts 被注入到由ElectronExtensionsRendererClient::GetLowestIsolatedWorldId()
定义的隔离世界中。这些世界ID对于预加载脚本是不可访问的。
建议解决方案
一个可能的解决方案是重新设计预加载脚本API。不要使用webPreferences
选项为预加载脚本注入设置全局偏好,而是可以将脚本注入选项定义为每个预加载脚本。这将从Chrome扩展内容脚本中获得大量灵感。
interface PreloadScript {
/** Path to preload script. */
script: string
/** Whether this preload script requires node integration. */
nodeIntegration: boolean
/** Whether to inject into all frames. */
allFrames: boolean
/** Which script contextes should the preload script be injected into. */
context:
| 'main' // main world (0)
| 'isolated' // isolated world (999) - default
| 'extensions' // extension isolated worlds ([1 << 20]+)
| 'any' // any newly created script context
}
const preloadScripts: PreloadScript[] = [
{
script: 'preload.js',
nodeIntegration: true,
context: 'isolated'
},
{
script: 'custom-chrome-apis.js',
allFrames: true,
context: 'extensions'
}
]
session.defaultSession.setPreloads(preloadScripts)
考虑过的替代方案
1. Chrome扩展JS绑定系统
原生Chrome扩展提供了一个API,用于将 features 绑定到扩展脚本上下文。Extension Bindings documentation 提供了更多详细信息。
最初,Chrome API功能是通过JavaScript实现的。这种绑定机制现在仅用于遗留API,最终将被取代。受此API启发,Electron可以提供一个新的JS绑定机制,以完全扩展Chrome扩展。
2. 'created-script-context'事件用于渲染器webFrame
当扩展被注入到渲染器时,它们需要首先创建一个新的脚本上下文。RendererClientBase
类上有一个方便的方法,可以修改它以接受世界ID。
# shell\renderer\renderer_client_base.h
-virtual void DidCreateScriptContext(v8::Handle<v8::Context> context,
- content::RenderFrame* render_frame);
+virtual void DidCreateScriptContext(v8::Handle<v8::Context> context,
+ content::RenderFrame* render_frame,
+ int32_t world_id);
通过将此信息转发给webFrame
API,我们可以将其作为事件发出。预加载脚本可以监听此事件以了解何时创建扩展脚本上下文。
此外,extensions::ScriptContextSet::GetContextByV8Context
将允许我们知道它是否用于管理扩展。可以将有关扩展的元数据传递给事件。
// preload.js
webFrame.on('created-script-context', (worldId: number) => {
if (isExtensionWorld(worldId)) {
webFrame.executeJavaScriptInIsolatedWorld(worldId, [
{ url: 'custom-chrome-apis.js' }
])
}
})
// custom-chrome-apis.js
window.chrome = {
browserAction: {
setTitle(details, cb) {
// ...
}
}
}
chrome.ipcRenderer
此解决方案的最后一部分是为Chrome扩展提供ipcRenderer
API。这样自定义API(如browserAction
)就可以与浏览器进程进行通信。
这可能是危险的,因为信任所有具有ipcRenderer
API的扩展并不是很好的做法。Chrome的扩展系统有一个权限系统,其中一些API仅根据脚本的上下文注入(参见 Feature::Context
枚举)。
创建一个新的本地扩展功能只允许特殊Electron扩展上下文可能是实现这一点的一种方法。在添加新的chrome.*
API后,注入的脚本然后可以调用delete chrome.ipcRenderer
以防止扩展使用它。
其他信息
参考 #19447
5条答案
按热度按时间thigvfpy1#
另外,如果你更倾向于避免引入这种破坏性的变化,
webFrame
中可以有一个名为injectChromeAPI
的API,它接受一个用户定义的chrome
API对象作为参数,与现有的本地实现合并。这个方法将检查当前的webContents
是否是扩展背景页面,并将指定的chrome
API注入到相应的世界中,而不会打扰用户应该注入哪个世界。oiopk7p52#
我添加了另一个备选方案,即向
webFrame
添加一个新事件,以便在扩展脚本上下文创建时知道。不过我还不确定如何解决IPC问题。@sentialx ^ 这个想法与你的有些相似
7uzetpgm3#
@samuelmaddock 是否可以在预加载中导入ipcRenderer并在setTitle方法中使用它?
ijnw1ujt4#
在预加载中导入ipcRenderer并在setTitle方法中使用它是否可行?
在我编写的示例中,预加载脚本和注入到隔离世界中的脚本将位于不同的文件中。重要的一点是,它们在不同的脚本上下文中运行。
预加载脚本的上下文将能够访问
ipcRenderer
API。扩展的隔离世界脚本上下文将无法访问。
尽管如此,也许可以将API添加到隔离世界脚本上下文中。我需要进一步研究这个问题。
kxe2p93d5#
这是可能的:
但是开发者需要记住在最后调用
delete window.ipcRenderer
。这并不好。