/**
 * Copyright (c) 2015-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 */

#import "RCTContextExecutor.h"

#import <pthread.h>

#import <JavaScriptCore/JavaScriptCore.h>
#import <Appkit/AppKit.h>

#import "RCTAssert.h"
#import "RCTDefines.h"
#import "RCTDevMenu.h"
#import "RCTLog.h"
#import "RCTProfile.h"
#import "RCTPerformanceLogger.h"
#import "RCTUtils.h"

#ifndef RCT_JSC_PROFILER
#if RCT_DEV
#define RCT_JSC_PROFILER 1
#else
#define RCT_JSC_PROFILER 0
#endif
#endif

#if RCT_JSC_PROFILER
#include <dlfcn.h>

static NSString * const RCTJSCProfilerEnabledDefaultsKey = @"RCTJSCProfilerEnabled";

#ifndef RCT_JSC_PROFILER_DYLIB
#define RCT_JSC_PROFILER_DYLIB [[[NSBundle mainBundle] pathForResource:[NSString stringWithFormat:@"RCTJSCProfiler.osx%zd", [[NSProcessInfo processInfo] operatingSystemVersion].patchVersion] ofType:@"dylib" inDirectory:@"RCTJSCProfiler"] UTF8String]
#endif
#endif

@interface RCTJavaScriptContext : NSObject <RCTInvalidating>

@property (nonatomic, strong, readonly) JSContext *context;
@property (nonatomic, assign, readonly) JSGlobalContextRef ctx;

- (instancetype)initWithJSContext:(JSContext *)context NS_DESIGNATED_INITIALIZER;

@end

@implementation RCTJavaScriptContext
{
  RCTJavaScriptContext *_self;
}

- (instancetype)initWithJSContext:(JSContext *)context
{
  if ((self = [super init])) {
    _context = context;
    _self = self;
  }
  return self;
}

RCT_NOT_IMPLEMENTED(-(instancetype)init)

- (JSGlobalContextRef)ctx
{
  return _context.JSGlobalContextRef;
}

- (BOOL)isValid
{
  return _context != nil;
}

- (void)invalidate
{
  if (self.isValid) {
    _context = nil;
    _self = nil;
  }
}

- (void)dealloc
{
  CFRunLoopStop([[NSRunLoop currentRunLoop] getCFRunLoop]);
}

@end

// Private bridge interface to allow middle-batch calls
@interface RCTBridge (RCTContextExecutor)

- (void)handleBuffer:(NSArray *)buffer batchEnded:(BOOL)hasEnded;

@end

@implementation RCTContextExecutor
{
  RCTJavaScriptContext *_context;
  NSThread *_javaScriptThread;
}

@synthesize valid = _valid;
@synthesize bridge = _bridge;

RCT_EXPORT_MODULE()

/**
 * The one tiny pure native hook that we implement is a native logging hook.
 * You could even argue that this is not necessary - we could plumb logging
 * calls through a batched bridge, but having the pure native hook allows
 * logging to successfully come through even in the event that a batched bridge
 * crashes.
 */

static JSValueRef RCTNativeLoggingHook(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef *exception)
{
  if (argumentCount > 0) {
    JSStringRef messageRef = JSValueToStringCopy(context, arguments[0], exception);
    if (!messageRef) {
      return JSValueMakeUndefined(context);
    }
    NSString *message = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, messageRef);
    JSStringRelease(messageRef);
    NSRegularExpression *regex = [NSRegularExpression regularExpressionWithPattern:
                                  @"( stack: )?([_a-z0-9]*)@?(http://|file:///)[a-z.0-9:/_-]+/([a-z0-9_]+).bundle(:[0-9]+:[0-9]+)"
                                                                           options:NSRegularExpressionCaseInsensitive
                                                                             error:NULL];
    message = [regex stringByReplacingMatchesInString:message
                                              options:0
                                                range:(NSRange){0, message.length}
                                         withTemplate:@"[$4$5]  \t$2"];

    RCTLogLevel level = RCTLogLevelInfo;
    if (argumentCount > 1) {
      level = MAX(level, JSValueToNumber(context, arguments[1], exception) - 1);
    }
    RCTGetLogFunction()(level, nil, nil, message);
  }

  return JSValueMakeUndefined(context);
}

