/// This file is a part of Ultralight, a next-generation HTML renderer. /// /// Website: /// /// Copyright (C) 2021 Ultralight, Inc. All rights reserved. #pragma once #include #include #include #include #include #include namespace ultralight { /// /// Set the current JSContext. /// /// Most JavaScriptCore C API calls require an active JavaScript execution /// context (JSContextRef). You can get the JSContextRef for a page via /// `View::LockJSContext()`. This context changes with each page navigation. /// /// **Note**: /// You MUST set a JSContext before using most of the C++ API below. /// void AExport SetJSContext(JSContextRef ctx); /// /// Get the current JSContext. /// JSContextRef AExport GetJSContext(); /// /// JavaScript String wrapper that automatically manages JSStringRef lifetime /// and provides helpful conversions. /// class AExport JSString { public: /// Create empty string JSString(); /// Create from C-string JSString(const char* str); /// Create from Ultralight String JSString(const String& str); /// Create from existing JSStringRef JSString(JSStringRef str); /// Copy constructor JSString(const JSString& other); /// Destructor ~JSString(); /// Assignment operator JSString& operator=(const JSString& other); /// Cast to String operator String(); /// Cast to underlying JSStringRef operator JSStringRef() const { return instance_; } protected: JSStringRef instance_; }; class JSArray; class JSObject; class JSFunction; /// Tag type used with the JSValue constructor to create "Null" types struct AExport JSValueNullTag {}; /// Tag type used with the JSValue constructor to create "Undefined" types struct AExport JSValueUndefinedTag {}; /// /// JavaScript variant value wrapper that automatically manages JSValueRef /// lifetime and provides helpful conversions. /// class AExport JSValue { public: /// Create null (empty) JSValue JSValue(); /// Create null JSValue explicitly JSValue(JSValueNullTag); /// Create undefined JSValue JSValue(JSValueUndefinedTag); /// Create boolean JSValue JSValue(bool val); /// Create unsigned integer JSValue (aka, Number) [will be cast to double] JSValue(uint32_t val); /// Create integer JSValue (aka, Number) [will be cast to double] JSValue(int32_t val); /// Create unsigned integer JSValue (aka, Number) [will be cast to double] JSValue(uint64_t val); /// Create integer JSValue (aka, Number) [will be cast to double] JSValue(int64_t val); /// Create double JSValue (aka, Number) JSValue(double val); /// Create string JSValue JSValue(const char* val); /// Create string JSValue JSValue(const String& val); /// Create string JSValue JSValue(JSString val); /// Create from existing JSValueRef JSValue(JSValueRef val); /// Create object JSValue JSValue(JSObjectRef obj); /// Copy constructor, a shallow copy is made, the constructed JSValue will /// point to the same JSValueRef. JSValue(const JSValue& other); /// Destructor virtual ~JSValue(); /// A shallow copy is made, this JSValue will point to the same JSValueRef virtual JSValue& operator=(const JSValue& other); /// Whether or not the value is a JavaScript Null type. bool IsNull() const; /// Whether or not the value is a JavaScript Undefined type. bool IsUndefined() const; /// Whether or not the value is a JavaScript Boolean type. bool IsBoolean() const; /// Whether or not the value is a JavaScript Number type. bool IsNumber() const; /// Whether or not the value is a JavaScript String type. bool IsString() const; /// Whether or not the value is a JavaScript Object type. bool IsObject() const; /// Whether or not the value is a JavaScript Array type. bool IsArray() const; /// Whether or not the value is a JavaScript Function type. bool IsFunction() const; /// Get the value as a Boolean bool ToBoolean() const; /// Get the value as a Number (Double) double ToNumber() const; /// Get the value as a Number (Integer) int64_t ToInteger() const { return static_cast(ToNumber()); } /// Get the value as a String JSString ToString() const; /// Get the value as an Object (will debug assert if not an Object) JSObject ToObject() const; /// Get the value as an Array (will debug asset if not an Array) JSArray ToArray() const; /// Get the value as a Function (will debug asset if not a Function) JSFunction ToFunction() const; operator bool() const { return ToBoolean(); } operator double() const { return ToNumber(); } operator uint32_t() const { return static_cast(ToNumber()); } operator int32_t() const { return static_cast(ToNumber()); } operator uint64_t() const { return static_cast(ToNumber()); } operator int64_t() const { return ToInteger(); } operator String() const { return ToString(); } operator JSString() const { return ToString(); } operator JSObject() const; operator JSObjectRef() const; operator JSArray() const; operator JSFunction() const; /// Get the underlying JSValueRef operator JSValueRef() const { return instance(); } /// /// Get the bound context for this JSValue (it is cached at creation). /// JSContextRef context() const { return ctx_; } /// /// Set the JSContext for this JSValue. /// /// **Note**: /// JSValues created from within a JSCallback have a temporary JSContext /// that is destroyed when the callback returns. You will need to "move" /// any JSValues created within these callbacks to the View's main context /// (call set_context() with the main context) before using them outside /// the callback. /// void set_context(JSContextRef context) { ctx_ = context; } protected: JSValue(JSContextRef ctx); JSValue(JSContextRef ctx, JSValueRef val); virtual JSValueRef instance() const; JSContextRef ctx_; JSValueRef instance_ = nullptr; friend class JSFunction; }; /// /// A vector of JSValues, used for passing around arguments in JSCallback. /// class AExport JSArgs { public: /// Create an empty list of JavaScript arguments JSArgs(); /// Create a list of JavaScript arguments using a C++ initializer list JSArgs(const std::initializer_list& values); /// Copy-constructor JSArgs(const JSArgs& other); /// Destructor ~JSArgs(); /// Assignment operator JSArgs& operator=(const JSArgs& other); /// /// Access an element of the argument list by index. /// /// **Note**: /// All JSValues are actually wrappers of JSValueRef instances so even /// though this function doesn't return a JSValue& you are still operating /// directly on the underlying JavaScript value instance. /// JSValue operator[](size_t pos); /// /// Access an element of the argument list by index. (const overload) /// /// **Note**: /// All JSValues are actually wrappers of JSValueRef instances so even /// though this function doesn't return a JSValue& you are still operating /// directly on the underlying JavaScript value instance. /// const JSValue operator[](size_t pos) const; /// Whether or not the argument list is empty. bool empty() const; /// The number of elements in the argument list. size_t size() const; /// Clear the argument list. void clear(); /// Add a new argument to the end of the list. void push_back(const JSValue& val); /// Remove the last item from the end of the list. void pop_back(); /// Get the argument list as a C-array of JSValues JSValue* data(); /// Get the argument list as a C-array of JSValues (const overload) const JSValue* data() const; protected: void* instance_; }; /// /// JSCallback typedef used for binding C++ callbacks to JavaScript functions. /// /// Takes two arguments (const JSObject& thisObj, const JSArgs& args) and /// returns nothing (void). /// typedef std::function JSCallback; /// /// JSCallbackWithRetval typedef used for binding C++ callbacks to JavaScript /// functions with an optional return value. /// /// Takes two arguments (const JSObject& thisObj, const JSArgs& args) and /// returns a JSValue back to JavaScript. /// typedef std::function JSCallbackWithRetval; /// /// Macro to help bind C++ member functions to a JSCallback /// /// Usage: JSCallback callback = BindJSCallback(&MyClass::MyMemberFunction); /// /// **Note**: Expected to run from within an instance of 'MyClass', note the /// 'this' keyword in the macro. /// #define BindJSCallback(fn) (JSCallback)std::bind(fn, this, std::placeholders::_1, std::placeholders::_2) /// /// Macro to help bind C++ member functions to a JSCallbackWithRetval /// /// Usage: JSCallback callback = BindJSCallback(&MyClass::MyMemberFunction); /// /// **Note**: Expected to run from within an instance of 'MyClass', note the /// 'this' keyword in the macro. /// #define BindJSCallbackWithRetval(fn) (JSCallbackWithRetval)std::bind(fn, this, std::placeholders::_1, std::placeholders::_2) /// /// Wrapper for JSObject property value (JSValue subclass). Allows new value assignment /// to object property, binding C++ callbacks to object properties via function objects, /// as well as value query via the JSValue interface. /// class AExport JSPropertyValue : public JSValue { public: virtual ~JSPropertyValue(); /// Assign a new value to the property (internally calls JSObjectSetProperty) virtual JSPropertyValue& operator=(const JSValue& value); /// Bind to native C++ callback (creates a Function object that can be called from JS) JSPropertyValue& operator=(const JSCallback& callback); /// Bind to native C++ callback with return value (creates a Function object that can be called from JS) JSPropertyValue& operator=(const JSCallbackWithRetval& callback); protected: virtual JSValueRef instance() const; JSPropertyValue(JSContextRef ctx, JSObjectRef proxy_obj, unsigned idx); JSPropertyValue(JSContextRef ctx, JSObjectRef proxy_obj, JSString idx); JSPropertyValue(const JSPropertyValue&) = default; JSPropertyValue& operator=(const JSPropertyValue&) = delete; JSObject* proxyObj_; bool using_numeric_idx_; unsigned numeric_idx_; JSString string_idx_; friend class JSArray; friend class JSObject; }; /// /// JSArray wrapper that automatically manages lifetime and provides /// convenient access to indices and Array functions. /// class AExport JSArray { public: /// Create empty Array JSArray(); /// Create Array from list of JSValues JSArray(const std::initializer_list& values); /// Create Array from existing JSObjectRef (JavaScriptCore C API) JSArray(JSObjectRef array_obj); /// Copy constructor (shallow copy, will point to same instance) JSArray(const JSArray& other); ~JSArray(); /// Assignment (shallow assignment, will point to same instance) JSArray& operator=(const JSArray& other); /// Get number of elements in the Array unsigned length(); /// Push an element to back of Array void push(const JSValue& val); /// Find the index (location) of a certain value, will return -1 if not found int indexOf(const JSValue& val, int start = 0) const; /// Get a property by array index (numbering starts at 0) JSPropertyValue operator[](unsigned idx) const; /// Get the underlying JSObjectRef (JavaScriptCore C API) operator JSObjectRef() const { return instance_; } /// /// Get the bound context for this JSArray (it is cached at creation). /// JSContextRef context() const { return ctx_; } /// /// Set the JSContext for this JSArray. /// /// **Note**: /// JSArrays created from within a JSCallback have a temporary JSContext /// that is destroyed when the callback returns. You will need to "move" /// any JSArrays created within these callbacks to the View's main context /// (call set_context() with the main context) before using them outside /// the callback. /// void set_context(JSContextRef context) { ctx_ = context; } protected: JSArray(JSContextRef ctx, JSValueRef val); JSContextRef ctx_; JSObjectRef instance_; friend class JSValue; }; /// /// JSObject wrapper that automatically manages lifetime and provides /// convenient access to properties. /// class AExport JSObject { public: /// Create empty Object JSObject(); /// Create from existing JSObjectRef from JavaScriptCore C API JSObject(JSObjectRef obj); /// Copy constructor (shallow copy, will point to same instance) JSObject(const JSObject& other); ~JSObject(); /// Assignment (shallow assignment, will point to same instance) JSObject& operator=(const JSObject& other); /// Get a property by name JSPropertyValue operator[](JSString propertyName) const; /// Check if a property exists bool HasProperty(JSString propertyName) const; /// Remove a property bool DeleteProperty(JSString propertyName); /// Get the underlying JSObjectRef (JavaScriptCore C API) operator JSObjectRef() const { return instance_; } /// /// Get the bound context for this JSObject (it is cached at creation). /// JSContextRef context() const { return ctx_; } /// /// Set the JSContext for this JSObject. /// /// **Note**: /// JSObjects created from within a JSCallback have a temporary JSContext /// that is destroyed when the callback returns. You will need to "move" /// any JSObjects created within these callbacks to the View's main context /// (call set_context() with the main context) before using them outside /// the callback. /// void set_context(JSContextRef context) { ctx_ = context; } protected: JSObject(JSContextRef ctx, JSValueRef val); JSObject(JSContextRef ctx, JSObjectRef obj); JSContextRef ctx_; JSObjectRef instance_; friend class JSValue; friend class JSPropertyValue; }; /// /// JSFunction wrapper that automatically manages lifetime and provides /// convenient function invocation operators. /// class AExport JSFunction { public: /// Create an empty Function. /// NOTE: It is OKAY to create this without calling SetJSContext() first. JSFunction(); /// Copy constructor (shallow copy, will point to same instance) JSFunction(const JSFunction& other); ~JSFunction(); /// Assignment (shallow assignment, will point to same instance) JSFunction& operator=(const JSFunction& other); /// Whether or not this is a valid, callable Function object. bool IsValid() const; /// Call function (using Global Object for 'this') and return the result. JSValue operator()(const JSArgs& args); /// Call function (with explicit object for 'this') and return the result JSValue operator()(const JSObject& thisObject, const JSArgs& args); /// Get the underlying JSObjectRef (JavaScriptCore C API) operator JSObjectRef() const { return instance_; } /// /// Get the bound context for this JSFunction (it is cached at creation). /// JSContextRef context() const { return ctx_; } /// /// Set the JSContext for this JSFunction. /// /// **Note**: /// JSFunctions created from within a JSCallback have a temporary JSContext /// that is destroyed when the callback returns. You will need to "move" /// any JSFunctions created within these callbacks to the View's main context /// (call set_context() with the main context) before using them outside /// the callback. /// void set_context(JSContextRef context) { ctx_ = context; } protected: JSFunction(JSContextRef ctx, JSValueRef val); JSContextRef ctx_; JSObjectRef instance_; friend class JSValue; }; /// /// Get the Global Object for the current JSContext. /// In JavaScript, this would be equivalent to the "window" object. /// JSObject AExport JSGlobalObject(); /// /// Evaluate a string of JavaScript and return a result. /// JSValue AExport JSEval(const JSString& str); } // namespace ultralight