mirror of
				https://github.com/microsoft/playwright.git
				synced 2025-06-26 21:40:17 +00:00 
			
		
		
		
	chore: move waitFor methods from DOMWorld to Frame (#87)
This almost removes the DOMWorld, so we can unify them across the browsers.
This commit is contained in:
		
							parent
							
								
									2eb653740a
								
							
						
					
					
						commit
						3decf1f996
					
				@ -17,30 +17,23 @@
 | 
			
		||||
 | 
			
		||||
import { ExecutionContext } from './ExecutionContext';
 | 
			
		||||
import { Frame } from './Frame';
 | 
			
		||||
import { ElementHandle, JSHandle } from './JSHandle';
 | 
			
		||||
import { TimeoutSettings } from '../TimeoutSettings';
 | 
			
		||||
import { WaitTask, WaitTaskParams, waitForSelectorOrXPath } from '../waitTask';
 | 
			
		||||
import { JSHandle } from './JSHandle';
 | 
			
		||||
import { WaitTask, WaitTaskParams } from '../waitTask';
 | 
			
		||||
 | 
			
		||||
export class DOMWorld {
 | 
			
		||||
  private _frame: Frame;
 | 
			
		||||
  private _timeoutSettings: TimeoutSettings;
 | 
			
		||||
  private _contextPromise: Promise<ExecutionContext>;
 | 
			
		||||
  private _contextResolveCallback: ((c: ExecutionContext) => void) | null;
 | 
			
		||||
  private _context: ExecutionContext | null;
 | 
			
		||||
  _context: ExecutionContext | null;
 | 
			
		||||
  _waitTasks = new Set<WaitTask<JSHandle>>();
 | 
			
		||||
  private _detached = false;
 | 
			
		||||
 | 
			
		||||
  constructor(frame: Frame, timeoutSettings: TimeoutSettings) {
 | 
			
		||||
  constructor(frame: Frame) {
 | 
			
		||||
    this._frame = frame;
 | 
			
		||||
    this._timeoutSettings = timeoutSettings;
 | 
			
		||||
    this._contextPromise;
 | 
			
		||||
    this._setContext(null);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  frame(): Frame {
 | 
			
		||||
    return this._frame;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _setContext(context: ExecutionContext | null) {
 | 
			
		||||
    this._context = context;
 | 
			
		||||
    if (context) {
 | 
			
		||||
@ -55,10 +48,6 @@ export class DOMWorld {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _hasContext(): boolean {
 | 
			
		||||
    return !this._contextResolveCallback;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _detach() {
 | 
			
		||||
    this._detached = true;
 | 
			
		||||
    for (const waitTask of this._waitTasks)
 | 
			
		||||
@ -71,42 +60,7 @@ export class DOMWorld {
 | 
			
		||||
    return this._contextPromise;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async waitForSelector(selector: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } | undefined): Promise<ElementHandle | null> {
 | 
			
		||||
    const params = waitForSelectorOrXPath(selector, false /* isXPath */, { timeout: this._timeoutSettings.timeout(), ...options });
 | 
			
		||||
    const handle = await this._scheduleWaitTask(params);
 | 
			
		||||
    if (!handle.asElement()) {
 | 
			
		||||
      await handle.dispose();
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
    return handle.asElement();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async waitForXPath(xpath: string, options: { visible?: boolean, hidden?: boolean, timeout?: number } = {}): Promise<ElementHandle | null> {
 | 
			
		||||
    const params = waitForSelectorOrXPath(xpath, true /* isXPath */, { timeout: this._timeoutSettings.timeout(), ...options });
 | 
			
		||||
    const handle = await this._scheduleWaitTask(params);
 | 
			
		||||
    if (!handle.asElement()) {
 | 
			
		||||
      await handle.dispose();
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
    return handle.asElement();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  waitForFunction(pageFunction: Function | string, options: { polling?: string | number; timeout?: number; } = {}, ...args): Promise<JSHandle> {
 | 
			
		||||
    const {
 | 
			
		||||
      polling = 'raf',
 | 
			
		||||
      timeout = this._timeoutSettings.timeout(),
 | 
			
		||||
    } = options;
 | 
			
		||||
    const params: WaitTaskParams = {
 | 
			
		||||
      predicateBody: pageFunction,
 | 
			
		||||
      title: 'function',
 | 
			
		||||
      polling,
 | 
			
		||||
      timeout,
 | 
			
		||||
      args
 | 
			
		||||
    };
 | 
			
		||||
    return this._scheduleWaitTask(params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _scheduleWaitTask(params: WaitTaskParams): Promise<JSHandle> {
 | 
			
		||||
  scheduleWaitTask(params: WaitTaskParams): Promise<JSHandle> {
 | 
			
		||||
    const task = new WaitTask(params, () => this._waitTasks.delete(task));
 | 
			
		||||
    this._waitTasks.add(task);
 | 
			
		||||
    if (this._context)
 | 
			
		||||
 | 
			
		||||
@ -16,7 +16,6 @@
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import { CDPSession } from './Connection';
 | 
			
		||||
import { DOMWorld } from './DOMWorld';
 | 
			
		||||
import { Frame } from './Frame';
 | 
			
		||||
import { assert, helper } from '../helper';
 | 
			
		||||
import { valueFromRemoteObject, getExceptionMessage } from './protocolHelper';
 | 
			
		||||
@ -32,19 +31,19 @@ const SOURCE_URL_REGEX = /^[\040\t]*\/\/[@#] sourceURL=\s*(\S*?)\s*$/m;
 | 
			
		||||
 | 
			
		||||
export class ExecutionContext implements types.EvaluationContext<JSHandle> {
 | 
			
		||||
  _client: CDPSession;
 | 
			
		||||
  _world: DOMWorld;
 | 
			
		||||
  private _frame: Frame;
 | 
			
		||||
  private _injectedPromise: Promise<JSHandle> | null = null;
 | 
			
		||||
  private _documentPromise: Promise<ElementHandle> | null = null;
 | 
			
		||||
  private _contextId: number;
 | 
			
		||||
 | 
			
		||||
  constructor(client: CDPSession, contextPayload: Protocol.Runtime.ExecutionContextDescription, world: DOMWorld | null) {
 | 
			
		||||
  constructor(client: CDPSession, contextPayload: Protocol.Runtime.ExecutionContextDescription, frame: Frame | null) {
 | 
			
		||||
    this._client = client;
 | 
			
		||||
    this._world = world;
 | 
			
		||||
    this._frame = frame;
 | 
			
		||||
    this._contextId = contextPayload.id;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  frame(): Frame | null {
 | 
			
		||||
    return this._world ? this._world.frame() : null;
 | 
			
		||||
    return this._frame;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  evaluate: types.Evaluate<JSHandle> = (pageFunction, ...args) => {
 | 
			
		||||
@ -154,7 +153,7 @@ export class ExecutionContext implements types.EvaluationContext<JSHandle> {
 | 
			
		||||
 | 
			
		||||
  async _adoptElementHandle(elementHandle: ElementHandle): Promise<ElementHandle> {
 | 
			
		||||
    assert(elementHandle.executionContext() !== this, 'Cannot adopt handle that already belongs to this execution context');
 | 
			
		||||
    assert(this._world, 'Cannot adopt handle without DOMWorld');
 | 
			
		||||
    assert(this._frame, 'Cannot adopt handle without a Frame');
 | 
			
		||||
    const nodeInfo = await this._client.send('DOM.describeNode', {
 | 
			
		||||
      objectId: elementHandle._remoteObject.objectId,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -27,6 +27,7 @@ import { ElementHandle, JSHandle } from './JSHandle';
 | 
			
		||||
import { Response } from './NetworkManager';
 | 
			
		||||
import { Protocol } from './protocol';
 | 
			
		||||
import { LifecycleWatcher } from './LifecycleWatcher';
 | 
			
		||||
import { waitForSelectorOrXPath, WaitTaskParams } from '../waitTask';
 | 
			
		||||
 | 
			
		||||
const readFileAsync = helper.promisify(fs.readFile);
 | 
			
		||||
 | 
			
		||||
@ -51,8 +52,8 @@ export class Frame {
 | 
			
		||||
    this._parentFrame = parentFrame;
 | 
			
		||||
    this._id = frameId;
 | 
			
		||||
 | 
			
		||||
    this._mainWorld = new DOMWorld(this, frameManager._timeoutSettings);
 | 
			
		||||
    this._secondaryWorld = new DOMWorld(this, frameManager._timeoutSettings);
 | 
			
		||||
    this._mainWorld = new DOMWorld(this);
 | 
			
		||||
    this._secondaryWorld = new DOMWorld(this);
 | 
			
		||||
 | 
			
		||||
    if (this._parentFrame)
 | 
			
		||||
      this._parentFrame._childFrames.add(this);
 | 
			
		||||
@ -383,11 +384,13 @@ export class Frame {
 | 
			
		||||
      visible?: boolean;
 | 
			
		||||
      hidden?: boolean;
 | 
			
		||||
      timeout?: number; } | undefined): Promise<ElementHandle | null> {
 | 
			
		||||
    const handle = await this._secondaryWorld.waitForSelector(selector, options);
 | 
			
		||||
    if (!handle)
 | 
			
		||||
      return null;
 | 
			
		||||
    const params = waitForSelectorOrXPath(selector, false /* isXPath */, { timeout: this._frameManager._timeoutSettings.timeout(), ...options });
 | 
			
		||||
    const handle = await this._secondaryWorld.scheduleWaitTask(params);
 | 
			
		||||
    let result = null;
 | 
			
		||||
    if (handle.asElement()) {
 | 
			
		||||
      const mainExecutionContext = await this._mainWorld.executionContext();
 | 
			
		||||
    const result = await mainExecutionContext._adoptElementHandle(handle);
 | 
			
		||||
      result = await mainExecutionContext._adoptElementHandle(handle.asElement());
 | 
			
		||||
    }
 | 
			
		||||
    await handle.dispose();
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
@ -396,11 +399,13 @@ export class Frame {
 | 
			
		||||
      visible?: boolean;
 | 
			
		||||
      hidden?: boolean;
 | 
			
		||||
      timeout?: number; } | undefined): Promise<ElementHandle | null> {
 | 
			
		||||
    const handle = await this._secondaryWorld.waitForXPath(xpath, options);
 | 
			
		||||
    if (!handle)
 | 
			
		||||
      return null;
 | 
			
		||||
    const params = waitForSelectorOrXPath(xpath, true /* isXPath */, { timeout: this._frameManager._timeoutSettings.timeout(), ...options });
 | 
			
		||||
    const handle = await this._secondaryWorld.scheduleWaitTask(params);
 | 
			
		||||
    let result = null;
 | 
			
		||||
    if (handle.asElement()) {
 | 
			
		||||
      const mainExecutionContext = await this._mainWorld.executionContext();
 | 
			
		||||
    const result = await mainExecutionContext._adoptElementHandle(handle);
 | 
			
		||||
      result = await mainExecutionContext._adoptElementHandle(handle.asElement());
 | 
			
		||||
    }
 | 
			
		||||
    await handle.dispose();
 | 
			
		||||
    return result;
 | 
			
		||||
  }
 | 
			
		||||
@ -409,7 +414,18 @@ export class Frame {
 | 
			
		||||
    pageFunction: Function | string,
 | 
			
		||||
    options: { polling?: string | number; timeout?: number; } = {},
 | 
			
		||||
    ...args): Promise<JSHandle> {
 | 
			
		||||
    return this._mainWorld.waitForFunction(pageFunction, options, ...args);
 | 
			
		||||
    const {
 | 
			
		||||
      polling = 'raf',
 | 
			
		||||
      timeout = this._frameManager._timeoutSettings.timeout(),
 | 
			
		||||
    } = options;
 | 
			
		||||
    const params: WaitTaskParams = {
 | 
			
		||||
      predicateBody: pageFunction,
 | 
			
		||||
      title: 'function',
 | 
			
		||||
      polling,
 | 
			
		||||
      timeout,
 | 
			
		||||
      args
 | 
			
		||||
    };
 | 
			
		||||
    return this._mainWorld.scheduleWaitTask(params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async title(): Promise<string> {
 | 
			
		||||
 | 
			
		||||
@ -25,6 +25,7 @@ import { LifecycleWatcher } from './LifecycleWatcher';
 | 
			
		||||
import { NetworkManager, Response } from './NetworkManager';
 | 
			
		||||
import { Page } from './Page';
 | 
			
		||||
import { Protocol } from './protocol';
 | 
			
		||||
import { DOMWorld } from './DOMWorld';
 | 
			
		||||
 | 
			
		||||
const UTILITY_WORLD_NAME = '__playwright_utility_world__';
 | 
			
		||||
 | 
			
		||||
@ -258,11 +259,11 @@ export class FrameManager extends EventEmitter {
 | 
			
		||||
  _onExecutionContextCreated(contextPayload) {
 | 
			
		||||
    const frameId = contextPayload.auxData ? contextPayload.auxData.frameId : null;
 | 
			
		||||
    const frame = this._frames.get(frameId) || null;
 | 
			
		||||
    let world = null;
 | 
			
		||||
    let world: DOMWorld | null = null;
 | 
			
		||||
    if (frame) {
 | 
			
		||||
      if (contextPayload.auxData && !!contextPayload.auxData['isDefault']) {
 | 
			
		||||
        world = frame._mainWorld;
 | 
			
		||||
      } else if (contextPayload.name === UTILITY_WORLD_NAME && !frame._secondaryWorld._hasContext()) {
 | 
			
		||||
      } else if (contextPayload.name === UTILITY_WORLD_NAME && !frame._secondaryWorld._context) {
 | 
			
		||||
        // In case of multiple sessions to the same target, there's a race between
 | 
			
		||||
        // connections so we might end up creating multiple isolated worlds.
 | 
			
		||||
        // We can use either.
 | 
			
		||||
@ -271,7 +272,7 @@ export class FrameManager extends EventEmitter {
 | 
			
		||||
    }
 | 
			
		||||
    if (contextPayload.auxData && contextPayload.auxData['type'] === 'isolated')
 | 
			
		||||
      this._isolatedWorlds.add(contextPayload.name);
 | 
			
		||||
    const context: ExecutionContext = new ExecutionContext(this._client, contextPayload, world);
 | 
			
		||||
    const context: ExecutionContext = new ExecutionContext(this._client, contextPayload, frame);
 | 
			
		||||
    if (world)
 | 
			
		||||
      world._setContext(context);
 | 
			
		||||
    this._contextIdToContext.set(contextPayload.id, context);
 | 
			
		||||
@ -282,16 +283,18 @@ export class FrameManager extends EventEmitter {
 | 
			
		||||
    if (!context)
 | 
			
		||||
      return;
 | 
			
		||||
    this._contextIdToContext.delete(executionContextId);
 | 
			
		||||
    if (context._world)
 | 
			
		||||
      context._world._setContext(null);
 | 
			
		||||
    const frame = context.frame();
 | 
			
		||||
    if (frame) {
 | 
			
		||||
      if (frame._mainWorld._context === context)
 | 
			
		||||
        frame._mainWorld._setContext(null);
 | 
			
		||||
      if (frame._secondaryWorld._context === context)
 | 
			
		||||
        frame._secondaryWorld._setContext(null);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  _onExecutionContextsCleared() {
 | 
			
		||||
    for (const context of this._contextIdToContext.values()) {
 | 
			
		||||
      if (context._world)
 | 
			
		||||
        context._world._setContext(null);
 | 
			
		||||
    }
 | 
			
		||||
    this._contextIdToContext.clear();
 | 
			
		||||
    for (const contextId of Array.from(this._contextIdToContext.keys()))
 | 
			
		||||
      this._onExecutionContextDestroyed(contextId);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  executionContextById(contextId: number): ExecutionContext {
 | 
			
		||||
 | 
			
		||||
@ -15,9 +15,9 @@
 | 
			
		||||
 * limitations under the License.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
import {ElementHandle, JSHandle} from './JSHandle';
 | 
			
		||||
import { JSHandle } from './JSHandle';
 | 
			
		||||
import { ExecutionContext } from './ExecutionContext';
 | 
			
		||||
import { WaitTaskParams, WaitTask, waitForSelectorOrXPath } from '../waitTask';
 | 
			
		||||
import { WaitTaskParams, WaitTask } from '../waitTask';
 | 
			
		||||
 | 
			
		||||
export class DOMWorld {
 | 
			
		||||
  _frame: any;
 | 
			
		||||
@ -69,42 +69,7 @@ export class DOMWorld {
 | 
			
		||||
    return this._contextPromise;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async waitForSelector(selector: string, options: { visible?: boolean; hidden?: boolean; timeout?: number; } | undefined): Promise<ElementHandle | null> {
 | 
			
		||||
    const params = waitForSelectorOrXPath(selector, false /* isXPath */, { timeout: this._timeoutSettings.timeout(), ...options });
 | 
			
		||||
    const handle = await this._scheduleWaitTask(params);
 | 
			
		||||
    if (!handle.asElement()) {
 | 
			
		||||
      await handle.dispose();
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
    return handle.asElement();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async waitForXPath(xpath: string, options: { visible?: boolean, hidden?: boolean, timeout?: number } = {}): Promise<ElementHandle | null> {
 | 
			
		||||
    const params = waitForSelectorOrXPath(xpath, true /* isXPath */, { timeout: this._timeoutSettings.timeout(), ...options });
 | 
			
		||||
    const handle = await this._scheduleWaitTask(params);
 | 
			
		||||
    if (!handle.asElement()) {
 | 
			
		||||
      await handle.dispose();
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
    return handle.asElement();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  waitForFunction(pageFunction: Function | string, options: { polling?: string | number; timeout?: number; } | undefined = {}, ...args): Promise<JSHandle> {
 | 
			
		||||
    const {
 | 
			
		||||
      polling = 'raf',
 | 
			
		||||
      timeout = this._timeoutSettings.timeout(),
 | 
			
		||||
    } = options;
 | 
			
		||||
    const params: WaitTaskParams = {
 | 
			
		||||
      predicateBody: pageFunction,
 | 
			
		||||
      title: 'function',
 | 
			
		||||
      polling,
 | 
			
		||||
      timeout,
 | 
			
		||||
      args
 | 
			
		||||
    };
 | 
			
		||||
    return this._scheduleWaitTask(params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private _scheduleWaitTask(params: WaitTaskParams): Promise<JSHandle> {
 | 
			
		||||
  scheduleWaitTask(params: WaitTaskParams): Promise<JSHandle> {
 | 
			
		||||
    const task = new WaitTask(params, () => this._waitTasks.delete(task));
 | 
			
		||||
    this._waitTasks.add(task);
 | 
			
		||||
    if (this._context)
 | 
			
		||||
 | 
			
		||||
@ -29,6 +29,7 @@ import { TimeoutSettings } from '../TimeoutSettings';
 | 
			
		||||
import { NetworkManager } from './NetworkManager';
 | 
			
		||||
import { MultiClickOptions, ClickOptions, SelectOption } from '../input';
 | 
			
		||||
import * as types from '../types';
 | 
			
		||||
import { waitForSelectorOrXPath, WaitTaskParams } from '../waitTask';
 | 
			
		||||
 | 
			
		||||
const readFileAsync = helper.promisify(fs.readFile);
 | 
			
		||||
 | 
			
		||||
@ -375,16 +376,48 @@ export class Frame {
 | 
			
		||||
    return Promise.reject(new Error('Unsupported target type: ' + (typeof selectorOrFunctionOrTimeout)));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  waitForFunction(pageFunction: Function | string, options: { polling?: string | number; timeout?: number; } | undefined = {}, ...args): Promise<JSHandle> {
 | 
			
		||||
    return this._mainWorld.waitForFunction(pageFunction, options, ...args);
 | 
			
		||||
  waitForFunction(
 | 
			
		||||
    pageFunction: Function | string,
 | 
			
		||||
    options: { polling?: string | number; timeout?: number; } = {},
 | 
			
		||||
    ...args): Promise<JSHandle> {
 | 
			
		||||
    const {
 | 
			
		||||
      polling = 'raf',
 | 
			
		||||
      timeout = this._frameManager._timeoutSettings.timeout(),
 | 
			
		||||
    } = options;
 | 
			
		||||
    const params: WaitTaskParams = {
 | 
			
		||||
      predicateBody: pageFunction,
 | 
			
		||||
      title: 'function',
 | 
			
		||||
      polling,
 | 
			
		||||
      timeout,
 | 
			
		||||
      args
 | 
			
		||||
    };
 | 
			
		||||
    return this._mainWorld.scheduleWaitTask(params);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  waitForSelector(selector: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined): Promise<ElementHandle> {
 | 
			
		||||
    return this._mainWorld.waitForSelector(selector, options);
 | 
			
		||||
  async waitForSelector(selector: string, options: {
 | 
			
		||||
      visible?: boolean;
 | 
			
		||||
      hidden?: boolean;
 | 
			
		||||
      timeout?: number; } | undefined): Promise<ElementHandle | null> {
 | 
			
		||||
    const params = waitForSelectorOrXPath(selector, false /* isXPath */, { timeout: this._frameManager._timeoutSettings.timeout(), ...options });
 | 
			
		||||
    const handle = await this._mainWorld.scheduleWaitTask(params);
 | 
			
		||||
    if (!handle.asElement()) {
 | 
			
		||||
      await handle.dispose();
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
    return handle.asElement();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  waitForXPath(xpath: string, options: { timeout?: number; visible?: boolean; hidden?: boolean; } | undefined): Promise<ElementHandle> {
 | 
			
		||||
    return this._mainWorld.waitForXPath(xpath, options);
 | 
			
		||||
  async waitForXPath(xpath: string, options: {
 | 
			
		||||
      visible?: boolean;
 | 
			
		||||
      hidden?: boolean;
 | 
			
		||||
      timeout?: number; } | undefined): Promise<ElementHandle | null> {
 | 
			
		||||
    const params = waitForSelectorOrXPath(xpath, true /* isXPath */, { timeout: this._frameManager._timeoutSettings.timeout(), ...options });
 | 
			
		||||
    const handle = await this._mainWorld.scheduleWaitTask(params);
 | 
			
		||||
    if (!handle.asElement()) {
 | 
			
		||||
      await handle.dispose();
 | 
			
		||||
      return null;
 | 
			
		||||
    }
 | 
			
		||||
    return handle.asElement();
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  async content(): Promise<string> {
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user