-
-
Save c-kick/2d717790aadd3aa86884ee0b07c3119f to your computer and use it in GitHub Desktop.
/*! | |
* | |
* NEW VERSION AT https://github.com/c-kick/mobileConsole | |
* | |
* hnl.mobileConsole - javascript mobile console - v1.3.8 - 04/01/2021 | |
* Adds html console to webpage. Especially useful for debugging JS on mobile devices. | |
* Supports 'log', 'trace', 'info', 'warn', 'error', 'group', 'groupEnd', 'table', 'assert', 'clear' | |
* Inspired by code by Jakub Fiala (https://gist.github.com/jakubfiala/8fe3461ab6508f46003d) | |
* Licensed under the MIT license | |
* | |
* Changelog: | |
* 1.3.8 | |
* - fixed bug when logging numbers | |
* | |
* Original author: @hnldesign | |
* Further changes, comments: @hnldesign | |
* Copyright (c) 2014-2020 HN Leussink | |
* Dual licensed under the MIT and GPL licenses. | |
* | |
* Info: http://www.hnldesign.nl/work/code/javascript-mobile-console/ | |
* Demo: http://code.hnldesign.nl/demo/hnl.MobileConsole.html | |
*/ | |
//Polyfills | |
//Date.now polyfill | |
if (!Date.now) { | |
Date.now = function now() { | |
return new Date().getTime(); | |
}; | |
} | |
//Array.isArray polyfill | |
if (typeof Array.isArray === 'undefined') { | |
Array.isArray = function(obj) { | |
return Object.prototype.toString.call(obj) === '[object Array]'; | |
}; | |
} | |
//Array.slice polyfill | |
if (!Uint8Array.prototype.slice) { | |
Object.defineProperty(Uint8Array.prototype, 'slice', { | |
value: function (begin, end) | |
{ | |
return new Uint8Array(Array.prototype.slice.call(this, begin, end)); | |
} | |
}); | |
} | |
//Array.filter polyfill | |
if (!Array.prototype.filter) { | |
Array.prototype.filter = function(fun/*, thisArg*/) { | |
if (this === void 0 || this === null) { | |
throw new TypeError(); | |
} | |
var t = Object(this); | |
var len = t.length >>> 0; | |
if (typeof fun !== 'function') { | |
throw new TypeError(); | |
} | |
var res = []; | |
var thisArg = arguments.length >= 2 ? arguments[1] : void 0; | |
for (var i = 0; i < len; i++) { | |
if (i in t) { | |
var val = t[i]; | |
if (fun.call(thisArg, val, i, t)) { | |
res.push(val); | |
} | |
} | |
} | |
return res; | |
}; | |
} | |
//Function.bind polyfill | |
if (!Function.prototype.bind) { | |
Function.prototype.bind = function(oThis) { | |
if (typeof this !== 'function') { | |
// closest thing possible to the ECMAScript 5 | |
// internal IsCallable function | |
throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); | |
} | |
var aArgs = Array.prototype.slice.call(arguments, 1), | |
fToBind = this, | |
fNOP = function() {}, | |
fBound = function() { | |
return fToBind.apply(this instanceof fNOP | |
? this | |
: oThis, | |
aArgs.concat(Array.prototype.slice.call(arguments))); | |
}; | |
if (this.prototype) { | |
// Function.prototype doesn't have a prototype property | |
fNOP.prototype = this.prototype; | |
} | |
fBound.prototype = new fNOP(); | |
return fBound; | |
}; | |
} | |
//Array.prototype.indexOf polyfill | |
// Production steps of ECMA-262, Edition 5, 15.4.4.14 | |
// Referentie: http://es5.github.io/#x15.4.4.14 | |
if (!Array.prototype.indexOf) { | |
Array.prototype.indexOf = function(searchElement, fromIndex) { | |
var k; | |
if (this == null) { | |
throw new TypeError('"this" is null or not defined'); | |
} | |
var o = Object(this); | |
var len = o.length >>> 0; | |
if (len === 0) { | |
return -1; | |
} | |
var n = +fromIndex || 0; | |
if (Math.abs(n) === Infinity) { | |
n = 0; | |
} | |
if (n >= len) { | |
return -1; | |
} | |
k = Math.max(n >= 0 ? n : len - Math.abs(n), 0); | |
while (k < len) { | |
if (k in o && o[k] === searchElement) { | |
return k; | |
} | |
k++; | |
} | |
return -1; | |
}; | |
} | |
//String.prototype.trim polyfill | |
if (!String.prototype.trim) { | |
String.prototype.trim = function () { | |
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, ''); | |
}; | |
} | |
//Array.prototype.map polyfill | |
// Production steps of ECMA-262, Edition 5, 15.4.4.19 | |
// Reference: http://es5.github.io/#x15.4.4.19 | |
if (!Array.prototype.map) { | |
Array.prototype.map = function(callback/*, thisArg*/) { | |
var T, A, k; | |
if (this == null) { | |
throw new TypeError('this is null or not defined'); | |
} | |
var O = Object(this); | |
var len = O.length >>> 0; | |
if (typeof callback !== 'function') { | |
throw new TypeError(callback + ' is not a function'); | |
} | |
if (arguments.length > 1) { | |
T = arguments[1]; | |
} | |
A = new Array(len); | |
k = 0; | |
while (k < len) { | |
var kValue, mappedValue; | |
if (k in O) { | |
kValue = O[k]; | |
mappedValue = callback.call(T, kValue, k, O); | |
A[k] = mappedValue; | |
} | |
k++; | |
} | |
return A; | |
}; | |
} | |
// DocReady - Fires supplied function when document is ready | |
if (typeof 'docReady' !== 'function') { | |
(function (funcName, baseObj) { | |
// The public function name defaults to window.docReady | |
// but you can pass in your own object and own function name and those will be used | |
// if you want to put them in a different namespace | |
funcName = funcName || 'docReady'; | |
baseObj = baseObj || window; | |
var i, len, readyList = [], readyFired = false, readyEventHandlersInstalled = false; | |
// call this when the document is ready | |
// this function protects itself against being called more than once | |
function ready() { | |
if (!readyFired) { | |
// this must be set to true before we start calling callbacks | |
readyFired = true; | |
for (i = 0, len = readyList.length; i < len; i = i + 1) { | |
// if a callback here happens to add new ready handlers, | |
// the docReady() function will see that it already fired | |
// and will schedule the callback to run right after | |
// this event loop finishes so all handlers will still execute | |
// in order and no new ones will be added to the readyList | |
// while we are processing the list | |
readyList[i].fn.call(window, readyList[i].ctx); | |
} | |
// allow any closures held by these functions to free | |
readyList = []; | |
} | |
} | |
function readyStateChange() { | |
if (document.readyState === 'complete') { | |
ready(); | |
} | |
} | |
// This is the one public interface | |
// docReady(fn, context); | |
// the context argument is optional - if present, it will be passed | |
// as an argument to the callback | |
baseObj[funcName] = function (callback, context) { | |
// if ready has already fired, then just schedule the callback | |
// to fire asynchronously, but right away | |
if (readyFired) { | |
setTimeout(function () {callback(context); }, 1); | |
return; | |
} | |
// add the function and context to the list | |
readyList.push({fn: callback, ctx: context}); | |
// if document already ready to go, schedule the ready function to run | |
if (document.readyState === 'complete') { | |
setTimeout(ready, 1); | |
} else if (!readyEventHandlersInstalled) { | |
// otherwise if we don't have event handlers installed, install them | |
if (document.addEventListener) { | |
// first choice is DOMContentLoaded event | |
document.addEventListener('DOMContentLoaded', ready, false); | |
// backup is window load event | |
window.addEventListener('load', ready, false); | |
} else { | |
// must be IE | |
document.attachEvent('onreadystatechange', readyStateChange); | |
window.attachEvent('onload', ready); | |
} | |
readyEventHandlersInstalled = true; | |
} | |
}; | |
}('docReady', window)); | |
} | |
//define console variable | |
var console = window.console; | |
var mobileConsole = (function () { | |
'use strict'; | |
//options and other variable containers | |
var options = { | |
overrideAutorun: false, //set this to true to skip mobile-detection and run the console no matter what. | |
startMinimized: false, | |
version: '1.3.7', | |
baseClass: 'mobileConsole_', | |
animParams: 'all 200ms ease', | |
browserinfo: { | |
isMobile: (function (a) { | |
return (/(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i.test(a) || /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s\-)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|\-m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw\-(n|u)|c55\/|capi|ccwa|cdm\-|cell|chtm|cldc|cmd\-|co(mp|nd)|craw|da(it|ll|ng)|dbte|dc\-s|devi|dica|dmob|do(c|p)o|ds(12|\-d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(\-|_)|g1 u|g560|gene|gf\-5|g\-mo|go(\.w|od)|gr(ad|un)|haie|hcit|hd\-(m|p|t)|hei\-|hi(pt|ta)|hp( i|ip)|hs\-c|ht(c(\-| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i\-(20|go|ma)|i230|iac( |\-|\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc\-|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|\-[a-w])|libw|lynx|m1\-w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|m\-cr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(\-| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)\-|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|\-([1-8]|c))|phil|pire|pl(ay|uc)|pn\-2|po(ck|rt|se)|prox|psio|pt\-g|qa\-a|qc(07|12|21|32|60|\-[2-7]|i\-)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h\-|oo|p\-)|sdk\/|se(c(\-|0|1)|47|mc|nd|ri)|sgh\-|shar|sie(\-|m)|sk\-0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h\-|v\-|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl\-|tdg\-|tel(i|m)|tim\-|t\-mo|to(pl|sh)|ts(70|m\-|m3|m5)|tx\-9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|\-v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(\-| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas\-|your|zeto|zte\-/i.test(a.substr(0, 4))); | |
}(navigator.userAgent || navigator.vendor || window.opera)), | |
browserChrome: /chrome/.test(navigator.userAgent.toLowerCase()), | |
ffox: /firefox/.test(navigator.userAgent.toLowerCase()) && !/chrome/.test(navigator.userAgent.toLowerCase()), | |
safari: /safari/.test(navigator.userAgent.toLowerCase()) && !/chrome/.test(navigator.userAgent.toLowerCase()), | |
trident: /trident/.test(navigator.userAgent.toLowerCase()), | |
evtLstn: typeof window.addEventListener === 'function' | |
}, | |
methods: ['log', 'trace', 'info', 'warn', 'error', 'group', 'groupCollapsed', 'groupEnd', 'table', 'assert', 'time', 'timeEnd', 'clear'], | |
hideButtons: ['group', 'groupCollapsed', 'groupEnd', 'table', 'assert', 'time', 'timeEnd'], | |
ratio: 0.4, //screen/console-ratio that determines the height of the console (reevaluated on every minimize/maximize). | |
paddingLeft: 0, //used when grouping, no need to change as it will be reset. | |
groupDepth: 0, //used when grouping, no need to change as it will be reset. | |
truncate: 400 //hard limit for large strings. For speed/mem issues with consecutive logging of large strings | |
}, | |
messages = { | |
clear : 'Console was cleared', | |
empty: '(Empty string)' | |
}, | |
status = { | |
initialized: false, | |
acActive : false, | |
acHovered : false, | |
acInput : '', | |
timers : {} | |
}, | |
history = { | |
output : { | |
prevMsg : '', | |
prevMethod : '', | |
counter : 0 | |
}, | |
input : { | |
commands : window.sessionStorage ? (sessionStorage.getItem('mobileConsoleCommandHistory') ? JSON.parse(sessionStorage.getItem('mobileConsoleCommandHistory')) : []) : [], | |
commandIdx: window.sessionStorage ? (sessionStorage.getItem('mobileConsoleCommandHistory') ? JSON.parse(sessionStorage.getItem('mobileConsoleCommandHistory')).length : 0) : 0, | |
acIdx: 0, | |
acHovered: false | |
} | |
}, | |
//'backup' original console for reference & internal debugging | |
missingMethod = function() { return true; }, //method is not supported on this device's original console, return dummy | |
originalConsole = { | |
log: (console && typeof console.log === 'function') ? console.log.bind(console) : missingMethod, | |
info: (console && typeof console.info === 'function') ? console.info.bind(console) : missingMethod, | |
dir: (console && typeof console.dir === 'function') ? console.dir.bind(console) : missingMethod, | |
group: (console && typeof console.group === 'function') ? console.group.bind(console) : missingMethod, | |
groupEnd: (console && typeof console.groupEnd === 'function') ? console.groupEnd.bind(console) : missingMethod, | |
warn: (console && typeof console.warn === 'function') ? console.warn.bind(console) : missingMethod, | |
error: (console && typeof console.error === 'function') ? console.error.bind(console) : missingMethod, | |
trace: (console && typeof console.trace === 'function') ? console.trace.bind(console) : missingMethod, | |
clear: (console && typeof console.clear === 'function') ? console.clear.bind(console) : missingMethod | |
}, | |
// reference variables | |
mobileConsole, consoleElement, commandLine; | |
//helpers for all sub functions | |
function setCSS(el, css) { | |
var i; | |
for (i in css) { | |
if (css.hasOwnProperty(i)) { | |
el.style[i] = css[i]; | |
} | |
} | |
return el; | |
} | |
function htmlToString(html) { | |
var string; | |
try { string = String(html); } catch(e) { string = JSON.stringify(html); } //this should be done differently, but works for now | |
return string.replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>') | |
.replace(/"/g, '"').replace(/ /g, '\u00a0').replace(/(?:\r\n|\r|\n)/g, '<br />').trim(); | |
} | |
function createElem(type, className, css) { | |
if (!type) { return; } | |
var element = setCSS(document.createElement(type), css); | |
if (className) { element.className = options.baseClass + className; } | |
return setCSS(element, css); | |
} | |
function storeCommand(command) { | |
if (history) { | |
history.input.commands.push(encodeURI(command.trim())); | |
history.input.commandIdx = history.input.commands.length; | |
if (window.sessionStorage) { sessionStorage.setItem('mobileConsoleCommandHistory', JSON.stringify(history.input.commands)); } | |
} | |
} | |
function valBetween(val, min, max) { | |
return (Math.min(max, Math.max(min, val))); | |
} | |
function getMaxHeight() { | |
return valBetween(Math.floor((window.innerHeight || document.documentElement.clientHeight) * options.ratio), 55, 300); | |
} | |
function getClass(item) { | |
var returnVal = ''; | |
if (item && item.constructor) { | |
returnVal = item.constructor.name; | |
} else { | |
returnVal = Object.prototype.toString.call(item); | |
} | |
return String(returnVal); | |
} | |
// elements | |
var elements = { | |
lines: [], | |
acItems: [], | |
base: createElem('div', 'base', { | |
boxSizing: 'border-box', | |
position: 'fixed', | |
resize: 'none', | |
fontSize: '10px', | |
lineHeight: '10px', | |
bottom: 0, | |
top: 'auto', | |
right: 0, | |
width: '100%', | |
zIndex: 20000, | |
padding: 0, | |
paddingBottom: options.browserinfo.isMobile ? '35px' : '25px', | |
margin: 0, | |
border: '0 none', | |
borderTop: '1px solid #808080', | |
backgroundColor: '#ffffff' | |
}), | |
topbar : createElem('div', 'topbar', { | |
boxSizing: 'border-box', | |
position: 'absolute', | |
height: '28px', | |
left: 0, | |
right: 0, | |
display: 'block', | |
padding: '0 2px', | |
overflow: 'hidden', | |
webkitOverflowScrolling: 'touch', | |
color: '#444444', | |
backgroundColor: '#f3f3f3', | |
border: '0 none', | |
borderTop: '1px solid #a3a3a3', | |
borderBottom: '1px solid #a3a3a3', | |
whiteSpace: 'nowrap', | |
overflowX: 'auto' | |
}), | |
scrollcontainer : createElem('div', 'scroller', { | |
boxSizing: 'border-box', | |
border: '0 none', | |
fontFamily: 'Consolas, monaco, monospace', | |
position: 'relative', | |
display: 'block', | |
height: getMaxHeight() + 'px', | |
overflow: 'auto', | |
webkitOverflowScrolling: 'touch', | |
'-webkit-transition': options.animParams, | |
'-moz-transition': options.animParams, | |
'-o-transition': options.animParams, | |
'transition': options.animParams | |
}), | |
table : createElem('table', 'table', { | |
border: '0 none', | |
margin: 0, | |
position: 'relative', | |
tableLayout: 'auto', | |
width: '100%', | |
borderCollapse: 'collapse' | |
}), | |
stackTraceTable : createElem('table', 'stackTraceTable', { | |
border: '0 none', | |
margin: 0, | |
display: 'none', | |
marginLeft: '10px', | |
marginTop: options.browserinfo.isMobile ? '8px' : '4px', | |
tableLayout: 'auto', | |
maxWidth: '100%', | |
color: '#333333' | |
}), | |
tr : createElem('tr', 'table_row', { | |
verticalAlign: 'top' | |
}), | |
td : createElem('td', 'table_row', { | |
border: '0 none', | |
padding: '2px 4px', | |
verticalAlign: 'top' | |
}), | |
msgContainer : createElem('span', 'msgContainer', { | |
border: '0 none', | |
margin: 0, | |
display: 'inline', | |
overflow: 'hidden' | |
}), | |
tdLeft : createElem('td', 'table_row_data', { | |
border: '0 none', | |
textAlign: 'left', | |
padding: options.browserinfo.isMobile ? '8px 12px' : '4px 8px' | |
}), | |
tdRight : createElem('td', 'table_row_data', { | |
border: '0 none', | |
textAlign: 'left', | |
padding: options.browserinfo.isMobile ? '8px 12px' : '4px 8px', | |
whiteSpace: 'nowrap', | |
overflow: 'hidden' | |
}), | |
link : createElem('a', 'link', { | |
color: '#1155cc', | |
textDecoration: 'underline' | |
}), | |
dot : createElem('div', 'table_row_data_dot', { | |
display: 'inline', | |
borderRadius: '50%', | |
fontSize: '80%', | |
fontWeight: 'bold', | |
padding: '2px 5px', | |
textAlign: 'center', | |
marginRight: '5px', | |
backgroundColor: '#333333', | |
color: '#ffffff' | |
}), | |
button : createElem('button', 'button', { | |
display: 'inline-block', | |
fontFamily: '"Helvetica Neue",Helvetica,Arial,sans-serif', | |
fontWeight: 'normal', | |
textTransform: 'capitalize', | |
fontSize: '12px', | |
lineHeight: '26px', | |
height: '26px', | |
padding: '0 8px', | |
margin: 0, | |
textAlign: 'center', | |
marginRight: '5px', | |
border: '0 none', | |
backgroundColor: 'transparent', | |
color: 'inherit', | |
cursor: 'pointer' | |
}), | |
buttons : { | |
}, | |
input : createElem('div', 'input', { | |
boxSizing: 'border-box', | |
height: options.browserinfo.isMobile ? '35px' : '29px', | |
fontFamily: 'Consolas, monaco, monospace', | |
position: 'absolute', | |
bottom: 0, | |
left: 0, | |
right: 0, | |
margin: 0, | |
border: '0 none', | |
borderTop: '1px solid #EEEEEE' | |
}), | |
gt : createElem('DIV', 'gt', { | |
position: 'absolute', | |
bottom: 0, | |
width: '25px', | |
lineHeight: options.browserinfo.isMobile ? '34px' : '28px', | |
height: options.browserinfo.isMobile ? '34px' : '28px', | |
textAlign: 'center', | |
fontSize: '16px', | |
fontFamily: 'Consolas, monaco, monospace', | |
fontWeight: 'bold', | |
color: '#3577B1', | |
zIndex: 2 | |
}), | |
consoleinput : createElem('input', 'consoleinput', { | |
boxSizing: 'border-box', | |
position: 'absolute', | |
bottom: 0, | |
width : '100%', | |
fontSize: options.browserinfo.isMobile ? '16px' : 'inherit', //prevents ios safari's zoom on focus | |
fontFamily: 'Consolas, monaco, monospace', | |
paddingLeft: '25px', | |
margin: 0, | |
height: options.browserinfo.isMobile ? '35px' : '25px', | |
border: '0 none', | |
outline: 'none', | |
outlineWidth: 0, | |
boxShadow: 'none', | |
'-moz-appearance': 'none', | |
'-webkit-appearance': 'none', | |
backgroundColor: 'transparent', | |
color: '#000000', | |
zIndex: 1 | |
}), | |
autocomplete : createElem('div', 'autocomplete', { | |
display: 'none', | |
position: 'absolute', | |
bottom: options.browserinfo.isMobile ? '35px' : '28px', | |
left: 0, | |
boxShadow: '1px 2px 5px rgba(0,0,0,0.1)', | |
color: '#000000', | |
backgroundColor: '#FFFFFF', | |
border: '1px solid #b5b5b5' | |
}), | |
autocompleteItem : createElem('a', 'autocompleteitem', { | |
display: 'block', | |
textDecoration: 'none', | |
fontSize: options.browserinfo.isMobile ? '16px' : 'inherit', | |
padding: '5px 8px', | |
wordWrap: 'break-word', | |
whiteSpace: 'nowrap' | |
}), | |
arrowUp: '<img width="10" height="10" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAMAAADzjKfhAAAACVBMVEUAAAD///93d3eMZ/YKAAAAAnRSTlMAAHaTzTgAAAAdSURBVHgBY2CEAAYgYAJiEMXEBKHADCYIgKmD0QAFdAA2OHJXEwAAAABJRU5ErkJggg==">', | |
arrowDown: '<img width="10" height="10" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAG0lEQVR42mNgwAfKy8v/48I4FeA0AacVDFQBAP9wJkE/KhUMAAAAAElFTkSuQmCC">', | |
arrowRight: '<img width="10" height="10" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAcAAAAHCAYAAADEUlfTAAAAJUlEQVR42mNgAILy8vL/DLgASBKnApgkVgXIkhgKiNKJ005s4gDLbCZBiSxfygAAAABJRU5ErkJggg==">' | |
}; | |
//shared functions | |
var setLineStyle = (function () { | |
var lineStyles = function (style) { | |
switch (style) { | |
case 'log': | |
return { | |
text : { | |
borderBottom: '1px solid #DDDDDD', | |
color: '#000000' | |
}, | |
dot : { | |
color: '#FFFFFF', | |
backgroundColor: '#8097bd' | |
} | |
}; | |
case 'info': | |
return { | |
text : { | |
borderBottom: '1px solid #DDDDDD', | |
color: '#1f3dc4' | |
}, | |
dot : { | |
color: '#FFFFFF', | |
backgroundColor: '#367AB4' | |
} | |
}; | |
case 'warn': | |
return { | |
text : { | |
borderBottom: '1px solid #DDDDDD', | |
color: '#CE8724', | |
backgroundColor : '#fff6e0' | |
}, | |
dot : { | |
color: '#FFFFFF', | |
backgroundColor: '#e8a400' | |
} | |
}; | |
case 'error': | |
case 'table': | |
return { | |
text : { | |
borderBottom: '1px solid #DDDDDD', | |
color: '#FF0000', | |
backgroundColor : '#ffe5e5' | |
}, | |
dot : { | |
color: '#FFFFFF', | |
backgroundColor: '#FF0000' | |
} | |
}; | |
case 'assert': | |
return { | |
text : { | |
borderBottom: '1px solid #DDDDDD', | |
color: '#FF0000', | |
backgroundColor : '#ffe5e5' | |
}, | |
dot : { | |
color: '#FFFFFF', | |
backgroundColor: '#FF0000' | |
} | |
}; | |
case 'trace': | |
return { | |
text : { | |
borderBottom: '1px solid #DDDDDD', | |
color: '#000000' | |
}, | |
dot : { | |
//will not happen | |
} | |
}; | |
case 'time': | |
case 'timeEnd': | |
return { | |
text : { | |
borderBottom: '1px solid #DDDDDD', | |
color: '#0000ff' | |
}, | |
dot : { | |
color: '#FFFFFF', | |
backgroundColor: '#0000ff' | |
} | |
}; | |
default: | |
return { | |
text : { | |
borderBottom: '1px solid #DDDDDD', | |
color: '#000000' | |
}, | |
dot : { | |
color: '#FFFFFF', | |
backgroundColor: '#8097bd' | |
} | |
}; | |
} | |
}; | |
var color, dot; | |
return function (element, type, msg) { | |
if (status.initialized) { | |
color = (typeof msg === 'undefined' || msg === htmlToString(messages.empty)) ? {color: '#808080'} : ((msg === htmlToString(messages.clear)) ? {color: '#808080', fontStyle: 'italic'} : (lineStyles(type) !== undefined ? lineStyles(type).text : lineStyles.log.text)); | |
dot = typeof lineStyles(type) !== 'undefined' ? lineStyles(type).dot : lineStyles.log.dot; | |
setCSS(element, color); | |
//has dot? | |
if (element.childNodes[0].childNodes[0].className.indexOf('dot') !== -1) { | |
setCSS(element.childNodes[0].childNodes[0], lineStyles(type).dot); | |
} | |
} | |
}; | |
}()), | |
getLink = function (href, textString) { | |
var HTMLurl = elements.link.cloneNode(false); | |
if (href) { | |
HTMLurl.setAttribute('href', href); | |
HTMLurl.setAttribute('target', '_blank'); | |
} | |
HTMLurl.innerHTML = textString || href.split('\\').pop().split('/').filter(Boolean).pop(); | |
return HTMLurl; | |
}, | |
toggleHeight = function () { | |
if (status.initialized) { | |
var existingPadding = parseInt(document.body.style.paddingBottom, 10) - Math.abs(elements.base.offsetHeight + elements.topbar.offsetHeight); | |
var newHeight = (elements.base.minimized) ? getMaxHeight() + 'px' : '0px'; | |
setCSS(elements.scrollcontainer, { | |
height: newHeight | |
}); | |
setCSS(document.body, { | |
paddingBottom: existingPadding + Math.abs(parseInt(newHeight, 10) + elements.topbar.offsetHeight) + 'px' | |
}); | |
elements.buttons.toggler.innerHTML = (elements.base.minimized) ? elements.arrowDown : elements.arrowUp; | |
elements.buttons.toggler.setAttribute('title', (elements.base.minimized) ? 'Minimize console' : 'Maximize console'); | |
elements.base.minimized = !elements.base.minimized; | |
return elements.base.minimized; | |
} | |
return 'Not built!'; | |
}, | |
about = (function () { | |
return function () { | |
console.info( | |
'--==## Mobile Console ' + (status.initialized ? 'active' : 'inactive') + ' ##==--' + '\n' + | |
'--===============================--' + '\n' + | |
'MobileConsole v' + options.version + ', running on ' + navigator.userAgent.toLowerCase() | |
); | |
}; | |
}()); | |
// --==** sub functions start here **==-- | |
//initializes the console HTML element | |
function initConsoleElement() { | |
//reference | |
var ref; | |
//core | |
function toggleScroll() { | |
elements.scrollcontainer.scrollTop = elements.scrollcontainer.scrollHeight; | |
elements.scrollcontainer.scrollLeft = 0; | |
} | |
function destroyConsole() { | |
//conan the destroyer. Very basic; just removes the console element. mobileConsole will still 'pipe' console logging | |
//don't see any reason for now to reverse that. | |
elements.base.parentNode.removeChild(elements.base); | |
status.initialized = false; | |
console.warn( | |
'--==## Mobile Console DESTROYED ##==--' + '\n' + | |
'To enable again: reload the page. Tip: use the minimize button instead of closing.' | |
); | |
} | |
function assemble() { | |
var i = options.methods.length, key; | |
//add close button | |
elements.buttons.closer = elements.button.cloneNode(false); | |
elements.buttons.closer.innerHTML = '✕'; | |
elements.buttons.closer.setAttribute('title', 'Close (destroy) console'); | |
setCSS(elements.buttons.closer, { float: 'right', margin: '0'}); | |
//add buttons | |
while (i--) { | |
elements.buttons[options.methods[i]] = elements.button.cloneNode(false); | |
elements.buttons[options.methods[i]].innerHTML = options.methods[i].charAt(0).toUpperCase() + options.methods[i].slice(1); | |
elements.buttons[options.methods[i]].setAttribute('title', (options.methods[i] !== 'clear') ? 'Toggle the display of ' + options.methods[i] + ' messages' : 'Clear the console'); | |
} | |
//add min/maximize button | |
elements.buttons.toggler = elements.button.cloneNode(false); | |
elements.buttons.toggler.innerHTML = elements.arrowDown; | |
elements.buttons.toggler.setAttribute('title', 'Minimize console'); | |
//assemble everything | |
for (key in elements.buttons) { | |
if (elements.buttons.hasOwnProperty(key)) { | |
elements.topbar.insertBefore(elements.buttons[key], elements.topbar.firstChild); | |
} | |
} | |
elements.scrollcontainer.appendChild(elements.table); | |
elements.base.appendChild(elements.topbar); | |
elements.base.appendChild(elements.scrollcontainer); | |
status.initialized = true; | |
return elements.base; | |
} | |
function attach(console) { | |
document.body.appendChild(console); | |
setCSS(elements.topbar, { | |
top: -Math.abs(elements.topbar.offsetHeight) + 'px' | |
}); | |
var existingPadding = isNaN(parseInt(document.body.style.paddingBottom, 10)) ? 0 : parseInt(document.body.style.paddingBottom, 10); | |
setCSS(document.body, { | |
paddingBottom: existingPadding + Math.abs(console.offsetHeight + elements.topbar.offsetHeight) + 'px' | |
}); | |
elements.scrollcontainer.scrollTop = elements.scrollcontainer.scrollHeight; | |
return elements.base; | |
} | |
function toggleLogType(e) { | |
var button = e.currentTarget || e.srcElement; | |
var logType = button.innerHTML.toLowerCase(); | |
var elems = elements.lines[logType], i = elems.length; | |
button.toggled = (typeof button.toggled === 'undefined') ? true : !button.toggled; | |
setCSS(button, { opacity: (button.toggled) ? '0.5' : '' }); | |
while (i--) { | |
setCSS(elems[i], { display: (button.toggled) ? 'none' : '' }); | |
} | |
toggleScroll(); | |
button.blur(); | |
return button; | |
} | |
function setBinds() { | |
var methods = options.methods, i = methods.length; | |
while (i--) { | |
if (methods[i] !== 'clear') { | |
if (options.browserinfo.evtLstn) { | |
elements.buttons[methods[i]].addEventListener('click', toggleLogType, false); | |
} else { | |
elements.buttons[methods[i]].attachEvent('onclick', toggleLogType); | |
} | |
} | |
if (options.hideButtons.indexOf(methods[i]) !== -1) { //hide buttons that we don't want | |
setCSS(elements.buttons[methods[i]], { display: 'none' }); | |
} | |
} | |
if (options.browserinfo.evtLstn) { | |
elements.buttons.toggler.addEventListener('click', toggleHeight, false); | |
elements.buttons.closer.addEventListener('click', destroyConsole, false); | |
elements.buttons.clear.addEventListener('click', console.clear, false); | |
} else { | |
elements.buttons.toggler.attachEvent('onclick', toggleHeight); | |
elements.buttons.closer.attachEvent('onclick', destroyConsole); | |
elements.buttons.clear.attachEvent('onclick', console.clear); | |
} | |
} | |
//init | |
function init() { | |
var element = assemble(); | |
docReady(function () { | |
setBinds(); | |
attach(element); | |
if (options.startMinimized) { | |
toggleHeight(); | |
} | |
}); | |
//expose Public methods and variables | |
return { | |
toggleHeight : toggleHeight, | |
toggleScroll : toggleScroll, | |
destroy: destroyConsole | |
}; | |
} | |
if (!ref) { | |
ref = init(); | |
} | |
return ref; | |
} | |
//initializes the new console logger | |
function initConsole() { | |
//reference | |
var ref; | |
//sub helpers | |
function isEmpty(obj) { | |
for(var prop in obj) { | |
if(obj.hasOwnProperty(prop)) | |
return false; | |
} | |
return JSON.stringify(obj) === JSON.stringify({}); | |
} | |
function isElement(o) { | |
return ( | |
typeof HTMLElement === 'object' ? o instanceof HTMLElement : //DOM2 | |
o && typeof o === 'object' && o !== null && o.nodeType === 1 && typeof o.nodeName === 'string' | |
); | |
} | |
function objectToString(object) { | |
var prop, output = ''; | |
if (!isElement(object)) { | |
for (prop in object) { | |
if (!object.hasOwnProperty(prop)) { | |
continue; | |
} else if (typeof (object[prop]) === 'object') { | |
output += prop + ((Array.isArray(object[prop])) ? ': Array(' + object[prop].length + ')' : ': {…}'); | |
} else if (typeof (object[prop]) === 'function') { | |
output += 'func: f'; | |
} else { | |
output += prop + ': <span style="color:#c54300;">"' + object[prop] + '"</span>'; | |
} | |
output += ', '; | |
} | |
return '<em>{' + output.slice(0, -2) + '}</em>'; // returns cleaned up JSON | |
} | |
return htmlToString(object.outerHTML); | |
} | |
function urlFromString(string) { | |
string = String(string); | |
//searches for url in string, returns url as string | |
var match, uriPattern = /\b((?:[a-z][\w-]+:(?:\/{1,3}|[a-z0-9%])|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}\/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'".,<>?«»“â€â€˜â€™]))/ig; | |
try { | |
match = string.match(uriPattern)[0]; | |
return match; | |
} catch (e) { | |
return ''; | |
} | |
} | |
function filterOut(array, match) { | |
return array.filter(function(item){ | |
return typeof item === 'string' && item.indexOf(match) === -1; | |
}); | |
} | |
function preFilterTrace(array) { | |
var newArray = array.split('\n').filter(Boolean), //filter cleans out empty values | |
isCommandLine = false, stealthThese, i; | |
if (newArray[0].indexOf('http') === -1) { newArray.shift(); } //remove first line if contains no 'http' (Chrome starts with 'Error', Firefox doesn't..) | |
if (newArray[0].indexOf('console.') !== -1 || newArray[0].indexOf('console[method]') !== -1) { newArray.shift(); } | |
if (newArray.length > 0) { | |
isCommandLine = newArray[newArray.length - 1].indexOf('keydown') !== -1; | |
newArray = newArray.filter(function(item){ return item !== ''; }); | |
if (isCommandLine) { | |
stealthThese = ['submitCommand', 'eval', 'setBinds', 'interceptConsole', 'newConsole']; | |
newArray.pop(); //remove last index, as it is the keydown event. | |
i = stealthThese.length; | |
while(i--) { | |
newArray = filterOut(newArray, stealthThese[i]); | |
} | |
} | |
} | |
if (isCommandLine || newArray.length === 0) { | |
newArray.push('(anonymous function) console:1:1'); | |
} | |
return newArray; | |
} | |
//core | |
function formatStackTrace(trace, origtrace) { | |
var callStack = []; | |
//original stack is hidden inside trace object, if specified | |
var stackTraceOrig = (typeof trace !== 'undefined' && typeof trace[4] !== 'undefined') ? trace[4].stack : undefined; | |
//if the first line contains this, skip it. Meant for browsers that begin the stack with the error message itself (already captured before formatStackTrace) | |
var traceToProcess = (origtrace && origtrace !== '') ? origtrace : stackTraceOrig, | |
i, | |
lines, | |
url, | |
txt, | |
thisLine, | |
lineAndColumn, | |
caller, | |
separator = options.browserinfo.ffox ? '@' : '()'; | |
//stop if no source trace can be determined | |
if (!traceToProcess) { return; } | |
lines = preFilterTrace(traceToProcess); //pre filters all lines by filtering out all mobileConsole's own methods so mobileConsole runs Stealth and unobtrusive | |
i = lines.length; | |
while (i--) { | |
thisLine = lines[i].trim(); | |
lineAndColumn = thisLine.match(/(?::)(\d+)(?::)(\d+)/); | |
url = urlFromString(thisLine).replace(lineAndColumn[0], '').split('#')[0] || ''; | |
caller = htmlToString(thisLine.replace(urlFromString(thisLine), '').replace(separator, '').replace('at ', '').trim()); | |
if (caller === '' || caller === lineAndColumn[0]) { continue; } | |
if (url[url.length - 1] === '/') { | |
txt = '(index)'; | |
} else { | |
txt = url.split('\\').pop().split('/').filter(Boolean).pop() || caller; | |
} | |
callStack.push({ | |
caller: caller, | |
url: url ? url.split(':')[0] + ':' + url.split(':')[1] : caller, | |
linkText: txt + lineAndColumn[0], | |
line: lineAndColumn[1], | |
col: lineAndColumn[2], | |
originalLine: thisLine | |
}); | |
} | |
return callStack; | |
} | |
function traceToTable(table, trace) { | |
var i, tdLeft, tdRight, tr; | |
if (typeof trace === 'undefined') { | |
return; | |
} | |
trace.reverse(); //reverse order of trace, just like in a browser's console | |
i = trace.length; | |
while (i--) { | |
tdLeft = elements.td.cloneNode(false); | |
tdRight = elements.td.cloneNode(false); | |
tr = elements.tr.cloneNode(false); | |
tdLeft.innerHTML = trace[i].caller; | |
tdRight.innerHTML = ' @ '; | |
tdRight.appendChild(getLink((trace[i].url || ''), trace[i].linkText)); | |
tr.appendChild(tdLeft); | |
tr.appendChild(tdRight); | |
table.insertBefore(tr, table.firstChild); | |
} | |
return table; | |
} | |
function colorizeData(key, value) { | |
var valueColor = '#3c53da', keyColor = '#ae33b7', classname = getClass(value); | |
if (value && classname.indexOf('HTML') !== -1) { | |
value = htmlToString(value.outerHTML); | |
valueColor = '#ad8200'; | |
} else if (key === 'innerHTML' || key === 'outerHTML') { | |
value = htmlToString(value); | |
valueColor = '#ad8200'; | |
} | |
if (typeof value === 'string') { | |
valueColor = '#c54300'; | |
if (value.length > options.truncate) { | |
value = '"' + String(value).substring(0, options.truncate) + '" [...] <br/><span style="color:#FF0000;text-decoration: underline;">Note: string was truncated to ' + options.truncate + ' chars</span>'; | |
} else { | |
value = '"' + value + '"'; | |
} | |
} else if (value === null) { | |
valueColor = '#808080'; | |
value = 'null'; | |
} else if (typeof value === 'undefined' || value === undefined) { | |
valueColor = '#808080'; | |
value = 'undefined'; | |
} else if (typeof value === 'object') { | |
if (isEmpty(value)) { | |
value = '{}'; | |
} else { | |
valueColor = ''; | |
//iterate over object to create another table inside | |
var tempTable = createElem('table', 'stackTraceSubTable', { | |
border: '0 none', | |
margin: 0, | |
display: 'none', | |
marginLeft: '10px', | |
marginTop: options.browserinfo.isMobile ? '8px' : '4px', | |
tableLayout: 'auto', | |
maxWidth: '100%', | |
color: '#333333' | |
}), | |
wrap = document.createElement('div'); | |
wrap.appendChild(objectToTable(tempTable, value).cloneNode(true)); | |
if (Array.isArray(value)) { | |
value = 'Array(' + value.length + ')' + wrap.innerHTML; | |
} else { | |
value = wrap.innerHTML; | |
} | |
} | |
} | |
return '<span style="color:' + keyColor + ';">' + key + ':</span> <span style="color:' + valueColor + ';">' + value + '</span>'; | |
} | |
function objectToTable(table, object) { | |
var i, tdLeft, tr; | |
if (isElement(object)){ | |
tdLeft = elements.td.cloneNode(false); tr = elements.tr.cloneNode(false); | |
tdLeft.innerHTML = htmlToString(object.outerHTML); | |
tr.appendChild(tdLeft); | |
table.appendChild(tr); | |
} else { | |
for (i in object) { | |
if (object.hasOwnProperty(i)) { | |
tdLeft = elements.td.cloneNode(false); tr = elements.tr.cloneNode(false); | |
tdLeft.innerHTML = colorizeData(i, object[i]); | |
tr.appendChild(tdLeft); | |
table.appendChild(tr); | |
} | |
} | |
} | |
return table; | |
} | |
function toggleDetails(e) { | |
var button = e.currentTarget || e.srcElement, i, hidden; | |
if (button.getAttribute('toggles') === 'table') { | |
var tables = button.parentElement.getElementsByTagName('table'); | |
i = tables.length; | |
while (i--) { | |
hidden = (tables[i].currentStyle ? tables[i].currentStyle.display : window.getComputedStyle(tables[i], null).display) === 'none'; | |
button.innerHTML = button.innerHTML.replace((hidden ? elements.arrowRight : elements.arrowDown), (hidden ? elements.arrowDown : elements.arrowRight)); | |
setCSS(tables[i], { display: hidden ? 'table' : 'none' }); | |
} | |
} | |
} | |
function isRepeat(message, method) { | |
return (history.output.prevMsg === message && history.output.prevMethod === method) && (typeof message !== 'object') && (method !== 'trace') && (method !== 'group') && (method !== 'groupCollapsed') && (method !== 'groupEnd'); | |
} | |
function newConsole() { | |
try { | |
//get arguments, set vars | |
var method = arguments[0], className, isHTMLElement, | |
message = (typeof arguments[1].newMessage !== 'undefined') ? arguments[1].newMessage : undefined, | |
stackTrace = (typeof arguments[1].newStackTrace !== 'undefined') ? arguments[1].newStackTrace : undefined, | |
colors = (typeof arguments[1].slice !== 'undefined') ? arguments[1].slice(1) : []; | |
//if message emtpy or undefined, show empty message-message | |
if (message === '' || typeof message === 'undefined' || message === undefined) { message = messages.empty; } | |
if (isRepeat(message, method) && method.indexOf('time') === -1) { | |
// up the counter and add the dot | |
history.output.counter = history.output.counter + 1; | |
elements.table.lastChild.countDot = elements.table.lastChild.countDot || elements.dot.cloneNode(false); | |
elements.table.lastChild.firstChild.insertBefore(elements.table.lastChild.countDot, elements.table.lastChild.firstChild.firstChild).innerHTML = history.output.counter; | |
setLineStyle(elements.table.lastChild, method, message); | |
} else { | |
history.output.prevMsg = message; | |
history.output.prevMethod = method; | |
history.output.counter = 1; | |
//an object requires some more handling | |
if (typeof message === 'object' && method !== 'assert' && method !== 'timeEnd') { | |
message = isElement(message) ? | |
htmlToString(message.outerHTML.match(/<(.*?)>/g)[0] + '...' + message.outerHTML.match(/<(.*?)>/g).pop()) : //gets e.g. <div>...</div> | |
objectToString(message); | |
} else if (method !== 'assert' && method.indexOf('time') === -1) { | |
//handle colors | |
var parts = message.toString().split('%c'); parts.shift(); | |
var l = parts.length; | |
if (l > 0 && colors.length > 0) { | |
while(l--) { | |
parts[l] = '<span style="' + colors[l] + '">' + htmlToString(parts[l]) + '</span>'; | |
} | |
message = parts.join(''); | |
} else { | |
message = htmlToString(message); | |
} | |
} | |
var detailTable, | |
stackTable, | |
msgContainer = elements.msgContainer.cloneNode(false), | |
lineContainer = elements.tr.cloneNode(false), | |
leftContainer = elements.tdLeft.cloneNode(true), | |
rightContainer = elements.tdRight.cloneNode(false), | |
arrows = stackTrace ? elements.arrowRight + ' ' : ''; | |
switch (method) { | |
case 'assert': | |
if (message[0] === false) { | |
msgContainer.innerHTML = arrows + 'Assertion failed: ' + message[1]; | |
} | |
stackTable = traceToTable(elements.stackTraceTable.cloneNode(false), stackTrace); | |
method = 'error'; //groups it under 'error' and is thus toggleable in view | |
break; | |
case 'log': | |
case 'debug': | |
case 'info': | |
case 'warn': | |
if (typeof arguments[1].newMessage === 'object') { | |
detailTable = objectToTable(elements.stackTraceTable.cloneNode(false), arguments[1].newMessage); | |
msgContainer.innerHTML = elements.arrowRight + ' ' + message; | |
} else { | |
msgContainer.innerHTML = message; | |
} | |
break; | |
case 'error': | |
case 'trace': | |
case 'dir': | |
case 'table': | |
//left side | |
if (method === 'table' || typeof arguments[1].newMessage === 'object') { | |
detailTable = objectToTable(elements.stackTraceTable.cloneNode(false), arguments[1].newMessage); | |
msgContainer.innerHTML = elements.arrowRight + ' ' + message; | |
} else if (method === 'trace') { | |
message = 'console.trace()'; | |
msgContainer.innerHTML = arrows + message; | |
} else { | |
msgContainer.innerHTML = arrows + message; | |
} | |
stackTable = traceToTable(elements.stackTraceTable.cloneNode(false), stackTrace); | |
break; | |
case 'group': | |
case 'groupCollapsed': | |
case 'groupEnd': | |
if (method !== 'groupEnd') { | |
options.groupDepth = options.groupDepth + 1; | |
msgContainer.innerHTML = '<strong>' + message + '</strong>'; | |
msgContainer.setAttribute('toggles', 'group_' + options.groupDepth); | |
} else { | |
options.groupDepth = valBetween(options.groupDepth - 1, 0, 99); | |
history.output.prevMsg = ''; | |
} | |
if (options.groupDepth > 0) { | |
options.paddingLeft = (options.groupDepth * 23) + 'px'; | |
} else { | |
options.paddingLeft = 0; | |
} | |
break; | |
case 'time': | |
case 'timeEnd': | |
var timerName = arguments[1].newMessage || 'default', now, passed; | |
if (method === 'time') { | |
status.timers[timerName] = Date.now(); | |
if (typeof arguments[1].original === 'function') { | |
arguments[1].original.apply(console, arguments[1].originalArguments); //make sure we still call the original console.time to start the browser's console timer | |
} | |
return; | |
} | |
now = Date.now(); | |
if (!status.timers[timerName]) { | |
console.warn('Timer "' + timerName + '" does not exist.'); | |
return; | |
} | |
passed = now - (status.timers[timerName] || 0); | |
message = timerName + ': ' + passed + 'ms'; | |
msgContainer.innerHTML = message; | |
delete status.timers[timerName]; | |
break; | |
default: | |
msgContainer.innerHTML = message; | |
} | |
if (!msgContainer.innerHTML) { return; } | |
leftContainer.appendChild(msgContainer); | |
if (detailTable || stackTable) { | |
setCSS(msgContainer, {cursor : 'pointer'}); | |
leftContainer.appendChild(detailTable || stackTable); | |
msgContainer.setAttribute('toggles', 'table'); | |
} | |
//populate right side | |
if (stackTrace && typeof stackTrace[stackTrace.length - 1] !== 'undefined') { | |
rightContainer.appendChild(setCSS(getLink(stackTrace[0].url, stackTrace[0].linkText), {color: '#808080'})); | |
} | |
//add to line | |
lineContainer.appendChild(leftContainer); | |
lineContainer.appendChild(rightContainer); | |
//set colors | |
setCSS(lineContainer, { display: (elements.buttons[method].toggled ? 'none' : '') }); | |
setLineStyle(lineContainer, method, message); | |
//set binds | |
if (options.browserinfo.evtLstn) { | |
msgContainer.addEventListener('click', toggleDetails, false); | |
} else { | |
msgContainer.attachEvent('onclick', toggleDetails); | |
} | |
//store the lines in the object corresponding to the method used | |
elements.lines[method].push(lineContainer); | |
//handle grouping (group and groupEnd | |
if (options.paddingLeft !== 0) { | |
setCSS(leftContainer, {paddingLeft: options.paddingLeft}); | |
setCSS(msgContainer, {borderLeft: '1px solid #808080', paddingLeft: '5px'}); | |
} | |
//add the line to the table | |
elements.table.appendChild(lineContainer); | |
} | |
//scroll | |
consoleElement.toggleScroll(); | |
//========================================================== | |
//make sure we still call the original method, if applicable (not window.onerror) | |
if (typeof arguments[1].original === 'function') { | |
arguments[1].original.apply(console, arguments[1].originalArguments); | |
} | |
} catch (e) { | |
//not logging. why? throw error | |
if (options.browserinfo.isMobile) { alert(e); } | |
originalConsole.error('mobileConsole generated an error logging this event! (type: ' + typeof message + ')'); | |
originalConsole.error(arguments); | |
originalConsole.error(e); | |
//try to re-log it as an error | |
newConsole('error', e); | |
} | |
} | |
function interceptConsole(method) { | |
var original = console ? console[method] : missingMethod(), i, stackTraceOrig; | |
if (!console) { console = {}; } //create empty console if we have no console (IE?) | |
console[method] = function () { | |
var args = Array.prototype.slice.call(arguments); | |
args.original = original; | |
args.originalArguments = arguments; | |
args.newMessage = (method === 'assert') ? [args[0], args[1]] : args[0]; | |
//create an Error and get its stack trace and format it | |
try { throw new Error(); } catch (e) { stackTraceOrig = e.stack; } | |
args.newStackTrace = formatStackTrace(args.newStackTrace, stackTraceOrig); | |
if (method === 'clear') { | |
try { | |
elements.table.innerHTML = ''; | |
} catch (e) { | |
console.log('This browser does not allow clearing tables, the console cannot be cleared.'); | |
return; | |
} | |
history.output.prevMethod = ''; | |
i = options.methods.length; | |
while (i--) { | |
elements.lines[options.methods[i]] = []; | |
} | |
options.groupDepth = 0; | |
options.paddingLeft = 0; | |
console.log(messages.clear); | |
originalConsole.clear(); | |
return; | |
} | |
//Handle the new console logging | |
newConsole(method, args); | |
}; | |
} | |
//init | |
function init() { | |
//Intercept all original console methods including trace. Register the event type as a line type. | |
var i = options.methods.length; | |
while (i--) { | |
elements.lines[options.methods[i]] = []; | |
interceptConsole(options.methods[i]); | |
} | |
//Bind to window.onerror | |
window.onerror = function() { | |
var args = Array.prototype.slice.call(arguments); | |
args.newMessage = args[0]; | |
args.newStackTrace = formatStackTrace(arguments); | |
newConsole('error', args); | |
}; | |
return { | |
//nothing to expose | |
}; | |
} | |
//return | |
if (!ref) { | |
ref = init(); | |
} | |
return ref; | |
} | |
//initialize the console commandline | |
function initCommandLine() { | |
//reference | |
var ref; | |
//sub helpers | |
function getFromArrayById(id) { | |
var pos = elements.acItems.map(function(x) {return x.id; }).indexOf(id); | |
return { | |
position: pos, | |
element: (pos !== -1) ? elements.acItems[pos] : undefined | |
}; | |
} | |
function findInArray(array, match) { | |
return array.filter(function(item, index, self){ | |
return (typeof item === 'string' && item.indexOf(match) > -1) && (index === self.indexOf(item)); | |
}); | |
} | |
//core | |
function assemble() { | |
elements.consoleinput.setAttribute('type', 'text'); | |
elements.consoleinput.setAttribute('autocapitalize', 'off'); | |
elements.consoleinput.setAttribute('autocorrect', 'off'); | |
elements.autocompleteItem.setAttribute('href', '#'); | |
elements.gt.innerHTML = '>'; | |
elements.input.appendChild(elements.gt); | |
elements.input.appendChild(elements.consoleinput); | |
elements.input.appendChild(elements.autocomplete); | |
elements.base.appendChild(elements.input); | |
return elements.base; | |
} | |
function submitCommand(command) { | |
if (command !== '') { | |
storeCommand(command); | |
var result; | |
try { | |
result = eval.call(window, command.trim()); | |
console.log.call(window, result); | |
} catch(e) { | |
console.error(e.message); | |
} finally { | |
elements.consoleinput.value = ''; | |
} | |
} | |
} | |
function hoverAutoComplete(e) { | |
if (typeof e === 'undefined') { return; } | |
//unset any already hovered elements | |
var hovered = getFromArrayById('hover').element, target = e.target || e.srcElement, over; | |
if (typeof hovered !== 'undefined') { | |
setCSS(hovered, { | |
color: '', | |
backgroundColor: '' | |
}).id = ''; | |
} | |
if (e.type === 'mouseover') { | |
status.acHovered = true; | |
over = true; | |
} else { | |
over = false; | |
} | |
setCSS(target, { | |
color: over ? '#FFFFFF' : '', | |
backgroundColor: over ? 'rgb(66,139,202)' : '' | |
}).id = over ? 'hover' : ''; | |
} | |
function toggleAutoComplete(show) { | |
var hidden = (elements.autocomplete.currentStyle ? elements.autocomplete.currentStyle.display : window.getComputedStyle(elements.autocomplete, null).display) === 'none'; | |
show = (typeof show === 'undefined') ? hidden : show; | |
setCSS(elements.autocomplete, {display: (show) ? 'inherit' : 'none'}); | |
status.acActive = show; | |
if (!show) { status.acHovered = false; } | |
} | |
function clickAutoComplete(e) { | |
if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } | |
var tgt = e.target || e.srcElement; | |
elements.consoleinput.value = tgt.innerHTML; | |
elements.consoleinput.focus(); | |
toggleAutoComplete(); | |
} | |
function autoComplete(command) { | |
if (command.length < 1) { | |
toggleAutoComplete(false); | |
return; | |
} | |
var searchString = encodeURI(command), matches, match, row, i, maxAmount = options.browserinfo.isMobile ? 3 : 5; | |
elements.autocomplete.innerHTML = ''; | |
elements.acItems = []; | |
matches = findInArray(history.input.commands, searchString); | |
matches = matches.slice(Math.max(matches.length - maxAmount, 0)); | |
i = matches.length; | |
while (i--) { | |
match = decodeURI(matches[i]); | |
row = elements.autocompleteItem.cloneNode(false); | |
row.innerHTML = match; | |
row.onmouseover = hoverAutoComplete; | |
elements.autocomplete.insertBefore(row, elements.autocomplete.firstChild); | |
elements.acItems.unshift(row); | |
} | |
toggleAutoComplete(matches.length > 0); | |
} | |
function setBinds() { | |
if (options.browserinfo.evtLstn) { | |
elements.autocomplete.addEventListener('click', clickAutoComplete, false); | |
} else { | |
elements.autocomplete.attachEvent('onclick', clickAutoComplete); | |
} | |
document.onkeydown = function (e) { | |
e = e || window.event; | |
var tgt = e.target || e.srcElement; | |
if (tgt === elements.consoleinput) { | |
if ((e.key === 'Enter' || e.keyCode === 13)) { //enter | |
if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } | |
if(!status.acHovered) { | |
submitCommand(elements.consoleinput.value); | |
} else { | |
elements.consoleinput.value = getFromArrayById('hover').element.innerHTML; | |
elements.consoleinput.focus(); | |
} | |
toggleAutoComplete(false); | |
status.acInput = ''; | |
} else if ((e.keyCode === 38 || e.keyCode === 40)) { //up and down arrows for history browsing | |
if (e.preventDefault) { e.preventDefault(); } else { e.returnValue = false; } | |
var up = (e.keyCode === 40); | |
if(status.acActive) { | |
//autocomplete window is opened | |
//get id of currently hovered element | |
var hovered = getFromArrayById('hover').position; | |
var counter = (hovered === -1) ? elements.acItems.length : hovered; | |
//hover new (in- or decreased number) one | |
counter = valBetween((counter += (up) ? 1 : -1), 0, elements.acItems.length - 1); | |
hoverAutoComplete({target : elements.acItems[counter], type : 'mouseover'}); | |
} else { | |
//autocompete window not opened | |
var hist = history.input.commands; | |
history.input.commandIdx += (up) ? 1 : -1; | |
history.input.commandIdx = valBetween(history.input.commandIdx, 0, hist.length); | |
elements.consoleinput.value = typeof hist[history.input.commandIdx] === 'undefined' ? '' : decodeURI(hist[history.input.commandIdx]); | |
} | |
} | |
} | |
if (e.keyCode === 27 && status.acActive) { | |
toggleAutoComplete(false); | |
} | |
}; | |
document.onkeyup = function (e) { | |
e = e || window.event; | |
var tgt = e.target || e.srcElement; | |
if (tgt === elements.consoleinput && status.acInput !== elements.consoleinput.value && (e.keyCode !== 38 && e.keyCode !== 40 && e.keyCode !== 27 && e.key !== 'Enter' && e.keyCode !== 13)) { | |
status.acInput = elements.consoleinput.value.trim(); | |
autoComplete(elements.consoleinput.value); | |
} | |
}; | |
} | |
//init | |
function init() { | |
assemble(); | |
setBinds(); | |
return { | |
//nothing to expose | |
}; | |
} | |
//return | |
if (!ref) { | |
ref = init(); | |
} | |
return ref; | |
} | |
function init() { | |
if (!status.initialized) { | |
if (consoleElement && mobileConsole) { | |
console.error( 'Mobile Console cannot be reconstructed! Reload the page to enable Mobile Console again.' + '\n' + | |
'Tip: use the minimize button instead of closing.' ); | |
return; | |
} else { | |
status.initialized = true; | |
//populate references | |
if (!mobileConsole) { | |
//taps into native console and adds new functionality | |
mobileConsole = initConsole(); | |
} | |
if (!consoleElement && mobileConsole) { | |
//creates the new HTML console element and attaches it to document | |
consoleElement = initConsoleElement(); | |
} | |
if (!commandLine && consoleElement && mobileConsole) { | |
//creates an HTML commandline and attaches it to existing console element | |
commandLine = initCommandLine(); | |
} | |
} | |
} | |
//log a 'welcome' message | |
console.info( '--==## Mobile Console v' + options.version + ' ' + (status.initialized ? 'active' : 'inactive' ) + ' ##==--' ); | |
} | |
//autorun if mobile | |
if (options.browserinfo.isMobile || options.overrideAutorun) { | |
init(); | |
} | |
//expose the mobileConsole's methods | |
return { | |
init : init, | |
about: about, | |
toggle : toggleHeight, | |
status : status, | |
options : options | |
}; | |
}()); |
Hello,
I am trying to use this with AR.js but it seems stop it from working. Any idea how to handle this cause it would be great to use for debugging.
Thanks,
jrDev
I have no experience with AR.js, but if my script breaks it, I take it that it throws errors you can see/trace on a desktop browser (using the regular console)
Hello,
I am trying to use this with AR.js but it seems stop it from working. Any idea how to handle this cause it would be great to use for debugging.
Thanks,
jrDevI have no experience with AR.js, but if my script breaks it, I take it that it throws errors you can see/trace on a desktop browser (using the regular console)
I’ll check and get back to you, I appreciate the reply!
Thanks,
jrDev
it has error when run html on webview
Uncaught SecurityError: Failed to read the 'sessionStorage' property from 'Window': Access is denied for this document. -- From line 281 of about:blank
my code:
`webView = (WebView) findViewById(R.id.webView1);
webView.getSettings().setJavaScriptEnabled(true);
webView.getSettings().setAllowFileAccess(true);
webView.getSettings().setLoadWithOverviewMode(true);
webView.getSettings().setUseWideViewPort(true);
webView.getSettings().getDisplayZoomControls();
webView.getSettings().setDomStorageEnabled(true);
webView.getSettings().setDatabaseEnabled(true);
webView.getSettings().setBuiltInZoomControls(true);
// Create database folder
// String databasePath = webView.getContext().getDir("databases", Context.MODE_PRIVATE).getPath();
// webView.getSettings().setDatabasePath(databasePath);//
// webView.getContext().getPackageName() + "/databases/";
String cache_path = getApplicationContext().getFilesDir().getAbsolutePath() + "/cache";
webView.getSettings().setAppCacheEnabled(true);
webView.getSettings().setSaveFormData(true);
webView.getSettings().setAppCachePath(cache_path);
fragmentSave = MainActivity.getActivity().getFragmentSave();
setInitialConfiguration();
// LOADING THE HTML DOCUMENT
String js = loadAssetTextAsString("hnl.mobileConsole.js").toString();//loadAssetTextAsString("3DObjectMaker.htm");
String fragmentHTML = fragmentSave.getTabEditor().getText().toString();//loadAssetTextAsString("3DObjectMaker.htm");
int enbody = fragmentHTML.indexOf("</body>");
String resultHTML = "";
if( enbody >= 0 ) {
String resultHTML_begin = fragmentHTML.substring( 0, enbody);
String resultHTML_end = fragmentHTML.substring( enbody);
resultHTML = resultHTML_begin+"<h1>Sao ko được</h1><script>"+js+ "</script>"+
" <script>alert('sbc'); window.onload = function() { if (!mobileConsole.status.initialized) {" +
" mobileConsole.init();" +
" } };</script>"
+resultHTML_end;
} else {
resultHTML = fragmentHTML+"<h1>Sao ko được</h1><script>"+js+ "</script>"+
" <script>alert('sbc'); window.addEventListener('load', function() {" +
"if (!mobileConsole.status.initialized) { " +
"alert('111');" +
" mobileConsole.init(); " +
" }; " +
"}, false); </script>";
}
webView.loadDataWithBaseURL(null, resultHTML, "text/html", "utf-8", null);`
and AndroidManifest.xml:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" /> <uses-permission android:name="android.permission.CLEAR_APP_CACHE" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.DELETE_CACHE_FILES" tools:ignore="ProtectedPermissions" /> <uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Hi vietnux, thanks for your feedback!
The error you are experiencing is casused by the "Block third-party cookies and site data" setting for the website in question. See: https://www.chromium.org/for-testers/bug-reporting-guidelines/uncaught-securityerror-failed-to-read-the-localstorage-property-from-window-access-is-denied-for-this-document
I'll see if I can write a check for it (to see if sessionstorage is available) some time later, to circumvent the error, but it sounds like an edge-case to me.
hello when i run this script in my webapp i get this error:
Html Webpack Plugin:
Error: /Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:284
})('docReady', window);
-----------------^
ReferenceError: window is not defined
-
index.html:284 Object../src/hnl.MobileConsole.js
/Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:284:18 -
index.html:1878 webpack_require
/Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:1878:41 -
index.html:1850 Object../node_modules/html-webpack-plugin/lib/loader.js!./src/index.html
/Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:1850:34 -
index.html:1878 webpack_require
/Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:1878:41 -
index.html:1888
/Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:1888:18 -
index.html:1889
/Users/ymoukhli/Desktop/webar/web_ar_playground/src/index.html:1889:12 -
node:vm:139 Script.runInContext
node:vm:139:12 -
index.js:121 HtmlWebpackPlugin.evaluateCompilationResult
[web_ar_playground]/[html-webpack-plugin]/index.js:121:28 -
index.js:297
[web_ar_playground]/[html-webpack-plugin]/index.js:297:26 -
async Promise.all
Hi Youssef,
You are probably building a webapp using something like React or NodeJs. Webapps are not browsers, so they dont have a window
available. As MobileConsole relies on some window events/methods (like the original console
, which is an integral part of window
), I'm afraid it just won't work without some rewriting, sorry.
Really nice but why a gist instead of a repo?
Really nice but why a gist instead of a repo?
Good question. Couple of reasons: 1) no experience setting up a repo 2) no time to handle all issues 3) not time to maintain it
Hello,
I am trying to use this with AR.js but it seems stop it from working. Any idea how to handle this cause it would be great to use for debugging.
Thanks,
jrDev