Skip to content

Commit

Permalink
Sync Javascript Binding - Add support for property interception (#3935)
Browse files Browse the repository at this point in the history
* Core - added support for property interceptor

* Incorporated the review comments

* Incorporated the review comments 2

* Review comments exclude trygetproperty and trysetproperty for .netcore app

* Added the unit test cases for trygetproperty and trysetproperty

* Fixed .net core app build failure

* Added the separate test case for property interceptor

Co-authored-by: sghanti <santosh_ghanti@intuit.com>
  • Loading branch information
2 people authored and amaitland committed Feb 14, 2022
1 parent 20aba5c commit c417970
Show file tree
Hide file tree
Showing 10 changed files with 164 additions and 6 deletions.
4 changes: 4 additions & 0 deletions CefSharp.Example/CefSharp.Example.netcore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@
<None Remove="obj/**/*.*" />
<Compile Remove="obj/**/*.*" />
</ItemGroup>

<ItemGroup>
<Compile Remove="ModelBinding\PropertyInterceptorLogger.cs" />
</ItemGroup>

<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk.WindowsDesktop" />
</Project>
26 changes: 26 additions & 0 deletions CefSharp.Example/ModelBinding/PropertyInterceptorLogger.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Copyright © 2022 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

using System;
using System.Diagnostics;
using CefSharp.ModelBinding;

namespace CefSharp.Example.ModelBinding
{
public class PropertyInterceptorLogger : IPropertyInterceptor
{
object IPropertyInterceptor.InterceptGet(Func<object> propertyGetter, string propertyName)
{
object result = propertyGetter();
Debug.WriteLine("InterceptGet " + propertyName);
return result;
}

void IPropertyInterceptor.InterceptSet(Action<object> propertySetter, object parameter, string propertName)
{
Debug.WriteLine("InterceptSet " + propertName);
propertySetter(parameter);
}
}
}
33 changes: 33 additions & 0 deletions CefSharp.Test/JavascriptBinding/JavaScriptObjectRepositoryFacts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

using CefSharp.Example.ModelBinding;
using CefSharp.Internals;
using System;
using System.Collections.Generic;
using Xunit;

Expand Down Expand Up @@ -46,5 +48,36 @@ public void CanRegisterJavascriptObjectBindWhenNamespaceIsNull()
Assert.True(result.Success);
Assert.Equal("ok", result.ReturnValue.ToString());
}

#if !NETCOREAPP
[Fact]
public void CanRegisterJavascriptObjectPropertyBindWhenNamespaceIsNull()
{
IJavascriptObjectRepositoryInternal javascriptObjectRepository = new JavascriptObjectRepository();
var name = nameof(NoNamespaceClass);

BindingOptions bindingOptions = new BindingOptions()
{
Binder = BindingOptions.DefaultBinder.Binder,
PropertyInterceptor = new PropertyInterceptorLogger()
};
javascriptObjectRepository.Register(name, new NoNamespaceClass(), false, bindingOptions);
Assert.True(javascriptObjectRepository.IsBound(name));

var boundObjects = javascriptObjectRepository.GetObjects(new List<string> { name });
Assert.Single(boundObjects);

object getResult, setResult = 100;
string exception;
NoNamespaceClass noNamespaceClass = new NoNamespaceClass();
bool retValue = javascriptObjectRepository.TrySetProperty(boundObjects[0].Id, "year", setResult, out exception);
Assert.True(retValue);

retValue = javascriptObjectRepository.TryGetProperty(boundObjects[0].Id, "year", out getResult, out exception);
Assert.True(retValue);
Assert.Equal(100, Convert.ToInt32(getResult));
}
#endif
}

}
7 changes: 5 additions & 2 deletions CefSharp.Wpf.Example/Views/BrowserTabView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ public BrowserTabView()
var bindingOptions = new BindingOptions()
{
Binder = BindingOptions.DefaultBinder.Binder,
MethodInterceptor = new MethodInterceptorLogger() // intercept .net methods calls from js and log it
MethodInterceptor = new MethodInterceptorLogger(), // intercept .net methods calls from js and log it
#if !NETCOREAPP
PropertyInterceptor = new PropertyInterceptorLogger()
#endif
};

//To use the ResolveObject below and bind an object with isAsync:false we must set CefSharpSettings.WcfEnabled = true before
Expand Down Expand Up @@ -90,7 +93,7 @@ public BrowserTabView()
{
if (e.ObjectName == "bound")
{
repo.Register("bound", new BoundObject(), isAsync: false, options: BindingOptions.DefaultBinder);
repo.Register("bound", new BoundObject(), isAsync: false, options: bindingOptions);
}
else if (e.ObjectName == "boundAsync")
{
Expand Down
9 changes: 9 additions & 0 deletions CefSharp/BindingOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,5 +29,14 @@ public static BindingOptions DefaultBinder
/// for logging calls (from js) to .net methods.
/// </summary>
public IMethodInterceptor MethodInterceptor { get; set; }

#if !NETCOREAPP
/// <summary>
/// Interceptor used for intercepting get/set calls to the target object property. For instance, can be used
/// for logging calls to .net property (from js)
/// </summary>
public IPropertyInterceptor PropertyInterceptor { get; set; }
#endif

}
}
1 change: 1 addition & 0 deletions CefSharp/CefSharp.netcore.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<Compile Remove="CefRuntime.cs" />
<Compile Remove="DevTools\DevToolsClient.Generated.cs" />
<Compile Remove="Internals\Partial\ChromiumWebBrowser.Partial.cs" />
<Compile Remove="ModelBinding\IPropertyInterceptor.cs" />
</ItemGroup>

