// Copyright (c) 2011 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.

// Tests for the top plugins to catch regressions in our plugin host code, as
// well as in the out of process code.  Currently this tests:
//  Flash
//  Real
//  QuickTime
//  Windows Media Player
//    -this includes both WMP plugins.  npdsplay.dll is the older one that
//     comes with XP.  np-mswmp.dll can be downloaded from Microsoft and
//     needs SP2 or Vista.

#include "build/build_config.h"

#if defined(OS_WIN)
#include <windows.h>
#include <shellapi.h>
#include <shlobj.h>
#include <comutil.h>
#endif

#include <stdlib.h>
#include <string.h>
#include <memory.h>

#include <string>

#include "base/file_path.h"
#include "base/file_util.h"
#include "base/message_loop.h"
#include "base/path_service.h"
#include "base/test/test_timeouts.h"
#include "chrome/browser/plugin_download_helper.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/test/automation/automation_proxy.h"
#include "chrome/test/automation/tab_proxy.h"
#include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/ui/ui_test.h"
#include "content/browser/net/url_request_mock_http_job.h"
#include "net/base/net_util.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_test_util.h"
#include "third_party/npapi/bindings/npapi.h"
#include "webkit/plugins/npapi/plugin_constants_win.h"
#include "webkit/plugins/npapi/plugin_list.h"
#include "webkit/plugins/plugin_switches.h"

#if defined(OS_WIN)
#include "base/win/registry.h"
#endif

class PluginTest : public UITest {
 public:
  // Generate the URL for testing a particular test.
  // HTML for the tests is all located in test_directory\plugin\<testcase>
  // Set |mock_http| to true to use mock HTTP server.
  static GURL GetTestUrl(const std::string& test_case,
                         const std::string& query,
                         bool mock_http) {
    static const FilePath::CharType kPluginPath[] = FILE_PATH_LITERAL("plugin");
    if (mock_http) {
      FilePath plugin_path = FilePath(kPluginPath).AppendASCII(test_case);
      GURL url = URLRequestMockHTTPJob::GetMockUrl(plugin_path);
      if (!query.empty()) {
        GURL::Replacements replacements;
        replacements.SetQueryStr(query);
        return url.ReplaceComponents(replacements);
      }
      return url;
    }

    FilePath path;
    PathService::Get(chrome::DIR_TEST_DATA, &path);
    path = path.Append(kPluginPath).AppendASCII(test_case);
    return ui_test_utils::GetFileUrlWithQuery(path, query);
  }

 protected:
  virtual void SetUp() {
#if defined(OS_WIN)
    const testing::TestInfo* const test_info =
        testing::UnitTest::GetInstance()->current_test_info();
    if (strcmp(test_info->name(), "MediaPlayerNew") == 0) {
      // The installer adds our process names to the registry key below.  Since
      // the installer might not have run on this machine, add it manually.
      base::win::RegKey regkey;
      if (regkey.Open(HKEY_LOCAL_MACHINE,
                      L"Software\\Microsoft\\MediaPlayer\\ShimInclusionList",
                      KEY_WRITE) == ERROR_SUCCESS) {
        regkey.CreateKey(L"CHROME.EXE", KEY_READ);
      }
    } else if (strcmp(test_info->name(), "MediaPlayerOld") == 0) {
      // When testing the old WMP plugin, we need to force Chrome to not load
      // the new plugin.
      launch_arguments_.AppendSwitch(switches::kUseOldWMPPlugin);
    } else if (strcmp(test_info->name(), "FlashSecurity") == 0) {
      launch_arguments_.AppendSwitchASCII(switches::kTestSandbox,
                                          "security_tests.dll");
    }
#endif  // defined(OS_WIN)

    launch_arguments_.AppendSwitch(switches::kAllowOutdatedPlugins);
    launch_arguments_.AppendSwitch(switches::kAlwaysAuthorizePlugins);

    UITest::SetUp();
  }

