google の検索結果で、文字切りされているタイトルをフル表示する Greasemonkeyスクリプト

需要があるかは謎ですが、title属性にもフルのタイトルが入ってなくて微妙だと思ったので。


google の検索結果で、タイトルが長いと途中で文字切りされている場合があります。
例えば、このブログの記事だとこれとか。

こういう文字切りをしている場合は、リンクのtitle属性にはフルのタイトルが入っていて、
マウスオーバーでポップアップして全部見ることができるケースが多いですが、google はそうなっていません。


なので、マウスオーバー時点で ajaxでタイトルを取りにいって、フル表示するスクリプトを作りました。
show_full-title_for_google.user.js


マウスオーバーで、

こうなります。


【補足】

  • ページを開いた時点でなく、リンクをマウスオーバーした時点でタイトルを取りにいくようにしたのは、検索結果を100件表示とかに設定してた場合に、たくさんリクエストを投げちゃって微妙かなと思ったからです
  • タイトルを取りにいってるのは、実際のサイトではなく googleのキャッシュですが、理由としては実際のサイトがutf-8でないと文字化けするからです。なので、googleのキャッシュがないとうまく動きません

iPhone Safariだとaudio/videoタグのautoplay, autobuffer属性が効かない

Resources - Safari - Apple Developer
User Control of Downloads Over Cellular Networks」の部分

In Safari on iPhone OS (for all devices, including iPad), where the user may be on a cellular network and be charged per data unit, autobuffering and autoplay are disabled. No data is loaded until the user initiates it. This means the JavaScript play() and load() methods are also inactive until the user initiates playback, unless the play() method is triggered by user action. In other words, a user-initiated Play button works, but an onLoad play event does not.

「携帯が従量課金とかの可能性があるから、autoplayとautobufferは無効にしてるよ。
ユーザが意図して再生を開始するまで、データはロードされないよ。
それは、ユーザのアクションをトリガーにして再生を開始するまでは、JSのplay()とかload()も機能しないってことを意味するよ。
つまり、ユーザによって開始されたplay()は動作するけど、onloadとかのイベントだと動作しないよ。」
って感じでしょうか。


たしかに、実際以下のようなhtmlを書いても、再生されなかった。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>html5 audio test on iPhone</title>
</head>
<body>

<audio src="music.mp3" autoplay></audio>

</body>
</html>


でも、下のようなhtmlは再生されてはいけないはずなのだが再生された。

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>html5</title>
<script type="text/javascript"><!--
function onload_play() {
    var aud = document.getElementById('aud');
    aud.load();
    aud.play();
}
//--></script>
</head>
<body onload="onload_play();">

<audio id="aud" src="music.mp3"></audio>

</body>
</html>

これがバグなのかどうかはわからない。


が、いずれにせよ、サイレントモードを無視して再生されるので、

音を鳴らすのは微妙。

なんかいい方法はないのか。。。

(jpmobile の) mobile_filter hankaku => true は controller.response.body が freeze されてるとエラーになる

そもそも response.body が freeze された後に、文字変換すんなよというのは置いておいて。。。

例えば、

class HogeController < ApplicationController
  mobile_filter :hankaku => true
  around_filter :my_around_filter
  # mobile_filter は around_filter なので、filterの実行順は
  #   mobile_filter(before) --> my_around_filter(before) --> action(index) -->
  #   my_around_filter(after) --> mobile_filter(after)
  #
  # (mobile_filterは、正確にはオプション指定によるが、around_filter が1〜4つだが、
  #  ここではまとめて1つとして記述している)

  def index
  end

  private
  def my_around_filter
    # before filter では何もしない
    yield
    # after filter で、response.body を freeze
    response.body.freeze
  end
end

これを実行すると、「can't modify frozen string」でエラーになる

