Bug 1736429 - Set up jswindowactors for screenshots and pass dimensions and scroll offsets from the child. r=mconley,sfoster

* Add jswindowactors for the Screenshots component, to facilitate communication between the UI (lives in the parent) and the content document.
* Fetch content document dimensions/offsets with e.g: `ScreenshotsUtils.fetchContentBounds(gBrowser.selectedBrowser)`.

Differential Revision: https://phabricator.services.mozilla.com/D129326
This commit is contained in:
Sam Foster 2021-11-19 15:25:19 +00:00
Родитель 62b77fa25e
Коммит 861b4bbc43
6 изменённых файлов: 295 добавлений и 7 удалений

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

@ -0,0 +1,156 @@
/* 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/. */
/* eslint-env mozilla/browser-window */
"use strict";
var EXPORTED_SYMBOLS = ["ScreenshotsComponentChild"];
class ScreenshotsComponentChild extends JSWindowActorChild {
receiveMessage(message) {
switch (message.name) {
case "Screenshots:ShowOverlay":
return this.startScreenshotsOverlay();
case "Screenshots:HideOverlay":
return this.endScreenshotsOverlay();
case "Screenshots:getFullPageBounds":
return this.getFullPageBounds();
case "Screenshots:getVisibleBounds":
return this.getVisibleBounds();
}
return null;
}
/**
* Resolves when the document is ready to have an overlay injected into it.
*
* @returns {Promise}
* @resolves {Boolean} true when document is ready or rejects
*/
documentIsReady() {
const document = this.document;
// Some pages take ages to finish loading - if at all.
// We want to respond to enable the screenshots UI as soon that is possible
function readyEnough() {
return (
document.readyState !== "uninitialized" && document.documentElement
);
}
if (readyEnough()) {
return Promise.resolve();
}
return new Promise((resolve, reject) => {
function onChange(event) {
if (event.type === "pagehide") {
document.removeEventListener("readystatechange", onChange);
this.contentWindow.removeEventListener("pagehide", onChange);
reject(new Error("document unloaded before it was ready"));
} else if (readyEnough()) {
document.removeEventListener("readystatechange", onChange);
this.contentWindow.removeEventListener("pagehide", onChange);
resolve();
}
}
document.addEventListener("readystatechange", onChange);
this.contentWindow.addEventListener("pagehide", onChange, { once: true });
});
}
/**
* Wait until the document is ready and then show the screenshots overlay
*
* @returns {Boolean} true when document is ready and the overlay is shown
* otherwise false
*/
async startScreenshotsOverlay(details = {}) {
try {
await this.documentIsReady();
} catch (ex) {
console.warn(`ScreenshotsComponentChild: ${ex.message}`);
return false;
}
return true;
}
/**
* Remove the screenshots overlay.
*
* @returns {Boolean}
* true when the overlay has been removed otherwise false
*/
endScreenshotsOverlay() {
// this function will be implemented soon
return true;
}
/**
* Gets the full page bounds for a full page screenshot.
*
* @returns { object }
* The device pixel ratio and a DOMRect of the scrollable content bounds.
*
* devicePixelRatio (float):
* The device pixel ratio of the screen
*
* rect (object):
* top (int):
* The scroll top position for the content window.
*
* left (int):
* The scroll left position for the content window.
*
* width (int):
* The scroll width of the content window.
*
* height (int):
* The scroll height of the content window.
*/
getFullPageBounds() {
let doc = this.document.documentElement;
let rect = new DOMRect(
doc.scrollTop,
doc.scrollLeft,
doc.scrollWidth,
doc.scrollHeight
);
let devicePixelRatio = this.document.ownerGlobal.devicePixelRatio;
return { devicePixelRatio, rect };
}
/**
* Gets the visible page bounds for a visible screenshot.
*
* @returns { object }
* The device pixel ratio and a DOMRect of the current visible
* content bounds.
*
* devicePixelRatio (float):
* The device pixel ratio of the screen
*
* rect (object):
* top (int):
* The top position for the content window.
*
* left (int):
* The left position for the content window.
*
* width (int):
* The width of the content window.
*
* height (int):
* The height of the content window.
*/
getVisibleBounds() {
let doc = this.document.documentElement;
let rect = new DOMRect(
doc.clientTop,
doc.clientLeft,
doc.clientWidth,
doc.clientHeight
);
let devicePixelRatio = this.document.ownerGlobal.devicePixelRatio;
return { devicePixelRatio, rect };
}
}

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

