Repository: GoogleChrome/lighthouse
Pull request #12510 (2021-05-18 22:44:59)

core(fr): align base artifacts with legacy gather-runner
**Summary**
Brings the base artifacts support in FR up to snuff with legacy gather runner.

**Related Issues/PRs**
ref #11313 


patrickhulce: > Will base artifacts be available in gatherers as dependencies, or can we just pass them in FRTransitionalContext separately?

Great question, this does not yet enable base artifacts as dependencies. We can talk about how to accomplish that when the need arises (has it already come up for you?). URL is the biggest one I can think of, but that's already available on the phaseContext. 
adamraine: > We can talk about how to accomplish that when the need arises (has it already come up for you?)

Yes, I know `script-elements` uses `HostFormFactor`.

I think it would be easier to include base artifacts separately in the context, or include them as dependencies for all gatherers by default. Essentially I want to avoid writing:
```js
/** @type {LH.Gatherer.FRTransitionalContext<'HostFormFactor'>} */
meta = {
	supportedModes = ['navigation'],
	dependencies: {HostFormFactor: HostFormFactor.symbol},
}
```

Patch:
diff --git a/changelog.md b/changelog.md
index 5a4b97a76..abdaa335d 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,3 +1,81 @@
+<a name="8.0.0"></a>
+# 8.0.0 (2021-06-02)
+[Full Changelog](https://github.com/GoogleChrome/lighthouse/compare/v7.5.0...v8.0.0)
+
+We expect this release to ship in the DevTools of [Chrome 93](https://chromiumdash.appspot.com/schedule), and is already in PageSpeed Insights!
+
+## Notable changes
+* The **Performance Category** had a number of scoring changes to align with other performance tools and to better reflect the state of the web.
+  - The Performance Score has been reweighted ([#12577](https://github.com/GoogleChrome/lighthouse/pull/12577))
+  - The TBT and FCP score curves have been updated ([#12576](https://github.com/GoogleChrome/lighthouse/pull/12576), [#12556](https://github.com/GoogleChrome/lighthouse/pull/12556))
+  - CLS has been updated to its new, windowed
+definition ([#12554](https://github.com/GoogleChrome/lighthouse/pull/12554))
+
+  See the [v8.0 Performance FAQ](https://github.com/GoogleChrome/lighthouse/blob/master/docs/v8-perf-faq.md) for more detail.
+
+  <a href="https://github.com/GoogleChrome/lighthouse/blob/master/docs/v8-perf-faq.md"><img width="500" alt="the new metric weightings in the Lighthouse score calculator" src="https://user-images.githubusercontent.com/39191/120410971-de337100-c308-11eb-9fb6-368a33c0855e.png"></a>
+
+
+* The report includes a new metric filter. Pick a metric to focus on the opportunities and diagnostics most relevant to improving just that metric:
+
+  <img width="350" alt="the new metric filter in the lighthouse report" src="https://user-images.githubusercontent.com/316891/120384128-61de6500-c2eb-11eb-9d15-5b92981d897e.png">
+* The [Lighthouse Treemap](#treemap-release) is now available across all the major Lighthouse clients. If your site exposes source maps to Lighthouse, look for the "View Treemap" button to see a breakdown of your shipped JavaScript, filterable by size and coverage on load.
+
+## 🆕 New audits
+* The new audit `csp-xss` has been added to Best Practices to evaluate Content Security Policies and suggest ways of making them more secure. This can be a challenging audit to pass depending on hosting environment and page content, so for now it is unscored ([#12514](https://github.com/GoogleChrome/lighthouse/pull/12514), [#12551](https://github.com/GoogleChrome/lighthouse/pull/12551))
+
+## 🤖💥 Breaking changes for programmatic users
+* Legacy metrics `estimated-input-latency` and `first-cpu-idle` have been deprecated, unweighted, and hidden since Lighthouse 6. They have now been removed ([#12553](https://github.com/GoogleChrome/lighthouse/pull/12553))
+* Starting with Node 13, Node is built with `full-icu` by default, so the very large `intl` polyfill has been removed from Lighthouse's dependencies. See the [readme FAQ](https://github.com/GoogleChrome/lighthouse#how-do-i-get-localized-lighthouse-results-via-the-cli) if you're stuck with `small-icu` but need localization support ([#12426](https://github.com/GoogleChrome/lighthouse/pull/12426))
+* Long-time audit `uses-webp-images` has a new ID, `modern-image-formats`. WebP is now rather mainstream, and this frees the audit to start including details on other cutting-edge image formats in the future ([#12535](https://github.com/GoogleChrome/lighthouse/pull/12535))
+* The `image-elements` artifact has been restructured for clarity ([#12568](https://github.com/GoogleChrome/lighthouse/pull/12568))
+
+## 🧱 Core
+
+* navigate: only observe longtasks in PerfObserver ([#12545](https://github.com/GoogleChrome/lighthouse/pull/12545))
+* script-treemap-data: do not create nodes with blank names ([#12569](https://github.com/GoogleChrome/lighthouse/pull/12569))
+
+## ⛏️👷 Fraggle Rock
+
+  Support for auditing user flows ([#11313](https://github.com/GoogleChrome/lighthouse/issues/11313))
+
+* add `settings` to gather context ([#12574](https://github.com/GoogleChrome/lighthouse/pull/12574))
+* fix usage of distributed conditional type ([#12565](https://github.com/GoogleChrome/lighthouse/pull/12565))
+* support `inspector-issues` gatherer ([#12530](https://github.com/GoogleChrome/lighthouse/pull/12530))
+* support `tags-blocking-first-paint` gatherer ([#12527](https://github.com/GoogleChrome/lighthouse/pull/12527))
+* support `response-compression` gatherer ([#12508](https://github.com/GoogleChrome/lighthouse/pull/12508))
+* align `navigation-runner` with legacy `gather-runner` ([#12478](https://github.com/GoogleChrome/lighthouse/pull/12478))
+
+## 📔 Report
+
+* improve accessibility of metric filter ([#12552](https://github.com/GoogleChrome/lighthouse/pull/12552))
+* treemap: add placeholder, gist, and file upload features ([#12511](https://github.com/GoogleChrome/lighthouse/pull/12511))
+* treemap: support passing gzipped data in url ([#12509](https://github.com/GoogleChrome/lighthouse/pull/12509), [#12519](https://github.com/GoogleChrome/lighthouse/pull/12519))
+* viewer: fix gist icon ([#12505](https://github.com/GoogleChrome/lighthouse/pull/12505))
+
+## 👥 Clients
+
+* fix main session OOPIF checks for devtools ([#12533](https://github.com/GoogleChrome/lighthouse/pull/12533))
+* render treemap button in all clients ([#12516](https://github.com/GoogleChrome/lighthouse/pull/12516), [#12570](https://github.com/GoogleChrome/lighthouse/pull/12570), [#12590](https://github.com/GoogleChrome/lighthouse/pull/12590))
+* backport proto formatting fixes ([#12566](https://github.com/GoogleChrome/lighthouse/pull/12566))
+
+## 🌍 i18n
+
+* import new strings ([#12515](https://github.com/GoogleChrome/lighthouse/pull/12515), [#12591](https://github.com/GoogleChrome/lighthouse/pull/12591))
+
+## 📋 Tests
+
+* run unit tests on all active node versions in CI ([#12513](https://github.com/GoogleChrome/lighthouse/pull/12513))
+* sync devtools webtests ([#12578](https://github.com/GoogleChrome/lighthouse/pull/12578))
+* treemap: reduce `debug.json` ([#12555](https://github.com/GoogleChrome/lighthouse/pull/12555))
+
+## 🕸️ Deps
+
+* update `lighthouse-plugin-publisher-ads` to 1.4.1 ([#12592](https://github.com/GoogleChrome/lighthouse/pull/12592))
+* update `axe-core` to 4.2.1 ([#12575](https://github.com/GoogleChrome/lighthouse/pull/12575))
+* update `puppeteer` to 9.1.1 ([#12284](https://github.com/GoogleChrome/lighthouse/pull/12284))
+* update `chrome-launcher` to 0.14.0 ([#12507](https://github.com/GoogleChrome/lighthouse/pull/12507))
+
 <a name="7.5.0"></a>
 # 7.5.0 (2021-05-18)
 [Full Changelog](https://github.com/GoogleChrome/lighthouse/compare/v7.4.0...v7.5.0)
@@ -12,25 +90,24 @@ Thanks to our new contributor 👽🐷🐰🐯🐻!
 
 ## Notable Changes
 
+<a name="treemap-release"></a>
 We are releasing the Lighthouse Treemap!
 
-![image](https://user-images.githubusercontent.com/4071474/118602146-2d08d480-b767-11eb-9273-9a8de7000e67.png)
+<a href="https://user-images.githubusercontent.com/4071474/118602146-2d08d480-b767-11eb-9273-9a8de7000e67.png"><img src="https://user-images.githubusercontent.com/4071474/118602146-2d08d480-b767-11eb-9273-9a8de7000e67.png" width="48%"></a> <a href="https://user-images.githubusercontent.com/4071474/118602240-4742b280-b767-11eb-9f6a-433788029a30.png"><img src="https://user-images.githubusercontent.com/4071474/118602240-4742b280-b767-11eb-9f6a-433788029a30.png" width="48%"></a>
 
 You may already be familiar with treemaps thanks to [webtreemap](https://github.com/evmar/webtreemap) (which we use!) or [source-map-explorer](https://github.com/danvk/source-map-explorer). With Lighthouse Treemap, you'll be able to view all the JavaScript bundles on your page easily from a Lighthouse report, in addition to some insights that may help reduce the amount of JavaScript on a page. The only requirement is that source maps are accessible (either publicly, or securely from the same computer that is running the Lighthouse audit).
 
-We even collect code coverage data from Chrome, and extrapolate the coverage of individual modules in a bundle:
-
-![image](https://user-images.githubusercontent.com/4071474/118602240-4742b280-b767-11eb-9f6a-433788029a30.png)
-
-Note: this only takes into account a cold-load: code only used after user interaction will be marked as unused. Stay tuned for a future release, which will enable you to configure user flows and capture even more accurate performance insights.
+We even collect **code coverage** data from Chrome, and extrapolate the coverage of individual modules in a bundle. Note: this only takes into account a cold-load: code only used after user interaction will be marked as unused. Stay tuned for a future release, which will enable you to configure user flows and capture even more accurate performance insights.
 
 If we detect a large module included by multiple bundles, we'll alert you of that too.
 
 You can access Lighthouse Treemap from the report:
 
-![image](https://user-images.githubusercontent.com/4071474/118600879-6e4cb480-b766-11eb-85f7-e8281006711b.png)
+<img src="https://user-images.githubusercontent.com/39191/120411450-aed13400-c309-11eb-9746-2f07d5efd276.png" width="350">
+
+Currently, only reports generated with the Lighthouse Node CLI will connect to the Lighthouse Treemap App. This functionality will be in  DevTools and PageSpeed Insights as of Lighthouse v8.0.
 
-Currently, only reports generated with the Lighthouse Node CLI will connect to the Lighthouse Treemap App. We are working on adding this functionality to Lighthouse in DevTools and PageSpeed Insights.
+[Demo](https://googlechrome.github.io/lighthouse/treemap/?gist=30a18304de56e7be08f10976a1b11702)
 
 ## Core
 
diff --git a/docs/plugins.md b/docs/plugins.md
index 5e0df985c..5320b0bda 100644
--- a/docs/plugins.md
+++ b/docs/plugins.md
@@ -60,10 +60,10 @@ A Lighthouse plugin is just a node module with a name that starts with `lighthou
   "name": "lighthouse-plugin-cats",
   "main": "plugin.js",
   "peerDependencies": {
-    "lighthouse": "^7.5.0"
+    "lighthouse": "^8.0.0"
   },
   "devDependencies": {
-    "lighthouse": "^7.5.0"
+    "lighthouse": "^8.0.0"
   }
 }
 ```
diff --git a/docs/recipes/custom-audit/package.json b/docs/recipes/custom-audit/package.json
index ec666077e..12c84b3b4 100644
--- a/docs/recipes/custom-audit/package.json
+++ b/docs/recipes/custom-audit/package.json
@@ -3,6 +3,6 @@
   "private": true,
   "scripts": {},
   "devDependencies": {
-    "lighthouse": "^7.5.0"
+    "lighthouse": "^8.0.0"
   }
 }
diff --git a/docs/recipes/gulp/package.json b/docs/recipes/gulp/package.json
index 744a52081..587deb0fc 100644
--- a/docs/recipes/gulp/package.json
+++ b/docs/recipes/gulp/package.json
@@ -7,6 +7,6 @@
   "devDependencies": {
     "gulp": "^3.9.1",
     "gulp-connect": "^5.0.0",
-    "lighthouse": "^7.5.0"
+    "lighthouse": "^8.0.0"
   }
 }
diff --git a/docs/recipes/lighthouse-plugin-example/package.json b/docs/recipes/lighthouse-plugin-example/package.json
index fdd17044c..ed9c97f77 100644
--- a/docs/recipes/lighthouse-plugin-example/package.json
+++ b/docs/recipes/lighthouse-plugin-example/package.json
@@ -3,9 +3,9 @@
   "private": true,
   "main": "./plugin.js",
   "peerDependencies": {
-    "lighthouse": "^7.5.0"
+    "lighthouse": "^8.0.0"
   },
   "devDependencies": {
-    "lighthouse": "^7.5.0"
+    "lighthouse": "^8.0.0"
   }
 }
diff --git a/docs/v8-perf-faq.md b/docs/v8-perf-faq.md
new file mode 100644
index 000000000..127940cd7
--- /dev/null
+++ b/docs/v8-perf-faq.md
@@ -0,0 +1,244 @@
+# v8.0 Performance FAQ
+
+### Give me a summary of the perf score changes in v8.0. What's new/different?
+
+First, it may be useful to refresh on [the math behind Lighthouse's metric
+scores and performance score.](https://web.dev/performance-scoring/)
+
+In [Lighthouse v8.0](https://github.com/GoogleChrome/lighthouse/releases/tag/v8.0.0), we updated the score curves for FCP and TBT measurements,
+making both a bit more strict. CLS has been updated to its new, [windowed
+definition](https://web.dev/evolving-cls/). Additionally, the Performance
+Score's weighted average was
+[rebalanced](https://googlechrome.github.io/lighthouse/scorecalc/#FCP=3000&SI=5800&FMP=4000&TTI=7300&FCI=6500&LCP=4000&TBT=600&CLS=0.25&device=mobile&version=8&version=6&version=5),
+giving more weight to CLS and TBT than before, and slightly decreasing the
+weights of FCP, SI, and TTI.
+
+From an analysis of HTTP Archive's latest [crawl of the
+web](https://httparchive.org/faq#how-does-the-http-archive-decide-which-urls-to-test),
+we project that the performance score for the majority of sites will stay the
+same or improve in Lighthouse 8.0.
+- ~20% of sites may see a drop of up to 5 points, though likely less
+- ~20% of sites will see little detectable change
+- ~30% of sites should see a moderate improvement of a few points
+- ~30% could see a significant improvement of 5 points or more
+
+The biggest drops in scores are due to TBT scoring becoming stricter and the
+metric's slightly higher weight. The biggest improvements in scores are also due
+to TBT changes in the long tail and the windowing of CLS, and both metrics'
+higher weights.
+
+### What are the exact score weighting changes?
+
+#### Changes by metric
+
+| metric                         | v6 weight | v8 weight | Δ |
+|--------------------------------|-----------|-----------|----------|
+| First Contentful Paint (FCP)   | 15        | **10**    | -5       |
+| Speed Index (SI)               | 15        | **10**    | -5       |
+| Largest Contentful Paint (LCP) | 25        | **25**    | 0        |
+| Time To Interactive (TTI)        | 15        | **10**    | -5       |
+| Total Blocking Time (TBT)        | 25        | **30**    | 5        |
+| Cumulative Layout Shift (CLS)    | 5         | **15**    | 10       |
+
+#### Changes by phase
+
+| phase          | metric                         | v6 phase weight | v8 phase weight | Δ   |
+|----------------|--------------------------------|-----------------|-----------------|-----|
+| early          | First Contentful Paint (FCP)   | 15              | 10              | -5  |
+| mid            | Speed Index (SI)               | 40              | 35              | -5  |
+|                | Largest Contentful Paint (LCP) |                 |                 |     |
+| interactivity  | Time To Interactive (TTI)      | 40              | 40              | 0   |
+|                | Total Blocking Time (TBT)      |                 |                 |     |
+| predictability | Cumulative Layout Shift (CLS)  | 5               | 15              | 10  |
+
+### Why did the weight of CLS go up?
+
+When introduced in Lighthouse v6, it was still early days for the metric.
+There've been [many improvements and
+bugfixes](https://chromium.googlesource.com/chromium/src/+/refs/heads/main/docs/speed/metrics_changelog/cls.md)
+to the CLS metric since then. Now, given its maturity and established placement in Core
+Web Vitals, the weight increases from 5% to 15%.
+
+### Why are the Core Web Vitals metrics weighted differently in the performance score?
+
+The Core Web Vitals metrics are [independent signals in the Page Experience
+ranking
+update](https://support.google.com/webmasters/thread/104436075/core-web-vitals-page-experience-faqs-updated-march-2021).
+Lighthouse weighs each lab equivalent metric based on what we believe creates
+the best incentives to improve overall page experience for users.
+
+LCP, CLS, and TBT are [very good
+metrics](https://chromium.googlesource.com/chromium/src/+/lkgr/docs/speed/good_toplevel_metrics.md)
+and that's why they are the three highest-weighted metrics in the performance
+score.
+
+### How should I think about the Lighthouse performance score in relation to Core Web Vitals?
+
+[Core Web Vitals](https://web.dev/vitals/) refer to a specific set of key user
+experience metrics, their passing thresholds, and percentile at which they're measured.
+In general, CWV's primary focus is field data.
+
+The Lighthouse score is a means to understand the degree of opportunity
+available to improve critical elements of user experience. The lower the score,
+the more likely the user will struggle with load performance, responsiveness, or
+content stability.
+
+Lighthouse's lab-based data overlaps with Core Web Vitals in a few key ways.
+Lighthouse features two of the three core vitals (LCP and CLS) with the exact
+same passing thresholds. There's no user input in a Lighthouse run, so it cannot
+compute FID. Instead, we have TBT, which you can consider a proxy metric for
+FID, and though they measure two different things they are both signals about a
+page's interactivity.
+
+_So CWV and Lighthouse have commonalities, but are different. How can you
+rationalize paying attention to both?_
+
+Ultimately, a combination approach is most effective. Use field data for the
+long-term overview of your user's experience, and use lab data to iterate your
+way to the best experience possible for your users. CrUX data summarizes [the
+most recent 28
+days](https://developers.google.com/web/tools/chrome-user-experience-report/api/reference#data-pipeline),
+so it'll take some time to confidently determine that any change has definite
+impact.
+
+Lighthouse's analysis allows you to debug and optimize in an environment that is
+repeatable with an immediate feedback loop. In addition, lab-based tooling can
+provide significantly more detail than field instrumentation, as it's not
+limited to web-exposed APIs and cross-origin restrictions.
+
+The exact numbers of your lab and field metrics aren't expected to match, but
+any substantial improvement to your lab metrics should be observable in the
+field once it's been deployed. The higher the Lighthouse score, the less you're
+leaving up to chance in the field.
+
+### What blindspots from the field does lab tooling illuminate?
+
+Field data analyzes all successful page loads. Lab tooling analyzes the
+experience of a fixed configuration for a hypothetical user. If every potential
+user in the world successfully loaded an equal number of pages on your site, we
+might not need to focus on the experience of a hypothetical one, but in reality
+we know this isn't the case. Users who have better experiences use your site
+more; that's why we care about performance in the first place! Lab tooling shows
+you the quality of the experience for these hypothetical users that field data
+might be [missing
+entirely](https://blog.chriszacharias.com/page-weight-matters).
+
+Lighthouse mobile reports emulate a slow 4G connection on a mid-tier Android
+device. While field data might not indicate these conditions are especially
+common for your site, analyzing how your site performs in these tougher
+conditions helps expand your site's audience. Lighthouse identifies the worst
+experiences, experiences you can't see in the field because they were so bad the
+user never came back (or waited around in the first place).
+
+### How should I work to optimize CLS differently given that it has been updated?
+
+The [windowing
+adjustment](https://www.google.com/url?q=https://web.dev/evolving-cls/&sa=D&source=editors&ust=1622570731600000&usg=AOvVaw2R7Y5uFrQX7Mpdj__5SdYq)
+will likely not have much effect for the lab measurement, but instead will have
+a large effect on the field CLS for long-lived pages.
+
+Lighthouse 8 introduces another adjustment to our CLS definition: including
+layout shift contributions from subframes. This brings our implementation in
+line with how CrUX computes field CLS. This comes with the implication that
+iframes (including ones you may not control) may be adding layout shifts which
+ultimately affect your CLS score. Keep in mind that the subframe contributions
+are [weighted by the in-viewport
+portion](https://github.com/WICG/layout-instability#cumulative-scores) of the
+iframe.
+
+### Why don't the numbers for TBT and FID match, if TBT is a proxy metric for FID?
+
+The commonality between TBT (collected in a lab environment) and FID (collected
+in a field context) is that they measure the impact on input responsiveness from
+long tasks on the main thread. Beyond that, they're quite different. FID
+captures the delay in handling the first input event of the page, whenever that
+input happened.  TBT roughly captures how dangerous the length of all the main
+thread's tasks are.
+
+It's very possible to have a page that does well on FID, but poorly on TBT.  And
+it's slightly harder, but possible, to do well on TBT but poorly on FID\*.   So,
+you shouldn't expect your TBT and FID measurements to correlate strongly. A
+large-scale analysis found their [Spearman's
+ρ](https://en.wikipedia.org/wiki/Spearman%27s_rank_correlation_coefficient) at
+about 0.40, which indicates a connection, but not one as strong as many would
+prefer.
+
+From the Lighthouse project's perspective, the current passing threshold for FID
+is quite lenient but more importantly, the percentile-of-record for FID (75th
+percentile) is not sufficient for detecting issues. The 95th percentile is a
+much stronger indicator of problematic interactions for this metric. We
+encourage user-centric teams to focus on the 95th percentile of all input delays
+(not just the first) in their field data in order to identify and address
+problems that surface just 5% of the time.
+
+\*Aside: the [Chrome 91 FID change for
+double-tap-to-zoom](https://chromium.googlesource.com/chromium/src.git/+/refs/heads/main/docs/speed/metrics_changelog/2021_05_fid.md)
+fixes a lot of high FID / low TBT cases and may be observable in your field
+metrics, with higher percentiles improving slightly. Most remaining high FID /
+low TBT cases are likely due to incorrect meta viewport tags, which [Lighthouse
+will
+flag](https://www.google.com/url?q=https://web.dev/viewport/&sa=D&source=editors&ust=1622651275263000&usg=AOvVaw1OS_kJ9oNMlPSjIJbFy7c8).
+Delivering a mobile-friendly viewport, reducing main-thread blocking JS, and
+keeping your TBT low is the best defense against bad FID in the field.
+
+### Overall, what motivated the changes to the performance score?
+
+As with all Lighthouse score updates, changes are made to reflect
+the latest in how to measure user-experience quality holistically and accurately,
+and to focus attention on key priorities.
+
+Heavy JS and long tasks are a problem for the web that's
+[worsening](https://httparchive.org/reports/state-of-javascript#bytesJs). Field
+FID is currently too lenient and not sufficiently incentivizing action to
+address the problem. Lighthouse has historically weighed its interactivity
+metrics at 40-55% of the performance score and—as interactivity is key to user
+experience—we maintain a 40% weighting (TBT and TTI together) in Lighthouse
+8.0.
+
+[FCP's score curve was
+adjusted](https://github.com/GoogleChrome/lighthouse/pull/12556) to align with
+the current de facto ["good" threshold](https://web.dev/fcp/#what-is-a-good-fcp-score),
+and as a result will score a bit more strictly.
+
+The curve for TBT was made stricter to [more closely
+approach](https://github.com/GoogleChrome/lighthouse/pull/12576) the ideal score
+curve. TBT has had (and still has) a more lenient curve than our methodology
+dictates, but the new curve is more linear which means there's a larger range
+where improvements in the metric are rewarded with improvements in the score. If
+your page currently scores poorly with TBT, the new curve will be more
+responsive to changes as page performance incrementally improves.
+
+FCP's weight drops slightly from 15% to 10% because it's fairly gameable and is also partly
+captured by Speed Index.
+
+### What's the story with TTI?
+
+TTI serves a useful role as it's the largest metric value reported (often &gt;10
+seconds) and helps anchor perceptions.
+
+We see TBT as a stronger metric for evaluating the health of your main thread
+and its impact on interactivity, plus it [has lower
+variability](https://docs.google.com/document/d/1xCERB_X7PiP5RAZDwyIkODnIXoBk-Oo7Mi9266aEdGg/edit).
+ TTI serves as a nice complement that captures the cost of long tasks, often
+from heavy JavaScript. That said, we expect to continue to reduce the weight
+of TTI and will likely remove it in a future major Lighthouse release.
+
+### How does the Lighthouse Perf score get calculated? What is it based on?
+
+The Lighthouse perf score is calculated from a weighted, blended set of
+performance metrics. You can see the current and previous Lighthouse score
+compositions (which metrics we are blending together, and at what weights) in
+the [score
+calculator](https://googlechrome.github.io/lighthouse/scorecalc/#FCP=3000&SI=5800&FMP=4000&TTI=7300&FCI=6500&LCP=4000&TBT=600&CLS=0.25&device=mobile&version=8&version=6&version=5),
+and learn more about the [calculation specifics
+here](https://web.dev/performance-scoring/).
+
+### What is the most exciting update in LH v8?
+
+We're really excited about the [interactive
+treemap](https://github.com/GoogleChrome/lighthouse/blob/v8changelog/changelog.md#treemap-release),
+[filtering audits by
+metric](https://github.com/GoogleChrome/lighthouse/blob/v8changelog/changelog.md#:~:text=new%20metric%20filter),
+and the new [Content Security Policy
+audit](https://web.dev/strict-csp/#adopting-a-strict-csp), which was a
+collaboration with the Google Web Security team.
diff --git a/lighthouse-core/fraggle-rock/config/filters.js b/lighthouse-core/fraggle-rock/config/filters.js
index ec04449ac..701884022 100644
--- a/lighthouse-core/fraggle-rock/config/filters.js
+++ b/lighthouse-core/fraggle-rock/config/filters.js
@@ -7,6 +7,21 @@
 
 const Audit = require('../../audits/audit.js');
 
+/** @type {Record<keyof LH.FRBaseArtifacts, string>} */
+const baseArtifactKeySource = {
+  fetchTime: '',
+  LighthouseRunWarnings: '',
+  HostFormFactor: '',
+  HostUserAgent: '',
+  BenchmarkIndex: '',
+  settings: '',
+  Timing: '',
+  URL: '',
+  PageLoadError: '',
+};
+
+const baseArtifactKeys = Object.keys(baseArtifactKeySource);
+
 /**
  * Filters an array of artifacts down to the set that supports the specified gather mode.
  *
@@ -31,8 +46,9 @@ function filterArtifactsByGatherMode(artifacts, mode) {
 function filterAuditsByAvailableArtifacts(audits, availableArtifacts) {
   if (!audits) return null;
 
-  const availableAritfactIds = new Set(availableArtifacts.map(artifact => artifact.id));
-
+  const availableAritfactIds = new Set(
+    availableArtifacts.map(artifact => artifact.id).concat(baseArtifactKeys)
+  );
   return audits.filter(audit =>
     audit.implementation.meta.requiredArtifacts.every(id => availableAritfactIds.has(id))
   );
diff --git a/lighthouse-core/fraggle-rock/gather/base-artifacts.js b/lighthouse-core/fraggle-rock/gather/base-artifacts.js
index 8b758f716..76301994c 100644
--- a/lighthouse-core/fraggle-rock/gather/base-artifacts.js
+++ b/lighthouse-core/fraggle-rock/gather/base-artifacts.js
@@ -5,7 +5,13 @@
  */
 'use strict';
 
-const {getBrowserVersion, getBenchmarkIndex} = require('../../gather/driver/environment.js');
+const log = require('lighthouse-logger');
+const isEqual = require('lodash.isequal');
+const {
+  getBrowserVersion,
+  getBenchmarkIndex,
+  getEnvironmentWarnings,
+} = require('../../gather/driver/environment.js');
 
 /**
  * @param {LH.Config.FRConfig} config
@@ -21,17 +27,11 @@ async function getBaseArtifacts(config, driver) {
 
   const BenchmarkIndex = await getBenchmarkIndex(driver.executionContext);
 
-  /** @type {Array<string | LH.IcuMessage>} */
-  const LighthouseRunWarnings = [];
-
-  // TODO(FR-COMPAT): support slow host CPU warning
-  // TODO(FR-COMPAT): support redirected URL warning
-
   return {
     // Meta artifacts.
     fetchTime: new Date().toJSON(),
     Timing: [],
-    LighthouseRunWarnings,
+    LighthouseRunWarnings: [],
     settings: config.settings,
     // Environment artifacts that can always be computed.
     HostFormFactor,
@@ -39,17 +39,59 @@ async function getBaseArtifacts(config, driver) {
     BenchmarkIndex,
     // Contextual artifacts whose collection changes based on gather mode.
     URL: {requestedUrl: '', finalUrl: ''},
-    PageLoadError: null, // TODO(FR-COMPAT): support PageLoadError
+    PageLoadError: null,
     // Artifacts that have been replaced by regular gatherers in Fraggle Rock.
     Stacks: [],
     NetworkUserAgent: '',
-    WebAppManifest: null, // replaced by standard gatherer
-    InstallabilityErrors: {errors: []}, // replaced by standard gatherer
+    WebAppManifest: null,
+    InstallabilityErrors: {errors: []},
     traces: {},
     devtoolsLogs: {},
   };
 }
 
+/**
+ * Deduplicates identical warnings.
+ * @param {Array<string | LH.IcuMessage>} warnings
+ * @return {Array<string | LH.IcuMessage>}
+ */
+function deduplicateWarnings(warnings) {
+  /** @type {Array<string | LH.IcuMessage>} */
+  const unique = [];
+
+  for (const warning of warnings) {
+    if (unique.some(existing => isEqual(warning, existing))) continue;
+    unique.push(warning);
+  }
+
+  return unique;
+}
+
+/**
+ * @param {LH.FRBaseArtifacts} baseArtifacts
+ * @param {Partial<LH.Artifacts>} gathererArtifacts
+ * @return {LH.Artifacts}
+ */
+function finalizeArtifacts(baseArtifacts, gathererArtifacts) {
+  const warnings = baseArtifacts.LighthouseRunWarnings
+    .concat(gathererArtifacts.LighthouseRunWarnings || [])
+    .concat(getEnvironmentWarnings({settings: baseArtifacts.settings, baseArtifacts}));
+
+  // Cast to remove the partial from gathererArtifacts.
+  const artifacts = /** @type {LH.Artifacts} */ ({...baseArtifacts, ...gathererArtifacts});
+
+  // Set the post-run meta artifacts.
+  artifacts.Timing = log.getTimeEntries();
+  artifacts.LighthouseRunWarnings = deduplicateWarnings(warnings);
+
+  // Check that the runner remembered to mutate the special-case URL artifact.
+  if (!artifacts.URL.requestedUrl) throw new Error('Runner did not set requestedUrl');
+  if (!artifacts.URL.finalUrl) throw new Error('Runner did not set finalUrl');
+
+  return artifacts;
+}
+
 module.exports = {
   getBaseArtifacts,
+  finalizeArtifacts,
 };
diff --git a/lighthouse-core/fraggle-rock/gather/navigation-runner.js b/lighthouse-core/fraggle-rock/gather/navigation-runner.js
index 32a0dcd50..9aa04b312 100644
--- a/lighthouse-core/fraggle-rock/gather/navigation-runner.js
+++ b/lighthouse-core/fraggle-rock/gather/navigation-runner.js
@@ -19,7 +19,7 @@ const storage = require('../../gather/driver/storage.js');
 const emulation = require('../../lib/emulation.js');
 const {defaultNavigationConfig} = require('../../config/constants.js');
 const {initializeConfig} = require('../config/config.js');
-const {getBaseArtifacts} = require('./base-artifacts.js');
+const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
 const i18n = require('../../lib/i18n/i18n.js');
 const LighthouseError = require('../../lib/lh-error.js');
 const {getPageLoadError} = require('../../lib/navigation-error.js');
@@ -126,7 +126,7 @@ async function _collectNetworkRecords(navigationContext, phaseState) {
  * @param {PhaseState} phaseState
  * @param {UnPromise<ReturnType<typeof _setupNavigation>>} setupResult
  * @param {UnPromise<ReturnType<typeof _navigate>>} navigateResult
- * @return {Promise<{artifacts: Partial<LH.GathererArtifacts>, warnings: Array<LH.IcuMessage>, pageLoadError: LH.LighthouseError | undefined}>}
+ * @return {Promise<{finalUrl: string, artifacts: Partial<LH.GathererArtifacts>, warnings: Array<LH.IcuMessage>, pageLoadError: LH.LighthouseError | undefined}>}
  */
 async function _computeNavigationResult(
   navigationContext,
@@ -150,12 +150,17 @@ async function _computeNavigationResult(
     const localizedMessage = i18n.getFormatted(pageLoadError.friendlyMessage, locale);
     log.error('NavigationRunner', localizedMessage, navigationContext.requestedUrl);
 
-    return {artifacts: {}, warnings: [...warnings, pageLoadError.friendlyMessage], pageLoadError};
+    return {
+      finalUrl,
+      pageLoadError,
+      artifacts: {},
+      warnings: [...warnings, pageLoadError.friendlyMessage],
+    };
   } else {
     await collectPhaseArtifacts({phase: 'getArtifact', ...phaseState});
 
     const artifacts = await awaitArtifacts(phaseState.artifactState);
-    return {artifacts, warnings, pageLoadError: undefined};
+    return {finalUrl, artifacts, warnings, pageLoadError: undefined};
   }
 }
 
@@ -194,6 +199,8 @@ async function _navigations({driver, config, requestedUrl, computedCache}) {
 
   /** @type {Partial<LH.FRArtifacts & LH.FRBaseArtifacts>} */
   const artifacts = {};
+  /** @type {Array<LH.IcuMessage>} */
+  const LighthouseRunWarnings = [];
 
   for (const navigation of config.navigations) {
     const navigationContext = {
@@ -205,16 +212,20 @@ async function _navigations({driver, config, requestedUrl, computedCache}) {
     };
 
     const navigationResult = await _navigation(navigationContext);
-    if (navigationResult.pageLoadError && navigation.loadFailureMode === 'fatal') {
-      artifacts.PageLoadError = navigationResult.pageLoadError;
-      break;
+    if (navigation.loadFailureMode === 'fatal') {
+      if (navigationResult.pageLoadError) {
+        artifacts.PageLoadError = navigationResult.pageLoadError;
+        break;
+      }
+
+      artifacts.URL = {requestedUrl, finalUrl: navigationResult.finalUrl};
     }
 
-    // TODO(FR-COMPAT): merge RunWarnings between navigations
+    LighthouseRunWarnings.push(...navigationResult.warnings);
     Object.assign(artifacts, navigationResult.artifacts);
   }
 
-  return {artifacts};
+  return {artifacts: {...artifacts, LighthouseRunWarnings}};
 }
 
 /**
@@ -243,7 +254,7 @@ async function navigation(options) {
       const {artifacts} = await _navigations({driver, config, requestedUrl, computedCache});
       await _cleanup({driver, config, requestedUrl});
 
-      return /** @type {LH.Artifacts} */ ({...baseArtifacts, ...artifacts}); // Cast to drop Partial<>
+      return finalizeArtifacts(baseArtifacts, artifacts);
     },
     {
       url: requestedUrl,
diff --git a/lighthouse-core/fraggle-rock/gather/snapshot-runner.js b/lighthouse-core/fraggle-rock/gather/snapshot-runner.js
index cbe01bf91..be0b7f139 100644
--- a/lighthouse-core/fraggle-rock/gather/snapshot-runner.js
+++ b/lighthouse-core/fraggle-rock/gather/snapshot-runner.js
@@ -13,7 +13,7 @@ const {
   awaitArtifacts,
 } = require('./runner-helpers.js');
 const {initializeConfig} = require('../config/config.js');
-const {getBaseArtifacts} = require('./base-artifacts.js');
+const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
 
 /** @param {{page: import('puppeteer').Page, config?: LH.Config.Json}} options */
 async function snapshot(options) {
@@ -44,7 +44,7 @@ async function snapshot(options) {
       });
 
       const artifacts = await awaitArtifacts(artifactState);
-      return /** @type {LH.Artifacts} */ ({...baseArtifacts, ...artifacts}); // Cast to drop Partial<>
+      return finalizeArtifacts(baseArtifacts, artifacts);
     },
     {
       url,
diff --git a/lighthouse-core/fraggle-rock/gather/timespan-runner.js b/lighthouse-core/fraggle-rock/gather/timespan-runner.js
index 555b2bdca..9fbb68276 100644
--- a/lighthouse-core/fraggle-rock/gather/timespan-runner.js
+++ b/lighthouse-core/fraggle-rock/gather/timespan-runner.js
@@ -13,7 +13,7 @@ const {
   awaitArtifacts,
 } = require('./runner-helpers.js');
 const {initializeConfig} = require('../config/config.js');
-const {getBaseArtifacts} = require('./base-artifacts.js');
+const {getBaseArtifacts, finalizeArtifacts} = require('./base-artifacts.js');
 
 /**
  * @param {{page: import('puppeteer').Page, config?: LH.Config.Json}} options
@@ -56,7 +56,7 @@ async function startTimespan(options) {
           await collectPhaseArtifacts({phase: 'getArtifact', ...phaseOptions});
 
           const artifacts = await awaitArtifacts(artifactState);
-          return /** @type {LH.Artifacts} */ ({...baseArtifacts, ...artifacts}); // Cast to drop Partial<>
+          return finalizeArtifacts(baseArtifacts, artifacts);
         },
         {
           url: finalUrl,
diff --git a/lighthouse-core/gather/gather-runner.js b/lighthouse-core/gather/gather-runner.js
index a9147d4db..a46abe781 100644
--- a/lighthouse-core/gather/gather-runner.js
+++ b/lighthouse-core/gather/gather-runner.js
@@ -20,6 +20,7 @@ const WebAppManifest = require('./gatherers/web-app-manifest.js');
 const InstallabilityErrors = require('./gatherers/installability-errors.js');
 const NetworkUserAgent = require('./gatherers/network-user-agent.js');
 const Stacks = require('./gatherers/stacks.js');
+const {finalizeArtifacts} = require('../fraggle-rock/gather/base-artifacts.js');
 
 /** @typedef {import('../gather/driver.js')} Driver */
 /** @typedef {import('../lib/arbitrary-equality-map.js')} ArbitraryEqualityMap */
@@ -446,18 +447,6 @@ class GatherRunner {
     log.timeEnd(status);
   }
 
-  /**
-   * Finalize baseArtifacts after gathering is fully complete.
-   * @param {LH.BaseArtifacts} baseArtifacts
-   */
-  static finalizeBaseArtifacts(baseArtifacts) {
-    // Take only unique LighthouseRunWarnings.
-    baseArtifacts.LighthouseRunWarnings = Array.from(new Set(baseArtifacts.LighthouseRunWarnings));
-
-    // Take the timing entries we've gathered so far.
-    baseArtifacts.Timing = log.getTimeEntries();
-  }
-
   /**
    * @param {Array<LH.Config.Pass>} passConfigs
    * @param {{driver: Driver, requestedUrl: string, settings: LH.Config.Settings, computedCache: Map<string, ArbitraryEqualityMap>}} options
@@ -515,8 +504,7 @@ class GatherRunner {
       }
 
       await GatherRunner.disposeDriver(driver, options);
-      GatherRunner.finalizeBaseArtifacts(baseArtifacts);
-      return /** @type {LH.Artifacts} */ ({...baseArtifacts, ...artifacts}); // Cast to drop Partial<>.
+      return finalizeArtifacts(baseArtifacts, artifacts);
     } catch (err) {
       // Clean up on error. Don't await so that the root error, not a disposal error, is shown.
       GatherRunner.disposeDriver(driver, options);
diff --git a/lighthouse-treemap/README.md b/lighthouse-treemap/README.md
index b0f238261..79e5d5673 100644
--- a/lighthouse-treemap/README.md
+++ b/lighthouse-treemap/README.md
@@ -1,5 +1,7 @@
 # Lighthouse Treemap Viewer
 
+[Demo](https://googlechrome.github.io/lighthouse/treemap/?gist=30a18304de56e7be08f10976a1b11702)
+
 ## Development
 
 ```sh
diff --git a/lighthouse-treemap/app/src/main.js b/lighthouse-treemap/app/src/main.js
index ede9f55bd..2548f3f74 100644
--- a/lighthouse-treemap/app/src/main.js
+++ b/lighthouse-treemap/app/src/main.js
@@ -64,10 +64,22 @@ class TreemapViewer {
     /** @type {WeakMap<LH.Treemap.Node, LH.Treemap.NodePath>} */
     this.nodeToPathMap = new WeakMap();
 
-    this.documentUrl = options.lhr.requestedUrl;
+    this.documentUrl = new URL(options.lhr.requestedUrl);
     this.el = el;
     this.getHueForD1NodeName = TreemapUtil.stableHasher(TreemapUtil.COLOR_HUES);
 
+    // These depth one node uses the network URL for the name, but we want
+    // to elide common parts of the URL so text fits better in the UI.
+    for (const node of this.depthOneNodesByGroup.scripts) {
+      try {
+        const url = new URL(node.name);
+        node.name = TreemapUtil.elideSameOrigin(url, this.documentUrl);
+        if (url.href === this.documentUrl.href) {
+          node.name += ' (inline)';
+        }
+      } catch {}
+    }
+
     /* eslint-disable no-unused-expressions */
     /** @type {LH.Treemap.Node} */
     this.currentTreemapRoot;
@@ -94,8 +106,8 @@ class TreemapViewer {
 
   createHeader() {
     const urlEl = TreemapUtil.find('a.lh-header--url');
-    urlEl.textContent = this.documentUrl;
-    urlEl.href = this.documentUrl;
+    urlEl.textContent = this.documentUrl.toString();
+    urlEl.href = this.documentUrl.toString();
 
     this.createBundleSelector();
   }
@@ -215,7 +227,7 @@ class TreemapViewer {
   wrapNodesInNewRootNode(nodes) {
     const children = [...nodes];
     return {
-      name: this.documentUrl,
+      name: this.documentUrl.toString(),
       resourceBytes: children.reduce((acc, cur) => cur.resourceBytes + acc, 0),
       unusedBytes: children.reduce((acc, cur) => (cur.unusedBytes || 0) + acc, 0),
       children,
@@ -432,11 +444,6 @@ class TreemapViewer {
         } else {
           name = path.join('/');
         }
-
-        // Elide the document URL.
-        if (name.startsWith(this.currentTreemapRoot.name)) {
-          name = name.replace(this.currentTreemapRoot.name, '//');
-        }
       }
 
       data.push({
@@ -468,7 +475,7 @@ class TreemapViewer {
       const dataRow = cell.getRow().getData();
       if (!dataRow.bundleNode) return '';
 
-      return `${dataRow.bundleNode.name} (bundle) ${dataRow.name}`;
+      return `${dataRow.bundleNode.name} ${dataRow.name}`;
     };
 
     /** @param {Tabulator.CellComponent} cell */
@@ -620,8 +627,8 @@ class TreemapViewer {
 
       // Shade the element to communicate coverage.
       if (this.currentViewMode.id === 'unused-bytes') {
-        const pctUsed = (1 - (node.unusedBytes || 0) / node.resourceBytes) * 100;
-        node.dom.style.setProperty('--pctUsed', `${pctUsed}%`);
+        const pctUnused = (node.unusedBytes || 0) / node.resourceBytes * 100;
+        node.dom.style.setProperty('--pctUnused', `${pctUnused}%`);
       }
     });
   }
diff --git a/lighthouse-treemap/app/src/util.js b/lighthouse-treemap/app/src/util.js
index c1d5c8ec5..5d03ed96d 100644
--- a/lighthouse-treemap/app/src/util.js
+++ b/lighthouse-treemap/app/src/util.js
@@ -62,6 +62,15 @@ class TreemapUtil {
     return string.slice(0, length - 1) + '…';
   }
 
+  /**
+   * @param {URL} url
+   * @param {URL} fromRelativeUrl
+   */
+  static elideSameOrigin(url, fromRelativeUrl) {
+    if (url.origin !== fromRelativeUrl.origin) return url.toString();
+    return url.toString().replace(fromRelativeUrl.origin, '');
+  }
+
   /**
    * @template {string} T
    * @param {T} name
diff --git a/lighthouse-treemap/app/styles/treemap.css b/lighthouse-treemap/app/styles/treemap.css
index 3b7affc9a..d8fb3bcb9 100644
--- a/lighthouse-treemap/app/styles/treemap.css
+++ b/lighthouse-treemap/app/styles/treemap.css
@@ -219,7 +219,7 @@ header {
   right: 0;
   content: '';
   display: block;
-  width: var(--pctUsed);
+  width: var(--pctUnused);
   background: repeating-linear-gradient(
           -45deg,
           hsla(0, 0%, 0%, 0),
diff --git a/package.json b/package.json
index 29899eb57..56726e835 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "lighthouse",
-  "version": "7.5.0",
+  "version": "8.0.0",
   "description": "Automated auditing, performance metrics, and best practices for the web.",
   "main": "./lighthouse-core/index.js",
   "bin": {


Test patch:
diff --git a/lighthouse-core/test/fraggle-rock/api-test-pptr.js b/lighthouse-core/test/fraggle-rock/api-test-pptr.js
index 960a5ba62..ddcad87fd 100644
--- a/lighthouse-core/test/fraggle-rock/api-test-pptr.js
+++ b/lighthouse-core/test/fraggle-rock/api-test-pptr.js
@@ -95,7 +95,7 @@ describe('Fraggle Rock API', () => {
 
       const {auditResults, erroredAudits, failedAudits} = getAuditsBreakdown(lhr);
       // TODO(FR-COMPAT): This assertion can be removed when full compatibility is reached.
-      expect(auditResults.length).toMatchInlineSnapshot(`74`);
+      expect(auditResults.length).toMatchInlineSnapshot(`78`);
 
       expect(erroredAudits).toHaveLength(0);
       expect(failedAudits.map(audit => audit.id)).toContain('label');
@@ -121,7 +121,7 @@ describe('Fraggle Rock API', () => {
 
       const {auditResults, erroredAudits, failedAudits} = getAuditsBreakdown(lhr);
       // TODO(FR-COMPAT): This assertion can be removed when full compatibility is reached.
-      expect(auditResults.length).toMatchInlineSnapshot(`28`);
+      expect(auditResults.length).toMatchInlineSnapshot(`35`);
 
       expect(erroredAudits).toHaveLength(0);
       expect(failedAudits.map(audit => audit.id)).toContain('errors-in-console');
@@ -159,8 +159,8 @@ describe('Fraggle Rock API', () => {
       const {lhr} = result;
       const {auditResults, failedAudits, erroredAudits} = getAuditsBreakdown(lhr);
       // TODO(FR-COMPAT): This assertion can be removed when full compatibility is reached.
-      expect(auditResults.length).toMatchInlineSnapshot(`114`);
-      expect(erroredAudits).toHaveLength(0);
+      expect(auditResults.length).toMatchInlineSnapshot(`145`);
+      expect(erroredAudits).toHaveLength(1); // FIXME: MainDocumentContent broken from merge
 
       const failedAuditIds = failedAudits.map(audit => audit.id);
       expect(failedAuditIds).toContain('label');
diff --git a/lighthouse-core/test/fraggle-rock/config/filters-test.js b/lighthouse-core/test/fraggle-rock/config/filters-test.js
index 131f6f137..a1c2a9738 100644
--- a/lighthouse-core/test/fraggle-rock/config/filters-test.js
+++ b/lighthouse-core/test/fraggle-rock/config/filters-test.js
@@ -88,6 +88,25 @@ describe('Fraggle Rock Config Filtering', () => {
       ]);
     });
 
+    it('should not filter audits with dependencies on base artifacts', () => {
+      class SnapshotWithBase extends BaseAudit {
+        static meta = {
+          id: 'snapshot',
+          requiredArtifacts: /** @type {any} */ (['Snapshot', 'URL', 'HostUserAgent']),
+          ...auditMeta,
+        };
+      }
+
+      const auditsWithBaseArtifacts = [SnapshotWithBase, TimespanAudit].map(audit => ({
+        implementation: audit,
+        options: {},
+      }));
+      const partialArtifacts = [{id: 'Snapshot', gatherer: {instance: snapshotGatherer}}];
+      expect(
+        filters.filterAuditsByAvailableArtifacts(auditsWithBaseArtifacts, partialArtifacts)
+      ).toEqual([{implementation: SnapshotWithBase, options: {}}]);
+    });
+
     it('should be noop when all artifacts available', () => {
       expect(filters.filterAuditsByAvailableArtifacts(audits, artifacts)).toEqual(audits);
     });
@@ -133,16 +152,16 @@ describe('Fraggle Rock Config Filtering', () => {
         {implementation: ManualAudit, options: {}},
       ];
 
-      const filteredCategories = filters.filterCategoriesByAvailableAudits({
-        snapshot: categories.snapshot,
-        timespanWithManual: {
-          title: 'Timespan + Manual',
-          auditRefs: [
-            {id: 'timespan', weight: 0},
-            {id: 'manual', weight: 0},
-          ],
+      const filteredCategories = filters.filterCategoriesByAvailableAudits(
+        {
+          snapshot: categories.snapshot,
+          timespanWithManual: {
+            title: 'Timespan + Manual',
+            auditRefs: [{id: 'timespan', weight: 0}, {id: 'manual', weight: 0}],
+          },
         },
-      }, partialAudits);
+        partialAudits
+      );
       expect(filteredCategories).not.toHaveProperty('timespanWithManual');
     });
 
diff --git a/lighthouse-core/test/fraggle-rock/gather/base-artifacts-test.js b/lighthouse-core/test/fraggle-rock/gather/base-artifacts-test.js
new file mode 100644
index 000000000..2d9c7112a
--- /dev/null
+++ b/lighthouse-core/test/fraggle-rock/gather/base-artifacts-test.js
@@ -0,0 +1,152 @@
+/**
+ * @license Copyright 2021 The Lighthouse Authors. 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';
+
+/* eslint-env jest */
+
+const {
+  getBaseArtifacts,
+  finalizeArtifacts,
+} = require('../../../fraggle-rock/gather/base-artifacts.js');
+const {initializeConfig} = require('../../../fraggle-rock/config/config.js');
+const {createMockDriver} = require('./mock-driver.js');
+const LighthouseError = require('../../../lib/lh-error.js');
+
+/** @param {{userAgent: string}} params */
+function getMockDriverForArtifacts({userAgent}) {
+  const driverMock = createMockDriver();
+  driverMock._session.sendCommand.mockResponse('Browser.getVersion', {
+    userAgent,
+    product: '',
+  });
+
+  driverMock._executionContext.evaluate.mockResolvedValue(500);
+
+  return driverMock;
+}
+
+describe('getBaseArtifacts', () => {
+  let driverMock = getMockDriverForArtifacts({userAgent: 'Desktop Chrome'});
+
+  beforeEach(() => {
+    driverMock = getMockDriverForArtifacts({userAgent: 'Desktop Chrome'});
+  });
+
+  it('should fetch the desktop user agent', async () => {
+    const {config} = initializeConfig(undefined, {gatherMode: 'navigation'});
+    const artifacts = await getBaseArtifacts(config, driverMock.asDriver());
+    expect(artifacts.HostUserAgent).toEqual('Desktop Chrome');
+    expect(artifacts.HostFormFactor).toEqual('desktop');
+  });
+
+  it('should fetch the mobile user agent', async () => {
+    driverMock = getMockDriverForArtifacts({userAgent: 'Mobile Chrome'});
+
+    const {config} = initializeConfig(undefined, {gatherMode: 'navigation'});
+    const artifacts = await getBaseArtifacts(config, driverMock.asDriver());
+    expect(artifacts.HostUserAgent).toEqual('Mobile Chrome');
+    expect(artifacts.HostFormFactor).toEqual('mobile');
+  });
+
+  it('should fetch benchmark index', async () => {
+    const {config} = initializeConfig(undefined, {gatherMode: 'navigation'});
+    const artifacts = await getBaseArtifacts(config, driverMock.asDriver());
+    expect(artifacts.BenchmarkIndex).toEqual(500);
+  });
+
+  it('should return settings', async () => {
+    const {config} = initializeConfig(undefined, {gatherMode: 'navigation'});
+    const artifacts = await getBaseArtifacts(config, driverMock.asDriver());
+    expect(artifacts.settings).toEqual(config.settings);
+  });
+});
+
+describe('finalizeArtifacts', () => {
+  /** @type {LH.FRBaseArtifacts} */
+  let baseArtifacts;
+  /** @type {Partial<LH.Artifacts>} */
+  let gathererArtifacts = {};
+
+  beforeEach(async () => {
+    const {config} = initializeConfig(undefined, {gatherMode: 'navigation'});
+    const driver = getMockDriverForArtifacts({userAgent: 'Desktop Chrome'}).asDriver();
+    baseArtifacts = await getBaseArtifacts(config, driver);
+    baseArtifacts.URL = {requestedUrl: 'http://example.com', finalUrl: 'https://example.com'};
+    gathererArtifacts = {};
+  });
+
+  it('should merge the two objects', () => {
+    baseArtifacts.LighthouseRunWarnings = [{i18nId: '1', formattedDefault: 'Yes'}];
+    gathererArtifacts.LighthouseRunWarnings = [{i18nId: '2', formattedDefault: 'No'}];
+
+    const winningError = new LighthouseError(LighthouseError.errors.NO_LCP);
+    baseArtifacts.PageLoadError = new LighthouseError(LighthouseError.errors.NO_FCP);
+    gathererArtifacts.PageLoadError = winningError;
+
+    gathererArtifacts.MainDocumentContent = '<html>';
+    gathererArtifacts.RobotsTxt = {status: 404, content: null};
+
+    const artifacts = finalizeArtifacts(baseArtifacts, gathererArtifacts);
+    expect(artifacts).toMatchObject({
+      PageLoadError: winningError,
+      HostUserAgent: 'Desktop Chrome',
+      BenchmarkIndex: 500,
+      MainDocumentContent: '<html>',
+      RobotsTxt: {status: 404, content: null},
+      LighthouseRunWarnings: [
+        {i18nId: '1', formattedDefault: 'Yes'},
+        {i18nId: '2', formattedDefault: 'No'},
+      ],
+    });
+  });
+
+  it('should add timing entries', () => {
+    const artifacts = finalizeArtifacts(baseArtifacts, gathererArtifacts);
+    expect(artifacts.Timing.length).toBeGreaterThan(0);
+  });
+
+  it('should add environment warnings', () => {
+    baseArtifacts.settings.channel = 'cli';
+    baseArtifacts.settings.throttlingMethod = 'simulate';
+    baseArtifacts.BenchmarkIndex = 200;
+    const artifacts = finalizeArtifacts(baseArtifacts, gathererArtifacts);
+    expect(artifacts.LighthouseRunWarnings).toHaveLength(1);
+    expect(artifacts.LighthouseRunWarnings[0]).toBeDisplayString(/slower CPU/);
+  });
+
+  it('should dedupe warnings', () => {
+    baseArtifacts.LighthouseRunWarnings = [
+      {i18nId: '1', formattedDefault: 'Yes', values: {test: 1}},
+      {i18nId: '1', formattedDefault: 'Yes', values: {test: 2}},
+    ];
+    gathererArtifacts.LighthouseRunWarnings = [
+      {i18nId: '1', formattedDefault: 'Yes', values: {test: 1}},
+      {i18nId: '1', formattedDefault: 'Yes', values: {test: 3}},
+      {i18nId: '2', formattedDefault: 'No'},
+    ];
+
+    const artifacts = finalizeArtifacts(baseArtifacts, gathererArtifacts);
+    expect(artifacts.LighthouseRunWarnings).toEqual([
+      {i18nId: '1', formattedDefault: 'Yes', values: {test: 1}},
+      {i18nId: '1', formattedDefault: 'Yes', values: {test: 2}},
+      {i18nId: '1', formattedDefault: 'Yes', values: {test: 3}},
+      {i18nId: '2', formattedDefault: 'No'},
+    ]);
+  });
+
+  it('should throw if URL was not set', () => {
+    const run = () => finalizeArtifacts(baseArtifacts, gathererArtifacts);
+
+    baseArtifacts.URL = {requestedUrl: '', finalUrl: ''};
+    expect(run).toThrowError(/requestedUrl/);
+
+    baseArtifacts.URL = {requestedUrl: '', finalUrl: 'https://example.com'};
+    expect(run).toThrowError(/requestedUrl/);
+
+    baseArtifacts.URL = {requestedUrl: 'https://example.com', finalUrl: ''};
+    expect(run).toThrowError(/finalUrl/);
+  });
+});
diff --git a/lighthouse-core/test/results/sample_v2.json b/lighthouse-core/test/results/sample_v2.json
index 8cf1f2560..74a44e885 100644
--- a/lighthouse-core/test/results/sample_v2.json
+++ b/lighthouse-core/test/results/sample_v2.json
@@ -8,7 +8,7 @@
       "axe-core": "4.1.3"
     }
   },
-  "lighthouseVersion": "7.5.0",
+  "lighthouseVersion": "8.0.0",
   "fetchTime": "2018-03-13T00:55:45.840Z",
   "requestedUrl": "http://localhost:10200/dobetterweb/dbw_tester.html",
   "finalUrl": "http://localhost:10200/dobetterweb/dbw_tester.html",
diff --git a/third-party/chromium-webtests/webtests/http/tests/devtools/lighthouse/lighthouse-emulate-run-expected.txt b/third-party/chromium-webtests/webtests/http/tests/devtools/lighthouse/lighthouse-emulate-run-expected.txt
index d6df06a4d..d90f90deb 100644
--- a/third-party/chromium-webtests/webtests/http/tests/devtools/lighthouse/lighthouse-emulate-run-expected.txt
+++ b/third-party/chromium-webtests/webtests/http/tests/devtools/lighthouse/lighthouse-emulate-run-expected.txt
@@ -14,7 +14,7 @@ Generate report: enabled visible
 
 =============== Lighthouse Results ===============
 URL: http://127.0.0.1:8000/devtools/lighthouse/resources/lighthouse-emulate-pass.html
-Version: 7.5.0
+Version: 8.0.0
 formFactor: mobile
 screenEmulation: {
   "mobile": true,
diff --git a/third-party/chromium-webtests/webtests/http/tests/devtools/lighthouse/lighthouse-successful-run-expected.txt b/third-party/chromium-webtests/webtests/http/tests/devtools/lighthouse/lighthouse-successful-run-expected.txt
index 7370260d1..722faaf3e 100644
--- a/third-party/chromium-webtests/webtests/http/tests/devtools/lighthouse/lighthouse-successful-run-expected.txt
+++ b/third-party/chromium-webtests/webtests/http/tests/devtools/lighthouse/lighthouse-successful-run-expected.txt
@@ -382,7 +382,7 @@ Generating results...
 
 =============== Lighthouse Results ===============
 URL: http://127.0.0.1:8000/devtools/lighthouse/resources/lighthouse-basic.html
-Version: 7.5.0
+Version: 8.0.0
 ViewportDimensions: {
   "innerWidth": 980,
   "innerHeight": 1743,