diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /remote/test/browser/page/browser_createIsolatedWorld.js | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'remote/test/browser/page/browser_createIsolatedWorld.js')
-rw-r--r-- | remote/test/browser/page/browser_createIsolatedWorld.js | 494 |
1 files changed, 494 insertions, 0 deletions
diff --git a/remote/test/browser/page/browser_createIsolatedWorld.js b/remote/test/browser/page/browser_createIsolatedWorld.js new file mode 100644 index 0000000000..8272beb044 --- /dev/null +++ b/remote/test/browser/page/browser_createIsolatedWorld.js @@ -0,0 +1,494 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +// Test Page.createIsolatedWorld + +const DOC = toDataURL("default-test-page"); +const DOC_IFRAME = toDataURL(`<iframe src="data:text/html,${DOC}"></iframe>`); + +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: DOC }); + 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: DOC }); + await loadEvent; + + let errorThrown = ""; + try { + await Page.createIsolatedWorld({ + frameId, + worldName: WORLD_NAME_1, + grantUniversalAccess: true, + }); + await assertEventOrder({ 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: DOC }); + await loadEvent; + + await Page.createIsolatedWorld({ + frameId, + worldName: WORLD_NAME_2, + grantUniversalAccess: true, + }); + await assertEventOrder({ 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: DOC }); + await loadEvent; + + const { executionContextId: isolatedId } = await Page.createIsolatedWorld({ + frameId, + worldName: WORLD_NAME_1, + grantUniversalAccess: true, + }); + await assertEventOrder({ + history, + expectedEvents: [ + DESTROYED, // default, about:blank + CREATED, // default, DOC + CREATED, // isolated, DOC + ], + }); + + 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, DOC, "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: DOC }); + await frameNavigated; + + await assertEventOrder({ + history, + expectedEvents: [ + DESTROYED, // default, about:blank + DESTROYED, // isolated, about:blank + CLEARED, + CREATED, // default, DOC + ], + }); + + 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: DOC_IFRAME }); + 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 assertEventOrder({ + history: historyTo, + expectedEvents: [ + DESTROYED, // default, about:blank + CREATED, // default, DOC_IFRAME + CREATED, // default, DOC + CREATED, // isolated, DOC_IFRAME + CREATED, // isolated, DOC + ], + }); + + 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: DOC }); + await loadEventFrom; + + await assertEventOrder({ + history: historyFrom, + expectedEvents: [ + DESTROYED, // default, DOC + DESTROYED, // isolated, DOC + DESTROYED, // default, DOC_IFRAME + DESTROYED, // isolated, DOC_IFRAME + CREATED, // default, DOC + ], + }); + + 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 assertEventOrder(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, + expectedEvents, + "Received Runtime context events in expected order" + ); +} |