// Copyright (c) 2006-2009 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 <string>

#include "app/l10n_util.h"
#include "base/file_path.h"
#include "base/gfx/rect.h"
#include "base/keyboard_codes.h"
#include "chrome/browser/view_ids.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/test/automation/automation_constants.h"
#include "chrome/test/automation/browser_proxy.h"
#include "chrome/test/automation/tab_proxy.h"
#include "chrome/test/automation/window_proxy.h"
#include "chrome/test/ui/ui_test.h"
#include "grit/generated_resources.h"
#include "net/base/net_util.h"
#include "views/event.h"

class BlockedPopupContainerInteractiveTest : public UITest {
 protected:
  BlockedPopupContainerInteractiveTest() {
    show_window_ = true;
  }

  virtual void SetUp() {
    UITest::SetUp();

    browser_ = automation()->GetBrowserWindow(0);
    ASSERT_TRUE(browser_.get());

    window_ = browser_->GetWindow();
    ASSERT_TRUE(window_.get());

    tab_ = browser_->GetTab(0);
    ASSERT_TRUE(tab_.get());
  }

  void NavigateMainTabTo(const std::string& file_name) {
    FilePath filename(test_data_directory_);
    filename = filename.AppendASCII("constrained_files");
    filename = filename.AppendASCII(file_name);
    ASSERT_TRUE(tab_->NavigateToURL(net::FilePathToFileURL(filename)));
  }

  void SimulateClickInCenterOf(const scoped_refptr<WindowProxy>& window) {
    gfx::Rect tab_view_bounds;
    ASSERT_TRUE(window->GetViewBounds(VIEW_ID_TAB_CONTAINER,
                                      &tab_view_bounds, true));

    // Simulate a click of the actual link to force user_gesture to be
    // true; if we don't, the resulting popup will be constrained, which
    // isn't what we want to test.
    ASSERT_TRUE(window->SimulateOSClick(tab_view_bounds.CenterPoint(),
                                        views::Event::EF_LEFT_BUTTON_DOWN));
  }

  scoped_refptr<BrowserProxy> browser_;
  scoped_refptr<WindowProxy> window_;
  scoped_refptr<TabProxy> tab_;
};

TEST_F(BlockedPopupContainerInteractiveTest, TestOpenAndResizeTo) {
  NavigateMainTabTo("constrained_window_onload_resizeto.html");
  SimulateClickInCenterOf(window_);

  ASSERT_TRUE(automation()->WaitForWindowCountToBecome(2, 1000));

  scoped_refptr<BrowserProxy> popup_browser(automation()->GetBrowserWindow(1));
  ASSERT_TRUE(popup_browser != NULL);
  scoped_refptr<WindowProxy> popup_window(popup_browser->GetWindow());
  ASSERT_TRUE(popup_window != NULL);

  // Make sure we were created with the correct width and height.
  gfx::Rect rect;
  bool is_timeout = false;
  ASSERT_TRUE(popup_window->GetViewBoundsWithTimeout(
                  VIEW_ID_TAB_CONTAINER, &rect, false, 1000, &is_timeout));
  ASSERT_FALSE(is_timeout);
  EXPECT_EQ(300, rect.width());
  EXPECT_EQ(320, rect.height());

#if defined(OS_LINUX)
  // It seems we have to wait a little bit for the widgets to spin up before
  // we can start clicking on them.
  PlatformThread::Sleep(500);
#endif

  SimulateClickInCenterOf(popup_window);

  // No idea how to wait here other then sleeping. This timeout used to be
  // lower, then we started hitting it before it was done. :(
  PlatformThread::Sleep(5000);

  // The actual content will be LESS than (200, 200) because resizeTo
  // deals with a window's outer{Width,Height} instead of its
  // inner{Width,Height}.
  is_timeout = false;
  ASSERT_TRUE(popup_window->GetViewBoundsWithTimeout(
                 VIEW_ID_TAB_CONTAINER, &rect, false, 1000, &is_timeout));
  ASSERT_FALSE(is_timeout);
  EXPECT_LT(rect.height(), 200);
#if defined(OS_LINUX)
  // On Linux we may run in an environment where there is no window frame. In
  // this case our width might be exactly 200. The height will still be less
  // because we have to show the location bar.
  EXPECT_LE(rect.width(), 200);
#else
  EXPECT_LT(rect.width(), 200);
#endif
}

