ldr で購読

:ldr [url] で購読。url 省略時は現在のタブ。

(function () {
  liberator.commands.addUserCommand(['ldr'], 'Subscribe URL with livedoor Reader',
    function(arg, special) {
      var url = (arg) ? arg : window._content.top.location;
      window.loadURI('http://reader.livedoor.com/subscribe/' + url);
    },
    {}
  );
})();

あ、見ればわかるとおり、バックグラウンドで動いたりせず、遷移します。

Adium の負荷状況を見て再起動する Bash Script

Adium で Twitter などのサービスを IM で受け続けていると、やがて CPU 食いまくって kill しない限り固まる現象が頻発する。
これがウザいなあとおもったので簡単な Shell Script 書いてみた。


なんちゃって daemon がこんな感じ。ファイル名は supervisediumd にした。
1秒間隔で監視して CPU 使用率 75% 超えた状態が、10回続けば再起動する。

#!/bin/sh

# settings
COUNT_LIMIT=10
CPU_LIMIT=750
APP="/Applications/Adium.app"

PIDFILE=$1
if [ -f $PIDFILE ] ; then
  echo "supervisediumd is already running"
  exit 0
fi
trap "rm -rf $PIDFILE; echo 'stopped supervisediumd'" EXIT
echo $$ > $PIDFILE
echo "start supervising adium"

COUNT=0
while [ 1 ] ; do
  PSINFO=`ps ux | grep $APP | grep -v grep`
  if [ -z "$PSINFO" ] ; then
    open -a $APP
  fi
  PID=`echo $PSINFO | awk '{print $2}'`
  CPU=`echo $PSINFO | awk '{print $3 * 10}'`
  if [ $CPU -gt $CPU_LIMIT ] ; then
    COUNT=`expr $COUNT + 1`
    if [ $COUNT -gt $COUNT_LIMIT ] ; then
      kill $PID
      open -a $APP
      COUNT=0
    fi
  else
    COUNT=0
  fi
  sleep 1
done

操作用のコマンドはこんな感じ。ファイル名は supervisedium にしてる。

#!/bin/sh

PIDFILE="/tmp/supervisediumd.`whoami`"
COMMAND="supervisediumd"

case $1 in
  "clean" )
    echo "cleaning pidfile: $PIDFILE"
    rm -rf $PIDFILE
    ;;
  "start" | "" )
    if [ -f $PIDFILE ] ; then
      echo "supervisediumd is already running"
      exit 0
    else
      $COMMAND $PIDFILE &
      echo "starting supervisediumd $!"
    fi
    ;;
  "stop" | "shutdown" )
    echo "stopping supervisediumd..."
    if [ -f $PIDFILE ] ; then
      kill `cat $PIDFILE`
    fi
    ;;
  "restart" )
    echo "restarting supervisediumd..."
    if [ -f $PIDFILE ] ; then
      kill `cat $PIDFILE`
    fi
    while [ -f $PIDFILE ] ; do
      sleep 1
    done
    $COMMAND $PIDFILE &
    ;;
  * )
    cat <<_EOT_
usage:
$0 [start|stop|shutdown|clean|restart]
  start :
    start supervisediumd
  stop, shutdown :
    shutdown supervisediumd
  clean :
    cleaning pidfile
  restart :
    restart supervisediumd
_EOT_
    ;;
esac

で、両方 PATH 通ってるところに置いて、 supervisedium start すれば OK 。
引数無しの場合は start するようにしているので、ログイン項目に登録しておいたら、起動時に勝手に監視はじめてくれるので便利。
閾値の類は、適当に弄って好みにあわせればいいとおもいます。

いまどきのイベントハンドリングは遅いのかどうか問題

id:HolyGrail 周りで盛り上がってたので、ちょっと調べてみた。
DOMContentLoaded イベント使ったら、計測どころを何処にしていいかわからないので、それ以外の部分で。

昔ながらのやりかた。

<html lang="ja">
<head>
<title>Test</title>
</head>
<body>
<script>
var start = new Date();
</script>
<div>
<ol>
  <li><a class="events" onclick="javascript:(function(){alert(1)})()">click!</a></li>
</ol>
</div>
<script>
var end = new Date();
alert(end.getTime() - start.getTime());
</script>
</body>
</html>

いまどきのやりかた。

