Repository: GoogleChrome/lighthouse
Pull request #8842 (2019-05-03 22:47:03)

report(redesign): respect prefers-color-scheme
You can test this in FF 67 beta. Just need to change OS to dark mode (for osx, Appearance -> Dark)

brendankenny: > force true incase already dark (ex: scores all 100)

really `report-ui-features.js` should be setting dark mode for fireworks anyways, not `report-renderer.js`. Not everything that runs the report renderer is showing *only* the report (PSI, web.dev), so it shouldn't be reaching out and adding classes to `body` anyways.

Can you move it in here? Not sure what's the best way to do it...e.g check for `.score100', maybe?
Hoten: done. can I remove the check for devtools too?
googlebot: So there's good news and bad news.

:thumbsup: The good news is that everyone that needs to sign a CLA (the pull request submitter and all commit authors) have done so.  Everything is all good there.

:confused: The bad news is that it appears that one or more commits were authored or co-authored by someone other than the pull request submitter.  We need to confirm that all authors are ok with their commits being contributed to this project.  Please have them confirm that here in the pull request.

*Note to project maintainer: This is a terminal state, meaning the `cla/google` commit status will not change from this state. It's up to you to confirm consent of all the commit author(s), set the `cla` label to `yes` (if enabled on your project), and then merge this pull request when appropriate.*

