com.gargoylesoftware.htmlunit.javascript.host.Console 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.host;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_CONSOLE_HANDLE_WINDOW;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF78;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.IE;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import com.gargoylesoftware.htmlunit.WebConsole;
import com.gargoylesoftware.htmlunit.WebConsole.Formatter;
import com.gargoylesoftware.htmlunit.WebWindow;
import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
import net.sourceforge.htmlunit.corejs.javascript.BaseFunction;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.Delegator;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.NativeArray;
import net.sourceforge.htmlunit.corejs.javascript.NativeFunction;
import net.sourceforge.htmlunit.corejs.javascript.NativeObject;
import net.sourceforge.htmlunit.corejs.javascript.RhinoException;
import net.sourceforge.htmlunit.corejs.javascript.ScriptRuntime;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
/**
* A JavaScript object for {@code Console}.
*
* @author Andrea Martino
* @author Ronald Brill
*/
@JsxClass(isJSObject = false, value = {CHROME, EDGE, FF, FF78})
@JsxClass(IE)
public class Console extends SimpleScriptable {
private static final Map TIMERS = new HashMap<>();
private static Formatter FORMATTER_ = new ConsoleFormatter();
private WebWindow webWindow_;
/**
* Default constructor.
*/
public Console() {
}
/**
* Sets the Window JavaScript object this console belongs to.
* @param webWindow the Window JavaScript object this console belongs to
*/
public void setWebWindow(final WebWindow webWindow) {
webWindow_ = webWindow;
}
/**
* This method writes an error message to the console if the
* assertion is false. If the assertion is true, nothing happens.
* @param cx the JavaScript context
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param funObj the function
*/
@JsxFunction(functionName = "assert")
public static void assertMethod(final Context cx, final Scriptable thisObj,
final Object[] args, final Function funObj) {
if (args.length < 1 || ScriptRuntime.toBoolean(args[0])) {
return;
}
if (args.length == 1) {
log(cx, thisObj, new String[] {"Assertion failed"}, null);
return;
}
final Object[] data;
final Object first = args[1];
if (first instanceof CharSequence
|| first instanceof ScriptableObject && ("String".equals(((Scriptable) first).getClassName()))) {
data = new Object[args.length - 1];
data[0] = "Assertion failed: " + first.toString();
System.arraycopy(args, 2, data, 1, data.length - 1);
}
else {
data = new Object[args.length];
data[0] = "Assertion failed:";
System.arraycopy(args, 1, data, 1, data.length - 1);
}
log(cx, thisObj, data, null);
}
/**
* This method performs logging to the console at {@code log} level.
* @param cx the JavaScript context
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param funObj the function
*/
@JsxFunction
public static void log(final Context cx, final Scriptable thisObj,
final Object[] args, final Function funObj) {
final WebConsole webConsole = toWebConsole(thisObj);
final Formatter oldFormatter = webConsole.getFormatter();
webConsole.setFormatter(FORMATTER_);
webConsole.info(args);
webConsole.setFormatter(oldFormatter);
}
private static WebConsole toWebConsole(Scriptable thisObj) {
if (thisObj instanceof Window
&& ((SimpleScriptable) thisObj).getDomNodeOrDie().hasFeature(JS_CONSOLE_HANDLE_WINDOW)) {
thisObj = ((Window) thisObj).getConsole();
}
if (thisObj instanceof Console) {
return ((Console) thisObj).getWebConsole();
}
throw ScriptRuntime.typeError("object does not implemennt interface Console");
}
/**
* This method performs logging to the console at {@code info} level.
* @param cx the JavaScript context
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param funObj the function
*/
@JsxFunction
public static void info(final Context cx, final Scriptable thisObj,
final Object[] args, final Function funObj) {
final WebConsole webConsole = toWebConsole(thisObj);
final Formatter oldFormatter = webConsole.getFormatter();
webConsole.setFormatter(FORMATTER_);
webConsole.info(args);
webConsole.setFormatter(oldFormatter);
}
/**
* This method performs logging to the console at {@code warn} level.
* @param cx the JavaScript context
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param funObj the function
*/
@JsxFunction
public static void warn(final Context cx, final Scriptable thisObj,
final Object[] args, final Function funObj) {
final WebConsole webConsole = toWebConsole(thisObj);
final Formatter oldFormatter = webConsole.getFormatter();
webConsole.setFormatter(FORMATTER_);
webConsole.warn(args);
webConsole.setFormatter(oldFormatter);
}
/**
* This method performs logging to the console at {@code error} level.
* @param cx the JavaScript context
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param funObj the function
*/
@JsxFunction
public static void error(final Context cx, final Scriptable thisObj,
final Object[] args, final Function funObj) {
final WebConsole webConsole = toWebConsole(thisObj);
final Formatter oldFormatter = webConsole.getFormatter();
webConsole.setFormatter(FORMATTER_);
webConsole.error(args);
webConsole.setFormatter(oldFormatter);
}
/**
* This method performs logging to the console at {@code debug} level.
* @param cx the JavaScript context
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param funObj the function
*/
@JsxFunction
public static void debug(final Context cx, final Scriptable thisObj,
final Object[] args, final Function funObj) {
final WebConsole webConsole = toWebConsole(thisObj);
final Formatter oldFormatter = webConsole.getFormatter();
webConsole.setFormatter(FORMATTER_);
webConsole.debug(args);
webConsole.setFormatter(oldFormatter);
}
/**
* This method performs logging to the console at {@code trace} level.
* @param cx the JavaScript context
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param funObj the function
*/
@JsxFunction
public static void trace(final Context cx, final Scriptable thisObj,
final Object[] args, final Function funObj) {
final RhinoException e = ScriptRuntime.throwError(cx, funObj, null);
final WebConsole webConsole = toWebConsole(thisObj);
final Formatter oldFormatter = webConsole.getFormatter();
webConsole.setFormatter(FORMATTER_);
webConsole.info(e.getScriptStackTrace());
webConsole.setFormatter(oldFormatter);
}
private WebConsole getWebConsole() {
return webWindow_.getWebClient().getWebConsole();
}
/**
* Implementation of console {@code dir} function. This method does not enter recursively
* in the passed object, nor prints the details of objects or functions.
* @param o the object to be printed
*/
@JsxFunction
public void dir(final Object o) {
if (o instanceof ScriptableObject) {
final ScriptableObject obj = (ScriptableObject) o;
final Object[] ids = obj.getIds();
if (ids != null && ids.length > 0) {
final StringBuilder sb = new StringBuilder();
for (final Object id : ids) {
final Object value = obj.get(id);
if (value instanceof Delegator) {
sb.append(id + ": " + ((Delegator) value).getClassName() + "\n");
}
else if (value instanceof SimpleScriptable) {
sb.append(id + ": " + ((SimpleScriptable) value).getClassName() + "\n");
}
else if (value instanceof BaseFunction) {
sb.append(id + ": function " + ((BaseFunction) value).getFunctionName() + "()\n");
}
else {
sb.append(id + ": " + value + "\n");
}
}
getWebConsole().info(sb.toString());
}
}
}
/**
* Implementation of group. Currently missing.
*/
@JsxFunction
public void group() {
// TODO not implemented
}
/**
* Implementation of endGroup. Currently missing.
*/
@JsxFunction
public void groupEnd() {
// TODO not implemented
}
/**
* Implementation of groupCollapsed. Currently missing.
*/
@JsxFunction
public void groupCollapsed() {
// TODO not implemented
}
/**
* This method replicates Firefox's behavior: if the timer already exists,
* the start time is not overwritten. In both cases, the line is printed on the
* console.
* @param timerName the name of the timer
*/
@JsxFunction
public void time(final String timerName) {
if (!TIMERS.containsKey(timerName)) {
TIMERS.put(timerName, Long.valueOf(System.currentTimeMillis()));
}
getWebConsole().info(timerName + ": timer started");
}
/**
* This method replicates Firefox's behavior: if no timer is found, nothing is
* logged to the console.
* @param timerName the name of the timer
*/
@JsxFunction
public void timeEnd(final String timerName) {
final Long startTime = TIMERS.remove(timerName);
if (startTime != null) {
getWebConsole().info(timerName + ": " + (System.currentTimeMillis() - startTime.longValue()) + "ms");
}
}
/**
* Because there is no timeline in HtmlUnit this does nothing.
* @param label the label
*/
@JsxFunction({CHROME, EDGE, FF, FF78})
public void timeStamp(final String label) {
}
/**
* This class is the default formatter used by Console.
*/
private static class ConsoleFormatter implements Formatter {
private static void appendNativeArray(final NativeArray a, final StringBuilder sb, final int level) {
sb.append('[');
if (level < 3) {
for (int i = 0; i < a.size(); i++) {
if (i > 0) {
sb.append(", ");
}
final Object val = a.get(i);
if (val != null) {
appendValue(val, sb, level + 1);
}
}
}
sb.append(']');
}
private static void appendNativeObject(final NativeObject obj, final StringBuilder sb, final int level) {
if (level == 0) {
// For whatever reason, when a native object is printed at the
// root level Firefox puts brackets outside it. This is not the
// case when a native object is printed as part of an array or
// inside another object.
sb.append('(');
}
sb.append('{');
if (level < 3) {
final Object[] ids = obj.getIds();
if (ids != null && ids.length > 0) {
boolean needsSeparator = false;
for (final Object key : ids) {
if (needsSeparator) {
sb.append(", ");
}
sb.append(key);
sb.append(": ");
appendValue(obj.get(key), sb, level + 1);
needsSeparator = true;
}
}
}
sb.append('}');
if (level == 0) {
sb.append(')');
}
}
/**
* This methods appends the val parameter to the passed StringBuffer.
* FireBug's console prints some object differently if printed at the
* root level or as part of an array or native object. This method tries
* to simulate Firebus's behavior.
*
* @param val
* the object to be printed
* @param sb
* the StringBuilder used as destination
* @param level
* the recursion level. If zero, it mean the object is
* printed at the console root level. Otherwise, the object
* is printed as part of an array or a native object.
*/
private static void appendValue(final Object val, final StringBuilder sb, final int level) {
if (val instanceof NativeFunction) {
sb.append('(');
// Remove unnecessary new lines and spaces from the function
final Pattern p = Pattern.compile("[ \\t]*\\r?\\n[ \\t]*",
Pattern.MULTILINE);
final Matcher m = p.matcher(((NativeFunction) val).toString());
sb.append(m.replaceAll(" ").trim());
sb.append(')');
}
else if (val instanceof BaseFunction) {
sb.append("function ");
sb.append(((BaseFunction) val).getFunctionName());
sb.append("() {[native code]}");
}
else if (val instanceof NativeObject) {
appendNativeObject((NativeObject) val, sb, level);
}
else if (val instanceof NativeArray) {
appendNativeArray((NativeArray) val, sb, level);
}
else if (val instanceof Delegator) {
if (level == 0) {
sb.append("[object ");
sb.append(((Delegator) val).getDelegee().getClassName());
sb.append(']');
}
else {
sb.append("({})");
}
}
else if (val instanceof Promise) {
sb.append("Promise");
if (level == 0) {
final Promise p = (Promise) val;
sb.append(' ').append(p.getLogDetails());
}
}
else if (val instanceof SimpleScriptable) {
if (level == 0) {
sb.append("[object ");
sb.append(((SimpleScriptable) val).getClassName());
sb.append(']');
}
else {
sb.append("({})");
}
}
else if (val instanceof String) {
if (level == 0) {
sb.append((String) val);
}
else {
// When printing a string as part of an array or native
// object,
// enclose it in double quotes and escape its content.
sb.append(quote((String) val));
}
}
else if (val instanceof Number) {
sb.append(((Number) val).toString());
}
else {
// ?!?
sb.append(val);
}
}
/**
* Even if similar, this is not JSON encoding. This replicates the way
* Firefox console prints strings when logging.
* @param s the string to be quoted
*/
private static String quote(final CharSequence s) {
final StringBuilder sb = new StringBuilder();
sb.append('\"');
for (int i = 0; i < s.length(); i++) {
final char ch = s.charAt(i);
switch (ch) {
case '\\':
sb.append("\\\\");
break;
case '\"':
sb.append("\\\"");
break;
case '\b':
sb.append("\\b");
break;
case '\t':
sb.append("\\t");
break;
case '\n':
sb.append("\\n");
break;
case '\f':
sb.append("\\f");
break;
case '\r':
sb.append("\\r");
break;
default:
if (ch < ' ' || ch > '~') {
sb.append("\\u" + Integer.toHexString(ch).toUpperCase(Locale.ROOT));
}
else {
sb.append(ch);
}
}
}
sb.append('\"');
return sb.toString();
}
private static String formatToString(final Object o) {
if (o == null) {
return "null";
}
else if (o instanceof NativeFunction) {
return ((NativeFunction) o).toString();
}
else if (o instanceof BaseFunction) {
return "function " + ((BaseFunction) o).getFunctionName()
+ "\n" + " [native code]\n" + "}";
}
else if (o instanceof NativeArray) {
// If an array is embedded inside another array, just return
// "[object Object]"
return "[object Object]";
}
else if (o instanceof Delegator) {
return "[object " + ((Delegator) o).getDelegee().getClassName()
+ "]";
}
else if (o instanceof NativeObject) {
return "[object " + ((NativeObject) o).getClassName() + "]";
}
else if (o instanceof SimpleScriptable) {
return "[object " + ((SimpleScriptable) o).getClassName() + "]";
}
else {
return o.toString();
}
}
ConsoleFormatter() {
}
@Override
public String printObject(final Object o) {
final StringBuilder sb = new StringBuilder();
appendValue(o, sb, 0);
return sb.toString();
}
@Override
public String parameterAsString(final Object o) {
if (o instanceof NativeArray) {
final StringBuilder sb = new StringBuilder();
for (int i = 0; i < ((NativeArray) o).size(); i++) {
if (i > 0) {
sb.append(',');
}
sb.append(formatToString(((NativeArray) o).get(i)));
}
return sb.toString();
}
return formatToString(o);
}
@Override
public String parameterAsInteger(final Object o) {
if (o instanceof Number) {
return Integer.toString(((Number) o).intValue());
}
else if (o instanceof String) {
try {
return Integer.toString(Integer.parseInt((String) o));
}
catch (final NumberFormatException e) {
// Swallow the exception and return below
}
}
return "NaN";
}
@Override
public String parameterAsFloat(final Object o) {
if (o instanceof Number) {
return Float.toString(((Number) o).floatValue());
}
else if (o instanceof String) {
try {
return Float.toString(Float.parseFloat((String) o));
}
catch (final NumberFormatException e) {
// Swallow the exception and return below
}
}
return "NaN";
}
}
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy