Bug 1381132 - Export Screenshots 10.7.0 to Firefox; r=kmag
authorJared Hirsch <ohai@6a68.net>
Mon, 17 Jul 2017 16:49:34 -0700
changeset 418210 c0815bdf7b7226ac831f5843736a52c24154d019
parent 418209 61d6383a750a14d2f2e2b2381a06f2b9f5286caa
child 418211 962cf9dd104c650e014590b916689537c6fb1cb8
push id7566
push usermtabara@mozilla.com
push dateWed, 02 Aug 2017 08:25:16 +0000
treeherdermozilla-beta@86913f512c3c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskmag
bugs1381132
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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);