electron 允许开发者向原生Chrome扩展程序添加新的API

vzgqcmou  于 2个月前  发布在  Electron
关注(0)|答案(5)|浏览(37)

问题描述

作为用户,我希望能够在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

thigvfpy

thigvfpy1#

另外,如果你更倾向于避免引入这种破坏性的变化,webFrame中可以有一个名为injectChromeAPI的API,它接受一个用户定义的chrome API对象作为参数,与现有的本地实现合并。这个方法将检查当前的webContents是否是扩展背景页面,并将指定的chrome API注入到相应的世界中,而不会打扰用户应该注入哪个世界。

oiopk7p5

oiopk7p52#

我添加了另一个备选方案,即向webFrame添加一个新事件,以便在扩展脚本上下文创建时知道。不过我还不确定如何解决IPC问题。
@sentialx ^ 这个想法与你的有些相似

7uzetpgm

7uzetpgm3#

@samuelmaddock 是否可以在预加载中导入ipcRenderer并在setTitle方法中使用它?

ijnw1ujt

ijnw1ujt4#

在预加载中导入ipcRenderer并在setTitle方法中使用它是否可行?
在我编写的示例中,预加载脚本和注入到隔离世界中的脚本将位于不同的文件中。重要的一点是,它们在不同的脚本上下文中运行。
预加载脚本的上下文将能够访问ipcRenderer API。
扩展的隔离世界脚本上下文将无法访问。
尽管如此,也许可以将API添加到隔离世界脚本上下文中。我需要进一步研究这个问题。

kxe2p93d

kxe2p93d5#

这是可能的:

const { webFrame, ipcRenderer } = require('electron');

(async () => {
  const w = await webFrame.executeJavaScriptInIsolatedWorld(worldId, [
    {
      code: 'window',
    }
  ]);

  w.ipcRenderer = ipcRenderer;

  await webFrame.executeJavaScriptInIsolatedWorld(worldId, [
    {
      code: 'console.log(window.ipcRenderer)',
    }
  ]);
})();

但是开发者需要记住在最后调用 delete window.ipcRenderer 。这并不好。

相关问题