diff options
Diffstat (limited to 'src')
35 files changed, 2805 insertions, 1507 deletions
diff --git a/src/closure-externs.js b/src/closure-externs.js index a82aa669..fe6d84aa 100644 --- a/src/closure-externs.js +++ b/src/closure-externs.js @@ -108,3 +108,63 @@ var flags = {}; flags.binary; +/** + * @fileoverview Definitions for W3C's Gamepad specification. + * @see http://www.w3.org/TR/gamepad/ + * @externs + */ + +/** + * @typedef {{id: string, index: number, timestamp: number, axes: Array.<number>, buttons: Array.<number>}} + */ +var Gamepad; + +/** +* @type {Array.<number>} +*/ +Gamepad.buttons; + +/** +* @type {Array.<number>} +*/ +Gamepad.axes; + +/** +* @type {number} +*/ +Gamepad.index; + +/** +* @type {string} +*/ +Gamepad.id; + +/** +* @type {number} +*/ +Gamepad.timestamp; + +/** + * @return {Array.<Gamepad>} + */ +navigator.getGamepads = function() {}; + +/** + * @return {Array.<Gamepad>} + */ +navigator.webkitGetGamepads = function() {}; + +/** + * @return {Array.<Gamepad>} + */ +navigator.webkitGamepads = function() {}; + +/** + * @return {Array.<Gamepad>} + */ +navigator.mozGamepads = function() {}; + +/** + * @return {Array.<Gamepad>} + */ +navigator.gamepads = function() {}; diff --git a/src/deps_info.json b/src/deps_info.json index b38ffd00..029a20e1 100644 --- a/src/deps_info.json +++ b/src/deps_info.json @@ -2,6 +2,7 @@ "uuid_compare": ["memcmp"], "SDL_Init": ["malloc", "free"], "SDL_GL_GetProcAddress": ["emscripten_GetProcAddress"], - "eglGetProcAddress": ["emscripten_GetProcAddress"] + "eglGetProcAddress": ["emscripten_GetProcAddress"], + "emscripten_GetProcAddress": ["strstr"] } diff --git a/src/embind/embind.js b/src/embind/embind.js index f0cd0c74..6ec07cd9 100644 --- a/src/embind/embind.js +++ b/src/embind/embind.js @@ -1,6 +1,6 @@ /*global Module*/ /*global _malloc, _free, _memcpy*/ -/*global FUNCTION_TABLE, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32*/ +/*global FUNCTION_TABLE, HEAP8, HEAPU8, HEAP16, HEAPU16, HEAP32, HEAPU32, HEAPF32, HEAPF64*/ /*global readLatin1String*/ /*global __emval_register, _emval_handle_array, __emval_decref*/ /*global ___getTypeName*/ @@ -35,7 +35,7 @@ function throwUnboundTypeError(message, types) { seen[type] = true; } types.forEach(visit); - + throw new UnboundTypeError(message + ': ' + unboundTypes.map(getTypeName).join([', '])); } @@ -55,7 +55,7 @@ function ensureOverloadTable(proto, methodName, humanName) { // Move the previous function into the overload table. proto[methodName].overloadTable = []; proto[methodName].overloadTable[prevFunc.argCount] = prevFunc; - } + } } /* Registers a symbol (function, class, enum, ...) as part of the Module JS object so that @@ -72,7 +72,7 @@ function exposePublicSymbol(name, value, numArguments) { if (undefined === numArguments || (undefined !== Module[name].overloadTable && undefined !== Module[name].overloadTable[numArguments])) { throwBindingError("Cannot register public name '" + name + "' twice"); } - + // We are exposing a function with the same name as an existing function. Create an overload table and a function selector // that routes between the two. ensureOverloadTable(Module, name, name); @@ -164,6 +164,10 @@ var typeDependencies = {}; var registeredPointers = {}; function registerType(rawType, registeredInstance) { + if (!('argPackAdvance' in registeredInstance)) { + throw new TypeError('registerType registeredInstance requires argPackAdvance'); + } + var name = registeredInstance.name; if (!rawType) { throwBindingError('type "' + name + '" must have a positive integer typeid pointer'); @@ -268,6 +272,7 @@ function __embind_register_void(rawType, name) { name = readLatin1String(name); registerType(rawType, { name: name, + 'argPackAdvance': 0, 'fromWireType': function() { return undefined; }, @@ -278,7 +283,9 @@ function __embind_register_void(rawType, name) { }); } -function __embind_register_bool(rawType, name, trueValue, falseValue) { +function __embind_register_bool(rawType, name, size, trueValue, falseValue) { + var shift = getShiftFromSize(size); + name = readLatin1String(name); registerType(rawType, { name: name, @@ -290,21 +297,80 @@ function __embind_register_bool(rawType, name, trueValue, falseValue) { 'toWireType': function(destructors, o) { return o ? trueValue : falseValue; }, + 'argPackAdvance': 8, + 'readValueFromPointer': function(pointer) { + // TODO: if heap is fixed (like in asm.js) this could be executed outside + var heap; + if (size === 1) { + heap = HEAP8; + } else if (size === 2) { + heap = HEAP16; + } else if (size === 4) { + heap = HEAP32; + } else { + throw new TypeError("Unknown boolean type size: " + name); + } + return this['fromWireType'](heap[pointer >> shift]); + }, destructorFunction: null, // This type does not need a destructor }); } +function getShiftFromSize(size) { + switch (size) { + case 1: return 0; + case 2: return 1; + case 4: return 2; + case 8: return 3; + default: + throw new TypeError('Unknown type size: ' + size); + } +} + +function integerReadValueFromPointer(name, shift, signed) { + switch (shift) { + case 0: return function(pointer) { + var heap = signed ? HEAP8 : HEAPU8; + return this['fromWireType'](heap[pointer]); + }; + case 1: return function(pointer) { + var heap = signed ? HEAP16 : HEAPU16; + return this['fromWireType'](heap[pointer >> 1]); + }; + case 2: return function(pointer) { + var heap = signed ? HEAP32 : HEAPU32; + return this['fromWireType'](heap[pointer >> 2]); + }; + default: + throw new TypeError("Unknown integer type: " + name); + } +} + +function floatReadValueFromPointer(name, shift) { + switch (shift) { + case 2: return function(pointer) { + return this['fromWireType'](HEAPF32[pointer >> 2]); + }; + case 3: return function(pointer) { + return this['fromWireType'](HEAPF64[pointer >> 3]); + }; + default: + throw new TypeError("Unknown float type: " + name); + } +} + // When converting a number from JS to C++ side, the valid range of the number is // [minRange, maxRange], inclusive. -function __embind_register_integer(primitiveType, name, minRange, maxRange) { +function __embind_register_integer(primitiveType, name, size, minRange, maxRange) { name = readLatin1String(name); if (maxRange === -1) { // LLVM doesn't have signed and unsigned 32-bit types, so u32 literals come out as 'i32 -1'. Always treat those as max u32. maxRange = 4294967295; } + + var shift = getShiftFromSize(size); + registerType(primitiveType, { name: name, - minRange: minRange, - maxRange: maxRange, 'fromWireType': function(value) { return value; }, @@ -319,11 +385,16 @@ function __embind_register_integer(primitiveType, name, minRange, maxRange) { } return value | 0; }, + 'argPackAdvance': 8, + 'readValueFromPointer': integerReadValueFromPointer(name, shift, minRange !== 0), destructorFunction: null, // This type does not need a destructor }); } -function __embind_register_float(rawType, name) { + + +function __embind_register_float(rawType, name, size) { + var shift = getShiftFromSize(size); name = readLatin1String(name); registerType(rawType, { name: name, @@ -338,10 +409,17 @@ function __embind_register_float(rawType, name) { } return value; }, + 'argPackAdvance': 8, + 'readValueFromPointer': floatReadValueFromPointer(name, shift), destructorFunction: null, // This type does not need a destructor }); } +// For types whose wire types are 32-bit pointers. +function simpleReadValueFromPointer(pointer) { + return this['fromWireType'](HEAPU32[pointer >> 2]); +} + function __embind_register_std_string(rawType, name) { name = readLatin1String(name); registerType(rawType, { @@ -394,6 +472,8 @@ function __embind_register_std_string(rawType, name) { } return ptr; }, + 'argPackAdvance': 8, + 'readValueFromPointer': simpleReadValueFromPointer, destructorFunction: function(ptr) { _free(ptr); }, }); } @@ -434,6 +514,8 @@ function __embind_register_std_wstring(rawType, charSize, name) { } return ptr; }, + 'argPackAdvance': 8, + 'readValueFromPointer': simpleReadValueFromPointer, destructorFunction: function(ptr) { _free(ptr); }, }); } @@ -450,6 +532,8 @@ function __embind_register_emval(rawType, name) { 'toWireType': function(destructors, value) { return __emval_register(value); }, + 'argPackAdvance': 8, + 'readValueFromPointer': simpleReadValueFromPointer, destructorFunction: null, // This type does not need a destructor }); } @@ -463,7 +547,7 @@ function __embind_register_memory_view(rawType, name) { Int32Array, Uint32Array, Float32Array, - Float64Array, + Float64Array, ]; name = readLatin1String(name); @@ -476,6 +560,10 @@ function __embind_register_memory_view(rawType, name) { var TA = typeMapping[type]; return new TA(HEAP8.buffer, data, size); }, + 'argPackAdvance': 16, + 'readValueFromPointer': function(ptr) { + return this['fromWireType'](ptr); + }, }); } @@ -531,7 +619,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp if (argCount < 2) { throwBindingError("argTypes array size mismatch! Must at least get return value and 'this' types!"); } - + var isClassMethodFunc = (argTypes[1] !== null && classType !== null); if (!isClassMethodFunc && !FUNCTION_TABLE[cppTargetFunc]) { @@ -560,7 +648,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp // Determine if we need to use a dynamic stack to store the destructors for the function parameters. // TODO: Remove this completely once all function invokers are being dynamically generated. var needsDestructorStack = false; - + for(var i = 1; i < argTypes.length; ++i) { // Skip return value at index 0 - it's not deleted here. if (argTypes[i] !== null && argTypes[i].destructorFunction === undefined) { // The type does not define a destructor function - must use dynamic stack needsDestructorStack = true; @@ -595,7 +683,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp invokerFnBody += (returns?"var rv = ":"") + "invoker(fn"+(argsListWired.length>0?", ":"")+argsListWired+");\n"; - + if (needsDestructorStack) { invokerFnBody += "runDestructors(destructors);\n"; } else { @@ -608,7 +696,7 @@ function craftInvokerFunction(humanName, argTypes, classType, cppInvokerFunc, cp } } } - + if (returns) { invokerFnBody += "return retType.fromWireType(rv);\n"; } @@ -676,7 +764,7 @@ function __embind_finalize_value_array(rawTupleType) { var rawConstructor = reg.rawConstructor; var rawDestructor = reg.rawDestructor; - + whenDependentTypesAreResolved([rawTupleType], elementTypes, function(elementTypes) { elements.forEach(function(elt, i) { var getterReturnType = elementTypes[i]; @@ -718,6 +806,8 @@ function __embind_finalize_value_array(rawTupleType) { } return ptr; }, + 'argPackAdvance': 8, + 'readValueFromPointer': simpleReadValueFromPointer, destructorFunction: rawDestructor, }]; }); @@ -819,6 +909,8 @@ function __embind_finalize_value_object(structType) { } return ptr; }, + 'argPackAdvance': 8, + 'readValueFromPointer': simpleReadValueFromPointer, destructorFunction: rawDestructor, }]; }); @@ -860,7 +952,7 @@ var genericPointerToWireType = function(destructors, handle) { if (undefined === handle.$$.smartPtr) { throwBindingError('Passing raw pointer to smart pointer is illegal'); } - + switch (this.sharingPolicy) { case 0: // NONE // no upcasting @@ -870,11 +962,11 @@ var genericPointerToWireType = function(destructors, handle) { throwBindingError('Cannot convert argument of type ' + (handle.$$.smartPtrType ? handle.$$.smartPtrType.name : handle.$$.ptrType.name) + ' to parameter type ' + this.name); } break; - + case 1: // INTRUSIVE ptr = handle.$$.smartPtr; break; - + case 2: // BY_EMVAL if (handle.$$.smartPtrType === this) { ptr = handle.$$.smartPtr; @@ -891,7 +983,7 @@ var genericPointerToWireType = function(destructors, handle) { } } break; - + default: throwBindingError('Unsupporting sharing policy'); } @@ -985,7 +1077,7 @@ function RegisteredPointer( this['toWireType'] = genericPointerToWireType; // Here we must leave this.destructorFunction undefined, since whether genericPointerToWireType returns // a pointer that needs to be freed up is runtime-dependent, and cannot be evaluated at registration time. - // TODO: Create an alternative mechanism that allows removing the use of var destructors = []; array in + // TODO: Create an alternative mechanism that allows removing the use of var destructors = []; array in // craftInvokerFunction altogether. } } @@ -1003,6 +1095,9 @@ RegisteredPointer.prototype.destructor = function(ptr) { } }; +RegisteredPointer.prototype['argPackAdvance'] = 8; +RegisteredPointer.prototype['readValueFromPointer'] = simpleReadValueFromPointer; + RegisteredPointer.prototype['fromWireType'] = function(ptr) { // ptr is a raw pointer (or a raw smartpointer) @@ -1110,7 +1205,7 @@ ClassHandle.prototype['isAliasOf'] = function(other) { right = rightClass.upcast(right); rightClass = rightClass.baseClass; } - + return leftClass === rightClass && left === right; }; @@ -1195,7 +1290,7 @@ Module['setDelayFunction'] = function setDelayFunction(fn) { delayFunction(flushPendingDeletes); } }; - + function RegisteredClass( name, constructor, @@ -1298,7 +1393,7 @@ function __embind_register_class( true, false, false); - + var pointerConverter = new RegisteredPointer( name + '*', registeredClass, @@ -1360,10 +1455,10 @@ function __embind_register_class_constructor( for (var i = 1; i < argCount; ++i) { args[i] = argTypes[i]['toWireType'](destructors, arguments[i - 1]); } - + var ptr = invoker.apply(null, args); runDestructors(destructors); - + return argTypes[0]['fromWireType'](ptr); }; return []; @@ -1447,7 +1542,7 @@ function __embind_register_class_function( } whenDependentTypesAreResolved([], rawArgTypes, function(argTypes) { - + var memberFunction = craftInvokerFunction(humanName, argTypes, classType, rawInvoker, context); // Replace the initial unbound-handler-stub function with the appropriate member function, now that all types @@ -1627,8 +1722,11 @@ function __embind_register_smart_ptr( function __embind_register_enum( rawType, - name + name, + size, + isSigned ) { + var shift = getShiftFromSize(size); name = readLatin1String(name); function constructor() { @@ -1644,6 +1742,8 @@ function __embind_register_enum( 'toWireType': function(destructors, c) { return c.value; }, + 'argPackAdvance': 8, + 'readValueFromPointer': integerReadValueFromPointer(name, shift, isSigned), destructorFunction: null, }); exposePublicSymbol(name, constructor); diff --git a/src/embind/emval.js b/src/embind/emval.js index 039f1d61..4007701a 100644 --- a/src/embind/emval.js +++ b/src/embind/emval.js @@ -55,6 +55,7 @@ function requireHandle(handle) { if (!handle) { throwBindingError('Cannot use deleted val. handle = ' + handle); } + return _emval_handle_array[handle].value; } function __emval_register(value) { @@ -105,9 +106,9 @@ function __emval_new_cstring(v) { return __emval_register(getStringOrSymbol(v)); } -function __emval_take_value(type, v) { +function __emval_take_value(type, argv) { type = requireRegisteredType(type, '_emval_take_value'); - v = type['fromWireType'](v); + var v = type['readValueFromPointer'](argv); return __emval_register(v); } @@ -116,70 +117,51 @@ var __newers = {}; // arity -> function function craftEmvalAllocator(argCount) { /*This function returns a new function that looks like this: - function emval_allocator_3(handle, argTypes, arg0Wired, arg1Wired, arg2Wired) { + function emval_allocator_3(constructor, argTypes, args) { var argType0 = requireRegisteredType(HEAP32[(argTypes >> 2)], "parameter 0"); - var arg0 = argType0.fromWireType(arg0Wired); + var arg0 = argType0.readValueFromPointer(args); var argType1 = requireRegisteredType(HEAP32[(argTypes >> 2) + 1], "parameter 1"); - var arg1 = argType1.fromWireType(arg1Wired); + var arg1 = argType1.readValueFromPointer(args + 8); var argType2 = requireRegisteredType(HEAP32[(argTypes >> 2) + 2], "parameter 2"); - var arg2 = argType2.fromWireType(arg2Wired); - var constructor = _emval_handle_array[handle].value; - var emval = new constructor(arg0, arg1, arg2); - return emval; + var arg2 = argType2.readValueFromPointer(args + 16); + var obj = new constructor(arg0, arg1, arg2); + return __emval_register(obj); } */ - var args1 = ["requireRegisteredType", "HEAP32", "_emval_handle_array", "__emval_register"]; - var args2 = [requireRegisteredType, HEAP32, _emval_handle_array, __emval_register]; - var argsList = ""; - var argsListWired = ""; for(var i = 0; i < argCount; ++i) { argsList += (i!==0?", ":"")+"arg"+i; // 'arg0, arg1, ..., argn' - argsListWired += ", arg"+i+"Wired"; // ', arg0Wired, arg1Wired, ..., argnWired' } - var invokerFnBody = - "return function emval_allocator_"+argCount+"(handle, argTypes " + argsListWired + ") {\n"; + var functionBody = + "return function emval_allocator_"+argCount+"(constructor, argTypes, args) {\n"; for(var i = 0; i < argCount; ++i) { - invokerFnBody += + functionBody += "var argType"+i+" = requireRegisteredType(HEAP32[(argTypes >> 2) + "+i+"], \"parameter "+i+"\");\n" + - "var arg"+i+" = argType"+i+".fromWireType(arg"+i+"Wired);\n"; + "var arg"+i+" = argType"+i+".readValueFromPointer(args);\n" + + "args += argType"+i+".argPackAdvance;\n"; } - invokerFnBody += - "var constructor = _emval_handle_array[handle].value;\n" + + functionBody += "var obj = new constructor("+argsList+");\n" + "return __emval_register(obj);\n" + "}\n"; - args1.push(invokerFnBody); - var invokerFunction = new_(Function, args1).apply(null, args2); - return invokerFunction; + /*jshint evil:true*/ + return (new Function("requireRegisteredType", "HEAP32", "__emval_register", functionBody))( + requireRegisteredType, HEAP32, __emval_register); } -function __emval_new(handle, argCount, argTypes) { - requireHandle(handle); - +function __emval_new(handle, argCount, argTypes, args) { + handle = requireHandle(handle); + var newer = __newers[argCount]; if (!newer) { newer = craftEmvalAllocator(argCount); __newers[argCount] = newer; } - if (argCount === 0) { - return newer(handle, argTypes); - } else if (argCount === 1) { - return newer(handle, argTypes, arguments[3]); - } else if (argCount === 2) { - return newer(handle, argTypes, arguments[3], arguments[4]); - } else if (argCount === 3) { - return newer(handle, argTypes, arguments[3], arguments[4], arguments[5]); - } else if (argCount === 4) { - return newer(handle, argTypes, arguments[3], arguments[4], arguments[5], arguments[6]); - } else { - // This is a slow path! (.apply and .splice are slow), so a few specializations are present above. - return newer.apply(null, arguments.splice(1)); - } + return newer(handle, argTypes, args); } // appease jshint (technically this code uses eval) @@ -196,46 +178,39 @@ function __emval_get_module_property(name) { } function __emval_get_property(handle, key) { - requireHandle(handle); - return __emval_register(_emval_handle_array[handle].value[_emval_handle_array[key].value]); + handle = requireHandle(handle); + key = requireHandle(key); + return __emval_register(handle[key]); } function __emval_set_property(handle, key, value) { - requireHandle(handle); - _emval_handle_array[handle].value[_emval_handle_array[key].value] = _emval_handle_array[value].value; + handle = requireHandle(handle); + key = requireHandle(key); + value = requireHandle(value); + handle[key] = value; } function __emval_as(handle, returnType, destructorsRef) { - requireHandle(handle); + handle = requireHandle(handle); returnType = requireRegisteredType(returnType, 'emval::as'); var destructors = []; var rd = __emval_register(destructors); HEAP32[destructorsRef >> 2] = rd; - return returnType['toWireType'](destructors, _emval_handle_array[handle].value); + return returnType['toWireType'](destructors, handle); } -function parseParameters(argCount, argTypes, argWireTypes) { - var a = new Array(argCount); - for (var i = 0; i < argCount; ++i) { - var argType = requireRegisteredType( - HEAP32[(argTypes >> 2) + i], - "parameter " + i); - a[i] = argType['fromWireType'](argWireTypes[i]); - } - return a; -} - -function __emval_call(handle, argCount, argTypes) { - requireHandle(handle); +function __emval_call(handle, argCount, argTypes, argv) { + handle = requireHandle(handle); var types = lookupTypes(argCount, argTypes); var args = new Array(argCount); for (var i = 0; i < argCount; ++i) { - args[i] = types[i]['fromWireType'](arguments[3 + i]); + var type = types[i]; + args[i] = type['readValueFromPointer'](argv); + argv += type.argPackAdvance; } - var fn = _emval_handle_array[handle].value; - var rv = fn.apply(undefined, args); + var rv = handle.apply(undefined, args); return __emval_register(rv); } @@ -255,44 +230,59 @@ function allocateDestructors(destructorsRef) { return destructors; } +// Leave id 0 undefined. It's not a big deal, but might be confusing +// to have null be a valid method caller. +var methodCallers = [undefined]; + +function addMethodCaller(caller) { + var id = methodCallers.length; + methodCallers.push(caller); + return id; +} + function __emval_get_method_caller(argCount, argTypes) { var types = lookupTypes(argCount, argTypes); var retType = types[0]; var signatureName = retType.name + "_$" + types.slice(1).map(function (t) { return t.name; }).join("_") + "$"; - var args1 = ["addFunction", "createNamedFunction", "requireHandle", "getStringOrSymbol", "_emval_handle_array", "retType", "allocateDestructors"]; - var args2 = [Runtime.addFunction, createNamedFunction, requireHandle, getStringOrSymbol, _emval_handle_array, retType, allocateDestructors]; + var params = ["retType"]; + var args = [retType]; var argsList = ""; // 'arg0, arg1, arg2, ... , argN' - var argsListWired = ""; // 'arg0Wired, ..., argNWired' for (var i = 0; i < argCount - 1; ++i) { argsList += (i !== 0 ? ", " : "") + "arg" + i; - argsListWired += ", arg" + i + "Wired"; - args1.push("argType" + i); - args2.push(types[1 + i]); + params.push("argType" + i); + args.push(types[1 + i]); } - var invokerFnBody = - "return addFunction(createNamedFunction('" + signatureName + "', function (handle, name, destructorsRef" + argsListWired + ") {\n" + - " requireHandle(handle);\n" + - " name = getStringOrSymbol(name);\n"; + var functionBody = + "return function (handle, name, destructors, args) {\n"; for (var i = 0; i < argCount - 1; ++i) { - invokerFnBody += " var arg" + i + " = argType" + i + ".fromWireType(arg" + i + "Wired);\n"; + functionBody += + " var arg" + i + " = argType" + i + ".readValueFromPointer(args);\n" + + " args += argType" + i + ".argPackAdvance;\n"; } - invokerFnBody += - " var obj = _emval_handle_array[handle].value;\n" + - " var rv = obj[name](" + argsList + ");\n" + - " return retType.toWireType(allocateDestructors(destructorsRef), rv);\n" + - "}));\n"; - - args1.push(invokerFnBody); - var invokerFunction = new_(Function, args1).apply(null, args2); - return invokerFunction; + functionBody += + " var rv = handle[name](" + argsList + ");\n" + + " return retType.toWireType(destructors, rv);\n" + + "};\n"; + + params.push(functionBody); + var invokerFunction = new_(Function, params).apply(null, args); + return addMethodCaller(createNamedFunction(signatureName, invokerFunction)); +} + +function __emval_call_method(caller, handle, methodName, destructorsRef, args) { + caller = methodCallers[caller]; + handle = requireHandle(handle); + methodName = getStringOrSymbol(methodName); + return caller(handle, methodName, allocateDestructors(destructorsRef), args); } function __emval_has_function(handle, name) { + handle = requireHandle(handle); name = getStringOrSymbol(name); - return _emval_handle_array[handle].value[name] instanceof Function; + return handle[name] instanceof Function; } diff --git a/src/headlessCanvas.js b/src/headlessCanvas.js index 4bd17a7b..1eefe48e 100644 --- a/src/headlessCanvas.js +++ b/src/headlessCan |