Merge pull request #7157 from Mossop/moveguest

Allow moving a webcontents to a different webview
This commit is contained in:
Cheng Zhao 2016-09-20 14:28:42 +09:00 committed by GitHub
commit e3e450613d
12 changed files with 424 additions and 63 deletions

View file

@ -1455,6 +1455,25 @@ content::WebContents* WebContents::HostWebContents() {
return embedder_->web_contents(); 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) { v8::Local<v8::Value> WebContents::DevToolsWebContents(v8::Isolate* isolate) {
if (devtools_web_contents_.IsEmpty()) if (devtools_web_contents_.IsEmpty())
return v8::Null(isolate); return v8::Null(isolate);
@ -1552,6 +1571,7 @@ void WebContents::BuildPrototype(v8::Isolate* isolate,
&WebContents::ShowDefinitionForSelection) &WebContents::ShowDefinitionForSelection)
.SetMethod("copyImageAt", &WebContents::CopyImageAt) .SetMethod("copyImageAt", &WebContents::CopyImageAt)
.SetMethod("capturePage", &WebContents::CapturePage) .SetMethod("capturePage", &WebContents::CapturePage)
.SetMethod("setEmbedder", &WebContents::SetEmbedder)
.SetProperty("id", &WebContents::ID) .SetProperty("id", &WebContents::ID)
.SetProperty("session", &WebContents::Session) .SetProperty("session", &WebContents::Session)
.SetProperty("hostWebContents", &WebContents::HostWebContents) .SetProperty("hostWebContents", &WebContents::HostWebContents)

View file

@ -102,6 +102,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
void SetAudioMuted(bool muted); void SetAudioMuted(bool muted);
bool IsAudioMuted(); bool IsAudioMuted();
void Print(mate::Arguments* args); void Print(mate::Arguments* args);
void SetEmbedder(const WebContents* embedder);
// Print current page as PDF. // Print current page as PDF.
void PrintToPDF(const base::DictionaryValue& setting, void PrintToPDF(const base::DictionaryValue& setting,

View file

@ -110,6 +110,10 @@ void WebFrame::AttachGuest(int id) {
content::RenderFrame::FromWebFrame(web_frame_)->AttachGuest(id); 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, void WebFrame::SetSpellCheckProvider(mate::Arguments* args,
const std::string& language, const std::string& language,
bool auto_spell_correct_turned_on, bool auto_spell_correct_turned_on,
@ -202,6 +206,7 @@ void WebFrame::BuildPrototype(
.SetMethod("registerElementResizeCallback", .SetMethod("registerElementResizeCallback",
&WebFrame::RegisterElementResizeCallback) &WebFrame::RegisterElementResizeCallback)
.SetMethod("attachGuest", &WebFrame::AttachGuest) .SetMethod("attachGuest", &WebFrame::AttachGuest)
.SetMethod("detachGuest", &WebFrame::DetachGuest)
.SetMethod("setSpellCheckProvider", &WebFrame::SetSpellCheckProvider) .SetMethod("setSpellCheckProvider", &WebFrame::SetSpellCheckProvider)
.SetMethod("registerURLSchemeAsSecure", .SetMethod("registerURLSchemeAsSecure",
&WebFrame::RegisterURLSchemeAsSecure) &WebFrame::RegisterURLSchemeAsSecure)

View file

@ -53,6 +53,7 @@ class WebFrame : public mate::Wrappable<WebFrame> {
int element_instance_id, int element_instance_id,
const GuestViewContainer::ResizeCallback& callback); const GuestViewContainer::ResizeCallback& callback);
void AttachGuest(int element_instance_id); void AttachGuest(int element_instance_id);
void DetachGuest(int element_instance_id);
// Set the provider that will be used by SpellCheckClient for spell check. // Set the provider that will be used by SpellCheckClient for spell check.
void SetSpellCheckProvider(mate::Arguments* args, void SetSpellCheckProvider(mate::Arguments* args,

View file

@ -214,6 +214,21 @@ A list of strings which specifies the blink features to be disabled separated by
The full list of supported feature strings can be found in the The full list of supported feature strings can be found in the
[RuntimeEnabledFeatures.in][blink-feature-string] file. [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 ## Methods
The `webview` tag has the following methods: The `webview` tag has the following methods:

View file

@ -8,6 +8,7 @@ let webViewManager = null
const supportedWebViewEvents = [ const supportedWebViewEvents = [
'load-commit', 'load-commit',
'did-attach',
'did-finish-load', 'did-finish-load',
'did-fail-load', 'did-fail-load',
'did-frame-finish-load', 'did-frame-finish-load',
@ -40,10 +41,9 @@ const supportedWebViewEvents = [
'update-target-url' 'update-target-url'
] ]
let nextInstanceId = 0 let nextGuestInstanceId = 0
const guestInstances = {} const guestInstances = {}
const embedderElementsMap = {} const embedderElementsMap = {}
const reverseEmbedderElementsMap = {}
// Moves the last element of array to the first one. // Moves the last element of array to the first one.
const moveLastToFirst = function (list) { const moveLastToFirst = function (list) {
@ -51,8 +51,8 @@ const moveLastToFirst = function (list) {
} }
// Generate guestInstanceId. // Generate guestInstanceId.
const getNextInstanceId = function () { const getNextGuestInstanceId = function () {
return ++nextInstanceId return ++nextGuestInstanceId
} }
// Create a new guest instance. // Create a new guest instance.
@ -61,43 +61,21 @@ const createGuest = function (embedder, params) {
webViewManager = process.atomBinding('web_view_manager') webViewManager = process.atomBinding('web_view_manager')
} }
const id = getNextInstanceId(embedder) const guestInstanceId = getNextGuestInstanceId(embedder)
const guest = webContents.create({ const guest = webContents.create({
isGuest: true, isGuest: true,
partition: params.partition, partition: params.partition,
embedder: embedder embedder: embedder
}) })
guestInstances[id] = { guestInstances[guestInstanceId] = {
guest: guest, guest: guest,
embedder: embedder embedder: embedder
} }
// Destroy guest when the embedder is gone or navigated. watchEmbedder(embedder)
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)
}
})
// Init guest web view after attached. // Init guest web view after attached.
guest.once('did-attach', function () { guest.on('did-attach', function () {
let opts let opts
params = this.attachParams params = this.attachParams
delete this.attachParams delete this.attachParams
@ -133,6 +111,10 @@ const createGuest = function (embedder, params) {
// Dispatch events to embedder. // Dispatch events to embedder.
const fn = function (event) { const fn = function (event) {
guest.on(event, function (_, ...args) { 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)) embedder.send.apply(embedder, ['ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-' + guest.viewInstanceId, event].concat(args))
}) })
} }
@ -142,35 +124,56 @@ const createGuest = function (embedder, params) {
// Dispatch guest's IPC messages to embedder. // Dispatch guest's IPC messages to embedder.
guest.on('ipc-message-host', function (_, [channel, ...args]) { 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)) embedder.send.apply(embedder, ['ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-' + guest.viewInstanceId, channel].concat(args))
}) })
// Autosize. // Autosize.
guest.on('size-changed', function (_, ...args) { 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)) 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. // Attach the guest to an element of embedder.
const attachGuest = function (embedder, elementInstanceId, guestInstanceId, params) { const attachGuest = function (embedder, elementInstanceId, guestInstanceId, params) {
let guest, key, oldGuestInstanceId, ref1, webPreferences let guest, guestInstance, key, oldKey, oldGuestInstanceId, ref1, webPreferences
guest = guestInstances[guestInstanceId].guest
// Destroy the old guest when attaching. // Destroy the old guest when attaching.
key = (embedder.getId()) + '-' + elementInstanceId key = (embedder.getId()) + '-' + elementInstanceId
oldGuestInstanceId = embedderElementsMap[key] oldGuestInstanceId = embedderElementsMap[key]
if (oldGuestInstanceId != null) { 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) { if (oldGuestInstanceId === guestInstanceId) {
return return
} }
if (guestInstances[oldGuestInstanceId] == null) {
return
}
destroyGuest(embedder, oldGuestInstanceId) 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 = { webPreferences = {
guestInstanceId: guestInstanceId, guestInstanceId: guestInstanceId,
nodeIntegration: (ref1 = params.nodeintegration) != null ? ref1 : false, nodeIntegration: (ref1 = params.nodeintegration) != null ? ref1 : false,
@ -187,19 +190,67 @@ const attachGuest = function (embedder, elementInstanceId, guestInstanceId, para
webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences) webViewManager.addGuest(guestInstanceId, elementInstanceId, embedder, guest, webPreferences)
guest.attachParams = params guest.attachParams = params
embedderElementsMap[key] = guestInstanceId embedderElementsMap[key] = guestInstanceId
reverseEmbedderElementsMap[guestInstanceId] = key
guest.setEmbedder(embedder)
guestInstance.embedder = embedder
guestInstance.elementInstanceId = elementInstanceId
watchEmbedder(embedder)
} }
// Destroy an existing guest instance. // Destroy an existing guest instance.
const destroyGuest = function (embedder, id) { const destroyGuest = function (embedder, guestInstanceId) {
webViewManager.removeGuest(embedder, id) if (!(guestInstanceId in guestInstances)) {
guestInstances[id].guest.destroy() return
delete guestInstances[id] }
const key = reverseEmbedderElementsMap[id] let guestInstance = guestInstances[guestInstanceId]
if (key != null) { if (embedder !== guestInstance.embedder) {
delete reverseEmbedderElementsMap[id] return
return delete embedderElementsMap[key] }
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)
}
} }
} }
@ -211,23 +262,24 @@ ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, elementI
attachGuest(event.sender, elementInstanceId, guestInstanceId, params) attachGuest(event.sender, elementInstanceId, guestInstanceId, params)
}) })
ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, id) { ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_DESTROY_GUEST', function (event, guestInstanceId) {
destroyGuest(event.sender, id) destroyGuest(event.sender, guestInstanceId)
}) })
ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_SET_SIZE', function (event, id, params) { ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_SET_SIZE', function (event, guestInstanceId, params) {
const guestInstance = guestInstances[id] const guestInstance = guestInstances[guestInstanceId]
return guestInstance != null ? guestInstance.guest.setSize(params) : void 0 return guestInstance != null ? guestInstance.guest.setSize(params) : void 0
}) })
// Returns WebContents from its guest id. // Returns WebContents from its guest id.
exports.getGuest = function (id) { exports.getGuest = function (guestInstanceId) {
const guestInstance = guestInstances[id] const guestInstance = guestInstances[guestInstanceId]
return guestInstance != null ? guestInstance.guest : void 0 return guestInstance != null ? guestInstance.guest : void 0
} }
// Returns the embedder of the guest. // Returns the embedder of the guest.
exports.getEmbedder = function (id) { const getEmbedder = function (guestInstanceId) {
const guestInstance = guestInstances[id] const guestInstance = guestInstances[guestInstanceId]
return guestInstance != null ? guestInstance.embedder : void 0 return guestInstance != null ? guestInstance.embedder : void 0
} }
exports.getEmbedder = getEmbedder

