blob: 0d57751086aa95bbfcac409f4ca7e66dcf6bd1bc [file] [log] [blame]
// Copyright 2014 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/renderer_context_menu/render_view_context_menu.h"
#include "base/memory/ptr_util.h"
#include "base/run_loop.h"
#include "base/strings/utf_string_conversions.h"
#include "base/threading/thread_task_runner_handle.h"
#include "chrome/app/chrome_command_ids.h"
#include "chrome/browser/custom_handlers/protocol_handler_registry.h"
#include "chrome/browser/extensions/menu_manager.h"
#include "chrome/browser/extensions/menu_manager_factory.h"
#include "chrome/browser/extensions/test_extension_environment.h"
#include "chrome/browser/extensions/test_extension_prefs.h"
#include "chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings.h"
#include "chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings_factory.h"
#include "chrome/browser/prefs/incognito_mode_prefs.h"
#include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/chrome_render_view_host_test_harness.h"
#include "chrome/test/base/testing_profile.h"
#include "components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.h"
#include "components/data_reduction_proxy/core/browser/data_store.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
#include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
#include "components/prefs/pref_registry_simple.h"
#include "components/prefs/pref_service.h"
#include "components/proxy_config/proxy_config_pref_names.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/test_renderer_host.h"
#include "content/public/test/web_contents_tester.h"
#include "extensions/browser/extension_prefs.h"
#include "extensions/common/url_pattern.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/WebKit/public/web/WebContextMenuData.h"
#include "url/gurl.h"
using extensions::Extension;
using extensions::MenuItem;
using extensions::MenuManager;
using extensions::MenuManagerFactory;
using extensions::URLPatternSet;
namespace {
// Generates a ContextMenuParams that matches the specified contexts.
static content::ContextMenuParams CreateParams(int contexts) {
content::ContextMenuParams rv;
rv.is_editable = false;
rv.media_type = blink::WebContextMenuData::MediaTypeNone;
rv.page_url = GURL("http://test.page/");
static const base::char16 selected_text[] = { 's', 'e', 'l', 0 };
if (contexts & MenuItem::SELECTION)
rv.selection_text = selected_text;
if (contexts & MenuItem::LINK)
rv.link_url = GURL("http://test.link/");
if (contexts & MenuItem::EDITABLE)
rv.is_editable = true;
if (contexts & MenuItem::IMAGE) {
rv.src_url = GURL("http://test.image/");
rv.media_type = blink::WebContextMenuData::MediaTypeImage;
}
if (contexts & MenuItem::VIDEO) {
rv.src_url = GURL("http://test.video/");
rv.media_type = blink::WebContextMenuData::MediaTypeVideo;
}
if (contexts & MenuItem::AUDIO) {
rv.src_url = GURL("http://test.audio/");
rv.media_type = blink::WebContextMenuData::MediaTypeAudio;
}
if (contexts & MenuItem::FRAME)
rv.frame_url = GURL("http://test.frame/");
return rv;
}
// Returns a test context menu.
std::unique_ptr<TestRenderViewContextMenu> CreateContextMenu(
content::WebContents* web_contents,
ProtocolHandlerRegistry* registry) {
content::ContextMenuParams params = CreateParams(MenuItem::LINK);
params.unfiltered_link_url = params.link_url;
std::unique_ptr<TestRenderViewContextMenu> menu(
new TestRenderViewContextMenu(web_contents->GetMainFrame(), params));
menu->set_protocol_handler_registry(registry);
menu->Init();
return menu;
}
} // namespace
class RenderViewContextMenuTest : public testing::Test {
protected:
RenderViewContextMenuTest() = default;
// Proxy defined here to minimize friend classes in RenderViewContextMenu
static bool ExtensionContextAndPatternMatch(
const content::ContextMenuParams& params,
MenuItem::ContextList contexts,
const URLPatternSet& patterns) {
return RenderViewContextMenu::ExtensionContextAndPatternMatch(
params, contexts, patterns);
}
// Returns a test item.
std::unique_ptr<MenuItem> CreateTestItem(const Extension* extension,
int uid) {
MenuItem::Type type = MenuItem::NORMAL;
MenuItem::ContextList contexts(MenuItem::ALL);
const MenuItem::ExtensionKey key(extension->id());
bool incognito = false;
MenuItem::Id id(incognito, key);
id.uid = uid;
return base::MakeUnique<MenuItem>(id, "Added by an extension", false, true,
type, contexts);
}
private:
content::RenderViewHostTestEnabler rvh_test_enabler_;
DISALLOW_COPY_AND_ASSIGN(RenderViewContextMenuTest);
};
// Generates a URLPatternSet with a single pattern
static URLPatternSet CreatePatternSet(const std::string& pattern) {
URLPattern target(URLPattern::SCHEME_HTTP);
target.Parse(pattern);
URLPatternSet rv;
rv.AddPattern(target);
return rv;
}
TEST_F(RenderViewContextMenuTest, TargetIgnoredForPage) {
content::ContextMenuParams params = CreateParams(0);
MenuItem::ContextList contexts;
contexts.Add(MenuItem::PAGE);
URLPatternSet patterns = CreatePatternSet("*://test.none/*");
EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns));
}
TEST_F(RenderViewContextMenuTest, TargetCheckedForLink) {
content::ContextMenuParams params = CreateParams(MenuItem::LINK);
MenuItem::ContextList contexts;
contexts.Add(MenuItem::PAGE);
contexts.Add(MenuItem::LINK);
URLPatternSet patterns = CreatePatternSet("*://test.none/*");
EXPECT_FALSE(ExtensionContextAndPatternMatch(params, contexts, patterns));
}
TEST_F(RenderViewContextMenuTest, TargetCheckedForImage) {
content::ContextMenuParams params = CreateParams(MenuItem::IMAGE);
MenuItem::ContextList contexts;
contexts.Add(MenuItem::PAGE);
contexts.Add(MenuItem::IMAGE);
URLPatternSet patterns = CreatePatternSet("*://test.none/*");
EXPECT_FALSE(ExtensionContextAndPatternMatch(params, contexts, patterns));
}
TEST_F(RenderViewContextMenuTest, TargetCheckedForVideo) {
content::ContextMenuParams params = CreateParams(MenuItem::VIDEO);
MenuItem::ContextList contexts;
contexts.Add(MenuItem::PAGE);
contexts.Add(MenuItem::VIDEO);
URLPatternSet patterns = CreatePatternSet("*://test.none/*");
EXPECT_FALSE(ExtensionContextAndPatternMatch(params, contexts, patterns));
}
TEST_F(RenderViewContextMenuTest, TargetCheckedForAudio) {
content::ContextMenuParams params = CreateParams(MenuItem::AUDIO);
MenuItem::ContextList contexts;
contexts.Add(MenuItem::PAGE);
contexts.Add(MenuItem::AUDIO);
URLPatternSet patterns = CreatePatternSet("*://test.none/*");
EXPECT_FALSE(ExtensionContextAndPatternMatch(params, contexts, patterns));
}
TEST_F(RenderViewContextMenuTest, MatchWhenLinkedImageMatchesTarget) {
content::ContextMenuParams params = CreateParams(MenuItem::IMAGE |
MenuItem::LINK);
MenuItem::ContextList contexts;
contexts.Add(MenuItem::LINK);
contexts.Add(MenuItem::IMAGE);
URLPatternSet patterns = CreatePatternSet("*://test.link/*");
EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns));
}
TEST_F(RenderViewContextMenuTest, MatchWhenLinkedImageMatchesSource) {
content::ContextMenuParams params = CreateParams(MenuItem::IMAGE |
MenuItem::LINK);
MenuItem::ContextList contexts;
contexts.Add(MenuItem::LINK);
contexts.Add(MenuItem::IMAGE);
URLPatternSet patterns = CreatePatternSet("*://test.image/*");
EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns));
}
TEST_F(RenderViewContextMenuTest, NoMatchWhenLinkedImageMatchesNeither) {
content::ContextMenuParams params = CreateParams(MenuItem::IMAGE |
MenuItem::LINK);
MenuItem::ContextList contexts;
contexts.Add(MenuItem::LINK);
contexts.Add(MenuItem::IMAGE);
URLPatternSet patterns = CreatePatternSet("*://test.none/*");
EXPECT_FALSE(ExtensionContextAndPatternMatch(params, contexts, patterns));
}
TEST_F(RenderViewContextMenuTest, TargetIgnoredForFrame) {
content::ContextMenuParams params = CreateParams(MenuItem::FRAME);
MenuItem::ContextList contexts;
contexts.Add(MenuItem::FRAME);
URLPatternSet patterns = CreatePatternSet("*://test.none/*");
EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns));
}
TEST_F(RenderViewContextMenuTest, TargetIgnoredForEditable) {
content::ContextMenuParams params = CreateParams(MenuItem::EDITABLE);
MenuItem::ContextList contexts;
contexts.Add(MenuItem::EDITABLE);
URLPatternSet patterns = CreatePatternSet("*://test.none/*");
EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns));
}
TEST_F(RenderViewContextMenuTest, TargetIgnoredForSelection) {
content::ContextMenuParams params =
CreateParams(MenuItem::SELECTION);
MenuItem::ContextList contexts;
contexts.Add(MenuItem::SELECTION);
URLPatternSet patterns = CreatePatternSet("*://test.none/*");
EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns));
}
TEST_F(RenderViewContextMenuTest, TargetIgnoredForSelectionOnLink) {
content::ContextMenuParams params = CreateParams(
MenuItem::SELECTION | MenuItem::LINK);
MenuItem::ContextList contexts;
contexts.Add(MenuItem::SELECTION);
contexts.Add(MenuItem::LINK);
URLPatternSet patterns = CreatePatternSet("*://test.none/*");
EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns));
}
TEST_F(RenderViewContextMenuTest, TargetIgnoredForSelectionOnImage) {
content::ContextMenuParams params = CreateParams(
MenuItem::SELECTION | MenuItem::IMAGE);
MenuItem::ContextList contexts;
contexts.Add(MenuItem::SELECTION);
contexts.Add(MenuItem::IMAGE);
URLPatternSet patterns = CreatePatternSet("*://test.none/*");
EXPECT_TRUE(ExtensionContextAndPatternMatch(params, contexts, patterns));
}
class RenderViewContextMenuExtensionsTest : public RenderViewContextMenuTest {
protected:
RenderViewContextMenuExtensionsTest() = default;
void SetUp() override {
RenderViewContextMenuTest::SetUp();
// TestingProfile does not provide a protocol registry.
registry_.reset(new ProtocolHandlerRegistry(profile(), nullptr));
}
void TearDown() override {
registry_.reset();
RenderViewContextMenuTest::TearDown();
}
TestingProfile* profile() const { return environment_.profile(); }
extensions::TestExtensionEnvironment& environment() {
return environment_;
}
protected:
extensions::TestExtensionEnvironment environment_;
std::unique_ptr<ProtocolHandlerRegistry> registry_;
DISALLOW_COPY_AND_ASSIGN(RenderViewContextMenuExtensionsTest);
};
TEST_F(RenderViewContextMenuExtensionsTest,
ItemWithSameTitleFromTwoExtensions) {
MenuManager* menu_manager = // Owned by profile().
static_cast<MenuManager*>(
(MenuManagerFactory::GetInstance()->SetTestingFactoryAndUse(
profile(),
&MenuManagerFactory::BuildServiceInstanceForTesting)));
const Extension* extension1 = environment().MakeExtension(
base::DictionaryValue(), "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
const Extension* extension2 = environment().MakeExtension(
base::DictionaryValue(), "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
// Create two items in two extensions with same title.
ASSERT_TRUE(
menu_manager->AddContextItem(extension1, CreateTestItem(extension1, 1)));
ASSERT_TRUE(
menu_manager->AddContextItem(extension2, CreateTestItem(extension2, 2)));
std::unique_ptr<content::WebContents> web_contents = environment().MakeTab();
std::unique_ptr<TestRenderViewContextMenu> menu(
CreateContextMenu(web_contents.get(), registry_.get()));
const ui::MenuModel& model = menu->menu_model();
base::string16 expected_title = base::ASCIIToUTF16("Added by an extension");
int num_items_found = 0;
for (int i = 0; i < model.GetItemCount(); ++i) {
if (expected_title == model.GetLabelAt(i))
++num_items_found;
}
// Expect both items to be found.
ASSERT_EQ(2, num_items_found);
}
class RenderViewContextMenuPrefsTest : public ChromeRenderViewHostTestHarness {
public:
RenderViewContextMenuPrefsTest() = default;
void SetUp() override {
ChromeRenderViewHostTestHarness::SetUp();
registry_.reset(new ProtocolHandlerRegistry(profile(), nullptr));
}
void TearDown() override {
registry_.reset();
ChromeRenderViewHostTestHarness::TearDown();
}
std::unique_ptr<TestRenderViewContextMenu> CreateContextMenu() {
return ::CreateContextMenu(web_contents(), registry_.get());
}
void AppendImageItems(TestRenderViewContextMenu* menu) {
menu->AppendImageItems();
}
void SetupDataReductionProxy(bool enable_data_reduction_proxy) {
drp_test_context_ =
data_reduction_proxy::DataReductionProxyTestContext::Builder()
.WithParamsFlags(
data_reduction_proxy::DataReductionProxyParams::kAllowed |
data_reduction_proxy::DataReductionProxyParams::
kFallbackAllowed |
data_reduction_proxy::DataReductionProxyParams::kPromoAllowed)
.WithMockConfig()
.SkipSettingsInitialization()
.Build();
DataReductionProxyChromeSettings* settings =
DataReductionProxyChromeSettingsFactory::GetForBrowserContext(
profile());
// TODO(bengr): Remove proxy_config::prefs::kProxy registration after M46.
// See http://crbug.com/445599.
PrefRegistrySimple* registry =
drp_test_context_->pref_service()->registry();
registry->RegisterDictionaryPref(proxy_config::prefs::kProxy);
drp_test_context_->SetDataReductionProxyEnabled(
enable_data_reduction_proxy);
settings->set_data_reduction_proxy_enabled_pref_name_for_test(
drp_test_context_->GetDataReductionProxyEnabledPrefName());
settings->InitDataReductionProxySettings(
drp_test_context_->io_data(), drp_test_context_->pref_service(),
drp_test_context_->request_context_getter(),
base::MakeUnique<data_reduction_proxy::DataStore>(),
base::ThreadTaskRunnerHandle::Get(),
base::ThreadTaskRunnerHandle::Get());
}
// Force destruction of |DataReductionProxySettings| so that objects on DB
// task runner can be destroyed before test threads are destroyed. This method
// must be called by tests that call |SetupDataReductionProxy|. We cannot
// destroy |drp_test_context_| until browser context keyed services are
// destroyed since |DataReductionProxyChromeSettings| holds a pointer to the
// |PrefService|, which is owned by |drp_test_context_|.
void DestroyDataReductionProxySettings() {
drp_test_context_->DestroySettings();
}
protected:
std::unique_ptr<data_reduction_proxy::DataReductionProxyTestContext>
drp_test_context_;
private:
std::unique_ptr<ProtocolHandlerRegistry> registry_;
DISALLOW_COPY_AND_ASSIGN(RenderViewContextMenuPrefsTest);
};
// Verifies when Incognito Mode is not available (disabled by policy),
// Open Link in Incognito Window link in the context menu is disabled.
TEST_F(RenderViewContextMenuPrefsTest,
DisableOpenInIncognitoWindowWhenIncognitoIsDisabled) {
std::unique_ptr<TestRenderViewContextMenu> menu(CreateContextMenu());
// Initially the Incognito mode is be enabled. So is the Open Link in
// Incognito Window link.
ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD));
EXPECT_TRUE(
menu->IsCommandIdEnabled(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD));
// Disable Incognito mode.
IncognitoModePrefs::SetAvailability(profile()->GetPrefs(),
IncognitoModePrefs::DISABLED);
menu = CreateContextMenu();
ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD));
EXPECT_FALSE(
menu->IsCommandIdEnabled(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD));
}
// Make sure the checking custom command id that is not enabled will not
// cause DCHECK failure.
TEST_F(RenderViewContextMenuPrefsTest,
IsCustomCommandIdEnabled) {
std::unique_ptr<TestRenderViewContextMenu> menu(CreateContextMenu());
EXPECT_FALSE(menu->IsCommandIdEnabled(IDC_CONTENT_CONTEXT_CUSTOM_FIRST));
}
// Verify that request headers specify that data reduction proxy should return
// the original non compressed resource when "Save Image As..." is used with
// Data Saver enabled.
TEST_F(RenderViewContextMenuPrefsTest, DataSaverEnabledSaveImageAs) {
SetupDataReductionProxy(true);
content::ContextMenuParams params = CreateParams(MenuItem::IMAGE);
params.unfiltered_link_url = params.link_url;
content::WebContents* wc = web_contents();
std::unique_ptr<TestRenderViewContextMenu> menu(
new TestRenderViewContextMenu(wc->GetMainFrame(), params));
menu->ExecuteCommand(IDC_CONTENT_CONTEXT_SAVEIMAGEAS, 0);
const std::string& headers =
content::WebContentsTester::For(web_contents())->GetSaveFrameHeaders();
EXPECT_TRUE(headers.find(
"Chrome-Proxy-Accept-Transform: identity") != std::string::npos);
EXPECT_TRUE(headers.find("Cache-Control: no-cache") != std::string::npos);
DestroyDataReductionProxySettings();
}
// Verify that request headers do not specify pass through when "Save Image
// As..." is used with Data Saver disabled.
TEST_F(RenderViewContextMenuPrefsTest, DataSaverDisabledSaveImageAs) {
SetupDataReductionProxy(false);
content::ContextMenuParams params = CreateParams(MenuItem::IMAGE);
params.unfiltered_link_url = params.link_url;
content::WebContents* wc = web_contents();
std::unique_ptr<TestRenderViewContextMenu> menu(
new TestRenderViewContextMenu(wc->GetMainFrame(), params));
menu->ExecuteCommand(IDC_CONTENT_CONTEXT_SAVEIMAGEAS, 0);
const std::string& headers =
content::WebContentsTester::For(web_contents())->GetSaveFrameHeaders();
EXPECT_TRUE(headers.find(
"Chrome-Proxy-Accept-Transform: identity") == std::string::npos);
EXPECT_TRUE(headers.find("Cache-Control: no-cache") == std::string::npos);
DestroyDataReductionProxySettings();
}
// Verify that the Chrome-Proxy Lo-Fi directive causes the context menu to
// display the "Load Image" menu item.
TEST_F(RenderViewContextMenuPrefsTest, DataSaverLoadImage) {
SetupDataReductionProxy(true);
content::ContextMenuParams params = CreateParams(MenuItem::IMAGE);
params.properties[
data_reduction_proxy::chrome_proxy_content_transform_header()] =
data_reduction_proxy::empty_image_directive();
params.unfiltered_link_url = params.link_url;
content::WebContents* wc = web_contents();
std::unique_ptr<TestRenderViewContextMenu> menu(
new TestRenderViewContextMenu(wc->GetMainFrame(), params));
AppendImageItems(menu.get());
ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_LOAD_ORIGINAL_IMAGE));
DestroyDataReductionProxySettings();
}