Skip to content

Commit

Permalink
JSB Add support for dynamic keyword (using ExpandoObject)
Browse files Browse the repository at this point in the history
As ExpandoObject implements IDictionary we can use it interchangeable with only minor breaking changes.
Those expecting Dictionary<string, object> should change to IDictionary<string, object> or ExpandoObject for their param types
This will also impact anyone who has implemented their own custom IBinder
I'd suggest changing the type checking from explicitly checking Dictionary<string, object> to use Type.IsAssignableFrom or another method of comparison
DefaultBinder binder has been updated to accommodate ExpandoObject (now being returned by ObjectsSerialization.cpp)
Add new async binding for testing new assignment method
Rewrite some of the async bound object tests to use await and let - modernize them
  • Loading branch information
amaitland committed Dec 21, 2017
1 parent a6f20e8 commit 7a0b06a
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 67 deletions.
10 changes: 7 additions & 3 deletions CefSharp.Core/Internals/Serialization/ObjectsSerialization.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "Primitives.h"

using namespace System::Collections::Generic;
using namespace System::Dynamic;

namespace CefSharp
{
Expand Down Expand Up @@ -62,17 +63,20 @@ namespace CefSharp
}
else if (type == VTYPE_DICTIONARY)
{
auto dict = gcnew Dictionary<String^, Object^>();

IDictionary<String^, Object^>^ expandoObj = gcnew ExpandoObject();
auto subDict = list->GetDictionary(index);
std::vector<CefString> keys;
subDict->GetKeys(keys);

for (auto i = 0; i < keys.size(); i++)
{
dict->Add(StringUtils::ToClr(keys[i]), DeserializeObject(subDict, keys[i], javascriptCallbackFactory));
auto key = StringUtils::ToClr(keys[i]);
auto value = DeserializeObject(subDict, keys[i], javascriptCallbackFactory);
expandoObj->Add(key, value);
}

result = dict;
result = expandoObj;
}

return result;
Expand Down
18 changes: 17 additions & 1 deletion CefSharp.Example/AsyncBoundObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading;

namespace CefSharp.Example
Expand Down Expand Up @@ -34,9 +36,11 @@ public string Hello(string name)
return "Hello " + name;
}

public void DoSomething()
public string DoSomething()
{
Thread.Sleep(1000);

return "Waited for 1000ms before returning";
}

public JsObject ReturnObject(string name)
Expand All @@ -55,5 +59,17 @@ public JsObject[] ObjectArray(string name)
new JsObject() { Value = "Item2" }
};
}

public string DynamiObjectList(IList<dynamic> objects)
{
var builder = new StringBuilder();

foreach(var browser in objects)
{
builder.Append("Browser(Name:" + browser.Name + ";Engine:" + browser.Engine.Name + ");");
}

return builder.ToString();
}
}
}
96 changes: 50 additions & 46 deletions CefSharp.Example/Resources/BindingTest.html
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
var p = document.createElement('p');
var br = document.createElement('br');
var br2 = document.createElement('br');
var hr = document.createElement('hr');
var title = document.createTextNode('Async Call: ');
var callText = document.createTextNode(call);
var endText = document.createTextNode(end);
Expand All @@ -24,28 +25,29 @@
p.appendChild(callText);
p.appendChild(br2);
p.appendChild(endText);

p.appendChild(hr);

asResult.appendChild(p);
}

function asyncError()
{
var call = "Async call (Throw Exception): " + Date();
let call = "Async call (Throw Exception): " + Date();
boundAsync.error().catch(function (e)
{
var end = "Error: " + e + "(" + Date() + ")";
writeAsyncResult(call, end);
});
}

function asyncDivOk()
async function asyncDivOk()
{
var call = "Async call (Divide 16 / 2): " + Date();
boundAsync.div(16, 2).then(function (res)
{
var end = "Result: " + res + "(" + Date() + ")";
writeAsyncResult(call, end);
});
let call = "Async call (Divide 16 / 2): " + Date();

let res = await boundAsync.div(16, 2);

let end = "Result: " + res + "(" + Date() + ")";
writeAsyncResult(call, end);
}

function asyncDivFail()
Expand All @@ -63,53 +65,57 @@
});
}

function asyncHello()
async function asyncHello()
{
var call = "Async call (Hello): " + Date();
boundAsync.hello('CefSharp').then(function (res)
{
var end = "Result: " + res + "(" + Date() + ")";
writeAsyncResult(call, end);
});
let call = "Async call (Hello): " + Date();
let res = await boundAsync.hello('CefSharp');

var end = "Result: " + res + "(" + Date() + ")";
writeAsyncResult(call, end);

return end;
}

function asyncDoSomething()
async function asyncDoSomething()
{
var call = "Async call (Long Running Task): " + Date();
boundAsync.doSomething().then(function (res)
{
var end = "Result: " + res + "(" + Date() + ")";
writeAsyncResult(call, end);
});
let call = "Async call (Long Running Task): " + Date();
let res = await boundAsync.doSomething();

let end = "Result: " + res + "(" + Date() + ")";
writeAsyncResult(call, end);
}

function asyncObject()
async function asyncObject()
{
var call = "Async call (Object): " + Date();
boundAsync.returnObject('CefSharp').then(function (res)
{
var end = "Result: " + JSON.stringify(res) + " (" + Date() + ")";
writeAsyncResult(call, end);
});
let call = "Async call (Object): " + Date();
var res = await boundAsync.returnObject('CefSharp');
var end = "Result: " + JSON.stringify(res) + " (" + Date() + ")";
writeAsyncResult(call, end);
}