<html lang="ja">
<head>
<title>Test</title>
<script>
var $C = (function() {
  if (document.getElementsByClassName) 
    return function(classname,tag,node) {
      if (node == null) node = document;
      if(tag == null) tag = '*';
      return document.getElementsByClassName(classname,tag,node);
    }
  else 
    return function(classname,tag,node) {
      if (node == null) node = document;
      if(tag == null) tag = '*';
      var elems = node.getElementsByTagName(tag);
      var pattern = new RegExp('(?:^|\\s)' + name + '(?:\\s|$)');
      var found = [];
      for(var i = 0, length = elems.length; i < length; i++)
        if(pattern.test(elems[i].className)) found.push(elems[i]);
      return found;
    }
})();
var addEvent = (function() {
  if(document.addEventListener)
    return function(elem,type,fn,useCapture) {
      elem.addEventListener(type,fn,useCapture);
    }
  else if (document.attachEvent)
    return function(elem,type,fn,useCapture) {
      elem.attachEvent('on' + type);
    }
  else
    return function(elem,type,fn,useCapture) {
      elem['on' + type] = fn;
    }
})();
</script>
</head>
<body>
<script>
var start = new Date();
</script>
<div>
<ol>
  <li><a class="events" >click!</a></li>
</ol>
</div>
<script>
var elems = $C('events');
for (var i = 0,length = elems.length; i < length; i++)
  addEvent(elems[i],'click',function(){alert(1)},false);
var end = new Date();
alert(end.getTime() - start.getTime());
</script>
</body>
</html>

結果

li 要素を 400 にして試してみた。環境は、MacBook (CPU:Core Duo 2GHz, MEM: 2GB)

ブラウザ 昔ながらのやりかた いまどきのやりかた
Firefox3B5 10ms 32ms
Safari3.1.1 9ms 8ms
Opera9.27 20-50ms 40-80ms

結論

体感では違いがわからない程度。Safari はむしろいまどきのほうが早い傾向がある。Safari すげー。


まあ、特殊な事情が無い限り、いまどきのやりかたのほうがいろいろと便利なのでいいんじゃないすかね。
jQuery とか YUI なんか使うと、イベントバブル辺りもよしなにやってくれるし、便利便利。逆に初心者は積極的にライブラリ使った方がいいと思う。


補足

10000 でも試してみた

ブラウザ 昔ながらのやりかた いまどきのやりかた
Firefox3B5 2000-3500ms 1900-2400ms
Safari3.1.1 180-200ms 200-210ms
Opera9.27 650-710ms 1300-1500ms

だいぶ結果が変わった。
当り前だけど、完全に実装によるよね。
一概に速いとか遅いとか言えないので、その辺、特定の環境を根拠にどうこう言うのはおかしいよな。


はっきり言えるのは、 Safari3 素晴らしいと言うことぐらいですね。

LDR Full Feed が 時々動かなくなる件

w.Keybind オブジェクトができる前に走るから。


ちょっと、自分が適当に混ぜ込んだ Site Info とかも混じってるけど以下、 patch 。Keybind オブジェクトができるまで、 setTimeout まわるようにした。

--- ldrfullfeed.user.js.200802119	2008-02-19 23:48:09.000000000 +0900
+++ ldrfullfeed.user.js	2008-02-19 23:44:26.000000000 +0900
@@ -30,6 +30,16 @@
 
 var SITE_INFO = [
     {
+      url:    'http://today-yuuri.cocolog-nifty.com/yuuri/',
+      xpath:  '//div[@class="entry-body-text"]',
+      enc:    'UTF-8',
+    },
+    {
+      url:    'http://cross-breed.com/ura/',
+      xpath:  '//div[@class="contentarea"]',
+      enc:    'UTF-8',
+    },
+    {
       url:    'http://(rssblog.ameba.jp|ameblo.jp)',
       xpath:  '//div[@class="subContents"]',
       base:   'http://ameblo.jp',
@@ -146,6 +156,11 @@
       xpath:  '//div[(@class="main") or (@class="mainmore") or (@id="comment")]',
       enc:    'EUC-JP',
     },
+    {
+      url:    'http://blog.goo.ne.jp',
+      xpath:  '//div[(@class="entry-body-text")]',
+      enc:    'EUC-JP',
+    },
 ];
 
 // == [Application] =================================================
@@ -313,10 +328,17 @@
 if(LOADING_MOTION){
   addStyle(CSS, 'gm_fulfeed');
 }
-w.Keybind.add(KEY, function(){
-  launchFullFeed(SITE_INFO);
-});
 
