--- 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);