Add a guestinstance attribute to webviews reflecting their current guest
instance ID and allowing moving a guest instance to a new webview.
This commit is contained in:
Родитель
01fa9827b4
Коммит
313b2faa3c
|
@ -1433,6 +1433,25 @@ content::WebContents* WebContents::HostWebContents() {
|
|||
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);
|
||||
|
@ -1529,6 +1548,7 @@ void WebContents::BuildPrototype(v8::Isolate* 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)
|
||||
|
|
|
@ -102,6 +102,7 @@ class WebContents : public mate::TrackableObject<WebContents>,
|
|||
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,
|
||||
|
|
|
@ -110,6 +110,10 @@ void WebFrame::AttachGuest(int 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,
|
||||
const std::string& language,
|
||||
bool auto_spell_correct_turned_on,
|
||||
|
@ -202,6 +206,7 @@ void WebFrame::BuildPrototype(
|
|||
.SetMethod("registerElementResizeCallback",
|
||||
&WebFrame::RegisterElementResizeCallback)
|
||||
.SetMethod("attachGuest", &WebFrame::AttachGuest)
|
||||
.SetMethod("detachGuest", &WebFrame::DetachGuest)
|
||||
.SetMethod("setSpellCheckProvider", &WebFrame::SetSpellCheckProvider)
|
||||
.SetMethod("registerURLSchemeAsSecure",
|
||||
&WebFrame::RegisterURLSchemeAsSecure)
|
||||
|
|
|
@ -53,6 +53,7 @@ class WebFrame : public mate::Wrappable<WebFrame> {
|
|||
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,
|
||||
|
|
|
@ -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
|
||||
[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:
|
||||
|
|
|
@ -8,6 +8,7 @@ let webViewManager = null
|
|||
|
||||
const supportedWebViewEvents = [
|
||||
'load-commit',
|
||||
'did-attach',
|
||||
'did-finish-load',
|
||||
'did-fail-load',
|
||||
'did-frame-finish-load',
|
||||
|
@ -40,10 +41,9 @@ const supportedWebViewEvents = [
|
|||
'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) {
|
||||
|
@ -51,8 +51,8 @@ const moveLastToFirst = function (list) {
|
|||
}
|
||||
|
||||
// Generate guestInstanceId.
|
||||
const getNextInstanceId = function () {
|
||||
return ++nextInstanceId
|
||||
const getNextGuestInstanceId = function () {
|
||||
return ++nextGuestInstanceId
|
||||
}
|
||||
|
||||
// Create a new guest instance.
|
||||
|
@ -61,43 +61,21 @@ const createGuest = function (embedder, params) {
|
|||
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
|
||||
|
@ -133,6 +111,10 @@ const createGuest = function (embedder, params) {
|
|||
// 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))
|
||||
})
|
||||
}
|
||||
|
@ -142,35 +124,56 @@ const createGuest = function (embedder, params) {
|
|||
|
||||
// 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,
|
||||
|
@ -187,19 +190,67 @@ const attachGuest = function (embedder, elementInstanceId, guestInstanceId, para
|
|||
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 destroyGuest = function (embedder, guestInstanceId) {
|
||||
if (!(guestInstanceId in guestInstances)) {
|
||||
return
|
||||
}
|
||||
|
||||
const key = reverseEmbedderElementsMap[id]
|
||||
if (key != null) {
|
||||
delete reverseEmbedderElementsMap[id]
|
||||
return delete embedderElementsMap[key]
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -211,23 +262,24 @@ ipcMain.on('ELECTRON_GUEST_VIEW_MANAGER_ATTACH_GUEST', function (event, elementI
|
|||
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
|
||||
|
|
|
@ -7,6 +7,7 @@ var requestId = 0
|
|||
|
||||
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'],
|
||||
|
@ -62,6 +63,15 @@ var dispatchEvent = function (webView, eventName, eventKey, ...args) {
|
|||
|
||||
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))
|
||||
})
|
||||
|
@ -85,6 +95,7 @@ module.exports = {
|
|||
})
|
||||
},
|
||||
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)
|
||||
|
|
|
@ -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.
|
||||
class SrcAttribute extends WebViewAttribute {
|
||||
constructor (webViewImpl) {
|
||||
|
@ -287,6 +327,7 @@ WebViewImpl.prototype.setupWebViewAttributes = function () {
|
|||
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) => {
|
||||
|
|
|
@ -17,6 +17,7 @@ module.exports = {
|
|||
ATTRIBUTE_USERAGENT: 'useragent',
|
||||
ATTRIBUTE_BLINKFEATURES: 'blinkfeatures',
|
||||
ATTRIBUTE_DISABLEBLINKFEATURES: 'disableblinkfeatures',
|
||||
ATTRIBUTE_GUESTINSTANCE: 'guestinstance',
|
||||
|
||||
// Internal attribute.
|
||||
ATTRIBUTE_INTERNALINSTANCEID: 'internalinstanceid',
|
||||
|
|
|
@ -71,12 +71,13 @@ var WebViewImpl = (function () {
|
|||
// 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.
|
||||
|
@ -184,7 +185,7 @@ var WebViewImpl = (function () {
|
|||
|
||||
WebViewImpl.prototype.createGuest = function () {
|
||||
return guestViewInternal.createGuest(this.buildParams(), (event, guestInstanceId) => {
|
||||
this.attachWindow(guestInstanceId)
|
||||
this.attachGuestInstance(guestInstanceId)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -257,8 +258,9 @@ var WebViewImpl = (function () {
|
|||
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
|
||||
|
@ -324,10 +326,11 @@ var registerWebViewElement = function () {
|
|||
}
|
||||
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
|
||||
|
@ -335,6 +338,10 @@ var registerWebViewElement = function () {
|
|||
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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -2,7 +2,7 @@ const assert = require('assert')
|
|||
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)
|
||||
|
@ -975,4 +975,194 @@ describe('<webview> tag', function () {
|
|||
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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Загрузка…
Ссылка в новой задаче