Open Bug 1720116 Opened 4 years ago Updated 14 days ago

Handle ANSI color codes in console.log

Categories

(DevTools :: Console, enhancement, P3)

Firefox 91
enhancement

Tracking

(Not tracked)

People

(Reporter: fvsch, Unassigned)

Details

Attachments

(2 files)

I’m working on StackBlitz (stackblitz.com) which recently launched support for running npm packages using Node.js APIs in the browser, using the browser's JavaScript VM. This enables running web servers such as express, and build tools such as webpack, directly in the browser.

Some of those tools use popular npm packages such as chalk (one of the very top npm packages, accounting to 0,3% of all package downloads in my calculations) and colors to log colorized text to stdout. When running in Firefox, this results in text looking like this in the Console:

[31mthis is an error[39m
[33mthis is a warning[39m

and extreme cases can be completely unreadable:

[31mA[39m[37mm[39m[34me[39m[31mr[39m[37mi[39m[34mc[39m[31ma[39m[37m,[39m [31mH[39m[37me[39m[34mc[39m[31mk[39m [34mY[39m[31me[39m[37ma[39m[34mh[39m[31m![39m

It could be great if console.log input could either support ANSI color codes (which Chrome DevTools does), or alternatively strip them to show the raw text in a readable fashion.

I haven't seen other websites using ANSI color codes with console.log yet, so I can't say it's widespread.

For comparison, here's the result in Chrome DevTools.

Limited test case:

console.log(`
\u001B[31mThis is an error\u001B[39m
\u001B[33mThis is a warning\u001B[39m
`)

Works in Chrome, not in Firefox.


I have a more complex test case, shown above, which uses the chalk and colors npm pages.
https://stackblitz.com/edit/node-colors-test?file=README.md

It's a bit complex because it runs in StackBlitz WebContainer which doesn't officially support Firefox yet.
You can get it to run in Firefox (at least in Nightly) with:

  1. In about:config, set dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled to true.
  2. You probably need to set Enhanced Tracking Protection to “Standard” if it’s set to “Strict”.
  3. In the browser console, run localStorage.webcontainer_any_ua = 'true'.
  4. Reload the page.

thanks for filing Florens!

For what it's worth, Safari doesn't support this either, and I couldn't find anything about this in the spec (https://console.spec.whatwg.org/)

Chrome seems to treat it as a "style block" (%c), as console.log(%cHello \u001B[31mRED, "background-color: yellow") won't apply the background color (or any other property, say font-size) to "RED".

I filed an issue in the console spec repo: https://github.com/whatwg/console/issues/197

I'm currently working on improving support for ANSI color codes in Chromium DevTools. Relevant links:

Improvements shipped in Chrome DevTools: https://developer.chrome.com/blog/new-in-devtools-99/#console

Chrome supports ANSI color since 2018.
https://stackoverflow.com/a/52733695/11468937

Any plans about it in Firefox?

It would also be useful to have this when viewing content served as text/plain. CI logs tend to be served as such and can also contain color codes. Now one has to download the file and open it using less -R or a similar tool.

This add-on provides this functionality already: https://addons.mozilla.org/en-US/firefox/addon/retrotxt/

Still doesnt have colors in console?

It would be nice to have this feature.

Priority: -- → P3

(In reply to steve02081504 from comment #12)

its quiet easy to impl
https://cdn.jsdelivr.net/npm/ansi-up
https://github.com/steve02081504/virtual-console/blob/master/util.mjs

would you want to give this a try?
We should probably handle the ANSI color escape codes where we process args in the C++ (https://searchfox.org/firefox-main/rev/813224374ca2d9aa44770230bc40bd351bcbe6ea/dom/console/Console.cpp#1801-1819), to transform them into color/background-color into the styles string so we don't have to update the client

Flags: needinfo?(steve02081504)

(In reply to Nicolas Chevobbe [:nchevobbe] from comment #13)

(In reply to steve02081504 from comment #12)

its quiet easy to impl
https://cdn.jsdelivr.net/npm/ansi-up
https://github.com/steve02081504/virtual-console/blob/master/util.mjs

would you want to give this a try?
We should probably handle the ANSI color escape codes where we process args in the C++ (https://searchfox.org/firefox-main/rev/813224374ca2d9aa44770230bc40bd351bcbe6ea/dom/console/Console.cpp#1801-1819), to transform them into color/background-color into the styles string so we don't have to update the client

yeah ofcorce I can help for test but I dono how to PR or build for firefox...

Flags: needinfo?(steve02081504)
// 新函数:解析字符串中的 ANSI,转为多个文本片段 + 样式。
// 如果无 ANSI,直接追加原字符串作为单个元素。
static bool ProcessStringWithANSI(JSContext* aCx, const nsString& input,
                                  Sequence<JS::Value>& aSequence,
                                  Sequence<nsString>& aStyles) {
  if (input.IsEmpty() || input.FindChar('\x1B') == kNotFound) {
    // 无 ANSI,直接追加为 JS 字符串。
    JS::Rooted<JSString*> jsStr(aCx, JS_NewUCStringCopyN(aCx, input.get(), input.Length()));
    if (NS_WARN_IF(!jsStr)) {
      return false;
    }
    JS::Value val = JS::StringValue(jsStr);
    if (NS_WARN_IF(!aSequence.AppendElement(val, fallible))) {
      return false;
    }
    // 填充空样式。
    if (NS_WARN_IF(!aStyles.AppendElement(VoidString(), fallible))) {
      return false;
    }
    return true;
  }

  // 有 ANSI:使用状态机解析。
  nsString currentText;  // 当前文本片段。
  nsString currentStyle; // 当前 CSS 样式字符串。
  bool bold = false, italic = false, underline = false;
  nsString fgColor, bgColor;  // 为空表示默认。

  // 颜色映射(基本 16 色;可扩展到 256 色)。
  static const nsString kFgColors[8] = {u"black", u"red", u"green", u"yellow", u"blue", u"magenta", u"cyan", u"white"};
  static const nsString kBrightFgColors[8] = {u"gray", u"lightred", u"lightgreen", u"lightyellow", u"lightblue", u"lightmagenta", u"lightcyan", u"white"};  // 近似。

  nsString::const_iterator iter = input.begin();
  nsString::const_iterator end = input.end();

  auto FlushSegment = [&]() -> bool {
    if (!currentText.IsEmpty()) {
      JS::Rooted<JSString*> jsStr(aCx, JS_NewUCStringCopyN(aCx, currentText.get(), currentText.Length()));
      if (NS_WARN_IF(!jsStr)) {
        return false;
      }
      JS::Value val = JS::StringValue(jsStr);
      if (NS_WARN_IF(!aSequence.AppendElement(val, fallible))) {
        return false;
      }
      if (NS_WARN_IF(!aStyles.AppendElement(currentStyle, fallible))) {
        return false;
      }
      currentText.Truncate();
    }
    return true;
  };

  while (iter != end) {
    if (*iter == '\x1B' && iter + 1 != end && *(iter + 1) == '[') {  // ESC [
      // 刷新当前片段(如果有)。
      if (NS_WARN_IF(!FlushSegment())) {
        return false;
      }

      // 跳过 ESC [,解析参数。
      iter += 2;
      nsString params;
      while (iter != end && *iter != 'm') {
        params.Append(*iter);
        ++iter;
      }
      if (iter != end) ++iter;  // 跳过 'm'。

      // 解析参数(多个用 ; 分隔)。
      nsTArray<int32_t> codes;
      nsString paramStr = params;
      nsStringTokenizer tokenizer(paramStr, ';');
      while (tokenizer.HasMoreTokens()) {
        nsString token = tokenizer.NextToken();
        int32_t code = token.ToInteger(nullptr);  // 忽略错误。
        codes.AppendElement(code);
      }

      // 更新状态。
      for (int32_t code : codes) {
        switch (code) {
          case 0:  // 重置。
            bold = italic = underline = false;
            fgColor.Truncate();
            bgColor.Truncate();
            break;
          case 1: bold = true; break;
          case 3: italic = true; break;
          case 4: underline = true; break;
          case 30 ... 37: fgColor = kFgColors[code - 30]; break;
          case 40 ... 47: bgColor = kFgColors[code - 40]; break;
          case 90 ... 97: fgColor = kBrightFgColors[code - 90]; break;
          case 100 ... 107: bgColor = kBrightFgColors[code - 100]; break;
          // TODO: 支持 38/48 for 256/RGB (使用 rgb() ).
          default: break;  // 忽略未知。
        }
      }

      // 构建新 CSS 样式。
      currentStyle.Truncate();
      if (!fgColor.IsEmpty()) {
        currentStyle.AppendLiteral(u"color: ");
        currentStyle.Append(fgColor);
        currentStyle.AppendLiteral(u"; ");
      }
      if (!bgColor.IsEmpty()) {
        currentStyle.AppendLiteral(u"background-color: ");
        currentStyle.Append(bgColor);
        currentStyle.AppendLiteral(u"; ");
      }
      if (bold) currentStyle.AppendLiteral(u"font-weight: bold; ");
      if (italic) currentStyle.AppendLiteral(u"font-style: italic; ");
      if (underline) currentStyle.AppendLiteral(u"text-decoration: underline; ");
      continue;
    }

    // 普通字符,追加到当前文本。
    currentText.Append(*iter);
    ++iter;
  }

  // 刷新最后片段。
  if (NS_WARN_IF(!FlushSegment())) {
    return false;
  }

  // 填充多余样式为空(如果需要对齐)。
  while (aStyles.Length() < aSequence.Length()) {
    if (NS_WARN_IF(!aStyles.AppendElement(VoidString(), fallible))) {
      return false;
    }
  }
  return true;
}

not sure is this right

(In reply to steve02081504 from comment #14)

yeah ofcorce I can help for test but I dono how to PR or build for firefox...

Great! You can go through https://firefox-source-docs.mozilla.org/devtools/index.html to check how to setup the environment and see how you can ask for review on your patch

(In reply to Nicolas Chevobbe [:nchevobbe] from comment #16)

Great! You can go through https://firefox-source-docs.mozilla.org/devtools/index.html to check how to setup the environment and see how you can ask for review on your patch

I may have notime for this unfamiliar complex contribution process so thnaks but no?
free to use my code anyway :D

Guys, 2026, FF still cannot make past era ansi codes output to be colored in browser console? Even node js have it is built in now

You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: