diff options
Diffstat (limited to 'remote/cdp/test/browser/runtime')
20 files changed, 2702 insertions, 0 deletions
diff --git a/remote/cdp/test/browser/runtime/browser.ini b/remote/cdp/test/browser/runtime/browser.ini new file mode 100644 index 0000000000..f039968784 --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser.ini @@ -0,0 +1,32 @@ +[DEFAULT] +tags = cdp +subsuite = remote +args = + --remote-debugging-port + --remote-allow-origins=null +prefs = # Bug 1600054: Make CDP Fission compatible + fission.bfcacheInParent=false + fission.webContentIsolationStrategy=0 +support-files = + !/remote/cdp/test/browser/chrome-remote-interface.js + !/remote/cdp/test/browser/head.js + doc_console_events.html + doc_console_events_onload.html + doc_empty.html + doc_frame.html + doc_frameset_single.html + head.js + +[browser_callFunctionOn.js] +[browser_callFunctionOn_awaitPromise.js] +[browser_callFunctionOn_returnByValue.js] +[browser_consoleAPICalled.js] +https_first_disabled = true +[browser_evaluate.js] +[browser_evaluate_awaitPromise.js] +[browser_evaluate_returnByValue.js] +[browser_exceptionThrown.js] +https_first_disabled = true +[browser_executionContextEvents.js] +[browser_getProperties.js] +[browser_remoteObjects.js] diff --git a/remote/cdp/test/browser/runtime/browser_callFunctionOn.js b/remote/cdp/test/browser/runtime/browser_callFunctionOn.js new file mode 100644 index 0000000000..ce0c5e7e7a --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser_callFunctionOn.js @@ -0,0 +1,304 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_DOC = toDataURL("default-test-page"); + +add_task(async function FunctionDeclarationMissing({ client }) { + const { Runtime } = client; + + let errorThrown = ""; + try { + await Runtime.callFunctionOn(); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("functionDeclaration: string value expected")); +}); + +add_task(async function functionDeclarationInvalidTypes({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + for (const functionDeclaration of [null, true, 1, [], {}]) { + let errorThrown = ""; + try { + await Runtime.callFunctionOn({ functionDeclaration, executionContextId }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("functionDeclaration: string value expected")); + } +}); + +add_task(async function functionDeclarationGetCurrentLocation({ client }) { + const { Runtime } = client; + + await loadURL(TEST_DOC); + const { id: executionContextId } = await enableRuntime(client); + + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: "() => location.href", + executionContextId, + }); + is(result.value, TEST_DOC, "Works against the test page"); +}); + +add_task(async function argumentsInvalidTypes({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + for (const args of [null, true, 1, "foo", {}]) { + let errorThrown = ""; + try { + await Runtime.callFunctionOn({ + functionDeclaration: "", + arguments: args, + executionContextId, + }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("arguments: array value expected")); + } +}); + +add_task(async function argumentsPrimitiveTypes({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + for (const args of [null, true, 1, "foo", {}]) { + let errorThrown = ""; + try { + await Runtime.callFunctionOn({ + functionDeclaration: "", + arguments: args, + executionContextId, + }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("arguments: array value expected")); + } +}); + +add_task(async function executionContextIdNorObjectIdSpecified({ client }) { + const { Runtime } = client; + + let errorThrown = ""; + try { + await Runtime.callFunctionOn({ + functionDeclaration: "", + }); + } catch (e) { + errorThrown = e.message; + } + ok( + errorThrown.includes( + "Either objectId or executionContextId must be specified" + ) + ); +}); + +add_task(async function executionContextIdInvalidTypes({ client }) { + const { Runtime } = client; + + for (const executionContextId of [null, true, "foo", [], {}]) { + let errorThrown = ""; + try { + await Runtime.callFunctionOn({ + functionDeclaration: "", + executionContextId, + }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("executionContextId: number value expected")); + } +}); + +add_task(async function executionContextIdInvalidValue({ client }) { + const { Runtime } = client; + + let errorThrown = ""; + try { + await Runtime.callFunctionOn({ + functionDeclaration: "", + executionContextId: -1, + }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("Cannot find context with specified id")); +}); + +add_task(async function objectIdInvalidTypes({ client }) { + const { Runtime } = client; + + for (const objectId of [null, true, 1, [], {}]) { + let errorThrown = ""; + try { + await Runtime.callFunctionOn({ functionDeclaration: "", objectId }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("objectId: string value expected")); + } +}); + +add_task(async function objectId({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + // First create an object + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: "() => ({ foo: 42 })", + executionContextId, + }); + + is(result.type, "object", "The type is correct"); + is(result.subtype, undefined, "The subtype is undefined for objects"); + ok(!!result.objectId, "Got an object id"); + + // Then apply a method on this object + const { result: result2 } = await Runtime.callFunctionOn({ + functionDeclaration: "function () { return this.foo; }", + executionContextId, + objectId: result.objectId, + }); + + is(result2.type, "number", "The type is correct"); + is(result2.subtype, undefined, "The subtype is undefined for numbers"); + is(result2.value, 42, "Expected value returned"); +}); + +add_task(async function objectIdArgumentReference({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + // First create a remote JS object + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: "() => ({ foo: 1 })", + executionContextId, + }); + + is(result.type, "object", "The type is correct"); + is(result.subtype, undefined, "The subtype is undefined for objects"); + ok(!!result.objectId, "Got an object id"); + + // Then increment the `foo` attribute of this JS object, + // while returning this attribute value + const { result: result2 } = await Runtime.callFunctionOn({ + functionDeclaration: "arg => ++arg.foo", + arguments: [{ objectId: result.objectId }], + executionContextId, + }); + + Assert.deepEqual( + result2, + { + type: "number", + value: 2, + }, + "The result has the expected type and value" + ); + + // Finally, try to pass this JS object and get it back. Ensure that it + // returns the same object id. Also increment the attribute again. + const { result: result3 } = await Runtime.callFunctionOn({ + functionDeclaration: "arg => { arg.foo++; return arg; }", + arguments: [{ objectId: result.objectId }], + executionContextId, + }); + + is(result3.type, "object", "The type is correct"); + is(result3.subtype, undefined, "The subtype is undefined for objects"); + // Remote objects don't have unique ids. So you may have multiple object ids + // that reference the same remote object + ok(!!result3.objectId, "Got an object id"); + isnot(result3.objectId, result.objectId, "The object id is different"); + + // Assert that we can still access this object and that its foo attribute + // has been incremented. Use the second object id we got from previous call + // to callFunctionOn. + const { result: result4 } = await Runtime.callFunctionOn({ + functionDeclaration: "arg => arg.foo", + arguments: [{ objectId: result3.objectId }], + executionContextId, + }); + + Assert.deepEqual( + result4, + { + type: "number", + value: 3, + }, + "The result has the expected type and value" + ); +}); + +add_task(async function exceptionDetailsJavascriptError({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { exceptionDetails } = await Runtime.callFunctionOn({ + functionDeclaration: "doesNotExists()", + executionContextId, + }); + + Assert.deepEqual( + exceptionDetails, + { + text: "doesNotExists is not defined", + }, + "Javascript error is passed to the client" + ); +}); + +add_task(async function exceptionDetailsThrowError({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { exceptionDetails } = await Runtime.callFunctionOn({ + functionDeclaration: "() => { throw new Error('foo') }", + executionContextId, + }); + + Assert.deepEqual( + exceptionDetails, + { + text: "foo", + }, + "Exception details are passed to the client" + ); +}); + +add_task(async function exceptionDetailsThrowValue({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { exceptionDetails } = await Runtime.callFunctionOn({ + functionDeclaration: "() => { throw 'foo' }", + executionContextId, + }); + + Assert.deepEqual( + exceptionDetails, + { + exception: { + type: "string", + value: "foo", + }, + }, + "Exception details are passed as a RemoteObject" + ); +}); diff --git a/remote/cdp/test/browser/runtime/browser_callFunctionOn_awaitPromise.js b/remote/cdp/test/browser/runtime/browser_callFunctionOn_awaitPromise.js new file mode 100644 index 0000000000..d15cd8d221 --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser_callFunctionOn_awaitPromise.js @@ -0,0 +1,181 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function awaitPromiseInvalidTypes({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + for (const awaitPromise of [null, 1, "foo", [], {}]) { + let errorThrown = ""; + try { + await Runtime.callFunctionOn({ + functionDeclaration: "", + awaitPromise, + executionContextId, + }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("awaitPromise: boolean value expected")); + } +}); + +add_task(async function awaitPromiseResolve({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: "() => Promise.resolve(42)", + awaitPromise: true, + executionContextId, + }); + + is(result.type, "number", "The type is correct"); + is(result.subtype, undefined, "The subtype is undefined for numbers"); + is(result.value, 42, "The result is the promise's resolution"); +}); + +add_task(async function awaitPromiseDelayedResolve({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: "() => new Promise(r => setTimeout(() => r(42), 0))", + awaitPromise: true, + executionContextId, + }); + is(result.type, "number", "The type is correct"); + is(result.subtype, undefined, "The subtype is undefined for numbers"); + is(result.value, 42, "The result is the promise's resolution"); +}); + +add_task(async function awaitPromiseReject({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { exceptionDetails } = await Runtime.callFunctionOn({ + functionDeclaration: "() => Promise.reject(42)", + awaitPromise: true, + executionContextId, + }); + // TODO: Implement all values for exceptionDetails (bug 1548480) + is( + exceptionDetails.exception.value, + 42, + "The result is the promise's rejection" + ); +}); + +add_task(async function awaitPromiseDelayedReject({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { exceptionDetails } = await Runtime.callFunctionOn({ + functionDeclaration: + "() => new Promise((_,r) => setTimeout(() => r(42), 0))", + awaitPromise: true, + executionContextId, + }); + is( + exceptionDetails.exception.value, + 42, + "The result is the promise's rejection" + ); +}); + +add_task(async function awaitPromiseDelayedRejectError({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { exceptionDetails } = await Runtime.callFunctionOn({ + functionDeclaration: + "() => new Promise((_,r) => setTimeout(() => r(new Error('foo')), 0))", + awaitPromise: true, + executionContextId, + }); + + Assert.deepEqual( + exceptionDetails, + { + text: "foo", + }, + "Exception details are passed to the client" + ); +}); + +add_task(async function awaitPromiseResolveWithoutWait({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: "() => Promise.resolve(42)", + awaitPromise: false, + executionContextId, + }); + + is(result.type, "object", "The type is correct"); + is(result.subtype, "promise", "The subtype is promise"); + ok(!!result.objectId, "We got the object id for the promise"); + ok(!result.value, "We do not receive any value"); +}); + +add_task(async function awaitPromiseDelayedResolveWithoutWait({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: "() => new Promise(r => setTimeout(() => r(42), 0))", + awaitPromise: false, + executionContextId, + }); + + is(result.type, "object", "The type is correct"); + is(result.subtype, "promise", "The subtype is promise"); + ok(!!result.objectId, "We got the object id for the promise"); + ok(!result.value, "We do not receive any value"); +}); + +add_task(async function awaitPromiseRejectWithoutWait({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: "() => Promise.reject(42)", + awaitPromise: false, + executionContextId, + }); + + is(result.type, "object", "The type is correct"); + is(result.subtype, "promise", "The subtype is promise"); + ok(!!result.objectId, "We got the object id for the promise"); + ok(!result.exceptionDetails, "We do not receive any exception"); +}); + +add_task(async function awaitPromiseDelayedRejectWithoutWait({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: + "() => new Promise((_,r) => setTimeout(() => r(42), 0))", + awaitPromise: false, + executionContextId, + }); + + is(result.type, "object", "The type is correct"); + is(result.subtype, "promise", "The subtype is promise"); + ok(!!result.objectId, "We got the object id for the promise"); + ok(!result.exceptionDetails, "We do not receive any exception"); +}); diff --git a/remote/cdp/test/browser/runtime/browser_callFunctionOn_returnByValue.js b/remote/cdp/test/browser/runtime/browser_callFunctionOn_returnByValue.js new file mode 100644 index 0000000000..8ba8e896d9 --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser_callFunctionOn_returnByValue.js @@ -0,0 +1,401 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function returnAsObjectTypes({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const expressions = [ + { expression: "({foo:true})", type: "object", subtype: undefined }, + { expression: "Symbol('foo')", type: "symbol", subtype: undefined }, + { expression: "new Promise(()=>{})", type: "object", subtype: "promise" }, + { expression: "new Int8Array(8)", type: "object", subtype: "typedarray" }, + { expression: "new WeakMap()", type: "object", subtype: "weakmap" }, + { expression: "new WeakSet()", type: "object", subtype: "weakset" }, + { expression: "new Map()", type: "object", subtype: "map" }, + { expression: "new Set()", type: "object", subtype: "set" }, + { expression: "/foo/", type: "object", subtype: "regexp" }, + { expression: "[1, 2]", type: "object", subtype: "array" }, + { expression: "new Proxy({}, {})", type: "object", subtype: "proxy" }, + { expression: "new Date()", type: "object", subtype: "date" }, + { + expression: "document", + type: "object", + subtype: "node", + className: "HTMLDocument", + description: "#document", + }, + { + expression: `{{ + const div = document.createElement('div'); + div.id = "foo"; + return div; + }}`, + type: "object", + subtype: "node", + className: "HTMLDivElement", + description: "div#foo", + }, + ]; + + for (const entry of expressions) { + const { expression, type, subtype, className, description } = entry; + + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: `() => ${expression}`, + executionContextId, + }); + + is(result.type, type, "The type is correct"); + is(result.subtype, subtype, "The subtype is correct"); + ok(!!result.objectId, "Got an object id"); + if (className) { + is(result.className, className, "The className is correct"); + } + if (description) { + is(result.description, description, "The description is correct"); + } + } +}); + +add_task(async function returnAsObjectDifferentObjectIds({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const expressions = [{}, "document"]; + for (const expression of expressions) { + const { result: result1 } = await Runtime.callFunctionOn({ + functionDeclaration: `() => ${JSON.stringify(expression)}`, + executionContextId, + }); + const { result: result2 } = await Runtime.callFunctionOn({ + functionDeclaration: `() => ${JSON.stringify(expression)}`, + executionContextId, + }); + is( + result1.objectId, + result2.objectId, + `Different object ids returned for ${expression}` + ); + } +}); + +add_task(async function returnAsObjectPrimitiveTypes({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const expressions = [42, "42", true, 4.2]; + for (const expression of expressions) { + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: `() => ${JSON.stringify(expression)}`, + executionContextId, + }); + is(result.value, expression, `Evaluating primitive '${expression}' works`); + is(result.type, typeof expression, `${expression} type is correct`); + } +}); + +add_task(async function returnAsObjectNotSerializable({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const notSerializableNumbers = { + number: ["-0", "NaN", "Infinity", "-Infinity"], + bigint: ["42n"], + }; + + for (const type in notSerializableNumbers) { + for (const expression of notSerializableNumbers[type]) { + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: `() => ${expression}`, + executionContextId, + }); + Assert.deepEqual( + result, + { + type, + unserializableValue: expression, + description: expression, + }, + `Evaluating unserializable '${expression}' works` + ); + } + } +}); + +// `null` is special as it has its own subtype, is of type 'object' +// but is returned as a value, without an `objectId` attribute +add_task(async function returnAsObjectNull({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: "() => null", + executionContextId, + }); + Assert.deepEqual( + result, + { + type: "object", + subtype: "null", + value: null, + }, + "Null type is correct" + ); +}); + +// undefined doesn't work with JSON.stringify, so test it independently +add_task(async function returnAsObjectUndefined({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: "() => undefined", + executionContextId, + }); + Assert.deepEqual( + result, + { + type: "undefined", + }, + "Undefined type is correct" + ); +}); + +add_task(async function returnByValueInvalidTypes({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + for (const returnByValue of [null, 1, "foo", [], {}]) { + let errorThrown = ""; + try { + await Runtime.callFunctionOn({ + functionDeclaration: "", + executionContextId, + returnByValue, + }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("returnByValue: boolean value expected")); + } +}); + +add_task(async function returnByValueCyclicValue({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const functionDeclarations = [ + "() => { const b = { a: 1}; b.b = b; return b; }", + "() => window", + ]; + + for (const functionDeclaration of functionDeclarations) { + let errorThrown; + try { + await Runtime.callFunctionOn({ + functionDeclaration, + executionContextId, + returnByValue: true, + }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("Object reference chain is too long")); + } +}); + +add_task(async function returnByValueNotPossible({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const functionDeclarations = [ + "() => Symbol('foo')", + "() => [Symbol('foo')]", + "() => { return {a: Symbol('foo')}; }", + ]; + + for (const functionDeclaration of functionDeclarations) { + let errorThrown; + try { + await Runtime.callFunctionOn({ + functionDeclaration, + executionContextId, + returnByValue: true, + }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("Object couldn't be returned by value")); + } +}); + +add_task(async function returnByValue({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const values = [ + null, + 42, + 42.0, + "42", + true, + false, + { foo: true }, + { foo: { bar: 42, str: "str", array: [1, 2, 3] } }, + [42, "42", true], + [{ foo: true }], + ]; + + for (const value of values) { + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: `() => (${JSON.stringify(value)})`, + executionContextId, + returnByValue: true, + }); + + Assert.deepEqual( + result, + { + type: typeof value, + value, + description: value != null ? value.toString() : value, + }, + "The returned value is the same than the input value" + ); + } +}); + +add_task(async function returnByValueNotSerializable({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const notSerializableNumbers = { + number: ["-0", "NaN", "Infinity", "-Infinity"], + bigint: ["42n"], + }; + + for (const type in notSerializableNumbers) { + for (const unserializableValue of notSerializableNumbers[type]) { + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: `() => (${unserializableValue})`, + executionContextId, + returnByValue: true, + }); + + Assert.deepEqual( + result, + { + type, + unserializableValue, + description: unserializableValue, + }, + "The returned value is the same than the input value" + ); + } + } +}); + +// Test undefined individually as JSON.stringify doesn't return a string +add_task(async function returnByValueUndefined({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: "() => {}", + executionContextId, + returnByValue: true, + }); + + Assert.deepEqual( + result, + { + type: "undefined", + }, + "Undefined type is correct" + ); +}); + +add_task(async function returnByValueArguments({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const values = [ + 42, + 42.0, + "42", + true, + false, + null, + { foo: true }, + { foo: { bar: 42, str: "str", array: [1, 2, 3] } }, + [42, "42", true], + [{ foo: true }], + ]; + + for (const value of values) { + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: "a => a", + arguments: [{ value }], + executionContextId, + returnByValue: true, + }); + + Assert.deepEqual( + result, + { + type: typeof value, + value, + description: value != null ? value.toString() : value, + }, + "The returned value is the same than the input value" + ); + } +}); + +add_task(async function returnByValueArgumentsNotSerializable({ client }) { + const { Runtime } = client; + + const { id: executionContextId } = await enableRuntime(client); + + const notSerializableNumbers = { + number: ["-0", "NaN", "Infinity", "-Infinity"], + bigint: ["42n"], + }; + + for (const type in notSerializableNumbers) { + for (const unserializableValue of notSerializableNumbers[type]) { + const { result } = await Runtime.callFunctionOn({ + functionDeclaration: "a => a", + arguments: [{ unserializableValue }], + executionContextId, + returnByValue: true, + }); + + Assert.deepEqual( + result, + { + type, + unserializableValue, + description: unserializableValue, + }, + "The returned value is the same than the input value" + ); + } + } +}); diff --git a/remote/cdp/test/browser/runtime/browser_consoleAPICalled.js b/remote/cdp/test/browser/runtime/browser_consoleAPICalled.js new file mode 100644 index 0000000000..eac774bd28 --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser_consoleAPICalled.js @@ -0,0 +1,380 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Request a longer timeout as we have many tests which are longer +requestLongerTimeout(2); + +const PAGE_CONSOLE_EVENTS = + "https://example.com/browser/remote/cdp/test/browser/runtime/doc_console_events.html"; +const PAGE_CONSOLE_EVENTS_ONLOAD = + "https://example.com/browser/remote/cdp/test/browser/runtime/doc_console_events_onload.html"; + +add_task(async function noEventsWhenRuntimeDomainDisabled({ client }) { + await runConsoleTest(client, 0, async () => { + SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => + content.console.log("foo") + ); + }); +}); + +add_task(async function noEventsAfterRuntimeDomainDisabled({ client }) { + const { Runtime } = client; + + await Runtime.enable(); + await Runtime.disable(); + + await runConsoleTest(client, 0, async () => { + SpecialPowers.spawn(gBrowser.selectedBrowser, [], () => + content.console.log("foo") + ); + }); +}); + +add_task(async function noEventsForJavascriptErrors({ client }) { + await loadURL(PAGE_CONSOLE_EVENTS); + const context = await enableRuntime(client); + + await runConsoleTest(client, 0, async () => { + evaluate(client, context.id, () => { + document.getElementById("js-error").click(); + }); + }); +}); + +add_task(async function consoleAPI({ client }) { + const context = await enableRuntime(client); + + const events = await runConsoleTest(client, 1, async () => { + await evaluate(client, context.id, () => { + console.log("foo"); + }); + }); + + is(events[0].type, "log", "Got expected type"); + is(events[0].args[0].value, "foo", "Got expected argument value"); + is( + events[0].executionContextId, + context.id, + "Got event from current execution context" + ); +}); + +add_task(async function consoleAPIBeforeEnable({ client }) { + const { Runtime } = client; + const timeBefore = Date.now(); + + const check = async () => { + const events = await runConsoleTest( + client, + 1, + async () => { + await Runtime.enable(); + }, + // Set custom before timestamp as the event is before our callback + { timeBefore } + ); + + is(events[0].type, "log", "Got expected type"); + is(events[0].args[0].value, "foo", "Got expected argument value"); + }; + + // Load the page which runs a log on load + await loadURL(PAGE_CONSOLE_EVENTS_ONLOAD); + await check(); + + // Disable and re-enable Runtime domain, should send event again + await Runtime.disable(); + await check(); +}); + +add_task(async function consoleAPITypes({ client }) { + const expectedEvents = ["dir", "error", "log", "timeEnd", "trace", "warning"]; + const levels = ["dir", "error", "log", "time", "timeEnd", "trace", "warn"]; + + const context = await enableRuntime(client); + + const events = await runConsoleTest( + client, + expectedEvents.length, + async () => { + for (const level of levels) { + await evaluate( + client, + context.id, + level => { + console[level]("foo"); + }, + level + ); + } + } + ); + + events.forEach((event, index) => { + console.log(`Check for event type "${expectedEvents[index]}"`); + is(event.type, expectedEvents[index], "Got expected type"); + }); +}); + +add_task(async function consoleAPIArgumentsCount({ client }) { + const argumentList = [[], ["foo"], ["foo", "bar", "cheese"]]; + + const context = await enableRuntime(client); + + const events = await runConsoleTest(client, argumentList.length, async () => { + for (const args of argumentList) { + await evaluate( + client, + context.id, + args => { + console.log(...args); + }, + args + ); + } + }); + + events.forEach((event, index) => { + console.log(`Check for event args "${argumentList[index]}"`); + + const argValues = event.args.map(arg => arg.value); + Assert.deepEqual(argValues, argumentList[index], "Got expected args"); + }); +}); + +add_task(async function consoleAPIArgumentsAsRemoteObject({ client }) { + const context = await enableRuntime(client); + + const events = await runConsoleTest(client, 1, async () => { + await evaluate(client, context.id, () => { + console.log("foo", Symbol("foo")); + }); + }); + + Assert.equal(events[0].args.length, 2, "Got expected amount of arguments"); + + is(events[0].args[0].type, "string", "Got expected argument type"); + is(events[0].args[0].value, "foo", "Got expected argument value"); + + is(events[0].args[1].type, "symbol", "Got expected argument type"); + is( + events[0].args[1].description, + "Symbol(foo)", + "Got expected argument description" + ); + ok(!!events[0].args[1].objectId, "Got objectId for argument"); +}); + +add_task(async function consoleAPIByContentInteraction({ client }) { + await loadURL(PAGE_CONSOLE_EVENTS); + const context = await enableRuntime(client); + + const events = await runConsoleTest(client, 1, async () => { + evaluate(client, context.id, () => { + document.getElementById("console-error").click(); + }); + }); + + is(events[0].type, "error", "Got expected type"); + is(events[0].args[0].value, "foo", "Got expected argument value"); + is( + events[0].executionContextId, + context.id, + "Got event from current execution context" + ); + + const { callFrames } = events[0].stackTrace; + is(callFrames.length, 1, "Got expected amount of call frames"); + + is(callFrames[0].functionName, "", "Got expected call frame function name"); + is(callFrames[0].lineNumber, 0, "Got expected call frame line number"); + is(callFrames[0].columnNumber, 8, "Got expected call frame column number"); + is( + callFrames[0].url, + "javascript:console.error('foo')", + "Got expected call frame URL" + ); +}); + +add_task(async function consoleAPIByScript({ client }) { + const context = await enableRuntime(client); + + const events = await runConsoleTest(client, 1, async () => { + await evaluate(client, context.id, function runLog() { + console.trace("foo"); + }); + }); + + const { callFrames } = events[0].stackTrace; + is(callFrames.length, 1, "Got expected amount of call frames"); + + is( + callFrames[0].functionName, + "runLog", + "Got expected call frame function name" + ); + is(callFrames[0].lineNumber, 1, "Got expected call frame line number"); + is(callFrames[0].columnNumber, 14, "Got expected call frame column number"); + is(callFrames[0].url, "debugger eval code", "Got expected call frame URL"); +}); + +add_task(async function consoleAPIByScriptSubstack({ client }) { + await loadURL(PAGE_CONSOLE_EVENTS); + const context = await enableRuntime(client); + + const events = await runConsoleTest(client, 1, async () => { + await evaluate(client, context.id, () => { + document.getElementById("log-wrapper").click(); + }); + }); + + const { callFrames } = events[0].stackTrace; + is(callFrames.length, 5, "Got expected amount of call frames"); + + is( + callFrames[0].functionName, + "runLogCaller", + "Got expected call frame function name (frame 1)" + ); + is( + callFrames[0].lineNumber, + 13, + "Got expected call frame line number (frame 1)" + ); + is( + callFrames[0].columnNumber, + 16, + "Got expected call frame column number (frame 1)" + ); + is( + callFrames[0].url, + PAGE_CONSOLE_EVENTS, + "Got expected call frame UR (frame 1)" + ); + + is( + callFrames[1].functionName, + "runLogChild", + "Got expected call frame function name (frame 2)" + ); + is( + callFrames[1].lineNumber, + 17, + "Got expected call frame line number (frame 2)" + ); + is( + callFrames[1].columnNumber, + 8, + "Got expected call frame column number (frame 2)" + ); + is( + callFrames[1].url, + PAGE_CONSOLE_EVENTS, + "Got expected call frame URL (frame 2)" + ); + + is( + callFrames[2].functionName, + "runLogParent", + "Got expected call frame function name (frame 3)" + ); + is( + callFrames[2].lineNumber, + 20, + "Got expected call frame line number (frame 3)" + ); + is( + callFrames[2].columnNumber, + 6, + "Got expected call frame column number (frame 3)" + ); + is( + callFrames[2].url, + PAGE_CONSOLE_EVENTS, + "Got expected call frame URL (frame 3)" + ); + + is( + callFrames[3].functionName, + "onclick", + "Got expected call frame function name (frame 4)" + ); + is( + callFrames[3].lineNumber, + 0, + "Got expected call frame line number (frame 4)" + ); + is( + callFrames[3].columnNumber, + 0, + "Got expected call frame column number (frame 4)" + ); + is( + callFrames[3].url, + PAGE_CONSOLE_EVENTS, + "Got expected call frame URL (frame 4)" + ); + + is( + callFrames[4].functionName, + "", + "Got expected call frame function name (frame 5)" + ); + is( + callFrames[4].lineNumber, + 1, + "Got expected call frame line number (frame 5)" + ); + is( + callFrames[4].columnNumber, + 45, + "Got expected call frame column number (frame 5)" + ); + is( + callFrames[4].url, + "debugger eval code", + "Got expected call frame URL (frame 5)" + ); +}); + +async function runConsoleTest(client, eventCount, callback, options = {}) { + let { timeBefore } = options; + + const { Runtime } = client; + + const EVENT_CONSOLE_API_CALLED = "Runtime.consoleAPICalled"; + + const history = new RecordEvents(eventCount); + history.addRecorder({ + event: Runtime.consoleAPICalled, + eventName: EVENT_CONSOLE_API_CALLED, + messageFn: payload => + `Received ${EVENT_CONSOLE_API_CALLED} for ${payload.type}`, + }); + + timeBefore ??= Date.now(); + await callback(); + + const consoleAPIentries = await history.record(); + is(consoleAPIentries.length, eventCount, "Got expected amount of events"); + + if (eventCount == 0) { + return []; + } + + const timeAfter = Date.now(); + + // Check basic details for consoleAPICalled events + consoleAPIentries.forEach(({ payload }) => { + const timestamp = payload.timestamp; + + ok( + timestamp >= timeBefore && timestamp <= timeAfter, + `Timestamp ${timestamp} in expected range [${timeBefore} - ${timeAfter}]` + ); + }); + + return consoleAPIentries.map(event => event.payload); +} diff --git a/remote/cdp/test/browser/runtime/browser_evaluate.js b/remote/cdp/test/browser/runtime/browser_evaluate.js new file mode 100644 index 0000000000..c5e1965883 --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser_evaluate.js @@ -0,0 +1,256 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const TEST_DOC = toDataURL("default-test-page"); + +add_task(async function contextIdInvalidValue({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + let errorThrown = ""; + try { + await Runtime.evaluate({ expression: "", contextId: -1 }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("Cannot find context with specified id")); +}); + +add_task(async function contextIdNotSpecified({ client }) { + const { Runtime } = client; + + await loadURL(TEST_DOC); + await enableRuntime(client); + + const { result } = await Runtime.evaluate({ expression: "location.href" }); + is(result.value, TEST_DOC, "Works against the current document"); +}); + +add_task(async function contextIdSpecified({ client }) { + const { Runtime } = client; + + await loadURL(TEST_DOC); + const { id: contextId } = await enableRuntime(client); + + const { result } = await Runtime.evaluate({ + expression: "location.href", + contextId, + }); + is(result.value, TEST_DOC, "Works against the targetted document"); +}); + +add_task(async function returnAsObjectTypes({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const expressions = [ + { expression: "({foo:true})", type: "object", subtype: undefined }, + { expression: "Symbol('foo')", type: "symbol", subtype: undefined }, + { expression: "new Promise(()=>{})", type: "object", subtype: "promise" }, + { expression: "new Int8Array(8)", type: "object", subtype: "typedarray" }, + { expression: "new WeakMap()", type: "object", subtype: "weakmap" }, + { expression: "new WeakSet()", type: "object", subtype: "weakset" }, + { expression: "new Map()", type: "object", subtype: "map" }, + { expression: "new Set()", type: "object", subtype: "set" }, + { expression: "/foo/", type: "object", subtype: "regexp" }, + { expression: "[1, 2]", type: "object", subtype: "array" }, + { expression: "new Proxy({}, {})", type: "object", subtype: "proxy" }, + { expression: "new Date()", type: "object", subtype: "date" }, + { + expression: "document", + type: "object", + subtype: "node", + className: "HTMLDocument", + description: "#document", + }, + { + expression: `(() => {{ + const div = document.createElement('div'); + div.id = "foo"; + return div; + }})()`, + type: "object", + subtype: "node", + className: "HTMLDivElement", + description: "div#foo", + }, + ]; + + for (const entry of expressions) { + const { expression, type, subtype, className, description } = entry; + + const { result } = await Runtime.evaluate({ expression }); + + is(result.type, type, "The type is correct"); + is(result.subtype, subtype, "The subtype is correct"); + ok(!!result.objectId, "Got an object id"); + if (className) { + is(result.className, className, "The className is correct"); + } + if (description) { + is(result.description, description, "The description is correct"); + } + } +}); + +add_task(async function returnAsObjectDifferentObjectIds({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const expressions = [{}, "document"]; + for (const expression of expressions) { + const { result: result1 } = await Runtime.evaluate({ + expression: JSON.stringify(expression), + }); + const { result: result2 } = await Runtime.evaluate({ + expression: JSON.stringify(expression), + }); + is( + result1.objectId, + result2.objectId, + `Different object ids returned for ${expression}` + ); + } +}); + +add_task(async function returnAsObjectPrimitiveTypes({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const expressions = [42, "42", true, 4.2]; + for (const expression of expressions) { + const { result } = await Runtime.evaluate({ + expression: JSON.stringify(expression), + }); + is(result.value, expression, `Evaluating primitive '${expression}' works`); + is(result.type, typeof expression, `${expression} type is correct`); + } +}); + +add_task(async function returnAsObjectNotSerializable({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const notSerializableNumbers = { + number: ["-0", "NaN", "Infinity", "-Infinity"], + bigint: ["42n"], + }; + + for (const type in notSerializableNumbers) { + for (const expression of notSerializableNumbers[type]) { + const { result } = await Runtime.evaluate({ expression }); + Assert.deepEqual( + result, + { + type, + unserializableValue: expression, + description: expression, + }, + `Evaluating unserializable '${expression}' works` + ); + } + } +}); + +// `null` is special as it has its own subtype, is of type 'object' +// but is returned as a value, without an `objectId` attribute +add_task(async function returnAsObjectNull({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { result } = await Runtime.evaluate({ + expression: "null", + }); + Assert.deepEqual( + result, + { + type: "object", + subtype: "null", + value: null, + }, + "Null type is correct" + ); +}); + +// undefined doesn't work with JSON.stringify, so test it independently +add_task(async function returnAsObjectUndefined({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { result } = await Runtime.evaluate({ + expression: "undefined", + }); + Assert.deepEqual( + result, + { + type: "undefined", + }, + "Undefined type is correct" + ); +}); + +add_task(async function exceptionDetailsJavascriptError({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { exceptionDetails } = await Runtime.evaluate({ + expression: "doesNotExists()", + }); + + Assert.deepEqual( + exceptionDetails, + { + text: "doesNotExists is not defined", + }, + "Javascript error is passed to the client" + ); +}); + +add_task(async function exceptionDetailsThrowError({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { exceptionDetails } = await Runtime.evaluate({ + expression: "throw new Error('foo')", + }); + + Assert.deepEqual( + exceptionDetails, + { + text: "foo", + }, + "Exception details are passed to the client" + ); +}); + +add_task(async function exceptionDetailsThrowValue({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { exceptionDetails } = await Runtime.evaluate({ + expression: "throw 'foo'", + }); + + Assert.deepEqual( + exceptionDetails, + { + exception: { + type: "string", + value: "foo", + }, + }, + "Exception details are passed as a RemoteObject" + ); +}); diff --git a/remote/cdp/test/browser/runtime/browser_evaluate_awaitPromise.js b/remote/cdp/test/browser/runtime/browser_evaluate_awaitPromise.js new file mode 100644 index 0000000000..b99dfb5ba9 --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser_evaluate_awaitPromise.js @@ -0,0 +1,169 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function awaitPromiseInvalidTypes({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + for (const awaitPromise of [null, 1, "foo", [], {}]) { + let errorThrown = ""; + try { + await Runtime.evaluate({ + expression: "", + awaitPromise, + }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("awaitPromise: boolean value expected")); + } +}); + +add_task(async function awaitPromiseResolve({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { result } = await Runtime.evaluate({ + expression: "Promise.resolve(42)", + awaitPromise: true, + }); + + is(result.type, "number", "The type is correct"); + is(result.subtype, undefined, "The subtype is undefined for numbers"); + is(result.value, 42, "The result is the promise's resolution"); +}); + +add_task(async function awaitPromiseReject({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { exceptionDetails } = await Runtime.evaluate({ + expression: "Promise.reject(42)", + awaitPromise: true, + }); + // TODO: Implement all values for exceptionDetails (bug 1548480) + is( + exceptionDetails.exception.value, + 42, + "The result is the promise's rejection" + ); +}); + +add_task(async function awaitPromiseDelayedResolve({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { result } = await Runtime.evaluate({ + expression: "new Promise(r => setTimeout(() => r(42), 0))", + awaitPromise: true, + }); + is(result.type, "number", "The type is correct"); + is(result.subtype, undefined, "The subtype is undefined for numbers"); + is(result.value, 42, "The result is the promise's resolution"); +}); + +add_task(async function awaitPromiseDelayedReject({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { exceptionDetails } = await Runtime.evaluate({ + expression: "new Promise((_,r) => setTimeout(() => r(42), 0))", + awaitPromise: true, + }); + is( + exceptionDetails.exception.value, + 42, + "The result is the promise's rejection" + ); +}); + +add_task(async function awaitPromiseDelayedRejectError({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { exceptionDetails } = await Runtime.evaluate({ + expression: + "new Promise((_,r) => setTimeout(() => r(new Error('foo')), 0))", + awaitPromise: true, + }); + + Assert.deepEqual( + exceptionDetails, + { + text: "foo", + }, + "Exception details are passed to the client" + ); +}); + +add_task(async function awaitPromiseResolveWithoutWait({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { result } = await Runtime.evaluate({ + expression: "Promise.resolve(42)", + awaitPromise: false, + }); + + is(result.type, "object", "The type is correct"); + is(result.subtype, "promise", "The subtype is promise"); + ok(!!result.objectId, "We got the object id for the promise"); + ok(!result.value, "We do not receive any value"); +}); + +add_task(async function awaitPromiseDelayedResolveWithoutWait({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { result } = await Runtime.evaluate({ + expression: "new Promise(r => setTimeout(() => r(42), 0))", + awaitPromise: false, + }); + + is(result.type, "object", "The type is correct"); + is(result.subtype, "promise", "The subtype is promise"); + ok(!!result.objectId, "We got the object id for the promise"); + ok(!result.value, "We do not receive any value"); +}); + +add_task(async function awaitPromiseRejectWithoutWait({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { result } = await Runtime.evaluate({ + expression: "Promise.reject(42)", + awaitPromise: false, + }); + + is(result.type, "object", "The type is correct"); + is(result.subtype, "promise", "The subtype is promise"); + ok(!!result.objectId, "We got the object id for the promise"); + ok(!result.exceptionDetails, "We do not receive any exception"); +}); + +add_task(async function awaitPromiseDelayedRejectWithoutWait({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { result } = await Runtime.evaluate({ + expression: "new Promise((_,r) => setTimeout(() => r(42), 0))", + awaitPromise: false, + }); + + is(result.type, "object", "The type is correct"); + is(result.subtype, "promise", "The subtype is promise"); + ok(!!result.objectId, "We got the object id for the promise"); + ok(!result.exceptionDetails, "We do not receive any exception"); +}); diff --git a/remote/cdp/test/browser/runtime/browser_evaluate_returnByValue.js b/remote/cdp/test/browser/runtime/browser_evaluate_returnByValue.js new file mode 100644 index 0000000000..dfd816b85a --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser_evaluate_returnByValue.js @@ -0,0 +1,151 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function returnByValueInvalidTypes({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + for (const returnByValue of [null, 1, "foo", [], {}]) { + let errorThrown = ""; + try { + await Runtime.evaluate({ + expression: "", + returnByValue, + }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("returnByValue: boolean value expected")); + } +}); + +add_task(async function returnByValueCyclicValue({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const expressions = ["const b = { a: 1}; b.b = b; b", "window"]; + + for (const expression of expressions) { + let errorThrown; + try { + await Runtime.evaluate({ + expression, + returnByValue: true, + }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("Object reference chain is too long")); + } +}); + +add_task(async function returnByValueNotPossible({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const expressions = ["Symbol(42)", "[Symbol(42)]", "{a: Symbol(42)}"]; + + for (const expression of expressions) { + let errorThrown; + try { + await Runtime.evaluate({ + expression, + returnByValue: true, + }); + } catch (e) { + errorThrown = e.message; + } + ok(errorThrown.includes("Object couldn't be returned by value")); + } +}); + +add_task(async function returnByValue({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const values = [ + null, + 42, + 42.0, + "42", + true, + false, + { foo: true }, + { foo: { bar: 42, str: "str", array: [1, 2, 3] } }, + [42, "42", true], + [{ foo: true }], + ]; + + for (const value of values) { + const { result } = await Runtime.evaluate({ + expression: `(${JSON.stringify(value)})`, + returnByValue: true, + }); + + Assert.deepEqual( + result, + { + type: typeof value, + value, + description: value != null ? value.toString() : value, + }, + `Returned expected value for ${JSON.stringify(value)}` + ); + } +}); + +add_task(async function returnByValueNotSerializable({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const notSerializableNumbers = { + number: ["-0", "NaN", "Infinity", "-Infinity"], + bigint: ["42n"], + }; + + for (const type in notSerializableNumbers) { + for (const unserializableValue of notSerializableNumbers[type]) { + const { result } = await Runtime.evaluate({ + expression: `(${unserializableValue})`, + returnByValue: true, + }); + + Assert.deepEqual( + result, + { + type, + unserializableValue, + description: unserializableValue, + }, + `Returned expected value for ${JSON.stringify(unserializableValue)}` + ); + } + } +}); + +// Test undefined individually as JSON.stringify doesn't return a string +add_task(async function returnByValueUndefined({ client }) { + const { Runtime } = client; + + await enableRuntime(client); + + const { result } = await Runtime.evaluate({ + expression: "undefined", + returnByValue: true, + }); + + Assert.deepEqual( + result, + { + type: "undefined", + }, + "Undefined type is correct" + ); +}); diff --git a/remote/cdp/test/browser/runtime/browser_exceptionThrown.js b/remote/cdp/test/browser/runtime/browser_exceptionThrown.js new file mode 100644 index 0000000000..23e6dc2de9 --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser_exceptionThrown.js @@ -0,0 +1,121 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +const PAGE_CONSOLE_EVENTS = + "https://example.com/browser/remote/cdp/test/browser/runtime/doc_console_events.html"; + +add_task(async function noEventsWhenRuntimeDomainDisabled({ client }) { + await runExceptionThrownTest(client, 0, async () => { + await throwScriptError({ text: "foo" }); + }); +}); + +add_task(async function noEventsAfterRuntimeDomainDisabled({ client }) { + const { Runtime } = client; + + await Runtime.enable(); + await Runtime.disable(); + + await runExceptionThrownTest(client, 0, async () => { + await throwScriptError({ text: "foo" }); + }); +}); + +add_task(async function noEventsForScriptErrorWithoutException({ client }) { + const { Runtime } = client; + + await Runtime.enable(); + + await runExceptionThrownTest(client, 0, async () => { + await throwScriptError({ text: "foo" }); + }); +}); + +add_task(async function eventsForScriptErrorWithException({ client }) { + await loadURL(PAGE_CONSOLE_EVENTS); + + const context = await enableRuntime(client); + + const events = await runExceptionThrownTest(client, 1, async () => { + evaluate(client, context.id, () => { + document.getElementById("js-error").click(); + }); + }); + + is( + typeof events[0].exceptionId, + "number", + "Got expected type for exception id" + ); + is( + events[0].text, + "TypeError: foo.click is not a function", + "Got expected text" + ); + is(events[0].lineNumber, 8, "Got expected line number"); + is(events[0].columnNumber, 10, "Got expected column number"); + is(events[0].url, PAGE_CONSOLE_EVENTS, "Got expected url"); + is( + events[0].executionContextId, + context.id, + "Got event from current execution context" + ); + + const callFrames = events[0].stackTrace.callFrames; + is(callFrames.length, 2, "Got expected amount of call frames"); + + is(callFrames[0].functionName, "throwError", "Got expected function name"); + is(typeof callFrames[0].scriptId, "string", "Got scriptId as string"); + is(callFrames[0].url, PAGE_CONSOLE_EVENTS, "Got expected url"); + is(callFrames[0].lineNumber, 8, "Got expected line number"); + is(callFrames[0].columnNumber, 10, "Got expected column number"); + + is(callFrames[1].functionName, "onclick", "Got expected function name"); + is(callFrames[1].url, PAGE_CONSOLE_EVENTS, "Got expected url"); +}); + +async function runExceptionThrownTest( + client, + eventCount, + callback, + options = {} +) { + const { Runtime } = client; + + const EVENT_EXCEPTION_THROWN = "Runtime.exceptionThrown"; + + const history = new RecordEvents(eventCount); + history.addRecorder({ + event: Runtime.exceptionThrown, + eventName: EVENT_EXCEPTION_THROWN, + messageFn: payload => `Received "${payload.name}"`, + }); + + const timeBefore = Date.now(); + await callback(); + + const exceptionThrownEvents = await history.record(); + is(exceptionThrownEvents.length, eventCount, "Got expected amount of events"); + + if (eventCount == 0) { + return []; + } + + const timeAfter = Date.now(); + + // Check basic details for entryAdded events + exceptionThrownEvents.forEach(event => { + const details = event.payload.exceptionDetails; + const timestamp = event.payload.timestamp; + + is(typeof details, "object", "Got expected 'exceptionDetails' property"); + ok( + timestamp >= timeBefore && timestamp <= timeAfter, + `Timestamp ${timestamp} in expected range [${timeBefore} - ${timeAfter}]` + ); + }); + + return exceptionThrownEvents.map(event => event.payload.exceptionDetails); +} diff --git a/remote/cdp/test/browser/runtime/browser_executionContextEvents.js b/remote/cdp/test/browser/runtime/browser_executionContextEvents.js new file mode 100644 index 0000000000..9e6230901a --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser_executionContextEvents.js @@ -0,0 +1,332 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the Runtime execution context events + +const DESTROYED = "Runtime.executionContextDestroyed"; +const CREATED = "Runtime.executionContextCreated"; +const CLEARED = "Runtime.executionContextsCleared"; + +add_task(async function noEventsWhenRuntimeDomainDisabled({ client }) { + const { Runtime } = client; + + const history = recordContextEvents(Runtime, 0); + await loadURL(PAGE_FRAME_URL); + await assertEventOrder({ history, expectedEvents: [] }); +}); + +add_task(async function noEventsAfterRuntimeDomainDisabled({ client }) { + const { Runtime } = client; + + await Runtime.enable(); + await Runtime.disable(); + + const history = recordContextEvents(Runtime, 0); + await loadURL(PAGE_FRAME_URL); + await assertEventOrder({ history, expectedEvents: [] }); +}); + +add_task(async function eventsWhenNavigatingWithNoFrames({ client }) { + const { Page, Runtime } = client; + + const previousContext = await enableRuntime(client); + const history = recordContextEvents(Runtime, 3); + + const { frameId } = await Page.navigate({ url: PAGE_FRAME_URL }); + await assertEventOrder({ history }); + + const { executionContextId: destroyedId } = + history.findEvent(DESTROYED).payload; + is( + destroyedId, + previousContext.id, + "The destroyed event reports the previous context id" + ); + + const { context: contextCreated } = history.findEvent(CREATED).payload; + checkDefaultContext(contextCreated); + isnot( + contextCreated.id, + previousContext.id, + "The new execution context has a different id" + ); + is( + contextCreated.auxData.frameId, + frameId, + "The execution context frame id is the same " + + "than the one returned by Page.navigate" + ); +}); + +add_task(async function eventsWhenNavigatingFrameSet({ client }) { + const { Runtime } = client; + + const previousContext = await enableRuntime(client); + + // Check navigation to a frameset + const historyTo = recordContextEvents(Runtime, 4); + await loadURL(FRAMESET_SINGLE_URL); + await assertEventOrder({ + history: historyTo, + expectedEvents: [DESTROYED, CLEARED, CREATED, CREATED], + }); + + const { executionContextId: destroyedId } = + historyTo.findEvent(DESTROYED).payload; + is( + destroyedId, + previousContext.id, + "The destroyed event reports the previous context id" + ); + + const contexts = historyTo.findEvents(CREATED); + const createdTopContext = contexts[0].payload.context; + const createdFrameContext = contexts[1].payload.context; + + checkDefaultContext(createdTopContext); + isnot( + createdTopContext.id, + previousContext.id, + "The new execution context has a different id" + ); + is( + createdTopContext.origin, + BASE_ORIGIN, + "The execution context origin is the frameset" + ); + + checkDefaultContext(createdFrameContext); + isnot( + createdFrameContext.id, + createdTopContext.id, + "The new frame's execution context has a different id" + ); + is( + createdFrameContext.origin, + BASE_ORIGIN, + "The frame's execution context origin is the frame" + ); + + // Check navigation from a frameset + const historyFrom = recordContextEvents(Runtime, 4); + await loadURL(PAGE_FRAME_URL); + await assertEventOrder({ + history: historyFrom, + // Bug 1644657: The cleared event should come last but we emit destroy events + // for the top-level context and for frames afterward. Chrome only sends out + // the cleared event on navigation. + expectedEvents: [DESTROYED, CLEARED, CREATED, DESTROYED], + }); + + const destroyedContextIds = historyFrom.findEvents(DESTROYED); + is( + destroyedContextIds[0].payload.executionContextId, + createdTopContext.id, + "The destroyed event reports the previous context id" + ); + is( + destroyedContextIds[1].payload.executionContextId, + createdFrameContext.id, + "The destroyed event reports the previous frame's context id" + ); + + const { context: contextCreated } = historyFrom.findEvent(CREATED).payload; + checkDefaultContext(contextCreated); + isnot( + contextCreated.id, + createdTopContext.id, + "The new execution context has a different id" + ); + is( + contextCreated.origin, + BASE_ORIGIN, + "The execution context origin is not the frameset" + ); +}); + +add_task(async function eventsWhenNavigatingBackWithNoFrames({ client }) { + const { Runtime } = client; + + // Load an initial URL so that navigating back will work + await loadURL(PAGE_FRAME_URL); + const previousContext = await enableRuntime(client); + + const executionContextCreated = Runtime.executionContextCreated(); + await loadURL(PAGE_URL); + const { context: createdContext } = await executionContextCreated; + + const history = recordContextEvents(Runtime, 3); + gBrowser.selectedBrowser.goBack(); + await assertEventOrder({ history }); + + const { executionContextId: destroyedId } = + history.findEvent(DESTROYED).payload; + is( + destroyedId, + createdContext.id, + "The destroyed event reports the current context id" + ); + + const { context } = history.findEvent(CREATED).payload; + checkDefaultContext(context); + is( + context.origin, + previousContext.origin, + "The new execution context has the same origin as the previous one." + ); + isnot( + context.id, + previousContext.id, + "The new execution context has a different id" + ); + ok(context.auxData.isDefault, "The execution context is the default one"); + is( + context.auxData.frameId, + previousContext.auxData.frameId, + "The execution context frame id is always the same" + ); + is(context.auxData.type, "default", "Execution context has 'default' type"); + is(context.name, "", "The default execution context is named ''"); + + const { result } = await Runtime.evaluate({ + contextId: context.id, + expression: "location.href", + }); + is( + result.value, + PAGE_FRAME_URL, + "Runtime.evaluate works and is against the page we just navigated to" + ); +}); + +add_task(async function eventsWhenReloadingPageWithNoFrames({ client }) { + const { Page, Runtime } = client; + + // Load an initial URL so that reload will work + await loadURL(PAGE_FRAME_URL); + const previousContext = await enableRuntime(client); + + await Page.enable(); + + const history = recordContextEvents(Runtime, 3); + const frameNavigated = Page.frameNavigated(); + gBrowser.selectedBrowser.reload(); + await frameNavigated; + + await assertEventOrder({ history }); + + const { executionContextId } = history.findEvent(DESTROYED).payload; + is( + executionContextId, + previousContext.id, + "The destroyed event reports the previous context id" + ); + + const { context } = history.findEvent(CREATED).payload; + checkDefaultContext(context); + is( + context.auxData.frameId, + previousContext.auxData.frameId, + "The execution context frame id is the same as before reloading" + ); + + isnot( + executionContextId, + context.id, + "The destroyed id is different from the created one" + ); +}); + +add_task(async function eventsWhenNavigatingByLocationWithNoFrames({ client }) { + const { Runtime } = client; + + const previousContext = await enableRuntime(client); + const history = recordContextEvents(Runtime, 3); + + await Runtime.evaluate({ + contextId: previousContext.id, + expression: `window.location = '${PAGE_FRAME_URL}';`, + }); + await assertEventOrder({ history }); + + const { executionContextId: destroyedId } = + history.findEvent(DESTROYED).payload; + is( + destroyedId, + previousContext.id, + "The destroyed event reports the previous context id" + ); + + const { context: createdContext } = history.findEvent(CREATED).payload; + checkDefaultContext(createdContext); + is( + createdContext.auxData.frameId, + previousContext.auxData.frameId, + "The execution context frame id is identical " + + "to the one from before before setting the window's location" + ); + isnot( + destroyedId, + createdContext.id, + "The destroyed id is different from the created one" + ); +}); + +function recordContextEvents(Runtime, total) { + const history = new RecordEvents(total); + + history.addRecorder({ + event: Runtime.executionContextDestroyed, + eventName: DESTROYED, + messageFn: payload => { + return `Received ${DESTROYED} for id ${payload.executionContextId}`; + }, + }); + history.addRecorder({ + event: Runtime.executionContextCreated, + eventName: CREATED, + messageFn: ({ context }) => { + return ( + `Received ${CREATED} for id ${context.id}` + + ` type: ${context.auxData.type}` + + ` name: ${context.name}` + + ` origin: ${context.origin}` + ); + }, + }); + history.addRecorder({ + event: Runtime.executionContextsCleared, + eventName: CLEARED, + }); + + return history; +} + +async function assertEventOrder(options = {}) { + const { history, expectedEvents = [DESTROYED, CLEARED, CREATED] } = options; + const events = await history.record(); + const eventNames = events.map(item => item.eventName); + info(`Expected events: ${expectedEvents}`); + info(`Received events: ${eventNames}`); + + is( + events.length, + expectedEvents.length, + "Received expected number of Runtime context events" + ); + Assert.deepEqual( + events.map(item => item.eventName), + expectedEvents, + "Received Runtime context events in expected order" + ); +} + +function checkDefaultContext(context) { + ok(!!context.id, "The execution context has an id"); + ok(context.auxData.isDefault, "The execution context is the default one"); + is(context.auxData.type, "default", "Execution context has 'default' type"); + ok(!!context.origin, "The execution context has an origin"); + is(context.name, "", "The default execution context is named ''"); +} diff --git a/remote/cdp/test/browser/runtime/browser_getProperties.js b/remote/cdp/test/browser/runtime/browser_getProperties.js new file mode 100644 index 0000000000..f897c3dfcd --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser_getProperties.js @@ -0,0 +1,184 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the Runtime remote object + +add_task(async function ({ client }) { + const firstContext = await testRuntimeEnable(client); + const contextId = firstContext.id; + + await testGetOwnSimpleProperties(client, contextId); + await testGetCustomProperty(client, contextId); + await testGetPrototypeProperties(client, contextId); + await testGetGetterSetterProperties(client, contextId); +}); + +async function testRuntimeEnable({ Runtime }) { + // Enable watching for new execution context + await Runtime.enable(); + info("Runtime domain has been enabled"); + + // Calling Runtime.enable will emit executionContextCreated for the existing contexts + const { context } = await Runtime.executionContextCreated(); + ok(!!context.id, "The execution context has an id"); + ok(context.auxData.isDefault, "The execution context is the default one"); + ok(!!context.auxData.frameId, "The execution context has a frame id set"); + + return context; +} + +async function testGetOwnSimpleProperties({ Runtime }, contextId) { + const { result } = await Runtime.evaluate({ + contextId, + expression: "({ bool: true, fun() {}, int: 1, object: {}, string: 'foo' })", + }); + is(result.subtype, undefined, "JS Object has no subtype"); + is(result.type, "object", "The type is correct"); + ok(!!result.objectId, "Got an object id"); + + const { result: result2 } = await Runtime.getProperties({ + objectId: result.objectId, + ownProperties: true, + }); + is( + result2.length, + 5, + "ownProperties=true allows to iterate only over direct object properties (i.e. ignore prototype)" + ); + result2.sort((a, b) => a.name > b.name); + is(result2[0].name, "bool"); + is(result2[0].configurable, true); + is(result2[0].enumerable, true); + is(result2[0].writable, true); + is(result2[0].value.type, "boolean"); + is(result2[0].value.value, true); + is(result2[0].isOwn, true); + + is(result2[1].name, "fun"); + is(result2[1].configurable, true); + is(result2[1].enumerable, true); + is(result2[1].writable, true); + is(result2[1].value.type, "function"); + ok(!!result2[1].value.objectId); + is(result2[1].isOwn, true); + + is(result2[2].name, "int"); + is(result2[2].configurable, true); + is(result2[2].enumerable, true); + is(result2[2].writable, true); + is(result2[2].value.type, "number"); + is(result2[2].value.value, 1); + is(result2[2].isOwn, true); + + is(result2[3].name, "object"); + is(result2[3].configurable, true); + is(result2[3].enumerable, true); + is(result2[3].writable, true); + is(result2[3].value.type, "object"); + ok(!!result2[3].value.objectId); + is(result2[3].isOwn, true); + + is(result2[4].name, "string"); + is(result2[4].configurable, true); + is(result2[4].enumerable, true); + is(result2[4].writable, true); + is(result2[4].value.type, "string"); + is(result2[4].value.value, "foo"); + is(result2[4].isOwn, true); +} + +async function testGetPrototypeProperties({ Runtime }, contextId) { + const { result } = await Runtime.evaluate({ + contextId, + expression: "({ foo: 42 })", + }); + is(result.subtype, undefined, "JS Object has no subtype"); + is(result.type, "object", "The type is correct"); + ok(!!result.objectId, "Got an object id"); + + const { result: result2 } = await Runtime.getProperties({ + objectId: result.objectId, + ownProperties: false, + }); + ok(result2.length > 1, "We have more properties than just the object one"); + const foo = result2.find(p => p.name == "foo"); + ok(foo, "The object property is described"); + ok(foo.isOwn, "and is reported as 'own' property"); + + const toString = result2.find(p => p.name == "toString"); + ok( + toString, + "Function from Object's prototype are also described like toString" + ); + ok(!toString.isOwn, "but are reported as not being an 'own' property"); +} + +async function testGetGetterSetterProperties({ Runtime }, contextId) { + const { result } = await Runtime.evaluate({ + contextId, + expression: + "({ get prop() { return this.x; }, set prop(v) { this.x = v; } })", + }); + is(result.subtype, undefined, "JS Object has no subtype"); + is(result.type, "object", "The type is correct"); + ok(!!result.objectId, "Got an object id"); + + const { result: result2 } = await Runtime.getProperties({ + objectId: result.objectId, + ownProperties: true, + }); + is(result2.length, 1); + + is(result2[0].name, "prop"); + is(result2[0].configurable, true); + is(result2[0].enumerable, true); + is( + result2[0].writable, + undefined, + "writable is only set for data properties" + ); + + is(result2[0].get.type, "function"); + ok(!!result2[0].get.objectId); + is(result2[0].set.type, "function"); + ok(!!result2[0].set.objectId); + + is(result2[0].isOwn, true); + + const { result: result3 } = await Runtime.callFunctionOn({ + executionContextId: contextId, + functionDeclaration: "(set, get) => { set(42); return get(); }", + arguments: [ + { objectId: result2[0].set.objectId }, + { objectId: result2[0].get.objectId }, + ], + }); + is(result3.type, "number", "The type is correct"); + is(result3.subtype, undefined, "The subtype is undefined for numbers"); + is(result3.value, 42, "The getter returned the value set by the setter"); +} + +async function testGetCustomProperty({ Runtime }, contextId) { + const { result } = await Runtime.evaluate({ + contextId, + expression: `const obj = {}; Object.defineProperty(obj, "prop", { value: 42 }); obj`, + }); + is(result.subtype, undefined, "JS Object has no subtype"); + is(result.type, "object", "The type is correct"); + ok(!!result.objectId, "Got an object id"); + + const { result: result2 } = await Runtime.getProperties({ + objectId: result.objectId, + ownProperties: true, + }); + is(result2.length, 1, "We only get the one object's property"); + is(result2[0].name, "prop"); + is(result2[0].configurable, false); + is(result2[0].enumerable, false); + is(result2[0].writable, false); + is(result2[0].value.type, "number"); + is(result2[0].value.value, 42); + is(result2[0].isOwn, true); +} diff --git a/remote/cdp/test/browser/runtime/browser_remoteObjects.js b/remote/cdp/test/browser/runtime/browser_remoteObjects.js new file mode 100644 index 0000000000..9176028966 --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser_remoteObjects.js @@ -0,0 +1,83 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test the Runtime remote object + +add_task(async function ({ client }) { + const firstContext = await testRuntimeEnable(client); + const contextId = firstContext.id; + + await testObjectRelease(client, contextId); +}); + +async function testRuntimeEnable({ Runtime }) { + // Enable watching for new execution context + await Runtime.enable(); + info("Runtime domain has been enabled"); + + // Calling Runtime.enable will emit executionContextCreated for the existing contexts + const { context } = await Runtime.executionContextCreated(); + ok(!!context.id, "The execution context has an id"); + ok(context.auxData.isDefault, "The execution context is the default one"); + ok(!!context.auxData.frameId, "The execution context has a frame id set"); + + return context; +} + +async function testObjectRelease({ Runtime }, contextId) { + const { result } = await Runtime.evaluate({ + contextId, + expression: "({ foo: 42 })", + }); + is(result.subtype, undefined, "JS Object has no subtype"); + is(result.type, "object", "The type is correct"); + ok(!!result.objectId, "Got an object id"); + + const { result: result2 } = await Runtime.callFunctionOn({ + executionContextId: contextId, + functionDeclaration: "obj => JSON.stringify(obj)", + arguments: [{ objectId: result.objectId }], + }); + is(result2.type, "string", "The type is correct"); + is(result2.value, JSON.stringify({ foo: 42 }), "Got the object's JSON"); + + const { result: result3 } = await Runtime.callFunctionOn({ + objectId: result.objectId, + functionDeclaration: "function () { return this.foo; }", + }); + is(result3.type, "number", "The type is correct"); + is(result3.value, 42, "Got the object's foo attribute"); + + await Runtime.releaseObject({ + objectId: result.objectId, + }); + info("Object is released"); + + try { + await Runtime.callFunctionOn({ + executionContextId: contextId, + functionDeclaration: "() => {}", + arguments: [{ objectId: result.objectId }], + }); + ok(false, "callFunctionOn with a released object as argument should throw"); + } catch (e) { + ok( + e.message.includes("Could not find object with given id"), + "callFunctionOn throws on released argument" + ); + } + try { + await Runtime.callFunctionOn({ + objectId: result.objectId, + functionDeclaration: "() => {}", + }); + ok(false, "callFunctionOn with a released object as target should throw"); + } catch (e) { + ok( + e.message.includes("Cannot find context with specified id"), + "callFunctionOn throws on released target" + ); + } +} diff --git a/remote/cdp/test/browser/runtime/browser_withDefaultPrefs.js b/remote/cdp/test/browser/runtime/browser_withDefaultPrefs.js new file mode 100644 index 0000000000..20b8264f92 --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser_withDefaultPrefs.js @@ -0,0 +1,9 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function enableRuntime_noHangAfterNavigation({ client }) { + await loadURL(PAGE_URL); + await enableRuntime(client); +}); diff --git a/remote/cdp/test/browser/runtime/browser_with_default_prefs.ini b/remote/cdp/test/browser/runtime/browser_with_default_prefs.ini new file mode 100644 index 0000000000..a3f78c9961 --- /dev/null +++ b/remote/cdp/test/browser/runtime/browser_with_default_prefs.ini @@ -0,0 +1,13 @@ +[DEFAULT] +tags = cdp +subsuite = remote +args = + --remote-debugging-port + --remote-allow-origins=null +support-files = + !/remote/cdp/test/browser/chrome-remote-interface.js + !/remote/cdp/test/browser/head.js + doc_empty.html + head.js + +[browser_withDefaultPrefs.js] diff --git a/remote/cdp/test/browser/runtime/doc_console_events.html b/remote/cdp/test/browser/runtime/doc_console_events.html new file mode 100644 index 0000000000..f52fcfa555 --- /dev/null +++ b/remote/cdp/test/browser/runtime/doc_console_events.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Empty page</title> + <script> + function throwError() { + let foo = {}; + foo.click(); + } + + function runLogParent() { + function runLogCaller() { + console.trace("foo"); + } + + function runLogChild() { + runLogCaller(); + } + + runLogChild(); + } + </script> +</head> +<body> + <a id="console-log" href="javascript:console.log('foo')">console.log()</a><br/> + <a id="console-error" href="javascript:console.error('foo')">console.error()</a><br/> + <a id="js-error" onclick="throwError()">Javascript Error</a><br/> + <a id="log-wrapper" onclick="runLogParent()">console.log() in function wrappers</a><br/> +</body> +</html> diff --git a/remote/cdp/test/browser/runtime/doc_console_events_onload.html b/remote/cdp/test/browser/runtime/doc_console_events_onload.html new file mode 100644 index 0000000000..608a22991a --- /dev/null +++ b/remote/cdp/test/browser/runtime/doc_console_events_onload.html @@ -0,0 +1,12 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Console events onload</title> + <script> + console.log("foo"); + </script> +</head> +<body> +</body> +</html> diff --git a/remote/cdp/test/browser/runtime/doc_empty.html b/remote/cdp/test/browser/runtime/doc_empty.html new file mode 100644 index 0000000000..e59d2d8901 --- /dev/null +++ b/remote/cdp/test/browser/runtime/doc_empty.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Empty page</title> +</head> +<body> +</body> +</html> diff --git a/remote/cdp/test/browser/runtime/doc_frame.html b/remote/cdp/test/browser/runtime/doc_frame.html new file mode 100644 index 0000000000..e2efd61554 --- /dev/null +++ b/remote/cdp/test/browser/runtime/doc_frame.html @@ -0,0 +1,9 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Frame page</title> +</head> +<body> +</body> +</html> diff --git a/remote/cdp/test/browser/runtime/doc_frameset_single.html b/remote/cdp/test/browser/runtime/doc_frameset_single.html new file mode 100644 index 0000000000..2ad56a140e --- /dev/null +++ b/remote/cdp/test/browser/runtime/doc_frameset_single.html @@ -0,0 +1,10 @@ +<!DOCTYPE html> +<html> +<head> + <meta charset="utf-8"> + <title>Frameset with a single frame</title> +</head> +<body> + <iframe src="doc_frame.html"></iframe> +</body> +</html> diff --git a/remote/cdp/test/browser/runtime/head.js b/remote/cdp/test/browser/runtime/head.js new file mode 100644 index 0000000000..8f05f225ec --- /dev/null +++ b/remote/cdp/test/browser/runtime/head.js @@ -0,0 +1,15 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +Services.scriptloader.loadSubScript( + "chrome://mochitests/content/browser/remote/cdp/test/browser/head.js", + this +); + +const BASE_ORIGIN = "https://example.com"; +const BASE_PATH = `${BASE_ORIGIN}/browser/remote/cdp/test/browser/runtime`; +const FRAMESET_SINGLE_URL = `${BASE_PATH}/doc_frameset_single.html`; +const PAGE_FRAME_URL = `${BASE_PATH}/doc_frame.html`; +const PAGE_URL = `${BASE_PATH}/doc_empty.html`; |