FastJSLogger

Intercepts console.log calls to display log messages in a div floating on the page. Tries to be a replacement for normal browser Error Consoles (which can be a little slow to open).

  1. // ==UserScript==
  2. // @name FastJSLogger
  3. // @namespace FastJSLogger
  4. // @description Intercepts console.log calls to display log messages in a div floating on the page. Tries to be a replacement for normal browser Error Consoles (which can be a little slow to open).
  5. // @include *
  6. // But this script appears to break Facebook somewhat
  7. // @exclude *facebook.com/*
  8. // @version 1.2.8
  9. // @grant none
  10. // ==/UserScript==
  11.  
  12.  
  13. (function(){
  14.  
  15. // You may configure FastJSLogger before loading it, by putting options in
  16. // window.FJSL = { startHidden: false, displaySearchFilter: true };
  17.  
  18. // var FJSL = {};
  19. if (!window.FJSL) {
  20. window.FJSL = {};
  21. }
  22. var FJSL = window.FJSL;
  23.  
  24. // @concern Don't double-load
  25. // I haven't actually had any problems from double loading, but it seems silly to have two loggers.
  26. if (FJSL.loaded) {
  27. console.log("FJSL refusing to load a second time.");
  28. return;
  29. }
  30. FJSL.loaded = true;
  31.  
  32. FJSL.defaults = {
  33. autoHide: true, // hides some time after displaying, even if you are focused on it!
  34. startHidden: true,
  35. logTimeouts: false,
  36. logEvents: false, // Log any activity over the wrapped listeners.
  37. logCommonMouseEvents: false, // These can be triggered a lot!
  38. logChangesToGlobal: true, // Logs any new properties which are added to window
  39.  
  40. watchWindowForErrors: true, // Catches and reports DOM error events, including syntax errors from scripts loaded later, and missing images. BUG: It can hide the line number from Chrome's console - sometimes worth disabling to get that back.
  41. interceptTimeouts: true, // Wraps calls to setTimeout, so any errors thrown may be reported.
  42. interceptEvents: true, // Wraps any listeners registered later, so errors can be caught and reported.
  43.  
  44. displaySearchFilter: false, // This messes up the size of the textbox on Firefox and Konqueror but not Chrome.
  45.  
  46. // Hack to avoid infinite loops. Disabled since they have stopped! :)
  47. // Mute console.log messages repeated to the browser console.
  48. // (Display them only in the fastlogger, keep the browser log clear for
  49. // warnings, errors and info messages.)
  50. // TODO: Similarly, we may want to mute log messages in the FJSL, and only
  51. // show info/warn/errors.
  52. muteLogRepeater: 0,
  53.  
  54. logNodeInsertions: false, // Watch for and report DOMNodeInserted events.
  55.  
  56. bringBackTheStatusBar: true // TODO: add fake statusbar div, watch window.status for change?
  57. };
  58.  
  59. for (var k in FJSL.defaults) {
  60. if (FJSL[k] === undefined) {
  61. FJSL[k] = FJSL.defaults[k];
  62. }
  63. }
  64.  
  65. if (this.localStorage) {
  66. // TODO: Recall and store FJSL preferences in localStorage.
  67. // UNWANTED: During testing, I am toggling this setting.
  68. // FJSL.interceptTimeouts = !Boolean(localStorage['fastjslogger_interceptTimeouts']);
  69. localStorage['fastjslogger_interceptTimeouts'] = FJSL.interceptTimeouts;
  70. }
  71.  
  72.  
  73.  
  74. // This fails to intercept GM_log calls from other userscripts.
  75. // However it intercepts everything when we run bookmarklets.
  76.  
  77. // Partly DONE: We may be able to capture errors by overriding setTimeout,
  78. // setInterval, XMLHttpRequest and any other functions which use callbacks,
  79. // with our own versions which attempt the callback within a try-catch which
  80. // logs and throws any intercepted exceptions.
  81. // Unfortunately wrapping a try-catch around the main scripts in the document
  82. // (which have alread run) is a bit harder from here. ;)
  83. // Aha! Turns out we can catch those errors with window.onerror!
  84.  
  85. // TODO: No thanks to Wikipedia's pre styling(?) I can't set a good default, so we need font zoom buttons!
  86. // TODO: Add ability to minimize but popup again on new log message.
  87. // TODO: Report file/line-numbers where possible. (Split output into table? :E)
  88. // TODO: Option to watch for new globals.
  89. // TODO: Options to toggle logging of any intercepted setTimeouts, XMLHttpRequest's etc, in case the reader is interested.
  90.  
  91. // TESTING: all DOM events! TODO: XHR.
  92. // TESTING: We could even put intercepts on generic functions, in case an error occurs inside them, in a context which we had otherwise failed to intercept.
  93.  
  94. // TODO: If the FJSL is wanted for custom logging, not normal console.log
  95. // logging, we will need to expose a function (FJSL.log()?), and *not*
  96. // intercept console.log.
  97.  
  98. // TODO: Perhaps instead of creating a new console, we should just replace
  99. // functions in the existing one (and leave anything we haven't altered
  100. // intact).
  101.  
  102. // TODO: See https://github.com/h5bp/html5-boilerplate/blob/master/js/plugins.js
  103. // for more log actions.
  104.  
  105. var loadFJSL = function(){
  106.  
  107.  
  108.  
  109. // I did have two running happily in parallel (Chrome userscript and page
  110. // script) but it is rarely useful. Perhaps we should close the older one?
  111. if (document.getElementById("fastJSLogger") != null) {
  112. return;
  113. }
  114.  
  115.  
  116.  
  117. // GUI creation library functions
  118.  
  119. function addStyle(css) {
  120. // Konqueror 3.5 does not act on textValue, it requires textContent.
  121. // innerHTML doesn't work for selectors containing '>'
  122. var st = newNode("style", { type: "text/css", innerHTML: css } );
  123. document.getElementsByTagName('head')[0].appendChild(st);
  124. }
  125.  
  126. function newNode(tag,data) {
  127. var elem = document.createElement(tag);
  128. if (data) {
  129. for (var prop in data) {
  130. elem[prop] = data[prop];
  131. }
  132. }
  133. return elem;
  134. }
  135.  
  136. function newSpan(text) {
  137. return newNode("span",{textContent:text});
  138. }
  139.  
  140. function addCloseButtonTo(elem) {
  141.  
  142. var b = document.createElement("button");
  143. b.textContent = "X";
  144. b.style.zIndex = 2000;
  145.  
  146. b.onclick = function() {
  147. //GM_log("[",b,"] Destroying:",elem);
  148. elem.style.display = 'none';
  149. elem.parentNode.removeChild(elem);
  150. };
  151.  
  152. b.style.float = 'right'; // BUG: not working in FF4!
  153. elem.appendChild(b);
  154.  
  155. }
  156.  
  157.  
  158.  
  159. // == Main Feature - Present floating popup for log messages ==
  160.  
  161. // Cleaner to expose the div via id? It may have been removed from DOM!
  162. // We could fetch logDiv from FJSL when needed?
  163. var logDiv = null;
  164. var logContainer = null;
  165.  
  166. var autoHideTimer = null;
  167.  
  168. var oldSetTimeout = window.setTimeout;
  169.  
  170. function createGUI() {
  171.  
  172. var css = "";
  173. css += " .fastJSLogger { position: fixed; right: 8px; top: 8px; width: 40%; /*max-height: 90%; height: 320px;*/ background-color: #333; color: white; border: 1px solid #666; border-radius: 5px; padding: 2px 4px; z-index: 10000; } ";
  174. css += " .fastJSLogger > span { max-height: 10%; padding: 0 0.3em; }";
  175. css += " .fastJSLogger > .fjsl-title { font-weight: bold; }";
  176. // css += " .fastJSLogger > pre { max-height: 90%; overflow: auto; }";
  177. //// On the pre, max-height: 90% is not working, but specifying px does.
  178. var maxHeight = window.innerHeight * 0.8 | 0;
  179. css += " .fastJSLogger > pre { max-height: "+maxHeight+"px; overflow: auto; word-wrap: break-word; }";
  180. css += " .fastJSLogger > pre { padding: 0px; margin: 0.4em 0.2em; }";
  181. // Must set the colors again, in case the page defined its own bg or fg color for pres that conflicts ours.
  182. css += " .fastJSLogger > pre { /* background-color: #ffffcc; */ background-color: #888; color: black; }";
  183. css += " .fastJSLogger > pre { font-family: Sans; }";
  184. // css += " .fastJSLogger > pre > input { width: 100%, background-color: #888888; }";
  185. css += " .fastJSLogger > pre > div { border-top: 1px solid #888; padding: 0px 2px; ";
  186. css += " white-space: normal; word-break: break-all; }";
  187. css += " .fastJSLogger > pre > .log { background-color: #eee; color: #555; }";
  188. css += " .fastJSLogger > pre > .info { background-color: #aaf; }";
  189. css += " .fastJSLogger > pre > .warn { background-color: #ff4; }";
  190. css += " .fastJSLogger > pre > .error { background-color: #f99; }";
  191. css += " .fastJSLogger { opacity: 0.1; transition: opacity 1s ease-out; } ";
  192. css += " .fastJSLogger:hover { opacity: 1.0; transition: opacity 400ms ease-out; } ";
  193. css += " .fastJSLogger.notifying { opacity: 1.0; transition: opacity 200ms ease-out; } ";
  194. if (document.location.host.match(/wikipedia/))
  195. css += " .fastJSLogger > pre { font-size: 60%; }";
  196. else
  197. css += " .fastJSLogger > pre { font-size: 70%; } ";
  198. addStyle(css);
  199.  
  200. // Add var before logDiv to break hideLogger()!
  201. logDiv = newNode("div",{ id: 'fastJSLogger', className: 'fastJSLogger' });
  202. if (FJSL.startHidden) {
  203. hideLogger();
  204. }
  205. document.body.appendChild(logDiv);
  206.  
  207. // logDiv.style.position = 'fixed';
  208. // logDiv.style.top = '20px';
  209. // logDiv.style.right = '20px';
  210.  
  211. // @todo refactor
  212. // I/O: logDiv, logContainer
  213.  
  214. var heading = newSpan("FastJSLogger");
  215. heading.className = "fjsl-title";
  216. logDiv.appendChild(heading);
  217.  
  218. // addCloseButtonTo(logDiv);
  219.  
  220. var closeButton = newSpan("[X]");
  221. closeButton.style.float = 'right';
  222. closeButton.style.cursor = 'pointer';
  223. closeButton.style.paddingLeft = '5px';
  224. closeButton.onclick = function() { logDiv.parentNode.removeChild(logDiv); };
  225. logDiv.appendChild(closeButton);
  226.  
  227. var logContainer = newNode("pre");
  228.  
  229. var rollupButton = newSpan("[--]");
  230. rollupButton.style.float = 'right';
  231. rollupButton.style.cursor = 'pointer';
  232. rollupButton.style.paddingLeft = '10px';
  233. rollupButton.onclick = function() {
  234. if (logContainer.style.display == 'none') {
  235. logContainer.style.display = '';
  236. rollupButton.textContent = "[--]";
  237. } else {
  238. logContainer.style.display = 'none';
  239. rollupButton.textContent = "[+]";
  240. }
  241. };
  242. logDiv.appendChild(rollupButton);
  243.  
  244. if (FJSL.displaySearchFilter) {
  245. function createSearchFilter(logDiv,logContainer) {
  246. var searchFilter = document.createElement("input");
  247. searchFilter.type = 'text';
  248. searchFilter.style.float = 'right';
  249. searchFilter.style.paddingLeft = '5px';
  250. searchFilter.onchange = function(evt) {
  251. var searchText = this.value;
  252. // console.log("Searching for "+searchText);
  253. var logLines = logContainer.childNodes;
  254. for (var i=0;i<logLines.length;i++) {
  255. if (logLines[i].textContent.indexOf(searchText) >= 0) {
  256. logLines[i].style.display = '';
  257. } else {
  258. logLines[i].style.display = 'none';
  259. }
  260. }
  261. };
  262. return searchFilter;
  263. }
  264. var searchFilter = createSearchFilter(logDiv,logContainer);
  265. // logDiv.appendChild(document.createElement("br"));
  266. logDiv.appendChild(searchFilter);
  267. }
  268.  
  269. logDiv.appendChild(logContainer);
  270.  
  271. return [logDiv,logContainer];
  272.  
  273. }
  274.  
  275. // @parameter channel String
  276. // @parameter args Array<*>
  277. // Do not pass more than two parameters; addToFastJSLog will ignore any extra.
  278. function addToFastJSLog(channel, args) {
  279.  
  280. // Make FJSL visible if hidden
  281. showLogger();
  282.  
  283. if (logContainer) {
  284.  
  285. var out = "";
  286. for (var i=0; i<args.length; i++) {
  287. var obj = args[i];
  288. var str = "" + obj;
  289. if (obj && obj.constructor === "Array") {
  290. str = "[" + obj.map(showObject).join(", ") + "]";
  291. }
  292. // Non-standard: inform type if toString() is dull.
  293. if (str === "[object Object]") {
  294. str = "";
  295. if (typeof obj !== 'object') {
  296. // Surely this is incredibly unlikely! Apart from the obvious case of a String.
  297. str += "(" + (typeof obj) + ")";
  298. }
  299. if (obj.constructor && obj.constructor.name) {
  300. str = "[" + obj.constructor.name + "]";
  301. if (obj.constructor.name === "Object") {
  302. str = "";
  303. // Because showObject will give it {}s, this is implied.
  304. // Although Chrome's console does actually print: "Object { ... }"
  305. }
  306. }
  307. str += showObject(obj);
  308. } else {
  309. }
  310. str = shortenString(str);
  311. var gap = (i>0?' ':'');
  312. out += gap + str;
  313. }
  314.  
  315. var d = document.createElement("div");
  316. d.className = channel;
  317. d.textContent = out;
  318.  
  319. if (logContainer.childNodes.length >= 1000) {
  320. logContainer.removeChild(logContainer.firstChild);
  321. }
  322.  
  323. logContainer.appendChild(d);
  324.  
  325. // Scroll to bottom
  326. // TODO: This is undesirable if the scrollbar was not already at the bottom, i.e. the user has scrolled up manually and is trying to read earlier log entries!
  327. logContainer.scrollTop = logContainer.scrollHeight;
  328.  
  329. }
  330.  
  331. if (autoHideTimer !== null) {
  332. clearTimeout(autoHideTimer);
  333. autoHideTimer = null;
  334. }
  335. if (FJSL.autoHide) {
  336. // Never log this setTimeout! That produces an infinite loop!
  337. autoHideTimer = oldSetTimeout(hideLogger,15 * 1000);
  338. }
  339.  
  340. return d;
  341.  
  342. }
  343.  
  344. function debounce(duration, fn) {
  345. var timer = null;
  346. return function() {
  347. clearTimeout(timer);
  348. timer = setTimeout(fn, duration);
  349. };
  350. }
  351.  
  352. var clearOpacity = debounce(3000, function(){
  353. //logDiv.style.opacity = '';
  354. logDiv.className = logDiv.className.replace(/(^|\s)notifying(\s|$)/, '');
  355. });
  356.  
  357. function showLogger() {
  358. if (logDiv) {
  359. // logDiv.style.display = '';
  360. //// BUG: Transition is not working! Perhaps it only works when switching between CSS classes.
  361. // logDiv.style._webkit_transition_property = 'opacity';
  362. // logDiv.style._webkit_transition_duration = '2s';
  363. //logDiv.style.opacity = 1.0;
  364. if (!logDiv.className.match(/(^|\s)notifying(\s|$)/)) {
  365. logDiv.className += " notifying";
  366. }
  367. clearOpacity();
  368. }
  369. }
  370.  
  371. function hideLogger() {
  372. if (logDiv) {
  373. // logDiv.style.display = 'none';
  374. // logDiv.style._webkit_transition_property = 'opacity';
  375. // logDiv.style._webkit_transition_duration = '2s';
  376. // logDiv.style.opacity = 0.0;
  377. clearOpacity();
  378. }
  379. }
  380.  
  381. var k = createGUI();
  382. logDiv = k[0];
  383. logContainer = k[1];
  384.  
  385. // target.console.log("FastJSLogger loaded this="+this+" GM_log="+typeof this.GM_log);
  386. // console.log("interceptTimeouts is "+(FJSL.interceptTimeouts?"ENABLED":"DISABLED"));
  387.  
  388. // Intercept messages for console.log if it exists.
  389. // Create console.log if it does not exist.
  390.  
  391. var oldConsole = this.console; // When running as a userscript in Chrome, cannot see this.console!
  392. // TODO: We should probably remove all this GM_ stuff.
  393. // We aren't using it even if we do find it.
  394. var oldGM_log = this.GM_log;
  395.  
  396. var target = ( this.unsafeWindow ? this.unsafeWindow : window );
  397.  
  398. // Replace the old console
  399. target.console = {};
  400.  
  401. var lastArgs = [];
  402.  
  403. target.console.log = function(a,b,c) {
  404. // I tried disabling this and regretted it!
  405. // My Console bookmarklet can cause an infloop with FJSL if you want to test it.
  406. var args = Array.prototype.slice.call(arguments, 0);
  407. if (arraysEqual(args, lastArgs)) {
  408. return;
  409. }
  410. lastArgs = args;
  411.  
  412. // Replicate to the old loggers we intercepted (overrode)
  413.  
  414. if (oldConsole && oldConsole.log && !FJSL.muteLogRepeater) {
  415. // Some browsers dislike use of .call and .apply here, e.g. GM in FF4.
  416. // So to avoid "oldConsole.log is not a function":
  417. try {
  418. oldConsole.log.apply(oldConsole,arguments);
  419. } catch (e) {
  420. // Ugly chars to signify that sucky fallback is being used
  421. oldConsole.log("[noapply]",a,b,c);
  422. }
  423. }
  424.  
  425. /*
  426. //// WARNING: *This* is the cause of infinite console.logging, if it has been implemented by FallbackGMAPI.
  427. //// Solution: FBGMAPI should take a reference to console when creating it's GM_log, not waiting until later to look for the console.
  428. if (oldGM_log) {
  429. oldGM_log(a,b,c);
  430. }
  431. */
  432.  
  433. addToFastJSLog("log", arguments);
  434. };
  435.  
  436. //// NOT TESTED. Intercept Greasemonkey log messages. (Worth noting we have disabled calls *to* GM_log above.)
  437. // this.GM_log = target.console.log;
  438.  
  439. // == MAIN SCRIPT ENDS ==
  440. // The rest is all optional so may be stripped for minification.
  441.  
  442.  
  443.  
  444. /* Provide/intercept console.info/warn/error(). */
  445.  
  446. target.console.error = function() {
  447. // We can get away with this in Chrome!
  448.  
  449. //var args = Array.prototype.slice.call(arguments,0);
  450. //args.unshift("[ERROR]");
  451.  
  452. oldConsole.error.apply(oldConsole, arguments);
  453.  
  454. addToFastJSLog("error", arguments); // ,'\n'+getStack(2,20).join("\n"));
  455.  
  456. // Report stacktrace if we were passed an error.
  457. if (arguments[0] instanceof Error) {
  458. //target.console.error("" + arguments[0].stack);
  459. addToFastJSLog("error", "" + arguments[0].stack);
  460. }
  461. };
  462.  
  463. // Could generalise the two functions below:
  464. //interceptLogLevel("warn");
  465. //interceptLogLevel("info");
  466.  
  467. target.console.warn = function() {
  468. //var args = Array.prototype.slice.call(arguments,0);
  469. //args.push(""+getCallerFromStack());
  470.  
  471. oldConsole.warn.apply(oldConsole, arguments);
  472. addToFastJSLog("warn", arguments);
  473.  
  474. //// This was quite useful on one occasion. But not in the presence of lots of warnings!
  475. // logStack(getStackFromCaller());
  476. };
  477.  
  478. target.console.info = function() {
  479. oldConsole.info.apply(oldConsole, arguments);
  480. addToFastJSLog("info", arguments);
  481. };
  482.  
  483. /*
  484. target.console.error = function(e) {
  485. // target.console.log("[ERROR]",e,e.stack);
  486. var newArgs = [];
  487. newArgs.push("[ERROR]");
  488. for (var i=0;i<arguments.length;i++) {
  489. newArgs.push(arguments[i]);
  490. }
  491. try {
  492. newArgs.push("(reported from function "+arguments.callee.caller.name+")");
  493. // console.error("caller = "+arguments.callee.caller);
  494. // console.error("stack = "+e.stack);
  495. } catch (e) {
  496. }
  497. target.console.log.apply(target.console,newArgs);
  498. target.console.log.apply(target.console,["Stacktrace:\n",e.stack]);
  499. };
  500. */
  501.  
  502.  
  503.  
  504. // Some more library functions:
  505.  
  506. function arraysEqual(a, b) {
  507. for (var i=0; i<a.length; i++) {
  508. if (a[i] !== b[i]) {
  509. return false;
  510. }
  511. }
  512. return true;
  513. }
  514.  
  515. function tryToDo(fn, target, args) {
  516. try {
  517.  
  518. return fn.apply(target, args);
  519.  
  520. } catch (e) {
  521. // Actually hard to read!
  522. // var prettyFn = fn; // ("" + fn) .replace(/\n/g,'\\n');
  523. var fnName = fn && fn.name;
  524. if (!fnName) {
  525. fnName = "<anonymous>";
  526. }
  527. // console.log("[ERR]", e, prettyFn);
  528. // console.log("[Exception]", e, "from " + fnName + "()");
  529. if (e.stack) {
  530. console.error("!! " + e.stack);
  531. } else if (e.lineNumber) {
  532. console.error("!! " + e + " on " + e.lineNumber + ")");
  533. } else {
  534. console.error("!!", e);
  535. }
  536. // var prettyFn = ("" + fn).replace(/\n/,/ /, 'g');
  537. var prettyFn = shortenString(fn);
  538. console.error("occurred when calling: " + prettyFn);
  539. // If we rethrow the error, browser devtools will show the throw as coming from here!
  540. // But we should throw it anyway. That is what the function's caller expects.
  541. // At one point we tried to reproduce the error by calling fn again, but not catching it.
  542. // However this is a dangerous tactic. What if fn() creates a setTimeout before throwing its exception? Then calling it again would produce a second setTimeout!
  543. //return fn.apply(target, args);
  544. throw e;
  545. }
  546. }
  547.  
  548. // TODO: Unify showObject and shortenString with replay.js's toDetailedString and toSimpleString.
  549. function showObject(obj) {
  550. return "{ " + Object.keys(obj).map(function(prop) {
  551. return prop + ": " + shortenString(obj[prop]);
  552. }).join(", ") + " }";
  553. }
  554.  
  555. function getXPath(node) {
  556. var parent = node.parentNode;
  557. if (!parent) {
  558. return '';
  559. }
  560. var siblings = parent.childNodes;
  561. var totalCount = 0;
  562. var thisCount = -1;
  563. for (var i=0;i<siblings.length;i++) {
  564. var sibling = siblings[i];
  565. if (sibling.nodeType == node.nodeType) {
  566. totalCount++;
  567. }
  568. if (sibling == node) {
  569. thisCount = totalCount;
  570. break;
  571. }
  572. }
  573. return getXPath(parent) + '/' + node.nodeName.toLowerCase() + (totalCount>1 ? '[' + thisCount + ']' : '' );
  574. }
  575.  
  576. function getStack(drop,max) {
  577. var stack;
  578. try {
  579. throw new Error("Dummy for getStack");
  580. } catch (e) {
  581. stack = e.stack.split('\n').slice(drop).slice(0,max);
  582. }
  583. return stack;
  584. }
  585.  
  586. // What frame/function called the function which called us?
  587. function getCallerFromStack() {
  588. return getStack(4,1);
  589. }
  590.  
  591. // The frame/function which called our caller, and all above it.
  592. function getStackFromCaller() {
  593. return getStack(4,-1);
  594. }
  595.  
  596. function logStack(stack) {
  597. for (var i=0;i<stack.length;i++) {
  598. console.log(""+stack[i]);
  599. }
  600. }
  601.  
  602.  
  603.  
  604. // == Error Interceptors ==
  605.  
  606. // Whenever a callback is placed, we should wrap it!
  607. // DONE:
  608. // event listeners (added after we run)
  609. // setTimeout
  610. // TODO:
  611. // setInterval
  612. // XMLHttpRequest
  613.  
  614. // We could even wrap any standard functions which are common sources of
  615. // Errors, in case we fail to catch them any other way.
  616.  
  617. if (FJSL.watchWindowForErrors) {
  618. // Registers a window.onerror event handler, which catches DOM Errors like
  619. // img elements which failed to load their src resource.
  620. function handleError(evt) {
  621.  
  622. if (!FJSL.firstWindowErrorEvent) {
  623. FJSL.firstWindowErrorEvent = evt;
  624. }
  625. //// Expose this event for inspection
  626. FJSL.lastWindowErrorEvent = evt;
  627. // console.log("Error caught by FJSL:");
  628. // console.log(evt);
  629. // console.log(Object.keys(evt));
  630. // target.console.error(evt.filename+":"+evt.lineno+" "+evt.message+" ["+evt.srcElement+"]",evt);
  631.  
  632. // In fact repeating these to the browser log is redundant, since the
  633. // browser is likely to report these errors anyway.
  634. // Therefore, we could use addToFastJSLog instead of console.log below.
  635.  
  636. // Chrome sometimes gives us a message, sometimes doesn't.
  637. if (evt.message) {
  638. // This is what Chrome provides for an error thrown by a page script.
  639. // In Chrome this event object contains neither an Error nor a stack-trace.
  640. // Also the current stack-trace is uninformative (see nothing about the call to handleError.)
  641. // So we don't use the following: , evt, getStack(0,99).join('\n')
  642. console.error("[Caught Error] "+evt.message+" "+evt.filename+":"+evt.lineno);
  643. } else {
  644. var report = '[Caught Unknown Error] ';
  645. report += "type="+evt.type+" ";
  646.  
  647. // For some errors neither Firefox nor Chrome give us a message.
  648. // But sometimes we can peek into the element that fired the error.
  649. // If it was an image, then its src attribute may be useful.
  650. var elem = evt.srcElement || evt.target;
  651. if (elem) {
  652. report += "From element "+elem+" ";
  653. try {
  654. report += "with src="+elem.src+" ";
  655. } catch (e) { }
  656. }
  657.  
  658. /*
  659. if (elem == window) {
  660. FJSL.lastEE = evt;
  661. }
  662. */
  663.  
  664. /*
  665. if (!elem) {
  666. // Firefox 14 was providing only a constructor function in the event,
  667. // so I wonder if calling it will reproduce the error for us. Either
  668. // that, or it will try to construct an event object. :P
  669. // I will probably remove this if I see it again and confirm that it's useless.
  670. // I did get a HUGE string from Firefox which was pretty interesting.
  671. // But it's not what we were looking for.
  672. if (typeof evt.constructor == "function") {
  673. try {
  674. var result = evt.constructor();
  675. if (result) {
  676. console.error(report + "Constructor result: "+result);
  677. }
  678. } catch (e) {
  679. // I think in Firefox this gives us useful information.
  680. // TODO: But I have seen us reach here in Chrome, with an error "cannot call DOM Object constructor" presumably meaning out evt.constructor() call was forbidden, and we should look elsewhere for information.
  681. report += "(Constructor error: \""+e+"\") ";
  682. // Dirty cheat expose it for dev/debugger:
  683. evt.FJSLconstructorError = e;
  684. // I have come to the conclusion that although they are different, the errors I get in both Firefox and Chrome are errors about the way I have called the constructor, and not any useful error I was after :P
  685. // FF: Timestamp: 29/07/12 18:10:14
  686. // Error: NS_ERROR_XPC_NOT_ENOUGH_ARGS: Not enough arguments
  687. console.log("[Constructor Error]",e);
  688. throw e;
  689. }
  690. }
  691. }
  692. */
  693.  
  694. console.error(report,evt);
  695. }
  696. }
  697. window.addEventListener("error",handleError,true);
  698. // document.body.addEventListener("error",handleError,true);
  699. }
  700.  
  701. // Split into shortenString, escapeString, compressString, and toSimpleString.
  702. function shortenString(s) {
  703. s = ""+s;
  704. s = s.replace(/\n[ \t]+/,'\\n ','g');
  705. s = s.replace(/\n/,'\\n','g');
  706. s = s.replace(/\t/,' ','g');
  707. if (s.length > 202) {
  708. s = s.substring(0,202) + "...";
  709. }
  710. return s;
  711. }
  712.  
  713. if (FJSL.interceptTimeouts) {
  714.  
  715. window.setTimeout = function(fn, ms) {
  716. if (FJSL.logTimeouts) {
  717. // TODO: We used to pass ,fn to log here. That was nice in Chrome, because we can fold it open or closed, but too spammy for plain FJSL. Recommendation: Refactor out prettyShow() fn which will determine browser and provide raw object for chrome or shortened string for noob browsers?
  718. console.info("[EVENT] setTimeout() called: "+ms+", "+shortenString(fn));
  719. }
  720. var wrappedFn = function(){
  721. // setTimeout can be passed a string. But we want a function so we can call it later.
  722. if (typeof fn === 'string') {
  723. var str = fn;
  724. fn = function(){ eval(str); };
  725. }
  726. // CONSIDER: Of dubious value
  727. if (typeof fn !== 'function') {
  728. console.error("[FJSL] setTimeout was not given a function!",fn);
  729. return;
  730. }
  731.  
  732. // We don't really need to return here. setTimeout won't do anything with the returned value.
  733. // Similarly it probably hasn't passed us any arguments. The context object 'this' is probably 'window'.
  734. return tryToDo(fn, this, arguments);
  735. };
  736.  
  737. return oldSetTimeout(wrappedFn, ms);
  738. };
  739.  
  740. }
  741.  
  742.  
  743.  
  744. // == General Info Logging Utilities ==
  745.  
  746. if (FJSL.logNodeInsertions) {
  747. document.body.addEventListener('DOMNodeInserted', function(evt) {
  748. if (evt.target === logContainer || evt.target.parentNode === logContainer) {
  749. return;
  750. }
  751. // console.log("[DOMNodeInserted]: target=",evt.target," event=",evt);
  752. // console.log("[DOMNodeInserted]:",evt,evt.target,"path="+getXPath(evt.target),"html="+evt.target.outerHTML);
  753. console.log("[DOMNodeInserted]: path="+getXPath(evt.target)+" html="+evt.target.outerHTML,evt);
  754. }, true);
  755. }
  756.  
  757. if (FJSL.interceptEvents) {
  758.  
  759. // This store helps us to remove the wrapped event listeners which we add
  760. var wrapped_and_unwrapped = [];
  761.  
  762. var realAddEventListener = HTMLElement.prototype.addEventListener;
  763. // HTMLElement.prototype.oldAddEventListener = realAddEventListener;
  764. HTMLElement.prototype.addEventListener = function(type,handler,capture,other){
  765. if (FJSL.logEvents) {
  766. // Note niceFunc is re-used later, so don't remove this!
  767. var niceFunc = handler.name || (""+handler).substring(0,80).replace(/\n/," ",'g').replace(/[ \t]*/," ",'g')+"...";
  768. /*
  769. console.info("[EVENTS] Listening for "+type+" events on "+getXPath(this)+" with handler "+niceFunc);
  770. console.info(getStack(3,3).join("\n"));
  771. */
  772. }
  773. var newHandler = function(evt) {
  774. if (FJSL.logEvents) {
  775. var isSpammyEvent = ["mousemove","mouseover","mouseout","mousedown","mouseup","keydown","keyup"].indexOf(evt.type) >= 0;
  776. if (!isSpammyEvent || FJSL.logCommonMouseEvents) {
  777. if (evt.target.parentNode == logContainer) {
  778. // Do not log events in the console's log area, such as DOMNodeInserted!
  779. } else {
  780. // console.log("("+type+") on "+evt.target);
  781. // console.info("[EVENT] "+type+" on "+getXPath(evt.target));
  782. // console.log("[EVENT] "+type+" on "+getXPath(evt.target)+" evt="+showObject(evt));
  783. console.info("[EVENT] "+type+" on "+getXPath(evt.target)+" being handled by "+niceFunc);
  784. }
  785. }
  786. }
  787. return tryToDo(handler, this, arguments);
  788. // return handler.apply(this, arguments);
  789. };
  790. wrapped_and_unwrapped.push({
  791. unwrapped: handler,
  792. wrapped: newHandler,
  793. });
  794. return realAddEventListener.call(this,type,newHandler,capture,other);
  795. };
  796.  
  797. // We cannot remove the original handler, because it was never added. Instead we need to remove the wrapper that we did add.
  798. var realRemoveEventListener = HTMLElement.prototype.removeEventListener;
  799. // HTMLElement.prototype.oldRemoveEventListener = realAddEventListener;
  800. HTMLElement.prototype.removeEventListener = function(type,handler,capture,other){
  801. //console.log("removeEventListener was called with:",type,handler);
  802. //try { throw new Error("dummy for stacktrace"); } catch (e) { console.log("At:",e); }
  803. for (var i = wrapped_and_unwrapped.length; i--;) {
  804. var both = wrapped_and_unwrapped[i];
  805. if (handler === both.unwrapped) {
  806. handler = both.wrapped;
  807. wrapped_and_unwrapped.splice(i,1);
  808. break;
  809. }
  810. }
  811. return realRemoveEventListener.call(this,type,handler,capture,other);
  812. };
  813.  
  814. }
  815.  
  816. if (FJSL.logChangesToGlobal) {
  817. function newChangeWatcher() {
  818. var checkSpeed = 4000;
  819. var watcher = {};
  820. var global = window;
  821. var knownKeys = {};
  822. var firstCheck = true;
  823. function checkGlobal() {
  824. if (FJSL.logChangesToGlobal) {
  825. for (var key in global) {
  826. if (knownKeys[key] === undefined) {
  827. if (!firstCheck) {
  828. var obj = global[key];
  829. console.log("[New global detected] "+key+" =",obj);
  830. }
  831. knownKeys[key] = 1;
  832. }
  833. }
  834. firstCheck = false;
  835. }
  836. oldSetTimeout(checkGlobal,checkSpeed);
  837. }
  838. oldSetTimeout(checkGlobal,checkSpeed);
  839. return watcher;
  840. };
  841. var changeWatcher = newChangeWatcher();
  842. }
  843.  
  844.  
  845.  
  846. };
  847.  
  848. if (document.body) {
  849. loadFJSL();
  850. } else {
  851. // doc bod may not exist if we are loaded in the HEAD! Better wait till later...
  852. setTimeout(loadFJSL,1000);
  853. // But we would quite like to start doing stuff now anyway!
  854. // TODO: Load what we can immediately, delay only things we must.
  855. // E.g. start capturing text, even if we can't display it yet.
  856. }
  857.  
  858. })();
  859.  

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址