910e62b5创建于 1月15日历史提交
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <memory>

#include "base/run_loop.h"
#include "base/test/run_until.h"
#include "build/build_config.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_commands.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/exclusive_access/exclusive_access_test.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/views/frame/browser_view.h"
#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/permissions/permission_request_manager_test_api.h"
#include "components/permissions/features.h"
#include "components/permissions/request_type.h"
#include "components/strings/grit/components_strings.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "ui/base/test/ui_controls.h"
#include "ui/events/base_event_utils.h"
#include "ui/views/test/button_test_api.h"
#include "ui/views/test/views_test_utils.h"
#include "ui/views/test/widget_activation_waiter.h"
#include "ui/views/test/widget_test.h"

enum ChipFeatureConfig {
  REQUEST_CHIP,
  REQUEST_CHIP_LOCATION_BAR_ICON_OVERRIDE
};

class PermissionBubbleInteractiveUITest : public InProcessBrowserTest {
 public:
  PermissionBubbleInteractiveUITest() = default;

  PermissionBubbleInteractiveUITest(const PermissionBubbleInteractiveUITest&) =
      delete;
  PermissionBubbleInteractiveUITest& operator=(
      const PermissionBubbleInteractiveUITest&) = delete;

  void EnsureWindowActive(ui::BaseWindow* window, const char* message) {
    EnsureWindowActive(
        views::Widget::GetWidgetForNativeWindow(window->GetNativeWindow()),
        message);
  }

  void EnsureWindowActive(views::Widget* widget, const char* message) {
    SCOPED_TRACE(message);
    EXPECT_TRUE(widget);

    views::test::WaitForWidgetActive(widget, true);
  }

  // Send Ctrl/Cmd+keycode in the key window to the browser.
  void SendAcceleratorSync(ui::KeyboardCode keycode, bool shift, bool alt) {
#if BUILDFLAG(IS_MAC)
    bool control = false;
    bool command = true;
#else
    bool control = true;
    bool command = false;
#endif

    // Wait for "key press" instead of "key release" because some tests destroy
    // the target in response to "key press", which prevents "key release" from
    // being observed.
    ASSERT_TRUE(ui_test_utils::SendKeyPressSync(
        browser(), keycode, control, shift, alt, command,
        /* wait_for=*/ui_controls::KeyEventType::kKeyPress));
  }

  void SetUpOnMainThread() override {
    // Make the browser active (ensures the app can receive key events).
    EXPECT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));

    test_api_ =
        std::make_unique<test::PermissionRequestManagerTestApi>(browser());
    EXPECT_TRUE(test_api_->manager());

    test_api_->AddSimpleRequest(browser()
                                    ->tab_strip_model()
                                    ->GetActiveWebContents()
                                    ->GetPrimaryMainFrame(),
                                permissions::RequestType::kGeolocation);

    EXPECT_TRUE(browser()->window()->IsActive());

    // The permission prompt is shown asynchronously.
    base::RunLoop().RunUntilIdle();
    OpenBubbleIfRequestChipUiIsShown();

    EnsureWindowActive(test_api_->GetPromptWindow(), "show permission bubble");
  }

  void JumpToNextOpenTab() {
#if BUILDFLAG(IS_MAC)
    SendAcceleratorSync(ui::VKEY_RIGHT, false, true);
#else
    SendAcceleratorSync(ui::VKEY_TAB, false, false);
#endif
  }

  void JumpToPreviousOpenTab() {
#if BUILDFLAG(IS_MAC)
    SendAcceleratorSync(ui::VKEY_LEFT, false, true);
#else
    SendAcceleratorSync(ui::VKEY_TAB, true, false);
#endif
    views::test::RunScheduledLayout(
        BrowserView::GetBrowserViewForBrowser(browser()));
  }

  void OpenBubbleIfRequestChipUiIsShown() {
    // If the permission request is displayed using the chip UI, simulate a
    // click on the chip to trigger showing the prompt.
    BrowserView* browser_view =
        BrowserView::GetBrowserViewForBrowser(browser());
    LocationBarView* lbv = browser_view->toolbar()->location_bar();
    if (lbv->GetChipController()->IsPermissionPromptChipVisible() &&
        !lbv->GetChipController()->IsBubbleShowing()) {
      views::test::ButtonTestApi(lbv->GetChipController()->chip())
          .NotifyClick(ui::MouseEvent(
              ui::EventType::kMousePressed, gfx::Point(), gfx::Point(),
              ui::EventTimeForNow(), ui::EF_LEFT_MOUSE_BUTTON, 0));
      base::RunLoop().RunUntilIdle();
    }
  }

  void TestSwitchingTabsWithCurlyBraces() {
    // Also test switching tabs with curly braces. "VKEY_OEM_4" is
    // LeftBracket/Brace on a US keyboard, which ui::MacKeyCodeForWindowsKeyCode
    // will map to '{' when shift is passed. Also note there are only two tabs
    // so it doesn't matter which direction is taken (it wraps).
    chrome::FocusLocationBar(browser());
    SendAcceleratorSync(ui::VKEY_OEM_4, true, false);
    EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
    OpenBubbleIfRequestChipUiIsShown();
    EnsureWindowActive(test_api_->GetPromptWindow(),
                       "switch to permission tab with curly brace");
    EXPECT_TRUE(test_api_->GetPromptWindow());

    SendAcceleratorSync(ui::VKEY_OEM_4, true, false);
    EXPECT_EQ(1, browser()->tab_strip_model()->active_index());
    browser()->window()->Activate();
    EnsureWindowActive(browser()->window(), "switch away with curly brace");
    EXPECT_FALSE(test_api_->GetPromptWindow());
  }

 protected:
  std::unique_ptr<test::PermissionRequestManagerTestApi> test_api_;
};