  void TestPlugin(const std::string& test_case,
                  const std::string& query,
                  int timeout,
                  bool mock_http) {
    GURL url = GetTestUrl(test_case, query, mock_http);
    NavigateToURL(url);
    WaitForFinish(timeout, mock_http);
  }

  // Waits for the test case to finish.
  void WaitForFinish(const int wait_time, bool mock_http) {
    static const char kTestCompleteCookie[] = "status";
    static const char kTestCompleteSuccess[] = "OK";

    GURL url = GetTestUrl("done", "", mock_http);
    scoped_refptr<TabProxy> tab(GetActiveTab());

    const std::string result =
        WaitUntilCookieNonEmpty(tab, url, kTestCompleteCookie, wait_time);
    ASSERT_EQ(kTestCompleteSuccess, result);
  }
};

TEST_F(PluginTest, Flash) {
  // Note: This does not work with the npwrapper on 64-bit Linux. Install the
  // native 64-bit Flash to run the test.
  // TODO(thestig) Update this list if we decide to only test against internal
  // Flash plugin in the future?
  std::string kFlashQuery =
#if defined(OS_WIN)
      "npswf32.dll"
#elif defined(OS_MACOSX)
      "Flash Player.plugin"
#elif defined(OS_POSIX)
      "libflashplayer.so"
#endif
      ;
  TestPlugin("flash.html", kFlashQuery,
             TestTimeouts::action_max_timeout_ms(), false);
}

class ClickToPlayPluginTest : public PluginTest {
 public:
  ClickToPlayPluginTest() {
    dom_automation_enabled_ = true;
  }
};

TEST_F(ClickToPlayPluginTest, Flash) {
  scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0));
  ASSERT_TRUE(browser.get());
  ASSERT_TRUE(browser->SetDefaultContentSetting(CONTENT_SETTINGS_TYPE_PLUGINS,
                                                CONTENT_SETTING_BLOCK));

  GURL url = GetTestUrl("flash-clicktoplay.html", "", true);
  NavigateToURL(url);

  scoped_refptr<TabProxy> tab(browser->GetTab(0));
  ASSERT_TRUE(tab.get());

  ASSERT_TRUE(tab->LoadBlockedPlugins());

  WaitForFinish(TestTimeouts::action_max_timeout_ms(), true);
}

TEST_F(ClickToPlayPluginTest, FlashDocument) {
  scoped_refptr<BrowserProxy> browser(automation()->GetBrowserWindow(0));
  ASSERT_TRUE(browser.get());
  ASSERT_TRUE(browser->SetDefaultContentSetting(CONTENT_SETTINGS_TYPE_PLUGINS,
                                                CONTENT_SETTING_BLOCK));

  scoped_refptr<TabProxy> tab(browser->GetTab(0));
  ASSERT_TRUE(tab.get());
  GURL url = GetTestUrl("js-invoker.swf", "callback=done", true);
  NavigateToURL(url);

  // Inject the callback function into the HTML page generated by the browser.
  ASSERT_TRUE(tab->ExecuteJavaScript("window.done = function() {"
                                     "  window.location = \"done.html\";"
                                     "}"));

  ASSERT_TRUE(tab->LoadBlockedPlugins());

  WaitForFinish(TestTimeouts::action_max_timeout_ms(), true);
}

#if defined(OS_WIN)
// Windows only test
TEST_F(PluginTest, DISABLED_FlashSecurity) {
  TestPlugin("flash.html", "", TestTimeouts::action_max_timeout_ms(), false);
}
#endif  // defined(OS_WIN)

#if defined(OS_WIN)
// TODO(port) Port the following tests to platforms that have the required
// plugins.
// Flaky: http://crbug.com/55915
TEST_F(PluginTest, FLAKY_Quicktime) {
  TestPlugin("quicktime.html", "",
             TestTimeouts::action_max_timeout_ms(), false);
}

// Disabled - http://crbug.com/44662
TEST_F(PluginTest, DISABLED_MediaPlayerNew) {
  TestPlugin("wmp_new.html", "", TestTimeouts::action_max_timeout_ms(), false);
}

