diff options
author | asargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-02 04:08:07 +0000 |
---|---|---|
committer | asargent@chromium.org <asargent@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2010-08-02 04:08:07 +0000 |
commit | 745feedbf4bd357333c156b222728863abdbe4c2 (patch) | |
tree | 2873aca4d78a4f8629bdcbffc32f95eb7dd33bb5 /chrome/browser | |
parent | 1cc549438eaddc8c2443e5d6487928e5c5574794 (diff) | |
download | chromium_src-745feedbf4bd357333c156b222728863abdbe4c2.zip chromium_src-745feedbf4bd357333c156b222728863abdbe4c2.tar.gz chromium_src-745feedbf4bd357333c156b222728863abdbe4c2.tar.bz2 |
Set a max limit on extension items' labels in context menus.
Also do some cleanup of context menu tests, including adding a generic
mechanism to let javascript pass a test message and have C++ test code wait
for and retrieve those messages.
BUG=49744
TEST=Follow steps in bug report
Review URL: http://codereview.chromium.org/3017047
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@54514 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser')
10 files changed, 319 insertions, 55 deletions
diff --git a/chrome/browser/extensions/extension_context_menu_browsertest.cc b/chrome/browser/extensions/extension_context_menu_browsertest.cc index 705f98b..4ba39de 100644 --- a/chrome/browser/extensions/extension_context_menu_browsertest.cc +++ b/chrome/browser/extensions/extension_context_menu_browsertest.cc @@ -2,16 +2,21 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "app/menus/menu_model.h" #include "chrome/app/chrome_dll_resource.h" #include "chrome/browser/browser.h" #include "chrome/browser/extensions/extension_browsertest.h" +#include "chrome/browser/extensions/extension_test_message_listener.h" +#include "chrome/browser/extensions/extensions_service.h" #include "chrome/browser/tab_contents/render_view_context_menu.h" #include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/chrome_switches.h" #include "chrome/test/ui_test_utils.h" +#include "net/base/mock_host_resolver.h" #include "third_party/WebKit/WebKit/chromium/public/WebContextMenuData.h" #include "webkit/glue/context_menu.h" +using menus::MenuModel; using WebKit::WebContextMenuData; // This test class helps us sidestep platform-specific issues with popping up a @@ -25,96 +30,201 @@ class TestRenderViewContextMenu : public RenderViewContextMenu { virtual ~TestRenderViewContextMenu() {} - bool HasExtensionItemWithTitle(std::string title) { + bool HasExtensionItemWithLabel(const std::string& label) { + string16 label16 = UTF8ToUTF16(label); std::map<int, ExtensionMenuItem::Id>::iterator i; for (i = extension_item_map_.begin(); i != extension_item_map_.end(); ++i) { - int id = i->first; - ExtensionMenuItem* item = GetExtensionMenuItem(id); - if (item && item->title() == title) { + const ExtensionMenuItem::Id& id = i->second; + string16 tmp_label; + EXPECT_TRUE(GetItemLabel(id, &tmp_label)); + if (tmp_label == label16) return true; - } } return false; } + // Looks in the menu for an extension item with |id|, and if it is found and + // has a label, that is put in |result| and we return true. Otherwise returns + // false. + bool GetItemLabel(const ExtensionMenuItem::Id& id, string16* result) { + int command_id = 0; + if (!FindCommandId(id, &command_id)) + return false; + + MenuModel* model = NULL; + int index = -1; + if (!GetMenuModelAndItemIndex(command_id, &model, &index)) { + return false; + } + *result = model->GetLabelAt(index); + return true; + } + protected: + // These two functions implement pure virtual methods of + // RenderViewContextMenu. virtual bool GetAcceleratorForCommandId(int command_id, menus::Accelerator* accelerator) { // None of our commands have accelerators, so always return false. return false; } virtual void PlatformInit() {} + + + // Given an extension menu item id, tries to find the corresponding command id + // in the menu. + bool FindCommandId(const ExtensionMenuItem::Id& id, int* command_id) { + std::map<int, ExtensionMenuItem::Id>::const_iterator i; + for (i = extension_item_map_.begin(); i != extension_item_map_.end(); ++i) { + if (i->second == id) { + *command_id = i->first; + return true; + } + } + return false; + } + + // Searches for an menu item with |command_id|. If it's found, the return + // value is true and the model and index where it appears in that model are + // returned in |found_model| and |found_index|. Otherwise returns false. + bool GetMenuModelAndItemIndex(int command_id, + MenuModel** found_model, + int *found_index) { + std::vector<MenuModel*> models_to_search; + models_to_search.push_back(&menu_model_); + + while (!models_to_search.empty()) { + MenuModel* model = models_to_search.back(); + models_to_search.pop_back(); + for (int i = 0; i < model->GetItemCount(); i++) { + if (model->GetCommandIdAt(i) == command_id) { + *found_model = model; + *found_index = i; + return true; + } else if (model->GetTypeAt(i) == MenuModel::TYPE_SUBMENU) { + models_to_search.push_back(model->GetSubmenuModelAt(i)); + } + } + } + + return false; + } }; class ExtensionContextMenuBrowserTest : public ExtensionBrowserTest { public: // Helper to load an extension from context_menus/|subdirectory| in the // extensions test data dir. - void LoadContextMenuExtension(std::string subdirectory) { + bool LoadContextMenuExtension(std::string subdirectory) { FilePath extension_dir = test_data_dir_.AppendASCII("context_menus").AppendASCII(subdirectory); - ASSERT_TRUE(LoadExtension(extension_dir)); + return LoadExtension(extension_dir); } - // This creates a test menu using |params|, looks for an extension item with - // the given |title|, and returns true if the item was found. - bool MenuHasItemWithTitle(const ContextMenuParams& params, - std::string title) { + // This creates and returns a test menu for a page with |url|. + TestRenderViewContextMenu* CreateMenuForURL(const GURL& url) { TabContents* tab_contents = browser()->GetSelectedTabContents(); - TestRenderViewContextMenu menu(tab_contents, params); - menu.Init(); - return menu.HasExtensionItemWithTitle(title); + WebContextMenuData data; + ContextMenuParams params(data); + params.page_url = url; + TestRenderViewContextMenu* menu = + new TestRenderViewContextMenu(tab_contents, params); + menu->Init(); + return menu; } -}; -// Returns a new ContextMenuParams initialized with reasonable default values. -ContextMenuParams* CreateParams() { - WebContextMenuData data; - ContextMenuParams* params = new ContextMenuParams(data); - return params; -} + // Shortcut to return the current ExtensionMenuManager. + ExtensionMenuManager* menu_manager() { + return browser()->profile()->GetExtensionsService()->menu_manager(); + } + // This gets all the items that any extension has registered for possible + // inclusion in context menus. + ExtensionMenuItem::List GetItems() { + ExtensionMenuItem::List result; + std::set<std::string> extension_ids = menu_manager()->ExtensionIds(); + std::set<std::string>::iterator i; + for (i = extension_ids.begin(); i != extension_ids.end(); ++i) { + const ExtensionMenuItem::List* list = menu_manager()->MenuItems(*i); + result.insert(result.end(), list->begin(), list->end()); + } + return result; + } + + // This creates a test menu for a page with |url|, looks for an extension item + // with the given |label|, and returns true if the item was found. + bool MenuHasItemWithLabel(const GURL& url, const std::string& label) { + scoped_ptr<TestRenderViewContextMenu> menu(CreateMenuForURL(url)); + return menu->HasExtensionItemWithLabel(label); + } +}; + +// Tests adding a simple context menu item. IN_PROC_BROWSER_TEST_F(ExtensionContextMenuBrowserTest, Simple) { - LoadContextMenuExtension("simple"); + ExtensionTestMessageListener listener1("created item"); + ExtensionTestMessageListener listener2("onclick fired"); + ASSERT_TRUE(LoadContextMenuExtension("simple")); - // The extension's background page will create a context menu item and then - // cause a navigation on success - we wait for that here. - ASSERT_TRUE(ui_test_utils::WaitForNavigationsInCurrentTab(browser(), 1)); + // Wait for the extension to tell us it's created an item. + ASSERT_TRUE(listener1.WaitUntilSatisfied()); - // Initialize the data we need to create a context menu. - TabContents* tab_contents = browser()->GetSelectedTabContents(); - scoped_ptr<ContextMenuParams> params(CreateParams()); - params->page_url = GURL("http://www.google.com"); + GURL page_url("http://www.google.com"); // Create and build our test context menu. - TestRenderViewContextMenu menu(tab_contents, *params); - menu.Init(); + scoped_ptr<TestRenderViewContextMenu> menu(CreateMenuForURL(page_url)); // Look for the extension item in the menu, and execute it. int command_id = IDC_EXTENSIONS_CONTEXT_CUSTOM_FIRST; - ASSERT_TRUE(menu.IsCommandIdEnabled(command_id)); - menu.ExecuteCommand(command_id); + ASSERT_TRUE(menu->IsCommandIdEnabled(command_id)); + menu->ExecuteCommand(command_id); - // The onclick handler for the extension item will cause a navigation - we - // wait for that here. - ASSERT_TRUE(ui_test_utils::WaitForNavigationsInCurrentTab(browser(), 1)); + // Wait for the extension's script to tell us its onclick fired. + ASSERT_TRUE(listener2.WaitUntilSatisfied()); } +// Tests that setting "documentUrlPatterns" for an item properly restricts +// those items to matching pages. IN_PROC_BROWSER_TEST_F(ExtensionContextMenuBrowserTest, Patterns) { - // The js test code will create two items with patterns and then navigate a - // tab to tell us to proceed. - LoadContextMenuExtension("patterns"); - ASSERT_TRUE(ui_test_utils::WaitForNavigationsInCurrentTab(browser(), 1)); + ExtensionTestMessageListener listener("created items"); - scoped_ptr<ContextMenuParams> params(CreateParams()); + ASSERT_TRUE(LoadContextMenuExtension("patterns")); + + // Wait for the js test code to create its two items with patterns. + ASSERT_TRUE(listener.WaitUntilSatisfied()); // Check that a document url that should match the items' patterns appears. - params->frame_url = GURL("http://www.google.com"); - ASSERT_TRUE(MenuHasItemWithTitle(*params, std::string("test_item1"))); - ASSERT_TRUE(MenuHasItemWithTitle(*params, std::string("test_item2"))); - - // Now check for a non-matching url. - params->frame_url = GURL("http://www.test.com"); - ASSERT_FALSE(MenuHasItemWithTitle(*params, std::string("test_item1"))); - ASSERT_FALSE(MenuHasItemWithTitle(*params, std::string("test_item2"))); + GURL google_url("http://www.google.com"); + ASSERT_TRUE(MenuHasItemWithLabel(google_url, std::string("test_item1"))); + ASSERT_TRUE(MenuHasItemWithLabel(google_url, std::string("test_item2"))); + + // Now check with a non-matching url. + GURL test_url("http://www.test.com"); + ASSERT_FALSE(MenuHasItemWithLabel(test_url, std::string("test_item1"))); + ASSERT_FALSE(MenuHasItemWithLabel(test_url, std::string("test_item2"))); +} + +// Tests registering an item with a very long title that should get truncated in +// the actual menu displayed. +IN_PROC_BROWSER_TEST_F(ExtensionContextMenuBrowserTest, LongTitle) { + ExtensionTestMessageListener listener("created"); + + // Load the extension and wait until it's created a menu item. + ASSERT_TRUE(LoadContextMenuExtension("long_title")); + ASSERT_TRUE(listener.WaitUntilSatisfied()); + + // Make sure we have an item registered with a long title. + size_t limit = RenderViewContextMenu::kMaxExtensionItemTitleLength; + ExtensionMenuItem::List items = GetItems(); + ASSERT_EQ(1u, items.size()); + ExtensionMenuItem* item = items.at(0); + ASSERT_GT(item->title().size(), limit); + + // Create a context menu, then find the item's label. It should be properly + // truncated. + GURL url("http://foo.com/"); + scoped_ptr<TestRenderViewContextMenu> menu(CreateMenuForURL(url)); + + string16 label; + ASSERT_TRUE(menu->GetItemLabel(item->id(), &label)); + ASSERT_TRUE(label.size() <= limit); } diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc index d22cfe6..70b3851 100644 --- a/chrome/browser/extensions/extension_function_dispatcher.cc +++ b/chrome/browser/extensions/extension_function_dispatcher.cc @@ -4,6 +4,8 @@ #include "chrome/browser/extensions/extension_function_dispatcher.h" +#include <map> + #include "base/process_util.h" #include "base/singleton.h" #include "base/ref_counted.h" @@ -222,6 +224,7 @@ void FactoryRegistry::ResetFunctions() { RegisterFunction<ExtensionTestLogFunction>(); RegisterFunction<ExtensionTestQuotaResetFunction>(); RegisterFunction<ExtensionTestCreateIncognitoTabFunction>(); + RegisterFunction<ExtensionTestSendMessageFunction>(); // Accessibility. RegisterFunction<GetFocusedControlFunction>(); diff --git a/chrome/browser/extensions/extension_menu_manager.cc b/chrome/browser/extensions/extension_menu_manager.cc index ebc7e85..cafb95a 100644 --- a/chrome/browser/extensions/extension_menu_manager.cc +++ b/chrome/browser/extensions/extension_menu_manager.cc @@ -6,6 +6,7 @@ #include <algorithm> +#include "app/l10n_util.h" #include "base/logging.h" #include "base/stl_util-inl.h" #include "base/string_util.h" @@ -76,11 +77,16 @@ std::set<ExtensionMenuItem::Id> ExtensionMenuItem::RemoveAllDescendants() { } string16 ExtensionMenuItem::TitleWithReplacement( - const string16& selection) const { + const string16& selection, size_t max_length) const { string16 result = UTF8ToUTF16(title_); // TODO(asargent) - Change this to properly handle %% escaping so you can // put "%s" in titles that won't get substituted. ReplaceSubstringsAfterOffset(&result, 0, ASCIIToUTF16("%s"), selection); + + if (result.length() > max_length) { + result = WideToUTF16(l10n_util::TruncateString(UTF16ToWideHack(result), + max_length)); + } return result; } diff --git a/chrome/browser/extensions/extension_menu_manager.h b/chrome/browser/extensions/extension_menu_manager.h index c2d23a5..84c0588 100644 --- a/chrome/browser/extensions/extension_menu_manager.h +++ b/chrome/browser/extensions/extension_menu_manager.h @@ -120,8 +120,10 @@ class ExtensionMenuItem { target_url_patterns_ = patterns; } - // Returns the title with any instances of %s replaced by |selection|. - string16 TitleWithReplacement(const string16& selection) const; + // Returns the title with any instances of %s replaced by |selection|. The + // result will be no longer than |max_length|. + string16 TitleWithReplacement(const string16& selection, + size_t max_length) const; // Set the checked state to |checked|. Returns true if successful. bool SetChecked(bool checked); diff --git a/chrome/browser/extensions/extension_test_api.cc b/chrome/browser/extensions/extension_test_api.cc index e5ae650..e9e46be 100644 --- a/chrome/browser/extensions/extension_test_api.cc +++ b/chrome/browser/extensions/extension_test_api.cc @@ -4,6 +4,8 @@ #include "chrome/browser/extensions/extension_test_api.h" +#include <string> + #include "chrome/browser/browser.h" #include "chrome/browser/profile.h" #include "chrome/browser/extensions/extensions_service.h" @@ -50,3 +52,14 @@ bool ExtensionTestCreateIncognitoTabFunction::RunImpl() { Browser::OpenURLOffTheRecord(profile(), GURL(url)); return true; } + +bool ExtensionTestSendMessageFunction::RunImpl() { + std::string message; + EXTENSION_FUNCTION_VALIDATE(args_->GetString(0, &message)); + std::string id = extension_id(); + NotificationService::current()->Notify( + NotificationType::EXTENSION_TEST_MESSAGE, + Source<std::string>(&id), + Details<std::string>(&message)); + return true; +} diff --git a/chrome/browser/extensions/extension_test_api.h b/chrome/browser/extensions/extension_test_api.h index 602e708..f59d0b5 100644 --- a/chrome/browser/extensions/extension_test_api.h +++ b/chrome/browser/extensions/extension_test_api.h @@ -38,4 +38,10 @@ class ExtensionTestCreateIncognitoTabFunction : public SyncExtensionFunction { DECLARE_EXTENSION_FUNCTION_NAME("test.createIncognitoTab") }; +class ExtensionTestSendMessageFunction : public SyncExtensionFunction { + ~ExtensionTestSendMessageFunction() {} + virtual bool RunImpl(); + DECLARE_EXTENSION_FUNCTION_NAME("test.sendMessage") +}; + #endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_TEST_API_H_ diff --git a/chrome/browser/extensions/extension_test_message_listener.cc b/chrome/browser/extensions/extension_test_message_listener.cc new file mode 100644 index 0000000..3cf971b --- /dev/null +++ b/chrome/browser/extensions/extension_test_message_listener.cc @@ -0,0 +1,43 @@ +// Copyright (c) 2010 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/extensions/extension_test_message_listener.h" + +#include "base/logging.h" +#include "chrome/common/notification_service.h" +#include "chrome/common/notification_type.h" +#include "chrome/test/ui_test_utils.h" + +ExtensionTestMessageListener::ExtensionTestMessageListener( + const std::string& expected_message) + : expected_message_(expected_message), satisfied_(false), + waiting_(false) { + registrar_.Add(this, NotificationType::EXTENSION_TEST_MESSAGE, + NotificationService::AllSources()); +} + +ExtensionTestMessageListener::~ExtensionTestMessageListener() {} + +bool ExtensionTestMessageListener::WaitUntilSatisfied() { + if (satisfied_) + return true; + waiting_ = true; + ui_test_utils::RunMessageLoop(); + return satisfied_; +} + +void ExtensionTestMessageListener::Observe( + NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + const std::string& content = *Details<std::string>(details).ptr(); + if (!satisfied_ && content == expected_message_) { + satisfied_ = true; + registrar_.RemoveAll(); // Stop listening for more messages. + if (waiting_) { + waiting_ = false; + MessageLoopForUI::current()->Quit(); + } + } +} diff --git a/chrome/browser/extensions/extension_test_message_listener.h b/chrome/browser/extensions/extension_test_message_listener.h new file mode 100644 index 0000000..2aeedd8 --- /dev/null +++ b/chrome/browser/extensions/extension_test_message_listener.h @@ -0,0 +1,70 @@ +// Copyright (c) 2010 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. + +#ifndef CHROME_BROWSER_EXTENSIONS_EXTENSION_TEST_MESSAGE_LISTENER_H_ +#define CHROME_BROWSER_EXTENSIONS_EXTENSION_TEST_MESSAGE_LISTENER_H_ +#pragma once + +#include <string> + +#include "chrome/common/notification_observer.h" +#include "chrome/common/notification_registrar.h" + +// A message from javascript sent via chrome.test.sendMessage(). +struct ExtensionTestMessage { + // The sender's extension id. + std::string extension_id; + + // The message content. + std::string content; +}; + +// This class helps us wait for incoming messages sent from javascript via +// chrome.test.sendMessage(). A sample usage would be: +// +// ExtensionTestMessageListener listener("foo"); +// ... do some work +// ASSERT_TRUE(listener.WaitUntilSatisfied()); +// +// TODO(asargent) - In the future we may want to add the ability to listen for +// multiple messages, and/or to wait for "any" message and then retrieve the +// contents of that message. We may also want to specify an extension id as +// satisfaction criteria in addition to message content. +// +// Note that when using it in browser tests, you need to make sure it gets +// destructed *before* the browser gets torn down. Two common patterns are to +// either make it a local variable inside your test body, or if it's a member +// variable of a ExtensionBrowserTest subclass, override the +// InProcessBrowserTest::CleanUpOnMainThread() method and clean it up there. +class ExtensionTestMessageListener : public NotificationObserver { + public: + // We immediately start listening for |expected_message|. + explicit ExtensionTestMessageListener(const std::string& expected_message); + ~ExtensionTestMessageListener(); + + // This returns true immediately if we've already gotten the expected + // message, or waits until it arrives. Returns false if the wait is + // interrupted and we still haven't gotten the message. + bool WaitUntilSatisfied(); + + // Implements the NotificationObserver interface. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + private: + NotificationRegistrar registrar_; + + // The message we're expecting. + std::string expected_message_; + + // Whether we've seen expected_message_ yet. + bool satisfied_; + + // If we're waiting, then we want to post a quit task when the expected + // message arrives. + bool waiting_; +}; + +#endif // CHROME_BROWSER_EXTENSIONS_EXTENSION_TEST_MESSAGE_LISTENER_H_ diff --git a/chrome/browser/tab_contents/render_view_context_menu.cc b/chrome/browser/tab_contents/render_view_context_menu.cc index 64e69e6..f7dd166 100644 --- a/chrome/browser/tab_contents/render_view_context_menu.cc +++ b/chrome/browser/tab_contents/render_view_context_menu.cc @@ -54,6 +54,11 @@ using WebKit::WebContextMenuData; using WebKit::WebMediaPlayerAction; // static +const size_t RenderViewContextMenu::kMaxExtensionItemTitleLength = 75; +// static +const size_t RenderViewContextMenu::kMaxSelectionTextLength = 50; + +// static bool RenderViewContextMenu::IsDevToolsURL(const GURL& url) { return url.SchemeIs(chrome::kChromeUIScheme) && url.host() == chrome::kChromeUIDevToolsHost; @@ -202,7 +207,8 @@ void RenderViewContextMenu::AppendExtensionItems( } else { ExtensionMenuItem* item = items[0]; extension_item_map_[menu_id] = item->id(); - title = item->TitleWithReplacement(PrintableSelectionText()); + title = item->TitleWithReplacement(PrintableSelectionText(), + kMaxExtensionItemTitleLength); submenu_items = GetRelevantExtensionItems(item->children(), params_); } @@ -243,7 +249,8 @@ void RenderViewContextMenu::RecursivelyAppendExtensionItems( if (menu_id >= IDC_EXTENSIONS_CONTEXT_CUSTOM_LAST) return; extension_item_map_[menu_id] = item->id(); - string16 title = item->TitleWithReplacement(selection_text); + string16 title = item->TitleWithReplacement(selection_text, + kMaxExtensionItemTitleLength); if (item->type() == ExtensionMenuItem::NORMAL) { ExtensionMenuItem::List children = GetRelevantExtensionItems(item->children(), params_); @@ -1367,7 +1374,8 @@ bool RenderViewContextMenu::IsDevCommandEnabled(int id) const { } string16 RenderViewContextMenu::PrintableSelectionText() { - return WideToUTF16(l10n_util::TruncateString(params_.selection_text, 50)); + return WideToUTF16(l10n_util::TruncateString(params_.selection_text, + kMaxSelectionTextLength)); } // Controller functions -------------------------------------------------------- diff --git a/chrome/browser/tab_contents/render_view_context_menu.h b/chrome/browser/tab_contents/render_view_context_menu.h index f552544..21abb1d 100644 --- a/chrome/browser/tab_contents/render_view_context_menu.h +++ b/chrome/browser/tab_contents/render_view_context_menu.h @@ -32,6 +32,9 @@ struct WebMediaPlayerAction; class RenderViewContextMenu : public menus::SimpleMenuModel::Delegate { public: + static const size_t kMaxExtensionItemTitleLength; + static const size_t kMaxSelectionTextLength; + RenderViewContextMenu(TabContents* tab_contents, const ContextMenuParams& params); |