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): any): 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.     ]: any): 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]: any): 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. }