mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-03 10:31:10 +08:00
Override ShadowRoot.adoptedStyleSheets to handle cross-document cases.
This also migrate the `<devtools-icon>`, `<devtools-toolbar>` and `<devtools-button>` custom elements to use the new CSS approach (instead of the legacy approach), since it works now, even across multiple documents. Bug: 391381439 Change-Id: I48c629c6073579051082dd1ca9360bb7d9cc30e2 Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/6184803 Reviewed-by: Alex Rudenko <alexrudenko@chromium.org> Commit-Queue: Alex Rudenko <alexrudenko@chromium.org> Auto-Submit: Benedikt Meurer <bmeurer@chromium.org>
This commit is contained in:
committed by
Devtools-frontend LUCI CQ
parent
a48d7b9b0a
commit
fbedad605d
@@ -2193,7 +2193,7 @@ grd_files_debug_sources = [
|
||||
"front_end/ui/components/adorners/Adorner.js",
|
||||
"front_end/ui/components/adorners/adorner.css.js",
|
||||
"front_end/ui/components/buttons/Button.js",
|
||||
"front_end/ui/components/buttons/button.css.legacy.js",
|
||||
"front_end/ui/components/buttons/button.css.js",
|
||||
"front_end/ui/components/cards/Card.js",
|
||||
"front_end/ui/components/cards/card.css.js",
|
||||
"front_end/ui/components/chrome_link/ChromeLink.js",
|
||||
@@ -2231,7 +2231,7 @@ grd_files_debug_sources = [
|
||||
"front_end/ui/components/icon_button/Icon.js",
|
||||
"front_end/ui/components/icon_button/IconButton.js",
|
||||
"front_end/ui/components/icon_button/fileSourceIcon.css.js",
|
||||
"front_end/ui/components/icon_button/icon.css.legacy.js",
|
||||
"front_end/ui/components/icon_button/icon.css.js",
|
||||
"front_end/ui/components/icon_button/iconButton.css.js",
|
||||
"front_end/ui/components/input/checkbox.css.js",
|
||||
"front_end/ui/components/input/textInput.css.js",
|
||||
@@ -2502,7 +2502,7 @@ grd_files_debug_sources = [
|
||||
"front_end/ui/legacy/themeColors.css.legacy.js",
|
||||
"front_end/ui/legacy/theme_support/ThemeSupport.js",
|
||||
"front_end/ui/legacy/tokens.css.legacy.js",
|
||||
"front_end/ui/legacy/toolbar.css.legacy.js",
|
||||
"front_end/ui/legacy/toolbar.css.js",
|
||||
"front_end/ui/legacy/treeoutline.css.legacy.js",
|
||||
"front_end/ui/legacy/viewContainers.css.legacy.js",
|
||||
"front_end/ui/lit-html/i18n-template.js",
|
||||
|
||||
@@ -399,3 +399,64 @@ DOMTokenList.prototype['toggle'] = function(token: string, force: boolean|undefi
|
||||
return originalToggle.call(this, token, Boolean(force));
|
||||
};
|
||||
})();
|
||||
|
||||
// DevTools uses multiple documents when the main window is undocked, and the
|
||||
// device mode toolbar is enabled. Since `CSSStyleSheet` objects cannot be
|
||||
// shared across multiple documents, we override the `adoptedStyleSheets`
|
||||
// accessor here, and clone the `CSSStyleSheet` objects under hood on-demand.
|
||||
//
|
||||
// NOTE: Since `adoptedStyleSheets` is an `ObservableArray`, which can be
|
||||
// mutated in place, we need to override both the setter and the getter, and
|
||||
// for the latter, we need to return a proxy that intercepts mutations to the
|
||||
// array-indexed properties.
|
||||
const originalAdoptedStyleSheets = Object.getOwnPropertyDescriptor(ShadowRoot.prototype, 'adoptedStyleSheets');
|
||||
if (originalAdoptedStyleSheets) {
|
||||
const styleSheetInDocumentCache = new WeakMap<CSSStyleSheet, WeakMap<Document, CSSStyleSheet>>();
|
||||
|
||||
/**
|
||||
* Returns a clone of the `styleSheet` within the given `document`.
|
||||
*/
|
||||
function styleSheetInDocument(styleSheet: CSSStyleSheet, document: Document): CSSStyleSheet {
|
||||
let styleSheetCache = styleSheetInDocumentCache.get(styleSheet);
|
||||
if (styleSheetCache) {
|
||||
const cachedSheet = styleSheetCache.get(document);
|
||||
if (cachedSheet) {
|
||||
return cachedSheet;
|
||||
}
|
||||
} else {
|
||||
styleSheetCache = new WeakMap<Document, CSSStyleSheet>();
|
||||
styleSheetInDocumentCache.set(styleSheet, styleSheetCache);
|
||||
}
|
||||
|
||||
const clonedStyleSheet = new document.defaultView.CSSStyleSheet();
|
||||
for (const {cssText} of styleSheet.cssRules) {
|
||||
clonedStyleSheet.insertRule(cssText);
|
||||
}
|
||||
styleSheetCache.set(document, clonedStyleSheet);
|
||||
return clonedStyleSheet;
|
||||
}
|
||||
|
||||
Object.defineProperty(ShadowRoot.prototype, 'adoptedStyleSheets', {
|
||||
configurable: true,
|
||||
enumerable: true,
|
||||
get(this: ShadowRoot): CSSStyleSheet[] {
|
||||
const target = originalAdoptedStyleSheets.get.call(this);
|
||||
const {ownerDocument} = this.host;
|
||||
return new Proxy(target, {
|
||||
set(target, propertyKey, value, receiver) {
|
||||
if (propertyKey === String(propertyKey >>> 0) && ownerDocument !== document) {
|
||||
value = styleSheetInDocument(value, ownerDocument);
|
||||
}
|
||||
return Reflect.set(target, propertyKey, value, receiver);
|
||||
},
|
||||
});
|
||||
},
|
||||
set(this: ShadowRoot, styleSheets: CSSStyleSheet[]) {
|
||||
const {ownerDocument} = this.host;
|
||||
if (ownerDocument !== document) {
|
||||
styleSheets = styleSheets.map(styleSheet => styleSheetInDocument(styleSheet, ownerDocument));
|
||||
}
|
||||
originalAdoptedStyleSheets.set.call(this, styleSheets);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -10,8 +10,6 @@ import("../visibility.gni")
|
||||
|
||||
generate_css("legacy_css_files") {
|
||||
sources = [ "button.css" ]
|
||||
|
||||
legacy = true
|
||||
}
|
||||
|
||||
devtools_module("button") {
|
||||
|
||||
@@ -7,7 +7,7 @@ import '../icon_button/icon_button.js';
|
||||
import * as LitHtml from '../../lit-html/lit-html.js';
|
||||
import * as VisualLogging from '../../visual_logging/visual_logging.js';
|
||||
|
||||
import buttonStyles from './button.css.legacy.js';
|
||||
import buttonStyles from './button.css.js';
|
||||
|
||||
const {html, Directives: {ifDefined, ref, classMap}} = LitHtml;
|
||||
|
||||
@@ -122,14 +122,6 @@ export class Button extends HTMLElement {
|
||||
super();
|
||||
this.setAttribute('role', 'presentation');
|
||||
this.addEventListener('click', this.#boundOnClick, true);
|
||||
|
||||
// TODO(crbug.com/359141904): Ideally we would be using
|
||||
// adopted style sheets for installing css styles, but this
|
||||
// currently throws an error when sharing the styles across
|
||||
// multiple documents. This is a workaround.
|
||||
const styleElement = document.createElement('style');
|
||||
styleElement.textContent = buttonStyles.cssContent;
|
||||
this.#shadow.appendChild(styleElement);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -272,6 +264,7 @@ export class Button extends HTMLElement {
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
this.#shadow.adoptedStyleSheets = [buttonStyles];
|
||||
this.#render();
|
||||
}
|
||||
|
||||
|
||||
@@ -17,8 +17,6 @@ generate_css("css_files") {
|
||||
|
||||
generate_css("legacy_css_files") {
|
||||
sources = [ "icon.css" ]
|
||||
|
||||
legacy = true
|
||||
}
|
||||
|
||||
devtools_module("icon_button") {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import iconStyles from './icon.css.legacy.js';
|
||||
import iconStyles from './icon.css.js';
|
||||
|
||||
/**
|
||||
* @deprecated
|
||||
@@ -75,17 +75,10 @@ export class Icon extends HTMLElement {
|
||||
this.#icon = document.createElement('span');
|
||||
this.#shadowRoot = this.attachShadow({mode: 'open'});
|
||||
this.#shadowRoot.appendChild(this.#icon);
|
||||
}
|
||||
|
||||
// TODO(crbug.com/359141904): Ideally we'd have a `connectedCallback()` that would just
|
||||
// install the CSS via `adoptedStyleSheets`, but that throws when using the
|
||||
// same `CSSStyleSheet` across two different documents (which happens in the
|
||||
// case of undocked DevTools windows and using the DeviceMode). So the work-
|
||||
// around for now is to use legacy CSS injected as a <style> tag into the
|
||||
// ShadowRoot (which has been working well for the legacy UI components for
|
||||
// a long time).
|
||||
const styleElement = document.createElement('style');
|
||||
styleElement.textContent = iconStyles.cssContent;
|
||||
this.#shadowRoot.appendChild(styleElement);
|
||||
connectedCallback(): void {
|
||||
this.#shadowRoot.adoptedStyleSheets = [iconStyles];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -12,6 +12,7 @@ generate_css("css_files") {
|
||||
sources = [
|
||||
"emptyWidget.css",
|
||||
"inspectorCommon.css",
|
||||
"toolbar.css",
|
||||
]
|
||||
}
|
||||
|
||||
@@ -50,7 +51,6 @@ generate_css("legacy_css_files") {
|
||||
"textPrompt.css",
|
||||
"themeColors.css",
|
||||
"tokens.css",
|
||||
"toolbar.css",
|
||||
"treeoutline.css",
|
||||
"viewContainers.css",
|
||||
]
|
||||
|
||||
@@ -45,9 +45,9 @@ import {GlassPane, PointerEventsBehavior} from './GlassPane.js';
|
||||
import {bindCheckbox} from './SettingsUI.js';
|
||||
import type {Suggestion} from './SuggestBox.js';
|
||||
import {Events as TextPromptEvents, TextPrompt} from './TextPrompt.js';
|
||||
import toolbarStyles from './toolbar.css.legacy.js';
|
||||
import toolbarStyles from './toolbar.css.js';
|
||||
import {Tooltip} from './Tooltip.js';
|
||||
import {CheckboxLabel, createShadowRootWithCoreStyles, LongClickController} from './UIUtils.js';
|
||||
import {CheckboxLabel, LongClickController} from './UIUtils.js';
|
||||
|
||||
const UIStrings = {
|
||||
/**
|
||||
@@ -83,19 +83,21 @@ const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
|
||||
* @prop {boolean} wrappable - The `"wrappable"` attribute is reflected as property.
|
||||
*/
|
||||
export class Toolbar extends HTMLElement {
|
||||
#shadowRoot = this.attachShadow({mode: 'open'});
|
||||
private items: ToolbarItem[] = [];
|
||||
enabled: boolean = true;
|
||||
private compactLayout = false;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
createShadowRootWithCoreStyles(this, {cssFile: toolbarStyles}).createChild('slot');
|
||||
this.#shadowRoot.createChild('slot');
|
||||
}
|
||||
|
||||
connectedCallback(): void {
|
||||
if (!this.hasAttribute('role')) {
|
||||
this.setAttribute('role', 'toolbar');
|
||||
}
|
||||
this.#shadowRoot.adoptedStyleSheets = [toolbarStyles];
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user