From 5f638157f7de067af17c07ee67942d5061481ee6 Mon Sep 17 00:00:00 2001 From: attila Date: Mon, 8 Apr 2024 17:39:34 +0200 Subject: [PATCH] WebBrowserComponent: Improve native integrations --- BREAKING_CHANGES.md | 32 + CMakeLists.txt | 6 + docs/CMake API.md | 14 + .../Builds/Android/app/CMakeLists.txt | 8 + .../VisualStudio2017/DemoRunner_App.vcxproj | 7 +- .../DemoRunner_App.vcxproj.filters | 15 + .../VisualStudio2019/DemoRunner_App.vcxproj | 7 +- .../DemoRunner_App.vcxproj.filters | 15 + .../VisualStudio2022/DemoRunner_App.vcxproj | 7 +- .../DemoRunner_App.vcxproj.filters | 15 + .../Builds/Android/app/CMakeLists.txt | 8 + .../AudioPerformanceTest_App.vcxproj | 7 +- .../AudioPerformanceTest_App.vcxproj.filters | 15 + .../Builds/Android/app/CMakeLists.txt | 8 + .../AudioPluginHost_App.vcxproj | 7 +- .../AudioPluginHost_App.vcxproj.filters | 15 + .../AudioPluginHost_App.vcxproj | 7 +- .../AudioPluginHost_App.vcxproj.filters | 15 + .../AudioPluginHost_App.vcxproj | 7 +- .../AudioPluginHost_App.vcxproj.filters | 15 + .../BinaryBuilder_ConsoleApp.vcxproj | 2 +- .../BinaryBuilder_ConsoleApp.vcxproj.filters | 3 + extras/Build/CMake/FindWebView2.cmake | 92 ++ extras/Build/CMake/JUCEUtils.cmake | 16 + .../Builds/Android/app/CMakeLists.txt | 8 + .../NetworkGraphicsDemo_App.vcxproj | 7 +- .../NetworkGraphicsDemo_App.vcxproj.filters | 15 + .../VisualStudio2017/Projucer_App.vcxproj | 7 +- .../Projucer_App.vcxproj.filters | 15 + .../VisualStudio2019/Projucer_App.vcxproj | 7 +- .../Projucer_App.vcxproj.filters | 15 + .../VisualStudio2022/Projucer_App.vcxproj | 7 +- .../Projucer_App.vcxproj.filters | 15 + .../ProjectSaving/jucer_ProjectExport_MSVC.h | 24 +- .../UnitTestRunner_ConsoleApp.vcxproj | 7 +- .../UnitTestRunner_ConsoleApp.vcxproj.filters | 15 + .../UnitTestRunner_ConsoleApp.vcxproj | 7 +- .../UnitTestRunner_ConsoleApp.vcxproj.filters | 15 + .../UnitTestRunner_ConsoleApp.vcxproj | 7 +- .../UnitTestRunner_ConsoleApp.vcxproj.filters | 15 + .../WindowsDLL_StaticLibrary.vcxproj | 7 +- .../WindowsDLL_StaticLibrary.vcxproj.filters | 15 + .../juce_audio_processors.h | 1 + .../utilities/juce_ParameterAttachments.cpp | 156 +++ .../utilities/juce_ParameterAttachments.h | 140 +++ modules/juce_core/juce_core.h | 1 + modules/juce_core/misc/juce_OptionsHelpers.h | 44 + modules/juce_gui_basics/widgets/juce_Slider.h | 88 +- .../detail/juce_WebControlRelayEvents.h | 255 +++++ modules/juce_gui_extra/juce_gui_extra.cpp | 3 + modules/juce_gui_extra/juce_gui_extra.h | 36 +- .../misc/juce_WebBrowserComponent.cpp | 663 +++++++++++++ .../misc/juce_WebBrowserComponent.h | 294 +++++- .../misc/juce_WebControlRelays.cpp | 256 +++++ .../misc/juce_WebControlRelays.h | 248 +++++ .../java/app/com/rmsl/juce/JuceWebView.java | 141 --- .../java/app/com/rmsl/juce/JuceWebView21.java | 141 --- .../app/com/rmsl/juce/JuceWebViewClasses.java | 356 +++++++ .../native/juce_NSViewComponent_mac.mm | 3 - .../juce_WebBrowserComponent_android.cpp | 911 +++++++++++------- .../native/juce_WebBrowserComponent_linux.cpp | 888 +++++++++++++---- .../native/juce_WebBrowserComponent_mac.mm | 730 ++++++++++---- .../juce_WebBrowserComponent_windows.cpp | 537 ++++++----- 63 files changed, 5155 insertions(+), 1288 deletions(-) create mode 100644 extras/Build/CMake/FindWebView2.cmake create mode 100644 modules/juce_core/misc/juce_OptionsHelpers.h create mode 100644 modules/juce_gui_extra/detail/juce_WebControlRelayEvents.h create mode 100644 modules/juce_gui_extra/misc/juce_WebControlRelays.cpp create mode 100644 modules/juce_gui_extra/misc/juce_WebControlRelays.h delete mode 100644 modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebView.java delete mode 100644 modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebView21.java create mode 100644 modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebViewClasses.java diff --git a/BREAKING_CHANGES.md b/BREAKING_CHANGES.md index 8ffdf20477..f4056f1584 100644 --- a/BREAKING_CHANGES.md +++ b/BREAKING_CHANGES.md @@ -4,6 +4,38 @@ ## Change +The `WebBrowserComponent::pageAboutToLoad()` function on Android now only +receives callbacks for entire page navigation events, as opposed to every +resource fetch operation. Returning `false` from the function now prevents +this operation from taking any effect, as opposed to producing potentially +visible error messages. + +**Possible Issues** + +Code that previously depended on the ability to allow or fail resource +requests on Android may fail to work correctly. + +**Workaround** + +Navigating to webpages can still be prevented by returning `false` from this +function, similarly to other platforms. + +Resource requests sent to the domain returned by +`WebBrowserComponent::getResourceProviderRoot()` can be served or rejected by +using the `WebBrowserComponent::ResourceProvider` feature. + +Resource requests sent to other domains can not be controlled on Android +anymore. + +**Rationale** + +Prior to this change there was no way to reject a page load operation without +any visible effect, like there was on the other platforms. The fine grained per +resource control was not possible on other platforms. This change makes the +Android implementation more consistent with the other platforms. + +## Change + The minimum supported compilers and deployment targets have been updated, with the new minimums listed in the top level [README](README.md). diff --git a/CMakeLists.txt b/CMakeLists.txt index 5b899601cf..86eeeff64e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -123,6 +123,12 @@ if(JUCE_BUILD_EXAMPLES) add_subdirectory(examples) endif() +string(CONCAT webview2_option_message "Location that overrides the default directory where our " + "FindWebView2 script is looking for the " + "*Microsoft.Web.WebView2* directory") + +option(JUCE_WEBVIEW2_PACKAGE_LOCATION ${webview2_option_message} "") + # ================================================================================================== # Install configuration diff --git a/docs/CMake API.md b/docs/CMake API.md index bbc1ea1b78..6463cb9b3d 100644 --- a/docs/CMake API.md +++ b/docs/CMake API.md @@ -232,6 +232,15 @@ option is enabled, which may improve build times for established products that u handle plugin bundle structures, icons, plists, and so on. If this option is enabled, then `JUCE_ENABLE_MODULE_SOURCE_GROUPS` will have no effect. +#### `JUCE_WEBVIEW2_PACKAGE_LOCATION` + +You can ask JUCE to link the WebView2 library statically to your target on Windows, by specifying +the `NEEDS_WEBVIEW2` option when creating your target. In this case JUCE will search for the +WebView2 package on your system. The default search location is +`%userprofile%\AppData\Local\PackageManagement\NuGet\Packages`. This location can be overriden by +specifying this option. The provided location should contain the `*Microsoft.Web.WebView2*` +directory. + ### Functions #### `juce_add_` @@ -406,6 +415,11 @@ attributes directly to these creation functions, rather than adding them later. are set on a JUCE target. By default, we don't link Webkit because you might not need it, but if you get linker or include errors that reference Webkit, just set this argument to `TRUE`. +`NEEDS_WEBVIEW2` +- On Windows, JUCE may or may not need to link to WebView2 depending on the compile definitions that + are set on a JUCE target. By default, we don't link WebView2 because you might not need it, but + if you get linker or include errors that reference WebView2, just set this argument to `TRUE`. + `NEEDS_STORE_KIT` - On macOS, JUCE may or may not need to link to StoreKit depending on the compile definitions that are set on a JUCE target. By default, we don't link StoreKit because you might not need it, but diff --git a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt index c9299b07d4..3b52b495c5 100644 --- a/examples/DemoRunner/Builds/Android/app/CMakeLists.txt +++ b/examples/DemoRunner/Builds/Android/app/CMakeLists.txt @@ -1077,6 +1077,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_core/misc/juce_EnumHelpers.h" "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" + "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -1983,6 +1984,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.h" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.h" + "../../../../../modules/juce_gui_extra/detail/juce_WebControlRelayEvents.h" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.cpp" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.h" "../../../../../modules/juce_gui_extra/embedding/juce_ActiveXControlComponent.h" @@ -2014,6 +2016,8 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_gui_extra/misc/juce_SystemTrayIconComponent.h" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.cpp" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.h" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.cpp" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.h" "../../../../../modules/juce_gui_extra/native/juce_ActiveXComponent_windows.cpp" "../../../../../modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp" "../../../../../modules/juce_gui_extra/native/juce_AppleRemote_mac.mm" @@ -3171,6 +3175,7 @@ set_source_files_properties( "../../../../../modules/juce_core/misc/juce_EnumHelpers.h" "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" + "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -4077,6 +4082,7 @@ set_source_files_properties( "../../../../../modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.h" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.h" + "../../../../../modules/juce_gui_extra/detail/juce_WebControlRelayEvents.h" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.cpp" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.h" "../../../../../modules/juce_gui_extra/embedding/juce_ActiveXControlComponent.h" @@ -4108,6 +4114,8 @@ set_source_files_properties( "../../../../../modules/juce_gui_extra/misc/juce_SystemTrayIconComponent.h" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.cpp" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.h" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.cpp" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.h" "../../../../../modules/juce_gui_extra/native/juce_ActiveXComponent_windows.cpp" "../../../../../modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp" "../../../../../modules/juce_gui_extra/native/juce_AppleRemote_mac.mm" diff --git a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj index 1b6b8a7b0b..6e05b6b30e 100644 --- a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj @@ -2657,6 +2657,9 @@ true + + true + true @@ -3405,6 +3408,7 @@ + @@ -3860,6 +3864,7 @@ + @@ -3879,6 +3884,7 @@ + @@ -3965,5 +3971,4 @@ - diff --git a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters index a491dbb4c5..69909ca0cb 100644 --- a/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2017/DemoRunner_App.vcxproj.filters @@ -650,6 +650,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -3379,6 +3382,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -5355,6 +5361,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6720,6 +6729,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -6777,6 +6789,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj index 03c4030665..cfea614a1e 100644 --- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj @@ -2657,6 +2657,9 @@ true + + true + true @@ -3405,6 +3408,7 @@ + @@ -3860,6 +3864,7 @@ + @@ -3879,6 +3884,7 @@ + @@ -3965,5 +3971,4 @@ - diff --git a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters index 697dd5488c..90141d94dc 100644 --- a/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2019/DemoRunner_App.vcxproj.filters @@ -650,6 +650,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -3379,6 +3382,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -5355,6 +5361,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6720,6 +6729,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -6777,6 +6789,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj index 5ed0268deb..221ba5ae78 100644 --- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj +++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj @@ -2657,6 +2657,9 @@ true + + true + true @@ -3405,6 +3408,7 @@ + @@ -3860,6 +3864,7 @@ + @@ -3879,6 +3884,7 @@ + @@ -3965,5 +3971,4 @@ - diff --git a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters index 782b8736e1..f191cccb5d 100644 --- a/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters +++ b/examples/DemoRunner/Builds/VisualStudio2022/DemoRunner_App.vcxproj.filters @@ -650,6 +650,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -3379,6 +3382,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -5355,6 +5361,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6720,6 +6729,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -6777,6 +6789,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt index 513bcd0232..5854c58cf4 100644 --- a/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPerformanceTest/Builds/Android/app/CMakeLists.txt @@ -953,6 +953,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_core/misc/juce_EnumHelpers.h" "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" + "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -1762,6 +1763,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.h" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.h" + "../../../../../modules/juce_gui_extra/detail/juce_WebControlRelayEvents.h" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.cpp" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.h" "../../../../../modules/juce_gui_extra/embedding/juce_ActiveXControlComponent.h" @@ -1793,6 +1795,8 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_gui_extra/misc/juce_SystemTrayIconComponent.h" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.cpp" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.h" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.cpp" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.h" "../../../../../modules/juce_gui_extra/native/juce_ActiveXComponent_windows.cpp" "../../../../../modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp" "../../../../../modules/juce_gui_extra/native/juce_AppleRemote_mac.mm" @@ -2747,6 +2751,7 @@ set_source_files_properties( "../../../../../modules/juce_core/misc/juce_EnumHelpers.h" "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" + "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -3556,6 +3561,7 @@ set_source_files_properties( "../../../../../modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.h" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.h" + "../../../../../modules/juce_gui_extra/detail/juce_WebControlRelayEvents.h" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.cpp" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.h" "../../../../../modules/juce_gui_extra/embedding/juce_ActiveXControlComponent.h" @@ -3587,6 +3593,8 @@ set_source_files_properties( "../../../../../modules/juce_gui_extra/misc/juce_SystemTrayIconComponent.h" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.cpp" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.h" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.cpp" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.h" "../../../../../modules/juce_gui_extra/native/juce_ActiveXComponent_windows.cpp" "../../../../../modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp" "../../../../../modules/juce_gui_extra/native/juce_AppleRemote_mac.mm" diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj index c9a884c37e..b43ce54201 100644 --- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj +++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj @@ -2371,6 +2371,9 @@ true + + true + true @@ -2961,6 +2964,7 @@ + @@ -3363,6 +3367,7 @@ + @@ -3382,6 +3387,7 @@ + @@ -3414,5 +3420,4 @@ - diff --git a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters index 5aae037d0b..99437145e5 100644 --- a/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters +++ b/extras/AudioPerformanceTest/Builds/VisualStudio2022/AudioPerformanceTest_App.vcxproj.filters @@ -572,6 +572,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -2956,6 +2959,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -4635,6 +4641,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -5841,6 +5850,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -5898,6 +5910,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt index c3511ab18a..5afbc63479 100644 --- a/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt +++ b/extras/AudioPluginHost/Builds/Android/app/CMakeLists.txt @@ -986,6 +986,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_core/misc/juce_EnumHelpers.h" "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" + "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -1892,6 +1893,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.h" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.h" + "../../../../../modules/juce_gui_extra/detail/juce_WebControlRelayEvents.h" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.cpp" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.h" "../../../../../modules/juce_gui_extra/embedding/juce_ActiveXControlComponent.h" @@ -1923,6 +1925,8 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_gui_extra/misc/juce_SystemTrayIconComponent.h" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.cpp" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.h" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.cpp" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.h" "../../../../../modules/juce_gui_extra/native/juce_ActiveXComponent_windows.cpp" "../../../../../modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp" "../../../../../modules/juce_gui_extra/native/juce_AppleRemote_mac.mm" @@ -2933,6 +2937,7 @@ set_source_files_properties( "../../../../../modules/juce_core/misc/juce_EnumHelpers.h" "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" + "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -3839,6 +3844,7 @@ set_source_files_properties( "../../../../../modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.h" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.h" + "../../../../../modules/juce_gui_extra/detail/juce_WebControlRelayEvents.h" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.cpp" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.h" "../../../../../modules/juce_gui_extra/embedding/juce_ActiveXControlComponent.h" @@ -3870,6 +3876,8 @@ set_source_files_properties( "../../../../../modules/juce_gui_extra/misc/juce_SystemTrayIconComponent.h" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.cpp" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.h" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.cpp" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.h" "../../../../../modules/juce_gui_extra/native/juce_ActiveXComponent_windows.cpp" "../../../../../modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp" "../../../../../modules/juce_gui_extra/native/juce_AppleRemote_mac.mm" diff --git a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj index 1c47f8919a..3b62176222 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj @@ -2505,6 +2505,9 @@ true + + true + true @@ -3141,6 +3144,7 @@ + @@ -3596,6 +3600,7 @@ + @@ -3615,6 +3620,7 @@ + @@ -3681,5 +3687,4 @@ - diff --git a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters index 0b699f6afd..ef6005f60b 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2017/AudioPluginHost_App.vcxproj.filters @@ -614,6 +614,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -3163,6 +3166,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -4911,6 +4917,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6276,6 +6285,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -6333,6 +6345,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj index 7eb2871c58..b7fdf0a2fa 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj @@ -2505,6 +2505,9 @@ true + + true + true @@ -3141,6 +3144,7 @@ + @@ -3596,6 +3600,7 @@ + @@ -3615,6 +3620,7 @@ + @@ -3681,5 +3687,4 @@ - diff --git a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters index f74ce22662..8f917b497d 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2019/AudioPluginHost_App.vcxproj.filters @@ -614,6 +614,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -3163,6 +3166,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -4911,6 +4917,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6276,6 +6285,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -6333,6 +6345,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj index 5af2e0e6fd..79583b920f 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj +++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj @@ -2505,6 +2505,9 @@ true + + true + true @@ -3141,6 +3144,7 @@ + @@ -3596,6 +3600,7 @@ + @@ -3615,6 +3620,7 @@ + @@ -3681,5 +3687,4 @@ - diff --git a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters index 4cfdce5aa2..c59ccfb021 100644 --- a/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters +++ b/extras/AudioPluginHost/Builds/VisualStudio2022/AudioPluginHost_App.vcxproj.filters @@ -614,6 +614,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -3163,6 +3166,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -4911,6 +4917,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6276,6 +6285,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -6333,6 +6345,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj index 40862e0f20..a5a99dcf64 100644 --- a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj +++ b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj @@ -568,6 +568,7 @@ + @@ -668,5 +669,4 @@ - diff --git a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters index b3b7616180..a4fa26def5 100644 --- a/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters +++ b/extras/BinaryBuilder/Builds/VisualStudio2022/BinaryBuilder_ConsoleApp.vcxproj.filters @@ -669,6 +669,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc diff --git a/extras/Build/CMake/FindWebView2.cmake b/extras/Build/CMake/FindWebView2.cmake new file mode 100644 index 0000000000..6bc9da6c28 --- /dev/null +++ b/extras/Build/CMake/FindWebView2.cmake @@ -0,0 +1,92 @@ +# ============================================================================== +# +# This file is part of the JUCE framework. +# Copyright (c) Raw Material Software Limited +# +# JUCE is an open source framework subject to commercial or open source +# licensing. +# +# By downloading, installing, or using the JUCE framework, or combining the +# JUCE framework with any other source code, object code, content or any other +# copyrightable work, you agree to the terms of the JUCE End User Licence +# Agreement, and all incorporated terms including the JUCE Privacy Policy and +# the JUCE Website Terms of Service, as applicable, which will bind you. If you +# do not agree to the terms of these agreements, we will not license the JUCE +# framework to you, and you must discontinue the installation or download +# process and cease use of the JUCE framework. +# +# JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ +# JUCE Privacy Policy: https://juce.com/juce-privacy-policy +# JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ +# +# Or: +# +# You may also use this code under the terms of the AGPLv3: +# https://www.gnu.org/licenses/agpl-3.0.en.html +# +# THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL +# WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF +# MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. +# +# ============================================================================== + +include(FindPackageHandleStandardArgs) + +if(JUCE_WEBVIEW2_PACKAGE_LOCATION) + set(initial_search_dir ${JUCE_WEBVIEW2_PACKAGE_LOCATION}) +else() + set(initial_search_dir "$ENV{USERPROFILE}/AppData/Local/PackageManagement/NuGet/Packages") +endif() + +file(GLOB subdirs "${initial_search_dir}/*Microsoft.Web.WebView2*") + +if(subdirs) + list(GET subdirs 0 search_dir) + list(LENGTH subdirs num_webview2_packages) + + if(num_webview2_packages GREATER 1) + message(WARNING "Multiple WebView2 packages found in the local NuGet folder. Proceeding with ${search_dir}.") + endif() + + find_path(WebView2_root_dir build/native/include/WebView2.h HINTS ${search_dir}) + + set(WebView2_include_dir "${WebView2_root_dir}/build/native/include") + + if(${CMAKE_SYSTEM_PROCESSOR} STREQUAL "arm64") + set(WebView2_arch arm64) + else() + if(CMAKE_SIZEOF_VOID_P EQUAL 8) + set(WebView2_arch x64) + elseif(CMAKE_SIZEOF_VOID_P EQUAL 4) + set(WebView2_arch x86) + endif() + endif() + + set(WebView2_library "${WebView2_root_dir}/build/native/${WebView2_arch}/WebView2LoaderStatic.lib") +elseif(NOT WebView2_FIND_QUIETLY) + message(WARNING + "WebView2 wasn't found in the the local NuGet folder." + "\n" + "To install NuGet and the WebView2 package containing the statically linked library, " + "open a PowerShell and issue the following commands" + "\n" + "> Register-PackageSource -provider NuGet -name nugetRepository -location https://www.nuget.org/api/v2\n" + "> Install-Package NuGet.CommandLine -Scope CurrentUser\n" + "> Install-Package Microsoft.Web.WebView2 -Scope CurrentUser -RequiredVersion 1.0.1901.177\n") +endif() + +find_package_handle_standard_args(WebView2 DEFAULT_MSG WebView2_include_dir WebView2_library) + +if(WebView2_FOUND) + set(WebView2_INCLUDE_DIRS ${WebView2_include_dir}) + set(WebView2_LIBRARIES ${WebView2_library} ) + + mark_as_advanced(WebView2_library WebView2_include_dir WebView2_root_dir) + + if(NOT TARGET juce_webview2) + add_library(juce_webview2 INTERFACE) + add_library(juce::juce_webview2 ALIAS juce_webview2) + target_include_directories(juce_webview2 INTERFACE ${WebView2_INCLUDE_DIRS}) + target_link_libraries(juce_webview2 INTERFACE ${WebView2_LIBRARIES}) + endif() +endif() diff --git a/extras/Build/CMake/JUCEUtils.cmake b/extras/Build/CMake/JUCEUtils.cmake index 7f02473b59..61e0d88a5b 100644 --- a/extras/Build/CMake/JUCEUtils.cmake +++ b/extras/Build/CMake/JUCEUtils.cmake @@ -275,6 +275,17 @@ function(_juce_link_optional_libraries target) if(CMAKE_SYSTEM_NAME STREQUAL "iOS" AND needs_camera) _juce_link_frameworks("${target}" PRIVATE ImageIO) endif() + elseif(WIN32) + get_target_property(needs_webview2 ${target} JUCE_NEEDS_WEBVIEW2) + + if (needs_webview2) + if(NOT ("${JUCE_CMAKE_UTILS_DIR}" IN_LIST CMAKE_MODULE_PATH)) + list(APPEND CMAKE_MODULE_PATH "${JUCE_CMAKE_UTILS_DIR}") + endif() + + find_package(WebView2 REQUIRED) + target_link_libraries(${target} PRIVATE juce::juce_webview2) + endif() endif() endfunction() @@ -1864,6 +1875,7 @@ function(_juce_initialise_target target) COMPANY_EMAIL NEEDS_CURL # Set this true if you want to link curl on Linux NEEDS_WEB_BROWSER # Set this true if you want to link webkit on Linux + NEEDS_WEBVIEW2 # Set this true if you want to link WebView2 statically on Windows NEEDS_STORE_KIT # Set this true if you want in-app-purchases on Mac PUSH_NOTIFICATIONS_ENABLED NETWORK_MULTICAST_ENABLED @@ -2130,6 +2142,10 @@ function(juce_add_pip header) list(APPEND extra_target_args PLUGINHOST_AU TRUE) endif() + if("JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING=1" IN_LIST pip_moduleflags) + list(APPEND extra_target_args NEEDS_WEBVIEW2 TRUE) + endif() + if(pip_kind STREQUAL "AudioProcessor") _juce_get_metadata("${metadata_dict}" documentControllerClass JUCE_PIP_DOCUMENTCONTROLLER_CLASS) diff --git a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt index 19ae61a69b..1e319523c0 100644 --- a/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt +++ b/extras/NetworkGraphicsDemo/Builds/Android/app/CMakeLists.txt @@ -957,6 +957,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_core/misc/juce_EnumHelpers.h" "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" + "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -1781,6 +1782,7 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.h" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.h" + "../../../../../modules/juce_gui_extra/detail/juce_WebControlRelayEvents.h" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.cpp" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.h" "../../../../../modules/juce_gui_extra/embedding/juce_ActiveXControlComponent.h" @@ -1812,6 +1814,8 @@ add_library( ${BINARY_NAME} "../../../../../modules/juce_gui_extra/misc/juce_SystemTrayIconComponent.h" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.cpp" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.h" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.cpp" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.h" "../../../../../modules/juce_gui_extra/native/juce_ActiveXComponent_windows.cpp" "../../../../../modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp" "../../../../../modules/juce_gui_extra/native/juce_AppleRemote_mac.mm" @@ -2831,6 +2835,7 @@ set_source_files_properties( "../../../../../modules/juce_core/misc/juce_EnumHelpers.h" "../../../../../modules/juce_core/misc/juce_EnumHelpers_test.cpp" "../../../../../modules/juce_core/misc/juce_Functional.h" + "../../../../../modules/juce_core/misc/juce_OptionsHelpers.h" "../../../../../modules/juce_core/misc/juce_Result.cpp" "../../../../../modules/juce_core/misc/juce_Result.h" "../../../../../modules/juce_core/misc/juce_RuntimePermissions.cpp" @@ -3655,6 +3660,7 @@ set_source_files_properties( "../../../../../modules/juce_gui_extra/code_editor/juce_LuaCodeTokeniser.h" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.cpp" "../../../../../modules/juce_gui_extra/code_editor/juce_XMLCodeTokeniser.h" + "../../../../../modules/juce_gui_extra/detail/juce_WebControlRelayEvents.h" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.cpp" "../../../../../modules/juce_gui_extra/documents/juce_FileBasedDocument.h" "../../../../../modules/juce_gui_extra/embedding/juce_ActiveXControlComponent.h" @@ -3686,6 +3692,8 @@ set_source_files_properties( "../../../../../modules/juce_gui_extra/misc/juce_SystemTrayIconComponent.h" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.cpp" "../../../../../modules/juce_gui_extra/misc/juce_WebBrowserComponent.h" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.cpp" + "../../../../../modules/juce_gui_extra/misc/juce_WebControlRelays.h" "../../../../../modules/juce_gui_extra/native/juce_ActiveXComponent_windows.cpp" "../../../../../modules/juce_gui_extra/native/juce_AndroidViewComponent.cpp" "../../../../../modules/juce_gui_extra/native/juce_AppleRemote_mac.mm" diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj index 70ca26142f..f45470e92d 100644 --- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj +++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj @@ -2392,6 +2392,9 @@ true + + true + true @@ -3052,6 +3055,7 @@ + @@ -3461,6 +3465,7 @@ + @@ -3480,6 +3485,7 @@ + @@ -3548,5 +3554,4 @@ - diff --git a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters index 1695c5bda9..10211f5c14 100644 --- a/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters +++ b/extras/NetworkGraphicsDemo/Builds/VisualStudio2022/NetworkGraphicsDemo_App.vcxproj.filters @@ -581,6 +581,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -3010,6 +3013,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -4776,6 +4782,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6003,6 +6012,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -6060,6 +6072,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj index f2a40dbad3..7f0bc7daaa 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj @@ -1574,6 +1574,9 @@ true + + true + true @@ -1869,6 +1872,7 @@ + @@ -2278,6 +2282,7 @@ + @@ -2297,6 +2302,7 @@ + @@ -2344,5 +2350,4 @@ - diff --git a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters index 9d19b2167c..eb111973b6 100644 --- a/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2017/Projucer_App.vcxproj.filters @@ -305,6 +305,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -1954,6 +1957,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -2754,6 +2760,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -3981,6 +3990,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -4038,6 +4050,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj index 878e82740b..d95c784e46 100644 --- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj @@ -1574,6 +1574,9 @@ true + + true + true @@ -1869,6 +1872,7 @@ + @@ -2278,6 +2282,7 @@ + @@ -2297,6 +2302,7 @@ + @@ -2344,5 +2350,4 @@ - diff --git a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters index ff4d056b6a..4c027a6749 100644 --- a/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2019/Projucer_App.vcxproj.filters @@ -305,6 +305,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -1954,6 +1957,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -2754,6 +2760,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -3981,6 +3990,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -4038,6 +4050,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj index 824e632ab3..46349d6895 100644 --- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj +++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj @@ -1574,6 +1574,9 @@ true + + true + true @@ -1869,6 +1872,7 @@ + @@ -2278,6 +2282,7 @@ + @@ -2297,6 +2302,7 @@ + @@ -2344,5 +2350,4 @@ - diff --git a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters index 3a4884bf14..93e78711c8 100644 --- a/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters +++ b/extras/Projucer/Builds/VisualStudio2022/Projucer_App.vcxproj.filters @@ -305,6 +305,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -1954,6 +1957,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -2754,6 +2760,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -3981,6 +3990,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -4038,6 +4050,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h index bda6742651..d840330b2d 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_MSVC.h @@ -844,11 +844,11 @@ public: } { - auto* importGroup = projectXml.createNewChildElement ("ImportGroup"); - importGroup->setAttribute ("Label", "ExtensionTargets"); - if (owner.shouldAddWebView2Package()) { + auto* importGroup = projectXml.createNewChildElement ("ImportGroup"); + importGroup->setAttribute ("Label", "ExtensionTargets"); + auto packageTargetsPath = "packages\\" + getWebView2PackageName() + "." + getWebView2PackageVersion() + "\\build\\native\\" + getWebView2PackageName() + ".targets"; @@ -856,6 +856,13 @@ public: e->setAttribute ("Project", packageTargetsPath); e->setAttribute ("Condition", "Exists('" + packageTargetsPath + "')"); } + + if (owner.shouldLinkWebView2Statically()) + { + auto* propertyGroup = projectXml.createNewChildElement ("PropertyGroup"); + auto* loaderPref = propertyGroup->createNewChildElement ("WebView2LoaderPreference"); + loaderPref->addTextElement ("Static"); + } } } @@ -1882,11 +1889,18 @@ protected: bool shouldAddWebView2Package() const { return project.getEnabledModules().isModuleEnabled ("juce_gui_extra") - && project.isConfigFlagEnabled ("JUCE_USE_WIN_WEBVIEW2", false); + && ( project.isConfigFlagEnabled ("JUCE_USE_WIN_WEBVIEW2", false) + || project.isConfigFlagEnabled ("JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING", false)); + } + + bool shouldLinkWebView2Statically() const + { + return project.getEnabledModules().isModuleEnabled ("juce_gui_extra") + && project.isConfigFlagEnabled ("JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING", false); } static String getWebView2PackageName() { return "Microsoft.Web.WebView2"; } - static String getWebView2PackageVersion() { return "1.0.902.49"; } + static String getWebView2PackageVersion() { return "1.0.1901.177"; } void createPackagesConfigFile() const { diff --git a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj index 3ab28027ab..ad13bd327d 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj @@ -2513,6 +2513,9 @@ true + + true + true @@ -3237,6 +3240,7 @@ + @@ -3692,6 +3696,7 @@ + @@ -3711,6 +3716,7 @@ + @@ -3815,5 +3821,4 @@ - diff --git a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters index 3dc6f78c05..da2ac2662d 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2017/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -614,6 +614,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -3184,6 +3187,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -5025,6 +5031,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6390,6 +6399,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -6447,6 +6459,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj index 5220e724aa..7e10c32d81 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj @@ -2513,6 +2513,9 @@ true + + true + true @@ -3237,6 +3240,7 @@ + @@ -3692,6 +3696,7 @@ + @@ -3711,6 +3716,7 @@ + @@ -3815,5 +3821,4 @@ - diff --git a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters index e177e837f7..267af7544d 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2019/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -614,6 +614,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -3184,6 +3187,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -5025,6 +5031,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6390,6 +6399,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -6447,6 +6459,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj index fe77a6bfb4..d79d393698 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj +++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj @@ -2513,6 +2513,9 @@ true + + true + true @@ -3237,6 +3240,7 @@ + @@ -3692,6 +3696,7 @@ + @@ -3711,6 +3716,7 @@ + @@ -3815,5 +3821,4 @@ - diff --git a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters index 395dda8a60..9bdc83fe2a 100644 --- a/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters +++ b/extras/UnitTestRunner/Builds/VisualStudio2022/UnitTestRunner_ConsoleApp.vcxproj.filters @@ -614,6 +614,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -3184,6 +3187,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -5025,6 +5031,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -6390,6 +6399,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -6447,6 +6459,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj index f7d042ea04..d1e05c512b 100644 --- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj +++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj @@ -2391,6 +2391,9 @@ true + + true + true @@ -3028,6 +3031,7 @@ + @@ -3437,6 +3441,7 @@ + @@ -3456,6 +3461,7 @@ + @@ -3521,5 +3527,4 @@ - diff --git a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters index 3800118c3f..4ad221d138 100644 --- a/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters +++ b/extras/WindowsDLL/Builds/VisualStudio2022/WindowsDLL_StaticLibrary.vcxproj.filters @@ -575,6 +575,9 @@ {DF95D4BF-E18C-125A-5EBB-8993A06E232C} + + {E0FCBD5F-0B11-D78C-F786-52AB7FEE2383} + {118946F2-AC24-0F09-62D5-753DF87A60CD} @@ -3007,6 +3010,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native @@ -4743,6 +4749,9 @@ JUCE Modules\juce_core\misc + + JUCE Modules\juce_core\misc + JUCE Modules\juce_core\misc @@ -5970,6 +5979,9 @@ JUCE Modules\juce_gui_extra\code_editor + + JUCE Modules\juce_gui_extra\detail + JUCE Modules\juce_gui_extra\documents @@ -6027,6 +6039,9 @@ JUCE Modules\juce_gui_extra\misc + + JUCE Modules\juce_gui_extra\misc + JUCE Modules\juce_gui_extra\native diff --git a/modules/juce_audio_processors/juce_audio_processors.h b/modules/juce_audio_processors/juce_audio_processors.h index 5c20eb19a8..487d047731 100644 --- a/modules/juce_audio_processors/juce_audio_processors.h +++ b/modules/juce_audio_processors/juce_audio_processors.h @@ -64,6 +64,7 @@ #define JUCE_AUDIO_PROCESSORS_H_INCLUDED #include +#include #include //============================================================================== diff --git a/modules/juce_audio_processors/utilities/juce_ParameterAttachments.cpp b/modules/juce_audio_processors/utilities/juce_ParameterAttachments.cpp index 5e0f0a9fcb..2f602c37e7 100644 --- a/modules/juce_audio_processors/utilities/juce_ParameterAttachments.cpp +++ b/modules/juce_audio_processors/utilities/juce_ParameterAttachments.cpp @@ -274,4 +274,160 @@ void ButtonParameterAttachment::buttonClicked (Button*) attachment.setValueAsCompleteGesture (button.getToggleState() ? 1.0f : 0.0f); } +//============================================================================== +#if JUCE_WEB_BROWSER +WebSliderParameterAttachment::WebSliderParameterAttachment (RangedAudioParameter& parameterIn, + WebSliderRelay& sliderStateIn, + UndoManager* undoManager) + : sliderState (sliderStateIn), + parameter (parameterIn), + attachment (parameter, [this] (float newValue) { setValue (newValue); }, undoManager) +{ + sendInitialUpdate(); + sliderState.addListener (this); +} + +WebSliderParameterAttachment::~WebSliderParameterAttachment() +{ + sliderState.removeListener (this); +} + +void WebSliderParameterAttachment::sendInitialUpdate() +{ + const auto range = parameter.getNormalisableRange(); + DynamicObject::Ptr object { new DynamicObject }; + object->setProperty (detail::WebSliderRelayEvents::Event::eventTypeKey, "propertiesChanged"); + object->setProperty ("start", range.start); + object->setProperty ("end", range.end); + object->setProperty ("skew", range.skew); + object->setProperty ("name", parameter.getName (100)); + object->setProperty ("label", parameter.getLabel()); + object->setProperty ("numSteps", parameter.getNumSteps()); + object->setProperty ("interval", range.interval); + sliderState.emitEvent (object.get()); + attachment.sendInitialUpdate(); +} + +void WebSliderParameterAttachment::setValue (float newValue) +{ + const ScopedValueSetter svs (ignoreCallbacks, true); + sliderState.setValue (newValue); +} + +void WebSliderParameterAttachment::sliderValueChanged (WebSliderRelay* slider) +{ + if (ignoreCallbacks) + { + jassertfalse; + return; + } + + attachment.setValueAsPartOfGesture (slider->getValue()); +} + +//============================================================================== +WebToggleButtonParameterAttachment::WebToggleButtonParameterAttachment (RangedAudioParameter& parameterIn, + WebToggleButtonRelay& button, + UndoManager* undoManager) + : relay (button), + parameter (parameterIn), + attachment (parameter, [this] (float f) { setValue (f); }, undoManager) +{ + sendInitialUpdate(); + relay.addListener (this); +} + +WebToggleButtonParameterAttachment::~WebToggleButtonParameterAttachment() +{ + relay.removeListener (this); +} + +void WebToggleButtonParameterAttachment::sendInitialUpdate() +{ + DynamicObject::Ptr object { new DynamicObject }; + object->setProperty (detail::WebSliderRelayEvents::Event::eventTypeKey, "propertiesChanged"); + object->setProperty ("name", parameter.getName (100)); + relay.emitEvent (object.get()); + attachment.sendInitialUpdate(); +} + +void WebToggleButtonParameterAttachment::setValue (float newValue) +{ + const ScopedValueSetter svs (ignoreCallbacks, true); + relay.setToggleState (newValue >= 0.5f); +} + +void WebToggleButtonParameterAttachment::toggleStateChanged (bool newValue) +{ + if (ignoreCallbacks) + { + jassertfalse; + return; + } + + attachment.setValueAsCompleteGesture (newValue ? 1.0f : 0.0f); +} + +void WebToggleButtonParameterAttachment::initialUpdateRequested() +{ + sendInitialUpdate(); +} + +//============================================================================== +WebComboBoxParameterAttachment::WebComboBoxParameterAttachment (RangedAudioParameter& parameterIn, + WebComboBoxRelay& combo, + UndoManager* undoManager) + : relay (combo), + parameter (parameterIn), + attachment (parameter, [this] (float f) { setValue (f); }, undoManager) +{ + sendInitialUpdate(); + relay.addListener (this); +} + +WebComboBoxParameterAttachment::~WebComboBoxParameterAttachment() +{ + relay.removeListener (this); +} + +void WebComboBoxParameterAttachment::sendInitialUpdate() +{ + DynamicObject::Ptr object { new DynamicObject }; + object->setProperty (detail::WebSliderRelayEvents::Event::eventTypeKey, "propertiesChanged"); + object->setProperty ("name", parameter.getName (100)); + + if (auto* choiceParameter = dynamic_cast (¶meter)) + object->setProperty ("choices", choiceParameter->choices); + else + object->setProperty ("choices", StringArray{}); + + relay.emitEvent (object.get()); + attachment.sendInitialUpdate(); +} + +void WebComboBoxParameterAttachment::setValue (float newValue) +{ + const auto normValue = parameter.convertTo0to1 (newValue); + + const ScopedValueSetter svs (ignoreCallbacks, true); + relay.setValue (normValue); +} + +void WebComboBoxParameterAttachment::valueChanged (float newValue) +{ + if (ignoreCallbacks) + { + jassertfalse; + return; + } + + attachment.setValueAsCompleteGesture (parameter.convertFrom0to1 (newValue)); +} + +void WebComboBoxParameterAttachment::initialUpdateRequested() +{ + sendInitialUpdate(); +} +#endif + } // namespace juce diff --git a/modules/juce_audio_processors/utilities/juce_ParameterAttachments.h b/modules/juce_audio_processors/utilities/juce_ParameterAttachments.h index 8185d72b08..c54d008341 100644 --- a/modules/juce_audio_processors/utilities/juce_ParameterAttachments.h +++ b/modules/juce_audio_processors/utilities/juce_ParameterAttachments.h @@ -257,4 +257,144 @@ private: bool ignoreCallbacks = false; }; +#if JUCE_WEB_BROWSER || DOXYGEN +//============================================================================== +/** + An object of this class maintains a connection between a WebSliderRelay and a + plug-in parameter. + + During the lifetime of this object it keeps the two things in sync, making it + easy to connect a WebSliderRelay to a parameter. When this object is deleted, + the connection is broken. Make sure that your parameter and WebSliderRelay are + not deleted before this object! + + @tags{Audio} +*/ +class WebSliderParameterAttachment : private WebSliderRelay::Listener +{ +public: + /** Creates a connection between a plug-in parameter and a WebSliderRelay. + + @param parameter The parameter to use + @param sliderStateIn The WebSliderRelay to use + @param undoManager An optional UndoManager + */ + WebSliderParameterAttachment (RangedAudioParameter& parameterIn, + WebSliderRelay& sliderStateIn, + UndoManager* undoManager = nullptr); + + /** Destructor. */ + ~WebSliderParameterAttachment() override; + + /** Call this after setting up your slider in the case where you need to do + extra setup after constructing this attachment. + */ + void sendInitialUpdate(); + +private: + void setValue (float newValue); + + void sliderValueChanged (WebSliderRelay*) override; + + void sliderDragStarted (WebSliderRelay*) override { attachment.beginGesture(); } + void sliderDragEnded (WebSliderRelay*) override { attachment.endGesture(); } + void initialUpdateRequested (WebSliderRelay*) override { sendInitialUpdate(); } + + WebSliderRelay& sliderState; + RangedAudioParameter& parameter; + ParameterAttachment attachment; + bool ignoreCallbacks = false; +}; + +//============================================================================== +/** + An object of this class maintains a connection between a WebToggleButtonRelay and a + plug-in parameter. + + During the lifetime of this object it keeps the two things in sync, making it + easy to connect a WebToggleButtonRelay to a parameter. When this object is deleted, + the connection is broken. Make sure that your parameter and WebToggleButtonRelay are + not deleted before this object! + + @tags{Audio} +*/ +class WebToggleButtonParameterAttachment : private WebToggleButtonRelay::Listener +{ +public: + /** Creates a connection between a plug-in parameter and a WebToggleButtonRelay. + + @param parameter The parameter to use + @param button The WebToggleButtonRelay to use + @param undoManager An optional UndoManager + */ + WebToggleButtonParameterAttachment (RangedAudioParameter& parameterIn, + WebToggleButtonRelay& button, + UndoManager* undoManager = nullptr); + + /** Destructor. */ + ~WebToggleButtonParameterAttachment() override; + + /** Call this after setting up your button in the case where you need to do + extra setup after constructing this attachment. + */ + void sendInitialUpdate(); + +private: + void setValue (float newValue); + + void toggleStateChanged (bool newValue) override; + void initialUpdateRequested() override; + + WebToggleButtonRelay& relay; + RangedAudioParameter& parameter; + ParameterAttachment attachment; + bool ignoreCallbacks = false; +}; + +//============================================================================== +/** + An object of this class maintains a connection between a WebComboBoxRelay and a + plug-in parameter. + + During the lifetime of this object it keeps the two things in sync, making it + easy to connect a WebComboBoxRelay to a parameter. When this object is deleted, + the connection is broken. Make sure that your parameter and WebComboBoxRelay are + not deleted before this object! + + @tags{Audio} +*/ +class WebComboBoxParameterAttachment : private WebComboBoxRelay::Listener +{ +public: + /** Creates a connection between a plug-in parameter and a WebComboBoxRelay. + + @param parameter The parameter to use + @param combo The WebComboBoxRelay to use + @param undoManager An optional UndoManager + */ + WebComboBoxParameterAttachment (RangedAudioParameter& parameterIn, WebComboBoxRelay& combo, + UndoManager* undoManager = nullptr); + + /** Destructor. */ + ~WebComboBoxParameterAttachment() override; + + /** Call this after setting up your combo box in the case where you need to do + extra setup after constructing this attachment. + */ + void sendInitialUpdate(); + +private: + void setValue (float newValue); + + void valueChanged (float newValue) override; + void initialUpdateRequested() override; + + WebComboBoxRelay& relay; + RangedAudioParameter& parameter; + ParameterAttachment attachment; + bool ignoreCallbacks = false; +}; + +#endif + } // namespace juce diff --git a/modules/juce_core/juce_core.h b/modules/juce_core/juce_core.h index 065a740e7f..9d5e7d08af 100644 --- a/modules/juce_core/juce_core.h +++ b/modules/juce_core/juce_core.h @@ -365,6 +365,7 @@ JUCE_END_IGNORE_WARNINGS_MSVC #include "memory/juce_Reservoir.h" #include "files/juce_AndroidDocument.h" #include "streams/juce_AndroidDocumentInputSource.h" +#include "misc/juce_OptionsHelpers.h" #include "detail/juce_CallbackListenerList.h" diff --git a/modules/juce_core/misc/juce_OptionsHelpers.h b/modules/juce_core/misc/juce_OptionsHelpers.h new file mode 100644 index 0000000000..55760f9f00 --- /dev/null +++ b/modules/juce_core/misc/juce_OptionsHelpers.h @@ -0,0 +1,44 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +template +class OptionsBuilder +{ +public: + virtual ~OptionsBuilder() = default; + + virtual OptionsType buildOptions (const OptionsType& initialOptions) = 0; + + OptionsType buildOptions() { return buildOptions (OptionsType{}); } +}; diff --git a/modules/juce_gui_basics/widgets/juce_Slider.h b/modules/juce_gui_basics/widgets/juce_Slider.h index 6a613de720..fc588c33bd 100644 --- a/modules/juce_gui_basics/widgets/juce_Slider.h +++ b/modules/juce_gui_basics/widgets/juce_Slider.h @@ -35,6 +35,51 @@ namespace juce { +/** A class for receiving callbacks from a Slider or WebSliderRelay. + + To be told when a slider's value changes, you can register a Slider::Listener + object using Slider::addListener(). + + @see Slider::addListener, Slider::removeListener, WebSliderRelay::addListener, + WebSliderRelay::removeListener +*/ +template +class JUCE_API SliderListener +{ +public: + //============================================================================== + /** Destructor. */ + virtual ~SliderListener() = default; + + //============================================================================== + /** Called when the slider's value is changed. + + This may be caused by dragging it, or by typing in its text entry box, + or by a call to Slider::setValue(). + + You can find out the new value using Slider::getValue(). + + @see Slider::valueChanged + */ + virtual void sliderValueChanged (Emitter*) = 0; + + //============================================================================== + /** Called when the slider is about to be dragged. + + This is called when a drag begins, then it's followed by multiple calls + to sliderValueChanged(), and then sliderDragEnded() is called after the + user lets go. + + @see sliderDragEnded, Slider::startedDragging + */ + virtual void sliderDragStarted (Emitter*) {} + + /** Called after a drag operation has finished. + @see sliderDragStarted, Slider::stoppedDragging + */ + virtual void sliderDragEnded (Emitter*) {} +}; + //============================================================================== /** A slider control for changing a value. @@ -557,48 +602,7 @@ public: NotificationType notification = sendNotificationAsync); //============================================================================== - /** A class for receiving callbacks from a Slider. - - To be told when a slider's value changes, you can register a Slider::Listener - object using Slider::addListener(). - - @see Slider::addListener, Slider::removeListener - */ - class JUCE_API Listener - { - public: - //============================================================================== - /** Destructor. */ - virtual ~Listener() = default; - - //============================================================================== - /** Called when the slider's value is changed. - - This may be caused by dragging it, or by typing in its text entry box, - or by a call to Slider::setValue(). - - You can find out the new value using Slider::getValue(). - - @see Slider::valueChanged - */ - virtual void sliderValueChanged (Slider* slider) = 0; - - //============================================================================== - /** Called when the slider is about to be dragged. - - This is called when a drag begins, then it's followed by multiple calls - to sliderValueChanged(), and then sliderDragEnded() is called after the - user lets go. - - @see sliderDragEnded, Slider::startedDragging - */ - virtual void sliderDragStarted (Slider*) {} - - /** Called after a drag operation has finished. - @see sliderDragStarted, Slider::stoppedDragging - */ - virtual void sliderDragEnded (Slider*) {} - }; + using Listener = SliderListener; /** Adds a listener to be called when this slider's value changes. */ void addListener (Listener* listener); diff --git a/modules/juce_gui_extra/detail/juce_WebControlRelayEvents.h b/modules/juce_gui_extra/detail/juce_WebControlRelayEvents.h new file mode 100644 index 0000000000..1cf4fff5f6 --- /dev/null +++ b/modules/juce_gui_extra/detail/juce_WebControlRelayEvents.h @@ -0,0 +1,255 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce::detail +{ + +#if JUCE_WEB_BROWSER || DOXYGEN + +struct WebSliderRelayEvents +{ + WebSliderRelayEvents() = delete; + + struct Event + { + String eventType; + DynamicObject::Ptr object; + + static std::optional extract (const var& v) + { + auto* dynObj = v.getDynamicObject(); + + if (dynObj == nullptr) + return std::nullopt; + + const auto eventTypeProp = dynObj->getProperty (eventTypeKey); + + if (! eventTypeProp.isString()) + return std::nullopt; + + return Event { eventTypeProp.toString(), dynObj }; + } + + static inline const Identifier eventTypeKey { "eventType" }; + }; + + struct ValueChanged + { + float newValue; + + static std::optional extract (const Event& event) + { + if (event.eventType != eventId.toString()) + return std::nullopt; + + const auto newValue = event.object->getProperty (newValueKey); + + if (! (newValue.isInt() || newValue.isInt64() || newValue.isDouble())) + return std::nullopt; + + return ValueChanged { (float) newValue }; + } + + static inline const Identifier eventId { "valueChanged" }; + static inline const Identifier newValueKey { "value" }; + }; + + struct SliderDragStarted + { + static std::optional extract (const Event& event) + { + if (event.eventType != eventId.toString()) + return std::nullopt; + + return SliderDragStarted{}; + } + + static inline const Identifier eventId { "sliderDragStarted" }; + }; + + struct SliderDragEnded + { + static std::optional extract (const Event& event) + { + if (event.eventType != eventId.toString()) + return std::nullopt; + + return SliderDragEnded{}; + } + + static inline const Identifier eventId { "sliderDragEnded" }; + }; + + struct InitialUpdateRequested + { + static std::optional extract (const Event& event) + { + if (event.eventType != eventId.toString()) + return std::nullopt; + + return InitialUpdateRequested{}; + } + + static inline const Identifier eventId { "requestInitialUpdate" }; + }; +}; + +//============================================================================== +struct WebToggleButtonRelayEvents +{ + WebToggleButtonRelayEvents() = delete; + + struct Event + { + String eventType; + DynamicObject::Ptr object; + + static std::optional extract (const var& v) + { + auto* dynObj = v.getDynamicObject(); + + if (dynObj == nullptr) + return std::nullopt; + + const auto eventTypeProp = dynObj->getProperty (eventTypeKey); + + if (! eventTypeProp.isString()) + return std::nullopt; + + return Event { eventTypeProp.toString(), dynObj }; + } + + static inline const Identifier eventTypeKey { "eventType" }; + }; + + struct ToggleStateChanged + { + bool value; + + static std::optional extract (const Event& event) + { + if (event.eventType != eventId.toString()) + return std::nullopt; + + const auto newState = event.object->getProperty (valueKey); + + if (! newState.isBool()) + return std::nullopt; + + return ToggleStateChanged { newState }; + } + + static inline const Identifier eventId { "valueChanged" }; + static inline const Identifier valueKey { "value" }; + }; + + struct InitialUpdateRequested + { + static std::optional extract (const Event& event) + { + if (event.eventType != eventId.toString()) + return std::nullopt; + + return InitialUpdateRequested{}; + } + + static inline const Identifier eventId { "requestInitialUpdate" }; + }; +}; + +//============================================================================== +struct WebComboBoxRelayEvents +{ + WebComboBoxRelayEvents() = delete; + + struct Event + { + String eventType; + DynamicObject::Ptr object; + + static std::optional extract (const var& v) + { + auto* dynObj = v.getDynamicObject(); + + if (dynObj == nullptr) + return std::nullopt; + + const auto eventTypeProp = dynObj->getProperty (eventTypeKey); + + if (! eventTypeProp.isString()) + return std::nullopt; + + return Event { eventTypeProp.toString(), dynObj }; + } + + static inline const Identifier eventTypeKey { "eventType" }; + }; + + struct ValueChanged + { + float value; + + static std::optional extract (const Event& event) + { + if (event.eventType != eventId.toString()) + return std::nullopt; + + const auto newValue = event.object->getProperty (valueKey); + + if (! (newValue.isInt() || newValue.isInt64() || newValue.isDouble())) + return std::nullopt; + + return ValueChanged { (float) newValue }; + } + + static inline const Identifier eventId { "valueChanged" }; + static inline const Identifier valueKey { "value" }; + }; + + struct InitialUpdateRequested + { + static std::optional extract (const Event& event) + { + if (event.eventType != eventId.toString()) + return std::nullopt; + + return InitialUpdateRequested{}; + } + + static inline const Identifier eventId { "requestInitialUpdate" }; + }; +}; + +#endif +} diff --git a/modules/juce_gui_extra/juce_gui_extra.cpp b/modules/juce_gui_extra/juce_gui_extra.cpp index ff25c6cd93..fee9682cc6 100644 --- a/modules/juce_gui_extra/juce_gui_extra.cpp +++ b/modules/juce_gui_extra/juce_gui_extra.cpp @@ -128,6 +128,8 @@ #include #include #include + #include + #include JUCE_END_IGNORE_WARNINGS_GCC_LIKE #endif @@ -150,6 +152,7 @@ #include "misc/juce_LiveConstantEditor.cpp" #include "misc/juce_AnimatedAppComponent.cpp" #include "misc/juce_WebBrowserComponent.cpp" +#include "misc/juce_WebControlRelays.cpp" //============================================================================== #if JUCE_MAC || JUCE_IOS diff --git a/modules/juce_gui_extra/juce_gui_extra.h b/modules/juce_gui_extra/juce_gui_extra.h index 1643c1980f..08afbb0056 100644 --- a/modules/juce_gui_extra/juce_gui_extra.h +++ b/modules/juce_gui_extra/juce_gui_extra.h @@ -76,17 +76,43 @@ #define JUCE_WEB_BROWSER 1 #endif +/** Config: JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING + Enables the use of the Microsoft Edge (Chromium) WebView2 browser on Windows. + + If using the Projucer, the Microsoft.Web.WebView2 package will be added to the + project solution if this flag is enabled. If you are building using CMake you + will need to manually add the package via the NuGet package manager. + + Using this flag requires statically linking against WebView2LoaderStatic.lib, + which at this time is only available through the NuGet package, but is missing + in VCPKG. + + In addition to enabling this macro, you will need to use the + WebBrowserComponent::Options::Backend::webview2 option when instantiating the + WebBrowserComponent. +*/ +#ifndef JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING + #define JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING 0 +#else + #define JUCE_USE_WIN_WEBVIEW2 1 +#endif + /** Config: JUCE_USE_WIN_WEBVIEW2 - Enables the use of the Microsoft Edge (Chromium) WebView2 browser on Windows, - currently in developer preview. + Enables the use of the Microsoft Edge (Chromium) WebView2 browser on Windows. If using the Projucer, the Microsoft.Web.WebView2 package will be added to the project solution if this flag is enabled. If you are building using CMake you will need to manually add the package via the Visual Studio package manager. + If the WITH_STATIC_LINKING variant of this flag is also set, you must statically + link against WebView2LoaderStatic.lib, otherwise dynamic loading will be used. + + See more about dynamic linking in the documentation of + WebBrowserComponent::Options::WinWebView2::withDLLLocation(). + In addition to enabling this macro, you will need to use the - WindowsWebView2WebBrowserComponent wrapper - see the documentation of that - class for more details. + WebBrowserComponent::Options::Backend::webview2 option when instantiating the + WebBrowserComponent. */ #ifndef JUCE_USE_WIN_WEBVIEW2 #define JUCE_USE_WIN_WEBVIEW2 0 @@ -130,3 +156,5 @@ #include "misc/juce_WebBrowserComponent.h" #include "misc/juce_LiveConstantEditor.h" #include "misc/juce_AnimatedAppComponent.h" +#include "detail/juce_WebControlRelayEvents.h" +#include "misc/juce_WebControlRelays.h" diff --git a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.cpp b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.cpp index 5ae8ea35af..26188c1cff 100644 --- a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.cpp +++ b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.cpp @@ -43,6 +43,669 @@ bool WebBrowserComponent::pageLoadHadNetworkError ([[maybe_unused]] const String void WebBrowserComponent::windowCloseRequest() {} void WebBrowserComponent::newWindowAttemptingToLoad ([[maybe_unused]] const String& newURL) {} +// At least this much code has to be injected as user script, since the native backend functions +// rely on the presence of the `window.__JUCE__.backend.emitByBackend()` function. The rest can be +// optionally imported as JS. +static constexpr const char* lowLevelIntegrationsScript = R"( +if ( + typeof window.__JUCE__ !== "undefined" && + typeof window.__JUCE__.getAndroidUserScripts !== "undefined" && + typeof window.inAndroidUserScriptEval === "undefined" +) { + window.inAndroidUserScriptEval = true; + eval(window.__JUCE__.getAndroidUserScripts()); + delete window.inAndroidUserScriptEval; +} + +{ + if (typeof window.__JUCE__ === "undefined") { + console.warn( + "The 'window.__JUCE__' object is undefined." + + " Native integration features will not work." + + " Defining a placeholder 'window.__JUCE__' object." + ); + + window.__JUCE__ = { + postMessage: function () {}, + }; + } + + if (typeof window.__JUCE__.initialisationData === "undefined") { + window.__JUCE__.initialisationData = { + __juce__platform: [], + __juce__functions: [], + __juce__registeredGlobalEventIds: [], + __juce__sliders: [], + __juce__toggles: [], + __juce__comboBoxes: [], + }; + } + + class ListenerList { + constructor() { + this.listeners = new Map(); + this.listenerId = 0; + } + + addListener(fn) { + const newListenerId = this.listenerId++; + this.listeners.set(newListenerId, fn); + return newListenerId; + } + + removeListener(id) { + if (this.listeners.has(id)) { + this.listeners.delete(id); + } + } + + callListeners(payload) { + for (const [, value] of this.listeners) { + value(payload); + } + } + } + + class EventListenerList { + constructor() { + this.eventListeners = new Map(); + } + + addEventListener(eventId, fn) { + if (!this.eventListeners.has(eventId)) + this.eventListeners.set(eventId, new ListenerList()); + + const id = this.eventListeners.get(eventId).addListener(fn); + + return [eventId, id]; + } + + removeEventListener([eventId, id]) { + if (this.eventListeners.has(eventId)) { + this.eventListeners.get(eventId).removeListener(id); + } + } + + emitEvent(eventId, object) { + if (this.eventListeners.has(eventId)) + this.eventListeners.get(eventId).callListeners(object); + } + } + + class Backend { + constructor() { + this.listeners = new EventListenerList(); + } + + addEventListener(eventId, fn) { + return this.listeners.addEventListener(eventId, fn); + } + + removeEventListener([eventId, id]) { + this.listeners.removeEventListener(eventId, id); + } + + emitEvent(eventId, object) { + window.__JUCE__.postMessage( + JSON.stringify({ eventId: eventId, payload: object }) + ); + } + + emitByBackend(eventId, object) { + this.listeners.emitEvent(eventId, JSON.parse(object)); + } + } + + if (typeof window.__JUCE__.backend === "undefined") + window.__JUCE__.backend = new Backend(); +} +)"; + +static void evaluationHandler (WebBrowserComponent::EvaluationResult r) +{ + if (r.getResult() == nullptr) + { + // The unsupported return type means a successful Javascript evaluation that yielded a + // result that cannot be translated and returned to native code such as a Promise. + jassert (r.getError()->type == WebBrowserComponent::EvaluationResult::Error::Type::unsupportedReturnType); + DBG (r.getError()->message); + return; + } +} + +static StringArray getUserScriptsForInitialisationData (const std::vector>& data) +{ + std::map sortedData; + + for (const auto& [key, value] : data) + sortedData[key].add (JSON::toString (value)); + + StringArray result; + + for (const auto& [key, values] : sortedData) + result.add ("window.__JUCE__.initialisationData." + key + " = [" + + values.joinIntoString (",") + "];"); + + return result; +} + +template +static auto getCommaSeparatedList (const T& mapOfStringable) +{ + StringArray keys; + + for (auto it : mapOfStringable) + keys.add (it.first.toString().quoted()); + + return keys.joinIntoString (","); +} + +struct NativeEvents +{ + NativeEvents() = delete; + + struct NativeEvent + { + String eventId; + var payload; + + static constexpr std::optional marshallingVersion = std::nullopt; + + template + static void serialise (Archive& archive, Item& item) + { + archive (named ("eventId", item.eventId), + named ("payload", item.payload)); + } + }; + + struct Invoke + { + String name; + var params; + int64 resultId; + + static constexpr std::optional marshallingVersion = std::nullopt; + + template + static void serialise (Archive& archive, Item& item) + { + archive (named ("name", item.name), + named ("params", item.params), + named ("resultId", item.resultId)); + } + + static inline const Identifier eventId { "__juce__invoke" }; + static inline const Identifier completeId { "__juce__complete" }; + }; +}; + +class NativeFunctionsProvider : public OptionsBuilder, + private AsyncUpdater +{ +public: + explicit NativeFunctionsProvider (WebBrowserComponent& ownerIn) + : owner (ownerIn) + { + } + + WebBrowserComponent::Options buildOptions (const WebBrowserComponent::Options& initialOptions) override + { + nativeFunctions = initialOptions.getNativeFunctions(); + + if (nativeFunctions.empty()) + return initialOptions; + + auto options = initialOptions.withNativeIntegrationEnabled() + .withEventListener (NativeEvents::Invoke::eventId, + [this] (const auto& object) + { + handleNativeFunctionCall (object); + }); + + for (auto it : nativeFunctions) + options = options.withInitialisationData ("__juce__functions", it.first.toString()); + + return options; + } + +private: + void handleAsyncUpdate() override + { + const auto popFront = [&] (auto&& callback) + { + functionCompletionsMutex.lock(); + ErasedScopeGuard unlocker { [&] { functionCompletionsMutex.unlock(); } }; + + if (! functionCompletions.empty()) + { + auto completion = functionCompletions.front(); + functionCompletions.pop_front(); + unlocker.reset(); + callback (completion); + return true; + } + else + { + return false; + } + }; + + while (popFront ([&] (const auto& c) { emitCompletionEvent (c.first, c.second); })) + ; + } + + void handleNativeFunctionCall (const var& object) + { + const auto invocation = FromVar::convert (object); + + if (! invocation.has_value()) + { + jassertfalse; + return; + } + + auto it = nativeFunctions.find (invocation->name); + + if (it == nativeFunctions.end()) + { + jassertfalse; + return; + } + + jassert (invocation->params.isArray()); + + it->second (*invocation->params.getArray(), + [this, resultId = invocation->resultId] (auto result) + { + completeNativeFunctionCall (resultId, result); + }); + } + + void completeNativeFunctionCall (int64 resultId, const var& object) + { + if (MessageManager::getInstance()->isThisTheMessageThread()) + { + emitCompletionEvent (resultId, object); + } + else + { + const std::lock_guard lock { functionCompletionsMutex }; + functionCompletions.push_back (std::make_pair (resultId, object)); + triggerAsyncUpdate(); + } + } + + void emitCompletionEvent (int64 resultId, const var& object) + { + DynamicObject::Ptr eventObject { new DynamicObject() }; + eventObject->setProperty ("promiseId", resultId); + eventObject->setProperty ("result", object); + + jassert (owner.isVisible()); + owner.emitEventIfBrowserIsVisible (NativeEvents::Invoke::completeId, eventObject.get()); + } + + WebBrowserComponent& owner; + std::map nativeFunctions; + std::deque> functionCompletions; + std::mutex functionCompletionsMutex; + + JUCE_DECLARE_NON_COPYABLE (NativeFunctionsProvider) + JUCE_DECLARE_NON_MOVEABLE (NativeFunctionsProvider) +}; + +class NativeEventListeners +{ +public: + void addListener (const Identifier& eventId, std::function handler) + { + listeners.push_back (std::move (handler)); + listenerMap[eventId].add (&listeners.back()); + } + + void emit (const Identifier& eventId, const var& object) + { + if (const auto& it = listenerMap.find (eventId); it != listenerMap.end()) + it->second.call ([&object] (auto& l) { l (object); }); + } + +private: + std::list listeners; + + std::map> listenerMap; +}; + +class WebBrowserComponent::Impl +{ +public: + Impl (WebBrowserComponent& ownerIn, const Options& optionsIn) + : owner (ownerIn), + options ([&] + { + makeFunctionsProviderIfNecessary (nativeFunctionsProvider, *this, optionsIn); + + if (nativeFunctionsProvider.has_value()) + return getOptions (optionsIn).withOptionsFrom (*nativeFunctionsProvider); + + return getOptions (optionsIn); + }()) + { + auto userScripts = options.getUserScripts(); + + // Adding user scripts in reverse order of depending on each other. The most depended on + // comes last. + for (const auto& eventListener : options.getEventListeners()) + { + userScripts.insert (0, + "window.__JUCE__.initialisationData.__juce__registeredGlobalEventIds = [" + + getCommaSeparatedList (options.getEventListeners()) + "];"); + + addPermanentEventListener (eventListener.first, eventListener.second); + } + + for (const auto& script : getUserScriptsForInitialisationData (options.getInitialisationData())) + userScripts.insert (0, script); + + userScripts.insert (0, lowLevelIntegrationsScript); + + platform = createAndInitPlatformDependentPart (*this, options, userScripts); + } + + void emitEvent (const Identifier& eventId, const var& object) + { + evaluateJavascript ("window.__JUCE__.backend.emitByBackend(" + eventId.toString().quoted() + ", " + + JSON::toString (object, true).quoted ('\'') + ");", evaluationHandler); + } + + void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) + { + platform->goToURL (url, headers, postData); + } + + void stop() + { + platform->stop(); + } + + void goBack() + { + platform->goBack(); + + owner.lastURL.clear(); + owner.blankPageShown = false; + } + + void goForward() + { + owner.lastURL.clear(); + + platform->goForward(); + } + + void refresh() + { + platform->refresh(); + } + + void evaluateJavascript (const String& script, EvaluationCallback callback) + { + platform->evaluateJavascript (script, std::move (callback)); + } + + void setSize (int width, int height) + { + platform->setWebViewSize (width, height); + } + + void checkWindowAssociation() + { + platform->checkWindowAssociation(); + } + + void fallbackPaint (Graphics& webBrowserComponentContext) + { + platform->fallbackPaint (webBrowserComponentContext); + } + + void focusGainedWithDirection (FocusChangeType type, FocusChangeDirection dir) + { + platform->focusGainedWithDirection (type, dir); + } + + struct Platform; + +private: + class PlatformInterface + { + public: + PlatformInterface() = default; + virtual ~PlatformInterface() = default; + + virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0; + virtual void goBack() = 0; + virtual void goForward() = 0; + virtual void stop() = 0; + virtual void refresh() = 0; + virtual void evaluateJavascript (const String&, WebBrowserComponent::EvaluationCallback) = 0; + virtual void setWebViewSize (int, int) = 0; + virtual void checkWindowAssociation() = 0; + + virtual void focusGainedWithDirection (FocusChangeType, FocusChangeDirection) {} + virtual void fallbackPaint (Graphics&) {} + }; + + static void makeFunctionsProviderIfNecessary (std::optional& provider, + WebBrowserComponent::Impl& impl, + const WebBrowserComponent::Options& options) + { + if (! options.getNativeFunctions().empty()) + provider.emplace (impl.owner); + } + + static Options getOptions (const Options& optionsIn) + { + const auto os = SystemStats::getOperatingSystemType(); + + const auto platformString = [&] + { + using OsType = SystemStats::OperatingSystemType; + + if ((os & OsType::MacOSX) != 0) + return "macos"; + + if ((os & OsType::iOS) != 0) + return "ios"; + + if ((os & OsType::Windows) != 0) + return "windows"; + + if ((os & OsType::Android) != 0) + return "android"; + + if ((os & OsType::Linux) != 0) + return "linux"; + + return ""; + }(); + + return optionsIn.withInitialisationData ("__juce__platform", platformString); + } + + void addPermanentEventListener (const Identifier& eventId, NativeEventListener listener) + { + nativeEventListeners.addListener (eventId, std::move (listener)); + } + + std::optional handleResourceRequest (const String& url) + { + if (resourceProvider != nullptr) + return resourceProvider (url); + + return std::nullopt; + } + + void handleNativeEvent (const var& message) + { + const auto event = FromVar::convert (message); + + if (! event.has_value()) + { + jassertfalse; + return; + } + + nativeEventListeners.emit (event->eventId, event->payload); + } + + static std::unique_ptr createAndInitPlatformDependentPart (WebBrowserComponent::Impl&, + const Options&, + const StringArray&); + + WebBrowserComponent& owner; + std::optional nativeFunctionsProvider; + Options options; + ResourceProvider resourceProvider { options.getResourceProvider() }; + NativeEventListeners nativeEventListeners; + + std::unique_ptr platform; +}; + +//============================================================================== +WebBrowserComponent::WebBrowserComponent (const Options& options) + : impl (std::make_unique (*this, options)), + unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden()) +{ + setOpaque (true); + + #if JUCE_LINUX + ignoreUnused (blankPageShown); + ignoreUnused (unloadPageWhenHidden); + #endif +} + +WebBrowserComponent::~WebBrowserComponent() = default; + +void WebBrowserComponent::emitEventIfBrowserIsVisible (const Identifier& eventId, const var& object) +{ + if (isVisible()) + impl->emitEvent (eventId, object); +} + +const String& WebBrowserComponent::getResourceProviderRoot() +{ + const auto os = SystemStats::getOperatingSystemType(); + + using OsType = SystemStats::OperatingSystemType; + + if ( (os & OsType::MacOSX) != 0 + || (os & OsType::iOS) != 0 + || (os & OsType::Linux) != 0) + { + static String backendUrl { "juce://juce.backend/" }; + return backendUrl; + } + + if ( (os & OsType::Windows) != 0 + || (os & OsType::Android) != 0) + { + static String backendUrl { "https://juce.backend/" }; + return backendUrl; + } + + static String emptyUrl { "" }; + return emptyUrl; +} + +void WebBrowserComponent::goToURL (const String& url, + const StringArray* headers, + const MemoryBlock* postData) +{ + lastURL = url; + + if (headers != nullptr) + lastHeaders = *headers; + else + lastHeaders.clear(); + + if (postData != nullptr) + lastPostData = *postData; + else + lastPostData.reset(); + + blankPageShown = false; + + impl->goToURL (url, headers, postData); +} + +void WebBrowserComponent::stop() +{ + impl->stop(); +} + +void WebBrowserComponent::goBack() +{ + impl->goBack(); + lastURL.clear(); + blankPageShown = false; +} + +void WebBrowserComponent::goForward() +{ + lastURL.clear(); + impl->goForward(); +} + +void WebBrowserComponent::refresh() +{ + impl->refresh(); +} + +void WebBrowserComponent::paint (Graphics& g) +{ + impl->fallbackPaint (g); +} + +void WebBrowserComponent::parentHierarchyChanged() +{ + impl->checkWindowAssociation(); +} + +void WebBrowserComponent::visibilityChanged() +{ + impl->checkWindowAssociation(); +} + +void WebBrowserComponent::resized() +{ + impl->setSize (getWidth(), getHeight()); +} + +void WebBrowserComponent::reloadLastURL() +{ + const auto ptrOrNullIfEmpty = [] (auto& value) + { + return ! value.isEmpty() ? &value : nullptr; + }; + + if (lastURL.isNotEmpty()) + { + goToURL (lastURL, ptrOrNullIfEmpty (lastHeaders), ptrOrNullIfEmpty (lastPostData)); + lastURL.clear(); + } +} + +void WebBrowserComponent::evaluateJavascript (const String& script, EvaluationCallback callback) +{ + impl->evaluateJavascript (script, std::move (callback)); +} + +void WebBrowserComponent::focusGainedWithDirection (FocusChangeType type, + FocusChangeDirection direction) +{ + impl->focusGainedWithDirection (type, direction); +} + #endif } // namespace juce diff --git a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h index 8c2eb530ad..26be90a150 100644 --- a/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h +++ b/modules/juce_gui_extra/misc/juce_WebBrowserComponent.h @@ -37,6 +37,16 @@ namespace juce #if JUCE_WEB_BROWSER || DOXYGEN +#if (JUCE_MAC && (defined (MAC_OS_X_VERSION_10_13) && (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_13))) \ + || (JUCE_IOS && (defined (__IPHONE_11_0) && (__IPHONE_OS_VERSION_MIN_REQUIRED >= __IPHONE_11_0))) \ + || (JUCE_WINDOWS && JUCE_USE_WIN_WEBVIEW2) \ + || JUCE_ANDROID \ + || JUCE_LINUX + #define JUCE_WEB_BROWSER_RESOURCE_PROVIDER_AVAILABLE 1 +#else + #define JUCE_WEB_BROWSER_RESOURCE_PROVIDER_AVAILABLE 0 +#endif + //============================================================================== /** A component that displays an embedded web browser. @@ -54,6 +64,37 @@ namespace juce class JUCE_API WebBrowserComponent : public Component { public: + //============================================================================== + /** Type for a listener registered with Options::withNativeEventListener. */ + using NativeEventListener = std::function; + + /** Type for the completion passed as the second parameter of NativeFunction. Can be called + from any thread. + */ + using NativeFunctionCompletion = std::function; + + /** Type for functions registered with Options::withNativeFunction. The first parameter is an + Array object containing the arguments of the Javascript function invocation. + + The second parameter is the result that completes the Promise returned by the Javascript + function call. It can be called from any thread. + */ + using NativeFunction = std::function&, NativeFunctionCompletion)>; + + /** A resource returned by a ResourceProvider. + + @see Options::withResourceProvider + */ + struct Resource + { + std::vector data; + String mimeType; + }; + + /** The type used in Options::withResourceProvider. + */ + using ResourceProvider = std::function (const String&)>; + //============================================================================== /** Options to configure WebBrowserComponent. @@ -105,7 +146,7 @@ public: handy to stop the browser using resources in the background when it's not actually being used. */ - [[nodiscard]] Options withKeepPageLoadedWhenBrowserIsHidden() const { return withMember (*this, &Options::keepPageLoadedWhenBrowserIsHidden, true); } + [[nodiscard]] Options withKeepPageLoadedWhenBrowserIsHidden() const { return withMember (*this, &Options::keepPageLoadedWhenBrowserIsHidden, true); } /** Use a specific user agent string when requesting web pages. @@ -125,7 +166,13 @@ public: */ [[nodiscard]] WinWebView2 withDLLLocation (const File& location) const { return withMember (*this, &WinWebView2::dllLocation, location); } - /** Sets a non-default location for storing user data for the browser instance. */ + /** Sets a non-default location for storing user data for the browser instance. + + In plugin projects you may find it necessary to use this option and specify a + location such as File::SpecialLocationType::tempDirectory. Otherwise WebView2 + may function incorrectly due to being denied access to the default user data + location. + */ [[nodiscard]] WinWebView2 withUserDataFolder (const File& folder) const { return withMember (*this, &WinWebView2::userDataFolder, folder); } /** If this is set, the status bar usually displayed in the lower-left of the webview @@ -165,23 +212,157 @@ public: Colour backgroundColour; }; + /** Specifies options that apply to the Windows implementation when the WebView2 feature is + enabled. + + @see withBackend + */ [[nodiscard]] Options withWinWebView2Options (const WinWebView2& winWebView2Options) const { return withMember (*this, &Options::winWebView2, winWebView2Options); } + /** Enables native integration features for the code running inside the WebBrowserComponent. + + This injects data and function objects under `window.__JUCE__.backend` through which + scripts running in the WebBrowserComponent can send events to the backend and call + registered native functions. + + You should only enable native integrations if you have full control over the content + loaded into the component. Navigating to 3rd party websites with these integrations + enabled may expose the application and the computer to security risks. + + @see withNativeFunction, withEventListener + */ + [[nodiscard]] Options withNativeIntegrationEnabled (bool enabled = true) const + { + return withMember (*this, &Options::enableNativeIntegration, enabled); + } + + /** Registers a NativeFunction under the given name. + + To call this function from the frontend, you can import the JUCE frontend helper module + or issue a call to the low-level frontend API. + + @code + import { getNativeFunction } from "./juce"; + + function someJavascriptFunction() { + const myBackendFunction = getNativeFunction("myBackendFunction"); + myBackendFunction (1, 2, "some string"); + } + @endcode + */ + [[nodiscard]] Options withNativeFunction (const Identifier& name, NativeFunction callback) const + { + auto copy = *this; + jassert (copy.nativeFunctions.count (name) == 0); + copy.nativeFunctions[name] = std::move (callback); + return copy; + } + + /** Registers a NativeEventListener that receives events sent to the specified eventId. + + To send a message to this listener from the frontend, call for example + @code window.__JUCE__.backend.emitEvent(eventId, { x: 2, y: 6 }); @endcode + */ + [[nodiscard]] Options withEventListener (const Identifier& eventId, NativeEventListener listener) const + { + auto copy = *this; + copy.eventListeners.emplace_back (eventId, std::move (listener)); + return copy; + } + + /** Adds a Javascript code that will be evaluated before any other resource is loaded but + after the JUCE backend definitions become available, hence the specified script can + rely on the presence of `window.__JUCE__.backend`. + + This script will be evaluated after all goToUrl() calls. + */ + [[nodiscard]] Options withUserScript (StringRef script) const + { + auto copy = *this; + copy.userScripts.add (script); + return copy; + } + + /** Ensures that there will be a Javascript Array under + `window.__JUCE__.backend.initialisationData.name` and that it will contain the value + provided here. + + The initialisation data is injected prior to loading any resource. Multiple values added + for the same name will all be available in the Array. + */ + [[nodiscard]] Options withInitialisationData (StringRef name, const var& value) const + { + auto copy = *this; + copy.initialisationData.push_back (std::make_pair (String { name }, value)); + return copy; + } + +#if JUCE_WEB_BROWSER_RESOURCE_PROVIDER_AVAILABLE || JUCE_DOXYGEN + /** Sets a ResourceProvider object that can complete WebView resource requests and return + data without having to issue a network operation. + + Requests sent to WebBrowserComponent::getResourceProviderRoot() + "resource.path" will + invoke the provider with the path "/resource.path". + + If you call WebBrowserComponent::goToURL with the value returned by + WebBrowserComponent::getResourceProviderRoot, your resource provider will receive a + request for the resource "/" for which you will typically want to return the contents of + your index.html. + + You can also specify an optional allowedOriginIn parameter that will make your + ResourceProvider available to scripts loaded from that origin. E.g. if you specify + "http://localhost:3000", then a script loaded from such a local development server will + be able to access resources such as getResourceProviderRoot() + "live_data.bin". + + Allowing external origins is handy for development, but is a potential security risk in + publicly released binaries. + */ + [[nodiscard]] Options withResourceProvider (ResourceProvider provider, + std::optional allowedOriginIn = std::nullopt) const + { + return withMember (withMember (*this, &Options::resourceProvider, std::move (provider)), + &Options::allowedOrigin, + std::move (allowedOriginIn)); + } +#endif + + /** Adds all options provided by the builder to the returned Options object. + */ + template + OptionsType withOptionsFrom (OptionsBuilder& builder) const + { + return builder.buildOptions (static_cast (*this)); + } + //============================================================================== - Backend getBackend() const noexcept { return browserBackend; } - bool keepsPageLoadedWhenBrowserIsHidden() const noexcept { return keepPageLoadedWhenBrowserIsHidden; } - String getUserAgent() const { return userAgent; } - WinWebView2 getWinWebView2BackendOptions() const { return winWebView2; } + auto getBackend() const noexcept { return browserBackend; } + auto keepsPageLoadedWhenBrowserIsHidden() const noexcept { return keepPageLoadedWhenBrowserIsHidden; } + auto getUserAgent() const { return userAgent; } + auto getWinWebView2BackendOptions() const { return winWebView2; } + auto getNativeIntegrationsEnabled() const { return enableNativeIntegration; } + const auto& getNativeFunctions() const { return nativeFunctions; } + const auto& getEventListeners() const { return eventListeners; } + const auto& getUserScripts() const { return userScripts; } + const auto& getInitialisationData() const { return initialisationData; } + auto getResourceProvider() const { return resourceProvider; } + const auto& getAllowedOrigin() const { return allowedOrigin; } private: //============================================================================== Backend browserBackend = Backend::defaultBackend; bool keepPageLoadedWhenBrowserIsHidden = false; + bool enableNativeIntegration = false; String userAgent; WinWebView2 winWebView2; + std::map nativeFunctions; + std::vector> eventListeners; + StringArray userScripts; + std::vector> initialisationData; + ResourceProvider resourceProvider; + std::optional allowedOrigin; }; //============================================================================== @@ -230,6 +411,101 @@ public: /** Clear cookies that the OS has stored for the WebComponents of this application */ static void clearCookies(); + /** Returns a platform specific string that represents the root address for resources served + by the ResourceProvider. + + If you pass this value to goToURL the ResourceProvider will receive a request with the "/" + path parameter. In response to this request the provider may typically want to return the + contents of the "index.html" file. + + @see ResourceProvider, Options::withResourceProvider, goToURL + */ + static const String& getResourceProviderRoot(); + + //============================================================================== + /** On MacOS, iOS and Linux getResult will return a nullptr if the evaluation failed. In this + case getError will return a non-null Error result, which contains more information about + why the evaluation failed. It could be e.g. a syntax error or referencing an undefined + object. + + On Windows and Android getResult will always return a valid pointer to a variant, and + getError will always return a nullptr. In case there was an evaluation failure, getResult + returns a void variant, which is indistinguishable from a successful evaluation that yielded + a null result. Unfortunately these platforms don't offer a way to detect evaluation errors. + */ + class EvaluationResult + { + public: + struct Error + { + enum class Type + { + /** Error occurring for a reason unknown to us. */ + unknown, + + /** Error occurring because of a Javascript exception being thrown. */ + javascriptException, + + /** Error occurring because the returned result cannot be serialised into a native + type e.g. Promise. + */ + unsupportedReturnType + }; + + Type type; + String message; + }; + + EvaluationResult (const var& result) : value { result } + { + } + + EvaluationResult (const Error& error) : value { error } + { + } + + const var* getResult() const + { + return std::get_if (&value); + } + + const Error* getError() const + { + return std::get_if (&value); + } + + private: + std::variant value; + }; + + /** Callback type that can be passed optionally to evaluateJavascript. */ + using EvaluationCallback = std::function; + + /** Evaluates the specified script in the context of the current state of the + WebBrowserComponent. + + If the optional callback is provided it will be called with the result of the evaluation. + The callback will be called on the message thread. + */ + void evaluateJavascript (const String& script, EvaluationCallback callback = nullptr); + + /** Emits an object on the frontend under the specified eventId. + + Ids beginning with `__juce` are reserved for the framework implementation. + + Example for listening to such events on the frontend: + @code + // Subscribing + const removalToken = window.__JUCE__.backend.addEventListener(eventId, (objectFromBackend) => { + console.log(objectFromBackend.message); + }); + + // Unsubscribing + window.__JUCE__.backend.removeEventListener(removalToken); + @endcode + */ + void emitEventIfBrowserIsVisible (const Identifier& eventId, const var& object); + //============================================================================== /** This callback is called when the browser is about to navigate to a new location. @@ -279,7 +555,7 @@ public: void focusGainedWithDirection (FocusChangeType, FocusChangeDirection) override; /** @internal */ - class Pimpl; + class Impl; private: std::unique_ptr createAccessibilityHandler() override @@ -287,15 +563,13 @@ private: return std::make_unique (*this, AccessibilityRole::group); } - //============================================================================== - std::unique_ptr browser; + std::unique_ptr impl; bool blankPageShown = false, unloadPageWhenHidden; String lastURL; StringArray lastHeaders; MemoryBlock lastPostData; void reloadLastURL(); - void checkWindowAssociation(); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebBrowserComponent) }; diff --git a/modules/juce_gui_extra/misc/juce_WebControlRelays.cpp b/modules/juce_gui_extra/misc/juce_WebControlRelays.cpp new file mode 100644 index 0000000000..0a2b8b09c6 --- /dev/null +++ b/modules/juce_gui_extra/misc/juce_WebControlRelays.cpp @@ -0,0 +1,256 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +#if JUCE_WEB_BROWSER + +WebSliderRelay::WebSliderRelay (WebBrowserComponent& browserIn, StringRef nameIn) + : browser (browserIn), + name (nameIn) +{ +} + +void WebSliderRelay::setValue (float newValue) +{ + using namespace detail; + + DynamicObject::Ptr object { new DynamicObject }; + object->setProperty (WebSliderRelayEvents::Event::eventTypeKey, + WebSliderRelayEvents::ValueChanged::eventId.toString()); + object->setProperty (WebSliderRelayEvents::ValueChanged::newValueKey, newValue); + + value = newValue; + emitEvent (object.get()); +} + +float WebSliderRelay::getValue() const +{ + return value; +} + +void WebSliderRelay::addListener (Listener* l) +{ + listeners.add (l); +} + +void WebSliderRelay::removeListener (Listener* l) +{ + listeners.remove (l); +} + +WebBrowserComponent::Options WebSliderRelay::buildOptions (const WebBrowserComponent::Options& initialOptions) +{ + return initialOptions + .withEventListener (eventId, [this] (auto object) { handleEvent (object); }) + .withInitialisationData ("__juce__sliders", name); +} + +void WebSliderRelay::emitEvent (const var& payload) +{ + browser.emitEventIfBrowserIsVisible (eventId, payload); +} + +void WebSliderRelay::handleEvent (const var& event) +{ + using namespace detail; + + if (const auto sliderEvent = WebSliderRelayEvents::Event::extract (event)) + { + if (const auto valueChanged = WebSliderRelayEvents::ValueChanged::extract (*sliderEvent)) + { + if (! approximatelyEqual (std::exchange (value, valueChanged->newValue), valueChanged->newValue)) + listeners.call ([this] (Listener& l) { l.sliderValueChanged (this); }); + + return; + } + + if (const auto dragStarted = WebSliderRelayEvents::SliderDragStarted::extract (*sliderEvent)) + { + listeners.call ([this] (Listener& l) { l.sliderDragStarted (this); }); + return; + } + + if (const auto dragEnded = WebSliderRelayEvents::SliderDragEnded::extract (*sliderEvent)) + { + listeners.call ([this] (Listener& l) { l.sliderDragEnded (this); }); + return; + } + + if (const auto initialUpdate = + WebSliderRelayEvents::InitialUpdateRequested::extract (*sliderEvent)) + { + listeners.call ([this] (Listener& l) { l.initialUpdateRequested (this); }); + return; + } + } + + const auto s = JSON::toString (event); + jassertfalse; +} + +//============================================================================== +WebToggleButtonRelay::WebToggleButtonRelay ([[maybe_unused]] WebBrowserComponent& browserIn, StringRef nameIn) + : browser (browserIn), + name (nameIn) +{ +} + +void WebToggleButtonRelay::setToggleState (bool newState) +{ + using namespace detail; + + DynamicObject::Ptr object { new DynamicObject }; + object->setProperty (WebToggleButtonRelayEvents::Event::eventTypeKey, + WebToggleButtonRelayEvents::ToggleStateChanged::eventId.toString()); + object->setProperty (WebToggleButtonRelayEvents::ToggleStateChanged::valueKey, newState); + + emitEvent (object.get()); +} + +void WebToggleButtonRelay::addListener (Listener* l) +{ + listeners.add (l); +} + +void WebToggleButtonRelay::removeListener (Listener* l) +{ + listeners.remove (l); +} + +WebBrowserComponent::Options WebToggleButtonRelay::buildOptions (const WebBrowserComponent::Options& initialOptions) +{ + return initialOptions + .withEventListener (eventId, [this] (auto object) { handleEvent (object); }) + .withInitialisationData ("__juce__toggles", name); +} + +void WebToggleButtonRelay::emitEvent (const var& payload) +{ + browser.emitEventIfBrowserIsVisible (eventId, payload); +} + +void WebToggleButtonRelay::handleEvent (const var& event) +{ + using namespace detail; + + if (const auto buttonEvent = WebToggleButtonRelayEvents::Event::extract (event)) + { + if (const auto toggleStateChanged = WebToggleButtonRelayEvents::ToggleStateChanged::extract (*buttonEvent)) + { + listeners.call ([&toggleStateChanged] (Listener& l) + { l.toggleStateChanged (toggleStateChanged->value); }); + return; + } + + if (const auto initialUpdate = WebToggleButtonRelayEvents::InitialUpdateRequested::extract (*buttonEvent)) + { + listeners.call ([] (Listener& l) { l.initialUpdateRequested(); }); + return; + } + } + + const auto s = JSON::toString (event); + jassertfalse; +} + +//============================================================================== +WebComboBoxRelay::WebComboBoxRelay ([[maybe_unused]] WebBrowserComponent& browserIn, StringRef nameIn) + : browser (browserIn), + name (nameIn) +{ +} + +void WebComboBoxRelay::setValue (float newValue) +{ + using namespace detail; + + DynamicObject::Ptr object { new DynamicObject }; + object->setProperty (WebComboBoxRelayEvents::Event::eventTypeKey, + WebComboBoxRelayEvents::ValueChanged::eventId.toString()); + object->setProperty (WebComboBoxRelayEvents::ValueChanged::valueKey, newValue); + + emitEvent (object.get()); +} + +void WebComboBoxRelay::addListener (Listener* l) +{ + listeners.add (l); +} + +void WebComboBoxRelay::removeListener (Listener* l) +{ + listeners.remove (l); +} + +WebBrowserComponent::Options WebComboBoxRelay::buildOptions (const WebBrowserComponent::Options& initialOptions) +{ + return initialOptions + .withEventListener (eventId, [this] (auto object) { handleEvent (object); }) + .withInitialisationData ("__juce__comboBoxes", name); +} + +void WebComboBoxRelay::emitEvent (const var& payload) +{ + browser.emitEventIfBrowserIsVisible (eventId, payload); +} + +void WebComboBoxRelay::handleEvent (const var& event) +{ + using namespace detail; + + if (const auto buttonEvent = WebComboBoxRelayEvents::Event::extract (event)) + { + if (const auto valueChanged = WebComboBoxRelayEvents::ValueChanged::extract (*buttonEvent)) + { + listeners.call ([&valueChanged] (Listener& l) + { l.valueChanged (valueChanged->value); }); + return; + } + + if (const auto initialUpdate = WebComboBoxRelayEvents::InitialUpdateRequested::extract (*buttonEvent)) + { + listeners.call ([] (Listener& l) { l.initialUpdateRequested(); }); + return; + } + } + + const auto s = JSON::toString (event); + jassertfalse; +} + +#endif + +} // namespace juce diff --git a/modules/juce_gui_extra/misc/juce_WebControlRelays.h b/modules/juce_gui_extra/misc/juce_WebControlRelays.h new file mode 100644 index 0000000000..ef53c7d2ec --- /dev/null +++ b/modules/juce_gui_extra/misc/juce_WebControlRelays.h @@ -0,0 +1,248 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +namespace juce +{ + +#if JUCE_WEB_BROWSER || DOXYGEN + +/** Helper class that relays audio parameter information to an object inside a WebBrowserComponent. + + In order to create a relay you need to specify a WebBrowserComponent object and an identifier + for the relayed state. This will result in a Javascript object becoming available inside the + WebBrowserComponent under the provided identifier. + + You can then use a WebSliderParameterAttachment as you would a SliderAttachment, to attach the + relay to a RangedAudioParameter. This will synchronise the state and events of the Javascript + object with the audio parameter at all times. + + @code + // Add a relay to your AudioProcessorEditor members + WebSliderRelay cutoffSliderRelay { webComponent, "cutoffSlider" }; + @endcode + + @code + // In your Javascript GUI code you obtain an object from the framework + import * as Juce from "juce-framework-frontend"; + const sliderState = Juce.getSliderState("cutoffSlider"); + @endcode + + @see WebSliderParameterAttachment +*/ +class JUCE_API WebSliderRelay : public OptionsBuilder +{ +public: + /** Creating a relay will ensure that a Javascript object under the provided name will be + available in the specified WebBrowserComponent's context. Use the frontend framework's + getSliderState function with the same name to get a hold of this object. + */ + WebSliderRelay (WebBrowserComponent& browserIn, StringRef nameIn); + + //============================================================================== + /** @internal */ + struct Listener : public SliderListener + { + virtual void initialUpdateRequested (WebSliderRelay*) = 0; + }; + + /** @internal */ + void setValue (float newValue); + + /** @internal */ + float getValue() const; + + /** @internal */ + void addListener (Listener* l); + + /** @internal */ + void removeListener (Listener* l); + + /** @internal */ + WebBrowserComponent::Options buildOptions (const WebBrowserComponent::Options& initialOptions) override; + + /** @internal */ + void emitEvent (const var& payload); + +private: + void handleEvent (const var& event); + + WebBrowserComponent& browser; + String name; + float value{}; + Identifier eventId { "__juce__slider" + name }; + ListenerList listeners; + + JUCE_DECLARE_NON_COPYABLE (WebSliderRelay) + JUCE_DECLARE_NON_MOVEABLE (WebSliderRelay) +}; + +/** Helper class that relays audio parameter information to an object inside a WebBrowserComponent. + + In order to create a relay you need to specify a WebBrowserComponent object and an identifier + for the relayed state. This will result in a Javascript object becoming available inside the + WebBrowserComponent under the provided identifier. + + You can then use a WebToggleButtonParameterAttachment as you would a ButtonParameterAttachment, + to attach the relay to a RangedAudioParameter. This will synchronise the state and events of + the Javascript object with the audio parameter at all times. + + @code + // Add a relay to your AudioProcessorEditor members + WebToggleButtonRelay muteToggleRelay { webComponent, "muteToggle" }; + @endcode + + @code + // In your Javascript GUI code you obtain an object from the framework + import * as Juce from "juce-framework-frontend"; + const checkboxState = Juce.getToggleState("muteToggle"); + @endcode + + @see WebToggleButtonParameterAttachment +*/ +class JUCE_API WebToggleButtonRelay : public OptionsBuilder +{ +public: + /** Creating a relay will ensure that a Javascript object under the provided name will be + available in the specified WebBrowserComponent's context. Use the frontend framework's + getToggleState function with the same name to get a hold of this object. + */ + WebToggleButtonRelay (WebBrowserComponent& browserIn, StringRef nameIn); + + //============================================================================== + /** @internal */ + struct Listener + { + virtual ~Listener() = default; + virtual void toggleStateChanged (bool) = 0; + virtual void initialUpdateRequested() = 0; + }; + + /** @internal */ + void setToggleState (bool newState); + + /** @internal */ + void addListener (Listener* l); + + /** @internal */ + void removeListener (Listener* l); + + /** @internal */ + WebBrowserComponent::Options buildOptions (const WebBrowserComponent::Options& initialOptions) override; + + /** @internal */ + void emitEvent (const var& payload); + +private: + void handleEvent (const var& event); + + WebBrowserComponent& browser; + String name; + Identifier eventId { "__juce__toggle" + name }; + ListenerList listeners; + + JUCE_DECLARE_NON_COPYABLE (WebToggleButtonRelay) + JUCE_DECLARE_NON_MOVEABLE (WebToggleButtonRelay) +}; + +/** Helper class that relays audio parameter information to an object inside a WebBrowserComponent. + + In order to create a relay you need to specify a WebBrowserComponent object and an identifier + for the relayed state. This will result in a Javascript object becoming available inside the + WebBrowserComponent under the provided identifier. + + You can then use a WebComboBoxParameterAttachment as you would a ComboBoxParameterAttachment, + to attach the relay to a RangedAudioParameter. This will synchronise the state and events of + the Javascript object with the audio parameter at all times. + + @code + // Add a relay to your AudioProcessorEditor members + WebComboBoxRelay filterTypeComboRelay { webComponent, "filterTypeCombo" }; + @endcode + + @code + // In your Javascript GUI code you obtain an object from the framework + import * as Juce from "juce-framework-frontend"; + const comboBoxState = Juce.getComboBoxState("filterTypeCombo"); + @endcode + + @see WebComboBoxParameterAttachment +*/ +class JUCE_API WebComboBoxRelay : public OptionsBuilder +{ +public: + /** Creating a relay will ensure that a Javascript object under the provided name will be + available in the specified WebBrowserComponent's context. Use the frontend framework's + getComboBoxState function with the same name to get a hold of this object. + */ + WebComboBoxRelay (WebBrowserComponent& browserIn, StringRef nameIn); + + //============================================================================== + /** @internal */ + struct Listener + { + virtual ~Listener() = default; + virtual void valueChanged (float) = 0; + virtual void initialUpdateRequested() = 0; + }; + + /** @internal */ + void setValue (float newValue); + + /** @internal */ + void addListener (Listener* l); + + /** @internal */ + void removeListener (Listener* l); + + /** @internal */ + WebBrowserComponent::Options buildOptions (const WebBrowserComponent::Options& initialOptions) override; + + /** @internal */ + void emitEvent (const var& payload); + +private: + void handleEvent (const var& event); + + WebBrowserComponent& browser; + String name; + Identifier eventId { "__juce__comboBox" + name }; + ListenerList listeners; + + JUCE_DECLARE_NON_COPYABLE (WebComboBoxRelay) + JUCE_DECLARE_NON_MOVEABLE (WebComboBoxRelay) +}; + +#endif + +} diff --git a/modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebView.java b/modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebView.java deleted file mode 100644 index 673247311c..0000000000 --- a/modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebView.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE framework. - Copyright (c) Raw Material Software Limited - - JUCE is an open source framework subject to commercial or open source - licensing. - - By downloading, installing, or using the JUCE framework, or combining the - JUCE framework with any other source code, object code, content or any other - copyrightable work, you agree to the terms of the JUCE End User Licence - Agreement, and all incorporated terms including the JUCE Privacy Policy and - the JUCE Website Terms of Service, as applicable, which will bind you. If you - do not agree to the terms of these agreements, we will not license the JUCE - framework to you, and you must discontinue the installation or download - process and cease use of the JUCE framework. - - JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ - JUCE Privacy Policy: https://juce.com/juce-privacy-policy - JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ - - Or: - - You may also use this code under the terms of the AGPLv3: - https://www.gnu.org/licenses/agpl-3.0.en.html - - THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL - WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF - MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. - - ============================================================================== -*/ - -package com.rmsl.juce; - -import android.graphics.Bitmap; -import android.net.http.SslError; -import android.os.Message; -import android.webkit.WebResourceResponse; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.webkit.SslErrorHandler; -import android.webkit.WebChromeClient; - - -//============================================================================== -public class JuceWebView -{ - static public class Client extends WebViewClient - { - public Client (long hostToUse) - { - host = hostToUse; - } - - public void hostDeleted () - { - synchronized (hostLock) - { - host = 0; - } - } - - public void onPageFinished (WebView view, String url) - { - if (host == 0) - return; - - webViewPageLoadFinished (host, view, url); - } - - public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) - { - if (host == 0) - return; - - webViewReceivedSslError (host, view, handler, error); - } - - public void onPageStarted (WebView view, String url, Bitmap favicon) - { - if (host != 0) - webViewPageLoadStarted (host, view, url); - } - - public WebResourceResponse shouldInterceptRequest (WebView view, String url) - { - synchronized (hostLock) - { - if (host != 0) - { - boolean shouldLoad = webViewPageLoadStarted (host, view, url); - - if (shouldLoad) - return null; - } - } - - return new WebResourceResponse ("text/html", null, null); - } - - private native boolean webViewPageLoadStarted (long host, WebView view, String url); - - private native void webViewPageLoadFinished (long host, WebView view, String url); - - private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error); - - private long host; - private final Object hostLock = new Object (); - } - - static public class ChromeClient extends WebChromeClient - { - public ChromeClient (long hostToUse) - { - host = hostToUse; - } - - @Override - public void onCloseWindow (WebView window) - { - webViewCloseWindowRequest (host, window); - } - - @Override - public boolean onCreateWindow (WebView view, boolean isDialog, - boolean isUserGesture, Message resultMsg) - { - webViewCreateWindowRequest (host, view); - return false; - } - - private native void webViewCloseWindowRequest (long host, WebView view); - - private native void webViewCreateWindowRequest (long host, WebView view); - - private long host; - private final Object hostLock = new Object (); - } -} diff --git a/modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebView21.java b/modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebView21.java deleted file mode 100644 index ab7f036eb3..0000000000 --- a/modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebView21.java +++ /dev/null @@ -1,141 +0,0 @@ -/* - ============================================================================== - - This file is part of the JUCE framework. - Copyright (c) Raw Material Software Limited - - JUCE is an open source framework subject to commercial or open source - licensing. - - By downloading, installing, or using the JUCE framework, or combining the - JUCE framework with any other source code, object code, content or any other - copyrightable work, you agree to the terms of the JUCE End User Licence - Agreement, and all incorporated terms including the JUCE Privacy Policy and - the JUCE Website Terms of Service, as applicable, which will bind you. If you - do not agree to the terms of these agreements, we will not license the JUCE - framework to you, and you must discontinue the installation or download - process and cease use of the JUCE framework. - - JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ - JUCE Privacy Policy: https://juce.com/juce-privacy-policy - JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ - - Or: - - You may also use this code under the terms of the AGPLv3: - https://www.gnu.org/licenses/agpl-3.0.en.html - - THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL - WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF - MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. - - ============================================================================== -*/ - -package com.rmsl.juce; - -import android.graphics.Bitmap; -import android.net.http.SslError; -import android.os.Message; -import android.webkit.WebResourceResponse; -import android.webkit.WebView; -import android.webkit.WebViewClient; -import android.webkit.SslErrorHandler; -import android.webkit.WebChromeClient; - - -//============================================================================== -public class JuceWebView21 -{ - static public class Client extends WebViewClient - { - public Client (long hostToUse) - { - host = hostToUse; - } - - public void hostDeleted () - { - synchronized (hostLock) - { - host = 0; - } - } - - public void onPageFinished (WebView view, String url) - { - if (host == 0) - return; - - webViewPageLoadFinished (host, view, url); - } - - public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) - { - if (host == 0) - return; - - webViewReceivedSslError (host, view, handler, error); - } - - public void onPageStarted (WebView view, String url, Bitmap favicon) - { - if (host != 0) - webViewPageLoadStarted (host, view, url); - } - - public WebResourceResponse shouldInterceptRequest (WebView view, String url) - { - synchronized (hostLock) - { - if (host != 0) - { - boolean shouldLoad = webViewPageLoadStarted (host, view, url); - - if (shouldLoad) - return null; - } - } - - return new WebResourceResponse ("text/html", null, null); - } - - private native boolean webViewPageLoadStarted (long host, WebView view, String url); - - private native void webViewPageLoadFinished (long host, WebView view, String url); - - private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error); - - private long host; - private final Object hostLock = new Object (); - } - - static public class ChromeClient extends WebChromeClient - { - public ChromeClient (long hostToUse) - { - host = hostToUse; - } - - @Override - public void onCloseWindow (WebView window) - { - webViewCloseWindowRequest (host, window); - } - - @Override - public boolean onCreateWindow (WebView view, boolean isDialog, - boolean isUserGesture, Message resultMsg) - { - webViewCreateWindowRequest (host, view); - return false; - } - - private native void webViewCloseWindowRequest (long host, WebView view); - - private native void webViewCreateWindowRequest (long host, WebView view); - - private long host; - private final Object hostLock = new Object (); - } -} diff --git a/modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebViewClasses.java b/modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebViewClasses.java new file mode 100644 index 0000000000..7317bf0df1 --- /dev/null +++ b/modules/juce_gui_extra/native/java/app/com/rmsl/juce/JuceWebViewClasses.java @@ -0,0 +1,356 @@ +/* + ============================================================================== + + This file is part of the JUCE framework. + Copyright (c) Raw Material Software Limited + + JUCE is an open source framework subject to commercial or open source + licensing. + + By downloading, installing, or using the JUCE framework, or combining the + JUCE framework with any other source code, object code, content or any other + copyrightable work, you agree to the terms of the JUCE End User Licence + Agreement, and all incorporated terms including the JUCE Privacy Policy and + the JUCE Website Terms of Service, as applicable, which will bind you. If you + do not agree to the terms of these agreements, we will not license the JUCE + framework to you, and you must discontinue the installation or download + process and cease use of the JUCE framework. + + JUCE End User Licence Agreement: https://juce.com/legal/juce-8-licence/ + JUCE Privacy Policy: https://juce.com/juce-privacy-policy + JUCE Website Terms of Service: https://juce.com/juce-website-terms-of-service/ + + Or: + + You may also use this code under the terms of the AGPLv3: + https://www.gnu.org/licenses/agpl-3.0.en.html + + THE JUCE FRAMEWORK IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL + WARRANTIES, WHETHER EXPRESSED OR IMPLIED, INCLUDING WARRANTY OF + MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, ARE DISCLAIMED. + + ============================================================================== +*/ + +package com.rmsl.juce; + +import android.content.Context; +import android.graphics.Bitmap; +import android.net.http.SslError; +import android.os.Build; +import android.os.Message; +import android.webkit.JavascriptInterface; +import android.webkit.ValueCallback; +import android.webkit.WebResourceResponse; +import android.webkit.WebResourceRequest; +import android.webkit.WebSettings; +import android.webkit.WebView; +import android.webkit.WebViewClient; +import android.webkit.SslErrorHandler; +import android.webkit.WebChromeClient; + +import java.lang.annotation.Native; +import java.util.ArrayList; + +//============================================================================== +public class JuceWebViewClasses +{ + static public class NativeInterface + { + private long host; + private final Object hostLock = new Object (); + + //============================================================================== + public NativeInterface (long hostToUse) + { + host = hostToUse; + } + + public void hostDeleted () + { + synchronized (hostLock) + { + host = 0; + } + } + + //============================================================================== + public void onPageStarted (WebView view, String url) + { + if (host == 0) + return; + + handleResourceRequest (host, view, url); + } + + public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) + { + synchronized (hostLock) + { + if (host != 0) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + return handleResourceRequest (host, view, request.getUrl().toString()); + } + } + + return null; + } + + public void onPageFinished (WebView view, String url) + { + if (host == 0) + return; + + webViewPageLoadFinished (host, view, url); + } + + public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) + { + if (host == 0) + return; + + webViewReceivedSslError (host, view, handler, error); + } + + public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse) + { + if (host == 0) + return; + + webViewReceivedHttpError (host, view, request, errorResponse); + } + + public void onCloseWindow (WebView window) + { + if (host == 0) + return; + + webViewCloseWindowRequest (host, window); + } + + public boolean onCreateWindow (WebView view, boolean isDialog, + boolean isUserGesture, Message resultMsg) + { + if (host == 0) + return false; + + webViewCreateWindowRequest (host, view); + return false; + } + + public String postMessageHandler (String message) + { + synchronized (hostLock) + { + if (host != 0) + { + String result = postMessage (host, message); + + if (result == null) + return ""; + + return result; + } + } + + return ""; + } + + public void handleJavascriptEvaluationResult (long evalId, String result) + { + if (host == 0) + return; + + evaluationResultHandler (host, evalId, result); + } + + public boolean shouldOverrideUrlLoading (String url) + { + if (host == 0) + return false; + + return ! pageAboutToLoad (host, url); + } + + //============================================================================== + private native WebResourceResponse handleResourceRequest (long host, WebView view, String url); + private native void webViewPageLoadFinished (long host, WebView view, String url); + private native void webViewReceivedSslError (long host, WebView view, SslErrorHandler handler, SslError error); + private native void webViewReceivedHttpError (long host, WebView view, WebResourceRequest request, WebResourceResponse errorResponse); + + private native void webViewCloseWindowRequest (long host, WebView view); + private native void webViewCreateWindowRequest (long host, WebView view); + + private native void evaluationResultHandler (long host, long evalId, String result); + private native String postMessage (long host, String message); + + private native boolean pageAboutToLoad (long host, String url); + } + + static public class Client extends WebViewClient + { + private NativeInterface nativeInterface; + + //============================================================================== + public Client (NativeInterface nativeInterfaceIn) + { + nativeInterface = nativeInterfaceIn; + } + + //============================================================================== + @Override + public void onPageFinished (WebView view, String url) + { + nativeInterface.onPageFinished (view, url); + } + + @Override + public void onReceivedSslError (WebView view, SslErrorHandler handler, SslError error) + { + nativeInterface.onReceivedSslError (view, handler, error); + } + + @Override + public void onReceivedHttpError (WebView view, WebResourceRequest request, WebResourceResponse errorResponse) + { + nativeInterface.onReceivedHttpError (view, request, errorResponse); + } + + @Override + public void onPageStarted (WebView view, String url, Bitmap favicon) + { + nativeInterface.onPageStarted (view, url); + } + + @Override + public WebResourceResponse shouldInterceptRequest (WebView view, WebResourceRequest request) + { + return nativeInterface.shouldInterceptRequest (view, request); + } + + @Override + public boolean shouldOverrideUrlLoading (WebView view, WebResourceRequest request) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) + return nativeInterface.shouldOverrideUrlLoading (request.getUrl().toString()); + + return false; + } + + @Override + public boolean shouldOverrideUrlLoading (WebView view, String url) + { + return nativeInterface.shouldOverrideUrlLoading (url); + } + } + + static public class ChromeClient extends WebChromeClient + { + private NativeInterface nativeInterface; + + //============================================================================== + public ChromeClient (NativeInterface nativeInterfaceIn) + { + nativeInterface = nativeInterfaceIn; + } + + //============================================================================== + @Override + public void onCloseWindow (WebView window) + { + nativeInterface.onCloseWindow (window); + } + + @Override + public boolean onCreateWindow (WebView view, boolean isDialog, + boolean isUserGesture, Message resultMsg) + { + return nativeInterface.onCreateWindow (view, isDialog, isUserGesture, resultMsg); + } + } + + static public class WebAppInterface + { + private NativeInterface nativeInterface; + String userScripts; + + //============================================================================== + public WebAppInterface (NativeInterface nativeInterfaceIn, String userScriptsIn) + { + nativeInterface = nativeInterfaceIn; + userScripts = userScriptsIn; + } + + //============================================================================== + @JavascriptInterface + public String postMessage (String message) + { + return nativeInterface.postMessageHandler (message); + } + + @JavascriptInterface + public String getAndroidUserScripts() + { + return userScripts; + } + } + + static public class JuceWebView extends WebView + { + private NativeInterface nativeInterface; + private long evaluationId = 0; + + public JuceWebView (Context context, long host, String userAgent, String initScripts) + { + super (context); + + nativeInterface = new NativeInterface (host); + + WebSettings settings = getSettings(); + settings.setJavaScriptEnabled (true); + settings.setBuiltInZoomControls (true); + settings.setDisplayZoomControls (false); + settings.setSupportMultipleWindows (true); + settings.setUserAgentString (userAgent); + + setWebChromeClient (new ChromeClient (nativeInterface)); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + { + setWebContentsDebuggingEnabled (true); + } + + setWebViewClient (new Client (nativeInterface)); + addJavascriptInterface (new WebAppInterface (nativeInterface, initScripts), "__JUCE__"); + } + + public void disconnectNative() + { + nativeInterface.hostDeleted(); + } + + public long evaluateJavascript (String script) + { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) + { + final long currentEvaluationId = evaluationId++; + final NativeInterface accessibleNativeInterface = nativeInterface; + + super.evaluateJavascript (script, new ValueCallback() + { + @Override + public void onReceiveValue(String result) + { + accessibleNativeInterface.handleJavascriptEvaluationResult (currentEvaluationId, + result); + } + }); + + return currentEvaluationId; + } + + return -1; + } + } +} diff --git a/modules/juce_gui_extra/native/juce_NSViewComponent_mac.mm b/modules/juce_gui_extra/native/juce_NSViewComponent_mac.mm index 4479ce7f15..d91ef3fb03 100644 --- a/modules/juce_gui_extra/native/juce_NSViewComponent_mac.mm +++ b/modules/juce_gui_extra/native/juce_NSViewComponent_mac.mm @@ -90,9 +90,6 @@ public: auto peerView = (NSView*) peer->getNativeHandle(); [peerView addSubview: view]; - if (@available (macOS 10.10, *)) - [view setAccessibilityParent:static_cast (owner.getAccessibilityHandler()->getNativeImplementation())]; - componentMovedOrResized (false, false); } else diff --git a/modules/juce_gui_extra/native/juce_WebBrowserComponent_android.cpp b/modules/juce_gui_extra/native/juce_WebBrowserComponent_android.cpp index 4b37b6b2f4..7c7f85b6b8 100644 --- a/modules/juce_gui_extra/native/juce_WebBrowserComponent_android.cpp +++ b/modules/juce_gui_extra/native/juce_WebBrowserComponent_android.cpp @@ -35,119 +35,276 @@ namespace juce { -//============================================================================== -// This byte-code is generated from native/java/com/rmsl/juce/JuceWebView.java with min sdk version 16 +// This byte-code is generated from native/java/app/com/rmsl/juce/JuceWebViewClasses.java with min sdk version 23. // See juce_core/native/java/README.txt on how to generate this byte-code. -static const uint8 JuceWebView16ByteCode[] = -{31,139,8,8,150,114,161,94,0,3,74,117,99,101,87,101,98,86,105,101,119,49,54,66,121,116,101,67,111,100,101,46,100,101,120,0,125, -150,93,108,20,85,20,199,207,124,236,78,119,218,110,183,5,74,191,40,109,69,168,72,89,176,162,165,11,88,40,159,101,81,161,88,226, -106,34,211,221,107,59,101,118,102,153,153,109,27,67,16,161,137,134,240,96,4,222,72,140,9,18,35,62,18,195,131,15,4,53,250,226,155, -209,23,30,212,4,195,131,15,198,24,98,20,19,255,119,238,221,101,129,226,110,126,123,206,61,231,220,123,207,189,247,236,204, -45,176,121,115,195,224,38,242,254,253,180,235,204,157,47,183,223,188,240,115,199,231,119,79,174,251,240,23,243,246,209,206,219, -79,221,168,39,42,17,209,252,196,179,45,36,63,247,76,162,17,18,246,165,92,42,68,141,144,55,32,117,200,215,85,162,37,144,39,32, -53,30,131,159,108,29,81,8,217,16,71,27,244,129,181,96,0,188,0,118,130,55,192,9,240,1,184,6,126,0,247,64,171,65,244,28,56,10,22, -192,71,224,107,112,27,212,97,220,149,96,16,236,1,99,224,69,48,14,94,5,71,65,1,216,192,3,62,152,7,111,131,115,224,60,184,4,62, -6,87,193,117,112,19,124,7,126,4,191,130,223,192,63,160,62,65,212,9,214,130,109,96,31,176,64,17,204,131,147,224,12,56,11,222,7,87, -193,55,224,39,240,23,104,54,197,126,96,73,132,212,9,67,18,204,4,51,97,155,169,129,196,62,38,65,19,72,129,102,192,55,126,137, -220,235,101,160,21,44,7,43,65,76,142,119,57,38,108,149,67,106,147,250,103,176,183,75,253,26,244,78,169,127,1,189,67,234,223,66, -239,146,250,247,208,187,165,126,11,250,10,169,95,174,177,223,169,209,255,132,222,35,243,227,227,244,74,157,39,197,215,182,58, -90,99,138,250,229,58,87,71,82,180,99,164,144,8,53,35,89,39,219,9,82,165,140,211,64,36,27,104,125,36,53,26,150,50,19,141,35,226, -76,244,91,19,201,58,74,71,50,65,27,34,105,208,70,57,239,96,36,99,180,37,146,245,180,53,146,58,109,139,246,94,204,155,170,206, -79,145,22,147,123,201,107,58,68,227,138,72,51,26,79,145,231,87,241,47,192,255,149,244,215,75,127,170,198,127,1,254,63,164,159, -103,189,0,253,172,121,95,63,111,138,62,151,76,30,175,69,122,187,41,234,161,148,226,190,62,140,87,74,241,61,127,45,165,80,174, -69,212,137,142,17,248,248,171,77,81,7,227,56,140,210,72,156,212,141,73,172,62,22,249,6,76,81,111,194,103,192,215,18,213,87,101, -158,231,171,243,168,15,205,163,97,30,53,154,71,156,149,66,59,77,81,167,135,183,107,180,66,105,69,250,185,29,42,117,43,73,140, -208,173,172,145,245,168,224,155,192,156,90,212,62,96,138,122,30,31,81,137,247,192,153,168,155,225,75,70,150,210,68,146,244,131, -253,127,243,253,212,163,248,9,83,172,173,54,126,8,163,137,232,38,68,39,177,199,122,180,222,163,166,168,183,241,210,35,99,251, -42,25,199,141,5,227,162,113,101,54,206,207,182,255,46,63,27,158,147,74,51,232,247,4,175,97,229,240,105,172,68,29,95,64,127,12, -184,81,211,227,67,90,3,241,118,41,215,68,123,47,154,52,132,185,186,213,102,165,91,237,83,227,212,161,109,194,46,42,244,76,179, -209,219,127,183,17,214,53,242,185,183,4,115,244,70,187,196,191,61,82,226,89,99,10,191,216,213,100,244,28,172,253,156,122,168,125, -238,161,54,175,17,3,79,2,165,166,205,45,122,85,170,164,73,189,89,214,30,63,111,173,234,173,232,98,12,174,55,227,219,36,107, -211,64,230,75,96,141,111,177,93,59,220,70,13,163,211,190,87,100,163,142,205,220,144,226,82,42,99,148,26,43,231,217,17,54,57,97, -179,185,245,51,214,172,69,90,54,155,165,246,172,229,22,124,207,46,164,167,124,171,52,109,231,131,244,14,59,44,90,165,12,117, -86,93,46,11,211,211,97,88,74,143,7,206,46,223,247,252,12,45,173,58,189,32,125,128,5,129,53,197,50,212,83,181,206,177,201,99,118, -88,237,176,23,118,135,249,139,68,32,165,218,148,51,180,106,145,136,67,44,240,202,126,158,65,150,60,55,192,76,109,139,68,241, -165,101,168,251,49,158,202,248,253,217,188,87,76,251,197,192,73,207,96,75,210,53,251,178,234,193,76,250,254,47,82,198,116,62,62, -134,15,80,176,156,89,251,88,218,114,93,47,180,66,219,115,211,187,220,188,227,5,182,59,53,234,88,65,192,211,125,52,102,159, -235,50,95,250,123,23,241,31,96,197,73,25,192,16,178,44,203,207,51,109,123,232,88,42,135,227,161,207,172,98,134,90,132,217,177, -220,169,244,75,147,51,44,31,62,104,67,28,210,200,144,50,65,234,196,24,105,19,99,89,210,241,147,165,24,255,205,194,154,133,53, -203,173,188,169,228,72,207,69,238,92,54,151,203,82,189,149,207,227,224,119,59,214,84,64,49,198,143,153,140,55,173,89,59,239,185, -100,76,139,19,39,125,218,11,66,170,231,191,59,153,195,66,86,160,58,222,200,122,249,99,148,224,218,97,239,149,128,81,157,29, -236,180,45,199,155,162,70,59,128,193,223,195,130,176,236,51,210,93,171,200,168,209,115,71,177,111,236,136,237,22,188,57,74,162, -137,85,134,53,237,151,81,129,187,241,39,8,166,49,69,163,104,143,135,150,207,103,108,241,220,67,44,207,236,89,86,168,84,36,37, -124,22,148,157,240,64,48,69,173,193,180,87,118,10,251,220,144,161,200,74,225,33,118,188,140,217,201,20,246,172,103,21,40,17,178, -121,254,47,40,58,164,135,211,118,64,90,217,119,40,54,107,57,101,228,56,139,243,166,246,185,74,165,85,19,173,140,212,81,113, -213,36,93,241,45,151,62,158,48,159,170,186,136,214,135,28,149,213,84,58,60,178,164,248,156,216,13,71,89,107,36,213,229,25,117, -102,110,128,2,101,196,72,230,232,45,125,248,233,117,131,92,27,136,188,155,51,234,94,120,7,201,72,110,221,223,221,69,25,117,120, -200,72,158,237,162,253,218,240,208,147,70,242,221,28,141,106,195,171,87,69,182,131,220,185,98,235,123,51,26,109,90,58,208,27,163, -206,149,231,113,13,48,146,164,54,40,67,109,245,106,163,218,167,39,214,45,87,42,138,170,38,149,161,46,181,45,209,134,23,189, -166,146,170,180,104,239,156,210,47,24,218,105,188,167,128,174,220,48,20,229,22,94,104,122,76,133,183,14,222,123,70,92,122,57,9, -229,147,58,68,128,115,9,69,185,14,126,79,240,231,99,51,34,111,153,149,247,179,82,35,71,72,220,107,249,51,179,114,183,229,207, -203,218,251,109,229,142,27,163,251,247,220,56,221,191,235,106,41,161,243,231,188,210,35,238,11,23,160,199,123,164,29,29,149,148, -176,243,123,149,218,35,230,229,119,99,77,198,243,119,191,222,35,239,22,220,32,251,70,119,144,148,200,149,223,195,255,3,213, -111,243,3,192,11,0,0,0,0}; - -//============================================================================== -// This byte-code is generated from native/javacore/app/com/rmsl/juce/JuceWebView21.java with min sdk version 21 -// See juce_core/native/java/README.txt on how to generate this byte-code. -static const uint8 JuceWebView21ByteCode[] = -{31,139,8,8,45,103,161,94,0,3,74,117,99,101,87,101,98,86,105,101,119,50,49,46,100,101,120,0,141,151,93,140,27,87,21,199,207, -204,216,30,219,99,59,182,55,251,145,143,221,110,210,173,178,105,154,186,155,164,52,169,211,106,241,38,219,221,48,41,52,155,108, -138,43,85,154,181,47,235,73,188,51,206,204,120,119,65,162,132,80,148,138,34,148,168,20,181,125,129,135,16,129,4,18,168,125,136, -42,224,133,207,74,60,160,138,135,208,71,210,151,162,128,242,148,86,136,7,254,247,99,28,111,18,34,108,253,124,206,61,231,220, -143,115,239,153,241,76,147,173,103,159,216,255,36,253,228,121,251,31,197,127,189,252,251,226,31,94,89,242,217,87,31,123,227, -151,175,95,184,28,222,168,188,152,39,234,16,209,250,226,129,50,169,207,247,115,68,243,36,237,67,188,173,17,149,32,111,66,38,32, -175,233,68,195,144,215,33,13,200,75,248,105,101,136,110,65,222,74,17,125,6,82,38,81,1,148,192,35,96,18,236,5,115,224,37,176,14, -190,7,126,1,62,4,159,130,209,52,209,83,224,52,248,22,248,41,248,19,184,9,114,24,191,2,102,65,29,120,224,28,232,130,175,129, -243,224,34,120,29,92,2,63,0,111,131,31,130,171,224,93,240,62,248,0,124,8,62,2,55,192,39,224,54,160,44,145,5,6,193,4,120,20,28, -2,243,224,203,160,5,190,14,46,131,119,192,143,193,175,193,7,224,175,224,35,240,49,248,4,220,2,183,65,201,66,206,96,22,188,12, -214,193,101,75,238,25,210,37,164,69,106,106,130,153,176,237,132,227,160,2,216,4,138,36,247,157,31,204,0,216,12,6,213,153,240, -253,31,1,91,192,86,176,19,36,129,174,206,48,165,198,111,165,164,125,64,217,183,170,113,248,103,155,210,59,136,217,174,244,117, -232,99,74,63,223,167,127,23,250,168,210,223,130,254,144,210,175,64,223,161,244,159,245,233,215,160,143,43,253,119,125,246,63, -247,233,215,161,63,172,114,226,99,78,40,253,227,148,220,143,61,98,95,202,180,87,237,205,30,33,101,91,199,247,89,145,167,33,114, -225,251,185,91,228,92,16,237,172,178,91,162,98,185,204,208,62,33,139,180,95,200,36,213,148,156,17,227,202,184,28,250,61,38,100, -142,14,8,153,167,39,133,180,232,115,66,102,233,41,33,53,122,90,200,52,29,17,114,19,29,21,210,164,89,33,83,244,156,56,79,185, -142,114,111,61,132,158,242,124,248,135,207,122,16,141,253,57,82,243,75,127,182,207,63,7,255,11,202,159,87,254,114,159,255,69, -248,47,42,63,63,255,18,244,225,220,29,125,60,39,251,236,206,241,120,67,232,111,91,114,172,78,81,67,123,39,198,235,20,121,93,189, -132,118,189,44,107,48,129,17,248,248,87,45,185,222,5,28,104,103,58,77,250,84,1,89,38,133,239,231,150,220,123,233,203,192,87,22, -181,27,207,115,173,55,79,226,174,121,12,204,163,139,121,146,34,82,163,63,90,50,255,147,159,55,104,84,27,194,242,235,53,157, -198,180,2,70,24,211,118,137,58,78,17,95,111,6,115,26,162,253,23,75,94,43,11,211,58,241,30,83,72,251,16,124,5,97,233,44,150,40, -241,194,228,191,121,93,36,68,252,223,44,153,91,127,252,65,140,38,163,203,136,46,136,154,225,249,222,176,228,117,179,208,185, -103,236,64,39,243,156,249,170,249,166,121,117,53,53,128,21,77,222,166,94,191,127,254,159,253,54,247,250,241,92,116,250,212,146, -53,94,214,78,94,192,14,232,11,175,162,63,6,156,50,18,169,131,198,102,226,237,160,168,163,54,114,134,87,228,247,145,156,209, -89,40,211,220,155,89,58,136,185,199,244,146,54,166,239,212,211,180,213,56,140,211,48,104,95,201,220,49,121,59,15,235,46,117, -239,222,134,57,119,139,221,230,223,73,37,177,239,57,233,151,167,83,16,247,242,254,207,127,238,106,167,19,27,219,188,214,248,169, -104,125,109,110,73,40,153,196,106,99,221,196,105,24,74,31,80,245,204,239,131,70,47,50,214,77,113,15,211,85,164,161,164,166,252, -3,248,150,212,53,144,193,157,99,144,239,255,97,215,115,163,103,41,55,211,10,252,21,54,211,118,153,23,81,74,73,237,24,149,143, -117,27,236,52,91,90,116,217,218,190,169,199,207,56,171,14,105,54,25,182,109,211,22,219,241,154,129,239,54,43,203,129,211,105, -185,141,176,82,115,163,21,167,83,165,82,207,229,177,168,114,42,112,171,180,109,131,169,21,69,157,202,66,216,62,26,4,126,80,165, -205,61,167,31,86,142,179,48,116,150,89,149,198,123,214,53,182,116,214,141,122,29,230,96,111,179,224,62,17,88,106,127,42,85,122, -248,62,17,39,88,232,119,131,6,59,193,206,117,89,136,160,137,7,6,133,29,223,11,177,156,145,251,68,241,125,169,210,216,255,240, -196,139,120,212,110,248,43,149,96,37,108,87,206,96,63,43,27,54,117,98,227,130,39,30,28,171,162,70,31,20,85,165,157,118,211,105, -175,186,103,43,142,231,249,145,19,185,190,87,57,234,53,218,126,232,122,203,51,109,39,12,249,162,239,141,153,247,60,22,40,255, -142,251,248,143,179,149,37,21,192,16,50,104,243,130,168,184,62,58,118,186,209,66,20,48,103,165,74,101,105,110,59,222,114,229, -139,75,103,88,35,218,104,67,28,150,81,37,109,145,244,197,99,100,44,30,179,41,129,31,155,146,252,215,134,21,37,182,104,115,43, -111,106,117,74,212,133,187,110,215,235,54,89,78,163,129,26,153,109,59,203,33,37,25,175,8,202,11,17,31,22,153,95,113,86,221,134, -239,81,106,153,69,167,130,54,153,45,89,51,148,104,249,97,68,22,255,61,194,218,44,98,77,74,243,134,237,55,206,82,134,107,39, -253,83,24,33,237,134,71,92,167,237,47,83,222,13,97,8,158,67,169,116,3,70,9,207,89,97,148,247,189,25,108,39,59,237,122,77,127, -141,10,104,34,249,168,175,253,37,212,240,44,46,175,176,133,41,242,178,189,16,57,1,159,113,192,247,78,176,6,115,87,89,115,14,87, -130,40,106,42,223,49,198,133,78,102,32,107,148,50,1,11,187,237,232,120,184,76,67,97,203,239,182,155,243,94,196,80,159,157,72, -149,49,101,165,221,246,157,38,101,34,182,206,175,178,149,54,37,162,150,27,82,58,242,229,182,147,209,197,118,36,87,157,118,23, -185,172,162,96,104,203,90,92,174,189,132,226,49,183,198,174,190,228,98,223,176,242,241,196,248,164,189,100,135,238,114,196,89, -143,40,251,189,169,15,223,229,233,229,159,90,147,251,185,166,85,204,130,62,92,213,207,172,237,165,87,180,121,179,80,167,111, -107,137,218,19,83,79,115,245,113,225,254,45,85,245,223,124,3,1,7,200,44,60,243,133,177,237,116,88,175,77,155,133,239,108,39, -219,168,77,239,54,11,23,235,116,194,168,29,154,20,182,35,70,109,207,46,161,213,245,218,33,115,244,153,215,254,110,208,225,161, -189,59,146,180,237,161,55,112,223,55,11,164,23,180,233,145,188,190,73,127,36,145,153,26,214,98,69,215,139,218,244,118,125,196, -26,201,145,110,224,233,74,43,39,191,121,62,113,41,109,92,208,73,3,41,237,87,105,77,187,142,127,255,100,74,135,55,11,239,103, -105,83,121,99,44,237,74,6,81,224,181,172,166,189,7,110,130,43,22,191,201,15,162,199,143,248,255,123,81,253,71,104,125,50,126, -223,224,247,250,248,157,131,223,227,251,223,59,226,119,15,254,63,30,191,127,164,232,206,59,136,81,148,58,255,239,210,198,229, -179,22,30,59,40,53,46,237,252,57,75,43,202,231,17,254,92,172,143,203,121,249,59,139,161,226,249,115,81,98,92,206,197,159,157,72, -245,221,175,22,206,215,202,223,143,254,11,250,146,74,12,88,13,0,0,0,0}; - +static const uint8 juceWebViewClasses[] = +{ + 0x1f, 0x8b, 0x08, 0x08, 0x96, 0xd6, 0x11, 0x65, 0x00, 0x03, 0x63, 0x6c, + 0x61, 0x73, 0x73, 0x65, 0x73, 0x2e, 0x64, 0x65, 0x78, 0x00, 0x95, 0x59, + 0x6b, 0x6c, 0x1c, 0x57, 0x15, 0x3e, 0x33, 0xb3, 0x0f, 0xbf, 0xf6, 0x6d, + 0x7b, 0xd7, 0x8f, 0x38, 0x9b, 0xb5, 0xdb, 0x3c, 0x1a, 0x67, 0x6d, 0x27, + 0x4e, 0xec, 0x38, 0xa4, 0x8e, 0x1d, 0x87, 0xc4, 0xac, 0xdb, 0x26, 0xb6, + 0x37, 0xd4, 0x09, 0x4d, 0xc7, 0xbb, 0x13, 0xef, 0x26, 0xeb, 0x99, 0x65, + 0x67, 0xd6, 0x09, 0x54, 0x04, 0x53, 0x82, 0x14, 0xa9, 0x0f, 0x55, 0xe2, + 0x55, 0x21, 0x28, 0x41, 0x0a, 0x52, 0xa9, 0x5a, 0x29, 0x3f, 0x4a, 0x55, + 0x5a, 0x7e, 0x14, 0xd4, 0x1f, 0x15, 0x14, 0x10, 0x10, 0xa9, 0x95, 0xa8, + 0xa0, 0x88, 0xa2, 0x46, 0xa8, 0x48, 0xfc, 0x00, 0x29, 0x42, 0x88, 0xf2, + 0xdd, 0x3b, 0x77, 0xd6, 0xe3, 0x8d, 0x4b, 0xda, 0xb5, 0xbf, 0x3d, 0xe7, + 0x9e, 0x73, 0xee, 0xbd, 0xe7, 0x9e, 0x7b, 0xee, 0x99, 0x99, 0x9d, 0xbc, + 0x76, 0xb1, 0x69, 0x60, 0xf7, 0x30, 0xfd, 0xbc, 0xfa, 0xb5, 0x0f, 0x7f, + 0x7c, 0xf3, 0xbf, 0x7f, 0x3c, 0x39, 0xf6, 0x98, 0xfc, 0xbd, 0x6b, 0x37, + 0xae, 0xdf, 0xd4, 0x27, 0x2e, 0x3e, 0xba, 0x2d, 0x9e, 0x6b, 0xe9, 0x24, + 0x2a, 0x13, 0xd1, 0xc5, 0xec, 0x9e, 0x28, 0x89, 0xcf, 0x5c, 0x07, 0x91, + 0x41, 0xb6, 0x7c, 0x33, 0xb0, 0x53, 0x26, 0xba, 0x1b, 0xf4, 0x59, 0xd0, + 0x66, 0x26, 0xf7, 0x10, 0x8d, 0x81, 0xbe, 0x01, 0xea, 0x07, 0xbd, 0xea, + 0x23, 0x7a, 0x1c, 0xbd, 0x57, 0xd1, 0x18, 0x08, 0x12, 0x0d, 0x01, 0xfb, + 0x80, 0x7b, 0x81, 0x49, 0xe0, 0x38, 0xf0, 0x20, 0x70, 0x1a, 0x78, 0x18, + 0x58, 0x06, 0xca, 0x40, 0x15, 0x78, 0x04, 0xf8, 0x32, 0x70, 0x19, 0xf8, + 0x3e, 0xf0, 0x0a, 0xf0, 0x2b, 0xe0, 0x6f, 0x00, 0x85, 0x30, 0x2f, 0x70, + 0x14, 0x28, 0x00, 0x57, 0x80, 0xe7, 0x80, 0xdf, 0x00, 0xff, 0x02, 0x62, + 0x61, 0xa2, 0x7e, 0x60, 0x06, 0xb8, 0x04, 0xfc, 0x08, 0xf8, 0x03, 0xd0, + 0x1c, 0xc1, 0xdc, 0x80, 0x09, 0x7c, 0x1b, 0x78, 0x15, 0x78, 0x17, 0x20, + 0xf8, 0xd8, 0x0b, 0x4c, 0x00, 0x59, 0xe0, 0x1c, 0xf0, 0x08, 0x70, 0x09, + 0x78, 0x14, 0xb8, 0x02, 0x3c, 0x01, 0x7c, 0x1d, 0xf8, 0x0e, 0xf0, 0x0c, + 0xf0, 0x43, 0xe0, 0x39, 0xe0, 0x3a, 0xf0, 0x12, 0xf0, 0x0a, 0xf0, 0x4b, + 0xe0, 0xd7, 0xc0, 0xef, 0x81, 0xb7, 0x80, 0x77, 0x80, 0xbf, 0x00, 0x7f, + 0x07, 0xfe, 0x0d, 0x44, 0x63, 0x44, 0x7d, 0xc0, 0x08, 0x70, 0x04, 0x38, + 0x03, 0xac, 0x00, 0x5f, 0x05, 0x1e, 0x03, 0x5e, 0x00, 0x5e, 0x07, 0x7e, + 0x01, 0xbc, 0x05, 0xfc, 0x19, 0x78, 0x1f, 0xb8, 0x05, 0xf8, 0x5b, 0x89, + 0xe2, 0xc0, 0x56, 0x60, 0x2f, 0x70, 0x04, 0x38, 0x05, 0x18, 0xc0, 0x2a, + 0xf0, 0x04, 0xf0, 0x03, 0xe0, 0x27, 0xc0, 0x6f, 0x81, 0xf7, 0x01, 0xb9, + 0x8d, 0xa8, 0x1d, 0xd8, 0x01, 0x4c, 0x03, 0xa7, 0x01, 0x0b, 0x78, 0x12, + 0xf8, 0x16, 0x70, 0x15, 0x78, 0x01, 0x78, 0x13, 0xf8, 0x2b, 0xf0, 0x01, + 0xe0, 0x6f, 0x47, 0x2c, 0x80, 0x03, 0x40, 0x16, 0x30, 0x00, 0x6c, 0x23, + 0x89, 0xb0, 0x13, 0xc2, 0x48, 0x22, 0x64, 0x04, 0xb7, 0x08, 0xd3, 0x10, + 0xba, 0x51, 0x1c, 0x48, 0x00, 0x48, 0x11, 0x42, 0xea, 0x50, 0x17, 0xd0, + 0x0d, 0x6c, 0x02, 0x7a, 0x44, 0xbe, 0x24, 0x81, 0x2d, 0x40, 0x0a, 0xe8, + 0x05, 0xfa, 0x80, 0xbb, 0xc8, 0xce, 0xa1, 0xad, 0xc0, 0x36, 0x60, 0x3b, + 0xb0, 0x83, 0xe5, 0x16, 0x30, 0x4a, 0x76, 0x1e, 0xb1, 0xcf, 0x48, 0x80, + 0xa8, 0x11, 0x54, 0x11, 0xed, 0x66, 0x81, 0x71, 0xc8, 0x03, 0x82, 0xcf, + 0x08, 0x9b, 0x16, 0x61, 0xd3, 0x28, 0x7c, 0x60, 0x9f, 0x26, 0xc1, 0xcf, + 0x05, 0x6c, 0x7d, 0xb7, 0x18, 0x73, 0xa7, 0xf0, 0x91, 0x7d, 0xfa, 0x05, + 0x7f, 0x1a, 0xf2, 0x5d, 0x82, 0xcf, 0x83, 0x4f, 0x0b, 0xbe, 0x0c, 0x7e, + 0x50, 0xf0, 0x17, 0x5d, 0xfc, 0x65, 0xf0, 0x03, 0x82, 0x1f, 0x77, 0xd9, + 0xb3, 0xb9, 0x86, 0x04, 0xff, 0x14, 0xf8, 0xdd, 0x82, 0x7f, 0xda, 0x25, + 0xbf, 0xe6, 0xe2, 0x9f, 0x07, 0xbf, 0x57, 0xf0, 0x2f, 0xba, 0xf8, 0xd7, + 0xc0, 0xef, 0x11, 0xfc, 0x9b, 0x2e, 0xf9, 0x0d, 0x57, 0xdf, 0x77, 0xc1, + 0x0f, 0x0b, 0xfe, 0xa6, 0x4b, 0xfe, 0x4f, 0x57, 0xdf, 0xff, 0xb8, 0xe4, + 0x9e, 0xe0, 0x9a, 0xbc, 0x25, 0xb8, 0x26, 0x1f, 0x71, 0xd9, 0xb7, 0xb2, + 0xf3, 0x2a, 0xf8, 0x6e, 0xf0, 0xfb, 0xc5, 0x5e, 0xb2, 0x75, 0x1d, 0x10, + 0x7c, 0xc6, 0xc5, 0x33, 0xdf, 0x3e, 0x25, 0xf8, 0xbe, 0xa0, 0x5d, 0x0b, + 0x7a, 0xc4, 0x98, 0x21, 0x64, 0xcb, 0x7d, 0x3c, 0x7f, 0x6c, 0x1a, 0x41, + 0x06, 0xe5, 0x38, 0x8d, 0xd1, 0x59, 0x91, 0x53, 0x4b, 0x22, 0xaf, 0x26, + 0x39, 0xb5, 0xed, 0x58, 0x8e, 0x4d, 0x73, 0xda, 0x45, 0x19, 0xe6, 0x93, + 0x90, 0xb7, 0x62, 0x07, 0x35, 0x9e, 0x0f, 0x5e, 0xca, 0xf3, 0xbd, 0x6e, + 0x20, 0x99, 0xef, 0xb3, 0x44, 0x9f, 0xe6, 0x39, 0x90, 0xa4, 0x07, 0x05, + 0x5d, 0x10, 0xf4, 0x94, 0xa0, 0xa7, 0x39, 0xed, 0xa1, 0xcf, 0x11, 0xcb, + 0x9d, 0x30, 0xef, 0x17, 0x40, 0x86, 0x4e, 0x10, 0xcb, 0x73, 0x7b, 0x9c, + 0x10, 0xb2, 0xd8, 0xa6, 0x31, 0xba, 0x9f, 0xd3, 0x3e, 0x7a, 0x80, 0xfb, + 0x6f, 0xcb, 0xc3, 0xd4, 0x4e, 0xc7, 0x39, 0x8d, 0xd3, 0x09, 0x4e, 0xdb, + 0x68, 0x8e, 0xd3, 0x56, 0x9a, 0xe7, 0x54, 0x21, 0x95, 0xd3, 0x14, 0x2d, + 0x72, 0xda, 0xcb, 0x69, 0x04, 0x27, 0x44, 0xe6, 0xb4, 0x9b, 0x66, 0x39, + 0xed, 0xe1, 0x34, 0x0a, 0x4e, 0xe6, 0x74, 0x13, 0x8d, 0x73, 0xda, 0x40, + 0x87, 0x44, 0x3c, 0x26, 0x38, 0xf5, 0xd0, 0x11, 0x4e, 0xa3, 0xf4, 0x10, + 0xa7, 0x49, 0x3a, 0xc3, 0x69, 0x82, 0x1e, 0xe6, 0xf1, 0x69, 0xe4, 0xfd, + 0x63, 0x88, 0xc0, 0x61, 0x4e, 0x43, 0x74, 0x94, 0x53, 0x99, 0x8e, 0x71, + 0xda, 0x40, 0x9f, 0xe1, 0xd4, 0x5e, 0x4f, 0x4c, 0xac, 0x27, 0x26, 0xd6, + 0xc1, 0xe8, 0x09, 0x4e, 0xed, 0x75, 0xc4, 0xc4, 0x3a, 0x62, 0x38, 0xb1, + 0x59, 0x4e, 0x7d, 0x74, 0x92, 0x53, 0x3f, 0x7d, 0x96, 0x53, 0x7b, 0x7d, + 0x31, 0x9c, 0xe0, 0x45, 0x4e, 0x9b, 0xa9, 0x28, 0xe8, 0x39, 0x4e, 0x83, + 0x74, 0x9e, 0xd3, 0x00, 0x95, 0x38, 0x6d, 0xa1, 0x65, 0xbe, 0x6f, 0x5d, + 0xdc, 0xcf, 0x56, 0xec, 0xdb, 0x14, 0xa7, 0x7e, 0x3e, 0x6e, 0x9b, 0x88, + 0x7b, 0x57, 0x2d, 0xfe, 0x84, 0x9d, 0xb4, 0xcf, 0x30, 0xfb, 0x7c, 0x88, + 0xcf, 0x9b, 0x28, 0x30, 0x5f, 0x8c, 0xdb, 0xed, 0xb0, 0xd0, 0x07, 0x5d, + 0xfa, 0x77, 0xa0, 0x7f, 0x52, 0xe8, 0x23, 0x42, 0xce, 0xea, 0xd0, 0x40, + 0xc0, 0xd6, 0x7f, 0x00, 0xfd, 0x4b, 0x42, 0x1f, 0x15, 0xfd, 0x03, 0xae, + 0xfe, 0x1e, 0x14, 0xaf, 0xdf, 0x09, 0x7d, 0x4c, 0xe8, 0xbb, 0x5c, 0xfa, + 0x56, 0xe8, 0x6f, 0x0a, 0x7d, 0xeb, 0x06, 0xfa, 0x3e, 0xe8, 0x8f, 0x24, + 0xec, 0x76, 0xdb, 0x06, 0xfa, 0xc3, 0xd0, 0x9f, 0x16, 0x7a, 0x1f, 0xd6, + 0xef, 0x15, 0x3a, 0x56, 0xe3, 0xe6, 0xb0, 0x20, 0x7d, 0x7e, 0x2b, 0x25, + 0x27, 0x9b, 0xe4, 0x10, 0x02, 0x20, 0xe3, 0x4f, 0x22, 0xc7, 0x16, 0xb5, + 0x27, 0xcc, 0x5a, 0xa7, 0x24, 0xb6, 0x62, 0x85, 0x6b, 0xd7, 0x74, 0xbc, + 0x6f, 0xf2, 0x6e, 0x64, 0x44, 0x10, 0x59, 0xa2, 0xd4, 0x6a, 0xa4, 0x3d, + 0xae, 0x44, 0xfa, 0x40, 0x9a, 0x92, 0x4a, 0x8b, 0x1c, 0x91, 0x89, 0x6b, + 0x9d, 0xbe, 0x2c, 0xc6, 0x73, 0x92, 0x44, 0x95, 0x30, 0x1b, 0xaf, 0x45, + 0xd6, 0xc3, 0x64, 0xd3, 0xe4, 0x00, 0x76, 0xbd, 0x49, 0x0a, 0x49, 0xeb, + 0xed, 0xfd, 0xc2, 0xde, 0xad, 0xbf, 0xdd, 0xcf, 0x26, 0xee, 0xa7, 0x54, + 0xe7, 0x8b, 0xaf, 0xe6, 0xcb, 0x36, 0xf8, 0xc2, 0x74, 0x9e, 0xf5, 0x3a, + 0x85, 0xe9, 0xb6, 0x53, 0x4c, 0x0e, 0x22, 0x2e, 0x4c, 0x5b, 0xd7, 0x6f, + 0x7c, 0x07, 0x62, 0xf3, 0x51, 0xba, 0x7b, 0x84, 0xce, 0x5b, 0xf3, 0xc5, + 0xf6, 0x42, 0xa6, 0x53, 0xb2, 0x42, 0x0b, 0x8a, 0x07, 0x7e, 0xb1, 0xea, + 0x15, 0xe4, 0xfe, 0xba, 0x63, 0xb7, 0x59, 0xea, 0xc6, 0x7a, 0x12, 0xc8, + 0x27, 0xd6, 0x5f, 0xae, 0x8d, 0xdd, 0xc0, 0xc6, 0x1e, 0x50, 0x68, 0x76, + 0xd0, 0x83, 0xf1, 0x7b, 0x29, 0x3c, 0x11, 0x84, 0xbd, 0x5c, 0xdb, 0x33, + 0x96, 0x3f, 0xb3, 0xe3, 0x5e, 0x6a, 0x97, 0x25, 0x7a, 0x55, 0x5e, 0x98, + 0xf0, 0xd2, 0xdc, 0x84, 0x8f, 0x52, 0x4a, 0x84, 0xca, 0xc7, 0xdb, 0x69, + 0x52, 0x36, 0x06, 0x1a, 0xb1, 0xa6, 0x30, 0xc6, 0xf1, 0xd5, 0xd6, 0xc9, + 0xea, 0x52, 0x39, 0xd9, 0x80, 0x5a, 0x8b, 0x8b, 0x2a, 0x2d, 0x24, 0xbd, + 0xb0, 0x8f, 0x51, 0x79, 0x60, 0x0b, 0x4d, 0x7a, 0x4f, 0xf5, 0xfa, 0x48, + 0x0f, 0x6f, 0x62, 0xf1, 0x57, 0xa2, 0xad, 0x3a, 0x74, 0x93, 0xf0, 0x4b, + 0xc1, 0x77, 0xd4, 0xab, 0x27, 0x3d, 0xa8, 0x11, 0x7a, 0xd2, 0xc7, 0x65, + 0x7e, 0xd4, 0xcd, 0x94, 0x12, 0xa2, 0xb9, 0xbb, 0x7c, 0x18, 0xad, 0x99, + 0x6b, 0x7a, 0x30, 0xe6, 0xe7, 0xc3, 0x9b, 0xe1, 0x7d, 0x0a, 0x73, 0xce, + 0xf5, 0x31, 0x4d, 0x90, 0x5b, 0x27, 0xa1, 0x49, 0x29, 0xad, 0xb6, 0x6c, + 0x60, 0x2f, 0x4d, 0xfa, 0x3b, 0x3d, 0x07, 0x11, 0xe9, 0x4e, 0x1a, 0x62, + 0x97, 0x00, 0xbe, 0x7b, 0xee, 0xfd, 0x9b, 0x0b, 0x33, 0x3f, 0xee, 0x22, + 0xbb, 0x12, 0x2a, 0xb5, 0x88, 0xb2, 0xeb, 0xf6, 0xdc, 0x03, 0x0d, 0xa8, + 0x4e, 0xb3, 0xc7, 0xfd, 0xd4, 0x8e, 0x25, 0x0d, 0x4a, 0x92, 0x32, 0x22, + 0x45, 0x79, 0x9b, 0x65, 0x90, 0x9f, 0x5a, 0xfc, 0x2c, 0x83, 0x18, 0x2d, + 0x9f, 0xec, 0xa3, 0xb6, 0xc5, 0x16, 0x5f, 0x0f, 0x45, 0x80, 0xa8, 0x2f, + 0xe2, 0x0b, 0x80, 0x6e, 0xf5, 0xd9, 0xd7, 0xef, 0x4d, 0x7c, 0xce, 0x1e, + 0x58, 0x7a, 0x6b, 0xf1, 0x66, 0xbe, 0xcc, 0xf6, 0xf9, 0x79, 0x64, 0x06, + 0x7d, 0x1e, 0xc4, 0x77, 0xd4, 0xa7, 0x20, 0x43, 0xcb, 0xe3, 0x43, 0x34, + 0x31, 0x1c, 0xe2, 0xb1, 0x77, 0x6c, 0x23, 0x3c, 0xf6, 0xb0, 0xc5, 0x46, + 0x0e, 0x62, 0x3b, 0x47, 0x65, 0xd8, 0x7b, 0x43, 0xde, 0xf2, 0xf8, 0x4e, + 0xf2, 0x1c, 0x6f, 0xf2, 0xfe, 0xc9, 0xeb, 0x95, 0x42, 0x5e, 0xe6, 0x09, + 0xeb, 0x63, 0xef, 0x76, 0xcf, 0xed, 0xfe, 0x2b, 0xf0, 0x1f, 0x99, 0x58, + 0x1e, 0xef, 0xe7, 0xbe, 0x8e, 0xfa, 0x7c, 0xd4, 0xe9, 0x63, 0x96, 0x11, + 0xe1, 0x37, 0x6b, 0x39, 0x9e, 0x33, 0xbf, 0xdb, 0xe0, 0x35, 0xbb, 0x6f, + 0x90, 0xf8, 0x5f, 0x17, 0xcf, 0x60, 0x27, 0x76, 0x01, 0x9e, 0xfb, 0xfb, + 0xd1, 0x4e, 0x41, 0x63, 0x67, 0xdb, 0xa9, 0x70, 0x03, 0x2d, 0x44, 0xfd, + 0x7c, 0x6d, 0x4d, 0xb0, 0xf6, 0x09, 0x5b, 0x56, 0xbb, 0x66, 0x55, 0xe1, + 0x05, 0x49, 0xca, 0x28, 0xc6, 0x09, 0x92, 0xff, 0x61, 0x8f, 0xe9, 0xff, + 0xe6, 0x8a, 0x2f, 0xc5, 0xa3, 0xe1, 0xe1, 0x23, 0xdb, 0xa3, 0xb7, 0xf0, + 0x5c, 0x64, 0xbe, 0xb7, 0x43, 0xb0, 0x30, 0xe8, 0x87, 0x8f, 0x41, 0x0a, + 0x48, 0xf0, 0x4b, 0xf2, 0xf2, 0x7d, 0x63, 0xfe, 0x34, 0xae, 0x8b, 0x51, + 0xe0, 0xb6, 0x18, 0xb1, 0x39, 0xca, 0xe3, 0x83, 0x88, 0x50, 0x90, 0x9f, + 0x65, 0xa5, 0x96, 0xcb, 0x1f, 0x61, 0x9b, 0xdd, 0xfd, 0x09, 0x6c, 0xfb, + 0x84, 0xad, 0x7b, 0x9d, 0x91, 0x8f, 0x58, 0xa7, 0xdf, 0xf4, 0x5f, 0x66, + 0x2b, 0xdd, 0x23, 0xf6, 0xfd, 0x93, 0xf4, 0x19, 0x16, 0x7d, 0xd6, 0xaa, + 0x8e, 0x9d, 0x53, 0x73, 0xa8, 0x3b, 0x11, 0xce, 0xcb, 0xeb, 0xeb, 0x55, + 0xb8, 0x11, 0xa7, 0x60, 0x17, 0x6a, 0xe3, 0x5a, 0xfd, 0x73, 0x7a, 0x36, + 0x88, 0x3d, 0x63, 0x95, 0xa2, 0x11, 0x95, 0xa2, 0x09, 0x23, 0x4b, 0xae, + 0xf3, 0xe0, 0xa9, 0xe9, 0x83, 0x5c, 0xd6, 0x20, 0x76, 0xa4, 0x9b, 0x9c, + 0xfb, 0xd0, 0x00, 0xe7, 0xed, 0xfb, 0x87, 0x26, 0x31, 0x6f, 0x77, 0xad, + 0xbf, 0x73, 0xee, 0x1d, 0x1b, 0x66, 0xe5, 0x11, 0xd4, 0x5f, 0xab, 0x63, + 0xac, 0x67, 0x73, 0x6d, 0xb7, 0x3d, 0xb5, 0x39, 0xba, 0x6b, 0xd7, 0x3f, + 0xe7, 0x3a, 0xa5, 0xd4, 0x7a, 0x3a, 0x7c, 0x13, 0xbf, 0xef, 0x95, 0x85, + 0x27, 0x8a, 0xa0, 0x32, 0xad, 0x5d, 0x1b, 0x99, 0x2c, 0xca, 0xaf, 0x66, + 0xb6, 0x2c, 0x26, 0xec, 0x63, 0x62, 0xfc, 0x2e, 0xd1, 0xee, 0x12, 0xfd, + 0xba, 0xb1, 0x4a, 0x26, 0xef, 0x11, 0xbe, 0x04, 0xc0, 0xf5, 0xf0, 0xdd, + 0x57, 0xb6, 0x6d, 0xcf, 0x92, 0xef, 0x40, 0x51, 0x2f, 0x5a, 0x07, 0x49, + 0x3e, 0x38, 0x46, 0x2d, 0x93, 0x85, 0x8a, 0xb1, 0xac, 0x4d, 0x96, 0x8a, + 0x9a, 0x6e, 0x91, 0x4f, 0x50, 0x69, 0x9a, 0xe4, 0xe9, 0x0c, 0x35, 0x4f, + 0x57, 0x73, 0xda, 0x49, 0x6d, 0x31, 0x5b, 0xd4, 0x2e, 0x90, 0x94, 0x21, + 0x25, 0x03, 0xa1, 0x07, 0x5f, 0x19, 0x92, 0x33, 0xac, 0x89, 0xaf, 0x8e, + 0x8c, 0xaa, 0xe7, 0x2b, 0x46, 0x31, 0x9f, 0xce, 0x19, 0xba, 0x85, 0xce, + 0xe9, 0x49, 0x46, 0x2f, 0x5a, 0x63, 0x2e, 0xd5, 0x52, 0x45, 0x2d, 0x17, + 0x8a, 0x39, 0x33, 0x3d, 0x51, 0xb4, 0x96, 0xd5, 0xf2, 0x18, 0x45, 0x6a, + 0x2a, 0x5d, 0xb3, 0xd2, 0xf3, 0x95, 0xe2, 0x18, 0x75, 0xad, 0x13, 0x15, + 0x2c, 0xab, 0x9c, 0x9e, 0x35, 0x4b, 0x53, 0x95, 0x8a, 0x51, 0x19, 0xa3, + 0xd6, 0x9a, 0xd2, 0x30, 0xd3, 0x33, 0x9a, 0x69, 0xaa, 0x4b, 0xda, 0x18, + 0xf5, 0xd5, 0xa4, 0x17, 0xb4, 0xc5, 0xf3, 0x45, 0x2b, 0x3d, 0xad, 0xae, + 0xa8, 0x66, 0xae, 0x52, 0x2c, 0x5b, 0xc7, 0xe0, 0x43, 0xe5, 0xac, 0x9a, + 0x83, 0x55, 0xb2, 0xde, 0xca, 0x19, 0xf6, 0x28, 0xe4, 0x25, 0x0d, 0xa3, + 0xf7, 0xd4, 0x5b, 0x64, 0xd5, 0x52, 0x55, 0x9b, 0x54, 0x4b, 0xa5, 0x45, + 0x35, 0x77, 0xfe, 0x4e, 0xfa, 0x03, 0xb7, 0xcf, 0x80, 0x98, 0xb9, 0xe3, + 0x3a, 0x46, 0xbd, 0x1b, 0x58, 0x9c, 0xd0, 0x4c, 0xa3, 0x5a, 0xc9, 0x69, + 0x27, 0xb4, 0xcf, 0x57, 0x35, 0xd3, 0xda, 0x60, 0x39, 0xeb, 0x8c, 0xcc, + 0xb2, 0xa1, 0x9b, 0x58, 0x4e, 0xf7, 0x06, 0x56, 0xb3, 0x9a, 0x65, 0x15, + 0xf5, 0x25, 0x73, 0x8c, 0x12, 0x1b, 0x68, 0xd9, 0xf6, 0x6d, 0xb0, 0x08, + 0xa1, 0x71, 0x5c, 0x4c, 0x67, 0x72, 0xc6, 0x72, 0xba, 0xb2, 0x6c, 0x96, + 0xd2, 0xe7, 0xb0, 0xed, 0x69, 0xd7, 0xde, 0x4f, 0x96, 0x54, 0xd3, 0xd4, + 0xcc, 0xbe, 0xf5, 0x6b, 0xda, 0xfe, 0x31, 0x3a, 0x08, 0xd3, 0x81, 0x3b, + 0x9b, 0xba, 0x44, 0x7d, 0x83, 0x63, 0xb4, 0xeb, 0x13, 0xf5, 0x18, 0xa3, + 0xa1, 0x3b, 0xdb, 0xdf, 0xa7, 0x5a, 0xc5, 0x15, 0xcd, 0x95, 0x18, 0x1f, + 0xa3, 0x0f, 0x9a, 0x87, 0xca, 0x65, 0x57, 0x9f, 0xd4, 0x1d, 0xfb, 0x30, + 0x9b, 0xbc, 0x5a, 0x5a, 0x29, 0x9e, 0x4f, 0xab, 0xba, 0x6e, 0x58, 0x98, + 0xd5, 0xd0, 0xd3, 0x53, 0x7a, 0xae, 0x64, 0x98, 0xd8, 0x23, 0x6e, 0xc5, + 0x12, 0xe2, 0xff, 0xd8, 0xcc, 0x68, 0x56, 0xc1, 0xc8, 0xb3, 0x2d, 0xbb, + 0xdd, 0xe8, 0x98, 0xae, 0x6b, 0x15, 0x31, 0xc8, 0x96, 0x0d, 0xf4, 0x33, + 0xda, 0xf2, 0xa2, 0x30, 0x60, 0xbe, 0x6c, 0xda, 0xc0, 0x64, 0xb6, 0xb8, + 0xa4, 0xab, 0x56, 0xb5, 0x82, 0xe5, 0x44, 0x33, 0xe7, 0x70, 0x64, 0xd2, + 0x25, 0x55, 0x5f, 0x4a, 0xdf, 0xbf, 0x78, 0x4e, 0xcb, 0x59, 0xeb, 0x65, + 0xb3, 0x56, 0x05, 0xfe, 0x8c, 0x51, 0xa8, 0x2e, 0x78, 0x24, 0x65, 0x49, + 0xce, 0x4e, 0x93, 0x27, 0x3b, 0x8d, 0x92, 0xa0, 0x64, 0x59, 0x5d, 0xc8, + 0xb2, 0xba, 0xe0, 0x65, 0xdf, 0x28, 0x0f, 0x59, 0xc6, 0xf2, 0x4a, 0xa1, + 0x64, 0x33, 0x4c, 0x99, 0xc9, 0x4c, 0xf3, 0x6f, 0xa6, 0x5b, 0xa0, 0x50, + 0x5d, 0x60, 0x49, 0x5a, 0x20, 0x65, 0x01, 0xa3, 0xc8, 0x0b, 0xe8, 0xb1, + 0xc0, 0x06, 0x5a, 0xc8, 0x2c, 0x80, 0x6f, 0x38, 0x73, 0x66, 0x7a, 0x7e, + 0x72, 0xea, 0xcc, 0x19, 0x6a, 0x56, 0x73, 0x39, 0x1c, 0xfc, 0x23, 0x25, + 0x75, 0xc9, 0xa4, 0x76, 0x35, 0x9f, 0xdf, 0xe0, 0xb0, 0x53, 0x38, 0x5f, + 0x34, 0x51, 0x87, 0x74, 0x2c, 0xc4, 0xf6, 0x98, 0xa2, 0xda, 0x0a, 0x0e, + 0xab, 0x6a, 0x69, 0x6b, 0xe6, 0xd4, 0x22, 0x64, 0x08, 0xc6, 0xb1, 0x3c, + 0xc5, 0xd7, 0x5a, 0x38, 0x65, 0xd5, 0x92, 0x25, 0xea, 0x02, 0xb5, 0x2d, + 0x69, 0xd6, 0x21, 0xfb, 0xcc, 0xcc, 0x9b, 0x5a, 0x65, 0x96, 0x77, 0x36, + 0xa9, 0x19, 0x62, 0xe7, 0xc0, 0x91, 0x0f, 0x8d, 0xf9, 0x4a, 0x89, 0x92, + 0x05, 0xde, 0x69, 0x6d, 0x92, 0xa9, 0xba, 0x41, 0xa9, 0xcd, 0xb6, 0xa8, + 0x3b, 0xf5, 0xe4, 0x29, 0x18, 0xf8, 0x6e, 0x66, 0xdf, 0x87, 0xb5, 0x92, + 0x66, 0x69, 0x79, 0x6a, 0x60, 0x8d, 0x8c, 0x91, 0x3b, 0x4f, 0x1e, 0x5d, + 0x5d, 0xd6, 0x28, 0xa4, 0xd7, 0x05, 0x3f, 0x60, 0xe8, 0x93, 0xc8, 0x14, + 0xed, 0x64, 0x51, 0xcf, 0x1b, 0x17, 0x28, 0x88, 0x66, 0x45, 0xc3, 0x12, + 0xd7, 0xda, 0x0f, 0xa0, 0x3e, 0x1e, 0x41, 0x95, 0x37, 0x0b, 0x18, 0x2f, + 0x60, 0xb7, 0x67, 0x2d, 0xb5, 0xc2, 0x86, 0x0f, 0x32, 0x97, 0x72, 0x1a, + 0x46, 0xe4, 0x65, 0x8c, 0x62, 0xb5, 0x76, 0xfe, 0x28, 0xaa, 0x2e, 0x2f, + 0x8d, 0x14, 0x5d, 0x13, 0x3a, 0xe5, 0x92, 0x42, 0x65, 0x8c, 0x72, 0x68, + 0xd1, 0xa8, 0x5a, 0x73, 0x46, 0xc6, 0x50, 0xf3, 0xd4, 0x5c, 0x86, 0xa3, + 0xa2, 0x1a, 0x53, 0xd4, 0xd5, 0x70, 0x22, 0xd8, 0x6e, 0x6a, 0xd6, 0x44, + 0xb5, 0x58, 0xc2, 0x0e, 0x2d, 0x18, 0xc6, 0x32, 0xbb, 0x32, 0x54, 0x8c, + 0x92, 0xc9, 0xe5, 0x87, 0x8b, 0x66, 0xb9, 0xa4, 0x7e, 0x61, 0x9d, 0xbc, + 0x15, 0x72, 0x16, 0x42, 0x3b, 0xd4, 0x53, 0xba, 0xba, 0x58, 0x82, 0xc3, + 0x1d, 0x90, 0xce, 0x56, 0xcb, 0x65, 0xa3, 0x62, 0xcd, 0x20, 0x92, 0xc5, + 0x72, 0x49, 0xac, 0xd4, 0xa4, 0x28, 0x54, 0x6c, 0x6f, 0x0e, 0x2d, 0xa1, + 0xce, 0xd8, 0xc9, 0xca, 0x65, 0x75, 0x05, 0x98, 0x7a, 0x84, 0xcc, 0xbe, + 0x44, 0x99, 0x87, 0xb5, 0xc5, 0xea, 0xd2, 0x12, 0x8c, 0x9d, 0x29, 0xc2, + 0xb6, 0x7e, 0xad, 0x1e, 0xc2, 0xc5, 0x82, 0x51, 0x2d, 0xe5, 0x79, 0xd0, + 0x73, 0x5a, 0xd9, 0x72, 0xf6, 0x2b, 0x61, 0xcb, 0xef, 0x5f, 0xd1, 0x2a, + 0x95, 0x62, 0x5e, 0xc3, 0xde, 0xb3, 0x50, 0xb0, 0x79, 0x7d, 0x56, 0xa1, + 0x68, 0xf6, 0x0d, 0x50, 0x83, 0x65, 0x08, 0x4f, 0x9a, 0xab, 0xae, 0xb4, + 0xd9, 0x84, 0x84, 0xe8, 0xb3, 0x73, 0xb8, 0x88, 0x39, 0xeb, 0x0f, 0x54, + 0x9c, 0xa9, 0x73, 0xd5, 0x4a, 0x05, 0x93, 0x4f, 0xb9, 0xd3, 0xd3, 0xbb, + 0xc2, 0x77, 0xa9, 0xe3, 0x82, 0xe3, 0x5e, 0x6d, 0xe3, 0x1d, 0x97, 0x3a, + 0x1d, 0x95, 0x2b, 0x09, 0x1c, 0x5d, 0x5c, 0xe8, 0x58, 0x02, 0x30, 0x47, + 0x6b, 0x49, 0x91, 0x10, 0x8a, 0xdb, 0xb7, 0x3e, 0x5e, 0xa7, 0xa9, 0xed, + 0xff, 0x33, 0xd2, 0xa5, 0x4b, 0x87, 0x47, 0x1e, 0x49, 0xb1, 0x8b, 0x9e, + 0xa6, 0xe7, 0x53, 0xfb, 0x53, 0x79, 0xed, 0x62, 0x6a, 0x67, 0x0a, 0x15, + 0xb1, 0x5c, 0x2c, 0x71, 0x7f, 0xfb, 0x97, 0x8d, 0xbc, 0x06, 0x45, 0x05, + 0xa9, 0xac, 0x9a, 0x1a, 0x94, 0x05, 0xd5, 0xec, 0xcf, 0x15, 0xb4, 0xdc, + 0x79, 0xb3, 0xba, 0x6c, 0xa6, 0xf6, 0x9f, 0x55, 0x4b, 0xa6, 0xb6, 0x33, + 0xb5, 0x5c, 0xd4, 0xfb, 0xd5, 0x72, 0x31, 0xb5, 0x7f, 0x68, 0xf7, 0xce, + 0x94, 0x59, 0x50, 0xfb, 0x07, 0xd1, 0x89, 0xc5, 0x21, 0x7f, 0x76, 0xcf, + 0xe0, 0xe2, 0x62, 0x7e, 0x68, 0x64, 0x71, 0x78, 0xef, 0xee, 0xfc, 0xa0, + 0x36, 0xaa, 0x0d, 0x8c, 0xe6, 0x86, 0xcf, 0xee, 0x1b, 0xca, 0xef, 0xcb, + 0x0d, 0xe7, 0xd4, 0xe1, 0xd1, 0x91, 0xfc, 0x30, 0x46, 0x45, 0xf4, 0x4d, + 0x4c, 0x87, 0x4e, 0x23, 0xbb, 0x86, 0x76, 0x0d, 0xf5, 0xe7, 0xb5, 0x95, + 0xd4, 0x97, 0x48, 0x6e, 0x97, 0x0a, 0x89, 0x36, 0x39, 0x21, 0xdf, 0xeb, + 0x69, 0x9c, 0x89, 0x7b, 0x1c, 0xc6, 0x2b, 0x77, 0x4a, 0x85, 0x6e, 0x29, + 0x2e, 0xc9, 0x71, 0xa9, 0xd0, 0xd9, 0xcd, 0xc5, 0x34, 0xd3, 0xc3, 0xa5, + 0x9e, 0xf8, 0xf6, 0x78, 0x7b, 0x7c, 0x47, 0x5c, 0x71, 0x8c, 0x1b, 0x1c, + 0xe6, 0x1e, 0x09, 0xb7, 0xe2, 0x82, 0x1f, 0x91, 0x3b, 0x60, 0xeb, 0x4d, + 0x44, 0x13, 0xad, 0x89, 0x50, 0x22, 0x9c, 0x88, 0x91, 0x84, 0xc7, 0x52, + 0xb9, 0xf9, 0x2b, 0xab, 0x9e, 0xab, 0xa1, 0x16, 0xe9, 0xa7, 0x21, 0x49, + 0x5a, 0x0d, 0x41, 0xe6, 0x97, 0xe4, 0x20, 0x64, 0x6f, 0x84, 0x43, 0xd2, + 0x7b, 0x61, 0xc8, 0x22, 0x92, 0xf4, 0x5d, 0xe0, 0x65, 0xe0, 0x6d, 0xd8, + 0xdc, 0x02, 0x9e, 0x0d, 0x93, 0x22, 0xc9, 0xf2, 0x53, 0x49, 0x89, 0xff, + 0xb7, 0xaf, 0xae, 0x7a, 0x6e, 0x44, 0xe2, 0x3f, 0x4b, 0xde, 0x82, 0xcd, + 0x37, 0xa2, 0x24, 0x4b, 0xb2, 0x17, 0xe8, 0xc0, 0x28, 0x37, 0xa3, 0x5d, + 0xd2, 0x95, 0x56, 0x49, 0x7a, 0x11, 0xd2, 0xa6, 0x66, 0xbf, 0x2c, 0x45, + 0xb7, 0x40, 0xfa, 0x7a, 0xbb, 0xf4, 0x28, 0xa6, 0x06, 0x1a, 0x00, 0xc6, + 0x7b, 0x04, 0x75, 0xa3, 0x57, 0xfa, 0x07, 0x1e, 0x6a, 0xaf, 0xc6, 0x25, + 0xe9, 0x06, 0x70, 0xb9, 0x0d, 0xbe, 0x24, 0x24, 0xe9, 0x79, 0xe0, 0x6d, + 0xe0, 0xf1, 0x0e, 0x45, 0x7a, 0x0f, 0xb2, 0x6b, 0x18, 0xfd, 0x7a, 0x1b, + 0x9b, 0xb3, 0x11, 0x4f, 0xc0, 0x7b, 0x31, 0xfa, 0xad, 0x8e, 0x7d, 0xd2, + 0x6b, 0x1d, 0xb0, 0xea, 0x60, 0x77, 0x9a, 0xa3, 0x90, 0x3c, 0xdd, 0x69, + 0xdf, 0x5b, 0x86, 0xe3, 0x44, 0xed, 0xf1, 0x35, 0x7e, 0x73, 0xdc, 0xbe, + 0x17, 0xdd, 0x16, 0xb7, 0xef, 0x4f, 0x07, 0x40, 0xf7, 0x02, 0x07, 0x5d, + 0x36, 0x19, 0x17, 0x3f, 0x2f, 0xec, 0x1f, 0x72, 0xc9, 0x54, 0x21, 0x3b, + 0x07, 0x5a, 0x48, 0xd0, 0xba, 0x8f, 0x55, 0xd7, 0xbe, 0x22, 0xda, 0xce, + 0x7d, 0x39, 0x7b, 0xc6, 0x5c, 0x85, 0xec, 0xe9, 0x3a, 0xbb, 0x6b, 0x75, + 0xed, 0x97, 0x45, 0xdb, 0x79, 0x9e, 0x60, 0xbf, 0x85, 0x5e, 0x87, 0x6c, + 0x44, 0xd0, 0xd7, 0xeb, 0xec, 0x43, 0xb4, 0x7e, 0x1e, 0x46, 0x9d, 0xf7, + 0x25, 0x6c, 0x0c, 0xe7, 0x9d, 0x09, 0x5b, 0xb3, 0xf3, 0xde, 0x84, 0xdd, + 0x63, 0x3b, 0xef, 0x4e, 0xd8, 0x5d, 0xb6, 0xf3, 0xfe, 0x84, 0x3d, 0x0f, + 0x39, 0xef, 0x50, 0xa4, 0xa4, 0xfd, 0xfc, 0xcd, 0xde, 0xa3, 0x48, 0xe1, + 0xb5, 0xdf, 0xb4, 0xe4, 0xa4, 0x3d, 0x3e, 0x7b, 0xb7, 0xe2, 0x49, 0xda, + 0xcf, 0x89, 0x2c, 0x36, 0xec, 0x87, 0x76, 0xd6, 0x97, 0xfd, 0x56, 0xa6, + 0x84, 0xed, 0x67, 0x08, 0x16, 0x23, 0x9f, 0x90, 0xb3, 0xdf, 0xd1, 0xd8, + 0x43, 0x28, 0xf3, 0x8f, 0xbd, 0xd3, 0xf9, 0x1f, 0x8b, 0x9b, 0x02, 0xf1, + 0x0c, 0x1a, 0x00, 0x00, 0x00, 0x00, +}; #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (constructor, "", "(Landroid/content/Context;)V") \ - METHOD (getSettings, "getSettings", "()Landroid/webkit/WebSettings;") \ + METHOD (constructor, "", "(Landroid/content/Context;JLjava/lang/String;Ljava/lang/String;)V") \ METHOD (canGoBack, "canGoBack", "()Z") \ METHOD (goBack, "goBack", "()V") \ METHOD (goForward, "goForward", "()V") \ @@ -155,23 +312,11 @@ static const uint8 JuceWebView21ByteCode[] = METHOD (loadUrl, "loadUrl", "(Ljava/lang/String;Ljava/util/Map;)V") \ METHOD (postUrl, "postUrl", "(Ljava/lang/String;[B)V") \ METHOD (reload, "reload", "()V") \ - METHOD (setWebChromeClient, "setWebChromeClient", "(Landroid/webkit/WebChromeClient;)V") \ - METHOD (setWebViewClient, "setWebViewClient", "(Landroid/webkit/WebViewClient;)V") \ - METHOD (stopLoading, "stopLoading", "()V") + METHOD (evaluateJavascript, "evaluateJavascript", "(Ljava/lang/String;)J") \ + METHOD (stopLoading, "stopLoading", "()V") \ + METHOD (disconnectNative, "disconnectNative", "()V") -DECLARE_JNI_CLASS (AndroidWebView, "android/webkit/WebView") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (constructor, "", "()V") - -DECLARE_JNI_CLASS (AndroidWebChromeClient, "android/webkit/WebChromeClient") -#undef JNI_CLASS_MEMBERS - -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (constructor, "", "()V") - -DECLARE_JNI_CLASS (AndroidWebViewClient, "android/webkit/WebViewClient") +DECLARE_JNI_CLASS_WITH_BYTECODE (JuceWebView, "com/rmsl/juce/JuceWebViewClasses$JuceWebView", 16, juceWebViewClasses) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ @@ -180,16 +325,6 @@ DECLARE_JNI_CLASS (AndroidWebViewClient, "android/webkit/WebViewClient") DECLARE_JNI_CLASS (AndroidCookieManager, "android/webkit/CookieManager") #undef JNI_CLASS_MEMBERS -#define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (setBuiltInZoomControls, "setBuiltInZoomControls", "(Z)V") \ - METHOD (setDisplayZoomControls, "setDisplayZoomControls", "(Z)V") \ - METHOD (setJavaScriptEnabled, "setJavaScriptEnabled", "(Z)V") \ - METHOD (setSupportMultipleWindows, "setSupportMultipleWindows", "(Z)V") \ - METHOD (setUserAgentString, "setUserAgentString", "(Ljava/lang/String;)V") - -DECLARE_JNI_CLASS (WebSettings, "android/webkit/WebSettings") -#undef JNI_CLASS_MEMBERS - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (toString, "toString", "()Ljava/lang/String;") @@ -202,54 +337,117 @@ DECLARE_JNI_CLASS (SslError, "android/net/http/SslError") DECLARE_JNI_CLASS (URLEncoder, "java/net/URLEncoder") #undef JNI_CLASS_MEMBERS -//============================================================================== -class WebBrowserComponent::Pimpl : public AndroidViewComponent, - public AsyncUpdater +class MessageThreadExecutor : private AsyncUpdater { public: - Pimpl (WebBrowserComponent& o, const String& userAgent) + MessageThreadExecutor() = default; + + ~MessageThreadExecutor() override + { + cancelPendingUpdate(); + } + + void execute (std::function fn) + { + const std::lock_guard lock { functionsMutex }; + functions.emplace_back (std::move (fn)); + triggerAsyncUpdate(); + } + +private: + void handleAsyncUpdate() override + { + const auto popFront = [&]() -> std::function + { + const std::lock_guard lock { functionsMutex }; + + if (functions.empty()) + return nullptr; + + const ScopeGuard scope { [&] { functions.pop_front(); } }; + + return functions.front(); + }; + + for (auto fn = popFront(); fn != nullptr; fn = popFront()) + fn(); + } + + std::deque> functions; + std::mutex functionsMutex; + + JUCE_DECLARE_NON_COPYABLE (MessageThreadExecutor) +}; + +//============================================================================== +struct WebBrowserComponent::Impl::Platform : public AndroidViewComponent, + public PlatformInterface, + public AsyncUpdater +{ +public: + Platform (WebBrowserComponent& o, + const WebBrowserComponent::Options& browserOptions, + const StringArray& userScripts) : owner (o) { auto* env = getEnv(); - setView (env->NewObject (AndroidWebView, AndroidWebView.constructor, getMainActivity().get())); + setView (env->NewObject (JuceWebView, + JuceWebView.constructor, + getMainActivity().get(), + reinterpret_cast (this), + javaString (browserOptions.getUserAgent()).get(), + javaString (userScripts.joinIntoString ("\n")).get())); - auto settings = LocalRef (env->CallObjectMethod ((jobject) getView(), AndroidWebView.getSettings)); - env->CallVoidMethod (settings, WebSettings.setJavaScriptEnabled, true); - env->CallVoidMethod (settings, WebSettings.setBuiltInZoomControls, true); - env->CallVoidMethod (settings, WebSettings.setDisplayZoomControls, false); - env->CallVoidMethod (settings, WebSettings.setSupportMultipleWindows, true); - - if (userAgent.isNotEmpty()) - env->CallVoidMethod (settings, WebSettings.setUserAgentString, javaString (userAgent).get()); - - juceWebChromeClient = GlobalRef (LocalRef (env->NewObject (JuceWebChromeClient, JuceWebChromeClient.constructor, - reinterpret_cast (this)))); - env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebChromeClient, juceWebChromeClient.get()); - - auto sdkVersion = getAndroidSDKVersion(); - - if (sdkVersion >= 21) - juceWebViewClient = GlobalRef (LocalRef (env->NewObject (JuceWebViewClient21, JuceWebViewClient21.constructor, - reinterpret_cast (this)))); - else - juceWebViewClient = GlobalRef (LocalRef (env->NewObject (JuceWebViewClient16, JuceWebViewClient16.constructor, - reinterpret_cast (this)))); - - env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebViewClient, juceWebViewClient.get()); + owner.addAndMakeVisible (this); } - ~Pimpl() + void checkWindowAssociation() override + { + if (owner.isShowing()) + { + if (owner.blankPageShown) + owner.goBack(); + } + else + { + if (owner.unloadPageWhenHidden && ! owner.blankPageShown) + { + // when the component becomes invisible, some stuff like flash + // carries on playing audio, so we need to force it onto a blank + // page to avoid this, (and send it back when it's made visible again). + + owner.blankPageShown = true; + goToURL ("about:blank", nullptr, nullptr); + } + } + } + + void setWebViewSize (int width, int height) override + { + setSize (width, height); + } + + void evaluateJavascript (const String& script, WebBrowserComponent::EvaluationCallback callback) override + { + JUCE_ASSERT_MESSAGE_THREAD + + auto* env = getEnv(); + auto view = (jobject) getView(); + + const auto resultId = (int64) env->CallLongMethod (view, + JuceWebView.evaluateJavascript, + javaString (script).get()); + + if (callback != nullptr) + evaluationCallbacks[resultId] = std::move (callback); + } + + ~Platform() override { auto* env = getEnv(); - env->CallVoidMethod ((jobject) getView(), AndroidWebView.stopLoading); - - auto defaultChromeClient = LocalRef (env->NewObject (AndroidWebChromeClient, AndroidWebChromeClient.constructor)); - auto defaultViewClient = LocalRef (env->NewObject (AndroidWebViewClient, AndroidWebViewClient .constructor)); - - env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebChromeClient, defaultChromeClient.get()); - env->CallVoidMethod ((jobject) getView(), AndroidWebView.setWebViewClient, defaultViewClient .get()); + env->CallVoidMethod ((jobject) getView(), JuceWebView.stopLoading); masterReference.clear(); @@ -258,19 +456,20 @@ public: // the lock we need when calling hostDeleted. responseReadyEvent.signal(); - env->CallVoidMethod (juceWebViewClient, getAndroidSDKVersion() >= 21 ? JuceWebViewClient21.hostDeleted - : JuceWebViewClient16.hostDeleted); + resourceRequester.cancelAllResourceRequests(); + + env->CallVoidMethod ((jobject) getView(), JuceWebView.disconnectNative); } void goToURL (const String& url, const StringArray* headers, - const MemoryBlock* postData) + const MemoryBlock* postData) override { auto* env = getEnv(); if (headers == nullptr && postData == nullptr) { - env->CallVoidMethod ((jobject) getView(), AndroidWebView.loadUrl, javaString (url).get(), 0); + env->CallVoidMethod ((jobject) getView(), JuceWebView.loadUrl, javaString (url).get(), 0); } else if (headers != nullptr && postData == nullptr) { @@ -288,7 +487,7 @@ public: javaString (value).get()); } - env->CallVoidMethod ((jobject) getView(), AndroidWebView.loadUrl, + env->CallVoidMethod ((jobject) getView(), JuceWebView.loadUrl, javaString (url).get(), headersMap.get()); } else if (headers == nullptr && postData != nullptr) @@ -300,7 +499,7 @@ public: auto bytes = LocalRef ((jbyteArray) env->CallObjectMethod (encodingString, JavaString.getBytes)); - env->CallVoidMethod ((jobject) getView(), AndroidWebView.postUrl, + env->CallVoidMethod ((jobject) getView(), JuceWebView.postUrl, javaString (url).get(), bytes.get()); } else if (headers != nullptr && postData != nullptr) @@ -313,41 +512,41 @@ public: } } - void stop() + void stop() override { connectionThread = nullptr; - getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.stopLoading); + getEnv()->CallVoidMethod ((jobject) getView(), JuceWebView.stopLoading); } - void goBack() + void goBack() override { connectionThread = nullptr; auto* env = getEnv(); auto view = (jobject) getView(); - if (env->CallBooleanMethod (view, AndroidWebView.canGoBack)) - env->CallVoidMethod (view, AndroidWebView.goBack); + if (env->CallBooleanMethod (view, JuceWebView.canGoBack)) + env->CallVoidMethod (view, JuceWebView.goBack); else owner.reloadLastURL(); } - void goForward() + void goForward() override { connectionThread = nullptr; - getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.goForward); + getEnv()->CallVoidMethod ((jobject) getView(), JuceWebView.goForward); } - void refresh() + void refresh() override { connectionThread = nullptr; - getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.reload); + getEnv()->CallVoidMethod ((jobject) getView(), JuceWebView.reload); } - void handleAsyncUpdate() + void handleAsyncUpdate() override { jassert (connectionThread != nullptr); @@ -363,7 +562,7 @@ public: auto mimeType = javaString ("text/html"); auto encoding = javaString ("utf-8"); - getEnv()->CallVoidMethod ((jobject) getView(), AndroidWebView.loadDataWithBaseURL, + getEnv()->CallVoidMethod ((jobject) getView(), JuceWebView.loadDataWithBaseURL, url.get(), data.get(), mimeType.get(), encoding.get(), 0); } @@ -378,7 +577,7 @@ public: if (MessageManager::getInstance()->isThisTheMessageThread()) return owner.pageAboutToLoad (url); - WeakReference weakRef (this); + WeakReference weakRef (this); if (weakRef == nullptr) return false; @@ -416,10 +615,10 @@ private: String data; }; - ConnectionThread (Pimpl& ownerToUse, + ConnectionThread (Platform& ownerToUse, URL& url, const StringArray& headers) - : Thread ("WebBrowserComponent::Pimpl::ConnectionThread"), + : Thread ("WebBrowserComponent::Platform::ConnectionThread"), owner (ownerToUse), webInputStream (new WebInputStream (url, true)) { @@ -479,70 +678,220 @@ private: result.data = ostream.toUTF8(); } - Pimpl& owner; + Platform& owner; std::unique_ptr webInputStream; Result result; }; + class MessageThreadResourceRequester : private AsyncUpdater + { + public: + explicit MessageThreadResourceRequester (Platform& ownerIn) + : owner (ownerIn) + {} + + ~MessageThreadResourceRequester() override + { + cancelPendingUpdate(); + } + + // The Platform destructor will block for as long as a Java thread waits for a Resource, so this + // must be called from ~Platform(). + void cancelAllResourceRequests() + { + std::lock_guard lock { queueMutex }; + + while (! queue.empty()) + { + auto* r = queue.front(); + queue.pop_front(); + r->event.signal(); + } + } + + std::optional handleResourceRequestOnMessageThread (StringRef uri) + { + if (MessageManager::getInstance()->isThisTheMessageThread()) + return owner.owner.impl->handleResourceRequest (uri); + + WaitableResource resource { uri }; + + { + std::lock_guard lock { queueMutex }; + queue.push_back (&resource); + } + + triggerAsyncUpdate(); + resource.event.wait (-1); + + return resource.resource; + } + + private: + struct WaitableResource + { + explicit WaitableResource (StringRef uriIn) : uri (uriIn) {} + + String uri; + WaitableEvent event; + std::optional resource; + }; + + void handleAsyncUpdate() override + { + const auto popFront = [&]() -> WaitableResource* + { + const std::lock_guard lock { queueMutex }; + + if (queue.empty()) + return nullptr; + + const ScopeGuard scope { [&] { queue.pop_front(); } }; + + return queue.front(); + }; + + for (auto* r = popFront(); r != nullptr; r = popFront()) + { + r->resource = (owner.owner.impl->handleResourceRequest (r->uri)); + r->event.signal(); + } + } + + Platform& owner; + std::deque queue; + std::mutex queueMutex; + }; + //============================================================================== #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ METHOD (constructor, "", "(J)V") \ METHOD (hostDeleted, "hostDeleted", "()V") \ - CALLBACK (generatedCallback<&Pimpl::webViewReceivedHttpError>, "webViewReceivedHttpError", "(JLandroid/webkit/WebView;Landroid/webkit/WebResourceRequest;Landroid/webkit/WebResourceResponse;)V") \ - CALLBACK (generatedCallback<&Pimpl::webViewPageLoadStarted>, "webViewPageLoadStarted", "(JLandroid/webkit/WebView;Ljava/lang/String;)Z") \ - CALLBACK (generatedCallback<&Pimpl::webViewPageLoadFinished>, "webViewPageLoadFinished", "(JLandroid/webkit/WebView;Ljava/lang/String;)V") \ - CALLBACK (generatedCallback<&Pimpl::webViewReceivedSslError>, "webViewReceivedSslError", "(JLandroid/webkit/WebView;Landroid/webkit/SslErrorHandler;Landroid/net/http/SslError;)V") \ + CALLBACK (generatedCallback<&Platform::webViewReceivedHttpError>, "webViewReceivedHttpError", "(JLandroid/webkit/WebView;Landroid/webkit/WebResourceRequest;Landroid/webkit/WebResourceResponse;)V") \ + CALLBACK (generatedCallback<&Platform::handleResourceRequest>, "handleResourceRequest", "(JLandroid/webkit/WebView;Ljava/lang/String;)Landroid/webkit/WebResourceResponse;") \ + CALLBACK (generatedCallback<&Platform::webViewPageLoadFinished>, "webViewPageLoadFinished", "(JLandroid/webkit/WebView;Ljava/lang/String;)V") \ + CALLBACK (generatedCallback<&Platform::webViewReceivedSslError>, "webViewReceivedSslError", "(JLandroid/webkit/WebView;Landroid/webkit/SslErrorHandler;Landroid/net/http/SslError;)V") \ + CALLBACK (generatedCallback<&Platform::webViewCloseWindowRequest>, "webViewCloseWindowRequest", "(JLandroid/webkit/WebView;)V") \ + CALLBACK (generatedCallback<&Platform::webViewCreateWindowRequest>, "webViewCreateWindowRequest", "(JLandroid/webkit/WebView;)V") \ + CALLBACK (generatedCallback<&Platform::evaluationResultHandler>, "evaluationResultHandler", "(JJLjava/lang/String;)V") \ + CALLBACK (generatedCallback<&Platform::postMessageHandler>, "postMessage", "(JLjava/lang/String;)Ljava/lang/String;") \ + CALLBACK (generatedCallback<&Platform::pageAboutToLoad>, "pageAboutToLoad", "(JLjava/lang/String;)Z") - DECLARE_JNI_CLASS_WITH_BYTECODE (JuceWebViewClient21, "com/rmsl/juce/JuceWebView21$Client", 21, JuceWebView21ByteCode) + DECLARE_JNI_CLASS_WITH_BYTECODE (NativeInterface, "com/rmsl/juce/JuceWebViewClasses$NativeInterface", 16, juceWebViewClasses) #undef JNI_CLASS_MEMBERS #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (constructor, "", "(J)V") \ - METHOD (hostDeleted, "hostDeleted", "()V") \ - CALLBACK (generatedCallback<&Pimpl::webViewPageLoadStarted>, "webViewPageLoadStarted", "(JLandroid/webkit/WebView;Ljava/lang/String;)Z") \ - CALLBACK (generatedCallback<&Pimpl::webViewPageLoadFinished>, "webViewPageLoadFinished", "(JLandroid/webkit/WebView;Ljava/lang/String;)V") \ - CALLBACK (generatedCallback<&Pimpl::webViewReceivedSslError>, "webViewReceivedSslError", "(JLandroid/webkit/WebView;Landroid/webkit/SslErrorHandler;Landroid/net/http/SslError;)V") \ + METHOD (create, "", "(Ljava/lang/String;Ljava/lang/String;Ljava/io/InputStream;)V") \ + METHOD (setResponseHeaders, "setResponseHeaders", "(Ljava/util/Map;)V") \ - DECLARE_JNI_CLASS_WITH_BYTECODE (JuceWebViewClient16, "com/rmsl/juce/JuceWebView$Client", 16, JuceWebView16ByteCode) + DECLARE_JNI_CLASS (WebResourceResponse, "android/webkit/WebResourceResponse") #undef JNI_CLASS_MEMBERS - static jboolean webViewPageLoadStarted (JNIEnv*, Pimpl& t, jobject /*webView*/, jstring url) + #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ + METHOD (create, "", "([B)V") \ + + DECLARE_JNI_CLASS (ByteArrayInputStream, "java/io/ByteArrayInputStream") + #undef JNI_CLASS_MEMBERS + + static jobject handleResourceRequest (JNIEnv* env, Platform& t, jobject /*webView*/, jstring url) { - return t.handlePageAboutToLoad (juceString (url)); + const auto uriString = juceString (url); + + if (const auto resourceRequestUri = uriString.fromFirstOccurrenceOf ("https://juce.backend", false, false); + resourceRequestUri.isNotEmpty()) + { + if (auto response = t.resourceRequester.handleResourceRequestOnMessageThread (resourceRequestUri)) + { + jbyteArray byteArray = env->NewByteArray ((jsize) response->data.size()); + env->SetByteArrayRegion (byteArray, 0, (jsize) response->data.size(), (jbyte*) response->data.data()); + LocalRef byteArrayInputStream { env->NewObject (ByteArrayInputStream, ByteArrayInputStream.create, byteArray) }; + auto resource = env->NewObject (WebResourceResponse, + WebResourceResponse.create, + javaString (response->mimeType).get(), + response->mimeType.contains ("text") ? javaString ("utf-8").get() : nullptr, + byteArrayInputStream.get()); + + if (const auto allowedOrigin = t.owner.impl->options.getAllowedOrigin()) + { + LocalRef headersMap { env->NewObject (JavaHashMap, + JavaHashMap.constructorWithCapacity, + 1) }; + + env->CallObjectMethod (headersMap, JavaMap.put, + javaString ("Access-Control-Allow-Origin").get(), + javaString (*allowedOrigin).get()); + + env->CallVoidMethod (resource, + WebResourceResponse.setResponseHeaders, + headersMap.get()); + } + + return resource; + } + } + + return nullptr; } - static void webViewPageLoadFinished (JNIEnv*, Pimpl& t, jobject /*webView*/, jstring url) + static void webViewPageLoadFinished (JNIEnv*, Platform& t, jobject /*webView*/, jstring url) { t.owner.pageFinishedLoading (juceString (url)); } - static void webViewReceivedSslError (JNIEnv* env, Pimpl& t, jobject /*webView*/, jobject /*sslErrorHandler*/, jobject sslError) + static void webViewReceivedSslError (JNIEnv* env, Platform& t, jobject /*webView*/, jobject /*sslErrorHandler*/, jobject sslError) { const auto errorString = LocalRef ((jstring) env->CallObjectMethod (sslError, SslError.toString)); t.owner.pageLoadHadNetworkError (juceString (errorString)); } + static void evaluationResultHandler (JNIEnv*, Platform& t, jlong evalId, jstring result) + { + auto it = t.evaluationCallbacks.find ((int64) evalId); + + if (it == t.evaluationCallbacks.end()) + return; + + (it->second) (EvaluationResult { JSON::fromString (juceString (result)) }); + + t.evaluationCallbacks.erase (it); + } + + static jobject postMessageHandler (JNIEnv*, Platform& t, jstring message) + { + t.messageThreadExecutor.execute ([&t, msg = juceString (message)] + { + var obj; + + if (! JSON::parse (msg, obj).wasOk()) + { + jassertfalse; + return; + } + + t.owner.impl->handleNativeEvent (obj); + }); + + return nullptr; + } + + static bool pageAboutToLoad (JNIEnv*, Platform& t, jstring url) + { + return t.handlePageAboutToLoad (juceString (url)); + } + //============================================================================== - #define JNI_CLASS_MEMBERS(METHOD, STATICMETHOD, FIELD, STATICFIELD, CALLBACK) \ - METHOD (constructor, "", "(J)V") \ - CALLBACK (generatedCallback<&Pimpl::webViewCloseWindowRequest>, "webViewCloseWindowRequest", "(JLandroid/webkit/WebView;)V") \ - CALLBACK (generatedCallback<&Pimpl::webViewCreateWindowRequest>, "webViewCreateWindowRequest", "(JLandroid/webkit/WebView;)V") \ - - DECLARE_JNI_CLASS (JuceWebChromeClient, "com/rmsl/juce/JuceWebView$ChromeClient") - #undef JNI_CLASS_MEMBERS - - static void webViewCloseWindowRequest (JNIEnv*, Pimpl& t, jobject) + static void webViewCloseWindowRequest (JNIEnv*, Platform& t, jobject) { t.owner.windowCloseRequest(); } - static void webViewCreateWindowRequest (JNIEnv*, Pimpl& t, jobject) + static void webViewCreateWindowRequest (JNIEnv*, Platform& t, jobject) { t.owner.newWindowAttemptingToLoad ({}); } //============================================================================== - static void webViewReceivedHttpError (JNIEnv* env, Pimpl& t, jobject /*webView*/, jobject /*request*/, jobject errorResponse) + static void webViewReceivedHttpError (JNIEnv* env, Platform& t, jobject /*webView*/, jobject /*request*/, jobject errorResponse) { LocalRef responseClass (env->FindClass ("android/webkit/WebResourceResponse")); @@ -565,130 +914,26 @@ private: } //============================================================================== - GlobalRef juceWebChromeClient; - GlobalRef juceWebViewClient; std::unique_ptr connectionThread; WaitableEvent responseReadyEvent; + std::unordered_map evaluationCallbacks; + MessageThreadExecutor messageThreadExecutor; + MessageThreadResourceRequester resourceRequester { *this }; - WeakReference::Master masterReference; - friend class WeakReference; + WeakReference::Master masterReference; + friend class WeakReference; }; //============================================================================== -WebBrowserComponent::WebBrowserComponent (const Options& options) - : blankPageShown (false), - unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden()) +auto WebBrowserComponent::Impl::createAndInitPlatformDependentPart (WebBrowserComponent::Impl& impl, + const WebBrowserComponent::Options& options, + const StringArray& userScripts) + -> std::unique_ptr { - setOpaque (true); - - browser.reset (new Pimpl (*this, options.getUserAgent())); - addAndMakeVisible (browser.get()); -} - -WebBrowserComponent::~WebBrowserComponent() = default; - -//============================================================================== -void WebBrowserComponent::goToURL (const String& url, - const StringArray* headers, - const MemoryBlock* postData) -{ - lastURL = url; - - if (headers != nullptr) - lastHeaders = *headers; - else - lastHeaders.clear(); - - if (postData != nullptr) - lastPostData = *postData; - else - lastPostData.reset(); - - blankPageShown = false; - - browser->goToURL (url, headers, postData); -} - -void WebBrowserComponent::stop() -{ - browser->stop(); -} - -void WebBrowserComponent::goBack() -{ - browser->goBack(); - - lastURL.clear(); - blankPageShown = false; -} - -void WebBrowserComponent::goForward() -{ - lastURL.clear(); - - browser->goForward(); -} - -void WebBrowserComponent::refresh() -{ - browser->refresh(); + return std::make_unique (impl.owner, options, userScripts); } //============================================================================== -void WebBrowserComponent::paint (Graphics& g) -{ - g.fillAll (Colours::white); -} - -void WebBrowserComponent::checkWindowAssociation() -{ - if (isShowing()) - { - if (blankPageShown) - goBack(); - } - else - { - if (unloadPageWhenHidden && ! blankPageShown) - { - // when the component becomes invisible, some stuff like flash - // carries on playing audio, so we need to force it onto a blank - // page to avoid this, (and send it back when it's made visible again). - - blankPageShown = true; - browser->goToURL ("about:blank", nullptr, nullptr); - } - } -} - -void WebBrowserComponent::reloadLastURL() -{ - if (lastURL.isNotEmpty()) - { - goToURL (lastURL, &lastHeaders, lastPostData.isEmpty() ? nullptr : &lastPostData); - lastURL.clear(); - } -} - -void WebBrowserComponent::parentHierarchyChanged() -{ - checkWindowAssociation(); -} - -void WebBrowserComponent::resized() -{ - browser->setSize (getWidth(), getHeight()); -} - -void WebBrowserComponent::visibilityChanged() -{ - checkWindowAssociation(); -} - -void WebBrowserComponent::focusGainedWithDirection (FocusChangeType, FocusChangeDirection) -{ -} - void WebBrowserComponent::clearCookies() { auto* env = getEnv(); diff --git a/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp b/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp index 22e2301ca9..ac25557343 100644 --- a/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp +++ b/modules/juce_gui_extra/native/juce_WebBrowserComponent_linux.cpp @@ -91,6 +91,93 @@ public: JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_get_uri, juce_webkit_web_view_get_uri, (WebKitWebView*), const gchar*) + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_run_javascript, juce_webkit_web_view_run_javascript, + (WebKitWebView*, const gchar*, GCancellable*, GAsyncReadyCallback, gpointer), void) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_javascript_result_unref, juce_webkit_javascript_result_unref, + (WebKitJavascriptResult*), void) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_run_javascript_finish, juce_webkit_web_view_run_javascript_finish, + (WebKitWebView*, GAsyncResult*, GError**), WebKitJavascriptResult*) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_javascript_result_get_js_value, juce_webkit_javascript_result_get_js_value, + (WebKitJavascriptResult*), JSCValue*) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (jsc_value_to_string, juce_jsc_value_to_string, + (JSCValue*), char*) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_get_user_content_manager, + juce_webkit_web_view_get_user_content_manager, + (WebKitWebView*), WebKitUserContentManager*) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_set_javascript_can_access_clipboard, + juce_webkit_settings_set_javascript_can_access_clipboard, + (WebKitSettings*, gboolean), void) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_set_enable_write_console_messages_to_stdout, + juce_webkit_settings_set_enable_write_console_messages_to_stdout, + (WebKitSettings*, gboolean), void) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_settings_set_enable_developer_extras, + juce_webkit_settings_set_enable_developer_extras, + (WebKitSettings*, gboolean), void) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_user_content_manager_register_script_message_handler, + juce_webkit_user_content_manager_register_script_message_handler, + (WebKitUserContentManager*, const gchar*), void) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_user_script_new, + juce_webkit_user_script_new, + (const gchar*, + WebKitUserContentInjectedFrames, + WebKitUserScriptInjectionTime, + const gchar* const*, + const gchar* const*), + WebKitUserScript*) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_user_content_manager_add_script, + juce_webkit_user_content_manager_add_script, + (WebKitUserContentManager*, WebKitUserScript*), void) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_context_register_uri_scheme, + juce_webkit_web_context_register_uri_scheme, + (WebKitWebContext*, + const gchar*, + WebKitURISchemeRequestCallback, + gpointer, + GDestroyNotify), + void) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_web_view_get_context, + juce_webkit_web_view_get_context, + (WebKitWebView*), + WebKitWebContext*) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_uri_scheme_request_get_path, + juce_webkit_uri_scheme_request_get_path, + (WebKitURISchemeRequest*), + const gchar*) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_uri_scheme_response_new, + juce_webkit_uri_scheme_response_new, + (GInputStream*, gint64), + WebKitURISchemeResponse*) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_uri_scheme_response_set_http_headers, + juce_webkit_uri_scheme_response_set_http_headers, + (WebKitURISchemeResponse*, SoupMessageHeaders*), + void) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_uri_scheme_response_set_status, + juce_webkit_uri_scheme_response_set_status, + (WebKitURISchemeResponse*, guint, const gchar*), + void) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (webkit_uri_scheme_request_finish_with_response, + juce_webkit_uri_scheme_request_finish_with_response, + (WebKitURISchemeRequest*, WebKitURISchemeResponse*), + void) + //============================================================================== JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gtk_init, juce_gtk_init, (int*, char***), void) @@ -125,6 +212,21 @@ public: JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_object_unref, juce_g_object_unref, (gpointer), void) + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_memory_input_stream_new, juce_g_memory_input_stream_new, + (), GInputStream*) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_memory_input_stream_new_from_bytes, juce_g_memory_input_stream_new_from_bytes, + (GBytes*), GInputStream*) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_bytes_new, juce_g_bytes_new, + (gconstpointer, gsize), GBytes*) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_bytes_unref, juce_g_bytes_unref, + (GBytes*), void) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_error_free, juce_g_error_free, + (GError*), void) + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_signal_connect_data, juce_g_signal_connect_data, (gpointer, const gchar*, GCallback, gpointer, GClosureNotify, GConnectFlags), gulong) @@ -132,6 +234,21 @@ public: JUCE_GENERATE_FUNCTION_WITH_DEFAULT (gdk_set_allowed_backends, juce_gdk_set_allowed_backends, (const char*), void) + //============================================================================== + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (jsc_value_to_json, juce_jsc_value_to_json, + (JSCValue*, guint), char*) + + //============================================================================== + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (soup_message_headers_new, juce_soup_message_headers_new, + (SoupMessageHeadersType), SoupMessageHeaders*) + + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (soup_message_headers_append, juce_soup_message_headers_append, + (SoupMessageHeaders*, const char*, const char*), void) + + //============================================================================== + JUCE_GENERATE_FUNCTION_WITH_DEFAULT (g_free, juce_g_free, + (gpointer), void) + //============================================================================== JUCE_DECLARE_SINGLETON_SINGLETHREADED_MINIMAL (WebKitSymbols) @@ -178,45 +295,96 @@ private: bool loadWebkitSymbols() { return loadSymbols (webkitLib, - makeSymbolBinding (juce_webkit_settings_new, "webkit_settings_new"), - makeSymbolBinding (juce_webkit_settings_set_hardware_acceleration_policy, "webkit_settings_set_hardware_acceleration_policy"), - makeSymbolBinding (juce_webkit_settings_set_user_agent, "webkit_settings_set_user_agent"), - makeSymbolBinding (juce_webkit_web_view_new_with_settings, "webkit_web_view_new_with_settings"), - makeSymbolBinding (juce_webkit_policy_decision_use, "webkit_policy_decision_use"), - makeSymbolBinding (juce_webkit_policy_decision_ignore, "webkit_policy_decision_ignore"), - makeSymbolBinding (juce_webkit_web_view_go_back, "webkit_web_view_go_back"), - makeSymbolBinding (juce_webkit_web_view_go_forward, "webkit_web_view_go_forward"), - makeSymbolBinding (juce_webkit_web_view_reload, "webkit_web_view_reload"), - makeSymbolBinding (juce_webkit_web_view_stop_loading, "webkit_web_view_stop_loading"), - makeSymbolBinding (juce_webkit_uri_request_get_uri, "webkit_uri_request_get_uri"), - makeSymbolBinding (juce_webkit_web_view_load_uri, "webkit_web_view_load_uri"), - makeSymbolBinding (juce_webkit_navigation_action_get_request, "webkit_navigation_action_get_request"), - makeSymbolBinding (juce_webkit_navigation_policy_decision_get_frame_name, "webkit_navigation_policy_decision_get_frame_name"), - makeSymbolBinding (juce_webkit_navigation_policy_decision_get_navigation_action, "webkit_navigation_policy_decision_get_navigation_action"), - makeSymbolBinding (juce_webkit_web_view_get_uri, "webkit_web_view_get_uri")); + makeSymbolBinding (juce_webkit_settings_new, "webkit_settings_new"), + makeSymbolBinding (juce_webkit_settings_set_hardware_acceleration_policy, "webkit_settings_set_hardware_acceleration_policy"), + makeSymbolBinding (juce_webkit_settings_set_user_agent, "webkit_settings_set_user_agent"), + makeSymbolBinding (juce_webkit_web_view_new_with_settings, "webkit_web_view_new_with_settings"), + makeSymbolBinding (juce_webkit_policy_decision_use, "webkit_policy_decision_use"), + makeSymbolBinding (juce_webkit_policy_decision_ignore, "webkit_policy_decision_ignore"), + makeSymbolBinding (juce_webkit_web_view_go_back, "webkit_web_view_go_back"), + makeSymbolBinding (juce_webkit_web_view_go_forward, "webkit_web_view_go_forward"), + makeSymbolBinding (juce_webkit_web_view_reload, "webkit_web_view_reload"), + makeSymbolBinding (juce_webkit_web_view_stop_loading, "webkit_web_view_stop_loading"), + makeSymbolBinding (juce_webkit_uri_request_get_uri, "webkit_uri_request_get_uri"), + makeSymbolBinding (juce_webkit_web_view_load_uri, "webkit_web_view_load_uri"), + makeSymbolBinding (juce_webkit_navigation_action_get_request, "webkit_navigation_action_get_request"), + makeSymbolBinding (juce_webkit_navigation_policy_decision_get_frame_name, "webkit_navigation_policy_decision_get_frame_name"), + makeSymbolBinding (juce_webkit_navigation_policy_decision_get_navigation_action, "webkit_navigation_policy_decision_get_navigation_action"), + makeSymbolBinding (juce_webkit_web_view_get_uri, "webkit_web_view_get_uri"), + makeSymbolBinding (juce_webkit_web_view_run_javascript, "webkit_web_view_run_javascript"), + makeSymbolBinding (juce_webkit_javascript_result_unref, "webkit_javascript_result_unref"), + makeSymbolBinding (juce_webkit_web_view_get_user_content_manager, "webkit_web_view_get_user_content_manager"), + makeSymbolBinding (juce_webkit_settings_set_javascript_can_access_clipboard, "webkit_settings_set_javascript_can_access_clipboard"), + makeSymbolBinding (juce_webkit_settings_set_enable_write_console_messages_to_stdout, "webkit_settings_set_enable_write_console_messages_to_stdout"), + makeSymbolBinding (juce_webkit_settings_set_enable_developer_extras, "webkit_settings_set_enable_developer_extras"), + makeSymbolBinding (juce_webkit_user_content_manager_register_script_message_handler, "webkit_user_content_manager_register_script_message_handler"), + makeSymbolBinding (juce_webkit_user_script_new, "webkit_user_script_new"), + makeSymbolBinding (juce_webkit_user_content_manager_add_script, "webkit_user_content_manager_add_script"), + makeSymbolBinding (juce_webkit_javascript_result_get_js_value, "webkit_javascript_result_get_js_value"), + makeSymbolBinding (juce_jsc_value_to_string, "jsc_value_to_string"), + makeSymbolBinding (juce_webkit_web_view_run_javascript_finish, "webkit_web_view_run_javascript_finish"), + makeSymbolBinding (juce_webkit_web_context_register_uri_scheme, "webkit_web_context_register_uri_scheme"), + makeSymbolBinding (juce_webkit_web_view_get_context, "webkit_web_view_get_context"), + makeSymbolBinding (juce_webkit_uri_scheme_request_get_path, "webkit_uri_scheme_request_get_path"), + makeSymbolBinding (juce_webkit_uri_scheme_response_new, "webkit_uri_scheme_response_new"), + makeSymbolBinding (juce_webkit_uri_scheme_response_set_http_headers, "webkit_uri_scheme_response_set_http_headers"), + makeSymbolBinding (juce_webkit_uri_scheme_response_set_status, "webkit_uri_scheme_response_set_status"), + makeSymbolBinding (juce_webkit_uri_scheme_request_finish_with_response, "webkit_uri_scheme_request_finish_with_response")); } bool loadGtkSymbols() { return loadSymbols (gtkLib, - makeSymbolBinding (juce_gtk_init, "gtk_init"), - makeSymbolBinding (juce_gtk_plug_new, "gtk_plug_new"), - makeSymbolBinding (juce_gtk_scrolled_window_new, "gtk_scrolled_window_new"), - makeSymbolBinding (juce_gtk_container_add, "gtk_container_add"), - makeSymbolBinding (juce_gtk_widget_show_all, "gtk_widget_show_all"), - makeSymbolBinding (juce_gtk_plug_get_id, "gtk_plug_get_id"), - makeSymbolBinding (juce_gtk_main, "gtk_main"), - makeSymbolBinding (juce_gtk_main_quit, "gtk_main_quit"), - makeSymbolBinding (juce_g_unix_fd_add, "g_unix_fd_add"), - makeSymbolBinding (juce_g_object_ref, "g_object_ref"), - makeSymbolBinding (juce_g_object_unref, "g_object_unref"), - makeSymbolBinding (juce_g_signal_connect_data, "g_signal_connect_data"), - makeSymbolBinding (juce_gdk_set_allowed_backends, "gdk_set_allowed_backends")); + makeSymbolBinding (juce_gtk_init, "gtk_init"), + makeSymbolBinding (juce_gtk_plug_new, "gtk_plug_new"), + makeSymbolBinding (juce_gtk_scrolled_window_new, "gtk_scrolled_window_new"), + makeSymbolBinding (juce_gtk_container_add, "gtk_container_add"), + makeSymbolBinding (juce_gtk_widget_show_all, "gtk_widget_show_all"), + makeSymbolBinding (juce_gtk_plug_get_id, "gtk_plug_get_id"), + makeSymbolBinding (juce_gtk_main, "gtk_main"), + makeSymbolBinding (juce_gtk_main_quit, "gtk_main_quit"), + makeSymbolBinding (juce_g_unix_fd_add, "g_unix_fd_add"), + makeSymbolBinding (juce_g_object_ref, "g_object_ref"), + makeSymbolBinding (juce_g_object_unref, "g_object_unref"), + makeSymbolBinding (juce_g_bytes_new, "g_bytes_new"), + makeSymbolBinding (juce_g_bytes_unref, "g_bytes_unref"), + makeSymbolBinding (juce_g_signal_connect_data, "g_signal_connect_data"), + makeSymbolBinding (juce_gdk_set_allowed_backends, "gdk_set_allowed_backends"), + makeSymbolBinding (juce_g_memory_input_stream_new, "g_memory_input_stream_new"), + makeSymbolBinding (juce_g_memory_input_stream_new_from_bytes, "g_memory_input_stream_new_from_bytes")); + } + + bool loadJsLibSymbols() + { + return loadSymbols (jsLib, + makeSymbolBinding (juce_jsc_value_to_json, "jsc_value_to_json")); + } + + bool loadSoupLibSymbols() + { + return loadSymbols (soupLib, + makeSymbolBinding (juce_soup_message_headers_new, "soup_message_headers_new"), + makeSymbolBinding (juce_soup_message_headers_append, "soup_message_headers_append")); + } + + bool loadGlibSymbols() + { + return loadSymbols (glib, + makeSymbolBinding (juce_g_free, "g_free")); } //============================================================================== - DynamicLibrary gtkLib { "libgtk-3.so" }, webkitLib { "libwebkit2gtk-4.0.so" }; - const bool webKitIsAvailable = loadWebkitSymbols() && loadGtkSymbols(); + DynamicLibrary gtkLib { "libgtk-3.so" }, + webkitLib { "libwebkit2gtk-4.0.so" }, + jsLib { "libjavascriptcoregtk-4.0.so" }, + soupLib { "libsoup-2.4.so" }, + glib { "libglib-2.0.so" }; + + const bool webKitIsAvailable = loadWebkitSymbols() + && loadGtkSymbols() + && loadJsLibSymbols() + && loadSoupLibSymbols() + && loadGlibSymbols(); JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WebKitSymbols) }; @@ -237,6 +405,12 @@ public: virtual void receiverHadError() = 0; }; + enum class ReturnAfterMessageReceived + { + no, + yes + }; + CommandReceiver (Responder* responderToUse, int inputChannelToUse) : responder (responderToUse), inChannel (inputChannelToUse) { @@ -252,7 +426,7 @@ public: int getFd() const { return inChannel; } - void tryNextRead() + void tryNextRead (ReturnAfterMessageReceived ret = ReturnAfterMessageReceived::no) { for (;;) { @@ -279,10 +453,13 @@ public: { pos = 0; - if (! receivingLength) + if (! std::exchange (receivingLength, ! receivingLength)) + { parseJSON (String (buffer.getData(), bufferLength.len)); - receivingLength = (! receivingLength); + if (ret == ReturnAfterMessageReceived::yes) + return; + } } } @@ -352,6 +529,120 @@ private: #define juce_g_signal_connect(instance, detailed_signal, c_handler, data) \ WebKitSymbols::getInstance()->juce_g_signal_connect_data (instance, detailed_signal, c_handler, data, nullptr, (GConnectFlags) 0) +static constexpr const char* platformSpecificIntegrationScript = R"( +window.__JUCE__ = { + postMessage: function (object) { + window.webkit.messageHandlers.__JUCE__.postMessage(object); + }, +}; +)"; + +struct InitialisationData +{ + bool nativeIntegrationsEnabled; + String userAgent; + String userScript; + String allowedOrigin; + + static constexpr std::optional marshallingVersion = std::nullopt; + + template + static void serialise (Archive& archive, Item& item) + { + archive (named ("nativeIntegrationsEnabled", item.nativeIntegrationsEnabled), + named ("userAgent", item.userAgent), + named ("userScript", item.userScript), + named ("allowedOrigin", item.allowedOrigin)); + } +}; + +struct EvaluateJavascriptParams +{ + String script; + bool requireCallback; + + static constexpr std::optional marshallingVersion = std::nullopt; + + template + static void serialise (Archive& archive, Item& item) + { + archive (named ("script", item.script), + named ("requireCallback", item.requireCallback)); + } +}; + +struct EvaluateJavascriptCallbackParams +{ + bool success; + + // This is necessary because a DynamicObject with a property value of var::undefined() + // cannot be unserialised. So we need to signal this case with an extra variable. + bool hasPayload; + + var payload; + String error; + + static constexpr std::optional marshallingVersion = std::nullopt; + + template + static void serialise (Archive& archive, Item& item) + { + archive(named ("success", item.success), + named ("hasPayload", item.hasPayload), + named ("payload", item.payload), + named ("error", item.error)); + } + + static inline String key { "evaluateJavascriptCallbackParams" }; +}; + +struct ResourceRequest +{ + int64 requestId; + String path; + + static constexpr std::optional marshallingVersion = std::nullopt; + + template + static void serialise (Archive& archive, Item& item) + { + archive (named ("requestId", item.requestId), + named ("path", item.path)); + } + + static inline const String key { "resourceRequest" }; +}; + +template <> +struct SerialisationTraits +{ + static constexpr auto marshallingVersion = std::nullopt; + + template + static void serialise (Archive& archive, T& item) + { + archive (named ("data", item.data), + named ("mimeType", item.mimeType)); + } +}; + +struct ResourceRequestResponse +{ + int64 requestId; + std::optional resource; + + static constexpr std::optional marshallingVersion = std::nullopt; + + template + static void serialise (Archive& archive, T& item) + { + archive (named ("requestId", item.requestId), + named ("resource", item.resource)); + } + + static inline const String key { "resourceRequestResponse" }; +}; + //============================================================================== class GtkChildProcess final : private CommandReceiver::Responder { @@ -367,23 +658,77 @@ public: { CommandReceiver::setBlocking (outChannel, true); + { + const ScopeGuard scope { [this] { CommandReceiver::setBlocking (receiver.getFd(), false); } }; + CommandReceiver::setBlocking (receiver.getFd(), true); + receiver.tryNextRead (CommandReceiver::ReturnAfterMessageReceived::yes); + + if (! initialisationData.has_value()) + { + std::cerr << "The first message received by GtkChildProcess should have been " + "the initialisationData, but it wasn't." << std::endl; + + return 1; + } + } + + auto& wk = *WebKitSymbols::getInstance(); + // webkit2gtk crashes when using the wayland backend embedded into an x11 window - WebKitSymbols::getInstance()->juce_gdk_set_allowed_backends ("x11"); + wk.juce_gdk_set_allowed_backends ("x11"); - WebKitSymbols::getInstance()->juce_gtk_init (nullptr, nullptr); + wk.juce_gtk_init (nullptr, nullptr); + + auto* settings = wk.juce_webkit_settings_new(); + + static constexpr int webkitHadwareAccelerationPolicyNeverFlag = 2; - auto* settings = WebKitSymbols::getInstance()->juce_webkit_settings_new(); WebKitSymbols::getInstance()->juce_webkit_settings_set_hardware_acceleration_policy (settings, - /* WEBKIT_HARDWARE_ACCELERATION_POLICY_NEVER */ 2); - if (userAgent.isNotEmpty()) - WebKitSymbols::getInstance()->juce_webkit_settings_set_user_agent (settings, userAgent.toRawUTF8()); + webkitHadwareAccelerationPolicyNeverFlag); + if (initialisationData->userAgent.isNotEmpty()) + WebKitSymbols::getInstance()->juce_webkit_settings_set_user_agent (settings, + initialisationData->userAgent.toRawUTF8()); auto* plug = WebKitSymbols::getInstance()->juce_gtk_plug_new (0); auto* container = WebKitSymbols::getInstance()->juce_gtk_scrolled_window_new (nullptr, nullptr); + #if JUCE_DEBUG + wk.juce_webkit_settings_set_enable_write_console_messages_to_stdout (settings, true); + wk.juce_webkit_settings_set_enable_developer_extras (settings, true); + #endif + auto* webviewWidget = WebKitSymbols::getInstance()->juce_webkit_web_view_new_with_settings (settings); webview = (WebKitWebView*) webviewWidget; + if (initialisationData->nativeIntegrationsEnabled) + { + manager = wk.juce_webkit_web_view_get_user_content_manager (webview); + + // It's probably fine to not disconnect these signals, given that upon closing the + // WebBrowserComponent the entire subprocess is cleaned up with the manager and + // everything. + juce_g_signal_connect (manager, + "script-message-received::__JUCE__", + G_CALLBACK (+[] (WebKitUserContentManager*, WebKitJavascriptResult* r, gpointer arg) + { + static_cast (arg)->invokeCallback (r); + }), + this); + + wk.juce_webkit_user_content_manager_register_script_message_handler (manager, "__JUCE__"); + + auto* context = wk.juce_webkit_web_view_get_context (webview); + wk.juce_webkit_web_context_register_uri_scheme (context, "juce", resourceRequestedCallback, this, nullptr); + + const StringArray userScripts { platformSpecificIntegrationScript, + initialisationData->userScript }; + + wk.juce_webkit_user_content_manager_add_script (manager, wk.juce_webkit_user_script_new (userScripts.joinIntoString ("\n").toRawUTF8(), + WEBKIT_USER_CONTENT_INJECT_TOP_FRAME, + WEBKIT_USER_SCRIPT_INJECT_AT_DOCUMENT_START, + nullptr, nullptr)); + } + WebKitSymbols::getInstance()->juce_gtk_container_add ((GtkContainer*) container, webviewWidget); WebKitSymbols::getInstance()->juce_gtk_container_add ((GtkContainer*) plug, container); @@ -420,6 +765,15 @@ public: return 0; } + void invokeCallback (WebKitJavascriptResult* r) + { + auto& wk = *WebKitSymbols::getInstance(); + + auto s = wk.juce_jsc_value_to_string (wk.juce_webkit_javascript_result_get_js_value (r)); + CommandReceiver::sendCommand (outChannel, "invokeCallback", var (s)); + wk.juce_g_free (s); + } + void goToURL (const var& params) { static Identifier urlIdentifier ("url"); @@ -445,16 +799,91 @@ public: } } + void evaluateJavascript (const var& params) + { + const auto jsParams = FromVar::convert (params); + + if (! jsParams.has_value()) + { + std::cerr << "Wrong params received by evaluateJavascript()" << std::endl; + return; + } + + WebKitSymbols::getInstance()->juce_webkit_web_view_run_javascript (webview, + jsParams->script.toRawUTF8(), + nullptr, + javascriptFinishedCallback, + this); + } + + void handleResourceRequesteResponse (const var& params) + { + auto& wk = *WebKitSymbols::getInstance(); + + const auto response = FromVar::convert (params); + + if (! response.has_value()) + { + std::cerr << "Bad request response received" << std::endl; + return; + } + + auto* request = requestIds.remove (response->requestId); + + // The WebKitURISchemeResponse object will take ownership of the headers + auto* headers = wk.juce_soup_message_headers_new (SoupMessageHeadersType::SOUP_MESSAGE_HEADERS_RESPONSE); + + if (initialisationData->allowedOrigin.isNotEmpty()) + wk.juce_soup_message_headers_append (headers, "Access-Control-Allow-Origin", initialisationData->allowedOrigin.toRawUTF8()); + + if (response->resource.has_value()) + { + auto* streamBytes = wk.juce_g_bytes_new (response->resource->data.data(), + static_cast (response->resource->data.size())); + ScopeGuard bytesScope { [&] { wk.juce_g_bytes_unref (streamBytes); } }; + + auto* stream = wk.juce_g_memory_input_stream_new_from_bytes (streamBytes); + ScopeGuard streamScope { [&] { wk.juce_g_object_unref (stream); } }; + + auto* webkitResponse = wk.juce_webkit_uri_scheme_response_new (stream, + static_cast (response->resource->data.size())); + ScopeGuard webkitResponseScope { [&] { wk.juce_g_object_unref (webkitResponse); } }; + + wk.juce_soup_message_headers_append (headers, "Content-Type", response->resource->mimeType.toRawUTF8()); + + wk.juce_webkit_uri_scheme_response_set_http_headers (webkitResponse, headers); + wk.juce_webkit_uri_scheme_response_set_status (webkitResponse, 200, nullptr); + wk.juce_webkit_uri_scheme_request_finish_with_response (request, webkitResponse); + + return; + } + + auto* stream = wk.juce_g_memory_input_stream_new(); + ScopeGuard streamScope { [&] { wk.juce_g_object_unref (stream); } }; + + auto* webkitResponse = wk.juce_webkit_uri_scheme_response_new (stream, 0); + ScopeGuard webkitResponseScope { [&] { wk.juce_g_object_unref (webkitResponse); } }; + + wk.juce_webkit_uri_scheme_response_set_http_headers (webkitResponse, headers); + wk.juce_webkit_uri_scheme_response_set_status (webkitResponse, 404, nullptr); + wk.juce_webkit_uri_scheme_request_finish_with_response (request, webkitResponse); + } + //============================================================================== void handleCommand (const String& cmd, const var& params) override { - if (cmd == "quit") quit(); - else if (cmd == "goToURL") goToURL (params); - else if (cmd == "goBack") WebKitSymbols::getInstance()->juce_webkit_web_view_go_back (webview); - else if (cmd == "goForward") WebKitSymbols::getInstance()->juce_webkit_web_view_go_forward (webview); - else if (cmd == "refresh") WebKitSymbols::getInstance()->juce_webkit_web_view_reload (webview); - else if (cmd == "stop") WebKitSymbols::getInstance()->juce_webkit_web_view_stop_loading (webview); - else if (cmd == "decision") handleDecisionResponse (params); + auto& wk = *WebKitSymbols::getInstance(); + + if (cmd == "quit") quit(); + else if (cmd == "goToURL") goToURL (params); + else if (cmd == "goBack") wk.juce_webkit_web_view_go_back (webview); + else if (cmd == "goForward") wk.juce_webkit_web_view_go_forward (webview); + else if (cmd == "refresh") wk.juce_webkit_web_view_reload (webview); + else if (cmd == "stop") wk.juce_webkit_web_view_stop_loading (webview); + else if (cmd == "decision") handleDecisionResponse (params); + else if (cmd == "init") initialisationData = FromVar::convert (params); + else if (cmd == "evaluateJavascript") evaluateJavascript (params); + else if (cmd == ResourceRequestResponse::key) handleResourceRequesteResponse (params); } void receiverHadError() override @@ -587,6 +1016,27 @@ public: } private: + void handleEvaluationCallback (const std::optional& value, const String& error) + { + const auto success = value.has_value(); + const auto hasPayload = success && ! value->isUndefined(); + + CommandReceiver::sendCommand (outChannel, + EvaluateJavascriptCallbackParams::key, + *ToVar::convert (EvaluateJavascriptCallbackParams { success, + hasPayload, + hasPayload ? *value : var{}, + error })); + } + + void handleResourceRequestedCallback (WebKitURISchemeRequest* request, const String& path) + { + const auto requestId = requestIds.insert (request); + CommandReceiver::sendCommand (outChannel, + ResourceRequest::key, + *ToVar::convert (ResourceRequest { requestId, path })); + } + static gboolean pipeReadyStatic (gint fd, GIOCondition condition, gpointer user) { return (reinterpret_cast (user)->pipeReady (fd, condition) ? TRUE : FALSE); @@ -619,31 +1069,223 @@ private: owner.onLoadFailed (error); } + static var fromJSCValue (JSCValue* value) + { + auto* json = WebKitSymbols::getInstance()->juce_jsc_value_to_json (value, 0); + ScopeGuard jsonFreeGuard { [&json] + { + if (json != nullptr) + WebKitSymbols::getInstance()->juce_g_free (json); + } }; + + if (json == nullptr) + return var::undefined(); + + return JSON::fromString (CharPointer_UTF8 { json }); + } + + struct WebKitJavascriptResultDeleter + { + void operator() (WebKitJavascriptResult* ptr) const noexcept + { + if (ptr != nullptr) + WebKitSymbols::getInstance()->juce_webkit_javascript_result_unref (ptr); + } + }; + + using WebKitJavascriptResultUniquePtr = std::unique_ptr; + + static void javascriptFinishedCallback (GObject*, GAsyncResult* result, gpointer user) + { + auto& wk = *WebKitSymbols::getInstance(); + + GError* error = nullptr; + ScopeGuard errorFreeGuard { [&error, &wk] + { + if (error != nullptr) + wk.juce_g_error_free (error); + } }; + + auto* owner = reinterpret_cast (user); + + // Using the non-deprecated webkit_javascript_result_get_js_value() functions seems easier + // but returned values fail the JS_IS_VALUE() internal assertion. The example code from the + // documentation doesn't seem to work either. + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") + WebKitJavascriptResultUniquePtr jsResult { wk.juce_webkit_web_view_run_javascript_finish (owner->webview, + result, + &error) }; + JUCE_END_IGNORE_WARNINGS_GCC_LIKE + + if (jsResult == nullptr) + { + owner->handleEvaluationCallback (std::nullopt, + error != nullptr ? String { CharPointer_UTF8 { error->message } } + : String{}); + + return; + } + + const auto jsValueResult = [&]() -> std::tuple, String> + { + auto* jsValue = wk.juce_webkit_javascript_result_get_js_value (jsResult.get()); + + if (jsValue == nullptr) + return { std::nullopt, String{} }; + + return { fromJSCValue (jsValue), String{} }; + }(); + + owner->handleEvaluationCallback (std::get<0> (jsValueResult), std::get<1> (jsValueResult)); + } + + static void resourceRequestedCallback (WebKitURISchemeRequest* request, gpointer user) + { + String path { CharPointer_UTF8 { WebKitSymbols::getInstance()->juce_webkit_uri_scheme_request_get_path (request) } }; + reinterpret_cast (user)->handleResourceRequestedCallback (request, path); + } + + class RequestIds + { + public: + int64 insert (WebKitURISchemeRequest* request) + { + const auto requestId = nextRequestId++; + + if (nextRequestId == std::numeric_limits::max()) + nextRequestId = 0; + + requests[requestId] = request; + return requestId; + } + + WebKitURISchemeRequest* remove (int64 requestId) + { + auto it = requests.find (requestId); + + if (it == requests.end()) + { + std::cerr << "Outstanding request not found for id " << requestId << std::endl; + return nullptr; + } + + auto r = it->second; + requests.erase (it); + + return r; + } + + private: + std::map requests; + int64 nextRequestId = 0; + }; + int outChannel = 0; CommandReceiver receiver; String userAgent; WebKitWebView* webview = nullptr; Array decisions; + WebKitUserContentManager* manager = nullptr; + std::optional initialisationData; + RequestIds requestIds; }; //============================================================================== -class WebBrowserComponent::Pimpl : private Thread, - private CommandReceiver::Responder +struct WebBrowserComponent::Impl::Platform : public PlatformInterface, + private Thread, + private CommandReceiver::Responder { public: - Pimpl (WebBrowserComponent& parent, const String& userAgentToUse) - : Thread ("Webview"), owner (parent), userAgent (userAgentToUse) + Platform (WebBrowserComponent& browserIn, + const WebBrowserComponent::Options& optionsIn, + const StringArray& userStrings) + : Thread ("Webview"), browser (browserIn), userAgent (optionsIn.getUserAgent()) { webKitIsAvailable = WebKitSymbols::getInstance()->isWebKitAvailable(); + init (InitialisationData { optionsIn.getNativeIntegrationsEnabled(), + userAgent, + userStrings.joinIntoString ("\n"), + optionsIn.getAllowedOrigin() ? *optionsIn.getAllowedOrigin() : "" }); } - ~Pimpl() override + ~Platform() override { quit(); } + void fallbackPaint (Graphics& g) override + { + g.fillAll (Colours::white); + } + + void evaluateJavascript (const String& script, WebBrowserComponent::EvaluationCallback callback) override + { + if (callback != nullptr) + evaluationCallbacks.push_back (std::move (callback)); + + CommandReceiver::sendCommand (outChannel, + "evaluateJavascript", + *ToVar::convert (EvaluateJavascriptParams { script, callback != nullptr })); + } + + void handleJavascriptEvaluationCallback (const var& paramsIn) + { + const auto params = FromVar::convert (paramsIn); + + if (! params.has_value() || evaluationCallbacks.size() == 0) + { + jassertfalse; + return; + } + + const auto result = [&] + { + using Error = EvaluationResult::Error; + + if (! params->success) + { + if (params->error.isNotEmpty()) + return EvaluationResult { Error { Error::Type::javascriptException, params->error } }; + + return EvaluationResult { Error { Error::Type::unknown, {} } }; + } + + return EvaluationResult { params->hasPayload ? params->payload : var::undefined() }; + }(); + + auto& cb = evaluationCallbacks.front(); + cb (result); + evaluationCallbacks.pop_front(); + } + + void handleResourceRequest (const var& paramsIn) + { + const auto params = FromVar::convert (paramsIn); + + if (! params.has_value()) + { + jassertfalse; + return; + } + + const auto response = browser.impl->handleResourceRequest (params->path); + + CommandReceiver::sendCommand (outChannel, + ResourceRequestResponse::key, + *ToVar::convert (ResourceRequestResponse { params->requestId, response })); + } + + void setWebViewSize (int, int) override + { + resized(); + } + + void checkWindowAssociation() override + { + } + //============================================================================== - void init() + void init (const InitialisationData& initialisationData) { if (! webKitIsAvailable) return; @@ -659,6 +1301,8 @@ public: CommandReceiver::setBlocking (threadControl[0], false); CommandReceiver::setBlocking (threadControl[1], true); + CommandReceiver::sendCommand (outChannel, "init", *ToVar::convert (initialisationData)); + unsigned long windowHandle; auto actual = read (inChannel, &windowHandle, sizeof (windowHandle)); @@ -676,7 +1320,7 @@ public: startThread(); xembed.reset (new XEmbedComponent (windowHandle)); - owner.addAndMakeVisible (xembed.get()); + browser.addAndMakeVisible (xembed.get()); } void quit() @@ -711,7 +1355,7 @@ public: } //============================================================================== - void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) + void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override { if (! webKitIsAvailable) return; @@ -729,15 +1373,15 @@ public: CommandReceiver::sendCommand (outChannel, "goToURL", var (params.get())); } - void goBack() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goBack", {}); } - void goForward() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goForward", {}); } - void refresh() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "refresh", {}); } - void stop() { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "stop", {}); } + void goBack() override { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goBack", {}); } + void goForward() override { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "goForward", {}); } + void refresh() override { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "refresh", {}); } + void stop() override { if (webKitIsAvailable) CommandReceiver::sendCommand (outChannel, "stop", {}); } void resized() { if (xembed != nullptr) - xembed->setBounds (owner.getLocalBounds()); + xembed->setBounds (browser.getLocalBounds()); } private: @@ -892,11 +1536,19 @@ private: { auto url = params.getProperty ("url", var()).toString(); - if (cmd == "pageAboutToLoad") handlePageAboutToLoad (url, params); - else if (cmd == "pageFinishedLoading") owner.pageFinishedLoading (url); - else if (cmd == "windowCloseRequest") owner.windowCloseRequest(); - else if (cmd == "newWindowAttemptingToLoad") owner.newWindowAttemptingToLoad (url); - else if (cmd == "pageLoadHadNetworkError") handlePageLoadHadNetworkError (params); + if (cmd == "pageAboutToLoad") handlePageAboutToLoad (url, params); + else if (cmd == "pageFinishedLoading") browser.pageFinishedLoading (url); + else if (cmd == "windowCloseRequest") browser.windowCloseRequest(); + else if (cmd == "newWindowAttemptingToLoad") browser.newWindowAttemptingToLoad (url); + else if (cmd == "pageLoadHadNetworkError") handlePageLoadHadNetworkError (params); + else if (cmd == "invokeCallback") invokeCallback (params); + else if (cmd == EvaluateJavascriptCallbackParams::key) handleJavascriptEvaluationCallback (params); + else if (cmd == ResourceRequest::key) handleResourceRequest (params); + } + + void invokeCallback (const var& params) + { + browser.impl->handleNativeEvent (JSON::fromString (params.toString())); } void handlePageAboutToLoad (const String& url, const var& inputParams) @@ -908,7 +1560,7 @@ private: DynamicObject::Ptr params = new DynamicObject; params->setProperty ("decision_id", decision_id); - params->setProperty ("allow", owner.pageAboutToLoad (url)); + params->setProperty ("allow", browser.pageAboutToLoad (url)); CommandReceiver::sendCommand (outChannel, "decision", var (params.get())); } @@ -918,7 +1570,7 @@ private: { String error = params.getProperty ("error", "Unknown error"); - if (owner.pageLoadHadNetworkError (error)) + if (browser.pageLoadHadNetworkError (error)) goToURL (String ("data:text/plain,") + error, nullptr, nullptr); } @@ -936,7 +1588,7 @@ private: //============================================================================== bool webKitIsAvailable = false; - WebBrowserComponent& owner; + WebBrowserComponent& browser; String userAgent; std::unique_ptr receiver; int childProcess = 0, inChannel = 0, outChannel = 0; @@ -945,104 +1597,16 @@ private: std::shared_ptr livenessProbe = std::make_shared (0); std::vector pfds; std::optional subprocessFile; + std::deque evaluationCallbacks; }; //============================================================================== -WebBrowserComponent::WebBrowserComponent (const Options& options) - : browser (new Pimpl (*this, options.getUserAgent())) -{ - ignoreUnused (blankPageShown); - ignoreUnused (unloadPageWhenHidden); - - setOpaque (true); - - browser->init(); -} - -WebBrowserComponent::~WebBrowserComponent() -{ -} - -//============================================================================== -void WebBrowserComponent::goToURL (const String& url, - const StringArray* headers, - const MemoryBlock* postData) -{ - lastURL = url; - - if (headers != nullptr) - lastHeaders = *headers; - else - lastHeaders.clear(); - - if (postData != nullptr) - lastPostData = *postData; - else - lastPostData.reset(); - - browser->goToURL (url, headers, postData); -} - -void WebBrowserComponent::stop() -{ - browser->stop(); -} - -void WebBrowserComponent::goBack() -{ - lastURL.clear(); - - browser->goBack(); -} - -void WebBrowserComponent::goForward() -{ - lastURL.clear(); - browser->goForward(); -} - -void WebBrowserComponent::refresh() -{ - browser->refresh(); -} - -//============================================================================== -void WebBrowserComponent::paint (Graphics& g) -{ - g.fillAll (Colours::white); -} - -void WebBrowserComponent::checkWindowAssociation() -{ -} - -void WebBrowserComponent::reloadLastURL() -{ - if (lastURL.isNotEmpty()) - { - goToURL (lastURL, &lastHeaders, &lastPostData); - lastURL.clear(); - } -} - -void WebBrowserComponent::parentHierarchyChanged() -{ - checkWindowAssociation(); -} - -void WebBrowserComponent::resized() -{ - if (browser != nullptr) - browser->resized(); -} - -void WebBrowserComponent::visibilityChanged() -{ - checkWindowAssociation(); -} - -void WebBrowserComponent::focusGainedWithDirection (FocusChangeType, FocusChangeDirection) +auto WebBrowserComponent::Impl::createAndInitPlatformDependentPart (WebBrowserComponent::Impl& impl, + const WebBrowserComponent::Options& options, + const StringArray& userStrings) + -> std::unique_ptr { + return std::make_unique (impl.owner, options, userStrings); } void WebBrowserComponent::clearCookies() diff --git a/modules/juce_gui_extra/native/juce_WebBrowserComponent_mac.mm b/modules/juce_gui_extra/native/juce_WebBrowserComponent_mac.mm index 3f11a1e5a1..7c4cad144b 100644 --- a/modules/juce_gui_extra/native/juce_WebBrowserComponent_mac.mm +++ b/modules/juce_gui_extra/native/juce_WebBrowserComponent_mac.mm @@ -115,13 +115,98 @@ static NSMutableURLRequest* getRequestForURL (const String& url, const StringArr return nullptr; } +static var fromObject (id object) +{ + // An undefined var serialises to 'undefined' i.e. an expression not returning a value + if (object == nil) + return var::undefined(); + + if ([object isKindOfClass:[NSNumber class]]) + { + // The object returned by evaluateJavaScript is a __NSCFBoolean*, which is a private class + // to the framework, but a handle to this class can be obtained through @YES. When cast to + // an NSNumber this object would have the wrong type encoding, so [number objCType]; would + // return 'c' instead of 'B', hence that approach wouldn't work. + if ([object isKindOfClass: [@YES class]]) + return static_cast (object).boolValue == YES ? true : false; + + return static_cast (object).doubleValue; + } + + if ([object isKindOfClass:[NSString class]]) + return nsStringToJuce (object); + + if ([object isKindOfClass:[NSArray class]]) + { + Array result; + + auto* array = static_cast (object); + + for (id elem in array) + result.add (fromObject (elem)); + + return result; + } + + if ([object isKindOfClass:[NSDictionary class]]) + { + const auto* dict = static_cast (object); + + DynamicObject::Ptr result (new DynamicObject()); + + for (id key in dict) + result->setProperty (nsStringToJuce (key), fromObject ([dict objectForKey:key])); + + return result.get(); + } + + if ([object isKindOfClass:[NSDate class]]) + { + JUCE_AUTORELEASEPOOL + { + auto* date = static_cast (object); + auto* formatter = [[NSDateFormatter alloc] init]; + const auto javascriptDateFormatString = @"yyyy'-'MM'-'dd'T'HH':'mm':'ss.SSS'Z'"; + [formatter setDateFormat: javascriptDateFormatString]; + [formatter setTimeZone: [NSTimeZone timeZoneWithName: @"UTC"]]; + NSString* dateString = [formatter stringFromDate: date]; + return nsStringToJuce (dateString); + } + } + + // Returning a Void var, which serialises to 'null' + if ([object isKindOfClass:[NSNull class]]) + return {}; + + jassertfalse; + return {}; +} + +using LastFocusChange = std::optional; + +static const char* lastFocusChangeMemberName = "lastFocusChangeHandle"; + +[[maybe_unused]] static void setLastFocusChangeHandle (id instance, LastFocusChange* object) +{ + object_setInstanceVariable (instance, lastFocusChangeMemberName, object); +} + +[[maybe_unused]] static LastFocusChange* getLastFocusChangeHandle (id instance) +{ + return getIvar (instance, lastFocusChangeMemberName); +} + #if JUCE_MAC template struct WebViewKeyEquivalentResponder final : public ObjCClass { + using Base = ObjCClass; + WebViewKeyEquivalentResponder() - : ObjCClass ("WebViewKeyEquivalentResponder_") + : Base ("WebViewKeyEquivalentResponder_") { + this->template addIvar (lastFocusChangeMemberName); + this->addMethod (@selector (performKeyEquivalent:), [] (id self, SEL selector, NSEvent* event) { @@ -156,8 +241,48 @@ struct WebViewKeyEquivalentResponder final : public ObjCClass return sendAction (@selector (selectAll:)); } - return ObjCClass::template sendSuperclassMessage (self, selector, event); + return Base::template sendSuperclassMessage (self, selector, event); }); + + this->addMethod (@selector (resignFirstResponder), + [] (id self, SEL selector) + { + const auto result = Base::template sendSuperclassMessage (self, selector); + + auto* focusChangeTypeHandle = getLastFocusChangeHandle (self); + jassert (focusChangeTypeHandle != nullptr); // Forgot to call setLastFocusChangeHandle? + focusChangeTypeHandle->emplace (Component::FocusChangeDirection::unknown); + + auto* currentEvent = [NSApp currentEvent]; + + if (currentEvent == nullptr) + return result; + + const auto eventType = [currentEvent type]; + + if ( eventType != NSEventTypeKeyUp + && eventType != NSEventTypeKeyDown + && eventType != NSEventTypeFlagsChanged) + { + return result; + } + + // Device independent key numbers should be compared with Carbon + // constants, but we define the one we need here to avoid importing + // Carbon.h + static constexpr unsigned short carbonTabKeycode = 0x30; + + if ([currentEvent keyCode] != carbonTabKeycode) + return result; + + const auto shiftKeyDown = ([currentEvent modifierFlags] & NSEventModifierFlagShift) != 0; + + focusChangeTypeHandle->emplace (shiftKeyDown ? Component::FocusChangeDirection::backward + : Component::FocusChangeDirection::forward); + + return result; + }); + this->registerClass(); } }; @@ -267,16 +392,56 @@ private: JUCE_END_IGNORE_WARNINGS_GCC_LIKE #endif +// Connects the delegate to the rest of the implementation without making WebViewDelegateClass +// a nested class as well. +class DelegateConnector +{ +public: + DelegateConnector (WebBrowserComponent& browserIn, + std::function handleNativeEventFnIn, + std::function (const String&)> handleResourceRequestFnIn, + const WebBrowserComponent::Options& optionsIn) + : browser (browserIn), + handleNativeEventFn (std::move (handleNativeEventFnIn)), + handleResourceRequestFn (std::move (handleResourceRequestFnIn)), + options (optionsIn) + { + } + + auto& getBrowser() { return browser; } + + void handleNativeEvent (const var& message) + { + handleNativeEventFn (message); + } + + auto handleResourceRequest (const String& url) + { + return handleResourceRequestFn (url); + } + + [[nodiscard]] const auto& getOptions() const + { + return options; + } + +private: + WebBrowserComponent& browser; + std::function handleNativeEventFn; + std::function (const String&)> handleResourceRequestFn; + WebBrowserComponent::Options options; +}; + struct API_AVAILABLE (macos (10.10)) WebViewDelegateClass final : public ObjCClass { WebViewDelegateClass() : ObjCClass ("JUCEWebViewDelegate_") { - addIvar ("owner"); + addIvar ("connector"); addMethod (@selector (webView:decidePolicyForNavigationAction:decisionHandler:), [] (id self, SEL, WKWebView*, WKNavigationAction* navigationAction, void (^decisionHandler) (WKNavigationActionPolicy)) { - if (getOwner (self)->pageAboutToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString]))) + if (getConnector (self)->getBrowser().pageAboutToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString]))) decisionHandler (WKNavigationActionPolicyAllow); else decisionHandler (WKNavigationActionPolicyCancel); @@ -285,34 +450,112 @@ struct API_AVAILABLE (macos (10.10)) WebViewDelegateClass final : public ObjCCla addMethod (@selector (webView:didFinishNavigation:), [] (id self, SEL, WKWebView* webview, WKNavigation*) { - getOwner (self)->pageFinishedLoading (nsStringToJuce ([[webview URL] absoluteString])); + getConnector (self)->getBrowser().pageFinishedLoading (nsStringToJuce ([[webview URL] absoluteString])); }); addMethod (@selector (webView:didFailNavigation:withError:), [] (id self, SEL, WKWebView*, WKNavigation*, NSError* error) { - displayError (getOwner (self), error); + displayError (&getConnector (self)->getBrowser(), error); }); addMethod (@selector (webView:didFailProvisionalNavigation:withError:), [] (id self, SEL, WKWebView*, WKNavigation*, NSError* error) { - displayError (getOwner (self), error); + displayError (&getConnector (self)->getBrowser(), error); }); addMethod (@selector (webViewDidClose:), [] (id self, SEL, WKWebView*) { - getOwner (self)->windowCloseRequest(); + getConnector (self)->getBrowser().windowCloseRequest(); }); addMethod (@selector (webView:createWebViewWithConfiguration:forNavigationAction:windowFeatures:), [] (id self, SEL, WKWebView*, WKWebViewConfiguration*, WKNavigationAction* navigationAction, WKWindowFeatures*) { - getOwner (self)->newWindowAttemptingToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString])); + getConnector (self)->getBrowser().newWindowAttemptingToLoad (nsStringToJuce ([[[navigationAction request] URL] absoluteString])); return nil; }); + addMethod (@selector (userContentController:didReceiveScriptMessage:), + [] (id self, SEL, id, id message) + { + const auto object = fromObject ([message body]); + + if (! object.isString()) + { + jassertfalse; + return; + } + + getConnector (self)->handleNativeEvent (JSON::fromString (object.toString())); + }); + + addMethod (@selector (webView:startURLSchemeTask:), + [] (id self, SEL, id, id urlSchemeTask) + { + const auto request = [urlSchemeTask request]; + + auto* url = [&] + { + auto r = [request URL]; + + return r == nil ? [NSURL URLWithString:@""] : (NSURL* _Nonnull) r; + }(); + + const auto path = nsStringToJuce ([url path]); + + const auto resource = getConnector (self)->handleResourceRequest (path); + + JUCE_AUTORELEASEPOOL + { + const auto makeResponse = [&url] (auto responseCode, id headers=nil) + { + auto response = [[NSHTTPURLResponse alloc] initWithURL:url + statusCode:responseCode + HTTPVersion:@"HTTP/1.1" + headerFields:headers]; + + if (response == nil) + return [[NSHTTPURLResponse alloc] autorelease]; + + return (NSHTTPURLResponse* _Nonnull) [response autorelease]; + }; + + if (resource.has_value()) + { + NSMutableDictionary* headers = [@ { + @"Content-Length" : juceStringToNS (String { resource->data.size() }), + @"Content-Type" : juceStringToNS (resource->mimeType), + } mutableCopy]; + + if (auto allowedOrigin = getConnector (self)->getOptions().getAllowedOrigin()) + { + [headers setObject:juceStringToNS (*allowedOrigin) + forKey:@"Access-Control-Allow-Origin"]; + } + + auto response = makeResponse (200, headers); + + [urlSchemeTask didReceiveResponse:response]; + [urlSchemeTask didReceiveData:[NSData dataWithBytes:resource->data.data() + length:resource->data.size()]]; + } + else + { + [urlSchemeTask didReceiveResponse:makeResponse (404)]; + } + + [urlSchemeTask didFinish]; + } + }); + + addMethod (@selector (webView:stopURLSchemeTask:), + [] (id, SEL, id, id) + { + }); + JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") if (@available (macOS 10.12, *)) { @@ -384,8 +627,15 @@ struct API_AVAILABLE (macos (10.10)) WebViewDelegateClass final : public ObjCCla registerClass(); } - static void setOwner (id self, WebBrowserComponent* owner) { object_setInstanceVariable (self, "owner", owner); } - static WebBrowserComponent* getOwner (id self) { return getIvar (self, "owner"); } + static void setConnector (id self, DelegateConnector* connector) + { + object_setInstanceVariable (self, "connector", connector); + } + + static DelegateConnector* getConnector (id self) + { + return getIvar (self, "connector"); + } private: static void displayError (WebBrowserComponent* owner, NSError* error) @@ -403,25 +653,32 @@ private: }; //============================================================================== -struct WebViewBase +struct WebBrowserComponent::Impl::Platform { - virtual ~WebViewBase() = default; - - virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0; - virtual void goBack() = 0; - virtual void goForward() = 0; - virtual void stop() = 0; - virtual void refresh() = 0; - - virtual id getWebView() = 0; + class WKWebViewImpl; + class WebViewImpl; }; +static constexpr const char* platformSpecificIntegrationScript = R"( +window.__JUCE__ = { + postMessage: function (object) { + window.webkit.messageHandlers.__JUCE__.postMessage(object); + }, +}; +)"; + +//============================================================================== #if JUCE_MAC JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wdeprecated-declarations") -class WebViewImpl : public WebViewBase +class WebBrowserComponent::Impl::Platform::WebViewImpl : public WebBrowserComponent::Impl::PlatformInterface, + #if JUCE_MAC + public NSViewComponent + #else + public UIViewComponent + #endif { public: - WebViewImpl (WebBrowserComponent* owner, const String& userAgent) + WebViewImpl (WebBrowserComponent::Impl& implIn, const String& userAgent) : browser (implIn.owner) { static WebViewKeyEquivalentResponder webviewClass; @@ -429,24 +686,59 @@ public: frameName: nsEmptyString() groupName: nsEmptyString()]); + setLastFocusChangeHandle (webView.get(), &lastFocusChange); + webView.get().customUserAgent = juceStringToNS (userAgent); static DownloadClickDetectorClass cls; clickListener.reset ([cls.createInstance() init]); - DownloadClickDetectorClass::setOwner (clickListener.get(), owner); + DownloadClickDetectorClass::setOwner (clickListener.get(), &browser); [webView.get() setPolicyDelegate: clickListener.get()]; [webView.get() setFrameLoadDelegate: clickListener.get()]; [webView.get() setUIDelegate: clickListener.get()]; + + setView (webView.get()); + browser.addAndMakeVisible (this); } ~WebViewImpl() override { + setView (nil); + [webView.get() setPolicyDelegate: nil]; [webView.get() setFrameLoadDelegate: nil]; [webView.get() setUIDelegate: nil]; } + void setWebViewSize (int width, int height) override + { + setSize (width, height); + } + + void checkWindowAssociation() override + { + if (browser.isShowing()) + { + browser.reloadLastURL(); + + if (browser.blankPageShown) + browser.goBack(); + } + else + { + if (browser.unloadPageWhenHidden && ! browser.blankPageShown) + { + // when the component becomes invisible, some stuff like flash + // carries on playing audio, so we need to force it onto a blank + // page to avoid this, (and send it back when it's made visible again). + + browser.blankPageShown = true; + goToURL ("about:blank", nullptr, nullptr); + } + } + } + void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override @@ -486,9 +778,7 @@ public: void stop() override { [webView.get() stopLoading: nil]; } void refresh() override { [webView.get() reload: nil]; } - id getWebView() override { return webView.get(); } - - void mouseMove (const MouseEvent&) + void mouseMove (const MouseEvent&) override { JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wundeclared-selector") // WebKit doesn't capture mouse-moves itself, so it seems the only way to make @@ -498,47 +788,185 @@ public: JUCE_END_IGNORE_WARNINGS_GCC_LIKE } + void evaluateJavascript (const String&, WebBrowserComponent::EvaluationCallback) override + { + // This feature is only available on MacOS 10.11 and above + jassertfalse; + } + private: + WebBrowserComponent& browser; + LastFocusChange lastFocusChange; ObjCObjectHandle webView; ObjCObjectHandle clickListener; }; JUCE_END_IGNORE_WARNINGS_GCC_LIKE #endif -class API_AVAILABLE (macos (10.11)) WKWebViewImpl : public WebViewBase +class API_AVAILABLE (macos (10.11)) WebBrowserComponent::Impl::Platform::WKWebViewImpl : public WebBrowserComponent::Impl::PlatformInterface, + #if JUCE_MAC + public NSViewComponent + #else + public UIViewComponent + #endif { public: - WKWebViewImpl (WebBrowserComponent* owner, const String& userAgent) + WKWebViewImpl (WebBrowserComponent::Impl& implIn, + const WebBrowserComponent::Options& browserOptions, + const StringArray& userScripts) + : owner (implIn), + delegateConnector (implIn.owner, + [this] (const auto& m) { owner.handleNativeEvent (m); }, + [this] (const auto& r) { return owner.handleResourceRequest (r); }, + browserOptions) { - #if JUCE_MAC - static WebViewKeyEquivalentResponder webviewClass; + ObjCObjectHandle config { [WKWebViewConfiguration new] }; + id preferences = [config.get() preferences]; - webView.reset ([webviewClass.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f)]); - #else - webView.reset ([[WKWebView alloc] initWithFrame: CGRectMake (0, 0, 100.0f, 100.0f)]); - #endif - - if (userAgent.isNotEmpty()) - webView.get().customUserAgent = juceStringToNS (userAgent); + [preferences setValue:@(true) forKey:@"fullScreenEnabled"]; + [preferences setValue:@(true) forKey:@"DOMPasteAllowed"]; + [preferences setValue:@(true) forKey:@"javaScriptCanAccessClipboard"]; static WebViewDelegateClass cls; webViewDelegate.reset ([cls.createInstance() init]); - WebViewDelegateClass::setOwner (webViewDelegate.get(), owner); + WebViewDelegateClass::setConnector (webViewDelegate.get(), &delegateConnector); + + if (browserOptions.getNativeIntegrationsEnabled()) + { + [[config.get() userContentController] addScriptMessageHandler:webViewDelegate.get() + name:@"__JUCE__"]; + } + + // It isn't necessary to concatenate all scripts and add them as one. They will still work + // when added separately. But in the latter case sometimes only the first one is visible in + // the WebView developer console, so concatenating them helps with debugging. + auto allUserScripts = userScripts; + allUserScripts.insert (0, platformSpecificIntegrationScript); + + NSUniquePtr script { [[WKUserScript alloc] + initWithSource:juceStringToNS (allUserScripts.joinIntoString ("\n")) + injectionTime:WKUserScriptInjectionTimeAtDocumentStart + forMainFrameOnly:YES] }; + + [[config.get() userContentController] addUserScript:script.get()]; + + if (@available (macOS 10.13, iOS 11.0, *)) + { + if (browserOptions.getResourceProvider() != nullptr) + [config.get() setURLSchemeHandler:webViewDelegate.get() forURLScheme:@"juce"]; + } + + #if JUCE_DEBUG + [preferences setValue: @(true) forKey: @"developerExtrasEnabled"]; + #endif + + #if JUCE_MAC + static WebViewKeyEquivalentResponder webviewClass; + + webView.reset ([webviewClass.createInstance() initWithFrame: NSMakeRect (0, 0, 100.0f, 100.0f) + configuration: config.get()]); + + setLastFocusChangeHandle (webView.get(), &lastFocusChange); + #else + webView.reset ([[WKWebView alloc] initWithFrame: CGRectMake (0, 0, 100.0f, 100.0f) + configuration: config.get()]); + #endif + + if (const auto userAgent = browserOptions.getUserAgent(); userAgent.isNotEmpty()) + webView.get().customUserAgent = juceStringToNS (userAgent); [webView.get() setNavigationDelegate: webViewDelegate.get()]; [webView.get() setUIDelegate: webViewDelegate.get()]; - #if JUCE_DEBUG - [[[webView.get() configuration] preferences] setValue: @(true) forKey: @"developerExtrasEnabled"]; - #endif + setView (webView.get()); + owner.owner.addAndMakeVisible (this); } ~WKWebViewImpl() override { + setView (nil); [webView.get() setNavigationDelegate: nil]; [webView.get() setUIDelegate: nil]; } + void setWebViewSize (int width, int height) override + { + setSize (width, height); + } + + void checkWindowAssociation() override + { + auto& browser = owner.owner; + + if (browser.isShowing()) + { + browser.reloadLastURL(); + + if (browser.blankPageShown) + browser.goBack(); + } + else + { + if (browser.unloadPageWhenHidden && ! browser.blankPageShown) + { + // when the component becomes invisible, some stuff like flash + // carries on playing audio, so we need to force it onto a blank + // page to avoid this, (and send it back when it's made visible again). + + browser.blankPageShown = true; + goToURL ("about:blank", nullptr, nullptr); + } + } + } + + void focusGainedWithDirection (FocusChangeType, FocusChangeDirection) override + { + const auto webViewFocusLossDirection = std::exchange (lastFocusChange, std::nullopt); + + // We didn't receive the focus from the WebView, so we need to pass it onto it + if (! webViewFocusLossDirection.has_value()) + { + #if JUCE_MAC + [[webView.get() window] makeFirstResponder: webView.get()]; + #endif + return; + } + + auto* comp = [&]() -> Component* + { + auto* c = owner.owner.getParentComponent(); + + if (c == nullptr) + return nullptr; + + const auto traverser = c->createFocusTraverser(); + + if (*webViewFocusLossDirection == FocusChangeDirection::forward) + { + if (auto* next = traverser->getNextComponent (this); next != nullptr) + return next; + + return traverser->getDefaultComponent (c); + } + + if (*webViewFocusLossDirection == FocusChangeDirection::backward) + { + if (auto* previous = traverser->getPreviousComponent (&owner.owner); previous != nullptr) + return previous; + + if (auto all = traverser->getAllComponents (c); ! all.empty()) + return all.back(); + } + + return nullptr; + }(); + + if (comp != nullptr) + comp->getAccessibilityHandler()->grabFocus(); + else + giveAwayKeyboardFocus(); + } + void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override @@ -574,169 +1002,87 @@ public: void stop() override { [webView.get() stopLoading]; } void refresh() override { [webView.get() reload]; } - id getWebView() override { return webView.get(); } + void evaluateJavascript (const String& script, WebBrowserComponent::EvaluationCallback callback) override + { + [webView.get() evaluateJavaScript: juceStringToNS (script) + completionHandler: ^(id obj, NSError* error) + { + if (callback == nullptr) + return; + + if (error != nil) + { + const auto resultError = [&]() -> WebBrowserComponent::EvaluationResult::Error + { + const auto errorCode = [error code]; + + if (errorCode == 4) + { + String errorMsgTemplate { "JAVASCRIPT_ERROR at (EVALUATION_SOURCE:LINE_NUMBER:COLUMN_NUMBER)" }; + + if (id m = [error.userInfo objectForKey:@"WKJavaScriptExceptionMessage"]; m != nil) + errorMsgTemplate = errorMsgTemplate.replace ("JAVASCRIPT_ERROR", nsStringToJuce (m)); + + if (id m = [error.userInfo objectForKey:@"WKJavaScriptExceptionSourceURL"]; m != nil) + errorMsgTemplate = errorMsgTemplate.replace ("EVALUATION_SOURCE", nsStringToJuce ([m absoluteString])); + + if (id m = [error.userInfo objectForKey:@"WKJavaScriptExceptionLineNumber"]; m != nil) + errorMsgTemplate = errorMsgTemplate.replace ("LINE_NUMBER", String { [m intValue] }); + + if (id m = [error.userInfo objectForKey:@"WKJavaScriptExceptionColumnNumber"]; m != nil) + errorMsgTemplate = errorMsgTemplate.replace ("COLUMN_NUMBER", String { [m intValue] }); + + return { WebBrowserComponent::EvaluationResult::Error::Type::javascriptException, + errorMsgTemplate }; + } + else if (errorCode == 5) + { + String errorMessage; + + if (id m = [[error userInfo] objectForKey:@"NSLocalizedDescription"]; m != nil) + errorMessage = nsStringToJuce (m); + + return { WebBrowserComponent::EvaluationResult::Error::Type::unsupportedReturnType, + errorMessage }; + } + + return { WebBrowserComponent::EvaluationResult::Error::Type::unknown, "Unknown error" }; + }(); + + callback (EvaluationResult { resultError }); + } + else + { + callback (EvaluationResult { fromObject (obj) }); + } + }]; + } private: + WebBrowserComponent::Impl& owner; + DelegateConnector delegateConnector; + LastFocusChange lastFocusChange; ObjCObjectHandle webView; ObjCObjectHandle webViewDelegate; }; //============================================================================== -class WebBrowserComponent::Pimpl - #if JUCE_MAC - : public NSViewComponent - #else - : public UIViewComponent - #endif +auto WebBrowserComponent::Impl::createAndInitPlatformDependentPart (WebBrowserComponent::Impl& impl, + const WebBrowserComponent::Options& options, + const StringArray& userScripts) + -> std::unique_ptr { -public: - Pimpl (WebBrowserComponent* owner, const String& userAgent) - { - if (@available (macOS 10.11, *)) - webView = std::make_unique (owner, userAgent); - #if JUCE_MAC - else - webView = std::make_unique (owner, userAgent); - #endif + if (@available (macOS 10.11, *)) + return std::make_unique (impl, options, userScripts); - setView (webView->getWebView()); - } + #if JUCE_MAC + return std::make_unique (impl, options.getUserAgent()); + #endif - ~Pimpl() override - { - webView = nullptr; - setView (nil); - } - - void goToURL (const String& url, - const StringArray* headers, - const MemoryBlock* postData) - { - webView->goToURL (url, headers, postData); - } - - void goBack() { webView->goBack(); } - void goForward() { webView->goForward(); } - - void stop() { webView->stop(); } - void refresh() { webView->refresh(); } - -private: - std::unique_ptr webView; -}; - -//============================================================================== -WebBrowserComponent::WebBrowserComponent (const Options& options) - : unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden()) -{ - setOpaque (true); - browser.reset (new Pimpl (this, options.getUserAgent())); - addAndMakeVisible (browser.get()); -} - -WebBrowserComponent::~WebBrowserComponent() = default; - -//============================================================================== -void WebBrowserComponent::goToURL (const String& url, - const StringArray* headers, - const MemoryBlock* postData) -{ - lastURL = url; - - if (headers != nullptr) - lastHeaders = *headers; - else - lastHeaders.clear(); - - if (postData != nullptr) - lastPostData = *postData; - else - lastPostData.reset(); - - blankPageShown = false; - - browser->goToURL (url, headers, postData); -} - -void WebBrowserComponent::stop() -{ - browser->stop(); -} - -void WebBrowserComponent::goBack() -{ - lastURL.clear(); - blankPageShown = false; - browser->goBack(); -} - -void WebBrowserComponent::goForward() -{ - lastURL.clear(); - browser->goForward(); -} - -void WebBrowserComponent::refresh() -{ - browser->refresh(); + return {}; } //============================================================================== -void WebBrowserComponent::paint (Graphics&) -{ -} - -void WebBrowserComponent::checkWindowAssociation() -{ - if (isShowing()) - { - reloadLastURL(); - - if (blankPageShown) - goBack(); - } - else - { - if (unloadPageWhenHidden && ! blankPageShown) - { - // when the component becomes invisible, some stuff like flash - // carries on playing audio, so we need to force it onto a blank - // page to avoid this, (and send it back when it's made visible again). - - blankPageShown = true; - browser->goToURL ("about:blank", nullptr, nullptr); - } - } -} - -void WebBrowserComponent::reloadLastURL() -{ - if (lastURL.isNotEmpty()) - { - goToURL (lastURL, &lastHeaders, &lastPostData); - lastURL.clear(); - } -} - -void WebBrowserComponent::parentHierarchyChanged() -{ - checkWindowAssociation(); -} - -void WebBrowserComponent::resized() -{ - browser->setSize (getWidth(), getHeight()); -} - -void WebBrowserComponent::visibilityChanged() -{ - checkWindowAssociation(); -} - -void WebBrowserComponent::focusGainedWithDirection (FocusChangeType, FocusChangeDirection) -{ -} - void WebBrowserComponent::clearCookies() { NSHTTPCookieStorage* storage = [NSHTTPCookieStorage sharedHTTPCookieStorage]; diff --git a/modules/juce_gui_extra/native/juce_WebBrowserComponent_windows.cpp b/modules/juce_gui_extra/native/juce_WebBrowserComponent_windows.cpp index 92fe4b3c1e..656b807670 100644 --- a/modules/juce_gui_extra/native/juce_WebBrowserComponent_windows.cpp +++ b/modules/juce_gui_extra/native/juce_WebBrowserComponent_windows.cpp @@ -35,30 +35,15 @@ namespace juce { -struct InternalWebViewType +struct WebBrowserComponent::Impl::Platform : public PlatformInterface { - InternalWebViewType() = default; - virtual ~InternalWebViewType() = default; - - virtual void createBrowser() = 0; - virtual bool hasBrowserBeenCreated() = 0; - - virtual void goToURL (const String&, const StringArray*, const MemoryBlock*) = 0; - - virtual void stop() = 0; - virtual void goBack() = 0; - virtual void goForward() = 0; - virtual void refresh() = 0; - - virtual void focusGained() {} - virtual void setWebViewSize (int, int) = 0; - - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (InternalWebViewType) + class Win32WebView; + class WebView2; }; //============================================================================== -class Win32WebView final : public InternalWebViewType, - public ActiveXControlComponent +class WebBrowserComponent::Impl::Platform::Win32WebView final : public WebBrowserComponent::Impl::PlatformInterface, + public ActiveXControlComponent { public: Win32WebView (WebBrowserComponent& parent, const String& userAgentToUse) @@ -68,6 +53,35 @@ public: owner.addAndMakeVisible (this); } + void checkWindowAssociation() override + { + if (owner.isShowing()) + { + if (! hasBrowserBeenCreated() && owner.getPeer() != nullptr) + { + createBrowser(); + owner.reloadLastURL(); + } + else + { + if (owner.blankPageShown) + goBack(); + } + } + else + { + if (owner.unloadPageWhenHidden && ! owner.blankPageShown) + { + // when the component becomes invisible, some stuff like flash + // carries on playing audio, so we need to force it onto a blank + // page to avoid this.. + + owner.blankPageShown = true; + goToURL ("about:blank", nullptr, nullptr); + } + } + } + ~Win32WebView() override { if (connectionPoint != nullptr) @@ -80,7 +94,7 @@ public: browser->Release(); } - void createBrowser() override + void createBrowser() { JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") @@ -117,13 +131,24 @@ public: JUCE_END_IGNORE_WARNINGS_GCC_LIKE } - bool hasBrowserBeenCreated() override + bool hasBrowserBeenCreated() { return browser != nullptr; } + void fallbackPaint (Graphics& webBrowserComponentContext) override + { + if (! hasBrowserBeenCreated()) + { + webBrowserComponentContext.fillAll (Colours::white); + checkWindowAssociation(); + } + } + void goToURL (const String& url, const StringArray* requestedHeaders, const MemoryBlock* postData) override { + checkWindowAssociation(); + if (browser != nullptr) { VARIANT headerFlags, frame, postDataVar, headersVar; // (_variant_t isn't available in all compilers) @@ -211,7 +236,7 @@ public: browser->Refresh(); } - void focusGained() override + void focusGainedWithDirection (FocusChangeType, FocusChangeDirection) override { JUCE_BEGIN_IGNORE_WARNINGS_GCC_LIKE ("-Wlanguage-extension-token") @@ -241,13 +266,17 @@ public: JUCE_END_IGNORE_WARNINGS_GCC_LIKE } - using ActiveXControlComponent::focusGained; - void setWebViewSize (int width, int height) override { setSize (width, height); } + void evaluateJavascript (const String&, WebBrowserComponent::EvaluationCallback) override + { + // This feature is only supported when using WebView2 + jassertfalse; + } + private: WebBrowserComponent& owner; IWebBrowser2* browser = nullptr; @@ -404,24 +433,69 @@ static bool anyChildWindow (HWND hwnd, std::function predicate) return result; } -class WebView2 final : public InternalWebViewType, - public Component, - public ComponentMovementWatcher, - private AsyncUpdater +static constexpr const char* platformSpecificIntegrationScript = R"( +window.__JUCE__ = { + postMessage: function(object) { + window.chrome.webview.postMessage(object); + }, +}; +)"; + +class WebBrowserComponent::Impl::Platform::WebView2 final : public WebBrowserComponent::Impl::PlatformInterface, + public Component, + public ComponentMovementWatcher, + private AsyncUpdater { public: - WebView2 (WebBrowserComponent& o, const WebBrowserComponent::Options& prefs) - : ComponentMovementWatcher (&o), - owner (o), - preferences (prefs.getWinWebView2BackendOptions()), - userAgent (prefs.getUserAgent()) + static std::unique_ptr tryConstruct (WebBrowserComponent& o, + const WebBrowserComponent::Options& prefs, + const StringArray& userScriptsIn) { - if (auto handle = createWebViewHandle (preferences)) - webViewHandle = std::move (*handle); - else - throw std::runtime_error ("Failed to create the CoreWebView2Environment"); + if (auto handle = createWebViewHandle (prefs)) + return rawToUniquePtr (new WebView2 (o, prefs, userScriptsIn, std::move (*handle))); - owner.addAndMakeVisible (this); + return nullptr; + } + + void checkWindowAssociation() override + { + if (owner.isShowing()) + { + if (! hasBrowserBeenCreated() && owner.getPeer() != nullptr) + { + createBrowser(); + owner.reloadLastURL(); + } + else + { + if (owner.blankPageShown) + goBack(); + } + } + else + { + if (owner.unloadPageWhenHidden && ! owner.blankPageShown) + { + // when the component becomes invisible, some stuff like flash + // carries on playing audio, so we need to force it onto a blank + // page to avoid this.. + + owner.blankPageShown = true; + goToURL ("about:blank", nullptr, nullptr); + } + } + + if (! hasBrowserBeenCreated()) + createBrowser(); + } + + void fallbackPaint (Graphics& webBrowserComponentContext) override + { + if (! hasBrowserBeenCreated()) + { + webBrowserComponentContext.fillAll (Colours::white); + checkWindowAssociation(); + } } void focusGainedWithDirection (FocusChangeType, FocusChangeDirection direction) override @@ -450,7 +524,7 @@ public: closeWebView(); } - void createBrowser() override + void createBrowser() { if (webView == nullptr) { @@ -459,7 +533,7 @@ public: } } - bool hasBrowserBeenCreated() override + bool hasBrowserBeenCreated() { return webView != nullptr || webView2ConstructionHelper.webView2BeingCreated == this @@ -468,6 +542,8 @@ public: void goToURL (const String& url, const StringArray* headers, const MemoryBlock* postData) override { + checkWindowAssociation(); + urlRequest = { url, headers != nullptr ? *headers : StringArray(), postData != nullptr && postData->getSize() > 0 ? *postData : MemoryBlock() }; @@ -517,7 +593,7 @@ public: setSize (width, height); } - void componentMovedOrResized (bool /*wasMoved*/, bool /*wasResized*/) override + void componentMovedOrResized (bool, bool) override { if (auto* peer = owner.getTopLevelComponent()->getPeer()) setControlBounds (peer->getAreaCoveredBy (owner)); @@ -549,36 +625,42 @@ public: ComSmartPtr environment; }; - static std::optional createWebViewHandle (const WebBrowserComponent::Options::WinWebView2& options) + static std::optional createWebViewHandle (const WebBrowserComponent::Options& options) { - using CreateWebViewEnvironmentWithOptionsFunc = HRESULT (*) (PCWSTR, PCWSTR, - ICoreWebView2EnvironmentOptions*, - ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*); + using CreateWebViewEnvironmentWithOptionsFunc = HRESULT (__stdcall *) (PCWSTR, PCWSTR, + ICoreWebView2EnvironmentOptions*, + ICoreWebView2CreateCoreWebView2EnvironmentCompletedHandler*); - auto dllPath = options.getDLLLocation().getFullPathName(); + auto dllPath = options.getWinWebView2BackendOptions().getDLLLocation().getFullPathName(); if (dllPath.isEmpty()) dllPath = "WebView2Loader.dll"; WebViewHandle result; - result.loaderHandle = WebViewHandle::LibraryRef (LoadLibraryA (dllPath.toUTF8()), &::FreeLibrary); + auto* createWebViewEnvironmentWithOptions = [&]() -> CreateWebViewEnvironmentWithOptionsFunc + { + #if JUCE_USE_WIN_WEBVIEW2_WITH_STATIC_LINKING + return &CreateCoreWebView2EnvironmentWithOptions; + #else + result.loaderHandle = WebViewHandle::LibraryRef (LoadLibraryA (dllPath.toUTF8()), &::FreeLibrary); - if (result.loaderHandle == nullptr) - return {}; + if (result.loaderHandle == nullptr) + return nullptr; - auto* createWebViewEnvironmentWithOptions = (CreateWebViewEnvironmentWithOptionsFunc) GetProcAddress (result.loaderHandle.get(), - "CreateCoreWebView2EnvironmentWithOptions"); - if (createWebViewEnvironmentWithOptions == nullptr) - return {}; + return (CreateWebViewEnvironmentWithOptionsFunc) GetProcAddress (result.loaderHandle.get(), + "CreateCoreWebView2EnvironmentWithOptions"); + #endif + }(); auto webViewOptions = Microsoft::WRL::Make(); - const auto userDataFolder = options.getUserDataFolder().getFullPathName(); + + const auto userDataFolder = options.getWinWebView2BackendOptions().getUserDataFolder().getFullPathName(); auto hr = createWebViewEnvironmentWithOptions (nullptr, userDataFolder.isNotEmpty() ? userDataFolder.toWideCharPointer() : nullptr, webViewOptions.Get(), - Callback( + Callback ( [&result] (HRESULT, ICoreWebView2Environment* env) -> HRESULT { result.environment = env; @@ -591,7 +673,54 @@ public: return result; } + void evaluateJavascript (const String& script, EvaluationCallback callbackIn) override + { + if (webView == nullptr) + { + scriptsWaitingForExecution.push_back ({ script, std::move (callbackIn) }); + triggerAsyncUpdate(); + return; + } + + webView->ExecuteScript (script.toUTF16(), + Callback ( + [callback = std::move (callbackIn)] (HRESULT error, PCWSTR result) -> HRESULT + { + if (callback == nullptr) + return S_OK; + + const auto callbackArg = [&] + { + using Error = WebBrowserComponent::EvaluationResult::Error; + + if (error != S_OK) + return EvaluationResult { Error { Error::Type::unknown, "Error code: " + String { error } } }; + + return EvaluationResult { JSON::fromString (StringRef { CharPointer_UTF16 { result } }) }; + }(); + + callback (callbackArg); + + return S_OK; + }).Get()); + } + private: + //============================================================================== + WebView2 (WebBrowserComponent& o, + const WebBrowserComponent::Options& prefs, + const StringArray& userScriptsIn, + WebViewHandle handle) + : ComponentMovementWatcher (&o), + owner (o), + preferences (prefs), + userAgent (prefs.getUserAgent()), + userScripts (userScriptsIn), + webViewHandle (std::move (handle)) + { + owner.addAndMakeVisible (this); + } + //============================================================================== template static String getUriStringFromArgs (ArgType* args) @@ -600,8 +729,10 @@ private: { LPWSTR uri; args->get_Uri (&uri); + String result { CharPointer_UTF16 { uri } }; + CoTaskMemFree (uri); - return uri; + return result; } return {}; @@ -677,20 +808,17 @@ private: return S_OK; }).Get(), &navigationCompletedToken); - webView->AddWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT); + webView->AddWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_ALL); webView->add_WebResourceRequested (Callback ( [this] (ICoreWebView2*, ICoreWebView2WebResourceRequestedEventArgs* args) -> HRESULT { - if (urlRequest.url.isEmpty()) - return S_OK; - ComSmartPtr request; args->get_Request (request.resetAndGetPointerAddress()); auto uriString = getUriStringFromArgs (request); - if (uriString == urlRequest.url + if (! urlRequest.url.isEmpty() && uriString == urlRequest.url || (uriString.endsWith ("/") && uriString.upToLastOccurrenceOf ("/", false, false) == urlRequest.url)) { String method ("GET"); @@ -721,8 +849,49 @@ private: urlRequest = {}; } + if (const auto resourceRequestUri = uriString.fromFirstOccurrenceOf ("https://juce.backend", false, false); + resourceRequestUri.isNotEmpty()) + { + if (auto responseData = owner.impl->handleResourceRequest (resourceRequestUri)) + { + ComSmartPtr stream (SHCreateMemStream ((BYTE*) responseData->data.data(), + (UINT) responseData->data.size())); + + StringArray headers { "Content-Type: " + responseData->mimeType }; + + if (const auto allowedOrigin = owner.impl->options.getAllowedOrigin()) + headers.add ("Access-Control-Allow-Origin: " + *allowedOrigin); + + ComSmartPtr response; + + if (webViewHandle.environment->CreateWebResourceResponse (stream, + 200, + L"OK", + headers.joinIntoString ("\n").toUTF16(), + response.resetAndGetPointerAddress()) + != S_OK) + { + return E_FAIL; + } + + args->put_Response (response); + } + } + return S_OK; }).Get(), &webResourceRequestedToken); + + webView->add_WebMessageReceived (Callback ( + [this] (ICoreWebView2*, ICoreWebView2WebMessageReceivedEventArgs* args) -> HRESULT + { + if (LPWSTR message = {}; + args->TryGetWebMessageAsString (std::addressof (message)) == S_OK) + { + owner.impl->handleNativeEvent (JSON::fromString (StringRef { CharPointer_UTF16 (message) })); + } + + return S_OK; + }).Get(), &webMessageReceivedToken); } if (webViewController != nullptr) @@ -786,6 +955,9 @@ private: webView->RemoveWebResourceRequestedFilter (L"*", COREWEBVIEW2_WEB_RESOURCE_CONTEXT_DOCUMENT); webView->remove_WebResourceRequested (webResourceRequestedToken); } + + if (webMessageReceivedToken.value != 0) + webView->remove_WebMessageReceived (webMessageReceivedToken); } if (webViewController != nullptr) @@ -802,7 +974,7 @@ private: if (controller2 != nullptr) { - const auto bgColour = preferences.getBackgroundColour(); + const auto bgColour = preferences.getWinWebView2BackendOptions().getBackgroundColour(); controller2->put_DefaultBackgroundColor ({ (BYTE) bgColour.getAlpha(), (BYTE) bgColour.getRed(), @@ -815,8 +987,12 @@ private: if (settings != nullptr) { - settings->put_IsStatusBarEnabled (! preferences.getIsStatusBarDisabled()); - settings->put_IsBuiltInErrorPageEnabled (! preferences.getIsBuiltInErrorPageDisabled()); + #if ! JUCE_DEBUG + settings->put_AreDevToolsEnabled (false); + #endif + + settings->put_IsStatusBarEnabled (! preferences.getWinWebView2BackendOptions().getIsStatusBarDisabled()); + settings->put_IsBuiltInErrorPageEnabled (! preferences.getWinWebView2BackendOptions().getIsBuiltInErrorPageDisabled()); if (userAgent.isNotEmpty()) { @@ -862,6 +1038,22 @@ private: weakThis->webViewController = controller; controller->get_CoreWebView2 (weakThis->webView.resetAndGetPointerAddress()); + auto allUserScripts = weakThis->userScripts; + allUserScripts.insert (0, platformSpecificIntegrationScript); + + for (const auto& script : allUserScripts) + { + weakThis->webView->AddScriptToExecuteOnDocumentCreated (script.toUTF16(), + Callback ( + [] (HRESULT error, PCWSTR) -> HRESULT + { + if (error != S_OK) + jassertfalse; + + return S_OK; + }).Get()); + } + if (weakThis->webView != nullptr) { if (UINT32 browserProcessId; @@ -936,7 +1128,24 @@ private: //============================================================================== void handleAsyncUpdate() override { - createWebView(); + if (webView == nullptr && ! webViewBeingCreated) + { + webViewBeingCreated = true; + createWebView(); + } + + if (webView == nullptr && ! scriptsWaitingForExecution.empty()) + { + triggerAsyncUpdate(); + return; + } + + while (! scriptsWaitingForExecution.empty()) + { + auto [script, callback] = scriptsWaitingForExecution.front(); + scriptsWaitingForExecution.pop_front(); + evaluateJavascript (script, std::move (callback)); + } } //============================================================================== @@ -962,19 +1171,22 @@ private: //============================================================================== WebBrowserComponent& owner; - WebBrowserComponent::Options::WinWebView2 preferences; + WebBrowserComponent::Options preferences; String userAgent; + StringArray userScripts; WebViewHandle webViewHandle; ComSmartPtr webViewController; ComSmartPtr webView; + bool webViewBeingCreated = false; EventRegistrationToken navigationStartingToken { 0 }, newWindowRequestedToken { 0 }, windowCloseRequestedToken { 0 }, navigationCompletedToken { 0 }, webResourceRequestedToken { 0 }, - moveFocusRequestedToken { 0 }; + moveFocusRequestedToken { 0 }, + webMessageReceivedToken { 0 }; bool inMoveFocusRequested = false; @@ -995,6 +1207,7 @@ private: }; inline static WebView2ConstructionHelper webView2ConstructionHelper; + std::deque> scriptsWaitingForExecution; NativeScaleFactorNotifier scaleFactorNotifier { this, [this] (auto) @@ -1010,176 +1223,6 @@ private: #endif //============================================================================== -class WebBrowserComponent::Pimpl -{ -public: - Pimpl (WebBrowserComponent& owner, - [[maybe_unused]] const Options& preferences, - bool useWebView2, - const String& userAgent) - { - if (useWebView2) - { - #if JUCE_USE_WIN_WEBVIEW2 - try - { - internal.reset (new WebView2 (owner, preferences)); - } - catch (const std::runtime_error&) {} - #endif - } - - if (internal == nullptr) - internal.reset (new Win32WebView (owner, userAgent)); - } - - InternalWebViewType& getInternalWebView() - { - return *internal; - } - -private: - std::unique_ptr internal; -}; - -//============================================================================== -WebBrowserComponent::WebBrowserComponent (const Options& options) - : browser (new Pimpl (*this, options, - options.getBackend() == Options::Backend::webview2, options.getUserAgent())), - unloadPageWhenHidden (! options.keepsPageLoadedWhenBrowserIsHidden()) -{ - setOpaque (true); -} - -WebBrowserComponent::~WebBrowserComponent() -{ -} - -//============================================================================== -void WebBrowserComponent::goToURL (const String& url, - const StringArray* headers, - const MemoryBlock* postData) -{ - lastURL = url; - - if (headers != nullptr) - lastHeaders = *headers; - else - lastHeaders.clear(); - - if (postData != nullptr) - lastPostData = *postData; - else - lastPostData.reset(); - - blankPageShown = false; - - if (! browser->getInternalWebView().hasBrowserBeenCreated()) - checkWindowAssociation(); - - browser->getInternalWebView().goToURL (url, headers, postData); -} - -void WebBrowserComponent::stop() -{ - browser->getInternalWebView().stop(); -} - -void WebBrowserComponent::goBack() -{ - lastURL.clear(); - blankPageShown = false; - - browser->getInternalWebView().goBack(); -} - -void WebBrowserComponent::goForward() -{ - lastURL.clear(); - - browser->getInternalWebView().goForward(); -} - -void WebBrowserComponent::refresh() -{ - browser->getInternalWebView().refresh(); -} - -//============================================================================== -void WebBrowserComponent::paint (Graphics& g) -{ - if (! browser->getInternalWebView().hasBrowserBeenCreated()) - { - g.fillAll (Colours::white); - checkWindowAssociation(); - } -} - -void WebBrowserComponent::checkWindowAssociation() -{ - if (isShowing()) - { - if (! browser->getInternalWebView().hasBrowserBeenCreated() && getPeer() != nullptr) - { - browser->getInternalWebView().createBrowser(); - reloadLastURL(); - } - else - { - if (blankPageShown) - goBack(); - } - } - else - { - if (browser != nullptr && unloadPageWhenHidden && ! blankPageShown) - { - // when the component becomes invisible, some stuff like flash - // carries on playing audio, so we need to force it onto a blank - // page to avoid this.. - - blankPageShown = true; - browser->getInternalWebView().goToURL ("about:blank", nullptr, nullptr); - } - } -} - -void WebBrowserComponent::reloadLastURL() -{ - if (lastURL.isNotEmpty()) - { - goToURL (lastURL, &lastHeaders, &lastPostData); - lastURL.clear(); - } -} - -void WebBrowserComponent::parentHierarchyChanged() -{ - checkWindowAssociation(); -} - -void WebBrowserComponent::resized() -{ - browser->getInternalWebView().setWebViewSize (getWidth(), getHeight()); -} - -void WebBrowserComponent::visibilityChanged() -{ - checkWindowAssociation(); -} - -void WebBrowserComponent::focusGainedWithDirection (FocusChangeType type, FocusChangeDirection dir) -{ - ignoreUnused (type, dir); - - #if JUCE_USE_WIN_WEBVIEW2 - if (auto* webView2 = dynamic_cast (&browser->getInternalWebView())) - webView2->focusGainedWithDirection (type, dir); - else - #endif - browser->getInternalWebView().focusGained(); -} - void WebBrowserComponent::clearCookies() { HeapBlock<::INTERNET_CACHE_ENTRY_INFOA> entry; @@ -1226,11 +1269,27 @@ bool WebBrowserComponent::areOptionsSupported (const Options& options) if (options.getBackend() != Options::Backend::webview2) return false; - if (auto webView = WebView2::createWebViewHandle (options.getWinWebView2BackendOptions())) + if (auto webView = WebBrowserComponent::Impl::Platform::WebView2::createWebViewHandle (options)) return true; #endif return false; } +auto WebBrowserComponent::Impl::createAndInitPlatformDependentPart (WebBrowserComponent::Impl& impl, + const WebBrowserComponent::Options& options, + [[maybe_unused]] const StringArray& userScripts) + -> std::unique_ptr +{ + if (options.getBackend() == WebBrowserComponent::Options::Backend::webview2) + { + #if JUCE_USE_WIN_WEBVIEW2 + if (auto constructed = Platform::WebView2::tryConstruct (impl.owner, options, userScripts)) + return constructed; + #endif + } + + return std::make_unique (impl.owner, options.getUserAgent()); +} + } // namespace juce