Slide 1

Slide 1 text

WebKit::WebView Essentials id:yutailang0119 2026/7/25 Sapporo.swift #1 1

Slide 2

Slide 2 text

yutailang0119 ● 株式会社はてな ○ ○ ○ ● ● ● @京都オフィス アプリケーションエンジニア サービスプラットフォーム try! Swift Tokyo Organizer AVP座談会🥽 🐈󰴛 2

Slide 3

Slide 3 text

https://fortee.jp/iosdc-japan-2026/proposal/62b52f01-f999-418b-ab65-a561b49f5202 3

Slide 4

Slide 4 text

Agenda ● WebKit::WebViewを知る・潜る・拡げる ● WebViewBackportを作る・拡げる ● バックポートからの学び 4

Slide 5

Slide 5 text

Agenda ● Meet / Dive into / Enhance WebKit::WebView ● Meet / Enhance WebViewBackport ○ @WebViewBackport ● Result 5

Slide 6

Slide 6 text

Meet WebKit::WebView 6

Slide 7

Slide 7 text

Meet WebKit::WebView ● SwiftUI::View実装のWebView ○ iOS / iPadOS / Mac Catalyst / macOS / visionOS 26.0+ ● 制御状態はWebPageが担当 ○ @Observable ● Meet WebKit for SwiftUI ○ WWDC25 Session 231 7

Slide 8

Slide 8 text

Meet WebKit::WebView @available(iOS 26.0, *) struct ContentWebView: View { @State private var webPage = WebPage() var body: some View { WebView(webPage) .onAppear { webPage.load(URL(string: "https://apple.com")) } } } 8

Slide 9

Slide 9 text

Dive into WebKit::WebView 9

Slide 10

Slide 10 text

Dive into WebKit::WebView ● Representableを介し、WKWebViewを表示 ○ class WebPageWebView: WKWebView ● 値型で再設計された設定 ○ WebPage.Configuration <- WKWebViewConfiguration ● 抽象化したWKWebView関連Delegateを提供 ○ WebPage.DialogPresenting <- WKUIDelegateAdapter ○ WebPage.NavigationDeciding <- WKNavigationDelegateAdapter 10

Slide 11

Slide 11 text

Dive into WebKit::WebView ● WebKitに実装されている ● Open Source Software ○ https://github.com/WebKit/WebKit ● SwiftUI APIはSwift overlayに配置 ○ Source/WebKit/UIProcess/API/Swift/ ■ UIProcess = アプリ本体のプロセス ■ 公開APIとUIが置かれる 11

Slide 12

Slide 12 text

Enhance WebKit::WebView 12

Slide 13

Slide 13 text

Enhance WebKit::WebView ● WebKitを参考にバックポートできそう ● 大まかに3つを実装できれば達成可能 ○ WebViewBackport: SwiftUI::View ○ WebViewRepresentableBackport: UIViewRepresentable ○ @Observable class WebPageBackport ● ⚠ 回避必要な箇所がある 13

Slide 14

Slide 14 text

Enhance WebKit::WebView ● Appleのoverlayは非公開APIに依存 ○ internal import WebKit_Internal ○ import WebKit_Private ○ @_spi(...) import SwiftUI ● third-partyのバックポートは公開APIだけで 再構築する 14

Slide 15

Slide 15 text

Enhance WebKit::WebView ● ① 公開APIで代替可能 -> 置換・ラップ ● ② 内部処理を観測 -> 近似 ● ③ 等価なし-> 挙動差・縮小 ○ SPI、version 15

Slide 16

Slide 16 text

① 公開APIで代替可能な 置換、ラップ 16

Slide 17

Slide 17 text

① 公開APIで代替可能な置換、ラップ ● Delegateをasyncプロトコルに変換 ○ WKNavigationDelegate -> NavigationDeciding ○ WKUIDelegate -> DialogPresenting ● WK* をラップ ○ FrameInfo・NavigationAction・BackForwardList ● NavigationEvent -> some AsyncSequence 17

Slide 18

Slide 18 text

② 内部処理を 観測して近似 18

Slide 19

Slide 19 text

② 内部処理を観測して近似 ● @ObservableをKVOから再現 ○ title / url はWKWebView由来のcomputed ○ 読取: getter で access(keyPath:) 登録 ○ 変更: KVO(.prior) の前/後 -> willSet/didSet ○ .prior = 変更前通知(willSet 相当) 19

Slide 20

Slide 20 text

③ 等価なし -> 挙動差・縮小 20

Slide 21

Slide 21 text

③ 等価なし -> 挙動差 ● NavigationEvent.finishedのタイミング差 ○ WebKit: 描画反映を待つ(SPI) ■ _do(afterNextPresentationUpdate:) ○ Backport: didFinish から同期 ○ 期待挙動と相違 ■ 完了時にまだ未描画のことがある 21

Slide 22

Slide 22 text

③ 等価なし -> 挙動差 ● BackForwardListをprivateデリゲートで取得 ○ 履歴変化を知る公開手段がない ■ @objc(_webView:backForwardListItemAdded:removed:) ○ privateデリゲート ○ 未公開のため保証なし -> 将来壊れうる 22

Slide 23

Slide 23 text

③ 等価なし -> 縮小 ● バージョンで欠ける(下位OSに無い) ○ 提供できない / 上位限定 ● バージョンで変わる(型がOSで違う) ○ 統一シグネチャで包めない 23

Slide 24

Slide 24 text

③ 等価なし -> 縮小 ● 設定もすべては移せない ○ Configurationは一部プロパティのみ対応 ○ 未対応の設定は使えない 24

Slide 25

Slide 25 text

WebViewの Backport達成 25

Slide 26

Slide 26 text

Meet WebViewBackport 26

Slide 27

Slide 27 text

