mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-24 05:11:16 +00:00
Bug 1741736 - Add overlay to Screenshots component implementation. r=sfoster,fluent-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D132832
This commit is contained in:
parent
a207ad062a
commit
bb01085481
@ -7,6 +7,14 @@
|
|||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["ScreenshotsComponentChild"];
|
var EXPORTED_SYMBOLS = ["ScreenshotsComponentChild"];
|
||||||
|
|
||||||
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyModuleGetters(this, {
|
||||||
|
ScreenshotsOverlayChild: "resource:///modules/ScreenshotsOverlayChild.jsm",
|
||||||
|
});
|
||||||
|
|
||||||
class ScreenshotsComponentChild extends JSWindowActorChild {
|
class ScreenshotsComponentChild extends JSWindowActorChild {
|
||||||
receiveMessage(message) {
|
receiveMessage(message) {
|
||||||
switch (message.name) {
|
switch (message.name) {
|
||||||
@ -22,6 +30,13 @@ class ScreenshotsComponentChild extends JSWindowActorChild {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send a request to cancel the screenshot to the parent process
|
||||||
|
*/
|
||||||
|
requestCancelScreenshot() {
|
||||||
|
this.sendAsyncMessage("Screenshots:CancelScreenshot", null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves when the document is ready to have an overlay injected into it.
|
* Resolves when the document is ready to have an overlay injected into it.
|
||||||
*
|
*
|
||||||
@ -64,16 +79,35 @@ class ScreenshotsComponentChild extends JSWindowActorChild {
|
|||||||
* @returns {Boolean} true when document is ready and the overlay is shown
|
* @returns {Boolean} true when document is ready and the overlay is shown
|
||||||
* otherwise false
|
* otherwise false
|
||||||
*/
|
*/
|
||||||
async startScreenshotsOverlay(details = {}) {
|
async startScreenshotsOverlay() {
|
||||||
try {
|
try {
|
||||||
await this.documentIsReady();
|
await this.documentIsReady();
|
||||||
} catch (ex) {
|
} catch (ex) {
|
||||||
console.warn(`ScreenshotsComponentChild: ${ex.message}`);
|
console.warn(`ScreenshotsComponentChild: ${ex.message}`);
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
await this.documentIsReady();
|
||||||
|
let overlay =
|
||||||
|
this._overlay ||
|
||||||
|
(this._overlay = new ScreenshotsOverlayChild.AnonymousContentOverlay(
|
||||||
|
this.document,
|
||||||
|
this
|
||||||
|
));
|
||||||
|
this.document.addEventListener("keydown", this.handler);
|
||||||
|
overlay.initialize();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Function to handle the escape key press to cancel screenshots overlay
|
||||||
|
* @param event The keydown event
|
||||||
|
*/
|
||||||
|
handler = event => {
|
||||||
|
if (event.key === "Escape") {
|
||||||
|
this.requestCancelScreenshot();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Remove the screenshots overlay.
|
* Remove the screenshots overlay.
|
||||||
*
|
*
|
||||||
@ -81,7 +115,8 @@ class ScreenshotsComponentChild extends JSWindowActorChild {
|
|||||||
* true when the overlay has been removed otherwise false
|
* true when the overlay has been removed otherwise false
|
||||||
*/
|
*/
|
||||||
endScreenshotsOverlay() {
|
endScreenshotsOverlay() {
|
||||||
// this function will be implemented soon
|
this.document.removeEventListener("keydown", this.handler);
|
||||||
|
this._overlay?.tearDown();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,6 +172,7 @@
|
|||||||
norolluponanchor="true"
|
norolluponanchor="true"
|
||||||
consumeoutsideclicks="never"
|
consumeoutsideclicks="never"
|
||||||
level="parent"
|
level="parent"
|
||||||
|
noautohide="true"
|
||||||
tabspecific="true">
|
tabspecific="true">
|
||||||
<screenshots-buttons></screenshots-buttons>
|
<screenshots-buttons></screenshots-buttons>
|
||||||
</panel>
|
</panel>
|
||||||
|
@ -273,10 +273,6 @@ var whitelist = [
|
|||||||
// (The references to these files are dynamically generated, so the test can't
|
// (The references to these files are dynamically generated, so the test can't
|
||||||
// find the references)
|
// find the references)
|
||||||
{ file: "chrome://browser/content/screenshots/copied-notification.svg" },
|
{ file: "chrome://browser/content/screenshots/copied-notification.svg" },
|
||||||
{
|
|
||||||
file:
|
|
||||||
"chrome://browser/content/screenshots/icon-welcome-face-without-eyes.svg",
|
|
||||||
},
|
|
||||||
|
|
||||||
{ file: "resource://app/modules/SnapshotSelector.jsm" },
|
{ file: "resource://app/modules/SnapshotSelector.jsm" },
|
||||||
|
|
||||||
|
@ -666,6 +666,9 @@ let JSWINDOWACTORS = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
ScreenshotsComponent: {
|
ScreenshotsComponent: {
|
||||||
|
parent: {
|
||||||
|
moduleURI: "resource:///modules/ScreenshotsUtils.jsm",
|
||||||
|
},
|
||||||
child: {
|
child: {
|
||||||
moduleURI: "resource:///actors/ScreenshotsComponentChild.jsm",
|
moduleURI: "resource:///actors/ScreenshotsComponentChild.jsm",
|
||||||
},
|
},
|
||||||
|
291
browser/components/screenshots/ScreenshotsOverlayChild.jsm
Normal file
291
browser/components/screenshots/ScreenshotsOverlayChild.jsm
Normal file
@ -0,0 +1,291 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Screenshots overlay is inserted into the document's
|
||||||
|
* canvasFrame anonymous content container (see dom/webidl/Document.webidl).
|
||||||
|
*
|
||||||
|
* This container gets cleared automatically when the document navigates.
|
||||||
|
*
|
||||||
|
* Since the overlay markup is inserted in the canvasFrame using
|
||||||
|
* insertAnonymousContent, this means that it can be modified using the API
|
||||||
|
* described in AnonymousContent.webidl.
|
||||||
|
*
|
||||||
|
* Any mutation of this content must be via the AnonymousContent API.
|
||||||
|
* This is similar in design to [devtools' highlighters](https://firefox-source-docs.mozilla.org/devtools/tools/highlighters.html#inserting-content-in-the-page),
|
||||||
|
* though as Screenshots doesnt need to work on XUL documents, or allow multiple kinds of
|
||||||
|
* highlight/overlay our case is a little simpler.
|
||||||
|
*
|
||||||
|
* To retrieve the AnonymousContent instance, use the `content` getter.
|
||||||
|
*/
|
||||||
|
|
||||||
|
var EXPORTED_SYMBOLS = ["ScreenshotsOverlayChild"];
|
||||||
|
|
||||||
|
const { XPCOMUtils } = ChromeUtils.import(
|
||||||
|
"resource://gre/modules/XPCOMUtils.jsm"
|
||||||
|
);
|
||||||
|
|
||||||
|
XPCOMUtils.defineLazyGetter(this, "overlayLocalization", () => {
|
||||||
|
return new Localization(["browser/screenshotsOverlay.ftl"], true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const STYLESHEET_URL =
|
||||||
|
"chrome://browser/content/screenshots/overlay/overlay.css";
|
||||||
|
const HTML_NS = "http://www.w3.org/1999/xhtml";
|
||||||
|
|
||||||
|
class AnonymousContentOverlay {
|
||||||
|
constructor(contentDocument, screenshotsChild) {
|
||||||
|
this.listeners = new Map();
|
||||||
|
this.elements = new Map();
|
||||||
|
|
||||||
|
this.screenshotsChild = screenshotsChild;
|
||||||
|
|
||||||
|
this.contentDocument = contentDocument;
|
||||||
|
// aliased for easier diffs/maintenance of the event management code borrowed from devtools highlighters
|
||||||
|
this.pageListenerTarget = contentDocument.ownerGlobal;
|
||||||
|
|
||||||
|
this.overlayFragment = null;
|
||||||
|
|
||||||
|
this.overlayId = "screenshots-overlay-container";
|
||||||
|
this._initialized = false;
|
||||||
|
}
|
||||||
|
get content() {
|
||||||
|
if (!this._content || Cu.isDeadWrapper(this._content)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return this._content;
|
||||||
|
}
|
||||||
|
async initialize() {
|
||||||
|
if (this._initialized) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let document = this.contentDocument;
|
||||||
|
let window = document.ownerGlobal;
|
||||||
|
|
||||||
|
// Inject stylesheet
|
||||||
|
if (!this.overlayFragment) {
|
||||||
|
try {
|
||||||
|
window.windowUtils.loadSheetUsingURIString(
|
||||||
|
STYLESHEET_URL,
|
||||||
|
window.windowUtils.AGENT_SHEET
|
||||||
|
);
|
||||||
|
} catch {
|
||||||
|
// The method fails if the url is already loaded.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject markup for the overlay UI
|
||||||
|
this.overlayFragment = this.overlayFragment
|
||||||
|
? this.overlayFragment
|
||||||
|
: this.buildOverlay();
|
||||||
|
|
||||||
|
this._content = document.insertAnonymousContent(
|
||||||
|
this.overlayFragment.children[0]
|
||||||
|
);
|
||||||
|
|
||||||
|
this.addEventListenerForElement(
|
||||||
|
"screenshots-cancel-button",
|
||||||
|
"click",
|
||||||
|
(event, targetId) => {
|
||||||
|
this.screenshotsChild.requestCancelScreenshot();
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// TODO:
|
||||||
|
// * Define & hook up drag handles for custom region selection
|
||||||
|
|
||||||
|
this._initialized = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
tearDown() {
|
||||||
|
if (this._content) {
|
||||||
|
this._removeAllListeners();
|
||||||
|
try {
|
||||||
|
this.contentDocument.removeAnonymousContent(this._content);
|
||||||
|
} catch (e) {
|
||||||
|
// If the current window isn't the one the content was inserted into, this
|
||||||
|
// will fail, but that's fine.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this._initialized = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
createElem(elemName, props = {}, className = "") {
|
||||||
|
let elem = this.contentDocument.createElementNS(HTML_NS, elemName);
|
||||||
|
if (props) {
|
||||||
|
for (let [name, value] of Object.entries(props)) {
|
||||||
|
elem[name] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (className) {
|
||||||
|
elem.className = className;
|
||||||
|
}
|
||||||
|
// register this element so we can target events at it
|
||||||
|
if (elem.id) {
|
||||||
|
this.elements.set(elem.id, elem);
|
||||||
|
}
|
||||||
|
return elem;
|
||||||
|
}
|
||||||
|
|
||||||
|
buildOverlay() {
|
||||||
|
let [cancel, instrustions] = overlayLocalization.formatMessagesSync([
|
||||||
|
{ id: "screenshots-overlay-cancel-button" },
|
||||||
|
{ id: "screenshots-overlay-instructions" },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const htmlString = `
|
||||||
|
<div id="${this.overlayId}">
|
||||||
|
<div class="fixed-container">
|
||||||
|
<div class="face-container">
|
||||||
|
<div class="eye left"><div class="eyeball"></div></div>
|
||||||
|
<div class="eye right"><div class="eyeball"></div></div>
|
||||||
|
<div class="face"></div>
|
||||||
|
</div>
|
||||||
|
<div class="preview-instructions" data-l10n-id="screenshots-instructions">${instrustions.value}</div>
|
||||||
|
<div class="cancel-shot" id="screenshots-cancel-button" data-l10n-id="screenshots-overlay-cancel-button">${cancel.value}</div>
|
||||||
|
</div>
|
||||||
|
</div`;
|
||||||
|
|
||||||
|
const parser = new this.contentDocument.ownerGlobal.DOMParser();
|
||||||
|
const tmpDoc = parser.parseFromString(htmlString, "text/html");
|
||||||
|
const fragment = this.contentDocument.createDocumentFragment();
|
||||||
|
|
||||||
|
fragment.appendChild(tmpDoc.body.children[0]);
|
||||||
|
return fragment;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The event tooling is borrowed directly from devtools' highlighters (CanvasFrameAnonymousContentHelper)
|
||||||
|
/**
|
||||||
|
* Add an event listener to one of the elements inserted in the canvasFrame
|
||||||
|
* native anonymous container.
|
||||||
|
* Like other methods in this helper, this requires the ID of the element to
|
||||||
|
* be passed in.
|
||||||
|
*
|
||||||
|
* Note that if the content page navigates, the event listeners won't be
|
||||||
|
* added again.
|
||||||
|
*
|
||||||
|
* Also note that unlike traditional DOM events, the events handled by
|
||||||
|
* listeners added here will propagate through the document only through
|
||||||
|
* bubbling phase, so the useCapture parameter isn't supported.
|
||||||
|
* It is possible however to call e.stopPropagation() to stop the bubbling.
|
||||||
|
*
|
||||||
|
* IMPORTANT: the chrome-only canvasFrame insertion API takes great care of
|
||||||
|
* not leaking references to inserted elements to chrome JS code. That's
|
||||||
|
* because otherwise, chrome JS code could freely modify native anon elements
|
||||||
|
* inside the canvasFrame and probably change things that are assumed not to
|
||||||
|
* change by the C++ code managing this frame.
|
||||||
|
* See https://wiki.mozilla.org/DevTools/Highlighter#The_AnonymousContent_API
|
||||||
|
* Unfortunately, the inserted nodes are still available via
|
||||||
|
* event.originalTarget, and that's what the event handler here uses to check
|
||||||
|
* that the event actually occured on the right element, but that also means
|
||||||
|
* consumers of this code would be able to access the inserted elements.
|
||||||
|
* Therefore, the originalTarget property will be nullified before the event
|
||||||
|
* is passed to your handler.
|
||||||
|
*
|
||||||
|
* IMPL DETAIL: A single event listener is added per event types only, at
|
||||||
|
* browser level and if the event originalTarget is found to have the provided
|
||||||
|
* ID, the callback is executed (and then IDs of parent nodes of the
|
||||||
|
* originalTarget are checked too).
|
||||||
|
*
|
||||||
|
* @param {String} id
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Function} handler
|
||||||
|
*/
|
||||||
|
addEventListenerForElement(id, type, handler) {
|
||||||
|
if (typeof id !== "string") {
|
||||||
|
throw new Error(
|
||||||
|
"Expected a string ID in addEventListenerForElement but got: " + id
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If no one is listening for this type of event yet, add one listener.
|
||||||
|
if (!this.listeners.has(type)) {
|
||||||
|
const target = this.pageListenerTarget;
|
||||||
|
target.addEventListener(type, this, true);
|
||||||
|
// Each type entry in the map is a map of ids:handlers.
|
||||||
|
this.listeners.set(type, new Map());
|
||||||
|
}
|
||||||
|
|
||||||
|
const listeners = this.listeners.get(type);
|
||||||
|
listeners.set(id, handler);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an event listener from one of the elements inserted in the
|
||||||
|
* canvasFrame native anonymous container.
|
||||||
|
* @param {String} id
|
||||||
|
* @param {String} type
|
||||||
|
*/
|
||||||
|
removeEventListenerForElement(id, type) {
|
||||||
|
const listeners = this.listeners.get(type);
|
||||||
|
if (!listeners) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
listeners.delete(id);
|
||||||
|
|
||||||
|
// If no one is listening for event type anymore, remove the listener.
|
||||||
|
if (!listeners.size) {
|
||||||
|
const target = this.pageListenerTarget;
|
||||||
|
target.removeEventListener(type, this, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handleEvent(event) {
|
||||||
|
const listeners = this.listeners.get(event.type);
|
||||||
|
if (!listeners) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the originalTarget property to avoid exposing references to native
|
||||||
|
// anonymous elements. See addEventListenerForElement's comment.
|
||||||
|
let isPropagationStopped = false;
|
||||||
|
const eventProxy = new Proxy(event, {
|
||||||
|
get: (obj, name) => {
|
||||||
|
if (name === "originalTarget") {
|
||||||
|
return null;
|
||||||
|
} else if (name === "stopPropagation") {
|
||||||
|
return () => {
|
||||||
|
isPropagationStopped = true;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return obj[name];
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start at originalTarget, bubble through ancestors and call handlers when
|
||||||
|
// needed.
|
||||||
|
let node = event.originalTarget;
|
||||||
|
while (node) {
|
||||||
|
let nodeId = node.id;
|
||||||
|
if (nodeId) {
|
||||||
|
const handler = listeners.get(node.id);
|
||||||
|
if (handler) {
|
||||||
|
handler(eventProxy, nodeId);
|
||||||
|
if (isPropagationStopped) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (nodeId == this.overlayId) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
node = node.parentNode;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_removeAllListeners() {
|
||||||
|
if (this.pageListenerTarget) {
|
||||||
|
const target = this.pageListenerTarget;
|
||||||
|
for (const [type] of this.listeners) {
|
||||||
|
target.removeEventListener(type, this, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.listeners.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var ScreenshotsOverlayChild = {
|
||||||
|
AnonymousContentOverlay,
|
||||||
|
};
|
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
"use strict";
|
"use strict";
|
||||||
|
|
||||||
var EXPORTED_SYMBOLS = ["ScreenshotsUtils"];
|
var EXPORTED_SYMBOLS = ["ScreenshotsUtils", "ScreenshotsComponentParent"];
|
||||||
|
|
||||||
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
|
||||||
|
|
||||||
@ -12,6 +12,16 @@ const PanelPosition = "bottomright topright";
|
|||||||
const PanelOffsetX = -33;
|
const PanelOffsetX = -33;
|
||||||
const PanelOffsetY = -8;
|
const PanelOffsetY = -8;
|
||||||
|
|
||||||
|
class ScreenshotsComponentParent extends JSWindowActorParent {
|
||||||
|
receiveMessage(message) {
|
||||||
|
switch (message.name) {
|
||||||
|
case "Screenshots:CancelScreenshot":
|
||||||
|
let browser = message.target.browsingContext.topFrameElement;
|
||||||
|
ScreenshotsUtils.closePanel(browser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var ScreenshotsUtils = {
|
var ScreenshotsUtils = {
|
||||||
initialized: false,
|
initialized: false,
|
||||||
initialize() {
|
initialize() {
|
||||||
@ -57,9 +67,8 @@ var ScreenshotsUtils = {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case "screenshots-take-screenshot":
|
case "screenshots-take-screenshot":
|
||||||
// need to toggle because panel button was clicked
|
// need to close the preview because screenshot was taken
|
||||||
// and we need to hide the buttons
|
this.closePanel(browser);
|
||||||
this.togglePreview(browser);
|
|
||||||
|
|
||||||
// init UI as a tab dialog box
|
// init UI as a tab dialog box
|
||||||
let dialogBox = gBrowser.getTabDialogBox(browser);
|
let dialogBox = gBrowser.getTabDialogBox(browser);
|
||||||
@ -103,8 +112,31 @@ var ScreenshotsUtils = {
|
|||||||
return actor;
|
return actor;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* If the buttons panel exists and the panel is open we will hipe the panel
|
* Open the panel buttons and call child actor to open the overlay
|
||||||
* popup and hide the screenshot overlay.
|
* @param browser The current browser
|
||||||
|
*/
|
||||||
|
openPanel(browser) {
|
||||||
|
let actor = this.getActor(browser);
|
||||||
|
actor.sendQuery("Screenshots:ShowOverlay");
|
||||||
|
this.createOrDisplayButtons(browser);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* Close the panel and call child actor to close the overlay
|
||||||
|
* @param browser The current browser
|
||||||
|
*/
|
||||||
|
closePanel(browser) {
|
||||||
|
let buttonsPanel = browser.ownerDocument.querySelector(
|
||||||
|
"#screenshotsPagePanel"
|
||||||
|
);
|
||||||
|
if (buttonsPanel && buttonsPanel.state !== "closed") {
|
||||||
|
buttonsPanel.hidePopup();
|
||||||
|
}
|
||||||
|
let actor = this.getActor(browser);
|
||||||
|
actor.sendQuery("Screenshots:HideOverlay");
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* If the buttons panel exists and is open we will hide both the panel
|
||||||
|
* popup and the overlay.
|
||||||
* Otherwise create or display the buttons.
|
* Otherwise create or display the buttons.
|
||||||
* @param browser The current browser.
|
* @param browser The current browser.
|
||||||
*/
|
*/
|
||||||
@ -117,6 +149,8 @@ var ScreenshotsUtils = {
|
|||||||
let actor = this.getActor(browser);
|
let actor = this.getActor(browser);
|
||||||
return actor.sendQuery("Screenshots:HideOverlay");
|
return actor.sendQuery("Screenshots:HideOverlay");
|
||||||
}
|
}
|
||||||
|
let actor = this.getActor(browser);
|
||||||
|
actor.sendQuery("Screenshots:ShowOverlay");
|
||||||
return this.createOrDisplayButtons(browser);
|
return this.createOrDisplayButtons(browser);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
@ -173,10 +207,9 @@ var ScreenshotsUtils = {
|
|||||||
template.replaceWith(clone);
|
template.replaceWith(clone);
|
||||||
buttonsPanel = doc.querySelector("#screenshotsPagePanel");
|
buttonsPanel = doc.querySelector("#screenshotsPagePanel");
|
||||||
}
|
}
|
||||||
|
|
||||||
let anchor = doc.querySelector("#navigator-toolbox");
|
let anchor = doc.querySelector("#navigator-toolbox");
|
||||||
buttonsPanel.openPopup(anchor, PanelPosition, PanelOffsetX, PanelOffsetY);
|
buttonsPanel.openPopup(anchor, PanelPosition, PanelOffsetX, PanelOffsetY);
|
||||||
let actor = this.getActor(browser);
|
|
||||||
return actor.sendQuery("Screenshots:ShowOverlay");
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* Gets the full page bounds from the screenshots child actor.
|
* Gets the full page bounds from the screenshots child actor.
|
||||||
|
@ -16,3 +16,7 @@ browser.jar:
|
|||||||
content/browser/screenshots/screenshots-buttons.css (screenshots-buttons.css)
|
content/browser/screenshots/screenshots-buttons.css (screenshots-buttons.css)
|
||||||
content/browser/screenshots/screenshots.css (content/screenshots.css)
|
content/browser/screenshots/screenshots.css (content/screenshots.css)
|
||||||
content/browser/screenshots/screenshots.html (content/screenshots.html)
|
content/browser/screenshots/screenshots.html (content/screenshots.html)
|
||||||
|
content/browser/screenshots/overlay/ (overlay/**)
|
||||||
|
|
||||||
|
|
||||||
|
% content screenshots-overlay %overlay/
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||||
|
|
||||||
EXTRA_JS_MODULES += [
|
EXTRA_JS_MODULES += [
|
||||||
|
"ScreenshotsOverlayChild.jsm",
|
||||||
"ScreenshotsUtils.jsm",
|
"ScreenshotsUtils.jsm",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
126
browser/components/screenshots/overlay/overlay.css
Normal file
126
browser/components/screenshots/overlay/overlay.css
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
/* 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/. */
|
||||||
|
|
||||||
|
:-moz-native-anonymous #screenshots-overlay-container {
|
||||||
|
/*
|
||||||
|
Content CSS applying to the html element can impact the overlay.
|
||||||
|
To avoid that, possible cases have been set to initial.
|
||||||
|
*/
|
||||||
|
text-transform: initial;
|
||||||
|
text-indent: initial;
|
||||||
|
letter-spacing: initial;
|
||||||
|
word-spacing: initial;
|
||||||
|
color: initial;
|
||||||
|
direction: initial;
|
||||||
|
writing-mode: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overlay content is position: fixed as we need to allow for the possiblily
|
||||||
|
* of the document scrolling or changing size while the overlay is visible
|
||||||
|
*/
|
||||||
|
:-moz-native-anonymous #screenshots-overlay-container {
|
||||||
|
position: fixed;
|
||||||
|
top: 0; left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: rgba(0, 0, 0, 0.7);
|
||||||
|
pointer-events: auto;
|
||||||
|
cursor: crosshair;
|
||||||
|
}
|
||||||
|
|
||||||
|
:-moz-native-anonymous #screenshots-overlay-container[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
:-moz-native-anonymous #screenshots-overlay-container[dragging] {
|
||||||
|
cursor: grabbing;
|
||||||
|
}
|
||||||
|
|
||||||
|
:-moz-native-anonymous #screenshots-cancel-button {
|
||||||
|
background-color: transparent;
|
||||||
|
width: fit-content;
|
||||||
|
cursor: pointer;
|
||||||
|
outline: none;
|
||||||
|
border-radius: 3px;
|
||||||
|
border: 1px #9b9b9b solid;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "segoe ui", "helvetica neue", helvetica, ubuntu, roboto, noto, arial, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
margin-top: 40px;
|
||||||
|
padding: 10px 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:-moz-native-anonymous .fixed-container {
|
||||||
|
align-items: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100vh;
|
||||||
|
justify-content: center;
|
||||||
|
inset-inline-start: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
:-moz-native-anonymous .face-container {
|
||||||
|
position: relative;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:-moz-native-anonymous .face {
|
||||||
|
width: 62px;
|
||||||
|
height: 62px;
|
||||||
|
display: block;
|
||||||
|
background-image: url("chrome://browser/content/screenshots/icon-welcome-face-without-eyes.svg");
|
||||||
|
}
|
||||||
|
|
||||||
|
:-moz-native-anonymous .eye {
|
||||||
|
background-color: #fff;
|
||||||
|
width: 10px;
|
||||||
|
height: 14px;
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
inset-inline-start: 16px;
|
||||||
|
top: 19px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:-moz-native-anonymous .eyeball {
|
||||||
|
position: absolute;
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
background-color: #000;
|
||||||
|
border-radius: 50%;
|
||||||
|
inset-inline-start: 2px;
|
||||||
|
top: 4px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
:-moz-native-anonymous .left {
|
||||||
|
margin-inline-start: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:-moz-native-anonymous .right {
|
||||||
|
margin-inline-start: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:-moz-native-anonymous .preview-instructions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
animation: pulse 125mm cubic-bezier(0.07, 0.95, 0, 1);
|
||||||
|
color: #fff;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, "segoe ui", "helvetica neue", helvetica, ubuntu, roboto, noto, arial, sans-serif;
|
||||||
|
font-size: 24px;
|
||||||
|
line-height: 32px;
|
||||||
|
text-align: center;
|
||||||
|
padding-top: 20px;
|
||||||
|
width: 400px;
|
||||||
|
user-select: none;
|
||||||
|
}
|
@ -7,6 +7,7 @@ prefs =
|
|||||||
extensions.screenshots.disabled=false
|
extensions.screenshots.disabled=false
|
||||||
screenshots.browser.component.enabled=true
|
screenshots.browser.component.enabled=true
|
||||||
|
|
||||||
|
[browser_screenshots_test_escape.js]
|
||||||
[browser_screenshots_test_full_page.js]
|
[browser_screenshots_test_full_page.js]
|
||||||
skip-if = (!debug && os == 'win' && os_version == '6.1') # Bug 1746281
|
skip-if = (!debug && os == 'win' && os_version == '6.1') # Bug 1746281
|
||||||
[browser_screenshots_test_toggle_pref.js]
|
[browser_screenshots_test_toggle_pref.js]
|
||||||
|
@ -0,0 +1,52 @@
|
|||||||
|
/* Any copyright is dedicated to the Public Domain.
|
||||||
|
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
add_task(async function test_fullpageScreenshot() {
|
||||||
|
CustomizableUI.addWidgetToArea(
|
||||||
|
"screenshot-button",
|
||||||
|
CustomizableUI.AREA_NAVBAR
|
||||||
|
);
|
||||||
|
let screenshotBtn = document.getElementById("screenshot-button");
|
||||||
|
Assert.ok(screenshotBtn, "The screenshots button was added to the nav bar");
|
||||||
|
|
||||||
|
await BrowserTestUtils.withNewTab(
|
||||||
|
{
|
||||||
|
gBrowser,
|
||||||
|
url: TEST_PAGE,
|
||||||
|
},
|
||||||
|
async browser => {
|
||||||
|
let helper = new ScreenshotsHelper(browser);
|
||||||
|
let contentInfo = await helper.getContentDimensions();
|
||||||
|
ok(contentInfo, "Got dimensions back from the content");
|
||||||
|
|
||||||
|
// click toolbar button so panel shows
|
||||||
|
helper.triggerUIFromToolbar();
|
||||||
|
|
||||||
|
let panel = gBrowser.selectedBrowser.ownerDocument.querySelector(
|
||||||
|
"#screenshotsPagePanel"
|
||||||
|
);
|
||||||
|
await BrowserTestUtils.waitForMutationCondition(
|
||||||
|
panel,
|
||||||
|
{ attributes: true },
|
||||||
|
() => {
|
||||||
|
return BrowserTestUtils.is_visible(panel);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
ok(BrowserTestUtils.is_visible(panel), "Panel buttons are visible");
|
||||||
|
|
||||||
|
EventUtils.synthesizeKey("KEY_Escape");
|
||||||
|
|
||||||
|
await BrowserTestUtils.waitForMutationCondition(
|
||||||
|
panel,
|
||||||
|
{ attributes: true },
|
||||||
|
() => {
|
||||||
|
return BrowserTestUtils.is_hidden(panel);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
ok(BrowserTestUtils.is_hidden(panel), "Panel buttons are hidden");
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
6
browser/locales/en-US/browser/screenshotsOverlay.ftl
Normal file
6
browser/locales/en-US/browser/screenshotsOverlay.ftl
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# 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/.
|
||||||
|
|
||||||
|
screenshots-overlay-cancel-button = Cancel
|
||||||
|
screenshots-overlay-instructions = Drag or click on the page to select a region. Press ESC to cancel.
|
Loading…
Reference in New Issue
Block a user