Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
187 changes: 184 additions & 3 deletions assets/js/wpr-beacon.js
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,187 @@
return url;
}
}
/**
* Fetches external stylesheet links from known font providers, retrieves their CSS,
* parses them into in-memory CSSStyleSheet objects, and extracts font-family/font-face
* information into a structured object.
*
* @async
* @function externalStylesheetsDoc
* @returns {Promise<{styleSheets: CSSStyleSheet[], fontPairs: Object}>} An object containing:
* - styleSheets: Array of parsed CSSStyleSheet objects (not attached to the DOM).
* - fontPairs: An object mapping font URLs to arrays of font variation objects
* ({family, weight, style}).
*
* @example
* const { styleSheets, fontPairs } = await externalStylesheetsDoc();
* this.logger.logMessage(fontPairs);
*/
async externalStylesheetsDoc() {
function generateFontPairsFromStyleSheets(styleSheetsArray) {
const fontPairs = {};
function _extractFirstUrlFromSrc(srcValue) {
if (!srcValue) return null;
const urlMatch = srcValue.match(/url\s*\(\s*(['"]?)(.+?)\1\s*\)/);
return urlMatch ? urlMatch[2] : null;
}
function _cleanFontFamilyName(fontFamilyValue) {
if (!fontFamilyValue) return "";
return fontFamilyValue.replace(/^['"]+|['"]+$/g, "").trim();
}
if (!styleSheetsArray || !Array.isArray(styleSheetsArray)) {
console.warn(
"generateFontPairsFromStyleSheets: Input is not a valid array. Received:",
styleSheetsArray
);
return fontPairs;
}
if (styleSheetsArray.length === 0) {
return fontPairs;
}
styleSheetsArray.forEach((sheet) => {
if (sheet && sheet.cssRules) {
try {
for (const rule of sheet.cssRules) {
if (rule.type === CSSRule.FONT_FACE_RULE) {
const cssFontFaceRule = rule;
const fontFamily = _cleanFontFamilyName(
cssFontFaceRule.style.getPropertyValue("font-family")
);
const fontWeight = cssFontFaceRule.style.getPropertyValue("font-weight") || "normal";
const fontStyle = cssFontFaceRule.style.getPropertyValue("font-style") || "normal";
const src = cssFontFaceRule.style.getPropertyValue("src");
const fontUrl = _extractFirstUrlFromSrc(src);
if (fontFamily && fontUrl) {
const variation = {
family: fontFamily,
weight: fontWeight,
style: fontStyle
};
if (!fontPairs[fontUrl]) fontPairs[fontUrl] = [];
const variationExists = fontPairs[fontUrl].some(
(v) => v.family === variation.family && v.weight === variation.weight && v.style === variation.style
);
if (!variationExists) fontPairs[fontUrl].push(variation);
}
}
}
} catch (e) {
console.warn(
"Error processing CSS rules from a stylesheet:",
e,
sheet
);
}
} else if (sheet && !sheet.cssRules) {
console.warn(
"Skipping a stylesheet as its cssRules are not accessible or it is empty:",
sheet
);
}
});
return fontPairs;
}
const externalFontsProviders = [
"fonts.googleapis.com",
"fonts.gstatic.com",
"use.typekit.net",
"fonts.adobe.com",
"cdn.fonts.net"
// Add more known external font domains as needed
];
const links = [
...document.querySelectorAll('link[rel="stylesheet"]')
].filter(
(link) => externalFontsProviders.some((domain) => link.href.includes(domain))
);
if (links.length === 0) {
this.logger.logMessage("No external CSS links found to process.");
return {
// Consistent return structure
styleSheets: [],
// The retrievable CSSStyleSheet objects
fontPairs: {}
// Processed data from these sheets
};
}
const fetchedCssPromises = links.map(
(linkElement) => fetch(linkElement.href, { mode: "cors" }).then((response) => {
if (response.ok) {
return response.text();
}
console.warn(
`Failed to fetch external CSS from ${linkElement.href}: ${response.status} ${response.statusText}`
);
return null;
}).catch((error) => {
console.error(
`Network error fetching external CSS from ${linkElement.href}:`,
error
);
return null;
})
);
const cssTexts = await Promise.all(fetchedCssPromises);
const temporaryStyleSheets = [];
cssTexts.forEach((txt) => {
if (txt && txt.trim() !== "") {
try {
const sheet = new CSSStyleSheet();
sheet.replaceSync(txt);
temporaryStyleSheets.push(sheet);
} catch (error) {
console.error(
"Could not parse fetched CSS into a stylesheet:",
error,
`
CSS (first 200 chars): ${txt.substring(0, 200)}...`
);
}
}
});
if (temporaryStyleSheets.length > 0) {
this.logger.logMessage(
`[Beacon] ${temporaryStyleSheets.length} stylesheet(s) fetched and parsed into CSSStyleSheet objects.`
);
} else {
this.logger.logMessage(
"[Beacon] No stylesheets were successfully parsed from the fetched CSS."
);
}
const processedFontPairs = generateFontPairsFromStyleSheets(temporaryStyleSheets);
return {
styleSheets: temporaryStyleSheets,
fontPairs: processedFontPairs
};
}
/**
* Asynchronously initializes and parses external font stylesheets.
*
* Fetches external font stylesheets and font pairs using `externalStylesheetsDoc`,
* then stores the parsed results in `externalParsedSheets` and `externalParsedPairs`.
* Logs the process and handles errors by resetting `externalParsedSheets` to an empty array.
*
* @async
* @returns {Promise<void>} Resolves when external font stylesheets have been initialized.
*/
async _initializeExternalFontSheets() {
this.logger.logMessage("Initializing external font stylesheets...");
try {
const result = await this.externalStylesheetsDoc();
this.externalParsedSheets = result.styleSheets || [];
this.externalParsedPairs = result.fontPairs || [];
this.logger.logMessage(
`Successfully parsed ${this.externalParsedSheets.length} external font stylesheets.`
);
} catch (error) {
this.logger.logMessage(
"Error initializing external font stylesheets:",
error
);
this.externalParsedSheets = [];
}
}
/**
* Retrieves a map of network-loaded fonts.
*
Expand All @@ -477,7 +658,7 @@
*/
getFontFaceRules() {
const stylesheetFonts = {};
Array.from(document.styleSheets).forEach((sheet) => {
Array.from(Array.from(document.styleSheets)).filter((sheet) => !sheet.href || new URL(sheet.href).origin === location.origin).forEach((sheet) => {
try {
Array.from(sheet.cssRules || []).forEach((rule) => {
if (rule instanceof CSSFontFaceRule) {
Expand Down Expand Up @@ -541,11 +722,11 @@
*/
async run() {
await document.fonts.ready;
await this._initializeExternalFontSheets();
const networkLoadedFonts = this.getNetworkLoadedFonts();
const stylesheetFonts = this.getFontFaceRules();
const hostedFonts = /* @__PURE__ */ new Map();
const externalFontPairs = this.config.font_data;
const externalFontsResults = await this.processExternalFonts(externalFontPairs);
const externalFontsResults = await this.processExternalFonts(this.externalParsedPairs);
const elements = Array.from(document.getElementsByTagName("*")).filter((el) => this.isElementAboveFold(el));
elements.forEach((element) => {
const processElementFont = (style, pseudoElement = null) => {
Expand Down
3 changes: 2 additions & 1 deletion assets/js/wpr-beacon.min.js

Large diffs are not rendered by default.

6 changes: 3 additions & 3 deletions assets/js/wpr-beacon.min.js.map

Large diffs are not rendered by default.

15 changes: 10 additions & 5 deletions inc/Engine/Media/PreloadFonts/AJAX/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ public function add_data(): array {
$max_preload_fonts_number = 1;
}

$fonts = $this->remove_excluded_fonts( $fonts, $this->context->get_exclusions() );
$fonts = $this->filter_fonts( $fonts, $this->context->get_exclusions() );

foreach ( (array) $fonts as $index => $font ) {
$preload_fonts[ $index ] = sanitize_url( wp_unslash( $font ) );
Expand Down Expand Up @@ -147,14 +147,14 @@ public function check_data(): array {
}

/**
* Removes excluded fonts from the list of fonts to be preloaded.
* Filter font urls before saving into DB by checking exclusions list and extensions.
*
* @param array $fonts Array of fonts to be preloaded.
* @param array $exclusions Array of fonts to be excluded.
*
* @return array Filtered array of fonts, excluding those specified in the exclusion list.
*/
private function remove_excluded_fonts( array $fonts, array $exclusions ): array {
private function filter_fonts( array $fonts, array $exclusions ): array {
if ( empty( $exclusions ) ) {
return $fonts;
}
Expand All @@ -163,12 +163,17 @@ private function remove_excluded_fonts( array $fonts, array $exclusions ): array
* Create a single regex pattern from all exclusions.
* Use a different delimiter (#) to avoid issues with URLs containing slashes.
*/
$pattern = '#(' . implode( '|', array_map( 'preg_quote', $exclusions ) ) . ')#i';
$pattern = '#(' . implode( '|', array_map( 'preg_quote', $exclusions ) ) . ')#i';
$extensions = $this->context->get_extensions();

// Filter out fonts that match the pattern.
$filtered_fonts = array_filter(
$fonts,
function ( $font ) use ( $pattern, $exclusions ) {
function ( $font ) use ( $pattern, $exclusions, $extensions ) {
if ( ! in_array( pathinfo( $font, PATHINFO_EXTENSION ), $extensions, true ) ) {
return false;
}

// Check exact match ( Mainly url match ).
if ( in_array( $font, $exclusions, true ) ) {
return false;
Expand Down
25 changes: 25 additions & 0 deletions inc/Engine/Media/PreloadFonts/Context/Context.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,17 @@ class Context implements ContextInterface {
*/
private $options;

/**
* List of allowed extensions.
*
* @var string[]
*/
private $extensions = [
'woff2',
'woff',
'ttf',
];

/**
* Constructor.
*
Expand Down Expand Up @@ -61,4 +72,18 @@ public function get_exclusions(): array {
*/
return wpm_apply_filters_typed( 'string[]', 'rocket_preload_fonts_excluded_fonts', [] );
}

/**
* Get filtered allowed list of extensions.
*
* @return string[]
*/
public function get_extensions(): array {
/**
* Filters the list of processed font extensions.
*
* @param string[] $processed_extensions Array of processed font extensions.
*/
return wpm_apply_filters_typed( 'string[]', 'rocket_preload_fonts_processed_extensions', $this->extensions );
}
}
23 changes: 1 addition & 22 deletions inc/Engine/Media/PreloadFonts/Frontend/Controller.php
Original file line number Diff line number Diff line change
Expand Up @@ -78,30 +78,9 @@ public function add_custom_data( array $data ): array {
return $data;
}

/**
* Filters the list of mock font urls.
*
* @param array $font_data Array of font data.
*/
$font_data = wpm_apply_filters_typed( 'array', 'rocket_preload_fonts_font_data', [] );

$processed_extensions = [
'woff2',
'woff',
'ttf',
];

/**
* Filters the list of processed font extensions.
*
* @param string[] $processed_extensions Array of processed font extensions.
*/
$processed_extensions = wpm_apply_filters_typed( 'string[]', 'rocket_preload_fonts_processed_extensions', $processed_extensions );

$data['preload_fonts_exclusions'] = $this->context->get_exclusions();
$data['font_data'] = $font_data;
$data['status']['preload_fonts'] = $this->context->is_allowed();
$data['processed_extensions'] = $processed_extensions;
$data['processed_extensions'] = $this->context->get_extensions();

return $data;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@
</head>
<body>
</html>
<script>var rocket_beacon_data = {"ajax_url":"http:\/\/example.org\/wp-admin\/admin-ajax.php","nonce":"96ac96b69e","url":"http:\/\/example.org","is_mobile":false,"width_threshold":1600,"height_threshold":700,"delay":500,"debug":false,"status":{"atf":true,"lrc":true,"preload_fonts":true,"preconnect_external_domain":true},"elements":"img, video, picture, p, main, div, li, svg, section, header, span","lrc_threshold":1800,"preload_fonts_exclusions":[],"font_data":[],"processed_extensions":["woff2","woff","ttf"],"preconnect_external_domain_elements":["link","script","iframe"],"preconnect_external_domain_exclusions":[]}</script>
<script>var rocket_beacon_data = {"ajax_url":"http:\/\/example.org\/wp-admin\/admin-ajax.php","nonce":"96ac96b69e","url":"http:\/\/example.org","is_mobile":false,"width_threshold":1600,"height_threshold":700,"delay":500,"debug":false,"status":{"atf":true,"lrc":true,"preload_fonts":true,"preconnect_external_domain":true},"elements":"img, video, picture, p, main, div, li, svg, section, header, span","lrc_threshold":1800,"preload_fonts_exclusions":[],"processed_extensions":["woff2","woff","ttf"],"preconnect_external_domain_elements":["link","script","iframe"],"preconnect_external_domain_exclusions":[]}</script>
<script data-name="wpr-wpr-beacon" src='http://example.org/wp-content/plugins/wp-rocket/assets/js/wpr-beacon.min.js' async>
</script>
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,5 @@ <h1>Iframe Header</h1>
</body>
</html>
" width="600" height="400" style="border:none;"></iframe>
<script>var rocket_beacon_data = {"ajax_url":"http:\/\/example.org\/wp-admin\/admin-ajax.php","nonce":"96ac96b69e","url":"http:\/\/example.org","is_mobile":false,"width_threshold":1600,"height_threshold":700,"delay":500,"debug":false,"status":{"atf":true,"lrc":true,"preload_fonts":true,"preconnect_external_domain":true},"elements":"img, video, picture, p, main, div, li, svg, section, header, span","lrc_threshold":1800,"preload_fonts_exclusions":[],"font_data":[],"processed_extensions":["woff2","woff","ttf"],"preconnect_external_domain_elements":["link","script","iframe"],"preconnect_external_domain_exclusions":[]}</script><script data-name="wpr-wpr-beacon" src='http://example.org/wp-content/plugins/wp-rocket/assets/js/wpr-beacon.min.js' async></script></body>
<script>var rocket_beacon_data = {"ajax_url":"http:\/\/example.org\/wp-admin\/admin-ajax.php","nonce":"96ac96b69e","url":"http:\/\/example.org","is_mobile":false,"width_threshold":1600,"height_threshold":700,"delay":500,"debug":false,"status":{"atf":true,"lrc":true,"preload_fonts":true,"preconnect_external_domain":true},"elements":"img, video, picture, p, main, div, li, svg, section, header, span","lrc_threshold":1800,"preload_fonts_exclusions":[],"processed_extensions":["woff2","woff","ttf"],"preconnect_external_domain_elements":["link","script","iframe"],"preconnect_external_domain_exclusions":[]}</script><script data-name="wpr-wpr-beacon" src='http://example.org/wp-content/plugins/wp-rocket/assets/js/wpr-beacon.min.js' async></script></body>
</html>
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</head>
<body>
<script>
var rocket_beacon_data = {"ajax_url":"http:\/\/example.org\/wp-admin\/admin-ajax.php","nonce":"96ac96b69e","url":"http:\/\/example.org","is_mobile":false,"width_threshold":1600,"height_threshold":700,"delay":500,"debug":false,"status":{"atf":true,"lrc":true,"preload_fonts":true,"preconnect_external_domain":true},"elements":"img, video, picture, p, main, div, li, svg, section, header, span","lrc_threshold":1800,"preload_fonts_exclusions":[],"font_data":[],"processed_extensions":["woff2","woff","ttf"],"preconnect_external_domain_elements":["link","script","iframe"],"preconnect_external_domain_exclusions":[]}</script>
var rocket_beacon_data = {"ajax_url":"http:\/\/example.org\/wp-admin\/admin-ajax.php","nonce":"96ac96b69e","url":"http:\/\/example.org","is_mobile":false,"width_threshold":1600,"height_threshold":700,"delay":500,"debug":false,"status":{"atf":true,"lrc":true,"preload_fonts":true,"preconnect_external_domain":true},"elements":"img, video, picture, p, main, div, li, svg, section, header, span","lrc_threshold":1800,"preload_fonts_exclusions":[],"processed_extensions":["woff2","woff","ttf"],"preconnect_external_domain_elements":["link","script","iframe"],"preconnect_external_domain_exclusions":[]}</script>
<script data-name="wpr-wpr-beacon" src='http://example.org/wp-content/plugins/wp-rocket/assets/js/wpr-beacon.min.js' async>
</script>
</body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</head>
<body>
<script>
var rocket_beacon_data = {"ajax_url":"http:\/\/example.org\/wp-admin\/admin-ajax.php","nonce":"96ac96b69e","url":"http:\/\/example.org","is_mobile":false,"width_threshold":1600,"height_threshold":700,"delay":500,"debug":false,"status":{"atf":true,"lrc":true,"preload_fonts":true,"preconnect_external_domain":true},"elements":"img, video, picture, p, main, div, li, svg, section, header, span","lrc_threshold":1800,"preload_fonts_exclusions":[],"font_data":[],"processed_extensions":["woff2","woff","ttf"],"preconnect_external_domain_elements":["link","script","iframe"],"preconnect_external_domain_exclusions":[]}</script>
var rocket_beacon_data = {"ajax_url":"http:\/\/example.org\/wp-admin\/admin-ajax.php","nonce":"96ac96b69e","url":"http:\/\/example.org","is_mobile":false,"width_threshold":1600,"height_threshold":700,"delay":500,"debug":false,"status":{"atf":true,"lrc":true,"preload_fonts":true,"preconnect_external_domain":true},"elements":"img, video, picture, p, main, div, li, svg, section, header, span","lrc_threshold":1800,"preload_fonts_exclusions":[],"processed_extensions":["woff2","woff","ttf"],"preconnect_external_domain_elements":["link","script","iframe"],"preconnect_external_domain_exclusions":[]}</script>
<script data-name="wpr-wpr-beacon" src='http://example.org/wp-content/plugins/wp-rocket/assets/js/wpr-beacon.min.js' async>
</script>
</body>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</head>
<body>
<script>
var rocket_beacon_data = {"ajax_url":"http:\/\/example.org\/wp-admin\/admin-ajax.php","nonce":"96ac96b69e","url":"http:\/\/example.org","is_mobile":false,"width_threshold":1600,"height_threshold":700,"delay":500,"debug":false,"status":{"atf":true,"lrc":true,"preload_fonts":true,"preconnect_external_domain":true},"elements":"img, video, picture, p, main, div, li, svg, section, header, span","lrc_threshold":1800,"preload_fonts_exclusions":[],"font_data":[],"processed_extensions":["woff2","woff","ttf"],"preconnect_external_domain_elements":["link","script","iframe"],"preconnect_external_domain_exclusions":[]}</script>
var rocket_beacon_data = {"ajax_url":"http:\/\/example.org\/wp-admin\/admin-ajax.php","nonce":"96ac96b69e","url":"http:\/\/example.org","is_mobile":false,"width_threshold":1600,"height_threshold":700,"delay":500,"debug":false,"status":{"atf":true,"lrc":true,"preload_fonts":true,"preconnect_external_domain":true},"elements":"img, video, picture, p, main, div, li, svg, section, header, span","lrc_threshold":1800,"preload_fonts_exclusions":[],"processed_extensions":["woff2","woff","ttf"],"preconnect_external_domain_elements":["link","script","iframe"],"preconnect_external_domain_exclusions":[]}</script>
<script data-name="wpr-wpr-beacon" src='http://example.org/wp-content/plugins/wp-rocket/assets/js/wpr-beacon.min.js' async>
</script>
</body>
Expand Down
Loading
Loading