javascripthtmlcssgoogle-chromehtml-editor

Extra div Element in Chrome Developer Tools


I'm using an HTML editor that I built, which you can access here: HTML Editor.

Here's the relevant source code:

const previewer = document.querySelector('iframe'),
  editor = document.querySelector('textarea'),
  previewSelector = document.getElementById('previewSelector'),
  downloadLink = document.getElementById('downloadLink'),
  fontSizeSelector = document.getElementById('fontSizeSelector'),
  runButton = document.getElementById('runButton'),
  fileInput = document.getElementById('fileInput'),
  editorSizeInput = document.getElementById('editorSizeInput'),
  editorWrapper = document.getElementById('editorWrapper'),
  previewerWrapper = document.getElementById('previewerWrapper'),
  editorSizeOutput = document.querySelector('output'),
  verticalViewToggle = document.getElementById('verticalViewToggle'),
  mainContainer = document.querySelector('main'),
  darkEditorToggle = document.getElementById('darkEditorToggle'),
  spellcheckToggle = document.getElementById('spellcheckToggle'),
  footerContainerToggle = document.getElementById('footerContainerToggle'),
  footerContainer = document.querySelector('footer'),
  copyNotification = document.getElementById('copyNotification');
let previewTimeout = null;

function toggleClass(element, className, force) {
  element.classList.toggle(className, force);
}

function preview() {
  previewer.replaceWith(previewer); // a fresh iframe to delete JavaScript variables
  const previewerDocument = previewer.contentDocument;
  previewerDocument.write(editor.value);
  previewerDocument.close();
}

function timedPreview() {
  const previewSelectorValue = previewSelector.value;
  if (previewSelectorValue === 'Instant') {
    preview();
  } else if (previewSelectorValue !== 'Manual') {
    clearTimeout(previewTimeout);
    previewTimeout = setTimeout(preview, previewSelectorValue);
  }
}

function createDownloadURL() {
  const blob = new Blob([editor.value], {
    type: 'text/html; charset=utf-8'
  });
  downloadLink.href = URL.createObjectURL(blob);
}

function resizeFont() {
  editor.style.fontSize = fontSizeSelector.value + 'px';
}

function toggleRunButton() {
  toggleClass(runButton, 'hidden', previewSelector.value !== 'Manual');
}

function resizeEditor() {
  const editorSizeInputValue = editorSizeInput.value;
  editorWrapper.style.flexGrow = editorSizeInputValue;
  previewerWrapper.style.flexGrow = 100 - editorSizeInputValue;
  editorSizeOutput.value = (editorSizeInputValue / 100).toFixed(2);
}

runButton.addEventListener('click', preview);

editor.addEventListener('input', timedPreview);

downloadLink.addEventListener('click', createDownloadURL);
downloadLink.addEventListener('contextmenu', createDownloadURL);

fontSizeSelector.addEventListener('change', resizeFont);

previewSelector.addEventListener('change', toggleRunButton);

editorSizeInput.addEventListener('input', resizeEditor);

document.getElementById('resetButton').addEventListener('click', function() {
  if (!editor.value || editor.value !== editor.defaultValue && confirm('Your input will be lost.\nAre you sure you want to reset?')) {
    fileInput.value = '';
    downloadLink.download = 'template.html';
    editor.value = editor.defaultValue;
    timedPreview();
  }
});

document.getElementById('selectButton').addEventListener('click', function() {
  editor.select();
});

fileInput.addEventListener('change', async function() {
  const file = this.files[0];
  if (file) { // to ensure that there's a file to read so Chrome, for example, doesn't run this function when you cancel choosing a new file
    downloadLink.download = file.name;
    editor.value = await file.text();
    timedPreview();
  }
});

verticalViewToggle.addEventListener('click', function() {
  toggleClass(mainContainer, 'vertical');
  toggleClass(this, 'active');
});

darkEditorToggle.addEventListener('click', function() {
  toggleClass(editor, 'dark');
  toggleClass(this, 'active');
});

