From 34aebfbef5d83f45131b2a542d59cb67781c1547 Mon Sep 17 00:00:00 2001 From: Domenic Denicola Date: Fri, 30 Dec 2022 17:14:14 +0900 Subject: [PATCH] Live viewer: stop using iframes We can just assume the browser has a working URL constructor. --- live-viewer/index.html | 2 - live-viewer/live-viewer.mjs | 265 +++++++++++++++++------------------- 2 files changed, 123 insertions(+), 144 deletions(-) diff --git a/live-viewer/index.html b/live-viewer/index.html index f4300bd..909c6ab 100644 --- a/live-viewer/index.html +++ b/live-viewer/index.html @@ -63,5 +63,3 @@

jsdom/whatwg-url's components

- - diff --git a/live-viewer/live-viewer.mjs b/live-viewer/live-viewer.mjs index a68d9d2..e950edf 100644 --- a/live-viewer/live-viewer.mjs +++ b/live-viewer/live-viewer.mjs @@ -1,158 +1,139 @@ import whatwgURL from "./whatwg-url.mjs"; -(() => { - const urlInput = document.querySelector("#url"); - const baseInput = document.querySelector("#base"); - - const te = new TextEncoder(); - const td = new TextDecoder(); - - // Use an iframe to avoid affecting the main page. This is especially bad in Edge where it - // appears to break Edge's DevTools. - const browserIframeDocument = document.querySelector("#browser-iframe").contentDocument; - const browserAnchor = browserIframeDocument.createElement("a"); - const browserBase = browserIframeDocument.createElement("base"); - browserIframeDocument.head.appendChild(browserBase); - browserIframeDocument.body.appendChild(browserAnchor); - - const components = [ - "href", - "protocol", - "username", - "password", - "port", - "hostname", - "pathname", - "search", - "hash", - "origin" - ]; - - urlInput.addEventListener("input", update); - baseInput.addEventListener("input", update); - window.addEventListener("hashchange", setFromFragment); - setFromFragment(); - - function update() { - const browserResult = getBrowserResult(); - const jsdomResult = getJsdomResult(); - const mismatchedComponents = getMismatchedComponents(browserResult, jsdomResult); - - setResult("browser", browserResult, mismatchedComponents); - setResult("jsdom", jsdomResult, mismatchedComponents); - updateFragmentForSharing(); - } - - function setResult(kind, result, mismatchedComponents) { - const output = document.querySelector(`#${kind}-output`); - const error = document.querySelector(`#${kind}-error`); - - if (result instanceof Error) { - output.hidden = true; - error.hidden = false; - error.textContent = result.toString(); - } else { - output.hidden = false; - error.hidden = true; - for (const component of components) { - const componentEl = output.querySelector(`.${component}`).querySelector("td"); - setComponentElValue(componentEl, result[component]); - setComponentElMismatch(componentEl, mismatchedComponents.has(component)); - } - } - } - - function setComponentElValue(componentEl, value) { - // This shows up in Edge where username/password are undefined. - const isNonString = typeof value !== "string"; - const isEmptyString = value === ""; - componentEl.textContent = isEmptyString ? "(empty string)" : value; - componentEl.classList.toggle("empty-string", isEmptyString); - componentEl.classList.toggle("non-string", isNonString); - } - - function setComponentElMismatch(componentEl, isMismatched) { - componentEl.classList.toggle("pass", !isMismatched); - componentEl.classList.toggle("fail", isMismatched); - } - - function getMismatchedComponents(result1, result2) { - const mismatched = new Set(); +const urlInput = document.querySelector("#url"); +const baseInput = document.querySelector("#base"); + +const te = new TextEncoder(); +const td = new TextDecoder(); + +const components = [ + "href", + "protocol", + "username", + "password", + "port", + "hostname", + "pathname", + "search", + "hash", + "origin" +]; + +urlInput.addEventListener("input", update); +baseInput.addEventListener("input", update); +window.addEventListener("hashchange", setFromFragment); +setFromFragment(); + +function update() { + const browserResult = getBrowserResult(); + const jsdomResult = getJsdomResult(); + const mismatchedComponents = getMismatchedComponents(browserResult, jsdomResult); + + setResult("browser", browserResult, mismatchedComponents); + setResult("jsdom", jsdomResult, mismatchedComponents); + updateFragmentForSharing(); +} + +function setResult(kind, result, mismatchedComponents) { + const output = document.querySelector(`#${kind}-output`); + const error = document.querySelector(`#${kind}-error`); + + if (result instanceof Error) { + output.hidden = true; + error.hidden = false; + error.textContent = result.toString(); + } else { + output.hidden = false; + error.hidden = true; for (const component of components) { - if (result1[component] !== result2[component]) { - mismatched.add(component); - } + const componentEl = output.querySelector(`.${component}`).querySelector("td"); + setComponentElValue(componentEl, result[component]); + setComponentElMismatch(componentEl, mismatchedComponents.has(component)); } - return mismatched; } - - function getBrowserResult() { - // First make sure the base is not invalid by testing it against an about:blank base. - browserBase.href = "about:blank"; - browserAnchor.href = baseInput.value; - if (browserAnchor.protocol === ":") { - return new Error("Browser could not parse the base URL"); +} + +function setComponentElValue(componentEl, value) { + // This shows up in Edge where username/password are undefined. + const isNonString = typeof value !== "string"; + const isEmptyString = value === ""; + + componentEl.textContent = isEmptyString ? "(empty string)" : value; + componentEl.classList.toggle("empty-string", isEmptyString); + componentEl.classList.toggle("non-string", isNonString); +} + +function setComponentElMismatch(componentEl, isMismatched) { + componentEl.classList.toggle("pass", !isMismatched); + componentEl.classList.toggle("fail", isMismatched); +} + +function getMismatchedComponents(result1, result2) { + const mismatched = new Set(); + for (const component of components) { + if (result1[component] !== result2[component]) { + mismatched.add(component); } - - // Now actually parse the URL against the base. - browserAnchor.href = urlInput.value; - browserBase.href = baseInput.value; - if (browserAnchor.protocol === ":") { - return new Error("Browser could not parse the input"); - } - - return browserAnchor; } - - function getJsdomResult() { - try { - return new whatwgURL.URL(urlInput.value, baseInput.value); - } catch (e) { - return e; - } + return mismatched; +} + +function getBrowserResult() { + try { + return new URL(urlInput.value, baseInput.value); + } catch (e) { + return e; } +} - function updateFragmentForSharing() { - location.hash = `url=${encodeToBase64(urlInput.value)}&base=${encodeToBase64(baseInput.value)}`; +function getJsdomResult() { + try { + return new whatwgURL.URL(urlInput.value, baseInput.value); + } catch (e) { + return e; } +} - function setFromFragment() { - const pieces = /#url=([^&]*)&base=(.*)/u.exec(location.hash); - if (!pieces) { - return; - } - const [, urlEncoded, baseEncoded] = pieces; - try { - urlInput.value = decodeFromBase64(urlEncoded); - } catch (e) { - // eslint-disable-next-line no-console - console.warn("url hash parameter was not deserializable."); - } - - try { - baseInput.value = decodeFromBase64(baseEncoded); - } catch (e) { - // eslint-disable-next-line no-console - console.warn("base hash parameter was not deserializable."); - } +function updateFragmentForSharing() { + location.hash = `url=${encodeToBase64(urlInput.value)}&base=${encodeToBase64(baseInput.value)}`; +} - update(); +function setFromFragment() { + const pieces = /#url=([^&]*)&base=(.*)/u.exec(location.hash); + if (!pieces) { + return; } - - // btoa / atob don't work on Unicode. - // This version is a superset of btoa / atob, so it maintains compatibility with older versions of - // the live viewer which used btoa / atob directly. - function encodeToBase64(originalString) { - const bytes = te.encode(originalString); - const byteString = Array.from(bytes, byte => String.fromCharCode(byte)).join(""); - const encoded = btoa(byteString); - return encoded; + const [, urlEncoded, baseEncoded] = pieces; + try { + urlInput.value = decodeFromBase64(urlEncoded); + } catch (e) { + // eslint-disable-next-line no-console + console.warn("url hash parameter was not deserializable."); } - function decodeFromBase64(encoded) { - const byteString = atob(encoded); - const bytes = Uint8Array.from(byteString, char => char.charCodeAt(0)); - const originalString = td.decode(bytes); - return originalString; + try { + baseInput.value = decodeFromBase64(baseEncoded); + } catch (e) { + // eslint-disable-next-line no-console + console.warn("base hash parameter was not deserializable."); } -})(); + + update(); +} + +// btoa / atob don't work on Unicode. +// This version is a superset of btoa / atob, so it maintains compatibility with older versions of +// the live viewer which used btoa / atob directly. +function encodeToBase64(originalString) { + const bytes = te.encode(originalString); + const byteString = Array.from(bytes, byte => String.fromCharCode(byte)).join(""); + const encoded = btoa(byteString); + return encoded; +} + +function decodeFromBase64(encoded) { + const byteString = atob(encoded); + const bytes = Uint8Array.from(byteString, char => char.charCodeAt(0)); + const originalString = td.decode(bytes); + return originalString; +}