From 5580575c0fdc38902482ee17cd1128b404572425 Mon Sep 17 00:00:00 2001 From: Shelley Vohr Date: Mon, 21 Mar 2022 18:35:54 +0100 Subject: [PATCH] feat: support more color formats for `backgroundColor` (#31868) --- docs/api/browser-view.md | 30 ++++++++- docs/api/browser-window.md | 56 ++++++++++++---- shell/browser/api/electron_api_base_window.cc | 4 +- shell/browser/api/electron_api_base_window.h | 2 +- .../browser/api/electron_api_browser_view.cc | 4 +- .../api/electron_api_browser_window.cc | 4 +- .../browser/api/electron_api_web_contents.cc | 2 +- shell/browser/native_window.cc | 2 +- shell/browser/ui/cocoa/electron_touch_bar.mm | 2 +- shell/browser/web_contents_preferences.cc | 2 +- shell/common/color_util.cc | 64 +++++++++++-------- shell/common/color_util.h | 8 ++- spec-main/api-browser-window-spec.ts | 19 ++++++ 13 files changed, 144 insertions(+), 55 deletions(-) diff --git a/docs/api/browser-view.md b/docs/api/browser-view.md index 783a54855c869..ed63f4a4881db 100644 --- a/docs/api/browser-view.md +++ b/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`. diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index 768d27284104e..5588d78562dbe 100644 --- a/docs/api/browser-window.md +++ b/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 the 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])` diff --git a/shell/browser/api/electron_api_base_window.cc b/shell/browser/api/electron_api_base_window.cc index 704fceeae40c3..3024eba2098ec 100644 --- a/shell/browser/api/electron_api_base_window.cc +++ b/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()); } diff --git a/shell/browser/api/electron_api_base_window.h b/shell/browser/api/electron_api_base_window.h index e70202e0e7c61..7651ddd0e653c 100644 --- a/shell/browser/api/electron_api_base_window.h +++ b/shell/browser/api/electron_api_base_window.h @@ -159,7 +159,7 @@ class BaseWindow : public gin_helper::TrackableObject, 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); diff --git a/shell/browser/api/electron_api_browser_view.cc b/shell/browser/api/electron_api_browser_view.cc index 6feadb81074a3..1a9735cef11e3 100644 --- a/shell/browser/api/electron_api_browser_view.cc +++ b/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)); } } diff --git a/shell/browser/api/electron_api_browser_window.cc b/shell/browser/api/electron_api_browser_window.cc index 5df4b474b244a..b0629e27c74d7 100644 --- a/shell/browser/api/electron_api_browser_window.cc +++ b/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)); } } } diff --git a/shell/browser/api/electron_api_web_contents.cc b/shell/browser/api/electron_api_web_contents.cc index f6d275e8f4c3b..58d3f7f016685 100644 --- a/shell/browser/api/electron_api_web_contents.cc +++ b/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( diff --git a/shell/browser/native_window.cc b/shell/browser/native_window.cc index 6e42c30304943..b62ce2f617055 100644 --- a/shell/browser/native_window.cc +++ b/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); diff --git a/shell/browser/ui/cocoa/electron_touch_bar.mm b/shell/browser/ui/cocoa/electron_touch_bar.mm index 6b3a1383dd0ed..af690bd79d851 100644 --- a/shell/browser/ui/cocoa/electron_touch_bar.mm +++ b/shell/browser/ui/cocoa/electron_touch_bar.mm @@ -339,7 +339,7 @@ - (bool)hasItemWithID:(const std::string&)item_id { } - (NSColor*)colorFromHexColorString:(const std::string&)colorString { - SkColor color = electron::ParseHexColor(colorString); + SkColor color = electron::ParseCSSColor(colorString); return skia::SkColorToDeviceNSColor(color); } diff --git a/shell/browser/web_contents_preferences.cc b/shell/browser/web_contents_preferences.cc index c7bdcbf362241..44030d1e6bfd0 100644 --- a/shell/browser/web_contents_preferences.cc +++ b/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; diff --git a/shell/common/color_util.cc b/shell/common/color_util.cc index 9e15bc618f394..afa4c7f7a40bc 100644 --- a/shell/common/color_util.cc +++ b/shell/common/color_util.cc @@ -4,46 +4,54 @@ #include "shell/common/color_util.h" +#include +#include #include -#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 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) { diff --git a/shell/common/color_util.h b/shell/common/color_util.h index 88b68cc398f9f..9bcceef2fbd3a 100644 --- a/shell/common/color_util.h +++ b/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 diff --git a/spec-main/api-browser-window-spec.ts b/spec-main/api-browser-window-spec.ts index 89ff468ec5202..1f6e88b5088dc 100644 --- a/spec-main/api-browser-window-spec.ts +++ b/spec-main/api-browser-window-spec.ts @@ -1076,6 +1076,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()', () => {