// 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/run_loop.h" #include "base/strings/utf_string_conversions.h" #include "base/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. scoped_ptr CreateContextMenu( content::WebContents* web_contents, ProtocolHandlerRegistry* registry) { content::ContextMenuParams params = CreateParams(MenuItem::LINK); params.unfiltered_link_url = params.link_url; scoped_ptr 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. 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 new 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_; scoped_ptr registry_; DISALLOW_COPY_AND_ASSIGN(RenderViewContextMenuExtensionsTest); }; TEST_F(RenderViewContextMenuExtensionsTest, ItemWithSameTitleFromTwoExtensions) { MenuManager* menu_manager = // Owned by profile(). static_cast( (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. MenuItem* item1 = CreateTestItem(extension1, 1); ASSERT_TRUE(menu_manager->AddContextItem(extension1, item1)); MenuItem* item2 = CreateTestItem(extension2, 2); ASSERT_TRUE(menu_manager->AddContextItem(extension2, item2)); scoped_ptr web_contents = environment().MakeTab(); scoped_ptr 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(); } scoped_ptr 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(), make_scoped_ptr(new 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: scoped_ptr drp_test_context_; private: scoped_ptr 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) { scoped_ptr 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) { scoped_ptr 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(); scoped_ptr 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: pass-through") != 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(); scoped_ptr 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: pass-through") == 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_header()] = data_reduction_proxy::chrome_proxy_lo_fi_directive(); params.unfiltered_link_url = params.link_url; content::WebContents* wc = web_contents(); scoped_ptr menu( new TestRenderViewContextMenu(wc->GetMainFrame(), params)); AppendImageItems(menu.get()); ASSERT_TRUE(menu->IsItemPresent(IDC_CONTENT_CONTEXT_LOAD_ORIGINAL_IMAGE)); DestroyDataReductionProxySettings(); }