diff --git a/chromium_src/BUILD.gn b/chromium_src/BUILD.gn index a3e4730317388..efab0de19963a 100644 --- a/chromium_src/BUILD.gn +++ b/chromium_src/BUILD.gn @@ -66,8 +66,11 @@ static_library("chrome") { "//chrome/browser/extensions/global_shortcut_listener_win.cc", "//chrome/browser/extensions/global_shortcut_listener_win.h", "//chrome/browser/icon_loader_win.cc", + "//chrome/browser/ui/frame/window_frame_util.h", + "//chrome/browser/ui/view_ids.h", "//chrome/browser/win/chrome_process_finder.cc", "//chrome/browser/win/chrome_process_finder.h", + "//chrome/browser/win/titlebar_config.h", "//chrome/child/v8_crashpad_support_win.cc", "//chrome/child/v8_crashpad_support_win.h", ] diff --git a/docs/api/browser-window.md b/docs/api/browser-window.md index ee9c5b70667cb..0031a0d525580 100644 --- a/docs/api/browser-window.md +++ b/docs/api/browser-window.md @@ -213,16 +213,13 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. * `followWindow` - The backdrop should automatically appear active when the window is active, and inactive when it is not. This is the default. * `active` - The backdrop should always appear active. * `inactive` - The backdrop should always appear inactive. - * `titleBarStyle` String (optional) - The style of window title bar. + * `titleBarStyle` String (optional) _macOS_ _Windows_ - The style of window title bar. Default is `default`. Possible values are: - * `default` - Results in the standard gray opaque Mac title - bar. - * `hidden` - Results in a hidden title bar and a full size content window, yet - the title bar still has the standard window controls ("traffic lights") in - the top left. - * `hiddenInset` - Results in a hidden title bar with an alternative look + * `default` - Results in the standard title bar for macOS or Windows respectively. + * `hidden` - Results in a hidden title bar and a full size content window. On macOS, the window still has the standard window controls (“traffic lights”) in the top left. On Windows, when combined with `titleBarOverlay: true` it will activate the Window Controls Overlay (see `titleBarOverlay` for more information), otherwise no window controls will be shown. + * `hiddenInset` - Only on macOS, results in a hidden title bar with an alternative look where the traffic light buttons are slightly more inset from the window edge. - * `customButtonsOnHover` - Results in a hidden title bar and a full size + * `customButtonsOnHover` - Only on macOS, results in a hidden title bar and a full size content window, the traffic light buttons will display when being hovered over in the top left of the window. **Note:** This option is currently experimental. @@ -403,10 +400,7 @@ It creates a new `BrowserWindow` with native properties as set by the `options`. contain the layout of the document—without requiring scrolling. Enabling this will cause the `preferred-size-changed` event to be emitted on the `WebContents` when the preferred size changes. Default is `false`. - * `titleBarOverlay` Boolean (optional) - On macOS, when using a frameless window in conjunction with - `win.setWindowButtonVisibility(true)` or using a `titleBarStyle` so that the traffic lights are visible, - this property enables the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and - [CSS Environment Variables][overlay-css-env-vars]. Default is `false`. + * `titleBarOverlay` [OverlayOptions](structures/overlay-options.md) | Boolean (optional) - When using a frameless window in conjuction with `win.setWindowButtonVisibility(true)` on macOS or using a `titleBarStyle` so that the standard window controls ("traffic lights" on macOS) are visible, this property enables the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and [CSS Environment Variables][overlay-css-env-vars]. Specifying `true` will result in an overlay with default system colors. Default is `false`. On Windows, the [OverlayOptions](structures/overlay-options.md) can be used instead of a boolean to specify colors for the overlay. When setting minimum or maximum window size with `minWidth`/`maxWidth`/ `minHeight`/`maxHeight`, it only constrains the users. It won't prevent you from diff --git a/docs/api/frameless-window.md b/docs/api/frameless-window.md index 7a73c8925e200..7f327904b36e7 100644 --- a/docs/api/frameless-window.md +++ b/docs/api/frameless-window.md @@ -18,17 +18,17 @@ const win = new BrowserWindow({ width: 800, height: 600, frame: false }) win.show() ``` -### Alternatives on macOS +### Alternatives -There's an alternative way to specify a chromeless window. +There's an alternative way to specify a chromeless window on macOS and Windows. Instead of setting `frame` to `false` which disables both the titlebar and window controls, you may want to have the title bar hidden and your content extend to the full window size, -yet still preserve the window controls ("traffic lights") for standard window actions. +yet still preserve the window controls ("traffic lights" on macOS) for standard window actions. You can do so by specifying the `titleBarStyle` option: #### `hidden` -Results in a hidden title bar and a full size content window, yet the title bar still has the standard window controls (“traffic lights”) in the top left. +Results in a hidden title bar and a full size content window. On macOS, the title bar still has the standard window controls (“traffic lights”) in the top left. ```javascript const { BrowserWindow } = require('electron') @@ -36,6 +36,8 @@ const win = new BrowserWindow({ titleBarStyle: 'hidden' }) win.show() ``` +### Alternatives on macOS + #### `hiddenInset` Results in a hidden title bar with an alternative look where the traffic light buttons are slightly more inset from the window edge. @@ -63,19 +65,33 @@ win.show() ## Windows Control Overlay -On macOS, when using a frameless window in conjuction with `win.setWindowButtonVisibility(true)` or using one of the `titleBarStyle`s described above so -that the traffic lights are visible, you can access the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and -[CSS Environment Variables][overlay-css-env-vars] by setting the `titleBarOverlay` option to true: +When using a frameless window in conjuction with `win.setWindowButtonVisibility(true)` on macOS, using one of the `titleBarStyle`s as described above so +that the traffic lights are visible, or using `titleBarStyle: hidden` on Windows, you can access the Window Controls Overlay [JavaScript APIs][overlay-javascript-apis] and +[CSS Environment Variables][overlay-css-env-vars] by setting the `titleBarOverlay` option to true. Specifying `true` will result in an overlay with default system colors. + +On Windows, you can also specify the color of the overlay and its symbols by setting `titleBarOverlay` to an object with the options `color` and `symbolColor`. If an option is not specified, the color will default to its system color for the window control buttons: ```javascript const { BrowserWindow } = require('electron') const win = new BrowserWindow({ - titleBarStyle: 'hiddenInset', + titleBarStyle: 'hidden', titleBarOverlay: true }) win.show() ``` +```javascript +const { BrowserWindow } = require('electron') +const win = new BrowserWindow({ + titleBarStyle: 'hidden', + titleBarOverlay: { + color: '#2f3241', + symbolColor: '#74b1be' + } +}) +win.show() +``` + ## Transparent window By setting the `transparent` option to `true`, you can also make the frameless diff --git a/docs/api/structures/overlay-options.md b/docs/api/structures/overlay-options.md new file mode 100644 index 0000000000000..9e0e4815ea59c --- /dev/null +++ b/docs/api/structures/overlay-options.md @@ -0,0 +1,4 @@ +# OverlayOptions Object + +* `color` String (optional) _Windows_ - The CSS color of the Window Controls Overlay when enabled. Default is the system color. +* `symbolColor` String (optional) _Windows_ - The CSS color of the symbols on the Window Controls Overlay when enabled. Default is the system color. diff --git a/electron_strings.grdp b/electron_strings.grdp index b86aaf9d849cc..6fc83d90e5ea9 100644 --- a/electron_strings.grdp +++ b/electron_strings.grdp @@ -1,5 +1,19 @@ + + + Close + + + Minimize + + + Maximize + + + Restore + + Printing Service diff --git a/filenames.auto.gni b/filenames.auto.gni index 829a794466aa9..0bf902fd04f0a 100644 --- a/filenames.auto.gni +++ b/filenames.auto.gni @@ -101,6 +101,7 @@ auto_filenames = { "docs/api/structures/new-window-web-contents-event.md", "docs/api/structures/notification-action.md", "docs/api/structures/notification-response.md", + "docs/api/structures/overlay-options.md", "docs/api/structures/point.md", "docs/api/structures/post-body.md", "docs/api/structures/printer-info.md", diff --git a/filenames.gni b/filenames.gni index 3894978859e9b..271513816fda4 100644 --- a/filenames.gni +++ b/filenames.gni @@ -90,6 +90,10 @@ filenames = { "shell/browser/ui/views/electron_views_delegate_win.cc", "shell/browser/ui/views/win_frame_view.cc", "shell/browser/ui/views/win_frame_view.h", + "shell/browser/ui/views/win_caption_button.cc", + "shell/browser/ui/views/win_caption_button.h", + "shell/browser/ui/views/win_caption_button_container.cc", + "shell/browser/ui/views/win_caption_button_container.h", "shell/browser/ui/win/dialog_thread.cc", "shell/browser/ui/win/dialog_thread.h", "shell/browser/ui/win/electron_desktop_native_widget_aura.cc", diff --git a/patches/chromium/.patches b/patches/chromium/.patches index c1441f133587b..5b3e52c0e6a99 100644 --- a/patches/chromium/.patches +++ b/patches/chromium/.patches @@ -133,6 +133,8 @@ attach_to_correct_frame_in.patch merge_m92_speculative_fix_for_crash_in.patch cherry-pick-d727013bb543.patch pa_make_getusablesize_handle_nullptr_gracefully.patch +dpwas_window_control_overlay_api_values_account_for_page_zoom_factor.patch +reland_make_clientview_a_child_of_the_nonclientframeview.patch content-visibility_force_range_base_extent_when_computing_visual.patch cherry-pick-6215793f008f.patch cherry-pick-6048fcd52f42.patch diff --git a/patches/chromium/dpwas_window_control_overlay_api_values_account_for_page_zoom_factor.patch b/patches/chromium/dpwas_window_control_overlay_api_values_account_for_page_zoom_factor.patch new file mode 100644 index 0000000000000..1126cbe5b720b --- /dev/null +++ b/patches/chromium/dpwas_window_control_overlay_api_values_account_for_page_zoom_factor.patch @@ -0,0 +1,701 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Mike Jackson +Date: Wed, 9 Jun 2021 16:48:30 +0000 +Subject: dpwas: Window Control Overlay API values account for page zoom factor + +The overlay's bounding rect passed from the browser process +to the render process doesn't take the page's zoom factor +(browser zoom - Ctrl+/-) into account. The bounding rect is +exposed via a JS API/Event and CSS environment variables, so +we need to convert from Frame space coordinates to unzoomed +CSS pixels. When calculating the new rect, ensure that we return +a slightly larger rect if needed to avoid rendering contents +smaller than the Window Control Overlay. e.g. If the height of +the Window Control Overlay is 32, and page's zoom factor is 500% +we will return a height of 7, instead of 6. + +LocalFrame is notified of page zoom change via +SynchronizeVisualProperties, to ensure we are only computing this +in a single pass, we also add the Window Control Overlay rect +to the SynchronizeVisualProperties message. + +Manual testing: + +1) Enable 'Desktop PWA Window Controls Overlay' flags +2) Install https://amandabaker.github.io/pwa/windowControlsOverlay-newCSSVars/index.html +3) Toggle Window Control Overlay on +4) Change zoom level for PWA via the 3 dots menu +5) As you increase the zoom level, the values returned should decrease +6) As you decrease the zoom level, the values returned should increase + +Screenshots: + 100%: https://imgur.com/a/L4MV4RW + 80%: https://imgur.com/a/xH79oZg + 125%: https://imgur.com/a/CcqlkPV + +Explainer: https://github.com/WICG/window-controls-overlay/blob/master/explainer.md +Design Doc: https://docs.google.com/document/d/1k0YL_-VMLIfjYCgJ2v6cMvuUv2qMKg4BgLI2tJ4qtyo/edit?usp=sharing +I2P: https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/cper6nNLFRQ/hU91kfCWBQAJ + +Bug: 937121, 1213123 +Change-Id: I6744bb5a64b4021195734464b9a024e15277baa7 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2918946 +Commit-Queue: Mike Jackson +Reviewed-by: Daniel Cheng +Reviewed-by: Avi Drissman +Reviewed-by: danakj +Cr-Commit-Position: refs/heads/master@{#890815} + +diff --git a/content/browser/renderer_host/render_widget_host_delegate.cc b/content/browser/renderer_host/render_widget_host_delegate.cc +index 26c7a93644bb2b9f58817294265b80de33e9ef1b..3780835536c56f076831aadac63878133f21a0cd 100644 +--- a/content/browser/renderer_host/render_widget_host_delegate.cc ++++ b/content/browser/renderer_host/render_widget_host_delegate.cc +@@ -91,6 +91,10 @@ blink::mojom::DisplayMode RenderWidgetHostDelegate::GetDisplayMode() const { + return blink::mojom::DisplayMode::kBrowser; + } + ++gfx::Rect RenderWidgetHostDelegate::GetWindowsControlsOverlayRect() const { ++ return gfx::Rect(); ++} ++ + bool RenderWidgetHostDelegate::HasMouseLock( + RenderWidgetHostImpl* render_widget_host) { + return false; +diff --git a/content/browser/renderer_host/render_widget_host_delegate.h b/content/browser/renderer_host/render_widget_host_delegate.h +index 51bcc78ecd8f5f40e90a5e9077ac59b37c5c3e13..74d81a2a91ef515c3b89e2ceaa197b894c9fd9b7 100644 +--- a/content/browser/renderer_host/render_widget_host_delegate.h ++++ b/content/browser/renderer_host/render_widget_host_delegate.h +@@ -215,6 +215,10 @@ class CONTENT_EXPORT RenderWidgetHostDelegate { + // to frame-based widgets. Other widgets are always kBrowser. + virtual blink::mojom::DisplayMode GetDisplayMode() const; + ++ // Returns the Window Control Overlay rectangle. Only applies to an ++ // outermost main frame's widget. Other widgets always returns an empty rect. ++ virtual gfx::Rect GetWindowsControlsOverlayRect() const; ++ + // Notification that the widget has lost capture. + virtual void LostCapture(RenderWidgetHostImpl* render_widget_host) {} + +diff --git a/content/browser/renderer_host/render_widget_host_impl.cc b/content/browser/renderer_host/render_widget_host_impl.cc +index fc8916ac6dc76968e0cbd06877ffb80c95f3abf4..18f0ea3b8ea34f6287e92299ef147bccaedd302a 100644 +--- a/content/browser/renderer_host/render_widget_host_impl.cc ++++ b/content/browser/renderer_host/render_widget_host_impl.cc +@@ -879,6 +879,8 @@ blink::VisualProperties RenderWidgetHostImpl::GetVisualProperties() { + auto& current_screen_info = visual_properties.screen_infos.mutable_current(); + + visual_properties.is_fullscreen_granted = delegate_->IsFullscreen(); ++ visual_properties.window_controls_overlay_rect = ++ delegate_->GetWindowsControlsOverlayRect(); + + if (is_frame_widget) + visual_properties.display_mode = delegate_->GetDisplayMode(); +@@ -2659,7 +2661,9 @@ bool RenderWidgetHostImpl::StoredVisualPropertiesNeedsUpdate( + old_visual_properties->is_pinch_gesture_active != + new_visual_properties.is_pinch_gesture_active || + old_visual_properties->root_widget_window_segments != +- new_visual_properties.root_widget_window_segments; ++ new_visual_properties.root_widget_window_segments || ++ old_visual_properties->window_controls_overlay_rect != ++ new_visual_properties.window_controls_overlay_rect; + } + + void RenderWidgetHostImpl::AutoscrollStart(const gfx::PointF& position) { +diff --git a/content/browser/web_contents/web_contents_impl.cc b/content/browser/web_contents/web_contents_impl.cc +index ca51a8a45570fafc0dfe2b400cbb7172a9be632d..835a100a98882e3fff1e679ed596171ce865a653 100644 +--- a/content/browser/web_contents/web_contents_impl.cc ++++ b/content/browser/web_contents/web_contents_impl.cc +@@ -7872,10 +7872,22 @@ gfx::Size WebContentsImpl::GetSize() { + + #endif // !defined(OS_MAC) + ++gfx::Rect WebContentsImpl::GetWindowsControlsOverlayRect() const { ++ return window_controls_overlay_rect_; ++} ++ + void WebContentsImpl::UpdateWindowControlsOverlay( + const gfx::Rect& bounding_rect) { +- GetMainFrame()->GetAssociatedLocalMainFrame()->UpdateWindowControlsOverlay( +- bounding_rect); ++ if (window_controls_overlay_rect_ == bounding_rect) ++ return; ++ ++ window_controls_overlay_rect_ = bounding_rect; ++ ++ // Updates to the |window_controls_overlay_rect_| are sent via ++ // the VisualProperties message. ++ if (RenderWidgetHost* render_widget_host = ++ GetMainFrame()->GetRenderWidgetHost()) ++ render_widget_host->SynchronizeVisualProperties(); + } + + BrowserPluginEmbedder* WebContentsImpl::GetBrowserPluginEmbedder() const { +diff --git a/content/browser/web_contents/web_contents_impl.h b/content/browser/web_contents/web_contents_impl.h +index 17034e75d2ab5bd4e716e9c72277c77a53387808..3e32a9b4e17bb515066acaf014d1fe659cc83772 100644 +--- a/content/browser/web_contents/web_contents_impl.h ++++ b/content/browser/web_contents/web_contents_impl.h +@@ -960,6 +960,7 @@ class CONTENT_EXPORT WebContentsImpl : public WebContents, + bool IsWidgetForMainFrame(RenderWidgetHostImpl* render_widget_host) override; + bool IsShowingContextMenuOnPage() const override; + void DidChangeScreenOrientation() override; ++ gfx::Rect GetWindowsControlsOverlayRect() const override; + + // RenderFrameHostManager::Delegate ------------------------------------------ + +@@ -2091,6 +2092,12 @@ class CONTENT_EXPORT WebContentsImpl : public WebContents, + // with OOPIF renderers. + blink::mojom::TextAutosizerPageInfo text_autosizer_page_info_; + ++ // Stores the rect of the Windows Control Overlay, which contains system UX ++ // affordances (e.g. close), for installed desktop Progress Web Apps (PWAs), ++ // if the app specifies the 'window-controls-overlay' DisplayMode in its ++ // manifest. This is in frame space coordinates. ++ gfx::Rect window_controls_overlay_rect_; ++ + // Observe native theme for changes to dark mode, preferred color scheme, and + // preferred contrast. Used to notify the renderer of preferred color scheme + // and preferred contrast changes. +diff --git a/content/browser/web_contents/web_contents_impl_browsertest.cc b/content/browser/web_contents/web_contents_impl_browsertest.cc +index 2e75cbf168dbfa48d9f094ed84398197fd0487aa..73dc93a8afd6fa1ff38e900590681e22d43f7ca4 100644 +--- a/content/browser/web_contents/web_contents_impl_browsertest.cc ++++ b/content/browser/web_contents/web_contents_impl_browsertest.cc +@@ -43,6 +43,7 @@ + #include "content/public/browser/back_forward_cache.h" + #include "content/public/browser/browser_thread.h" + #include "content/public/browser/file_select_listener.h" ++#include "content/public/browser/host_zoom_map.h" + #include "content/public/browser/invalidate_type.h" + #include "content/public/browser/javascript_dialog_manager.h" + #include "content/public/browser/load_notification_details.h" +@@ -88,6 +89,7 @@ + #include "testing/gmock/include/gmock/gmock.h" + #include "third_party/blink/public/common/client_hints/client_hints.h" + #include "third_party/blink/public/common/features.h" ++#include "third_party/blink/public/common/page/page_zoom.h" + #include "third_party/blink/public/mojom/frame/fullscreen.mojom.h" + #include "ui/base/clipboard/clipboard_format_type.h" + #include "url/gurl.h" +@@ -4466,19 +4468,74 @@ class WebContentsImplBrowserTestWindowControlsOverlay + } + + void ValidateTitlebarAreaCSSValue(const std::string& name, +- const std::string& expected_result) { ++ int expected_result) { + SCOPED_TRACE(name); +- + EXPECT_EQ( + expected_result, + EvalJs(shell()->web_contents(), + JsReplace( +- "(() => {const e = document.getElementById('target');const " +- "style = window.getComputedStyle(e, null); return " +- "style.getPropertyValue($1);})();", ++ "(() => {" ++ "const e = document.getElementById('target');" ++ "const style = window.getComputedStyle(e, null);" ++ "return Math.round(style.getPropertyValue($1).replace('px', " ++ "''));" ++ "})();", + name))); + } + ++ ++ void ValidateWindowsControlOverlayState(WebContents* web_contents, ++ const gfx::Rect& expected_rect, ++ int css_fallback_value) { ++ EXPECT_EQ(!expected_rect.IsEmpty(), ++ EvalJs(web_contents, "navigator.windowControlsOverlay.visible")); ++ EXPECT_EQ( ++ expected_rect.x(), ++ EvalJs(web_contents, ++ "navigator.windowControlsOverlay.getBoundingClientRect().x")); ++ EXPECT_EQ( ++ expected_rect.y(), ++ EvalJs(web_contents, ++ "navigator.windowControlsOverlay.getBoundingClientRect().y")); ++ EXPECT_EQ( ++ expected_rect.width(), ++ EvalJs( ++ web_contents, ++ "navigator.windowControlsOverlay.getBoundingClientRect().width")); ++ EXPECT_EQ( ++ expected_rect.height(), ++ EvalJs( ++ web_contents, ++ "navigator.windowControlsOverlay.getBoundingClientRect().height")); ++ ++ // When the overlay is not visible, the environment variables should be ++ // undefined, and the the fallback value should be used. ++ gfx::Rect css_rect = expected_rect; ++ if (css_rect.IsEmpty()) { ++ css_rect.SetRect(css_fallback_value, css_fallback_value, ++ css_fallback_value, css_fallback_value); ++ } ++ ++ ValidateTitlebarAreaCSSValue("left", css_rect.x()); ++ ValidateTitlebarAreaCSSValue("top", css_rect.y()); ++ ValidateTitlebarAreaCSSValue("width", css_rect.width()); ++ ValidateTitlebarAreaCSSValue("height", css_rect.height()); ++ } ++ ++ void WaitForWindowControlsOverlayUpdate( ++ WebContents* web_contents, ++ const gfx::Rect& bounding_client_rect) { ++ EXPECT_TRUE( ++ ExecJs(web_contents->GetMainFrame(), ++ "navigator.windowControlsOverlay.ongeometrychange = (e) => {" ++ " document.title = 'ongeometrychange'" ++ "}")); ++ ++ web_contents->UpdateWindowControlsOverlay(bounding_client_rect); ++ TitleWatcher title_watcher(web_contents, u"ongeometrychange"); ++ ignore_result(title_watcher.WaitAndGetTitle()); ++ } ++ + private: + base::test::ScopedFeatureList scoped_feature_list_; + }; +@@ -4500,24 +4557,12 @@ IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTestWindowControlsOverlay, + // empty. + int empty_rect_value = 0; + +- EXPECT_EQ(false, +- EvalJs(web_contents, "navigator.windowControlsOverlay.visible")); +- EXPECT_EQ( +- empty_rect_value, +- EvalJs(web_contents, +- "navigator.windowControlsOverlay.getBoundingClientRect().x")); +- EXPECT_EQ( +- empty_rect_value, +- EvalJs(web_contents, +- "navigator.windowControlsOverlay.getBoundingClientRect().y")); +- EXPECT_EQ( +- empty_rect_value, +- EvalJs(web_contents, +- "navigator.windowControlsOverlay.getBoundingClientRect().width")); +- EXPECT_EQ( +- empty_rect_value, +- EvalJs(web_contents, +- "navigator.windowControlsOverlay.getBoundingClientRect().height")); ++ ++ // Update bounds and ensure that JS APIs and CSS variables are updated. ++ gfx::Rect bounding_client_rect(1, 2, 3, 4); ++ WaitForWindowControlsOverlayUpdate(web_contents, bounding_client_rect); ++ ValidateWindowsControlOverlayState(web_contents, bounding_client_rect, 50); ++} + + // When the overlay is not visble, the environment variables should be + // undefined, and the the fallback value of 50px should be used. +@@ -4535,31 +4580,15 @@ IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTestWindowControlsOverlay, + gfx::Rect bounding_client_rect = + gfx::Rect(new_x, new_y, new_width, new_height); + +- web_contents->UpdateWindowControlsOverlay(bounding_client_rect); +- +- EXPECT_EQ(true, +- EvalJs(web_contents, "navigator.windowControlsOverlay.visible")); +- EXPECT_EQ( +- new_x, +- EvalJs(web_contents, +- "navigator.windowControlsOverlay.getBoundingClientRect().x")); +- EXPECT_EQ( +- new_y, +- EvalJs(web_contents, +- "navigator.windowControlsOverlay.getBoundingClientRect().y")); +- EXPECT_EQ( +- new_width, +- EvalJs(web_contents, +- "navigator.windowControlsOverlay.getBoundingClientRect().width")); +- EXPECT_EQ( +- new_height, +- EvalJs(web_contents, +- "navigator.windowControlsOverlay.getBoundingClientRect().height")); +- +- ValidateTitlebarAreaCSSValue("left", "1px"); +- ValidateTitlebarAreaCSSValue("top", "2px"); +- ValidateTitlebarAreaCSSValue("width", "3px"); +- ValidateTitlebarAreaCSSValue("height", "4px"); ++ // Update bounds and ensure that JS APIs and CSS variables are updated. ++ gfx::Rect bounding_client_rect(0, 0, 100, 32); ++ WaitForWindowControlsOverlayUpdate(web_contents, bounding_client_rect); ++ ValidateWindowsControlOverlayState(web_contents, bounding_client_rect, 55); ++ ++ // Now toggle Windows Controls Overlay off. ++ gfx::Rect empty_rect; ++ WaitForWindowControlsOverlayUpdate(web_contents, empty_rect); ++ ValidateWindowsControlOverlayState(web_contents, empty_rect, 55); + } + + IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTestWindowControlsOverlay, +@@ -4568,14 +4597,16 @@ IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTestWindowControlsOverlay, + + GURL url(url::kAboutBlankURL); + EXPECT_TRUE(NavigateToURL(shell(), url)); +- EXPECT_TRUE(ExecuteScript( +- web_contents->GetMainFrame(), +- "geometrychangeCount = 0;" +- "navigator.windowControlsOverlay.ongeometrychange = (e) => {" +- " geometrychangeCount++;" +- " rect = e.boundingRect;" +- " visible = e.visible;" +- "}")); ++ ++ EXPECT_TRUE( ++ ExecJs(web_contents->GetMainFrame(), ++ "geometrychangeCount = 0;" ++ "navigator.windowControlsOverlay.ongeometrychange = (e) => {" ++ " geometrychangeCount++;" ++ " rect = e.boundingRect;" ++ " visible = e.visible;" ++ " document.title = 'ongeometrychange' + geometrychangeCount" ++ "}")); + + WaitForLoadStop(web_contents); + +@@ -4584,23 +4615,107 @@ IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTestWindowControlsOverlay, + EXPECT_EQ(0, EvalJs(web_contents, "geometrychangeCount")); + + // Information about the bounds should be updated. +- const int x = 2; +- const int y = 2; +- const int width = 2; +- const int height = 2; +- +- gfx::Rect bounding_client_rect = gfx::Rect(x, y, width, height); +- ++ gfx::Rect bounding_client_rect = gfx::Rect(2, 3, 4, 5); + web_contents->UpdateWindowControlsOverlay(bounding_client_rect); ++ TitleWatcher title_watcher(web_contents, u"ongeometrychange1"); ++ ignore_result(title_watcher.WaitAndGetTitle()); + + // Expect the "geometrychange" event to have fired once. + EXPECT_EQ(1, EvalJs(web_contents, "geometrychangeCount")); + + // Validate the event payload. + EXPECT_EQ(true, EvalJs(web_contents, "visible")); +- EXPECT_EQ(x, EvalJs(web_contents, "rect.x;")); +- EXPECT_EQ(y, EvalJs(web_contents, "rect.y")); +- EXPECT_EQ(width, EvalJs(web_contents, "rect.width")); +- EXPECT_EQ(height, EvalJs(web_contents, "rect.height")); ++ EXPECT_EQ(bounding_client_rect.x(), EvalJs(web_contents, "rect.x;")); ++ EXPECT_EQ(bounding_client_rect.y(), EvalJs(web_contents, "rect.y")); ++ EXPECT_EQ(bounding_client_rect.width(), EvalJs(web_contents, "rect.width")); ++ EXPECT_EQ(bounding_client_rect.height(), EvalJs(web_contents, "rect.height")); ++} ++ ++#if !defined(OS_ANDROID) ++IN_PROC_BROWSER_TEST_F(WebContentsImplBrowserTestWindowControlsOverlay, ++ ValidatePageScaleChangesInfoAndFiresEvent) { ++ auto* web_contents = shell()->web_contents(); ++ GURL url( ++ R"(data:text/html,
)"); ++ ++ EXPECT_TRUE(NavigateToURL(shell(), url)); ++ WaitForLoadStop(web_contents); ++ ++ gfx::Rect bounding_client_rect = gfx::Rect(5, 10, 15, 20); ++ WaitForWindowControlsOverlayUpdate(web_contents, bounding_client_rect); ++ ++ // Update zoom level, confirm the "geometrychange" event is fired, ++ // and CSS variables are updated ++ EXPECT_TRUE( ++ ExecJs(web_contents->GetMainFrame(), ++ "geometrychangeCount = 0;" ++ "navigator.windowControlsOverlay.ongeometrychange = (e) => {" ++ " geometrychangeCount++;" ++ " rect = e.boundingRect;" ++ " visible = e.visible;" ++ " document.title = 'ongeometrychangefromzoomlevel'" ++ "}")); ++ content::HostZoomMap::SetZoomLevel(web_contents, 1.5); ++ TitleWatcher title_watcher(web_contents, u"ongeometrychangefromzoomlevel"); ++ ignore_result(title_watcher.WaitAndGetTitle()); ++ ++ // Validate the event payload. ++ double zoom_factor = blink::PageZoomLevelToZoomFactor( ++ content::HostZoomMap::GetZoomLevel(web_contents)); ++ gfx::Rect scaled_rect = ++ gfx::ScaleToEnclosingRectSafe(bounding_client_rect, 1.0f / zoom_factor); ++ ++ EXPECT_EQ(true, EvalJs(web_contents, "visible")); ++ EXPECT_EQ(scaled_rect.x(), EvalJs(web_contents, "rect.x")); ++ EXPECT_EQ(scaled_rect.y(), EvalJs(web_contents, "rect.y")); ++ EXPECT_EQ(scaled_rect.width(), EvalJs(web_contents, "rect.width")); ++ EXPECT_EQ(scaled_rect.height(), EvalJs(web_contents, "rect.height")); ++ ValidateWindowsControlOverlayState(web_contents, scaled_rect, 60); ++} ++#endif ++ ++class WebContentsImplBrowserTestWindowControlsOverlayNonOneDeviceScaleFactor ++ : public WebContentsImplBrowserTestWindowControlsOverlay { ++ public: ++ void SetUp() override { ++#if defined(OS_MAC) ++ // Device scale factor on MacOSX is always an integer. ++ EnablePixelOutput(2.0f); ++#else ++ EnablePixelOutput(1.25f); ++#endif ++ WebContentsImplBrowserTestWindowControlsOverlay::SetUp(); ++ } ++}; ++ ++IN_PROC_BROWSER_TEST_F( ++ WebContentsImplBrowserTestWindowControlsOverlayNonOneDeviceScaleFactor, ++ ValidateScaledCorrectly) { ++ auto* web_contents = shell()->web_contents(); ++ GURL url( ++ R"(data:text/html,
)"); ++ ++ EXPECT_TRUE(NavigateToURL(shell(), url)); ++ WaitForLoadStop(web_contents); ++#if defined(OS_MAC) ++ // Device scale factor on MacOSX is always an integer. ++ ASSERT_EQ(2.0f, ++ web_contents->GetRenderWidgetHostView()->GetDeviceScaleFactor()); ++#else ++ ASSERT_EQ(1.25f, ++ web_contents->GetRenderWidgetHostView()->GetDeviceScaleFactor()); ++#endif ++ ++ gfx::Rect bounding_client_rect = gfx::Rect(5, 10, 15, 20); ++ WaitForWindowControlsOverlayUpdate(web_contents, bounding_client_rect); ++ ValidateWindowsControlOverlayState(web_contents, bounding_client_rect, 70); + } + } // namespace content +diff --git a/third_party/blink/common/widget/visual_properties.cc b/third_party/blink/common/widget/visual_properties.cc +index 433ca5954c9f316905f289948ab2e4ebe66b7833..55932091bafe8959c855529d49b9f66cd6e386f0 100644 +--- a/third_party/blink/common/widget/visual_properties.cc ++++ b/third_party/blink/common/widget/visual_properties.cc +@@ -33,7 +33,8 @@ bool VisualProperties::operator==(const VisualProperties& other) const { + page_scale_factor == other.page_scale_factor && + compositing_scale_factor == other.compositing_scale_factor && + root_widget_window_segments == other.root_widget_window_segments && +- is_pinch_gesture_active == other.is_pinch_gesture_active; ++ is_pinch_gesture_active == other.is_pinch_gesture_active && ++ window_controls_overlay_rect == other.window_controls_overlay_rect; + } + + bool VisualProperties::operator!=(const VisualProperties& other) const { +diff --git a/third_party/blink/common/widget/visual_properties_mojom_traits.cc b/third_party/blink/common/widget/visual_properties_mojom_traits.cc +index d378def431a2643de08951ff861b68868b1d7250..262eec364918a668a2f5e65af2044c24d3380aa7 100644 +--- a/third_party/blink/common/widget/visual_properties_mojom_traits.cc ++++ b/third_party/blink/common/widget/visual_properties_mojom_traits.cc +@@ -24,6 +24,7 @@ bool StructTraits< + !data.ReadBrowserControlsParams(&out->browser_controls_params) || + !data.ReadLocalSurfaceId(&out->local_surface_id) || + !data.ReadRootWidgetWindowSegments(&out->root_widget_window_segments) || ++ !data.ReadWindowControlsOverlayRect(&out->window_controls_overlay_rect) || + data.page_scale_factor() <= 0 || data.compositing_scale_factor() <= 0) + return false; + out->auto_resize_enabled = data.auto_resize_enabled(); +diff --git a/third_party/blink/public/common/widget/visual_properties.h b/third_party/blink/public/common/widget/visual_properties.h +index 3c16c86e704558b40e00b40264a4d7018d89fb5e..e020adae74d1f061bbbfc5bc10e8a40a69f93410 100644 +--- a/third_party/blink/public/common/widget/visual_properties.h ++++ b/third_party/blink/public/common/widget/visual_properties.h +@@ -129,6 +129,13 @@ struct BLINK_COMMON_EXPORT VisualProperties { + // main frame's renderer, and needs to be shared with subframes. + bool is_pinch_gesture_active = false; + ++ // The rect of the Windows Control Overlay, which contains system UX ++ // affordances (e.g. close), for installed desktop Progress Web Apps (PWAs), ++ // if the app specifies the 'window-controls-overlay' DisplayMode in its ++ // manifest. This is only valid and to be consumed by the outermost main ++ // frame. ++ gfx::Rect window_controls_overlay_rect; ++ + VisualProperties(); + VisualProperties(const VisualProperties& other); + ~VisualProperties(); +diff --git a/third_party/blink/public/common/widget/visual_properties_mojom_traits.h b/third_party/blink/public/common/widget/visual_properties_mojom_traits.h +index f6634310fd17acc7299db892d68aea770578a0f1..8d7ab89e5d434e4098a55c6b78d06bfb6f3faa29 100644 +--- a/third_party/blink/public/common/widget/visual_properties_mojom_traits.h ++++ b/third_party/blink/public/common/widget/visual_properties_mojom_traits.h +@@ -97,6 +97,11 @@ struct BLINK_COMMON_EXPORT StructTraitsGetChromeClient().WindowToViewportScalar(&local_frame_root, ++ 1.0f); ++ const float zoom_factor = local_frame_root.PageZoomFactor(); ++ const float scale_factor = zoom_factor / window_to_viewport_factor; ++ gfx::Rect window_controls_overlay_rect = ++ gfx::ScaleToEnclosingRectSafe(bounding_rect_in_dips, 1.0f / scale_factor); ++ ++ bool fire_event = ++ (window_controls_overlay_rect != window_controls_overlay_rect_); ++ + is_window_controls_overlay_visible_ = !window_controls_overlay_rect.IsEmpty(); + window_controls_overlay_rect_ = window_controls_overlay_rect; + + DocumentStyleEnvironmentVariables& vars = + GetDocument()->GetStyleEngine().EnsureEnvironmentVariables(); +- vars.SetVariable( +- UADefinedVariable::kTitlebarAreaX, +- StyleEnvironmentVariables::FormatPx(window_controls_overlay_rect_.x())); +- vars.SetVariable( +- UADefinedVariable::kTitlebarAreaY, +- StyleEnvironmentVariables::FormatPx(window_controls_overlay_rect_.y())); +- vars.SetVariable(UADefinedVariable::kTitlebarAreaWidth, +- StyleEnvironmentVariables::FormatPx( +- window_controls_overlay_rect_.width())); +- vars.SetVariable(UADefinedVariable::kTitlebarAreaHeight, +- StyleEnvironmentVariables::FormatPx( +- window_controls_overlay_rect_.height())); +- +- auto* window_controls_overlay = +- WindowControlsOverlay::FromIfExists(*DomWindow()->navigator()); +- +- if (window_controls_overlay) { +- window_controls_overlay->WindowControlsOverlayChanged( +- window_controls_overlay_rect); ++ ++ if (is_window_controls_overlay_visible_) { ++ vars.SetVariable( ++ UADefinedVariable::kTitlebarAreaX, ++ StyleEnvironmentVariables::FormatPx(window_controls_overlay_rect_.x())); ++ vars.SetVariable( ++ UADefinedVariable::kTitlebarAreaY, ++ StyleEnvironmentVariables::FormatPx(window_controls_overlay_rect_.y())); ++ vars.SetVariable(UADefinedVariable::kTitlebarAreaWidth, ++ StyleEnvironmentVariables::FormatPx( ++ window_controls_overlay_rect_.width())); ++ vars.SetVariable(UADefinedVariable::kTitlebarAreaHeight, ++ StyleEnvironmentVariables::FormatPx( ++ window_controls_overlay_rect_.height())); ++ } else { ++ const UADefinedVariable vars_to_remove[] = { ++ UADefinedVariable::kTitlebarAreaX, ++ UADefinedVariable::kTitlebarAreaY, ++ UADefinedVariable::kTitlebarAreaWidth, ++ UADefinedVariable::kTitlebarAreaHeight, ++ }; ++ for (auto var_to_remove : vars_to_remove) { ++ vars.RemoveVariable(StyleEnvironmentVariables::GetVariableName(var_to_remove)); ++ } ++ } ++ ++ if (fire_event) { ++ auto* window_controls_overlay = ++ WindowControlsOverlay::FromIfExists(*DomWindow()->navigator()); ++ ++ if (window_controls_overlay) { ++ window_controls_overlay->WindowControlsOverlayChanged( ++ window_controls_overlay_rect_); ++ } + } + } + +diff --git a/third_party/blink/renderer/core/frame/local_frame.h b/third_party/blink/renderer/core/frame/local_frame.h +index 38ad1f729ffc7416ae56c771f5b518fb63520c08..d725460b26f25ca5746126f76efdfc8722943492 100644 +--- a/third_party/blink/renderer/core/frame/local_frame.h ++++ b/third_party/blink/renderer/core/frame/local_frame.h +@@ -732,8 +732,7 @@ class CORE_EXPORT LocalFrame final + void UpdateBrowserControlsState(cc::BrowserControlsState constraints, + cc::BrowserControlsState current, + bool animate) override; +- void UpdateWindowControlsOverlay( +- const gfx::Rect& window_controls_overlay_rect) override; ++ void UpdateWindowControlsOverlay(const gfx::Rect& bounding_rect_in_dips); + + // mojom::FullscreenVideoElementHandler implementation: + void RequestFullscreenVideoElement() final; +diff --git a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc +index 1f13dc8bce4a41b96bb2bfce776d6b55500db5b6..d33496ce445cb2af4b21cdd23bdc011d6214b352 100644 +--- a/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc ++++ b/third_party/blink/renderer/core/frame/web_frame_widget_impl.cc +@@ -1516,6 +1516,10 @@ void WebFrameWidgetImpl::ApplyVisualPropertiesSizing( + widget_base_->VisibleViewportSizeInDIPs()), + visual_properties.browser_controls_params); + } ++ ++ LocalRootImpl()->GetFrame()->UpdateWindowControlsOverlay( ++ visual_properties.window_controls_overlay_rect); ++ + } else { + // Widgets in a WebView's frame tree without a local main frame + // set the size of the WebView to be the |visible_viewport_size|, in order +diff --git a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py +index 2816268f68d8910b11c5b6ea6d0c2a1a92bd2e1a..ac95c2f8e18081cca7a2c14899c9d7a9444fa565 100755 +--- a/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py ++++ b/third_party/blink/tools/blinkpy/presubmit/audit_non_blink_usage.py +@@ -279,6 +279,7 @@ _CONFIG = [ + 'gfx::RectF', + 'gfx::RRectF', + 'gfx::ScaleToCeiledSize', ++ 'gfx::ScaleToEnclosingRectSafe', + 'gfx::ScaleVector2d', + 'gfx::Size', + 'gfx::SizeF', diff --git a/patches/chromium/reland_make_clientview_a_child_of_the_nonclientframeview.patch b/patches/chromium/reland_make_clientview_a_child_of_the_nonclientframeview.patch new file mode 100644 index 0000000000000..0fcc878e05bd9 --- /dev/null +++ b/patches/chromium/reland_make_clientview_a_child_of_the_nonclientframeview.patch @@ -0,0 +1,1727 @@ +From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 +From: Amanda Baker +Date: Sat, 10 Apr 2021 00:00:15 +0000 +Subject: Reland "Make ClientView a child of the NonClientFrameView" + +This is a reland of ec39b1ddb4cca45bf582ca55ad1585ceb51d9306 + +Original change's description: +> Make ClientView a child of the NonClientFrameView +> +> This refactor changes the hierarchy of views under +> NonClientView. Previously, NonClientFrameView and ClientView were +> siblings under NonClientView. This will change the hierarchy to: +> NonClientView > NonClientFrameView > ClientView. +> +> This change also enables a cleaner implementation of the Window +> Controls Overlay (see before (https://crrev.com/c/2504573) vs +> after (https://crrev.com/c/2545685)) +> +> Window controls overlay links: +> Explainer: https://github.com/WICG/window-controls-overlay/blob/master/explainer.md +> Design Doc: https://docs.google.com/document/d/1k0YL_-VMLIfjYCgJ2v6cMvuUv2qMKg4BgLI2tJ4qtyo/edit?usp=sharing +> I2P: https://groups.google.com/a/chromium.org/forum/#!msg/blink-dev/cper6nNLFRQ/hU91kfCWBQAJ +> +> Bug: 1175276, 937121 +> Change-Id: If16de54a858f571c628b66c7801ef3777c6cc924 +> Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2522616 +> Commit-Queue: Amanda Baker +> Reviewed-by: Mitsuru Oshima +> Reviewed-by: Matt Giuca +> Reviewed-by: David Tseng +> Reviewed-by: Leonard Grey +> Reviewed-by: Peter Kasting +> Cr-Commit-Position: refs/heads/master@{#864068} + +Bug: 1175276 +Bug: 937121 +Change-Id: Ib8919739dd685a4ea20b56fd241750678c38a9c0 +Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2780032 +Reviewed-by: Leonard Grey +Reviewed-by: Mitsuru Oshima +Reviewed-by: Peter Kasting +Reviewed-by: Matt Giuca +Reviewed-by: David Tseng +Commit-Queue: Amanda Baker +Cr-Commit-Position: refs/heads/master@{#871173} + +diff --git a/apps/ui/views/app_window_frame_view.cc b/apps/ui/views/app_window_frame_view.cc +index 1a73ae216937d4329ff38f3af8ca2c8f395fa1aa..f96d7bec703431ddc2ba67c4a8e588c361d859c9 100644 +--- a/apps/ui/views/app_window_frame_view.cc ++++ b/apps/ui/views/app_window_frame_view.cc +@@ -249,8 +249,11 @@ gfx::Size AppWindowFrameView::CalculatePreferredSize() const { + } + + void AppWindowFrameView::Layout() { ++ NonClientFrameView::Layout(); ++ + if (!draw_frame_) + return; ++ + gfx::Size close_size = close_button_->GetPreferredSize(); + const int kButtonOffsetY = 0; + const int kButtonSpacing = 1; +diff --git a/ash/app_list/views/search_box_view_unittest.cc b/ash/app_list/views/search_box_view_unittest.cc +index 8e548da40dd51e15df17bc8550340ebcf0ca4cad..748307a88e512e8237cb124ddb31d1f6129e2ff2 100644 +--- a/ash/app_list/views/search_box_view_unittest.cc ++++ b/ash/app_list/views/search_box_view_unittest.cc +@@ -92,9 +92,9 @@ class SearchBoxViewTest : public views::test::WidgetTest, + } + + void TearDown() override { +- view_.reset(); + app_list_view_->GetWidget()->Close(); + widget_->CloseNow(); ++ view_.reset(); + views::test::WidgetTest::TearDown(); + } + +diff --git a/ash/frame/default_frame_header_unittest.cc b/ash/frame/default_frame_header_unittest.cc +index 1299e1993e7d0986a553e69865bf950a1313afd5..28b5ccfb5900c0c1240e1debdde95174bf752a54 100644 +--- a/ash/frame/default_frame_header_unittest.cc ++++ b/ash/frame/default_frame_header_unittest.cc +@@ -277,7 +277,7 @@ TEST_F(DefaultFrameHeaderTest, ResizeAndReorderDuringAnimation) { + LayerDestroyedChecker checker(animating_layer); + + // Change the view's stacking order should stop the animation. +- ASSERT_EQ(2u, frame_view_1->children().size()); ++ ASSERT_EQ(3u, frame_view_1->children().size()); + frame_view_1->ReorderChildView(extra_view_1, 0); + + EXPECT_EQ( +diff --git a/ash/frame/non_client_frame_view_ash.cc b/ash/frame/non_client_frame_view_ash.cc +index 1f8a6931448f9fe7b7e364c02c01f6782c0873a3..bc73d9da6b020b39da1b56a9e42d3aac9ae5ea12 100644 +--- a/ash/frame/non_client_frame_view_ash.cc ++++ b/ash/frame/non_client_frame_view_ash.cc +@@ -327,16 +327,16 @@ gfx::Size NonClientFrameViewAsh::CalculatePreferredSize() const { + } + + void NonClientFrameViewAsh::Layout() { +- if (!GetEnabled()) +- return; + views::NonClientFrameView::Layout(); ++ if (!GetFrameEnabled()) ++ return; + aura::Window* frame_window = frame_->GetNativeWindow(); + frame_window->SetProperty(aura::client::kTopViewInset, + NonClientTopBorderHeight()); + } + + gfx::Size NonClientFrameViewAsh::GetMinimumSize() const { +- if (!GetEnabled()) ++ if (!GetFrameEnabled()) + return gfx::Size(); + + gfx::Size min_client_view_size(frame_->client_view()->GetMinimumSize()); +@@ -358,13 +358,6 @@ gfx::Size NonClientFrameViewAsh::GetMaximumSize() const { + return gfx::Size(width, height); + } + +-void NonClientFrameViewAsh::SetVisible(bool visible) { +- overlay_view_->SetVisible(visible); +- views::View::SetVisible(visible); +- // We need to re-layout so that client view will occupy entire window. +- InvalidateLayout(); +-} +- + void NonClientFrameViewAsh::SetShouldPaintHeader(bool paint) { + header_view_->SetShouldPaintHeader(paint); + } +@@ -372,7 +365,7 @@ void NonClientFrameViewAsh::SetShouldPaintHeader(bool paint) { + int NonClientFrameViewAsh::NonClientTopBorderHeight() const { + // The frame should not occupy the window area when it's in fullscreen, + // not visible or disabled. +- if (frame_->IsFullscreen() || !GetVisible() || !GetEnabled() || ++ if (frame_->IsFullscreen() || !GetFrameEnabled() || + header_view_->in_immersive_mode()) { + return 0; + } +@@ -395,6 +388,15 @@ SkColor NonClientFrameViewAsh::GetInactiveFrameColorForTest() const { + return frame_->GetNativeWindow()->GetProperty(kFrameInactiveColorKey); + } + ++void NonClientFrameViewAsh::SetFrameEnabled(bool enabled) { ++ if (enabled == frame_enabled_) ++ return; ++ ++ frame_enabled_ = enabled; ++ overlay_view_->SetVisible(frame_enabled_); ++ InvalidateLayout(); ++} ++ + void NonClientFrameViewAsh::OnDidSchedulePaint(const gfx::Rect& r) { + // We may end up here before |header_view_| has been added to the Widget. + if (header_view_->GetWidget()) { +@@ -410,9 +412,18 @@ void NonClientFrameViewAsh::OnDidSchedulePaint(const gfx::Rect& r) { + bool NonClientFrameViewAsh::DoesIntersectRect(const views::View* target, + const gfx::Rect& rect) const { + CHECK_EQ(target, this); +- // NonClientView hit tests the NonClientFrameView first instead of going in +- // z-order. Return false so that events get to the OverlayView. +- return false; ++ ++ // Give the OverlayView the first chance to handle events. ++ if (frame_enabled_ && overlay_view_->HitTestRect(rect)) ++ return false; ++ ++ // Handle the event if it's within the bounds of the ClientView. ++ gfx::RectF rect_in_client_view_coords_f(rect); ++ View::ConvertRectToTarget(this, frame_->client_view(), ++ &rect_in_client_view_coords_f); ++ gfx::Rect rect_in_client_view_coords = ++ gfx::ToEnclosingRect(rect_in_client_view_coords_f); ++ return frame_->client_view()->HitTestRect(rect_in_client_view_coords); + } + + chromeos::FrameCaptionButtonContainerView* +diff --git a/ash/frame/non_client_frame_view_ash.h b/ash/frame/non_client_frame_view_ash.h +index ac2633a19af8939d9655a09549d7c6c567e00a6e..c5efb7933d3077f75ccb7296ac873cdd1a1c9ea4 100644 +--- a/ash/frame/non_client_frame_view_ash.h ++++ b/ash/frame/non_client_frame_view_ash.h +@@ -90,7 +90,6 @@ class ASH_EXPORT NonClientFrameViewAsh : public views::NonClientFrameView { + void Layout() override; + gfx::Size GetMinimumSize() const override; + gfx::Size GetMaximumSize() const override; +- void SetVisible(bool visible) override; + + // If |paint| is false, we should not paint the header. Used for overview mode + // with OnOverviewModeStarting() and OnOverviewModeEnded() to hide/show the +@@ -111,6 +110,9 @@ class ASH_EXPORT NonClientFrameViewAsh : public views::NonClientFrameView { + + views::Widget* frame() { return frame_; } + ++ bool GetFrameEnabled() const { return frame_enabled_; } ++ virtual void SetFrameEnabled(bool enabled); ++ + protected: + // views::View: + void OnDidSchedulePaint(const gfx::Rect& r) override; +@@ -141,6 +143,8 @@ class ASH_EXPORT NonClientFrameViewAsh : public views::NonClientFrameView { + + OverlayView* overlay_view_ = nullptr; + ++ bool frame_enabled_ = true; ++ + std::unique_ptr immersive_helper_; + + base::CallbackListSubscription paint_as_active_subscription_ = +diff --git a/ash/frame/non_client_frame_view_ash_unittest.cc b/ash/frame/non_client_frame_view_ash_unittest.cc +index 67093a915488503984e01a43d47964ad7eafc89c..b1b0f64e0b47d558ae06d4e444b531efd54ab5e3 100644 +--- a/ash/frame/non_client_frame_view_ash_unittest.cc ++++ b/ash/frame/non_client_frame_view_ash_unittest.cc +@@ -543,19 +543,19 @@ TEST_F(NonClientFrameViewAshTest, FrameVisibility) { + delegate->non_client_frame_view(); + EXPECT_EQ(client_bounds, widget->client_view()->GetLocalBounds().size()); + +- non_client_frame_view->SetVisible(false); ++ non_client_frame_view->SetFrameEnabled(false); + widget->GetRootView()->Layout(); + EXPECT_EQ(gfx::Size(200, 100), + widget->client_view()->GetLocalBounds().size()); +- EXPECT_FALSE(widget->non_client_view()->frame_view()->GetVisible()); ++ EXPECT_FALSE(non_client_frame_view->GetFrameEnabled()); + EXPECT_EQ( + window_bounds, + non_client_frame_view->GetClientBoundsForWindowBounds(window_bounds)); + +- non_client_frame_view->SetVisible(true); ++ non_client_frame_view->SetFrameEnabled(true); + widget->GetRootView()->Layout(); + EXPECT_EQ(client_bounds, widget->client_view()->GetLocalBounds().size()); +- EXPECT_TRUE(widget->non_client_view()->frame_view()->GetVisible()); ++ EXPECT_TRUE(non_client_frame_view->GetFrameEnabled()); + EXPECT_EQ(32, delegate->GetNonClientFrameViewTopBorderHeight()); + EXPECT_EQ( + gfx::Rect(gfx::Point(10, 42), client_bounds), +diff --git a/ash/hud_display/hud_display.cc b/ash/hud_display/hud_display.cc +index ad7d615f0cd2cf4e897f98deac7aa08915f4434f..d9ba5e54e6de749f846a6376e25e746eafa013cc 100644 +--- a/ash/hud_display/hud_display.cc ++++ b/ash/hud_display/hud_display.cc +@@ -6,6 +6,7 @@ + + #include "ash/fast_ink/view_tree_host_root_view.h" + #include "ash/fast_ink/view_tree_host_widget.h" ++#include "ash/frame/non_client_frame_view_ash.h" + #include "ash/hud_display/graphs_container_view.h" + #include "ash/hud_display/hud_constants.h" + #include "ash/hud_display/hud_header_view.h" +@@ -82,12 +83,11 @@ std::unique_ptr MakeClientView(views::Widget* widget) { + } + + void InitializeFrameView(views::WidgetDelegate* delegate) { +- auto* frame_view = delegate->GetWidget()->non_client_view()->frame_view(); ++ auto* frame_view = static_cast( ++ delegate->GetWidget()->non_client_view()->frame_view()); + // TODO(oshima): support component type with TYPE_WINDOW_FLAMELESS widget. +- if (frame_view) { +- frame_view->SetEnabled(false); +- frame_view->SetVisible(false); +- } ++ if (frame_view) ++ frame_view->SetFrameEnabled(false); + } + + } // namespace +diff --git a/ash/system/holding_space/holding_space_tray_bubble.cc b/ash/system/holding_space/holding_space_tray_bubble.cc +index 1ffb354110867722be54898d2f045f1707af7f4b..5e55fbe7b361f9e9ca6ffaa1d20fbab79090724a 100644 +--- a/ash/system/holding_space/holding_space_tray_bubble.cc ++++ b/ash/system/holding_space/holding_space_tray_bubble.cc +@@ -319,6 +319,8 @@ HoldingSpaceTrayBubble::HoldingSpaceTrayBubble( + + // Create and customize bubble view. + TrayBubbleView* bubble_view = new TrayBubbleView(init_params); ++ // Ensure bubble frame does not draw background behind bubble view. ++ bubble_view->set_color(SK_ColorTRANSPARENT); + child_bubble_container_ = + bubble_view->AddChildView(std::make_unique()); + child_bubble_container_->SetMaxHeight(CalculateMaxHeight()); +@@ -339,12 +341,6 @@ HoldingSpaceTrayBubble::HoldingSpaceTrayBubble( + bubble_wrapper_ = + std::make_unique(holding_space_tray, bubble_view); + +- // Set bubble frame to be invisible. +- bubble_wrapper_->GetBubbleWidget() +- ->non_client_view() +- ->frame_view() +- ->SetVisible(false); +- + event_handler_ = + std::make_unique(this, &delegate_); + +diff --git a/ash/wm/system_modal_container_layout_manager_unittest.cc b/ash/wm/system_modal_container_layout_manager_unittest.cc +index 1723e7ea3ddaf9dd232765b07ccbce0d88bdf4c4..e8a5f8db3583782e139341d5bf7f791aaa98709e 100644 +--- a/ash/wm/system_modal_container_layout_manager_unittest.cc ++++ b/ash/wm/system_modal_container_layout_manager_unittest.cc +@@ -547,11 +547,12 @@ TEST_F(SystemModalContainerLayoutManagerTest, ShowModalWhileHidden) { + TEST_F(SystemModalContainerLayoutManagerTest, ChangeCapture) { + std::unique_ptr widget_window(ShowToplevelTestWindow(false)); + views::test::CaptureTrackingView* view = new views::test::CaptureTrackingView; +- views::View* contents_view = ++ views::View* client_view = + views::Widget::GetWidgetForNativeView(widget_window.get()) +- ->GetContentsView(); +- contents_view->AddChildView(view); +- view->SetBoundsRect(contents_view->bounds()); ++ ->non_client_view() ++ ->client_view(); ++ client_view->AddChildView(view); ++ view->SetBoundsRect(client_view->bounds()); + + gfx::Point center(view->width() / 2, view->height() / 2); + views::View::ConvertPointToScreen(view, ¢er); +diff --git a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js +index c790c2b55c3a1757bb9d6afe200796e4cc66178f..9d6a58dfc034413a36646ea8520ed62faa609e93 100644 +--- a/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js ++++ b/chrome/browser/resources/chromeos/accessibility/chromevox/background/background_test.js +@@ -3047,7 +3047,7 @@ TEST_F('ChromeVoxBackgroundTest', 'ImageAnnotations', function() { + + TEST_F('ChromeVoxBackgroundTest', 'VolumeChanges', function() { + const mockFeedback = this.createMockFeedback(); +- this.runWithLoadedTree(``, function() { ++ this.runWithLoadedTree('

