feat: Add content script world isolation (#17032)
* Execute content script in isolated world * Inject script into newly created extension worlds * Create new content_script_bundle for extension scripts * Initialize chrome API in content script bundle * Define Chrome extension isolated world ID range 1 << 20 was chosen as it provides a sufficiently large range of IDs for extensions, but also provides a large enough buffer for any user worlds in [1000, 1 << 20). Ultimately this range can be changed if any user application raises it as an issue. * Insert content script CSS into document This now avoids a script wrapper to inject the style sheet. This closely matches the code used by chromium in `ScriptInjection::InjectCss`. * Pass extension ID to isolated world via v8 private
This commit is contained in:
parent
6072da239d
commit
f943db7ad5
11 changed files with 187 additions and 44 deletions
33
BUILD.gn
33
BUILD.gn
|
@ -137,6 +137,37 @@ npm_action("atom_browserify_isolated") {
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
npm_action("atom_browserify_content_script") {
|
||||||
|
script = "browserify"
|
||||||
|
deps = [
|
||||||
|
":build_electron_definitions",
|
||||||
|
]
|
||||||
|
|
||||||
|
inputs = [
|
||||||
|
"lib/content_script/init.js",
|
||||||
|
"tsconfig.electron.json",
|
||||||
|
"tsconfig.json",
|
||||||
|
]
|
||||||
|
|
||||||
|
outputs = [
|
||||||
|
"$target_gen_dir/js2c/content_script_bundle.js",
|
||||||
|
]
|
||||||
|
|
||||||
|
args = [
|
||||||
|
"lib/content_script/init.js",
|
||||||
|
"-t",
|
||||||
|
"aliasify",
|
||||||
|
"-p",
|
||||||
|
"[",
|
||||||
|
"tsify",
|
||||||
|
"-p",
|
||||||
|
"tsconfig.electron.json",
|
||||||
|
"]",
|
||||||
|
"-o",
|
||||||
|
rebase_path(outputs[0]),
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
copy("atom_js2c_copy") {
|
copy("atom_js2c_copy") {
|
||||||
sources = [
|
sources = [
|
||||||
"lib/common/asar.js",
|
"lib/common/asar.js",
|
||||||
|
@ -149,12 +180,14 @@ copy("atom_js2c_copy") {
|
||||||
|
|
||||||
action("atom_js2c") {
|
action("atom_js2c") {
|
||||||
deps = [
|
deps = [
|
||||||
|
":atom_browserify_content_script",
|
||||||
":atom_browserify_isolated",
|
":atom_browserify_isolated",
|
||||||
":atom_browserify_sandbox",
|
":atom_browserify_sandbox",
|
||||||
":atom_js2c_copy",
|
":atom_js2c_copy",
|
||||||
]
|
]
|
||||||
|
|
||||||
browserify_sources = [
|
browserify_sources = [
|
||||||
|
"$target_gen_dir/js2c/content_script_bundle.js",
|
||||||
"$target_gen_dir/js2c/isolated_bundle.js",
|
"$target_gen_dir/js2c/isolated_bundle.js",
|
||||||
"$target_gen_dir/js2c/preload_bundle.js",
|
"$target_gen_dir/js2c/preload_bundle.js",
|
||||||
]
|
]
|
||||||
|
|
|
@ -113,6 +113,12 @@ void AtomRenderFrameObserver::DidCreateScriptContext(
|
||||||
CreateIsolatedWorldContext();
|
CreateIsolatedWorldContext();
|
||||||
renderer_client_->SetupMainWorldOverrides(context, render_frame_);
|
renderer_client_->SetupMainWorldOverrides(context, render_frame_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (world_id >= World::ISOLATED_WORLD_EXTENSIONS &&
|
||||||
|
world_id <= World::ISOLATED_WORLD_EXTENSIONS_END) {
|
||||||
|
renderer_client_->SetupExtensionWorldOverrides(context, render_frame_,
|
||||||
|
world_id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void AtomRenderFrameObserver::DraggableRegionsChanged() {
|
void AtomRenderFrameObserver::DraggableRegionsChanged() {
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
#include "base/strings/string16.h"
|
#include "base/strings/string16.h"
|
||||||
#include "content/public/renderer/render_frame_observer.h"
|
#include "content/public/renderer/render_frame_observer.h"
|
||||||
#include "ipc/ipc_platform_file.h"
|
#include "ipc/ipc_platform_file.h"
|
||||||
|
#include "third_party/blink/public/platform/web_isolated_world_ids.h"
|
||||||
#include "third_party/blink/public/web/web_local_frame.h"
|
#include "third_party/blink/public/web/web_local_frame.h"
|
||||||
|
|
||||||
namespace base {
|
namespace base {
|
||||||
|
@ -21,9 +22,19 @@ namespace atom {
|
||||||
|
|
||||||
enum World {
|
enum World {
|
||||||
MAIN_WORLD = 0,
|
MAIN_WORLD = 0,
|
||||||
|
|
||||||
// Use a high number far away from 0 to not collide with any other world
|
// Use a high number far away from 0 to not collide with any other world
|
||||||
// IDs created internally by Chrome.
|
// IDs created internally by Chrome.
|
||||||
ISOLATED_WORLD = 999
|
ISOLATED_WORLD = 999,
|
||||||
|
|
||||||
|
// Numbers for isolated worlds for extensions are set in
|
||||||
|
// lib/renderer/content-script-injector.ts, and are greater than or equal to
|
||||||
|
// this number, up to ISOLATED_WORLD_EXTENSIONS_END.
|
||||||
|
ISOLATED_WORLD_EXTENSIONS = 1 << 20,
|
||||||
|
|
||||||
|
// Last valid isolated world ID.
|
||||||
|
ISOLATED_WORLD_EXTENSIONS_END =
|
||||||
|
blink::IsolatedWorldId::kEmbedderWorldIdLimit - 1
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper class to forward the messages to the client.
|
// Helper class to forward the messages to the client.
|
||||||
|
|
|
@ -210,6 +210,27 @@ void AtomRendererClient::SetupMainWorldOverrides(
|
||||||
&isolated_bundle_args, nullptr);
|
&isolated_bundle_args, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AtomRendererClient::SetupExtensionWorldOverrides(
|
||||||
|
v8::Handle<v8::Context> context,
|
||||||
|
content::RenderFrame* render_frame,
|
||||||
|
int world_id) {
|
||||||
|
auto* isolate = context->GetIsolate();
|
||||||
|
|
||||||
|
std::vector<v8::Local<v8::String>> isolated_bundle_params = {
|
||||||
|
node::FIXED_ONE_BYTE_STRING(isolate, "nodeProcess"),
|
||||||
|
node::FIXED_ONE_BYTE_STRING(isolate, "isolatedWorld"),
|
||||||
|
node::FIXED_ONE_BYTE_STRING(isolate, "worldId")};
|
||||||
|
|
||||||
|
std::vector<v8::Local<v8::Value>> isolated_bundle_args = {
|
||||||
|
GetEnvironment(render_frame)->process_object(),
|
||||||
|
GetContext(render_frame->GetWebFrame(), isolate)->Global(),
|
||||||
|
v8::Integer::New(isolate, world_id)};
|
||||||
|
|
||||||
|
node::per_process::native_module_loader.CompileAndCall(
|
||||||
|
context, "electron/js2c/content_script_bundle", &isolated_bundle_params,
|
||||||
|
&isolated_bundle_args, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
node::Environment* AtomRendererClient::GetEnvironment(
|
node::Environment* AtomRendererClient::GetEnvironment(
|
||||||
content::RenderFrame* render_frame) const {
|
content::RenderFrame* render_frame) const {
|
||||||
if (injected_frames_.find(render_frame) == injected_frames_.end())
|
if (injected_frames_.find(render_frame) == injected_frames_.end())
|
||||||
|
|
|
@ -33,6 +33,9 @@ class AtomRendererClient : public RendererClientBase {
|
||||||
content::RenderFrame* render_frame) override;
|
content::RenderFrame* render_frame) override;
|
||||||
void SetupMainWorldOverrides(v8::Handle<v8::Context> context,
|
void SetupMainWorldOverrides(v8::Handle<v8::Context> context,
|
||||||
content::RenderFrame* render_frame) override;
|
content::RenderFrame* render_frame) override;
|
||||||
|
void SetupExtensionWorldOverrides(v8::Handle<v8::Context> context,
|
||||||
|
content::RenderFrame* render_frame,
|
||||||
|
int world_id) override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// content::ContentRendererClient:
|
// content::ContentRendererClient:
|
||||||
|
|
|
@ -270,6 +270,30 @@ void AtomSandboxedRendererClient::SetupMainWorldOverrides(
|
||||||
&isolated_bundle_args, nullptr);
|
&isolated_bundle_args, nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void AtomSandboxedRendererClient::SetupExtensionWorldOverrides(
|
||||||
|
v8::Handle<v8::Context> context,
|
||||||
|
content::RenderFrame* render_frame,
|
||||||
|
int world_id) {
|
||||||
|
auto* isolate = context->GetIsolate();
|
||||||
|
|
||||||
|
mate::Dictionary process = mate::Dictionary::CreateEmpty(isolate);
|
||||||
|
process.SetMethod("binding", GetBinding);
|
||||||
|
|
||||||
|
std::vector<v8::Local<v8::String>> isolated_bundle_params = {
|
||||||
|
node::FIXED_ONE_BYTE_STRING(isolate, "nodeProcess"),
|
||||||
|
node::FIXED_ONE_BYTE_STRING(isolate, "isolatedWorld"),
|
||||||
|
node::FIXED_ONE_BYTE_STRING(isolate, "worldId")};
|
||||||
|
|
||||||
|
std::vector<v8::Local<v8::Value>> isolated_bundle_args = {
|
||||||
|
process.GetHandle(),
|
||||||
|
GetContext(render_frame->GetWebFrame(), isolate)->Global(),
|
||||||
|
v8::Integer::New(isolate, world_id)};
|
||||||
|
|
||||||
|
node::per_process::native_module_loader.CompileAndCall(
|
||||||
|
context, "electron/js2c/content_script_bundle", &isolated_bundle_params,
|
||||||
|
&isolated_bundle_args, nullptr);
|
||||||
|
}
|
||||||
|
|
||||||
void AtomSandboxedRendererClient::WillReleaseScriptContext(
|
void AtomSandboxedRendererClient::WillReleaseScriptContext(
|
||||||
v8::Handle<v8::Context> context,
|
v8::Handle<v8::Context> context,
|
||||||
content::RenderFrame* render_frame) {
|
content::RenderFrame* render_frame) {
|
||||||
|
|
|
@ -32,6 +32,9 @@ class AtomSandboxedRendererClient : public RendererClientBase {
|
||||||
content::RenderFrame* render_frame) override;
|
content::RenderFrame* render_frame) override;
|
||||||
void SetupMainWorldOverrides(v8::Handle<v8::Context> context,
|
void SetupMainWorldOverrides(v8::Handle<v8::Context> context,
|
||||||
content::RenderFrame* render_frame) override;
|
content::RenderFrame* render_frame) override;
|
||||||
|
void SetupExtensionWorldOverrides(v8::Handle<v8::Context> context,
|
||||||
|
content::RenderFrame* render_frame,
|
||||||
|
int world_id) override;
|
||||||
// content::ContentRendererClient:
|
// content::ContentRendererClient:
|
||||||
void RenderFrameCreated(content::RenderFrame*) override;
|
void RenderFrameCreated(content::RenderFrame*) override;
|
||||||
void RenderViewCreated(content::RenderView*) override;
|
void RenderViewCreated(content::RenderView*) override;
|
||||||
|
|
|
@ -34,6 +34,9 @@ class RendererClientBase : public content::ContentRendererClient {
|
||||||
virtual void DidClearWindowObject(content::RenderFrame* render_frame);
|
virtual void DidClearWindowObject(content::RenderFrame* render_frame);
|
||||||
virtual void SetupMainWorldOverrides(v8::Handle<v8::Context> context,
|
virtual void SetupMainWorldOverrides(v8::Handle<v8::Context> context,
|
||||||
content::RenderFrame* render_frame) = 0;
|
content::RenderFrame* render_frame) = 0;
|
||||||
|
virtual void SetupExtensionWorldOverrides(v8::Handle<v8::Context> context,
|
||||||
|
content::RenderFrame* render_frame,
|
||||||
|
int world_id) = 0;
|
||||||
|
|
||||||
bool isolated_world() const { return isolated_world_; }
|
bool isolated_world() const { return isolated_world_; }
|
||||||
|
|
||||||
|
|
|
@ -95,6 +95,12 @@ webFrame.setSpellCheckProvider('en-US', {
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### `webFrame.insertCSS(css)`
|
||||||
|
|
||||||
|
* `css` String - CSS source code.
|
||||||
|
|
||||||
|
Inserts `css` as a style sheet in the document.
|
||||||
|
|
||||||
### `webFrame.insertText(text)`
|
### `webFrame.insertText(text)`
|
||||||
|
|
||||||
* `text` String
|
* `text` String
|
||||||
|
@ -119,7 +125,7 @@ this limitation.
|
||||||
|
|
||||||
### `webFrame.executeJavaScriptInIsolatedWorld(worldId, scripts[, userGesture, callback])`
|
### `webFrame.executeJavaScriptInIsolatedWorld(worldId, scripts[, userGesture, callback])`
|
||||||
|
|
||||||
* `worldId` Integer - The ID of the world to run the javascript in, `0` is the default world, `999` is the world used by Electrons `contextIsolation` feature. You can provide any integer here.
|
* `worldId` Integer - The ID of the world to run the javascript in, `0` is the default world, `999` is the world used by Electrons `contextIsolation` feature. Chrome extensions reserve the range of IDs in `[1 << 20, 1 << 29)`. You can provide any integer here.
|
||||||
* `scripts` [WebSource[]](structures/web-source.md)
|
* `scripts` [WebSource[]](structures/web-source.md)
|
||||||
* `userGesture` Boolean (optional) - Default is `false`.
|
* `userGesture` Boolean (optional) - Default is `false`.
|
||||||
* `callback` Function (optional) - Called after script has been executed.
|
* `callback` Function (optional) - Called after script has been executed.
|
||||||
|
@ -129,27 +135,27 @@ Work like `executeJavaScript` but evaluates `scripts` in an isolated context.
|
||||||
|
|
||||||
### `webFrame.setIsolatedWorldContentSecurityPolicy(worldId, csp)` _(Deprecated)_
|
### `webFrame.setIsolatedWorldContentSecurityPolicy(worldId, csp)` _(Deprecated)_
|
||||||
|
|
||||||
* `worldId` Integer - The ID of the world to run the javascript in, `0` is the default world, `999` is the world used by Electrons `contextIsolation` feature. You can provide any integer here.
|
* `worldId` Integer - The ID of the world to run the javascript in, `0` is the default world, `999` is the world used by Electrons `contextIsolation` feature. Chrome extensions reserve the range of IDs in `[1 << 20, 1 << 29)`. You can provide any integer here.
|
||||||
* `csp` String
|
* `csp` String
|
||||||
|
|
||||||
Set the content security policy of the isolated world.
|
Set the content security policy of the isolated world.
|
||||||
|
|
||||||
### `webFrame.setIsolatedWorldHumanReadableName(worldId, name)` _(Deprecated)_
|
### `webFrame.setIsolatedWorldHumanReadableName(worldId, name)` _(Deprecated)_
|
||||||
|
|
||||||
* `worldId` Integer - The ID of the world to run the javascript in, `0` is the default world, `999` is the world used by Electrons `contextIsolation` feature. You can provide any integer here.
|
* `worldId` Integer - The ID of the world to run the javascript in, `0` is the default world, `999` is the world used by Electrons `contextIsolation` feature. Chrome extensions reserve the range of IDs in `[1 << 20, 1 << 29)`. You can provide any integer here.
|
||||||
* `name` String
|
* `name` String
|
||||||
|
|
||||||
Set the name of the isolated world. Useful in devtools.
|
Set the name of the isolated world. Useful in devtools.
|
||||||
|
|
||||||
### `webFrame.setIsolatedWorldSecurityOrigin(worldId, securityOrigin)` _(Deprecated)_
|
### `webFrame.setIsolatedWorldSecurityOrigin(worldId, securityOrigin)` _(Deprecated)_
|
||||||
|
|
||||||
* `worldId` Integer - The ID of the world to run the javascript in, `0` is the default world, `999` is the world used by Electrons `contextIsolation` feature. You can provide any integer here.
|
* `worldId` Integer - The ID of the world to run the javascript in, `0` is the default world, `999` is the world used by Electrons `contextIsolation` feature. Chrome extensions reserve the range of IDs in `[1 << 20, 1 << 29)`. You can provide any integer here.
|
||||||
* `securityOrigin` String
|
* `securityOrigin` String
|
||||||
|
|
||||||
Set the security origin of the isolated world.
|
Set the security origin of the isolated world.
|
||||||
|
|
||||||
### `webFrame.setIsolatedWorldInfo(worldId, info)`
|
### `webFrame.setIsolatedWorldInfo(worldId, info)`
|
||||||
* `worldId` Integer - The ID of the world to run the javascript in, `0` is the default world, `999` is the world used by Electrons `contextIsolation` feature. You can provide any integer here.
|
* `worldId` Integer - The ID of the world to run the javascript in, `0` is the default world, `999` is the world used by Electrons `contextIsolation` feature. Chrome extensions reserve the range of IDs in `[1 << 20, 1 << 29)`. You can provide any integer here.
|
||||||
* `info` Object
|
* `info` Object
|
||||||
* `securityOrigin` String (optional) - Security origin for the isolated world.
|
* `securityOrigin` String (optional) - Security origin for the isolated world.
|
||||||
* `csp` String (optional) - Content Security Policy for the isolated world.
|
* `csp` String (optional) - Content Security Policy for the isolated world.
|
||||||
|
|
35
lib/content_script/init.js
Normal file
35
lib/content_script/init.js
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
'use strict'
|
||||||
|
|
||||||
|
/* global nodeProcess, isolatedWorld, worldId */
|
||||||
|
|
||||||
|
const { EventEmitter } = require('events')
|
||||||
|
|
||||||
|
process.atomBinding = require('@electron/internal/common/atom-binding-setup').atomBindingSetup(nodeProcess.binding, 'renderer')
|
||||||
|
|
||||||
|
const v8Util = process.atomBinding('v8_util')
|
||||||
|
// The `lib/renderer/ipc-renderer-internal.js` module looks for the ipc object in the
|
||||||
|
// "ipc-internal" hidden value
|
||||||
|
v8Util.setHiddenValue(global, 'ipc-internal', new EventEmitter())
|
||||||
|
// The process object created by browserify is not an event emitter, fix it so
|
||||||
|
// the API is more compatible with non-sandboxed renderers.
|
||||||
|
for (const prop of Object.keys(EventEmitter.prototype)) {
|
||||||
|
if (process.hasOwnProperty(prop)) {
|
||||||
|
delete process[prop]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Object.setPrototypeOf(process, EventEmitter.prototype)
|
||||||
|
|
||||||
|
const isolatedWorldArgs = v8Util.getHiddenValue(isolatedWorld, 'isolated-world-args')
|
||||||
|
|
||||||
|
if (isolatedWorldArgs) {
|
||||||
|
const { ipcRendererInternal, guestInstanceId, isHiddenPage, openerId, usesNativeWindowOpen } = isolatedWorldArgs
|
||||||
|
const { windowSetup } = require('@electron/internal/renderer/window-setup')
|
||||||
|
windowSetup(ipcRendererInternal, guestInstanceId, openerId, isHiddenPage, usesNativeWindowOpen)
|
||||||
|
}
|
||||||
|
|
||||||
|
const extensionId = v8Util.getHiddenValue(isolatedWorld, `extension-${worldId}`)
|
||||||
|
|
||||||
|
if (extensionId) {
|
||||||
|
const chromeAPI = require('@electron/internal/renderer/chrome-api')
|
||||||
|
chromeAPI.injectTo(extensionId, false, window)
|
||||||
|
}
|
|
@ -1,5 +1,24 @@
|
||||||
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
|
import { ipcRendererInternal } from '@electron/internal/renderer/ipc-renderer-internal'
|
||||||
import { runInThisContext } from 'vm'
|
import { webFrame } from 'electron'
|
||||||
|
|
||||||
|
const v8Util = process.atomBinding('v8_util')
|
||||||
|
|
||||||
|
const IsolatedWorldIDs = {
|
||||||
|
/**
|
||||||
|
* Start of extension isolated world IDs, as defined in
|
||||||
|
* atom_render_frame_observer.h
|
||||||
|
*/
|
||||||
|
ISOLATED_WORLD_EXTENSIONS: 1 << 20
|
||||||
|
}
|
||||||
|
|
||||||
|
let isolatedWorldIds = IsolatedWorldIDs.ISOLATED_WORLD_EXTENSIONS
|
||||||
|
const extensionWorldId: {[key: string]: number | undefined} = {}
|
||||||
|
|
||||||
|
// https://cs.chromium.org/chromium/src/extensions/renderer/script_injection.cc?type=cs&sq=package:chromium&g=0&l=52
|
||||||
|
const getIsolatedWorldIdForInstance = () => {
|
||||||
|
// TODO(samuelmaddock): allocate and cleanup IDs
|
||||||
|
return isolatedWorldIds++
|
||||||
|
}
|
||||||
|
|
||||||
// Check whether pattern matches.
|
// Check whether pattern matches.
|
||||||
// https://developer.chrome.com/extensions/match_patterns
|
// https://developer.chrome.com/extensions/match_patterns
|
||||||
|
@ -12,21 +31,21 @@ const matchesPattern = function (pattern: string) {
|
||||||
|
|
||||||
// Run the code with chrome API integrated.
|
// Run the code with chrome API integrated.
|
||||||
const runContentScript = function (this: any, extensionId: string, url: string, code: string) {
|
const runContentScript = function (this: any, extensionId: string, url: string, code: string) {
|
||||||
const context: { chrome?: any } = {}
|
// Assign unique world ID to each extension
|
||||||
require('@electron/internal/renderer/chrome-api').injectTo(extensionId, false, context)
|
const worldId = extensionWorldId[extensionId] ||
|
||||||
const wrapper = `((chrome) => {\n ${code}\n })`
|
(extensionWorldId[extensionId] = getIsolatedWorldIdForInstance())
|
||||||
try {
|
|
||||||
const compiledWrapper = runInThisContext(wrapper, {
|
// store extension ID for content script to read in isolated world
|
||||||
filename: url,
|
v8Util.setHiddenValue(global, `extension-${worldId}`, extensionId)
|
||||||
lineOffset: 1,
|
|
||||||
displayErrors: true
|
webFrame.setIsolatedWorldInfo(worldId, {
|
||||||
})
|
name: `${extensionId} [${worldId}]`
|
||||||
return compiledWrapper.call(this, context.chrome)
|
// TODO(samuelmaddock): read `content_security_policy` from extension manifest
|
||||||
} catch (error) {
|
// csp: manifest.content_security_policy,
|
||||||
// TODO(samuelmaddock): Run scripts in isolated world, see chromium script_injection.cc
|
})
|
||||||
console.error(`Error running content script JavaScript for '${extensionId}'`)
|
|
||||||
console.error(error)
|
const sources = [{ code, url }]
|
||||||
}
|
webFrame.executeJavaScriptInIsolatedWorld(worldId, sources)
|
||||||
}
|
}
|
||||||
|
|
||||||
const runAllContentScript = function (scripts: Array<Electron.InjectionBase>, extensionId: string) {
|
const runAllContentScript = function (scripts: Array<Electron.InjectionBase>, extensionId: string) {
|
||||||
|
@ -36,28 +55,7 @@ const runAllContentScript = function (scripts: Array<Electron.InjectionBase>, ex
|
||||||
}
|
}
|
||||||
|
|
||||||
const runStylesheet = function (this: any, url: string, code: string) {
|
const runStylesheet = function (this: any, url: string, code: string) {
|
||||||
const wrapper = `((code) => {
|
webFrame.insertCSS(code)
|
||||||
function init() {
|
|
||||||
const styleElement = document.createElement('style');
|
|
||||||
styleElement.textContent = code;
|
|
||||||
document.head.append(styleElement);
|
|
||||||
}
|
|
||||||
document.addEventListener('DOMContentLoaded', init);
|
|
||||||
})`
|
|
||||||
|
|
||||||
try {
|
|
||||||
const compiledWrapper = runInThisContext(wrapper, {
|
|
||||||
filename: url,
|
|
||||||
lineOffset: 1,
|
|
||||||
displayErrors: true
|
|
||||||
})
|
|
||||||
|
|
||||||
return compiledWrapper.call(this, code)
|
|
||||||
} catch (error) {
|
|
||||||
// TODO(samuelmaddock): Insert stylesheet directly into document, see chromium script_injection.cc
|
|
||||||
console.error(`Error inserting content script stylesheet ${url}`)
|
|
||||||
console.error(error)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const runAllStylesheet = function (css: Array<Electron.InjectionBase>) {
|
const runAllStylesheet = function (css: Array<Electron.InjectionBase>) {
|
||||||
|
|
Loading…
Reference in a new issue