summaryrefslogtreecommitdiffstats
path: root/chrome/browser/cocoa
diff options
context:
space:
mode:
authorjeremy@chromium.org <jeremy@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-23 16:00:25 +0000
committerjeremy@chromium.org <jeremy@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2009-06-23 16:00:25 +0000
commit68b1e92848aebead28f92118e698a1b644d6f572 (patch)
treeed7d70696afd92878d2637ab9bd47c7ab87a9f9f /chrome/browser/cocoa
parenteb888d2f99d7de22640caa2da7cd707261afc2e4 (diff)
downloadchromium_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.h65
-rw-r--r--chrome/browser/cocoa/rwhvm_editcommand_helper.mm224
-rw-r--r--chrome/browser/cocoa/rwhvm_editcommand_helper_unittest.mm172
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.
+}