d.hetima RSSフィード

2011-08-15

Safari 5.1 WebKit2 WKView の Client 関数を置き換える

WebView にあった各種 delegate は Cocoa の流儀で実装されていたのでやりやすかったのだけど WKView ではそれがなくなってしまい、泣いてました。これができるかどうかが SafariStand にとって死活問題。

WebKit2 では delegate ではなく WKPageXxxClient という呼び方になっている。Objective-C クラスではなく関数をまとめた構造体。setter はあるが getter がないため元の値を取っておくのが難しい。WebKit2 側の保存場所をつきとめるか、Safari 側の呼び出しを捉えるか。で、前者が見つかったので対応してみる。

WKView → WKViewData → webPageProxy と辿ると、Client の構造体を捕まえられる。この関数ポインタを自前のものと置き換えればOKのようだった。Objective-C クラスとは違ってメソッドが存在しているかどうかなどのチェックを動的に行えないので、けっこう危険な橋なんだけどとにかく渡る。具体的なソースコードはこちらから。

WebKit2 WKView client hook for Safari 5.1 ― Gist

パッチをするタイミングだが、[TabContentView initWithFrame:(CGRect) andBrowserWKView:(id)] の引数に WKView が渡されるのでここで捕まえた。また現行 SIMBL の仕様により、ロード時点で生成済みの WKView も存在するので、ウインドウを巡回するなどして既存の WKView にパッチする必要がある。


コンテキストメニューもこれでいじれるとは思うが、

SIMBL プラグインで Safari 5.1 のコンテキストメニューに項目を追加する - Yarukidenized:ヤルキデナイズド

の方が NSMenu を扱えるから楽だろう。ちなみに文字列を選択しているかどうかの判別など追加した版はこちら。

WebKit2 context menu hack ― Gist

2011-02-15

ニコニコ動画のファイルをキャッシュから保存する Chrome 機能拡張 Nicopy(※ただしMacに限る)

Chromeを入れてみたら思いのほか快適だったので乗り換え検討中。とりあえず表題の機能拡張を作った。SafariStand と同じようにロードが完了してから操作するタイプ。OpenMeta やら Growl やらに対応させてみた。

キャッシュを探してコピーする部分とフォルダ選択ダイアログをNPAPIプラグインで書いた。作るのがちょっと面倒だけど、ネイティブコードを仕込めるのはありがたい。ここはwin用を作る技術がないのでMac専用となりました。

今回は、ちょっとしたサイトならもうTumblrでいいんじゃない?と思える10個の理由 - IDEA*IDEA を読んでtumblrを使ってみることにした。followしとけばDashboardに更新情報とか流れてくるかも!

Nicopy

f:id:hetima:20110215002257p:image

2010-06-14

終了時の状態を復元するSafari機能拡張 HsRestoreSession 公開

終了時の状態を復元するSafari機能拡張、HsRestoreSession を公開しました。SafariStandのRestore Last Workspaceと似たような機能です。問答無用で復元するのではなく、どのタブを復元するか選択できるページを表示します。

できないこと:

・履歴の復元

・fileプロトコルを表示しているページの復元

この辺はSafari機能拡張からアクセスすることができません。セキュリティはかなり厳しい。

・ページのサムネイルを表示

表示しているページのサムネイルを取得することはできるんだけどかなり大きなpngbase64エンコードした文字列のみ。開いているタブ全部を記録するのは厳しいので見送りました。ローカルにあるキャッシュを使う手もあるけれど、ファイル参照やfileプロトコルを扱えないので駄目。websnapr 系のサービスを使うのは検討中。

・ウィンドウの位置・サイズの復元

これはできそう

・復元前にタブの順番を入れ替える

これはJavaScriptを使いこなせればできるんだが!

結果的に SafariStand から機能を切り出したものになったけれど、すべての機能を移植するのは無理です。いや、ほとんどの機能は移植無理です。SIMBLが劇的ビフォーアフター並みに改造できるのに比べて、Safari機能拡張で出来るのは部屋の模様替えくらい。そのかわり安全性はずっと高いし、Windows版でも動く。

2010-06-12

Safari Extension のクラスリファレンスのようなもの

Safari Extensions Reference を参考に一瞥しやすいようにまとめました。言語仕様を無視したオレオレ表記方法ですみません。

global.html

終了時に body onunload で処理可能(アンインストール時にも呼ばれる)。

SafariExtensionSettings の値は Safari の初期設定ファイルに保存される(com.apple.Safari.plist に ExtensionSettings-extension.bundle.id-DEVELOPERID として)。

safari.application == SafariApplication
safari.extension == SafariExtension
safari.self == SafariExtensionGlobalPage

class SafariExtension{
  (readonly array)bars;
  
  //safari-extension://extension.bundle.id-DEVELOPERID/
  (readonly DOMString)baseURI;
  (readonly array)toolbarItems;
  (readonly SafariExtensionGlobalPage)globalPage;
  (readonly SafariExtensionSecureSettings)secureSettings;
  (readonly SafariExtensionSettings)settings;

