chrome-devtools-frontend 1.0.1567721 → 1.0.1568864

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.
@@ -6,9 +6,10 @@ import * as Common from '../../core/common/common.js';
6
6
  import * as Host from '../../core/host/host.js';
7
7
  import * as i18n from '../../core/i18n/i18n.js';
8
8
  import * as Root from '../../core/root/root.js';
9
+ import * as AiCodeGeneration from '../../models/ai_code_generation/ai_code_generation.js';
9
10
  import * as Snackbars from '../../ui/components/snackbars/snackbars.js';
10
11
  import * as UI from '../../ui/legacy/legacy.js';
11
- import {html, nothing, render} from '../../ui/lit/lit.js';
12
+ import {html, type LitTemplate, nothing, render} from '../../ui/lit/lit.js';
12
13
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
13
14
 
14
15
  import styles from './aiCodeCompletionTeaser.css.js';
@@ -61,17 +62,32 @@ const UIStringsNotTranslate = {
61
62
  */
62
63
  freDisclaimerTextAiWontAlwaysGetItRight: 'This feature uses AI and won’t always get it right',
63
64
  /**
64
- * @description Second disclaimer item text for the fre dialog.
65
+ * @description Code completion disclaimer item text for the fre dialog.
66
+ */
67
+ freDisclaimerTextAsYouType:
68
+ 'As you type, relevant data is being send to Google to generate code suggestions. Press Tab to accept.',
69
+ /**
70
+ * @description Code generation disclaimer item text for the fre dialog.
71
+ */
72
+ freDisclaimerDescribeCodeInComment:
73
+ 'In Console or Sources, describe the code you need in a comment, then press Ctrl+I to generate it.',
74
+ /**
75
+ * @description Code generation disclaimer item text for the fre dialog.
76
+ */
77
+ freDisclaimerDescribeCodeInCommentForMacOs:
78
+ 'In Console or Sources, describe the code you need in a comment, then press Cmd+I to generate it.',
79
+ /**
80
+ * @description Privacy disclaimer item text for the fre dialog.
65
81
  */
66
82
  freDisclaimerTextPrivacy:
67
83
  'To generate code suggestions, your console input, the history of your current console session, the currently inspected CSS, and the contents of the currently open file are shared with Google. This data may be seen by human reviewers to improve this feature.',
68
84
  /**
69
- * @description Second disclaimer item text for the fre dialog when enterprise logging is off.
85
+ * @description Privacy disclaimer item text for the fre dialog when enterprise logging is off.
70
86
  */
71
87
  freDisclaimerTextPrivacyNoLogging:
72
88
  'To generate code suggestions, your console input, the history of your current console session, the currently inspected CSS, and the contents of the currently open file are shared with Google. This data will not be used to improve Google’s AI models. Your organization may change these settings at any time.',
73
89
  /**
74
- * @description Third disclaimer item text for the fre dialog.
90
+ * @description Last disclaimer item text for the fre dialog.
75
91
  */
76
92
  freDisclaimerTextUseWithCaution: 'Use generated code snippets with caution',
77
93
  /**
@@ -192,15 +208,34 @@ export class AiCodeCompletionTeaser extends UI.Widget.Widget {
192
208
  }
193
209
  }
194
210
 
195
- onAction = async(event: Event): Promise<void> => {
196
- event.preventDefault();
197
- const result = await FreDialog.show({
198
- header: {iconName: 'smart-assistant', text: lockedString(UIStringsNotTranslate.freDisclaimerHeader)},
199
- reminderItems: [
200
- {
201
- iconName: 'psychiatry',
202
- content: lockedString(UIStringsNotTranslate.freDisclaimerTextAiWontAlwaysGetItRight),
203
- },
211
+ #createReminderItems(): Array<{
212
+ iconName: string,
213
+ content: Common.UIString.LocalizedString|LitTemplate,
214
+ }> {
215
+ const reminderItems: Array<{
216
+ iconName: string,
217
+ content: Common.UIString.LocalizedString | LitTemplate,
218
+ }> = [{
219
+ iconName: 'psychiatry',
220
+ content: lockedString(UIStringsNotTranslate.freDisclaimerTextAiWontAlwaysGetItRight),
221
+ }];
222
+
223
+ const devtoolsLocale = i18n.DevToolsLocale.DevToolsLocale.instance();
224
+ if (AiCodeGeneration.AiCodeGeneration.AiCodeGeneration.isAiCodeGenerationEnabled(devtoolsLocale.locale)) {
225
+ reminderItems.push(
226
+ {
227
+ iconName: 'code',
228
+ content: lockedString(UIStringsNotTranslate.freDisclaimerTextAsYouType),
229
+ },
230
+ {
231
+ iconName: 'text-analysis',
232
+ content: Host.Platform.isMac() ?
233
+ lockedString(UIStringsNotTranslate.freDisclaimerDescribeCodeInCommentForMacOs) :
234
+ lockedString(UIStringsNotTranslate.freDisclaimerDescribeCodeInComment),
235
+ });
236
+ }
237
+
238
+ reminderItems.push(
204
239
  {
205
240
  iconName: 'google',
206
241
  content: this.#noLogging ? lockedString(UIStringsNotTranslate.freDisclaimerTextPrivacyNoLogging) :
@@ -217,8 +252,16 @@ export class AiCodeCompletionTeaser extends UI.Widget.Widget {
217
252
  })}
218
253
  >${lockedString(UIStringsNotTranslate.freDisclaimerTextUseWithCaution)}</x-link>`,
219
254
  // clang-format on
220
- }
221
- ],
255
+ });
256
+ return reminderItems;
257
+ }
258
+
259
+ onAction = async(event: Event): Promise<void> => {
260
+ event.preventDefault();
261
+
262
+ const result = await FreDialog.show({
263
+ header: {iconName: 'smart-assistant', text: lockedString(UIStringsNotTranslate.freDisclaimerHeader)},
264
+ reminderItems: this.#createReminderItems(),
222
265
  onLearnMoreClick: () => {
223
266
  void UI.ViewManager.ViewManager.instance().showView('chrome-ai');
224
267
  },
@@ -2,7 +2,7 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
 
5
- import * as Common from '../../core/common/common.js';
5
+ import type * as Common from '../../core/common/common.js';
6
6
  import type * as SDK from '../../core/sdk/sdk.js';
7
7
  import * as UI from '../../ui/legacy/legacy.js';
8
8
 
@@ -10,16 +10,11 @@ import {type ComputedStyleModel, type CSSModelChangedEvent, Events} from './Comp
10
10
 
11
11
  export class ElementsSidebarPane extends UI.Widget.VBox {
12
12
  protected computedStyleModelInternal: ComputedStyleModel;
13
- private readonly updateThrottler: Common.Throttler.Throttler;
14
- private updateWhenVisible: boolean;
15
13
  constructor(computedStyleModel: ComputedStyleModel, delegatesFocus?: boolean) {
16
14
  super({useShadowDom: true, delegatesFocus, classes: ['flex-none']});
17
15
  this.computedStyleModelInternal = computedStyleModel;
18
16
  this.computedStyleModelInternal.addEventListener(Events.CSS_MODEL_CHANGED, this.onCSSModelChanged, this);
19
17
  this.computedStyleModelInternal.addEventListener(Events.COMPUTED_STYLE_CHANGED, this.onComputedStyleChanged, this);
20
-
21
- this.updateThrottler = new Common.Throttler.Throttler(100);
22
- this.updateWhenVisible = false;
23
18
  }
24
19
 
25
20
  node(): SDK.DOMModel.DOMNode|null {
@@ -34,29 +29,10 @@ export class ElementsSidebarPane extends UI.Widget.VBox {
34
29
  return this.computedStyleModelInternal;
35
30
  }
36
31
 
37
- async doUpdate(): Promise<void> {
32
+ override async performUpdate(): Promise<void> {
38
33
  return;
39
34
  }
40
35
 
41
- update(): void {
42
- this.updateWhenVisible = !this.isShowing();
43
- if (this.updateWhenVisible) {
44
- return;
45
- }
46
- void this.updateThrottler.schedule(innerUpdate.bind(this));
47
-
48
- function innerUpdate(this: ElementsSidebarPane): Promise<void> {
49
- return this.isShowing() ? this.doUpdate() : Promise.resolve();
50
- }
51
- }
52
-
53
- override wasShown(): void {
54
- super.wasShown();
55
- if (this.updateWhenVisible) {
56
- this.update();
57
- }
58
- }
59
-
60
36
  onCSSModelChanged(_event: Common.EventTarget.EventTargetEvent<CSSModelChangedEvent|null>): void {
61
37
  }
62
38
 
@@ -2,6 +2,7 @@
2
2
  // Use of this source code is governed by a BSD-style license that can be
3
3
  // found in the LICENSE file.
4
4
  /* eslint-disable @devtools/no-imperative-dom-api */
5
+ /* eslint-disable @devtools/no-lit-render-outside-of-view */
5
6
 
6
7
  /*
7
8
  * Copyright (C) 2007 Apple Inc. All rights reserved.
@@ -35,22 +36,21 @@ import * as Common from '../../core/common/common.js';
35
36
  import * as Platform from '../../core/platform/platform.js';
36
37
  import * as SDK from '../../core/sdk/sdk.js';
37
38
  import * as UI from '../../ui/legacy/legacy.js';
39
+ import {Directives, html, type LitTemplate, nothing, render} from '../../ui/lit/lit.js';
38
40
  import * as VisualLogging from '../../ui/visual_logging/visual_logging.js';
39
41
 
40
42
  import type {ComputedStyleModel} from './ComputedStyleModel.js';
41
43
  import {ElementsSidebarPane} from './ElementsSidebarPane.js';
42
44
  import metricsSidebarPaneStyles from './metricsSidebarPane.css.js';
43
45
 
46
+ const {live} = Directives;
47
+
44
48
  export class MetricsSidebarPane extends ElementsSidebarPane {
45
49
  originalPropertyData: SDK.CSSProperty.CSSProperty|null;
46
50
  previousPropertyDataCandidate: SDK.CSSProperty.CSSProperty|null;
47
51
  private inlineStyle: SDK.CSSStyleDeclaration.CSSStyleDeclaration|null;
48
52
  private highlightMode: string;
49
- private boxElements: Array<{
50
- element: HTMLElement,
51
- name: string,
52
- backgroundColor: string,
53
- }>;
53
+ private computedStyle: Map<string, string>|null;
54
54
  private isEditingMetrics?: boolean;
55
55
 
56
56
  constructor(computedStyleModel: ComputedStyleModel) {
@@ -61,15 +61,15 @@ export class MetricsSidebarPane extends ElementsSidebarPane {
61
61
  this.previousPropertyDataCandidate = null;
62
62
  this.inlineStyle = null;
63
63
  this.highlightMode = '';
64
- this.boxElements = [];
64
+ this.computedStyle = null;
65
65
  this.contentElement.setAttribute('jslog', `${VisualLogging.pane('styles-metrics')}`);
66
66
  }
67
67
 
68
- override doUpdate(): Promise<void> {
68
+ override async performUpdate(): Promise<void> {
69
69
  // "style" attribute might have changed. Update metrics unless they are being edited
70
70
  // (if a CSS property is added, a StyleSheetChanged event is dispatched).
71
71
  if (this.isEditingMetrics) {
72
- return Promise.resolve();
72
+ return await Promise.resolve();
73
73
  }
74
74
 
75
75
  // FIXME: avoid updates of a collapsed pane.
@@ -78,18 +78,20 @@ export class MetricsSidebarPane extends ElementsSidebarPane {
78
78
  if (!node || node.nodeType() !== Node.ELEMENT_NODE || !cssModel) {
79
79
  this.contentElement.removeChildren();
80
80
  this.element.classList.add('collapsed');
81
- return Promise.resolve();
81
+ return await Promise.resolve();
82
82
  }
83
83
 
84
84
  function callback(this: MetricsSidebarPane, style: Map<string, string>|null): void {
85
85
  if (!style || this.node() !== node) {
86
+ this.computedStyle = null;
86
87
  return;
87
88
  }
89
+ this.computedStyle = style;
88
90
  this.updateMetrics(style);
89
91
  }
90
92
 
91
93
  if (!node.id) {
92
- return Promise.resolve();
94
+ return await Promise.resolve();
93
95
  }
94
96
 
95
97
  const promises = [
@@ -100,11 +102,11 @@ export class MetricsSidebarPane extends ElementsSidebarPane {
100
102
  }
101
103
  }),
102
104
  ];
103
- return Promise.all(promises) as unknown as Promise<void>;
105
+ return await (Promise.all(promises) as unknown as Promise<void>);
104
106
  }
105
107
 
106
108
  override onCSSModelChanged(): void {
107
- this.update();
109
+ this.requestUpdate();
108
110
  }
109
111
 
110
112
  /**
@@ -153,47 +155,35 @@ export class MetricsSidebarPane extends ElementsSidebarPane {
153
155
  SDK.OverlayModel.OverlayModel.hideDOMNodeHighlight();
154
156
  }
155
157
 
156
- for (const {element, name, backgroundColor} of this.boxElements) {
157
- const shouldHighlight = !node || mode === 'all' || name === mode;
158
- element.style.backgroundColor = shouldHighlight ? backgroundColor : '';
159
- element.classList.toggle('highlighted', shouldHighlight);
158
+ if (this.computedStyle) {
159
+ this.updateMetrics(this.computedStyle, mode);
160
160
  }
161
161
  }
162
162
 
163
- private updateMetrics(style: Map<string, string>): void {
163
+ private updateMetrics(style: Map<string, string>, highlightedMode = 'all'): void {
164
164
  // Updating with computed style.
165
- const metricsElement = document.createElement('div');
166
- metricsElement.className = 'metrics';
167
165
  const self = this;
168
166
 
169
167
  function createBoxPartElement(
170
- this: MetricsSidebarPane, style: Map<string, string>, name: string, side: string,
171
- suffix: string): HTMLDivElement {
172
- const element = document.createElement('div');
173
- element.className = side;
174
-
168
+ this: MetricsSidebarPane, style: Map<string, string>, name: string, side: string, suffix: string): LitTemplate {
175
169
  const propertyName = (name !== 'position' ? name + '-' : '') + side + suffix;
176
170
  let value = style.get(propertyName);
177
- if (value === undefined) {
178
- return element;
179
- }
180
171
 
181
172
  if (value === '' || (name !== 'position' && value === 'unset')) {
182
173
  value = '\u2012';
183
174
  } else if (name === 'position' && value === 'auto') {
184
175
  value = '\u2012';
185
176
  }
186
- value = value.replace(/px$/, '');
187
- value = Platform.NumberUtilities.toFixedIfFloating(value);
188
-
189
- element.textContent = value;
190
- element.setAttribute('jslog', `${VisualLogging.value(propertyName).track({
191
- dblclick: true,
192
- keydown: 'Enter|Escape|ArrowUp|ArrowDown|PageUp|PageDown',
193
- change: true,
194
- })}`);
195
- element.addEventListener('dblclick', this.startEditing.bind(this, element, name, propertyName, style), false);
196
- return element;
177
+ value = value?.replace(/px$/, '');
178
+ value = value ? Platform.NumberUtilities.toFixedIfFloating(value) : value;
179
+ // clang-format off
180
+ return html`<div class=${side} jslog=${VisualLogging.value(propertyName).track({
181
+ dblclick: true, keydown: 'Enter|Escape|ArrowUp|ArrowDown|PageUp|PageDown', change: true,
182
+ })}
183
+ @dblclick=${(e: Event) => this.startEditing(e.currentTarget, name, propertyName, style)}
184
+ .innerText=${live(value ?? '')}>
185
+ </div>`;
186
+ // clang-format on
197
187
  }
198
188
 
199
189
  function getContentAreaWidthPx(style: Map<string, string>): string {
@@ -263,8 +253,7 @@ export class MetricsSidebarPane extends ElementsSidebarPane {
263
253
  Common.Color.Legacy.fromRGBA([0, 0, 0, 0]),
264
254
  ];
265
255
  const boxLabels = ['content', 'padding', 'border', 'margin', 'position'];
266
- let previousBox: HTMLDivElement|null = null;
267
- this.boxElements = [];
256
+ let previousBox: LitTemplate = nothing;
268
257
  for (let i = 0; i < boxes.length; ++i) {
269
258
  const name = boxes[i];
270
259
  const display = style.get('display');
@@ -282,76 +271,64 @@ export class MetricsSidebarPane extends ElementsSidebarPane {
282
271
  continue;
283
272
  }
284
273
 
285
- const boxElement = document.createElement('div');
286
- boxElement.className = `${name} highlighted`;
274
+ const node = this.node();
275
+ const shouldHighlight = !node || highlightedMode === 'all' || name === highlightedMode;
287
276
  const backgroundColor = boxColors[i].asString(Common.Color.Format.RGBA) || '';
288
- boxElement.style.backgroundColor = backgroundColor;
289
- boxElement.setAttribute('jslog', `${VisualLogging.metricsBox().context(name).track({hover: true})}`);
290
- boxElement.addEventListener(
291
- 'mouseover', this.highlightDOMNode.bind(this, true, name === 'position' ? 'all' : name), false);
292
- this.boxElements.push({element: boxElement, name, backgroundColor});
293
-
294
- if (name === 'content') {
295
- const widthElement = document.createElement('span');
296
- widthElement.textContent = getContentAreaWidthPx(style);
297
- widthElement.addEventListener(
298
- 'dblclick', this.startEditing.bind(this, widthElement, 'width', 'width', style), false);
299
- widthElement.setAttribute('jslog', `${VisualLogging.value('width').track({
300
- dblclick: true,
301
- keydown: 'Enter|Escape|ArrowUp|ArrowDown|PageUp|PageDown',
302
- change: true,
303
- })}`);
304
-
305
- const heightElement = document.createElement('span');
306
- heightElement.textContent = getContentAreaHeightPx(style);
307
- heightElement.addEventListener(
308
- 'dblclick', this.startEditing.bind(this, heightElement, 'height', 'height', style), false);
309
- heightElement.setAttribute('jslog', `${VisualLogging.value('height').track({
310
- dblclick: true,
311
- keydown: 'Enter|Escape|ArrowUp|ArrowDown|PageUp|PageDown',
312
- change: true,
313
- })}`);
314
-
315
- const timesElement = document.createElement('span');
316
- timesElement.textContent = ' × ';
317
-
318
- boxElement.appendChild(widthElement);
319
- boxElement.appendChild(timesElement);
320
- boxElement.appendChild(heightElement);
321
- } else {
322
- const suffix = (name === 'border' ? '-width' : '');
323
277
 
324
- const labelElement = document.createElement('div');
325
- labelElement.className = 'label';
326
- labelElement.textContent = boxLabels[i];
327
- boxElement.appendChild(labelElement);
328
-
329
- boxElement.appendChild(createBoxPartElement.call(this, style, name, 'top', suffix));
330
- boxElement.appendChild(document.createElement('br'));
331
- boxElement.appendChild(createBoxPartElement.call(this, style, name, 'left', suffix));
332
-
333
- if (previousBox) {
334
- boxElement.appendChild(previousBox);
335
- }
336
-
337
- boxElement.appendChild(createBoxPartElement.call(this, style, name, 'right', suffix));
338
- boxElement.appendChild(document.createElement('br'));
339
- boxElement.appendChild(createBoxPartElement.call(this, style, name, 'bottom', suffix));
340
- }
341
-
342
- previousBox = boxElement;
278
+ const suffix = (name === 'border' ? '-width' : '');
279
+ // clang-format off
280
+ const box: LitTemplate = html`
281
+ <div
282
+ class="${name} ${shouldHighlight ? 'highlighted' : ''}"
283
+ style="background-color: ${shouldHighlight ? backgroundColor : ''}"
284
+ jslog=${VisualLogging.metricsBox().context(name).track({hover: true})}
285
+ @mouseover=${this.highlightDOMNode.bind(this, true, name === 'position' ? 'all' : name)}>
286
+ ${name === 'content' ? html`
287
+ <span jslog=${VisualLogging.value('width').track({
288
+ dblclick: true,
289
+ keydown: 'Enter|Escape|ArrowUp|ArrowDown|PageUp|PageDown',
290
+ change: true,
291
+ })}
292
+ @dblclick=${(e: Event) => this.startEditing(e.currentTarget, 'width', 'width', style)}
293
+ .innerText=${live(getContentAreaWidthPx(style))}>
294
+ </span>
295
+ <span> × </span>
296
+ <span jslog=${VisualLogging.value('height').track({
297
+ dblclick: true,
298
+ keydown: 'Enter|Escape|ArrowUp|ArrowDown|PageUp|PageDown',
299
+ change: true,
300
+ })}
301
+ @dblclick=${(e: Event) => this.startEditing(e.currentTarget, 'height', 'height', style)}
302
+ .innerText=${live(getContentAreaHeightPx(style))}>
303
+ </span>` : html`
304
+ <div class="label">${boxLabels[i]}</div>
305
+ ${createBoxPartElement.call(this, style, name, 'top', suffix)}
306
+ <br>
307
+ ${createBoxPartElement.call(this, style, name, 'left', suffix)}
308
+ ${previousBox}
309
+ ${createBoxPartElement.call(this, style, name, 'right', suffix)}
310
+ <br>
311
+ ${createBoxPartElement.call(this, style, name, 'bottom', suffix)}`}
312
+ </div>`;
313
+ // clang-format on
314
+
315
+ previousBox = box;
343
316
  }
344
- if (previousBox) {
345
- metricsElement.appendChild(previousBox);
346
- }
347
- metricsElement.addEventListener('mouseover', this.highlightDOMNode.bind(this, false, 'all'), false);
348
- metricsElement.addEventListener('mouseleave', this.highlightDOMNode.bind(this, false, 'all'), false);
349
- this.contentElement.removeChildren();
350
- this.contentElement.appendChild(metricsElement);
317
+ // clang-format off
318
+ render(html`
319
+ <div class="metrics" @mouseover=${this.highlightDOMNode.bind(this, false, 'all')}
320
+ @mouseleave=${this.highlightDOMNode.bind(this, false, 'all')}>
321
+ ${previousBox}
322
+ </div>`, this.contentElement);
323
+ // clang-format on
351
324
  this.element.classList.remove('collapsed');
352
325
  }
353
326
 
354
- startEditing(targetElement: Element, box: string, styleProperty: string, computedStyle: Map<string, string>): void {
327
+ startEditing(target: EventTarget|null, box: string, styleProperty: string, computedStyle: Map<string, string>): void {
328
+ if (!(target instanceof Element)) {
329
+ return;
330
+ }
331
+ const targetElement = target as Element;
355
332
  if (UI.UIUtils.isBeingEdited(targetElement)) {
356
333
  return;
357
334
  }
@@ -429,7 +406,7 @@ export class MetricsSidebarPane extends ElementsSidebarPane {
429
406
  }
430
407
  }
431
408
  this.editingEnded(element, context);
432
- this.update();
409
+ this.requestUpdate();
433
410
  }
434
411
 
435
412
  private applyUserInput(
@@ -519,7 +496,7 @@ export class MetricsSidebarPane extends ElementsSidebarPane {
519
496
  }
520
497
 
521
498
  if (commitEditor) {
522
- this.update();
499
+ this.requestUpdate();
523
500
  }
524
501
  }
525
502
  }
@@ -208,7 +208,7 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
208
208
  super(computedStyleModel, true /* delegatesFocus */);
