main
js 130 lines 3.25 KB
Raw
1 /**
2 * Copyright (c) Meta Platforms, Inc. and affiliates.
3 *
4 * This source code is licensed under the MIT license found in the
5 * LICENSE file in the root directory of this source tree.
6 *
7 * @flow
8 */
9
10 import type {StyleXPlugin} from 'react-devtools-shared/src/frontend/types';
11 import isArray from 'react-devtools-shared/src/isArray';
12
13 const cachedStyleNameToValueMap: Map<string, string> = new Map();
14
15 export function getStyleXData(data: any): StyleXPlugin {
16 const sources = new Set<string>();
17 const resolvedStyles = {};
18
19 crawlData(data, sources, resolvedStyles);
20
21 return {
22 sources: Array.from(sources).sort(),
23 resolvedStyles,
24 };
25 }
26
27 export function crawlData(
28 data: any,
29 sources: Set<string>,
30 resolvedStyles: Object,
31 ): void {
32 if (data == null) {
33 return;
34 }
35
36 if (isArray(data)) {
37 data.forEach(entry => {
38 if (entry == null) {
39 return;
40 }
41
42 if (isArray(entry)) {
43 crawlData(entry, sources, resolvedStyles);
44 } else {
45 crawlObjectProperties(entry, sources, resolvedStyles);
46 }
47 });
48 } else {
49 crawlObjectProperties(data, sources, resolvedStyles);
50 }
51
52 resolvedStyles = Object.fromEntries<string, any>(
53 Object.entries(resolvedStyles).sort(),
54 );
55 }
56
57 function crawlObjectProperties(
58 entry: Object,
59 sources: Set<string>,
60 resolvedStyles: Object,
61 ): void {
62 const keys = Object.keys(entry);
63 keys.forEach(key => {
64 const value = entry[key];
65 if (typeof value === 'string') {
66 if (key === value) {
67 // Special case; this key is the name of the style's source/file/module.
68 sources.add(key);
69 } else {
70 const propertyValue = getPropertyValueForStyleName(value);
71 if (propertyValue != null) {
72 resolvedStyles[key] = propertyValue;
73 }
74 }
75 } else {
76 const nestedStyle = {};
77 resolvedStyles[key] = nestedStyle;
78 crawlData([value], sources, nestedStyle);
79 }
80 });
81 }
82
83 function getPropertyValueForStyleName(styleName: string): string | null {
84 if (cachedStyleNameToValueMap.has(styleName)) {
85 return cachedStyleNameToValueMap.get(styleName) as any as string;
86 }
87
88 for (
89 let styleSheetIndex = 0;
90 styleSheetIndex < document.styleSheets.length;
91 styleSheetIndex++
92 ) {
93 const styleSheet = document.styleSheets[
94 styleSheetIndex
95 ] as any as CSSStyleSheet;
96 let rules: CSSRuleList | null = null;
97 // this might throw if CORS rules are enforced https://www.w3.org/TR/cssom-1/#the-cssstylesheet-interface
98 try {
99 rules = styleSheet.cssRules;
100 } catch (_e) {
101 continue;
102 }
103
104 for (let ruleIndex = 0; ruleIndex < rules.length; ruleIndex++) {
105 if (!(rules[ruleIndex] instanceof CSSStyleRule)) {
106 continue;
107 }
108 const rule = rules[ruleIndex] as any as CSSStyleRule;
109 const {cssText, selectorText, style} = rule;
110
111 if (selectorText != null) {
112 if (selectorText.startsWith(`.${styleName}`)) {
113 const match = cssText.match(/{ *([a-z\-]+):/);
114 if (match !== null) {
115 const property = match[1];
116 const value = style.getPropertyValue(property);
117
118 cachedStyleNameToValueMap.set(styleName, value);
119
120 return value;
121 } else {
122 return null;
123 }
124 }
125 }
126 }
127 }
128
129 return null;
130 }