Розробка расширення для Safari (iOS Safari Extension)
Safari Web Extension на iOS—це той же WebExtension API, що й у desktop-браузерах, але з жорсткими обмеженнями мобільної платформи. Розширення упаковується всередину нативного iOS-додатку, розповсюджується через App Store та активується користувачем вручну в настройках Safari. Це не «просто портувати Chrome-розширення»—є тонкості, які ломають половину desktop-логіки.
Структура та точки входу
Розширення складається з двох частин: нативний Extension target (Swift/ObjC) та веб-частина (JS/HTML/CSS). Нативна частина—SafariWebExtensionHandler, реалізуючий NSExtensionRequestHandling. Через нього JS-код розширення спілкується з нативним додатком через browser.runtime.sendNativeMessage().
Маніфест: Safari підтримує Manifest V2 та V3. Apple рекомендує MV3, але browser.action в MV3 на iOS працює інакше, ніж у Chrome—toolbar popup не підтримується напрямку. Замість цього використовуйте SFSafariExtensionViewController на нативній стороні.
class SafariWebExtensionHandler: NSObject, NSExtensionRequestHandling {
func beginRequest(with context: NSExtensionContext) {
guard let item = context.inputItems.first as? NSExtensionItem,
let message = item.userInfo?[SFExtensionMessageKey] else {
context.completeRequest(returningItems: nil, completionHandler: nil)
return
}
// обробляємо message від JS
let response = NSExtensionItem()
response.userInfo = [SFExtensionMessageKey: ["status": "ok"]]
context.completeRequest(returningItems: [response], completionHandler: nil)
}
}
Обмеження iOS, які ломають desktop-логіку
Background scripts не працюють. У desktop Chrome background.js живе постійно. В iOS Safari—ні. Service Worker (MV3) вивантажується миттєво після виконання завдання. Будь-яке стан, який desktop-розширення зберігає в пам'яті background script, на iOS потрібно персистити через browser.storage.local або передавати через нативний хост.
webRequest API відсутній. Перехват та модифікація мережевих запитів—через Content Blockers (WKContentRuleList), а не через webRequest. Якщо desktop-розширення блокує рекламу через webRequest.onBeforeRequest, цей код не перенести напрямку.
Доступ до вкладок обмежений. browser.tabs.query() повертає тільки активну вкладку. Ітерація по всіх открытих вкладках, як на desktop, недоступна.
Content scripts інжектуються з затримкою. На iOS сторінка може повністю завантажитися до того, як content script почне виконуватися. Код, який рахує на перехват DOMContentLoaded, може не встигнути.
Типичний сценарій: розширення-менеджер паролей
Припустимо, потрібне автозаповнення через content script. JS інжектується в сторінку, знаходить <input type="password">, повідомляє нативному хосту через browser.runtime.sendNativeMessage(). Нативний хост звертається до keychain через Security.framework та повертає дані. Content script заповнює поле.
Проблема: sendNativeMessage на iOS працює тільки коли нативне Extension target запущене. Якщо користувач не відкривав додаток після перезавантаження пристрою—Extension Host може не встигнути стартувати. Потрібен retry-механізм на стороні JS.
Інструмент міграції від Apple
Для конвертації існуючого Chrome/Firefox розширення: xcrun safari-web-extension-converter. Він створює Xcode-проект з правильною структурою, але не виправляє несумісні API—тільки попереджає про них. Після конвертації завжди залишається ручна доробка.
Ревью та дозволи
App Store потребує обґрунтування кожного дозволу в маніфесті. tabs—для чого? storage—що саме зберігається? Розмиті відповіді в формі подачі приводять до 4.0 Design rejection з проханням уточнити функціональність.
Розширення з <all_urls> в host_permissions проходять посилену перевірку. Apple може запросити відеодемонстрацію роботи розширення на реальному пристрої.
Тривалість розробки: 1–3 тижні залежно від складності JS-логіки та обсягу нативного взаємодії. Вартість розраховується індивідуально.