<Import Project="Sdk.targets" Sdk="Microsoft.NET.Sdk" />
Expand Down
2 changes: 2 additions & 0 deletions CefSharp/Internals/IJavascriptObjectRepositoryInternal.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,10 @@ public interface IJavascriptObjectRepositoryInternal : IJavascriptObjectReposito
{
TryCallMethodResult TryCallMethod(long objectId, string name, object[] parameters);
Task<TryCallMethodResult> TryCallMethodAsync(long objectId, string name, object[] parameters);
#if !NETCOREAPP
bool TryGetProperty(long objectId, string name, out object result, out string exception);
bool TrySetProperty(long objectId, string name, object value, out string exception);
#endif
bool IsBrowserInitialized { get; set; }
List<JavascriptObject> GetObjects(List<string> names = null);
List<JavascriptObject> GetLegacyBoundObjects();
Expand Down
4 changes: 4 additions & 0 deletions CefSharp/Internals/JavascriptObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ public class JavascriptObject //: DynamicObject maybe later

public IMethodInterceptor MethodInterceptor { get; set; }

#if !NETCOREAPP
public IPropertyInterceptor PropertyInterceptor { get; set; }
#endif

public JavascriptObject()
{
Methods = new List<JavascriptMethod>();
Expand Down
28 changes: 24 additions & 4 deletions CefSharp/Internals/JavascriptObjectRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,6 +250,9 @@ public void Register(string name, object value, bool isAsync, BindingOptions opt
jsObject.IsAsync = isAsync;
jsObject.Binder = options?.Binder;
jsObject.MethodInterceptor = options?.MethodInterceptor;
#if !NETCOREAPP
jsObject.PropertyInterceptor = options?.PropertyInterceptor;
#endif

AnalyseObjectForBinding(jsObject, analyseMethods: true, analyseProperties: !isAsync, readPropertyValue: false);
}
Expand Down Expand Up @@ -562,6 +565,7 @@ protected virtual async Task<TryCallMethodResult> TryCallMethodAsync(long object
return new TryCallMethodResult(false, result, exception);
}

#if !NETCOREAPP
bool IJavascriptObjectRepositoryInternal.TryGetProperty(long objectId, string name, out object result, out string exception)
{
return TryGetProperty(objectId, name, out result, out exception);
Expand All @@ -585,8 +589,14 @@ protected virtual bool TryGetProperty(long objectId, string name, out object res

try
{
result = property.GetValue(obj.Value);

if (obj.PropertyInterceptor == null)
{
result = property.GetValue(obj.Value);
}
else
{
result = obj.PropertyInterceptor.InterceptGet(() => property.GetValue(obj.Value), property.ManagedName);
}
return true;
}
catch (Exception ex)
Expand All @@ -596,7 +606,9 @@ protected virtual bool TryGetProperty(long objectId, string name, out object res

return false;
}
#endif

#if !NETCOREAPP
bool IJavascriptObjectRepositoryInternal.TrySetProperty(long objectId, string name, object value, out string exception)
{
return TrySetProperty(objectId, name, value, out exception);
Expand All @@ -618,8 +630,14 @@ protected virtual bool TrySetProperty(long objectId, string name, object value,
}
try
{
property.SetValue(obj.Value, value);

if (obj.PropertyInterceptor == null)
{
property.SetValue(obj.Value, value);
}
else
{
obj.PropertyInterceptor.InterceptSet((p) => property.SetValue(obj.Value, p), value, property.ManagedName);
}
return true;
}
catch (Exception ex)
Expand All @@ -629,6 +647,8 @@ protected virtual bool TrySetProperty(long objectId, string name, object value,

return false;
}
#endif


/// <summary>
/// Analyse the object and generate metadata which will
Expand Down
56 changes: 56 additions & 0 deletions CefSharp/ModelBinding/IPropertyInterceptor.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace CefSharp.ModelBinding
{
/// <summary>
/// Provides the capability intercepting get/set property calls made from javascript as part of the
/// JavascriptBinding (JSB) implementation.
/// </summary>
public interface IPropertyInterceptor
{
/// <summary>
/// Called before the get property is invokved. You are now responsible for evaluating
/// the property and returning the result.
/// </summary>
/// <param name="propertyGetter">A Func that represents the property to be called</param>
/// <param name="propertName">Name of the property to be called</param>
/// <returns>The property result</returns>
/// <example>
/// <code>
/// <![CDATA[
/// public object IPropertyInterceptor.InterceptGet(Func<object> propertyGetter, string propertyName)
/// {
/// object result = propertyGetter();
/// Debug.WriteLine("InterceptGet " + propertyName);
/// return result;
/// }
/// ]]>
/// </code>
/// </example>
object InterceptGet(Func<object> propertyGetter, string propertName);

/// <summary>
/// Called before the set property is invokved. You are now responsible for evaluating
/// the property.
/// </summary>
/// <param name="propertySetter">A Func that represents the property to be called</param>
/// <param name="parameter">paramater to be set to property</param>
/// <param name="propertName">Name of the property to be called</param>
/// <example>
/// <code>
/// <![CDATA[
/// public object IPropertyInterceptor.InterceptSet(Action<object> propertySetter, object parameter, string propertName)
/// {
/// Debug.WriteLine("InterceptSet " + propertName);
/// propertySetter(parameter);
/// }
/// ]]>
/// </code>
/// </example>
void InterceptSet(Action<Object> propertySetter, object parameter, string propertName);
}
}

0 comments on commit c417970

Please sign in to comment.