2017/04/05 9:31 pm
Save to #4
Uncategorized, ,

いくつか気なる点が出始めてきた。

Kokoni のコンテキストメニューは、image/video/audio に対するものと link に対する物の両方に対して表示するようになっている。ここで、例えば img 要素を含んだ a 要素に対してコンテキストメニューを出したとき、保存対象は a.href か img.src か? という難しい判断を迫られる。

Save file to ではこういう場合、リンク先を保存というメニュー項目と画像を保存というメニュー項目を両方出現させる。しかし Chrome の拡張の場合、それは不可能なのだった。コンテキストメニューのルートに複数のメニュー項目を追加しようとした場合、ルートには拡張の名前の項目だけが追加され、拡張側から追加しようとした項目はその下に迂回されてしまう。

つまり通常はコンテキストメニューから “ここに保存” -> “Pictures” というルートを辿るのが、上記のマークアップの場合は “Kokoni” -> “ここにリンクを保存” または “ここに画像を保存” -> (いずれかの)”Pictures” というように展開されるメニューが1段増える。

リンクに対するコンテキストメニューを出した時はそういうものだと受け入れるとして、問題はどうも拡張から生成できるメニュー項目数に妙な制限があるらしいことなのだった。つまりツリーに対応するメニュー項目群を2倍定義しないといけないのだが、そうすると簡単に制限に引っかかる可能性が高くなる。メニュー項目をより動的に生成できるようなイベントが用意されていればその都度必要とされる項目だけを生成することで解決するのだが、chrome のコンテキストメニュー API はいまいちその辺の融通が効かない。

もうひとつ、参照したディレクトリの履歴をコンテキストメニューに含めるようにしているが、現状では単純に最後に参照したディレクトリを先頭に持ってくるようにしているので、例えば 9 割方 Pictures/けものフレンズ に保存しているが、たまに Pictures に保存しただけで履歴の順番が変わってしまう。履歴のソート順として参照回数順、辞書順などのオプションを実装する必要がある。

2017/04/02 9:17 pm
Save to #3
Uncategorized, ,

インストール手順を英語でも書いた。

Enjoy Kokoni!
https://appsweets.net/kokoni/index-en.html

同時に、basename を省略してアクセスしても、accept-language ヘッダの先頭が ja かどうかで適宜振り分けるようにしたのだが。Chrome の場合 $ LANGUAGE=en google-chrome などとしても accept-language ヘッダの内容は変化しない、Firefox の場合は逆に言語設定で ja を先頭にしているにかかわらず accept-language には ja が追加されない…など、それぞれがなんか妙な動きをして困る。

困るといえば、テストのためにいろんな画像を虹裏から落としているのだが、割と同じ画像を保存してしまうことがある。こういう場合に Kokoni 側で面倒を見るべきだろうか。でもそれはそれで面倒くさいなあ…。

やっぱりやめた。

$ fdupes -fdN ~/ピクチャ/けものフレンズ

すればいいだけのことだ。

それにしてもこの手の拡張が Chrome にも欲しいという意見は2012年頃からあったのに、なんで誰も作らなかったんだろう…。

4:15 am
Save to #2
Uncategorized, ,

Chrome Web Storeに公開した。

それから、詳しいインストールの仕方を書いたページを作った。しかし絶対パスや相対パスというむつかしいこんぴゅーたー用語が万人に通じるのか若干不安がある。Chrome の filesystem まわりの API がフルパスの扱いに関してぶっ壊れっぱなしで、スクリプト側で良きに計らえないのが悪い。

2017/03/28 7:50 pm
Save to
Uncategorized, ,

Firefox に Save File to という素敵な拡張がある。これはリンクや、メディアファイルに対するコンテキストメニューにホームディレクトリの任意のディレクトリツリーを展開し、選択したメニュー項目のパスへ直接ダウンロードする機能を提供する。つまり、とりあえず ~/Downloads に落として、落とし終わったら適当なところに移動…という手間を省ける。

似たようなものは Chrome にはないのだろうかと探してみたのだが、ないようなので作った。

本家には保存ダイアログにまで割り込んだり、サブメニューを左右交互に展開させるような芸の細かいオプションがあるが、残念ながら Chrome の拡張にそこまでの自由度はないので勝手に移植物としてはかなり不完全。

2017/01/26 3:11 am
The Space
Uncategorized, , , ,

qeema 内でデグレードしてた箇所があったのを修正。Chrome で、IME がオンの状態でスペースキーを押すと(設定によっては)いわゆる全角スペース(U+3000、IDEOGRAPHIC SPACE)が直接入力されるわけだが、それが wasavi に正しく伝達されなくなっていた。

