From 5aa1f760246c7497a9a1319296146363fb5d8230 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20G=C3=BCneli?= Date: Mon, 30 Jun 2025 20:35:42 +0200 Subject: [PATCH 1/9] Custom metric for content-visibility --- dist/almanac.js | 34 ++++++++++++++++++++++++++++++++++ metric-summary.md | 30 ++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) diff --git a/dist/almanac.js b/dist/almanac.js index 4bf0bf6f..163747a3 100644 --- a/dist/almanac.js +++ b/dist/almanac.js @@ -777,5 +777,39 @@ return JSON.stringify({ 'length_of_h1s': (() => { return [...document.querySelectorAll('h1')].map(node => node.innerText.length); + })(), + + 'content_visibility': (() => { + // Detects elements using the content-visibility CSS property + const elements = document.querySelectorAll('*'); + const contentVisibilityElements = []; + const contentVisibilityValues = {}; + + for (const element of elements) { + try { + const computedStyle = getComputedStyle(element); + const contentVisibility = computedStyle.getPropertyValue('content-visibility'); + + if (contentVisibility && contentVisibility !== 'visible') { + contentVisibilityElements.push({ + tagName: element.tagName.toLowerCase(), + contentVisibility: contentVisibility.trim(), + className: element.className || '', + id: element.id || '' + }); + + const value = contentVisibility.trim(); + contentVisibilityValues[value] = (contentVisibilityValues[value] || 0) + 1; + } + } catch (e) { + // continue regardless of error + } + } + + return { + total: contentVisibilityElements.length, + elements: contentVisibilityElements, + values: contentVisibilityValues + }; })() }); diff --git a/metric-summary.md b/metric-summary.md index f69a1d98..aaa9c548 100644 --- a/metric-summary.md +++ b/metric-summary.md @@ -723,6 +723,36 @@ Example response: [5, 12, 11] ``` +## content_visibility + +Detects elements using the `content-visibility` CSS property for performance optimization analysis. Returns information about elements that have content-visibility set to values other than 'visible' (such as 'auto', 'hidden', or 'skip'). + +Example response: + +```json +{ + "total": 15, + "elements": [ + { + "tagName": "div", + "contentVisibility": "auto", + "className": "lazy-section", + "id": "section-1" + }, + { + "tagName": "section", + "contentVisibility": "hidden", + "className": "hidden-content", + "id": "" + } + ], + "values": { + "auto": 12, + "hidden": 3 + } +} +``` + ## [Images.js](https://github.com/HTTPArchive/custom-metrics/blob/main/dist/Images.js) metrics A JSON array of `` elements on the page. From 3a7863718b232eefe33cc47c30fdac6e8b70840e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20G=C3=BCneli?= Date: Tue, 1 Jul 2025 08:51:40 +0200 Subject: [PATCH 2/9] Move content_visibility to css.js --- dist/almanac.js | 34 ---------------------------------- dist/css.js | 34 ++++++++++++++++++++++++++++++++++ metric-summary.md | 4 +++- 3 files changed, 37 insertions(+), 35 deletions(-) diff --git a/dist/almanac.js b/dist/almanac.js index 163747a3..4bf0bf6f 100644 --- a/dist/almanac.js +++ b/dist/almanac.js @@ -777,39 +777,5 @@ return JSON.stringify({ 'length_of_h1s': (() => { return [...document.querySelectorAll('h1')].map(node => node.innerText.length); - })(), - - 'content_visibility': (() => { - // Detects elements using the content-visibility CSS property - const elements = document.querySelectorAll('*'); - const contentVisibilityElements = []; - const contentVisibilityValues = {}; - - for (const element of elements) { - try { - const computedStyle = getComputedStyle(element); - const contentVisibility = computedStyle.getPropertyValue('content-visibility'); - - if (contentVisibility && contentVisibility !== 'visible') { - contentVisibilityElements.push({ - tagName: element.tagName.toLowerCase(), - contentVisibility: contentVisibility.trim(), - className: element.className || '', - id: element.id || '' - }); - - const value = contentVisibility.trim(); - contentVisibilityValues[value] = (contentVisibilityValues[value] || 0) + 1; - } - } catch (e) { - // continue regardless of error - } - } - - return { - total: contentVisibilityElements.length, - elements: contentVisibilityElements, - values: contentVisibilityValues - }; })() }); diff --git a/dist/css.js b/dist/css.js index 61ae04df..63894af5 100644 --- a/dist/css.js +++ b/dist/css.js @@ -75,4 +75,38 @@ return JSON.stringify({ externalCssInBody: countExternalCssInBody(), inlineCssInHead: countInlineCssInHead(), inlineCssInBody: countInlineCssInBody(), + + content_visibility: (() => { + // Detects elements using the content-visibility CSS property + const elements = document.querySelectorAll('*'); + const contentVisibilityElements = []; + const contentVisibilityValues = {}; + + for (const element of elements) { + try { + const computedStyle = getComputedStyle(element); + const contentVisibility = computedStyle.getPropertyValue('content-visibility'); + + if (contentVisibility && contentVisibility !== 'visible') { + contentVisibilityElements.push({ + tagName: element.tagName.toLowerCase(), + contentVisibility: contentVisibility.trim(), + className: element.className || '', + id: element.id || '' + }); + + const value = contentVisibility.trim(); + contentVisibilityValues[value] = (contentVisibilityValues[value] || 0) + 1; + } + } catch (e) { + // continue regardless of error + } + } + + return { + total: contentVisibilityElements.length, + elements: contentVisibilityElements, + values: contentVisibilityValues + }; + })() }); diff --git a/metric-summary.md b/metric-summary.md index aaa9c548..ab6b606b 100644 --- a/metric-summary.md +++ b/metric-summary.md @@ -723,7 +723,9 @@ Example response: [5, 12, 11] ``` -## content_visibility +## [css.js](https://github.com/HTTPArchive/custom-metrics/blob/main/dist/css.js) metrics + +### content_visibility Detects elements using the `content-visibility` CSS property for performance optimization analysis. Returns information about elements that have content-visibility set to values other than 'visible' (such as 'auto', 'hidden', or 'skip'). From 1a8d5fe4aaa3d0f2bf1e84dffe65fa2623973010 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20G=C3=BCneli?= Date: Fri, 4 Jul 2025 10:43:17 +0200 Subject: [PATCH 3/9] Replace expensive DOM traversal with efficient regex-based CSS parsing --- dist/content-visibility.js | 111 +++++++++++++++++++++++++++++++++++++ dist/css.js | 34 ------------ metric-summary.md | 38 ++++++------- 3 files changed, 129 insertions(+), 54 deletions(-) create mode 100644 dist/content-visibility.js diff --git a/dist/content-visibility.js b/dist/content-visibility.js new file mode 100644 index 00000000..7265faf3 --- /dev/null +++ b/dist/content-visibility.js @@ -0,0 +1,111 @@ +/** + * Content Visibility Custom Metric - Optimized Version + * + * Analyzes CSS usage of the content-visibility property using efficient + * regex-based detection with performance optimizations. + */ + +//[content-visibility] + +(() => { + /** + * Extract content-visibility declarations from CSS using regex + * @param {string} css - The CSS string to analyze + * @returns {Array} Array of content-visibility values found + */ + function extractContentVisibilityValues(css) { + if (!css || typeof css !== 'string') { + return []; + } + + const contentVisibilityValues = []; + + // Remove CSS comments first + css = css.replace(/\/\*[\s\S]*?\*\//g, ''); + + // Regex to match content-visibility property declarations + // Matches: content-visibility: value; or content-visibility: value + const contentVisibilityRegex = /content-visibility\s*:\s*([^;}\s]+(?:\s+[^;}\s]+)*)/gi; + let regexMatch; + + while ((regexMatch = contentVisibilityRegex.exec(css)) !== null) { + const value = regexMatch[1].trim(); + if (value) { + contentVisibilityValues.push(value); + } + } + + return contentVisibilityValues; + } + + /** + * Optimized function to get unique values without Set + * @param {Array} values - Array of values + * @returns {Array} Array of unique values + */ + function getUniqueValues(values) { + const unique = []; + const seen = {}; + + for (let valueIndex = 0; valueIndex < values.length; valueIndex++) { + const value = values[valueIndex]; + if (!seen[value]) { + seen[value] = true; + unique.push(value); + } + } + + return unique; + } + + /** + * Custom metric to analyze content-visibility usage with optimizations + * @returns {Object} Content visibility analysis results + */ + function contentVisibility() { + const contentVisibilityValues = []; + + // Process stylesheets first (usually largest source) + const stylesheets = $WPT_BODIES.filter(body => body.type === 'Stylesheet'); + for (let stylesheetIndex = 0; stylesheetIndex < stylesheets.length; stylesheetIndex++) { + const stylesheet = stylesheets[stylesheetIndex]; + if (stylesheet.response_body) { + const values = extractContentVisibilityValues(stylesheet.response_body); + contentVisibilityValues.push(...values); + } + } + + // Process style blocks (usually fewer elements) + const styleElements = document.querySelectorAll('style'); + for (let styleIndex = 0; styleIndex < styleElements.length; styleIndex++) { + const styleElement = styleElements[styleIndex]; + if (styleElement.innerHTML) { + const values = extractContentVisibilityValues(styleElement.innerHTML); + contentVisibilityValues.push(...values); + } + } + + // Process inline styles (most expensive - limit scope if possible) + // Only process if we haven't found any content-visibility yet + if (contentVisibilityValues.length === 0) { + const elementsWithStyle = document.querySelectorAll('[style]'); + for (let elementIndex = 0; elementIndex < elementsWithStyle.length; elementIndex++) { + const elementWithStyle = elementsWithStyle[elementIndex]; + const styleAttr = elementWithStyle.getAttribute('style'); + if (styleAttr && styleAttr.includes('content-visibility')) { + const values = extractContentVisibilityValues(styleAttr); + contentVisibilityValues.push(...values); + } + } + } + + return { + used: contentVisibilityValues.length > 0, + count: contentVisibilityValues.length, + values: contentVisibilityValues, + uniqueValues: getUniqueValues(contentVisibilityValues) + }; + } + + return contentVisibility(); +})(); \ No newline at end of file diff --git a/dist/css.js b/dist/css.js index 63894af5..61ae04df 100644 --- a/dist/css.js +++ b/dist/css.js @@ -75,38 +75,4 @@ return JSON.stringify({ externalCssInBody: countExternalCssInBody(), inlineCssInHead: countInlineCssInHead(), inlineCssInBody: countInlineCssInBody(), - - content_visibility: (() => { - // Detects elements using the content-visibility CSS property - const elements = document.querySelectorAll('*'); - const contentVisibilityElements = []; - const contentVisibilityValues = {}; - - for (const element of elements) { - try { - const computedStyle = getComputedStyle(element); - const contentVisibility = computedStyle.getPropertyValue('content-visibility'); - - if (contentVisibility && contentVisibility !== 'visible') { - contentVisibilityElements.push({ - tagName: element.tagName.toLowerCase(), - contentVisibility: contentVisibility.trim(), - className: element.className || '', - id: element.id || '' - }); - - const value = contentVisibility.trim(); - contentVisibilityValues[value] = (contentVisibilityValues[value] || 0) + 1; - } - } catch (e) { - // continue regardless of error - } - } - - return { - total: contentVisibilityElements.length, - elements: contentVisibilityElements, - values: contentVisibilityValues - }; - })() }); diff --git a/metric-summary.md b/metric-summary.md index ab6b606b..87999428 100644 --- a/metric-summary.md +++ b/metric-summary.md @@ -723,35 +723,33 @@ Example response: [5, 12, 11] ``` -## [css.js](https://github.com/HTTPArchive/custom-metrics/blob/main/dist/css.js) metrics +## [content-visibility.js](https://github.com/HTTPArchive/custom-metrics/blob/main/dist/content-visibility.js) metrics ### content_visibility +Detects CSS rules using the `content-visibility` property for performance optimization analysis. This metric uses efficient regex-based detection with performance optimizations to find content-visibility declarations in stylesheets, style blocks, and inline styles. Returns information about CSS rules that have content-visibility set to values other than 'visible' (such as 'auto', 'hidden', or 'skip'). -Detects elements using the `content-visibility` CSS property for performance optimization analysis. Returns information about elements that have content-visibility set to values other than 'visible' (such as 'auto', 'hidden', or 'skip'). +**Performance Optimizations:** +- Early exit for inline styles when content-visibility is found in stylesheets/style blocks +- Pre-filtering of inline styles to avoid unnecessary regex processing Example response: ```json { - "total": 15, - "elements": [ - { - "tagName": "div", - "contentVisibility": "auto", - "className": "lazy-section", - "id": "section-1" - }, - { - "tagName": "section", - "contentVisibility": "hidden", - "className": "hidden-content", - "id": "" - } + "used": true, + "count": 5, + "values": [ + "auto", + "hidden", + "skip", + "auto", + "hidden" ], - "values": { - "auto": 12, - "hidden": 3 - } + "uniqueValues": [ + "auto", + "hidden", + "skip" + ] } ``` From 6d19ce8c848c9f72ff90c806ecda74b585281899 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20G=C3=BCneli?= Date: Fri, 4 Jul 2025 10:49:14 +0200 Subject: [PATCH 4/9] Fix linter issues --- dist/content-visibility.js | 4 ++-- metric-summary.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/dist/content-visibility.js b/dist/content-visibility.js index 7265faf3..024661f3 100644 --- a/dist/content-visibility.js +++ b/dist/content-visibility.js @@ -1,6 +1,6 @@ /** * Content Visibility Custom Metric - Optimized Version - * + * * Analyzes CSS usage of the content-visibility property using efficient * regex-based detection with performance optimizations. */ @@ -108,4 +108,4 @@ } return contentVisibility(); -})(); \ No newline at end of file +})(); diff --git a/metric-summary.md b/metric-summary.md index 87999428..ed8c88c5 100644 --- a/metric-summary.md +++ b/metric-summary.md @@ -740,7 +740,7 @@ Example response: "count": 5, "values": [ "auto", - "hidden", + "hidden", "skip", "auto", "hidden" From cad76c8fe69e93549b5c6569933ee78fe4a875ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20G=C3=BCneli?= Date: Fri, 4 Jul 2025 11:31:44 +0200 Subject: [PATCH 5/9] Add debugging --- dist/content-visibility.js | 151 +++++++++++++++++++++++++------------ 1 file changed, 101 insertions(+), 50 deletions(-) diff --git a/dist/content-visibility.js b/dist/content-visibility.js index 024661f3..71fbb0ef 100644 --- a/dist/content-visibility.js +++ b/dist/content-visibility.js @@ -20,19 +20,24 @@ const contentVisibilityValues = []; - // Remove CSS comments first - css = css.replace(/\/\*[\s\S]*?\*\//g, ''); - - // Regex to match content-visibility property declarations - // Matches: content-visibility: value; or content-visibility: value - const contentVisibilityRegex = /content-visibility\s*:\s*([^;}\s]+(?:\s+[^;}\s]+)*)/gi; - let regexMatch; - - while ((regexMatch = contentVisibilityRegex.exec(css)) !== null) { - const value = regexMatch[1].trim(); - if (value) { - contentVisibilityValues.push(value); + try { + // Remove CSS comments first + css = css.replace(/\/\*[\s\S]*?\*\//g, ''); + + // Regex to match content-visibility property declarations + // Matches: content-visibility: value; or content-visibility: value + const contentVisibilityRegex = /content-visibility\s*:\s*([^;}\s]+(?:\s+[^;}\s]+)*)/gi; + let regexMatch; + + while ((regexMatch = contentVisibilityRegex.exec(css)) !== null) { + const value = regexMatch[1].trim(); + if (value) { + contentVisibilityValues.push(value); + } } + } catch (error) { + // Return empty array if regex processing fails + return []; } return contentVisibilityValues; @@ -47,12 +52,17 @@ const unique = []; const seen = {}; - for (let valueIndex = 0; valueIndex < values.length; valueIndex++) { - const value = values[valueIndex]; - if (!seen[value]) { - seen[value] = true; - unique.push(value); + try { + for (let valueIndex = 0; valueIndex < values.length; valueIndex++) { + const value = values[valueIndex]; + if (!seen[value]) { + seen[value] = true; + unique.push(value); + } } + } catch (error) { + // Return original array if deduplication fails + return values; } return unique; @@ -64,47 +74,88 @@ */ function contentVisibility() { const contentVisibilityValues = []; + let debugInfo = { + stylesheetsProcessed: 0, + styleBlocksProcessed: 0, + inlineStylesProcessed: 0, + errors: [] + }; - // Process stylesheets first (usually largest source) - const stylesheets = $WPT_BODIES.filter(body => body.type === 'Stylesheet'); - for (let stylesheetIndex = 0; stylesheetIndex < stylesheets.length; stylesheetIndex++) { - const stylesheet = stylesheets[stylesheetIndex]; - if (stylesheet.response_body) { - const values = extractContentVisibilityValues(stylesheet.response_body); - contentVisibilityValues.push(...values); + try { + // Process stylesheets first (usually largest source) + if (typeof $WPT_BODIES !== 'undefined' && Array.isArray($WPT_BODIES)) { + const stylesheets = $WPT_BODIES.filter(body => body.type === 'Stylesheet'); + debugInfo.stylesheetsProcessed = stylesheets.length; + + for (let stylesheetIndex = 0; stylesheetIndex < stylesheets.length; stylesheetIndex++) { + const stylesheet = stylesheets[stylesheetIndex]; + if (stylesheet && stylesheet.response_body) { + const values = extractContentVisibilityValues(stylesheet.response_body); + contentVisibilityValues.push(...values); + } + } } - } - // Process style blocks (usually fewer elements) - const styleElements = document.querySelectorAll('style'); - for (let styleIndex = 0; styleIndex < styleElements.length; styleIndex++) { - const styleElement = styleElements[styleIndex]; - if (styleElement.innerHTML) { - const values = extractContentVisibilityValues(styleElement.innerHTML); - contentVisibilityValues.push(...values); + // Process style blocks (usually fewer elements) + if (typeof document !== 'undefined' && document.querySelectorAll) { + try { + const styleElements = document.querySelectorAll('style'); + debugInfo.styleBlocksProcessed = styleElements.length; + + for (let styleIndex = 0; styleIndex < styleElements.length; styleIndex++) { + const styleElement = styleElements[styleIndex]; + if (styleElement && styleElement.innerHTML) { + const values = extractContentVisibilityValues(styleElement.innerHTML); + contentVisibilityValues.push(...values); + } + } + } catch (error) { + debugInfo.errors.push('style_blocks_error: ' + error.message); + } } - } - // Process inline styles (most expensive - limit scope if possible) - // Only process if we haven't found any content-visibility yet - if (contentVisibilityValues.length === 0) { - const elementsWithStyle = document.querySelectorAll('[style]'); - for (let elementIndex = 0; elementIndex < elementsWithStyle.length; elementIndex++) { - const elementWithStyle = elementsWithStyle[elementIndex]; - const styleAttr = elementWithStyle.getAttribute('style'); - if (styleAttr && styleAttr.includes('content-visibility')) { - const values = extractContentVisibilityValues(styleAttr); - contentVisibilityValues.push(...values); + // Process inline styles (most expensive - limit scope if possible) + // Only process if we haven't found any content-visibility yet + if (contentVisibilityValues.length === 0 && typeof document !== 'undefined' && document.querySelectorAll) { + try { + const elementsWithStyle = document.querySelectorAll('[style]'); + debugInfo.inlineStylesProcessed = elementsWithStyle.length; + + for (let elementIndex = 0; elementIndex < elementsWithStyle.length; elementIndex++) { + const elementWithStyle = elementsWithStyle[elementIndex]; + if (elementWithStyle) { + const styleAttr = elementWithStyle.getAttribute('style'); + if (styleAttr && styleAttr.includes('content-visibility')) { + const values = extractContentVisibilityValues(styleAttr); + contentVisibilityValues.push(...values); + } + } + } + } catch (error) { + debugInfo.errors.push('inline_styles_error: ' + error.message); } } - } - return { - used: contentVisibilityValues.length > 0, - count: contentVisibilityValues.length, - values: contentVisibilityValues, - uniqueValues: getUniqueValues(contentVisibilityValues) - }; + return { + used: contentVisibilityValues.length > 0, + count: contentVisibilityValues.length, + values: contentVisibilityValues, + uniqueValues: getUniqueValues(contentVisibilityValues), + debug: debugInfo + }; + } catch (error) { + // Return a safe fallback if the entire function fails + return { + used: false, + count: 0, + values: [], + uniqueValues: [], + debug: { + ...debugInfo, + errors: [...debugInfo.errors, 'main_error: ' + error.message] + } + }; + } } return contentVisibility(); From f7522aa120c3b2e3e7b60d0bab7f8e4d0185761b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20G=C3=BCneli?= Date: Fri, 4 Jul 2025 16:43:24 +0200 Subject: [PATCH 6/9] Add try catch --- dist/content-visibility.js | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/dist/content-visibility.js b/dist/content-visibility.js index 71fbb0ef..c4941e67 100644 --- a/dist/content-visibility.js +++ b/dist/content-visibility.js @@ -158,5 +158,22 @@ } } - return contentVisibility(); + // Try to execute the metric, with ultimate fallback + try { + return contentVisibility(); + } catch (error) { + // Ultimate fallback - if even the main function fails + return { + used: false, + count: 0, + values: [], + uniqueValues: [], + debug: { + stylesheetsProcessed: 0, + styleBlocksProcessed: 0, + inlineStylesProcessed: 0, + errors: ['ultimate_error: ' + error.message] + } + }; + } })(); From a4f74679003c3e2a356b5941c0e56989699b0ba2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20G=C3=BCneli?= Date: Fri, 4 Jul 2025 16:53:59 +0200 Subject: [PATCH 7/9] Return data JSON-stringified --- dist/content-visibility.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/dist/content-visibility.js b/dist/content-visibility.js index c4941e67..c8d4d7ff 100644 --- a/dist/content-visibility.js +++ b/dist/content-visibility.js @@ -160,10 +160,11 @@ // Try to execute the metric, with ultimate fallback try { - return contentVisibility(); + const result = contentVisibility(); + return JSON.stringify(result); } catch (error) { // Ultimate fallback - if even the main function fails - return { + const fallbackResult = { used: false, count: 0, values: [], @@ -175,5 +176,6 @@ errors: ['ultimate_error: ' + error.message] } }; + return JSON.stringify(fallbackResult); } })(); From 7faf74308261474d881fc1d70e35fe92dd1017f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20G=C3=BCneli?= Date: Sat, 5 Jul 2025 16:57:18 +0200 Subject: [PATCH 8/9] Use parsed_css --- dist/content-visibility.js | 233 +++++++++++-------------------------- 1 file changed, 65 insertions(+), 168 deletions(-) diff --git a/dist/content-visibility.js b/dist/content-visibility.js index c8d4d7ff..ae42e4b4 100644 --- a/dist/content-visibility.js +++ b/dist/content-visibility.js @@ -1,181 +1,78 @@ -/** - * Content Visibility Custom Metric - Optimized Version - * - * Analyzes CSS usage of the content-visibility property using efficient - * regex-based detection with performance optimizations. - */ - -//[content-visibility] - -(() => { - /** - * Extract content-visibility declarations from CSS using regex - * @param {string} css - The CSS string to analyze - * @returns {Array} Array of content-visibility values found - */ - function extractContentVisibilityValues(css) { - if (!css || typeof css !== 'string') { - return []; - } - - const contentVisibilityValues = []; - - try { - // Remove CSS comments first - css = css.replace(/\/\*[\s\S]*?\*\//g, ''); - - // Regex to match content-visibility property declarations - // Matches: content-visibility: value; or content-visibility: value - const contentVisibilityRegex = /content-visibility\s*:\s*([^;}\s]+(?:\s+[^;}\s]+)*)/gi; - let regexMatch; - - while ((regexMatch = contentVisibilityRegex.exec(css)) !== null) { - const value = regexMatch[1].trim(); - if (value) { - contentVisibilityValues.push(value); - } - } - } catch (error) { - // Return empty array if regex processing fails - return []; - } +//[content_visibility] +// Uncomment the previous line for testing on webpagetest.org +function extractContentVisibilityFromAST(ast) { + const contentVisibilityValues = []; + + if (!ast || !Array.isArray(ast)) { return contentVisibilityValues; } - - /** - * Optimized function to get unique values without Set - * @param {Array} values - Array of values - * @returns {Array} Array of unique values - */ - function getUniqueValues(values) { - const unique = []; - const seen = {}; - - try { - for (let valueIndex = 0; valueIndex < values.length; valueIndex++) { - const value = values[valueIndex]; - if (!seen[value]) { - seen[value] = true; - unique.push(value); - } - } - } catch (error) { - // Return original array if deduplication fails - return values; - } - - return unique; - } - - /** - * Custom metric to analyze content-visibility usage with optimizations - * @returns {Object} Content visibility analysis results - */ - function contentVisibility() { - const contentVisibilityValues = []; - let debugInfo = { - stylesheetsProcessed: 0, - styleBlocksProcessed: 0, - inlineStylesProcessed: 0, - errors: [] - }; - - try { - // Process stylesheets first (usually largest source) - if (typeof $WPT_BODIES !== 'undefined' && Array.isArray($WPT_BODIES)) { - const stylesheets = $WPT_BODIES.filter(body => body.type === 'Stylesheet'); - debugInfo.stylesheetsProcessed = stylesheets.length; - - for (let stylesheetIndex = 0; stylesheetIndex < stylesheets.length; stylesheetIndex++) { - const stylesheet = stylesheets[stylesheetIndex]; - if (stylesheet && stylesheet.response_body) { - const values = extractContentVisibilityValues(stylesheet.response_body); - contentVisibilityValues.push(...values); - } - } - } - - // Process style blocks (usually fewer elements) - if (typeof document !== 'undefined' && document.querySelectorAll) { - try { - const styleElements = document.querySelectorAll('style'); - debugInfo.styleBlocksProcessed = styleElements.length; - - for (let styleIndex = 0; styleIndex < styleElements.length; styleIndex++) { - const styleElement = styleElements[styleIndex]; - if (styleElement && styleElement.innerHTML) { - const values = extractContentVisibilityValues(styleElement.innerHTML); - contentVisibilityValues.push(...values); - } - } - } catch (error) { - debugInfo.errors.push('style_blocks_error: ' + error.message); + + const rulesToProcess = [...ast]; + + while (rulesToProcess.length > 0) { + const rule = rulesToProcess.shift(); + + if (rule.type === 'rule' && rule.declarations) { + // Process regular CSS rules + for (const declaration of rule.declarations) { + if (declaration.type === 'declaration' && + declaration.property && + declaration.property.toLowerCase() === 'content-visibility' && + declaration.value) { + contentVisibilityValues.push(declaration.value.trim()); } } - - // Process inline styles (most expensive - limit scope if possible) - // Only process if we haven't found any content-visibility yet - if (contentVisibilityValues.length === 0 && typeof document !== 'undefined' && document.querySelectorAll) { - try { - const elementsWithStyle = document.querySelectorAll('[style]'); - debugInfo.inlineStylesProcessed = elementsWithStyle.length; - - for (let elementIndex = 0; elementIndex < elementsWithStyle.length; elementIndex++) { - const elementWithStyle = elementsWithStyle[elementIndex]; - if (elementWithStyle) { - const styleAttr = elementWithStyle.getAttribute('style'); - if (styleAttr && styleAttr.includes('content-visibility')) { - const values = extractContentVisibilityValues(styleAttr); - contentVisibilityValues.push(...values); - } + } else if (rule.type === 'media' && rule.rules) { + rulesToProcess.push(...rule.rules); + } else if (rule.type === 'supports' && rule.rules) { + rulesToProcess.push(...rule.rules); + } else if (rule.type === 'keyframes' && rule.keyframes) { + for (const keyframe of rule.keyframes) { + if (keyframe.declarations) { + for (const declaration of keyframe.declarations) { + if (declaration.type === 'declaration' && + declaration.property && + declaration.property.toLowerCase() === 'content-visibility' && + declaration.value) { + contentVisibilityValues.push(declaration.value.trim()); } } - } catch (error) { - debugInfo.errors.push('inline_styles_error: ' + error.message); } } - - return { - used: contentVisibilityValues.length > 0, - count: contentVisibilityValues.length, - values: contentVisibilityValues, - uniqueValues: getUniqueValues(contentVisibilityValues), - debug: debugInfo - }; - } catch (error) { - // Return a safe fallback if the entire function fails - return { - used: false, - count: 0, - values: [], - uniqueValues: [], - debug: { - ...debugInfo, - errors: [...debugInfo.errors, 'main_error: ' + error.message] - } - }; } } + + return contentVisibilityValues; +} + +function getUniqueValues(values) { + const unique = []; + const seen = {}; + for (let i = 0; i < values.length; i++) { + const value = values[i]; + if (!seen[value]) { + seen[value] = true; + unique.push(value); + } + } + return unique; +} - // Try to execute the metric, with ultimate fallback - try { - const result = contentVisibility(); - return JSON.stringify(result); - } catch (error) { - // Ultimate fallback - if even the main function fails - const fallbackResult = { - used: false, - count: 0, - values: [], - uniqueValues: [], - debug: { - stylesheetsProcessed: 0, - styleBlocksProcessed: 0, - inlineStylesProcessed: 0, - errors: ['ultimate_error: ' + error.message] - } - }; - return JSON.stringify(fallbackResult); +const contentVisibilityValues = []; + +if (typeof parsed_css !== 'undefined' && Array.isArray(parsed_css)) { + for (const cssData of parsed_css) { + if (cssData && cssData.ast) { + const values = extractContentVisibilityFromAST(cssData.ast); + contentVisibilityValues.push(...values); + } } -})(); +} + +return { + used: contentVisibilityValues.length > 0, + count: contentVisibilityValues.length, + values: contentVisibilityValues, + uniqueValues: getUniqueValues(contentVisibilityValues) +}; From 9f7e264e72fc94441653bc9a625dd87607c0c3cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Burak=20G=C3=BCneli?= Date: Sat, 5 Jul 2025 17:07:02 +0200 Subject: [PATCH 9/9] Rename file to content_visibility --- ...ent-visibility.js => content_visibility.js} | 18 +++++++++--------- metric-summary.md | 2 +- 2 files changed, 10 insertions(+), 10 deletions(-) rename dist/{content-visibility.js => content_visibility.js} (91%) diff --git a/dist/content-visibility.js b/dist/content_visibility.js similarity index 91% rename from dist/content-visibility.js rename to dist/content_visibility.js index ae42e4b4..77ea7b7d 100644 --- a/dist/content-visibility.js +++ b/dist/content_visibility.js @@ -3,21 +3,21 @@ function extractContentVisibilityFromAST(ast) { const contentVisibilityValues = []; - + if (!ast || !Array.isArray(ast)) { return contentVisibilityValues; } - + const rulesToProcess = [...ast]; - + while (rulesToProcess.length > 0) { const rule = rulesToProcess.shift(); - + if (rule.type === 'rule' && rule.declarations) { // Process regular CSS rules for (const declaration of rule.declarations) { - if (declaration.type === 'declaration' && - declaration.property && + if (declaration.type === 'declaration' && + declaration.property && declaration.property.toLowerCase() === 'content-visibility' && declaration.value) { contentVisibilityValues.push(declaration.value.trim()); @@ -31,8 +31,8 @@ function extractContentVisibilityFromAST(ast) { for (const keyframe of rule.keyframes) { if (keyframe.declarations) { for (const declaration of keyframe.declarations) { - if (declaration.type === 'declaration' && - declaration.property && + if (declaration.type === 'declaration' && + declaration.property && declaration.property.toLowerCase() === 'content-visibility' && declaration.value) { contentVisibilityValues.push(declaration.value.trim()); @@ -42,7 +42,7 @@ function extractContentVisibilityFromAST(ast) { } } } - + return contentVisibilityValues; } diff --git a/metric-summary.md b/metric-summary.md index ed8c88c5..3845711c 100644 --- a/metric-summary.md +++ b/metric-summary.md @@ -723,7 +723,7 @@ Example response: [5, 12, 11] ``` -## [content-visibility.js](https://github.com/HTTPArchive/custom-metrics/blob/main/dist/content-visibility.js) metrics +## [content_visibility.js](https://github.com/HTTPArchive/custom-metrics/blob/main/dist/content_visibility.js) metrics ### content_visibility Detects CSS rules using the `content-visibility` property for performance optimization analysis. This metric uses efficient regex-based detection with performance optimizations to find content-visibility declarations in stylesheets, style blocks, and inline styles. Returns information about CSS rules that have content-visibility set to values other than 'visible' (such as 'auto', 'hidden', or 'skip').