エラー箇所は、「vendor/plugins/jpmobile/lib/jpmobile/filter.rb:103:in `gsub!'」

周辺のコードは、次のようになっている。

def filter(str, from, to)
  str = str.clone
  from.each_with_index do |int, i|
    str.gsub!(int, to[i])            # line. 103
  end
  str
end

str に response.body が入ってくるのだが、
freezeされているものを gsub! で(全角カナ --> 半角カナ に)破壊的に変更しようとしてエラーとなっている。


ちなみに、hankaku => true 無しだと、問題なく動作する

class HogeController < ApplicationController
  mobile_filter                        # 全角・半角変換無し
  around_filter :my_around_filter

  def index
  end

  private
  def my_around_filter
    # before
    yield
    # after
    response.body.freeze
  end
end

これであっても、freeze 後に文字コードを変えているのだが、なぜこっちではエラーにならないのか。


結論から言うと、文字コード変更などでは、response.body そのものを変更しているのではなく、
response.body の文字列をコピーしたものを変更して、それをresponse.body に入れているからだと思われる。


frozen? でresponse.body の状態を出力させてみると

class HogeController < ApplicationController
  around_filter :my_around_filter1     # 確認用
  mobile_filter                        # 全角・半角変換無し
  around_filter :my_around_filter2
  # 実行順序は、
  #    my_around_filter1(before) --> mobile_filter(before) --> my_around_filter2(before) -->
  #    action(index) -->
  #    my_around_filter2(after) --> mobile_filter(after) --> my_around_filter1(after)

  def index
  end

  private
  def my_around_filter1
    # before
    yield
    # after
    logger.debug("### #{response.body.frozen?}")
  end

  def my_around_filter2
    # before
    yield
    # after
    response.body.freeze
    logger.debug("### #{response.body.frozen?}")
  end

# ログは以下のようになり、mobile_filter で response.body の freeze 状態が解除されているのが確認できる
#   ### true
#   ### false
end


で、全角・半角変換有りの話しに戻って、気になるのが、エラーが出ていた103行目の2行上にある「str = str.clone」
これは一体何のために存在しているのか。


もし、freezeされていない文字列のコピーを作りたいという考えであるとすれば、
cloneでは意図する動作になっていない。

プログラミング言語 Ruby リファレンスマニュアル の clone, dup の説明に書いてあるとおり、
clone では、freeze状態までコピーしてしまう。


試しに、101行目のcloneをdupに変更したら、問題なく動作した。


これがjpmobileの意図する通りの挙動なのか、バグなのかは分からないが、
全角・半角変換だけがfreezeされた場合にエラーになるのは、個人的には微妙な挙動だと思う。
response.bodyがfreezeされていた時にエラーにするなら、文字コード変換などでもエラーを出力し、
freezeされていてもfreezeを解除して実行してしまうのであれば、
全角・半角変換でもエラーにならないようになっていると、
mobile_filter全体として一貫性がある気がする。


ただ、文字コード変更や、絵文字のキャリア出し分けみたいな変換は、外見を変えるものではないのに対し、
全角・半角変換は外見からして変えるものだから、こういう挙動にしているんだと言われたら、
それはそれで納得できそうな気もする。

before_filter, after_filter, around_filter, prepend_before_filter, prepend_after_filter, prepend_filter の順番整理メモ

before_filter

before_filter :b1, :b2

# b1 --> b2 --> action
before_filter :b1
before_filter :b2

# b1 --> b2 --> action

before_filter + prepend_before_filter

before_filter :b1, :b2
prepend_before_filter :b3

# b3 --> b1 --> b2 --> action
before_filter :b1, :b2
prepend_before_filter :b3
prepend_before_filter :b4

# b4 --> b3 --> b1 --> b2 --> action
before_filter :b1, :b2
prepend_before_filter :b3, :b4

# b3 --> b4 --> b1 --> b2 --> action

別々に prepend_before_filter を宣言したときと、
カンマ区切りで並べたときの順序が同じにならないのは紛らわしいなぁ。。

after_filter

before_filter と同じ

after_filter :a1, :a2

# action --> a1 --> a2
after_filter :a1
after_filter :a2

# action --> a1 --> a2

after_filter + prepend_after_filter

after_filter :a1, :a2
prepend_after_filter :a3

# action --> a3 --> a1 --> a2
after_filter :a1, :a2
prepend_after_filter :a3
prepend_after_filter :a4

# action --> a4 --> a3 --> a1 --> a2
after_filter :a1, :a2
prepend_after_filter :a3, :a4

# action --> a3 --> a4 --> a1 --> a2

around_filter

before_filter, after_filter と同じ

around_filter :ar1, :ar2

# ar1(before) --> ar2(before) -->
# action -->
# ar2(after) --> ar1(after)
around_filter :ar1
around_filter :ar2

# ar1(before) --> ar2(before) -->
# action -->
# ar2(after) --> ar1(after)

around_filter + prepend_around_filter

around_filter :ar1, :ar2
prepend_around_filter :ar3

# ar3(before) --> ar1(before) --> ar2(before) -->
# action -->
# ar2(after) --> ar1(after) --> ar3(after)
around_filter :ar1, :ar2
prepend_around_filter :ar3
prepend_around_filter :ar4

# ar4(before) --> ar3(before) --> ar1(before) --> ar2(before) -->
# action -->
# ar2(after) --> ar1(after) --> ar3(after) --> ar4(after)
around_filter :ar1, :ar2
prepend_around_filter :ar3, ar4

# ar3(before) --> ar4(before) --> ar1(before) --> ar2(before) -->
# action -->
# ar2(after) --> ar1(after) --> ar4(after) --> ar3(after)

before_filter + around_filter

before_filter :b1
around_filter :ar1

# b1 --> ar1(before) --> action --> ar1(after)
before_filter :b1
around_filter :ar1
before_filter :b2

# b1 --> ar1(before) --> b2 --> action --> ar1(after)
before_filter :b1
around_filter :ar1
before_filter :b2
around_filter :ar2

# b1 --> ar1(before) --> b2 --> ar2(before) -->
# action --> ar2(after) --> ar1(after)


around_filter :ar1
before_filter :b1

# ar1(before) --> b1 --> action --> ar1(after)
around_filter :ar1
before_filter :b1
around_filter :ar2
before_filter :b2

# ar1(before) --> b1 --> ar2(before) --> b2 -->
# action --> ar2(after) --> ar1(after)

prepend_before_filter + around_filter

around_filter :ar1
prepend_before_filter :b1

# b1 --> ar1(before) --> action --> ar1(after)
around_filter :ar1
prepend_before_filter :b1
prepend_before_filter :b2

# b2 --> b1 --> ar1(before) --> action --> ar1(after)
around_filter :ar1
prepend_before_filter :b1, :b2

# b1 --> b2 --> ar1(before) --> action --> ar1(after)
around_filter :ar1
prepend_before_filter :b1
around_filter :ar2
prepend_before_filter :b2

# b2 --> b1 --> ar1(before) --> ar2(before) -->
# action --> ar2(after) --> ar1(after)

before_filter + prepend_around_filter

before_filter :b1
prepend_around_filter :ar1

# ar1(before) --> b1 --> action --> ar1(after)
before_filter :b1
prepend_around_filter :ar1
prepend_around_filter :ar2

# ar2(before) --> ar1(before) --> b1 --> action --> ar1(after) --> ar2(after)
before_filter :b1
prepend_around_filter :ar1, :ar2

# ar1(before) --> ar2(before) --> b1 --> action --> ar2(after) --> ar1(after)
before_filter :b1
prepend_around_filter :ar1
before_filter :b1
prepend_around_filter :ar2

# ar2(before) --> ar1(before) --> b1 --> b2 -->
# action --> ar1(after) --> ar2(after)

prepend_before_filter + prepend_around_filter

prepend_before_filter :b1
prepend_around_filter :ar1

# ar1(before) --> b1 --> action --> ar1(after)
prepend_around_filter :ar1
prepend_before_filter :b1

# b1 --> ar1(before) --> action --> ar1(after)

after_filter + around_filter

after_filter :a1
around_filter :ar1

# ar1(before) --> action --> ar1(after) --> a1

a1 が ar1のafter より後にまわされる。。

around_filter :ar1
after_filter :a1

# ar1(before) --> action --> ar1(after) --> a1

逆は当然か

after_filter :a1
around_filter :ar1
after_filter :a2

# ar1(before) --> action --> ar1(after) --> a1 --> a2
after_filter :a1
around_filter :ar1
after_filter :a2
around_filter :ar2

# ar1(before) --> ar2(before) --> action -->
# ar2(after) --> ar1(after) --> a1 --> a2

やっぱ、気持ち悪いなぁ。。

prepend_after_filter + around_filter

prepend_after_filter :a1
around_filter :ar1

# ar1(before) --> action --> ar1(after) --> a1

prepend でも無理。aroundのafterより優先的にはできない。

around_filter :ar1
prepend_after_filter :a1

# ar1(before) --> action --> ar1(after) --> a1

まぁ無理ですよね。

prepend_after_filter :a1
around_filter :ar1
prepend_after_filter :a2
around_filter :ar2

# ar1(before) --> ar2(before) --> action -->
# ar2(after) --> ar1(after) --> a2 --> a1

after_filter 同士内でのみ prepend が効いてるが、around_filter には効かない

after_filter + prepend_after_filter

after_filter :a1
prepend_around_filter :ar1
after_filter :a2
prepend_around_filter :ar2

# ar2(before) --> ar1(before) --> action -->
# ar1(after) --> ar2(after) --> a1 --> a2

そもそもprependしなくても、after_filterはaroundより優先的には実行されてないから、
当然こうなりますよね。

prepend_after_filter * prepend_around_filter

prepend_after_filter :a1
prepend_around_filter :ar1
prepend_after_filter :a2
prepend_around_filter :ar2

# ar2(before) --> ar1(before) --> action -->
# ar1(after) --> ar2(after) --> a2 --> a1

ふむ


まとめ

before と around は自然な挙動だけど、after と around はちょっと気持ち悪い(afterをaroundのafterより先に実行させることができない)

jpmobile のmobile_filter を部分的に無効にする(強引な方法)メモ

使える状況が限られているし、jpmobile自体をいじるし、グローバル変数を汚すしで、
極悪な解だが、一応メモしておく。


たとえば下のように、親コントローラに mobile_filter を記述し、それを継承した子コントローラがいくつかあるとする

# 親
class MobileController < ApplicationController
  mobile_filter :hankaku => true
end

# 子
class HogeController < MobileController
  def index
  end

  ...
end

class FooController < MobileController
  def index
  end

  ...
end

で、HogeControllerでは、半角全角変換をしたく無い場合にどうするか。


真っ当な方法としては、mobile_filter の宣言を 親コントローラであるMobileControllerに記述することをやめて、
それぞれのコントローラに記述する方法

# 親
class MobileController < ApplicationController
end

# 子
class HogeController < MobileController
  def index
  end

  ...
end

class FooController < MobileController
  mobile_filter :hankaku => true

  def index
  end

  ...
end

たしかにこれでもよいが、さらにHogeControllerの中の
index のみ半角全角変換を外したい場合は、どうすればよいか。
(言い換えると、HogeController でも、index以外では半角全角変換したい場合はどうしたらよいか)


ここからが極悪解だが、
mobile_filter の宣言部分( jpmobile/lib/jpmobile/filter.rb )を以下のようにいじる

class ActionController::Base #:nodoc:
  def self.mobile_filter(options={})
    options = {:emoticon=>true, :hankaku=>false}.update(options)

    if options[:emoticon]
      around_filter Jpmobile::Filter::Emoticon::Outer.new
    end
    around_filter Jpmobile::Filter::Sjis.new
    if options[:emoticon]
      around_filter Jpmobile::Filter::Emoticon::Inner.new
    end
    if options[:hankaku]
#      around_filter Jpmobile::Filter::HankakuKana.new    # この1行をコメントアウトし、以下2行を追加
      $jpmobile_filter_hankakukana = Jpmobile::Filter::HankakuKana.new    # グローバル変数に半角全角変換フィルタのインスタンスを格納して取っておく
      around_filter $jpmobile_filter_hankakukana
    end
  end
end

なおこの例では、半角全角変換のみ無効にしようとしているので、1つしかグローバル変数に入れていないが、
全てを無効にしたいのであれば、Jpmobile::Filter::Emoticon::outer, Jpmobile::Filter::Sjis, Jpmobile::Filter::Emoticon::Inner も入れておく必要があると思われる


これで、フィルタのインスタンスが格納されているので、skip_filterやそのオプションが使用可能となった。
つまり、外したいコントローラで、外したいアクションをskip_filterで指定すれば良い

# 親
class MobileController < ApplicationController
  mobile_filter :hankaku => true
end

# 子
class HogeController < MobileController
  # HogeController の index のみ半角全角変換フィルタをskip
  skip_filter $jpmobile_filter_hankakukana, :only => :index

  def index
  end

  ...
end

class FooController < MobileController
  def index
  end

  ...
end

条件として、グローバル変数を使っているせいで、mobile_filter 宣言は複数回不可能
(どこか1つだけ、正しく動作して、それ以外は効かなくなる)


もっと良い方法があったら教えてくださいorz


ちなみに、rails は 2.3.5 です。

Togetter で「RT」以降の文字を薄くするGreasemonkeyスクリプト

タイトル通りですが、RT/QT という文字列以降を薄く(#999999)するスクリプトを作りました。


下記リンク先のInstallボタンからインストールしてください。
RT obscure for Togetter for Greasemonkey


こんな感じです。


【2010/07/04 03:08 追記】
Toggeter --> Togetter に修正しました。すみませんorz

JSON.parse に渡せるJSON文字列が厳密すぎて使いにくい件

JSON文字列をオブジェクトに変換してくれる JSON.parse() を使ってみたが、
かなり使いにくく感じた。


というのも、

JSON.parse("{hoge: 1}");  // error

これがSyntaxErrorになる*1


JSON.stringify() が厳密なJSON文字列をくれるらしいので、
どう書けばよかったのか、回答を頂く。

JSON.stringify({hoge: 1});  // {"hoge":1}


試す。

JSON.parse('{"hoge":1}');  // ok


でも、文字列をシングルクオートにすると、

JSON.parse("{'hoge':1}");  // error

SyntaxErrorになる。


値が文字列の場合も同様。

JSON.parse('{"hoge":"foo"}');  // ok

JSON.parse('{"hoge":' + "'foo'" + '}');  // error ({"hoge":'foo'} こんな文字列)

うーん。。。


これだとeval使いたくなりますな。。

*1:試した環境は Firefox3.6.3、Chrome5.0.375.70