4
\$\begingroup\$

Occasionally, I do some web dev on computers where "inspect" is disabled, which can make debugging JavaScript (a language that is really good at dodging errors) a real pain.

I've created a JavaScript file that when you include it via a script tag on an HTML page, it will rewrite the console handlers and add some elements to the bottom of the page:

Picture of the emulated JavaScript console on the web page

Code:

// https://gist.github.com/UnsignedArduino/e23b8329c3a786d1e4e99d8ee941436e

// Include this JavaScript file in an HTML file and it will add a DIV element to the bottom of the page which will contain console output and
// an textarea to run JavaScript code!

// Set false to do nothing
const on_page_console = true;

if (on_page_console) {
  (() => {
const element_to_append_to = document.body;

element_to_append_to.appendChild(document.createElement("br"));
element_to_append_to.appendChild(document.createElement("br"));
  
const on_page_console_div = document.createElement("div");
on_page_console_div.style.border = "1px outset black";
on_page_console_div.style.padding = "5px";

const warning_b = document.createElement("b");
warning_b.innerHTML = "On page console:";
on_page_console_div.appendChild(warning_b);

element_to_append_to.appendChild(document.createElement("br"));

on_page_console_div.appendChild(document.createElement("br"));

const console_textarea = document.createElement("textarea");
console_textarea.type = "text";
console_textarea.rows = 10;
console_textarea.cols = 40;
console_textarea.id = "command_input";
console_textarea.name = "command_input";
console_textarea.readOnly = true;
console_textarea.style = "width: 100%; resize: vertical; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box;"
on_page_console_div.appendChild(console_textarea);

on_page_console_div.appendChild(document.createElement("br"));

const command_label = document.createElement("label");
command_label.for = "command_input";
command_label.innerHTML = "Run JavaScript code: " + 
                          "(Remember that you can only run code in the context " + 
                          'of <a href="/on_page_console.js"><code>/on_page_console.js</code></a> - see ' + 
                          '<a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts#using_eval_in_content_scripts">here</a>.)';
on_page_console_div.appendChild(command_label);
  
const command_input = document.createElement("textarea");
command_input.type = "text";
command_input.rows = 10;
command_input.cols = 40;
command_input.id = "command_input";
command_input.name = "command_input";
command_input.style = "width: 100%; resize: vertical; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box;"
on_page_console_div.appendChild(command_input);
  
on_page_console_div.appendChild(document.createElement("br"));
  
const command_button = document.createElement("button");
command_button.type = "button";
command_button.innerHTML = "Run";
command_button.onclick = () => {
  console.log("Result: " + eval(command_input.value));
};
on_page_console_div.appendChild(command_button);
  
element_to_append_to.appendChild(on_page_console_div);

const auto_scroll = true;
  
// https://stackoverflow.com/a/50773729/10291933

function produce_text(name, args) {
  return args.reduce((output, arg) => {
    return output + (typeof arg === "object" && (JSON || {}).stringify ? JSON.stringify(arg) : arg) + "\n";
  }, "");
}

function rewire_logging_func(name) {
  console["old" + name] = console[name];
  console[name] = (...arguments) => {
    console_textarea.innerHTML += produce_text(name, arguments);;
    console_textarea.scrollTop = console_textarea.scrollHeight;
    console["old" + name].apply(undefined, arguments);
  };
}

function rewire_logging() {
  rewire_logging_func("log");
  rewire_logging_func("debug");
  rewire_logging_func("warn");
  rewire_logging_func("error");
  rewire_logging_func("info");
}
  
window.onerror = (error_msg, url, line_number, col_number, error) => {
  let error_output;
  if (error.stack == null) {
    error_output = error_msg + "\n  URL: " + url + ":" + line_number + ":" + col_number;
  } else {
    error_output = error.stack;
  }
  console.error(error_output);
  return false;
};
  
rewire_logging();
  })();
}

In case it's needed, here's a Repl.

I would appreciate it if I could get help with (my probably weird) code styles, best practices on how to implement such a thing, and performance. (The page will freeze while executing JavaScript) Note that this was designed with the fact that the server could only serve static files, and not inject HTML and such.

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

Here are my thoughts:

