| (function() { |
| "use strict"; |
| // Define functions one by one and do not override the whole |
| // test_driver_internal as it masks the new testing fucntions |
| // that will be added in the future. |
| const leftButton = 0; |
| |
| function getInViewCenterPoint(rect) { |
| var left = Math.max(0, rect.left); |
| var right = Math.min(window.innerWidth, rect.right); |
| var top = Math.max(0, rect.top); |
| var bottom = Math.min(window.innerHeight, rect.bottom); |
| |
| var x = 0.5 * (left + right); |
| var y = 0.5 * (top + bottom); |
| |
| return [x, y]; |
| } |
| |
| function getPointerInteractablePaintTree(element, frame) { |
| var frameDocument = frame == window ? window.document : frame.contentDocument; |
| if (!frameDocument.contains(element)) { |
| return []; |
| } |
| |
| var rectangles = element.getClientRects(); |
| if (rectangles.length === 0) { |
| return []; |
| } |
| |
| var centerPoint = getInViewCenterPoint(rectangles[0]); |
| if ("elementsFromPoint" in document) { |
| return frameDocument.elementsFromPoint(centerPoint[0], centerPoint[1]); |
| } else if ("msElementsFromPoint" in document) { |
| var rv = frameDocument.msElementsFromPoint(centerPoint[0], centerPoint[1]); |
| return Array.prototype.slice.call(rv ? rv : []); |
| } else { |
| throw new Error("document.elementsFromPoint unsupported"); |
| } |
| } |
| |
| function inView(element, frame) { |
| var pointerInteractablePaintTree = getPointerInteractablePaintTree(element, frame); |
| return pointerInteractablePaintTree.indexOf(element) !== -1 || element.contains(pointerInteractablePaintTree[0], frame); |
| } |
| |
| function findElementInFrame(element, frame) { |
| var foundFrame = frame; |
| var frameDocument = frame == window ? window.document : frame.contentDocument; |
| if (!frameDocument.contains(element)) { |
| foundFrame = null; |
| var frames = document.getElementsByTagName("iframe"); |
| for (let i = 0; i < frames.length; i++) { |
| if (findElementInFrame(element, frames[i])) { |
| foundFrame = frames[i]; |
| break; |
| } |
| } |
| } |
| return foundFrame; |
| } |
| |
| window.test_driver_internal.click = function(element, coords) { |
| return new Promise(function(resolve, reject) { |
| if (window.chrome && chrome.gpuBenchmarking) { |
| chrome.gpuBenchmarking.pointerActionSequence( |
| [{ |
| source: 'mouse', |
| actions: [ |
| {name: 'pointerMove', x: coords.x, y: coords.y}, |
| {name: 'pointerDown', x: coords.x, y: coords.y, button: leftButton}, |
| {name: 'pointerUp', button: leftButton} |
| ] |
| }], |
| resolve); |
| } else { |
| reject(new Error("GPU benchmarking is not enabled.")); |
| } |
| }); |
| }; |
| |
| // https://w3c.github.io/webdriver/#element-send-keys |
| window.test_driver_internal.send_keys = function(element, keys) { |
| return new Promise((resolve, reject) => { |
| element.focus(); |
| if (!window.eventSender) |
| reject(new Error("No eventSender")); |
| if (element.localName === 'input' && element.type === 'file') { |
| element.addEventListener('drop', resolve); |
| eventSender.beginDragWithFiles([keys]); |
| const centerX = element.offsetLeft + element.offsetWidth / 2; |
| const centerY = element.offsetTop + element.offsetHeight / 2; |
| // Moving the mouse could interfere with the test, if it also tries to control |
| // mouse movements. This can cause differences between tests run with run_web_tests |
| // and tests run with wptrunner. |
| eventSender.mouseMoveTo(centerX * devicePixelRatio, centerY * devicePixelRatio); |
| eventSender.mouseUp(); |
| return; |
| } |
| window.requestAnimationFrame(() => { |
| for(var i = 0; i < keys.length; ++i) { |
| let eventSenderKeys = keys[i]; |
| let charCode = keys.charCodeAt(i); |
| let modifierValue; |
| // See https://w3c.github.io/webdriver/#keyboard-actions and |
| // EventSender::KeyDown(). |
| if (charCode == 0xE004) { |
| eventSenderKeys = "Tab"; |
| } else if (charCode == 0xE050) { |
| eventSenderKeys = "ShiftRight"; |
| } else if (charCode == 0xE012) { |
| eventSenderKeys = "ArrowLeft"; |
| } else if (charCode == 0xE013) { |
| eventSenderKeys = "ArrowUp"; |
| } else if (charCode == 0xE014) { |
| eventSenderKeys = "ArrowRight"; |
| } else if (charCode == 0xE015) { |
| eventSenderKeys = "ArrowDown"; |
| } else if (charCode == 0xE00C) { |
| eventSenderKeys = "Escape"; |
| } else if (charCode == 0xE003) { |
| eventSenderKeys = "Backspace"; |
| } else if (charCode == 0xE009) { |
| eventSenderKeys = "ControlLeft"; |
| modifierValue = "ctrlKey"; |
| } else if (charCode == 0xE00A) { |
| eventSenderKeys = "AltLeft"; |
| modifierValue = "altKey"; |
| } else if (charCode == 0xE03D) { |
| eventSenderKeys = "MetaLeft"; |
| modifierValue = "metaKey"; |
| } else if (charCode == 0xE008) { |
| eventSenderKeys = "ShiftLeft"; |
| modifierValue = "shiftKey"; |
| } else if (charCode == 0xE006) { |
| eventSenderKeys = "Enter"; |
| modifierValue = "enter"; |
| } else if (charCode >= 0xE000 && charCode <= 0xF8FF) { |
| reject(new Error("No support for this code: U+" + charCode.toString(16))); |
| return; |
| } |
| |
| window.eventSender.keyDown(eventSenderKeys, modifierValue); |
| } |
| resolve(); |
| }); |
| }); |
| }; |
| |
| window.test_driver_internal.freeze = function() { |
| return new Promise(function(resolve, reject) { |
| if (window.chrome && chrome.gpuBenchmarking) { |
| chrome.gpuBenchmarking.freeze(); |
| resolve(); |
| } else { |
| reject(new Error("GPU benchmarking is not enabled.")); |
| } |
| }); |
| }; |
| |
| window.test_driver_internal.generate_test_report = function(message) { |
| return new Promise(function(resolve, reject) { |
| if (internals) { |
| internals.generateTestReport(message); |
| resolve(); |
| } else { |
| reject(new Error("window.internals not enabled.")); |
| } |
| }); |
| }; |
| |
| window.test_driver_internal.action_sequence = function(actions) { |
| if (window.top !== window) { |
| return Promise.reject(new Error("can only send actions in top-level window")); |
| } |
| |
| var didScrollIntoView = false; |
| for (let i = 0; i < actions.length; i++) { |
| var last_x_position = 0; |
| var last_y_position = 0; |
| var first_pointer_down = false; |
| for (let j = 0; j < actions[i].actions.length; j++) { |
| if (actions[i].actions[j].type == "keyDown" || |
| actions[i].actions[j].type == "keyUp") { |
| return Promise.reject(new Error("we do not support keydown and keyup actions, " + |
| "please use test_driver.send_keys")); |
| } |
| |
| if ('origin' in actions[i].actions[j]) { |
| if (typeof(actions[i].actions[j].origin) === 'string') { |
| if (actions[i].actions[j].origin == "viewport") { |
| last_x_position = actions[i].actions[j].x; |
| last_y_position = actions[i].actions[j].y; |
| } else if (actions[i].actions[j].origin == "pointer") { |
| return Promise.reject(new Error("pointer origin is not supported right now")); |
| } else { |
| return Promise.reject(new Error("pointer origin is not given correctly")); |
| } |
| } else { |
| var element = actions[i].actions[j].origin; |
| var frame = findElementInFrame(element, window); |
| if (frame == null) { |
| return Promise.reject(new Error("element in different document or iframe")); |
| } |
| |
| if (!inView(element, frame)) { |
| if (didScrollIntoView) |
| return Promise.reject(new Error("already scrolled into view, the element is not found")); |
| |
| element.scrollIntoView({behavior: "instant", |
| block: "end", |
| inline: "nearest"}); |
| didScrollIntoView = true; |
| } |
| |
| var pointerInteractablePaintTree = getPointerInteractablePaintTree(element, frame); |
| if (pointerInteractablePaintTree.length === 0 || |
| !element.contains(pointerInteractablePaintTree[0])) { |
| return Promise.reject(new Error("element click intercepted error")); |
| } |
| |
| var rect = element.getClientRects()[0]; |
| var centerPoint = getInViewCenterPoint(rect); |
| last_x_position = actions[i].actions[j].x + centerPoint[0]; |
| last_y_position = actions[i].actions[j].y + centerPoint[1]; |
| if (frame != window) { |
| var frameRect = frame.getClientRects(); |
| last_x_position += frameRect[0].left; |
| last_y_position += frameRect[0].top; |
| } |
| } |
| } |
| |
| if (actions[i].actions[j].type == "pointerDown" || |
| actions[i].actions[j].type == "pointerMove" || |
| actions[i].actions[j].type == "scroll") { |
| actions[i].actions[j].x = last_x_position; |
| actions[i].actions[j].y = last_y_position; |
| } |
| |
| if ('parameters' in actions[i] && actions[i].parameters.pointerType == "touch") { |
| if (actions[i].actions[j].type == "pointerMove" && !first_pointer_down) { |
| actions[i].actions[j].type = "pause"; |
| } else if (actions[i].actions[j].type == "pointerDown") { |
| first_pointer_down = true; |
| } else if (actions[i].actions[j].type == "pointerUp") { |
| first_pointer_down = false; |
| } |
| } |
| } |
| } |
| |
| return new Promise(function(resolve, reject) { |
| if (window.chrome && chrome.gpuBenchmarking) { |
| chrome.gpuBenchmarking.pointerActionSequence(actions, resolve); |
| } else { |
| reject(new Error("GPU benchmarking is not enabled.")); |
| } |
| }); |
| }; |
| |
| let virtualAuthenticatorManager_; |
| |
| async function findAuthenticator(authenticatorManager, authenticatorId) { |
| let authenticators = (await authenticatorManager.getAuthenticators()).authenticators; |
| let foundAuthenticator; |
| for (let authenticator of authenticators) { |
| if ((await authenticator.getUniqueId()).id == authenticatorId) { |
| foundAuthenticator = authenticator; |
| break; |
| } |
| } |
| if (!foundAuthenticator) { |
| throw "Cannot find authenticator with ID " + authenticatorId; |
| } |
| return foundAuthenticator; |
| } |
| |
| async function loadVirtualAuthenticatorManager() { |
| if (!virtualAuthenticatorManager_) { |
| const {VirtualAuthenticatorManager} = await import( |
| '/gen/third_party/blink/public/mojom/webauthn/virtual_authenticator.mojom.m.js'); |
| virtualAuthenticatorManager_ = VirtualAuthenticatorManager.getRemote(); |
| } |
| return virtualAuthenticatorManager_; |
| } |
| |
| function urlSafeBase64ToUint8Array(base64url) { |
| let base64 = base64url.replace(/-/g, "+").replace(/_/g, "/"); |
| // Add padding to make the length of the base64 string divisible by 4. |
| if (base64.length % 4 != 0) |
| base64 += "=".repeat(4 - base64.length % 4); |
| return Uint8Array.from(atob(base64), c => c.charCodeAt(0)); |
| } |
| |
| function uint8ArrayToUrlSafeBase64(array) { |
| let binary = ""; |
| for (let i = 0; i < array.length; ++i) |
| binary += String.fromCharCode(array[i]); |
| |
| return window.btoa(binary) |
| .replace(/\+/g, "-") |
| .replace(/\//g, "_") |
| .replace(/=/g, ""); |
| } |
| |
| window.test_driver_internal.add_virtual_authenticator = async function(options) { |
| let manager = await loadVirtualAuthenticatorManager(); |
| |
| const {AuthenticatorAttachment, AuthenticatorTransport} = await import( |
| '/gen/third_party/blink/public/mojom/webauthn/authenticator.mojom.m.js'); |
| const {ClientToAuthenticatorProtocol, Ctap2Version} = await import( |
| '/gen/third_party/blink/public/mojom/webauthn/virtual_authenticator.mojom.m.js'); |
| |
| options = Object.assign({ |
| hasResidentKey: false, |
| hasUserVerification: false, |
| isUserConsenting: true, |
| isUserVerified: false, |
| extensions: [], |
| }, options); |
| let mojoOptions = {}; |
| switch (options.protocol) { |
| case "ctap1/u2f": |
| mojoOptions.protocol = ClientToAuthenticatorProtocol.U2F; |
| break; |
| case "ctap2": |
| mojoOptions.protocol = ClientToAuthenticatorProtocol.CTAP2; |
| mojoOptions.ctap2Version = Ctap2Version.CTAP2_0; |
| break; |
| case "ctap2_1": |
| mojoOptions.protocol = ClientToAuthenticatorProtocol.CTAP2; |
| mojoOptions.ctap2Version = Ctap2Version.CTAP2_1; |
| break; |
| default: |
| throw "Unknown protocol " + options.protocol; |
| } |
| switch (options.transport) { |
| case "usb": |
| mojoOptions.transport = AuthenticatorTransport.USB; |
| mojoOptions.attachment = AuthenticatorAttachment.CROSS_PLATFORM; |
| break; |
| case "nfc": |
| mojoOptions.transport = AuthenticatorTransport.NFC; |
| mojoOptions.attachment = AuthenticatorAttachment.CROSS_PLATFORM; |
| break; |
| case "ble": |
| mojoOptions.transport = AuthenticatorTransport.BLE; |
| mojoOptions.attachment = AuthenticatorAttachment.CROSS_PLATFORM; |
| break; |
| case "internal": |
| mojoOptions.transport = AuthenticatorTransport.INTERNAL; |
| mojoOptions.attachment = AuthenticatorAttachment.PLATFORM; |
| break; |
| default: |
| throw "Unknown transport " + options.transport; |
| } |
| mojoOptions.hasResidentKey = options.hasResidentKey; |
| mojoOptions.hasUserVerification = options.hasUserVerification; |
| mojoOptions.hasLargeBlob = options.extensions.indexOf("largeBlob") !== -1; |
| mojoOptions.isUserPresent = options.isUserConsenting; |
| |
| let authenticator = (await manager.createAuthenticator(mojoOptions)).authenticator; |
| await authenticator.setUserVerified(options.isUserVerified); |
| return (await authenticator.getUniqueId()).id; |
| }; |
| |
| window.test_driver_internal.add_credential = async function(authenticatorId, credential) { |
| if (credential.isResidentCredential) { |
| throw "The mojo virtual authenticator manager does not support resident credentials"; |
| } |
| let manager = await loadVirtualAuthenticatorManager(); |
| let authenticator = await findAuthenticator(manager, authenticatorId); |
| |
| let registration = { |
| keyHandle: urlSafeBase64ToUint8Array(credential.credentialId), |
| privateKey: urlSafeBase64ToUint8Array(credential.privateKey), |
| rpId: credential.rpId, |
| counter: credential.signCount, |
| }; |
| let addRegistrationResponse = await authenticator.addRegistration(registration); |
| if (!addRegistrationResponse.added) { |
| throw "Could not add credential"; |
| } |
| }; |
| |
| window.test_driver_internal.get_credentials = async function(authenticatorId) { |
| let manager = await loadVirtualAuthenticatorManager(); |
| let authenticator = await findAuthenticator(manager, authenticatorId); |
| |
| let getCredentialsResponse = await authenticator.getRegistrations(); |
| return getCredentialsResponse.keys.map(key => ({ |
| credentialId: uint8ArrayToUrlSafeBase64(key.keyHandle), |
| privateKey: uint8ArrayToUrlSafeBase64(key.privateKey), |
| rpId: key.rpId, |
| signCount: key.counter, |
| isResidentCredential: false, |
| })); |
| }; |
| |
| window.test_driver_internal.remove_credential = async function(authenticatorId, credentialId) { |
| let manager = await loadVirtualAuthenticatorManager(); |
| let authenticator = await findAuthenticator(manager, authenticatorId); |
| |
| let removeRegistrationResponse = await authenticator.removeRegistration( |
| urlSafeBase64ToUint8Array(credentialId)); |
| if (!removeRegistrationResponse.removed) { |
| throw "Could not remove credential"; |
| } |
| }; |
| |
| window.test_driver_internal.remove_all_credentials = async function(authenticatorId) { |
| let manager = await loadVirtualAuthenticatorManager(); |
| let authenticator = await findAuthenticator(manager, authenticatorId); |
| await authenticator.clearRegistrations(); |
| } |
| |
| window.test_driver_internal.set_user_verified = async function(authenticatorId, options) { |
| let manager = await loadVirtualAuthenticatorManager(); |
| let authenticator = await findAuthenticator(manager, authenticatorId); |
| await authenticator.setUserVerified(options.isUserVerified); |
| } |
| |
| window.test_driver_internal.remove_virtual_authenticator = async function(authenticatorId) { |
| let manager = await loadVirtualAuthenticatorManager(); |
| let response = await manager.removeAuthenticator(authenticatorId); |
| if (!response.removed) |
| throw "Could not remove authenticator"; |
| } |
| |
| window.test_driver_internal.set_permission = function(permission_params) { |
| // TODO(https://crbug.com/977612): Chromium currently lacks support for |
| // |permission_params.one_realm| and will always consider it is set to false. |
| return internals.setPermission(permission_params.descriptor, |
| permission_params.state); |
| } |
| |
| window.test_driver_internal.set_storage_access = function(origin, embedding_origin, blocked) { |
| return internals.setStorageAccess(origin, embedding_origin, blocked); |
| } |
| |
| // Enable automation so we don't wait for user input on unimplemented APIs |
| window.test_driver_internal.in_automation = true; |
| |
| })(); |