Skip to content

Commit e168fdd

Browse files
authored
fix(evaluate): consistently serialize json values (#2377)
1 parent 609bc4c commit e168fdd

17 files changed

+408
-439
lines changed

src/chromium/crAccessibility.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ class CRAXNode implements accessibility.AXNode {
9595
}
9696

9797
async _findElement(element: dom.ElementHandle): Promise<CRAXNode | null> {
98-
const remoteObject = element._remoteObject as Protocol.Runtime.RemoteObject;
99-
const {node: {backendNodeId}} = await this._client.send('DOM.describeNode', {objectId: remoteObject.objectId});
98+
const objectId = element._objectId!;
99+
const {node: {backendNodeId}} = await this._client.send('DOM.describeNode', { objectId });
100100
const needle = this.find(node => node._payload.backendDOMNodeId === backendNodeId);
101101
return needle || null;
102102
}

src/chromium/crExecutionContext.ts

Lines changed: 17 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@
1717

1818
import { CRSession } from './crConnection';
1919
import { helper } from '../helper';
20-
import { valueFromRemoteObject, getExceptionMessage, releaseObject } from './crProtocolHelper';
20+
import { getExceptionMessage, releaseObject } from './crProtocolHelper';
2121
import { Protocol } from './protocol';
2222
import * as js from '../javascript';
2323
import * as debugSupport from '../debug/debugSupport';
24+
import { RemoteObject, parseEvaluationResultValue } from '../remoteObject';
2425

2526
export class CRExecutionContext implements js.ExecutionContextDelegate {
2627
_client: CRSession;
@@ -31,7 +32,7 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
3132
this._contextId = contextPayload.id;
3233
}
3334

34-
async rawEvaluate(expression: string): Promise<js.RemoteObject> {
35+
async rawEvaluate(expression: string): Promise<RemoteObject> {
3536
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', {
3637
expression: debugSupport.ensureSourceUrl(expression),
3738
contextId: this._contextId,
@@ -51,29 +52,7 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
5152

5253
if (typeof pageFunction !== 'function')
5354
throw new Error(`Expected to get |string| or |function| as the first argument, but got "${pageFunction}" instead.`);
54-
55-
const { functionText, values, handles, dispose } = await js.prepareFunctionCall<Protocol.Runtime.CallArgument>(pageFunction, context, args, (value: any) => {
56-
if (typeof value === 'bigint') // eslint-disable-line valid-typeof
57-
return { handle: { unserializableValue: `${value.toString()}n` } };
58-
if (Object.is(value, -0))
59-
return { handle: { unserializableValue: '-0' } };
60-
if (Object.is(value, Infinity))
61-
return { handle: { unserializableValue: 'Infinity' } };
62-
if (Object.is(value, -Infinity))
63-
return { handle: { unserializableValue: '-Infinity' } };
64-
if (Object.is(value, NaN))
65-
return { handle: { unserializableValue: 'NaN' } };
66-
if (value && (value instanceof js.JSHandle)) {
67-
const remoteObject = toRemoteObject(value);
68-
if (remoteObject.unserializableValue)
69-
return { handle: { unserializableValue: remoteObject.unserializableValue } };
70-
if (!remoteObject.objectId)
71-
return { handle: { value: remoteObject.value } };
72-
return { handle: { objectId: remoteObject.objectId } };
73-
}
74-
return { value };
75-
});
76-
55+
const { functionText, values, handles, dispose } = await js.prepareFunctionCall(pageFunction, context, args);
7756
return this._callOnUtilityScript(context,
7857
'callFunction', [
7958
{ value: functionText },
@@ -87,7 +66,7 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
8766
const utilityScript = await context.utilityScript();
8867
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', {
8968
functionDeclaration: `function (...args) { return this.${method}(...args) }` + debugSupport.generateSourceUrl(),
90-
objectId: utilityScript._remoteObject.objectId,
69+
objectId: utilityScript._objectId,
9170
arguments: [
9271
{ value: returnByValue },
9372
...args
@@ -98,14 +77,14 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
9877
}).catch(rewriteError);
9978
if (exceptionDetails)
10079
throw new Error('Evaluation failed: ' + getExceptionMessage(exceptionDetails));
101-
return returnByValue ? valueFromRemoteObject(remoteObject) : context.createHandle(remoteObject);
80+
return returnByValue ? parseEvaluationResultValue(remoteObject.value) : context.createHandle(remoteObject);
10281
} finally {
10382
dispose();
10483
}
10584
}
10685

10786
async getProperties(handle: js.JSHandle): Promise<Map<string, js.JSHandle>> {
108-
const objectId = toRemoteObject(handle).objectId;
87+
const objectId = handle._objectId;
10988
if (!objectId)
11089
return new Map();
11190
const response = await this._client.send('Runtime.getProperties', {
@@ -114,43 +93,28 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
11493
});
11594
const result = new Map();
11695
for (const property of response.result) {
117-
if (!property.enumerable)
96+
if (!property.enumerable || !property.value)
11897
continue;
11998
result.set(property.name, handle._context.createHandle(property.value));
12099
}
121100
return result;
122101
}
123102

124103
async releaseHandle(handle: js.JSHandle): Promise<void> {
125-
await releaseObject(this._client, toRemoteObject(handle));
104+
if (!handle._objectId)
105+
return;
106+
await releaseObject(this._client, handle._objectId);
126107
}
127108

128109
async handleJSONValue<T>(handle: js.JSHandle<T>): Promise<T> {
129-
const remoteObject = toRemoteObject(handle);
130-
if (remoteObject.objectId) {
131-
const response = await this._client.send('Runtime.callFunctionOn', {
132-
functionDeclaration: 'function() { return this; }' + debugSupport.generateSourceUrl(),
133-
objectId: remoteObject.objectId,
134-
returnByValue: true,
135-
awaitPromise: true,
136-
});
137-
return valueFromRemoteObject(response.result);
110+
if (handle._objectId) {
111+
return this._callOnUtilityScript(handle._context,
112+
`jsonValue`, [
113+
{ objectId: handle._objectId },
114+
], true, () => {});
138115
}
139-
return valueFromRemoteObject(remoteObject);
116+
return handle._value;
140117
}
141-
142-
handleToString(handle: js.JSHandle, includeType: boolean): string {
143-
const object = toRemoteObject(handle);
144-
if (object.objectId) {
145-
const type = object.subtype || object.type;
146-
return 'JSHandle@' + type;
147-
}
148-
return (includeType ? 'JSHandle:' : '') + valueFromRemoteObject(object);
149-
}
150-
}
151-
152-
function toRemoteObject(handle: js.JSHandle): Protocol.Runtime.RemoteObject {
153-
return handle._remoteObject as Protocol.Runtime.RemoteObject;
154118
}
155119

156120
function rewriteError(error: Error): Protocol.Runtime.evaluateReturnValue {

src/chromium/crPage.ts

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -665,7 +665,7 @@ class FrameSession {
665665
_onLogEntryAdded(event: Protocol.Log.entryAddedPayload) {
666666
const {level, text, args, source, url, lineNumber} = event.entry;
667667
if (args)
668-
args.map(arg => releaseObject(this._client, arg));
668+
args.map(arg => releaseObject(this._client, arg.objectId!));
669669
if (source !== 'worker')
670670
this._page.emit(Events.Page.Console, new ConsoleMessage(level, text, [], {url, lineNumber}));
671671
}
@@ -778,7 +778,7 @@ class FrameSession {
778778

779779
async _getContentFrame(handle: dom.ElementHandle): Promise<frames.Frame | null> {
780780
const nodeInfo = await this._client.send('DOM.describeNode', {
781-
objectId: handle._remoteObject.objectId
781+
objectId: handle._objectId
782782
});
783783
if (!nodeInfo || typeof nodeInfo.node.frameId !== 'string')
784784
return null;
@@ -795,11 +795,10 @@ class FrameSession {
795795
});
796796
if (!documentElement)
797797
return null;
798-
const remoteObject = documentElement._remoteObject;
799-
if (!remoteObject.objectId)
798+
if (!documentElement._objectId)
800799
return null;
801800
const nodeInfo = await this._client.send('DOM.describeNode', {
802-
objectId: remoteObject.objectId
801+
objectId: documentElement._objectId
803802
});
804803
const frameId = nodeInfo && typeof nodeInfo.node.frameId === 'string' ?
805804
nodeInfo.node.frameId : null;
@@ -809,7 +808,7 @@ class FrameSession {
809808

810809
async _getBoundingBox(handle: dom.ElementHandle): Promise<types.Rect | null> {
811810
const result = await this._client.send('DOM.getBoxModel', {
812-
objectId: handle._remoteObject.objectId
811+
objectId: handle._objectId
813812
}).catch(logError(this._page));
814813
if (!result)
815814
return null;
@@ -823,7 +822,7 @@ class FrameSession {
823822

824823
async _scrollRectIntoViewIfNeeded(handle: dom.ElementHandle, rect?: types.Rect): Promise<'success' | 'invisible'> {
825824
return await this._client.send('DOM.scrollIntoViewIfNeeded', {
826-
objectId: handle._remoteObject.objectId,
825+
objectId: handle._objectId,
827826
rect,
828827
}).then(() => 'success' as const).catch(e => {
829828
if (e instanceof Error && e.message.includes('Node does not have a layout object'))
@@ -839,7 +838,7 @@ class FrameSession {
839838

840839
async _getContentQuads(handle: dom.ElementHandle): Promise<types.Quad[] | null> {
841840
const result = await this._client.send('DOM.getContentQuads', {
842-
objectId: handle._remoteObject.objectId
841+
objectId: handle._objectId
843842
}).catch(logError(this._page));
844843
if (!result)
845844
return null;
@@ -853,7 +852,7 @@ class FrameSession {
853852

854853
async _adoptElementHandle<T extends Node>(handle: dom.ElementHandle<T>, to: dom.FrameExecutionContext): Promise<dom.ElementHandle<T>> {
855854
const nodeInfo = await this._client.send('DOM.describeNode', {
856-
objectId: handle._remoteObject.objectId,
855+
objectId: handle._objectId,
857856
});
858857
return this._adoptBackendNodeId(nodeInfo.node.backendNodeId, to) as Promise<dom.ElementHandle<T>>;
859858
}

src/chromium/crProtocolHelper.ts

Lines changed: 2 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
* limitations under the License.
1616
*/
1717

18-
import { assert } from '../helper';
1918
import { CRSession } from './crConnection';
2019
import { Protocol } from './protocol';
2120
import * as fs from 'fs';
@@ -35,31 +34,8 @@ export function getExceptionMessage(exceptionDetails: Protocol.Runtime.Exception
3534
return message;
3635
}
3736

38-
export function valueFromRemoteObject(remoteObject: Protocol.Runtime.RemoteObject): any {
39-
assert(!remoteObject.objectId, 'Cannot extract value when objectId is given');
40-
if (remoteObject.unserializableValue) {
41-
if (remoteObject.type === 'bigint' && typeof BigInt !== 'undefined')
42-
return BigInt(remoteObject.unserializableValue.replace('n', ''));
43-
switch (remoteObject.unserializableValue) {
44-
case '-0':
45-
return -0;
46-
case 'NaN':
47-
return NaN;
48-
case 'Infinity':
49-
return Infinity;
50-
case '-Infinity':
51-
return -Infinity;
52-
default:
53-
throw new Error('Unsupported unserializable value: ' + remoteObject.unserializableValue);
54-
}
55-
}
56-
return remoteObject.value;
57-
}
58-
59-
export async function releaseObject(client: CRSession, remoteObject: Protocol.Runtime.RemoteObject) {
60-
if (!remoteObject.objectId)
61-
return;
62-
await client.send('Runtime.releaseObject', {objectId: remoteObject.objectId}).catch(error => {});
37+
export async function releaseObject(client: CRSession, objectId: string) {
38+
await client.send('Runtime.releaseObject', { objectId }).catch(error => {});
6339
}
6440

6541
export async function readProtocolStream(client: CRSession, handle: string, path: string | null): Promise<Buffer> {

src/console.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ export class ConsoleMessage {
4242

4343
text(): string {
4444
if (this._text === undefined)
45-
this._text = this._args.map(arg => arg._context._delegate.handleToString(arg, false /* includeType */)).join(' ');
45+
this._text = this._args.map(arg => arg._handleToString(false /* includeType */)).join(' ');
4646
return this._text;
4747
}
4848

src/firefox/ffAccessibility.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { Protocol } from './protocol';
2121
import * as dom from '../dom';
2222

2323
export async function getAccessibilityTree(session: FFSession, needle?: dom.ElementHandle): Promise<{tree: accessibility.AXNode, needle: accessibility.AXNode | null}> {
24-
const objectId = needle ? needle._remoteObject.objectId : undefined;
24+
const objectId = needle ? needle._objectId : undefined;
2525
const { tree } = await session.send('Accessibility.getFullAXTree', { objectId });
2626
const axNode = new FFAXNode(tree);
2727
return {

0 commit comments

Comments
 (0)