blob: 080df45cd4133c7ea3f033e32ad3e5c5e6e4c4b8 [file] [log] [blame]
(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;
})();