173 lines
5.3 KiB
JavaScript
173 lines
5.3 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
|
|
/* 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 https://mozilla.org/MPL/2.0/. */
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "console", () => {
|
|
return console.createInstance({
|
|
prefix: "UserCharacteristicsPage",
|
|
maxLogLevelPref: "toolkit.telemetry.user_characteristics_ping.logLevel",
|
|
});
|
|
});
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
setTimeout: "resource://gre/modules/Timer.sys.mjs",
|
|
});
|
|
|
|
export class UserCharacteristicsWindowInfoChild extends JSWindowActorChild {
|
|
constructor() {
|
|
super();
|
|
|
|
// Expected properties to collect before
|
|
// sending the "WindowInfo::Done" message
|
|
this.targetProperties = new Set(["ScreenInfo", "PointerInfo"]);
|
|
this.collectedProperties = new Set();
|
|
// Event handlers to remove when the actor is destroyed
|
|
this.handlers = {};
|
|
this.destroyed = false;
|
|
}
|
|
|
|
/*
|
|
* This function populates the screen info from the tab it is running in,
|
|
* and sends the properties to the parent actor.
|
|
*/
|
|
populateScreenInfo() {
|
|
// I'm not sure if this is really necessary, but it's here to prevent
|
|
// the actor from running in the about:fingerprintingprotection page
|
|
// which is a hidden browser with no toolbars etc.
|
|
if (this.document.location.href === "about:fingerprintingprotection") {
|
|
return;
|
|
}
|
|
|
|
const result = new Map([
|
|
["screenWidth", this.contentWindow.screen.width],
|
|
["screenHeight", this.contentWindow.screen.height],
|
|
["outerHeight", this.contentWindow.outerHeight],
|
|
["innerHeight", this.contentWindow.innerHeight],
|
|
["outerWidth", this.contentWindow.outerWidth],
|
|
["innerWidth", this.contentWindow.innerWidth],
|
|
["availHeight", this.contentWindow.screen.availHeight],
|
|
["availWidth", this.contentWindow.screen.availWidth],
|
|
["pixelRatio", this.contentWindow.devicePixelRatio.toString()],
|
|
]);
|
|
|
|
if (result.values().some(v => v <= 0)) {
|
|
return;
|
|
}
|
|
|
|
this.sendMessage("ScreenInfo:Populated", result);
|
|
this.propertyCollected("ScreenInfo");
|
|
}
|
|
|
|
/*
|
|
* This function listens for touchstart and pointerdown events
|
|
* and sends the properties of the event to the parent actor.
|
|
* The reason that we listen for both events is because rotationAngle
|
|
* is only available in touch events and the rest of the properties
|
|
* are only available in pointer events.
|
|
*/
|
|
populatePointerInfo() {
|
|
const { promise, resolve } = Promise.withResolvers();
|
|
|
|
const touchStartHandler = e => mergeEvents(e);
|
|
this.contentWindow.windowRoot.addEventListener(
|
|
"touchstart",
|
|
touchStartHandler,
|
|
{ once: true }
|
|
);
|
|
|
|
// Allow some time for the touchstart event to be recorded
|
|
const pointerDownHandler = e => lazy.setTimeout(() => mergeEvents(e), 500);
|
|
this.contentWindow.windowRoot.addEventListener(
|
|
"pointerdown",
|
|
pointerDownHandler,
|
|
{ once: true }
|
|
);
|
|
|
|
const mergedEvents = {};
|
|
const mergeEvents = event => {
|
|
if (event.type === "touchstart") {
|
|
mergedEvents.touch = event;
|
|
} else {
|
|
mergedEvents.pointer = event;
|
|
}
|
|
|
|
// Resolve the promise if we got pointerdown
|
|
if (mergedEvents.pointer) {
|
|
resolve(mergedEvents);
|
|
}
|
|
};
|
|
|
|
promise.then(e => {
|
|
const map = new Map([
|
|
["pointerPressure", e.pointer.pressure],
|
|
["pointerTangentinalPressure", e.pointer.tangentialPressure],
|
|
["pointerTiltx", e.pointer.tiltX],
|
|
["pointerTilty", e.pointer.tiltY],
|
|
["pointerTwist", e.pointer.twist],
|
|
["pointerWidth", e.pointer.width],
|
|
["pointerHeight", e.pointer.height],
|
|
["touchRotationAngle", e.touch?.rotationAngle || 0],
|
|
]);
|
|
this.sendMessage("PointerInfo:Populated", map);
|
|
this.propertyCollected("PointerInfo");
|
|
});
|
|
|
|
this.handlers.touchstart = touchStartHandler;
|
|
this.handlers.pointerdown = pointerDownHandler;
|
|
|
|
if (Cu.isInAutomation) {
|
|
// Just simulate the events if we are in automation
|
|
const pointerEvent = new PointerEvent("pointerdown");
|
|
this.contentWindow.windowRoot.dispatchEvent(pointerEvent);
|
|
}
|
|
}
|
|
|
|
sendMessage(name, obj, transferables) {
|
|
if (this.destroyed) {
|
|
return;
|
|
}
|
|
|
|
this.sendAsyncMessage(name, obj, transferables);
|
|
}
|
|
|
|
propertyCollected(name) {
|
|
this.collectedProperties.add(name);
|
|
if (this.targetProperties.difference(this.collectedProperties).size === 0) {
|
|
this.sendMessage("WindowInfo::Done");
|
|
}
|
|
}
|
|
|
|
didDestroy() {
|
|
this.destroyed = true;
|
|
for (const [type, handler] of Object.entries(this.handlers)) {
|
|
this.contentWindow?.windowRoot?.removeEventListener(type, handler);
|
|
}
|
|
}
|
|
|
|
async receiveMessage(msg) {
|
|
lazy.console.debug("Actor Child: Got ", msg.name);
|
|
switch (msg.name) {
|
|
case "WindowInfo:PopulateFromDocument":
|
|
if (this.document.readyState == "complete") {
|
|
this.populateScreenInfo();
|
|
this.populatePointerInfo();
|
|
}
|
|
break;
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
async handleEvent(event) {
|
|
lazy.console.debug("Actor Child: Got ", event.type);
|
|
switch (event.type) {
|
|
case "DOMContentLoaded":
|
|
this.populateScreenInfo();
|
|
this.populatePointerInfo();
|
|
break;
|
|
}
|
|
}
|
|
}
|