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