Meet WebViewBackport struct ContentWebViewBackport: View { @State private var webPage = WebPageBackport() var body: some View { WebViewBackport(webPage) .onAppear { webPage.load(URL(string: "https://apple.com")) } } } 27

Slide 28

Slide 28 text

Meet WebViewBackport ● 使用側のインターフェースは同じ ● 設定、Delegateもサポート ○ WebPageBackport.Configuration ○ any WebPageBackport.NavigationDeciding ○ any WebPageBackport.DialogPresenting 28

Slide 29

Slide 29 text

Enhance WebViewBackport 29

Slide 30

Slide 30 text

Enhance WebViewBackport @WebViewBackport @available(iOS 26.0, *) struct ContentWebView: View { @State private var webPage = WebPage() var body: some View { WebView(webPage) .onAppear { webPage.load(URL(string: "https://apple.com")) } } } 30

Slide 31

Slide 31 text

Enhance WebViewBackport @WebViewBackport @available(iOS 26.0, *) struct ContentWebView: View { @State private var webPage = WebPage() var body: some View { WebView(webPage) .onAppear { webPage.load(URL(string: "https://apple.com")) } } } 31

Slide 32

Slide 32 text

@WebViewBackport ● macro WebViewBackport ○ @attached(peer, names: suffixed(Backport)) ○ これ自身はWebViewバックポートの実装ではない ● マクロを付与したスコープ内の特定文字列を 置換した実装を生成 ○ WebView -> WebViewBackport ○ WebPage -> WebPageBackport 32

Slide 33

Slide 33 text

@WebViewBackport struct ContentWebView: View { ... } ↓Expand Macro struct ContentWebViewBackport: View { @State private var webPage = WebPageBackport() var body: some View { WebViewBackport(webPage) .onAppear { webPage.load(URL(string: "https://apple.com")) } } 33

Slide 34

Slide 34 text

@WebViewBackport struct ContentView: View { var body: some View { if #available(iOS 26.0, *) { ContentWebView() } else { ContentWebViewBackport() } } } 34

Slide 35

Slide 35 text

Fix WebKit, but… 35

Slide 36

Slide 36 text

Fix WebKit, but… ● WebKitにバグ報告、修正可能 ○ WebKit Bugzilla ● Bug 311485 ■ にファイル選択が反応しない ● ⚠ OSにバンドルされるまで直らない ○ OSバックポートされない 36

Slide 37

Slide 37 text

37

Slide 38

Slide 38 text

Pitfall of @WebViewBackport 38

Slide 39

Slide 39 text

Pitfall of @WebViewBackport @WebViewBackport @available(iOS 26.0, *) struct ContentWebView: View { @State private var configuration = WebPage.Configuration() @State private var webPage = WebPage() var body: some View { WebView(webPage) .task { let cookies: [HTTPCookie] = ... await configuration.websiteDataStore .httpCookieStore.setCookies(cookies) } 39

Slide 40

Slide 40 text

Pitfall of @WebViewBackport struct ContentWebView: View { ... } ↓Expand Macro struct ContentWebViewBackport: View { @State private var configuration = WebPageBackport.Configuration() @State private var webPage = WebPageBackport() var body: some View { WebViewBackport(webPage) .task { let cookies: [HTTPCookie] = ... await configuration.websiteDataStore .httpCookieStore.setCookies(cookies) } 40

Slide 41

Slide 41 text

Pitfall of @WebViewBackport struct ContentWebView: View { ... } 💥 struct ContentWebViewBackport: View { @State private var configuration = WebPageBackport.Configuration() unrecognized selector @State private var webPage = WebPageBackport() -[WKHTTPCookieStore var body: some View { WebViewBackport(webPage) setCookies:completionHandler:] .task { let cookies: [HTTPCookie] = ... await configuration.websiteDataStore .httpCookieStore.setCookies(cookies) } 41

Slide 42

Slide 42 text

Pitfall of @WebViewBackport ● WKHTTPCookieStore.setCookies(_:) ■ setCookies(_:completionHandler:) ○ iOS / iPadOS / Mac Catalyst / macOS / visionOS 26.0+ ● マクロは呼び出し先を検証しない ○ 実行時エラーが起きうる ● iOS 18以下では unrecognized selector 42

Slide 43

Slide 43 text

Pitfall of @WebViewBackport @WebViewBackport @available(iOS 26.0, *) struct ContentWebView: View { var body: some View { WebView(webPage) .task { let cookies: [HTTPCookie] = ... let store = configuration.websiteDataStore.httpCookieStore for cookie in cookies { await store.setCookie(cookie) } } 43

Slide 44

Slide 44 text

Result 44

Slide 45

Slide 45 text

Result ● 公開APIで“ほぼ”作れる ● APIレイヤーとエンジンの境界が限界になる ● OSにバンドルされるまでは届かない ● WebKit::WebView を「歩いて」読み解いた 45

Slide 46

Slide 46 text

References 46

Slide 47

Slide 47 text

References ● WebKit/WebKit ○ ● iOS 18向けバックポートから学ぶWebKit::WebViewの歩き方 ○ ● https://github.com/WebKit/WebKit https://fortee.jp/iosdc-japan-2026/proposal/62b52f01-f999-418b-ab65-a561b49f5202 WKHTTPCookieStore.setCookies(_:) async ○ https://developer.apple.com/documentation/webkit/wkhttpcookiestore/setcookies(_:completio nhandler:) ● WebKit Bugzilla Bug 311485 ○ ● https://bugs.webkit.org/show_bug.cgi?id=311485 SwiftUIにおけるWebViewの実装 - cockscomblog? ○ https://cockscomb.hatenablog.com/entry/2025/06/04/093418 47

Slide 48

Slide 48 text

WebKit::WebView利用企業 48 48