SkV8Example.cpp revision 48d94b8ce51a4b75fe1fe996b135fcd5b2d34779
1/* 2 * Copyright 2013 Google Inc. 3 * 4 * 5 * Use of this source code is governed by a BSD-style license that can be 6 * found in the LICENSE file. 7 * 8 */ 9#include <v8.h> 10 11using namespace v8; 12 13#include "SkV8Example.h" 14 15#include "gl/GrGLUtil.h" 16#include "gl/GrGLDefines.h" 17#include "gl/GrGLInterface.h" 18#include "SkApplication.h" 19#include "SkCommandLineFlags.h" 20#include "SkData.h" 21#include "SkDraw.h" 22#include "SkGpuDevice.h" 23#include "SkGraphics.h" 24#include "SkScalar.h" 25 26 27DEFINE_string2(infile, i, NULL, "Name of file to load JS from.\n"); 28 29void application_init() { 30 SkGraphics::Init(); 31 SkEvent::Init(); 32} 33 34void application_term() { 35 SkEvent::Term(); 36 SkGraphics::Term(); 37} 38 39// Extracts a C string from a V8 Utf8Value. 40const char* ToCString(const v8::String::Utf8Value& value) { 41 return *value ? *value : "<string conversion failed>"; 42} 43 44// Slight modification to an original function found in the V8 sample shell.cc. 45void reportException(Isolate* isolate, TryCatch* try_catch) { 46 HandleScope handleScope(isolate); 47 String::Utf8Value exception(try_catch->Exception()); 48 const char* exception_string = ToCString(exception); 49 Handle<Message> message = try_catch->Message(); 50 if (message.IsEmpty()) { 51 // V8 didn't provide any extra information about this error; just 52 // print the exception. 53 fprintf(stderr, "%s\n", exception_string); 54 } else { 55 // Print (filename):(line number): (message). 56 String::Utf8Value filename(message->GetScriptResourceName()); 57 const char* filename_string = ToCString(filename); 58 int linenum = message->GetLineNumber(); 59 fprintf(stderr, 60 "%s:%i: %s\n", filename_string, linenum, exception_string); 61 // Print line of source code. 62 String::Utf8Value sourceline(message->GetSourceLine()); 63 const char* sourceline_string = ToCString(sourceline); 64 fprintf(stderr, "%s\n", sourceline_string); 65 // Print wavy underline. 66 int start = message->GetStartColumn(); 67 for (int i = 0; i < start; i++) { 68 fprintf(stderr, " "); 69 } 70 int end = message->GetEndColumn(); 71 for (int i = start; i < end; i++) { 72 fprintf(stderr, "^"); 73 } 74 fprintf(stderr, "\n"); 75 String::Utf8Value stack_trace(try_catch->StackTrace()); 76 if (stack_trace.length() > 0) { 77 const char* stack_trace_string = ToCString(stack_trace); 78 fprintf(stderr, "%s\n", stack_trace_string); 79 } 80 } 81} 82 83SkV8ExampleWindow::SkV8ExampleWindow(void* hwnd, JsCanvas* canvas) 84 : INHERITED(hwnd) 85 , fJsCanvas(canvas) 86{ 87 this->setConfig(SkBitmap::kARGB_8888_Config); 88 this->setVisibleP(true); 89 this->setClipToBounds(false); 90} 91 92JsCanvas* JsCanvas::Unwrap(Handle<Object> obj) { 93 Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0)); 94 void* ptr = field->Value(); 95 return static_cast<JsCanvas*>(ptr); 96} 97 98void JsCanvas::Inval(const v8::FunctionCallbackInfo<Value>& args) { 99 Unwrap(args.This())->fWindow->inval(NULL); 100} 101 102void JsCanvas::FillRect(const v8::FunctionCallbackInfo<Value>& args) { 103 JsCanvas* jsCanvas = Unwrap(args.This()); 104 SkCanvas* canvas = jsCanvas->fCanvas; 105 106 if (args.Length() != 4) { 107 args.GetIsolate()->ThrowException( 108 v8::String::NewFromUtf8( 109 args.GetIsolate(), "Error: 4 arguments required.")); 110 return; 111 } 112 // TODO(jcgregorio) Really figure out the conversion from JS numbers to 113 // SkScalars. Maybe test if int first? Not sure of the performance impact. 114 double x = args[0]->NumberValue(); 115 double y = args[1]->NumberValue(); 116 double w = args[2]->NumberValue(); 117 double h = args[3]->NumberValue(); 118 119 SkRect rect = { 120 SkDoubleToScalar(x), 121 SkDoubleToScalar(y), 122 SkDoubleToScalar(x) + SkDoubleToScalar(w), 123 SkDoubleToScalar(y) + SkDoubleToScalar(h) 124 }; 125 canvas->drawRect(rect, jsCanvas->fFillStyle); 126} 127 128void JsCanvas::GetFillStyle(Local<String> name, 129 const PropertyCallbackInfo<Value>& info) { 130 JsCanvas* jsCanvas = Unwrap(info.This()); 131 SkColor color = jsCanvas->fFillStyle.getColor(); 132 char buf[8]; 133 sprintf(buf, "#%02X%02X%02X", SkColorGetR(color), SkColorGetG(color), 134 SkColorGetB(color)); 135 136 info.GetReturnValue().Set(String::NewFromUtf8(info.GetIsolate(), buf)); 137} 138 139void JsCanvas::SetFillStyle(Local<String> name, Local<Value> value, 140 const PropertyCallbackInfo<void>& info) { 141 JsCanvas* jsCanvas = Unwrap(info.This()); 142 Local<String> s = value->ToString(); 143 if (s->Length() != 7) { 144 info.GetIsolate()->ThrowException( 145 v8::String::NewFromUtf8( 146 info.GetIsolate(), "Invalid fill style format.")); 147 return; 148 } 149 char buf[8]; 150 s->WriteUtf8(buf, sizeof(buf)); 151 152 if (buf[0] != '#') { 153 info.GetIsolate()->ThrowException( 154 v8::String::NewFromUtf8( 155 info.GetIsolate(), "Invalid fill style format.")); 156 return; 157 } 158 159 long color = strtol(buf+1, NULL, 16); 160 jsCanvas->fFillStyle.setColor(SkColorSetA(SkColor(color), SK_AlphaOPAQUE)); 161} 162 163 164Persistent<ObjectTemplate> JsCanvas::fCanvasTemplate; 165 166Handle<ObjectTemplate> JsCanvas::makeCanvasTemplate() { 167 EscapableHandleScope handleScope(fIsolate); 168 169 Local<ObjectTemplate> result = ObjectTemplate::New(); 170 171 // Add a field to store the pointer to a JsCanvas instance. 172 result->SetInternalFieldCount(1); 173 174 // Add accessors for each of the fields of the canvas object. 175 result->SetAccessor( 176 String::NewFromUtf8(fIsolate, "fillStyle", String::kInternalizedString), 177 GetFillStyle, SetFillStyle); 178 179 // Add methods. 180 result->Set( 181 String::NewFromUtf8( 182 fIsolate, "fillRect", String::kInternalizedString), 183 FunctionTemplate::New(FillRect)); 184 result->Set( 185 String::NewFromUtf8( 186 fIsolate, "inval", String::kInternalizedString), 187 FunctionTemplate::New(Inval)); 188 189 // Return the result through the current handle scope. 190 return handleScope.Escape(result); 191} 192 193 194// Wraps 'this' in a Javascript object. 195Handle<Object> JsCanvas::wrap() { 196 // Handle scope for temporary handles. 197 EscapableHandleScope handleScope(fIsolate); 198 199 // Fetch the template for creating JavaScript JsCanvas wrappers. 200 // It only has to be created once, which we do on demand. 201 if (fCanvasTemplate.IsEmpty()) { 202 Handle<ObjectTemplate> raw_template = this->makeCanvasTemplate(); 203 fCanvasTemplate.Reset(fIsolate, raw_template); 204 } 205 Handle<ObjectTemplate> templ = 206 Local<ObjectTemplate>::New(fIsolate, fCanvasTemplate); 207 208 // Create an empty JsCanvas wrapper. 209 Local<Object> result = templ->NewInstance(); 210 211 // Wrap the raw C++ pointer in an External so it can be referenced 212 // from within JavaScript. 213 Handle<External> canvasPtr = External::New(fIsolate, this); 214 215 // Store the canvas pointer in the JavaScript wrapper. 216 result->SetInternalField(0, canvasPtr); 217 218 // Return the result through the current handle scope. Since each 219 // of these handles will go away when the handle scope is deleted 220 // we need to call Close to let one, the result, escape into the 221 // outer handle scope. 222 return handleScope.Escape(result); 223} 224 225void JsCanvas::onDraw(SkCanvas* canvas, SkOSWindow* window) { 226 // Record canvas and window in this. 227 fCanvas = canvas; 228 fWindow = window; 229 230 // Create a handle scope to keep the temporary object references. 231 HandleScope handleScope(fIsolate); 232 233 // Create a local context from our global context. 234 Local<Context> context = Local<Context>::New(fIsolate, fContext); 235 236 // Enter the context so all the remaining operations take place there. 237 Context::Scope contextScope(context); 238 239 // Wrap the C++ this pointer in a JavaScript wrapper. 240 Handle<Object> canvasObj = this->wrap(); 241 242 // Set up an exception handler before calling the Process function. 243 TryCatch tryCatch; 244 245 // Invoke the process function, giving the global object as 'this' 246 // and one argument, this JsCanvas. 247 const int argc = 1; 248 Handle<Value> argv[argc] = { canvasObj }; 249 Local<Function> onDraw = 250 Local<Function>::New(fIsolate, fOnDraw); 251 Handle<Value> result = onDraw->Call(context->Global(), argc, argv); 252 253 // Handle any exceptions or output. 254 if (result.IsEmpty()) { 255 SkASSERT(tryCatch.HasCaught()); 256 // Print errors that happened during execution. 257 reportException(fIsolate, &tryCatch); 258 } else { 259 SkASSERT(!tryCatch.HasCaught()); 260 if (!result->IsUndefined()) { 261 // If all went well and the result wasn't undefined then print 262 // the returned value. 263 String::Utf8Value str(result); 264 const char* cstr = ToCString(str); 265 printf("%s\n", cstr); 266 } 267 } 268} 269 270void SkV8ExampleWindow::onDraw(SkCanvas* canvas) { 271 272 canvas->save(); 273 canvas->drawColor(SK_ColorWHITE); 274 275 // Now jump into JS and call the onDraw(canvas) method defined there. 276 fJsCanvas->onDraw(canvas, this); 277 278 canvas->restore(); 279 280 INHERITED::onDraw(canvas); 281} 282 283 284#ifdef SK_BUILD_FOR_WIN 285void SkV8ExampleWindow::onHandleInval(const SkIRect& rect) { 286 RECT winRect; 287 winRect.top = rect.top(); 288 winRect.bottom = rect.bottom(); 289 winRect.right = rect.right(); 290 winRect.left = rect.left(); 291 InvalidateRect((HWND)this->getHWND(), &winRect, false); 292} 293#endif 294 295// Creates a new execution environment containing the built-in 296// function draw(). 297Handle<Context> createRootContext(Isolate* isolate) { 298 // Create a template for the global object. 299 Handle<ObjectTemplate> global = ObjectTemplate::New(); 300 301 // This is where we would inject any globals into the root Context 302 // using global->Set(...) 303 304 return Context::New(isolate, NULL, global); 305} 306 307 308// Parse and run script. Then fetch out the onDraw function from the global 309// object. 310bool JsCanvas::initialize(const char script[]) { 311 312 // Create a stack-allocated handle scope. 313 HandleScope handleScope(fIsolate); 314 315 printf("Before create context\n"); 316 317 // Create a new context. 318 Handle<Context> context = createRootContext(fIsolate); 319 320 // Enter the scope so all operations take place in the scope. 321 Context::Scope contextScope(context); 322 323 v8::TryCatch try_catch; 324 325 // Compile the source code. 326 Handle<String> source = String::NewFromUtf8(fIsolate, script); 327 printf("Before Compile\n"); 328 Handle<Script> compiled_script = Script::Compile(source); 329 printf("After Compile\n"); 330 331 if (compiled_script.IsEmpty()) { 332 // Print errors that happened during compilation. 333 reportException(fIsolate, &try_catch); 334 return false; 335 } 336 printf("After Exception.\n"); 337 338 // Try running it now to create the onDraw function. 339 Handle<Value> result = compiled_script->Run(); 340 341 // Handle any exceptions or output. 342 if (result.IsEmpty()) { 343 SkASSERT(try_catch.HasCaught()); 344 // Print errors that happened during execution. 345 reportException(fIsolate, &try_catch); 346 return false; 347 } else { 348 SkASSERT(!try_catch.HasCaught()); 349 if (!result->IsUndefined()) { 350 // If all went well and the result wasn't undefined then print 351 // the returned value. 352 String::Utf8Value str(result); 353 const char* cstr = ToCString(str); 354 printf("%s\n", cstr); 355 return false; 356 } 357 } 358 359 Handle<String> fn_name = String::NewFromUtf8(fIsolate, "onDraw"); 360 Handle<Value> fn_val = context->Global()->Get(fn_name); 361 362 if (!fn_val->IsFunction()) { 363 printf("Not a function.\n"); 364 return false; 365 } 366 367 // It is a function; cast it to a Function. 368 Handle<Function> fn_fun = Handle<Function>::Cast(fn_val); 369 370 // Store the function in a Persistent handle, since we also want that to 371 // remain after this call returns. 372 fOnDraw.Reset(fIsolate, fn_fun); 373 374 // Also make the context persistent. 375 fContext.Reset(fIsolate, context); 376 return true; 377} 378 379SkOSWindow* create_sk_window(void* hwnd, int argc, char** argv) { 380 printf("Started\n"); 381 382 SkCommandLineFlags::Parse(argc, argv); 383 384 // Get the default Isolate created at startup. 385 Isolate* isolate = Isolate::GetCurrent(); 386 387 JsCanvas* jsCanvas = new JsCanvas(isolate); 388 389 const char* script = 390"function onDraw(canvas) { \n" 391" canvas.fillStyle = '#00FF00'; \n" 392" canvas.fillRect(20, 20, 100, 100); \n" 393" canvas.inval(); \n" 394"} \n"; 395 396 SkAutoTUnref<SkData> data; 397 if (FLAGS_infile.count()) { 398 data.reset(SkData::NewFromFileName(FLAGS_infile[0])); 399 script = static_cast<const char*>(data->data()); 400 } 401 if (NULL == script) { 402 printf("Could not load file: %s.\n", FLAGS_infile[0]); 403 exit(1); 404 } 405 if (!jsCanvas->initialize(script)) { 406 printf("Failed to initialize.\n"); 407 exit(1); 408 } 409 return new SkV8ExampleWindow(hwnd, jsCanvas); 410} 411