mirror of
https://github.com/puppeteer/puppeteer
synced 2024-06-14 14:02:48 +00:00
fix!: remove root
from WaitForSelectorOptions
(#8848)
This commit is contained in:
parent
498fbf924c
commit
1155c8eac8
@ -14,17 +14,17 @@ Unlike [Frame.waitForSelector()](./puppeteer.frame.waitforselector.md), this met
|
||||
class ElementHandle {
|
||||
waitForSelector<Selector extends string>(
|
||||
selector: Selector,
|
||||
options?: Exclude<WaitForSelectorOptions, 'root'>
|
||||
options?: WaitForSelectorOptions
|
||||
): Promise<ElementHandle<NodeFor<Selector>> | null>;
|
||||
}
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | -------------------------------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| selector | Selector | The selector to query and wait for. |
|
||||
| options | Exclude<[WaitForSelectorOptions](./puppeteer.waitforselectoroptions.md), 'root'> | <i>(Optional)</i> Options for customizing waiting behavior. |
|
||||
| Parameter | Type | Description |
|
||||
| --------- | --------------------------------------------------------------- | ----------------------------------------------------------- |
|
||||
| selector | Selector | The selector to query and wait for. |
|
||||
| options | [WaitForSelectorOptions](./puppeteer.waitforselectoroptions.md) | <i>(Optional)</i> Options for customizing waiting behavior. |
|
||||
|
||||
**Returns:**
|
||||
|
||||
|
@ -34,17 +34,17 @@ const puppeteer = require('puppeteer');
|
||||
class Page {
|
||||
waitForSelector<Selector extends string>(
|
||||
selector: Selector,
|
||||
options?: Exclude<WaitForSelectorOptions, 'root'>
|
||||
options?: WaitForSelectorOptions
|
||||
): Promise<ElementHandle<NodeFor<Selector>> | null>;
|
||||
}
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --------- | -------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
||||
| selector | Selector | A [selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) of an element to wait for |
|
||||
| options | Exclude<[WaitForSelectorOptions](./puppeteer.waitforselectoroptions.md), 'root'> | <i>(Optional)</i> Optional waiting parameters |
|
||||
| Parameter | Type | Description |
|
||||
| --------- | --------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
|
||||
| selector | Selector | A [selector](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Selectors) of an element to wait for |
|
||||
| options | [WaitForSelectorOptions](./puppeteer.waitforselectoroptions.md) | <i>(Optional)</i> Optional waiting parameters |
|
||||
|
||||
**Returns:**
|
||||
|
||||
|
@ -12,9 +12,8 @@ export interface WaitForSelectorOptions
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Modifiers | Type | Description |
|
||||
| --------------------------------------------------------- | --------- | --------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [hidden?](./puppeteer.waitforselectoroptions.hidden.md) | | boolean | <i>(Optional)</i> Wait for the selected element to not be found in the DOM or to be hidden, i.e. have <code>display: none</code> or <code>visibility: hidden</code> CSS properties. |
|
||||
| [root?](./puppeteer.waitforselectoroptions.root.md) | | [ElementHandle](./puppeteer.elementhandle.md)<Node> | <i>(Optional)</i> |
|
||||
| [timeout?](./puppeteer.waitforselectoroptions.timeout.md) | | number | <p><i>(Optional)</i> Maximum time to wait in milliseconds. Pass <code>0</code> to disable timeout.</p><p>The default value can be changed by using [Page.setDefaultTimeout()](./puppeteer.page.setdefaulttimeout.md)</p> |
|
||||
| [visible?](./puppeteer.waitforselectoroptions.visible.md) | | boolean | <i>(Optional)</i> Wait for the selected element to be present in DOM and to be visible, i.e. to not have <code>display: none</code> or <code>visibility: hidden</code> CSS properties. |
|
||||
| Property | Modifiers | Type | Description |
|
||||
| --------------------------------------------------------- | --------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
||||
| [hidden?](./puppeteer.waitforselectoroptions.hidden.md) | | boolean | <i>(Optional)</i> Wait for the selected element to not be found in the DOM or to be hidden, i.e. have <code>display: none</code> or <code>visibility: hidden</code> CSS properties. |
|
||||
| [timeout?](./puppeteer.waitforselectoroptions.timeout.md) | | number | <p><i>(Optional)</i> Maximum time to wait in milliseconds. Pass <code>0</code> to disable timeout.</p><p>The default value can be changed by using [Page.setDefaultTimeout()](./puppeteer.page.setdefaulttimeout.md)</p> |
|
||||
| [visible?](./puppeteer.waitforselectoroptions.visible.md) | | boolean | <i>(Optional)</i> Wait for the selected element to be present in DOM and to be visible, i.e. to not have <code>display: none</code> or <code>visibility: hidden</code> CSS properties. |
|
||||
|
@ -1,17 +0,0 @@
|
||||
---
|
||||
sidebar_label: WaitForSelectorOptions.root
|
||||
---
|
||||
|
||||
# WaitForSelectorOptions.root property
|
||||
|
||||
> Warning: This API is now obsolete.
|
||||
>
|
||||
> Do not use. Use the [ElementHandle.waitForSelector()](./puppeteer.elementhandle.waitforselector.md)
|
||||
|
||||
**Signature:**
|
||||
|
||||
```typescript
|
||||
interface WaitForSelectorOptions {
|
||||
root?: ElementHandle<Node>;
|
||||
}
|
||||
```
|
@ -18,11 +18,8 @@ import {Protocol} from 'devtools-protocol';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {ElementHandle} from './ElementHandle.js';
|
||||
import {
|
||||
IsolatedWorld,
|
||||
PageBinding,
|
||||
WaitForSelectorOptions,
|
||||
} from './IsolatedWorld.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {MAIN_WORLD, PageBinding, PUPPETEER_WORLD} from './IsolatedWorld.js';
|
||||
import {InternalQueryHandler} from './QueryHandler.js';
|
||||
|
||||
async function queryAXTree(
|
||||
@ -89,52 +86,86 @@ function parseAriaSelector(selector: string): ARIAQueryOption {
|
||||
return queryOptions;
|
||||
}
|
||||
|
||||
const queryOne = async (
|
||||
element: ElementHandle<Node>,
|
||||
selector: string
|
||||
): Promise<ElementHandle<Node> | null> => {
|
||||
const exeCtx = element.executionContext();
|
||||
const queryOneId = async (element: ElementHandle<Node>, selector: string) => {
|
||||
const {name, role} = parseAriaSelector(selector);
|
||||
const res = await queryAXTree(exeCtx._client, element, name, role);
|
||||
const res = await queryAXTree(element.client, element, name, role);
|
||||
if (!res[0] || !res[0].backendDOMNodeId) {
|
||||
return null;
|
||||
}
|
||||
return (await exeCtx._world!.adoptBackendNode(
|
||||
res[0].backendDOMNodeId
|
||||
return res[0].backendDOMNodeId;
|
||||
};
|
||||
|
||||
const queryOne: InternalQueryHandler['queryOne'] = async (
|
||||
element,
|
||||
selector
|
||||
) => {
|
||||
const id = await queryOneId(element, selector);
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
return (await element.frame.worlds[MAIN_WORLD].adoptBackendNode(
|
||||
id
|
||||
)) as ElementHandle<Node>;
|
||||
};
|
||||
|
||||
const waitFor = async (
|
||||
isolatedWorld: IsolatedWorld,
|
||||
selector: string,
|
||||
options: WaitForSelectorOptions
|
||||
): Promise<ElementHandle<Element> | null> => {
|
||||
const waitFor: InternalQueryHandler['waitFor'] = async (
|
||||
elementOrFrame,
|
||||
selector,
|
||||
options
|
||||
) => {
|
||||
let frame: Frame;
|
||||
let element: ElementHandle<Node> | undefined;
|
||||
if (elementOrFrame instanceof Frame) {
|
||||
frame = elementOrFrame;
|
||||
} else {
|
||||
frame = elementOrFrame.frame;
|
||||
element = await frame.worlds[PUPPETEER_WORLD].adoptHandle(elementOrFrame);
|
||||
}
|
||||
const binding: PageBinding = {
|
||||
name: 'ariaQuerySelector',
|
||||
pptrFunction: async (selector: string) => {
|
||||
const root = options.root || (await isolatedWorld.document());
|
||||
const element = await queryOne(root, selector);
|
||||
return element;
|
||||
const id = await queryOneId(
|
||||
element || (await frame.worlds[PUPPETEER_WORLD].document()),
|
||||
selector
|
||||
);
|
||||
if (!id) {
|
||||
return null;
|
||||
}
|
||||
return (await frame.worlds[PUPPETEER_WORLD].adoptBackendNode(
|
||||
id
|
||||
)) as ElementHandle<Node>;
|
||||
},
|
||||
};
|
||||
return (await isolatedWorld._waitForSelectorInPage(
|
||||
const result = await frame.worlds[PUPPETEER_WORLD]._waitForSelectorInPage(
|
||||
(_: Element, selector: string) => {
|
||||
return (
|
||||
globalThis as unknown as {
|
||||
ariaQuerySelector(selector: string): void;
|
||||
ariaQuerySelector(selector: string): Node | null;
|
||||
}
|
||||
).ariaQuerySelector(selector);
|
||||
},
|
||||
element,
|
||||
selector,
|
||||
options,
|
||||
binding
|
||||
)) as ElementHandle<Element> | null;
|
||||
);
|
||||
if (element) {
|
||||
await element.dispose();
|
||||
}
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
if (!(result instanceof ElementHandle)) {
|
||||
await result.dispose();
|
||||
return null;
|
||||
}
|
||||
return result.frame.worlds[MAIN_WORLD].transferHandle(result);
|
||||
};
|
||||
|
||||
const queryAll = async (
|
||||
element: ElementHandle<Node>,
|
||||
selector: string
|
||||
): Promise<Array<ElementHandle<Node>>> => {
|
||||
const queryAll: InternalQueryHandler['queryAll'] = async (
|
||||
element,
|
||||
selector
|
||||
) => {
|
||||
const exeCtx = element.executionContext();
|
||||
const {name, role} = parseAriaSelector(selector);
|
||||
const res = await queryAXTree(exeCtx._client, element, name, role);
|
||||
|
@ -3,11 +3,7 @@ import {assert} from '../util/assert.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {FrameManager} from './FrameManager.js';
|
||||
import {
|
||||
MAIN_WORLD,
|
||||
PUPPETEER_WORLD,
|
||||
WaitForSelectorOptions,
|
||||
} from './IsolatedWorld.js';
|
||||
import {WaitForSelectorOptions} from './IsolatedWorld.js';
|
||||
import {
|
||||
BoundingBox,
|
||||
BoxModel,
|
||||
@ -310,26 +306,16 @@ export class ElementHandle<
|
||||
*/
|
||||
async waitForSelector<Selector extends string>(
|
||||
selector: Selector,
|
||||
options: Exclude<WaitForSelectorOptions, 'root'> = {}
|
||||
options: WaitForSelectorOptions = {}
|
||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||
const frame = this.#frame;
|
||||
const adoptedRoot = await frame.worlds[PUPPETEER_WORLD].adoptHandle(this);
|
||||
const handle = await frame.worlds[PUPPETEER_WORLD].waitForSelector(
|
||||
selector,
|
||||
{
|
||||
...options,
|
||||
root: adoptedRoot,
|
||||
}
|
||||
);
|
||||
await adoptedRoot.dispose();
|
||||
if (!handle) {
|
||||
return null;
|
||||
}
|
||||
const result = (await frame.worlds[MAIN_WORLD].adoptHandle(
|
||||
handle
|
||||
)) as ElementHandle<NodeFor<Selector>>;
|
||||
await handle.dispose();
|
||||
return result;
|
||||
const {updatedSelector, queryHandler} =
|
||||
getQueryHandlerAndSelector(selector);
|
||||
assert(queryHandler.waitFor, 'Query handler does not support waiting');
|
||||
return (await queryHandler.waitFor(
|
||||
this,
|
||||
updatedSelector,
|
||||
options
|
||||
)) as ElementHandle<NodeFor<Selector>> | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {isErrorLike} from '../util/ErrorLike.js';
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {ElementHandle} from './ElementHandle.js';
|
||||
@ -15,6 +16,7 @@ import {
|
||||
} from './IsolatedWorld.js';
|
||||
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
||||
import {Page} from './Page.js';
|
||||
import {getQueryHandlerAndSelector} from './QueryHandler.js';
|
||||
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
|
||||
|
||||
/**
|
||||
@ -579,18 +581,14 @@ export class Frame {
|
||||
selector: Selector,
|
||||
options: WaitForSelectorOptions = {}
|
||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||
const handle = await this.worlds[PUPPETEER_WORLD].waitForSelector(
|
||||
selector,
|
||||
const {updatedSelector, queryHandler} =
|
||||
getQueryHandlerAndSelector(selector);
|
||||
assert(queryHandler.waitFor, 'Query handler does not support waiting');
|
||||
return (await queryHandler.waitFor(
|
||||
this,
|
||||
updatedSelector,
|
||||
options
|
||||
);
|
||||
if (!handle) {
|
||||
return null;
|
||||
}
|
||||
const mainHandle = (await this.worlds[MAIN_WORLD].adoptHandle(
|
||||
handle
|
||||
)) as ElementHandle<NodeFor<Selector>>;
|
||||
await handle.dispose();
|
||||
return mainHandle;
|
||||
)) as ElementHandle<NodeFor<Selector>> | null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -16,16 +16,19 @@
|
||||
|
||||
import {Protocol} from 'devtools-protocol';
|
||||
import {assert} from '../util/assert.js';
|
||||
import {
|
||||
createDeferredPromise,
|
||||
DeferredPromise,
|
||||
} from '../util/DeferredPromise.js';
|
||||
import {CDPSession} from './Connection.js';
|
||||
import {ElementHandle} from './ElementHandle.js';
|
||||
import {TimeoutError} from './Errors.js';
|
||||
import {ExecutionContext} from './ExecutionContext.js';
|
||||
import {FrameManager} from './FrameManager.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {FrameManager} from './FrameManager.js';
|
||||
import {MouseButton} from './Input.js';
|
||||
import {JSHandle} from './JSHandle.js';
|
||||
import {LifecycleWatcher, PuppeteerLifeCycleEvent} from './LifecycleWatcher.js';
|
||||
import {getQueryHandlerAndSelector} from './QueryHandler.js';
|
||||
import {TimeoutSettings} from './TimeoutSettings.js';
|
||||
import {EvaluateFunc, HandleFor, NodeFor} from './types.js';
|
||||
import {
|
||||
@ -37,10 +40,6 @@ import {
|
||||
makePredicateString,
|
||||
pageBindingInitString,
|
||||
} from './util.js';
|
||||
import {
|
||||
createDeferredPromise,
|
||||
DeferredPromise,
|
||||
} from '../util/DeferredPromise.js';
|
||||
|
||||
// predicateQueryHandler and checkWaitForOptions are declared here so that
|
||||
// TypeScript knows about them when used in the predicate function below.
|
||||
@ -80,10 +79,6 @@ export interface WaitForSelectorOptions {
|
||||
* @defaultValue `30000` (30 seconds)
|
||||
*/
|
||||
timeout?: number;
|
||||
/**
|
||||
* @deprecated Do not use. Use the {@link ElementHandle.waitForSelector}
|
||||
*/
|
||||
root?: ElementHandle<Node>;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -122,7 +117,7 @@ export interface IsolatedWorldChart {
|
||||
*/
|
||||
export class IsolatedWorld {
|
||||
#frame: Frame;
|
||||
#documentPromise: Promise<ElementHandle<Document>> | null = null;
|
||||
#document?: ElementHandle<Document>;
|
||||
#contextPromise: DeferredPromise<ExecutionContext> = createDeferredPromise();
|
||||
#detached = false;
|
||||
|
||||
@ -169,7 +164,7 @@ export class IsolatedWorld {
|
||||
}
|
||||
|
||||
clearContext(): void {
|
||||
this.#documentPromise = null;
|
||||
this.#document = undefined;
|
||||
this.#contextPromise = createDeferredPromise();
|
||||
}
|
||||
|
||||
@ -248,15 +243,14 @@ export class IsolatedWorld {
|
||||
}
|
||||
|
||||
async document(): Promise<ElementHandle<Document>> {
|
||||
if (this.#documentPromise) {
|
||||
return this.#documentPromise;
|
||||
if (this.#document) {
|
||||
return this.#document;
|
||||
}
|
||||
this.#documentPromise = this.executionContext().then(async context => {
|
||||
return await context.evaluateHandle(() => {
|
||||
return document;
|
||||
});
|
||||
const context = await this.executionContext();
|
||||
this.#document = await context.evaluateHandle(() => {
|
||||
return document;
|
||||
});
|
||||
return this.#documentPromise;
|
||||
return this.#document;
|
||||
}
|
||||
|
||||
async $x(expression: string): Promise<Array<ElementHandle<Node>>> {
|
||||
@ -294,20 +288,6 @@ export class IsolatedWorld {
|
||||
return document.$$eval(selector, pageFunction, ...args);
|
||||
}
|
||||
|
||||
async waitForSelector<Selector extends string>(
|
||||
selector: Selector,
|
||||
options: WaitForSelectorOptions
|
||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||
const {updatedSelector, queryHandler} =
|
||||
getQueryHandlerAndSelector(selector);
|
||||
assert(queryHandler.waitFor, 'Query handler does not support waiting');
|
||||
return (await queryHandler.waitFor(
|
||||
this,
|
||||
updatedSelector,
|
||||
options
|
||||
)) as ElementHandle<NodeFor<Selector>> | null;
|
||||
}
|
||||
|
||||
async content(): Promise<string> {
|
||||
return await this.evaluate(() => {
|
||||
let retVal = '';
|
||||
@ -707,10 +687,11 @@ export class IsolatedWorld {
|
||||
|
||||
async _waitForSelectorInPage(
|
||||
queryOne: Function,
|
||||
root: ElementHandle<Node> | undefined,
|
||||
selector: string,
|
||||
options: WaitForSelectorOptions,
|
||||
binding?: PageBinding
|
||||
): Promise<ElementHandle<Node> | null> {
|
||||
): Promise<JSHandle<unknown> | null> {
|
||||
const {
|
||||
visible: waitForVisible = false,
|
||||
hidden: waitForHidden = false,
|
||||
@ -738,16 +719,10 @@ export class IsolatedWorld {
|
||||
timeout,
|
||||
args: [selector, waitForVisible, waitForHidden],
|
||||
binding,
|
||||
root: options.root,
|
||||
root,
|
||||
};
|
||||
const waitTask = new WaitTask(waitTaskOptions);
|
||||
const jsHandle = await waitTask.promise;
|
||||
const elementHandle = jsHandle.asElement();
|
||||
if (!elementHandle) {
|
||||
await jsHandle.dispose();
|
||||
return null;
|
||||
}
|
||||
return elementHandle;
|
||||
return waitTask.promise;
|
||||
}
|
||||
|
||||
waitForFunction(
|
||||
@ -798,6 +773,12 @@ export class IsolatedWorld {
|
||||
});
|
||||
return (await this.adoptBackendNode(nodeInfo.node.backendNodeId)) as T;
|
||||
}
|
||||
|
||||
async transferHandle<T extends JSHandle<Node>>(handle: T): Promise<T> {
|
||||
const result = await this.adoptHandle(handle);
|
||||
await handle.dispose();
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3396,7 +3396,7 @@ export class Page extends EventEmitter {
|
||||
*/
|
||||
async waitForSelector<Selector extends string>(
|
||||
selector: Selector,
|
||||
options: Exclude<WaitForSelectorOptions, 'root'> = {}
|
||||
options: WaitForSelectorOptions = {}
|
||||
): Promise<ElementHandle<NodeFor<Selector>> | null> {
|
||||
return await this.mainFrame().waitForSelector(selector, options);
|
||||
}
|
||||
|
@ -16,7 +16,12 @@
|
||||
|
||||
import {ariaHandler} from './AriaQueryHandler.js';
|
||||
import {ElementHandle} from './ElementHandle.js';
|
||||
import {IsolatedWorld, WaitForSelectorOptions} from './IsolatedWorld.js';
|
||||
import {Frame} from './Frame.js';
|
||||
import {
|
||||
MAIN_WORLD,
|
||||
PUPPETEER_WORLD,
|
||||
WaitForSelectorOptions,
|
||||
} from './IsolatedWorld.js';
|
||||
|
||||
/**
|
||||
* @public
|
||||
@ -58,11 +63,9 @@ export interface InternalQueryHandler {
|
||||
/**
|
||||
* Waits until a single node appears for a given selector and
|
||||
* {@link ElementHandle}.
|
||||
*
|
||||
* Akin to {@link Window.prototype.querySelectorAll}.
|
||||
*/
|
||||
waitFor?: (
|
||||
isolatedWorld: IsolatedWorld,
|
||||
elementOrFrame: ElementHandle<Node> | Frame,
|
||||
selector: string,
|
||||
options: WaitForSelectorOptions
|
||||
) => Promise<ElementHandle<Node> | null>;
|
||||
@ -84,12 +87,34 @@ function internalizeCustomQueryHandler(
|
||||
await jsHandle.dispose();
|
||||
return null;
|
||||
};
|
||||
internalHandler.waitFor = (
|
||||
domWorld: IsolatedWorld,
|
||||
selector: string,
|
||||
options: WaitForSelectorOptions
|
||||
) => {
|
||||
return domWorld._waitForSelectorInPage(queryOne, selector, options);
|
||||
internalHandler.waitFor = async (elementOrFrame, selector, options) => {
|
||||
let frame: Frame;
|
||||
let element: ElementHandle<Node> | undefined;
|
||||
if (elementOrFrame instanceof Frame) {
|
||||
frame = elementOrFrame;
|
||||
} else {
|
||||
frame = elementOrFrame.frame;
|
||||
element = await frame.worlds[PUPPETEER_WORLD].adoptHandle(
|
||||
elementOrFrame
|
||||
);
|
||||
}
|
||||
const result = await frame.worlds[PUPPETEER_WORLD]._waitForSelectorInPage(
|
||||
queryOne,
|
||||
element,
|
||||
selector,
|
||||
options
|
||||
);
|
||||
if (element) {
|
||||
await element.dispose();
|
||||
}
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
if (!(result instanceof ElementHandle)) {
|
||||
await result.dispose();
|
||||
return null;
|
||||
}
|
||||
return frame.worlds[MAIN_WORLD].transferHandle(result);
|
||||
};
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user