#import #import #import #import "atom_application.h" #import "native.h" #import "include/cef_base.h" #import "path_watcher.h" #import #include static std::string windowState = "{}"; static NSLock *windowStateLock = [[NSLock alloc] init]; namespace v8_extensions { using namespace std; NSString *stringFromCefV8Value(const CefRefPtr& value); void throwException(const CefRefPtr& global, CefRefPtr exception, NSString *message); Native::Native() : CefV8Handler() { } void Native::CreateContextBinding(CefRefPtr context) { const char* methodNames[] = { "exists", "read", "write", "absolute", "getAllFilePathsAsync", "traverseTree", "isDirectory", "isFile", "remove", "writeToPasteboard", "readFromPasteboard", "quit", "watchPath", "unwatchPath", "getWatchedPaths", "unwatchAllPaths", "makeDirectory", "move", "moveToTrash", "reload", "lastModified", "md5ForPath", "exec", "getPlatform", "setWindowState", "getWindowState" }; CefRefPtr nativeObject = CefV8Value::CreateObject(NULL); int arrayLength = sizeof(methodNames) / sizeof(const char *); for (int i = 0; i < arrayLength; i++) { const char *functionName = methodNames[i]; CefRefPtr function = CefV8Value::CreateFunction(functionName, this); nativeObject->SetValue(functionName, function, V8_PROPERTY_ATTRIBUTE_NONE); } CefRefPtr global = context->GetGlobal(); global->SetValue("$native", nativeObject, V8_PROPERTY_ATTRIBUTE_NONE); } bool Native::Execute(const CefString& name, CefRefPtr object, const CefV8ValueList& arguments, CefRefPtr& retval, CefString& exception) { if (name == "exists") { std::string cc_value = arguments[0]->GetStringValue().ToString(); const char *path = cc_value.c_str(); retval = CefV8Value::CreateBool(access(path, F_OK) == 0); return true; } else if (name == "read") { NSString *path = stringFromCefV8Value(arguments[0]); NSError *error = nil; NSStringEncoding *encoding = nil; NSString *contents = [NSString stringWithContentsOfFile:path usedEncoding:encoding error:&error]; NSError *binaryFileError = nil; if (error) { contents = [NSString stringWithContentsOfFile:path encoding:NSASCIIStringEncoding error:&binaryFileError]; } if (binaryFileError) { exception = [[binaryFileError localizedDescription] UTF8String]; } else { retval = CefV8Value::CreateString([contents UTF8String]); } return true; } else if (name == "write") { NSString *path = stringFromCefV8Value(arguments[0]); NSString *content = stringFromCefV8Value(arguments[1]); NSFileManager *fm = [NSFileManager defaultManager]; // Create parent directories if they don't exist BOOL exists = [fm fileExistsAtPath:[path stringByDeletingLastPathComponent] isDirectory:nil]; if (!exists) { [fm createDirectoryAtPath:[path stringByDeletingLastPathComponent] withIntermediateDirectories:YES attributes:nil error:nil]; } NSError *error = nil; BOOL success = [content writeToFile:path atomically:YES encoding:NSUTF8StringEncoding error:&error]; if (error) { exception = [[error localizedDescription] UTF8String]; } else if (!success) { std::string exception = "Cannot write to '"; exception += [path UTF8String]; exception += "'"; } return true; } else if (name == "absolute") { NSString *path = stringFromCefV8Value(arguments[0]); path = [path stringByStandardizingPath]; if ([path characterAtIndex:0] == '/') { retval = CefV8Value::CreateString([path UTF8String]); } return true; } else if (name == "getAllFilePathsAsync") { std::string argument = arguments[0]->GetStringValue().ToString(); CefRefPtr callback = arguments[1]; CefRefPtr context = CefV8Context::GetCurrentContext(); dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0); dispatch_async(queue, ^{ int rootPathLength = argument.size() + 1; char rootPath[rootPathLength]; strcpy(rootPath, argument.c_str()); char * const treePaths[] = {rootPath, NULL}; FTS *tree = fts_open(treePaths, FTS_COMFOLLOW | FTS_PHYSICAL| FTS_NOCHDIR | FTS_NOSTAT, NULL); std::vector paths; if (tree != NULL) { FTSENT *entry; int arrayIndex = 0; while ((entry = fts_read(tree)) != NULL) { if (entry->fts_level == 0) { continue; } bool isFile = entry->fts_info == FTS_NSOK; if (!isFile) { continue; } int pathLength = entry->fts_pathlen - rootPathLength; char relative[pathLength + 1]; relative[pathLength] = '\0'; strncpy(relative, entry->fts_path + rootPathLength, pathLength); paths.push_back(relative); } } dispatch_queue_t mainQueue = dispatch_get_main_queue(); dispatch_async(mainQueue, ^{ context->Enter(); CefRefPtr v8Paths = CefV8Value::CreateArray(paths.size()); for (int i = 0; i < paths.size(); i++) { v8Paths->SetValue(i, CefV8Value::CreateString(paths[i])); } CefV8ValueList callbackArgs; callbackArgs.push_back(v8Paths); callback->ExecuteFunction(callback, callbackArgs); context->Exit(); }); }); return true; } else if (name == "traverseTree") { std::string argument = arguments[0]->GetStringValue().ToString(); int rootPathLength = argument.size() + 1; char rootPath[rootPathLength]; strcpy(rootPath, argument.c_str()); char * const paths[] = {rootPath, NULL}; FTS *tree = fts_open(paths, FTS_COMFOLLOW | FTS_PHYSICAL| FTS_NOCHDIR | FTS_NOSTAT, NULL); if (tree == NULL) { return true; } CefRefPtr onFile = arguments[1]; CefRefPtr onDir = arguments[2]; CefV8ValueList args; FTSENT *entry; while ((entry = fts_read(tree)) != NULL) { if (entry->fts_level == 0) { continue; } bool isFile = entry->fts_info == FTS_NSOK; bool isDir = entry->fts_info == FTS_D; if (!isFile && !isDir) { continue; } int pathLength = entry->fts_pathlen - rootPathLength; char relative[pathLength + 1]; relative[pathLength] = '\0'; strncpy(relative, entry->fts_path + rootPathLength, pathLength); args.clear(); args.push_back(CefV8Value::CreateString(relative)); if (isFile) { onFile->ExecuteFunction(onFile, args); } else { CefRefPtr enterDir = onDir->ExecuteFunction(onDir, args); if(enterDir != NULL && !enterDir->GetBoolValue()) { fts_set(tree, entry, FTS_SKIP); } } } return true; } else if (name == "isDirectory") { NSString *path = stringFromCefV8Value(arguments[0]); BOOL isDir = false; BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]; retval = CefV8Value::CreateBool(exists && isDir); return true; } else if (name == "isFile") { NSString *path = stringFromCefV8Value(arguments[0]); BOOL isDir = false; BOOL exists = [[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&isDir]; retval = CefV8Value::CreateBool(exists && !isDir); return true; } else if (name == "remove") { NSString *path = stringFromCefV8Value(arguments[0]); NSError *error = nil; [[NSFileManager defaultManager] removeItemAtPath:path error:&error]; if (error) { exception = [[error localizedDescription] UTF8String]; } return true; } else if (name == "writeToPasteboard") { NSString *text = stringFromCefV8Value(arguments[0]); NSPasteboard *pb = [NSPasteboard generalPasteboard]; [pb declareTypes:[NSArray arrayWithObjects:NSStringPboardType, nil] owner:nil]; [pb setString:text forType:NSStringPboardType]; return true; } else if (name == "readFromPasteboard") { NSPasteboard *pb = [NSPasteboard generalPasteboard]; NSArray *results = [pb readObjectsForClasses:[NSArray arrayWithObjects:[NSString class], nil] options:nil]; if (results) { retval = CefV8Value::CreateString([[results objectAtIndex:0] UTF8String]); } return true; } else if (name == "quit") { [NSApp terminate:nil]; return true; } else if (name == "watchPath") { NSString *path = stringFromCefV8Value(arguments[0]); CefRefPtr function = arguments[1]; CefRefPtr context = CefV8Context::GetCurrentContext(); WatchCallback callback = ^(NSString *eventType, NSString *path) { context->Enter(); CefV8ValueList args; args.push_back(CefV8Value::CreateString(std::string([eventType UTF8String], [eventType lengthOfBytesUsingEncoding:NSUTF8StringEncoding]))); args.push_back(CefV8Value::CreateString(std::string([path UTF8String], [path lengthOfBytesUsingEncoding:NSUTF8StringEncoding]))); function->ExecuteFunction(function, args); context->Exit(); }; PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()]; NSString *watchId = [pathWatcher watchPath:path callback:[[callback copy] autorelease]]; if (watchId) { retval = CefV8Value::CreateString([watchId UTF8String]); } else { exception = std::string("Failed to watch path '") + std::string([path UTF8String]) + std::string("' (it may not exist)"); } return true; } else if (name == "unwatchPath") { NSString *path = stringFromCefV8Value(arguments[0]); NSString *callbackId = stringFromCefV8Value(arguments[1]); NSError *error = nil; PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()]; [pathWatcher unwatchPath:path callbackId:callbackId error:&error]; if (error) { exception = [[error localizedDescription] UTF8String]; } return true; } else if (name == "getWatchedPaths") { PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()]; NSArray *paths = [pathWatcher watchedPaths]; CefRefPtr pathsArray = CefV8Value::CreateArray([paths count]); for (int i = 0; i < [paths count]; i++) { CefRefPtr path = CefV8Value::CreateString([[paths objectAtIndex:i] UTF8String]); pathsArray->SetValue(i, path); } retval = pathsArray; return true; } else if (name == "unwatchAllPaths") { PathWatcher *pathWatcher = [PathWatcher pathWatcherForContext:CefV8Context::GetCurrentContext()]; [pathWatcher unwatchAllPaths]; return true; } else if (name == "makeDirectory") { NSString *path = stringFromCefV8Value(arguments[0]); NSFileManager *fm = [NSFileManager defaultManager]; NSError *error = nil; [fm createDirectoryAtPath:path withIntermediateDirectories:NO attributes:nil error:&error]; if (error) { exception = [[error localizedDescription] UTF8String]; } return true; } else if (name == "move") { NSString *sourcePath = stringFromCefV8Value(arguments[0]); NSString *targetPath = stringFromCefV8Value(arguments[1]); NSFileManager *fm = [NSFileManager defaultManager]; NSError *error = nil; [fm moveItemAtPath:sourcePath toPath:targetPath error:&error]; if (error) { exception = [[error localizedDescription] UTF8String]; } return true; } else if (name == "moveToTrash") { NSString *sourcePath = stringFromCefV8Value(arguments[0]); bool success = [[NSWorkspace sharedWorkspace] performFileOperation:NSWorkspaceRecycleOperation source:[sourcePath stringByDeletingLastPathComponent] destination:@"" files:[NSArray arrayWithObject:[sourcePath lastPathComponent]] tag:nil]; if (!success) { std::string exception = "Can not move "; exception += [sourcePath UTF8String]; exception += " to trash."; } return true; } else if (name == "reload") { CefV8Context::GetCurrentContext()->GetBrowser()->ReloadIgnoreCache(); } else if (name == "lastModified") { NSString *path = stringFromCefV8Value(arguments[0]); NSFileManager *fm = [NSFileManager defaultManager]; NSError *error = nil; NSDictionary *attributes = [fm attributesOfItemAtPath:path error:&error]; if (error) { exception = [[error localizedDescription] UTF8String]; } NSDate *lastModified = [attributes objectForKey:NSFileModificationDate]; retval = CefV8Value::CreateDate(CefTime([lastModified timeIntervalSince1970])); return true; } else if (name == "md5ForPath") { NSString *path = stringFromCefV8Value(arguments[0]); unsigned char outputData[CC_MD5_DIGEST_LENGTH]; NSData *inputData = [[NSData alloc] initWithContentsOfFile:path]; CC_MD5([inputData bytes], [inputData length], outputData); [inputData release]; NSMutableString *hash = [[NSMutableString alloc] init]; for (NSUInteger i = 0; i < CC_MD5_DIGEST_LENGTH; i++) { [hash appendFormat:@"%02x", outputData[i]]; } retval = CefV8Value::CreateString([hash UTF8String]); return true; } else if (name == "exec") { NSString *command = stringFromCefV8Value(arguments[0]); CefRefPtr options = arguments[1]; CefRefPtr callback = arguments[2]; NSTask *task = [[NSTask alloc] init]; [task setLaunchPath:@"/bin/sh"]; [task setStandardInput:[NSFileHandle fileHandleWithNullDevice]]; [task setArguments:[NSArray arrayWithObjects:@"-l", @"-c", command, nil]]; NSPipe *stdout = [NSPipe pipe]; NSPipe *stderr = [NSPipe pipe]; [task setStandardOutput:stdout]; [task setStandardError:stderr]; CefRefPtr context = CefV8Context::GetCurrentContext(); void (^outputHandle)(NSString *contents, CefRefPtr function) = nil; void (^taskTerminatedHandle)(NSString *output, NSString *errorOutput) = nil; outputHandle = ^(NSString *contents, CefRefPtr function) { context->Enter(); CefV8ValueList args; args.push_back(CefV8Value::CreateString(std::string([contents UTF8String], [contents lengthOfBytesUsingEncoding:NSUTF8StringEncoding]))); CefRefPtr retval = function->ExecuteFunction(function, args); if (function->HasException()) { throwException(context->GetGlobal(), function->GetException(), @"Error thrown in OutputHandle"); } context->Exit(); }; taskTerminatedHandle = ^(NSString *output, NSString *errorOutput) { context->Enter(); CefV8ValueList args; args.push_back(CefV8Value::CreateInt([task terminationStatus])); args.push_back(CefV8Value::CreateString([output UTF8String])); args.push_back(CefV8Value::CreateString([errorOutput UTF8String])); callback->ExecuteFunction(callback, args); if (callback->HasException()) { throwException(context->GetGlobal(), callback->GetException(), @"Error thrown in TaskTerminatedHandle"); } context->Exit(); stdout.fileHandleForReading.writeabilityHandler = nil; stderr.fileHandleForReading.writeabilityHandler = nil; }; task.terminationHandler = ^(NSTask *) { NSString *output = [[NSString alloc] initWithData:[[stdout fileHandleForReading] readDataToEndOfFile] encoding:NSUTF8StringEncoding]; NSString *errorOutput = [[NSString alloc] initWithData:[[stderr fileHandleForReading] readDataToEndOfFile] encoding:NSUTF8StringEncoding]; dispatch_sync(dispatch_get_main_queue(), ^() { taskTerminatedHandle(output, errorOutput); }); [output release]; [errorOutput release]; }; CefRefPtr stdoutFunction = options->GetValue("stdout"); if (stdoutFunction->IsFunction()) { stdout.fileHandleForReading.writeabilityHandler = ^(NSFileHandle *fileHandle) { NSData *data = [fileHandle availableData]; NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; dispatch_sync(dispatch_get_main_queue(), ^() { outputHandle(contents, stdoutFunction); }); [contents release]; }; } CefRefPtr stderrFunction = options->GetValue("stderr"); if (stderrFunction->IsFunction()) { stderr.fileHandleForReading.writeabilityHandler = ^(NSFileHandle *fileHandle) { NSData *data = [fileHandle availableData]; NSString *contents = [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]; dispatch_sync(dispatch_get_main_queue(), ^() { outputHandle(contents, stderrFunction); }); [contents release]; }; } [task launch]; return true; } else if (name == "getPlatform") { retval = CefV8Value::CreateString("mac"); return true; } else if (name == "setWindowState") { [windowStateLock lock]; windowState = arguments[0]->GetStringValue().ToString(); [windowStateLock unlock]; return true; } else if (name == "getWindowState") { [windowStateLock lock]; retval = CefV8Value::CreateString(windowState); [windowStateLock unlock]; return true; } return false; }; NSString *stringFromCefV8Value(const CefRefPtr& value) { std::string cc_value = value->GetStringValue().ToString(); return [NSString stringWithUTF8String:cc_value.c_str()]; } void throwException(const CefRefPtr& global, CefRefPtr exception, NSString *message) { CefV8ValueList arguments; message = [message stringByAppendingFormat:@"\n%s", exception->GetMessage().ToString().c_str()]; arguments.push_back(CefV8Value::CreateString(std::string([message UTF8String], [message lengthOfBytesUsingEncoding:NSUTF8StringEncoding]))); CefRefPtr console = global->GetValue("console"); console->GetValue("error")->ExecuteFunction(console, arguments); } } // namespace v8_extensions