// Do-very-little native hook for testing.
static JSValueRef RCTNoop(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, __unused size_t argumentCount, __unused const JSValueRef arguments[], __unused JSValueRef *exception)
{
  static int counter = 0;
  counter++;
  return JSValueMakeUndefined(context);
}

static NSString *RCTJSValueToNSString(JSContextRef context, JSValueRef value)
{
  JSStringRef JSString = JSValueToStringCopy(context, value, NULL);
  CFStringRef string = JSStringCopyCFString(kCFAllocatorDefault, JSString);
  JSStringRelease(JSString);

  return (__bridge_transfer NSString *)string;
}

static NSString *RCTJSValueToJSONString(JSContextRef context, JSValueRef value, unsigned indent)
{
  JSStringRef JSString = JSValueCreateJSONString(context, value, indent, NULL);
  CFStringRef string = JSStringCopyCFString(kCFAllocatorDefault, JSString);
  JSStringRelease(JSString);

  return (__bridge_transfer NSString *)string;
}

static NSError *RCTNSErrorFromJSError(JSContextRef context, JSValueRef jsError)
{
  NSString *errorMessage = jsError ? RCTJSValueToNSString(context, jsError) : @"unknown JS error";
  NSString *details = jsError ? RCTJSValueToJSONString(context, jsError, 2) : @"no details";
  return [NSError errorWithDomain:@"JS" code:1 userInfo:@{NSLocalizedDescriptionKey: errorMessage, NSLocalizedFailureReasonErrorKey: details}];
}

#if RCT_DEV

static JSValueRef RCTNativeTraceBeginSection(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], __unused JSValueRef *exception)
{
  static int profileCounter = 1;
  NSString *profileName;
  double tag = 0;

  if (argumentCount > 0) {
    if (JSValueIsNumber(context, arguments[0])) {
      tag = JSValueToNumber(context, arguments[0], NULL);
    } else {
      profileName = RCTJSValueToNSString(context, arguments[0]);
    }
  } else {
    profileName = [NSString stringWithFormat:@"Profile %d", profileCounter++];
  }

  if (argumentCount > 1 && JSValueIsString(context, arguments[1])) {
    profileName = RCTJSValueToNSString(context, arguments[1]);
  }

  if (profileName) {
    RCTProfileBeginEvent(tag, profileName, nil);
  }

  return JSValueMakeUndefined(context);
}

static JSValueRef RCTNativeTraceEndSection(JSContextRef context, __unused JSObjectRef object, __unused JSObjectRef thisObject, __unused size_t argumentCount, __unused const JSValueRef arguments[], __unused JSValueRef *exception)
{
  if (argumentCount > 0) {
    JSValueRef *error = NULL;
    double tag = JSValueToNumber(context, arguments[0], error);

    if (error == NULL) {
      RCTProfileEndEvent((uint64_t)tag, @"console", nil);
    }
  }

  return JSValueMakeUndefined(context);
}

