chore: add mobile app

This commit is contained in:
2026-02-07 22:33:02 +01:00
parent db34640acc
commit e89384ad4e
29327 changed files with 3886944 additions and 12 deletions
@@ -0,0 +1,16 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup Label="UserMacros" />
<!--
To customize common C++/WinRT project properties:
* right-click the project node
* expand the Common Properties item
* select the C++/WinRT property page
For more advanced scenarios, and complete documentation, please see:
https://github.com/Microsoft/cppwinrt/tree/master/nuget
-->
<PropertyGroup />
<ItemDefinitionGroup />
</Project>
@@ -0,0 +1,3 @@
EXPORTS
DllCanUnloadNow = WINRT_CanUnloadNow PRIVATE
DllGetActivationFactory = WINRT_GetActivationFactory PRIVATE
@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<Filter Include="Resources">
<UniqueIdentifier>accd3aa8-1ba0-4223-9bbe-0c431709210b</UniqueIdentifier>
<Extensions>rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tga;tiff;tif;png;wav;mfcribbon-ms</Extensions>
</Filter>
<Filter Include="Generated Files">
<UniqueIdentifier>{926ab91d-31b4-48c3-b9a4-e681349f27f0}</UniqueIdentifier>
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="pch.cpp" />
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="ReactPackageProvider.cpp" />
<ClCompile Include="WebViewManager.cpp" />
</ItemGroup>
<ItemGroup>
<ClInclude Include="pch.h" />
<ClInclude Include="ReactPackageProvider.h" />
<ClInclude Include="WebViewManager.h" />
</ItemGroup>
<ItemGroup>
<None Include="ReactNativeWebView.def" />
<None Include="packages.config" />
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
</ItemGroup>
<ItemGroup>
<Midl Include="ReactPackageProvider.idl" />
</ItemGroup>
</Project>
@@ -0,0 +1,189 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(SolutionDir)\ExperimentalFeatures.props" Condition="Exists('$(SolutionDir)\ExperimentalFeatures.props')" />
<Import Project="$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props" Condition="Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.props')" />
<PropertyGroup Label="Globals">
<CppWinRTOptimized>true</CppWinRTOptimized>
<CppWinRTRootNamespaceAutoMerge>true</CppWinRTRootNamespaceAutoMerge>
<MinimalCoreWin>true</MinimalCoreWin>
<ProjectGuid>{00AA3765-C6A0-4713-B3F9-BFE47B9C83F5}</ProjectGuid>
<ProjectName>ReactNativeWebView</ProjectName>
<RootNamespace>ReactNativeWebView</RootNamespace>
<DefaultLanguage>en-US</DefaultLanguage>
<MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
<AppContainerApplication>true</AppContainerApplication>
<ApplicationType>Windows Store</ApplicationType>
<ApplicationTypeRevision>10.0</ApplicationTypeRevision>
<WindowsTargetPlatformVersion Condition=" '$(WindowsTargetPlatformVersion)' == '' ">10.0</WindowsTargetPlatformVersion>
<WindowsTargetPlatformMinVersion>10.0.17763.0</WindowsTargetPlatformMinVersion>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|Win32">
<Configuration>Debug</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|Win32">
<Configuration>Release</Configuration>
<Platform>Win32</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup Label="Configuration">
<ConfigurationType>DynamicLibrary</ConfigurationType>
<PlatformToolset>v140</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '15.0'">v141</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '16.0'">v142</PlatformToolset>
<PlatformToolset Condition="'$(VisualStudioVersion)' == '17.0'">v143</PlatformToolset>
<CharacterSet>Unicode</CharacterSet>
<GenerateManifest>false</GenerateManifest>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Debug'" Label="Configuration">
<UseDebugLibraries>true</UseDebugLibraries>
<LinkIncremental>true</LinkIncremental>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)'=='Release'" Label="Configuration">
<UseDebugLibraries>false</UseDebugLibraries>
<WholeProgramOptimization>true</WholeProgramOptimization>
<LinkIncremental>false</LinkIncremental>
</PropertyGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
<ImportGroup Label="ExtensionSettings">
</ImportGroup>
<PropertyGroup>
<ReactNativeWindowsDir Condition="'$(ReactNativeWindowsDir)' == ''">$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), 'node_modules\react-native-windows\package.json'))\node_modules\react-native-windows\</ReactNativeWindowsDir>
</PropertyGroup>
<ImportGroup Label="Shared">
<Import Project="$(ReactNativeWindowsDir)\Microsoft.ReactNative.Cxx\Microsoft.ReactNative.Cxx.vcxitems" Label="Shared" />
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
</ImportGroup>
<ImportGroup Label="PropertySheets">
<Import Project="PropertySheet.props" />
</ImportGroup>
<ImportGroup Label="ReactNativeWindowsPropertySheets">
<Import Project="$(ReactNativeWindowsDir)\PropertySheets\external\Microsoft.ReactNative.Uwp.CppLib.props" Condition="Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.props')" />
<Import Project="$(SolutionDir)\packages\$(WinUIPackageProps)" Condition="'$(WinUIPackageProps)'!='' And Exists('$(SolutionDir)\packages\$(WinUIPackageProps)')" />
</ImportGroup>
<PropertyGroup>
<WinUI2_HasWebView2 Condition="$([MSBuild]::VersionGreaterThanOrEquals('$(WinUIPackageVersion)','2.8.0-prerelease.210927001'))">true</WinUI2_HasWebView2>
</PropertyGroup>
<PropertyGroup />
<ItemDefinitionGroup>
<ClCompile>
<PrecompiledHeader>Use</PrecompiledHeader>
<PrecompiledHeaderFile>pch.h</PrecompiledHeaderFile>
<PrecompiledHeaderOutputFile>$(IntDir)pch.pch</PrecompiledHeaderOutputFile>
<WarningLevel>Level4</WarningLevel>
<AdditionalOptions>%(AdditionalOptions) /bigobj</AdditionalOptions>
<!--Temporarily disable cppwinrt heap enforcement to work around xaml compiler generated std::shared_ptr use -->
<AdditionalOptions Condition="'$(CppWinRTHeapEnforcement)'==''">/DWINRT_NO_MAKE_DETECTION %(AdditionalOptions)</AdditionalOptions>
<DisableSpecificWarnings>28204</DisableSpecificWarnings>
<PreprocessorDefinitions>_WINRT_DLL;%(PreprocessorDefinitions)</PreprocessorDefinitions>
<AdditionalUsingDirectories>$(WindowsSDK_WindowsMetadata);$(AdditionalUsingDirectories)</AdditionalUsingDirectories>
<LanguageStandard>stdcpp17</LanguageStandard>
</ClCompile>
<Link>
<SubSystem>Console</SubSystem>
<GenerateWindowsMetadata>true</GenerateWindowsMetadata>
<ModuleDefinitionFile>ReactNativeWebView.def</ModuleDefinitionFile>
</Link>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Debug'">
<ClCompile>
<PreprocessorDefinitions>_DEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(Configuration)'=='Release'">
<ClCompile>
<PreprocessorDefinitions>NDEBUG;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
</ItemDefinitionGroup>
<ItemDefinitionGroup Condition="'$(WinUI2_HasWebView2)'=='true'">
<ClCompile>
<PreprocessorDefinitions>WINUI2_HAS_WEBVIEW2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</ClCompile>
<Midl>
<PreprocessorDefinitions>WINUI2_HAS_WEBVIEW2;%(PreprocessorDefinitions)</PreprocessorDefinitions>
</Midl>
</ItemDefinitionGroup>
<ItemGroup>
<ClInclude Include="ReactWebView.h" />
<ClInclude Include="ReactWebView2.h" />
<ClInclude Include="ReactWebView2Manager.h" />
<ClInclude Include="pch.h" />
<ClInclude Include="ReactPackageProvider.h">
<DependentUpon>ReactPackageProvider.idl</DependentUpon>
</ClInclude>
<ClInclude Include="ReactWebViewHelpers.h" />
<ClInclude Include="ReactWebViewManager.h" />
<ClInclude Include="WebBridge.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="ReactWebView.cpp" />
<ClCompile Include="ReactWebView2.cpp" />
<ClCompile Include="ReactWebView2Manager.cpp" />
<ClCompile Include="pch.cpp">
<PrecompiledHeader>Create</PrecompiledHeader>
</ClCompile>
<ClCompile Include="ReactPackageProvider.cpp">
<DependentUpon>ReactPackageProvider.idl</DependentUpon>
</ClCompile>
<ClCompile Include="$(GeneratedFilesDir)module.g.cpp" />
<ClCompile Include="ReactWebViewHelpers.cpp" />
<ClCompile Include="ReactWebViewManager.cpp" />
<ClCompile Include="WebBridge.cpp" />
</ItemGroup>
<ItemGroup>
<Midl Include="ReactWebView.idl" />
<Midl Include="ReactPackageProvider.idl" />
<Midl Include="WebBridge.idl" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="ReactNativeWebView.def" />
</ItemGroup>
<ItemGroup>
<None Include="PropertySheet.props" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="$(ReactNativeWindowsDir)\Microsoft.ReactNative\Microsoft.ReactNative.vcxproj">
<Project>{f7d32bd0-2749-483e-9a0d-1635ef7e3136}</Project>
<Private>false</Private>
</ProjectReference>
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
<ImportGroup Label="ReactNativeWindowsTargets">
<Import Project="$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.targets" Condition="Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.targets')" />
</ImportGroup>
<Target Name="EnsureReactNativeWindowsTargets" BeforeTargets="PrepareForBuild">
<PropertyGroup>
<ErrorText>This project references targets in your node_modules\react-native-windows folder that are missing. The missing file is {0}.</ErrorText>
</PropertyGroup>
<Error Condition="!Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.props')" Text="$([System.String]::Format('$(ErrorText)', '$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.props'))" />
<Error Condition="!Exists('$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.targets')" Text="$([System.String]::Format('$(ErrorText)', '$(ReactNativeWindowsDir)\PropertySheets\External\Microsoft.ReactNative.Uwp.CppLib.targets'))" />
</Target>
<ImportGroup Label="ExtensionTargets">
<Import Project="$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets" Condition="Exists('$(SolutionDir)packages\Microsoft.Windows.CppWinRT.2.0.200316.3\build\native\Microsoft.Windows.CppWinRT.targets')" />
<Import Project="$(SolutionDir)packages\$(WinUIPackageName).$(WinUIPackageVersion)\build\native\$(WinUIPackageName).targets" Condition="Exists('$(SolutionDir)packages\$(WinUIPackageName).$(WinUIPackageVersion)\build\native\$(WinUIPackageName).targets')" />
<Import Project="$(SolutionDir)\packages\Microsoft.ReactNative.$(ReactNativeWindowsVersion)\build\native\Microsoft.ReactNative.targets" Condition="'$(UseExperimentalNuget)'=='true' And Exists('$(SolutionDir)\packages\Microsoft.ReactNative.Cxx.$(ReactNativeWindowsVersion)\build\native\Microsoft.ReactNative.Cxx.targets')" />
<Import Project="$(SolutionDir)\packages\Microsoft.ReactNative.Cxx.$(ReactNativeWindowsVersion)\build\native\Microsoft.ReactNative.Cxx.targets" Condition="'$(UseExperimentalNuget)'=='true' And Exists('$(SolutionDir)\packages\Microsoft.ReactNative.Cxx.$(ReactNativeWindowsVersion)\build\native\Microsoft.ReactNative.Cxx.targets')" />
</ImportGroup>
<Target Name="Deploy" />
</Project>
@@ -0,0 +1,26 @@
#include "pch.h"
#include "ReactPackageProvider.h"
#if __has_include("ReactPackageProvider.g.cpp")
#include "ReactPackageProvider.g.cpp"
#endif
#include "ReactWebView2Manager.h"
#ifndef USE_WINUI3
#include "ReactWebViewManager.h"
#endif
using namespace winrt::Microsoft::ReactNative;
namespace winrt::ReactNativeWebView::implementation {
void ReactPackageProvider::CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept {
#ifndef USE_WINUI3
packageBuilder.AddViewManager(L"ReactWebViewManager", []() { return winrt::make<ReactWebViewManager>(); });
#endif
#if HAS_WEBVIEW2
packageBuilder.AddViewManager(L"ReactWebView2Manager", []() { return winrt::make<ReactWebView2Manager>(); });
#endif
}
} // namespace winrt::ReactNativeWebView::implementation
@@ -0,0 +1,20 @@
#pragma once
#include "ReactPackageProvider.g.h"
using namespace winrt::Microsoft::ReactNative;
namespace winrt::ReactNativeWebView::implementation {
struct ReactPackageProvider : ReactPackageProviderT<ReactPackageProvider> {
ReactPackageProvider() = default;
void CreatePackage(IReactPackageBuilder const &packageBuilder) noexcept;
};
} // namespace winrt::ReactNativeWebView::implementation
namespace winrt::ReactNativeWebView::factory_implementation {
struct ReactPackageProvider : ReactPackageProviderT<ReactPackageProvider, implementation::ReactPackageProvider> {};
} // namespace winrt::ReactNativeWebView::factory_implementation
@@ -0,0 +1,7 @@
namespace ReactNativeWebView {
[webhosthidden]
[default_interface]
runtimeclass ReactPackageProvider : Microsoft.ReactNative.IReactPackageProvider {
ReactPackageProvider();
};
} // namespace ReactNativeWebView
@@ -0,0 +1,242 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "pch.h"
#ifndef USE_WINUI3
#include "JSValueXaml.h"
#include "ReactWebView.h"
#include "ReactWebView.g.cpp"
#include <winrt/Windows.Foundation.Metadata.h>
#include <optional>
namespace winrt {
using namespace Microsoft::ReactNative;
using namespace Windows::Data::Json;
using namespace Windows::Foundation;
using namespace Windows::UI;
using namespace Windows::UI::Popups;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::UI::Xaml::Input;
using namespace Windows::UI::Xaml::Media;
} // namespace winrt
namespace winrt::ReactNativeWebView::implementation {
ReactWebView::ReactWebView(winrt::IReactContext const& reactContext) : m_reactContext(reactContext) {
m_webView = winrt::WebView();
this->Content(m_webView);
RegisterEvents();
}
ReactWebView::~ReactWebView()
{
if (m_messagingEnabled) {
m_webBridge.MessagePostEvent(m_messageToken);
}
}
void ReactWebView::RegisterEvents() {
m_navigationStartingRevoker = m_webView.NavigationStarting(
winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) {
if (auto self = ref.get()) {
self->OnNavigationStarting(sender, args);
}
});
m_navigationCompletedRevoker = m_webView.NavigationCompleted(
winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) {
if (auto self = ref.get()) {
self->OnNavigationCompleted(sender, args);
}
});
m_navigationFailedRevoker = m_webView.NavigationFailed(
winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) {
if (auto self = ref.get()) {
self->OnNavigationFailed(sender, args);
}
});
m_domContentLoadedRevoker = m_webView.DOMContentLoaded(
winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) {
if (auto self = ref.get())
{
self->OnDOMContentLoaded(sender, args);
}
});
}
bool ReactWebView::Is17763OrHigher() {
static std::optional<bool> hasUniversalAPIContract_v7;
if (!hasUniversalAPIContract_v7.has_value()) {
hasUniversalAPIContract_v7 = winrt::Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 7);
}
return hasUniversalAPIContract_v7.value();
}
void ReactWebView::WriteWebViewNavigationEventArg(winrt::WebView const& sender, winrt::IJSValueWriter const& eventDataWriter) {
auto tag = this->GetValue(winrt::FrameworkElement::TagProperty()).as<winrt::IPropertyValue>().GetInt64();
WriteProperty(eventDataWriter, L"canGoBack", sender.CanGoBack());
WriteProperty(eventDataWriter, L"canGoForward", sender.CanGoForward());
if (Is17763OrHigher()) {
WriteProperty(eventDataWriter, L"loading", !sender.IsLoaded());
}
WriteProperty(eventDataWriter, L"target", tag);
WriteProperty(eventDataWriter, L"title", sender.DocumentTitle());
if (auto uri = sender.Source()) {
WriteProperty(eventDataWriter, L"url", uri.AbsoluteCanonicalUri());
}
}
void ReactWebView::OnNavigationStarting(winrt::WebView const& webView, winrt::WebViewNavigationStartingEventArgs const& /*args*/) {
m_reactContext.DispatchEvent(
*this,
L"topLoadingStart",
[&](winrt::IJSValueWriter const& eventDataWriter) noexcept {
eventDataWriter.WriteObjectBegin();
WriteWebViewNavigationEventArg(webView, eventDataWriter);
eventDataWriter.WriteObjectEnd();
});
if (m_messagingEnabled) {
m_webBridge = WebBridge();
m_messageToken = m_webBridge.MessagePostEvent([this](const auto&, hstring const& message)
{
this->OnMessagePosted(message);
});
webView.AddWebAllowedObject(L"__REACT_WEB_VIEW_BRIDGE", m_webBridge);
}
}
void ReactWebView::OnMessagePosted(hstring const& message)
{
HandleMessageFromJS(message);
}
void ReactWebView::OnNavigationCompleted(winrt::WebView const& webView, winrt::WebViewNavigationCompletedEventArgs const& /*args*/) {
m_reactContext.DispatchEvent(
*this,
L"topLoadingFinish",
[&](winrt::IJSValueWriter const& eventDataWriter) noexcept {
eventDataWriter.WriteObjectBegin();
WriteWebViewNavigationEventArg(webView, eventDataWriter);
eventDataWriter.WriteObjectEnd();
});
if (m_messagingEnabled) {
winrt::hstring windowAlert =
L"window.alert = function (msg) "
L"{__REACT_WEB_VIEW_BRIDGE.postMessage(`{\"type\":\"__alert\",\"message\":\"${msg}\"}`)};";
winrt::hstring postMessage =
L"window.postMessage = function (data) "
L"{__REACT_WEB_VIEW_BRIDGE.postMessage(typeof data == 'string' ? data : JSON.stringify(data))};";
winrt::hstring reactNativeWebviewPostMessage =
L"window.ReactNativeWebView = {postMessage: function (data) "
L"{__REACT_WEB_VIEW_BRIDGE.postMessage(typeof data == 'string' ? data : JSON.stringify(data))}};";
webView.InvokeScriptAsync(L"eval", {windowAlert + postMessage + reactNativeWebviewPostMessage});
}
}
void ReactWebView::OnNavigationFailed(winrt::IInspectable const& /*sender*/, winrt::WebViewNavigationFailedEventArgs const& args) {
m_reactContext.DispatchEvent(
*this,
L"topLoadingError",
[&](winrt::IJSValueWriter const& eventDataWriter) noexcept {
auto httpCode = static_cast<int32_t>(args.WebErrorStatus());
eventDataWriter.WriteObjectBegin();
{
WriteProperty(eventDataWriter, L"code", httpCode);
WriteWebViewNavigationEventArg(m_webView, eventDataWriter);
}
eventDataWriter.WriteObjectEnd();
});
}
void ReactWebView::HandleMessageFromJS(winrt::hstring const& message) {
winrt::JsonObject jsonObject;
if (winrt::JsonObject::TryParse(message, jsonObject)) {
if (auto v = jsonObject.Lookup(L"type"); v && v.ValueType() == JsonValueType::String) {
auto type = v.GetString();
if (type == L"__alert") {
auto dialog = winrt::MessageDialog(jsonObject.GetNamedString(L"message"));
dialog.Commands().Append(winrt::UICommand(L"OK"));
dialog.ShowAsync();
return;
}
}
}
m_reactContext.DispatchEvent(
*this,
L"topMessage",
[&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept {
eventDataWriter.WriteObjectBegin();
{
WriteProperty(eventDataWriter, L"data", message);
}
eventDataWriter.WriteObjectEnd();
});
}
void ReactWebView::PostMessage(winrt::hstring const& message) {
if (m_messagingEnabled) {
HandleMessageFromJS(message);
}
}
void ReactWebView::OnDOMContentLoaded(winrt::WebView const& webView, winrt::WebViewDOMContentLoadedEventArgs const& /*args*/)
{
m_reactContext.DispatchEvent(
*this,
L"topDOMContentLoaded",
[&](winrt::IJSValueWriter const& eventDataWriter) noexcept
{
eventDataWriter.WriteObjectBegin();
WriteWebViewNavigationEventArg(webView, eventDataWriter);
eventDataWriter.WriteObjectEnd();
});
// Polyfill for missing globalThis:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/globalThis
webView.InvokeScriptAsync(
L"eval", {L"if(typeof globalThis === 'undefined') { var globalThis = Function('return this')(); }"});
// Polyfill for missing BigInt:
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/BigInt
webView.InvokeScriptAsync(L"eval", {L"window.BigInt = function (data) { return Number(parseFloat(data)); }"});
// Polyfill for missing DOMRect: https://developer.mozilla.org/en-US/docs/Web/API/DOMRect
webView.InvokeScriptAsync(
L"eval",
{L"window.DOMRect = function (x = 0, y = 0, width = 0, height = 0) { return Object.assign({ x: x, y: y, "
L"height: height, width: width, top: ((y > 0) ? y : (y + height)), right: ((x > 0) ? (x + width) : x), "
L"bottom: ((y > 0) ? (y + height) : y), left: ((x > 0) ? x : (x + width)) }); }"});
if (!m_injectedJavascript.empty())
{
webView.InvokeScriptAsync(L"eval", {m_injectedJavascript});
}
}
void ReactWebView::SetInjectedJavascript(winrt::hstring const& payload)
{
m_injectedJavascript = payload;
}
void ReactWebView::RequestFocus()
{
FocusManager::TryFocusAsync(m_webView, FocusState::Programmatic);
}
void ReactWebView::MessagingEnabled(bool enabled) noexcept{
m_messagingEnabled = enabled;
}
bool ReactWebView::MessagingEnabled() const noexcept{
return m_messagingEnabled;
}
} // namespace winrt::ReactNativeWebView::implementation
#endif // USE_WINUI3
@@ -0,0 +1,56 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#ifndef USE_WINUI3
#include "winrt/Microsoft.ReactNative.h"
#include "NativeModules.h"
#include "ReactWebView.g.h"
#include "winrt/ReactNativeWebView.h"
using namespace winrt::ReactNativeWebView;
namespace winrt::ReactNativeWebView::implementation {
class ReactWebView : public ReactWebViewT<ReactWebView> {
public:
ReactWebView(Microsoft::ReactNative::IReactContext const& reactContext);
void MessagingEnabled(bool enabled) noexcept;
bool MessagingEnabled() const noexcept;
void SetInjectedJavascript(winrt::hstring const& payload);
void RequestFocus();
void PostMessage(winrt::hstring const& message);
~ReactWebView();
private:
bool m_messagingEnabled{ true };
winrt::Windows::UI::Xaml::Controls::WebView m_webView{ nullptr };
Microsoft::ReactNative::IReactContext m_reactContext{ nullptr };
WebBridge m_webBridge{ nullptr };
winrt::event_token m_messageToken;
winrt::hstring m_injectedJavascript;
winrt::Windows::UI::Xaml::Controls::WebView::NavigationStarting_revoker m_navigationStartingRevoker{};
winrt::Windows::UI::Xaml::Controls::WebView::NavigationCompleted_revoker m_navigationCompletedRevoker{};
winrt::Windows::UI::Xaml::Controls::WebView::NavigationFailed_revoker m_navigationFailedRevoker{};
winrt::Windows::UI::Xaml::Controls::WebView::DOMContentLoaded_revoker m_domContentLoadedRevoker{};
void HandleMessageFromJS(winrt::hstring const& message);
void RegisterEvents();
void WriteWebViewNavigationEventArg(winrt::Windows::UI::Xaml::Controls::WebView const& sender, winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter);
void OnDOMContentLoaded(winrt::Windows::UI::Xaml::Controls::WebView const& sender, winrt::Windows::UI::Xaml::Controls::WebViewDOMContentLoadedEventArgs const& args);
void OnNavigationStarting(winrt::Windows::UI::Xaml::Controls::WebView const& sender, winrt::Windows::UI::Xaml::Controls::WebViewNavigationStartingEventArgs const& args);
void OnNavigationCompleted(winrt::Windows::UI::Xaml::Controls::WebView const& sender, winrt::Windows::UI::Xaml::Controls::WebViewNavigationCompletedEventArgs const& args);
void OnNavigationFailed(winrt::Windows::Foundation::IInspectable const& sender, winrt::Windows::UI::Xaml::Controls::WebViewNavigationFailedEventArgs const& args);
void OnMessagePosted(hstring const& message);
bool Is17763OrHigher();
};
} // namespace winrt::ReactNativeWebView::implementation
namespace winrt::ReactNativeWebView::factory_implementation {
struct ReactWebView : ReactWebViewT<ReactWebView, implementation::ReactWebView> {};
} // namespace winrt::ReactNativeWebView::factory_implementation
#endif // USE_WINUI3
@@ -0,0 +1,31 @@
#include <NamespaceRedirect.h>
namespace ReactNativeWebView{
#if !defined(USE_WINUI3)
[default_interface]
runtimeclass ReactWebView : Windows.UI.Xaml.Controls.ContentPresenter{
ReactWebView(Microsoft.ReactNative.IReactContext context);
Boolean MessagingEnabled;
void SetInjectedJavascript(String payload);
void RequestFocus();
void PostMessage(String message);
};
#endif
#if __has_include(<VersionMacros.h>)
#include <VersionMacros.h>
#else
#define RNW_VERSION_AT_LEAST(x,y,z) false
#endif
#if RNW_VERSION_AT_LEAST(0,68,0) && (defined(WINUI2_HAS_WEBVIEW2) || defined(USE_WINUI3))
[default_interface]
runtimeclass ReactWebView2 : XAML_NAMESPACE.Controls.ContentPresenter{
ReactWebView2(Microsoft.ReactNative.IReactContext context);
void NavigateToHtml(String html);
void NavigateWithWebResourceRequest(Microsoft.ReactNative.IJSValueReader request);
String InjectedJavascript;
Boolean MessagingEnabled;
Boolean LinkHandlingEnabled;
};
#endif
} // namespace ReactNativeWebView
@@ -0,0 +1,473 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "pch.h"
#include "ReactWebView2.h"
#include "ReactWebViewHelpers.h"
#if HAS_WEBVIEW2
#include "JSValueXaml.h"
#include "ReactWebView2.g.cpp"
#include <winrt/Windows.Foundation.Metadata.h>
#include <winrt/Windows.System.h>
#include <optional>
namespace mux {
using namespace winrt::Microsoft::UI::Xaml::Controls;
}
namespace winrt {
using namespace Microsoft::ReactNative;
using namespace Windows::Foundation;
using namespace Microsoft::Web::WebView2::Core;
using namespace Windows::Data::Json;
using namespace Windows::UI::Popups;
using namespace Windows::Web::Http;
using namespace Windows::Storage::Streams;
using namespace Windows::Security::Cryptography;
using namespace xaml;
using namespace xaml::Controls;
using namespace xaml::Input;
using namespace xaml::Media;
} // namespace winrt
namespace winrt::ReactNativeWebView::implementation {
ReactWebView2::ReactWebView2(winrt::IReactContext const& reactContext) : m_reactContext(reactContext) {
m_webView = mux::WebView2();
this->Content(m_webView);
RegisterEvents();
}
ReactWebView2::~ReactWebView2() {}
void ReactWebView2::RegisterEvents() {
m_navigationStartingRevoker = m_webView.NavigationStarting(
winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) {
if (auto self = ref.get()) {
self->OnNavigationStarting(sender, args);
}
});
m_navigationCompletedRevoker = m_webView.NavigationCompleted(
winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args) {
if (auto self = ref.get()) {
self->OnNavigationCompleted(sender, args);
}
});
m_CoreWebView2InitializedRevoker = m_webView.CoreWebView2Initialized(
winrt::auto_revoke, [ref = get_weak()](auto const& sender, auto const& args){
if (auto self = ref.get()) {
self->OnCoreWebView2Initialized(sender, args);
}
});
}
void ReactWebView2::RegisterCoreWebView2Events()
{
// We need to wait for the CoreWebView component to be initialized before registering its event listeners
assert(m_webView.CoreWebView2());
m_webResourceRequestedRevoker = m_webView.CoreWebView2().WebResourceRequested(
winrt::auto_revoke,
[ref = get_weak()](auto const& sender, auto const& args)
{
if (auto self = ref.get())
{
self->OnCoreWebView2ResourceRequseted(sender, args);
}
});
m_CoreWebView2DOMContentLoadedRevoker = m_webView.CoreWebView2().DOMContentLoaded(
winrt::auto_revoke,
[ref = get_weak()](auto const& sender, auto const& args)
{
if (auto self = ref.get())
{
self->OnCoreWebView2DOMContentLoaded(sender, args);
}
});
m_frameNavigationStartingRevoker = m_webView.CoreWebView2().FrameNavigationStarting(
winrt::auto_revoke,
[ref = get_weak()](auto const& sender, auto const& args)
{
if (auto self = ref.get())
{
self->OnCoreWebView2FrameNavigationStarted(sender, args);
}
});
m_frameNavigationCompletedRevoker = m_webView.CoreWebView2().FrameNavigationCompleted(
winrt::auto_revoke,
[ref = get_weak()](auto const& sender, auto const& args)
{
if (auto self = ref.get())
{
self->OnCoreWebView2FrameNavigationCompleted(sender, args);
}
});
m_sourceChangedRevoker = m_webView.CoreWebView2().SourceChanged(
winrt::auto_revoke,
[ref = get_weak()](auto const& sender, auto const& args)
{
if (auto self = ref.get())
{
self->OnCoreWebView2SourceChanged(sender, args);
}
});
m_newWindowRequestedRevoker = m_webView.CoreWebView2().NewWindowRequested(
winrt::auto_revoke,
[ref = get_weak()](auto const& sender, auto const& args)
{
if (auto self = ref.get())
{
self->OnCoreWebView2NewWindowRequested(sender, args);
}
});
}
bool ReactWebView2::Is17763OrHigher() {
static std::optional<bool> hasUniversalAPIContract_v7;
if (!hasUniversalAPIContract_v7.has_value()) {
hasUniversalAPIContract_v7 = winrt::Windows::Foundation::Metadata::ApiInformation::IsApiContractPresent(L"Windows.Foundation.UniversalApiContract", 7);
}
return hasUniversalAPIContract_v7.value();
}
void ReactWebView2::WriteWebViewNavigationEventArg(mux::WebView2 const& sender, winrt::IJSValueWriter const& eventDataWriter) {
auto tag = this->GetValue(winrt::FrameworkElement::TagProperty()).as<winrt::IPropertyValue>().GetInt64();
WriteProperty(eventDataWriter, L"canGoBack", sender.CanGoBack());
WriteProperty(eventDataWriter, L"canGoForward", sender.CanGoForward());
if (Is17763OrHigher()) {
WriteProperty(eventDataWriter, L"loading", !sender.IsLoaded());
}
WriteProperty(eventDataWriter, L"target", tag);
if (auto uri = sender.Source()) {
WriteProperty(eventDataWriter, L"url", uri.AbsoluteCanonicalUri());
}
}
void ReactWebView2::OnNavigationStarting(mux::WebView2 const& webView, winrt::CoreWebView2NavigationStartingEventArgs const& /* args */) {
m_reactContext.DispatchEvent(
*this,
L"topLoadingStart",
[&](winrt::IJSValueWriter const& eventDataWriter) noexcept {
eventDataWriter.WriteObjectBegin();
WriteWebViewNavigationEventArg(webView, eventDataWriter);
eventDataWriter.WriteObjectEnd();
});
if (m_messagingEnabled) {
if (m_messageToken)
{
// In case the webview has a new navigation, we need to clean up the old WebMessageReceived handler
webView.WebMessageReceived(m_messageToken);
}
m_messageToken = webView.WebMessageReceived([this](auto const& /* sender */ , winrt::CoreWebView2WebMessageReceivedEventArgs const& messageArgs)
{
try {
auto message = messageArgs.TryGetWebMessageAsString();
this->OnMessagePosted(message);
}
catch (...) {
return;
}
});
}
}
void ReactWebView2::OnMessagePosted(hstring const& message)
{
HandleMessageFromJS(message);
}
void ReactWebView2::OnNavigationCompleted(mux::WebView2 const& webView, winrt::CoreWebView2NavigationCompletedEventArgs const& /* args */) {
m_reactContext.DispatchEvent(
*this,
L"topLoadingFinish",
[&](winrt::IJSValueWriter const& eventDataWriter) noexcept {
eventDataWriter.WriteObjectBegin();
WriteWebViewNavigationEventArg(webView, eventDataWriter);
eventDataWriter.WriteObjectEnd();
});
if (m_messagingEnabled) {
winrt::hstring message = LR"(window.alert = function (msg) {window.chrome.webview.postMessage(`{"type":"__alert","message":"${msg}"}`)};
window.ReactNativeWebView = {postMessage: function (data) {window.chrome.webview.postMessage(String(data))}};
const originalPostMessage = globalThis.postMessage;
globalThis.postMessage = function (data) { originalPostMessage(data); globalThis.ReactNativeWebView.postMessage(typeof data == 'string' ? data : JSON.stringify(data));};)";
webView.ExecuteScriptAsync(message);
}
}
void ReactWebView2::OnCoreWebView2Initialized(mux::WebView2 const& sender, mux::CoreWebView2InitializedEventArgs const& /* args */) {
assert(sender.CoreWebView2());
RegisterCoreWebView2Events();
if (m_navigateToHtml != L"") {
m_webView.NavigateToString(m_navigateToHtml);
m_navigateToHtml = L"";
}
if (!m_request.empty())
{
auto uriString = winrt::to_hstring(m_request.at("uri").AsString());
sender.CoreWebView2().AddWebResourceRequestedFilter(
uriString, winrt::CoreWebView2WebResourceContext::All);
}
}
void ReactWebView2::OnCoreWebView2ResourceRequseted(
winrt::Microsoft::Web::WebView2::Core::CoreWebView2 const& sender,
winrt::CoreWebView2WebResourceRequestedEventArgs const& args)
{
assert(sender);
if (!m_request.empty()) {
auto uriString = winrt::to_hstring(m_request.at("uri").AsString());
if (args.Request().Uri() == uriString) {
SetupRequest(m_request, args.Request());
}
}
}
void ReactWebView2::OnCoreWebView2DOMContentLoaded(
winrt::Microsoft::Web::WebView2::Core::CoreWebView2 const& sender,
winrt::Microsoft::Web::WebView2::Core::CoreWebView2DOMContentLoadedEventArgs const& /* args */)
{
m_reactContext.DispatchEvent(
*this,
L"topDOMContentLoaded",
[&](winrt::IJSValueWriter const& eventDataWriter) noexcept
{
eventDataWriter.WriteObjectBegin();
WriteWebViewNavigationEventArg(m_webView, eventDataWriter);
eventDataWriter.WriteObjectEnd();
});
if (!m_injectedJavascript.empty())
{
sender.ExecuteScriptAsync(m_injectedJavascript);
}
}
void ReactWebView2::OnCoreWebView2FrameNavigationStarted(
winrt::Microsoft::Web::WebView2::Core::CoreWebView2 const& /* sender */,
winrt::Microsoft::Web::WebView2::Core::CoreWebView2NavigationStartingEventArgs const& /* args */)
{
m_reactContext.DispatchEvent(
*this,
L"topFrameNavigationStart",
[&](winrt::IJSValueWriter const& eventDataWriter) noexcept
{
eventDataWriter.WriteObjectBegin();
WriteWebViewNavigationEventArg(m_webView, eventDataWriter);
eventDataWriter.WriteObjectEnd();
});
}
void ReactWebView2::OnCoreWebView2FrameNavigationCompleted(
winrt::Microsoft::Web::WebView2::Core::CoreWebView2 const& /* sender */,
winrt::Microsoft::Web::WebView2::Core::CoreWebView2NavigationCompletedEventArgs const& /* args */)
{
m_reactContext.DispatchEvent(
*this,
L"topFrameNavigationFinish",
[&](winrt::IJSValueWriter const& eventDataWriter) noexcept
{
eventDataWriter.WriteObjectBegin();
WriteWebViewNavigationEventArg(m_webView, eventDataWriter);
eventDataWriter.WriteObjectEnd();
});
}
void ReactWebView2::OnCoreWebView2SourceChanged(
winrt::Microsoft::Web::WebView2::Core::CoreWebView2 const& /* sender */,
winrt::Microsoft::Web::WebView2::Core::CoreWebView2SourceChangedEventArgs const& /* args */)
{
m_reactContext.DispatchEvent(
*this,
L"topSourceChanged",
[&](winrt::IJSValueWriter const& eventDataWriter) noexcept
{
eventDataWriter.WriteObjectBegin();
WriteWebViewNavigationEventArg(m_webView, eventDataWriter);
eventDataWriter.WriteObjectEnd();
});
}
void ReactWebView2::OnCoreWebView2NewWindowRequested(
winrt::Microsoft::Web::WebView2::Core::CoreWebView2 const& sender,
winrt::Microsoft::Web::WebView2::Core::CoreWebView2NewWindowRequestedEventArgs const& args)
{
if (m_linkHandlingEnabled) {
m_reactContext.DispatchEvent(
*this,
L"topOpenWindow",
[&](winrt::IJSValueWriter const& eventDataWriter) noexcept
{
eventDataWriter.WriteObjectBegin();
WriteProperty(eventDataWriter, L"targetUrl", args.Uri());
eventDataWriter.WriteObjectEnd();
});
args.Handled(true);
} else {
try
{
winrt::Windows::Foundation::Uri uri(args.Uri());
winrt::Windows::System::Launcher::LaunchUriAsync(uri);
args.Handled(true);
}
catch (winrt::hresult_error& e)
{
// Do Nothing
}
}
}
void ReactWebView2::HandleMessageFromJS(winrt::hstring const& message) {
winrt::JsonObject jsonObject;
if (winrt::JsonObject::TryParse(message, jsonObject) && jsonObject.HasKey(L"type"))
{
if (auto v = jsonObject.Lookup(L"type"); v && v.ValueType() == JsonValueType::String) {
auto type = v.GetString();
if (type == L"__alert") {
auto dialog = winrt::MessageDialog(jsonObject.GetNamedString(L"message"));
dialog.Commands().Append(winrt::UICommand(L"OK"));
dialog.ShowAsync();
return;
}
}
}
m_reactContext.DispatchEvent(
*this,
L"topMessage",
[&](winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter) noexcept {
eventDataWriter.WriteObjectBegin();
WriteProperty(eventDataWriter, L"data", message);
eventDataWriter.WriteObjectEnd();
});
}
void ReactWebView2::MessagingEnabled(bool enabled) noexcept{
m_messagingEnabled = enabled;
}
bool ReactWebView2::MessagingEnabled() const noexcept{
return m_messagingEnabled;
}
void ReactWebView2::LinkHandlingEnabled(bool enabled) noexcept {
m_linkHandlingEnabled = enabled;
}
bool ReactWebView2::LinkHandlingEnabled() const noexcept {
return m_linkHandlingEnabled;
}
void ReactWebView2::WebResourceRequestSource(Microsoft::ReactNative::JSValueObject const& source) noexcept
{
m_request = std::move(source.Copy());
}
JSValueObject ReactWebView2::WebResourceRequestSource() const noexcept
{
return m_request.Copy();
}
void ReactWebView2::InjectedJavascript(winrt::hstring const& injectedJavascript) noexcept {
m_injectedJavascript = injectedJavascript;
}
winrt::hstring ReactWebView2::InjectedJavascript() const noexcept
{
return m_injectedJavascript;
}
void ReactWebView2::NavigateToHtml(winrt::hstring const& html) {
if (m_webView.CoreWebView2()) {
m_webView.NavigateToString(html);
}
else {
m_webView.EnsureCoreWebView2Async();
m_navigateToHtml = html;
}
}
winrt::fire_and_forget ReactWebView2::NavigateWithWebResourceRequest(Microsoft::ReactNative::IJSValueReader const& source)
{
m_request = JSValueObject::ReadFrom(source);
co_await m_webView.EnsureCoreWebView2Async();
assert(m_webView.CoreWebView2());
if (m_webView.CoreWebView2())
{
auto uri = winrt::Uri(winrt::to_hstring(m_request.at("uri").AsString()));
auto method = (m_request.find("method") != m_request.end()) ? m_request.at("method").AsString() : "GET";
auto webResourceRequest = m_webView.CoreWebView2().Environment().CreateWebResourceRequest(
uri.ToString(), winrt::to_hstring(method), nullptr, L"");
SetupRequest(m_request.Copy(), webResourceRequest);
m_webView.CoreWebView2().NavigateWithWebResourceRequest(webResourceRequest);
}
}
void ReactWebView2::WriteCookiesToWebView2(std::string const& cookies) {
// Persisting cookies passed from JS
// Cookies are separated by ;, and adheres to the Set-Cookie HTTP header format of RFC-6265.
auto cookieManager = m_webView.CoreWebView2().CookieManager();
auto cookiesList = ReactWebViewHelpers::SplitString(cookies, ";,");
for (const auto& cookie_str : cookiesList) {
auto cookieData = ReactWebViewHelpers::ParseSetCookieHeader(ReactWebViewHelpers::TrimString(cookie_str));
if (!cookieData.count("Name") || !cookieData.count("Value")) {
continue;
}
auto cookie = cookieManager.CreateCookie(
winrt::to_hstring(cookieData["Name"]),
winrt::to_hstring(cookieData["Value"]),
cookieData.count("Domain") ? winrt::to_hstring(cookieData["Domain"]) : L"",
cookieData.count("Path") ? winrt::to_hstring(cookieData["Path"]) : L"");
cookieManager.AddOrUpdateCookie(cookie);
}
}
void ReactWebView2::SetupRequest(Microsoft::ReactNative::JSValueObject const& srcMap, winrt::Microsoft::Web::WebView2::Core::CoreWebView2WebResourceRequest const& request) {
bool hasHeaders = srcMap.find("headers") != srcMap.end();
auto method = srcMap.find("method") != srcMap.end() ? srcMap.at("method").AsString() : "GET";
request.Method(winrt::to_hstring(method));
if (method == "POST")
{
auto formBody = srcMap.at("body").AsString();
winrt::InMemoryRandomAccessStream formContent;
winrt::IBuffer buffer{winrt::CryptographicBuffer::ConvertStringToBinary(
winrt::to_hstring(formBody), winrt::BinaryStringEncoding::Utf8)};
formContent.ReadAsync(buffer, buffer.Length(), InputStreamOptions::None);
request.Content(formContent);
}
if (hasHeaders)
{
for (auto const& header : srcMap.at("headers").AsObject())
{
auto const& headerKey = header.first;
auto const& headerValue = header.second;
if (headerValue.IsNull())
continue;
if (headerKey == "Cookie") {
WriteCookiesToWebView2(headerValue.AsString());
} else {
request.Headers().SetHeader(winrt::to_hstring(headerKey), winrt::to_hstring(headerValue.AsString()));
}
}
}
}
} // namespace winrt::ReactNativeWebView::implementation
#endif // HAS_WEBVIEW2
@@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#if HAS_WEBVIEW2
#include "winrt/Microsoft.ReactNative.h"
#include "NativeModules.h"
#include "ReactWebView2.g.h"
#include "winrt/ReactNativeWebView.h"
using namespace winrt::ReactNativeWebView;
namespace winrt::ReactNativeWebView::implementation {
class ReactWebView2 : public ReactWebView2T<ReactWebView2> {
public:
ReactWebView2(Microsoft::ReactNative::IReactContext const& reactContext);
void MessagingEnabled(bool enabled) noexcept;
bool MessagingEnabled() const noexcept;
void LinkHandlingEnabled(bool enabled) noexcept;
bool LinkHandlingEnabled() const noexcept;
void WebResourceRequestSource(Microsoft::ReactNative::JSValueObject const& source) noexcept;
Microsoft::ReactNative::JSValueObject WebResourceRequestSource() const noexcept;
void InjectedJavascript(winrt::hstring const& injectedJavascript) noexcept;
winrt::hstring InjectedJavascript() const noexcept;
void NavigateToHtml(winrt::hstring const& html);
winrt::fire_and_forget NavigateWithWebResourceRequest(Microsoft::ReactNative::IJSValueReader const& source);
~ReactWebView2();
private:
winrt::hstring m_navigateToHtml = L"";
winrt::Microsoft::ReactNative::JSValueObject m_request{};
bool m_messagingEnabled{ true };
bool m_linkHandlingEnabled{ true };
winrt::hstring m_injectedJavascript = L"";
winrt::Microsoft::UI::Xaml::Controls::WebView2 m_webView{ nullptr };
Microsoft::ReactNative::IReactContext m_reactContext{ nullptr };
winrt::event_token m_messageToken;
winrt::Microsoft::UI::Xaml::Controls::WebView2::NavigationStarting_revoker m_navigationStartingRevoker{};
winrt::Microsoft::UI::Xaml::Controls::WebView2::NavigationCompleted_revoker m_navigationCompletedRevoker{};
winrt::Microsoft::UI::Xaml::Controls::WebView2::CoreWebView2Initialized_revoker m_CoreWebView2InitializedRevoker{};
winrt::Microsoft::Web::WebView2::Core::CoreWebView2::WebResourceRequested_revoker
m_webResourceRequestedRevoker{};
winrt::Microsoft::Web::WebView2::Core::CoreWebView2::DOMContentLoaded_revoker
m_CoreWebView2DOMContentLoadedRevoker{};
winrt::Microsoft::Web::WebView2::Core::CoreWebView2::FrameNavigationStarting_revoker m_frameNavigationStartingRevoker{};
winrt::Microsoft::Web::WebView2::Core::CoreWebView2::FrameNavigationCompleted_revoker m_frameNavigationCompletedRevoker{};
winrt::Microsoft::Web::WebView2::Core::CoreWebView2::SourceChanged_revoker m_sourceChangedRevoker{};
winrt::Microsoft::Web::WebView2::Core::CoreWebView2::NewWindowRequested_revoker m_newWindowRequestedRevoker{};
void HandleMessageFromJS(winrt::hstring const& message);
void RegisterEvents();
void RegisterCoreWebView2Events();
void WriteWebViewNavigationEventArg(winrt::Microsoft::UI::Xaml::Controls::WebView2 const& sender, winrt::Microsoft::ReactNative::IJSValueWriter const& eventDataWriter);
void OnNavigationStarting(winrt::Microsoft::UI::Xaml::Controls::WebView2 const& sender, winrt::Microsoft::Web::WebView2::Core::CoreWebView2NavigationStartingEventArgs const& args);
void OnNavigationCompleted(winrt::Microsoft::UI::Xaml::Controls::WebView2 const& sender, winrt::Microsoft::Web::WebView2::Core::CoreWebView2NavigationCompletedEventArgs const& args);
void OnCoreWebView2Initialized(winrt::Microsoft::UI::Xaml::Controls::WebView2 const& sender, winrt::Microsoft::UI::Xaml::Controls::CoreWebView2InitializedEventArgs const& args);
void OnCoreWebView2ResourceRequseted(
winrt::Microsoft::Web::WebView2::Core::CoreWebView2 const& sender,
winrt::Microsoft::Web::WebView2::Core::CoreWebView2WebResourceRequestedEventArgs const& args);
void OnCoreWebView2DOMContentLoaded(
winrt::Microsoft::Web::WebView2::Core::CoreWebView2 const& sender,
winrt::Microsoft::Web::WebView2::Core::CoreWebView2DOMContentLoadedEventArgs const& args);
void OnCoreWebView2FrameNavigationStarted(
winrt::Microsoft::Web::WebView2::Core::CoreWebView2 const& sender,
winrt::Microsoft::Web::WebView2::Core::CoreWebView2NavigationStartingEventArgs const& args);
void OnCoreWebView2FrameNavigationCompleted(
winrt::Microsoft::Web::WebView2::Core::CoreWebView2 const& sender,
winrt::Microsoft::Web::WebView2::Core::CoreWebView2NavigationCompletedEventArgs const& args);
void OnCoreWebView2SourceChanged(
winrt::Microsoft::Web::WebView2::Core::CoreWebView2 const& sender,
winrt::Microsoft::Web::WebView2::Core::CoreWebView2SourceChangedEventArgs const& args);
void OnCoreWebView2NewWindowRequested(
winrt::Microsoft::Web::WebView2::Core::CoreWebView2 const& sender,
winrt::Microsoft::Web::WebView2::Core::CoreWebView2NewWindowRequestedEventArgs const& args);
void OnMessagePosted(hstring const& message);
bool Is17763OrHigher();
void WriteCookiesToWebView2(std::string const& cookies);
void SetupRequest(Microsoft::ReactNative::JSValueObject const& srcMap, winrt::Microsoft::Web::WebView2::Core::CoreWebView2WebResourceRequest const& request);
};
} // namespace winrt::ReactNativeWebView2::implementation
namespace winrt::ReactNativeWebView::factory_implementation {
struct ReactWebView2 : ReactWebView2T<ReactWebView2, implementation::ReactWebView2> {};
} // namespace winrt::ReactNativeWebView2::factory_implementation
#endif // HAS_WEBVIEW2
@@ -0,0 +1,205 @@
#include "pch.h"
#include "ReactWebView2Manager.h"
#if HAS_WEBVIEW2
#include "NativeModules.h"
#include "ReactWebView2.h"
#include "JSValueXaml.h"
namespace mux {
using namespace winrt::Microsoft::UI::Xaml::Controls;
}
namespace winrt {
using namespace Microsoft::ReactNative;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::Web::Http;
using namespace Windows::Web::Http::Headers;
using namespace xaml;
using namespace xaml::Controls;
using namespace xaml::Input;
}
namespace winrt::ReactNativeWebView::implementation {
ReactWebView2Manager::ReactWebView2Manager() {}
// IViewManager
winrt::hstring ReactWebView2Manager::Name() noexcept {
return L"RCTWebView2";
}
winrt::FrameworkElement ReactWebView2Manager::CreateView() noexcept {
auto view = winrt::ReactNativeWebView::ReactWebView2(m_reactContext);
return view;
}
// IViewManagerWithReactContext
winrt::IReactContext ReactWebView2Manager::ReactContext() noexcept {
return m_reactContext;
}
void ReactWebView2Manager::ReactContext(IReactContext reactContext) noexcept {
m_reactContext = reactContext;
}
// IViewManagerWithNativeProperties
IMapView<hstring, ViewManagerPropertyType> ReactWebView2Manager::NativeProps() noexcept {
auto nativeProps = winrt::single_threaded_map<hstring, ViewManagerPropertyType>();
nativeProps.Insert(L"source", ViewManagerPropertyType::Map);
nativeProps.Insert(L"messagingEnabled", ViewManagerPropertyType::Boolean);
nativeProps.Insert(L"injectedJavaScript", ViewManagerPropertyType::String);
nativeProps.Insert(L"linkHandlingEnabled", ViewManagerPropertyType::String);
return nativeProps.GetView();
}
void ReactWebView2Manager::UpdateProperties(
FrameworkElement const& view,
IJSValueReader const& propertyMapReader) noexcept {
auto control = view.as<winrt::ContentPresenter>();
auto content = control.Content();
auto webView = content.as<mux::WebView2>();
const JSValueObject& propertyMap = JSValueObject::ReadFrom(propertyMapReader);
for (auto const& pair : propertyMap) {
auto const& propertyName = pair.first;
auto const& propertyValue = pair.second;
if (propertyValue.IsNull()) continue;
if (propertyName == "source") {
auto const& srcMap = propertyValue.AsObject();
auto reactWebView2 = view.as<winrt::ReactNativeWebView::ReactWebView2>();
std::string const fileScheme = "file://";
if (srcMap.find("uri") != srcMap.end()) {
auto uriString = srcMap.at("uri").AsString();
if (uriString.length() == 0) {
continue;
}
bool isPackagerAsset = false;
if (srcMap.find("__packager_asset") != srcMap.end()) {
isPackagerAsset = srcMap.at("__packager_asset").AsBoolean();
}
if (isPackagerAsset && uriString.find(fileScheme) == 0) {
auto bundleRootPath = winrt::to_string(ReactNativeHost().InstanceSettings().BundleRootPath());
uriString.replace(0, std::size(fileScheme), bundleRootPath.empty() ? "ms-appx-web:///Bundle/" : bundleRootPath);
}
if (uriString.find("ms-appdata://") == 0 || uriString.find("ms-appx-web://") == 0) {
reactWebView2.NavigateToHtml(to_hstring(uriString));
} else {
reactWebView2.NavigateWithWebResourceRequest(MakeJSValueTreeReader(JSValue(srcMap.Copy())));
}
}
else if (srcMap.find("html") != srcMap.end()) {
auto htmlString = srcMap.at("html").AsString();
reactWebView2.NavigateToHtml(to_hstring(htmlString));
}
}
else if (propertyName == "messagingEnabled") {
auto messagingEnabled = propertyValue.To<bool>();
auto reactWebView2 = view.as<ReactNativeWebView::ReactWebView2>();
reactWebView2.MessagingEnabled(messagingEnabled);
}
else if (propertyName == "injectedJavaScript")
{
auto injectedJavascript = propertyValue.AsString();
auto reactWebView2 = view.as<ReactNativeWebView::ReactWebView2>();
reactWebView2.InjectedJavascript(to_hstring(injectedJavascript));
}
else if (propertyName == "linkHandlingEnabled") {
auto linkHandlingEnabled = propertyValue.To<bool>();
auto reactWebView2 = view.as<ReactNativeWebView::ReactWebView2>();
reactWebView2.LinkHandlingEnabled(linkHandlingEnabled);
}
}
}
// IViewManagerWithExportedEventTypeConstants
ConstantProviderDelegate ReactWebView2Manager::ExportedCustomBubblingEventTypeConstants() noexcept {
return nullptr;
}
ConstantProviderDelegate ReactWebView2Manager::ExportedCustomDirectEventTypeConstants() noexcept {
return [](winrt::IJSValueWriter const& constantWriter) {
WriteCustomDirectEventTypeConstant(constantWriter, "DOMContentLoaded");
WriteCustomDirectEventTypeConstant(constantWriter, "LoadingStart");
WriteCustomDirectEventTypeConstant(constantWriter, "LoadingFinish");
WriteCustomDirectEventTypeConstant(constantWriter, "LoadingError");
WriteCustomDirectEventTypeConstant(constantWriter, "Message");
WriteCustomDirectEventTypeConstant(constantWriter, "FrameNavigationStart");
WriteCustomDirectEventTypeConstant(constantWriter, "FrameNavigationFinish");
WriteCustomDirectEventTypeConstant(constantWriter, "OpenWindow");
WriteCustomDirectEventTypeConstant(constantWriter, "SourceChanged");
};
}
// IViewManagerWithCommands
IVectorView<hstring> ReactWebView2Manager::Commands() noexcept {
auto commands = winrt::single_threaded_vector<hstring>();
commands.Append(L"goForward");
commands.Append(L"goBack");
commands.Append(L"reload");
commands.Append(L"stopLoading");
commands.Append(L"injectJavaScript");
commands.Append(L"requestFocus");
commands.Append(L"clearCache");
commands.Append(L"postMessage");
commands.Append(L"loadUrl");
return commands.GetView();
}
void ReactWebView2Manager::DispatchCommand(
FrameworkElement const& view,
winrt::hstring const& commandId,
winrt::IJSValueReader const& commandArgsReader) noexcept {
auto control = view.as<winrt::ContentPresenter>();
auto content = control.Content();
auto webView = content.as<mux::WebView2>();
auto commandArgs = JSValue::ReadArrayFrom(commandArgsReader);
if (commandId == L"goForward") {
if (webView.CanGoForward()) {
webView.GoForward();
}
}
else if (commandId == L"goBack") {
if (webView.CanGoBack()) {
webView.GoBack();
}
}
else if (commandId == L"reload") {
webView.Reload();
}
else if (commandId == L"stopLoading") {
if (webView.CoreWebView2() != nullptr) {
webView.CoreWebView2().Stop();
}
}
else if (commandId == L"injectJavaScript") {
webView.ExecuteScriptAsync(winrt::to_hstring(commandArgs[0].AsString()));
}
else if (commandId == L"requestFocus") {
FocusManager::TryFocusAsync(webView, FocusState::Programmatic);
}
else if (commandId == L"clearCache") {
// There is no way to clear the cache in WebView2 because it is shared with Edge.
// The best we can do is clear the cookies, because we cannot access history or local storage.
auto cookieManager = webView.CoreWebView2().CookieManager();
cookieManager.DeleteAllCookies();
}
else if (commandId == L"loadUrl") {
auto uri = winrt::Uri(to_hstring(commandArgs[0].AsString()));
webView.Source(uri);
}
else if (commandId == L"postMessage") {
if (webView.CoreWebView2() != nullptr) {
auto message = commandArgs[0].AsString();
webView.CoreWebView2().PostWebMessageAsString(to_hstring(message));
}
}
}
} // namespace winrt::ReactNativeWebView::implementation
#endif // HAS_WEBVIEW2
@@ -0,0 +1,57 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "ReactWebView2.h"
#if HAS_WEBVIEW2
#include "winrt/Microsoft.ReactNative.h"
#include "NativeModules.h"
namespace winrt::ReactNativeWebView::implementation {
class ReactWebView2Manager : public winrt::implements<
ReactWebView2Manager,
winrt::Microsoft::ReactNative::IViewManager,
winrt::Microsoft::ReactNative::IViewManagerWithReactContext,
winrt::Microsoft::ReactNative::IViewManagerWithNativeProperties,
winrt::Microsoft::ReactNative::IViewManagerWithExportedEventTypeConstants,
winrt::Microsoft::ReactNative::IViewManagerWithCommands> {
public:
ReactWebView2Manager();
// IViewManager
winrt::hstring Name() noexcept;
xaml::FrameworkElement CreateView() noexcept;
// IViewManagerWithReactContext
winrt::Microsoft::ReactNative::IReactContext ReactContext() noexcept;
void ReactContext(winrt::Microsoft::ReactNative::IReactContext reactContext) noexcept;
// IViewManagerWithNativeProperties
winrt::Windows::Foundation::Collections::
IMapView<winrt::hstring, winrt::Microsoft::ReactNative::ViewManagerPropertyType>
NativeProps() noexcept;
void UpdateProperties(
xaml::FrameworkElement const& view,
winrt::Microsoft::ReactNative::IJSValueReader const& propertyMapReader) noexcept;
// IViewManagerWithExportedEventTypeConstants
winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomBubblingEventTypeConstants() noexcept;
winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomDirectEventTypeConstants() noexcept;
// IViewManagerWithCommands
winrt::Windows::Foundation::Collections::IVectorView<winrt::hstring> Commands() noexcept;
void DispatchCommand(
xaml::FrameworkElement const& view,
winrt::hstring const& commandId,
winrt::Microsoft::ReactNative::IJSValueReader const& commandArgsReader) noexcept;
private:
winrt::Microsoft::ReactNative::IReactContext m_reactContext{ nullptr };
};
} // namespace winrt::ReactNativeWebView::implementation
#endif // HAS_WEBVIEW2
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "pch.h"
#include "winrt/ReactNativeWebView.h"
using namespace winrt::ReactNativeWebView;
namespace winrt::ReactNativeWebView::implementation {
namespace ReactWebViewHelpers {
std::string TrimString(const std::string& str) {
std::string trimmedString = str;
// Trim from start
trimmedString.erase(0, trimmedString.find_first_not_of(" \t\n\r\f\v"));
// Trim from end
trimmedString.erase(trimmedString.find_last_not_of(" \t\n\r\f\v") + 1);
return trimmedString;
}
std::vector<std::string> SplitString(
const std::string& str,
const std::string& delim) {
std::vector<std::string> tokens;
auto startPos = 0;
auto endPos = str.find(delim);
while (endPos != std::string::npos) {
auto token = str.substr(startPos, endPos - startPos);
tokens.push_back(TrimString(token));
startPos = endPos + delim.length();
endPos = str.find(delim, startPos);
}
auto lastToken = str.substr(startPos);
tokens.push_back(TrimString(lastToken));
return tokens;
}
std::map<std::string, std::string> ParseSetCookieHeader(
const std::string& setCookieHeader) {
std::map<std::string, std::string> cookie;
// Split the header into individual cookie strings
auto cookieStrings = SplitString(setCookieHeader, ";");
// Extract the cookie name and value from the first string
auto nameValuePair = SplitString(cookieStrings[0], "=");
cookie["Name"] = TrimString(nameValuePair[0]);
cookie["Value"] = TrimString(nameValuePair[1]);
// Extract the attributes from the remaining strings
for (std::size_t i = 1; i < cookieStrings.size(); ++i) {
auto attributeValuePair = SplitString(cookieStrings[i], "=");
auto attributeName = attributeValuePair[0];
auto attributeValue =
attributeValuePair.size() > 1 ? attributeValuePair[1] : "";
cookie[attributeName] = TrimString(attributeValue);
}
return cookie;
}
}
} // namespace winrt::ReactNativeWebView::implementation
@@ -0,0 +1,16 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#include "winrt/ReactNativeWebView.h"
using namespace winrt::ReactNativeWebView;
namespace winrt::ReactNativeWebView::implementation {
namespace ReactWebViewHelpers {
std::string TrimString(const std::string& str);
std::vector<std::string> SplitString(const std::string& str, const std::string& delim);
std::map<std::string, std::string> ParseSetCookieHeader(const std::string& setCookieHeader);
}
} // namespace winrt::ReactNativeWebView::implementation
@@ -0,0 +1,224 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#include "pch.h"
#ifndef USE_WINUI3
#include "ReactWebViewManager.h"
#include "NativeModules.h"
#include "ReactWebView.h"
#include "JSValueXaml.h"
namespace winrt {
using namespace Microsoft::ReactNative;
using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::UI;
using namespace Windows::UI::Xaml;
using namespace Windows::UI::Xaml::Controls;
using namespace Windows::Web::Http;
using namespace Windows::Web::Http::Headers;
}
namespace winrt::ReactNativeWebView::implementation {
ReactWebViewManager::ReactWebViewManager() {}
// IViewManager
winrt::hstring ReactWebViewManager::Name() noexcept {
return L"RCTWebView";
}
winrt::FrameworkElement ReactWebViewManager::CreateView() noexcept {
auto view = winrt::ReactNativeWebView::ReactWebView(m_reactContext);
return view;
}
// IViewManagerWithReactContext
winrt::IReactContext ReactWebViewManager::ReactContext() noexcept {
return m_reactContext;
}
void ReactWebViewManager::ReactContext(IReactContext reactContext) noexcept {
m_reactContext = reactContext;
}
// IViewManagerWithNativeProperties
IMapView<hstring, ViewManagerPropertyType> ReactWebViewManager::NativeProps() noexcept {
auto nativeProps = winrt::single_threaded_map<hstring, ViewManagerPropertyType>();
nativeProps.Insert(L"source", ViewManagerPropertyType::Map);
nativeProps.Insert(L"backgroundColor", ViewManagerPropertyType::Color);
nativeProps.Insert(L"messagingEnabled", ViewManagerPropertyType::Boolean);
return nativeProps.GetView();
}
void ReactWebViewManager::UpdateProperties(
FrameworkElement const& view,
IJSValueReader const& propertyMapReader) noexcept {
auto control = view.as<winrt::ContentPresenter>();
auto content = control.Content();
auto webView = content.as<winrt::WebView>();
const JSValueObject& propertyMap = JSValueObject::ReadFrom(propertyMapReader);
for (auto const& pair : propertyMap) {
auto const& propertyName = pair.first;
auto const& propertyValue = pair.second;
if (propertyValue.IsNull()) continue;
if (propertyName == "source") {
auto const& srcMap = propertyValue.AsObject();
if (srcMap.find("uri") != srcMap.end()) {
auto uriString = srcMap.at("uri").AsString();
if (uriString.length() == 0) {
continue;
}
bool isPackagerAsset = false;
if (srcMap.find("__packager_asset") != srcMap.end()) {
isPackagerAsset = srcMap.at("__packager_asset").AsBoolean();
}
if (isPackagerAsset && uriString.find("file://") == 0) {
auto bundleRootPath = winrt::to_string(ReactNativeHost().InstanceSettings().BundleRootPath());
uriString.replace(0, 7, bundleRootPath.empty() ? "ms-appx-web:///Bundle/" : bundleRootPath);
}
if (uriString.find("ms-appdata://") == 0 || uriString.find("ms-appx-web://") == 0) {
webView.Navigate(winrt::Uri(to_hstring(uriString)));
}
else {
bool hasHeaders = srcMap.find("headers") != srcMap.end();
auto httpRequest = winrt::HttpRequestMessage();
httpRequest.RequestUri(winrt::Uri(to_hstring(uriString)));
if (srcMap.find("method") != srcMap.end() && srcMap.at("method").AsString() == "POST")
{
httpRequest.Method(winrt::HttpMethod::Post());
auto formBody = srcMap.at("body").AsString();
bool isUrlEncodedForm = hasHeaders &&
srcMap.at("headers").AsObject().find("content-type") !=
srcMap.at("headers").AsObject().end() &&
srcMap.at("headers").AsObject().at("content-type") == "application/x-www-form-urlencoded";
if (isUrlEncodedForm)
{
auto formContent = winrt::single_threaded_observable_map<winrt::hstring, winrt::hstring>();
size_t counter = 0u;
auto current = formBody.find_first_of("&");
while (counter <= formBody.find_last_of("&"))
{
auto keyValueSeparator = formBody.substr(counter, counter + current).find('=');
if (keyValueSeparator <= current)
{
auto key = winrt::to_hstring(formBody.substr(counter, keyValueSeparator));
auto value = winrt::to_hstring(formBody.substr(
keyValueSeparator + counter + 1, current - keyValueSeparator - 1));
formContent.Insert(key, value);
}
counter += current + 1;
current = formBody.substr(counter, formBody.size() - counter).find_first_of("&");
}
httpRequest.Content(winrt::HttpFormUrlEncodedContent(formContent));
httpRequest.Headers().Accept().TryParseAdd(L"application/x-www-form-urlencoded");
}
else
{
httpRequest.Content(winrt::HttpStringContent(to_hstring(formBody)));
}
}
if (hasHeaders) {
for (auto const& header : srcMap.at("headers").AsObject()) {
auto const& headerKey = header.first;
auto const& headerValue = header.second;
if (headerValue.IsNull()) continue;
httpRequest.Headers().TryAppendWithoutValidation(winrt::to_hstring(headerKey), winrt::to_hstring(headerValue.AsString()));
}
}
webView.NavigateWithHttpRequestMessage(httpRequest);
}
}
else if (srcMap.find("html") != srcMap.end()) {
auto htmlString = srcMap.at("html").AsString();
webView.NavigateToString(to_hstring(htmlString));
}
}
else if (propertyName == "backgroundColor") {
auto color = propertyValue.To<winrt::Color>();
webView.DefaultBackgroundColor(color.A==0 ? winrt::Colors::Transparent() : color);
}
else if (propertyName == "messagingEnabled") {
auto messagingEnabled = propertyValue.To<bool>();
auto reactWebView = view.as<ReactNativeWebView::ReactWebView>();
reactWebView.MessagingEnabled(messagingEnabled);
}
}
}
// IViewManagerWithExportedEventTypeConstants
ConstantProviderDelegate ReactWebViewManager::ExportedCustomBubblingEventTypeConstants() noexcept {
return nullptr;
}
ConstantProviderDelegate ReactWebViewManager::ExportedCustomDirectEventTypeConstants() noexcept {
return [](winrt::IJSValueWriter const& constantWriter) {
WriteCustomDirectEventTypeConstant(constantWriter, "LoadingStart");
WriteCustomDirectEventTypeConstant(constantWriter, "LoadingFinish");
WriteCustomDirectEventTypeConstant(constantWriter, "LoadingError");
WriteCustomDirectEventTypeConstant(constantWriter, "Message");
WriteCustomDirectEventTypeConstant(constantWriter, "DOMContentLoaded");
WriteCustomDirectEventTypeConstant(constantWriter, "Message");
};
}
// IViewManagerWithCommands
IVectorView<hstring> ReactWebViewManager::Commands() noexcept {
auto commands = winrt::single_threaded_vector<hstring>();
commands.Append(L"goForward");
commands.Append(L"goBack");
commands.Append(L"reload");
commands.Append(L"stopLoading");
commands.Append(L"injectJavaScript");
commands.Append(L"postMessage");
commands.Append(L"loadUrl");
return commands.GetView();
}
void ReactWebViewManager::DispatchCommand(
FrameworkElement const& view,
winrt::hstring const& commandId,
winrt::IJSValueReader const& commandArgsReader) noexcept {
auto control = view.as<winrt::ContentPresenter>();
auto content = control.Content();
auto webView = content.as<winrt::WebView>();
auto commandArgs = JSValue::ReadArrayFrom(commandArgsReader);
if (commandId == L"goForward") {
if (webView.CanGoForward()) {
webView.GoForward();
}
}
else if (commandId == L"goBack") {
if (webView.CanGoBack()) {
webView.GoBack();
}
}
else if (commandId == L"reload") {
webView.Refresh();
}
else if (commandId == L"stopLoading") {
webView.Stop();
}
else if (commandId == L"injectJavaScript") {
webView.InvokeScriptAsync(L"eval", { winrt::to_hstring(commandArgs[0].AsString()) });
}
else if (commandId == L"postMessage") {
auto reactWebView = view.as<ReactNativeWebView::ReactWebView>();
reactWebView.PostMessage(to_hstring(commandArgs[0].AsString()));
}
else if (commandId == L"loadUrl") {
webView.Navigate(winrt::Uri(to_hstring(commandArgs[0].AsString())));
}
}
} // namespace winrt::ReactWebView::implementation
#endif // USE_WINUI3
@@ -0,0 +1,58 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
#pragma once
#ifndef USE_WINUI3
#include "winrt/Microsoft.ReactNative.h"
#include "NativeModules.h"
#include "ReactWebView.h"
namespace winrt::ReactNativeWebView::implementation {
class ReactWebViewManager : public winrt::implements<
ReactWebViewManager,
winrt::Microsoft::ReactNative::IViewManager,
winrt::Microsoft::ReactNative::IViewManagerWithReactContext,
winrt::Microsoft::ReactNative::IViewManagerWithNativeProperties,
winrt::Microsoft::ReactNative::IViewManagerWithExportedEventTypeConstants,
winrt::Microsoft::ReactNative::IViewManagerWithCommands> {
public:
ReactWebViewManager();
// IViewManager
winrt::hstring Name() noexcept;
winrt::Windows::UI::Xaml::FrameworkElement CreateView() noexcept;
// IViewManagerWithReactContext
winrt::Microsoft::ReactNative::IReactContext ReactContext() noexcept;
void ReactContext(winrt::Microsoft::ReactNative::IReactContext reactContext) noexcept;
// IViewManagerWithNativeProperties
winrt::Windows::Foundation::Collections::
IMapView<winrt::hstring, winrt::Microsoft::ReactNative::ViewManagerPropertyType>
NativeProps() noexcept;
void UpdateProperties(
winrt::Windows::UI::Xaml::FrameworkElement const& view,
winrt::Microsoft::ReactNative::IJSValueReader const& propertyMapReader) noexcept;
// IViewManagerWithExportedEventTypeConstants
winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomBubblingEventTypeConstants() noexcept;
winrt::Microsoft::ReactNative::ConstantProviderDelegate ExportedCustomDirectEventTypeConstants() noexcept;
// IViewManagerWithCommands
winrt::Windows::Foundation::Collections::IVectorView<winrt::hstring> Commands() noexcept;
void DispatchCommand(
winrt::Windows::UI::Xaml::FrameworkElement const& view,
winrt::hstring const& commandId,
winrt::Microsoft::ReactNative::IJSValueReader const& commandArgsReader) noexcept;
private:
winrt::Microsoft::ReactNative::IReactContext m_reactContext{ nullptr };
};
} // namespace winrt::ReactWebView::implementation
#endif // USE_WINUI3
@@ -0,0 +1,19 @@
#include "pch.h"
#include "WebBridge.h"
#include "WebBridge.g.cpp"
namespace winrt::ReactNativeWebView::implementation
{
void WebBridge::PostMessage(hstring const& message)
{
m_messageEvent(*this, message);
}
winrt::event_token WebBridge::MessagePostEvent(Windows::Foundation::EventHandler<hstring> const& handler)
{
return m_messageEvent.add(handler);
}
void WebBridge::MessagePostEvent(winrt::event_token const& token) noexcept
{
m_messageEvent.remove(token);
}
}
@@ -0,0 +1,24 @@
#pragma once
#include "WebBridge.g.h"
namespace winrt::ReactNativeWebView::implementation
{
struct WebBridge : WebBridgeT<WebBridge>
{
WebBridge() = default;
void PostMessage(hstring const& message);
winrt::event_token MessagePostEvent(Windows::Foundation::EventHandler<hstring> const& handler);
void MessagePostEvent(winrt::event_token const& token) noexcept;
private:
winrt::event<Windows::Foundation::EventHandler<winrt::hstring>> m_messageEvent;
};
}
namespace winrt::ReactNativeWebView::factory_implementation
{
struct WebBridge : WebBridgeT<WebBridge, implementation::WebBridge>
{
};
}
@@ -0,0 +1,11 @@
namespace ReactNativeWebView
{
[default_interface]
[allowforweb]
runtimeclass WebBridge
{
WebBridge();
void PostMessage(String message);
event Windows.Foundation.EventHandler<String> MessagePostEvent;
}
}
@@ -0,0 +1 @@
#include "pch.h"
@@ -0,0 +1,37 @@
#pragma once
#define NOMINMAX
#if __has_include(<VersionMacros.h>)
#include <VersionMacros.h>
#else
#define RNW_VERSION_AT_LEAST(x,y,z) false
#endif
#if RNW_VERSION_AT_LEAST(0,68,0) && (defined(WINUI2_HAS_WEBVIEW2) || defined(USE_WINUI3))
#define HAS_WEBVIEW2 1
#else
#define HAS_WEBVIEW2 0
#endif
#include <unknwn.h>
#include <CppWinRTIncludes.h>
#include <UI.Xaml.Controls.h>
#include <UI.Xaml.Input.h>
#include <UI.Xaml.Markup.h>
#include <UI.Xaml.Navigation.h>
#include <winrt/Windows.Data.Json.h>
#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.UI.Popups.h>
#include <winrt/Windows.Web.Http.h>
#include <winrt/Windows.Web.Http.Headers.h>
#include <winrt/Microsoft.ReactNative.h>
#include <winrt/Microsoft.UI.Xaml.Controls.h>
#include <winrt/Windows.Security.Cryptography.h>
#include <winrt/Windows.Storage.Streams.h>
#if HAS_WEBVIEW2
#include <winrt/Microsoft.Web.WebView2.Core.h>
#endif