Skip to content
Open
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
36 changes: 29 additions & 7 deletions src/BeaconLcp.js
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ class BeaconLcp {
current_src: ""
};

const css_bg_url_rgx = /url\(\s*?['"]?\s*?(.+?)\s*?["']?\s*?\)/ig;
const css_bg_url_rgx = /url\(\s*(['"]?)(.*?)\1\s*\)/ig;

if (nodeName === "img" && element.srcset) {
element_info.type = "img-srcset";
Expand All @@ -106,22 +106,38 @@ class BeaconLcp {
element_info.type = "img";
element_info.src = element.src;
element_info.current_src = element.currentSrc;
// Return null if src is empty or just whitespace
if (!element_info.src || !element_info.src.trim()) {
return null;
}
} else if (nodeName === "video") {
element_info.type = "img";
const source = element.querySelector('source');
element_info.src = element.poster || (source ? source.src : '');
element_info.current_src = element_info.src;
// Return null if src is empty or just whitespace
if (!element_info.src || !element_info.src.trim()) {
return null;
}
} else if (nodeName === "svg") {
const imageElement = element.querySelector('image');
if (imageElement) {
const href = imageElement.getAttribute('href') || '';
if (!href || !href.trim()) {
return null;
}
element_info.type = "img";
element_info.src = imageElement.getAttribute('href') || '';
element_info.src = href;
element_info.current_src = element_info.src;
}
} else if (nodeName === "picture") {
element_info.type = "picture";
const img = element.querySelector('img');
element_info.src = img ? img.src : "";
// Return null if src is empty or just whitespace
if (!element_info.src || !element_info.src.trim()) {
return null;
}
element_info.sources = Array.from(element.querySelectorAll('source')).map(source => ({
srcset: source.srcset || '',
media: source.media || '',
Expand Down Expand Up @@ -149,10 +165,10 @@ class BeaconLcp {
}

const matches = [...full_bg_prop.matchAll(css_bg_url_rgx)];
element_info.bg_set = matches.map(m => m[1] ? { src: m[1].trim() + (m[2] ? " " + m[2].trim() : "") } : {});
if (element_info.bg_set.every(item => item.src === "")) {
element_info.bg_set = matches.map(m => m[1] ? { src: m[1].trim() } : {});
}
// m[2] is the URL content (m[1] is the quote character)
element_info.bg_set = matches
.map(m => m[2] ? { src: m[2].trim() } : {})
.filter(item => item.src && item.src !== "");

if (element_info.bg_set.length <= 0) {
return null;
Expand All @@ -171,7 +187,13 @@ class BeaconLcp {

_initWithFirstElementWithInfo(elements) {
const firstElementWithInfo = elements.find(item => {
return item.elementInfo !== null && (item.elementInfo.src || item.elementInfo.srcset);
if (!item.elementInfo) {
return false;
}
const hasSrc = item.elementInfo.src &&
(typeof item.elementInfo.src === 'string' ? item.elementInfo.src.trim() !== '' : Array.isArray(item.elementInfo.src));
const hasSrcset = item.elementInfo.srcset && item.elementInfo.srcset.trim();
return hasSrc || hasSrcset;
});

if (!firstElementWithInfo) {
Expand Down
139 changes: 139 additions & 0 deletions test/BeaconLcp.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,31 @@ describe('BeaconManager', function() {

assert.strictEqual(beacon.performanceImages.length, 0);
});

it('should skip elements with empty src strings', function() {
const elements = [
{ element: { nodeName: 'img' }, elementInfo: { type: 'img', src: '' } }, // empty src
{ element: { nodeName: 'img' }, elementInfo: { type: 'img', src: ' ' } }, // whitespace src
{ element: { nodeName: 'img' }, elementInfo: { type: 'img', src: 'http://example.com/valid.jpg' } },
];

beacon._initWithFirstElementWithInfo(elements);

assert.strictEqual(beacon.performanceImages.length, 1);
assert.strictEqual(beacon.performanceImages[0].src, 'http://example.com/valid.jpg');
assert.strictEqual(beacon.performanceImages[0].label, 'lcp');
});

it('should handle elements with srcset instead of src', function() {
const elements = [
{ element: { nodeName: 'img' }, elementInfo: { type: 'img-srcset', src: '', srcset: 'image-320w.jpg 320w, image-640w.jpg 640w' } },
];

beacon._initWithFirstElementWithInfo(elements);

assert.strictEqual(beacon.performanceImages.length, 1);
assert.strictEqual(beacon.performanceImages[0].srcset, 'image-320w.jpg 320w, image-640w.jpg 640w');
});
});

describe('#_getElementInfo()', function() {
Expand All @@ -95,6 +120,120 @@ describe('BeaconManager', function() {

assert.strictEqual(elementInfo, null);
});

it('should return null for img elements with empty src', function() {
const element = {
nodeName: 'img',
src: '',
currentSrc: ''
};

const elementInfo = beacon._getElementInfo(element);

assert.strictEqual(elementInfo, null);
});

it('should return null for img elements with whitespace-only src', function() {
const element = {
nodeName: 'img',
src: ' ',
currentSrc: ' '
};

const elementInfo = beacon._getElementInfo(element);

assert.strictEqual(elementInfo, null);
});

it('should return null for svg image elements with empty href', function() {
const imageElement = {
getAttribute: sinon.stub().returns('')
};
const element = {
nodeName: 'svg',
querySelector: sinon.stub().returns(imageElement)
};

const elementInfo = beacon._getElementInfo(element);

assert.strictEqual(elementInfo, null);
assert.strictEqual(element.querySelector.calledWith('image'), true);
assert.strictEqual(imageElement.getAttribute.calledWith('href'), true);
});

it('should return null for svg image elements with whitespace-only href', function() {
const imageElement = {
getAttribute: sinon.stub().returns(' \n\t ')
};
const element = {
nodeName: 'svg',
querySelector: sinon.stub().returns(imageElement)
};

const elementInfo = beacon._getElementInfo(element);

assert.strictEqual(elementInfo, null);
});

it('should return valid element info for svg image elements with non-empty href', function() {
const imageElement = {
getAttribute: sinon.stub().returns('https://example.com/image.svg')
};
const element = {
nodeName: 'svg',
querySelector: sinon.stub().returns(imageElement)
};

const elementInfo = beacon._getElementInfo(element);

assert.notStrictEqual(elementInfo, null);
assert.strictEqual(elementInfo.type, 'img');
assert.strictEqual(elementInfo.src, 'https://example.com/image.svg');
});

it('should return null for video elements with empty poster and no source', function() {
const element = {
nodeName: 'video',
poster: '',
querySelector: sinon.stub().returns(null)
};

const elementInfo = beacon._getElementInfo(element);

assert.strictEqual(elementInfo, null);
});

it('should return null for picture elements with empty img src', function() {
const imgElement = {
src: ''
};
const element = {
nodeName: 'picture',
querySelector: sinon.stub().returns(imgElement),
querySelectorAll: sinon.stub().returns([])
};

const elementInfo = beacon._getElementInfo(element);

assert.strictEqual(elementInfo, null);
});

it('should return valid element info for picture elements with non-empty img src', function() {
const imgElement = {
src: 'https://example.com/image.jpg'
};
const element = {
nodeName: 'picture',
querySelector: sinon.stub().returns(imgElement),
querySelectorAll: sinon.stub().returns([])
};

const elementInfo = beacon._getElementInfo(element);

assert.notStrictEqual(elementInfo, null);
assert.strictEqual(elementInfo.type, 'picture');
assert.strictEqual(elementInfo.src, 'https://example.com/image.jpg');
});
});

describe('#_generateLcpCandidates()', function() {
Expand Down