zoukankan      html  css  js  c++  java
  • electron中重写js 内置函数 需要重新内核编译

    一,electron用typescript实现了函数。比如重写了 windows.history.back go等js 内置函数。不走blink的js binding。

     D:develectron7srcelectronlib endererwindow-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:develectron7srcelectronlibrowser
    avigation-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:develectron7srcelectronlibrowser
    avigation-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:develectron7srcelectronshellrowserapiatom_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:develectron7srccontentrowserframe_host avigation_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:develectron7srcelectronspec-mainchromium-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:develectron7srcelectronelectron.d.ts 如IpcRenderer, ipcMain

      interface IpcRenderer extends NodeJS.EventEmitter {
    
        // Docs: http://electronjs.org/docsapiipc-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:develectron7srcelectrondocsapiipc-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:develectron7srcelectronlib endereripc-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:develectron7srcelectronlib endererapiipc-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:develectron7srcelectronshell endererapiatom_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));
      }
  • 相关阅读:
    vm选项大全
    the.book.of.gimp.pdf文字不显示
    我为什么在这里随笔记下这些,因为这些东西经常反复的忘记,有时候就连为什么要在这里随便都忘了,所以也把它随笔记下来。
    emacs search, 讲的很清楚。
    请教问题时,经常不会说的一些英语,
    dpkg -P xx
    sources.list修改后安装报错
    RDD、DataFrame和DataSet比较
    Cloud foundry基础
    Cloudify基本介绍
  • 原文地址:https://www.cnblogs.com/bigben0123/p/13453243.html
Copyright © 2011-2022 走看看