diff --git a/packages/trace-viewer/src/ui/networkResourceDetails.css b/packages/trace-viewer/src/ui/networkResourceDetails.css index 0914d065bb..a2d3f5a86f 100644 --- a/packages/trace-viewer/src/ui/networkResourceDetails.css +++ b/packages/trace-viewer/src/ui/networkResourceDetails.css @@ -48,6 +48,22 @@ overflow: hidden; } +.network-font-preview { + font-family: font-preview; + font-size: 30px; + line-height: 40px; + padding: 16px; + padding-left: 6px; + overflow: hidden; + text-overflow: ellipsis; + text-align: center; +} + +.network-font-preview-error { + margin-top: 8px; + text-align: center; +} + .tab-network .toolbar { min-height: 30px !important; background-color: initial !important; diff --git a/packages/trace-viewer/src/ui/networkResourceDetails.tsx b/packages/trace-viewer/src/ui/networkResourceDetails.tsx index 3a760c999f..3eb2f8ef73 100644 --- a/packages/trace-viewer/src/ui/networkResourceDetails.tsx +++ b/packages/trace-viewer/src/ui/networkResourceDetails.tsx @@ -101,12 +101,13 @@ const ResponseTab: React.FunctionComponent<{ const BodyTab: React.FunctionComponent<{ resource: ResourceSnapshot; }> = ({ resource }) => { - const [responseBody, setResponseBody] = React.useState<{ dataUrl?: string, text?: string, mimeType?: string } | null>(null); + const [responseBody, setResponseBody] = React.useState<{ dataUrl?: string, text?: string, mimeType?: string, font?: BinaryData } | null>(null); React.useEffect(() => { const readResources = async () => { if (resource.response.content._sha1) { const useBase64 = resource.response.content.mimeType.includes('image'); + const isFont = resource.response.content.mimeType.includes('font'); const response = await fetch(`sha1/${resource.response.content._sha1}`); if (useBase64) { const blob = await response.blob(); @@ -114,6 +115,9 @@ const BodyTab: React.FunctionComponent<{ const eventPromise = new Promise(f => reader.onload = f); reader.readAsDataURL(blob); setResponseBody({ dataUrl: (await eventPromise).target.result }); + } else if (isFont) { + const font = await response.arrayBuffer(); + setResponseBody({ font }); } else { const formattedBody = formatBody(await response.text(), resource.response.content.mimeType); setResponseBody({ text: formattedBody, mimeType: resource.response.content.mimeType }); @@ -128,11 +132,48 @@ const BodyTab: React.FunctionComponent<{ return
{!resource.response.content._sha1 &&
Response body is not available for this request.
} + {responseBody && responseBody.font && } {responseBody && responseBody.dataUrl && } {responseBody && responseBody.text && }
; }; +const FontPreview: React.FunctionComponent<{ + font: BinaryData; +}> = ({ font }) => { + const [isError, setIsError] = React.useState(false); + + React.useEffect(() => { + let fontFace: FontFace; + try { + // note: constant font family name will lead to bugs + // when displaying two font previews. + fontFace = new FontFace('font-preview', font); + if (fontFace.status === 'loaded') + document.fonts.add(fontFace); + if (fontFace.status === 'error') + setIsError(true); + } catch { + setIsError(true); + } + + return () => { + document.fonts.delete(fontFace); + }; + }, [font]); + + if (isError) + return
Could not load font preview
; + + return
+ ABCDEFGHIJKLM
+ NOPQRSTUVWXYZ
+ abcdefghijklm
+ nopqrstuvwxyz
+ 1234567890 +
; +}; + function statusClass(statusCode: number): string { if (statusCode < 300 || statusCode === 304) return 'green-circle';