この全角スペースの直接入力に対して Chrome が発生させるキーボードイベントは実におかしい。

  • まず keydown イベントが発生する。この時、keyCode は 229 で入力されつつある文字が全角スペースだという判断には使えない
  • 各種 Composition Events は一切発生しない
  • input イベントが発生する。このイベントは単に通知のためのもので、何が入力されたかという情報は与えてくれない

という感じに実におかしい。Firefox であれば正しく Composition Events が発生する。IME まわりは Firefox の方が Chrome よりずっと真っ当だ。

対応策として、input 内でなんとかするしかなく、実際そうしている。keydown でその時点での対象の要素の値を保存しておき、input で差分を取ってイベントを生成させる。デグレードしたのはその処理が正しく呼ばれていなかった。

2017/01/14 10:06 pm
Testing with selenium javascript binding #3
Uncategorized, , , , , ,

テストを開始しても、Chrome は起動するもののテストページへのナビゲーションが発生せず、そのままタイムアウトで失敗してしまうことがたまにある。これの原因がよくわからない。とりあえずエラーメッセージとしては “unable to discover open pages” というものが返される。

原因はよくわからない。テスト用とは別の常用の Chrome を起動している状態でテストを開始すると、そういう状況が発生することがあるが、しないこともある。常用の Chrome を落とした状態でも発生することがある。また youtube とかの動画を再生中だと発生する確率が上がるような気がしないでもない。要するに chromedriver が Chrome を起動するものの、複数ある Chrome のうち自分が起動させたものを見失うことがある、というような感じなのだが……そんなわけあるかいな。

上記のエラーメッセージでググっても特にこれだというものもない。謎。

ちなみに Linux 版の Chrome で UI のロケールを任意のものにするには、LANGUAGE=en google-chrome などとする。LANG でも LC_ALL でもなく、LANGUAGE。また、--lang スイッチは効かない。

 * * *

というわけで、とりあえずすべての機能テストを java から javascript へ移植した。疲れた。次にこれを Windows 上の Firefox にて通してテストする。とその前に、Linux 上でも動かしてみよう。

ナウい Firefox で Selenium のテストをするには、geckodriver が必要なので、これをパスの通ったところに置いておく。

var options = new firefox.Options();
options.setProfile(profilePath);
result = new webdriver.Builder()
      .withCapabilities(webdriver.Capabilities.firefox())
      .setFirefoxOptions(options)
      .build()

こんな感じで起動。これは既存のプロファイルを利用するような動作を意図しているが、どうも既存のプロファイルを /tmp あたりにまるごとコピーしてから起動するような感じがする。そのため実際に Firefox のウィンドウが表示されるまでは結構待たされる。

それから、webdriver.actions().sendKeys().perform() が未実装なのだそうでエラーになる。その代わり WebElement#sendKeys() を使う。geckodriver 自体が新しいプロジェクトなので、すべての想定された機能が実装されるまでにはもうちょっとかかる雰囲気。

 * * *

結局のところ wasavi でテストするには

  1. npm install -g selenium-webdriver
  2. npm install -g chromedriver geckodriver operadriver
  3. npm install -g mocha
  4. npm install

と入れて、make run-chrome としてとりあえずテスト用プロファイルでもって起動し、開発者モードで wasavi を組み込み、ついでに dropbox などに wasavi からアクセスして認証を得ておき、ブラウザを閉じてから make test-chrome とする……という感じ。ただし Firefox に関しては、過去の記事の通りオンザフライで WebExtensions ベースの拡張を登録するのが現在のところできないので、wasavi をビルドした上で xpi を登録したプロファイルを用意する必要がある。

2016/07/22 8:35 pm
Talk about news #2
Uncategorized, , ,

PhoneticNews は一種の RSS リーダーと考えることができるが(ただし NHK ニュースに関しては公式サイトが内部的に利用している、限りなく RSS に近い独自形式の XML を利用している)、任意のアドレスのフィードを利用できるわけではないので用途はかなり限定的だ。

なぜフィードを絞っているのかというと、発話の際のトラフィックの問題だ。PhoneticNews は発話データを得るのに VoiceTextAPI を利用している。この API のエンドポイントに文字列を POST すると、それを読み上げた発話の aac データが返ってくる。

この VoiceText へのネットワークアクセスは PhoneticNews をインストールした Chrome がそれぞれ個別に行うため、任意のフィードを登録できるようにすると VoiceText 側の負荷が結構なものになるかもしれないのである。まあ、勝手な想像なのですが。

API のリファレンスには呼び出しに関する回数や頻度の制限などは記述されていないので、どこまで許されるのか実際わからないのであった。うーんお問合せしたほうがいいのかなー。