ℹ️ **Googlers: [Go here](https://goto.google.com/prinfo/https%3A%2F%2Fgithub.com%2FGoogleChrome%2Flighthouse%2Fpull%2F8842) for more info**.

<!-- need_author_consent -->
paulirish: failing on travis 

![image](https://user-images.githubusercontent.com/39191/57201711-84a5c000-6f51-11e9-8995-2bd5c8263b1f.png)

Hoten: @paulirish fixed
paulirish: @Hoten your merge added back the test that was removed https://github.com/GoogleChrome/lighthouse/pull/8842/files#diff-219d90e30aff619017455031ea860fec

Patch:
diff --git a/lighthouse-core/audits/apple-touch-icon.js b/lighthouse-core/audits/apple-touch-icon.js
new file mode 100644
index 000000000..46313a29e
--- /dev/null
+++ b/lighthouse-core/audits/apple-touch-icon.js
@@ -0,0 +1,71 @@
+/**
+ * @license Copyright 2019 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';
+
+const Audit = require('./audit.js');
+const i18n = require('../lib/i18n/i18n.js');
+
+/**
+ * @fileoverview Audits if a page has an `apple-touch-icon` link element with a valid href.
+ */
+
+const UIStrings = {
+  /** Title of a Lighthouse audit that tells the user that their site contains a vaild touch icon. This descriptive title is shown when the page contains a valid iOS touch icon. "apple-touch-icon" is an HTML attribute value and should not be translated. */
+  title: 'Provides a valid `apple-touch-icon`',
+  /** Title of a Lighthouse audit that tells the user that their site contains a vaild touch icon. This descriptive title is shown when the page does not contain a valid iOS touch icon. "apple-touch-icon" is an HTML attribute value and should not be translated. */
+  failureTitle: 'Does not provide a valid `apple-touch-icon`',
+  /** Description of a Lighthouse audit that tells the user what having a valid apple-touch-icon does. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. "apple-touch-icon" is an HTML attribute value and should not be translated. */
+  description: 'For ideal appearance on iOS when users add to the home screen, define an ' +
+  'apple-touch-icon. It must point to a non-transparent 192px (or 180px) square PNG. ' +
+  '[Learn More](https://developers.google.com/web/fundamentals/design-and-ux/browser-customization/).',
+  /** Warning that HTML attribute `apple-touch-icon-precomposed` should not be used in favor of `apple-touch-icon`.  "apple-touch-icon-precomposed" and "apple-touch-icon" are HTML attribute values and should not be translated. */
+  precomposedWarning: '`apple-touch-icon-precomposed` is out of date; ' +
+  '`apple-touch-icon` is preferred.',
+};
+
+const str_ = i18n.createMessageInstanceIdFn(__filename, UIStrings);
+
+class AppleTouchIcon extends Audit {
+  /**
+   * @return {LH.Audit.Meta}
+   */
+  static get meta() {
+    return {
+      id: 'apple-touch-icon',
+      title: str_(UIStrings.title),
+      failureTitle: str_(UIStrings.failureTitle),
+      description: str_(UIStrings.description),
+      requiredArtifacts: ['LinkElements'],
+    };
+  }
+
+  /**
+   * @param {LH.Artifacts} artifacts
+   * @return {LH.Audit.Product}
+   */
+  static audit(artifacts) {
+    const appleTouchIcons = artifacts.LinkElements
+      .filter(el => el.rel === 'apple-touch-icon' || el.rel === 'apple-touch-icon-precomposed')
+      .filter(el => !!el.href);
+
+    // Audit passes if an `apple-touch-icon` exists.
+    const passed = appleTouchIcons.length !== 0;
+
+    const warnings = [];
+    if (appleTouchIcons.filter(el => el.rel === 'apple-touch-icon-precomposed').length !== 0
+      && appleTouchIcons.filter(el => el.rel === 'apple-touch-icon').length === 0) {
+      warnings.push(str_(UIStrings.precomposedWarning));
+    }
+
+    return {
+      score: passed ? 1 : 0,
+      warnings,
+    };
+  }
+}
+
+module.exports = AppleTouchIcon;
+module.exports.UIStrings = UIStrings;
diff --git a/lighthouse-core/config/default-config.js b/lighthouse-core/config/default-config.js
index 9184e5492..d309072f7 100644
--- a/lighthouse-core/config/default-config.js
+++ b/lighthouse-core/config/default-config.js
@@ -179,6 +179,7 @@ const defaultConfig = {
     'critical-request-chains',
     'redirects',
     'installable-manifest',
+    'apple-touch-icon',
     'splash-screen',
     'themed-omnibox',
     'content-width',
@@ -388,7 +389,7 @@ const defaultConfig = {
         {id: 'bootup-time', weight: 0, group: 'diagnostics'},
         {id: 'mainthread-work-breakdown', weight: 0, group: 'diagnostics'},
         {id: 'font-display', weight: 0, group: 'diagnostics'},
-        {id: 'performance-budget', weight: 0},
+        {id: 'performance-budget', weight: 0, group: 'budgets'},
         {id: 'resource-summary', weight: 0, group: 'diagnostics'},
         // Audits past this point don't belong to a group and will not be shown automatically
         {id: 'network-requests', weight: 0},
@@ -523,6 +524,7 @@ const defaultConfig = {
         {id: 'content-width', weight: 1, group: 'pwa-optimized'},
         {id: 'viewport', weight: 2, group: 'pwa-optimized'},
         {id: 'without-javascript', weight: 1, group: 'pwa-optimized'},
+        {id: 'apple-touch-icon', weight: 1, group: 'pwa-optimized'},
         // Manual audits
         {id: 'pwa-cross-browser', weight: 0},
         {id: 'pwa-page-transitions', weight: 0},
diff --git a/lighthouse-core/lib/i18n/en-US.json b/lighthouse-core/lib/i18n/en-US.json
index 02e444d58..adfc34c8f 100644
--- a/lighthouse-core/lib/i18n/en-US.json
+++ b/lighthouse-core/lib/i18n/en-US.json
@@ -423,6 +423,22 @@
     "message": "`<video>` elements contain a `<track>` element with `[kind=\"description\"]`",
     "description": "Title of an accesibility audit that evaluates if all video elements have child track elements that contain a description of the video content. This title is descriptive of the successful state and is shown to users when no user action is required."
   },
+  "lighthouse-core/audits/apple-touch-icon.js | description": {
+    "message": "For ideal appearance on iOS when users add to the home screen, define an apple-touch-icon. It must point to a non-transparent 192px (or 180px) square PNG. [Learn More](https://developers.google.com/web/fundamentals/design-and-ux/browser-customization/).",
+    "description": "Description of a Lighthouse audit that tells the user what having a valid apple-touch-icon does. This is displayed after a user expands the section to see more. No character length limits. 'Learn More' becomes link text to additional documentation. \"apple-touch-icon\" is an HTML attribute value and should not be translated."
+  },
+  "lighthouse-core/audits/apple-touch-icon.js | failureTitle": {
+    "message": "Does not provide a valid `apple-touch-icon`",
+    "description": "Title of a Lighthouse audit that tells the user that their site contains a vaild touch icon. This descriptive title is shown when the page does not contain a valid iOS touch icon. \"apple-touch-icon\" is an HTML attribute value and should not be translated."
+  },
+  "lighthouse-core/audits/apple-touch-icon.js | precomposedWarning": {
+    "message": "`apple-touch-icon-precomposed` is out of date; `apple-touch-icon` is preferred.",
+    "description": "Warning that HTML attribute `apple-touch-icon-precomposed` should not be used in favor of `apple-touch-icon`.  \"apple-touch-icon-precomposed\" and \"apple-touch-icon\" are HTML attribute values and should not be translated."
+  },
+  "lighthouse-core/audits/apple-touch-icon.js | title": {
+    "message": "Provides a valid `apple-touch-icon`",
+    "description": "Title of a Lighthouse audit that tells the user that their site contains a vaild touch icon. This descriptive title is shown when the page contains a valid iOS touch icon. \"apple-touch-icon\" is an HTML attribute value and should not be translated."
+  },
   "lighthouse-core/audits/bootup-time.js | chromeExtensionsWarning": {
     "message": "Chrome extensions negatively affected this page's load performance. Try auditing the page in incognito mode or from a Chrome profile without extensions.",
     "description": "A message displayed in a Lighthouse audit result warning that Chrome extensions on the user's system substantially affected Lighthouse's measurements and instructs the user on how to run again without those extensions."
diff --git a/lighthouse-core/lib/i18n/locales/ta.json b/lighthouse-core/lib/i18n/locales/ta.json
index a081a20f8..ba2e010b9 100644
--- a/lighthouse-core/lib/i18n/locales/ta.json
+++ b/lighthouse-core/lib/i18n/locales/ta.json
@@ -447,10 +447,10 @@
     "message": "மதிப்பு"
   },
   "lighthouse-core/audits/dobetterweb/dom-size.js | description": {
-    "message": "Browser engineers recommend pages contain fewer than ~1,500 DOM elements. The sweet spot is a tree depth < 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)."
+    "message": "~1,500 DOM உறுப்புகளுக்குக் குறைவான உறுப்புகளைக் கொண்ட பக்கங்களை உலாவிப் பொறியாளர்கள் பரிந்துரைக்கின்றனர். 32 உறுப்புகளுக்குக் குறைவான கிளை அடுக்குகளும் 60க்குக் குறைவான துணைக்கிளை/கிளை உறுப்புகளும் இருப்பது உகந்தது. ஒரு பெரிய DOM, நினைவகப் பயன்பாட்டை அதிகரிக்கலாம், [ஸ்டைல் கணக்கீடுகளை] நீட்டிக்கலாம், மேலும் (https://developers.google.com/web/fundamentals/performance/rendering/reduce-the-scope-and-complexity-of-style-calculations) அதிகச் செலவு பிடிக்கும் [தளவமைப்பு மறுசீராக்கங்களை] ஏற்படுத்தலாம் (https://developers.google.com/speed/articles/reflow). [மேலும் அறிக](https://developers.google.com/web/tools/lighthouse/audits/dom-size)."
   },
   "lighthouse-core/audits/dobetterweb/dom-size.js | displayValue": {
-    "message": "{itemCount, plural,\n    =1 {1 element}\n    other {# elements}\n    }"
+    "message": "{itemCount,plural, =1{1 உறுப்பு}other{# உறுப்புகள்}}"
   },
   "lighthouse-core/audits/dobetterweb/dom-size.js | failureTitle": {
     "message": "அபரிமிதமான DOM அளவைத் தவிர்க்கவும்"
@@ -459,7 +459,7 @@
     "message": "அதிகபட்ச DOM கிளை அடுக்கு"
   },
   "lighthouse-core/audits/dobetterweb/dom-size.js | statisticDOMElements": {
-    "message": "Total DOM Elements"
+    "message": "மொத்த DOM உறுப்புக்கள்"
   },
   "lighthouse-core/audits/dobetterweb/dom-size.js | statisticDOMWidth": {
     "message": "அதிகபட்சத் துணை உறுப்புகள்"
@@ -876,22 +876,22 @@
     "message": "SEO"
   },
   "lighthouse-core/config/default-config.js | seoContentGroupDescription": {
-    "message": "Format your HTML in a way that enables crawlers to better understand your app’s content."
+    "message": "ஆப்ஸின் உள்ளடக்கத்தை crawlerகள் நன்கு புரிந்துகொள்ள வசதியாக HTMLலைப் பொருத்தமாக வடிவமைக்கவும்."
   },
   "lighthouse-core/config/default-config.js | seoContentGroupTitle": {
-    "message": "Content Best Practices"
+    "message": "உள்ளடக்கம் தொடர்பான சிறந்த நடைமுறைகள்"
   },
   "lighthouse-core/config/default-config.js | seoCrawlingGroupDescription": {
-    "message": "To appear in search results, crawlers need access to your app."
+    "message": "தேடல் முடிவுகளில் தோன்ற உங்கள் ஆப்ஸிற்கான அணுகல் crawlerகளுக்குத் தேவை."
   },
   "lighthouse-core/config/default-config.js | seoCrawlingGroupTitle": {
-    "message": "Crawling and Indexing"
+    "message": "Crawling மற்றும் அட்டவணைப்படுத்துதல்"
   },
   "lighthouse-core/config/default-config.js | seoMobileGroupDescription": {
-    "message": "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/)."
+    "message": "பக்கங்களின் உள்ளடக்கத்தைப் பயனர்கள் பின்ச் செய்தோ பெரிதாக்கியோ படிக்க வேண்டிய அவசியம் ஏற்படாதவாறு உங்கள் பக்கங்கள் மொபைலுக்கு ஏற்றவையாக இருக்க வேண்டும். [மேலும் அறிக](https://developers.google.com/search/mobile-sites/)."
   },
   "lighthouse-core/config/default-config.js | seoMobileGroupTitle": {
-    "message": "Mobile Friendly"
+    "message": "மொபைலுக்கேற்றது"
   },
   "lighthouse-core/lib/i18n/i18n.js | columnCacheTTL": {
     "message": "தற்காலிக நினைவக TTL"
@@ -927,7 +927,7 @@
     "message": "உங்கள் பக்கம் ஏற்றப்படுகையில் டிரேஸைப் பதிவுசெய்யும்போது ஏதோ தவறாகிவிட்டது. Lighthouseஸை மீண்டும் இயக்கவும். ({errorCode})"
   },
   "lighthouse-core/lib/lh-error.js | criTimeout": {
-    "message": "Timeout waiting for initial Debugger Protocol connection."
+    "message": "முதல் பிழைதிருத்தும் நெறிமுறை இணைப்பிற்கான நேரமுடிவு காத்திருப்பு."
   },
   "lighthouse-core/lib/lh-error.js | didntCollectScreenshots": {
     "message": "பக்கம் ஏற்றப்பட்டபோது ஸ்க்ரீன்ஷாட்கள் எதையும் Chrome சேகரிக்கவில்லை. பக்கத்தில் உள்ளடக்கம் காண்பிக்கப்படுவதை உறுதிசெய்து, Lighthouseஸை மீண்டும் இயக்கவும். ({errorCode})"
@@ -1020,45 +1020,45 @@
     "message": "எச்சரிக்கைகள்: "
   },
   "stack-packs/packs/wordpress.js | efficient_animated_content": {
-    "message": "Consider uploading your GIF to a service which will make it available to embed as an HTML5 video."
+    "message": "உங்கள் GIFஃபை HTML5 வீடியோவாக உட்பொதிந்து கிடைக்கச் செய்யும் சேவையில் பதிவேற்ற முயலுங்கள்."
   },
   "stack-packs/packs/wordpress.js | offscreen_images": {
-    "message": "Install a [lazy-load WordPress plugin](https://wordpress.org/plugins/search/lazy+load/) that provides the ability to defer any offscreen images, or switch to a theme that provides that functionality. Also consider using [the AMP plugin](https://wordpress.org/plugins/amp/)."
+    "message": "[lazy-load WordPress செருகுநிரலை] நிறுவி (https://wordpress.org/plugins/search/lazy+load/) திரைக்கு வெளியிலுள்ள படங்களைத் தேவையான போது ஏற்றும் திறனைப் பெறலாம் அல்லது அந்தத் திறனுள்ள தீமிற்கு மாறலாம். [AMP செருகுநிரலையும்](https://wordpress.org/plugins/amp/) பயன்படுத்திப் பார்க்கவும்."
   },
   "stack-packs/packs/wordpress.js | render_blocking_resources": {
-    "message": "There are a number of WordPress plugins that can help you [inline critical assets](https://wordpress.org/plugins/search/critical+css/) or [defer less important resources](https://wordpress.org/plugins/search/defer+css+javascript/). Beware that optimizations provided by these plugins may break features of your theme or plugins, so you will likely need to make code changes."
+    "message": "[முக்கியமான சொத்துகளை முன்னிலைப்படுத்தவும்](https://wordpress.org/plugins/search/critical+css/) அல்லது [முக்கியத்துவம் குறைவான ஆதாரங்களைத் தவிர்க்கவும்](https://wordpress.org/plugins/search/defer+css+javascript/) உங்களுக்கு உதவக்கூடிய பல WordPress செருகுநிரல்கள் உள்ளன. இந்தச் செருகுநிரல்கள் வழங்கும் மேம்படுத்துதல்கள் உங்களின் தீம் அல்லது செருகுநிரலில் உள்ள அம்சத்தைப் பாதிக்கும். அதனால் நீங்கள் குறியீட்டில் சில மாற்றங்களைச் செய்ய வேண்டியிருக்கும்."
   },
   "stack-packs/packs/wordpress.js | time_to_first_byte": {
-    "message": "Themes, plugins, and server specifications all contribute to server response time. Consider finding a more optimized theme, carefully selecting an optimization plugin, and/or upgrading your server."
+    "message": "தீம்கள், செருகுநிரல்கள் மற்றும் சேவையக விவரக்குறிப்புகள் அனைத்தும் சேவையகத்தின் வேகத்தை நிர்ணயிக்கும். இன்னும் பல மேம்படுத்தப்பட்ட தீம்மைக் கண்டறிந்து, மேம்படுத்தும் செருகுநிரலைக் கவனமாகத் தேர்ந்தெடுத்து மற்றும்/அல்லது சேவையகத்தை மேம்படுத்தவும்."
   },
   "stack-packs/packs/wordpress.js | total_byte_weight": {
-    "message": "Consider showing excerpts in your post lists (e.g. via the more tag), reducing the number of posts shown on a given page, breaking your long posts into multiple pages, or using a plugin to lazy-load comments."
+    "message": "இடுகை பட்டியல்களில் முக்கியமான பகுதியை மட்டும் காட்டலாம் (உதாரணமாக மேலும் என்ற குறிச்சொல்லுடன்), பக்கத்தில் இடுகைகளின் எண்ணிக்கையைக் குறைக்கலாம், ஒரு பெரிய இடுகையைப் பல சின்ன பக்கங்களாகப் பிரிக்கலாம் அல்லது தேவையுள்ள போது மட்டும் கருத்துகளைச் செருகுநிரல்கள் மூலம் ஏற்றலாம்."
   },
   "stack-packs/packs/wordpress.js | unminified_css": {
-    "message": "A number of [WordPress plugins](https://wordpress.org/plugins/search/minify+css/) can speed up your site by concatenating, minifying, and compressing your styles. You may also want to use a build process to do this minification up-front if possible."
+    "message": "பல [WordPress செருகுநிரல்கள்](https://wordpress.org/plugins/search/minify+javascript/) உங்கள் இணையதளத்தில் உள்ள ஸ்டைல்களைச் சிறிதாக்கி சுருக்கி மற்றும் ஒன்றிணைத்து அதை வேகப்படுத்தலாம். ஸ்கிரிப்ட்டைச் சிறிதாக்க பதிப்பு முறைமையைப் பயன்படுத்த நேரிடலாம்."
   },
   "stack-packs/packs/wordpress.js | unminified_javascript": {
-    "message": "A number of [WordPress plugins](https://wordpress.org/plugins/search/minify+javascript/) can speed up your site by concatenating, minifying, and compressing your scripts. You may also want to use a build process to do this minification up front if possible."
+    "message": "பல [WordPress செருகுநிரல்கள்](https://wordpress.org/plugins/search/minify+javascript/) உங்கள் இணையதளத்தில் உள்ள ஸ்கிரிப்ட்டைச் சிறிதாக்கி சுருக்கி மற்றும் ஒன்றிணைத்து அதை வேகப்படுத்தலாம். ஸ்கிரிப்ட்டைச் சிறிதாக்க பதிப்பு முறைமையைப் பயன்படுத்த நேரிடலாம்."
   },
   "stack-packs/packs/wordpress.js | unused_css_rules": {
-    "message": "Consider reducing, or switching, the number of [WordPress plugins](https://wordpress.org/plugins/) loading unused CSS in your page. To identify plugins that are adding extraneous CSS, try running [code coverage](https://developers.google.com/web/updates/2017/04/devtools-release-notes#coverage) in Chrome DevTools. You can identify the theme/plugin responsible from the URL of the stylesheet. Look out for plugins that have many stylesheets in the list which have a lot of red in code coverage. A plugin should only enqueue a stylesheet if it is actually used on the page."
+    "message": "உங்கள் பக்கத்தில் [WordPress செருகுநிரல்கள்](https://wordpress.org/plugins/) ஏற்றும் பயன்படுத்தப்படாத CSSகளின் எண்ணிக்கையைக் குறைக்கவும் அல்லது மாற்றிப் பார்க்கவும். Chrome DevToolsஸில் [குறியீட்டுக் கவரேஜ்] (https://developers.google.com/web/updates/2017/04/devtools-release-notes#coverage) என்பதை இயக்கி தேவையற்ற CSSஸைச் சேர்க்கும் செருகுநிரல்களைக் கண்டறியலாம். stylesheetடின் URLலில் இதற்குக் காரணமான தீம்/செருகுநிரலைக் கண்டறியலாம். அதிகமாகச் சிகப்பில் உள்ள குறியீட்டு கவரேஜைக் கொண்ட பட்டியலில் பல stylesheetகளைக் கொண்ட செருகுநிரல்களைக் கண்டறியவும். இணையப் பக்கத்தில் பயன்படுத்தும் பட்சத்தில் ஒரு செருகுநிரல் ஒரு stylesheetடை மட்டுமே வரிசையில் சேர்க்க வேண்டும்."
   },
   "stack-packs/packs/wordpress.js | unused_javascript": {
-    "message": "Consider reducing, or switching, the number of [WordPress plugins](https://wordpress.org/plugins/) loading unused JavaScript in your page. To identify plugins that are adding extraneous JS, try running [code coverage](https://developers.google.com/web/updates/2017/04/devtools-release-notes#coverage) in Chrome DevTools. You can identify the theme/plugin responsible from the URL of the script. Look out for plugins that have many scripts in the list which have a lot of red in code coverage. A plugin should only enqueue a script if it is actually used on the page."
+    "message": "உங்கள் பக்கத்தில் [WordPress செருகுநிரல்கள்](https://wordpress.org/plugins/) ஏற்றும் பயன்படுத்தப்படாத JavaScriptகளின் எண்ணிக்கையைக் குறைக்கவும் அல்லது மாற்றிப் பார்க்கவும். Chrome DevToolsஸில் [குறியீட்டுக் கவரேஜ்](https://developers.google.com/web/updates/2017/04/devtools-release-notes#coverage) என்பதை இயக்கி தேவையற்ற JSகளைச் சேர்க்கும் செருகுநிரல்களைக் கண்டறியவும். ஸ்கிரிப்ட்டின் URLலில் இதற்குக் காரணமான தீம்/செருகுநிரலைக் கண்டறியலாம். அதிகமாகச் சிகப்பில் உள்ள குறியீட்டு கவரேஜைக் கொண்ட பட்டியலில் பல ஸ்கிரிப்ட்களைக் கொண்ட செருகுநிரல்களைக் கண்டறியவும். இணையப் பக்கத்தில் பயன்படுத்தும் பட்சத்தில் ஒரு செருகுநிரல் ஒரு ஸ்கிரிப்ட்டை மட்டுமே வரிசையில் சேர்க்க வேண்டும்."
   },
   "stack-packs/packs/wordpress.js | uses_long_cache_ttl": {
-    "message": "Read about [Browser Caching in WordPress](https://codex.wordpress.org/WordPress_Optimization#Browser_Caching)."
+    "message": "[WordPressஸில் உலாவியின் தற்காலிக சேமிப்பு] பற்றி அறிய: (https://codex.wordpress.org/WordPress_Optimization#Browser_Caching)."
   },
   "stack-packs/packs/wordpress.js | uses_optimized_images": {
-    "message": "Consider using an [image optimization WordPress plugin](https://wordpress.org/plugins/search/optimize+images/) that compresses your images while retaining quality."
+    "message": "[படங்களை மேம்படுத்தும் WordPress செருகுநிரல்](https://wordpress.org/plugins/search/optimize+images/) என்பதைப் பயன்படுத்தி உங்கள் படங்களின் தரத்தை இழக்காமல் சுருங்கச் செய்யலாம்."
   },
   "stack-packs/packs/wordpress.js | uses_responsive_images": {
-    "message": "Upload images directly through the [media library](https://codex.wordpress.org/Media_Library_Screen) to ensure that the required image sizes are available, and then insert them from the media library or use the image widget to ensure the optimal image sizes are used (including those for the responsive breakpoints). Avoid using `Full Size` images unless the dimensions are adequate for their usage. [Learn More](https://codex.wordpress.org/Inserting_Images_into_Posts_and_Pages#Image_Size)."
+    "message": "[மீடியா லைப்ரரி](https://codex.wordpress.org/Media_Library_Screen) மூலம் படத்தை நேரடியாகப் பதிவேற்றி சரியான அளவில் கிடைக்கிறதா என்று உறுதிப்படுத்தலாம், பிறகு மீடியா லைப்ரரியிலிருந்தோ பட விட்ஜெட்டைப் பயன்படுத்தியோ மேம்படுத்தப்பட்ட அளவில் படங்கள் பயன்படுத்தப்படுகிறதா என்பதை உறுதி செய்யவும் (பதிலளிக்கும் அளவு வரம்புகள் உட்பட). பயன்படுத்துவதற்கு ஏதுவான அளவுகள் உள்ளபோது மட்டுமே 'முழு அளவு' படங்களைப் பயன்படுத்தவும். [மேலும் அறிக](https://codex.wordpress.org/Inserting_Images_into_Posts_and_Pages#Image_Size)."
   },
   "stack-packs/packs/wordpress.js | uses_text_compression": {
-    "message": "You can enable text compression in your web server configuration."
+    "message": "இணையச் சேவையக உள்ளமைவில் உரை சுருக்குதலை இயக்கலாம்."
   },
   "stack-packs/packs/wordpress.js | uses_webp_images": {
-    "message": "Consider using a [plugin](https://wordpress.org/plugins/search/convert+webp/) or service that will automatically convert your uploaded images to the optimal formats."
+    "message": "நீங்கள் பதிவேற்றிய படங்களை மேம்படுத்தப்பட்ட வடிவத்திற்குத் தானாக மாற்ற ஒரு [செருகுநிரலையோ](https://wordpress.org/plugins/search/convert+webp/) சேவையையோ பயன்படுத்திப் பாருங்கள்."
   }
 }
\ No newline at end of file
diff --git a/lighthouse-core/report/html/renderer/performance-category-renderer.js b/lighthouse-core/report/html/renderer/performance-category-renderer.js
index 943f80d69..8ca8c4539 100644
--- a/lighthouse-core/report/html/renderer/performance-category-renderer.js
+++ b/lighthouse-core/report/html/renderer/performance-category-renderer.js
@@ -164,6 +164,20 @@ class PerformanceCategoryRenderer extends CategoryRenderer {
       filmstripEl && timelineEl.appendChild(filmstripEl);
     }
 
+    // Budgets
+    const budgetAudit = category.auditRefs.find(audit => audit.id === 'performance-budget');
+    if (budgetAudit && budgetAudit.result.details) {
+      const table = this.detailsRenderer.render(budgetAudit.result.details);
+      if (table) {
+        table.id = budgetAudit.id;
+        table.classList.add('lh-audit');
+        const budgetsGroupEl = this.renderAuditGroup(groups.budgets);
+        budgetsGroupEl.appendChild(table);
+        budgetsGroupEl.classList.add('lh-audit-group--budgets');
+        element.appendChild(budgetsGroupEl);
+      }
+    }
+
     // Opportunities
     const opportunityAudits = category.auditRefs
         .filter(audit => audit.group === 'load-opportunities' && !Util.showAsPassed(audit.result))
diff --git a/lighthouse-core/report/html/renderer/report-renderer.js b/lighthouse-core/report/html/renderer/report-renderer.js
index 813ae2024..928dc4742 100644
--- a/lighthouse-core/report/html/renderer/report-renderer.js
+++ b/lighthouse-core/report/html/renderer/report-renderer.js
@@ -242,17 +242,6 @@ class ReportRenderer {
       wrapper.appendChild(renderer.render(category, report.categoryGroups));
     }
 
-    // Fireworks
-    const scoresAll100 = report.reportCategories.every(cat => cat.score === 1);
-    if (!this._dom.isDevTools() && scoresAll100) {
-      const scoresContainer = this._dom.find('.lh-scores-container', headerContainer);
-      scoresContainer.classList.add('score100');
-      this._dom._document.body.classList.add('dark');
-      scoresContainer.addEventListener('click', _ => {
-        scoresContainer.classList.toggle('fireworks-paused');
-      });
-    }
-
     if (scoreHeader) {
       const scoreGauges =
         this._renderScoreGauges(report, categoryRenderer, specificCategoryRenderers);
diff --git a/lighthouse-core/report/html/renderer/report-ui-features.js b/lighthouse-core/report/html/renderer/report-ui-features.js
index e0733f3b5..2cd777ec9 100644
--- a/lighthouse-core/report/html/renderer/report-ui-features.js
+++ b/lighthouse-core/report/html/renderer/report-ui-features.js
@@ -89,7 +89,27 @@ class ReportUIFeatures {
     this._document.addEventListener('keyup', this.onKeyUp);
     this._document.addEventListener('copy', this.onCopy);
     const topbarLogo = this._dom.find('.lh-topbar__logo', this._document);
-    topbarLogo.addEventListener('click', this._toggleDarkTheme);
+    topbarLogo.addEventListener('click', () => this._toggleDarkTheme());
+
+    let turnOffTheLights = false;
+    if (window.matchMedia('(prefers-color-scheme: dark)').matches) {
+      turnOffTheLights = true;
+    }
+
+    // Fireworks.
+    const scoresAll100 = Object.values(report.categories).every(cat => cat.score === 1);
+    if (!this._dom.isDevTools() && scoresAll100) {
+      turnOffTheLights = true;
+      const scoresContainer = this._dom.find('.lh-scores-container', this._document);
+      scoresContainer.classList.add('score100');
+      scoresContainer.addEventListener('click', _ => {
+        scoresContainer.classList.toggle('fireworks-paused');
+      });
+    }
+
+    if (turnOffTheLights) {
+      this._toggleDarkTheme(true);
+    }
 
     // There is only a sticky header when at least 2 categories are present.
     if (Object.keys(this.json.categories).length >= 2) {
@@ -513,9 +533,10 @@ class ReportUIFeatures {
 
   /**
    * @private
+   * @param {boolean} [force]
    */
-  _toggleDarkTheme() {
-    this._document.body.classList.toggle('dark');
+  _toggleDarkTheme(force) {
+    this._document.body.classList.toggle('dark', force);
   }
 
   _updateStickyHeaderOnScroll() {
diff --git a/lighthouse-core/report/html/report-styles.css b/lighthouse-core/report/html/report-styles.css
index 39f353415..ea5205614 100644
--- a/lighthouse-core/report/html/report-styles.css
+++ b/lighthouse-core/report/html/report-styles.css
@@ -566,7 +566,7 @@
   align-items: center;
   flex-wrap: wrap;
   justify-content: space-between;
-  padding: 10px var(--text-indent);
+  padding: 10px 0;
 }
 
 .lh-metric__details {
@@ -581,7 +581,6 @@
 .lh-metrics__disclaimer {
   color: var(--medium-75-gray);
   margin: var(--lh-section-vpadding) 0;
-  padding: 0 var(--text-indent);
 }
 
 .lh-metric__description {
@@ -839,6 +838,21 @@
   vertical-align: middle;
 }
 
+/* Style the "over budget" columns red. */
+.lh-audit-group--budgets .lh-table tbody tr td:nth-child(4),
+.lh-audit-group--budgets .lh-table tbody tr td:nth-child(5){
+  color: var(--color-red-700);
+}
+
+/* Align the "over budget request count" text to be close to the "over budget bytes" column. */
+.lh-audit-group--budgets .lh-table tbody tr td:nth-child(4){
+  text-align: right;
+}
+
+.lh-audit-group--budgets .lh-table {
+  width: 100%;
+}
+
 .lh-audit-group--pwa-fast-reliable .lh-audit-group__header::before {
   content: '';
   background-image: var(--pwa-fast-reliable-gray-url);
diff --git a/lighthouse-core/report/html/templates.html b/lighthouse-core/report/html/templates.html
index cc96aee34..6df3186d4 100644
--- a/lighthouse-core/report/html/templates.html
+++ b/lighthouse-core/report/html/templates.html
@@ -143,7 +143,6 @@ limitations under the License.
   </div>
 </template>
 
-
 <!-- Lighthouse perf opportunity header -->
 <template id="tmpl-lh-opportunity-header">
   <div class="lh-load-opportunity__header lh-load-opportunity__cols">
@@ -152,7 +151,6 @@ limitations under the License.
   </div>
 </template>
 
-
 <!-- Lighthouse score container -->
 <template id="tmpl-lh-scores-wrapper">
   <style>
diff --git a/proto/sample_v2_round_trip.json b/proto/sample_v2_round_trip.json
index 89196f363..401a3ff9f 100644
--- a/proto/sample_v2_round_trip.json
+++ b/proto/sample_v2_round_trip.json
@@ -20,6 +20,14 @@
             "scoreDisplayMode": "binary",
             "title": "Uses Application Cache"
         },
+        "apple-touch-icon": {
+            "description": "For ideal appearance on iOS when users add to the home screen, define an apple-touch-icon. It must point to a non-transparent 192px (or 180px) square PNG. [Learn More](https://developers.google.com/web/fundamentals/design-and-ux/browser-customization/).",
+            "id": "apple-touch-icon",
+            "score": 0.0,
+            "scoreDisplayMode": "binary",
+            "title": "Does not provide a valid `apple-touch-icon`",
+            "warnings": []
+        },
         "aria-allowed-attr": {
             "description": "Each ARIA `role` supports a specific subset of `aria-*` attributes. Mismatching these invalidates the `aria-*` attributes. [Learn more](https://dequeuniversity.com/rules/axe/3.1/aria-allowed-attr?application=lighthouse).",
             "id": "aria-allowed-attr",
@@ -3576,6 +3584,7 @@
                     "weight": 0.0
                 },
                 {
+                    "group": "budgets",
                     "id": "performance-budget",
                     "weight": 0.0
                 },
@@ -3683,6 +3692,11 @@
                     "id": "without-javascript",
                     "weight": 1.0
                 },
+                {
+                    "group": "pwa-optimized",
+                    "id": "apple-touch-icon",
+                    "weight": 1.0
+                },
                 {
                     "id": "pwa-cross-browser",
                     "weight": 0.0
@@ -3699,7 +3713,7 @@
             "description": "These checks validate the aspects of a Progressive Web App. [Learn more](https://developers.google.com/web/progressive-web-apps/checklist).",
             "id": "pwa",
             "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.",
-            "score": 0.42,
+            "score": 0.41,
             "title": "Progressive Web App"
         },
         "seo": {
@@ -4208,6 +4222,12 @@
                 "name": "lh:computed:ManifestValues",
                 "startTime": 0.0
             },
+            {
+                "duration": 100.0,
+                "entryType": "measure",
+                "name": "lh:audit:apple-touch-icon",
+                "startTime": 0.0
+            },
             {
                 "duration": 100.0,
                 "entryType": "measure",


Test patch:
diff --git a/clients/test/extension/extension-test.js b/clients/test/extension/extension-test.js
index 1517e5c00..63e86e88c 100644
--- a/clients/test/extension/extension-test.js
+++ b/clients/test/extension/extension-test.js
@@ -135,8 +135,10 @@ describe('Lighthouse chrome extension', function() {
     for (const category of lighthouseCategories) {
       let expected = getAuditsOfCategory(category);
       if (category === 'performance') {
-        expected = getAuditsOfCategory(category).filter(a => !!a.group);
+        expected = getAuditsOfCategory(category)
+          .filter(a => !!a.group && a.id !== 'performance-budget');
       }
+      // Performance budget audit is not included in the Chrome extension of Lighthouse
       expected = expected.map(audit => audit.id);
       const elementIds = await getAuditElementsIds({category, selector: selectors.audits});
 
diff --git a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
index d1e45ec5d..d2aa1fbba 100644
--- a/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
+++ b/lighthouse-cli/test/cli/__snapshots__/index-test.js.snap
@@ -69,6 +69,9 @@ Object {
     Object {
       "path": "installable-manifest",
     },
+    Object {
+      "path": "apple-touch-icon",
+    },
     Object {
       "path": "splash-screen",
     },
@@ -818,6 +821,7 @@ Object {
           "weight": 0,
         },
         Object {
+          "group": "budgets",
           "id": "performance-budget",
           "weight": 0,
         },
@@ -923,6 +927,11 @@ Object {
           "id": "without-javascript",
           "weight": 1,
         },
+        Object {
+          "group": "pwa-optimized",
+          "id": "apple-touch-icon",
+          "weight": 1,
+        },
         Object {
           "id": "pwa-cross-browser",
           "weight": 0,
diff --git a/lighthouse-cli/test/smokehouse/pwa-expectations.js b/lighthouse-cli/test/smokehouse/pwa-expectations.js
index b4a91c98c..85616240b 100644
--- a/lighthouse-cli/test/smokehouse/pwa-expectations.js
+++ b/lighthouse-cli/test/smokehouse/pwa-expectations.js
@@ -66,6 +66,9 @@ module.exports = [
         'content-width': {
           score: 1,
         },
+        'apple-touch-icon': {
+          score: 1,
+        },
 
         // "manual" audits. Just verify in the results.
         'pwa-cross-browser': {
@@ -128,6 +131,9 @@ module.exports = [
         'content-width': {
           score: 1,
         },
+        'apple-touch-icon': {
+          score: 1,
+        },
 
         // "manual" audits. Just verify in the results.
         'pwa-cross-browser': {
diff --git a/lighthouse-cli/test/smokehouse/pwa2-expectations.js b/lighthouse-cli/test/smokehouse/pwa2-expectations.js
index 3a56038d3..b8b93d09b 100644
--- a/lighthouse-cli/test/smokehouse/pwa2-expectations.js
+++ b/lighthouse-cli/test/smokehouse/pwa2-expectations.js
@@ -61,6 +61,12 @@ module.exports = [
         'content-width': {
           score: 1,
         },
+        'apple-touch-icon': {
+          score: 1,
+          warnings: [
+            /apple-touch-icon-precomposed/,
+          ],
+        },
 
         // "manual" audits. Just verify in the results.
         'pwa-cross-browser': {
@@ -123,6 +129,9 @@ module.exports = [
         'content-width': {
           score: 1,
         },
+        'apple-touch-icon': {
+          score: 0,
+        },
 
         // "manual" audits. Just verify in the results.
         'pwa-cross-browser': {
diff --git a/lighthouse-cli/test/smokehouse/pwa3-expectations.js b/lighthouse-cli/test/smokehouse/pwa3-expectations.js
index 351a34272..d05f37387 100644
--- a/lighthouse-cli/test/smokehouse/pwa3-expectations.js
+++ b/lighthouse-cli/test/smokehouse/pwa3-expectations.js
@@ -58,6 +58,9 @@ module.exports = [
         'content-width': {
           score: 1,
         },
+        'apple-touch-icon': {
+          score: 1,
+        },
 
         // "manual" audits. Just verify in the results.
         'pwa-cross-browser': {
diff --git a/lighthouse-core/test/audits/apple-touch-icon-test.js b/lighthouse-core/test/audits/apple-touch-icon-test.js
new file mode 100644
index 000000000..39ce2a1b0
--- /dev/null
+++ b/lighthouse-core/test/audits/apple-touch-icon-test.js
@@ -0,0 +1,98 @@
+/**
+ * @license Copyright 2019 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';
+
+const AppleTouchIcon = require('../../audits/apple-touch-icon.js');
+
+/* eslint-env jest */
+
+describe('PWA: apple-touch-icon audit', () => {
+  it(`fails when apple-touch-icon is not present`, () => {
+    const artifacts = {
+      LinkElements: [
+        {rel: 'iOS-touch-thing', href: 'https://example.com/else.png'},
+        {rel: 'something else', href: 'https://example.com/else.png'},
+      ],
+    };
+
+    const {score} = AppleTouchIcon.audit(artifacts);
+
+    expect(score).toBe(0);
+  });
+
+  it(`fails when apple-touch-icon does not have an href`, () => {
+    const artifacts = {
+      LinkElements: [{rel: 'apple-touch-icon'}],
+    };
+
+    const {score} = AppleTouchIcon.audit(artifacts);
+
+    expect(score).toBe(0);
+  });
+
+  it(`passes but warns when apple-touch-icon-precomposed only`, () => {
+    const artifacts = {
+      LinkElements: [{rel: 'apple-touch-icon-precomposed', href: 'https://example.com/touch-icon.png'}],
+    };
+
+    const {score, warnings} = AppleTouchIcon.audit(artifacts);
+
+    expect(score).toBe(1);
+    expect(warnings[0]).toBeDisplayString('`apple-touch-icon-precomposed` is ' +
+      'out of date; `apple-touch-icon` is preferred.');
+  });
+
+  it(`passes with no warning when precomposed with normal`, () => {
+    const artifacts = {
+      LinkElements: [
+        {rel: 'apple-touch-icon', href: 'https://example.com/touch-icon.png'},
+        {rel: 'apple-touch-icon-precomposed', href: 'https://example.com/touch-icon.png'},
+      ],
+    };
+
+    const {score, warnings} = AppleTouchIcon.audit(artifacts);
+
+    expect(score).toBe(1);
+    expect(warnings).toEqual([]);
+  });
+
+  it(`passes when apple-touch-icon is on page`, () => {
+    const artifacts = {
+      LinkElements: [{rel: 'apple-touch-icon', href: 'https://example.com/touch-icon.png'}],
+    };
+
+    const {score} = AppleTouchIcon.audit(artifacts);
+
+    expect(score).toBe(1);
+  });
+
+  it(`passes with multiple apple-touch-icons on page`, () => {
+    const artifacts = {
+      LinkElements: [
+        {rel: 'apple-touch-icon', sizes: '152x152', href: 'https://example.com/touch-icon.png'},
+        {rel: 'apple-touch-icon', sizes: '180x180', href: 'https://example.com/touch-icon.png'},
+      ],
+    };
+
+    const {score} = AppleTouchIcon.audit(artifacts);
+
+    expect(score).toBe(1);
+  });
+
+  it(`passes when lots of LinkElements`, () => {
+    const artifacts = {
+      LinkElements: [
+        {rel: 'iOS-touch-thing', href: 'https://example.com/else.png'},
+        {rel: 'apple-touch-icon', href: 'https://example.com/touch-icon.png'},
+        {rel: 'something else', href: 'https://example.com/else.png'},
+      ],
+    };
+
+    const {score} = AppleTouchIcon.audit(artifacts);
+
+    expect(score).toBe(1);
+  });
+});
diff --git a/lighthouse-core/test/report/html/renderer/category-renderer-test.js b/lighthouse-core/test/report/html/renderer/category-renderer-test.js
index 8e691f5a5..b18f27521 100644
--- a/lighthouse-core/test/report/html/renderer/category-renderer-test.js
+++ b/lighthouse-core/test/report/html/renderer/category-renderer-test.js
@@ -366,7 +366,7 @@ describe('CategoryRenderer', () => {
       const manualAudits = elem.querySelectorAll('.lh-clump--manual .lh-audit');
 
       assert.equal(passedAudits.length, 2);
-      assert.equal(failedAudits.length, 8);
+      assert.equal(failedAudits.length, 9);
       assert.equal(warningAudits.length, 2);
       assert.equal(manualAudits.length, 3);
     });
@@ -380,7 +380,7 @@ describe('CategoryRenderer', () => {
       const failedAudits = elem.querySelectorAll('.lh-clump--failed .lh-audit');
 
       assert.equal(passedAudits.length, 0);
-      assert.equal(failedAudits.length, 12);
+      assert.equal(failedAudits.length, 13);
     });
 
     it('expands warning audit group', () => {
diff --git a/lighthouse-core/test/report/html/renderer/performance-category-renderer-test.js b/lighthouse-core/test/report/html/renderer/performance-category-renderer-test.js
index 776763a7c..42c115c40 100644
--- a/lighthouse-core/test/report/html/renderer/performance-category-renderer-test.js
+++ b/lighthouse-core/test/report/html/renderer/performance-category-renderer-test.js
@@ -68,7 +68,7 @@ describe('PerfCategoryRenderer', () => {
   it('renders the sections', () => {
     const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
     const sections = categoryDOM.querySelectorAll('.lh-category > .lh-audit-group');
-    assert.equal(sections.length, 4);
+    assert.equal(sections.length, 5);
   });
 
   it('renders the metrics', () => {
@@ -151,7 +151,8 @@ describe('PerfCategoryRenderer', () => {
     const passedSection = categoryDOM.querySelector('.lh-category > .lh-clump--passed');
 
     const passedAudits = category.auditRefs.filter(audit =>
-        audit.group && audit.group !== 'metrics' && Util.showAsPassed(audit.result));
+      audit.group && audit.group !== 'metrics' && audit.id !== 'performance-budget'
+        && Util.showAsPassed(audit.result));
     const passedElements = passedSection.querySelectorAll('.lh-audit');
     assert.equal(passedElements.length, passedAudits.length);
   });
@@ -175,6 +176,36 @@ describe('PerfCategoryRenderer', () => {
     });
   });
 
+  describe('budgets', () => {
+    it('renders a performance budget', () => {
+      const categoryDOM = renderer.render(category, sampleResults.categoryGroups);
+
+      const budgetsGroup = categoryDOM.querySelector('.lh-audit-group.lh-audit-group--budgets');
+      assert.ok(budgetsGroup);
+
+      const header = budgetsGroup.querySelector('.lh-audit-group__header');
+      assert.ok(header);
+
+      const budgetTable = budgetsGroup.querySelector('#performance-budget.lh-table');
+      assert.ok(budgetTable);
+
+      const lhrBudgetEntries = sampleResults.audits['performance-budget'].details.items;
+      const tableRows = budgetTable.querySelectorAll('tbody > tr');
+      assert.strictEqual(tableRows.length, lhrBudgetEntries.length);
+    });
+
+    it('does not render a budget table when performance-budget audit is notApplicable', () => {
+      const budgetlessCategory = JSON.parse(JSON.stringify(category));
+      const budgetRef = budgetlessCategory.auditRefs.find(a => a.id === 'performance-budget');
+      budgetRef.result.scoreDisplayMode = 'notApplicable';
+      delete budgetRef.result.details;
+
+      const categoryDOM = renderer.render(budgetlessCategory, sampleResults.categoryGroups);
+      const budgetsGroup = categoryDOM.querySelector('.lh-audit-group.lh-audit-group--budgets');
+      assert.strictEqual(budgetsGroup, null);
+    });
+  });
+
   // This is done all in CSS, but tested here.
   describe('metric description toggles', () => {
     let container;
diff --git a/lighthouse-core/test/report/html/renderer/report-renderer-test.js b/lighthouse-core/test/report/html/renderer/report-renderer-test.js
index d6d8e6a98..9e4836459 100644
--- a/lighthouse-core/test/report/html/renderer/report-renderer-test.js
+++ b/lighthouse-core/test/report/html/renderer/report-renderer-test.js
@@ -82,8 +82,6 @@ describe('ReportRenderer', () => {
       // 3 sets of gauges - one in sticky header, one in scores header, and one in each section.
       assert.equal(output.querySelectorAll('.lh-gauge__wrapper, .lh-gauge--pwa__wrapper').length,
           sampleResults.reportCategories.length * 3, 'renders category gauges');
-      // no fireworks
-      assert.ok(output.querySelector('.score100') === null, 'has no fireworks treatment');
     });
 
     it('renders additional reports by replacing the existing one', () => {
@@ -214,23 +212,6 @@ describe('ReportRenderer', () => {
     assert.equal(renderer._templateContext, otherDocument);
   });
 
-  it('should render an all 100 report with fireworks', () => {
-    const container = renderer._dom._document.body;
-
-    sampleResults.reportCategories.forEach(element => {
-      element.score = 1;
-    });
-
-    const output = renderer.renderReport(sampleResults, container);
-    // standard checks
-    assert.ok(output.querySelector('.lh-header-sticky'), 'has a header');
-    assert.ok(output.querySelector('.lh-report'), 'has report body');
-    assert.equal(output.querySelectorAll('.lh-gauge__wrapper, .lh-gauge--pwa__wrapper').length,
-        sampleResults.reportCategories.length * 3, 'renders category gauges');
-    // fireworks!
-    assert.ok(output.querySelector('.score100'), 'has fireworks treatment');
-  });
-
   it('should add LHR channel to doc link parameters', () => {
     const lhrChannel = sampleResults.configSettings.channel;
     // Make sure we have a channel in the LHR.
@@ -255,7 +236,8 @@ describe('ReportRenderer', () => {
 
     let notApplicableCount = 0;
     Object.values(clonedSampleResult.audits).forEach(audit => {
-      if (audit.scoreDisplayMode === 'notApplicable') {
+      // The performance-budget audit is omitted from the DOM when it is not applicable
+      if (audit.scoreDisplayMode === 'notApplicable' && audit.id !== 'performance-budget') {
         notApplicableCount++;
         audit.scoreDisplayMode = 'not_applicable';
       }
diff --git a/lighthouse-core/test/report/html/renderer/report-ui-features-test.js b/lighthouse-core/test/report/html/renderer/report-ui-features-test.js
index 4e83cd0cc..54a3d7852 100644
--- a/lighthouse-core/test/report/html/renderer/report-ui-features-test.js
+++ b/lighthouse-core/test/report/html/renderer/report-ui-features-test.js
@@ -171,4 +171,22 @@ describe('ReportUIFeatures', () => {
       });
     });
   });
+
+  describe('fireworks', () => {
+    it('should render an non-all 100 report without fireworks', () => {
+      const lhr = JSON.parse(JSON.stringify(sampleResults));
+      lhr.categories.performance.score = 0.5;
+      const container = render(lhr);
+      assert.ok(container.querySelector('.score100') === null, 'has no fireworks treatment');
+    });
+
+    it('should render an all 100 report with fireworks', () => {
+      const lhr = JSON.parse(JSON.stringify(sampleResults));
+      Object.values(lhr.categories).forEach(element => {
+        element.score = 1;
+      });
+      const container = render(lhr);
+      assert.ok(container.querySelector('.score100'), 'has fireworks treatment');
+    });
+  });
 });
diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json
index 95f002dd0..7fe39c6e7 100644
--- a/lighthouse-core/test/results/sample_v2.json
+++ b/lighthouse-core/test/results/sample_v2.json
@@ -476,6 +476,14 @@
         ]
       }
     },
+    "apple-touch-icon": {
+      "id": "apple-touch-icon",
+      "title": "Does not provide a valid `apple-touch-icon`",
+      "description": "For ideal appearance on iOS when users add to the home screen, define an apple-touch-icon. It must point to a non-transparent 192px (or 180px) square PNG. [Learn More](https://developers.google.com/web/fundamentals/design-and-ux/browser-customization/).",
+      "score": 0,
+      "scoreDisplayMode": "binary",
+      "warnings": []
+    },
     "splash-screen": {
       "id": "splash-screen",
       "title": "Is not configured for a custom splash screen",
@@ -3448,7 +3456,8 @@
         },
         {
           "id": "performance-budget",
-          "weight": 0
+          "weight": 0,
+          "group": "budgets"
         },
         {
           "id": "resource-summary",
@@ -3929,6 +3938,11 @@
           "weight": 1,
           "group": "pwa-optimized"
         },
+        {
+          "id": "apple-touch-icon",
+          "weight": 1,
+          "group": "pwa-optimized"
+        },
         {
           "id": "pwa-cross-browser",
           "weight": 0
@@ -3943,7 +3957,7 @@
         }
       ],
       "id": "pwa",
-      "score": 0.42
+      "score": 0.41
     }
   },
   "categoryGroups": {
@@ -4312,6 +4326,12 @@
         "duration": 100,
         "entryType": "measure"
       },
+      {
+        "startTime": 0,
+        "name": "lh:audit:apple-touch-icon",
+        "duration": 100,
+        "entryType": "measure"
+      },
       {
         "startTime": 0,
         "name": "lh:audit:splash-screen",
@@ -5150,6 +5170,12 @@
       "lighthouse-core/audits/redirects.js | description": [
         "audits.redirects.description"
       ],
+      "lighthouse-core/audits/apple-touch-icon.js | failureTitle": [
+        "audits[apple-touch-icon].title"
+      ],
+      "lighthouse-core/audits/apple-touch-icon.js | description": [
+        "audits[apple-touch-icon].description"
+      ],
       "lighthouse-core/audits/mainthread-work-breakdown.js | title": [
         "audits[mainthread-work-breakdown].title"
       ],