Path: blob/trunk/javascript/selenium-webdriver/bidi/network.js
2884 views
// Licensed to the Software Freedom Conservancy (SFC) under one1// or more contributor license agreements. See the NOTICE file2// distributed with this work for additional information3// regarding copyright ownership. The SFC licenses this file4// to you under the Apache License, Version 2.0 (the5// "License"); you may not use this file except in compliance6// with the License. You may obtain a copy of the License at7//8// http://www.apache.org/licenses/LICENSE-2.09//10// Unless required by applicable law or agreed to in writing,11// software distributed under the License is distributed on an12// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13// KIND, either express or implied. See the License for the14// specific language governing permissions and limitations15// under the License.1617const { BeforeRequestSent, ResponseStarted, FetchError } = require('./networkTypes')18const { AddInterceptParameters } = require('./addInterceptParameters')19const { ContinueResponseParameters } = require('./continueResponseParameters')20const { ContinueRequestParameters } = require('./continueRequestParameters')21const { ProvideResponseParameters } = require('./provideResponseParameters')2223const NetworkEvent = {24BEFORE_REQUEST_SENT: 'network.beforeRequestSent',25RESPONSE_STARTED: 'network.responseStarted',26RESPONSE_COMPLETED: 'network.responseCompleted',27AUTH_REQUIRED: 'network.authRequired',28FETCH_ERROR: 'network.fetchError',29}3031const CacheBehavior = Object.freeze({32DEFAULT: 'default',33BYPASS: 'bypass',34})3536/**37* Represents all commands and events of Network module.38* Described in https://w3c.github.io/webdriver-bidi/#module-network.39*/40class Network {41#callbackId = 042#listener4344/**45* Represents a Network object.46* @constructor47* @param {Driver} driver - The driver to fetch the BiDi connection.48* @param {Array} browsingContextIds - An array of browsing context IDs that the network events will be subscribed to.49*/50constructor(driver, browsingContextIds) {51this._driver = driver52this._browsingContextIds = browsingContextIds53this.#listener = new Map()54this.#listener.set(NetworkEvent.AUTH_REQUIRED, new Map())55this.#listener.set(NetworkEvent.BEFORE_REQUEST_SENT, new Map())56this.#listener.set(NetworkEvent.FETCH_ERROR, new Map())57this.#listener.set(NetworkEvent.RESPONSE_STARTED, new Map())58this.#listener.set(NetworkEvent.RESPONSE_COMPLETED, new Map())59}6061addCallback(eventType, callback) {62const id = ++this.#callbackId6364const eventCallbackMap = this.#listener.get(eventType)65eventCallbackMap.set(id, callback)66return id67}6869removeCallback(id) {70let hasId = false71for (const [, callbacks] of this.#listener) {72if (callbacks.has(id)) {73callbacks.delete(id)74hasId = true75}76}7778if (!hasId) {79throw Error(`Callback with id ${id} not found`)80}81}8283invokeCallbacks(eventType, data) {84const callbacks = this.#listener.get(eventType)85if (callbacks) {86for (const [, callback] of callbacks) {87callback(data)88}89}90}9192async init() {93this.bidi = await this._driver.getBidi()94}9596/**97* Subscribes to the 'network.beforeRequestSent' event and handles it with the provided callback.98*99* @param {Function} callback - The callback function to handle the event.100* @returns {Promise<void>} - A promise that resolves when the subscription is successful.101*/102async beforeRequestSent(callback) {103await this.subscribeAndHandleEvent('network.beforeRequestSent', callback)104}105106/**107* Subscribes to the 'network.responseStarted' event and handles it with the provided callback.108*109* @param {Function} callback - The callback function to handle the event.110* @returns {Promise<void>} - A promise that resolves when the subscription is successful.111*/112async responseStarted(callback) {113await this.subscribeAndHandleEvent('network.responseStarted', callback)114}115116/**117* Subscribes to the 'network.responseCompleted' event and handles it with the provided callback.118*119* @param {Function} callback - The callback function to handle the event.120* @returns {Promise<void>} - A promise that resolves when the subscription is successful.121*/122async responseCompleted(callback) {123await this.subscribeAndHandleEvent('network.responseCompleted', callback)124}125126/**127* Subscribes to the 'network.authRequired' event and handles it with the provided callback.128*129* @param {Function} callback - The callback function to handle the event.130* @returns {Promise<number>} - A promise that resolves when the subscription is successful.131*/132async authRequired(callback) {133return await this.subscribeAndHandleEvent('network.authRequired', callback)134}135136/**137* Subscribes to the 'network.fetchError' event and handles it with the provided callback.138*139* @param {Function} callback - The callback function to handle the event.140* @returns {Promise<void>} - A promise that resolves when the subscription is successful.141*/142async fetchError(callback) {143await this.subscribeAndHandleEvent('network.fetchError', callback)144}145146async subscribeAndHandleEvent(eventType, callback) {147if (this._browsingContextIds != null) {148await this.bidi.subscribe(eventType, this._browsingContextIds)149} else {150await this.bidi.subscribe(eventType)151}152let id = this.addCallback(eventType, callback)153154this.ws = await this.bidi.socket155this.ws.on('message', (event) => {156const { params } = JSON.parse(Buffer.from(event.toString()))157if (params) {158let response = null159if ('initiator' in params) {160response = new BeforeRequestSent(161params.context,162params.navigation,163params.redirectCount,164params.request,165params.timestamp,166params.initiator,167)168} else if ('response' in params) {169response = new ResponseStarted(170params.context,171params.navigation,172params.redirectCount,173params.request,174params.timestamp,175params.response,176)177} else if ('errorText' in params) {178response = new FetchError(179params.context,180params.navigation,181params.redirectCount,182params.request,183params.timestamp,184params.errorText,185)186}187this.invokeCallbacks(eventType, response)188}189})190return id191}192193/**194* Adds a network intercept.195*196* @param {AddInterceptParameters} params - The parameters for the network intercept.197* @returns {Promise<string>} - A promise that resolves to the added intercept's id.198* @throws {Error} - If params is not an instance of AddInterceptParameters.199*/200async addIntercept(params) {201if (!(params instanceof AddInterceptParameters)) {202throw new Error(`Params must be an instance of AddInterceptParameters. Received:'${params}'`)203}204205const command = {206method: 'network.addIntercept',207params: Object.fromEntries(params.asMap()),208}209210let response = await this.bidi.send(command)211212return response.result.intercept213}214215/**216* Removes an intercept.217*218* @param {string} interceptId - The ID of the intercept to be removed.219* @returns {Promise<void>} - A promise that resolves when the intercept is successfully removed.220*/221async removeIntercept(interceptId) {222const command = {223method: 'network.removeIntercept',224params: { intercept: interceptId },225}226227await this.bidi.send(command)228}229230/**231* Continues the network request with authentication credentials.232* @param {string} requestId - The ID of the request to continue.233* @param {string} username - The username for authentication.234* @param {string} password - The password for authentication.235* @returns {Promise<void>} - A promise that resolves when the command is sent.236*/237async continueWithAuth(requestId, username, password) {238const command = {239method: 'network.continueWithAuth',240params: {241request: requestId.toString(),242action: 'provideCredentials',243credentials: {244type: 'password',245username: username,246password: password,247},248},249}250await this.bidi.send(command)251}252253/**254* Fails a network request.255*256* @param {number} requestId - The ID of the request to fail.257* @returns {Promise<void>} - A promise that resolves when the command is sent.258*/259async failRequest(requestId) {260const command = {261method: 'network.failRequest',262params: {263request: requestId.toString(),264},265}266await this.bidi.send(command)267}268269/**270* Continues the network request with authentication but without providing credentials.271* @param {string} requestId - The ID of the request to continue with authentication.272* @returns {Promise<void>} - A promise that resolves when the command is sent.273*/274async continueWithAuthNoCredentials(requestId) {275const command = {276method: 'network.continueWithAuth',277params: {278request: requestId.toString(),279action: 'default',280},281}282await this.bidi.send(command)283}284285/**286* Cancels the authentication for a specific request.287*288* @param {string} requestId - The ID of the request to cancel authentication for.289* @returns {Promise<void>} - A promise that resolves when the command is sent.290*/291async cancelAuth(requestId) {292const command = {293method: 'network.continueWithAuth',294params: {295request: requestId.toString(),296action: 'cancel',297},298}299await this.bidi.send(command)300}301302/**303* Continues the network request with the provided parameters.304*305* @param {ContinueRequestParameters} params - The parameters for continuing the request.306* @throws {Error} If params is not an instance of ContinueRequestParameters.307* @returns {Promise<void>} A promise that resolves when the command is sent.308*/309async continueRequest(params) {310if (!(params instanceof ContinueRequestParameters)) {311throw new Error(`Params must be an instance of ContinueRequestParameters. Received:'${params}'`)312}313314const command = {315method: 'network.continueRequest',316params: Object.fromEntries(params.asMap()),317}318319await this.bidi.send(command)320}321322/**323* Continues the network response with the given parameters.324*325* @param {ContinueResponseParameters} params - The parameters for continuing the response.326* @throws {Error} If params is not an instance of ContinueResponseParameters.327* @returns {Promise<void>} A promise that resolves when the command is sent.328*/329async continueResponse(params) {330if (!(params instanceof ContinueResponseParameters)) {331throw new Error(`Params must be an instance of ContinueResponseParameters. Received:'${params}'`)332}333334const command = {335method: 'network.continueResponse',336params: Object.fromEntries(params.asMap()),337}338339await this.bidi.send(command)340}341342/**343* Provides a response for the network.344*345* @param {ProvideResponseParameters} params - The parameters for providing the response.346* @throws {Error} If params is not an instance of ProvideResponseParameters.347* @returns {Promise<void>} A promise that resolves when the command is sent.348*/349async provideResponse(params) {350if (!(params instanceof ProvideResponseParameters)) {351throw new Error(`Params must be an instance of ProvideResponseParameters. Received:'${params}'`)352}353354const command = {355method: 'network.provideResponse',356params: Object.fromEntries(params.asMap()),357}358359await this.bidi.send(command)360}361362/**363* Sets the cache behavior for network requests.364*365* @param {string} behavior - The cache behavior ("default" or "bypass")366* @param {Array<string>} [contexts] - Optional array of browsing context IDs367* @returns {Promise<void>} A promise that resolves when the cache behavior is set368* @throws {Error} If behavior is invalid or context IDs are invalid369*/370async setCacheBehavior(behavior, contexts = null) {371if (!Object.values(CacheBehavior).includes(behavior)) {372throw new Error(`Cache behavior must be either "${CacheBehavior.DEFAULT}" or "${CacheBehavior.BYPASS}"`)373}374375const command = {376method: 'network.setCacheBehavior',377params: {378cacheBehavior: behavior,379},380}381382if (contexts !== null) {383if (384!Array.isArray(contexts) ||385contexts.length === 0 ||386contexts.some((c) => typeof c !== 'string' || c.trim() === '')387) {388throw new Error('Contexts must be an array of non-empty strings')389}390command.params.contexts = contexts391}392393await this.bidi.send(command)394}395396/**397* Unsubscribes from network events for all browsing contexts.398* @returns {Promise<void>} A promise that resolves when the network connection is closed.399*/400async close() {401if (402this._browsingContextIds !== null &&403this._browsingContextIds !== undefined &&404this._browsingContextIds.length > 0405) {406await this.bidi.unsubscribe(407'network.beforeRequestSent',408'network.responseStarted',409'network.responseCompleted',410'network.authRequired',411this._browsingContextIds,412)413} else {414await this.bidi.unsubscribe(415'network.beforeRequestSent',416'network.responseStarted',417'network.responseCompleted',418'network.authRequired',419)420}421}422}423424async function getNetworkInstance(driver, browsingContextIds = null) {425let instance = new Network(driver, browsingContextIds)426await instance.init()427return instance428}429430module.exports = { Network: getNetworkInstance, CacheBehavior }431432433