@ -25,6 +25,9 @@ with Files("PageStyleChild.jsm"):
with Files("PluginChild.jsm"): with Files("PluginChild.jsm"):
BUG_COMPONENT = ("Core", "Plug-ins") BUG_COMPONENT = ("Core", "Plug-ins")
with Files("ScreenshotsComponentChild.jsm"):
BUG_COMPONENT = ("Firefox", "Screenshots")
with Files("WebRTCChild.jsm"): with Files("WebRTCChild.jsm"):
BUG_COMPONENT = ("Firefox", "Site Permissions") BUG_COMPONENT = ("Firefox", "Site Permissions")
@ -79,6 +82,7 @@ FINAL_TARGET_FILES.actors += [
"RefreshBlockerParent.jsm", "RefreshBlockerParent.jsm",
"RFPHelperChild.jsm", "RFPHelperChild.jsm",
"RFPHelperParent.jsm", "RFPHelperParent.jsm",
"ScreenshotsComponentChild.jsm",
"SearchSERPTelemetryChild.jsm", "SearchSERPTelemetryChild.jsm",
"SearchSERPTelemetryParent.jsm", "SearchSERPTelemetryParent.jsm",
"SwitchDocumentDirectionChild.jsm", "SwitchDocumentDirectionChild.jsm",

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

@ -645,6 +645,13 @@ let JSWINDOWACTORS = {
enablePreference: "accessibility.blockautorefresh", enablePreference: "accessibility.blockautorefresh",
}, },
ScreenshotsComponent: {
child: {
moduleURI: "resource:///actors/ScreenshotsComponentChild.jsm",
},
enablePreference: "screenshots.browser.component.enabled",
},
SearchSERPTelemetry: { SearchSERPTelemetry: {
parent: { parent: {
moduleURI: "resource:///actors/SearchSERPTelemetryParent.jsm", moduleURI: "resource:///actors/SearchSERPTelemetryParent.jsm",

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

@ -14,6 +14,14 @@ const PanelOffsetY = -8;
var ScreenshotsUtils = { var ScreenshotsUtils = {
initialize() { initialize() {
if (
!Services.prefs.getBoolPref(
"screenshots.browser.component.enabled",
false
)
) {
return;
}
Services.obs.addObserver(this, "menuitem-screenshot"); Services.obs.addObserver(this, "menuitem-screenshot");
Services.obs.addObserver(this, "screenshots-take-screenshot"); Services.obs.addObserver(this, "screenshots-take-screenshot");
}, },
@ -23,6 +31,8 @@ var ScreenshotsUtils = {
let currDialogBox = browser.tabDialogBox; let currDialogBox = browser.tabDialogBox;
let zoom = subj.ZoomManager.getZoomForBrowser(browser);
switch (topic) { switch (topic) {
case "menuitem-screenshot": case "menuitem-screenshot":
// if dialog box exists then find the correct dialog box and close it // if dialog box exists then find the correct dialog box and close it
@ -56,7 +66,7 @@ var ScreenshotsUtils = {
// init UI as a tab dialog box // init UI as a tab dialog box
let dialogBox = gBrowser.getTabDialogBox(browser); let dialogBox = gBrowser.getTabDialogBox(browser);
return dialogBox.open( let { dialog } = dialogBox.open(
`chrome://browser/content/screenshots/screenshots.html?browsingContextId=${browser.browsingContext.id}`, `chrome://browser/content/screenshots/screenshots.html?browsingContextId=${browser.browsingContext.id}`,
{ {
features: "resizable=no", features: "resizable=no",
@ -64,9 +74,15 @@ var ScreenshotsUtils = {
allowDuplicateDialogs: false, allowDuplicateDialogs: false,
} }
); );
this.doScreenshot(browser, dialog, zoom, data);
} }
return null; return null;
}, },
/**
* Notify screenshots when screenshot command is used.
* @param window The current window the screenshot command was used.
* @param type The type of screenshot taken. Used for telemetry.
*/
notify(window, type) { notify(window, type) {
if (Services.prefs.getBoolPref("screenshots.browser.component.enabled")) { if (Services.prefs.getBoolPref("screenshots.browser.component.enabled")) {
Services.obs.notifyObservers( Services.obs.notifyObservers(
@ -77,16 +93,40 @@ var ScreenshotsUtils = {
Services.obs.notifyObservers(null, "menuitem-screenshot-extension", type); Services.obs.notifyObservers(null, "menuitem-screenshot-extension", type);
} }
}, },
/**
* Creates and returns a Screenshots actor.
* @param browser The current browser.
* @returns JSWindowActor The screenshot actor.
*/
getActor(browser) {
let actor = browser.browsingContext.currentWindowGlobal.getActor(
"ScreenshotsComponent"
);
return actor;
},
/**
* If the buttons panel exists and the panel is open we will hipe the panel
* popup and hide the screenshot overlay.
* Otherwise create or display the buttons.
* @param browser The current browser.
*/
togglePreview(browser) { togglePreview(browser) {
let buttonsPanel = browser.ownerDocument.querySelector( let buttonsPanel = browser.ownerDocument.querySelector(
"#screenshotsPagePanel" "#screenshotsPagePanel"
); );
if (buttonsPanel && buttonsPanel.state !== "closed") { if (buttonsPanel && buttonsPanel.state !== "closed") {
buttonsPanel.hidePopup(); buttonsPanel.hidePopup();
} else { let actor = this.getActor(browser);
this.createOrDisplayButtons(browser); return actor.sendQuery("Screenshots:HideOverlay");
} }
return this.createOrDisplayButtons(browser);
}, },
/**
* If the buttons panel does not exist then we will replace the buttons
* panel template with the buttons panel then open the buttons panel and
* show the screenshots overaly.
* @param browser The current browser.
*/
createOrDisplayButtons(browser) { createOrDisplayButtons(browser) {
let doc = browser.ownerDocument; let doc = browser.ownerDocument;
let buttonsPanel = doc.querySelector("#screenshotsPagePanel"); let buttonsPanel = doc.querySelector("#screenshotsPagePanel");
@ -98,5 +138,90 @@ var ScreenshotsUtils = {
} }
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.
* @param browser The current browser.
* @returns { object }
* Contains the full page bounds from the screenshots child actor.
*/
fetchFullPageBounds(browser) {
let actor = this.getActor(browser);
return actor.sendQuery("Screenshots:getFullPageBounds");
},
/**
* Gets the visible bounds from the screenshots child actor.
* @param browser The current browser.
* @returns { object }
* Contains the visible bounds from the screenshots child actor.
*/
fetchVisibleBounds(browser) {
let actor = this.getActor(browser);
return actor.sendQuery("Screenshots:getVisibleBounds");
},
/**
* Add screenshot-ui to the dialog box and then take the screenshot
* @param browser The current browser.
* @param dialog The dialog box to show the screenshot preview.
* @param zoom The current zoom level.
* @param type The type of screenshot taken.
*/
async doScreenshot(browser, dialog, zoom, type) {
await dialog._dialogReady;
let screenshotsUI = dialog._frame.contentDocument.createElement(
"screenshots-ui"
);
dialog._frame.contentDocument.body.appendChild(screenshotsUI);
let rect;
if (type === "full-page") {
({ rect } = await this.fetchFullPageBounds(browser));
} else {
({ rect } = await this.fetchVisibleBounds(browser));
}
return this.takeScreenshot(browser, dialog, rect, zoom);
},
/**
* Take the screenshot and add the image to the dialog box
* @param browser The current browser.
* @param dialog The dialog box to show the screenshot preview.
* @param rect DOMRect containing bounds of the screenshot.
* @param zoom The current zoom level.
*/
async takeScreenshot(browser, dialog, rect, zoom) {
let browsingContext = BrowsingContext.get(browser.browsingContext.id);
let snapshot = await browsingContext.currentWindowGlobal.drawSnapshot(
rect,
zoom,
"rgb(255,255,255)"
);
let canvas = dialog._frame.contentDocument.createElementNS(
"http://www.w3.org/1999/xhtml",
"html:canvas"
);
let context = canvas.getContext("2d");
canvas.width = snapshot.width;
canvas.height = snapshot.height;
context.drawImage(snapshot, 0, 0);
canvas.toBlob(function(blob) {
let newImg = dialog._frame.contentDocument.createElement("img");
let url = URL.createObjectURL(blob);
newImg.id = "placeholder-image";
newImg.src = url;
dialog._frame.contentDocument
.getElementById("preview-image-div")
.appendChild(newImg);
});
snapshot.close();
}, },
}; };

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

@ -37,7 +37,5 @@
</div> </div>
</div> </div>
</template> </template>
<screenshots-ui></screenshots-ui>
</body> </body>
</html> </html>

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

@ -21,8 +21,6 @@ class ScreenshotsUI extends HTMLElement {
} }
async connectedCallback() { async connectedCallback() {
this.initialize(); this.initialize();
await this.takeVisibleScreenshot();
} }
initialize() { initialize() {