238 lines
7.1 KiB
JavaScript
238 lines
7.1 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
/* globals browser, getStrings, selectorLoader, analytics, communication, catcher, log, senderror, startBackground, blobConverters, startSelectionWithOnboarding */
|
|
|
|
"use strict";
|
|
|
|
this.main = (function() {
|
|
const exports = {};
|
|
|
|
const { incrementCount } = analytics;
|
|
|
|
const manifest = browser.runtime.getManifest();
|
|
let backend;
|
|
|
|
exports.setBackend = function(newBackend) {
|
|
backend = newBackend;
|
|
backend = backend.replace(/\/*$/, "");
|
|
};
|
|
|
|
exports.getBackend = function() {
|
|
return backend;
|
|
};
|
|
|
|
communication.register("getBackend", () => {
|
|
return backend;
|
|
});
|
|
|
|
for (const permission of manifest.permissions) {
|
|
if (/^https?:\/\//.test(permission)) {
|
|
exports.setBackend(permission);
|
|
break;
|
|
}
|
|
}
|
|
|
|
function toggleSelector(tab) {
|
|
return analytics
|
|
.refreshTelemetryPref()
|
|
.then(() => selectorLoader.toggle(tab.id))
|
|
.catch(error => {
|
|
if (
|
|
error.message &&
|
|
/Missing host permission for the tab/.test(error.message)
|
|
) {
|
|
error.noReport = true;
|
|
}
|
|
error.popupMessage = "UNSHOOTABLE_PAGE";
|
|
throw error;
|
|
});
|
|
}
|
|
|
|
// This is called by startBackground.js, where is registered as a click
|
|
// handler for the webextension page action.
|
|
exports.onClicked = catcher.watchFunction(tab => {
|
|
_startShotFlow(tab, "toolbar-button");
|
|
});
|
|
|
|
exports.onClickedContextMenu = catcher.watchFunction(tab => {
|
|
_startShotFlow(tab, "context-menu");
|
|
});
|
|
|
|
exports.onShortcut = catcher.watchFunction(tab => {
|
|
_startShotFlow(tab, "keyboard-shortcut");
|
|
});
|
|
|
|
const _startShotFlow = (tab, inputType) => {
|
|
if (!tab) {
|
|
// Not in a page/tab context, ignore
|
|
return;
|
|
}
|
|
if (!urlEnabled(tab.url)) {
|
|
senderror.showError({
|
|
popupMessage: "UNSHOOTABLE_PAGE",
|
|
});
|
|
return;
|
|
}
|
|
|
|
catcher.watchPromise(
|
|
toggleSelector(tab).catch(error => {
|
|
throw error;
|
|
})
|
|
);
|
|
};
|
|
|
|
function urlEnabled(url) {
|
|
// Allow screenshots on urls related to web pages in reader mode.
|
|
if (url && url.startsWith("about:reader?url=")) {
|
|
return true;
|
|
}
|
|
if (
|
|
isShotOrMyShotPage(url) ||
|
|
/^(?:about|data|moz-extension):/i.test(url) ||
|
|
isBlacklistedUrl(url)
|
|
) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
function isShotOrMyShotPage(url) {
|
|
// It's okay to take a shot of any pages except shot pages and My Shots
|
|
if (!url.startsWith(backend)) {
|
|
return false;
|
|
}
|
|
const path = url
|
|
.substr(backend.length)
|
|
.replace(/^\/*/, "")
|
|
.replace(/[?#].*/, "");
|
|
if (path === "shots") {
|
|
return true;
|
|
}
|
|
if (/^[^/]{1,4000}\/[^/]{1,4000}$/.test(path)) {
|
|
// Blocks {:id}/{:domain}, but not /, /privacy, etc
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isBlacklistedUrl(url) {
|
|
// These specific domains are not allowed for general WebExtension permission reasons
|
|
// Discussion: https://bugzilla.mozilla.org/show_bug.cgi?id=1310082
|
|
// List of domains copied from: https://searchfox.org/mozilla-central/source/browser/app/permissions#18-19
|
|
// Note we disable it here to be informative, the security check is done in WebExtension code
|
|
const badDomains = ["testpilot.firefox.com"];
|
|
let domain = url.replace(/^https?:\/\//i, "");
|
|
domain = domain.replace(/\/.*/, "").replace(/:.*/, "");
|
|
domain = domain.toLowerCase();
|
|
return badDomains.includes(domain);
|
|
}
|
|
|
|
communication.register("getStrings", (sender, ids) => {
|
|
return getStrings(ids.map(id => ({ id })));
|
|
});
|
|
|
|
communication.register("captureTelemetry", (sender, ...args) => {
|
|
catcher.watchPromise(incrementCount(...args));
|
|
});
|
|
|
|
communication.register("openShot", async (sender, { url, copied }) => {
|
|
if (copied) {
|
|
const id = crypto.randomUUID();
|
|
const [title, message] = await getStrings([
|
|
{ id: "screenshots-notification-link-copied-title" },
|
|
{ id: "screenshots-notification-link-copied-details" },
|
|
]);
|
|
return browser.notifications.create(id, {
|
|
type: "basic",
|
|
iconUrl: "chrome://browser/content/screenshots/copied-notification.svg",
|
|
title,
|
|
message,
|
|
});
|
|
}
|
|
return null;
|
|
});
|
|
|
|
communication.register("copyShotToClipboard", async (sender, blob) => {
|
|
let buffer = await blobConverters.blobToArray(blob);
|
|
await browser.clipboard.setImageData(buffer, blob.type.split("/", 2)[1]);
|
|
|
|
const [title, message] = await getStrings([
|
|
{ id: "screenshots-notification-image-copied-title" },
|
|
{ id: "screenshots-notification-image-copied-details" },
|
|
]);
|
|
|
|
catcher.watchPromise(incrementCount("copy"));
|
|
return browser.notifications.create({
|
|
type: "basic",
|
|
iconUrl: "chrome://browser/content/screenshots/copied-notification.svg",
|
|
title,
|
|
message,
|
|
});
|
|
});
|
|
|
|
communication.register("downloadShot", (sender, info) => {
|
|
// 'data:' urls don't work directly, let's use a Blob
|
|
// see http://stackoverflow.com/questions/40269862/save-data-uri-as-file-using-downloads-download-api
|
|
const blob = blobConverters.dataUrlToBlob(info.url);
|
|
const url = URL.createObjectURL(blob);
|
|
let downloadId;
|
|
const onChangedCallback = catcher.watchFunction(function(change) {
|
|
if (!downloadId || downloadId !== change.id) {
|
|
return;
|
|
}
|
|
if (change.state && change.state.current !== "in_progress") {
|
|
URL.revokeObjectURL(url);
|
|
browser.downloads.onChanged.removeListener(onChangedCallback);
|
|
}
|
|
});
|
|
browser.downloads.onChanged.addListener(onChangedCallback);
|
|
catcher.watchPromise(incrementCount("download"));
|
|
return browser.windows.getLastFocused().then(windowInfo => {
|
|
return browser.downloads
|
|
.download({
|
|
url,
|
|
incognito: windowInfo.incognito,
|
|
filename: info.filename,
|
|
})
|
|
.catch(error => {
|
|
// We are not logging error message when user cancels download
|
|
if (error && error.message && !error.message.includes("canceled")) {
|
|
log.error(error.message);
|
|
}
|
|
})
|
|
.then(id => {
|
|
downloadId = id;
|
|
});
|
|
});
|
|
});
|
|
|
|
communication.register("abortStartShot", () => {
|
|
// Note, we only show the error but don't report it, as we know that we can't
|
|
// take shots of these pages:
|
|
senderror.showError({
|
|
popupMessage: "UNSHOOTABLE_PAGE",
|
|
});
|
|
});
|
|
|
|
// A Screenshots page wants us to start/force onboarding
|
|
communication.register("requestOnboarding", sender => {
|
|
return startSelectionWithOnboarding(sender.tab);
|
|
});
|
|
|
|
communication.register("getPlatformOs", () => {
|
|
return catcher.watchPromise(
|
|
browser.runtime.getPlatformInfo().then(platformInfo => {
|
|
return platformInfo.os;
|
|
})
|
|
);
|
|
});
|
|
|
|
// This allows the web site show notifications through sitehelper.js
|
|
communication.register("showNotification", (sender, notification) => {
|
|
return browser.notifications.create(notification);
|
|
});
|
|
|
|
return exports;
|
|
})();
|