SIGN IN SIGN UP
cefsharp / CefSharp UNCLAIMED

.NET (WPF and Windows Forms) bindings for the Chromium Embedded Framework

0 0 0 C#
// Copyright © 2019 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.
#pragma once
#include "include/cef_v8.h"
#include "RegisterBoundObjectRegistry.h"
Refactor CefSharp.Core into CefSharp.Core.Runtime (#3311) * Net Core - Rename CefSharp.Core.dll to CefSharp.Core.Runtime.dll Partial rename, only Net Core, folder not renamed * Net Core - Rename CefSharp.Core.RefAssembly to CefSharp.Core.netcore Remove GenApi * Core - Rename CefDragDataWrapper to DragData Move into CefSharp.Core namespace * WinForms/WPF/OffScreen - Migrate from GitLink command line to Nuget package * Net Core - Refactor to have CefSharp.Core.dll contain only public Api * Net Core - Remove CefSharp.Core.RefAssembly * Net Core - Change CefSharp.Core.netcore output folder * Net Core - Restructure nuget packages * Net Core - Add Cefsharp.Core.Runtime.RefAssembly * Net Core - Hide CLI/C++ classes from intellisense Make sure users don't attempt to load them directly * Rename CefSharp.Core to CefSharp.Core.Runtime * Core - Restructure Net 4.5.2 packages to use CefSharp.Core.dll anycpu variant Attempt to load CefSharp.Core.Runtime at runtime rather than having to use msbuild to copy the correct version * Rename CefSharp.Core.netcore to CefSharp.Core * WPF/WinForms/OffScreen - Change from x86/64 to AnyCPU As they are all managed assemblies they can target AnyCPU. Includes CefSharp.dll * Convert RequestContextBuilder from C++ to C# Now part of the CefSharp.Core PublicApi * Update version number to 87.1.11 * Migrate more of the public Api to C# * Net Core - Basic restructure complete * Net Core - ModuleInitializer (Doesn't work yet) * Remove direct references to BrowserSettings * Net Core - ModuleInitializer load CefShar.Core.Runtime.dl * Net Core - Load libcef.dll via CLR Module initializer If no RID is specified then we can load libcef.dll using the module initializer * Add version to CefSharp.Core * Remove dependency on CefSharp.Core.Runtime Rewrite common targets * AnyCPU app.config transform Improve AnyCPU support * Improve Net Core 3 support Only delete CefSharp.Core.Runtime.dll when AnyCPU * Nuget - Add CefSharp.Core.Runtime reference when TargetFramework = NetCore * Fix Typos Based on #3306 * Net Core - Rename CefSharp.Core.Runtime RefAssembly source file * Net Full - Generate CefSharp.Core.Runtime Ref Assembly For now the powershell build script generates the .cs file based on a x86 release build.ps1 It's not possible to directly install GenApi as it requires a Sdk style project * Net Core - Old packages copy files to required folders * Test - Install newer .Net Compiler and set Lang Version to 7.3 * Net Core - Exclude Net 452 Runtime generated reference source * Core - Add Refactoring TODO * Ref Assembly - Generate source as part of build - Move ref assembly source generate into GenerateRefAssemblySource.ps1 - Call before project build Runs locally, see if Appveyor has a problem with the powershell script execution * Core - Add more factory methods to create instances of managed wrappers * Net Core - Make Initialzier properties internal Not quite sure what the public API should look like as yet, so making internal for now.
2020-12-16 10:47:34 +10:00
#include "..\CefSharp.Core.Runtime\Internals\Messaging\Messages.h"
#include "..\CefSharp.Core.Runtime\Internals\Serialization\Primitives.h"
using namespace System;
using namespace CefSharp::Internals::Messaging;
using namespace CefSharp::Internals::Serialization;
using namespace CefSharp::BrowserSubprocess;
namespace CefSharp
{
namespace BrowserSubprocess
{
const CefString kBindObjectAsync = CefString("BindObjectAsync");
const CefString kBindObjectAsyncCamelCase = CefString("bindObjectAsync");
private class BindObjectAsyncHandler : public CefV8Handler
{
private:
gcroot<RegisterBoundObjectRegistry^> _callbackRegistry;
gcroot<Dictionary<String^, JavascriptObject^>^> _javascriptObjects;
gcroot<CefBrowserWrapper^> _browserWrapper;
public:
BindObjectAsyncHandler(RegisterBoundObjectRegistry^ callbackRegistery, Dictionary<String^, JavascriptObject^>^ javascriptObjects, CefBrowserWrapper^ browserWrapper)
{
_callbackRegistry = callbackRegistery;
_javascriptObjects = javascriptObjects;
_browserWrapper = browserWrapper;
}
~BindObjectAsyncHandler()
{
_callbackRegistry = nullptr;
_javascriptObjects = nullptr;
_browserWrapper = nullptr;
}
bool Execute(const CefString& name, CefRefPtr<CefV8Value> object, const CefV8ValueList& arguments, CefRefPtr<CefV8Value>& retval, CefString& exception) override
{
auto context = CefV8Context::GetCurrentContext();
if (context.get() && context->Enter())
{
try
{
auto params = CefListValue::Create();
//We need to store a seperate index into our params as
//there are instances we skip over already cached objects
//and end up with empty strings in the list.
//e.g. first object is already bound/cached, we previously
//second object isn't we end up with a list of "", "secondObject"
int paramsIndex = 0;
auto boundObjectRequired = false;
auto notifyIfAlreadyBound = false;
auto ignoreCache = false;
auto cachedObjects = gcnew List<JavascriptObject^>();
//TODO: Create object to represent this information
auto objectNamesWithBoundStatus = gcnew List<Tuple<String^, bool, bool>^>();
auto objectCount = 0;
if (arguments.size() > 0)
{
objectCount = (int)arguments.size();
//If first argument is an object, we'll see if it contains config values
if (arguments[0]->IsObject())
{
//Upper and camelcase options are supported
notifyIfAlreadyBound = GetV8BoolValue(arguments[0], "NotifyIfAlreadyBound", "notifyIfAlreadyBound");
ignoreCache = GetV8BoolValue(arguments[0], "IgnoreCache", "ignoreCache");
//If we have a config object then we remove that from the count
objectCount = objectCount - 1;
}
auto global = context->GetGlobal();
//Loop through all arguments and ignore anything that's not a string
for (size_t i = 0; i < arguments.size(); i++)
{
//Validate arg as being a string
if (arguments[i]->IsString())
{
auto objectName = arguments[i]->GetStringValue();
auto managedObjectName = StringUtils::ToClr(objectName);
auto alreadyBound = global->HasValue(objectName);
auto cached = false;
//Check if the object has already been bound
if (alreadyBound)
{
cached = _javascriptObjects->ContainsKey(managedObjectName);
}
else
{
//If no matching object found then we'll add the object name to the list
boundObjectRequired = true;
params->SetString(paramsIndex++, objectName);
JavascriptObject^ obj;
if (_javascriptObjects->TryGetValue(managedObjectName, obj))
{
cachedObjects->Add(obj);
cached = true;
}
}
objectNamesWithBoundStatus->Add(Tuple::Create(managedObjectName, alreadyBound, cached));
}
}
}
else
{
//No objects names were specified so we default to makeing the request
boundObjectRequired = true;
}
auto frame = context->GetFrame();
if (frame.get() && frame->IsValid())
{
if (boundObjectRequired || ignoreCache)
{
//If the number of cached objects matches the number of args
//(we have a cached copy of all requested objects)
//then we'll immediately bind the cached objects
//If objectCount and cachedObject count are both 0 then we'll
//send the kJavascriptRootObjectRequest message
//https://github.com/cefsharp/CefSharp/issues/3470
if (objectCount > 0 && cachedObjects->Count == objectCount && ignoreCache == false)
{
if (Object::ReferenceEquals(_browserWrapper, nullptr))
{
exception = "BindObjectAsyncHandler::Execute - Browser wrapper null, unable to bind objects";
return true;
}
auto browser = context->GetBrowser();
auto rootObjectWrappers = _browserWrapper->JavascriptRootObjectWrappers;
JavascriptRootObjectWrapper^ rootObject;
if (!rootObjectWrappers->TryGetValue(frame->GetIdentifier(), rootObject))
{
#ifdef NETCOREAPP
rootObject = gcnew JavascriptRootObjectWrapper(browser->GetIdentifier());
#else
rootObject = gcnew JavascriptRootObjectWrapper(browser->GetIdentifier(), _browserWrapper->BrowserProcess);
#endif
rootObjectWrappers->TryAdd(frame->GetIdentifier(), rootObject);
}
//Cached objects only contains a list of objects not already bound
rootObject->Bind(cachedObjects, context->GetGlobal());
//Objects already bound or ignore cache
CefRefPtr<CefV8Value> promiseResolve;
CefRefPtr<CefV8Exception> promiseException;
auto promiseResolveScript = StringUtils::ToNative("Promise.resolve({Success:true, Count:" + cachedObjects->Count + ", Message:'OK'});");
if (context->Eval(promiseResolveScript, CefString(), 0, promiseResolve, promiseException))
{
retval = promiseResolve;
}
else
{
exception = promiseException->GetMessage();
return true;
}
NotifyObjectBound(frame, objectNamesWithBoundStatus);
}
else
{
CefRefPtr<CefV8Value> promiseData;
CefRefPtr<CefV8Exception> promiseException;
//this will create a promise and give us the reject/resolve functions {p: Promise, res: resolve(), rej: reject()}
if (!context->Eval(CefAppUnmanagedWrapper::kPromiseCreatorScript, CefString(), 0, promiseData, promiseException))
{
exception = promiseException->GetMessage();
return true;
}
//when refreshing the browser this is sometimes null, in this case return true and log message
//https://github.com/cefsharp/CefSharp/pull/2446
if (promiseData == nullptr)
{
LOG(WARNING) << "BindObjectAsyncHandler::Execute promiseData returned nullptr";
return true;
}
//return the promose
retval = promiseData->GetValue("p");
//References to the promise resolve and reject methods
auto resolve = promiseData->GetValue("res");
auto reject = promiseData->GetValue("rej");
auto callback = gcnew JavascriptAsyncMethodCallback(context, resolve, reject);
auto request = CefProcessMessage::Create(kJavascriptRootObjectRequest);
auto argList = request->GetArgumentList();
//Obtain a callbackId then send off the Request for objects
auto callbackId = _callbackRegistry->SaveMethodCallback(callback);
SetInt64(argList, 0, callbackId);
argList->SetList(1, params);
frame->SendProcessMessage(CefProcessId::PID_BROWSER, request);
}
}
else
{
//Objects already bound or ignore cache
CefRefPtr<CefV8Value> promiseResolve;
CefRefPtr<CefV8Exception> promiseException;
auto promiseResolveScript = CefString("Promise.resolve({Success:false, Count:0, Message:'Object(s) already bound'});");
if (context->Eval(promiseResolveScript, CefString(), 0, promiseResolve, promiseException))
{
retval = promiseResolve;
if (notifyIfAlreadyBound)
{
NotifyObjectBound(frame, objectNamesWithBoundStatus);
}
}
else
{
exception = promiseException->GetMessage();
}
}
}
else
{
exception = "BindObjectAsyncHandler::Execute - Frame is invalid.";
}
}
finally
{
context->Exit();
}
}
else
{
exception = "BindObjectAsyncHandler::Execute - Unable to Get or Enter Context";
}
return true;
}
private:
void NotifyObjectBound(const CefRefPtr<CefFrame> frame, List<Tuple<String^, bool, bool>^>^ objectNamesWithBoundStatus)
{
//Send message notifying Browser Process of which objects were bound
//We do this after the objects have been created in the V8Context to gurantee
//they are accessible.
auto msg = CefProcessMessage::Create(kJavascriptObjectsBoundInJavascript);
auto args = msg->GetArgumentList();
auto boundObjects = CefListValue::Create();
auto index = 0;
for each (auto obj in objectNamesWithBoundStatus)
{
auto dict = CefDictionaryValue::Create();
auto name = obj->Item1;
auto alreadyBound = obj->Item2;
auto isCached = obj->Item3;
dict->SetString("Name", StringUtils::ToNative(name));
dict->SetBool("IsCached", isCached);
dict->SetBool("AlreadyBound", alreadyBound);
boundObjects->SetDictionary(index++, dict);
}
args->SetList(0, boundObjects);
frame->SendProcessMessage(CefProcessId::PID_BROWSER, msg);
}
bool GetV8BoolValue(const CefRefPtr<CefV8Value> val, const CefString key, const CefString camelCaseKey)
{
if (val->HasValue(key))
{
auto obj = val->GetValue(key);
if (obj->IsBool())
{
return obj->GetBoolValue();
}
}
if (val->HasValue(camelCaseKey))
{
auto obj = val->GetValue(camelCaseKey);
if (obj->IsBool())
{
return obj->GetBoolValue();
}
}
return false;
}
2021-09-03 10:51:18 +10:00
IMPLEMENT_REFCOUNTINGM(BindObjectAsyncHandler);
};
}
}