mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
refactor(firefox): migrate onto ExecutionContext events (#4064)
Juggler now has Runtime domain that emits Execution Context events "ExecutionContextCreated" and "ExecutionContextDestroyed".
This commit is contained in:
parent
56dafd7424
commit
4ecbd91e4b
@ -15,10 +15,10 @@ class ExecutionContext {
|
|||||||
|
|
||||||
async evaluateHandle(pageFunction, ...args) {
|
async evaluateHandle(pageFunction, ...args) {
|
||||||
if (helper.isString(pageFunction)) {
|
if (helper.isString(pageFunction)) {
|
||||||
const payload = await this._session.send('Page.evaluate', {
|
const payload = await this._session.send('Runtime.evaluate', {
|
||||||
script: pageFunction,
|
expression: pageFunction,
|
||||||
executionContextId: this._executionContextId,
|
executionContextId: this._executionContextId,
|
||||||
});
|
}).catch(rewriteError);
|
||||||
return createHandle(this, payload.result, payload.exceptionDetails);
|
return createHandle(this, payload.result, payload.exceptionDetails);
|
||||||
}
|
}
|
||||||
if (typeof pageFunction !== 'function')
|
if (typeof pageFunction !== 'function')
|
||||||
@ -59,10 +59,10 @@ class ExecutionContext {
|
|||||||
return {unserializableValue: 'NaN'};
|
return {unserializableValue: 'NaN'};
|
||||||
return {value: arg};
|
return {value: arg};
|
||||||
});
|
});
|
||||||
let payload = null;
|
let callFunctionPromise;
|
||||||
try {
|
try {
|
||||||
payload = await this._session.send('Page.evaluate', {
|
callFunctionPromise = this._session.send('Runtime.callFunction', {
|
||||||
functionText,
|
functionDeclaration: functionText,
|
||||||
args,
|
args,
|
||||||
executionContextId: this._executionContextId
|
executionContextId: this._executionContextId
|
||||||
});
|
});
|
||||||
@ -71,7 +71,14 @@ class ExecutionContext {
|
|||||||
err.message += ' Are you passing a nested JSHandle?';
|
err.message += ' Are you passing a nested JSHandle?';
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
|
const payload = await callFunctionPromise.catch(rewriteError);
|
||||||
return createHandle(this, payload.result, payload.exceptionDetails);
|
return createHandle(this, payload.result, payload.exceptionDetails);
|
||||||
|
|
||||||
|
function rewriteError(error) {
|
||||||
|
if (error.message.includes('Failed to find execution context with id'))
|
||||||
|
throw new Error('Execution context was destroyed, most likely because of a navigation.');
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
frame() {
|
frame() {
|
||||||
|
@ -22,15 +22,41 @@ class FrameManager extends EventEmitter {
|
|||||||
this._timeoutSettings = timeoutSettings;
|
this._timeoutSettings = timeoutSettings;
|
||||||
this._mainFrame = null;
|
this._mainFrame = null;
|
||||||
this._frames = new Map();
|
this._frames = new Map();
|
||||||
|
/** @type {!Map<string, !ExecutionContext>} */
|
||||||
|
this._contextIdToContext = new Map();
|
||||||
this._eventListeners = [
|
this._eventListeners = [
|
||||||
helper.addEventListener(this._session, 'Page.eventFired', this._onEventFired.bind(this)),
|
helper.addEventListener(this._session, 'Page.eventFired', this._onEventFired.bind(this)),
|
||||||
helper.addEventListener(this._session, 'Page.frameAttached', this._onFrameAttached.bind(this)),
|
helper.addEventListener(this._session, 'Page.frameAttached', this._onFrameAttached.bind(this)),
|
||||||
helper.addEventListener(this._session, 'Page.frameDetached', this._onFrameDetached.bind(this)),
|
helper.addEventListener(this._session, 'Page.frameDetached', this._onFrameDetached.bind(this)),
|
||||||
helper.addEventListener(this._session, 'Page.navigationCommitted', this._onNavigationCommitted.bind(this)),
|
helper.addEventListener(this._session, 'Page.navigationCommitted', this._onNavigationCommitted.bind(this)),
|
||||||
helper.addEventListener(this._session, 'Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)),
|
helper.addEventListener(this._session, 'Page.sameDocumentNavigation', this._onSameDocumentNavigation.bind(this)),
|
||||||
|
helper.addEventListener(this._session, 'Runtime.executionContextCreated', this._onExecutionContextCreated.bind(this)),
|
||||||
|
helper.addEventListener(this._session, 'Runtime.executionContextDestroyed', this._onExecutionContextDestroyed.bind(this)),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
executionContextById(executionContextId) {
|
||||||
|
return this._contextIdToContext.get(executionContextId) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_onExecutionContextCreated({executionContextId, auxData}) {
|
||||||
|
const frameId = auxData ? auxData.frameId : null;
|
||||||
|
const frame = this._frames.get(frameId) || null;
|
||||||
|
const context = new ExecutionContext(this._session, frame, executionContextId);
|
||||||
|
if (frame)
|
||||||
|
frame._setContext(context);
|
||||||
|
this._contextIdToContext.set(executionContextId, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
_onExecutionContextDestroyed({executionContextId}) {
|
||||||
|
const context = this._contextIdToContext.get(executionContextId);
|
||||||
|
if (!context)
|
||||||
|
return;
|
||||||
|
this._contextIdToContext.delete(executionContextId);
|
||||||
|
if (context._frame)
|
||||||
|
context._frame._setContext(null);
|
||||||
|
}
|
||||||
|
|
||||||
frame(frameId) {
|
frame(frameId) {
|
||||||
return this._frames.get(frameId);
|
return this._frames.get(frameId);
|
||||||
}
|
}
|
||||||
@ -122,19 +148,37 @@ class Frame {
|
|||||||
this._name = '';
|
this._name = '';
|
||||||
/** @type {!Set<!Frame>} */
|
/** @type {!Set<!Frame>} */
|
||||||
this._children = new Set();
|
this._children = new Set();
|
||||||
this._isDetached = false;
|
this._detached = false;
|
||||||
|
|
||||||
|
|
||||||
this._firedEvents = new Set();
|
this._firedEvents = new Set();
|
||||||
|
|
||||||
/** @type {!Set<!WaitTask>} */
|
/** @type {!Set<!WaitTask>} */
|
||||||
this._waitTasks = new Set();
|
this._waitTasks = new Set();
|
||||||
this._documentPromise = null;
|
this._documentPromise = null;
|
||||||
|
this._contextPromise;
|
||||||
|
this._contextResolveCallback = null;
|
||||||
|
this._setContext(null);
|
||||||
|
}
|
||||||
|
|
||||||
this._executionContext = new ExecutionContext(this._session, this, this._frameId);
|
_setContext(context) {
|
||||||
|
if (context) {
|
||||||
|
this._contextResolveCallback.call(null, context);
|
||||||
|
this._contextResolveCallback = null;
|
||||||
|
for (const waitTask of this._waitTasks)
|
||||||
|
waitTask.rerun();
|
||||||
|
} else {
|
||||||
|
this._documentPromise = null;
|
||||||
|
this._contextPromise = new Promise(fulfill => {
|
||||||
|
this._contextResolveCallback = fulfill;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async executionContext() {
|
async executionContext() {
|
||||||
return this._executionContext;
|
if (this._detached)
|
||||||
|
throw new Error(`Execution Context is not available in detached frame "${this.url()}" (are you trying to evaluate?)`);
|
||||||
|
return this._contextPromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -267,7 +311,7 @@ class Frame {
|
|||||||
_detach() {
|
_detach() {
|
||||||
this._parentFrame._children.delete(this);
|
this._parentFrame._children.delete(this);
|
||||||
this._parentFrame = null;
|
this._parentFrame = null;
|
||||||
this._isDetached = true;
|
this._detached = true;
|
||||||
for (const waitTask of this._waitTasks)
|
for (const waitTask of this._waitTasks)
|
||||||
waitTask.terminate(new Error('waitForFunction failed: frame got detached.'));
|
waitTask.terminate(new Error('waitForFunction failed: frame got detached.'));
|
||||||
}
|
}
|
||||||
@ -438,7 +482,8 @@ class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async evaluate(pageFunction, ...args) {
|
async evaluate(pageFunction, ...args) {
|
||||||
return this._executionContext.evaluate(pageFunction, ...args);
|
const context = await this.executionContext();
|
||||||
|
return context.evaluate(pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
_document() {
|
_document() {
|
||||||
@ -497,7 +542,8 @@ class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async evaluateHandle(pageFunction, ...args) {
|
async evaluateHandle(pageFunction, ...args) {
|
||||||
return this._executionContext.evaluateHandle(pageFunction, ...args);
|
const context = await this.executionContext();
|
||||||
|
return context.evaluateHandle(pageFunction, ...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -636,7 +682,7 @@ class Frame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
isDetached() {
|
isDetached() {
|
||||||
return this._isDetached;
|
return this._detached;
|
||||||
}
|
}
|
||||||
|
|
||||||
childFrames() {
|
childFrames() {
|
||||||
|
@ -59,7 +59,7 @@ class JSHandle {
|
|||||||
* @return {!Promise<Map<string, !JSHandle>>}
|
* @return {!Promise<Map<string, !JSHandle>>}
|
||||||
*/
|
*/
|
||||||
async getProperties() {
|
async getProperties() {
|
||||||
const response = await this._session.send('Page.getObjectProperties', {
|
const response = await this._session.send('Runtime.getObjectProperties', {
|
||||||
executionContextId: this._executionContextId,
|
executionContextId: this._executionContextId,
|
||||||
objectId: this._objectId,
|
objectId: this._objectId,
|
||||||
});
|
});
|
||||||
@ -85,10 +85,10 @@ class JSHandle {
|
|||||||
async jsonValue() {
|
async jsonValue() {
|
||||||
if (!this._objectId)
|
if (!this._objectId)
|
||||||
return this._deserializeValue(this._protocolValue);
|
return this._deserializeValue(this._protocolValue);
|
||||||
const simpleValue = await this._session.send('Page.evaluate', {
|
const simpleValue = await this._session.send('Runtime.callFunction', {
|
||||||
executionContextId: this._executionContextId,
|
executionContextId: this._executionContextId,
|
||||||
returnByValue: true,
|
returnByValue: true,
|
||||||
functionText: (e => e).toString(),
|
functionDeclaration: (e => e).toString(),
|
||||||
args: [this._protocolValue],
|
args: [this._protocolValue],
|
||||||
});
|
});
|
||||||
return this._deserializeValue(simpleValue.result);
|
return this._deserializeValue(simpleValue.result);
|
||||||
@ -105,9 +105,13 @@ class JSHandle {
|
|||||||
if (!this._objectId)
|
if (!this._objectId)
|
||||||
return;
|
return;
|
||||||
this._disposed = true;
|
this._disposed = true;
|
||||||
await this._session.send('Page.disposeObject', {
|
await this._session.send('Runtime.disposeObject', {
|
||||||
executionContextId: this._executionContextId,
|
executionContextId: this._executionContextId,
|
||||||
objectId: this._objectId,
|
objectId: this._objectId,
|
||||||
|
}).catch(error => {
|
||||||
|
// Exceptions might happen in case of a page been navigated or closed.
|
||||||
|
// Swallow these since they are harmless and we don't leak anything in this case.
|
||||||
|
debugError(error);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ class Page extends EventEmitter {
|
|||||||
await Promise.all([
|
await Promise.all([
|
||||||
session.send('Page.enable'),
|
session.send('Page.enable'),
|
||||||
session.send('Network.enable'),
|
session.send('Network.enable'),
|
||||||
|
session.send('Runtime.enable'),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
if (defaultViewport)
|
if (defaultViewport)
|
||||||
@ -139,7 +140,7 @@ class Page extends EventEmitter {
|
|||||||
else
|
else
|
||||||
expression = helper.evaluationString(deliverErrorValue, name, seq, error);
|
expression = helper.evaluationString(deliverErrorValue, name, seq, error);
|
||||||
}
|
}
|
||||||
this._session.send('Page.evaluate', { script: expression, executionContextId: event.frameId }).catch(debugError);
|
this._session.send('Runtime.evaluate', { expression, executionContextId: event.executionContextId }).catch(debugError);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {string} name
|
* @param {string} name
|
||||||
@ -651,9 +652,9 @@ class Page extends EventEmitter {
|
|||||||
_onClosed() {
|
_onClosed() {
|
||||||
}
|
}
|
||||||
|
|
||||||
_onConsole({type, args, frameId, location}) {
|
_onConsole({type, args, executionContextId, location}) {
|
||||||
const frame = this._frameManager.frame(frameId);
|
const context = this._frameManager.executionContextById(executionContextId);
|
||||||
this.emit(Events.Page.Console, new ConsoleMessage(type, args.map(arg => createHandle(frame._executionContext, arg)), location));
|
this.emit(Events.Page.Console, new ConsoleMessage(type, args.map(arg => createHandle(context, arg)), location));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -9,7 +9,7 @@
|
|||||||
"node": ">=8.9.4"
|
"node": ">=8.9.4"
|
||||||
},
|
},
|
||||||
"puppeteer": {
|
"puppeteer": {
|
||||||
"firefox_revision": "3ba79216e3c5ae4e85006047cdd93eac4197427d"
|
"firefox_revision": "764023af0aa07d232984dec6bc81d9e904f25ddb"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"install": "node install.js",
|
"install": "node install.js",
|
||||||
|
@ -209,7 +209,7 @@ module.exports.addTests = function({testRunner, expect}) {
|
|||||||
const result = await page.evaluate(() => document.execCommand('copy'));
|
const result = await page.evaluate(() => document.execCommand('copy'));
|
||||||
expect(result).toBe(true);
|
expect(result).toBe(true);
|
||||||
});
|
});
|
||||||
it_fails_ffox('should throw a nice error after a navigation', async({page, server}) => {
|
it('should throw a nice error after a navigation', async({page, server}) => {
|
||||||
const executionContext = await page.mainFrame().executionContext();
|
const executionContext = await page.mainFrame().executionContext();
|
||||||
|
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
|
Loading…
Reference in New Issue
Block a user