237 lines
7.5 KiB
JavaScript
237 lines
7.5 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/. */
|
|
|
|
import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
|
|
|
|
const lazy = {};
|
|
|
|
ChromeUtils.defineESModuleGetters(lazy, {
|
|
BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.sys.mjs",
|
|
SessionStore:
|
|
"moz-src:///browser/components/sessionstore/SessionStore.sys.mjs",
|
|
});
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "WindowsUIUtils", () =>
|
|
Cc["@mozilla.org/windows-ui-utils;1"].getService(Ci.nsIWindowsUIUtils)
|
|
);
|
|
|
|
const PREF_UI_DENSITY = "browser.uidensity";
|
|
const PREF_AUTO_TOUCH_MODE = "browser.touchmode.auto";
|
|
const PREF_NOVA_ENABLED = "browser.nova.enabled";
|
|
|
|
/**
|
|
* The "Window density" setting as it is presented in about:preferences and on
|
|
* the Customize page.
|
|
*
|
|
* @param {Window} win
|
|
* The window to read the gUIDensity mode constants from.
|
|
* @returns {string}
|
|
*/
|
|
function currentSetting(win) {
|
|
if (
|
|
Services.prefs.getBoolPref(PREF_NOVA_ENABLED, false) &&
|
|
!Services.prefs.prefHasUserValue(PREF_UI_DENSITY)
|
|
) {
|
|
return "automatic";
|
|
}
|
|
let { gUIDensity } = win;
|
|
switch (Services.prefs.getIntPref(PREF_UI_DENSITY, gUIDensity.MODE_NORMAL)) {
|
|
case gUIDensity.MODE_COMPACT:
|
|
return "compact";
|
|
case gUIDensity.MODE_TOUCH:
|
|
return "touch";
|
|
default:
|
|
return Services.prefs.getBoolPref(PREF_AUTO_TOUCH_MODE, false)
|
|
? "standard_and_touch_for_tablet_mode"
|
|
: "standard";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* The density the window has resolved to, which differs from the setting
|
|
* whenever the setting resolves automatically.
|
|
*
|
|
* @param {Window} win
|
|
* The window to resolve the density for.
|
|
* @returns {string}
|
|
*/
|
|
function effectiveDensity(win) {
|
|
let { gUIDensity } = win;
|
|
switch (gUIDensity.getCurrentDensity().mode) {
|
|
case gUIDensity.MODE_COMPACT:
|
|
return "compact";
|
|
case gUIDensity.MODE_TOUCH:
|
|
return "touch";
|
|
default:
|
|
return "standard";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @returns {string} "yes", "no", or "unknown".
|
|
*/
|
|
function touchCapable() {
|
|
switch (AppConstants.platform) {
|
|
case "macosx":
|
|
return "no";
|
|
case "win":
|
|
return lazy.WindowsUIUtils.isTabletCapable ? "yes" : "no";
|
|
default:
|
|
// Neither GTK nor anything else exposes a usable signal.
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Records `ui_density.mode`, which describes both the window density the user
|
|
* has configured and the one the browser actually resolved to.
|
|
*
|
|
* The setting and the automatic adjustment counter are tracked per session, so
|
|
* this module owns the recording instead of gUIDensity, which is instantiated
|
|
* once per browser window. The window-scoped parts of the event (the effective
|
|
* density and the window dimensions) are read from the top browser window, so a
|
|
* session with several windows open still records one event per change rather
|
|
* than one per window.
|
|
*/
|
|
export const UIDensityTelemetry = {
|
|
_initialized: false,
|
|
/** @type {string|null} The `current` value of the last recorded event. */
|
|
_current: null,
|
|
/**
|
|
* @type {WeakMap<Window, string>} The `effective` value last recorded for
|
|
* each window. Kept per window because the density a window resolves to
|
|
* depends on that window: the top window can change to one that resolved
|
|
* differently, and comparing a window against another window's last
|
|
* recorded value would swallow that window's own changes.
|
|
*/
|
|
_lastEffective: new WeakMap(),
|
|
/** @type {number} Automatic changes of `effective` so far this session. */
|
|
_autoAdjustments: 0,
|
|
|
|
/**
|
|
* Seeds the per-window state for `win`, and, on the first call that gets a
|
|
* window able to provide the window-scoped parts of the event, records the
|
|
* startup event and starts watching for changes. Called from every browser
|
|
* window's gUIDensity.init().
|
|
*
|
|
* @param {Window} win
|
|
* The browser window being initialized.
|
|
*/
|
|
async init(win) {
|
|
// A window has to be seeded before it can report a change, so that its
|
|
// first gUIDensity notification isn't mistaken for one: customize mode
|
|
// notifies for every density it previews, without the density the window
|
|
// resolves to ever changing.
|
|
this._lastEffective.set(win, effectiveDensity(win));
|
|
|
|
// Popups and taskbar tabs have no density controls and are excluded from
|
|
// BrowserWindowTracker.getTopWindow(), so they can't provide the
|
|
// window-scoped parts of the event.
|
|
if (this._initialized || !win.toolbar.visible) {
|
|
return;
|
|
}
|
|
|
|
await lazy.SessionStore.promiseAllWindowsRestored;
|
|
if (this._initialized || win != lazy.BrowserWindowTracker.getTopWindow()) {
|
|
return;
|
|
}
|
|
this._initialized = true;
|
|
|
|
Services.prefs.addObserver(PREF_UI_DENSITY, this);
|
|
Services.prefs.addObserver(PREF_AUTO_TOUCH_MODE, this);
|
|
|
|
this._record(win, true);
|
|
},
|
|
|
|
/**
|
|
* Stops recording and clears the session state, so that a following init()
|
|
* behaves like a fresh session. Only used by tests: in a real session the
|
|
* observers live for as long as the application does.
|
|
*/
|
|
uninit() {
|
|
if (!this._initialized) {
|
|
return;
|
|
}
|
|
this._initialized = false;
|
|
this._current = null;
|
|
this._lastEffective = new WeakMap();
|
|
this._autoAdjustments = 0;
|
|
Services.prefs.removeObserver(PREF_UI_DENSITY, this);
|
|
Services.prefs.removeObserver(PREF_AUTO_TOUCH_MODE, this);
|
|
},
|
|
|
|
observe() {
|
|
let win = lazy.BrowserWindowTracker.getTopWindow();
|
|
if (win) {
|
|
this._record(win, false);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Called by gUIDensity when a window starts applying a different density,
|
|
* which under the automatic setting can happen without the user touching
|
|
* anything (a resize, tablet mode, the sidebar launcher expanding, ...).
|
|
*
|
|
* @param {Window} win
|
|
* The window whose density changed.
|
|
*/
|
|
onDensityChanged(win) {
|
|
if (!this._initialized || win != lazy.BrowserWindowTracker.getTopWindow()) {
|
|
return;
|
|
}
|
|
this._record(win, false);
|
|
},
|
|
|
|
/**
|
|
* Records the event, unless nothing that the event describes has changed.
|
|
*
|
|
* @param {Window} win
|
|
* The window to read the window-scoped parts of the event from.
|
|
* @param {boolean} onStartup
|
|
* Whether this is the once-per-session startup event.
|
|
*/
|
|
_record(win, onStartup) {
|
|
let current = currentSetting(win);
|
|
let effective = effectiveDensity(win);
|
|
|
|
if (
|
|
!onStartup &&
|
|
current == this._current &&
|
|
effective == this._lastEffective.get(win)
|
|
) {
|
|
return;
|
|
}
|
|
// A setting change is reported both by the pref observer and by
|
|
// gUIDensity, so whichever arrives first records it and the other bails
|
|
// out above.
|
|
let settingChanged = !onStartup && current != this._current;
|
|
|
|
let extra = {
|
|
current,
|
|
effective,
|
|
auto_adjustments_prior: this._autoAdjustments,
|
|
touch_capable: touchCapable(),
|
|
window_width: win.outerWidth,
|
|
window_height: win.outerHeight,
|
|
// The UI scale, not nsIDOMWindowUtils.displayDPI: that reports the scale
|
|
// factor on Windows but the display's physical DPI on macOS and Linux.
|
|
device_pixel_ratio: String(win.devicePixelRatio),
|
|
on_startup: onStartup,
|
|
};
|
|
if (settingChanged && this._current) {
|
|
extra.previous = this._current;
|
|
}
|
|
|
|
if (settingChanged) {
|
|
this._autoAdjustments = 0;
|
|
} else if (!onStartup) {
|
|
this._autoAdjustments++;
|
|
}
|
|
this._current = current;
|
|
this._lastEffective.set(win, effective);
|
|
|
|
Glean.uiDensity.mode.record(extra);
|
|
},
|
|
};
|