1 /* 2 * Copyright (c) 2021 Huawei Device Co., Ltd. 3 * Licensed under the Apache License, Version 2.0 (the "License"); 4 * you may not use this file except in compliance with the License. 5 * You may obtain a copy of the License at 6 * 7 * http://www.apache.org/licenses/LICENSE-2.0 8 * 9 * Unless required by applicable law or agreed to in writing, software 10 * distributed under the License is distributed on an "AS IS" BASIS, 11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 * See the License for the specific language governing permissions and 13 * limitations under the License. 14 */ 15 16 #ifndef FOUNDATION_ACE_FRAMEWORKS_DECLARATIVE_FRONTEND_ENGINE_BINDINGS_IMPLEMENTATION_H 17 #define FOUNDATION_ACE_FRAMEWORKS_DECLARATIVE_FRONTEND_ENGINE_BINDINGS_IMPLEMENTATION_H 18 19 #include <cmath> 20 #include <string> 21 #include <unordered_map> 22 #include <vector> 23 24 #include "base/log/log.h" 25 #include "base/memory/ace_type.h" 26 #include "bindings_defines.h" 27 #include "frameworks/bridge/common/utils/function_traits.h" 28 #include "frameworks/bridge/declarative_frontend/engine/js_ref_ptr.h" 29 #include "frameworks/bridge/declarative_frontend/engine/js_types.h" 30 31 namespace OHOS::Ace::Framework { 32 33 enum MethodOptions : uint8_t { 34 NONE = 0, 35 RETURN_SELF = 1 << 0, // for chaining 36 STRICT_TYPE_CHECK = 1 << 1, 37 }; 38 39 class IFunctionBinding { 40 public: IFunctionBinding(const char * name,MethodOptions options)41 IFunctionBinding(const char* name, MethodOptions options) : name_(name), options_(options) {} ~IFunctionBinding()42 virtual ~IFunctionBinding() {} 43 Name()44 const char* Name() const 45 { 46 return name_; 47 } 48 Options()49 MethodOptions Options() const 50 { 51 return options_; 52 } 53 54 private: 55 const char* name_; 56 MethodOptions options_; 57 }; 58 59 template<typename Class, typename ReturnType, typename... Args> 60 class FunctionBinding : public IFunctionBinding { 61 using FunctionPtr = ReturnType (Class::*)(Args...); 62 63 public: FunctionBinding(const char * name,MethodOptions options,FunctionPtr func)64 FunctionBinding(const char* name, MethodOptions options, FunctionPtr func) 65 : IFunctionBinding(name, options), func_(func) 66 {} 67 68 ~FunctionBinding() override = default; 69 Get()70 FunctionPtr Get() 71 { 72 return func_; 73 } 74 75 private: 76 FunctionPtr func_; 77 }; 78 79 template<typename ReturnType, typename... Args> 80 class StaticFunctionBinding : public IFunctionBinding { 81 using FunctionPtr = ReturnType (*)(Args...); 82 83 public: StaticFunctionBinding(const char * name,MethodOptions options,FunctionPtr func)84 StaticFunctionBinding(const char* name, MethodOptions options, FunctionPtr func) 85 : IFunctionBinding(name, options), func_(func) 86 {} 87 ~StaticFunctionBinding() override = default; 88 Get()89 FunctionPtr Get() 90 { 91 return func_; 92 } 93 94 private: 95 FunctionPtr func_; 96 }; 97 98 /** 99 * \brief A class template that binds C++ classes to Javascript. 100 * 101 * This class is the entry point for binding classes and methods to Javascript regardless of which engine is 102 * underneath. In the following text, a class C is the C++ class to be bound to Javascript, and engine-specific 103 * implementation is abbeviated as ESI. 104 * 105 * Methods and member variables registered to javascript are equivalent to a property in a javascript object. 106 * \p Method(...) is used to register methods. If a member function is registered directly, the ESI should take care of 107 * the value conversion between Javascript and C++ types. 108 * 109 * If more control is required than just mere conversions between JS and C++ (such as error checks, weak types, storing 110 * JS objects etc.), methods can be bound to a signature that enables this control. The signautre is <tt> void(*)(const 111 * JSCallbackInfo&)</tt>. This is done to work regardless of the engine underneath. See \p JSRef , \p JSRefPtr for more 112 * info on usage. 113 * 114 * If a direct communication with the engine is required when communicating with javascript, member functions and 115 * callbacks with engine-specific signatures can be registered to Javascript and the embedder can access the engine APIs 116 * directly. Additional options can be passed to \p Method(...) when binding member functions, such as the object can 117 * return itself in case of functions that do not return a value, so that in Javascript one can do so-called "chaining": 118 * \code{.js} 119 * object.width(10).height(10).top(0).left(20) 120 * \endcode 121 * 122 * There is a general constructor for every bound class. When calling \p Bind however, the constructor argument types 123 * must be passes as template arguments (see example) so that the automatic conversion checks and converts the values. 124 * 125 * 126 * 127 * Engine-specific implementation guide: 128 * This code is using the curiously recurring template pattern (CRTP) idiom as neither function templates nor static 129 * methods can be overloaded. Therefore, an ESI "inherits" from this class because these class methods call methods 130 * that have to be defined, otherwise the compiler will report an error. As the binding itself is considerably 131 * engine-specific, this class implements only engine-agnostic attributes such as class names, method names, member 132 * function pointers etc. The actual value conversion is to be implemented by the engine implementation, and choosing 133 * the engine to be used should be conducted by the build system. 134 * 135 * ESIs are expected to define several aliases: 136 * 1. one called \p JSClass with the class C and the ESI class template as template 137 * arguments to this class: 138 * \code{.cpp} 139 * template<typename C> 140 * using JSClass = JSClassImpl<C, MyJSEngineClassImpl> 141 * \endcode 142 * 143 * Inside the ImplDetail class template: 144 * 2. \p BindingTarget that corresponds to an ESI object template 145 * \code{.cpp} 146 * // A v8 object template 147 * using BindingTarget = v8::Local<v8::ObjectTemplate>; 148 * // A QJS object template 149 * using BindingTarget = JSValue; 150 * \endcode 151 * 152 * 3. \p FunctionCallback and \p MemberFunctionCallback corresponding to ESI callback signatures: 153 * \code{.cpp} 154 * // v8 callback signatures 155 * using FunctionCallback = void (*)(const v8::FunctionCallbackInfo<v8::Value>&); 156 * using MemberFunctionCallback = void (C::*)(const v8::FunctionCallbackInfo<v8::Value>&); 157 * // QJS callback signatures 158 * using FunctionCallback = JSValue (*)(JSContext* ctx, JSValueConst thisObj, int argc, JSValueConst* argv); 159 * using MemberFunctionCallback = JSValue (C::*)(JSContext* ctx, JSValueConst thisObj, int argc, JSValueConst* argv); 160 * \endcode 161 * 162 * 163 * \tparam C The C++ class to be bound 164 * \tparam ImplDetail an engine-specific class template that takes class C as a template argument 165 * 166 * \example Binding classes TwoDPoint and ThreeDPoint as "Point2" and "Point3" in javascript, and registering its 167 * methods. 168 * 169 * \code{.cpp} 170 * class TwoDPoint { 171 * public: 172 * TwoDPoint(float x, float y) : x_(x), y_(y) {} 173 * ~TwoDPoint() {} 174 * 175 * void SetX(float x) { x_ = x; } 176 * void SetY(float y) { y_ = y; } 177 * virtual void Print() { std::cout << "Point(" << x_ << ", " << y_ << ")" << std::endl; } 178 * 179 * static void Parse(const JSCallbackInfo& info) { 180 * JSRef<JSVal> arg = info[0]; 181 * if (arg->IsString()) { 182 * std::string strArg = arg->ToString(); 183 * TwoDPoint* instance = ParseFromString(); 184 * if (instance) { 185 * info.SetReturnValue(instance); 186 * } else { 187 * JSException::Throw("Error parsing Point2(%s)", strArg.c_str()); 188 * } 189 * } else { 190 * JSException::Throw("Point2.Parse: Argument is not a string!"); 191 * } 192 * } 193 * 194 * float GetX() { return x_; } 195 * float GetY() { return y_; } 196 * private: 197 * static TwoDPoint* ParseFromString(const std::string& str) { 198 * // some parsing code: 199 * auto [x,y] = Parse(str); 200 * return new TwoDPoint(x,y); 201 * } 202 * float x_; 203 * float y_; 204 * }; 205 * 206 * class ThreeDPoint : public TwoDPoint { 207 * public: 208 * ThreeDPoint(float x, float y, float z) : TwoDPoint(x,y) : z_(z) {} 209 * ~ThreeDPoint() {} 210 * 211 * void SetZ(float z) { z_ = z; } 212 * virtual void Print() override { std::cout << "Point(" << x_ << ", " << y_ << ", " << z_ << ")" << std::endl; } 213 * 214 * float GetZ() { return z_; } 215 * private: 216 * float z_; 217 * }; 218 * 219 * // We are using V8 engine for this example: 220 * template<typename C> 221 * using JSClass = JSClassImpl<C, V8Class>; 222 * 223 * // Somewhere in engine-initialization: 224 * JSClass<TwoDPoint>::Declare("Point2"); 225 * JSClass<TwoDPoint>::Method("setX", &TwoDPoint::SetX, MethodOptions::RETURN_SELF); 226 * JSClass<TwoDPoint>::Method("getX", &TwoDPoint::GetX); 227 * JSClass<TwoDPoint>::Method("setY", &TwoDPoint::SetY, MethodOptions::RETURN_SELF); 228 * JSClass<TwoDPoint>::Method("getY", &TwoDPoint::GetY); 229 * JSClass<TwoDPoint>::Method("print", &TwoDPoint::Print); 230 * JSClass<TwoDPoint>::StaticMethod("parse", &TwoDPoint::Parse); 231 * JSClass<TwoDPoint>::Bind<float, float>(globalObject); // Note the template arguments. Here we are specifying 232 * // that we're expecting the JS constructor to accept 233 * // two "float" arguments 234 * 235 * JSClass<ThreeDPoint>::Declare("Point3"); 236 * JSClass<ThreeDPoint>::Method("setZ", &ThreeDPoint::SetZ, MethodOptions::RETURN_SELF); 237 * JSClass<ThreeDPoint>::Method("getZ", &ThreeDPoint::GetZ); 238 * JSClass<ThreeDPoint>::Inherit<TwoDPoint>(); 239 * JSClass<ThreeDPoint>::Bind<float, float, float>(globalObject); 240 * // Note the template arguments. Here we are specifying 241 * // that we're expecting the JS constructor to accept 242 * // three "float" arguments 243 * \endcode 244 * 245 * \code{.js} 246 * // C++ call tree 247 * let point = new Point2(1,2); // V8Class<TwoDPoint>::InternalConstructor<float, float> 248 * point.print(); // V8Class<TwoDPoint>::InternalMethodCallback<void> 249 * // "Point(1,2)" 250 * let other = Point2.parse("(3,4)"); // V8Class<TwoDPoint>::JSStaticMethodCallback 251 * console.log("point.x is " + point.getX()); // V8Class<TwoDPoint>::InternalMethodCallback<float> 252 * // "point.x is 1" 253 * console.log("other.x is " + other.getX()); // V8Class<TwoDPoint>::InternalMethodCallback<float> 254 * // "other.x is 3" 255 * point.setX(5).setY(10); // V8Class<TwoDPoint>::InternalMethodCallback<void, float> 256 * // V8Class<TwoDPoint>::InternalMethodCallback<void, float> 257 * point.print(); // V8Class<TwoDPoint>::InternalMethodCallback<void> 258 * // "Point(5,10)" 259 * let anotherPoint = new Point3(1,2,3); // V8Class<ThreeDPoint>::InternalConstructor<float, float, float> 260 * anotherPoint.setX(5).setY(6).setZ(7) // V8Class<TwoDPoint>::InternalMethodCallback<void, float> 261 * // V8Class<TwoDPoint>::InternalMethodCallback<void, float> 262 * // V8Class<ThreeDPoint>::InternalMethodCallback<void, float> 263 * anotherpoint.print(); // V8Class<ThreeDPoint>::InternalMethodCallback<void> 264 * // "Point(5,6,7)" 265 * \endcode 266 * \class JSClassImpl 267 */ 268 template<typename C, template<typename> typename ImplDetail> 269 class JSClassImpl { 270 public: 271 JSClassImpl() = delete; 272 273 /** 274 * Declare class C that will be exposed with the given \p name in Javascript 275 * \note This must be always called first before any other registrations. The engine-specific implementations 276 * should instantiate object templates with this call 277 * \param name A string literal 278 * \static 279 */ 280 static void Declare(const char* name); 281 282 /** 283 * Register a method that is a member of a class C or its base class. 284 * \note Trying to bind a method of unrelated classes will result in a compile error 285 * \param name The name of the method that will be exposed as in Javascript 286 * \param func A member-function pointer belonging to class C's base class 287 * \param options Method options flags, default value is NONE 288 * 289 * \tparam Base A base class to \p C . No need to specify, since it will be deducted from the function pointer 290 * \tparam R The return type of \p func . No need to specify, since it will be deducted from the function pointer 291 * \tparam Args... Types of function arguments of \p func . No need to specify, since they will be deducted from 292 * the function pointer 293 * \static 294 */ 295 template<typename Base, typename R, typename... Args> 296 static void Method(const char* name, R (Base::*func)(Args...), MethodOptions options = MethodOptions::NONE); 297 298 /** 299 * Register a static method of class C. 300 * \param name The name of the method that will be exposed as in Javascript 301 * \param func A static function 302 * \param options Method options flags, default value is NONE 303 * 304 * \tparam R The return type of \p func . No need to specify, since it will be deducted from the function pointer 305 * \tparam Args... Types of function arguments of \p func . No need to specify, since they will be deducted from 306 * the function pointer 307 * \static 308 */ 309 template<typename R, typename... Args> 310 static void StaticMethod(const char* name, R (*func)(Args...), MethodOptions options = MethodOptions::NONE); 311 312 /** 313 * Register a static method of class C with a void(*)(const JSCallbackInfo&) signature. 314 * \param name The name of the method that will be exposed as in Javascript 315 * \param func A static function with void(*)(const JSCallbackInfo&) signature 316 * \static 317 */ 318 static void StaticMethod(const char* name, JSFunctionCallback func); 319 320 /** 321 * Register a method that is a member of a related class T with an engine-specific callback signature 322 * 323 * \tparam T A class that is either equivalent or a base to C 324 * \param name The name of the method that will be exposed as in Javascript 325 * \param callback A member-function pointer belonging to class C's class with engine-specific signature 326 * \static 327 */ 328 template<typename T> 329 static void CustomMethod(const char* name, MemberFunctionCallback<T> callback); 330 331 /** 332 * Register a method with an engine-specific callback signature 333 * 334 * \param name The name of the method that will be exposed as in Javascript 335 * \param callback A function pointer with the engine-specific signature 336 * \static 337 */ 338 static void CustomMethod(const char* name, FunctionCallback callback); 339 340 /** 341 * Register a method with an generic callback signature 342 * 343 * \param name The name of the method that will be exposed as in Javascript 344 * \param callback A function pointer with the engine-specific signature 345 * \static 346 */ 347 template<typename T> 348 static void CustomMethod(const char* name, JSMemberFunctionCallback<T> callback); 349 350 template<typename T> 351 static void CustomProperty(const char* name, MemberFunctionGetCallback<T> getter, 352 MemberFunctionSetCallback<T> setter); 353 354 static void CustomProperty(const char* name, FunctionGetCallback getter, 355 FunctionSetCallback setter); 356 357 template<typename T> 358 static void CustomProperty(const char* name, JSMemberFunctionCallback<T> getter, 359 JSMemberFunctionCallback<T> setter); 360 /** 361 * Register a static method with an engine-specific callback signature 362 * 363 * \param name The name of the method that will be exposed as in Javascript 364 * \param callback A function pointer with the engine-specific signature 365 * \static 366 */ 367 static void CustomStaticMethod(const char* name, FunctionCallback callback); 368 369 static void ExoticGetter(ExoticGetterCallback callback); 370 static void ExoticSetter(ExoticSetterCallback callback); 371 static void ExoticHasProperty(ExoticHasPropertyCallback callback); 372 373 template<typename T> 374 static void StaticConstant(const char* name, T value); 375 376 /** 377 * Bind the class to Javascript with a custom constructor that has engine-specific callback signature 378 * 379 * \param bindTarget An object template to bind this class to. 380 * \param ctor Constructor 381 * \static 382 */ 383 static void Bind(BindingTarget bindTarget, FunctionCallback ctor); 384 385 /** 386 * Bind the class to Javascript with custom constructor, destructor and GC mark callbacks. 387 * If no destructor callback is specified, the C++ instance is simply "delete"-d on garbage collection sweeps. 388 * 389 * \param bindTarget An object template to bind this class to 390 * \param ctor Constructor with void(*)(const JSCallbackInfo&) signature 391 * \param dtor Destructor with void(*)(C* instance) signature (optional) 392 * \param gcMark A GC mark callback with void(*)(C* instance, const JSGCMarkCallbackInfo&) signature (optional) 393 * \static 394 */ 395 static void Bind(BindingTarget bindTarget, JSFunctionCallback ctor, JSDestructorCallback<C> dtor = nullptr, 396 JSGCMarkCallback<C> gcMark = nullptr); 397 398 /** 399 * Bind the class to Javascript with optional destructor and GC mark callbacks. 400 * If no destructor callback is specified, the C++ instance is simply "delete"-d on garbage collection sweeps. 401 * 402 * \tparam Args... A list of argument types that the constructor of class C accepts. 403 * \param bindTarget An object template to bind this class to. 404 * \param dtor Destructor with void(*)(C* instance) signature (optional) 405 * \param gcMark A GC mark callback with void(*)(C* instance, const JSGCMarkCallbackInfo&) signature (optional) 406 * \static 407 */ 408 template<typename... Args> 409 static void Bind( 410 BindingTarget bindTarget, JSDestructorCallback<C> dtor = nullptr, JSGCMarkCallback<C> gcMark = nullptr); 411 412 /** 413 * Inherit all bound methods and properties from \p Base 414 * \note A binding for the base class must exist beforehand with 415 * \code JSClassImpl<Base,Impl>::Declare("MyBaseClass") \endcode 416 * 417 * \tparam Base A base class of C 418 */ 419 template<typename Base> 420 static void Inherit(); 421 422 static IFunctionBinding* GetFunctionBinding(int id); 423 static IFunctionBinding* GetGetFunctionBinding(int id); 424 static IFunctionBinding* GetSetFunctionBinding(int id); 425 426 /** 427 * Get the Javascript name of class C 428 * \return The javascript name 429 */ 430 static const char* JSName(); 431 432 /** 433 * Create new instance of declared class 434 * \return new JS object instance 435 */ 436 static JSRef<JSObject> NewInstance(); 437 438 private: 439 static std::string jsName; 440 /* OPTIMIZE(cvetan): Functions can be stored at compile-time with an additional index as a template parameter, for 441 example: 442 443 template<int N, typename RetType, typename... Args> 444 static RetType(C::*function_)(Args...); 445 446 But only if current approach proves to be a bottleneck. 447 */ 448 static std::unordered_map<int, IFunctionBinding*> functions_; 449 static std::unordered_map<int, IFunctionBinding*> getFunctions_; 450 static std::unordered_map<int, IFunctionBinding*> setFunctions_; 451 static int nextFreeId_; 452 }; 453 454 }; // namespace OHOS::Ace::Framework 455 456 #include "bindings_implementation.inl" 457 458 #endif // FOUNDATION_ACE_FRAMEWORKS_DECLARATIVE_FRONTEND_ENGINE_BINDINGS_IMPLEMENTATION_H 459