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
55 changes: 53 additions & 2 deletions src/BeaconPreloadFonts.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,30 @@ class BeaconPreloadFonts {
.map(ext => ext.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'))
.join('|');
this.FONT_FILE_REGEX = new RegExp(`\\.(${extensions})(\\?.*)?$`, 'i');

// Elements that cannot be styled with font-family
this.EXCLUDED_TAG_NAMES = new Set([
// Metadata/document head
'BASE', 'HEAD', 'LINK', 'META', 'STYLE', 'TITLE', 'SCRIPT',

// Media
'IMG', 'VIDEO', 'AUDIO', 'EMBED', 'OBJECT', 'IFRAME',

// Templating, wrappers, components, fallback
'NOSCRIPT', 'TEMPLATE', 'SLOT', 'CANVAS',

// Resources
'SOURCE', 'TRACK', 'PARAM',

// SVG references
'USE', 'SYMBOL',

// Layout work
'BR', 'HR', 'WBR',

// Obsolete/deprecated
'APPLET', 'ACRONYM', 'BGSOUND', 'BIG', 'BLINK', 'CENTER', 'FONT', 'FRAME', 'FRAMESET', 'MARQUEE', 'NOFRAMES', 'STRIKE', 'TT', 'U', 'XMP'
]);
Comment thread
Miraeld marked this conversation as resolved.
}

/**
Expand Down Expand Up @@ -56,6 +80,19 @@ class BeaconPreloadFonts {
return false;
}

/**
* Checks if an element can be styled with font-family.
*
* This method determines if the provided element's tag name is not in the list
* of excluded tag names that cannot be styled with font-family CSS property.
*
* @param {Element} element - The element to check.
* @returns {boolean} True if the element can be styled with font-family, false otherwise.
*/
canElementBeStyledWithFontFamily(element) {
return !this.EXCLUDED_TAG_NAMES.has(element.tagName);
}

/**
* Checks if an element is visible in the viewport.
*
Expand Down Expand Up @@ -620,6 +657,20 @@ class BeaconPreloadFonts {
return elementTop <= foldPosition;
}

/**
* Checks if an element can be processed for font analysis.
*
* This method combines checks for whether an element can be styled with font-family
* and whether it is above the fold, providing a single method to determine if an
* element should be processed during font analysis.
*
* @param {Element} element - The element to check.
* @returns {boolean} True if the element can be processed, false otherwise.
*/
canElementBeProcessed(element) {
return this.canElementBeStyledWithFontFamily(element) && this.isElementAboveFold(element);
}

/**
* Initiates the process of analyzing and summarizing font usage on the page.
* This method fetches network-loaded fonts, stylesheet fonts, and external font pairs.
Expand All @@ -638,7 +689,7 @@ class BeaconPreloadFonts {
const externalFontsResults = await this.processExternalFonts(this.externalParsedPairs);

const elements = Array.from(document.getElementsByTagName('*'))
.filter(el => this.isElementAboveFold(el));
.filter(el => this.canElementBeProcessed(el));

elements.forEach(element => {
const processElementFont = (style, pseudoElement = null) => {
Expand Down Expand Up @@ -875,7 +926,7 @@ class BeaconPreloadFonts {
async processExternalFonts(fontPairs) {
const matches = new Map();
const elements = Array.from(document.getElementsByTagName('*'))
.filter(el => this.isElementAboveFold(el));
.filter(el => this.canElementBeProcessed(el));

const fontMap = new Map();
Object.entries(fontPairs).forEach(([url, variations]) => {
Expand Down
106 changes: 106 additions & 0 deletions test/BeaconPreloadFonts.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -509,6 +509,112 @@ describe('BeaconPreloadFonts', () => {
});
});

describe('canElementBeStyledWithFontFamily', () => {
it('should return true for elements that can be styled with font-family', () => {
const element = { tagName: 'DIV' };
const result = beaconPreloadFonts.canElementBeStyledWithFontFamily(element);
assert.strictEqual(result, true);
});

it('should return true for text elements', () => {
const element = { tagName: 'P' };
const result = beaconPreloadFonts.canElementBeStyledWithFontFamily(element);
assert.strictEqual(result, true);
});

it('should return false for IMG elements', () => {
const element = { tagName: 'IMG' };
const result = beaconPreloadFonts.canElementBeStyledWithFontFamily(element);
assert.strictEqual(result, false);
});

it('should return false for SCRIPT elements', () => {
const element = { tagName: 'SCRIPT' };
const result = beaconPreloadFonts.canElementBeStyledWithFontFamily(element);
assert.strictEqual(result, false);
});

it('should return false for STYLE elements', () => {
const element = { tagName: 'STYLE' };
const result = beaconPreloadFonts.canElementBeStyledWithFontFamily(element);
assert.strictEqual(result, false);
});

it('should return false for META elements', () => {
const element = { tagName: 'META' };
const result = beaconPreloadFonts.canElementBeStyledWithFontFamily(element);
assert.strictEqual(result, false);
});

it('should return false for BR elements', () => {
const element = { tagName: 'BR' };
const result = beaconPreloadFonts.canElementBeStyledWithFontFamily(element);
assert.strictEqual(result, false);
});

it('should return false for deprecated FONT elements', () => {
const element = { tagName: 'FONT' };
const result = beaconPreloadFonts.canElementBeStyledWithFontFamily(element);
assert.strictEqual(result, false);
});
});

describe('canElementBeProcessed', () => {
it('should return true for elements that can be styled and are above fold', () => {
const element = document.createElement('div');
element.tagName = 'DIV';
document.body.appendChild(element);

// Mock the methods to return true for both checks
sinon.stub(beaconPreloadFonts, 'canElementBeStyledWithFontFamily').returns(true);
sinon.stub(beaconPreloadFonts, 'isElementAboveFold').returns(true);

const result = beaconPreloadFonts.canElementBeProcessed(element);
assert.strictEqual(result, true);

// Clean up
document.body.removeChild(element);
beaconPreloadFonts.canElementBeStyledWithFontFamily.restore();
beaconPreloadFonts.isElementAboveFold.restore();
});

it('should return false for elements that cannot be styled even if above fold', () => {
const element = document.createElement('img');
element.tagName = 'IMG';
document.body.appendChild(element);

// Mock the methods - can't be styled but is above fold
sinon.stub(beaconPreloadFonts, 'canElementBeStyledWithFontFamily').returns(false);
sinon.stub(beaconPreloadFonts, 'isElementAboveFold').returns(true);

const result = beaconPreloadFonts.canElementBeProcessed(element);
assert.strictEqual(result, false);

// Clean up
document.body.removeChild(element);
beaconPreloadFonts.canElementBeStyledWithFontFamily.restore();
beaconPreloadFonts.isElementAboveFold.restore();
});

it('should return false for elements that can be styled but are below fold', () => {
const element = document.createElement('div');
element.tagName = 'DIV';
document.body.appendChild(element);

// Mock the methods - can be styled but is below fold
sinon.stub(beaconPreloadFonts, 'canElementBeStyledWithFontFamily').returns(true);
sinon.stub(beaconPreloadFonts, 'isElementAboveFold').returns(false);

const result = beaconPreloadFonts.canElementBeProcessed(element);
assert.strictEqual(result, false);

// Clean up
document.body.removeChild(element);
beaconPreloadFonts.canElementBeStyledWithFontFamily.restore();
beaconPreloadFonts.isElementAboveFold.restore();
});
});

describe('run', () => {
it('should log no fonts found if no fonts are above the fold', async () => {
// Mock methods
Expand Down
Loading