instance ID and allowing moving a guest instance to a new webview.
return embedder_->web_contents();
}
+void WebContents::SetEmbedder(const WebContents* embedder) {
+ if (embedder) {
+ NativeWindow* owner_window = nullptr;
+ auto relay = NativeWindowRelay::FromWebContents(embedder->web_contents());
+ if (relay) {
+ owner_window = relay->window.get();
+ }
+ if (owner_window)
+ SetOwnerWindow(owner_window);
+
+ content::RenderWidgetHostView* rwhv =
+ web_contents()->GetRenderWidgetHostView();
+ if (rwhv) {
+ rwhv->Hide();
+ rwhv->Show();
+ }
+ }
+}
+
v8::Local<v8::Value> WebContents::DevToolsWebContents(v8::Isolate* isolate) {
if (devtools_web_contents_.IsEmpty())
return v8::Null(isolate);
&WebContents::ShowDefinitionForSelection)
.SetMethod("copyImageAt", &WebContents::CopyImageAt)
.SetMethod("capturePage", &WebContents::CapturePage)
+ .SetMethod("setEmbedder", &WebContents::SetEmbedder)
.SetProperty("id", &WebContents::ID)
.SetProperty("session", &WebContents::Session)
.SetProperty("hostWebContents", &WebContents::HostWebContents)
void SetAudioMuted(bool muted);
bool IsAudioMuted();
void Print(mate::Arguments* args);
+ void SetEmbedder(const WebContents* embedder);
// Print current page as PDF.
void PrintToPDF(const base::DictionaryValue& setting,
content::RenderFrame::FromWebFrame(web_frame_)->AttachGuest(id);
}
+void WebFrame::DetachGuest(int id) {
+ content::RenderFrame::FromWebFrame(web_frame_)->DetachGuest(id);
+}
+
void WebFrame::SetSpellCheckProvider(mate::Arguments* args,
const std::string& language,
bool auto_spell_correct_turned_on,
.SetMethod("registerElementResizeCallback",
&WebFrame::RegisterElementResizeCallback)
.SetMethod("attachGuest", &WebFrame::AttachGuest)
+ .SetMethod("detachGuest", &WebFrame::DetachGuest)
.SetMethod("setSpellCheckProvider", &WebFrame::SetSpellCheckProvider)
.SetMethod("registerURLSchemeAsSecure",
&WebFrame::RegisterURLSchemeAsSecure)
int element_instance_id,
const GuestViewContainer::ResizeCallback& callback);
void AttachGuest(int element_instance_id);
+ void DetachGuest(int element_instance_id);
// Set the provider that will be used by SpellCheckClient for spell check.
void SetSpellCheckProvider(mate::Arguments* args,
The full list of supported feature strings can be found in the
[RuntimeEnabledFeatures.in][blink-feature-string] file.
+### `guestinstance`
+
+```html
+<webview src="https://www.github.com/" guestinstance="3"></webview>
+```
+
+A value that links the webview to a specific webContents. When a webview
+first loads a new webContents is created and this attribute is set to its
+instance identifier. Setting this attribute on a new or existing webview
+connects it to the existing webContents that currently renders in a different
+webview.
+
+The existing webview will see the `destroy` event and will then create a new
+webContents when a new url is loaded.
+
## Methods
The `webview` tag has the following methods:
const supportedWebViewEvents = [
'load-commit',
+ 'did-attach',
'did-finish-load',
'did-fail-load',
'did-frame-finish-load',
'update-target-url'
]
-let nextInstanceId = 0
+let nextGuestInstanceId = 0
const guestInstances = {}
const embedderElementsMap = {}
-const reverseEmbedderElementsMap = {}
// Moves the last element of array to the first one.
const moveLastToFirst = function (list) {
}
// Generate guestInstanceId.
-const getNextInstanceId = function () {
- return ++nextInstanceId
+const getNextGuestInstanceId = function () {
+ return ++nextGuestInstanceId
}
// Create a new guest instance.
webViewManager = process.atomBinding('web_view_manager')
}
- const id = getNextInstanceId(embedder)
+ const guestInstanceId = getNextGuestInstanceId(embedder)
const guest = webContents.create({
isGuest: true,
partition: params.partition,
embedder: embedder
})
- guestInstances[id] = {
+ guestInstances[guestInstanceId] = {
guest: guest,
embedder: embedder
}
- // Destroy guest when the embedder is gone or navigated.
- const destroyEvents = ['will-destroy', 'crashed', 'did-navigate']
- const destroy = function () {
- if (guestInstances[id] != null) {
- destroyGuest(embedder, id)
- }
- }
- for (const event of destroyEvents) {
- embedder.once(event, destroy)
-
- // Users might also listen to the crashed event, so we must ensure the guest
- // is destroyed before users' listener gets called. It is done by moving our
- // listener to the first one in queue.
- const listeners = embedder._events[event]
- if (Array.isArray(listeners)) {
- moveLastToFirst(listeners)
- }
- }
- guest.once('destroyed', function () {
- for (const event of destroyEvents) {
- embedder.removeListener(event, destroy)
- }
- })
+ watchEmbedder(embedder)
// Init guest web view after attached.
- guest.once('did-attach', function () {
+ guest.on('did-attach', function () {
let opts
params = this.attachParams
delete this.attachParams
// Dispatch events to embedder.
const fn = function (event) {
guest.on(event, function (_, ...args) {
+ const embedder = getEmbedder(guestInstanceId)
+ if (!embedder) {
+ return
+ }
embedder.send.apply(embedder, ['ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-' + guest.viewInstanceId, event].concat(args))
})
}
// Dispatch guest's IPC messages to embedder.
guest.on('ipc-message-host', function (_, [channel, ...args]) {
+ const embedder = getEmbedder(guestInstanceId)
+ if (!embedder) {
+ return
+ }
embedder.send.apply(embedder, ['ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-' + guest.viewInstanceId, channel].concat(args))
})
// Autosize.
guest.on('size-changed', function (_, ...args) {
+ const embedder = getEmbedder(guestInstanceId)
+ if (!embedder) {
+ return
+ }
embedder.send.apply(embedder, ['ELECTRON_GUEST_VIEW_INTERNAL_SIZE_CHANGED-' + guest.viewInstanceId].concat(args))
})
- return id
+ return guestInstanceId
}
// Attach the guest to an element of embedder.
const attachGuest = function (embedder, elementInstanceId, guestInstanceId, params) {
- let guest, key, oldGuestInstanceId, ref1, webPreferences
- guest = guestInstances[guestInstanceId].guest
+ let guest, guestInstance, key, oldKey, oldGuestInstanceId, ref1, webPreferences
// Destroy the old guest when attaching.
key = (embedder.getId()) + '-' + elementInstanceId
oldGuestInstanceId = embedderElementsMap[key]
if (oldGuestInstanceId != null) {
- // Reattachment to the same guest is not currently supported.
+ // Reattachment to the same guest is just a no-op.
if (oldGuestInstanceId === guestInstanceId) {
return
}
- if (guestInstances[oldGuestInstanceId] == null) {
- return
- }
+
destroyGuest(embedder, oldGuestInstanceId)
}
+
+ guestInstance = guestInstances[guestInstanceId]
+ // If this isn't a valid guest instance then do nothing.
+ if (!guestInstance) {
+ return
+ }
+ guest = guestInstance.guest
+
+ // If this guest is already attached to an element then remove it
+ if (guestInstance.elementInstanceId) {
+ oldKey = (guestInstance.embedder.getId()) + '-' + guestInstance.elementInstanceId
+ delete embedderElementsMap[oldKey]
+ webViewManager.removeGuest(guestInstance.embedder, guestInstanceId)
+ guestInstance.embedder.send('ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-' + guest.viewInstanceId)
+ }
+
webPreferences = {
guestInstanceId: guestInstanceId,
nodeIntegration: (ref1 = params.nodeintegration) != null ? ref1 : false,
webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences)
guest.attachParams = params
embedderElementsMap[key] = guestInstanceId
- reverseEmbedderElementsMap[guestInstanceId] = key
+
+ guest.setEmbedder(embedder)
+ guestInstance.embedder = embedder
+ guestInstance.elementInstanceId = elementInstanceId
+
+ watchEmbedder(embedder)
}
// Destroy an existing guest instance.
-const destroyGuest = function (embedder, id) {
- webViewManager.removeGuest(embedder, id)
- guestInstances[id].guest.destroy()
- delete guestInstances[id]
-
- const key = reverseEmbedderElementsMap[id]
- if (key != null) {
- delete reverseEmbedderElementsMap[id]
- return delete embedderElementsMap[key]
+const destroyGuest = function (embedder, guestInstanceId) {
+ if (!(guestInstanceId in guestInstances)) {
+ return
+ }
+
+ let guestInstance = guestInstances[guestInstanceId]
+ if (embedder !== guestInstance.embedder) {
+ return
+ }
+
+ webViewManager.removeGuest(embedder, guestInstanceId)
+ guestInstance.guest.destroy()
+ delete guestInstances[guestInstanceId]
+
+ const key = embedder.getId() + '-' + guestInstance.elementInstanceId
+ return delete embedderElementsMap[key]
+}
+
+// Once an embedder has had a guest attached we watch it for destruction to
+// destroy any remaining guests.
+const watchedEmbedders = new Set()
+const watchEmbedder = function (embedder) {
+ if (watchedEmbedders.has(embedder)) {
+ return
+ }
+ watchedEmbedders.add(embedder)
+
+ const destroyEvents = ['will-destroy', 'crashed', 'did-navigate']
+ const destroy = function () {
+ for (const guestInstanceId of Object.keys(guestInstances)) {
+ if (guestInstances[guestInstanceId].embedder === embedder) {
+ destroyGuest(embedder, parseInt(guestInstanceId))
+ }
+ }
+
+ for (const event of destroyEvents) {
+ embedder.removeListener(event, destroy)
+ }
+
+ watchedEmbedders.delete(embedder)
+ }
+
+ for (const event of destroyEvents) {
+ embedder.once(event, destroy)
+
+ // Users might also listen to the crashed event, so we must ensure the guest
+ // is destroyed before users' listener gets called. It is done by moving our
+ // listener to the first one in queue.
+ const listeners = embedder._events[event]
+ if (Array.isArray(listeners)) {
+ moveLastToFirst(listeners)
+ }
}
}
attachGuest(event.sender, elementInstanceId, guestInstanceId, params)
})
-ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, id) {
- destroyGuest(event.sender, id)
+ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, guestInstanceId) {
+ destroyGuest(event.sender, guestInstanceId)
})
-ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_SET_SIZE', function (event, id, params) {
- const guestInstance = guestInstances[id]
+ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_SET_SIZE', function (event, guestInstanceId, params) {
+ const guestInstance = guestInstances[guestInstanceId]
return guestInstance != null ? guestInstance.guest.setSize(params) : void 0
})
// Returns WebContents from its guest id.
-exports.getGuest = function (id) {
- const guestInstance = guestInstances[id]
+exports.getGuest = function (guestInstanceId) {
+ const guestInstance = guestInstances[guestInstanceId]
return guestInstance != null ? guestInstance.guest : void 0
}
// Returns the embedder of the guest.
-exports.getEmbedder = function (id) {
- const guestInstance = guestInstances[id]
+const getEmbedder = function (guestInstanceId) {
+ const guestInstance = guestInstances[guestInstanceId]
return guestInstance != null ? guestInstance.embedder : void 0
}
+exports.getEmbedder = getEmbedder
var WEB_VIEW_EVENTS = {
'load-commit': ['url', 'isMainFrame'],
+ 'did-attach': [],
'did-finish-load': [],
'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL', 'isMainFrame'],
'did-frame-finish-load': ['isMainFrame'],
module.exports = {
registerEvents: function (webView, viewInstanceId) {
+ ipcRenderer.on('ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-' + viewInstanceId, function () {
+ var domEvent
+ webFrame.detachGuest(webView.internalInstanceId)
+ webView.guestInstanceId = undefined
+ webView.reset()
+ domEvent = new Event('destroyed')
+ webView.dispatchEvent(domEvent)
+ })
+
ipcRenderer.on('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-' + viewInstanceId, function (event, eventName, ...args) {
dispatchEvent.apply(null, [webView, eventName, eventName].concat(args))
})
})
},
deregisterEvents: function (viewInstanceId) {
+ ipcRenderer.removeAllListeners('ELECTRON_GUEST_VIEW_INTERNAL_DESTROY_GUEST-' + viewInstanceId)
ipcRenderer.removeAllListeners('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-' + viewInstanceId)
ipcRenderer.removeAllListeners('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-' + viewInstanceId)
return ipcRenderer.removeAllListeners('ELECTRON_GUEST_VIEW_INTERNAL_SIZE_CHANGED-' + viewInstanceId)
}
}
+// An attribute that controls the guest instance this webview is connected to
+class GuestInstanceAttribute extends WebViewAttribute {
+ constructor (webViewImpl) {
+ super(webViewConstants.ATTRIBUTE_GUESTINSTANCE, webViewImpl)
+ }
+
+ // Retrieves and returns the attribute's value.
+ getValue () {
+ if (this.webViewImpl.webviewNode.hasAttribute(this.name)) {
+ return parseInt(this.webViewImpl.webviewNode.getAttribute(this.name))
+ }
+ return undefined
+ }
+
+ // Sets the attribute's value.
+ setValue (value) {
+ if (!value) {
+ return this.webViewImpl.webviewNode.removeAttribute(this.name)
+ }
+ if (isNaN(value)) {
+ return
+ }
+ return this.webViewImpl.webviewNode.setAttribute(this.name, value)
+ }
+
+ handleMutation (oldValue, newValue) {
+ if (!newValue) {
+ this.webViewImpl.reset()
+ return
+ }
+
+ const intVal = parseInt(newValue)
+ if (!isNaN(newValue) && remote.getGuestWebContents(intVal)) {
+ this.webViewImpl.attachGuestInstance(intVal)
+ } else {
+ this.setValueIgnoreMutation(oldValue)
+ }
+ }
+}
+
// Attribute that handles the location and navigation of the webview.
class SrcAttribute extends WebViewAttribute {
constructor (webViewImpl) {
this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES] = new DisableBlinkFeaturesAttribute(this)
+ this.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE] = new GuestInstanceAttribute(this)
const autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH]
autosizeAttributes.forEach((attribute) => {
ATTRIBUTE_USERAGENT: 'useragent',
ATTRIBUTE_BLINKFEATURES: 'blinkfeatures',
ATTRIBUTE_DISABLEBLINKFEATURES: 'disableblinkfeatures',
+ ATTRIBUTE_GUESTINSTANCE: 'guestinstance',
// Internal attribute.
ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid',
// that we don't end up allocating a second guest.
if (this.guestInstanceId) {
guestViewInternal.destroyGuest(this.guestInstanceId)
- this.webContents = null
this.guestInstanceId = void 0
- this.beforeFirstNavigation = true
- this.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true
}
- this.internalInstanceId = 0
+
+ this.webContents = null
+ this.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].setValueIgnoreMutation(undefined)
+ this.beforeFirstNavigation = true
+ this.attributes[webViewConstants.ATTRIBUTE_PARTITION].validPartitionId = true
}
// Sets the <webview>.request property.
WebViewImpl.prototype.createGuest = function () {
return guestViewInternal.createGuest(this.buildParams(), (event, guestInstanceId) => {
- this.attachWindow(guestInstanceId)
+ this.attachGuestInstance(guestInstanceId)
})
}
return params
}
- WebViewImpl.prototype.attachWindow = function (guestInstanceId) {
+ WebViewImpl.prototype.attachGuestInstance = function (guestInstanceId) {
this.guestInstanceId = guestInstanceId
+ this.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].setValueIgnoreMutation(guestInstanceId)
this.webContents = remote.getGuestWebContents(this.guestInstanceId)
if (!this.internalInstanceId) {
return true
}
guestViewInternal.deregisterEvents(internal.viewInstanceId)
internal.elementAttached = false
- return internal.reset()
+ internal.reset()
+ this.internalInstanceId = 0
}
proto.attachedCallback = function () {
- var internal
+ var internal, instance
internal = v8Util.getHiddenValue(this, 'internal')
if (!internal) {
return
if (!internal.elementAttached) {
guestViewInternal.registerEvents(internal, internal.viewInstanceId)
internal.elementAttached = true
+ instance = internal.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].getValue()
+ if (instance) {
+ return internal.attachGuestInstance(instance)
+ }
return internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse()
}
}
--- /dev/null
+<html>
+<body>
+<script type="text/javascript" charset="utf-8">
+ var ipcRenderer = require('electron').ipcRenderer
+
+ ipcRenderer.on('guestinstance', function(event, instance) {
+ var webview = new WebView()
+ webview.setAttribute('guestinstance', instance)
+ document.body.appendChild(webview)
+
+ webview.addEventListener('did-attach', function (){
+ ipcRenderer.send('pong')
+ })
+ })
+</script>
+</body>
+</html>
const path = require('path')
const http = require('http')
const url = require('url')
-const {app, session, ipcMain, BrowserWindow} = require('electron').remote
+const {app, session, getGuestWebContents, ipcMain, BrowserWindow} = require('electron').remote
describe('<webview> tag', function () {
this.timeout(20000)
done()
})
})
+
+ describe('guestinstance attribute', function () {
+ it('before loading there is no attribute', function () {
+ document.body.appendChild(webview)
+ assert(!webview.hasAttribute('guestinstance'))
+ })
+
+ it('loading a page sets the guest view', function (done) {
+ var loadListener = function () {
+ webview.removeEventListener('did-finish-load', loadListener, false)
+ var instance = webview.getAttribute('guestinstance')
+ assert.equal(instance, parseInt(instance))
+
+ var guest = getGuestWebContents(parseInt(instance))
+ assert.equal(guest, webview.getWebContents())
+ done()
+ }
+ webview.addEventListener('did-finish-load', loadListener, false)
+ webview.src = 'file://' + fixtures + '/api/blank.html'
+ document.body.appendChild(webview)
+ })
+
+ it('deleting the attribute destroys the webview', function (done) {
+ var loadListener = function () {
+ webview.removeEventListener('did-finish-load', loadListener, false)
+ var destroyListener = function () {
+ webview.removeEventListener('destroyed', destroyListener, false)
+ assert.equal(getGuestWebContents(instance), null)
+ done()
+ }
+ webview.addEventListener('destroyed', destroyListener, false)
+
+ var instance = parseInt(webview.getAttribute('guestinstance'))
+ webview.removeAttribute('guestinstance')
+ }
+ webview.addEventListener('did-finish-load', loadListener, false)
+ webview.src = 'file://' + fixtures + '/api/blank.html'
+ document.body.appendChild(webview)
+ })
+
+ it('setting the attribute on a new webview moves the contents', function (done) {
+ var loadListener = function () {
+ webview.removeEventListener('did-finish-load', loadListener, false)
+ var webContents = webview.getWebContents()
+ var instance = webview.getAttribute('guestinstance')
+
+ var destroyListener = function () {
+ webview.removeEventListener('destroyed', destroyListener, false)
+ assert.equal(webContents, webview2.getWebContents())
+ // Make sure that events are hooked up to the right webview now
+ webview2.addEventListener('console-message', function (e) {
+ assert.equal(e.message, 'a')
+ document.body.removeChild(webview2)
+ done()
+ })
+
+ webview2.src = 'file://' + fixtures + '/pages/a.html'
+ }
+ webview.addEventListener('destroyed', destroyListener, false)
+
+ var webview2 = new WebView()
+ webview2.setAttribute('guestinstance', instance)
+ document.body.appendChild(webview2)
+ }
+ webview.addEventListener('did-finish-load', loadListener, false)
+ webview.src = 'file://' + fixtures + '/api/blank.html'
+ document.body.appendChild(webview)
+ })
+
+ it('setting the attribute to an invalid guestinstance does nothing', function (done) {
+ var loadListener = function () {
+ webview.removeEventListener('did-finish-load', loadListener, false)
+ webview.setAttribute('guestinstance', 55)
+
+ // Make sure that events are still hooked up to the webview
+ webview.addEventListener('console-message', function (e) {
+ assert.equal(e.message, 'a')
+ done()
+ })
+
+ webview.src = 'file://' + fixtures + '/pages/a.html'
+ }
+ webview.addEventListener('did-finish-load', loadListener, false)
+
+ webview.src = 'file://' + fixtures + '/api/blank.html'
+ document.body.appendChild(webview)
+ })
+
+ it('setting the attribute on an existing webview moves the contents', function (done) {
+ var load1Listener = function () {
+ webview.removeEventListener('did-finish-load', load1Listener, false)
+ var webContents = webview.getWebContents()
+ var instance = webview.getAttribute('guestinstance')
+ var destroyedInstance
+
+ var destroyListener = function () {
+ webview.removeEventListener('destroyed', destroyListener, false)
+ 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', function (e) {
+ assert.equal(e.message, 'a')
+ document.body.removeChild(webview2)
+ done()
+ })
+
+ webview2.src = 'file://' + fixtures + '/pages/a.html'
+ }
+ webview.addEventListener('destroyed', destroyListener, false)
+
+ var webview2 = new WebView()
+ var load2Listener = function () {
+ webview2.removeEventListener('did-finish-load', load2Listener, false)
+ destroyedInstance = webview2.getAttribute('guestinstance')
+ assert.notEqual(instance, destroyedInstance)
+
+ webview2.setAttribute('guestinstance', instance)
+ }
+ webview2.addEventListener('did-finish-load', load2Listener, false)
+ webview2.src = 'file://' + fixtures + '/api/blank.html'
+ document.body.appendChild(webview2)
+ }
+ webview.addEventListener('did-finish-load', load1Listener, false)
+ webview.src = 'file://' + fixtures + '/api/blank.html'
+ document.body.appendChild(webview)
+ })
+
+ it('moving a guest back to its original webview should work', function (done) {
+ var loadListener = function () {
+ webview.removeEventListener('did-finish-load', loadListener, false)
+ var webContents = webview.getWebContents()
+ var instance = webview.getAttribute('guestinstance')
+
+ var destroy1Listener = function () {
+ webview.removeEventListener('destroyed', destroy1Listener, false)
+ assert.equal(webContents, webview2.getWebContents())
+ assert.equal(null, webview.getWebContents())
+
+ var destroy2Listener = function () {
+ webview2.removeEventListener('destroyed', destroy2Listener, false)
+ assert.equal(webContents, webview.getWebContents())
+ assert.equal(null, webview2.getWebContents())
+
+ // Make sure that events are hooked up to the right webview now
+ webview.addEventListener('console-message', function (e) {
+ assert.equal(e.message, 'a')
+ document.body.removeChild(webview2)
+ done()
+ })
+
+ webview.src = 'file://' + fixtures + '/pages/a.html'
+ }
+ webview2.addEventListener('destroyed', destroy2Listener, false)
+
+ webview.setAttribute('guestinstance', instance)
+ }
+ webview.addEventListener('destroyed', destroy1Listener, false)
+
+ var webview2 = new WebView()
+ webview2.setAttribute('guestinstance', instance)
+ document.body.appendChild(webview2)
+ }
+ webview.addEventListener('did-finish-load', loadListener, false)
+ webview.src = 'file://' + fixtures + '/api/blank.html'
+ document.body.appendChild(webview)
+ })
+
+ it('setting the attribute on a webview in a different window moves the contents', function (done) {
+ var loadListener = function () {
+ webview.removeEventListener('did-finish-load', loadListener, false)
+ var instance = webview.getAttribute('guestinstance')
+
+ w = new BrowserWindow({ show: false })
+ w.webContents.once('did-finish-load', function () {
+ ipcMain.once('pong', function () {
+ assert(!webview.hasAttribute('guestinstance'))
+
+ done()
+ })
+
+ w.webContents.send('guestinstance', instance)
+ })
+ w.loadURL('file://' + fixtures + '/pages/webview-move-to-window.html')
+ }
+ webview.addEventListener('did-finish-load', loadListener, false)
+ webview.src = 'file://' + fixtures + '/api/blank.html'
+ document.body.appendChild(webview)
+ })
+ })
})