(前回)Cocoaの日々: Safari用独自プラグインを作る(7) - Evernote を class-dump する
これまでの調査を元に Safari のコンテキストメニューへオリジナルのメニューを追加してみよう。
Safari ではコンテキストメニューに表示する NSMenuItem の配列を BrowserWebView の次のメソッドで取得するようだ。
- (id)webView:(id)arg1 contextMenuItemsForElement:(id)arg2 defaultMenuItems:(id)arg3;
Evernote はこのメソッドの置き換えているようなので、今回は同じようにこのメソッドを置き換えてみる。
まずコンテキストメニューに関する処理を受け持つ専用のクラスを用意した。名前は Evernote のそれをまねした。
SXSafariContextMenuSwizzler.h
@interface SXSafariContextMenuSwizzler : NSObject {
}
+ (void)setup;
@endSXSafariContextMenuSwizzler.m
static SXSafariContextMenuSwizzler* _shared_instance = nil;
+ (void)setup
{
if (!_shared_instance) {
_shared_instance = [[SXSafariContextMenuSwizzler alloc] init];
[_shared_instance swizzleMethodForClass:objc_getClass("BrowserWebView")
orginalSelector:@selector(
webView:contextMenuItemsForElement:defaultMenuItems:)
alternativeSelector:@selector(
_sx_webView:contextMenuItemsForElement:defaultMenuItems:)];
}
}+[setup] でインスタンスを作り、置換メソッド swizzleMethodForClass:orginalSelector:alternativeSelector を使いメソッドを置き換える。
その置換メソッドはこんな感じ。
-(void)swizzleMethodForClass:(Class)cls orginalSelector:(SEL)org_sel alternativeSelector:(SEL)alt_sel
{
Method org_method = class_getInstanceMethod(cls, org_sel);
class_addMethod(cls, alt_sel,
class_getMethodImplementation([self class], alt_sel),
method_getTypeEncoding(org_method));
Method alt_method = class_getInstanceMethod(cls, alt_sel);
method_exchangeImplementations(org_method, alt_method);
}メソッドを追加した後、method_exchangeImplementations() でメソッドを置換している。class_addMethod( ) の引数は若干ややこしいかもしれない。続いて置換した代替メソッド。
- (NSArray *)_sx_webView:(WebView *)sender contextMenuItemsForElement:(NSDictionary *)element
defaultMenuItems:(NSArray *)defaultMenuItems {
NSArray* menu_items = [self _sx_webView:sender
contextMenuItemsForElement:element
defaultMenuItems:defaultMenuItems];
NSMutableArray* new_menu_items = [NSMutableArray arrayWithArray:menu_items];
[new_menu_items addObject:[NSMenuItem separatorItem]];
NSMenuItem* item = [[[NSMenuItem alloc] initWithTitle:@"Say Hello"
action:@selector(sayHello:)
keyEquivalent:@""] autorelease];
[item setTarget:_shared_instance];
[new_menu_items addObject:item];
return new_menu_items;
}最初にオリジナルメソッドを呼び出して MenuItem の配列を取得し、そこへ独自の MenuItem を追加する。ターゲットには+[setup]でとっておいた _shared_instance を割り当てる。
続いてメニューが選択された時の処理。ここではログへ文字列を表示してみた。
- (void)sayHello:(id)sender
{
NSLog(@"Hello !");
}
最後にコントローラ。プラグインが読み込まれ +[load] が呼び出されたら、メソッドの置換を行う。
static BOOL initialized_flag = NO;
+(void)load
{
if (!initialized_flag) {
initialized_flag = YES;
[SXSafariContextMenuSwizzler setup];
NSLog(@"+[PluginController load] was called");
}
}さて実行してみよう。右クリックでコンテキストメニューを表示させる。
出た。
"Say Hello" を選ぶ。

"Hello !" がログに出た。
これでユーザからの操作を受け取る入り口が一つ確保できた。
ソースコード:SafariPlugInStudy-1.zip
----------------
(参考情報)
下記で公開されている SIMBLプラグインのソースコードも参考になった。
[SIMBL]Safari のコンテキストメニューから項目を削除するプラグイン書いた - Yarukidenized:ヤルキデナイズド
それと GitHub が便利そうだ。ダウンロードせずにソースコードが読めるのがいい。このブログのソースコードもいつか移していきたい(その前に Git を習得しなければならないが)。

