一,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

相关文章:

  • 2022-01-15
  • 2022-12-23
  • 2021-06-20
  • 2021-06-23
  • 2021-10-07
  • 2021-11-27
  • 2021-05-15
  • 2022-12-23
猜你喜欢
  • 2022-02-19
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2021-10-02
  • 2021-11-11
  • 2021-11-01
相关资源
相似解决方案