mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-16 03:25:20 +08:00
[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:
committed by
Devtools-frontend LUCI CQ
parent
350c955309
commit
9c1948671d
@@ -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), () => {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user