// // SBRProcessBundleBridge.m // SBrowser // // Created by James Magahern on 7/22/20. // #import "SBRProcessBundleBridge.h" #import "SBRWebProcessDelegate.h" #import "SBRWebProcessProxy.h" #import #import #import #import #import #import #import #import @interface SBRProcessBundleBridge () @end @implementation SBRProcessBundleBridge { WKWebView *_webView; WKWebViewConfiguration *_webViewConfiguration; WKProcessPool *_processPool; id _webProcessProxy; _WKUserStyleSheet *_darkModeStyleSheet; WKUserScript *_readabilityScript; NSArray *_userScripts; } - (void)tearDown { [[_webView _remoteObjectRegistry] unregisterExportedObject:self interface:[self _webProcessDelegateInterface]]; } - (_WKRemoteObjectInterface *)_webProcessDelegateInterface { static dispatch_once_t onceToken; static _WKRemoteObjectInterface *interface = nil; dispatch_once(&onceToken, ^{ interface = [_WKRemoteObjectInterface remoteObjectInterfaceWithProtocol:@protocol(SBRWebProcessDelegate)]; }); return interface; } - (_WKRemoteObjectInterface *)_webProcessProxyInterface { static dispatch_once_t onceToken; static _WKRemoteObjectInterface *interface = nil; dispatch_once(&onceToken, ^{ interface = [_WKRemoteObjectInterface remoteObjectInterfaceWithProtocol:@protocol(SBRWebProcessProxy)]; }); return interface; } - (instancetype)initWithWebViewConfiguration:(WKWebViewConfiguration *)webViewConfiguration { self = [super init]; if (self) { if (!webViewConfiguration) { webViewConfiguration = [[WKWebViewConfiguration alloc] init]; // Inject bundle _WKProcessPoolConfiguration *poolConfiguration = [[_WKProcessPoolConfiguration alloc] init]; NSURL *bundleURL = [[[NSBundle mainBundle] builtInPlugInsURL] URLByAppendingPathComponent:@"SBrowserProcessBundle.bundle"]; // Make sure it exists. Bail if otherwise. NSBundle *pluginBundle = [NSBundle bundleWithURL:bundleURL]; NSAssert(pluginBundle != nil, @"Attix process bundle not found at %@", bundleURL.path); [poolConfiguration setInjectedBundleURL:bundleURL]; // Set up process pool _processPool = [[WKProcessPool alloc] _initWithConfiguration:poolConfiguration]; // Initialize allowed origins now NSArray *allowedOrigins = [[_policyDataSource allowedOriginsForScriptResources] allObjects]; [_processPool _setObject:allowedOrigins forBundleParameter:SBRGetAllowedOriginsKey()]; [_processPool _setObject:@(_allowAllScripts) forBundleParameter:SBRGetAllScriptsAllowedKey()]; webViewConfiguration.processPool = _processPool; } _webViewConfiguration = webViewConfiguration; // User scripts WKUserContentController *userContentController = [_webViewConfiguration userContentController]; for (WKUserScript *script in [self _userScripts]) { [userContentController addUserScript:script]; } // Instantiate web view WKWebView *webView = [[WKWebView alloc] initWithFrame:CGRectZero configuration:webViewConfiguration]; // Configure proxy interface (interface to remote web process) _webProcessProxy = [[webView _remoteObjectRegistry] remoteObjectProxyWithInterface:[self _webProcessProxyInterface]]; // Configure delegate interface (registering us as the web process delegate for the remote process) [[webView _remoteObjectRegistry] registerExportedObject:self interface:[self _webProcessDelegateInterface]]; _webView = webView; } return self; } - (WKUserScript *)_loadScriptForResource:(NSString *)resourceName withExtension:(NSString *)extension { NSURL *url = [[NSBundle mainBundle] URLForResource:resourceName withExtension:extension]; NSString *source = [NSString stringWithContentsOfURL:url encoding:NSUTF8StringEncoding error:nil]; return [[WKUserScript alloc] initWithSource:source injectionTime:WKUserScriptInjectionTimeAtDocumentEnd forMainFrameOnly:YES]; } - (NSArray *)_userScripts { if (!_userScripts) { _userScripts = @[ [self _loadScriptForResource:@"Tagger" withExtension:@"js"], ]; } return _userScripts; } #pragma mark - (void)webProcessDidConnect { NSLog(@"SBRProcessBundleBridge: did connect. Saying hello, syncing allowlist"); _webContentProcessConnected = YES; [_webProcessProxy hello]; [self policyDataSourceDidChange]; } - (void)webProcessDidAllowScriptWithOrigin:(NSString *)origin { [[self delegate] webProcess:self didAllowScriptResourceFromOrigin:origin]; } - (void)webProcessDidBlockScriptWithOrigin:(NSString *)origin { [[self delegate] webProcess:self didBlockScriptResourceFromOrigin:origin]; } #pragma mark Actions - (void)policyDataSourceDidChange { NSArray *allowedOrigins = [[_policyDataSource allowedOriginsForScriptResources] allObjects]; [_processPool _setObject:allowedOrigins forBundleParameter:SBRGetAllowedOriginsKey()]; [_webProcessProxy syncAllowedResourceOrigins:allowedOrigins]; NSDictionary *policyTypes = [_policyDataSource scriptPolicyTypeByOrigin]; [_processPool _setObject:policyTypes forBundleParameter:SBRGetPolicyTypeByOriginKey()]; [_webProcessProxy syncPolicyTypes:policyTypes]; } - (void)setAllowAllScripts:(BOOL)allowAllScripts { _allowAllScripts = allowAllScripts; [_processPool _setObject:@(_allowAllScripts) forBundleParameter:SBRGetAllScriptsAllowedKey()]; [_webProcessProxy setAllScriptsAllowed:allowAllScripts]; } - (void)setDarkModeEnabled:(BOOL)darkModeEnabled { _darkModeEnabled = darkModeEnabled; WKUserContentController *userContentController = [_webViewConfiguration userContentController]; if (darkModeEnabled) { if (!_darkModeStyleSheet) { NSURL *styleSheetURL = [[NSBundle mainBundle] URLForResource:@"darkmode" withExtension:@"css"]; NSString *styleSheetSource = [NSString stringWithContentsOfURL:styleSheetURL encoding:NSUTF8StringEncoding error:nil]; _darkModeStyleSheet = [[_WKUserStyleSheet alloc] initWithSource:styleSheetSource forMainFrameOnly:NO]; } [userContentController _addUserStyleSheet:_darkModeStyleSheet]; } else if (_darkModeStyleSheet) { [userContentController _removeUserStyleSheet:_darkModeStyleSheet]; } } - (void)parseDocumentForReaderMode:(void (^)(NSString * _Nonnull))completionBlock { WKUserContentController *userContentController = [_webViewConfiguration userContentController]; if (!_readabilityScript) { _readabilityScript = [self _loadScriptForResource:@"Readability" withExtension:@"js"]; } [userContentController _addUserScriptImmediately:_readabilityScript]; NSString *script = @"" "var documentClone = document.cloneNode(true);" "var article = new Readability(documentClone).parse();" "article.content"; [_webView evaluateJavaScript:script completionHandler:^(NSString *result, NSError * _Nullable error) { if (error != nil) { NSLog(@"Bridge: Readability error: %@", error.localizedDescription); } else { completionBlock(result); } }]; } @end