Add a canvas object with drawRect() and inval().

BUG=
R=robertphillips@google.com

Author: jcgregorio@google.com

Review URL: https://codereview.chromium.org/110693002

git-svn-id: http://skia.googlecode.com/svn/trunk@12595 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/.gitignore b/.gitignore
index b44f4da..2e09730 100644
--- a/.gitignore
+++ b/.gitignore
@@ -7,3 +7,4 @@
 out/
 third_party/externals/
 xcodebuild/
+TAGS
diff --git a/experimental/SkV8Example/SkV8Example.cpp b/experimental/SkV8Example/SkV8Example.cpp
index 93f9d70..3c7a5f3 100644
--- a/experimental/SkV8Example/SkV8Example.cpp
+++ b/experimental/SkV8Example/SkV8Example.cpp
@@ -33,139 +33,201 @@
 
 // Extracts a C string from a V8 Utf8Value.
 const char* ToCString(const v8::String::Utf8Value& value) {
-  return *value ? *value : "<string conversion failed>";
+    return *value ? *value : "<string conversion failed>";
 }
 
 // Slight modification to an original function found in the V8 sample shell.cc.
 void reportException(Isolate* isolate, TryCatch* try_catch) {
-  HandleScope handle_scope(isolate);
-  String::Utf8Value exception(try_catch->Exception());
-  const char* exception_string = ToCString(exception);
-  Handle<Message> message = try_catch->Message();
-  if (message.IsEmpty()) {
-    // V8 didn't provide any extra information about this error; just
-    // print the exception.
-    fprintf(stderr, "%s\n", exception_string);
-  } else {
-    // Print (filename):(line number): (message).
-    String::Utf8Value filename(message->GetScriptResourceName());
-    const char* filename_string = ToCString(filename);
-    int linenum = message->GetLineNumber();
-    fprintf(stderr, "%s:%i: %s\n", filename_string, linenum, exception_string);
-    // Print line of source code.
-    String::Utf8Value sourceline(message->GetSourceLine());
-    const char* sourceline_string = ToCString(sourceline);
-    fprintf(stderr, "%s\n", sourceline_string);
-    // Print wavy underline.
-    int start = message->GetStartColumn();
-    for (int i = 0; i < start; i++) {
-      fprintf(stderr, " ");
+    HandleScope handleScope(isolate);
+    String::Utf8Value exception(try_catch->Exception());
+    const char* exception_string = ToCString(exception);
+    Handle<Message> message = try_catch->Message();
+    if (message.IsEmpty()) {
+        // V8 didn't provide any extra information about this error; just
+        // print the exception.
+        fprintf(stderr, "%s\n", exception_string);
+    } else {
+        // Print (filename):(line number): (message).
+        String::Utf8Value filename(message->GetScriptResourceName());
+        const char* filename_string = ToCString(filename);
+        int linenum = message->GetLineNumber();
+        fprintf(stderr,
+                "%s:%i: %s\n", filename_string, linenum, exception_string);
+        // Print line of source code.
+        String::Utf8Value sourceline(message->GetSourceLine());
+        const char* sourceline_string = ToCString(sourceline);
+        fprintf(stderr, "%s\n", sourceline_string);
+        // Print wavy underline.
+        int start = message->GetStartColumn();
+        for (int i = 0; i < start; i++) {
+            fprintf(stderr, " ");
+        }
+        int end = message->GetEndColumn();
+        for (int i = start; i < end; i++) {
+            fprintf(stderr, "^");
+        }
+        fprintf(stderr, "\n");
+        String::Utf8Value stack_trace(try_catch->StackTrace());
+        if (stack_trace.length() > 0) {
+            const char* stack_trace_string = ToCString(stack_trace);
+            fprintf(stderr, "%s\n", stack_trace_string);
+        }
     }
-    int end = message->GetEndColumn();
-    for (int i = start; i < end; i++) {
-      fprintf(stderr, "^");
-    }
-    fprintf(stderr, "\n");
-    String::Utf8Value stack_trace(try_catch->StackTrace());
-    if (stack_trace.length() > 0) {
-      const char* stack_trace_string = ToCString(stack_trace);
-      fprintf(stderr, "%s\n", stack_trace_string);
-    }
-  }
 }
 
-SkV8ExampleWindow::SkV8ExampleWindow(void* hwnd,
-                     Isolate* isolate,
-                     Handle<Context> context,
-                     Handle<Script> script)
+SkV8ExampleWindow::SkV8ExampleWindow(void* hwnd, JsCanvas* canvas)
     : INHERITED(hwnd)
-    , fIsolate(isolate)
+    , fJsCanvas(canvas)
 {
-    // Convert the Handle<> objects into Persistent<> objects using Reset().
-    fContext.Reset(isolate, context);
-    fScript.Reset(isolate, script);
-
     fRotationAngle = SkIntToScalar(0);
     this->setConfig(SkBitmap::kARGB_8888_Config);
     this->setVisibleP(true);
     this->setClipToBounds(false);
 }
 
-// Simple global for the Draw function.
-SkCanvas* gCanvas = NULL;
+JsCanvas* JsCanvas::unwrap(Handle<Object> obj) {
+    Handle<External> field = Handle<External>::Cast(obj->GetInternalField(0));
+    void* ptr = field->Value();
+    return static_cast<JsCanvas*>(ptr);
+}
+
+void JsCanvas::inval(const v8::FunctionCallbackInfo<Value>& args) {
+    unwrap(args.This())->fWindow->inval(NULL);
+}
+
+void JsCanvas::drawRect(const v8::FunctionCallbackInfo<Value>& args) {
+    SkCanvas* canvas = unwrap(args.This())->fCanvas;
+
+    canvas->drawColor(SK_ColorWHITE);
+
+    // Draw a rectangle with red paint.
+    SkPaint paint;
+    paint.setColor(SK_ColorRED);
+    SkRect rect = {
+        SkIntToScalar(10), SkIntToScalar(10),
+        SkIntToScalar(128), SkIntToScalar(128)
+    };
+    canvas->drawRect(rect, paint);
+}
+
+Persistent<ObjectTemplate> JsCanvas::fCanvasTemplate;
+
+Handle<ObjectTemplate> JsCanvas::makeCanvasTemplate() {
+    EscapableHandleScope handleScope(fIsolate);
+
+    Local<ObjectTemplate> result = ObjectTemplate::New();
+
+    // Add a field to store the pointer to a JsCanvas instance.
+    result->SetInternalFieldCount(1);
+
+    // Add accessors for each of the fields of the canvas object.
+    result->Set(
+            String::NewFromUtf8(
+                    fIsolate, "drawRect", String::kInternalizedString),
+            FunctionTemplate::New(drawRect));
+    result->Set(
+            String::NewFromUtf8(
+                    fIsolate, "inval", String::kInternalizedString),
+            FunctionTemplate::New(inval));
+
+    // Return the result through the current handle scope.
+    return handleScope.Escape(result);
+}
 
 
-// Draw is called from within V8 when the Javascript function draw() is called.
-void Draw(const v8::FunctionCallbackInfo<v8::Value>& args) {
-  if (NULL == gCanvas) {
-    printf("Can't Draw Now.\n");
-    return;
-  }
+// Wraps 'this' in a Javascript object.
+Handle<Object> JsCanvas::wrap() {
+    // Handle scope for temporary handles.
+    EscapableHandleScope handleScope(fIsolate);
 
-  gCanvas->drawColor(SK_ColorWHITE);
-  SkPaint paint;
-  paint.setColor(SK_ColorRED);
+    // Fetch the template for creating JavaScript JsCanvas wrappers.
+    // It only has to be created once, which we do on demand.
+    if (fCanvasTemplate.IsEmpty()) {
+        Handle<ObjectTemplate> raw_template = this->makeCanvasTemplate();
+        fCanvasTemplate.Reset(fIsolate, raw_template);
+    }
+    Handle<ObjectTemplate> templ =
+            Local<ObjectTemplate>::New(fIsolate, fCanvasTemplate);
 
-  // Draw a rectangle with blue paint
-  SkRect rect = {
-    SkIntToScalar(10), SkIntToScalar(10),
-    SkIntToScalar(128), SkIntToScalar(128)
-  };
-  gCanvas->drawRect(rect, paint);
+    // Create an empty JsCanvas wrapper.
+    Local<Object> result = templ->NewInstance();
+
+    // Wrap the raw C++ pointer in an External so it can be referenced
+    // from within JavaScript.
+    Handle<External> canvasPtr = External::New(fIsolate, this);
+
+    // Store the canvas pointer in the JavaScript wrapper.
+    result->SetInternalField(0, canvasPtr);
+
+    // Return the result through the current handle scope.  Since each
+    // of these handles will go away when the handle scope is deleted
+    // we need to call Close to let one, the result, escape into the
+    // outer handle scope.
+    return handleScope.Escape(result);
+}
+
+void JsCanvas::onDraw(SkCanvas* canvas, SkOSWindow* window) {
+    // Record canvas and window in this.
+    fCanvas = canvas;
+    fWindow = window;
+
+    // Create a handle scope to keep the temporary object references.
+    HandleScope handleScope(fIsolate);
+
+    // Create a local context from our global context.
+    Local<Context> context = Local<Context>::New(fIsolate, fContext);
+
+    // Enter the context so all the remaining operations take place there.
+    Context::Scope contextScope(context);
+
+    // Wrap the C++ this pointer in a JavaScript wrapper.
+    Handle<Object> canvasObj = this->wrap();
+
+    // Set up an exception handler before calling the Process function.
+    TryCatch tryCatch;
+
+    // Invoke the process function, giving the global object as 'this'
+    // and one argument, this JsCanvas.
+    const int argc = 1;
+    Handle<Value> argv[argc] = { canvasObj };
+    Local<Function> onDraw =
+            Local<Function>::New(fIsolate, fOnDraw);
+    Handle<Value> result = onDraw->Call(context->Global(), argc, argv);
+
+    // Handle any exceptions or output.
+    if (result.IsEmpty()) {
+        SkASSERT(tryCatch.HasCaught());
+        // Print errors that happened during execution.
+        reportException(fIsolate, &tryCatch);
+    } else {
+        SkASSERT(!tryCatch.HasCaught());
+        if (!result->IsUndefined()) {
+            // If all went well and the result wasn't undefined then print
+            // the returned value.
+            String::Utf8Value str(result);
+            const char* cstr = ToCString(str);
+            printf("%s\n", cstr);
+        }
+    }
 }
 
 void SkV8ExampleWindow::onDraw(SkCanvas* canvas) {
-  printf("Draw\n");
 
-  gCanvas = canvas;
-  canvas->save();
-  fRotationAngle += SkDoubleToScalar(0.2);
-  if (fRotationAngle > SkDoubleToScalar(360.0)) {
-    fRotationAngle -= SkDoubleToScalar(360.0);
-  }
-  canvas->rotate(fRotationAngle);
+    canvas->save();
 
-  // Create a Handle scope for temporary references.
-  HandleScope handle_scope(fIsolate);
-
-  // Create a local context from our persistent context.
-  Local<Context> context =
-            Local<Context>::New(fIsolate, fContext);
-
-  // Enter the context so all operations take place within it.
-  Context::Scope context_scope(context);
-
-  TryCatch try_catch;
-
-  // Create a local script from our persistent script.
-  Local<Script> script =
-            Local<Script>::New(fIsolate, fScript);
-
-  // Run the script.
-  Handle<Value> result = script->Run();
-
-  if (result.IsEmpty()) {
-    SkASSERT(try_catch.HasCaught());
-    // Print errors that happened during execution.
-    reportException(fIsolate, &try_catch);
-  } else {
-    SkASSERT(!try_catch.HasCaught());
-    if (!result->IsUndefined()) {
-      // If all went well and the result wasn't undefined then print
-      // the returned value.
-      String::Utf8Value str(result);
-      const char* cstr = ToCString(str);
-      printf("%s\n", cstr);
+    fRotationAngle += SkDoubleToScalar(0.2);
+    if (fRotationAngle > SkDoubleToScalar(360.0)) {
+        fRotationAngle -= SkDoubleToScalar(360.0);
     }
-  }
 
-  canvas->restore();
+    canvas->rotate(fRotationAngle);
 
-  // Trigger an invalidation which should trigger another redraw to simulate
-  // animation.
-  this->inval(NULL);
+    // Now jump into JS and call the onDraw(canvas) method defined there.
+    fJsCanvas->onDraw(canvas, this);
 
-  INHERITED::onDraw(canvas);
+    canvas->restore();
+
+    INHERITED::onDraw(canvas);
 }
 
 
@@ -185,49 +247,99 @@
 Handle<Context> createRootContext(Isolate* isolate) {
   // Create a template for the global object.
   Handle<ObjectTemplate> global = ObjectTemplate::New();
-  // Bind the global 'draw' function to the C++ Draw callback.
-  global->Set(String::NewFromUtf8(isolate, "draw"),
-              FunctionTemplate::New(Draw));
+
+  // This is where we would inject any globals into the root Context
+  // using global->Set(...)
 
   return Context::New(isolate, NULL, global);
 }
 
+
+// Parse and run script. Then fetch out the onDraw function from the global
+// object.
+bool JsCanvas::initialize(const char script[]) {
+
+    // Create a stack-allocated handle scope.
+    HandleScope handleScope(fIsolate);
+
+    printf("Before create context\n");
+
+    // Create a new context.
+    Handle<Context> context = createRootContext(fIsolate);
+
+    // Enter the scope so all operations take place in the scope.
+    Context::Scope contextScope(context);
+
+    v8::TryCatch try_catch;
+
+    // Compile the source code.
+    Handle<String> source = String::NewFromUtf8(fIsolate, script);
+    printf("Before Compile\n");
+    Handle<Script> compiled_script = Script::Compile(source);
+    printf("After Compile\n");
+
+    if (compiled_script.IsEmpty()) {
+        // Print errors that happened during compilation.
+        reportException(fIsolate, &try_catch);
+        return false;
+    }
+    printf("After Exception.\n");
+
+    // Try running it now to create the onDraw function.
+    Handle<Value> result = compiled_script->Run();
+
+    // Handle any exceptions or output.
+    if (result.IsEmpty()) {
+        SkASSERT(try_catch.HasCaught());
+        // Print errors that happened during execution.
+        reportException(fIsolate, &try_catch);
+        return false;
+    } else {
+        SkASSERT(!try_catch.HasCaught());
+        if (!result->IsUndefined()) {
+            // If all went well and the result wasn't undefined then print
+            // the returned value.
+            String::Utf8Value str(result);
+            const char* cstr = ToCString(str);
+            printf("%s\n", cstr);
+            return false;
+        }
+    }
+
+    Handle<String> fn_name = String::NewFromUtf8(fIsolate, "onDraw");
+    Handle<Value> fn_val = context->Global()->Get(fn_name);
+
+    if (!fn_val->IsFunction()) {
+        return false;
+    }
+
+    // It is a function; cast it to a Function.
+    Handle<Function> fn_fun = Handle<Function>::Cast(fn_val);
+
+    // Store the function in a Persistent handle, since we also want that to
+    // remain after this call returns.
+    fOnDraw.Reset(fIsolate, fn_fun);
+
+    // Also make the context persistent.
+    fContext.Reset(fIsolate, context);
+    return true;
+}
+
 SkOSWindow* create_sk_window(void* hwnd, int argc, char** argv) {
-  printf("Started\n");
+    printf("Started\n");
 
-  // Get the default Isolate created at startup.
-  Isolate* isolate = Isolate::GetCurrent();
-  printf("Isolate\n");
+    // Get the default Isolate created at startup.
+    Isolate* isolate = Isolate::GetCurrent();
 
-  // Create a stack-allocated handle scope.
-  HandleScope handle_scope(isolate);
+    JsCanvas* jsCanvas = new JsCanvas(isolate);
+    const char* script = "function onDraw(canvas){"
+            "canvas.drawRect();"
+            "canvas.inval();"
+            "};";
+    if (!jsCanvas->initialize(script)) {
+        printf("Failed to initialize.\n");
+        exit(1);
+    }
 
-  printf("Before create context\n");
-  // Create a new context.
-  //
-  Handle<Context> context = createRootContext(isolate);
-
-  // Enter the scope so all operations take place in the scope.
-  Context::Scope context_scope(context);
-
-  v8::TryCatch try_catch;
-
-  // Compile the source code.
-  Handle<String> source = String::NewFromUtf8(isolate, "draw();");
-  printf("Before Compile\n");
-  Handle<Script> script = Script::Compile(source);
-  printf("After Compile\n");
-
-  // Try running it now. It won't have a valid context, but shouldn't fail.
-  script->Run();
-
-  if (script.IsEmpty()) {
-    // Print errors that happened during compilation.
-    reportException(isolate, &try_catch);
-    exit(1);
-  }
-  printf("After Exception.\n");
-
-  // SkV8ExampleWindow will make persistent handles to hold the context and script.
-  return new SkV8ExampleWindow(hwnd, isolate, context, script);
+    return new SkV8ExampleWindow(hwnd, jsCanvas);
 }
diff --git a/experimental/SkV8Example/SkV8Example.h b/experimental/SkV8Example/SkV8Example.h
index 97cf580..878e1f0 100644
--- a/experimental/SkV8Example/SkV8Example.h
+++ b/experimental/SkV8Example/SkV8Example.h
@@ -14,36 +14,86 @@
 
 #include "SkWindow.h"
 
-
 using namespace v8;
 
-
 class SkCanvas;
-
+class JsCanvas;
 
 class SkV8ExampleWindow : public SkOSWindow {
 public:
-   SkV8ExampleWindow(void* hwnd,
-                     Isolate* isolate,
-                     Handle<Context> context,
-                     Handle<Script> script);
-
+    SkV8ExampleWindow(void* hwnd, JsCanvas* canvas);
 
 protected:
     virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE;
 
-
-
 #ifdef SK_BUILD_FOR_WIN
     virtual void onHandleInval(const SkIRect&) SK_OVERRIDE;
 #endif
 
 private:
     typedef SkOSWindow INHERITED;
-    Isolate* fIsolate;
-    Persistent<Context> fContext;
-    Persistent<Script> fScript;
     SkScalar fRotationAngle;
+    JsCanvas* fJsCanvas;
+};
+
+
+// Provides the canvas implementation in JS, and the OnDraw() method in C++
+// that's used to bridge from C++ to JS. Should be used in JS as:
+//
+//  function onDraw(canvas) {
+//   canvas.drawRect();
+//   canvas.inval();
+//  }
+//
+class JsCanvas {
+public:
+    JsCanvas(Isolate* isolate)
+            : fIsolate(isolate)
+            , fCanvas(NULL)
+            , fWindow(NULL)
+    {}
+    ~JsCanvas();
+
+    // Parse the script.
+    bool initialize(const char script[]);
+
+    // Call this with the SkCanvas you want onDraw to draw on.
+    void onDraw(SkCanvas* canvas, SkOSWindow* window);
+
+private:
+    // Implementation of the canvas.drawRect() JS function.
+    static void drawRect(const v8::FunctionCallbackInfo<Value>& args);
+
+    // Implementation of the canvas.inval() JS function.
+    static void inval(const v8::FunctionCallbackInfo<Value>& args);
+
+    // Get the pointer out of obj.
+    static JsCanvas* unwrap(Handle<Object> obj);
+
+    // Create a template for JS object associated with JsCanvas, called lazily
+    // by Wrap() and the results are stored in fCanvasTemplate;
+    Handle<ObjectTemplate> makeCanvasTemplate();
+
+    // Wrap the 'this' pointer into an Object. Can be retrieved via Unwrap.
+    Handle<Object> wrap();
+
+    Isolate* fIsolate;
+
+    // Only valid when inside OnDraw().
+    SkCanvas* fCanvas;
+
+    // Only valid when inside OnDraw().
+    SkOSWindow* fWindow;
+
+    // The context that the script will be parsed into.
+    Persistent<Context> fContext;
+
+    // A handle to the onDraw function defined in the script.
+    Persistent<Function> fOnDraw;
+
+    // The template for what a canvas object looks like. The canvas object is
+    // what's passed into the JS onDraw() function.
+    static Persistent<ObjectTemplate> fCanvasTemplate;
 };
 
 #endif