// http://crbug.com/4809
TEST_F(PluginTest, DISABLED_MediaPlayerOld) {
  TestPlugin("wmp_old.html", "", TestTimeouts::action_max_timeout_ms(), false);
}

// Disabled - http://crbug.com/44673
TEST_F(PluginTest, DISABLED_Real) {
  TestPlugin("real.html", "", TestTimeouts::action_max_timeout_ms(), false);
}

TEST_F(PluginTest, FlashOctetStream) {
  TestPlugin("flash-octet-stream.html", "",
             TestTimeouts::action_max_timeout_ms(), false);
}

#if defined(OS_WIN)
// http://crbug.com/53926
TEST_F(PluginTest, FLAKY_FlashLayoutWhilePainting) {
#else
TEST_F(PluginTest, FlashLayoutWhilePainting) {
#endif
  TestPlugin("flash-layout-while-painting.html", "",
             TestTimeouts::action_max_timeout_ms(), true);
}

// http://crbug.com/8690
TEST_F(PluginTest, DISABLED_Java) {
  TestPlugin("Java.html", "", TestTimeouts::action_max_timeout_ms(), false);
}

TEST_F(PluginTest, Silverlight) {
  TestPlugin("silverlight.html", "",
             TestTimeouts::action_max_timeout_ms(), false);
}

namespace {

class TestURLRequestContextGetter : public net::URLRequestContextGetter {
 public:
  explicit TestURLRequestContextGetter() {
    io_message_loop_proxy_ = base::MessageLoopProxy::current();
  }
  virtual net::URLRequestContext* GetURLRequestContext() {
    if (!context_)
      context_ = new TestURLRequestContext();
    return context_;
  }
  virtual scoped_refptr<base::MessageLoopProxy> GetIOMessageLoopProxy() const {
    return io_message_loop_proxy_;
  }

 protected:
  scoped_refptr<base::MessageLoopProxy> io_message_loop_proxy_;

 private:
  virtual ~TestURLRequestContextGetter() {}

  scoped_refptr<net::URLRequestContext> context_;
};

}  // namespace
// This class provides functionality to test the plugin installer download
// file functionality.
class PluginInstallerDownloadTest
    : public PluginDownloadUrlHelper::DownloadDelegate,
      public testing::Test {
 public:
  PluginInstallerDownloadTest()
      : success_(false),
        download_helper_(NULL) {}
  ~PluginInstallerDownloadTest() {}

  void Start() {
    initial_download_path_ = PluginTest::GetTestUrl("flash.html", "", false);
    download_helper_ = new PluginDownloadUrlHelper(
        initial_download_path_.spec(), NULL,
        static_cast<PluginDownloadUrlHelper::DownloadDelegate*>(this));
    TestURLRequestContextGetter* context_getter =
        new TestURLRequestContextGetter;
    download_helper_->InitiateDownload(context_getter,
                                       context_getter->GetIOMessageLoopProxy());

    MessageLoop::current()->PostDelayedTask(
        FROM_HERE, new MessageLoop::QuitTask,
        TestTimeouts::action_max_timeout_ms());
  }

  virtual void OnDownloadCompleted(const FilePath& download_path,
                                   bool success) {
    success_ = success;
    final_download_path_ = download_path;
    MessageLoop::current()->Quit();
    download_helper_ = NULL;
  }

  FilePath final_download_path() const {
    return final_download_path_;
  }

  FilePath initial_download_path() const {
    return final_download_path_;
  }

  bool success() const {
    return success_;
  }

 private:
  FilePath final_download_path_;
  PluginDownloadUrlHelper* download_helper_;
  bool success_;
  GURL initial_download_path_;
};

// This test validates that the plugin downloader downloads the specified file
// to a temporary path with the same file name.
TEST_F(PluginInstallerDownloadTest, PluginInstallerDownloadPathTest) {
  MessageLoop loop(MessageLoop::TYPE_IO);
  Start();
  loop.Run();

  EXPECT_TRUE(success());
  EXPECT_TRUE(initial_download_path().BaseName().value() ==
              final_download_path().BaseName().value());
}
#endif  // defined(OS_WIN)