test

', function() { + const bounds = ChromeVoxState.instance.getFocusBounds(); + mockFeedback.call(press(KeyCode.VOLUME_UP)) + .expectSpeech('Volume', 'Slider', /\d+%/) +diff --git a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.cc b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.cc +index 180167b310fdfe5392c8b0f919cc2ce0d6556b3d..c32e8027d855505b5413c884b341bdf542ebcc42 100644 +--- a/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.cc ++++ b/chrome/browser/ui/views/apps/app_info_dialog/app_info_dialog_container.cc +@@ -89,21 +89,6 @@ class FullSizeBubbleFrameView : public views::BubbleFrameView { + ~FullSizeBubbleFrameView() override = default; + + private: +- // Overridden from views::ViewTargeterDelegate: +- bool DoesIntersectRect(const View* target, +- const gfx::Rect& rect) const override { +- // Make sure click events can still reach the close button, even if the +- // ClientView overlaps it. +- // NOTE: |rect| is in the mirrored coordinate space, so we must use the +- // close button's mirrored bounds to correctly target the close button when +- // in RTL mode. +- if (IsCloseButtonVisible() && +- GetCloseButtonMirroredBounds().Intersects(rect)) { +- return true; +- } +- return views::BubbleFrameView::DoesIntersectRect(target, rect); +- } +- + // Overridden from views::BubbleFrameView: + bool ExtendClientIntoTitle() const override { return true; } + }; +diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc +index ca4a6ad167418b234bdae1e07ef398a7b6b97ed4..361cab54d78f5c0a9a6c6cdde0d002af6b358a9a 100644 +--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc ++++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view.cc +@@ -66,6 +66,11 @@ BrowserNonClientFrameView::~BrowserNonClientFrameView() { + g_browser_process->profile_manager()-> + GetProfileAttributesStorage().RemoveObserver(this); + } ++ ++ // WebAppFrameToolbarView::ToolbarButtonContainer is an ++ // ImmersiveModeController::Observer, so it must be destroyed before the ++ // BrowserView destroys the ImmersiveModeController. ++ delete web_app_frame_toolbar_; + } + + void BrowserNonClientFrameView::OnBrowserViewInitViewsComplete() { +@@ -294,68 +299,6 @@ void BrowserNonClientFrameView::ChildPreferredSizeChanged(views::View* child) { + Layout(); + } + +-bool BrowserNonClientFrameView::DoesIntersectRect(const views::View* target, +- const gfx::Rect& rect) const { +- DCHECK_EQ(target, this); +- if (!views::ViewTargeterDelegate::DoesIntersectRect(this, rect)) { +- // |rect| is outside the frame's bounds. +- return false; +- } +- +- bool should_leave_to_top_container = false; +-#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) +- // In immersive mode, the caption buttons container is reparented to the +- // TopContainerView and hence |rect| should not be claimed here. See +- // BrowserNonClientFrameViewChromeOS::OnImmersiveRevealStarted(). +- should_leave_to_top_container = +- browser_view_->immersive_mode_controller()->IsRevealed(); +-#endif +- +- if (!browser_view_->GetTabStripVisible()) { +- // Claim |rect| if it is above the top of the topmost client area view. +- return !should_leave_to_top_container && (rect.y() < GetTopInset(false)); +- } +- +- // If the rect is outside the bounds of the client area, claim it. +- gfx::RectF rect_in_client_view_coords_f(rect); +- View::ConvertRectToTarget(this, frame_->client_view(), +- &rect_in_client_view_coords_f); +- gfx::Rect rect_in_client_view_coords = +- gfx::ToEnclosingRect(rect_in_client_view_coords_f); +- if (!frame_->client_view()->HitTestRect(rect_in_client_view_coords)) +- return true; +- +- // Otherwise, claim |rect| only if it is above the bottom of the tab strip +- // region view in a non-tab portion. +- TabStripRegionView* tab_strip_region_view = +- browser_view_->tab_strip_region_view(); +- +- // The |tab_strip_region_view| may not be in a Widget (e.g. when switching +- // into immersive reveal the BrowserView's TopContainerView is reparented). +- if (tab_strip_region_view->GetWidget()) { +- gfx::RectF rect_in_region_view_coords_f(rect); +- View::ConvertRectToTarget(this, tab_strip_region_view, +- &rect_in_region_view_coords_f); +- gfx::Rect rect_in_region_view_coords = +- gfx::ToEnclosingRect(rect_in_region_view_coords_f); +- if (rect_in_region_view_coords.y() >= +- tab_strip_region_view->GetLocalBounds().bottom()) { +- // |rect| is below the tab_strip_region_view. +- return false; +- } +- +- if (tab_strip_region_view->HitTestRect(rect_in_region_view_coords)) { +- // Claim |rect| if it is in a non-tab portion of the tabstrip. +- return tab_strip_region_view->IsRectInWindowCaption( +- rect_in_region_view_coords); +- } +- } +- +- // We claim |rect| because it is above the bottom of the tabstrip, but +- // not in the tabstrip itself. +- return !should_leave_to_top_container; +-} +- + void BrowserNonClientFrameView::OnProfileAdded( + const base::FilePath& profile_path) { + OnProfileAvatarChanged(profile_path); +diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view.h +index 8760b6b6239e7bc102305ab9280860ac847fedc3..875ba7daf98a8d59390fe3c2f0281f29d43f964d 100644 +--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view.h ++++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view.h +@@ -157,8 +157,6 @@ class BrowserNonClientFrameView : public views::NonClientFrameView, + + // views::NonClientFrameView: + void ChildPreferredSizeChanged(views::View* child) override; +- bool DoesIntersectRect(const views::View* target, +- const gfx::Rect& rect) const override; + + // ProfileAttributesStorage::Observer: + void OnProfileAdded(const base::FilePath& profile_path) override; +diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc +index 945848b668620c5f037b219b6c76d35d851af291..05b1a9917da90ac9c2077f9fe39ce1418b04db1f 100644 +--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc ++++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.cc +@@ -401,6 +401,27 @@ void BrowserNonClientFrameViewChromeOS::ChildPreferredSizeChanged( + } + } + ++bool BrowserNonClientFrameViewChromeOS::DoesIntersectRect( ++ const views::View* target, ++ const gfx::Rect& rect) const { ++ DCHECK_EQ(target, this); ++ if (!views::ViewTargeterDelegate::DoesIntersectRect(this, rect)) { ++ // |rect| is outside the frame's bounds. ++ return false; ++ } ++ ++ bool should_leave_to_top_container = false; ++#if BUILDFLAG(IS_CHROMEOS_ASH) || BUILDFLAG(IS_CHROMEOS_LACROS) ++ // In immersive mode, the caption buttons container is reparented to the ++ // TopContainerView and hence |rect| should not be claimed here. See ++ // BrowserNonClientFrameViewChromeOS::OnImmersiveRevealStarted(). ++ should_leave_to_top_container = ++ browser_view()->immersive_mode_controller()->IsRevealed(); ++#endif ++ ++ return !should_leave_to_top_container; ++} ++ + SkColor BrowserNonClientFrameViewChromeOS::GetTitleColor() { + return browser_view()->GetRegularOrGuestSession() + ? kNormalWindowTitleTextColor +@@ -561,7 +582,13 @@ void BrowserNonClientFrameViewChromeOS::OnImmersiveRevealStarted() { + } + + void BrowserNonClientFrameViewChromeOS::OnImmersiveRevealEnded() { +- AddChildViewAt(caption_button_container_, 0); ++ // Ensure the caption button container receives events before the browser view ++ // by placing it higher in the z-order. ++ // [0] - FrameAnimatorView ++ // [1] - BrowserView ++ // [2] - FrameCaptionButtonContainerView ++ const int kCaptionButtonContainerIndex = 2; ++ AddChildViewAt(caption_button_container_, kCaptionButtonContainerIndex); + if (web_app_frame_toolbar()) + AddChildViewAt(web_app_frame_toolbar(), 0); + Layout(); +diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.h b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.h +index 97df69a7209fafdaf2597b46536da0743f87b7fd..4bbc1a599464e3556f0da4da917bae9b9e03884c 100644 +--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.h ++++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_chromeos.h +@@ -79,6 +79,8 @@ class BrowserNonClientFrameViewChromeOS + gfx::Size GetMinimumSize() const override; + void OnThemeChanged() override; + void ChildPreferredSizeChanged(views::View* child) override; ++ bool DoesIntersectRect(const views::View* target, ++ const gfx::Rect& rect) const override; + + // BrowserFrameHeaderChromeOS::AppearanceProvider: + SkColor GetTitleColor() override; +diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm +index 387e4c46a90d3f365fee56388a60805a35f61fa1..1eeda0de0520fba7ef6a6d8606e3aef27601599c 100644 +--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm ++++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_mac.mm +@@ -338,6 +338,8 @@ FullscreenToolbarStyle GetUserPreferredToolbarStyle(bool always_show) { + } + + void BrowserNonClientFrameViewMac::Layout() { ++ NonClientFrameView::Layout(); ++ + const int available_height = GetTopInset(true); + int leading_x = kFramePaddingLeft; + int trailing_x = width(); +diff --git a/chrome/browser/ui/views/frame/browser_non_client_frame_view_unittest.cc b/chrome/browser/ui/views/frame/browser_non_client_frame_view_unittest.cc +index 58cb7172a89c054175f04e5d031b3114d638ed26..f62322371d905b5eabd64b0e5bd63c8784c4341b 100644 +--- a/chrome/browser/ui/views/frame/browser_non_client_frame_view_unittest.cc ++++ b/chrome/browser/ui/views/frame/browser_non_client_frame_view_unittest.cc +@@ -61,12 +61,15 @@ class BrowserNonClientFrameViewPopupTest + #define MAYBE_HitTestPopupTopChrome HitTestPopupTopChrome + #endif + TEST_F(BrowserNonClientFrameViewPopupTest, MAYBE_HitTestPopupTopChrome) { +- EXPECT_FALSE(frame_view_->HitTestRect(gfx::Rect(-1, 4, 1, 1))); +- EXPECT_FALSE(frame_view_->HitTestRect(gfx::Rect(4, -1, 1, 1))); ++ constexpr gfx::Rect kLeftOfFrame(-1, 4, 1, 1); ++ EXPECT_FALSE(frame_view_->HitTestRect(kLeftOfFrame)); ++ ++ constexpr gfx::Rect kAboveFrame(4, -1, 1, 1); ++ EXPECT_FALSE(frame_view_->HitTestRect(kAboveFrame)); ++ + const int top_inset = frame_view_->GetTopInset(false); +- EXPECT_FALSE(frame_view_->HitTestRect(gfx::Rect(4, top_inset, 1, 1))); +- if (top_inset > 0) +- EXPECT_TRUE(frame_view_->HitTestRect(gfx::Rect(4, top_inset - 1, 1, 1))); ++ const gfx::Rect in_browser_view(4, top_inset, 1, 1); ++ EXPECT_TRUE(frame_view_->HitTestRect(in_browser_view)); + } + + class BrowserNonClientFrameViewTabbedTest +@@ -108,7 +111,9 @@ TEST_F(BrowserNonClientFrameViewTabbedTest, MAYBE_HitTestTabstrip) { + + // Hits client portions of the tabstrip (near the bottom left corner of the + // first tab). +- EXPECT_FALSE(frame_view_->HitTestRect(gfx::Rect( ++ EXPECT_TRUE(frame_view_->HitTestRect(gfx::Rect( ++ tabstrip_bounds.x() + 10, tabstrip_bounds.bottom() - 10, 1, 1))); ++ EXPECT_TRUE(frame_view_->browser_view()->HitTestRect(gfx::Rect( + tabstrip_bounds.x() + 10, tabstrip_bounds.bottom() - 10, 1, 1))); + + // Tabs extend to the top of the tabstrip everywhere in this test context on +diff --git a/chrome/browser/ui/views/frame/browser_view.cc b/chrome/browser/ui/views/frame/browser_view.cc +index c33c4327974e7a5d80ac8da0af06a7cc7c271b63..58a0cec1d400fcd987d9a7873df5638c9c29e066 100644 +--- a/chrome/browser/ui/views/frame/browser_view.cc ++++ b/chrome/browser/ui/views/frame/browser_view.cc +@@ -2928,6 +2928,12 @@ void BrowserView::ViewHierarchyChanged( + } + + void BrowserView::AddedToWidget() { ++ // BrowserView may be added to a widget more than once if the user changes ++ // themes after starting the browser. Do not re-initialize BrowserView in ++ // this case. ++ if (initialized_) ++ return; ++ + views::ClientView::AddedToWidget(); + + widget_observation_.Observe(GetWidget()); +diff --git a/chrome/browser/ui/views/frame/glass_browser_frame_view.cc b/chrome/browser/ui/views/frame/glass_browser_frame_view.cc +index 3ae9bc2369f3327c258a0ea080ba9c2aee0e1c3b..452070396204ca6fd6b231a8dc98ff132f55aa2f 100644 +--- a/chrome/browser/ui/views/frame/glass_browser_frame_view.cc ++++ b/chrome/browser/ui/views/frame/glass_browser_frame_view.cc +@@ -403,6 +403,7 @@ void GlassBrowserFrameView::Layout() { + LayoutCaptionButtons(); + LayoutTitleBar(); + LayoutClientView(); ++ NonClientFrameView::Layout(); + } + + /////////////////////////////////////////////////////////////////////////////// +diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.cc b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.cc +index c788b1cf970cceefb82b36c2a24de5bdf1432119..843f8f87b82862c1030b96976b4a99e29f0f7aa4 100644 +--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.cc ++++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.cc +@@ -18,6 +18,7 @@ + #include "ui/gfx/font.h" + #include "ui/views/controls/button/image_button.h" + #include "ui/views/controls/label.h" ++#include "ui/views/view_utils.h" + #include "ui/views/window/caption_button_layout_constants.h" + #include "ui/views/window/frame_caption_button.h" + +@@ -606,10 +607,20 @@ gfx::Size OpaqueBrowserFrameViewLayout::GetPreferredSize( + + void OpaqueBrowserFrameViewLayout::ViewAdded(views::View* host, + views::View* view) { ++ if (views::IsViewClass(view)) { ++ client_view_ = static_cast(view); ++ return; ++ } ++ + SetView(view->GetID(), view); + } + + void OpaqueBrowserFrameViewLayout::ViewRemoved(views::View* host, + views::View* view) { ++ if (views::IsViewClass(view)) { ++ client_view_ = nullptr; ++ return; ++ } ++ + SetView(view->GetID(), nullptr); + } +diff --git a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h +index 3a8d8905cb1f68a89d36ac6fe53fb6fe5d581dfc..ec7d845fede82b43de7009ee7eb965097cf3d3f6 100644 +--- a/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h ++++ b/chrome/browser/ui/views/frame/opaque_browser_frame_view_layout.h +@@ -217,6 +217,8 @@ class OpaqueBrowserFrameViewLayout : public views::LayoutManager { + std::vector leading_buttons_; + std::vector trailing_buttons_; + ++ views::ClientView* client_view_ = nullptr; ++ + DISALLOW_COPY_AND_ASSIGN(OpaqueBrowserFrameViewLayout); + }; + +diff --git a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.cc b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.cc +index c0a4ab3705b813fedd0155b37934c807e30119cc..c6d8c62fd09ec82db897fe0bb2bf13ab03c0fc95 100644 +--- a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.cc ++++ b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.cc +@@ -29,6 +29,7 @@ WebAppFrameToolbarView::WebAppFrameToolbarView(views::Widget* widget, + DCHECK(browser_view_); + DCHECK(web_app::AppBrowserController::IsWebApp(browser_view_->browser())); + SetID(VIEW_ID_WEB_APP_FRAME_TOOLBAR); ++ SetEventTargeter(std::make_unique(this)); + + { + // TODO(tluk) fix the need for both LayoutInContainer() and a layout +@@ -219,6 +220,24 @@ ReloadButton* WebAppFrameToolbarView::GetReloadButton() { + return left_container_ ? left_container_->reload_button() : nullptr; + } + ++bool WebAppFrameToolbarView::DoesIntersectRect(const View* target, ++ const gfx::Rect& rect) const { ++ DCHECK_EQ(target, this); ++ if (!views::ViewTargeterDelegate::DoesIntersectRect(this, rect)) ++ return false; ++ ++ // If the rect is inside the bounds of the center_container, do not claim it. ++ // There is no actionable content in the center_container, and it overlaps ++ // tabs in tabbed PWA windows. ++ gfx::RectF rect_in_center_container_coords_f(rect); ++ View::ConvertRectToTarget(this, center_container_, ++ &rect_in_center_container_coords_f); ++ gfx::Rect rect_in_client_view_coords = ++ gfx::ToEnclosingRect(rect_in_center_container_coords_f); ++ ++ return !center_container_->HitTestRect(rect_in_client_view_coords); ++} ++ + PageActionIconController* + WebAppFrameToolbarView::GetPageActionIconControllerForTesting() { + return right_container_->page_action_icon_controller(); +diff --git a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h +index 8d3f6ad1375d86bcca05c603a9dab01481e752e1..0bca228da4e73319f424b2ae2409bcaabe82f775 100644 +--- a/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h ++++ b/chrome/browser/ui/views/web_apps/frame_toolbar/web_app_frame_toolbar_view.h +@@ -15,9 +15,11 @@ + #include "ui/gfx/color_palette.h" + #include "ui/views/accessible_pane_view.h" + #include "ui/views/metadata/metadata_header_macros.h" ++#include "ui/views/view_targeter_delegate.h" + + namespace views { + class View; ++class ViewTargeterDelegate; + class Widget; + } // namespace views + +@@ -29,7 +31,8 @@ class WebAppToolbarButtonContainer; + + // A container for web app buttons in the title bar. + class WebAppFrameToolbarView : public views::AccessiblePaneView, +- public ToolbarButtonProvider { ++ public ToolbarButtonProvider, ++ public views::ViewTargeterDelegate { + public: + METADATA_HEADER(WebAppFrameToolbarView); + WebAppFrameToolbarView(views::Widget* widget, BrowserView* browser_view); +@@ -71,6 +74,10 @@ class WebAppFrameToolbarView : public views::AccessiblePaneView, + ToolbarButton* GetBackButton() override; + ReloadButton* GetReloadButton() override; + ++ // views::ViewTargeterDelegate ++ bool DoesIntersectRect(const View* target, ++ const gfx::Rect& rect) const override; ++ + WebAppNavigationButtonContainer* get_left_container_for_testing() { + return left_container_; + } +diff --git a/components/exo/client_controlled_shell_surface.cc b/components/exo/client_controlled_shell_surface.cc +index 2efee6195ef3dae4a8cbd4c35ebeb809c88c1bc3..39fb793b30c99b0c6b9ee3ba64c54820d456418f 100644 +--- a/components/exo/client_controlled_shell_surface.cc ++++ b/components/exo/client_controlled_shell_surface.cc +@@ -1024,7 +1024,7 @@ void ClientControlledShellSurface::SetWidgetBounds(const gfx::Rect& bounds) { + gfx::Rect ClientControlledShellSurface::GetShadowBounds() const { + gfx::Rect shadow_bounds = ShellSurfaceBase::GetShadowBounds(); + const ash::NonClientFrameViewAsh* frame_view = GetFrameView(); +- if (frame_view->GetVisible()) { ++ if (frame_view->GetFrameEnabled()) { + // The client controlled geometry is only for the client + // area. When the chrome side frame is enabled, the shadow height + // has to include the height of the frame, and the total height is +@@ -1083,7 +1083,7 @@ float ClientControlledShellSurface::GetScale() const { + base::Optional ClientControlledShellSurface::GetWidgetBounds() + const { + const ash::NonClientFrameViewAsh* frame_view = GetFrameView(); +- if (frame_view->GetVisible()) { ++ if (frame_view->GetFrameEnabled()) { + gfx::Rect visible_bounds = ShellSurfaceBase::GetVisibleBounds(); + if (widget_->IsMaximized() && frame_type_ == SurfaceFrameType::NORMAL) { + // When the widget is maximized in clamshell mode, client sends +@@ -1262,7 +1262,7 @@ void ClientControlledShellSurface::UpdateFrame() { + .work_area(); + + ash::WindowState* window_state = GetWindowState(); +- bool enable_wide_frame = GetFrameView()->GetVisible() && ++ bool enable_wide_frame = GetFrameView()->GetFrameEnabled() && + window_state->IsMaximizedOrFullscreenOrPinned() && + work_area.width() != geometry().width(); + bool update_frame = state_changed_; +diff --git a/components/exo/client_controlled_shell_surface_unittest.cc b/components/exo/client_controlled_shell_surface_unittest.cc +index dc4756c4a80c0521a4367ad0a76e0bba9e195869..20d4d7cdbf746bfc323c6ecef2e68c22972f1e80 100644 +--- a/components/exo/client_controlled_shell_surface_unittest.cc ++++ b/components/exo/client_controlled_shell_surface_unittest.cc +@@ -517,7 +517,7 @@ TEST_F(ClientControlledShellSurfaceTest, Frame) { + + // Normal state. + widget->LayoutRootViewIfNecessary(); +- EXPECT_TRUE(frame_view->GetVisible()); ++ EXPECT_TRUE(frame_view->GetFrameEnabled()); + EXPECT_EQ(normal_window_bounds, widget->GetWindowBoundsInScreen()); + EXPECT_EQ(client_bounds, + frame_view->GetClientBoundsForWindowBounds(normal_window_bounds)); +@@ -528,7 +528,7 @@ TEST_F(ClientControlledShellSurfaceTest, Frame) { + surface->Commit(); + + widget->LayoutRootViewIfNecessary(); +- EXPECT_TRUE(frame_view->GetVisible()); ++ EXPECT_TRUE(frame_view->GetFrameEnabled()); + EXPECT_EQ(fullscreen_bounds, widget->GetWindowBoundsInScreen()); + EXPECT_EQ( + gfx::Size(800, 568), +@@ -541,7 +541,7 @@ TEST_F(ClientControlledShellSurfaceTest, Frame) { + surface->Commit(); + + widget->LayoutRootViewIfNecessary(); +- EXPECT_TRUE(frame_view->GetVisible()); ++ EXPECT_TRUE(frame_view->GetFrameEnabled()); + EXPECT_EQ(gfx::Rect(0, 200, 800, 400), widget->GetWindowBoundsInScreen()); + + display_manager->UpdateWorkAreaOfDisplay(display_id, gfx::Insets(0, 0, 0, 0)); +@@ -552,7 +552,7 @@ TEST_F(ClientControlledShellSurfaceTest, Frame) { + surface->Commit(); + + widget->LayoutRootViewIfNecessary(); +- EXPECT_TRUE(frame_view->GetVisible()); ++ EXPECT_TRUE(frame_view->GetFrameEnabled()); + EXPECT_EQ(fullscreen_bounds, widget->GetWindowBoundsInScreen()); + EXPECT_EQ(fullscreen_bounds, + frame_view->GetClientBoundsForWindowBounds(fullscreen_bounds)); +@@ -562,7 +562,7 @@ TEST_F(ClientControlledShellSurfaceTest, Frame) { + surface->Commit(); + + widget->LayoutRootViewIfNecessary(); +- EXPECT_TRUE(frame_view->GetVisible()); ++ EXPECT_TRUE(frame_view->GetFrameEnabled()); + EXPECT_EQ(fullscreen_bounds, widget->GetWindowBoundsInScreen()); + EXPECT_EQ(fullscreen_bounds, + frame_view->GetClientBoundsForWindowBounds(fullscreen_bounds)); +@@ -587,7 +587,7 @@ TEST_F(ClientControlledShellSurfaceTest, Frame) { + surface->Commit(); + + widget->LayoutRootViewIfNecessary(); +- EXPECT_TRUE(frame_view->GetVisible()); ++ EXPECT_TRUE(frame_view->GetFrameEnabled()); + EXPECT_EQ(normal_window_bounds, widget->GetWindowBoundsInScreen()); + EXPECT_EQ(client_bounds, + frame_view->GetClientBoundsForWindowBounds(normal_window_bounds)); +@@ -599,7 +599,7 @@ TEST_F(ClientControlledShellSurfaceTest, Frame) { + surface->Commit(); + + widget->LayoutRootViewIfNecessary(); +- EXPECT_FALSE(frame_view->GetVisible()); ++ EXPECT_FALSE(frame_view->GetFrameEnabled()); + EXPECT_EQ(client_bounds, widget->GetWindowBoundsInScreen()); + EXPECT_EQ(client_bounds, + frame_view->GetClientBoundsForWindowBounds(client_bounds)); +@@ -611,14 +611,14 @@ TEST_F(ClientControlledShellSurfaceTest, Frame) { + surface->Commit(); + + widget->LayoutRootViewIfNecessary(); +- EXPECT_TRUE(frame_view->GetVisible()); ++ EXPECT_TRUE(frame_view->GetFrameEnabled()); + EXPECT_TRUE(frame_view->GetHeaderView()->in_immersive_mode()); + + surface->SetFrame(SurfaceFrameType::NONE); + surface->Commit(); + + widget->LayoutRootViewIfNecessary(); +- EXPECT_FALSE(frame_view->GetVisible()); ++ EXPECT_FALSE(frame_view->GetFrameEnabled()); + EXPECT_FALSE(frame_view->GetHeaderView()->in_immersive_mode()); + } + +@@ -2015,7 +2015,7 @@ TEST_F(ClientControlledShellSurfaceTest, SnappedInTabletMode) { + // Snapped window can also use auto hide. + surface->SetFrame(SurfaceFrameType::AUTOHIDE); + surface->Commit(); +- EXPECT_TRUE(frame_view->GetVisible()); ++ EXPECT_TRUE(frame_view->GetFrameEnabled()); + EXPECT_TRUE(frame_view->GetHeaderView()->in_immersive_mode()); + } + +diff --git a/components/exo/shell_surface_base.cc b/components/exo/shell_surface_base.cc +index b123d175c82cde0121860c2763f8c2da0c1f3b51..ba83bbd01676e75a65f30743a95622a3c5c0e9fc 100644 +--- a/components/exo/shell_surface_base.cc ++++ b/components/exo/shell_surface_base.cc +@@ -111,8 +111,7 @@ class CustomFrameView : public ash::NonClientFrameViewAsh { + ShellSurfaceBase* shell_surface, + bool enabled) + : NonClientFrameViewAsh(widget), shell_surface_(shell_surface) { +- SetEnabled(enabled); +- SetVisible(enabled); ++ SetFrameEnabled(enabled); + if (!enabled) + NonClientFrameViewAsh::SetShouldPaintHeader(false); + } +@@ -121,7 +120,7 @@ class CustomFrameView : public ash::NonClientFrameViewAsh { + + // Overridden from ash::NonClientFrameViewAsh: + void SetShouldPaintHeader(bool paint) override { +- if (GetVisible()) { ++ if (GetFrameEnabled()) { + NonClientFrameViewAsh::SetShouldPaintHeader(paint); + return; + } +@@ -129,46 +128,46 @@ class CustomFrameView : public ash::NonClientFrameViewAsh { + + // Overridden from views::NonClientFrameView: + gfx::Rect GetBoundsForClientView() const override { +- if (GetVisible()) ++ if (GetFrameEnabled()) + return ash::NonClientFrameViewAsh::GetBoundsForClientView(); + return bounds(); + } + gfx::Rect GetWindowBoundsForClientBounds( + const gfx::Rect& client_bounds) const override { +- if (GetVisible()) { ++ if (GetFrameEnabled()) { + return ash::NonClientFrameViewAsh::GetWindowBoundsForClientBounds( + client_bounds); + } + return client_bounds; + } + int NonClientHitTest(const gfx::Point& point) override { +- if (GetVisible() || shell_surface_->server_side_resize()) ++ if (GetFrameEnabled() || shell_surface_->server_side_resize()) + return ash::NonClientFrameViewAsh::NonClientHitTest(point); + return GetWidget()->client_view()->NonClientHitTest(point); + } + void GetWindowMask(const gfx::Size& size, SkPath* window_mask) override { +- if (GetVisible()) ++ if (GetFrameEnabled()) + return ash::NonClientFrameViewAsh::GetWindowMask(size, window_mask); + } + void ResetWindowControls() override { +- if (GetVisible()) ++ if (GetFrameEnabled()) + return ash::NonClientFrameViewAsh::ResetWindowControls(); + } + void UpdateWindowIcon() override { +- if (GetVisible()) ++ if (GetFrameEnabled()) + return ash::NonClientFrameViewAsh::ResetWindowControls(); + } + void UpdateWindowTitle() override { +- if (GetVisible()) ++ if (GetFrameEnabled()) + return ash::NonClientFrameViewAsh::UpdateWindowTitle(); + } + void SizeConstraintsChanged() override { +- if (GetVisible()) ++ if (GetFrameEnabled()) + return ash::NonClientFrameViewAsh::SizeConstraintsChanged(); + } + gfx::Size GetMinimumSize() const override { + gfx::Size minimum_size = shell_surface_->GetMinimumSize(); +- if (GetVisible()) { ++ if (GetFrameEnabled()) { + return ash::NonClientFrameViewAsh::GetWindowBoundsForClientBounds( + gfx::Rect(minimum_size)) + .size(); +@@ -177,7 +176,7 @@ class CustomFrameView : public ash::NonClientFrameViewAsh { + } + gfx::Size GetMaximumSize() const override { + gfx::Size maximum_size = shell_surface_->GetMaximumSize(); +- if (GetVisible() && !maximum_size.IsEmpty()) { ++ if (GetFrameEnabled() && !maximum_size.IsEmpty()) { + return ash::NonClientFrameViewAsh::GetWindowBoundsForClientBounds( + gfx::Rect(maximum_size)) + .size(); +@@ -710,11 +709,10 @@ void ShellSurfaceBase::OnSetFrame(SurfaceFrameType frame_type) { + + CustomFrameView* frame_view = + static_cast(widget_->non_client_view()->frame_view()); +- if (frame_view->GetEnabled() == frame_enabled()) ++ if (frame_view->GetFrameEnabled() == frame_enabled()) + return; + +- frame_view->SetEnabled(frame_enabled()); +- frame_view->SetVisible(frame_enabled()); ++ frame_view->SetFrameEnabled(frame_enabled()); + frame_view->SetShouldPaintHeader(frame_enabled()); + widget_->GetRootView()->Layout(); + // TODO(oshima): We probably should wait applying these if the +diff --git a/components/ui_devtools/views/overlay_agent_unittest.cc b/components/ui_devtools/views/overlay_agent_unittest.cc +index 00c8f36619857e7e7b44a1d0c9382ae74b0dbb2a..9e6494d76eccb835a1b253e779542d9c9941d5e7 100644 +--- a/components/ui_devtools/views/overlay_agent_unittest.cc ++++ b/components/ui_devtools/views/overlay_agent_unittest.cc +@@ -114,12 +114,14 @@ class OverlayAgentTest : public views::ViewsTestBase { + } + #endif + +- void CreateWidget(const gfx::Rect& bounds) { ++ void CreateWidget(const gfx::Rect& bounds, ++ views::Widget::InitParams::Type type) { + widget_ = std::make_unique(); + views::Widget::InitParams params; + params.delegate = nullptr; + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.bounds = bounds; ++ params.type = type; + #if defined(USE_AURA) + params.parent = GetContext(); + #endif +@@ -129,7 +131,8 @@ class OverlayAgentTest : public views::ViewsTestBase { + + void CreateWidget() { + // Create a widget with default bounds. +- return CreateWidget(gfx::Rect(0, 0, 400, 400)); ++ return CreateWidget(gfx::Rect(0, 0, 400, 400), ++ views::Widget::InitParams::Type::TYPE_WINDOW); + } + + views::Widget* widget() { return widget_.get(); } +@@ -176,13 +179,14 @@ TEST_F(OverlayAgentTest, FindElementIdTargetedByPointWindow) { + #endif + + TEST_F(OverlayAgentTest, FindElementIdTargetedByPointViews) { +- CreateWidget(); ++ // Use a frameless window instead of deleting all children of |contents_view| ++ CreateWidget(gfx::Rect(0, 0, 400, 400), ++ views::Widget::InitParams::Type::TYPE_WINDOW_FRAMELESS); + + std::unique_ptr root; + dom_agent()->getDocument(&root); + +- views::View* contents_view = widget()->GetContentsView(); +- contents_view->RemoveAllChildViews(true); ++ views::View* contents_view = widget()->GetRootView(); + + views::View* child_1 = new views::View; + views::View* child_2 = new views::View; +@@ -203,7 +207,7 @@ TEST_F(OverlayAgentTest, FindElementIdTargetedByPointViews) { + child_1->SetBounds(20, 20, 100, 100); + child_2->SetBounds(90, 50, 100, 100); + +- EXPECT_EQ(GetViewAtPoint(1, 1), widget()->GetContentsView()); ++ EXPECT_EQ(GetViewAtPoint(1, 1), widget()->GetRootView()); + EXPECT_EQ(GetViewAtPoint(21, 21), child_1); + EXPECT_EQ(GetViewAtPoint(170, 130), child_2); + // At the overlap. +@@ -237,7 +241,7 @@ TEST_F(OverlayAgentTest, HighlightRects) { + + for (const auto& test_case : kTestCases) { + SCOPED_TRACE(testing::Message() << "Case: " << test_case.name); +- CreateWidget(kWidgetBounds); ++ CreateWidget(kWidgetBounds, views::Widget::InitParams::Type::TYPE_WINDOW); + // Can't just use kWidgetBounds because of Mac's menu bar. + gfx::Vector2d widget_screen_offset = + widget()->GetClientAreaBoundsInScreen().OffsetFromOrigin(); +diff --git a/third_party/wayland/features.gni b/third_party/wayland/features.gni +index ecdc2c72ff2b267ea180ddb04ad2c6e6842653ea..424be6e75be0e0da3d00e42ee82d596912acd7a1 100644 +--- a/third_party/wayland/features.gni ++++ b/third_party/wayland/features.gni +@@ -18,5 +18,5 @@ declare_args() { + + # This may be set by Chromium packagers who do not wish to use the bundled + # wayland scanner. +- use_system_wayland_scanner = (host_toolchain == default_toolchain && is_msan) ++ use_system_wayland_scanner = host_toolchain == default_toolchain && is_msan + } +diff --git a/ui/views/BUILD.gn b/ui/views/BUILD.gn +index c0e55a44f12a63a8a4a989b0de96df616cbb0709..d5dd660994324ef282b3affe7ed1d0da8892111f 100644 +--- a/ui/views/BUILD.gn ++++ b/ui/views/BUILD.gn +@@ -1196,7 +1196,6 @@ test("views_unittests") { + "window/dialog_delegate_unittest.cc", + "window/frame_caption_button_unittest.cc", + "window/hit_test_utils_unittest.cc", +- "window/non_client_view_unittest.cc", + ] + + configs += [ "//build/config:precompiled_headers" ] +diff --git a/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc b/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc +index d34d57d14514a799eca3a1cef2c07b513c5034fd..0cca80545e2d53caa3999c68529af9d32f20b2f5 100644 +--- a/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc ++++ b/ui/views/accessibility/view_ax_platform_node_delegate_unittest.cc +@@ -115,7 +115,8 @@ class ViewAXPlatformNodeDelegateTest : public ViewsTestBase { + ui::AXPlatformNode::NotifyAddAXModeFlags(ui::kAXModeComplete); + + widget_ = new Widget; +- Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); ++ Widget::InitParams params = ++ CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS); + params.bounds = gfx::Rect(0, 0, 200, 200); + widget_->Init(std::move(params)); + +@@ -127,7 +128,7 @@ class ViewAXPlatformNodeDelegateTest : public ViewsTestBase { + label_->SetID(DEFAULT_VIEW_ID); + button_->AddChildView(label_); + +- widget_->GetContentsView()->AddChildView(button_); ++ widget_->GetRootView()->AddChildView(button_); + widget_->Show(); + } + +@@ -163,7 +164,7 @@ class ViewAXPlatformNodeDelegateTest : public ViewsTestBase { + // child Views. + View::Views SetUpExtraViews() { + View* parent_view = +- widget_->GetContentsView()->AddChildView(std::make_unique()); ++ widget_->GetRootView()->AddChildView(std::make_unique()); + View::Views views{parent_view}; + for (int i = 0; i < 4; i++) + views.push_back(parent_view->AddChildView(std::make_unique())); +@@ -223,7 +224,7 @@ class ViewAXPlatformNodeDelegateTableTest + auto table = + std::make_unique(model_.get(), columns, TEXT_ONLY, true); + table_ = table.get(); +- widget_->GetContentsView()->AddChildView( ++ widget_->GetRootView()->AddChildView( + TableView::CreateScrollViewWithTable(std::move(table))); + } + +@@ -536,12 +537,12 @@ TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigation) { + ViewAXPlatformNodeDelegate* child_view_3 = view_accessibility(extra_views[3]); + ViewAXPlatformNodeDelegate* child_view_4 = view_accessibility(extra_views[4]); + +- EXPECT_EQ(view_accessibility(widget_->GetContentsView())->GetNativeObject(), ++ EXPECT_EQ(view_accessibility(widget_->GetRootView())->GetNativeObject(), + parent_view->GetParent()); + EXPECT_EQ(4, parent_view->GetChildCount()); + +- EXPECT_EQ(2, button_accessibility()->GetIndexInParent()); +- EXPECT_EQ(3, parent_view->GetIndexInParent()); ++ EXPECT_EQ(0, button_accessibility()->GetIndexInParent()); ++ EXPECT_EQ(1, parent_view->GetIndexInParent()); + + EXPECT_EQ(child_view_1->GetNativeObject(), parent_view->ChildAtIndex(0)); + EXPECT_EQ(child_view_2->GetNativeObject(), parent_view->ChildAtIndex(1)); +@@ -585,8 +586,6 @@ TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigationWithLeafViews) { + // view is added as the next sibling of the already present button view. + // + // Widget +- // ++NonClientView +- // ++NonClientFrameView + // ++Button + // ++++Label + // 0 = ++ParentView +@@ -596,7 +595,7 @@ TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigationWithLeafViews) { + // 4 = ++++ChildView4 + View::Views extra_views = SetUpExtraViews(); + ViewAXPlatformNodeDelegate* contents_view = +- view_accessibility(widget_->GetContentsView()); ++ view_accessibility(widget_->GetRootView()); + ViewAXPlatformNodeDelegate* parent_view = view_accessibility(extra_views[0]); + ViewAXPlatformNodeDelegate* child_view_1 = view_accessibility(extra_views[1]); + ViewAXPlatformNodeDelegate* child_view_2 = view_accessibility(extra_views[2]); +@@ -610,12 +609,12 @@ TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigationWithLeafViews) { + parent_view->OverrideIsLeaf(true); + child_view_2->OverrideIsLeaf(true); + +- EXPECT_EQ(4, contents_view->GetChildCount()); ++ EXPECT_EQ(2, contents_view->GetChildCount()); + EXPECT_EQ(contents_view->GetNativeObject(), parent_view->GetParent()); + EXPECT_EQ(0, parent_view->GetChildCount()); + +- EXPECT_EQ(2, button_accessibility()->GetIndexInParent()); +- EXPECT_EQ(3, parent_view->GetIndexInParent()); ++ EXPECT_EQ(0, button_accessibility()->GetIndexInParent()); ++ EXPECT_EQ(1, parent_view->GetIndexInParent()); + + EXPECT_FALSE(contents_view->IsIgnored()); + EXPECT_FALSE(parent_view->IsIgnored()); +@@ -647,12 +646,12 @@ TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigationWithLeafViews) { + // have no effect. + parent_view->OverrideIsLeaf(false); + +- EXPECT_EQ(4, contents_view->GetChildCount()); ++ EXPECT_EQ(2, contents_view->GetChildCount()); + EXPECT_EQ(contents_view->GetNativeObject(), parent_view->GetParent()); + EXPECT_EQ(4, parent_view->GetChildCount()); + +- EXPECT_EQ(2, button_accessibility()->GetIndexInParent()); +- EXPECT_EQ(3, parent_view->GetIndexInParent()); ++ EXPECT_EQ(0, button_accessibility()->GetIndexInParent()); ++ EXPECT_EQ(1, parent_view->GetIndexInParent()); + + EXPECT_FALSE(contents_view->IsIgnored()); + EXPECT_FALSE(parent_view->IsIgnored()); +@@ -691,8 +690,6 @@ TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigationWithIgnoredViews) { + // view is added as the next sibling of the already present button view. + // + // Widget +- // ++NonClientView +- // ++NonClientFrameView + // ++Button + // ++++Label + // 0 = ++ParentView +@@ -702,7 +699,7 @@ TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigationWithIgnoredViews) { + // 4 = ++++ChildView4 + View::Views extra_views = SetUpExtraViews(); + ViewAXPlatformNodeDelegate* contents_view = +- view_accessibility(widget_->GetContentsView()); ++ view_accessibility(widget_->GetRootView()); + ViewAXPlatformNodeDelegate* parent_view = view_accessibility(extra_views[0]); + ViewAXPlatformNodeDelegate* child_view_1 = view_accessibility(extra_views[1]); + ViewAXPlatformNodeDelegate* child_view_2 = view_accessibility(extra_views[2]); +@@ -716,7 +713,7 @@ TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigationWithIgnoredViews) { + EXPECT_EQ(contents_view->GetNativeObject(), parent_view->GetParent()); + EXPECT_EQ(3, parent_view->GetChildCount()); + +- EXPECT_EQ(2, button_accessibility()->GetIndexInParent()); ++ EXPECT_EQ(0, button_accessibility()->GetIndexInParent()); + EXPECT_EQ(-1, parent_view->GetIndexInParent()); + + EXPECT_EQ(child_view_1->GetNativeObject(), parent_view->ChildAtIndex(0)); +@@ -724,17 +721,17 @@ TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigationWithIgnoredViews) { + EXPECT_EQ(child_view_4->GetNativeObject(), parent_view->ChildAtIndex(2)); + + EXPECT_EQ(button_accessibility()->GetNativeObject(), +- contents_view->ChildAtIndex(2)); +- EXPECT_EQ(child_view_1->GetNativeObject(), contents_view->ChildAtIndex(3)); +- EXPECT_EQ(child_view_3->GetNativeObject(), contents_view->ChildAtIndex(4)); +- EXPECT_EQ(child_view_4->GetNativeObject(), contents_view->ChildAtIndex(5)); ++ contents_view->ChildAtIndex(0)); ++ EXPECT_EQ(child_view_1->GetNativeObject(), contents_view->ChildAtIndex(1)); ++ EXPECT_EQ(child_view_3->GetNativeObject(), contents_view->ChildAtIndex(2)); ++ EXPECT_EQ(child_view_4->GetNativeObject(), contents_view->ChildAtIndex(3)); + + EXPECT_EQ(nullptr, parent_view->GetNextSibling()); + EXPECT_EQ(nullptr, parent_view->GetPreviousSibling()); + + EXPECT_EQ(contents_view->GetNativeObject(), child_view_1->GetParent()); + EXPECT_EQ(0, child_view_1->GetChildCount()); +- EXPECT_EQ(3, child_view_1->GetIndexInParent()); ++ EXPECT_EQ(1, child_view_1->GetIndexInParent()); + EXPECT_EQ(child_view_3->GetNativeObject(), child_view_1->GetNextSibling()); + EXPECT_EQ(button_accessibility()->GetNativeObject(), + child_view_1->GetPreviousSibling()); +@@ -747,14 +744,14 @@ TEST_F(ViewAXPlatformNodeDelegateTest, TreeNavigationWithIgnoredViews) { + + EXPECT_EQ(contents_view->GetNativeObject(), child_view_3->GetParent()); + EXPECT_EQ(0, child_view_3->GetChildCount()); +- EXPECT_EQ(4, child_view_3->GetIndexInParent()); ++ EXPECT_EQ(2, child_view_3->GetIndexInParent()); + EXPECT_EQ(child_view_4->GetNativeObject(), child_view_3->GetNextSibling()); + EXPECT_EQ(child_view_1->GetNativeObject(), + child_view_3->GetPreviousSibling()); + + EXPECT_EQ(contents_view->GetNativeObject(), child_view_4->GetParent()); + EXPECT_EQ(0, child_view_4->GetChildCount()); +- EXPECT_EQ(5, child_view_4->GetIndexInParent()); ++ EXPECT_EQ(3, child_view_4->GetIndexInParent()); + EXPECT_EQ(nullptr, child_view_4->GetNextSibling()); + EXPECT_EQ(child_view_3->GetNativeObject(), + child_view_4->GetPreviousSibling()); +diff --git a/ui/views/bubble/bubble_frame_view.cc b/ui/views/bubble/bubble_frame_view.cc +index a2380fa3ac51d01c1858893d88df8ca25ea4adf4..95a4b1df05fba900db6116360bbdedf864f78e2a 100644 +--- a/ui/views/bubble/bubble_frame_view.cc ++++ b/ui/views/bubble/bubble_frame_view.cc +@@ -391,8 +391,20 @@ void BubbleFrameView::Layout() { + header_bottom = header_view_->bounds().bottom(); + } + +- if (bounds.IsEmpty()) ++ // Only account for footnote_container_'s height if it's visible, because ++ // content_margins_ adds extra padding even if all child views are invisible. ++ if (footnote_container_ && footnote_container_->GetVisible()) { ++ const int width = contents_bounds.width(); ++ const int height = footnote_container_->GetHeightForWidth(width); ++ footnote_container_->SetBounds( ++ contents_bounds.x(), contents_bounds.bottom() - height, width, height); ++ } ++ ++ NonClientFrameView::Layout(); ++ ++ if (bounds.IsEmpty()) { + return; ++ } + + // The buttons are positioned somewhat closer to the edge of the bubble. + const int close_margin = +@@ -442,15 +454,6 @@ void BubbleFrameView::Layout() { + + title_icon_->SetBounds(bounds.x(), bounds.y(), title_icon_pref_size.width(), + title_height); +- +- // Only account for footnote_container_'s height if it's visible, because +- // content_margins_ adds extra padding even if all child views are invisible. +- if (footnote_container_ && footnote_container_->GetVisible()) { +- const int width = contents_bounds.width(); +- const int height = footnote_container_->GetHeightForWidth(width); +- footnote_container_->SetBounds( +- contents_bounds.x(), contents_bounds.bottom() - height, width, height); +- } + } + + void BubbleFrameView::OnThemeChanged() { +diff --git a/ui/views/cocoa/drag_drop_client_mac_unittest.mm b/ui/views/cocoa/drag_drop_client_mac_unittest.mm +index 0f1881bd1fea1827d8c972c41817bc72b09efcd2..9966491dcd41da16521951424adb14f06134cc0c 100644 +--- a/ui/views/cocoa/drag_drop_client_mac_unittest.mm ++++ b/ui/views/cocoa/drag_drop_client_mac_unittest.mm +@@ -195,7 +195,7 @@ void SetUp() override { + widget_->Show(); + + target_ = new DragDropView(); +- widget_->GetContentsView()->AddChildView(target_); ++ widget_->non_client_view()->frame_view()->AddChildView(target_); + target_->SetBoundsRect(bounds); + + drag_drop_client()->source_operation_ = ui::DragDropTypes::DRAG_COPY; +@@ -329,7 +329,7 @@ DragOperation OnPerformDrop(const ui::DropTargetEvent& event) override { + SetData(data); + + target_ = new DragDropCloseView(); +- widget_->GetContentsView()->AddChildView(target_); ++ widget_->non_client_view()->frame_view()->AddChildView(target_); + target_->SetBoundsRect(gfx::Rect(0, 0, 100, 100)); + target_->set_formats(ui::OSExchangeData::STRING | ui::OSExchangeData::URL); + +diff --git a/ui/views/controls/table/table_view_unittest.cc b/ui/views/controls/table/table_view_unittest.cc +index 12cb14b36c6798f7151b7bb67841db53a30e29fb..f377aff61222583149a7fd615e3fc0ef806c13b3 100644 +--- a/ui/views/controls/table/table_view_unittest.cc ++++ b/ui/views/controls/table/table_view_unittest.cc +@@ -444,12 +444,13 @@ class TableViewTest : public ViewsTestBase, + helper_ = std::make_unique(table_); + + widget_ = std::make_unique(); +- Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW); ++ Widget::InitParams params = ++ CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS); + params.ownership = views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET; + params.bounds = gfx::Rect(0, 0, 650, 650); + params.delegate = GetWidgetDelegate(widget_.get()); + widget_->Init(std::move(params)); +- widget_->GetContentsView()->AddChildView(std::move(scroll_view)); ++ widget_->GetRootView()->AddChildView(std::move(scroll_view)); + widget_->Show(); + } + +diff --git a/ui/views/view_unittest_mac.mm b/ui/views/view_unittest_mac.mm +index f2e3724183b02739983cb1bcce06f7f222efc536..a3f5c3e28fc9ec48017e17dc4c6a1ac1150a57f6 100644 +--- a/ui/views/view_unittest_mac.mm ++++ b/ui/views/view_unittest_mac.mm +@@ -117,7 +117,7 @@ void SetUp() override { + + view_ = new ThreeFingerSwipeView; + view_->SetSize(widget_->GetClientAreaBoundsInScreen().size()); +- widget_->GetContentsView()->AddChildView(view_); ++ widget_->non_client_view()->frame_view()->AddChildView(view_); + } + + void TearDown() override { +diff --git a/ui/views/widget/native_widget_mac_unittest.mm b/ui/views/widget/native_widget_mac_unittest.mm +index 15e1197030b3f1e93ed617b9cb8ff2feaf3d35d8..7233e122869034a0db15908f2c8a8869000c04b5 100644 +--- a/ui/views/widget/native_widget_mac_unittest.mm ++++ b/ui/views/widget/native_widget_mac_unittest.mm +@@ -573,8 +573,10 @@ void WaitForPaintCount(int target) { + + Widget* widget = CreateTopLevelPlatformWidget(); + widget->SetBounds(gfx::Rect(0, 0, 300, 300)); +- widget->GetContentsView()->AddChildView(new CursorView(0, hand)); +- widget->GetContentsView()->AddChildView(new CursorView(100, ibeam)); ++ widget->non_client_view()->frame_view()->AddChildView( ++ new CursorView(0, hand)); ++ widget->non_client_view()->frame_view()->AddChildView( ++ new CursorView(100, ibeam)); + widget->Show(); + NSWindow* widget_window = widget->GetNativeWindow().GetNativeNSWindow(); + +@@ -879,8 +881,8 @@ void WaitForPaintCount(int target) { + const std::u16string long_tooltip(2000, 'W'); + + // Create a nested layout to test corner cases. +- LabelButton* back = +- widget->GetContentsView()->AddChildView(std::make_unique()); ++ LabelButton* back = widget->non_client_view()->frame_view()->AddChildView( ++ std::make_unique()); + back->SetBounds(10, 10, 80, 80); + widget->Show(); + +@@ -944,7 +946,7 @@ void WaitForPaintCount(int target) { + + CustomTooltipView* view_below = new CustomTooltipView(u"Back", view_above); + view_below->SetBoundsRect(widget_below->GetContentsView()->bounds()); +- widget_below->GetContentsView()->AddChildView(view_below); ++ widget_below->non_client_view()->frame_view()->AddChildView(view_below); + + widget_below->Show(); + widget_above->Show(); +diff --git a/ui/views/widget/widget_unittest.cc b/ui/views/widget/widget_unittest.cc +index 3ae33afe803df33138861760f455edc10c4022fa..66bdbb121ec2eb170fd2c423cfc21eddea05604c 100644 +--- a/ui/views/widget/widget_unittest.cc ++++ b/ui/views/widget/widget_unittest.cc +@@ -3808,7 +3808,7 @@ TEST_F(WidgetTest, MouseWheelEvent) { + WidgetAutoclosePtr widget(CreateTopLevelPlatformWidget()); + widget->SetBounds(gfx::Rect(0, 0, 600, 600)); + EventCountView* event_count_view = new EventCountView(); +- widget->GetContentsView()->AddChildView(event_count_view); ++ widget->client_view()->AddChildView(event_count_view); + event_count_view->SetBounds(0, 0, 600, 600); + widget->Show(); + +@@ -3819,81 +3819,6 @@ TEST_F(WidgetTest, MouseWheelEvent) { + EXPECT_EQ(1, event_count_view->GetEventCount(ui::ET_MOUSEWHEEL)); + } + +-class LayoutCountingView : public View { +- public: +- LayoutCountingView() = default; +- ~LayoutCountingView() override = default; +- +- void set_layout_closure(base::OnceClosure layout_closure) { +- layout_closure_ = std::move(layout_closure); +- } +- +- size_t GetAndClearLayoutCount() { +- const size_t count = layout_count_; +- layout_count_ = 0u; +- return count; +- } +- +- // View: +- void Layout() override { +- ++layout_count_; +- View::Layout(); +- if (layout_closure_) +- std::move(layout_closure_).Run(); +- } +- +- private: +- size_t layout_count_ = 0u; +- +- // If valid, this is run when Layout() is called. +- base::OnceClosure layout_closure_; +- +- DISALLOW_COPY_AND_ASSIGN(LayoutCountingView); +-}; +- +-using WidgetInvalidateLayoutTest = ViewsTestBaseWithNativeWidgetType; +- +-TEST_P(WidgetInvalidateLayoutTest, InvalidateLayout) { +- std::unique_ptr widget = +- CreateTestWidget(Widget::InitParams::TYPE_WINDOW); +- LayoutCountingView* view = +- widget->widget_delegate()->GetContentsView()->AddChildView( +- std::make_unique()); +- view->parent()->SetLayoutManager(std::make_unique()); +- // Force an initial Layout(). +- // TODO(sky): this shouldn't be necessary, adding a child view should trigger +- // ScheduleLayout(). +- view->Layout(); +- widget->Show(); +- +- ui::Compositor* compositor = widget->GetCompositor(); +- ASSERT_TRUE(compositor); +- compositor->ScheduleDraw(); +- ui::DrawWaiterForTest::WaitForCompositingEnded(compositor); +- +- base::RunLoop run_loop; +- view->GetAndClearLayoutCount(); +- // Don't use WaitForCompositingEnded() here as it's entirely possible nothing +- // will be drawn (which means WaitForCompositingEnded() isn't run). Instead +- // wait for Layout() to be called. +- view->set_layout_closure(run_loop.QuitClosure()); +- EXPECT_FALSE(ViewTestApi(view).needs_layout()); +- EXPECT_FALSE(ViewTestApi(widget->GetRootView()).needs_layout()); +- view->InvalidateLayout(); +- EXPECT_TRUE(ViewTestApi(view).needs_layout()); +- EXPECT_TRUE(ViewTestApi(widget->GetRootView()).needs_layout()); +- run_loop.Run(); +- EXPECT_EQ(1u, view->GetAndClearLayoutCount()); +- EXPECT_FALSE(ViewTestApi(view).needs_layout()); +- EXPECT_FALSE(ViewTestApi(widget->GetRootView()).needs_layout()); +-} +- +-INSTANTIATE_TEST_SUITE_P( +- WidgetInvalidateLayoutTest, +- WidgetInvalidateLayoutTest, +- ::testing::Values(ViewsTestBase::NativeWidgetType::kDefault, +- ViewsTestBase::NativeWidgetType::kDesktop)); +- + class WidgetShadowTest : public WidgetTest { + public: + WidgetShadowTest() = default; +diff --git a/ui/views/window/client_view.cc b/ui/views/window/client_view.cc +index 5527e26da2710ecfc50d361c5834495d622a935d..227efb29047d87b39e2e9b7e0b45b027000ea761 100644 +--- a/ui/views/window/client_view.cc ++++ b/ui/views/window/client_view.cc +@@ -87,8 +87,6 @@ void ClientView::ViewHierarchyChanged( + // TODO(weili): This seems fragile and can be refactored. + // Tracked at https://crbug.com/1012466. + AddChildViewAt(contents_view_, 0); +- } else if (!details.is_add && details.child == contents_view_) { +- contents_view_ = nullptr; + } + } + +diff --git a/ui/views/window/custom_frame_view.cc b/ui/views/window/custom_frame_view.cc +index eda68f689da621422ce17d7e1d50fe0ba4efe771..9c2f931e0e4d37c4653c9a7a7f34ea4d8b8a49b1 100644 +--- a/ui/views/window/custom_frame_view.cc ++++ b/ui/views/window/custom_frame_view.cc +@@ -211,6 +211,7 @@ void CustomFrameView::Layout() { + } + + LayoutClientView(); ++ NonClientFrameView::Layout(); + } + + gfx::Size CustomFrameView::CalculatePreferredSize() const { +diff --git a/ui/views/window/non_client_view.cc b/ui/views/window/non_client_view.cc +index 73170394a794489dd4c409a29d7b0f47a887e1dd..7d8bb5d42ca2730810f8384463058014c2aa2259 100644 +--- a/ui/views/window/non_client_view.cc ++++ b/ui/views/window/non_client_view.cc +@@ -11,6 +11,7 @@ + #include "ui/accessibility/ax_node_data.h" + #include "ui/base/hit_test.h" + #include "ui/gfx/geometry/rect_conversions.h" ++#include "ui/views/layout/fill_layout.h" + #include "ui/views/metadata/metadata_impl_macros.h" + #include "ui/views/rect_based_targeting_utils.h" + #include "ui/views/view_targeter.h" +@@ -24,18 +25,6 @@ + + namespace views { + +-namespace { +- +-// The frame view and the client view are always at these specific indices, +-// because the RootView message dispatch sends messages to items higher in the +-// z-order first and we always want the client view to have first crack at +-// handling mouse messages. +-constexpr int kFrameViewIndex = 0; +-constexpr int kClientViewIndex = 1; +-// The overlay view is always on top (view == children().back()). +- +-} // namespace +- + NonClientFrameView::~NonClientFrameView() = default; + + bool NonClientFrameView::ShouldPaintAsActive() const { +@@ -129,18 +118,19 @@ void NonClientFrameView::OnThemeChanged() { + SchedulePaint(); + } + +-NonClientFrameView::NonClientFrameView() { +- SetEventTargeter(std::make_unique(this)); +-} ++void NonClientFrameView::Layout() { ++ if (GetLayoutManager()) ++ GetLayoutManager()->Layout(this); + +-// ViewTargeterDelegate: +-bool NonClientFrameView::DoesIntersectRect(const View* target, +- const gfx::Rect& rect) const { +- CHECK_EQ(target, this); ++ views::ClientView* client_view = GetWidget()->client_view(); ++ client_view->SetBoundsRect(GetBoundsForClientView()); ++ SkPath client_clip; ++ if (GetClientMask(client_view->size(), &client_clip)) ++ client_view->SetClipPath(client_clip); ++} + +- // For the default case, we assume the non-client frame view never overlaps +- // the client view. +- return !GetWidget()->client_view()->bounds().Intersects(rect); ++NonClientFrameView::NonClientFrameView() { ++ SetEventTargeter(std::make_unique(this)); + } + + #if defined(OS_WIN) +@@ -165,13 +155,18 @@ NonClientView::~NonClientView() { + + void NonClientView::SetFrameView( + std::unique_ptr frame_view) { +- // See comment in header about ownership. +- frame_view->set_owned_by_client(); +- if (frame_view_.get()) ++ // If there is an existing frame view, remove the client view before removing ++ // the frame view to prevent the client view from being deleted. ++ if (frame_view_.get()) { ++ frame_view_->RemoveChildView(client_view_); + RemoveChildView(frame_view_.get()); ++ } ++ + frame_view_ = std::move(frame_view); +- if (parent()) +- AddChildViewAt(frame_view_.get(), kFrameViewIndex); ++ if (parent()) { ++ AddChildViewAt(frame_view_.get(), 0); ++ frame_view_->AddChildViewAt(client_view_, 0); ++ } + } + + void NonClientView::SetOverlayView(View* view) { +@@ -262,11 +257,6 @@ void NonClientView::Layout() { + // into a View hierarchy once" ( http://codereview.chromium.org/27317 ), but + // where that is still the case it should simply be fixed. + frame_view_->SetBoundsRect(GetLocalBounds()); +- client_view_->SetBoundsRect(frame_view_->GetBoundsForClientView()); +- +- SkPath client_clip; +- if (frame_view_->GetClientMask(client_view_->size(), &client_clip)) +- client_view_->SetClipPath(client_clip); + + if (overlay_view_) + overlay_view_->SetBoundsRect(GetLocalBounds()); +@@ -302,8 +292,8 @@ void NonClientView::ViewHierarchyChanged( + // the various setters, and create and add children directly in the + // constructor. + if (details.is_add && GetWidget() && details.child == this) { +- AddChildViewAt(frame_view_.get(), kFrameViewIndex); +- AddChildViewAt(client_view_, kClientViewIndex); ++ AddChildViewAt(frame_view_.get(), 0); ++ frame_view_->AddChildViewAt(client_view_, 0); + if (overlay_view_) + AddChildView(overlay_view_); + } +diff --git a/ui/views/window/non_client_view.h b/ui/views/window/non_client_view.h +index 58558cc0df457ba3c077784906c5ea4093c301c9..c47c5b6353cbf242e6790651d8382d813cb58250 100644 +--- a/ui/views/window/non_client_view.h ++++ b/ui/views/window/non_client_view.h +@@ -101,11 +101,7 @@ class VIEWS_EXPORT NonClientFrameView : public View, + // View: + void GetAccessibleNodeData(ui::AXNodeData* node_data) override; + void OnThemeChanged() override; +- +- protected: +- // ViewTargeterDelegate: +- bool DoesIntersectRect(const View* target, +- const gfx::Rect& rect) const override; ++ void Layout() override; + + private: + #if defined(OS_WIN) +@@ -121,10 +117,11 @@ class VIEWS_EXPORT NonClientFrameView : public View, + // + // The NonClientView is the logical root of all Views contained within a + // Window, except for the RootView which is its parent and of which it is the +-// sole child. The NonClientView has two children, the NonClientFrameView which ++// sole child. The NonClientView has one child, the NonClientFrameView which + // is responsible for painting and responding to events from the non-client +-// portions of the window, and the ClientView, which is responsible for the +-// same for the client area of the window: ++// portions of the window, and for forwarding events to its child, the ++// ClientView, which is responsible for the same for the client area of the ++// window: + // + // +- views::Widget ------------------------------------+ + // | +- views::RootView ------------------------------+ | +@@ -135,23 +132,17 @@ class VIEWS_EXPORT NonClientFrameView : public View, + // | | | | << of the non-client areas of a >> | | | | + // | | | | << views::Widget. >> | | | | + // | | | | | | | | +-// | | | +----------------------------------------+ | | | +-// | | | +- views::ClientView or subclass --------+ | | | +-// | | | | | | | | +-// | | | | << all painting and event receiving >> | | | | +-// | | | | << of the client areas of a >> | | | | +-// | | | | << views::Widget. >> | | | | +-// | | | | | | | | ++// | | | | +- views::ClientView or subclass ----+ | | | | ++// | | | | | | | | | | ++// | | | | | << all painting and event >> | | | | | ++// | | | | | << receiving of the client >> | | | | | ++// | | | | | << areas of a views::Widget. >> | | | | | ++// | | | | +----------------------------------+ | | | | | + // | | | +----------------------------------------+ | | | + // | | +--------------------------------------------+ | | + // | +------------------------------------------------+ | + // +----------------------------------------------------+ + // +-// The NonClientFrameView and ClientView are siblings because due to theme +-// changes the NonClientFrameView may be replaced with different +-// implementations (e.g. during the switch from DWM/Aero-Glass to Vista Basic/ +-// Classic rendering). +-// + class VIEWS_EXPORT NonClientView : public View, public ViewTargeterDelegate { + public: + METADATA_HEADER(NonClientView); +diff --git a/ui/views/window/non_client_view_unittest.cc b/ui/views/window/non_client_view_unittest.cc +deleted file mode 100644 +index fd66cb2c607fe47102fc14f5cc4d946863ac274c..0000000000000000000000000000000000000000 +--- a/ui/views/window/non_client_view_unittest.cc ++++ /dev/null +@@ -1,102 +0,0 @@ +-// Copyright 2018 The Chromium Authors. All rights reserved. +-// Use of this source code is governed by a BSD-style license that can be +-// found in the LICENSE file. +- +-#include "ui/views/window/non_client_view.h" +- +-#include +- +-#include "ui/views/test/views_test_base.h" +-#include "ui/views/widget/widget_delegate.h" +-#include "ui/views/window/client_view.h" +-#include "ui/views/window/native_frame_view.h" +- +-namespace views { +-namespace test { +- +-namespace { +- +-class NonClientFrameTestView : public NativeFrameView { +- public: +- using NativeFrameView::NativeFrameView; +- int layout_count() const { return layout_count_; } +- +- // NativeFrameView: +- void Layout() override { +- NativeFrameView::Layout(); +- ++layout_count_; +- } +- +- private: +- int layout_count_ = 0; +-}; +- +-class ClientTestView : public ClientView { +- public: +- using ClientView::ClientView; +- int layout_count() const { return layout_count_; } +- +- // ClientView: +- void Layout() override { +- ClientView::Layout(); +- ++layout_count_; +- } +- +- private: +- int layout_count_ = 0; +-}; +- +-class TestWidgetDelegate : public WidgetDelegateView { +- public: +- // WidgetDelegateView: +- std::unique_ptr CreateNonClientFrameView( +- Widget* widget) override { +- return std::make_unique(widget); +- } +- +- views::ClientView* CreateClientView(Widget* widget) override { +- return new ClientTestView(widget, this); +- } +-}; +- +-class NonClientViewTest : public ViewsTestBase { +- public: +- Widget::InitParams CreateParams(Widget::InitParams::Type type) override { +- Widget::InitParams params = ViewsTestBase::CreateParams(type); +- params.delegate = new TestWidgetDelegate; +- return params; +- } +-}; +- +-} // namespace +- +-// Ensure Layout() is not called excessively on a ClientView when Widget bounds +-// are changing. +-TEST_F(NonClientViewTest, OnlyLayoutChildViewsOnce) { +- std::unique_ptr widget = +- CreateTestWidget(Widget::InitParams::TYPE_WINDOW); +- +- NonClientView* non_client_view = widget->non_client_view(); +- non_client_view->Layout(); +- +- auto* frame_view = +- static_cast(non_client_view->frame_view()); +- auto* client_view = +- static_cast(non_client_view->client_view()); +- +- int initial_frame_view_layouts = frame_view->layout_count(); +- int initial_client_view_layouts = client_view->layout_count(); +- +- // Make sure it does no layout when nothing has changed. +- non_client_view->Layout(); +- EXPECT_EQ(frame_view->layout_count(), initial_frame_view_layouts); +- EXPECT_EQ(client_view->layout_count(), initial_client_view_layouts); +- +- // Ensure changing bounds triggers a (single) layout. +- widget->SetBounds(gfx::Rect(0, 0, 161, 100)); +- EXPECT_EQ(frame_view->layout_count(), initial_frame_view_layouts + 1); +- EXPECT_EQ(client_view->layout_count(), initial_client_view_layouts + 1); +-} +- +-} // namespace test +-} // namespace views diff --git a/shell/browser/native_window.cc b/shell/browser/native_window.cc index eb51d80134e49..dd80d4367ce5c 100644 --- a/shell/browser/native_window.cc +++ b/shell/browser/native_window.cc @@ -24,6 +24,34 @@ #include "ui/display/win/screen_win.h" #endif +namespace gin { + +template <> +struct Converter { + static bool FromV8(v8::Isolate* isolate, + v8::Handle val, + electron::NativeWindow::TitleBarStyle* out) { + using TitleBarStyle = electron::NativeWindow::TitleBarStyle; + std::string title_bar_style; + if (!ConvertFromV8(isolate, val, &title_bar_style)) + return false; + if (title_bar_style == "hidden") { + *out = TitleBarStyle::kHidden; +#if defined(OS_MAC) + } else if (title_bar_style == "hiddenInset") { + *out = TitleBarStyle::kHiddenInset; + } else if (title_bar_style == "customButtonsOnHover") { + *out = TitleBarStyle::kCustomButtonsOnHover; +#endif + } else { + return false; + } + return true; + } +}; + +} // namespace gin + namespace electron { namespace { @@ -54,7 +82,19 @@ NativeWindow::NativeWindow(const gin_helper::Dictionary& options, options.Get(options::kFrame, &has_frame_); options.Get(options::kTransparent, &transparent_); options.Get(options::kEnableLargerThanScreen, &enable_larger_than_screen_); - options.Get(options::ktitleBarOverlay, &titlebar_overlay_); + options.Get(options::kTitleBarStyle, &title_bar_style_); + + v8::Local titlebar_overlay; + if (options.Get(options::ktitleBarOverlay, &titlebar_overlay)) { + if (titlebar_overlay->IsBoolean()) { + options.Get(options::ktitleBarOverlay, &titlebar_overlay_); + } else if (titlebar_overlay->IsObject()) { + titlebar_overlay_ = true; +#if !defined(OS_WIN) + DCHECK(false); +#endif + } + } if (parent) options.Get("modal", &is_modal_); diff --git a/shell/browser/native_window.h b/shell/browser/native_window.h index 2515b2c9c1b18..77cf99e2824f1 100644 --- a/shell/browser/native_window.h +++ b/shell/browser/native_window.h @@ -315,6 +315,14 @@ class NativeWindow : public base::SupportsUserData, views::Widget* widget() const { return widget_.get(); } views::View* content_view() const { return content_view_; } + enum class TitleBarStyle { + kNormal, + kHidden, + kHiddenInset, + kCustomButtonsOnHover, + }; + TitleBarStyle title_bar_style() const { return title_bar_style_; } + bool has_frame() const { return has_frame_; } void set_has_frame(bool has_frame) { has_frame_ = has_frame; } @@ -346,8 +354,12 @@ class NativeWindow : public base::SupportsUserData, [&browser_view](NativeBrowserView* n) { return (n == browser_view); }); } + // The boolean parsing of the "titleBarOverlay" option bool titlebar_overlay_ = false; + // The "titleBarStyle" option. + TitleBarStyle title_bar_style_ = TitleBarStyle::kNormal; + private: std::unique_ptr widget_; diff --git a/shell/browser/native_window_mac.h b/shell/browser/native_window_mac.h index 33db0f782e991..3c953c30609c8 100644 --- a/shell/browser/native_window_mac.h +++ b/shell/browser/native_window_mac.h @@ -183,14 +183,6 @@ class NativeWindowMac : public NativeWindow, kInactive, }; - enum class TitleBarStyle { - kNormal, - kHidden, - kHiddenInset, - kCustomButtonsOnHover, - }; - TitleBarStyle title_bar_style() const { return title_bar_style_; } - ElectronPreviewItem* preview_item() const { return preview_item_.get(); } ElectronTouchBar* touch_bar() const { return touch_bar_.get(); } bool zoom_to_page_width() const { return zoom_to_page_width_; } @@ -263,9 +255,6 @@ class NativeWindowMac : public NativeWindow, // The presentation options before entering kiosk mode. NSApplicationPresentationOptions kiosk_options_; - // The "titleBarStyle" option. - TitleBarStyle title_bar_style_ = TitleBarStyle::kNormal; - // The "visualEffectState" option. VisualEffectState visual_effect_state_ = VisualEffectState::kFollowWindow; diff --git a/shell/browser/native_window_mac.mm b/shell/browser/native_window_mac.mm index 23afa90b6480f..0106e2f6d0e09 100644 --- a/shell/browser/native_window_mac.mm +++ b/shell/browser/native_window_mac.mm @@ -164,28 +164,6 @@ - (void)drawRect:(NSRect)dirtyRect { namespace gin { -template <> -struct Converter { - static bool FromV8(v8::Isolate* isolate, - v8::Handle val, - electron::NativeWindowMac::TitleBarStyle* out) { - using TitleBarStyle = electron::NativeWindowMac::TitleBarStyle; - std::string title_bar_style; - if (!ConvertFromV8(isolate, val, &title_bar_style)) - return false; - if (title_bar_style == "hidden") { - *out = TitleBarStyle::kHidden; - } else if (title_bar_style == "hiddenInset") { - *out = TitleBarStyle::kHiddenInset; - } else if (title_bar_style == "customButtonsOnHover") { - *out = TitleBarStyle::kCustomButtonsOnHover; - } else { - return false; - } - return true; - } -}; - template <> struct Converter { static bool FromV8(v8::Isolate* isolate, @@ -274,7 +252,6 @@ void ViewDidMoveToSuperview(NSView* self, SEL _cmd) { height); options.Get(options::kResizable, &resizable_); - options.Get(options::kTitleBarStyle, &title_bar_style_); options.Get(options::kZoomToPageWidth, &zoom_to_page_width_); options.Get(options::kSimpleFullScreen, &always_simple_fullscreen_); options.GetOptional(options::kTrafficLightPosition, &traffic_light_position_); diff --git a/shell/browser/native_window_views.cc b/shell/browser/native_window_views.cc index 667ce9506b455..8e9228e7e21a3 100644 --- a/shell/browser/native_window_views.cc +++ b/shell/browser/native_window_views.cc @@ -71,12 +71,14 @@ #elif defined(OS_WIN) #include "base/win/win_util.h" +#include "extensions/common/image_util.h" #include "shell/browser/ui/views/win_frame_view.h" #include "shell/browser/ui/win/electron_desktop_native_widget_aura.h" #include "skia/ext/skia_utils_win.h" #include "ui/base/win/shell.h" #include "ui/display/screen.h" #include "ui/display/win/screen_win.h" +#include "ui/gfx/color_utils.h" #include "ui/views/widget/desktop_aura/desktop_native_widget_aura.h" #endif @@ -170,6 +172,37 @@ NativeWindowViews::NativeWindowViews(const gin_helper::Dictionary& options, options.Get("thickFrame", &thick_frame_); if (transparent()) thick_frame_ = false; + + overlay_button_color_ = color_utils::GetSysSkColor(COLOR_BTNFACE); + overlay_symbol_color_ = color_utils::GetSysSkColor(COLOR_BTNTEXT); + + v8::Local titlebar_overlay; + if (options.Get(options::ktitleBarOverlay, &titlebar_overlay) && + titlebar_overlay->IsObject()) { + v8::Isolate* isolate = JavascriptEnvironment::GetIsolate(); + gin_helper::Dictionary titlebar_overlay_obj = + gin::Dictionary::CreateEmpty(isolate); + options.Get(options::ktitleBarOverlay, &titlebar_overlay_obj); + + std::string overlay_color_string; + if (titlebar_overlay_obj.Get(options::kOverlayButtonColor, + &overlay_color_string)) { + bool success = extensions::image_util::ParseCssColorString( + overlay_color_string, &overlay_button_color_); + DCHECK(success); + } + + std::string overlay_symbol_color_string; + if (titlebar_overlay_obj.Get(options::kOverlaySymbolColor, + &overlay_symbol_color_string)) { + bool success = extensions::image_util::ParseCssColorString( + overlay_symbol_color_string, &overlay_symbol_color_); + DCHECK(success); + } + } + + if (title_bar_style_ != TitleBarStyle::kNormal) + set_has_frame(false); #endif if (enable_larger_than_screen()) diff --git a/shell/browser/native_window_views.h b/shell/browser/native_window_views.h index 937667bd452df..4b9d61e8a2603 100644 --- a/shell/browser/native_window_views.h +++ b/shell/browser/native_window_views.h @@ -19,6 +19,7 @@ #if defined(OS_WIN) #include "base/win/scoped_gdi_object.h" #include "shell/browser/ui/win/taskbar_host.h" + #endif namespace views { @@ -174,6 +175,15 @@ class NativeWindowViews : public NativeWindow, TaskbarHost& taskbar_host() { return taskbar_host_; } #endif +#if defined(OS_WIN) + bool IsWindowControlsOverlayEnabled() const { + return (title_bar_style_ == NativeWindowViews::TitleBarStyle::kHidden) && + titlebar_overlay_; + } + SkColor overlay_button_color() const { return overlay_button_color_; } + SkColor overlay_symbol_color() const { return overlay_symbol_color_; } +#endif + private: // views::WidgetObserver: void OnWidgetActivationChanged(views::Widget* widget, bool active) override; @@ -294,6 +304,11 @@ class NativeWindowViews : public NativeWindow, // Whether the window is currently being moved. bool is_moving_ = false; + + // The color to use as the theme and symbol colors respectively for Window + // Controls Overlay if enabled on Windows. + SkColor overlay_button_color_; + SkColor overlay_symbol_color_; #endif // Handles unhandled keyboard messages coming back from the renderer process. diff --git a/shell/browser/ui/views/frameless_view.cc b/shell/browser/ui/views/frameless_view.cc index 734896a66ee53..ca81a55cfc0ab 100644 --- a/shell/browser/ui/views/frameless_view.cc +++ b/shell/browser/ui/views/frameless_view.cc @@ -85,17 +85,17 @@ int FramelessView::NonClientHitTest(const gfx::Point& cursor) { return HTCAPTION; } + // Support resizing frameless window by dragging the border. + int frame_component = ResizingBorderHitTest(cursor); + if (frame_component != HTNOWHERE) + return frame_component; + // Check for possible draggable region in the client area for the frameless // window. SkRegion* draggable_region = window_->draggable_region(); if (draggable_region && draggable_region->contains(cursor.x(), cursor.y())) return HTCAPTION; - // Support resizing frameless window by dragging the border. - int frame_component = ResizingBorderHitTest(cursor); - if (frame_component != HTNOWHERE) - return frame_component; - return HTCLIENT; } diff --git a/shell/browser/ui/views/frameless_view.h b/shell/browser/ui/views/frameless_view.h index 7719fa19c32f2..6fbd21a853b4b 100644 --- a/shell/browser/ui/views/frameless_view.h +++ b/shell/browser/ui/views/frameless_view.h @@ -48,6 +48,8 @@ class FramelessView : public views::NonClientFrameView { NativeWindowViews* window_ = nullptr; views::Widget* frame_ = nullptr; + friend class NativeWindowsViews; + private: DISALLOW_COPY_AND_ASSIGN(FramelessView); }; diff --git a/shell/browser/ui/views/win_caption_button.cc b/shell/browser/ui/views/win_caption_button.cc new file mode 100644 index 0000000000000..7ea67f02eeffe --- /dev/null +++ b/shell/browser/ui/views/win_caption_button.cc @@ -0,0 +1,220 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "shell/browser/ui/views/win_caption_button.h" + +#include + +#include "base/i18n/rtl.h" +#include "base/numerics/safe_conversions.h" +#include "chrome/browser/ui/frame/window_frame_util.h" +#include "chrome/grit/theme_resources.h" +#include "shell/browser/ui/views/win_frame_view.h" +#include "shell/common/color_util.h" +#include "ui/base/theme_provider.h" +#include "ui/gfx/animation/tween.h" +#include "ui/gfx/color_utils.h" +#include "ui/gfx/geometry/rect_conversions.h" +#include "ui/gfx/scoped_canvas.h" +#include "ui/views/metadata/metadata_impl_macros.h" + +namespace electron { + +WinCaptionButton::WinCaptionButton(PressedCallback callback, + WinFrameView* frame_view, + ViewID button_type, + const std::u16string& accessible_name) + : views::Button(std::move(callback)), + frame_view_(frame_view), + button_type_(button_type) { + SetAnimateOnStateChange(true); + // Not focusable by default, only for accessibility. + SetFocusBehavior(FocusBehavior::ACCESSIBLE_ONLY); + SetAccessibleName(accessible_name); +} + +gfx::Size WinCaptionButton::CalculatePreferredSize() const { + // TODO(bsep): The sizes in this function are for 1x device scale and don't + // match Windows button sizes at hidpi. + int height = WindowFrameUtil::kWindows10GlassCaptionButtonHeightRestored; + int base_width = WindowFrameUtil::kWindows10GlassCaptionButtonWidth; + return gfx::Size(base_width + GetBetweenButtonSpacing(), height); +} + +void WinCaptionButton::OnPaintBackground(gfx::Canvas* canvas) { + // Paint the background of the button (the semi-transparent rectangle that + // appears when you hover or press the button). + + const SkColor bg_color = frame_view_->window()->overlay_button_color(); + const SkAlpha theme_alpha = SkColorGetA(bg_color); + + gfx::Rect bounds = GetContentsBounds(); + bounds.Inset(0, 0, 0, 0); + + canvas->FillRect(bounds, SkColorSetA(bg_color, theme_alpha)); + + SkColor base_color; + SkAlpha hovered_alpha, pressed_alpha; + if (button_type_ == VIEW_ID_CLOSE_BUTTON) { + base_color = SkColorSetRGB(0xE8, 0x11, 0x23); + hovered_alpha = SK_AlphaOPAQUE; + pressed_alpha = 0x98; + } else { + // Match the native buttons. + base_color = frame_view_->GetReadableFeatureColor(bg_color); + hovered_alpha = 0x1A; + pressed_alpha = 0x33; + + if (theme_alpha > 0) { + // Theme buttons have slightly increased opacity to make them stand out + // against a visually-busy frame image. + constexpr float kAlphaScale = 1.3f; + hovered_alpha = base::ClampRound(hovered_alpha * kAlphaScale); + pressed_alpha = base::ClampRound(pressed_alpha * kAlphaScale); + } + } + + SkAlpha alpha; + if (GetState() == STATE_PRESSED) + alpha = pressed_alpha; + else + alpha = gfx::Tween::IntValueBetween(hover_animation().GetCurrentValue(), + SK_AlphaTRANSPARENT, hovered_alpha); + canvas->FillRect(bounds, SkColorSetA(base_color, alpha)); +} + +void WinCaptionButton::PaintButtonContents(gfx::Canvas* canvas) { + PaintSymbol(canvas); +} + +int WinCaptionButton::GetBetweenButtonSpacing() const { + const int display_order_index = GetButtonDisplayOrderIndex(); + return display_order_index == 0 + ? 0 + : WindowFrameUtil::kWindows10GlassCaptionButtonVisualSpacing; +} + +int WinCaptionButton::GetButtonDisplayOrderIndex() const { + int button_display_order = 0; + switch (button_type_) { + case VIEW_ID_MINIMIZE_BUTTON: + button_display_order = 0; + break; + case VIEW_ID_MAXIMIZE_BUTTON: + case VIEW_ID_RESTORE_BUTTON: + button_display_order = 1; + break; + case VIEW_ID_CLOSE_BUTTON: + button_display_order = 2; + break; + default: + NOTREACHED(); + return 0; + } + + // Reverse the ordering if we're in RTL mode + if (base::i18n::IsRTL()) + button_display_order = 2 - button_display_order; + + return button_display_order; +} + +namespace { + +// Canvas::DrawRect's stroke can bleed out of |rect|'s bounds, so this draws a +// rectangle inset such that the result is constrained to |rect|'s size. +void DrawRect(gfx::Canvas* canvas, + const gfx::Rect& rect, + const cc::PaintFlags& flags) { + gfx::RectF rect_f(rect); + float stroke_half_width = flags.getStrokeWidth() / 2; + rect_f.Inset(stroke_half_width, stroke_half_width); + canvas->DrawRect(rect_f, flags); +} + +} // namespace + +void WinCaptionButton::PaintSymbol(gfx::Canvas* canvas) { + SkColor symbol_color = frame_view_->window()->overlay_symbol_color(); + + if (button_type_ == VIEW_ID_CLOSE_BUTTON && + hover_animation().is_animating()) { + symbol_color = gfx::Tween::ColorValueBetween( + hover_animation().GetCurrentValue(), symbol_color, SK_ColorWHITE); + } else if (button_type_ == VIEW_ID_CLOSE_BUTTON && + (GetState() == STATE_HOVERED || GetState() == STATE_PRESSED)) { + symbol_color = SK_ColorWHITE; + } + + gfx::ScopedCanvas scoped_canvas(canvas); + const float scale = canvas->UndoDeviceScaleFactor(); + + const int symbol_size_pixels = std::round(10 * scale); + gfx::RectF bounds_rect(GetContentsBounds()); + bounds_rect.Scale(scale); + gfx::Rect symbol_rect(gfx::ToEnclosingRect(bounds_rect)); + symbol_rect.ClampToCenteredSize( + gfx::Size(symbol_size_pixels, symbol_size_pixels)); + + cc::PaintFlags flags; + flags.setAntiAlias(false); + flags.setColor(symbol_color); + flags.setStyle(cc::PaintFlags::kStroke_Style); + // Stroke width jumps up a pixel every time we reach a new integral scale. + const int stroke_width = std::floor(scale); + flags.setStrokeWidth(stroke_width); + + switch (button_type_) { + case VIEW_ID_MINIMIZE_BUTTON: { + const int y = symbol_rect.CenterPoint().y(); + const gfx::Point p1 = gfx::Point(symbol_rect.x(), y); + const gfx::Point p2 = gfx::Point(symbol_rect.right(), y); + canvas->DrawLine(p1, p2, flags); + return; + } + + case VIEW_ID_MAXIMIZE_BUTTON: + DrawRect(canvas, symbol_rect, flags); + return; + + case VIEW_ID_RESTORE_BUTTON: { + // Bottom left ("in front") square. + const int separation = std::floor(2 * scale); + symbol_rect.Inset(0, separation, separation, 0); + DrawRect(canvas, symbol_rect, flags); + + // Top right ("behind") square. + canvas->ClipRect(symbol_rect, SkClipOp::kDifference); + symbol_rect.Offset(separation, -separation); + DrawRect(canvas, symbol_rect, flags); + return; + } + + case VIEW_ID_CLOSE_BUTTON: { + flags.setAntiAlias(true); + // The close button's X is surrounded by a "halo" of transparent pixels. + // When the X is white, the transparent pixels need to be a bit brighter + // to be visible. + const float stroke_halo = + stroke_width * (symbol_color == SK_ColorWHITE ? 0.1f : 0.05f); + flags.setStrokeWidth(stroke_width + stroke_halo); + + // TODO(bsep): This sometimes draws misaligned at fractional device scales + // because the button's origin isn't necessarily aligned to pixels. + canvas->ClipRect(symbol_rect); + SkPath path; + path.moveTo(symbol_rect.x(), symbol_rect.y()); + path.lineTo(symbol_rect.right(), symbol_rect.bottom()); + path.moveTo(symbol_rect.right(), symbol_rect.y()); + path.lineTo(symbol_rect.x(), symbol_rect.bottom()); + canvas->DrawPath(path, flags); + return; + } + + default: + NOTREACHED(); + return; + } +} +} // namespace electron diff --git a/shell/browser/ui/views/win_caption_button.h b/shell/browser/ui/views/win_caption_button.h new file mode 100644 index 0000000000000..a70591665cd99 --- /dev/null +++ b/shell/browser/ui/views/win_caption_button.h @@ -0,0 +1,54 @@ +// Copyright (c) 2016 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_H_ +#define SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_H_ + +#include "chrome/browser/ui/view_ids.h" +#include "ui/gfx/canvas.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/metadata/metadata_header_macros.h" + +namespace electron { + +class WinFrameView; + +class WinCaptionButton : public views::Button { + public: + WinCaptionButton(PressedCallback callback, + WinFrameView* frame_view, + ViewID button_type, + const std::u16string& accessible_name); + WinCaptionButton(const WinCaptionButton&) = delete; + WinCaptionButton& operator=(const WinCaptionButton&) = delete; + + // // views::Button: + gfx::Size CalculatePreferredSize() const override; + void OnPaintBackground(gfx::Canvas* canvas) override; + void PaintButtonContents(gfx::Canvas* canvas) override; + + // private: + // Returns the amount we should visually reserve on the left (right in RTL) + // for spacing between buttons. We do this instead of repositioning the + // buttons to avoid the sliver of deadspace that would result. + int GetBetweenButtonSpacing() const; + + // Returns the order in which this button will be displayed (with 0 being + // drawn farthest to the left, and larger indices being drawn to the right of + // smaller indices). + int GetButtonDisplayOrderIndex() const; + + // The base color to use for the button symbols and background blending. Uses + // the more readable of black and white. + SkColor GetBaseColor() const; + + // Paints the minimize/maximize/restore/close icon for the button. + void PaintSymbol(gfx::Canvas* canvas); + + WinFrameView* frame_view_; + ViewID button_type_; +}; +} // namespace electron + +#endif // SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_H_ diff --git a/shell/browser/ui/views/win_caption_button_container.cc b/shell/browser/ui/views/win_caption_button_container.cc new file mode 100644 index 0000000000000..aba141e722512 --- /dev/null +++ b/shell/browser/ui/views/win_caption_button_container.cc @@ -0,0 +1,143 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "shell/browser/ui/views/win_caption_button_container.h" + +#include +#include + +#include "shell/browser/ui/views/win_caption_button.h" +#include "shell/browser/ui/views/win_frame_view.h" +#include "ui/base/l10n/l10n_util.h" +#include "ui/strings/grit/ui_strings.h" +#include "ui/views/layout/flex_layout.h" +#include "ui/views/view_class_properties.h" + +namespace electron { + +namespace { + +std::unique_ptr CreateCaptionButton( + views::Button::PressedCallback callback, + WinFrameView* frame_view, + ViewID button_type, + int accessible_name_resource_id) { + return std::make_unique( + std::move(callback), frame_view, button_type, + l10n_util::GetStringUTF16(accessible_name_resource_id)); +} + +bool HitTestCaptionButton(WinCaptionButton* button, const gfx::Point& point) { + return button && button->GetVisible() && button->bounds().Contains(point); +} + +} // anonymous namespace + +WinCaptionButtonContainer::WinCaptionButtonContainer(WinFrameView* frame_view) + : frame_view_(frame_view), + minimize_button_(AddChildView(CreateCaptionButton( + base::BindRepeating(&views::Widget::Minimize, + base::Unretained(frame_view_->frame())), + frame_view_, + VIEW_ID_MINIMIZE_BUTTON, + IDS_APP_ACCNAME_MINIMIZE))), + maximize_button_(AddChildView(CreateCaptionButton( + base::BindRepeating(&views::Widget::Maximize, + base::Unretained(frame_view_->frame())), + frame_view_, + VIEW_ID_MAXIMIZE_BUTTON, + IDS_APP_ACCNAME_MAXIMIZE))), + restore_button_(AddChildView(CreateCaptionButton( + base::BindRepeating(&views::Widget::Restore, + base::Unretained(frame_view_->frame())), + frame_view_, + VIEW_ID_RESTORE_BUTTON, + IDS_APP_ACCNAME_RESTORE))), + close_button_(AddChildView(CreateCaptionButton( + base::BindRepeating(&views::Widget::CloseWithReason, + base::Unretained(frame_view_->frame()), + views::Widget::ClosedReason::kCloseButtonClicked), + frame_view_, + VIEW_ID_CLOSE_BUTTON, + IDS_APP_ACCNAME_CLOSE))) { + // Layout is horizontal, with buttons placed at the trailing end of the view. + // This allows the container to expand to become a faux titlebar/drag handle. + auto* const layout = SetLayoutManager(std::make_unique()); + layout->SetOrientation(views::LayoutOrientation::kHorizontal) + .SetMainAxisAlignment(views::LayoutAlignment::kEnd) + .SetCrossAxisAlignment(views::LayoutAlignment::kStart) + .SetDefault( + views::kFlexBehaviorKey, + views::FlexSpecification(views::LayoutOrientation::kHorizontal, + views::MinimumFlexSizeRule::kPreferred, + views::MaximumFlexSizeRule::kPreferred, + /* adjust_width_for_height */ false, + views::MinimumFlexSizeRule::kScaleToZero)); +} + +WinCaptionButtonContainer::~WinCaptionButtonContainer() {} + +int WinCaptionButtonContainer::NonClientHitTest(const gfx::Point& point) const { + DCHECK(HitTestPoint(point)) + << "should only be called with a point inside this view's bounds"; + if (HitTestCaptionButton(minimize_button_, point)) { + return HTMINBUTTON; + } + if (HitTestCaptionButton(maximize_button_, point)) { + return HTMAXBUTTON; + } + if (HitTestCaptionButton(restore_button_, point)) { + return HTMAXBUTTON; + } + if (HitTestCaptionButton(close_button_, point)) { + return HTCLOSE; + } + return HTCAPTION; +} + +void WinCaptionButtonContainer::ResetWindowControls() { + minimize_button_->SetState(views::Button::STATE_NORMAL); + maximize_button_->SetState(views::Button::STATE_NORMAL); + restore_button_->SetState(views::Button::STATE_NORMAL); + close_button_->SetState(views::Button::STATE_NORMAL); + InvalidateLayout(); +} + +void WinCaptionButtonContainer::AddedToWidget() { + views::Widget* const widget = GetWidget(); + + DCHECK(!widget_observation_.IsObserving()); + widget_observation_.Observe(widget); + + UpdateButtons(); + + if (frame_view_->window()->IsWindowControlsOverlayEnabled()) { + SetPaintToLayer(); + } +} + +void WinCaptionButtonContainer::RemovedFromWidget() { + DCHECK(widget_observation_.IsObserving()); + widget_observation_.Reset(); +} + +void WinCaptionButtonContainer::OnWidgetBoundsChanged( + views::Widget* widget, + const gfx::Rect& new_bounds) { + UpdateButtons(); +} + +void WinCaptionButtonContainer::UpdateButtons() { + const bool is_maximized = frame_view_->frame()->IsMaximized(); + restore_button_->SetVisible(is_maximized); + maximize_button_->SetVisible(!is_maximized); + + // In touch mode, windows cannot be taken out of fullscreen or tiled mode, so + // the maximize/restore button should be disabled. + const bool is_touch = ui::TouchUiController::Get()->touch_ui(); + restore_button_->SetEnabled(!is_touch); + maximize_button_->SetEnabled(!is_touch); + InvalidateLayout(); +} +} // namespace electron diff --git a/shell/browser/ui/views/win_caption_button_container.h b/shell/browser/ui/views/win_caption_button_container.h new file mode 100644 index 0000000000000..b8cd11f6fbe77 --- /dev/null +++ b/shell/browser/ui/views/win_caption_button_container.h @@ -0,0 +1,70 @@ +// Copyright 2020 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_CONTAINER_H_ +#define SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_CONTAINER_H_ + +#include "base/scoped_observation.h" +#include "ui/base/pointer/touch_ui_controller.h" +#include "ui/views/controls/button/button.h" +#include "ui/views/metadata/metadata_header_macros.h" +#include "ui/views/view.h" +#include "ui/views/widget/widget.h" +#include "ui/views/widget/widget_observer.h" + +namespace electron { + +class WinFrameView; +class WinCaptionButton; + +// Provides a container for Windows 10 caption buttons that can be moved between +// frame and browser window as needed. When extended horizontally, becomes a +// grab bar for moving the window. +class WinCaptionButtonContainer : public views::View, + public views::WidgetObserver { + public: + explicit WinCaptionButtonContainer(WinFrameView* frame_view); + ~WinCaptionButtonContainer() override; + + // Tests to see if the specified |point| (which is expressed in this view's + // coordinates and which must be within this view's bounds) is within one of + // the caption buttons. Returns one of HitTestCompat enum defined in + // ui/base/hit_test.h, HTCAPTION if the area hit would be part of the window's + // drag handle, and HTNOWHERE otherwise. + // See also ClientView::NonClientHitTest. + int NonClientHitTest(const gfx::Point& point) const; + + private: + // views::View: + void AddedToWidget() override; + void RemovedFromWidget() override; + + // views::WidgetObserver: + void OnWidgetBoundsChanged(views::Widget* widget, + const gfx::Rect& new_bounds) override; + + void ResetWindowControls(); + + // Sets caption button visibility and enabled state based on window state. + // Only one of maximize or restore button should ever be visible at the same + // time, and both are disabled in tablet UI mode. + void UpdateButtons(); + + WinFrameView* const frame_view_; + WinCaptionButton* const minimize_button_; + WinCaptionButton* const maximize_button_; + WinCaptionButton* const restore_button_; + WinCaptionButton* const close_button_; + + base::ScopedObservation + widget_observation_{this}; + + base::CallbackListSubscription subscription_ = + ui::TouchUiController::Get()->RegisterCallback( + base::BindRepeating(&WinCaptionButtonContainer::UpdateButtons, + base::Unretained(this))); +}; +} // namespace electron + +#endif // SHELL_BROWSER_UI_VIEWS_WIN_CAPTION_BUTTON_CONTAINER_H_ diff --git a/shell/browser/ui/views/win_frame_view.cc b/shell/browser/ui/views/win_frame_view.cc index 55119cf1e4222..cacde477d78fd 100644 --- a/shell/browser/ui/views/win_frame_view.cc +++ b/shell/browser/ui/views/win_frame_view.cc @@ -1,11 +1,24 @@ // Copyright (c) 2014 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. +// +// Portions of this file are sourced from +// chrome/browser/ui/views/frame/glass_browser_frame_view.cc, +// Copyright (c) 2012 The Chromium Authors, +// which is governed by a BSD-style license #include "shell/browser/ui/views/win_frame_view.h" +#include +#include + #include "base/win/windows_version.h" #include "shell/browser/native_window_views.h" +#include "shell/browser/ui/views/win_caption_button_container.h" +#include "ui/base/win/hwnd_metrics.h" +#include "ui/display/win/dpi.h" +#include "ui/display/win/screen_win.h" +#include "ui/gfx/geometry/dip_util.h" #include "ui/views/widget/widget.h" #include "ui/views/win/hwnd_util.h" @@ -17,6 +30,30 @@ WinFrameView::WinFrameView() {} WinFrameView::~WinFrameView() {} +void WinFrameView::Init(NativeWindowViews* window, views::Widget* frame) { + window_ = window; + frame_ = frame; + + if (window->IsWindowControlsOverlayEnabled()) { + caption_button_container_ = + AddChildView(std::make_unique(this)); + } else { + caption_button_container_ = nullptr; + } +} + +SkColor WinFrameView::GetReadableFeatureColor(SkColor background_color) { + // color_utils::GetColorWithMaxContrast()/IsDark() aren't used here because + // they switch based on the Chrome light/dark endpoints, while we want to use + // the system native behavior below. + const auto windows_luma = [](SkColor c) { + return 0.25f * SkColorGetR(c) + 0.625f * SkColorGetG(c) + + 0.125f * SkColorGetB(c); + }; + return windows_luma(background_color) <= 128.0f ? SK_ColorWHITE + : SK_ColorBLACK; +} + gfx::Rect WinFrameView::GetWindowBoundsForClientBounds( const gfx::Rect& client_bounds) const { return views::GetWindowBoundsForClientBounds( @@ -24,15 +61,195 @@ gfx::Rect WinFrameView::GetWindowBoundsForClientBounds( client_bounds); } +int WinFrameView::FrameBorderThickness() const { + return (IsMaximized() || frame()->IsFullscreen()) + ? 0 + : display::win::ScreenWin::GetSystemMetricsInDIP(SM_CXSIZEFRAME); +} + int WinFrameView::NonClientHitTest(const gfx::Point& point) { if (window_->has_frame()) return frame_->client_view()->NonClientHitTest(point); - else - return FramelessView::NonClientHitTest(point); + + if (ShouldCustomDrawSystemTitlebar()) { + // See if the point is within any of the window controls. + if (caption_button_container_) { + gfx::Point local_point = point; + + ConvertPointToTarget(parent(), caption_button_container_, &local_point); + if (caption_button_container_->HitTestPoint(local_point)) { + const int hit_test_result = + caption_button_container_->NonClientHitTest(local_point); + if (hit_test_result != HTNOWHERE) + return hit_test_result; + } + } + + // On Windows 8+, the caption buttons are almost butted up to the top right + // corner of the window. This code ensures the mouse isn't set to a size + // cursor while hovering over the caption buttons, thus giving the incorrect + // impression that the user can resize the window. + if (base::win::GetVersion() >= base::win::Version::WIN8) { + RECT button_bounds = {0}; + if (SUCCEEDED(DwmGetWindowAttribute( + views::HWNDForWidget(frame()), DWMWA_CAPTION_BUTTON_BOUNDS, + &button_bounds, sizeof(button_bounds)))) { + gfx::RectF button_bounds_in_dips = gfx::ConvertRectToDips( + gfx::Rect(button_bounds), display::win::GetDPIScale()); + // TODO(crbug.com/1131681): GetMirroredRect() requires an integer rect, + // but the size in DIPs may not be an integer with a fractional device + // scale factor. If we want to keep using integers, the choice to use + // ToFlooredRectDeprecated() seems to be doing the wrong thing given the + // comment below about insetting 1 DIP instead of 1 physical pixel. We + // should probably use ToEnclosedRect() and then we could have inset 1 + // physical pixel here. + gfx::Rect buttons = GetMirroredRect( + gfx::ToFlooredRectDeprecated(button_bounds_in_dips)); + + // There is a small one-pixel strip right above the caption buttons in + // which the resize border "peeks" through. + constexpr int kCaptionButtonTopInset = 1; + // The sizing region at the window edge above the caption buttons is + // 1 px regardless of scale factor. If we inset by 1 before converting + // to DIPs, the precision loss might eliminate this region entirely. The + // best we can do is to inset after conversion. This guarantees we'll + // show the resize cursor when resizing is possible. The cost of which + // is also maybe showing it over the portion of the DIP that isn't the + // outermost pixel. + buttons.Inset(0, kCaptionButtonTopInset, 0, 0); + if (buttons.Contains(point)) + return HTNOWHERE; + } + } + + int top_border_thickness = FrameTopBorderThickness(false); + // At the window corners the resize area is not actually bigger, but the 16 + // pixels at the end of the top and bottom edges trigger diagonal resizing. + constexpr int kResizeCornerWidth = 16; + int window_component = GetHTComponentForFrame( + point, top_border_thickness, top_border_thickness, top_border_thickness, + kResizeCornerWidth - FrameBorderThickness(), + frame()->widget_delegate()->CanResize()); + if (window_component != HTNOWHERE) + return window_component; + } + + // Use the parent class's hittest last + return FramelessView::NonClientHitTest(point); } const char* WinFrameView::GetClassName() const { return kViewClassName; } +bool WinFrameView::IsMaximized() const { + return frame()->IsMaximized(); +} + +bool WinFrameView::ShouldCustomDrawSystemTitlebar() const { + return window()->IsWindowControlsOverlayEnabled(); +} + +void WinFrameView::Layout() { + LayoutCaptionButtons(); + if (window()->IsWindowControlsOverlayEnabled()) { + LayoutWindowControlsOverlay(); + } + NonClientFrameView::Layout(); +} + +int WinFrameView::FrameTopBorderThickness(bool restored) const { + // Mouse and touch locations are floored but GetSystemMetricsInDIP is rounded, + // so we need to floor instead or else the difference will cause the hittest + // to fail when it ought to succeed. + return std::floor( + FrameTopBorderThicknessPx(restored) / + display::win::ScreenWin::GetScaleFactorForHWND(HWNDForView(this))); +} + +int WinFrameView::FrameTopBorderThicknessPx(bool restored) const { + // Distinct from FrameBorderThickness() because we can't inset the top + // border, otherwise Windows will give us a standard titlebar. + // For maximized windows this is not true, and the top border must be + // inset in order to avoid overlapping the monitor above. + + // See comments in BrowserDesktopWindowTreeHostWin::GetClientAreaInsets(). + const bool needs_no_border = + (ShouldCustomDrawSystemTitlebar() && frame()->IsMaximized()) || + frame()->IsFullscreen(); + if (needs_no_border && !restored) + return 0; + + // Note that this method assumes an equal resize handle thickness on all + // sides of the window. + // TODO(dfried): Consider having it return a gfx::Insets object instead. + return ui::GetFrameThickness( + MonitorFromWindow(HWNDForView(this), MONITOR_DEFAULTTONEAREST)); +} + +int WinFrameView::TitlebarMaximizedVisualHeight() const { + int maximized_height = + display::win::ScreenWin::GetSystemMetricsInDIP(SM_CYCAPTION); + return maximized_height; +} + +int WinFrameView::TitlebarHeight(bool restored) const { + if (frame()->IsFullscreen() && !restored) + return 0; + + return TitlebarMaximizedVisualHeight() + FrameTopBorderThickness(false); +} + +int WinFrameView::WindowTopY() const { + // The window top is SM_CYSIZEFRAME pixels when maximized (see the comment in + // FrameTopBorderThickness()) and floor(system dsf) pixels when restored. + // Unfortunately we can't represent either of those at hidpi without using + // non-integral dips, so we return the closest reasonable values instead. + if (IsMaximized()) + return FrameTopBorderThickness(false); + + return 1; +} + +void WinFrameView::LayoutCaptionButtons() { + if (!caption_button_container_) + return; + + // Non-custom system titlebar already contains caption buttons. + if (!ShouldCustomDrawSystemTitlebar()) { + caption_button_container_->SetVisible(false); + return; + } + + caption_button_container_->SetVisible(true); + + const gfx::Size preferred_size = + caption_button_container_->GetPreferredSize(); + int height = preferred_size.height(); + + height = IsMaximized() ? TitlebarMaximizedVisualHeight() + : TitlebarHeight(false) - WindowTopY(); + + // TODO(mlaurencin): This -1 creates a 1 pixel gap between the right + // edge of the overlay and the edge of the window, allowing for this edge + // portion to return the correct hit test and be manually resized properly. + // Alternatives can be explored, but the differences in view structures + // between Electron and Chromium may result in this as the best option. + + caption_button_container_->SetBounds(width() - preferred_size.width(), + WindowTopY(), preferred_size.width() - 1, + height); +} + +void WinFrameView::LayoutWindowControlsOverlay() { + int overlay_height = caption_button_container_->size().height(); + int overlay_width = caption_button_container_->size().width(); + int bounding_rect_width = width() - overlay_width; + auto bounding_rect = + GetMirroredRect(gfx::Rect(0, 0, bounding_rect_width, overlay_height)); + + window()->SetWindowControlsOverlayRect(bounding_rect); + window()->NotifyLayoutWindowControlsOverlay(); +} + } // namespace electron diff --git a/shell/browser/ui/views/win_frame_view.h b/shell/browser/ui/views/win_frame_view.h index 884a13e506c3e..c0311a38f11f8 100644 --- a/shell/browser/ui/views/win_frame_view.h +++ b/shell/browser/ui/views/win_frame_view.h @@ -1,11 +1,18 @@ // Copyright (c) 2014 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. +// +// Portions of this file are sourced from +// chrome/browser/ui/views/frame/glass_browser_frame_view.h, +// Copyright (c) 2012 The Chromium Authors, +// which is governed by a BSD-style license #ifndef SHELL_BROWSER_UI_VIEWS_WIN_FRAME_VIEW_H_ #define SHELL_BROWSER_UI_VIEWS_WIN_FRAME_VIEW_H_ +#include "shell/browser/native_window_views.h" #include "shell/browser/ui/views/frameless_view.h" +#include "shell/browser/ui/views/win_caption_button.h" namespace electron { @@ -15,6 +22,14 @@ class WinFrameView : public FramelessView { WinFrameView(); ~WinFrameView() override; + void Init(NativeWindowViews* window, views::Widget* frame) override; + + // Alpha to use for features in the titlebar (the window title and caption + // buttons) when the window is inactive. They are opaque when active. + static constexpr SkAlpha kInactiveTitlebarFeatureAlpha = 0x66; + + SkColor GetReadableFeatureColor(SkColor background_color); + // views::NonClientFrameView: gfx::Rect GetWindowBoundsForClientBounds( const gfx::Rect& client_bounds) const override; @@ -23,7 +38,50 @@ class WinFrameView : public FramelessView { // views::View: const char* GetClassName() const override; + NativeWindowViews* window() const { return window_; } + views::Widget* frame() const { return frame_; } + + bool IsMaximized() const; + + bool ShouldCustomDrawSystemTitlebar() const; + + // Visual height of the titlebar when the window is maximized (i.e. excluding + // the area above the top of the screen). + int TitlebarMaximizedVisualHeight() const; + + protected: + // views::View: + void Layout() override; + private: + friend class WinCaptionButtonContainer; + + int FrameBorderThickness() const; + + // Returns the thickness of the window border for the top edge of the frame, + // which is sometimes different than FrameBorderThickness(). Does not include + // the titlebar/tabstrip area. If |restored| is true, this is calculated as if + // the window was restored, regardless of its current state. + int FrameTopBorderThickness(bool restored) const; + int FrameTopBorderThicknessPx(bool restored) const; + + // Returns the height of the titlebar for popups or other browser types that + // don't have tabs. + int TitlebarHeight(bool restored) const; + + // Returns the y coordinate for the top of the frame, which in maximized mode + // is the top of the screen and in restored mode is 1 pixel below the top of + // the window to leave room for the visual border that Windows draws. + int WindowTopY() const; + + void LayoutCaptionButtons(); + void LayoutWindowControlsOverlay(); + + // The container holding the caption buttons (minimize, maximize, close, etc.) + // May be null if the caption button container is destroyed before the frame + // view. Always check for validity before using! + WinCaptionButtonContainer* caption_button_container_; + DISALLOW_COPY_AND_ASSIGN(WinFrameView); }; diff --git a/shell/common/options_switches.cc b/shell/common/options_switches.cc index f2f575f43bcd9..c4bbdd528c61a 100644 --- a/shell/common/options_switches.cc +++ b/shell/common/options_switches.cc @@ -31,6 +31,11 @@ const char kFullscreen[] = "fullscreen"; const char kTrafficLightPosition[] = "trafficLightPosition"; const char kRoundedCorners[] = "roundedCorners"; +// The color to use as the theme and symbol colors respectively for Window +// Controls Overlay if enabled on Windows. +const char kOverlayButtonColor[] = "color"; +const char kOverlaySymbolColor[] = "symbolColor"; + // Whether the window should show in taskbar. const char kSkipTaskbar[] = "skipTaskbar"; diff --git a/shell/common/options_switches.h b/shell/common/options_switches.h index 16b2aa079dc9d..704e5b4b58e41 100644 --- a/shell/common/options_switches.h +++ b/shell/common/options_switches.h @@ -58,6 +58,8 @@ extern const char kVisualEffectState[]; extern const char kTrafficLightPosition[]; extern const char kRoundedCorners[]; extern const char ktitleBarOverlay[]; +extern const char kOverlayButtonColor[]; +extern const char kOverlaySymbolColor[]; // WebPreferences. extern const char kZoomFactor[]; diff --git a/spec-main/api-browser-window-spec.ts b/spec-main/api-browser-window-spec.ts index a78ce386beea3..6c3731ce3c7c8 100644 --- a/spec-main/api-browser-window-spec.ts +++ b/spec-main/api-browser-window-spec.ts @@ -5,6 +5,7 @@ import * as fs from 'fs'; import * as os from 'os'; import * as qs from 'querystring'; import * as http from 'http'; +import * as semver from 'semver'; import { AddressInfo } from 'net'; import { app, BrowserWindow, BrowserView, dialog, ipcMain, OnBeforeSendHeadersListenerDetails, protocol, screen, webContents, session, WebContents, BrowserWindowConstructorOptions } from 'electron/main'; @@ -1882,7 +1883,7 @@ describe('BrowserWindow module', () => { }); }); - ifdescribe(process.platform === 'darwin' && parseInt(os.release().split('.')[0]) >= 14)('"titleBarStyle" option', () => { + ifdescribe(process.platform === 'win32' || (process.platform === 'darwin' && semver.gte(os.release(), '14.0.0')))('"titleBarStyle" option', () => { const testWindowsOverlay = async (style: any) => { const w = new BrowserWindow({ show: false, @@ -1896,12 +1897,22 @@ describe('BrowserWindow module', () => { titleBarOverlay: true }); const overlayHTML = path.join(__dirname, 'fixtures', 'pages', 'overlay.html'); - await w.loadFile(overlayHTML); + if (process.platform === 'darwin') { + await w.loadFile(overlayHTML); + } else { + const overlayReady = emittedOnce(ipcMain, 'geometrychange'); + await w.loadFile(overlayHTML); + await overlayReady; + } const overlayEnabled = await w.webContents.executeJavaScript('navigator.windowControlsOverlay.visible'); expect(overlayEnabled).to.be.true('overlayEnabled'); const overlayRect = await w.webContents.executeJavaScript('getJSOverlayProperties()'); expect(overlayRect.y).to.equal(0); - expect(overlayRect.x).to.be.greaterThan(0); + if (process.platform === 'darwin') { + expect(overlayRect.x).to.be.greaterThan(0); + } else { + expect(overlayRect.x).to.equal(0); + } expect(overlayRect.width).to.be.greaterThan(0); expect(overlayRect.height).to.be.greaterThan(0); const cssOverlayRect = await w.webContents.executeJavaScript('getCssOverlayProperties();'); @@ -1923,7 +1934,7 @@ describe('BrowserWindow module', () => { const contentSize = w.getContentSize(); expect(contentSize).to.deep.equal([400, 400]); }); - it('creates browser window with hidden inset title bar', () => { + ifit(process.platform === 'darwin')('creates browser window with hidden inset title bar', () => { const w = new BrowserWindow({ show: false, width: 400, @@ -1936,7 +1947,7 @@ describe('BrowserWindow module', () => { it('sets Window Control Overlay with hidden title bar', async () => { await testWindowsOverlay('hidden'); }); - it('sets Window Control Overlay with hidden inset title bar', async () => { + ifit(process.platform === 'darwin')('sets Window Control Overlay with hidden inset title bar', async () => { await testWindowsOverlay('hiddenInset'); }); });