Bug 1381132 - Export Screenshots 10.7.0 to Firefox; r=kmag draft
authorJared Hirsch <ohai@6a68.net>
Mon, 17 Jul 2017 16:49:34 -0700
changeset 610192 7ad707faa5898dec228cf6d9af575fef0f2c4390
parent 609865 e0b0865639cebc1b5afa0268a4b073fcdde0e69c
child 637777 17a574e0cf736c5d90284e1a947d222afd74c2bd
push id68799
push userbmo:jhirsch@mozilla.com
push dateMon, 17 Jul 2017 23:54:08 +0000
reviewerskmag
bugs1381132
milestone56.0a1
Bug 1381132 - Export Screenshots 10.7.0 to Firefox; r=kmag MozReview-Commit-ID: 49iAxm2BiQA
browser/extensions/screenshots/install.rdf
browser/extensions/screenshots/moz.build
browser/extensions/screenshots/webextension/assertIsBlankDocument.js
browser/extensions/screenshots/webextension/background/selectorLoader.js
browser/extensions/screenshots/webextension/background/startBackground.js
browser/extensions/screenshots/webextension/clipboard.js
browser/extensions/screenshots/webextension/icons/icon-starred-32-v2.svg
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.7.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/assertIsBlankDocument.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/assertIsBlankDocument.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.assertIsBlankDocument = function assertIsBlankDocument(doc) {
+  if (doc.documentURI !== browser.extension.getURL("blank.html")) {
+    let exc = new Error('iframe URL does not match expected blank.html');
+    exc.foundURL = doc.documentURI;
+    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",
+    "assertIsBlankDocument.js",
     "background/selectorLoader.js",
     "selector/callBackground.js",
     "selector/util.js"
   ];
 
   const selectorScripts = [
     "clipboard.js",
     "makeUuid.js",
--- a/browser/extensions/screenshots/webextension/background/startBackground.js
+++ b/browser/extensions/screenshots/webextension/background/startBackground.js
@@ -50,17 +50,17 @@ this.startBackground = (function() {
     });
   });
 
   // Note this duplicates functionality in main.js, but we need to change
   // the onboarding icon before main.js loads up
   browser.storage.local.get(["hasSeenOnboarding"]).then((result) => {
     let hasSeenOnboarding = !!result.hasSeenOnboarding;
     if (!hasSeenOnboarding) {
-      let path = "icons/icon-starred-32.svg";
+      let path = "icons/icon-starred-32-v2.svg";
       browser.browserAction.setIcon({path});
     }
   }).catch((error) => {
     console.error("Error loading Screenshots onboarding flag:", error);
   });
 
   browser.runtime.onMessage.addListener((req, sender, sendResponse) => {
     loadIfNecessary().then(() => {
--- a/browser/extensions/screenshots/webextension/clipboard.js
+++ b/browser/extensions/screenshots/webextension/clipboard.js
@@ -1,23 +1,41 @@
-/* globals catcher */
+/* globals catcher, assertIsBlankDocument */
 
 "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.addEventListener("load", catcher.watchFunction(() => {
+        try {
+          let doc = element.contentDocument;
+          assertIsBlankDocument(doc);
+          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"));
+          }
+          el.remove();
+          resolve(copied);
+        } finally {
+          element.remove();
+        }
+      }), {once: true});
+      document.body.appendChild(element);
+    });
   };
 
   return exports;
 })();
 null;
