Skip to content

Commit 79f0fef

Browse files
committed
add resource timing api & co
1 parent 97c8825 commit 79f0fef

11 files changed

+529
-75
lines changed

docs/assets/index.e17f1f40.js

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/assets/index.f5baa570.js

-1
This file was deleted.

docs/assets/vendor.dfdf208d.js docs/assets/vendor.0d07671f.js

+11-6
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/index.html

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@
1111
break-inside: avoid;
1212
}
1313
</style>
14-
<script type="module" crossorigin src="assets/index.f5baa570.js"></script>
15-
<link rel="modulepreload" href="assets/vendor.dfdf208d.js">
14+
<script type="module" crossorigin src="assets/index.e17f1f40.js"></script>
15+
<link rel="modulepreload" href="assets/vendor.0d07671f.js">
1616
<link rel="stylesheet" href="assets/index.f44316bf.css">
1717
</head>
1818
<body>

tester/package.json

+4
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,14 @@
66
"serve": "vite preview"
77
},
88
"dependencies": {
9+
"@chakra-ui/icons": "^1.0.13",
910
"@chakra-ui/react": "^1.6.3",
1011
"@emotion/react": "11",
1112
"@emotion/styled": "11",
1213
"@react-three/fiber": "^6.2.3",
14+
"@visx/group": "^1.7.0",
15+
"@visx/shape": "^1.12.0",
16+
"@visx/text": "^1.10.0",
1317
"chart.js": "^3.3.2",
1418
"copy-to-clipboard": "^3.3.1",
1519
"framer-motion": "4",

tester/src/App.jsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import DeviceSensors from "./testers/DeviceSensors";
1515
import base64 from "./utils/base64";
1616
import MediaDevices from "./testers/MediaDevices";
1717
import EncryptedMediaExtensions from "./testers/EncryptedMediaExtensions";
18+
import ResourceTiming from "./testers/ResourceTiming";
1819

1920
const AppPersisted = () => {
2021
const dispatch = useDispatch();
@@ -65,7 +66,8 @@ const App = () => {
6566

6667
<Container maxW="container.xl">
6768
<Box w="100%" sx={{ columnCount: window.outerWidth > 500 ? 2 : 1, columnGap: "24px" }}>
68-
<EncryptedMediaExtensions />
69+
{/*<EncryptedMediaExtensions />*/}
70+
<ResourceTiming />
6971
<BasicInformation />
7072
<MediaDevices />
7173

tester/src/testers/BasicInformation.jsx

+43-24
Original file line numberDiff line numberDiff line change
@@ -27,34 +27,53 @@ const probeStackLimit = async () => {
2727
let accessor = 'window.parent';
2828
let p = 0;
2929
while (true) {
30-
p += 50;
30+
p += 500;
3131
try {
3232
eval(accessor);
3333
} catch (err) {
3434
break;
3535
}
36-
for (let i=0; i<50; i++) {
36+
for (let i=0; i<500; i++) {
3737
accessor += '.parent';
3838
}
3939
await new Promise((resolve) => setTimeout(resolve, 50)); // helps to prevent early freeze/crash
4040
}
4141
return p;
4242
}
4343

44+
const getConnectionInformation = async () => {
45+
const connection = await (navigator.connection || navigator.mozConnection || navigator.webkitConnection);
46+
if (!connection) return {};
47+
return {
48+
effectiveType: connection.effectiveType,
49+
saveData: connection.saveData,
50+
rtt: connection.rtt,
51+
downlink: connection.downlink,
52+
}
53+
}
54+
4455
const BasicInformation = ({ fn, value }) => {
4556
fn(async () => {
4657
const devtools = devToolsOpened();
4758
const stackLimit = await probeStackLimit();
59+
const connection = await getConnectionInformation();
4860
return {
49-
"deviceMemory": navigator.deviceMemory,
50-
"hardwareConcurrency": navigator.hardwareConcurrency,
51-
"window.innerHeight": window.innerHeight,
52-
"window.innerWidth": window.innerWidth,
53-
"window.outerHeight": window.outerHeight,
54-
"window.outerWidth": window.outerWidth,
55-
"devTools.isOpen": devtools.isOpen,
56-
"devTools.orientation": devtools.orientation,
57-
"stackLimit": stackLimit,
61+
navigator: {
62+
deviceMemory: navigator.deviceMemory,
63+
hardwareConcurrency: navigator.hardwareConcurrency,
64+
},
65+
performance: {
66+
"jsHeapSizeLimit": performance.memory.jsHeapSizeLimit,
67+
},
68+
stackLimit: stackLimit,
69+
window: {
70+
innerHeight: window.innerHeight,
71+
innerWidth: window.innerWidth,
72+
outerHeight: window.outerHeight,
73+
outerWidth: window.outerWidth,
74+
},
75+
devtools,
76+
connection,
5877
};
5978
});
6079

@@ -68,28 +87,28 @@ const BasicInformation = ({ fn, value }) => {
6887
<Text fontSize="sm" mb={2}>
6988
Available hardware details:
7089
</Text>
71-
<DictToTable dict={value} limitKeys={["deviceMemory", "hardwareConcurrency", "stackLimit"]} />
90+
<DictToTable dict={value} limitKeys={["navigator", "stackLimit", "performance"]} />
91+
</Box>
92+
</GridItem>
93+
<GridItem>
94+
<Box mb={4}>
95+
<Text fontSize="sm" mb={2}>
96+
Window dimensions:
97+
</Text>
98+
<DictToTable dict={value} limitKeys={["window"]} />
7299
</Box>
73100
<Box>
74101
<Text>
75102
<Code>devTools</Code> opened? {value['devTools.isOpen'] ? "✔️" : "❌"} {value['devTools.orientation'] && `(${value['devTools.orientation']})`}
76103
</Text>
77104
</Box>
78105
</GridItem>
79-
<GridItem>
80-
<Text fontSize="sm" mb={2}>
81-
Window dimensions:
82-
</Text>
83-
<DictToTable dict={value} limitKeys={[
84-
"window.innerWidth",
85-
"window.innerHeight",
86-
"window.outerHeight",
87-
"window.outerWidth",
88-
]} />
89-
90-
</GridItem>
91106
</SimpleGrid>
92107

108+
<Text fontSize="sm" mb={2} mt={2}>
109+
Connection information:
110+
</Text>
111+
<DictToTable dict={value.connection} limitKeys={["downlink", "rtt", "effectiveType", "saveData"]} />
93112
<Divider my={4} />
94113
<Text fontSize="xs">
95114
DevTools information are based on window sizing (borrowed from <Link color="teal.500" href="https://github.com/sindresorhus/devtools-detect/blob/main/index.js">sindresorhus/devtools-detect</Link>)

tester/src/testers/DocumentStatus.jsx

+9-5
Original file line numberDiff line numberDiff line change
@@ -5,13 +5,17 @@ import {DictToTable} from "./utils";
55

66
const DocumentStatus = ({ fn, value }) => {
77
fn(() => ({
8-
hasFocus: document.hasFocus(),
9-
compatMode: document.compatMode,
10-
documentURI: document.documentURI,
11-
designMode: document.designMode,
8+
document: {
9+
hasFocus: document.hasFocus(),
10+
compatMode: document.compatMode,
11+
documentURI: document.documentURI,
12+
designMode: document.designMode,
13+
}
1214
}));
1315
return (
14-
<DictToTable dict={value} />
16+
<DictToTable dict={value} limitKeys={[
17+
"document"
18+
]} />
1519
);
1620
};
1721

tester/src/testers/ResourceTiming.jsx

+123
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
import tester from "./tester";
2+
import React from "react";
3+
import {Text, Code, Alert, Divider, Heading} from "@chakra-ui/react";
4+
import {DictToTable} from "./utils";
5+
import {Bar} from '@visx/shape';
6+
import {Group} from "@visx/group";
7+
import {Text as VText} from '@visx/text';
8+
9+
const TIMING_COLOR = {
10+
"resource": "#4834d4",
11+
"paint": "#eb4d4b",
12+
"mark": "#22a6b3",
13+
"navigation": "#6ab04c",
14+
}
15+
16+
const TimelineVisualisation = ({ data }) => {
17+
const timelineWrapper = React.useRef(undefined);
18+
const [svgDimensions, setSvgDimensions] = React.useState(undefined);
19+
20+
React.useEffect(() => {
21+
if (!timelineWrapper.current) return;
22+
const { clientWidth } = timelineWrapper.current;
23+
setSvgDimensions({ width: clientWidth, height: (10 * data.length) });
24+
}, [timelineWrapper]);
25+
26+
const normalizedData = React.useMemo(() => {
27+
if (!svgDimensions) return [];
28+
const { width } = svgDimensions;
29+
const minW = Math.min(...data.map((d) => d.startTime));
30+
const maxW = Math.max(...data.map((d) => d.startTime + d.duration));
31+
const W = maxW - minW;
32+
33+
return data.map((e) => {
34+
return {
35+
x: ((e.startTime - minW)/W) * width,
36+
width: !!e.duration ? ((e.duration / W) * width) : W,
37+
fill: TIMING_COLOR[e.entryType],
38+
name: e.name,
39+
};
40+
});
41+
}, [svgDimensions, data]);
42+
43+
return (
44+
<div ref={timelineWrapper} style={{ marginTop: '8px' }}>
45+
{svgDimensions && normalizedData && (
46+
<svg {...svgDimensions}>
47+
<Group>
48+
{normalizedData.map((e, idx) => (
49+
<>
50+
<Bar y={idx * 10} height={8} {...e} key={idx} />
51+
<VText {...e} x={e.x + e.width + 6} fill="gray" y={idx * 10 + 6.5} height={12} fontSize={6} width={undefined}>{e.name}</VText>
52+
</>
53+
))}
54+
</Group>
55+
</svg>
56+
)}
57+
</div>
58+
)
59+
}
60+
61+
const ResourceTiming = ({ fn, value }) => {
62+
fn(async () => {
63+
await new Promise((resolve) => setTimeout(resolve, 1000));
64+
const performanceEntries = window.performance.getEntries();
65+
const navigationTiming = performanceEntries.find((k) => k instanceof PerformanceNavigationTiming);
66+
return {
67+
navigationType: navigationTiming.type,
68+
encodedBodySize: navigationTiming.encodedBodySize,
69+
entriesCount: performanceEntries.length,
70+
domainLookupTime: navigationTiming.domainLookupEnd - navigationTiming.domainLookupStart,
71+
}
72+
});
73+
74+
const [timeline, setTimelineData] = React.useState([]);
75+
React.useEffect(() => {
76+
setTimeout(() => setTimelineData(window.performance.getEntries()), 3000);
77+
}, []);
78+
79+
if (!value) return null;
80+
81+
return (
82+
<>
83+
<Text my={4}>
84+
You entered this page by <Code>{value.navigationType}</Code> action. Encoded body size of this page is <Code>{value.encodedBodySize}B</Code> (this can vary by encoding supported by your browser).
85+
</Text>
86+
87+
{value.domainLookupTime < 1 && (
88+
<Alert status="info" mb={4}>
89+
<Text fontSize="sm" >
90+
You have very likely <strong>already visited</strong> this page because domain lookup time was under 1ms.
91+
</Text>
92+
</Alert>
93+
)}
94+
95+
<DictToTable dict={value} limitKeys={["entriesCount", "domainLookupTime", "encodedBodySize", "navigationType"]} />
96+
97+
{timeline.length > 0 && (
98+
<>
99+
<Divider my={4} />
100+
<Heading size="sm">
101+
Loading timeline
102+
</Heading>
103+
<Text fontSize="sm" mb={2}>
104+
This is visualisation of resource loading. It is not included in the fingerprint hash.
105+
</Text>
106+
107+
<TimelineVisualisation data={timeline} />
108+
</>
109+
)}
110+
</>
111+
);
112+
};
113+
114+
export default tester(ResourceTiming, {
115+
key: 'resourceTiming',
116+
title: "Resource Timing API",
117+
explainer: (
118+
<>
119+
Performance APIs can be useful to detect that proxy is in use by validating request timings. Data comes from <Code>window.performance</Code> scope.
120+
</>
121+
)
122+
});
123+

0 commit comments

Comments
 (0)