いずれにしてもできうることを考えると、

  • 各 Chrome から個別に VoiceText にアクセスさせることをやめて、ここのサーバを経由させる。ここのサーバが代理的に VoiceText へのやり取りを行う: ここのサーバへの負荷はどうするのかという話になるが、現状でここのサーバは CloudFlare のお世話になっているのでたぶん負荷の面では大丈夫
  • 発話データを Chrome 自身の tts で生成する: ただし、日本語の発話データは OSX か、Windows10 以降じゃないと取得できない、らしい

あたりになると思う。はてさて。

2016/07/19 12:00 am
Talk about news
Uncategorized, , ,

PhoneticNews という Chrome エクステンションを前書いたのだが、いくつか既知のバグがあり、特に Chrome の起動時に二重に読み上げが行われる現象がある。この機会に探ってみた。

このエクステンションは定期的に NHK のニュースを読み込み、それを読み上げる。そのためにバックグラウンドで定期的に読み込み処理、読み上げ処理を走らせることが動作の核になる。一般的にそれを実現するには setTimeout や setInterval を使うのだが、Chrome のエクステンションの場合はバックグラウンドページの構造について Event Pages という別のアプローチがあり、PhoneticNews もそれを使っている。しかしこれがなかなかに癖があり、正しくそれに対応していなかったのがバグの原因になっている。

エクステンションのバックグラウンドが Event Pages のルールに従うか否かで変わる最大の物は、つまりバックグラウンドページが勝手にアンロードされるか否かということだ。バックグラウンドがアイドル状態になると即アンロードされる。アンロードというのは Chrome ブラウザの環境下においては、エクステンションに割り当てられたプロセスを終了するという意味だ。つまり Event Pages に対応すると、タスクマネージャを開いた時ずらっと Chrome が並ぶのを幾ばくか減らすことができるというわけだ。これはもちろんその分のメモリを開放するという意味もあるが、特にモバイル機器において不要な負荷を減らしバッテリーの寿命を伸ばすという効果があるのだと思う。

さて Event Pages では setTimeout/setInterval の代わりに chrome.alarms を使う。これにアラームを登録すると指定したタイミングにバックグラウンドがロードされ、次に chrome.runtime.onStartup に登録したイベントハンドラが呼ばれ、最後にアラームに登録したイベントハンドラが呼ばれる。

このアラームが特徴的なのは、登録が Chrome ブラウザに対して永続的であるということだ。定期的に発生するアラームは、エクステンションのインストール時にただ 1 度登録すればいいのである。PhoneticNews ではそれを知らず、アラームの登録をエクステンションのスタートアップ時に毎回行っていた。これが起動時に二重に読み上げが行われていた原因だ。

その他、従来は NHK ニュースだけに限っていたのを他のニュース配信社もいくつか含めるようにした。
phoneticnews-options
これ以外に、例えば共同通信や時事通信あたりも入れようかなと思ったのだが、なぜか日本の配信社は RSS フィードを公開していない、あるいはこっそりとしか公開していないところが多い。

アイコン押下時のポップアップでは最後に読み込んだ 1 件のニュースだけを表示していたのだが、最大 10 件にまで拡大した。
phoneticnews-popup

2016/05/01 7:00 am
Optimize a loading of assets
Uncategorized, , ,

赤福プラスの話。

赤福プラス 3.x はふたばの画像掲示板を開いた時に DOMContentLoaded のタイミングで一旦ドキュメントをすべて書き換える。これはなかなかアグレッシブな動作で、3.x 未満の赤福プラスや、おそらくは他の、ブラウザ上で動作するふたば閲覧エクステンションはあくまでふたばがネイティブで送出するコンテンツに何かを「付け足す」ようになっていると思う。赤福プラス 3.x はそうではなく、完全に書き換える。元のドキュメントからメタな中間 XML ドキュメントを生成し、それを XSLT によって HTML に変換・再構築し、元のドキュメントに上書きする。

さて DOMContentLoaded というのは定義としては DOM の構造を構築し終わった段階で発火するイベントで、load イベントに比べてかなり早い段階で発生する。しかし、これはドキュメントに付随するスタイルシートやスクリプトや画像や iframe で指定されるサブフレームのドキュメントをまだ読んでいない段階というわけでは *ない*。もしかしたら並行して読んでいる途中かもしれないし、そうでないかもしれない。特に最新のブラウザほど、並行動作をしている割合が高い。

このブラウザの動作は、赤福プラスの仕様とは相容れない。もしその手のアセットを読み込むとしても、それは赤福プラスが管理する文書の下で読み込まれるべきで、その前段階でネットワークアクセスが発生するのは全て無駄になるため、好ましくない。

そういうわけで、その辺りを最適化するために以前から Presto Opera では外部の script 要素の動作をブロックしたりしていた。似たようなことは Chrome の WebRequest API でもできるわけで、やってみた。

