diff --git a/src/chromium/ExecutionContext.ts b/src/chromium/ExecutionContext.ts index 12a4443a48..44b0fde665 100644 --- a/src/chromium/ExecutionContext.ts +++ b/src/chromium/ExecutionContext.ts @@ -125,7 +125,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate { if (error.message.includes('Object couldn\'t be returned by value')) return {result: {type: 'undefined'}}; - if (error.message.endsWith('Cannot find context with specified id') || error.message.endsWith('Inspected target navigated or closed')) + if (error.message.endsWith('Cannot find context with specified id') || error.message.endsWith('Inspected target navigated or closed') || error.message.endsWith('Execution context was destroyed.')) throw new Error('Execution context was destroyed, most likely because of a navigation.'); throw error; } @@ -140,7 +140,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate { for (const property of response.result) { if (!property.enumerable) continue; - result.set(property.name, handle.executionContext()._createHandle(property.value)); + result.set(property.name, handle._context._createHandle(property.value)); } return result; } diff --git a/src/chromium/api.ts b/src/chromium/api.ts index 9cead6fc5f..2453ee25d2 100644 --- a/src/chromium/api.ts +++ b/src/chromium/api.ts @@ -7,7 +7,7 @@ export { ElementHandle } from '../dom'; export { TimeoutError } from '../errors'; export { Frame } from '../frames'; export { Keyboard, Mouse } from '../input'; -export { ExecutionContext, JSHandle } from '../javascript'; +export { JSHandle } from '../javascript'; export { Request, Response } from '../network'; export { Browser } from './Browser'; export { BrowserContext } from '../browserContext'; diff --git a/src/chromium/features/workers.ts b/src/chromium/features/workers.ts index dea3fa1b21..aa13f58da7 100644 --- a/src/chromium/features/workers.ts +++ b/src/chromium/features/workers.ts @@ -85,10 +85,6 @@ export class Worker extends EventEmitter { return this._url; } - async executionContext(): Promise { - return this._executionContextPromise; - } - evaluate: types.Evaluate = async (pageFunction, ...args) => { return (await this._executionContextPromise).evaluate(pageFunction, ...args as any); } diff --git a/src/console.ts b/src/console.ts index edb057a73e..90e8caf4c9 100644 --- a/src/console.ts +++ b/src/console.ts @@ -28,7 +28,7 @@ export class ConsoleMessage { text(): string { if (this._text === undefined) - this._text = this._args.map(arg => arg.executionContext()._delegate.handleToString(arg, false /* includeType */)).join(' '); + this._text = this._args.map(arg => arg._context._delegate.handleToString(arg, false /* includeType */)).join(' '); return this._text; } diff --git a/src/dom.ts b/src/dom.ts index 96bfb935ca..8f65bee20c 100644 --- a/src/dom.ts +++ b/src/dom.ts @@ -87,6 +87,10 @@ export class ElementHandle extends js.JSHandle { this._page = context.frame()._page; } + frame(): frames.Frame { + return this._context.frame(); + } + asElement(): ElementHandle | null { return this; } diff --git a/src/firefox/ExecutionContext.ts b/src/firefox/ExecutionContext.ts index 4764bead76..4e8a459ea8 100644 --- a/src/firefox/ExecutionContext.ts +++ b/src/firefox/ExecutionContext.ts @@ -106,7 +106,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate { return context._createHandle(payload.result); function rewriteError(error) : never { - if (error.message.includes('Failed to find execution context with id')) + if (error.message.includes('Failed to find execution context with id') || error.message.includes('Execution context was destroyed!')) throw new Error('Execution context was destroyed, most likely because of a navigation.'); throw error; } @@ -119,7 +119,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate { }); const result = new Map(); for (const property of response.properties) - result.set(property.name, handle.executionContext()._createHandle(property.value)); + result.set(property.name, handle._context._createHandle(property.value)); return result; } diff --git a/src/firefox/api.ts b/src/firefox/api.ts index 8791edcb1a..d355d5976a 100644 --- a/src/firefox/api.ts +++ b/src/firefox/api.ts @@ -7,7 +7,7 @@ export { Browser } from './Browser'; export { BrowserContext } from '../browserContext'; export { BrowserFetcher } from '../browserFetcher'; export { Dialog } from '../dialog'; -export { ExecutionContext, JSHandle } from '../javascript'; +export { JSHandle } from '../javascript'; export { ElementHandle } from '../dom'; export { Accessibility } from './features/accessibility'; export { Interception } from './features/interception'; diff --git a/src/frames.ts b/src/frames.ts index 19ad9fb855..a4c0b7185a 100644 --- a/src/frames.ts +++ b/src/frames.ts @@ -354,10 +354,6 @@ export class Frame { return this._context('utility'); } - executionContext(): Promise { - return this._mainContext(); - } - evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => { const context = await this._mainContext(); return context.evaluateHandle(pageFunction, ...args as any); @@ -632,7 +628,7 @@ export class Frame { const values = value === undefined ? [] : Array.isArray(value) ? value : [value]; const context = await this._utilityContext(); const adoptedValues = await Promise.all(values.map(async value => { - if (value instanceof dom.ElementHandle && value.executionContext() !== context) { + if (value instanceof dom.ElementHandle && value._context !== context) { const adopted = this._page._delegate.adoptElementHandle(value, context); toDispose.push(adopted); return adopted; diff --git a/src/javascript.ts b/src/javascript.ts index 4e15162901..57190ee743 100644 --- a/src/javascript.ts +++ b/src/javascript.ts @@ -47,10 +47,6 @@ export class JSHandle { this._remoteObject = remoteObject; } - executionContext(): ExecutionContext { - return this._context; - } - evaluate: types.EvaluateOn = (pageFunction, ...args) => { return this._context.evaluate(pageFunction, this, ...args); } diff --git a/src/page.ts b/src/page.ts index 21a35e7540..054740d0d1 100644 --- a/src/page.ts +++ b/src/page.ts @@ -174,8 +174,7 @@ export class Page extends EventEmitter { } evaluateHandle: types.EvaluateHandle = async (pageFunction, ...args) => { - const context = await this.mainFrame().executionContext(); - return context.evaluateHandle(pageFunction, ...args as any); + return this.mainFrame().evaluateHandle(pageFunction, ...args as any); } $eval: types.$Eval = (selector, pageFunction, ...args) => { diff --git a/src/webkit/ExecutionContext.ts b/src/webkit/ExecutionContext.ts index 72361eea9a..2dece987cf 100644 --- a/src/webkit/ExecutionContext.ts +++ b/src/webkit/ExecutionContext.ts @@ -261,7 +261,7 @@ export class ExecutionContextDelegate implements js.ExecutionContextDelegate { for (const property of response.properties) { if (!property.enumerable) continue; - result.set(property.name, handle.executionContext()._createHandle(property.value)); + result.set(property.name, handle._context._createHandle(property.value)); } return result; } diff --git a/src/webkit/FrameManager.ts b/src/webkit/FrameManager.ts index bf9208440b..1cdb8399cf 100644 --- a/src/webkit/FrameManager.ts +++ b/src/webkit/FrameManager.ts @@ -215,7 +215,7 @@ export class FrameManager implements PageDelegate { else if (type === 'timing') derivedType = 'timeEnd'; - const mainFrameContext = await this._page.mainFrame().executionContext(); + const mainFrameContext = await this._page.mainFrame()._mainContext(); const handles = (parameters || []).map(p => { let context: dom.FrameExecutionContext | null = null; if (p.objectId) { diff --git a/src/webkit/api.ts b/src/webkit/api.ts index ae27636b9b..d6080780ce 100644 --- a/src/webkit/api.ts +++ b/src/webkit/api.ts @@ -5,7 +5,7 @@ export { TimeoutError } from '../errors'; export { Browser } from './Browser'; export { BrowserContext } from '../browserContext'; export { BrowserFetcher } from '../browserFetcher'; -export { ExecutionContext, JSHandle } from '../javascript'; +export { JSHandle } from '../javascript'; export { ElementHandle } from '../dom'; export { Frame } from '../frames'; export { Mouse, Keyboard } from '../input'; diff --git a/test/chromium/workers.spec.js b/test/chromium/workers.spec.js index 7f3e746501..fa7a824035 100644 --- a/test/chromium/workers.spec.js +++ b/test/chromium/workers.spec.js @@ -67,11 +67,11 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { expect(log.args().length).toBe(4); expect(await (await log.args()[3].getProperty('origin')).jsonValue()).toBe('null'); }); - it('should have an execution context', async function({page}) { + it('should evaluate', async function({page}) { const workerCreatedPromise = new Promise(x => page.workers.once('workercreated', x)); await page.evaluate(() => new Worker(`data:text/javascript,console.log(1)`)); const worker = await workerCreatedPromise; - expect(await (await worker.executionContext()).evaluate('1+1')).toBe(2); + expect(await worker.evaluate('1+1')).toBe(2); }); it('should report errors', async function({page}) { const errorPromise = new Promise(x => page.on('pageerror', x)); diff --git a/test/evaluation.spec.js b/test/evaluation.spec.js index acbbc3f869..e908c36bc7 100644 --- a/test/evaluation.spec.js +++ b/test/evaluation.spec.js @@ -83,7 +83,7 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { location.reload(); return new Promise(() => {}); }).catch(e => error = e); - expect(error.message).toContain('Protocol error'); + expect(error.message).toContain('navigation'); }); it('should await promise', async({page, server}) => { const result = await page.evaluate(() => Promise.resolve(8 * 7)); @@ -227,13 +227,15 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { expect(result).toBe(true); }); it('should throw a nice error after a navigation', async({page, server}) => { - const executionContext = await page.mainFrame().executionContext(); - + const errorPromise = page.evaluate(() => new Promise(f => window.__resolve = f)).catch(e => e); await Promise.all([ page.waitForNavigation(), - executionContext.evaluate(() => window.location.reload()) + page.evaluate(() => { + window.location.reload(); + setTimeout(() => window.__resolve(42), 1000); + }) ]); - const error = await executionContext.evaluate(() => null).catch(e => e); + const error = await errorPromise; expect(error.message).toContain('navigation'); }); it.skip(FFOX)('should not throw an error when evaluation does a navigation', async({page, server}) => { diff --git a/test/frame.spec.js b/test/frame.spec.js index 16ef962ed0..08f533a9e1 100644 --- a/test/frame.spec.js +++ b/test/frame.spec.js @@ -22,33 +22,6 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { const {it, fit, xit} = testRunner; const {beforeAll, beforeEach, afterAll, afterEach} = testRunner; - describe('Frame.executionContext', function() { - it('should work', async({page, server}) => { - await page.goto(server.EMPTY_PAGE); - await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); - expect(page.frames().length).toBe(2); - const [frame1, frame2] = page.frames(); - const context1 = await frame1.executionContext(); - const context2 = await frame2.executionContext(); - expect(context1).toBeTruthy(); - expect(context2).toBeTruthy(); - expect(context1 !== context2).toBeTruthy(); - expect(context1.frame()).toBe(frame1); - expect(context2.frame()).toBe(frame2); - - await Promise.all([ - context1.evaluate(() => window.a = 1), - context2.evaluate(() => window.a = 2) - ]); - const [a1, a2] = await Promise.all([ - context1.evaluate(() => window.a), - context2.evaluate(() => window.a) - ]); - expect(a1).toBe(1); - expect(a2).toBe(2); - }); - }); - describe('Frame.evaluateHandle', function() { it('should work', async({page, server}) => { await page.goto(server.EMPTY_PAGE); @@ -66,6 +39,24 @@ module.exports.addTests = function({testRunner, expect, FFOX, CHROME, WEBKIT}) { await frame1.evaluate(() => 7 * 8).catch(e => error = e); expect(error.message).toContain('Execution Context is not available in detached frame'); }); + it('should be isolated between frames', async({page, server}) => { + await page.goto(server.EMPTY_PAGE); + await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE); + expect(page.frames().length).toBe(2); + const [frame1, frame2] = page.frames(); + expect(frame1 !== frame2).toBeTruthy(); + + await Promise.all([ + frame1.evaluate(() => window.a = 1), + frame2.evaluate(() => window.a = 2) + ]); + const [a1, a2] = await Promise.all([ + frame1.evaluate(() => window.a), + frame2.evaluate(() => window.a) + ]); + expect(a1).toBe(1); + expect(a2).toBe(2); + }); }); describe('Frame Management', function() { diff --git a/test/page.spec.js b/test/page.spec.js index 7ea7155a70..859cf9cefe 100644 --- a/test/page.spec.js +++ b/test/page.spec.js @@ -146,14 +146,17 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF expect(await page.evaluate(() => !!window.opener)).toBe(false); expect(await popup.evaluate(() => !!window.opener)).toBe(true); }); - it('should work with fake-clicking target=_blank and rel=noopener', async({page, server}) => { + it.skip(FFOX)('should work with fake-clicking target=_blank and rel=noopener', async({page, server}) => { + // TODO: FFOX sends events for "one-style.html" request to both pages. await page.goto(server.EMPTY_PAGE); await page.setContent('yo'); const [popup] = await Promise.all([ - new Promise(x => page.once('popup', x)), + page.waitForEvent('popup'), page.$eval('a', a => a.click()), ]); expect(await page.evaluate(() => !!window.opener)).toBe(false); + // TODO: At this point popup might still have about:blank as the current document. + // FFOX is slow enough to trigger this. We should do something about popups api. expect(await popup.evaluate(() => !!window.opener)).toBe(false); }); it('should work with clicking target=_blank and rel=noopener', async({page, server}) => { @@ -381,7 +384,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF it('should work with relaxed search params match', async({page, server}) => { await page.goto(server.EMPTY_PAGE); const [request] = await Promise.all([ - page.waitForRequest({ searchParams: { 'foo': ['bar', /^baz$/], 'bar': 'foo' } }), + page.waitForRequest({ searchParams: { 'foo': ['bar', /^baz$/], 'bar': 'foo' }, url: /\.png/ }), page.evaluate(() => { fetch('/digits/1.png?key=value&foo=something'); fetch('/digits/2.png?foo=baz'); @@ -402,7 +405,7 @@ module.exports.addTests = function({testRunner, expect, headless, playwright, FF it('should throw for incorrect searchParams match', async({page, server}) => { await page.goto(server.EMPTY_PAGE); const [error] = await Promise.all([ - page.waitForRequest({ searchParams: { 'foo': 123 } }).catch(e => e), + page.waitForRequest({ searchParams: { 'foo': 123 }, url: /\.png/ }).catch(e => e), page.evaluate(() => { fetch('/digits/1.png?foo=bar'); }) diff --git a/test/waittask.spec.js b/test/waittask.spec.js index de4cf887e9..64180130ae 100644 --- a/test/waittask.spec.js +++ b/test/waittask.spec.js @@ -252,7 +252,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO await otherFrame.evaluate(addElement, 'div'); await page.evaluate(addElement, 'div'); const eHandle = await watchdog; - expect(eHandle.executionContext().frame()).toBe(page.mainFrame()); + expect(eHandle.frame()).toBe(page.mainFrame()); }); it('should run in specified frame', async({page, server}) => { @@ -264,7 +264,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO await frame1.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div'); const eHandle = await waitForSelectorPromise; - expect(eHandle.executionContext().frame()).toBe(frame2); + expect(eHandle.frame()).toBe(frame2); }); it('should throw when frame is detached', async({page, server}) => { @@ -415,7 +415,7 @@ module.exports.addTests = function({testRunner, expect, product, playwright, FFO await frame1.evaluate(addElement, 'div'); await frame2.evaluate(addElement, 'div'); const eHandle = await waitForXPathPromise; - expect(eHandle.executionContext().frame()).toBe(frame2); + expect(eHandle.frame()).toBe(frame2); }); it('should throw when frame is detached', async({page, server}) => { await utils.attachFrame(page, 'frame1', server.EMPTY_PAGE);