static void RCTInstallJSCProfiler(RCTBridge *bridge, JSContextRef context)
{
#if RCT_JSC_PROFILER
  void *JSCProfiler = dlopen(RCT_JSC_PROFILER_DYLIB, RTLD_NOW);
  if (JSCProfiler != NULL) {
    void (*nativeProfilerStart)(JSContextRef, const char *) =
      (__typeof__(nativeProfilerStart))dlsym(JSCProfiler, "nativeProfilerStart");
    void (*nativeProfilerEnd)(JSContextRef, const char *, const char *) =
      (__typeof__(nativeProfilerEnd))dlsym(JSCProfiler, "nativeProfilerEnd");

    if (nativeProfilerStart != NULL && nativeProfilerEnd != NULL) {
      void (*nativeProfilerEnableByteCode)(void) =
        (__typeof__(nativeProfilerEnableByteCode))dlsym(JSCProfiler, "nativeProfilerEnableByteCode");

      if (nativeProfilerEnableByteCode != NULL) {
        nativeProfilerEnableByteCode();
      }

      static BOOL isProfiling = NO;
      [bridge.devMenu addItem:[RCTDevMenuItem toggleItemWithKey:RCTJSCProfilerEnabledDefaultsKey title:@"Start Profiling" selectedTitle:@"Stop Profiling" handler:^(BOOL shouldStart) {

        if (shouldStart == isProfiling) {
          return;
        }

        isProfiling = shouldStart;

        if (shouldStart) {
          nativeProfilerStart(context, "profile");
        } else {
          NSString *outputFile = [NSTemporaryDirectory() stringByAppendingPathComponent:@"cpu_profile.json"];
          nativeProfilerEnd(context, "profile", outputFile.UTF8String);
          NSData *profileData = [NSData dataWithContentsOfFile:outputFile
                                                       options:NSDataReadingMappedIfSafe
                                                         error:NULL];

          RCTProfileSendResult(bridge, @"cpu-profile", profileData);
        }
      }]];
    }
  }
#endif
}

#endif

+ (void)runRunLoopThread
{
  @autoreleasepool {
    // copy thread name to pthread name
    pthread_setname_np([NSThread currentThread].name.UTF8String);

    // Set up a dummy runloop source to avoid spinning
    CFRunLoopSourceContext noSpinCtx = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
    CFRunLoopSourceRef noSpinSource = CFRunLoopSourceCreate(NULL, 0, &noSpinCtx);
    CFRunLoopAddSource(CFRunLoopGetCurrent(), noSpinSource, kCFRunLoopDefaultMode);
    CFRelease(noSpinSource);

    // run the run loop
    while (kCFRunLoopRunStopped != CFRunLoopRunInMode(kCFRunLoopDefaultMode, ((NSDate *)[NSDate distantFuture]).timeIntervalSinceReferenceDate, NO)) {
      RCTAssert(NO, @"not reached assertion"); // runloop spun. that's bad.
    }
  }
}

- (instancetype)init
{
  NSThread *javaScriptThread = [[NSThread alloc] initWithTarget:[self class]
                                                       selector:@selector(runRunLoopThread)
                                                         object:nil];
  javaScriptThread.name = @"com.facebook.React.JavaScript";
  javaScriptThread.threadPriority = [NSThread mainThread].threadPriority;
  [javaScriptThread start];

  return [self initWithJavaScriptThread:javaScriptThread context:nil];
}

- (instancetype)initWithJavaScriptThread:(NSThread *)javaScriptThread
                                 context:(JSContext *)context
{
  RCTAssert(javaScriptThread != nil,
            @"Can't initialize RCTContextExecutor without a javaScriptThread");

  if ((self = [super init])) {
    _valid = YES;
    _javaScriptThread = javaScriptThread;
    __weak RCTContextExecutor *weakSelf = self;
    [self executeBlockOnJavaScriptQueue: ^{
      RCTContextExecutor *strongSelf = weakSelf;
      if (!strongSelf) {
        return;
      }
      // Assumes that no other JS tasks are scheduled before.
      if (context) {
        strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context];
      }
    }];
  }

  return self;
}

- (instancetype)initWithJavaScriptThread:(NSThread *)javaScriptThread
                        globalContextRef:(JSGlobalContextRef)contextRef
{
  JSContext *context = contextRef ? [JSContext contextWithJSGlobalContextRef:contextRef] : nil;
  return [self initWithJavaScriptThread:javaScriptThread context:context];
}

