LoginSignup
9

More than 1 year has passed since last update.

Safariの拡張機能の作り方

Last updated at Posted at 2022-11-27

前提

  • 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
  3. You can use dark theme
What you can do with signing up
9