(前回)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; @end
SXSafariContextMenuSwizzler.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 を習得しなければならないが)。