function asyncObjectArray()
async function asyncObjectArray()
{
var call = "Async call (ObjectArray): " + Date();
boundAsync.objectArray('CefSharp').then(function (res)
{
var end = "Result: [ " + res.map(function (item) { return item.Value }) + " ] (" + Date() + ")";
writeAsyncResult(call, end);
});
let call = "Async call (ObjectArray): " + Date();
let res = await boundAsync.objectArray('CefSharp');
let end = "Result: [ " + res.map(function (item) { return item.Value }) + " ] (" + Date() + ")";
writeAsyncResult(call, end);
}

async function asyncDictionaryPassedAsParam()
{
let call = [{ Name : "Chrome", Engine : {Name : "WebKit"} }, { Name : "Chromium", Engine : {Name : "WebKit"} }, { Name : "Opera", Engine : {Name : "WebKit"} }];
let res = await boundAsync.dynamiObjectList(call);
writeAsyncResult(call, res);
}

asyncError();
asyncDivOk();
asyncDivFail();
asyncDoSomething();
asyncHello();
asyncObject();
asyncObjectArray();
asyncDictionaryPassedAsParam();
</script>
</p>
<p>
Expand Down Expand Up @@ -293,13 +299,8 @@
<script type="text/javascript">
function str2ab(str)
{
var buf = new ArrayBuffer(str.length * 2); // 2 bytes for each char
var bufView = new Uint16Array(buf);
for (var i = 0, strLen = str.length; i < strLen; i++)
{
bufView[i] = str.charCodeAt(i);
}
return buf;
var enc = new TextEncoder("utf-8");
return enc.encode(str).buffer;
}

document.write(bound.methodWithParams('With 1 Params', 'hello-world') + "<br/>");
Expand All @@ -313,8 +314,11 @@
document.write(bound.methodWithThreeParamsOneOptionalOneArray("Test", null) + "<br/>");
document.write(bound.methodWithThreeParamsOneOptionalOneArray(null, null, "Arg1", "Arg2") + "<br/>");

var buffer = str2ab("Testing array buffer");
document.write(bound.complexParamObject(buffer));
//CEF Does not currently support ArrayBuffer directly
//https://bitbucket.org/chromiumembedded/cef/issues/244
//https://bitbucket.org/chromiumembedded/cef/pull-requests/12/v8-renderer-add-basic-arraybuffer-support/diff
//var buffer = str2ab("Testing array buffer");
//document.write(bound.complexParamObject(buffer));
</script>
</p>
</body>
Expand Down
8 changes: 4 additions & 4 deletions CefSharp/Internals/JavascriptObjectRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,14 +174,14 @@ public bool TryCallMethod(long objectId, string name, object[] parameters, out o
{
var paramType = method.Parameters[i].Type;

if(parameters[i].GetType() == typeof(Dictionary<string, object>))
if(typeof(IDictionary<string, object>).IsAssignableFrom(parameters[i].GetType()))
{
var dictionary = (Dictionary<string, object>)parameters[i];
var dictionary = (IDictionary<string, object>)parameters[i];
parameters[i] = obj.Binder.Bind(dictionary, paramType);
}
else if (parameters[i].GetType() == typeof(List<object>))
else if (typeof(IList<object>).IsAssignableFrom(parameters[i].GetType()))
{
var list = (List<object>)parameters[i];
var list = (IList<object>)parameters[i];
parameters[i] = obj.Binder.Bind(list, paramType);
}
}
Expand Down
28 changes: 15 additions & 13 deletions CefSharp/ModelBinding/DefaultBinder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ public virtual object Bind(object obj, Type modelType)

if (val != null)
{
if (val.GetType() == typeof(Dictionary<string, object>))
if (typeof(IDictionary<string, object>).IsAssignableFrom(val.GetType()))
{
var subModel = Bind(val, genericType);
model.Add(subModel);
Expand All @@ -114,13 +114,18 @@ public virtual object Bind(object obj, Type modelType)
}
else
{
foreach (var modelProperty in bindingContext.ValidModelBindingMembers)
{
var val = GetValue(modelProperty.Name, bindingContext);

if (val != null)
//If the object type is a dictionary (we're using ExpandoObject instead of Dictionary now)
//Then attempt to bind all the members
if (typeof(IDictionary<string, object>).IsAssignableFrom(bindingContext.Object.GetType()))
{
foreach (var modelProperty in bindingContext.ValidModelBindingMembers)
{
BindValue(modelProperty, val, bindingContext);
var val = GetValue(modelProperty.Name, bindingContext);

if (val != null)
{
BindValue(modelProperty, val, bindingContext);
}
}
}
}
Expand Down Expand Up @@ -187,13 +192,10 @@ protected virtual object CreateModel(Type modelType, Type genericType)

protected virtual object GetValue(string propertyName, BindingContext context)
{
if (context.Object.GetType() == typeof(Dictionary<string, object>))
var dictionary = (IDictionary<string, object>)context.Object;
if (dictionary.ContainsKey(propertyName))
{
var dictionary = (Dictionary<string, object>)context.Object;
if (dictionary.ContainsKey(propertyName))
{
return dictionary[propertyName];
}
return dictionary[propertyName];
}

return null;
Expand Down

0 comments on commit 7a0b06a

Please sign in to comment.