diff options
author | jeremy@chromium.org <jeremy@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-23 16:00:25 +0000 |
---|---|---|
committer | jeremy@chromium.org <jeremy@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2009-06-23 16:00:25 +0000 |
commit | 68b1e92848aebead28f92118e698a1b644d6f572 (patch) | |
tree | ed7d70696afd92878d2637ab9bd47c7ab87a9f9f /chrome/browser/cocoa | |
parent | eb888d2f99d7de22640caa2da7cd707261afc2e4 (diff) | |
download | chromium_src-68b1e92848aebead28f92118e698a1b644d6f572.zip chromium_src-68b1e92848aebead28f92118e698a1b644d6f572.tar.gz chromium_src-68b1e92848aebead28f92118e698a1b644d6f572.tar.bz2 |
Re-land r18853
Review URL: http://codereview.chromium.org/141008
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@19023 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'chrome/browser/cocoa')
-rw-r--r-- | chrome/browser/cocoa/rwhvm_editcommand_helper.h | 65 | ||||
-rw-r--r-- | chrome/browser/cocoa/rwhvm_editcommand_helper.mm | 224 | ||||
-rw-r--r-- | chrome/browser/cocoa/rwhvm_editcommand_helper_unittest.mm | 172 |
3 files changed, 461 insertions, 0 deletions
diff --git a/chrome/browser/cocoa/rwhvm_editcommand_helper.h b/chrome/browser/cocoa/rwhvm_editcommand_helper.h new file mode 100644 index 0000000..aa948db --- /dev/null +++ b/chrome/browser/cocoa/rwhvm_editcommand_helper.h @@ -0,0 +1,65 @@ +// Copyright (c) 2009 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_COCOA_RWHVM_EDITCOMMAND_HELPER_H_ +#define CHROME_BROWSER_COCOA_RWHVM_EDITCOMMAND_HELPER_H_ + +#import <Cocoa/Cocoa.h> + +#include "base/hash_tables.h" +#include "base/logging.h" +#include "chrome/browser/renderer_host/render_widget_host_view_mac.h" +#include "testing/gtest/include/gtest/gtest_prod.h" + +// RenderWidgetHostViewMacEditCommandHelper is the real name of this class +// but that's too long, so we use a shorter version. +// +// This class mimics the behavior of WebKit's WebView class in a way that makes +// sense for Chrome. +// +// WebCore has the concept of "core commands", basically named actions such as +// "Select All" and "Move Cursor Left". The commands are executed using their +// string value by WebCore. +// +// This class is responsible for 2 things: +// 1. Provide an abstraction to determine the enabled/disabled state of menu +// items that correspond to edit commands. +// 2. Hook up a bunch of objc selectors to the RenderWidgetHostViewCocoa object. +// (note that this is not a misspelling of RenderWidgetHostViewMac, it's in +// fact a distinct object) When these selectors are called, the relevant +// edit command is executed in WebCore. +class RWHVMEditCommandHelper { + FRIEND_TEST(RWHVMEditCommandHelperTest, TestAddEditingSelectorsToClass); + FRIEND_TEST(RWHVMEditCommandHelperTest, TestEditingCommandDelivery); + + public: + RWHVMEditCommandHelper(); + + // Adds editing selectors to the objc class using the objc runtime APIs. + // Each selector is connected to a single c method which forwards the message + // to WebCore's ExecuteCoreCommand() function. + // This method is idempotent. + // The class passed in must conform to the RenderWidgetHostViewMacOwner + // protocol. + void AddEditingSelectorsToClass(Class klass); + + // Is a given menu item currently enabled? + // SEL - the objc selector currently associated with an NSMenuItem. + // owner - An object we can retrieve a RenderWidgetHostViewMac from to + // determine the command states. + bool IsMenuItemEnabled(SEL item_action, + id<RenderWidgetHostViewMacOwner> owner); + + protected: + // Gets a list of all the selectors that AddEditingSelectorsToClass adds to + // the aforementioned class. + // returns an array of NSStrings WITHOUT the trailing ':'s. + NSArray* GetEditSelectorNames(); + + private: + base::hash_set<std::string> edit_command_set_; + DISALLOW_COPY_AND_ASSIGN(RWHVMEditCommandHelper); +}; + +#endif // CHROME_BROWSER_COCOA_RWHVM_EDITCOMMAND_HELPER_H_ diff --git a/chrome/browser/cocoa/rwhvm_editcommand_helper.mm b/chrome/browser/cocoa/rwhvm_editcommand_helper.mm new file mode 100644 index 0000000..87956b0 --- /dev/null +++ b/chrome/browser/cocoa/rwhvm_editcommand_helper.mm @@ -0,0 +1,224 @@ +// Copyright (c), 2009 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. + +#import "chrome/browser/cocoa/rwhvm_editcommand_helper.h" + +#import <objc/runtime.h> + +#include "chrome/browser/renderer_host/render_widget_host.h" +#import "chrome/browser/renderer_host/render_widget_host_view_mac.h" + +namespace { +// The names of all the objc selectors w/o ':'s added to an object by +// AddEditingSelectorsToClass(). +// +// This needs to be kept in Sync with WEB_COMMAND list in the WebKit tree at: +// WebKit/mac/WebView/WebHTMLView.mm . +const char* kEditCommands[] = { + "alignCenter", + "alignJustified", + "alignLeft", + "alignRight", + "copy", + "cut", + "delete", + "deleteBackward", + "deleteBackwardByDecomposingPreviousCharacter", + "deleteForward", + "deleteToBeginningOfLine", + "deleteToBeginningOfParagraph", + "deleteToEndOfLine", + "deleteToEndOfParagraph", + "deleteToMark", + "deleteWordBackward", + "deleteWordForward", + "ignoreSpelling", + "indent", + "insertBacktab", + "insertLineBreak", + "insertNewline", + "insertNewlineIgnoringFieldEditor", + "insertParagraphSeparator", + "insertTab", + "insertTabIgnoringFieldEditor", + "makeTextWritingDirectionLeftToRight", + "makeTextWritingDirectionNatural", + "makeTextWritingDirectionRightToLeft", + "moveBackward", + "moveBackwardAndModifySelection", + "moveDown", + "moveDownAndModifySelection", + "moveForward", + "moveForwardAndModifySelection", + "moveLeft", + "moveLeftAndModifySelection", + "moveParagraphBackwardAndModifySelection", + "moveParagraphForwardAndModifySelection", + "moveRight", + "moveRightAndModifySelection", + "moveToBeginningOfDocument", + "moveToBeginningOfDocumentAndModifySelection", + "moveToBeginningOfLine", + "moveToBeginningOfLineAndModifySelection", + "moveToBeginningOfParagraph", + "moveToBeginningOfParagraphAndModifySelection", + "moveToBeginningOfSentence", + "moveToBeginningOfSentenceAndModifySelection", + "moveToEndOfDocument", + "moveToEndOfDocumentAndModifySelection", + "moveToEndOfLine", + "moveToEndOfLineAndModifySelection", + "moveToEndOfParagraph", + "moveToEndOfParagraphAndModifySelection", + "moveToEndOfSentence", + "moveToEndOfSentenceAndModifySelection", + "moveUp", + "moveUpAndModifySelection", + "moveWordBackward", + "moveWordBackwardAndModifySelection", + "moveWordForward", + "moveWordForwardAndModifySelection", + "moveWordLeft", + "moveWordLeftAndModifySelection", + "moveWordRight", + "moveWordRightAndModifySelection", + "outdent", + "pageDown", + "pageDownAndModifySelection", + "pageUp", + "pageUpAndModifySelection", + "selectAll", + "selectLine", + "selectParagraph", + "selectSentence", + "selectToMark", + "selectWord", + "setMark", + "subscript", + "superscript", + "swapWithMark", + "transpose", + "underline", + "unscript", + "yank", + "yankAndSelect"}; + +// Maps an objc-selector to a core command name. +// +// Returns the core command name (which is the selector name with the trailing +// ':' stripped in most cases). +// +// Adapted from a function by the same name in +// WebKit/mac/WebView/WebHTMLView.mm . +// Capitalized names are returned from this function, but that's simply +// matching WebHTMLView.mm. +NSString* CommandNameForSelector(SEL selector) { + if (selector == @selector(insertParagraphSeparator:) || + selector == @selector(insertNewlineIgnoringFieldEditor:)) + return @"InsertNewline"; + if (selector == @selector(insertTabIgnoringFieldEditor:)) + return @"InsertTab"; + if (selector == @selector(pageDown:)) + return @"MovePageDown"; + if (selector == @selector(pageDownAndModifySelection:)) + return @"MovePageDownAndModifySelection"; + if (selector == @selector(pageUp:)) + return @"MovePageUp"; + if (selector == @selector(pageUpAndModifySelection:)) + return @"MovePageUpAndModifySelection"; + + // Remove the trailing colon. + NSString* selector_str = NSStringFromSelector(selector); + int selector_len = [selector_str length]; + return [selector_str substringToIndex:selector_len - 1]; +} + +// This function is installed via the objc runtime as the implementation of all +// the various editing selectors. +// The objc runtime hookup occurs in +// RWHVMEditCommandHelper::AddEditingSelectorsToClass(). +// +// self - the object we're attached to; it must implement the +// RenderWidgetHostViewMacOwner protocol. +// _cmd - the selector that fired. +// sender - the id of the object that sent the message. +// +// The selector is translated into an edit comand and then forwarded down the +// pipeline to WebCore. +// The route the message takes is: +// RenderWidgetHostViewMac -> RenderViewHost -> +// | IPC | -> +// RenderView -> currently focused WebFrame. +// The WebFrame is in the Chrome glue layer and forwards the message to WebCore. +void EditCommandImp(id self, SEL _cmd, id sender) { + // Make sure |self| is the right type. + DCHECK([self conformsToProtocol:@protocol(RenderWidgetHostViewMacOwner)]); + + // SEL -> command name string. + NSString* command_name_ns = CommandNameForSelector(_cmd); + std::string edit_command([command_name_ns UTF8String]); + + // Forward the edit command string down the pipeline. + RenderWidgetHostViewMac* rwhv = [(id<RenderWidgetHostViewMacOwner>)self + renderWidgetHostViewMac]; + DCHECK(rwhv); + + // The second parameter is the core command value which isn't used here. + rwhv->GetRenderWidgetHost()->ForwardEditCommand(edit_command, ""); +} + +} // namespace + +RWHVMEditCommandHelper::RWHVMEditCommandHelper() { + for (size_t i = 0; i < arraysize(kEditCommands); ++i) { + edit_command_set_.insert(kEditCommands[i]); + } +} + +// Dynamically adds Selectors to the aformentioned class. +void RWHVMEditCommandHelper::AddEditingSelectorsToClass(Class klass) { + for (size_t i = 0; i < arraysize(kEditCommands); ++i) { + // Append trailing ':' to command name to get selector name. + NSString* sel_str = [NSString stringWithFormat: @"%s:", kEditCommands[i]]; + + SEL edit_selector = NSSelectorFromString(sel_str); + // May want to use @encode() for the last parameter to this method. + // If class_addMethod fails we assume that all the editing selectors where + // added to the class. + // If a certain class already implements a method then class_addMethod + // returns NO, which we can safely ignore. + class_addMethod(klass, edit_selector, (IMP)EditCommandImp, "v@:@"); + } +} + +bool RWHVMEditCommandHelper::IsMenuItemEnabled(SEL item_action, + id<RenderWidgetHostViewMacOwner> owner) { + const char* selector_name = sel_getName(item_action); + // TODO(jeremy): The final form of this function will check state + // associated with the Browser. + + // For now just mark all edit commands as enabled. + NSString* selector_name_ns = [NSString stringWithUTF8String:selector_name]; + + // Remove trailing ':' + size_t str_len = [selector_name_ns length]; + selector_name_ns = [selector_name_ns substringToIndex:str_len - 1]; + std::string edit_command_name([selector_name_ns UTF8String]); + + // search for presence in set and return. + bool ret = edit_command_set_.find(edit_command_name) != + edit_command_set_.end(); + return ret; +} + +NSArray* RWHVMEditCommandHelper::GetEditSelectorNames() { + size_t num_edit_commands = arraysize(kEditCommands); + NSMutableArray* ret = [NSMutableArray arrayWithCapacity:num_edit_commands]; + + for (size_t i = 0; i < num_edit_commands; ++i) { + [ret addObject:[NSString stringWithUTF8String:kEditCommands[i]]]; + } + + return ret; +} diff --git a/chrome/browser/cocoa/rwhvm_editcommand_helper_unittest.mm b/chrome/browser/cocoa/rwhvm_editcommand_helper_unittest.mm new file mode 100644 index 0000000..4fd5452 --- /dev/null +++ b/chrome/browser/cocoa/rwhvm_editcommand_helper_unittest.mm @@ -0,0 +1,172 @@ +// Copyright (c) 2009 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. + +#import "chrome/browser/cocoa/rwhvm_editcommand_helper.h" + +#import <Cocoa/Cocoa.h> + +#include "chrome/browser/renderer_host/mock_render_process_host.h" +#include "chrome/browser/renderer_host/render_widget_host.h" +#include "chrome/test/testing_profile.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/platform_test.h" + +class RWHVMEditCommandHelperTest : public PlatformTest { +}; + +// Bare bones obj-c class for testing purposes. +@interface RWHVMEditCommandHelperTestClass : NSObject +@end + +@implementation RWHVMEditCommandHelperTestClass +@end + +// Class that owns a RenderWidgetHostViewMac. +@interface RenderWidgetHostViewMacOwner : + NSObject<RenderWidgetHostViewMacOwner> { + RenderWidgetHostViewMac* rwhvm_; +} + +- (id) initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)rwhvm; +@end + +@implementation RenderWidgetHostViewMacOwner + +- (id)initWithRenderWidgetHostViewMac:(RenderWidgetHostViewMac*)rwhvm { + if ((self = [super init])) { + rwhvm_ = rwhvm; + } + return self; +} + +- (RenderWidgetHostViewMac*)renderWidgetHostViewMac { + return rwhvm_; +} + +@end + + +namespace { + // Returns true if all the edit command names in the array are present + // in test_obj. + // edit_commands is a list of NSStrings, selector names are formed by + // appending a trailing ':' to the string. + bool CheckObjectRespondsToEditCommands(NSArray* edit_commands, id test_obj) { + for (NSString* edit_command_name in edit_commands) { + NSString* sel_str = [edit_command_name stringByAppendingString:@":"]; + if (![test_obj respondsToSelector:NSSelectorFromString(sel_str)]) { + return false; + } + } + + return true; + } +} // namespace + +// Create a Mock RenderWidget +class MockRenderWidgetHostEditCommandCounter : public RenderWidgetHost { + public: + MockRenderWidgetHostEditCommandCounter(RenderProcessHost* process, + int routing_id) : + RenderWidgetHost(process, routing_id) {} + + MOCK_METHOD2(ForwardEditCommand, void(const std::string&, + const std::string&)); +}; + + +// Tests that editing commands make it through the pipeline all the way to +// RenderWidgetHost. +TEST_F(RWHVMEditCommandHelperTest, TestEditingCommandDelivery) { + RWHVMEditCommandHelper helper; + NSArray* edit_command_strings = helper.GetEditSelectorNames(); + + // Set up a mock render widget and set expectations. + MessageLoopForUI message_loop; + TestingProfile profile; + MockRenderProcessHost mock_process(&profile); + MockRenderWidgetHostEditCommandCounter mock_render_widget(&mock_process, 0); + + size_t num_edit_commands = [edit_command_strings count]; + EXPECT_CALL(mock_render_widget, + ForwardEditCommand(testing::_, testing::_)).Times(num_edit_commands); + +// TODO(jeremy): Figure this out and reenable this test. +// For some bizarre reason this code doesn't work, running the code in the +// debugger confirms that the function is called with the correct parameters +// however gmock appears not to be able to match up parameters correctly. +// Disable for now till we can figure this out. +#if 0 + // Tell Mock object that we expect to recieve each edit command once. + std::string empty_str; + for (NSString* edit_command_name in edit_command_strings) { + std::string command([edit_command_name UTF8String]); + EXPECT_CALL(mock_render_widget, + ForwardEditCommand(command, empty_str)).Times(1); + } +#endif // 0 + + // RenderWidgetHostViewMac self destructs (RenderWidgetHostViewMacCocoa + // takes ownership) so no need to delete it ourselves. + RenderWidgetHostViewMac* rwhvm = new RenderWidgetHostViewMac( + &mock_render_widget); + + RenderWidgetHostViewMacOwner* rwhwvm_owner = + [[[RenderWidgetHostViewMacOwner alloc] + initWithRenderWidgetHostViewMac:rwhvm] autorelease]; + + helper.AddEditingSelectorsToClass([rwhwvm_owner class]); + + for (NSString* edit_command_name in edit_command_strings) { + NSString* sel_str = [edit_command_name stringByAppendingString:@":"]; + [rwhwvm_owner performSelector:NSSelectorFromString(sel_str) withObject:nil]; + } +} + +// Test RWHVMEditCommandHelper::AddEditingSelectorsToClass +TEST_F(RWHVMEditCommandHelperTest, TestAddEditingSelectorsToClass) { + RWHVMEditCommandHelper helper; + NSArray* edit_command_strings = helper.GetEditSelectorNames(); + ASSERT_GT([edit_command_strings count], 0U); + + // Create a class instance and add methods to the class. + RWHVMEditCommandHelperTestClass* test_obj = + [[[RWHVMEditCommandHelperTestClass alloc] init] autorelease]; + + // Check that edit commands aren't already attached to the object. + ASSERT_FALSE(CheckObjectRespondsToEditCommands(edit_command_strings, + test_obj)); + + helper.AddEditingSelectorsToClass([test_obj class]); + + // Check that all edit commands where added. + ASSERT_TRUE(CheckObjectRespondsToEditCommands(edit_command_strings, + test_obj)); + + // AddEditingSelectorsToClass() should be idempotent. + helper.AddEditingSelectorsToClass([test_obj class]); + + // Check that all edit commands are still there. + ASSERT_TRUE(CheckObjectRespondsToEditCommands(edit_command_strings, + test_obj)); +} + +// Test RWHVMEditCommandHelper::IsMenuItemEnabled. +TEST_F(RWHVMEditCommandHelperTest, TestMenuItemEnabling) { + RWHVMEditCommandHelper helper; + RenderWidgetHostViewMacOwner* rwhvm_owner = + [[[RenderWidgetHostViewMacOwner alloc] init] autorelease]; + + // The select all menu should always be enabled. + SEL select_all = NSSelectorFromString(@"selectAll:"); + ASSERT_TRUE(helper.IsMenuItemEnabled(select_all, rwhvm_owner)); + + // Random selectors should be enabled by the function. + SEL garbage_selector = NSSelectorFromString(@"randomGarbageSelector:"); + ASSERT_FALSE(helper.IsMenuItemEnabled(garbage_selector, rwhvm_owner)); + + // TODO(jeremy): Currently IsMenuItemEnabled just returns true for all edit + // selectors. Once we go past that we should do more extensive testing here. +} |