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