1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/renderer_context_menu/render_view_context_menu.h"
7 #include "base/prefs/pref_registry_simple.h"
8 #include "base/prefs/pref_service.h"
9 #include "base/run_loop.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "base/thread_task_runner_handle.h"
12 #include "chrome/app/chrome_command_ids.h"
13 #include "chrome/browser/custom_handlers/protocol_handler_registry.h"
14 #include "chrome/browser/extensions/menu_manager.h"
15 #include "chrome/browser/extensions/menu_manager_factory.h"
16 #include "chrome/browser/extensions/test_extension_environment.h"
17 #include "chrome/browser/extensions/test_extension_prefs.h"
18 #include "chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings.h"
19 #include "chrome/browser/net/spdyproxy/data_reduction_proxy_chrome_settings_factory.h"
20 #include "chrome/browser/prefs/incognito_mode_prefs.h"
21 #include "chrome/browser/renderer_context_menu/render_view_context_menu_test_util.h"
22 #include "chrome/test/base/chrome_render_view_host_test_harness.h"
23 #include "chrome/test/base/testing_profile.h"
24 #include "components/data_reduction_proxy/core/browser/data_reduction_proxy_test_utils.h"
25 #include "components/data_reduction_proxy/core/browser/data_store.h"
26 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_headers.h"
27 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_params.h"
28 #include "components/data_reduction_proxy/core/common/data_reduction_proxy_pref_names.h"
29 #include "components/proxy_config/proxy_config_pref_names.h"
30 #include "content/public/browser/web_contents.h"
31 #include "content/public/test/test_renderer_host.h"
32 #include "content/public/test/web_contents_tester.h"
34 #include "extensions/browser/extension_prefs.h"
35 #include "extensions/common/url_pattern.h"
36 #include "testing/gtest/include/gtest/gtest.h"
37 #include "third_party/WebKit/public/web/WebContextMenuData.h"
40 using extensions::Extension
;
41 using extensions::MenuItem
;
42 using extensions::MenuManager
;
43 using extensions::MenuManagerFactory
;
44 using extensions::URLPatternSet
;
48 // Generates a ContextMenuParams that matches the specified contexts.
49 static content::ContextMenuParams
CreateParams(int contexts
) {
50 content::ContextMenuParams rv
;
51 rv
.is_editable
= false;
52 rv
.media_type
= blink::WebContextMenuData::MediaTypeNone
;
53 rv
.page_url
= GURL("http://test.page/");
55 static const base::char16 selected_text
[] = { 's', 'e', 'l', 0 };
56 if (contexts
& MenuItem::SELECTION
)
57 rv
.selection_text
= selected_text
;
59 if (contexts
& MenuItem::LINK
)
60 rv
.link_url
= GURL("http://test.link/");
62 if (contexts
& MenuItem::EDITABLE
)
63 rv
.is_editable
= true;
65 if (contexts
& MenuItem::IMAGE
) {
66 rv
.src_url
= GURL("http://test.image/");
67 rv
.media_type
= blink::WebContextMenuData::MediaTypeImage
;
70 if (contexts
& MenuItem::VIDEO
) {
71 rv
.src_url
= GURL("http://test.video/");
72 rv
.media_type
= blink::WebContextMenuData::MediaTypeVideo
;
75 if (contexts
& MenuItem::AUDIO
) {
76 rv
.src_url
= GURL("http://test.audio/");
77 rv
.media_type
= blink::WebContextMenuData::MediaTypeAudio
;
80 if (contexts
& MenuItem::FRAME
)
81 rv
.frame_url
= GURL("http://test.frame/");
88 class RenderViewContextMenuTest
: public testing::Test
{
90 // Proxy defined here to minimize friend classes in RenderViewContextMenu
91 static bool ExtensionContextAndPatternMatch(
92 const content::ContextMenuParams
& params
,
93 MenuItem::ContextList contexts
,
94 const URLPatternSet
& patterns
) {
95 return RenderViewContextMenu::ExtensionContextAndPatternMatch(
96 params
, contexts
, patterns
);
99 // Returns a test item.
100 MenuItem
* CreateTestItem(const Extension
* extension
, int uid
) {
101 MenuItem::Type type
= MenuItem::NORMAL
;
102 MenuItem::ContextList
contexts(MenuItem::ALL
);
103 const MenuItem::ExtensionKey
key(extension
->id());
104 bool incognito
= false;
105 MenuItem::Id
id(incognito
, key
);
107 return new MenuItem(id
, "Added by an extension", false, true, type
,
111 // Returns a test context menu.
112 TestRenderViewContextMenu
* CreateContextMenu(
113 TestingProfile
* profile
,
114 content::WebContents
* web_contents
) {
115 content::ContextMenuParams params
= CreateParams(MenuItem::LINK
);
116 params
.unfiltered_link_url
= params
.link_url
;
117 TestRenderViewContextMenu
* menu
=
118 new TestRenderViewContextMenu(web_contents
->GetMainFrame(), params
);
119 // TestingProfile (returned by profile()) does not provide a protocol
121 scoped_ptr
<ProtocolHandlerRegistry
> registry_(
122 new ProtocolHandlerRegistry(profile
, NULL
));
123 menu
->protocol_handler_registry_
= registry_
.get();
128 content::RenderViewHostTestEnabler rvh_test_enabler_
;
131 // Generates a URLPatternSet with a single pattern
132 static URLPatternSet
CreatePatternSet(const std::string
& pattern
) {
133 URLPattern
target(URLPattern::SCHEME_HTTP
);
134 target
.Parse(pattern
);
137 rv
.AddPattern(target
);
142 TEST_F(RenderViewContextMenuTest
, TargetIgnoredForPage
) {
143 content::ContextMenuParams params
= CreateParams(0);
145 MenuItem::ContextList contexts
;
146 contexts
.Add(MenuItem::PAGE
);
148 URLPatternSet patterns
= CreatePatternSet("*://test.none/*");
150 EXPECT_TRUE(ExtensionContextAndPatternMatch(params
, contexts
, patterns
));
153 TEST_F(RenderViewContextMenuTest
, TargetCheckedForLink
) {
154 content::ContextMenuParams params
= CreateParams(MenuItem::LINK
);
156 MenuItem::ContextList contexts
;
157 contexts
.Add(MenuItem::PAGE
);
158 contexts
.Add(MenuItem::LINK
);
160 URLPatternSet patterns
= CreatePatternSet("*://test.none/*");
162 EXPECT_FALSE(ExtensionContextAndPatternMatch(params
, contexts
, patterns
));
165 TEST_F(RenderViewContextMenuTest
, TargetCheckedForImage
) {
166 content::ContextMenuParams params
= CreateParams(MenuItem::IMAGE
);
168 MenuItem::ContextList contexts
;
169 contexts
.Add(MenuItem::PAGE
);
170 contexts
.Add(MenuItem::IMAGE
);
172 URLPatternSet patterns
= CreatePatternSet("*://test.none/*");
174 EXPECT_FALSE(ExtensionContextAndPatternMatch(params
, contexts
, patterns
));
177 TEST_F(RenderViewContextMenuTest
, TargetCheckedForVideo
) {
178 content::ContextMenuParams params
= CreateParams(MenuItem::VIDEO
);
180 MenuItem::ContextList contexts
;
181 contexts
.Add(MenuItem::PAGE
);
182 contexts
.Add(MenuItem::VIDEO
);
184 URLPatternSet patterns
= CreatePatternSet("*://test.none/*");
186 EXPECT_FALSE(ExtensionContextAndPatternMatch(params
, contexts
, patterns
));
189 TEST_F(RenderViewContextMenuTest
, TargetCheckedForAudio
) {
190 content::ContextMenuParams params
= CreateParams(MenuItem::AUDIO
);
192 MenuItem::ContextList contexts
;
193 contexts
.Add(MenuItem::PAGE
);
194 contexts
.Add(MenuItem::AUDIO
);
196 URLPatternSet patterns
= CreatePatternSet("*://test.none/*");
198 EXPECT_FALSE(ExtensionContextAndPatternMatch(params
, contexts
, patterns
));
201 TEST_F(RenderViewContextMenuTest
, MatchWhenLinkedImageMatchesTarget
) {
202 content::ContextMenuParams params
= CreateParams(MenuItem::IMAGE
|
205 MenuItem::ContextList contexts
;
206 contexts
.Add(MenuItem::LINK
);
207 contexts
.Add(MenuItem::IMAGE
);
209 URLPatternSet patterns
= CreatePatternSet("*://test.link/*");
211 EXPECT_TRUE(ExtensionContextAndPatternMatch(params
, contexts
, patterns
));
214 TEST_F(RenderViewContextMenuTest
, MatchWhenLinkedImageMatchesSource
) {
215 content::ContextMenuParams params
= CreateParams(MenuItem::IMAGE
|
218 MenuItem::ContextList contexts
;
219 contexts
.Add(MenuItem::LINK
);
220 contexts
.Add(MenuItem::IMAGE
);
222 URLPatternSet patterns
= CreatePatternSet("*://test.image/*");
224 EXPECT_TRUE(ExtensionContextAndPatternMatch(params
, contexts
, patterns
));
227 TEST_F(RenderViewContextMenuTest
, NoMatchWhenLinkedImageMatchesNeither
) {
228 content::ContextMenuParams params
= CreateParams(MenuItem::IMAGE
|
231 MenuItem::ContextList contexts
;
232 contexts
.Add(MenuItem::LINK
);
233 contexts
.Add(MenuItem::IMAGE
);
235 URLPatternSet patterns
= CreatePatternSet("*://test.none/*");
237 EXPECT_FALSE(ExtensionContextAndPatternMatch(params
, contexts
, patterns
));
240 TEST_F(RenderViewContextMenuTest
, TargetIgnoredForFrame
) {
241 content::ContextMenuParams params
= CreateParams(MenuItem::FRAME
);
243 MenuItem::ContextList contexts
;
244 contexts
.Add(MenuItem::FRAME
);
246 URLPatternSet patterns
= CreatePatternSet("*://test.none/*");
248 EXPECT_TRUE(ExtensionContextAndPatternMatch(params
, contexts
, patterns
));
251 TEST_F(RenderViewContextMenuTest
, TargetIgnoredForEditable
) {
252 content::ContextMenuParams params
= CreateParams(MenuItem::EDITABLE
);
254 MenuItem::ContextList contexts
;
255 contexts
.Add(MenuItem::EDITABLE
);
257 URLPatternSet patterns
= CreatePatternSet("*://test.none/*");
259 EXPECT_TRUE(ExtensionContextAndPatternMatch(params
, contexts
, patterns
));
262 TEST_F(RenderViewContextMenuTest
, TargetIgnoredForSelection
) {
263 content::ContextMenuParams params
=
264 CreateParams(MenuItem::SELECTION
);
266 MenuItem::ContextList contexts
;
267 contexts
.Add(MenuItem::SELECTION
);
269 URLPatternSet patterns
= CreatePatternSet("*://test.none/*");
271 EXPECT_TRUE(ExtensionContextAndPatternMatch(params
, contexts
, patterns
));
274 TEST_F(RenderViewContextMenuTest
, TargetIgnoredForSelectionOnLink
) {
275 content::ContextMenuParams params
= CreateParams(
276 MenuItem::SELECTION
| MenuItem::LINK
);
278 MenuItem::ContextList contexts
;
279 contexts
.Add(MenuItem::SELECTION
);
280 contexts
.Add(MenuItem::LINK
);
282 URLPatternSet patterns
= CreatePatternSet("*://test.none/*");
284 EXPECT_TRUE(ExtensionContextAndPatternMatch(params
, contexts
, patterns
));
287 TEST_F(RenderViewContextMenuTest
, TargetIgnoredForSelectionOnImage
) {
288 content::ContextMenuParams params
= CreateParams(
289 MenuItem::SELECTION
| MenuItem::IMAGE
);
291 MenuItem::ContextList contexts
;
292 contexts
.Add(MenuItem::SELECTION
);
293 contexts
.Add(MenuItem::IMAGE
);
295 URLPatternSet patterns
= CreatePatternSet("*://test.none/*");
297 EXPECT_TRUE(ExtensionContextAndPatternMatch(params
, contexts
, patterns
));
300 TEST_F(RenderViewContextMenuTest
, ItemWithSameTitleFromTwoExtensions
) {
301 extensions::TestExtensionEnvironment env
;
303 MenuManager
* menu_manager
= // Owned by env.profile().
304 static_cast<MenuManager
*>(
305 (MenuManagerFactory::GetInstance()->SetTestingFactoryAndUse(
307 &MenuManagerFactory::BuildServiceInstanceForTesting
)));
309 const Extension
* extension1
=
310 env
.MakeExtension(base::DictionaryValue(),
311 "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
312 const Extension
* extension2
=
313 env
.MakeExtension(base::DictionaryValue(),
314 "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
316 // Create two items in two extensions with same title.
317 MenuItem
* item1
= CreateTestItem(extension1
, 1);
318 ASSERT_TRUE(menu_manager
->AddContextItem(extension1
, item1
));
319 MenuItem
* item2
= CreateTestItem(extension2
, 2);
320 ASSERT_TRUE(menu_manager
->AddContextItem(extension2
, item2
));
322 scoped_ptr
<content::WebContents
> web_contents
= env
.MakeTab();
323 scoped_ptr
<TestRenderViewContextMenu
> menu(
324 CreateContextMenu(env
.profile(), web_contents
.get()));
326 const ui::MenuModel
& model
= menu
->menu_model();
327 base::string16 expected_title
= base::ASCIIToUTF16("Added by an extension");
328 int num_items_found
= 0;
329 for (int i
= 0; i
< model
.GetItemCount(); ++i
) {
330 if (expected_title
== model
.GetLabelAt(i
))
334 // Expect both items to be found.
335 ASSERT_EQ(2, num_items_found
);
338 class RenderViewContextMenuPrefsTest
: public ChromeRenderViewHostTestHarness
{
340 void SetUp() override
{
341 ChromeRenderViewHostTestHarness::SetUp();
342 registry_
.reset(new ProtocolHandlerRegistry(profile(), NULL
));
345 void TearDown() override
{
347 ChromeRenderViewHostTestHarness::TearDown();
350 TestRenderViewContextMenu
* CreateContextMenu() {
351 content::ContextMenuParams params
= CreateParams(MenuItem::LINK
);
352 params
.unfiltered_link_url
= params
.link_url
;
353 content::WebContents
* wc
= web_contents();
354 TestRenderViewContextMenu
* menu
= new TestRenderViewContextMenu(
355 wc
->GetMainFrame(), params
);
356 // TestingProfile (returned by profile()) does not provide a protocol
358 menu
->protocol_handler_registry_
= registry_
.get();
363 void AppendImageItems(TestRenderViewContextMenu
* menu
) {
364 menu
->AppendImageItems();
367 void SetupDataReductionProxy(bool enable_data_reduction_proxy
) {
369 data_reduction_proxy::DataReductionProxyTestContext::Builder()
371 data_reduction_proxy::DataReductionProxyParams::kAllowed
|
372 data_reduction_proxy::DataReductionProxyParams::
374 data_reduction_proxy::DataReductionProxyParams::kPromoAllowed
)
376 .SkipSettingsInitialization()
379 DataReductionProxyChromeSettings
* settings
=
380 DataReductionProxyChromeSettingsFactory::GetForBrowserContext(
383 // TODO(bengr): Remove proxy_config::prefs::kProxy registration after M46.
384 // See http://crbug.com/445599.
385 PrefRegistrySimple
* registry
=
386 drp_test_context_
->pref_service()->registry();
387 registry
->RegisterDictionaryPref(proxy_config::prefs::kProxy
);
389 drp_test_context_
->pref_service()->SetBoolean(
390 data_reduction_proxy::prefs::kDataReductionProxyEnabled
,
391 enable_data_reduction_proxy
);
392 drp_test_context_
->InitSettings();
394 settings
->InitDataReductionProxySettings(
395 drp_test_context_
->io_data(), drp_test_context_
->pref_service(),
396 drp_test_context_
->request_context_getter(),
397 make_scoped_ptr(new data_reduction_proxy::DataStore()),
398 base::ThreadTaskRunnerHandle::Get(),
399 base::ThreadTaskRunnerHandle::Get());
402 // Force destruction of |DataReductionProxySettings| so that objects on DB
403 // task runner can be destroyed before test threads are destroyed. This method
404 // must be called by tests that call |SetupDataReductionProxy|. We cannot
405 // destroy |drp_test_context_| until browser context keyed services are
406 // destroyed since |DataReductionProxyChromeSettings| holds a pointer to the
407 // |PrefService|, which is owned by |drp_test_context_|.
408 void DestroyDataReductionProxySettings() {
409 drp_test_context_
->DestroySettings();
413 scoped_ptr
<data_reduction_proxy::DataReductionProxyTestContext
>
417 scoped_ptr
<ProtocolHandlerRegistry
> registry_
;
420 // Verifies when Incognito Mode is not available (disabled by policy),
421 // Open Link in Incognito Window link in the context menu is disabled.
422 TEST_F(RenderViewContextMenuPrefsTest
,
423 DisableOpenInIncognitoWindowWhenIncognitoIsDisabled
) {
424 scoped_ptr
<TestRenderViewContextMenu
> menu(CreateContextMenu());
426 // Initially the Incognito mode is be enabled. So is the Open Link in
427 // Incognito Window link.
428 ASSERT_TRUE(menu
->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD
));
430 menu
->IsCommandIdEnabled(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD
));
432 // Disable Incognito mode.
433 IncognitoModePrefs::SetAvailability(profile()->GetPrefs(),
434 IncognitoModePrefs::DISABLED
);
435 menu
.reset(CreateContextMenu());
436 ASSERT_TRUE(menu
->IsItemPresent(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD
));
438 menu
->IsCommandIdEnabled(IDC_CONTENT_CONTEXT_OPENLINKOFFTHERECORD
));
441 // Make sure the checking custom command id that is not enabled will not
442 // cause DCHECK failure.
443 TEST_F(RenderViewContextMenuPrefsTest
,
444 IsCustomCommandIdEnabled
) {
445 scoped_ptr
<TestRenderViewContextMenu
> menu(CreateContextMenu());
447 EXPECT_FALSE(menu
->IsCommandIdEnabled(IDC_CONTENT_CONTEXT_CUSTOM_FIRST
));
450 // Verify that request headers specify that data reduction proxy should return
451 // the original non compressed resource when "Save Image As..." is used with
452 // Data Saver enabled.
453 TEST_F(RenderViewContextMenuPrefsTest
, DataSaverEnabledSaveImageAs
) {
454 SetupDataReductionProxy(true);
456 content::ContextMenuParams params
= CreateParams(MenuItem::IMAGE
);
457 params
.unfiltered_link_url
= params
.link_url
;
458 content::WebContents
* wc
= web_contents();
459 scoped_ptr
<TestRenderViewContextMenu
> menu(
460 new TestRenderViewContextMenu(wc
->GetMainFrame(), params
));
462 menu
->ExecuteCommand(IDC_CONTENT_CONTEXT_SAVEIMAGEAS
, 0);
464 const std::string
& headers
=
465 content::WebContentsTester::For(web_contents())->GetSaveFrameHeaders();
466 EXPECT_TRUE(headers
.find("Chrome-Proxy: pass-through") != std::string::npos
);
467 EXPECT_TRUE(headers
.find("Cache-Control: no-cache") != std::string::npos
);
469 DestroyDataReductionProxySettings();
472 // Verify that request headers do not specify pass through when "Save Image
473 // As..." is used with Data Saver disabled.
474 TEST_F(RenderViewContextMenuPrefsTest
, DataSaverDisabledSaveImageAs
) {
475 SetupDataReductionProxy(false);
477 content::ContextMenuParams params
= CreateParams(MenuItem::IMAGE
);
478 params
.unfiltered_link_url
= params
.link_url
;
479 content::WebContents
* wc
= web_contents();
480 scoped_ptr
<TestRenderViewContextMenu
> menu(
481 new TestRenderViewContextMenu(wc
->GetMainFrame(), params
));
483 menu
->ExecuteCommand(IDC_CONTENT_CONTEXT_SAVEIMAGEAS
, 0);
485 const std::string
& headers
=
486 content::WebContentsTester::For(web_contents())->GetSaveFrameHeaders();
487 EXPECT_TRUE(headers
.find("Chrome-Proxy: pass-through") == std::string::npos
);
488 EXPECT_TRUE(headers
.find("Cache-Control: no-cache") == std::string::npos
);
490 DestroyDataReductionProxySettings();
493 // Verify that the Chrome-Proxy Lo-Fi directive causes the context menu to
494 // display the "Load Image" menu item.
495 TEST_F(RenderViewContextMenuPrefsTest
, DataSaverLoadImage
) {
496 SetupDataReductionProxy(true);
497 content::ContextMenuParams params
= CreateParams(MenuItem::IMAGE
);
498 params
.properties
[data_reduction_proxy::chrome_proxy_header()] =
499 data_reduction_proxy::chrome_proxy_lo_fi_directive();
500 params
.unfiltered_link_url
= params
.link_url
;
501 content::WebContents
* wc
= web_contents();
502 scoped_ptr
<TestRenderViewContextMenu
> menu(
503 new TestRenderViewContextMenu(wc
->GetMainFrame(), params
));
504 AppendImageItems(menu
.get());
506 ASSERT_TRUE(menu
->IsItemPresent(IDC_CONTENT_CONTEXT_LOAD_ORIGINAL_IMAGE
));
508 DestroyDataReductionProxySettings();