diff --git a/api/CONFIG.md b/api/CONFIG.md index 03935e112d..4dd4686f16 100644 --- a/api/CONFIG.md +++ b/api/CONFIG.md @@ -56,6 +56,22 @@ sources | | Key | Default Value | Description :--- | :--- | :--- root | "/" | Make root open index.html +default_index | "" | Set default 'index.html' path to open for implicit routes + +# Section `permissions` + +Key | Default Value | Description +:--- | :--- | :--- +allow_fullscreen | true | Allow/Disallow fullscreen in application +allow_microphone | true | Allow/Disallow microphone in application +allow_camera | true | Allow/Disallow camera in application +allow_user_media | true | Allow/Disallow user media (microphone + camera) in application +allow_geolocation | true | Allow/Disallow geolocation in application +allow_notifications | true | Allow/Disallow notifications in application +allow_sensors | true | Allow/Disallow sensors in application +allow_clipboard | true | Allow/Disallow clipboard in application +allow_bluetooth | true | Allow/Disallow bluetooth in application +allow_data_access | true | Allow/Disallow data access in application watch | false | Enable watch mode # Section `debug` diff --git a/src/android/runtime.cc b/src/android/runtime.cc index 0a8f99806a..b038f82f90 100644 --- a/src/android/runtime.cc +++ b/src/android/runtime.cc @@ -12,6 +12,18 @@ namespace SSC::android { this->rootDirectory = rootDirectory; } + bool isPermissionAllowed (const String& name) { + static const auto config = SSC::getUserConfig(); + const auto permission = String("permissions_allow_") + name; + + // `true` by default + if (!config.contains(permission)) { + return true; + } + + return config.at(permission) != "false"; + } + Runtime::~Runtime () { this->env->DeleteGlobalRef(this->self); } @@ -149,4 +161,20 @@ extern "C" { return true; } + + jboolean external(Runtime, isPermissionAllowed)( + JNIEnv *env, + jobject self, + jstring permission + ) { + auto runtime = Runtime::from(env, self); + auto name = StringWrap(env, rootDirectory).str(); + + if (runtime == nullptr) { + Throw(env, RuntimeNotInitializedException); + return false; + } + + return runtime->isPermissionAllowed(name); + } } diff --git a/src/android/runtime.kt b/src/android/runtime.kt index a584a995dc..121b1cdb85 100644 --- a/src/android/runtime.kt +++ b/src/android/runtime.kt @@ -78,4 +78,7 @@ open class Runtime ( @Throws(java.lang.Exception::class) external fun stopTimers (): Boolean; + + @Throws(java.lang.Exception::class) + external fun isPermissionAllowed (permission: String): Long; } diff --git a/src/android/window.kt b/src/android/window.kt index a5b7e917bf..f7a9678c00 100644 --- a/src/android/window.kt +++ b/src/android/window.kt @@ -25,6 +25,7 @@ open class Window (runtime: Runtime, activity: MainActivity) { } fun load () { + val runtime = this.runtime.get() ?: return val isDebugEnabled = this.runtime.get()?.isDebugEnabled() ?: false val filename = this.getPathToFileToLoad() val activity = this.activity.get() ?: return @@ -47,7 +48,13 @@ open class Window (runtime: Runtime, activity: MainActivity) { activity.webview?.apply { // features settings.javaScriptEnabled = true - settings.domStorageEnabled = true + + settings.domStorageEnabled = runtime.isPermissionAllowed("data_access") + settings.databaseEnabled = runtime.isPermissionAllowed("data_access") + + settings.geolocationEnabled = runtime.isPermissionAllowed("geolocation") + settings.appCacheEnabled = true + settings.javaScriptCanOpenWindowsAutomatically = true // allow list settings.allowFileAccess = true diff --git a/src/cli/cli.cc b/src/cli/cli.cc index e04471fd5d..5fc9a0e6ee 100644 --- a/src/cli/cli.cc +++ b/src/cli/cli.cc @@ -2683,6 +2683,7 @@ int main (const int argc, const char* argv[]) { flags = "-std=c++2a -ObjC++ -v"; flags += " -framework UniformTypeIdentifiers"; flags += " -framework CoreBluetooth"; + flags += " -framework CoreLocation"; flags += " -framework Network"; flags += " -framework UserNotifications"; flags += " -framework WebKit"; @@ -2940,6 +2941,21 @@ int main (const int argc, const char* argv[]) { manifestContext["android_manifest_xml_permissions"] = ""; + if (settings["permission_allow_geolocation"] != "false") { + manifestContext["android_manifest_xml_permissions"] += "\n"; + manifestContext["android_manifest_xml_permissions"] += "\n"; + } + + if (settings["permission_allow_user_media"] != "false") { + if (settings["permission_allow_camera"] != "false") { + manifestContext["android_manifest_xml_permissions"] += "\n"; + } + + if (settings["permission_allow_microphone"] != "false") { + manifestContext["android_manifest_xml_permissions"] += "\n"; + } + } + if (settings["android_manifest_permissions"].size() > 0) { settings["android_manifest_permissions"] = replace(settings["android_manifest_permissions"], ",", " "); for (auto const &value: parseStringList(settings["android_manifest_permissions"])) { @@ -3749,6 +3765,7 @@ int main (const int argc, const char* argv[]) { settings["build_extensions_" + extension + "_ios_compiler_flags"] + " -framework UniformTypeIdentifiers" + " -framework CoreBluetooth" + + " -framework CoreLocation" + " -framework Network" + " -framework UserNotifications" + " -framework WebKit" + @@ -3872,6 +3889,7 @@ int main (const int argc, const char* argv[]) { << " -F " << iosSdkPath << "/System/Library/Frameworks/" << " -framework UniformTypeIdentifiers" << " -framework CoreBluetooth" + << " -framework CoreLocation" << " -framework Foundation" << " -framework Network" << " -framework UserNotifications" @@ -4643,6 +4661,7 @@ int main (const int argc, const char* argv[]) { if (platform.mac) { compilerFlags += " -framework UniformTypeIdentifiers"; compilerFlags += " -framework CoreBluetooth"; + compilerFlags += " -framework CoreLocation"; compilerFlags += " -framework Network"; compilerFlags += " -framework UserNotifications"; compilerFlags += " -framework WebKit"; @@ -5476,20 +5495,29 @@ int main (const int argc, const char* argv[]) { } } - FileSystemWatcher* watcher = new FileSystemWatcher(sources); - auto watching = watcher->start([&]( + // allow changes to 'socket.ini' to be observed + sources.push_back("socket.ini"); + + FileSystemWatcher* sourcesWatcher = new FileSystemWatcher(sources); + auto watchingSources = sourcesWatcher->start([=]( const String& path, const Vector& events, const FileSystemWatcher::Context& context ) { + auto settingsForSourcesWatcher = settings; + extendMap( + settingsForSourcesWatcher, + parseINI(readFile(targetPath / "socket.ini")) + ); + handleBuildPhaseForCopyMappedFiles( - settings, + settingsForSourcesWatcher, targetPlatform, pathResourcesRelativeToUserBuild ); handleBuildPhaseForUserScript( - settings, + settingsForSourcesWatcher, targetPlatform, pathResourcesRelativeToUserBuild, targetPath, @@ -5497,10 +5525,10 @@ int main (const int argc, const char* argv[]) { false ); - log("file '" + path +"' did change"); + log("File '" + path + "' did change"); }); - if (!watching) { + if (!watchingSources) { log("Unable to start watching"); exit(1); } diff --git a/src/cli/templates.hh b/src/cli/templates.hh index 7c5a82cebb..31cf9f21d8 100644 --- a/src/cli/templates.hh +++ b/src/cli/templates.hh @@ -193,6 +193,16 @@ constexpr auto gPListInfo = R"XML( This app needs access to the camera NSBluetoothAlwaysUsageDescription The app would like to discover and connect to peers + + NSLocationUsageDescription + {{meta_title}} would like access to your location + + NSLocationWhenInUseUsageDescription + {{meta_title}} would like access to your location while open + + NSLocationAlwaysAndWhenInUseUsageDescription + {{meta_title}} would like access to your location + NSMainNibFile MainMenu LSMultipleInstancesProhibited @@ -317,7 +327,9 @@ constexpr auto gAndroidManifest = R"XML( > + android:exported="true" + android:configChanges="orientation|screenSize" + > @@ -974,12 +986,21 @@ constexpr auto gXCodePlist = R"XML( NSHighResolutionCapable + NSLocalNetworkUsageDescription - The app would like to discover and connect to peers + {{meta_title}} would like to discover and connect to peers NSBluetoothAlwaysUsageDescription - The app would like to discover and connect to peers + {{meta_title}} would like to discover and connect to peers NSBluetoothPeripheralUsageDescription - The app would like to discover and connect to peers + {{meta_title}} would like to discover and connect to peers + + NSLocationUsageDescription + {{meta_title}} would like access to your location + NSLocationWhenInUseUsageDescription + {{meta_title}} would like access to your location while open + NSLocationAlwaysAndWhenInUseUsageDescription + {{meta_title}} would like access to your location + NSRequiresAquaSystemAppearance NSSupportsAutomaticGraphicsSwitching @@ -1426,6 +1447,51 @@ sources = "src" ; default value: "/" root = "/" +; Set default 'index.html' path to open for implicit routes +; default value: "" +; default_index = "" + +[permissions] +; Allow/Disallow fullscreen in application +; default value: true +; allow_fullscreen = true + +; Allow/Disallow microphone in application +; default value: true +; allow_microphone = true + +; Allow/Disallow camera in application +; default value: true +; allow_camera = true + +; Allow/Disallow user media (microphone + camera) in application +; default value: true +; allow_user_media = true + +; Allow/Disallow geolocation in application +; default value: true +; allow_geolocation = true + +; Allow/Disallow notifications in application +; default value: true +; allow_notifications = true + +; Allow/Disallow sensors in application +; default value: true +; allow_sensors = true + +; Allow/Disallow clipboard in application +; default value: true +; allow_clipboard = true + +; Allow/Disallow bluetooth in application +; default value: true +; allow_bluetooth = true + +; Allow/Disallow data access in application +; default value: true +; allow_data_access = true + ; Enable watch mode ; default value: false watch = false diff --git a/src/common.hh b/src/common.hh index a3275f8c6c..d16f149350 100644 --- a/src/common.hh +++ b/src/common.hh @@ -369,8 +369,12 @@ namespace SSC { const String& separator ) { StringStream joined; + auto missing = vector.size(); for (const auto& item : vector) { - joined << item << separator << " "; + joined << item; + if (--missing > 0) { + joined << separator << " "; + } } return trim(joined.str()); diff --git a/src/window/apple.mm b/src/window/apple.mm index 3c7e8660fe..a6bae6c171 100644 --- a/src/window/apple.mm +++ b/src/window/apple.mm @@ -50,6 +50,145 @@ @implementation SSCBridgedWebView int lastX = 0; int lastY = 0; +- (void) webView: (WKWebView*) webView + requestDeviceOrientationAndMotionPermissionForOrigin: (WKSecurityOrigin*) origin + initiatedByFrame: (WKFrameInfo*) frame + decisionHandler: (void (^)(WKPermissionDecision decision)) decisionHandler { + static auto userConfig = SSC::getUserConfig(); + + if (userConfig["permissions_allow_device_orientation"] == "false") { + decisionHandler(WKPermissionDecisionDeny); + return; + } + + decisionHandler(WKPermissionDecisionGrant); +} + +- (void) webView: (WKWebView*) webView + requestMediaCapturePermissionForOrigin: (WKSecurityOrigin*) origin + initiatedByFrame: (WKFrameInfo*) frame + type: (WKMediaCaptureType) type + decisionHandler: (void (^)(WKPermissionDecision decision)) decisionHandler { + static auto userConfig = SSC::getUserConfig(); + + if (userConfig["permissions_allow_user_media"] == "false") { + decisionHandler(WKPermissionDecisionDeny); + return; + } + + if (type == WKMediaCaptureTypeCameraAndMicrophone) { + if ( + userConfig["permissions_allow_camera"] == "false" || + userConfig["permissions_allow_microphone"] == "false" + ) { + decisionHandler(WKPermissionDecisionDeny); + return; + } + } + + if ( + type == WKMediaCaptureTypeCamera && + userConfig["permissions_allow_camera"] == "false" + ) { + decisionHandler(WKPermissionDecisionDeny); + return; + } + + if ( + type == WKMediaCaptureTypeMicrophone && + userConfig["permissions_allow_microphone"] == "false" + ) { + decisionHandler(WKPermissionDecisionDeny); + return; + } + + decisionHandler(WKPermissionDecisionGrant); +} + +- (void) _webView: (WKWebView*) webView + requestGeolocationPermissionForOrigin: (WKSecurityOrigin*) origin + initiatedByFrame: (WKFrameInfo*) frame + decisionHandler: (void (^)(WKPermissionDecision decision)) decisionHandler { + decisionHandler(WKPermissionDecisionGrant); +} + +- (void) _webView: (WKWebView*) webView + requestGeolocationPermissionForFrame: (WKFrameInfo*) frame + decisionHandler: (void (^)(WKPermissionDecision decision)) decisionHandler { + + decisionHandler(WKPermissionDecisionGrant); +} + +- (void) webView: (WKWebView*) webView + runJavaScriptAlertPanelWithMessage: (NSString*) message + initiatedByFrame: (WKFrameInfo*) frame + completionHandler: (void (^)(void)) completionHandler { +#if TARGET_OS_IPHONE || TARGET_OS_IPHONE + auto alert = [UIAlertController + alertControllerWithTitle: nil + message: message + preferredStyle: UIAlertControllerStyleAlert + ]; + + auto ok = [UIAlertAction + actionWithTitle: @"OK" + style: UIAlertActionStyleDefault + handler: ^(UIAlertAction * action) { + completionHandler(); + }]; + + [alert addAction: ok]; + + [webView presentViewController:alert animated: YES completion: nil]; +#else + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText: message]; + [alert setInformativeText: message]; + [alert addButtonWithTitle: @"OK"]; + [alert runModal]; + completionHandler(); +#endif +} + +- (void) webView: (WKWebView*) webView + runJavaScriptConfirmPanelWithMessage: (NSString*) message + initiatedByFrame: (WKFrameInfo*) frame + completionHandler: (void (^)(BOOL result)) completionHandler { +#if TARGET_OS_IPHONE || TARGET_OS_IPHONE + auto alert = [UIAlertController + alertControllerWithTitle: nil + message: message + preferredStyle: UIAlertControllerStyleAlert + ]; + + auto ok = [UIAlertAction + actionWithTitle: @"OK" + style: UIAlertActionStyleDefault + handler: ^(UIAlertAction * action) { + completionHandler(YES); + }]; + + auto cancel = [UIAlertAction + actionWithTitle: @"Cancel" + style: UIAlertActionStyleDefault + handler: ^(UIAlertAction * action) { + completionHandler(NO); + }]; + + [alert addAction: ok]; + [alert addAction: cancel]; + + [webView presentViewController:alert animated: YES completion: nil]; +#else + NSAlert *alert = [[NSAlert alloc] init]; + [alert setMessageText: message]; + [alert setInformativeText: message]; + [alert addButtonWithTitle: @"OK"]; + [alert addButtonWithTitle: @"Cancel"]; + completionHandler([alert runModal] == NSAlertFirstButtonReturn); +#endif +} + - (BOOL) prepareForDragOperation: (id)info { [info setDraggingFormation: NSDraggingFormationNone]; return NO; @@ -413,50 +552,6 @@ - (NSString*) filePromiseProvider: (NSFilePromiseProvider*)filePromiseProvider f static bool isDelegateSet = false; Window::Window (App& app, WindowOptions opts) : app(app), opts(opts) { - this->bridge = new IPC::Bridge(app.core); - - this->bridge->router.dispatchFunction = [this] (auto callback) { - this->app.dispatch(callback); - }; - - this->bridge->router.evaluateJavaScriptFunction = [this](auto js) { - dispatch_async(dispatch_get_main_queue(), ^{ this->eval(js); }); - }; - - this->bridge->router.map("window.eval", [=](auto message, auto router, auto reply) { - auto value = message.value; - auto seq = message.seq; - auto script = [NSString stringWithUTF8String: value.c_str()]; - - dispatch_async(dispatch_get_main_queue(), ^{ - [webview evaluateJavaScript: script completionHandler: ^(id result, NSError *error) { - if (result) { - auto msg = String([[NSString stringWithFormat:@"%@", result] UTF8String]); - this->bridge->router.send(seq, msg, Post{}); - } else if (error) { - auto exception = (NSString *) error.userInfo[@"WKJavaScriptExceptionMessage"]; - auto message = [[NSString stringWithFormat:@"%@", exception] UTF8String]; - auto err = encodeURIComponent(String(message)); - - if (err == "(null)") { - this->bridge->router.send(seq, "null", Post{}); - return; - } - - auto json = JSON::Object::Entries { - {"err", JSON::Object::Entries { - {"message", String("Error: ") + err} - }} - }; - - this->bridge->router.send(seq, JSON::Object(json).str(), Post{}); - } else { - this->bridge->router.send(seq, "undefined", Post{}); - } - }]; - }); - }); - // Window style: titled, closable, minimizable uint style = NSWindowStyleMaskTitled; @@ -506,6 +601,53 @@ - (NSString*) filePromiseProvider: (NSFilePromiseProvider*)filePromiseProvider f // window.movableByWindowBackground = true; window.titlebarAppearsTransparent = true; + static auto userConfig = SSC::getUserConfig(); + + this->bridge = new IPC::Bridge(app.core); + + this->bridge->router.dispatchFunction = [this] (auto callback) { + this->app.dispatch(callback); + }; + + this->bridge->router.evaluateJavaScriptFunction = [this](auto js) { + dispatch_async(dispatch_get_main_queue(), ^{ this->eval(js); }); + }; + + this->bridge->router.map("window.eval", [=](auto message, auto router, auto reply) { + auto value = message.value; + auto seq = message.seq; + auto script = [NSString stringWithUTF8String: value.c_str()]; + + dispatch_async(dispatch_get_main_queue(), ^{ + [webview evaluateJavaScript: script completionHandler: ^(id result, NSError *error) { + if (result) { + auto msg = String([[NSString stringWithFormat:@"%@", result] UTF8String]); + this->bridge->router.send(seq, msg, Post{}); + } else if (error) { + auto exception = (NSString *) error.userInfo[@"WKJavaScriptExceptionMessage"]; + auto message = [[NSString stringWithFormat:@"%@", exception] UTF8String]; + auto err = encodeURIComponent(String(message)); + + if (err == "(null)") { + this->bridge->router.send(seq, "null", Post{}); + return; + } + + auto json = JSON::Object::Entries { + {"err", JSON::Object::Entries { + {"message", String("Error: ") + err} + }} + }; + + this->bridge->router.send(seq, JSON::Object(json).str(), Post{}); + } else { + this->bridge->router.send(seq, "undefined", Post{}); + } + }]; + }); + }); + + // Initialize WKWebView WKWebViewConfiguration* config = [WKWebViewConfiguration new]; // https://webkit.org/blog/10882/app-bound-domains/ @@ -519,7 +661,13 @@ - (NSString*) filePromiseProvider: (NSFilePromiseProvider*)filePromiseProvider f forURLScheme: @"socket"]; WKPreferences* prefs = [config preferences]; - [prefs setJavaScriptCanOpenWindowsAutomatically:NO]; + prefs.javaScriptCanOpenWindowsAutomatically = NO; + + if (userConfig["permissions_allow_fullscreen"] == "false") { + [prefs setValue: @NO forKey: @"fullScreenEnabled"]; + } else { + [prefs setValue: @YES forKey: @"fullScreenEnabled"]; + } if (SSC::isDebugEnabled()) { [prefs setValue:@YES forKey:@"developerExtrasEnabled"]; @@ -528,6 +676,55 @@ - (NSString*) filePromiseProvider: (NSFilePromiseProvider*)filePromiseProvider f } } + if (userConfig["permissions_allow_clipboard"] == "false") { + [prefs setValue: @NO forKey: @"javaScriptCanAccessClipboard"]; + } else { + [prefs setValue: @YES forKey: @"javaScriptCanAccessClipboard"]; + } + + if (userConfig["permissions_allow_data_access"] == "false") { + [prefs setValue: @NO forKey: @"storageAPIEnabled"]; + } else { + [prefs setValue: @YES forKey: @"storageAPIEnabled"]; + } + + if (userConfig["permissions_allow_device_orientation"] == "false") { + [prefs setValue: @NO forKey: @"deviceOrientationEventEnabled"]; + } else { + [prefs setValue: @YES forKey: @"deviceOrientationEventEnabled"]; + } + + if (userConfig["permissions_allow_notifications"] == "false") { + [prefs setValue: @NO forKey: @"appBadgeEnabled"]; + [prefs setValue: @NO forKey: @"notificationsEnabled"]; + [prefs setValue: @NO forKey: @"notificationEventEnabled"]; + } else { + [prefs setValue: @YES forKey: @"appBadgeEnabled"]; + [prefs setValue: @YES forKey: @"notificationsEnabled"]; + [prefs setValue: @YES forKey: @"notificationEventEnabled"]; + } + + #if !TARGET_OS_IPHONE + [prefs setValue: @YES forKey: @"cookieEnabled"]; + + if (userConfig["permissions_allow_user_media"] == "false") { + [prefs setValue: @NO forKey: @"mediaStreamEnabled"]; + } else { + [prefs setValue: @YES forKey: @"mediaStreamEnabled"]; + } + #endif + + [prefs setValue: @YES forKey: @"offlineApplicationCacheIsEnabled"]; + +/* + + if (userConfig["permissions_allow_geolocation"] == "false") { + [prefs setValue: @NO forKey: @"WebKitAlwaysRequestGeolocationPermission"]; + } else { + [prefs setValue: @YES forKey: @"WebKitAlwaysRequestGeolocationPermission"]; + } +*/ + WKUserContentController* controller = [config userContentController]; // Add preload script, normalizing the interface to be cross-platform. @@ -597,6 +794,7 @@ - (NSString*) filePromiseProvider: (NSFilePromiseProvider*)filePromiseProvider f SSCNavigationDelegate *navDelegate = [[SSCNavigationDelegate alloc] init]; [webview setNavigationDelegate: navDelegate]; + webview.UIDelegate = webview; if (!isDelegateSet) { isDelegateSet = true; diff --git a/src/window/linux.cc b/src/window/linux.cc index 697580d5b3..b83370e041 100644 --- a/src/window/linux.cc +++ b/src/window/linux.cc @@ -184,7 +184,105 @@ namespace SSC { this ); - webview = webkit_web_view_new_with_user_content_manager(cm); + + static auto userConfig = SSC::getUserConfig(); + auto webContext = webkit_web_context_get_default(); + auto cookieManager = webkit_web_context_get_cookie_manager(webContext); + auto settings = webkit_settings_new(); + auto policies = webkit_website_policies_new_with_policies( + "autoplay", userConfig["permission_allow_autoplay"] != "false" ? WEBKIT_AUTOPLAY_ALLOW : WEBKIT_AUTOPLAY_DENY, + NULL + ); + + webview = GTK_WIDGET(WEBKIT_WEB_VIEW(g_object_new(WEBKIT_TYPE_WEB_VIEW, + "web-context", webContext, + "settings", settings, + "user-content-manager", cm, + "website-policies", policies, + NULL + ))); + + webkit_cookie_manager_set_accept_policy(cookieManager, WEBKIT_COOKIE_POLICY_ACCEPT_ALWAYS); + + g_signal_connect( + G_OBJECT(webview), + "permission-request", + G_CALLBACK(+[]( + WebKitWebView* webview, + WebKitPermissionRequest *request, + gpointer userData + ) -> bool { + static auto userConfig = SSC::getUserConfig(); + + if (WEBKIT_IS_GEOLOCATION_PERMISSION_REQUEST(request)) { + if (userConfig["permissions_allow_geolocation"] != "false") { + webkit_permission_request_allow(request); + return TRUE; + } else { + webkit_permission_request_deny(request); + return FALSE; + } + } else if (WEBKIT_IS_NOTIFICATION_PERMISSION_REQUEST(request)) { + if (userConfig["permissions_allow_notifications"] != "false") { + webkit_permission_request_allow(request); + return TRUE; + } else { + webkit_permission_request_deny(request); + return FALSE; + } + } else if (WEBKIT_IS_USER_MEDIA_PERMISSION_REQUEST(request)) { + if (userConfig["permissions_allow_user_media"] == "false") { + webkit_permission_request_deny(request); + return FALSE; + } else { + if (webkit_user_media_permission_is_for_audio_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) { + if (userConfig["permissions_allow_microphone"] == "false") { + webkit_permission_request_deny(request); + return FALSE; + } + } + + if (webkit_user_media_permission_is_for_video_device(WEBKIT_USER_MEDIA_PERMISSION_REQUEST(request))) { + if (userConfig["permissions_allow_camera"] == "false") { + webkit_permission_request_deny(request); + return FALSE; + } + } + + webkit_permission_request_allow(request); + return TRUE; + } + } else if (WEBKIT_IS_WEBSITE_DATA_ACCESS_PERMISSION_REQUEST(request)) { + if (userConfig["permissions_allow_data_access"] != "false") { + webkit_permission_request_allow(request); + return TRUE; + } else { + webkit_permission_request_deny(request); + return FALSE; + } + } else if (WEBKIT_IS_DEVICE_INFO_PERMISSION_REQUEST(request)) { + if (userConfig["permissions_allow_device_info"] != "false") { + webkit_permission_request_allow(request); + return TRUE; + } else { + webkit_permission_request_deny(request); + return FALSE; + } + } else if (WEBKIT_IS_MEDIA_KEY_SYSTEM_PERMISSION_REQUEST(request)) { + if (userConfig["permissions_allow_media_key_system"] != "false") { + webkit_permission_request_allow(request); + return TRUE; + } else { + webkit_permission_request_deny(request); + return FALSE; + } + } + + webkit_permission_request_deny(request); + return FALSE; + }), + this + ); g_signal_connect( G_OBJECT(webview), @@ -575,10 +673,16 @@ namespace SSC { ) ); - WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview)); - webkit_settings_set_javascript_can_access_clipboard(settings, true); webkit_settings_set_zoom_text_only(settings, false); + if (userConfig["permissions_allow_clipboard"] != "false") { + webkit_settings_set_javascript_can_access_clipboard(settings, true); + } + + if (userConfig["permissions_allow_fullscreen"] != "false") { + webkit_settings_set_enable_fullscreen(settings, true); + } + GdkRGBA rgba = {0}; webkit_web_view_set_background_color(WEBKIT_WEB_VIEW(webview), &rgba); diff --git a/src/window/win.cc b/src/window/win.cc index 832c66cb38..bea8ceec55 100644 --- a/src/window/win.cc +++ b/src/window/win.cc @@ -699,6 +699,7 @@ namespace SSC { window, Microsoft::WRL::Callback( [&, preload](HRESULT result, ICoreWebView2Controller* c) -> HRESULT { + static auto userConfig = SSC::getUserConfig(); if (c != nullptr) { controller = c; controller->get_CoreWebView2(&webview); @@ -1226,11 +1227,78 @@ namespace SSC { ICoreWebView2 *webview, ICoreWebView2PermissionRequestedEventArgs *args ) -> HRESULT { + static auto userConfig = SSC::getUserConfig(); COREWEBVIEW2_PERMISSION_KIND kind; args->get_PermissionKind(&kind); + if (kind == COREWEBVIEW2_PERMISSION_KIND_MICROPHONE) { + if ( + userConfig["permissions_allow_microphone"] == "false" || + userConfig["permissions_allow_user_media"] == "false" + ) { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } + } + + if (kind == COREWEBVIEW2_PERMISSION_KIND_CAMERA) { + if ( + userConfig["permissions_allow_camera"] == "false" || + userConfig["permissions_allow_user_media"] == "false" + ) { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } + } + + if (kind == COREWEBVIEW2_PERMISSION_KIND_GEOLOCATION) { + if (userConfig["permissions_allow_geolocation"] == "false") { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } + } + + if (kind == COREWEBVIEW2_PERMISSION_KIND_NOTIFICATIONS) { + if (userConfig["permissions_allow_notifications"] == "false") { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } + } + + if (kind == COREWEBVIEW2_PERMISSION_KIND_OTHER_SENSORS) { + if (userConfig["permissions_allow_sensors"] == "false") { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } + } + if (kind == COREWEBVIEW2_PERMISSION_KIND_CLIPBOARD_READ) { - args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + if (userConfig["permissions_allow_clipboard"] == "false") { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } + } + + if (kind == COREWEBVIEW2_PERMISSION_KIND_AUTOPLAY) { + if (userConfig["permissions_allow_autoplay"] == "false") { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } + } + + if (kind == COREWEBVIEW2_PERMISSION_KIND_LOCAL_FONTS) { + if (userConfig["permissions_allow_local_fonts"] == "false") { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_DENY); + } else { + args->put_State(COREWEBVIEW2_PERMISSION_STATE_ALLOW); + } } return S_OK; diff --git a/src/window/window.hh b/src/window/window.hh index 3269f73488..6c63ed4b51 100644 --- a/src/window/window.hh +++ b/src/window/window.hh @@ -22,6 +22,36 @@ > - (NSDragOperation) draggingSession: (NSDraggingSession *) session sourceOperationMaskForDraggingContext: (NSDraggingContext) context; + +- (void) webView: (WKWebView*) webView + requestDeviceOrientationAndMotionPermissionForOrigin: (WKSecurityOrigin*) origin + initiatedByFrame: (WKFrameInfo*) frame + decisionHandler: (void (^)(WKPermissionDecision decision)) decisionHandler; + +- (void) webView: (WKWebView*) webView + requestMediaCapturePermissionForOrigin: (WKSecurityOrigin*) origin + initiatedByFrame: (WKFrameInfo*) frame + type: (WKMediaCaptureType) type + decisionHandler: (void (^)(WKPermissionDecision decision)) decisionHandler; + +- (void) _webView: (WKWebView*) webView + requestGeolocationPermissionForOrigin: (WKSecurityOrigin*) origin + initiatedByFrame: (WKFrameInfo*) frame + decisionHandler: (void (^)(WKPermissionDecision decision)) decisionHandler; + +- (void) _webView: (WKWebView*) webView + requestGeolocationPermissionForFrame: (WKFrameInfo*) frame + decisionHandler: (void (^)(WKPermissionDecision decision)) decisionHandler; + +- (void) webView: (WKWebView*) webView + runJavaScriptAlertPanelWithMessage: (NSString*) message + initiatedByFrame: (WKFrameInfo*) frame + completionHandler: (void (^)(void)) completionHandler; + +- (void) webView: (WKWebView*) webView + runJavaScriptConfirmPanelWithMessage: (NSString*) message + initiatedByFrame: (WKFrameInfo*) frame + completionHandler: (void (^)(BOOL result)) completionHandler; @end #endif