org.htmlunit.javascript.DebugFrameImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xlt Show documentation
Show all versions of xlt Show documentation
XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.
/*
* Copyright (c) 2002-2024 Gargoyle Software Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.htmlunit.javascript;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.htmlunit.corejs.javascript.Context;
import org.htmlunit.corejs.javascript.EcmaError;
import org.htmlunit.corejs.javascript.Function;
import org.htmlunit.corejs.javascript.IdFunctionObject;
import org.htmlunit.corejs.javascript.JavaScriptException;
import org.htmlunit.corejs.javascript.NativeFunction;
import org.htmlunit.corejs.javascript.Scriptable;
import org.htmlunit.corejs.javascript.ScriptableObject;
import org.htmlunit.corejs.javascript.debug.DebuggableScript;
import org.htmlunit.javascript.host.event.Event;
/**
*
* HtmlUnit's implementation of the {@link org.htmlunit.corejs.javascript.debug.DebugFrame} interface,
* which logs stack entries as well as exceptions. All logging is done at the TRACE
level. This class does
* a fairly good job of guessing names for anonymous functions when they are referenced by name from an existing
* object. See the Rhino documentation or
* the
* interface source code for more information on the
* {@link org.htmlunit.corejs.javascript.debug.DebugFrame} interface and its uses.
*
*
*
* Please note that this class is intended mainly to aid in the debugging and development of
* HtmlUnit itself, rather than the debugging and development of web applications.
*
*
* @author Daniel Gredler
* @author Marc Guillemot
* @see DebuggerImpl
*/
public class DebugFrameImpl extends DebugFrameAdapter {
private static final Log LOG = LogFactory.getLog(DebugFrameImpl.class);
private static final String KEY_LAST_LINE = "DebugFrameImpl#line";
private static final String KEY_LAST_SOURCE = "DebugFrameImpl#source";
private final DebuggableScript functionOrScript_;
/**
* Creates a new debug frame.
*
* @param functionOrScript the function or script to which this frame corresponds
*/
public DebugFrameImpl(final DebuggableScript functionOrScript) {
functionOrScript_ = functionOrScript;
}
/**
* {@inheritDoc}
*/
@Override
public void onEnter(final Context cx, final Scriptable activation, final Scriptable thisObj, final Object[] args) {
if (LOG.isTraceEnabled()) {
final StringBuilder sb = new StringBuilder();
final String line = getFirstLine(cx);
final String source = getSourceName(cx);
sb.append(source).append(':').append(line).append(' ');
Scriptable parent = activation.getParentScope();
while (parent != null) {
sb.append(" ");
parent = parent.getParentScope();
}
final String functionName = getFunctionName(thisObj);
sb.append(functionName).append('(');
final int nbParams = functionOrScript_.getParamCount();
for (int i = 0; i < nbParams; i++) {
final String argAsString;
if (i < args.length) {
argAsString = stringValue(args[i]);
}
else {
argAsString = "undefined";
}
sb.append(getParamName(i)).append(": ").append(argAsString);
if (i < nbParams - 1) {
sb.append(", ");
}
}
sb.append(')');
LOG.trace(sb);
}
}
private static String stringValue(final Object arg) {
if (arg instanceof NativeFunction) {
// Don't return the string value of the function, because it's usually
// multiple lines of content and messes up the log.
final String name = StringUtils.defaultIfEmpty(((NativeFunction) arg).getFunctionName(), "anonymous");
return "[function " + name + "]";
}
else if (arg instanceof IdFunctionObject) {
return "[function " + ((IdFunctionObject) arg).getFunctionName() + "]";
}
else if (arg instanceof Function) {
return "[function anonymous]";
}
String asString;
try {
// try to get the js representation
asString = JavaScriptEngine.toString(arg);
if (arg instanceof Event) {
asString += "<" + ((Event) arg).getType() + ">";
}
}
catch (final Throwable e) {
// seems to be a bug (many bugs) in rhino (TODO: investigate it)
asString = String.valueOf(arg);
}
return asString;
}
/**
* {@inheritDoc}
*/
@Override
public void onExceptionThrown(final Context cx, final Throwable t) {
if (LOG.isTraceEnabled()) {
if (t instanceof JavaScriptException) {
final JavaScriptException e = (JavaScriptException) t;
if (LOG.isTraceEnabled()) {
LOG.trace(getSourceName(cx) + ":" + getFirstLine(cx)
+ " Exception thrown: " + JavaScriptEngine.toString(e.details()));
}
}
else if (t instanceof EcmaError) {
final EcmaError e = (EcmaError) t;
if (LOG.isTraceEnabled()) {
LOG.trace(getSourceName(cx) + ":" + getFirstLine(cx)
+ " Exception thrown: " + JavaScriptEngine.toString(e.details()));
}
}
else {
if (LOG.isTraceEnabled()) {
LOG.trace(getSourceName(cx) + ":" + getFirstLine(cx) + " Exception thrown: " + t.getCause());
}
}
}
}
/**
* {@inheritDoc}
*/
@Override
public void onLineChange(final Context cx, final int lineNumber) {
cx.putThreadLocal(KEY_LAST_LINE, lineNumber);
cx.putThreadLocal(KEY_LAST_SOURCE, functionOrScript_.getSourceName());
}
/**
* Returns the name of the function corresponding to this frame, if it is a function and it has
* a name. If the function does not have a name, this method will try to return the name under
* which it was referenced. See this page for a good
* explanation of how the thisObj
plays into this guess.
*
* @param thisObj the object via which the function was referenced, used to try to guess a
* function name if the function is anonymous
* @return the name of the function corresponding to this frame
*/
private String getFunctionName(final Scriptable thisObj) {
if (functionOrScript_.isFunction()) {
final String name = functionOrScript_.getFunctionName();
if (name != null && !name.isEmpty()) {
// A named function -- we can just return the name.
return name;
}
// An anonymous function -- try to figure out how it was referenced.
// For example, someone may have set foo.prototype.bar = function() { ... };
// And then called fooInstance.bar() -- in which case it's "named" bar.
// on our HtmlUnitScriptable we need to avoid looking at the properties we have defined => TODO: improve it
if (thisObj instanceof HtmlUnitScriptable) {
return "[anonymous]";
}
Scriptable obj = thisObj;
while (obj != null) {
for (final Object id : obj.getIds()) {
if (id instanceof String) {
final String s = (String) id;
if (obj instanceof ScriptableObject) {
Object o = ((ScriptableObject) obj).getGetterOrSetter(s, 0, thisObj, false);
if (o == null) {
o = ((ScriptableObject) obj).getGetterOrSetter(s, 0, thisObj, true);
if (o != null) {
return "__defineSetter__ " + s;
}
}
else {
return "__defineGetter__ " + s;
}
}
final Object o;
try {
// within a try block as this sometimes throws (not sure why)
o = obj.get(s, obj);
}
catch (final Exception e) {
return "[anonymous]";
}
if (o instanceof NativeFunction) {
final NativeFunction f = (NativeFunction) o;
if (f.getDebuggableView() == functionOrScript_) {
return s;
}
}
}
}
obj = obj.getPrototype();
}
// Unable to intuit a name -- doh!
return "[anonymous]";
}
// Just a script -- no function name.
return "[script]";
}
/**
* Returns the name of the parameter at the specified index, or ???
if there is no
* corresponding name.
*
* @param index the index of the parameter whose name is to be returned
* @return the name of the parameter at the specified index, or ???
if there is no corresponding name
*/
private String getParamName(final int index) {
if (index >= 0 && functionOrScript_.getParamCount() > index) {
return functionOrScript_.getParamOrVarName(index);
}
return "???";
}
/**
* Returns the name of this frame's source.
*
* @return the name of this frame's source
*/
private static String getSourceName(final Context cx) {
String source = (String) cx.getThreadLocal(KEY_LAST_SOURCE);
if (source == null) {
return "unknown";
}
// only the file name is interesting the rest of the url is mostly noise
source = StringUtils.substringAfterLast(source, "/");
// embedded scripts have something like "foo.html from (3, 10) to (10, 13)"
source = StringUtils.substringBefore(source, " ");
return source;
}
/**
* Returns the line number of the first line in this frame's function or script, or ???
* if it cannot be determined. This is necessary because the line numbers provided by Rhino are unordered.
*
* @return the line number of the first line in this frame's function or script, or ???
* if it cannot be determined
*/
private static String getFirstLine(final Context cx) {
final Object line = cx.getThreadLocal(KEY_LAST_LINE);
final String result;
if (line == null) {
result = "??";
}
else {
result = String.valueOf(line);
}
return StringUtils.leftPad(result, 5);
}
}