// Copyright (c) 2006-2008 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/view_ids.h"
#include "chrome/views/view.h"
#include "chrome/test/automation/browser_proxy.h"
#include "chrome/test/automation/window_proxy.h"
#include "chrome/test/automation/tab_proxy.h"
#include "chrome/test/ui/ui_test.h"
#include "net/url_request/url_request_unittest.h"

namespace {

// The delay waited after sending an OS simulated event.
const int kActionDelayMs = 500;

const wchar_t kDocRoot[] = L"chrome/test/data";

const wchar_t kSimplePage[] = L"files/focus/page_with_focus.html";
const wchar_t kStealFocusPage[] = L"files/focus/page_steals_focus.html";
const wchar_t kTypicalPage[] = L"files/focus/typical_page.html";

class BrowserFocusTest : public UITest {
 public:
  BrowserFocusTest() {
    show_window_ = true;
    dom_automation_enabled_ = true;
  }
};

// Activate a tab by clicking on it.  Returns true if the call was successful
// (meaning the messages were correctly sent, but does not guarantee the tab
// has been changed).
bool ActivateTabByClick(AutomationProxy* automation,
                        WindowProxy* browser_window,
                        int tab_index) {
  // Click on the tab.
  gfx::Rect bounds;

  if (!browser_window->GetViewBounds(VIEW_ID_TAB_0 + tab_index, &bounds, true))
    return false;

  POINT click(bounds.CenterPoint().ToPOINT());
  if (!browser_window->SimulateOSClick(click,
                                       views::Event::EF_LEFT_BUTTON_DOWN))
    return false;

  // Wait a bit to let the click be processed.
  ::Sleep(kActionDelayMs);

  return true;
}

}  // namespace

