Bug 1381132 - Export Screenshots 10.6.0 to Firefox; r?Mossop draft
authorIan Bicking <ianb@colorstudy.com>
Fri, 14 Jul 2017 17:07:10 -0400
changeset 609135 14e286338efbc2780546979534aedc4ca0641bff
parent 609034 3688dec833831eb553030b9b65c9c93ba39267fe
child 637512 e4ae7c91a0eccdb4a1953498ef4a1392eb73fc01
push id68514
push userbmo:ianb@mozilla.com
push dateFri, 14 Jul 2017 21:07:57 +0000
reviewersMossop
bugs1381132
milestone56.0a1
Bug 1381132 - Export Screenshots 10.6.0 to Firefox; r?Mossop MozReview-Commit-ID: 8tfKRXYZVnT
browser/extensions/screenshots/install.rdf
browser/extensions/screenshots/moz.build
browser/extensions/screenshots/webextension/assertIsBlankDocumentUrl.js
browser/extensions/screenshots/webextension/background/selectorLoader.js
browser/extensions/screenshots/webextension/clipboard.js
browser/extensions/screenshots/webextension/manifest.json
browser/extensions/screenshots/webextension/onboarding/slides.js
browser/extensions/screenshots/webextension/selector/shooter.js
browser/extensions/screenshots/webextension/selector/ui.js
browser/extensions/screenshots/webextension/selector/uicontrol.js
--- a/browser/extensions/screenshots/install.rdf
+++ b/browser/extensions/screenshots/install.rdf
@@ -7,14 +7,14 @@
     <em:targetApplication>
       <Description>
         <em:id>{ec8030f7-c20a-464f-9b0e-13a3a9e97384}</em:id> <!--Firefox-->
         <em:minVersion>51.0a1</em:minVersion>
         <em:maxVersion>*</em:maxVersion>
       </Description>
     </em:targetApplication>
     <em:type>2</em:type>
-    <em:version>10.5.0</em:version>
+    <em:version>10.6.0</em:version>
     <em:bootstrap>true</em:bootstrap>
     <em:homepageURL>https://pageshot.net/</em:homepageURL>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
   </Description>
 </RDF>
