plyght's own C++ browser for macOS
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

improve browser extension stuff

plyght d25aed3e d101094f

+32 -9
+1 -1
src/app/SettingsDialog.cpp
··· 180 180 extensionLayout->addWidget(addExtension); 181 181 extensionsCol->addWidget(extensionRow); 182 182 extensionsCol->addWidget(makeHelp( 183 - "Loads unpacked Chrome extension content scripts into new tabs. WebKit cannot run Chrome background workers, popups, or privileged Chrome APIs.", 183 + "Loads local WebExtensions through WebKit on macOS 15.4+ and injects a compatibility runtime for older systems. Unpacked folders work best; background workers, native messaging, request blocking, and store installs depend on WebKit support and extension permissions.", 184 184 theme, extensionsCard)); 185 185 root->addWidget(extensionsCard); 186 186
+31 -8
src/services/ChromeExtensionManager.cpp
··· 40 40 - (WKWebView *)webViewForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; return (__bridge WKWebView *)self.view->nativeWebView(); } 41 41 - (NSString *)titleForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; return self.view->title().toNSString(); } 42 42 - (NSUInteger)indexInWindowForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; return g_browserWindow ? (NSUInteger)g_browserWindow->extensionViews().indexOf(self.view) : NSNotFound; } 43 + - (NSURL *)urlForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; QUrl url = self.view ? self.view->url() : QUrl(); return url.isValid() ? [NSURL URLWithString:url.toString().toNSString()] : nil; } 43 44 - (NSURL *)pendingURLForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; return nil; } 44 - - (BOOL)isLoadingCompleteForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; return YES; } 45 + - (BOOL)isLoadingCompleteForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; WKWebView *wk = self.view ? (__bridge WKWebView *)self.view->nativeWebView() : nil; return !wk || wk.estimatedProgress >= 1.0; } 46 + - (BOOL)isPinnedForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; return NO; } 47 + - (BOOL)isMutedForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; return NO; } 48 + - (BOOL)isReaderModeAvailableForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; return NO; } 45 49 - (void)loadURL:(NSURL *)url forWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler { (void)context; self.view->load(QUrl(QString::fromNSString(url.absoluteString))); completionHandler(nil); } 46 50 - (void)reloadFromOrigin:(BOOL)fromOrigin forWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler { (void)context; (void)fromOrigin; self.view->reload(); completionHandler(nil); } 47 51 - (void)goBackForWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler { (void)context; self.view->back(); completionHandler(nil); } ··· 55 59 - (NSArray<id<WKWebExtensionTab>> *)tabsForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; NSMutableArray *tabs = [NSMutableArray array]; if (!g_browserWindow) return tabs; for (WebView *view : g_browserWindow->extensionViews()) { PocbExtensionTab *tab = [PocbExtensionTab new]; tab.view = view; [tabs addObject:tab]; } return tabs; } 56 60 - (id<WKWebExtensionTab>)activeTabForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; if (!g_browserWindow || !g_browserWindow->extensionCurrentView()) return nil; PocbExtensionTab *tab = [PocbExtensionTab new]; tab.view = g_browserWindow->extensionCurrentView(); return tab; } 57 61 - (WKWebExtensionWindowType)windowTypeForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; return WKWebExtensionWindowTypeNormal; } 58 - - (WKWebExtensionWindowState)windowStateForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; return WKWebExtensionWindowStateNormal; } 62 + - (WKWebExtensionWindowState)windowStateForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; return g_browserWindow && g_browserWindow->isFullScreen() ? WKWebExtensionWindowStateFullscreen : WKWebExtensionWindowStateNormal; } 63 + - (CGRect)frameForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; return g_browserWindow ? CGRectMake(g_browserWindow->x(), g_browserWindow->y(), g_browserWindow->width(), g_browserWindow->height()) : CGRectZero; } 59 64 - (BOOL)isPrivateForWebExtensionContext:(WKWebExtensionContext *)context { (void)context; return NO; } 60 - - (void)focusForWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler { (void)context; if (g_browserWindow) g_browserWindow->raise(); completionHandler(nil); } 65 + - (void)focusForWebExtensionContext:(WKWebExtensionContext *)context completionHandler:(void (^)(NSError *))completionHandler { (void)context; if (g_browserWindow) { g_browserWindow->show(); g_browserWindow->raise(); g_browserWindow->activateWindow(); } completionHandler(nil); } 61 66 @end 62 67 63 68 @implementation PocbExtensionDelegate 64 69 - (NSArray<id<WKWebExtensionWindow>> *)webExtensionController:(WKWebExtensionController *)controller openWindowsForExtensionContext:(WKWebExtensionContext *)extensionContext { (void)controller; (void)extensionContext; return g_browserWindow ? @[ [PocbExtensionWindow new] ] : @[]; } 65 70 - (id<WKWebExtensionWindow>)webExtensionController:(WKWebExtensionController *)controller focusedWindowForExtensionContext:(WKWebExtensionContext *)extensionContext { (void)controller; (void)extensionContext; return g_browserWindow ? [PocbExtensionWindow new] : nil; } 66 - - (void)webExtensionController:(WKWebExtensionController *)controller openNewTabUsingConfiguration:(WKWebExtensionTabConfiguration *)configuration forExtensionContext:(WKWebExtensionContext *)extensionContext completionHandler:(void (^)(id<WKWebExtensionTab>, NSError *))completionHandler { (void)controller; (void)extensionContext; NSURL *url = [configuration respondsToSelector:@selector(URL)] ? [configuration valueForKey:@"URL"] : nil; WebView *view = g_browserWindow ? g_browserWindow->extensionCreateTab(url ? QUrl(QString::fromNSString(url.absoluteString)) : QUrl(), false) : nullptr; if (!view) { completionHandler(nil, [NSError errorWithDomain:@"pocb.extensions" code:1 userInfo:@{NSLocalizedDescriptionKey:@"No browser window is available"}]); return; } PocbExtensionTab *tab = [PocbExtensionTab new]; tab.view = view; completionHandler(tab, nil); } 71 + - (void)webExtensionController:(WKWebExtensionController *)controller openNewTabUsingConfiguration:(WKWebExtensionTabConfiguration *)configuration forExtensionContext:(WKWebExtensionContext *)extensionContext completionHandler:(void (^)(id<WKWebExtensionTab>, NSError *))completionHandler { (void)controller; (void)extensionContext; NSURL *url = [configuration respondsToSelector:@selector(URL)] ? [configuration valueForKey:@"URL"] : nil; BOOL shouldActivate = YES; @try { id active = [configuration valueForKey:@"shouldActivate"]; if ([active respondsToSelector:@selector(boolValue)]) shouldActivate = [active boolValue]; } @catch (...) {} WebView *view = g_browserWindow ? g_browserWindow->extensionCreateTab(url ? QUrl(QString::fromNSString(url.absoluteString)) : QUrl(), !shouldActivate) : nullptr; if (!view) { completionHandler(nil, [NSError errorWithDomain:@"pocb.extensions" code:1 userInfo:@{NSLocalizedDescriptionKey:@"No browser window is available"}]); return; } PocbExtensionTab *tab = [PocbExtensionTab new]; tab.view = view; completionHandler(tab, nil); } 67 72 - (void)webExtensionController:(WKWebExtensionController *)controller openNewWindowUsingConfiguration:(WKWebExtensionWindowConfiguration *)configuration forExtensionContext:(WKWebExtensionContext *)extensionContext completionHandler:(void (^)(id<WKWebExtensionWindow>, NSError *))completionHandler { (void)controller; (void)configuration; (void)extensionContext; if (!g_browserWindow) { completionHandler(nil, [NSError errorWithDomain:@"pocb.extensions" code:2 userInfo:@{NSLocalizedDescriptionKey:@"No browser window is available"}]); return; } g_browserWindow->extensionCreateTab(QUrl(), false); completionHandler([PocbExtensionWindow new], nil); } 68 73 - (void)webExtensionController:(WKWebExtensionController *)controller openOptionsPageForExtensionContext:(WKWebExtensionContext *)extensionContext completionHandler:(void (^)(NSError *))completionHandler { (void)controller; if (!g_browserWindow || !extensionContext.optionsPageURL) { completionHandler([NSError errorWithDomain:@"pocb.extensions" code:3 userInfo:@{NSLocalizedDescriptionKey:@"No options page is available"}]); return; } g_browserWindow->extensionCreateTab(QUrl(QString::fromNSString(extensionContext.optionsPageURL.absoluteString)), false); completionHandler(nil); } 69 74 - (void)webExtensionController:(WKWebExtensionController *)controller didUpdateAction:(WKWebExtensionAction *)action forExtensionContext:(WKWebExtensionContext *)context { (void)controller; if (!g_browserWindow) return; QString key = QString::fromNSString(context.webExtension.displayName ?: context.webExtension.version ?: @"extension"); QString label = QString::fromNSString(action.label.length ? action.label : (context.webExtension.displayName ?: @"Extension")); g_browserWindow->extensionSetAction(key, label, [action] { if (action.presentsPopup && action.popupPopover) { NSWindow *window = g_browserWindow ? (__bridge NSWindow *)reinterpret_cast<void *>(g_browserWindow->winId()) : nil; NSView *view = window.contentView; [action.popupPopover showRelativeToRect:NSMakeRect(NSMidX(view.bounds), NSMaxY(view.bounds) - 44, 1, 1) ofView:view preferredEdge:NSMinYEdge]; } }); } ··· 241 246 window.__pocbChromeExtensionsInstalled = true; 242 247 var scripts = __POCB_PAYLOAD__; 243 248 window.chrome = window.chrome || {}; 244 - chrome.runtime = chrome.runtime || { id: 'pocb', getURL: function(path){ return path || ''; }, sendMessage: function(){ var cb = arguments[arguments.length - 1]; if (typeof cb === 'function') cb(undefined); } }; 249 + function event(){ var ls=[]; return { addListener:function(fn){ if(typeof fn==='function' && ls.indexOf(fn)<0) ls.push(fn); }, removeListener:function(fn){ ls=ls.filter(function(x){return x!==fn;}); }, hasListener:function(fn){ return ls.indexOf(fn)>=0; }, hasListeners:function(){ return ls.length>0; }, _emit:function(){ var a=arguments; ls.slice().forEach(function(fn){ try{ fn.apply(null,a); }catch(e){ console.error(e); } }); } }; } 250 + var onMessage = event(); 251 + chrome.runtime = chrome.runtime || {}; 252 + chrome.runtime.id = chrome.runtime.id || 'pocb'; 253 + chrome.runtime.getURL = chrome.runtime.getURL || function(path){ return String(path || ''); }; 254 + chrome.runtime.onMessage = chrome.runtime.onMessage || onMessage; 255 + chrome.runtime.sendMessage = chrome.runtime.sendMessage || function(message, options, cb){ if (typeof options === 'function') cb = options; var responded = false; onMessage._emit(message, { id: chrome.runtime.id, url: location.href }, function(value){ responded = true; if (typeof cb === 'function') cb(value); }); if (!responded && typeof cb === 'function') setTimeout(function(){ cb(undefined); }, 0); }; 256 + chrome.runtime.connect = chrome.runtime.connect || function(){ var port = { name:'', onMessage:event(), onDisconnect:event(), postMessage:function(m){ setTimeout(function(){ port.onMessage._emit(m, port); },0); }, disconnect:function(){ port.onDisconnect._emit(port); } }; return port; }; 257 + chrome.tabs = chrome.tabs || {}; 258 + chrome.tabs.query = chrome.tabs.query || function(info, cb){ if (typeof cb === 'function') cb([{ id: 1, active: true, currentWindow: true, url: location.href, title: document.title }]); }; 259 + chrome.tabs.getCurrent = chrome.tabs.getCurrent || function(cb){ if (typeof cb === 'function') cb({ id: 1, active: true, currentWindow: true, url: location.href, title: document.title }); }; 260 + chrome.tabs.sendMessage = chrome.tabs.sendMessage || function(tabId, message, options, cb){ if (typeof options === 'function') cb = options; chrome.runtime.sendMessage(message, cb); }; 261 + chrome.windows = chrome.windows || {}; 262 + chrome.windows.getCurrent = chrome.windows.getCurrent || function(info, cb){ if (typeof info === 'function') cb = info; if (typeof cb === 'function') cb({ id: 1, focused: true, type: 'normal', tabs: [{ id: 1, active: true, url: location.href, title: document.title }] }); }; 245 263 chrome.storage = chrome.storage || {}; 264 + chrome.storage.onChanged = chrome.storage.onChanged || event(); 246 265 chrome.storage.local = chrome.storage.local || { 247 - get: function(keys, cb){ var data = {}; try { data = JSON.parse(localStorage.getItem('__pocb_extension_storage') || '{}'); } catch(e) {} if (typeof cb === 'function') cb(data); }, 248 - set: function(items, cb){ var data = {}; try { data = JSON.parse(localStorage.getItem('__pocb_extension_storage') || '{}'); } catch(e) {} Object.assign(data, items || {}); localStorage.setItem('__pocb_extension_storage', JSON.stringify(data)); if (typeof cb === 'function') cb(); }, 249 - remove: function(keys, cb){ var data = {}; try { data = JSON.parse(localStorage.getItem('__pocb_extension_storage') || '{}'); } catch(e) {} (Array.isArray(keys) ? keys : [keys]).forEach(function(k){ delete data[k]; }); localStorage.setItem('__pocb_extension_storage', JSON.stringify(data)); if (typeof cb === 'function') cb(); } 266 + _read: function(){ try { return JSON.parse(localStorage.getItem('__pocb_extension_storage') || '{}'); } catch(e) { return {}; } }, 267 + _write: function(data){ localStorage.setItem('__pocb_extension_storage', JSON.stringify(data || {})); }, 268 + get: function(keys, cb){ var data = this._read(), out = {}; if (keys == null) out = data; else if (Array.isArray(keys)) keys.forEach(function(k){ if (Object.prototype.hasOwnProperty.call(data,k)) out[k]=data[k]; }); else if (typeof keys === 'string') { if (Object.prototype.hasOwnProperty.call(data,keys)) out[keys]=data[keys]; } else if (typeof keys === 'object') { Object.keys(keys).forEach(function(k){ out[k]=Object.prototype.hasOwnProperty.call(data,k)?data[k]:keys[k]; }); } if (typeof cb === 'function') cb(out); }, 269 + set: function(items, cb){ var data = this._read(), changes = {}; Object.keys(items || {}).forEach(function(k){ changes[k] = { oldValue: data[k], newValue: items[k] }; data[k]=items[k]; }); this._write(data); chrome.storage.onChanged._emit(changes, 'local'); if (typeof cb === 'function') cb(); }, 270 + remove: function(keys, cb){ var data = this._read(), changes = {}; (Array.isArray(keys) ? keys : [keys]).forEach(function(k){ if (Object.prototype.hasOwnProperty.call(data,k)) { changes[k] = { oldValue: data[k] }; delete data[k]; } }); this._write(data); chrome.storage.onChanged._emit(changes, 'local'); if (typeof cb === 'function') cb(); }, 271 + clear: function(cb){ var data = this._read(), changes = {}; Object.keys(data).forEach(function(k){ changes[k] = { oldValue: data[k] }; }); this._write({}); chrome.storage.onChanged._emit(changes, 'local'); if (typeof cb === 'function') cb(); }, 272 + getBytesInUse: function(keys, cb){ var data = this._read(), selected = {}; if (keys == null) selected = data; else (Array.isArray(keys) ? keys : [keys]).forEach(function(k){ if (Object.prototype.hasOwnProperty.call(data,k)) selected[k]=data[k]; }); if (typeof cb === 'function') cb(new Blob([JSON.stringify(selected)]).size); } 250 273 }; 251 274 function ok(script){ 252 275 var href = location.href;