1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript;
16
17 import org.apache.commons.lang3.StringUtils;
18 import org.apache.commons.logging.Log;
19 import org.apache.commons.logging.LogFactory;
20 import org.htmlunit.corejs.javascript.Context;
21 import org.htmlunit.corejs.javascript.EcmaError;
22 import org.htmlunit.corejs.javascript.Function;
23 import org.htmlunit.corejs.javascript.IdFunctionObject;
24 import org.htmlunit.corejs.javascript.JavaScriptException;
25 import org.htmlunit.corejs.javascript.NativeFunction;
26 import org.htmlunit.corejs.javascript.Scriptable;
27 import org.htmlunit.corejs.javascript.ScriptableObject;
28 import org.htmlunit.corejs.javascript.debug.DebuggableScript;
29 import org.htmlunit.javascript.host.event.Event;
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53 public class DebugFrameImpl extends DebugFrameAdapter {
54
55 private static final Log LOG = LogFactory.getLog(DebugFrameImpl.class);
56
57 private static final String KEY_LAST_LINE = "DebugFrameImpl#line";
58 private static final String KEY_LAST_SOURCE = "DebugFrameImpl#source";
59
60 private final DebuggableScript functionOrScript_;
61
62
63
64
65
66
67 public DebugFrameImpl(final DebuggableScript functionOrScript) {
68 super();
69 functionOrScript_ = functionOrScript;
70 }
71
72
73
74
75 @Override
76 public void onEnter(final Context cx, final Scriptable activation, final Scriptable thisObj, final Object[] args) {
77 if (LOG.isTraceEnabled()) {
78 final StringBuilder sb = new StringBuilder();
79
80 final String line = getFirstLine(cx);
81 final String source = getSourceName(cx);
82 sb.append(source).append(':').append(line).append(' ');
83
84 Scriptable parent = activation.getParentScope();
85 while (parent != null) {
86 sb.append(" ");
87 parent = parent.getParentScope();
88 }
89 final String functionName = getFunctionName(thisObj);
90 sb.append(functionName).append('(');
91 final int nbParams = functionOrScript_.getParamCount();
92 for (int i = 0; i < nbParams; i++) {
93 final String argAsString;
94 if (i < args.length) {
95 argAsString = stringValue(args[i]);
96 }
97 else {
98 argAsString = "undefined";
99 }
100 sb.append(getParamName(i)).append(": ").append(argAsString);
101 if (i < nbParams - 1) {
102 sb.append(", ");
103 }
104 }
105 sb.append(')');
106
107 LOG.trace(sb);
108 }
109 }
110
111 private static String stringValue(final Object arg) {
112 if (arg instanceof NativeFunction) {
113
114
115 final String name = StringUtils.defaultIfEmpty(((NativeFunction) arg).getFunctionName(), "anonymous");
116 return "[function " + name + "]";
117 }
118 else if (arg instanceof IdFunctionObject) {
119 return "[function " + ((IdFunctionObject) arg).getFunctionName() + "]";
120 }
121 else if (arg instanceof Function) {
122 return "[function anonymous]";
123 }
124 String asString;
125 try {
126
127 asString = JavaScriptEngine.toString(arg);
128 if (arg instanceof Event) {
129 asString += "<" + ((Event) arg).getType() + ">";
130 }
131 }
132 catch (final Throwable e) {
133
134 asString = String.valueOf(arg);
135 }
136 return asString;
137 }
138
139
140
141
142 @Override
143 public void onExceptionThrown(final Context cx, final Throwable t) {
144 if (LOG.isTraceEnabled()) {
145 if (t instanceof JavaScriptException) {
146 final JavaScriptException e = (JavaScriptException) t;
147 LOG.trace(getSourceName(cx) + ":" + getFirstLine(cx)
148 + " Exception thrown: " + e.details());
149 }
150 else if (t instanceof EcmaError) {
151 final EcmaError e = (EcmaError) t;
152 LOG.trace(getSourceName(cx) + ":" + getFirstLine(cx)
153 + " Exception thrown: " + e.details());
154 }
155 else {
156 LOG.trace(getSourceName(cx) + ":" + getFirstLine(cx) + " Exception thrown: " + t.getCause());
157 }
158 }
159 }
160
161
162
163
164 @Override
165 public void onLineChange(final Context cx, final int lineNumber) {
166 cx.putThreadLocal(KEY_LAST_LINE, lineNumber);
167 cx.putThreadLocal(KEY_LAST_SOURCE, functionOrScript_.getSourceName());
168 }
169
170
171
172
173
174
175
176
177
178
179
180
181 private String getFunctionName(final Scriptable thisObj) {
182 if (functionOrScript_.isFunction()) {
183 final String name = functionOrScript_.getFunctionName();
184 if (name != null && !name.isEmpty()) {
185
186 return name;
187 }
188
189
190
191
192
193 if (thisObj instanceof HtmlUnitScriptable) {
194 return "[anonymous]";
195 }
196
197 Scriptable obj = thisObj;
198 while (obj != null) {
199 for (final Object id : obj.getIds()) {
200 if (id instanceof String) {
201 final String s = (String) id;
202 if (obj instanceof ScriptableObject) {
203 Object o = ((ScriptableObject) obj).getGetterOrSetter(s, 0, thisObj, false);
204 if (o == null) {
205 o = ((ScriptableObject) obj).getGetterOrSetter(s, 0, thisObj, true);
206 if (o != null) {
207 return "__defineSetter__ " + s;
208 }
209 }
210 else {
211 return "__defineGetter__ " + s;
212 }
213 }
214 final Object o;
215 try {
216
217 o = obj.get(s, obj);
218 }
219 catch (final Exception e) {
220 return "[anonymous]";
221 }
222 if (o instanceof NativeFunction) {
223 final NativeFunction f = (NativeFunction) o;
224 if (f.getDebuggableView() == functionOrScript_) {
225 return s;
226 }
227 }
228 }
229 }
230 obj = obj.getPrototype();
231 }
232
233 return "[anonymous]";
234 }
235
236 return "[script]";
237 }
238
239
240
241
242
243
244
245
246 private String getParamName(final int index) {
247 if (index >= 0 && functionOrScript_.getParamCount() > index) {
248 return functionOrScript_.getParamOrVarName(index);
249 }
250 return "???";
251 }
252
253
254
255
256
257
258 private static String getSourceName(final Context cx) {
259 String source = (String) cx.getThreadLocal(KEY_LAST_SOURCE);
260 if (source == null) {
261 return "unknown";
262 }
263
264 source = StringUtils.substringAfterLast(source, "/");
265
266 source = StringUtils.substringBefore(source, " ");
267 return source;
268 }
269
270
271
272
273
274
275
276
277 private static String getFirstLine(final Context cx) {
278 final Object line = cx.getThreadLocal(KEY_LAST_LINE);
279 final String result;
280 if (line == null) {
281 result = "??";
282 }
283 else {
284 result = String.valueOf(line);
285 }
286 return StringUtils.leftPad(result, 5);
287 }
288 }