209
209
  this.setMinimumSize(96, 26);
210
210
  this.registerRequiredCSS(stylesSidebarPaneStyles);
211
- Common.Settings.Settings.instance().moduleSetting('text-editor-indent').addChangeListener(this.update.bind(this));
211
+ Common.Settings.Settings.instance().moduleSetting('text-editor-indent').addChangeListener(this.requestUpdate, this);
212
212
  this.toolbarPaneElement = this.createStylesSidebarToolbar();
213
213
  this.noMatchesElement = this.contentElement.createChild('div', 'gray-info-message hidden');
214
214
  this.noMatchesElement.textContent = i18nString(UIStrings.noMatchingSelectorOrStyle);
@@ -330,7 +330,7 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
330
330
  revealProperty(cssProperty: SDK.CSSProperty.CSSProperty): void {
331
331
  void this.decorator.highlightProperty(cssProperty);
332
332
  this.lastRevealedProperty = cssProperty;
333
- this.update();
333
+ this.requestUpdate();
334
334
  }
335
335
 
336
336
  jumpToProperty(propertyName: string, sectionName?: string, blockName?: string): boolean {
@@ -366,7 +366,7 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
366
366
  this.#swatchPopoverHelper.hide();
367
367
  this.#updateAbortController?.abort();
368
368
  this.resetCache();
369
- this.update();
369
+ this.requestUpdate();
370
370
  }
