com.gargoylesoftware.htmlunit.javascript.DebugFrameImpl Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of htmlunit Show documentation
Show all versions of htmlunit Show documentation
A headless browser intended for use in testing web-based applications.
/*
* Copyright (c) 2002-2021 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 com.gargoylesoftware.htmlunit.javascript;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.gargoylesoftware.htmlunit.javascript.host.event.Event;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.EcmaError;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.IdFunctionObject;
import net.sourceforge.htmlunit.corejs.javascript.JavaScriptException;
import net.sourceforge.htmlunit.corejs.javascript.NativeFunction;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
import net.sourceforge.htmlunit.corejs.javascript.debug.DebuggableScript;
/**
*
* HtmlUnit's implementation of the {@link net.sourceforge.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 net.sourceforge.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 = null;
try {
// try to get the js representation
asString = Context.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: " + Context.toString(e.details()));
}
}
else if (t instanceof EcmaError) {
final EcmaError e = (EcmaError) t;
if (LOG.isTraceEnabled()) {
LOG.trace(getSourceName(cx) + ":" + getFirstLine(cx)
+ " Exception thrown: " + Context.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 SimpleScriptable we need to avoid looking at the properties we have defined => TODO: improve it
if (thisObj instanceof SimpleScriptable) {
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) {
Function f = ((ScriptableObject) obj).getGetterOrSetter(s, 0, thisObj, false);
if (f == null) {
f = ((ScriptableObject) obj).getGetterOrSetter(s, 0, thisObj, true);
if (f != 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);
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy