From eddae27077fbe18b90c4e46ebcd4dc0315f90d3a Mon Sep 17 00:00:00 2001 From: "trop[bot]" <37223003+trop[bot]@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:04:39 +0100 Subject: [PATCH] fix: add theme data source for devtools. (#44635) * fix: add theme data source for devtools. Co-authored-by: Bill Shen * chore: add ut. Co-authored-by: bill.shen * chore: remove recording histograms Co-authored-by: Bill Shen * fix: add theme data source for devtools. Co-authored-by: Bill Shen * chore: separate files Co-authored-by: Bill Shen * chore: separate files Co-authored-by: Bill Shen * chore: remove pragma once Co-authored-by: Bill Shen * chore: fix lint issue. Co-authored-by: bill.shen --------- Co-authored-by: trop[bot] <37223003+trop[bot]@users.noreply.github.com> Co-authored-by: Bill Shen --- chromium_src/BUILD.gn | 1 + filenames.gni | 4 + shell/browser/ui/devtools_ui.cc | 114 +-------- shell/browser/ui/devtools_ui.h | 1 + .../ui/devtools_ui_bundle_data_source.cc | 136 +++++++++++ .../ui/devtools_ui_bundle_data_source.h | 40 ++++ .../ui/devtools_ui_theme_data_source.cc | 220 ++++++++++++++++++ .../ui/devtools_ui_theme_data_source.h | 36 +++ spec/chromium-spec.ts | 32 +++ 9 files changed, 474 insertions(+), 110 deletions(-) create mode 100644 shell/browser/ui/devtools_ui_bundle_data_source.cc create mode 100644 shell/browser/ui/devtools_ui_bundle_data_source.h create mode 100644 shell/browser/ui/devtools_ui_theme_data_source.cc create mode 100644 shell/browser/ui/devtools_ui_theme_data_source.h diff --git a/chromium_src/BUILD.gn b/chromium_src/BUILD.gn index 7bab23e5cb..8e6147c4bb 100644 --- a/chromium_src/BUILD.gn +++ b/chromium_src/BUILD.gn @@ -180,6 +180,7 @@ static_library("chrome") { public_deps = [ "//chrome/browser:dev_ui_browser_resources", "//chrome/browser/resources/accessibility:resources", + "//chrome/browser/ui/color:color_headers", "//chrome/browser/ui/color:mixers", "//chrome/common", "//chrome/common:version_header", diff --git a/filenames.gni b/filenames.gni index e5b7d73dd9..b0a9e33217 100644 --- a/filenames.gni +++ b/filenames.gni @@ -507,6 +507,10 @@ filenames = { "shell/browser/ui/devtools_manager_delegate.h", "shell/browser/ui/devtools_ui.cc", "shell/browser/ui/devtools_ui.h", + "shell/browser/ui/devtools_ui_bundle_data_source.cc", + "shell/browser/ui/devtools_ui_bundle_data_source.h", + "shell/browser/ui/devtools_ui_theme_data_source.cc", + "shell/browser/ui/devtools_ui_theme_data_source.h", "shell/browser/ui/drag_util.cc", "shell/browser/ui/drag_util.h", "shell/browser/ui/electron_menu_model.cc", diff --git a/shell/browser/ui/devtools_ui.cc b/shell/browser/ui/devtools_ui.cc index 5f8fe7c944..2cd42a5ec8 100644 --- a/shell/browser/ui/devtools_ui.cc +++ b/shell/browser/ui/devtools_ui.cc @@ -5,127 +5,21 @@ #include "shell/browser/ui/devtools_ui.h" #include -#include -#include -#include "base/memory/ref_counted_memory.h" -#include "base/strings/strcat.h" -#include "base/strings/string_util.h" -#include "chrome/common/webui_url_constants.h" -#include "content/public/browser/devtools_frontend_host.h" -#include "content/public/browser/url_data_source.h" -#include "content/public/browser/web_contents.h" #include "content/public/browser/web_ui.h" +#include "shell/browser/ui/devtools_ui_bundle_data_source.h" +#include "shell/browser/ui/devtools_ui_theme_data_source.h" namespace electron { -namespace { - -std::string PathWithoutParams(const std::string& path) { - return GURL(base::StrCat({content::kChromeDevToolsScheme, - url::kStandardSchemeSeparator, - chrome::kChromeUIDevToolsHost})) - .Resolve(path) - .path() - .substr(1); -} - -std::string GetMimeTypeForUrl(const GURL& url) { - std::string filename = url.ExtractFileName(); - if (base::EndsWith(filename, ".html", base::CompareCase::INSENSITIVE_ASCII)) { - return "text/html"; - } else if (base::EndsWith(filename, ".css", - base::CompareCase::INSENSITIVE_ASCII)) { - return "text/css"; - } else if (base::EndsWith(filename, ".js", - base::CompareCase::INSENSITIVE_ASCII) || - base::EndsWith(filename, ".mjs", - base::CompareCase::INSENSITIVE_ASCII)) { - return "application/javascript"; - } else if (base::EndsWith(filename, ".png", - base::CompareCase::INSENSITIVE_ASCII)) { - return "image/png"; - } else if (base::EndsWith(filename, ".map", - base::CompareCase::INSENSITIVE_ASCII)) { - return "application/json"; - } else if (base::EndsWith(filename, ".ts", - base::CompareCase::INSENSITIVE_ASCII)) { - return "application/x-typescript"; - } else if (base::EndsWith(filename, ".gif", - base::CompareCase::INSENSITIVE_ASCII)) { - return "image/gif"; - } else if (base::EndsWith(filename, ".svg", - base::CompareCase::INSENSITIVE_ASCII)) { - return "image/svg+xml"; - } else if (base::EndsWith(filename, ".manifest", - base::CompareCase::INSENSITIVE_ASCII)) { - return "text/cache-manifest"; - } - return "text/html"; -} - -class BundledDataSource : public content::URLDataSource { - public: - BundledDataSource() = default; - ~BundledDataSource() override = default; - - // disable copy - BundledDataSource(const BundledDataSource&) = delete; - BundledDataSource& operator=(const BundledDataSource&) = delete; - - // content::URLDataSource implementation. - std::string GetSource() override { return chrome::kChromeUIDevToolsHost; } - - void StartDataRequest(const GURL& url, - const content::WebContents::Getter& wc_getter, - GotDataCallback callback) override { - const std::string path = content::URLDataSource::URLToRequestPath(url); - // Serve request from local bundle. - std::string bundled_path_prefix(chrome::kChromeUIDevToolsBundledPath); - bundled_path_prefix += "/"; - if (base::StartsWith(path, bundled_path_prefix, - base::CompareCase::INSENSITIVE_ASCII)) { - StartBundledDataRequest(path.substr(bundled_path_prefix.length()), - std::move(callback)); - return; - } - - // We do not handle remote and custom requests. - std::move(callback).Run(nullptr); - } - - std::string GetMimeType(const GURL& url) override { - return GetMimeTypeForUrl(url); - } - - bool ShouldAddContentSecurityPolicy() override { return false; } - - bool ShouldDenyXFrameOptions() override { return false; } - - bool ShouldServeMimeTypeAsContentTypeHeader() override { return true; } - - void StartBundledDataRequest(const std::string& path, - GotDataCallback callback) { - std::string filename = PathWithoutParams(path); - scoped_refptr bytes = - content::DevToolsFrontendHost::GetFrontendResourceBytes(filename); - - DLOG_IF(WARNING, !bytes) - << "Unable to find dev tool resource: " << filename - << ". If you compiled with debug_devtools=1, try running with " - "--debug-devtools."; - std::move(callback).Run(bytes); - } -}; - -} // namespace - DevToolsUI::DevToolsUI(content::BrowserContext* browser_context, content::WebUI* web_ui) : WebUIController(web_ui) { web_ui->SetBindings(content::BindingsPolicySet()); content::URLDataSource::Add(browser_context, std::make_unique()); + content::URLDataSource::Add(browser_context, + std::make_unique()); } } // namespace electron diff --git a/shell/browser/ui/devtools_ui.h b/shell/browser/ui/devtools_ui.h index 190499b3b3..c34815a167 100644 --- a/shell/browser/ui/devtools_ui.h +++ b/shell/browser/ui/devtools_ui.h @@ -14,6 +14,7 @@ class DevToolsUI : public content::WebUIController { public: explicit DevToolsUI(content::BrowserContext* browser_context, content::WebUI* web_ui); + ~DevToolsUI() override = default; // disable copy DevToolsUI(const DevToolsUI&) = delete; diff --git a/shell/browser/ui/devtools_ui_bundle_data_source.cc b/shell/browser/ui/devtools_ui_bundle_data_source.cc new file mode 100644 index 0000000000..9f649f98f1 --- /dev/null +++ b/shell/browser/ui/devtools_ui_bundle_data_source.cc @@ -0,0 +1,136 @@ +// Copyright (c) 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/browser/ui/devtools_ui_bundle_data_source.h" + +#include +#include +#include + +#include "base/memory/ref_counted_memory.h" +#include "base/metrics/histogram_functions.h" +#include "base/strings/strcat.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "chrome/browser/ui/color/chrome_color_id.h" +#include "chrome/browser/ui/color/chrome_color_provider_utils.h" +#include "chrome/common/webui_url_constants.h" +#include "content/public/browser/devtools_frontend_host.h" +#include "content/public/browser/url_data_source.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_ui.h" +#include "net/base/url_util.h" +#include "ui/base/webui/web_ui_util.h" +#include "ui/color/color_provider.h" +#include "ui/color/color_provider_utils.h" + +namespace electron { +namespace { +std::string PathWithoutParams(const std::string& path) { + return GURL(base::StrCat({content::kChromeDevToolsScheme, + url::kStandardSchemeSeparator, + chrome::kChromeUIDevToolsHost})) + .Resolve(path) + .path() + .substr(1); +} + +scoped_refptr CreateNotFoundResponse() { + const char kHttpNotFound[] = "HTTP/1.1 404 Not Found\n\n"; + return base::MakeRefCounted( + base::byte_span_from_cstring(kHttpNotFound)); +} + +std::string GetMimeTypeForUrl(const GURL& url) { + std::string filename = url.ExtractFileName(); + if (base::EndsWith(filename, ".html", base::CompareCase::INSENSITIVE_ASCII)) { + return "text/html"; + } else if (base::EndsWith(filename, ".css", + base::CompareCase::INSENSITIVE_ASCII)) { + return "text/css"; + } else if (base::EndsWith(filename, ".js", + base::CompareCase::INSENSITIVE_ASCII) || + base::EndsWith(filename, ".mjs", + base::CompareCase::INSENSITIVE_ASCII)) { + return "application/javascript"; + } else if (base::EndsWith(filename, ".png", + base::CompareCase::INSENSITIVE_ASCII)) { + return "image/png"; + } else if (base::EndsWith(filename, ".map", + base::CompareCase::INSENSITIVE_ASCII)) { + return "application/json"; + } else if (base::EndsWith(filename, ".ts", + base::CompareCase::INSENSITIVE_ASCII)) { + return "application/x-typescript"; + } else if (base::EndsWith(filename, ".gif", + base::CompareCase::INSENSITIVE_ASCII)) { + return "image/gif"; + } else if (base::EndsWith(filename, ".svg", + base::CompareCase::INSENSITIVE_ASCII)) { + return "image/svg+xml"; + } else if (base::EndsWith(filename, ".manifest", + base::CompareCase::INSENSITIVE_ASCII)) { + return "text/cache-manifest"; + } + return "text/html"; +} +} // namespace + +#pragma mark - BundledDataSource + +std::string BundledDataSource::GetSource() { + // content::URLDataSource implementation. + return chrome::kChromeUIDevToolsHost; +} + +void BundledDataSource::StartDataRequest( + const GURL& url, + const content::WebContents::Getter& wc_getter, + GotDataCallback callback) { + const std::string path = content::URLDataSource::URLToRequestPath(url); + // Serve request from local bundle. + std::string bundled_path_prefix(chrome::kChromeUIDevToolsBundledPath); + bundled_path_prefix += "/"; + if (base::StartsWith(path, bundled_path_prefix, + base::CompareCase::INSENSITIVE_ASCII)) { + StartBundledDataRequest(path.substr(bundled_path_prefix.length()), + std::move(callback)); + return; + } + + // We do not handle remote and custom requests. + std::move(callback).Run(CreateNotFoundResponse()); +} + +std::string BundledDataSource::GetMimeType(const GURL& url) { + return GetMimeTypeForUrl(url); +} + +bool BundledDataSource::ShouldAddContentSecurityPolicy() { + return false; +} + +bool BundledDataSource::ShouldDenyXFrameOptions() { + return false; +} + +bool BundledDataSource::ShouldServeMimeTypeAsContentTypeHeader() { + return true; +} + +void BundledDataSource::StartBundledDataRequest(const std::string& path, + GotDataCallback callback) { + std::string filename = PathWithoutParams(path); + scoped_refptr bytes = + content::DevToolsFrontendHost::GetFrontendResourceBytes(filename); + + DLOG_IF(WARNING, !bytes) + << "Unable to find dev tool resource: " << filename + << ". If you compiled with debug_devtools=1, try running with " + "--debug-devtools."; + std::move(callback).Run(bytes); +} + +} // namespace electron diff --git a/shell/browser/ui/devtools_ui_bundle_data_source.h b/shell/browser/ui/devtools_ui_bundle_data_source.h new file mode 100644 index 0000000000..690c79948a --- /dev/null +++ b/shell/browser/ui/devtools_ui_bundle_data_source.h @@ -0,0 +1,40 @@ +// Copyright (c) 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ELECTRON_SHELL_BROWSER_UI_DEVTOOLS_UI_BUNDLE_DATA_SOURCE_H_ +#define ELECTRON_SHELL_BROWSER_UI_DEVTOOLS_UI_BUNDLE_DATA_SOURCE_H_ + +#include "content/public/browser/url_data_source.h" + +namespace electron { +// A BundledDataSource implementation that handles devtools://devtools/ +// requests. +class BundledDataSource : public content::URLDataSource { + public: + BundledDataSource() = default; + ~BundledDataSource() override = default; + + // disable copy + BundledDataSource(const BundledDataSource&) = delete; + BundledDataSource& operator=(const BundledDataSource&) = delete; + + std::string GetSource() override; + + void StartDataRequest(const GURL& url, + const content::WebContents::Getter& wc_getter, + GotDataCallback callback) override; + + private: + std::string GetMimeType(const GURL& url) override; + + bool ShouldAddContentSecurityPolicy() override; + bool ShouldDenyXFrameOptions() override; + bool ShouldServeMimeTypeAsContentTypeHeader() override; + + void StartBundledDataRequest(const std::string& path, + GotDataCallback callback); +}; +} // namespace electron + +#endif // ELECTRON_SHELL_BROWSER_UI_DEVTOOLS_UI_BUNDLE_DATA_SOURCE_H_ diff --git a/shell/browser/ui/devtools_ui_theme_data_source.cc b/shell/browser/ui/devtools_ui_theme_data_source.cc new file mode 100644 index 0000000000..ac4e82b5d1 --- /dev/null +++ b/shell/browser/ui/devtools_ui_theme_data_source.cc @@ -0,0 +1,220 @@ +// Copyright (c) 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#include "shell/browser/ui/devtools_ui_theme_data_source.h" + +#include +#include +#include + +#include "base/memory/ref_counted_memory.h" +#include "base/metrics/histogram_functions.h" +#include "base/strings/strcat.h" +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_split.h" +#include "base/strings/string_util.h" +#include "chrome/browser/ui/color/chrome_color_id.h" +#include "chrome/browser/ui/color/chrome_color_provider_utils.h" +#include "chrome/common/webui_url_constants.h" +#include "content/public/browser/devtools_frontend_host.h" +#include "content/public/browser/url_data_source.h" +#include "content/public/browser/web_contents.h" +#include "content/public/browser/web_ui.h" +#include "net/base/url_util.h" +#include "ui/base/webui/web_ui_util.h" +#include "ui/color/color_provider.h" +#include "ui/color/color_provider_utils.h" + +namespace electron { + +namespace { +GURL GetThemeUrl(const std::string& path) { + return GURL(std::string(content::kChromeDevToolsScheme) + + url::kStandardSchemeSeparator + + std::string(chrome::kChromeUIThemeHost) + "/" + path); +} + +scoped_refptr CreateNotFoundResponse() { + const char kHttpNotFound[] = "HTTP/1.1 404 Not Found\n\n"; + return base::MakeRefCounted( + base::byte_span_from_cstring(kHttpNotFound)); +} + +std::string GetMimeTypeForUrl(const GURL& url) { + std::string filename = url.ExtractFileName(); + if (base::EndsWith(filename, ".html", base::CompareCase::INSENSITIVE_ASCII)) { + return "text/html"; + } else if (base::EndsWith(filename, ".css", + base::CompareCase::INSENSITIVE_ASCII)) { + return "text/css"; + } else if (base::EndsWith(filename, ".js", + base::CompareCase::INSENSITIVE_ASCII) || + base::EndsWith(filename, ".mjs", + base::CompareCase::INSENSITIVE_ASCII)) { + return "application/javascript"; + } else if (base::EndsWith(filename, ".png", + base::CompareCase::INSENSITIVE_ASCII)) { + return "image/png"; + } else if (base::EndsWith(filename, ".map", + base::CompareCase::INSENSITIVE_ASCII)) { + return "application/json"; + } else if (base::EndsWith(filename, ".ts", + base::CompareCase::INSENSITIVE_ASCII)) { + return "application/x-typescript"; + } else if (base::EndsWith(filename, ".gif", + base::CompareCase::INSENSITIVE_ASCII)) { + return "image/gif"; + } else if (base::EndsWith(filename, ".svg", + base::CompareCase::INSENSITIVE_ASCII)) { + return "image/svg+xml"; + } else if (base::EndsWith(filename, ".manifest", + base::CompareCase::INSENSITIVE_ASCII)) { + return "text/cache-manifest"; + } + return "text/html"; +} +} // namespace + +std::string ThemeDataSource::GetSource() { + // kChromeUIThemeHost + return chrome::kChromeUIThemeHost; +} + +void ThemeDataSource::StartDataRequest( + const GURL& url, + const content::WebContents::Getter& wc_getter, + GotDataCallback callback) { + // TODO(crbug.com/40050262): Simplify usages of |path| since |url| is + // available. + const std::string path = content::URLDataSource::URLToRequestPath(url); + // Default scale factor if not specified. + float scale = 1.0f; + // All frames by default if not specified. + int frame = -1; + std::string parsed_path; + webui::ParsePathAndImageSpec(GetThemeUrl(path), &parsed_path, &scale, &frame); + + // kColorsCssPath should stay consistent with COLORS_CSS_SELECTOR in + // colors_css_updater.js. + constexpr char kColorsCssPath[] = "colors.css"; + if (parsed_path == kColorsCssPath) { + SendColorsCss(url, wc_getter, std::move(callback)); + return; + } + + std::move(callback).Run(CreateNotFoundResponse()); +} + +std::string ThemeDataSource::GetMimeType(const GURL& url) { + return GetMimeTypeForUrl(url); +} + +void ThemeDataSource::SendColorsCss( + const GURL& url, + const content::WebContents::Getter& wc_getter, + content::URLDataSource::GotDataCallback callback) { + const ui::ColorProvider& color_provider = wc_getter.Run()->GetColorProvider(); + + std::string sets_param; + std::vector color_id_sets; + bool generate_rgb_vars = false; + std::string generate_rgb_vars_query_value; + if (net::GetValueForKeyInQuery(url, "generate_rgb_vars", + &generate_rgb_vars_query_value)) { + generate_rgb_vars = + base::ToLowerASCII(generate_rgb_vars_query_value) == "true"; + } + bool shadow_host = false; + std::string shadow_host_query_value; + if (net::GetValueForKeyInQuery(url, "shadow_host", + &shadow_host_query_value)) { + shadow_host = base::ToLowerASCII(shadow_host_query_value) == "true"; + } + if (!net::GetValueForKeyInQuery(url, "sets", &sets_param)) { + LOG(ERROR) + << "colors.css requires a 'sets' query parameter to specify the " + "color " + "id sets returned e.g chrome://theme/colors.css?sets=ui,chrome"; + std::move(callback).Run(nullptr); + return; + } + color_id_sets = base::SplitStringPiece(sets_param, ",", base::TRIM_WHITESPACE, + base::SPLIT_WANT_ALL); + + using ColorIdCSSCallback = base::RepeatingCallback; + auto generate_color_mapping = + [&color_id_sets, &color_provider, &generate_rgb_vars]( + std::string set_name, ui::ColorId start, ui::ColorId end, + ColorIdCSSCallback color_css_name) { + // Only return these mappings if specified in the query parameter. + auto it = base::ranges::find(color_id_sets, set_name); + if (it == color_id_sets.end()) { + return std::string(); + } + color_id_sets.erase(it); + std::string css_string; + for (ui::ColorId id = start; id < end; ++id) { + const SkColor color = color_provider.GetColor(id); + std::string css_id_to_color_mapping = + base::StringPrintf("%s:%s;", color_css_name.Run(id).c_str(), + ui::ConvertSkColorToCSSColor(color).c_str()); + base::StrAppend(&css_string, {css_id_to_color_mapping}); + if (generate_rgb_vars) { + // Also generate a r,g,b string for each color so apps can construct + // colors with their own opacities in css. + const std::string css_rgb_color_str = + color_utils::SkColorToRgbString(color); + const std::string css_id_to_rgb_color_mapping = + base::StringPrintf("%s-rgb:%s;", color_css_name.Run(id).c_str(), + css_rgb_color_str.c_str()); + base::StrAppend(&css_string, {css_id_to_rgb_color_mapping}); + } + } + return css_string; + }; + + // Convenience lambda for wrapping + // |ConvertColorProviderColorIdToCSSColorId|. + auto generate_color_provider_mapping = [&generate_color_mapping]( + std::string set_name, + ui::ColorId start, ui::ColorId end, + std::string (*color_id_name)( + ui::ColorId)) { + auto color_id_to_css_name = base::BindRepeating( + [](std::string (*color_id_name)(ui::ColorId), ui::ColorId id) { + return ui::ConvertColorProviderColorIdToCSSColorId(color_id_name(id)); + }, + color_id_name); + return generate_color_mapping(set_name, start, end, color_id_to_css_name); + }; + + std::string css_selector; + if (shadow_host) { + css_selector = ":host"; + } else { + // This selector requires more specificity than other existing CSS + // selectors that define variables. We increase the specifity by adding + // a pseudoselector. + css_selector = "html:not(#z)"; + } + + std::string css_string = base::StrCat( + {css_selector, "{", "--user-color-source: baseline-default;", + generate_color_provider_mapping("ui", ui::kUiColorsStart, + ui::kUiColorsEnd, ui::ColorIdName), + generate_color_provider_mapping("chrome", kChromeColorsStart, + kChromeColorsEnd, &ChromeColorIdName), + "}"}); + if (!color_id_sets.empty()) { + LOG(ERROR) << "Unrecognized color set(s) specified for " + "chrome://theme/colors.css: " + << base::JoinString(color_id_sets, ","); + std::move(callback).Run(nullptr); + return; + } + + std::move(callback).Run( + base::MakeRefCounted(std::move(css_string))); +} +} // namespace electron diff --git a/shell/browser/ui/devtools_ui_theme_data_source.h b/shell/browser/ui/devtools_ui_theme_data_source.h new file mode 100644 index 0000000000..4a4bdb682c --- /dev/null +++ b/shell/browser/ui/devtools_ui_theme_data_source.h @@ -0,0 +1,36 @@ +// Copyright (c) 2024 The Chromium Authors. All rights reserved. +// Use of this source code is governed by the MIT license that can be +// found in the LICENSE file. + +#ifndef ELECTRON_SHELL_BROWSER_UI_DEVTOOLS_UI_THEME_DATA_SOURCE_H_ +#define ELECTRON_SHELL_BROWSER_UI_DEVTOOLS_UI_THEME_DATA_SOURCE_H_ + +#include "content/public/browser/url_data_source.h" + +namespace electron { +// A ThemeDataSource implementation that handles devtools://theme/ +// requests. +class ThemeDataSource : public content::URLDataSource { + public: + ThemeDataSource() = default; + ~ThemeDataSource() override = default; + + ThemeDataSource(const ThemeDataSource&) = delete; + ThemeDataSource& operator=(const ThemeDataSource&) = delete; + + std::string GetSource() override; + + void StartDataRequest(const GURL& url, + const content::WebContents::Getter& wc_getter, + GotDataCallback callback) override; + + private: + std::string GetMimeType(const GURL& url) override; + + void SendColorsCss(const GURL& url, + const content::WebContents::Getter& wc_getter, + content::URLDataSource::GotDataCallback callback); +}; + +} // namespace electron +#endif // ELECTRON_SHELL_BROWSER_UI_DEVTOOLS_UI_THEME_DATA_SOURCE_H_ diff --git a/spec/chromium-spec.ts b/spec/chromium-spec.ts index b428b270b5..63cea38c6d 100644 --- a/spec/chromium-spec.ts +++ b/spec/chromium-spec.ts @@ -2858,6 +2858,38 @@ describe('chromium features', () => { await new Promise((resolve) => { utter.onend = resolve; }); }); }); + + describe('devtools', () => { + it('fetch colors.css', async () => { + // + const w = new BrowserWindow({ show: false }); + const devtools = new BrowserWindow({ show: false }); + const devToolsOpened = once(w.webContents, 'devtools-opened'); + + w.webContents.setDevToolsWebContents(devtools.webContents); + w.webContents.openDevTools(); + await devToolsOpened; + expect(devtools.webContents.getURL().startsWith('devtools://devtools')).to.be.true(); + + const result = await devtools.webContents.executeJavaScript(` + document.body.querySelector('link[href*=\\'//theme/colors.css\\']')?.getAttribute('href'); + `); + expect(result.startsWith('devtools://theme/colors.css?sets=ui,chrome')).to.be.true(); + const colorAccentResult = await devtools.webContents.executeJavaScript(` + const style = getComputedStyle(document.body); + style.getPropertyValue('--color-accent'); + `); + expect(colorAccentResult).to.not.equal(''); + const colorAppMenuHighlightSeverityLow = await devtools.webContents.executeJavaScript(` + style.getPropertyValue('--color-app-menu-highlight-severity-low'); + `); + expect(colorAppMenuHighlightSeverityLow).to.not.equal(''); + const rgb = await devtools.webContents.executeJavaScript(` + style.getPropertyValue('--color-accent-rgb'); + `); + expect(rgb).to.equal(''); + }); + }); }); describe('font fallback', () => {