--- a/browser/extensions/screenshots/webextension/icons/icon-starred-32-v2.svg
+++ b/browser/extensions/screenshots/webextension/icons/icon-starred-32-v2.svg
@@ -1,1 +1,1 @@
-<svg width="32" height="32" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg"><title>icon copy 2-32</title><g fill="none" fill-rule="evenodd"><g fill="context-fill"><path d="M8 2a4 4 0 0 0-4 4h4V2zm6 0h-4v4h4V2zm14 22a4 4 0 0 0 4-4h-4v4zm-12.2.642l6.034 4.6a4 4 0 0 0 5.57-.984L19.28 22.2l-3.48 2.442z" fill-rule="nonzero"/><path d="M21.86 17.437L9.9 26.016a4.988 4.988 0 1 1-2.3-3.266l2.8-1.964-2.484-1.738A5 5 0 1 1 4 10.1V8h4v3.022A4.976 4.976 0 0 1 10 15c-.008.196-.027.39-.058.584l3.936 2.76 4.46-3.292a9.014 9.014 0 0 0 3.522 2.385zM5 17.5a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zm0 12a2.5 2.5 0 1 0 0-5 2.5 2.5 0 0 0 0 5zM16.512 6H16V2h3.343a9.018 9.018 0 0 0-2.83 4zM28 17.488V18h4v-3.343a9.018 9.018 0 0 1-4 2.83z"/></g><g transform="translate(18 2)"><circle fill="#00FEFF" cx="7" cy="7" r="7"/><path d="M7 2c.332 0 .765.182.89.694l.401 1.653.075.31.308-.09 1.645-.482a.906.906 0 0 1 1.113.542.868.868 0 0 1-.223.985L9.965 6.783l-.232.22.232.218 1.244 1.172a.869.869 0 0 1 .223.984.906.906 0 0 1-1.113.542l-1.645-.481-.308-.09-.075.31-.402 1.652c-.124.513-.557.694-.889.694-.332 0-.765-.181-.89-.694L5.71 9.657l-.075-.31-.308.091-1.645.481a.906.906 0 0 1-1.113-.542.869.869 0 0 1 .223-.984L4.035 7.22l.232-.219-.232-.219L2.79 5.612a.868.868 0 0 1-.223-.985.906.906 0 0 1 1.113-.542l1.645.481.308.09.075-.309.402-1.653C6.235 2.182 6.668 2 7 2" fill="#005A71"/></g></g></svg>
+<svg data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" width="32" height="32"><title>icon-starred-32-v2</title><path fill="context-fill" d="M8 2a4 4 0 0 0-4 4h4zm6 0h-4v4h4zm14 22a4 4 0 0 0 4-4h-4zm-12.2.64l6 4.6a4 4 0 0 0 5.57-1l-8.09-6.04zM21.86 17.44L9.9 26a5 5 0 1 1-2.3-3.27l2.8-2L7.92 19A5 5 0 1 1 4 10.1V8h4v3a5 5 0 0 1 2 4 5 5 0 0 1-.06.58l3.94 2.76 4.46-3.29a9 9 0 0 0 3.52 2.39zM5 17.5A2.5 2.5 0 1 0 2.5 15 2.5 2.5 0 0 0 5 17.5zm0 12A2.5 2.5 0 1 0 2.5 27 2.5 2.5 0 0 0 5 29.5zM16.51 6H16V2h3.34a9 9 0 0 0-2.83 4zM28 17.49V18h4v-3.34a9 9 0 0 1-4 2.83z"/><circle fill="#00feff" cx="25" cy="9" r="7"/><path fill="#005a71" d="M25 4a.89.89 0 0 1 .89.69l.4 1.65.07.31.31-.09 1.64-.48a.91.91 0 0 1 1.11.54.87.87 0 0 1-.22 1L28 8.78l-.27.22.23.22 1.24 1.17a.87.87 0 0 1 .22 1 .91.91 0 0 1-1.11.54l-1.64-.48-.31-.09-.07.31-.4 1.65a.92.92 0 0 1-1.78 0l-.4-1.65-.07-.31-.31.09-1.64.48a.91.91 0 0 1-1.11-.54.87.87 0 0 1 .22-1L22 9.22l.27-.22-.27-.22-1.21-1.17a.87.87 0 0 1-.22-1 .91.91 0 0 1 1.11-.54l1.64.48.31.09.07-.31.4-1.65A.89.89 0 0 1 25 4"/></svg>
--- 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.7.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, assertIsBlankDocument */
 
 "use strict";
 
 this.slides = (function() {
   let exports = {};
 
   const { watchFunction } = catcher;
 
@@ -30,33 +30,34 @@ this.slides = (function() {
       iframe.style.left = "0";
       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.addEventListener("load", catcher.watchFunction(() => {
+        doc = iframe.contentDocument;
+        assertIsBlankDocument(doc);
         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);
         activateSlide(doc);
         resolve();
-      });
+      }), {once: true});
       document.body.appendChild(iframe);
       iframe.focus();
       window.addEventListener("resize", onResize);
     });
   };
 
   exports.remove = exports.unload = function() {
     window.removeEventListener("resize", onResize);
--- 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, assertIsBlankDocument */
 
 "use strict";
 
 this.ui = (function() { // eslint-disable-line no-unused-vars
   let exports = {};
   const SAVE_BUTTON_HEIGHT = 50;
 
   const { watchFunction } = catcher;
@@ -87,32 +87,33 @@ this.ui = (function() { // eslint-disabl
           this.element.style.zIndex = "99999999999";
           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.addEventListener("load", watchFunction(() => {
             this.document = this.element.contentDocument;
+            assertIsBlankDocument(this.document);
             this.document.documentElement.innerHTML = `
                <head>
                 <style>${substitutedCss}</style>
                 <title></title>
                </head>
                <body></body>`;
             installHandlerOnDocument(this.document);
             if (this.addClassName) {
               this.document.body.className = this.addClassName;
             }
             this.document.documentElement.dir = browser.i18n.getMessage("@@bidi_dir");
             this.document.documentElement.lang = browser.i18n.getMessage("@@ui_locale");
             resolve();
-          });
+          }), {once: true});
           document.body.appendChild(this.element);
         } else {
           resolve();
         }
       });
     },
 
     hide() {
@@ -158,30 +159,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 +193,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) {
@@ -214,18 +215,19 @@ this.ui = (function() { // eslint-disabl
           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(() => {
+          this.element.addEventListener("load", watchFunction(() => {
             this.document = this.element.contentDocument;
+            assertIsBlankDocument(this.document)
             this.document.documentElement.innerHTML = `
                <head>
                 <style>${substitutedCss}</style>
                 <title></title>
                </head>
                <body>
                  <div class="preview-overlay">
                    <div class="fixed-container">
@@ -257,17 +259,17 @@ this.ui = (function() { // eslint-disabl
             overlay.querySelector(".full-page").textContent = browser.i18n.getMessage("saveScreenshotFullPage");
             overlay.querySelector(".myshots-button").addEventListener(
               "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onOpenMyShots)));
             overlay.querySelector(".visible").addEventListener(
               "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickVisible)));
             overlay.querySelector(".full-page").addEventListener(
               "click", watchFunction(assertIsTrusted(standardOverlayCallbacks.onClickFullPage)));
             resolve();
-          });
+          }), {once: true});
           document.body.appendChild(this.element);
           this.unhide();
         } else {
           resolve();
         }
       });
     },
 
@@ -277,26 +279,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);