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", &params.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;
  }
View Code
复制代码

 这个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));
  }
复制代码

 

posted @   Bigben  阅读(1366)  评论(0编辑  收藏  举报
编辑推荐:
· 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中的文件们
点击右上角即可分享
微信分享提示