diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /remote/cdp/test/browser/page/browser_createIsolatedWorld.js | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'remote/cdp/test/browser/page/browser_createIsolatedWorld.js')
-rw-r--r-- | remote/cdp/test/browser/page/browser_createIsolatedWorld.js | 491 |
1 files changed, 491 insertions, 0 deletions
diff --git a/remote/cdp/test/browser/page/browser_createIsolatedWorld.js b/remote/cdp/test/browser/page/browser_createIsolatedWorld.js new file mode 100644 index 0000000000..4dc569f8c5 --- /dev/null +++ b/remote/cdp/test/browser/page/browser_createIsolatedWorld.js @@ -0,0 +1,491 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test Page.createIsolatedWorld + +const WORLD_NAME_1 = "testWorld1"; +const WORLD_NAME_2 = "testWorld2"; + +const DESTROYED = "Runtime.executionContextDestroyed"; +const CREATED = "Runtime.executionContextCreated"; +const CLEARED = "Runtime.executionContextsCleared"; + +add_task(async function frameIdMissing({ client }) { + const { Page } = client; + + let errorThrown = ""; + try { + await Page.createIsolatedWorld({ + worldName: WORLD_NAME_1, + grantUniversalAccess: true, + }); + } catch (e) { + errorThrown = e.message; + } + ok( + errorThrown.match(/frameId: string value expected/), + `Fails with missing frameId` + ); +}); + +add_task(async function frameIdInvalidTypes({ client }) { + const { Page } = client; + + for (const frameId of [null, true, 1, [], {}]) { + let errorThrown = ""; + try { + await Page.createIsolatedWorld({ + frameId, + }); + } catch (e) { + errorThrown = e.message; + } + ok( + errorThrown.match(/frameId: string value expected/), + `Fails with invalid type: ${frameId}` + ); + } +}); + +add_task(async function worldNameInvalidTypes({ client }) { + const { Page } = client; + + await Page.enable(); + info("Page notifications are enabled"); + + const loadEvent = Page.loadEventFired(); + const { frameId } = await Page.navigate({ url: PAGE_URL }); + await loadEvent; + + for (const worldName of [null, true, 1, [], {}]) { + let errorThrown = ""; + try { + await Page.createIsolatedWorld({ + frameId, + worldName, + }); + } catch (e) { + errorThrown = e.message; + } + ok( + errorThrown.match(/worldName: string value expected/), + `Fails with invalid type: ${worldName}` + ); + } +}); + +add_task(async function noEventsWhenRuntimeDomainDisabled({ client }) { + const { Page, Runtime } = client; + + await Page.enable(); + info("Page notifications are enabled"); + + const history = recordEvents(Runtime, 0); + const loadEvent = Page.loadEventFired(); + const { frameId } = await Page.navigate({ url: PAGE_URL }); + await loadEvent; + + let errorThrown = ""; + try { + await Page.createIsolatedWorld({ + frameId, + worldName: WORLD_NAME_1, + grantUniversalAccess: true, + }); + await assertEvents({ history, expectedEvents: [] }); + } catch (e) { + errorThrown = e.message; + } + todo( + errorThrown === "", + "No contexts tracked internally without Runtime enabled (Bug 1623482)" + ); +}); + +add_task(async function noEventsAfterRuntimeDomainDisabled({ client }) { + const { Page, Runtime } = client; + + await Page.enable(); + info("Page notifications are enabled"); + + await enableRuntime(client); + await Runtime.disable(); + info("Runtime notifications are disabled"); + + const history = recordEvents(Runtime, 0); + const loadEvent = Page.loadEventFired(); + const { frameId } = await Page.navigate({ url: PAGE_URL }); + await loadEvent; + + await Page.createIsolatedWorld({ + frameId, + worldName: WORLD_NAME_2, + grantUniversalAccess: true, + }); + await assertEvents({ history, expectedEvents: [] }); +}); + +add_task(async function contextCreatedAfterNavigation({ client }) { + const { Page, Runtime } = client; + + await Page.enable(); + info("Page notifications are enabled"); + + await enableRuntime(client); + + const history = recordEvents(Runtime, 3); + const loadEvent = Page.loadEventFired(); + const { frameId } = await Page.navigate({ url: PAGE_URL }); + await loadEvent; + + const { executionContextId: isolatedId } = await Page.createIsolatedWorld({ + frameId, + worldName: WORLD_NAME_1, + grantUniversalAccess: true, + }); + await assertEvents({ + history, + expectedEvents: [ + DESTROYED, // default, about:blank + CREATED, // default, PAGE_URL + CREATED, // isolated, PAGE_URL + ], + }); + + const contexts = history + .findEvents(CREATED) + .map(event => event.payload.context); + const defaultContext = contexts[0]; + const isolatedContext = contexts[1]; + is(defaultContext.auxData.isDefault, true, "Default context is default"); + is( + defaultContext.auxData.type, + "default", + "Default context has type 'default'" + ); + is(defaultContext.origin, BASE_ORIGIN, "Default context has expected origin"); + checkIsolated(isolatedContext, isolatedId, WORLD_NAME_1, frameId); + compareContexts(isolatedContext, defaultContext); +}); + +add_task(async function contextDestroyedForNavigation({ client }) { + const { Page, Runtime } = client; + + const defaultContext = await enableRuntime(client); + const isolatedContext = await createIsolatedContext(client, defaultContext); + + await Page.enable(); + + const history = recordEvents(Runtime, 4, true); + const frameNavigated = Page.frameNavigated(); + await Page.navigate({ url: PAGE_URL }); + await frameNavigated; + + await assertEvents({ + history, + expectedEvents: [ + DESTROYED, // default, about:blank + DESTROYED, // isolated, about:blank + CLEARED, + CREATED, // default, PAGE_URL + ], + }); + + const destroyed = history + .findEvents(DESTROYED) + .map(event => event.payload.executionContextId); + ok(destroyed.includes(isolatedContext.id), "Isolated context destroyed"); + ok(destroyed.includes(defaultContext.id), "Default context destroyed"); + + const { context: newContext } = history.findEvent(CREATED).payload; + is(newContext.auxData.isDefault, true, "The new context is a default one"); + ok(!!newContext.id, "The new context has an id"); + ok( + ![defaultContext.id, isolatedContext.id].includes(newContext.id), + "The new context has a new id" + ); +}); + +add_task(async function contextsForFramesetNavigation({ client }) { + const { Page, Runtime } = client; + + await Page.enable(); + info("Page notifications are enabled"); + + await enableRuntime(client); + + // check creation when navigating to a frameset + const historyTo = recordEvents(Runtime, 5); + const loadEventTo = Page.loadEventFired(); + const { frameId: frameIdTo } = await Page.navigate({ + url: FRAMESET_SINGLE_URL, + }); + await loadEventTo; + + const { frameTree } = await Page.getFrameTree(); + const subFrame = frameTree.childFrames[0].frame; + + const { executionContextId: contextIdParent } = + await Page.createIsolatedWorld({ + frameId: frameIdTo, + worldName: WORLD_NAME_1, + grantUniversalAccess: true, + }); + const { executionContextId: contextIdSubFrame } = + await Page.createIsolatedWorld({ + frameId: subFrame.id, + worldName: WORLD_NAME_2, + grantUniversalAccess: true, + }); + + await assertEvents({ + history: historyTo, + expectedEvents: [ + DESTROYED, // default, about:blank + CREATED, // default, FRAMESET_SINGLE_URL + CREATED, // default, PAGE_URL + CREATED, // isolated, FRAMESET_SINGLE_URL + CREATED, // isolated, PAGE_URL + ], + }); + + const contextsCreated = historyTo + .findEvents(CREATED) + .map(event => event.payload.context); + const parentDefaultContextCreated = contextsCreated[0]; + const frameDefaultContextCreated = contextsCreated[1]; + const parentIsolatedContextCreated = contextsCreated[2]; + const frameIsolatedContextCreated = contextsCreated[3]; + + checkIsolated( + parentIsolatedContextCreated, + contextIdParent, + WORLD_NAME_1, + frameIdTo + ); + compareContexts(parentIsolatedContextCreated, parentDefaultContextCreated); + + checkIsolated( + frameIsolatedContextCreated, + contextIdSubFrame, + WORLD_NAME_2, + subFrame.id + ); + compareContexts(frameIsolatedContextCreated, frameDefaultContextCreated); + + // check destroying when navigating away from a frameset + const historyFrom = recordEvents(Runtime, 6); + const loadEventFrom = Page.loadEventFired(); + await Page.navigate({ url: PAGE_URL }); + await loadEventFrom; + + await assertEvents({ + history: historyFrom, + expectedEvents: [ + DESTROYED, // default, PAGE_URL + DESTROYED, // isolated, PAGE_URL + DESTROYED, // default, FRAMESET_SINGLE_URL + DESTROYED, // isolated, FRAMESET_SINGLE_URL + CREATED, // default, PAGE_URL + ], + }); + + const contextsDestroyed = historyFrom + .findEvents(DESTROYED) + .map(event => event.payload.executionContextId); + contextsCreated.forEach(context => { + ok( + contextsDestroyed.includes(context.id), + `Context with id ${context.id} destroyed` + ); + }); + + const { context: newContext } = historyFrom.findEvent(CREATED).payload; + is(newContext.auxData.isDefault, true, "The new context is a default one"); + ok(!!newContext.id, "The new context has an id"); + ok( + ![parentDefaultContextCreated.id, frameDefaultContextCreated.id].includes( + newContext.id + ), + "The new context has a new id" + ); +}); + +add_task(async function evaluateInIsolatedAndDefault({ client }) { + const { Runtime } = client; + + const defaultContext = await enableRuntime(client); + const isolatedContext = await createIsolatedContext(client, defaultContext); + + const { result: objDefault } = await Runtime.evaluate({ + contextId: defaultContext.id, + expression: "({ foo: 1 })", + }); + const { result: objIsolated } = await Runtime.evaluate({ + contextId: isolatedContext.id, + expression: "({ foo: 10 })", + }); + const { result: result1 } = await Runtime.callFunctionOn({ + executionContextId: isolatedContext.id, + functionDeclaration: "arg => ++arg.foo", + arguments: [{ objectId: objIsolated.objectId }], + }); + is(result1.value, 11, "Isolated context incremented the expected value"); + + let errorThrown = ""; + try { + await Runtime.callFunctionOn({ + executionContextId: isolatedContext.id, + functionDeclaration: "arg => ++arg.foo", + arguments: [{ objectId: objDefault.objectId }], + }); + } catch (e) { + errorThrown = e.message; + } + ok( + errorThrown.match(/Could not find object with given id/), + "Contexts do not share objects" + ); +}); + +add_task(async function contextEvaluationIsIsolated({ client }) { + const { Runtime } = client; + + // If a document makes changes to standard global object, an isolated + // world should not be affected + await loadURL(toDataURL("<script>window.Node = null</script>")); + + const defaultContext = await enableRuntime(client); + const isolatedContext = await createIsolatedContext(client, defaultContext); + + const { result: result1 } = await Runtime.callFunctionOn({ + executionContextId: defaultContext.id, + functionDeclaration: "arg => window.Node", + }); + const { result: result2 } = await Runtime.callFunctionOn({ + executionContextId: isolatedContext.id, + functionDeclaration: "arg => window.Node", + }); + is(result1.value, null, "Default context sees content changes to global"); + todo_isnot( + result2.value, + null, + "Isolated context is not affected by changes to global, Bug 1601421" + ); +}); + +function checkIsolated(context, expectedId, expectedName, expectedFrameId) { + is( + expectedId, + context.id, + "createIsolatedWorld returns id of isolated context" + ); + is( + context.auxData.frameId, + expectedFrameId, + "Isolated context has expected frameId" + ); + is(context.auxData.isDefault, false, "Isolated context is not default"); + is(context.auxData.type, "isolated", "Isolated context has type 'isolated'"); + is(context.name, expectedName, "Isolated context is named as requested"); + ok(!!context.origin, "Isolated context has an origin"); +} + +function compareContexts(isolatedContext, defaultContext) { + isnot( + defaultContext.name, + isolatedContext.name, + "The contexts have different names" + ); + isnot( + defaultContext.id, + isolatedContext.id, + "The contexts have different ids" + ); + is( + defaultContext.origin, + isolatedContext.origin, + "The contexts have same origin" + ); + is( + defaultContext.auxData.frameId, + isolatedContext.auxData.frameId, + "The contexts have same frameId" + ); +} + +async function createIsolatedContext( + client, + defaultContext, + worldName = WORLD_NAME_1 +) { + const { Page, Runtime } = client; + + const frameId = defaultContext.auxData.frameId; + + const isolatedContextCreated = Runtime.executionContextCreated(); + const { executionContextId: isolatedId } = await Page.createIsolatedWorld({ + frameId, + worldName, + grantUniversalAccess: true, + }); + const { context: isolatedContext } = await isolatedContextCreated; + info("Isolated world created"); + + checkIsolated(isolatedContext, isolatedId, worldName, frameId); + compareContexts(isolatedContext, defaultContext); + + return isolatedContext; +} + +function recordEvents(Runtime, total, cleared = false) { + 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: payload => { + return ( + `Received ${CREATED} for id ${payload.context.id}` + + ` type: ${payload.context.auxData.type}` + + ` name: ${payload.context.name}` + + ` origin: ${payload.context.origin}` + ); + }, + }); + if (cleared) { + history.addRecorder({ + event: Runtime.executionContextsCleared, + eventName: CLEARED, + }); + } + + return history; +} + +async function assertEvents(options = {}) { + const { history, expectedEvents, timeout = 1000 } = options; + const events = await history.record(timeout); + 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( + eventNames.sort(), + expectedEvents.sort(), + "Received expected Runtime context events" + ); +} |