Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: support more color formats for backgroundColor #31868

Merged
merged 10 commits into from Mar 21, 2022
28 changes: 26 additions & 2 deletions docs/api/browser-view.md
Expand Up @@ -70,5 +70,29 @@ The `bounds` of this BrowserView instance as `Object`.

#### `view.setBackgroundColor(color)` _Experimental_

* `color` string - Color in `#aarrggbb` or `#argb` form. The alpha channel is
codebytere marked this conversation as resolved.
Show resolved Hide resolved
optional.
* `color` string - Color in Hex, RGB, RGBA, HSL, HSLA or named CSS color format. The alpha channel is
codebytere marked this conversation as resolved.
Show resolved Hide resolved
optional for the hex type.

Examples of valid `color` values:

* Hex
* #fff (shorthand RGB)
* #ffff (shorthand ARGB)
* #ffffff (RGB)
* #ffffffff (ARGB)
codebytere marked this conversation as resolved.
Show resolved Hide resolved
* 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`
54 changes: 43 additions & 11 deletions docs/api/browser-window.md
Expand Up @@ -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:
Expand Down Expand Up @@ -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
codebytere marked this conversation as resolved.
Show resolved Hide resolved
`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.
Expand Down Expand Up @@ -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_

Expand Down Expand Up @@ -1041,8 +1072,9 @@ 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.
codebytere marked this conversation as resolved.
Show resolved Hide resolved

See [Setting `backgroundColor`](#setting-the-backgroundcolor-property).

#### `win.setContentBounds(bounds[, animate])`

Expand Down
4 changes: 2 additions & 2 deletions shell/browser/api/electron_api_base_window.cc
Expand Up @@ -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());
}

Expand Down
2 changes: 1 addition & 1 deletion shell/browser/api/electron_api_base_window.h
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions shell/browser/api/electron_api_browser_view.cc
Expand Up @@ -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));
}
}

Expand Down
4 changes: 2 additions & 2 deletions shell/browser/api/electron_api_browser_window.cc
Expand Up @@ -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) {
Expand All @@ -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));
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion shell/browser/api/electron_api_web_contents.cc
Expand Up @@ -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(
Expand Down
2 changes: 1 addition & 1 deletion shell/browser/native_window.cc
Expand Up @@ -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);
Expand Down
2 changes: 1 addition & 1 deletion shell/browser/ui/cocoa/electron_touch_bar.mm
Expand Up @@ -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);
}

Expand Down
2 changes: 1 addition & 1 deletion shell/browser/web_contents_preferences.cc
Expand Up @@ -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;
Expand Down
64 changes: 36 additions & 28 deletions shell/common/color_util.cc
Expand Up @@ -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) {
Expand Down
8 changes: 5 additions & 3 deletions shell/common/color_util.h
Expand Up @@ -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);
codebytere marked this conversation as resolved.
Show resolved Hide resolved

// 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
Expand Down
19 changes: 19 additions & 0 deletions spec-main/api-browser-window-spec.ts
Expand Up @@ -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()', () => {
Expand Down