- (void)setUp
{
  __weak RCTContextExecutor *weakSelf = self;
  [self executeBlockOnJavaScriptQueue:^{
    RCTContextExecutor *strongSelf = weakSelf;
    if (!strongSelf.isValid) {
      return;
    }
    if (!strongSelf->_context) {
      JSContext *context = [[JSContext alloc] init];
      strongSelf->_context = [[RCTJavaScriptContext alloc] initWithJSContext:context];
    }
    [strongSelf _addNativeHook:RCTNativeLoggingHook withName:"nativeLoggingHook"];
    [strongSelf _addNativeHook:RCTNoop withName:"noop"];

    __weak RCTBridge *bridge = strongSelf->_bridge;
    strongSelf->_context.context[@"nativeFlushQueueImmediate"] = ^(NSArray *calls){
      if (!weakSelf.valid || !calls) {
        return;
      }

      [bridge handleBuffer:calls batchEnded:NO];
    };

#if RCT_DEV

    [strongSelf _addNativeHook:RCTNativeTraceBeginSection withName:"nativeTraceBeginSection"];
    [strongSelf _addNativeHook:RCTNativeTraceEndSection withName:"nativeTraceEndSection"];

    RCTInstallJSCProfiler(_bridge, strongSelf->_context.ctx);

    for (NSString *event in @[RCTProfileDidStartProfiling, RCTProfileDidEndProfiling]) {
      [[NSNotificationCenter defaultCenter] addObserver:strongSelf
                                               selector:@selector(toggleProfilingFlag:)
                                                   name:event
                                                 object:nil];
    }
#endif
  }];
}

- (void)toggleProfilingFlag:(NSNotification *)notification
{
  [self executeBlockOnJavaScriptQueue:^{
    BOOL enabled = [notification.name isEqualToString:RCTProfileDidStartProfiling];
    NSString *script = [NSString stringWithFormat:@"var p = require('BridgeProfiling') || {}; p.setEnabled && p.setEnabled(%@)", enabled ? @"true" : @"false"];
    JSStringRef scriptJSRef = JSStringCreateWithUTF8CString(script.UTF8String);
    JSEvaluateScript(_context.ctx, scriptJSRef, NULL, NULL, 0, NULL);
    JSStringRelease(scriptJSRef);
  }];
}

- (void)_addNativeHook:(JSObjectCallAsFunctionCallback)hook withName:(const char *)name
{
  JSObjectRef globalObject = JSContextGetGlobalObject(_context.ctx);

  JSStringRef JSName = JSStringCreateWithUTF8CString(name);
  JSObjectSetProperty(_context.ctx, globalObject, JSName, JSObjectMakeFunctionWithCallback(_context.ctx, JSName, hook), kJSPropertyAttributeNone, NULL);
  JSStringRelease(JSName);

}

- (void)invalidate
{
  if (!self.isValid) {
    return;
  }

  _valid = NO;

#if RCT_DEV
  [[NSNotificationCenter defaultCenter] removeObserver:self];
#endif

  [_context performSelector:@selector(invalidate)
                   onThread:_javaScriptThread
                 withObject:nil
              waitUntilDone:NO];
  _context = nil;
}

- (void)dealloc
{
  [self invalidate];
}

