Repository: microsoft/playwright
File: packages/playwright-core/src/server/bidi/bidiPage.ts (TypeScript)

Code before:
    const existed = this._realmToWorkerContext.delete(params.realm);
    if (existed) {
      this._page.removeWorker(params.realm);
      return true;
    }
    return false;
  }

  // TODO: route the message directly to the browser
  private _onBrowsingContextDestroyed(params: bidi.BrowsingContext.Info) {
    this._browserContext._browser._onBrowsingContextDestroyed(params);
  }

  private _onNavigationStarted(params: bidi.BrowsingContext.NavigationInfo) {
    const frameId = params.context;
    this._page.frameManager.frameRequestedNavigation(frameId, params.navigation!);
  }

  private async _onNavigationCommitted(params: bidi.BrowsingContext.NavigationInfo) {
    const frameId = params.context;
    const namePromise = this._frameNamePromises.get(frameId) ?? '';
    await this._page.frameManager.frameCommittedNewDocumentNavigation(frameId, params.url, namePromise, params.navigation!, /* initial */ false);
    this._frameNamePromises.delete(frameId);
  }

  private async _onDomContentLoaded(params: bidi.BrowsingContext.NavigationInfo) {
    const frameId = params.context;
    const frame = this._page.frameManager.frame(frameId)!;
    await this._waitForChildFrameNames(frame, false);
    this._page.frameManager.frameLifecycleEvent(frameId, 'domcontentloaded');
  }

  private async _onLoad(params: bidi.BrowsingContext.NavigationInfo) {
    const frameId = params.context;
    const frame = this._page.frameManager.frame(frameId)!;
    await this._waitForChildFrameNames(frame, true);
    this._page.frameManager.frameLifecycleEvent(frameId, 'load');
  }

  private async _waitForChildFrameNames(frame: frames.Frame, recursive: boolean) {
    for (const childFrame of frame.childFrames()) {
      await this._frameNamePromises.get(childFrame._id);
      if (recursive)
        await this._waitForChildFrameNames(childFrame, recursive);
    }
  }

  private _onNavigationAborted(params: bidi.BrowsingContext.NavigationInfo) {
    this._page.frameManager.frameAbortedNavigation(params.context, 'Navigation aborted', params.navigation || undefined);
  }


Review comment (suggestion):
Event listeners should be synchronous, we don't want to mess up the event order due to random awaits.

The frame name usually doesn't change, so we could query it on frame attach and if it arrives before the first navigation it will automatically propagate to the clients. This would be racy in some cases, but doesn't require async event handlers. We now have locators (and frame locators) that allow to select iframe elements and don't depend on this method, this could be a fallback for the folks that need to find a frame.

I don't think we should make all these handlers async just for the frame.name(). I'd rather keep the property best effort. If we want a proper fix, we'd need to make the name available synchronously.


Code after:
    const existed = this._realmToWorkerContext.delete(params.realm);
    if (existed) {
      this._page.removeWorker(params.realm);
      return true;
    }
    return false;
  }

  // TODO: route the message directly to the browser
  private _onBrowsingContextDestroyed(params: bidi.BrowsingContext.Info) {
    this._browserContext._browser._onBrowsingContextDestroyed(params);
  }

  private _onNavigationStarted(params: bidi.BrowsingContext.NavigationInfo) {
    const frameId = params.context;
    this._page.frameManager.frameRequestedNavigation(frameId, params.navigation!);
  }

  private _onNavigationCommitted(params: bidi.BrowsingContext.NavigationInfo) {
    const frameId = params.context;
    const frame = this._page.frameManager.frame(frameId)!;
    this._page.frameManager.frameCommittedNewDocumentNavigation(frameId, params.url, frame._name, params.navigation!, /* initial */ false);
  }

  private _onDomContentLoaded(params: bidi.BrowsingContext.NavigationInfo) {
    const frameId = params.context;
    this._page.frameManager.frameLifecycleEvent(frameId, 'domcontentloaded');
  }

  private _onLoad(params: bidi.BrowsingContext.NavigationInfo) {
    this._page.frameManager.frameLifecycleEvent(params.context, 'load');
  }

  private _onNavigationAborted(params: bidi.BrowsingContext.NavigationInfo) {
    this._page.frameManager.frameAbortedNavigation(params.context, 'Navigation aborted', params.navigation || undefined);
  }

  private _onNavigationFailed(params: bidi.BrowsingContext.NavigationInfo) {
    this._page.frameManager.frameAbortedNavigation(params.context, 'Navigation failed', params.navigation || undefined);
  }

  private _onFragmentNavigated(params: bidi.BrowsingContext.NavigationInfo) {
    this._page.frameManager.frameCommittedSameDocumentNavigation(params.context, params.url);
  }

  private _onHistoryUpdated(params: bidi.BrowsingContext.HistoryUpdatedParameters) {
    this._page.frameManager.frameCommittedSameDocumentNavigation(params.context, params.url);
  }

  private _onUserPromptOpened(event: bidi.BrowsingContext.UserPromptOpenedParameters) {
    this._page.browserContext.dialogManager.dialogDidOpen(new dialog.Dialog(