Skip to content

[BUG][Electron] Page is not a BrowserWindow #6760

@samuelmaddock

Description

@samuelmaddock

Context:

## System:
 - OS: Windows 10 10.0.19042
 - Memory: 16.79 GB / 31.91 GB
## Binaries:
 - Node: 14.16.1 - C:\Program Files\nodejs\node.EXE
 - Yarn: 1.19.1 - D:\Program Files (x86)\Yarn\bin\yarn.CMD
 - npm: 7.11.2 - C:\Program Files\nodejs\npm.CMD
## Languages:
 - Bash: 4.4.23 - C:\Program Files\Git\usr\bin\bash.EXE
## npmPackages:
 - playwright: ^1.11.1 => 1.11.1

Code Snippet

Creates two Pages: one for the BrowserWindow's WebContents and one for the BrowserView's WebContents.

const { _electron: electron } = require('playwright');

(async () => {
  const app = await electron.launch();

  app.on('window', async page => {
    console.log('new window page', page.url())
  });

  await app.evaluate((electron) => {
    const win = electron.BrowserWindow.getAllWindows()[0]
    const view = new electron.BrowserView()
    view.setBounds({ x: 0, y: 0, width: 300, height: 300 })
    view.webContents.loadURL('https://www.electronjs.org/docs/api/browser-view')
    win.addBrowserView(view)
  });
})();

Describe the bug

Playwright is conflating Electron's BrowserWindow abstraction as an equivalent to Pages. However, it's possible for Electron to have multiple WebContents per BrowserWindow.

In the method below, a new Page will increment the BrowserWindow counter.

private _onPage(page: Page) {
// Needs to be sync.
const windowId = ++this._lastWindowId;
(page as any)._browserWindowId = windowId;
}

async browserWindow(page: Page): Promise<js.JSHandle<BrowserWindow>> {
const electronHandle = await this._nodeElectronHandlePromise;
return await electronHandle.evaluateHandle(({ BrowserWindow }, windowId) => BrowserWindow.fromId(windowId), (page as any)._browserWindowId);
}

This ID counter will become out of sync when the code snippet above is run. I also suspect this ID becoming out of sync is leading to the bug described in #6748.


I believe that Playwright might want to redesign how it stores and retrieves BrowserWindow objects from Electron. Based on the info received from Chrome DevTools Protocol, I suspect the TargetID might be the most stable identifier which could be used.

With some contributions upstream, I could see the TargetID being used to get the associated WebContents, which could then be used to get the owning BrowserWindow.

I'd be open to helping make this happen if it makes sense. Possible new APIs could be defined as followed:

interface WebContents {
  static fromDevToolsTargetId(targetId: string): WebContents;
}

Then getting the BrowserWindow could be done by calling BrowserWindow.fromWebContents(webContents).

// playwright/src/server/electron/electron.ts
async browserWindow(page: Page): Promise<js.JSHandle<BrowserWindow>> {
  const targetId = (page as any)._delegate._targetId;
  const electronHandle = await this._nodeElectronHandlePromise;
  return await electronHandle.evaluateHandle(({ BrowserWindow, webContents }, targetId) => {
    const wc = webContents.fromDevToolsTargetId(targetId)
    return BrowserWindow.fromWebContents(wc)
  }, targetId);
}

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions