chromium/chrome/browser/ui/cocoa/tab_contents/web_contents_view_mac_interactive_uitest.mm

// 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.

#import <Cocoa/Cocoa.h>

#include "base/run_loop.h"
#import "base/task/single_thread_task_runner.h"
#include "base/test/bind.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_window.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/browser/ui/tabs/tab_strip_model_observer.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/interactive_test_utils.h"
#include "components/input/native_web_keyboard_event.h"
#include "content/public/browser/render_widget_host.h"
#include "content/public/browser/render_widget_host_view.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_utils.h"
#import "testing/gtest_mac.h"

using WebContentsViewMacInteractiveTest = InProcessBrowserTest;

namespace {

// A helper that will wait until a tab is removed from a specific Browser.
class TabRemovedWaiter : public TabStripModelObserver {
 public:
  explicit TabRemovedWaiter(Browser* browser) {
    browser->tab_strip_model()->AddObserver(this);
  }
  TabRemovedWaiter(const TabRemovedWaiter&) = delete;
  TabRemovedWaiter& operator=(const TabRemovedWaiter&) = delete;
  ~TabRemovedWaiter() override = default;

  void Wait() { run_loop_.Run(); }

  // TabStripModelObserver:
  void OnTabStripModelChanged(
      TabStripModel* tab_strip_model,
      const TabStripModelChange& change,
      const TabStripSelectionChange& selection) override {
    if (change.type() == TabStripModelChange::kRemoved)
      run_loop_.Quit();
  }

 private:
  base::RunLoop run_loop_;
};

}  // namespace

// Integration test for a <select> popup run by the Mac-specific
// content::PopupMenuHelper, owned by a WebContentsViewMac.
// TODO(crbug.com/40758190): this test is flaking on the bots. Re-enable it when
// it's fixed.
IN_PROC_BROWSER_TEST_F(WebContentsViewMacInteractiveTest,
                       DISABLED_SelectMenuLifetime) {
  EXPECT_TRUE(ui_test_utils::BringBrowserWindowToFront(browser()));
  EXPECT_TRUE(embedded_test_server()->Start());

  ASSERT_TRUE(
      AddTabAtIndex(1, GURL(embedded_test_server()->GetURL("/select.html")),
                    ui::PAGE_TRANSITION_LINK));
  content::WebContents* web_contents =
      browser()->tab_strip_model()->GetActiveWebContents();

  __block NSString* first_item;

  // Set up a callback to trigger when a native menu is displayed.
  id token = [NSNotificationCenter.defaultCenter
      addObserverForName:NSMenuDidBeginTrackingNotification
                  object:nil
                   queue:nil
              usingBlock:^(NSNotification* notification) {
                first_item = [[[notification.object itemAtIndex:0] title] copy];
                // We can't close the tab until after
                // NSMenuDidBeginTrackingNotification is processed (i.e. after
                // this block returns). So post a task to run on the inner run
                // loop which will close the tab (and cancel tracking in
                // ~PopupMenuHelper()) and quit the outer run loop to continue
                // the test.
                base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
                    FROM_HERE, base::BindLambdaForTesting([&] {
                      browser()->tab_strip_model()->CloseWebContentsAt(1, 0);
                    }));
              }];

  EXPECT_EQ(2, browser()->tab_strip_model()->count());
  TabRemovedWaiter tab_removed_waiter(browser());

  // Trigger the <select> menu via a simulated click, and wait for the ensuing
  // callback/observer/block/posted task magic to unfold.
  content::SimulateMouseClickOrTapElementWithId(web_contents, "select");
  tab_removed_waiter.Wait();

  [NSNotificationCenter.defaultCenter removeObserver:token];

  // Expect that the menu is no longer being tracked.
  EXPECT_NE(NSEventTrackingRunLoopMode, NSRunLoop.currentRunLoop.currentMode);
  // Expect that the menu that was shown was the expected one.
  EXPECT_NSEQ(@"Apple", first_item);
  // Expect that the browser tab is no longer there.
  EXPECT_EQ(1, browser()->tab_strip_model()->count());
}