wp-rocket icon indicating copy to clipboard operation
wp-rocket copied to clipboard

3.19: Preload fonts external domains

Open DahmaniAdame opened this issue 7 months ago • 1 comments

Enhancement opportunity The current implementation restricts processing of external CSS and font files to a predefined allowlist of domains.

This approach limits flexibility and requires frequent maintenance to support new valid sources.

Transitioning to an exclusion-based model, where all external domains are processed by default unless explicitly excluded due to known conflicts, would improve compatibility, reduce manual configuration, and future-proof the system against third-party domain changes.

User story

As product, I want the logic to process all external CSS and font files by default, so that new or previously unsupported domains don’t require manual allowing and integration becomes hands-free for all users.

Acceptance Criteria

  • The system processes external CSS and font resources from any domain by default
  • A centralized exclusion list is used to block processing from specific domains known to cause issues
    • Exclusions are controlled by a filter (no UI)
    • Exclusions are managed through a backend exclusion box and included in the Dynamic Lists content
  • The old mechanism to allow specific domains is deprecated in favor of the new exclusion-based model

N/A

Additional context Initial discussion here - https://group-onecom.slack.com/archives/C08F4LZB2QG/p1748344684733299

DahmaniAdame avatar May 28 '25 05:05 DahmaniAdame

QA Note: Test page template with fonts loaded from multiple external domains. Template: https://github.com/wp-media/rocket-test-data/blob/master/templates/preloadfonts_multiple_external_fonts.php Page URL: https://rocketlabsqa.ovh/preloadfonts_multiple_external_fonts/

hanna-meda avatar May 30 '25 07:05 hanna-meda

Hello hello,

This issue concerns two repos:

  1. Rocket-scripts
  2. WP Rocket

Scope a solution

To implement this feature, we must transition from a hardcoded allowlist approach to an exclusion-based model for external font processing. This involves modifying the JavaScript beacon to process all external domains by default, implementing backend exclusion management through dynamic lists, and providing developer filters for custom exclusions.

Rocket-Scripts

We need to replace the logic behind this with something like:

// Process ALL external CSS links, filter by exclusions instead of allowlist
const links = [
  ...document.querySelectorAll('link[rel="stylesheet"]')
].filter(link => {
  try {
    const linkUrl = new URL(link.href);
    const currentUrl = new URL(window.location.href);
    
    // Only process external domains (different origin)
    if (linkUrl.origin === currentUrl.origin) {
      return false;
    }
    
    // Check exclusions instead of allowlist
    const exclusions = this.config.external_font_exclusions || [];
    return !exclusions.some(exclusion => link.href.includes(exclusion));
  } catch (e) {
    return false;
  }
});

WP Rocket

  1. Backend configuration In the Subscriber:
  • Add this method:
<?php
/**
 * Get external font exclusions for beacon configuration
 *
 * @since 3.19.1
 * @return array
 */
private function get_external_font_exclusions( array $exclusion ): array {
    // Merge with dynamic lists exclusions  
    $lists = $this->data_manager->get_lists()->external_font_exclusions ?? [];
    
    return array_merge( $exclusions, (array) $lists );
}

Also add this: 'rocket_external_font_exclusions' => 'get_external_font_exclusions', to get_subscribed_events.

  • In add_custom_data() here Add
/**
     * Filters the external font domains to exclude from processing
     *
     * @since 3.19.1
     *
     * @param array $exclusions Array of domains to exclude from external font processing
     */
    $data['external_font_exclusions'] = wpm_apply_filters_typed( 'string[]', 'rocket_external_font_exclusions', [] );
  1. Dynamic Lists Support (DynamicLists.php):
  • Create this method:
<?php
/**
 * Get the external font exclusions
 *
 * @since 3.19.1
 * @return array
 */
public function get_external_font_exclusions(): array {
    $lists = $this->providers['defaultlists']->data_manager->get_lists();
    
    return $lists->external_font_exclusions ?? [];
}

Effort estimation:

M

This touches multiple systems (JavaScript beacon, PHP backend, dynamic lists, filters) and requires careful testing to ensure no regressions with existing font providers. The implementation is straightforward but the coordination across multiple components and thorough testing bumps it to medium effort.

Miraeld avatar Jul 07 '25 00:07 Miraeld

Beacon Part

I'm worried about this new development because I think it could be resource intensive on the beacon part, we could be fetching a lot of css contents that may not contain any font related rule. If we are ready to overlook this, then that's fine, otherwise, maybe we should think deeper and find another way to process these external fonts, if we are to continue with this. @wordpressfan WDYT?

PHP Part

private function get_external_font_exclusions(): array {
    /**
     * Filters the external font domains to exclude from processing
     *
     * @since 3.19.1
     *
     * @param array $exclusions Array of domains to exclude from external font processing
     */
    $exclusions = wpm_apply_filters_typed( 'string[]', 'rocket_external_font_exclusions', [] );
    
    // Merge with dynamic lists exclusions  
    $lists = $this->data_manager->get_lists()->external_font_exclusions ?? [];
    
    return array_merge( $exclusions, (array) $lists );
}

We already have the data_manager as a dependency in the subscriber here. I'd say to build on top of it instead of adding a new one in the controller. going forward, can we add this wpm_apply_filters_typed( 'string[]', 'rocket_external_font_exclusions', [] ); directly to add_custom_data method and then make this get_external_font_exclusions method public and allow it accept an array as argument and move it to the subscriber class while hooking it to rocket_external_font_exclusions. The logic will be preserved as it is except the new array argument that's being passed. @Miraeld WDYT?

jeawhanlee avatar Jul 07 '25 09:07 jeawhanlee

Yea I Agree, we could move the logic from the controller into the Subscriber where we already have data_manager Grooming is updated

Miraeld avatar Jul 08 '25 11:07 Miraeld

LGTM

jeawhanlee avatar Jul 08 '25 12:07 jeawhanlee

Reopening based on slack thread cc @Miraeld, @DahmaniAdame

hanna-meda avatar Aug 01 '25 10:08 hanna-meda