diff --git a/front_end/panels/application/preloading/PreloadingView.ts b/front_end/panels/application/preloading/PreloadingView.ts
index e56142c72a5..feeab636150 100644
--- a/front_end/panels/application/preloading/PreloadingView.ts
+++ b/front_end/panels/application/preloading/PreloadingView.ts
@@ -2,13 +2,14 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-import type * as Common from '../../../core/common/common.js';
+import * as Common from '../../../core/common/common.js';
import * as i18n from '../../../core/i18n/i18n.js';
import * as Platform from '../../../core/platform/platform.js';
import {assertNotNullOrUndefined} from '../../../core/platform/platform.js';
import * as SDK from '../../../core/sdk/sdk.js';
import * as Protocol from '../../../generated/protocol.js';
import * as Bindings from '../../../models/bindings/bindings.js';
+import * as Buttons from '../../../ui/components/buttons/buttons.js';
import type * as DataGrid from '../../../ui/components/data_grid/data_grid.js';
import * as SplitView from '../../../ui/components/split_view/split_view.js';
// eslint-disable-next-line rulesdir/es_modules_import
@@ -72,6 +73,10 @@ const UIStrings = {
*@description Text in grid and details: Preloading failed.
*/
statusFailure: 'Failure',
+ /**
+ *@description Text to pretty print a file
+ */
+ prettyPrint: 'Pretty print',
};
const str_ = i18n.i18n.registerUIStrings('panels/application/preloading/PreloadingView.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
@@ -179,6 +184,8 @@ export class PreloadingRuleSetView extends UI.Widget.VBox {
private readonly ruleSetGrid = new PreloadingComponents.RuleSetGrid.RuleSetGrid();
private readonly ruleSetDetails = new PreloadingComponents.RuleSetDetailsView.RuleSetDetailsView();
+ private shouldPrettyPrint = Common.Settings.Settings.instance().moduleSetting('auto-pretty-print-minified').get();
+
constructor(model: SDK.PreloadingModel.PreloadingModel) {
super(/* isWebComponent */ true, /* delegatesFocus */ false);
@@ -209,9 +216,16 @@ export class PreloadingRuleSetView extends UI.Widget.VBox {
this.warningsView.show(this.warningsContainer);
this.ruleSetGrid.addEventListener('cellfocused', this.onRuleSetsGridCellFocused.bind(this));
+
+ const onPrettyPrintToggle = (): void => {
+ this.shouldPrettyPrint = !this.shouldPrettyPrint;
+ this.updateRuleSetDetails();
+ };
+
LitHtml.render(
LitHtml.html`
- <${SplitView.SplitView.SplitView.litTagName} .horizontal=${true} style="--min-sidebar-size: 0px">
+ <${SplitView.SplitView.SplitView.litTagName} .horizontal=${
+ true} style="--min-sidebar-size: max(100vh-200px, 0px)">
${this.ruleSetGrid}
@@ -219,7 +233,21 @@ export class PreloadingRuleSetView extends UI.Widget.VBox {
jslog=${VisualLogging.section('rule-set-details')}>
${this.ruleSetDetails}
- ${SplitView.SplitView.SplitView.litTagName}>`,
+ ${SplitView.SplitView.SplitView.litTagName}>
+
+ <${Buttons.Button.Button.litTagName}
+ .iconName=${'brackets'}
+ .toggledIconName=${'brackets'}
+ .toggled=${this.shouldPrettyPrint}
+ .toggleType=${Buttons.Button.ToggleType.PRIMARY}
+ .title=${i18nString(UIStrings.prettyPrint)}
+ .variant=${Buttons.Button.Variant.ICON_TOGGLE}
+ .size=${Buttons.Button.Size.SMALL}
+ @click=${onPrettyPrintToggle}
+ jslog=${VisualLogging.action().track({click: true}).context('preloading-status-panel-pretty-print')}>
+ ${Buttons.Button.Button.litTagName}>
+
+ `,
this.contentElement, {host: this});
this.hsplit = this.contentElement.querySelector('devtools-split-view') as SplitView.SplitView.SplitView;
}
@@ -249,6 +277,7 @@ export class PreloadingRuleSetView extends UI.Widget.VBox {
private updateRuleSetDetails(): void {
const id = this.focusedRuleSetId;
const ruleSet = id === null ? null : this.model.getRuleSetById(id);
+ this.ruleSetDetails.shouldPrettyPrint = this.shouldPrettyPrint;
this.ruleSetDetails.data = ruleSet;
if (ruleSet === null) {
diff --git a/front_end/panels/application/preloading/components/RuleSetDetailsView.test.ts b/front_end/panels/application/preloading/components/RuleSetDetailsView.test.ts
index e000e3b1826..85ff904906d 100644
--- a/front_end/panels/application/preloading/components/RuleSetDetailsView.test.ts
+++ b/front_end/panels/application/preloading/components/RuleSetDetailsView.test.ts
@@ -14,9 +14,11 @@ import * as PreloadingComponents from './components.js';
const coordinator = Coordinator.RenderCoordinator.RenderCoordinator.instance();
-async function renderRuleSetDetailsView(data: PreloadingComponents.RuleSetDetailsView.RuleSetDetailsViewData):
- Promise {
+async function renderRuleSetDetailsView(
+ data: PreloadingComponents.RuleSetDetailsView.RuleSetDetailsViewData,
+ shouldPrettyPrint: boolean): Promise {
const component = new PreloadingComponents.RuleSetDetailsView.RuleSetDetailsView();
+ component.shouldPrettyPrint = shouldPrettyPrint;
component.data = data;
renderElementIntoDOM(component);
assert.isNotNull(component.shadowRoot);
@@ -29,7 +31,8 @@ describeWithEnvironment('RuleSetDetailsView', () => {
it('renders nothing if not selected', async () => {
const data = null;
- const component = await renderRuleSetDetailsView(data);
+ const component = await renderRuleSetDetailsView(data, false);
+ await coordinator.done({waitForWork: true});
assert.isNotNull(component.shadowRoot);
assert.strictEqual(component.shadowRoot.textContent, '');
});
@@ -50,7 +53,8 @@ describeWithEnvironment('RuleSetDetailsView', () => {
`,
backendNodeId: 1 as Protocol.DOM.BackendNodeId,
};
- const component = await renderRuleSetDetailsView(data);
+ const component = await renderRuleSetDetailsView(data, false);
+ await coordinator.done({waitForWork: true});
assert.deepEqual(component.shadowRoot?.getElementById('error-message-text')?.textContent, undefined);
const textEditor = component.shadowRoot?.querySelector('devtools-text-editor') as TextEditor.TextEditor.TextEditor;
@@ -74,7 +78,8 @@ describeWithEnvironment('RuleSetDetailsView', () => {
url: 'https://example.com/speculationrules.json',
requestId: 'reqeustId' as Protocol.Network.RequestId,
};
- const component = await renderRuleSetDetailsView(data);
+ const component = await renderRuleSetDetailsView(data, false);
+ await coordinator.done({waitForWork: true});
assert.deepEqual(component.shadowRoot?.getElementById('error-message-text')?.textContent, undefined);
const textEditor = component.shadowRoot?.querySelector('devtools-text-editor') as TextEditor.TextEditor.TextEditor;
assert.strictEqual(textEditor.state.doc.toString(), data.sourceText);
@@ -94,7 +99,8 @@ describeWithEnvironment('RuleSetDetailsView', () => {
errorType: Protocol.Preload.RuleSetErrorType.SourceIsNotJsonObject,
errorMessage: 'Line: 6, column: 1, Syntax error.',
};
- const component = await renderRuleSetDetailsView(data);
+ const component = await renderRuleSetDetailsView(data, false);
+ await coordinator.done({waitForWork: true});
assert.deepEqual(
component.shadowRoot?.getElementById('error-message-text')?.textContent, 'Line: 6, column: 1, Syntax error.');
const textEditor = component.shadowRoot?.querySelector('devtools-text-editor') as TextEditor.TextEditor.TextEditor;
@@ -118,11 +124,38 @@ describeWithEnvironment('RuleSetDetailsView', () => {
errorType: Protocol.Preload.RuleSetErrorType.InvalidRulesSkipped,
errorMessage: 'A list rule must have a "urls" array.',
};
- const component = await renderRuleSetDetailsView(data);
+ const component = await renderRuleSetDetailsView(data, false);
+ await coordinator.done({waitForWork: true});
assert.deepEqual(
component.shadowRoot?.getElementById('error-message-text')?.textContent,
'A list rule must have a "urls" array.');
const textEditor = component.shadowRoot?.querySelector('devtools-text-editor') as TextEditor.TextEditor.TextEditor;
assert.strictEqual(textEditor.state.doc.toString(), data.sourceText);
});
+
+ it('renders formatted rule set', async () => {
+ const data: Protocol.Preload.RuleSet = {
+ id: 'ruleSetId:1' as Protocol.Preload.RuleSetId,
+ loaderId: 'loaderId:1' as Protocol.Network.LoaderId,
+ sourceText: '{"prefetch":[{"source": "list","urls": ["/subresource.js"]}]}',
+ backendNodeId: 1 as Protocol.DOM.BackendNodeId,
+ };
+ const component = await renderRuleSetDetailsView(data, true);
+ await coordinator.done({waitForWork: true});
+ assert.deepEqual(component.shadowRoot?.getElementById('error-message-text')?.textContent, undefined);
+
+ const textEditor = component.shadowRoot?.querySelector('devtools-text-editor') as TextEditor.TextEditor.TextEditor;
+ // Formatted sourceText should be different from the data.sourceText in this case.
+ assert.notEqual(textEditor.state.doc.toString(), data.sourceText);
+ assert.strictEqual(textEditor.state.doc.toString(), `{
+ "prefetch": [
+ {
+ "source": "list",
+ "urls": [
+ "/subresource.js"
+ ]
+ }
+ ]
+}`);
+ });
});
diff --git a/front_end/panels/application/preloading/components/RuleSetDetailsView.ts b/front_end/panels/application/preloading/components/RuleSetDetailsView.ts
index 087be71f72f..45ace2acac0 100644
--- a/front_end/panels/application/preloading/components/RuleSetDetailsView.ts
+++ b/front_end/panels/application/preloading/components/RuleSetDetailsView.ts
@@ -5,6 +5,7 @@
import {assertNotNullOrUndefined} from '../../../../core/platform/platform.js';
import * as SDK from '../../../../core/sdk/sdk.js';
import type * as Protocol from '../../../../generated/protocol.js';
+import * as Formatter from '../../../../models/formatter/formatter.js';
import * as CodeMirror from '../../../../third_party/codemirror.next/codemirror.next.js';
import * as CodeHighlighter from '../../../../ui/components/code_highlighter/code_highlighter.js';
import * as IconButton from '../../../../ui/components/icon_button/icon_button.js';
@@ -26,6 +27,7 @@ export type RuleSetDetailsViewData = RuleSet|null;
export class RuleSetDetailsView extends LegacyWrapper.LegacyWrapper.WrappableComponent {
readonly #shadow = this.attachShadow({mode: 'open'});
#data: RuleSetDetailsViewData = null;
+ #shouldPrettyPrint: boolean = true;
#editorState?: CodeMirror.EditorState;
connectedCallback(): void {
@@ -37,7 +39,13 @@ export class RuleSetDetailsView extends LegacyWrapper.LegacyWrapper.WrappableCom
void this.#render();
}
+ set shouldPrettyPrint(shouldPrettyPrint: boolean) {
+ this.#shouldPrettyPrint = shouldPrettyPrint;
+ }
+
async #render(): Promise {
+ const sourceText = await this.#getSourceText();
+
await coordinator.write('RuleSetDetailsView render', () => {
if (this.#data === null) {
LitHtml.render(LitHtml.nothing, this.#shadow, {host: this});
@@ -52,7 +60,7 @@ export class RuleSetDetailsView extends LegacyWrapper.LegacyWrapper.WrappableCom
${this.#maybeError()}
- ${this.#renderSource()}
+ ${this.#renderSource(sourceText)}
`, this.#shadow, {host: this});
// clang-format on
@@ -85,28 +93,37 @@ export class RuleSetDetailsView extends LegacyWrapper.LegacyWrapper.WrappableCom
// clang-format on
}
- #renderSource(): LitHtml.LitTemplate {
+ #renderSource(sourceText: string): LitHtml.LitTemplate {
this.#editorState = CodeMirror.EditorState.create({
- doc: this.#data?.sourceText,
+ doc: sourceText,
extensions: [
- TextEditor.Config.baseConfiguration(this.#data?.sourceText || ''),
+ TextEditor.Config.baseConfiguration(sourceText || ''),
CodeMirror.lineNumbers(),
CodeMirror.EditorState.readOnly.of(true),
codeMirrorJsonType as CodeMirror.Extension,
CodeMirror.syntaxHighlighting(CodeHighlighter.CodeHighlighter.highlightStyle),
],
});
-
// Disabled until https://crbug.com/1079231 is fixed.
// clang-format off
// TODO(https://crbug.com/1425354): Add Raw button.
- return LitHtml.html`
- <${TextEditor.TextEditor.TextEditor.litTagName} .style.flexGrow = '1' .state=${
- this.#editorState
- }>${TextEditor.TextEditor.TextEditor.litTagName}>
- `;
+ return LitHtml.html`
+ <${TextEditor.TextEditor.TextEditor.litTagName} .style.flexGrow = '1' .state=${
+ this.#editorState
+ }>${TextEditor.TextEditor.TextEditor.litTagName}>
+ `;
// clang-format on
}
+
+ async #getSourceText(): Promise {
+ if (this.#shouldPrettyPrint && this.#data?.sourceText !== undefined) {
+ const formattedResult =
+ await Formatter.ScriptFormatter.formatScriptContent('application/json', this.#data.sourceText);
+ return formattedResult.formattedContent;
+ }
+
+ return this.#data?.sourceText || '';
+ }
}
customElements.define('devtools-resources-rulesets-details-view', RuleSetDetailsView);