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 "Global.h"
10 
11 #include "SkWindow.h"
12 #include "SkEvent.h"
13 
14 
15 Global* Global::gGlobal = NULL;
16 
17 // Extracts a C string from a V8 Utf8Value.
to_cstring(const v8::String::Utf8Value & value)18 static const char* to_cstring(const v8::String::Utf8Value& value) {
19     return *value ? *value : "<string conversion failed>";
20 }
21 
getNextTimerID()22 int32_t Global::getNextTimerID() {
23     do {
24         fLastTimerID++;
25         if (fLastTimerID < 0) {
26             fLastTimerID = 0;
27         }
28     } while (fTimeouts.find(fLastTimerID) != fTimeouts.end());
29     return fLastTimerID;
30 }
31 
32 // Slight modification to an original function found in the V8 sample shell.cc.
reportException(v8::TryCatch * tryCatch)33 void Global::reportException(v8::TryCatch* tryCatch) {
34     v8::HandleScope handleScope(fIsolate);
35     v8::String::Utf8Value exception(tryCatch->Exception());
36     const char* exceptionString = to_cstring(exception);
37     v8::Handle<v8::Message> message = tryCatch->Message();
38     if (message.IsEmpty()) {
39         // V8 didn't provide any extra information about this error; just
40         // print the exception.
41         fprintf(stderr, "%s\n", exceptionString);
42     } else {
43         // Print (filename):(line number): (message).
44         v8::String::Utf8Value filename(message->GetScriptOrigin().ResourceName());
45         const char* filenameString = to_cstring(filename);
46         int linenum = message->GetLineNumber();
47         fprintf(stderr,
48                 "%s:%i: %s\n", filenameString, linenum, exceptionString);
49         // Print line of source code.
50         v8::String::Utf8Value sourceline(message->GetSourceLine());
51         const char* sourceLineString = to_cstring(sourceline);
52         fprintf(stderr, "%s\n", sourceLineString);
53         // Print wavy underline.
54         int start = message->GetStartColumn();
55         for (int i = 0; i < start; i++) {
56             fprintf(stderr, " ");
57         }
58         int end = message->GetEndColumn();
59         for (int i = start; i < end; i++) {
60             fprintf(stderr, "^");
61         }
62         fprintf(stderr, "\n");
63         v8::String::Utf8Value stackTrace(tryCatch->StackTrace());
64         if (stackTrace.length() > 0) {
65             const char* stackTraceString = to_cstring(stackTrace);
66             fprintf(stderr, "%s\n", stackTraceString);
67         }
68     }
69 }
70 
71 // The callback that implements the JavaScript 'inval' function.
72 // Invalidates the current window, forcing a redraw.
73 //
74 // JS: inval();
Inval(const v8::FunctionCallbackInfo<v8::Value> & args)75 void Global::Inval(const v8::FunctionCallbackInfo<v8::Value>& args) {
76     gGlobal->getWindow()->inval(NULL);
77 }
78 
79 // The callback that is invoked by v8 whenever the JavaScript 'print'
80 // function is called. Prints its arguments on stdout separated by
81 // spaces and ending with a newline.
82 //
83 // JS: print("foo", "bar");
Print(const v8::FunctionCallbackInfo<v8::Value> & args)84 void Global::Print(const v8::FunctionCallbackInfo<v8::Value>& args) {
85     bool first = true;
86     v8::HandleScope handleScope(args.GetIsolate());
87     for (int i = 0; i < args.Length(); i++) {
88         if (first) {
89             first = false;
90         } else {
91             printf(" ");
92         }
93         v8::String::Utf8Value str(args[i]);
94         printf("%s", to_cstring(str));
95     }
96     printf("\n");
97     fflush(stdout);
98 }
99 
100 // The callback that is invoked by v8 whenever the JavaScript 'setTimeout'
101 // function is called.
102 //
103 // JS: setTimeout(on_timeout, 500);
SetTimeout(const v8::FunctionCallbackInfo<v8::Value> & args)104 void Global::SetTimeout(const v8::FunctionCallbackInfo<v8::Value>& args) {
105     if (args.Length() != 2) {
106         args.GetIsolate()->ThrowException(
107                 v8::String::NewFromUtf8(
108                         args.GetIsolate(), "Error: 2 arguments required."));
109         return;
110     }
111 
112     // Pull out the first arg, make sure it's a function.
113     if (!args[0]->IsFunction()) {
114         printf("Not a function passed to setTimeout.\n");
115         return;
116     }
117     v8::Handle<v8::Function> timeoutFn = v8::Handle<v8::Function>::Cast(args[0]);
118 
119     double delay = args[1]->NumberValue();
120     int32_t id = gGlobal->getNextTimerID();
121 
122     gGlobal->fTimeouts[id].Reset(gGlobal->fIsolate, timeoutFn);
123 
124     // Create an SkEvent and add it with the right delay.
125     SkEvent* evt = new SkEvent();
126     evt->setTargetProc(Global::TimeOutProc);
127     evt->setFast32(id);
128     evt->postDelay(delay);
129 
130     args.GetReturnValue().Set(v8::Integer::New(gGlobal->fIsolate, id));
131 }
132 
133 // Callback function for SkEvents used to implement timeouts.
TimeOutProc(const SkEvent & evt)134 bool Global::TimeOutProc(const SkEvent& evt) {
135     // Create a handle scope to keep the temporary object references.
136     v8::HandleScope handleScope(gGlobal->getIsolate());
137 
138     // Create a local context from our global context.
139     v8::Local<v8::Context> context = gGlobal->getContext();
140 
141     // Enter the context so all the remaining operations take place there.
142     v8::Context::Scope contextScope(context);
143 
144     // Set up an exception handler before calling the Process function.
145     v8::TryCatch tryCatch;
146 
147     int32_t id = evt.getFast32();
148     if (gGlobal->fTimeouts.find(gGlobal->fLastTimerID) == gGlobal->fTimeouts.end()) {
149         printf("Not a valid timer ID.\n");
150         return true;
151     }
152 
153     const int argc = 0;
154     v8::Local<v8::Function> onTimeout =
155             v8::Local<v8::Function>::New(gGlobal->getIsolate(), gGlobal->fTimeouts[id]);
156     v8::Handle<v8::Value> result = onTimeout->Call(context->Global(), argc, NULL);
157     gGlobal->fTimeouts.erase(id);
158 
159     // Handle any exceptions or output.
160     if (result.IsEmpty()) {
161         SkASSERT(tryCatch.HasCaught());
162         // Print errors that happened during execution.
163         gGlobal->reportException(&tryCatch);
164     } else {
165         SkASSERT(!tryCatch.HasCaught());
166         if (!result->IsUndefined()) {
167             // If all went well and the result wasn't undefined then print the
168             // returned value.
169             v8::String::Utf8Value str(result);
170             const char* cstr = to_cstring(str);
171             printf("%s\n", cstr);
172         }
173     }
174     return true;
175 }
176 
177 // Creates a new execution environment containing the built-in functions.
createRootContext()178 v8::Handle<v8::Context> Global::createRootContext() {
179   // Create a template for the global object.
180   v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
181 
182   global->Set(v8::String::NewFromUtf8(fIsolate, "print"),
183               v8::FunctionTemplate::New(fIsolate, Global::Print));
184   global->Set(v8::String::NewFromUtf8(fIsolate, "setTimeout"),
185               v8::FunctionTemplate::New(fIsolate, Global::SetTimeout));
186   global->Set(v8::String::NewFromUtf8(fIsolate, "inval"),
187               v8::FunctionTemplate::New(fIsolate, Global::Inval));
188 
189 
190   return v8::Context::New(fIsolate, NULL, global);
191 }
192 
initialize()193 void Global::initialize() {
194     // Create a stack-allocated handle scope.
195     v8::HandleScope handleScope(fIsolate);
196 
197     // Create a new context.
198     v8::Handle<v8::Context> context = this->createRootContext();
199 
200     // Make the context persistent.
201     fContext.Reset(fIsolate, context);
202 }
203 
204 
205 // Creates the root context, parses the script into it, then stores the
206 // context in a global.
207 //
208 // TODO(jcgregorio) Currently only handles one script. Need to move
209 // createRootContext to another call that's only done once.
parseScript(const char script[])210 bool Global::parseScript(const char script[]) {
211 
212     // Create a stack-allocated handle scope.
213     v8::HandleScope handleScope(fIsolate);
214 
215     // Get the global context.
216     v8::Handle<v8::Context> context = this->getContext();
217 
218     // Enter the scope so all operations take place in the scope.
219     v8::Context::Scope contextScope(context);
220 
221     v8::TryCatch tryCatch;
222 
223     // Compile the source code.
224     v8::Handle<v8::String> source = v8::String::NewFromUtf8(fIsolate, script);
225     v8::Handle<v8::Script> compiledScript = v8::Script::Compile(source);
226 
227     if (compiledScript.IsEmpty()) {
228         // Print errors that happened during compilation.
229         this->reportException(&tryCatch);
230         return false;
231     }
232 
233     // Try running it now to create the onDraw function.
234     v8::Handle<v8::Value> result = compiledScript->Run();
235 
236     // Handle any exceptions or output.
237     if (result.IsEmpty()) {
238         SkASSERT(tryCatch.HasCaught());
239         // Print errors that happened during execution.
240         this->reportException(&tryCatch);
241         return false;
242     }
243 
244     return true;
245 }
246