electron中重写js 内置函数 需要重新内核编译
一,electron用typescript实现了函数。比如重写了 windows.history.back go等js 内置函数。不走blink的js binding。
D:\dev\electron7\src\electron\lib\renderer\window-setup.ts
window.open
if (!usesNativeWindowOpen) { // TODO(MarshallOfSound): Make compatible with ctx isolation without hole-punch // Make the browser window or guest view emit "new-window" event. (window as any).open = function (url?: string, frameName?: string, features?: string) { if (url != null && url !== '') { url = resolveURL(url, location.href); } const guestId = ipcRendererInternal.sendSync('ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', url, toString(frameName), toString(features)); if (guestId != null) { return getOrCreateProxy(guestId); } else { return null; } }; if (shouldUseContextBridge) internalContextBridge.overrideGlobalValueWithDynamicPropsFromIsolatedWorld(['open'], window.open); }
history接口实现
window.history.forward = function () { ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_FORWARD'); }; if (shouldUseContextBridge) internalContextBridge.overrideGlobalValueFromIsolatedWorld(['history', 'forward'], window.history.forward); window.history.go = function (offset: number) { ipcRendererInternal.send('ELECTRON_NAVIGATION_CONTROLLER_GO_TO_OFFSET', +offset); }; if (shouldUseContextBridge) internalContextBridge.overrideGlobalValueFromIsolatedWorld(['history', 'go'], window.history.go); const getHistoryLength = () => ipcRendererInternal.sendSync('ELECTRON_NAVIGATION_CONTROLLER_LENGTH'); Object.defineProperty(window.history, 'length', { get: getHistoryLength, set () {} });
在browser进程中接收到back事件,真正做back的又传回给了chromium内核,通过web contents的loadURL接口。
D:\dev\electron7\src\electron\lib\browser\navigation-controller.js 'use strict'; const { ipcMainInternal } = require('@electron/internal/browser/ipc-main-internal'); // The history operation in renderer is redirected to browser. ipcMainInternal.on('ELECTRON_NAVIGATION_CONTROLLER_GO_BACK', function (event) { event.sender.goBack(); });
event.sender是谁:D:\dev\electron7\src\electron\lib\browser\navigation-controller.js
NavigationController.prototype.goBack = function () { if (!this.canGoBack()) { return; } this.pendingIndex = this.getActiveIndex() - 1; if (this.inPageIndex > -1 && this.pendingIndex >= this.inPageIndex) { return this.webContents._goBack(); } else { return this.webContents._loadURL(this.history[this.pendingIndex], {}); //调用electron的绑定的webContents } };
声明:
export const syncMethods = new Set([ 'getURL', 'getTitle', 'isLoading', 'isLoadingMainFrame', 'isWaitingForResponse', 'stop', 'reload', 'reloadIgnoringCache', 'canGoBack', 'canGoForward', 'canGoToOffset', 'clearHistory', 'goBack',
_loadURL的绑定定义在:D:\dev\electron7\src\electron\shell\browser\api\atom_api_web_contents.cc
// static void WebContents::BuildPrototype(v8::Isolate* isolate, v8::Local<v8::FunctionTemplate> prototype) { prototype->SetClassName(mate::StringToV8(isolate, "WebContents")); mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) .MakeDestroyable() .SetMethod("setBackgroundThrottling", &WebContents::SetBackgroundThrottling) .SetMethod("getProcessId", &WebContents::GetProcessID) .SetMethod("getOSProcessId", &WebContents::GetOSProcessID) .SetMethod("_getOSProcessIdForFrame", &WebContents::GetOSProcessIdForFrame) .SetMethod("equal", &WebContents::Equal) .SetMethod("_loadURL", &WebContents::LoadURL)
这里在同文件中又实现:
void WebContents::LoadURL(const GURL& url, const mate::Dictionary& options) { if (!url.is_valid() || url.spec().size() > url::kMaxURLChars) { Emit("did-fail-load", static_cast<int>(net::ERR_INVALID_URL), net::ErrorToShortString(net::ERR_INVALID_URL), url.possibly_invalid_spec(), true); return; } content::NavigationController::LoadURLParams params(url); if (!options.Get("httpReferrer", ¶ms.referrer)) { GURL http_referrer; if (options.Get("httpReferrer", &http_referrer)) params.referrer = content::Referrer(http_referrer.GetAsReferrer(), network::mojom::ReferrerPolicy::kDefault); } std::string user_agent; if (options.Get("userAgent", &user_agent)) web_contents()->SetUserAgentOverride(user_agent, false); std::string extra_headers; if (options.Get("extraHeaders", &extra_headers)) params.extra_headers = extra_headers; scoped_refptr<network::ResourceRequestBody> body; if (options.Get("postData", &body)) { params.post_data = body; params.load_type = content::NavigationController::LOAD_TYPE_HTTP_POST; } GURL base_url_for_data_url; if (options.Get("baseURLForDataURL", &base_url_for_data_url)) { params.base_url_for_data_url = base_url_for_data_url; params.load_type = content::NavigationController::LOAD_TYPE_DATA; } bool reload_ignoring_cache = false; if (options.Get("reloadIgnoringCache", &reload_ignoring_cache) && reload_ignoring_cache) { params.reload_type = content::ReloadType::BYPASSING_CACHE; } params.transition_type = ui::PAGE_TRANSITION_TYPED; params.should_clear_history_list = true; params.override_user_agent = content::NavigationController::UA_OVERRIDE_TRUE; // Discord non-committed entries to ensure that we don't re-use a pending // entry web_contents()->GetController().DiscardNonCommittedEntries(); web_contents()->GetController().LoadURLWithParams(params);//这里到跑了下面位置:
//D:\dev\electron7\src\content\browser\frame_host\navigation_controller_impl.cc
// void NavigationControllerImpl::LoadURLWithParams(const LoadURLParams& params) // Set the background color of RenderWidgetHostView. // We have to call it right after LoadURL because the RenderViewHost is only // created after loading a page. auto* const view = web_contents()->GetRenderWidgetHostView(); if (view) { auto* web_preferences = WebContentsPreferences::From(web_contents()); std::string color_name; if (web_preferences->GetPreference(options::kBackgroundColor, &color_name)) { view->SetBackgroundColor(ParseHexColor(color_name)); } else { view->SetBackgroundColor(SK_ColorTRANSPARENT); } } }
跑到了内核的
web_contents()->GetController().LoadURLWithParams(params);
二、window.open的测试代码。其中调用了内部ipc,用js找到了ipcRenderer通道。spec里面是测试代码。
w.webContents.executeJavaScript
D:\dev\electron7\src\electron\spec-main\chromium-spec.ts
describe('window.open', () => { it('denies custom open when nativeWindowOpen: true', async () => { const w = new BrowserWindow({ show: false, webPreferences: { contextIsolation: false, nodeIntegration: true, nativeWindowOpen: true } }); w.loadURL('about:blank'); const previousListeners = process.listeners('uncaughtException'); process.removeAllListeners('uncaughtException'); try { const uncaughtException = new Promise<Error>(resolve => { process.once('uncaughtException', resolve); }); expect(await w.webContents.executeJavaScript(`(${function () { const ipc = process.electronBinding('ipc').ipc; return ipc.sendSync(true, 'ELECTRON_GUEST_WINDOW_MANAGER_WINDOW_OPEN', ['', '', ''])[0]; }})()`)).to.be.null('null'); const exception = await uncaughtException; expect(exception.message).to.match(/denied: expected native window\.open/); } finally { previousListeners.forEach(l => process.on('uncaughtException', l)); } }); }); })
三、electron定义自定义的接口:D:\dev\electron7\src\electron\electron.d.ts 如IpcRenderer, ipcMain

interface IpcRenderer extends NodeJS.EventEmitter { // Docs: http://electronjs.org/docs\api\ipc-renderer /** * Resolves with the response from the main process. * * Send a message to the main process asynchronously via `channel` and expect an * asynchronous result. Arguments will be serialized as JSON internally and hence * no functions or prototype chain will be included. * * The main process should listen for `channel` with `ipcMain.handle()`. * For example: */ invoke(channel: string, ...args: any[]): Promise<any>; /** * Listens to `channel`, when a new message arrives `listener` would be called with * `listener(event, args...)`. */ on(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this; /** * Adds a one time `listener` function for the event. This `listener` is invoked * only the next time a message is sent to `channel`, after which it is removed. */ once(channel: string, listener: (event: IpcRendererEvent, ...args: any[]) => void): this; /** * Removes all listeners, or those of the specified `channel`. */ removeAllListeners(channel: string): this; /** * Removes the specified `listener` from the listener array for the specified * `channel`. */ removeListener(channel: string, listener: (...args: any[]) => void): this; /** * Send a message to the main process asynchronously via `channel`, you can also * send arbitrary arguments. Arguments will be serialized as JSON internally and * hence no functions or prototype chain will be included. * * The main process handles it by listening for `channel` with the `ipcMain` * module. */ send(channel: string, ...args: any[]): void; /** * The value sent back by the `ipcMain` handler. * * Send a message to the main process synchronously via `channel`, you can also * send arbitrary arguments. Arguments will be serialized in JSON internally and * hence no functions or prototype chain will be included. * * The main process handles it by listening for `channel` with `ipcMain` module, * and replies by setting `event.returnValue`. * * **Note:** Sending a synchronous message will block the whole renderer process, * unless you know what you are doing you should never use it. */ sendSync(channel: string, ...args: any[]): any; sendSyncEx(channel: string, ...args: any[]): any; /** * Sends a message to a window with `webContentsId` via `channel`. */ sendTo(webContentsId: number, channel: string, ...args: any[]): void; /** * Like `ipcRenderer.send` but the event will be sent to the `<webview>` element in * the host page instead of the main process. */ sendToHost(channel: string, ...args: any[]): void; }
这个electron.d.ts声明是自动生成的。electron会根据json格式的说明文档,自己分析生成。
比如ipcRenderer的文档在:D:\dev\electron7\src\electron\docs\api\ipc-renderer.md,在文档中添加新的方法声明:
### `ipcRenderer.sendSyncEx(channel, ...args)` * `channel` String * `...args` any[] Returns `any` - The value sent back by the [`ipcMain`](ipc-main.md) handler. Send a message to the main process synchronously via `channel`, you can also send arbitrary arguments. Arguments will be serialized in JSON internally and hence no functions or prototype chain will be included. The main process handles it by listening for `channel` with [`ipcMain`](ipc-main.md) module, and replies by setting `event.returnValue`. **Note:** Sending a synchronous message will block the whole renderer process, unless you know what you are doing you should never use it.
electron借助doc parser(https://github.com/electron/docs-parser),生成,filenames.auto.gni ,electron-api.json。
借助构建脚本 electron/build.gn
# We geneate the definitions twice here, once in //electron/electron.d.ts # and once in $target_gen_dir # The one in $target_gen_dir is used for the actual TSC build later one # and the one in //electron/electron.d.ts is used by your IDE (vscode) # for typescript prompting npm_action("build_electron_definitions") { script = "gn-typescript-definitions" args = [ rebase_path("$target_gen_dir/tsc/typings/electron.d.ts") ] inputs = auto_filenames.api_docs + [ "yarn.lock" ] outputs = [ "$target_gen_dir/tsc/typings/electron.d.ts", ] }
根据输入文件 inputs = auto_filenames.api_docs
它找的是filename.auto.gni 里面的定义的模块,最终生成
electron.d.ts
https://github.com/electron/typescript-definitions
四、ipcRendererInternal 是electron内部的消息传递,它和对外提供的 其实用的一个ipc通道,通过第一个参数internal的boolean值控制是否发往外部。
D:\dev\electron7\src\electron\lib\renderer\ipc-renderer-internal.ts
const binding = process.electronBinding('ipc'); const v8Util = process.electronBinding('v8_util'); // Created by init.js. export const ipcRendererInternal: Electron.IpcRendererInternal = v8Util.getHiddenValue(global, 'ipc-internal'); const internal = true; if (!ipcRendererInternal.send) { ipcRendererInternal.send = function (channel, ...args) { return binding.ipc.send(internal, channel, args); }; ipcRendererInternal.sendSync = function (channel, ...args) { return binding.ipc.sendSync(internal, channel, args)[0]; }; ipcRendererInternal.sendSyncEx = function (channel, ...args) { return binding.ipc.sendSync(false, channel, args)[0]; }; ipcRendererInternal.sendTo = function (webContentsId, channel, ...args) { return binding.ipc.sendTo(internal, false, webContentsId, channel, args); }; ipcRendererInternal.sendToAll = function (webContentsId, channel, ...args) { return binding.ipc.sendTo(internal, true, webContentsId, channel, args); }; }
供外部的ipc调用的实现
D:\dev\electron7\src\electron\lib\renderer\api\ipc-renderer.js
'use strict'; const { ipc } = process.electronBinding('ipc'); const v8Util = process.electronBinding('v8_util'); // Created by init.js. const ipcRenderer = v8Util.getHiddenValue(global, 'ipc');//都来源于ipc const internal = false; //发送时的标志,不是发往内部 if (!ipcRenderer.send) { ipcRenderer.send = function (channel, ...args) { return ipc.send(internal, channel, args); }; ipcRenderer.sendSync = function (channel, ...args) { const result = ipc.sendSync(internal, channel, args); if (!Array.isArray(result) || result.length !== 1) { throw new Error(`Unexpected return value from ipcRenderer.sendSync: ${result}`); } return result[0]; }; ipcRenderer.sendToHost = function (channel, ...args) { return ipc.sendToHost(channel, args); }; ipcRenderer.sendTo = function (webContentsId, channel, ...args) { return ipc.sendTo(internal, false, webContentsId, channel, args); }; ipcRenderer.sendToAll = function (webContentsId, channel, ...args) { return ipc.sendTo(internal, true, webContentsId, channel, args); }; ipcRenderer.invoke = function (channel, ...args) { return ipc.invoke(channel, args).then(({ error, result }) => { if (error) { throw new Error(`Error invoking remote method '${channel}': ${error}`); } return result; }); }; } module.exports = ipcRenderer; //可以看到倒出给了外部。
electron/internal/browser/ipc-main-internal
const webContentsMethods = new Set([ 'getURL', 'loadURL', 'executeJavaScript', 'print' ]);
ipc的c++实现:D:\dev\electron7\src\electron\shell\renderer\api\atom_api_renderer_ipc.cc
static void BuildPrototype(v8::Isolate* isolate, v8::Local<v8::FunctionTemplate> prototype) { prototype->SetClassName(mate::StringToV8(isolate, "IPCRenderer")); mate::ObjectTemplateBuilder(isolate, prototype->PrototypeTemplate()) .SetMethod("send", &IPCRenderer::Send) .SetMethod("sendSync", &IPCRenderer::SendSync) .SetMethod("sendTo", &IPCRenderer::SendTo) .SetMethod("sendToHost", &IPCRenderer::SendToHost) .SetMethod("invoke", &IPCRenderer::Invoke); } ...... base::Value SendSync(bool internal, const std::string& channel, base::ListValue arguments) { base::ListValue result; electron_browser_ptr_->MessageSync(internal, channel, std::move(arguments), &result); if (!result.is_list() || result.GetList().empty()) return base::Value{}; return std::move(result.GetList().at(0)); }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
2018-08-07 Nginx实践篇(2)- Nginx作为静态资源web服务 - 控制浏览器缓存、防盗链
2015-08-07 udp 内网穿透 互发消息
2015-08-07 p2p软件如何穿透内网进行通信
2013-08-07 SDL OPENGL 在linux ubuntu示例
2013-08-07 SDL源码阅读笔记(2) video dirver的初始化及选择
2013-08-07 SDL源码阅读笔记(1) 基本模块
2012-08-07 sdcard中的文件们