UNPKG

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