Frontend Development 14 min read

Design and Implementation of CSS Visual Editing in the AUX Tool

This article explains how the internally developed AUX visual development tool extracts original CSS source metadata from the browser using native APIs, stylesheet inspection, and the Chrome DevTools Protocol, and how it processes that data to provide accurate, editable CSS code within a low‑invasion, code‑centric workflow.

ByteFE
ByteFE
ByteFE
Design and Implementation of CSS Visual Editing in the AUX Tool

There are two main categories of page‑building solutions: low‑code/no‑code platforms that target operations and product teams, and DSL‑ or code‑based platforms that embed visual capabilities directly into developers' workflows. AUX is a visual development tool created by the front‑end architecture team that belongs to the latter category.

AUX offers a weakly invasive visual interface that directly modifies source code, ensuring the generated code remains maintainable and extensible. Because it operates purely on code, a large part of its effort is devoted to reverse‑engineering runtime information back to compile‑time representations so that user actions in the browser can be faithfully reproduced as source code.

The primary goal of the CSS visual editor is to let developers edit CSS styles in the browser, see live rendering updates, and finally generate the corresponding CSS code that is written back to the project. The interaction flow is: (1) click an element to select it, (2) open the AUX CSS property panel showing the element's style information, (3) preview changes in real time, and (4) submit the changes to write them into the source files.

The first solution considered uses the native browser APIs window.getComputedStyle(element, [pseudoElt]) and HTMLElement.style to obtain computed styles and inline styles. While simple and accurate for computed values, this approach only yields the post‑calculation results, not the original CSS source.

The second solution inspects document.stylesheets to retrieve CSSStyleSheet objects and their cssRules . This provides access to the original rule text, but it is limited by CORS restrictions on third‑party stylesheets and cannot retrieve the browser's default (user‑agent) styles.

The final solution leverages the Chrome DevTools Protocol (CDP). CDP can control and inspect Chromium‑based browsers, exposing methods such as CSS.forcePseudoState and CSS.getMatchedStylesForNode . These APIs return a rich set of style information—including inline styles, attribute styles, matched CSS rules, pseudo‑element styles, and inherited styles—covering both author‑provided and default UA styles, thus overcoming the shortcomings of the previous approaches.

Implementation-wise, AUX adopts the CDP approach via a Chrome extension (instead of a server‑side CDP client) to avoid the need for remote debugging ports. The extension communicates with AUX through a bridge, fetching CSS metadata for a selected DOM node, processing it according to cascade rules, and feeding the refined data back to the editor.

The core of the style parsing is the CSS.getMatchedStylesForNode API. The following pseudo‑code illustrates how the returned payloads (inline, attribute, matched, and inherited styles) are merged into a cascade structure:

class CssStyle {
  constructor() {}
  // ...
  _buildCascade(inlinePayload, attributesPayload, matchedPayload, inheritedPayload) {
    const nodeCascades = [];
    const nodeStyles = [];
    // Inline styles have highest priority
    if (inlinePayload) {
      const style = new CSSStyleDeclaration(inlinePayload, Type.Inline);
      nodeStyles.push(style);
    }
    // Add matched rules in reverse order to respect CSS order
    let addedAttributesStyle;
    for (let i = matchedPayload.length - 1; i >= 0; --i) {
      const rule = new CSSStyleRule(matchedPayload[i].rule);
      if ((rule.isInjected() || rule.isUserAgent()) && !addedAttributesStyle) {
        addedAttributesStyle = true;
        addAttributesStyle.call(this);
      }
      nodeStyles.push(rule.style);
    }
    if (!addedAttributesStyle) addAttributesStyle.call(this);
    nodeCascades.push(new NodeCascade(this, nodeStyles, false));
    // Process inherited styles
    for (let i = 0; inheritedPayload && i < inheritedPayload.length; ++i) {
      const entry = inheritedPayload[i];
      const inheritedInlineStyle = entry.inlineStyle ? new CSSStyleDeclaration(entry.inlineStyle, Type.Inline) : null;
      if (inheritedInlineStyle && this._containsInherited(inheritedInlineStyle)) {
        inheritedStyles.push(inheritedInlineStyle);
      }
      const inheritedRules = entry.matchedCSSRules || [];
      for (let j = inheritedRules.length - 1; j >= 0; --j) {
        const inheritedRule = new CSSStyleRule(inheritedRules[j].rule);
        if (!this._containsInherited(inheritedRule.style)) continue;
        if (containsStyle(nodeStyles, inheritedRule.style) || containsStyle(this._inheritedStyles, inheritedRule.style)) continue;
        inheritedStyles.push(inheritedRule.style);
        this._inheritedStyles.add(inheritedRule.style);
      }
      nodeCascades.push(new NodeCascade(this, inheritedStyles, true));
    }
    return nodeCascades;
    function addAttributesStyle() {
      if (!attributesPayload) return;
      const style = new CSSStyleDeclaration(attributesPayload);
      nodeStyles.push(style);
    }
    function containsStyle(styles, query) {
      if (!query.styleSheetId || !query.range) return false;
      for (const style of styles) {
        if (query.styleSheetId === style.styleSheetId && style.range && query.range.equal(style.range)) {
          return true;
        }
      }
      return false;
    }
  }
}

This cascade processing yields a comprehensive style model that includes inline, attribute, and matched styles, enabling the editor to present accurate source‑level CSS to the developer.

A practical nuance arises when an element is hovered during selection; the hover pseudo‑class is active, making it difficult to retrieve the element's non‑hover style. The article leaves this problem as an open question for readers.

In summary, the AUX CSS visual editing module combines CDP‑based style extraction with careful cascade reconstruction to provide developers with precise, editable CSS source metadata, bridging the gap between runtime inspection and source‑code generation.

frontendweb developmentcssChrome DevTools ProtocolAUXVisual Editing
ByteFE
Written by

ByteFE

Cutting‑edge tech, article sharing, and practical insights from the ByteDance frontend team.

0 followers
Reader feedback

How this landed with the community

login Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.