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#import "content/browser/renderer_host/render_widget_host_view_mac_editcommand_helper.h"
6
7#import <objc/runtime.h>
8
9#include "content/browser/renderer_host/render_widget_host_impl.h"
10#import "content/browser/renderer_host/render_widget_host_view_mac.h"
11
12namespace content {
13namespace {
14
15// The names of all the objc selectors w/o ':'s added to an object by
16// AddEditingSelectorsToClass().
17//
18// This needs to be kept in Sync with WEB_COMMAND list in the WebKit tree at:
19// WebKit/mac/WebView/WebHTMLView.mm .
20const char* kEditCommands[] = {
21  "alignCenter",
22  "alignJustified",
23  "alignLeft",
24  "alignRight",
25  "copy",
26  "cut",
27  "delete",
28  "deleteBackward",
29  "deleteBackwardByDecomposingPreviousCharacter",
30  "deleteForward",
31  "deleteToBeginningOfLine",
32  "deleteToBeginningOfParagraph",
33  "deleteToEndOfLine",
34  "deleteToEndOfParagraph",
35  "deleteToMark",
36  "deleteWordBackward",
37  "deleteWordForward",
38  "ignoreSpelling",
39  "indent",
40  "insertBacktab",
41  "insertLineBreak",
42  "insertNewline",
43  "insertNewlineIgnoringFieldEditor",
44  "insertParagraphSeparator",
45  "insertTab",
46  "insertTabIgnoringFieldEditor",
47  "makeTextWritingDirectionLeftToRight",
48  "makeTextWritingDirectionNatural",
49  "makeTextWritingDirectionRightToLeft",
50  "moveBackward",
51  "moveBackwardAndModifySelection",
52  "moveDown",
53  "moveDownAndModifySelection",
54  "moveForward",
55  "moveForwardAndModifySelection",
56  "moveLeft",
57  "moveLeftAndModifySelection",
58  "moveParagraphBackwardAndModifySelection",
59  "moveParagraphForwardAndModifySelection",
60  "moveRight",
61  "moveRightAndModifySelection",
62  "moveToBeginningOfDocument",
63  "moveToBeginningOfDocumentAndModifySelection",
64  "moveToBeginningOfLine",
65  "moveToBeginningOfLineAndModifySelection",
66  "moveToBeginningOfParagraph",
67  "moveToBeginningOfParagraphAndModifySelection",
68  "moveToBeginningOfSentence",
69  "moveToBeginningOfSentenceAndModifySelection",
70  "moveToEndOfDocument",
71  "moveToEndOfDocumentAndModifySelection",
72  "moveToEndOfLine",
73  "moveToEndOfLineAndModifySelection",
74  "moveToEndOfParagraph",
75  "moveToEndOfParagraphAndModifySelection",
76  "moveToEndOfSentence",
77  "moveToEndOfSentenceAndModifySelection",
78  "moveUp",
79  "moveUpAndModifySelection",
80  "moveWordBackward",
81  "moveWordBackwardAndModifySelection",
82  "moveWordForward",
83  "moveWordForwardAndModifySelection",
84  "moveWordLeft",
85  "moveWordLeftAndModifySelection",
86  "moveWordRight",
87  "moveWordRightAndModifySelection",
88  "outdent",
89  "pageDown",
90  "pageDownAndModifySelection",
91  "pageUp",
92  "pageUpAndModifySelection",
93  "selectAll",
94  "selectLine",
95  "selectParagraph",
96  "selectSentence",
97  "selectToMark",
98  "selectWord",
99  "setMark",
100  "showGuessPanel",
101  "subscript",
102  "superscript",
103  "swapWithMark",
104  "transpose",
105  "underline",
106  "unscript",
107  "yank",
108  "yankAndSelect"};
109
110
111// This function is installed via the objc runtime as the implementation of all
112// the various editing selectors.
113// The objc runtime hookup occurs in
114// RenderWidgetHostViewMacEditCommandHelper::AddEditingSelectorsToClass().
115//
116// self - the object we're attached to; it must implement the
117// RenderWidgetHostViewMacOwner protocol.
118// _cmd - the selector that fired.
119// sender - the id of the object that sent the message.
120//
121// The selector is translated into an edit comand and then forwarded down the
122// pipeline to WebCore.
123// The route the message takes is:
124// RenderWidgetHostViewMac -> RenderViewHost ->
125// | IPC | ->
126// RenderView -> currently focused WebFrame.
127// The WebFrame is in the Chrome glue layer and forwards the message to WebCore.
128void EditCommandImp(id self, SEL _cmd, id sender) {
129  // Make sure |self| is the right type.
130  DCHECK([self conformsToProtocol:@protocol(RenderWidgetHostViewMacOwner)]);
131
132  // SEL -> command name string.
133  NSString* command_name_ns =
134      RenderWidgetHostViewMacEditCommandHelper::CommandNameForSelector(_cmd);
135  std::string command([command_name_ns UTF8String]);
136
137  // Forward the edit command string down the pipeline.
138  RenderWidgetHostViewMac* rwhv = [(id<RenderWidgetHostViewMacOwner>)self
139      renderWidgetHostViewMac];
140  DCHECK(rwhv);
141
142  RenderWidgetHostImpl* rwh =
143      RenderWidgetHostImpl::From(rwhv->GetRenderWidgetHost());
144  // The second parameter is the core command value which isn't used here.
145  rwh->ExecuteEditCommand(command, "");
146}
147
148}  // namespace
149
150// Maps an objc-selector to a core command name.
151//
152// Returns the core command name (which is the selector name with the trailing
153// ':' stripped in most cases).
154//
155// Adapted from a function by the same name in
156// WebKit/mac/WebView/WebHTMLView.mm .
157// Capitalized names are returned from this function, but that's simply
158// matching WebHTMLView.mm.
159NSString* RenderWidgetHostViewMacEditCommandHelper::CommandNameForSelector(
160    SEL selector) {
161  if (selector == @selector(insertParagraphSeparator:) ||
162      selector == @selector(insertNewlineIgnoringFieldEditor:))
163    return @"InsertNewline";
164  if (selector == @selector(insertTabIgnoringFieldEditor:))
165    return @"InsertTab";
166  if (selector == @selector(pageDown:))
167    return @"MovePageDown";
168  if (selector == @selector(pageDownAndModifySelection:))
169    return @"MovePageDownAndModifySelection";
170  if (selector == @selector(pageUp:))
171    return @"MovePageUp";
172  if (selector == @selector(pageUpAndModifySelection:))
173    return @"MovePageUpAndModifySelection";
174
175  // Remove the trailing colon.
176  NSString* selector_str = NSStringFromSelector(selector);
177  int selector_len = [selector_str length];
178  return [selector_str substringToIndex:selector_len - 1];
179}
180
181RenderWidgetHostViewMacEditCommandHelper::
182    RenderWidgetHostViewMacEditCommandHelper() {
183  for (size_t i = 0; i < arraysize(kEditCommands); ++i) {
184    edit_command_set_.insert(kEditCommands[i]);
185  }
186}
187
188RenderWidgetHostViewMacEditCommandHelper::
189    ~RenderWidgetHostViewMacEditCommandHelper() {}
190
191// Dynamically adds Selectors to the aformentioned class.
192void RenderWidgetHostViewMacEditCommandHelper::AddEditingSelectorsToClass(
193    Class klass) {
194  for (size_t i = 0; i < arraysize(kEditCommands); ++i) {
195    // Append trailing ':' to command name to get selector name.
196    NSString* sel_str = [NSString stringWithFormat: @"%s:", kEditCommands[i]];
197
198    SEL edit_selector = NSSelectorFromString(sel_str);
199    // May want to use @encode() for the last parameter to this method.
200    // If class_addMethod fails we assume that all the editing selectors where
201    // added to the class.
202    // If a certain class already implements a method then class_addMethod
203    // returns NO, which we can safely ignore.
204    class_addMethod(klass, edit_selector, (IMP)EditCommandImp, "v@:@");
205  }
206}
207
208bool RenderWidgetHostViewMacEditCommandHelper::IsMenuItemEnabled(
209    SEL item_action,
210    id<RenderWidgetHostViewMacOwner> owner) {
211  const char* selector_name = sel_getName(item_action);
212  // TODO(jeremy): The final form of this function will check state
213  // associated with the Browser.
214
215  // For now just mark all edit commands as enabled.
216  NSString* selector_name_ns = [NSString stringWithUTF8String:selector_name];
217
218  // Remove trailing ':'
219  size_t str_len = [selector_name_ns length];
220  selector_name_ns = [selector_name_ns substringToIndex:str_len - 1];
221  std::string edit_command_name([selector_name_ns UTF8String]);
222
223  // search for presence in set and return.
224  bool ret = edit_command_set_.find(edit_command_name) !=
225      edit_command_set_.end();
226  return ret;
227}
228
229NSArray* RenderWidgetHostViewMacEditCommandHelper::GetEditSelectorNames() {
230  size_t num_edit_commands = arraysize(kEditCommands);
231  NSMutableArray* ret = [NSMutableArray arrayWithCapacity:num_edit_commands];
232
233  for (size_t i = 0; i < num_edit_commands; ++i) {
234      [ret addObject:[NSString stringWithUTF8String:kEditCommands[i]]];
235  }
236
237  return ret;
238}
239
240}  // namespace content
241