search
LoginSignup
5

posted at

updated at

Safariの拡張機能の作り方

前提

  • Swift
  • Xcode14.1
  • iOS15以降(14以前はSafariが拡張機能に対応していない)

こんなアプリが作れます↓ぜひ使ってみてください!

Safariでサイト上にメモをすることができるアプリ
レシピサイトに、自分なりのメモをしたりできます。

用意

まず新規アプリを作ってみないと、ファイル構成は理解できないと思います。(僕はそうでした)
必ず新規アプリを作ってから、次へ読み進んでください。

・新規アプリを作る

Xcodeで新規アプリを作成

  • File > New > Project

上のメニューから「Multiplatform」を選ぶ。
Safari Extension App」を選んでファイルを作成

Safari拡張機能の概要・ファイル構成

以下のようなファイル構成になっています。
※使うものだけをリストアップします。

  • Shared(Extension) - 拡張機能の制御
    ├ SafariWebExtensionHandler(拡張機能とアプリの橋渡し役)
    ├ Resources
     ├ _locales
      ├ en
       ├ messages(拡張機能の名前を設定)
     ├ manifest(拡張機能の許可周り)
     ├ background(SafariWebExtensionHandlerとの通信)
     ├ content(Safariのサイト上に何かを表示するためのjs)
     ├ popup.html(拡張機能のポップアップのHTML)
     ├ popup.js(拡張機能のポップアップのjs)
     ├ popup.css(拡張機能のcss)
  • iOS(App) - アプリ側の制御
    ├ AppDelegate
    ├ SceneDelegate
    ├ Main

これを理解するのに3日かかりました笑

popupとは?

tutorial2-1.jpg
ここを押すと...
IMG_1064.PNG
画面下部に表示されるこのViewのことです。

まずは動かしてみる

準備1

まずはSafari Extensionの動作を理解するために、簡単なアプリを作ってみましょう。
https://zenn.dev/h1d3mun3/articles/0bbcdcef81223c
このサイトが非常にわかりやすいので、このサイトの最後までやってみてください。

準備2

上記のサイトと同じことができたら、iOS(APP)ディレクトリにTopViewController.Swiftを作ってください。
「File -> New -> File -> Cocoa Touch Class -> UIViewController」です。
※これが、Storyboardの初期ページのスクリプトになります。

どんな流れで動いている?

では、どのような流れでこのExtensionが動いているのか説明します。

まずは今のディレクトリを確認しましょう。

  • Shared(Extension) - 拡張機能の制御
    ├ SafariWebExtensionHandler(拡張機能とアプリの橋渡し役)
    ├ Resources
     ├ _locales
      ├ en
       ├ messages(拡張機能の名前を設定)
     ├ manifest(拡張機能の許可周り)
     ├ background(SafariWebExtensionHandlerとの通信)
     ├ content(Safariのサイト上に何かを表示するためのjs)
     ├ popup.html(拡張機能のポップアップのHTML)
     ├ popup.js(拡張機能のポップアップのjs)
     ├ popup.css(拡張機能のcss)
  • iOS(App) - アプリ側の制御
    ├ AppDelegate
    ├ SceneDelegate
    ├ Main
    ├ TopViewController(アプリ画面の動作 通常のアプリと同じ)

流れ

TopViewController
(データを保存)

UserDefaults(AppGroup)で保存
(※AppGroupの使用が必須)

SafariWebExtensionHandler
(Background.jsからのリクエストで、アプリ側のデータ(Userdefaultsのデータ)を取得して返す)

background.js
(SafariWebExtensionHandlerに値をリクエストして、返り値をcontent.js等に返す)
↑↓
content.js or popup.js
(ユーザーに見える部分。拡張機能部分。
データが欲しいときは、background.jsにリクエストをする)

コード一覧

permission関係(manifest)

  • 初期設定(まずは以下を設定してください)
manifeset
"background": {
    "scripts": [ "background.js" ],
    "service_worker": "background.js",
    "persistent": false
}
  • 特定のページで動作させる場合
manifeset
"permissions": ["nativeMessaging"]
  • 全てのサイトで動作させる場合
manifeset
"permissions": ["nativeMessaging", "activeTab","<all_urls>"],
"content_scripts": [{
    "js": [ "content.js" ],
    "matches": [ "<all_urls>" ]
}]

content.js → background.jsの通信

content.js
browser.runtime.sendMessage({ key: "value" }).then((response) => {
    console.log("返り値は: ", response);

    switch(response.type) {
        case "message":
            console.log("返り値のTypeはmessageです");
            break;
    }
});