TEST_F(BrowserFocusTest, BrowsersRememberFocus) {
  scoped_refptr<HTTPTestServer> server =
      HTTPTestServer::CreateServer(kDocRoot);
  ASSERT_TRUE(NULL != server.get());

  // First we navigate to our test page.
  GURL url = server->TestServerPageW(kSimplePage);
  scoped_ptr<TabProxy> tab(GetActiveTab());
  EXPECT_NE(AUTOMATION_MSG_NAVIGATION_ERROR, tab->NavigateToURL(url));

  // The focus should be on the Tab contents.
  scoped_ptr<WindowProxy> window(automation()->GetActiveWindow());
  ASSERT_TRUE(window.get() != NULL);

  scoped_ptr<BrowserProxy> browser(automation()->
      GetBrowserForWindow(window.get()));
  ASSERT_TRUE(browser.get() != NULL);

  int focused_view_id;
  EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(VIEW_ID_TAB_CONTAINER, focused_view_id);

  // Now hide the window, show it again, the focus should not have changed.
  EXPECT_TRUE(window->SetVisible(false));
  EXPECT_TRUE(window->SetVisible(true));
  EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(VIEW_ID_TAB_CONTAINER, focused_view_id);

  // Click on the location bar.
  gfx::Rect bounds;
  EXPECT_TRUE(window->GetViewBounds(VIEW_ID_LOCATION_BAR, &bounds, false));
  POINT click(bounds.CenterPoint().ToPOINT());

  EXPECT_TRUE(window->SimulateOSClick(click,
      views::Event::EF_LEFT_BUTTON_DOWN));
  ::Sleep(kActionDelayMs);
  EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(VIEW_ID_LOCATION_BAR, focused_view_id);

  // Hide the window, show it again, the focus should not have changed.
  EXPECT_TRUE(window->SetVisible(false));
  EXPECT_TRUE(window->SetVisible(true));
  EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(VIEW_ID_LOCATION_BAR, focused_view_id);

  // Open a new browser window.
  EXPECT_TRUE(automation()->OpenNewBrowserWindow(SW_SHOWNORMAL));
  scoped_ptr<WindowProxy> new_window(automation()->GetActiveWindow());
  ASSERT_TRUE(new_window.get() != NULL);
  scoped_ptr<BrowserProxy> new_browser(automation()->
      GetBrowserForWindow(new_window.get()));
  ASSERT_TRUE(new_browser.get() != NULL);

  // Let's make sure we have 2 different browser windows.
  EXPECT_TRUE(browser->handle() != new_browser->handle());

  tab.reset(new_browser->GetActiveTab());
  EXPECT_TRUE(tab.get());
  tab->NavigateToURL(url);

  // Switch to the 1st browser window, focus should still be on the location
  // bar and the second browser should have nothing focused.
  EXPECT_TRUE(window->Activate());
  EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(VIEW_ID_LOCATION_BAR, focused_view_id);
  EXPECT_TRUE(new_window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(-1, focused_view_id);

  // Switch back to the second browser, focus should still be on the page.
  EXPECT_TRUE(new_window->Activate());
  EXPECT_TRUE(new_window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(VIEW_ID_TAB_CONTAINER, focused_view_id);
  EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(-1, focused_view_id);
}

// Tabs remember focus.
TEST_F(BrowserFocusTest, TabsRememberFocus) {
  scoped_refptr<HTTPTestServer> server =
      HTTPTestServer::CreateServer(kDocRoot);
  ASSERT_TRUE(NULL != server.get());

  scoped_ptr<WindowProxy> window(automation()->GetActiveWindow());
  ASSERT_TRUE(window.get() != NULL);
  scoped_ptr<BrowserProxy> browser(
      automation()->GetBrowserForWindow(window.get()));
  ASSERT_TRUE(browser.get() != NULL);

  // First we navigate to our test page.
  GURL url = server->TestServerPageW(kSimplePage);
  scoped_ptr<TabProxy> tab(GetActiveTab());
  tab->NavigateToURL(url);

  // Create several tabs.
  EXPECT_TRUE(browser->AppendTab(url));
  EXPECT_TRUE(browser->AppendTab(url));
  EXPECT_TRUE(browser->AppendTab(url));
  EXPECT_TRUE(browser->AppendTab(url));

  int tab_count;
  EXPECT_TRUE(browser->GetTabCount(&tab_count));
  ASSERT_EQ(5, tab_count);

  // Alternate focus for the tab.
  const bool kFocusPage[3][5] = {
    { true, true, true, true, false },
    { false, false, false, false, false },
    { false, true, false, true, false }
  };

  for (int i = 1; i < 3; i++) {
    for (int j = 0; j < 5; j++) {
      // Click on the tab.
      ActivateTabByClick(automation(), window.get(), j);

      // Activate the location bar or the page.
      int view_id = kFocusPage[i][j] ? VIEW_ID_TAB_CONTAINER :
                                       VIEW_ID_LOCATION_BAR;

      gfx::Rect bounds;
      EXPECT_TRUE(window->GetViewBounds(view_id, &bounds, true));
      POINT click(bounds.CenterPoint().ToPOINT());
      EXPECT_TRUE(window->SimulateOSClick(click,
          views::Event::EF_LEFT_BUTTON_DOWN));
      ::Sleep(kActionDelayMs);
    }

    // Now come back to the tab and check the right view is focused.
    for (int j = 0; j < 5; j++) {
      // Click on the tab.
      ActivateTabByClick(automation(), window.get(), j);

      // Activate the location bar or the page.
      int exp_view_id = kFocusPage[i][j] ? VIEW_ID_TAB_CONTAINER :
                                           VIEW_ID_LOCATION_BAR;
      int focused_view_id;
      EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
      EXPECT_EQ(exp_view_id, focused_view_id);
    }
  }
}

// Background window does not steal focus.
TEST_F(BrowserFocusTest, BackgroundBrowserDontStealFocus) {
  scoped_refptr<HTTPTestServer> server =
      HTTPTestServer::CreateServer(kDocRoot);
  ASSERT_TRUE(NULL != server.get());

  // First we navigate to our test page.
  GURL simple_page_url = server->TestServerPageW(kSimplePage);
  scoped_ptr<TabProxy> tab(GetActiveTab());
  tab->NavigateToURL(simple_page_url);

  scoped_ptr<WindowProxy> window(automation()->GetActiveWindow());
  ASSERT_TRUE(window.get() != NULL);
  scoped_ptr<BrowserProxy> browser(
      automation()->GetBrowserForWindow(window.get()));
  ASSERT_TRUE(browser.get() != NULL);

  // Open a new browser window.
  EXPECT_TRUE(automation()->OpenNewBrowserWindow(SW_SHOWNORMAL));
  scoped_ptr<WindowProxy> new_window(automation()->GetActiveWindow());
  ASSERT_TRUE(window.get() != NULL);
  scoped_ptr<BrowserProxy> new_browser(
      automation()->GetBrowserForWindow(new_window.get()));
  ASSERT_TRUE(new_browser.get() != NULL);

  GURL steal_focus_url = server->TestServerPageW(kStealFocusPage);
  new_browser->AppendTab(steal_focus_url);

  // Make the first browser active
  EXPECT_TRUE(window->Activate());

  // Wait for the focus to be stolen by the other browser.
  ::Sleep(2000);

  // Make sure the 1st browser window is still active.
  bool is_active = false;
  EXPECT_TRUE(window->IsActive(&is_active));
  EXPECT_TRUE(is_active);
}

// Page cannot steal focus when focus is on location bar.
TEST_F(BrowserFocusTest, LocationBarLockFocus) {
  scoped_refptr<HTTPTestServer> server =
      HTTPTestServer::CreateServer(kDocRoot);
  ASSERT_TRUE(NULL != server.get());

  // Open the page that steals focus.
  GURL url = server->TestServerPageW(kStealFocusPage);
  scoped_ptr<TabProxy> tab(GetActiveTab());
  tab->NavigateToURL(url);

  scoped_ptr<WindowProxy> window(automation()->GetActiveWindow());
  ASSERT_TRUE(window.get() != NULL);
  scoped_ptr<BrowserProxy> browser(
      automation()->GetBrowserForWindow(window.get()));
  ASSERT_TRUE(browser.get() != NULL);

  // Click on the location bar.
  gfx::Rect bounds;
  EXPECT_TRUE(window->GetViewBounds(VIEW_ID_LOCATION_BAR, &bounds, true));
  POINT click(bounds.CenterPoint().ToPOINT());
  EXPECT_TRUE(window->SimulateOSClick(click,
      views::Event::EF_LEFT_BUTTON_DOWN));
  ::Sleep(kActionDelayMs);

  // Wait for the page to steal focus.
  ::Sleep(2000);

  // Make sure the location bar is still focused.
  int focused_view_id;
  EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(VIEW_ID_LOCATION_BAR, focused_view_id);
}

// Focus traversal
TEST_F(BrowserFocusTest, FocusTraversal) {
  scoped_refptr<HTTPTestServer> server =
      HTTPTestServer::CreateServer(kDocRoot);
  ASSERT_TRUE(NULL != server.get());

  // Open the page the test page.
  GURL url = server->TestServerPageW(kTypicalPage);
  scoped_ptr<TabProxy> tab(GetActiveTab());
  tab->NavigateToURL(url);

  scoped_ptr<WindowProxy> window(automation()->GetActiveWindow());
  ASSERT_TRUE(window.get() != NULL);
  scoped_ptr<BrowserProxy> browser(
    automation()->GetBrowserForWindow(window.get()));
  ASSERT_TRUE(browser.get() != NULL);

  // Click on the location bar.
  gfx::Rect bounds;
  EXPECT_TRUE(window->GetViewBounds(VIEW_ID_LOCATION_BAR, &bounds, true));
  POINT click(bounds.CenterPoint().ToPOINT());
  EXPECT_TRUE(window->SimulateOSClick(click,
      views::Event::EF_LEFT_BUTTON_DOWN));
  ::Sleep(kActionDelayMs);

  const wchar_t* kExpElementIDs[] = {
    L"",  // Initially no element in the page should be focused
          // (the location bar is focused).
    L"textEdit", L"searchButton", L"luckyButton", L"googleLink", L"gmailLink",
    L"gmapLink"
  };

  // Test forward focus traversal.
  for (int i = 0; i < 3; ++i) {
    // Location bar should be focused.
    int focused_view_id;
    EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
    EXPECT_EQ(VIEW_ID_LOCATION_BAR, focused_view_id);

    // Now let's press tab to move the focus.
    for (int j = 0; j < 7; ++j) {
      // Let's make sure the focus is on the expected element in the page.
      std::wstring actual;
      ASSERT_TRUE(tab->ExecuteAndExtractString(L"",
          L"window.domAutomationController.send(getFocusedElement());",
          &actual));
      ASSERT_STREQ(kExpElementIDs[j], actual.c_str());

      window->SimulateOSKeyPress(L'\t', 0);
      ::Sleep(kActionDelayMs);
    }
  }

  // Now let's try reverse focus traversal.
  for (int i = 0; i < 3; ++i) {
    // Location bar should be focused.
    int focused_view_id;
    EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
    EXPECT_EQ(VIEW_ID_LOCATION_BAR, focused_view_id);

    // Now let's press tab to move the focus.
    for (int j = 0; j < 7; ++j) {
      window->SimulateOSKeyPress(L'\t', views::Event::EF_SHIFT_DOWN);
      ::Sleep(kActionDelayMs);

      // Let's make sure the focus is on the expected element in the page.
      std::wstring actual;
      ASSERT_TRUE(tab->ExecuteAndExtractString(L"",
          L"window.domAutomationController.send(getFocusedElement());",
          &actual));
      ASSERT_STREQ(kExpElementIDs[6 - j], actual.c_str());
    }
  }
}

// Make sure Find box can request focus, even when it is already open.
TEST_F(BrowserFocusTest, FindFocusTest) {
  scoped_refptr<HTTPTestServer> server =
      HTTPTestServer::CreateServer(kDocRoot);
  ASSERT_TRUE(NULL != server.get());

  // Open some page (any page that doesn't steal focus).
  GURL url = server->TestServerPageW(kTypicalPage);
  scoped_ptr<TabProxy> tab(GetActiveTab());
  tab->NavigateToURL(url);

  scoped_ptr<WindowProxy> window(automation()->GetActiveWindow());
  ASSERT_TRUE(window.get() != NULL);
  scoped_ptr<BrowserProxy> browser(
      automation()->GetBrowserForWindow(window.get()));
  ASSERT_TRUE(browser.get() != NULL);

  // Press Ctrl+F, which will make the Find box open and request focus.
  static const int VK_F = 0x46;
  EXPECT_TRUE(window->SimulateOSKeyPress(VK_F, views::Event::EF_CONTROL_DOWN));
  ::Sleep(kActionDelayMs);
  int focused_view_id;
  EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(VIEW_ID_FIND_IN_PAGE_TEXT_FIELD, focused_view_id);

  // Click on the location bar.
  gfx::Rect bounds;
  EXPECT_TRUE(window->GetViewBounds(VIEW_ID_LOCATION_BAR, &bounds, true));
  POINT click(bounds.CenterPoint().ToPOINT());
  EXPECT_TRUE(window->SimulateOSClick(click,
              views::Event::EF_LEFT_BUTTON_DOWN));
  ::Sleep(kActionDelayMs);
  // Make sure the location bar is focused.
  EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(VIEW_ID_LOCATION_BAR, focused_view_id);

  // Now press Ctrl+F again and focus should move to the Find box.
  EXPECT_TRUE(window->SimulateOSKeyPress(VK_F, views::Event::EF_CONTROL_DOWN));
  ::Sleep(kActionDelayMs);
  EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(VIEW_ID_FIND_IN_PAGE_TEXT_FIELD, focused_view_id);

  // Set focus to the page.
  EXPECT_TRUE(window->GetViewBounds(VIEW_ID_TAB_CONTAINER, &bounds, true));
  click = bounds.CenterPoint().ToPOINT();
  EXPECT_TRUE(window->SimulateOSClick(click,
                                      views::Event::EF_LEFT_BUTTON_DOWN));
  ::Sleep(kActionDelayMs);
  EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(VIEW_ID_TAB_CONTAINER, focused_view_id);

  // Now press Ctrl+F again and focus should move to the Find box.
  EXPECT_TRUE(window->SimulateOSKeyPress(VK_F, views::Event::EF_CONTROL_DOWN));
  ::Sleep(kActionDelayMs);
  EXPECT_TRUE(window->GetFocusedViewID(&focused_view_id));
  EXPECT_EQ(VIEW_ID_FIND_IN_PAGE_TEXT_FIELD, focused_view_id);
}