-
-
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 | |
}; | |
}()); |
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
I’ll check and get back to you, I appreciate the reply!
Thanks,
jrDev