Commit 2147655e authored by Thomas Lukaszewicz's avatar Thomas Lukaszewicz Committed by Commit Bot

Tab Search: Switch tab search to use the WebUIBubbleManager

This CL switches Tab Search to use the WebUIBubbleManager and updates
WebUIBubbleManager to handle UMA metrics / extensions APIs.

This CL also removes the now redundant TabSearchBubbleView and
WebBubbleDialogView code.

Bug: 1099917
Change-Id: I808de88af774f91539c9610f066317f155252037
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2514077Reviewed-by: default avatarAlexei Svitkine <asvitkine@chromium.org>
Reviewed-by: default avatarPeter Boström <pbos@chromium.org>
Commit-Queue: Thomas Lukaszewicz <tluk@chromium.org>
Cr-Commit-Position: refs/heads/master@{#824018}
parent fe6e8407
......@@ -3934,8 +3934,6 @@ static_library("ui") {
"views/tab_icon_view.h",
"views/tab_modal_confirm_dialog_views.cc",
"views/tab_modal_confirm_dialog_views.h",
"views/tab_search/tab_search_bubble_view.cc",
"views/tab_search/tab_search_bubble_view.h",
"views/tab_sharing/tab_sharing_ui_views.cc",
"views/tab_sharing/tab_sharing_ui_views.h",
"views/tabs/alert_indicator.cc",
......
......@@ -18,10 +18,12 @@ constexpr base::TimeDelta kWebViewRetentionTime =
WebUIBubbleManagerBase::WebUIBubbleManagerBase(
views::View* anchor_view,
content::BrowserContext* browser_context,
const GURL& webui_url)
const GURL& webui_url,
bool enable_extension_apis)
: anchor_view_(anchor_view),
browser_context_(browser_context),
webui_url_(webui_url),
enable_extension_apis_(enable_extension_apis),
cache_timer_(std::make_unique<base::RetainingOneShotTimer>(
FROM_HERE,
kWebViewRetentionTime,
......@@ -69,6 +71,10 @@ void WebUIBubbleManagerBase::OnWidgetDestroying(views::Widget* widget) {
cache_timer_->Reset();
}
void WebUIBubbleManagerBase::ResetWebViewForTesting() {
ResetWebView();
}
void WebUIBubbleManagerBase::ResetWebView() {
cached_web_view_.reset();
}
......@@ -9,6 +9,7 @@
#include <utility>
#include "base/scoped_observer.h"
#include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
#include "chrome/browser/ui/views/bubble/webui_bubble_view.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/widget/widget.h"
......@@ -27,7 +28,8 @@ class WebUIBubbleManagerBase : public views::WidgetObserver {
public:
WebUIBubbleManagerBase(views::View* anchor_view,
content::BrowserContext* browser_context,
const GURL& webui_url);
const GURL& webui_url,
bool enable_extension_apis = false);
WebUIBubbleManagerBase(const WebUIBubbleManagerBase&) = delete;
const WebUIBubbleManagerBase& operator=(const WebUIBubbleManagerBase&) =
delete;
......@@ -42,6 +44,12 @@ class WebUIBubbleManagerBase : public views::WidgetObserver {
content::BrowserContext* browser_context() { return browser_context_; }
const GURL& webui_url() const { return webui_url_; }
bool enable_extension_apis() const { return enable_extension_apis_; }
void ResetWebViewForTesting();
base::WeakPtr<WebUIBubbleDialogView> bubble_view_for_testing() {
return bubble_view_;
}
private:
virtual std::unique_ptr<WebUIBubbleView> CreateWebView() = 0;
......@@ -51,6 +59,7 @@ class WebUIBubbleManagerBase : public views::WidgetObserver {
content::BrowserContext* browser_context_;
GURL webui_url_;
base::WeakPtr<WebUIBubbleDialogView> bubble_view_;
const bool enable_extension_apis_;
// A cached WebView used to make re-triggering the UI faster. This is not set
// when the bubble is showing. It will only be set when the bubble is
......@@ -72,6 +81,12 @@ class WebUIBubbleManager : public WebUIBubbleManagerBase {
private:
std::unique_ptr<WebUIBubbleView> CreateWebView() override {
auto web_view = std::make_unique<WebUIBubbleView>(browser_context());
if (enable_extension_apis()) {
// In order for the WebUI in the renderer to use extensions APIs we must
// add a ChromeExtensionWebContentsObserver to the WebView's WebContents.
extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
web_view->GetWebContents());
}
web_view->template LoadURL<T>(webui_url());
return web_view;
}
......
......@@ -5,6 +5,7 @@
#include "chrome/browser/ui/views/bubble/webui_bubble_manager.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/views/bubble/webui_bubble_dialog_view.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"
......@@ -43,6 +44,13 @@ class WebUIBubbleManagerBrowserTest : public InProcessBrowserTest {
InProcessBrowserTest::SetUpOnMainThread();
bubble_manager_ = std::make_unique<TestWebUIBubbleManager>(browser());
}
void TearDownOnMainThread() override {
auto* widget = bubble_manager_->GetBubbleWidget();
if (widget)
widget->CloseNow();
bubble_manager()->ResetWebViewForTesting();
InProcessBrowserTest::TearDownOnMainThread();
}
TestWebUIBubbleManager* bubble_manager() { return bubble_manager_.get(); }
......@@ -50,7 +58,7 @@ class WebUIBubbleManagerBrowserTest : public InProcessBrowserTest {
std::unique_ptr<TestWebUIBubbleManager> bubble_manager_;
};
IN_PROC_BROWSER_TEST_F(WebUIBubbleManagerBrowserTest, CreateAndClose) {
IN_PROC_BROWSER_TEST_F(WebUIBubbleManagerBrowserTest, CreateAndCloseBubble) {
EXPECT_EQ(nullptr, bubble_manager()->GetBubbleWidget());
bubble_manager()->ShowBubble();
EXPECT_NE(nullptr, bubble_manager()->GetBubbleWidget());
......@@ -59,3 +67,18 @@ IN_PROC_BROWSER_TEST_F(WebUIBubbleManagerBrowserTest, CreateAndClose) {
bubble_manager()->CloseBubble();
EXPECT_TRUE(bubble_manager()->GetBubbleWidget()->IsClosed());
}
IN_PROC_BROWSER_TEST_F(WebUIBubbleManagerBrowserTest,
ShowUISetsBubbleWidgetVisible) {
EXPECT_EQ(nullptr, bubble_manager()->GetBubbleWidget());
bubble_manager()->ShowBubble();
EXPECT_NE(nullptr, bubble_manager()->GetBubbleWidget());
EXPECT_FALSE(bubble_manager()->GetBubbleWidget()->IsClosed());
EXPECT_FALSE(bubble_manager()->GetBubbleWidget()->IsVisible());
bubble_manager()->bubble_view_for_testing()->ShowUI();
EXPECT_TRUE(bubble_manager()->GetBubbleWidget()->IsVisible());
bubble_manager()->CloseBubble();
EXPECT_TRUE(bubble_manager()->GetBubbleWidget()->IsClosed());
}
......@@ -122,7 +122,6 @@
#include "chrome/browser/ui/views/side_panel.h"
#include "chrome/browser/ui/views/status_bubble_views.h"
#include "chrome/browser/ui/views/tab_contents/chrome_web_contents_view_focus_helper.h"
#include "chrome/browser/ui/views/tab_search/tab_search_bubble_view.h"
#include "chrome/browser/ui/views/tabs/browser_tab_strip_controller.h"
#include "chrome/browser/ui/views/tabs/new_tab_button.h"
#include "chrome/browser/ui/views/tabs/tab.h"
......@@ -2685,12 +2684,7 @@ void BrowserView::CreateTabSearchBubble() {
#endif // BUILDFLAG(ENABLE_WEBUI_TAB_STRIP)
DCHECK(GetTabSearchButton());
if (GetTabSearchButton()->ShowTabSearchBubble()) {
// Only log the open action if it resulted in creating a new instance of the
// Tab Search bubble.
base::UmaHistogramEnumeration("Tabs.TabSearch.OpenAction",
TabSearchOpenAction::kKeyboardShortcut);
}
GetTabSearchButton()->ShowTabSearchBubble(true);
}
void BrowserView::CloseTabSearchBubble() {
......
// 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 "chrome/browser/ui/views/tab_search/tab_search_bubble_view.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/browser/extensions/chrome_extension_web_contents_observer.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/webui/tab_search/tab_search_ui.h"
#include "chrome/common/webui_url_constants.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/widget/widget.h"
// static.
views::Widget* TabSearchBubbleView::CreateTabSearchBubble(
content::BrowserContext* browser_context,
views::View* anchor_view) {
return views::WebBubbleDialogView::CreateWebBubbleDialog<TabSearchUI>(
std::make_unique<TabSearchBubbleView>(browser_context, anchor_view),
GURL(chrome::kChromeUITabSearchURL));
}
TabSearchBubbleView::TabSearchBubbleView(
content::BrowserContext* browser_context,
views::View* anchor_view)
: WebBubbleDialogView(browser_context, anchor_view),
close_bubble_helper_(this, BrowserList::GetInstance()->GetLastActive()) {
extensions::ChromeExtensionWebContentsObserver::CreateForWebContents(
web_view()->GetWebContents());
}
TabSearchBubbleView::~TabSearchBubbleView() {
if (timer_.has_value()) {
UmaHistogramMediumTimes("Tabs.TabSearch.WindowDisplayedDuration2",
timer_->Elapsed());
}
}
void TabSearchBubbleView::AddedToWidget() {
WebBubbleDialogView::AddedToWidget();
observed_bubble_widget_.Add(GetWidget());
}
void TabSearchBubbleView::OnWidgetVisibilityChanged(views::Widget* widget,
bool visible) {
if (GetWidget() == widget && visible && !timer_.has_value()) {
timer_ = base::ElapsedTimer();
}
}
// 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 CHROME_BROWSER_UI_VIEWS_TAB_SEARCH_TAB_SEARCH_BUBBLE_VIEW_H_
#define CHROME_BROWSER_UI_VIEWS_TAB_SEARCH_TAB_SEARCH_BUBBLE_VIEW_H_
#include "base/scoped_observer.h"
#include "base/timer/elapsed_timer.h"
#include "chrome/browser/ui/views/close_bubble_on_tab_activation_helper.h"
#include "ui/views/controls/webview/web_bubble_dialog_view.h"
namespace views {
class Widget;
} // namespace views
namespace content {
class BrowserContext;
} // namespace content
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class TabSearchOpenAction {
kMouseClick = 0,
kKeyboardNavigation = 1,
kKeyboardShortcut = 2,
kTouchGesture = 3,
kMaxValue = kTouchGesture,
};
class TabSearchBubbleView : public views::WebBubbleDialogView,
public views::WidgetObserver {
public:
static views::Widget* CreateTabSearchBubble(
content::BrowserContext* browser_context,
views::View* anchor_view);
TabSearchBubbleView(content::BrowserContext* browser_context,
views::View* anchor_view);
TabSearchBubbleView(const TabSearchBubbleView&) = delete;
TabSearchBubbleView& operator=(const TabSearchBubbleView&) = delete;
~TabSearchBubbleView() override;
// views::WebBubbleDialogView:
void AddedToWidget() override;
// views::WidgetObserver:
void OnWidgetVisibilityChanged(views::Widget* widget, bool visible) override;
const base::Optional<base::ElapsedTimer>& timer_for_testing() {
return timer_;
}
private:
// Time the Tab Search window has been open.
base::Optional<base::ElapsedTimer> timer_;
ScopedObserver<views::Widget, views::WidgetObserver> observed_bubble_widget_{
this};
CloseBubbleOnTabActivationHelper close_bubble_helper_;
};
#endif // CHROME_BROWSER_UI_VIEWS_TAB_SEARCH_TAB_SEARCH_BUBBLE_VIEW_H_
// 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 "chrome/browser/ui/views/tab_search/tab_search_bubble_view.h"
#include <string>
#include "base/test/scoped_feature_list.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/test/test_browser_dialog.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/tabs/tab_search_button.h"
#include "chrome/browser/ui/webui/tab_search/tab_search_ui.h"
#include "chrome/common/webui_url_constants.h"
#include "content/public/test/browser_test.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
class TabSearchBubbleBrowserTest : public InProcessBrowserTest {
public:
TabSearchBubbleBrowserTest() {
feature_list_.InitAndEnableFeature(features::kTabSearch);
}
TabSearchBubbleBrowserTest(TabSearchBubbleBrowserTest&) = delete;
TabSearchBubbleBrowserTest& operator=(TabSearchBubbleBrowserTest&) = delete;
~TabSearchBubbleBrowserTest() override = default;
// InProcessBrowserTest:
void SetUpOnMainThread() override {
auto* anchor =
BrowserView::GetBrowserViewForBrowser(browser())->GetTabSearchButton();
auto bubble_delegate =
std::make_unique<TabSearchBubbleView>(browser()->profile(), anchor);
bubble_view_ = bubble_delegate.get();
bubble_ = views::WebBubbleDialogView::CreateWebBubbleDialog<TabSearchUI>(
std::move(bubble_delegate), GURL(chrome::kChromeUITabSearchURL));
}
views::Widget* bubble() { return bubble_; }
TabSearchBubbleView* bubble_view() { return bubble_view_; }
private:
views::Widget* bubble_ = nullptr;
TabSearchBubbleView* bubble_view_ = nullptr;
base::test::ScopedFeatureList feature_list_;
};
IN_PROC_BROWSER_TEST_F(TabSearchBubbleBrowserTest, ShowTriggersTimer) {
EXPECT_NE(nullptr, bubble());
// Timer should not be active when the bubble is not visible.
EXPECT_FALSE(bubble()->IsVisible());
EXPECT_FALSE(bubble_view()->timer_for_testing());
// Showing the bubble should trigger the timer.
bubble_view()->ShowUI();
EXPECT_TRUE(bubble()->IsVisible());
EXPECT_TRUE(bubble_view()->timer_for_testing());
bubble()->CloseNow();
}
class TabSearchBubbleBrowserUITest : public DialogBrowserTest {
public:
TabSearchBubbleBrowserUITest() {
feature_list_.InitAndEnableFeature(features::kTabSearch);
}
TabSearchBubbleBrowserUITest(TabSearchBubbleBrowserUITest&) = delete;
TabSearchBubbleBrowserUITest& operator=(TabSearchBubbleBrowserUITest&) =
delete;
~TabSearchBubbleBrowserUITest() override = default;
// DialogBrowserTest:
void ShowUi(const std::string& name) override {
AppendTab(chrome::kChromeUISettingsURL);
AppendTab(chrome::kChromeUIHistoryURL);
AppendTab(chrome::kChromeUIBookmarksURL);
BrowserView* browser_view =
BrowserView::GetBrowserViewForBrowser(browser());
DCHECK(browser_view);
views::View* anchor_view = browser_view->GetTabSearchButton();
TabSearchBubbleView::CreateTabSearchBubble(browser()->profile(),
anchor_view);
}
void AppendTab(std::string url) {
chrome::AddTabAt(browser(), GURL(url), -1, true);
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Invokes a tab search bubble.
IN_PROC_BROWSER_TEST_F(TabSearchBubbleBrowserUITest, InvokeUi_default) {
ShowAndVerifyUi();
}
......@@ -4,15 +4,26 @@
#include "chrome/browser/ui/views/tabs/tab_search_button.h"
#include "base/bind.h"
#include "base/metrics/histogram_functions.h"
#include "chrome/app/vector_icons/vector_icons.h"
#include "chrome/browser/ui/views/tab_search/tab_search_bubble_view.h"
#include "chrome/browser/ui/views/tabs/tab_strip_controller.h"
#include "chrome/common/webui_url_constants.h"
#include "ui/gfx/paint_vector_icon.h"
#include "ui/views/widget/widget.h"
namespace {
// These values are persisted to logs. Entries should not be renumbered and
// numeric values should never be reused.
enum class TabSearchOpenAction {
kMouseClick = 0,
kKeyboardNavigation = 1,
kKeyboardShortcut = 2,
kTouchGesture = 3,
kMaxValue = kTouchGesture,
};
TabSearchOpenAction GetActionForEvent(const ui::Event& event) {
if (event.IsMouseEvent()) {
return TabSearchOpenAction::kMouseClick;
......@@ -24,7 +35,15 @@ TabSearchOpenAction GetActionForEvent(const ui::Event& event) {
} // namespace
TabSearchButton::TabSearchButton(TabStrip* tab_strip)
: NewTabButton(tab_strip, PressedCallback()) {
: NewTabButton(tab_strip, PressedCallback()),
webui_bubble_manager_(this,
tab_strip->controller()->GetProfile(),
GURL(chrome::kChromeUITabSearchURL),
true),
widget_open_timer_(base::BindRepeating([](base::TimeDelta time_elapsed) {
base::UmaHistogramMediumTimes("Tabs.TabSearch.WindowDisplayedDuration3",
time_elapsed);
})) {
SetImageHorizontalAlignment(HorizontalAlignment::ALIGN_CENTER);
SetImageVerticalAlignment(VerticalAlignment::ALIGN_MIDDLE);
......@@ -54,18 +73,25 @@ void TabSearchButton::FrameColorsChanged() {
}
void TabSearchButton::OnWidgetDestroying(views::Widget* widget) {
DCHECK_EQ(bubble_, widget);
observed_bubble_widget_.Remove(bubble_);
bubble_ = nullptr;
DCHECK_EQ(webui_bubble_manager_.GetBubbleWidget(), widget);
observed_bubble_widget_.Remove(webui_bubble_manager_.GetBubbleWidget());
pressed_lock_.reset();
}
bool TabSearchButton::ShowTabSearchBubble() {
if (bubble_)
bool TabSearchButton::ShowTabSearchBubble(bool triggered_by_keyboard_shortcut) {
if (!webui_bubble_manager_.ShowBubble())
return false;
bubble_ = TabSearchBubbleView::CreateTabSearchBubble(
tab_strip()->controller()->GetProfile(), this);
observed_bubble_widget_.Add(bubble_);
if (triggered_by_keyboard_shortcut) {
base::UmaHistogramEnumeration("Tabs.TabSearch.OpenAction",
TabSearchOpenAction::kKeyboardShortcut);
}
// There should only ever be a single bubble widget active for the
// TabSearchButton.
DCHECK(!observed_bubble_widget_.IsObservingSources());
observed_bubble_widget_.Add(webui_bubble_manager_.GetBubbleWidget());
widget_open_timer_.Reset(webui_bubble_manager_.GetBubbleWidget());
// Hold the pressed lock while the |bubble_| is active.
pressed_lock_ = menu_button_controller_->TakeLock();
......@@ -73,12 +99,7 @@ bool TabSearchButton::ShowTabSearchBubble() {
}
void TabSearchButton::CloseTabSearchBubble() {
if (bubble_)
bubble_->CloseWithReason(views::Widget::ClosedReason::kUnspecified);
}
bool TabSearchButton::IsBubbleVisible() const {
return bubble_ && bubble_->IsVisible();
webui_bubble_manager_.CloseBubble();
}
void TabSearchButton::PaintIcon(gfx::Canvas* canvas) {
......@@ -88,10 +109,12 @@ void TabSearchButton::PaintIcon(gfx::Canvas* canvas) {
}
void TabSearchButton::ButtonPressed(const ui::Event& event) {
// Only log the open action if it resulted in creating a new instance of the
// Tab Search bubble.
if (ShowTabSearchBubble()) {
// Only log the open action if it resulted in creating a new instance of the
// Tab Search bubble.
base::UmaHistogramEnumeration("Tabs.TabSearch.OpenAction",
GetActionForEvent(event));
return;
}
CloseTabSearchBubble();
}
......@@ -5,9 +5,12 @@
#ifndef CHROME_BROWSER_UI_VIEWS_TABS_TAB_SEARCH_BUTTON_H_
#define CHROME_BROWSER_UI_VIEWS_TABS_TAB_SEARCH_BUTTON_H_
#include "chrome/browser/ui/views/bubble/webui_bubble_manager.h"
#include "chrome/browser/ui/views/tabs/new_tab_button.h"
#include "chrome/browser/ui/webui/tab_search/tab_search_ui.h"
#include "ui/views/controls/button/menu_button_controller.h"
#include "ui/views/widget/widget_observer.h"
#include "ui/views/widget/widget_utils.h"
namespace gfx {
class Canvas;
......@@ -44,12 +47,12 @@ class TabSearchButton : public NewTabButton,
// When this is called the bubble may already be showing or be loading in.
// This returns true if the method call results in the creation of a new Tab
// Search bubble.
bool ShowTabSearchBubble();
bool ShowTabSearchBubble(bool triggered_by_keyboard_shortcut = false);
void CloseTabSearchBubble();
bool IsBubbleVisible() const;
views::Widget* bubble_for_testing() { return bubble_; }
WebUIBubbleManagerBase* webui_bubble_manager_for_testing() {
return &webui_bubble_manager_;
}
protected:
// NewTabButton:
......@@ -58,15 +61,16 @@ class TabSearchButton : public NewTabButton,
private:
void ButtonPressed(const ui::Event& event);
WebUIBubbleManager<TabSearchUI> webui_bubble_manager_;
views::WidgetOpenTimer widget_open_timer_;
views::MenuButtonController* menu_button_controller_ = nullptr;
// A lock to keep the TabSearchButton pressed while |bubble_| is showing or
// in the process of being shown.
std::unique_ptr<views::MenuButtonController::PressedLock> pressed_lock_;
// |bubble_| is non-null while the tab search bubble is active.
views::Widget* bubble_ = nullptr;
ScopedObserver<views::Widget, views::WidgetObserver> observed_bubble_widget_{
this};
};
......
......@@ -11,9 +11,13 @@
#include "base/test/scoped_feature_list.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_tabstrip.h"
#include "chrome/browser/ui/test/test_browser_dialog.h"
#include "chrome/browser/ui/ui_features.h"
#include "chrome/browser/ui/views/bubble/webui_bubble_manager.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/tabs/tab_search_button.h"
#include "chrome/common/webui_url_constants.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "content/public/test/browser_test.h"
#include "ui/base/accelerators/accelerator.h"
......@@ -42,68 +46,79 @@ class TabSearchButtonBrowserTest : public InProcessBrowserTest {
return browser_view()->GetTabSearchButton();
}
WebUIBubbleManagerBase* bubble_manager() {
return tab_search_button()->webui_bubble_manager_for_testing();
}
void RunUntilBubbleWidgetDestroyed() {
ASSERT_NE(nullptr, tab_search_button()->bubble_for_testing());
ASSERT_NE(nullptr, bubble_manager()->GetBubbleWidget());
base::RunLoop run_loop;
base::ThreadTaskRunnerHandle::Get()->PostTask(FROM_HERE,
run_loop.QuitClosure());
run_loop.Run();
ASSERT_EQ(nullptr, tab_search_button()->bubble_for_testing());
ASSERT_EQ(nullptr, bubble_manager()->GetBubbleWidget());
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
IN_PROC_BROWSER_TEST_F(TabSearchButtonBrowserTest, CreateAndClose) {
ASSERT_EQ(nullptr, tab_search_button()->bubble_for_testing());
IN_PROC_BROWSER_TEST_F(TabSearchButtonBrowserTest, ButtonClickCreatesBubble) {
ASSERT_EQ(nullptr, bubble_manager()->GetBubbleWidget());
views::test::ButtonTestApi(tab_search_button()).NotifyClick(GetDummyEvent());
ASSERT_NE(nullptr, tab_search_button()->bubble_for_testing());
tab_search_button()->CloseTabSearchBubble();
ASSERT_TRUE(tab_search_button()->bubble_for_testing()->IsClosed());
RunUntilBubbleWidgetDestroyed();
}
IN_PROC_BROWSER_TEST_F(TabSearchButtonBrowserTest, TestBubbleVisible) {
EXPECT_FALSE(tab_search_button()->IsBubbleVisible());
ASSERT_EQ(nullptr, tab_search_button()->bubble_for_testing());
views::test::ButtonTestApi(tab_search_button()).NotifyClick(GetDummyEvent());
ASSERT_NE(nullptr, tab_search_button()->bubble_for_testing());
// The bubble should not be visible initially since the UI must notify the
// bubble it is ready before the bubble is shown.
EXPECT_FALSE(tab_search_button()->IsBubbleVisible());
// Trigger showing the bubble.
tab_search_button()->bubble_for_testing()->Show();
// The bubble should be visible after being shown.
EXPECT_TRUE(tab_search_button()->IsBubbleVisible());
ASSERT_NE(nullptr, bubble_manager()->GetBubbleWidget());
tab_search_button()->CloseTabSearchBubble();
ASSERT_TRUE(tab_search_button()->bubble_for_testing()->IsClosed());
ASSERT_TRUE(bubble_manager()->GetBubbleWidget()->IsClosed());
RunUntilBubbleWidgetDestroyed();
}
// On macOS, most accelerators are handled by CommandDispatcher.
#if !defined(OS_MAC)
IN_PROC_BROWSER_TEST_F(TabSearchButtonBrowserTest, TestBubbleKeyboardShortcut) {
ASSERT_EQ(nullptr, tab_search_button()->bubble_for_testing());
IN_PROC_BROWSER_TEST_F(TabSearchButtonBrowserTest,
KeyboardShortcutTriggersBubble) {
ASSERT_EQ(nullptr, bubble_manager()->GetBubbleWidget());
auto accelerator = ui::Accelerator(
ui::VKEY_A, ui::EF_SHIFT_DOWN | ui::EF_PLATFORM_ACCELERATOR);
browser_view()->AcceleratorPressed(accelerator);
// Accelerator keys should have created the tab search bubble.
ASSERT_NE(nullptr, tab_search_button()->bubble_for_testing());
ASSERT_NE(nullptr, bubble_manager()->GetBubbleWidget());
tab_search_button()->CloseTabSearchBubble();
ASSERT_TRUE(tab_search_button()->bubble_for_testing()->IsClosed());
ASSERT_TRUE(bubble_manager()->GetBubbleWidget()->IsClosed());
RunUntilBubbleWidgetDestroyed();
}
#endif
class TabSearchButtonBrowserUITest : public DialogBrowserTest {
public:
// DialogBrowserTest:
void SetUp() override {
feature_list_.InitAndEnableFeature(features::kTabSearch);
DialogBrowserTest::SetUp();
}
void ShowUi(const std::string& name) override {
AppendTab(chrome::kChromeUISettingsURL);
AppendTab(chrome::kChromeUIHistoryURL);
AppendTab(chrome::kChromeUIBookmarksURL);
auto* tab_search_button =
BrowserView::GetBrowserViewForBrowser(browser())->GetTabSearchButton();
views::test::ButtonTestApi(tab_search_button).NotifyClick(GetDummyEvent());
}
void AppendTab(std::string url) {
chrome::AddTabAt(browser(), GURL(url), -1, true);
}
private:
base::test::ScopedFeatureList feature_list_;
};
// Invokes a tab search bubble.
IN_PROC_BROWSER_TEST_F(TabSearchButtonBrowserUITest, InvokeUi_default) {
ShowAndVerifyUi();
}
......@@ -2281,7 +2281,6 @@ if (!is_android) {
"../browser/ui/views/status_bubble_views_browsertest.cc",
"../browser/ui/views/sync/inline_login_ui_browsertest.cc",
"../browser/ui/views/sync/profile_signin_confirmation_dialog_views_browsertest.cc",
"../browser/ui/views/tab_search/tab_search_bubble_view_browsertest.cc",
"../browser/ui/views/tab_sharing/tab_sharing_ui_views_browsertest.cc",
"../browser/ui/views/tabs/tab_group_editor_bubble_view_browsertest.cc",
"../browser/ui/views/tabs/tab_hover_card_bubble_view_browsertest.cc",
......@@ -5943,7 +5942,6 @@ test("unit_tests") {
"//ui/web_dialogs:test_support",
]
sources += [
"../../ui/views/controls/webview/web_bubble_dialog_view_unittest.cc",
"../../ui/views/controls/webview/web_dialog_view_unittest.cc",
"../../ui/views/controls/webview/webview_unittest.cc",
"../browser/ui/media_router/media_router_ui_unittest.cc",
......
......@@ -2088,6 +2088,9 @@ reviews. Googlers can read more about this at go/gwsq-gerrit.
<histogram name="Tabs.TabSearch.WindowDisplayedDuration2" units="ms"
expires_after="M90">
<obsolete>
Deprecated as of 2020/11. The way the metric is emitted has changed.
</obsolete>
<owner>tluk@chromium.org</owner>
<owner>robliao@chromium.org</owner>
<summary>
......@@ -2108,6 +2111,29 @@ reviews. Googlers can read more about this at go/gwsq-gerrit.
</summary>
</histogram>
<histogram name="Tabs.TabSearch.WindowDisplayedDuration3" units="ms"
expires_after="M90">
<owner>tluk@chromium.org</owner>
<owner>robliao@chromium.org</owner>
<summary>
Tab Search is a feature that allows users to better search their browsers
for their desired tabs. It can be opened and closed. This records the amount
of time between when a Tab Search bubble is opened and when it is closed. It
does so by recording the difference in time between when the the hosting
WebUIBubbleDialogView's Widget is first created and when the widget is
destroyed.
The Tab Search UI is a bubble anchored to an element within a browser window
and is closed if the user switches to a tab, presses the escape key or
performs an action to return focus to the hosting window. The Tab Search UI
bubble will also close if the hosting browser window is closed or crashes.
Users may leave the bubble open for long periods of time without directly
interacting with the UI which could result in a long tail of displayed
durations.
</summary>
</histogram>
<histogram name="Tabs.TabsStatsDailyEventInterval"
enum="DailyEventIntervalType" expires_after="M77">
<owner>sebmarchand@chromium.org</owner>
......
......@@ -6,8 +6,6 @@ component("webview") {
sources = [
"unhandled_keyboard_event_handler.cc",
"unhandled_keyboard_event_handler.h",
"web_bubble_dialog_view.cc",
"web_bubble_dialog_view.h",
"web_contents_set_background_color.cc",
"web_contents_set_background_color.h",
"web_dialog_view.cc",
......@@ -51,7 +49,6 @@ component("webview") {
"//ui/gfx",
"//ui/gfx/geometry",
"//ui/views",
"//ui/webui",
]
if (is_linux || is_chromeos || is_android || is_fuchsia) {
......
......@@ -6,5 +6,4 @@ include_rules = [
"+ui/content_accelerators",
"+ui/views",
"+ui/web_dialogs",
"+ui/webui",
]
// 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 "ui/views/controls/webview/web_bubble_dialog_view.h"
#include <memory>
#include "content/public/browser/keyboard_event_processing_result.h"
#include "content/public/browser/native_web_keyboard_event.h"
#include "ui/gfx/geometry/rounded_corners_f.h"
#include "ui/views/controls/webview/unhandled_keyboard_event_handler.h"
#include "ui/views/layout/fill_layout.h"
#include "ui/views/widget/widget.h"
namespace views {
namespace {
bool IsEscapeEvent(const content::NativeWebKeyboardEvent& event) {
return event.GetType() ==
content::NativeWebKeyboardEvent::Type::kRawKeyDown &&
event.windows_key_code == ui::VKEY_ESCAPE;
}
// The min / max size available to the WebBubbleDialogView.
// These are arbitrary sizes that match those set by ExtensionPopup.
// TODO(tluk): Determine the correct size constraints for the
// WebBubbleDialogView.
constexpr gfx::Size kMinSize(25, 25);
constexpr gfx::Size kMaxSize(800, 600);
class BubbleWebView : public WebView {
public:
BubbleWebView(content::BrowserContext* browser_context,
WebBubbleDialogView* parent)
: WebView(browser_context), parent_(parent) {
EnableSizingFromWebContents(kMinSize, kMaxSize);
// Allow the embedder to handle accelerators not handled by the WebContents.
set_allow_accelerators(true);
}
~BubbleWebView() override = default;
// WebView:
void PreferredSizeChanged() override {
WebView::PreferredSizeChanged();
parent_->OnWebViewSizeChanged();
}
// content::WebContentsDelegate:
bool HandleContextMenu(content::RenderFrameHost* render_frame_host,
const content::ContextMenuParams& params) override {
// Ignores context menu.
return true;
}
content::KeyboardEventProcessingResult PreHandleKeyboardEvent(
content::WebContents* source,
const content::NativeWebKeyboardEvent& event) override {
// Close the bubble if an escape event is detected. Handle this here to
// prevent the renderer from capturing the event and not propagating it up.
if (IsEscapeEvent(event) && GetWidget()) {
GetWidget()->CloseWithReason(views::Widget::ClosedReason::kEscKeyPressed);
return content::KeyboardEventProcessingResult::HANDLED;
}
return content::KeyboardEventProcessingResult::NOT_HANDLED;
}
bool HandleKeyboardEvent(
content::WebContents* source,
const content::NativeWebKeyboardEvent& event) override {
return unhandled_keyboard_event_handler_.HandleKeyboardEvent(
event, GetFocusManager());
}
private:
WebBubbleDialogView* parent_;
// A handler to handle unhandled keyboard messages coming back from the
// renderer process.
views::UnhandledKeyboardEventHandler unhandled_keyboard_event_handler_;
};
} // namespace
WebBubbleDialogView::WebBubbleDialogView(
content::BrowserContext* browser_context,
View* anchor_view)
: BubbleDialogDelegateView(anchor_view, BubbleBorder::TOP_RIGHT),
web_view_(AddChildView(
std::make_unique<BubbleWebView>(browser_context, this))) {
SetButtons(ui::DIALOG_BUTTON_NONE);
set_margins(gfx::Insets());
SetLayoutManager(std::make_unique<FillLayout>());
SetVisible(false);
}
WebBubbleDialogView::~WebBubbleDialogView() = default;
void WebBubbleDialogView::OnWebViewSizeChanged() {
if (!hosted_in_bubble_) {
PreferredSizeChanged();
return;
}
SizeToContents();
}
gfx::Size WebBubbleDialogView::CalculatePreferredSize() const {
// Constrain the size to popup min/max.
gfx::Size preferred_size = BubbleDialogDelegateView::CalculatePreferredSize();
preferred_size.SetToMax(kMinSize);
preferred_size.SetToMin(kMaxSize);
return preferred_size;
}
void WebBubbleDialogView::AddedToWidget() {
if (!hosted_in_bubble_)
return;
BubbleDialogDelegateView::AddedToWidget();
web_view_->holder()->SetCornerRadii(gfx::RoundedCornersF(GetCornerRadius()));
}
void WebBubbleDialogView::ShowUI() {
SetVisible(true);
DCHECK(GetWidget());
GetWidget()->Show();
web_view_->GetWebContents()->Focus();
}
} // namespace views
// 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 UI_VIEWS_CONTROLS_WEBVIEW_WEB_BUBBLE_DIALOG_VIEW_H_
#define UI_VIEWS_CONTROLS_WEBVIEW_WEB_BUBBLE_DIALOG_VIEW_H_
#include <memory>
#include <utility>
#include "base/memory/weak_ptr.h"
#include "ui/views/bubble/bubble_dialog_delegate_view.h"
#include "ui/views/controls/webview/webview.h"
#include "ui/views/controls/webview/webview_export.h"
#include "ui/webui/mojo_bubble_web_ui_controller.h"
namespace content {
class BrowserContext;
} // namespace content
namespace views {
class Widget;
// A BubbleDialogDelegateView that hosts WebUI and resizes to fit the hosted
// WebContents.
class WEBVIEW_EXPORT WebBubbleDialogView
: public BubbleDialogDelegateView,
public ui::MojoBubbleWebUIController::Embedder {
public:
// |CreateWebBubbleDialog()| returns a Widget instance owned by its
// NativeWidget. When the NativeWidget is destroyed (in response to a native
// destruction message), it deletes the Widget from its destructor.
template <typename T>
static Widget* CreateWebBubbleDialog(
std::unique_ptr<WebBubbleDialogView> bubble_view,
const GURL& url) {
bubble_view->SetVisible(true);
bubble_view->LoadURL<T>(url);
bubble_view->hosted_in_bubble_ = true;
return BubbleDialogDelegateView::CreateBubble(std::move(bubble_view));
}
WebBubbleDialogView(content::BrowserContext* browser_context,
View* anchor_view);
WebBubbleDialogView(const WebBubbleDialogView&) = delete;
WebBubbleDialogView& operator=(const WebBubbleDialogView&) = delete;
~WebBubbleDialogView() override;
void OnWebViewSizeChanged();
WebView* web_view() { return web_view_; }
// BubbleDialogDelegateView:
gfx::Size CalculatePreferredSize() const override;
void AddedToWidget() override;
// MojoBubbleWebUIController::Embedder:
void ShowUI() override;
// The type T enables WebBubbleDialogView to know what WebUIController is
// being used for the bubble's WebUI and allows it to make sure the associated
// WebUI is a MojoBubbleWebUIController at compile time.
// TODO(pbos): Move LoadURL into BubbleWebView.
template <typename T>
void LoadURL(const GURL& url) {
// Lie to WebContents so it starts rendering and eventually calls ShowUI().
web_view_->GetWebContents()->WasShown();
web_view_->LoadInitialURL(url);
T* webui_bubble_controller = web_view_->GetWebContents()
->GetWebUI()
->GetController()
->template GetAs<T>();
// Depends on the WebUIController object being constructed synchronously
// when the navigation is started in LoadInitialURL().
webui_bubble_controller->set_embedder(weak_ptr_factory_.GetWeakPtr());
}
// Used for tests that create the bubble instead of calling
// CreateWebBubbleDialog().
void set_hosted_in_bubble_for_testing() { hosted_in_bubble_ = true; }
private:
WebView* const web_view_;
// TODO(pbos): Remove this by separating WebBubbleDialogView content from its
// BubbleDialogDelegateView parent.
bool hosted_in_bubble_ = false;
base::WeakPtrFactory<WebBubbleDialogView> weak_ptr_factory_{this};
};
} // namespace views
#endif // UI_VIEWS_CONTROLS_WEBVIEW_WEB_BUBBLE_DIALOG_VIEW_H_
// Copyright 2019 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/controls/webview/web_bubble_dialog_view.h"
#include <memory>
#include <utility>
#include "content/public/test/browser_task_environment.h"
#include "content/public/test/test_browser_context.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/views/test/views_test_base.h"
#include "ui/views/widget/unique_widget_ptr.h"
namespace views {
namespace test {
class WebBubbleDialogViewTest : public ViewsTestBase {
public:
WebBubbleDialogViewTest()
: ViewsTestBase(std::unique_ptr<base::test::TaskEnvironment>(
std::make_unique<content::BrowserTaskEnvironment>())) {}
WebBubbleDialogViewTest(const WebBubbleDialogViewTest&) = delete;
WebBubbleDialogViewTest& operator=(const WebBubbleDialogViewTest&) = delete;
~WebBubbleDialogViewTest() override = default;
// ViewsTestBase:
void SetUp() override {
ViewsTestBase::SetUp();
browser_context_ = std::make_unique<content::TestBrowserContext>();
anchor_widget_ = std::make_unique<Widget>();
Widget::InitParams params = CreateParams(Widget::InitParams::TYPE_WINDOW);
anchor_widget_->Init(std::move(params));
auto bubble_view = std::make_unique<WebBubbleDialogView>(
browser_context_.get(), anchor_widget_->GetContentsView());
bubble_view_ = bubble_view.get();
bubble_view_->set_hosted_in_bubble_for_testing();
bubble_widget_ =
BubbleDialogDelegateView::CreateBubble(std::move(bubble_view));
}
void TearDown() override {
bubble_widget_->CloseNow();
anchor_widget_.reset();
ViewsTestBase::TearDown();
}
protected:
WebBubbleDialogView* bubble_view() { return bubble_view_; }
Widget* bubble_widget() { return bubble_widget_; }
private:
std::unique_ptr<content::TestBrowserContext> browser_context_;
views::UniqueWidgetPtr anchor_widget_;
Widget* bubble_widget_ = nullptr;
WebBubbleDialogView* bubble_view_ = nullptr;
};
TEST_F(WebBubbleDialogViewTest, TestBubbleResize) {
views::WebView* const web_view = bubble_view()->web_view();
constexpr gfx::Size web_view_initial_size(100, 100);
web_view->SetPreferredSize(gfx::Size(100, 100));
bubble_view()->OnWebViewSizeChanged();
const gfx::Size widget_initial_size =
bubble_widget()->GetWindowBoundsInScreen().size();
// The bubble should be at least as big as the webview.
EXPECT_GE(widget_initial_size.width(), web_view_initial_size.width());
EXPECT_GE(widget_initial_size.height(), web_view_initial_size.height());
// Resize the webview.
constexpr gfx::Size web_view_final_size(200, 200);
web_view->SetPreferredSize(web_view_final_size);
bubble_view()->OnWebViewSizeChanged();
// Ensure the bubble resizes as expected.
const gfx::Size widget_final_size =
bubble_widget()->GetWindowBoundsInScreen().size();
EXPECT_LT(widget_initial_size.width(), widget_final_size.width());
EXPECT_LT(widget_initial_size.height(), widget_final_size.height());
// The bubble should be at least as big as the webview.
EXPECT_GE(widget_final_size.width(), web_view_final_size.width());
EXPECT_GE(widget_final_size.height(), web_view_final_size.height());
}
} // namespace test
} // namespace views
......@@ -4,6 +4,8 @@
#include "ui/views/widget/widget_utils.h"
#include <utility>
#include "ui/views/widget/widget.h"
#if defined(USE_AURA)
......@@ -12,6 +14,26 @@
namespace views {
WidgetOpenTimer::WidgetOpenTimer(Callback callback)
: callback_(std::move(callback)) {}
WidgetOpenTimer::~WidgetOpenTimer() = default;
void WidgetOpenTimer::OnWidgetDestroying(Widget* widget) {
DCHECK(open_timer_.has_value());
DCHECK(observed_widget_.IsObserving(widget));
callback_.Run(open_timer_->Elapsed());
open_timer_.reset();
observed_widget_.Remove(widget);
}
void WidgetOpenTimer::Reset(Widget* widget) {
DCHECK(!open_timer_.has_value());
DCHECK(!observed_widget_.IsObserving(widget));
observed_widget_.Add(widget);
open_timer_ = base::ElapsedTimer();
}
gfx::NativeWindow GetRootWindow(const Widget* widget) {
gfx::NativeWindow window = widget->GetNativeWindow();
#if defined(USE_AURA)
......
......@@ -5,12 +5,43 @@
#ifndef UI_VIEWS_WIDGET_WIDGET_UTILS_H_
#define UI_VIEWS_WIDGET_WIDGET_UTILS_H_
#include "base/callback.h"
#include "base/scoped_observer.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "ui/gfx/native_widget_types.h"
#include "ui/views/views_export.h"
#include "ui/views/widget/widget.h"
#include "ui/views/widget/widget_observer.h"
namespace views {
class Widget;
class VIEWS_EXPORT WidgetOpenTimer : public WidgetObserver {
public:
using Callback = base::RepeatingCallback<void(base::TimeDelta)>;
explicit WidgetOpenTimer(Callback callback);
WidgetOpenTimer(const WidgetOpenTimer&) = delete;
const WidgetOpenTimer& operator=(const WidgetOpenTimer&) = delete;
~WidgetOpenTimer() override;
// WidgetObserver:
void OnWidgetDestroying(Widget* widget) override;
// Called to start the |open_timer_|.
void Reset(Widget* widget);
private:
// Callback run when the passed in Widget is destroyed.
Callback callback_;
// Time the bubble has been open. Used for UMA metrics collection.
base::Optional<base::ElapsedTimer> open_timer_;
ScopedObserver<Widget, WidgetObserver> observed_widget_{this};
};
// Returns the root window for |widget|. On non-Aura, this is equivalent to
// widget->GetNativeWindow().
VIEWS_EXPORT gfx::NativeWindow GetRootWindow(const Widget* widget);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment