refreshless-8.x-1.x-dev/modules/refreshless_turbo/js/browser_fixes/redirect_url_race.js

modules/refreshless_turbo/js/browser_fixes/redirect_url_race.js
/**
 * @file
 * Redirect URL race condition fix/workaround.
 *
 * This is notably a problem in Firefox where everything on Turbo's side appears
 * to be called in the correct sequence, but a history.pushState() very quickly
 * followed by a history.replaceState() can result in the URL being incorrectly
 * left on the pre-redirect URL in this sequence:
 *
 * 1. Redirecting URL
 * 2. Redirected to URL (briefly visible)
 * 3. Redirected URL
 *
 * The third item is unexpected, and not seen in Chrome.
 *
 * This is reproducible on the /admin/compact link on /admin/config, and seems
 * to occur regardless of network latency, i.e. can be seen both in a local DDEV
 * site and one running on a remote server. It's unclear if this only happens
 * with GET request redirects, as POST redirects in Drupal aren't as obvious or
 * common.
 *
 * @see https://bugzilla.mozilla.org/buglist.cgi?quicksearch=replaceState
 *   Does not currently yield a specific report of this issue but worth checking
 *   in the future.
 */
(function(html, $, once, Turbo) {

  'use strict';

  /**
   * Our event namespace.
   *
   * @type {String}
   *
   * @see https://learn.jquery.com/events/event-basics/#namespacing-events
   */
  const eventNamespace = 'refreshless-turbo-firefox-redirect-race-fix';

  /**
   * Our once() identifier.
   *
   * @type {String}
   */
  const onceName = eventNamespace;

  /**
   * Property name on the HTML element to save the class to.
   *
   * @type {String}
   */
  const propName = eventNamespace;

  /**
   * Duration to watch for mismatched Turbo and window location after a load.
   *
   * Note that this starts at a 'refreshless:load' after a
   * 'refreshless:redirect', and not at the 'refreshless:redirect' event which
   * triggers much earlier.
   *
   * @type {Number}
   */
  const watchDuration = 100;

  /**
   * Watches for and attempts to fix incorrect URLs after a redirect.
   */
  class RedirectUrlRaceFixer {

    /**
     * Duration to watch for mismatched Turbo and window location after a load.
     *
     * @type {Number}
     */
    #duration;

    /**
     * The window.location object to check against.
     *
     * @type {Location}
     */
    #location;

    /**
     * A timeout ID or null if one isn't active.
     *
     * @type {Number|Null}
     */
    #timeoutId = null;

    /**
     * Hotwire Turbo instance.
     *
     * @type {Turbo}
     */
    #Turbo;

    constructor(duration, location, Turbo) {

      this.#duration = duration;

      this.#location = location;

      this.#Turbo = Turbo;

    }

    /**
     * Destroy this instance.
     */
    destroy() {

      this.stop();

    }

    /**
     * Start watching.
     */
    async start() {

      if (this.isWatching() === true) {
        this.stop();
      }

      this.#timeoutId = setTimeout(() => {
        // Doesn't actually need to do anything other than expire.
      }, this.#duration);

      const turboHistory = this.#Turbo.navigator.history;

      while (this.isWatching() === true) {

        // Wait for at least one frame to be rendered between checks so that we
        // don't hog the main thread.
        await new Promise(requestAnimationFrame);
        await new Promise(requestAnimationFrame);

        if (turboHistory.location.href === this.#location.href) {
          continue;
        }

        this.stop();

        turboHistory.update(
          history.replaceState, turboHistory.location,
          turboHistory.restorationIdentifier,
        );

      }

    }

    /**
     * Stop watching.
     */
    stop() {

      if (this.isWatching() !== true) {
        return;
      }

      clearTimeout(this.#timeoutId);

      this.#timeoutId = null;

    }

    /**
     * Whether we're currently watching.
     *
     * @return {Boolean}
     *   True if watching; false otherwise.
     */
    isWatching() {
      return this.#timeoutId !== null;
    }

  }

  $(once(
    onceName, html,
  )).on(`refreshless:redirect.${eventNamespace}`, (event) => {

    if (typeof $(event.target).prop(propName) !== 'undefined') {
      $(event.target).prop(propName).destroy();
    }

    $(event.target)
    .off(`refreshless:load.${eventNamespace}`)
    .one(`refreshless:load.${eventNamespace}`, (event) => {

      $(event.target).prop(propName, new RedirectUrlRaceFixer(
        watchDuration, window.location, Turbo,
      ));

      $(event.target).prop(propName).start();

    });

  });

})(document.documentElement, jQuery, once, Turbo);

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc