UNPKG

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