lighthouse 8.0.0-dev.20210709 → 8.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (299) hide show
  1. package/docs/architecture.md +101 -0
  2. package/docs/authenticated-pages.md +44 -0
  3. package/docs/bug-labels.md +35 -0
  4. package/docs/configuration.md +213 -0
  5. package/docs/emulation.md +32 -0
  6. package/docs/error-reporting.md +42 -0
  7. package/docs/hacking-tips.md +55 -0
  8. package/docs/headless-chrome.md +119 -0
  9. package/docs/lantern.md +55 -0
  10. package/docs/new-audits.md +99 -0
  11. package/docs/performance-budgets.md +187 -0
  12. package/docs/plugins.md +374 -0
  13. package/docs/puppeteer.md +95 -0
  14. package/docs/readme.md +164 -0
  15. package/docs/recipes/auth/README.md +129 -0
  16. package/docs/recipes/auth/example-lh-auth.js +81 -0
  17. package/docs/recipes/auth/package.json +11 -0
  18. package/docs/recipes/auth/server/public/dashboard-unauthenticated.html +21 -0
  19. package/docs/recipes/auth/server/public/dashboard.html +25 -0
  20. package/docs/recipes/auth/server/public/home-unauthenticated.html +27 -0
  21. package/docs/recipes/auth/server/public/home.html +17 -0
  22. package/docs/recipes/auth/server/public/unauthenticated.html +22 -0
  23. package/docs/recipes/auth/server/server.js +85 -0
  24. package/docs/recipes/custom-audit/custom-config.js +36 -0
  25. package/docs/recipes/custom-audit/package.json +8 -0
  26. package/docs/recipes/custom-audit/readme.md +32 -0
  27. package/docs/recipes/custom-audit/searchable-audit.js +46 -0
  28. package/docs/recipes/custom-audit/searchable-gatherer.js +32 -0
  29. package/docs/recipes/custom-gatherer-puppeteer/custom-audit.js +34 -0
  30. package/{root.js → docs/recipes/custom-gatherer-puppeteer/custom-config.js} +23 -2
  31. package/docs/recipes/custom-gatherer-puppeteer/custom-gatherer.js +51 -0
  32. package/docs/recipes/custom-gatherer-puppeteer/package.json +13 -0
  33. package/docs/recipes/custom-gatherer-puppeteer/readme.md +7 -0
  34. package/docs/recipes/custom-gatherer-puppeteer/test.sh +9 -0
  35. package/docs/recipes/gulp/gulpfile.js +79 -0
  36. package/docs/recipes/gulp/package.json +12 -0
  37. package/docs/recipes/gulp/public/index.html +22 -0
  38. package/docs/recipes/gulp/readme.md +6 -0
  39. package/docs/recipes/integration-test/README.md +14 -0
  40. package/docs/recipes/integration-test/example-lh-auth.test.js +160 -0
  41. package/docs/recipes/integration-test/package.json +9 -0
  42. package/docs/recipes/lighthouse-plugin-example/audits/preload-as.js +46 -0
  43. package/docs/recipes/lighthouse-plugin-example/package.json +11 -0
  44. package/docs/recipes/lighthouse-plugin-example/plugin-recipe-screenshot.png +0 -0
  45. package/docs/recipes/lighthouse-plugin-example/plugin.js +24 -0
  46. package/docs/recipes/lighthouse-plugin-example/readme.md +53 -0
  47. package/docs/releasing.md +164 -0
  48. package/docs/scoring.md +79 -0
  49. package/docs/throttling.md +150 -0
  50. package/docs/understanding-results.md +209 -0
  51. package/docs/v8-perf-faq.md +244 -0
  52. package/docs/variability.md +150 -0
  53. package/jest.config.js +0 -3
  54. package/lighthouse-cli/bin.js +8 -6
  55. package/lighthouse-core/audits/accessibility/aria-command-name.js +1 -1
  56. package/lighthouse-core/audits/accessibility/aria-meter-name.js +1 -1
  57. package/lighthouse-core/audits/accessibility/aria-progressbar-name.js +1 -1
  58. package/lighthouse-core/audits/accessibility/aria-tooltip-name.js +1 -1
  59. package/lighthouse-core/audits/accessibility/aria-treeitem-name.js +1 -1
  60. package/lighthouse-core/audits/apple-touch-icon.js +0 -1
  61. package/lighthouse-core/audits/audit.js +3 -3
  62. package/lighthouse-core/audits/autocomplete.js +2 -2
  63. package/lighthouse-core/audits/byte-efficiency/byte-efficiency-audit.js +14 -29
  64. package/lighthouse-core/audits/byte-efficiency/duplicated-javascript.js +2 -3
  65. package/lighthouse-core/audits/byte-efficiency/efficient-animated-content.js +1 -1
  66. package/lighthouse-core/audits/byte-efficiency/legacy-javascript.js +2 -3
  67. package/lighthouse-core/audits/byte-efficiency/modern-image-formats.js +15 -55
  68. package/lighthouse-core/audits/byte-efficiency/offscreen-images.js +4 -8
  69. package/lighthouse-core/audits/byte-efficiency/render-blocking-resources.js +8 -15
  70. package/lighthouse-core/audits/byte-efficiency/total-byte-weight.js +9 -10
  71. package/lighthouse-core/audits/byte-efficiency/unminified-css.js +1 -1
  72. package/lighthouse-core/audits/byte-efficiency/unminified-javascript.js +1 -1
  73. package/lighthouse-core/audits/byte-efficiency/unused-css-rules.js +1 -1
  74. package/lighthouse-core/audits/byte-efficiency/unused-javascript.js +1 -2
  75. package/lighthouse-core/audits/byte-efficiency/uses-long-cache-ttl.js +2 -2
  76. package/lighthouse-core/audits/byte-efficiency/uses-optimized-images.js +1 -2
  77. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images.js +24 -46
  78. package/lighthouse-core/audits/byte-efficiency/uses-text-compression.js +1 -1
  79. package/lighthouse-core/audits/critical-request-chains.js +0 -1
  80. package/lighthouse-core/audits/csp-xss.js +1 -1
  81. package/lighthouse-core/audits/diagnostics.js +0 -1
  82. package/lighthouse-core/audits/dobetterweb/geolocation-on-start.js +0 -1
  83. package/lighthouse-core/audits/dobetterweb/inspector-issues.js +1 -1
  84. package/lighthouse-core/audits/dobetterweb/notification-on-start.js +0 -1
  85. package/lighthouse-core/audits/dobetterweb/uses-http2.js +0 -1
  86. package/lighthouse-core/audits/final-screenshot.js +4 -8
  87. package/lighthouse-core/audits/font-display.js +0 -1
  88. package/lighthouse-core/audits/installable-manifest.js +3 -4
  89. package/lighthouse-core/audits/largest-contentful-paint-element.js +0 -1
  90. package/lighthouse-core/audits/long-tasks.js +1 -1
  91. package/lighthouse-core/audits/maskable-icon.js +0 -1
  92. package/lighthouse-core/audits/metrics/first-contentful-paint-3g.js +2 -4
  93. package/lighthouse-core/audits/metrics/first-contentful-paint.js +2 -4
  94. package/lighthouse-core/audits/metrics/first-meaningful-paint.js +2 -4
  95. package/lighthouse-core/audits/metrics/interactive.js +2 -4
  96. package/lighthouse-core/audits/metrics/largest-contentful-paint.js +2 -4
  97. package/lighthouse-core/audits/metrics/max-potential-fid.js +2 -4
  98. package/lighthouse-core/audits/metrics/speed-index.js +2 -4
  99. package/lighthouse-core/audits/metrics/total-blocking-time.js +2 -10
  100. package/lighthouse-core/audits/metrics.js +2 -4
  101. package/lighthouse-core/audits/performance-budget.js +0 -1
  102. package/lighthouse-core/audits/predictive-perf.js +6 -9
  103. package/lighthouse-core/audits/preload-fonts.js +1 -11
  104. package/lighthouse-core/audits/preload-lcp-image.js +5 -18
  105. package/lighthouse-core/audits/redirects.js +8 -10
  106. package/lighthouse-core/audits/screenshot-thumbnails.js +13 -28
  107. package/lighthouse-core/audits/seo/canonical.js +1 -2
  108. package/lighthouse-core/audits/seo/font-size.js +4 -4
  109. package/lighthouse-core/audits/seo/hreflang.js +2 -3
  110. package/lighthouse-core/audits/seo/http-status-code.js +16 -21
  111. package/lighthouse-core/audits/seo/is-crawlable.js +3 -4
  112. package/lighthouse-core/audits/seo/robots-txt.js +2 -2
  113. package/lighthouse-core/audits/seo/tap-targets.js +5 -5
  114. package/lighthouse-core/audits/server-response-time.js +2 -20
  115. package/lighthouse-core/audits/splash-screen.js +0 -1
  116. package/lighthouse-core/audits/themed-omnibox.js +0 -1
  117. package/lighthouse-core/audits/third-party-facades.js +0 -1
  118. package/lighthouse-core/audits/third-party-summary.js +3 -1
  119. package/lighthouse-core/audits/timing-budget.js +2 -4
  120. package/lighthouse-core/audits/uses-rel-preconnect.js +9 -14
  121. package/lighthouse-core/audits/uses-rel-preload.js +2 -13
  122. package/lighthouse-core/computed/main-thread-tasks.js +2 -2
  123. package/lighthouse-core/computed/metrics/cumulative-layout-shift.js +3 -3
  124. package/lighthouse-core/computed/metrics/first-contentful-paint-all-frames.js +6 -6
  125. package/lighthouse-core/computed/metrics/first-contentful-paint.js +8 -9
  126. package/lighthouse-core/computed/metrics/first-meaningful-paint.js +9 -10
  127. package/lighthouse-core/computed/metrics/interactive.js +24 -25
  128. package/lighthouse-core/computed/metrics/lantern-first-contentful-paint.js +6 -6
  129. package/lighthouse-core/computed/metrics/lantern-first-meaningful-paint.js +6 -6
  130. package/lighthouse-core/computed/metrics/lantern-largest-contentful-paint.js +6 -6
  131. package/lighthouse-core/computed/metrics/lantern-metric.js +8 -16
  132. package/lighthouse-core/computed/metrics/largest-contentful-paint-all-frames.js +7 -7
  133. package/lighthouse-core/computed/metrics/largest-contentful-paint.js +9 -10
  134. package/lighthouse-core/computed/metrics/max-potential-fid.js +11 -8
  135. package/lighthouse-core/computed/metrics/metric.js +5 -32
  136. package/lighthouse-core/computed/metrics/speed-index.js +5 -6
  137. package/lighthouse-core/computed/metrics/timing-summary.js +45 -55
  138. package/lighthouse-core/computed/metrics/total-blocking-time.js +29 -36
  139. package/lighthouse-core/computed/page-dependency-graph.js +14 -14
  140. package/lighthouse-core/computed/resource-summary.js +7 -3
  141. package/lighthouse-core/computed/speedline.js +3 -3
  142. package/lighthouse-core/computed/trace-of-tab.js +86 -15
  143. package/lighthouse-core/computed/user-timings.js +5 -5
  144. package/lighthouse-core/config/default-config.js +0 -1
  145. package/lighthouse-core/fraggle-rock/config/config.js +7 -9
  146. package/lighthouse-core/fraggle-rock/config/default-config.js +2 -40
  147. package/lighthouse-core/fraggle-rock/config/filters.js +6 -39
  148. package/lighthouse-core/fraggle-rock/config/validation.js +4 -4
  149. package/lighthouse-core/fraggle-rock/gather/base-artifacts.js +19 -54
  150. package/lighthouse-core/fraggle-rock/gather/navigation-runner.js +10 -21
  151. package/lighthouse-core/fraggle-rock/gather/runner-helpers.js +2 -2
  152. package/lighthouse-core/fraggle-rock/gather/snapshot-runner.js +2 -2
  153. package/lighthouse-core/fraggle-rock/gather/timespan-runner.js +2 -2
  154. package/lighthouse-core/gather/driver/prepare.js +0 -3
  155. package/lighthouse-core/gather/fetcher.js +2 -0
  156. package/lighthouse-core/gather/gather-runner.js +14 -2
  157. package/lighthouse-core/gather/gatherers/image-elements.js +61 -24
  158. package/lighthouse-core/gather/gatherers/inspector-issues.js +2 -2
  159. package/lighthouse-core/gather/gatherers/script-elements.js +12 -45
  160. package/lighthouse-core/gather/gatherers/seo/font-size.js +5 -5
  161. package/lighthouse-core/gather/gatherers/seo/tap-targets.js +12 -64
  162. package/lighthouse-core/gather/gatherers/service-worker.js +3 -20
  163. package/lighthouse-core/gather/gatherers/trace-compat.js +1 -1
  164. package/lighthouse-core/gather/gatherers/trace-elements.js +3 -15
  165. package/lighthouse-core/gather/gatherers/trace.js +4 -13
  166. package/lighthouse-core/gather/gatherers/viewport-dimensions.js +1 -1
  167. package/lighthouse-core/lib/dependency-graph/simulator/network-analyzer.js +2 -13
  168. package/lighthouse-core/lib/emulation.js +2 -0
  169. package/lighthouse-core/lib/i18n/i18n.js +2 -2
  170. package/lighthouse-core/lib/i18n/locales/ar-XB.json +141 -141
  171. package/lighthouse-core/lib/i18n/locales/ar.json +143 -143
  172. package/lighthouse-core/lib/i18n/locales/bg.json +143 -143
  173. package/lighthouse-core/lib/i18n/locales/ca.json +141 -141
  174. package/lighthouse-core/lib/i18n/locales/cs.json +141 -141
  175. package/lighthouse-core/lib/i18n/locales/da.json +141 -141
  176. package/lighthouse-core/lib/i18n/locales/de.json +142 -142
  177. package/lighthouse-core/lib/i18n/locales/el.json +141 -141
  178. package/lighthouse-core/lib/i18n/locales/en-GB.json +141 -141
  179. package/lighthouse-core/lib/i18n/locales/en-US.ctc.json +6659 -0
  180. package/lighthouse-core/lib/i18n/locales/en-US.json +144 -150
  181. package/lighthouse-core/lib/i18n/locales/en-XA.json +141 -141
  182. package/lighthouse-core/lib/i18n/locales/en-XL.ctc.json +6628 -0
  183. package/lighthouse-core/lib/i18n/locales/en-XL.json +144 -150
  184. package/lighthouse-core/lib/i18n/locales/es-419.json +141 -141
  185. package/lighthouse-core/lib/i18n/locales/es.json +141 -141
  186. package/lighthouse-core/lib/i18n/locales/fi.json +143 -143
  187. package/lighthouse-core/lib/i18n/locales/fil.json +142 -142
  188. package/lighthouse-core/lib/i18n/locales/fr.json +141 -141
  189. package/lighthouse-core/lib/i18n/locales/he.json +142 -142
  190. package/lighthouse-core/lib/i18n/locales/hi.json +143 -143
  191. package/lighthouse-core/lib/i18n/locales/hr.json +141 -141
  192. package/lighthouse-core/lib/i18n/locales/hu.json +143 -143
  193. package/lighthouse-core/lib/i18n/locales/id.json +143 -143
  194. package/lighthouse-core/lib/i18n/locales/it.json +143 -143
  195. package/lighthouse-core/lib/i18n/locales/ja.json +143 -143
  196. package/lighthouse-core/lib/i18n/locales/ko.json +143 -143
  197. package/lighthouse-core/lib/i18n/locales/lt.json +143 -143
  198. package/lighthouse-core/lib/i18n/locales/lv.json +143 -143
  199. package/lighthouse-core/lib/i18n/locales/nl.json +141 -141
  200. package/lighthouse-core/lib/i18n/locales/no.json +141 -141
  201. package/lighthouse-core/lib/i18n/locales/pl.json +141 -141
  202. package/lighthouse-core/lib/i18n/locales/pt-PT.json +141 -141
  203. package/lighthouse-core/lib/i18n/locales/pt.json +141 -141
  204. package/lighthouse-core/lib/i18n/locales/ro.json +144 -144
  205. package/lighthouse-core/lib/i18n/locales/ru.json +143 -143
  206. package/lighthouse-core/lib/i18n/locales/sk.json +142 -142
  207. package/lighthouse-core/lib/i18n/locales/sl.json +141 -141
  208. package/lighthouse-core/lib/i18n/locales/sr-Latn.json +143 -143
  209. package/lighthouse-core/lib/i18n/locales/sr.json +143 -143
  210. package/lighthouse-core/lib/i18n/locales/sv.json +142 -142
  211. package/lighthouse-core/lib/i18n/locales/ta.json +141 -141
  212. package/lighthouse-core/lib/i18n/locales/te.json +143 -143
  213. package/lighthouse-core/lib/i18n/locales/th.json +143 -143
  214. package/lighthouse-core/lib/i18n/locales/tr.json +143 -143
  215. package/lighthouse-core/lib/i18n/locales/uk.json +141 -141
  216. package/lighthouse-core/lib/i18n/locales/vi.json +143 -143
  217. package/lighthouse-core/lib/i18n/locales/zh-HK.json +141 -141
  218. package/lighthouse-core/lib/i18n/locales/zh-TW.json +143 -143
  219. package/lighthouse-core/lib/i18n/locales/zh.json +143 -143
  220. package/lighthouse-core/lib/lh-error.js +2 -2
  221. package/lighthouse-core/lib/minify-trace.js +1 -1
  222. package/lighthouse-core/lib/network-request.js +1 -17
  223. package/lighthouse-core/lib/page-functions.js +5 -6
  224. package/lighthouse-core/lib/rect-helpers.js +3 -3
  225. package/lighthouse-core/lib/sd-validation/assets/jsonldcontext.json +7137 -0
  226. package/lighthouse-core/lib/sd-validation/assets/schema-tree.json +9900 -0
  227. package/{report/report-assets.js → lighthouse-core/lib/sd-validation/helpers/walk-object.js} +20 -12
  228. package/lighthouse-core/lib/sd-validation/json-expander.js +56 -0
  229. package/lighthouse-core/lib/sd-validation/json-linter.js +49 -0
  230. package/lighthouse-core/lib/sd-validation/jsonld-keyword-validator.js +50 -0
  231. package/lighthouse-core/lib/sd-validation/line-number-from-jsonld-path.js +60 -0
  232. package/lighthouse-core/lib/sd-validation/schema-validator.js +146 -0
  233. package/lighthouse-core/lib/sd-validation/scripts/download-jsonldcontext.js +30 -0
  234. package/lighthouse-core/lib/sd-validation/scripts/generate-schema-tree.js +97 -0
  235. package/lighthouse-core/lib/sd-validation/sd-validation.js +77 -0
  236. package/lighthouse-core/lib/tappable-rects.js +1 -1
  237. package/lighthouse-core/lib/tracehouse/trace-processor.js +42 -116
  238. package/lighthouse-core/lib/url-shim.js +2 -29
  239. package/lighthouse-core/report/html/html-report-assets.js +38 -0
  240. package/{report/README.md → lighthouse-core/report/html/readme.md} +11 -7
  241. package/{report → lighthouse-core/report/html}/renderer/category-renderer.js +0 -0
  242. package/{report → lighthouse-core/report/html}/renderer/crc-details-renderer.js +0 -0
  243. package/{report → lighthouse-core/report/html}/renderer/details-renderer.js +1 -1
  244. package/{report → lighthouse-core/report/html}/renderer/dom.js +0 -0
  245. package/{report → lighthouse-core/report/html}/renderer/element-screenshot-renderer.js +0 -0
  246. package/{report → lighthouse-core/report/html}/renderer/i18n.js +1 -1
  247. package/{report → lighthouse-core/report/html}/renderer/logger.js +0 -0
  248. package/{report → lighthouse-core/report/html}/renderer/performance-category-renderer.js +0 -0
  249. package/{report → lighthouse-core/report/html}/renderer/psi.js +0 -0
  250. package/{report → lighthouse-core/report/html}/renderer/pwa-category-renderer.js +0 -0
  251. package/{report → lighthouse-core/report/html}/renderer/report-renderer.js +0 -0
  252. package/{report → lighthouse-core/report/html}/renderer/report-ui-features.js +3 -3
  253. package/{report → lighthouse-core/report/html}/renderer/snippet-renderer.js +0 -0
  254. package/{report → lighthouse-core/report/html}/renderer/text-encoding.js +0 -0
  255. package/{report → lighthouse-core/report/html}/renderer/util.js +4 -7
  256. package/{report/assets/styles.css → lighthouse-core/report/html/report-styles.css} +0 -0
  257. package/{report/assets/standalone-template.html → lighthouse-core/report/html/report-template.html} +41 -1
  258. package/{report/assets → lighthouse-core/report/html}/templates.html +0 -0
  259. package/{report → lighthouse-core/report}/report-generator.js +1 -1
  260. package/lighthouse-core/runner.js +1 -1
  261. package/package.json +12 -13
  262. package/readme.md +2 -2
  263. package/third-party/chromium-synchronization/inspector-issueAdded-types-test.js +2 -3
  264. package/third-party/chromium-synchronization/installability-errors-test.js +2 -2
  265. package/third-party/download-content-shell/download-content-shell.js +1 -2
  266. package/third-party/snyk/snapshot.json +1 -1
  267. package/tsconfig.json +1 -0
  268. package/types/artifacts.d.ts +21 -41
  269. package/types/audit.d.ts +0 -2
  270. package/types/config.d.ts +2 -16
  271. package/types/externs.d.ts +0 -1
  272. package/types/gatherer.d.ts +13 -13
  273. package/types/html-renderer.d.ts +14 -14
  274. package/types/i18n.d.ts +1 -1
  275. package/lighthouse-core/audits/byte-efficiency/uses-responsive-images-snapshot.js +0 -94
  276. package/lighthouse-core/computed/image-records.js +0 -62
  277. package/lighthouse-core/computed/metrics/navigation-metric.js +0 -46
  278. package/lighthouse-core/computed/processed-navigation.js +0 -21
  279. package/lighthouse-core/computed/processed-trace.js +0 -21
  280. package/lighthouse-core/gather/gatherers/gather-context.js +0 -25
  281. package/lighthouse-core/gather/gatherers/host-form-factor.js +0 -31
  282. package/lighthouse-core/gather/gatherers/host-user-agent.js +0 -29
  283. package/lighthouse-core/lib/lh-trace-processor.js +0 -46
  284. package/report/clients/standalone.js +0 -52
  285. package/report/test/renderer/category-renderer-test.js +0 -488
  286. package/report/test/renderer/crc-details-renderer-test.js +0 -115
  287. package/report/test/renderer/details-renderer-test.js +0 -843
  288. package/report/test/renderer/dom-test.js +0 -157
  289. package/report/test/renderer/element-screenshot-renderer-test.js +0 -189
  290. package/report/test/renderer/i18n-test.js +0 -146
  291. package/report/test/renderer/performance-category-renderer-test.js +0 -332
  292. package/report/test/renderer/psi-test.js +0 -152
  293. package/report/test/renderer/pwa-category-renderer-test.js +0 -303
  294. package/report/test/renderer/report-renderer-test.js +0 -325
  295. package/report/test/renderer/report-ui-features-test.js +0 -572
  296. package/report/test/renderer/snippet-renderer-test.js +0 -291
  297. package/report/test/renderer/text-encoding-test.js +0 -41
  298. package/report/test/renderer/util-test.js +0 -388
  299. package/report/test/report-generator-test.js +0 -145
