UNPKG

12.5 kBJavaScriptView Raw
1/**
2 * Copyright 2017 Google Inc. All rights reserved.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17const {helper, assert} = require('./helper');
18
19const EVALUATION_SCRIPT_URL = '__puppeteer_evaluation_script__';
20const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
21
22class ExecutionContext {
23 /**
24 * @param {!Puppeteer.CDPSession} client
25 * @param {!Protocol.Runtime.ExecutionContextDescription} contextPayload
26 * @param {function(!Protocol.Runtime.RemoteObject):!JSHandle} objectHandleFactory
27 * @param {?Puppeteer.Frame} frame
28 */
29 constructor(client, contextPayload, objectHandleFactory, frame) {
30 this._client = client;
31 this._frame = frame;
32 this._contextId = contextPayload.id;
33 this._isDefault = contextPayload.auxData ? !!contextPayload.auxData['isDefault'] : false;
34 this._objectHandleFactory = objectHandleFactory;
35 }
36
37 /**
38 * @return {?Puppeteer.Frame}
39 */
40 frame() {
41 return this._frame;
42 }
43
44 /**
45 * @param {Function|string} pageFunction
46 * @param {...*} args
47 * @return {!Promise<(!Object|undefined)>}
48 */
49 /* async */ evaluate(pageFunction, ...args) {return (fn => {
50 const gen = fn.call(this);
51 return new Promise((resolve, reject) => {
52 function step(key, arg) {
53 let info, value;
54 try {
55 info = gen[key](arg);
56 value = info.value;
57 } catch (error) {
58 reject(error);
59 return;
60 }
61 if (info.done) {
62 resolve(value);
63 } else {
64 return Promise.resolve(value).then(
65 value => {
66 step('next', value);
67 },
68 err => {
69 step('throw', err);
70 });
71 }
72 }
73 return step('next');
74 });
75})(function*(){
76 const handle = (yield this.evaluateHandle(pageFunction, ...args));
77 const result = (yield handle.jsonValue().catch(error => {
78 if (error.message.includes('Object reference chain is too long'))
79 return;
80 if (error.message.includes('Object couldn\'t be returned by value'))
81 return;
82 throw error;
83 }));
84 (yield handle.dispose());
85 return result;
86 });}
87
88 /**
89 * @param {Function|string} pageFunction
90 * @param {...*} args
91 * @return {!Promise<!JSHandle>}
92 */
93 /* async */ evaluateHandle(pageFunction, ...args) {return (fn => {
94 const gen = fn.call(this);
95 return new Promise((resolve, reject) => {
96 function step(key, arg) {
97 let info, value;
98 try {
99 info = gen[key](arg);
100 value = info.value;
101 } catch (error) {
102 reject(error);
103 return;
104 }
105 if (info.done) {
106 resolve(value);
107 } else {
108 return Promise.resolve(value).then(
109 value => {
110 step('next', value);
111 },
112 err => {
113 step('throw', err);
114 });
115 }
116 }
117 return step('next');
118 });
119})(function*(){
120 const suffix = `//# sourceURL=${EVALUATION_SCRIPT_URL}`;
121
122 if (helper.isString(pageFunction)) {
123 const contextId = this._contextId;
124 const expression = /** @type {string} */ (pageFunction);
125 const expressionWithSourceUrl = SOURCE_URL_REGEX.test(expression) ? expression : expression + '\n' + suffix;
126 const {exceptionDetails, result: remoteObject} = (yield this._client.send('Runtime.evaluate', {
127 expression: expressionWithSourceUrl,
128 contextId,
129 returnByValue: false,
130 awaitPromise: true,
131 userGesture: true
132 }).catch(rewriteError));
133 if (exceptionDetails)
134 throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
135 return this._objectHandleFactory(remoteObject);
136 }
137
138 if (typeof pageFunction !== 'function')
139 throw new Error('The following is not a function: ' + pageFunction);
140
141 const { exceptionDetails, result: remoteObject } = (yield this._client.send('Runtime.callFunctionOn', {
142 functionDeclaration: pageFunction.toString() + '\n' + suffix + '\n',
143 executionContextId: this._contextId,
144 arguments: args.map(convertArgument.bind(this)),
145 returnByValue: false,
146 awaitPromise: true,
147 userGesture: true
148 }).catch(rewriteError));
149 if (exceptionDetails)
150 throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
151 return this._objectHandleFactory(remoteObject);
152
153 /**
154 * @param {*} arg
155 * @return {*}
156 * @this {ExecutionContext}
157 */
158 function convertArgument(arg) {
159 if (Object.is(arg, -0))
160 return { unserializableValue: '-0' };
161 if (Object.is(arg, Infinity))
162 return { unserializableValue: 'Infinity' };
163 if (Object.is(arg, -Infinity))
164 return { unserializableValue: '-Infinity' };
165 if (Object.is(arg, NaN))
166 return { unserializableValue: 'NaN' };
167 const objectHandle = arg && (arg instanceof JSHandle) ? arg : null;
168 if (objectHandle) {
169 if (objectHandle._context !== this)
170 throw new Error('JSHandles can be evaluated only in the context they were created!');
171 if (objectHandle._disposed)
172 throw new Error('JSHandle is disposed!');
173 if (objectHandle._remoteObject.unserializableValue)
174 return { unserializableValue: objectHandle._remoteObject.unserializableValue };
175 if (!objectHandle._remoteObject.objectId)
176 return { value: objectHandle._remoteObject.value };
177 return { objectId: objectHandle._remoteObject.objectId };
178 }
179 return { value: arg };
180 }
181
182 /**
183 * @param {!Error} error
184 * @return {!Protocol.Runtime.evaluateReturnValue}
185 */
186 function rewriteError(error) {
187 if (error.message.endsWith('Cannot find context with specified id'))
188 throw new Error('Execution context was destroyed, most likely because of a navigation.');
189 throw error;
190 }
191 });}
192
193 /**
194 * @param {!JSHandle} prototypeHandle
195 * @return {!Promise<!JSHandle>}
196 */
197 /* async */ queryObjects(prototypeHandle) {return (fn => {
198 const gen = fn.call(this);
199 return new Promise((resolve, reject) => {
200 function step(key, arg) {
201 let info, value;
202 try {
203 info = gen[key](arg);
204 value = info.value;
205 } catch (error) {
206 reject(error);
207 return;
208 }
209 if (info.done) {
210 resolve(value);
211 } else {
212 return Promise.resolve(value).then(
213 value => {
214 step('next', value);
215 },
216 err => {
217 step('throw', err);
218 });
219 }
220 }
221 return step('next');
222 });
223})(function*(){
224 assert(!prototypeHandle._disposed, 'Prototype JSHandle is disposed!');
225 assert(prototypeHandle._remoteObject.objectId, 'Prototype JSHandle must not be referencing primitive value');
226 const response = (yield this._client.send('Runtime.queryObjects', {
227 prototypeObjectId: prototypeHandle._remoteObject.objectId
228 }));
229 return this._objectHandleFactory(response.objects);
230 });}
231}
232
233class JSHandle {
234 /**
235 * @param {!ExecutionContext} context
236 * @param {!Puppeteer.CDPSession} client
237 * @param {!Protocol.Runtime.RemoteObject} remoteObject
238 */
239 constructor(context, client, remoteObject) {
240 this._context = context;
241 this._client = client;
242 this._remoteObject = remoteObject;
243 this._disposed = false;
244 }
245
246 /**
247 * @return {!ExecutionContext}
248 */
249 executionContext() {
250 return this._context;
251 }
252
253 /**
254 * @param {string} propertyName
255 * @return {!Promise<?JSHandle>}
256 */
257 /* async */ getProperty(propertyName) {return (fn => {
258 const gen = fn.call(this);
259 return new Promise((resolve, reject) => {
260 function step(key, arg) {
261 let info, value;
262 try {
263 info = gen[key](arg);
264 value = info.value;
265 } catch (error) {
266 reject(error);
267 return;
268 }
269 if (info.done) {
270 resolve(value);
271 } else {
272 return Promise.resolve(value).then(
273 value => {
274 step('next', value);
275 },
276 err => {
277 step('throw', err);
278 });
279 }
280 }
281 return step('next');
282 });
283})(function*(){
284 const objectHandle = (yield this._context.evaluateHandle((object, propertyName) => {
285 const result = {__proto__: null};
286 result[propertyName] = object[propertyName];
287 return result;
288 }, this, propertyName));
289 const properties = (yield objectHandle.getProperties());
290 const result = properties.get(propertyName) || null;
291 (yield objectHandle.dispose());
292 return result;
293 });}
294
295 /**
296 * @return {!Promise<Map<string, !JSHandle>>}
297 */
298 /* async */ getProperties() {return (fn => {
299 const gen = fn.call(this);
300 return new Promise((resolve, reject) => {
301 function step(key, arg) {
302 let info, value;
303 try {
304 info = gen[key](arg);
305 value = info.value;
306 } catch (error) {
307 reject(error);
308 return;
309 }
310 if (info.done) {
311 resolve(value);
312 } else {
313 return Promise.resolve(value).then(
314 value => {
315 step('next', value);
316 },
317 err => {
318 step('throw', err);
319 });
320 }
321 }
322 return step('next');
323 });
324})(function*(){
325 const response = (yield this._client.send('Runtime.getProperties', {
326 objectId: this._remoteObject.objectId,
327 ownProperties: true
328 }));
329 const result = new Map();
330 for (const property of response.result) {
331 if (!property.enumerable)
332 continue;
333 result.set(property.name, this._context._objectHandleFactory(property.value));
334 }
335 return result;
336 });}
337
338 /**
339 * @return {!Promise<?Object>}
340 */
341 /* async */ jsonValue() {return (fn => {
342 const gen = fn.call(this);
343 return new Promise((resolve, reject) => {
344 function step(key, arg) {
345 let info, value;
346 try {
347 info = gen[key](arg);
348 value = info.value;
349 } catch (error) {
350 reject(error);
351 return;
352 }
353 if (info.done) {
354 resolve(value);
355 } else {
356 return Promise.resolve(value).then(
357 value => {
358 step('next', value);
359 },
360 err => {
361 step('throw', err);
362 });
363 }
364 }
365 return step('next');
366 });
367})(function*(){
368 if (this._remoteObject.objectId) {
369 const response = (yield this._client.send('Runtime.callFunctionOn', {
370 functionDeclaration: 'function() { return this; }',
371 objectId: this._remoteObject.objectId,
372 returnByValue: true,
373 awaitPromise: true,
374 }));
375 return helper.valueFromRemoteObject(response.result);
376 }
377 return helper.valueFromRemoteObject(this._remoteObject);
378 });}
379
380 /**
381 * @return {?Puppeteer.ElementHandle}
382 */
383 asElement() {
384 return null;
385 }
386
387 /* async */ dispose() {return (fn => {
388 const gen = fn.call(this);
389 return new Promise((resolve, reject) => {
390 function step(key, arg) {
391 let info, value;
392 try {
393 info = gen[key](arg);
394 value = info.value;
395 } catch (error) {
396 reject(error);
397 return;
398 }
399 if (info.done) {
400 resolve(value);
401 } else {
402 return Promise.resolve(value).then(
403 value => {
404 step('next', value);
405 },
406 err => {
407 step('throw', err);
408 });
409 }
410 }
411 return step('next');
412 });
413})(function*(){
414 if (this._disposed)
415 return;
416 this._disposed = true;
417 (yield helper.releaseObject(this._client, this._remoteObject));
418 });}
419
420 /**
421 * @override
422 * @return {string}
423 */
424 toString() {
425 if (this._remoteObject.objectId) {
426 const type = this._remoteObject.subtype || this._remoteObject.type;
427 return 'JSHandle@' + type;
428 }
429 return 'JSHandle:' + helper.valueFromRemoteObject(this._remoteObject);
430 }
431}
432
433helper.tracePublicAPI(JSHandle);
434module.exports = {ExecutionContext, JSHandle, EVALUATION_SCRIPT_URL};