// Helper function used to get the number of blocked popups out of the window
// title.
bool ParseCountOutOfTitle(const std::wstring& title, int* output) {
  // Since we will be reading the number of popup windows open by grabbing the
  // number out of the window title, and that format string is localized, we
  // need to find out the offset into that string.
  const wchar_t* placeholder = L"XXXX";
  size_t offset =
      l10n_util::GetStringF(IDS_POPUPS_BLOCKED_COUNT, placeholder).
      find(placeholder);

  std::wstring number;
  while (offset < title.size() && iswdigit(title[offset])) {
    number += title[offset];
    offset++;
  }

  return StringToInt(WideToUTF16(number), output);
}

// Tests that in the window.open() equivalent of a fork bomb, we stop building
// windows.
// Flaky, http://crbug.com/26692.
TEST_F(BlockedPopupContainerInteractiveTest, FLAKY_DontSpawnEndlessPopups) {
  NavigateMainTabTo("infinite_popups.html");
  SimulateClickInCenterOf(window_);

  ASSERT_TRUE(automation()->WaitForWindowCountToBecome(2, 1000));

  scoped_refptr<BrowserProxy> popup_browser(automation()->GetBrowserWindow(1));
  ASSERT_TRUE(popup_browser.get());
  scoped_refptr<TabProxy> popup_tab(popup_browser->GetTab(0));
  ASSERT_TRUE(popup_tab.get());

  // And now we spin, waiting to make sure that we don't spawn popup
  // windows endlessly. The current limit is 25, so allowing for possible race
  // conditions and one off errors, don't break out until we go over 30 popup
  // windows (in which case we are bork bork bork).
  const int kMaxPopupWindows = 30;

  int popup_window_count = 0;
  int new_popup_window_count = 0;
  int times_slept = 0;
  bool continuing = true;
  while (continuing && popup_window_count < kMaxPopupWindows) {
    ASSERT_TRUE(popup_tab->GetBlockedPopupCount(&new_popup_window_count));
    if (new_popup_window_count == popup_window_count) {
      if (times_slept == 10) {
        continuing = false;
      } else {
        // Nothing intereseting is going on wait it out.
        PlatformThread::Sleep(automation::kSleepTime);
        times_slept++;
      }
    } else {
      times_slept = 0;
    }

    EXPECT_GE(new_popup_window_count, popup_window_count);
    EXPECT_LE(new_popup_window_count, kMaxPopupWindows);
    popup_window_count = new_popup_window_count;
  }
}

// Make sure that we refuse to close windows when a constrained popup is
// displayed.
TEST_F(BlockedPopupContainerInteractiveTest, FLAKY_WindowOpenWindowClosePopup) {
  NavigateMainTabTo("openclose_main.html");
  SimulateClickInCenterOf(window_);

  ASSERT_TRUE(automation()->WaitForWindowCountToBecome(2, 5000));

  // Make sure we have a blocked popup notification
  scoped_refptr<BrowserProxy> popup_browser(automation()->GetBrowserWindow(1));
  ASSERT_TRUE(popup_browser.get());
  scoped_refptr<WindowProxy> popup_window(popup_browser->GetWindow());
  ASSERT_TRUE(popup_window.get());
  scoped_refptr<TabProxy> popup_tab(popup_browser->GetTab(0));
  ASSERT_TRUE(popup_tab.get());
  ASSERT_TRUE(popup_tab->WaitForBlockedPopupCountToChangeTo(1, 1000));

  // Ensure we didn't close the first popup window.
  ASSERT_FALSE(automation()->WaitForWindowCountToBecome(1, 3000));
}