371
371
 
372
372
  private sectionsContainerKeyDown(event: Event): void {
@@ -534,7 +534,7 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
534
534
  this.nodeStylesUpdatedForTest(node, false);
535
535
  }
536
536
 
537
- override async doUpdate(): Promise<void> {
537
+ override async performUpdate(): Promise<void> {
538
538
  this.#updateAbortController?.abort();
539
539
  this.#updateAbortController = new AbortController();
540
540
  await this.#innerDoUpdate(this.#updateAbortController.signal);
@@ -745,7 +745,7 @@ export class StylesSidebarPane extends Common.ObjectWrapper.eventMixin<EventType
745
745
  }
746
746
 
747
747
  this.resetCache();
748
- this.update();
748
+ this.requestUpdate();
749
749
  }
750
750
 
751
751
  #scheduleResetUpdateIfNotEditing(): void {
@@ -108,6 +108,41 @@ export function rotateFlexDirectionIcon(direction: PhysicalDirection): IconInfo
108
108
  };
109
109
  }
110
110
 
111
+ /**
112
+ * Rotates the grid direction icon in such way that it indicates
113
+ * the desired `direction` and the arrow in the icon is always at the bottom
114
+ * or at the right.
115
+ *
116
+ * By default, the icon is pointing top-down with the arrow on the right-hand side.
117
+ */
118
+ export function rotateGridDirectionIcon(direction: PhysicalDirection): IconInfo {
119
+ // Default to LTR.
120
+ let flipX = true;
121
+ let flipY = false;
122
+ let rotate = -90;
123
+
124
+ if (direction === PhysicalDirection.RIGHT_TO_LEFT) {
125
+ rotate = 90;
126
+ flipY = false;
127
+ flipX = false;
128
+ } else if (direction === PhysicalDirection.TOP_TO_BOTTOM) {
129
+ rotate = 0;
130
+ flipX = false;
131
+ flipY = false;
132
+ } else if (direction === PhysicalDirection.BOTTOM_TO_TOP) {
133
+ rotate = 0;
134
+ flipX = false;
135
+ flipY = true;
136
+ }
137
+
138
+ return {
139
+ iconName: 'grid-direction',
140
+ rotate,
141
+ scaleX: flipX ? -1 : 1,
142
+ scaleY: flipY ? -1 : 1,
143
+ };
144
+ }
145
+
111
146
  export function rotateAlignContentIcon(iconName: string, direction: PhysicalDirection): IconInfo {
112
147
  return {
113
148
  iconName,
@@ -156,6 +191,14 @@ function flexDirectionIcon(value: string): (styles: ComputedStyles) => IconInfo
156
191
  return getIcon;
157
192
  }
158
193
 
194
+ function gridDirectionIcon(value: string): (styles: ComputedStyles) => IconInfo {
195
+ function getIcon(computedStyles: ComputedStyles): IconInfo {
196
+ const directions = getPhysicalDirections(computedStyles);
197
+ return rotateGridDirectionIcon(directions[value]);
198
+ }
199
+ return getIcon;
200
+ }
201
+
159
202
  function flexAlignContentIcon(iconName: string): (styles: ComputedStyles) => IconInfo {
160
203
  function getIcon(computedStyles: ComputedStyles): IconInfo {
161
204
  const directions = getPhysicalDirections(computedStyles);
@@ -178,7 +221,9 @@ function flexAlignContentIcon(iconName: string): (styles: ComputedStyles) => Ico
178
221
  function gridAlignContentIcon(iconName: string): (styles: ComputedStyles) => IconInfo {
179
222
  function getIcon(computedStyles: ComputedStyles): IconInfo {
180
223
  const directions = getPhysicalDirections(computedStyles);
181
- return rotateAlignContentIcon(iconName, directions.column);
224
+ const gridAutoFlow = computedStyles.get('grid-auto-flow') || 'row';
225
+ const direction = gridAutoFlow.includes('column') ? directions.row : directions.column;
226
+ return rotateAlignContentIcon(iconName, direction);
182
227
  }
183
228
  return getIcon;
184
229
  }
@@ -194,7 +239,9 @@ function flexJustifyContentIcon(iconName: string): (styles: ComputedStyles) => I
194
239
  function gridJustifyContentIcon(iconName: string): (styles: ComputedStyles) => IconInfo {
195
240
  function getIcon(computedStyles: ComputedStyles): IconInfo {
196
241
  const directions = getPhysicalDirections(computedStyles);
197
- return rotateJustifyContentIcon(iconName, directions.row);
242
+ const gridAutoFlow = computedStyles.get('grid-auto-flow') || 'row';
243
+ const direction = gridAutoFlow.includes('column') ? directions.column : directions.row;
244
+ return rotateJustifyContentIcon(iconName, direction);
198
245
  }
199
246
  return getIcon;
200
247
  }
@@ -202,7 +249,9 @@ function gridJustifyContentIcon(iconName: string): (styles: ComputedStyles) => I
202
249
  function gridJustifyItemsIcon(iconName: string): (styles: ComputedStyles) => IconInfo {
203
250
  function getIcon(computedStyles: ComputedStyles): IconInfo {
204
251
  const directions = getPhysicalDirections(computedStyles);
205
- return rotateJustifyItemsIcon(iconName, directions.row);
252
+ const gridAutoFlow = computedStyles.get('grid-auto-flow') || 'row';
253
+ const direction = gridAutoFlow.includes('column') ? directions.column : directions.row;
254
+ return rotateJustifyItemsIcon(iconName, direction);
206
255
  }
207
256
  return getIcon;
208
257
  }
@@ -229,7 +278,9 @@ function flexAlignItemsIcon(iconName: string): (styles: ComputedStyles) => IconI
229
278
  function gridAlignItemsIcon(iconName: string): (styles: ComputedStyles) => IconInfo {
230
279
  function getIcon(computedStyles: ComputedStyles): IconInfo {
231
280
  const directions = getPhysicalDirections(computedStyles);
232
- return rotateAlignItemsIcon(iconName, directions.column);
281
+ const gridAutoFlow = computedStyles.get('grid-auto-flow') || 'row';
282
+ const direction = gridAutoFlow.includes('column') ? directions.row : directions.column;
283
+ return rotateAlignItemsIcon(iconName, direction);
233
284
  }
234
285
  return getIcon;
235
286
  }
@@ -338,6 +389,8 @@ const flexItemIcons = new Map([
338
389
  ]);
339
390
 
340
391
  const gridContainerIcons = new Map([
392
+ ['grid-auto-flow: row', gridDirectionIcon('row')],
393
+ ['grid-auto-flow: column', gridDirectionIcon('column')],
341
394
  ['align-content: center', gridAlignContentIcon('align-content-center')],
342
395
  ['align-content: space-around', gridAlignContentIcon('align-content-space-around')],
343
396
  ['align-content: space-between', gridAlignContentIcon('align-content-space-between')],