  //whitelist と blacklist は optional
  //runAtEnd が true ならページのスクリプトを実行した後、false なら実行前にロード
  //add の返り値は remove するときに使う
  DOMString addContentScript(DOMString source, array whitelist, array blacklist, boolean runAtEnd);
  DOMString addContentScriptFromURL(DOMString url, array whitelist, array blacklist, boolean runAtEnd);
  void removeContentScript(DOMString url);
  void removeContentScripts();
  DOMString addContentStyleSheet(DOMString source, array whitelist, array blacklist);
  DOMString addContentStyleSheetFromURL(DOMString url, array whitelist, array blacklist);
  void removeContentStyleSheet(DOMString url);
  void removeContentStyleSheets();
}

class SafariApplication{
  (readonly array of SafariBrowserWindow)browserWindows;
  (readonly SafariBrowserWindow)activeBrowserWindow;

  SafariBrowserWindow openBrowserWindow();
  void addEventListener(DOMString type, function, boolean useCapture);
  void removeEventListener(DOMString type, function, boolean useCapture);
}

class SafariExtensionGlobalPage{
  (readonly DOMWindow)contentWindow;
}


class SafariBrowserTab{
  (readonly SafariBrowserWindow)browserWindow;
  (readonly SafariWebPageProxy)page;
  (readonly DOMString)title;
  (DOMString)url;

  void activate();
  void close();
  //base-64 でエンコードされた png データ。でかい。
  DOMString visibleContentsAsDataURL();
}

class SafariBrowserWindow{
  (readonly array of SafariBrowserTab)tabs;
  (readonly SafariBrowserTab)activeTab;
  (readonly boolean)visible;

  void activate();
  void close();

  //index がマイナスか大きすぎたら右端に追加
  //visibility = "foreground" or "background"
  SafariBrowserTab openTab (Optional DOMString visibility, Optional long index);
  void insertTab (SafariBrowserTab tab, long index);
}

class SafariWebPageProxy{
	void dispatchMessage (DOMString name, any message);
}
//設定の読み書き
safari.extension.settings.key = value;
var value = safari.extension.settings.key;

//inject.js からの message は target に SafariBrowserTab が入っている
safari.application.addEventListener("message", function(messageEvent) {
  if(messageEvent.name === "hoge") {
    messageEvent.target.page.dispatchMessage("re-hoge", "ok");
  }
},false);

//bundle 内のリソースのURL
var imageUrl = safari.extension.baseURI+"image.png";

inject.js

inject.js はフレームや iframe にもそれぞれ読み込まれる。

スクリプトを開始:」に指定するとページのスクリプトを実行する前に、「スクリプトを終了:」に指定するとページのスクリプトを実行した後にロードされる、と Safari Extensions Development Guide に書いてあった。正確なタイミングはよく分かりません。

safari.extension == SafariContentExtension;
safari.self == SafariContentWebPage;

//global.html の safari.extension と比べるとかなり制限されている。
class SafariContentExtension{
  (readonly DOMString)baseURI;
}

class SafariContentWebPage{
  (readonly SafariContentBrowserTabProxy)tab;
  void addEventListener(DOMString type, function, boolean useCapture);
  void removeEventListener(DOMString type, function, boolean useCapture);
}

class SafariContentBrowserTabProxy{
  //同期メッセージを送る messageEvent.name==="canLoad"
  any canLoad(BeforeLoadEvent event, Optional any message);
  //通常の dispatchMessage は非同期
  void dispatchMessage (DOMString name, Optional any message);
  void setContextMenuEventUserInfo(MouseEvent event, any userInfo);
}

//inject.js はフレームや iframe にもそれぞれ読み込まれる。メインのdocumentだけでやりたい処理は
if(window.top===window){
  
}
//とかするとよい

//上記 global.html のサンプルに対応するコード
safari.self.addEventListener("message", function(messageEvent) {
  if(messageEvent.name === "re-hoge") {
    alert(messageEvent.message);
  }
},false);
safari.self.tab.dispatchMessage("hoge");


extension bar.html

表示していなくてもロードはされる。

safari.extension == SafariExtension;
safari.application == SafariApplication;
safari.self == SafariExtensionBar;

class SafariExtensionBar{
  (readonly SafariBrowserWindow) browserWindow;
  (readonly DOMWindow)contentWindow;
  (readonly DOMString)identifier;
  (DOMString)label;
  (readonly boolean)visible;
  void hide(boolean doNotRemember);
  void show(boolean doNotRemember);
  void addEventListener(DOMString type, function, boolean useCapture);
  void removeEventListener(DOMString type, function, boolean useCapture);
}

2009-10-26

ATOK for Mac 無償試用版をアンインストールしても残ったファイル

ATOKの試用期限が切れそうだったのでアンインストールしたけれど以下のファイル群が削除されないままだった。

/Applications/JustSystems
/Library/Application Support/JustSystems
/Library/JustSystems

の各フォルダと

/Library/LaunchAgents/com.justsystems.launchd.jslmaUI.plist
/Library/LaunchAgents/com.justsystems.launchd.UpdateChecker.plist
/Library/LaunchDaemons/com.justsystems.launchd.jslmad.plist
/Library/LaunchDaemons/com.justsystems.OnlineUpdate.plist

のファイル。他にも ~/Library/Preferences/ などに設定やユーザー辞書も残る。

LaunchAgents と LaunchDaemons が問題で、これが残ったままだとアップデートチェックが稼働し続ける。自己責任で削除しますた。