--- a/browser/extensions/screenshots/moz.build
+++ b/browser/extensions/screenshots/moz.build
@@ -7,16 +7,17 @@
 FINAL_TARGET_FILES.features['screenshots@mozilla.org'] += [
   'bootstrap.js',
   'install.rdf'
 ]
 
 # This file list is automatically generated by Screenshots' export scripts.
 # AUTOMATIC INSERTION START
 FINAL_TARGET_FILES.features['screenshots@mozilla.org']["webextension"] += [
+  'webextension/assertIsBlankDocumentUrl.js',
   'webextension/assertIsTrusted.js',
   'webextension/blank.html',
   'webextension/catcher.js',
   'webextension/clipboard.js',
   'webextension/domainFromUrl.js',
   'webextension/log.js',
   'webextension/makeUuid.js',
   'webextension/manifest.json',
new file mode 100644
--- /dev/null
+++ b/browser/extensions/screenshots/webextension/assertIsBlankDocumentUrl.js
@@ -0,0 +1,12 @@
+/** For use inside an iframe onload function, throws an Error if iframe src is not blank.html
+
+    Should be applied *inside* catcher.watchFunction
+*/
+this.assertIsBlankDocumentUrl = function assertIsBlankDocumentUrl(documentUrl) {
+  if (documentUrl !== browser.extension.getURL("blank.html")) {
+    let exc = new Error('iframe URL does not match expected blank.html');
+    exc.foundURL = documentUrl;
+    throw exc;
+  }
+}
+null;
--- a/browser/extensions/screenshots/webextension/background/selectorLoader.js
+++ b/browser/extensions/screenshots/webextension/background/selectorLoader.js
@@ -9,16 +9,17 @@ this.selectorLoader = (function() {
 
   // These modules are loaded in order, first standardScripts, then optionally onboardingScripts, and then selectorScripts
   // The order is important due to dependencies
   const standardScripts = [
     "build/buildSettings.js",
     "log.js",
     "catcher.js",
     "assertIsTrusted.js",
+    "assertIsBlankDocumentUrl.js",
     "background/selectorLoader.js",
     "selector/callBackground.js",
     "selector/util.js"
   ];
 
   const selectorScripts = [
     "clipboard.js",
     "makeUuid.js",
--- a/browser/extensions/screenshots/webextension/clipboard.js
+++ b/browser/extensions/screenshots/webextension/clipboard.js
@@ -1,23 +1,42 @@
-/* globals catcher */
+/* globals catcher, assertIsBlankDocumentUrl */
 
 "use strict";
 
 this.clipboard = (function() {
   let exports = {};
 
   exports.copy = function(text) {
-    let el = document.createElement("textarea");
-    document.body.appendChild(el);
-    el.value = text;
-    el.select();
-    const copied = document.execCommand("copy");
-    document.body.removeChild(el);
-    if (!copied) {
-      catcher.unhandled(new Error("Clipboard copy failed"));
-    }
-    return copied;
+    return new Promise((resolve, reject) => {
+      let element = document.createElement("iframe");
+      element.src = browser.extension.getURL("blank.html");
+      // We can't actually hide the iframe while copying, but we can make
+      // it close to invisible:
+      element.style.opacity = "0";
+      element.style.width = "1px";
+      element.style.height = "1px";
+      element.onload = catcher.watchFunction(() => {
+        try {
+          element.onload = null;
+          let doc = element.contentDocument;
+          assertIsBlankDocumentUrl(doc.URL);
+          let el = doc.createElement("textarea");
+          doc.body.appendChild(el);
+          el.value = text;
+          el.select();
+          const copied = doc.execCommand("copy");
+          if (!copied) {
+            catcher.unhandled(new Error("Clipboard copy failed"));
+          }
+          doc.body.removeChild(el);
+          resolve(copied);
+        } finally {
+          document.body.removeChild(element);
+        }
+      });
+      document.body.appendChild(element);
+    });
   };
 
   return exports;
 })();
 null;
--- a/browser/extensions/screenshots/webextension/manifest.json
+++ b/browser/extensions/screenshots/webextension/manifest.json
@@ -1,12 +1,12 @@
 {
   "manifest_version": 2,
   "name": "Firefox Screenshots",
-  "version": "10.5.0",
+  "version": "10.6.0",
   "description": "__MSG_addonDescription__",
   "author": "__MSG_addonAuthorsList__",
   "homepage_url": "https://github.com/mozilla-services/screenshots",
   "applications": {
     "gecko": {
       "id": "screenshots@mozilla.org"
     }
   },
--- a/browser/extensions/screenshots/webextension/onboarding/slides.js
+++ b/browser/extensions/screenshots/webextension/onboarding/slides.js
@@ -1,9 +1,9 @@
-/* globals log, catcher, onboardingHtml, onboardingCss, util, shooter, callBackground, assertIsTrusted */
+/* globals log, catcher, onboardingHtml, onboardingCss, util, shooter, callBackground, assertIsTrusted, assertIsBlankDocumentUrl */
 
 "use strict";
 
 this.slides = (function() {
   let exports = {};
 
   const { watchFunction } = catcher;
 
@@ -31,21 +31,23 @@ this.slides = (function() {
       iframe.style.margin = "0";
       iframe.scrolling = "no";
       updateIframeSize();
       let html = onboardingHtml.replace('<style></style>', `<style>${onboardingCss}</style>`);
       html = html.replace(/MOZ_EXTENSION([^\"]+)/g, (match, filename) => {
         return browser.extension.getURL(filename);
       });
       iframe.onload = catcher.watchFunction(() => {
+        iframe.onload = null;
+        doc = iframe.contentDocument;
+        assertIsBlankDocumentUrl(doc.URL);
         let parsedDom = (new DOMParser()).parseFromString(
           html,
           "text/html"
         );
-        doc = iframe.contentDocument;
         doc.replaceChild(
           doc.adoptNode(parsedDom.documentElement),
           doc.documentElement
         );
         doc.addEventListener("keyup", onKeyUp);
         doc.documentElement.dir = browser.i18n.getMessage("@@bidi_dir");
         doc.documentElement.lang = browser.i18n.getMessage("@@ui_locale");
         localizeText(doc);
--- a/browser/extensions/screenshots/webextension/selector/shooter.js
+++ b/browser/extensions/screenshots/webextension/selector/shooter.js
@@ -117,18 +117,19 @@ this.shooter = (function() { // eslint-d
         scrollY: window.scrollY,
         innerHeight: window.innerHeight,
         innerWidth: window.innerWidth
       },
       selectedPos,
       shotId: shotObject.id,
       shot: shotObject.asJson()
     }).then((url) => {
-      const copied = clipboard.copy(url);
-      return callBackground("openShot", { url, copied });
+      return clipboard.copy(url).then((copied) => {
+        return callBackground("openShot", { url, copied });
+      });
     }, (error) => {
       if ('popupMessage' in error && (error.popupMessage == "REQUEST_ERROR" || error.popupMessage == 'CONNECTION_ERROR')) {
         // The error has been signaled to the user, but unlike other errors (or
         // success) we should not abort the selection
         deactivateAfterFinish = false;
         return;
       }
       if (error.name != "BackgroundError") {
--- a/browser/extensions/screenshots/webextension/selector/ui.js
+++ b/browser/extensions/screenshots/webextension/selector/ui.js
@@ -1,9 +1,9 @@
-/* globals log, util, catcher, inlineSelectionCss, callBackground, assertIsTrusted */
+/* globals log, util, catcher, inlineSelectionCss, callBackground, assertIsTrusted, assertIsBlankDocumentUrl */
 
 "use strict";
 
 this.ui = (function() { // eslint-disable-line no-unused-vars
   let exports = {};
   const SAVE_BUTTON_HEIGHT = 50;
 
   const { watchFunction } = catcher;
@@ -88,17 +88,19 @@ this.ui = (function() { // eslint-disabl
           this.element.style.border = "none";
           this.element.style.position = "absolute";
           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(() => {
+            this.element.onload = null;
             this.document = this.element.contentDocument;
+            assertIsBlankDocumentUrl(this.document.URL);
             this.document.documentElement.innerHTML = `
                <head>
                 <style>${substitutedCss}</style>
                 <title></title>
                </head>
                <body></body>`;
             installHandlerOnDocument(this.document);
             if (this.addClassName) {
@@ -158,30 +160,30 @@ this.ui = (function() { // eslint-disabl
       if (force && visible) {
         this.element.style.display = "";
       }
     },
 
     initSizeWatch() {
       this.stopSizeWatch();
       this.sizeTracking.timer = setInterval(watchFunction(this.updateElementSize.bind(this)), 2000);
-      window.addEventListener("resize", this.onResize, true);
+      window.addEventListener("resize", watchFunction(assertIsTrusted(this.onResize)), true);
     },
 
     stopSizeWatch() {
       if (this.sizeTracking.timer) {
         clearTimeout(this.sizeTracking.timer);
         this.sizeTracking.timer = null;
       }
       if (this.sizeTracking.windowDelayer) {
         clearTimeout(this.sizeTracking.windowDelayer);
         this.sizeTracking.windowDelayer = null;
       }
       this.sizeTracking.lastHeight = this.sizeTracking.lastWidth = null;
-      window.removeEventListener("resize", this.onResize, true);
+      window.removeEventListener("resize", watchFunction(assertIsTrusted(this.onResize)), true);
     },
 
     getElementFromPoint(x, y) {
       this.element.style.pointerEvents = "none";
       let el;
       try {
         el = document.elementFromPoint(x, y);
       } finally {
@@ -192,17 +194,17 @@ this.ui = (function() { // eslint-disabl
 
     remove() {
       this.stopSizeWatch();
       util.removeNode(this.element);
       this.element = this.document = null;
     }
   };
 
-  iframeSelection.onResize = watchFunction(onResize.bind(iframeSelection));
+  iframeSelection.onResize = watchFunction(assertIsTrusted(onResize.bind(iframeSelection)));
 
   let iframePreSelection = exports.iframePreSelection = {
     element: null,
     document: null,
     sizeTracking: {
       windowDelayer: null
     },
     display(installHandlerOnDocument, standardOverlayCallbacks) {
@@ -215,17 +217,19 @@ this.ui = (function() { // eslint-disabl
           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(() => {
+            this.element.onload = null;
             this.document = this.element.contentDocument;
+            assertIsBlankDocumentUrl(this.document.URL)
             this.document.documentElement.innerHTML = `
                <head>
                 <style>${substitutedCss}</style>
                 <title></title>
                </head>
                <body>
                  <div class="preview-overlay">
                    <div class="fixed-container">
@@ -277,26 +281,26 @@ this.ui = (function() { // eslint-disabl
         // time-delay
         return;
       }
       this.element.style.height = window.innerHeight + "px";
       this.element.style.width = window.innerWidth + "px";
     },
 
     hide() {
-      window.removeEventListener("scroll", this.onScroll);
+      window.removeEventListener("scroll", watchFunction(assertIsTrusted(this.onScroll)));
       window.removeEventListener("resize", this.onResize, true);
       if (this.element) {
         this.element.style.display = "none";
       }
     },
 
     unhide() {
       this.updateElementSize();
-      window.addEventListener("scroll", this.onScroll);
+      window.addEventListener("scroll", watchFunction(assertIsTrusted(this.onScroll)));
       window.addEventListener("resize", this.onResize, true);
       this.element.style.display = "";
       this.element.focus();
     },
 
     onScroll() {
       exports.HoverBox.hide();
     },
--- a/browser/extensions/screenshots/webextension/selector/uicontrol.js
+++ b/browser/extensions/screenshots/webextension/selector/uicontrol.js
@@ -864,35 +864,35 @@ this.uicontrol = (function() {
    * Event handlers
    */
 
   let primedDocumentHandlers = new Map();
   let registeredDocumentHandlers = []
 
   function addHandlers() {
     ["mouseup", "mousedown", "mousemove", "click"].forEach((eventName) => {
-      let fn = watchFunction((function(eventName, event) {
+      let fn = watchFunction(assertIsTrusted((function(eventName, event) {
         if (typeof event.button == "number" && event.button !== 0) {
           // Not a left click
           return undefined;
         }
         if (event.ctrlKey || event.shiftKey || event.altKey || event.metaKey) {
           // Modified click of key
           return undefined;
         }
         let state = getState();
         let handler = stateHandlers[state];
         if (handler[eventName]) {
           return handler[eventName](event);
         }
         return undefined;
-      }).bind(null, eventName));
+      }).bind(null, eventName)));
       primedDocumentHandlers.set(eventName, fn);
     });
-    primedDocumentHandlers.set("keyup", keyupHandler);
+    primedDocumentHandlers.set("keyup", watchFunction(assertIsTrusted(keyupHandler)));
     window.addEventListener('beforeunload', beforeunloadHandler);
   }
 
   let mousedownSetOnDocument = false;
 
   function installHandlersOnDocument(docObj) {
     for (let [eventName, handler] of primedDocumentHandlers) {
       let watchHandler = watchFunction(handler);