diff --git a/.gitignore b/.gitignore index e9ef3a2..621ea6d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,12 @@ .vscode/* +# Key for embedding +key.json + # NPM things package.json package-lock.json node_modules/ # Yarn things -yarn.lock \ No newline at end of file +yarn.lock diff --git a/index.html b/index.html index 852619c..e091f19 100644 --- a/index.html +++ b/index.html @@ -299,7 +299,7 @@
- + @@ -345,5 +345,8 @@ src="js/initalize/debug.populate.js" type="text/javascript"> + + + diff --git a/js/index.js b/js/index.js index 085cb31..b328b14 100644 --- a/js/index.js +++ b/js/index.js @@ -1,6 +1,86 @@ //TODO FIND OUT WHY I HAVE TO RESIZE A TEXTBOX AND THEN START USING IT TO AVOID THE 1px WHITE LINE ON LEFT EDGES DURING IMG2IMG //...lmao did setting min width 200 on info div fix that accidentally? once the canvas is infinite and the menu bar is hideable it'll probably be a problem again +/** + * Workaround for Firefox bug #733698 + * + * https://bugzilla.mozilla.org/show_bug.cgi?id=733698 + * + * Workaround by https://github.com/subzey on https://gist.github.com/subzey/2030480 + * + * Replaces and handles NS_ERROR_FAILURE errors triggered by 733698. + */ +(function () { + var FakeTextMetrics, + proto, + fontSetterNative, + measureTextNative, + fillTextNative, + strokeTextNative; + + if ( + !window.CanvasRenderingContext2D || + !window.TextMetrics || + !(proto = window.CanvasRenderingContext2D.prototype) || + !proto.hasOwnProperty("font") || + !proto.hasOwnProperty("mozTextStyle") || + typeof proto.__lookupSetter__ !== "function" || + !(fontSetterNative = proto.__lookupSetter__("font")) + ) { + return; + } + + proto.__defineSetter__("font", function (value) { + try { + return fontSetterNative.call(this, value); + } catch (e) { + if (e.name !== "NS_ERROR_FAILURE") { + throw e; + } + } + }); + + measureTextNative = proto.measureText; + FakeTextMetrics = function () { + this.width = 0; + this.isFake = true; + this.__proto__ = window.TextMetrics.prototype; + }; + proto.measureText = function ($0) { + try { + return measureTextNative.apply(this, arguments); + } catch (e) { + if (e.name !== "NS_ERROR_FAILURE") { + throw e; + } else { + return new FakeTextMetrics(); + } + } + }; + + fillTextNative = proto.fillText; + proto.fillText = function ($0, $1, $2, $3) { + try { + fillTextNative.apply(this, arguments); + } catch (e) { + if (e.name !== "NS_ERROR_FAILURE") { + throw e; + } + } + }; + + strokeTextNative = proto.strokeText; + proto.strokeText = function ($0, $1, $2, $3) { + try { + strokeTextNative.apply(this, arguments); + } catch (e) { + if (e.name !== "NS_ERROR_FAILURE") { + throw e; + } + } + }; +})(); + window.onload = startup; var stableDiffusionData = { @@ -91,7 +171,7 @@ function startup() { ? hostEl.value.substring(0, hostEl.value.length - 1) : hostEl.value; hostEl.value = host; - localStorage.setItem("host", host); + localStorage.setItem("openoutpaint/host", host); checkConnection(); }; }); @@ -112,7 +192,7 @@ function testHostConfiguration() { * Check host configuration */ const hostEl = document.getElementById("host"); - hostEl.value = localStorage.getItem("host"); + hostEl.value = localStorage.getItem("openoutpaint/host"); const requestHost = (prompt, def = "http://127.0.0.1:7860") => { let value = window.prompt(prompt, def); @@ -121,12 +201,12 @@ function testHostConfiguration() { value = value.endsWith("/") ? value.substring(0, value.length - 1) : value; host = value; hostEl.value = host; - localStorage.setItem("host", host); + localStorage.setItem("openoutpaint/host", host); testHostConfiguration(); }; - const current = localStorage.getItem("host"); + const current = localStorage.getItem("openoutpaint/host"); if (current) { if (!current.match(/^https?:\/\/[a-z0-9][a-z0-9.]+[a-z0-9](:[0-9]+)?$/i)) requestHost( @@ -507,19 +587,19 @@ function changeMaskBlur() { stableDiffusionData.mask_blur = parseInt( document.getElementById("maskBlur").value ); - localStorage.setItem("mask_blur", stableDiffusionData.mask_blur); + localStorage.setItem("openoutpaint/mask_blur", stableDiffusionData.mask_blur); } function changeSeed() { stableDiffusionData.seed = document.getElementById("seed").value; - localStorage.setItem("seed", stableDiffusionData.seed); + localStorage.setItem("openoutpaint/seed", stableDiffusionData.seed); } function changeHiResFix() { stableDiffusionData.enable_hr = Boolean( document.getElementById("cbxHRFix").checked ); - localStorage.setItem("enable_hr", stableDiffusionData.enable_hr); + localStorage.setItem("openoutpaint/enable_hr", stableDiffusionData.enable_hr); } function changeSyncCursorSize() { @@ -800,7 +880,10 @@ function changeStyles() { }); } - localStorage.setItem("promptStyle", JSON.stringify(selectedString)); + localStorage.setItem( + "openoutpaint/promptStyle", + JSON.stringify(selectedString) + ); // change the model if (selectedString.length > 0) @@ -821,16 +904,16 @@ async function getSamplers() { })); // Initial sampler - if (localStorage.getItem("sampler") != null) { - samplerAutoComplete.value = localStorage.getItem("sampler"); + if (localStorage.getItem("openoutpaint/sampler") != null) { + samplerAutoComplete.value = localStorage.getItem("openoutpaint/sampler"); } else { samplerAutoComplete.value = data[0].name; - localStorage.setItem("sampler", samplerAutoComplete.value); + localStorage.setItem("openoutpaint/sampler", samplerAutoComplete.value); } samplerAutoComplete.onchange.on(({value}) => { stableDiffusionData.sampler_index = value; - localStorage.setItem("sampler", value); + localStorage.setItem("openoutpaint/sampler", value); }); } catch (e) { console.warn("[index] Failed to fetch samplers"); @@ -841,8 +924,8 @@ async function upscaleAndDownload() { // Future improvements: some upscalers take a while to upscale, so we should show a loading bar or something, also a slider for the upscale amount // get cropped canvas, send it to upscaler, download result - var upscale_factor = localStorage.getItem("upscale_x") - ? localStorage.getItem("upscale_x") + var upscale_factor = localStorage.getItem("openoutpaint/upscale_x") + ? localStorage.getItem("openoutpaint/upscale_x") : 2; var upscaler = upscalerAutoComplete.value; var croppedCanvas = cropCanvas( @@ -896,21 +979,23 @@ async function upscaleAndDownload() { function loadSettings() { // set default values if not set var _mask_blur = - localStorage.getItem("mask_blur") == null + localStorage.getItem("openoutpaint/mask_blur") == null ? 0 - : localStorage.getItem("mask_blur"); + : localStorage.getItem("openoutpaint/mask_blur"); var _seed = - localStorage.getItem("seed") == null ? -1 : localStorage.getItem("seed"); + localStorage.getItem("openoutpaint/seed") == null + ? -1 + : localStorage.getItem("openoutpaint/seed"); let _enable_hr = - localStorage.getItem("enable_hr") === null + localStorage.getItem("openoutpaint/enable_hr") === null ? false - : localStorage.getItem("enable_hr") === "true"; + : localStorage.getItem("openoutpaint/enable_hr") === "true"; let _sync_cursor_size = - localStorage.getItem("sync_cursor_size") === null + localStorage.getItem("openoutpaint/sync_cursor_size") === null ? true - : localStorage.getItem("sync_cursor_size") === "true"; + : localStorage.getItem("openoutpaint/sync_cursor_size") === "true"; // set the values into the UI document.getElementById("maskBlur").value = Number(_mask_blur); diff --git a/js/initalize/layers.populate.js b/js/initalize/layers.populate.js index 917f8ff..7346aad 100644 --- a/js/initalize/layers.populate.js +++ b/js/initalize/layers.populate.js @@ -3,10 +3,14 @@ const imageCollection = layers.registerCollection( "image", { w: parseInt( - (localStorage && localStorage.getItem("settings.canvas-width")) || 2048 + (localStorage && + localStorage.getItem("openoutpaint/settings.canvas-width")) || + 2048 ), h: parseInt( - (localStorage && localStorage.getItem("settings.canvas-height")) || 2048 + (localStorage && + localStorage.getItem("openoutpaint/settings.canvas-height")) || + 2048 ), }, { @@ -57,7 +61,7 @@ const uiCtx = uiCanvas.getContext("2d", {desynchronized: true}); * Here we setup canvas dynamic scaling */ (() => { - let expandSize = localStorage.getItem("expand-size") || 1024; + let expandSize = localStorage.getItem("openoutpaint/expand-size") || 1024; expandSize = parseInt(expandSize, 10); const askSize = () => { @@ -66,7 +70,7 @@ const uiCtx = uiCanvas.getContext("2d", {desynchronized: true}); if (!by) return null; else { const len = parseInt(by, 10); - localStorage.setItem("expand-size", len); + localStorage.setItem("openoutpaint/expand-size", len); expandSize = len; return len; } diff --git a/js/prompt.js b/js/prompt.js index 53ebf3e..781548e 100644 --- a/js/prompt.js +++ b/js/prompt.js @@ -20,8 +20,8 @@ async function getStyles() { /** @type {string[]} */ let stored = null; try { - stored = JSON.parse(localStorage.getItem("promptStyle")); - // doesn't seem to throw a syntaxerror if the localstorage item simply doesn't exist? + stored = JSON.parse(localStorage.getItem("openoutpaint/promptStyle")); + // doesn't seem to throw a syntaxerror if the localStorage item simply doesn't exist? if (stored == null) stored = []; } catch (e) { stored = []; @@ -40,11 +40,14 @@ async function getStyles() { selected = value; } stableDiffusionData.styles = selected; - localStorage.setItem("promptStyle", JSON.stringify(selected)); + localStorage.setItem( + "openoutpaint/promptStyle", + JSON.stringify(selected) + ); }); styleSelectElement.value = stored; - localStorage.setItem("promptStyle", JSON.stringify(stored)); + localStorage.setItem("openoutpaint/promptStyle", JSON.stringify(stored)); } catch (e) { console.warn("[index] Failed to fetch prompt styles"); console.warn(e); @@ -66,18 +69,21 @@ async function getStyles() { promptEl.oninput = () => { stableDiffusionData.prompt = promptEl.value; promptEl.title = promptEl.value; - localStorage.setItem("prompt", stableDiffusionData.prompt); + localStorage.setItem("openoutpaint/prompt", stableDiffusionData.prompt); }; negativePromptEl.oninput = () => { stableDiffusionData.negative_prompt = negativePromptEl.value; negativePromptEl.title = negativePromptEl.value; - localStorage.setItem("neg_prompt", stableDiffusionData.negative_prompt); + localStorage.setItem( + "openoutpaint/neg_prompt", + stableDiffusionData.negative_prompt + ); }; // Load from local storage if set - const storedPrompt = localStorage.getItem("prompt"); - const storedNeg = localStorage.getItem("neg_prompt"); + const storedPrompt = localStorage.getItem("openoutpaint/prompt"); + const storedNeg = localStorage.getItem("openoutpaint/neg_prompt"); const promptDefaultValue = storedPrompt === null ? defaultPrompt : storedPrompt; const negativePromptDefaultValue = @@ -137,7 +143,7 @@ async function getStyles() { stableDiffusionData.prompt = prompt; promptEl.title = prompt; promptEl.value = prompt; - localStorage.setItem("prompt", prompt); + localStorage.setItem("openoutpaint/prompt", prompt); }); promptBtn.textContent = (samePrompt ? "= " : "") + prompt; @@ -147,7 +153,7 @@ async function getStyles() { stableDiffusionData.negative_prompt = negative; negativePromptEl.title = negative; negativePromptEl.value = negative; - localStorage.setItem("neg_prompt", negative); + localStorage.setItem("openoutpaint/neg_prompt", negative); }); negativeBtn.textContent = (sameNegativePrompt ? "= " : "") + negative; diff --git a/js/webui.js b/js/webui.js new file mode 100644 index 0000000..9151498 --- /dev/null +++ b/js/webui.js @@ -0,0 +1,109 @@ +/** + * This file should only be actually loaded if we are in a trusted environment. + */ +(async () => { + // Check if key file exists + const response = await fetch("key.json"); + + /** @type {{host?: string, trusted?: boolean, key: string}} */ + let data = null; + if (response.status === 200) { + data = await response.json(); + console.info("[webui] key.json loaded successfully"); + } + if (response.status !== 200 || (!data.key && !data.trusted)) { + console.warn( + "[webui] An accessible key.json file with a 'key' or 'trusted' should be provided to allow for messaging" + ); + console.warn(data); + return; + } + + const key = data.key; + + // Check if we are running inside an iframe or embed + try { + const frame = window.frameElement; + + if (frame === null) { + console.info("[webui] Not running inside a frame"); + } else { + console.info( + `[webui] Window is child of '${window.parent.document.URL}'` + ); + if (data.host && !window.parent.document.URL.startsWith(data.host)) { + console.warn( + `[webui] Window does not trust parent '${window.parent.document.URL}'` + ); + console.warn("[webui] Will NOT setup message listeners"); + return; + } + } + } catch (e) { + console.warn( + `[webui] Running in a third party iframe or embed, and blocked by CORS` + ); + console.warn(e); + return; + } + + if (data) { + let parentWindow = null; + + if (!data.trusted) console.debug(`[webui] Loaded key`); + + window.addEventListener("message", ({data, origin, source}) => { + if (!data.trusted && data.key !== key) { + console.warn( + `[webui] Message with incorrect key was received from '${origin}'` + ); + console.warn(data); + return; + } + + if (!parentWindow && !data.type === "openoutpaint/init") { + console.warn(`[webui] Communication has not been initialized`); + } + + try { + switch (data.type) { + case "openoutpaint/init": + parentWindow = source; + console.debug( + `[webui] Communication with '${origin}' has been initialized` + ); + break; + case "openoutpaint/add-resource": + { + const image = document.createElement("img"); + image.src = data.image.dataURL; + image.onload = async () => { + await tools.stamp.state.addResource( + data.image.resourceName || "External Resource", + image + ); + tools.stamp.enable(); + }; + } + break; + default: + console.warn(`[webui] Unsupported message type: ${data.type}`); + break; + } + + // Send acknowledgement + parentWindow && + parentWindow.postMessage({ + type: "openoutpaint/ack", + message: data, + }); + } catch (e) { + console.warn( + `[webui] Message of type '${data.type}' has invalid format` + ); + console.warn(e); + console.warn(data); + } + }); + } +})(); diff --git a/pages/configuration.html b/pages/configuration.html index 7203f32..42483db 100644 --- a/pages/configuration.html +++ b/pages/configuration.html @@ -4,22 +4,22 @@ openOutpaint 🐠 - - + + - - + + - + - - - + + + - - - + + + @@ -65,14 +65,21 @@ const canvasHeight = document.getElementById("canvas-height"); function writeToLocalStorage() { - localStorage.setItem("settings.canvas-width", canvasWidth.value); - localStorage.setItem("settings.canvas-height", canvasHeight.value); + localStorage.setItem( + "openoutpaint/settings.canvas-width", + canvasWidth.value + ); + localStorage.setItem( + "openoutpaint/settings.canvas-height", + canvasHeight.value + ); } // Loads values from local storage - canvasWidth.value = localStorage.getItem("settings.canvas-width") || 2048; + canvasWidth.value = + localStorage.getItem("openoutpaint/settings.canvas-width") || 2048; canvasHeight.value = - localStorage.getItem("settings.canvas-height") || 2048; + localStorage.getItem("openoutpaint/settings.canvas-height") || 2048; writeToLocalStorage();