- (void)executeJSCall:(NSString *)name
               method:(NSString *)method
            arguments:(NSArray *)arguments
             callback:(RCTJavaScriptCallback)onComplete
{
  RCTAssert(onComplete != nil, @"onComplete block should not be nil");
  __weak RCTContextExecutor *weakSelf = self;
  [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{

    RCTContextExecutor *strongSelf = weakSelf;
    if (!strongSelf || !strongSelf.isValid) {
      return;
    }
    NSError *error;
    NSString *argsString = (arguments.count == 1) ? RCTJSONStringify(arguments[0], &error) : RCTJSONStringify(arguments, &error);
    if (!argsString) {
      RCTLogError(@"Cannot convert argument to string: %@", error);
      onComplete(nil, error);
      return;
    }

    JSValueRef errorJSRef = NULL;
    JSValueRef resultJSRef = NULL;
    JSGlobalContextRef contextJSRef = JSContextGetGlobalContext(strongSelf->_context.ctx);
    JSObjectRef globalObjectJSRef = JSContextGetGlobalObject(strongSelf->_context.ctx);

    // get require
    JSStringRef requireNameJSStringRef = JSStringCreateWithUTF8CString("require");
    JSValueRef requireJSRef = JSObjectGetProperty(contextJSRef, globalObjectJSRef, requireNameJSStringRef, &errorJSRef);
    JSStringRelease(requireNameJSStringRef);

    if (requireJSRef != NULL && !JSValueIsUndefined(contextJSRef, requireJSRef) && errorJSRef == NULL) {

      // get module
      JSStringRef moduleNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)name);
      JSValueRef moduleNameJSRef = JSValueMakeString(contextJSRef, moduleNameJSStringRef);
      JSValueRef moduleJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)requireJSRef, NULL, 1, (const JSValueRef *)&moduleNameJSRef, &errorJSRef);
      JSStringRelease(moduleNameJSStringRef);

      if (moduleJSRef != NULL && errorJSRef == NULL && !JSValueIsUndefined(contextJSRef, moduleJSRef)) {

        // get method
        JSStringRef methodNameJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)method);
        JSValueRef methodJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)moduleJSRef, methodNameJSStringRef, &errorJSRef);
        JSStringRelease(methodNameJSStringRef);

        if (methodJSRef != NULL && errorJSRef == NULL) {

          // direct method invoke with no arguments
          if (arguments.count == 0) {
            resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 0, NULL, &errorJSRef);
          }

          // direct method invoke with 1 argument
          else if(arguments.count == 1) {
            JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString);
            JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef);
            resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)methodJSRef, (JSObjectRef)moduleJSRef, 1, &argsJSRef, &errorJSRef);
            JSStringRelease(argsJSStringRef);

          } else {
            // apply invoke with array of arguments
            JSStringRef applyNameJSStringRef = JSStringCreateWithUTF8CString("apply");
            JSValueRef applyJSRef = JSObjectGetProperty(contextJSRef, (JSObjectRef)methodJSRef, applyNameJSStringRef, &errorJSRef);
            JSStringRelease(applyNameJSStringRef);

            if (applyJSRef != NULL && errorJSRef == NULL) {
              // invoke apply
              JSStringRef argsJSStringRef = JSStringCreateWithCFString((__bridge CFStringRef)argsString);
              JSValueRef argsJSRef = JSValueMakeFromJSONString(contextJSRef, argsJSStringRef);

              JSValueRef args[2];
              args[0] = JSValueMakeNull(contextJSRef);
              args[1] = argsJSRef;

              resultJSRef = JSObjectCallAsFunction(contextJSRef, (JSObjectRef)applyJSRef, (JSObjectRef)methodJSRef, 2, args, &errorJSRef);
              JSStringRelease(argsJSStringRef);
            }
          }
        }
      }
    }

    if (errorJSRef) {
      onComplete(nil, RCTNSErrorFromJSError(contextJSRef, errorJSRef));
      return;
    }

    // Looks like making lots of JSC API calls is slower than communicating by using a JSON
    // string. Also it ensures that data stuctures don't have cycles and non-serializable fields.
    // see [RCTContextExecutorTests testDeserializationPerf]
    id objcValue;
    // We often return `null` from JS when there is nothing for native side. JSONKit takes an extra hundred microseconds
    // to handle this simple case, so we are adding a shortcut to make executeJSCall method even faster
    if (!JSValueIsNull(contextJSRef, resultJSRef)) {
      JSStringRef jsJSONString = JSValueCreateJSONString(contextJSRef, resultJSRef, 0, nil);
      if (jsJSONString) {
        NSString *objcJSONString = (__bridge_transfer NSString *)JSStringCopyCFString(kCFAllocatorDefault, jsJSONString);
        JSStringRelease(jsJSONString);

        objcValue = RCTJSONParse(objcJSONString, NULL);
      }
    }

    onComplete(objcValue, nil);
  }), 0, @"js_call", (@{@"module":name, @"method": method, @"args": arguments}))];
}