spellcheckToggle.addEventListener('click', function() {
  editor.spellcheck = !editor.spellcheck;
  toggleClass(this, 'active');
});

footerContainerToggle.addEventListener('click', function() {
  toggleClass(footerContainer, 'hidden');
  toggleClass(this, 'active');
});

document.getElementById('copyButton').addEventListener('click', function() {
  navigator.clipboard.writeText(location);
  toggleClass(copyNotification, 'hidden');
  setTimeout(toggleClass, 2000, copyNotification, 'hidden');
});

// Save the current state to localStorage.
document.addEventListener('visibilitychange', function() {
  if (document.hidden) {
    const currentState = {
      spellcheckToggle: spellcheckToggle.className,
      verticalViewToggle: verticalViewToggle.className,
      darkEditorToggle: darkEditorToggle.className,
      footerContainerToggle: footerContainerToggle.className,
      previewSelector: previewSelector.value,
      fontSizeSelector: fontSizeSelector.value,
      editorSizeInput: editorSizeInput.value,
      editor: editor.value,
    };
    localStorage.setItem('state', JSON.stringify(currentState));
  }
});

// Restore the state from localStorage.
const restoredState = JSON.parse(localStorage.getItem('state'));
if (restoredState) {
  spellcheckToggle.className = restoredState.spellcheckToggle;
  verticalViewToggle.className = restoredState.verticalViewToggle;
  darkEditorToggle.className = restoredState.darkEditorToggle;
  footerContainerToggle.className = restoredState.footerContainerToggle;
  previewSelector.value = restoredState.previewSelector;
  fontSizeSelector.value = restoredState.fontSizeSelector;
  editorSizeInput.value = restoredState.editorSizeInput;
  editor.value = restoredState.editor;
}

// Update the UI based on the restored state.
editor.spellcheck = spellcheckToggle.classList.contains('active');
toggleClass(mainContainer, 'vertical', verticalViewToggle.classList.contains('active'));
toggleClass(editor, 'dark', darkEditorToggle.classList.contains('active'));
toggleClass(footerContainer, 'hidden', !footerContainerToggle.classList.contains('active'));
toggleRunButton();
resizeFont();
resizeEditor();
timedPreview();
html,
body {
  height: 100%;
  padding: 0;
  margin: 0;
}

body {
  display: flex;
  flex-direction: column;
}

header,
footer {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 5px;
  padding: 5px;
}

header {
  background: linear-gradient(#FFF, #CCC);
}

footer {
  background: linear-gradient(#CCC, #FFF);
}

main {
  display: flex;
  flex: 1;
}

main.vertical {
  flex-direction: column;
}

header *,
footer * {
  font: 0.75rem Arial;
  color: #333;
}

select,
button,
input {
  margin: 0;
}

label[for="editorSizeInput"],
address {
  margin-left: auto;
}

#editorSizeInput,
iframe {
  padding: 0;
}

output {
  font-family: monospace;
}

.toggle {
  padding: 2px 6px;
  border: 1px solid #666;
  border-radius: 12px;
  background: transparent;
}

#footerContainerToggle {
  width: 16px;
  height: 16px;
  padding: 0;
  border-bottom-width: 5px;
  border-radius: 0;
}

.active {
  border-color: #333;
  background: #FFF;
}

#copyButton {
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}

img {
  display: block;
}

div {
  position: relative;
}

#previewerWrapper {
  border-left: 5px solid #CCC;
}

main.vertical #previewerWrapper {
  border-left: 0;
  border-top: 5px solid #CCC;
}

div * {
  position: absolute;
  width: 100%;
  height: 100%;
  border: 0;
  margin: 0;
  background: #FFF;
}

textarea {
  box-sizing: border-box;
  padding: 5px;
  outline: 0;
  resize: none;
  color: #333;
}

textarea.dark {
  background: #333;
  color: #FFF;
}

