Browse Source

feat: support more color formats for `backgroundColor` (#33364)

* feat: support more color formats for backgroundColor

* feat: support more formats in getBackgroundColor

* chore: remove redundant includes

* refactor: differentiate rgb/rgba/hsl/hsla + docs

* chore: address review cleanup comments

* refactor: simple getBackgroundColor

* chore: fix iwyu

* fix: typescript arg

* Update docs/api/browser-view.md

Co-authored-by: Jeremy Rose <[email protected]>

* chore: address comments

Co-authored-by: Shelley Vohr <[email protected]>
Co-authored-by: Jeremy Rose <[email protected]>
trop[bot] 3 years ago
parent
commit
cee4e3707f

+ 28 - 2
docs/api/browser-view.md

@@ -70,5 +70,31 @@ The `bounds` of this BrowserView instance as `Object`.
 
 #### `view.setBackgroundColor(color)` _Experimental_
 
-* `color` string - Color in `#aarrggbb` or `#argb` form. The alpha channel is
-  optional.
+* `color` string - Color in Hex, RGB, ARGB, HSL, HSLA or named CSS color format. The alpha channel is
+  optional for the hex type.
+
+Examples of valid `color` values:
+
+* Hex
+  * #fff (RGB)
+  * #ffff (ARGB)
+  * #ffffff (RRGGBB)
+  * #ffffffff (AARRGGBB)
+* RGB
+  * rgb\(([\d]+),\s*([\d]+),\s*([\d]+)\)
+    * e.g. rgb(255, 255, 255)
+* RGBA
+  * rgba\(([\d]+),\s*([\d]+),\s*([\d]+),\s*([\d.]+)\)
+    * e.g. rgba(255, 255, 255, 1.0)
+* HSL
+  * hsl\((-?[\d.]+),\s*([\d.]+)%,\s*([\d.]+)%\)
+    * e.g. hsl(200, 20%, 50%)
+* HSLA
+  * hsla\((-?[\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)
+    * e.g. hsla(200, 20%, 50%, 0.5)
+* Color name
+  * Options are listed in [SkParseColor.cpp](https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/src/utils/SkParseColor.cpp;l=11-152;drc=eea4bf52cb0d55e2a39c828b017c80a5ee054148)
+  * Similar to CSS Color Module Level 3 keywords, but case-sensitive.
+    * e.g. `blueviolet` or `red`
+
+**Note:** Hex format with alpha takes `AARRGGBB` or `ARGB`, _not_ `RRGGBBA` or `RGA`.

+ 45 - 11
docs/api/browser-window.md

@@ -66,6 +66,18 @@ win.loadURL('https://github.com')
 Note that even for apps that use `ready-to-show` event, it is still recommended
 to set `backgroundColor` to make app feel more native.
 
+Some examples of valid `backgroundColor` values include:
+
+```js
+const win = new BrowserWindow()
+win.setBackgroundColor('hsl(230, 100%, 50%)')
+win.setBackgroundColor('rgb(255, 145, 145)')
+win.setBackgroundColor('#ff00a3')
+win.setBackgroundColor('blueviolet')
+```
+
+For more information about these color types see valid options in [win.setBackgroundColor](browser-window.md#winsetbackgroundcolorbackgroundcolor).
+
 ## Parent and child windows
 
 By using `parent` option, you can create child windows:
@@ -199,9 +211,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`.
   * `enableLargerThanScreen` boolean (optional) - Enable the window to be resized larger
     than screen. Only relevant for macOS, as other OSes allow
     larger-than-screen windows by default. Default is `false`.
-  * `backgroundColor` string (optional) - Window's background color as a hexadecimal value,
-    like `#66CD00` or `#FFF` or `#80FFFFFF` (alpha in #AARRGGBB format is supported if
-    `transparent` is set to `true`). Default is `#FFF` (white).
+  * `backgroundColor` string (optional) - The window's background color in Hex, RGB, RGBA, HSL, HSLA or named CSS color format. Alpha in #AARRGGBB format is supported if `transparent` is set to `true`. Default is `#FFF` (white). See [win.setBackgroundColor](browser-window.md#winsetbackgroundcolorbackgroundcolor) for more information.
   * `hasShadow` boolean (optional) - Whether window should have a shadow. Default is `true`.
   * `opacity` number (optional) - Set the initial opacity of the window, between 0.0 (fully
     transparent) and 1.0 (fully opaque). This is only implemented on Windows and macOS.
@@ -992,12 +1002,33 @@ APIs like `win.setSize`.
 
 #### `win.setBackgroundColor(backgroundColor)`
 
-* `backgroundColor` string - Window's background color as a hexadecimal value,
-  like `#66CD00` or `#FFF` or `#80FFFFFF` (alpha is supported if `transparent`
-  is `true`). Default is `#FFF` (white).
-
-Sets the background color of the window. See [Setting
-`backgroundColor`](#setting-the-backgroundcolor-property).
+* `backgroundColor` string - Color in Hex, RGB, RGBA, HSL, HSLA or named CSS color format. The alpha channel is optional for the hex type.
+
+Examples of valid `backgroundColor` values:
+
+* Hex
+  * #fff (shorthand RGB)
+  * #ffff (shorthand ARGB)
+  * #ffffff (RGB)
+  * #ffffffff (ARGB)
+* RGB
+  * rgb\(([\d]+),\s*([\d]+),\s*([\d]+)\)
+    * e.g. rgb(255, 255, 255)
+* RGBA
+  * rgba\(([\d]+),\s*([\d]+),\s*([\d]+),\s*([\d.]+)\)
+    * e.g. rgba(255, 255, 255, 1.0)
+* HSL
+  * hsl\((-?[\d.]+),\s*([\d.]+)%,\s*([\d.]+)%\)
+    * e.g. hsl(200, 20%, 50%)
+* HSLA
+  * hsla\((-?[\d.]+),\s*([\d.]+)%,\s*([\d.]+)%,\s*([\d.]+)\)
+    * e.g. hsla(200, 20%, 50%, 0.5)
+* Color name
+  * Options are listed in [SkParseColor.cpp](https://source.chromium.org/chromium/chromium/src/+/main:third_party/skia/src/utils/SkParseColor.cpp;l=11-152;drc=eea4bf52cb0d55e2a39c828b017c80a5ee054148)
+  * Similar to CSS Color Module Level 3 keywords, but case-sensitive.
+    * e.g. `blueviolet` or `red`
+
+Sets the background color of the window. See [Setting `backgroundColor`](#setting-the-backgroundcolor-property).
 
 #### `win.previewFile(path[, displayName])` _macOS_
 
@@ -1041,8 +1072,11 @@ Returns [`Rectangle`](structures/rectangle.md) - The `bounds` of the window as `
 
 #### `win.getBackgroundColor()`
 
-Returns `string` - Gets the background color of the window. See [Setting
-`backgroundColor`](#setting-the-backgroundcolor-property).
+Returns `string` - Gets the background color of the window in Hex (`#RRGGBB`) format.
+
+See [Setting `backgroundColor`](#setting-the-backgroundcolor-property).
+
+**Note:** The alpha value is _not_ returned alongside the red, green, and blue values.
 
 #### `win.setContentBounds(bounds[, animate])`
 

+ 2 - 2
shell/browser/api/electron_api_base_window.cc

@@ -643,11 +643,11 @@ bool BaseWindow::IsTabletMode() const {
 }
 
 void BaseWindow::SetBackgroundColor(const std::string& color_name) {
-  SkColor color = ParseHexColor(color_name);
+  SkColor color = ParseCSSColor(color_name);
   window_->SetBackgroundColor(color);
 }
 
-std::string BaseWindow::GetBackgroundColor() {
+std::string BaseWindow::GetBackgroundColor(gin_helper::Arguments* args) {
   return ToRGBHex(window_->GetBackgroundColor());
 }
 

+ 1 - 1
shell/browser/api/electron_api_base_window.h

@@ -159,7 +159,7 @@ class BaseWindow : public gin_helper::TrackableObject<BaseWindow>,
   bool IsKiosk();
   bool IsTabletMode() const;
   virtual void SetBackgroundColor(const std::string& color_name);
-  std::string GetBackgroundColor();
+  std::string GetBackgroundColor(gin_helper::Arguments* args);
   void SetHasShadow(bool has_shadow);
   bool HasShadow();
   void SetOpacity(const double opacity);

+ 2 - 2
shell/browser/api/electron_api_browser_view.cc

@@ -154,11 +154,11 @@ gfx::Rect BrowserView::GetBounds() {
 }
 
 void BrowserView::SetBackgroundColor(const std::string& color_name) {
-  view_->SetBackgroundColor(ParseHexColor(color_name));
+  view_->SetBackgroundColor(ParseCSSColor(color_name));
 
   if (web_contents()) {
     auto* wc = web_contents()->web_contents();
-    wc->SetPageBaseBackgroundColor(ParseHexColor(color_name));
+    wc->SetPageBaseBackgroundColor(ParseCSSColor(color_name));
   }
 }
 

+ 2 - 2
shell/browser/api/electron_api_browser_window.cc

@@ -370,7 +370,7 @@ void BrowserWindow::Blur() {
 
 void BrowserWindow::SetBackgroundColor(const std::string& color_name) {
   BaseWindow::SetBackgroundColor(color_name);
-  SkColor color = ParseHexColor(color_name);
+  SkColor color = ParseCSSColor(color_name);
   web_contents()->SetPageBaseBackgroundColor(color);
   auto* rwhv = web_contents()->GetRenderWidgetHostView();
   if (rwhv) {
@@ -384,7 +384,7 @@ void BrowserWindow::SetBackgroundColor(const std::string& color_name) {
     auto* web_preferences =
         WebContentsPreferences::From(api_web_contents_->web_contents());
     if (web_preferences) {
-      web_preferences->SetBackgroundColor(ParseHexColor(color_name));
+      web_preferences->SetBackgroundColor(ParseCSSColor(color_name));
     }
   }
 }

+ 1 - 1
shell/browser/api/electron_api_web_contents.cc

@@ -773,7 +773,7 @@ WebContents::WebContents(v8::Isolate* isolate,
     // and we then need to pull it back out and check it here.
     std::string background_color;
     options.GetHidden(options::kBackgroundColor, &background_color);
-    bool transparent = ParseHexColor(background_color) == SK_ColorTRANSPARENT;
+    bool transparent = ParseCSSColor(background_color) == SK_ColorTRANSPARENT;
 
     content::WebContents::CreateParams params(session->browser_context());
     auto* view = new OffScreenWebContentsView(

+ 1 - 1
shell/browser/native_window.cc

@@ -241,7 +241,7 @@ void NativeWindow::InitFromOptions(const gin_helper::Dictionary& options) {
 #endif
   std::string color;
   if (options.Get(options::kBackgroundColor, &color)) {
-    SetBackgroundColor(ParseHexColor(color));
+    SetBackgroundColor(ParseCSSColor(color));
   } else if (!transparent()) {
     // For normal window, use white as default background.
     SetBackgroundColor(SK_ColorWHITE);

+ 1 - 1
shell/browser/ui/cocoa/electron_touch_bar.mm

@@ -339,7 +339,7 @@ static NSString* const ImageScrubberItemIdentifier = @"scrubber.image.item";
 }
 
 - (NSColor*)colorFromHexColorString:(const std::string&)colorString {
-  SkColor color = electron::ParseHexColor(colorString);
+  SkColor color = electron::ParseCSSColor(colorString);
   return skia::SkColorToDeviceNSColor(color);
 }
 

+ 1 - 1
shell/browser/web_contents_preferences.cc

@@ -235,7 +235,7 @@ void WebContentsPreferences::Merge(
   }
   std::string background_color;
   if (web_preferences.GetHidden(options::kBackgroundColor, &background_color))
-    background_color_ = ParseHexColor(background_color);
+    background_color_ = ParseCSSColor(background_color);
   std::string safe_dialogs_message;
   if (web_preferences.Get("safeDialogsMessage", &safe_dialogs_message))
     safe_dialogs_message_ = safe_dialogs_message;

+ 36 - 28
shell/common/color_util.cc

@@ -4,46 +4,54 @@
 
 #include "shell/common/color_util.h"
 
+#include <cmath>
+#include <utility>
 #include <vector>
 
-#include "base/strings/string_number_conversions.h"
+#include "base/cxx17_backports.h"
 #include "base/strings/string_util.h"
 #include "base/strings/stringprintf.h"
+#include "content/public/common/color_parser.h"
+#include "ui/gfx/color_utils.h"
 
-namespace electron {
+namespace {
+
+bool IsHexFormat(const std::string& str) {
+  // Must be either #ARGB or #AARRGGBB.
+  bool is_hex_length = str.length() == 5 || str.length() == 9;
+  if (str[0] != '#' || !is_hex_length)
+    return false;
+
+  if (!std::all_of(str.begin() + 1, str.end(), ::isxdigit))
+    return false;
 
-SkColor ParseHexColor(const std::string& color_string) {
-  // Check the string for incorrect formatting.
-  if (color_string.empty() || color_string[0] != '#')
-    return SK_ColorWHITE;
+  return true;
+}
+
+}  // namespace
 
-  // Prepend FF if alpha channel is not specified.
-  std::string source = color_string.substr(1);
-  if (source.size() == 3)
-    source.insert(0, "F");
-  else if (source.size() == 6)
-    source.insert(0, "FF");
+namespace electron {
 
-  // Convert the string from #FFF format to #FFFFFF format.
-  std::string formatted_color;
-  if (source.size() == 4) {
-    for (size_t i = 0; i < 4; ++i) {
-      formatted_color += source[i];
-      formatted_color += source[i];
+SkColor ParseCSSColor(const std::string& color_string) {
+  // ParseCssColorString expects RGBA and we historically use ARGB
+  // so we need to convert before passing to ParseCssColorString.
+  std::string color_str = color_string;
+  if (IsHexFormat(color_str)) {
+    if (color_str.length() == 5) {
+      // #ARGB => #RGBA
+      std::swap(color_str[1], color_str[4]);
+    } else {
+      // #AARRGGBB => #RRGGBBAA
+      std::swap(color_str[1], color_str[7]);
+      std::swap(color_str[2], color_str[8]);
     }
-  } else if (source.size() == 8) {
-    formatted_color = source;
-  } else {
-    return SK_ColorWHITE;
   }
 
-  // Convert the string to an integer and make sure it is in the correct value
-  // range.
-  std::vector<uint8_t> bytes;
-  if (!base::HexStringToBytes(formatted_color, &bytes))
-    return SK_ColorWHITE;
+  SkColor color;
+  if (!content::ParseCssColorString(color_str, &color))
+    color = SK_ColorWHITE;
 
-  return SkColorSetARGB(bytes[0], bytes[1], bytes[2], bytes[3]);
+  return color;
 }
 
 std::string ToRGBHex(SkColor color) {

+ 5 - 3
shell/common/color_util.h

@@ -11,12 +11,14 @@
 
 namespace electron {
 
-// Parse hex color like "#FFF" or "#EFEFEF"
-SkColor ParseHexColor(const std::string& color_string);
+// Parses a CSS-style color string from hex, rgb(), rgba(),
+// hsl(), hsla(), or color name formats.
+SkColor ParseCSSColor(const std::string& color_string);
 
-// Convert color to RGB hex value like "#ABCDEF"
+// Convert color to RGB hex value like "#RRGGBB".
 std::string ToRGBHex(SkColor color);
 
+// Convert color to RGBA hex value like "#RRGGBBAA".
 std::string ToRGBAHex(SkColor color, bool include_hash = true);
 
 }  // namespace electron

+ 19 - 0
spec-main/api-browser-window-spec.ts

@@ -1075,6 +1075,25 @@ describe('BrowserWindow module', () => {
         w.setBackgroundColor(backgroundColor);
         expect(w.getBackgroundColor()).to.equal(backgroundColor);
       });
+      it('returns correct color with multiple passed formats', () => {
+        w.destroy();
+        w = new BrowserWindow({});
+
+        w.setBackgroundColor('#AABBFF');
+        expect(w.getBackgroundColor()).to.equal('#AABBFF');
+
+        w.setBackgroundColor('blueviolet');
+        expect(w.getBackgroundColor()).to.equal('#8A2BE2');
+
+        w.setBackgroundColor('rgb(255, 0, 185)');
+        expect(w.getBackgroundColor()).to.equal('#FF00B9');
+
+        w.setBackgroundColor('rgba(245, 40, 145, 0.8)');
+        expect(w.getBackgroundColor()).to.equal('#F52891');
+
+        w.setBackgroundColor('hsl(155, 100%, 50%)');
+        expect(w.getBackgroundColor()).to.equal('#00FF95');
+      });
     });
 
     describe('BrowserWindow.getNormalBounds()', () => {