All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gargoylesoftware.htmlunit.javascript.DebugFrameImpl Maven / Gradle / Ivy

There is a newer version: 2.70.0
Show newest version
/*
 * 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