#if BUILDFLAG(IS_CHROMEOS)
// TODO(crbug.com/1072425): views::test::WidgetTest::GetAllWidgets() crashes
// on Chrome OS, need to investigate\fix that.
#define MAYBE_CmdWClosesWindow DISABLED_CmdWClosesWindow
#else
#define MAYBE_CmdWClosesWindow CmdWClosesWindow
#endif

// There is only one tab. Ctrl/Cmd+w will close it along with the browser
// window.
IN_PROC_BROWSER_TEST_F(PermissionBubbleInteractiveUITest,
                       MAYBE_CmdWClosesWindow) {
  EXPECT_TRUE(browser()->window()->IsVisible());

  // On Windows, the WM_NCDESTROY message triggering Widget destruction may not
  // have been processed by the time `SendAcceleratorSync` returns (only waits
  // for WM_KEYDOWN). For that reason, wait until there are no more widgets
  // instead of checking immediately that there are no more widgets.
  SendAcceleratorSync(ui::VKEY_W, false, false);
  EXPECT_TRUE(base::test::RunUntil(
      [&] { return views::test::WidgetTest::GetAllWidgets().empty(); }));
}

#if BUILDFLAG(IS_MAC)
// TODO(crbug.com/40839289): For Mac builders, the test fails after activating
// the browser and cannot spot the widget. Needs investigation and fix.
#define MAYBE_SwitchTabs DISABLED_SwitchTabs
#else
#define MAYBE_SwitchTabs SwitchTabs
#endif

// Add a tab, ensure we can switch away and back using Ctrl+Tab and
// Ctrl+Shift+Tab at aura and using Cmd+Alt+Left/Right and curly braces at
// MacOS.
IN_PROC_BROWSER_TEST_F(PermissionBubbleInteractiveUITest, MAYBE_SwitchTabs) {
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());
  EXPECT_TRUE(test_api_->GetPromptWindow());

  // Add a blank tab in the foreground.
  AddBlankTabAndShow(browser());
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());

#if BUILDFLAG(IS_MAC)
  // The bubble should hide and give focus back to the browser. However, the
  // test environment can't guarantee that macOS decides that the Browser window
  // is actually the "best" window to activate upon closing the current key
  // window. So activate it manually.
  browser()->window()->Activate();
  EnsureWindowActive(browser()->window(), "tab added");
#endif

  // Prompt is hidden while its tab is not active.
  EXPECT_FALSE(test_api_->GetPromptWindow());

  // Now a webcontents is active, it gets a first shot at processing the
  // accelerator before sending it back unhandled to the browser via IPC. That's
  // all a bit much to handle in a test, so activate the location bar.
  chrome::FocusLocationBar(browser());

  JumpToPreviousOpenTab();
  EXPECT_EQ(0, browser()->tab_strip_model()->active_index());

  OpenBubbleIfRequestChipUiIsShown();

  // Note we don't need to makeKeyAndOrderFront for mac os: the permission
  // window will take focus when it is shown again.
  EnsureWindowActive(
      test_api_->GetPromptWindow(),
      "switched to permission tab with ctrl+shift+tab or arrow at mac os");
  EXPECT_TRUE(test_api_->GetPromptWindow());

  // Ensure we can switch away with the bubble active.
  JumpToNextOpenTab();
  EXPECT_EQ(1, browser()->tab_strip_model()->active_index());

  browser()->window()->Activate();
  EnsureWindowActive(browser()->window(),
                     "switch away with ctrl+tab or arrow at mac os");
  EXPECT_FALSE(test_api_->GetPromptWindow());

#if BUILDFLAG(IS_MAC)
  TestSwitchingTabsWithCurlyBraces();
#endif
}