diff --git a/.gitignore b/.gitignore index 06d86d241627..55534c108973 100644 --- a/.gitignore +++ b/.gitignore @@ -40,3 +40,9 @@ yarn-error.log /chrome.zip plots/out** + +*__pycache__ +*.pyc +proto/scripts/*_pb2.* +proto/scripts/*_pb.* +proto/scripts/*_processed.json diff --git a/lighthouse-core/lib/proto-preprocessor.js b/lighthouse-core/lib/proto-preprocessor.js new file mode 100644 index 000000000000..6e9f85331e4e --- /dev/null +++ b/lighthouse-core/lib/proto-preprocessor.js @@ -0,0 +1,109 @@ +/** + * @license Copyright 2018 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ + +'use strict'; + +const fs = require('fs'); + +/** + * @fileoverview Helper functions to transform an LHR into a proto-ready LHR. + * + * FIXME: This file is 100% technical debt. Our eventual goal is for the + * roundtrip JSON to match the Golden LHR 1:1. + */ + +/** + * @param {string} result + */ +function processForProto(result) { + /** @type {LH.Result} */ + const reportJson = JSON.parse(result); + + // Clean up actions that require 'audits' to exist + if ('audits' in reportJson) { + Object.keys(reportJson.audits).forEach(auditName => { + const audit = reportJson.audits[auditName]; + + // Rewrite the 'not-applicable' scoreDisplayMode to 'not_applicable'. #6201 + if ('scoreDisplayMode' in audit) { + if (audit.scoreDisplayMode === 'not-applicable') { + // @ts-ignore Breaking the LH.Result type + audit.scoreDisplayMode = 'not_applicable'; + } + } + // Drop raw values. #6199 + if ('rawValue' in audit) { + delete audit.rawValue; + } + // Normalize displayValue to always be a string, not an array. #6200 + + if (Array.isArray(audit.displayValue)) { + /** @type {Array}*/ + const values = []; + audit.displayValue.forEach(item => { + values.push(item); + }); + audit.displayValue = values.join(' | '); + } + }); + } + + // Drop the i18n icuMessagePaths. Painful in proto, and low priority to expose currently. + if ('i18n' in reportJson && 'icuMessagePaths' in reportJson.i18n) { + delete reportJson.i18n.icuMessagePaths; + } + + // Remove any found empty strings, as they are dropped after round-tripping anyway + /** + * @param {any} obj + */ + function removeStrings(obj) { + if (obj && typeof obj === 'object' && !Array.isArray(obj)) { + Object.keys(obj).forEach(key => { + if (typeof obj[key] === 'string' && obj[key] === '') { + delete obj[key]; + } else if (typeof obj[key] === 'object' || Array.isArray(obj[key])) { + removeStrings(obj[key]); + } + }); + } else if (Array.isArray(obj)) { + obj.forEach(item => { + if (typeof item === 'object' || Array.isArray(item)) { + removeStrings(item); + } + }); + } + } + + removeStrings(reportJson); + + return JSON.stringify(reportJson); +} + +// @ts-ignore claims always false, but this checks if cli or module +if (require.main === module) { + // read in the argv for the input & output + const args = process.argv.slice(2); + let input; + let output; + + if (args.length) { + // find can return undefined, so default it to '' with OR + input = (args.find(flag => flag.startsWith('--in')) || '').replace('--in=', ''); + output = (args.find(flag => flag.startsWith('--out')) || '').replace('--out=', ''); + } + + if (input && output) { + // process the file + const report = processForProto(fs.readFileSync(input, 'utf-8')); + // write to output from argv + fs.writeFileSync(output, report, 'utf-8'); + } +} else { + module.exports = { + processForProto, + }; +} diff --git a/lighthouse-core/test/lib/proto-preprocessor-test.js b/lighthouse-core/test/lib/proto-preprocessor-test.js new file mode 100644 index 000000000000..3b2b33c130df --- /dev/null +++ b/lighthouse-core/test/lib/proto-preprocessor-test.js @@ -0,0 +1,94 @@ +/** + * @license Copyright 2018 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +const processForProto = require('../../lib/proto-preprocessor').processForProto; + +/* eslint-env jest */ +describe('processing for proto', () => { + it('cleans up audits', () => { + const input = { + 'audits': { + 'critical-request-chains': { + 'scoreDisplayMode': 'not-applicable', + 'rawValue': 14.3, + 'displayValue': ['hello %d', 123], + }, + }, + }; + const expectation = { + 'audits': { + 'critical-request-chains': { + 'scoreDisplayMode': 'not_applicable', + 'displayValue': 'hello %d | 123', + }, + }, + }; + const output = processForProto(JSON.stringify(input)); + + expect(JSON.parse(output)).toMatchObject(expectation); + }); + + + it('removes i18n icuMessagePaths', () => { + const input = { + 'i18n': { + 'icuMessagePaths': { + 'content': 'paths', + }, + }, + }; + const expectation = { + 'i18n': {}, + }; + const output = processForProto(JSON.stringify(input)); + + expect(JSON.parse(output)).toMatchObject(expectation); + }); + + it('removes empty strings', () => { + const input = { + 'audits': { + 'critical-request-chains': { + 'details': { + 'chains': { + '1': '', + }, + }, + }, + }, + 'i18n': { + 'icuMessagePaths': { + 'content': 'paths', + }, + '2': '', + '3': [ + { + 'hello': 'world', + '4': '', + }, + ], + }, + }; + const expectation = { + 'audits': { + 'critical-request-chains': { + 'details': { + 'chains': {}, + }, + }, + }, + 'i18n': { + '3': [ + {'hello': 'world'}, + ], + }, + }; + const output = processForProto(JSON.stringify(input)); + + expect(JSON.parse(output)).toMatchObject(expectation); + }); +}); diff --git a/lighthouse-core/test/report/proto-test.js b/lighthouse-core/test/report/proto-test.js new file mode 100644 index 000000000000..cf918a68504e --- /dev/null +++ b/lighthouse-core/test/report/proto-test.js @@ -0,0 +1,72 @@ +/** + * @license Copyright 2017 Google Inc. All Rights Reserved. + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. + */ +'use strict'; + +const path = require('path'); +const fs = require('fs'); + +const sample = fs.readFileSync(path.resolve(__dirname, '../results/sample_v2.json')); +const roundTripJson = require('../../../proto/sample_v2_round_trip'); +const preprocessor = require('../../lib/proto-preprocessor.js'); + +/* eslint-env jest */ + +describe('round trip JSON comparison subsets', () => { + let sampleJson; + + beforeEach(() => { + sampleJson = JSON.parse(preprocessor.processForProto(sample)); + }); + + it('has the same audit results sans details', () => { + Object.keys(sampleJson.audits).forEach(audit => { + delete sampleJson.audits[audit].details; + }); + + expect(roundTripJson.audits).toMatchObject(sampleJson.audits); + }); + + it('has the same audit results & details if applicable', () => { + Object.keys(sampleJson.audits).forEach(auditId => { + expect(roundTripJson.audits[auditId]).toMatchObject(sampleJson.audits[auditId]); + + if ('details' in sampleJson.audits[auditId]) { + expect(roundTripJson.audits[auditId].details) + .toMatchObject(sampleJson.audits[auditId].details); + } + }); + }); + + it('has the same i18n rendererFormattedStrings', () => { + expect(roundTripJson.i18n).toMatchObject(sampleJson.i18n); + }); + + it('has the same top level values', () => { + Object.keys(sampleJson).forEach(audit => { + if (typeof sampleJson[audit] === 'object' && !Array.isArray(sampleJson[audit])) { + delete sampleJson[audit]; + } + }); + + expect(roundTripJson).toMatchObject(sampleJson); + }); + + it('has the same config values', () => { + expect(roundTripJson.configSettings).toMatchObject(sampleJson.configSettings); + }); +}); + +describe('round trip JSON comparison to everything', () => { + let sampleJson; + + beforeEach(() => { + sampleJson = JSON.parse(preprocessor.processForProto(sample)); + }); + + it('has the same JSON overall', () => { + expect(roundTripJson).toMatchObject(sampleJson); + }); +}); diff --git a/lighthouse-extension/app/src/lightrider-entry.js b/lighthouse-extension/app/src/lightrider-entry.js index 426d424280c0..f34959058bc2 100644 --- a/lighthouse-extension/app/src/lightrider-entry.js +++ b/lighthouse-extension/app/src/lightrider-entry.js @@ -9,6 +9,7 @@ const lighthouse = require('../../../lighthouse-core/index'); const assetSaver = require('../../../lighthouse-core/lib/asset-saver.js'); const LHError = require('../../../lighthouse-core/lib/lh-error.js'); +const preprocessor = require('../../../lighthouse-core/lib/proto-preprocessor.js'); /** @type {Record<'mobile'|'desktop', LH.Config.Json>} */ const LR_PRESETS = { @@ -46,6 +47,12 @@ async function runLighthouseInLR(connection, url, flags, {lrDevice, categoryIDs, if (logAssets) { await assetSaver.logAssets(results.artifacts, results.lhr.audits); } + + // pre process the LHR for proto + if (flags.output === 'json' && typeof results.report === 'string') { + return preprocessor.processForProto(results.report); + } + return results.report; } catch (err) { // If an error ruined the entire lighthouse run, attempt to return a meaningful error. diff --git a/package.json b/package.json index ff507bb42fb9..a3ec2b95d83a 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "build-all:task:windows": "yarn build-extension && yarn build-viewer", "build-extension": "cd ./lighthouse-extension && yarn build", "build-viewer": "cd ./lighthouse-viewer && yarn build", - "clean": "rimraf *.report.html *.report.dom.html *.report.json *.devtoolslog.json *.trace.json || true", + "clean": "rimraf proto/scripts/*.json proto/scripts/*_pb2.* proto/scripts/*_pb.* proto/scripts/__pycache__ proto/scripts/*.pyc *.report.html *.report.dom.html *.report.json *.devtoolslog.json *.trace.json || true", "lint": "[ \"$CI\" = true ] && eslint --quiet -f codeframe . || eslint .", "smoke": "node lighthouse-cli/test/smokehouse/run-smoke.js", "debug": "node --inspect-brk ./lighthouse-cli/index.js", @@ -63,7 +63,10 @@ "diff:sample-json": "yarn i18n:checks && bash lighthouse-core/scripts/assert-golden-lhr-unchanged.sh", "ultradumbBenchmark": "./lighthouse-core/scripts/benchmark.js", "mixed-content": "./lighthouse-cli/index.js --chrome-flags='--headless' --preset=mixed-content", - "minify-latest-run": "./lighthouse-core/scripts/lantern/minify-trace.js ./latest-run/defaultPass.trace.json ./latest-run/defaultPass.trace.min.json && ./lighthouse-core/scripts/lantern/minify-devtoolslog.js ./latest-run/defaultPass.devtoolslog.json ./latest-run/defaultPass.devtoolslog.min.json" + "minify-latest-run": "./lighthouse-core/scripts/lantern/minify-trace.js ./latest-run/defaultPass.trace.json ./latest-run/defaultPass.trace.min.json && ./lighthouse-core/scripts/lantern/minify-devtoolslog.js ./latest-run/defaultPass.devtoolslog.json ./latest-run/defaultPass.devtoolslog.min.json", + "update:proto": "yarn compile-proto && yarn build-proto", + "compile-proto": "protoc --python_out=./ ./proto/lighthouse-result.proto && mv ./proto/*_pb2.py ./proto/scripts", + "build-proto": "cd proto/scripts && PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION=cpp PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION_VERSION=2 python json_roundtrip_via_proto.py" }, "devDependencies": { "@types/chrome": "^0.0.60", diff --git a/proto/README.md b/proto/README.md new file mode 100644 index 000000000000..9e0c52c22f64 --- /dev/null +++ b/proto/README.md @@ -0,0 +1,29 @@ +## How to compile protos + use locally + +1. Install the proto compiler + 1. Manual install + 1. Get the latest proto [release](https://github.com/protocolbuffers/protobuf/releases) (select one with python included if you want to run this validator) + 1. Install the [C++ Protocol Buffer Runtime](https://github.com/protocolbuffers/protobuf/blob/master/src/README.md) + 1. Brew install + 1. `brew install protobuf` +1. Run `yarn compile-proto` then `yarn build-proto` + +## Proto Resources +- [Protobuf Github Repo](https://github.com/protocolbuffers/protobuf) +- [Protobuf Docs](https://developers.google.com/protocol-buffers/docs/overview) + +## LHR Round Trip Flow +``` +LHR round trip flow: + (Compiling the Proto) + lighthouse_result.proto -> protoc --python_out ... -> lighthouse_result.pb2 + ⭏ + (used by) + (Making a Round Trip JSON) ⭏ + lhr.json --> proto_preprocessor.js -> lhr_processed.json -> json_roundtrip_via_proto.py -> lhr.round_trip.json +``` + +## Hacking Hints +- Clean out compiled proto and json with `yarn clean` +- Round trips might jumble the order of your JSON keys and lists! +- Is your `six` installation troubling your `pip install protobuf`? Mine did. Try `pip install --ignore-installed six`. \ No newline at end of file diff --git a/proto/lighthouse-result.proto b/proto/lighthouse-result.proto new file mode 100644 index 000000000000..73c0c536edfe --- /dev/null +++ b/proto/lighthouse-result.proto @@ -0,0 +1,378 @@ +syntax = "proto3"; + +import "google/protobuf/struct.proto"; +import "google/protobuf/timestamp.proto"; +import "google/protobuf/wrappers.proto"; + +// Canonical list of errors created by Lighthouse. +enum LighthouseError { + UNPARSEABLE_ERROR_CODE = 0; + // No error in Lighthouse; the results are reliable. + NO_ERROR = 1; + // An uncategorized error occurred, likely a JavaScript exception. + UNKNOWN_ERROR = 2; + // The trace did not contain any screenshot events. + NO_SPEEDLINE_FRAMES = 3; + // No visual change between the beginning and end of load. + SPEEDINDEX_OF_ZERO = 4; + // The trace did not contain any screenshot events. + NO_SCREENSHOTS = 5; + // The computed speedindex results are non-finite. + INVALID_SPEEDLINE = 6; + // The trace did not contain a TracingStartedInPage event. + NO_TRACING_STARTED = 7; + // The trace did not contain a navigationStart event. + NO_NAVSTART = 8; + // The trace did not contain a firstContentfulPaint event. + NO_FCP = 9; + // The trace did not contain a domContentLoaded event. + NO_DCL = 10; + // No network request could be identified as the primary HTML document. + NO_DOCUMENT_REQUEST = 11; + // The HTML document's network request failed due to Chrome-internal reasons + // (canceled, blocked, etc). + FAILED_DOCUMENT_REQUEST = 12; + // The HTML document's network request completed, but returned an HTTP status + // code of 4xx or 5xx. + ERRORED_DOCUMENT_REQUEST = 13; + // Chromium's tracing controller did not manage to begin tracing across + // processes. Typically fixed by restarting Chromium. + TRACING_ALREADY_STARTED = 14; + // The trace data wasn't parsed correctly. + PARSING_PROBLEM = 15; + // The trace data failed to stream over the protocol. + READ_FAILED = 16; +} + +// The overarching Lighthouse Response object (LHR) +// https://github.com/GoogleChrome/lighthouse/blob/master/typings/lhr.d.ts +message LighthouseResult { + // The timestamp of when the results were generated. + google.protobuf.Timestamp fetch_time = 1; + + // The url requested to lightrider. + string requested_url = 2; + + // The final analyzed URL, differs from requested_url when there were + // redirects. + string final_url = 3; + + // The version of Lighthouse with which these results were generated. + string lighthouse_version = 4; + + // Message containing environment configuration for a LH run + message Environment { + // The user agent that was used by the network + string network_user_agent = 1; + + // The user agent used by the host + string host_user_agent = 2; + + // The benchmark index that indicates rough device class + int32 benchmark_index = 3; + } + + // The environment that the audit was run in + Environment environment = 5; + + // The user agent that was used to run the audit + string user_agent = 6; + + // Any top level warnings that this run encountered + // default is [] hence ListValue + google.protobuf.ListValue run_warnings = 7; + + // Message containing a runtime error config + message RuntimeError { + // The text repr of the error type + LighthouseError code = 1; + + // The error explanitory message + string message = 2; + } + + // A runtime error that was encountered during the run + RuntimeError runtime_error = 8; + + // A map containing the results of the audits, keyed by audit id. Audits may + // be referenced within more than one category. + map audits = 9; + + // A map containing the categories of audits, keyed by category id. + map categories = 10; + + // A map containing groups that categories can belong to, keyed by group id + map category_groups = 11; + + // Message containing the configuration settings for the LH run + message ConfigSettings { + // The output types the audit made (json, html...) + repeated string output = 1; + + // The maximum amount of time to wait for loading in ms + int32 max_wait_for_load = 2; + + // Network throttling options + enum ThrottlingMethod { + // Unknown method. Should not be used + UNKNOWN_THROTTLING_METHOD = 0; + + // Use devtools to throttle the request + devtools = 1; + + // Use no additional throttling (only throttling provided by system itself) + provided = 2; + + // Simulate throttling with Lantern + simulate = 3; + } + + // The throttling method used during this audit + ThrottlingMethod throttling_method = 3; + + // Message containing the throttling settings used in an LH configuration + message Throttling { + // The round trip time in ms + int32 rtt_ms = 1; + + // The throughput in kilobytes per second + google.protobuf.DoubleValue throughput_kbps = 2; + + // The request latency in milliseconds + google.protobuf.DoubleValue request_latency_ms = 3; + + // The download throughput in kilobytes per second + google.protobuf.DoubleValue download_throughput_kbps = 4; + + // The upload throughput in kilobytes per second + google.protobuf.DoubleValue upload_throughput_kbps = 5; + + // The amount of slowdown to apply to the cpu + int32 cpu_slowdown_multiplier = 6; + } + + // The throttling settings used + Throttling throttling = 4; + + // Flag indicating this audit was gather only or not + google.protobuf.BoolValue gather_mode = 5; + + // Flag disabling clearing the browser cache before runs + google.protobuf.BoolValue disable_storage_reset = 6; + + // Flag indicating if device emulation was enabled or not + google.protobuf.BoolValue disable_device_emulation = 7; + + // The possible form factors an audit can be run in + enum EmulatedFormFactor { + // Unknown form factor. This should not be used + UNKNOWN_FORM_FACTOR = 0; + + // Mobile form factor + mobile = 1; + + // Desktop form factor + desktop = 2; + + // No emulation used, uses system directly + none = 3; + } + + // The form factor used in the audit + EmulatedFormFactor emulated_form_factor = 8; + + // The locale that was active during the audit + string locale = 9; + + // List of URL patterns that were blocked during this audit + // nullable list of strings + google.protobuf.Value blocked_url_patterns = 10; + + // Comma delimited list of trace categories to capture + // nullable string + google.protobuf.Value additional_trace_categories = 11; + + // Map of extra HTTP Headers to pass in with the request + google.protobuf.Value extra_headers = 12; + + // List of the audits that were preformed, empty if all were run + // nullable list of strings + google.protobuf.Value only_audits = 13; + + // List of the categories that were run, empty if all were run + // nullable list of strings + google.protobuf.Value only_categories = 14; + + // List of the audits that were skipped, empty if all were run + // nullable list of strings + google.protobuf.Value skip_audits = 15; + } + + // The settings that were used to run this audit + ConfigSettings config_settings = 12; + + // i18n info in version 1 message format + I18n i18n = 13; +} + +// Message containing a category +message CategoryGroup { + // The human readable title of the group + string title = 1; + + // The description of what the category is grouping + string description = 2; +} + +// Message containing a category of audits and their combined weighted score. +message LhrCategory { + // the internal id of the category + string id = 1; + + // The human-friendly name of the category. + string title = 2; + + // A description of what this category is about (e.g. these help you validate + // your PWA). + string description = 3; + + // The overall score of the category, the weighted average of all its audits, + // from 0-1. + // This value is nullable, so is a `Value` type + google.protobuf.Value score = 4; + + // An description for manual audits within this category. + string manual_description = 5; + + // A Category's reference to an AuditResult, with a weight for category scoring. + message AuditRef { + // Matches a key in the top-level `audits` map. + string id = 1; + + // The weight of the audit's score in the overall category score. + google.protobuf.DoubleValue weight = 2; + + // The category group that the audit belongs to + string group = 3; + } + + // References to all the audit members and their weight in this category. + repeated AuditRef audit_refs = 6; +} + +// Message containing the result of an individual Lighthouse audit. +message AuditResult { + // The internal audit id + string id = 1; + + // A brief description of the audit. The text can change depending on if the + // audit passed or failed. + string title = 2; + + // A more detailed description that describes why the audit is important and + // links to Lighthouse documentation on the audit; markdown links supported. + string description = 3; + + // The scored value determined by the audit, in the range `0-1`, or NaN if + // `score_display_mode` indicates not scored. + // This value is nullable, so is a `Value` type + google.protobuf.Value score = 4; + + // The ways an audit score should be interpreted: + enum ScoreDisplayMode { + // Unknown mode. This should not be used. + SCORE_DISPLAY_MODE_UNSPECIFIED = 0; + // Pass/fail audit (0 and 1 are the only possible scores). + binary = 1; + // Scores of 0-1, inclusive. + numeric = 2; + // The audit is an FYI only, and can't be interpreted as pass/fail. Score is + // NaN and should be ignored. + informative = 3; + // The audit turned out to not apply to the page. Score is NaN and should be + // ignored. + not_applicable = 4; + // The audit exists only to tell you to review something yourself. Score is + // NaN and should be ignored + manual = 5; + // There was an error while running the audit (check `error_message` for + // details). Score is NaN and should be ignored. + error = 6; + } + + // The mode for how the score should be interpreted. + ScoreDisplayMode score_display_mode = 5; + + // The human readable value that is displayed as the audit's result + string display_value = 6; + + // An explanation of audit-related issues encountered on the test page. + string explanation = 7; + + // Error message from any exception thrown while running this audit. + string error_message = 8; + + // Extra information provided by some types of audits. Given as an arbitrary object. + google.protobuf.Struct details = 9; + + // List of warnings associated with this audit + // type of `Value` since this can be null + google.protobuf.Value warnings = 10; +} + +// Message containing the i18n data for the LHR - Version 1 +message I18n { + // Message holding the formatted strings used in the renderer + message RendererFormattedStrings { + // The disclaimer shown below a performance metric value + string variance_disclaimer = 1; + + // The heading for the estimated page load savings opportunity of an audit + string opportunity_resource_column_label = 2; + + // The heading for the estimated page load savings of opportunitu audits + string opportunity_savings_column_label = 3; + + // The error string shown next to an erroring audit + string error_missing_audit_info = 4; + + // The label shown next to an audit or metric that has had an error + string error_label = 5; + + // The label shown above a bulleted list of warnings + string warning_header = 6; + + // The tooltip text on an expandable chevron icon + string audit_group_expand_tooltip = 7; + + // The heading that is shown above a list of audits that are passing + string passed_audits_group_title = 8; + + // The heading shown above a list of audits that do not apply to a page + string not_applicable_audits_group_title = 9; + + // The heading shown above a list of audits that were not computerd in the run + string manual_audits_group_title = 10; + + // The label shown preceding important warnings that may have invalidated an entire report + string toplevel_warnings_message = 11; + + // The label that explains the score gauges scale (0-49, 50-89, 90-100) + string scorescale_label = 12; + + // The label for values shown in the summary of critical request chains + string crc_longest_duration_label = 13; + + // The label for the initial request in a critical request chain + string crc_initial_navigation = 14; + + // The disclaimer shown under performance explaning that the network can vary + string ls_performance_category_description = 15; + + // The title of the lab data performance category + string lab_data_title = 16; + } + + // The message holding all formatted strings + RendererFormattedStrings renderer_formatted_strings = 1; +} diff --git a/proto/sample_v2_round_trip.json b/proto/sample_v2_round_trip.json new file mode 100644 index 000000000000..e3c15bf4ee27 --- /dev/null +++ b/proto/sample_v2_round_trip.json @@ -0,0 +1,3310 @@ +{ + "audits": { + "accesskeys": { + "description": "Access keys let users quickly focus a part of the page. For proper navigation, each access key must be unique. [Learn more](https://dequeuniversity.com/rules/axe/2.2/accesskeys?application=lighthouse).", + "id": "accesskeys", + "score": null, + "scoreDisplayMode": "not_applicable", + "title": "`[accesskey]` values are unique" + }, + "appcache-manifest": { + "description": "Application Cache is deprecated. [Learn more](https://developers.google.com/web/tools/lighthouse/audits/appcache).", + "displayValue": "Found \"clock.appcache\"", + "id": "appcache-manifest", + "score": 0.0, + "scoreDisplayMode": "binary", + "title": "Uses Application Cache" + }, + "aria-allowed-attr": { + "description": "Each ARIA `role` supports a specific subset of `aria-*` attributes. Mismatching these invalidates the `aria-*` attributes. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-allowed-attr?application=lighthouse).", + "id": "aria-allowed-attr", + "score": null, + "scoreDisplayMode": "not_applicable", + "title": "`[aria-*]` attributes match their roles" + }, + "aria-required-attr": { + "description": "Some ARIA roles have required attributes that describe the state of the element to screen readers. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-required-attr?application=lighthouse).", + "id": "aria-required-attr", + "score": null, + "scoreDisplayMode": "not_applicable", + "title": "`[role]`s have all required `[aria-*]` attributes" + }, + "aria-required-children": { + "description": "Some ARIA parent roles must contain specific child roles to perform their intended accessibility functions. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-required-children?application=lighthouse).", + "id": "aria-required-children", + "score": null, + "scoreDisplayMode": "not_applicable", + "title": "Elements with `[role]` that require specific children `[role]`s, are present" + }, + "aria-required-parent": { + "description": "Some ARIA child roles must be contained by specific parent roles to properly perform their intended accessibility functions. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-required-parent?application=lighthouse).", + "id": "aria-required-parent", + "score": null, + "scoreDisplayMode": "not_applicable", + "title": "`[role]`s are contained by their required parent element" + }, + "aria-roles": { + "description": "ARIA roles must have valid values in order to perform their intended accessibility functions. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-roles?application=lighthouse).", + "id": "aria-roles", + "score": null, + "scoreDisplayMode": "not_applicable", + "title": "`[role]` values are valid" + }, + "aria-valid-attr": { + "description": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid names. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-valid-attr?application=lighthouse).", + "id": "aria-valid-attr", + "score": null, + "scoreDisplayMode": "not_applicable", + "title": "`[aria-*]` attributes are valid and not misspelled" + }, + "aria-valid-attr-value": { + "description": "Assistive technologies, like screen readers, can't interpret ARIA attributes with invalid values. [Learn more](https://dequeuniversity.com/rules/axe/2.2/aria-valid-attr-value?application=lighthouse).", + "id": "aria-valid-attr-value", + "score": null, + "scoreDisplayMode": "not_applicable", + "title": "`[aria-*]` attributes have valid values" + }, + "audio-caption": { + "description": "Captions make audio elements usable for deaf or hearing-impaired users, providing critical information such as who is talking, what they're saying, and other non-speech information. [Learn more](https://dequeuniversity.com/rules/axe/2.2/audio-caption?application=lighthouse).", + "id": "audio-caption", + "score": null, + "scoreDisplayMode": "not_applicable", + "title": "`