tl;dr: Page freezing might be related to sharing an id between elements. Some other changes might fix readability, depending on your preferences.

  1. Page freezing: I couldn't reproduce this, but I have a guess as to what was causing it. You are sharing an id between the the two text areas - I think you copy/pasted the code for the output text area to reuse as the input text area. You want to avoid reusing id's.
  2. This may be a personal preference, so take it for what it is worth. I have a hard time reading/deciphering html when it is added using create/append. I prefer to write it up as a string and use insertAdjacentHTML at the end. (you can see my approach for this in the code at the end).
  3. I renamed/added a few variables to help with readability and reuse.
  • I changed on_page_console to showCustomConsole. I thought this would make the purpose of the variable/it's use in the if statement a bit more understandable. Now if you look at the if statement, it is obvious what we are checking before we run the encased code.
  • I moved the id's used for the textareas out of the html string and defined them as variables. This made reuse easier, and if you want to change the id to something more meaningful, you only need to do so in 1 spot.
  1. Styling - I'm a bit wishy-washy on this one. I typically try to avoid inline-styling, but you don't have a lot of it happening. I noticed you reused the styling for the two textareas, so I saved those as a string and inserted them into the consoleHtml string as an argument. If you were going to really add bunch of bells & whistles to this, I'd suggest using classes in a style script to better handle styling.

  2. I noticed in your example, you had 'console.log('here') in the input textarea. All you need to do is write "here" and you'll get "Result: here" in the output textarea. If you want to do some math, just type it in (example, 1+1 in the input area will result in "Result: 2" in the output area.). You can even use variables:

    let a = 1

    let b = 2

    a + b

    Result: 3

  3. Template Literals - I prefer these to strings. I think it is easier to read a string using these, especially when you are mashing strings and variables together.

  4. I didn't take apart the functions you built based off of the code from the stack overflow link in the comments. I figured they were mostly self-explanatory. I did however change the console_textarea to console_output, using the id for the element. I thought this was another place where the code was more readable and storing the id in a variable helped avoid any accidental id reuse.

I originally worked through this on codepen.

const showCustomConsole = true
if(showCustomConsole){
  let documentBody = document.body
  let consoleTitle = `On Page Console:`
  let consoleId = 'console_out'
  let consoleLabel = `Run JavaScript code: 
    (Remember that you can only run code in the context 
    of <a href="/on_page_console.js"><code>/on_page_console.js</code></a> - see 
    <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts#using_eval_in_content_scripts">here</a>.)
  `
  let scriptId = 'script_in'
  let textAreaStyle = 'style="width: 100%; resize: vertical; -webkit-box-sizing: border-box; -moz-box-sizing: border-box; box-sizing: border-box;"'
  
  function runHandler(){
    console.log(`Result: ${eval(document.getElementById(scriptId).value)}`)
  }
  
  
  // https://stackoverflow.com/a/50773729/10291933

  function produce_text(name, args) {
    return args.reduce((output, arg) => {
      return output + (typeof arg === "object" && (JSON || {}).stringify ? JSON.stringify(arg) : arg) + "\n";
    }, "");
  }
  
  function rewire_logging_func(name) {
    let console_output = document.getElementById(consoleId)
    console["old" + name] = console[name];
    console[name] = (...arguments) => {
      console_output.innerHTML += produce_text(name, arguments);;
      console_output.scrollTop = console_output.scrollHeight;
      console["old" + name].apply(undefined, arguments);
    };
  }
  
  function rewire_logging() {
    rewire_logging_func("log");
    rewire_logging_func("debug");
    rewire_logging_func("warn");
    rewire_logging_func("error");
    rewire_logging_func("info");
  }
  
  window.onerror = (error_msg, url, line_number, col_number, error) => {
    let error_output;
    if (error.stack == null) {
      error_output = error_msg + "\n  URL: " + url + ":" + line_number + ":" + col_number;
    } else {
      error_output = error.stack;
    }
    console.error(error_output);
    return false;
  };
  
  let consoleHtml = `
    <br><br>
    <div style="border:1px outset black; padding: 5px;">
      <b>${consoleTitle}</b>
      <br>
      <textarea rows="10" cols="40' type="text" id="${consoleId}" readonly style="${textAreaStyle}"></textarea>
      <br>
      <label for="${consoleId}">${consoleLabel}</label>
      <textarea rows="10" cols="40' type="text" id="${scriptId}" style="${textAreaStyle}"></textarea>
      <button onclick="runHandler()">Run</button>
    </div>
  `
  documentBody.insertAdjacentHTML("beforeend", consoleHtml)
  rewire_logging()
}
<head>
  <title>Inspect console directly on a web page</title>
</head>
<body>
  <h3>Sample Web Page</h3>
  <script></script>
</body>

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.