mirror of
https://gitlab.winehq.org/wine/wine-gecko.git
synced 2024-09-13 09:24:08 -07:00
Bug 1207089 - Telemetry for permission notifications. r=MattN,vladan
This commit is contained in:
parent
7eb09f8840
commit
e644940cbc
@ -5708,13 +5708,33 @@
|
||||
"kind": "boolean",
|
||||
"description": "Count the number of times the user clicked 'allow' on the hidden-plugin infobar."
|
||||
},
|
||||
"POPUP_NOTIFICATION_MAINACTION_TRIGGERED_MS": {
|
||||
"expires_in_version": "40",
|
||||
"kind": "linear",
|
||||
"low": 25,
|
||||
"high": "80 * 25",
|
||||
"n_buckets": "80 + 1",
|
||||
"description": "The time (in milliseconds) after showing a PopupNotification that the mainAction was first triggered"
|
||||
"POPUP_NOTIFICATION_STATS": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "48",
|
||||
"kind": "enumerated",
|
||||
"keyed": true,
|
||||
"n_values": 40,
|
||||
"description": "(Bug 1207089) Usage of popup notifications, keyed by ID (0 = Offered, 1..4 = Action, 5 = Click outside, 6 = Leave page, 7 = Use 'X', 8 = Not now, 10 = Open submenu, 11 = Learn more. Add 20 if happened after reopen.)"
|
||||
},
|
||||
"POPUP_NOTIFICATION_MAIN_ACTION_MS": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "48",
|
||||
"kind": "exponential",
|
||||
"keyed": true,
|
||||
"low": 100,
|
||||
"high": 600000,
|
||||
"n_buckets": 40,
|
||||
"description": "(Bug 1207089) Time in ms between initially requesting a popup notification and triggering the main action, keyed by ID"
|
||||
},
|
||||
"POPUP_NOTIFICATION_DISMISSAL_MS": {
|
||||
"alert_emails": ["firefox-dev@mozilla.org"],
|
||||
"expires_in_version": "48",
|
||||
"kind": "exponential",
|
||||
"keyed": true,
|
||||
"low": 200,
|
||||
"high": 20000,
|
||||
"n_buckets": 50,
|
||||
"description": "(Bug 1207089) Time in ms between displaying a popup notification and dismissing it without an action the first time, keyed by ID"
|
||||
},
|
||||
"DEVTOOLS_DEBUGGER_RDP_LOCAL_RELOAD_MS": {
|
||||
"expires_in_version": "never",
|
||||
|
@ -492,7 +492,7 @@
|
||||
</xul:hbox>
|
||||
<children includes="popupnotificationcontent"/>
|
||||
<xul:label class="text-link popup-notification-learnmore-link"
|
||||
xbl:inherits="href=learnmoreurl">&learnMore;</xul:label>
|
||||
xbl:inherits="onclick=learnmoreclick,href=learnmoreurl">&learnMore;</xul:label>
|
||||
<xul:spacer flex="1"/>
|
||||
<xul:hbox class="popup-notification-button-container"
|
||||
pack="end" align="center">
|
||||
@ -500,7 +500,7 @@
|
||||
<xul:button anonid="button"
|
||||
class="popup-notification-menubutton"
|
||||
type="menu-button"
|
||||
xbl:inherits="oncommand=buttoncommand,label=buttonlabel,accesskey=buttonaccesskey">
|
||||
xbl:inherits="oncommand=buttoncommand,onpopupshown=buttonpopupshown,label=buttonlabel,accesskey=buttonaccesskey">
|
||||
<xul:menupopup anonid="menupopup"
|
||||
xbl:inherits="oncommand=menucommand">
|
||||
<children/>
|
||||
|
@ -7,6 +7,7 @@ this.EXPORTED_SYMBOLS = ["PopupNotifications"];
|
||||
var Cc = Components.classes, Ci = Components.interfaces, Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/PrivateBrowsingUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
const NOTIFICATION_EVENT_DISMISSED = "dismissed";
|
||||
@ -21,6 +22,21 @@ const ICON_ANCHOR_ATTRIBUTE = "popupnotificationanchor";
|
||||
|
||||
const PREF_SECURITY_DELAY = "security.notification_enable_delay";
|
||||
|
||||
// Enumerated values for the POPUP_NOTIFICATION_STATS telemetry histogram.
|
||||
const TELEMETRY_STAT_OFFERED = 0;
|
||||
const TELEMETRY_STAT_ACTION_1 = 1;
|
||||
const TELEMETRY_STAT_ACTION_2 = 2;
|
||||
const TELEMETRY_STAT_ACTION_3 = 3;
|
||||
const TELEMETRY_STAT_ACTION_LAST = 4;
|
||||
const TELEMETRY_STAT_DISMISSAL_CLICK_ELSEWHERE = 5;
|
||||
const TELEMETRY_STAT_DISMISSAL_LEAVE_PAGE = 6;
|
||||
const TELEMETRY_STAT_DISMISSAL_CLOSE_BUTTON = 7;
|
||||
const TELEMETRY_STAT_DISMISSAL_NOT_NOW = 8;
|
||||
const TELEMETRY_STAT_OPEN_SUBMENU = 10;
|
||||
const TELEMETRY_STAT_LEARN_MORE = 11;
|
||||
|
||||
const TELEMETRY_STAT_REOPENED_OFFSET = 20;
|
||||
|
||||
var popupNotificationsMap = new WeakMap();
|
||||
var gNotificationParents = new WeakMap;
|
||||
|
||||
@ -54,6 +70,13 @@ function Notification(id, message, anchorID, mainAction, secondaryActions,
|
||||
this.browser = browser;
|
||||
this.owner = owner;
|
||||
this.options = options || {};
|
||||
|
||||
this._dismissed = false;
|
||||
this.wasDismissed = false;
|
||||
this.recordedTelemetryStats = new Set();
|
||||
this.isPrivate = PrivateBrowsingUtils.isWindowPrivate(
|
||||
this.browser.ownerDocument.defaultView);
|
||||
this.timeCreated = this.owner.window.performance.now();
|
||||
}
|
||||
|
||||
Notification.prototype = {
|
||||
@ -68,6 +91,20 @@ Notification.prototype = {
|
||||
options: null,
|
||||
timeShown: null,
|
||||
|
||||
/**
|
||||
* Indicates whether the notification is currently dismissed.
|
||||
*/
|
||||
set dismissed(value) {
|
||||
this._dismissed = value;
|
||||
if (value) {
|
||||
// Keep the dismissal into account when recording telemetry.
|
||||
this.wasDismissed = true;
|
||||
}
|
||||
},
|
||||
get dismissed() {
|
||||
return this._dismissed;
|
||||
},
|
||||
|
||||
/**
|
||||
* Removes the notification and updates the popup accordingly if needed.
|
||||
*/
|
||||
@ -95,7 +132,45 @@ Notification.prototype = {
|
||||
|
||||
reshow: function() {
|
||||
this.owner._reshowNotifications(this.anchorElement, this.browser);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a value to the specified histogram, that must be keyed by ID.
|
||||
*/
|
||||
_recordTelemetry(histogramId, value) {
|
||||
if (this.isPrivate) {
|
||||
// The reason why we don't record telemetry in private windows is because
|
||||
// the available actions can be different from regular mode. The main
|
||||
// difference is that all of the persistent permission options like
|
||||
// "Always remember" aren't there, so they really need to be handled
|
||||
// separately to avoid skewing results. For notifications with the same
|
||||
// choices, there would be no reason not to record in private windows as
|
||||
// well, but it's just simpler to use the same check for everything.
|
||||
return;
|
||||
}
|
||||
let histogram = Services.telemetry.getKeyedHistogramById(histogramId);
|
||||
histogram.add("(all)", value);
|
||||
histogram.add(this.id, value);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds an enumerated value to the POPUP_NOTIFICATION_STATS histogram,
|
||||
* ensuring that it is recorded at most once for each distinct Notification.
|
||||
*
|
||||
* Statistics for reopened notifications are recorded in separate buckets.
|
||||
*
|
||||
* @param value
|
||||
* One of the TELEMETRY_STAT_ constants.
|
||||
*/
|
||||
_recordTelemetryStat(value) {
|
||||
if (this.wasDismissed) {
|
||||
value += TELEMETRY_STAT_REOPENED_OFFSET;
|
||||
}
|
||||
if (!this.recordedTelemetryStats.has(value)) {
|
||||
this.recordedTelemetryStats.add(value);
|
||||
this._recordTelemetry("POPUP_NOTIFICATION_STATS", value);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
@ -416,6 +491,12 @@ PopupNotifications.prototype = {
|
||||
case "activate":
|
||||
case "TabSelect":
|
||||
let self = this;
|
||||
// This is where we could detect if the panel is dismissed if the page
|
||||
// was switched. Unfortunately, the user usually has clicked elsewhere
|
||||
// at this point so this value only gets recorded for programmatic
|
||||
// reasons, like the "Learn More" link being clicked and resulting in a
|
||||
// tab switch.
|
||||
this.nextDismissReason = TELEMETRY_STAT_DISMISSAL_LEAVE_PAGE;
|
||||
// setTimeout(..., 0) needed, otherwise openPopup from "activate" event
|
||||
// handler results in the popup being hidden again for some reason...
|
||||
this.window.setTimeout(function () {
|
||||
@ -465,7 +546,11 @@ PopupNotifications.prototype = {
|
||||
/**
|
||||
* Dismisses the notification without removing it.
|
||||
*/
|
||||
_dismiss: function PopupNotifications_dismiss() {
|
||||
_dismiss: function PopupNotifications_dismiss(telemetryReason) {
|
||||
if (telemetryReason) {
|
||||
this.nextDismissReason = telemetryReason;
|
||||
}
|
||||
|
||||
let browser = this.panel.firstChild &&
|
||||
this.panel.firstChild.notification.browser;
|
||||
this.panel.hidePopup();
|
||||
@ -546,17 +631,21 @@ PopupNotifications.prototype = {
|
||||
popupnotification.setAttribute("label", n.message);
|
||||
popupnotification.setAttribute("id", popupnotificationID);
|
||||
popupnotification.setAttribute("popupid", n.id);
|
||||
popupnotification.setAttribute("closebuttoncommand", "PopupNotifications._dismiss();");
|
||||
popupnotification.setAttribute("closebuttoncommand", `PopupNotifications._dismiss(${TELEMETRY_STAT_DISMISSAL_CLOSE_BUTTON});`);
|
||||
if (n.mainAction) {
|
||||
popupnotification.setAttribute("buttonlabel", n.mainAction.label);
|
||||
popupnotification.setAttribute("buttonaccesskey", n.mainAction.accessKey);
|
||||
popupnotification.setAttribute("buttoncommand", "PopupNotifications._onButtonCommand(event);");
|
||||
popupnotification.setAttribute("buttoncommand", "PopupNotifications._onButtonEvent(event, 'buttoncommand');");
|
||||
popupnotification.setAttribute("buttonpopupshown", "PopupNotifications._onButtonEvent(event, 'buttonpopupshown');");
|
||||
popupnotification.setAttribute("learnmoreclick", "PopupNotifications._onButtonEvent(event, 'learnmoreclick');");
|
||||
popupnotification.setAttribute("menucommand", "PopupNotifications._onMenuCommand(event);");
|
||||
popupnotification.setAttribute("closeitemcommand", "PopupNotifications._dismiss();event.stopPropagation();");
|
||||
popupnotification.setAttribute("closeitemcommand", `PopupNotifications._dismiss(${TELEMETRY_STAT_DISMISSAL_NOT_NOW});event.stopPropagation();`);
|
||||
} else {
|
||||
popupnotification.removeAttribute("buttonlabel");
|
||||
popupnotification.removeAttribute("buttonaccesskey");
|
||||
popupnotification.removeAttribute("buttoncommand");
|
||||
popupnotification.removeAttribute("buttonpopupshown");
|
||||
popupnotification.removeAttribute("learnmoreclick");
|
||||
popupnotification.removeAttribute("menucommand");
|
||||
popupnotification.removeAttribute("closeitemcommand");
|
||||
}
|
||||
@ -588,6 +677,8 @@ PopupNotifications.prototype = {
|
||||
popupnotification.notification = n;
|
||||
|
||||
if (n.secondaryActions) {
|
||||
let telemetryStatId = TELEMETRY_STAT_ACTION_2;
|
||||
|
||||
n.secondaryActions.forEach(function (a) {
|
||||
let item = doc.createElementNS(XUL_NS, "menuitem");
|
||||
item.setAttribute("label", a.label);
|
||||
@ -596,6 +687,13 @@ PopupNotifications.prototype = {
|
||||
item.action = a;
|
||||
|
||||
popupnotification.appendChild(item);
|
||||
|
||||
// We can only record a limited number of actions in telemetry. If
|
||||
// there are more, the latest are all recorded in the last bucket.
|
||||
item.action.telemetryStatId = telemetryStatId;
|
||||
if (telemetryStatId < TELEMETRY_STAT_ACTION_LAST) {
|
||||
telemetryStatId++;
|
||||
}
|
||||
}, this);
|
||||
|
||||
if (n.options.hideNotNow) {
|
||||
@ -658,9 +756,18 @@ PopupNotifications.prototype = {
|
||||
// click-to-play plugins, so copy the popupid and use css.
|
||||
this.panel.setAttribute("popupid", this.panel.firstChild.getAttribute("popupid"));
|
||||
notificationsToShow.forEach(function (n) {
|
||||
// Record that the notification was actually displayed on screen.
|
||||
// Notifications that were opened a second time or that were originally
|
||||
// shown with "options.dismissed" will be recorded in a separate bucket.
|
||||
n._recordTelemetryStat(TELEMETRY_STAT_OFFERED);
|
||||
// Remember the time the notification was shown for the security delay.
|
||||
n.timeShown = this.window.performance.now();
|
||||
}, this);
|
||||
|
||||
// Unless the panel closing is triggered by a specific known code path,
|
||||
// the next reason will be that the user clicked elsewhere.
|
||||
this.nextDismissReason = TELEMETRY_STAT_DISMISSAL_CLICK_ELSEWHERE;
|
||||
|
||||
this.panel.openPopup(anchorElement, "bottomcenter topleft");
|
||||
notificationsToShow.forEach(function (n) {
|
||||
this._fireCallback(n, NOTIFICATION_EVENT_SHOWN);
|
||||
@ -979,6 +1086,16 @@ PopupNotifications.prototype = {
|
||||
if (notifications.indexOf(notificationObj) == -1)
|
||||
return;
|
||||
|
||||
// Record the time of the first notification dismissal if the main action
|
||||
// was not triggered in the meantime.
|
||||
let timeSinceShown = this.window.performance.now() - notificationObj.timeShown;
|
||||
if (!notificationObj.wasDismissed &&
|
||||
!notificationObj.recordedTelemetryMainAction) {
|
||||
notificationObj._recordTelemetry("POPUP_NOTIFICATION_DISMISSAL_MS",
|
||||
timeSinceShown);
|
||||
}
|
||||
notificationObj._recordTelemetryStat(this.nextDismissReason);
|
||||
|
||||
// Do not mark the notification as dismissed or fire NOTIFICATION_EVENT_DISMISSED
|
||||
// if the notification is removed.
|
||||
if (notificationObj.options.removeOnDismissal) {
|
||||
@ -990,7 +1107,7 @@ PopupNotifications.prototype = {
|
||||
}, this);
|
||||
},
|
||||
|
||||
_onButtonCommand: function PopupNotifications_onButtonCommand(event) {
|
||||
_onButtonEvent(event, type) {
|
||||
// Need to find the associated notification object, which is a bit tricky
|
||||
// since it isn't associated with the button directly - this is kind of
|
||||
// gross and very dependent on the structure of the popupnotification
|
||||
@ -1002,27 +1119,42 @@ PopupNotifications.prototype = {
|
||||
notificationEl = parent;
|
||||
|
||||
if (!notificationEl)
|
||||
throw "PopupNotifications_onButtonCommand: couldn't find notification element";
|
||||
throw "PopupNotifications._onButtonEvent: couldn't find notification element";
|
||||
|
||||
if (!notificationEl.notification)
|
||||
throw "PopupNotifications_onButtonCommand: couldn't find notification";
|
||||
throw "PopupNotifications._onButtonEvent: couldn't find notification";
|
||||
|
||||
let notification = notificationEl.notification;
|
||||
let timeSinceShown = this.window.performance.now() - notification.timeShown;
|
||||
|
||||
// Only report the first time mainAction is triggered and remember that this occurred.
|
||||
if (!notification.timeMainActionFirstTriggered) {
|
||||
notification.timeMainActionFirstTriggered = timeSinceShown;
|
||||
Services.telemetry.getHistogramById("POPUP_NOTIFICATION_MAINACTION_TRIGGERED_MS").
|
||||
add(timeSinceShown);
|
||||
if (type == "buttonpopupshown") {
|
||||
notification._recordTelemetryStat(TELEMETRY_STAT_OPEN_SUBMENU);
|
||||
return;
|
||||
}
|
||||
|
||||
if (type == "learnmoreclick") {
|
||||
notification._recordTelemetryStat(TELEMETRY_STAT_LEARN_MORE);
|
||||
return;
|
||||
}
|
||||
|
||||
// Record the total timing of the main action since the notification was
|
||||
// created, even if the notification was dismissed in the meantime.
|
||||
let timeSinceCreated = this.window.performance.now() - notification.timeCreated;
|
||||
if (!notification.recordedTelemetryMainAction) {
|
||||
notification.recordedTelemetryMainAction = true;
|
||||
notification._recordTelemetry("POPUP_NOTIFICATION_MAIN_ACTION_MS",
|
||||
timeSinceCreated);
|
||||
}
|
||||
|
||||
let timeSinceShown = this.window.performance.now() - notification.timeShown;
|
||||
if (timeSinceShown < this.buttonDelay) {
|
||||
Services.console.logStringMessage("PopupNotifications_onButtonCommand: " +
|
||||
Services.console.logStringMessage("PopupNotifications._onButtonEvent: " +
|
||||
"Button click happened before the security delay: " +
|
||||
timeSinceShown + "ms");
|
||||
return;
|
||||
}
|
||||
|
||||
notification._recordTelemetryStat(TELEMETRY_STAT_ACTION_1);
|
||||
|
||||
try {
|
||||
notification.mainAction.callback.call();
|
||||
} catch(error) {
|
||||
@ -1044,6 +1176,9 @@ PopupNotifications.prototype = {
|
||||
throw "menucommand target has no associated action/notification";
|
||||
|
||||
event.stopPropagation();
|
||||
|
||||
target.notification._recordTelemetryStat(target.action.telemetryStatId);
|
||||
|
||||
try {
|
||||
target.action.callback.call();
|
||||
} catch(error) {
|
||||
|
Loading…
Reference in New Issue
Block a user