Move WebCoreScriptDebugger up to WebKit
[WebKit-https.git] / WebKit / mac / WebView / WebCoreScriptDebugger.mm
1 /*
2  * Copyright (C) 2005, 2008 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 // FIXME: This file and the classes in it should have the prefix "Web" instead
30 // of "WebCore". The best way to fix this is to merge WebCoreScriptCallFrame
31 // with WebScriptCallFrame and WebCoreScriptDebugger with WebScriptDebugger.
32
33 #import "WebCoreScriptDebugger.h"
34
35 #import <JavaScriptCore/ExecState.h>
36 #import <JavaScriptCore/JSGlobalObject.h>
37 #import <JavaScriptCore/debugger.h>
38 #import <JavaScriptCore/function.h>
39 #import <JavaScriptCore/interpreter.h>
40 #import <WebCore/KURL.h>
41 #import <WebCore/PlatformString.h>
42 #import <WebCore/WebCoreObjCExtras.h>
43 #import <WebCore/WebScriptObjectPrivate.h>
44 #import <WebCore/runtime_root.h>
45
46 using namespace KJS;
47 using namespace WebCore;
48
49 @interface WebCoreScriptDebugger (WebCoreScriptDebuggerInternal)
50
51 - (WebCoreScriptCallFrame *)_enterFrame:(ExecState *)state;
52 - (WebCoreScriptCallFrame *)_leaveFrame;
53
54 @end
55
56 @interface WebCoreScriptCallFrame (WebCoreScriptDebuggerInternal)
57
58 - (WebCoreScriptCallFrame *)_initWithGlobalObject:(WebScriptObject *)globalObj caller:(WebCoreScriptCallFrame *)caller state:(ExecState *)state;
59 - (void)_setWrapper:(id)wrapper;
60 - (id)_convertValueToObjcValue:(JSValue *)value;
61
62 @end
63
64 // convert UString to NSString
65 static NSString *toNSString(const UString& s)
66 {
67     if (s.isEmpty())
68         return nil;
69     return [NSString stringWithCharacters:reinterpret_cast<const unichar*>(s.data()) length:s.size()];
70 }
71
72 // convert UString to NSURL
73 static NSURL *toNSURL(const UString& s)
74 {
75     if (s.isEmpty())
76         return nil;
77     return KURL(s);
78 }
79
80 // C++ interface to KJS debugger callbacks
81
82 class WebCoreScriptDebuggerImp : public KJS::Debugger {
83
84   private:
85     WebCoreScriptDebugger  *_objc;      // our ObjC half
86     bool                    _nested;    // true => this is a nested call
87     WebCoreScriptCallFrame *_current;   // top stack frame (copy of same field from ObjC side)
88
89   public:
90     // constructor
91     WebCoreScriptDebuggerImp(WebCoreScriptDebugger *objc, JSGlobalObject* globalObject) : _objc(objc) {
92         _nested = true;
93         _current = [_objc _enterFrame:globalObject->globalExec()];
94         attach(globalObject);
95         [[_objc delegate] enteredFrame:_current sourceId:-1 line:-1];
96         _nested = false;
97     }
98
99     // callbacks - relay to delegate
100     virtual bool sourceParsed(ExecState *state, int sid, const UString &url, const UString &source, int lineNumber, int errorLine, const UString &errorMsg) {
101         if (!_nested) {
102             _nested = true;
103             [[_objc delegate] parsedSource:toNSString(source) fromURL:toNSURL(url) sourceId:sid startLine:lineNumber errorLine:errorLine errorMessage:toNSString(errorMsg)];
104             _nested = false;
105         }
106         return true;
107     }
108     virtual bool callEvent(ExecState *state, int sid, int lineno, JSObject *func, const List &args) {
109         if (!_nested) {
110             _nested = true;
111             _current = [_objc _enterFrame:state];
112             [[_objc delegate] enteredFrame:_current sourceId:sid line:lineno];
113             _nested = false;
114         }
115         return true;
116     }
117     virtual bool atStatement(ExecState *state, int sid, int lineno, int lastLine) {
118         if (!_nested) {
119             _nested = true;
120             [[_objc delegate] hitStatement:_current sourceId:sid line:lineno];
121             _nested = false;
122         }
123         return true;
124     }
125     virtual bool returnEvent(ExecState *state, int sid, int lineno, JSObject *func) {
126         if (!_nested) {
127             _nested = true;
128             [[_objc delegate] leavingFrame:_current sourceId:sid line:lineno];
129             _current = [_objc _leaveFrame];
130             _nested = false;
131         }
132         return true;
133     }
134     virtual bool exception(ExecState *state, int sid, int lineno, JSValue *exception) {
135         if (!_nested) {
136             _nested = true;
137             [[_objc delegate] exceptionRaised:_current sourceId:sid line:lineno];
138             _nested = false;
139         }
140         return true;
141     }
142
143 };
144
145 // WebCoreScriptDebugger
146 //
147 // This is the main (behind-the-scenes) debugger class in WebCore.
148 //
149 // The WebCoreScriptDebugger has two faces, one for Objective-C (this class), and another (WebCoreScriptDebuggerImp)
150 // for C++.  The ObjC side creates the C++ side, which does the real work of attaching to the global object and
151 // forwarding the KJS debugger callbacks to the delegate.
152
153 @implementation WebCoreScriptDebugger
154
155 #ifndef BUILDING_ON_TIGER
156 + (void)initialize
157 {
158     WebCoreObjCFinalizeOnMainThread(self);
159 }
160 #endif
161
162 - (WebCoreScriptDebugger *)initWithDelegate:(id<WebScriptDebugger>)delegate
163 {
164     if ((self = [super init])) {
165         _delegate  = delegate;
166         _globalObj = [_delegate globalObject];
167         _debugger  = new WebCoreScriptDebuggerImp(self, [_globalObj _rootObject]->globalObject());
168     }
169     return self;
170 }
171
172 - (void)dealloc
173 {
174     [_current release];
175     delete _debugger;
176     [super dealloc];
177 }
178
179 - (void)finalize
180 {
181     delete _debugger;
182     [super finalize];
183 }
184
185 - (id<WebScriptDebugger>)delegate
186 {
187     return _delegate;
188 }
189
190 @end
191
192 @implementation WebCoreScriptDebugger (WebCoreScriptDebuggerInternal)
193
194 - (WebCoreScriptCallFrame *)_enterFrame:(ExecState *)state;
195 {
196     WebCoreScriptCallFrame *callee = [[WebCoreScriptCallFrame alloc] _initWithGlobalObject:_globalObj caller:_current state:state];
197     [callee _setWrapper:[_delegate newWrapperForFrame:callee]];
198     return _current = callee;
199 }
200
201 - (WebCoreScriptCallFrame *)_leaveFrame;
202 {
203     WebCoreScriptCallFrame *caller = [[_current caller] retain];
204     [_current release];
205     return _current = caller;
206 }
207
208 @end
209
210 // WebCoreScriptCallFrame
211 //
212 // One of these is created to represent each stack frame.  Additionally, there is a "global"
213 // frame to represent the outermost scope.  This global frame is always the last frame in
214 // the chain of callers.
215 //
216 // The delegate can assign a "wrapper" to each frame object so it can relay calls through its
217 // own exported interface.  This class is private to WebCore (and the delegate).
218
219 @implementation WebCoreScriptCallFrame (WebCoreScriptDebuggerInternal)
220
221 - (WebCoreScriptCallFrame *)_initWithGlobalObject:(WebScriptObject *)globalObj caller:(WebCoreScriptCallFrame *)caller state:(ExecState *)state
222 {
223     if ((self = [super init])) {
224         _globalObj = globalObj;
225         _caller    = caller;    // (already retained)
226         _state     = state;
227     }
228     return self;
229 }
230
231 - (void)_setWrapper:(id)wrapper
232 {
233     _wrapper = wrapper;     // (already retained)
234 }
235
236 - (id)_convertValueToObjcValue:(JSValue *)value
237 {
238     if (!value)
239         return nil;
240
241     if (value == [_globalObj _imp])
242         return _globalObj;
243
244     Bindings::RootObject* root1 = [_globalObj _originRootObject];
245     if (!root1)
246         return nil;
247
248     Bindings::RootObject* root2 = [_globalObj _rootObject];
249     if (!root2)
250         return nil;
251
252     return [WebScriptObject _convertValueToObjcValue:value originRootObject:root1 rootObject:root2];
253 }
254
255 @end
256
257 @implementation WebCoreScriptCallFrame
258
259 - (void)dealloc
260 {
261     [_wrapper release];
262     [_caller release];
263     [super dealloc];
264 }
265
266 - (id)wrapper
267 {
268     return _wrapper;
269 }
270
271 - (WebCoreScriptCallFrame *)caller
272 {
273     return _caller;
274 }
275
276 // Returns an array of scope objects (most local first).
277 // The properties of each scope object are the variables for that scope.
278 // Note that the last entry in the array will _always_ be the global object (windowScriptObject),
279 // whose properties are the global variables.
280
281 - (NSArray *)scopeChain
282 {
283     if (!_state->scopeNode()) {  // global frame
284         return [NSArray arrayWithObject:_globalObj];
285     }
286
287     ScopeChain      chain  = _state->scopeChain();
288     NSMutableArray *scopes = [[NSMutableArray alloc] init];
289
290     while (!chain.isEmpty()) {
291         [scopes addObject:[self _convertValueToObjcValue:chain.top()]];
292         chain.pop();
293     }
294
295     NSArray *result = [NSArray arrayWithArray:scopes];
296     [scopes release];
297     return result;
298 }
299
300 // Returns the name of the function for this frame, if available.
301 // Returns nil for anonymous functions and for the global frame.
302
303 - (NSString *)functionName
304 {
305     if (_state->scopeNode()) {
306         FunctionImp* func = _state->function();
307         if (func) {
308             Identifier fn = func->functionName();
309             return toNSString(fn.ustring());
310         }
311     }
312     return nil;
313 }
314
315 // Returns the pending exception for this frame (nil if none).
316
317 - (id)exception
318 {
319     if (!_state->hadException()) return nil;
320     return [self _convertValueToObjcValue:_state->exception()];
321 }
322
323 // Evaluate some JavaScript code in the context of this frame.
324 // The code is evaluated as if by "eval", and the result is returned.
325 // If there is an (uncaught) exception, it is returned as though _it_ were the result.
326 // Calling this method on the global frame is not quite the same as calling the WebScriptObject
327 // method of the same name, due to the treatment of exceptions.
328
329 // FIXME: If "script" contains var declarations, the machinery to handle local variables
330 // efficiently in JavaScriptCore will not work properly. This could lead to crashes or
331 // incorrect variable values. So this is not appropriate for evaluating arbitrary script.
332 - (id)evaluateWebScript:(NSString *)script
333 {
334     JSLock lock;
335
336     UString code = String(script);
337
338     ExecState* state = _state;
339     JSGlobalObject* globalObject = state->dynamicGlobalObject();
340
341     // find "eval"
342     JSObject *eval = NULL;
343     if (state->scopeNode()) {  // "eval" won't work without context (i.e. at global scope)
344         JSValue *v = globalObject->get(state, "eval");
345         if (v->isObject() && static_cast<JSObject *>(v)->implementsCall())
346             eval = static_cast<JSObject *>(v);
347         else
348             // no "eval" - fallback operates on global exec state
349             state = globalObject->globalExec();
350     }
351
352     JSValue *savedException = state->exception();
353     state->clearException();
354
355     // evaluate
356     JSValue *result;
357     if (eval) {
358         List args;
359         args.append(jsString(code));
360         result = eval->call(state, NULL, args);
361     } else
362         // no "eval", or no context (i.e. global scope) - use global fallback
363         result = Interpreter::evaluate(state, UString(), 0, code.data(), code.size(), globalObject).value();
364
365     if (state->hadException())
366         result = state->exception();    // (may be redundant depending on which eval path was used)
367     state->setException(savedException);
368
369     return [self _convertValueToObjcValue:result];
370 }
371
372 @end