100
Performance
100
Progressive Web App
100
Accessibility
100
Best Practices
100
SEO
Score scale:0-4445-7475-100
Lighthouse3.0.0-beta.0
Performance
Metrics
First Contentful Paint
1,380 ms
First contentful paint marks the time at which the first text/image is painted.Learn more.
Speed Index
1,380 ms
Speed Index shows how quickly the contents of a page are visibly populated.Learn more.
Time to Interactive
2,060 ms
Interactive marks the time at which the page is fully interactive.Learn more.
First Meaningful Paint
1,600 ms
First Meaningful Paint measures when the primary content of a page is visible.Learn more.
First CPU Idle
1,830 ms
First CPU Idle marks the first time at which the page's main thread is quiet enough to handle input.Learn more.
Estimated Input Latency
13 ms
The score above is an estimate of how long your app takes to respond to user input, in milliseconds, during the busiest 5s window of page load. If your latency is higher than 50 ms, users may perceive your app as laggy.Learn more.
Values are estimated and may vary.
Screenshot
Screenshot
Screenshot
Screenshot
Screenshot
Screenshot
Screenshot
Screenshot
Screenshot
Screenshot
Passed audits
22 audits
1Eliminate render-blocking resources
Resources are blocking the first paint of your page. Consider delivering critical JS/CSS inline and deferring all non-critical JS/styles.Learn more.
2Properly size images
Serve images that are appropriately-sized to save cellular data and improve load time.Learn more.
3Defer offscreen images
Consider lazy-loading offscreen and hidden images after all critical resources have finished loading to lower time to interactive.Learn more.
4Minify CSS
Minifying CSS files can reduce network payload sizes.Learn more.
5Minify JavaScript
Minifying JavaScript files can reduce payload sizes and script parse time.Learn more.
6Defer unused CSSPotential savings of 30 KB
Remove unused rules from stylesheets to reduce unnecessary bytes consumed by network activity.Learn more.
URL
Original
Potential Savings
\n.footer-icons li,.footer-icons li a,.footer-icons li a svg,.footer-icons li svg,.footer-icons-black ...
32 KB
30 KB
7Efficiently encode images
Optimized images load faster and consume less cellular data.Learn more.
8Serve images in next-gen formats
Image formats like JPEG 2000, JPEG XR, and WebP often provide better compression than PNG or JPEG, which means faster downloads and less data consumption.Learn more.
9Enable text compression
Text-based responses should be served with compression (gzip, deflate or brotli) to minimize total network bytes.Learn more.
10Avoid multiple, costly round trips to any originPotential savings of 0 ms
Consider adding preconnect or dns-prefetch resource hints to establish early connections to important third-party origins.Learn more.
11Keep server response times low (TTFB)
Time To First Byte identifies the time at which your server sends a response.Learn more.
12Avoid multiple page redirects0 ms
Redirects introduce additional delays before the page can be loaded.Learn more.
13Preload key requestsPotential savings of 0 ms
Consider using to prioritize fetching late-discovered resources sooner.Learn more.
14Use video formats for animated content
Large GIFs are inefficient for delivering animated content. Consider using MPEG4/WebM videos for animations and PNG/WebP for static images instead of GIF to save network bytes.Learn more
15Avoids enormous network payloadsTotal size was 64 KB
Large network payloads cost users real money and are highly correlated with long load times.Learn more.
URL
Total Size
Transfer Time
https://www.robertgabriel.ninja
35 KB
20 ms
…js/vendor.min.js
(www.robertgabriel.ninja)
23 KB
10 ms
…cloudflare-static/rocket-loader.min.js
(ajax.cloudflare.com)
3 KB
0 ms
…me/me-4.webp
(www.robertgabriel.ninja)
2 KB
0 ms
…js/app.min.js
(www.robertgabriel.ninja)
1 KB
0 ms
…js/app.min.js
(www.robertgabriel.ninja)
0 KB
0 ms
16Uses efficient cache policy on static assets1 asset found
A long cache lifetime can speed up repeat visits to your page.Learn more.
URL
Cache TTL
Size (KB)
…cloudflare-static/rocket-loader.min.js
(ajax.cloudflare.com)
1 d 23 h 59 m 53 s
3 KB
17Avoids an excessive DOM size184 nodes
Browser engineers recommend pages contain fewer than ~1,500 DOM nodes. The sweet spot is a tree depth < 32 elements and fewer than 60 children/parent element. A large DOM can increase memory usage, cause longerstyle calculations, and produce costlylayout reflows.Learn more.
Total DOM Nodes
Maximum DOM Depth
Maximum Children
184
11
39
\n
\n
18Critical Request Chains
The Critical Request Chains below show you what resources are issued with a high priority. Consider reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load.Learn more.
Longest chain:88.8ms over1 requests, totalling34.6 KB
Initial Navigation
/(www.robertgabriel.ninja) - 88.8ms,34.62 KB
19User Timing marks and measures
Consider instrumenting your app with the User Timing API to create custom, real-world measurements of key user experiences.Learn more.
20JavaScript boot-up time140 ms
Consider reducing the time spent parsing, compiling, and executing JS. You may find delivering smaller JS payloads helps with this.Learn more.
21Minimizes main thread work270 ms
Consider reducing the time spent parsing, compiling and executing JS. You may find delivering smaller JS payloads helps with this.
Category
Work
Time spent
Style & Layout
Layout
63 ms
Style & Layout
Recalculate Style
49 ms
Parsing HTML & CSS
Parse HTML
79 ms
Script Evaluation
Evaluate Script
42 ms
Script Evaluation
Run Microtasks
5 ms
Compositing
Update Layer Tree
11 ms
Compositing
Composite Layers
7 ms
Garbage collection
DOM GC
11 ms
Paint
Paint
7 ms
Script Parsing & Compile
Compile Script
1 ms
22All text remains visible during webfont loads
Leverage the font-display CSS feature to ensure text is user-visible while webfonts are loading.Learn more.
Progressive Web App
These checks validate the aspects of a Progressive Web App, as specified by the baselinePWA Checklist.
Additional items to manually check
3 audits
These checks are required by the baselinePWA Checklist but are not automatically checked by Lighthouse. They do not affect your score but it's important that you verify them manually.
1Site works cross-browser
To reach the most number of users, sites should work across every major browser.Learn more.
2Page transitions don't feel like they block on the network
Transitions should feel snappy as you tap around, even on a slow network, a key to perceived performance.Learn more.
3Each page has a URL
Ensure individual pages are deep linkable via the URLs and that URLs are unique for the purpose of shareability on social media.Learn more.
Passed audits
11 audits
1Registers a service worker
The service worker is the technology that enables your app to use many Progressive Web App features, such as offline, add to homescreen, and push notifications.Learn more.
2Responds with a 200 when offline
If you're building a Progressive Web App, consider using a service worker so that your app can work offline.Learn more.
3Contains some content when JavaScript is not available
Your app should display some content when JavaScript is disabled, even if it's just a warning to the user that JavaScript is required to use the app.Learn more.
4Uses HTTPS
All sites should be protected with HTTPS, even ones that don't handle sensitive data. HTTPS prevents intruders from tampering with or passively listening in on the communications between your app and your users, and is a prerequisite for HTTP/2 and many new web platform APIs.Learn more.
5Redirects HTTP traffic to HTTPS
If you've already set up HTTPS, make sure that you redirect all HTTP traffic to HTTPS.Learn more.
6Page load is fast enough on 3G
A fast page load over a 3G network ensures a good mobile user experience.Learn more.
7User can be prompted to Install the Web App
Browsers can proactively prompt users to add your app to their homescreen, which can lead to higher engagement.Learn more.
8Configured for a custom splash screen
A themed splash screen ensures a high-quality experience when users launch your app from their homescreens.Learn more.
9Address bar matches brand colors
The browser address bar can be themed to match your site.Learn more.
10Has a tag withwidth orinitial-scale
Add a viewport meta tag to optimize your app for mobile screens.Learn more.
11Content is sized correctly for the viewport
If the width of your app's content doesn't match the width of the viewport, your app might not be optimized for mobile screens.Learn more.
Accessibility
These checks highlight opportunities toimprove the accessibility of your web app. Only a subset of accessibility issues can be automatically detected so manual testing is also encouraged.
Additional items to manually check
10 audits
These items address areas which an automated testing tool cannot cover. Learn more in our guide onconducting an accessibility review.
1The page has a logical tab order
Tabbing through the page follows the visual layout. Users cannot focus elements that are offscreen.Learn more.
2Interactive controls are keyboard focusable
Custom interactive controls are keyboard focusable and display a focus indicator.Learn more.
3The user's focus is directed to new content added to the page
If new content, such as a dialog, is added to the page, the user's focus is directed to it.Learn more.
4User focus is not accidentally trapped in a region
A user can tab into and out of any control or region without accidentally trapping their focus.Learn more.
5Custom controls have associated labels
Custom interactive controls have associated labels, provided by aria-label or aria-labelledby.Learn more.
6Custom controls have ARIA roles
Custom interactive controls have appropriate ARIA roles.Learn more.
7Visual order on the page follows DOM order
DOM order matches the visual order, improving navigation for assistive technology.Learn more.
8Offscreen content is hidden from assistive technology
Offscreen content is hidden with display: none or aria-hidden=true.Learn more.
9Headings don't skip levels
Headings are used to create an outline for the page and heading levels are not skipped.Learn more.
10HTML5 landmark elements are used to improve navigation
Landmark elements (
,
Passed audits
11 audits
Elements Use Attributes Correctly
These are opportunities to improve the configuration of your HTML elements.
1Image elements have[alt] attributes
Informative elements should aim for short, descriptive alternate text. Decorative elements can be ignored with an empty alt attribute.Learn more.
Elements Have Discernible Names
These are opportunities to improve the semantics of the controls in your application. This may enhance the experience for users of assistive technology, like a screen reader.
Elements Describe Contents Well
These are opportunities to make your content easier to understand for a user of assistive technology, like a screen reader.
1The page contains a heading, skip link, or landmark region
Adding ways to bypass repetitive content lets keyboard users navigate the page more efficiently.Learn more.
2Document has a</code> element</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>The title gives screen reader users an overview of the page, and search engine users rely on it heavily to determine if a page is relevant to their search.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/title">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> </details> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Color Contrast Is Satisfactory</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>These are opportunities to improve the legibility of your content.</span></div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="color-contrast"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span>Background and foreground colors have a sufficient contrast ratio</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Low-contrast text is difficult or impossible for many users to read.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/color-contrast?application=lighthouse">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> </details> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Elements Are Well Structured</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>These are opportunities to make sure your HTML is appropriately structured.</span></div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="duplicate-id"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span><code>[id]</code> attributes on the page are unique</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>The value of an id attribute must be unique to prevent other instances from being overlooked by assistive technologies.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/duplicate-id?application=lighthouse">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="list"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">2</span><span class="lh-audit__title"><span>Lists contain only<code><li></code> elements and script supporting elements (<code><script></code> and<code><template></code>).</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Screen readers have a specific way of announcing lists. Ensuring proper list structure aids screen reader output.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/list?application=lighthouse">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="listitem"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">3</span><span class="lh-audit__title"><span>List items (<code><li></code>) are contained within<code><ul></code> or<code><ol></code> parent elements</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Screen readers require list items (`<li>`) to be contained within a parent `<ul>` or `<ol>` to be announced properly.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/listitem?application=lighthouse">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> </details> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Page Specifies Valid Language</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>These are opportunities to improve the interpretation of your content by users in different locales.</span></div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="html-has-lang"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span><code><html></code> element has a<code>[lang]</code> attribute</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>If a page doesn't specify a lang attribute, a screen reader assumes that the page is in the default language that the user chose when setting up the screen reader. If the page isn't actually in the default language, then the screen reader might not announce the page's text correctly.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/html-lang?application=lighthouse">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="html-lang-valid"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">2</span><span class="lh-audit__title"><span><code><html></code> element has a valid value for its<code>[lang]</code> attribute</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Specifying a valid<a rel="noopener" target="_blank" href="https://www.w3.org/International/questions/qa-choosing-language-tags#question">BCP 47 language</a> helps screen readers announce text properly.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/valid-lang?application=lighthouse">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> </details> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Meta Tags Used Properly</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>These are opportunities to improve the user experience of your site.</span></div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="meta-viewport"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span><code>[user-scalable="no"]</code> is not used in the<code><meta name="viewport"></code> element and the<code>[maximum-scale]</code> attribute is not less than 5.</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Disabling zooming is problematic for users with low vision who rely on screen magnification to properly see the contents of a web page.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/meta-viewport?application=lighthouse">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> </details> </details> <details class="lh-audit-group lh-audit-group--not-applicable"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Not applicable</div> <div class="lh-audit-group__itemcount">24 audits</div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Elements Use Attributes Correctly</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>These are opportunities to improve the configuration of your HTML elements.</span></div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="accesskeys"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span><code>[accesskey]</code> values are unique</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Access keys let users quickly focus a part of the page. For proper navigation, each access key must be unique.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/accesskeys?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="audio-caption"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">2</span><span class="lh-audit__title"><span><code><audio></code> elements contain a<code><track></code> element with<code>[kind="captions"]</code></span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Captions make audio elements usable for deaf or hearing-impaired users, providing critical information such as who is talking, what they're saying, and other non-speech information.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/audio-caption?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="input-image-alt"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">3</span><span class="lh-audit__title"><span><code><input type="image"></code> elements have<code>[alt]</code> text</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>When an image is being used as an `<input>` button, providing alternative text can help screen reader users understand the purpose of the button.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/input-image-alt?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="tabindex"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">4</span><span class="lh-audit__title"><span>No element has a<code>[tabindex]</code> value greater than 0</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>A value greater than 0 implies an explicit navigation ordering. Although technically valid, this often creates frustrating experiences for users who rely on assistive technologies.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/tabindex?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="td-headers-attr"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">5</span><span class="lh-audit__title"><span>Cells in a<code><table></code> element that use the<code>[headers]</code> attribute only refer to other cells of that same table.</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Screen readers have features to make navigating tables easier. Ensuring `<td>` cells using the `[headers]` attribute only refer to other cells in the same table may improve the experience for screen reader users.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/td-headers-attr?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="th-has-data-cells"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">6</span><span class="lh-audit__title"><span><code><th></code> elements and elements with<code>[role="columnheader"/"rowheader"]</code> have data cells they describe.</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Screen readers have features to make navigating tables easier. Ensuring table headers always refer to some set of cells may improve the experience for screen reader users.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/th-has-data-cells?application=lighthouse">Learn more</a>.</span></div> </details> </div> </details> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">ARIA Attributes Follow Best Practices</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>These are opportunities to improve the usage of ARIA in your application which may enhance the experience for users of assistive technology, like a screen reader.</span></div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="aria-allowed-attr"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span><code>[aria-*]</code> attributes match their roles</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Each ARIA `role` supports a specific subset of `aria-*` attributes. Mismatching these invalidates the `aria-*` attributes.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/aria-allowed-attr?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="aria-required-attr"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">2</span><span class="lh-audit__title"><span><code>[role]</code>s have all required<code>[aria-*]</code> attributes</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Some ARIA roles have required attributes that describe the state of the element to screen readers.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/aria-required-attr?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="aria-required-children"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">3</span><span class="lh-audit__title"><span>Elements with<code>[role]</code> that require specific children<code>[role]</code>s, are present</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Some ARIA parent roles must contain specific child roles to perform their intended accessibility functions.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/aria-required-children?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="aria-required-parent"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">4</span><span class="lh-audit__title"><span><code>[role]</code>s are contained by their required parent element</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Some ARIA child roles must be contained by specific parent roles to properly perform their intended accessibility functions.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/aria-required-parent?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="aria-roles"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">5</span><span class="lh-audit__title"><span><code>[role]</code> values are valid</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>ARIA roles must have valid values in order to perform their intended accessibility functions.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/aria-roles?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="aria-valid-attr-value"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">6</span><span class="lh-audit__title"><span><code>[aria-*]</code> attributes have valid values</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid values.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/aria-valid-attr-value?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="aria-valid-attr"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">7</span><span class="lh-audit__title"><span><code>[aria-*]</code> attributes are valid and not misspelled</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid names.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/aria-valid-attr?application=lighthouse">Learn more</a>.</span></div> </details> </div> </details> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Elements Have Discernible Names</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>These are opportunities to improve the semantics of the controls in your application. This may enhance the experience for users of assistive technology, like a screen reader.</span></div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="button-name"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span>Buttons have an accessible name</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>When a button doesn't have an accessible name, screen readers announce it as "button", making it unusable for users who rely on screen readers.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/button-name?application=lighthouse">Learn more</a>.</span></div> </details> </div> </details> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Elements Describe Contents Well</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>These are opportunities to make your content easier to understand for a user of assistive technology, like a screen reader.</span></div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="frame-title"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span><code><frame></code> or<code><iframe></code> elements have a title</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Screen reader users rely on frame titles to describe the contents of frames.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/frame-title?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="label"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">2</span><span class="lh-audit__title"><span>Form elements have associated labels</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Labels ensure that form controls are announced properly by assistive technologies, like screen readers.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/label?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="layout-table"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">3</span><span class="lh-audit__title"><span>Presentational<code><table></code> elements avoid using<code><th></code>,<code><caption></code> or the<code>[summary]</code> attribute.</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>A table being used for layout purposes should not include data elements, such as the th or caption elements or the summary attribute, because this can create a confusing experience for screen reader users.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/__layout-table?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="object-alt"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">4</span><span class="lh-audit__title"><span><code><object></code> elements have<code>[alt]</code> text</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Screen readers cannot translate non-text content. Adding alt text to `<object>` elements helps screen readers convey meaning to users.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/object-alt?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="video-caption"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">5</span><span class="lh-audit__title"><span><code><video></code> elements contain a<code><track></code> element with<code>[kind="captions"]</code></span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>When a video provides a caption it is easier for deaf and hearing impaired users to access its information.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/video-caption?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="video-description"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">6</span><span class="lh-audit__title"><span><code><video></code> elements contain a<code><track></code> element with<code>[kind="description"]</code></span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Audio descriptions provide relevant information for videos that dialogue cannot, such as facial expressions and scenes.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/video-description?application=lighthouse">Learn more</a>.</span></div> </details> </div> </details> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Elements Are Well Structured</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>These are opportunities to make sure your HTML is appropriately structured.</span></div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="definition-list"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span><code><dl></code>'s contain only properly-ordered<code><dt></code> and<code><dd></code> groups,<code><script></code> or<code><template></code> elements.</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>When definition lists are not properly marked up, screen readers may produce confusing or inaccurate output.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/definition-list?application=lighthouse">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="dlitem"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">2</span><span class="lh-audit__title"><span>Definition list items are wrapped in<code><dl></code> elements</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Definition list items (`<dt>` and `<dd>`) must be wrapped in a parent `<dl>` element to ensure that screen readers can properly announce them.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/dlitem?application=lighthouse">Learn more</a>.</span></div> </details> </div> </details> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Page Specifies Valid Language</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>These are opportunities to improve the interpretation of your content by users in different locales.</span></div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="valid-lang"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span><code>[lang]</code> attributes have a valid value</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Specifying a valid<a rel="noopener" target="_blank" href="https://www.w3.org/International/questions/qa-choosing-language-tags#question">BCP 47 language</a> on elements helps ensure that text is pronounced correctly by a screen reader.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/valid-lang?application=lighthouse">Learn more</a>.</span></div> </details> </div> </details> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Meta Tags Used Properly</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>These are opportunities to improve the user experience of your site.</span></div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="meta-refresh"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span>The document does not use<code><meta http-equiv="refresh"></code></span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Users do not expect a page to refresh automatically, and doing so will move focus back to the top of the page. This may create a frustrating or confusing experience.<a rel="noopener" target="_blank" href="https://dequeuniversity.com/rules/axe/2.2/meta-refresh?application=lighthouse">Learn more</a>.</span></div> </details> </div> </details> </details> </div> <div class="lh-category"><span class="lh-permalink" id="best-practices"></span> <div class="lh-category-header"> <div class="lh-score__gauge"><a class="lh-gauge__wrapper lh-gauge__wrapper--pass" href="#best-practices"> <svg class="lh-gauge" viewbox="0 0 120 120" fill="none" stroke-width="2"> <circle class="lh-gauge-base" r="53" cx="60" cy="60"></circle> <circle class="lh-gauge-arc" transform="rotate(-90 60 60)" stroke-dasharray="0 329" stroke-dashoffset="0" r="53" cx="60" cy="60" style="stroke-dasharray: 329, 329;"></circle> </svg> <div class="lh-gauge__percentage">100</div> <div class="lh-gauge__label">Best Practices</div></a></div><span class="lh-category-header__title"><span>Best Practices</span></span> <div class="lh-category-header__description"></div> </div> <details class="lh-audit-group lh-passed-audits"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Passed audits</div> <div class="lh-audit-group__itemcount">16 audits</div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit lh-audit--pass lh-audit--binary" id="appcache-manifest"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span>Avoids Application Cache</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Application Cache is deprecated.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/appcache">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="no-websql"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">2</span><span class="lh-audit__title"><span>Avoids WebSQL DB</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Web SQL is deprecated. Consider using IndexedDB instead.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/web-sql">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="is-on-https"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">3</span><span class="lh-audit__title"><span>Uses HTTPS</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>All sites should be protected with HTTPS, even ones that don't handle sensitive data. HTTPS prevents intruders from tampering with or passively listening in on the communications between your app and your users, and is a prerequisite for HTTP/2 and many new web platform APIs.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/https">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="uses-http2"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">4</span><span class="lh-audit__title"><span>Uses HTTP/2 for its own resources</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>HTTP/2 offers many benefits over HTTP/1.1, including binary headers, multiplexing, and server push.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/http2">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="uses-passive-event-listeners"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">5</span><span class="lh-audit__title"><span>Uses passive listeners to improve scrolling performance</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Consider marking your touch and wheel event listeners as `passive` to improve your page's scroll performance.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/passive-event-listeners">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="no-mutation-events"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">6</span><span class="lh-audit__title"><span>Avoids Mutation Events in its own scripts</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Mutation Events are deprecated and harm performance. Consider using Mutation Observers instead.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/mutation-events">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="no-document-write"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">7</span><span class="lh-audit__title"><span>Avoids<code>document.write()</code></span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>For users on slow connections, external scripts dynamically injected via `document.write()` can delay page load by tens of seconds.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/document-write">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="external-anchors-use-rel-noopener"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">8</span><span class="lh-audit__title"><span>Links to cross-origin destinations are safe</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Add `rel="noopener"` or `rel="noreferrer"` to any external links to improve performance and prevent security vulnerabilities.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/noopener">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="geolocation-on-start"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">9</span><span class="lh-audit__title"><span>Avoids requesting the geolocation permission on page load</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Users are mistrustful of or confused by sites that request their location without context. Consider tying the request to user gestures instead.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/geolocation-on-load">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="no-vulnerable-libraries"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">10</span><span class="lh-audit__title"><span>Avoids front-end JavaScript libraries with known security vulnerabilities</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Some third-party scripts may contain known security vulnerabilities that are easily identified and exploited by attackers.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="notification-on-start"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">11</span><span class="lh-audit__title"><span>Avoids requesting the notification permission on page load</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Users are mistrustful of or confused by sites that request to send notifications without context. Consider tying the request to user gestures instead.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/notifications-on-load">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="deprecations"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">12</span><span class="lh-audit__title"><span>Avoids deprecated APIs</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Deprecated APIs will eventually be removed from the browser.<a rel="noopener" target="_blank" href="https://www.chromestatus.com/features#deprecated">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="manifest-short-name-length"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">13</span><span class="lh-audit__title"><span>Manifest's<code>short_name</code> won't be truncated when displayed on homescreen</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Make your app's `short_name` fewer than 12 characters to ensure that it's not truncated on homescreens.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/manifest-short_name-is-not-truncated">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="password-inputs-can-be-pasted-into"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">14</span><span class="lh-audit__title"><span>Allows users to paste into password fields</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Preventing password pasting undermines good security policy.<a rel="noopener" target="_blank" href="https://www.ncsc.gov.uk/blog-post/let-them-paste-passwords">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="errors-in-console"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">15</span><span class="lh-audit__title"><span>No browser errors logged to the console</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Errors logged to the console indicate unresolved problems. They can come from network request failures and other browser concerns.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="image-aspect-ratio"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">16</span><span class="lh-audit__title"><span>Displays images with correct aspect ratio</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Image display dimensions should match natural aspect ratio.</span></div><span class="lh-details"></span> </details> </div> </details> </div> <div class="lh-category"><span class="lh-permalink" id="seo"></span> <div class="lh-category-header"> <div class="lh-score__gauge"><a class="lh-gauge__wrapper lh-gauge__wrapper--pass" href="#seo"> <svg class="lh-gauge" viewbox="0 0 120 120" fill="none" stroke-width="2"> <circle class="lh-gauge-base" r="53" cx="60" cy="60"></circle> <circle class="lh-gauge-arc" transform="rotate(-90 60 60)" stroke-dasharray="0 329" stroke-dashoffset="0" r="53" cx="60" cy="60" style="stroke-dasharray: 329, 329;"></circle> </svg> <div class="lh-gauge__percentage">100</div> <div class="lh-gauge__label">SEO</div></a></div><span class="lh-category-header__title"><span>SEO</span></span> <div class="lh-category-header__description"><span>These checks ensure that your page is optimized for search engine results ranking. There are additional factors Lighthouse does not check that may affect your search ranking.<a rel="noopener" target="_blank" href="https://support.google.com/webmasters/answer/35769">Learn more</a>.</span></div> </div> <details class="lh-audit-group lh-audit-group--manual"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Additional items to manually check</div> <div class="lh-audit-group__itemcount">2 audits</div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>Run these additional validators on your site to check additional SEO best practices.</span></div> <div class="lh-audit lh-audit--pass lh-audit--manual" id="mobile-friendly"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span>Page is mobile friendly</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Take the<a rel="noopener" target="_blank" href="https://search.google.com/test/mobile-friendly">Mobile-Friendly Test</a> to check for audits not covered by Lighthouse, like sizing tap targets appropriately.<a rel="noopener" target="_blank" href="https://developers.google.com/search/mobile-sites/">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--manual" id="structured-data"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">2</span><span class="lh-audit__title"><span>Structured data is valid</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Run the<a rel="noopener" target="_blank" href="https://search.google.com/structured-data/testing-tool/">Structured Data Testing Tool</a> and the<a rel="noopener" target="_blank" href="http://linter.structured-data.org/">Structured Data Linter</a> to validate structured data.<a rel="noopener" target="_blank" href="https://developers.google.com/search/dist/guides/mark-up-content">Learn more</a>.</span></div> </details> </div> </details> <details class="lh-audit-group lh-passed-audits"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Passed audits</div> <div class="lh-audit-group__itemcount">10 audits</div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Mobile Friendly</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>Make sure your pages are mobile friendly so users don’t have to pinch or zoom in order to read the content pages.<a rel="noopener" target="_blank" href="https://developers.google.com/search/mobile-sites/">Learn more</a>.</span></div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="viewport"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span>Has a<code><meta name="viewport"></code> tag with<code>width</code> or<code>initial-scale</code></span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Add a viewport meta tag to optimize your app for mobile screens.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/has-viewport-meta-tag">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="font-size"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">2</span><span class="lh-audit__title"><span>Document uses legible font sizes</span></span><span class="lh-audit__display-text">100% legible text</span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Font sizes less than 12px are too small to be legible and require mobile visitors to “pinch to zoom” in order to read. Strive to have >60% of page text ≥12px.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/font-sizes">Learn more</a>.</span></div> <table class="lh-table lh-details"> <thead> <tr> <th class="lh-table-column--url"> <div class="lh-text">Source</div> </th> <th class="lh-table-column--code"> <div class="lh-text">Selector</div> </th> <th class="lh-table-column--text"> <div class="lh-text">% of Page Text</div> </th> <th class="lh-table-column--text"> <div class="lh-text">Font Size</div> </th> </tr> </thead> <tbody> <tr> <td class="lh-table-column--url"> <div class="lh-text__url"> <div class="lh-text">Legible text</div> </div> </td> <td class="lh-table-column--code"> <pre class="lh-code"></pre> </td> <td class="lh-table-column--text"> <div class="lh-text">100.00%</div> </td> <td class="lh-table-column--text"> <div class="lh-text">≥ 12px</div> </td> </tr> </tbody> </table> </details> </div> </details> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Content Best Practices</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>Format your HTML in a way that enables crawlers to better understand your app’s content.</span></div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="document-title"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span>Document has a<code><title></code> element</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>The title gives screen reader users an overview of the page, and search engine users rely on it heavily to determine if a page is relevant to their search.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/title">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="meta-description"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">2</span><span class="lh-audit__title"><span>Document has a meta description</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Meta descriptions may be included in search results to concisely summarize page content.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/description">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="link-text"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">3</span><span class="lh-audit__title"><span>Links have descriptive text</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Descriptive link text helps search engines understand your content.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/descriptive-link-text">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="hreflang"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">4</span><span class="lh-audit__title"><span>Document has a valid<code>hreflang</code></span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>hreflang links tell search engines what version of a page they should list in search results for a given language or region.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/hreflang">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="canonical"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">5</span><span class="lh-audit__title"><span>Document has a valid<code>rel=canonical</code></span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Canonical links suggest which URL to show in search results.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/canonical">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="plugins"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">6</span><span class="lh-audit__title"><span>Document avoids plugins</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Search engines can't index plugin content, and many devices restrict plugins or don't support them.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/plugins">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> </details> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Crawling and Indexing</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>To appear in search results, crawlers need access to your app.</span></div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="http-status-code"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span>Page has successful HTTP status code</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Pages with unsuccessful HTTP status codes may not be indexed properly.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/successful-http-code">Learn more</a>.</span></div> </details> </div> <div class="lh-audit lh-audit--pass lh-audit--binary" id="is-crawlable"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">2</span><span class="lh-audit__title"><span>Page isn’t blocked from indexing</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>Search engines are unable to include your pages in search results if they don't have permission to crawl them.<a rel="noopener" target="_blank" href="https://developers.google.com/web/tools/lighthouse/audits/indexing">Learn more</a>.</span></div><span class="lh-details"></span> </details> </div> </details> </details> <details class="lh-audit-group lh-audit-group--not-applicable"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Not applicable</div> <div class="lh-audit-group__itemcount">1 audits</div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <details class="lh-audit-group lh-audit-group--unadorned"> <summary class="lh-audit-group__summary"> <div class="lh-audit-group__header">Crawling and Indexing</div> <div class="lh-audit-group__itemcount"></div> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </summary> <div class="lh-audit-group__description"><span>To appear in search results, crawlers need access to your app.</span></div> <div class="lh-audit lh-audit--pass lh-audit--not-applicable" id="robots-txt"> <details class="lh-expandable-details"> <summary class="lh-audit__header lh-expandable-details__summary"><span class="lh-audit__index">1</span><span class="lh-audit__title"><span>robots.txt is valid</span></span><span class="lh-audit__display-text"></span> <div class="lh-audit__score-icon"></div> <div class="lh-chevron-container"> <svg class="lh-chevron" title="See audits" xmlns="http://www.w3.org/2000/svg" viewbox="0 0 100 100"> <g class="lh-chevron__lines"> <path class="lh-chevron__line lh-chevron__line-left" d="M10 50h40" stroke="#707173"></path> <path class="lh-chevron__line lh-chevron__line-right" d="M90 50H50" stroke="#707173"></path> </g> </svg> </div> </summary> <div class="lh-audit__description"><span>If your robots.txt file is malformed, crawlers may not be able to understand how you want your website to be crawled or indexed.</span></div> </details> </div> </details> </details> </div> </div> <style> .lh-footer { background-color: var(--header-bg-color); border-top: 1px solid var(--report-secondary-border-color); padding: var(--section-indent) calc(var(--default-padding) * 2); } .lh-footer .lh-generated { text-align: center; border-top: 1px solid var(--report-border-color); padding-top: var(--default-padding); } .lh-env { padding: var(--default-padding) 0; } .lh-env__items { padding-left: 16px; } span.lh-env__name { font-weight: bold; color: var(--secondary-text-color); } span.lh-env__description { font-family: var(--monospace-font-family); font-size: var(--caption-font-size); padding-left: 5px; } </style> <footer class="lh-footer"> <div class="lh-env"> <div class="lh-env__title">Runtime settings</div> <ul class="lh-env__items" id="runtime-settings"> <template id="tmpl-lh-env__items"> <li class="lh-env__item"><span class="lh-env__name"></span><span class="lh-env__description"></span></li> </template> <li class="lh-env__item"><span class="lh-env__name">URL:</span><span class="lh-env__description">https://www.robertgabriel.ninja/</span></li> <li class="lh-env__item"><span class="lh-env__name">Fetch time:</span><span class="lh-env__description">Jun 7, 2018, 12:45 AM CDT</span></li> <li class="lh-env__item"><span class="lh-env__name">Device:</span><span class="lh-env__description">Emulated Nexus 5X</span></li> <li class="lh-env__item"><span class="lh-env__name">Network throttling:</span><span class="lh-env__description">150 ms TCP RTT, 1,638.4 Kbps throughput (Simulated)</span></li> <li class="lh-env__item"><span class="lh-env__name">CPU throttling:</span><span class="lh-env__description">4x slowdown (Simulated)</span></li> <li class="lh-env__item"><span class="lh-env__name">User agent:</span><span class="lh-env__description">Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36</span></li> </ul> </div> <div class="lh-generated">Generated by<b>Lighthouse</b><span class="lh-footer__version">3.0.0-beta.0</span> |<a href="https://github.com/GoogleChrome/Lighthouse/issues" target="_blank" rel="noopener">File an issue</a></div> </footer> </div> </div> </main> <div id="lh-log"></div> <script> /** * @license Copyright 2017 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ 'use strict'; /* globals self URL */ const ELLIPSIS = '\\u2026'; const NBSP = '\\xa0'; const PASS_THRESHOLD = 0.75; const RATINGS = { PASS: {label: 'pass', minScore: PASS_THRESHOLD}, AVERAGE: {label: 'average', minScore: 0.45}, FAIL: {label: 'fail'}, ERROR: {label: 'error'}, }; class Util { static get PASS_THRESHOLD() { return PASS_THRESHOLD; } static get MS_DISPLAY_VALUE() { return `%10d${NBSP}ms`; } /** * @param {string|Array<string|number>=} displayValue * @return {string} */ static formatDisplayValue(displayValue) { if (typeof displayValue === 'string') return displayValue; if (!displayValue) return ''; const replacementRegex = /%([0-9]*(\\.[0-9]+)?d|s)/; const template = /** @type {string} */ (displayValue[0]); if (typeof template !== 'string') { // First value should always be the format string, but we don't want to fail to build // a report, return a placeholder. return 'UNKNOWN'; } let output = template; for (const replacement of displayValue.slice(1)) { if (!replacementRegex.test(output)) { // eslint-disable-next-line no-console console.warn('Too many replacements given'); break; } output = output.replace(replacementRegex, match => { const granularity = Number(match.match(/[0-9.]+/)) || 1; return match === '%s' ? replacement.toLocaleString() : (Math.round(Number(replacement) / granularity) * granularity).toLocaleString(); }); } if (replacementRegex.test(output)) { // eslint-disable-next-line no-console console.warn('Not enough replacements given'); } return output; } /** * Used to determine if the "passed" for the purposes of showing up in the "failed" or "passed" * sections of the report. * * @param {{score: (number|null), scoreDisplayMode: string}} audit * @return {boolean} */ static showAsPassed(audit) { switch (audit.scoreDisplayMode) { case 'manual': case 'not-applicable': return true; case 'error': case 'informative': return false; case 'numeric': case 'binary': default: // Numeric audits that are within PASS_THRESHOLD will still show up with failing. // For opportunities, we want to have them show up with other failing for contrast. // For diagnostics, we sort by score so they'll be lowest priority. return Number(audit.score) === 1; } } /** * Convert a score to a rating label. * @param {number|null} score * @param {string=} scoreDisplayMode * @return {string} */ static calculateRating(score, scoreDisplayMode) { // Handle edge cases first, manual and not applicable receive 'pass', errored audits receive 'error' if (scoreDisplayMode === 'manual' || scoreDisplayMode === 'not-applicable') { return RATINGS.PASS.label; } else if (scoreDisplayMode === 'error') { return RATINGS.ERROR.label; } else if (score === null) { return RATINGS.FAIL.label; } // At this point, we're rating a standard binary/numeric audit let rating = RATINGS.FAIL.label; if (score >= RATINGS.PASS.minScore) { rating = RATINGS.PASS.label; } else if (score >= RATINGS.AVERAGE.minScore) { rating = RATINGS.AVERAGE.label; } return rating; } /** * Format number. * @param {number} number * @param {number=} granularity Number of decimal places to include. Defaults to 0.1. * @return {string} */ static formatNumber(number, granularity = 0.1) { const coarseValue = Math.round(number / granularity) * granularity; return coarseValue.toLocaleString(); } /** * @param {number} size * @param {number=} granularity Controls how coarse the displayed value is, defaults to .01 * @return {string} */ static formatBytesToKB(size, granularity = 0.1) { const kbs = (Math.round(size / 1024 / granularity) * granularity).toLocaleString(); return `${kbs}${NBSP}KB`; } /** * @param {number} ms * @param {number=} granularity Controls how coarse the displayed value is, defaults to 10 * @return {string} */ static formatMilliseconds(ms, granularity = 10) { const coarseTime = Math.round(ms / granularity) * granularity; return `${coarseTime.toLocaleString()}${NBSP}ms`; } /** * @param {number} ms * @param {number=} granularity Controls how coarse the displayed value is, defaults to 0.1 * @return {string} */ static formatSeconds(ms, granularity = 0.1) { const coarseTime = Math.round(ms / 1000 / granularity) * granularity; return `${coarseTime.toLocaleString()}${NBSP}s`; } /** * Format time. * @param {string} date * @return {string} */ static formatDateTime(date) { const options = { month: 'short', day: 'numeric', year: 'numeric', hour: 'numeric', minute: 'numeric', timeZoneName: 'short', }; let formatter = new Intl.DateTimeFormat('en-US', options); // Force UTC if runtime timezone could not be detected. // See https://github.com/GoogleChrome/lighthouse/issues/1056 const tz = formatter.resolvedOptions().timeZone; if (!tz || tz.toLowerCase() === 'etc/unknown') { options.timeZone = 'UTC'; formatter = new Intl.DateTimeFormat('en-US', options); } return formatter.format(new Date(date)); } /** * Converts a time in milliseconds into a duration string, i.e. `1d 2h 13m 52s` * @param {number} timeInMilliseconds * @return {string} */ static formatDuration(timeInMilliseconds) { let timeInSeconds = timeInMilliseconds / 1000; if (Math.round(timeInSeconds) === 0) { return 'None'; } /** @type {Array<string>} */ const parts = []; const unitLabels = /** @type {Object<string, number>} */ ({ d: 60 * 60 * 24, h: 60 * 60, m: 60, s: 1, }); Object.keys(unitLabels).forEach(label => { const unit = unitLabels[label]; const numberOfUnits = Math.floor(timeInSeconds / unit); if (numberOfUnits > 0) { timeInSeconds -= numberOfUnits * unit; parts.push(`${numberOfUnits}\\xa0${label}`); } }); return parts.join(' '); } /** * @param {URL} parsedUrl * @param {{numPathParts?: number, preserveQuery?: boolean, preserveHost?: boolean}=} options * @return {string} */ static getURLDisplayName(parsedUrl, options) { // Closure optional properties aren't optional in tsc, so fallback needs undefined values. options = options || {numPathParts: undefined, preserveQuery: undefined, preserveHost: undefined}; const numPathParts = options.numPathParts !== undefined ? options.numPathParts : 2; const preserveQuery = options.preserveQuery !== undefined ? options.preserveQuery : true; const preserveHost = options.preserveHost || false; let name; if (parsedUrl.protocol === 'about:' || parsedUrl.protocol === 'data:') { // Handle 'about:*' and 'data:*' URLs specially since they have no path. name = parsedUrl.href; } else { name = parsedUrl.pathname; const parts = name.split('/').filter(part => part.length); if (numPathParts && parts.length > numPathParts) { name = ELLIPSIS + parts.slice(-1 * numPathParts).join('/'); } if (preserveHost) { name = `${parsedUrl.host}/${name.replace(/^\\//, '')}`; } if (preserveQuery) { name = `${name}${parsedUrl.search}`; } } const MAX_LENGTH = 64; // Always elide hexadecimal hash name = name.replace(/([a-f0-9]{7})[a-f0-9]{13}[a-f0-9]*/g, `$1${ELLIPSIS}`); // Also elide other hash-like mixed-case strings name = name.replace(/([a-zA-Z0-9-_]{9})(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])[a-zA-Z0-9-_]{10,}/g, `$1${ELLIPSIS}`); // Also elide long number sequences name = name.replace(/(\\d{3})\d{6,}/g, `$1${ELLIPSIS}`); // Merge any adjacent ellipses name = name.replace(/\\u2026+/g, ELLIPSIS); // Elide query params first if (name.length > MAX_LENGTH && name.includes('?')) { // Try to leave the first query parameter intact name = name.replace(/\\?([^=]*)(=)?.*/, `?$1$2${ELLIPSIS}`); // Remove it all if it's still too long if (name.length > MAX_LENGTH) { name = name.replace(/\\?.*/, `?${ELLIPSIS}`); } } // Elide too long names next if (name.length > MAX_LENGTH) { const dotIndex = name.lastIndexOf('.'); if (dotIndex >= 0) { name = name.slice(0, MAX_LENGTH - 1 - (name.length - dotIndex)) + // Show file extension `${ELLIPSIS}${name.slice(dotIndex)}`; } else { name = name.slice(0, MAX_LENGTH - 1) + ELLIPSIS; } } return name; } /** * Split a URL into a file, hostname and origin for easy display. * @param {string} url * @return {{file: string, hostname: string, origin: string}} */ static parseURL(url) { const parsedUrl = new URL(url); return { file: Util.getURLDisplayName(parsedUrl), hostname: parsedUrl.hostname, origin: parsedUrl.origin, }; } /** * @param {number} startTime * @param {number} endTime * @return {string} */ static chainDuration(startTime, endTime) { return Util.formatNumber((endTime - startTime) * 1000); } /** * @param {LH.Config.Settings} settings * @return {Array<{name: string, description: string}>} */ static getEnvironmentDisplayValues(settings) { const emulationDesc = Util.getEmulationDescriptions(settings); return [ { name: 'Device', description: emulationDesc.deviceEmulation, }, { name: 'Network throttling', description: emulationDesc.networkThrottling, }, { name: 'CPU throttling', description: emulationDesc.cpuThrottling, }, ]; } /** * @param {LH.Config.Settings} settings * @return {{deviceEmulation: string, networkThrottling: string, cpuThrottling: string, summary: string}} */ static getEmulationDescriptions(settings) { let cpuThrottling; let networkThrottling; let summary; const throttling = settings.throttling; switch (settings.throttlingMethod) { case 'provided': cpuThrottling = 'Provided by environment'; networkThrottling = 'Provided by environment'; summary = 'No throttling applied'; break; case 'devtools': { const {cpuSlowdownMultiplier, requestLatencyMs} = throttling; cpuThrottling = `${Util.formatNumber(cpuSlowdownMultiplier)}x slowdown (DevTools)`; networkThrottling = `${Util.formatNumber(requestLatencyMs)}${NBSP}ms HTTP RTT, ` + `${Util.formatNumber(throttling.downloadThroughputKbps)}${NBSP}Kbps down, ` + `${Util.formatNumber(throttling.uploadThroughputKbps)}${NBSP}Kbps up (DevTools)`; summary = 'Throttled Fast 3G network'; break; } case 'simulate': { const {cpuSlowdownMultiplier, rttMs, throughputKbps} = throttling; cpuThrottling = `${Util.formatNumber(cpuSlowdownMultiplier)}x slowdown (Simulated)`; networkThrottling = `${Util.formatNumber(rttMs)}${NBSP}ms TCP RTT, ` + `${Util.formatNumber(throughputKbps)}${NBSP}Kbps throughput (Simulated)`; summary = 'Simulated Fast 3G network'; break; } default: cpuThrottling = 'Unknown'; networkThrottling = 'Unknown'; summary = 'Unknown'; } const deviceEmulation = settings.disableDeviceEmulation ? 'No emulation' : 'Emulated Nexus 5X'; return { deviceEmulation, cpuThrottling, networkThrottling, summary: `${deviceEmulation}, ${summary}`, }; } } if (typeof module !== 'undefined' && module.exports) { module.exports = Util; } else { self.Util = Util; } ; /** * @license Copyright 2017 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ 'use strict'; /* globals URL self */ class DOM { /** * @param {Document} document */ constructor(document) { /** @type {Document} */ this._document = document; } // TODO(bckenny): can pass along `createElement`'s inferred type /** * @param {string} name * @param {string=} className * @param {Object<string, (string|undefined)>=} attrs Attribute key/val pairs. * Note: if an attribute key has an undefined value, this method does not * set the attribute on the node. * @return {Element} */ createElement(name, className, attrs = {}) { const element = this._document.createElement(name); if (className) { element.className = className; } Object.keys(attrs).forEach(key => { const value = attrs[key]; if (typeof value !== 'undefined') { element.setAttribute(key, value); } }); return element; } /** * @return {DocumentFragment} */ createFragment() { return this._document.createDocumentFragment(); } /** * @param {Element} parentElem * @param {string} elementName * @param {string=} className * @param {Object<string, (string|undefined)>=} attrs Attribute key/val pairs. * Note: if an attribute key has an undefined value, this method does not * set the attribute on the node. * @return {Element} */ createChildOf(parentElem, elementName, className, attrs) { const element = this.createElement(elementName, className, attrs); parentElem.appendChild(element); return element; } /** * @param {string} selector * @param {ParentNode} context * @return {DocumentFragment} A clone of the template content. * @throws {Error} */ cloneTemplate(selector, context) { const template = /** @type {?HTMLTemplateElement} */ (context.querySelector(selector)); if (!template) { throw new Error(`Template not found: template${selector}`); } const clone = this._document.importNode(template.content, true); // Prevent duplicate styles in the DOM. After a template has been stamped // for the first time, remove the clone's styles so they're not re-added. if (template.hasAttribute('data-stamped')) { this.findAll('style', clone).forEach(style => style.remove()); } template.setAttribute('data-stamped', 'true'); return clone; } /** * Resets the "stamped" state of the templates. */ resetTemplates() { this.findAll('template[data-stamped]', this._document).forEach(t => { t.removeAttribute('data-stamped'); }); } /** * @param {string} text * @return {Element} */ convertMarkdownLinkSnippets(text) { const element = this.createElement('span'); // Split on markdown links (e.g. [some link](https://...)). const parts = text.split(/\\[([^\]]*?)\]\((https?:\/\/.*?)\)/g); while (parts.length) { // Pop off the same number of elements as there are capture groups. const [preambleText, linkText, linkHref] = parts.splice(0, 3); element.appendChild(this._document.createTextNode(preambleText)); // Append link if there are any. if (linkText && linkHref) { const a = /** @type {HTMLAnchorElement} */ (this.createElement('a')); a.rel = 'noopener'; a.target = '_blank'; a.textContent = linkText; a.href = (new URL(linkHref)).href; element.appendChild(a); } } return element; } /** * @param {string} text * @return {Element} */ convertMarkdownCodeSnippets(text) { const element = this.createElement('span'); const parts = text.split(/`(.*?)`/g); // Split on markdown code slashes while (parts.length) { // Pop off the same number of elements as there are capture groups. const [preambleText, codeText] = parts.splice(0, 2); element.appendChild(this._document.createTextNode(preambleText)); if (codeText) { const pre = /** @type {HTMLPreElement} */ (this.createElement('code')); pre.textContent = codeText; element.appendChild(pre); } } return element; } /** * @return {Document} */ document() { return this._document; } /** * TODO(paulirish): import and conditionally apply the DevTools frontend subclasses instead of this * @return {boolean} */ isDevTools() { return !!this._document.querySelector('.lh-devtools'); } /** * Guaranteed context.querySelector. Always returns an element or throws if * nothing matches query. * @param {string} query * @param {ParentNode} context * @return {HTMLElement} */ find(query, context) { /** @type {?HTMLElement} */ const result = context.querySelector(query); if (result === null) { throw new Error(`query ${query} not found`); } return result; } /** * Helper for context.querySelectorAll. Returns an Array instead of a NodeList. * @param {string} query * @param {ParentNode} context * @return {Array<HTMLElement>} */ findAll(query, context) { return Array.from(context.querySelectorAll(query)); } } if (typeof module !== 'undefined' && module.exports) { module.exports = DOM; } else { self.DOM = DOM; } ; /** * @license Copyright 2017 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ 'use strict'; /* globals self CriticalRequestChainRenderer Util URL */ /** @typedef {import('./dom.js')} DOM */ /** @typedef {import('./crc-details-renderer.js')} CRCDetailsJSON */ class DetailsRenderer { /** * @param {DOM} dom */ constructor(dom) { /** @type {DOM} */ this._dom = dom; /** @type {ParentNode} */ this._templateContext; // eslint-disable-line no-unused-expressions } /** * @param {ParentNode} context */ setTemplateContext(context) { this._templateContext = context; } /** * @param {DetailsJSON} details * @return {Element} */ render(details) { switch (details.type) { case 'text': return this._renderText(/** @type {StringDetailsJSON} */ (details)); case 'url': return this._renderTextURL(/** @type {StringDetailsJSON} */ (details)); case 'bytes': return this._renderBytes(/** @type {NumericUnitDetailsJSON} */ (details)); case 'ms': // eslint-disable-next-line max-len return this._renderMilliseconds(/** @type {NumericUnitDetailsJSON} */ (details)); case 'link': // @ts-ignore - TODO(bckenny): Fix type hierarchy return this._renderLink(/** @type {LinkDetailsJSON} */ (details)); case 'thumbnail': return this._renderThumbnail(/** @type {ThumbnailDetails} */ (details)); case 'filmstrip': // @ts-ignore - TODO(bckenny): Fix type hierarchy return this._renderFilmstrip(/** @type {FilmstripDetails} */ (details)); case 'table': // @ts-ignore - TODO(bckenny): Fix type hierarchy return this._renderTable(/** @type {TableDetailsJSON} */ (details)); case 'code': return this._renderCode(details); case 'node': return this.renderNode(/** @type {NodeDetailsJSON} */(details)); case 'criticalrequestchain': return CriticalRequestChainRenderer.render(this._dom, this._templateContext, // @ts-ignore - TODO(bckenny): Fix type hierarchy /** @type {CRCDetailsJSON} */ (details)); default: { throw new Error(`Unknown type: ${details.type}`); } } } /** * @param {NumericUnitDetailsJSON} details * @return {Element} */ _renderBytes(details) { // TODO: handle displayUnit once we have something other than 'kb' const value = Util.formatBytesToKB(details.value, details.granularity); return this._renderText({type: 'text', value}); } /** * @param {NumericUnitDetailsJSON} details * @return {Element} */ _renderMilliseconds(details) { let value = Util.formatMilliseconds(details.value, details.granularity); if (details.displayUnit === 'duration') { value = Util.formatDuration(details.value); } return this._renderText({type: 'text', value}); } /** * @param {StringDetailsJSON} text * @return {HTMLElement} */ _renderTextURL(text) { const url = text.value; let displayedPath; let displayedHost; let title; try { const parsed = Util.parseURL(url); displayedPath = parsed.file === '/' ? parsed.origin : parsed.file; displayedHost = parsed.file === '/' ? '' : `(${parsed.hostname})`; title = url; } catch (/** @type {!Error} */ e) { if (!(e instanceof TypeError)) { throw e; } displayedPath = url; } const element = /** @type {HTMLElement} */ (this._dom.createElement('div', 'lh-text__url')); element.appendChild(this._renderText({ value: displayedPath, type: 'text', })); if (displayedHost) { const hostElem = this._renderText({ value: displayedHost, type: 'text', }); hostElem.classList.add('lh-text__url-host'); element.appendChild(hostElem); } if (title) element.title = url; return element; } /** * @param {LinkDetailsJSON} details * @return {Element} */ _renderLink(details) { const allowedProtocols = ['https:', 'http:']; const url = new URL(details.url); if (!allowedProtocols.includes(url.protocol)) { // Fall back to just the link text if protocol not allowed. return this._renderText({ type: 'text', value: details.text, }); } const a = /** @type {HTMLAnchorElement} */ (this._dom.createElement('a')); a.rel = 'noopener'; a.target = '_blank'; a.textContent = details.text; a.href = url.href; return a; } /** * @param {StringDetailsJSON} text * @return {Element} */ _renderText(text) { const element = this._dom.createElement('div', 'lh-text'); element.textContent = text.value; return element; } /** * Create small thumbnail with scaled down image asset. * If the supplied details doesn't have an image/* mimeType, then an empty span is returned. * @param {ThumbnailDetails} details * @return {Element} */ _renderThumbnail(details) { const element = /** @type {HTMLImageElement}*/ (this._dom.createElement('img', 'lh-thumbnail')); /** @type {string} */ // @ts-ignore - type should have a value if we get here. const strValue = details.value; element.src = strValue; element.title = strValue; element.alt = ''; return element; } /** * @param {TableDetailsJSON} details * @return {Element} */ _renderTable(details) { if (!details.items.length) return this._dom.createElement('span'); const tableElem = this._dom.createElement('table', 'lh-table'); const theadElem = this._dom.createChildOf(tableElem, 'thead'); const theadTrElem = this._dom.createChildOf(theadElem, 'tr'); for (const heading of details.headings) { const itemType = heading.itemType || 'text'; const classes = `lh-table-column--${itemType}`; this._dom.createChildOf(theadTrElem, 'th', classes).appendChild(this.render({ type: 'text', value: heading.text || '', })); } const tbodyElem = this._dom.createChildOf(tableElem, 'tbody'); for (const row of details.items) { const rowElem = this._dom.createChildOf(tbodyElem, 'tr'); for (const heading of details.headings) { const key = /** @type {keyof DetailsJSON} */ (heading.key); // TODO(bckenny): type should be naturally inferred here. const value = /** @type {number|string|DetailsJSON|undefined} */ (row[key]); if (typeof value === 'undefined' || value === null) { this._dom.createChildOf(rowElem, 'td', 'lh-table-column--empty'); continue; } // handle nested types like code blocks in table rows. // @ts-ignore - TODO(bckenny): narrow first if (value.type) { const valueAsDetails = /** @type {DetailsJSON} */ (value); const classes = `lh-table-column--${valueAsDetails.type}`; this._dom.createChildOf(rowElem, 'td', classes).appendChild(this.render(valueAsDetails)); continue; } // build new details item to render const item = { value: /** @type {number|string} */ (value), type: heading.itemType, displayUnit: heading.displayUnit, granularity: heading.granularity, }; /** @type {string|undefined} */ // @ts-ignore - TODO(bckenny): handle with refactoring above const valueType = value.type; const classes = `lh-table-column--${valueType || heading.itemType}`; this._dom.createChildOf(rowElem, 'td', classes).appendChild(this.render(item)); } } return tableElem; } /** * @param {NodeDetailsJSON} item * @return {Element} * @protected */ renderNode(item) { const element = /** @type {HTMLSpanElement} */ (this._dom.createElement('span', 'lh-node')); if (item.snippet) { element.textContent = item.snippet; } if (item.selector) { element.title = item.selector; } if (item.path) element.setAttribute('data-path', item.path); if (item.selector) element.setAttribute('data-selector', item.selector); if (item.snippet) element.setAttribute('data-snippet', item.snippet); return element; } /** * @param {FilmstripDetails} details * @return {Element} */ _renderFilmstrip(details) { const filmstripEl = this._dom.createElement('div', 'lh-filmstrip'); for (const thumbnail of details.items) { const frameEl = this._dom.createChildOf(filmstripEl, 'div', 'lh-filmstrip__frame'); this._dom.createChildOf(frameEl, 'img', 'lh-filmstrip__thumbnail', { src: `data:image/jpeg;base64,${thumbnail.data}`, alt: `Screenshot`, }); } return filmstripEl; } /** * @param {DetailsJSON} details * @return {Element} */ _renderCode(details) { const pre = this._dom.createElement('pre', 'lh-code'); pre.textContent = /** @type {string} */ (details.value); return pre; } } if (typeof module !== 'undefined' && module.exports) { module.exports = DetailsRenderer; } else { self.DetailsRenderer = DetailsRenderer; } // TODO, what's the diff between DetailsJSON and NumericUnitDetailsJSON? /** * @typedef {{ type: string, value: (string|number|undefined), summary?: OpportunitySummary, granularity?: number, displayUnit?: string }} DetailsJSON */ /** * @typedef {{ type: string, value: string, granularity?: number, displayUnit?: string, }} StringDetailsJSON */ /** * @typedef {{ type: string, value: number, granularity?: number, displayUnit?: string, }} NumericUnitDetailsJSON */ /** * @typedef {{ type: string, path?: string, selector?: string, snippet?: string }} NodeDetailsJSON */ /** * @typedef {{ itemType: string, key: string, text?: string, granularity?: number, displayUnit?: string, }} TableHeaderJSON */ /** @typedef {{ type: string, items: Array<DetailsJSON>, headings: Array<TableHeaderJSON> }} TableDetailsJSON */ /** @typedef {{ type: string, value?: string, }} ThumbnailDetails */ /** @typedef {{ type: string, text: string, url: string }} LinkDetailsJSON */ /** @typedef {{ type: string, scale: number, items: Array<{timing: number, timestamp: number, data: string}>, }} FilmstripDetails */ /** @typedef {{ wastedMs?: number, wastedBytes?: number }} OpportunitySummary */ ; /** * @license Copyright 2017 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ 'use strict'; /** * @fileoverview This file contains helpers for constructing and rendering the * critical request chains network tree. */ /* globals self Util */ /** @typedef {import('./dom.js')} DOM */ class CriticalRequestChainRenderer { /** * Create render context for critical-request-chain tree display. * @param {LH.Audit.SimpleCriticalRequestNode} tree * @return {{tree: LH.Audit.SimpleCriticalRequestNode, startTime: number, transferSize: number}} */ static initTree(tree) { let startTime = 0; const rootNodes = Object.keys(tree); if (rootNodes.length > 0) { const node = tree[rootNodes[0]]; startTime = node.request.startTime; } return {tree, startTime, transferSize: 0}; } /** * Helper to create context for each critical-request-chain node based on its * parent. Calculates if this node is the last child, whether it has any * children itself and what the tree looks like all the way back up to the root, * so the tree markers can be drawn correctly. * @param {LH.Audit.SimpleCriticalRequestNode} parent * @param {string} id * @param {number} startTime * @param {number} transferSize * @param {Array<boolean>=} treeMarkers * @param {boolean=} parentIsLastChild * @return {CRCSegment} */ static createSegment(parent, id, startTime, transferSize, treeMarkers, parentIsLastChild) { const node = parent[id]; const siblings = Object.keys(parent); const isLastChild = siblings.indexOf(id) === (siblings.length - 1); const hasChildren = Object.keys(node.children).length > 0; // Copy the tree markers so that we don't change by reference. const newTreeMarkers = Array.isArray(treeMarkers) ? treeMarkers.slice(0) : []; // Add on the new entry. if (typeof parentIsLastChild !== 'undefined') { newTreeMarkers.push(!parentIsLastChild); } return { node, isLastChild, hasChildren, startTime, transferSize: transferSize + node.request.transferSize, treeMarkers: newTreeMarkers, }; } /** * Creates the DOM for a tree segment. * @param {DOM} dom * @param {DocumentFragment} tmpl * @param {CRCSegment} segment * @return {Node} */ static createChainNode(dom, tmpl, segment) { const chainsEl = dom.cloneTemplate('#tmpl-lh-crc__chains', tmpl); // Hovering over request shows full URL. dom.find('.crc-node', chainsEl).setAttribute('title', segment.node.request.url); const treeMarkeEl = dom.find('.crc-node__tree-marker', chainsEl); // Construct lines and add spacers for sub requests. segment.treeMarkers.forEach(separator => { if (separator) { treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker vert')); treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker')); } else { treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker')); treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker')); } }); if (segment.isLastChild) { treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker up-right')); treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker right')); } else { treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker vert-right')); treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker right')); } if (segment.hasChildren) { treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker horiz-down')); } else { treeMarkeEl.appendChild(dom.createElement('span', 'tree-marker right')); } // Fill in url, host, and request size information. const {file, hostname} = Util.parseURL(segment.node.request.url); const treevalEl = dom.find('.crc-node__tree-value', chainsEl); dom.find('.crc-node__tree-file', treevalEl).textContent = `${file}`; dom.find('.crc-node__tree-hostname', treevalEl).textContent = hostname ? `(${hostname})` : ''; if (!segment.hasChildren) { const span = dom.createElement('span', 'crc-node__chain-duration'); span.textContent = ' - ' + Util.chainDuration( segment.node.request.startTime, segment.node.request.endTime) + 'ms, '; const span2 = dom.createElement('span', 'crc-node__chain-duration'); span2.textContent = Util.formatBytesToKB(segment.node.request.transferSize, 0.01); treevalEl.appendChild(span); treevalEl.appendChild(span2); } return chainsEl; } /** * Recursively builds a tree from segments. * @param {DOM} dom * @param {DocumentFragment} tmpl * @param {CRCSegment} segment * @param {Element} elem Parent element. * @param {CRCDetailsJSON} details */ static buildTree(dom, tmpl, segment, elem, details) { elem.appendChild(CriticalRequestChainRenderer.createChainNode(dom, tmpl, segment)); for (const key of Object.keys(segment.node.children)) { const childSegment = CriticalRequestChainRenderer.createSegment(segment.node.children, key, segment.startTime, segment.transferSize, segment.treeMarkers, segment.isLastChild); CriticalRequestChainRenderer.buildTree(dom, tmpl, childSegment, elem, details); } } /** * @param {DOM} dom * @param {ParentNode} templateContext * @param {CRCDetailsJSON} details * @return {Element} */ static render(dom, templateContext, details) { const tmpl = dom.cloneTemplate('#tmpl-lh-crc', templateContext); const containerEl = dom.find('.lh-crc', tmpl); // Fill in top summary. dom.find('.lh-crc__longest_duration', tmpl).textContent = Util.formatNumber(details.longestChain.duration) + 'ms'; dom.find('.lh-crc__longest_length', tmpl).textContent = details.longestChain.length.toString(); dom.find('.lh-crc__longest_transfersize', tmpl).textContent = Util.formatBytesToKB(details.longestChain.transferSize); // Construct visual tree. const root = CriticalRequestChainRenderer.initTree(details.chains); for (const key of Object.keys(root.tree)) { const segment = CriticalRequestChainRenderer.createSegment(root.tree, key, root.startTime, root.transferSize); CriticalRequestChainRenderer.buildTree(dom, tmpl, segment, containerEl, details); } return dom.find('.lh-crc-container', tmpl); } } // Allow Node require()'ing. if (typeof module !== 'undefined' && module.exports) { module.exports = CriticalRequestChainRenderer; } else { self.CriticalRequestChainRenderer = CriticalRequestChainRenderer; } /** @typedef {{ type: string, header: {text: string}, longestChain: {duration: number, length: number, transferSize: number}, chains: LH.Audit.SimpleCriticalRequestNode }} CRCDetailsJSON */ /** @typedef {{ node: LH.Audit.SimpleCriticalRequestNode[string], isLastChild: boolean, hasChildren: boolean, startTime: number, transferSize: number, treeMarkers: Array<boolean> }} CRCSegment */ ; /** * @license Copyright 2017 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ 'use strict'; /* global URL */ /** * @fileoverview * @suppress {reportUnknownTypes} */ /** * Generate a filenamePrefix of hostname_YYYY-MM-DD_HH-MM-SS * Date/time uses the local timezone, however Node has unreliable ICU * support, so we must construct a YYYY-MM-DD date format manually. :/ * @param {{finalUrl: string, fetchTime: string}} lhr * @return {string} */ function getFilenamePrefix(lhr) { const hostname = new (getUrlConstructor())(lhr.finalUrl).hostname; const date = (lhr.fetchTime && new Date(lhr.fetchTime)) || new Date(); const timeStr = date.toLocaleTimeString('en-US', {hour12: false}); const dateParts = date.toLocaleDateString('en-US', { year: 'numeric', month: '2-digit', day: '2-digit', }).split('/'); // @ts-ignore - parts exists dateParts.unshift(dateParts.pop()); const dateStr = dateParts.join('-'); const filenamePrefix = `${hostname}_${dateStr}_${timeStr}`; // replace characters that are unfriendly to filenames return filenamePrefix.replace(/[/?<>\\\:*|":]/g, '-'); } function getUrlConstructor() { if (typeof module !== 'undefined' && module.exports) { return require('./url-shim'); } else { return URL; } } // @ts-ignore - suppress `module` error for type-checking in a browser context. if (typeof module !== 'undefined' && module.exports) { module.exports = {getFilenamePrefix}; } ; /** * @license Copyright 2017 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ 'use strict'; /** * Logs messages via a UI butter. */ class Logger { /** * @param {Element} element */ constructor(element) { /** @type {Element} */ this.el = element; this._id = undefined; } /** * Shows a butter bar. * @param {string} msg The message to show. * @param {boolean=} autoHide True to hide the message after a duration. * Default is true. */ log(msg, autoHide = true) { this._id && clearTimeout(this._id); this.el.textContent = msg; this.el.classList.add('show'); if (autoHide) { this._id = setTimeout(_ => { this.el.classList.remove('show'); }, 7000); } } /** * @param {string} msg */ warn(msg) { this.log('Warning: ' + msg); } /** * @param {string} msg */ error(msg) { this.log(msg); // Rethrow to make sure it's auditable as an error, but in a setTimeout so page // recovers gracefully and user can try loading a report again. setTimeout(_ => { throw new Error(msg); }, 0); } /** * Explicitly hides the butter bar. */ hide() { this._id && clearTimeout(this._id); this.el.classList.remove('show'); } } if (typeof module !== 'undefined' && module.exports) { module.exports = Logger; } ; /** * @license Copyright 2017 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ 'use strict'; /** * @fileoverview Adds export button, print, and other dynamic functionality to * the report. */ /* globals self URL Blob CustomEvent getFilenamePrefix window */ /** @typedef {import('./dom.js')} DOM */ /** @typedef {import('./report-renderer.js').ReportJSON} ReportJSON */ class ReportUIFeatures { /** * @param {DOM} dom */ constructor(dom) { /** @type {ReportJSON} */ this.json; // eslint-disable-line no-unused-expressions /** @type {DOM} */ this._dom = dom; /** @type {Document} */ this._document = this._dom.document(); /** @type {boolean} */ this._copyAttempt = false; /** @type {HTMLElement} */ this.exportButton; // eslint-disable-line no-unused-expressions /** @type {HTMLElement} */ this.headerSticky; // eslint-disable-line no-unused-expressions /** @type {HTMLElement} */ this.headerBackground; // eslint-disable-line no-unused-expressions /** @type {HTMLElement} */ this.lighthouseIcon; // eslint-disable-line no-unused-expressions /** @type {!HTMLElement} */ this.scoresWrapperBg; // eslint-disable-line no-unused-expressions /** @type {!HTMLElement} */ this.productInfo; // eslint-disable-line no-unused-expressions /** @type {HTMLElement} */ this.toolbar; // eslint-disable-line no-unused-expressions /** @type {HTMLElement} */ this.toolbarMetadata; // eslint-disable-line no-unused-expressions /** @type {HTMLElement} */ this.env; // eslint-disable-line no-unused-expressions /** @type {number} */ this.headerOverlap = 0; /** @type {number} */ this.headerHeight = 0; /** @type {number} */ this.latestKnownScrollY = 0; /** @type {boolean} */ this.isAnimatingHeader = false; this.onMediaQueryChange = this.onMediaQueryChange.bind(this); this.onCopy = this.onCopy.bind(this); this.onExportButtonClick = this.onExportButtonClick.bind(this); this.onExport = this.onExport.bind(this); this.onKeyDown = this.onKeyDown.bind(this); this.printShortCutDetect = this.printShortCutDetect.bind(this); this.onScroll = this.onScroll.bind(this); this.onChevronClick = this.onChevronClick.bind(this); } /** * Adds export button, print, and other functionality to the report. The method * should be called whenever the report needs to be re-rendered. * @param {ReportJSON} report */ initFeatures(report) { if (this._dom.isDevTools()) return; this.json = report; this._setupMediaQueryListeners(); this._setupExportButton(); this._setUpCollapseDetailsAfterPrinting(); this._setupHeaderAnimation(); this._resetUIState(); this._document.addEventListener('keydown', this.printShortCutDetect); // @ts-ignore - tsc thinks document can't listen for `copy` this._document.addEventListener('copy', this.onCopy); } /** * Fires a custom DOM event on target. * @param {string} name Name of the event. * @param {Node=} target DOM node to fire the event on. * @param {*=} detail Custom data to include. */ _fireEventOn(name, target = this._document, detail) { const event = new CustomEvent(name, detail ? {detail} : undefined); target.dispatchEvent(event); } _setupMediaQueryListeners() { const mediaQuery = self.matchMedia('(max-width: 600px)'); mediaQuery.addListener(this.onMediaQueryChange); // Ensure the handler is called on init this.onMediaQueryChange(mediaQuery); } /** * Handle media query change events. * @param {MediaQueryList} mql */ onMediaQueryChange(mql) { const root = this._dom.find('.lh-root', this._document); root.classList.toggle('lh-narrow', mql.matches); } _setupExportButton() { this.exportButton = this._dom.find('.lh-export__button', this._document); this.exportButton.addEventListener('click', this.onExportButtonClick); const dropdown = this._dom.find('.lh-export__dropdown', this._document); dropdown.addEventListener('click', this.onExport); } _setupHeaderAnimation() { const scoresWrapper = this._dom.find('.lh-scores-wrapper', this._document); this.headerOverlap = /** @type {number} */ // @ts-ignore - TODO: move off CSSOM to support other browsers (scoresWrapper.computedStyleMap().get('margin-top').value); this.headerSticky = this._dom.find('.lh-header-sticky', this._document); this.headerBackground = this._dom.find('.lh-header-bg', this._document); this.lighthouseIcon = this._dom.find('.lh-lighthouse', this._document); this.scoresWrapperBg = this._dom.find('.lh-scores-wrapper__background', this._document); this.productInfo = this._dom.find('.lh-product-info', this._document); this.toolbar = this._dom.find('.lh-toolbar', this._document); this.toolbarMetadata = this._dom.find('.lh-toolbar__metadata', this._document); // @ts-ignore - TODO: move off CSSOM to support other browsers this.headerHeight = this.headerBackground.computedStyleMap().get('height').value; this._document.addEventListener('scroll', this.onScroll, {passive: true}); const toolbarChevron = this._dom.find('.lh-toggle-arrow', this.toolbar); toolbarChevron.addEventListener('click', this.onChevronClick); } /** * Handle copy events. * @param {ClipboardEvent} e */ onCopy(e) { // Only handle copy button presses (e.g. ignore the user copying page text). if (this._copyAttempt) { // We want to write our own data to the clipboard, not the user's text selection. e.preventDefault(); e.clipboardData.setData('text/plain', JSON.stringify(this.json, null, 2)); this._fireEventOn('lh-log', this._document, { cmd: 'log', msg: 'Report JSON copied to clipboard', }); } this._copyAttempt = false; } /** * Copies the report JSON to the clipboard (if supported by the browser). */ onCopyButtonClick() { this._fireEventOn('lh-analytics', this._document, { cmd: 'send', fields: {hitType: 'event', eventCategory: 'report', eventAction: 'copy'}, }); try { if (this._document.queryCommandSupported('copy')) { this._copyAttempt = true; // Note: In Safari 10.0.1, execCommand('copy') returns true if there's // a valid text selection on the page. See http://caniuse.com/#feat=clipboard. if (!this._document.execCommand('copy')) { this._copyAttempt = false; // Prevent event handler from seeing this as a copy attempt. this._fireEventOn('lh-log', this._document, { cmd: 'warn', msg: 'Your browser does not support copy to clipboard.', }); } } } catch (/** @type {Error} */ e) { this._copyAttempt = false; this._fireEventOn('lh-log', this._document, {cmd: 'log', msg: e.message}); } } onScroll() { this.latestKnownScrollY = window.scrollY; if (!this.isAnimatingHeader) { window.requestAnimationFrame(this.animateHeader.bind(this)); } this.isAnimatingHeader = true; } onChevronClick() { const toggle = this._dom.find('.lh-config__settings-toggle', this._document); if (toggle.hasAttribute('open')) { toggle.removeAttribute('open'); } else { toggle.setAttribute('open', 'true'); } } animateHeader() { const collapsedHeaderHeight = 50; const heightDiff = this.headerHeight - collapsedHeaderHeight + this.headerOverlap; const scrollPct = Math.min(1, this.latestKnownScrollY / (this.headerHeight - collapsedHeaderHeight)); const scoresContainer = /** @type {HTMLElement} */ (this.scoresWrapperBg.parentElement); this.headerSticky.style.transform = `translateY(${heightDiff * scrollPct * -1}px)`; this.headerBackground.style.transform = `translateY(${scrollPct * this.headerOverlap}px)`; this.lighthouseIcon.style.transform = `translate3d(calc(var(--report-content-width) / 2),` + ` calc(-100% - ${scrollPct * this.headerOverlap * -1}px), 0) scale(${1 - scrollPct})`; this.lighthouseIcon.style.opacity = Math.max(0, 1 - scrollPct).toString(); // Switch up the score background & shadows this.scoresWrapperBg.style.opacity = (1 - scrollPct).toString(); this.scoresWrapperBg.style.transform = `scaleY(${1 - scrollPct * 0.2})`; const scoreShadow = this._dom.find('.lh-scores-wrapper__shadow', scoresContainer); scoreShadow.style.opacity = scrollPct.toString(); scoreShadow.style.transform = `scaleY(${1 - scrollPct * 0.2})`; // Fade & move the scorescale const scoreScalePositionDelta = 32; const scoreScale = this._dom.find('.lh-scorescale', scoresContainer); scoreScale.style.opacity = `${1 - scrollPct}`; scoreScale.style.transform = `translateY(${scrollPct * -scoreScalePositionDelta}px)`; // Move the toolbar & export this.toolbar.style.transform = `translateY(${heightDiff * scrollPct}px)`; const exportParent = this.exportButton.parentElement; if (exportParent) { exportParent.style.transform = `translateY(${heightDiff * scrollPct}px)`; } this.exportButton.style.transform = `scale(${1 - 0.2 * scrollPct})`; // Start showing the productinfo when we are at the 50% mark of our animation const opacity = scrollPct < 0.5 ? 0 : (scrollPct - 0.5) * 2; this.productInfo.style.opacity = this.toolbarMetadata.style.opacity = opacity.toString(); this.isAnimatingHeader = false; } closeExportDropdown() { this.exportButton.classList.remove('active'); } /** * Click handler for export button. * @param {Event} e */ onExportButtonClick(e) { e.preventDefault(); const el = /** @type {Element} */ (e.target); el.classList.toggle('active'); this._document.addEventListener('keydown', this.onKeyDown); } /** * Resets the state of page before capturing the page for export. * When the user opens the exported HTML page, certain UI elements should * be in their closed state (not opened) and the templates should be unstamped. */ _resetUIState() { this.closeExportDropdown(); this._dom.resetTemplates(); } /** * Handler for "export as" button. * @param {Event} e */ onExport(e) { e.preventDefault(); const el = /** @type {?Element} */ (e.target); if (!el || !el.hasAttribute('data-action')) { return; } switch (el.getAttribute('data-action')) { case 'copy': this.onCopyButtonClick(); break; case 'print-summary': this.collapseAllDetails(); this.closeExportDropdown(); self.print(); break; case 'print-expanded': this.expandAllDetails(); this.closeExportDropdown(); self.print(); break; case 'save-json': { const jsonStr = JSON.stringify(this.json, null, 2); this._saveFile(new Blob([jsonStr], {type: 'application/json'})); break; } case 'save-html': { const htmlStr = this.getReportHtml(); try { this._saveFile(new Blob([htmlStr], {type: 'text/html'})); } catch (/** @type {Error} */ e) { this._fireEventOn('lh-log', this._document, { cmd: 'error', msg: 'Could not export as HTML. ' + e.message, }); } break; } case 'open-viewer': { const viewerPath = '/lighthouse/viewer/'; ReportUIFeatures.openTabAndSendJsonReport(this.json, viewerPath); break; } case 'save-gist': { this.saveAsGist(); break; } } this.closeExportDropdown(); this._document.removeEventListener('keydown', this.onKeyDown); } /** * Keydown handler for the document. * @param {KeyboardEvent} e */ onKeyDown(e) { if (e.keyCode === 27) { // ESC this.closeExportDropdown(); } } /** * Opens a new tab to the online viewer and sends the local page's JSON results * to the online viewer using postMessage. * @param {ReportJSON} reportJson * @param {string} viewerPath * @protected */ static openTabAndSendJsonReport(reportJson, viewerPath) { const VIEWER_ORIGIN = 'https://googlechrome.github.io'; // Chrome doesn't allow us to immediately postMessage to a popup right // after it's created. Normally, we could also listen for the popup window's // load event, however it is cross-domain and won't fire. Instead, listen // for a message from the target app saying "I'm open". const json = reportJson; window.addEventListener('message', function msgHandler(/** @type {Event} */ e) { const messageEvent = /** @type {MessageEvent} */ (e); if (messageEvent.origin !== VIEWER_ORIGIN) { return; } if (popup && messageEvent.data.opened) { popup.postMessage({lhresults: json}, VIEWER_ORIGIN); window.removeEventListener('message', msgHandler); } }); // The popup's window.name is keyed by version+url+fetchTime, so we reuse/select tabs correctly // @ts-ignore - If this is a v2 LHR, use old `generatedTime`. const fallbackFetchTime = /** @type {string} */ (json.generatedTime); const fetchTime = json.fetchTime || fallbackFetchTime; const windowName = `${json.lighthouseVersion}-${json.requestedUrl}-${fetchTime}`; const popup = window.open(`${VIEWER_ORIGIN}${viewerPath}`, windowName); } /** * Expands audit details when user prints via keyboard shortcut. * @param {KeyboardEvent} e */ printShortCutDetect(e) { if ((e.ctrlKey || e.metaKey) && e.keyCode === 80) { // Ctrl+P this.closeExportDropdown(); } } /** * Expands all audit `<details>`. * Ideally, a print stylesheet could take care of this, but CSS has no way to * open a `<details>` element. */ expandAllDetails() { const details = /** @type {Array<HTMLDetailsElement>} */ (this._dom.findAll( '.lh-categories details', this._document)); details.map(detail => detail.open = true); } /** * Collapses all audit `<details>`. * open a `<details>` element. */ collapseAllDetails() { const details = /** @type {Array<HTMLDetailsElement>} */ (this._dom.findAll( '.lh-categories details', this._document)); details.map(detail => detail.open = false); } /** * Sets up listeners to collapse audit `<details>` when the user closes the * print dialog, all `<details>` are collapsed. */ _setUpCollapseDetailsAfterPrinting() { // FF and IE implement these old events. if ('onbeforeprint' in self) { self.addEventListener('afterprint', this.collapseAllDetails); } else { const win = /** @type {Window} */ (self); // Note: FF implements both window.onbeforeprint and media listeners. However, // it doesn't matchMedia doesn't fire when matching 'print'. win.matchMedia('print').addListener(mql => { if (mql.matches) { this.expandAllDetails(); } else { this.collapseAllDetails(); } }); } } /** * Returns the html that recreates this report. * @return {string} * @protected */ getReportHtml() { this._resetUIState(); return this._document.documentElement.outerHTML; } /** * Save json as a gist. Unimplemented in base UI features. * @protected */ saveAsGist() { throw new Error('Cannot save as gist from base report'); } /** * Downloads a file (blob) using a[download]. * @param {Blob|File} blob The file to save. * @private */ _saveFile(blob) { const filename = getFilenamePrefix({ finalUrl: this.json.finalUrl, fetchTime: this.json.fetchTime, }); const ext = blob.type.match('json') ? '.json' : '.html'; const href = URL.createObjectURL(blob); const a = /** @type {HTMLAnchorElement} */ (this._dom.createElement('a')); a.download = `${filename}${ext}`; a.href = href; this._document.body.appendChild(a); // Firefox requires anchor to be in the DOM. a.click(); // cleanup. this._document.body.removeChild(a); setTimeout(_ => URL.revokeObjectURL(href), 500); } } if (typeof module !== 'undefined' && module.exports) { module.exports = ReportUIFeatures; } else { self.ReportUIFeatures = ReportUIFeatures; } ; /** * @license Copyright 2017 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ 'use strict'; /* globals self, Util */ /** @typedef {import('./dom.js')} DOM */ /** @typedef {import('./report-renderer.js')} ReportRenderer */ /** @typedef {import('./report-renderer.js').AuditJSON} AuditJSON */ /** @typedef {import('./report-renderer.js').CategoryJSON} CategoryJSON */ /** @typedef {import('./report-renderer.js').GroupJSON} GroupJSON */ /** @typedef {import('./details-renderer.js')} DetailsRenderer */ /** @typedef {import('./util.js')} Util */ class CategoryRenderer { /** * @param {DOM} dom * @param {DetailsRenderer} detailsRenderer */ constructor(dom, detailsRenderer) { /** @type {DOM} */ this.dom = dom; /** @type {DetailsRenderer} */ this.detailsRenderer = detailsRenderer; /** @type {ParentNode} */ this.templateContext = this.dom.document(); this.detailsRenderer.setTemplateContext(this.templateContext); } /** * @param {AuditJSON} audit * @param {number} index * @return {Element} */ renderAudit(audit, index) { const tmpl = this.dom.cloneTemplate('#tmpl-lh-audit', this.templateContext); return this.populateAuditValues(audit, index, tmpl); } /** * Populate an DOM tree with audit details. Used by renderAudit and renderOpportunity * @param {AuditJSON} audit * @param {number} index * @param {DocumentFragment} tmpl * @return {Element} */ populateAuditValues(audit, index, tmpl) { const auditEl = this.dom.find('.lh-audit', tmpl); auditEl.id = audit.result.id; const scoreDisplayMode = audit.result.scoreDisplayMode; if (audit.result.displayValue) { const displayValue = Util.formatDisplayValue(audit.result.displayValue); this.dom.find('.lh-audit__display-text', auditEl).textContent = displayValue; } const titleEl = this.dom.find('.lh-audit__title', auditEl); titleEl.appendChild(this.dom.convertMarkdownCodeSnippets(audit.result.title)); this.dom.find('.lh-audit__description', auditEl) .appendChild(this.dom.convertMarkdownLinkSnippets(audit.result.description)); const header = /** @type {HTMLDetailsElement} */ (this.dom.find('details', auditEl)); if (audit.result.details && audit.result.details.type) { const elem = this.detailsRenderer.render(audit.result.details); elem.classList.add('lh-details'); header.appendChild(elem); } this.dom.find('.lh-audit__index', auditEl).textContent = `${index + 1}`; // Add chevron SVG to the end of the summary this.dom.find('.lh-chevron-container', auditEl).appendChild(this._createChevron()); this._setRatingClass(auditEl, audit.result.score, scoreDisplayMode); if (audit.result.scoreDisplayMode === 'error') { auditEl.classList.add(`lh-audit--error`); const textEl = this.dom.find('.lh-audit__display-text', auditEl); textEl.textContent = 'Error!'; textEl.classList.add('tooltip-boundary'); const tooltip = this.dom.createChildOf(textEl, 'div', 'tooltip tooltip--error'); tooltip.textContent = audit.result.errorMessage || 'Report error: no audit information'; } else if (audit.result.explanation) { const explEl = this.dom.createChildOf(titleEl, 'div', 'lh-audit-explanation'); explEl.textContent = audit.result.explanation; } const warnings = audit.result.warnings; if (!warnings || warnings.length === 0) return auditEl; // Add list of warnings or singular warning const warningsEl = this.dom.createChildOf(titleEl, 'div', 'lh-warnings'); if (warnings.length === 1) { warningsEl.textContent = `Warning: ${warnings.join('')}`; } else { warningsEl.textContent = 'Warnings: '; const warningsUl = this.dom.createChildOf(warningsEl, 'ul'); for (const warning of warnings) { const item = this.dom.createChildOf(warningsUl, 'li'); item.textContent = warning; } } return auditEl; } /** * @return {!HTMLElement} */ _createChevron() { const chevronTmpl = this.dom.cloneTemplate('#tmpl-lh-chevron', this.templateContext); const chevronEl = this.dom.find('.lh-chevron', chevronTmpl); return chevronEl; } /** * @param {!Element} element DOM node to populate with values. * @param {number|null} score * @param {string} scoreDisplayMode * @return {Element} */ _setRatingClass(element, score, scoreDisplayMode) { const rating = Util.calculateRating(score, scoreDisplayMode); element.classList.add(`lh-audit--${rating}`, `lh-audit--${scoreDisplayMode}`); return element; } /** * @param {CategoryJSON} category * @return {Element} */ renderCategoryHeader(category) { const tmpl = this.dom.cloneTemplate('#tmpl-lh-category-header', this.templateContext); const gaugeContainerEl = this.dom.find('.lh-score__gauge', tmpl); const gaugeEl = this.renderScoreGauge(category); gaugeContainerEl.appendChild(gaugeEl); this.dom.find('.lh-category-header__title', tmpl).appendChild( this.dom.convertMarkdownCodeSnippets(category.title)); if (category.description) { const descEl = this.dom.convertMarkdownLinkSnippets(category.description); this.dom.find('.lh-category-header__description', tmpl).appendChild(descEl); } return /** @type {Element} */ (tmpl.firstElementChild); } /** * Renders the group container for a group of audits. Individual audit elements can be added * directly to the returned element. * @param {GroupJSON} group * @param {{expandable: boolean, itemCount?: number}} opts * @return {Element} */ renderAuditGroup(group, opts) { const expandable = opts.expandable; const groupEl = this.dom.createElement(expandable ? 'details' : 'div', 'lh-audit-group'); const summmaryEl = this.dom.createChildOf(groupEl, 'summary', 'lh-audit-group__summary'); const headerEl = this.dom.createChildOf(summmaryEl, 'div', 'lh-audit-group__header'); const itemCountEl = this.dom.createChildOf(summmaryEl, 'div', 'lh-audit-group__itemcount'); if (expandable) { const chevronEl = summmaryEl.appendChild(this._createChevron()); chevronEl.title = 'Show audits'; } if (group.description) { const auditGroupDescription = this.dom.createElement('div', 'lh-audit-group__description'); auditGroupDescription.appendChild(this.dom.convertMarkdownLinkSnippets(group.description)); groupEl.appendChild(auditGroupDescription); } headerEl.textContent = group.title; if (opts.itemCount) { itemCountEl.textContent = `${opts.itemCount} audits`; } return groupEl; } /** * Find the total number of audits contained within a section. * Accounts for nested subsections like Accessibility. * @param {Array<Element>} elements * @return {number} */ _getTotalAuditsLength(elements) { // Create a scratch element to append sections to so we can reuse querySelectorAll(). const scratch = this.dom.createElement('div'); elements.forEach(function(element) { scratch.appendChild(element); }); const subAudits = scratch.querySelectorAll('.lh-audit'); if (subAudits.length) { return subAudits.length; } else { return elements.length; } } /** * @param {Array<Element>} elements * @return {Element} */ _renderFailedAuditsSection(elements) { const failedElem = this.dom.createElement('div'); failedElem.classList.add('lh-failed-audits'); elements.forEach(elem => failedElem.appendChild(elem)); return failedElem; } /** * @param {Array<Element>} elements * @return {Element} */ renderPassedAuditsSection(elements) { const passedElem = this.renderAuditGroup({ title: `Passed audits`, }, {expandable: true, itemCount: this._getTotalAuditsLength(elements)}); passedElem.classList.add('lh-passed-audits'); elements.forEach(elem => passedElem.appendChild(elem)); return passedElem; } /** * @param {Array<Element>} elements * @return {Element} */ _renderNotApplicableAuditsSection(elements) { const notApplicableElem = this.renderAuditGroup({ title: `Not applicable`, }, {expandable: true, itemCount: this._getTotalAuditsLength(elements)}); notApplicableElem.classList.add('lh-audit-group--not-applicable'); elements.forEach(elem => notApplicableElem.appendChild(elem)); return notApplicableElem; } /** * @param {Array<AuditJSON>} manualAudits * @param {string} manualDescription * @return {Element} */ _renderManualAudits(manualAudits, manualDescription) { const group = {title: 'Additional items to manually check', description: manualDescription}; const auditGroupElem = this.renderAuditGroup(group, {expandable: true, itemCount: manualAudits.length}); auditGroupElem.classList.add('lh-audit-group--manual'); manualAudits.forEach((audit, i) => { auditGroupElem.appendChild(this.renderAudit(audit, i)); }); return auditGroupElem; } /** * @param {ParentNode} context */ setTemplateContext(context) { this.templateContext = context; this.detailsRenderer.setTemplateContext(context); } /** * @param {CategoryJSON} category * @return {DocumentFragment} */ renderScoreGauge(category) { const tmpl = this.dom.cloneTemplate('#tmpl-lh-gauge', this.templateContext); const wrapper = /** @type {HTMLAnchorElement} */ (this.dom.find('.lh-gauge__wrapper', tmpl)); wrapper.href = `#${category.id}`; wrapper.classList.add(`lh-gauge__wrapper--${Util.calculateRating(category.score)}`); // Cast `null` to 0 const numericScore = Number(category.score); const gauge = this.dom.find('.lh-gauge', tmpl); // 329 is ~= 2 * Math.PI * gauge radius (53) // https://codepen.io/xgad/post/svg-radial-progress-meters // score of 50: `stroke-dasharray: 164.5 329`; /** @type {?SVGCircleElement} */ const gaugeArc = gauge.querySelector('.lh-gauge-arc'); if (gaugeArc) { gaugeArc.style.strokeDasharray = `${numericScore * 329} 329`; } const scoreOutOf100 = Math.round(numericScore * 100); const percentageEl = this.dom.find('.lh-gauge__percentage', tmpl); percentageEl.textContent = scoreOutOf100.toString(); if (category.score === null) { percentageEl.textContent = '?'; percentageEl.title = 'Errors occurred while auditing'; } this.dom.find('.lh-gauge__label', tmpl).textContent = category.title; return tmpl; } /** * @param {CategoryJSON} category * @param {Object<string, GroupJSON>} groupDefinitions * @return {Element} */ render(category, groupDefinitions) { const element = this.dom.createElement('div', 'lh-category'); this.createPermalinkSpan(element, category.id); element.appendChild(this.renderCategoryHeader(category)); const auditRefs = category.auditRefs; const manualAudits = auditRefs.filter(audit => audit.result.scoreDisplayMode === 'manual'); const nonManualAudits = auditRefs.filter(audit => !manualAudits.includes(audit)); /** @type {Object<string, {passed: Array<AuditJSON>, failed: Array<AuditJSON>, notApplicable: Array<AuditJSON>}>} */ const auditsGroupedByGroup = {}; const auditsUngrouped = {passed: [], failed: [], notApplicable: []}; nonManualAudits.forEach(auditRef => { let group; if (auditRef.group) { const groupId = auditRef.group; if (auditsGroupedByGroup[groupId]) { group = auditsGroupedByGroup[groupId]; } else { group = {passed: [], failed: [], notApplicable: []}; auditsGroupedByGroup[groupId] = group; } } else { group = auditsUngrouped; } if (auditRef.result.scoreDisplayMode === 'not-applicable') { group.notApplicable.push(auditRef); } else if (Util.showAsPassed(auditRef.result)) { group.passed.push(auditRef); } else { group.failed.push(auditRef); } }); const failedElements = /** @type {Array<Element>} */ ([]); const passedElements = /** @type {Array<Element>} */ ([]); const notApplicableElements = /** @type {Array<Element>} */ ([]); auditsUngrouped.failed.forEach((/** @type {AuditJSON} */ audit, i) => failedElements.push(this.renderAudit(audit, i))); auditsUngrouped.passed.forEach((/** @type {AuditJSON} */ audit, i) => passedElements.push(this.renderAudit(audit, i))); auditsUngrouped.notApplicable.forEach((/** @type {AuditJSON} */ audit, i) => notApplicableElements.push(this.renderAudit(audit, i))); Object.keys(auditsGroupedByGroup).forEach(groupId => { const group = groupDefinitions[groupId]; const groups = auditsGroupedByGroup[groupId]; if (groups.failed.length) { const auditGroupElem = this.renderAuditGroup(group, {expandable: false}); groups.failed.forEach((item, i) => auditGroupElem.appendChild(this.renderAudit(item, i))); auditGroupElem.classList.add('lh-audit-group--unadorned'); failedElements.push(auditGroupElem); } if (groups.passed.length) { const auditGroupElem = this.renderAuditGroup(group, {expandable: true}); groups.passed.forEach((item, i) => auditGroupElem.appendChild(this.renderAudit(item, i))); auditGroupElem.classList.add('lh-audit-group--unadorned'); passedElements.push(auditGroupElem); } if (groups.notApplicable.length) { const auditGroupElem = this.renderAuditGroup(group, {expandable: true}); groups.notApplicable.forEach((item, i) => auditGroupElem.appendChild(this.renderAudit(item, i))); auditGroupElem.classList.add('lh-audit-group--unadorned'); notApplicableElements.push(auditGroupElem); } }); if (failedElements.length) { const failedElem = this._renderFailedAuditsSection(failedElements); element.appendChild(failedElem); } if (manualAudits.length) { const manualEl = this._renderManualAudits(manualAudits, category.manualDescription); element.appendChild(manualEl); } if (passedElements.length) { const passedElem = this.renderPassedAuditsSection(passedElements); element.appendChild(passedElem); } if (notApplicableElements.length) { const notApplicableElem = this._renderNotApplicableAuditsSection(notApplicableElements); element.appendChild(notApplicableElem); } return element; } /** * Create a non-semantic span used for hash navigation of categories * @param {Element} element * @param {string} id */ createPermalinkSpan(element, id) { const permalinkEl = this.dom.createChildOf(element, 'span', 'lh-permalink'); permalinkEl.id = id; } } if (typeof module !== 'undefined' && module.exports) { module.exports = CategoryRenderer; } else { self.CategoryRenderer = CategoryRenderer; } ; /** * @license Copyright 2018 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ 'use strict'; /* globals self, Util, CategoryRenderer */ /** @typedef {import('./dom.js')} DOM */ /** @typedef {import('./report-renderer.js').CategoryJSON} CategoryJSON */ /** @typedef {import('./report-renderer.js').GroupJSON} GroupJSON */ /** @typedef {import('./report-renderer.js').AuditJSON} AuditJSON */ /** @typedef {import('./details-renderer.js').OpportunitySummary} OpportunitySummary */ /** @typedef {import('./details-renderer.js').FilmstripDetails} FilmstripDetails */ class PerformanceCategoryRenderer extends CategoryRenderer { /** * @param {AuditJSON} audit * @return {Element} */ _renderMetric(audit) { const tmpl = this.dom.cloneTemplate('#tmpl-lh-metric', this.templateContext); const element = this.dom.find('.lh-metric', tmpl); element.id = audit.result.id; const rating = Util.calculateRating(audit.result.score, audit.result.scoreDisplayMode); element.classList.add(`lh-metric--${rating}`); const titleEl = this.dom.find('.lh-metric__title', tmpl); titleEl.textContent = audit.result.title; const valueEl = this.dom.find('.lh-metric__value', tmpl); valueEl.textContent = Util.formatDisplayValue(audit.result.displayValue); const descriptionEl = this.dom.find('.lh-metric__description', tmpl); descriptionEl.appendChild(this.dom.convertMarkdownLinkSnippets(audit.result.description)); if (audit.result.scoreDisplayMode === 'error') { descriptionEl.textContent = ''; valueEl.textContent = 'Error!'; const tooltip = this.dom.createChildOf(descriptionEl, 'span'); tooltip.textContent = audit.result.errorMessage || 'Report error: no metric information'; } return element; } /** * @param {AuditJSON} audit * @param {number} index * @param {number} scale * @return {Element} */ _renderOpportunity(audit, index, scale) { const oppTmpl = this.dom.cloneTemplate('#tmpl-lh-opportunity', this.templateContext); const element = this.populateAuditValues(audit, index, oppTmpl); element.id = audit.result.id; const details = audit.result.details; if (!details) { return element; } const summaryInfo = /** @type {OpportunitySummary} */ (details.summary); if (!summaryInfo || !summaryInfo.wastedMs || audit.result.scoreDisplayMode === 'error') { return element; } // Overwrite the displayValue with opportunity's wastedMs const displayEl = this.dom.find('.lh-audit__display-text', element); const sparklineWidthPct = `${summaryInfo.wastedMs / scale * 100}%`; this.dom.find('.lh-sparkline__bar', element).style.width = sparklineWidthPct; displayEl.textContent = Util.formatSeconds(summaryInfo.wastedMs, 0.01); // Set [title] tooltips if (audit.result.displayValue) { const displayValue = Util.formatDisplayValue(audit.result.displayValue); this.dom.find('.lh-load-opportunity__sparkline', element).title = displayValue; displayEl.title = displayValue; } return element; } /** * Get an audit's wastedMs to sort the opportunity by, and scale the sparkline width * Opportunties with an error won't have a summary object, so MIN_VALUE is returned to keep any * erroring opportunities last in sort order. * @param {AuditJSON} audit * @return {number} */ _getWastedMs(audit) { if ( audit.result.details && audit.result.details.summary && typeof audit.result.details.summary.wastedMs === 'number' ) { return audit.result.details.summary.wastedMs; } else { return Number.MIN_VALUE; } } /** * @param {CategoryJSON} category * @param {Object<string, GroupJSON>} groups * @return {Element} * @override */ render(category, groups) { const element = this.dom.createElement('div', 'lh-category'); this.createPermalinkSpan(element, category.id); element.appendChild(this.renderCategoryHeader(category)); // Metrics const metricAudits = category.auditRefs.filter(audit => audit.group === 'metrics'); const metricAuditsEl = this.renderAuditGroup(groups['metrics'], {expandable: false}); const keyMetrics = metricAudits.filter(a => a.weight >= 3); const otherMetrics = metricAudits.filter(a => a.weight < 3); const metricsBoxesEl = this.dom.createChildOf(metricAuditsEl, 'div', 'lh-metric-container'); const metricsColumn1El = this.dom.createChildOf(metricsBoxesEl, 'div', 'lh-metric-column'); const metricsColumn2El = this.dom.createChildOf(metricsBoxesEl, 'div', 'lh-metric-column'); keyMetrics.forEach(item => { metricsColumn1El.appendChild(this._renderMetric(item)); }); otherMetrics.forEach(item => { metricsColumn2El.appendChild(this._renderMetric(item)); }); const estValuesEl = this.dom.createChildOf(metricsColumn2El, 'div', 'lh-metrics__disclaimer lh-metrics__disclaimer'); estValuesEl.textContent = 'Values are estimated and may vary.'; metricAuditsEl.classList.add('lh-audit-group--metrics'); element.appendChild(metricAuditsEl); // Filmstrip const timelineEl = this.dom.createChildOf(element, 'div', 'lh-filmstrip-container'); const thumbnailAudit = category.auditRefs.find(audit => audit.id === 'screenshot-thumbnails'); const thumbnailResult = thumbnailAudit && thumbnailAudit.result; if (thumbnailResult && thumbnailResult.details) { timelineEl.id = thumbnailResult.id; const filmstripEl = this.detailsRenderer.render(thumbnailResult.details); timelineEl.appendChild(filmstripEl); } // Opportunities const opportunityAudits = category.auditRefs .filter(audit => audit.group === 'load-opportunities' && !Util.showAsPassed(audit.result)) .sort((auditA, auditB) => this._getWastedMs(auditB) - this._getWastedMs(auditA)); if (opportunityAudits.length) { // Scale the sparklines relative to savings, minimum 2s to not overstate small savings const minimumScale = 2000; const wastedMsValues = opportunityAudits.map(audit => this._getWastedMs(audit)); const maxWaste = Math.max(...wastedMsValues); const scale = Math.max(Math.ceil(maxWaste / 1000) * 1000, minimumScale); const groupEl = this.renderAuditGroup(groups['load-opportunities'], {expandable: false}); const tmpl = this.dom.cloneTemplate('#tmpl-lh-opportunity-header', this.templateContext); const headerEl = this.dom.find('.lh-load-opportunity__header', tmpl); groupEl.appendChild(headerEl); opportunityAudits.forEach((item, i) => groupEl.appendChild(this._renderOpportunity(item, i, scale))); groupEl.classList.add('lh-audit-group--opportunities'); element.appendChild(groupEl); } // Diagnostics const diagnosticAudits = category.auditRefs .filter(audit => audit.group === 'diagnostics' && !Util.showAsPassed(audit.result)) .sort((a, b) => { const scoreA = a.result.scoreDisplayMode === 'informative' ? 100 : Number(a.result.score); const scoreB = b.result.scoreDisplayMode === 'informative' ? 100 : Number(b.result.score); return scoreA - scoreB; }); if (diagnosticAudits.length) { const groupEl = this.renderAuditGroup(groups['diagnostics'], {expandable: false}); diagnosticAudits.forEach((item, i) => groupEl.appendChild(this.renderAudit(item, i))); groupEl.classList.add('lh-audit-group--diagnostics'); element.appendChild(groupEl); } // Passed audits const passedElements = category.auditRefs .filter(audit => (audit.group === 'load-opportunities' || audit.group === 'diagnostics') && Util.showAsPassed(audit.result)) .map((audit, i) => this.renderAudit(audit, i)); if (!passedElements.length) return element; const passedElem = this.renderPassedAuditsSection(passedElements); element.appendChild(passedElem); return element; } } if (typeof module !== 'undefined' && module.exports) { module.exports = PerformanceCategoryRenderer; } else { self.PerformanceCategoryRenderer = PerformanceCategoryRenderer; } ; /** * @license Copyright 2017 Google Inc. All Rights Reserved. * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ 'use strict'; /** * @fileoverview The entry point for rendering the Lighthouse report based on the JSON output. * This file is injected into the report HTML along with the JSON report. * * Dummy text for ensuring report robustness: \\u003c/script> pre$`post %%LIGHTHOUSE_JSON%% */ /** @typedef {import('./dom.js')} DOM */ /** @typedef {import('./details-renderer.js').DetailsJSON} DetailsJSON */ /* globals self, Util, DetailsRenderer, CategoryRenderer, PerformanceCategoryRenderer */ class ReportRenderer { /** * @param {DOM} dom */ constructor(dom) { /** @type {DOM} */ this._dom = dom; /** @type {ParentNode} */ this._templateContext = this._dom.document(); } /** * @param {ReportJSON} report * @param {Element} container Parent element to render the report into. */ renderReport(report, container) { // If any mutations happen to the report within the renderers, we want the original object untouched const clone = /** @type {ReportJSON} */ (JSON.parse(JSON.stringify(report))); // TODO(phulce): we all agree this is technical debt we should fix if (typeof clone.categories !== 'object') throw new Error('No categories provided.'); clone.reportCategories = Object.values(clone.categories); ReportRenderer.smooshAuditResultsIntoCategories(clone.audits, clone.reportCategories); container.textContent = ''; // Remove previous report. container.appendChild(this._renderReport(clone)); return /** @type {Element} **/ (container); } /** * Define a custom element for <templates> to be extracted from. For example: * this.setTemplateContext(new DOMParser().parseFromString(htmlStr, 'text/html')) * @param {ParentNode} context */ setTemplateContext(context) { this._templateContext = context; } /** * @param {ReportJSON} report * @return {DocumentFragment} */ _renderReportHeader(report) { const el = this._dom.cloneTemplate('#tmpl-lh-heading', this._templateContext); const domFragment = this._dom.cloneTemplate('#tmpl-lh-scores-wrapper', this._templateContext); const placeholder = this._dom.find('.lh-scores-wrapper-placeholder', el); /** @type {HTMLDivElement} */ (placeholder.parentNode).replaceChild(domFragment, placeholder); this._dom.find('.lh-config__timestamp', el).textContent = Util.formatDateTime(report.fetchTime); this._dom.find('.lh-product-info__version', el).textContent = report.lighthouseVersion; const metadataUrl = /** @type {HTMLAnchorElement} */ (this._dom.find('.lh-metadata__url', el)); const toolbarUrl = /** @type {HTMLAnchorElement}*/ (this._dom.find('.lh-toolbar__url', el)); metadataUrl.href = metadataUrl.textContent = report.finalUrl; toolbarUrl.href = toolbarUrl.textContent = report.finalUrl; const emulationDescriptions = Util.getEmulationDescriptions(report.configSettings || {}); this._dom.find('.lh-config__emulation', el).textContent = emulationDescriptions.summary; return el; } /** * @return {Element} */ _renderReportShortHeader() { const shortHeaderContainer = this._dom.createElement('div', 'lh-header-container'); const wrapper = this._dom.cloneTemplate('#tmpl-lh-scores-wrapper', this._templateContext); shortHeaderContainer.appendChild(wrapper); return shortHeaderContainer; } /** * @param {ReportJSON} report * @return {DocumentFragment} */ _renderReportFooter(report) { const footer = this._dom.cloneTemplate('#tmpl-lh-footer', this._templateContext); const env = this._dom.find('.lh-env__items', footer); env.id = 'runtime-settings'; const envValues = Util.getEnvironmentDisplayValues(report.configSettings || {}); [ {name: 'URL', description: report.finalUrl}, {name: 'Fetch time', description: Util.formatDateTime(report.fetchTime)}, ...envValues, {name: 'User agent', description: report.userAgent}, ].forEach(runtime => { const item = this._dom.cloneTemplate('#tmpl-lh-env__items', env); this._dom.find('.lh-env__name', item).textContent = `${runtime.name}:`; this._dom.find('.lh-env__description', item).textContent = runtime.description; env.appendChild(item); }); this._dom.find('.lh-footer__version', footer).textContent = report.lighthouseVersion; return footer; } /** * Returns a div with a list of top-level warnings, or an empty div if no warnings. * @param {ReportJSON} report * @return {Node} */ _renderReportWarnings(report) { if (!report.runWarnings || report.runWarnings.length === 0) { return this._dom.createElement('div'); } const container = this._dom.cloneTemplate('#tmpl-lh-warnings--toplevel', this._templateContext); const warnings = this._dom.find('ul', container); for (const warningString of report.runWarnings) { const warning = warnings.appendChild(this._dom.createElement('li')); warning.textContent = warningString; } return container; } /** * @param {ReportJSON} report * @return {DocumentFragment} */ _renderReport(report) { let header; const headerContainer = this._dom.createElement('div'); if (this._dom.isDevTools()) { headerContainer.classList.add('lh-header-plain'); header = this._renderReportShortHeader(); } else { headerContainer.classList.add('lh-header-sticky'); header = this._renderReportHeader(report); } headerContainer.appendChild(header); const scoresContainer = this._dom.find('.lh-scores-container', headerContainer); const container = this._dom.createElement('div', 'lh-container'); const reportSection = container.appendChild(this._dom.createElement('div', 'lh-report')); reportSection.appendChild(this._renderReportWarnings(report)); let scoreHeader; const isSoloCategory = report.reportCategories.length === 1; if (!isSoloCategory) { scoreHeader = this._dom.createElement('div', 'lh-scores-header'); } else { headerContainer.classList.add('lh-header--solo-category'); } const detailsRenderer = new DetailsRenderer(this._dom); const categoryRenderer = new CategoryRenderer(this._dom, detailsRenderer); categoryRenderer.setTemplateContext(this._templateContext); const perfCategoryRenderer = new PerformanceCategoryRenderer(this._dom, detailsRenderer); perfCategoryRenderer.setTemplateContext(this._templateContext); const categories = reportSection.appendChild(this._dom.createElement('div', 'lh-categories')); for (const category of report.reportCategories) { if (scoreHeader) { scoreHeader.appendChild(categoryRenderer.renderScoreGauge(category)); } let renderer = categoryRenderer; if (category.id === 'performance') { renderer = perfCategoryRenderer; } categories.appendChild(renderer.render(category, report.categoryGroups)); } if (scoreHeader) { const scoreScale = this._dom.cloneTemplate('#tmpl-lh-scorescale', this._templateContext); scoresContainer.appendChild(scoreHeader); scoresContainer.appendChild(scoreScale); } reportSection.appendChild(this._renderReportFooter(report)); const reportFragment = this._dom.createFragment(); reportFragment.appendChild(headerContainer); reportFragment.appendChild(container); return reportFragment; } /** * Place the AuditResult into the auditDfn (which has just weight & group) * @param {Object<string, LH.Audit.Result>} audits * @param {Array<CategoryJSON>} reportCategories */ static smooshAuditResultsIntoCategories(audits, reportCategories) { for (const category of reportCategories) { category.auditRefs.forEach(auditMeta => { const result = audits[auditMeta.id]; auditMeta.result = result; }); } } } if (typeof module !== 'undefined' && module.exports) { module.exports = ReportRenderer; } else { self.ReportRenderer = ReportRenderer; } /** * @typedef {{ id: string, score: (number|null), weight: number, group?: string, result: LH.Audit.Result }} AuditJSON */ /** * @typedef {{ title: string, id: string, score: (number|null), description?: string, manualDescription: string, auditRefs: Array<AuditJSON> }} CategoryJSON */ /** * @typedef {{ title: string, description?: string, }} GroupJSON */ /** * @typedef {{ lighthouseVersion: string, userAgent: string, fetchTime: string, timing: {total: number}, requestedUrl: string, finalUrl: string, runWarnings?: Array<string>, artifacts: {traces: {defaultPass: {traceEvents: Array}}}, audits: Object<string, LH.Audit.Result>, categories: Object<string, CategoryJSON>, reportCategories: Array<CategoryJSON>, categoryGroups: Object<string, GroupJSON>, configSettings: LH.Config.Settings, }} ReportJSON */ //# sourceURL=compiled-reportrenderer.js </script> <script>window.__LIGHTHOUSE_JSON__ = {"userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36","lighthouseVersion":"3.0.0-beta.0","fetchTime":"2018-06-07T05:45:16.887Z","requestedUrl":"https://www.robertgabriel.ninja/","finalUrl":"https://www.robertgabriel.ninja/","runWarnings":[],"audits":{"is-on-https":{"id":"is-on-https","title":"Uses HTTPS","description":"All sites should be protected with HTTPS, even ones that don't handle sensitive data. HTTPS prevents intruders from tampering with or passively listening in on the communications between your app and your users, and is a prerequisite for HTTP/2 and many new web platform APIs. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/https).","score":1,"scoreDisplayMode":"binary","rawValue":true,"displayValue":"","details":{"type":"table","headings":[],"items":[]}},"redirects-http":{"id":"redirects-http","title":"Redirects HTTP traffic to HTTPS","description":"If you've already set up HTTPS, make sure that you redirect all HTTP traffic to HTTPS. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/http-redirects-to-https).","score":1,"scoreDisplayMode":"binary","rawValue":true},"service-worker":{"id":"service-worker","title":"Registers a service worker","description":"The service worker is the technology that enables your app to use many Progressive Web App features, such as offline, add to homescreen, and push notifications. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/registered-service-worker).","score":1,"scoreDisplayMode":"binary","rawValue":true},"works-offline":{"id":"works-offline","title":"Responds with a 200 when offline","description":"If you're building a Progressive Web App, consider using a service worker so that your app can work offline. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/http-200-when-offline).","score":1,"scoreDisplayMode":"binary","rawValue":true,"warnings":[]},"viewport":{"id":"viewport","title":"Has a `\\u003cmeta name=\"viewport\">` tag with `width` or `initial-scale`","description":"Add a viewport meta tag to optimize your app for mobile screens. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/has-viewport-meta-tag).","score":1,"scoreDisplayMode":"binary","rawValue":true,"warnings":[]},"without-javascript":{"id":"without-javascript","title":"Contains some content when JavaScript is not available","description":"Your app should display some content when JavaScript is disabled, even if it's just a warning to the user that JavaScript is required to use the app. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/no-js).","score":1,"scoreDisplayMode":"binary","rawValue":true},"first-contentful-paint":{"id":"first-contentful-paint","title":"First Contentful Paint","description":"First contentful paint marks the time at which the first text/image is painted. [Learn more](https://developers.google.com/web/fundamentals/performance/user-centric-performance-metrics#first_paint_and_first_contentful_paint).","score":1,"scoreDisplayMode":"numeric","rawValue":1376.27205,"displayValue":["%10d ms",1376.27205]},"first-meaningful-paint":{"id":"first-meaningful-paint","title":"First Meaningful Paint","description":"First Meaningful Paint measures when the primary content of a page is visible. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-meaningful-paint).","score":0.99,"scoreDisplayMode":"numeric","rawValue":1595.1969000000001,"displayValue":["%10d ms",1595.1969000000001]},"load-fast-enough-for-pwa":{"id":"load-fast-enough-for-pwa","title":"Page load is fast enough on 3G","description":"A fast page load over a 3G network ensures a good mobile user experience. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/fast-3g).","score":1,"scoreDisplayMode":"binary","rawValue":2056.234925},"speed-index":{"id":"speed-index","title":"Speed Index","description":"Speed Index shows how quickly the contents of a page are visibly populated. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/speed-index).","score":1,"scoreDisplayMode":"numeric","rawValue":1376.27205,"displayValue":["%10d ms",1376.27205]},"screenshot-thumbnails":{"id":"screenshot-thumbnails","title":"Screenshot Thumbnails","description":"This is what the load of your site looked like.","score":null,"scoreDisplayMode":"informative","rawValue":true,"details":{"type":"filmstrip","scale":3000,"items":[{"timing":300,"timestamp":35454022625,"data":"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"},{"timing":600,"timestamp":35454322625,"data":"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"},{"timing":900,"timestamp":35454622625,"data":"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"},{"timing":1200,"timestamp":35454922625,"data":"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"},{"timing":1500,"timestamp":35455222625,"data":"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"},{"timing":1800,"timestamp":35455522625,"data":"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"},{"timing":2100,"timestamp":35455822625,"data":"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"},{"timing":2400,"timestamp":35456122625,"data":"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"},{"timing":2700,"timestamp":35456422625,"data":"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"},{"timing":3000,"timestamp":35456722625,"data":"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"}]}},"estimated-input-latency":{"id":"estimated-input-latency","title":"Estimated Input Latency","description":"The score above is an estimate of how long your app takes to respond to user input, in milliseconds, during the busiest 5s window of page load. If your latency is higher than 50 ms, users may perceive your app as laggy. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/estimated-input-latency).","score":1,"scoreDisplayMode":"numeric","rawValue":12.8,"displayValue":["%d ms",12.8]},"errors-in-console":{"id":"errors-in-console","title":"No browser errors logged to the console","description":"Errors logged to the console indicate unresolved problems. They can come from network request failures and other browser concerns.","score":1,"scoreDisplayMode":"binary","rawValue":0,"details":{"type":"table","headings":[],"items":[]}},"time-to-first-byte":{"id":"time-to-first-byte","title":"Keep server response times low (TTFB)","description":"Time To First Byte identifies the time at which your server sends a response. [Learn more](https://developers.google.com/web/tools/chrome-devtools/network-performance/issues).","score":1,"scoreDisplayMode":"binary","rawValue":74.58,"displayValue":"","details":{"summary":{"wastedMs":-525.42}}},"first-cpu-idle":{"id":"first-cpu-idle","title":"First CPU Idle","description":"First CPU Idle marks the first time at which the page's main thread is quiet enough to handle input. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/first-interactive).","score":1,"scoreDisplayMode":"numeric","rawValue":1832.7926000000002,"displayValue":["%10d ms",1832.7926000000002]},"interactive":{"id":"interactive","title":"Time to Interactive","description":"Interactive marks the time at which the page is fully interactive. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/consistently-interactive).","score":0.99,"scoreDisplayMode":"numeric","rawValue":2056.234925,"displayValue":["%10d ms",2056.234925]},"user-timings":{"id":"user-timings","title":"User Timing marks and measures","description":"Consider instrumenting your app with the User Timing API to create custom, real-world measurements of key user experiences. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/user-timing).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true,"displayValue":"","details":{"type":"table","headings":[],"items":[]}},"critical-request-chains":{"id":"critical-request-chains","title":"Critical Request Chains","description":"The Critical Request Chains below show you what resources are issued with a high priority. Consider reducing the length of chains, reducing the download size of resources, or deferring the download of unnecessary resources to improve page load. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/critical-request-chains).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true,"displayValue":"","details":{"type":"criticalrequestchain","header":{"type":"text","text":"View critical network waterfall:"},"chains":{"39A06CBF5C1F8C59434395266BABD9A2":{"request":{"url":"https://www.robertgabriel.ninja/","startTime":35453.725033,"endTime":35453.813857,"_responseReceivedTime":35453.801468000005,"transferSize":35450},"children":{}}},"longestChain":{"duration":88.82399999856716,"length":1,"transferSize":35450}}},"redirects":{"id":"redirects","title":"Avoid multiple page redirects","description":"Redirects introduce additional delays before the page can be loaded. [Learn more](https://developers.google.com/speed/dist/insights/AvoidRedirects).","score":1,"scoreDisplayMode":"numeric","rawValue":0,"displayValue":["%d ms",0],"details":{"type":"table","headings":[],"items":[],"summary":{"wastedMs":0}}},"webapp-install-banner":{"id":"webapp-install-banner","title":"User can be prompted to Install the Web App","description":"Browsers can proactively prompt users to add your app to their homescreen, which can lead to higher engagement. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/install-prompt).","score":1,"scoreDisplayMode":"binary","rawValue":true,"warnings":[]},"splash-screen":{"id":"splash-screen","title":"Configured for a custom splash screen","description":"A themed splash screen ensures a high-quality experience when users launch your app from their homescreens. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/custom-splash-screen).","score":1,"scoreDisplayMode":"binary","rawValue":true},"themed-omnibox":{"id":"themed-omnibox","title":"Address bar matches brand colors","description":"The browser address bar can be themed to match your site. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/address-bar).","score":1,"scoreDisplayMode":"binary","rawValue":true},"manifest-short-name-length":{"id":"manifest-short-name-length","title":"Manifest's `short_name` won't be truncated when displayed on homescreen","description":"Make your app's `short_name` fewer than 12 characters to ensure that it's not truncated on homescreens. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/manifest-short_name-is-not-truncated).","score":1,"scoreDisplayMode":"binary","rawValue":true},"content-width":{"id":"content-width","title":"Content is sized correctly for the viewport","description":"If the width of your app's content doesn't match the width of the viewport, your app might not be optimized for mobile screens. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/content-sized-correctly-for-viewport).","score":1,"scoreDisplayMode":"binary","rawValue":true,"explanation":""},"image-aspect-ratio":{"id":"image-aspect-ratio","title":"Displays images with correct aspect ratio","description":"Image display dimensions should match natural aspect ratio.","score":1,"scoreDisplayMode":"binary","rawValue":true,"warnings":[],"details":{"type":"table","headings":[],"items":[]}},"deprecations":{"id":"deprecations","title":"Avoids deprecated APIs","description":"Deprecated APIs will eventually be removed from the browser. [Learn more](https://www.chromestatus.com/features#deprecated).","score":1,"scoreDisplayMode":"binary","rawValue":true,"displayValue":"","details":{"type":"table","headings":[],"items":[]}},"mainthread-work-breakdown":{"id":"mainthread-work-breakdown","title":"Minimizes main thread work","description":"Consider reducing the time spent parsing, compiling and executing JS. You may find delivering smaller JS payloads helps with this.","score":1,"scoreDisplayMode":"numeric","rawValue":274.6520000100136,"displayValue":["%10d ms",274.6520000100136],"details":{"type":"table","headings":[{"key":"group","itemType":"text","text":"Category"},{"key":"category","itemType":"text","text":"Work"},{"key":"duration","itemType":"ms","granularity":1,"text":"Time spent"}],"items":[{"category":"Layout","group":"Style & Layout","duration":62.712000012397766},{"category":"Recalculate Style","group":"Style & Layout","duration":49.4480000436306},{"category":"Parse HTML","group":"Parsing HTML & CSS","duration":79.36399999260902},{"category":"Evaluate Script","group":"Script Evaluation","duration":41.76399999856949},{"category":"Run Microtasks","group":"Script Evaluation","duration":4.504000008106232},{"category":"Update Layer Tree","group":"Compositing","duration":11.191999971866608},{"category":"Composite Layers","group":"Compositing","duration":6.843999981880188},{"category":"DOM GC","group":"Garbage collection","duration":10.532000005245209},{"category":"Paint","group":"Paint","duration":6.819999992847443},{"category":"Compile Script","group":"Script Parsing & Compile","duration":1.472000002861023}]}},"bootup-time":{"id":"bootup-time","title":"JavaScript boot-up time","description":"Consider reducing the time spent parsing, compiling, and executing JS. You may find delivering smaller JS payloads helps with this. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/bootup).","score":1,"scoreDisplayMode":"numeric","rawValue":136.9440000653267,"displayValue":["%10d ms",136.9440000653267],"details":{"type":"table","headings":[],"items":[],"summary":{"wastedMs":136.9440000653267}}},"uses-rel-preload":{"id":"uses-rel-preload","title":"Preload key requests","description":"Consider using \u003clink rel=preload> to prioritize fetching late-discovered resources sooner. [Learn more](https://developers.google.com/web/updates/2016/03/link-rel-preload).","score":1,"scoreDisplayMode":"numeric","rawValue":0,"displayValue":["Potential savings of %10d ms",0],"details":{"type":"table","headings":[],"items":[],"summary":{"wastedMs":0}}},"uses-rel-preconnect":{"id":"uses-rel-preconnect","title":"Avoid multiple, costly round trips to any origin","description":"Consider adding preconnect or dns-prefetch resource hints to establish early connections to important third-party origins. [Learn more](https://developers.google.com/web/fundamentals/performance/resource-prioritization#preconnect).","score":1,"scoreDisplayMode":"numeric","rawValue":0,"displayValue":["Potential savings of %10d ms",0],"details":{"type":"table","headings":[],"items":[],"summary":{"wastedMs":0}}},"font-display":{"id":"font-display","title":"All text remains visible during webfont loads","description":"Leverage the font-display CSS feature to ensure text is user-visible while webfonts are loading. [Learn more](https://developers.google.com/web/updates/2016/02/font-display).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"network-requests":{"id":"network-requests","title":"Network Requests","description":"Lists the network requests that were made during page load.","score":null,"scoreDisplayMode":"informative","rawValue":7,"details":{"type":"table","headings":[{"key":"url","itemType":"url","text":"URL"},{"key":"startTime","itemType":"ms","granularity":1,"text":"Start Time"},{"key":"endTime","itemType":"ms","granularity":1,"text":"End Time"},{"key":"transferSize","itemType":"bytes","displayUnit":"kb","granularity":1,"text":"Transfer Size"},{"key":"statusCode","itemType":"text","text":"Status Code"},{"key":"mimeType","itemType":"text","text":"MIME Type"},{"key":"resourceType","itemType":"text","text":"Resource Type"}],"items":[{"url":"https://www.robertgabriel.ninja/","startTime":0,"endTime":88.82399999856716,"transferSize":35450,"statusCode":200,"mimeType":"text/html","resourceType":"document"},{"url":"https://www.robertgabriel.ninja/assets/img/me/me-4.webp","startTime":103.3229999957257,"endTime":132.81799999822397,"transferSize":2181,"statusCode":200,"mimeType":"image/webp","resourceType":"image"},{"url":"https://ajax.cloudflare.com/cdn-cgi/scripts/4f936b58/cloudflare-static/rocket-loader.min.js","startTime":122.6909999968484,"endTime":160.93899999395944,"transferSize":3557,"statusCode":200,"mimeType":"application/javascript","resourceType":"script"},{"url":"https://www.robertgabriel.ninja/assets/js/app.min.js","startTime":162.7909999951953,"endTime":175.3889999963576,"transferSize":0,"statusCode":0,"resourceType":"script"},{"url":"https://www.robertgabriel.ninja/assets/js/vendor.min.js","startTime":164.55199999472825,"endTime":200.98199999483768,"transferSize":23771,"statusCode":200,"mimeType":"application/javascript","resourceType":"script"},{"url":"https://www.robertgabriel.ninja/assets/js/app.min.js","startTime":216.44799999921815,"endTime":239.4189999977243,"transferSize":551,"statusCode":200,"mimeType":"application/javascript","resourceType":"script"},{"url":"data:image/webp;base64,UklGRjoAAABXRUJQVlA4IC4AAACyAgCdASoCAAIALmk0mk0iIiIiIgBoSygABc6WWgAA/veff/0PP","startTime":241.57899999408983,"endTime":241.62999999680324,"transferSize":0,"statusCode":200,"mimeType":"image/webp","resourceType":"image"}]}},"metrics":{"id":"metrics","title":"Metrics","description":"Collects all available metrics.","score":null,"scoreDisplayMode":"informative","rawValue":2056.234925,"details":{"items":[{"firstContentfulPaint":1376,"firstMeaningfulPaint":1595,"firstCPUIdle":1833,"interactive":2056,"speedIndex":1376,"estimatedInputLatency":13,"observedNavigationStart":0,"observedNavigationStartTs":35453722625,"observedFirstPaint":216,"observedFirstPaintTs":35453938479,"observedFirstContentfulPaint":216,"observedFirstContentfulPaintTs":35453938482,"observedFirstMeaningfulPaint":216,"observedFirstMeaningfulPaintTs":35453938484,"observedTraceEnd":1472,"observedTraceEndTs":35455194303,"observedLoad":176,"observedLoadTs":35453898161,"observedDomContentLoaded":166,"observedDomContentLoadedTs":35453888564,"observedFirstVisualChange":216,"observedFirstVisualChangeTs":35453938625,"observedLastVisualChange":768,"observedLastVisualChangeTs":35454490625,"observedSpeedIndex":228,"observedSpeedIndexTs":35453950833}]}},"pwa-cross-browser":{"id":"pwa-cross-browser","title":"Site works cross-browser","description":"To reach the most number of users, sites should work across every major browser. [Learn more](https://developers.google.com/web/progressive-web-apps/checklist#site-works-cross-browser).","score":null,"scoreDisplayMode":"manual","rawValue":false},"pwa-page-transitions":{"id":"pwa-page-transitions","title":"Page transitions don't feel like they block on the network","description":"Transitions should feel snappy as you tap around, even on a slow network, a key to perceived performance. [Learn more](https://developers.google.com/web/progressive-web-apps/checklist#page-transitions-dont-feel-like-they-block-on-the-network).","score":null,"scoreDisplayMode":"manual","rawValue":false},"pwa-each-page-has-url":{"id":"pwa-each-page-has-url","title":"Each page has a URL","description":"Ensure individual pages are deep linkable via the URLs and that URLs are unique for the purpose of shareability on social media. [Learn more](https://developers.google.com/web/progressive-web-apps/checklist#each-page-has-a-url).","score":null,"scoreDisplayMode":"manual","rawValue":false},"accesskeys":{"id":"accesskeys","title":"`[accesskey]` values are unique","description":"Access keys let users quickly focus a part of the page. For proper navigation, each access key must be unique. [Learn more](https://dequeuniversity.com/rules/axe/2.2/accesskeys?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"aria-allowed-attr":{"id":"aria-allowed-attr","title":"`[aria-*]` attributes match their roles","description":"Each ARIA `role` supports a specific subset of `aria-*` attributes. Mismatching these invalidates the `aria-*` attributes. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-allowed-attr?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"aria-required-attr":{"id":"aria-required-attr","title":"`[role]`s have all required `[aria-*]` attributes","description":"Some ARIA roles have required attributes that describe the state of the element to screen readers. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-required-attr?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"aria-required-children":{"id":"aria-required-children","title":"Elements with `[role]` that require specific children `[role]`s, are present","description":"Some ARIA parent roles must contain specific child roles to perform their intended accessibility functions. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-required-children?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"aria-required-parent":{"id":"aria-required-parent","title":"`[role]`s are contained by their required parent element","description":"Some ARIA child roles must be contained by specific parent roles to properly perform their intended accessibility functions. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-required-parent?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"aria-roles":{"id":"aria-roles","title":"`[role]` values are valid","description":"ARIA roles must have valid values in order to perform their intended accessibility functions. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-roles?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"aria-valid-attr-value":{"id":"aria-valid-attr-value","title":"`[aria-*]` attributes have valid values","description":"Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid values. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-valid-attr-value?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"aria-valid-attr":{"id":"aria-valid-attr","title":"`[aria-*]` attributes are valid and not misspelled","description":"Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid names. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-valid-attr?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"audio-caption":{"id":"audio-caption","title":"`\u003caudio>` elements contain a `\u003ctrack>` element with `[kind=\"captions\"]`","description":"Captions make audio elements usable for deaf or hearing-impaired users, providing critical information such as who is talking, what they're saying, and other non-speech information. [Learn more](https://dequeuniversity.com/rules/axe/2.2/audio-caption?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"button-name":{"id":"button-name","title":"Buttons have an accessible name","description":"When a button doesn't have an accessible name, screen readers announce it as \"button\", making it unusable for users who rely on screen readers. [Learn more](https://dequeuniversity.com/rules/axe/2.2/button-name?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"bypass":{"id":"bypass","title":"The page contains a heading, skip link, or landmark region","description":"Adding ways to bypass repetitive content lets keyboard users navigate the page more efficiently. [Learn more](https://dequeuniversity.com/rules/axe/2.2/bypass?application=lighthouse).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"color-contrast":{"id":"color-contrast","title":"Background and foreground colors have a sufficient contrast ratio","description":"Low-contrast text is difficult or impossible for many users to read. [Learn more](https://dequeuniversity.com/rules/axe/2.2/color-contrast?application=lighthouse).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"definition-list":{"id":"definition-list","title":"`\u003cdl>`'s contain only properly-ordered `\u003cdt>` and `\u003cdd>` groups, `\u003cscript>` or `\u003ctemplate>` elements.","description":"When definition lists are not properly marked up, screen readers may produce confusing or inaccurate output. [Learn more](https://dequeuniversity.com/rules/axe/2.2/definition-list?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"dlitem":{"id":"dlitem","title":"Definition list items are wrapped in `\u003cdl>` elements","description":"Definition list items (`\u003cdt>` and `\u003cdd>`) must be wrapped in a parent `\u003cdl>` element to ensure that screen readers can properly announce them. [Learn more](https://dequeuniversity.com/rules/axe/2.2/dlitem?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"document-title":{"id":"document-title","title":"Document has a `\u003ctitle>` element","description":"The title gives screen reader users an overview of the page, and search engine users rely on it heavily to determine if a page is relevant to their search. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/title).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"duplicate-id":{"id":"duplicate-id","title":"`[id]` attributes on the page are unique","description":"The value of an id attribute must be unique to prevent other instances from being overlooked by assistive technologies. [Learn more](https://dequeuniversity.com/rules/axe/2.2/duplicate-id?application=lighthouse).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"frame-title":{"id":"frame-title","title":"`\u003cframe>` or `\u003ciframe>` elements have a title","description":"Screen reader users rely on frame titles to describe the contents of frames. [Learn more](https://dequeuniversity.com/rules/axe/2.2/frame-title?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"html-has-lang":{"id":"html-has-lang","title":"`\u003chtml>` element has a `[lang]` attribute","description":"If a page doesn't specify a lang attribute, a screen reader assumes that the page is in the default language that the user chose when setting up the screen reader. If the page isn't actually in the default language, then the screen reader might not announce the page's text correctly. [Learn more](https://dequeuniversity.com/rules/axe/2.2/html-lang?application=lighthouse).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"html-lang-valid":{"id":"html-lang-valid","title":"`\u003chtml>` element has a valid value for its `[lang]` attribute","description":"Specifying a valid [BCP 47 language](https://www.w3.org/International/questions/qa-choosing-language-tags#question) helps screen readers announce text properly. [Learn more](https://dequeuniversity.com/rules/axe/2.2/valid-lang?application=lighthouse).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"image-alt":{"id":"image-alt","title":"Image elements have `[alt]` attributes","description":"Informative elements should aim for short, descriptive alternate text. Decorative elements can be ignored with an empty alt attribute. [Learn more](https://dequeuniversity.com/rules/axe/2.2/image-alt?application=lighthouse).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"input-image-alt":{"id":"input-image-alt","title":"`\u003cinput type=\"image\">` elements have `[alt]` text","description":"When an image is being used as an `\u003cinput>` button, providing alternative text can help screen reader users understand the purpose of the button. [Learn more](https://dequeuniversity.com/rules/axe/2.2/input-image-alt?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"label":{"id":"label","title":"Form elements have associated labels","description":"Labels ensure that form controls are announced properly by assistive technologies, like screen readers. [Learn more](https://dequeuniversity.com/rules/axe/2.2/label?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"layout-table":{"id":"layout-table","title":"Presentational `\u003ctable>` elements avoid using `\u003cth>`, `\u003ccaption>` or the `[summary]` attribute.","description":"A table being used for layout purposes should not include data elements, such as the th or caption elements or the summary attribute, because this can create a confusing experience for screen reader users. [Learn more](https://dequeuniversity.com/rules/axe/2.2/__layout-table?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"link-name":{"id":"link-name","title":"Links have a discernible name","description":"Link text (and alternate text for images, when used as links) that is discernible, unique, and focusable improves the navigation experience for screen reader users. [Learn more](https://dequeuniversity.com/rules/axe/2.2/link-name?application=lighthouse).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"list":{"id":"list","title":"Lists contain only `\u003cli>` elements and script supporting elements (`\u003cscript>` and `\u003ctemplate>`).","description":"Screen readers have a specific way of announcing lists. Ensuring proper list structure aids screen reader output. [Learn more](https://dequeuniversity.com/rules/axe/2.2/list?application=lighthouse).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"listitem":{"id":"listitem","title":"List items (`\u003cli>`) are contained within `\u003cul>` or `\u003col>` parent elements","description":"Screen readers require list items (`\u003cli>`) to be contained within a parent `\u003cul>` or `\u003col>` to be announced properly. [Learn more](https://dequeuniversity.com/rules/axe/2.2/listitem?application=lighthouse).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"meta-refresh":{"id":"meta-refresh","title":"The document does not use `\u003cmeta http-equiv=\"refresh\">`","description":"Users do not expect a page to refresh automatically, and doing so will move focus back to the top of the page. This may create a frustrating or confusing experience. [Learn more](https://dequeuniversity.com/rules/axe/2.2/meta-refresh?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"meta-viewport":{"id":"meta-viewport","title":"`[user-scalable=\"no\"]` is not used in the `\u003cmeta name=\"viewport\">` element and the `[maximum-scale]` attribute is not less than 5.","description":"Disabling zooming is problematic for users with low vision who rely on screen magnification to properly see the contents of a web page. [Learn more](https://dequeuniversity.com/rules/axe/2.2/meta-viewport?application=lighthouse).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"object-alt":{"id":"object-alt","title":"`\u003cobject>` elements have `[alt]` text","description":"Screen readers cannot translate non-text content. Adding alt text to `\u003cobject>` elements helps screen readers convey meaning to users. [Learn more](https://dequeuniversity.com/rules/axe/2.2/object-alt?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"tabindex":{"id":"tabindex","title":"No element has a `[tabindex]` value greater than 0","description":"A value greater than 0 implies an explicit navigation ordering. Although technically valid, this often creates frustrating experiences for users who rely on assistive technologies. [Learn more](https://dequeuniversity.com/rules/axe/2.2/tabindex?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"td-headers-attr":{"id":"td-headers-attr","title":"Cells in a `\u003ctable>` element that use the `[headers]` attribute only refer to other cells of that same table.","description":"Screen readers have features to make navigating tables easier. Ensuring `\u003ctd>` cells using the `[headers]` attribute only refer to other cells in the same table may improve the experience for screen reader users. [Learn more](https://dequeuniversity.com/rules/axe/2.2/td-headers-attr?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"th-has-data-cells":{"id":"th-has-data-cells","title":"`\u003cth>` elements and elements with `[role=\"columnheader\"/\"rowheader\"]` have data cells they describe.","description":"Screen readers have features to make navigating tables easier. Ensuring table headers always refer to some set of cells may improve the experience for screen reader users. [Learn more](https://dequeuniversity.com/rules/axe/2.2/th-has-data-cells?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"valid-lang":{"id":"valid-lang","title":"`[lang]` attributes have a valid value","description":"Specifying a valid [BCP 47 language](https://www.w3.org/International/questions/qa-choosing-language-tags#question) on elements helps ensure that text is pronounced correctly by a screen reader. [Learn more](https://dequeuniversity.com/rules/axe/2.2/valid-lang?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"video-caption":{"id":"video-caption","title":"`\u003cvideo>` elements contain a `\u003ctrack>` element with `[kind=\"captions\"]`","description":"When a video provides a caption it is easier for deaf and hearing impaired users to access its information. [Learn more](https://dequeuniversity.com/rules/axe/2.2/video-caption?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"video-description":{"id":"video-description","title":"`\u003cvideo>` elements contain a `\u003ctrack>` element with `[kind=\"description\"]`","description":"Audio descriptions provide relevant information for videos that dialogue cannot, such as facial expressions and scenes. [Learn more](https://dequeuniversity.com/rules/axe/2.2/video-description?application=lighthouse).","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"custom-controls-labels":{"id":"custom-controls-labels","title":"Custom controls have associated labels","description":"Custom interactive controls have associated labels, provided by aria-label or aria-labelledby. [Learn more](https://developers.google.com/web/fundamentals/accessibility/how-to-review#try_it_with_a_screen_reader).","score":null,"scoreDisplayMode":"manual","rawValue":false},"custom-controls-roles":{"id":"custom-controls-roles","title":"Custom controls have ARIA roles","description":"Custom interactive controls have appropriate ARIA roles. [Learn more](https://developers.google.com/web/fundamentals/accessibility/how-to-review#try_it_with_a_screen_reader).","score":null,"scoreDisplayMode":"manual","rawValue":false},"focus-traps":{"id":"focus-traps","title":"User focus is not accidentally trapped in a region","description":"A user can tab into and out of any control or region without accidentally trapping their focus. [Learn more](https://developers.google.com/web/fundamentals/accessibility/how-to-review#start_with_the_keyboard).","score":null,"scoreDisplayMode":"manual","rawValue":false},"focusable-controls":{"id":"focusable-controls","title":"Interactive controls are keyboard focusable","description":"Custom interactive controls are keyboard focusable and display a focus indicator. [Learn more](https://developers.google.com/web/fundamentals/accessibility/how-to-review#start_with_the_keyboard).","score":null,"scoreDisplayMode":"manual","rawValue":false},"heading-levels":{"id":"heading-levels","title":"Headings don't skip levels","description":"Headings are used to create an outline for the page and heading levels are not skipped. [Learn more](https://developers.google.com/web/fundamentals/accessibility/how-to-review#take_advantage_of_headings_and_landmarks).","score":null,"scoreDisplayMode":"manual","rawValue":false},"logical-tab-order":{"id":"logical-tab-order","title":"The page has a logical tab order","description":"Tabbing through the page follows the visual layout. Users cannot focus elements that are offscreen. [Learn more](https://developers.google.com/web/fundamentals/accessibility/how-to-review#start_with_the_keyboard).","score":null,"scoreDisplayMode":"manual","rawValue":false},"managed-focus":{"id":"managed-focus","title":"The user's focus is directed to new content added to the page","description":"If new content, such as a dialog, is added to the page, the user's focus is directed to it. [Learn more](https://developers.google.com/web/fundamentals/accessibility/how-to-review#start_with_the_keyboard).","score":null,"scoreDisplayMode":"manual","rawValue":false},"offscreen-content-hidden":{"id":"offscreen-content-hidden","title":"Offscreen content is hidden from assistive technology","description":"Offscreen content is hidden with display: none or aria-hidden=true. [Learn more](https://developers.google.com/web/fundamentals/accessibility/how-to-review#try_it_with_a_screen_reader).","score":null,"scoreDisplayMode":"manual","rawValue":false},"use-landmarks":{"id":"use-landmarks","title":"HTML5 landmark elements are used to improve navigation","description":"Landmark elements (\u003cmain>, \u003cnav>, etc.) are used to improve the keyboard navigation of the page for assistive technology. [Learn more](https://developers.google.com/web/fundamentals/accessibility/how-to-review#take_advantage_of_headings_and_landmarks).","score":null,"scoreDisplayMode":"manual","rawValue":false},"visual-order-follows-dom":{"id":"visual-order-follows-dom","title":"Visual order on the page follows DOM order","description":"DOM order matches the visual order, improving navigation for assistive technology. [Learn more](https://developers.google.com/web/fundamentals/accessibility/how-to-review#try_it_with_a_screen_reader).","score":null,"scoreDisplayMode":"manual","rawValue":false},"uses-long-cache-ttl":{"id":"uses-long-cache-ttl","title":"Uses efficient cache policy on static assets","description":"A long cache lifetime can speed up repeat visits to your page. [Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching#cache-control).","score":1,"scoreDisplayMode":"numeric","rawValue":1067.1288182870371,"displayValue":"1 asset found","details":{"type":"table","headings":[{"key":"url","itemType":"url","text":"URL"},{"key":"cacheLifetimeMs","itemType":"ms","text":"Cache TTL","displayUnit":"duration"},{"key":"totalBytes","itemType":"bytes","text":"Size (KB)","displayUnit":"kb","granularity":1}],"items":[{"url":"https://ajax.cloudflare.com/cdn-cgi/scripts/4f936b58/cloudflare-static/rocket-loader.min.js","cacheControl":{"public":true},"cacheLifetimeMs":172793000,"cacheHitProbability":0.6999918981481481,"totalBytes":3557,"wastedBytes":1067.1288182870371}],"summary":{"wastedBytes":1067.1288182870371}}},"total-byte-weight":{"id":"total-byte-weight","title":"Avoids enormous network payloads","description":"Large network payloads cost users real money and are highly correlated with long load times. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/network-payloads).","score":1,"scoreDisplayMode":"numeric","rawValue":65510,"displayValue":["Total size was %d KB",63.974609375],"details":{"type":"table","headings":[{"key":"url","itemType":"url","text":"URL"},{"key":"totalBytes","itemType":"bytes","displayUnit":"kb","granularity":1,"text":"Total Size"},{"key":"totalMs","itemType":"ms","text":"Transfer Time"}],"items":[{"url":"https://www.robertgabriel.ninja/","totalBytes":35450,"totalMs":19.69203937996899},{"url":"https://www.robertgabriel.ninja/assets/js/vendor.min.js","totalBytes":23771,"totalMs":13.204498394957483},{"url":"https://ajax.cloudflare.com/cdn-cgi/scripts/4f936b58/cloudflare-static/rocket-loader.min.js","totalBytes":3557,"totalMs":1.9758697905373677},{"url":"https://www.robertgabriel.ninja/assets/img/me/me-4.webp","totalBytes":2181,"totalMs":1.21151869923025},{"url":"https://www.robertgabriel.ninja/assets/js/app.min.js","totalBytes":551,"totalMs":0.3060737291498706},{"url":"https://www.robertgabriel.ninja/assets/js/app.min.js","totalBytes":0,"totalMs":0}]}},"offscreen-images":{"id":"offscreen-images","title":"Defer offscreen images","description":"Consider lazy-loading offscreen and hidden images after all critical resources have finished loading to lower time to interactive. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/offscreen-images).","score":1,"scoreDisplayMode":"numeric","rawValue":0,"displayValue":"","warnings":[],"details":{"type":"table","headings":[],"items":[],"summary":{"wastedMs":0,"wastedBytes":0}}},"render-blocking-resources":{"id":"render-blocking-resources","title":"Eliminate render-blocking resources","description":"Resources are blocking the first paint of your page. Consider delivering critical JS/CSS inline and deferring all non-critical JS/styles. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/blocking-resources).","score":1,"scoreDisplayMode":"numeric","rawValue":0,"displayValue":"","details":{"type":"table","headings":[],"items":[],"summary":{"wastedMs":0}}},"unminified-css":{"id":"unminified-css","title":"Minify CSS","description":"Minifying CSS files can reduce network payload sizes. [Learn more](https://developers.google.com/speed/dist/insights/MinifyResources).","score":1,"scoreDisplayMode":"numeric","rawValue":0,"displayValue":"","details":{"type":"table","headings":[],"items":[],"summary":{"wastedMs":0,"wastedBytes":0}}},"unminified-javascript":{"id":"unminified-javascript","title":"Minify JavaScript","description":"Minifying JavaScript files can reduce payload sizes and script parse time. [Learn more](https://developers.google.com/speed/dist/insights/MinifyResources).","score":1,"scoreDisplayMode":"numeric","rawValue":0,"displayValue":"","warnings":[],"details":{"type":"table","headings":[],"items":[],"summary":{"wastedMs":0,"wastedBytes":0}}},"unused-css-rules":{"id":"unused-css-rules","title":"Defer unused CSS","description":"Remove unused rules from stylesheets to reduce unnecessary bytes consumed by network activity. [Learn more](https://developers.google.com/speed/dist/insights/OptimizeCSSDelivery).","score":1,"scoreDisplayMode":"numeric","rawValue":0,"displayValue":["Potential savings of %d KB",30],"details":{"type":"table","headings":[{"key":"url","itemType":"url","text":"URL"},{"key":"totalBytes","itemType":"bytes","displayUnit":"kb","granularity":1,"text":"Original"},{"key":"wastedBytes","itemType":"bytes","displayUnit":"kb","granularity":1,"text":"Potential Savings"}],"items":[{"url":{"type":"code","value":".footer-icons li,.footer-icons li a,.footer-icons li a svg,.footer-icons li svg,.footer-icons-black ..."},"wastedBytes":30573,"wastedPercent":92.80848135980851,"totalBytes":32942}],"summary":{"wastedMs":0,"wastedBytes":30573}}},"uses-webp-images":{"id":"uses-webp-images","title":"Serve images in next-gen formats","description":"Image formats like JPEG 2000, JPEG XR, and WebP often provide better compression than PNG or JPEG, which means faster downloads and less data consumption. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/webp).","score":1,"scoreDisplayMode":"numeric","rawValue":0,"displayValue":"","warnings":[],"details":{"type":"table","headings":[],"items":[],"summary":{"wastedMs":0,"wastedBytes":0}}},"uses-optimized-images":{"id":"uses-optimized-images","title":"Efficiently encode images","description":"Optimized images load faster and consume less cellular data. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/optimize-images).","score":1,"scoreDisplayMode":"numeric","rawValue":0,"displayValue":"","warnings":[],"details":{"type":"table","headings":[],"items":[],"summary":{"wastedMs":0,"wastedBytes":0}}},"uses-text-compression":{"id":"uses-text-compression","title":"Enable text compression","description":"Text-based responses should be served with compression (gzip, deflate or brotli) to minimize total network bytes. [Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/optimize-encoding-and-transfer).","score":1,"scoreDisplayMode":"numeric","rawValue":0,"displayValue":"","details":{"type":"table","headings":[],"items":[],"summary":{"wastedMs":0,"wastedBytes":0}}},"uses-responsive-images":{"id":"uses-responsive-images","title":"Properly size images","description":"Serve images that are appropriately-sized to save cellular data and improve load time. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/oversized-images).","score":1,"scoreDisplayMode":"numeric","rawValue":0,"displayValue":"","warnings":[],"details":{"type":"table","headings":[],"items":[],"summary":{"wastedMs":0,"wastedBytes":0}}},"efficient-animated-content":{"id":"efficient-animated-content","title":"Use video formats for animated content","description":"Large GIFs are inefficient for delivering animated content. Consider using MPEG4/WebM videos for animations and PNG/WebP for static images instead of GIF to save network bytes. [Learn more](https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/replace-animated-gifs-with-video/)","score":1,"scoreDisplayMode":"numeric","rawValue":0,"displayValue":"","details":{"type":"table","headings":[],"items":[],"summary":{"wastedMs":0,"wastedBytes":0}}},"appcache-manifest":{"id":"appcache-manifest","title":"Avoids Application Cache","description":"Application Cache is deprecated. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/appcache).","score":1,"scoreDisplayMode":"binary","rawValue":true,"displayValue":""},"dom-size":{"id":"dom-size","title":"Avoids an excessive DOM size","description":"Browser engineers recommend pages contain fewer than ~1,500 DOM nodes. The sweet spot is a tree depth \u003c 32 elements and fewer than 60 children/parent element. A large DOM can increase memory usage, cause longer [style calculations](https://developers.google.com/web/fundamentals/performance/rendering/reduce-the-scope-and-complexity-of-style-calculations), and produce costly [layout reflows](https://developers.google.com/speed/articles/reflow). [Learn more](https://developers.google.com/web/tools/lighthouse/audits/dom-size).","score":1,"scoreDisplayMode":"numeric","rawValue":184,"displayValue":["%d nodes",184],"details":{"type":"table","headings":[{"key":"totalNodes","itemType":"text","text":"Total DOM Nodes"},{"key":"depth","itemType":"text","text":"Maximum DOM Depth"},{"key":"width","itemType":"text","text":"Maximum Children"}],"items":[{"totalNodes":"184","depth":"11","width":"39"},{"totalNodes":"","depth":{"type":"code","value":"\u003cpath d=\"M165.9 397.4c0 2-2.3 3.6-5.2 3.6-3.3.3-5.6-1.3-5.6-3.6 0-2 2.3-3.6 5.2-3.6 3-.3 5.6 1.3 5.6 3.6zm-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9 2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3zm44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9.3 2 2.9 3.3 5.9 2.6 2.9-.7 4.9-2.6 4.6-4.6-.3-1.9-3-3.2-5.9-2.9zM244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2 12.8 2.3 17.3-5.6 17.3-12.1 0-6.2-.3-40.4-.3-61.4 0 0-70 15-84.7-29.8 0 0-11.4-29.1-27.8-36.6 0 0-22.9-15.7 1.6-15.4 0 0 24.9 2 38.6 25.8 21.9 38.6 58.6 27.5 72.9 20.9 2.3-16 8.8-27.1 16-33.7-55.9-6.2-112.3-14.3-112.3-110.5 0-27.5 7.6-41.3 23.6-58.9-2.6-6.5-11.1-33.3 2.6-67.9 20.9-6.5 69 27 69 27 20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27 13.7 34.7 5.2 61.4 2.6 67.9 16 17.7 25.8 31.5 25.8 58.9 0 96.5-58.9 104.2-114.8 110.5 9.2 7.9 17 22.9 17 46.4 0 33.7-.3 75.4-.3 83.6 0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252 496 113.3 383.5 8 244.8 8zM97.2 352.9c-1.3 1-1 3.3.7 5.2 1.6 1.6 3.9 2.3 5.2 1 1.3-1 1-3.3-.7-5.2-1.6-1.6-3.9-2.3-5.2-1zm-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9 1.6 1 3.6.7 4.3-.7.7-1.3-.3-2.9-2.3-3.9-2-.6-3.6-.3-4.3.7zm32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2 2.3 2.3 5.2 2.6 6.5 1 1.3-1.3.7-4.3-1.3-6.2-2.2-2.3-5.2-2.6-6.5-1zm-11.4-14.7c-1.6 1-1.6 3.6 0 5.9 1.6 2.3 4.3 3.3 5.6 2.3 1.6-1.3 1.6-3.9 0-6.2-1.4-2.3-4-3.3-5.6-2z\">"},"width":{"type":"code","value":"\u003chead>"}}]}},"external-anchors-use-rel-noopener":{"id":"external-anchors-use-rel-noopener","title":"Links to cross-origin destinations are safe","description":"Add `rel=\"noopener\"` or `rel=\"noreferrer\"` to any external links to improve performance and prevent security vulnerabilities. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/noopener).","score":1,"scoreDisplayMode":"binary","rawValue":true,"warnings":[],"details":{"type":"table","headings":[],"items":[]}},"geolocation-on-start":{"id":"geolocation-on-start","title":"Avoids requesting the geolocation permission on page load","description":"Users are mistrustful of or confused by sites that request their location without context. Consider tying the request to user gestures instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/geolocation-on-load).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"no-document-write":{"id":"no-document-write","title":"Avoids `document.write()`","description":"For users on slow connections, external scripts dynamically injected via `document.write()` can delay page load by tens of seconds. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/document-write).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"no-mutation-events":{"id":"no-mutation-events","title":"Avoids Mutation Events in its own scripts","description":"Mutation Events are deprecated and harm performance. Consider using Mutation Observers instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/mutation-events).","score":1,"scoreDisplayMode":"binary","rawValue":true,"warnings":[],"details":{"type":"table","headings":[],"items":[]}},"no-vulnerable-libraries":{"id":"no-vulnerable-libraries","title":"Avoids front-end JavaScript libraries with known security vulnerabilities","description":"Some third-party scripts may contain known security vulnerabilities that are easily identified and exploited by attackers.","score":1,"scoreDisplayMode":"binary","rawValue":true,"displayValue":"","details":{"type":"table","headings":[],"items":[],"summary":{}}},"no-websql":{"id":"no-websql","title":"Avoids WebSQL DB","description":"Web SQL is deprecated. Consider using IndexedDB instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/web-sql).","score":1,"scoreDisplayMode":"binary","rawValue":true,"displayValue":""},"notification-on-start":{"id":"notification-on-start","title":"Avoids requesting the notification permission on page load","description":"Users are mistrustful of or confused by sites that request to send notifications without context. Consider tying the request to user gestures instead. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/notifications-on-load).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"password-inputs-can-be-pasted-into":{"id":"password-inputs-can-be-pasted-into","title":"Allows users to paste into password fields","description":"Preventing password pasting undermines good security policy. [Learn more](https://www.ncsc.gov.uk/blog-post/let-them-paste-passwords).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"uses-http2":{"id":"uses-http2","title":"Uses HTTP/2 for its own resources","description":"HTTP/2 offers many benefits over HTTP/1.1, including binary headers, multiplexing, and server push. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/http2).","score":1,"scoreDisplayMode":"binary","rawValue":true,"displayValue":"","details":{"type":"table","headings":[],"items":[]}},"uses-passive-event-listeners":{"id":"uses-passive-event-listeners","title":"Uses passive listeners to improve scrolling performance","description":"Consider marking your touch and wheel event listeners as `passive` to improve your page's scroll performance. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/passive-event-listeners).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"meta-description":{"id":"meta-description","title":"Document has a meta description","description":"Meta descriptions may be included in search results to concisely summarize page content. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/description).","score":1,"scoreDisplayMode":"binary","rawValue":true},"http-status-code":{"id":"http-status-code","title":"Page has successful HTTP status code","description":"Pages with unsuccessful HTTP status codes may not be indexed properly. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/successful-http-code).","score":1,"scoreDisplayMode":"binary","rawValue":true},"font-size":{"id":"font-size","title":"Document uses legible font sizes","description":"Font sizes less than 12px are too small to be legible and require mobile visitors to “pinch to zoom” in order to read. Strive to have >60% of page text ≥12px. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/font-sizes).","score":1,"scoreDisplayMode":"binary","rawValue":true,"displayValue":["%.1d% legible text",100],"details":{"type":"table","headings":[{"key":"source","itemType":"url","text":"Source"},{"key":"selector","itemType":"code","text":"Selector"},{"key":"coverage","itemType":"text","text":"% of Page Text"},{"key":"fontSize","itemType":"text","text":"Font Size"}],"items":[{"source":"Legible text","selector":"","coverage":"100.00%","fontSize":"≥ 12px"}]}},"link-text":{"id":"link-text","title":"Links have descriptive text","description":"Descriptive link text helps search engines understand your content. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/descriptive-link-text).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[],"summary":{}}},"is-crawlable":{"id":"is-crawlable","title":"Page isn’t blocked from indexing","description":"Search engines are unable to include your pages in search results if they don't have permission to crawl them. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/indexing).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"robots-txt":{"id":"robots-txt","title":"robots.txt is valid","description":"If your robots.txt file is malformed, crawlers may not be able to understand how you want your website to be crawled or indexed.","score":null,"scoreDisplayMode":"not-applicable","rawValue":true},"hreflang":{"id":"hreflang","title":"Document has a valid `hreflang`","description":"hreflang links tell search engines what version of a page they should list in search results for a given language or region. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/hreflang).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"plugins":{"id":"plugins","title":"Document avoids plugins","description":"Search engines can't index plugin content, and many devices restrict plugins or don't support them. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/plugins).","score":1,"scoreDisplayMode":"binary","rawValue":true,"details":{"type":"table","headings":[],"items":[]}},"canonical":{"id":"canonical","title":"Document has a valid `rel=canonical`","description":"Canonical links suggest which URL to show in search results. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/canonical).","score":1,"scoreDisplayMode":"binary","rawValue":true},"mobile-friendly":{"id":"mobile-friendly","title":"Page is mobile friendly","description":"Take the [Mobile-Friendly Test](https://search.google.com/test/mobile-friendly) to check for audits not covered by Lighthouse, like sizing tap targets appropriately. [Learn more](https://developers.google.com/search/mobile-sites/).","score":null,"scoreDisplayMode":"manual","rawValue":false},"structured-data":{"id":"structured-data","title":"Structured data is valid","description":"Run the [Structured Data Testing Tool](https://search.google.com/structured-data/testing-tool/) and the [Structured Data Linter](http://linter.structured-data.org/) to validate structured data. [Learn more](https://developers.google.com/search/dist/guides/mark-up-content).","score":null,"scoreDisplayMode":"manual","rawValue":false}},"configSettings":{"output":"html","maxWaitForLoad":45000,"throttlingMethod":"simulate","throttling":{"rttMs":150,"throughputKbps":1638.4,"requestLatencyMs":562.5,"downloadThroughputKbps":1474.5600000000002,"uploadThroughputKbps":675,"cpuSlowdownMultiplier":4},"auditMode":false,"gatherMode":false,"disableStorageReset":false,"disableDeviceEmulation":false,"blockedUrlPatterns":null,"additionalTraceCategories":null,"extraHeaders":null,"onlyAudits":null,"onlyCategories":["performance","pwa","accessibility","best-practices","seo"],"skipAudits":null},"categories":{"performance":{"title":"Performance","auditRefs":[{"id":"first-contentful-paint","weight":3,"group":"metrics"},{"id":"first-meaningful-paint","weight":1,"group":"metrics"},{"id":"speed-index","weight":4,"group":"metrics"},{"id":"interactive","weight":5,"group":"metrics"},{"id":"first-cpu-idle","weight":2,"group":"metrics"},{"id":"estimated-input-latency","weight":0,"group":"metrics"},{"id":"render-blocking-resources","weight":0,"group":"load-opportunities"},{"id":"uses-responsive-images","weight":0,"group":"load-opportunities"},{"id":"offscreen-images","weight":0,"group":"load-opportunities"},{"id":"unminified-css","weight":0,"group":"load-opportunities"},{"id":"unminified-javascript","weight":0,"group":"load-opportunities"},{"id":"unused-css-rules","weight":0,"group":"load-opportunities"},{"id":"uses-optimized-images","weight":0,"group":"load-opportunities"},{"id":"uses-webp-images","weight":0,"group":"load-opportunities"},{"id":"uses-text-compression","weight":0,"group":"load-opportunities"},{"id":"uses-rel-preconnect","weight":0,"group":"load-opportunities"},{"id":"time-to-first-byte","weight":0,"group":"load-opportunities"},{"id":"redirects","weight":0,"group":"load-opportunities"},{"id":"uses-rel-preload","weight":0,"group":"load-opportunities"},{"id":"efficient-animated-content","weight":0,"group":"load-opportunities"},{"id":"total-byte-weight","weight":0,"group":"diagnostics"},{"id":"uses-long-cache-ttl","weight":0,"group":"diagnostics"},{"id":"dom-size","weight":0,"group":"diagnostics"},{"id":"critical-request-chains","weight":0,"group":"diagnostics"},{"id":"network-requests","weight":0},{"id":"metrics","weight":0},{"id":"user-timings","weight":0,"group":"diagnostics"},{"id":"bootup-time","weight":0,"group":"diagnostics"},{"id":"screenshot-thumbnails","weight":0},{"id":"mainthread-work-breakdown","weight":0,"group":"diagnostics"},{"id":"font-display","weight":0,"group":"diagnostics"}],"id":"performance","score":1},"pwa":{"title":"Progressive Web App","description":"These checks validate the aspects of a Progressive Web App, as specified by the baseline [PWA Checklist](https://developers.google.com/web/progressive-web-apps/checklist).","manualDescription":"These checks are required by the baseline [PWA Checklist](https://developers.google.com/web/progressive-web-apps/checklist) but are not automatically checked by Lighthouse. They do not affect your score but it's important that you verify them manually.","auditRefs":[{"id":"service-worker","weight":1},{"id":"works-offline","weight":1},{"id":"without-javascript","weight":1},{"id":"is-on-https","weight":1},{"id":"redirects-http","weight":1},{"id":"load-fast-enough-for-pwa","weight":1},{"id":"webapp-install-banner","weight":1},{"id":"splash-screen","weight":1},{"id":"themed-omnibox","weight":1},{"id":"viewport","weight":1},{"id":"content-width","weight":1},{"id":"pwa-cross-browser","weight":0},{"id":"pwa-page-transitions","weight":0},{"id":"pwa-each-page-has-url","weight":0}],"id":"pwa","score":1},"accessibility":{"title":"Accessibility","description":"These checks highlight opportunities to [improve the accessibility of your web app](https://developers.google.com/web/fundamentals/accessibility). Only a subset of accessibility issues can be automatically detected so manual testing is also encouraged.","manualDescription":"These items address areas which an automated testing tool cannot cover. Learn more in our guide on [conducting an accessibility review](https://developers.google.com/web/fundamentals/accessibility/how-to-review).","auditRefs":[{"id":"accesskeys","weight":0,"group":"a11y-correct-attributes"},{"id":"aria-allowed-attr","weight":0,"group":"a11y-aria"},{"id":"aria-required-attr","weight":0,"group":"a11y-aria"},{"id":"aria-required-children","weight":0,"group":"a11y-aria"},{"id":"aria-required-parent","weight":0,"group":"a11y-aria"},{"id":"aria-roles","weight":0,"group":"a11y-aria"},{"id":"aria-valid-attr-value","weight":0,"group":"a11y-aria"},{"id":"aria-valid-attr","weight":0,"group":"a11y-aria"},{"id":"audio-caption","weight":0,"group":"a11y-correct-attributes"},{"id":"button-name","weight":0,"group":"a11y-element-names"},{"id":"bypass","weight":10,"group":"a11y-describe-contents"},{"id":"color-contrast","weight":6,"group":"a11y-color-contrast"},{"id":"definition-list","weight":0,"group":"a11y-well-structured"},{"id":"dlitem","weight":0,"group":"a11y-well-structured"},{"id":"document-title","weight":2,"group":"a11y-describe-contents"},{"id":"duplicate-id","weight":5,"group":"a11y-well-structured"},{"id":"frame-title","weight":0,"group":"a11y-describe-contents"},{"id":"html-has-lang","weight":4,"group":"a11y-language"},{"id":"html-lang-valid","weight":1,"group":"a11y-language"},{"id":"image-alt","weight":8,"group":"a11y-correct-attributes"},{"id":"input-image-alt","weight":0,"group":"a11y-correct-attributes"},{"id":"label","weight":0,"group":"a11y-describe-contents"},{"id":"layout-table","weight":0,"group":"a11y-describe-contents"},{"id":"link-name","weight":9,"group":"a11y-element-names"},{"id":"list","weight":5,"group":"a11y-well-structured"},{"id":"listitem","weight":4,"group":"a11y-well-structured"},{"id":"meta-refresh","weight":0,"group":"a11y-meta"},{"id":"meta-viewport","weight":3,"group":"a11y-meta"},{"id":"object-alt","weight":0,"group":"a11y-describe-contents"},{"id":"tabindex","weight":0,"group":"a11y-correct-attributes"},{"id":"td-headers-attr","weight":0,"group":"a11y-correct-attributes"},{"id":"th-has-data-cells","weight":0,"group":"a11y-correct-attributes"},{"id":"valid-lang","weight":0,"group":"a11y-language"},{"id":"video-caption","weight":0,"group":"a11y-describe-contents"},{"id":"video-description","weight":0,"group":"a11y-describe-contents"},{"id":"logical-tab-order","weight":0},{"id":"focusable-controls","weight":0},{"id":"managed-focus","weight":0},{"id":"focus-traps","weight":0},{"id":"custom-controls-labels","weight":0},{"id":"custom-controls-roles","weight":0},{"id":"visual-order-follows-dom","weight":0},{"id":"offscreen-content-hidden","weight":0},{"id":"heading-levels","weight":0},{"id":"use-landmarks","weight":0}],"id":"accessibility","score":1},"best-practices":{"title":"Best Practices","auditRefs":[{"id":"appcache-manifest","weight":1},{"id":"no-websql","weight":1},{"id":"is-on-https","weight":1},{"id":"uses-http2","weight":1},{"id":"uses-passive-event-listeners","weight":1},{"id":"no-mutation-events","weight":1},{"id":"no-document-write","weight":1},{"id":"external-anchors-use-rel-noopener","weight":1},{"id":"geolocation-on-start","weight":1},{"id":"no-vulnerable-libraries","weight":1},{"id":"notification-on-start","weight":1},{"id":"deprecations","weight":1},{"id":"manifest-short-name-length","weight":1},{"id":"password-inputs-can-be-pasted-into","weight":1},{"id":"errors-in-console","weight":1},{"id":"image-aspect-ratio","weight":1}],"id":"best-practices","score":1},"seo":{"title":"SEO","description":"These checks ensure that your page is optimized for search engine results ranking. There are additional factors Lighthouse does not check that may affect your search ranking. [Learn more](https://support.google.com/webmasters/answer/35769).","manualDescription":"Run these additional validators on your site to check additional SEO best practices.","auditRefs":[{"id":"viewport","weight":1,"group":"seo-mobile"},{"id":"document-title","weight":1,"group":"seo-content"},{"id":"meta-description","weight":1,"group":"seo-content"},{"id":"http-status-code","weight":1,"group":"seo-crawl"},{"id":"link-text","weight":1,"group":"seo-content"},{"id":"is-crawlable","weight":1,"group":"seo-crawl"},{"id":"robots-txt","weight":0,"group":"seo-crawl"},{"id":"hreflang","weight":1,"group":"seo-content"},{"id":"canonical","weight":1,"group":"seo-content"},{"id":"font-size","weight":1,"group":"seo-mobile"},{"id":"plugins","weight":1,"group":"seo-content"},{"id":"mobile-friendly","weight":0},{"id":"structured-data","weight":0}],"id":"seo","score":1}},"categoryGroups":{"metrics":{"title":"Metrics"},"load-opportunities":{"title":"Opportunities","description":"These are opportunities to speed up your application by optimizing the following resources."},"diagnostics":{"title":"Diagnostics","description":"More information about the performance of your application."},"a11y-color-contrast":{"title":"Color Contrast Is Satisfactory","description":"These are opportunities to improve the legibility of your content."},"a11y-describe-contents":{"title":"Elements Describe Contents Well","description":"These are opportunities to make your content easier to understand for a user of assistive technology, like a screen reader."},"a11y-well-structured":{"title":"Elements Are Well Structured","description":"These are opportunities to make sure your HTML is appropriately structured."},"a11y-aria":{"title":"ARIA Attributes Follow Best Practices","description":"These are opportunities to improve the usage of ARIA in your application which may enhance the experience for users of assistive technology, like a screen reader."},"a11y-correct-attributes":{"title":"Elements Use Attributes Correctly","description":"These are opportunities to improve the configuration of your HTML elements."},"a11y-element-names":{"title":"Elements Have Discernible Names","description":"These are opportunities to improve the semantics of the controls in your application. This may enhance the experience for users of assistive technology, like a screen reader."},"a11y-language":{"title":"Page Specifies Valid Language","description":"These are opportunities to improve the interpretation of your content by users in different locales."},"a11y-meta":{"title":"Meta Tags Used Properly","description":"These are opportunities to improve the user experience of your site."},"seo-mobile":{"title":"Mobile Friendly","description":"Make sure your pages are mobile friendly so users don’t have to pinch or zoom in order to read the content pages. [Learn more](https://developers.google.com/search/mobile-sites/)."},"seo-content":{"title":"Content Best Practices","description":"Format your HTML in a way that enables crawlers to better understand your app’s content."},"seo-crawl":{"title":"Crawling and Indexing","description":"To appear in search results, crawlers need access to your app."}},"timing":{"total":7444}};</script> <script> window.addEventListener('DOMContentLoaded', _ => { const dom = new DOM(document); const renderer = new ReportRenderer(dom); const container = document.querySelector('main'); renderer.renderReport(window.__LIGHTHOUSE_JSON__, container); // Hook in JS features and page-level event listeners after the report // is in the document. const features = new ReportUIFeatures(dom); features.initFeatures(window.__LIGHTHOUSE_JSON__); }); document.addEventListener('lh-analytics', e => { if (window.ga) { ga(e.detail.cmd, e.detail.fields); } }); document.addEventListener('lh-log', e => { const logger = new Logger(document.querySelector('#lh-log')); switch (e.detail.cmd) { case 'log': logger.log(e.detail.msg); break; case 'warn': logger.warn(e.detail.msg); break; case 'error': logger.error(e.detail.msg); break; case 'hide': logger.hide(); break; } }); </script> <script> if ('serviceWorker' in navigator) { navigator.serviceWorker.register('/dd64034c.js'); } </script></body> </html>