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
15Global* Global::gGlobal = NULL;
16
17// Extracts a C string from a V8 Utf8Value.
18static const char* to_cstring(const v8::String::Utf8Value& value) {
19    return *value ? *value : "<string conversion failed>";
20}
21
22int32_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.
33void Global::reportException(TryCatch* tryCatch) {
34    HandleScope handleScope(fIsolate);
35    String::Utf8Value exception(tryCatch->Exception());
36    const char* exceptionString = to_cstring(exception);
37    Handle<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        String::Utf8Value filename(message->GetScriptResourceName());
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        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        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();
75void Global::Inval(const v8::FunctionCallbackInfo<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");
84void Global::Print(const v8::FunctionCallbackInfo<v8::Value>& args) {
85    bool first = true;
86    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);
104void 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    Handle<Function> timeoutFn = Handle<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(Integer::New(gGlobal->fIsolate, id));
131}
132
133// Callback function for SkEvents used to implement timeouts.
134bool Global::TimeOutProc(const SkEvent& evt) {
135    // Create a handle scope to keep the temporary object references.
136    HandleScope handleScope(gGlobal->getIsolate());
137
138    // Create a local context from our global context.
139    Local<Context> context = gGlobal->getContext();
140
141    // Enter the context so all the remaining operations take place there.
142    Context::Scope contextScope(context);
143
144    // Set up an exception handler before calling the Process function.
145    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    Local<Function> onTimeout =
155            Local<Function>::New(gGlobal->getIsolate(), gGlobal->fTimeouts[id]);
156    Handle<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            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.
178Handle<Context> Global::createRootContext() {
179  // Create a template for the global object.
180  Handle<ObjectTemplate> global = 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 Context::New(fIsolate, NULL, global);
191}
192
193void Global::initialize() {
194    // Create a stack-allocated handle scope.
195    HandleScope handleScope(fIsolate);
196
197    // Create a new context.
198    Handle<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.
210bool Global::parseScript(const char script[]) {
211
212    // Create a stack-allocated handle scope.
213    HandleScope handleScope(fIsolate);
214
215    // Get the global context.
216    Handle<Context> context = this->getContext();
217
218    // Enter the scope so all operations take place in the scope.
219    Context::Scope contextScope(context);
220
221    v8::TryCatch tryCatch;
222
223    // Compile the source code.
224    Handle<String> source = String::NewFromUtf8(fIsolate, script);
225    Handle<Script> compiledScript = 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    Handle<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