Bug 1454029 - Export Screenshots 32.1.0 to Firefox (code excluding translations and Raven update); r=_6a68

MozReview-Commit-ID: GA75ZjrWBjm

--HG--
extra : rebase_source : b4e282a57eb9b80f80a7a07dea171fe87a7ebc46
This commit is contained in:
Ian Bicking 2018-04-19 16:10:10 -05:00
Родитель 3954244573
Коммит e23d2028e1
17 изменённых файлов: 119 добавлений и 150 удалений

2
browser/extensions/screenshots/bootstrap.js поставляемый
Просмотреть файл

@ -109,7 +109,7 @@ const LibraryButton = {
const {nextSibling} = libraryViewInsertionPoint;
const item = win.document.createElement("toolbarbutton");
item.className = "subviewbutton subviewbutton-iconic";
item.addEventListener("command", () => win.openTrustedLinkIn(this.PAGE_TO_OPEN, "tab"));
item.addEventListener("command", () => win.openWebLinkIn(this.PAGE_TO_OPEN, "tab"));
item.id = this.ITEM_ID;
const iconURL = this.ICON_URL;
item.setAttribute("image", iconURL);

Просмотреть файл

@ -12,7 +12,7 @@
</Description>
</em:targetApplication>
<em:type>2</em:type>
<em:version>30.1.0</em:version>
<em:version>32.1.0</em:version>
<em:bootstrap>true</em:bootstrap>
<em:homepageURL>https://screenshots.firefox.com/</em:homepageURL>
<em:multiprocessCompatible>true</em:multiprocessCompatible>

Просмотреть файл

@ -15,22 +15,22 @@ async function togglePageActionPanel() {
}
function promiseOpenPageActionPanel() {
let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
const dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
return BrowserTestUtils.waitForCondition(() => {
// Wait for the main page action button to become visible. It's hidden for
// some URIs, so depending on when this is called, it may not yet be quite
// visible. It's up to the caller to make sure it will be visible.
info("Waiting for main page action button to have non-0 size");
let bounds = dwu.getBoundsWithoutFlushing(BrowserPageActions.mainButtonNode);
const bounds = dwu.getBoundsWithoutFlushing(BrowserPageActions.mainButtonNode);
return bounds.width > 0 && bounds.height > 0;
}).then(() => {
// Wait for the panel to become open, by clicking the button if necessary.
info("Waiting for main page action panel to be open");
if (BrowserPageActions.panelNode.state == "open") {
if (BrowserPageActions.panelNode.state === "open") {
return Promise.resolve();
}
let shownPromise = promisePageActionPanelEvent("popupshown");
const shownPromise = promisePageActionPanelEvent("popupshown");
EventUtils.synthesizeMouseAtCenter(BrowserPageActions.mainButtonNode, {});
return shownPromise;
}).then(() => {
@ -41,9 +41,9 @@ function promiseOpenPageActionPanel() {
function promisePageActionPanelEvent(eventType) {
return new Promise(resolve => {
let panel = BrowserPageActions.panelNode;
if ((eventType == "popupshown" && panel.state == "open") ||
(eventType == "popuphidden" && panel.state == "closed")) {
const panel = BrowserPageActions.panelNode;
if ((eventType === "popupshown" && panel.state === "open") ||
(eventType === "popuphidden" && panel.state === "closed")) {
executeSoon(resolve);
return;
}
@ -55,12 +55,12 @@ function promisePageActionPanelEvent(eventType) {
function promisePageActionViewChildrenVisible(panelViewNode) {
info("promisePageActionViewChildrenVisible waiting for a child node to be visible");
let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
const dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
return BrowserTestUtils.waitForCondition(() => {
let bodyNode = panelViewNode.firstChild;
for (let childNode of bodyNode.childNodes) {
let bounds = dwu.getBoundsWithoutFlushing(childNode);
const bodyNode = panelViewNode.firstChild;
for (const childNode of bodyNode.childNodes) {
const bounds = dwu.getBoundsWithoutFlushing(childNode);
if (bounds.width > 0 && bounds.height > 0) {
return true;
}
@ -78,8 +78,8 @@ add_task(async function() {
// Toggle the page action panel to get it to rebuild itself. An actionable
// page must be opened first.
let url = "http://example.com/browser_screenshots_ui_check";
await BrowserTestUtils.withNewTab(url, async () => {
const url = "http://example.com/browser_screenshots_ui_check";
await BrowserTestUtils.withNewTab(url, async () => { // eslint-disable-line space-before-function-paren
await togglePageActionPanel();
await BrowserTestUtils.waitForCondition(

Просмотреть файл

@ -11,11 +11,9 @@ this.main = (function() {
const manifest = browser.runtime.getManifest();
let backend;
let hasSeenOnboarding;
browser.storage.local.get(["hasSeenOnboarding"]).then((result) => {
hasSeenOnboarding = !!result.hasSeenOnboarding;
if (!hasSeenOnboarding) {
let hasSeenOnboarding = browser.storage.local.get(["hasSeenOnboarding"]).then((result) => {
const onboarded = !!result.hasSeenOnboarding;
if (!onboarded) {
setIconActive(false, null);
// Note that the branded name 'Firefox Screenshots' is not localized:
startBackground.photonPageActionPort.postMessage({
@ -23,6 +21,8 @@ this.main = (function() {
title: "Firefox Screenshots"
});
}
hasSeenOnboarding = Promise.resolve(onboarded);
return hasSeenOnboarding;
}).catch((error) => {
log.error("Error getting hasSeenOnboarding:", error);
});
@ -93,34 +93,36 @@ this.main = (function() {
// This is called by startBackground.js, directly in response to clicks on the Photon page action
exports.onClicked = catcher.watchFunction((tab) => {
if (shouldOpenMyShots(tab.url)) {
if (!hasSeenOnboarding) {
catcher.watchPromise(analytics.refreshTelemetryPref().then(() => {
sendEvent("goto-onboarding", "selection-button", {incognito: tab.incognito});
return forceOnboarding();
}));
return;
}
catcher.watchPromise(analytics.refreshTelemetryPref().then(() => {
sendEvent("goto-myshots", "about-newtab", {incognito: tab.incognito});
}));
catcher.watchPromise(
auth.authHeaders()
.then(() => browser.tabs.update({url: backend + "/shots"})));
} else {
catcher.watchPromise(
toggleSelector(tab)
.then(active => {
const event = active ? "start-shot" : "cancel-shot";
sendEvent(event, "toolbar-button", {incognito: tab.incognito});
}, (error) => {
if ((!hasSeenOnboarding) && error.popupMessage === "UNSHOOTABLE_PAGE") {
sendEvent("goto-onboarding", "selection-button", {incognito: tab.incognito});
return forceOnboarding();
}
throw error;
catcher.watchPromise(hasSeenOnboarding.then(onboarded => {
if (shouldOpenMyShots(tab.url)) {
if (!onboarded) {
catcher.watchPromise(analytics.refreshTelemetryPref().then(() => {
sendEvent("goto-onboarding", "selection-button", {incognito: tab.incognito});
return forceOnboarding();
}));
}
return;
}
catcher.watchPromise(analytics.refreshTelemetryPref().then(() => {
sendEvent("goto-myshots", "about-newtab", {incognito: tab.incognito});
}));
catcher.watchPromise(
auth.authHeaders()
.then(() => browser.tabs.update({url: backend + "/shots"})));
} else {
catcher.watchPromise(
toggleSelector(tab)
.then(active => {
const event = active ? "start-shot" : "cancel-shot";
sendEvent(event, "toolbar-button", {incognito: tab.incognito});
}, (error) => {
if ((!onboarded) && error.popupMessage === "UNSHOOTABLE_PAGE") {
sendEvent("goto-onboarding", "selection-button", {incognito: tab.incognito});
return forceOnboarding();
}
throw error;
}));
}
}));
});
function forceOnboarding() {
@ -273,8 +275,8 @@ this.main = (function() {
});
communication.register("hasSeenOnboarding", () => {
hasSeenOnboarding = true;
catcher.watchPromise(browser.storage.local.set({hasSeenOnboarding}));
hasSeenOnboarding = Promise.resolve(true);
catcher.watchPromise(browser.storage.local.set({hasSeenOnboarding: true}));
setIconActive(false, null);
startBackground.photonPageActionPort.postMessage({
type: "setProperties",

Просмотреть файл

@ -66,24 +66,26 @@ this.selectorLoader = (function() {
const loadingTabs = new Set();
exports.loadModules = function(tabId, hasSeenOnboarding) {
loadingTabs.add(tabId);
let promise = downloadOnlyCheck(tabId);
if (hasSeenOnboarding) {
promise = promise.then(() => {
return executeModules(tabId, standardScripts.concat(selectorScripts));
catcher.watchPromise(hasSeenOnboarding.then(onboarded => {
loadingTabs.add(tabId);
let promise = downloadOnlyCheck(tabId);
if (onboarded) {
promise = promise.then(() => {
return executeModules(tabId, standardScripts.concat(selectorScripts));
});
} else {
promise = promise.then(() => {
return executeModules(tabId, standardScripts.concat(onboardingScripts).concat(selectorScripts));
});
}
return promise.then((result) => {
loadingTabs.delete(tabId);
return result;
}, (error) => {
loadingTabs.delete(tabId);
throw error;
});
} else {
promise = promise.then(() => {
return executeModules(tabId, standardScripts.concat(onboardingScripts).concat(selectorScripts));
});
}
return promise.then((result) => {
loadingTabs.delete(tabId);
return result;
}, (error) => {
loadingTabs.delete(tabId);
throw error;
});
}));
};
// TODO: since bootstrap communication is now required, would this function

Просмотреть файл

@ -191,13 +191,14 @@ this.takeshot = (function() {
files.push({fieldName: "thumbnail", filename: "thumbnail.png", blob: thumbnail});
}
return createMultipart(
{shot: JSON.stringify(shot.asJson())},
{shot: JSON.stringify(shot)},
files
);
}
return {
"content-type": "application/json",
body: JSON.stringify(shot.asJson())
body: JSON.stringify(shot)
};
}).then((submission) => {

Просмотреть файл

@ -1,6 +1,9 @@
this.shot = (function () {let exports={}; // Note: in this library we can't use any "system" dependencies because this can be used from multiple
// environments
const isNode = typeof process !== "undefined" && Object.prototype.toString.call(process) === "[object process]";
const URL = (isNode && require("url").URL) || window.URL;
/** Throws an error if the condition isn't true. Any extra arguments after the condition
are used as console.error() arguments. */
function assert(condition, ...args) {
@ -13,23 +16,17 @@ function assert(condition, ...args) {
/** True if `url` is a valid URL */
function isUrl(url) {
// FIXME: this is rather naive, obviously
if ((/^about:.{1,8000}$/i).test(url)) {
try {
const parsed = new URL(url);
if (parsed.protocol === "view-source:") {
return isUrl(url.substr("view-source:".length));
}
return true;
} catch (e) {
return false;
}
if ((/^file:\/.{0,8000}$/i).test(url)) {
return true;
}
if ((/^data:.*$/i).test(url)) {
return true;
}
if ((/^chrome:.{0,8000}/i).test(url)) {
return true;
}
if ((/^view-source:/i).test(url)) {
return isUrl(url.substr("view-source:".length));
}
return (/^https?:\/\/[a-z0-9._-]{1,8000}[a-z0-9](:[0-9]{1,8000})?\/?/i).test(url);
}
function isValidClipImageUrl(url) {
@ -48,7 +45,7 @@ function assertUrl(url) {
}
function isSecureWebUri(url) {
return (/^https?:\/\/[a-z0-9._-]{1,8000}[a-z0-9](:[0-9]{1,8000})?\/?/i).test(url);
return isUrl(url) && url.toLowerCase().startsWith("https");
}
function assertOrigin(url) {
@ -115,39 +112,6 @@ function jsonify(obj, required, optional) {
return result;
}
/** Resolve url relative to base */
function resolveUrl(base, url) {
// FIXME: totally ad hoc and probably incorrect, but we can't
// use any libraries in this file
if (url.search(/^https?:/) !== -1) {
// Absolute url
return url;
}
if (url.indexOf("//") === 0) {
// Protocol-relative URL
return (/^https?:/i).exec(base)[0] + url;
}
if (url.indexOf("/") === 0) {
// Domain-relative URL
return (/^https?:\/\/[a-z0-9._-]{1,4000}/i).exec(base)[0] + url;
}
// Otherwise, a full relative URL
while (url.indexOf("./") === 0) {
url = url.substr(2);
}
if (!base) {
// It's not an absolute URL, and we don't have a base URL, so we have
// to throw away the URL
return null;
}
let match = (/.*\//).exec(base)[0];
if (match.search(/^https?:\/$/i) === 0) {
// Domain without path
match = match + "/";
}
return match + url;
}
/** True if the two objects look alike. Null, undefined, and absent properties
are all treated as equivalent. Traverses objects and arrays */
function deepEqual(a, b) {
@ -275,8 +239,8 @@ class AbstractShot {
}
if (typeof json[attr] === "object" && typeof this[attr] === "object" && this[attr] !== null) {
let val = this[attr];
if (val.asJson) {
val = val.asJson();
if (val.toJSON) {
val = val.toJSON();
}
if (!deepEqual(json[attr], val)) {
this[attr] = json[attr];
@ -292,7 +256,7 @@ class AbstractShot {
this.delClip(clipId);
} else if (!this.getClip(clipId)) {
this.setClip(clipId, json.clips[clipId]);
} else if (!deepEqual(this.getClip(clipId).asJson(), json.clips[clipId])) {
} else if (!deepEqual(this.getClip(clipId).toJSON(), json.clips[clipId])) {
this.setClip(clipId, json.clips[clipId]);
}
}
@ -301,18 +265,18 @@ class AbstractShot {
}
/** Returns a JSON version of this shot */
asJson() {
toJSON() {
const result = {};
for (const attr of this.REGULAR_ATTRS) {
let val = this[attr];
if (val && val.asJson) {
val = val.asJson();
if (val && val.toJSON) {
val = val.toJSON();
}
result[attr] = val;
}
result.clips = {};
for (const attr in this._clips) {
result.clips[attr] = this._clips[attr].asJson();
result.clips[attr] = this._clips[attr].toJSON();
}
return result;
}
@ -322,13 +286,13 @@ class AbstractShot {
const result = {clips: {}};
for (const attr of this.RECALL_ATTRS) {
let val = this[attr];
if (val && val.asJson) {
val = val.asJson();
if (val && val.toJSON) {
val = val.toJSON();
}
result[attr] = val;
}
for (const name of this.clipNames()) {
result.clips[name] = this.getClip(name).asJson();
result.clips[name] = this.getClip(name).toJSON();
}
return result;
}
@ -374,7 +338,8 @@ class AbstractShot {
// eslint-disable-next-line no-control-regex
filenameTitle = filenameTitle.replace(/[:\\<>/!@&?"*.|\x00-\x1F]/g, " ");
filenameTitle = filenameTitle.replace(/\s{1,4000}/g, " ");
let clipFilename = `Screenshot-${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()} ${filenameTitle}`;
const filenameDate = new Date(date.getTime() - date.getTimezoneOffset() * 60 * 1000).toISOString().substring(0, 10);
let clipFilename = `Screenshot_${filenameDate} ${filenameTitle}`;
const clipFilenameBytesSize = clipFilename.length * 2; // JS STrings are UTF-16
if (clipFilenameBytesSize > 251) { // 255 bytes (Usual filesystems max) - 4 for the ".png" file extension string
const excedingchars = (clipFilenameBytesSize - 246) / 2; // 251 - 5 for ellipsis "[...]"
@ -497,14 +462,8 @@ class AbstractShot {
return this._favicon;
}
set favicon(val) {
// We allow but ignore bad favicon URLs, as they seem somewhat common
// We set the favicon with tabs.Tab.faviConUrl, which is a full URL.
val = val || null;
if (!isUrl(val)) {
val = null;
}
if (val) {
val = resolveUrl(this.url, val);
}
this._favicon = val;
}
@ -658,7 +617,7 @@ class _Image {
this.alt = json.alt;
}
asJson() {
toJSON() {
return jsonify(this, ["url"], ["dimensions"]);
}
}
@ -689,7 +648,7 @@ class _Clip {
return `[Shot Clip id=${this.id} sortOrder=${this.sortOrder} image ${this.image.dimensions.x}x${this.image.dimensions.y}]`;
}
asJson() {
toJSON() {
return jsonify(this, ["createdDate"], ["sortOrder", "image"]);
}

Просмотреть файл

@ -14,6 +14,7 @@ this.clipboard = (function() {
element.style.opacity = "0";
element.style.width = "1px";
element.style.height = "1px";
element.style.display = "block";
element.addEventListener("load", catcher.watchFunction(() => {
try {
const doc = element.contentDocument;

Просмотреть файл

@ -1 +1 @@
<svg width="32" height="32" xmlns="http://www.w3.org/2000/svg" fill="context-fill" fill-opacity="context-fill-opacity"><path d="M8 2a4 4 0 0 0-4 4h4V2zm12 0h-4v4h4V2zm8 0v4h4a4 4 0 0 0-4-4zM14 2h-4v4h4V2zm12 0h-4v4h4V2zm2 10h4V8h-4v4zm0 12a4 4 0 0 0 4-4h-4v4zm0-6h4v-4h-4v4zm-.882-4.334a4 4 0 0 0-5.57-.984l-7.67 5.662-3.936-2.76c.031-.193.05-.388.058-.584a4.976 4.976 0 0 0-2-3.978V8H4v2.1a5 5 0 1 0 3.916 8.948l2.484 1.738-2.8 1.964a4.988 4.988 0 1 0 2.3 3.266l17.218-12.35zM5 17.5a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm0 12a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm10.8-4.858l6.034 4.6a4 4 0 0 0 5.57-.984L19.28 22.2l-3.48 2.442z"/></svg>
<svg viewBox="0 0 32 32" width="32" height="32" xmlns="http://www.w3.org/2000/svg" fill="context-fill" fill-opacity="context-fill-opacity"><path d="M8 2a4 4 0 0 0-4 4h4V2zm12 0h-4v4h4V2zm8 0v4h4a4 4 0 0 0-4-4zM14 2h-4v4h4V2zm12 0h-4v4h4V2zm2 10h4V8h-4v4zm0 12a4 4 0 0 0 4-4h-4v4zm0-6h4v-4h-4v4zm-.882-4.334a4 4 0 0 0-5.57-.984l-7.67 5.662-3.936-2.76c.031-.193.05-.388.058-.584a4.976 4.976 0 0 0-2-3.978V8H4v2.1a5 5 0 1 0 3.916 8.948l2.484 1.738-2.8 1.964a4.988 4.988 0 1 0 2.3 3.266l17.218-12.35zM5 17.5a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm0 12a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5zm10.8-4.858l6.034 4.6a4 4 0 0 0 5.57-.984L19.28 22.2l-3.48 2.442z"/></svg>

До

Ширина:  |  Высота:  |  Размер: 636 B

После

Ширина:  |  Высота:  |  Размер: 657 B

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 27 KiB

После

Ширина:  |  Высота:  |  Размер: 27 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 67 KiB

После

Ширина:  |  Высота:  |  Размер: 40 KiB

Двоичный файл не отображается.

До

Ширина:  |  Высота:  |  Размер: 57 KiB

После

Ширина:  |  Высота:  |  Размер: 34 KiB

Просмотреть файл

@ -1,7 +1,7 @@
{
"manifest_version": 2,
"name": "Firefox Screenshots",
"version": "30.1.0",
"version": "32.1.0",
"description": "__MSG_addonDescription__",
"author": "__MSG_addonAuthorsList__",
"homepage_url": "https://github.com/mozilla-services/screenshots",
@ -30,6 +30,9 @@
]
}
],
"icons": {
"32": "icons/icon-v2.svg"
},
"web_accessible_resources": [
"blank.html",
"icons/cancel.svg",

Просмотреть файл

@ -24,6 +24,7 @@ this.slides = (function() {
iframe.src = browser.extension.getURL("blank.html");
iframe.id = "firefox-screenshots-onboarding-iframe";
iframe.style.zIndex = "99999999999";
iframe.style.display = "block";
iframe.style.border = "none";
iframe.style.position = "fixed";
iframe.style.top = "0";

Просмотреть файл

@ -115,7 +115,7 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars
}
isSaving = null;
}, 1000);
selectedPos = selectedPos.asJson();
selectedPos = selectedPos.toJSON();
let captureText = "";
if (buildSettings.captureText) {
captureText = util.captureEnclosedText(selectedPos);
@ -152,7 +152,7 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars
},
selectedPos,
shotId: shotObject.id,
shot: shotObject.asJson(),
shot: shotObject.toJSON(),
imageBlob
}).then((url) => {
return clipboard.copy(url).then((copied) => {
@ -185,7 +185,7 @@ this.shooter = (function() { // eslint-disable-line no-unused-vars
if (!dataUrl) {
promise = callBackground(
"screenshotPage",
selectedPos.asJson(),
selectedPos.toJSON(),
{
scrollX: window.scrollX,
scrollY: window.scrollY,

Просмотреть файл

@ -173,7 +173,7 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
unhide() {
this.updateElementSize();
this.element.style.display = "";
this.element.style.display = "block";
catcher.watchPromise(callBackground("sendEvent", "internal", "unhide-selection-frame"));
if (highContrastCheck(this.element.contentWindow)) {
this.element.contentDocument.body.classList.add("hcm");
@ -222,7 +222,7 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
}
}
if (force && visible) {
this.element.style.display = "";
this.element.style.display = "block";
}
},
@ -344,7 +344,7 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
unhide() {
window.addEventListener("scroll", watchFunction(assertIsTrusted(this.onScroll)));
window.addEventListener("resize", this.onResize, true);
this.element.style.display = "";
this.element.style.display = "block";
catcher.watchPromise(callBackground("sendEvent", "internal", "unhide-preselection-frame"));
if (highContrastCheck(this.element.contentWindow)) {
this.element.contentDocument.body.classList.add("hcm");
@ -458,7 +458,7 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
},
unhide() {
this.element.style.display = "";
this.element.style.display = "block";
catcher.watchPromise(callBackground("sendEvent", "internal", "unhide-preview-frame"));
this.element.focus();
},
@ -635,7 +635,7 @@ this.ui = (function() { // eslint-disable-line no-unused-vars
this.bgRight.style.top = `${pos.top}px`;
this.bgRight.style.height = `${pos.bottom - pos.top}px`;
this.bgRight.style.left = `${pos.right}px`;
this.bgRight.style.width = "100%";
this.bgRight.style.width = `${document.body.scrollWidth - pos.right}px`;
// the download notice is injected into an iframe that matches the document size
// in order to reposition it on scroll we need to bind an updated positioning
// function to some window events.

Просмотреть файл

@ -351,7 +351,7 @@ this.uicontrol = (function() {
return new Selection(this.x1, this.y1, this.x2, this.y2);
}
asJson() {
toJSON() {
return {
left: this.left,
right: this.right,