UNPKG

7.27 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
19class ExecutionContext {
20 /**
21 * @param {!Puppeteer.CDPSession} client
22 * @param {!Protocol.Runtime.ExecutionContextDescription} contextPayload
23 * @param {function(!Protocol.Runtime.RemoteObject):!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', {
68 expression,
69 contextId,
70 returnByValue: false,
71 awaitPromise: true,
72 userGesture: true
73 });
74 if (exceptionDetails)
75 throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
76 return this._objectHandleFactory(remoteObject);
77 }
78
79 const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', {
80 functionDeclaration: pageFunction.toString(),
81 executionContextId: this._contextId,
82 arguments: args.map(convertArgument.bind(this)),
83 returnByValue: false,
84 awaitPromise: true,
85 userGesture: true
86 });
87 if (exceptionDetails)
88 throw new Error('Evaluation failed: ' + helper.getExceptionMessage(exceptionDetails));
89 return this._objectHandleFactory(remoteObject);
90
91 /**
92 * @param {*} arg
93 * @return {*}
94 * @this {Frame}
95 */
96 function convertArgument(arg) {
97 if (Object.is(arg, -0))
98 return { unserializableValue: '-0' };
99 if (Object.is(arg, Infinity))
100 return { unserializableValue: 'Infinity' };
101 if (Object.is(arg, -Infinity))
102 return { unserializableValue: '-Infinity' };
103 if (Object.is(arg, NaN))
104 return { unserializableValue: 'NaN' };
105 const objectHandle = arg && (arg instanceof JSHandle) ? arg : null;
106 if (objectHandle) {
107 if (objectHandle._context !== this)
108 throw new Error('JSHandles can be evaluated only in the context they were created!');
109 if (objectHandle._disposed)
110 throw new Error('JSHandle is disposed!');
111 if (objectHandle._remoteObject.unserializableValue)
112 return { unserializableValue: objectHandle._remoteObject.unserializableValue };
113 if (!objectHandle._remoteObject.objectId)
114 return { value: objectHandle._remoteObject.value };
115 return { objectId: objectHandle._remoteObject.objectId };
116 }
117 return { value: arg };
118 }
119 }
120
121 /**
122 * @param {!JSHandle} prototypeHandle
123 * @return {!Promise<!JSHandle>}
124 */
125 async queryObjects(prototypeHandle) {
126 assert(!prototypeHandle._disposed, 'Prototype JSHandle is disposed!');
127 assert(prototypeHandle._remoteObject.objectId, 'Prototype JSHandle must not be referencing primitive value');
128 const response = await this._client.send('Runtime.queryObjects', {
129 prototypeObjectId: prototypeHandle._remoteObject.objectId
130 });
131 return this._objectHandleFactory(response.objects);
132 }
133}
134
135class JSHandle {
136 /**
137 * @param {!ExecutionContext} context
138 * @param {!Puppeteer.CDPSession} client
139 * @param {!Protocol.Runtime.RemoteObject} remoteObject
140 */
141 constructor(context, client, remoteObject) {
142 this._context = context;
143 this._client = client;
144 this._remoteObject = remoteObject;
145 this._disposed = false;
146 }
147
148 /**
149 * @return {!ExecutionContext}
150 */
151 executionContext() {
152 return this._context;
153 }
154
155 /**
156 * @param {string} propertyName
157 * @return {!Promise<?JSHandle>}
158 */
159 async getProperty(propertyName) {
160 const objectHandle = await this._context.evaluateHandle((object, propertyName) => {
161 const result = {__proto__: null};
162 result[propertyName] = object[propertyName];
163 return result;
164 }, this, propertyName);
165 const properties = await objectHandle.getProperties();
166 const result = properties.get(propertyName) || null;
167 await objectHandle.dispose();
168 return result;
169 }
170
171 /**
172 * @return {!Promise<Map<string, !JSHandle>>}
173 */
174 async getProperties() {
175 const response = await this._client.send('Runtime.getProperties', {
176 objectId: this._remoteObject.objectId,
177 ownProperties: true
178 });
179 const result = new Map();
180 for (const property of response.result) {
181 if (!property.enumerable)
182 continue;
183 result.set(property.name, this._context._objectHandleFactory(property.value));
184 }
185 return result;
186 }
187
188 /**
189 * @return {!Promise<?Object>}
190 */
191 async jsonValue() {
192 if (this._remoteObject.objectId) {
193 const response = await this._client.send('Runtime.callFunctionOn', {
194 functionDeclaration: 'function() { return this; }',
195 objectId: this._remoteObject.objectId,
196 returnByValue: true,
197 awaitPromise: true,
198 });
199 return helper.valueFromRemoteObject(response.result);
200 }
201 return helper.valueFromRemoteObject(this._remoteObject);
202 }
203
204 /**
205 * @return {?Puppeteer.ElementHandle}
206 */
207 asElement() {
208 return null;
209 }
210
211 async dispose() {
212 if (this._disposed)
213 return;
214 this._disposed = true;
215 await helper.releaseObject(this._client, this._remoteObject);
216 }
217
218 /**
219 * @override
220 * @return {string}
221 */
222 toString() {
223 if (this._remoteObject.objectId) {
224 const type = this._remoteObject.subtype || this._remoteObject.type;
225 return 'JSHandle@' + type;
226 }
227 return 'JSHandle:' + helper.valueFromRemoteObject(this._remoteObject);
228 }
229}
230
231helper.tracePublicAPI(JSHandle);
232module.exports = {ExecutionContext, JSHandle};