一般的な原則は単純です。Objective-Cランタイムを使用すると、プログラム内の任意の場所から任意のクラスへのポインターを取得して、あらゆるクラスの不正行為を実行できます。 SIMBLの指示を参考にして、バンドルの開発を始めました。
タスクは次のとおりでした。Safariコンテキストメニューに項目を追加し、アクティブ化すると、カーソルの下の要素に対してXPathが選択されます。 F-Script Anywhereを使用すると、1分で、ターゲットブラウザーのWebUIDelegate(webView:contextMenuItemsForElement:defaultMenuItems:を含む)メソッドの実装がBrowserWebViewクラスを担当することがわかりました。 これは、DTRenameSelector関数を使用するために、その中のメソッドを置き換える必要があることを意味します。
BOOL DTRenameSelector ( Class _class, SEL _oldSelector, SEL _newSelector ) { Method method = nil ; // First, look for the methods method = class_getInstanceMethod ( _class, _oldSelector ) ; if ( method == nil ) return NO ; method - >method_name = _newSelector; return YES ; }
BOOL DTRenameSelector ( Class _class, SEL _oldSelector, SEL _newSelector ) { Method method = nil ; // First, look for the methods method = class_getInstanceMethod ( _class, _oldSelector ) ; if ( method == nil ) return NO ; method - >method_name = _newSelector; return YES ; }
BOOL DTRenameSelector ( Class _class, SEL _oldSelector, SEL _newSelector ) { Method method = nil ; // First, look for the methods method = class_getInstanceMethod ( _class, _oldSelector ) ; if ( method == nil ) return NO ; method - >method_name = _newSelector; return YES ; }
BOOL DTRenameSelector ( Class _class, SEL _oldSelector, SEL _newSelector ) { Method method = nil ; // First, look for the methods method = class_getInstanceMethod ( _class, _oldSelector ) ; if ( method == nil ) return NO ; method - >method_name = _newSelector; return YES ; }
BOOL DTRenameSelector ( Class _class, SEL _oldSelector, SEL _newSelector ) { Method method = nil ; // First, look for the methods method = class_getInstanceMethod ( _class, _oldSelector ) ; if ( method == nil ) return NO ; method - >method_name = _newSelector; return YES ; }
BOOL DTRenameSelector ( Class _class, SEL _oldSelector, SEL _newSelector ) { Method method = nil ; // First, look for the methods method = class_getInstanceMethod ( _class, _oldSelector ) ; if ( method == nil ) return NO ; method - >method_name = _newSelector; return YES ; }
BOOL DTRenameSelector ( Class _class, SEL _oldSelector, SEL _newSelector ) { Method method = nil ; // First, look for the methods method = class_getInstanceMethod ( _class, _oldSelector ) ; if ( method == nil ) return NO ; method - >method_name = _newSelector; return YES ; }
BOOL DTRenameSelector ( Class _class, SEL _oldSelector, SEL _newSelector ) { Method method = nil ; // First, look for the methods method = class_getInstanceMethod ( _class, _oldSelector ) ; if ( method == nil ) return NO ; method - >method_name = _newSelector; return YES ; }
BOOL DTRenameSelector ( Class _class, SEL _oldSelector, SEL _newSelector ) { Method method = nil ; // First, look for the methods method = class_getInstanceMethod ( _class, _oldSelector ) ; if ( method == nil ) return NO ; method - >method_name = _newSelector; return YES ; }
BOOL DTRenameSelector ( Class _class, SEL _oldSelector, SEL _newSelector ) { Method method = nil ; // First, look for the methods method = class_getInstanceMethod ( _class, _oldSelector ) ; if ( method == nil ) return NO ; method - >method_name = _newSelector; return YES ; }
これで、「フェイク」セレクターのスタブを作成できます。
- 静的 NSArray * webView_contextMenuItemsForElement_defaultMenuItems_ ( id self、 SEL _cmd、
- id * sender、 NSDictionary * element、 NSArray * defaultMenuItems )
- {
- 戻ります [自己
- _sxp_orig_webView :送信者
- contextMenuItemsForElement :要素
- defaultMenuItems : defaultMenuItems ] ;
- }
これはC関数であるため、メソッドの最初の2つの引数(selfおよび_cmd)は明示的に指定する必要があります。 内部では、関数は実際のセレクターを呼び出します。このセレクターは、ブート時に名前を変更するだけです。 Rantimeは、使用するクラスを初期化するときに+ loadメソッドを実行することを約束します。
- 静的 SXPPlugin * Plugin = nil ;
- @implementation SXPPlugin
- + ( SXPPlugin * ) sharedInstance
- {
- @synchronized ( self ) {
- if ( !プラグイン)
- プラグイン= [ [ SXPPlugin alloc ] init ] ;
- }
- return Plugin;
- }
- - ( void )スウィズル
- {
- クラス BrowserWebView = objc_getClass ( "BrowserWebView" ) ;
- if ( BrowserWebView ) {
- class_addMethod (
- BrowserWebView、
- @selector ( _sxp_fake_webView : contextMenuItemsForElement : defaultMenuItems :) 、
- ( IMP ) webView_contextMenuItemsForElement_defaultMenuItems_、
- "@@:@@@" ) ;
- DTRenameSelector (
- BrowserWebView、
- @selector ( webView : contextMenuItemsForElement : defaultMenuItems :) 、
- @selector ( _sxp_orig_webView : contextMenuItemsForElement : defaultMenuItems :) )) ;
- DTRenameSelector (
- BrowserWebView、
- @selector ( _sxp_fake_webView : contextMenuItemsForElement : defaultMenuItems :) 、
- @selector ( webView : contextMenuItemsForElement : defaultMenuItems :) )) ;
- } else {
- NSLog ( @ "BrowserWebViewクラスの取得に失敗しました" ) ;
- }
- }
- + ( void )ロード
- {
- SXPPlugin * plugin = [ SXPPlugin sharedInstance ] ;
- [プラグインスウィズル] ;
- }
- @end
ここで何が起こっていますか? バンドルがSafariにロードされるとすぐに、+ [SXPPlugin load]メソッドが機能します。これは、最初に自身の最初(および最後)のインスタンスを作成し、次にダミーセレクター(_sxp_fake)を追加してBrowserWebViewのメソッドを置き換えます現在の名前を(_sxp_orig)に変更してから、その場所にダミーをインストールします。 実際、ここの_sxp_origはSafari内のコードではなく、以前にダウンロードされた別の拡張機能-Speed Downloadを示しています。 また、SXPPluginの後も、1Passwordは同じ方法でロードされ、チェーンに押し込まれます(これをすべてアンロードする必要がある場合、最も重要なことは順序を維持することです)。 ちなみに、セレクターの説明の「@@:@@@」は、セレクターがid(最初の@)を返し、(id、SEL、id、id、id)を受け入れることを意味します。
以上で、この段階でバンドルを収集し、SIMBLにフックするか、InputManagerに直接フックすることができます(ただし、これはそれほど便利ではありません)。
機能を増やし続け、このメニューに独自の要素が必要です。
- - ( id ) init
- {
- if ( ( self = [ super init ] ) )) {
- //初期化メニュー
- NSMenu * m = [ [ NSMenu alloc ] initWithTitle : @ "XPath" ] ;
- NSMenuItem * mi = [ [ NSMenuItem alloc ]
- initWithTitle : @ "XPath for node"
- アクション: @selector ( onMenu :)
- keyEquivalent : @ "" ] ;
- [ mi setTarget : self ] ;
- [ m addItem : mi ] ;
- [ miリリース] ;
- mi = [ [ NSMenuItem alloc ]
- initWithTitle : @ "ブラウザを表示"
- アクション: @selector ( onMenuBrowser :)
- keyEquivalent : @ "" ] ;
- [ mi setTarget : self ] ;
- [ m addItem : mi ] ;
- [ miリリース] ;
- _myMenuItem = [ [ NSMenuItem alloc ]
- initWithTitle : @ "XPath"
- アクション:なし
- keyEquivalent : @ "" ] ;
- [ _myMenuItem setSubmenu : m ] ;
- [ mリリース] ;
- [ _myMenuItem setEnabled : YES ] ;
- }
- 自己を返す ;
- }
コンストラクターで、XPathメニュー項目を作成します。このメニュー項目には、ノードのXPathとShow browser要素を含むサブメニューがあります(両方の要素はメインクラスのアクションに関連付けられています)。
クラスの説明にメニューを追加します:
NSMenuItem *_myMenuItem;
プロパティを介してアクセス可能にします。
@property (readonly) NSMenuItem *myMenuItem;
。 これで、関数(BrowserWebViewのコンテキストで実行)からこのメニューにアクセスできます。
- 静的 NSArray * webView_contextMenuItemsForElement_defaultMenuItems_ ( id self、 SEL _cmd、
- id * sender、 NSDictionary * element、 NSArray * defaultMenuItems )
- {
- [ SXPPlugin sharedInstance ] .ctx = element;
- NSArray * itms = [ self
- _sxp_orig_webView :送信者
- contextMenuItemsForElement :要素
- defaultMenuItems : defaultMenuItems ] ;
- NSMutableArray * itms2 = [ NSMutableArray arrayWithArray : itms ] ;
- [ itms2 addObject : [ NSMenuItem separatorItem ] ] ;
- [ itms2 addObject : [ [ SXPPlugin sharedInstance ] myMenuItem ] ] ;
- itms2を返します。
- }
4行目に注意を払わないでください。 残りについては、すべてが明らかです。現在のメニュー項目のリストを取得し、そこにセパレータとメニューを追加して、上記に戻ります。
今、私たちはもっと面白いことを始めています。 はじめに-メインインターフェースが作成されるInterface Builderでのプログラミングの中断と数分間:
XPathは最上行に表示され、選択の結果は下のリストに表示されます。 メインクラスで必要なものすべて
[NSBundle loadNibNamed:@"XPathBrowser" owner:self];
し、initでxibロードを追加します。
[NSBundle loadNibNamed:@"XPathBrowser" owner:self];
。
さて、実際には、XPathを回転させています。 インターセプターのどこからスピンするかを知るために、辞書の「要素」を保存します。これには必要なすべての情報が含まれています。
- - ( void ) onMenu : ( id )送信者
- {
- [ウィンドウmakeKeyAndOrderFront : self ] ;
- NSString * xp = [ self xpathForNode : [ _ctx objectForKey : @ "WebElementDOMNode" ] ] ;
- [ xpathField setStringValue : xp ] ;
- [ self onEvaluate : self ] ;
- }
- - ( void ) onMenuBrowser : ( id )送信者
- {
- [ウィンドウmakeKeyAndOrderFront : self ] ;
- }
DOMNodeクラスのインスタンスは、クリックされたWebElementDOMNodeキーに関連付けられます。 XpathForNodeコード:ここでは説明しませんが、やや面倒です( gitで表示できるようにしたい人)。操作の原則は次のとおりです。親ノードを一番上に巻き戻します。 ノードにid属性がある場合、XPathに追加されます。親ノードに同じタイプのノードが複数ある場合(およびidがない場合)、ノードインデックスが使用されます。
しかし、XPathは計算するには十分ではありません。XPathを実行して結果を取得する必要があります。 これを行うには、NSXMLDocumentを使用して現在のフレームからデータを供給することができますが、これはJavaScriptからノードを取得する機能ほど興味深いものではありません。
- - ( void ) onEvaluate : ( id )送信者
- {
- NSString * xp = [ xpathField stringValue ] ;
- WebFrame * frame = [ _ctx objectForKey : @ "WebElementFrame" ] ;
- WebView * view = [ frame webView ] ;
- NSString * js = [ NSString
- stringWithFormat : @ "document.evaluate( \" %@ \ " 、document、null、XPathResult.ANY_TYPE、null)" 、xp ] ;
- id o = [ [ view windowScriptObject ] evaluateWebScript : js ] ;
- [ _nodes release ] ;
- _nodes = nil ;
- if ( ! [ [ o class ] isEqual : [ WebUndefined class ] ] ) {
- NSMutableArray * nodes = [ NSMutableArray array ] ;
- id n = [ o iterateNext ] ;
- while ( n ) {
- [ノードaddObject : [ self dictForNode : n ] ] ;
- n = [ o iterateNext ] ;
- }
- _nodes = [ノード保持] ;
- } ;
- [ outlineView reloadData ] ;
- }
当初、NSOutlineViewはDOM *オブジェクトを直接受け取りましたが、自己離脱ノードの複雑さのため(すべて明示的に保持する必要がありますか?)、ノード配列でツリーを再構築します。
ブラウザにアクセスできる場合、JSの潜在的な挿入を無視します。フレームでJSを実行する方がはるかに簡単です。 そして、引用文字はXPathではまったく有効ではないようです。
-dictForNode:メソッドはgitでも見ることができます。 そのタスクは、アウトラインビューがリストを作成するNSDictionary / NSArray構造のセットにDOMツリーを展開することです。
最終結果:
また、Pythonで同じことを記述しようとした代替プロジェクトもあります(ただし、XPathはlibxml / libxslt-lxmlの素晴らしいバインダーを作成しましたが)。 しかし、完全に機能するブラウザの欠如は依然として不便であることが判明しました。
ソースコード(MIT): http : //git.hackndev.com/?p=farcaller / safarixpath.git ; a=summary
パッチ、バグ修正、アイデアを歓迎します。
更新 :ヒントをありがとう、「Mac OS XおよびiPhone向けの開発」に移動しました
更新 :ワーカーのリクエストにより、DOMツリーとソースHTMLドキュメントの両方のXPath選択のサポートが追加されました。 エンコードが混乱する可能性があり、HTMLモードに該当する場合は明らかな変換があります-準備のために元のファイルを実際に要求します(utf8およびcp1251で動作するようです)。 NSXMLDocumentのほかに、いくつかのページで何かが完全に壊れているだけでなく、問題は調査中です。
DOMパーサーを修正すると同時に、関数(カウント、名前など)をサポートするようになりました。 NSXMLDocumentはこれを行うことができません。
最新のSafari用のSIMBLバンドル: http ://farcaller.net/stuff/SafariXPath.bundle-1.1.zip