Androidに失望した話

Nexus One用にBookmarkletを書いてみようと思ったのですが、、、

そもそもAndroidの標準ブラウザはjavascript:で始まるブックマークを登録することができません。

それでもアドレスバーに手打ちでjavascript:...と書くと動作するのでコピペで何とかなるかと思ったのですが、実際にはjavascriptスキームによるJSの実行に制限をかけている(?)ようでまともに使えそうにありません。

例えば、javascript:alert("a")ならアラートが表示されるし、javascript:alert(document)やjavascript:alert(document.createElement)でオブジェクトがあること、関数があることは確認できます。
ところがjavascript:alert(document.createElement("a"))とメソッドを実行してみるとスクリプトの結果が帰ってきません。

一体何のための制限なのか全く理解できません。iPhoneより自由なのが売りじゃなかったんですかね。買うんじゃなかったなあと思っています。

Scala 2.8 beta1でのアノテーション

最近Google App EngineScalaで書いたものを動かしてみようとしています。

JDOを試してみようと、こちらページ(http://hatenatunnel.appspot.com/hidemon/20100110/1263124777)などのサンプルをコピペで動かしてみたところ、Scalaコンパイラがどうもアノテーションの部分でエラーを吐きます。

   [scalac] XXX.scala:5: error: expected start of definition
   [scalac] @PersistenceCapable{val identityType = IdentityType.APPLICATION}
   [scalac]                    ^

アノテーション部分をJavaと同じように書き換えてみたら動きました。

- @PersistenceCapable{val identityType = IdentityType.APPLICATION}
+ @PersistenceCapable(identityType = IdentityType.APPLICATION)

Scala 2.8での変更っぽいですが、特にソースが見つけられずよく分かりませんでした。JavaScalaもあまり分かっていないので的外れなことを書いているかもしれません。

subscldr.jsが動かなくなったのを直してみた

Firefox3.6にしてからかどうかは定かではないのですが(他に原因は思いつきませんが)、自分の環境ではsubscldr.jsが動かなくなっていました。

liberator.echoを入れまくってデバッグしたところ、XHRで取得したDocumentに対するXPathがなぜかマッチしないことが原因なようでした。XHTML用のNSResolverを作り、XPath式にxhtml prefixをつけてやると動くようになりました。

どうして元々のコードでマッチしなくなったのかは分かりません。他の方がsubscldr.jsが動かなくなったという話も見かけないので、何か自分の環境がおかしいのかもしれません。

(追記)liblyのXPath機能を使う他のプラグインは問題なく動いているので、Livedoorが渡してくるコンテンツが変わったのかもしれません。以前と比較するのが難しいですが。

とりあえずパッチ

私はCodeReposのアカウントは持っていないので、もしこれが他の方にも役に立つ修正だったらご自由に取り込んでください。

@@ -195,11 +195,11 @@ liberator.plugins.subscldr = (function() {
        feedlinks: []
     };
 
-    $LXs('id("feed_candidates")/li', htmldoc).forEach( function(item) {
-      var feedlink = $LX('./a[@class="feedlink"]', item);
-      var title = $LX('./a[@class="subscribe_list"]', item);
-      var users = $LX('./span[@class="subscriber_count"]/a', item);
-      var yet = $LX('./input[@name="feedlink"]', item);
+    $LXs('id("feed_candidates")/xhtml:li', htmldoc).forEach( function(item) {
+      var feedlink = $LX('./xhtml:a[@class="feedlink"]', item);
+      var title = $LX('./xhtml:a[@class="subscribe_list"]', item);
+      var users = $LX('./xhtml:span[@class="subscriber_count"]/xhtml:a', item);
+      var yet = $LX('./xhtml:input[@name="feedlink"]', item);
       liberator.log("input:" + feedlink.href);
       subscribeInfo.feedlinks.push([feedlink.href, (yet != null), (title ? title.textContent : '' ) + ' / ' + (users ? users.textContent :  '0 user')]);
     });
@@ -235,8 +235,27 @@ liberator.plugins.subscldr = (function() {
   }
 
   // For convenience
-  function $LXs(a,b) libly.$U.getNodesFromXPath(a,b);
-  function $LX(a,b)  libly.$U.getFirstNodeFromXPath(a,b);
+  //function $LXs(a,b) libly.$U.getNodesFromXPath(a,b);
+  //function $LX(a,b)  libly.$U.getFirstNodeFromXPath(a,b);
+
+  function nsResolver(prefix) {
+    var ns = { 'xhtml': 'http://www.w3.org/1999/xhtml' };
+    return ns[prefix] || null;
+  }
+
+  function $LXs(a, b) {
+    var ret = [];
+    var res = (b.ownerDocument || b).evaluate(a, b, nsResolver, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    for (var i = 0; i < res.snapshotLength; i++) {
+      ret.push(res.snapshotItem(i));
+    }
+    return ret;
+  }
+
+  function $LX(a, b) {
+    var res = (b.ownerDocument || b).evaluate(a, b, nsResolver, XPathResult.FIRST_ORDERED_NODE_TYPE, null);
+    return res.singleNodeValue || null;
+  }
 
   // }}}
   return PUBLICS;

似たようなリンクをまとめて開くプラグインを作ってみた

リスト化されたリンクをまとめて開きたいとか、文書の目次から同一階層へのリンクをまとめて開きたいとか、画像まとめページから各画像をいっぺんに開きたいことってよくありますよね。

そういうリンクはHTML的に同じ構造になっているという仮定の元、ヒントモードで指定したリンクと同じ構造のリンクを探して一度に開くというプラグインを書いてみました。ソースコードは記事の末尾にあります。

仕組み

指定された要素をEとした時、E.parentNodeをコンテキストノードとしてXPath式"E.tagName"を評価します。結果がE自身のみであった場合、E.parentNode.parentNodeをコンテキストノードとして、XPath式"E.parentNode.tagName"+"/"+"E.tagName"を評価…というの繰り返し、結果にE以外の要素が含まれていたらそこで終わり、その各要素のリンク先をまとめて開きます。単純な仕組みですが、案外うまく動作しています。

<ul>
  <li><a>AAA</a></li>
  <li><a>BBB</a></li>
  <li><a>CCC</a></li>
</ul>

上の例では3つのリンクのうちどれを選択してもAAA, BBB, CCCの3つを開きます。

<div>
  <h3><a>Title A</a></h3>
  <p>...</p>
</div>
<div>
  <h3><a>Title B</a></h3>
  <p>...</p>
</div>

上の例ではどちらのリンクを選択しても、Title A, Bの両方を開けます。

<ul>
  <li><a>AAA</a></li>
  <li><a>BBB</a></li>
</ul>
<ul>
  <li><a>CCC</a></li>
</ul>

現状の実装では上の例で3つとも開きたかったとしても、AAAを選択した場合AAAとBBBが見つかった時点で終了してしまうのでCCCを含めて開くことはできません。ですが、選択したのがCCCだったら3つが開くことになります。対象ページのHTML構造を知らない限りは意識して選択することはできませんが。

こんな実装ですが、例えばGoogleの検索結果ページで1つの見出しを選択すると全ての検索結果をまとめて開くことができます。

ソース

// Vimperator plugin: Open Similar Links

/* Options
 *   g:open_similar_links_map (Default: "S")
 *      Key mapping for open similar links hint mode
 *   g:open_similar_links_hinttags (Default: "//a[@href]")
 *      XPath expression for open similar links
 *   g:open_similar_links_open_limit (Default: 20)
 *      If detect number of tabs over this value, confirm before opening links
 *   g:open_similar_links_open_mode (Default: 1)
 *      1: open links by native window.open
 *      2: open links by liberator.open(URLS, liberator.NEW_BACKGROUND_TAB)
 */

(function(){
    var open_similar_links_map = liberator.globalVariables.open_similar_links_map || "S";
    var open_similar_links_hinttags = liberator.globalVariables.open_similar_links_hinttags || "//a[@href]";
    var open_similar_links_open_limit = liberator.globalVariables.open_similar_links_open_limit || 20;
    var open_similar_links_open_mode = liberator.globalVariables.open_similar_links_open_mode || 1;

    var doc = null;

    function evalXPath(expr, context) {
        return doc.evaluate(expr, context, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    }

    function openLinks(res) {
        if (!res.snapshotLength) return;

        var urls = [];
        for (var i = 0; i < res.snapshotLength; i++) {
            var elem = res.snapshotItem(i);
            if (elem.href && elem.href.length >= 1) {
                urls.push(elem.href);
            }
        }
        switch (open_similar_links_open_mode) {
        case 2:
            liberator.open(urls.join(", "), liberator.NEW_BACKGROUND_TAB);
            break;
        case 1:
        default:
            var win = content.window;
            var tab_index = tabs.index();
            urls.forEach(function(url) {
                win.open(url);
            });
            tabs.select(tab_index);
            break;
        }
    }

    function search(elem, prev_expr, count) {
        var expr = elem.tagName + prev_expr;
        var res = evalXPath(expr, elem.parentNode);

        if (res.snapshotLength <= 1) {
            if (doc.body != elem.parentNode) {
                search(elem.parentNode, "/" + expr, ++count);
            } else {
                liberator.echo("Not found similar links");
                openLinks(res);
            }
        } else {
            if (res.snapshotLength >= open_similar_links_open_limit) {
                commandline.input("Found " + res.snapshotLength + " links, really open? [y/N]",
                    function (ans) {
                        if (ans.toLowerCase().indexOf("y") == 0) openLinks(res);
                    }, null
                );
            } else {
                liberator.echo("Found " + res.snapshotLength + " links");
                openLinks(res);
            }
        }
    }

    function startSearch(elem) {
        doc = elem.ownerDocument;
        search(elem, "", 0);
    }

    hints.addMode(open_similar_links_map, "Open Similar Links",
        function(elem) {
            startSearch(elem);
        },
        function() { return open_similar_links_hinttags;}
    );
})();

Weaveのステータスバー表示を消す

Weaveを試しにインストールしてみたところ、ステータスバーにアカウント名が表示されるのですが、他人に画面を見られた時にアカウント名を知られてしまうのはどうなのよということで消してみました。userChrome.cssに下記を記述。

statusbarpanel#sync-menu-button { display: none; }

LDRでピンをバックグラウンドのタブで開くためのVimperatorスクリプト

今まではLDRですべての外部リンクをバックグラウンドタブで開くUserScripts - os0x.blogGreasemonkeyスクリプトを使っていたのですが、更新しないようにしていたGreasemonkey本体を間違えて更新してしまったところ動かなくなってしまいました。せっかくなのでVimperatorスクリプトで書き直してみたところ、こんな感じになりました。

js <<EOM
var dummy_open = function(url, name){
    liberator.open(url, liberator.NEW_BACKGROUND_TAB);
    return true;
}
EOM
autocmd LocationChange 'reader\.livedoor\.com' :js content.window.open = dummy_open;

Cilを試してみた

CilというコマンドラインベースのIssue Trackerがあります。

http://kapiti.geek.nz/software/cil.html

cil is a (Distributed) Issue Tracking System backed by your VCS.

ということで、Git等のVCSとの連携を前提としたツールのようで、興味を持ったので試してみました。

Ubuntu 9.04ではaptでインストールできます。(apt-get install cil)

使い方など

プロジェクトのトップディレクトリ等でcil initコマンドを実行すると、.cilファイルとissuesディレクトリが作成されます。
.cilにはそのプロジェクトにおけるステータスや、Issueに設定できるラベルの定義を設定します。issuesディレクトリには登録されたIssueが保存されます。

cil addコマンドを実行するとエディタが立ち上がり、Issueを新規作成します。作成されたIssueにはGitのようにハッシュ値の識別子が割り当てられ、1つのIssueにつき1つのテキストファイルとしてissuesディレクトリに保存されます。

Issueの編集、ステータス変更などはedit, status等のコマンドがあるのでcil --helpを参照してください。

1つのIssueが1つのテキストファイルになっているため、issuesディレクトリをVCSで管理すればソースコードと一緒にIssue管理ができるというアプローチになっています。

わざわざ大がかりなシステムを用意する必要がなく、ソースツリーと一緒に管理してしまえるので、個人レベルのちょっとした開発ととても相性が良いのではないかと思っています。

Gitで使ってみる

Gitで管理しているプロジェクトにCilによるIssue管理を適用する場合、masterブランチでCilの管理をして、Issueに対応したトピックブランチで実際の作業をする流れが良さそうです。

  • masterブランチでIssueを作成してコミット
  • Issueに対応したトピックブランチを作成
  • トピックブランチで作業をしてmerge
  • masterブランチでIssueをClose

こうすることで、masterブランチを見れば常に最新のIssue状況が確認できるという運用が可能です。

ちょっと修正

Cilをインストールしてそのまま利用すると1つ問題があります。Issueの作成時に登録日時や更新日時が自動的に保存されるのですが、この日時のタイムゾーンがなぜかGMTに固定されています。

インストールしたディレクトリ(Ubuntu 9.04のaptで入れると/usr/share/perl5)のCIL/Base.pmの181行目と189行目のDatetime->nowしているところを修正するといいでしょう。

修正前

    my $time = DateTime->now->iso8601;

修正後

    my $time = DateTime->now( time_zone => 'local' )->iso8601;