1 /* 2 * Copyright (c) 2021-2022 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 abbreviated 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 signature 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 * 171 * // We are using V8 engine for this example: 172 * template<typename C> 173 * using JSClass = JSClassImpl<C, V8Class>; 174 * 175 * // Somewhere in engine-initialization: 176 * JSClass<TwoDPoint>::Declare("Point2"); 177 * JSClass<TwoDPoint>::Method("setX", &TwoDPoint::SetX, MethodOptions::RETURN_SELF); 178 * JSClass<TwoDPoint>::Method("getX", &TwoDPoint::GetX); 179 * JSClass<TwoDPoint>::Method("setY", &TwoDPoint::SetY, MethodOptions::RETURN_SELF); 180 * JSClass<TwoDPoint>::Method("getY", &TwoDPoint::GetY); 181 * JSClass<TwoDPoint>::Method("print", &TwoDPoint::Print); 182 * JSClass<TwoDPoint>::StaticMethod("parse", &TwoDPoint::Parse); 183 * JSClass<TwoDPoint>::Bind<float, float>(globalObject); // Note the template arguments. Here we are specifying 184 * // that we're expecting the JS constructor to accept 185 * // two "float" arguments 186 * 187 * JSClass<ThreeDPoint>::Declare("Point3"); 188 * JSClass<ThreeDPoint>::Method("setZ", &ThreeDPoint::SetZ, MethodOptions::RETURN_SELF); 189 * JSClass<ThreeDPoint>::Method("getZ", &ThreeDPoint::GetZ); 190 * JSClass<ThreeDPoint>::Inherit<TwoDPoint>(); 191 * JSClass<ThreeDPoint>::Bind<float, float, float>(globalObject); 192 * // Note the template arguments. Here we are specifying 193 * // that we're expecting the JS constructor to accept 194 * // three "float" arguments 195 * \endcode 196 * 197 * \code{.js} 198 * // C++ call tree 199 * let point = new Point2(1,2); // V8Class<TwoDPoint>::InternalConstructor<float, float> 200 * point.print(); // V8Class<TwoDPoint>::InternalMethodCallback<void> 201 * // "Point(1,2)" 202 * let other = Point2.parse("(3,4)"); // V8Class<TwoDPoint>::JSStaticMethodCallback 203 * console.log("point.x is " + point.getX()); // V8Class<TwoDPoint>::InternalMethodCallback<float> 204 * // "point.x is 1" 205 * console.log("other.x is " + other.getX()); // V8Class<TwoDPoint>::InternalMethodCallback<float> 206 * // "other.x is 3" 207 * point.setX(5).setY(10); // V8Class<TwoDPoint>::InternalMethodCallback<void, float> 208 * // V8Class<TwoDPoint>::InternalMethodCallback<void, float> 209 * point.print(); // V8Class<TwoDPoint>::InternalMethodCallback<void> 210 * // "Point(5,10)" 211 * let anotherPoint = new Point3(1,2,3); // V8Class<ThreeDPoint>::InternalConstructor<float, float, float> 212 * anotherPoint.setX(5).setY(6).setZ(7) // V8Class<TwoDPoint>::InternalMethodCallback<void, float> 213 * // V8Class<TwoDPoint>::InternalMethodCallback<void, float> 214 * // V8Class<ThreeDPoint>::InternalMethodCallback<void, float> 215 * anotherpoint.print(); // V8Class<ThreeDPoint>::InternalMethodCallback<void> 216 * // "Point(5,6,7)" 217 * \endcode 218 * \class JSClassImpl 219 */ 220 template<typename C, template<typename> typename ImplDetail> 221 class JSClassImpl { 222 public: 223 JSClassImpl() = delete; 224 225 /** 226 * Declare class C that will be exposed with the given \p name in Javascript 227 * \note This must be always called first before any other registrations. The engine-specific implementations 228 * should instantiate object templates with this call 229 * \param name A string literal 230 * \static 231 */ 232 static void Declare(const char* name); 233 234 /** 235 * Register a method that is a member of a class C or its base class. 236 * \note Trying to bind a method of unrelated classes will result in a compile error 237 * \param name The name of the method that will be exposed as in Javascript 238 * \param func A member-function pointer belonging to class C's base class 239 * \param options Method options flags, default value is NONE 240 * 241 * \tparam Base A base class to \p C . No need to specify, since it will be deducted from the function pointer 242 * \tparam R The return type of \p func . No need to specify, since it will be deducted from the function pointer 243 * \tparam Args... Types of function arguments of \p func . No need to specify, since they will be deducted from 244 * the function pointer 245 * \static 246 */ 247 template<typename Base, typename R, typename... Args> 248 static void Method(const char* name, R (Base::*func)(Args...), MethodOptions options = MethodOptions::NONE); 249 250 /** 251 * Register a static method of class C. 252 * \param name The name of the method that will be exposed as in Javascript 253 * \param func A static function 254 * \param options Method options flags, default value is NONE 255 * 256 * \tparam R The return type of \p func . No need to specify, since it will be deducted from the function pointer 257 * \tparam Args... Types of function arguments of \p func . No need to specify, since they will be deducted from 258 * the function pointer 259 * \static 260 */ 261 template<typename R, typename... Args> 262 static void StaticMethod(const char* name, R (*func)(Args...), MethodOptions options = MethodOptions::NONE); 263 264 /** 265 * Register a static method of class C with a void(*)(const JSCallbackInfo&) signature. 266 * \param name The name of the method that will be exposed as in Javascript 267 * \param func A static function with void(*)(const JSCallbackInfo&) signature 268 * \static 269 */ 270 static void StaticMethod(const char* name, JSFunctionCallback func); 271 272 /** 273 * Register a method that is a member of a related class T with an engine-specific callback signature 274 * 275 * \tparam T A class that is either equivalent or a base to C 276 * \param name The name of the method that will be exposed as in Javascript 277 * \param callback A member-function pointer belonging to class C's class with engine-specific signature 278 * \static 279 */ 280 template<typename T> 281 static void CustomMethod(const char* name, MemberFunctionCallback<T> callback); 282 283 /** 284 * Register a method with an engine-specific callback signature 285 * 286 * \param name The name of the method that will be exposed as in Javascript 287 * \param callback A function pointer with the engine-specific signature 288 * \static 289 */ 290 static void CustomMethod(const char* name, FunctionCallback callback); 291 292 /** 293 * Register a method with an generic callback signature 294 * 295 * \param name The name of the method that will be exposed as in Javascript 296 * \param callback A function pointer with the engine-specific signature 297 * \static 298 */ 299 template<typename T> 300 static void CustomMethod(const char* name, JSMemberFunctionCallback<T> callback); 301 302 template<typename T> 303 static void CustomProperty(const char* name, MemberFunctionGetCallback<T> getter, 304 MemberFunctionSetCallback<T> setter); 305 306 static void CustomProperty(const char* name, FunctionGetCallback getter, 307 FunctionSetCallback setter); 308 309 template<typename T> 310 static void CustomProperty(const char* name, JSMemberFunctionCallback<T> getter, 311 JSMemberFunctionCallback<T> setter); 312 /** 313 * Register a static method with an engine-specific callback signature 314 * 315 * \param name The name of the method that will be exposed as in Javascript 316 * \param callback A function pointer with the engine-specific signature 317 * \static 318 */ 319 static void CustomStaticMethod(const char* name, FunctionCallback callback); 320 321 static void ExoticGetter(ExoticGetterCallback callback); 322 static void ExoticSetter(ExoticSetterCallback callback); 323 static void ExoticHasProperty(ExoticHasPropertyCallback callback); 324 325 template<typename T> 326 static void StaticConstant(const char* name, T value); 327 328 /** 329 * Bind the class to Javascript with a custom constructor that has engine-specific callback signature 330 * 331 * \param bindTarget An object template to bind this class to. 332 * \param ctor Constructor 333 * \static 334 */ 335 static void Bind(BindingTarget bindTarget, FunctionCallback ctor); 336 337 /** 338 * Bind the class to Javascript with custom constructor, destructor and GC mark callbacks. 339 * If no destructor callback is specified, the C++ instance is simply "delete"-d on garbage collection sweeps. 340 * 341 * \param bindTarget An object template to bind this class to 342 * \param ctor Constructor with void(*)(const JSCallbackInfo&) signature 343 * \param dtor Destructor with void(*)(C* instance) signature (optional) 344 * \param gcMark A GC mark callback with void(*)(C* instance, const JSGCMarkCallbackInfo&) signature (optional) 345 * \static 346 */ 347 static void Bind(BindingTarget bindTarget, JSFunctionCallback ctor, JSDestructorCallback<C> dtor = nullptr, 348 JSGCMarkCallback<C> gcMark = nullptr); 349 350 /** 351 * Bind the class to Javascript with optional destructor and GC mark callbacks. 352 * If no destructor callback is specified, the C++ instance is simply "delete"-d on garbage collection sweeps. 353 * 354 * \tparam Args... A list of argument types that the constructor of class C accepts. 355 * \param bindTarget An object template to bind this class to. 356 * \param dtor Destructor with void(*)(C* instance) signature (optional) 357 * \param gcMark A GC mark callback with void(*)(C* instance, const JSGCMarkCallbackInfo&) signature (optional) 358 * \static 359 */ 360 template<typename... Args> 361 static void Bind( 362 BindingTarget bindTarget, JSDestructorCallback<C> dtor = nullptr, JSGCMarkCallback<C> gcMark = nullptr); 363 364 /** 365 * Inherit all bound methods and properties from \p Base 366 * \note A binding for the base class must exist beforehand with 367 * \code JSClassImpl<Base,Impl>::Declare("MyBaseClass") \endcode 368 * 369 * \tparam Base A base class of C 370 */ 371 template<typename Base> 372 static void Inherit(); 373 374 static IFunctionBinding* GetFunctionBinding(int id); 375 static IFunctionBinding* GetGetFunctionBinding(int id); 376 static IFunctionBinding* GetSetFunctionBinding(int id); 377 378 /** 379 * Get the Javascript name of class C 380 * \return The javascript name 381 */ 382 static const char* JSName(); 383 384 /** 385 * Create new instance of declared class 386 * \return new JS object instance 387 */ 388 static JSRef<JSObject> NewInstance(); 389 390 private: 391 static thread_local std::string jsName; 392 /* OPTIMIZE(cvetan): Functions can be stored at compile-time with an additional index as a template parameter, for 393 example: 394 395 template<int N, typename RetType, typename... Args> 396 static RetType(C::*function_)(Args...); 397 398 But only if current approach proves to be a bottleneck. 399 */ 400 static thread_local std::unordered_map<int, std::unique_ptr<IFunctionBinding>> functions_; 401 static thread_local std::unordered_map<int, std::unique_ptr<IFunctionBinding>> getFunctions_; 402 static thread_local std::unordered_map<int, std::unique_ptr<IFunctionBinding>> setFunctions_; 403 static thread_local int nextFreeId_; 404 }; 405 406 }; // namespace OHOS::Ace::Framework 407 408 #include "bindings_implementation.inl" 409 410 #endif // FOUNDATION_ACE_FRAMEWORKS_DECLARATIVE_FRONTEND_ENGINE_BINDINGS_IMPLEMENTATION_H 411