やることは、赤福プラスがページを再構築している間、そのページから発生するふたば外へのリクエストをすべてブロックすることである。そのために、

  • バックエンドの main.js にタブ ID をキーとするハッシュ initializingTabIds を用意する
  • WebRequest API によりすべてのリクエストをリスンする
  • ふたばの画像掲示板の URL がアクセスされたら、そのタブ ID を initializingTabIds に記録する
  • その他の何かがアクセスされた場合、それが属するタブ ID が initializingTabIds に含まれていれば、リクエストをキャンセルする
  • 赤福プラスのフロントエンドがページを再構築を完了したら、バックエンドへ initialized メッセージを投げる
  • バックエンドが initialized メッセージを受け取ったら、そのタブ ID を intializingTabIds から削除する

という処理を加えた。

ところで WebRequest を使うには manifest.json の permissions に “webrequest” および対象となる URL パターンを含める必要がある。今回の場合は URL は http://*/* および https://*/*/ というかなり広範囲に及ぶ強いパーミッションを必要とする。その場合 webstore での審査もけっこう時間がかかるようだ。昨日の午後提出して今審査が通った。

ちなみに Firefox 版はそういう最適化をしていないので読み込みが一番遅い。気が向いたらがんばる。

2016/04/13 8:44 am
Bringing a tab to the front
Uncategorized, ,

Chrome や Firefox は標準状態だと新規タブを開いたときそれをバックグラウンドにするのだが、個人的にはその仕様は使いにくく、フォアグラウンド(アクティブ)にしたい。Firefox の場合はそういうふうにするオプションが本体にあるので単にそれを使えばいい。しかし Chrome にはそういった標準的オプションはない。代わりにエクステンションを入れるしかない。いくらなんでもこの程度の機能は本体が持ってていいと思うんですけど。

というわけで、今までは Tabs to the front! というエクステンションを入れていた。

しかし、どうもこれが cVim と相性が悪い。Chrome 上で target=”_blank” なリンクをクリックして作成したタブは確かに常にアクティブになるのだが、cVim がエクステンションの chrome.tabs.* API を用いて生成したタブについては、アクティブになったりならなかったりとても不安定なのだ。

そこで、Tabs to the front! を使うのはやめ、cVim 側で対応することにした。cVim 側で新しいタブを開くのは単に t キーを押す。これは :TabNew<space> というキーストロークに仮想的にマップされているのだが、これを :TabNew!<space> にマップし直すことで(! を付ける)新しいタブがフォアグラウンドで開くことを指定する。これで解決!

と思ったら全然解決せず、不安定なままなのである。困ったことに cVim のバックグラウンドをデバッグし、実際に新規タブを生成するところで止めてみると確かに active パラメータは正しく渡されている。というかデバッガを起動している状態では思った通りの動作をするのだが閉じると不安定な状態に戻ってしまうのだ。ひっどい。つまりこれは、chrome.tabs.create() がなんかバグってんじゃないのかしらん?

この不具合は、しかし前述の通り Tabs to the front! を有効にしても直らない。このエクステンションは何をやっているのかといえば非常にシンプルで:

chrome.tabs.onCreated.addListener(function (tab) {
    chrome.tabs.update(tab.id, {active: true});
});

とまあ実にこれだけなのである。要するに新しいタブが作成されたことを察知したら、すかさずそれをアクティブに更新している。

これが動いたり動かなかったりするというのは、おそらくは chrome.tabs.update() と、chrome 内でタブを deactive にしている「何か」との、実行される順番がその時々で不定ということなんだろう。ということで、

chrome.tabs.onCreated.addListener(function (tab) {
    setTimeout(function () {
        chrome.tabs.update(tab.id, {active: true});
    }, 100);
});

なんて感じに遅延を挟んでやると思った通りの動きになる。しかし Tabs to the front! は人様のエクステンションなのでいじることはできない。どうしたものかな。

タブの動作の変更といえば拙作の Tabqueue があったので、そっちに機能を持たせることにした。Tabqueue とは、あるタブを閉じた後にどのタブをアクティブにするかについて、Opera12 のようにタブをアクティブ化した順のキューに応じて決定するという Presto Opera への未練タラタラなエクステンションである。従来はその機能に絞った単機能のエクステンションだったので、複数の機能を持たせるにあたり新しくオプションページを作成し
tabqueue-options
それを通して有効・無効を指定するようにした。ちなみに設定は chrome.storage.sync に保存されるようにしたので、複数のデバイス間で同期する。

ところでいつのまにかエクステンションのオプションはこのようにオーバーレイ形式になっていた。従来の、タブを1枚消費するタイプは obsolete なのだという。知らなかったそんなの…。

そういうわけで更新した

アーカイブ