@@ -5,18 +5,26 @@
5
5
  */
6
6
  'use strict';
7
7
 
8
- const fs = require('fs');
8
+ /**
9
+ * Recursively (DFS) traverses an object and calls provided function for each field.
10
+ *
11
+ * @param {*} obj
12
+ * @param {function(string, any, Array<string>, any): void} callback
13
+ * @param {Array<string>} path
14
+ */
15
+ module.exports = function walkObject(obj, callback, path = []) {
16
+ if (obj === null) {
17
+ return;
18
+ }
19
+
20
+ Object.entries(obj).forEach(([fieldName, fieldValue]) => {
21
+ const newPath = Array.from(path);
22
+ newPath.push(fieldName);
9
23
 
10
- const REPORT_TEMPLATE = fs.readFileSync(__dirname + '/assets/standalone-template.html', 'utf8');
11
- const REPORT_JAVASCRIPT = fs.readFileSync(__dirname + '/../dist/report/standalone.js', 'utf8');
12
- const REPORT_CSS = fs.readFileSync(__dirname + '/assets/styles.css', 'utf8');
13
- const REPORT_TEMPLATES = fs.readFileSync(__dirname + '/assets/templates.html', 'utf8');
24
+ callback(fieldName, fieldValue, newPath, obj);
14
25
 
15
- // Changes to this export interface should be reflected in build/build-dt-report-resources.js
16
- // and clients/devtools-report-assets.js
17
- module.exports = {
18
- REPORT_TEMPLATE,
19
- REPORT_TEMPLATES,
20
- REPORT_JAVASCRIPT,
21
- REPORT_CSS,
26
+ if (typeof fieldValue === 'object') {
27
+ walkObject(fieldValue, callback, newPath);
28
+ }
29
+ });
22
30
  };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @license Copyright 2018 The Lighthouse Authors. All Rights Reserved.
3
+ * 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
4
+ * 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.
5
+ */
6
+ 'use strict';
7
+
8
+ const {URL} = require('../url-shim.js');
9
+ const jsonld = require('jsonld');
10
+ const schemaOrgContext = require('./assets/jsonldcontext.json');
11
+ const SCHEMA_ORG_HOST = 'schema.org';
12
+
13
+ /**
14
+ * Custom loader that prevents network calls and allows us to return local version of the
15
+ * schema.org document
16
+ * @param {string} schemaUrl
17
+ * @return {Promise<import('jsonld').RemoteDocument>}
18
+ */
19
+ async function documentLoader(schemaUrl) {
20
+ let urlObj = null;
21
+
22
+ try {
23
+ // Give a dummy base URL so relative URLs will be considered valid.
24
+ urlObj = new URL(schemaUrl, 'http://example.com');
25
+ } catch (e) {
26
+ throw new Error('Error parsing URL: ' + schemaUrl);
27
+ }
28
+
29
+ if (urlObj.host === SCHEMA_ORG_HOST && urlObj.pathname === '/') {
30
+ return {
31
+ document: schemaOrgContext,
32
+ };
33
+ } else {
34
+ // We only process schema.org, for other schemas we return an empty object
35
+ return {
36
+ document: {},
37
+ };
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Takes JSON-LD object and normalizes it by following the expansion algorithm
43
+ * (https://json-ld.org/spec/latest/json-ld-api/#expansion).
44
+ *
45
+ * @param {any} inputObject
46
+ * @returns {Promise<LH.StructuredData.ExpandedSchemaRepresentation|null>}
47
+ */
48
+ module.exports = async function expand(inputObject) {
49
+ try {
50
+ return await jsonld.expand(inputObject, {documentLoader});
51
+ } catch (err) {
52
+ // jsonld wraps real errors in a bunch of junk, so see we have an underlying error first
53
+ if (err.details && err.details.cause) throw err.details.cause;
54
+ throw err;
55
+ }
56
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @license Copyright 2018 The Lighthouse Authors. All Rights Reserved.
3
+ * 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
4
+ * 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.
5
+ */
6
+ 'use strict';
7
+
8
+ const jsonlint = require('jsonlint-mod');
9
+
10
+ /**
11
+ * @param {string} input
12
+ * @returns {{message: string, lineNumber: number|null}|null}
13
+ */
14
+ module.exports = function parseJSON(input) {
15
+ try {
16
+ jsonlint.parse(input);
17
+ } catch (error) {
18
+ /** @type {number|null} */
19
+ let line = error.at;
20
+
21
+ // extract line number from message
22
+ if (!line) {
23
+ const regexLineResult = error.message.match(/Parse error on line (\d+)/);
24
+
25
+ if (regexLineResult) {
26
+ line = parseFloat(regexLineResult[1]);
27
+ }
28
+ }
29
+
30
+
31
+ // jsonlint error message points to a specific character, but we just want the message.
32
+ // Example:
33
+ // ---------^
34
+ // Unexpected character {
35
+ let message = /** @type {string} */ (error.message);
36
+ const regexMessageResult = error.message.match(/-+\^\n(.+)$/);
37
+
38
+ if (regexMessageResult) {
39
+ message = regexMessageResult[1];
40
+ }
41
+
42
+ return {
43
+ message,
44
+ lineNumber: line,
45
+ };
46
+ }
47
+
48
+ return null;
49
+ };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @license Copyright 2018 The Lighthouse Authors. All Rights Reserved.
3
+ * 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
4
+ * 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.
5
+ */
6
+ 'use strict';
7
+
8
+ const walkObject = require('./helpers/walk-object.js');
9
+
10
+ // This list comes from the JSON-LD 1.1 editors draft:
11
+ // https://w3c.github.io/json-ld-syntax/#syntax-tokens-and-keywords
12
+ const VALID_KEYWORDS = new Set([
13
+ '@base',
14
+ '@container',
15
+ '@context',
16
+ '@graph',
17
+ '@id',
18
+ '@index',
19
+ '@language',
20
+ '@list',
21
+ '@nest',
22
+ '@none',
23
+ '@prefix',
24
+ '@reverse',
25
+ '@set',
26
+ '@type',
27
+ '@value',
28
+ '@version',
29
+ '@vocab',
30
+ ]);
31
+
32
+ /**
33
+ * @param {*} json
34
+ * @return {Array<{path: string, message: string}>}
35
+ */
36
+ module.exports = function validateJsonLD(json) {
37
+ /** @type {Array<{path: string, message: string}>} */
38
+ const errors = [];
39
+
40
+ walkObject(json, (name, value, path) => {
41
+ if (name.startsWith('@') && !VALID_KEYWORDS.has(name)) {
42
+ errors.push({
43
+ path: path.join('/'),
44
+ message: `Unknown keyword "${name}"`,
45
+ });
46
+ }
47
+ });
48
+
49
+ return errors;
50
+ };
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @license Copyright 2019 The Lighthouse Authors. All Rights Reserved.
3
+ * 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
4
+ * 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.
5
+ */
6
+ 'use strict';
7
+
8
+ /**
9
+ * @param {*} obj - JSON-LD object
10
+ * @param {string} path - slash-separated path in the JSON-LD object (uses relative URIs)
11
+ * @returns {null | number} - line number of the path value in the prettified JSON
12
+ */
13
+ function getLineNumberFromJsonLDPath(obj, path) {
14
+ // To avoid having an extra dependency on a JSON parser we set a unique key in the
15
+ // object and then use that to identify the correct line
16
+ const searchKey = Math.random().toString();
17
+ obj = JSON.parse(JSON.stringify(obj));
18
+
19
+ setValueAtJsonLDPath(obj, path, searchKey);
20
+ const jsonLines = JSON.stringify(obj, null, 2).split('\n');
21
+ const lineIndex = jsonLines.findIndex(line => line.includes(searchKey));
22
+
23
+ return lineIndex === -1 ? null : lineIndex + 1;
24
+ }
25
+
26
+ /**
27
+ * @param {*} obj
28
+ * @param {string} path
29
+ * @param {*} value
30
+ */
31
+ function setValueAtJsonLDPath(obj, path, value) {
32
+ const pathParts = path.split('/').filter(p => !!p);
33
+ let currentObj = obj;
34
+ pathParts.forEach((pathPart, i) => {
35
+ if (pathPart === '0' && !Array.isArray(currentObj)) {
36
+ // jsonld expansion turns single values into arrays
37
+ return;
38
+ }
39
+
40
+ const isLastPart = pathParts.length - 1 === i;
41
+ for (const key of Object.keys(currentObj)) {
42
+ // The actual key in JSON might be an absolute IRI like "http://schema.org/author"
43
+ // but key provided by validator is "author"
44
+ const keyParts = key.split('/');
45
+ const relativeKey = keyParts[keyParts.length - 1];
46
+ if (relativeKey === pathPart) {
47
+ // If we've arrived at the end of the provided path set the value, otherwise
48
+ // continue iterating with the object at the key location
49
+ if (isLastPart) {
50
+ currentObj[key] = value;
51
+ } else {
52
+ currentObj = currentObj[key];
53
+ }
54
+ return;
55
+ }
56
+ }
57
+ });
58
+ }
59
+
60
+ module.exports = getLineNumberFromJsonLDPath;
@@ -0,0 +1,146 @@
1
+ /**
2
+ * @license Copyright 2018 The Lighthouse Authors. All Rights Reserved.
3
+ * 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
4
+ * 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.
5
+ */
6
+ 'use strict';
7
+
8
+ const walkObject = require('./helpers/walk-object.js');
9
+ const schemaStructure = require('./assets/schema-tree.json');
10
+ const TYPE_KEYWORD = '@type';
11
+ const SCHEMA_ORG_URL_REGEX = /https?:\/\/schema\.org\//;
12
+
13
+ /**
14
+ * @param {string} uri
15
+ * @returns {string}
16
+ */
17
+ function cleanName(uri) {
18
+ return uri.replace(SCHEMA_ORG_URL_REGEX, '');
19
+ }
20
+
21
+ /**
22
+ * @param {string} type
23
+ * @returns {Array<string>}
24
+ */
25
+ function getPropsForType(type) {
26
+ const cleanType = cleanName(type);
27
+ const props = schemaStructure.properties
28
+ .filter(prop => prop.parent.includes(cleanType))
29
+ .map(prop => prop.name);
30
+ const foundType = findType(type);
31
+ if (!foundType) throw new Error(`Unable to get props for missing type "${type}"`);
32
+ const parentTypes = foundType.parent;
33
+
34
+ return parentTypes.reduce((allProps, type) => allProps.concat(getPropsForType(type)), props);
35
+ }
36
+
37
+ /**
38
+ * @param {string} type
39
+ * @returns {{name: string, parent: Array<string>}|undefined}
40
+ */
41
+ function findType(type) {
42
+ const cleanType = cleanName(type);
43
+
44
+ return schemaStructure.types.find(typeObj => typeObj.name === cleanType);
45
+ }
46
+
47
+ /**
48
+ * Validates keys of given object based on its type(s). Returns an array of error messages.
49
+ *
50
+ * @param {string|Array<string>} typeOrTypes
51
+ * @param {Array<string>} keys
52
+ * @returns {Array<Pick<LH.StructuredData.ValidationError, "message" | "validTypes"> & {key?: string}>}
53
+ */
54
+ function validateObjectKeys(typeOrTypes, keys) {
55
+ /** @type {Array<string>} */
56
+ let types = [];
57
+
58
+ if (typeof typeOrTypes === 'string') {
59
+ types = [typeOrTypes];
60
+ } else if (Array.isArray(typeOrTypes)) {
61
+ types = typeOrTypes;
62
+ const invalidIndex = typeOrTypes.findIndex(s => typeof s !== 'string');
63
+ if (invalidIndex >= 0) {
64
+ return [{message: `Unknown value type at index ${invalidIndex}`}];
65
+ }
66
+ } else {
67
+ return [{message: 'Unknown value type'}];
68
+ }
69
+
70
+ const unknownTypes = types.filter(t => !findType(t));
71
+
72
+ if (unknownTypes.length) {
73
+ return unknownTypes
74
+ .filter(type => SCHEMA_ORG_URL_REGEX.test(type))
75
+ .map(type => ({
76
+ message: `Unrecognized schema.org type: ${type}`,
77
+ key: '@type',
78
+ }));
79
+ }
80
+
81
+ /** @type {Set<string>} */
82
+ const allKnownProps = new Set();
83
+
84
+ types.forEach(type => {
85
+ const knownProps = getPropsForType(type);
86
+
87
+ knownProps.forEach(key => allKnownProps.add(key));
88
+ });
89
+
90
+ const cleanKeys = keys
91
+ // Skip JSON-LD keywords (including invalid ones as they were already flagged in the json-ld validator)
92
+ .filter(key => key.indexOf('@') !== 0)
93
+ .map(key => cleanName(key));
94
+
95
+ return cleanKeys
96
+ // remove Schema.org input/output constraints http://schema.org/docs/actions.html#part-4
97
+ .map(key => key.replace(/-(input|output)$/, ''))
98
+ .filter(key => !allKnownProps.has(key))
99
+ .map(key => ({
100
+ message: `Unexpected property "${key}"`,
101
+ key,
102
+ validTypes: types,
103
+ }));
104
+ }
105
+
106
+ /**
107
+ * @param {LH.StructuredData.ExpandedSchemaRepresentation|null} expandedObj Valid JSON-LD object in expanded form
108
+ * @return {Array<Pick<LH.StructuredData.ValidationError, "message" | "validTypes" | "path">>}
109
+ */
110
+ module.exports = function validateSchemaOrg(expandedObj) {
111
+ /** @type {Array<Pick<LH.StructuredData.ValidationError, "message" | "validTypes" | "path">>} */
112
+ const errors = [];
113
+
114
+ if (expandedObj === null) {
115
+ return errors;
116
+ }
117
+
118
+ // If the array only has a single item, treat it as if it was at the root to simplify the error path.
119
+ // Arrays longer than a single item are handled in `walkObject` below.
120
+ if (Array.isArray(expandedObj) && expandedObj.length === 1) {
121
+ expandedObj = expandedObj[0];
122
+ }
123
+
124
+ walkObject(expandedObj, (name, value, path, obj) => {
125
+ if (name === TYPE_KEYWORD) {
126
+ const keyErrors = validateObjectKeys(value, Object.keys(obj));
127
+
128
+ keyErrors.forEach(error => {
129
+ errors.push({
130
+ validTypes: error.validTypes,
131
+ message: error.message,
132
+ // get rid of the last chunk (/@type) as it's the same for all errors
133
+ path:
134
+ '/' +
135
+ path
136
+ .slice(0, -1)
137
+ .concat(error.key || [])
138
+ .map(cleanName)
139
+ .join('/'),
140
+ });
141
+ });
142
+ }
143
+ });
144
+
145
+ return errors;
146
+ };
@@ -0,0 +1,30 @@
1
+ /**
2
+ * @license Copyright 2018 The Lighthouse Authors. All Rights Reserved.
3
+ * 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
4
+ * 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.
5
+ */
6
+ 'use strict';
7
+
8
+ /**
9
+ * Call this script to update assets/jsonldcontext.json with the latest schema.org spec
10
+ */
11
+
12
+ const fetch = require('node-fetch');
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+
16
+ const SCHEMA_ORG_URL = 'https://schema.org';
17
+ const CONTEXT_FILE = path.join(__dirname, '../assets/jsonldcontext.json');
18
+
19
+ async function run() {
20
+ try {
21
+ const response = await fetch(SCHEMA_ORG_URL, {headers: {Accept: 'application/ld+json'}});
22
+ const data = await response.json();
23
+ fs.writeFileSync(CONTEXT_FILE, JSON.stringify(data, null, 2));
24
+ console.log('Success.'); // eslint-disable-line no-console
25
+ } catch (e) {
26
+ console.error(e); // eslint-disable-line no-console
27
+ }
28
+ }
29
+
30
+ run();
@@ -0,0 +1,97 @@
1
+ /**
2
+ * @license Copyright 2018 The Lighthouse Authors. All Rights Reserved.
3
+ * 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
4
+ * 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.
5
+ */
6
+ 'use strict';
7
+
8
+ /**
9
+ * Call this script to update assets/schema-tree.json with the latest schema.org spec
10
+ */
11
+
12
+ const fetch = require('node-fetch');
13
+ const path = require('path');
14
+ const fs = require('fs');
15
+
16
+ const SCHEMA_ORG_URL = 'https://schema.org/version/latest/schema.jsonld';
17
+ const SCHEMA_TREE_FILE = path.join(__dirname, '../assets/schema-tree.json');
18
+
19
+ /** @typedef {import('jsonlint-mod').SchemaTreeItem} SchemaDefinition */
20
+
21
+ /** @typedef {import('jsonlint-mod').JSONSchemaSource} SchemaSource */
22
+
23
+ /** @typedef {{'@id': string}} IDRef */
24
+
25
+ /**
26
+ * @param {SchemaSource} data
27
+ */
28
+ function processData(data) {
29
+ /** @type {SchemaDefinition[]} */
30
+ const types = [];
31
+ /** @type {SchemaDefinition[]} */
32
+ const properties = [];
33
+
34
+ /** @param {string} str */
35
+ function removePrefix(str) {
36
+ return str.replace('http://schema.org/', '');
37
+ }
38
+
39
+ /**
40
+ * Accepts some set of id references and returns the array of cleaned references without the `http://schema.org` prefix.
41
+ * @param {Array<IDRef>|IDRef|undefined} parents
42
+ */
43
+ function cleanIdPrefixes(parents) {
44
+ if (Array.isArray(parents)) {
45
+ return parents.map(item => removePrefix(item['@id']));
46
+ } else if (parents && parents['@id']) {
47
+ return [removePrefix(parents['@id'])];
48
+ }
49
+
50
+ return [];
51
+ }
52
+
53
+ // Go through all the graph entries to find the valid types and properties.
54
+ // i.e. this converts
55
+ // [
56
+ // {@id: 'http://schema.org/CafeOrCoffeeShop', @type: 'rdfs:Class', rdfs:subClassOf: {@id: 'http://schema.org/FoodEstablishment'}},
57
+ // {@id: 'http://schema.org/bestRating', @type: 'rdfs:Property', http://schema.org/domainIncludes: {@id: 'http://schema.org/Rating'}},
58
+ // ]
59
+ // into
60
+ // {
61
+ // types: [{name: 'CafeOrCoffeeShop', parent: ['FoodEstablishment']}],
62
+ // properties: [{name: 'bestRating', parent: ['Rating']}],
63
+ // }
64
+ data['@graph'].forEach(item => {
65
+ if (item['rdfs:label'] === undefined) {
66
+ return;
67
+ }
68
+
69
+ if (item['@type'] === 'rdf:Property') {
70
+ properties.push({
71
+ name: item['rdfs:label'],
72
+ parent: cleanIdPrefixes(item['http://schema.org/domainIncludes']),
73
+ });
74
+ } else {
75
+ types.push({
76
+ name: item['rdfs:label'],
77
+ parent: cleanIdPrefixes(item['rdfs:subClassOf']),
78
+ });
79
+ }
80
+ });
81
+
82
+ return {types, properties};
83
+ }
84
+
85
+ async function run() {
86
+ try {
87
+ const response = await fetch(SCHEMA_ORG_URL);
88
+ const data = await response.json();
89
+ const processed = processData(data);
90
+ fs.writeFileSync(SCHEMA_TREE_FILE, JSON.stringify(processed, null, 2));
91
+ console.log('Success.'); // eslint-disable-line no-console
92
+ } catch (e) {
93
+ console.error(e); // eslint-disable-line no-console
94
+ }
95
+ }
96
+
97
+ run();
@@ -0,0 +1,77 @@
1
+ /**
2
+ * @license Copyright 2018 The Lighthouse Authors. All Rights Reserved.
3
+ * 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
4
+ * 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.
5
+ */
6
+ 'use strict';
7
+
8
+ const parseJSON = require('./json-linter.js');
9
+ const validateJsonLD = require('./jsonld-keyword-validator.js');
10
+ const expandAsync = require('./json-expander.js');
11
+ const validateSchemaOrg = require('./schema-validator.js');
12
+ const getLineNumberFromJsonLDPath = require('./line-number-from-jsonld-path.js');
13
+
14
+ /**
15
+ * Validates JSON-LD input. Returns array of error objects.
16
+ *
17
+ * @param {string} textInput
18
+ * @returns {Promise<Array<LH.StructuredData.ValidationError>>}
19
+ */
20
+ module.exports = async function validate(textInput) {
21
+ // STEP 1: VALIDATE JSON
22
+ const parseError = parseJSON(textInput);
23
+
24
+ if (parseError) {
25
+ return [{
26
+ validator: 'json',
27
+ lineNumber: parseError.lineNumber,
28
+ message: parseError.message,
29
+ }];
30
+ }
31
+
32
+ const inputObject = JSON.parse(textInput);
33
+
34
+ // STEP 2: VALIDATE JSONLD
35
+ const jsonLdErrors = validateJsonLD(inputObject);
36
+
37
+ if (jsonLdErrors.length) {
38
+ return jsonLdErrors.map(error => {
39
+ return {
40
+ validator: 'json-ld',
41
+ path: error.path,
42
+ message: error.message,
43
+ lineNumber: getLineNumberFromJsonLDPath(inputObject, error.path),
44
+ };
45
+ });
46
+ }
47
+
48
+ // STEP 3: EXPAND
49
+ /** @type {LH.StructuredData.ExpandedSchemaRepresentation|null} */
50
+ let expandedObj = null;
51
+ try {
52
+ expandedObj = await expandAsync(inputObject);
53
+ } catch (error) {
54
+ return [{
55
+ validator: 'json-ld-expand',
56
+ message: error.message,
57
+ }];
58
+ }
59
+
60
+ // STEP 4: VALIDATE SCHEMA
61
+ const schemaOrgErrors = validateSchemaOrg(expandedObj);
62
+
63
+ if (schemaOrgErrors.length) {
64
+ return schemaOrgErrors.map(error => {
65
+ return {
66
+ validator: 'schema-org',
67
+ path: error.path,
68
+ message: error.message,
69
+ lineNumber: error.path ? getLineNumberFromJsonLDPath(inputObject, error.path) : null,
70
+ validTypes: error.validTypes,
71
+ };
72
+ });
73
+ }
74
+
75
+ return [];
76
+ };
77
+
@@ -44,7 +44,7 @@ function almostEqual(a, b) {
44
44
  /**
45
45
  * Merge touching rects based on what appears as one tappable area to the user.
46
46
  * @param {LH.Artifacts.Rect[]} clientRects
47
- * @return {LH.Artifacts.Rect[]}
47
+ * @returns {LH.Artifacts.Rect[]}
48
48
  */
49
49
  function mergeTouchingClientRects(clientRects) {
50
50
  for (let i = 0; i < clientRects.length; i++) {