From d6ad1fed902bf21020cac54e83b06ce6a32ed5ee Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Tue, 15 Dec 2015 09:19:05 -0500 Subject: [PATCH 01/17] Initial React Windows commit including Includes the NativeModuleBase (equivalent of the JavaBaseModule in ReactAndroid) and the NativeModuleRegistry. --- ReactWindows/.gitignore | 76 ++ ReactWindows/Playground/App.xaml | 8 + ReactWindows/Playground/App.xaml.cs | 108 +++ .../Playground/ApplicationInsights.config | 3 + .../Assets/LockScreenLogo.scale-200.png | Bin 0 -> 1430 bytes .../Assets/SplashScreen.scale-200.png | Bin 0 -> 7700 bytes .../Assets/Square150x150Logo.scale-200.png | Bin 0 -> 2937 bytes .../Assets/Square44x44Logo.scale-200.png | Bin 0 -> 1647 bytes ...x44Logo.targetsize-24_altform-unplated.png | Bin 0 -> 1255 bytes ReactWindows/Playground/Assets/StoreLogo.png | Bin 0 -> 1451 bytes .../Assets/Wide310x150Logo.scale-200.png | Bin 0 -> 3204 bytes ReactWindows/Playground/MainPage.xaml | 13 + ReactWindows/Playground/MainPage.xaml.cs | 46 + ReactWindows/Playground/Package.appxmanifest | 49 + ReactWindows/Playground/Playground.csproj | 149 +++ .../Playground/Playground_TemporaryKey.pfx | Bin 0 -> 2454 bytes .../Playground/Properties/AssemblyInfo.cs | 29 + .../Playground/Properties/Default.rd.xml | 31 + ReactWindows/Playground/project.json | 19 + .../Assets/LockScreenLogo.scale-200.png | Bin 0 -> 1430 bytes .../Assets/SplashScreen.scale-200.png | Bin 0 -> 7700 bytes .../Assets/Square150x150Logo.scale-200.png | Bin 0 -> 2937 bytes .../Assets/Square44x44Logo.scale-200.png | Bin 0 -> 1647 bytes ...x44Logo.targetsize-24_altform-unplated.png | Bin 0 -> 1255 bytes .../ReactNative.Tests/Assets/StoreLogo.png | Bin 0 -> 1451 bytes .../Assets/Wide310x150Logo.scale-200.png | Bin 0 -> 3204 bytes .../Bridge/NativeModuleBaseTests.cs | 27 + .../Bridge/NativeModuleRegistryTests.cs | 88 ++ .../ReactNative.Tests/Internal/AssertEx.cs | 30 + .../ReactNative.Tests/Package.appxmanifest | 45 + .../Properties/AssemblyInfo.cs | 30 + .../Properties/UnitTestApp.rd.xml | 29 + .../ReactNative.Tests.csproj | 148 +++ .../ReactNative.Tests_TemporaryKey.pfx | Bin 0 -> 2454 bytes .../ReactNative.Tests/UnitTestApp.xaml | 8 + .../ReactNative.Tests/UnitTestApp.xaml.cs | 102 ++ ReactWindows/ReactNative.Tests/project.json | 16 + ReactWindows/ReactNative.sln | 84 ++ .../ReactNative/Bridge/CatalystInstance.cs | 40 + .../ReactNative/Bridge/ChakraReactBridge.cs | 23 + ReactWindows/ReactNative/Bridge/ICallback.cs | 7 + .../ReactNative/Bridge/ICatalystInstance.cs | 17 + .../ReactNative/Bridge/INativeMethod.cs | 11 + .../ReactNative/Bridge/INativeModule.cs | 19 + .../Bridge/IOnBatchCompleteListener.cs | 7 + .../ReactNative/Bridge/IReactBridge.cs | 13 + .../ReactNative/Bridge/NativeModuleBase.cs | 241 +++++ .../Bridge/NativeModuleRegistry.cs | 146 +++ .../Bridge/Queue/IMessageQueueThread.cs | 40 + .../Queue/IQueueThreadExceptionHandler.cs | 9 + .../Bridge/Queue/MessageQueueThread.cs | 54 ++ .../Queue/MessageQueueThreadExtensions.cs | 36 + .../Bridge/Queue/MessageQueueThreadKind.cs | 9 + .../Bridge/Queue/MessageQueueThreadSpec.cs | 34 + .../ReactNative/Bridge/ReactContext.cs | 12 + .../JavaScriptBackgroundWorkItemCallback.cs | 14 + .../JavaScriptBeforeCollectCallback.cs | 10 + .../ReactNative/Hosting/JavaScriptContext.cs | 500 ++++++++++ .../Hosting/JavaScriptEngineException.cs | 30 + .../Hosting/JavaScriptErrorCode.cs | 158 ++++ .../Hosting/JavaScriptException.cs | 44 + .../Hosting/JavaScriptFatalException.cs | 30 + .../JavaScriptMemoryAllocationCallback.cs | 17 + .../Hosting/JavaScriptMemoryEventType.cs | 23 + .../Hosting/JavaScriptNativeFunction.cs | 19 + .../JavaScriptObjectFinalizeCallback.cs | 12 + .../Hosting/JavaScriptPropertyId.cs | 141 +++ .../ReactNative/Hosting/JavaScriptRuntime.cs | 265 ++++++ .../Hosting/JavaScriptRuntimeAttributes.cs | 46 + .../Hosting/JavaScriptRuntimeVersion.cs | 23 + .../Hosting/JavaScriptScriptException.cs | 49 + .../Hosting/JavaScriptSourceContext.cs | 184 ++++ .../JavaScriptThreadServiceCallback.cs | 18 + .../Hosting/JavaScriptUsageException.cs | 30 + .../ReactNative/Hosting/JavaScriptValue.cs | 886 ++++++++++++++++++ .../Hosting/JavaScriptValueType.cs | 53 ++ ReactWindows/ReactNative/Hosting/Native.cs | 647 +++++++++++++ ReactWindows/ReactNative/IReactPackage.cs | 11 + ReactWindows/ReactNative/IViewManager.cs | 12 + ReactWindows/ReactNative/Imports.cs | 31 + .../ReactNative/Properties/AssemblyInfo.cs | 29 + .../ReactNative/Properties/ReactNative.rd.xml | 33 + .../ReactNative/ReactInstanceManager.cs | 16 + .../ReactNative/ReactMethodAttribute.cs | 9 + ReactWindows/ReactNative/ReactNative.csproj | 164 ++++ .../Reflection/MethodInfoHelpers.cs | 13 + .../Reflection/ReflectionHelpers.cs | 47 + ReactWindows/ReactNative/project.json | 17 + 88 files changed, 5437 insertions(+) create mode 100644 ReactWindows/.gitignore create mode 100644 ReactWindows/Playground/App.xaml create mode 100644 ReactWindows/Playground/App.xaml.cs create mode 100644 ReactWindows/Playground/ApplicationInsights.config create mode 100644 ReactWindows/Playground/Assets/LockScreenLogo.scale-200.png create mode 100644 ReactWindows/Playground/Assets/SplashScreen.scale-200.png create mode 100644 ReactWindows/Playground/Assets/Square150x150Logo.scale-200.png create mode 100644 ReactWindows/Playground/Assets/Square44x44Logo.scale-200.png create mode 100644 ReactWindows/Playground/Assets/Square44x44Logo.targetsize-24_altform-unplated.png create mode 100644 ReactWindows/Playground/Assets/StoreLogo.png create mode 100644 ReactWindows/Playground/Assets/Wide310x150Logo.scale-200.png create mode 100644 ReactWindows/Playground/MainPage.xaml create mode 100644 ReactWindows/Playground/MainPage.xaml.cs create mode 100644 ReactWindows/Playground/Package.appxmanifest create mode 100644 ReactWindows/Playground/Playground.csproj create mode 100644 ReactWindows/Playground/Playground_TemporaryKey.pfx create mode 100644 ReactWindows/Playground/Properties/AssemblyInfo.cs create mode 100644 ReactWindows/Playground/Properties/Default.rd.xml create mode 100644 ReactWindows/Playground/project.json create mode 100644 ReactWindows/ReactNative.Tests/Assets/LockScreenLogo.scale-200.png create mode 100644 ReactWindows/ReactNative.Tests/Assets/SplashScreen.scale-200.png create mode 100644 ReactWindows/ReactNative.Tests/Assets/Square150x150Logo.scale-200.png create mode 100644 ReactWindows/ReactNative.Tests/Assets/Square44x44Logo.scale-200.png create mode 100644 ReactWindows/ReactNative.Tests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png create mode 100644 ReactWindows/ReactNative.Tests/Assets/StoreLogo.png create mode 100644 ReactWindows/ReactNative.Tests/Assets/Wide310x150Logo.scale-200.png create mode 100644 ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs create mode 100644 ReactWindows/ReactNative.Tests/Bridge/NativeModuleRegistryTests.cs create mode 100644 ReactWindows/ReactNative.Tests/Internal/AssertEx.cs create mode 100644 ReactWindows/ReactNative.Tests/Package.appxmanifest create mode 100644 ReactWindows/ReactNative.Tests/Properties/AssemblyInfo.cs create mode 100644 ReactWindows/ReactNative.Tests/Properties/UnitTestApp.rd.xml create mode 100644 ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj create mode 100644 ReactWindows/ReactNative.Tests/ReactNative.Tests_TemporaryKey.pfx create mode 100644 ReactWindows/ReactNative.Tests/UnitTestApp.xaml create mode 100644 ReactWindows/ReactNative.Tests/UnitTestApp.xaml.cs create mode 100644 ReactWindows/ReactNative.Tests/project.json create mode 100644 ReactWindows/ReactNative.sln create mode 100644 ReactWindows/ReactNative/Bridge/CatalystInstance.cs create mode 100644 ReactWindows/ReactNative/Bridge/ChakraReactBridge.cs create mode 100644 ReactWindows/ReactNative/Bridge/ICallback.cs create mode 100644 ReactWindows/ReactNative/Bridge/ICatalystInstance.cs create mode 100644 ReactWindows/ReactNative/Bridge/INativeMethod.cs create mode 100644 ReactWindows/ReactNative/Bridge/INativeModule.cs create mode 100644 ReactWindows/ReactNative/Bridge/IOnBatchCompleteListener.cs create mode 100644 ReactWindows/ReactNative/Bridge/IReactBridge.cs create mode 100644 ReactWindows/ReactNative/Bridge/NativeModuleBase.cs create mode 100644 ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs create mode 100644 ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs create mode 100644 ReactWindows/ReactNative/Bridge/Queue/IQueueThreadExceptionHandler.cs create mode 100644 ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs create mode 100644 ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs create mode 100644 ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs create mode 100644 ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs create mode 100644 ReactWindows/ReactNative/Bridge/ReactContext.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptBackgroundWorkItemCallback.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptBeforeCollectCallback.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptContext.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptEngineException.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptErrorCode.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptException.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptFatalException.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptMemoryAllocationCallback.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptMemoryEventType.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptNativeFunction.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptObjectFinalizeCallback.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptPropertyId.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptRuntime.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptRuntimeAttributes.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptRuntimeVersion.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptScriptException.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptSourceContext.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptThreadServiceCallback.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptUsageException.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptValue.cs create mode 100644 ReactWindows/ReactNative/Hosting/JavaScriptValueType.cs create mode 100644 ReactWindows/ReactNative/Hosting/Native.cs create mode 100644 ReactWindows/ReactNative/IReactPackage.cs create mode 100644 ReactWindows/ReactNative/IViewManager.cs create mode 100644 ReactWindows/ReactNative/Imports.cs create mode 100644 ReactWindows/ReactNative/Properties/AssemblyInfo.cs create mode 100644 ReactWindows/ReactNative/Properties/ReactNative.rd.xml create mode 100644 ReactWindows/ReactNative/ReactInstanceManager.cs create mode 100644 ReactWindows/ReactNative/ReactMethodAttribute.cs create mode 100644 ReactWindows/ReactNative/ReactNative.csproj create mode 100644 ReactWindows/ReactNative/Reflection/MethodInfoHelpers.cs create mode 100644 ReactWindows/ReactNative/Reflection/ReflectionHelpers.cs create mode 100644 ReactWindows/ReactNative/project.json diff --git a/ReactWindows/.gitignore b/ReactWindows/.gitignore new file mode 100644 index 00000000000..996016dc621 --- /dev/null +++ b/ReactWindows/.gitignore @@ -0,0 +1,76 @@ +*AppPackages* + +#OS junk files +[Tt]humbs.db +*.DS_Store + +#Visual Studio files +*.[Oo]bj +*.user +*.aps +*.pch +*.vspscc +*.vssscc +*_i.c +*_p.c +*.ncb +*.suo +*.tlb +*.tlh +*.bak +*.[Cc]ache +*.ilk +*.log +*.lib +*.sbr +*.sdf +*.opensdf +*.opendb +*.unsuccessfulbuild +ipch/ +[Oo]bj/ +[Bb]in +[Dd]ebug*/ +[Rr]elease*/ +Ankh.NoLoad + +#MonoDevelop +*.pidb +*.userprefs + +#Tooling +_ReSharper*/ +*.resharper +[Tt]est[Rr]esult* +*.sass-cache + +#Project files +[Bb]uild/ + +#Subversion files +.svn + +# Office Temp Files +~$* + +# vim Temp Files +*~ + +#NuGet +packages/ +*.nupkg + +#ncrunch +*ncrunch* +*crunch*.local.xml + +# visual studio database projects +*.dbmdl + +#Test files +*.testsettings + +#Other files +*.DotSettings +.vs/ +*project.lock.json \ No newline at end of file diff --git a/ReactWindows/Playground/App.xaml b/ReactWindows/Playground/App.xaml new file mode 100644 index 00000000000..33929dda240 --- /dev/null +++ b/ReactWindows/Playground/App.xaml @@ -0,0 +1,8 @@ + + + diff --git a/ReactWindows/Playground/App.xaml.cs b/ReactWindows/Playground/App.xaml.cs new file mode 100644 index 00000000000..5cf14d70460 --- /dev/null +++ b/ReactWindows/Playground/App.xaml.cs @@ -0,0 +1,108 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.ApplicationModel; +using Windows.ApplicationModel.Activation; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +namespace Playground +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + sealed partial class App : Application + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + Microsoft.ApplicationInsights.WindowsAppInitializer.InitializeAsync( + Microsoft.ApplicationInsights.WindowsCollectors.Metadata | + Microsoft.ApplicationInsights.WindowsCollectors.Session); + this.InitializeComponent(); + this.Suspending += OnSuspending; + } + + /// + /// Invoked when the application is launched normally by the end user. Other entry points + /// will be used such as when the application is launched to open a specific file. + /// + /// Details about the launch request and process. + protected override void OnLaunched(LaunchActivatedEventArgs e) + { + +#if DEBUG + if (System.Diagnostics.Debugger.IsAttached) + { + this.DebugSettings.EnableFrameRateCounter = true; + } +#endif + + Frame rootFrame = Window.Current.Content as Frame; + + // Do not repeat app initialization when the Window already has content, + // just ensure that the window is active + if (rootFrame == null) + { + // Create a Frame to act as the navigation context and navigate to the first page + rootFrame = new Frame(); + + rootFrame.NavigationFailed += OnNavigationFailed; + + if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) + { + //TODO: Load state from previously suspended application + } + + // Place the frame in the current Window + Window.Current.Content = rootFrame; + } + + if (rootFrame.Content == null) + { + // When the navigation stack isn't restored navigate to the first page, + // configuring the new page by passing required information as a navigation + // parameter + rootFrame.Navigate(typeof(MainPage), e.Arguments); + } + // Ensure the current window is active + Window.Current.Activate(); + } + + /// + /// Invoked when Navigation to a certain page fails + /// + /// The Frame which failed navigation + /// Details about the navigation failure + void OnNavigationFailed(object sender, NavigationFailedEventArgs e) + { + throw new Exception("Failed to load Page " + e.SourcePageType.FullName); + } + + /// + /// Invoked when application execution is being suspended. Application state is saved + /// without knowing whether the application will be terminated or resumed with the contents + /// of memory still intact. + /// + /// The source of the suspend request. + /// Details about the suspend request. + private void OnSuspending(object sender, SuspendingEventArgs e) + { + var deferral = e.SuspendingOperation.GetDeferral(); + //TODO: Save application state and stop any background activity + deferral.Complete(); + } + } +} diff --git a/ReactWindows/Playground/ApplicationInsights.config b/ReactWindows/Playground/ApplicationInsights.config new file mode 100644 index 00000000000..cb2a232da35 --- /dev/null +++ b/ReactWindows/Playground/ApplicationInsights.config @@ -0,0 +1,3 @@ + + + diff --git a/ReactWindows/Playground/Assets/LockScreenLogo.scale-200.png b/ReactWindows/Playground/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..735f57adb5dfc01886d137b4e493d7e97cf13af3 GIT binary patch literal 1430 zcmaJ>TTC2P7~aKltDttVHYH6u8Io4i*}3fO&d$gd*bA_<3j~&e7%8(eXJLfhS!M@! zKrliY>>6yT4+Kr95$!DoD(Qn-5TP|{V_KS`k~E6(LGS@#`v$hQo&^^BKsw3HIsZBT z_y6C2n`lK@apunKojRQ^(_P}Mgewt$(^BBKCTZ;*xa?J3wQ7~@S0lUvbcLeq1Bg4o zH-bvQi|wt~L7q$~a-gDFP!{&TQfc3fX*6=uHv* zT&1&U(-)L%Xp^djI2?~eBF2cxC@YOP$+9d?P&h?lPy-9M2UT9fg5jKm1t$m#iWE{M zIf%q9@;fyT?0UP>tcw-bLkz;s2LlKl2qeP0w zECS7Ate+Awk|KQ+DOk;fl}Xsy4o^CY=pwq%QAAKKl628_yNPsK>?A>%D8fQG6IgdJ ztnxttBz#NI_a@fk7SU`WtrpsfZsNs9^0(2a z@C3#YO3>k~w7?2hipBf{#b6`}Xw1hlG$yi?;1dDs7k~xDAw@jiI*+tc;t2Lflg&bM)0!Y;0_@=w%`LW^8DsYpS#-bLOklX9r?Ei}TScw|4DbpW%+7 zFgAI)f51s}{y-eWb|vrU-Ya!GuYKP)J7z#*V_k^Xo>4!1Yqj*m)x&0L^tg3GJbVAJ zJ-Pl$R=NAabouV=^z_t;^K*0AvFs!vYU>_<|I^#c?>>CR<(T?=%{;U=aI*SbZADLH z&(f2wz_Y0??Tf|g;?|1Znw6}6U43Q#qNRwv1vp9uFn1)V#*4p&%$mP9x&15^OaBiDS(XppT|z^>;B{PLVEbS3IFYV yGvCsSX*m literal 0 HcmV?d00001 diff --git a/ReactWindows/Playground/Assets/SplashScreen.scale-200.png b/ReactWindows/Playground/Assets/SplashScreen.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..023e7f1feda78d5100569825acedfd213a0d84e9 GIT binary patch literal 7700 zcmeHLYj~4Yw%(;oxoEH#Kxq-eR|+VkP17b#Vk;?4QwkI+A{L04G+#<<(x#Un1#+h5>eArRq zTw$)ZvTWW_Y?bDho0nPVTh08+s`sp!j74rJTTtXIDww0SILedFv?sZ?yb@@}GN;#8 znk_b~Q(A0YR#uV4ef!osoV1M3;vQ8N$O|fStfgf$S5;ddUNv`tWtGjM;koG#N;7M< zP*84lnx(bn_KF&9Z5Ai$)#Cs3a|$OFw>WKCT$of*L7_CqQEinflT|W{JT+aKp-E0v zsxmYg)1(T>DROm+LN1eQw8}KCTp=C!$H7`PU!t9_Hw@TsTI2`udRZv*!a5`#A9hK6Y95L(CDUX&_@QxKV z_feX{UhA#ZWlvgpL$#w^D#lq`_A4AzDqd|Zv6y9PX&DNcN|l}_D^{q@GG&H^Pg583 z8FI6N8^H7b5WjGp;urW)d7F+_lcp%KsLX0viCmE(OHH+=%ZfD_=`voUuoUxFO^L;- z;!;2{g-YiiO6m4bs89OuF9!p{FGtH-f%8<2gY!h9s)4ciN%{Kh1+`}{^}M~+TDH9N z^Z5PlgVXMC&2&k*Hw^Lb9gny#ro$MOIxIt{+r)EA10$VR3 zanN8D{TUkl+v0CQ_>ZoHP<M-x#8@8ZiT#$Kh`(uRaX1g$Bg|qy$<#7 zSSAi{Nb8Y=lvNVeio+UGLCAtoLBfL`iOv`)yoJMDJBN>4IH@(l7YRF;61@>qq1iM9 zr@b#OC~SAxSle?5Pp8Z78{VO0YFr1x7kZU64Z23eLf2T2#6J_t;-E}DkB?NufZ0Ug zi?J&byXeaB-uTNVhuiM!UVQw}bZrJ3GtAETYp->!{q#zfN7D3AS9@Q7*V^85jGx#R z(QxYV(wW#F0XF9^^s>>H8pPlVJ>)3Oz z&_X8Sf@~?cH_O*cgi$U#`v`RRfv#y3m(ZpKk^5uLup+lVs$~}FZU$r_+}#hl%?g5m z-u-}-666ssp-xWQak~>PPy$mRc|~?pVSs1_@mBEXpPVfLF6(Ktf1S* zPPh@QZ=tFMs?LM2(5P3L2;l_6XX6s&cYsP1ip#eg0`ZEP0HGYh{UmS@o`MihLLvkU zgyAG0G`b1|qjxxh1(ODKFE%AP}Dq=3vK$P7TXP4GrM1kQ72!GUVMDl`rDC&2;TA}*nF z8$nQD&6ys_nc1*E7$*1S@R8$ymy(sQV}imGSedB@{!QR5P&N_H=-^o!?LsWs+2|mH z-e=)T^SvI)=_JIm7}j4;@*Z17=(#}m=~YF~z~CLI+vdAGlJDcdF$TM?CVI1%LhUrN zaa6DJ=Yh$)$k&Oz{-~8yw^GM^8prYxSxo zvI4k#ibryMa%%*8oI-5m61Koa_A_xg=(fwp0aBX{;X4Q;NXUhtaoJDo1>TqhWtn=_ zd5~chq#&6~c%8JZK#t_&J(9EVUU&upYeIovLt1>vaHe}UUq>#RGQj!EN#5+0@T`(@ z^g~>*c`VGRiSt;!$_4+0hk^I!@O3``5=sZ8IwlxWW7km1B&_t&E*u0_9UBa#VqwY* zz>nxv?FAsVnRaD(Bui=6i==BFUw0k4n$>`umU`F2l?7CYTD^)c2X+d9X&ddS9|gj? zM?knGkGCX&W8offw8aLC2$D{PjC3nVZwd4k?eZH8*mZ)U@3Qk8RDFOz_#WUA#vnzy zyP>KrCfKwSXea7}jgJjBc}PGY+4#6%lbZyjhy`5sZd_Vy6Wz;ixa?czkN}J9It1K6 zY!eu>|AwF^fwZlLAYyQI*lM@^>O>Iu6Vf6i>Q$?v!SeUS<{>UYMwz$*%Aq?w^`j{h z!$GZbhu=^D{&ET8;))LL%ZBDZkQqRd2;u~!d9bHGmLRhLDctNgYyjsuvoSZ#iVdoB z2!f--UUA#U;<{je#?cYt^{PIyKa%hW>}uepWMyAI{{Zo7?2>?$c9;whJae%oN|I-kpTQSx_C$Z&;f zi2i)qmEn=y4U0uvk)$m;zKfjPK@oc?I`}1Jzl$Q~aoKBd3kt7L#7gyt|A_qgz6ai< z=X%D1i!d2h?rHR^R8SUj&G||dkC?DT>{o#Yau<@uqVT{Xef&XG}5*E4aPk{}~ zplx&XhaV)&1EfI3Em;Bw#O5SV^c;{twb-1Rw)+=0!e_BLbd7tYmXCH0wrlOSS+~`7He8Iqx0{CN+DVit9;*6L~JAN zD&cyT)2?h}xnYmL?^)<7YyzZ3$FHU^Eg;DLqAV{#wv#Wj7S`Jdl1pX&{3(uZ?!uh} zDc$ZTNV*7le_W6}Hju~GMTxZQ1aWCeUc%!jv3MHAzt>Y-nQK%zfT*3ebDQA5b?iGn; zBjv3B+GhLTexd_(CzZDP4|#n5^~scvB6#Pk%Ho!kQ>yYw((Dv{6=$g3jT1!u6gORW zx5#`7Wy-ZHRa~IxGHdrp(bm%lf>2%J660nj$fCqN(epv@y!l9s7@k6EvxS{AMP>WY zX4$@F8^kayphIx-RGO$+LYl9YdoI5d|4#q9##`_F5Xnx`&GPzp2fB{-{P@ATw=X@~ z_|&^UMWAKD;jjBKTK(~o?cUFRK8EX=6>cXpfzg4ZpMB>*w_^8GSiT-Jp|xBOnzM+j z*09-@-~qJ(eqWq5@R4i^u4^{McCP(!3}C|v_WsTR*bIUxN(Nx`u##3B4{sE`Z`v8w zAwIG`?1~PkID~W{uDzmqH98Pew_1(;x2%8r^vY{)_&J2K)cN{W+h5+g)ZcjP&Ci#O zgy|8K@4kyMfwilHd&6TDlhb%++Pk!>9HRld6HT7gwyZGrxS$}CsD6`>6!!2K1@Mjf z(P0WYB7V_OFZyeWrbOFb>O54BNXf~K&?}3=^v;v_wT{DKr?jN^DtN&DXwX%u?s*c6`%8>WFz z7}YW^tp0bp^NriE)AB6M2l<7rn7fzePtR*omOevpfm9n?}2V*+0iW;S)C zhg`NAjL?D=W#k*$aR{>pGf~lD-rVtD;5jW1_*Jn1j1=es@Kcx4ySM_bwcQCT=d+DV z>Sz~L=Hj@(X%31nK$mWI@7d>}ORB`K(p=+`UD)+99YUGQc7y^bHZ1F(8|tL0 zdK*DT0kSXG_{BKTpP2*2PecdKV9;dq$^ZZDP;Nyq1kp-&GI5eAyZsK!e3V zK@rPy*{(`KIfo+lc878mDKk^V#`VT05}64kBtk%DgwLrOvLMj5-;*GNKv6c6pzMuL z6EP%ob|_0IW}lLRXCP2!9wWhEw3LA7iF#1O1mIZ@Z=6&bz41F;@S_GvYAG-#CW3z{ zP3+6vHhvP&A3$##Vo9$dT^#MoGg^|MDm=Bt1d2RRwSZ<;ZHICpLBv5Xs!D?BH^(9_ z7`H=N&^v|Z-%mP}wNzG{aiFCsRgwzwq!N6obW9+7(R; z(SZ=23`|`>qil!LMGG{_Heq!BD>(Y-zV9wD)}hz25JA37YR%39;kI4y9pgtcUass6 zP24}ZY$vvYeI`zy&)A_X#nY3017ap*0&jx|mVwyGhg3;!keU53a}Uhm3BZI$N$6Se zLWlAmy1S0xKJm4G_U@sN_Tm=`$xWJSEwKU98rZ&)1R^*$$1vA3oG#&*%SMxY_~oGP zP&PFJatFLM-Ps%84IV-+Ow)T{C7cqUAvauy4C z(FRz&?6$Rypj{xO!`y=*J5o4@U8Q-(y5(*=YoKeZ+-1YdljXxkA#B)zo=FeQH#?Le zycNUmEEHWO9a=X^pb#&cOq7-`7UA87#|S22)<7RUtZo|(zibX=w;K3qur9vy#`MNV z6UUcf9ZwEnKCCp+OoBnF@OdbvH)ANXO0o~Pi9l8=x3))}L<#vO0-~O4!~--Ket?d} zJaqsj<@CD1%S2cTW%rOP{Vto%0sGW~1RMa_j^)5nil0Yw- z0EE#bP+l4#P^%PQ+N*oxu1Zq05xZ!bXfYTg>9c{(Iw*lnjR^>kz%lAN^zFce7rppy zY8zA~3GD=A6d*hze&l4D_wA~+O!56)BZTe_rEu}Ezi<4!kG|W#amBZ5{&XS2@6R~H z{9o^y*BkH4$~yX9U&@CgbOzX1bn9xqF|zh$Dh0Y5y*E0e90*$!ObrHY3Ok0`2=O~r zCuke6KrP9KOf?V(YDsM<6pX2nVoN%M$LT^q#FmtaF?1^27F*IcNX~XRB(|hCFvdcc zc)$=S-)acdk$g4?_>jRqxpI6M3vHZk?0c^3=byamYDNf;uB{3NlKW5IhnOS3DNkMV z?tK8?kJ}pmvp%&&eTVOVjHP`q34hN1@!aK}H(K!vI`~gf|Gv+FNEQD5Yd<~yX7k_l h&G-K)@HZb3BABY{)U1?^%I#E6`MGoTtustd{~yM6srvu` literal 0 HcmV?d00001 diff --git a/ReactWindows/Playground/Assets/Square150x150Logo.scale-200.png b/ReactWindows/Playground/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..af49fec1a5484db1d52a7f9b5ec90a27c7030186 GIT binary patch literal 2937 zcma)84OCO-8BSud5)jwMLRVKgX(S?$n?Ld|vrsm<$CF7)&zTbyy1FE5bU`Q17MRv`9ue$;R(@8kR;#vJ*IM0>cJIAOte!d7oRgdH zd%ySjdB6L9=gX^A6)VzH7p2l@v~3zJAMw|DFy#^)F@@F*`mqUn=Il>l)8_+ab;nOW{%+iPx z+s{Eu|&pIs)Z7{La9~?xKfyl z#43?gjEL15d4WbOZo#SiP%>DB^+BcnJ=7dHEe;r#G=tuw|ka z%q@}##Uh7;tc%L_64m(kHtw74ty%BJMb)_1)#S0j`)F8_1jF7vScpsnH=0V19bO8y zR`0SjIdCUo&=>JwMQF8KHA<{ODHTiQh}0^@5QRmCA?gOH6_H3K^-_sNB^RrdNuK-R zOO*vOrKCVvDwgUck`kF(E7j{I#iiN;b*ZdCt4m@HPA`EuEqGGf4%!K<;(=I=&Vyrw z%TwcWtxa}8mCZ%Cyf&ActJ6_$ox5z6-D!0-dvnRx6t7y3d+h6QYpKWO;8OdnvERo7 zuEf>ih5`wqY)~o@OeVt-wM?Q!>QzdGRj!bz6fzYrfw$hZfAKzr2-M+D+R>}~oT574c;_3zquHcElqKIsryILt3g8n3jcMb+j?i?-L3FpZJ z2WRVBRdDPc+G5aaYg#5hpE+6nQ|(VSoxT3|biF;BUq#==-27Xi=gihDPYP$7?=9cP zYKE$jeQ|3~_L0VG-(F~2ZPyD0=k{J4Q~h(t__{-mz_w8{JDY9{`1ouzz!Vr5!ECdE z6U~O1k8c}24V7~zzXWTV-Pe4)y}wQJS&q%H5`Fo_f_JvIU489aCX$;P`u#!I-=^4ijC2{&9!O&h>mi?9oYD=GC#%)6{GzN6nQYw+Fal50!#x^asjBBR50i`+mho*ttoqV)ubM2KD9S~k7+FR4>{29?6 z{!l6kDdyTN0YJ9LgkPWeXm|gyi@zM3?0@{&pXT12w|78&W-q!RRF)&iLCEZVH<|fR zN0fr2^t8H(>L?>K#>^+jWROLral(Qy-xoBq1U7A&DV||wClb)Otd9?(gZ|8znMF}D zf<1haWz^s0qgecz;RFGt0C-B4g`jNGHsFU+;{<%t65v^sjk^h$lmWn#B0#_)9ij&d z-~lc`A)YYExi^7sBuPM^Y|wA2g*5?`K?#7tzELQYNxGo$UB$4J8RJp1k(8Jj+~hMT zlN~>M@KTTh^--8y3PK_NZ@AC!{PT=CziBzGd+wTJ^@icH!Bd}%)g8V)%K?|c&WTUk zy}qv1C%(fjRoZ4ozC3{O%@5?)XzH35zHns$pgU*Q?fj4v?fp1Qbm+j;3l;9jam9Da zXVcKjPlQ73x78QPu|Ffm6x?`~e3oD=gl=4kYK?={kD5j~QCXU)`HSdduNNENzA*2$ zOm3PzF!lN5e*06-f1Uot67wY#{o-S1!KZ7E=!~7ynnk9_iJR#kFoNbAOT#^2Gd17F zMmvU6>lndZQGd|ax9kUoXXO+$N?|j@6qpsF&_j7YXvwo_C{JpmLw5&#e6k>atv%es z5)7r*Wvv_JkUpT}M!_o!nVlEk1Zbl=a*2hQ*<|%*K1Glj^FcF`6kTzGQ3lz~2tCc@ z&x|tj;aH&1&9HwcJBcT`;{?a+pnej;M1HO(6Z{#J!cZA04hnFl;NXA+&`=7bjW_^o zfC40u3LMG?NdPtwGl>Tq6u}*QG)}-y;)lu-_>ee3kibW(69n0$0Zy!}9rQz%*v1iO zT9_H>99yIrSPYVy6^);rR}7Yo=J_T@hi+qhTZXnVWyf;JDYm5#eYLTxr*?kiNn!+Y zQ+LUkBafNJ#rH#C(?d5^;gw9o#%daEI{mA*LHPIHPU`#|H$hD zwm>0&+kahQ)E#%~k>&5@&#Vg82H?s%71=)(soi@174pi9--2{w{1$}Sz4zGn3Du&x bht0Iza^2ykEt4(epJ78uh5nDlX8(TxzDYwP literal 0 HcmV?d00001 diff --git a/ReactWindows/Playground/Assets/Square44x44Logo.scale-200.png b/ReactWindows/Playground/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..ce342a2ec8a61291ba76c54604aea7e9d20af11b GIT binary patch literal 1647 zcmaJ?eM}Q)7(e+G1Q(|`V9JhTI2>MkceK4;p;PR&$Pi?ejk3YQ_3o`S&|W_dsOZ8# zWPTt69g`t$ab`0cj-Y0yiBSOqmd)tG7G(}M5aP0_%&9TijB#&)I{zSE^4@#z^FF`l z`8{8`o%wlL(UI|y2!cdsuVamHH~H86F!*-15em4)NqUpCQM5?aoC_eCf@lV4wvF2a zjDQn1JBL69f&@2M3rvzJcfE!eZ8FZUBlFlC5RD)it33{mF9#B82AiyQE%w)`vlwa> zv{<1sm&kSKK$&%2jSFn7$t&P%%6Ue>R=EAnG8N7fqynWG8L3p!4801a;8{+nliO(qd(jNJ_?+9W3#hLIDLoT6~3fx9=`CC-D}-AMrpEO7HK zt3$GicGPc?GmDjy7K2P@La;eu4!$zWCZ`ym{Z$b zu-O6RM&K4JT|BIZB`E-gxqG%FzanI#+2FFmqHqXG7yxWB=w55RGOM)$xMb(>kSNR z2w=1AZi%z=AmG~yea~XaXJR!v7vLn(RUnELfiB1|6D84ICOS}^Zo2AdN}<&*h}G_u z{xZ!(%>tLT3J3<5XhWy-tg+6)0nmUUENLW8TWA{R6bgVd3X;anYFZ^IRis*_P-C-r z;i>%1^eL3UI2-{w8nuFFcs0e~7J{O2k^~Ce%+Ly4U?|=!0LH=t6()xi<^I-rs+9sF z*q{E-CxZbGPeu#a;XJwE;9S1?#R&uns>^0G3p`hEUF*v`M?@h%T%J%RChmD|EVydq zmHWh*_=S%emRC*mhxaVLzT@>Z2SX0u9v*DIJ@WC^kLVdlGV6LpK$KIrlJqc zpJ921)+3JJdTx|<`G&kXpKkjGJv=76R`yYIQ{#c-`%+`#V(7}Q;&@6U8!Td1`d;?N z_9mnI#?AA}4J!r)LN4!E-@H5eXauuB7TOawS>Y|{-P?NNx-lq+z1W-+y(;39P&&LP zL{N80?&=C*qKmdA^moMZRuPcD!B<*mq$ch=0Cnlitw#txRWhb3%TQvPqjkC`F69G4b! ze7z9MZ#+;_#l?H37UqUhDFb^l&s2{oM$3I0o^Q!yx;;V)QmCMo)Tb_ui|mit8MS?U zm##6$sZZ1$@|s%?l@>4Z<*Q}sRBSKMhb4I{e5LdEhsHIHTe8Bod5c>6QtT>$XgUBz z6MK`kO$=jmt@FqggOhJ5j~e@ygRbG;<{Vu)*+nn9aQeo0;$#j;|MS=S$&L?BeV25z xs3B`@=#`5TF{^6(A1rvdY@|-RtQ|iS5{tyX+wH?;n8E)G$kykv-D^wh{{!TZT%7;_ literal 0 HcmV?d00001 diff --git a/ReactWindows/Playground/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/ReactWindows/Playground/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 0000000000000000000000000000000000000000..f6c02ce97e0a802b85f6021e822c89f8bf57d5cd GIT binary patch literal 1255 zcmaJ>TWs4@7*5+{G#S+&C!qC#> zf>5N3P6jO*Cz>ug*(_DmW=)kea&m$gZ^+nyiF`;j%w@}y8)>p*SH}C`m?DXeieF2U zyQHecc_L%Gh!7GMt+hG06y;+|p4>m~}PjA}rKViGiEnn7G0ZO<>G|7q;2?NwGCM3s?eued6%hd$B+ z*kQJ{#~$S=DFE(%=E+UkmlEI*%3llUf~8Ja9YU1Vui0IbGBkW_gHB%Rd&!!ioX zs40O?i9I{};kle7GMvE7(rk`la=gTI)47=>%?q@^iL-nUo3}h4S}N-KHn8t5mVP8w z&bSErwp+37 zNJJ8?a|{r5Q3R0Z5s-LB1WHOwYC@7pCHWND#cL1cZ?{kJ368_*(UDWUDyb<}0y@o# zfMF016iMWPCb6obAxT$JlB6(2DrlXDTB&!0`!m??4F(qWMhjVZo?JXQmz`1*58Z=& zcDmB|S-E@j?BoFGix0flckqdS4jsPNzhfWyWIM98GxcLs89C(~dw%$_t;JjX-SD}E zfiGV;{8Q%8r}w9x>EEigW81>`kvnU@pK)4+xk9@+bNj9L!AAZ@SZ@q|)&BmY3+HZx zul~BeG4|}-;L%cHViQGQX?^zFfO0&#cHwel=d`lH9sJ-@Sl@n*(8J2>%Ac`IxyY?Q z{=GhWvC#gu-~Ia7*n{=+;qM?Ul_wy1+u7ho;=`>EwP^g~R@{unBds`!#@}tluZQpS zm)M~nYEifJWJGx?_6DcTy>#uh%>!H9=hb^(v`=m3F1{L>db=<5_tm+_&knAQ2EU$s Mu9UqpbNZeC0BbUo^Z)<= literal 0 HcmV?d00001 diff --git a/ReactWindows/Playground/Assets/StoreLogo.png b/ReactWindows/Playground/Assets/StoreLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..7385b56c0e4d3c6b0efe3324aa1194157d837826 GIT binary patch literal 1451 zcmaJ>eN5D57_Z|bH;{0+1#mbl)eTU3{h)Wf7EZV?;HD@XL@{B`Ui%(2aMxQ~xdXSv z5nzWi(LW)U2=Vc-cY@s7nPt{i0hc6!7xN4NNHI#EQl>YNBy8l4%x9gr_W-j zEZMQmmTIy(>;lblRfh`dIyTgc9W5d!VP$L4(kKrN1c5G~(O_#xG zAJCNTstD^5SeXFB+&$h=ToJP2H>xr$iqPs-#O*;4(!Fjw25-!gEb*)mU}=)J;Iu>w zxK(5XoD0wrPSKQ~rbL^Cw6O_03*l*}i=ydbu7adJ6y;%@tjFeXIXT+ms30pmbOP%Q zX}S;+LBh8Tea~TSkHzvX6$rYb)+n&{kSbIqh|c7hmlxmwSiq5iVhU#iEQ<>a18|O^Sln-8t&+t`*{qBWo5M?wFM(JuimAOb5!K#D}XbslM@#1ZVz_;!9U zpfEpLAOz=0g@bd6Xj_ILi-x^!M}73h^o@}hM$1jflTs|Yuj9AL@A3<-?MV4!^4q`e z)fO@A;{9K^?W?DbnesnPr6kK>$zaKo&;FhFd(GYFCIU^T+OIMb%Tqo+P%oq(IdX7S zf6+HLO?7o0m+p>~Tp5UrXWh!UH!wZ5kv!E`_w)PTpI(#Iw{AS`gH4^b(bm^ZCq^FZ zY9DD7bH}rq9mg88+KgA$Zp!iWncuU2n1AuIa@=sWvUR-s`Qb{R*kk(SPU^`$6BXz8 zn#7yaFOIK%qGxyi`dYtm#&qqox0$h=pNi#u=M8zUG@bpiZ=3sT=1}Trr}39cC)H|v zbL?W)=&s4zrh)7>L(|cc%$1#!zfL?HjpeP%T+x_a+jZ16b^iKOHxFEX$7d|8${H-* zIrOJ5w&i$>*D>AKaIoYg`;{L@jM((Kt?$N$5OnuPqVvq**Nm}(f0wwOF%iX_Pba;V z;m@wxX&NcV3?<1+u?A{y_DIj7#m3Af1rCE)o`D&Y3}0%7E;iX1yMDiS)sh0wKi!36 zL!Wmq?P^Ku&rK~HJd97KkLTRl>ScGFYZNlYytWnhmuu|)L&ND8_PmkayQb{HOY640 bno1(wj@u8DCVuFR|31B*4ek@pZJqxCDDe1x literal 0 HcmV?d00001 diff --git a/ReactWindows/Playground/Assets/Wide310x150Logo.scale-200.png b/ReactWindows/Playground/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..288995b397fdbef1fb7e85afd71445d5de1952c5 GIT binary patch literal 3204 zcmbVPeQXow8NYmBd90>}0NP?GhXW~VaeThm=a0tV#EwJMI!)6M3}|c4_Bl3=Kd>G0 z(GHx1wl<7(tP?FsOQkTilSo*iIvF%uArExJ73~P zSv1xEy!U(Wd4A9D`FQV@W3@F^qJ@PEF$@z`Z!*BbFsS(^?B zyiAzJ+q})bkgiQHWqEb*jJD-coHYr1^iocg)l!Qa{Xqs-l~6J}p-|##ZHYofskQ3$ zI0;xzXyhazBeXhIsg5A=%ufo@f)1yy&ScKS0;HF^!r_2UE^lpZEom(+@duma3awTv zCrCL-%D_SvYWIcdHkmI}#50(fkUi)Qgx!80ju>g1za^}ff>JI8Z@^-iCiaCgg@TgF z+vtE?Q9{VQUX&MW9SYYmGcxA14%N2@7FwBTD4N<(2{nWgV8$e3?-F=L^&FrtWn~(U_Q~~^uYiyeY6-KoTnfh9AWz@ zIKje0)u!_Lw)E}G!#kEfwKVdNt(UAf9*f>tEL_(=xco-T%jTi@7YlC3hs2ik%Le0H ztj}RTeCF(5mwvi3_56>-yB?l;J>-1%!9~=fs|QcNG3J~a@JCu`4SB460s0ZO+##4fFUSGLcj_ja^fL4&BKALfb#$6$O?>P@qx2Agl^x0i&ugt zsy5Pyu=()`7HRMG3IB7F1@`_ z+-!J%#i6e^U$e#+C%Q>_qVRzWRsG^W_n+@OcX@vzI&z;mzHNb!GQ?LWA(wtpqHqTM z1OFw_{Zn?fD)p)`c`kOgv{de=v@suGRqY{N^U7gI1VF3*F=obwaXI6ob5__Yn zVTguS!%(NI09J8x#AO_aW!9W7k*UvB;IWDFC3srwftr{kHj%g)fvnAm;&h_dnl~

MY- zf+K}sCe8qU6Ujs`3ua{U0Of$R_gVQBuUA za0v=mu#vIOqiiAZOr&h*$WyOw&k-xr$;G4Ixa!#TJNr>95(h>l%)PUy4p+^SgR(uR zta%k*?ny-+nAr8spEk1fo{J4i!b^Fia`N{_F6@zidA2ZTTrjl#^5Z-2KfB@Cu}l9s z(*|Z2jc?p~vn2f)3y9i*7zJV1L{$?|&q)4oaT;uXi6>1GkRXVTOzAz(RHEmr=eFIi z`}<>-Q?K0GN8!IYxeP1XKXO+jsJbp~o^);Bc;%b7Flpe7;1`Ny@3r7ZR;?R)aJt8C ziNlEC<@3f_lIV4TwV}&e;D!Ee5_|e#g0LUh=5vmYWYm7&2h*M>QPKvGh9-)wfMMW3 z8J9b%1k7dzPzO0_NGQy92BZ^FR6R~6;^6?lqO;-QUP4BY%cG%3vEhbm#>4vIhPBh3 z-+pZGjh$x%Hp{?=FHsMp0&wNPlj00us{&`1ZOZTqs8%4X&xH=UDr*xyBW(Zp&Em94 zf)ZSfn#yg0N)>!1kWdkqJ^S*z0FF5|fj&qcE#Na|%OY0$uO>!&hP+1ywfD_WXk@4J(?MBftK7>$Nvqh@tDuarN%PrTLQ2Uzysx>UV=V zk^RrDSvdQ?0;=hY67EgII-f4`t=+i*yS=Y~!XlqIy_4x&%+OdfbKOFPXS2X5%4R{N z$SQMX^AK6(fA + + + + + diff --git a/ReactWindows/Playground/MainPage.xaml.cs b/ReactWindows/Playground/MainPage.xaml.cs new file mode 100644 index 00000000000..c4d289d619e --- /dev/null +++ b/ReactWindows/Playground/MainPage.xaml.cs @@ -0,0 +1,46 @@ +using ReactNative.Hosting; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +// The Blank Page item template is documented at http://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409 + +namespace Playground +{ + ///

+ /// An empty page that can be used on its own or navigated to within a Frame. + /// + public sealed partial class MainPage : Page + { + public MainPage() + { + this.InitializeComponent(); + } + + protected override void OnNavigatedTo(NavigationEventArgs e) + { + base.OnNavigatedTo(e); + + try + { + var runtime = new JavaScriptRuntime(); + var context = runtime.CreateContext(); + } + catch (Exception ex) + { + + } + } + } +} diff --git a/ReactWindows/Playground/Package.appxmanifest b/ReactWindows/Playground/Package.appxmanifest new file mode 100644 index 00000000000..12652801043 --- /dev/null +++ b/ReactWindows/Playground/Package.appxmanifest @@ -0,0 +1,49 @@ + + + + + + + + + + Playground + ericroz + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ReactWindows/Playground/Playground.csproj b/ReactWindows/Playground/Playground.csproj new file mode 100644 index 00000000000..981e75778dd --- /dev/null +++ b/ReactWindows/Playground/Playground.csproj @@ -0,0 +1,149 @@ + + + + + Debug + x86 + {D52267B5-396F-424A-BB26-C9E750032846} + AppContainerExe + Properties + Playground + Playground + en-US + UAP + 10.0.10586.0 + 10.0.10240.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + Playground_TemporaryKey.pfx + + + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + true + + + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + true + true + + + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + true + + + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + true + true + + + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + true + + + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + true + true + + + + + PreserveNewest + + + + + + App.xaml + + + MainPage.xaml + + + + + + Designer + + + + + + + + + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + + + {c7673ad5-e3aa-468c-a5fd-fa38154e205c} + ReactNative + + + + 14.0 + + + + \ No newline at end of file diff --git a/ReactWindows/Playground/Playground_TemporaryKey.pfx b/ReactWindows/Playground/Playground_TemporaryKey.pfx new file mode 100644 index 0000000000000000000000000000000000000000..65693d193273b4b63c5b34b3274a21289b45418a GIT binary patch literal 2454 zcmZve2{=^!7sv0Nxnqnyd(uR<$yn~#muN(a7)#2&6p1m}szFI_m()a-Y`qvEQORCO zl%!Dw~em!%Wlja+&)-ArTalLA`Kclp-L z9?bE7!maoOW=8K#-;yYD(J#8Nqiev1`BbvXqes%>47Blbs#U$SXW)GWCT8IA%AIGf zNoDOE^1`P`_4{ol9Lp?5OxVS@G7pt0|&zk#)BlkG%m`eMq^ijsYRa^>^h9@h`o ziPw|gmkQ5j(Q}K$@)$8YiCy6Xs+f^h_0=!TkVBa>OS|I z@~rz)A75&kZ3s9QzqG8UCSmB=qc7ew7*1@+C&A2$SE3}*sm8Y##3%YWpkT26P^56f z)c$a4q{2Pw2@m_uK83Govc5asO7u%quXW$NVnW=)UH2mS6+ZUjw2sy! zRp$&V)h!?Fouo*{R)7?5?;!l)$vA3T6hQyFAZ+15K z9OmB=Il5#p>)N!JK9p0pu|cLb+H8Z3vSU|-4)OG*b=JG4R_#;9J9p&Egc&+_2p*^k zDj8fn6Xc7L^4ONM_NBt7OE%#2&;m(Vjw3I7cJ`%E>;-Sm_-&)Pd(9`ms?Z-dz3;&} z39^RW4rLqGD!Vna>cf~-4wCbT#59;>bFX?oEs-sZ3%*RXc3aMs)Czu_1v!vAQ`S>e z&tjQIF0yS#YISAf3BAV74EAA*4I(AM@v)+ z4k@WSzcE>^AMsIK3*F?Qq8oL~U8(eyi_OeuucOjt13-it9bh44s4%1lu zjIwi42lN>|0vxjsF4n*o{6ty~BWm5uu%Vp5Dz$X?w&M1T+1;1J$G%xZ&HG+hioc?X zCwr$9^?7b&r@fWBke+jX$Ua^EgmvTbV&Ae9=Y4Z$sX4TJ9j>;ILK!7(P_lwKi9npX zuFT7C;?t=1iK!e}G%QitiFhTujT;VZZ#2pq_4Lt|0_dVt3nhAhHd?_4(1hK&2xKl00ukK} zAjOtQ1FQjmG&B?l0*;~P3mgYzfPLutg9HLcP-Tkx5kN4?f>0fbmJ0^_erCx4wm;*T zsPaKu355}?_OH%=d>F8b5Q5!`Vj^l%zfXT`E`nKr5zO4L*-sEh6EH&%g6T*3UoZkb z%Ee+p3`R)Y%5g-o`CKunQ`6P^qb*`BVOD4gzldy)=jeum~s!pq=nw5D)~CSG$hADejeM z8oR%GvL@%%t+fOwy-TW+<=Xk!|4iLY>6p6Nd3fT&UYcxpzIr~BjWhH{lthCO_gW({($ zO!qO1O%~5qX)8f5ul;G^{rOqDf@0-*vi?eqP>J*`=n$qI987*9*uuxtFL^h&Obys< z$AdIhzQWbq0=qN+2VEYEpI3ZhAOEo$giKUJx*&D_x(dZM_JSv2st;2MY zzFn+AZFhQ7qMM5M8P0r7M!!nN=y{$+8JqNsgI_I1`*bnUU4oV*90m&l;58Dg2=k%4 zy;%eV*C7lHz~VoY!Vkp<^Kfya4;T)I$6|lqOY}COdkF$(rVk{V?3p##wZsn>IkvmX z#Txa(BVwj}k|s->wZoRvycPVi+|PZl)dbcuJsOdq?M|G)@;sZ8%HN#IOihErVrilIf3vUQ-f$>~8vH%t%1&PA@ wjCuQ7@~5QYydBf8hPiETi@F1e)>RZM4VFOV=Qqd=9$q9@+1zB8{rE%r2NT8z!T + + To enable dynamic creation of the specific instantiation of AppClass over System.Int32 + + + Using the Namespace directive to apply reflection policy to all the types in a particular namespace + +--> + + + + + + + + + + + + \ No newline at end of file diff --git a/ReactWindows/Playground/project.json b/ReactWindows/Playground/project.json new file mode 100644 index 00000000000..e3b2dba25fa --- /dev/null +++ b/ReactWindows/Playground/project.json @@ -0,0 +1,19 @@ +{ + "dependencies": { + "Microsoft.ApplicationInsights": "1.0.0", + "Microsoft.ApplicationInsights.PersistenceChannel": "1.0.0", + "Microsoft.ApplicationInsights.WindowsApps": "1.0.0", + "Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0" + }, + "frameworks": { + "uap10.0": {} + }, + "runtimes": { + "win10-arm": {}, + "win10-arm-aot": {}, + "win10-x86": {}, + "win10-x86-aot": {}, + "win10-x64": {}, + "win10-x64-aot": {} + } +} \ No newline at end of file diff --git a/ReactWindows/ReactNative.Tests/Assets/LockScreenLogo.scale-200.png b/ReactWindows/ReactNative.Tests/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..735f57adb5dfc01886d137b4e493d7e97cf13af3 GIT binary patch literal 1430 zcmaJ>TTC2P7~aKltDttVHYH6u8Io4i*}3fO&d$gd*bA_<3j~&e7%8(eXJLfhS!M@! zKrliY>>6yT4+Kr95$!DoD(Qn-5TP|{V_KS`k~E6(LGS@#`v$hQo&^^BKsw3HIsZBT z_y6C2n`lK@apunKojRQ^(_P}Mgewt$(^BBKCTZ;*xa?J3wQ7~@S0lUvbcLeq1Bg4o zH-bvQi|wt~L7q$~a-gDFP!{&TQfc3fX*6=uHv* zT&1&U(-)L%Xp^djI2?~eBF2cxC@YOP$+9d?P&h?lPy-9M2UT9fg5jKm1t$m#iWE{M zIf%q9@;fyT?0UP>tcw-bLkz;s2LlKl2qeP0w zECS7Ate+Awk|KQ+DOk;fl}Xsy4o^CY=pwq%QAAKKl628_yNPsK>?A>%D8fQG6IgdJ ztnxttBz#NI_a@fk7SU`WtrpsfZsNs9^0(2a z@C3#YO3>k~w7?2hipBf{#b6`}Xw1hlG$yi?;1dDs7k~xDAw@jiI*+tc;t2Lflg&bM)0!Y;0_@=w%`LW^8DsYpS#-bLOklX9r?Ei}TScw|4DbpW%+7 zFgAI)f51s}{y-eWb|vrU-Ya!GuYKP)J7z#*V_k^Xo>4!1Yqj*m)x&0L^tg3GJbVAJ zJ-Pl$R=NAabouV=^z_t;^K*0AvFs!vYU>_<|I^#c?>>CR<(T?=%{;U=aI*SbZADLH z&(f2wz_Y0??Tf|g;?|1Znw6}6U43Q#qNRwv1vp9uFn1)V#*4p&%$mP9x&15^OaBiDS(XppT|z^>;B{PLVEbS3IFYV yGvCsSX*m literal 0 HcmV?d00001 diff --git a/ReactWindows/ReactNative.Tests/Assets/SplashScreen.scale-200.png b/ReactWindows/ReactNative.Tests/Assets/SplashScreen.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..023e7f1feda78d5100569825acedfd213a0d84e9 GIT binary patch literal 7700 zcmeHLYj~4Yw%(;oxoEH#Kxq-eR|+VkP17b#Vk;?4QwkI+A{L04G+#<<(x#Un1#+h5>eArRq zTw$)ZvTWW_Y?bDho0nPVTh08+s`sp!j74rJTTtXIDww0SILedFv?sZ?yb@@}GN;#8 znk_b~Q(A0YR#uV4ef!osoV1M3;vQ8N$O|fStfgf$S5;ddUNv`tWtGjM;koG#N;7M< zP*84lnx(bn_KF&9Z5Ai$)#Cs3a|$OFw>WKCT$of*L7_CqQEinflT|W{JT+aKp-E0v zsxmYg)1(T>DROm+LN1eQw8}KCTp=C!$H7`PU!t9_Hw@TsTI2`udRZv*!a5`#A9hK6Y95L(CDUX&_@QxKV z_feX{UhA#ZWlvgpL$#w^D#lq`_A4AzDqd|Zv6y9PX&DNcN|l}_D^{q@GG&H^Pg583 z8FI6N8^H7b5WjGp;urW)d7F+_lcp%KsLX0viCmE(OHH+=%ZfD_=`voUuoUxFO^L;- z;!;2{g-YiiO6m4bs89OuF9!p{FGtH-f%8<2gY!h9s)4ciN%{Kh1+`}{^}M~+TDH9N z^Z5PlgVXMC&2&k*Hw^Lb9gny#ro$MOIxIt{+r)EA10$VR3 zanN8D{TUkl+v0CQ_>ZoHP<M-x#8@8ZiT#$Kh`(uRaX1g$Bg|qy$<#7 zSSAi{Nb8Y=lvNVeio+UGLCAtoLBfL`iOv`)yoJMDJBN>4IH@(l7YRF;61@>qq1iM9 zr@b#OC~SAxSle?5Pp8Z78{VO0YFr1x7kZU64Z23eLf2T2#6J_t;-E}DkB?NufZ0Ug zi?J&byXeaB-uTNVhuiM!UVQw}bZrJ3GtAETYp->!{q#zfN7D3AS9@Q7*V^85jGx#R z(QxYV(wW#F0XF9^^s>>H8pPlVJ>)3Oz z&_X8Sf@~?cH_O*cgi$U#`v`RRfv#y3m(ZpKk^5uLup+lVs$~}FZU$r_+}#hl%?g5m z-u-}-666ssp-xWQak~>PPy$mRc|~?pVSs1_@mBEXpPVfLF6(Ktf1S* zPPh@QZ=tFMs?LM2(5P3L2;l_6XX6s&cYsP1ip#eg0`ZEP0HGYh{UmS@o`MihLLvkU zgyAG0G`b1|qjxxh1(ODKFE%AP}Dq=3vK$P7TXP4GrM1kQ72!GUVMDl`rDC&2;TA}*nF z8$nQD&6ys_nc1*E7$*1S@R8$ymy(sQV}imGSedB@{!QR5P&N_H=-^o!?LsWs+2|mH z-e=)T^SvI)=_JIm7}j4;@*Z17=(#}m=~YF~z~CLI+vdAGlJDcdF$TM?CVI1%LhUrN zaa6DJ=Yh$)$k&Oz{-~8yw^GM^8prYxSxo zvI4k#ibryMa%%*8oI-5m61Koa_A_xg=(fwp0aBX{;X4Q;NXUhtaoJDo1>TqhWtn=_ zd5~chq#&6~c%8JZK#t_&J(9EVUU&upYeIovLt1>vaHe}UUq>#RGQj!EN#5+0@T`(@ z^g~>*c`VGRiSt;!$_4+0hk^I!@O3``5=sZ8IwlxWW7km1B&_t&E*u0_9UBa#VqwY* zz>nxv?FAsVnRaD(Bui=6i==BFUw0k4n$>`umU`F2l?7CYTD^)c2X+d9X&ddS9|gj? zM?knGkGCX&W8offw8aLC2$D{PjC3nVZwd4k?eZH8*mZ)U@3Qk8RDFOz_#WUA#vnzy zyP>KrCfKwSXea7}jgJjBc}PGY+4#6%lbZyjhy`5sZd_Vy6Wz;ixa?czkN}J9It1K6 zY!eu>|AwF^fwZlLAYyQI*lM@^>O>Iu6Vf6i>Q$?v!SeUS<{>UYMwz$*%Aq?w^`j{h z!$GZbhu=^D{&ET8;))LL%ZBDZkQqRd2;u~!d9bHGmLRhLDctNgYyjsuvoSZ#iVdoB z2!f--UUA#U;<{je#?cYt^{PIyKa%hW>}uepWMyAI{{Zo7?2>?$c9;whJae%oN|I-kpTQSx_C$Z&;f zi2i)qmEn=y4U0uvk)$m;zKfjPK@oc?I`}1Jzl$Q~aoKBd3kt7L#7gyt|A_qgz6ai< z=X%D1i!d2h?rHR^R8SUj&G||dkC?DT>{o#Yau<@uqVT{Xef&XG}5*E4aPk{}~ zplx&XhaV)&1EfI3Em;Bw#O5SV^c;{twb-1Rw)+=0!e_BLbd7tYmXCH0wrlOSS+~`7He8Iqx0{CN+DVit9;*6L~JAN zD&cyT)2?h}xnYmL?^)<7YyzZ3$FHU^Eg;DLqAV{#wv#Wj7S`Jdl1pX&{3(uZ?!uh} zDc$ZTNV*7le_W6}Hju~GMTxZQ1aWCeUc%!jv3MHAzt>Y-nQK%zfT*3ebDQA5b?iGn; zBjv3B+GhLTexd_(CzZDP4|#n5^~scvB6#Pk%Ho!kQ>yYw((Dv{6=$g3jT1!u6gORW zx5#`7Wy-ZHRa~IxGHdrp(bm%lf>2%J660nj$fCqN(epv@y!l9s7@k6EvxS{AMP>WY zX4$@F8^kayphIx-RGO$+LYl9YdoI5d|4#q9##`_F5Xnx`&GPzp2fB{-{P@ATw=X@~ z_|&^UMWAKD;jjBKTK(~o?cUFRK8EX=6>cXpfzg4ZpMB>*w_^8GSiT-Jp|xBOnzM+j z*09-@-~qJ(eqWq5@R4i^u4^{McCP(!3}C|v_WsTR*bIUxN(Nx`u##3B4{sE`Z`v8w zAwIG`?1~PkID~W{uDzmqH98Pew_1(;x2%8r^vY{)_&J2K)cN{W+h5+g)ZcjP&Ci#O zgy|8K@4kyMfwilHd&6TDlhb%++Pk!>9HRld6HT7gwyZGrxS$}CsD6`>6!!2K1@Mjf z(P0WYB7V_OFZyeWrbOFb>O54BNXf~K&?}3=^v;v_wT{DKr?jN^DtN&DXwX%u?s*c6`%8>WFz z7}YW^tp0bp^NriE)AB6M2l<7rn7fzePtR*omOevpfm9n?}2V*+0iW;S)C zhg`NAjL?D=W#k*$aR{>pGf~lD-rVtD;5jW1_*Jn1j1=es@Kcx4ySM_bwcQCT=d+DV z>Sz~L=Hj@(X%31nK$mWI@7d>}ORB`K(p=+`UD)+99YUGQc7y^bHZ1F(8|tL0 zdK*DT0kSXG_{BKTpP2*2PecdKV9;dq$^ZZDP;Nyq1kp-&GI5eAyZsK!e3V zK@rPy*{(`KIfo+lc878mDKk^V#`VT05}64kBtk%DgwLrOvLMj5-;*GNKv6c6pzMuL z6EP%ob|_0IW}lLRXCP2!9wWhEw3LA7iF#1O1mIZ@Z=6&bz41F;@S_GvYAG-#CW3z{ zP3+6vHhvP&A3$##Vo9$dT^#MoGg^|MDm=Bt1d2RRwSZ<;ZHICpLBv5Xs!D?BH^(9_ z7`H=N&^v|Z-%mP}wNzG{aiFCsRgwzwq!N6obW9+7(R; z(SZ=23`|`>qil!LMGG{_Heq!BD>(Y-zV9wD)}hz25JA37YR%39;kI4y9pgtcUass6 zP24}ZY$vvYeI`zy&)A_X#nY3017ap*0&jx|mVwyGhg3;!keU53a}Uhm3BZI$N$6Se zLWlAmy1S0xKJm4G_U@sN_Tm=`$xWJSEwKU98rZ&)1R^*$$1vA3oG#&*%SMxY_~oGP zP&PFJatFLM-Ps%84IV-+Ow)T{C7cqUAvauy4C z(FRz&?6$Rypj{xO!`y=*J5o4@U8Q-(y5(*=YoKeZ+-1YdljXxkA#B)zo=FeQH#?Le zycNUmEEHWO9a=X^pb#&cOq7-`7UA87#|S22)<7RUtZo|(zibX=w;K3qur9vy#`MNV z6UUcf9ZwEnKCCp+OoBnF@OdbvH)ANXO0o~Pi9l8=x3))}L<#vO0-~O4!~--Ket?d} zJaqsj<@CD1%S2cTW%rOP{Vto%0sGW~1RMa_j^)5nil0Yw- z0EE#bP+l4#P^%PQ+N*oxu1Zq05xZ!bXfYTg>9c{(Iw*lnjR^>kz%lAN^zFce7rppy zY8zA~3GD=A6d*hze&l4D_wA~+O!56)BZTe_rEu}Ezi<4!kG|W#amBZ5{&XS2@6R~H z{9o^y*BkH4$~yX9U&@CgbOzX1bn9xqF|zh$Dh0Y5y*E0e90*$!ObrHY3Ok0`2=O~r zCuke6KrP9KOf?V(YDsM<6pX2nVoN%M$LT^q#FmtaF?1^27F*IcNX~XRB(|hCFvdcc zc)$=S-)acdk$g4?_>jRqxpI6M3vHZk?0c^3=byamYDNf;uB{3NlKW5IhnOS3DNkMV z?tK8?kJ}pmvp%&&eTVOVjHP`q34hN1@!aK}H(K!vI`~gf|Gv+FNEQD5Yd<~yX7k_l h&G-K)@HZb3BABY{)U1?^%I#E6`MGoTtustd{~yM6srvu` literal 0 HcmV?d00001 diff --git a/ReactWindows/ReactNative.Tests/Assets/Square150x150Logo.scale-200.png b/ReactWindows/ReactNative.Tests/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..af49fec1a5484db1d52a7f9b5ec90a27c7030186 GIT binary patch literal 2937 zcma)84OCO-8BSud5)jwMLRVKgX(S?$n?Ld|vrsm<$CF7)&zTbyy1FE5bU`Q17MRv`9ue$;R(@8kR;#vJ*IM0>cJIAOte!d7oRgdH zd%ySjdB6L9=gX^A6)VzH7p2l@v~3zJAMw|DFy#^)F@@F*`mqUn=Il>l)8_+ab;nOW{%+iPx z+s{Eu|&pIs)Z7{La9~?xKfyl z#43?gjEL15d4WbOZo#SiP%>DB^+BcnJ=7dHEe;r#G=tuw|ka z%q@}##Uh7;tc%L_64m(kHtw74ty%BJMb)_1)#S0j`)F8_1jF7vScpsnH=0V19bO8y zR`0SjIdCUo&=>JwMQF8KHA<{ODHTiQh}0^@5QRmCA?gOH6_H3K^-_sNB^RrdNuK-R zOO*vOrKCVvDwgUck`kF(E7j{I#iiN;b*ZdCt4m@HPA`EuEqGGf4%!K<;(=I=&Vyrw z%TwcWtxa}8mCZ%Cyf&ActJ6_$ox5z6-D!0-dvnRx6t7y3d+h6QYpKWO;8OdnvERo7 zuEf>ih5`wqY)~o@OeVt-wM?Q!>QzdGRj!bz6fzYrfw$hZfAKzr2-M+D+R>}~oT574c;_3zquHcElqKIsryILt3g8n3jcMb+j?i?-L3FpZJ z2WRVBRdDPc+G5aaYg#5hpE+6nQ|(VSoxT3|biF;BUq#==-27Xi=gihDPYP$7?=9cP zYKE$jeQ|3~_L0VG-(F~2ZPyD0=k{J4Q~h(t__{-mz_w8{JDY9{`1ouzz!Vr5!ECdE z6U~O1k8c}24V7~zzXWTV-Pe4)y}wQJS&q%H5`Fo_f_JvIU489aCX$;P`u#!I-=^4ijC2{&9!O&h>mi?9oYD=GC#%)6{GzN6nQYw+Fal50!#x^asjBBR50i`+mho*ttoqV)ubM2KD9S~k7+FR4>{29?6 z{!l6kDdyTN0YJ9LgkPWeXm|gyi@zM3?0@{&pXT12w|78&W-q!RRF)&iLCEZVH<|fR zN0fr2^t8H(>L?>K#>^+jWROLral(Qy-xoBq1U7A&DV||wClb)Otd9?(gZ|8znMF}D zf<1haWz^s0qgecz;RFGt0C-B4g`jNGHsFU+;{<%t65v^sjk^h$lmWn#B0#_)9ij&d z-~lc`A)YYExi^7sBuPM^Y|wA2g*5?`K?#7tzELQYNxGo$UB$4J8RJp1k(8Jj+~hMT zlN~>M@KTTh^--8y3PK_NZ@AC!{PT=CziBzGd+wTJ^@icH!Bd}%)g8V)%K?|c&WTUk zy}qv1C%(fjRoZ4ozC3{O%@5?)XzH35zHns$pgU*Q?fj4v?fp1Qbm+j;3l;9jam9Da zXVcKjPlQ73x78QPu|Ffm6x?`~e3oD=gl=4kYK?={kD5j~QCXU)`HSdduNNENzA*2$ zOm3PzF!lN5e*06-f1Uot67wY#{o-S1!KZ7E=!~7ynnk9_iJR#kFoNbAOT#^2Gd17F zMmvU6>lndZQGd|ax9kUoXXO+$N?|j@6qpsF&_j7YXvwo_C{JpmLw5&#e6k>atv%es z5)7r*Wvv_JkUpT}M!_o!nVlEk1Zbl=a*2hQ*<|%*K1Glj^FcF`6kTzGQ3lz~2tCc@ z&x|tj;aH&1&9HwcJBcT`;{?a+pnej;M1HO(6Z{#J!cZA04hnFl;NXA+&`=7bjW_^o zfC40u3LMG?NdPtwGl>Tq6u}*QG)}-y;)lu-_>ee3kibW(69n0$0Zy!}9rQz%*v1iO zT9_H>99yIrSPYVy6^);rR}7Yo=J_T@hi+qhTZXnVWyf;JDYm5#eYLTxr*?kiNn!+Y zQ+LUkBafNJ#rH#C(?d5^;gw9o#%daEI{mA*LHPIHPU`#|H$hD zwm>0&+kahQ)E#%~k>&5@&#Vg82H?s%71=)(soi@174pi9--2{w{1$}Sz4zGn3Du&x bht0Iza^2ykEt4(epJ78uh5nDlX8(TxzDYwP literal 0 HcmV?d00001 diff --git a/ReactWindows/ReactNative.Tests/Assets/Square44x44Logo.scale-200.png b/ReactWindows/ReactNative.Tests/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..ce342a2ec8a61291ba76c54604aea7e9d20af11b GIT binary patch literal 1647 zcmaJ?eM}Q)7(e+G1Q(|`V9JhTI2>MkceK4;p;PR&$Pi?ejk3YQ_3o`S&|W_dsOZ8# zWPTt69g`t$ab`0cj-Y0yiBSOqmd)tG7G(}M5aP0_%&9TijB#&)I{zSE^4@#z^FF`l z`8{8`o%wlL(UI|y2!cdsuVamHH~H86F!*-15em4)NqUpCQM5?aoC_eCf@lV4wvF2a zjDQn1JBL69f&@2M3rvzJcfE!eZ8FZUBlFlC5RD)it33{mF9#B82AiyQE%w)`vlwa> zv{<1sm&kSKK$&%2jSFn7$t&P%%6Ue>R=EAnG8N7fqynWG8L3p!4801a;8{+nliO(qd(jNJ_?+9W3#hLIDLoT6~3fx9=`CC-D}-AMrpEO7HK zt3$GicGPc?GmDjy7K2P@La;eu4!$zWCZ`ym{Z$b zu-O6RM&K4JT|BIZB`E-gxqG%FzanI#+2FFmqHqXG7yxWB=w55RGOM)$xMb(>kSNR z2w=1AZi%z=AmG~yea~XaXJR!v7vLn(RUnELfiB1|6D84ICOS}^Zo2AdN}<&*h}G_u z{xZ!(%>tLT3J3<5XhWy-tg+6)0nmUUENLW8TWA{R6bgVd3X;anYFZ^IRis*_P-C-r z;i>%1^eL3UI2-{w8nuFFcs0e~7J{O2k^~Ce%+Ly4U?|=!0LH=t6()xi<^I-rs+9sF z*q{E-CxZbGPeu#a;XJwE;9S1?#R&uns>^0G3p`hEUF*v`M?@h%T%J%RChmD|EVydq zmHWh*_=S%emRC*mhxaVLzT@>Z2SX0u9v*DIJ@WC^kLVdlGV6LpK$KIrlJqc zpJ921)+3JJdTx|<`G&kXpKkjGJv=76R`yYIQ{#c-`%+`#V(7}Q;&@6U8!Td1`d;?N z_9mnI#?AA}4J!r)LN4!E-@H5eXauuB7TOawS>Y|{-P?NNx-lq+z1W-+y(;39P&&LP zL{N80?&=C*qKmdA^moMZRuPcD!B<*mq$ch=0Cnlitw#txRWhb3%TQvPqjkC`F69G4b! ze7z9MZ#+;_#l?H37UqUhDFb^l&s2{oM$3I0o^Q!yx;;V)QmCMo)Tb_ui|mit8MS?U zm##6$sZZ1$@|s%?l@>4Z<*Q}sRBSKMhb4I{e5LdEhsHIHTe8Bod5c>6QtT>$XgUBz z6MK`kO$=jmt@FqggOhJ5j~e@ygRbG;<{Vu)*+nn9aQeo0;$#j;|MS=S$&L?BeV25z xs3B`@=#`5TF{^6(A1rvdY@|-RtQ|iS5{tyX+wH?;n8E)G$kykv-D^wh{{!TZT%7;_ literal 0 HcmV?d00001 diff --git a/ReactWindows/ReactNative.Tests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/ReactWindows/ReactNative.Tests/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 0000000000000000000000000000000000000000..f6c02ce97e0a802b85f6021e822c89f8bf57d5cd GIT binary patch literal 1255 zcmaJ>TWs4@7*5+{G#S+&C!qC#> zf>5N3P6jO*Cz>ug*(_DmW=)kea&m$gZ^+nyiF`;j%w@}y8)>p*SH}C`m?DXeieF2U zyQHecc_L%Gh!7GMt+hG06y;+|p4>m~}PjA}rKViGiEnn7G0ZO<>G|7q;2?NwGCM3s?eued6%hd$B+ z*kQJ{#~$S=DFE(%=E+UkmlEI*%3llUf~8Ja9YU1Vui0IbGBkW_gHB%Rd&!!ioX zs40O?i9I{};kle7GMvE7(rk`la=gTI)47=>%?q@^iL-nUo3}h4S}N-KHn8t5mVP8w z&bSErwp+37 zNJJ8?a|{r5Q3R0Z5s-LB1WHOwYC@7pCHWND#cL1cZ?{kJ368_*(UDWUDyb<}0y@o# zfMF016iMWPCb6obAxT$JlB6(2DrlXDTB&!0`!m??4F(qWMhjVZo?JXQmz`1*58Z=& zcDmB|S-E@j?BoFGix0flckqdS4jsPNzhfWyWIM98GxcLs89C(~dw%$_t;JjX-SD}E zfiGV;{8Q%8r}w9x>EEigW81>`kvnU@pK)4+xk9@+bNj9L!AAZ@SZ@q|)&BmY3+HZx zul~BeG4|}-;L%cHViQGQX?^zFfO0&#cHwel=d`lH9sJ-@Sl@n*(8J2>%Ac`IxyY?Q z{=GhWvC#gu-~Ia7*n{=+;qM?Ul_wy1+u7ho;=`>EwP^g~R@{unBds`!#@}tluZQpS zm)M~nYEifJWJGx?_6DcTy>#uh%>!H9=hb^(v`=m3F1{L>db=<5_tm+_&knAQ2EU$s Mu9UqpbNZeC0BbUo^Z)<= literal 0 HcmV?d00001 diff --git a/ReactWindows/ReactNative.Tests/Assets/StoreLogo.png b/ReactWindows/ReactNative.Tests/Assets/StoreLogo.png new file mode 100644 index 0000000000000000000000000000000000000000..7385b56c0e4d3c6b0efe3324aa1194157d837826 GIT binary patch literal 1451 zcmaJ>eN5D57_Z|bH;{0+1#mbl)eTU3{h)Wf7EZV?;HD@XL@{B`Ui%(2aMxQ~xdXSv z5nzWi(LW)U2=Vc-cY@s7nPt{i0hc6!7xN4NNHI#EQl>YNBy8l4%x9gr_W-j zEZMQmmTIy(>;lblRfh`dIyTgc9W5d!VP$L4(kKrN1c5G~(O_#xG zAJCNTstD^5SeXFB+&$h=ToJP2H>xr$iqPs-#O*;4(!Fjw25-!gEb*)mU}=)J;Iu>w zxK(5XoD0wrPSKQ~rbL^Cw6O_03*l*}i=ydbu7adJ6y;%@tjFeXIXT+ms30pmbOP%Q zX}S;+LBh8Tea~TSkHzvX6$rYb)+n&{kSbIqh|c7hmlxmwSiq5iVhU#iEQ<>a18|O^Sln-8t&+t`*{qBWo5M?wFM(JuimAOb5!K#D}XbslM@#1ZVz_;!9U zpfEpLAOz=0g@bd6Xj_ILi-x^!M}73h^o@}hM$1jflTs|Yuj9AL@A3<-?MV4!^4q`e z)fO@A;{9K^?W?DbnesnPr6kK>$zaKo&;FhFd(GYFCIU^T+OIMb%Tqo+P%oq(IdX7S zf6+HLO?7o0m+p>~Tp5UrXWh!UH!wZ5kv!E`_w)PTpI(#Iw{AS`gH4^b(bm^ZCq^FZ zY9DD7bH}rq9mg88+KgA$Zp!iWncuU2n1AuIa@=sWvUR-s`Qb{R*kk(SPU^`$6BXz8 zn#7yaFOIK%qGxyi`dYtm#&qqox0$h=pNi#u=M8zUG@bpiZ=3sT=1}Trr}39cC)H|v zbL?W)=&s4zrh)7>L(|cc%$1#!zfL?HjpeP%T+x_a+jZ16b^iKOHxFEX$7d|8${H-* zIrOJ5w&i$>*D>AKaIoYg`;{L@jM((Kt?$N$5OnuPqVvq**Nm}(f0wwOF%iX_Pba;V z;m@wxX&NcV3?<1+u?A{y_DIj7#m3Af1rCE)o`D&Y3}0%7E;iX1yMDiS)sh0wKi!36 zL!Wmq?P^Ku&rK~HJd97KkLTRl>ScGFYZNlYytWnhmuu|)L&ND8_PmkayQb{HOY640 bno1(wj@u8DCVuFR|31B*4ek@pZJqxCDDe1x literal 0 HcmV?d00001 diff --git a/ReactWindows/ReactNative.Tests/Assets/Wide310x150Logo.scale-200.png b/ReactWindows/ReactNative.Tests/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000000000000000000000000000000000000..288995b397fdbef1fb7e85afd71445d5de1952c5 GIT binary patch literal 3204 zcmbVPeQXow8NYmBd90>}0NP?GhXW~VaeThm=a0tV#EwJMI!)6M3}|c4_Bl3=Kd>G0 z(GHx1wl<7(tP?FsOQkTilSo*iIvF%uArExJ73~P zSv1xEy!U(Wd4A9D`FQV@W3@F^qJ@PEF$@z`Z!*BbFsS(^?B zyiAzJ+q})bkgiQHWqEb*jJD-coHYr1^iocg)l!Qa{Xqs-l~6J}p-|##ZHYofskQ3$ zI0;xzXyhazBeXhIsg5A=%ufo@f)1yy&ScKS0;HF^!r_2UE^lpZEom(+@duma3awTv zCrCL-%D_SvYWIcdHkmI}#50(fkUi)Qgx!80ju>g1za^}ff>JI8Z@^-iCiaCgg@TgF z+vtE?Q9{VQUX&MW9SYYmGcxA14%N2@7FwBTD4N<(2{nWgV8$e3?-F=L^&FrtWn~(U_Q~~^uYiyeY6-KoTnfh9AWz@ zIKje0)u!_Lw)E}G!#kEfwKVdNt(UAf9*f>tEL_(=xco-T%jTi@7YlC3hs2ik%Le0H ztj}RTeCF(5mwvi3_56>-yB?l;J>-1%!9~=fs|QcNG3J~a@JCu`4SB460s0ZO+##4fFUSGLcj_ja^fL4&BKALfb#$6$O?>P@qx2Agl^x0i&ugt zsy5Pyu=()`7HRMG3IB7F1@`_ z+-!J%#i6e^U$e#+C%Q>_qVRzWRsG^W_n+@OcX@vzI&z;mzHNb!GQ?LWA(wtpqHqTM z1OFw_{Zn?fD)p)`c`kOgv{de=v@suGRqY{N^U7gI1VF3*F=obwaXI6ob5__Yn zVTguS!%(NI09J8x#AO_aW!9W7k*UvB;IWDFC3srwftr{kHj%g)fvnAm;&h_dnl~

MY- zf+K}sCe8qU6Ujs`3ua{U0Of$R_gVQBuUA za0v=mu#vIOqiiAZOr&h*$WyOw&k-xr$;G4Ixa!#TJNr>95(h>l%)PUy4p+^SgR(uR zta%k*?ny-+nAr8spEk1fo{J4i!b^Fia`N{_F6@zidA2ZTTrjl#^5Z-2KfB@Cu}l9s z(*|Z2jc?p~vn2f)3y9i*7zJV1L{$?|&q)4oaT;uXi6>1GkRXVTOzAz(RHEmr=eFIi z`}<>-Q?K0GN8!IYxeP1XKXO+jsJbp~o^);Bc;%b7Flpe7;1`Ny@3r7ZR;?R)aJt8C ziNlEC<@3f_lIV4TwV}&e;D!Ee5_|e#g0LUh=5vmYWYm7&2h*M>QPKvGh9-)wfMMW3 z8J9b%1k7dzPzO0_NGQy92BZ^FR6R~6;^6?lqO;-QUP4BY%cG%3vEhbm#>4vIhPBh3 z-+pZGjh$x%Hp{?=FHsMp0&wNPlj00us{&`1ZOZTqs8%4X&xH=UDr*xyBW(Zp&Em94 zf)ZSfn#yg0N)>!1kWdkqJ^S*z0FF5|fj&qcE#Na|%OY0$uO>!&hP+1ywfD_WXk@4J(?MBftK7>$Nvqh@tDuarN%PrTLQ2Uzysx>UV=V zk^RrDSvdQ?0;=hY67EgII-f4`t=+i*yS=Y~!XlqIy_4x&%+OdfbKOFPXS2X5%4R{N z$SQMX^AK6(fA( + () => builder.Add(null), + ex => Assert.AreEqual("module", ex.ParamName)); + } + + [TestMethod] + public void NativeModuleRegistry_Override_Disallowed() + { + var builder = new NativeModuleRegistry.Builder(); + builder.Add(new OverrideDisallowedModule()); + AssertEx.Throws(() => builder.Add(new OverrideDisallowedModule())); + } + + [TestMethod] + public void NativeModuleRegistry_Override_Allowed() + { + var builder = new NativeModuleRegistry.Builder(); + builder.Add(new OverrideAllowedModule()); + builder.Add(new OverrideAllowedModule()); + var registry = builder.Build(); + Assert.AreEqual(1, registry.Modules.Count); + } + + [TestMethod] + public void NativeModuleRegistry_ModuleWithNullName_Throws() + { + var builder = new NativeModuleRegistry.Builder(); + AssertEx.Throws( + () => builder.Add(new NullNameModule()), + ex => Assert.AreEqual("module", ex.ParamName)); + } + + class OverrideDisallowedModule : NativeModuleBase + { + public override string Name + { + get + { + return "Foo"; + } + } + } + + class OverrideAllowedModule : NativeModuleBase + { + public override string Name + { + get + { + return "Foo"; + } + } + + public override bool CanOverrideExistingModule + { + get + { + return true; + } + } + } + + class NullNameModule : NativeModuleBase + { + public override string Name + { + get + { + return null; + } + } + } + } +} diff --git a/ReactWindows/ReactNative.Tests/Internal/AssertEx.cs b/ReactWindows/ReactNative.Tests/Internal/AssertEx.cs new file mode 100644 index 00000000000..b95609260ed --- /dev/null +++ b/ReactWindows/ReactNative.Tests/Internal/AssertEx.cs @@ -0,0 +1,30 @@ +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using System; + +namespace ReactNative.Tests +{ + static class AssertEx + { + public static void Throws(Action action) + where T : Exception + { + Throws(action, _ => { }); + } + + public static void Throws(Action action, Action assert) + where T : Exception + { + try + { + action(); + } + catch (T ex) + { + assert(ex); + return; + } + + Assert.Fail("Excepted exception of type '{0}'.", typeof(T)); + } + } +} diff --git a/ReactWindows/ReactNative.Tests/Package.appxmanifest b/ReactWindows/ReactNative.Tests/Package.appxmanifest new file mode 100644 index 00000000000..c4a026da1df --- /dev/null +++ b/ReactWindows/ReactNative.Tests/Package.appxmanifest @@ -0,0 +1,45 @@ + + + + + + + + + ReactNative.Tests + ericroz + Assets\StoreLogo.png + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ReactWindows/ReactNative.Tests/Properties/AssemblyInfo.cs b/ReactWindows/ReactNative.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..b903241f6f6 --- /dev/null +++ b/ReactWindows/ReactNative.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ReactNative.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ReactNative.Tests")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] +[assembly: AssemblyMetadata("TargetPlatform","UAP")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/ReactWindows/ReactNative.Tests/Properties/UnitTestApp.rd.xml b/ReactWindows/ReactNative.Tests/Properties/UnitTestApp.rd.xml new file mode 100644 index 00000000000..efee59d2788 --- /dev/null +++ b/ReactWindows/ReactNative.Tests/Properties/UnitTestApp.rd.xml @@ -0,0 +1,29 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj b/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj new file mode 100644 index 00000000000..50ff64b266a --- /dev/null +++ b/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj @@ -0,0 +1,148 @@ + + + + + Debug + x86 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691} + AppContainerExe + Properties + ReactNative.Tests + ReactNative.Tests + en-US + UAP + 10.0.10586.0 + 10.0.10240.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + ReactNative.Tests_TemporaryKey.pfx + 14.0 + + + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + true + + + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + true + true + + + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + true + + + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + true + true + + + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + true + + + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + true + true + + + + + + + + + + + + + + UnitTestApp.xaml + + + + + MSBuild:Compile + Designer + + + + + Designer + + + + + + + + + + + + + + + + + + + {c7673ad5-e3aa-468c-a5fd-fa38154e205c} + ReactNative + + + + 14.0 + + + + \ No newline at end of file diff --git a/ReactWindows/ReactNative.Tests/ReactNative.Tests_TemporaryKey.pfx b/ReactWindows/ReactNative.Tests/ReactNative.Tests_TemporaryKey.pfx new file mode 100644 index 0000000000000000000000000000000000000000..b6e16b4b7c66e5173acb9c60304654b4281ac5a0 GIT binary patch literal 2454 zcmZuzdpJ~U7vFo&7&FF@am_)Z5S7iCF~}{xqU2KJek<2d#!T*);Y`V)5V;fcQ6woz z$h}OV8Y*d&ONDfbGRdh(oUP~k`ud*d{ITBW_xr82-giA~@4em)vvJuFh=SQTTMSO~ zq}9nU1W*{{uyLknHqHo<^iH3vy}1sLF6Ye-Z*%#HK!n z`%b$I7UxE(vrgNXxaSRmMu^%RljXzsjXZSZ+?Jueqxc?d?BH#u*!CL-qnqwC!#fj_ z{qjg(SuWu;X@V8ygu$IAlSERm>Ox@AVoLQhRgZl(BH1GJ?}h8bb3E#+V z&|>B9-2Ie5@^^7>QylA2DtUZiRYez5tgj}b^uf8hlSbjs2CcIQY$}lT$1L*`zS;EA zavsL1C%4~JR*2t|=5CwH80mlQO}Ojvg19Sv;ALjkwny0dpe8}Xn9E70exq_mRL0Eh zthtu-m)@OIC8P?&nW2y4thVe`PR;R?RuZ*oHKif4OgTW3>dze`TAO`7`s5}z*=;OJ z`*k)~*emii3U2m&h}hWnVAQW`It@QR?_R~|5IGw7 z{&ihi{HU__$07-vcuk2)-_r^QjAp#NaF6Tf7YI-KPfS0E-P134=y`R}NN(JFJdzMC zl|_?u*_!LFd8K8XC>3n;IM~jSmg!PoJxa&_^${yeBd1$WYo1I>xDgPwRr?OTCGUHY zi&{$1F)V{b^(${O_G+bAl+hdU!wc~>T^moS+T>Q`)|FN(?hz;}Dld`K2Xe;Mggh=( zLR60%$p<93jtuv$2SrUP-*ERYa|c2tPJ60IgsFEoZL1^IUo27LDv4EEO`jeOAJS3l zOYO_9*v~`BYAqVFHJ&YSuAOM`NR05+y|Aq`)`!U=bS`%tp&66mxZT&qx4KXKJ7=ks z^S#!G$C6XMJ_m*v+g>VeV*jnBDAYFIYUYlerrl?8FOBBRo3uN; zGAL5;+%obdsY*-d#@k^Gl|;TJ zM9GK=ny5>?WVDfV&Mmil>XZBM-Cc`s)#$q-=LzU2o}n@&@vpO_;`?*f-*-Ful=<3+ z_at1^=Y7arw|$vDTh6NtdGPi1K!fzI)-leEYSy5In|P32D(iHL@w=TBe78?8t{5ce z2Y$L&D2;R0Q%xR|ba=%*Lc45Gueor!2=We7P|&93|30zm8!emw;ojk^RF7-s?Rt}0 z8N8?SUqsooaDLLX>t4$V`;1!|{JUu(m(-hl62qRGldNYaS&mbRcj+;#zxw7X`o0Co zl6Kgfr_=IMR)s~I9Jbv*e%?QzoBHOn|718);wRdUK*X(m9yQv@Q zwpEQyhg>l?bvH3zo3 z*xJENn2jR;tbFZaK&heGC>2CjLQMYG>5t;Dp?R1MefT;1aWY5(G>&FN&k_A8%mzC+ zCm1GGz zWF1`{JzbJ6g+y^fC}oYpSn!@2!wZspF{)X(bgd)+^)E#u;^RC#01kb3>qeb1bKrTV8}j`7=a`?M$B zw6#e4!J}t}R(tC_s|#9P_rW|*^V>l&+hfus&$enbV#mL>qk{1flW$GqD7TaX+Ba)a zYBX<@ym37dc`K)*^a zw=X4cO-0KuTl=ZHCf<1CP_NCOx;(-iPf@5jMbi>Ij_W!6We7rn09c^`tHGj3w-qHp za1{-~04%e{Hm@;JSOkMbUN9jcVHE1uULubP=_LrH7OIx^PZ+tr7jb!hZI&w4AmRAj zxMg4@CBm&_x5U@5SbY5;V`{#}J4dee-cwXl3vOxVm~gR@B``J|WpU+uS#7WpNO&Kz zaCS@ctMUO4bCV?PA@d~XxBa6ky7B2A#HxF8rjqC4o7<`~>UZeR4gN78ew^dg8lVGr z%fy5@WG6fM@w_`4TSLoM%HcqIr6H>(6;obmYKRT}!tvhNliq!*_|G=oeC3MH=Di+f z#FZ*H8&`lW;H8^H3F_>Jx-{ q&?MoLb61_*&eZ&KGH9ucz+u101e-=M<7KeJEHO%om4;pWL;4#8jQnN* literal 0 HcmV?d00001 diff --git a/ReactWindows/ReactNative.Tests/UnitTestApp.xaml b/ReactWindows/ReactNative.Tests/UnitTestApp.xaml new file mode 100644 index 00000000000..997faae36ff --- /dev/null +++ b/ReactWindows/ReactNative.Tests/UnitTestApp.xaml @@ -0,0 +1,8 @@ + + + diff --git a/ReactWindows/ReactNative.Tests/UnitTestApp.xaml.cs b/ReactWindows/ReactNative.Tests/UnitTestApp.xaml.cs new file mode 100644 index 00000000000..a69d5b08e6e --- /dev/null +++ b/ReactWindows/ReactNative.Tests/UnitTestApp.xaml.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices.WindowsRuntime; +using Windows.ApplicationModel; +using Windows.ApplicationModel.Activation; +using Windows.Foundation; +using Windows.Foundation.Collections; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; +using Windows.UI.Xaml.Controls.Primitives; +using Windows.UI.Xaml.Data; +using Windows.UI.Xaml.Input; +using Windows.UI.Xaml.Media; +using Windows.UI.Xaml.Navigation; + +namespace ReactNative.Tests +{ + ///

+ /// Provides application-specific behavior to supplement the default Application class. + /// + sealed partial class App : Application + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + this.Suspending += OnSuspending; + } + + /// + /// Invoked when the application is launched normally by the end user. Other entry points + /// will be used such as when the application is launched to open a specific file. + /// + /// Details about the launch request and process. + protected override void OnLaunched(LaunchActivatedEventArgs e) + { + +#if DEBUG + if (System.Diagnostics.Debugger.IsAttached) + { + this.DebugSettings.EnableFrameRateCounter = true; + } +#endif + + Frame rootFrame = Window.Current.Content as Frame; + + // Do not repeat app initialization when the Window already has content, + // just ensure that the window is active + if (rootFrame == null) + { + // Create a Frame to act as the navigation context and navigate to the first page + rootFrame = new Frame(); + + rootFrame.NavigationFailed += OnNavigationFailed; + + if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) + { + //TODO: Load state from previously suspended application + } + + // Place the frame in the current Window + Window.Current.Content = rootFrame; + } + + Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.CreateDefaultUI(); + + // Ensure the current window is active + Window.Current.Activate(); + + Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(e.Arguments); + } + + /// + /// Invoked when Navigation to a certain page fails + /// + /// The Frame which failed navigation + /// Details about the navigation failure + void OnNavigationFailed(object sender, NavigationFailedEventArgs e) + { + throw new Exception("Failed to load Page " + e.SourcePageType.FullName); + } + + /// + /// Invoked when application execution is being suspended. Application state is saved + /// without knowing whether the application will be terminated or resumed with the contents + /// of memory still intact. + /// + /// The source of the suspend request. + /// Details about the suspend request. + private void OnSuspending(object sender, SuspendingEventArgs e) + { + var deferral = e.SuspendingOperation.GetDeferral(); + //TODO: Save application state and stop any background activity + deferral.Complete(); + } + } +} diff --git a/ReactWindows/ReactNative.Tests/project.json b/ReactWindows/ReactNative.Tests/project.json new file mode 100644 index 00000000000..c5949392705 --- /dev/null +++ b/ReactWindows/ReactNative.Tests/project.json @@ -0,0 +1,16 @@ +{ + "dependencies": { + "Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0" + }, + "frameworks": { + "uap10.0": {} + }, + "runtimes": { + "win10-arm": {}, + "win10-arm-aot": {}, + "win10-x86": {}, + "win10-x86-aot": {}, + "win10-x64": {}, + "win10-x64-aot": {} + } +} \ No newline at end of file diff --git a/ReactWindows/ReactNative.sln b/ReactWindows/ReactNative.sln new file mode 100644 index 00000000000..b27578ddf15 --- /dev/null +++ b/ReactWindows/ReactNative.sln @@ -0,0 +1,84 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.24720.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactNative", "ReactNative\ReactNative.csproj", "{C7673AD5-E3AA-468C-A5FD-FA38154E205C}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Playground", "Playground\Playground.csproj", "{D52267B5-396F-424A-BB26-C9E750032846}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ReactNative.Tests", "ReactNative.Tests\ReactNative.Tests.csproj", "{5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Debug|ARM = Debug|ARM + Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 + Release|Any CPU = Release|Any CPU + Release|ARM = Release|ARM + Release|x64 = Release|x64 + Release|x86 = Release|x86 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|ARM.ActiveCfg = Debug|ARM + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|ARM.Build.0 = Debug|ARM + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|x64.ActiveCfg = Debug|x64 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|x64.Build.0 = Debug|x64 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|x86.ActiveCfg = Debug|x86 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Debug|x86.Build.0 = Debug|x86 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|Any CPU.Build.0 = Release|Any CPU + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|ARM.ActiveCfg = Release|ARM + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|ARM.Build.0 = Release|ARM + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|x64.ActiveCfg = Release|x64 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|x64.Build.0 = Release|x64 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|x86.ActiveCfg = Release|x86 + {C7673AD5-E3AA-468C-A5FD-FA38154E205C}.Release|x86.Build.0 = Release|x86 + {D52267B5-396F-424A-BB26-C9E750032846}.Debug|Any CPU.ActiveCfg = Debug|x86 + {D52267B5-396F-424A-BB26-C9E750032846}.Debug|ARM.ActiveCfg = Debug|ARM + {D52267B5-396F-424A-BB26-C9E750032846}.Debug|ARM.Build.0 = Debug|ARM + {D52267B5-396F-424A-BB26-C9E750032846}.Debug|ARM.Deploy.0 = Debug|ARM + {D52267B5-396F-424A-BB26-C9E750032846}.Debug|x64.ActiveCfg = Debug|x64 + {D52267B5-396F-424A-BB26-C9E750032846}.Debug|x64.Build.0 = Debug|x64 + {D52267B5-396F-424A-BB26-C9E750032846}.Debug|x64.Deploy.0 = Debug|x64 + {D52267B5-396F-424A-BB26-C9E750032846}.Debug|x86.ActiveCfg = Debug|x86 + {D52267B5-396F-424A-BB26-C9E750032846}.Debug|x86.Build.0 = Debug|x86 + {D52267B5-396F-424A-BB26-C9E750032846}.Debug|x86.Deploy.0 = Debug|x86 + {D52267B5-396F-424A-BB26-C9E750032846}.Release|Any CPU.ActiveCfg = Release|x86 + {D52267B5-396F-424A-BB26-C9E750032846}.Release|ARM.ActiveCfg = Release|ARM + {D52267B5-396F-424A-BB26-C9E750032846}.Release|ARM.Build.0 = Release|ARM + {D52267B5-396F-424A-BB26-C9E750032846}.Release|ARM.Deploy.0 = Release|ARM + {D52267B5-396F-424A-BB26-C9E750032846}.Release|x64.ActiveCfg = Release|x64 + {D52267B5-396F-424A-BB26-C9E750032846}.Release|x64.Build.0 = Release|x64 + {D52267B5-396F-424A-BB26-C9E750032846}.Release|x64.Deploy.0 = Release|x64 + {D52267B5-396F-424A-BB26-C9E750032846}.Release|x86.ActiveCfg = Release|x86 + {D52267B5-396F-424A-BB26-C9E750032846}.Release|x86.Build.0 = Release|x86 + {D52267B5-396F-424A-BB26-C9E750032846}.Release|x86.Deploy.0 = Release|x86 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Debug|Any CPU.ActiveCfg = Debug|x86 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Debug|ARM.ActiveCfg = Debug|ARM + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Debug|ARM.Build.0 = Debug|ARM + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Debug|ARM.Deploy.0 = Debug|ARM + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Debug|x64.ActiveCfg = Debug|x64 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Debug|x64.Build.0 = Debug|x64 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Debug|x64.Deploy.0 = Debug|x64 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Debug|x86.ActiveCfg = Debug|x86 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Debug|x86.Build.0 = Debug|x86 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Debug|x86.Deploy.0 = Debug|x86 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Release|Any CPU.ActiveCfg = Release|x86 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Release|ARM.ActiveCfg = Release|ARM + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Release|ARM.Build.0 = Release|ARM + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Release|ARM.Deploy.0 = Release|ARM + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Release|x64.ActiveCfg = Release|x64 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Release|x64.Build.0 = Release|x64 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Release|x64.Deploy.0 = Release|x64 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Release|x86.ActiveCfg = Release|x86 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Release|x86.Build.0 = Release|x86 + {5AC8108B-1C39-4C4A-8653-DBBE4ECCE691}.Release|x86.Deploy.0 = Release|x86 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/ReactWindows/ReactNative/Bridge/CatalystInstance.cs b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs new file mode 100644 index 00000000000..7ca20165984 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Newtonsoft.Json.Linq; + +namespace ReactNative.Bridge +{ + class CatalystInstance : ICatalystInstance + { + public CatalystInstance(NativeModuleRegistry registry) + { + + } + + public ICollection NativeModules + { + get + { + throw new NotImplementedException(); + } + } + + public T GetNativeModule(Type nativeModuleInterface) where T : INativeModule + { + throw new NotImplementedException(); + } + + public void Initialize() + { + throw new NotImplementedException(); + } + + public void InvokeCallback(int callbackId, JArray arguments) + { + throw new NotImplementedException(); + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/ChakraReactBridge.cs b/ReactWindows/ReactNative/Bridge/ChakraReactBridge.cs new file mode 100644 index 00000000000..311b745a680 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/ChakraReactBridge.cs @@ -0,0 +1,23 @@ +using System; +using Newtonsoft.Json.Linq; + +namespace ReactNative.Bridge +{ + class ChakraReactBridge : IReactBridge + { + public void CallFunction(int moduleId, int methodId, JArray arguments) + { + throw new NotImplementedException(); + } + + public void InvokeCallback(int callbackID, JArray arguments) + { + throw new NotImplementedException(); + } + + public void SetGlobalVariable(string propertyName, string jsonEncodedArgument) + { + throw new NotImplementedException(); + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/ICallback.cs b/ReactWindows/ReactNative/Bridge/ICallback.cs new file mode 100644 index 00000000000..bbe29dbe30b --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/ICallback.cs @@ -0,0 +1,7 @@ +namespace ReactNative.Bridge +{ + public interface ICallback + { + void Invoke(params object[] arguments); + } +} diff --git a/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs b/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs new file mode 100644 index 00000000000..0d095686912 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; + +namespace ReactNative.Bridge +{ + public interface ICatalystInstance + { + ICollection NativeModules { get; } + + void InvokeCallback(int callbackId, JArray arguments); + + void Initialize(); + + T GetNativeModule(Type nativeModuleInterface) where T : INativeModule; + } +} diff --git a/ReactWindows/ReactNative/Bridge/INativeMethod.cs b/ReactWindows/ReactNative/Bridge/INativeMethod.cs new file mode 100644 index 00000000000..68b3febfbbb --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/INativeMethod.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json.Linq; + +namespace ReactNative.Bridge +{ + public interface INativeMethod + { + string Type { get; } + + void Invoke(ICatalystInstance instance, JArray parameters); + } +} diff --git a/ReactWindows/ReactNative/Bridge/INativeModule.cs b/ReactWindows/ReactNative/Bridge/INativeModule.cs new file mode 100644 index 00000000000..bba0c51b886 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/INativeModule.cs @@ -0,0 +1,19 @@ +using System.Collections.Generic; + +namespace ReactNative.Bridge +{ + public interface INativeModule + { + bool CanOverrideExistingModule { get; } + + IReadOnlyDictionary Constants { get; } + + IReadOnlyDictionary Methods { get; } + + string Name { get; } + + void Initialize(); + + void OnCatalystInstanceDestroy(); + } +} diff --git a/ReactWindows/ReactNative/Bridge/IOnBatchCompleteListener.cs b/ReactWindows/ReactNative/Bridge/IOnBatchCompleteListener.cs new file mode 100644 index 00000000000..4e11a7bd296 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/IOnBatchCompleteListener.cs @@ -0,0 +1,7 @@ +namespace ReactNative.Bridge +{ + public interface IOnBatchCompleteListener + { + void OnBatchComplete(); + } +} diff --git a/ReactWindows/ReactNative/Bridge/IReactBridge.cs b/ReactWindows/ReactNative/Bridge/IReactBridge.cs new file mode 100644 index 00000000000..38f6499cd40 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/IReactBridge.cs @@ -0,0 +1,13 @@ +using Newtonsoft.Json.Linq; + +namespace ReactNative.Bridge +{ + public interface IReactBridge + { + void CallFunction(int moduleId, int methodId, JArray arguments); + + void InvokeCallback(int callbackID, JArray arguments); + + void SetGlobalVariable(string propertyName, string jsonEncodedArgument); + } +} diff --git a/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs new file mode 100644 index 00000000000..b841164ef97 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs @@ -0,0 +1,241 @@ +using Newtonsoft.Json.Linq; +using ReactNative.Reflection; +using System; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; + +namespace ReactNative.Bridge +{ + public abstract class NativeModuleBase : INativeModule + { + private IReadOnlyDictionary _methods; + private IReadOnlyDictionary _constants; + + public virtual bool CanOverrideExistingModule + { + get + { + return false; + } + } + + public virtual IReadOnlyDictionary Constants + { + get + { + return _constants; + } + } + + public IReadOnlyDictionary Methods + { + get + { + if (_methods == null) + { + throw new InvalidOperationException("Module has not been initialized."); + } + + return _methods; + } + } + + public abstract string Name + { + get; + } + + public void Initialize() + { + _methods = InitializeMethods(); + _constants = CreateConstants(); + } + + public virtual void OnCatalystInstanceDestroy() + { + } + + protected virtual IReadOnlyDictionary CreateConstants() + { + return new Dictionary(); + } + + private IReadOnlyDictionary InitializeMethods() + { + var declaredMethods = GetType().GetTypeInfo().DeclaredMethods; + var exportedMethods = new List(); + foreach (var method in declaredMethods) + { + if (method.IsDefined(typeof(ReactMethodAttribute))) + { + exportedMethods.Add(method); + } + } + + var methodMap = new Dictionary(exportedMethods.Count); + foreach (var method in exportedMethods) + { + methodMap.Add(method.Name, new NativeMethod(this, method)); + } + + return methodMap; + } + + class NativeMethod : INativeMethod + { + const string METHOD_TYPE_REMOTE = "remote"; + const string METHOD_TYPE_REMOTE_ASYNC = "remoteAsync"; + + private readonly NativeModuleBase _instance; + + private readonly Lazy> _invokeDelegate; + + public NativeMethod(NativeModuleBase instance, MethodInfo method) + { + _instance = instance; + _invokeDelegate = new Lazy>(() => GenerateExpression(instance, method).Compile()); + + if (method.IsAsync()) + { + throw new NotImplementedException("Async methods not yet supported."); + } + + Type = METHOD_TYPE_REMOTE; + } + + public string Type + { + get; + } + + public void Invoke(ICatalystInstance instance, JArray parameters) + { + + } + + private static MethodInfo s_extractCallback = (MethodInfo)ReflectionHelpers.InfoOf(() => ExtractCallback(default(JToken), default(ICatalystInstance))); + private static MethodInfo s_extractGeneric = ((MethodInfo)ReflectionHelpers.InfoOf(() => Extract(default(JToken)))).GetGenericMethodDefinition(); + private static PropertyInfo s_countProperty = (PropertyInfo)ReflectionHelpers.InfoOf((JArray arr) => arr.Count); + private static Expression s_throwExpression = Expression.Throw(Expression.Constant(new ArgumentException("Invalid argument count."))); + + private static Expression> GenerateExpression(NativeModuleBase instance, MethodInfo method) + { + var parameterInfos = method.GetParameters(); + var n = parameterInfos.Length; + + var parameterExpressions = new ParameterExpression[n]; + var extractExpressions = new Expression[n]; + + var catalystInstanceParameter = Expression.Parameter(typeof(ICatalystInstance), "catalystInstance"); + var jsArgumentsParameter = Expression.Parameter(typeof(JArray), "jsArguments"); + + for (var i = 0; i < n; ++i) + { + var parameterInfo = parameterInfos[i]; + var parameterType = parameterInfo.ParameterType; + var parameterExpression = Expression.Parameter(parameterType, parameterInfo.Name); + parameterExpressions[i] = parameterExpression; + + if (parameterType == typeof(ICallback)) + { + // + // valueOf(parameterInfo.Name) = ExtractCallback(jsArguments[valueOf(i)], catalystInstance) + // + extractExpressions[i] = Expression.Assign( + parameterExpression, + Expression.Call( + s_extractCallback, + Expression.ArrayIndex( + jsArgumentsParameter, + Expression.Constant(i) + ), + catalystInstanceParameter + ) + ); + } + else + { + var extractMethod = s_extractGeneric.MakeGenericMethod(parameterType); + + // + // valueOf(parameterInfo.Name) = Extract(jsArguments[valueOf(i)]); + // + extractExpressions[i] = Expression.Assign( + parameterExpression, + Expression.Call( + extractMethod, + Expression.ArrayIndex( + jsArgumentsParameter, + Expression.Constant(i) + ) + ) + ); + } + } + + var blockStatements = new Expression[parameterInfos.Length + 2]; + + // + // if (args.Count != valueOf(n)) + // throw new ArgumentException("Invalid argument count."); + // + blockStatements[0] = Expression.Condition( + Expression.NotEqual( + Expression.Property(jsArgumentsParameter, s_countProperty), + Expression.Constant(n) + ), + s_throwExpression, + Expression.Empty() + ); + + // + // p0 = Extract(jsArguments[0]); + // p1 = Extract(jsArguments[1]); + // ... + // pn = Extract(jsArguments[n]); + // + Array.Copy(extractExpressions, 0, blockStatements, 1, parameterInfos.Length); + + blockStatements[parameterInfos.Length + 1] = Expression.Call( + Expression.Constant(instance), + method, + parameterExpressions); + + return Expression.Lambda>( + Expression.Block(parameterExpressions, blockStatements), + catalystInstanceParameter, + jsArgumentsParameter + ); + } + + private static T Extract(JToken value) + { + return value.ToObject(); + } + + private static ICallback ExtractCallback(JToken value, ICatalystInstance instance) + { + var id = value.Value(); + return new Callback(id, instance); + } + + class Callback : ICallback + { + private readonly int _id; + private readonly ICatalystInstance _instance; + + public Callback(int id, ICatalystInstance instance) + { + _id = id; + _instance = instance; + } + + public void Invoke(params object[] arguments) + { + _instance.InvokeCallback(_id, JArray.FromObject(arguments)); + } + } + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs b/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs new file mode 100644 index 00000000000..f0fd3c372f1 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs @@ -0,0 +1,146 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace ReactNative.Bridge +{ + public sealed class NativeModuleRegistry + { + private readonly IList _moduleTable; + private readonly IDictionary _moduleInstances; + private readonly IList _batchCompleteListenerModules; + + private NativeModuleRegistry( + IList moduleTable, + IDictionary moduleInstances) + { + _moduleTable = moduleTable; + _moduleInstances = moduleInstances; + _batchCompleteListenerModules = _moduleTable + .Select(moduleDefinition => moduleDefinition.Target) + .OfType() + .ToList(); + } + + public ICollection Modules + { + get + { + return _moduleInstances.Values; + } + } + + public T GetModule() where T : INativeModule + { + var instance = default(INativeModule); + if (_moduleInstances.TryGetValue(typeof(T), out instance)) + { + return (T)instance; + } + + throw new InvalidOperationException("No module instance for type '{0}'."); + } + + class ModuleDefinition + { + private readonly int _id; + private readonly string _name; + private readonly IList _methods; + + public ModuleDefinition(int id, string name, INativeModule target) + { + _id = id; + _name = name; + Target = target; + _methods = new List(target.Methods.Count); + + foreach (var entry in target.Methods) + { + _methods.Add( + new MethodRegistration( + entry.Key, + "NativeCall__" + target.Name + "_" + entry.Key, + entry.Value)); + } + } + + public INativeModule Target { get; } + + public void Invoke(ICatalystInstance catalystInstance, int methodId, JArray parameters) + { + _methods[methodId].Method.Invoke(catalystInstance, parameters); + } + + class MethodRegistration + { + public MethodRegistration(string name, string tracingName, INativeMethod method) + { + Name = name; + TracingName = tracingName; + Method = method; + } + + public string Name { get; } + + public string TracingName { get; } + + public INativeMethod Method { get; } + } + } + + public sealed class Builder + { + private readonly IDictionary _modules = + new Dictionary(); + + public Builder Add(INativeModule module) + { + if (module == null) + throw new ArgumentNullException(nameof(module)); + if (module.Name == null) + throw new ArgumentException( + nameof(module), + string.Format( + CultureInfo.InvariantCulture, + "Native module '{0}' cannot have a null `Name`.", + module.GetType())); + + var existing = default(INativeModule); + if (_modules.TryGetValue(module.Name, out existing) && !module.CanOverrideExistingModule) + { + throw new InvalidOperationException( + string.Format( + CultureInfo.InvariantCulture, + "Native module '{0}' tried to override '{1}' for module name '{2}'. " + + "If this was your intention, override `CanOverrideExistingModule`.", + module.GetType().Name, + existing.GetType().Name, + module.Name)); + + } + + _modules.Add(module.Name, module); + + return this; + } + + public NativeModuleRegistry Build() + { + var moduleTable = new List(_modules.Count); + var moduleInstances = new Dictionary(_modules.Count); + + var idx = 0; + foreach (var module in _modules.Values) + { + var moduleDef = new ModuleDefinition(idx++, module.Name, module); + moduleTable.Add(moduleDef); + moduleInstances.Add(module.GetType(), module); + } + + return new NativeModuleRegistry(moduleTable, moduleInstances); + } + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs b/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs new file mode 100644 index 00000000000..c53cfb69843 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs @@ -0,0 +1,40 @@ +using System; +using System.Threading.Tasks; + +namespace ReactNative.Bridge.Queue +{ + public interface IMessageQueueThread + { + /// + /// Runs the given action on this thread. + /// + /// + /// The action will be submitted to the end of the event queue + /// even if it is being submitted from the same queue Thread. + /// + /// The action. + void RunOnQueue(Action action); + + /// + /// Invokes the given function on this thread. + /// + /// + /// The function will be submitted to the end of the event queue + /// even if it is being submitted from the same queue Thread. + /// + /// The type of result expected. + /// The function. + /// The result of the function. + Task CallOnQueue(Func func); + + /// + /// Checks whether the current thread is also the thread + /// associated with this . + /// + /// + /// true if the current thread is associated with this + /// instance, false otherwise. + /// + bool IsOnThread(); + } +} diff --git a/ReactWindows/ReactNative/Bridge/Queue/IQueueThreadExceptionHandler.cs b/ReactWindows/ReactNative/Bridge/Queue/IQueueThreadExceptionHandler.cs new file mode 100644 index 00000000000..3b5d0c9f01b --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/Queue/IQueueThreadExceptionHandler.cs @@ -0,0 +1,9 @@ +using System; + +namespace ReactNative.Bridge.Queue +{ + public interface IQueueThreadExceptionHandler + { + void HandleException(Exception ex); + } +} diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs new file mode 100644 index 00000000000..097133b158b --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs @@ -0,0 +1,54 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Windows.System.Threading; + +namespace ReactNative.Bridge.Queue +{ + public abstract class MessageQueueThread : IMessageQueueThread + { + protected readonly ConcurrentQueue _runOnQueueQueue = + new ConcurrentQueue(); + + public abstract bool IsOnThread(); + + public void RunOnQueue(Action action) + { + _runOnQueueQueue.Enqueue(action); + } + + public abstract void Start(); + + public MessageQueueThread Create( + MessageQueueThreadSpec spec, + IQueueThreadExceptionHandler handler) + { + switch (spec.Kind) + { + case MessageQueueThreadKind.MainUi: + return new DispatcherMessageQueueThread(name, handler); + case MessageQueueThreadKind.NewBackground: + return new BackgroundMessageQueueThread(name, handler); + default: + throw new InvalidOperationException( + string.Format( + CultureInfo.InvariantCulture, + "Unknown thread type '{0}' with name '{1}'.", + spec.Kind, + spec.Name)); + } + } + + class DispatcherMessageQueueThread : MessageQueueThread + { + public DispatcherMessageQueueThread(string name, IQueueThreadExceptionHandler handler) + { + + } + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs new file mode 100644 index 00000000000..da7879643df --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs @@ -0,0 +1,36 @@ +using System; +using System.Threading.Tasks; + +namespace ReactNative.Bridge.Queue +{ + static class MessageQueueThreadExtensions + { + /// + /// Asserts , throwing if the false. + /// + /// The message queue thread. + /// + /// Thrown if the assertion fails. + /// + public static void AssertIsOnThread(this IMessageQueueThread actionQueue) + { + if (!actionQueue.IsOnThread()) + { + throw new InvalidOperationException("Thread access assertion failed."); + } + } + + public Task CallOnQueue(this IMessageQueueThread actionQueue, Func func) + { + var taskCompletionSource = new TaskCompletionSource(); + + actionQueue.RunOnQueue(async () => + { + var result = func; + await ThreadPool.RunAsync(taskCompletionSource.SetResult(result)); + }); + + return taskCompletionSource.Task; + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs new file mode 100644 index 00000000000..a3dc64d4a96 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs @@ -0,0 +1,9 @@ +namespace ReactNative.Bridge.Queue +{ + enum MessageQueueThreadKind + { + MainUi, + NewBackground, + } + +} diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs new file mode 100644 index 00000000000..c85261ec51f --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReactNative.Bridge.Queue +{ + public class MessageQueueThreadSpec + { + private MessageQueueThreadSpec(MessageQueueThreadKind kind, string name) + { + Name = name; + Kind = kind; + } + + public string Name { get; } + + internal MessageQueueThreadKind Kind { get; } + + public static MessageQueueThreadSpec MainUiThreadSpec { get; } = new MessageQueueThreadSpec(MessageQueueThreadKind.MainUi, "main_ui") + + public static MessageQueueThreadSpec Create(string name) + { + return new MessageQueueThreadSpec(MessageQueueThreadKind.NewBackground, name); + } + + enum MessageQueueThreadKind + { + MainUi, + NewBackground, + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/ReactContext.cs b/ReactWindows/ReactNative/Bridge/ReactContext.cs new file mode 100644 index 00000000000..6302dec264f --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/ReactContext.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReactNative.Bridge +{ + class ReactApplicationContext + { + } +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptBackgroundWorkItemCallback.cs b/ReactWindows/ReactNative/Hosting/JavaScriptBackgroundWorkItemCallback.cs new file mode 100644 index 00000000000..18ac1aa4536 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptBackgroundWorkItemCallback.cs @@ -0,0 +1,14 @@ +namespace ReactNative.Hosting +{ + using System; + + /// + /// A background work item callback. + /// + /// + /// This is passed to the host's thread service (if provided) to allow the host to + /// invoke the work item callback on the background thread of its choice. + /// + /// Data argument passed to the thread service. + public delegate void JavaScriptBackgroundWorkItemCallback(IntPtr callbackData); +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptBeforeCollectCallback.cs b/ReactWindows/ReactNative/Hosting/JavaScriptBeforeCollectCallback.cs new file mode 100644 index 00000000000..06643a89dba --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptBeforeCollectCallback.cs @@ -0,0 +1,10 @@ +namespace ReactNative.Hosting +{ + using System; + + /// + /// A callback called before collection. + /// + /// The state passed to SetBeforeCollectCallback. + public delegate void JavaScriptBeforeCollectCallback(IntPtr callbackState); +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptContext.cs b/ReactWindows/ReactNative/Hosting/JavaScriptContext.cs new file mode 100644 index 00000000000..7369efe82f1 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptContext.cs @@ -0,0 +1,500 @@ +namespace ReactNative.Hosting +{ + using System; + + /// + /// A script context. + /// + /// + /// + /// Each script context contains its own global object, distinct from the global object in + /// other script contexts. + /// + /// + /// Many Chakra hosting APIs require an "active" script context, which can be set using + /// Current. Chakra hosting APIs that require a current context to be set will note + /// that explicitly in their documentation. + /// + /// + public struct JavaScriptContext + { + /// + /// The reference. + /// + private readonly IntPtr reference; + + /// + /// Initializes a new instance of the struct. + /// + /// The reference. + internal JavaScriptContext(IntPtr reference) + { + this.reference = reference; + } + + /// + /// Gets an invalid context. + /// + public static JavaScriptContext Invalid + { + get { return new JavaScriptContext(IntPtr.Zero); } + } + + /// + /// Gets or sets the current script context on the thread. + /// + public static JavaScriptContext Current + { + get + { + JavaScriptContext reference; + Native.ThrowIfError(Native.JsGetCurrentContext(out reference)); + return reference; + } + + set + { + Native.ThrowIfError(Native.JsSetCurrentContext(value)); + } + } + + /// + /// Gets a value indicating whether the runtime of the current context is in an exception state. + /// + /// + /// + /// If a call into the runtime results in an exception (either as the result of running a + /// script or due to something like a conversion failure), the runtime is placed into an + /// "exception state." All calls into any context created by the runtime (except for the + /// exception APIs) will fail with InExceptionState until the exception is + /// cleared. + /// + /// + /// If the runtime of the current context is in the exception state when a callback returns + /// into the engine, the engine will automatically rethrow the exception. + /// + /// + /// Requires an active script context. + /// + /// + public static bool HasException + { + get + { + bool hasException; + Native.ThrowIfError(Native.JsHasException(out hasException)); + return hasException; + } + } + + /// + /// Gets a value indicating whether the heap of the current context is being enumerated. + /// + /// + /// Requires an active script context. + /// + public static bool IsEnumeratingHeap + { + get + { + bool isEnumerating; + Native.ThrowIfError(Native.JsIsEnumeratingHeap(out isEnumerating)); + return isEnumerating; + } + } + + /// + /// Gets the runtime that the context belongs to. + /// + public JavaScriptRuntime Runtime + { + get + { + JavaScriptRuntime handle; + Native.ThrowIfError(Native.JsGetRuntime(this, out handle)); + return handle; + } + } + + /// + /// Gets a value indicating whether the context is a valid context or not. + /// + public bool IsValid + { + get { return reference != IntPtr.Zero; } + } + + /// + /// Tells the runtime to do any idle processing it need to do. + /// + /// + /// + /// If idle processing has been enabled for the current runtime, calling Idle will + /// inform the current runtime that the host is idle and that the runtime can perform + /// memory cleanup tasks. + /// + /// + /// Idle will also return the number of system ticks until there will be more idle work + /// for the runtime to do. Calling Idle before this number of ticks has passed will do + /// no work. + /// + /// + /// Requires an active script context. + /// + /// + /// + /// The next system tick when there will be more idle work to do. Returns the + /// maximum number of ticks if there no upcoming idle work to do. + /// + public static uint Idle() + { + uint ticks; + Native.ThrowIfError(Native.JsIdle(out ticks)); + return ticks; + } + + /// + /// Parses a script and returns a Function representing the script. + /// + /// + /// Requires an active script context. + /// + /// The script to parse. + /// + /// A cookie identifying the script that can be used by script contexts that have debugging enabled. + /// + /// The location the script came from. + /// A Function representing the script code. + public static JavaScriptValue ParseScript(string script, JavaScriptSourceContext sourceContext, string sourceName) + { + JavaScriptValue result; + Native.ThrowIfError(Native.JsParseScript(script, sourceContext, sourceName, out result)); + return result; + } + + /// + /// Parses a serialized script and returns a Function representing the script. + /// + /// + /// Requires an active script context. + /// + /// The script to parse. + /// The serialized script. + /// + /// A cookie identifying the script that can be used by script contexts that have debugging enabled. + /// + /// The location the script came from. + /// A Function representing the script code. + public static JavaScriptValue ParseScript(string script, byte[] buffer, JavaScriptSourceContext sourceContext, string sourceName) + { + JavaScriptValue result; + Native.ThrowIfError(Native.JsParseSerializedScript(script, buffer, sourceContext, sourceName, out result)); + return result; + } + + /// + /// Parses a script and returns a Function representing the script. + /// + /// + /// Requires an active script context. + /// + /// The script to parse. + /// A Function representing the script code. + public static JavaScriptValue ParseScript(string script) + { + return ParseScript(script, JavaScriptSourceContext.None, string.Empty); + } + + /// + /// Parses a serialized script and returns a Function representing the script. + /// + /// + /// Requires an active script context. + /// + /// The script to parse. + /// The serialized script. + /// A Function representing the script code. + public static JavaScriptValue ParseScript(string script, byte[] buffer) + { + return ParseScript(script, buffer, JavaScriptSourceContext.None, string.Empty); + } + + /// + /// Executes a script. + /// + /// + /// Requires an active script context. + /// + /// The script to run. + /// + /// A cookie identifying the script that can be used by script contexts that have debugging enabled. + /// + /// The location the script came from. + /// The result of the script, if any. + public static JavaScriptValue RunScript(string script, JavaScriptSourceContext sourceContext, string sourceName) + { + JavaScriptValue result; + Native.ThrowIfError(Native.JsRunScript(script, sourceContext, sourceName, out result)); + return result; + } + + /// + /// Runs a serialized script. + /// + /// + /// Requires an active script context. + /// + /// The source code of the serialized script. + /// The serialized script. + /// + /// A cookie identifying the script that can be used by script contexts that have debugging enabled. + /// + /// The location the script came from. + /// The result of the script, if any. + public static JavaScriptValue RunScript(string script, byte[] buffer, JavaScriptSourceContext sourceContext, string sourceName) + { + JavaScriptValue result; + Native.ThrowIfError(Native.JsRunSerializedScript(script, buffer, sourceContext, sourceName, out result)); + return result; + } + + /// + /// Executes a script. + /// + /// + /// Requires an active script context. + /// + /// The script to run. + /// The result of the script, if any. + public static JavaScriptValue RunScript(string script) + { + return RunScript(script, JavaScriptSourceContext.None, string.Empty); + } + + /// + /// Runs a serialized script. + /// + /// + /// Requires an active script context. + /// + /// The source code of the serialized script. + /// The serialized script. + /// The result of the script, if any. + public static JavaScriptValue RunScript(string script, byte[] buffer) + { + return RunScript(script, buffer, JavaScriptSourceContext.None, string.Empty); + } + + /// + /// Serializes a parsed script to a buffer than can be reused. + /// + /// + /// + /// SerializeScript parses a script and then stores the parsed form of the script in a + /// runtime-independent format. The serialized script then can be deserialized in any + /// runtime without requiring the script to be re-parsed. + /// + /// + /// Requires an active script context. + /// + /// + /// The script to serialize. + /// The buffer to put the serialized script into. Can be null. + /// + /// The size of the buffer, in bytes, required to hold the serialized script. + /// + public static ulong SerializeScript(string script, byte[] buffer) + { + var bufferSize = (ulong)buffer.Length; + Native.ThrowIfError(Native.JsSerializeScript(script, buffer, ref bufferSize)); + return bufferSize; + } + + /// + /// Returns the exception that caused the runtime of the current context to be in the + /// exception state and resets the exception state for that runtime. + /// + /// + /// + /// If the runtime of the current context is not in an exception state, this API will throw + /// JsErrorInvalidArgument. If the runtime is disabled, this will return an exception + /// indicating that the script was terminated, but it will not clear the exception (the + /// exception will be cleared if the runtime is re-enabled using + /// EnableRuntimeExecution). + /// + /// + /// Requires an active script context. + /// + /// + /// The exception for the runtime of the current context. + public static JavaScriptValue GetAndClearException() + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsGetAndClearException(out reference)); + return reference; + } + + /// + /// Sets the runtime of the current context to an exception state. + /// + /// + /// + /// If the runtime of the current context is already in an exception state, this API will + /// throw JsErrorInExceptionState. + /// + /// + /// Requires an active script context. + /// + /// + /// + /// The JavaScript exception to set for the runtime of the current context. + /// + public static void SetException(JavaScriptValue exception) + { + Native.ThrowIfError(Native.JsSetException(exception)); + } + +#if RELEASE64 + /// + /// Starts debugging in the context. + /// + /// The debug application to use for debugging. + public static void StartDebugging(Native.IDebugApplication64 debugApplication) + { + Native.ThrowIfError(Native.JsStartDebugging(debugApplication)); + } +#endif + + /// + /// Starts debugging in the context. + /// + /// The debug application to use for debugging. + public static void StartDebugging(Native.IDebugApplication32 debugApplication) + { + Native.ThrowIfError(Native.JsStartDebugging(debugApplication)); + } + + /// + /// Starts profiling in the current context. + /// + /// + /// Requires an active script context. + /// + /// The profiling callback to use. + /// The profiling events to callback with. + /// A context to pass to the profiling callback. + public static void StartProfiling(Native.IActiveScriptProfilerCallback callback, Native.ProfilerEventMask eventMask, int context) + { + Native.ThrowIfError(Native.JsStartProfiling(callback, eventMask, context)); + } + + /// + /// Stops profiling in the current context. + /// + /// + /// + /// Will not return an error if profiling has not started. + /// + /// + /// Requires an active script context. + /// + /// + /// + /// The reason for stopping profiling to pass to the profiler callback. + /// + public static void StopProfiling(int reason) + { + Native.ThrowIfError(Native.JsStopProfiling(reason)); + } + + /// + /// Enumerates the heap of the current context. + /// + /// + /// + /// While the heap is being enumerated, the current context cannot be removed, and all calls to + /// modify the state of the context will fail until the heap enumerator is released. + /// + /// + /// Requires an active script context. + /// + /// + /// A heap enumerator. + public static Native.IActiveScriptProfilerHeapEnum EnumerateHeap() + { + Native.IActiveScriptProfilerHeapEnum enumerator; + Native.ThrowIfError(Native.JsEnumerateHeap(out enumerator)); + return enumerator; + } + + /// + /// Adds a reference to a script context. + /// + /// + /// Calling AddRef ensures that the context will not be freed until Release is called. + /// + /// The object's new reference count. + public uint AddRef() + { + uint count; + Native.ThrowIfError(Native.JsContextAddRef(this, out count)); + return count; + } + + /// + /// Releases a reference to a script context. + /// + /// + /// Removes a reference to a context that was created by AddRef. + /// + /// The object's new reference count. + public uint Release() + { + uint count; + Native.ThrowIfError(Native.JsContextRelease(this, out count)); + return count; + } + + /// + /// A scope automatically sets a context to current and resets the original context + /// when disposed. + /// + public struct Scope : IDisposable + { + /// + /// The previous context. + /// + private readonly JavaScriptContext previousContext; + + /// + /// Whether the structure has been disposed. + /// + private bool disposed; + + /// + /// Initializes a new instance of the struct. + /// + /// The context to create the scope for. + public Scope(JavaScriptContext context) + { + disposed = false; + previousContext = Current; + Current = context; + } + + /// + /// Disposes the scope and sets the previous context to current. + /// + public void Dispose() + { + if (disposed) + { + return; + } + + Current = previousContext; + disposed = true; + } + } + } +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptEngineException.cs b/ReactWindows/ReactNative/Hosting/JavaScriptEngineException.cs new file mode 100644 index 00000000000..86098f48818 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptEngineException.cs @@ -0,0 +1,30 @@ +namespace ReactNative.Hosting +{ + using System; + using System.Runtime.Serialization; + + /// + /// An exception that occurred in the workings of the JavaScript engine itself. + /// + public sealed class JavaScriptEngineException : JavaScriptException + { + /// + /// Initializes a new instance of the class. + /// + /// The error code returned. + public JavaScriptEngineException(JavaScriptErrorCode code) : + this(code, "A fatal exception has occurred in a JavaScript runtime") + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error code returned. + /// The error message. + public JavaScriptEngineException(JavaScriptErrorCode code, string message) : + base(code, message) + { + } + } +} \ No newline at end of file diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptErrorCode.cs b/ReactWindows/ReactNative/Hosting/JavaScriptErrorCode.cs new file mode 100644 index 00000000000..7abf8462c08 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptErrorCode.cs @@ -0,0 +1,158 @@ +namespace ReactNative.Hosting +{ + using System.Diagnostics.CodeAnalysis; + + /// + /// An error code returned from a Chakra hosting API. + /// + public enum JavaScriptErrorCode : uint + { + /// + /// Success error code. + /// + NoError = 0, + + /// + /// Category of errors that relates to incorrect usage of the API itself. + /// + CategoryUsage = 0x10000, + + /// + /// An argument to a hosting API was invalid. + /// + InvalidArgument, + + /// + /// An argument to a hosting API was null in a context where null is not allowed. + /// + NullArgument, + + /// + /// The hosting API requires that a context be current, but there is no current context. + /// + NoCurrentContext, + + /// + /// The engine is in an exception state and no APIs can be called until the exception is + /// cleared. + /// + InExceptionState, + + /// + /// A hosting API is not yet implemented. + /// + NotImplemented, + + /// + /// A hosting API was called on the wrong thread. + /// + WrongThread, + + /// + /// A runtime that is still in use cannot be disposed. + /// + RuntimeInUse, + + /// + /// A bad serialized script was used, or the serialized script was serialized by a + /// different version of the Chakra engine. + /// + BadSerializedScript, + + /// + /// The runtime is in a disabled state. + /// + InDisabledState, + + /// + /// Runtime does not support reliable script interruption. + /// + CannotDisableExecution, + + /// + /// A heap enumeration is currently underway in the script context. + /// + HeapEnumInProgress, + + /// + /// A hosting API that operates on Object values was called with a non-Object value. + /// + ArgumentNotObject, + + /// + /// A script context is in the middle of a profile callback. + /// + InProfileCallback, + + /// + /// A thread service callback is currently underway. + /// + InThreadServiceCallback, + + /// + /// Scripts cannot be serialized in debug contexts. + /// + CannotSerializeDebugScript, + + /// + /// The context cannot be put into a debug state because it is already in a debug state. + /// + AlreadyDebuggingContext, + + /// + /// The context cannot start profiling because it is already profiling. + /// + AlreadyProfilingContext, + + /// + /// Idle notification given when the host did not enable idle processing. + /// + IdleNotEnabled, + + /// + /// Category of errors that relates to errors occurring within the engine itself. + /// + CategoryEngine = 0x20000, + + /// + /// The Chakra engine has run out of memory. + /// + OutOfMemory, + + /// + /// Category of errors that relates to errors in a script. + /// + CategoryScript = 0x30000, + + /// + /// A JavaScript exception occurred while running a script. + /// + ScriptException, + + /// + /// JavaScript failed to compile. + /// + ScriptCompile, + + /// + /// A script was terminated due to a request to suspend a runtime. + /// + ScriptTerminated, + + /// + /// A script was terminated because it tried to use "eval" or "function" and eval was disabled. + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Eval is a valid function name.")] + ScriptEvalDisabled, + + /// + /// Category of errors that are fatal and signify failure of the engine. + /// + CategoryFatal = 0x40000, + + /// + /// A fatal error in the engine has occurred. + /// + Fatal, + } +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptException.cs b/ReactWindows/ReactNative/Hosting/JavaScriptException.cs new file mode 100644 index 00000000000..888c17aaff2 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptException.cs @@ -0,0 +1,44 @@ +namespace ReactNative.Hosting +{ + using System; + using System.Runtime.Serialization; + + /// + /// An exception returned from the Chakra engine. + /// + public class JavaScriptException : Exception + { + /// + /// The error code. + /// + private readonly JavaScriptErrorCode code; + + /// + /// Initializes a new instance of the class. + /// + /// The error code returned. + public JavaScriptException(JavaScriptErrorCode code) : + this(code, "A fatal exception has occurred in a JavaScript runtime") + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error code returned. + /// The error message. + public JavaScriptException(JavaScriptErrorCode code, string message) : + base(message) + { + this.code = code; + } + + /// + /// Gets the error code. + /// + public JavaScriptErrorCode ErrorCode + { + get { return code; } + } + } +} \ No newline at end of file diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptFatalException.cs b/ReactWindows/ReactNative/Hosting/JavaScriptFatalException.cs new file mode 100644 index 00000000000..7927401f4c8 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptFatalException.cs @@ -0,0 +1,30 @@ +namespace ReactNative.Hosting +{ + using System; + using System.Runtime.Serialization; + + /// + /// A fatal exception occurred. + /// + public sealed class JavaScriptFatalException : JavaScriptException + { + /// + /// Initializes a new instance of the class. + /// + /// The error code returned. + public JavaScriptFatalException(JavaScriptErrorCode code) : + this(code, "A fatal exception has occurred in a JavaScript runtime") + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error code returned. + /// The error message. + public JavaScriptFatalException(JavaScriptErrorCode code, string message) : + base(code, message) + { + } + } +} \ No newline at end of file diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptMemoryAllocationCallback.cs b/ReactWindows/ReactNative/Hosting/JavaScriptMemoryAllocationCallback.cs new file mode 100644 index 00000000000..df24f05dc00 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptMemoryAllocationCallback.cs @@ -0,0 +1,17 @@ +namespace ReactNative.Hosting +{ + using System; + + /// + /// User implemented callback routine for memory allocation events + /// + /// The state passed to SetRuntimeMemoryAllocationCallback. + /// The type of type allocation event. + /// The size of the allocation. + /// + /// For the Allocate event, returning true allows the runtime to continue with + /// allocation. Returning false indicates the allocation request is rejected. The return value + /// is ignored for other allocation events. + /// + public delegate bool JavaScriptMemoryAllocationCallback(IntPtr callbackState, JavaScriptMemoryEventType allocationEvent, UIntPtr allocationSize); +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptMemoryEventType.cs b/ReactWindows/ReactNative/Hosting/JavaScriptMemoryEventType.cs new file mode 100644 index 00000000000..83c0b3d54ec --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptMemoryEventType.cs @@ -0,0 +1,23 @@ +namespace ReactNative.Hosting +{ + /// + /// Allocation callback event type. + /// + public enum JavaScriptMemoryEventType + { + /// + /// Indicates a request for memory allocation. + /// + Allocate = 0, + + /// + /// Indicates a memory freeing event. + /// + Free = 1, + + /// + /// Indicates a failed allocation event. + /// + Failure = 2 + } +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptNativeFunction.cs b/ReactWindows/ReactNative/Hosting/JavaScriptNativeFunction.cs new file mode 100644 index 00000000000..ac119ff1a9f --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptNativeFunction.cs @@ -0,0 +1,19 @@ +using System; + +namespace ReactNative.Hosting +{ + using System.Runtime.InteropServices; + + /// + /// A function callback. + /// + /// + /// A Function object that represents the function being invoked. + /// + /// Indicates whether this is a regular call or a 'new' call. + /// The arguments to the call. + /// The number of arguments. + /// Callback data, if any. + /// The result of the call, if any. + public delegate JavaScriptValue JavaScriptNativeFunction(JavaScriptValue callee, bool isConstructCall, [MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 3)] JavaScriptValue[] arguments, ushort argumentCount, IntPtr callbackData); +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptObjectFinalizeCallback.cs b/ReactWindows/ReactNative/Hosting/JavaScriptObjectFinalizeCallback.cs new file mode 100644 index 00000000000..8a8f8385eca --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptObjectFinalizeCallback.cs @@ -0,0 +1,12 @@ +namespace ReactNative.Hosting +{ + using System; + + /// + /// A finalization callback. + /// + /// + /// The external data that was passed in when creating the object being finalized. + /// + public delegate void JavaScriptObjectFinalizeCallback(IntPtr data); +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptPropertyId.cs b/ReactWindows/ReactNative/Hosting/JavaScriptPropertyId.cs new file mode 100644 index 00000000000..7af468d1b20 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptPropertyId.cs @@ -0,0 +1,141 @@ +namespace ReactNative.Hosting +{ + using System; + + /// + /// A property identifier. + /// + /// + /// Property identifiers are used to refer to properties of JavaScript objects instead of using + /// strings. + /// + public struct JavaScriptPropertyId : IEquatable + { + /// + /// The id. + /// + private readonly IntPtr id; + + /// + /// Initializes a new instance of the struct. + /// + /// The ID. + internal JavaScriptPropertyId(IntPtr id) + { + this.id = id; + } + + /// + /// Gets an invalid ID. + /// + public static JavaScriptPropertyId Invalid + { + get { return new JavaScriptPropertyId(IntPtr.Zero); } + } + + /// + /// Gets the name associated with the property ID. + /// + /// + /// + /// Requires an active script context. + /// + /// + public string Name + { + get + { + string name; + Native.ThrowIfError(Native.JsGetPropertyNameFromId(this, out name)); + return name; + } + } + + /// + /// Gets the property ID associated with the name. + /// + /// + /// + /// Property IDs are specific to a context and cannot be used across contexts. + /// + /// + /// Requires an active script context. + /// + /// + /// + /// The name of the property ID to get or create. The name may consist of only digits. + /// + /// The property ID in this runtime for the given name. + public static JavaScriptPropertyId FromString(string name) + { + JavaScriptPropertyId id; + Native.ThrowIfError(Native.JsGetPropertyIdFromName(name, out id)); + return id; + } + + /// + /// The equality operator for property IDs. + /// + /// The first property ID to compare. + /// The second property ID to compare. + /// Whether the two property IDs are the same. + public static bool operator ==(JavaScriptPropertyId left, JavaScriptPropertyId right) + { + return left.Equals(right); + } + + /// + /// The inequality operator for property IDs. + /// + /// The first property ID to compare. + /// The second property ID to compare. + /// Whether the two property IDs are not the same. + public static bool operator !=(JavaScriptPropertyId left, JavaScriptPropertyId right) + { + return !left.Equals(right); + } + + /// + /// Checks for equality between property IDs. + /// + /// The other property ID to compare. + /// Whether the two property IDs are the same. + public bool Equals(JavaScriptPropertyId other) + { + return id == other.id; + } + + /// + /// Checks for equality between property IDs. + /// + /// The other property ID to compare. + /// Whether the two property IDs are the same. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + return obj is JavaScriptPropertyId && Equals((JavaScriptPropertyId)obj); + } + + /// + /// The hash code. + /// + /// The hash code of the property ID. + public override int GetHashCode() + { + return id.ToInt32(); + } + + /// + /// Converts the property ID to a string. + /// + /// The name of the property ID. + public override string ToString() + { + return Name; + } + } +} \ No newline at end of file diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptRuntime.cs b/ReactWindows/ReactNative/Hosting/JavaScriptRuntime.cs new file mode 100644 index 00000000000..a75d5ae46f2 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptRuntime.cs @@ -0,0 +1,265 @@ +namespace ReactNative.Hosting +{ + using System; + + /// + /// A Chakra runtime. + /// + /// + /// + /// Each Chakra runtime has its own independent execution engine, JIT compiler, and garbage + /// collected heap. As such, each runtime is completely isolated from other runtimes. + /// + /// + /// Runtimes can be used on any thread, but only one thread can call into a runtime at any + /// time. + /// + /// + /// NOTE: A JavaScriptRuntime, unlike other objects in the Chakra hosting API, is not + /// garbage collected since it contains the garbage collected heap itself. A runtime will + /// continue to exist until Dispose is called. + /// + /// + public struct JavaScriptRuntime : IDisposable + { + /// + /// The handle. + /// + private IntPtr handle; + + /// + /// Gets a value indicating whether the runtime is valid. + /// + public bool IsValid + { + get { return handle != IntPtr.Zero; } + } + + /// + /// Gets the current memory usage for a runtime. + /// + /// + /// Memory usage can be always be retrieved, regardless of whether or not the runtime is active + /// on another thread. + /// + public UIntPtr MemoryUsage + { + get + { + UIntPtr memoryUsage; + Native.ThrowIfError(Native.JsGetRuntimeMemoryUsage(this, out memoryUsage)); + return memoryUsage; + } + } + + /// + /// Gets or sets the current memory limit for a runtime. + /// + /// + /// The memory limit of a runtime can be always be retrieved, regardless of whether or not the + /// runtime is active on another thread. + /// + public UIntPtr MemoryLimit + { + get + { + UIntPtr memoryLimit; + Native.ThrowIfError(Native.JsGetRuntimeMemoryLimit(this, out memoryLimit)); + return memoryLimit; + } + + set + { + Native.ThrowIfError(Native.JsSetRuntimeMemoryLimit(this, value)); + } + } + + /// + /// Gets or sets a value indicating whether script execution is disabled in the runtime. + /// + public bool Disabled + { + get + { + bool isDisabled; + Native.ThrowIfError(Native.JsIsRuntimeExecutionDisabled(this, out isDisabled)); + return isDisabled; + } + + set + { + Native.ThrowIfError(value + ? Native.JsDisableRuntimeExecution(this) + : Native.JsEnableRuntimeExecution(this)); + } + } + + /// + /// Creates a new runtime. + /// + /// The attributes of the runtime to be created. + /// The version of the runtime to be created. + /// The thread service for the runtime. Can be null. + /// The runtime created. + public static JavaScriptRuntime Create(JavaScriptRuntimeAttributes attributes, JavaScriptRuntimeVersion version, JavaScriptThreadServiceCallback threadServiceCallback) + { + JavaScriptRuntime handle; + Native.ThrowIfError(Native.JsCreateRuntime(attributes, version, threadServiceCallback, out handle)); + return handle; + } + + /// + /// Creates a new runtime. + /// + /// The attributes of the runtime to be created. + /// The version of the runtime to be created. + /// The runtime created. + public static JavaScriptRuntime Create(JavaScriptRuntimeAttributes attributes, JavaScriptRuntimeVersion version) + { + return Create(attributes, version, null); + } + + /// + /// Creates a new runtime. + /// + /// The runtime created. + public static JavaScriptRuntime Create() + { + return Create(JavaScriptRuntimeAttributes.None, JavaScriptRuntimeVersion.Version11, null); + } + + /// + /// Disposes a runtime. + /// + /// + /// Once a runtime has been disposed, all resources owned by it are invalid and cannot be used. + /// If the runtime is active (i.e. it is set to be current on a particular thread), it cannot + /// be disposed. + /// + public void Dispose() + { + if (IsValid) + { + Native.ThrowIfError(Native.JsDisposeRuntime(this)); + } + + handle = IntPtr.Zero; + } + + /// + /// Performs a full garbage collection. + /// + public void CollectGarbage() + { + Native.ThrowIfError(Native.JsCollectGarbage(this)); + } + + /// + /// Sets a memory allocation callback for specified runtime + /// + /// + /// + /// Registering a memory allocation callback will cause the runtime to call back to the host + /// whenever it acquires memory from, or releases memory to, the OS. The callback routine is + /// called before the runtime memory manager allocates a block of memory. The allocation will + /// be rejected if the callback returns false. The runtime memory manager will also invoke the + /// callback routine after freeing a block of memory, as well as after allocation failures. + /// + /// + /// The callback is invoked on the current runtime execution thread, therefore execution is + /// blocked until the callback completes. + /// + /// + /// The return value of the callback is not stored; previously rejected allocations will not + /// prevent the runtime from invoking the callback again later for new memory allocations. + /// + /// + /// + /// User provided state that will be passed back to the callback. + /// + /// + /// Memory allocation callback to be called for memory allocation events. + /// + public void SetMemoryAllocationCallback(IntPtr callbackState, JavaScriptMemoryAllocationCallback allocationCallback) + { + Native.ThrowIfError(Native.JsSetRuntimeMemoryAllocationCallback(this, callbackState, allocationCallback)); + } + + /// + /// Sets a callback function that is called by the runtime before garbage collection. + /// + /// + /// + /// The callback is invoked on the current runtime execution thread, therefore execution is + /// blocked until the callback completes. + /// + /// + /// The callback can be used by hosts to prepare for garbage collection. For example, by + /// releasing unnecessary references on Chakra objects. + /// + /// + /// + /// User provided state that will be passed back to the callback. + /// + /// The callback function being set. + public void SetBeforeCollectCallback(IntPtr callbackState, JavaScriptBeforeCollectCallback beforeCollectCallback) + { + Native.ThrowIfError(Native.JsSetRuntimeBeforeCollectCallback(this, callbackState, beforeCollectCallback)); + } + +#if RELEASE64 + /// + /// Creates a debug script context for running scripts. + /// + /// + /// Each script context has its own global object that is isolated from all other script + /// contexts. + /// + /// The debug application to use. + /// The created script context. + public JavaScriptContext CreateContext(Native.IDebugApplication64 debugApplication) + { + JavaScriptContext reference; + Native.ThrowIfError(Native.JsCreateContext(this, debugApplication, out reference)); + return reference; + } +#endif + +#if !RELEASE64 + /// + /// Creates a debug script context for running scripts. + /// + /// + /// Each script context has its own global object that is isolated from all other script + /// contexts. + /// + /// The debug application to use. + /// The created script context. + public JavaScriptContext CreateContext(Native.IDebugApplication32 debugApplication) + { + JavaScriptContext reference; + Native.ThrowIfError(Native.JsCreateContext(this, debugApplication, out reference)); + return reference; + } +#endif + + /// + /// Creates a script context for running scripts. + /// + /// + /// Each script context has its own global object that is isolated from all other script + /// contexts. + /// + /// The created script context. + public JavaScriptContext CreateContext() + { + JavaScriptContext reference; +#if RELEASE64 + Native.JsCreateContext(this, (Native.IDebugApplication64)null, out reference); +#else + Native.JsCreateContext(this, (Native.IDebugApplication32)null, out reference); +#endif + return reference; + } + } +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptRuntimeAttributes.cs b/ReactWindows/ReactNative/Hosting/JavaScriptRuntimeAttributes.cs new file mode 100644 index 00000000000..765ebcb3f3f --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptRuntimeAttributes.cs @@ -0,0 +1,46 @@ +namespace ReactNative.Hosting +{ + using System; + using System.Diagnostics.CodeAnalysis; + + /// + /// Attributes of a runtime. + /// + [Flags] + public enum JavaScriptRuntimeAttributes + { + /// + /// No special attributes. + /// + None = 0x00000000, + + /// + /// The runtime will not do any work (such as garbage collection) on background threads. + /// + DisableBackgroundWork = 0x00000001, + + /// + /// The runtime should support reliable script interruption. This increases the number of + /// places where the runtime will check for a script interrupt request at the cost of a + /// small amount of runtime performance. + /// + AllowScriptInterrupt = 0x00000002, + + /// + /// Host will call Idle, so enable idle processing. Otherwise, the runtime will manage + /// memory slightly more aggressively. + /// + EnableIdleProcessing = 0x00000004, + + /// + /// Runtime will not generate native code. + /// + DisableNativeCodeGeneration = 0x00000008, + + /// + /// Using Eval or Function constructor will throw an exception. + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Eval is a valid function name.")] + DisableEval = 0x00000010, + } +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptRuntimeVersion.cs b/ReactWindows/ReactNative/Hosting/JavaScriptRuntimeVersion.cs new file mode 100644 index 00000000000..266a51a4580 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptRuntimeVersion.cs @@ -0,0 +1,23 @@ +namespace ReactNative.Hosting +{ + /// + /// Version of the runtime. + /// + public enum JavaScriptRuntimeVersion + { + /// + /// Create runtime with IE10 version. + /// + Version10 = 0, + + /// + /// Create runtime with IE11 version. + /// + Version11 = 1, + + /// + /// Create runtime with highest version present on the machine at runtime. + /// + VersionEdge = -1, + } +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptScriptException.cs b/ReactWindows/ReactNative/Hosting/JavaScriptScriptException.cs new file mode 100644 index 00000000000..5ae42613eae --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptScriptException.cs @@ -0,0 +1,49 @@ +namespace ReactNative.Hosting +{ + using System; + using System.Runtime.Serialization; + + /// + /// A script exception. + /// + public sealed class JavaScriptScriptException : JavaScriptException + { + /// + /// The error. + /// + private readonly JavaScriptValue error; + + /// + /// Initializes a new instance of the class. + /// + /// The error code returned. + /// The JavaScript error object. + public JavaScriptScriptException(JavaScriptErrorCode code, JavaScriptValue error) : + this(code, error, "JavaScript Exception") + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error code returned. + /// The JavaScript error object. + /// The error message. + public JavaScriptScriptException(JavaScriptErrorCode code, JavaScriptValue error, string message) : + base(code, message) + { + this.error = error; + } + + /// + /// Gets a JavaScript object representing the script error. + /// + public JavaScriptValue Error + { + get + { + return error; + } + } + } +} \ No newline at end of file diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptSourceContext.cs b/ReactWindows/ReactNative/Hosting/JavaScriptSourceContext.cs new file mode 100644 index 00000000000..1e7dc92af1f --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptSourceContext.cs @@ -0,0 +1,184 @@ +namespace ReactNative.Hosting +{ + using System; + + /// + /// A cookie that identifies a script for debugging purposes. + /// + public struct JavaScriptSourceContext : IEquatable + { + /// + /// The context. + /// + private readonly IntPtr context; + + /// + /// Initializes a new instance of the struct. + /// + /// The context. + private JavaScriptSourceContext(IntPtr context) + { + this.context = context; + } + + /// + /// Gets an empty source context. + /// + public static JavaScriptSourceContext None + { + get { return new JavaScriptSourceContext(new IntPtr(-1)); } + } + + /// + /// The equality operator for source contexts. + /// + /// The first source context to compare. + /// The second source context to compare. + /// Whether the two source contexts are the same. + public static bool operator ==(JavaScriptSourceContext left, JavaScriptSourceContext right) + { + return left.Equals(right); + } + + /// + /// The inequality operator for source contexts. + /// + /// The first source context to compare. + /// The second source context to compare. + /// Whether the two source contexts are not the same. + public static bool operator !=(JavaScriptSourceContext left, JavaScriptSourceContext right) + { + return !left.Equals(right); + } + + /// + /// Subtracts an offset from the value of the source context. + /// + /// The source context to subtract the offset from. + /// The offset to subtract. + /// A new source context that reflects the subtraction of the offset from the context. + public static JavaScriptSourceContext operator -(JavaScriptSourceContext context, int offset) + { + return FromIntPtr(context.context - offset); + } + + /// + /// Subtracts an offset from the value of the source context. + /// + /// The source context to subtract the offset from. + /// The offset to subtract. + /// A new source context that reflects the subtraction of the offset from the context. + public static JavaScriptSourceContext Subtract(JavaScriptSourceContext left, int right) + { + return left - right; + } + + /// + /// Decrements the value of the source context. + /// + /// The source context to decrement. + /// A new source context that reflects the decrementing of the context. + public static JavaScriptSourceContext operator --(JavaScriptSourceContext context) + { + return FromIntPtr(context.context - 1); + } + + /// + /// Decrements the value of the source context. + /// + /// The source context to decrement. + /// A new source context that reflects the decrementing of the context. + public static JavaScriptSourceContext Decrement(JavaScriptSourceContext left) + { + return --left; + } + + /// + /// Adds an offset from the value of the source context. + /// + /// The source context to add the offset to. + /// The offset to add. + /// A new source context that reflects the addition of the offset to the context. + public static JavaScriptSourceContext operator +(JavaScriptSourceContext context, int offset) + { + return FromIntPtr(context.context + offset); + } + + /// + /// Adds an offset from the value of the source context. + /// + /// The source context to add the offset to. + /// The offset to add. + /// A new source context that reflects the addition of the offset to the context. + public static JavaScriptSourceContext Add(JavaScriptSourceContext left, int right) + { + return left + right; + } + + /// + /// Increments the value of the source context. + /// + /// The source context to increment. + /// A new source context that reflects the incrementing of the context. + public static JavaScriptSourceContext operator ++(JavaScriptSourceContext context) + { + return FromIntPtr(context.context + 1); + } + + /// + /// Increments the value of the source context. + /// + /// The source context to increment. + /// A new source context that reflects the incrementing of the context. + public static JavaScriptSourceContext Increment(JavaScriptSourceContext left) + { + return ++left; + } + + /// + /// Creates a new source context. + /// + /// + /// The cookie for the source context. + /// + /// The new source context. + public static JavaScriptSourceContext FromIntPtr(IntPtr cookie) + { + return new JavaScriptSourceContext(cookie); + } + + /// + /// Checks for equality between source contexts. + /// + /// The other source context to compare. + /// Whether the two source contexts are the same. + public bool Equals(JavaScriptSourceContext other) + { + return context == other.context; + } + + /// + /// Checks for equality between source contexts. + /// + /// The other source context to compare. + /// Whether the two source contexts are the same. + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) + { + return false; + } + + return obj is JavaScriptSourceContext && Equals((JavaScriptSourceContext)obj); + } + + /// + /// The hash code. + /// + /// The hash code of the source context. + public override int GetHashCode() + { + return context.ToInt32(); + } + } +} \ No newline at end of file diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptThreadServiceCallback.cs b/ReactWindows/ReactNative/Hosting/JavaScriptThreadServiceCallback.cs new file mode 100644 index 00000000000..b9a3a3e337b --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptThreadServiceCallback.cs @@ -0,0 +1,18 @@ +namespace ReactNative.Hosting +{ + using System; + + /// + /// A thread service callback. + /// + /// + /// The host can specify a background thread service when creating a runtime. If + /// specified, then background work items will be passed to the host using this callback. The + /// host is expected to either begin executing the background work item immediately and return + /// true or return false and the runtime will handle the work item in-thread. + /// + /// The callback for the background work item. + /// The data argument to be passed to the callback. + /// Whether the thread service will execute the callback. + public delegate bool JavaScriptThreadServiceCallback(JavaScriptBackgroundWorkItemCallback callbackFunction, IntPtr callbackData); +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptUsageException.cs b/ReactWindows/ReactNative/Hosting/JavaScriptUsageException.cs new file mode 100644 index 00000000000..73fd9cff602 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptUsageException.cs @@ -0,0 +1,30 @@ +namespace ReactNative.Hosting +{ + using System; + using System.Runtime.Serialization; + + /// + /// An API usage exception occurred. + /// + public sealed class JavaScriptUsageException : JavaScriptException + { + /// + /// Initializes a new instance of the class. + /// + /// The error code returned. + public JavaScriptUsageException(JavaScriptErrorCode code) : + this(code, "A fatal exception has occurred in a JavaScript runtime") + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The error code returned. + /// The error message. + public JavaScriptUsageException(JavaScriptErrorCode code, string message) : + base(code, message) + { + } + } +} \ No newline at end of file diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptValue.cs b/ReactWindows/ReactNative/Hosting/JavaScriptValue.cs new file mode 100644 index 00000000000..aca13680f35 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptValue.cs @@ -0,0 +1,886 @@ +namespace ReactNative.Hosting +{ + using System; + using System.Runtime.InteropServices; + + /// + /// A JavaScript value. + /// + /// + /// A JavaScript value is one of the following types of values: Undefined, Null, Boolean, + /// String, Number, or Object. + /// + public struct JavaScriptValue + { + /// + /// The reference. + /// + private readonly IntPtr reference; + + /// + /// Initializes a new instance of the struct. + /// + /// The reference. + private JavaScriptValue(IntPtr reference) + { + this.reference = reference; + } + + /// + /// Gets an invalid value. + /// + public static JavaScriptValue Invalid + { + get { return new JavaScriptValue(IntPtr.Zero); } + } + + /// + /// Gets the value of undefined in the current script context. + /// + /// + /// Requires an active script context. + /// + public static JavaScriptValue Undefined + { + get + { + JavaScriptValue value; + Native.ThrowIfError(Native.JsGetUndefinedValue(out value)); + return value; + } + } + + /// + /// Gets the value of null in the current script context. + /// + /// + /// Requires an active script context. + /// + public static JavaScriptValue Null + { + get + { + JavaScriptValue value; + Native.ThrowIfError(Native.JsGetNullValue(out value)); + return value; + } + } + + /// + /// Gets the value of true in the current script context. + /// + /// + /// Requires an active script context. + /// + public static JavaScriptValue True + { + get + { + JavaScriptValue value; + Native.ThrowIfError(Native.JsGetTrueValue(out value)); + return value; + } + } + + /// + /// Gets the value of false in the current script context. + /// + /// + /// Requires an active script context. + /// + public static JavaScriptValue False + { + get + { + JavaScriptValue value; + Native.ThrowIfError(Native.JsGetFalseValue(out value)); + return value; + } + } + + /// + /// Gets the global object in the current script context. + /// + /// + /// Requires an active script context. + /// + public static JavaScriptValue GlobalObject + { + get + { + JavaScriptValue value; + Native.ThrowIfError(Native.JsGetGlobalObject(out value)); + return value; + } + } + + /// + /// Gets a value indicating whether the value is valid. + /// + public bool IsValid + { + get { return reference != IntPtr.Zero; } + } + + /// + /// Gets the JavaScript type of the value. + /// + /// + /// Requires an active script context. + /// + /// The type of the value. + public JavaScriptValueType ValueType + { + get + { + JavaScriptValueType type; + Native.ThrowIfError(Native.JsGetValueType(this, out type)); + return type; + } + } + + /// + /// Gets the length of a String value. + /// + /// + /// Requires an active script context. + /// + /// The length of the string. + public int StringLength + { + get + { + int length; + Native.ThrowIfError(Native.JsGetStringLength(this, out length)); + return length; + } + } + + /// + /// Gets or sets the prototype of an object. + /// + /// + /// Requires an active script context. + /// + public JavaScriptValue Prototype + { + get + { + JavaScriptValue prototypeReference; + Native.ThrowIfError(Native.JsGetPrototype(this, out prototypeReference)); + return prototypeReference; + } + + set + { + Native.ThrowIfError(Native.JsSetPrototype(this, value)); + } + } + + /// + /// Gets a value indicating whether an object is extensible or not. + /// + /// + /// Requires an active script context. + /// + public bool IsExtensionAllowed + { + get + { + bool allowed; + Native.ThrowIfError(Native.JsGetExtensionAllowed(this, out allowed)); + return allowed; + } + } + + /// + /// Gets a value indicating whether an object is an external object. + /// + /// + /// Requires an active script context. + /// + public bool HasExternalData + { + get + { + bool hasExternalData; + Native.ThrowIfError(Native.JsHasExternalData(this, out hasExternalData)); + return hasExternalData; + } + } + + /// + /// Gets or sets the data in an external object. + /// + /// + /// Requires an active script context. + /// + public IntPtr ExternalData + { + get + { + IntPtr data; + Native.ThrowIfError(Native.JsGetExternalData(this, out data)); + return data; + } + + set + { + Native.ThrowIfError(Native.JsSetExternalData(this, value)); + } + } + + /// + /// Creates a Boolean value from a bool value. + /// + /// + /// Requires an active script context. + /// + /// The value to be converted. + /// The converted value. + public static JavaScriptValue FromBoolean(bool value) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsBoolToBoolean(value, out reference)); + return reference; + } + + /// + /// Creates a Number value from a double value. + /// + /// + /// Requires an active script context. + /// + /// The value to be converted. + /// The new Number value. + public static JavaScriptValue FromDouble(double value) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsDoubleToNumber(value, out reference)); + return reference; + } + + /// + /// Creates a Number value from a int value. + /// + /// + /// Requires an active script context. + /// + /// The value to be converted. + /// The new Number value. + public static JavaScriptValue FromInt32(int value) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsIntToNumber(value, out reference)); + return reference; + } + + /// + /// Creates a String value from a string pointer. + /// + /// + /// Requires an active script context. + /// + /// The string to convert to a String value. + /// The new String value. + public static JavaScriptValue FromString(string value) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsPointerToString(value, new UIntPtr((uint)value.Length), out reference)); + return reference; + } + + /// + /// Creates a JavaScript value that is a projection of the passed in object. + /// + /// + /// Requires an active script context. + /// + /// An object to be projected. + /// A JavaScript value that is a projection of the object. + public static JavaScriptValue FromObject(object value) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsVariantToValue(ref value, out reference)); + return reference; + } + + /// + /// Creates a new Object. + /// + /// + /// Requires an active script context. + /// + /// The new Object. + public static JavaScriptValue CreateObject() + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsCreateObject(out reference)); + return reference; + } + + /// + /// Creates a new Object that stores some external data. + /// + /// + /// Requires an active script context. + /// + /// External data that the object will represent. May be null. + /// + /// A callback for when the object is finalized. May be null. + /// + /// The new Object. + public static JavaScriptValue CreateExternalObject(IntPtr data, JavaScriptObjectFinalizeCallback finalizer) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsCreateExternalObject(data, finalizer, out reference)); + return reference; + } + + /// + /// Creates a new JavaScript function. + /// + /// + /// Requires an active script context. + /// + /// The method to call when the function is invoked. + /// The new function object. + public static JavaScriptValue CreateFunction(JavaScriptNativeFunction function) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsCreateFunction(function, IntPtr.Zero, out reference)); + return reference; + } + + /// + /// Creates a new JavaScript function. + /// + /// + /// Requires an active script context. + /// + /// The method to call when the function is invoked. + /// Data to be provided to all function callbacks. + /// The new function object. + public static JavaScriptValue CreateFunction(JavaScriptNativeFunction function, IntPtr callbackData) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsCreateFunction(function, callbackData, out reference)); + return reference; + } + + /// + /// Creates a JavaScript array object. + /// + /// + /// Requires an active script context. + /// + /// The initial length of the array. + /// The new array object. + public static JavaScriptValue CreateArray(uint length) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsCreateArray(length, out reference)); + return reference; + } + + /// + /// Creates a new JavaScript error object + /// + /// + /// Requires an active script context. + /// + /// Message for the error object. + /// The new error object. + public static JavaScriptValue CreateError(JavaScriptValue message) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsCreateError(message, out reference)); + return reference; + } + + /// + /// Creates a new JavaScript RangeError error object + /// + /// + /// Requires an active script context. + /// + /// Message for the error object. + /// The new error object. + public static JavaScriptValue CreateRangeError(JavaScriptValue message) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsCreateRangeError(message, out reference)); + return reference; + } + + /// + /// Creates a new JavaScript ReferenceError error object + /// + /// + /// Requires an active script context. + /// + /// Message for the error object. + /// The new error object. + public static JavaScriptValue CreateReferenceError(JavaScriptValue message) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsCreateReferenceError(message, out reference)); + return reference; + } + + /// + /// Creates a new JavaScript SyntaxError error object + /// + /// + /// Requires an active script context. + /// + /// Message for the error object. + /// The new error object. + public static JavaScriptValue CreateSyntaxError(JavaScriptValue message) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsCreateSyntaxError(message, out reference)); + return reference; + } + + /// + /// Creates a new JavaScript TypeError error object + /// + /// + /// Requires an active script context. + /// + /// Message for the error object. + /// The new error object. + public static JavaScriptValue CreateTypeError(JavaScriptValue message) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsCreateTypeError(message, out reference)); + return reference; + } + + /// + /// Creates a new JavaScript URIError error object + /// + /// + /// Requires an active script context. + /// + /// Message for the error object. + /// The new error object. + public static JavaScriptValue CreateUriError(JavaScriptValue message) + { + JavaScriptValue reference; + Native.ThrowIfError(Native.JsCreateURIError(message, out reference)); + return reference; + } + + /// + /// Adds a reference to the object. + /// + /// + /// This only needs to be called on objects that are not going to be stored somewhere on + /// the stack. Calling AddRef ensures that the JavaScript object the value refers to will not be freed + /// until Release is called + /// + /// The object's new reference count. + public uint AddRef() + { + uint count; + Native.ThrowIfError(Native.JsAddRef(this, out count)); + return count; + } + + /// + /// Releases a reference to the object. + /// + /// + /// Removes a reference that was created by AddRef. + /// + /// The object's new reference count. + public uint Release() + { + uint count; + Native.ThrowIfError(Native.JsRelease(this, out count)); + return count; + } + + /// + /// Retrieves the bool value of a Boolean value. + /// + /// + /// Requires an active script context. + /// + /// The converted value. + public bool ToBoolean() + { + bool value; + Native.ThrowIfError(Native.JsBooleanToBool(this, out value)); + return value; + } + + /// + /// Retrieves the double value of a Number value. + /// + /// + /// + /// This function retrieves the value of a Number value. It will fail with + /// InvalidArgument if the type of the value is not Number. + /// + /// + /// Requires an active script context. + /// + /// + /// The double value. + public double ToDouble() + { + double value; + Native.ThrowIfError(Native.JsNumberToDouble(this, out value)); + return value; + } + + /// + /// Retrieves the string pointer of a String value. + /// + /// + /// + /// This function retrieves the string pointer of a String value. It will fail with + /// InvalidArgument if the type of the value is not String. + /// + /// + /// Requires an active script context. + /// + /// + /// The string. + public new string ToString() + { + IntPtr buffer; + UIntPtr length; + Native.ThrowIfError(Native.JsStringToPointer(this, out buffer, out length)); + + //return Marshal.PtrToStringAuto(buffer, (int)length); + return /* TODO */ null; + } + + /// + /// Retrieves the object representation of an Object value. + /// + /// + /// Requires an active script context. + /// + /// The object representation of the value. + public object ToObject() + { + object value; + Native.ThrowIfError(Native.JsValueToVariant(this, out value)); + return value; + } + + /// + /// Converts the value to Boolean using regular JavaScript semantics. + /// + /// + /// Requires an active script context. + /// + /// The converted value. + public JavaScriptValue ConvertToBoolean() + { + JavaScriptValue booleanReference; + Native.ThrowIfError(Native.JsConvertValueToBoolean(this, out booleanReference)); + return booleanReference; + } + + /// + /// Converts the value to Number using regular JavaScript semantics. + /// + /// + /// Requires an active script context. + /// + /// The converted value. + public JavaScriptValue ConvertToNumber() + { + JavaScriptValue numberReference; + Native.ThrowIfError(Native.JsConvertValueToNumber(this, out numberReference)); + return numberReference; + } + + /// + /// Converts the value to String using regular JavaScript semantics. + /// + /// + /// Requires an active script context. + /// + /// The converted value. + public JavaScriptValue ConvertToString() + { + JavaScriptValue stringReference; + Native.ThrowIfError(Native.JsConvertValueToString(this, out stringReference)); + return stringReference; + } + + /// + /// Converts the value to Object using regular JavaScript semantics. + /// + /// + /// Requires an active script context. + /// + /// The converted value. + public JavaScriptValue ConvertToObject() + { + JavaScriptValue objectReference; + Native.ThrowIfError(Native.JsConvertValueToObject(this, out objectReference)); + return objectReference; + } + + /// + /// Sets an object to not be extensible. + /// + /// + /// Requires an active script context. + /// + public void PreventExtension() + { + Native.ThrowIfError(Native.JsPreventExtension(this)); + } + + /// + /// Gets a property descriptor for an object's own property. + /// + /// + /// Requires an active script context. + /// + /// The ID of the property. + /// The property descriptor. + public JavaScriptValue GetOwnPropertyDescriptor(JavaScriptPropertyId propertyId) + { + JavaScriptValue descriptorReference; + Native.ThrowIfError(Native.JsGetOwnPropertyDescriptor(this, propertyId, out descriptorReference)); + return descriptorReference; + } + + /// + /// Gets the list of all properties on the object. + /// + /// + /// Requires an active script context. + /// + /// An array of property names. + public JavaScriptValue GetOwnPropertyNames() + { + JavaScriptValue propertyNamesReference; + Native.ThrowIfError(Native.JsGetOwnPropertyNames(this, out propertyNamesReference)); + return propertyNamesReference; + } + + /// + /// Determines whether an object has a property. + /// + /// + /// Requires an active script context. + /// + /// The ID of the property. + /// Whether the object (or a prototype) has the property. + public bool HasProperty(JavaScriptPropertyId propertyId) + { + bool hasProperty; + Native.ThrowIfError(Native.JsHasProperty(this, propertyId, out hasProperty)); + return hasProperty; + } + + /// + /// Gets an object's property. + /// + /// + /// Requires an active script context. + /// + /// The ID of the property. + /// The value of the property. + public JavaScriptValue GetProperty(JavaScriptPropertyId id) + { + JavaScriptValue propertyReference; + Native.ThrowIfError(Native.JsGetProperty(this, id, out propertyReference)); + return propertyReference; + } + + /// + /// Sets an object's property. + /// + /// + /// Requires an active script context. + /// + /// The ID of the property. + /// The new value of the property. + /// The property set should follow strict mode rules. + public void SetProperty(JavaScriptPropertyId id, JavaScriptValue value, bool useStrictRules) + { + Native.ThrowIfError(Native.JsSetProperty(this, id, value, useStrictRules)); + } + + /// + /// Deletes an object's property. + /// + /// + /// Requires an active script context. + /// + /// The ID of the property. + /// The property set should follow strict mode rules. + /// Whether the property was deleted. + public JavaScriptValue DeleteProperty(JavaScriptPropertyId propertyId, bool useStrictRules) + { + JavaScriptValue returnReference; + Native.ThrowIfError(Native.JsDeleteProperty(this, propertyId, useStrictRules, out returnReference)); + return returnReference; + } + + /// + /// Defines a new object's own property from a property descriptor. + /// + /// + /// Requires an active script context. + /// + /// The ID of the property. + /// The property descriptor. + /// Whether the property was defined. + public bool DefineProperty(JavaScriptPropertyId propertyId, JavaScriptValue propertyDescriptor) + { + bool result; + Native.ThrowIfError(Native.JsDefineProperty(this, propertyId, propertyDescriptor, out result)); + return result; + } + + /// + /// Test if an object has a value at the specified index. + /// + /// + /// Requires an active script context. + /// + /// The index to test. + /// Whether the object has an value at the specified index. + public bool HasIndexedProperty(JavaScriptValue index) + { + bool hasProperty; + Native.ThrowIfError(Native.JsHasIndexedProperty(this, index, out hasProperty)); + return hasProperty; + } + + /// + /// Retrieve the value at the specified index of an object. + /// + /// + /// Requires an active script context. + /// + /// The index to retrieve. + /// The retrieved value. + public JavaScriptValue GetIndexedProperty(JavaScriptValue index) + { + JavaScriptValue propertyReference; + Native.ThrowIfError(Native.JsGetIndexedProperty(this, index, out propertyReference)); + return propertyReference; + } + + /// + /// Set the value at the specified index of an object. + /// + /// + /// Requires an active script context. + /// + /// The index to set. + /// The value to set. + public void SetIndexedProperty(JavaScriptValue index, JavaScriptValue value) + { + Native.ThrowIfError(Native.JsSetIndexedProperty(this, index, value)); + } + + /// + /// Delete the value at the specified index of an object. + /// + /// + /// Requires an active script context. + /// + /// The index to delete. + public void DeleteIndexedProperty(JavaScriptValue index) + { + Native.ThrowIfError(Native.JsDeleteIndexedProperty(this, index)); + } + + /// + /// Compare two JavaScript values for equality. + /// + /// + /// + /// This function is equivalent to the "==" operator in JavaScript. + /// + /// + /// Requires an active script context. + /// + /// + /// The object to compare. + /// Whether the values are equal. + public bool Equals(JavaScriptValue other) + { + bool equals; + Native.ThrowIfError(Native.JsEquals(this, other, out equals)); + return equals; + } + + /// + /// Compare two JavaScript values for strict equality. + /// + /// + /// + /// This function is equivalent to the "===" operator in JavaScript. + /// + /// + /// Requires an active script context. + /// + /// + /// The object to compare. + /// Whether the values are strictly equal. + public bool StrictEquals(JavaScriptValue other) + { + bool equals; + Native.ThrowIfError(Native.JsStrictEquals(this, other, out equals)); + return equals; + } + + /// + /// Invokes a function. + /// + /// + /// Requires an active script context. + /// + /// The arguments to the call. + /// The Value returned from the function invocation, if any. + public JavaScriptValue CallFunction(params JavaScriptValue[] arguments) + { + JavaScriptValue returnReference; + + if (arguments.Length > ushort.MaxValue) + { + throw new ArgumentOutOfRangeException("arguments"); + } + + Native.ThrowIfError(Native.JsCallFunction(this, arguments, (ushort)arguments.Length, out returnReference)); + return returnReference; + } + + /// + /// Invokes a function as a constructor. + /// + /// + /// Requires an active script context. + /// + /// The arguments to the call. + /// The Value returned from the function invocation. + public JavaScriptValue ConstructObject(params JavaScriptValue[] arguments) + { + JavaScriptValue returnReference; + + if (arguments.Length > ushort.MaxValue) + { + throw new ArgumentOutOfRangeException("arguments"); + } + + Native.ThrowIfError(Native.JsConstructObject(this, arguments, (ushort)arguments.Length, out returnReference)); + return returnReference; + } + } +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptValueType.cs b/ReactWindows/ReactNative/Hosting/JavaScriptValueType.cs new file mode 100644 index 00000000000..3014e80ad28 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/JavaScriptValueType.cs @@ -0,0 +1,53 @@ +namespace ReactNative.Hosting +{ + /// + /// The JavaScript type of a JavaScriptValue. + /// + public enum JavaScriptValueType + { + /// + /// The value is the undefined value. + /// + Undefined = 0, + + /// + /// The value is the null value. + /// + Null = 1, + + /// + /// The value is a JavaScript number value. + /// + Number = 2, + + /// + /// The value is a JavaScript string value. + /// + String = 3, + + /// + /// The value is a JavaScript Boolean value. + /// + Boolean = 4, + + /// + /// The value is a JavaScript object value. + /// + Object = 5, + + /// + /// The value is a JavaScript function object value. + /// + Function = 6, + + /// + /// The value is a JavaScript error object value. + /// + Error = 7, + + /// + /// The value is a JavaScript array object value. + /// + Array = 8, + } +} \ No newline at end of file diff --git a/ReactWindows/ReactNative/Hosting/Native.cs b/ReactWindows/ReactNative/Hosting/Native.cs new file mode 100644 index 00000000000..e52a3d7fbfe --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/Native.cs @@ -0,0 +1,647 @@ +namespace ReactNative.Hosting +{ + using System; + using System.Diagnostics.CodeAnalysis; + using System.Runtime.InteropServices; + + /// + /// Native interfaces. + /// + public static class Native + { + /// + /// Event mask for profiling. + /// + public enum ProfilerEventMask + { + /// + /// Trace calls to script functions. + /// + TraceScriptFunctionCall = 0x1, + + /// + /// Trace calls to built-in functions. + /// + TraceNativeFunctionCall = 0x2, + + /// + /// Trace calls to DOM methods. + /// + TraceDomFunctionCall = 0x4, + + /// + /// Trace all calls except DOM methods. + /// + TraceAll = (TraceScriptFunctionCall | TraceNativeFunctionCall), + + /// + /// Trace all calls. + /// + TraceAllWithDom = (TraceAll | TraceDomFunctionCall) + } + + /// + /// Profiled script type. + /// + public enum ProfilerScriptType + { + /// + /// A user script. + /// + User, + + /// + /// A dynamic script. + /// + Dynamic, + + /// + /// A native script. + /// + Native, + + /// + /// A DOM-related script. + /// + Dom + } + + /// + /// IProcessDebugManager32 COM interface. + /// + [Guid("51973C2f-CB0C-11d0-B5C9-00A0244A0E7A")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IProcessDebugManager32 + { + /// + /// Creates a new debug application. + /// + /// The new debug application. + void CreateApplication(out IDebugApplication32 debugApplication); + + /// + /// Gets the default debug application. + /// + /// The default debug application. + void GetDefaultApplication(out IDebugApplication32 debugApplication); + + /// + /// Adds a new debug application. + /// + /// The new debug application. + /// An engine-defined cookie. + void AddApplication(IDebugApplication32 debugApplication, out uint cookie); + + /// + /// Removes a debug application. + /// + /// The cookie of the debug application to remove. + void RemoveApplication(uint cookie); + + /// + /// Creates a debug document helper. + /// + /// The outer unknown. + /// The new debug document helper. + void CreateDebugDocumentHelper(object outerUnknown, out IDebugDocumentHelper32 helper); + } + + /// + /// IProcessDebugManager64 COM interface. + /// + [Guid("56b9fC1C-63A9-4CC1-AC21-087D69A17FAB")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IProcessDebugManager64 + { + /// + /// Creates a new debug application. + /// + /// The new debug application. + void CreateApplication(out IDebugApplication64 debugApplication); + + /// + /// Gets the default debug application. + /// + /// The default debug application. + void GetDefaultApplication(out IDebugApplication64 debugApplication); + + /// + /// Adds a new debug application. + /// + /// The new debug application. + /// An engine-defined cookie. + void AddApplication(IDebugApplication64 debugApplication, out uint cookie); + + /// + /// Removes a debug application. + /// + /// The cookie of the debug application to remove. + void RemoveApplication(uint cookie); + + /// + /// Creates a debug document helper. + /// + /// The outer unknown. + /// The new debug document helper. + void CreateDebugDocumentHelper(object outerUnknown, out IDebugDocumentHelper64 helper); + } + + /// + /// IDebugApplication32 COM interface. + /// + [Guid("51973C32-CB0C-11d0-B5C9-00A0244A0E7A")] + public interface IDebugApplication32 + { + } + + /// + /// IDebugApplication64 COM interface. + /// + [Guid("4dedc754-04c7-4f10-9e60-16a390fe6e62")] + public interface IDebugApplication64 + { + } + + /// + /// IDebugDocumentHelper32 COM interface. + /// + [Guid("51973C26-CB0C-11d0-B5C9-00A0244A0E7A")] + public interface IDebugDocumentHelper32 + { + } + + /// + /// IDebugDocumentHelper64 COM interface. + /// + [Guid("c4c7363c-20fd-47f9-bd82-4855e0150871")] + public interface IDebugDocumentHelper64 + { + } + + /// + /// IActiveScriptProfilerCallback COM interface. + /// + [Guid("740eca23-7d9d-42e5-ba9d-f8b24b1c7a9b")] + [InterfaceType(ComInterfaceType.InterfaceIsIUnknown)] + public interface IActiveScriptProfilerCallback + { + /// + /// Called when the profile is started. + /// + /// The context provided when profiling was started. + void Initialize(uint context); + + /// + /// Called when profiling is stopped. + /// + /// The reason code provided when profiling was stopped. + void Shutdown(uint reason); + + /// + /// Called when a script is compiled. + /// + /// The ID of the script. + /// The type of the script. + /// The debug document context, if any. + void ScriptCompiled(int scriptId, ProfilerScriptType type, IntPtr debugDocumentContext); + + /// + /// Called when a function is compiled. + /// + /// The ID of the function. + /// The ID of the script. + /// The name of the function. + /// The function name hint. + /// The debug document context, if any. + void FunctionCompiled(int functionId, int scriptId, [MarshalAs(UnmanagedType.LPWStr)] string functionName, [MarshalAs(UnmanagedType.LPWStr)] string functionNameHint, IntPtr debugDocumentContext); + + /// + /// Called when a function is entered. + /// + /// The ID of the script. + /// The ID of the function. + void OnFunctionEnter(int scriptId, int functionId); + + /// + /// Called when a function is exited. + /// + /// The ID of the script. + /// The ID of the function. + void OnFunctionExit(int scriptId, int functionId); + } + + /// + /// IActiveScriptProfilerCallback2 COM interface. + /// + [Guid("31B7F8AD-A637-409C-B22F-040995B6103D")] + public interface IActiveScriptProfilerCallback2 : IActiveScriptProfilerCallback + { + /// + /// Called when a function is entered by name. + /// + /// The name of the function. + /// The type of the function. + void OnFunctionEnterByName(string functionName, ProfilerScriptType type); + + /// + /// Called when a function is exited by name. + /// + /// The name of the function. + /// The type of the function. + void OnFunctionExitByName(string functionName, ProfilerScriptType type); + } + + /// + /// IActiveScriptProfilerHeapEnum COM interface. + /// + [SuppressMessage("StyleCop.CSharp.DocumentationRules", "SA1650:ElementDocumentationMustBeSpelledCorrectly", Justification = "Name defined in COM.")] + [Guid("32E4694E-0D37-419B-B93D-FA20DED6E8EA")] + public interface IActiveScriptProfilerHeapEnum + { + } + + /// + /// Throws if a native method returns an error code. + /// + /// The error. + internal static void ThrowIfError(JavaScriptErrorCode error) + { + if (error != JavaScriptErrorCode.NoError) + { + switch (error) + { + case JavaScriptErrorCode.InvalidArgument: + throw new JavaScriptUsageException(error, "Invalid argument."); + + case JavaScriptErrorCode.NullArgument: + throw new JavaScriptUsageException(error, "Null argument."); + + case JavaScriptErrorCode.NoCurrentContext: + throw new JavaScriptUsageException(error, "No current context."); + + case JavaScriptErrorCode.InExceptionState: + throw new JavaScriptUsageException(error, "Runtime is in exception state."); + + case JavaScriptErrorCode.NotImplemented: + throw new JavaScriptUsageException(error, "Method is not implemented."); + + case JavaScriptErrorCode.WrongThread: + throw new JavaScriptUsageException(error, "Runtime is active on another thread."); + + case JavaScriptErrorCode.RuntimeInUse: + throw new JavaScriptUsageException(error, "Runtime is in use."); + + case JavaScriptErrorCode.BadSerializedScript: + throw new JavaScriptUsageException(error, "Bad serialized script."); + + case JavaScriptErrorCode.InDisabledState: + throw new JavaScriptUsageException(error, "Runtime is disabled."); + + case JavaScriptErrorCode.CannotDisableExecution: + throw new JavaScriptUsageException(error, "Cannot disable execution."); + + case JavaScriptErrorCode.AlreadyDebuggingContext: + throw new JavaScriptUsageException(error, "Context is already in debug mode."); + + case JavaScriptErrorCode.HeapEnumInProgress: + throw new JavaScriptUsageException(error, "Heap enumeration is in progress."); + + case JavaScriptErrorCode.ArgumentNotObject: + throw new JavaScriptUsageException(error, "Argument is not an object."); + + case JavaScriptErrorCode.InProfileCallback: + throw new JavaScriptUsageException(error, "In a profile callback."); + + case JavaScriptErrorCode.InThreadServiceCallback: + throw new JavaScriptUsageException(error, "In a thread service callback."); + + case JavaScriptErrorCode.CannotSerializeDebugScript: + throw new JavaScriptUsageException(error, "Cannot serialize a debug script."); + + case JavaScriptErrorCode.AlreadyProfilingContext: + throw new JavaScriptUsageException(error, "Already profiling this context."); + + case JavaScriptErrorCode.IdleNotEnabled: + throw new JavaScriptUsageException(error, "Idle is not enabled."); + + case JavaScriptErrorCode.OutOfMemory: + throw new JavaScriptEngineException(error, "Out of memory."); + + case JavaScriptErrorCode.ScriptException: + { + JavaScriptValue errorObject; + JavaScriptErrorCode innerError = JsGetAndClearException(out errorObject); + + if (innerError != JavaScriptErrorCode.NoError) + { + throw new JavaScriptFatalException(innerError); + } + + throw new JavaScriptScriptException(error, errorObject, "Script threw an exception."); + } + + case JavaScriptErrorCode.ScriptCompile: + { + JavaScriptValue errorObject; + JavaScriptErrorCode innerError = JsGetAndClearException(out errorObject); + + if (innerError != JavaScriptErrorCode.NoError) + { + throw new JavaScriptFatalException(innerError); + } + + throw new JavaScriptScriptException(error, errorObject, "Compile error."); + } + + case JavaScriptErrorCode.ScriptTerminated: + throw new JavaScriptScriptException(error, JavaScriptValue.Invalid, "Script was terminated."); + + case JavaScriptErrorCode.ScriptEvalDisabled: + throw new JavaScriptScriptException(error, JavaScriptValue.Invalid, "Eval of strings is disabled in this runtime."); + + case JavaScriptErrorCode.Fatal: + throw new JavaScriptFatalException(error); + + default: + throw new JavaScriptFatalException(error); + } + } + } + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCreateRuntime(JavaScriptRuntimeAttributes attributes, JavaScriptRuntimeVersion runtimeVersion, JavaScriptThreadServiceCallback threadService, out JavaScriptRuntime runtime); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCollectGarbage(JavaScriptRuntime handle); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsDisposeRuntime(JavaScriptRuntime handle); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetRuntimeMemoryUsage(JavaScriptRuntime runtime, out UIntPtr memoryUsage); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetRuntimeMemoryLimit(JavaScriptRuntime runtime, out UIntPtr memoryLimit); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsSetRuntimeMemoryLimit(JavaScriptRuntime runtime, UIntPtr memoryLimit); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsSetRuntimeMemoryAllocationCallback(JavaScriptRuntime runtime, IntPtr callbackState, JavaScriptMemoryAllocationCallback allocationCallback); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsSetRuntimeBeforeCollectCallback(JavaScriptRuntime runtime, IntPtr callbackState, JavaScriptBeforeCollectCallback beforeCollectCallback); + + [DllImport("chakra.dll", EntryPoint = "JsAddRef")] + internal static extern JavaScriptErrorCode JsContextAddRef(JavaScriptContext reference, out uint count); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsAddRef(JavaScriptValue reference, out uint count); + + [DllImport("chakra.dll", EntryPoint = "JsRelease")] + internal static extern JavaScriptErrorCode JsContextRelease(JavaScriptContext reference, out uint count); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsRelease(JavaScriptValue reference, out uint count); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCreateContext(JavaScriptRuntime runtime, IDebugApplication64 debugSite, out JavaScriptContext newContext); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCreateContext(JavaScriptRuntime runtime, IDebugApplication32 debugSite, out JavaScriptContext newContext); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetCurrentContext(out JavaScriptContext currentContext); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsSetCurrentContext(JavaScriptContext context); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetRuntime(JavaScriptContext context, out JavaScriptRuntime runtime); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsStartDebugging(IDebugApplication64 debugApplication); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsStartDebugging(IDebugApplication32 debugApplication); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsIdle(out uint nextIdleTick); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsParseScript(string script, JavaScriptSourceContext sourceContext, string sourceUrl, out JavaScriptValue result); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsRunScript(string script, JavaScriptSourceContext sourceContext, string sourceUrl, out JavaScriptValue result); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsSerializeScript(string script, byte[] buffer, ref ulong bufferSize); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsParseSerializedScript(string script, byte[] buffer, JavaScriptSourceContext sourceContext, string sourceUrl, out JavaScriptValue result); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsRunSerializedScript(string script, byte[] buffer, JavaScriptSourceContext sourceContext, string sourceUrl, out JavaScriptValue result); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetPropertyIdFromName(string name, out JavaScriptPropertyId propertyId); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetPropertyNameFromId(JavaScriptPropertyId propertyId, out string name); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetUndefinedValue(out JavaScriptValue undefinedValue); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetNullValue(out JavaScriptValue nullValue); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetTrueValue(out JavaScriptValue trueValue); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetFalseValue(out JavaScriptValue falseValue); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsBoolToBoolean(bool value, out JavaScriptValue booleanValue); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsBooleanToBool(JavaScriptValue booleanValue, out bool boolValue); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsConvertValueToBoolean(JavaScriptValue value, out JavaScriptValue booleanValue); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetValueType(JavaScriptValue value, out JavaScriptValueType type); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsDoubleToNumber(double doubleValue, out JavaScriptValue value); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsIntToNumber(int intValue, out JavaScriptValue value); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsNumberToDouble(JavaScriptValue value, out double doubleValue); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsConvertValueToNumber(JavaScriptValue value, out JavaScriptValue numberValue); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetStringLength(JavaScriptValue sringValue, out int length); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsPointerToString(string value, UIntPtr stringLength, out JavaScriptValue stringValue); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsStringToPointer(JavaScriptValue value, out IntPtr stringValue, out UIntPtr stringLength); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsConvertValueToString(JavaScriptValue value, out JavaScriptValue stringValue); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsVariantToValue(ref object var, out JavaScriptValue value); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsValueToVariant(JavaScriptValue obj, out object var); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetGlobalObject(out JavaScriptValue globalObject); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCreateObject(out JavaScriptValue obj); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCreateExternalObject(IntPtr data, JavaScriptObjectFinalizeCallback finalizeCallback, out JavaScriptValue obj); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsConvertValueToObject(JavaScriptValue value, out JavaScriptValue obj); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetPrototype(JavaScriptValue obj, out JavaScriptValue prototypeObject); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsSetPrototype(JavaScriptValue obj, JavaScriptValue prototypeObject); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetExtensionAllowed(JavaScriptValue obj, out bool value); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsPreventExtension(JavaScriptValue obj); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetProperty(JavaScriptValue obj, JavaScriptPropertyId propertyId, out JavaScriptValue value); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetOwnPropertyDescriptor(JavaScriptValue obj, JavaScriptPropertyId propertyId, out JavaScriptValue propertyDescriptor); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetOwnPropertyNames(JavaScriptValue obj, out JavaScriptValue propertyNames); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsSetProperty(JavaScriptValue obj, JavaScriptPropertyId propertyId, JavaScriptValue value, bool useStrictRules); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsHasProperty(JavaScriptValue obj, JavaScriptPropertyId propertyId, out bool hasProperty); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsDeleteProperty(JavaScriptValue obj, JavaScriptPropertyId propertyId, bool useStrictRules, out JavaScriptValue result); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsDefineProperty(JavaScriptValue obj, JavaScriptPropertyId propertyId, JavaScriptValue propertyDescriptor, out bool result); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsHasIndexedProperty(JavaScriptValue obj, JavaScriptValue index, out bool result); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetIndexedProperty(JavaScriptValue obj, JavaScriptValue index, out JavaScriptValue result); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsSetIndexedProperty(JavaScriptValue obj, JavaScriptValue index, JavaScriptValue value); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsDeleteIndexedProperty(JavaScriptValue obj, JavaScriptValue index); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsEquals(JavaScriptValue obj1, JavaScriptValue obj2, out bool result); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsStrictEquals(JavaScriptValue obj1, JavaScriptValue obj2, out bool result); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsHasExternalData(JavaScriptValue obj, out bool value); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetExternalData(JavaScriptValue obj, out IntPtr externalData); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsSetExternalData(JavaScriptValue obj, IntPtr externalData); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCreateArray(uint length, out JavaScriptValue result); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCallFunction(JavaScriptValue function, JavaScriptValue[] arguments, ushort argumentCount, out JavaScriptValue result); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsConstructObject(JavaScriptValue function, JavaScriptValue[] arguments, ushort argumentCount, out JavaScriptValue result); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCreateFunction(JavaScriptNativeFunction nativeFunction, IntPtr externalData, out JavaScriptValue function); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCreateError(JavaScriptValue message, out JavaScriptValue error); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCreateRangeError(JavaScriptValue message, out JavaScriptValue error); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCreateReferenceError(JavaScriptValue message, out JavaScriptValue error); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCreateSyntaxError(JavaScriptValue message, out JavaScriptValue error); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCreateTypeError(JavaScriptValue message, out JavaScriptValue error); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsCreateURIError(JavaScriptValue message, out JavaScriptValue error); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsHasException(out bool hasException); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsGetAndClearException(out JavaScriptValue exception); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsSetException(JavaScriptValue exception); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsDisableRuntimeExecution(JavaScriptRuntime runtime); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsEnableRuntimeExecution(JavaScriptRuntime runtime); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsIsRuntimeExecutionDisabled(JavaScriptRuntime runtime, out bool isDisabled); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsStartProfiling(IActiveScriptProfilerCallback callback, ProfilerEventMask eventMask, int context); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsStopProfiling(int reason); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsEnumerateHeap(out IActiveScriptProfilerHeapEnum enumerator); + + [DllImport("chakra.dll")] + internal static extern JavaScriptErrorCode JsIsEnumeratingHeap(out bool isEnumeratingHeap); + + /// + /// ProcessDebugManager COM interface. + /// + [ComImport] + [Guid("78A51822-51F4-11D0-8F20-00805F2CD064")] + public class ProcessDebugManager + { + } + } +} diff --git a/ReactWindows/ReactNative/IReactPackage.cs b/ReactWindows/ReactNative/IReactPackage.cs new file mode 100644 index 00000000000..d025fbbdcc4 --- /dev/null +++ b/ReactWindows/ReactNative/IReactPackage.cs @@ -0,0 +1,11 @@ +using System.Collections.Generic; + +namespace ReactNative.Bridge +{ + interface IReactPackage + { + IList CreateNativeModules(ReactApplicationContext context); + + IList CreateViewManagers(ReactApplicationContext context); + } +} diff --git a/ReactWindows/ReactNative/IViewManager.cs b/ReactWindows/ReactNative/IViewManager.cs new file mode 100644 index 00000000000..d07396ab6aa --- /dev/null +++ b/ReactWindows/ReactNative/IViewManager.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReactNative +{ + class IViewManager + { + } +} diff --git a/ReactWindows/ReactNative/Imports.cs b/ReactWindows/ReactNative/Imports.cs new file mode 100644 index 00000000000..09cde5e3043 --- /dev/null +++ b/ReactWindows/ReactNative/Imports.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json.Linq; + +namespace ReactNative +{ + public static class Imports + { + public static JObject Instance { get; } = JObject.Parse( + @"{ + AppRegistry: { + methods: [ + 'runApplication' + ] + }, + RCTDeviceEventEmitter: { + methods: [ + 'emit' + ] + }, + RCTEventEmitter: { + methods: [ + 'receiveEvent' + ] + }, + JSTimersExecution: { + methods: [ + 'callTimers' + ] + } + }"); + } +} diff --git a/ReactWindows/ReactNative/Properties/AssemblyInfo.cs b/ReactWindows/ReactNative/Properties/AssemblyInfo.cs new file mode 100644 index 00000000000..1d25425b68b --- /dev/null +++ b/ReactWindows/ReactNative/Properties/AssemblyInfo.cs @@ -0,0 +1,29 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("ReactNative")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("ReactNative")] +[assembly: AssemblyCopyright("Copyright © 2015")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] +[assembly: ComVisible(false)] \ No newline at end of file diff --git a/ReactWindows/ReactNative/Properties/ReactNative.rd.xml b/ReactWindows/ReactNative/Properties/ReactNative.rd.xml new file mode 100644 index 00000000000..f4bbd23eceb --- /dev/null +++ b/ReactWindows/ReactNative/Properties/ReactNative.rd.xml @@ -0,0 +1,33 @@ + + + + + + + + + diff --git a/ReactWindows/ReactNative/ReactInstanceManager.cs b/ReactWindows/ReactNative/ReactInstanceManager.cs new file mode 100644 index 00000000000..5a4eae1b751 --- /dev/null +++ b/ReactWindows/ReactNative/ReactInstanceManager.cs @@ -0,0 +1,16 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReactNative +{ + public abstract class ReactInstanceManager + { + public sealed class Builder + { + + } + } +} diff --git a/ReactWindows/ReactNative/ReactMethodAttribute.cs b/ReactWindows/ReactNative/ReactMethodAttribute.cs new file mode 100644 index 00000000000..33feea83084 --- /dev/null +++ b/ReactWindows/ReactNative/ReactMethodAttribute.cs @@ -0,0 +1,9 @@ +using System; + +namespace ReactNative +{ + [AttributeUsage(AttributeTargets.Method)] + public sealed class ReactMethodAttribute : Attribute + { + } +} diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj new file mode 100644 index 00000000000..d883000fa1c --- /dev/null +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -0,0 +1,164 @@ + + + + + Debug + AnyCPU + {C7673AD5-E3AA-468C-A5FD-FA38154E205C} + Library + Properties + ReactNative + ReactNative + en-US + UAP + 10.0.10586.0 + 10.0.10240.0 + 14 + 512 + {A5A43C5B-DE2A-4C0C-9213-0A381AF9435A};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} + + + AnyCPU + true + full + false + bin\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + prompt + 4 + + + AnyCPU + pdbonly + true + bin\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + prompt + 4 + + + x86 + true + bin\x86\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x86 + false + prompt + + + x86 + bin\x86\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x86 + false + prompt + + + ARM + true + bin\ARM\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + ARM + false + prompt + + + ARM + bin\ARM\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + ARM + false + prompt + + + x64 + true + bin\x64\Debug\ + DEBUG;TRACE;NETFX_CORE;WINDOWS_UWP + ;2008 + full + x64 + false + prompt + + + x64 + bin\x64\Release\ + TRACE;NETFX_CORE;WINDOWS_UWP + true + ;2008 + pdbonly + x64 + false + prompt + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 14.0 + + + + \ No newline at end of file diff --git a/ReactWindows/ReactNative/Reflection/MethodInfoHelpers.cs b/ReactWindows/ReactNative/Reflection/MethodInfoHelpers.cs new file mode 100644 index 00000000000..f372d4ea101 --- /dev/null +++ b/ReactWindows/ReactNative/Reflection/MethodInfoHelpers.cs @@ -0,0 +1,13 @@ +using System.Reflection; +using System.Threading.Tasks; + +namespace ReactNative.Reflection +{ + static class MethodInfoHelpers + { + public static bool IsAsync(this MethodInfo methodInfo) + { + return typeof(Task).IsAssignableFrom(methodInfo.ReturnType); + } + } +} diff --git a/ReactWindows/ReactNative/Reflection/ReflectionHelpers.cs b/ReactWindows/ReactNative/Reflection/ReflectionHelpers.cs new file mode 100644 index 00000000000..cd432080bb1 --- /dev/null +++ b/ReactWindows/ReactNative/Reflection/ReflectionHelpers.cs @@ -0,0 +1,47 @@ +using System; +using System.Linq.Expressions; +using System.Reflection; + +namespace ReactNative.Reflection +{ + static class ReflectionHelpers + { + public static MemberInfo InfoOf(Expression expression) + { + return _InfoOf(expression.Body); + } + + public static MemberInfo InfoOf(Expression> expression) + { + return _InfoOf(expression.Body); + } + + public static MemberInfo InfoOf(Expression> expression) + { + return _InfoOf(expression.Body); + } + + public static MemberInfo InfoOf(Expression expression) + { + return _InfoOf(expression); + } + + private static MemberInfo _InfoOf(Expression expression) + { + switch (expression.NodeType) + { + case ExpressionType.Call: + var callExpression = (MethodCallExpression)expression; + return callExpression.Method; + case ExpressionType.MemberAccess: + var memberExpression = (MemberExpression)expression; + return memberExpression.Member; + case ExpressionType.New: + var newExpression = (NewExpression)expression; + return newExpression.Constructor; + } + + throw new InvalidOperationException("Expected either a method call, member access, or new expression."); + } + } +} diff --git a/ReactWindows/ReactNative/project.json b/ReactWindows/ReactNative/project.json new file mode 100644 index 00000000000..f93afb36e29 --- /dev/null +++ b/ReactWindows/ReactNative/project.json @@ -0,0 +1,17 @@ +{ + "dependencies": { + "Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0", + "Newtonsoft.Json": "7.0.1" + }, + "frameworks": { + "uap10.0": {} + }, + "runtimes": { + "win10-arm": {}, + "win10-arm-aot": {}, + "win10-x86": {}, + "win10-x86-aot": {}, + "win10-x64": {}, + "win10-x64-aot": {} + } +} \ No newline at end of file From 1cb5ce4543f9aa54caa0b5c8f1d7fcf60c568017 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Tue, 15 Dec 2015 09:49:35 -0500 Subject: [PATCH 02/17] Deleting WIP files and fixing unit tests. --- .../Bridge/NativeModuleBaseTests.cs | 31 +++++++++++++++++++ .../Bridge/NativeModuleRegistryTests.cs | 2 -- .../ReactNative/Bridge/ChakraReactBridge.cs | 23 -------------- .../Bridge/NativeModuleRegistry.cs | 6 ++-- .../Bridge/Queue/IMessageQueueThread.cs | 12 ------- .../Bridge/Queue/MessageQueueThread.cs | 16 +++------- .../Queue/MessageQueueThreadExtensions.cs | 13 +++++--- .../Bridge/Queue/MessageQueueThreadSpec.cs | 8 +---- .../ReactNative/Bridge/ReactContext.cs | 2 +- ReactWindows/ReactNative/IReactPackage.cs | 11 ------- ReactWindows/ReactNative/IViewManager.cs | 12 ------- .../ReactNative/ReactInstanceManager.cs | 16 ---------- ReactWindows/ReactNative/ReactNative.csproj | 13 +++++--- 13 files changed, 57 insertions(+), 108 deletions(-) delete mode 100644 ReactWindows/ReactNative/Bridge/ChakraReactBridge.cs delete mode 100644 ReactWindows/ReactNative/IReactPackage.cs delete mode 100644 ReactWindows/ReactNative/IViewManager.cs delete mode 100644 ReactWindows/ReactNative/ReactInstanceManager.cs diff --git a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs index 0b43aa0c712..7928847ce5a 100644 --- a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs +++ b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs @@ -10,11 +10,30 @@ public class NativeModuleBaseTests [TestMethod] public void NativeModuleBase_ArgumentChecks() { + var fooCount = 0; + var barSum = 0; + var testModule = new TestNativeModule(() => fooCount++, x => barSum += x); + testModule.Initialize(); + + Assert.AreEqual(2, testModule.Methods.Count); + foreach (var key in testModule.Methods.Keys) + { + // TODO + } } class TestNativeModule : NativeModuleBase { + private readonly Action _onFoo; + private readonly Action _onBar; + + public TestNativeModule(Action onFoo, Action onBar) + { + _onFoo = onFoo; + _onBar = onBar; + } + public override string Name { get @@ -22,6 +41,18 @@ public override string Name return "Foo"; } } + + [ReactMethod] + public void Foo() + { + _onFoo(); + } + + [ReactMethod] + public void Bar(int x) + { + _onBar(x); + } } } } diff --git a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleRegistryTests.cs b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleRegistryTests.cs index e757acfc0eb..8507add9ced 100644 --- a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleRegistryTests.cs +++ b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleRegistryTests.cs @@ -31,8 +31,6 @@ public void NativeModuleRegistry_Override_Allowed() var builder = new NativeModuleRegistry.Builder(); builder.Add(new OverrideAllowedModule()); builder.Add(new OverrideAllowedModule()); - var registry = builder.Build(); - Assert.AreEqual(1, registry.Modules.Count); } [TestMethod] diff --git a/ReactWindows/ReactNative/Bridge/ChakraReactBridge.cs b/ReactWindows/ReactNative/Bridge/ChakraReactBridge.cs deleted file mode 100644 index 311b745a680..00000000000 --- a/ReactWindows/ReactNative/Bridge/ChakraReactBridge.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System; -using Newtonsoft.Json.Linq; - -namespace ReactNative.Bridge -{ - class ChakraReactBridge : IReactBridge - { - public void CallFunction(int moduleId, int methodId, JArray arguments) - { - throw new NotImplementedException(); - } - - public void InvokeCallback(int callbackID, JArray arguments) - { - throw new NotImplementedException(); - } - - public void SetGlobalVariable(string propertyName, string jsonEncodedArgument) - { - throw new NotImplementedException(); - } - } -} diff --git a/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs b/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs index f0fd3c372f1..3531e31a3ec 100644 --- a/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs +++ b/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs @@ -101,11 +101,11 @@ public Builder Add(INativeModule module) throw new ArgumentNullException(nameof(module)); if (module.Name == null) throw new ArgumentException( - nameof(module), string.Format( CultureInfo.InvariantCulture, "Native module '{0}' cannot have a null `Name`.", - module.GetType())); + module.GetType()), + nameof(module)); var existing = default(INativeModule); if (_modules.TryGetValue(module.Name, out existing) && !module.CanOverrideExistingModule) @@ -121,7 +121,7 @@ public Builder Add(INativeModule module) } - _modules.Add(module.Name, module); + _modules[module.Name] = module; return this; } diff --git a/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs b/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs index c53cfb69843..85e3efdef67 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs @@ -15,18 +15,6 @@ public interface IMessageQueueThread /// The action. void RunOnQueue(Action action); - /// - /// Invokes the given function on this thread. - /// - /// - /// The function will be submitted to the end of the event queue - /// even if it is being submitted from the same queue Thread. - /// - /// The type of result expected. - /// The function. - /// The result of the function. - Task CallOnQueue(Func func); - /// /// Checks whether the current thread is also the thread /// associated with this . diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs index 097133b158b..268b75a6c34 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs @@ -29,10 +29,10 @@ public MessageQueueThread Create( { switch (spec.Kind) { - case MessageQueueThreadKind.MainUi: - return new DispatcherMessageQueueThread(name, handler); - case MessageQueueThreadKind.NewBackground: - return new BackgroundMessageQueueThread(name, handler); + //case MessageQueueThreadKind.MainUi: + // return new DispatcherMessageQueueThread(name, handler); + //case MessageQueueThreadKind.NewBackground: + // return new BackgroundMessageQueueThread(name, handler); default: throw new InvalidOperationException( string.Format( @@ -42,13 +42,5 @@ public MessageQueueThread Create( spec.Name)); } } - - class DispatcherMessageQueueThread : MessageQueueThread - { - public DispatcherMessageQueueThread(string name, IQueueThreadExceptionHandler handler) - { - - } - } } } diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs index da7879643df..602c265e167 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Threading.Tasks; +using Windows.System.Threading; namespace ReactNative.Bridge.Queue { @@ -20,14 +21,18 @@ public static void AssertIsOnThread(this IMessageQueueThread actionQueue) } } - public Task CallOnQueue(this IMessageQueueThread actionQueue, Func func) + public static Task CallOnQueue(this IMessageQueueThread actionQueue, Func func) { var taskCompletionSource = new TaskCompletionSource(); - actionQueue.RunOnQueue(async () => + actionQueue.RunOnQueue(() => { - var result = func; - await ThreadPool.RunAsync(taskCompletionSource.SetResult(result)); + var result = func(); + + // TaskCompletionSource.SetResult can call continuations + // on the awaiter of the task completion source. + // TODO: Prevent such thread stealing. + taskCompletionSource.SetResult(result); }); return taskCompletionSource.Task; diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs index c85261ec51f..c2b0aba371f 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs @@ -18,17 +18,11 @@ private MessageQueueThreadSpec(MessageQueueThreadKind kind, string name) internal MessageQueueThreadKind Kind { get; } - public static MessageQueueThreadSpec MainUiThreadSpec { get; } = new MessageQueueThreadSpec(MessageQueueThreadKind.MainUi, "main_ui") + public static MessageQueueThreadSpec MainUiThreadSpec { get; } = new MessageQueueThreadSpec(MessageQueueThreadKind.MainUi, "main_ui"); public static MessageQueueThreadSpec Create(string name) { return new MessageQueueThreadSpec(MessageQueueThreadKind.NewBackground, name); } - - enum MessageQueueThreadKind - { - MainUi, - NewBackground, - } } } diff --git a/ReactWindows/ReactNative/Bridge/ReactContext.cs b/ReactWindows/ReactNative/Bridge/ReactContext.cs index 6302dec264f..971ca498da8 100644 --- a/ReactWindows/ReactNative/Bridge/ReactContext.cs +++ b/ReactWindows/ReactNative/Bridge/ReactContext.cs @@ -6,7 +6,7 @@ namespace ReactNative.Bridge { - class ReactApplicationContext + class ReactContext { } } diff --git a/ReactWindows/ReactNative/IReactPackage.cs b/ReactWindows/ReactNative/IReactPackage.cs deleted file mode 100644 index d025fbbdcc4..00000000000 --- a/ReactWindows/ReactNative/IReactPackage.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Collections.Generic; - -namespace ReactNative.Bridge -{ - interface IReactPackage - { - IList CreateNativeModules(ReactApplicationContext context); - - IList CreateViewManagers(ReactApplicationContext context); - } -} diff --git a/ReactWindows/ReactNative/IViewManager.cs b/ReactWindows/ReactNative/IViewManager.cs deleted file mode 100644 index d07396ab6aa..00000000000 --- a/ReactWindows/ReactNative/IViewManager.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ReactNative -{ - class IViewManager - { - } -} diff --git a/ReactWindows/ReactNative/ReactInstanceManager.cs b/ReactWindows/ReactNative/ReactInstanceManager.cs deleted file mode 100644 index 5a4eae1b751..00000000000 --- a/ReactWindows/ReactNative/ReactInstanceManager.cs +++ /dev/null @@ -1,16 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ReactNative -{ - public abstract class ReactInstanceManager - { - public sealed class Builder - { - - } - } -} diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index d883000fa1c..34a9f456e5a 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -107,14 +107,20 @@ - + - + + + + + + + @@ -139,10 +145,7 @@ - - - From b750b3e749c4033656d8566ed45a246c34df34d3 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Tue, 15 Dec 2015 13:24:04 -0500 Subject: [PATCH 03/17] Improves exception handling for native argument parsing. Adds argument null checks, argument count checks, and wraps the argument extraction in a try/catch to to bubble as a NativeArgumentsParseException. Adds additional unit tests for exception handling. --- .../Bridge/NativeModuleBaseTests.cs | 126 +++++++++- .../ReactNative/Bridge/INativeMethod.cs | 2 +- .../Bridge/NativeArgumentsParseException.cs | 17 ++ .../ReactNative/Bridge/NativeModuleBase.cs | 229 +++++++++++++----- ReactWindows/ReactNative/ReactNative.csproj | 2 + .../Reflection/ExpressionExtensions.cs | 14 ++ 6 files changed, 326 insertions(+), 64 deletions(-) create mode 100644 ReactWindows/ReactNative/Bridge/NativeArgumentsParseException.cs create mode 100644 ReactWindows/ReactNative/Reflection/ExpressionExtensions.cs diff --git a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs index 7928847ce5a..e2d34352632 100644 --- a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs +++ b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs @@ -1,6 +1,9 @@ using System; +using System.Collections.Generic; using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using Newtonsoft.Json.Linq; using ReactNative.Bridge; +using System.Linq; namespace ReactNative.Tests.Bridge { @@ -8,7 +11,56 @@ namespace ReactNative.Tests.Bridge public class NativeModuleBaseTests { [TestMethod] - public void NativeModuleBase_ArgumentChecks() + public void NativeModuleBase_MethodOverload_ThrowsNotSupported() + { + var module = new MethodOverloadNativeModule(); + AssertEx.Throws(() => module.Initialize()); + } + + [TestMethod] + public void NativeModuleBase_Invocation_ArgumentNull() + { + var testModule = new TestNativeModule(); + + testModule.Initialize(); + + var catalystInstance = new TestCatalystInstance(); + AssertEx.Throws( + () => testModule.Methods[nameof(TestNativeModule.Foo)].Invoke(null, new JArray()), + ex => Assert.AreEqual("catalystInstance", ex.ParamName)); + AssertEx.Throws( + () => testModule.Methods[nameof(TestNativeModule.Foo)].Invoke(catalystInstance, null), + ex => Assert.AreEqual("jsArguments", ex.ParamName)); + } + + [TestMethod] + public void NativeModuleBase_Invocation_ArgumentInvalidCount() + { + var testModule = new TestNativeModule(); + + testModule.Initialize(); + + var catalystInstance = new TestCatalystInstance(); + AssertEx.Throws( + () => testModule.Methods[nameof(TestNativeModule.Bar)].Invoke(catalystInstance, new JArray()), + ex => Assert.AreEqual("jsArguments", ex.ParamName)); + } + + [TestMethod] + public void NativeModuleBase_Invocation_ArgumentConversionException() + { + var testModule = new TestNativeModule(); + + testModule.Initialize(); + + var catalystInstance = new TestCatalystInstance(); + AssertEx.Throws( + () => testModule.Methods[nameof(TestNativeModule.Bar)].Invoke(catalystInstance, JArray.FromObject(new[] { default(object) })), + ex => Assert.AreEqual("jsArguments", ex.ParamName)); + } + + [TestMethod] + public void NativeModuleBase_Invocation() { var fooCount = 0; var barSum = 0; @@ -17,9 +69,35 @@ public void NativeModuleBase_ArgumentChecks() testModule.Initialize(); Assert.AreEqual(2, testModule.Methods.Count); - foreach (var key in testModule.Methods.Keys) + + var catalystInstance = new TestCatalystInstance(); + testModule.Methods["Foo"].Invoke(catalystInstance, new JArray()); + testModule.Methods["Foo"].Invoke(catalystInstance, new JArray()); + Assert.AreEqual(2, fooCount); + + testModule.Methods["Bar"].Invoke(catalystInstance, JArray.FromObject(new[] { 42 })); + testModule.Methods["Bar"].Invoke(catalystInstance, JArray.FromObject(new[] { 17 })); + Assert.AreEqual(59, barSum); + } + + class MethodOverloadNativeModule : NativeModuleBase + { + public override string Name + { + get + { + return "Test"; + } + } + + [ReactMethod] + public void Foo() + { + } + + [ReactMethod] + public void Foo(int x) { - // TODO } } @@ -28,6 +106,11 @@ class TestNativeModule : NativeModuleBase private readonly Action _onFoo; private readonly Action _onBar; + public TestNativeModule() + : this(() => { }, _ => { }) + { + } + public TestNativeModule(Action onFoo, Action onBar) { _onFoo = onFoo; @@ -54,5 +137,42 @@ public void Bar(int x) _onBar(x); } } + + class TestCatalystInstance : ICatalystInstance + { + private readonly Action _callback; + + public TestCatalystInstance() + : this((_, __) => { }) + { + } + + public TestCatalystInstance(Action callback) + { + _callback = callback; + } + + public ICollection NativeModules + { + get + { + throw new NotImplementedException(); + } + } + + public T GetNativeModule(Type nativeModuleInterface) where T : INativeModule + { + throw new NotImplementedException(); + } + + public void Initialize() + { + } + + public void InvokeCallback(int callbackId, JArray arguments) + { + _callback(callbackId, arguments); + } + } } } diff --git a/ReactWindows/ReactNative/Bridge/INativeMethod.cs b/ReactWindows/ReactNative/Bridge/INativeMethod.cs index 68b3febfbbb..ae456b7d033 100644 --- a/ReactWindows/ReactNative/Bridge/INativeMethod.cs +++ b/ReactWindows/ReactNative/Bridge/INativeMethod.cs @@ -6,6 +6,6 @@ public interface INativeMethod { string Type { get; } - void Invoke(ICatalystInstance instance, JArray parameters); + void Invoke(ICatalystInstance catalystInstance, JArray jsArguments); } } diff --git a/ReactWindows/ReactNative/Bridge/NativeArgumentsParseException.cs b/ReactWindows/ReactNative/Bridge/NativeArgumentsParseException.cs new file mode 100644 index 00000000000..ba3508a5fef --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/NativeArgumentsParseException.cs @@ -0,0 +1,17 @@ +using System; + +namespace ReactNative.Bridge +{ + public class NativeArgumentsParseException : ArgumentException + { + public NativeArgumentsParseException(string message, string paramName) + : base(message, paramName) + { + } + + public NativeArgumentsParseException(string message, string paramName, Exception innerException) + : base(message, paramName, innerException) + { + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs index b841164ef97..428d00ce8f3 100644 --- a/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs +++ b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs @@ -2,6 +2,7 @@ using ReactNative.Reflection; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq.Expressions; using System.Reflection; @@ -76,6 +77,18 @@ private IReadOnlyDictionary InitializeMethods() var methodMap = new Dictionary(exportedMethods.Count); foreach (var method in exportedMethods) { + var existingMethod = default(INativeMethod); + if (methodMap.TryGetValue(method.Name, out existingMethod)) + { + throw new NotSupportedException( + string.Format( + CultureInfo.InvariantCulture, + "React module '{0}' with name '{1}' has more than one ReactMethod with the name '{2}'.", + GetType(), + Name, + method.Name)); + } + methodMap.Add(method.Name, new NativeMethod(this, method)); } @@ -109,17 +122,23 @@ public string Type get; } - public void Invoke(ICatalystInstance instance, JArray parameters) + public void Invoke(ICatalystInstance catalystInstance, JArray jsArguments) { - + _invokeDelegate.Value(catalystInstance, jsArguments); } - private static MethodInfo s_extractCallback = (MethodInfo)ReflectionHelpers.InfoOf(() => ExtractCallback(default(JToken), default(ICatalystInstance))); - private static MethodInfo s_extractGeneric = ((MethodInfo)ReflectionHelpers.InfoOf(() => Extract(default(JToken)))).GetGenericMethodDefinition(); + private static ConstructorInfo s_newArgumentNullException = (ConstructorInfo)ReflectionHelpers.InfoOf(() => new ArgumentNullException(default(string))); + private static ConstructorInfo s_newArgumentException = (ConstructorInfo)ReflectionHelpers.InfoOf(() => new ArgumentException(default(string), default(string))); + private static ConstructorInfo s_newNativeArgumentParseException = (ConstructorInfo)ReflectionHelpers.InfoOf(() => new NativeArgumentsParseException(default(string), default(string))); + private static ConstructorInfo s_newNativeArgumentParseExceptionInner = (ConstructorInfo)ReflectionHelpers.InfoOf(() => new NativeArgumentsParseException(default(string), default(string), default(Exception))); + private static ConstructorInfo s_newCallback = (ConstructorInfo)ReflectionHelpers.InfoOf(() => new Callback(default(int), default(ICatalystInstance))); + private static MethodInfo s_valueInt = ((MethodInfo)ReflectionHelpers.InfoOf((JToken token) => token.Value())).GetGenericMethodDefinition().MakeGenericMethod(typeof(int)); + private static MethodInfo s_toObjectGeneric = ((MethodInfo)ReflectionHelpers.InfoOf((JToken token) => token.ToObject())).GetGenericMethodDefinition(); + private static MethodInfo s_stringFormat = (MethodInfo)ReflectionHelpers.InfoOf(() => string.Format(default(IFormatProvider), default(string), default(object))); + private static MethodInfo s_getIndex = (MethodInfo)ReflectionHelpers.InfoOf((JArray arr) => arr[0]); private static PropertyInfo s_countProperty = (PropertyInfo)ReflectionHelpers.InfoOf((JArray arr) => arr.Count); - private static Expression s_throwExpression = Expression.Throw(Expression.Constant(new ArgumentException("Invalid argument count."))); - private static Expression> GenerateExpression(NativeModuleBase instance, MethodInfo method) + private static Expression> GenerateExpression(NativeModuleBase module, MethodInfo method) { var parameterInfos = method.GetParameters(); var n = parameterInfos.Length; @@ -136,57 +155,66 @@ private static Expression> GenerateExpression( var parameterType = parameterInfo.ParameterType; var parameterExpression = Expression.Parameter(parameterType, parameterInfo.Name); parameterExpressions[i] = parameterExpression; - - if (parameterType == typeof(ICallback)) - { - // - // valueOf(parameterInfo.Name) = ExtractCallback(jsArguments[valueOf(i)], catalystInstance) - // - extractExpressions[i] = Expression.Assign( - parameterExpression, - Expression.Call( - s_extractCallback, - Expression.ArrayIndex( - jsArgumentsParameter, - Expression.Constant(i) - ), - catalystInstanceParameter - ) - ); - } - else - { - var extractMethod = s_extractGeneric.MakeGenericMethod(parameterType); - - // - // valueOf(parameterInfo.Name) = Extract(jsArguments[valueOf(i)]); - // - extractExpressions[i] = Expression.Assign( - parameterExpression, - Expression.Call( - extractMethod, - Expression.ArrayIndex( - jsArgumentsParameter, - Expression.Constant(i) - ) - ) - ); - } + extractExpressions[i] = GenerateExtractExpression( + parameterInfo.ParameterType, + parameterExpression, + Expression.Call(jsArgumentsParameter, s_getIndex, Expression.Constant(i)), + catalystInstanceParameter, + jsArgumentsParameter.Name, + module.Name, + method.Name, + i); } - var blockStatements = new Expression[parameterInfos.Length + 2]; + var blockStatements = new Expression[parameterInfos.Length + 4]; + + // + // if (catalystInstance == null) + // throw new ArgumentNullException(nameof(catalystInstance)); + // + blockStatements[0] = CreateNullCheckExpression(catalystInstanceParameter); + + // + // if (jsArguments == null) + // throw new ArgumentNullException(nameof(jsArguments)); + // + blockStatements[1] = CreateNullCheckExpression(jsArgumentsParameter); // - // if (args.Count != valueOf(n)) - // throw new ArgumentException("Invalid argument count."); + // if (jsArguments.Count != valueOf(parameterInfos.Count)) + // throw new NativeArgumentsParseException( + // string.Format( + // CultureInfo.InvariantCulture, + // "Module 'moduleName' method 'methodName' got '{0}' arguments, expected 'parameterCount'." + // jsArguments.Count)); // - blockStatements[0] = Expression.Condition( + blockStatements[2] = Expression.IfThen( Expression.NotEqual( - Expression.Property(jsArgumentsParameter, s_countProperty), - Expression.Constant(n) + Expression.MakeMemberAccess(jsArgumentsParameter, s_countProperty), + Expression.Constant(parameterInfos.Length) ), - s_throwExpression, - Expression.Empty() + Expression.Throw( + Expression.New( + s_newNativeArgumentParseException, + Expression.Call( + s_stringFormat, + Expression.Constant(CultureInfo.InvariantCulture), + Expression.Constant( + string.Format( + CultureInfo.InvariantCulture, + "Module '{0}' method '{1}' got '{{0}}' arguments, expected '{2}'.", + module.Name, + method.Name, + parameterInfos.Length) + ), + Expression.Convert( + Expression.MakeMemberAccess(jsArgumentsParameter, s_countProperty), + typeof(object) + ) + ), + Expression.Constant(jsArgumentsParameter.Name) + ) + ) ); // @@ -195,10 +223,10 @@ private static Expression> GenerateExpression( // ... // pn = Extract(jsArguments[n]); // - Array.Copy(extractExpressions, 0, blockStatements, 1, parameterInfos.Length); + Array.Copy(extractExpressions, 0, blockStatements, 3, parameterInfos.Length); - blockStatements[parameterInfos.Length + 1] = Expression.Call( - Expression.Constant(instance), + blockStatements[blockStatements.Length - 1] = Expression.Call( + Expression.Constant(module), method, parameterExpressions); @@ -209,17 +237,98 @@ private static Expression> GenerateExpression( ); } - private static T Extract(JToken value) + private static Expression GenerateExtractExpression( + Type type, + Expression leftExpression, + Expression tokenExpression, + Expression catalystInstanceExpression, + string parameterName, + string moduleName, + string methodName, + int argumentIndex) { - return value.ToObject(); - } + // + // try + // { + // ... + // } + // catch (Exception ex) + // { + // throw new NativeArgumentParseException( + // string.Format( + // CultureInfo.InvariantCulture, + // "Error extracting argument for module 'moduleName' method 'methodName' at index '{0}'.", + // argumentIndex), + // paramName, + // ex); + // } + // + var catchBlock = Expression.Parameter(typeof(Exception), "ex").Let(ex => + Expression.Catch( + ex, + Expression.Throw( + Expression.New( + s_newNativeArgumentParseExceptionInner, + Expression.Call( + s_stringFormat, + Expression.Constant(CultureInfo.InvariantCulture), + Expression.Constant( + string.Format( + CultureInfo.InvariantCulture, + "Error extracting argument for module '{0}' method '{1}' at index '{{0}}'.", + moduleName, + methodName) + ), + Expression.Constant(argumentIndex, typeof(object)) + ), + Expression.Constant(parameterName), + ex + ) + ) + ) + ); - private static ICallback ExtractCallback(JToken value, ICatalystInstance instance) - { - var id = value.Value(); - return new Callback(id, instance); + var valueExpression = default(Expression); + if (type == typeof(ICallback)) + { + valueExpression = Expression.Parameter(typeof(int), "id").Let(id => + Expression.Block( + new[] { id }, + Expression.Assign( + id, + Expression.Call(tokenExpression, s_valueInt) + ), + Expression.New(s_newCallback, id, catalystInstanceExpression) + ) + ); + } + else + { + var toObjectMethod = s_toObjectGeneric.MakeGenericMethod(type); + valueExpression = Expression.Call(tokenExpression, toObjectMethod); + } + + return Expression.TryCatch( + Expression.Block( + typeof(void), + Expression.Assign(leftExpression, valueExpression) + ), + catchBlock + ); } + private static Expression CreateNullCheckExpression(ParameterExpression parameter) + where T : class + { + return Expression.IfThen( + Expression.Equal( + parameter, + Expression.Default(typeof(T)) + ), + Expression.Throw(Expression.New(s_newArgumentNullException, Expression.Constant(parameter.Name))) + ); + } + class Callback : ICallback { private readonly int _id; diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index 34a9f456e5a..28c5d9ccbf8 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -112,6 +112,7 @@ + @@ -148,6 +149,7 @@ + diff --git a/ReactWindows/ReactNative/Reflection/ExpressionExtensions.cs b/ReactWindows/ReactNative/Reflection/ExpressionExtensions.cs new file mode 100644 index 00000000000..e96bb3db636 --- /dev/null +++ b/ReactWindows/ReactNative/Reflection/ExpressionExtensions.cs @@ -0,0 +1,14 @@ +using System; +using System.Linq.Expressions; + +namespace ReactNative.Reflection +{ + static class ExpressionExtensions + { + public static T Let(this TExpression expression, Func selector) + where TExpression : Expression + { + return selector(expression); + } + } +} From 6387b47315773f794cde45e0e6a501473597eb25 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Tue, 15 Dec 2015 13:54:17 -0500 Subject: [PATCH 04/17] Unit tests and bug fixes for ICallback binding. --- .../Bridge/NativeModuleBaseTests.cs | 98 ++++++++++++++++++- .../ReactNative/Bridge/NativeModuleBase.cs | 6 +- 2 files changed, 98 insertions(+), 6 deletions(-) diff --git a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs index e2d34352632..0f3a8d42088 100644 --- a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs +++ b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs @@ -71,15 +71,76 @@ public void NativeModuleBase_Invocation() Assert.AreEqual(2, testModule.Methods.Count); var catalystInstance = new TestCatalystInstance(); - testModule.Methods["Foo"].Invoke(catalystInstance, new JArray()); - testModule.Methods["Foo"].Invoke(catalystInstance, new JArray()); + testModule.Methods[nameof(TestNativeModule.Foo)].Invoke(catalystInstance, new JArray()); + testModule.Methods[nameof(TestNativeModule.Foo)].Invoke(catalystInstance, new JArray()); Assert.AreEqual(2, fooCount); - testModule.Methods["Bar"].Invoke(catalystInstance, JArray.FromObject(new[] { 42 })); - testModule.Methods["Bar"].Invoke(catalystInstance, JArray.FromObject(new[] { 17 })); + testModule.Methods[nameof(TestNativeModule.Bar)].Invoke(catalystInstance, JArray.FromObject(new[] { 42 })); + testModule.Methods[nameof(TestNativeModule.Bar)].Invoke(catalystInstance, JArray.FromObject(new[] { 17 })); Assert.AreEqual(59, barSum); } + [TestMethod] + public void NativeModuleBase_Invocation_Callbacks() + { + var callbackArgs = new object[] { 1, 2, 3 }; + var module = new CallbackNativeModule(callbackArgs); + module.Initialize(); + + var id = default(int); + var args = default(List); + + var catalystInstance = new TestCatalystInstance((i, a) => + { + id = i; + args = a.ToObject>(); + }); + + module.Methods[nameof(CallbackNativeModule.Foo)].Invoke(catalystInstance, JArray.FromObject(new[] { 42 })); + Assert.AreEqual(42, id); + Assert.IsTrue(args.Cast().SequenceEqual(callbackArgs)); + } + + [TestMethod] + public void NativeModuleBase_Invocation_Callbacks_InvalidArgumentThrows() + { + var callbackArgs = new object[] { 1, 2, 3 }; + var module = new CallbackNativeModule(callbackArgs); + module.Initialize(); + + var id = default(int); + var args = default(List); + + var catalystInstance = new TestCatalystInstance((i, a) => + { + id = i; + args = a.ToObject>(); + }); + + AssertEx.Throws( + () => module.Methods[nameof(CallbackNativeModule.Foo)].Invoke(catalystInstance, JArray.FromObject(new[] { default(object) })), + ex => Assert.AreEqual("jsArguments", ex.ParamName)); + } + + [TestMethod] + public void NativeModuleBase_Invocation_Callbacks_NullCallback() + { + var module = new CallbackNativeModule(null); + module.Initialize(); + + var id = default(int); + var args = default(List); + + var catalystInstance = new TestCatalystInstance((i, a) => + { + id = i; + args = a.ToObject>(); + }); + + module.Methods[nameof(CallbackNativeModule.Foo)].Invoke(catalystInstance, JArray.FromObject(new[] { 42 })); + Assert.AreEqual(0, args.Count); + } + class MethodOverloadNativeModule : NativeModuleBase { public override string Name @@ -138,6 +199,35 @@ public void Bar(int x) } } + class CallbackNativeModule : NativeModuleBase + { + private readonly object[] _callbackArgs; + + public CallbackNativeModule() + : this(null) + { + } + + public CallbackNativeModule(object[] callbackArgs) + { + _callbackArgs = callbackArgs; + } + + public override string Name + { + get + { + return "Test"; + } + } + + [ReactMethod] + public void Foo(ICallback callback) + { + callback.Invoke(_callbackArgs); + } + } + class TestCatalystInstance : ICatalystInstance { private readonly Action _callback; diff --git a/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs index 428d00ce8f3..727cd8959f1 100644 --- a/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs +++ b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs @@ -296,7 +296,7 @@ private static Expression GenerateExtractExpression( new[] { id }, Expression.Assign( id, - Expression.Call(tokenExpression, s_valueInt) + Expression.Call(s_valueInt, tokenExpression) ), Expression.New(s_newCallback, id, catalystInstanceExpression) ) @@ -331,6 +331,8 @@ private static Expression CreateNullCheckExpression(ParameterExpression param class Callback : ICallback { + private static readonly object[] s_empty = new object[0]; + private readonly int _id; private readonly ICatalystInstance _instance; @@ -342,7 +344,7 @@ public Callback(int id, ICatalystInstance instance) public void Invoke(params object[] arguments) { - _instance.InvokeCallback(_id, JArray.FromObject(arguments)); + _instance.InvokeCallback(_id, JArray.FromObject(arguments ?? s_empty)); } } } From 94494989f682dba348eaa5874a5dfaf131922860 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Tue, 15 Dec 2015 15:14:12 -0500 Subject: [PATCH 05/17] Adds tracing support using ETW. --- .../ReactNative/Bridge/NativeModuleBase.cs | 6 ++- ReactWindows/ReactNative/ReactNative.csproj | 2 + .../ReactNative/Tracing/TraceDisposable.cs | 45 +++++++++++++++++++ ReactWindows/ReactNative/Tracing/Tracer.cs | 21 +++++++++ 4 files changed, 73 insertions(+), 1 deletion(-) create mode 100644 ReactWindows/ReactNative/Tracing/TraceDisposable.cs create mode 100644 ReactWindows/ReactNative/Tracing/Tracer.cs diff --git a/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs index 727cd8959f1..4ee46d72e3f 100644 --- a/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs +++ b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs @@ -1,5 +1,6 @@ using Newtonsoft.Json.Linq; using ReactNative.Reflection; +using ReactNative.Tracing; using System; using System.Collections.Generic; using System.Globalization; @@ -124,7 +125,10 @@ public string Type public void Invoke(ICatalystInstance catalystInstance, JArray jsArguments) { - _invokeDelegate.Value(catalystInstance, jsArguments); + using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "callNativeModuleMethod")) + { + _invokeDelegate.Value(catalystInstance, jsArguments); + } } private static ConstructorInfo s_newArgumentNullException = (ConstructorInfo)ReflectionHelpers.InfoOf(() => new ArgumentNullException(default(string))); diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index 28c5d9ccbf8..c7a09e284b2 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -152,6 +152,8 @@ + + diff --git a/ReactWindows/ReactNative/Tracing/TraceDisposable.cs b/ReactWindows/ReactNative/Tracing/TraceDisposable.cs new file mode 100644 index 00000000000..a477402729c --- /dev/null +++ b/ReactWindows/ReactNative/Tracing/TraceDisposable.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Diagnostics.Tracing; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReactNative.Tracing +{ + public struct TraceDisposable : IDisposable + { + private static readonly EventSource s_eventSource = new EventSource("ReactNative"); + private static readonly Stopwatch s_stopwatch = Stopwatch.StartNew(); + + private readonly int _source; + private readonly string _title; + private readonly long _timestamp; + + public TraceDisposable(int source, string title) + { + _source = source; + _title = title; + _timestamp = s_stopwatch.ElapsedTicks; + } + + public void Dispose() + { + s_eventSource.Write(_title, new EventData(_source, TimeSpan.FromTicks(s_stopwatch.ElapsedTicks - _timestamp))); + } + + [EventData] + struct EventData + { + public EventData(int source, TimeSpan elapsed) + { + Source = source; + Elapsed = elapsed; + } + + public int Source { get; } + public TimeSpan Elapsed { get; } + } + } +} diff --git a/ReactWindows/ReactNative/Tracing/Tracer.cs b/ReactWindows/ReactNative/Tracing/Tracer.cs new file mode 100644 index 00000000000..5f3627f9556 --- /dev/null +++ b/ReactWindows/ReactNative/Tracing/Tracer.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReactNative.Tracing +{ + static class Tracer + { + public const int TRACE_TAG_REACT_BRIDGE = 0; + public const int TRACE_TAG_REACT_FRESCO = 1; + public const int TRACE_TAG_REACT_APPS = 2; + public const int TRACE_TAG_REACT_VIEW = 3; + + public static TraceDisposable Trace(int kind, string title) + { + return new TraceDisposable(kind, title); + } + } +} From 5d7b71ac766aa86879374bd646455344c61db09d Mon Sep 17 00:00:00 2001 From: Erik Schlegel Date: Tue, 15 Dec 2015 15:52:06 -0500 Subject: [PATCH 06/17] Adding JS Executor --- .../ReactNative/Bridge/ICSharpJSExecutor.cs | 12 +++++++ .../ReactNative/Bridge/JavaScriptExecuter.cs | 31 +++++++++++++++++++ .../Bridge/ProxyJavaScriptExecutor.cs | 9 ++++++ ReactWindows/ReactNative/ReactNative.csproj | 2 ++ 4 files changed, 54 insertions(+) create mode 100644 ReactWindows/ReactNative/Bridge/ICSharpJSExecutor.cs create mode 100644 ReactWindows/ReactNative/Bridge/JavaScriptExecuter.cs create mode 100644 ReactWindows/ReactNative/Bridge/ProxyJavaScriptExecutor.cs diff --git a/ReactWindows/ReactNative/Bridge/ICSharpJSExecutor.cs b/ReactWindows/ReactNative/Bridge/ICSharpJSExecutor.cs new file mode 100644 index 00000000000..eff8b90ad17 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/ICSharpJSExecutor.cs @@ -0,0 +1,12 @@ +namespace ReactNative.Bridge +{ + public interface ICSharpJSExecutor + { + void close(); + + void executeApplicationScript(string script, string sourceURL); + + string executeJSCall(string methodName, string jsonArgsArray); + + } +} diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptExecuter.cs b/ReactWindows/ReactNative/Bridge/JavaScriptExecuter.cs new file mode 100644 index 00000000000..ac3cd4f4a72 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/JavaScriptExecuter.cs @@ -0,0 +1,31 @@ + +namespace ReactNative.Bridge +{ + /// + /// Abstract class responsible for brokering communication between native and React Naitve components + /// + public abstract class JavaScriptExecuter + { + private ICSharpJSExecutor mJSExecutor; + + /// + /// Sets the JS executor + /// + /// + public JavaScriptExecuter(ICSharpJSExecutor executor) + { + mJSExecutor = executor; + } + + /// + /// Closes the executor + /// + public abstract void close(); + + /// + /// Instantiates the executor + /// + /// + public abstract void initialize(ICSharpJSExecutor executor); + } +} diff --git a/ReactWindows/ReactNative/Bridge/ProxyJavaScriptExecutor.cs b/ReactWindows/ReactNative/Bridge/ProxyJavaScriptExecutor.cs new file mode 100644 index 00000000000..2b4fcd41e41 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/ProxyJavaScriptExecutor.cs @@ -0,0 +1,9 @@ + +namespace ReactNative.Bridge.Executors +{ + public class ProxyJavaScriptExecutor : ICSharpJSExecutor + { + + + } +} diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index 34a9f456e5a..bbe794ee314 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -108,6 +108,8 @@ + + From 95673eab4b2c7873e35ce6563c2048d6d568a38e Mon Sep 17 00:00:00 2001 From: Erik Schlegel Date: Tue, 15 Dec 2015 19:17:55 -0500 Subject: [PATCH 07/17] Adding core react components --- .../ReactNative/Bridge/ReactContext.cs | 2 +- .../ReactNative/Core/ReactInstanceManager.cs | 61 +++++++++++++++++++ .../ReactNative/Core/ReactRootView.cs | 45 ++++++++++++++ ReactWindows/ReactNative/ReactNative.csproj | 11 ++-- .../ReactNative/UIManager/RootView.cs | 20 ++++++ .../UIManager/SizeMonitoringFrameLayout.cs | 46 ++++++++++++++ 6 files changed, 179 insertions(+), 6 deletions(-) create mode 100644 ReactWindows/ReactNative/Core/ReactInstanceManager.cs create mode 100644 ReactWindows/ReactNative/Core/ReactRootView.cs create mode 100644 ReactWindows/ReactNative/UIManager/RootView.cs create mode 100644 ReactWindows/ReactNative/UIManager/SizeMonitoringFrameLayout.cs diff --git a/ReactWindows/ReactNative/Bridge/ReactContext.cs b/ReactWindows/ReactNative/Bridge/ReactContext.cs index 971ca498da8..9177be93253 100644 --- a/ReactWindows/ReactNative/Bridge/ReactContext.cs +++ b/ReactWindows/ReactNative/Bridge/ReactContext.cs @@ -6,7 +6,7 @@ namespace ReactNative.Bridge { - class ReactContext + public class ReactContext { } } diff --git a/ReactWindows/ReactNative/Core/ReactInstanceManager.cs b/ReactWindows/ReactNative/Core/ReactInstanceManager.cs new file mode 100644 index 00000000000..07ce79aa759 --- /dev/null +++ b/ReactWindows/ReactNative/Core/ReactInstanceManager.cs @@ -0,0 +1,61 @@ + +namespace ReactNative.Core +{ + using ReactNative.Bridge; + + /// + /// This class is managing instances of {@link CatalystInstance}. It expose a way to configure + /// catalyst instance using {@link ReactPackage} and keeps track of the lifecycle of that + /// instance.It also sets up connection between the instance and developers support functionality + /// of the framework. + /// + /// An instance of this manager is required to start JS application in { @link ReactRootView} (see + /// {@link ReactRootView#startReactApplication} for more info). + /// + /// The lifecycle of the instance of {@link ReactInstanceManager} should be bound to the activity + /// that owns the { @link ReactRootView } that is used to render react application using this + /// instance manager (see {@link ReactRootView#startReactApplication}). It's required to pass + /// owning activity's lifecycle events to the instance manager (see {@link #onPause}, + /// {@link #onDestroy} and {@link #onResume}). + /// + public abstract class ReactInstanceManager + { + public interface ReactInstanceEventListener + { + /** + * Called when the react context is initialized (all modules registered). Always called on the + * UI thread. + */ + void onReactContextInitialized(ReactContext context); + } + + /// + /// Trigger react context initialization asynchronously in a background async task. + /// + public abstract void createReactContextInBackground(); + + /// + /// return whether createReactContextInBackground has been called + /// + /// + public abstract bool hasStartedCreatingInitialContext(); + + /// + /// Gets the URL where the last bundle was loaded from. + /// + /// URL where the last bundle was loaded from. + public abstract string getSourceUrl(); + + /// + /// Attach given {@param rootView} to a catalyst instance manager and start JS application + /// + /// + public abstract void attachMeasuredRootView(ReactRootView rootView); + + /// + /// Detach given rootView from current catalyst instance. + /// + /// + public abstract void detachRootView(ReactRootView rootView); + } +} diff --git a/ReactWindows/ReactNative/Core/ReactRootView.cs b/ReactWindows/ReactNative/Core/ReactRootView.cs new file mode 100644 index 00000000000..36f6c7c474b --- /dev/null +++ b/ReactWindows/ReactNative/Core/ReactRootView.cs @@ -0,0 +1,45 @@ + +namespace ReactNative.Core +{ + using Newtonsoft.Json.Linq; + using ReactNative.UIManager; + using System; + + /// + /// Default root view for catalyst apps. Provides the ability to listen for size changes so that a UI manager can re-layout its elements + /// + public class ReactRootView : SizeMonitoringFrameLayout, RootView + { + private ReactInstanceManager mReactInstanceManager; + private string mJSModuleName; + private bool mIsAttachedToWindow = false; + private bool mIsAttachedToInstance = false; + + public void startReactApplication(ReactInstanceManager reactInstanceManager, string moduleName) + { + //TODO: Add thread queue impl hook + //UiThreadUtil.assertOnUiThread(); + + mReactInstanceManager = reactInstanceManager; + mJSModuleName = moduleName; + + if (!mReactInstanceManager.hasStartedCreatingInitialContext()) + { + mReactInstanceManager.createReactContextInBackground(); + } + } + + //TODO: Implement this method for emitting events + private void sendEvent(string eventName, JArray parameters) + { + throw new NotImplementedException(); + + if (mReactInstanceManager != null) + { + // mReactInstanceManager.getCurrentReactContext() + // .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class) + //.emit(eventName, params); + } + } + } +} diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index 02f4f6a7096..8a515204b94 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -108,13 +108,13 @@ - - + + + - @@ -151,12 +151,13 @@ - - + + + 14.0 diff --git a/ReactWindows/ReactNative/UIManager/RootView.cs b/ReactWindows/ReactNative/UIManager/RootView.cs new file mode 100644 index 00000000000..1b54cde1ff4 --- /dev/null +++ b/ReactWindows/ReactNative/UIManager/RootView.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReactNative.UIManager +{ + /// + /// Interface for the root native view of a React native application. + /// + public interface RootView + { + /// + /// Called when a child starts a native gesture (e.g. a scroll in a ScrollView). + /// + /// + //void onChildStartedNativeGesture(CalibrationEventArgs wpEvent); + } +} diff --git a/ReactWindows/ReactNative/UIManager/SizeMonitoringFrameLayout.cs b/ReactWindows/ReactNative/UIManager/SizeMonitoringFrameLayout.cs new file mode 100644 index 00000000000..6ccd774388a --- /dev/null +++ b/ReactWindows/ReactNative/UIManager/SizeMonitoringFrameLayout.cs @@ -0,0 +1,46 @@ + +namespace ReactNative.UIManager +{ + /// + /// allows registering for size change events. The main purpose for this class is to hide complexity of ReactRootView + /// + public class SizeMonitoringFrameLayout + { + public interface OnSizeChangedListener + { + void onSizeChanged(int width, int height, int oldWidth, int oldHeight); + } + + private OnSizeChangedListener mOnSizeChangedListener; + + /*public SizeMonitoringFrameLayout(Context context) + { + super(context); + } + + public SizeMonitoringFrameLayout(Context context, AttributeSet attrs) + { + super(context, attrs); + } + + public SizeMonitoringFrameLayout(Context context, AttributeSet attrs, int defStyle) + { + super(context, attrs, defStyle); + }*/ + + public void setOnSizeChangedListener(OnSizeChangedListener onSizeChangedListener) + { + mOnSizeChangedListener = onSizeChangedListener; + } + + protected void onSizeChanged(int w, int h, int oldw, int oldh) + { + // base.onSizeChanged(w, h, oldw, oldh); + + if (mOnSizeChangedListener != null) + { + mOnSizeChangedListener.onSizeChanged(w, h, oldw, oldh); + } + } + } +} From 91c7bebaf70d5a660b5973c8bec00a0d2296a6a4 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Wed, 16 Dec 2015 00:44:19 -0500 Subject: [PATCH 08/17] Adds unit tests and minor tweaks to MessageQueueThread. --- .../Bridge/Queue/MessageQueueThreadTests.cs | 127 ++++++++++ .../Internal/DispatcherHelpers.cs | 14 ++ .../ReactNative.Tests.csproj | 2 + .../ReactNative.Tests/UnitTestApp.xaml.cs | 5 + .../Queue/IQueueThreadExceptionHandler.cs | 9 - .../Queue/LimitedConcurrencyTaskScheduler.cs | 185 +++++++++++++++ .../Bridge/Queue/MessageQueueThread.cs | 223 ++++++++++++++++-- .../Queue/MessageQueueThreadExtensions.cs | 12 +- .../Bridge/Queue/MessageQueueThreadKind.cs | 5 +- .../Bridge/Queue/MessageQueueThreadSpec.cs | 13 +- ReactWindows/ReactNative/ReactNative.csproj | 2 +- ReactWindows/ReactNative/project.json | 3 +- 12 files changed, 564 insertions(+), 36 deletions(-) create mode 100644 ReactWindows/ReactNative.Tests/Bridge/Queue/MessageQueueThreadTests.cs create mode 100644 ReactWindows/ReactNative.Tests/Internal/DispatcherHelpers.cs delete mode 100644 ReactWindows/ReactNative/Bridge/Queue/IQueueThreadExceptionHandler.cs create mode 100644 ReactWindows/ReactNative/Bridge/Queue/LimitedConcurrencyTaskScheduler.cs diff --git a/ReactWindows/ReactNative.Tests/Bridge/Queue/MessageQueueThreadTests.cs b/ReactWindows/ReactNative.Tests/Bridge/Queue/MessageQueueThreadTests.cs new file mode 100644 index 00000000000..e51c23d86f7 --- /dev/null +++ b/ReactWindows/ReactNative.Tests/Bridge/Queue/MessageQueueThreadTests.cs @@ -0,0 +1,127 @@ +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using ReactNative.Bridge.Queue; +using System; +using System.Threading; +using System.Threading.Tasks; +using static ReactNative.Tests.DispatcherHelpers; + +namespace ReactNative.Tests.Bridge.Queue +{ + [TestClass] + public class MessageQueueThreadTests + { + [TestMethod] + public void MessageQueueThread_ArgumentChecks() + { + AssertEx.Throws( + () => MessageQueueThread.Create(null, ex => { }), + ex => Assert.AreEqual("spec", ex.ParamName)); + + AssertEx.Throws( + () => MessageQueueThread.Create(MessageQueueThreadSpec.MainUiThreadSpec, null), + ex => Assert.AreEqual("handler", ex.ParamName)); + } + + [TestMethod] + public void MessageQueueThread_CreateUiThread_ThrowsNotSupported() + { + AssertEx.Throws(() => MessageQueueThreadSpec.Create("ui", MessageQueueThreadKind.MainUi)); + } + + [TestMethod] + public async Task MessageQueueThread_IsOnThread() + { + var thrown = 0; + var uiThread = default(IMessageQueueThread); + await RunOnDispatcherAsync(() => uiThread = MessageQueueThread.Create(MessageQueueThreadSpec.MainUiThreadSpec, ex => thrown++)); + var backgroundThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("background", MessageQueueThreadKind.BackgroundSingleThread), ex => thrown++); + var taskPoolThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("any", MessageQueueThreadKind.BackgroundAnyThread), ex => thrown++); + + var queueThreads = new[] + { + uiThread, + backgroundThread, + taskPoolThread + }; + + var countdown = new CountdownEvent(queueThreads.Length); + foreach (var queueThread in queueThreads) + { + queueThread.RunOnQueue(() => + { + Assert.IsTrue(queueThread.IsOnThread()); + countdown.Signal(); + }); + } + + Assert.IsTrue(countdown.Wait(5000)); + Assert.AreEqual(0, thrown); + } + + [TestMethod] + public async Task MessageQueueThread_HandlesException() + { + var exception = new Exception(); + var countdown = new CountdownEvent(3); + var handler = new Action(ex => + { + Assert.AreSame(exception, ex); + countdown.Signal(); + }); + + var uiThread = default(IMessageQueueThread); + await RunOnDispatcherAsync(() => uiThread = MessageQueueThread.Create(MessageQueueThreadSpec.MainUiThreadSpec, handler)); + var backgroundThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("background", MessageQueueThreadKind.BackgroundSingleThread), handler); + var taskPoolThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("any", MessageQueueThreadKind.BackgroundAnyThread), handler); + + var queueThreads = new[] + { + uiThread, + backgroundThread, + taskPoolThread + }; + + foreach (var queueThread in queueThreads) + { + queueThread.RunOnQueue(() => { throw exception; }); + } + + Assert.IsTrue(countdown.Wait(5000)); + } + + [TestMethod] + public async Task MessageQueueThread_OneAtATime() + { + var uiThread = default(IMessageQueueThread); + await RunOnDispatcherAsync(() => uiThread = MessageQueueThread.Create(MessageQueueThreadSpec.MainUiThreadSpec, ex => { Assert.Fail(); })); + var backgroundThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("background", MessageQueueThreadKind.BackgroundSingleThread), ex => { Assert.Fail(); }); + var taskPoolThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("any", MessageQueueThreadKind.BackgroundAnyThread), ex => { Assert.Fail(); }); + + var enter = new AutoResetEvent(false); + var exit = new AutoResetEvent(false); + + var queueThreads = new[] + { + uiThread, + backgroundThread, + taskPoolThread + }; + + foreach (var queueThread in queueThreads) + { + var count = 10; + for (var i = 0; i < count; ++i) + { + queueThread.RunOnQueue(() => { enter.Set(); exit.WaitOne(); }); + } + + for (var i = 0; i < count; ++i) + { + Assert.IsTrue(enter.WaitOne()); + Assert.IsFalse(enter.WaitOne(100)); + exit.Set(); + } + } + } + } +} diff --git a/ReactWindows/ReactNative.Tests/Internal/DispatcherHelpers.cs b/ReactWindows/ReactNative.Tests/Internal/DispatcherHelpers.cs new file mode 100644 index 00000000000..4f4250d72e0 --- /dev/null +++ b/ReactWindows/ReactNative.Tests/Internal/DispatcherHelpers.cs @@ -0,0 +1,14 @@ +using System; +using System.Threading.Tasks; +using Windows.UI.Core; + +namespace ReactNative.Tests +{ + static class DispatcherHelpers + { + public static async Task RunOnDispatcherAsync(Action action) + { + await App.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, new DispatchedHandler(action)); + } + } +} diff --git a/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj b/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj index 50ff64b266a..d95927fbcfe 100644 --- a/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj +++ b/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj @@ -96,8 +96,10 @@ + + UnitTestApp.xaml diff --git a/ReactWindows/ReactNative.Tests/UnitTestApp.xaml.cs b/ReactWindows/ReactNative.Tests/UnitTestApp.xaml.cs index a69d5b08e6e..d13aa490bcf 100644 --- a/ReactWindows/ReactNative.Tests/UnitTestApp.xaml.cs +++ b/ReactWindows/ReactNative.Tests/UnitTestApp.xaml.cs @@ -7,6 +7,7 @@ using Windows.ApplicationModel.Activation; using Windows.Foundation; using Windows.Foundation.Collections; +using Windows.UI.Core; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; using Windows.UI.Xaml.Controls.Primitives; @@ -22,6 +23,8 @@ namespace ReactNative.Tests /// sealed partial class App : Application { + public static CoreDispatcher Dispatcher { get; private set; } + /// /// Initializes the singleton application object. This is the first line of authored code /// executed, and as such is the logical equivalent of main() or WinMain(). @@ -72,6 +75,8 @@ protected override void OnLaunched(LaunchActivatedEventArgs e) // Ensure the current window is active Window.Current.Activate(); + Dispatcher = CoreWindow.GetForCurrentThread().Dispatcher; + Microsoft.VisualStudio.TestPlatform.TestExecutor.UnitTestClient.Run(e.Arguments); } diff --git a/ReactWindows/ReactNative/Bridge/Queue/IQueueThreadExceptionHandler.cs b/ReactWindows/ReactNative/Bridge/Queue/IQueueThreadExceptionHandler.cs deleted file mode 100644 index 3b5d0c9f01b..00000000000 --- a/ReactWindows/ReactNative/Bridge/Queue/IQueueThreadExceptionHandler.cs +++ /dev/null @@ -1,9 +0,0 @@ -using System; - -namespace ReactNative.Bridge.Queue -{ - public interface IQueueThreadExceptionHandler - { - void HandleException(Exception ex); - } -} diff --git a/ReactWindows/ReactNative/Bridge/Queue/LimitedConcurrencyTaskScheduler.cs b/ReactWindows/ReactNative/Bridge/Queue/LimitedConcurrencyTaskScheduler.cs new file mode 100644 index 00000000000..1d2a193277e --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/Queue/LimitedConcurrencyTaskScheduler.cs @@ -0,0 +1,185 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Windows.System.Threading; + +namespace ReactNative.Bridge.Queue +{ + /// + /// Provides a task scheduler that ensures a maximum concurrency level while + /// running on top of the thread pool. + /// + class LimitedConcurrencyLevelTaskScheduler : TaskScheduler + { + // Indicates whether the current thread is processing work items. + [ThreadStatic] + private static bool _currentThreadIsProcessingItems; + + // The list of tasks to be executed + private readonly LinkedList _tasks = new LinkedList(); // protected by lock(_tasks) + + // The maximum concurrency level allowed by this scheduler. + private readonly int _maxDegreeOfParallelism; + + // Indicates whether the scheduler is currently processing work items. + private int _delegatesQueuedOrRunning = 0; + + /// + /// Creates a new instance with the specified degree of parallelism. + /// + /// The degrees of parallelism. + public LimitedConcurrencyLevelTaskScheduler(int maxDegreeOfParallelism) + { + if (maxDegreeOfParallelism < 1) + { + throw new ArgumentOutOfRangeException(nameof(maxDegreeOfParallelism)); + } + + _maxDegreeOfParallelism = maxDegreeOfParallelism; + } + + /// + /// Queues a task to the scheduler. + /// + /// The task to enqueue. + protected sealed override void QueueTask(Task task) + { + // Add the task to the list of tasks to be processed. If there aren't enough + // delegates currently queued or running to process tasks, schedule another. + lock (_tasks) + { + _tasks.AddLast(task); + if (_delegatesQueuedOrRunning < _maxDegreeOfParallelism) + { + ++_delegatesQueuedOrRunning; + NotifyThreadPoolOfPendingWork(); + } + } + } + + /// + /// Inform the ThreadPool that there's work to be executed for this scheduler. + /// + private async void NotifyThreadPoolOfPendingWork() + { + await ThreadPool.RunAsync(_ => + { + // Note that the current thread is now processing work items. + // This is necessary to enable inlining of tasks into this thread. + _currentThreadIsProcessingItems = true; + + try + { + // Process all available items in the queue. + while (true) + { + Task item; + lock (_tasks) + { + // When there are no more items to be processed, + // note that we're done processing, and get out. + if (_tasks.Count == 0) + { + --_delegatesQueuedOrRunning; + break; + } + + // Get the next item from the queue + item = _tasks.First.Value; + _tasks.RemoveFirst(); + } + + // Execute the task we pulled out of the queue + TryExecuteTask(item); + } + } + // We're done processing items on the current thread + finally + { + _currentThreadIsProcessingItems = false; + } + }, + WorkItemPriority.Normal); + } + + /// + /// Attempts to execute the specified task on the current thread. + /// + /// The task to execute. + /// Task queue flag. + /// An indicator if the task was executed inline. + protected sealed override bool TryExecuteTaskInline(Task task, bool taskWasPreviouslyQueued) + { + // If this thread isn't already processing a task, we don't support inlining + if (!_currentThreadIsProcessingItems) + { + return false; + } + + // If the task was previously queued, remove it from the queue + if (taskWasPreviouslyQueued) + { + // Try to run the task. + if (TryDequeue(task)) + { + return TryExecuteTask(task); + } + else + { + return false; + } + } + else + { + return TryExecuteTask(task); + } + } + + /// + /// Attempt to remove a previously scheduled task from the scheduler. + /// + /// The task to dequeue. + /// An indicator if the task was dequeued. + protected sealed override bool TryDequeue(Task task) + { + lock (_tasks) + { + return _tasks.Remove(task); + } + } + + /// + /// Gets the maximum concurrency level supported by this scheduler. + /// + public sealed override int MaximumConcurrencyLevel { get { return _maxDegreeOfParallelism; } } + + /// + /// Gets an enumerable of the tasks currently scheduled on this scheduler. + /// + /// + protected sealed override IEnumerable GetScheduledTasks() + { + bool lockTaken = false; + try + { + Monitor.TryEnter(_tasks, ref lockTaken); + if (lockTaken) + { + return _tasks; + } + else + { + throw new NotSupportedException(); + } + } + finally + { + if (lockTaken) + { + Monitor.Exit(_tasks); + } + } + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs index 268b75a6c34..740e7c0e77d 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs @@ -2,37 +2,89 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Text; +using System.Reactive.Linq; +using System.Reactive.Subjects; +using System.Threading; using System.Threading.Tasks; +using Windows.Foundation; using Windows.System.Threading; +using Windows.UI.Core; namespace ReactNative.Bridge.Queue { - public abstract class MessageQueueThread : IMessageQueueThread + public abstract class MessageQueueThread : IMessageQueueThread, IDisposable { - protected readonly ConcurrentQueue _runOnQueueQueue = - new ConcurrentQueue(); + private bool _disposed; - public abstract bool IsOnThread(); + private MessageQueueThread() { } + + protected bool IsDisposed + { + get + { + return _disposed; + } + } + + public bool IsOnThread() + { + AssertNotDisposed(); + + return IsOnThreadCore(); + } public void RunOnQueue(Action action) { - _runOnQueueQueue.Enqueue(action); + if (action == null) + throw new ArgumentNullException(nameof(action)); + + AssertNotDisposed(); + + Enqueue(action); } - public abstract void Start(); + public void Dispose() + { + Dispose(true); + } + + protected abstract void Enqueue(Action action); + + protected abstract bool IsOnThreadCore(); - public MessageQueueThread Create( + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + _disposed = true; + } + } + + private void AssertNotDisposed() + { + if (_disposed) + { + throw new ObjectDisposedException("this"); + } + } + + public static MessageQueueThread Create( MessageQueueThreadSpec spec, - IQueueThreadExceptionHandler handler) + Action handler) { + if (spec == null) + throw new ArgumentNullException(nameof(spec)); + if (handler == null) + throw new ArgumentNullException(nameof(handler)); + switch (spec.Kind) { - //case MessageQueueThreadKind.MainUi: - // return new DispatcherMessageQueueThread(name, handler); - //case MessageQueueThreadKind.NewBackground: - // return new BackgroundMessageQueueThread(name, handler); + case MessageQueueThreadKind.MainUi: + return new DispatcherMessageQueueThread(spec.Name, handler); + case MessageQueueThreadKind.BackgroundSingleThread: + return new SingleBackgroundMessageQueueThread(spec.Name, handler); + case MessageQueueThreadKind.BackgroundAnyThread: + return new AnyBackgroundMessageQueueThread(spec.Name, handler); default: throw new InvalidOperationException( string.Format( @@ -42,5 +94,148 @@ public MessageQueueThread Create( spec.Name)); } } + + class DispatcherMessageQueueThread : MessageQueueThread + { + private readonly string _name; + private readonly Subject _actionObservable; + private readonly IDisposable _subscription; + + public DispatcherMessageQueueThread(string name, Action handler) + { + _name = name; + _actionObservable = new Subject(); + _subscription = _actionObservable + .ObserveOnDispatcher() + .Subscribe(action => + { + try + { + action(); + } + catch (Exception ex) + { + handler(ex); + } + }); + } + + protected override void Enqueue(Action action) + { + _actionObservable.OnNext(action); + } + + protected override bool IsOnThreadCore() + { + return CoreWindow.GetForCurrentThread().Dispatcher != null; + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + _actionObservable.Dispose(); + _subscription.Dispose(); + } + } + + class SingleBackgroundMessageQueueThread : MessageQueueThread + { + private static readonly Action s_canary = new Action(() => { }); + + private readonly string _name; + private readonly Action _handler; + private readonly BlockingCollection _queue; + private readonly ThreadLocal _indicator; + private readonly IAsyncAction _asyncAction; + + public SingleBackgroundMessageQueueThread(string name, Action handler) + { + _name = name; + _handler = handler; + _queue = new BlockingCollection(); + _indicator = new ThreadLocal(); + _asyncAction = ThreadPool.RunAsync(_ => + { + _indicator.Value = true; + Run(); + }, + WorkItemPriority.Normal); + } + + protected override bool IsOnThreadCore() + { + return _indicator.Value; + } + + protected override void Enqueue(Action action) + { + _queue.Add(action); + } + + protected override void Dispose(bool disposing) + { + base.Dispose(disposing); + + // Unblock the background thread. + Enqueue(s_canary); + } + + private void Run() + { + while (true) + { + var action = _queue.Take(); + if (IsDisposed) + { + break; + } + + try + { + action(); + } + catch (Exception ex) + { + _handler(ex); + } + } + } + } + + class AnyBackgroundMessageQueueThread : MessageQueueThread + { + private readonly string _name; + private readonly Action _handler; + private readonly TaskScheduler _taskScheduler; + private readonly TaskFactory _taskFactory; + + public AnyBackgroundMessageQueueThread(string name, Action handler) + { + _name = name; + _handler = handler; + _taskScheduler = new LimitedConcurrencyLevelTaskScheduler(1); + _taskFactory = new TaskFactory(_taskScheduler); + } + + protected override async void Enqueue(Action action) + { + await _taskFactory.StartNew(() => + { + try + { + action(); + } + catch (Exception ex) + { + _handler(ex); + } + }); + } + + protected override bool IsOnThreadCore() + { + return TaskScheduler.Current == _taskScheduler; + } + } } } diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs index 602c265e167..8863a0f0ee2 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs @@ -21,18 +21,24 @@ public static void AssertIsOnThread(this IMessageQueueThread actionQueue) } } + /// + /// Calls a function on a message queue and returns a task to await the response. + /// + /// Type of response. + /// The message queue thread. + /// The function. + /// A task to await the result. public static Task CallOnQueue(this IMessageQueueThread actionQueue, Func func) { var taskCompletionSource = new TaskCompletionSource(); - actionQueue.RunOnQueue(() => + actionQueue.RunOnQueue(async () => { var result = func(); // TaskCompletionSource.SetResult can call continuations // on the awaiter of the task completion source. - // TODO: Prevent such thread stealing. - taskCompletionSource.SetResult(result); + await Task.Run(() => taskCompletionSource.SetResult(result)); }); return taskCompletionSource.Task; diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs index a3dc64d4a96..9a6c555ad2c 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs @@ -1,9 +1,10 @@ namespace ReactNative.Bridge.Queue { - enum MessageQueueThreadKind + public enum MessageQueueThreadKind { MainUi, - NewBackground, + BackgroundSingleThread, + BackgroundAnyThread, } } diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs index c2b0aba371f..56e8f9c7d78 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ReactNative.Bridge.Queue { @@ -20,9 +16,14 @@ private MessageQueueThreadSpec(MessageQueueThreadKind kind, string name) public static MessageQueueThreadSpec MainUiThreadSpec { get; } = new MessageQueueThreadSpec(MessageQueueThreadKind.MainUi, "main_ui"); - public static MessageQueueThreadSpec Create(string name) + public static MessageQueueThreadSpec Create(string name, MessageQueueThreadKind kind) { - return new MessageQueueThreadSpec(MessageQueueThreadKind.NewBackground, name); + if (kind == MessageQueueThreadKind.MainUi) + { + throw new NotSupportedException("Use the singleton MainUiThreadSpec instance."); + } + + return new MessageQueueThreadSpec(kind, name); } } } diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index 49a1ca0b7c7..c7ecfdec9b1 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -118,7 +118,7 @@ - + diff --git a/ReactWindows/ReactNative/project.json b/ReactWindows/ReactNative/project.json index f93afb36e29..f94633cebfe 100644 --- a/ReactWindows/ReactNative/project.json +++ b/ReactWindows/ReactNative/project.json @@ -1,7 +1,8 @@ { "dependencies": { "Microsoft.NETCore.UniversalWindowsPlatform": "5.0.0", - "Newtonsoft.Json": "7.0.1" + "Newtonsoft.Json": "7.0.1", + "Rx-Xaml": "2.2.5" }, "frameworks": { "uap10.0": {} From aa3aaeca8bc701e7cfd32527d28adf9397e334c6 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Wed, 16 Dec 2015 10:09:00 -0500 Subject: [PATCH 09/17] Adding missing files from .csproj. --- ReactWindows/ReactNative/ReactNative.csproj | 2 ++ 1 file changed, 2 insertions(+) diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index c7ecfdec9b1..693349aca4f 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -110,6 +110,7 @@ + @@ -151,6 +152,7 @@ + From 144f140eef71c70261f144f802fb8908bdb232b1 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Wed, 16 Dec 2015 10:18:10 -0500 Subject: [PATCH 10/17] Deleting unused stub files. --- .../ReactNative/Bridge/ICSharpJSExecutor.cs | 12 ------- .../ReactNative/Bridge/JavaScriptExecuter.cs | 31 ------------------- .../Bridge/ProxyJavaScriptExecutor.cs | 9 ------ ReactWindows/ReactNative/ReactNative.csproj | 7 +---- 4 files changed, 1 insertion(+), 58 deletions(-) delete mode 100644 ReactWindows/ReactNative/Bridge/ICSharpJSExecutor.cs delete mode 100644 ReactWindows/ReactNative/Bridge/JavaScriptExecuter.cs delete mode 100644 ReactWindows/ReactNative/Bridge/ProxyJavaScriptExecutor.cs diff --git a/ReactWindows/ReactNative/Bridge/ICSharpJSExecutor.cs b/ReactWindows/ReactNative/Bridge/ICSharpJSExecutor.cs deleted file mode 100644 index eff8b90ad17..00000000000 --- a/ReactWindows/ReactNative/Bridge/ICSharpJSExecutor.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace ReactNative.Bridge -{ - public interface ICSharpJSExecutor - { - void close(); - - void executeApplicationScript(string script, string sourceURL); - - string executeJSCall(string methodName, string jsonArgsArray); - - } -} diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptExecuter.cs b/ReactWindows/ReactNative/Bridge/JavaScriptExecuter.cs deleted file mode 100644 index ac3cd4f4a72..00000000000 --- a/ReactWindows/ReactNative/Bridge/JavaScriptExecuter.cs +++ /dev/null @@ -1,31 +0,0 @@ - -namespace ReactNative.Bridge -{ - /// - /// Abstract class responsible for brokering communication between native and React Naitve components - /// - public abstract class JavaScriptExecuter - { - private ICSharpJSExecutor mJSExecutor; - - /// - /// Sets the JS executor - /// - /// - public JavaScriptExecuter(ICSharpJSExecutor executor) - { - mJSExecutor = executor; - } - - /// - /// Closes the executor - /// - public abstract void close(); - - /// - /// Instantiates the executor - /// - /// - public abstract void initialize(ICSharpJSExecutor executor); - } -} diff --git a/ReactWindows/ReactNative/Bridge/ProxyJavaScriptExecutor.cs b/ReactWindows/ReactNative/Bridge/ProxyJavaScriptExecutor.cs deleted file mode 100644 index 2b4fcd41e41..00000000000 --- a/ReactWindows/ReactNative/Bridge/ProxyJavaScriptExecutor.cs +++ /dev/null @@ -1,9 +0,0 @@ - -namespace ReactNative.Bridge.Executors -{ - public class ProxyJavaScriptExecutor : ICSharpJSExecutor - { - - - } -} diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index 693349aca4f..9447c2911ef 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -108,10 +108,7 @@ - - - @@ -159,9 +156,7 @@ - - - + 14.0 From 5b3739da66abcfeb9c43df46e0dbe1607a0e8f76 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Thu, 17 Dec 2015 00:10:17 -0500 Subject: [PATCH 11/17] Adds support for JavaScriptModules and configuration writer. One limitation of the .NET framework is that there is no equivalent of the Proxy class in Java that is capable of instantiating interfaces and specifying an invocation handler. Instead of using interfaces in the way they were used in the Android React implementation, this implementation just uses a base class and a settable property for an invocation handler. The configuration writer is straight-forward, it just uses the JsonWriter from Newtonsoft.Json. --- ReactWindows/Playground/MainPage.xaml.cs | 2 +- .../Bridge/JavaScriptModulesConfigTests.cs | 88 +++++++++ .../Bridge/NativeModuleBaseTests.cs | 6 +- .../Bridge/Queue/MessageQueueThreadTests.cs | 10 +- .../ReactNative.Tests.csproj | 1 + .../ReactNative/Bridge/CatalystInstance.cs | 185 ++++++++++++++++-- .../ReactNative/Bridge/DispatcherHelpers.cs | 16 ++ .../ReactNative/Bridge/ICatalystInstance.cs | 5 +- .../ReactNative/Bridge/IInvokeHandler.cs | 7 + .../ReactNative/Bridge/IJavaScriptExecutor.cs | 7 + .../ReactNative/Bridge/IJavaScriptModule.cs | 7 + .../ReactNative/Bridge/INativeModule.cs | 2 +- .../ReactNative/Bridge/IReactBridge.cs | 3 +- .../ReactNative/Bridge/IReactCallback.cs | 11 ++ .../Bridge/JavaScriptModuleBase.cs | 32 +++ .../Bridge/JavaScriptModuleRegistration.cs | 99 ++++++++++ .../Bridge/JavaScriptModuleRegistry.cs | 66 +++++++ .../Bridge/JavaScriptModulesConfig.cs | 61 ++++++ .../ReactNative/Bridge/NativeModuleBase.cs | 4 +- .../Bridge/NativeModuleRegistry.cs | 97 ++++++++- .../Queue/CatalystQueueConfiguration.cs | 79 ++++++++ .../Queue/CatalystQueueConfigurationSpec.cs | 84 ++++++++ .../Queue/ICatalystQueueConfiguration.cs | 15 ++ .../Bridge/Queue/MessageQueueThread.cs | 2 +- .../Bridge/Queue/MessageQueueThreadKind.cs | 2 +- .../Bridge/Queue/MessageQueueThreadSpec.cs | 4 +- .../ReactNative/Bridge/ReactBridge.cs | 43 ++++ .../ReactNative/Common/ReactConstants.cs | 7 + .../Modules/Core/RCTNativeAppEventEmitter.cs | 7 + ReactWindows/ReactNative/ReactNative.csproj | 17 +- .../ReactNative/Tracing/EventSourceManager.cs | 9 + .../ReactNative/Tracing/TraceDisposable.cs | 3 +- ReactWindows/ReactNative/Tracing/Tracer.cs | 5 + .../ReactNative/UIManager/AppRegistry.cs | 18 ++ .../UIManager/Events/RCTEventEmitter.cs | 18 ++ 35 files changed, 982 insertions(+), 40 deletions(-) create mode 100644 ReactWindows/ReactNative.Tests/Bridge/JavaScriptModulesConfigTests.cs create mode 100644 ReactWindows/ReactNative/Bridge/DispatcherHelpers.cs create mode 100644 ReactWindows/ReactNative/Bridge/IInvokeHandler.cs create mode 100644 ReactWindows/ReactNative/Bridge/IJavaScriptExecutor.cs create mode 100644 ReactWindows/ReactNative/Bridge/IJavaScriptModule.cs create mode 100644 ReactWindows/ReactNative/Bridge/IReactCallback.cs create mode 100644 ReactWindows/ReactNative/Bridge/JavaScriptModuleBase.cs create mode 100644 ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs create mode 100644 ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistry.cs create mode 100644 ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs create mode 100644 ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfiguration.cs create mode 100644 ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfigurationSpec.cs create mode 100644 ReactWindows/ReactNative/Bridge/Queue/ICatalystQueueConfiguration.cs create mode 100644 ReactWindows/ReactNative/Bridge/ReactBridge.cs create mode 100644 ReactWindows/ReactNative/Common/ReactConstants.cs create mode 100644 ReactWindows/ReactNative/Modules/Core/RCTNativeAppEventEmitter.cs create mode 100644 ReactWindows/ReactNative/Tracing/EventSourceManager.cs create mode 100644 ReactWindows/ReactNative/UIManager/AppRegistry.cs create mode 100644 ReactWindows/ReactNative/UIManager/Events/RCTEventEmitter.cs diff --git a/ReactWindows/Playground/MainPage.xaml.cs b/ReactWindows/Playground/MainPage.xaml.cs index c4d289d619e..1189daa7f1b 100644 --- a/ReactWindows/Playground/MainPage.xaml.cs +++ b/ReactWindows/Playground/MainPage.xaml.cs @@ -39,7 +39,7 @@ protected override void OnNavigatedTo(NavigationEventArgs e) } catch (Exception ex) { - + } } } diff --git a/ReactWindows/ReactNative.Tests/Bridge/JavaScriptModulesConfigTests.cs b/ReactWindows/ReactNative.Tests/Bridge/JavaScriptModulesConfigTests.cs new file mode 100644 index 00000000000..9a663f496c3 --- /dev/null +++ b/ReactWindows/ReactNative.Tests/Bridge/JavaScriptModulesConfigTests.cs @@ -0,0 +1,88 @@ +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using ReactNative.Bridge; +using System; +using System.Collections.Generic; +using System.IO; + +namespace ReactNative.Tests.Bridge +{ + [TestClass] + public class JavaScriptModulesConfigTests + { + [TestMethod] + public void JavaScriptModulesConfig_MethodOverrides_ThrowsNotSupported() + { + var builder = new JavaScriptModulesConfig.Builder(); + AssertEx.Throws(() => builder.Add()); + } + + [TestMethod] + public void JavaScriptModulesConfig_WriteModuleDefinitions() + { + var builder = new JavaScriptModulesConfig.Builder(); + builder.Add(); + var config = builder.Build(); + + using (var stringWriter = new StringWriter()) + { + using (var writer = new JsonTextWriter(stringWriter)) + { + config.WriteModuleDescriptions(writer); + } + + var actual = stringWriter.ToString(); + var expected = JObject.FromObject( + new Map + { + { + "ITestJavaScriptModule", + new Map + { + { "moduleID", 0 }, + { + "methods", + new Map + { + { + "Bar", + new Map + { + { "methodID", 0 }, + } + }, + { + "Foo", + new Map + { + { "methodID", 1 }, + } + } + } + } + } + } + } + ).ToString(Formatting.None); + + Assert.AreEqual(expected, actual); + } + } + } + + public class Map : Dictionary { } + + public interface IOverridesJavaScriptModule : IJavaScriptModule + { + void Foo(); + + void Foo(int x); + } + + public interface ITestJavaScriptModule : IJavaScriptModule + { + void Bar(); + void Foo(); + } +} diff --git a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs index 0f3a8d42088..3aceefe928d 100644 --- a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs +++ b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs @@ -4,6 +4,7 @@ using Newtonsoft.Json.Linq; using ReactNative.Bridge; using System.Linq; +using System.Threading.Tasks; namespace ReactNative.Tests.Bridge { @@ -242,7 +243,7 @@ public TestCatalystInstance(Action callback) _callback = callback; } - public ICollection NativeModules + public IEnumerable NativeModules { get { @@ -255,8 +256,9 @@ public T GetNativeModule(Type nativeModuleInterface) where T : INativeModule throw new NotImplementedException(); } - public void Initialize() + public Task InitializeAsync() { + return Task.FromResult(true); } public void InvokeCallback(int callbackId, JArray arguments) diff --git a/ReactWindows/ReactNative.Tests/Bridge/Queue/MessageQueueThreadTests.cs b/ReactWindows/ReactNative.Tests/Bridge/Queue/MessageQueueThreadTests.cs index e51c23d86f7..4bd3e2b6c9e 100644 --- a/ReactWindows/ReactNative.Tests/Bridge/Queue/MessageQueueThreadTests.cs +++ b/ReactWindows/ReactNative.Tests/Bridge/Queue/MessageQueueThreadTests.cs @@ -18,14 +18,14 @@ public void MessageQueueThread_ArgumentChecks() ex => Assert.AreEqual("spec", ex.ParamName)); AssertEx.Throws( - () => MessageQueueThread.Create(MessageQueueThreadSpec.MainUiThreadSpec, null), + () => MessageQueueThread.Create(MessageQueueThreadSpec.DispatcherThreadSpec, null), ex => Assert.AreEqual("handler", ex.ParamName)); } [TestMethod] public void MessageQueueThread_CreateUiThread_ThrowsNotSupported() { - AssertEx.Throws(() => MessageQueueThreadSpec.Create("ui", MessageQueueThreadKind.MainUi)); + AssertEx.Throws(() => MessageQueueThreadSpec.Create("ui", MessageQueueThreadKind.DispatcherThread)); } [TestMethod] @@ -33,7 +33,7 @@ public async Task MessageQueueThread_IsOnThread() { var thrown = 0; var uiThread = default(IMessageQueueThread); - await RunOnDispatcherAsync(() => uiThread = MessageQueueThread.Create(MessageQueueThreadSpec.MainUiThreadSpec, ex => thrown++)); + await RunOnDispatcherAsync(() => uiThread = MessageQueueThread.Create(MessageQueueThreadSpec.DispatcherThreadSpec, ex => thrown++)); var backgroundThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("background", MessageQueueThreadKind.BackgroundSingleThread), ex => thrown++); var taskPoolThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("any", MessageQueueThreadKind.BackgroundAnyThread), ex => thrown++); @@ -70,7 +70,7 @@ public async Task MessageQueueThread_HandlesException() }); var uiThread = default(IMessageQueueThread); - await RunOnDispatcherAsync(() => uiThread = MessageQueueThread.Create(MessageQueueThreadSpec.MainUiThreadSpec, handler)); + await RunOnDispatcherAsync(() => uiThread = MessageQueueThread.Create(MessageQueueThreadSpec.DispatcherThreadSpec, handler)); var backgroundThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("background", MessageQueueThreadKind.BackgroundSingleThread), handler); var taskPoolThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("any", MessageQueueThreadKind.BackgroundAnyThread), handler); @@ -93,7 +93,7 @@ public async Task MessageQueueThread_HandlesException() public async Task MessageQueueThread_OneAtATime() { var uiThread = default(IMessageQueueThread); - await RunOnDispatcherAsync(() => uiThread = MessageQueueThread.Create(MessageQueueThreadSpec.MainUiThreadSpec, ex => { Assert.Fail(); })); + await RunOnDispatcherAsync(() => uiThread = MessageQueueThread.Create(MessageQueueThreadSpec.DispatcherThreadSpec, ex => { Assert.Fail(); })); var backgroundThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("background", MessageQueueThreadKind.BackgroundSingleThread), ex => { Assert.Fail(); }); var taskPoolThread = MessageQueueThread.Create(MessageQueueThreadSpec.Create("any", MessageQueueThreadKind.BackgroundAnyThread), ex => { Assert.Fail(); }); diff --git a/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj b/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj index d95927fbcfe..83208716dc1 100644 --- a/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj +++ b/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj @@ -95,6 +95,7 @@ + diff --git a/ReactWindows/ReactNative/Bridge/CatalystInstance.cs b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs index 7ca20165984..4612017f911 100644 --- a/ReactWindows/ReactNative/Bridge/CatalystInstance.cs +++ b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs @@ -1,40 +1,203 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Newtonsoft.Json.Linq; +using ReactNative.Bridge.Queue; +using ReactNative.Tracing; +using ReactNative.Common; +using System.Threading; +using System.Threading.Tasks; +using Newtonsoft.Json; +using System.IO; namespace ReactNative.Bridge { - class CatalystInstance : ICatalystInstance + class CatalystInstance : ICatalystInstance, IDisposable { - public CatalystInstance(NativeModuleRegistry registry) + private readonly NativeModuleRegistry _registry; + private readonly CatalystQueueConfiguration _catalystQueueConfiguration; + private readonly IJavaScriptExecutor _jsExecutor; + + private readonly JavaScriptModulesConfig _jsModulesConfig; + private IReactBridge _bridge; + + private bool _initialized; + private bool _disposed; + + public CatalystInstance( + CatalystQueueConfigurationSpec catalystQueueConfigurationSpec, + IJavaScriptExecutor jsExecutor, + NativeModuleRegistry registry, + JavaScriptModulesConfig jsModulesConfig) { + _registry = registry; + _jsExecutor = jsExecutor; + _jsModulesConfig = jsModulesConfig; + _catalystQueueConfiguration = CatalystQueueConfiguration.Create( + catalystQueueConfigurationSpec, + HandleException); } - public ICollection NativeModules + public IEnumerable NativeModules { get { - throw new NotImplementedException(); + return _registry.Modules; } } public T GetNativeModule(Type nativeModuleInterface) where T : INativeModule { - throw new NotImplementedException(); + return _registry.GetModule(); } - public void Initialize() + public async Task InitializeAsync() { - throw new NotImplementedException(); + DispatcherHelpers.AssertOnDispatcher(); + if (_initialized) + { + throw new InvalidOperationException("This catalyst instance has already been initialized."); + } + + await InitializeBridgeAsync(); + + _initialized = true; + _registry.NotifyCatalystInstanceInitialize(); } public void InvokeCallback(int callbackId, JArray arguments) { - throw new NotImplementedException(); + if (_disposed) + { + Tracer.Write(ReactConstants.Tag, "Invoking JS callback after bridge has been destroyed."); + return; + } + + _catalystQueueConfiguration.JSQueueThread.RunOnQueue(() => + { + _catalystQueueConfiguration.JSQueueThread.AssertIsOnThread(); + if (_disposed) + { + return; + } + + using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "")) + { + _bridge.InvokeCallback(callbackId, arguments); + } + }); + } + + public void Dispose() + { + DispatcherHelpers.AssertOnDispatcher(); + + if (_disposed) + { + return; + } + + _disposed = true; + _registry.NotifyCatalystInstanceDispose(); + _catalystQueueConfiguration.Dispose(); + // TODO: notify bridge idle listeners + _bridge.Dispose(); + } + + internal void InvokeFunction(int moduleId, int methodId, JArray arguments, string tracingName) + { + _catalystQueueConfiguration.JSQueueThread.RunOnQueue(() => + { + _catalystQueueConfiguration.JSQueueThread.AssertIsOnThread(); + + if (_disposed) + { + return; + } + + using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, tracingName)) + { + if (_bridge == null) + { + throw new InvalidOperationException("Bridge has not been initialized."); + } + + _bridge.CallFunction(moduleId, methodId, arguments); + } + }); + } + + private Task InitializeBridgeAsync() + { + return _catalystQueueConfiguration.JSQueueThread.CallOnQueue(() => + { + _catalystQueueConfiguration.JSQueueThread.AssertIsOnThread(); + + using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "ReactBridgeCtor")) + { + _bridge = new ReactBridge( + _jsExecutor, + new NativeModulesReactCallback(this), + _catalystQueueConfiguration.NativeModulesQueueThread); + } + + using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "setBatchedBridgeConfig")) + { + _bridge.SetGlobalVariable("__fbBatchedBridgeConfig", BuildModulesConfig()); + } + + return _bridge; + }); + } + + private string BuildModulesConfig() + { + using (var stringWriter = new StringWriter()) + { + using (var writer = new JsonTextWriter(stringWriter)) + { + writer.WriteStartObject(); + writer.WritePropertyName("remoteModuleConfig"); + _registry.WriteModuleDescriptions(writer); + writer.WritePropertyName("localModulesConfig"); + _jsModulesConfig.WriteModuleDescriptions(writer); + writer.WriteEndObject(); + } + + return stringWriter.ToString(); + } + } + + private void HandleException(Exception ex) + { + // TODO + } + + class NativeModulesReactCallback : IReactCallback + { + private readonly CatalystInstance _parent; + + public NativeModulesReactCallback(CatalystInstance parent) + { + _parent = parent; + } + + public void Invoke(int moduleId, int methodId, JArray parameters) + { + _parent._catalystQueueConfiguration.NativeModulesQueueThread.AssertIsOnThread(); + + if (_parent._disposed) + { + return; + } + + _parent._registry.Invoke(_parent, moduleId, methodId, parameters); + } + + public void OnBatchComplete() + { + _parent._catalystQueueConfiguration.NativeModulesQueueThread.AssertIsOnThread(); + } } } } diff --git a/ReactWindows/ReactNative/Bridge/DispatcherHelpers.cs b/ReactWindows/ReactNative/Bridge/DispatcherHelpers.cs new file mode 100644 index 00000000000..76720a13005 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/DispatcherHelpers.cs @@ -0,0 +1,16 @@ +using System; +using Windows.UI.Core; + +namespace ReactNative.Bridge +{ + static class DispatcherHelpers + { + public static void AssertOnDispatcher() + { + if (CoreWindow.GetForCurrentThread().Dispatcher != null) + { + throw new InvalidOperationException("Thread does not have dispatcher access."); + } + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs b/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs index 0d095686912..da2d9d79177 100644 --- a/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs +++ b/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs @@ -1,16 +1,17 @@ using Newtonsoft.Json.Linq; using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace ReactNative.Bridge { public interface ICatalystInstance { - ICollection NativeModules { get; } + IEnumerable NativeModules { get; } void InvokeCallback(int callbackId, JArray arguments); - void Initialize(); + Task InitializeAsync(); T GetNativeModule(Type nativeModuleInterface) where T : INativeModule; } diff --git a/ReactWindows/ReactNative/Bridge/IInvokeHandler.cs b/ReactWindows/ReactNative/Bridge/IInvokeHandler.cs new file mode 100644 index 00000000000..63afff4606a --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/IInvokeHandler.cs @@ -0,0 +1,7 @@ +namespace ReactNative.Bridge +{ + public interface IInvokeHandler + { + void Invoke(string name, object[] args); + } +} diff --git a/ReactWindows/ReactNative/Bridge/IJavaScriptExecutor.cs b/ReactWindows/ReactNative/Bridge/IJavaScriptExecutor.cs new file mode 100644 index 00000000000..0c8687fa5fa --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/IJavaScriptExecutor.cs @@ -0,0 +1,7 @@ +namespace ReactNative.Bridge +{ + public interface IJavaScriptExecutor + { + // TODO + } +} diff --git a/ReactWindows/ReactNative/Bridge/IJavaScriptModule.cs b/ReactWindows/ReactNative/Bridge/IJavaScriptModule.cs new file mode 100644 index 00000000000..50a3894a24b --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/IJavaScriptModule.cs @@ -0,0 +1,7 @@ +namespace ReactNative.Bridge +{ + public interface IJavaScriptModule + { + IInvokeHandler InvokeHandler { set; } + } +} diff --git a/ReactWindows/ReactNative/Bridge/INativeModule.cs b/ReactWindows/ReactNative/Bridge/INativeModule.cs index bba0c51b886..f89d63be001 100644 --- a/ReactWindows/ReactNative/Bridge/INativeModule.cs +++ b/ReactWindows/ReactNative/Bridge/INativeModule.cs @@ -14,6 +14,6 @@ public interface INativeModule void Initialize(); - void OnCatalystInstanceDestroy(); + void OnCatalystInstanceDispose(); } } diff --git a/ReactWindows/ReactNative/Bridge/IReactBridge.cs b/ReactWindows/ReactNative/Bridge/IReactBridge.cs index 38f6499cd40..0a3a9f8c317 100644 --- a/ReactWindows/ReactNative/Bridge/IReactBridge.cs +++ b/ReactWindows/ReactNative/Bridge/IReactBridge.cs @@ -1,8 +1,9 @@ using Newtonsoft.Json.Linq; +using System; namespace ReactNative.Bridge { - public interface IReactBridge + public interface IReactBridge : IDisposable { void CallFunction(int moduleId, int methodId, JArray arguments); diff --git a/ReactWindows/ReactNative/Bridge/IReactCallback.cs b/ReactWindows/ReactNative/Bridge/IReactCallback.cs new file mode 100644 index 00000000000..72689329c5b --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/IReactCallback.cs @@ -0,0 +1,11 @@ +using Newtonsoft.Json.Linq; + +namespace ReactNative.Bridge +{ + public interface IReactCallback + { + void Invoke(int moduleId, int methodId, JArray parameters); + + void OnBatchComplete(); + } +} diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptModuleBase.cs b/ReactWindows/ReactNative/Bridge/JavaScriptModuleBase.cs new file mode 100644 index 00000000000..62d99913ffc --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/JavaScriptModuleBase.cs @@ -0,0 +1,32 @@ +using System; + +namespace ReactNative.Bridge +{ + public abstract class JavaScriptModuleBase : IJavaScriptModule + { + private IInvokeHandler _invokeHandler; + + public IInvokeHandler InvokeHandler + { + set + { + if (_invokeHandler != null) + { + throw new InvalidOperationException("InvokeHandler set more than once."); + } + + _invokeHandler = value; + } + } + + protected void Invoke(string name, params object[] args) + { + if (_invokeHandler == null) + { + throw new InvalidOperationException("InvokeHandler has not been set."); + } + + _invokeHandler.Invoke(name, args); + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs new file mode 100644 index 00000000000..6318f77a4a8 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; + +namespace ReactNative.Bridge +{ + internal class JavaScriptModuleRegistration + { + private readonly IDictionary _methodsToIds; + private readonly IDictionary _methodsToTracingStrings; + + public JavaScriptModuleRegistration(int moduleId, Type moduleInterface) + { + ModuleId = moduleId; + ModuleInterface = moduleInterface; + + var methods = moduleInterface.GetMethods(); + var methodNames = new string[methods.Length]; + for (var i = 0; i < methods.Length; ++i) + { + methodNames[i] = methods[i].Name; + } + + Array.Sort(methodNames, Comparer.Create((s1, s2) => s1.CompareTo(s2))); + + _methodsToIds = new Dictionary(methods.Length); + _methodsToTracingStrings = new Dictionary(methods.Length); + + InitializeMethodTables(methodNames); + } + + public int ModuleId { get; } + + public Type ModuleInterface { get; } + + public string Name + { + get + { + return ModuleInterface.Name; + } + } + + public IEnumerable Methods + { + get + { + return _methodsToIds.Keys; + } + } + + public int GetMethodId(string method) + { + var idx = default(int); + if (!_methodsToIds.TryGetValue(method, out idx)) + { + throw new InvalidOperationException("Unknown method: " + method); + } + + return idx; + } + + public string GetTracingName(string method) + { + var name = default(string); + if (!_methodsToTracingStrings.TryGetValue(method, out name)) + { + throw new InvalidOperationException("Unknown method: " + method); + } + + return name; + } + + private void InitializeMethodTables(string[] methods) + { + var lastMethod = default(string); + for (var i = 0; i < methods.Length; ++i) + { + var method = methods[i]; + if (method == lastMethod) + { + throw new NotSupportedException( + string.Format( + CultureInfo.InvariantCulture, + "Method overloading is not supported: {0}.{1}", + ModuleInterface.Name, + method)); + } + + lastMethod = method; + _methodsToIds.Add(method, i); + _methodsToTracingStrings.Add(method, "JSCall__" + Name + "_" + method); + } + } + } +} \ No newline at end of file diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistry.cs b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistry.cs new file mode 100644 index 00000000000..4b5ad0e5439 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistry.cs @@ -0,0 +1,66 @@ +using Newtonsoft.Json.Linq; +using System; +using System.Collections.Generic; +using System.Globalization; + +namespace ReactNative.Bridge +{ + class JavaScriptModuleRegistry + { + private readonly IDictionary _moduleInstances; + + public JavaScriptModuleRegistry( + CatalystInstance catalystInstance, + JavaScriptModulesConfig config) + { + _moduleInstances = new Dictionary(config.ModuleDefinitions.Count); + foreach (var registration in config.ModuleDefinitions) + { + var type = registration.ModuleInterface; + var moduleInstance = (IJavaScriptModule)Activator.CreateInstance(type); + var invokeHandler = new JavaScriptModuleInvokeHandler(catalystInstance, registration); + moduleInstance.InvokeHandler = invokeHandler; + _moduleInstances.Add(type, moduleInstance); + } + } + + public T GetJavaScriptModule() where T : IJavaScriptModule + { + var instance = default(IJavaScriptModule); + if (!_moduleInstances.TryGetValue(typeof(T), out instance)) + { + throw new InvalidOperationException( + string.Format( + CultureInfo.InvariantCulture, + "JS module '{0}' hasn't been registered.", + typeof(T))); + } + + return (T)instance; + } + + class JavaScriptModuleInvokeHandler : IInvokeHandler + { + private readonly CatalystInstance _catalystInstance; + private readonly JavaScriptModuleRegistration _moduleRegistration; + + public JavaScriptModuleInvokeHandler( + CatalystInstance catalystInstance, + JavaScriptModuleRegistration moduleRegistration) + { + _catalystInstance = catalystInstance; + _moduleRegistration = moduleRegistration; + } + + public void Invoke(string name, object[] args) + { + var tracingName = _moduleRegistration.GetTracingName(name); + _catalystInstance.InvokeFunction( + _moduleRegistration.ModuleId, + _moduleRegistration.GetMethodId(name), + JArray.FromObject(args), + tracingName); + } + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs b/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs new file mode 100644 index 00000000000..d559109232f --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs @@ -0,0 +1,61 @@ +using Newtonsoft.Json; +using System.Collections.Generic; + +namespace ReactNative.Bridge +{ + public class JavaScriptModulesConfig + { + private JavaScriptModulesConfig(IReadOnlyList modules) + { + ModuleDefinitions = modules; + } + + internal IReadOnlyList ModuleDefinitions + { + get; + } + + public void WriteModuleDescriptions(JsonWriter writer) + { + writer.WriteStartObject(); + foreach (var module in ModuleDefinitions) + { + writer.WritePropertyName(module.Name); + writer.WriteStartObject(); + writer.WritePropertyName("moduleID"); + writer.WriteValue(module.ModuleId); + writer.WritePropertyName("methods"); + writer.WriteStartObject(); + foreach (var method in module.Methods) + { + writer.WritePropertyName(method.Name); + writer.WriteStartObject(); + writer.WritePropertyName("methodID"); + writer.WriteValue(module.GetMethodId(method)); + writer.WriteEndObject(); + } + writer.WriteEndObject(); + writer.WriteEndObject(); + } + writer.WriteEndObject(); + } + + public sealed class Builder + { + private readonly List _modules = + new List(); + + public Builder Add() where T : IJavaScriptModule + { + var moduleId = _modules.Count; + _modules.Add(new JavaScriptModuleRegistration(moduleId, typeof(T))); + return this; + } + + public JavaScriptModulesConfig Build() + { + return new JavaScriptModulesConfig(_modules); + } + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs index 4ee46d72e3f..3b378b68316 100644 --- a/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs +++ b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs @@ -54,7 +54,7 @@ public void Initialize() _constants = CreateConstants(); } - public virtual void OnCatalystInstanceDestroy() + public virtual void OnCatalystInstanceDispose() { } @@ -65,7 +65,7 @@ protected virtual IReadOnlyDictionary CreateConstants() private IReadOnlyDictionary InitializeMethods() { - var declaredMethods = GetType().GetTypeInfo().DeclaredMethods; + var declaredMethods = GetType().GetMethods(); var exportedMethods = new List(); foreach (var method in declaredMethods) { diff --git a/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs b/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs index 3531e31a3ec..c86fd281f59 100644 --- a/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs +++ b/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs @@ -1,4 +1,6 @@ -using Newtonsoft.Json.Linq; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using ReactNative.Tracing; using System; using System.Collections.Generic; using System.Globalization; @@ -8,13 +10,13 @@ namespace ReactNative.Bridge { public sealed class NativeModuleRegistry { - private readonly IList _moduleTable; - private readonly IDictionary _moduleInstances; + private readonly IReadOnlyList _moduleTable; + private readonly IReadOnlyDictionary _moduleInstances; private readonly IList _batchCompleteListenerModules; private NativeModuleRegistry( - IList moduleTable, - IDictionary moduleInstances) + IReadOnlyList moduleTable, + IReadOnlyDictionary moduleInstances) { _moduleTable = moduleTable; _moduleInstances = moduleInstances; @@ -24,7 +26,7 @@ private NativeModuleRegistry( .ToList(); } - public ICollection Modules + public IEnumerable Modules { get { @@ -43,16 +45,67 @@ public T GetModule() where T : INativeModule throw new InvalidOperationException("No module instance for type '{0}'."); } + internal /* TODO: public? */ void Invoke( + ICatalystInstance catalystInstance, + int moduleId, + int methodId, + JArray parameters) + { + if (moduleId < 0) + throw new ArgumentOutOfRangeException("Invalid module ID: " + moduleId, nameof(moduleId)); + if (_moduleTable.Count < moduleId) + throw new ArgumentOutOfRangeException("Call to unknown module: " + moduleId, nameof(moduleId)); + + _moduleTable[moduleId].Invoke(catalystInstance, methodId, parameters); + } + + internal /* TODO: public? */ void NotifyCatalystInstanceInitialize() + { + DispatcherHelpers.AssertOnDispatcher(); + using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "NativeModuleRegistry_NotifyCatalystInstanceInitialize")) + { + foreach (var module in _moduleInstances.Values) + { + module.Initialize(); + } + } + } + + internal /* TODO: public? */ void NotifyCatalystInstanceDispose() + { + DispatcherHelpers.AssertOnDispatcher(); + using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "NativeModuleRegistry_NotifyCatalystInstanceDestroy")) + { + foreach (var module in _moduleInstances.Values) + { + module.OnCatalystInstanceDispose(); + } + } + } + + internal /* TODO: public? */ void WriteModuleDescriptions(JsonWriter writer) + { + using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "CreateJSON")) + { + writer.WriteStartObject(); + foreach (var moduleDef in _moduleTable) + { + writer.WritePropertyName(moduleDef.Name); + moduleDef.WriteModuleDescription(writer); + } + writer.WriteEndObject(); + } + } + class ModuleDefinition { private readonly int _id; - private readonly string _name; private readonly IList _methods; public ModuleDefinition(int id, string name, INativeModule target) { _id = id; - _name = name; + Name = name; Target = target; _methods = new List(target.Methods.Count); @@ -66,6 +119,10 @@ public ModuleDefinition(int id, string name, INativeModule target) } } + public int Id { get; } + + public string Name { get; } + public INativeModule Target { get; } public void Invoke(ICatalystInstance catalystInstance, int methodId, JArray parameters) @@ -73,6 +130,30 @@ public void Invoke(ICatalystInstance catalystInstance, int methodId, JArray para _methods[methodId].Method.Invoke(catalystInstance, parameters); } + public void WriteModuleDescription(JsonWriter writer) + { + writer.WriteStartObject(); + writer.WritePropertyName("moduleID"); + writer.WriteValue(_id); + writer.WritePropertyName("methods"); + writer.WriteStartObject(); + for (var i = 0; i < _methods.Count; ++i) + { + var method = _methods[i]; + writer.WritePropertyName(method.Name); + writer.WriteStartObject(); + writer.WritePropertyName("methodID"); + writer.WriteValue(i); + writer.WritePropertyName("type"); + writer.WriteValue(method.Method.Type); + writer.WriteEndObject(); + } + writer.WriteEndObject(); + writer.WritePropertyName("constants"); + JObject.FromObject(Target.Constants).WriteTo(writer); + writer.WriteEndObject(); + } + class MethodRegistration { public MethodRegistration(string name, string tracingName, INativeMethod method) diff --git a/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfiguration.cs b/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfiguration.cs new file mode 100644 index 00000000000..25e854134cf --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfiguration.cs @@ -0,0 +1,79 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReactNative.Bridge.Queue +{ + class CatalystQueueConfiguration : ICatalystQueueConfiguration, IDisposable + { + private readonly MessageQueueThread _dispatcherQueueThread; + private readonly MessageQueueThread _nativeModulesQueueThread; + private readonly MessageQueueThread _jsQueueThread; + + private CatalystQueueConfiguration( + MessageQueueThread dispatcherQueueThread, + MessageQueueThread nativeModulesQueueThread, + MessageQueueThread jsQueueThread) + { + _dispatcherQueueThread = dispatcherQueueThread; + _nativeModulesQueueThread = nativeModulesQueueThread; + _jsQueueThread = jsQueueThread; + } + + public IMessageQueueThread DispatcherQueueThread + { + get + { + return _dispatcherQueueThread; + } + } + + public IMessageQueueThread NativeModulesQueueThread + { + get + { + return _nativeModulesQueueThread; + } + } + + public IMessageQueueThread JSQueueThread + { + get + { + return _jsQueueThread; + } + } + + public void Dispose() + { + _dispatcherQueueThread.Dispose(); + _nativeModulesQueueThread.Dispose(); + _jsQueueThread.Dispose(); + } + + public static CatalystQueueConfiguration Create( + CatalystQueueConfigurationSpec spec, + Action exceptionHandler) + { + var dispatcherThreadSpec = MessageQueueThreadSpec.DispatcherThreadSpec; + var dispatcherThread = MessageQueueThread.Create(dispatcherThreadSpec, exceptionHandler); + + var jsThread = spec.JSQueueThreadSpec != dispatcherThreadSpec + ? MessageQueueThread.Create(spec.JSQueueThreadSpec, exceptionHandler) + : dispatcherThread; + + var nativeModulesThread = spec.NativeModulesQueueThreadSpec != dispatcherThreadSpec + ? MessageQueueThread.Create(spec.NativeModulesQueueThreadSpec, exceptionHandler) + : dispatcherThread; + + return new CatalystQueueConfiguration(dispatcherThread, nativeModulesThread, jsThread); + } + + public sealed class Builder + { + + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfigurationSpec.cs b/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfigurationSpec.cs new file mode 100644 index 00000000000..49e408ee28b --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfigurationSpec.cs @@ -0,0 +1,84 @@ +using System; + +namespace ReactNative.Bridge.Queue +{ + public sealed class CatalystQueueConfigurationSpec + { + private CatalystQueueConfigurationSpec( + MessageQueueThreadSpec nativeModulesQueueThreadSpec, + MessageQueueThreadSpec jsQueueThreadSpec) + { + NativeModulesQueueThreadSpec = nativeModulesQueueThreadSpec; + JSQueueThreadSpec = jsQueueThreadSpec; + } + + public MessageQueueThreadSpec NativeModulesQueueThreadSpec + { + get; + } + + public MessageQueueThreadSpec JSQueueThreadSpec + { + get; + } + + public static CatalystQueueConfigurationSpec Default { get; } = CreateDefault(); + + private static CatalystQueueConfigurationSpec CreateDefault() + { + return new Builder() + { + JSQueueThreadSpec = MessageQueueThreadSpec.Create("js", MessageQueueThreadKind.BackgroundSingleThread), + NativeModulesQueueThreadSpec = MessageQueueThreadSpec.Create("native_modules", MessageQueueThreadKind.BackgroundAnyThread), + } + .Build(); + } + + public sealed class Builder + { + private MessageQueueThreadSpec _nativeModulesQueueThreadSpec; + private MessageQueueThreadSpec _jsQueueThreadSpec; + + public MessageQueueThreadSpec NativeModulesQueueThreadSpec + { + set + { + if (_nativeModulesQueueThreadSpec != null) + { + throw new InvalidOperationException("Setting native modules queue thread spec multiple times!"); + } + + _nativeModulesQueueThreadSpec = value; + } + } + + public MessageQueueThreadSpec JSQueueThreadSpec + { + set + { + if (_jsQueueThreadSpec != null) + { + throw new InvalidOperationException("Setting native modules queue thread spec multiple times!"); + } + + _jsQueueThreadSpec = value; + } + } + + public CatalystQueueConfigurationSpec Build() + { + if (_nativeModulesQueueThreadSpec == null) + { + throw new InvalidOperationException("Native modules queue thread spec has not been set."); + } + + if (_jsQueueThreadSpec == null) + { + throw new InvalidOperationException("JS queue thread spec has not been set."); + } + + return new CatalystQueueConfigurationSpec(_nativeModulesQueueThreadSpec, _jsQueueThreadSpec); + } + } + } +} diff --git a/ReactWindows/ReactNative/Bridge/Queue/ICatalystQueueConfiguration.cs b/ReactWindows/ReactNative/Bridge/Queue/ICatalystQueueConfiguration.cs new file mode 100644 index 00000000000..f12485d1043 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/Queue/ICatalystQueueConfiguration.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReactNative.Bridge.Queue +{ + public interface ICatalystQueueConfiguration + { + IMessageQueueThread DispatcherQueueThread { get; } + IMessageQueueThread NativeModulesQueueThread { get; } + IMessageQueueThread JSQueueThread { get; } + } +} diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs index 740e7c0e77d..8f67af4693a 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs @@ -79,7 +79,7 @@ public static MessageQueueThread Create( switch (spec.Kind) { - case MessageQueueThreadKind.MainUi: + case MessageQueueThreadKind.DispatcherThread: return new DispatcherMessageQueueThread(spec.Name, handler); case MessageQueueThreadKind.BackgroundSingleThread: return new SingleBackgroundMessageQueueThread(spec.Name, handler); diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs index 9a6c555ad2c..520aa15c453 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs @@ -2,7 +2,7 @@ { public enum MessageQueueThreadKind { - MainUi, + DispatcherThread, BackgroundSingleThread, BackgroundAnyThread, } diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs index 56e8f9c7d78..118acf243aa 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs @@ -14,11 +14,11 @@ private MessageQueueThreadSpec(MessageQueueThreadKind kind, string name) internal MessageQueueThreadKind Kind { get; } - public static MessageQueueThreadSpec MainUiThreadSpec { get; } = new MessageQueueThreadSpec(MessageQueueThreadKind.MainUi, "main_ui"); + public static MessageQueueThreadSpec DispatcherThreadSpec { get; } = new MessageQueueThreadSpec(MessageQueueThreadKind.DispatcherThread, "main_ui"); public static MessageQueueThreadSpec Create(string name, MessageQueueThreadKind kind) { - if (kind == MessageQueueThreadKind.MainUi) + if (kind == MessageQueueThreadKind.DispatcherThread) { throw new NotSupportedException("Use the singleton MainUiThreadSpec instance."); } diff --git a/ReactWindows/ReactNative/Bridge/ReactBridge.cs b/ReactWindows/ReactNative/Bridge/ReactBridge.cs new file mode 100644 index 00000000000..eba512297de --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/ReactBridge.cs @@ -0,0 +1,43 @@ +using Newtonsoft.Json.Linq; +using ReactNative.Bridge.Queue; +using System; + +namespace ReactNative.Bridge +{ + public class ReactBridge : IReactBridge + { + private readonly IJavaScriptExecutor _jsExecutor; + private readonly IReactCallback _reactCallback; + private readonly IMessageQueueThread _nativeModulesQueueThread; + + public ReactBridge( + IJavaScriptExecutor jsExecutor, + IReactCallback reactCallback, + IMessageQueueThread nativeModulesQueueThread) + { + _jsExecutor = jsExecutor; + _reactCallback = reactCallback; + _nativeModulesQueueThread = nativeModulesQueueThread; + } + + public void CallFunction(int moduleId, int methodId, JArray arguments) + { + throw new NotImplementedException(); + } + + public void InvokeCallback(int callbackID, JArray arguments) + { + throw new NotImplementedException(); + } + + public void SetGlobalVariable(string propertyName, string jsonEncodedArgument) + { + throw new NotImplementedException(); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + } +} diff --git a/ReactWindows/ReactNative/Common/ReactConstants.cs b/ReactWindows/ReactNative/Common/ReactConstants.cs new file mode 100644 index 00000000000..d2b8921f542 --- /dev/null +++ b/ReactWindows/ReactNative/Common/ReactConstants.cs @@ -0,0 +1,7 @@ +namespace ReactNative.Common +{ + public static class ReactConstants + { + public const string Tag = "React"; + } +} diff --git a/ReactWindows/ReactNative/Modules/Core/RCTNativeAppEventEmitter.cs b/ReactWindows/ReactNative/Modules/Core/RCTNativeAppEventEmitter.cs new file mode 100644 index 00000000000..cad1bdc8496 --- /dev/null +++ b/ReactWindows/ReactNative/Modules/Core/RCTNativeAppEventEmitter.cs @@ -0,0 +1,7 @@ +namespace ReactNative.Modules.Core +{ + public interface RCTNativeAppEventEmitter + { + void emit(string eventName, object data); + } +} diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index 9447c2911ef..8e08cf74328 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -108,6 +108,12 @@ + + + + + + @@ -115,13 +121,18 @@ + + + + + @@ -152,11 +163,15 @@ + + - + + + 14.0 diff --git a/ReactWindows/ReactNative/Tracing/EventSourceManager.cs b/ReactWindows/ReactNative/Tracing/EventSourceManager.cs new file mode 100644 index 00000000000..1473c2a815e --- /dev/null +++ b/ReactWindows/ReactNative/Tracing/EventSourceManager.cs @@ -0,0 +1,9 @@ +using System.Diagnostics.Tracing; + +namespace ReactNative.Tracing +{ + static class EventSourceManager + { + public static EventSource Instance { get; } = new EventSource("ReactNative"); + } +} diff --git a/ReactWindows/ReactNative/Tracing/TraceDisposable.cs b/ReactWindows/ReactNative/Tracing/TraceDisposable.cs index a477402729c..a73896d8aef 100644 --- a/ReactWindows/ReactNative/Tracing/TraceDisposable.cs +++ b/ReactWindows/ReactNative/Tracing/TraceDisposable.cs @@ -10,7 +10,6 @@ namespace ReactNative.Tracing { public struct TraceDisposable : IDisposable { - private static readonly EventSource s_eventSource = new EventSource("ReactNative"); private static readonly Stopwatch s_stopwatch = Stopwatch.StartNew(); private readonly int _source; @@ -26,7 +25,7 @@ public TraceDisposable(int source, string title) public void Dispose() { - s_eventSource.Write(_title, new EventData(_source, TimeSpan.FromTicks(s_stopwatch.ElapsedTicks - _timestamp))); + EventSourceManager.Instance.Write(_title, new EventData(_source, TimeSpan.FromTicks(s_stopwatch.ElapsedTicks - _timestamp))); } [EventData] diff --git a/ReactWindows/ReactNative/Tracing/Tracer.cs b/ReactWindows/ReactNative/Tracing/Tracer.cs index 5f3627f9556..6e3a951273f 100644 --- a/ReactWindows/ReactNative/Tracing/Tracer.cs +++ b/ReactWindows/ReactNative/Tracing/Tracer.cs @@ -17,5 +17,10 @@ public static TraceDisposable Trace(int kind, string title) { return new TraceDisposable(kind, title); } + + public static void Write(string tag, string message) + { + EventSourceManager.Instance.Write(tag, message); + } } } diff --git a/ReactWindows/ReactNative/UIManager/AppRegistry.cs b/ReactWindows/ReactNative/UIManager/AppRegistry.cs new file mode 100644 index 00000000000..e34f5543edb --- /dev/null +++ b/ReactWindows/ReactNative/UIManager/AppRegistry.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json.Linq; +using ReactNative.Bridge; + +namespace ReactNative.UIManager +{ + public class AppRegistry : JavaScriptModuleBase + { + void runApplication(string appKey, JObject appParameters) + { + Invoke(nameof(runApplication), appKey, appParameters); + } + + void unmountApplicationComponentAtRootTag(int rootTagNode) + { + Invoke(nameof(unmountApplicationComponentAtRootTag), rootTagNode); + } + } +} diff --git a/ReactWindows/ReactNative/UIManager/Events/RCTEventEmitter.cs b/ReactWindows/ReactNative/UIManager/Events/RCTEventEmitter.cs new file mode 100644 index 00000000000..2f8cdd5dbab --- /dev/null +++ b/ReactWindows/ReactNative/UIManager/Events/RCTEventEmitter.cs @@ -0,0 +1,18 @@ +using Newtonsoft.Json.Linq; +using ReactNative.Bridge; + +namespace ReactNative.Modules.Core +{ + public class RCTEventEmitter : JavaScriptModuleBase + { + public void receiveEvent(int targetTag, string eventName, JObject @event) + { + Invoke(nameof(receiveEvent), targetTag, eventName, @event); + } + + public void receiveTouches(string eventName, JArray touches, JArray changedIndices) + { + Invoke(nameof(receiveTouches), touches, changedIndices); + } + } +} From 3d887bbafb57b5b1d409c0b7655af88660d6aa4d Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Thu, 17 Dec 2015 00:18:35 -0500 Subject: [PATCH 12/17] Cleans up namespaces. --- .../ReactNative/Bridge/CatalystInstance.cs | 11 +++---- .../Bridge/JavaScriptModuleRegistration.cs | 2 -- .../Bridge/JavaScriptModulesConfig.cs | 2 +- .../Queue/CatalystQueueConfiguration.cs | 9 ------ .../Queue/ICatalystQueueConfiguration.cs | 8 +---- .../Bridge/Queue/IMessageQueueThread.cs | 1 - .../Bridge/Queue/MessageQueueThread.cs | 1 - .../Queue/MessageQueueThreadExtensions.cs | 1 - .../ReactNative/Bridge/ReactContext.cs | 8 +---- ReactWindows/ReactNative/Imports.cs | 31 ------------------- ReactWindows/ReactNative/ReactNative.csproj | 12 ++++--- .../ReactNative/Tracing/TraceDisposable.cs | 4 --- ReactWindows/ReactNative/Tracing/Tracer.cs | 8 +---- 13 files changed, 16 insertions(+), 82 deletions(-) delete mode 100644 ReactWindows/ReactNative/Imports.cs diff --git a/ReactWindows/ReactNative/Bridge/CatalystInstance.cs b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs index 4612017f911..2850f38b01f 100644 --- a/ReactWindows/ReactNative/Bridge/CatalystInstance.cs +++ b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs @@ -1,13 +1,12 @@ -using System; -using System.Collections.Generic; +using Newtonsoft.Json; using Newtonsoft.Json.Linq; using ReactNative.Bridge.Queue; -using ReactNative.Tracing; using ReactNative.Common; -using System.Threading; -using System.Threading.Tasks; -using Newtonsoft.Json; +using ReactNative.Tracing; +using System; +using System.Collections.Generic; using System.IO; +using System.Threading.Tasks; namespace ReactNative.Bridge { diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs index 6318f77a4a8..349d9f8ad9d 100644 --- a/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs +++ b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs @@ -1,8 +1,6 @@ using System; -using System.Collections; using System.Collections.Generic; using System.Globalization; -using System.Linq; using System.Reflection; namespace ReactNative.Bridge diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs b/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs index d559109232f..f7f3f9cf376 100644 --- a/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs +++ b/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs @@ -28,7 +28,7 @@ public void WriteModuleDescriptions(JsonWriter writer) writer.WriteStartObject(); foreach (var method in module.Methods) { - writer.WritePropertyName(method.Name); + writer.WritePropertyName(method); writer.WriteStartObject(); writer.WritePropertyName("methodID"); writer.WriteValue(module.GetMethodId(method)); diff --git a/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfiguration.cs b/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfiguration.cs index 25e854134cf..274a6a859a7 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfiguration.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfiguration.cs @@ -1,8 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ReactNative.Bridge.Queue { @@ -70,10 +66,5 @@ public static CatalystQueueConfiguration Create( return new CatalystQueueConfiguration(dispatcherThread, nativeModulesThread, jsThread); } - - public sealed class Builder - { - - } } } diff --git a/ReactWindows/ReactNative/Bridge/Queue/ICatalystQueueConfiguration.cs b/ReactWindows/ReactNative/Bridge/Queue/ICatalystQueueConfiguration.cs index f12485d1043..9c1e8220e78 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/ICatalystQueueConfiguration.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/ICatalystQueueConfiguration.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ReactNative.Bridge.Queue +namespace ReactNative.Bridge.Queue { public interface ICatalystQueueConfiguration { diff --git a/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs b/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs index 85e3efdef67..55b0813a544 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs @@ -1,5 +1,4 @@ using System; -using System.Threading.Tasks; namespace ReactNative.Bridge.Queue { diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs index 8f67af4693a..d1101922497 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Concurrent; -using System.Collections.Generic; using System.Globalization; using System.Reactive.Linq; using System.Reactive.Subjects; diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs index 8863a0f0ee2..a642e36a112 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs @@ -1,6 +1,5 @@ using System; using System.Threading.Tasks; -using Windows.System.Threading; namespace ReactNative.Bridge.Queue { diff --git a/ReactWindows/ReactNative/Bridge/ReactContext.cs b/ReactWindows/ReactNative/Bridge/ReactContext.cs index 9177be93253..de402ead173 100644 --- a/ReactWindows/ReactNative/Bridge/ReactContext.cs +++ b/ReactWindows/ReactNative/Bridge/ReactContext.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ReactNative.Bridge +namespace ReactNative.Bridge { public class ReactContext { diff --git a/ReactWindows/ReactNative/Imports.cs b/ReactWindows/ReactNative/Imports.cs deleted file mode 100644 index 09cde5e3043..00000000000 --- a/ReactWindows/ReactNative/Imports.cs +++ /dev/null @@ -1,31 +0,0 @@ -using Newtonsoft.Json.Linq; - -namespace ReactNative -{ - public static class Imports - { - public static JObject Instance { get; } = JObject.Parse( - @"{ - AppRegistry: { - methods: [ - 'runApplication' - ] - }, - RCTDeviceEventEmitter: { - methods: [ - 'emit' - ] - }, - RCTEventEmitter: { - methods: [ - 'receiveEvent' - ] - }, - JSTimersExecution: { - methods: [ - 'callTimers' - ] - } - }"); - } -} diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index 8e08cf74328..b8f6a9018e7 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -109,10 +109,13 @@ + + + @@ -155,8 +158,9 @@ - + + @@ -166,12 +170,10 @@ - + - - - + 14.0 diff --git a/ReactWindows/ReactNative/Tracing/TraceDisposable.cs b/ReactWindows/ReactNative/Tracing/TraceDisposable.cs index a73896d8aef..a918d82a0e2 100644 --- a/ReactWindows/ReactNative/Tracing/TraceDisposable.cs +++ b/ReactWindows/ReactNative/Tracing/TraceDisposable.cs @@ -1,10 +1,6 @@ using System; -using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.Tracing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace ReactNative.Tracing { diff --git a/ReactWindows/ReactNative/Tracing/Tracer.cs b/ReactWindows/ReactNative/Tracing/Tracer.cs index 6e3a951273f..b8145bed925 100644 --- a/ReactWindows/ReactNative/Tracing/Tracer.cs +++ b/ReactWindows/ReactNative/Tracing/Tracer.cs @@ -1,10 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace ReactNative.Tracing +namespace ReactNative.Tracing { static class Tracer { From a4669b369fb4c497735b4a80b5350657104aed2e Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Thu, 17 Dec 2015 11:41:32 -0500 Subject: [PATCH 13/17] Adds documentation to all public APIs and more. --- .../Bridge/NativeModuleBaseTests.cs | 2 +- .../ReactNative/Bridge/CatalystInstance.cs | 2 +- ReactWindows/ReactNative/Bridge/ICallback.cs | 8 ++ .../ReactNative/Bridge/ICatalystInstance.cs | 24 +++++- .../ReactNative/Bridge/IInvocationHandler.cs | 15 ++++ .../ReactNative/Bridge/IInvokeHandler.cs | 7 -- .../ReactNative/Bridge/IJavaScriptModule.cs | 8 +- .../ReactNative/Bridge/INativeMethod.cs | 11 +++ .../ReactNative/Bridge/INativeModule.cs | 37 +++++++++ .../Bridge/IOnBatchCompleteListener.cs | 7 ++ .../ReactNative/Bridge/IReactBridge.cs | 20 +++++ .../ReactNative/Bridge/IReactCallback.cs | 13 +++ .../Bridge/JavaScriptModuleBase.cs | 21 ++++- .../Bridge/JavaScriptModuleRegistration.cs | 31 ++++++++ .../Bridge/JavaScriptModuleRegistry.cs | 25 +++++- .../Bridge/JavaScriptModulesConfig.cs | 23 ++++++ .../Bridge/NativeArgumentsParseException.cs | 14 ++++ .../ReactNative/Bridge/NativeModuleBase.cs | 79 +++++++++++++++++-- .../Bridge/NativeModuleRegistry.cs | 42 ++++++++++ .../Queue/CatalystQueueConfiguration.cs | 30 ++++++- .../Queue/CatalystQueueConfigurationSpec.cs | 29 ++++++- .../Queue/ICatalystQueueConfiguration.cs | 23 +++++- .../Bridge/Queue/IMessageQueueThread.cs | 3 + .../Bridge/Queue/MessageQueueThread.cs | 44 +++++++++++ .../Queue/MessageQueueThreadExtensions.cs | 5 +- .../Bridge/Queue/MessageQueueThreadKind.cs | 14 ++++ .../Bridge/Queue/MessageQueueThreadSpec.cs | 18 +++++ .../ReactNative/Bridge/ReactBridge.cs | 33 +++++++- .../Bridge/ReactContextNativeModuleBase.cs | 12 +++ .../ReactNative/Common/ReactConstants.cs | 6 ++ .../Modules/Core/RCTNativeAppEventEmitter.cs | 19 ++++- .../ReactNative/ReactMethodAttribute.cs | 4 + ReactWindows/ReactNative/ReactNative.csproj | 3 +- .../Reflection/ExpressionExtensions.cs | 13 +++ .../Reflection/MethodInfoHelpers.cs | 8 +- .../Reflection/ReflectionHelpers.cs | 40 ++++++++++ .../ReactNative/Tracing/EventSourceManager.cs | 6 ++ .../ReactNative/Tracing/TraceDisposable.cs | 25 ++++-- ReactWindows/ReactNative/Tracing/Tracer.cs | 36 +++++++-- .../ReactNative/UIManager/AppRegistry.cs | 12 +++ .../UIManager/Events/RCTEventEmitter.cs | 17 +++- 41 files changed, 741 insertions(+), 48 deletions(-) create mode 100644 ReactWindows/ReactNative/Bridge/IInvocationHandler.cs delete mode 100644 ReactWindows/ReactNative/Bridge/IInvokeHandler.cs create mode 100644 ReactWindows/ReactNative/Bridge/ReactContextNativeModuleBase.cs diff --git a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs index 3aceefe928d..beb66441c9a 100644 --- a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs +++ b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs @@ -251,7 +251,7 @@ public IEnumerable NativeModules } } - public T GetNativeModule(Type nativeModuleInterface) where T : INativeModule + public T GetNativeModule() where T : INativeModule { throw new NotImplementedException(); } diff --git a/ReactWindows/ReactNative/Bridge/CatalystInstance.cs b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs index 2850f38b01f..2d5cc69c6d4 100644 --- a/ReactWindows/ReactNative/Bridge/CatalystInstance.cs +++ b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs @@ -45,7 +45,7 @@ public IEnumerable NativeModules } } - public T GetNativeModule(Type nativeModuleInterface) where T : INativeModule + public T GetNativeModule() where T : INativeModule { return _registry.GetModule(); } diff --git a/ReactWindows/ReactNative/Bridge/ICallback.cs b/ReactWindows/ReactNative/Bridge/ICallback.cs index bbe29dbe30b..e04550933f6 100644 --- a/ReactWindows/ReactNative/Bridge/ICallback.cs +++ b/ReactWindows/ReactNative/Bridge/ICallback.cs @@ -1,7 +1,15 @@ namespace ReactNative.Bridge { + /// + /// Interface that represents a JavaScript callback function that can be + /// passed to a native module as a method parameter. + /// public interface ICallback { + /// + /// Invokes the callback. + /// + /// The callback arguments. void Invoke(params object[] arguments); } } diff --git a/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs b/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs index da2d9d79177..300031c67a6 100644 --- a/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs +++ b/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs @@ -5,14 +5,36 @@ namespace ReactNative.Bridge { + /// + /// An abstraction for the asynchronous JavaScript bridge. This provides an + /// environment allowing the invocation of JavaScript methods and lets a + /// set of native APIs be invokable from JavaScript as well. + /// public interface ICatalystInstance { + /// + /// Enumerates the available native modules. + /// IEnumerable NativeModules { get; } + /// + /// Invokes a JavaScript callback. + /// + /// The callback ID. + /// The arguments. void InvokeCallback(int callbackId, JArray arguments); + /// + /// Initializes the instance. + /// + /// A task to await initialization. Task InitializeAsync(); - T GetNativeModule(Type nativeModuleInterface) where T : INativeModule; + /// + /// Gets a native module instance. + /// + /// Type of native module. + /// The native module instance. + T GetNativeModule() where T : INativeModule; } } diff --git a/ReactWindows/ReactNative/Bridge/IInvocationHandler.cs b/ReactWindows/ReactNative/Bridge/IInvocationHandler.cs new file mode 100644 index 00000000000..3f21db92775 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/IInvocationHandler.cs @@ -0,0 +1,15 @@ +namespace ReactNative.Bridge +{ + /// + /// An interface for invoking methods specified by name. + /// + public interface IInvocationHandler + { + /// + /// Invoke the specified method. + /// + /// The name of the method. + /// The arguments for the method. + void Invoke(string name, object[] args); + } +} diff --git a/ReactWindows/ReactNative/Bridge/IInvokeHandler.cs b/ReactWindows/ReactNative/Bridge/IInvokeHandler.cs deleted file mode 100644 index 63afff4606a..00000000000 --- a/ReactWindows/ReactNative/Bridge/IInvokeHandler.cs +++ /dev/null @@ -1,7 +0,0 @@ -namespace ReactNative.Bridge -{ - public interface IInvokeHandler - { - void Invoke(string name, object[] args); - } -} diff --git a/ReactWindows/ReactNative/Bridge/IJavaScriptModule.cs b/ReactWindows/ReactNative/Bridge/IJavaScriptModule.cs index 50a3894a24b..2ac62b82d0c 100644 --- a/ReactWindows/ReactNative/Bridge/IJavaScriptModule.cs +++ b/ReactWindows/ReactNative/Bridge/IJavaScriptModule.cs @@ -1,7 +1,13 @@ namespace ReactNative.Bridge { + /// + /// An interface for JavaScript modules. + /// public interface IJavaScriptModule { - IInvokeHandler InvokeHandler { set; } + /// + /// The invocation handler. + /// + IInvocationHandler InvocationHandler { set; } } } diff --git a/ReactWindows/ReactNative/Bridge/INativeMethod.cs b/ReactWindows/ReactNative/Bridge/INativeMethod.cs index ae456b7d033..d42cb57a8ef 100644 --- a/ReactWindows/ReactNative/Bridge/INativeMethod.cs +++ b/ReactWindows/ReactNative/Bridge/INativeMethod.cs @@ -2,10 +2,21 @@ namespace ReactNative.Bridge { + /// + /// An interface representing native methods. + /// public interface INativeMethod { + /// + /// The type of method. + /// string Type { get; } + /// + /// Invoke the native method. + /// + /// The catalyst instance. + /// The arguments. void Invoke(ICatalystInstance catalystInstance, JArray jsArguments); } } diff --git a/ReactWindows/ReactNative/Bridge/INativeModule.cs b/ReactWindows/ReactNative/Bridge/INativeModule.cs index f89d63be001..524675d9a70 100644 --- a/ReactWindows/ReactNative/Bridge/INativeModule.cs +++ b/ReactWindows/ReactNative/Bridge/INativeModule.cs @@ -2,18 +2,55 @@ namespace ReactNative.Bridge { + /// + /// A native module whose API can be provided to JavaScript catalyst + /// instances. + /// + /// + /// s whose implementation is written in C# + /// should extend or + /// . + /// public interface INativeModule { + /// + /// Return true if you intend to override some other native module that + /// was registered, e.g., as part of a different package (such as the + /// core one). Trying to override without returning true from this + /// method is considered an error and will throw an exception during + /// initialization. By default, all modules return false. + /// bool CanOverrideExistingModule { get; } + /// + /// Get the constants exported by this module. + /// IReadOnlyDictionary Constants { get; } + /// + /// Get the methods callabke from JavaScript on this module. + /// IReadOnlyDictionary Methods { get; } + /// + /// Get the name of the module. + /// + /// + /// This will be the name used to require() this module + /// from JavaScript. + /// string Name { get; } + /// + /// Called after the creation of a , in + /// order to initialize native modules that require the catalyst or + /// JavaScript modules. + /// void Initialize(); + /// + /// Called before a is disposed. + /// void OnCatalystInstanceDispose(); } } diff --git a/ReactWindows/ReactNative/Bridge/IOnBatchCompleteListener.cs b/ReactWindows/ReactNative/Bridge/IOnBatchCompleteListener.cs index 4e11a7bd296..12d94899d62 100644 --- a/ReactWindows/ReactNative/Bridge/IOnBatchCompleteListener.cs +++ b/ReactWindows/ReactNative/Bridge/IOnBatchCompleteListener.cs @@ -1,7 +1,14 @@ namespace ReactNative.Bridge { + /// + /// Interface that will be notified when a batch of JavaScript to native + /// calls has finished. + /// public interface IOnBatchCompleteListener { + /// + /// Invoked when a batch of JavaScript to native calls has finished. + /// void OnBatchComplete(); } } diff --git a/ReactWindows/ReactNative/Bridge/IReactBridge.cs b/ReactWindows/ReactNative/Bridge/IReactBridge.cs index 0a3a9f8c317..88b6f9cafc5 100644 --- a/ReactWindows/ReactNative/Bridge/IReactBridge.cs +++ b/ReactWindows/ReactNative/Bridge/IReactBridge.cs @@ -3,12 +3,32 @@ namespace ReactNative.Bridge { + /// + /// Interface to the JavaScript execution environment and means of + /// transport for messages between JavaScript and the native environment. + /// public interface IReactBridge : IDisposable { + /// + /// Calls a JavaScript function. + /// + /// The module ID. + /// The method ID. + /// The arguments. void CallFunction(int moduleId, int methodId, JArray arguments); + /// + /// Invokes a JavaScript callback. + /// + /// The callback ID. + /// The arguments. void InvokeCallback(int callbackID, JArray arguments); + /// + /// Sets a global JavaScript variable. + /// + /// The property name. + /// The JSON-encoded value. void SetGlobalVariable(string propertyName, string jsonEncodedArgument); } } diff --git a/ReactWindows/ReactNative/Bridge/IReactCallback.cs b/ReactWindows/ReactNative/Bridge/IReactCallback.cs index 72689329c5b..55d38113add 100644 --- a/ReactWindows/ReactNative/Bridge/IReactCallback.cs +++ b/ReactWindows/ReactNative/Bridge/IReactCallback.cs @@ -2,10 +2,23 @@ namespace ReactNative.Bridge { + /// + /// Interface that represents a native callback that can be invoked from + /// JavaScript. + /// public interface IReactCallback { + /// + /// Invoke the native callback. + /// + /// The module ID. + /// The method ID. + /// The parameters. void Invoke(int moduleId, int methodId, JArray parameters); + /// + /// Signals that a batch of operations is complete. + /// void OnBatchComplete(); } } diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptModuleBase.cs b/ReactWindows/ReactNative/Bridge/JavaScriptModuleBase.cs index 62d99913ffc..19b3a3d459f 100644 --- a/ReactWindows/ReactNative/Bridge/JavaScriptModuleBase.cs +++ b/ReactWindows/ReactNative/Bridge/JavaScriptModuleBase.cs @@ -2,11 +2,17 @@ namespace ReactNative.Bridge { + /// + /// Base class for s. + /// public abstract class JavaScriptModuleBase : IJavaScriptModule { - private IInvokeHandler _invokeHandler; + private IInvocationHandler _invokeHandler; - public IInvokeHandler InvokeHandler + /// + /// The invocation handler. + /// + public IInvocationHandler InvocationHandler { set { @@ -19,6 +25,17 @@ public IInvokeHandler InvokeHandler } } + /// + /// Invoke a method by name. + /// + /// The name of the method. + /// The arguments. + /// + /// The expectation is that s will use + /// this method to notify the framework of a JavaScript call to be + /// executed. This is to overcome the absense of a performant "proxy" + /// implementation in the .NET framework. + /// protected void Invoke(string name, params object[] args) { if (_invokeHandler == null) diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs index 349d9f8ad9d..09e4e00176d 100644 --- a/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs +++ b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs @@ -5,11 +5,20 @@ namespace ReactNative.Bridge { + /// + /// Registration information for a . Creates + /// a mapping of methods to IDs. + /// internal class JavaScriptModuleRegistration { private readonly IDictionary _methodsToIds; private readonly IDictionary _methodsToTracingStrings; + /// + /// Instantiates the . + /// + /// The module ID. + /// The module type. public JavaScriptModuleRegistration(int moduleId, Type moduleInterface) { ModuleId = moduleId; @@ -30,10 +39,19 @@ public JavaScriptModuleRegistration(int moduleId, Type moduleInterface) InitializeMethodTables(methodNames); } + /// + /// The module ID. + /// public int ModuleId { get; } + /// + /// The module type. + /// public Type ModuleInterface { get; } + /// + /// The module name. + /// public string Name { get @@ -42,6 +60,9 @@ public string Name } } + /// + /// The set of methods available in the module. + /// public IEnumerable Methods { get @@ -50,6 +71,11 @@ public IEnumerable Methods } } + /// + /// Get the ID for a particular module method by name. + /// + /// The method name. + /// The method ID. public int GetMethodId(string method) { var idx = default(int); @@ -61,6 +87,11 @@ public int GetMethodId(string method) return idx; } + /// + /// Get the tracing name for a particular module method by name.s + /// + /// The method name. + /// The tracing name. public string GetTracingName(string method) { var name = default(string); diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistry.cs b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistry.cs index 4b5ad0e5439..fa7fc87b44a 100644 --- a/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistry.cs +++ b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistry.cs @@ -5,10 +5,22 @@ namespace ReactNative.Bridge { + /// + /// Class responsible for holding all s + /// registered to a . Requires that + /// JavaScript modules use the base + /// class, and implement each of it's methods to dispatch through the + /// method. + /// class JavaScriptModuleRegistry { private readonly IDictionary _moduleInstances; + /// + /// Instantiates the . + /// + /// The catalyst instance. + /// The module configuration. public JavaScriptModuleRegistry( CatalystInstance catalystInstance, JavaScriptModulesConfig config) @@ -18,12 +30,17 @@ public JavaScriptModuleRegistry( { var type = registration.ModuleInterface; var moduleInstance = (IJavaScriptModule)Activator.CreateInstance(type); - var invokeHandler = new JavaScriptModuleInvokeHandler(catalystInstance, registration); - moduleInstance.InvokeHandler = invokeHandler; + var invokeHandler = new JavaScriptModuleInvocationHandler(catalystInstance, registration); + moduleInstance.InvocationHandler = invokeHandler; _moduleInstances.Add(type, moduleInstance); } } + /// + /// Gets an instance of a . + /// + /// Type of JavaScript module. + /// The JavaScript module instance. public T GetJavaScriptModule() where T : IJavaScriptModule { var instance = default(IJavaScriptModule); @@ -39,12 +56,12 @@ public T GetJavaScriptModule() where T : IJavaScriptModule return (T)instance; } - class JavaScriptModuleInvokeHandler : IInvokeHandler + class JavaScriptModuleInvocationHandler : IInvocationHandler { private readonly CatalystInstance _catalystInstance; private readonly JavaScriptModuleRegistration _moduleRegistration; - public JavaScriptModuleInvokeHandler( + public JavaScriptModuleInvocationHandler( CatalystInstance catalystInstance, JavaScriptModuleRegistration moduleRegistration) { diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs b/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs index f7f3f9cf376..8396a2e6384 100644 --- a/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs +++ b/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs @@ -3,6 +3,10 @@ namespace ReactNative.Bridge { + /// + /// Class to store the configuration of JavaScript modules that can be used + /// across the bridge. + /// public class JavaScriptModulesConfig { private JavaScriptModulesConfig(IReadOnlyList modules) @@ -10,11 +14,18 @@ private JavaScriptModulesConfig(IReadOnlyList modu ModuleDefinitions = modules; } + /// + /// The module definitions. + /// internal IReadOnlyList ModuleDefinitions { get; } + /// + /// Writes the description of the modules to a . + /// + /// The JSON writer. public void WriteModuleDescriptions(JsonWriter writer) { writer.WriteStartObject(); @@ -40,11 +51,19 @@ public void WriteModuleDescriptions(JsonWriter writer) writer.WriteEndObject(); } + /// + /// Builder for instances. + /// public sealed class Builder { private readonly List _modules = new List(); + /// + /// Adds a JavaScript module of the given type. + /// + /// Type of JavaScript module. + /// The builder instance. public Builder Add() where T : IJavaScriptModule { var moduleId = _modules.Count; @@ -52,6 +71,10 @@ public Builder Add() where T : IJavaScriptModule return this; } + /// + /// Builds the instance. + /// + /// The instance. public JavaScriptModulesConfig Build() { return new JavaScriptModulesConfig(_modules); diff --git a/ReactWindows/ReactNative/Bridge/NativeArgumentsParseException.cs b/ReactWindows/ReactNative/Bridge/NativeArgumentsParseException.cs index ba3508a5fef..d4a4125259e 100644 --- a/ReactWindows/ReactNative/Bridge/NativeArgumentsParseException.cs +++ b/ReactWindows/ReactNative/Bridge/NativeArgumentsParseException.cs @@ -2,13 +2,27 @@ namespace ReactNative.Bridge { + /// + /// An exception thrown when converting between JavaScript and native arguments. + /// public class NativeArgumentsParseException : ArgumentException { + /// + /// Instantiates the . + /// + /// The exception message. + /// The parameter name. public NativeArgumentsParseException(string message, string paramName) : base(message, paramName) { } + /// + /// Instantiates the . + /// + /// The exception message. + /// The parameter name. + /// The inner exception. public NativeArgumentsParseException(string message, string paramName, Exception innerException) : base(message, paramName, innerException) { diff --git a/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs index 3b378b68316..dd58c5393ae 100644 --- a/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs +++ b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs @@ -9,11 +9,52 @@ namespace ReactNative.Bridge { + /// + /// Base class for catalyst native modules. Implementations can be linked + /// to lifecycle events, such as the creation and disposal of the + /// by overriding the appropriate methods. + /// + /// Native methods are exposed to JavaScript with the + /// annotation. These methods may only + /// use arguments that can be parsed by or + /// , which maps from a JavaScript function and can + /// be used only as a last parameter, or in the case of success and error + /// callback pairs, the last two arguments respectively. + /// + /// All methods annotated with must + /// return . + /// + /// Please note that it is not allowed to have multiple methods annotated + /// with that share the same name. + /// + /// + /// Default implementations of and + /// are provided for convenience. + /// Subclasses need not call these base methods should they choose to + /// override them. + /// public abstract class NativeModuleBase : INativeModule { - private IReadOnlyDictionary _methods; - private IReadOnlyDictionary _constants; + private static readonly IReadOnlyDictionary s_emptyConstants + = new Dictionary(); + private readonly IReadOnlyDictionary _methods; + + /// + /// Instantiates a . + /// + protected NativeModuleBase() + { + _methods = InitializeMethods(); + } + + /// + /// Return true if you intend to override some other native module that + /// was registered, e.g., as part of a different package (such as the + /// core one). Trying to override without returning true from this + /// method is considered an error and will throw an exception during + /// initialization. By default, all modules return false. + /// public virtual bool CanOverrideExistingModule { get @@ -22,14 +63,20 @@ public virtual bool CanOverrideExistingModule } } + /// + /// Get the constants exported by this module. + /// public virtual IReadOnlyDictionary Constants { get { - return _constants; + return s_emptyConstants; } } + /// + /// Get the methods callabke from JavaScript on this module. + /// public IReadOnlyDictionary Methods { get @@ -43,21 +90,41 @@ public IReadOnlyDictionary Methods } } + /// + /// Get the name of the module. + /// + /// + /// This will be the name used to require() this module + /// from JavaScript. + /// public abstract string Name { get; } - public void Initialize() + /// + /// Called after the creation of a , in + /// order to initialize native modules that require the catalyst or + /// JavaScript modules. + /// + public virtual void Initialize() { - _methods = InitializeMethods(); - _constants = CreateConstants(); } + /// + /// Called before a is disposed. + /// public virtual void OnCatalystInstanceDispose() { } + /// + /// Create the set of constants to configure the global environment. + /// + /// The set of constants. + /// + /// This virtual method will be called during . + /// protected virtual IReadOnlyDictionary CreateConstants() { return new Dictionary(); diff --git a/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs b/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs index c86fd281f59..4d0550ae135 100644 --- a/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs +++ b/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs @@ -8,6 +8,9 @@ namespace ReactNative.Bridge { + /// + /// A set of native APIs exposed to a particular JavaScript instance. + /// public sealed class NativeModuleRegistry { private readonly IReadOnlyList _moduleTable; @@ -26,6 +29,9 @@ private NativeModuleRegistry( .ToList(); } + /// + /// The set of native modules exposed. + /// public IEnumerable Modules { get @@ -34,6 +40,11 @@ public IEnumerable Modules } } + /// + /// Gets a module instance of a specific type. + /// + /// Type of module instance. + /// The module instance. public T GetModule() where T : INativeModule { var instance = default(INativeModule); @@ -45,6 +56,13 @@ public T GetModule() where T : INativeModule throw new InvalidOperationException("No module instance for type '{0}'."); } + /// + /// Invoke a method on a native module. + /// + /// The catalyst instance. + /// The module ID. + /// The method ID. + /// The parameters. internal /* TODO: public? */ void Invoke( ICatalystInstance catalystInstance, int moduleId, @@ -59,6 +77,10 @@ public T GetModule() where T : INativeModule _moduleTable[moduleId].Invoke(catalystInstance, methodId, parameters); } + /// + /// Hook to notify modules that the has + /// been initialized. + /// internal /* TODO: public? */ void NotifyCatalystInstanceInitialize() { DispatcherHelpers.AssertOnDispatcher(); @@ -71,6 +93,10 @@ public T GetModule() where T : INativeModule } } + /// + /// Hook to notify modules that the has + /// been disposed. + /// internal /* TODO: public? */ void NotifyCatalystInstanceDispose() { DispatcherHelpers.AssertOnDispatcher(); @@ -83,6 +109,10 @@ public T GetModule() where T : INativeModule } } + /// + /// Write the module descriptions to the given . + /// + /// The JSON writer. internal /* TODO: public? */ void WriteModuleDescriptions(JsonWriter writer) { using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "CreateJSON")) @@ -171,11 +201,19 @@ public MethodRegistration(string name, string tracingName, INativeMethod method) } } + /// + /// Builder for . + /// public sealed class Builder { private readonly IDictionary _modules = new Dictionary(); + /// + /// Add a native module to the builder. + /// + /// The native module. + /// The builder instance. public Builder Add(INativeModule module) { if (module == null) @@ -207,6 +245,10 @@ public Builder Add(INativeModule module) return this; } + /// + /// Build a instance. + /// + /// The instance. public NativeModuleRegistry Build() { var moduleTable = new List(_modules.Count); diff --git a/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfiguration.cs b/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfiguration.cs index 274a6a859a7..cb6480f893c 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfiguration.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfiguration.cs @@ -2,7 +2,13 @@ namespace ReactNative.Bridge.Queue { - class CatalystQueueConfiguration : ICatalystQueueConfiguration, IDisposable + /// + /// Specifies which s must be used to run + /// the various contexts of execution within catalyst (dispatcher, native + /// modules, and JS). Some of these queue *may* be the same but should be + /// coded against as if they are different. + /// + class CatalystQueueConfiguration : ICatalystQueueConfiguration { private readonly MessageQueueThread _dispatcherQueueThread; private readonly MessageQueueThread _nativeModulesQueueThread; @@ -18,6 +24,9 @@ private CatalystQueueConfiguration( _jsQueueThread = jsQueueThread; } + /// + /// The main UI thread. + /// public IMessageQueueThread DispatcherQueueThread { get @@ -26,6 +35,9 @@ public IMessageQueueThread DispatcherQueueThread } } + /// + /// The native modules thread. + /// public IMessageQueueThread NativeModulesQueueThread { get @@ -34,6 +46,9 @@ public IMessageQueueThread NativeModulesQueueThread } } + /// + /// The JavaScript thread. + /// public IMessageQueueThread JSQueueThread { get @@ -42,6 +57,13 @@ public IMessageQueueThread JSQueueThread } } + /// + /// Disposes the queue configuration. + /// + /// + /// Should be called whenever the corresponding + /// is disposed. + /// public void Dispose() { _dispatcherQueueThread.Dispose(); @@ -49,6 +71,12 @@ public void Dispose() _jsQueueThread.Dispose(); } + /// + /// Factory for the configuration. + /// + /// The configuration specification. + /// The exception handler. + /// The queue configuration. public static CatalystQueueConfiguration Create( CatalystQueueConfigurationSpec spec, Action exceptionHandler) diff --git a/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfigurationSpec.cs b/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfigurationSpec.cs index 49e408ee28b..89a4284236b 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfigurationSpec.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/CatalystQueueConfigurationSpec.cs @@ -2,6 +2,11 @@ namespace ReactNative.Bridge.Queue { + /// + /// Specification for creating a . + /// This exists so the is able to set + /// exception handlers on the s it uses. + /// public sealed class CatalystQueueConfigurationSpec { private CatalystQueueConfigurationSpec( @@ -12,16 +17,25 @@ private CatalystQueueConfigurationSpec( JSQueueThreadSpec = jsQueueThreadSpec; } + /// + /// The native modules specification. + /// public MessageQueueThreadSpec NativeModulesQueueThreadSpec { get; } + /// + /// The JavaScript specification. + /// public MessageQueueThreadSpec JSQueueThreadSpec { get; } - + + /// + /// The default instance. + /// public static CatalystQueueConfigurationSpec Default { get; } = CreateDefault(); private static CatalystQueueConfigurationSpec CreateDefault() @@ -34,11 +48,17 @@ private static CatalystQueueConfigurationSpec CreateDefault() .Build(); } + /// + /// Builder for . + /// public sealed class Builder { private MessageQueueThreadSpec _nativeModulesQueueThreadSpec; private MessageQueueThreadSpec _jsQueueThreadSpec; + /// + /// Set the native modules . + /// public MessageQueueThreadSpec NativeModulesQueueThreadSpec { set @@ -52,6 +72,9 @@ public MessageQueueThreadSpec NativeModulesQueueThreadSpec } } + /// + /// Set the JavaScript . + /// public MessageQueueThreadSpec JSQueueThreadSpec { set @@ -65,6 +88,10 @@ public MessageQueueThreadSpec JSQueueThreadSpec } } + /// + /// Build the . + /// + /// The instance. public CatalystQueueConfigurationSpec Build() { if (_nativeModulesQueueThreadSpec == null) diff --git a/ReactWindows/ReactNative/Bridge/Queue/ICatalystQueueConfiguration.cs b/ReactWindows/ReactNative/Bridge/Queue/ICatalystQueueConfiguration.cs index 9c1e8220e78..8babde85271 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/ICatalystQueueConfiguration.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/ICatalystQueueConfiguration.cs @@ -1,9 +1,28 @@ -namespace ReactNative.Bridge.Queue +using System; + +namespace ReactNative.Bridge.Queue { - public interface ICatalystQueueConfiguration + /// + /// Specifies which s must be used to run + /// the various contexts of execution within catalyst (dispatcher, native + /// modules, and JS). Some of these queue *may* be the same but should be + /// coded against as if they are different. + /// + public interface ICatalystQueueConfiguration : IDisposable { + /// + /// The main UI thread. + /// IMessageQueueThread DispatcherQueueThread { get; } + + /// + /// The native modules thread. + /// IMessageQueueThread NativeModulesQueueThread { get; } + + /// + /// The JavaScript thread. + /// IMessageQueueThread JSQueueThread { get; } } } diff --git a/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs b/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs index 55b0813a544..0176805fc28 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/IMessageQueueThread.cs @@ -2,6 +2,9 @@ namespace ReactNative.Bridge.Queue { + /// + /// Encapsulates an action queue. + /// public interface IMessageQueueThread { /// diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs index d1101922497..8afacc2f35f 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThread.cs @@ -11,12 +11,18 @@ namespace ReactNative.Bridge.Queue { + /// + /// Encapsulates an action queue. + /// public abstract class MessageQueueThread : IMessageQueueThread, IDisposable { private bool _disposed; private MessageQueueThread() { } + /// + /// Flags if the is disposed. + /// protected bool IsDisposed { get @@ -25,6 +31,13 @@ protected bool IsDisposed } } + /// + /// Checks if the caller is running on the queue instance. + /// + /// + /// true if the caller is calling from the queue, false + /// otherwise. + /// public bool IsOnThread() { AssertNotDisposed(); @@ -32,6 +45,10 @@ public bool IsOnThread() return IsOnThreadCore(); } + /// + /// Queues an action to run. + /// + /// The action. public void RunOnQueue(Action action) { if (action == null) @@ -42,15 +59,36 @@ public void RunOnQueue(Action action) Enqueue(action); } + /// + /// Disposes the action queue. + /// public void Dispose() { Dispose(true); } + /// + /// Enqueues the action. + /// + /// The action. protected abstract void Enqueue(Action action); + /// + /// Checks if the caller is running on the queue instance. + /// + /// + /// true if the caller is calling from the queue, false + /// otherwise. + /// protected abstract bool IsOnThreadCore(); + /// + /// Disposes the action queue. + /// + /// + /// false if dispose was triggered by a finalizer, true + /// otherwise. + /// protected virtual void Dispose(bool disposing) { if (disposing) @@ -67,6 +105,12 @@ private void AssertNotDisposed() } } + /// + /// Factory to create the action queue. + /// + /// The action queue specification. + /// The exception handler. + /// The action queue instance. public static MessageQueueThread Create( MessageQueueThreadSpec spec, Action handler) diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs index a642e36a112..d7a9d129b6e 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadExtensions.cs @@ -3,7 +3,10 @@ namespace ReactNative.Bridge.Queue { - static class MessageQueueThreadExtensions + /// + /// Extension methods for s. + /// + public static class MessageQueueThreadExtensions { /// /// Asserts , throwing if the false. diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs index 520aa15c453..1a6f582dba5 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadKind.cs @@ -1,9 +1,23 @@ namespace ReactNative.Bridge.Queue { + /// + /// Types of . + /// public enum MessageQueueThreadKind { + /// + /// Dispatcher thread type. + /// DispatcherThread, + + /// + /// Single background thread type. + /// BackgroundSingleThread, + + /// + /// Any background thread type. + /// BackgroundAnyThread, } diff --git a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs index 118acf243aa..add7d2d10ad 100644 --- a/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs +++ b/ReactWindows/ReactNative/Bridge/Queue/MessageQueueThreadSpec.cs @@ -2,6 +2,9 @@ namespace ReactNative.Bridge.Queue { + /// + /// Specification for creating a . + /// public class MessageQueueThreadSpec { private MessageQueueThreadSpec(MessageQueueThreadKind kind, string name) @@ -10,12 +13,27 @@ private MessageQueueThreadSpec(MessageQueueThreadKind kind, string name) Kind = kind; } + /// + /// The name of the . + /// public string Name { get; } + /// + /// The type of the . + /// internal MessageQueueThreadKind Kind { get; } + /// + /// Singleton dispatcher specification. + /// public static MessageQueueThreadSpec DispatcherThreadSpec { get; } = new MessageQueueThreadSpec(MessageQueueThreadKind.DispatcherThread, "main_ui"); + /// + /// Factory for creating s. + /// + /// The name. + /// The kind. + /// The instance. public static MessageQueueThreadSpec Create(string name, MessageQueueThreadKind kind) { if (kind == MessageQueueThreadKind.DispatcherThread) diff --git a/ReactWindows/ReactNative/Bridge/ReactBridge.cs b/ReactWindows/ReactNative/Bridge/ReactBridge.cs index eba512297de..e56479ed5f3 100644 --- a/ReactWindows/ReactNative/Bridge/ReactBridge.cs +++ b/ReactWindows/ReactNative/Bridge/ReactBridge.cs @@ -4,12 +4,24 @@ namespace ReactNative.Bridge { + /// + /// Class to the JavaScript execution environment and means of transport + /// for messages between JavaScript and the native environment. + /// public class ReactBridge : IReactBridge { private readonly IJavaScriptExecutor _jsExecutor; private readonly IReactCallback _reactCallback; private readonly IMessageQueueThread _nativeModulesQueueThread; + /// + /// Instantiates the . + /// + /// The JavaScript executor. + /// The native callback handler. + /// + /// The native modules queue thread. + /// public ReactBridge( IJavaScriptExecutor jsExecutor, IReactCallback reactCallback, @@ -20,21 +32,40 @@ public ReactBridge( _nativeModulesQueueThread = nativeModulesQueueThread; } + /// + /// Calls a JavaScript function. + /// + /// The module ID. + /// The method ID. + /// The arguments. public void CallFunction(int moduleId, int methodId, JArray arguments) { throw new NotImplementedException(); } + /// + /// Invokes a JavaScript callback. + /// + /// The callback ID. + /// The arguments. public void InvokeCallback(int callbackID, JArray arguments) { throw new NotImplementedException(); } + /// + /// Sets a global JavaScript variable. + /// + /// The property name. + /// The JSON-encoded value. public void SetGlobalVariable(string propertyName, string jsonEncodedArgument) { throw new NotImplementedException(); } - + + /// + /// Disposes the bridge. + /// public void Dispose() { throw new NotImplementedException(); diff --git a/ReactWindows/ReactNative/Bridge/ReactContextNativeModuleBase.cs b/ReactWindows/ReactNative/Bridge/ReactContextNativeModuleBase.cs new file mode 100644 index 00000000000..4a97b8b6b70 --- /dev/null +++ b/ReactWindows/ReactNative/Bridge/ReactContextNativeModuleBase.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace ReactNative.Bridge +{ + class ReactContextNativeModuleBase + { + } +} diff --git a/ReactWindows/ReactNative/Common/ReactConstants.cs b/ReactWindows/ReactNative/Common/ReactConstants.cs index d2b8921f542..07523c5c7fd 100644 --- a/ReactWindows/ReactNative/Common/ReactConstants.cs +++ b/ReactWindows/ReactNative/Common/ReactConstants.cs @@ -1,7 +1,13 @@ namespace ReactNative.Common { + /// + /// Set of constants used. + /// public static class ReactConstants { + /// + /// Trace tag for React components. + /// public const string Tag = "React"; } } diff --git a/ReactWindows/ReactNative/Modules/Core/RCTNativeAppEventEmitter.cs b/ReactWindows/ReactNative/Modules/Core/RCTNativeAppEventEmitter.cs index cad1bdc8496..04f9f1073e9 100644 --- a/ReactWindows/ReactNative/Modules/Core/RCTNativeAppEventEmitter.cs +++ b/ReactWindows/ReactNative/Modules/Core/RCTNativeAppEventEmitter.cs @@ -1,7 +1,20 @@ -namespace ReactNative.Modules.Core +using ReactNative.Bridge; + +namespace ReactNative.Modules.Core { - public interface RCTNativeAppEventEmitter + /// + /// Native app event emitter. + /// + public class RCTNativeAppEventEmitter : JavaScriptModuleBase { - void emit(string eventName, object data); + /// + /// Emit a native app event. + /// + /// The event name. + /// The event data. + public void emit(string eventName, object data) + { + Invoke(nameof(emit), eventName, data); + } } } diff --git a/ReactWindows/ReactNative/ReactMethodAttribute.cs b/ReactWindows/ReactNative/ReactMethodAttribute.cs index 33feea83084..af5f8b679e4 100644 --- a/ReactWindows/ReactNative/ReactMethodAttribute.cs +++ b/ReactWindows/ReactNative/ReactMethodAttribute.cs @@ -2,6 +2,10 @@ namespace ReactNative { + /// + /// An attribute for annotating methods in an + /// . + /// [AttributeUsage(AttributeTargets.Method)] public sealed class ReactMethodAttribute : Attribute { diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index b8f6a9018e7..2d2b6e645a9 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -109,7 +109,7 @@ - + @@ -135,6 +135,7 @@ + diff --git a/ReactWindows/ReactNative/Reflection/ExpressionExtensions.cs b/ReactWindows/ReactNative/Reflection/ExpressionExtensions.cs index e96bb3db636..1e3535ea89a 100644 --- a/ReactWindows/ReactNative/Reflection/ExpressionExtensions.cs +++ b/ReactWindows/ReactNative/Reflection/ExpressionExtensions.cs @@ -3,8 +3,21 @@ namespace ReactNative.Reflection { + /// + /// Helper methods for expressions. + /// static class ExpressionExtensions { + /// + /// Transforms an expression into a parameter that can be used in + /// multiple places in a larger expression. Particularly useful for + /// declaring parameters in a . + /// + /// Expression type. + /// Result type. + /// The expression. + /// The result selector. + /// The result. public static T Let(this TExpression expression, Func selector) where TExpression : Expression { diff --git a/ReactWindows/ReactNative/Reflection/MethodInfoHelpers.cs b/ReactWindows/ReactNative/Reflection/MethodInfoHelpers.cs index f372d4ea101..797868958d0 100644 --- a/ReactWindows/ReactNative/Reflection/MethodInfoHelpers.cs +++ b/ReactWindows/ReactNative/Reflection/MethodInfoHelpers.cs @@ -3,11 +3,11 @@ namespace ReactNative.Reflection { + /// + /// Helper methods for . + /// static class MethodInfoHelpers { - public static bool IsAsync(this MethodInfo methodInfo) - { - return typeof(Task).IsAssignableFrom(methodInfo.ReturnType); - } + } } diff --git a/ReactWindows/ReactNative/Reflection/ReflectionHelpers.cs b/ReactWindows/ReactNative/Reflection/ReflectionHelpers.cs index cd432080bb1..19d0406d007 100644 --- a/ReactWindows/ReactNative/Reflection/ReflectionHelpers.cs +++ b/ReactWindows/ReactNative/Reflection/ReflectionHelpers.cs @@ -1,26 +1,66 @@ using System; using System.Linq.Expressions; using System.Reflection; +using System.Threading.Tasks; namespace ReactNative.Reflection { + /// + /// Helpers for . + /// static class ReflectionHelpers { + /// + /// Checks if a method is asynchronous. + /// + /// The method. + /// + /// true if the method is asynchronous, false otherwise. + /// + public static bool IsAsync(this MethodInfo methodInfo) + { + return typeof(Task).IsAssignableFrom(methodInfo.ReturnType); + } + + /// + /// Gets the at the root of the expression. + /// + /// The expression. + /// The reflected member. public static MemberInfo InfoOf(Expression expression) { return _InfoOf(expression.Body); } + /// + /// Gets the at the root of the expression. + /// + /// Type of result. + /// The expression. + /// The reflected member. public static MemberInfo InfoOf(Expression> expression) { return _InfoOf(expression.Body); } + /// + /// Gets the at the root of the expression. + /// + /// Type of input. + /// Type of result. + /// The expression. + /// The reflected member. + public static MemberInfo InfoOf(Expression> expression) { return _InfoOf(expression.Body); } + /// + /// Gets the at the root of the expression. + /// + /// The expression. + /// The reflected member. public static MemberInfo InfoOf(Expression expression) { return _InfoOf(expression); diff --git a/ReactWindows/ReactNative/Tracing/EventSourceManager.cs b/ReactWindows/ReactNative/Tracing/EventSourceManager.cs index 1473c2a815e..6857234cee9 100644 --- a/ReactWindows/ReactNative/Tracing/EventSourceManager.cs +++ b/ReactWindows/ReactNative/Tracing/EventSourceManager.cs @@ -2,8 +2,14 @@ namespace ReactNative.Tracing { + /// + /// Static for the application. + /// static class EventSourceManager { + /// + /// The instance. + /// public static EventSource Instance { get; } = new EventSource("ReactNative"); } } diff --git a/ReactWindows/ReactNative/Tracing/TraceDisposable.cs b/ReactWindows/ReactNative/Tracing/TraceDisposable.cs index a918d82a0e2..dc78ce867b7 100644 --- a/ReactWindows/ReactNative/Tracing/TraceDisposable.cs +++ b/ReactWindows/ReactNative/Tracing/TraceDisposable.cs @@ -4,24 +4,39 @@ namespace ReactNative.Tracing { - public struct TraceDisposable : IDisposable + /// + /// Disposable implementation for tracing operations. + /// + /// + /// This implementation is created as a struct to minimize the heap impact + /// for tracing operations. + /// + struct /* do not make class */ TraceDisposable : IDisposable { private static readonly Stopwatch s_stopwatch = Stopwatch.StartNew(); - private readonly int _source; + private readonly int _traceId; private readonly string _title; private readonly long _timestamp; - public TraceDisposable(int source, string title) + /// + /// Instantiates the . + /// + /// The trace ID. + /// The event title. + public TraceDisposable(int traceId, string title) { - _source = source; + _traceId = traceId; _title = title; _timestamp = s_stopwatch.ElapsedTicks; } + /// + /// Disposed the instance, capturing the trace. + /// public void Dispose() { - EventSourceManager.Instance.Write(_title, new EventData(_source, TimeSpan.FromTicks(s_stopwatch.ElapsedTicks - _timestamp))); + EventSourceManager.Instance.Write(_title, new EventData(_traceId, TimeSpan.FromTicks(s_stopwatch.ElapsedTicks - _timestamp))); } [EventData] diff --git a/ReactWindows/ReactNative/Tracing/Tracer.cs b/ReactWindows/ReactNative/Tracing/Tracer.cs index b8145bed925..8b171b93e68 100644 --- a/ReactWindows/ReactNative/Tracing/Tracer.cs +++ b/ReactWindows/ReactNative/Tracing/Tracer.cs @@ -1,17 +1,43 @@ namespace ReactNative.Tracing { + /// + /// Tracing helpers for the application. + /// static class Tracer { + /// + /// Trace ID for bridge events. + /// public const int TRACE_TAG_REACT_BRIDGE = 0; - public const int TRACE_TAG_REACT_FRESCO = 1; - public const int TRACE_TAG_REACT_APPS = 2; - public const int TRACE_TAG_REACT_VIEW = 3; + + /// + /// Trace ID for application events. + /// + public const int TRACE_TAG_REACT_APPS = 1; - public static TraceDisposable Trace(int kind, string title) + /// + /// Trace ID for view events. + /// + public const int TRACE_TAG_REACT_VIEW = 2; + + /// + /// Creates a disposable to trace an operation from start to finish. + /// + /// The trace ID. + /// The event title. + /// + /// The instance to dispose when the operation is finished. + /// + public static TraceDisposable Trace(int traceId, string title) { - return new TraceDisposable(kind, title); + return new TraceDisposable(traceId, title); } + /// + /// Writes a trace. + /// + /// The trace tag. + /// The trace message. public static void Write(string tag, string message) { EventSourceManager.Instance.Write(tag, message); diff --git a/ReactWindows/ReactNative/UIManager/AppRegistry.cs b/ReactWindows/ReactNative/UIManager/AppRegistry.cs index e34f5543edb..2f904d3dc76 100644 --- a/ReactWindows/ReactNative/UIManager/AppRegistry.cs +++ b/ReactWindows/ReactNative/UIManager/AppRegistry.cs @@ -3,13 +3,25 @@ namespace ReactNative.UIManager { + /// + /// The application registry. + /// public class AppRegistry : JavaScriptModuleBase { + /// + /// Run the application. + /// + /// The app key. + /// The app parameters. void runApplication(string appKey, JObject appParameters) { Invoke(nameof(runApplication), appKey, appParameters); } + /// + /// Unmount the application. + /// + /// The root tag node. void unmountApplicationComponentAtRootTag(int rootTagNode) { Invoke(nameof(unmountApplicationComponentAtRootTag), rootTagNode); diff --git a/ReactWindows/ReactNative/UIManager/Events/RCTEventEmitter.cs b/ReactWindows/ReactNative/UIManager/Events/RCTEventEmitter.cs index 2f8cdd5dbab..5917d66bfff 100644 --- a/ReactWindows/ReactNative/UIManager/Events/RCTEventEmitter.cs +++ b/ReactWindows/ReactNative/UIManager/Events/RCTEventEmitter.cs @@ -3,13 +3,28 @@ namespace ReactNative.Modules.Core { - public class RCTEventEmitter : JavaScriptModuleBase + /// + /// JavaScript event emitter. + /// + public sealed class RCTEventEmitter : JavaScriptModuleBase { + /// + /// Receive an event. + /// + /// The target tag. + /// The event name. + /// The event data. public void receiveEvent(int targetTag, string eventName, JObject @event) { Invoke(nameof(receiveEvent), targetTag, eventName, @event); } + /// + /// Receives touches. + /// + /// The event name. + /// The touches. + /// The changed indices. public void receiveTouches(string eventName, JArray touches, JArray changedIndices) { Invoke(nameof(receiveTouches), touches, changedIndices); From 3983adc298d588f52457b6d47319d7aa39a3f418 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Thu, 17 Dec 2015 11:50:48 -0500 Subject: [PATCH 14/17] Fixes broken unit test. The initialization sequence of modules has changed, and now reflection checks will throw during the constructor call. --- ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs index beb66441c9a..18bdb6ae096 100644 --- a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs +++ b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs @@ -14,8 +14,7 @@ public class NativeModuleBaseTests [TestMethod] public void NativeModuleBase_MethodOverload_ThrowsNotSupported() { - var module = new MethodOverloadNativeModule(); - AssertEx.Throws(() => module.Initialize()); + AssertEx.Throws(() => new MethodOverloadNativeModule()); } [TestMethod] From ee667dbdb5e5b2f54933765ea798b61cf80cf380 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Thu, 17 Dec 2015 12:37:16 -0500 Subject: [PATCH 15/17] Adds unit tests for JavaScriptModuleRegistry. --- .../Bridge/JavaScriptModuleRegistryTests.cs | 97 +++++++++++++++++++ .../Bridge/JavaScriptModulesConfigTests.cs | 32 ++++-- .../Bridge/NativeModuleBaseTests.cs | 59 ++--------- .../Internal/MockCatalystInstance.cs | 63 ++++++++++++ .../ReactNative.Tests.csproj | 2 + .../ReactNative/Bridge/CatalystInstance.cs | 34 +++---- .../ReactNative/Bridge/ICatalystInstance.cs | 21 ++-- .../Bridge/JavaScriptModuleRegistration.cs | 18 ++-- .../Bridge/JavaScriptModuleRegistry.cs | 13 ++- .../Bridge/JavaScriptModulesConfig.cs | 2 +- .../ReactNative/Bridge/NativeModuleBase.cs | 2 +- 11 files changed, 247 insertions(+), 96 deletions(-) create mode 100644 ReactWindows/ReactNative.Tests/Bridge/JavaScriptModuleRegistryTests.cs create mode 100644 ReactWindows/ReactNative.Tests/Internal/MockCatalystInstance.cs diff --git a/ReactWindows/ReactNative.Tests/Bridge/JavaScriptModuleRegistryTests.cs b/ReactWindows/ReactNative.Tests/Bridge/JavaScriptModuleRegistryTests.cs new file mode 100644 index 00000000000..8ab7f6545d9 --- /dev/null +++ b/ReactWindows/ReactNative.Tests/Bridge/JavaScriptModuleRegistryTests.cs @@ -0,0 +1,97 @@ +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using ReactNative.Bridge; +using ReactNative.Modules.Core; +using ReactNative.UIManager; +using System; +using System.Collections.Generic; +using System.Threading; + +namespace ReactNative.Tests.Bridge +{ + [TestClass] + public class JavaScriptModuleRegistryTests + { + [TestMethod] + public void JavaScriptModuleRegistry_ArgumentChecks() + { + var catalystInstance = new MockCatalystInstance(); + var config = new JavaScriptModulesConfig.Builder().Build(); + + AssertEx.Throws( + () => new JavaScriptModuleRegistry(null, config), + ex => Assert.AreEqual("catalystInstance", ex.ParamName)); + + AssertEx.Throws( + () => new JavaScriptModuleRegistry(catalystInstance, null), + ex => Assert.AreEqual("config", ex.ParamName)); + } + + [TestMethod] + public void JavaScriptModuleRegistry_Invoke() + { + var config = new JavaScriptModulesConfig.Builder() + .Add() + .Add() + .Add() + .Build(); + + var are = new AutoResetEvent(false); + var moduleIds = new List(); + var methodIds = new List(); + var argsList = new List(); + var catalystInstance = new MockCatalystInstance((moduleId, methodId, args, tracingName) => + { + moduleIds.Add(moduleId); + methodIds.Add(methodId); + argsList.Add(args); + are.Set(); + }); + + var registry = new JavaScriptModuleRegistry(catalystInstance, config); + var module = registry.GetJavaScriptModule(); + + module.Foo(42); + + are.WaitOne(); + + Assert.AreEqual(1, moduleIds.Count); + Assert.AreEqual(1, methodIds.Count); + Assert.AreEqual(1, moduleIds.Count); + + Assert.AreEqual(2, moduleIds[0]); + Assert.AreEqual(2, methodIds[0]); + Assert.AreEqual( + JArray.FromObject(new[] { 42 }).ToString(Formatting.None), + argsList[0].ToString(Formatting.None)); + } + + [TestMethod] + public void JavaScriptModuleRegistry_InvalidModule_Throws() + { + var config = new JavaScriptModulesConfig.Builder().Build(); + var catalystInstance = new MockCatalystInstance(); + var registry = new JavaScriptModuleRegistry(catalystInstance, config); + AssertEx.Throws(() => registry.GetJavaScriptModule()); + } + + class TestJavaScriptModule : JavaScriptModuleBase + { + public void Bar() + { + Invoke(nameof(Bar)); + } + + public void Baz() + { + Invoke(nameof(Baz)); + } + + public void Foo(int x) + { + Invoke(nameof(Foo), x); + } + } + } +} diff --git a/ReactWindows/ReactNative.Tests/Bridge/JavaScriptModulesConfigTests.cs b/ReactWindows/ReactNative.Tests/Bridge/JavaScriptModulesConfigTests.cs index 9a663f496c3..f3ba394a53c 100644 --- a/ReactWindows/ReactNative.Tests/Bridge/JavaScriptModulesConfigTests.cs +++ b/ReactWindows/ReactNative.Tests/Bridge/JavaScriptModulesConfigTests.cs @@ -15,14 +15,14 @@ public class JavaScriptModulesConfigTests public void JavaScriptModulesConfig_MethodOverrides_ThrowsNotSupported() { var builder = new JavaScriptModulesConfig.Builder(); - AssertEx.Throws(() => builder.Add()); + AssertEx.Throws(() => builder.Add()); } [TestMethod] public void JavaScriptModulesConfig_WriteModuleDefinitions() { var builder = new JavaScriptModulesConfig.Builder(); - builder.Add(); + builder.Add(); var config = builder.Build(); using (var stringWriter = new StringWriter()) @@ -37,7 +37,7 @@ public void JavaScriptModulesConfig_WriteModuleDefinitions() new Map { { - "ITestJavaScriptModule", + "TestJavaScriptModule", new Map { { "moduleID", 0 }, @@ -73,16 +73,30 @@ public void JavaScriptModulesConfig_WriteModuleDefinitions() public class Map : Dictionary { } - public interface IOverridesJavaScriptModule : IJavaScriptModule + public class OverridesJavaScriptModule : JavaScriptModuleBase { - void Foo(); + public void Foo() + { + Invoke(nameof(Foo)); + } + - void Foo(int x); + public void Foo(int x) + { + Invoke(nameof(Foo), x); + } } - public interface ITestJavaScriptModule : IJavaScriptModule + public class TestJavaScriptModule : JavaScriptModuleBase { - void Bar(); - void Foo(); + public void Bar() + { + Invoke(nameof(Bar)); + } + + public void Foo() + { + Invoke(nameof(Foo)); + } } } diff --git a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs index 18bdb6ae096..edd4ab8987a 100644 --- a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs +++ b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleBaseTests.cs @@ -1,10 +1,9 @@ -using System; -using System.Collections.Generic; -using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; using Newtonsoft.Json.Linq; using ReactNative.Bridge; +using System; +using System.Collections.Generic; using System.Linq; -using System.Threading.Tasks; namespace ReactNative.Tests.Bridge { @@ -24,7 +23,7 @@ public void NativeModuleBase_Invocation_ArgumentNull() testModule.Initialize(); - var catalystInstance = new TestCatalystInstance(); + var catalystInstance = new MockCatalystInstance(); AssertEx.Throws( () => testModule.Methods[nameof(TestNativeModule.Foo)].Invoke(null, new JArray()), ex => Assert.AreEqual("catalystInstance", ex.ParamName)); @@ -40,7 +39,7 @@ public void NativeModuleBase_Invocation_ArgumentInvalidCount() testModule.Initialize(); - var catalystInstance = new TestCatalystInstance(); + var catalystInstance = new MockCatalystInstance(); AssertEx.Throws( () => testModule.Methods[nameof(TestNativeModule.Bar)].Invoke(catalystInstance, new JArray()), ex => Assert.AreEqual("jsArguments", ex.ParamName)); @@ -53,7 +52,7 @@ public void NativeModuleBase_Invocation_ArgumentConversionException() testModule.Initialize(); - var catalystInstance = new TestCatalystInstance(); + var catalystInstance = new MockCatalystInstance(); AssertEx.Throws( () => testModule.Methods[nameof(TestNativeModule.Bar)].Invoke(catalystInstance, JArray.FromObject(new[] { default(object) })), ex => Assert.AreEqual("jsArguments", ex.ParamName)); @@ -70,7 +69,7 @@ public void NativeModuleBase_Invocation() Assert.AreEqual(2, testModule.Methods.Count); - var catalystInstance = new TestCatalystInstance(); + var catalystInstance = new MockCatalystInstance(); testModule.Methods[nameof(TestNativeModule.Foo)].Invoke(catalystInstance, new JArray()); testModule.Methods[nameof(TestNativeModule.Foo)].Invoke(catalystInstance, new JArray()); Assert.AreEqual(2, fooCount); @@ -90,7 +89,7 @@ public void NativeModuleBase_Invocation_Callbacks() var id = default(int); var args = default(List); - var catalystInstance = new TestCatalystInstance((i, a) => + var catalystInstance = new MockCatalystInstance((i, a) => { id = i; args = a.ToObject>(); @@ -111,7 +110,7 @@ public void NativeModuleBase_Invocation_Callbacks_InvalidArgumentThrows() var id = default(int); var args = default(List); - var catalystInstance = new TestCatalystInstance((i, a) => + var catalystInstance = new MockCatalystInstance((i, a) => { id = i; args = a.ToObject>(); @@ -131,7 +130,7 @@ public void NativeModuleBase_Invocation_Callbacks_NullCallback() var id = default(int); var args = default(List); - var catalystInstance = new TestCatalystInstance((i, a) => + var catalystInstance = new MockCatalystInstance((i, a) => { id = i; args = a.ToObject>(); @@ -227,43 +226,5 @@ public void Foo(ICallback callback) callback.Invoke(_callbackArgs); } } - - class TestCatalystInstance : ICatalystInstance - { - private readonly Action _callback; - - public TestCatalystInstance() - : this((_, __) => { }) - { - } - - public TestCatalystInstance(Action callback) - { - _callback = callback; - } - - public IEnumerable NativeModules - { - get - { - throw new NotImplementedException(); - } - } - - public T GetNativeModule() where T : INativeModule - { - throw new NotImplementedException(); - } - - public Task InitializeAsync() - { - return Task.FromResult(true); - } - - public void InvokeCallback(int callbackId, JArray arguments) - { - _callback(callbackId, arguments); - } - } } } diff --git a/ReactWindows/ReactNative.Tests/Internal/MockCatalystInstance.cs b/ReactWindows/ReactNative.Tests/Internal/MockCatalystInstance.cs new file mode 100644 index 00000000000..00c27a6563e --- /dev/null +++ b/ReactWindows/ReactNative.Tests/Internal/MockCatalystInstance.cs @@ -0,0 +1,63 @@ +using Newtonsoft.Json.Linq; +using ReactNative.Bridge; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace ReactNative.Tests +{ + class MockCatalystInstance : ICatalystInstance + { + private readonly Action _callback; + private readonly Action _function; + + public MockCatalystInstance() + : this((_, __) => { }, (p0, p1, p2, p3) => { }) + { + } + + public MockCatalystInstance(Action callback) + : this(callback, (p0, p1, p2, p3) => { }) + { + } + + public MockCatalystInstance(Action function) + : this((_, __) => { }, function) + { + } + + public MockCatalystInstance(Action callback, Action function) + { + _callback = callback; + _function = function; + } + + public IEnumerable NativeModules + { + get + { + throw new NotImplementedException(); + } + } + + public T GetNativeModule() where T : INativeModule + { + throw new NotImplementedException(); + } + + public Task InitializeAsync() + { + return Task.FromResult(true); + } + + public void InvokeCallback(int callbackId, JArray arguments) + { + _callback(callbackId, arguments); + } + + public void InvokeFunction(int moduleId, int methodId, JArray arguments, string tracingName) + { + _function(moduleId, methodId, arguments, tracingName); + } + } +} diff --git a/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj b/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj index 83208716dc1..78aa48be995 100644 --- a/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj +++ b/ReactWindows/ReactNative.Tests/ReactNative.Tests.csproj @@ -95,12 +95,14 @@ + + UnitTestApp.xaml diff --git a/ReactWindows/ReactNative/Bridge/CatalystInstance.cs b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs index 2d5cc69c6d4..b0938f288d5 100644 --- a/ReactWindows/ReactNative/Bridge/CatalystInstance.cs +++ b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs @@ -87,23 +87,7 @@ public void InvokeCallback(int callbackId, JArray arguments) }); } - public void Dispose() - { - DispatcherHelpers.AssertOnDispatcher(); - - if (_disposed) - { - return; - } - - _disposed = true; - _registry.NotifyCatalystInstanceDispose(); - _catalystQueueConfiguration.Dispose(); - // TODO: notify bridge idle listeners - _bridge.Dispose(); - } - - internal void InvokeFunction(int moduleId, int methodId, JArray arguments, string tracingName) + public void InvokeFunction(int moduleId, int methodId, JArray arguments, string tracingName) { _catalystQueueConfiguration.JSQueueThread.RunOnQueue(() => { @@ -126,6 +110,22 @@ internal void InvokeFunction(int moduleId, int methodId, JArray arguments, strin }); } + public void Dispose() + { + DispatcherHelpers.AssertOnDispatcher(); + + if (_disposed) + { + return; + } + + _disposed = true; + _registry.NotifyCatalystInstanceDispose(); + _catalystQueueConfiguration.Dispose(); + // TODO: notify bridge idle listeners + _bridge.Dispose(); + } + private Task InitializeBridgeAsync() { return _catalystQueueConfiguration.JSQueueThread.CallOnQueue(() => diff --git a/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs b/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs index 300031c67a6..8c0bf99ffcb 100644 --- a/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs +++ b/ReactWindows/ReactNative/Bridge/ICatalystInstance.cs @@ -18,17 +18,26 @@ public interface ICatalystInstance IEnumerable NativeModules { get; } /// - /// Invokes a JavaScript callback. + /// Initializes the instance. /// - /// The callback ID. + /// A task to await initialization. + Task InitializeAsync(); + + /// + /// Invokes a JavaScript function. + /// + /// The module ID. + /// The method ID. /// The arguments. - void InvokeCallback(int callbackId, JArray arguments); + /// The tracing name. + void InvokeFunction(int moduleId, int methodId, JArray arguments, string tracingName); /// - /// Initializes the instance. + /// Invokes a JavaScript callback. /// - /// A task to await initialization. - Task InitializeAsync(); + /// The callback ID. + /// The arguments. + void InvokeCallback(int callbackId, JArray arguments); /// /// Gets a native module instance. diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs index 09e4e00176d..1a1a5e1b60f 100644 --- a/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs +++ b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistration.cs @@ -24,17 +24,17 @@ public JavaScriptModuleRegistration(int moduleId, Type moduleInterface) ModuleId = moduleId; ModuleInterface = moduleInterface; - var methods = moduleInterface.GetMethods(); - var methodNames = new string[methods.Length]; - for (var i = 0; i < methods.Length; ++i) + var methods = moduleInterface.GetTypeInfo().DeclaredMethods; + var methodNames = new List(); + foreach (var method in methods) { - methodNames[i] = methods[i].Name; + methodNames.Add(method.Name); } - Array.Sort(methodNames, Comparer.Create((s1, s2) => s1.CompareTo(s2))); + methodNames.Sort((s1, s2) => s1.CompareTo(s2)); - _methodsToIds = new Dictionary(methods.Length); - _methodsToTracingStrings = new Dictionary(methods.Length); + _methodsToIds = new Dictionary(methodNames.Count); + _methodsToTracingStrings = new Dictionary(methodNames.Count); InitializeMethodTables(methodNames); } @@ -103,10 +103,10 @@ public string GetTracingName(string method) return name; } - private void InitializeMethodTables(string[] methods) + private void InitializeMethodTables(IList methods) { var lastMethod = default(string); - for (var i = 0; i < methods.Length; ++i) + for (var i = 0; i < methods.Count; ++i) { var method = methods[i]; if (method == lastMethod) diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistry.cs b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistry.cs index fa7fc87b44a..f4f51e1c908 100644 --- a/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistry.cs +++ b/ReactWindows/ReactNative/Bridge/JavaScriptModuleRegistry.cs @@ -12,7 +12,7 @@ namespace ReactNative.Bridge /// class, and implement each of it's methods to dispatch through the /// method. /// - class JavaScriptModuleRegistry + public class JavaScriptModuleRegistry { private readonly IDictionary _moduleInstances; @@ -22,9 +22,14 @@ class JavaScriptModuleRegistry /// The catalyst instance. /// The module configuration. public JavaScriptModuleRegistry( - CatalystInstance catalystInstance, + ICatalystInstance catalystInstance, JavaScriptModulesConfig config) { + if (catalystInstance == null) + throw new ArgumentNullException(nameof(catalystInstance)); + if (config == null) + throw new ArgumentNullException(nameof(config)); + _moduleInstances = new Dictionary(config.ModuleDefinitions.Count); foreach (var registration in config.ModuleDefinitions) { @@ -58,11 +63,11 @@ public T GetJavaScriptModule() where T : IJavaScriptModule class JavaScriptModuleInvocationHandler : IInvocationHandler { - private readonly CatalystInstance _catalystInstance; + private readonly ICatalystInstance _catalystInstance; private readonly JavaScriptModuleRegistration _moduleRegistration; public JavaScriptModuleInvocationHandler( - CatalystInstance catalystInstance, + ICatalystInstance catalystInstance, JavaScriptModuleRegistration moduleRegistration) { _catalystInstance = catalystInstance; diff --git a/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs b/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs index 8396a2e6384..7750838fed3 100644 --- a/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs +++ b/ReactWindows/ReactNative/Bridge/JavaScriptModulesConfig.cs @@ -64,7 +64,7 @@ public sealed class Builder /// /// Type of JavaScript module. /// The builder instance. - public Builder Add() where T : IJavaScriptModule + public Builder Add() where T : IJavaScriptModule, new() { var moduleId = _modules.Count; _modules.Add(new JavaScriptModuleRegistration(moduleId, typeof(T))); diff --git a/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs index dd58c5393ae..911acdded8a 100644 --- a/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs +++ b/ReactWindows/ReactNative/Bridge/NativeModuleBase.cs @@ -132,7 +132,7 @@ protected virtual IReadOnlyDictionary CreateConstants() private IReadOnlyDictionary InitializeMethods() { - var declaredMethods = GetType().GetMethods(); + var declaredMethods = GetType().GetTypeInfo().DeclaredMethods; var exportedMethods = new List(); foreach (var method in declaredMethods) { From c56974d21330e7b4d7846c349d8b77ec5628bf0f Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Thu, 17 Dec 2015 12:57:35 -0500 Subject: [PATCH 16/17] Adds unit test for native module registry configuration writer. --- .../Bridge/NativeModuleRegistryTests.cs | 72 +++++++++++++++++-- .../ReactNative/Bridge/CatalystInstance.cs | 2 +- .../Bridge/NativeModuleRegistry.cs | 36 +++++----- 3 files changed, 87 insertions(+), 23 deletions(-) diff --git a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleRegistryTests.cs b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleRegistryTests.cs index 8507add9ced..be0b4a43da0 100644 --- a/ReactWindows/ReactNative.Tests/Bridge/NativeModuleRegistryTests.cs +++ b/ReactWindows/ReactNative.Tests/Bridge/NativeModuleRegistryTests.cs @@ -1,7 +1,10 @@ using Microsoft.VisualStudio.TestPlatform.UnitTestFramework; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; using ReactNative.Bridge; using System; -using System.Collections.Generic; +using System.IO; +using System.Linq; namespace ReactNative.Tests.Bridge { @@ -28,9 +31,12 @@ public void NativeModuleRegistry_Override_Disallowed() [TestMethod] public void NativeModuleRegistry_Override_Allowed() { - var builder = new NativeModuleRegistry.Builder(); - builder.Add(new OverrideAllowedModule()); - builder.Add(new OverrideAllowedModule()); + var registry = new NativeModuleRegistry.Builder() + .Add(new OverrideAllowedModule()) + .Add(new OverrideAllowedModule()) + .Build(); + + Assert.AreEqual(1, registry.Modules.Count()); } [TestMethod] @@ -42,6 +48,47 @@ public void NativeModuleRegistry_ModuleWithNullName_Throws() ex => Assert.AreEqual("module", ex.ParamName)); } + [TestMethod] + public void NativeModuleRegistry_WriteModuleDefinitions() + { + var registry = new NativeModuleRegistry.Builder() + .Add(new TestNativeModule()) + .Build(); + + using (var stringWriter = new StringWriter()) + { + using (var writer = new JsonTextWriter(stringWriter)) + { + registry.WriteModuleDescriptions(writer); + } + + var actual = JObject.Parse(stringWriter.ToString()); + Assert.AreEqual(1, actual.Properties().Count()); + + var moduleDef = actual.GetValue("Test") as JObject; + Assert.IsNotNull(moduleDef); + + var moduleId = moduleDef.GetValue("moduleID"); + Assert.IsNotNull(moduleId); + Assert.AreEqual("0", moduleId.ToString()); + + var methods = moduleDef.GetValue("methods") as JObject; + Assert.IsNotNull(methods); + + var fooMethod = methods.GetValue("Foo") as JObject; + Assert.IsNotNull(fooMethod); + + var barMethod = methods.GetValue("Bar") as JObject; + Assert.IsNotNull(barMethod); + + var fooMethodId = fooMethod.GetValue("methodID"); + var barMethodId = barMethod.GetValue("methodID"); + Assert.AreNotEqual(fooMethodId.ToString(), barMethodId.ToString()); + Assert.IsTrue(fooMethodId.ToString() == "0" || fooMethodId.ToString() == "1"); + Assert.IsTrue(barMethodId.ToString() == "0" || barMethodId.ToString() == "1"); + } + } + class OverrideDisallowedModule : NativeModuleBase { public override string Name @@ -82,5 +129,22 @@ public override string Name } } } + + class TestNativeModule : NativeModuleBase + { + public override string Name + { + get + { + return "Test"; + } + } + + [ReactMethod] + public void Foo(int x) { } + + [ReactMethod] + public void Bar(string x) { } + } } } diff --git a/ReactWindows/ReactNative/Bridge/CatalystInstance.cs b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs index b0938f288d5..dd1770fc790 100644 --- a/ReactWindows/ReactNative/Bridge/CatalystInstance.cs +++ b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs @@ -87,7 +87,7 @@ public void InvokeCallback(int callbackId, JArray arguments) }); } - public void InvokeFunction(int moduleId, int methodId, JArray arguments, string tracingName) + public /* TODO: internal? */ void InvokeFunction(int moduleId, int methodId, JArray arguments, string tracingName) { _catalystQueueConfiguration.JSQueueThread.RunOnQueue(() => { diff --git a/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs b/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs index 4d0550ae135..9da026fe0ee 100644 --- a/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs +++ b/ReactWindows/ReactNative/Bridge/NativeModuleRegistry.cs @@ -56,6 +56,24 @@ public T GetModule() where T : INativeModule throw new InvalidOperationException("No module instance for type '{0}'."); } + /// + /// Write the module descriptions to the given . + /// + /// The JSON writer. + public /* TODO: internal? */ void WriteModuleDescriptions(JsonWriter writer) + { + using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "CreateJSON")) + { + writer.WriteStartObject(); + foreach (var moduleDef in _moduleTable) + { + writer.WritePropertyName(moduleDef.Name); + moduleDef.WriteModuleDescription(writer); + } + writer.WriteEndObject(); + } + } + /// /// Invoke a method on a native module. /// @@ -109,24 +127,6 @@ public T GetModule() where T : INativeModule } } - /// - /// Write the module descriptions to the given . - /// - /// The JSON writer. - internal /* TODO: public? */ void WriteModuleDescriptions(JsonWriter writer) - { - using (Tracer.Trace(Tracer.TRACE_TAG_REACT_BRIDGE, "CreateJSON")) - { - writer.WriteStartObject(); - foreach (var moduleDef in _moduleTable) - { - writer.WritePropertyName(moduleDef.Name); - moduleDef.WriteModuleDescription(writer); - } - writer.WriteEndObject(); - } - } - class ModuleDefinition { private readonly int _id; From c24c6a1960335847c521ef1535b5007b0681fbe2 Mon Sep 17 00:00:00 2001 From: Eric Rozell Date: Fri, 18 Dec 2015 01:32:35 -0500 Subject: [PATCH 17/17] Implements IReactBridge and IJavaScriptExecutor Decided to pursue an IJavaScriptExecutor after all. The reason is because the IJavaScriptExecutor can be pre-initialized the with React Native JavaScript library, without having any of that logic leak into the bridge or catalyst instance. The IJavaScriptExecutor implementation uses Chakra, and we included some simple mapping visitors from JToken to JavaScriptValue and visa versa. The current ReactBridge is a trivial wrapper around the IJavaScriptExecutor, that has some knowledge of the React Native protocols. This changelist also includes some minor fixes to the DllImports for Chakra, which should eventually be refactored into a library (along with the JToken -> JavaScriptValue converters). --- .../ReactNative/Bridge/CatalystInstance.cs | 2 +- .../ReactNative/Bridge/IJavaScriptExecutor.cs | 11 +- .../ReactNative/Bridge/IReactBridge.cs | 2 +- .../ReactNative/Bridge/ReactBridge.cs | 74 --------- .../Bridge/ChakraJavaScriptExecutor.cs | 133 +++++++++++++++ .../JTokenToJavaScriptValueConverter.cs | 117 ++++++++++++++ .../JavaScriptValueToJTokenConverter.cs | 108 +++++++++++++ .../ReactNative/Hosting/Bridge/ReactBridge.cs | 153 ++++++++++++++++++ .../ReactNative/Hosting/JavaScriptContext.cs | 15 +- .../ReactNative/Hosting/JavaScriptRuntime.cs | 54 +------ .../ReactNative/Hosting/JavaScriptValue.cs | 3 +- ReactWindows/ReactNative/Hosting/Native.cs | 15 +- ReactWindows/ReactNative/ReactNative.csproj | 5 +- 13 files changed, 540 insertions(+), 152 deletions(-) delete mode 100644 ReactWindows/ReactNative/Bridge/ReactBridge.cs create mode 100644 ReactWindows/ReactNative/Hosting/Bridge/ChakraJavaScriptExecutor.cs create mode 100644 ReactWindows/ReactNative/Hosting/Bridge/JTokenToJavaScriptValueConverter.cs create mode 100644 ReactWindows/ReactNative/Hosting/Bridge/JavaScriptValueToJTokenConverter.cs create mode 100644 ReactWindows/ReactNative/Hosting/Bridge/ReactBridge.cs diff --git a/ReactWindows/ReactNative/Bridge/CatalystInstance.cs b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs index dd1770fc790..b59d45d633c 100644 --- a/ReactWindows/ReactNative/Bridge/CatalystInstance.cs +++ b/ReactWindows/ReactNative/Bridge/CatalystInstance.cs @@ -2,6 +2,7 @@ using Newtonsoft.Json.Linq; using ReactNative.Bridge.Queue; using ReactNative.Common; +using ReactNative.Hosting.Bridge; using ReactNative.Tracing; using System; using System.Collections.Generic; @@ -123,7 +124,6 @@ public void Dispose() _registry.NotifyCatalystInstanceDispose(); _catalystQueueConfiguration.Dispose(); // TODO: notify bridge idle listeners - _bridge.Dispose(); } private Task InitializeBridgeAsync() diff --git a/ReactWindows/ReactNative/Bridge/IJavaScriptExecutor.cs b/ReactWindows/ReactNative/Bridge/IJavaScriptExecutor.cs index 0c8687fa5fa..1749d74fe68 100644 --- a/ReactWindows/ReactNative/Bridge/IJavaScriptExecutor.cs +++ b/ReactWindows/ReactNative/Bridge/IJavaScriptExecutor.cs @@ -1,7 +1,12 @@ -namespace ReactNative.Bridge +using Newtonsoft.Json.Linq; +using System; + +namespace ReactNative.Bridge { - public interface IJavaScriptExecutor + public interface IJavaScriptExecutor : IDisposable { - // TODO + JToken Call(string moduleName, string methodName, JArray arguments); + + void SetGlobalVariable(string propertyName, JToken value); } } diff --git a/ReactWindows/ReactNative/Bridge/IReactBridge.cs b/ReactWindows/ReactNative/Bridge/IReactBridge.cs index 88b6f9cafc5..cab49c9087a 100644 --- a/ReactWindows/ReactNative/Bridge/IReactBridge.cs +++ b/ReactWindows/ReactNative/Bridge/IReactBridge.cs @@ -7,7 +7,7 @@ namespace ReactNative.Bridge /// Interface to the JavaScript execution environment and means of /// transport for messages between JavaScript and the native environment. /// - public interface IReactBridge : IDisposable + public interface IReactBridge { /// /// Calls a JavaScript function. diff --git a/ReactWindows/ReactNative/Bridge/ReactBridge.cs b/ReactWindows/ReactNative/Bridge/ReactBridge.cs deleted file mode 100644 index e56479ed5f3..00000000000 --- a/ReactWindows/ReactNative/Bridge/ReactBridge.cs +++ /dev/null @@ -1,74 +0,0 @@ -using Newtonsoft.Json.Linq; -using ReactNative.Bridge.Queue; -using System; - -namespace ReactNative.Bridge -{ - /// - /// Class to the JavaScript execution environment and means of transport - /// for messages between JavaScript and the native environment. - /// - public class ReactBridge : IReactBridge - { - private readonly IJavaScriptExecutor _jsExecutor; - private readonly IReactCallback _reactCallback; - private readonly IMessageQueueThread _nativeModulesQueueThread; - - /// - /// Instantiates the . - /// - /// The JavaScript executor. - /// The native callback handler. - /// - /// The native modules queue thread. - /// - public ReactBridge( - IJavaScriptExecutor jsExecutor, - IReactCallback reactCallback, - IMessageQueueThread nativeModulesQueueThread) - { - _jsExecutor = jsExecutor; - _reactCallback = reactCallback; - _nativeModulesQueueThread = nativeModulesQueueThread; - } - - /// - /// Calls a JavaScript function. - /// - /// The module ID. - /// The method ID. - /// The arguments. - public void CallFunction(int moduleId, int methodId, JArray arguments) - { - throw new NotImplementedException(); - } - - /// - /// Invokes a JavaScript callback. - /// - /// The callback ID. - /// The arguments. - public void InvokeCallback(int callbackID, JArray arguments) - { - throw new NotImplementedException(); - } - - /// - /// Sets a global JavaScript variable. - /// - /// The property name. - /// The JSON-encoded value. - public void SetGlobalVariable(string propertyName, string jsonEncodedArgument) - { - throw new NotImplementedException(); - } - - /// - /// Disposes the bridge. - /// - public void Dispose() - { - throw new NotImplementedException(); - } - } -} diff --git a/ReactWindows/ReactNative/Hosting/Bridge/ChakraJavaScriptExecutor.cs b/ReactWindows/ReactNative/Hosting/Bridge/ChakraJavaScriptExecutor.cs new file mode 100644 index 00000000000..18ecaaded5c --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/Bridge/ChakraJavaScriptExecutor.cs @@ -0,0 +1,133 @@ +using Newtonsoft.Json.Linq; +using ReactNative.Bridge; +using System; +using System.Diagnostics; + +namespace ReactNative.Hosting.Bridge +{ + class ChakraJavaScriptExecutor : IJavaScriptExecutor + { + private readonly JavaScriptRuntime _runtime; + private readonly JavaScriptValue _globalObject; + private readonly JavaScriptValue _requireFunction; + + public ChakraJavaScriptExecutor() + { + _runtime = JavaScriptRuntime.Create(); + _globalObject = JavaScriptValue.GlobalObject; + var requireId = JavaScriptPropertyId.FromString("require"); + _requireFunction = _globalObject.GetProperty(requireId); + + InitializeChakra(); + + // TODO: resolve how to inject React JavaScript library + } + + public JToken Call(string moduleName, string methodName, JArray arguments) + { + // Get the module + var moduleString = JavaScriptValue.FromString(moduleName); + var requireArguments = new[] { _globalObject, moduleString }; + var module = _requireFunction.CallFunction(requireArguments); + + // Get the method + var propertyId = JavaScriptPropertyId.FromString(methodName); + var method = module.GetProperty(propertyId); + + // Set up the arguments to pass in + var callArguments = new JavaScriptValue[arguments.Count + 1]; + callArguments[0] = _globalObject; // TODO: What is first argument? + + for (var i = 0; i < arguments.Count; ++i) + { + callArguments[i + 1] = JTokenToJavaScriptValueConverter.Convert(arguments[i]); + } + + // Invoke the function + var result = method.CallFunction(callArguments); + + // Convert the result + return JavaScriptValueToJTokenConverter.Convert(result); + } + + public void SetGlobalVariable(string propertyName, JToken value) + { + var javaScriptValue = JTokenToJavaScriptValueConverter.Convert(value); + var propertyId = JavaScriptPropertyId.FromString(propertyName); + _globalObject.SetProperty(propertyId, javaScriptValue, true); + } + + private void InitializeChakra() + { + // Set the current context + var context = _runtime.CreateContext(); + JavaScriptContext.Current = context; + + // Set the WinRT namespace (TODO: needed?) + Native.ThrowIfError( + Native.JsProjectWinRTNamespace("Windows")); + +#if DEBUG + // Start debugging. + JavaScriptContext.StartDebugging(); +#endif + + var consolePropertyId = default(JavaScriptPropertyId); + Native.ThrowIfError( + Native.JsGetPropertyIdFromName("console", out consolePropertyId)); + + var consoleObject = JavaScriptValue.CreateObject(); + _globalObject.SetProperty(consolePropertyId, consoleObject, true); + + DefineHostCallback(consoleObject, "log", ConsoleCallback, IntPtr.Zero); + DefineHostCallback(consoleObject, "warn", ConsoleCallback, IntPtr.Zero); + DefineHostCallback(consoleObject, "error", ConsoleCallback, IntPtr.Zero); + + Debug.WriteLine("Chakra initialization successful."); + } + + private static void DefineHostCallback( + JavaScriptValue obj, + string callbackName, + JavaScriptNativeFunction callback, + IntPtr callbackData) + { + var propertyId = JavaScriptPropertyId.FromString(callbackName); + var function = JavaScriptValue.CreateFunction(callback, callbackData); + obj.SetProperty(propertyId, function, true); + } + + private static JavaScriptValue ConsoleCallback( + JavaScriptValue callee, + bool isConstructCall, + JavaScriptValue[] arguments, + ushort argumentCount, + IntPtr callbackData) + { + try + { + Debug.Write("JS console> "); + + // First argument is this-context (? @TODO), ignore... + foreach (var argument in arguments) + { + Debug.Write(argument.ToString() + " "); + } + + Debug.WriteLine(""); + } + catch (Exception ex) + { + Debug.WriteLine( + "#EXCEPTION in ChakraExecutor::ConsoleCallback()! " + ex.Message); + } + + return JavaScriptValue.Invalid; + } + + public void Dispose() + { + _runtime.Dispose(); + } + } +} diff --git a/ReactWindows/ReactNative/Hosting/Bridge/JTokenToJavaScriptValueConverter.cs b/ReactWindows/ReactNative/Hosting/Bridge/JTokenToJavaScriptValueConverter.cs new file mode 100644 index 00000000000..f2931444b97 --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/Bridge/JTokenToJavaScriptValueConverter.cs @@ -0,0 +1,117 @@ +using Newtonsoft.Json.Linq; +using System; + +namespace ReactNative.Hosting.Bridge +{ + sealed class JTokenToJavaScriptValueConverter + { + private static readonly JTokenToJavaScriptValueConverter s_instance = + new JTokenToJavaScriptValueConverter(); + + private JTokenToJavaScriptValueConverter() { } + + public static JavaScriptValue Convert(JToken token) + { + return s_instance.Visit(token); + } + + private JavaScriptValue Visit(JToken token) + { + if (token == null) + throw new ArgumentNullException(nameof(token)); + + switch (token.Type) + { + case JTokenType.Array: + return VisitArray((JArray)token); + case JTokenType.Boolean: + return VisitBoolean((JValue)token); + case JTokenType.Float: + return VisitFloat((JValue)token); + case JTokenType.Integer: + return VisitInteger((JValue)token); + case JTokenType.Null: + return VisitNull(token); + case JTokenType.Object: + return VisitObject((JObject)token); + case JTokenType.String: + return VisitString((JValue)token); + case JTokenType.Undefined: + return VisitUndefined(token); + case JTokenType.Constructor: + case JTokenType.Property: + case JTokenType.Comment: + case JTokenType.Date: + case JTokenType.Raw: + case JTokenType.Bytes: + case JTokenType.Guid: + case JTokenType.Uri: + case JTokenType.TimeSpan: + case JTokenType.None: + default: + throw new NotSupportedException(); + } + } + + private JavaScriptValue VisitArray(JArray token) + { + var n = token.Count; + var values = new JavaScriptValue[n]; + for (var i = 0; i < n; ++i) + { + values[i] = Visit(token[i]); + } + + var array = JavaScriptValue.CreateArray((uint)n); + for (var i = 0; i < n; ++i) + { + array.SetIndexedProperty(JavaScriptValue.FromInt32(i), values[i]); + } + + return array; + } + + private JavaScriptValue VisitBoolean(JValue token) + { + return JavaScriptValue.FromBoolean(token.Value()); + } + + private JavaScriptValue VisitFloat(JValue token) + { + return JavaScriptValue.FromDouble(token.Value()); + } + + private JavaScriptValue VisitInteger(JValue token) + { + return JavaScriptValue.FromDouble(token.Value()); + } + + private JavaScriptValue VisitNull(JToken token) + { + return JavaScriptValue.Null; + } + + private JavaScriptValue VisitObject(JObject token) + { + var jsonObject = JavaScriptValue.CreateObject(); + foreach (var entry in token) + { + var value = Visit(entry.Value); + var propertyId = JavaScriptPropertyId.FromString(entry.Key); + jsonObject.SetProperty(propertyId, value, true); + } + + return jsonObject; + } + + private JavaScriptValue VisitString(JValue token) + { + return JavaScriptValue.FromString(token.Value()); + } + + private JavaScriptValue VisitUndefined(JToken token) + { + return JavaScriptValue.Undefined; + } + } +} diff --git a/ReactWindows/ReactNative/Hosting/Bridge/JavaScriptValueToJTokenConverter.cs b/ReactWindows/ReactNative/Hosting/Bridge/JavaScriptValueToJTokenConverter.cs new file mode 100644 index 00000000000..a92b17dcf2f --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/Bridge/JavaScriptValueToJTokenConverter.cs @@ -0,0 +1,108 @@ +using Newtonsoft.Json.Linq; +using System; + +namespace ReactNative.Hosting.Bridge +{ + sealed class JavaScriptValueToJTokenConverter + { + private static readonly JToken s_true = new JValue(true); + private static readonly JToken s_false = new JValue(false); + private static readonly JToken s_null = JValue.CreateNull(); + private static readonly JToken s_undefined = JValue.CreateUndefined(); + + private static readonly JavaScriptValueToJTokenConverter s_instance = + new JavaScriptValueToJTokenConverter(); + + private JavaScriptValueToJTokenConverter() { } + + public static JToken Convert(JavaScriptValue value) + { + return s_instance.Visit(value); + } + + private JToken Visit(JavaScriptValue value) + { + switch (value.ValueType) + { + case JavaScriptValueType.Array: + return VisitArray(value); + case JavaScriptValueType.Boolean: + return VisitBoolean(value); + case JavaScriptValueType.Null: + return VisitNull(value); + case JavaScriptValueType.Number: + return VisitNumber(value); + case JavaScriptValueType.Object: + return VisitObject(value); + case JavaScriptValueType.String: + return VisitString(value); + case JavaScriptValueType.Undefined: + return VisitUndefined(value); + case JavaScriptValueType.Function: + case JavaScriptValueType.Error: + default: + throw new NotSupportedException(); + } + } + + private JToken VisitArray(JavaScriptValue value) + { + var count = 0; + var array = new JArray(); + while (true) + { + var index = JavaScriptValue.FromInt32(count++); + if (!value.HasIndexedProperty(index)) + { + var element = value.GetIndexedProperty(index); + array.Add(Visit(element)); + } + else + { + break; + } + } + + return array; + } + + private JToken VisitBoolean(JavaScriptValue value) + { + return value.ToBoolean() ? s_true : s_false; + } + + private JToken VisitNull(JavaScriptValue value) + { + return s_null; + } + + private JToken VisitNumber(JavaScriptValue value) + { + return JToken.FromObject(value.ToObject()); + } + + private JToken VisitObject(JavaScriptValue value) + { + var jsonObject = new JObject(); + var properties = Visit(value.GetOwnPropertyNames()).ToObject(); + foreach (var property in properties) + { + var propertyId = JavaScriptPropertyId.FromString(property); + var propertyValue = value.GetProperty(propertyId); + jsonObject.Add(property, Visit(propertyValue)); + } + + return jsonObject; + } + + private JToken VisitString(JavaScriptValue value) + { + return JValue.CreateString(value.ToString()); + } + + private JToken VisitUndefined(JavaScriptValue value) + { + return s_undefined; + } + } +} diff --git a/ReactWindows/ReactNative/Hosting/Bridge/ReactBridge.cs b/ReactWindows/ReactNative/Hosting/Bridge/ReactBridge.cs new file mode 100644 index 00000000000..c49b3d9195e --- /dev/null +++ b/ReactWindows/ReactNative/Hosting/Bridge/ReactBridge.cs @@ -0,0 +1,153 @@ +using Newtonsoft.Json.Linq; +using ReactNative.Bridge; +using ReactNative.Bridge.Queue; + +namespace ReactNative.Hosting.Bridge +{ + /// + /// Class to the JavaScript execution environment and means of transport + /// for messages between JavaScript and the native environment. + /// + public class ReactBridge : IReactBridge + { + private readonly IJavaScriptExecutor _jsExecutor; + private readonly IReactCallback _reactCallback; + private readonly IMessageQueueThread _nativeModulesQueueThread; + + /// + /// Instantiates the . + /// + /// The JavaScript executor. + /// The native callback handler. + /// + /// The native modules queue thread. + /// + public ReactBridge( + IJavaScriptExecutor jsExecutor, + IReactCallback reactCallback, + IMessageQueueThread nativeModulesQueueThread) + { + _jsExecutor = jsExecutor; + _reactCallback = reactCallback; + _nativeModulesQueueThread = nativeModulesQueueThread; + } + + /// + /// Calls a JavaScript function. + /// + /// The module ID. + /// The method ID. + /// The arguments. + public void CallFunction(int moduleId, int methodId, JArray arguments) + { + var allArgs = new JArray + { + moduleId, + methodId, + arguments, + }; + + var message = new JObject + { + { "module", "BatchedBridge" }, + { "method", "callFunctionReturnFlushedQueue" }, + { "context", 15 }, + { "args", allArgs }, + }; + + var messageArray = new JArray + { + message, + }; + + // TODO: actually introduce batching here... + var processBatchArgs = new JArray + { + messageArray, + }; + + var response = _jsExecutor.Call("BatchedBridge", "processBatch", processBatchArgs); + + ProcessResponse(response); + } + + /// + /// Invokes a JavaScript callback. + /// + /// The callback ID. + /// The arguments. + public void InvokeCallback(int callbackId, JArray arguments) + { + var allArgs = new JArray + { + callbackId, + arguments, + }; + + var message = new JObject + { + { "module", "BatchedBridge" }, + { "method", "invokeCallbackAndReturnFlushedQueue" }, + { "args", allArgs }, + }; + + var messageArray = new JArray + { + message, + }; + + var processBatchArgs = new JArray + { + messageArray, + }; + + var response = _jsExecutor.Call("BatchedBridge", "processBatch", processBatchArgs); + + ProcessResponse(response); + } + + /// + /// Sets a global JavaScript variable. + /// + /// The property name. + /// The JSON-encoded value. + public void SetGlobalVariable(string propertyName, string jsonEncodedArgument) + { + _jsExecutor.SetGlobalVariable(propertyName, JToken.Parse(jsonEncodedArgument)); + } + + /// + /// Disposes the bridge. + /// + public void Dispose() + { + } + + private void ProcessResponse(JToken response) + { + var messages = response as JArray; + if (messages == null) + { + return; + } + + var moduleIds = messages[0].ToObject(); + var methodIds = messages[0].ToObject(); + var paramsArray = (JArray)messages[2]; + + _nativeModulesQueueThread.RunOnQueue(() => + { + for (var i = 0; i < moduleIds.Length; i++) + { + var moduleId = moduleIds[i]; + var methodId = methodIds[i]; + var args = (JArray)paramsArray[i]; + + _reactCallback.Invoke(moduleId, methodId, args); + }; + + _reactCallback.OnBatchComplete(); + }); + } + } +} diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptContext.cs b/ReactWindows/ReactNative/Hosting/JavaScriptContext.cs index 7369efe82f1..bfa6ca20f84 100644 --- a/ReactWindows/ReactNative/Hosting/JavaScriptContext.cs +++ b/ReactWindows/ReactNative/Hosting/JavaScriptContext.cs @@ -354,24 +354,13 @@ public static void SetException(JavaScriptValue exception) Native.ThrowIfError(Native.JsSetException(exception)); } -#if RELEASE64 /// /// Starts debugging in the context. /// /// The debug application to use for debugging. - public static void StartDebugging(Native.IDebugApplication64 debugApplication) + public static void StartDebugging() { - Native.ThrowIfError(Native.JsStartDebugging(debugApplication)); - } -#endif - - /// - /// Starts debugging in the context. - /// - /// The debug application to use for debugging. - public static void StartDebugging(Native.IDebugApplication32 debugApplication) - { - Native.ThrowIfError(Native.JsStartDebugging(debugApplication)); + Native.ThrowIfError(Native.JsStartDebugging()); } /// diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptRuntime.cs b/ReactWindows/ReactNative/Hosting/JavaScriptRuntime.cs index a75d5ae46f2..06525f579dd 100644 --- a/ReactWindows/ReactNative/Hosting/JavaScriptRuntime.cs +++ b/ReactWindows/ReactNative/Hosting/JavaScriptRuntime.cs @@ -98,13 +98,12 @@ public bool Disabled /// Creates a new runtime. /// /// The attributes of the runtime to be created. - /// The version of the runtime to be created. /// The thread service for the runtime. Can be null. /// The runtime created. - public static JavaScriptRuntime Create(JavaScriptRuntimeAttributes attributes, JavaScriptRuntimeVersion version, JavaScriptThreadServiceCallback threadServiceCallback) + public static JavaScriptRuntime Create(JavaScriptRuntimeAttributes attributes, JavaScriptThreadServiceCallback threadServiceCallback) { JavaScriptRuntime handle; - Native.ThrowIfError(Native.JsCreateRuntime(attributes, version, threadServiceCallback, out handle)); + Native.ThrowIfError(Native.JsCreateRuntime(attributes, threadServiceCallback, out handle)); return handle; } @@ -112,11 +111,10 @@ public static JavaScriptRuntime Create(JavaScriptRuntimeAttributes attributes, J /// Creates a new runtime. /// /// The attributes of the runtime to be created. - /// The version of the runtime to be created. /// The runtime created. - public static JavaScriptRuntime Create(JavaScriptRuntimeAttributes attributes, JavaScriptRuntimeVersion version) + public static JavaScriptRuntime Create(JavaScriptRuntimeAttributes attributes) { - return Create(attributes, version, null); + return Create(attributes, null); } /// @@ -125,7 +123,7 @@ public static JavaScriptRuntime Create(JavaScriptRuntimeAttributes attributes, J /// The runtime created. public static JavaScriptRuntime Create() { - return Create(JavaScriptRuntimeAttributes.None, JavaScriptRuntimeVersion.Version11, null); + return Create(JavaScriptRuntimeAttributes.None, null); } /// @@ -207,7 +205,6 @@ public void SetBeforeCollectCallback(IntPtr callbackState, JavaScriptBeforeColle Native.ThrowIfError(Native.JsSetRuntimeBeforeCollectCallback(this, callbackState, beforeCollectCallback)); } -#if RELEASE64 /// /// Creates a debug script context for running scripts. /// @@ -215,50 +212,11 @@ public void SetBeforeCollectCallback(IntPtr callbackState, JavaScriptBeforeColle /// Each script context has its own global object that is isolated from all other script /// contexts. /// - /// The debug application to use. - /// The created script context. - public JavaScriptContext CreateContext(Native.IDebugApplication64 debugApplication) - { - JavaScriptContext reference; - Native.ThrowIfError(Native.JsCreateContext(this, debugApplication, out reference)); - return reference; - } -#endif - -#if !RELEASE64 - /// - /// Creates a debug script context for running scripts. - /// - /// - /// Each script context has its own global object that is isolated from all other script - /// contexts. - /// - /// The debug application to use. - /// The created script context. - public JavaScriptContext CreateContext(Native.IDebugApplication32 debugApplication) - { - JavaScriptContext reference; - Native.ThrowIfError(Native.JsCreateContext(this, debugApplication, out reference)); - return reference; - } -#endif - - /// - /// Creates a script context for running scripts. - /// - /// - /// Each script context has its own global object that is isolated from all other script - /// contexts. - /// /// The created script context. public JavaScriptContext CreateContext() { JavaScriptContext reference; -#if RELEASE64 - Native.JsCreateContext(this, (Native.IDebugApplication64)null, out reference); -#else - Native.JsCreateContext(this, (Native.IDebugApplication32)null, out reference); -#endif + Native.ThrowIfError(Native.JsCreateContext(this, out reference)); return reference; } } diff --git a/ReactWindows/ReactNative/Hosting/JavaScriptValue.cs b/ReactWindows/ReactNative/Hosting/JavaScriptValue.cs index aca13680f35..bce6f4383b3 100644 --- a/ReactWindows/ReactNative/Hosting/JavaScriptValue.cs +++ b/ReactWindows/ReactNative/Hosting/JavaScriptValue.cs @@ -556,8 +556,7 @@ public double ToDouble() UIntPtr length; Native.ThrowIfError(Native.JsStringToPointer(this, out buffer, out length)); - //return Marshal.PtrToStringAuto(buffer, (int)length); - return /* TODO */ null; + return Marshal.PtrToStringUni(buffer, (int)length); } /// diff --git a/ReactWindows/ReactNative/Hosting/Native.cs b/ReactWindows/ReactNative/Hosting/Native.cs index e52a3d7fbfe..c805ada700b 100644 --- a/ReactWindows/ReactNative/Hosting/Native.cs +++ b/ReactWindows/ReactNative/Hosting/Native.cs @@ -369,7 +369,7 @@ internal static void ThrowIfError(JavaScriptErrorCode error) } [DllImport("chakra.dll")] - internal static extern JavaScriptErrorCode JsCreateRuntime(JavaScriptRuntimeAttributes attributes, JavaScriptRuntimeVersion runtimeVersion, JavaScriptThreadServiceCallback threadService, out JavaScriptRuntime runtime); + internal static extern JavaScriptErrorCode JsCreateRuntime(JavaScriptRuntimeAttributes attributes, JavaScriptThreadServiceCallback threadService, out JavaScriptRuntime runtime); [DllImport("chakra.dll")] internal static extern JavaScriptErrorCode JsCollectGarbage(JavaScriptRuntime handle); @@ -405,10 +405,7 @@ internal static void ThrowIfError(JavaScriptErrorCode error) internal static extern JavaScriptErrorCode JsRelease(JavaScriptValue reference, out uint count); [DllImport("chakra.dll")] - internal static extern JavaScriptErrorCode JsCreateContext(JavaScriptRuntime runtime, IDebugApplication64 debugSite, out JavaScriptContext newContext); - - [DllImport("chakra.dll")] - internal static extern JavaScriptErrorCode JsCreateContext(JavaScriptRuntime runtime, IDebugApplication32 debugSite, out JavaScriptContext newContext); + internal static extern JavaScriptErrorCode JsCreateContext(JavaScriptRuntime runtime, out JavaScriptContext newContext); [DllImport("chakra.dll")] internal static extern JavaScriptErrorCode JsGetCurrentContext(out JavaScriptContext currentContext); @@ -420,10 +417,7 @@ internal static void ThrowIfError(JavaScriptErrorCode error) internal static extern JavaScriptErrorCode JsGetRuntime(JavaScriptContext context, out JavaScriptRuntime runtime); [DllImport("chakra.dll")] - internal static extern JavaScriptErrorCode JsStartDebugging(IDebugApplication64 debugApplication); - - [DllImport("chakra.dll")] - internal static extern JavaScriptErrorCode JsStartDebugging(IDebugApplication32 debugApplication); + internal static extern JavaScriptErrorCode JsStartDebugging(); [DllImport("chakra.dll")] internal static extern JavaScriptErrorCode JsIdle(out uint nextIdleTick); @@ -635,6 +629,9 @@ internal static void ThrowIfError(JavaScriptErrorCode error) [DllImport("chakra.dll")] internal static extern JavaScriptErrorCode JsIsEnumeratingHeap(out bool isEnumeratingHeap); + [DllImport("chakra.dll", CharSet = CharSet.Unicode)] + internal static extern JavaScriptErrorCode JsProjectWinRTNamespace(string namespaceName); + /// /// ProcessDebugManager COM interface. /// diff --git a/ReactWindows/ReactNative/ReactNative.csproj b/ReactWindows/ReactNative/ReactNative.csproj index 2d2b6e645a9..b2b9d5b749f 100644 --- a/ReactWindows/ReactNative/ReactNative.csproj +++ b/ReactWindows/ReactNative/ReactNative.csproj @@ -133,7 +133,10 @@ - + + + +