.hidden {
  display: none;
}
<header>
  <a href="" download="template.html" id="downloadLink" title="Download HTML document">Download</a>
  <label for="fontSizeSelector">Font size</label>
  <select id="fontSizeSelector">
    <option>12</option>
    <option>13</option>
    <option selected>14</option>
    <option>15</option>
    <option>16</option>
    <option>17</option>
    <option>18</option>
    <option>19</option>
    <option>20</option>
  </select>
  <label for="previewSelector">Preview</label>
  <select id="previewSelector">
    <option>Instant</option>
    <optgroup label="Delayed">
      <option value="500" selected>0.5 s</option>
      <option value="1000">1 s</option>
      <option value="1500">1.5 s</option>
      <option value="2000">2 s</option>
    </optgroup>
    <option>Manual</option>
  </select>
  <button type="button" id="runButton">Run</button>
  <button type="button" id="resetButton">Reset</button>
  <button type="button" id="selectButton">Select</button>
  <input type="file" accept="text/html" id="fileInput">
  <label for="editorSizeInput">Editor size</label>
  <input type="range" id="editorSizeInput">
  <output for="editorSizeInput"></output>
  <button type="button" class="toggle" id="verticalViewToggle">Vertical view</button>
  <button type="button" class="toggle" id="darkEditorToggle">Dark editor</button>
  <button type="button" class="toggle" id="spellcheckToggle">Spellcheck</button>
  <button type="button" class="toggle" id="footerContainerToggle" title="Toggle footer"></button>
</header>
<main>
  <div id="editorWrapper">
    <textarea><!doctype html>
<html lang="en">
<head>
  <title>HTML Document Template</title>
  <style>
    p {
      font-family: Arial;
    }
  </style>
</head>
<body>
  <p>Hello, world!</p>
  <script>
    console.log(document.querySelector('p').textContent);
  </script>
</body>
</html></textarea>
  </div>
  <div id="previewerWrapper">
    <iframe></iframe>
  </div>
</main>
<footer>
  <span>Share</span>
  <a href="https://x.com/intent/post?text=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview&url=https%3A%2F%2Fhtmleditor.gitlab.io" target="_blank"><img src="images/x.svg" width="16" height="16" alt="X"></a>
  <a href="https://www.facebook.com/sharer.php?u=https%3A%2F%2Fhtmleditor.gitlab.io&t=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview" target="_blank"><img src="images/facebook.svg" width="16" height="16" alt="Facebook"></a>
  <a href="https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fhtmleditor.gitlab.io" target="_blank"><img src="images/linkedin.svg" width="16" height="16" alt="LinkedIn"></a>
  <a href="mailto:?subject=HTML%20Editor%3A%20online%20HTML%20editor%20with%20real-time%20preview&body=https%3A%2F%2Fhtmleditor.gitlab.io" target="_blank"><img src="images/email.svg" width="16" height="16" alt="Email"></a>
  <button type="button" id="copyButton"><img src="images/link.svg" width="16" height="16" alt="Link"></button>
  <span id="copyNotification" class="hidden">Copied!</span>
  <address><a href="https://codereview.stackexchange.com/questions/293330/html-editor-online-html-editor-with-real-time-preview" title="Code Review Stack Exchange">Feedback</a> | Created by <a href="https://mori.pages.dev" rel="author">Mori</a></address>
</footer>

However, when I inspect the resulting page in Chrome Developer Tools (version 127.0.6533.120 on Windows 10), I notice an unexpected div element at the end of the body:

<div id="cccTost"></div>

This div isn't in my source code, and I'm curious where it's coming from and why it's being added. Here's a screenshot showing the extra div in the DOM:

Extra div Element in Chrome Developer Tools

Can anyone explain what might be causing this and how to prevent it?


Solution

  • Thanks for this question.

    The <div id="cccTost"></div> section is created by Click Copy {Code} extension.

    If you see the source code of this plugin at line 8 to 12 of script.js file, the dom is created on initialization of the extension.

    init: function () {
        this.notifactionDom();
        this.copyCode();
    },
    notifactionDom: function () {
        let div = document.createElement('div');
        div.setAttribute("id", "cccTost");
        document.body.appendChild(div);
    },