// Copyright (c) 2012 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 <vector>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/memory/ref_counted.h"
#include "base/path_service.h"
#include "base/prefs/pref_service.h"
#include "base/process/kill.h"
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/plugins/plugin_prefs.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "content/public/browser/browser_child_process_host_iterator.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/child_process_data.h"
#include "content/public/browser/plugin_service.h"
#include "content/public/browser/web_contents.h"
#include "content/public/common/content_constants.h"
#include "content/public/common/content_paths.h"
#include "content/public/common/process_type.h"
#include "content/public/common/webplugininfo.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "net/base/filename_util.h"
#if defined(OS_WIN)
#include "ui/aura/window.h"
#include "ui/aura/window_tree_host.h"
#endif
using content::BrowserThread;
namespace {
class CallbackBarrier : public base::RefCountedThreadSafe<CallbackBarrier> {
public:
explicit CallbackBarrier(const base::Closure& target_callback)
: target_callback_(target_callback),
outstanding_callbacks_(0),
did_enable_(true) {
}
base::Callback<void(bool)> CreateCallback() {
outstanding_callbacks_++;
return base::Bind(&CallbackBarrier::MayRunTargetCallback, this);
}
private:
friend class base::RefCountedThreadSafe<CallbackBarrier>;
~CallbackBarrier() {
EXPECT_TRUE(target_callback_.is_null());
}
void MayRunTargetCallback(bool did_enable) {
EXPECT_GT(outstanding_callbacks_, 0);
did_enable_ = did_enable_ && did_enable;
if (--outstanding_callbacks_ == 0) {
EXPECT_TRUE(did_enable_);
target_callback_.Run();
target_callback_.Reset();
}
}
base::Closure target_callback_;
int outstanding_callbacks_;
bool did_enable_;
};
} // namespace
class ChromePluginTest : public InProcessBrowserTest {
protected:
ChromePluginTest() {}
static GURL GetURL(const char* filename) {
base::FilePath path;
PathService::Get(content::DIR_TEST_DATA, &path);
path = path.AppendASCII("plugin").AppendASCII(filename);
CHECK(base::PathExists(path));
return net::FilePathToFileURL(path);
}
static void LoadAndWait(Browser* window, const GURL& url, bool pass) {
content::WebContents* web_contents =
window->tab_strip_model()->GetActiveWebContents();
base::string16 expected_title(
base::ASCIIToUTF16(pass ? "OK" : "plugin_not_found"));
content::TitleWatcher title_watcher(web_contents, expected_title);
title_watcher.AlsoWaitForTitle(base::ASCIIToUTF16("FAIL"));
title_watcher.AlsoWaitForTitle(base::ASCIIToUTF16(
pass ? "plugin_not_found" : "OK"));
ui_test_utils::NavigateToURL(window, url);
ASSERT_EQ(expected_title, title_watcher.WaitAndGetTitle());
}
static void CrashFlash() {
scoped_refptr<content::MessageLoopRunner> runner =
new content::MessageLoopRunner;
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&CrashFlashInternal, runner->QuitClosure()));
runner->Run();
}
static void GetFlashPath(std::vector<base::FilePath>* paths) {
paths->clear();
std::vector<content::WebPluginInfo> plugins = GetPlugins();
for (std::vector<content::WebPluginInfo>::const_iterator it =
plugins.begin(); it != plugins.end(); ++it) {
if (it->name == base::ASCIIToUTF16(content::kFlashPluginName))
paths->push_back(it->path);
}
}
static std::vector<content::WebPluginInfo> GetPlugins() {
std::vector<content::WebPluginInfo> plugins;
scoped_refptr<content::MessageLoopRunner> runner =
new content::MessageLoopRunner;
content::PluginService::GetInstance()->GetPlugins(
base::Bind(&GetPluginsInfoCallback, &plugins, runner->QuitClosure()));
runner->Run();
return plugins;
}
static void EnableFlash(bool enable, Profile* profile) {
std::vector<base::FilePath> paths;
GetFlashPath(&paths);
ASSERT_FALSE(paths.empty());
PluginPrefs* plugin_prefs = PluginPrefs::GetForProfile(profile).get();
scoped_refptr<content::MessageLoopRunner> runner =
new content::MessageLoopRunner;
scoped_refptr<CallbackBarrier> callback_barrier(
new CallbackBarrier(runner->QuitClosure()));
for (std::vector<base::FilePath>::iterator iter = paths.begin();
iter != paths.end(); ++iter) {
plugin_prefs->EnablePlugin(enable, *iter,
callback_barrier->CreateCallback());
}
runner->Run();
}
static void EnsureFlashProcessCount(int expected) {
int actual = 0;
scoped_refptr<content::MessageLoopRunner> runner =
new content::MessageLoopRunner;
BrowserThread::PostTask(
BrowserThread::IO,
FROM_HERE,
base::Bind(&CountPluginProcesses, &actual, runner->QuitClosure()));
runner->Run();
ASSERT_EQ(expected, actual);
}
private:
static void CrashFlashInternal(const base::Closure& quit_task) {
bool found = false;
for (content::BrowserChildProcessHostIterator iter; !iter.Done(); ++iter) {
if (iter.GetData().process_type != content::PROCESS_TYPE_PLUGIN &&
iter.GetData().process_type != content::PROCESS_TYPE_PPAPI_PLUGIN) {
continue;
}
base::KillProcess(iter.GetData().handle, 0, true);
found = true;
}
ASSERT_TRUE(found) << "Didn't find Flash process!";
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, quit_task);
}
static void GetPluginsInfoCallback(
std::vector<content::WebPluginInfo>* rv,
const base::Closure& quit_task,
const std::vector<content::WebPluginInfo>& plugins) {
*rv = plugins;
quit_task.Run();
}
static void CountPluginProcesses(int* count, const base::Closure& quit_task) {
for (content::BrowserChildProcessHostIterator iter; !iter.Done(); ++iter) {
if (iter.GetData().process_type == content::PROCESS_TYPE_PLUGIN ||
iter.GetData().process_type == content::PROCESS_TYPE_PPAPI_PLUGIN) {
(*count)++;
}
}
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, quit_task);
}
};
// Tests a bunch of basic scenarios with Flash.
// This test fails under ASan on Mac, see http://crbug.com/147004.
// It fails elsewhere, too. See http://crbug.com/152071.
IN_PROC_BROWSER_TEST_F(ChromePluginTest, DISABLED_Flash) {
// Official builds always have bundled Flash.
#if !defined(OFFICIAL_BUILD)
std::vector<base::FilePath> flash_paths;
GetFlashPath(&flash_paths);
if (flash_paths.empty()) {
LOG(INFO) << "Test not running because couldn't find Flash.";
return;
}
#endif
GURL url = GetURL("flash.html");
EnsureFlashProcessCount(0);
// Try a single tab.
ASSERT_NO_FATAL_FAILURE(LoadAndWait(browser(), url, true));
EnsureFlashProcessCount(1);
Profile* profile = browser()->profile();
// Try another tab.
ASSERT_NO_FATAL_FAILURE(LoadAndWait(CreateBrowser(profile), url, true));
// Try an incognito window.
ASSERT_NO_FATAL_FAILURE(LoadAndWait(CreateIncognitoBrowser(), url, true));
EnsureFlashProcessCount(1);
// Now kill Flash process and verify it reloads.
CrashFlash();
EnsureFlashProcessCount(0);
ASSERT_NO_FATAL_FAILURE(LoadAndWait(browser(), url, true));
EnsureFlashProcessCount(1);
// Now try disabling it.
EnableFlash(false, profile);
CrashFlash();
ASSERT_NO_FATAL_FAILURE(LoadAndWait(browser(), url, false));
EnsureFlashProcessCount(0);
// Now enable it again.
EnableFlash(true, profile);
ASSERT_NO_FATAL_FAILURE(LoadAndWait(browser(), url, true));
EnsureFlashProcessCount(1);
}
#if defined(OFFICIAL_BUILD)
// Verify that the official builds have the known set of plugins.
IN_PROC_BROWSER_TEST_F(ChromePluginTest, InstalledPlugins) {
const char* expected[] = {
"Chrome PDF Viewer",
"Shockwave Flash",
"Native Client",
"Chrome Remote Desktop Viewer",
#if defined(OS_CHROMEOS)
"Google Talk Plugin",
"Google Talk Plugin Video Accelerator",
"Netflix",
#endif
};
std::vector<content::WebPluginInfo> plugins = GetPlugins();
for (size_t i = 0; i < ARRAYSIZE_UNSAFE(expected); ++i) {
size_t j = 0;
for (; j < plugins.size(); ++j) {
if (plugins[j].name == base::ASCIIToUTF16(expected[i]))
break;
}
ASSERT_TRUE(j != plugins.size()) << "Didn't find " << expected[i];
}
}
#endif
#if defined(OS_WIN)
namespace {
BOOL CALLBACK EnumerateChildren(HWND hwnd, LPARAM l_param) {
HWND* child = reinterpret_cast<HWND*>(l_param);
*child = hwnd;
// The first child window is the plugin, then its children. So stop
// enumerating after the first callback.
return FALSE;
}
} // namespace
// Test that if a background tab loads an NPAPI plugin, they are displayed after
// switching to that page. http://crbug.com/335900
IN_PROC_BROWSER_TEST_F(ChromePluginTest, WindowedNPAPIPluginHidden) {
browser()->profile()->GetPrefs()->SetBoolean(prefs::kPluginsAlwaysAuthorize,
true);
// First load the page in the background and wait for the NPAPI plugin's
// window to be created.
GURL url = ui_test_utils::GetTestUrl(
base::FilePath(),
base::FilePath().AppendASCII("windowed_npapi_plugin.html"));
ui_test_utils::NavigateToURLWithDisposition(
browser(), url, NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION);
// We create a third window just to trigger the second one to update its
// constrained window list. Normally this would be triggered by the status bar
// animation closing after the user middle clicked a link.
ui_test_utils::NavigateToURLWithDisposition(
browser(), GURL("about:blank"), NEW_BACKGROUND_TAB,
ui_test_utils::BROWSER_TEST_WAIT_FOR_TAB);
base::string16 expected_title(base::ASCIIToUTF16("created"));
content::WebContents* tab =
browser()->tab_strip_model()->GetWebContentsAt(1);
if (tab->GetTitle() != expected_title) {
content::TitleWatcher title_watcher(tab, expected_title);
EXPECT_EQ(expected_title, title_watcher.WaitAndGetTitle());
}
// Now activate the tab and verify that the plugin painted.
browser()->tab_strip_model()->ActivateTabAt(1, true);
base::string16 expected_title2(base::ASCIIToUTF16("shown"));
content::TitleWatcher title_watcher2(tab, expected_title2);
EXPECT_EQ(expected_title2, title_watcher2.WaitAndGetTitle());
HWND child = NULL;
HWND hwnd = tab->GetNativeView()->GetHost()->GetAcceleratedWidget();
EnumChildWindows(hwnd, EnumerateChildren,reinterpret_cast<LPARAM>(&child));
RECT region;
int result = GetWindowRgnBox(child, ®ion);
ASSERT_NE(result, NULLREGION);
}
#endif