/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; // Test the Runtime execution context events const DOC = toDataURL("default-test-page"); const DOC_IFRAME = toDataURL(``); 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(DOC); 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(DOC); 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: DOC }); 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(DOC_IFRAME); 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, DOC_IFRAME, "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, DOC, "The frame's execution context origin is the frame" ); // Check navigation from a frameset const historyFrom = recordContextEvents(Runtime, 4); await loadURL(DOC); 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, DESTROYED, CREATED], }); 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, DOC, "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(DOC); const previousContext = await enableRuntime(client); const executionContextCreated = Runtime.executionContextCreated(); await loadURL(toDataURL("other-test-page")); 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, DOC, "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(DOC); 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 = '${DOC}';`, }); 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 ''"); }