WebKitTools:
[WebKit-https.git] / WebKitTools / Drosera / DebuggerDocument.cpp
1 /*
2  * Copyright (C) 2007 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 #include "config.h"
30 #include "DebuggerDocument.h"
31
32 #include "ServerConnection.h"
33
34 #include <JavaScriptCore/JSContextRef.h>
35 #include <JavaScriptCore/JSRetainPtr.h>
36 #include <JavaScriptCore/JSStringRefCF.h>
37 #include <JavaScriptCore/RetainPtr.h>
38
39 DebuggerDocument::DebuggerDocument(ServerConnection* serverConn)
40     : m_server(serverConn)
41 {
42     ASSERT(m_server);
43 }
44
45 //-- Callbacks
46
47 JSValueRef DebuggerDocument::breakpointEditorHTMLCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef /*thisObject*/, size_t /*argumentCount*/, const JSValueRef /*arguments*/[], JSValueRef* /*exception*/)
48 {
49     RetainPtr<CFURLRef> htmlURLRef(AdoptCF, ::CFBundleCopyResourceURL(::CFBundleGetBundleWithIdentifier(CFSTR("org.webkit.drosera")), CFSTR("breakpointEditor"), CFSTR("html"), 0));
50     if (!htmlURLRef)
51         return JSValueMakeUndefined(context);
52
53     // FIXME: I'm open to a better way to do this.  We convert from UInt8 to CFString to JSString (3 string types!)
54     RetainPtr<CFReadStreamRef> readStreamRef(AdoptCF, CFReadStreamCreateWithFile(0, htmlURLRef.get()));
55     CFReadStreamRef readStream = readStreamRef.get();
56
57     if (!CFReadStreamOpen(readStream))
58         return JSValueMakeUndefined(context);
59
60     // Large enough for current BreakPointEditor.html but won't need to be changed if that file changes 
61     // because we loop over the entire file and read it in bufferLength pieces at a time
62     const CFIndex bufferLength = 740;
63     UInt8 buffer[bufferLength];
64     Vector<UInt8, bufferLength> charBuffer;
65     CFIndex readResult = bufferLength;
66     while (readResult == bufferLength) {
67         readResult = CFReadStreamRead(readStream, buffer, bufferLength);
68
69         // Error condition (-1) will not copy any data
70         for (int i = 0; i < readResult; i++)
71             charBuffer.append(buffer[i]);
72     }
73
74     CFReadStreamClose(readStream);
75     if (readResult == -1)
76         return JSValueMakeUndefined(context);
77
78     // FIXME: Is there a way to determine the encoding?
79     RetainPtr<CFStringRef> fileContents(AdoptCF, CFStringCreateWithBytes(0, charBuffer.data(), charBuffer.size(), kCFStringEncodingUTF8, true));
80     JSRetainPtr<JSStringRef> fileContentsJS(Adopt, JSStringCreateWithCFString(fileContents.get()));
81     JSValueRef ret = JSValueMakeString(context, fileContentsJS.get());
82
83     return ret;
84 }
85
86 JSValueRef DebuggerDocument::pauseCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef /*arguments*/[], JSValueRef* /*exception*/)
87 {
88     DebuggerDocument* debuggerDocument = reinterpret_cast<DebuggerDocument*>(JSObjectGetPrivate(thisObject));
89
90     debuggerDocument->platformPause();
91     return JSValueMakeUndefined(context);
92 }
93
94 JSValueRef DebuggerDocument::resumeCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef /*arguments*/[], JSValueRef* /*exception*/)
95 {
96     DebuggerDocument* debuggerDocument = reinterpret_cast<DebuggerDocument*>(JSObjectGetPrivate(thisObject));
97     debuggerDocument->platformResume();
98     return JSValueMakeUndefined(context);
99 }
100
101 JSValueRef DebuggerDocument::stepIntoCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef /*arguments*/[], JSValueRef* /*exception*/)
102 {
103     DebuggerDocument* debuggerDocument = reinterpret_cast<DebuggerDocument*>(JSObjectGetPrivate(thisObject));
104     debuggerDocument->platformStepInto();
105     return JSValueMakeUndefined(context);
106 }
107
108 JSValueRef DebuggerDocument::evaluateScriptCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
109 {
110     DebuggerDocument* debuggerDocument = reinterpret_cast<DebuggerDocument*>(JSObjectGetPrivate(thisObject));
111     if (argumentCount < 2)
112         return JSValueMakeUndefined(context);
113
114     if (!JSValueIsNumber(context, arguments[1]))
115         return JSValueMakeUndefined(context);
116     
117     double callFrame = JSValueToNumber(context, arguments[1], exception);
118     ASSERT(!*exception);
119
120     JSRetainPtr<JSStringRef> script(Adopt, JSValueToStringCopy(context, arguments[0], exception));
121     ASSERT(!*exception);
122
123     JSValueRef ret = debuggerDocument->platformEvaluateScript(context, script.get(), (int)callFrame);
124
125     return ret;
126 }
127
128 JSValueRef DebuggerDocument::currentFunctionStackCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t /*argumentCount*/, const JSValueRef /*arguments*/[], JSValueRef* exception)
129 {
130     DebuggerDocument* debuggerDocument = reinterpret_cast<DebuggerDocument*>(JSObjectGetPrivate(thisObject));
131     Vector<JSValueRef> stack;
132     debuggerDocument->getPlatformCurrentFunctionStack(context, stack);
133     return DebuggerDocument::toJSArray(context, stack, exception);
134 }
135
136 JSValueRef DebuggerDocument::localScopeVariableNamesForCallFrameCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
137 {
138     DebuggerDocument* debuggerDocument = reinterpret_cast<DebuggerDocument*>(JSObjectGetPrivate(thisObject));
139     if (argumentCount < 1)
140         return JSValueMakeUndefined(context);
141
142     if (!JSValueIsNumber(context, arguments[0]))
143         return JSValueMakeUndefined(context);    
144
145     double callFrame = JSValueToNumber(context, arguments[0], exception);
146     ASSERT(!*exception);
147
148     // Get the variable names
149     Vector<JSValueRef> localVariableNames;
150
151     debuggerDocument->getPlatformLocalScopeVariableNamesForCallFrame(context, static_cast<int>(callFrame), localVariableNames);
152     return DebuggerDocument::toJSArray(context, localVariableNames, exception);
153 }
154
155 JSValueRef DebuggerDocument::valueForScopeVariableNamedCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef thisObject, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
156 {
157     DebuggerDocument* debuggerDocument = reinterpret_cast<DebuggerDocument*>(JSObjectGetPrivate(thisObject));
158
159     if (argumentCount < 2)
160         return JSValueMakeUndefined(context);
161
162     if (!JSValueIsString(context, arguments[0]))
163         return JSValueMakeUndefined(context);
164
165     JSRetainPtr<JSStringRef> key(Adopt, JSValueToStringCopy(context, arguments[0], exception));
166     ASSERT(!*exception);
167
168     if (!JSValueIsNumber(context, arguments[1]))
169         return JSValueMakeUndefined(context);
170
171     double callFrame = JSValueToNumber(context, arguments[1], exception);
172     ASSERT(!*exception);
173
174     return debuggerDocument->platformValueForScopeVariableNamed(context, key.get(), (int)callFrame);
175 }
176
177 JSValueRef DebuggerDocument::logCallback(JSContextRef context, JSObjectRef /*function*/, JSObjectRef /*thisObject*/, size_t argumentCount, const JSValueRef arguments[], JSValueRef* exception)
178 {
179     if (argumentCount < 1)
180         return JSValueMakeUndefined(context);
181
182     if (!JSValueIsString(context, arguments[0]))
183         return JSValueMakeUndefined(context);
184
185     JSRetainPtr<JSStringRef> msg(Adopt, JSValueToStringCopy(context, arguments[0], exception));
186     ASSERT(!*exception);
187
188     DebuggerDocument::platformLog(msg.get());
189     return JSValueMakeUndefined(context);
190 }
191
192 //-- These are the calls into the JS. --//
193
194 bool DebuggerDocument::isPaused(JSContextRef context) const
195 {
196     JSObjectRef globalObject = JSContextGetGlobalObject(context);
197     JSRetainPtr<JSStringRef> string(Adopt, JSStringCreateWithUTF8CString("isPaused"));
198     JSValueRef objectProperty = JSObjectGetProperty(context, globalObject, string.get(), 0);
199     return JSValueToBoolean(context, objectProperty);
200 }
201
202 void DebuggerDocument::updateFileSource(JSContextRef context, JSStringRef documentSource, JSStringRef url)
203 {
204     JSValueRef documentSourceValue = JSValueMakeString(context, documentSource);
205     JSValueRef urlValue = JSValueMakeString(context, url);
206     JSValueRef forceValue = JSValueMakeBoolean(context, false);
207
208     JSValueRef arguments[] = { documentSourceValue, urlValue, forceValue };
209     int argumentsSize = sizeof(arguments)/sizeof(arguments[0]);
210
211     DebuggerDocument::callGlobalFunction(context, "updateFileSource", argumentsSize, arguments);
212 }
213
214 void DebuggerDocument::didParseScript(JSContextRef context, JSStringRef source, JSStringRef documentSource, JSStringRef url, JSValueRef sourceId, JSValueRef baseLine)
215 {
216     JSValueRef sourceValue = JSValueMakeString(context, source);
217     JSValueRef documentSourceValue = JSValueMakeString(context, documentSource);
218     JSValueRef urlValue = JSValueMakeString(context, url);
219
220     JSValueRef arguments[] = { sourceValue, documentSourceValue, urlValue, sourceId, baseLine };
221     int argumentsSize = sizeof(arguments)/sizeof(arguments[0]);
222
223     DebuggerDocument::callGlobalFunction(context, "didParseScript", argumentsSize, arguments);
224 }
225
226 void DebuggerDocument::willExecuteStatement(JSContextRef context, JSValueRef sourceId, JSValueRef lineno, JSValueRef* exception)
227 {
228     JSValueRef arguments[] = { sourceId, lineno };
229     int argumentsSize = sizeof(arguments)/sizeof(arguments[0]);
230
231     DebuggerDocument::callGlobalFunction(context, "willExecuteStatement", argumentsSize, arguments, exception);
232     if (*exception)
233         logException(context, *exception);
234 }
235
236 void DebuggerDocument::didEnterCallFrame(JSContextRef context, JSValueRef sourceId, JSValueRef lineno, JSValueRef* exception)
237 {
238     JSValueRef arguments[] = { sourceId, lineno };
239     int argumentsSize = sizeof(arguments)/sizeof(arguments[0]);
240
241     DebuggerDocument::callGlobalFunction(context, "didEnterCallFrame", argumentsSize, arguments, exception);
242     if (*exception)
243         logException(context, *exception);
244 }
245
246 void DebuggerDocument::willLeaveCallFrame(JSContextRef context, JSValueRef sourceId, JSValueRef lineno, JSValueRef* exception)
247 {
248     JSValueRef arguments[] = { sourceId, lineno };
249     int argumentsSize = sizeof(arguments)/sizeof(arguments[0]);
250
251     DebuggerDocument::callGlobalFunction(context, "willLeaveCallFrame", argumentsSize, arguments, exception);
252     if (*exception)
253         logException(context, *exception);
254 }
255
256 void DebuggerDocument::exceptionWasRaised(JSContextRef context, JSValueRef sourceId, JSValueRef lineno, JSValueRef* exception)
257 {
258     JSValueRef arguments[] = { sourceId, lineno };
259     int argumentsSize = sizeof(arguments)/sizeof(arguments[0]);
260
261     DebuggerDocument::callGlobalFunction(context, "exceptionWasRaised", argumentsSize, arguments, exception);
262 }
263
264 void DebuggerDocument::windowScriptObjectAvailable(JSContextRef context, JSObjectRef windowObject, JSValueRef* exception)
265 {
266     JSRetainPtr<JSStringRef> droseraStr(Adopt, JSStringCreateWithUTF8CString("DebuggerDocument"));
267     JSValueRef droseraObject = JSObjectMake(context, getDroseraJSClass(), this);
268
269     JSObjectSetProperty(context, windowObject, droseraStr.get(), droseraObject, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete, exception);
270     if (*exception)
271         logException(context, *exception);
272 }
273
274 JSValueRef DebuggerDocument::toJSArray(JSContextRef context, Vector<JSValueRef>& vectorValues, JSValueRef* exception)
275 {
276     JSObjectRef globalObject = JSContextGetGlobalObject(context);
277     JSRetainPtr<JSStringRef> constructorString(Adopt, JSStringCreateWithUTF8CString("Array"));
278     JSValueRef constructorProperty = JSObjectGetProperty(context, globalObject, constructorString.get(), exception);
279     ASSERT(!*exception);
280
281     JSObjectRef arrayConstructor = JSValueToObject(context, constructorProperty, exception);
282     ASSERT(!*exception);
283
284     JSObjectRef array = JSObjectCallAsConstructor(context, arrayConstructor, 0, 0, exception);
285     ASSERT(!*exception);
286
287     JSRetainPtr<JSStringRef> pushString(Adopt, JSStringCreateWithUTF8CString("push"));
288     JSValueRef pushValue = JSObjectGetProperty(context, array, pushString.get(), exception);
289     ASSERT(!*exception);
290
291     JSObjectRef push = JSValueToObject(context, pushValue, exception);
292     ASSERT(!*exception);
293
294     for (Vector<JSValueRef>::iterator it = vectorValues.begin(); it != vectorValues.end(); ++it) {
295         JSObjectCallAsFunction(context, push, array, 1, it, exception);
296         ASSERT(!*exception);
297     }
298     
299     return array;
300 }
301
302 // Private
303 JSValueRef DebuggerDocument::callGlobalFunction(JSContextRef context, const char* functionName, int argumentCount, JSValueRef arguments[], JSValueRef* exception)
304 {
305     JSObjectRef globalObject = JSContextGetGlobalObject(context);
306     return callFunctionOnObject(context, globalObject, functionName, argumentCount, arguments, exception);
307 }
308
309 JSValueRef DebuggerDocument::callFunctionOnObject(JSContextRef context, JSObjectRef object, const char* functionName, int argumentCount, JSValueRef arguments[], JSValueRef* exception)
310 {
311     JSRetainPtr<JSStringRef> string(Adopt, JSStringCreateWithUTF8CString(functionName));
312     JSValueRef objectProperty = JSObjectGetProperty(context, object, string.get(), exception);
313
314     JSObjectRef function = JSValueToObject(context, objectProperty, exception);
315     ASSERT(JSObjectIsFunction(context, function));
316  
317     JSValueRef returnValue = JSObjectCallAsFunction(context, function, 0, argumentCount, arguments, exception);
318     if (*exception)
319         logException(context, *exception);
320
321     return returnValue;
322 }
323
324 JSClassRef DebuggerDocument::getDroseraJSClass()
325 {
326     static JSClassRef droseraClass = 0;
327
328     if (!droseraClass) {
329         JSClassDefinition classDefinition = {0};
330         classDefinition.staticFunctions = DebuggerDocument::staticFunctions();
331
332         droseraClass = JSClassCreate(&classDefinition);
333     }
334
335     return droseraClass;
336 }
337
338 JSStaticFunction* DebuggerDocument::staticFunctions()
339 {
340     static JSStaticFunction staticFunctions[] = {
341         { "breakpointEditorHTML", DebuggerDocument::breakpointEditorHTMLCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
342         { "currentFunctionStack", DebuggerDocument::currentFunctionStackCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
343         { "evaluateScript", DebuggerDocument::evaluateScriptCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
344         { "localScopeVariableNamesForCallFrame", DebuggerDocument::localScopeVariableNamesForCallFrameCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
345         { "pause", DebuggerDocument::pauseCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
346         { "resume", DebuggerDocument::resumeCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
347         { "stepInto", DebuggerDocument::stepIntoCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
348         { "valueForScopeVariableNamed", DebuggerDocument::valueForScopeVariableNamedCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
349         { "log", DebuggerDocument::logCallback, kJSPropertyAttributeReadOnly | kJSPropertyAttributeDontDelete },
350         { 0, 0, 0 }
351     };
352
353     return staticFunctions;
354 }
355
356 void DebuggerDocument::logException(JSContextRef context, JSValueRef exception)
357 {
358     if (!exception)
359         return;
360     
361     JSRetainPtr<JSStringRef> msg(Adopt, JSValueToStringCopy(context, exception, 0));
362     DebuggerDocument::platformLog(msg.get());
363 }
364