popup.js → background.jsの通信

これも上記と同様です。

background.jsでの処理

content.jsから値を受け取り、SafariWebExtensionHandlerに送り、返り値をcontent.jsに返す
こういうことです↓

content.js → background.js → SafariWebExtensionHandler → background.js → content.js

background.js
// content.jsからデータを取得
browser.runtime.onMessage.addListener((request, sender, sendResponse) => {
    console.log("typeは、",request["type"])
    
    // content.jsからのデータをそのまま、SafariWebExtensionHandlerに送る
    browser.runtime.sendNativeMessage("application.id", request, function(response) {
        // responseには、SafariWebExtensionHandlerからの返り値
        //その返り値を、content.jsに返す
        sendResponse({
            type: "message",
            data: {
                memo: response["memo"]
            }
        });
    });
});

SafariWebExtensionHandler

最初から以下のような、 「データを取得→background.jsに値を返す」 コードが書かれています。
コメントでコードを説明します。

SafariWebExtensionHandler
func beginRequest(with context: NSExtensionContext) 
    //background.jsから取得したデータ
    let item = context.inputItems[0] as! NSExtensionItem
    //valueを取り出す
    let message = item.userInfo?[SFExtensionMessageKey]
    os_log(.default, "Received message from browser.runtime.sendNativeMessage: %@", message as! CVarArg)

    //※目印A

    //background.jsに返すデータを定義
    let response = NSExtensionItem()
    //返すデータを指定 「key:"文字列"」の形式
    response.userInfo = [ SFExtensionMessageKey: [ "Response to": message ] ]
    //background.jsに返す
    context.completeRequest(returningItems: [response], completionHandler: nil)
}
  • content.jsからのデータを取り出す
    SafariWebExtensionHandlerで、content.jsからのデータを取得したいときは、上のコードの「目印A」の位置に以下を追加します。
SafariWebExtensionHandler
//content.jsからのデータを文字列に
var jsonString : String = String(format: "%@",message as! CVarArg)
// JSON文字列をData型に変換
var personalData: Data =  jsonString.data(using: String.Encoding.utf8)!
//key(今回はtype)を指定して、データを取得
var type : String = jsonData["type"] as! String

このような流れで、アプリ側とSafari Extensionとの通信することができました。

popup.jsからcontent.jsへ通信する(番外編)

popup.js
browser.tabs.query({ currentWindow: true, active: true }).then((tabs) => {
    //content.jsへ送信
    browser.tabs.sendMessage(tabs[0].id, {
        type: "changeContent",
        data: "data"
    }).then(handleResponse, handleError);
});
content.js
//popup.jsからのデータを受け取る
browser.runtime.onMessage.addListener(handleMessage);
function handleMessage(request, sender, sendResponse) {
    //keyを指定してデータを取得
    console.log(request["data"]);

    //値をpopup.jsに返す(試していませんが、このコードで動くはずです...動かなかったらすみません)
    sendResponse({
        type: "message",
        data: {
            memo: response["memo"]
        }
    });
}

popup.jsで開いているWebページのURLを取得する

ついでに、開いているページのタイトルも。

popup.js
browser.tabs.query({ currentWindow: true, active: true }).then((tabs) => {
    let activeTabUrl = tabs[0].url;
    console.log("WebページのURLは、",activeTabUrl);

    let activeTabTitle = tabs[0].title;
    console.log("Webページのタイトルは、",activeTabTitle);
})

拡張機能の名前や説明を変更する

messages
{
    "extension_name": {
        "message": "名前",
        "description": "The display name for the extension."
    },
    "extension_description": {
        "message": "説明です。",
        "description": "Description of what the extension does."
    }
}

ログを確認する

  • SafariWebExtensionHandlerのログの確認方法
SafariWebExtensionHandler
os_log( "値は: %{public}s", st)

ログの見方はこのページの、「ログの確認方法」をみてください。
https://techblog.recochoku.jp/8423#crayon-62de528f61a77688745988

  • 拡張機能側のログの確認方法
SafariWebExtensionHandler
console.log("ログ",st);

ログの見方は、このページを見てください。
https://qiita.com/unsoluble_sugar/items/2a3d06631a6b8259dc44

まとめ

Safari Extensionは、仕組みがわかるまでは難しいですが、一度わかるとすぐに開発できると思うので、是非作ってみてくださいね!

紹介

Safari上でメモを書く&表示できるアプリ「クックメモ」をリリースしました。
もちろんSafari拡張機能のアプリです!ぜひ使ってみてください〜↓↓↓

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
5