[Elements] Add inspector style sheet rules for view transition pseudos automatically

Drive by:
* Add expand recursively context menu item to pseudo elements with children (vt pseudos as an example)

Fixed: 1511233
Change-Id: Iad76e633c529aefac0868008a7a41cb73f594904
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/5119644
Reviewed-by: Philip Pfaffe <pfaffe@chromium.org>
Commit-Queue: Philip Pfaffe <pfaffe@chromium.org>
Auto-Submit: Ergün Erdoğmuş <ergunsh@chromium.org>
This commit is contained in:
Ergun Erdogmus
2023-12-14 09:31:38 +00:00
committed by Devtools-frontend LUCI CQ
parent 350c955309
commit 9c1948671d
9 changed files with 174 additions and 5 deletions
+14
View File
@@ -238,6 +238,20 @@ export class DOMNode {
return this.#nodeNameInternal === 'AUDIO' || this.#nodeNameInternal === 'VIDEO';
}
isViewTransitionPseudoNode(): boolean {
if (!this.#pseudoTypeInternal) {
return false;
}
return [
Protocol.DOM.PseudoType.ViewTransition,
Protocol.DOM.PseudoType.ViewTransitionGroup,
Protocol.DOM.PseudoType.ViewTransitionImagePair,
Protocol.DOM.PseudoType.ViewTransitionOld,
Protocol.DOM.PseudoType.ViewTransitionNew,
].includes(this.#pseudoTypeInternal);
}
creationStackTrace(): Promise<Protocol.Runtime.StackTrace|null> {
if (this.#creationStackTraceInternal) {
return this.#creationStackTraceInternal;
@@ -211,6 +211,13 @@ export class ElementsPanel extends UI.Panel.Panel implements UI.SearchableView.S
private notFirstInspectElement?: boolean;
sidebarPaneView?: UI.View.TabbedViewLocation;
private stylesViewToReveal?: UI.View.SimpleView;
private nodeInsertedTaskRunner = {
queue: Promise.resolve(),
run(task: () => Promise<void>):
void {
this.queue = this.queue.then(task);
},
};
private cssStyleTrackerByCSSModel: Map<SDK.CSSModel.CSSModel, SDK.CSSModel.CSSPropertyTracker>;
@@ -378,10 +385,39 @@ export class ElementsPanel extends UI.Panel.Panel implements UI.SearchableView.S
treeOutline.focus();
}
domModel.addEventListener(SDK.DOMModel.Events.DocumentUpdated, this.documentUpdatedEvent, this);
domModel.addEventListener(SDK.DOMModel.Events.NodeInserted, this.handleNodeInserted, this);
}
private handleNodeInserted(event: Common.EventTarget.EventTargetEvent<SDK.DOMModel.DOMNode>): void {
// Queue the task for the case when all the view transitions are added
// around the same time. Otherwise there is a race condition on
// accessing `cssText` of inspector stylesheet causing some rules
// to be not added.
this.nodeInsertedTaskRunner.run(async () => {
const node = event.data;
if (!node.isViewTransitionPseudoNode()) {
return;
}
const cssModel = node.domModel().cssModel();
const styleSheetHeader = await cssModel.requestViaInspectorStylesheet(node);
if (!styleSheetHeader) {
return;
}
const cssText = await cssModel.getStyleSheetText(styleSheetHeader.id);
// Do not add a rule for the view transition pseudo if there already is a rule for it.
if (cssText?.includes(`${node.simpleSelector()} {`)) {
return;
}
await cssModel.setStyleSheetText(styleSheetHeader.id, `${cssText}\n${node.simpleSelector()} {}`, false);
});
}
modelRemoved(domModel: SDK.DOMModel.DOMModel): void {
domModel.removeEventListener(SDK.DOMModel.Events.DocumentUpdated, this.documentUpdatedEvent, this);
domModel.removeEventListener(SDK.DOMModel.Events.NodeInserted, this.handleNodeInserted, this);
const treeOutline = ElementsTreeOutline.forDOMModel(domModel);
if (!treeOutline) {
return;
@@ -708,7 +708,21 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
}, {jslogContext: 'focus'});
}
populateScrollIntoView(contextMenu: UI.ContextMenu.ContextMenu): void {
populatePseudoElementContextMenu(contextMenu: UI.ContextMenu.ContextMenu): void {
if (this.childCount() !== 0) {
this.populateExpandRecursively(contextMenu);
}
this.populateScrollIntoView(contextMenu);
}
private populateExpandRecursively(contextMenu: UI.ContextMenu.ContextMenu): void {
contextMenu.viewSection().appendItem(
i18nString(UIStrings.expandRecursively), this.expandRecursively.bind(this),
{jslogContext: 'expandRecursively'});
}
private populateScrollIntoView(contextMenu: UI.ContextMenu.ContextMenu): void {
contextMenu.viewSection().appendItem(
i18nString(UIStrings.scrollIntoView), () => this.nodeInternal.scrollIntoView(),
{jslogContext: 'scrollIntoView'});
@@ -797,9 +811,7 @@ export class ElementsTreeElement extends UI.TreeOutline.TreeElement {
i18nString(UIStrings.deleteElement), this.remove.bind(this), {jslogContext: 'deleteElement'});
}
contextMenu.viewSection().appendItem(
i18nString(UIStrings.expandRecursively), this.expandRecursively.bind(this),
{jslogContext: 'expandRecursively'});
this.populateExpandRecursively(contextMenu);
contextMenu.viewSection().appendItem(
i18nString(UIStrings.collapseChildren), this.collapseChildren.bind(this), {jslogContext: 'collapseChildren'});
const deviceModeWrapperAction = new Emulation.DeviceModeWrapper.ActionDelegate();
@@ -938,7 +938,7 @@ export class ElementsTreeOutline extends
} else if (commentNode) {
treeElement.populateNodeContextMenu(contextMenu);
} else if (isPseudoElement) {
treeElement.populateScrollIntoView(contextMenu);
treeElement.populatePseudoElementContextMenu(contextMenu);
}
contextMenu.viewSection().appendItem(i18nString(UIStrings.adornerSettings), () => {
+1
View File
@@ -8,6 +8,7 @@ node_ts_library("elements") {
sources = [
"accessibility-pane_test.ts",
"accessibility-tree_test.ts",
"add-view-transition-pseudo-styles_test.ts",
"adornment_test.ts",
"at-property-sections_test.ts",
"classes-pane_test.ts",
@@ -0,0 +1,57 @@
// Copyright 2023 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {assert} from 'chai';
import {getBrowserAndPages} from '../../shared/helper.js';
import {describe, it} from '../../shared/mocha-extensions.js';
import {
expandSelectedNodeRecursively,
getDisplayedStyleRules,
goToResourceAndWaitForStyleSection,
waitForAndClickTreeElementWithPartialText,
waitForExactStyleRule,
} from '../helpers/elements-helpers.js';
describe('View transition pseudo styles on inspector stylesheet', async () => {
it('should add view transition pseudo styles on inspector stylesheet when a view transition pseudo is added',
async () => {
const {frontend, target} = getBrowserAndPages();
await goToResourceAndWaitForStyleSection('elements/view-transition.html');
await target.bringToFront();
await target.evaluate('startFirstViewTransition()');
await frontend.bringToFront();
await waitForAndClickTreeElementWithPartialText('::view-transition');
await waitForExactStyleRule('::view-transition');
await expandSelectedNodeRecursively();
await waitForAndClickTreeElementWithPartialText('::view-transition-old(root)');
await waitForExactStyleRule('::view-transition-old(root)');
});
it('should not add view transition pseudo styles if inspector stylesheet already has view transition pseudo styles',
async () => {
const {frontend, target} = getBrowserAndPages();
await goToResourceAndWaitForStyleSection('elements/view-transition.html');
await target.bringToFront();
await target.evaluate('startFirstViewTransition()');
await frontend.bringToFront();
await waitForAndClickTreeElementWithPartialText('::view-transition');
await waitForExactStyleRule('::view-transition');
await target.bringToFront();
await target.evaluate('startNextViewTransition()');
await frontend.bringToFront();
await waitForAndClickTreeElementWithPartialText('::view-transition');
await waitForExactStyleRule('::view-transition');
const displayedRules = await getDisplayedStyleRules();
assert.strictEqual(displayedRules.filter(rule => rule.selectorText === '::view-transition').length, 1);
});
});
+7
View File
@@ -391,6 +391,13 @@ export const assertGutterDecorationForDomNodeExists = async () => {
export const getStyleRuleSelector = (selector: string) => `[aria-label="${selector}, css selector"]`;
export const waitForExactStyleRule = async (expectedSelector: string) => {
await waitForFunction(async () => {
const rules = await getDisplayedStyleRules();
return rules.find(rule => rule.selectorText === expectedSelector);
});
};
export const waitForStyleRule = async (expectedSelector: string) => {
await waitForFunction(async () => {
const rules = await getDisplayedStyleRules();
+1
View File
@@ -64,5 +64,6 @@ copy_to_gen("elements") {
"stylesheets-with-various-sources.html",
"switch-panels-while-editing-as-html.html",
"target.html",
"view-transition.html",
]
}
@@ -0,0 +1,41 @@
<!--
Copyright 2020 The Chromium Authors. All rights reserved.
Use of this source code is governed by a BSD-style license that can be
found in the LICENSE file.
-->
<!DOCTYPE html>
<meta charset="utf-8">
<style>
#next {
display: none;
}
::view-transition-old(root) {
animation-duration: 10s;
}
</style>
<div id="first">Initial state</div>
<div id="next">Next state</div>
<script>
const first = document.querySelector('#first');
const next = document.querySelector('#next');
const start = document.querySelector('#start');
async function startFirstViewTransition() {
const vt = document.startViewTransition(() => {
first.style.display = "none";
next.style.display = "block";
});
await vt.ready;
}
async function startNextViewTransition() {
const vt = document.startViewTransition(() => {
first.style.display = "block";
next.style.display = "none";
});
await vt.ready;
}
</script>