View file

@ -7,6 +7,7 @@ var requestId = 0
var WEB_VIEW_EVENTS = { var WEB_VIEW_EVENTS = {
'load-commit': ['url', 'isMainFrame'], 'load-commit': ['url', 'isMainFrame'],
'did-attach': [],
'did-finish-load': [], 'did-finish-load': [],
'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL', 'isMainFrame'], 'did-fail-load': ['errorCode', 'errorDescription', 'validatedURL', 'isMainFrame'],
'did-frame-finish-load': ['isMainFrame'], 'did-frame-finish-load': ['isMainFrame'],
@ -62,6 +63,15 @@ var dispatchEvent = function (webView, eventName, eventKey, ...args) {
module.exports = { module.exports = {
registerEvents: function (webView, viewInstanceId) { 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) { ipcRenderer.on('ELECTRON_GUEST_VIEW_INTERNAL_DISPATCH_EVENT-' + viewInstanceId, function (event, eventName, ...args) {
dispatchEvent.apply(null, [webView, eventName, eventName].concat(args)) dispatchEvent.apply(null, [webView, eventName, eventName].concat(args))
}) })
@ -85,6 +95,7 @@ module.exports = {
}) })
}, },
deregisterEvents: function (viewInstanceId) { 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_DISPATCH_EVENT-' + viewInstanceId)
ipcRenderer.removeAllListeners('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-' + viewInstanceId) ipcRenderer.removeAllListeners('ELECTRON_GUEST_VIEW_INTERNAL_IPC_MESSAGE-' + viewInstanceId)
return ipcRenderer.removeAllListeners('ELECTRON_GUEST_VIEW_INTERNAL_SIZE_CHANGED-' + viewInstanceId) return ipcRenderer.removeAllListeners('ELECTRON_GUEST_VIEW_INTERNAL_SIZE_CHANGED-' + viewInstanceId)

View file

@ -130,6 +130,46 @@ class PartitionAttribute extends WebViewAttribute {
} }
} }
// 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. // Attribute that handles the location and navigation of the webview.
class SrcAttribute extends WebViewAttribute { class SrcAttribute extends WebViewAttribute {
constructor (webViewImpl) { constructor (webViewImpl) {
@ -287,6 +327,7 @@ WebViewImpl.prototype.setupWebViewAttributes = function () {
this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this) this.attributes[webViewConstants.ATTRIBUTE_PRELOAD] = new PreloadAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this) this.attributes[webViewConstants.ATTRIBUTE_BLINKFEATURES] = new BlinkFeaturesAttribute(this)
this.attributes[webViewConstants.ATTRIBUTE_DISABLEBLINKFEATURES] = new DisableBlinkFeaturesAttribute(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] const autosizeAttributes = [webViewConstants.ATTRIBUTE_MAXHEIGHT, webViewConstants.ATTRIBUTE_MAXWIDTH, webViewConstants.ATTRIBUTE_MINHEIGHT, webViewConstants.ATTRIBUTE_MINWIDTH]
autosizeAttributes.forEach((attribute) => { autosizeAttributes.forEach((attribute) => {

View file

@ -17,6 +17,7 @@ module.exports = {
ATTRIBUTE_USERAGENT: 'useragent', ATTRIBUTE_USERAGENT: 'useragent',
ATTRIBUTE_BLINKFEATURES: 'blinkfeatures', ATTRIBUTE_BLINKFEATURES: 'blinkfeatures',
ATTRIBUTE_DISABLEBLINKFEATURES: 'disableblinkfeatures', ATTRIBUTE_DISABLEBLINKFEATURES: 'disableblinkfeatures',
ATTRIBUTE_GUESTINSTANCE: 'guestinstance',
// Internal attribute. // Internal attribute.
ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid', ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid',

View file

@ -71,12 +71,13 @@ var WebViewImpl = (function () {
// that we don't end up allocating a second guest. // that we don't end up allocating a second guest.
if (this.guestInstanceId) { if (this.guestInstanceId) {
guestViewInternal.destroyGuest(this.guestInstanceId) guestViewInternal.destroyGuest(this.guestInstanceId)
this.webContents = null
this.guestInstanceId = void 0 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. // Sets the <webview>.request property.
@ -184,7 +185,7 @@ var WebViewImpl = (function () {
WebViewImpl.prototype.createGuest = function () { WebViewImpl.prototype.createGuest = function () {
return guestViewInternal.createGuest(this.buildParams(), (event, guestInstanceId) => { return guestViewInternal.createGuest(this.buildParams(), (event, guestInstanceId) => {
this.attachWindow(guestInstanceId) this.attachGuestInstance(guestInstanceId)
}) })
} }
@ -257,8 +258,9 @@ var WebViewImpl = (function () {
return params return params
} }
WebViewImpl.prototype.attachWindow = function (guestInstanceId) { WebViewImpl.prototype.attachGuestInstance = function (guestInstanceId) {
this.guestInstanceId = guestInstanceId this.guestInstanceId = guestInstanceId
this.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].setValueIgnoreMutation(guestInstanceId)
this.webContents = remote.getGuestWebContents(this.guestInstanceId) this.webContents = remote.getGuestWebContents(this.guestInstanceId)
if (!this.internalInstanceId) { if (!this.internalInstanceId) {
return true return true
@ -324,10 +326,11 @@ var registerWebViewElement = function () {
} }
guestViewInternal.deregisterEvents(internal.viewInstanceId) guestViewInternal.deregisterEvents(internal.viewInstanceId)
internal.elementAttached = false internal.elementAttached = false
return internal.reset() internal.reset()
this.internalInstanceId = 0
} }
proto.attachedCallback = function () { proto.attachedCallback = function () {
var internal var internal, instance
internal = v8Util.getHiddenValue(this, 'internal') internal = v8Util.getHiddenValue(this, 'internal')
if (!internal) { if (!internal) {
return return
@ -335,6 +338,10 @@ var registerWebViewElement = function () {
if (!internal.elementAttached) { if (!internal.elementAttached) {
guestViewInternal.registerEvents(internal, internal.viewInstanceId) guestViewInternal.registerEvents(internal, internal.viewInstanceId)
internal.elementAttached = true internal.elementAttached = true
instance = internal.attributes[webViewConstants.ATTRIBUTE_GUESTINSTANCE].getValue()
if (instance) {
return internal.attachGuestInstance(instance)
}
return internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse() return internal.attributes[webViewConstants.ATTRIBUTE_SRC].parse()
} }
} }

View file

@ -0,0 +1,17 @@
<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>

View file

@ -2,7 +2,7 @@ const assert = require('assert')
const path = require('path') const path = require('path')
const http = require('http') const http = require('http')
const url = require('url') 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 () { describe('<webview> tag', function () {
this.timeout(20000) this.timeout(20000)
@ -1018,4 +1018,194 @@ describe('<webview> tag', function () {
done() 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)
})
})
}) })