AutoPagerizeもwindow共有不可の問題にはやっぱり影響受けてて、そのあたりちょっと考えてみましたよ
社長が訊く
任天堂のサイトにあるゲーム製作の舞台裏紹介ページ「社長が訊く」。現行のAutoPagerizeだとscriptタグで追加されるムービーへのリンクが変なことになるので、それをどうにかするスクリプトを書いた。
おそらくgreasemonkey版のAutoPagerizeでないと動かないので悪しからず。しかも以下に示す対策も必要。
AutoPagerizeにもsharedObjectを処方
ユーザースクリプト間のwindowの共有が問題となって、minibufferやLDRizeの動作に差し障りが出るのは既出の通り。対策もまた既出。他にもReblogCommandのような双方に依存するスクリプトがあれば、記事(id:aiwendi:20111115:1321338264) tでリブログ復活 - aiwendilの日記 のようにすれば解決する。
ということで、sharedObjectが用意できている環境ならば、windowの共有自体は下のようにすれば済む。行のオフセットが5000とかやたらアホみたいな数値になっているのは、ローカルにごりごり書いたSITEINFOの分なのでお気になさらぬよう。
@@ -5255,8 +5002,6 @@ if (typeof(window.AutoPagerize) == 'undefined') { } window.AutoPagerize.launchAutoPager = launchAutoPager + sharedObject.AutoPagerize = window.AutoPagerize + var ev = document.createEvent('Event') ev.initEvent('GM_AutoPagerizeLoaded', true, false) document.dispatchEvent(ev)
window.AutoPagerizeを全部sharedObject.AutoPagerizeに書き換えなくても、AutoPagerize側の準備が出来た時点(つまり、GM_AutoPagerizeLoadedイベントの発火直前)で、sharedObjectにもコピーを作ってやればいい、というわけ。
AutoPagerizeのAPI(自分用メモを兼ねて)
で、AutoPagerizeとスクリプトの連携に話は移るわけだが、今回書いたスクリプトはAutoPagerizeのAPI(http://autopagerize.jottit.com/apis_(ja))を利用している。
APIs
Filters
別のGreasemonkey Scriptから、AutoPagerizeの動作の途中に処理を挟むことができる。
Filter 実行タイミング 引数 用途 addDocumentFilter 次のページを読み込んで Document 作成した完了直後で、 Document からpageElement を切り出す前であり、「さらに次のページ」の URL 取得前 htmlDoc ( GM_xmlhttpRequest で取得したソースを元に作成した Document オブジェクト), this.requestURL (次のページとして取得した URL ), this.info (使用している SITEINFO ) nextLink, pageElement の取得の補助、 SITEINFO の動的な変更など addFilter 次のページの pageElement を挿入後 pageElement ( htmlDoc から getElementsByXPath ( this.info.pageElement , htmlDoc で切り出した) 要素の配列。要素自身だと間違われやすいがあくまで配列) 継ぎ足しした部分を他の Script から操作したい場合など addRequestFilter 次ページ取得のリクエストを送信する直前 GM_XHR用リクエストオプション リクエストのURLやオプションを変更する addResponseFilter 次ページ取得のレスポンス取得時 レスポンス、URL レスポンスの内容を変更する launchAutoPager
AutoPagerizeにsitoinfoを渡して実行させることができる。
http://autopagerize.jottit.com/apis_(ja)
引数はsitoinfoを格納した配列。
window.AutoPagerize下にあるAPIなので、現状、どうしてもwindowの共有が必須になる。今回スクリプトを書くのに参考にしたhttp://userscripts.org/scripts/show/48753にしても、sharedObject.AutoPagerizeを使うように書き換えて動かしているので。
まあ、windowの共有の代替策が見つかったところで、実のところまだ問題は残っているが。
イベントの抜け道
windowの共有の次は、スクリプト間のイベント伝播だ。GM_AutoPagerizeLoaded、GM_MinibufferLoadedのような専用イベントを用意しても、スクリプト間では伝播されないようになったので(同じスクリプト内ならイベントも伝播する。MinibufferとLDRizeを同じスクリプトにつっこんだら動く、というのもそういうことだ)、どうにかしてこれを伝播可能にしたい。
似たような事例というのは案外転がっているもので、ググっていたらこういうの(http://wp.serpere.info/archives/1107)に行き当たった。
XPCNativeWrapperの外側でスクリプトを評価する関数。Firebugも同じことをしている。unsafeWindowを触らないので安全。
function evalInPage(fun) {location.href = "javascript:void (" + fun + ")()";
}
これは面白い!Function.prototype.toString が関数のソースコードを返すことを利用した、巧妙にして簡潔なハック。
次のように引数を渡せるように改良すればさらに強力になる(引数に渡せるのはJSON化可能な値のみ)。
function evalInPage(func, args) {var argStr = JSON.stringify(args || []);
location.href = "javascript:void "+ func +".apply(null,"+ argStr +")";
}
http://wp.serpere.info/archives/1107
GM_xmlhttpRequest を使って別ドメインから得たデータを、unsafeWindow 上のライブラリを使って表示する、といったコードが安全かつ自然に書けるようになる。
つまり、スクリプトから実行してうまくいかないんなら、ページ側で実行してもらいましょう、という話。今回のスクリプトにしても、元ページの初期化関数にタダ乗りするようにしたのは、このページを見たおかげ。
課題とか
こいつを上手くMinibufferあたりに組み込んでやると、イベントの発火は可能になる。ページ側での実行になるのでLDRize等の他のスクリプトからイベントを参照することも可能だ、というところまでは確認した。イベントの授受は元々非同期前提の動作なので、そのへんも問題ない。
残る問題はAutoPagerizeのほう。普通に動かすだけなら今でも問題ないが、イベント伝播も元通りにしようとするといささか困った点が出てくる。GM_AutoPagerizeLoadedとGM_AutoPagerizeNextPageLoadedのふたつのイベントはごく普通のイベントなので問題はない。
今回APIを使ったのもそのあたりが原因で、MutationイベントであるAutoPagerize_DOMNodeInsertedをページ側で実行してもらおうとしたとき、発火対象ノードを一意に定める必要が出てくる。このイベントは実質AutoPagerizeのAPIみたいなもんで、これに対してaddEventListnerを設定することで新しく追加されたノードをいじれて、非常に使いやすい。
ノードそのものはJSON化不可なので、引数にしたところで意味がない。思いつく範囲では、ページ側に渡すスクリプトに、セレクタを利用するquerySelectorや、XPathを使うevaluateなどを用いたノード特定用の部品を組み込めばいいのだろうが……。少なくとも、テキストノードを選択することがうまく出来ないquerySelectorのほうは、選択肢から除外して良さそうだ。
書いてるうちに思いついたこと(暫定解決策)
pageElementがそもそも追加ノード、追加予定ノードの情報を全部持ってるんだから、こいつを上手くバラしてやればいいんだ、ということで、多分ページ側でAutoPagerizeの持つ全イベントのエミュレートは可能、だと思う。
おそらく、
pageElement: '//hoge/fuga|//hage/piyo',
に対して、
pageElementN: '(//hoge/fuga|//hage/piyo)[N]',
という感じでN番目のノードを指定してやれば、うまいこといくはず。
ところでウチのページに引用した箇所の見え方がひどい。CSSいじくったほうがいいのかね……。