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/spelling_menu_observer.h"
9 #include "base/prefs/pref_service.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/app/chrome_command_ids.h"
12 #include "chrome/browser/renderer_context_menu/render_view_context_menu.h"
13 #include "chrome/browser/spellchecker/spelling_service_client.h"
14 #include "chrome/common/pref_names.h"
15 #include "chrome/test/base/in_process_browser_test.h"
16 #include "chrome/test/base/testing_profile.h"
17 #include "components/renderer_context_menu/render_view_context_menu_observer.h"
19 using content::RenderViewHost
;
20 using content::WebContents
;
24 // A mock context menu used in this test. This class overrides virtual methods
25 // derived from the RenderViewContextMenuProxy class to monitor calls from the
26 // SpellingMenuObserver class.
27 class MockRenderViewContextMenu
: public RenderViewContextMenuProxy
{
29 // A menu item used in this test. This test uses a vector of this struct to
30 // hold menu items added by this test.
45 explicit MockRenderViewContextMenu(bool incognito
);
46 virtual ~MockRenderViewContextMenu();
48 // RenderViewContextMenuProxy implementation.
49 void AddMenuItem(int command_id
, const base::string16
& title
) override
;
50 void AddCheckItem(int command_id
, const base::string16
& title
) override
;
51 void AddSeparator() override
;
52 void AddSubMenu(int command_id
,
53 const base::string16
& label
,
54 ui::MenuModel
* model
) override
;
55 void UpdateMenuItem(int command_id
,
58 const base::string16
& title
) override
;
59 RenderViewHost
* GetRenderViewHost() const override
;
60 WebContents
* GetWebContents() const override
;
61 content::BrowserContext
* GetBrowserContext() const override
;
63 // Attaches a RenderViewContextMenuObserver to be tested.
64 void SetObserver(RenderViewContextMenuObserver
* observer
);
66 // Returns the number of items added by the test.
67 size_t GetMenuSize() const;
69 // Returns the i-th item.
70 bool GetMenuItem(size_t i
, MockMenuItem
* item
) const;
72 // Returns the writable profile used in this test.
73 PrefService
* GetPrefs();
76 // An observer used for initializing the status of menu items added in this
77 // test. A test should delete this RenderViewContextMenuObserver object.
78 RenderViewContextMenuObserver
* observer_
;
80 // A dummy profile used in this test. Call GetPrefs() when a test needs to
81 // change this profile and use PrefService methods.
82 scoped_ptr
<TestingProfile
> original_profile_
;
84 // Either |original_profile_| or its incognito profile.
87 // A list of menu items added by the SpellingMenuObserver class.
88 std::vector
<MockMenuItem
> items_
;
90 DISALLOW_COPY_AND_ASSIGN(MockRenderViewContextMenu
);
93 MockRenderViewContextMenu::MockRenderViewContextMenu(bool incognito
)
95 original_profile_
= TestingProfile::Builder().Build();
96 profile_
= incognito
? original_profile_
->GetOffTheRecordProfile()
97 : original_profile_
.get();
100 MockRenderViewContextMenu::~MockRenderViewContextMenu() {
103 void MockRenderViewContextMenu::AddMenuItem(int command_id
,
104 const base::string16
& title
) {
106 item
.command_id
= command_id
;
107 item
.enabled
= observer_
->IsCommandIdEnabled(command_id
);
108 item
.checked
= false;
111 items_
.push_back(item
);
114 void MockRenderViewContextMenu::AddCheckItem(int command_id
,
115 const base::string16
& title
) {
117 item
.command_id
= command_id
;
118 item
.enabled
= observer_
->IsCommandIdEnabled(command_id
);
119 item
.checked
= observer_
->IsCommandIdChecked(command_id
);
122 items_
.push_back(item
);
125 void MockRenderViewContextMenu::AddSeparator() {
127 item
.command_id
= -1;
128 item
.enabled
= false;
129 item
.checked
= false;
131 items_
.push_back(item
);
134 void MockRenderViewContextMenu::AddSubMenu(int command_id
,
135 const base::string16
& label
,
136 ui::MenuModel
* model
) {
138 item
.command_id
= -1;
139 item
.enabled
= false;
140 item
.checked
= false;
142 items_
.push_back(item
);
145 void MockRenderViewContextMenu::UpdateMenuItem(int command_id
,
148 const base::string16
& title
) {
149 for (std::vector
<MockMenuItem
>::iterator it
= items_
.begin();
150 it
!= items_
.end(); ++it
) {
151 if (it
->command_id
== command_id
) {
152 it
->enabled
= enabled
;
159 // The SpellingMenuObserver class tries to change a menu item not added by the
160 // class. This is an unexpected behavior and we should stop now.
164 RenderViewHost
* MockRenderViewContextMenu::GetRenderViewHost() const {
168 WebContents
* MockRenderViewContextMenu::GetWebContents() const {
172 content::BrowserContext
* MockRenderViewContextMenu::GetBrowserContext() const {
176 size_t MockRenderViewContextMenu::GetMenuSize() const {
177 return items_
.size();
180 bool MockRenderViewContextMenu::GetMenuItem(size_t i
,
181 MockMenuItem
* item
) const {
182 if (i
>= items_
.size())
184 item
->command_id
= items_
[i
].command_id
;
185 item
->enabled
= items_
[i
].enabled
;
186 item
->checked
= items_
[i
].checked
;
187 item
->hidden
= items_
[i
].hidden
;
188 item
->title
= items_
[i
].title
;
192 void MockRenderViewContextMenu::SetObserver(
193 RenderViewContextMenuObserver
* observer
) {
194 observer_
= observer
;
197 PrefService
* MockRenderViewContextMenu::GetPrefs() {
198 return profile_
->GetPrefs();
201 // A test class used in this file. This test should be a browser test because it
202 // accesses resources.
203 class SpellingMenuObserverTest
: public InProcessBrowserTest
{
205 SpellingMenuObserverTest();
207 void SetUpOnMainThread() override
{ Reset(false); }
209 void TearDownOnMainThread() override
{
214 void Reset(bool incognito
) {
216 menu_
.reset(new MockRenderViewContextMenu(incognito
));
217 observer_
.reset(new SpellingMenuObserver(menu_
.get()));
218 menu_
->SetObserver(observer_
.get());
221 void InitMenu(const char* word
, const char* suggestion
) {
222 content::ContextMenuParams params
;
223 params
.is_editable
= true;
224 params
.misspelled_word
= base::ASCIIToUTF16(word
);
225 params
.dictionary_suggestions
.clear();
227 params
.dictionary_suggestions
.push_back(base::ASCIIToUTF16(suggestion
));
228 observer_
->InitMenu(params
);
231 void ForceSuggestMode() {
232 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService
, true);
233 // Force a non-empty and non-"en" locale so SUGGEST is available.
234 base::ListValue dictionary
;
235 dictionary
.AppendString("fr");
236 menu()->GetPrefs()->Set(prefs::kSpellCheckDictionaries
, dictionary
);
238 ASSERT_TRUE(SpellingServiceClient::IsAvailable(
239 menu()->GetBrowserContext(), SpellingServiceClient::SUGGEST
));
240 ASSERT_FALSE(SpellingServiceClient::IsAvailable(
241 menu()->GetBrowserContext(), SpellingServiceClient::SPELLCHECK
));
244 ~SpellingMenuObserverTest() override
;
245 MockRenderViewContextMenu
* menu() { return menu_
.get(); }
246 SpellingMenuObserver
* observer() { return observer_
.get(); }
248 scoped_ptr
<SpellingMenuObserver
> observer_
;
249 scoped_ptr
<MockRenderViewContextMenu
> menu_
;
250 DISALLOW_COPY_AND_ASSIGN(SpellingMenuObserverTest
);
253 SpellingMenuObserverTest::SpellingMenuObserverTest() {
256 SpellingMenuObserverTest::~SpellingMenuObserverTest() {
261 // Tests that right-clicking a correct word does not add any items.
262 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest
, InitMenuWithCorrectWord
) {
264 EXPECT_EQ(static_cast<size_t>(0), menu()->GetMenuSize());
267 // Tests that right-clicking a misspelled word adds four items:
268 // "No spelling suggestions", "Add to dictionary", "Ask Google for suggestions",
270 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest
, InitMenuWithMisspelledWord
) {
271 InitMenu("wiimode", NULL
);
272 EXPECT_EQ(static_cast<size_t>(4), menu()->GetMenuSize());
274 // Read all the context-menu items added by this test and verify they are
275 // expected ones. We do not check the item titles to prevent resource changes
276 // from breaking this test. (I think it is not expected by those who change
278 MockRenderViewContextMenu::MockMenuItem item
;
279 menu()->GetMenuItem(0, &item
);
280 EXPECT_EQ(IDC_CONTENT_CONTEXT_NO_SPELLING_SUGGESTIONS
, item
.command_id
);
281 EXPECT_FALSE(item
.enabled
);
282 EXPECT_FALSE(item
.hidden
);
283 menu()->GetMenuItem(1, &item
);
284 EXPECT_EQ(IDC_SPELLCHECK_ADD_TO_DICTIONARY
, item
.command_id
);
285 EXPECT_TRUE(item
.enabled
);
286 EXPECT_FALSE(item
.hidden
);
287 menu()->GetMenuItem(2, &item
);
288 EXPECT_EQ(IDC_CONTENT_CONTEXT_SPELLING_TOGGLE
, item
.command_id
);
289 EXPECT_TRUE(item
.enabled
);
290 EXPECT_FALSE(item
.checked
);
291 EXPECT_FALSE(item
.hidden
);
292 menu()->GetMenuItem(3, &item
);
293 EXPECT_EQ(-1, item
.command_id
);
294 EXPECT_FALSE(item
.enabled
);
295 EXPECT_FALSE(item
.hidden
);
298 // Tests that right-clicking a correct word when we enable spelling-service
299 // integration to verify an item "Ask Google for suggestions" is checked. Even
300 // though this meanu itself does not add this item, its sub-menu adds the item
301 // and calls SpellingMenuObserver::IsChecked() to check it.
302 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest
,
303 EnableSpellingServiceWithCorrectWord
) {
304 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService
, true);
308 observer()->IsCommandIdChecked(IDC_CONTENT_CONTEXT_SPELLING_TOGGLE
));
311 // Tests that right-clicking a misspelled word when we enable spelling-service
312 // integration to verify an item "Ask Google for suggestions" is checked. (This
313 // test does not actually send JSON-RPC requests to the service because it makes
315 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest
, EnableSpellingService
) {
316 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService
, true);
317 base::ListValue dictionary
;
318 menu()->GetPrefs()->Set(prefs::kSpellCheckDictionaries
, dictionary
);
320 InitMenu("wiimode", NULL
);
321 EXPECT_EQ(static_cast<size_t>(4), menu()->GetMenuSize());
323 // To avoid duplicates, this test reads only the "Ask Google for suggestions"
324 // item and verifies it is enabled and checked.
325 MockRenderViewContextMenu::MockMenuItem item
;
326 menu()->GetMenuItem(2, &item
);
327 EXPECT_EQ(IDC_CONTENT_CONTEXT_SPELLING_TOGGLE
, item
.command_id
);
328 EXPECT_TRUE(item
.enabled
);
329 EXPECT_TRUE(item
.checked
);
330 EXPECT_FALSE(item
.hidden
);
333 // Test that there will be a separator after "no suggestions" if
334 // SpellingServiceClient::SUGGEST is on.
335 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest
, SeparatorAfterSuggestions
) {
337 InitMenu("jhhj", NULL
);
339 // The test should see a top separator, "No spelling suggestions",
340 // "No more Google suggestions" (from SpellingService) and a separator
341 // as the first four items, then possibly more (not relevant here).
342 EXPECT_LT(4U, menu()->GetMenuSize());
344 MockRenderViewContextMenu::MockMenuItem item
;
345 menu()->GetMenuItem(0, &item
);
346 EXPECT_EQ(-1, item
.command_id
);
347 EXPECT_FALSE(item
.enabled
);
348 EXPECT_FALSE(item
.hidden
);
350 menu()->GetMenuItem(1, &item
);
351 EXPECT_EQ(IDC_CONTENT_CONTEXT_SPELLING_SUGGESTION
, item
.command_id
);
352 EXPECT_FALSE(item
.enabled
);
353 EXPECT_FALSE(item
.hidden
);
355 menu()->GetMenuItem(2, &item
);
356 EXPECT_EQ(IDC_CONTENT_CONTEXT_NO_SPELLING_SUGGESTIONS
, item
.command_id
);
357 EXPECT_FALSE(item
.enabled
);
358 EXPECT_FALSE(item
.hidden
);
360 menu()->GetMenuItem(3, &item
);
361 EXPECT_EQ(-1, item
.command_id
);
362 EXPECT_FALSE(item
.enabled
);
363 EXPECT_FALSE(item
.hidden
);
366 // Test that we don't show "No more suggestions from Google" if the spelling
367 // service is enabled and that there is only one suggestion.
368 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest
,
369 NoMoreSuggestionsNotDisplayed
) {
370 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService
, true);
372 // Force a non-empty locale so SPELLCHECK is available.
373 base::ListValue dictionary
;
374 dictionary
.AppendString("en");
375 menu()->GetPrefs()->Set(prefs::kSpellCheckDictionaries
, dictionary
);
377 EXPECT_TRUE(SpellingServiceClient::IsAvailable(
378 menu()->GetBrowserContext(), SpellingServiceClient::SPELLCHECK
));
379 InitMenu("asdfkj", "asdf");
381 // The test should see a separator, a suggestion and another separator
382 // as the first two items, then possibly more (not relevant here).
383 EXPECT_LT(3U, menu()->GetMenuSize());
385 MockRenderViewContextMenu::MockMenuItem item
;
386 menu()->GetMenuItem(0, &item
);
387 EXPECT_EQ(-1, item
.command_id
);
388 EXPECT_FALSE(item
.enabled
);
389 EXPECT_FALSE(item
.hidden
);
391 menu()->GetMenuItem(1, &item
);
392 EXPECT_EQ(IDC_SPELLCHECK_SUGGESTION_0
, item
.command_id
);
393 EXPECT_TRUE(item
.enabled
);
394 EXPECT_FALSE(item
.hidden
);
396 menu()->GetMenuItem(2, &item
);
397 EXPECT_EQ(-1, item
.command_id
);
398 EXPECT_FALSE(item
.enabled
);
399 EXPECT_FALSE(item
.hidden
);
402 // Test that "Ask Google For Suggestions" is grayed out when using an
403 // off the record profile.
404 // TODO(rlp): Include graying out of autocorrect in this test when autocorrect
406 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest
,
407 NoSpellingServiceWhenOffTheRecord
) {
408 // Create a menu in an incognito profile.
411 // This means spellchecking is allowed. Default is that the service is
412 // contacted but this test makes sure that if profile is incognito, that
414 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService
, true);
416 // Force a non-empty locale so SUGGEST normally would be available.
417 base::ListValue dictionary
;
418 dictionary
.AppendString("en");
419 menu()->GetPrefs()->Set(prefs::kSpellCheckDictionaries
, dictionary
);
421 EXPECT_FALSE(SpellingServiceClient::IsAvailable(
422 menu()->GetBrowserContext(), SpellingServiceClient::SUGGEST
));
423 EXPECT_FALSE(SpellingServiceClient::IsAvailable(
424 menu()->GetBrowserContext(), SpellingServiceClient::SPELLCHECK
));
426 InitMenu("sjxdjiiiiii", NULL
);
428 // The test should see "No spelling suggestions" (from system checker).
429 // They should not see "No more Google suggestions" (from SpellingService) or
430 // a separator. The next 2 items should be "Add to Dictionary" followed
431 // by "Ask Google for suggestions" which should be disabled.
432 // TODO(rlp): add autocorrect here when it is functional.
433 EXPECT_LT(3U, menu()->GetMenuSize());
435 MockRenderViewContextMenu::MockMenuItem item
;
436 menu()->GetMenuItem(0, &item
);
437 EXPECT_EQ(IDC_CONTENT_CONTEXT_NO_SPELLING_SUGGESTIONS
, item
.command_id
);
438 EXPECT_FALSE(item
.enabled
);
439 EXPECT_FALSE(item
.hidden
);
441 menu()->GetMenuItem(1, &item
);
442 EXPECT_EQ(IDC_SPELLCHECK_ADD_TO_DICTIONARY
, item
.command_id
);
443 EXPECT_TRUE(item
.enabled
);
444 EXPECT_FALSE(item
.hidden
);
446 menu()->GetMenuItem(2, &item
);
447 EXPECT_EQ(IDC_CONTENT_CONTEXT_SPELLING_TOGGLE
, item
.command_id
);
448 EXPECT_FALSE(item
.enabled
);
449 EXPECT_FALSE(item
.hidden
);
452 // Test that the menu is preceeded by a separator if there are any suggestions,
453 // or if the SpellingServiceClient is available
454 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest
, SuggestionsForceTopSeparator
) {
455 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService
, false);
457 // First case: Misspelled word, no suggestions, no spellcheck service.
458 InitMenu("asdfkj", NULL
);
459 // See SpellingMenuObserverTest.InitMenuWithMisspelledWord on why 4 items.
460 EXPECT_EQ(static_cast<size_t>(4), menu()->GetMenuSize());
461 MockRenderViewContextMenu::MockMenuItem item
;
462 menu()->GetMenuItem(0, &item
);
463 EXPECT_NE(-1, item
.command_id
);
465 // Case #2. Misspelled word, suggestions, no spellcheck service.
467 menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService
, false);
468 InitMenu("asdfkj", "asdf");
470 // Expect at least separator and 4 default entries.
471 EXPECT_LT(static_cast<size_t>(5), menu()->GetMenuSize());
472 // This test only cares that the first one is a separator.
473 menu()->GetMenuItem(0, &item
);
474 EXPECT_EQ(-1, item
.command_id
);
476 // Case #3. Misspelled word, suggestion service is on.
479 InitMenu("asdfkj", NULL
);
481 // Should have at least 2 entries. Separator, suggestion.
482 EXPECT_LT(2U, menu()->GetMenuSize());
483 menu()->GetMenuItem(0, &item
);
484 EXPECT_EQ(-1, item
.command_id
);
485 menu()->GetMenuItem(1, &item
);
486 EXPECT_EQ(IDC_CONTENT_CONTEXT_SPELLING_SUGGESTION
, item
.command_id
);