1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201
|
// Copyright 2022 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <algorithm>
#include <string>
#include "base/containers/to_vector.h"
#include "base/memory/raw_ptr.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/test_future.h"
#include "build/buildflag.h"
#include "chrome/browser/extensions/extension_browsertest.h"
#include "chrome/browser/ui/chooser_bubble_testapi.h"
#include "chrome/browser/ui/toolbar/toolbar_action_view_controller.h"
#include "chrome/browser/ui/views/extensions/extensions_toolbar_container.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/toolbar/toolbar_action_view.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#include "extensions/test/test_extension_dir.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "ui/views/layout/animating_layout_manager_test_util.h"
#include "ui/views/test/widget_test.h"
#include "ui/views/view_utils.h"
namespace {
using ::testing::ElementsAre;
enum ChooserType {
kWebUsb,
kWebHid,
};
class DeviceChooserExtensionBrowserTest
: public extensions::ExtensionBrowserTest,
public testing::WithParamInterface<ChooserType> {
protected:
void SetUpOnMainThread() override {
ExtensionBrowserTest::SetUpOnMainThread();
extensions::TestExtensionDir dir;
dir.WriteManifest(R"(
{
"name": "test-extension",
"version": "1.0",
"manifest_version": 3
}
)");
// Showing a device chooser requires a Window context. Create an empty
// extension page so we can open the chooser from that page.
dir.WriteFile(FILE_PATH_LITERAL("page.html"), {});
extension_ = LoadExtension(dir.UnpackedPath());
ASSERT_TRUE(extension_);
ASSERT_TRUE(extensions_container()->GetVisible());
ASSERT_TRUE(extensions_container()->GetViewForId(extension_->id()));
// Navigate to the extension page.
auto url = extension_->ResolveExtensionURL("page.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ASSERT_EQ(url, web_contents()->GetLastCommittedURL());
}
void TearDownOnMainThread() override {
extension_ = nullptr;
ExtensionBrowserTest::TearDownOnMainThread();
}
const std::string& extension_id() { return extension_->id(); }
content::WebContents* web_contents() {
return browser()->tab_strip_model()->GetActiveWebContents();
}
ExtensionsToolbarContainer* extensions_container() {
return browser()->GetBrowserView().toolbar()->extensions_container();
}
bool ShowChooser() {
auto show_chooser_script = [](ChooserType type) -> std::string {
switch (type) {
case kWebUsb:
return "navigator.usb.requestDevice({filters:[]});";
case kWebHid:
return "navigator.hid.requestDevice({filters:[]});";
}
};
return content::ExecJs(web_contents(), show_chooser_script(GetParam()),
content::EXECUTE_SCRIPT_NO_RESOLVE_PROMISES);
}
std::vector<ToolbarActionView*> GetPinnedExtensionViews() {
auto is_visible = [&](ToolbarActionView* const action) -> bool {
#if BUILDFLAG(IS_MAC)
// TODO(crbug.com/40670141): Use IsActionVisibleOnToolbar() because it
// queries the underlying model and not GetVisible(), as that relies on an
// animation running, which is not reliable in unit tests on Mac.
return extensions_container()->IsActionVisibleOnToolbar(
action->view_controller()->GetId());
#else
return action->GetVisible();
#endif
};
std::vector<ToolbarActionView*> result;
for (views::View* child : extensions_container()->children()) {
// Ensure we don't downcast the ExtensionsToolbarButton.
if (views::IsViewClass<ToolbarActionView>(child)) {
auto* action = static_cast<ToolbarActionView*>(child);
if (is_visible(action)) {
result.push_back(action);
}
}
}
return result;
}
std::vector<std::string> GetPinnedExtensionNames() {
return base::ToVector(GetPinnedExtensionViews(), [](auto* view) {
return base::UTF16ToUTF8(view->view_controller()->GetActionName());
});
}
void WaitForAnimation() {
#if BUILDFLAG(IS_MAC)
// TODO(crbug.com/40670141): we avoid using animations on Mac due to the
// lack of support in unit tests. Therefore this is a no-op.
#else
views::test::WaitForAnimatingLayoutManager(extensions_container());
#endif
}
private:
raw_ptr<const extensions::Extension> extension_ = nullptr;
};
IN_PROC_BROWSER_TEST_P(DeviceChooserExtensionBrowserTest,
ChooserAnchoredToExtensionIcon) {
// Check that no widget is anchored to the extension icon.
EXPECT_FALSE(extensions_container()->GetAnchoredWidgetForExtensionForTesting(
extension_id()));
// Check that no extensions are pinned in the toolbar.
EXPECT_TRUE(GetPinnedExtensionNames().empty());
// Open the chooser dialog and leave it open without making a selection.
auto chooser_bubble_ui_waiter = test::ChooserBubbleUiWaiter::Create();
EXPECT_TRUE(ShowChooser());
// Wait for the chooser widget to be created.
chooser_bubble_ui_waiter->WaitForChange();
EXPECT_TRUE(chooser_bubble_ui_waiter->has_shown());
// Ensure the widget is visible and anchored to the extension icon.
auto* chooser_widget =
extensions_container()->GetAnchoredWidgetForExtensionForTesting(
extension_id());
ASSERT_TRUE(chooser_widget);
views::test::WidgetVisibleWaiter(chooser_widget).Wait();
views::test::WidgetDestroyedWaiter widget_destroyed_waiter(chooser_widget);
// Showing the chooser dialog temporarily pins the extension in the toolbar.
EXPECT_THAT(GetPinnedExtensionNames(), ElementsAre("test-extension"));
// Navigate away from the extension page. This dismisses the chooser.
auto url = GURL("https://chromium.org");
EXPECT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
EXPECT_EQ(url, web_contents()->GetLastCommittedURL());
// Wait for the widget to be destroyed and for the extension icon to be
// unpinned.
widget_destroyed_waiter.Wait();
WaitForAnimation();
// Check that no widget is anchored to the extension icon.
EXPECT_FALSE(extensions_container()->GetAnchoredWidgetForExtensionForTesting(
extension_id()));
// Check that no extensions are pinned in the toolbar.
EXPECT_TRUE(GetPinnedExtensionNames().empty());
}
INSTANTIATE_TEST_SUITE_P(DeviceChooserExtensionBrowserTests,
DeviceChooserExtensionBrowserTest,
testing::Values(ChooserType::kWebUsb,
ChooserType::kWebHid),
[](testing::TestParamInfo<ChooserType> info) {
switch (info.param) {
case ChooserType::kWebUsb:
return "WebUsb";
case ChooserType::kWebHid:
return "WebHid";
}
});
} // namespace
|