glass-browser 0.2.2

Local, revision-safe Chrome automation runtime for agents, with semantic observation, verified workflows, MCP, CLI, TUI, and Rust APIs
Documentation
<!doctype html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Glass Fixture</title>
  </head>
  <body>
    <main>
      <h1>Glass Fixture</h1>
      <label for="name">Name</label>
      <input id="name" aria-label="Name">
      <button id="save" type="button">Save</button>
      <button id="double" type="button">Double</button>
      <button class="duplicate" type="button">Duplicate</button>
      <button class="duplicate" type="button">Duplicate</button>
      <button id="disabled" type="button" disabled>Disabled action</button>
      <div style="position: relative; width: 180px; height: 40px">
        <button id="covered" type="button" style="width: 180px; height: 40px">Covered action</button>
        <div id="cover" style="position: absolute; inset: 0; z-index: 2"></div>
      </div>
      <button id="moving" type="button" style="animation: move 0.08s linear infinite alternate">Moving action</button>
      <button id="hover-reflow" type="button">Hover reflow action</button>
      <button id="text-unique" type="button"><span>Unique visible phrase</span></button>
      <span hidden>Unique visible phrase</span>
      <span style="opacity: 0"><span>Unique visible phrase</span></span>
      <span style="display: block; width: 0; height: 0; overflow: hidden"><span>Unique visible phrase</span></span>
      <button class="text-duplicate" type="button">Repeated phrase</button>
      <button class="text-duplicate" type="button">Repeated phrase</button>
      <button id="selector-text" type="button">#save</button>
      <button id="cancel-release" type="button">Cancellation release</button>
      <button id="sticky-covered" type="button" style="display: none; position: fixed; top: 500px; left: 1100px">Sticky-covered action</button>
      <div id="sticky-cover" style="display: none; position: fixed; top: 490px; left: 1090px; width: 180px; height: 70px; z-index: 5"></div>
      <p id="result"></p>
      <div id="editable" contenteditable="true" tabindex="0">Edit me</div>
      <input id="city" aria-label="City" list="cities" autocomplete="address-level2"><datalist id="cities"><option value="Paris"><option value="Prague"></datalist>
      <label><input id="agree" type="checkbox"> Agree</label>
      <select id="choice" aria-label="Choice"><option value="a">Alpha</option><option value="b">Beta</option></select>
      <input id="upload" type="file" aria-label="Upload">
      <div id="drag-source" tabindex="0" style="width:80px;height:30px">Drag source</div>
      <div id="drag-target" tabindex="0" style="width:100px;height:40px">Drag target</div>
      <div style="height: 1800px" aria-hidden="true"></div>
      <button id="detach-on-scroll" type="button">Detach on scroll</button>
      <button id="offscreen-save" type="button">Offscreen Save</button>
    </main>
    <style>@keyframes move { from { transform: translateX(0) } to { transform: translateX(80px) } }</style>
    <script>
      window.pointerEvents = [];
      for (const type of ["mousemove", "mousedown", "mouseup"]) {
        document.addEventListener(type, (event) => {
          window.pointerEvents.push({
            type,
            x: event.clientX,
            y: event.clientY,
          });
        }, true);
      }
      document.querySelector("#save").addEventListener("click", () => {
        document.querySelector("#result").textContent =
          "Saved " + document.querySelector("#name").value;
      });
      window.doubleClicks = 0;
      document.querySelector("#double").addEventListener("dblclick", () => {
        window.doubleClicks += 1;
      });
      document.querySelector("#offscreen-save").addEventListener("click", () => {
        document.querySelector("#result").textContent = "Offscreen saved";
      });
      document.querySelector("#hover-reflow").addEventListener("mousemove", () => {
        document.querySelector("#hover-reflow").style.transform = "translateX(120px)";
      }, {once: true});
      document.querySelector("#text-unique").addEventListener("click", () => {
        document.querySelector("#result").textContent = "Text unique clicked";
      });
      document.querySelector("#selector-text").addEventListener("click", () => {
        document.querySelector("#result").textContent = "Selector text clicked";
      });
      window.keyEvents = [];
      for (const type of ["keydown", "keypress", "keyup"]) document.addEventListener(type, event => window.keyEvents.push({type, key:event.key, ctrl:event.ctrlKey, shift:event.shiftKey}), true);
      window.dragEvents = [];
      for (const type of ["mousedown", "mousemove", "mouseup"]) document.querySelector("#drag-target").addEventListener(type, event => window.dragEvents.push(type));
      document.querySelector("#drag-target").addEventListener("mouseup", event => event.currentTarget.dataset.dropped = "yes");
      const nativeScrollIntoView = Element.prototype.scrollIntoView;
      Element.prototype.scrollIntoView = function(options) {
        nativeScrollIntoView.call(this, options);
        if (this.id === "detach-on-scroll") this.remove();
      };
    </script>
  </body>
</html>