-
-
Notifications
You must be signed in to change notification settings - Fork 137
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge branch 'main' into disable-simulator
- Loading branch information
Showing
213 changed files
with
7,327 additions
and
795 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -7,4 +7,5 @@ | |
!browsertime | ||
!browserscripts | ||
!browsersupport | ||
!index.js | ||
!index.js | ||
!visualmetrics |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
313 changes: 170 additions & 143 deletions
313
browserscripts/pageinfo/interactionToNextPaintInfo.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,152 +1,179 @@ | ||
(function () { | ||
// https://github.com/GoogleChrome/web-vitals/blob/main/src/lib/getLoadState.ts#L20 | ||
|
||
// This is an updated version of | ||
// https://github.com/GoogleChrome/web-vitals/blob/64f133590fcac72c1bc042bf7b4ab729d7e03316/src/onINP.ts | ||
// It was reworked the 19/5-2023 | ||
function getLoadState(timestamp) { | ||
if (document.readyState === 'loading') { | ||
// If the `readyState` is 'loading' there's no need to look at timestamps | ||
// since the timestamp has to be the current time or earlier. | ||
return 'loading'; | ||
} else { | ||
const navigationEntry = performance.getEntriesByType('navigation')[0]; | ||
if (navigationEntry) { | ||
if (timestamp < navigationEntry.domInteractive) { | ||
return 'loading'; | ||
} else if ( | ||
navigationEntry.domContentLoadedEventStart === 0 || | ||
timestamp < navigationEntry.domContentLoadedEventStart | ||
) { | ||
// If the `domContentLoadedEventStart` timestamp has not yet been | ||
// set, or if the given timestamp is less than that value. | ||
return 'dom-interactive'; | ||
} else if ( | ||
navigationEntry.domComplete === 0 || | ||
timestamp < navigationEntry.domComplete | ||
) { | ||
// If the `domComplete` timestamp has not yet been | ||
// set, or if the given timestamp is less than that value. | ||
return 'dom-content-loaded'; | ||
} | ||
} | ||
} | ||
// If any of the above fail, default to loaded. This could really only | ||
// happy if the browser doesn't support the performance timeline, which | ||
// most likely means this code would never run anyway. | ||
return 'complete'; | ||
} | ||
|
||
const supported = PerformanceObserver.supportedEntryTypes; | ||
if (!supported || supported.indexOf('event') === -1 || supported.indexOf('first-input') === -1) { | ||
return; | ||
// https://github.com/GoogleChrome/web-vitals/blob/main/src/lib/getSelector.ts#L24 | ||
function getName(node) { | ||
const name = node.nodeName; | ||
return node.nodeType === 1 | ||
? name.toLowerCase() | ||
: name.toUpperCase().replace(/^#/, ''); | ||
} | ||
var observe = function observe(type, callback, opts) { | ||
try { | ||
if (PerformanceObserver.supportedEntryTypes.includes(type)) { | ||
var po = new PerformanceObserver(function (list) { | ||
Promise.resolve().then(function () { | ||
callback(list.getEntries()); | ||
}); | ||
}); | ||
po.observe(Object.assign({type: type, buffered: true}, opts || {})); | ||
return po; | ||
} | ||
} catch (e) {} | ||
return; | ||
}; | ||
var interactionCountEstimate = 0; | ||
var minKnownInteractionId = Infinity; | ||
var maxKnownInteractionId = 0; | ||
var updateEstimate = function updateEstimate(entries) { | ||
entries.forEach(function (e) { | ||
if (e.interactionId) { | ||
minKnownInteractionId = Math.min( | ||
minKnownInteractionId, | ||
e.interactionId | ||
); | ||
maxKnownInteractionId = Math.max( | ||
maxKnownInteractionId, | ||
e.interactionId | ||
); | ||
interactionCountEstimate = maxKnownInteractionId | ||
? (maxKnownInteractionId - minKnownInteractionId) / 7 + 1 | ||
: 0; | ||
} | ||
}); | ||
}; | ||
var po; | ||
var getInteractionCount = function getInteractionCount() { | ||
return po ? interactionCountEstimate : performance.interactionCount || 0; | ||
}; | ||
var initInteractionCountPolyfill = function initInteractionCountPolyfill() { | ||
if ('interactionCount' in performance || po) return; | ||
po = observe('event', updateEstimate, { | ||
type: 'event', | ||
buffered: true, | ||
durationThreshold: 0, | ||
}); | ||
}; | ||
var prevInteractionCount = 0; | ||
var getInteractionCountForNavigation = | ||
function getInteractionCountForNavigation() { | ||
return getInteractionCount() - prevInteractionCount; | ||
}; | ||
var MAX_INTERACTIONS_TO_CONSIDER = 10; | ||
var longestInteractionList = []; | ||
var longestInteractionMap = {}; | ||
var processEntry = function processEntry(entry) { | ||
var minLongestInteraction = | ||
longestInteractionList[longestInteractionList.length - 1]; | ||
var existingInteraction = longestInteractionMap[entry.interactionId]; | ||
if ( | ||
existingInteraction || | ||
longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER || | ||
entry.duration > minLongestInteraction.latency | ||
) { | ||
if (existingInteraction) { | ||
existingInteraction.entries.push(entry); | ||
existingInteraction.latency = Math.max( | ||
existingInteraction.latency, | ||
entry.duration | ||
); | ||
} else { | ||
var interaction = { | ||
id: entry.interactionId, | ||
latency: entry.duration, | ||
entries: [entry], | ||
}; | ||
longestInteractionMap[interaction.id] = interaction; | ||
longestInteractionList.push(interaction); | ||
} | ||
longestInteractionList.sort(function (a, b) { | ||
return b.latency - a.latency; | ||
}); | ||
longestInteractionList | ||
.splice(MAX_INTERACTIONS_TO_CONSIDER) | ||
.forEach(function (i) { | ||
delete longestInteractionMap[i.id]; | ||
}); | ||
|
||
function getSelector(node) { | ||
let sel = ''; | ||
|
||
try { | ||
while (node && node.nodeType !== 9) { | ||
const el = node; | ||
const part = el.id | ||
? '#' + el.id | ||
: getName(el) + | ||
(el.classList && | ||
el.classList.value && | ||
el.classList.value.trim() && | ||
el.classList.value.trim().length | ||
? '.' + el.classList.value.trim().replace(/\s+/g, '.') | ||
: ''); | ||
if (sel.length + part.length > 100 - 1) return sel || part; | ||
sel = sel ? part + '>' + sel : part; | ||
if (el.id) break; | ||
node = el.parentNode; | ||
} | ||
}; | ||
var estimateP98LongestInteraction = function estimateP98LongestInteraction() { | ||
var candidateInteractionIndex = Math.min( | ||
longestInteractionList.length - 1, | ||
Math.floor(getInteractionCountForNavigation() / 50) | ||
); | ||
return longestInteractionList[candidateInteractionIndex]; | ||
}; | ||
|
||
var opts = {}; | ||
var metric = {}; | ||
initInteractionCountPolyfill(); | ||
var handleEntries = function handleEntries(entries) { | ||
entries.forEach(function (entry) { | ||
if (entry.interactionId) { | ||
processEntry(entry); | ||
} | ||
if (entry.entryType === 'first-input') { | ||
var noMatchingEntry = !longestInteractionList.some(function ( | ||
interaction | ||
) { | ||
return interaction.entries.some(function (prevEntry) { | ||
return ( | ||
entry.duration === prevEntry.duration && | ||
entry.startTime === prevEntry.startTime | ||
); | ||
}); | ||
}); | ||
if (noMatchingEntry) { | ||
processEntry(entry); | ||
} catch (err) { | ||
// Do nothing... | ||
} | ||
return sel; | ||
} | ||
|
||
// https://gist.github.com/karlgroves/7544592 | ||
function getDomPath(el) { | ||
const stack = []; | ||
while (el.parentNode != null) { | ||
let sibCount = 0; | ||
let sibIndex = 0; | ||
for (let i = 0; i < el.parentNode.childNodes.length; i++) { | ||
let sib = el.parentNode.childNodes[i]; | ||
if (sib.nodeName == el.nodeName) { | ||
if (sib === el) { | ||
sibIndex = sibCount; | ||
} | ||
sibCount++; | ||
} | ||
}); | ||
var inp = estimateP98LongestInteraction(); | ||
if (inp && inp.latency !== metric.value) { | ||
var cleanedEntries = []; | ||
for (var entry of inp.entries) { | ||
console.log(entry); | ||
cleanedEntries.push({ | ||
id: entry.interactionId, | ||
latency: entry.duration, | ||
name: entry.name | ||
}); | ||
} | ||
return {latency: inp.latency, entries: cleanedEntries}; | ||
} | ||
}; | ||
var po = observe('event', handleEntries, { | ||
durationThreshold: opts.durationThreshold || 40, | ||
}); | ||
if (po) { | ||
po.observe({type: 'first-input', buffered: true}); | ||
if (el.hasAttribute && el.hasAttribute('id') && el.id != '') { | ||
stack.unshift(el.nodeName.toLowerCase() + '#' + el.id); | ||
} else if (sibCount > 1) { | ||
stack.unshift(el.nodeName.toLowerCase() + ':eq(' + sibIndex + ')'); | ||
} else { | ||
stack.unshift(el.nodeName.toLowerCase()); | ||
} | ||
el = el.parentNode; | ||
} | ||
|
||
return stack.slice(1); | ||
} | ||
|
||
// https://github.com/GoogleChrome/web-vitals/blob/main/src/onINP.ts | ||
const observer = new PerformanceObserver(list => {}); | ||
observer.observe({type: 'event', buffered: true}); | ||
observer.observe({type: 'first-input', buffered: true}); | ||
const entries = observer.takeRecords(); | ||
|
||
const MAX_INTERACTIONS_TO_CONSIDER = 10; | ||
const longestInteractionList = []; | ||
const longestInteractionMap = {}; | ||
for (let entry of entries) { | ||
var minLongestInteraction = | ||
longestInteractionList[longestInteractionList.length - 1]; | ||
var existingInteraction = longestInteractionMap[entry.interactionId]; | ||
if ( | ||
existingInteraction || | ||
longestInteractionList.length < MAX_INTERACTIONS_TO_CONSIDER || | ||
entry.duration > minLongestInteraction.latency | ||
) { | ||
if (existingInteraction) { | ||
existingInteraction.entries.push(entry); | ||
existingInteraction.latency = Math.max( | ||
existingInteraction.latency, | ||
entry.duration | ||
); | ||
} else { | ||
var interaction = { | ||
id: entry.interactionId, | ||
latency: entry.duration, | ||
entries: [entry] | ||
}; | ||
longestInteractionMap[interaction.id] = interaction; | ||
longestInteractionList.push(interaction); | ||
} | ||
longestInteractionList.sort(function (a, b) { | ||
return b.latency - a.latency; | ||
}); | ||
longestInteractionList | ||
.splice(MAX_INTERACTIONS_TO_CONSIDER) | ||
.forEach(function (i) { | ||
delete longestInteractionMap[i.id]; | ||
}); | ||
} | ||
} | ||
|
||
const inp = longestInteractionList[longestInteractionList.length - 1]; | ||
if (inp) { | ||
const cleanedEntries = []; | ||
|
||
for (let entry of inp.entries) { | ||
cleanedEntries.push({ | ||
id: entry.interactionId, | ||
latency: entry.duration, | ||
name: entry.name | ||
}); | ||
} | ||
})({}); | ||
|
||
|
||
const longestEntry = inp.entries.sort((a, b) => { | ||
// Sort by: 1) duration (DESC), then 2) processing time (DESC) | ||
return ( | ||
b.duration - a.duration || | ||
b.processingEnd - | ||
b.processingStart - | ||
(a.processingEnd - a.processingStart) | ||
); | ||
})[0]; | ||
let element = longestEntry.target; | ||
|
||
return { | ||
latency: inp.latency, | ||
entries: cleanedEntries, | ||
eventType: longestEntry.name, | ||
eventTime: longestEntry.startTime, | ||
eventTarget: getSelector(element), | ||
loadState: getLoadState(longestEntry.startTime), | ||
domPath: element ? getDomPath(element).join(' > ') : '', | ||
tagName: element ? element.tagName : '', | ||
className: element ? element.className : '', | ||
tag: element ? element.cloneNode(false).outerHTML : '' | ||
}; | ||
} | ||
})({}); |
Oops, something went wrong.