Skip to content

Commit 173d624

Browse files
authored
[bidi][js] Add script message event (#13153)
1 parent fea3ddd commit 173d624

File tree

6 files changed

+212
-1
lines changed

6 files changed

+212
-1
lines changed

javascript/node/selenium-webdriver/bidi/protocolType.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ const NonPrimitiveType = {
4242
OBJECT: 'object',
4343
REGULAR_EXPRESSION: 'regexp',
4444
SET: 'set',
45+
CHANNEL: 'channel',
4546

4647
findByName(name) {
4748
return (

javascript/node/selenium-webdriver/bidi/protocolValue.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,10 @@ class LocalValue {
9898
return new LocalValue(NonPrimitiveType.SET, value)
9999
}
100100

101+
static createChannelValue(value) {
102+
return new LocalValue(NonPrimitiveType.CHANNEL, value)
103+
}
104+
101105
toJson() {
102106
let toReturn = {}
103107
toReturn[TYPE_CONSTANT] = this.type
@@ -195,10 +199,56 @@ class RegExpValue {
195199
}
196200
}
197201

202+
class SerializationOptions {
203+
constructor(
204+
maxDomDepth = 0,
205+
maxObjectDepth = null,
206+
includeShadowTree = 'none'
207+
) {
208+
this._maxDomDepth = maxDomDepth
209+
this._maxObjectDepth = maxObjectDepth
210+
211+
if (['none', 'open', 'all'].includes(includeShadowTree)) {
212+
throw Error(
213+
`Valid types are 'none', 'open', and 'all'. Received: ${includeShadowTree}`
214+
)
215+
}
216+
this._includeShadowTree = includeShadowTree
217+
}
218+
}
219+
220+
class ChannelValue {
221+
constructor(channel, options = undefined, resultOwnership = undefined) {
222+
this.channel = channel
223+
224+
if (options !== undefined) {
225+
if (options instanceof SerializationOptions) {
226+
this.options = options
227+
} else {
228+
throw Error(
229+
`Pass in SerializationOptions object. Received: ${options} `
230+
)
231+
}
232+
}
233+
234+
if (resultOwnership != undefined) {
235+
if (['root', 'none'].includes(resultOwnership)) {
236+
this.resultOwnership = resultOwnership
237+
} else {
238+
throw Error(
239+
`Valid types are 'root' and 'none. Received: ${resultOwnership}`
240+
)
241+
}
242+
}
243+
}
244+
}
245+
198246
module.exports = {
247+
ChannelValue,
199248
LocalValue,
200249
RemoteValue,
201250
ReferenceValue,
202251
RemoteReferenceType,
203252
RegExpValue,
253+
SerializationOptions,
204254
}

javascript/node/selenium-webdriver/bidi/realmInfo.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,4 +96,5 @@ class WindowRealmInfo extends RealmInfo {
9696
module.exports = {
9797
RealmInfo,
9898
RealmType,
99+
WindowRealmInfo,
99100
}

javascript/node/selenium-webdriver/bidi/scriptManager.js

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@ const {
2121
EvaluateResultException,
2222
ExceptionDetails,
2323
} = require('./evaluateResult')
24-
const { RealmInfo } = require('./realmInfo')
24+
const { Message } = require('./scriptTypes')
25+
const { RealmInfo, RealmType, WindowRealmInfo } = require('./realmInfo')
2526
const { RemoteValue } = require('./protocolValue')
27+
const { Source } = require('./scriptTypes')
2628
const { WebDriverError } = require('../lib/error')
2729

2830
class ScriptManager {
@@ -348,6 +350,55 @@ class ScriptManager {
348350
let response = await this.bidi.send(command)
349351
return this.realmInfoMapper(response.result.realms)
350352
}
353+
354+
async onMessage(callback) {
355+
await this.subscribeAndHandleEvent('script.message', callback)
356+
}
357+
358+
async onRealmCreated(callback) {
359+
await this.subscribeAndHandleEvent('script.realmCreated', callback)
360+
}
361+
362+
async subscribeAndHandleEvent(eventType, callback) {
363+
if (this._browsingContextIds != null) {
364+
await this.bidi.subscribe(eventType, this._browsingContextIds)
365+
} else {
366+
await this.bidi.subscribe(eventType)
367+
}
368+
await this._on(callback)
369+
}
370+
371+
async _on(callback) {
372+
this.ws = await this.bidi.socket
373+
this.ws.on('message', (event) => {
374+
const { params } = JSON.parse(Buffer.from(event.toString()))
375+
if (params) {
376+
let response = null
377+
if ('channel' in params) {
378+
response = new Message(
379+
params.channel,
380+
new RemoteValue(params.data),
381+
new Source(params.source)
382+
)
383+
} else if ('realm' in params) {
384+
if (params.type === RealmType.WINDOW) {
385+
response = new WindowRealmInfo(
386+
params.realm,
387+
params.origin,
388+
params.type,
389+
params.context,
390+
params.sandbox
391+
)
392+
} else if (params.realm !== null && params.type !== null) {
393+
response = new RealmInfo(params.realm, params.origin, params.type)
394+
} else if (params.realm !== null) {
395+
response = params.realm
396+
}
397+
}
398+
callback(response)
399+
}
400+
})
401+
}
351402
}
352403

353404
async function getScriptManagerInstance(browsingContextId, driver) {
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// Licensed to the Software Freedom Conservancy (SFC) under one
2+
// or more contributor license agreements. See the NOTICE file
3+
// distributed with this work for additional information
4+
// regarding copyright ownership. The SFC licenses this file
5+
// to you under the Apache License, Version 2.0 (the
6+
// "License"); you may not use this file except in compliance
7+
// with the License. You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
class Message {
19+
constructor(channel, data, source) {
20+
this._channel = channel
21+
this._data = data
22+
this._source = source
23+
}
24+
25+
get channel() {
26+
return this._channel
27+
}
28+
29+
get data() {
30+
return this._data
31+
}
32+
33+
get source() {
34+
return this._source
35+
}
36+
}
37+
38+
class Source {
39+
constructor(source) {
40+
this._browsingContextId = null
41+
this._realmId = source.realm
42+
43+
// Browsing context is returned as an optional parameter
44+
if ('context' in source) {
45+
this._browsingContextId = source.context
46+
}
47+
}
48+
49+
get browsingContextId() {
50+
return this._browsingContextId
51+
}
52+
53+
get realmId() {
54+
return this._realmId
55+
}
56+
}
57+
58+
module.exports = { Message, Source }

javascript/node/selenium-webdriver/test/bidi/bidi_test.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ const until = require('../../lib/until')
3232
// Imports for Script Module
3333
const ScriptManager = require('../../bidi/scriptManager')
3434
const {
35+
ChannelValue,
3536
LocalValue,
3637
ReferenceValue,
3738
RemoteReferenceType,
@@ -2131,6 +2132,55 @@ suite(
21312132

21322133
assert.equal(result_in_sandbox.result.type, 'undefined')
21332134
})
2135+
2136+
it('can listen to channel message', async function () {
2137+
const manager = await ScriptManager(undefined, driver)
2138+
2139+
let message = null
2140+
2141+
await manager.onMessage((m) => {
2142+
message = m
2143+
})
2144+
2145+
let argumentValues = []
2146+
let value = new ArgumentValue(
2147+
LocalValue.createChannelValue(new ChannelValue('channel_name'))
2148+
)
2149+
argumentValues.push(value)
2150+
2151+
const result = await manager.callFunctionInBrowsingContext(
2152+
await driver.getWindowHandle(),
2153+
'(channel) => channel("foo")',
2154+
false,
2155+
argumentValues
2156+
)
2157+
assert.equal(result.resultType, EvaluateResultType.SUCCESS)
2158+
assert.notEqual(message, null)
2159+
assert.equal(message.channel, 'channel_name')
2160+
assert.equal(message.data.type, 'string')
2161+
assert.equal(message.data.value, 'foo')
2162+
})
2163+
2164+
it('can listen to realm created message', async function () {
2165+
const manager = await ScriptManager(undefined, driver)
2166+
2167+
let realmInfo = null
2168+
2169+
await manager.onRealmCreated((result) => {
2170+
realmInfo = result
2171+
})
2172+
2173+
const id = await driver.getWindowHandle()
2174+
const browsingContext = await BrowsingContext(driver, {
2175+
browsingContextId: id,
2176+
})
2177+
2178+
await browsingContext.navigate(Pages.blankPage, 'complete')
2179+
2180+
assert.notEqual(realmInfo, null)
2181+
assert.notEqual(realmInfo.realmId, null)
2182+
assert.equal(realmInfo.realmType, RealmType.WINDOW)
2183+
})
21342184
})
21352185

21362186
describe('Network Inspector', function () {

0 commit comments

Comments
 (0)