TEST_F(BlockedPopupContainerInteractiveTest, BlockAlertFromBlockedPopup) {
  NavigateMainTabTo("block_alert.html");

  // Wait for there to be an app modal dialog (and fail if it's shown).
  ASSERT_FALSE(automation()->WaitForAppModalDialog(4000));

  // Ensure one browser window.
  int browser_window_count;
  ASSERT_TRUE(automation()->GetBrowserWindowCount(&browser_window_count));
  ASSERT_EQ(1, browser_window_count);

  // Ensure one blocked popup window: the popup didn't escape.
  int popup_count = 0;
  ASSERT_TRUE(tab_->GetBlockedPopupCount(&popup_count));
  ASSERT_EQ(1, popup_count);
}

TEST_F(BlockedPopupContainerInteractiveTest, ShowAlertFromNormalPopup) {
  NavigateMainTabTo("show_alert.html");
  SimulateClickInCenterOf(window_);

  ASSERT_TRUE(automation()->WaitForWindowCountToBecome(2, 5000));

  scoped_refptr<BrowserProxy> popup_browser(automation()->GetBrowserWindow(1));
  ASSERT_TRUE(popup_browser.get());
  scoped_refptr<WindowProxy> popup_window(popup_browser->GetWindow());
  ASSERT_TRUE(popup_window.get());
  scoped_refptr<TabProxy> popup_tab(popup_browser->GetTab(0));
  ASSERT_TRUE(popup_tab.get());

#if defined(OS_LINUX)
  // It seems we have to wait a little bit for the widgets to spin up before
  // we can start clicking on them.
  PlatformThread::Sleep(500);
#endif

  SimulateClickInCenterOf(popup_window);

  // Wait for there to be an app modal dialog.
  ASSERT_TRUE(automation()->WaitForAppModalDialog(5000));
}

// Make sure that window focus works while creating a popup window so that we
// don't
TEST_F(BlockedPopupContainerInteractiveTest, DontBreakOnBlur) {
  NavigateMainTabTo("window_blur_test.html");
  SimulateClickInCenterOf(window_);

  // Wait for the popup window to open.
  ASSERT_TRUE(automation()->WaitForWindowCountToBecome(2, 1000));

  // We popup shouldn't be closed by the onblur handler.
  ASSERT_FALSE(automation()->WaitForWindowCountToBecome(1, 1500));
}

// Tests that tab related keyboard accelerators are reserved by the app.

class BrowserInteractiveTest : public UITest {
};

TEST_F(BrowserInteractiveTest, ReserveKeyboardAccelerators) {
  const std::string kBadPage =
      "<html><script>"
      "document.onkeydown = function() {"
      "  event.preventDefault();"
      "  return false;"
      "}"
      "</script></html>";
  scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0));
  browser->AppendTab(GURL("data:text/html," + kBadPage));
  int tab_count = 0;
  ASSERT_TRUE(browser->GetTabCount(&tab_count));
  ASSERT_EQ(tab_count, 2);

  int active_tab = 0;
  ASSERT_TRUE(browser->GetActiveTabIndex(&active_tab));
  ASSERT_EQ(active_tab, 1);

  scoped_refptr<WindowProxy> window(browser->GetWindow());
  ASSERT_TRUE(window->SimulateOSKeyPress(
      base::VKEY_TAB, views::Event::EF_CONTROL_DOWN));
  ASSERT_TRUE(browser->WaitForTabToBecomeActive(0, action_max_timeout_ms()));

#if !defined(OS_MACOSX)  // see BrowserWindowCocoa::GetCommandId
  ASSERT_TRUE(browser->ActivateTab(1));
  ASSERT_TRUE(window->SimulateOSKeyPress(
      base::VKEY_F4, views::Event::EF_CONTROL_DOWN));
  ASSERT_TRUE(browser->WaitForTabCountToBecome(1, action_max_timeout_ms()));
#endif
}