fix: use OOPIF for webview tag (#13869)

* fix: use OOIF for webview tag

* fix: do not call GetNativeView for webview

* fix: OOIPF webview's WebContents is managed by embedder frame

* fix: guest view can not be focused

* fix: clear zoom controller when guest is destroyed

* fix: implement the webview resize event

The webview is no longer a browser plugin with the resize event, use
ResizeObserver instead.

* test: disable failed tests due to OOPIF webview

* fix: embedder can be destroyed earlier than guest

This happens when embedder is manually destroyed.

* fix: don't double attach

* fix: recreate iframe when webview is reattached

* fix: resize event may happen very early

* test: some tests are working after OOPIF webview

* chore: remove unused browser plugin webview code

* fix: get embedder via closure

When the "destroyed" event is emitted, the entry in guestInstances would be
cleared.

* chore: rename browserPluginNode to internalElement

* test: make the visibilityState test more robust

* chore: guestinstance can not work with OOPIF webview

* fix: element could be detached before got response from browser
This commit is contained in:
Cheng Zhao 2018-08-16 15:57:40 -07:00 committed by Charles Kerr
parent 48407c5b93
commit dd5b8769be
28 changed files with 268 additions and 1008 deletions

View file

@ -5,7 +5,7 @@ const path = require('path')
const http = require('http')
const url = require('url')
const {ipcRenderer, remote} = require('electron')
const {app, session, getGuestWebContents, ipcMain, BrowserWindow, webContents} = remote
const {app, session, ipcMain, BrowserWindow} = remote
const {closeWindow} = require('./window-helpers')
const {emittedOnce, waitForEvent} = require('./events-helpers')
@ -64,8 +64,7 @@ describe('<webview> tag', function () {
return closeTheWindow()
})
// TODO(codebytere): re-enable this test
xit('works without script tag in page', async () => {
it('works without script tag in page', async () => {
const w = await openTheWindow({show: false})
w.loadURL('file://' + fixtures + '/pages/webview-no-script.html')
await emittedOnce(ipcMain, 'pong')
@ -693,7 +692,8 @@ describe('<webview> tag', function () {
})
})
describe('setDevToolsWebContents() API', () => {
// FIXME(zcbenz): Disabled because of moving to OOPIF webview.
xdescribe('setDevToolsWebContents() API', () => {
it('sets webContents of webview as devtools', async () => {
const webview2 = new WebView()
loadWebView(webview2)
@ -990,9 +990,7 @@ describe('<webview> tag', function () {
})
})
// TODO(jkleinsc): this test causes the test suite to hang on Windows release
// builds. Temporarily disabling so that release build tests will finish.
xdescribe('found-in-page event', () => {
describe('found-in-page event', () => {
it('emits when a request is made', (done) => {
let requestId = null
let activeMatchOrdinal = []
@ -1149,12 +1147,15 @@ describe('<webview> tag', function () {
it('updates when the window is shown after the ready-to-show event', async () => {
const w = await openTheWindow({ show: false })
const readyToShowSignal = emittedOnce(w, 'ready-to-show')
const pongSignal1 = emittedOnce(ipcMain, 'pong')
w.loadURL(`file://${fixtures}/pages/webview-visibilitychange.html`)
await emittedOnce(w, 'ready-to-show')
await pongSignal1
const pongSignal2 = emittedOnce(ipcMain, 'pong')
await readyToShowSignal
w.show()
const [, visibilityState, hidden] = await emittedOnce(ipcMain, 'pong')
const [, visibilityState, hidden] = await pongSignal2
assert(!hidden)
assert.equal(visibilityState, 'visible')
})
@ -1255,232 +1256,6 @@ describe('<webview> tag', function () {
expect(tabId).to.be.not.equal(w.webContents.id)
})
// TODO(alexeykuzmin): Some tests rashe a renderer process.
// Fix them and enable the tests.
xdescribe('guestinstance attribute', () => {
it('before loading there is no attribute', () => {
loadWebView(webview) // Don't wait for loading to finish.
assert(!webview.hasAttribute('guestinstance'))
})
it('loading a page sets the guest view', async () => {
await loadWebView(webview, {
src: `file://${fixtures}/api/blank.html`
})
const instance = webview.getAttribute('guestinstance')
assert.equal(instance, parseInt(instance))
const guest = getGuestWebContents(parseInt(instance))
assert.equal(guest, webview.getWebContents())
})
it('deleting the attribute destroys the webview', async () => {
await loadWebView(webview, {
src: `file://${fixtures}/api/blank.html`
})
const instance = parseInt(webview.getAttribute('guestinstance'))
const waitForDestroy = waitForEvent(webview, 'destroyed')
webview.removeAttribute('guestinstance')
await waitForDestroy
expect(getGuestWebContents(instance)).to.equal(undefined)
})
it('setting the attribute on a new webview moves the contents', (done) => {
const loadListener = () => {
const webContents = webview.getWebContents()
const instance = webview.getAttribute('guestinstance')
const destroyListener = () => {
assert.equal(webContents, webview2.getWebContents())
// Make sure that events are hooked up to the right webview now
webview2.addEventListener('console-message', (e) => {
assert.equal(e.message, 'a')
document.body.removeChild(webview2)
done()
})
webview2.src = `file://${fixtures}/pages/a.html`
}
webview.addEventListener('destroyed', destroyListener, {once: true})
const webview2 = new WebView()
loadWebView(webview2, {
guestinstance: instance
})
}
webview.addEventListener('did-finish-load', loadListener, {once: true})
loadWebView(webview, {
src: `file://${fixtures}/api/blank.html`
})
})
it('setting the attribute to an invalid guestinstance does nothing', async () => {
await loadWebView(webview, {
src: `file://${fixtures}/api/blank.html`
})
webview.setAttribute('guestinstance', 55)
// Make sure that events are still hooked up to the webview
const waitForMessage = waitForEvent(webview, 'console-message')
webview.src = `file://${fixtures}/pages/a.html`
const {message} = await waitForMessage
assert.equal(message, 'a')
})
it('setting the attribute on an existing webview moves the contents', (done) => {
const load1Listener = () => {
const webContents = webview.getWebContents()
const instance = webview.getAttribute('guestinstance')
let destroyedInstance
const destroyListener = () => {
assert.equal(webContents, webview2.getWebContents())
assert.equal(null, getGuestWebContents(parseInt(destroyedInstance)))
// Make sure that events are hooked up to the right webview now
webview2.addEventListener('console-message', (e) => {
assert.equal(e.message, 'a')
document.body.removeChild(webview2)
done()
})
webview2.src = 'file://' + fixtures + '/pages/a.html'
}
webview.addEventListener('destroyed', destroyListener, {once: true})
const webview2 = new WebView()
const load2Listener = () => {
destroyedInstance = webview2.getAttribute('guestinstance')
assert.notEqual(instance, destroyedInstance)
webview2.setAttribute('guestinstance', instance)
}
webview2.addEventListener('did-finish-load', load2Listener, {once: true})
loadWebView(webview2, {
src: `file://${fixtures}/api/blank.html`
})
}
webview.addEventListener('did-finish-load', load1Listener, {once: true})
loadWebView(webview, {
src: `file://${fixtures}/api/blank.html`
})
})
it('moving a guest back to its original webview should work', (done) => {
loadWebView(webview, {
src: `file://${fixtures}/api/blank.html`
}).then(() => {
const webContents = webview.getWebContents()
const instance = webview.getAttribute('guestinstance')
const destroy1Listener = () => {
assert.equal(webContents, webview2.getWebContents())
assert.notStrictEqual(webContents, webview.getWebContents())
const destroy2Listener = () => {
assert.equal(webContents, webview.getWebContents())
assert.notStrictEqual(webContents, webview2.getWebContents())
// Make sure that events are hooked up to the right webview now
webview.addEventListener('console-message', (e) => {
document.body.removeChild(webview2)
assert.equal(e.message, 'a')
done()
})
webview.src = `file://${fixtures}/pages/a.html`
}
webview2.addEventListener('destroyed', destroy2Listener, {once: true})
webview.setAttribute('guestinstance', instance)
}
webview.addEventListener('destroyed', destroy1Listener, {once: true})
const webview2 = new WebView()
loadWebView(webview2, {guestinstance: instance})
})
})
// FIXME(alexeykuzmin): This test only passes if the previous test ^
// is run alongside.
it('setting the attribute on a webview in a different window moves the contents', (done) => {
loadWebView(webview, {
src: `file://${fixtures}/api/blank.html`
}).then(() => {
const instance = webview.getAttribute('guestinstance')
w = new BrowserWindow({ show: false })
w.webContents.once('did-finish-load', () => {
ipcMain.once('pong', () => {
assert(!webview.hasAttribute('guestinstance'))
done()
})
w.webContents.send('guestinstance', instance)
})
w.loadURL(`file://${fixtures}/pages/webview-move-to-window.html`)
})
})
it('does not delete the guestinstance attribute when moving the webview to another parent node', (done) => {
webview.addEventListener('dom-ready', function domReadyListener () {
webview.addEventListener('did-attach', () => {
assert(webview.guestinstance != null)
assert(webview.getWebContents() != null)
done()
})
document.body.replaceChild(webview, div)
})
webview.src = `file://${fixtures}/pages/a.html`
const div = document.createElement('div')
div.appendChild(webview)
document.body.appendChild(div)
})
it('does not destroy the webContents when hiding/showing the webview (regression)', (done) => {
webview.addEventListener('dom-ready', function () {
const instance = webview.getAttribute('guestinstance')
assert(instance != null)
// Wait for event directly since attach happens asynchronously over IPC
ipcMain.once('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', () => {
assert(webview.getWebContents() != null)
assert.equal(instance, webview.getAttribute('guestinstance'))
done()
})
webview.style.display = 'none'
webview.offsetHeight // eslint-disable-line
webview.style.display = 'block'
}, {once: true})
loadWebView(webview, {src: `file://${fixtures}/pages/a.html`})
})
it('does not reload the webContents when hiding/showing the webview (regression)', (done) => {
webview.addEventListener('dom-ready', function () {
webview.addEventListener('did-start-loading', () => {
done(new Error('webview started loading unexpectedly'))
})
// Wait for event directly since attach happens asynchronously over IPC
webview.addEventListener('did-attach', () => {
done()
})
webview.style.display = 'none'
webview.offsetHeight // eslint-disable-line
webview.style.display = 'block'
}, {once: true})
loadWebView(webview, {src: `file://${fixtures}/pages/a.html`})
})
})
describe('DOM events', () => {
let div
@ -1498,136 +1273,34 @@ describe('<webview> tag', function () {
})
it('emits resize events', async () => {
const firstResizeSignal = waitForEvent(webview, 'resize')
const domReadySignal = waitForEvent(webview, 'dom-ready')
webview.src = `file://${fixtures}/pages/a.html`
div.appendChild(webview)
document.body.appendChild(div)
const firstResizeEvent = await waitForEvent(webview, 'resize')
const firstResizeEvent = await firstResizeSignal
expect(firstResizeEvent.target).to.equal(webview)
expect(firstResizeEvent.newWidth).to.equal(100)
expect(firstResizeEvent.newHeight).to.equal(10)
await waitForEvent(webview, 'dom-ready')
await domReadySignal
const secondResizeSignal = waitForEvent(webview, 'resize')
const newWidth = 1234
const newHeight = 789
div.style.width = `${newWidth}px`
div.style.height = `${newHeight}px`
const secondResizeEvent = await waitForEvent(webview, 'resize')
const secondResizeEvent = await secondResizeSignal
expect(secondResizeEvent.target).to.equal(webview)
expect(secondResizeEvent.newWidth).to.equal(newWidth)
expect(secondResizeEvent.newHeight).to.equal(newHeight)
})
})
describe('disableguestresize attribute', () => {
it('does not have attribute by default', () => {
loadWebView(webview)
assert(!webview.hasAttribute('disableguestresize'))
})
it('resizes guest when attribute is not present', async () => {
const INITIAL_SIZE = 200
const w = await openTheWindow(
{show: false, width: INITIAL_SIZE, height: INITIAL_SIZE})
w.loadURL(`file://${fixtures}/pages/webview-guest-resize.html`)
await emittedOnce(ipcMain, 'webview-loaded')
const elementResize = emittedOnce(ipcMain, 'webview-element-resize')
.then(([, width, height]) => {
assert.equal(width, CONTENT_SIZE)
assert.equal(height, CONTENT_SIZE)
})
const guestResize = emittedOnce(ipcMain, 'webview-guest-resize')
.then(([, width, height]) => {
assert.equal(width, CONTENT_SIZE)
assert.equal(height, CONTENT_SIZE)
})
const CONTENT_SIZE = 300
assert(CONTENT_SIZE !== INITIAL_SIZE)
w.setContentSize(CONTENT_SIZE, CONTENT_SIZE)
return Promise.all([elementResize, guestResize])
})
// TODO(alexeykuzmin): [Ch66] Enable the test.
xit('does not resize guest when attribute is present', async () => {
const INITIAL_SIZE = 200
const w = await openTheWindow(
{show: false, width: INITIAL_SIZE, height: INITIAL_SIZE})
w.loadURL(`file://${fixtures}/pages/webview-no-guest-resize.html`)
await emittedOnce(ipcMain, 'webview-loaded')
const noGuestResizePromise = Promise.race([
emittedOnce(ipcMain, 'webview-guest-resize'),
new Promise(resolve => setTimeout(() => resolve(), 500))
]).then((eventData = null) => {
if (eventData !== null) {
// Means we got the 'webview-guest-resize' event before the time out.
return Promise.reject(new Error('Unexpected guest resize message'))
}
})
const CONTENT_SIZE = 300
assert(CONTENT_SIZE !== INITIAL_SIZE)
w.setContentSize(CONTENT_SIZE, CONTENT_SIZE)
return noGuestResizePromise
})
it('dispatches element resize event even when attribute is present', async () => {
const INITIAL_SIZE = 200
const w = await openTheWindow(
{show: false, width: INITIAL_SIZE, height: INITIAL_SIZE})
w.loadURL(`file://${fixtures}/pages/webview-no-guest-resize.html`)
await emittedOnce(ipcMain, 'webview-loaded')
const whenElementResized = emittedOnce(ipcMain, 'webview-element-resize')
const CONTENT_SIZE = 300
assert(CONTENT_SIZE !== INITIAL_SIZE)
w.setContentSize(CONTENT_SIZE, CONTENT_SIZE)
const [, width, height] = await whenElementResized
expect(width).to.equal(CONTENT_SIZE)
expect(height).to.equal(CONTENT_SIZE)
})
// TODO(alexeykuzmin): [Ch66] Enable the test.
xit('can be manually resized with setSize even when attribute is present', async () => {
const INITIAL_SIZE = 200
const w = await openTheWindow(
{show: false, width: INITIAL_SIZE, height: INITIAL_SIZE})
w.loadURL(`file://${fixtures}/pages/webview-no-guest-resize.html`)
await emittedOnce(ipcMain, 'webview-loaded')
const GUEST_WIDTH = 10
const GUEST_HEIGHT = 20
const guestResizePromise = emittedOnce(ipcMain, 'webview-guest-resize')
.then(([, width, height]) => {
expect(width).to.be.equal(GUEST_WIDTH)
expect(height).to.be.equal(GUEST_HEIGHT)
})
const wc = webContents.getAllWebContents().find((wc) => {
return wc.hostWebContents &&
wc.hostWebContents.id === w.webContents.id
})
assert(wc)
wc.setSize({
normal: {
width: GUEST_WIDTH,
height: GUEST_HEIGHT
}
})
return guestResizePromise
})
})
describe('zoom behavior', () => {
const zoomScheme = remote.getGlobal('zoomScheme')
const webviewSession = session.fromPartition('webview-temp')