+var timer = setTimeout(function() {
+  if(timer) clearTimeout(timer);
+  if (typeof w.Keybind != 'undefined' ) {
+    w.Keybind.add(KEY, function(){
+      launchFullFeed(SITE_INFO);
+    });
+  } else {
+    timer = setTimeout(arguments.callee,100);
+  }
+});
 // == [Utility] =====================================================
 
 function relativeToAbsolutePath (text, link){

DOMNodeInsertedIntoDocument イベントのクロスブラウザ対応試作

Firefox や、 Opera で DOMNodeInsertedIntoDocument イベントがあまりイケてなかったので、比較的マシな DOMNodeInserted イベント使って、似たような動きになるようなモノを作ってみた。


Firefox 2.0.0.12 と Opera 9.25 と Safari 3.0.4 で動作確認してます。
IE は、今、手元に環境が無いから試してないけど多分動くんじゃないかな。動けばいいな。

var InsertedIntoDocument = function(){};
(function(){
  
/*************************/
  /**
   * Original Source :
   * jQuery (jquery.com)
   */
  var ua = navigator.userAgent.toLowerCase();
  var browser = {
  	version: (ua.match(/.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/) || [])[1],
  	safari: /webkit/.test(ua),
  	opera: /opera/.test(ua),
  	msie: /msie/.test(ua) && !/opera/.test(ua),
  	mozilla: /mozilla/.test(ua) && !/(compatible|webkit)/.test(ua)
  };
/*************************/

  this.uniqid = function(prefix, table) {
    var id = prefix + ((new Date()).getTime() + Math.random());
    return (typeof table[id] != 'undefined') ? arguments.callee(prefix, table) : id;
  }

  if (!browser.msie && !browser.safari) {
    this.handlers = {};
    document.addEventListener('DOMNodeInserted',function(evt) {
      var handler =  InsertedIntoDocument.handlers[evt.target.__handler_id__];
      if (typeof handler != 'object') return;
/* target 周りが上手く行かないからとりあえず DOMNodeInserted を渡す。
      var e = document.createEvent("MutationEvents");
      e.initMutationEvent(
        "DOMNodeInsertedIntoDocument",
        evt.cancelBubble,
        evt.cancelable,
        undefined,
        evt.prevValue,
        evt.newValue,
        evt.attrName,
        evt.attrChange
      );
 */
      for (var i in handler) handler[i].call(evt.target,evt);
    },true);
  }

  this.add = (function() {
    // for Internet Explorer
    /* @cc_on
    return function(element, fn, useCapture) {
      target.attachEvent('onreadystatechange',fn);
    }
    @*/
    if(browser.safari) {
      return function(element, fn, useCapture) {
        var type =  (browser.version < 3)
            ? 'readystatechange' : 'DOMNodeInsertedIntoDocument';
        element.addEventListener(type, fn, useCapture);
      }
    } else if(!browser.msie) {
      return function(element, fn, useCapture) {
        if (typeof element.__handler_id__ == 'undefined')  
          element.__handler_id__ =  this.uniqid('handler_', this.handlers);
        var id = element.__handler_id__;
        if (typeof this.handlers[id] != 'object') this.handlers[id] = {};
        var listener_id = this.uniqid('listener_', this.handlers[id])
        fn.listener_id = listener_id;
        this.handlers[id][listener_id] = fn;
      }
    }
  })();
  this.remove = (function() {
    // for Internet Explorer
    /* @cc_on
      return function(element, fn, useCapture) {
        target.detachEvent('onreadystatechange',fn);
      }
    @*/
    if(browser.safari) {
      return function(element, fn, useCapture) {
        var type =  (browser.version < 3)
            ? 'readystatechange' : 'DOMNodeInsertedIntoDocument';
        element.removeEventListener(type, fn, useCapture);
      }
    } else if(!browser.msie) {
      return function(element, fn, useCapture) {
        delete this.handlers[element.__handler_id__][fn.listener_id];
        if (this.countProperty(this.handlers[element.__handler_id__]) == 0) 
          delete this.handlers[element.__handler_id__];
        delete fn.listener_id;
        delete element.__handler_id__;
      }
    }
  })();
  this.countProperty = function(obj) {
    var count = 0;
    for(var i in obj) count++;
    return count;
  }
  return this;
}).call(InsertedIntoDocument);

使い方はこんな感じ。

var e1 = document.createElement('div');
e1.id = "e1";
InsertedIntoDocument.add(e1,function(evt){
  console.log("DOMNodeInsertedIntoDocument !");
  console.dir(evt);
},true);
setTimeout(function() {
  document.body.appendChild(e1);
  console.log('added');
},1000);

イベントハンドラやリスナの管理とかするために、DOM オブジェクトや Function オブジェクトにそれぞれの ID 振ったりしてるのがあまり綺麗じゃないので、いい方法思いついたらなんとかしたい。
あと、ちゃんと DOMInsertedIntoDocument イベントをリスナに渡せるようにしたい。

せつなくてまぶしくて

UTF-8 で。

// ==UserScript==
// @name           Twitter Append Setunakutemabushikute
// @namespace      http://d.hatena.ne/jp/send/
// @include        http://twitter.com/home
// ==/UserScript==
/**
 * Original Source: 
 * http://userscripts.org/scripts/show/9086
 */
(function() {
  var onclick_orig;

  var w = (typeof unsafeWindow == 'undefined') ? window : unsafeWindow;
  var submit = w.document.getElementsByClassName('update-button')[0];
  var onclick = function() {
	  document.getElementById('status').value += 'せつなくてまぶしくて';
	
	  submit.onclick = onclick_orig;
	  submit.click();
	  submit.onclick = onclick;
    
    return false;
  };
  
  onclick_orig = submit.onclick;
  submit.onclick = onclick;
})();

AutoPagerize 対応の filter 管理GM

いちいち個別の filter スクリプトAutoPagerize 対応入れるのもなんかなと思ったから、ちょっと書いてみた。
こういうの userscripts.org に上げた方がいいのかな?

// ==UserScript==
// @name           Filterize
// @namespace      http://d.hatena.ne.jp/send/
// @description    apply filter on any site.
// @include        *
// ==/UserScript==
(function () {

var FilterContainer = function() {
  this.__container = {};
  this.addFilter = function(name,func) {
    this.__container[name] = func;
    return this;
  }
  this.batch = function(nodes) {
    for each (var node in nodes) 
      for each (var filter in this.__container) filter(node);
  }
};

if(window.Filterize == undefined)
  window.Filterize = {
    onAutoPagerized : new FilterContainer(),
    onLoaded : new FilterContainer(),
  };

// for AutoPagerize
if(window.AutoPagerize != undefined) 
  window.AutoPagerize.addFilter(function (context){
    return Filterize.onAutoPagerized.batch.call(Filterize.onAutoPagerized, context);
  });

// for loaded
// XXX: 調査してないけど、0 sec でも setTimeoutしたらうまく行った
window.Filterize.timer = setTimeout(function() {
  for each(var filter in window.Filterize) 
    if(filter.batch) filter.batch([document]);
  clearTimeout(window.Filterize.timer);
},0);


})();

AutoPagarize 対応が要らない場合のスクリプト

twitter の status 入力エリアを大きくする。

// ==UserScript==
// @name           AdjustStatusForTwitter
// @namespace      http://d.hatena.ne.jp/send/
// @description    adjust status text area for Filterize
// @include        http://twitter.com/home
// @include        http://twitter.com/replies
// @include        http://twitter.com/account/archive
// ==/UserScript==
if(window.Filterize != undefined) 
  Filterize.onLoaded.addFilter('adjustTextArea', function (context) {
    var textarea = context.getElementById('status');
    if (textarea) textarea.style['height'] = '3.5em';
  });

AutoPagerize 対応がいる場合のスクリプト

twitter半角文字とかで、デザイン崩れるのを矯正する。

// ==UserScript==
// @name           StatusBreakerForTwitter
// @namespace      http://d.hatena.ne.jp/send/
// @description    apply status break on twitter
// @include        http://twitter.com/*
// ==/UserScript==

/**
 * Original Script:
 *    url breaker+
 *    http://piro.sakura.ne.jp/latest/2005/06/url_breaker_plus.user.js
 * XXX: とりあえずUTF-8
 */
if(window.Filterize != undefined)
Filterize.onAutoPagerized.addFilter('applyWordBreak', function (context){
  var path = '//td[@class="content"]/span[@class="entry-title entry-content"]//child::text()';
  var elems = document.evaluate(path, context, null,7 ,null );
  var regex = /([@!-%'-/:=\\?@\\[-`\\{-~\w]|&[\w]{1,6};)/;
  var range = document.createRange();
  var wbr = document.createElement('wbr');
  
  var last;
  var elem;
  var started;
  for(var e = 0 ; e < elems.snapshotLength; e++) {
    elem = elems.snapshotItem(e);
    range.selectNode(elem);
    while(elem && (last = range.toString().search(regex)) !== -1) {
      range.setStart(elem, last + RegExp.$1.length);
      range.insertNode(wbr.cloneNode(true));
      elem = elem.nextSibling.nextSibling;
      range.selectNode(elem);
    }
  }
  range.detach();
});