From d4bc0c6254dbeaee068715f5a40469bd5d16439e Mon Sep 17 00:00:00 2001 From: Danny Coates Date: Mon, 20 Mar 2017 21:45:34 -0700 Subject: [PATCH] Split screenshot iframe into preselection and selection iframes. closes #2162 --- addon/webextension/selector/ui.js | 291 ++++++++++++++++------- addon/webextension/selector/uicontrol.js | 34 ++- 2 files changed, 217 insertions(+), 108 deletions(-) diff --git a/addon/webextension/selector/ui.js b/addon/webextension/selector/ui.js index 5f19177c95..622db7fb42 100644 --- a/addon/webextension/selector/ui.js +++ b/addon/webextension/selector/ui.js @@ -47,17 +47,26 @@ window.ui = (function () { // eslint-disable-line no-unused-vars }); function makeEl(tagName, className) { - if (! iframe.document) { + if (! iframe.document()) { throw new Error("Attempted makeEl before iframe was initialized"); } - let el = iframe.document.createElement(tagName); + let el = iframe.document().createElement(tagName); if (className) { el.className = className; } return el; } - let iframe = exports.iframe = { + function onResize() { + if (this.sizeTracking.windowDelayer) { + clearTimeout(this.sizeTracking.windowDelayer); + } + this.sizeTracking.windowDelayer = setTimeout(watchFunction(() => { + this.updateElementSize(true); + }), 50); + } + + let iframeSelection = exports.iframeSelection = { element: null, addClassName: "", sizeTracking: { @@ -72,7 +81,8 @@ window.ui = (function () { // eslint-disable-line no-unused-vars return new Promise((resolve, reject) => { if (! this.element) { this.element = document.createElement("iframe"); - this.element.id = "firefox-screenshots-iframe"; + this.element.id = "firefox-screenshots-selection-iframe"; + this.element.style.display = "none"; this.element.style.zIndex = "99999999999"; this.element.style.border = "none"; this.element.style.position = "absolute"; @@ -107,23 +117,26 @@ window.ui = (function () { // eslint-disable-line no-unused-vars } else { resolve(); } - this.initSizeWatch(); }); }, hide: function () { this.element.style.display = "none"; + this.stopSizeWatch(); }, unhide: function () { + this.updateElementSize(); this.element.style.display = ""; + this.initSizeWatch(); }, updateElementSize: function (force) { // Note: if someone sizes down the page, then the iframe will keep the // document from naturally shrinking. We use force to temporarily hide // the element so that we can tell if the document shrinks - if (force) { + const visible = this.element.style.display !== "none"; + if (force && visible) { this.element.style.display = "none"; } let height = Math.max( @@ -146,10 +159,9 @@ window.ui = (function () { // eslint-disable-line no-unused-vars this.sizeTracking.lastWidth = width; this.element.style.width = width + "px"; } - if (force) { + if (force && visible) { this.element.style.display = ""; } - WholePageOverlay.resetPosition(); }, initSizeWatch: function () { @@ -171,15 +183,6 @@ window.ui = (function () { // eslint-disable-line no-unused-vars window.removeEventListener("resize", this.onResize, true); }, - onResize: function () { - if (this.sizeTracking.windowDelayer) { - clearTimeout(this.sizeTracking.windowDelayer); - } - this.sizeTracking.windowDelayer = setTimeout(watchFunction(() => { - this.updateElementSize(true); - }), 100); - }, - getElementFromPoint: function (x, y) { this.element.style.pointerEvents = "none"; let el; @@ -198,83 +201,193 @@ window.ui = (function () { // eslint-disable-line no-unused-vars } }; - iframe.onResize = watchFunction(iframe.onResize.bind(iframe)); - - /** Represents the shadow overlay that covers the whole page */ - let WholePageOverlay = exports.WholePageOverlay = { + iframeSelection.onResize = watchFunction(onResize.bind(iframeSelection)); - el: null, - movingEl: null, - isScrollTracking: false, - - display: function (callbacks) { - this.remove(); - this.el = makeEl("div", "preview-overlay"); - this.el.innerHTML = ` -
-
-
-
-
-
-
-
-
-
- `; - this.el.querySelector(".preview-instructions").textContent = browser.i18n.getMessage("screenshotInstructions"); - this.el.querySelector(".myshots-text").textContent = browser.i18n.getMessage("myShotsLink"); - this.el.querySelector(".visible").textContent = browser.i18n.getMessage("saveScreenshotVisibleArea"); - this.el.querySelector(".full-page").textContent = browser.i18n.getMessage("saveScreenshotFullPage"); - this.el.querySelector(".myshots-button").addEventListener( - "click", watchFunction(callbacks.onOpenMyShots), false); - this.el.querySelector(".visible").addEventListener( - "click", watchFunction(callbacks.onClickVisible), false); - this.el.querySelector(".full-page").addEventListener( - "click", watchFunction(callbacks.onClickFullPage), false); - this.movingEl = this.el.querySelector(".fixed-container"); - iframe.document.body.appendChild(this.el); - this.resetPosition(); - window.addEventListener("scroll", this.onScroll, false); + let iframePreSelection = exports.iframePreSelection = { + element: null, + document: null, + sizeTracking: { + windowDelayer: null, + resizeBound: null, + lastHeight: null, + lastWidth: null + }, + display: function (installHandlerOnDocument, standardOverlayCallbacks) { + return new Promise((resolve, reject) => { + if (! this.element) { + this.element = document.createElement("iframe"); + this.element.id = "firefox-screenshots-preselection-iframe"; + this.element.style.zIndex = "99999999999"; + this.element.style.border = "none"; + this.element.style.position = "fixed"; + this.element.style.top = "0"; + this.element.style.left = "0"; + this.element.style.margin = "0"; + this.element.scrolling = "no"; + this.updateElementSize(); + this.element.onload = watchFunction(() => { + let parsedDom = (new DOMParser()).parseFromString(` + + + + + + +
+
+
+
+
+
+
+
+
+
+
+
+ + `, + "text/html" + ); + this.document = this.element.contentDocument; + this.document.replaceChild( + this.document.adoptNode(parsedDom.documentElement), + this.document.documentElement + ); + installHandlerOnDocument(this.document); + if (this.addClassName) { + this.document.body.className = this.addClassName; + } + const overlay = this.document.querySelector(".preview-overlay"); + overlay.querySelector(".preview-instructions").textContent = browser.i18n.getMessage("screenshotInstructions"); + overlay.querySelector(".myshots-text").textContent = browser.i18n.getMessage("myShotsLink"); + overlay.querySelector(".visible").textContent = browser.i18n.getMessage("saveScreenshotVisibleArea"); + overlay.querySelector(".full-page").textContent = browser.i18n.getMessage("saveScreenshotFullPage"); + overlay.querySelector(".myshots-button").addEventListener( + "click", watchFunction(standardOverlayCallbacks.onOpenMyShots), false); + overlay.querySelector(".visible").addEventListener( + "click", watchFunction(standardOverlayCallbacks.onClickVisible), false); + overlay.querySelector(".full-page").addEventListener( + "click", watchFunction(standardOverlayCallbacks.onClickFullPage), false); + resolve(); + }); + document.body.appendChild(this.element); + this.unhide(); + } else { + resolve(); + } + }); }, - resetPosition: function () { - if (! this.movingEl) { - return; + updateElementSize: function (force) { + // Note: if someone sizes down the page, then the iframe will keep the + // document from naturally shrinking. We use force to temporarily hide + // the element so that we can tell if the document shrinks + const visible = this.element.style.display !== "none"; + if (force && visible) { + this.element.style.display = "none"; + } + let height = Math.max( + document.documentElement.clientHeight, + window.innerHeight); + if (height !== this.sizeTracking.lastHeight) { + this.sizeTracking.lastHeight = height; + this.element.style.height = height + "px"; + } + let width = Math.max( + document.documentElement.clientWidth, + window.innerWidth); + if (width !== this.sizeTracking.lastWidth) { + this.sizeTracking.lastWidth = width; + this.element.style.width = width + "px"; + } + if (force && visible) { + this.element.style.display = ""; } - let scrollX = window.scrollX; - let scrollY = window.scrollY; - this.movingEl.style.top = scrollY + "px"; - this.movingEl.style.left = scrollX + "px"; - this.movingEl.style.width = window.innerWidth + "px"; - this.movingEl.style.height = window.innerHeight + "px"; + }, + + hide: function () { + window.removeEventListener("scroll", this.onScroll, false); + window.removeEventListener("resize", this.onResize, true); + this.element.style.display = "none"; + }, + + unhide: function () { + this.updateElementSize(); + window.addEventListener("scroll", this.onScroll, false); + window.addEventListener("resize", this.onResize, true); + this.element.style.display = ""; }, onScroll: function () { - this.resetPosition(); - /* Note, if we used requestAnimationFrame we'd improve the performance - some, but this creates very visible lag in the positioning: */ - /* - if (! this.isScrollTracking) { - window.requestAnimationFrame(() => { - this.resetPosition(); - this.isScrollTracking = false; - }); - this.isScrollTracking = true; + exports.HoverBox.hide(); + }, + + getElementFromPoint: function (x, y) { + this.element.style.pointerEvents = "none"; + let el; + try { + el = document.elementFromPoint(x, y); + } finally { + this.element.style.pointerEvents = ""; } - */ + return el; }, remove: function () { - util.removeNode(this.el); - this.el = null; - this.movingEl = null; - window.removeEventListener("scroll", this.onScroll, false); + this.hide(); + util.removeNode(this.element); + this.element = null; + this.document = null; } - }; - WholePageOverlay.onScroll = watchFunction(WholePageOverlay.onScroll.bind(WholePageOverlay)); + iframePreSelection.onResize = watchFunction(onResize.bind(iframePreSelection)); + + let iframe = exports.iframe = { + currentIframe: iframePreSelection, + display: function (installHandlerOnDocument, standardOverlayCallbacks) { + return iframeSelection.display(installHandlerOnDocument) + .then(() => iframePreSelection.display(installHandlerOnDocument, standardOverlayCallbacks)) + }, + + hide: function () { + this.currentIframe.hide(); + }, + + unhide: function () { + this.currentIframe.unhide(); + }, + + getElementFromPoint: function (x, y) { + return this.currentIframe.getElementFromPoint(x, y); + }, + + remove: function () { + iframeSelection.remove(); + iframePreSelection.remove(); + }, + + document: function () { + return this.currentIframe.document; + }, + + useSelection: function () { + if (this.currentIframe === iframePreSelection) { + this.hide(); + } + this.currentIframe = iframeSelection; + this.unhide(); + }, + + usePreSelection: function () { + if (this.currentIframe === iframeSelection) { + this.hide(); + } + this.currentIframe = iframePreSelection; + this.unhide(); + } + }; let movements = ["topLeft", "top", "topRight", "left", "right", "bottomLeft", "bottom", "bottomRight"]; @@ -357,7 +470,6 @@ window.ui = (function () { // eslint-disable-line no-unused-vars this.bgRight.style.height = pos.bottom - pos.top + "px"; this.bgRight.style.left = (pos.right - bodyRect.left) + "px"; this.bgRight.style.width = docWidth - (pos.right - bodyRect.left) + "px"; - WholePageOverlay.remove(); }, remove: function () { @@ -397,14 +509,14 @@ window.ui = (function () { // eslint-disable-line no-unused-vars boxEl.appendChild(elTarget); } this.bgTop = makeEl("div", "bghighlight"); - iframe.document.body.appendChild(this.bgTop); + iframe.document().body.appendChild(this.bgTop); this.bgLeft = makeEl("div", "bghighlight"); - iframe.document.body.appendChild(this.bgLeft); + iframe.document().body.appendChild(this.bgLeft); this.bgRight = makeEl("div", "bghighlight"); - iframe.document.body.appendChild(this.bgRight); + iframe.document().body.appendChild(this.bgRight); this.bgBottom = makeEl("div", "bghighlight"); - iframe.document.body.appendChild(this.bgBottom); - iframe.document.body.appendChild(boxEl); + iframe.document().body.appendChild(this.bgBottom); + iframe.document().body.appendChild(boxEl); this.el = boxEl; }, @@ -463,7 +575,7 @@ window.ui = (function () { // eslint-disable-line no-unused-vars display: function (rect) { if (! this.el) { this.el = makeEl("div", "hover-highlight"); - iframe.document.body.appendChild(this.el); + iframe.document().body.appendChild(this.el); } this.el.style.display = ""; this.el.style.top = (rect.top - 1) + "px"; @@ -495,7 +607,7 @@ window.ui = (function () { // eslint-disable-line no-unused-vars this.el.appendChild(this.xEl); this.yEl = makeEl("div"); this.el.appendChild(this.yEl); - iframe.document.body.appendChild(this.el); + iframe.document().body.appendChild(this.el); } this.xEl.textContent = x; this.yEl.textContent = y; @@ -511,7 +623,7 @@ window.ui = (function () { // eslint-disable-line no-unused-vars /** Removes every UI this module creates */ exports.remove = function () { for (let name in exports) { - if (name == "iframe") { + if (name.startsWith("iframe")) { continue; } if (typeof exports[name] == "object" && exports[name].remove) { @@ -519,7 +631,6 @@ window.ui = (function () { // eslint-disable-line no-unused-vars } } exports.iframe.remove(); - exports.Box.remove(); }; exports.triggerDownload = function (dataUrl, filename) { diff --git a/addon/webextension/selector/uicontrol.js b/addon/webextension/selector/uicontrol.js index bca82384d9..5cd23abb51 100644 --- a/addon/webextension/selector/uicontrol.js +++ b/addon/webextension/selector/uicontrol.js @@ -309,12 +309,7 @@ window.uicontrol = (function () { if (! rect) { return null; } - return new Selection( - rect.left + window.scrollX, - rect.top + window.scrollY, - rect.right + window.scrollX, - rect.bottom + window.scrollY - ); + return new Selection(rect.left, rect.top, rect.right, rect.bottom); }; /** Represents a single x/y point, typically for a mouse click that doesn't have a drag: */ @@ -347,8 +342,8 @@ window.uicontrol = (function () { start: function () { selectedPos = mousedownPos = null; this.cachedEl = null; + ui.iframe.usePreSelection(); ui.Box.remove(); - ui.WholePageOverlay.display(standardOverlayCallbacks); const handler = watchFunction(keyupHandler); document.addEventListener("keyup", handler, false); registeredDocumentHandlers.push({name: "keyup", doc: document, handler}); @@ -367,8 +362,8 @@ window.uicontrol = (function () { if (event.target.classList.contains("preview-overlay")) { // The hover is on the overlay, so we need to figure out the real element el = ui.iframe.getElementFromPoint( - event.pageX - window.pageXOffset, - event.pageY - window.pageYOffset + event.pageX + window.scrollX - window.pageXOffset, + event.pageY + window.scrollY - window.pageYOffset ); } else { // The hover is on the element we care about, so we use that @@ -503,8 +498,8 @@ window.uicontrol = (function () { maxAutoElementHeight: 600, start: function () { + ui.iframe.usePreSelection(); ui.Box.remove(); - ui.WholePageOverlay.display(standardOverlayCallbacks); }, mousemove: function (event) { @@ -528,8 +523,13 @@ window.uicontrol = (function () { } if (autoDetectRect) { selectedPos = autoDetectRect; + selectedPos.x1 += window.scrollX; + selectedPos.y1 += window.scrollY; + selectedPos.x2 += window.scrollX; + selectedPos.y2 += window.scrollY; autoDetectRect = null; mousedownPos = null; + ui.iframe.useSelection(); ui.Box.display(selectedPos, standardDisplayCallbacks); sendEvent("make-selection", "selection-click", eventOptionsForBox(selectedPos)); setState("selected"); @@ -584,8 +584,8 @@ window.uicontrol = (function () { stateHandlers.dragging = { start: function () { + ui.iframe.useSelection(); ui.Box.display(selectedPos); - ui.WholePageOverlay.remove(); }, mousemove: function (event) { @@ -618,7 +618,7 @@ window.uicontrol = (function () { stateHandlers.selected = { start: function () { - ui.WholePageOverlay.remove(); + ui.iframe.useSelection(); }, mousedown: function (event) { @@ -636,8 +636,7 @@ window.uicontrol = (function () { stateHandlers.resizing.startResize(event, "move"); } else if (! ui.Box.isControl(target)) { mousedownPos = new Pos(event.pageX, event.pageY); - mouseupNoAutoselect = true; - setState("draggingReady"); + setState("crosshairs"); } event.preventDefault(); return false; @@ -646,7 +645,7 @@ window.uicontrol = (function () { stateHandlers.resizing = { start: function () { - ui.WholePageOverlay.remove(); + ui.iframe.useSelection(); selectedPos.sortCoords(); }, @@ -723,7 +722,7 @@ window.uicontrol = (function () { stateHandlers.cancel = { start: function () { - ui.WholePageOverlay.remove(); + ui.iframe.hide(); ui.Box.remove(); } }; @@ -761,13 +760,12 @@ window.uicontrol = (function () { */ exports.activate = function () { - ui.Box.remove(); addHandlers(); // FIXME: self.options is gone if (self.options && self.options.styleMyShotsButton) { ui.iframe.addClassName = `styleMyShotsButton-${self.options.styleMyShotsButton.value}`; } - watchPromise(ui.iframe.display(installHandlersOnDocument).then(() => { + watchPromise(ui.iframe.display(installHandlersOnDocument, standardOverlayCallbacks).then(() => { setState("crosshairs"); })); }