このポストは Goodpatch Advent Calendar 2017 の11日目です.

はじめに

このページはどのブラウザで見ていますか?そう,Safari ですね. そんなあなたの Safari ライフを少しだけ便利にする Safari Extension を作ってみませんか? Welcome to ようこそサファリエクステンション!

基礎編

Google Chrome と比べてあまり流行っていない希少価値が高い Safari Extension. 主な情報は Apple の公式ページになります.

About Safari Extensions

Safari Extension Builder

メニュー > Develop > Show Extension Builder で開きます.

  • 名前や説明といった基本情報の設定
  • Extension 用 HTML ファイルの指定
  • Toolbar の設定 - Extension 対象のページの指定(ブラックリスト / ホワイトリスト)

などができます.

extension builder

:bulb: Develop メニューが無い場合は Safari > Preferences から有効にしてください.

safariextension ディレクトリ

Extension の内容は .safariextension というディレクトリの中に作っていきます.

extension directory

ディレクトリの構造は,以下の2点を除き,ほぼ自由です.

  • ./Info.plist : Extension Builder によって作られる
  • ./Icon.png : 64x64 px の画像をこの名前で置く必要がある

開発中の Extension を試す

Install ボタンを押すと,開発中の Extension を Safari にインストールすることができます.

さらに,Global Page 項目の Inspect Global Page ボタンを押すと,デバッガを開くことができます.

open debugger

デバッガでは,通常の Web サイト開発のように,ブレークポイントを設定してデバッグをすることができます.

break point

証明書

Apple Developer のサイトから取得します.

:warning: インストール後,一度 Extension Builder を閉じて開き直す必要があるので注意.

:bulb: 証明書がなくても,Extension を作ることはできます.

実践編

ひととおり Extension の作り方が分かったので,いくつか作ってみます.

i. URL を Markdwon 形式でコピーする

今開いているページの URL を Markdown 形式でコピーします.Google Chrome だとよくあるやつです.

mdlink

thedoritos/mdlink: Help copying the page URL in Markdown format.

:point_up: Toolbar から JavaScript を実行する

Toolbar Items にアイテムを追加し, Command を設定します.URL をコピーしたいので copy としました.

toolbar item

JS の方では, copy の Command にリスナーを登録しておきます. Extension のメインとなる HTML で次のような JS を読み込んでいます.

safari.application.addEventListener('command', function(event) {
  if (event.command !== 'copy') {
    return;
  }

  var tab = event.target.browserWindow.activeTab;
  var url = tab.url;
  var title = tab.title;

  var md = "[" + title + "](" + url + ")"
  window.prompt("Copy to clipboard: Cmd+C, Enter", md);
}, false);

:point_up: クリップボードにコピーできない

Safari Extension ではセキュリティ上の理由により,クリップボードにアクセスすることができません. そこで,コピー用のテキストを埋め込んだダイアログを表示するようにしています.

ii. 既存ページに CSS を追加する

既存のページに CSS を後付けで追加して,スタイルを上書きします. 例えば <h3><h4> の違いが分かりにくいとき(Markdown 形式で書けるページでよくあるやつです),スタイルを追加することで無理やり分かりやすくしてみます.

esa mier

※ 画像は dev/esa/api/v1 #noexpand - docs.esa.io より一部抜粋
※ esa.io を使っているのは,私が良く使うサイトだからです.他意はありません.

thedoritos/esa-mier: Customize esa.io style for me

:point_up: 追加の CSS を読み込む

Injected Content 項目の Style Sheets に,追加で読み込みたい CSS を設定します.

inject css

今回は <h3> 以下のヘッダに,要素名を表示してみます.これはひどい CSS だ.

h3::before {
    content: "H3: " !important;
    color: dimgray;
}

h4::before {
    content: "H4: " !important;
    color: dimgray;
}

h5::before {
    content: "H5: " !important;
    color: dimgray;
}

:point_up: 対象ページを指定する

Website Access 項目を設定することで,読み込むページを制限することができます.

target website

iii Google 検索を Hook する

Google の検索を Hook して,同じ検索ワードで esa.io 内を検索した結果を一緒に表示します.

esa niar

thedoritos/esa-niar: Search esa.io

:point_up: ページロードを Hook する

Injected Content 項目の End Scripts に,ページロード時に実行したい JS ファイルを設定します.

search script

:bulb: Start Scripts の方は,ページ読み込みに実行するファイルです.

:point_up: 対象ページ(URL)を指定する

Allowed Domains だけでは範囲が広すぎるので, Injected Content の Whitelist 項目で URL のパターンを指定して,検索結果画面だけでスクリプトが実行されるようにします.

:point_up: API キーを設定できるようにする

Settings and Local Storage

Extension には設定項目を用意することができます.各項目の値は Safari の設定画面から入力できるようになります.

settings

設定項目は,以下のようにして取得できます.

var apiKey = safari.extension.secureSettings.esaApiKey;
var team = safari.extension.settings.esaTeam;

ただし,実はこの settings は Injected Scripts の中からは参照できなくて,Global Page の中で読む必要があります.

  1. Injected Script から検索をリクエスト
  2. Global Page 側で settings を読んでリクエスト
  3. Injected Script へ結果をコールバック
  4. 画面に表示

Injected Scripts <=> Global Page のデータの受け渡しは,以下の項目が参考になります.

Messages and Proxies

結果をページに表示する

:angel: 適当にやりました.

おわりに

Safari ライフが便利になりそうな雰囲気を感じていただけたでしょうか?

勢いで書いたので目も当てられないコードになっているところもありますが,プロトタイプということでご容赦ください.プルリクエストを送って頂いたら喜びます.

来年も楽しいアニメが観られますように!

参考