一,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)); }