- (void)executeApplicationScript:(NSData *)script
                       sourceURL:(NSURL *)sourceURL
                      onComplete:(RCTJavaScriptCompleteBlock)onComplete
{
  RCTAssertParam(script);
  RCTAssertParam(sourceURL);

  __weak RCTContextExecutor *weakSelf = self;
  [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{
    RCTContextExecutor *strongSelf = weakSelf;
    if (!strongSelf || !strongSelf.isValid) {
      return;
    }

    RCTPerformanceLoggerStart(RCTPLScriptExecution);

    // JSStringCreateWithUTF8CString expects a null terminated C string
    NSMutableData *nullTerminatedScript = [NSMutableData dataWithCapacity:script.length + 1];

    [nullTerminatedScript appendData:script];
    [nullTerminatedScript appendBytes:"" length:1];

    JSValueRef jsError = NULL;
    JSStringRef execJSString = JSStringCreateWithUTF8CString(nullTerminatedScript.bytes);
    JSStringRef jsURL = JSStringCreateWithCFString((__bridge CFStringRef)sourceURL.absoluteString);
    JSValueRef result = JSEvaluateScript(strongSelf->_context.ctx, execJSString, NULL, jsURL, 0, &jsError);
    JSStringRelease(jsURL);
    JSStringRelease(execJSString);
    RCTPerformanceLoggerEnd(RCTPLScriptExecution);

    if (onComplete) {
      NSError *error;
      if (!result) {
        error = RCTNSErrorFromJSError(strongSelf->_context.ctx, jsError);
      }
      onComplete(error);
    }
  }), 0, @"js_call", (@{ @"url": sourceURL.absoluteString }))];
}

- (void)executeBlockOnJavaScriptQueue:(dispatch_block_t)block
{
  if ([NSThread currentThread] != _javaScriptThread) {
    [self performSelector:@selector(executeBlockOnJavaScriptQueue:)
                 onThread:_javaScriptThread withObject:block waitUntilDone:NO];
  } else {
    block();
  }
}

- (void)executeAsyncBlockOnJavaScriptQueue:(dispatch_block_t)block
{
  [self performSelector:@selector(executeBlockOnJavaScriptQueue:)
               onThread:_javaScriptThread
             withObject:block
          waitUntilDone:NO];
}

- (void)_runBlock:(dispatch_block_t)block
{
  block();
}

- (void)injectJSONText:(NSString *)script
   asGlobalObjectNamed:(NSString *)objectName
              callback:(RCTJavaScriptCompleteBlock)onComplete
{
  if (RCT_DEBUG) {
    RCTAssert(RCTJSONParse(script, NULL) != nil, @"%@ wasn't valid JSON!", script);
  }

  __weak RCTContextExecutor *weakSelf = self;
  [self executeBlockOnJavaScriptQueue:RCTProfileBlock((^{
    RCTContextExecutor *strongSelf = weakSelf;
    if (!strongSelf || !strongSelf.isValid) {
      return;
    }
    JSStringRef execJSString = JSStringCreateWithCFString((__bridge CFStringRef)script);
    JSValueRef valueToInject = JSValueMakeFromJSONString(strongSelf->_context.ctx, execJSString);
    JSStringRelease(execJSString);

    if (!valueToInject) {
      NSString *errorDesc = [NSString stringWithFormat:@"Can't make JSON value from script '%@'", script];
      RCTLogError(@"%@", errorDesc);

      if (onComplete) {
        NSError *error = [NSError errorWithDomain:@"JS" code:2 userInfo:@{NSLocalizedDescriptionKey: errorDesc}];
        onComplete(error);
      }
      return;
    }

    JSObjectRef globalObject = JSContextGetGlobalObject(strongSelf->_context.ctx);
    JSStringRef JSName = JSStringCreateWithCFString((__bridge CFStringRef)objectName);
    JSObjectSetProperty(strongSelf->_context.ctx, globalObject, JSName, valueToInject, kJSPropertyAttributeNone, NULL);
    JSStringRelease(JSName);
    if (onComplete) {
      onComplete(nil);
    }
  }), 0, @"js_call,json_call", (@{@"objectName": objectName}))];
}

RCT_EXPORT_METHOD(setContextName:(nonnull NSString *)name)
{
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wtautological-pointer-compare"
  if (JSGlobalContextSetName != NULL) {
#pragma clang diagnostic pop
    JSStringRef JSName = JSStringCreateWithCFString((__bridge CFStringRef)name);
    JSGlobalContextSetName(_context.ctx, JSName);
    JSStringRelease(JSName);
  }
}

@end
