chore: pass JSHandles instead of ObjectId to/from context delegate (#34895)
This commit is contained in:
parent
954457ba9e
commit
dbbdabfd1b
|
@ -61,7 +61,7 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
||||||
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
|
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
async rawEvaluateHandle(expression: string): Promise<js.ObjectId> {
|
async rawEvaluateHandle(context: js.ExecutionContext, expression: string): Promise<js.JSHandle> {
|
||||||
const response = await this._session.send('script.evaluate', {
|
const response = await this._session.send('script.evaluate', {
|
||||||
expression,
|
expression,
|
||||||
target: this._target,
|
target: this._target,
|
||||||
|
@ -72,7 +72,7 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
||||||
});
|
});
|
||||||
if (response.type === 'success') {
|
if (response.type === 'success') {
|
||||||
if ('handle' in response.result)
|
if ('handle' in response.result)
|
||||||
return response.result.handle!;
|
return createHandle(context, response.result);
|
||||||
throw new js.JavaScriptErrorInEvaluate('Cannot get handle: ' + JSON.stringify(response.result));
|
throw new js.JavaScriptErrorInEvaluate('Cannot get handle: ' + JSON.stringify(response.result));
|
||||||
}
|
}
|
||||||
if (response.type === 'exception')
|
if (response.type === 'exception')
|
||||||
|
@ -80,14 +80,14 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
||||||
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
|
throw new js.JavaScriptErrorInEvaluate('Unexpected response type: ' + JSON.stringify(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateWithArguments(functionDeclaration: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], objectIds: string[]): Promise<any> {
|
async evaluateWithArguments(functionDeclaration: string, returnByValue: boolean, utilityScript: js.JSHandle, values: any[], handles: js.JSHandle[]): Promise<any> {
|
||||||
const response = await this._session.send('script.callFunction', {
|
const response = await this._session.send('script.callFunction', {
|
||||||
functionDeclaration,
|
functionDeclaration,
|
||||||
target: this._target,
|
target: this._target,
|
||||||
arguments: [
|
arguments: [
|
||||||
{ handle: utilityScript._objectId! },
|
{ handle: utilityScript._objectId! },
|
||||||
...values.map(BidiSerializer.serialize),
|
...values.map(BidiSerializer.serialize),
|
||||||
...objectIds.map(handle => ({ handle })),
|
...handles.map(handle => ({ handle: handle._objectId! })),
|
||||||
],
|
],
|
||||||
resultOwnership: returnByValue ? undefined : bidi.Script.ResultOwnership.Root, // Necessary for the handle to be returned.
|
resultOwnership: returnByValue ? undefined : bidi.Script.ResultOwnership.Root, // Necessary for the handle to be returned.
|
||||||
serializationOptions: returnByValue ? {} : { maxObjectDepth: 0, maxDomDepth: 0 },
|
serializationOptions: returnByValue ? {} : { maxObjectDepth: 0, maxDomDepth: 0 },
|
||||||
|
@ -121,10 +121,12 @@ export class BidiExecutionContext implements js.ExecutionContextDelegate {
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
async releaseHandle(objectId: js.ObjectId): Promise<void> {
|
async releaseHandle(handle: js.JSHandle): Promise<void> {
|
||||||
|
if (!handle._objectId)
|
||||||
|
return;
|
||||||
await this._session.send('script.disown', {
|
await this._session.send('script.disown', {
|
||||||
target: this._target,
|
target: this._target,
|
||||||
handles: [objectId],
|
handles: [handle._objectId],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -46,24 +46,24 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
|
||||||
return remoteObject.value;
|
return remoteObject.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async rawEvaluateHandle(expression: string): Promise<js.ObjectId> {
|
async rawEvaluateHandle(context: js.ExecutionContext, expression: string): Promise<js.JSHandle> {
|
||||||
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', {
|
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.evaluate', {
|
||||||
expression,
|
expression,
|
||||||
contextId: this._contextId,
|
contextId: this._contextId,
|
||||||
}).catch(rewriteError);
|
}).catch(rewriteError);
|
||||||
if (exceptionDetails)
|
if (exceptionDetails)
|
||||||
throw new js.JavaScriptErrorInEvaluate(getExceptionMessage(exceptionDetails));
|
throw new js.JavaScriptErrorInEvaluate(getExceptionMessage(exceptionDetails));
|
||||||
return remoteObject.objectId!;
|
return createHandle(context, remoteObject);
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], objectIds: string[]): Promise<any> {
|
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle, values: any[], handles: js.JSHandle[]): Promise<any> {
|
||||||
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', {
|
const { exceptionDetails, result: remoteObject } = await this._client.send('Runtime.callFunctionOn', {
|
||||||
functionDeclaration: expression,
|
functionDeclaration: expression,
|
||||||
objectId: utilityScript._objectId,
|
objectId: utilityScript._objectId,
|
||||||
arguments: [
|
arguments: [
|
||||||
{ objectId: utilityScript._objectId },
|
{ objectId: utilityScript._objectId },
|
||||||
...values.map(value => ({ value })),
|
...values.map(value => ({ value })),
|
||||||
...objectIds.map(objectId => ({ objectId })),
|
...handles.map(handle => ({ objectId: handle._objectId! })),
|
||||||
],
|
],
|
||||||
returnByValue,
|
returnByValue,
|
||||||
awaitPromise: true,
|
awaitPromise: true,
|
||||||
|
@ -88,8 +88,10 @@ export class CRExecutionContext implements js.ExecutionContextDelegate {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async releaseHandle(objectId: js.ObjectId): Promise<void> {
|
async releaseHandle(handle: js.JSHandle): Promise<void> {
|
||||||
await releaseObject(this._client, objectId);
|
if (!handle._objectId)
|
||||||
|
return;
|
||||||
|
await releaseObject(this._client, handle._objectId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -105,7 +105,11 @@ export class FrameExecutionContext extends js.ExecutionContext {
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
`;
|
`;
|
||||||
this._injectedScriptPromise = this.rawEvaluateHandle(source).then(objectId => new js.JSHandle(this, 'object', 'InjectedScript', objectId));
|
this._injectedScriptPromise = this.rawEvaluateHandle(source)
|
||||||
|
.then(handle => {
|
||||||
|
handle._setPreview('InjectedScript');
|
||||||
|
return handle;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return this._injectedScriptPromise;
|
return this._injectedScriptPromise;
|
||||||
}
|
}
|
||||||
|
@ -118,7 +122,7 @@ export class ElementHandle<T extends Node = Node> extends js.JSHandle<T> {
|
||||||
declare readonly _objectId: string;
|
declare readonly _objectId: string;
|
||||||
readonly _frame: frames.Frame;
|
readonly _frame: frames.Frame;
|
||||||
|
|
||||||
constructor(context: FrameExecutionContext, objectId: js.ObjectId) {
|
constructor(context: FrameExecutionContext, objectId: string) {
|
||||||
super(context, 'node', undefined, objectId);
|
super(context, 'node', undefined, objectId);
|
||||||
this._page = context.frame._page;
|
this._page = context.frame._page;
|
||||||
this._frame = context.frame;
|
this._frame = context.frame;
|
||||||
|
|
|
@ -44,23 +44,23 @@ export class FFExecutionContext implements js.ExecutionContextDelegate {
|
||||||
return payload.result!.value;
|
return payload.result!.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
async rawEvaluateHandle(expression: string): Promise<js.ObjectId> {
|
async rawEvaluateHandle(context: js.ExecutionContext, expression: string): Promise<js.JSHandle> {
|
||||||
const payload = await this._session.send('Runtime.evaluate', {
|
const payload = await this._session.send('Runtime.evaluate', {
|
||||||
expression,
|
expression,
|
||||||
returnByValue: false,
|
returnByValue: false,
|
||||||
executionContextId: this._executionContextId,
|
executionContextId: this._executionContextId,
|
||||||
}).catch(rewriteError);
|
}).catch(rewriteError);
|
||||||
checkException(payload.exceptionDetails);
|
checkException(payload.exceptionDetails);
|
||||||
return payload.result!.objectId!;
|
return createHandle(context, payload.result!);
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], objectIds: string[]): Promise<any> {
|
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle, values: any[], handles: js.JSHandle[]): Promise<any> {
|
||||||
const payload = await this._session.send('Runtime.callFunction', {
|
const payload = await this._session.send('Runtime.callFunction', {
|
||||||
functionDeclaration: expression,
|
functionDeclaration: expression,
|
||||||
args: [
|
args: [
|
||||||
{ objectId: utilityScript._objectId, value: undefined },
|
{ objectId: utilityScript._objectId, value: undefined },
|
||||||
...values.map(value => ({ value })),
|
...values.map(value => ({ value })),
|
||||||
...objectIds.map(objectId => ({ objectId, value: undefined })),
|
...handles.map(handle => ({ objectId: handle._objectId!, value: undefined })),
|
||||||
],
|
],
|
||||||
returnByValue,
|
returnByValue,
|
||||||
executionContextId: this._executionContextId
|
executionContextId: this._executionContextId
|
||||||
|
@ -82,10 +82,12 @@ export class FFExecutionContext implements js.ExecutionContextDelegate {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async releaseHandle(objectId: js.ObjectId): Promise<void> {
|
async releaseHandle(handle: js.JSHandle): Promise<void> {
|
||||||
|
if (!handle._objectId)
|
||||||
|
return;
|
||||||
await this._session.send('Runtime.disposeObject', {
|
await this._session.send('Runtime.disposeObject', {
|
||||||
executionContextId: this._executionContextId,
|
executionContextId: this._executionContextId,
|
||||||
objectId
|
objectId: handle._objectId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,6 @@ import { LongStandingScope } from '../utils/isomorphic/manualPromise';
|
||||||
import type * as dom from './dom';
|
import type * as dom from './dom';
|
||||||
import type { UtilityScript } from './injected/utilityScript';
|
import type { UtilityScript } from './injected/utilityScript';
|
||||||
|
|
||||||
export type ObjectId = string;
|
|
||||||
|
|
||||||
interface TaggedAsJSHandle<T> {
|
interface TaggedAsJSHandle<T> {
|
||||||
__jshandle: T;
|
__jshandle: T;
|
||||||
}
|
}
|
||||||
|
@ -49,10 +47,10 @@ export type SmartHandle<T> = T extends Node ? dom.ElementHandle<T> : JSHandle<T>
|
||||||
|
|
||||||
export interface ExecutionContextDelegate {
|
export interface ExecutionContextDelegate {
|
||||||
rawEvaluateJSON(expression: string): Promise<any>;
|
rawEvaluateJSON(expression: string): Promise<any>;
|
||||||
rawEvaluateHandle(expression: string): Promise<ObjectId>;
|
rawEvaluateHandle(context: ExecutionContext, expression: string): Promise<JSHandle>;
|
||||||
evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: JSHandle<any>, values: any[], objectIds: ObjectId[]): Promise<any>;
|
evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: JSHandle, values: any[], handles: JSHandle[]): Promise<any>;
|
||||||
getProperties(object: JSHandle): Promise<Map<string, JSHandle>>;
|
getProperties(object: JSHandle): Promise<Map<string, JSHandle>>;
|
||||||
releaseHandle(objectId: ObjectId): Promise<void>;
|
releaseHandle(handle: JSHandle): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExecutionContext extends SdkObject {
|
export class ExecutionContext extends SdkObject {
|
||||||
|
@ -79,21 +77,21 @@ export class ExecutionContext extends SdkObject {
|
||||||
return this._raceAgainstContextDestroyed(this.delegate.rawEvaluateJSON(expression));
|
return this._raceAgainstContextDestroyed(this.delegate.rawEvaluateJSON(expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
rawEvaluateHandle(expression: string): Promise<ObjectId> {
|
rawEvaluateHandle(expression: string): Promise<JSHandle> {
|
||||||
return this._raceAgainstContextDestroyed(this.delegate.rawEvaluateHandle(expression));
|
return this._raceAgainstContextDestroyed(this.delegate.rawEvaluateHandle(this, expression));
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateWithArguments(expression: string, returnByValue: boolean, values: any[], objectIds: ObjectId[]): Promise<any> {
|
async evaluateWithArguments(expression: string, returnByValue: boolean, values: any[], handles: JSHandle[]): Promise<any> {
|
||||||
const utilityScript = await this._utilityScript();
|
const utilityScript = await this._utilityScript();
|
||||||
return this._raceAgainstContextDestroyed(this.delegate.evaluateWithArguments(expression, returnByValue, utilityScript, values, objectIds));
|
return this._raceAgainstContextDestroyed(this.delegate.evaluateWithArguments(expression, returnByValue, utilityScript, values, handles));
|
||||||
}
|
}
|
||||||
|
|
||||||
getProperties(object: JSHandle): Promise<Map<string, JSHandle>> {
|
getProperties(object: JSHandle): Promise<Map<string, JSHandle>> {
|
||||||
return this._raceAgainstContextDestroyed(this.delegate.getProperties(object));
|
return this._raceAgainstContextDestroyed(this.delegate.getProperties(object));
|
||||||
}
|
}
|
||||||
|
|
||||||
releaseHandle(objectId: ObjectId): Promise<void> {
|
releaseHandle(handle: JSHandle): Promise<void> {
|
||||||
return this.delegate.releaseHandle(objectId);
|
return this.delegate.releaseHandle(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
adoptIfNeeded(handle: JSHandle): Promise<JSHandle> | null {
|
adoptIfNeeded(handle: JSHandle): Promise<JSHandle> | null {
|
||||||
|
@ -108,7 +106,11 @@ export class ExecutionContext extends SdkObject {
|
||||||
${utilityScriptSource.source}
|
${utilityScriptSource.source}
|
||||||
return new (module.exports.UtilityScript())(${isUnderTest()});
|
return new (module.exports.UtilityScript())(${isUnderTest()});
|
||||||
})();`;
|
})();`;
|
||||||
this._utilityScriptPromise = this._raceAgainstContextDestroyed(this.delegate.rawEvaluateHandle(source).then(objectId => new JSHandle(this, 'object', 'UtilityScript', objectId)));
|
this._utilityScriptPromise = this._raceAgainstContextDestroyed(this.delegate.rawEvaluateHandle(this, source))
|
||||||
|
.then(handle => {
|
||||||
|
handle._setPreview('UtilityScript');
|
||||||
|
return handle;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
return this._utilityScriptPromise;
|
return this._utilityScriptPromise;
|
||||||
}
|
}
|
||||||
|
@ -122,13 +124,13 @@ export class JSHandle<T = any> extends SdkObject {
|
||||||
__jshandle: T = true as any;
|
__jshandle: T = true as any;
|
||||||
readonly _context: ExecutionContext;
|
readonly _context: ExecutionContext;
|
||||||
_disposed = false;
|
_disposed = false;
|
||||||
readonly _objectId: ObjectId | undefined;
|
readonly _objectId: string | undefined;
|
||||||
readonly _value: any;
|
readonly _value: any;
|
||||||
private _objectType: string;
|
private _objectType: string;
|
||||||
protected _preview: string;
|
protected _preview: string;
|
||||||
private _previewCallback: ((preview: string) => void) | undefined;
|
private _previewCallback: ((preview: string) => void) | undefined;
|
||||||
|
|
||||||
constructor(context: ExecutionContext, type: string, preview: string | undefined, objectId?: ObjectId, value?: any) {
|
constructor(context: ExecutionContext, type: string, preview: string | undefined, objectId?: string, value?: any) {
|
||||||
super(context, 'handle');
|
super(context, 'handle');
|
||||||
this._context = context;
|
this._context = context;
|
||||||
this._objectId = objectId;
|
this._objectId = objectId;
|
||||||
|
@ -185,7 +187,7 @@ export class JSHandle<T = any> extends SdkObject {
|
||||||
if (!this._objectId)
|
if (!this._objectId)
|
||||||
return this._value;
|
return this._value;
|
||||||
const script = `(utilityScript, ...args) => utilityScript.jsonValue(...args)`;
|
const script = `(utilityScript, ...args) => utilityScript.jsonValue(...args)`;
|
||||||
return this._context.evaluateWithArguments(script, true, [true], [this._objectId]);
|
return this._context.evaluateWithArguments(script, true, [true], [this]);
|
||||||
}
|
}
|
||||||
|
|
||||||
asElement(): dom.ElementHandle | null {
|
asElement(): dom.ElementHandle | null {
|
||||||
|
@ -197,7 +199,7 @@ export class JSHandle<T = any> extends SdkObject {
|
||||||
return;
|
return;
|
||||||
this._disposed = true;
|
this._disposed = true;
|
||||||
if (this._objectId) {
|
if (this._objectId) {
|
||||||
this._context.releaseHandle(this._objectId).catch(e => {});
|
this._context.releaseHandle(this).catch(e => {});
|
||||||
if ((globalThis as any).leakedJSHandles)
|
if ((globalThis as any).leakedJSHandles)
|
||||||
(globalThis as any).leakedJSHandles.delete(this);
|
(globalThis as any).leakedJSHandles.delete(this);
|
||||||
}
|
}
|
||||||
|
@ -254,11 +256,11 @@ export async function evaluateExpression(context: ExecutionContext, expression:
|
||||||
return { fallThrough: handle };
|
return { fallThrough: handle };
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const utilityScriptObjectIds: ObjectId[] = [];
|
const utilityScriptObjects: JSHandle[] = [];
|
||||||
for (const handle of await Promise.all(handles)) {
|
for (const handle of await Promise.all(handles)) {
|
||||||
if (handle._context !== context)
|
if (handle._context !== context)
|
||||||
throw new JavaScriptErrorInEvaluate('JSHandles can be evaluated only in the context they were created!');
|
throw new JavaScriptErrorInEvaluate('JSHandles can be evaluated only in the context they were created!');
|
||||||
utilityScriptObjectIds.push(handle._objectId!);
|
utilityScriptObjects.push(handle);
|
||||||
}
|
}
|
||||||
|
|
||||||
// See UtilityScript for arguments.
|
// See UtilityScript for arguments.
|
||||||
|
@ -266,7 +268,7 @@ export async function evaluateExpression(context: ExecutionContext, expression:
|
||||||
|
|
||||||
const script = `(utilityScript, ...args) => utilityScript.evaluate(...args)`;
|
const script = `(utilityScript, ...args) => utilityScript.evaluate(...args)`;
|
||||||
try {
|
try {
|
||||||
return await context.evaluateWithArguments(script, options.returnByValue || false, utilityScriptValues, utilityScriptObjectIds);
|
return await context.evaluateWithArguments(script, options.returnByValue || false, utilityScriptValues, utilityScriptObjects);
|
||||||
} finally {
|
} finally {
|
||||||
toDispose.map(handlePromise => handlePromise.then(handle => handle.dispose()));
|
toDispose.map(handlePromise => handlePromise.then(handle => handle.dispose()));
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,7 +48,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async rawEvaluateHandle(expression: string): Promise<js.ObjectId> {
|
async rawEvaluateHandle(context: js.ExecutionContext, expression: string): Promise<js.JSHandle> {
|
||||||
try {
|
try {
|
||||||
const response = await this._session.send('Runtime.evaluate', {
|
const response = await this._session.send('Runtime.evaluate', {
|
||||||
expression,
|
expression,
|
||||||
|
@ -57,13 +57,13 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
|
||||||
});
|
});
|
||||||
if (response.wasThrown)
|
if (response.wasThrown)
|
||||||
throw new js.JavaScriptErrorInEvaluate(response.result.description);
|
throw new js.JavaScriptErrorInEvaluate(response.result.description);
|
||||||
return response.result.objectId!;
|
return createHandle(context, response.result);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw rewriteError(error);
|
throw rewriteError(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], objectIds: string[]): Promise<any> {
|
async evaluateWithArguments(expression: string, returnByValue: boolean, utilityScript: js.JSHandle<any>, values: any[], handles: js.JSHandle[]): Promise<any> {
|
||||||
try {
|
try {
|
||||||
const response = await this._session.send('Runtime.callFunctionOn', {
|
const response = await this._session.send('Runtime.callFunctionOn', {
|
||||||
functionDeclaration: expression,
|
functionDeclaration: expression,
|
||||||
|
@ -71,7 +71,7 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
|
||||||
arguments: [
|
arguments: [
|
||||||
{ objectId: utilityScript._objectId },
|
{ objectId: utilityScript._objectId },
|
||||||
...values.map(value => ({ value })),
|
...values.map(value => ({ value })),
|
||||||
...objectIds.map(objectId => ({ objectId })),
|
...handles.map(handle => ({ objectId: handle._objectId! })),
|
||||||
],
|
],
|
||||||
returnByValue,
|
returnByValue,
|
||||||
emulateUserGesture: true,
|
emulateUserGesture: true,
|
||||||
|
@ -101,8 +101,10 @@ export class WKExecutionContext implements js.ExecutionContextDelegate {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
async releaseHandle(objectId: js.ObjectId): Promise<void> {
|
async releaseHandle(handle: js.JSHandle): Promise<void> {
|
||||||
await this._session.send('Runtime.releaseObject', { objectId });
|
if (!handle._objectId)
|
||||||
|
return;
|
||||||
|
await this._session.send('Runtime.releaseObject', { objectId: handle._objectId });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue