Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
WKWebView とめんどくさいお友達
Search
Megabits_mzq
February 22, 2022
Programming
820
1
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
WKWebView とめんどくさいお友達
Swift 愛好会 2022/2/22 登壇資料
Megabits_mzq
February 22, 2022
More Decks by Megabits_mzq
See All by Megabits_mzq
OTP を自動で入力する裏技
megabitsenmzq
0
210
SwiftUI と Shader を活用した楽しいオンボーディング起動画面の作成
megabitsenmzq
0
160
Liquid Glass, どこが変わったのか
megabitsenmzq
0
210
iPhone 16 Camera Control
megabitsenmzq
0
180
240fps で画像処理したい
megabitsenmzq
0
260
Swift 開発が楽になる道具たち
megabitsenmzq
1
800
Animoji を作ってみた
megabitsenmzq
0
220
MainMenu.xib を翻訳してみた
megabitsenmzq
0
320
先週解決した SwiftUI 問題
megabitsenmzq
0
170
Other Decks in Programming
See All in Programming
AI Augmentation Techniques with Liberty
ivargrimstad
1
410
私なりのハーネスエンジニアリング ~独自の言語処理系の開発を通して~
seosoft
0
190
SODA - FACT BOOK(JP)
sodainc
1
40k
AI-Powered Domain-Driven Design
ziobrando
0
420
作って学ぶSpring BootのAuto Configuration / JJUG Night Seminar 2026-10-19
rshindo
0
220
Augmenting AI with the Power of Jakarta EE
ivargrimstad
0
200
Web Framework Benchmark Battle 2026, Any%
a_matsuda
3
3.2k
GraphQL の onError と Nullability のこれから
quramy
0
210
動画プレイヤーはなぜカクつくのか 〜Flutterのレンダリングとネイティブの境界を解剖する〜
naruogram
0
330
Python Lambdaにおけるテストしやすい設計とAI支援の活用 / Designing Testable Python Lambda Functions with AI
kitano_yuichi
1
310
Vapor Mode でアクセシビリティは 壊れないか検証した話
fuqda
1
330
Coding with AI: How to trust the storyteller (AWS Community Day Adria, Ljubljana 2026)
slobodan
0
140
Featured
See All Featured
Optimising Largest Contentful Paint
csswizardry
37
4k
A Soul's Torment
seathinner
8
3.9k
Rebuilding a faster, lazier Slack
samanthasiow
85
9.7k
JAMstack: Web Apps at Ludicrous Speed - All Things Open 2022
reverentgeek
1
670
The #1 spot is gone: here's how to win anyway
tamaranovitovic
4
1.2k
Kristin Tynski - Automating Marketing Tasks With AI
techseoconnect
PRO
0
580
Darren the Foodie - Storyboard
khoart
PRO
4
4.1k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
560
BBQ
matthewcrist
90
10k
HTML-Aware ERB: The Path to Reactive Rendering @ RubyCon 2026, Rimini, Italy
marcoroth
5
910
Music & Morning Musume
bryan
48
7.5k
Fireside Chat
paigeccino
43
4.1k
Transcript
Jinyu Meng (Megabits Studio) WKWebView とめんどくさいお友達
個⼈紹介 独 ⽴ 開 発 者 で す ! Megabits
⾦⿂
個⼈紹介 Twitter @Megabits_mzq @Megabits_Studio
None
None
“WebKit を触ると、不幸になる” ──マハトマ・ガンディー が⾔ったことがない
None
self.view.window?.level = .floating
None
None
let width = view.frame.width let height = view.frame.height -
toolbarView.frame.maxY let script1 = """ Object.defineProperty(window, "screenTop", { get: function(){ return 0; }}); Object.defineProperty(window, "outerWidth", { get: function(){ return \(width); }}); Object.defineProperty(window, "outerHeight", { get: function(){ return \(height); }}); """ let userScript1 = WKUserScript(source: script1, injectionTime: .atDocumentStart, forMainFrameOnly: true) webView.configuration.userContentController.addUserScript(userScript1)
#import <AppKit/AppKit.h> #import <objc/runtime.h> static void HookMessage(Class cls, SEL selName,
IMP replaced, IMP *orig) { Method origMethod = class_getInstanceMethod(cls, selName); if (!origMethod) {return;} *orig = method_setImplementation(origMethod, replaced); } static NSRect (*original_NSScreen_frame)(NSScreen *self, SEL _cmd); static NSRect replaced_NSScreen_frame(NSScreen *self, SEL _cmd) { //https://stackoverflow.com/questions/1451342/objective-c-find-caller-of-method NSString *sourceString = [[NSThread callStackSymbols] objectAtIndex:1]; NSCharacterSet *separatorSet = [NSCharacterSet characterSetWithCharactersInString:@" -[]+?.,"]; NSMutableArray *array = [NSMutableArray arrayWithArray:[sourceString componentsSeparatedByCharactersInSet:separatorSet]]; [array removeObject:@""]; NSString *framework = [array objectAtIndex:1]; NSRect origFrame = original_NSScreen_frame(self, _cmd); if (![framework isEqualTo:@"WebCore"] { return origFrame; } return CGRectMake(0, 0, 0, 0); } #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wundeclared-selector" __attribute__((constructor)) static void makeMyMagicWork() { HookMessage( objc_getClass("NSScreen"), NSSelectorFromString(@"frame"), (IMP)&replaced_NSScreen_frame, (IMP *)&original_NSScreen_frame ); } #pragma clang diagnostic pop
None
None
None
func webView(_ webView: WKWebView, decidePolicyFor navigationResponse: WKNavigationResponse, decisionHandler: @escaping (WKNavigationResponsePolicy)
-> Void) { if navigationResponse.canShowMIMEType, let mimeType = navigationResponse.response.mimeType { let mainType = mimeType.components(separatedBy: "/")[0] // print(mainType, mimeType, navigationResponse.isForMainFrame) if !["image", "audio", "video"].contains(mainType) { if !(["text/plain", "text/xml"].contains(mimeType) && navigationResponse.isForMainFrame){ decisionHandler(.allow) return } } else if !navigationResponse.isForMainFrame { decisionHandler(.allow) return } } if let fileName = navigationResponse.response.suggestedFilename, let url = navigationResponse.response.url { webView.configuration.websiteDataStore.httpCookieStore.getAllCookies() { cookies in print(fileName) self.createNewDownload(url: url, fileName: fileName, cookies: cookies) } } decisionHandler(.cancel) }
MIME Type • text/plain • text/xml • Image/png • Audio/mp3
• Video/mp4 • … navigationResponse.canShowMIMEType, navigationResponse.response.mimeType
navigationResponse.isForMainFrame
Blob 👉 WKDownloadDelegate
None
override func willOpenMenu(_ menu: NSMenu, with event: NSEvent) { super.willOpenMenu(menu,
with: event) for (index, menuItem) in menu.items.enumerated() { switch menuItem.identifier?.rawValue { case "WKMenuItemIdentifierCopyImage": menu.removeItem(menuItem) default: break // print(menuItem.identifier?.rawValue ?? "") } } }
window.oncontextmenu = (event) => { var target = event.target var
href = target.href var parentElement = target while (href == null && parentElement.parentElement != null) { parentElement = parentElement.parentElement href = parentElement.href } if (href == null) { parentElement = null; } window.webkit.messageHandlers.oncontextmenu.postMessage({ nodeName: target.nodeName, id: target.id, src: target.src, hrefNodeName: parentElement?.nodeName, hrefId: parentElement?.id, href }); } """ https://stackover fl ow.com/a/66836354/1711103
None
@Megabits_mzq @Megabits_Studio https://menubarx.app