Problem/Motivation

When a user navigates away from a page (clicks a link, presses Escape, or uses browser back/forward) before AJAX placeholder content finishes loading, an AJAX error dialog is displayed to the user.

This happens because the browser aborts pending HTTP requests when navigating away, resulting in XMLHttpRequest.status === 0. The module uses Drupal.ajax({ url: endpoint }).execute() without custom error handling, so Drupal's default AJAX error handler shows an error message for what is essentially normal user navigation behavior.

Steps to reproduce

  1. Add an AJAX placeholder to a page with a callback that takes a few seconds to complete
  2. Navigate to that page
  3. Before the placeholder content finishes loading, click any link to navigate away (or press Escape)
  4. Observe an AJAX error message appearing despite no actual error occurring

Expected behavior

Aborted/cancelled AJAX requests should be silently ignored since they represent normal user navigation, not actual errors.

Actual behavior

An AJAX error dialog is shown to the user with a generic error message, creating a poor user experience.

Current implementation in js/ajax-placeholder.js:

Drupal.behaviors.ajaxPlaceholder = {
  attach: function (context) {
    once('ajax-placeholder', '.js-ajax-placeholder', context).forEach(function (ajaxPlaceholder) {
      let $element = $(ajaxPlaceholder);
      let endpoint = Drupal.url('ajax/placeholder/' + $element.data('hash'));
      Drupal.ajax({ url: endpoint }).execute(); // No error handling
    });
  }
};

Proposed resolution

Override the error handler for AJAX placeholder requests to silently ignore aborted requests (status 0).

Proposed changes

Drupal.behaviors.ajaxPlaceholder = {
  attach: function (context) {
    once('ajax-placeholder', '.js-ajax-placeholder', context).forEach(function (ajaxPlaceholder) {
      let $element = $(ajaxPlaceholder);
      let endpoint = Drupal.url('ajax/placeholder/' + $element.data('hash'));
      let ajaxObject = Drupal.ajax({ url: endpoint });

      // Override error handler to ignore aborted requests
      var originalError = ajaxObject.error;
      ajaxObject.error = function(xmlhttprequest, uri, customMessage) {
        // Status 0 indicates the request was aborted (user navigated away, pressed Escape, etc.)
        if (xmlhttprequest.status === 0) {
          return;
        }
        originalError.call(this, xmlhttprequest, uri, customMessage);
      };

      ajaxObject.execute();
    });
  }
};

Comments

introfini created an issue.