Home | History | Annotate | Download | only in renderer_context_menu
      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.
      4 
      5 #include "chrome/browser/renderer_context_menu/spelling_menu_observer.h"
      6 
      7 #include <vector>
      8 
      9 #include "base/command_line.h"
     10 #include "base/prefs/pref_service.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "chrome/app/chrome_command_ids.h"
     13 #include "chrome/browser/renderer_context_menu/render_view_context_menu.h"
     14 #include "chrome/browser/renderer_context_menu/render_view_context_menu_observer.h"
     15 #include "chrome/browser/spellchecker/spelling_service_client.h"
     16 #include "chrome/common/chrome_switches.h"
     17 #include "chrome/common/pref_names.h"
     18 #include "chrome/test/base/in_process_browser_test.h"
     19 #include "chrome/test/base/testing_profile.h"
     20 
     21 using content::RenderViewHost;
     22 using content::WebContents;
     23 
     24 namespace {
     25 
     26 // A mock context menu used in this test. This class overrides virtual methods
     27 // derived from the RenderViewContextMenuProxy class to monitor calls from the
     28 // SpellingMenuObserver class.
     29 class MockRenderViewContextMenu : public RenderViewContextMenuProxy {
     30  public:
     31   // A menu item used in this test. This test uses a vector of this struct to
     32   // hold menu items added by this test.
     33   struct MockMenuItem {
     34     MockMenuItem()
     35         : command_id(0),
     36           enabled(false),
     37           checked(false),
     38           hidden(true) {
     39     }
     40     int command_id;
     41     bool enabled;
     42     bool checked;
     43     bool hidden;
     44     base::string16 title;
     45   };
     46 
     47   explicit MockRenderViewContextMenu(bool incognito);
     48   virtual ~MockRenderViewContextMenu();
     49 
     50   // RenderViewContextMenuProxy implementation.
     51   virtual void AddMenuItem(int command_id,
     52                            const base::string16& title) OVERRIDE;
     53   virtual void AddCheckItem(int command_id,
     54                             const base::string16& title) OVERRIDE;
     55   virtual void AddSeparator() OVERRIDE;
     56   virtual void AddSubMenu(int command_id,
     57                           const base::string16& label,
     58                           ui::MenuModel* model) OVERRIDE;
     59   virtual void UpdateMenuItem(int command_id,
     60                               bool enabled,
     61                               bool hidden,
     62                               const base::string16& title) OVERRIDE;
     63   virtual RenderViewHost* GetRenderViewHost() const OVERRIDE;
     64   virtual WebContents* GetWebContents() const OVERRIDE;
     65   virtual Profile* GetProfile() const OVERRIDE;
     66 
     67   // Attaches a RenderViewContextMenuObserver to be tested.
     68   void SetObserver(RenderViewContextMenuObserver* observer);
     69 
     70   // Returns the number of items added by the test.
     71   size_t GetMenuSize() const;
     72 
     73   // Returns the i-th item.
     74   bool GetMenuItem(size_t i, MockMenuItem* item) const;
     75 
     76   // Returns the writable profile used in this test.
     77   PrefService* GetPrefs();
     78 
     79  private:
     80   // An observer used for initializing the status of menu items added in this
     81   // test. A test should delete this RenderViewContextMenuObserver object.
     82   RenderViewContextMenuObserver* observer_;
     83 
     84   // A dummy profile used in this test. Call GetPrefs() when a test needs to
     85   // change this profile and use PrefService methods.
     86   scoped_ptr<TestingProfile> profile_;
     87 
     88   // A list of menu items added by the SpellingMenuObserver class.
     89   std::vector<MockMenuItem> items_;
     90 
     91   DISALLOW_COPY_AND_ASSIGN(MockRenderViewContextMenu);
     92 };
     93 
     94 MockRenderViewContextMenu::MockRenderViewContextMenu(bool incognito)
     95     : observer_(NULL) {
     96   TestingProfile::Builder builder;
     97   if (incognito)
     98     builder.SetIncognito();
     99   profile_ = builder.Build();
    100 }
    101 
    102 MockRenderViewContextMenu::~MockRenderViewContextMenu() {
    103 }
    104 
    105 void MockRenderViewContextMenu::AddMenuItem(int command_id,
    106                                             const base::string16& title) {
    107   MockMenuItem item;
    108   item.command_id = command_id;
    109   item.enabled = observer_->IsCommandIdEnabled(command_id);
    110   item.checked = false;
    111   item.hidden = false;
    112   item.title = title;
    113   items_.push_back(item);
    114 }
    115 
    116 void MockRenderViewContextMenu::AddCheckItem(int command_id,
    117                                              const base::string16& title) {
    118   MockMenuItem item;
    119   item.command_id = command_id;
    120   item.enabled = observer_->IsCommandIdEnabled(command_id);
    121   item.checked = observer_->IsCommandIdChecked(command_id);
    122   item.hidden = false;
    123   item.title = title;
    124   items_.push_back(item);
    125 }
    126 
    127 void MockRenderViewContextMenu::AddSeparator() {
    128   MockMenuItem item;
    129   item.command_id = -1;
    130   item.enabled = false;
    131   item.checked = false;
    132   item.hidden = false;
    133   items_.push_back(item);
    134 }
    135 
    136 void MockRenderViewContextMenu::AddSubMenu(int command_id,
    137                                            const base::string16& label,
    138                                            ui::MenuModel* model) {
    139   MockMenuItem item;
    140   item.command_id = -1;
    141   item.enabled = false;
    142   item.checked = false;
    143   item.hidden = false;
    144   items_.push_back(item);
    145 }
    146 
    147 void MockRenderViewContextMenu::UpdateMenuItem(int command_id,
    148                                                bool enabled,
    149                                                bool hidden,
    150                                                const base::string16& title) {
    151   for (std::vector<MockMenuItem>::iterator it = items_.begin();
    152        it != items_.end(); ++it) {
    153     if (it->command_id == command_id) {
    154       it->enabled = enabled;
    155       it->hidden = hidden;
    156       it->title = title;
    157       return;
    158     }
    159   }
    160 
    161   // The SpellingMenuObserver class tries to change a menu item not added by the
    162   // class. This is an unexpected behavior and we should stop now.
    163   FAIL();
    164 }
    165 
    166 RenderViewHost* MockRenderViewContextMenu::GetRenderViewHost() const {
    167   return NULL;
    168 }
    169 
    170 WebContents* MockRenderViewContextMenu::GetWebContents() const {
    171   return NULL;
    172 }
    173 
    174 Profile* MockRenderViewContextMenu::GetProfile() const {
    175   return profile_.get();
    176 }
    177 
    178 size_t MockRenderViewContextMenu::GetMenuSize() const {
    179   return items_.size();
    180 }
    181 
    182 bool MockRenderViewContextMenu::GetMenuItem(size_t i,
    183                                             MockMenuItem* item) const {
    184   if (i >= items_.size())
    185     return false;
    186   item->command_id = items_[i].command_id;
    187   item->enabled = items_[i].enabled;
    188   item->checked = items_[i].checked;
    189   item->hidden = items_[i].hidden;
    190   item->title = items_[i].title;
    191   return true;
    192 }
    193 
    194 void MockRenderViewContextMenu::SetObserver(
    195     RenderViewContextMenuObserver* observer) {
    196   observer_ = observer;
    197 }
    198 
    199 PrefService* MockRenderViewContextMenu::GetPrefs() {
    200   return profile_->GetPrefs();
    201 }
    202 
    203 // A test class used in this file. This test should be a browser test because it
    204 // accesses resources.
    205 class SpellingMenuObserverTest : public InProcessBrowserTest {
    206  public:
    207   SpellingMenuObserverTest();
    208 
    209   virtual void SetUpOnMainThread() OVERRIDE {
    210     Reset(false);
    211   }
    212 
    213   virtual void CleanUpOnMainThread() OVERRIDE {
    214     observer_.reset();
    215     menu_.reset();
    216   }
    217 
    218   void Reset(bool incognito) {
    219     observer_.reset();
    220     menu_.reset(new MockRenderViewContextMenu(incognito));
    221     observer_.reset(new SpellingMenuObserver(menu_.get()));
    222     menu_->SetObserver(observer_.get());
    223   }
    224 
    225   void InitMenu(const char* word, const char* suggestion) {
    226     content::ContextMenuParams params;
    227     params.is_editable = true;
    228     params.misspelled_word = base::ASCIIToUTF16(word);
    229     params.dictionary_suggestions.clear();
    230     if (suggestion)
    231       params.dictionary_suggestions.push_back(base::ASCIIToUTF16(suggestion));
    232     observer_->InitMenu(params);
    233   }
    234 
    235   void ForceSuggestMode() {
    236     menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService, true);
    237     // Force a non-empty and non-"en" locale so SUGGEST is available.
    238     menu()->GetPrefs()->SetString(prefs::kSpellCheckDictionary, "fr");
    239     ASSERT_TRUE(SpellingServiceClient::IsAvailable(
    240         menu()->GetProfile(), SpellingServiceClient::SUGGEST));
    241     ASSERT_FALSE(SpellingServiceClient::IsAvailable(
    242         menu()->GetProfile(), SpellingServiceClient::SPELLCHECK));
    243   }
    244 
    245   virtual ~SpellingMenuObserverTest();
    246   MockRenderViewContextMenu* menu() { return menu_.get(); }
    247   SpellingMenuObserver* observer() { return observer_.get(); }
    248  private:
    249   scoped_ptr<SpellingMenuObserver> observer_;
    250   scoped_ptr<MockRenderViewContextMenu> menu_;
    251   DISALLOW_COPY_AND_ASSIGN(SpellingMenuObserverTest);
    252 };
    253 
    254 SpellingMenuObserverTest::SpellingMenuObserverTest() {
    255 }
    256 
    257 SpellingMenuObserverTest::~SpellingMenuObserverTest() {
    258 }
    259 
    260 }  // namespace
    261 
    262 // Tests that right-clicking a correct word does not add any items.
    263 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest, InitMenuWithCorrectWord) {
    264   InitMenu("", NULL);
    265   EXPECT_EQ(static_cast<size_t>(0), menu()->GetMenuSize());
    266 }
    267 
    268 // Tests that right-clicking a misspelled word adds four items:
    269 // "No spelling suggestions", "Add to dictionary", "Ask Google for suggestions",
    270 // and a separator.
    271 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest, InitMenuWithMisspelledWord) {
    272   InitMenu("wiimode", NULL);
    273   EXPECT_EQ(static_cast<size_t>(4), menu()->GetMenuSize());
    274 
    275   // Read all the context-menu items added by this test and verify they are
    276   // expected ones. We do not check the item titles to prevent resource changes
    277   // from breaking this test. (I think it is not expected by those who change
    278   // resources.)
    279   MockRenderViewContextMenu::MockMenuItem item;
    280   menu()->GetMenuItem(0, &item);
    281   EXPECT_EQ(IDC_CONTENT_CONTEXT_NO_SPELLING_SUGGESTIONS, item.command_id);
    282   EXPECT_FALSE(item.enabled);
    283   EXPECT_FALSE(item.hidden);
    284   menu()->GetMenuItem(1, &item);
    285   EXPECT_EQ(IDC_SPELLCHECK_ADD_TO_DICTIONARY, item.command_id);
    286   EXPECT_TRUE(item.enabled);
    287   EXPECT_FALSE(item.hidden);
    288   menu()->GetMenuItem(2, &item);
    289   EXPECT_EQ(IDC_CONTENT_CONTEXT_SPELLING_TOGGLE, item.command_id);
    290   EXPECT_TRUE(item.enabled);
    291   EXPECT_FALSE(item.checked);
    292   EXPECT_FALSE(item.hidden);
    293   menu()->GetMenuItem(3, &item);
    294   EXPECT_EQ(-1, item.command_id);
    295   EXPECT_FALSE(item.enabled);
    296   EXPECT_FALSE(item.hidden);
    297 }
    298 
    299 // Tests that right-clicking a correct word when we enable spelling-service
    300 // integration to verify an item "Ask Google for suggestions" is checked. Even
    301 // though this meanu itself does not add this item, its sub-menu adds the item
    302 // and calls SpellingMenuObserver::IsChecked() to check it.
    303 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest,
    304                        EnableSpellingServiceWithCorrectWord) {
    305   menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService, true);
    306   InitMenu("", NULL);
    307 
    308   EXPECT_TRUE(
    309       observer()->IsCommandIdChecked(IDC_CONTENT_CONTEXT_SPELLING_TOGGLE));
    310 }
    311 
    312 // Tests that right-clicking a misspelled word when we enable spelling-service
    313 // integration to verify an item "Ask Google for suggestions" is checked. (This
    314 // test does not actually send JSON-RPC requests to the service because it makes
    315 // this test flaky.)
    316 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest, EnableSpellingService) {
    317   menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService, true);
    318   menu()->GetPrefs()->SetString(prefs::kSpellCheckDictionary, std::string());
    319 
    320   InitMenu("wiimode", NULL);
    321   EXPECT_EQ(static_cast<size_t>(4), menu()->GetMenuSize());
    322 
    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);
    331 }
    332 
    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) {
    336   ForceSuggestMode();
    337   InitMenu("jhhj", NULL);
    338 
    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());
    343 
    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);
    349 
    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);
    354 
    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);
    359 
    360   menu()->GetMenuItem(3, &item);
    361   EXPECT_EQ(-1, item.command_id);
    362   EXPECT_FALSE(item.enabled);
    363   EXPECT_FALSE(item.hidden);
    364 }
    365 
    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);
    371 
    372   // Force a non-empty locale so SPELLCHECK is available.
    373   menu()->GetPrefs()->SetString(prefs::kSpellCheckDictionary, "en");
    374   EXPECT_TRUE(SpellingServiceClient::IsAvailable(menu()->GetProfile(),
    375     SpellingServiceClient::SPELLCHECK));
    376   InitMenu("asdfkj", "asdf");
    377 
    378   // The test should see a separator, a suggestion and another separator
    379   // as the first two items, then possibly more (not relevant here).
    380   EXPECT_LT(3U, menu()->GetMenuSize());
    381 
    382   MockRenderViewContextMenu::MockMenuItem item;
    383   menu()->GetMenuItem(0, &item);
    384   EXPECT_EQ(-1, item.command_id);
    385   EXPECT_FALSE(item.enabled);
    386   EXPECT_FALSE(item.hidden);
    387 
    388   menu()->GetMenuItem(1, &item);
    389   EXPECT_EQ(IDC_SPELLCHECK_SUGGESTION_0, item.command_id);
    390   EXPECT_TRUE(item.enabled);
    391   EXPECT_FALSE(item.hidden);
    392 
    393   menu()->GetMenuItem(2, &item);
    394   EXPECT_EQ(-1, item.command_id);
    395   EXPECT_FALSE(item.enabled);
    396   EXPECT_FALSE(item.hidden);
    397 }
    398 
    399 // Test that "Ask Google For Suggestions" is grayed out when using an
    400 // off the record profile.
    401 // TODO(rlp): Include graying out of autocorrect in this test when autocorrect
    402 // is functional.
    403 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest,
    404                        NoSpellingServiceWhenOffTheRecord) {
    405   // Create a menu in an incognito profile.
    406   Reset(true);
    407 
    408   // This means spellchecking is allowed. Default is that the service is
    409   // contacted but this test makes sure that if profile is incognito, that
    410   // is not an option.
    411   menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService, true);
    412 
    413   // Force a non-empty locale so SUGGEST normally would be available.
    414   menu()->GetPrefs()->SetString(prefs::kSpellCheckDictionary, "en");
    415   EXPECT_FALSE(SpellingServiceClient::IsAvailable(menu()->GetProfile(),
    416     SpellingServiceClient::SUGGEST));
    417   EXPECT_FALSE(SpellingServiceClient::IsAvailable(menu()->GetProfile(),
    418     SpellingServiceClient::SPELLCHECK));
    419 
    420   InitMenu("sjxdjiiiiii", NULL);
    421 
    422   // The test should see "No spelling suggestions" (from system checker).
    423   // They should not see "No more Google suggestions" (from SpellingService) or
    424   // a separator. The next 2 items should be "Add to Dictionary" followed
    425   // by "Ask Google for suggestions" which should be disabled.
    426   // TODO(rlp): add autocorrect here when it is functional.
    427   EXPECT_LT(3U, menu()->GetMenuSize());
    428 
    429   MockRenderViewContextMenu::MockMenuItem item;
    430   menu()->GetMenuItem(0, &item);
    431   EXPECT_EQ(IDC_CONTENT_CONTEXT_NO_SPELLING_SUGGESTIONS, item.command_id);
    432   EXPECT_FALSE(item.enabled);
    433   EXPECT_FALSE(item.hidden);
    434 
    435   menu()->GetMenuItem(1, &item);
    436   EXPECT_EQ(IDC_SPELLCHECK_ADD_TO_DICTIONARY, item.command_id);
    437   EXPECT_TRUE(item.enabled);
    438   EXPECT_FALSE(item.hidden);
    439 
    440   menu()->GetMenuItem(2, &item);
    441   EXPECT_EQ(IDC_CONTENT_CONTEXT_SPELLING_TOGGLE, item.command_id);
    442   EXPECT_FALSE(item.enabled);
    443   EXPECT_FALSE(item.hidden);
    444 }
    445 
    446 // Test that the menu is preceeded by a separator if there are any suggestions,
    447 // or if the SpellingServiceClient is available
    448 IN_PROC_BROWSER_TEST_F(SpellingMenuObserverTest, SuggestionsForceTopSeparator) {
    449   menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService, false);
    450 
    451   // First case: Misspelled word, no suggestions, no spellcheck service.
    452   InitMenu("asdfkj", NULL);
    453   // See SpellingMenuObserverTest.InitMenuWithMisspelledWord on why 4 items.
    454   EXPECT_EQ(static_cast<size_t>(4), menu()->GetMenuSize());
    455   MockRenderViewContextMenu::MockMenuItem item;
    456   menu()->GetMenuItem(0, &item);
    457   EXPECT_NE(-1, item.command_id);
    458 
    459   // Case #2. Misspelled word, suggestions, no spellcheck service.
    460   Reset(false);
    461   menu()->GetPrefs()->SetBoolean(prefs::kSpellCheckUseSpellingService, false);
    462   InitMenu("asdfkj", "asdf");
    463 
    464   // Expect at least separator and 4 default entries.
    465   EXPECT_LT(static_cast<size_t>(5), menu()->GetMenuSize());
    466   // This test only cares that the first one is a separator.
    467   menu()->GetMenuItem(0, &item);
    468   EXPECT_EQ(-1, item.command_id);
    469 
    470   // Case #3. Misspelled word, suggestion service is on.
    471   Reset(false);
    472   ForceSuggestMode();
    473   InitMenu("asdfkj", NULL);
    474 
    475   // Should have at least 2 entries. Separator, suggestion.
    476   EXPECT_LT(2U, menu()->GetMenuSize());
    477   menu()->GetMenuItem(0, &item);
    478   EXPECT_EQ(-1, item.command_id);
    479   menu()->GetMenuItem(1, &item);
    480   EXPECT_EQ(IDC_CONTENT_CONTEXT_SPELLING_SUGGESTION, item.command_id);
    481 }
    482