UNPKG

8.14 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._objectHandleFactory = objectHandleFactory;
34 }
35
36 /**
37 * @return {?Puppeteer.Frame}
38 */
39 frame() {
40 return this._frame;
41 }
42
43 /**
44 * @param {Function|string} pageFunction
45 * @param {...*} args
46 * @return {!Promise<(!Object|undefined)>}
47 */
48 async evaluate(pageFunction, ...args) {
49 const handle = await this.evaluateHandle(pageFunction, ...args);
50 const result = await handle.jsonValue().catch(error => {
51 if (error.message.includes('Object reference chain is too long'))
52 return;
53 if (error.message.includes('Object couldn\'t be returned by value'))
54 return;
55 throw error;
56 });
57 await handle.dispose();
58 return result;
59 }
60
61 /**
62 * @param {Function|string} pageFunction
63 * @param {...*} args
64 * @return {!Promise<!JSHandle>}
65 */
66 async evaluateHandle(pageFunction, ...args) {
67 const suffix = `//# sourceURL=${EVALUATION_SCRIPT_URL}`;
68
69 if (helper.isString(pageFunction)) {
70 const contextId = this._contextId;
71 const expression = /** @type {string} */ (pageFunction);
72 const expressionWithSourceUrl = SOURCE_URL_REGEX.test(expression) ? expression : expression + '\n' + suffix;
73 const {exceptionDetails, result: remoteObject} = await this._client.send('Runtime.evaluate', {
74 expression: expressionWithSourceUrl,
75 contextId,
76 returnByValue: false,
77 awaitPromise: true,
78 userGesture: true
79 }).catch(rewriteError);
80 if (exceptionDetails)
81 throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
82 return this._objectHandleFactory(remoteObject);
83 }
84
85 if (typeof pageFunction !== 'function')
86 throw new Error('The following is not a function: ' + pageFunction);
87
88 const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', {
89 functionDeclaration: pageFunction.toString() + '\n' + suffix + '\n',
90 executionContextId: this._contextId,
91 arguments: args.map(convertArgument.bind(this)),
92 returnByValue: false,
93 awaitPromise: true,
94 userGesture: true
95 }).catch(rewriteError);
96 if (exceptionDetails)
97 throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
98 return this._objectHandleFactory(remoteObject);
99
100 /**
101 * @param {*} arg
102 * @return {*}
103 * @this {Frame}
104 */
105 function convertArgument(arg) {
106 if (Object.is(arg, -0))
107 return { unserializableValue: '-0' };
108 if (Object.is(arg, Infinity))
109 return { unserializableValue: 'Infinity' };
110 if (Object.is(arg, -Infinity))
111 return { unserializableValue: '-Infinity' };
112 if (Object.is(arg, NaN))
113 return { unserializableValue: 'NaN' };
114 const objectHandle = arg && (arg instanceof JSHandle) ? arg : null;
115 if (objectHandle) {
116 if (objectHandle._context !== this)
117 throw new Error('JSHandles can be evaluated only in the context they were created!');
118 if (objectHandle._disposed)
119 throw new Error('JSHandle is disposed!');
120 if (objectHandle._remoteObject.unserializableValue)
121 return { unserializableValue: objectHandle._remoteObject.unserializableValue };
122 if (!objectHandle._remoteObject.objectId)
123 return { value: objectHandle._remoteObject.value };
124 return { objectId: objectHandle._remoteObject.objectId };
125 }
126 return { value: arg };
127 }
128
129 /**
130 * @param {!Error} error
131 * @return {!Protocol.Runtime.evaluateReturnValue}
132 */
133 function rewriteError(error) {
134 if (error.message.endsWith('Cannot find context with specified id'))
135 throw new Error('Execution context was destroyed, most likely because of a navigation.');
136 throw error;
137 }
138 }
139
140 /**
141 * @param {!JSHandle} prototypeHandle
142 * @return {!Promise<!JSHandle>}
143 */
144 async queryObjects(prototypeHandle) {
145 assert(!prototypeHandle._disposed, 'Prototype JSHandle is disposed!');
146 assert(prototypeHandle._remoteObject.objectId, 'Prototype JSHandle must not be referencing primitive value');
147 const response = await this._client.send('Runtime.queryObjects', {
148 prototypeObjectId: prototypeHandle._remoteObject.objectId
149 });
150 return this._objectHandleFactory(response.objects);
151 }
152}
153
154class JSHandle {
155 /**
156 * @param {!ExecutionContext} context
157 * @param {!Puppeteer.CDPSession} client
158 * @param {!Protocol.Runtime.RemoteObject} remoteObject
159 */
160 constructor(context, client, remoteObject) {
161 this._context = context;
162 this._client = client;
163 this._remoteObject = remoteObject;
164 this._disposed = false;
165 }
166
167 /**
168 * @return {!ExecutionContext}
169 */
170 executionContext() {
171 return this._context;
172 }
173
174 /**
175 * @param {string} propertyName
176 * @return {!Promise<?JSHandle>}
177 */
178 async getProperty(propertyName) {
179 const objectHandle = await this._context.evaluateHandle((object, propertyName) => {
180 const result = {__proto__: null};
181 result[propertyName] = object[propertyName];
182 return result;
183 }, this, propertyName);
184 const properties = await objectHandle.getProperties();
185 const result = properties.get(propertyName) || null;
186 await objectHandle.dispose();
187 return result;
188 }
189
190 /**
191 * @return {!Promise<Map<string, !JSHandle>>}
192 */
193 async getProperties() {
194 const response = await this._client.send('Runtime.getProperties', {
195 objectId: this._remoteObject.objectId,
196 ownProperties: true
197 });
198 const result = new Map();
199 for (const property of response.result) {
200 if (!property.enumerable)
201 continue;
202 result.set(property.name, this._context._objectHandleFactory(property.value));
203 }
204 return result;
205 }
206
207 /**
208 * @return {!Promise<?Object>}
209 */
210 async jsonValue() {
211 if (this._remoteObject.objectId) {
212 const response = await this._client.send('Runtime.callFunctionOn', {
213 functionDeclaration: 'function() { return this; }',
214 objectId: this._remoteObject.objectId,
215 returnByValue: true,
216 awaitPromise: true,
217 });
218 return helper.valueFromRemoteObject(response.result);
219 }
220 return helper.valueFromRemoteObject(this._remoteObject);
221 }
222
223 /**
224 * @return {?Puppeteer.ElementHandle}
225 */
226 asElement() {
227 return null;
228 }
229
230 async dispose() {
231 if (this._disposed)
232 return;
233 this._disposed = true;
234 await helper.releaseObject(this._client, this._remoteObject);
235 }
236
237 /**
238 * @override
239 * @return {string}
240 */
241 toString() {
242 if (this._remoteObject.objectId) {
243 const type = this._remoteObject.subtype || this._remoteObject.type;
244 return 'JSHandle@' + type;
245 }
246 return 'JSHandle:' + helper.valueFromRemoteObject(this._remoteObject);
247 }
248}
249
250helper.tracePublicAPI(JSHandle);
251module.exports = {ExecutionContext, JSHandle, EVALUATION_SCRIPT_URL};