| // Licensed to the Software Freedom Conservancy (SFC) under one |
| // or more contributor license agreements. See the NOTICE file |
| // distributed with this work for additional information |
| // regarding copyright ownership. The SFC licenses this file |
| // to you under the Apache License, Version 2.0 (the |
| // "License"); you may not use this file except in compliance |
| // with the License. You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, |
| // software distributed under the License is distributed on an |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| // KIND, either express or implied. See the License for the |
| // specific language governing permissions and limitations |
| // under the License. |
| |
| const BiDi = require('../bidi') |
| |
| /** |
| * One BiDi connection per driver, kept outside the WebDriver class itself so |
| * it isn't a discoverable method on it — see |
| * docs/decisions/17670-bidi-implementation-boundaries.md. Not re-exported from |
| * the package's public entry point. |
| * |
| * Stores the in-flight promise, not the resolved connection, so a second |
| * concurrent call for the same driver awaits the same creation instead of |
| * racing its own and leaking whichever connection loses. |
| * @type {WeakMap<object, Promise<BiDi>>} |
| */ |
| const connections = new WeakMap() |
| |
| /** |
| * Returns the BiDi connection for `driver`, creating it on first access. |
| * @param {object} driver The WebDriver instance to obtain a BiDi connection for. |
| * @returns {Promise<BiDi>} A promise resolving to `driver`'s BiDi connection, shared |
| * with any other in-flight or already-resolved call for the same driver. |
| */ |
| function getBidiConnection(driver) { |
| if (!connections.has(driver)) { |
| connections.set(driver, createConnection(driver)) |
| } |
| return connections.get(driver) |
| } |
| |
| /** |
| * @param {object} driver |
| * @returns {Promise<BiDi>} |
| */ |
| async function createConnection(driver) { |
| const caps = await driver.getCapabilities() |
| const webSocketUrl = caps['map_'].get('webSocketUrl') |
| if (!webSocketUrl) { |
| throw new Error('WebDriver instance must support BiDi protocol') |
| } |
| return new BiDi(webSocketUrl.replace('localhost', '127.0.0.1')) |
| } |
| |
| /** |
| * Closes `driver`'s BiDi connection, if one was ever opened. A no-op |
| * otherwise — must not lazily create a connection just to close it. |
| * |
| * Callers (e.g. quit()) invoke this fire-and-forget, so it must never reject: |
| * if the original connection attempt itself had failed, `pending` is already |
| * rejected and there is nothing live to close. |
| * @param {object} driver The WebDriver instance whose BiDi connection should be closed. |
| * @returns {Promise<void>} A promise that always resolves, once any open connection |
| * has been closed (or immediately, if none was ever opened). |
| */ |
| async function closeBidiConnection(driver) { |
| const pending = connections.get(driver) |
| if (pending === undefined) { |
| return |
| } |
| connections.delete(driver) |
| try { |
| const connection = await pending |
| await connection.close() |
| } catch { |
| // Nothing to close — the original connection attempt failed. |
| } |
| } |
| |
| module.exports = { getBidiConnection, closeBidiConnection } |