mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-25 05:41:12 +00:00
Bug 1822395 - Telemetry for new PiP controls. r=mconley
Differential Revision: https://phabricator.services.mozilla.com/D173079
This commit is contained in:
parent
aabb183c44
commit
229e52a604
@ -117,13 +117,13 @@ export class ContextMenuChild extends JSWindowActorChild {
|
||||
}
|
||||
break;
|
||||
case "pictureinpicture":
|
||||
if (!media.isCloningElementVisually) {
|
||||
Services.telemetry.keyedScalarAdd(
|
||||
"pictureinpicture.opened_method",
|
||||
"contextmenu",
|
||||
1
|
||||
);
|
||||
let args = {
|
||||
method: "contextMenu",
|
||||
firstTimeToggle: (!Services.prefs.getBoolPref(
|
||||
"media.videocontrols.picture-in-picture.video-toggle.has-used"
|
||||
)).toString(),
|
||||
@ -131,14 +131,16 @@ export class ContextMenuChild extends JSWindowActorChild {
|
||||
Services.telemetry.recordEvent(
|
||||
"pictureinpicture",
|
||||
"opened_method",
|
||||
"method",
|
||||
"contextMenu",
|
||||
null,
|
||||
args
|
||||
);
|
||||
}
|
||||
let event = new this.contentWindow.CustomEvent(
|
||||
"MozTogglePictureInPicture",
|
||||
{
|
||||
bubbles: true,
|
||||
detail: { reason: "contextMenu" },
|
||||
},
|
||||
this.contentWindow
|
||||
);
|
||||
|
@ -4878,13 +4878,7 @@ BrowserGlue.prototype = {
|
||||
Services.telemetry.recordEvent(
|
||||
"pictureinpicture.settings",
|
||||
"enable",
|
||||
"player"
|
||||
);
|
||||
} else {
|
||||
Services.telemetry.recordEvent(
|
||||
"pictureinpicture.settings",
|
||||
"disable",
|
||||
"player"
|
||||
"settings"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -333,6 +333,17 @@ var gMainPane = {
|
||||
)
|
||||
) {
|
||||
document.getElementById("pictureInPictureBox").hidden = false;
|
||||
setEventListener("pictureInPictureToggleEnabled", "command", function(
|
||||
event
|
||||
) {
|
||||
if (!event.target.checked) {
|
||||
Services.telemetry.recordEvent(
|
||||
"pictureinpicture.settings",
|
||||
"disable",
|
||||
"settings"
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (AppConstants.platform == "win") {
|
||||
|
@ -136,7 +136,10 @@ export class PictureInPictureLauncherChild extends JSWindowActorChild {
|
||||
switch (event.type) {
|
||||
case "MozTogglePictureInPicture": {
|
||||
if (event.isTrusted) {
|
||||
this.togglePictureInPicture(event.target);
|
||||
this.togglePictureInPicture({
|
||||
video: event.target,
|
||||
reason: event.detail,
|
||||
});
|
||||
}
|
||||
break;
|
||||
}
|
||||
@ -158,24 +161,25 @@ export class PictureInPictureLauncherChild extends JSWindowActorChild {
|
||||
* Picture-in-Picture window existing, this tells the parent to
|
||||
* close it before opening the new one.
|
||||
*
|
||||
* @param {Element} video The <video> element to view in a Picture
|
||||
* in Picture window.
|
||||
* @param {Object} pipObject An object containing the video and reason
|
||||
* for toggling the PiP video
|
||||
*
|
||||
* @return {Promise}
|
||||
* @resolves {undefined} Once the new Picture-in-Picture window
|
||||
* has been requested.
|
||||
*/
|
||||
async togglePictureInPicture(video) {
|
||||
async togglePictureInPicture(pipObject) {
|
||||
let { video, reason } = pipObject;
|
||||
if (video.isCloningElementVisually) {
|
||||
// The only way we could have entered here for the same video is if
|
||||
// we are toggling via the context menu, since we hide the inline
|
||||
// Picture-in-Picture toggle when a video is being displayed in
|
||||
// Picture-in-Picture. Turn off PiP in this case
|
||||
// we are toggling via the context menu or via the urlbar button,
|
||||
// since we hide the inline Picture-in-Picture toggle when a video
|
||||
// is being displayed in Picture-in-Picture. Turn off PiP in this case
|
||||
const stopPipEvent = new this.contentWindow.CustomEvent(
|
||||
"MozStopPictureInPicture",
|
||||
{
|
||||
bubbles: true,
|
||||
detail: { reason: "context-menu" },
|
||||
detail: reason,
|
||||
}
|
||||
);
|
||||
video.dispatchEvent(stopPipEvent);
|
||||
@ -245,7 +249,7 @@ export class PictureInPictureLauncherChild extends JSWindowActorChild {
|
||||
listOfVideos.sort((a, b) => b.duration - a.duration)[0];
|
||||
}
|
||||
if (video) {
|
||||
this.togglePictureInPicture(video);
|
||||
this.togglePictureInPicture({ video });
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -604,10 +608,25 @@ export class PictureInPictureToggleChild extends JSWindowActorChild {
|
||||
this.eligiblePipVideos
|
||||
)[0];
|
||||
if (video) {
|
||||
if (!video.isCloningElementVisually) {
|
||||
let args = {
|
||||
firstTimeToggle: (!Services.prefs.getBoolPref(
|
||||
"media.videocontrols.picture-in-picture.video-toggle.has-used"
|
||||
)).toString(),
|
||||
};
|
||||
Services.telemetry.recordEvent(
|
||||
"pictureinpicture",
|
||||
"opened_method",
|
||||
"urlBar",
|
||||
null,
|
||||
args
|
||||
);
|
||||
}
|
||||
let pipEvent = new this.contentWindow.CustomEvent(
|
||||
"MozTogglePictureInPicture",
|
||||
{
|
||||
bubbles: true,
|
||||
detail: { reason: "urlBar" },
|
||||
}
|
||||
);
|
||||
video.dispatchEvent(pipEvent);
|
||||
@ -1008,7 +1027,6 @@ export class PictureInPictureToggleChild extends JSWindowActorChild {
|
||||
1
|
||||
);
|
||||
let args = {
|
||||
method: "toggle",
|
||||
firstTimeToggle: (!Services.prefs.getBoolPref(
|
||||
TOGGLE_HAS_USED_PREF
|
||||
)).toString(),
|
||||
@ -1016,7 +1034,7 @@ export class PictureInPictureToggleChild extends JSWindowActorChild {
|
||||
Services.telemetry.recordEvent(
|
||||
"pictureinpicture",
|
||||
"opened_method",
|
||||
"method",
|
||||
"toggle",
|
||||
null,
|
||||
args
|
||||
);
|
||||
@ -1861,7 +1879,7 @@ export class PictureInPictureChild extends JSWindowActorChild {
|
||||
switch (event.type) {
|
||||
case "MozStopPictureInPicture": {
|
||||
if (event.isTrusted && event.target === this.getWeakVideo()) {
|
||||
const reason = event.detail?.reason || "video-el-remove";
|
||||
const reason = event.detail?.reason || "videoElRemove";
|
||||
this.closePictureInPicture({ reason });
|
||||
}
|
||||
break;
|
||||
@ -1927,7 +1945,7 @@ export class PictureInPictureChild extends JSWindowActorChild {
|
||||
// close Picture-in-Picture.
|
||||
this.emptiedTimeout = setTimeout(() => {
|
||||
if (!video || !video.src) {
|
||||
this.closePictureInPicture({ reason: "video-el-emptied" });
|
||||
this.closePictureInPicture({ reason: "videoElEmptied" });
|
||||
}
|
||||
}, EMPTIED_TIMEOUT_MS);
|
||||
break;
|
||||
@ -2269,7 +2287,7 @@ export class PictureInPictureChild extends JSWindowActorChild {
|
||||
// If the video element has gone away before we've had a chance to set up
|
||||
// Picture-in-Picture for it, tell the parent to close the Picture-in-Picture
|
||||
// window.
|
||||
await this.closePictureInPicture({ reason: "setup-failure" });
|
||||
await this.closePictureInPicture({ reason: "setupFailure" });
|
||||
return;
|
||||
}
|
||||
|
||||
@ -2495,7 +2513,7 @@ export class PictureInPictureChild extends JSWindowActorChild {
|
||||
return;
|
||||
}
|
||||
this.pause();
|
||||
this.closePictureInPicture({ reason: "close-player-shortcut" });
|
||||
this.closePictureInPicture({ reason: "closePlayerShortcut" });
|
||||
break;
|
||||
case "downArrow" /* Volume decrease */:
|
||||
if (this.isKeyDisabled(lazy.KEYBOARD_CONTROLS.VOLUME)) {
|
||||
|
@ -591,13 +591,11 @@ export var PictureInPicture = {
|
||||
}
|
||||
this.removePiPBrowserFromWeakMap(this.weakWinToBrowser.get(win));
|
||||
|
||||
let args = { reason };
|
||||
Services.telemetry.recordEvent(
|
||||
"pictureinpicture",
|
||||
"closed_method",
|
||||
"method",
|
||||
null,
|
||||
args
|
||||
reason,
|
||||
null
|
||||
);
|
||||
await this.closePipWindow(win);
|
||||
},
|
||||
@ -1265,6 +1263,11 @@ export var PictureInPicture = {
|
||||
|
||||
hideToggle() {
|
||||
Services.prefs.setBoolPref(TOGGLE_ENABLED_PREF, false);
|
||||
Services.telemetry.recordEvent(
|
||||
"pictureinpicture.settings",
|
||||
"disable",
|
||||
"player"
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -170,6 +170,13 @@ let Player = {
|
||||
*/
|
||||
deferredResize: null,
|
||||
|
||||
/**
|
||||
* Stores the previous events for seek forward/backward to correctly
|
||||
* record the sequence if 3 clicks happen within a 1 second interval
|
||||
*/
|
||||
backwardSequence: [],
|
||||
forwardSequence: [],
|
||||
|
||||
/**
|
||||
* Initializes the player browser, and sets up the initial state.
|
||||
*
|
||||
@ -236,6 +243,9 @@ let Player = {
|
||||
this.scrubber.addEventListener("change", event => {
|
||||
this.handleScrubbingDone(event);
|
||||
});
|
||||
this.scrubber.addEventListener("pointerdown", event => {
|
||||
this.recordEvent("seek", {});
|
||||
});
|
||||
|
||||
for (let radio of document.querySelectorAll(
|
||||
'input[type=radio][name="cc-size"]'
|
||||
@ -590,11 +600,35 @@ let Player = {
|
||||
|
||||
case "seekBackward": {
|
||||
this.actor.sendAsyncMessage("PictureInPicture:SeekBackward");
|
||||
this.recordEvent("backward", {});
|
||||
|
||||
let secondPreviousEvent = this.backwardSequence.at(-2);
|
||||
if (
|
||||
secondPreviousEvent &&
|
||||
event.timeStamp - secondPreviousEvent < 2000
|
||||
) {
|
||||
this.recordEvent("backward_sequence", {});
|
||||
this.backwardSequence = [];
|
||||
} else {
|
||||
this.backwardSequence.push(event.timeStamp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case "seekForward": {
|
||||
this.actor.sendAsyncMessage("PictureInPicture:SeekForward");
|
||||
this.recordEvent("forward", {});
|
||||
|
||||
let secondPreviousEvent = this.forwardSequence.at(-2);
|
||||
if (
|
||||
secondPreviousEvent &&
|
||||
event.timeStamp - secondPreviousEvent < 2000
|
||||
) {
|
||||
this.recordEvent("forward_sequence", {});
|
||||
this.forwardSequence = [];
|
||||
} else {
|
||||
this.forwardSequence.push(event.timeStamp);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@ -611,6 +645,9 @@ let Player = {
|
||||
|
||||
case "fullscreen": {
|
||||
this.fullscreenModeToggle();
|
||||
this.recordEvent("fullscreen", {
|
||||
enter: (!this.isFullscreen).toString(),
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
@ -641,7 +678,7 @@ let Player = {
|
||||
this.actor.sendAsyncMessage("PictureInPicture:Pause", {
|
||||
reason: "pip-closed",
|
||||
});
|
||||
this.closePipWindow({ reason: "close-button" });
|
||||
this.closePipWindow({ reason: "closeButton" });
|
||||
},
|
||||
|
||||
fullscreenModeToggle() {
|
||||
|
@ -30,9 +30,9 @@ const FIRST_CONTEXT_MENU_EXPECTED_EVENTS = [
|
||||
[
|
||||
"pictureinpicture",
|
||||
"opened_method",
|
||||
"method",
|
||||
"contextMenu",
|
||||
null,
|
||||
{ method: "contextMenu", firstTimeToggle: "true" },
|
||||
{ firstTimeToggle: "true" },
|
||||
],
|
||||
];
|
||||
|
||||
@ -40,9 +40,9 @@ const SECOND_CONTEXT_MENU_EXPECTED_EVENTS = [
|
||||
[
|
||||
"pictureinpicture",
|
||||
"opened_method",
|
||||
"method",
|
||||
"contextMenu",
|
||||
null,
|
||||
{ method: "contextMenu", firstTimeToggle: "false" },
|
||||
{ firstTimeToggle: "false" },
|
||||
],
|
||||
];
|
||||
|
||||
@ -51,9 +51,9 @@ const FIRST_TOGGLE_EXPECTED_EVENTS = [
|
||||
[
|
||||
"pictureinpicture",
|
||||
"opened_method",
|
||||
"method",
|
||||
"toggle",
|
||||
null,
|
||||
{ method: "toggle", firstTimeToggle: "true" },
|
||||
{ firstTimeToggle: "true" },
|
||||
],
|
||||
];
|
||||
|
||||
@ -62,9 +62,9 @@ const SECOND_TOGGLE_EXPECTED_EVENTS = [
|
||||
[
|
||||
"pictureinpicture",
|
||||
"opened_method",
|
||||
"method",
|
||||
"toggle",
|
||||
null,
|
||||
{ method: "toggle", firstTimeToggle: "false" },
|
||||
{ firstTimeToggle: "false" },
|
||||
],
|
||||
];
|
||||
|
||||
@ -235,34 +235,20 @@ add_task(async function test_eventTelemetry() {
|
||||
// open with context menu for first time
|
||||
await openAndClosePipWithContextMenu(browser, videoID);
|
||||
|
||||
let events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).content;
|
||||
|
||||
info(`Length of events is ${events?.length}`);
|
||||
await TestUtils.waitForCondition(
|
||||
() => {
|
||||
events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).content;
|
||||
info(`Length of events is ${events?.length}`);
|
||||
return events && events.length >= 1;
|
||||
},
|
||||
"Waiting for one opened_method pictureinpicture telemetry event.",
|
||||
200,
|
||||
100
|
||||
let filter = {
|
||||
category: "pictureinpicture",
|
||||
method: "opened_method",
|
||||
object: "contextMenu",
|
||||
};
|
||||
await waitForTelemeryEvents(
|
||||
filter,
|
||||
FIRST_CONTEXT_MENU_EXPECTED_EVENTS.length,
|
||||
"content"
|
||||
);
|
||||
info(JSON.stringify(events));
|
||||
|
||||
TelemetryTestUtils.assertEvents(
|
||||
FIRST_CONTEXT_MENU_EXPECTED_EVENTS,
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "opened_method",
|
||||
object: "method",
|
||||
},
|
||||
filter,
|
||||
{ clear: true, process: "content" }
|
||||
);
|
||||
|
||||
@ -273,84 +259,54 @@ add_task(async function test_eventTelemetry() {
|
||||
|
||||
await openAndClosePipWithToggle(browser, videoID);
|
||||
|
||||
events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).content;
|
||||
|
||||
info(`Length of events is ${events?.length}`);
|
||||
await TestUtils.waitForCondition(
|
||||
() => {
|
||||
events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).content;
|
||||
info(`Length of events is ${events?.length}`);
|
||||
return events && events.length >= 2;
|
||||
},
|
||||
"Waiting for both pictureinpicture telemetry events.",
|
||||
200,
|
||||
100
|
||||
filter = {
|
||||
category: "pictureinpicture",
|
||||
};
|
||||
await waitForTelemeryEvents(
|
||||
filter,
|
||||
FIRST_TOGGLE_EXPECTED_EVENTS.length,
|
||||
"content"
|
||||
);
|
||||
info(JSON.stringify(events));
|
||||
|
||||
TelemetryTestUtils.assertEvents(
|
||||
FIRST_TOGGLE_EXPECTED_EVENTS,
|
||||
{ category: "pictureinpicture" },
|
||||
{ clear: true, process: "content" }
|
||||
);
|
||||
TelemetryTestUtils.assertEvents(FIRST_TOGGLE_EXPECTED_EVENTS, filter, {
|
||||
clear: true,
|
||||
process: "content",
|
||||
});
|
||||
|
||||
// open with toggle for not first time
|
||||
await openAndClosePipWithToggle(browser, videoID);
|
||||
|
||||
info(`Length of events is ${events?.length}`);
|
||||
await TestUtils.waitForCondition(
|
||||
() => {
|
||||
events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).content;
|
||||
info(`Length of events is ${events?.length}`);
|
||||
return events && events.length >= 2;
|
||||
},
|
||||
"Waiting for both pictureinpicture telemetry events.",
|
||||
200,
|
||||
100
|
||||
filter = {
|
||||
category: "pictureinpicture",
|
||||
};
|
||||
await waitForTelemeryEvents(
|
||||
filter,
|
||||
SECOND_TOGGLE_EXPECTED_EVENTS.length,
|
||||
"content"
|
||||
);
|
||||
info(JSON.stringify(events));
|
||||
|
||||
TelemetryTestUtils.assertEvents(
|
||||
SECOND_TOGGLE_EXPECTED_EVENTS,
|
||||
{ category: "pictureinpicture" },
|
||||
{ clear: true, process: "content" }
|
||||
);
|
||||
TelemetryTestUtils.assertEvents(SECOND_TOGGLE_EXPECTED_EVENTS, filter, {
|
||||
clear: true,
|
||||
process: "content",
|
||||
});
|
||||
|
||||
// open with context menu for not first time
|
||||
await openAndClosePipWithContextMenu(browser, videoID);
|
||||
|
||||
info(`Length of events is ${events?.length}`);
|
||||
await TestUtils.waitForCondition(
|
||||
() => {
|
||||
events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).content;
|
||||
info(`Length of events is ${events?.length}`);
|
||||
return events && events.length >= 1;
|
||||
},
|
||||
"Waiting for one opened_method pictureinpicture telemetry event.",
|
||||
200,
|
||||
100
|
||||
filter = {
|
||||
category: "pictureinpicture",
|
||||
method: "opened_method",
|
||||
object: "contextMenu",
|
||||
};
|
||||
await waitForTelemeryEvents(
|
||||
filter,
|
||||
SECOND_CONTEXT_MENU_EXPECTED_EVENTS.length,
|
||||
"content"
|
||||
);
|
||||
info(JSON.stringify(events));
|
||||
|
||||
TelemetryTestUtils.assertEvents(
|
||||
SECOND_CONTEXT_MENU_EXPECTED_EVENTS,
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "opened_method",
|
||||
object: "method",
|
||||
},
|
||||
filter,
|
||||
{ true: false, process: "content" }
|
||||
);
|
||||
}
|
||||
|
@ -3,6 +3,23 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
const REWIND_EVENTS = [
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "seek",
|
||||
object: "player",
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "seek",
|
||||
object: "player",
|
||||
},
|
||||
];
|
||||
|
||||
const TEST_PAGE_LONG = TEST_ROOT + "test-video-selection.html";
|
||||
|
||||
const IMPROVED_CONTROLS_ENABLED_PREF =
|
||||
@ -255,6 +272,20 @@ add_task(async function testVideoScrubber() {
|
||||
expectedVideoTime,
|
||||
"Video current time is 7.96..."
|
||||
);
|
||||
|
||||
let filter = {
|
||||
category: "pictureinpicture",
|
||||
method: "seek",
|
||||
object: "player",
|
||||
};
|
||||
await waitForTelemeryEvents(filter, REWIND_EVENTS.length, "parent");
|
||||
|
||||
TelemetryTestUtils.assertEvents(REWIND_EVENTS, filter, {
|
||||
clear: true,
|
||||
process: "parent",
|
||||
});
|
||||
|
||||
await ensureMessageAndClosePiP(browser, videoID, pipWin, false);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -3,6 +3,8 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const TEST_PAGE_LONG = TEST_ROOT + "test-video-selection.html";
|
||||
|
||||
const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
||||
);
|
||||
@ -28,22 +30,131 @@ const EXPECTED_EVENT_CREATE_WITH_TEXT_TRACKS = [
|
||||
];
|
||||
|
||||
const EXPECTED_EVENT_CLOSED_METHOD_CLOSE_BUTTON = [
|
||||
[
|
||||
"pictureinpicture",
|
||||
"closed_method",
|
||||
"method",
|
||||
null,
|
||||
{ reason: "close-button" },
|
||||
],
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "closed_method",
|
||||
object: "closeButton",
|
||||
},
|
||||
];
|
||||
|
||||
const videoID = "with-controls";
|
||||
|
||||
const EXPECTED_EVENT_CLOSED_METHOD_UNPIP = [
|
||||
["pictureinpicture", "closed_method", "method", null, { reason: "unpip" }],
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "closed_method",
|
||||
object: "unpip",
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async () => {
|
||||
const FULLSCREEN_EVENTS = [
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "fullscreen",
|
||||
object: "player",
|
||||
extraKey: { enter: "true" },
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "fullscreen",
|
||||
object: "player",
|
||||
extraKey: { enter: "true" },
|
||||
},
|
||||
];
|
||||
|
||||
const FORWARD_EVENTS = [
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "forward",
|
||||
object: "player",
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "forward",
|
||||
object: "player",
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "forward",
|
||||
object: "player",
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "forward",
|
||||
object: "player",
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "forward",
|
||||
object: "player",
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "forward",
|
||||
object: "player",
|
||||
},
|
||||
];
|
||||
|
||||
const FORWARD_SEQUENCE_EVENTS = [
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "forward_sequence",
|
||||
object: "player",
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "forward_sequence",
|
||||
object: "player",
|
||||
},
|
||||
];
|
||||
|
||||
const BACKWARD_EVENTS = [
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "backward",
|
||||
object: "player",
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "backward",
|
||||
object: "player",
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "backward",
|
||||
object: "player",
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "backward",
|
||||
object: "player",
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "backward",
|
||||
object: "player",
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "backward",
|
||||
object: "player",
|
||||
},
|
||||
];
|
||||
|
||||
const BACKWARD_SEQUENCE_EVENTS = [
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "backward_sequence",
|
||||
object: "player",
|
||||
},
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "backward_sequence",
|
||||
object: "player",
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function testCreateAndCloseButtonTelemetry() {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
gBrowser,
|
||||
@ -57,64 +168,41 @@ add_task(async () => {
|
||||
let pipWin = await triggerPictureInPicture(browser, videoID);
|
||||
ok(pipWin, "Got Picture-in-Picture window.");
|
||||
|
||||
let events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).parent;
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => {
|
||||
events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).parent;
|
||||
return events && events.length >= 1;
|
||||
},
|
||||
"Waiting for one create pictureinpicture telemetry event.",
|
||||
200,
|
||||
100
|
||||
);
|
||||
|
||||
TelemetryTestUtils.assertEvents(
|
||||
EXPECTED_EVENT_CREATE,
|
||||
{
|
||||
let filter = {
|
||||
category: "pictureinpicture",
|
||||
method: "create",
|
||||
object: "player",
|
||||
},
|
||||
{ clear: true, process: "parent" }
|
||||
};
|
||||
await waitForTelemeryEvents(
|
||||
filter,
|
||||
EXPECTED_EVENT_CREATE.length,
|
||||
"parent"
|
||||
);
|
||||
|
||||
TelemetryTestUtils.assertEvents(EXPECTED_EVENT_CREATE, filter, {
|
||||
clear: true,
|
||||
process: "parent",
|
||||
});
|
||||
|
||||
let pipClosed = BrowserTestUtils.domWindowClosed(pipWin);
|
||||
let closeButton = pipWin.document.getElementById("close");
|
||||
EventUtils.synthesizeMouseAtCenter(closeButton, {}, pipWin);
|
||||
await pipClosed;
|
||||
|
||||
events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).parent;
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => {
|
||||
events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).parent;
|
||||
return events && events.length >= 1;
|
||||
},
|
||||
"Waiting for one closed_method pictureinpicture telemetry event.",
|
||||
200,
|
||||
100
|
||||
filter = {
|
||||
category: "pictureinpicture",
|
||||
method: "closed_method",
|
||||
object: "closeButton",
|
||||
};
|
||||
await waitForTelemeryEvents(
|
||||
filter,
|
||||
EXPECTED_EVENT_CLOSED_METHOD_CLOSE_BUTTON.length,
|
||||
"parent"
|
||||
);
|
||||
|
||||
TelemetryTestUtils.assertEvents(
|
||||
EXPECTED_EVENT_CLOSED_METHOD_CLOSE_BUTTON,
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "closed_method",
|
||||
object: "method",
|
||||
},
|
||||
filter,
|
||||
{ clear: true, process: "parent" }
|
||||
);
|
||||
|
||||
@ -127,7 +215,7 @@ add_task(async () => {
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function() {
|
||||
add_task(async function textTextTracksAndUnpipTelemetry() {
|
||||
await SpecialPowers.pushPrefEnv({
|
||||
set: [
|
||||
[
|
||||
@ -150,31 +238,20 @@ add_task(async function() {
|
||||
let pipWin = await triggerPictureInPicture(browser, videoID);
|
||||
ok(pipWin, "Got Picture-in-Picture window.");
|
||||
|
||||
let events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).parent;
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => {
|
||||
events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).parent;
|
||||
return events && events.length >= 1;
|
||||
},
|
||||
"Waiting for one create pictureinpicture telemetry event.",
|
||||
200,
|
||||
100
|
||||
let filter = {
|
||||
category: "pictureinpicture",
|
||||
method: "create",
|
||||
object: "player",
|
||||
};
|
||||
await waitForTelemeryEvents(
|
||||
filter,
|
||||
EXPECTED_EVENT_CREATE_WITH_TEXT_TRACKS.length,
|
||||
"parent"
|
||||
);
|
||||
|
||||
TelemetryTestUtils.assertEvents(
|
||||
EXPECTED_EVENT_CREATE_WITH_TEXT_TRACKS,
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "create",
|
||||
object: "player",
|
||||
},
|
||||
filter,
|
||||
{ clear: true, process: "parent" }
|
||||
);
|
||||
|
||||
@ -183,33 +260,168 @@ add_task(async function() {
|
||||
EventUtils.synthesizeMouseAtCenter(unpipButton, {}, pipWin);
|
||||
await pipClosed;
|
||||
|
||||
events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).parent;
|
||||
|
||||
await TestUtils.waitForCondition(
|
||||
() => {
|
||||
events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
).parent;
|
||||
return events && events.length >= 1;
|
||||
},
|
||||
"Waiting for one closed_method pictureinpicture telemetry event.",
|
||||
200,
|
||||
100
|
||||
filter = {
|
||||
category: "pictureinpicture",
|
||||
method: "closed_method",
|
||||
object: "unpip",
|
||||
};
|
||||
await waitForTelemeryEvents(
|
||||
filter,
|
||||
EXPECTED_EVENT_CLOSED_METHOD_UNPIP.length,
|
||||
"parent"
|
||||
);
|
||||
|
||||
TelemetryTestUtils.assertEvents(
|
||||
EXPECTED_EVENT_CLOSED_METHOD_UNPIP,
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "closed_method",
|
||||
object: "method",
|
||||
},
|
||||
filter,
|
||||
{ clear: true, process: "parent" }
|
||||
);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_fullscreen_events() {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
url: TEST_PAGE,
|
||||
gBrowser,
|
||||
},
|
||||
async browser => {
|
||||
Services.telemetry.clearEvents();
|
||||
|
||||
await ensureVideosReady(browser);
|
||||
|
||||
let pipWin = await triggerPictureInPicture(browser, videoID);
|
||||
ok(pipWin, "Got Picture-in-Picture window.");
|
||||
|
||||
let fullscreenBtn = pipWin.document.getElementById("fullscreen");
|
||||
|
||||
await promiseFullscreenEntered(pipWin, () => {
|
||||
fullscreenBtn.click();
|
||||
});
|
||||
|
||||
await promiseFullscreenExited(pipWin, () => {
|
||||
fullscreenBtn.click();
|
||||
});
|
||||
|
||||
let filter = {
|
||||
category: "pictureinpicture",
|
||||
method: "fullscreen",
|
||||
object: "player",
|
||||
};
|
||||
await waitForTelemeryEvents(filter, FULLSCREEN_EVENTS.length, "parent");
|
||||
|
||||
TelemetryTestUtils.assertEvents(FULLSCREEN_EVENTS, filter, {
|
||||
clear: true,
|
||||
process: "parent",
|
||||
});
|
||||
|
||||
await ensureMessageAndClosePiP(browser, videoID, pipWin, false);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
add_task(async function test_seek_forward_and_backward_events() {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
url: TEST_PAGE_LONG,
|
||||
gBrowser,
|
||||
},
|
||||
async browser => {
|
||||
let waitForVideoEvent = eventType => {
|
||||
return BrowserTestUtils.waitForContentEvent(browser, eventType, true);
|
||||
};
|
||||
|
||||
const videoID = "long";
|
||||
|
||||
Services.telemetry.clearEvents();
|
||||
|
||||
await ensureVideosReady(browser);
|
||||
|
||||
let pipWin = await triggerPictureInPicture(browser, videoID);
|
||||
ok(pipWin, "Got Picture-in-Picture window.");
|
||||
|
||||
let seekBackwardBtn = pipWin.document.getElementById("seekBackward");
|
||||
let seekForwardBtn = pipWin.document.getElementById("seekForward");
|
||||
|
||||
// Test seek forward
|
||||
let seekedForwardPromise = waitForVideoEvent("seeked");
|
||||
seekForwardBtn.click();
|
||||
seekForwardBtn.click();
|
||||
seekForwardBtn.click();
|
||||
seekForwardBtn.click();
|
||||
seekForwardBtn.click();
|
||||
seekForwardBtn.click();
|
||||
ok(await seekedForwardPromise, "The Forward button triggers");
|
||||
|
||||
let filter = {
|
||||
category: "pictureinpicture",
|
||||
method: "forward",
|
||||
object: "player",
|
||||
};
|
||||
await waitForTelemeryEvents(filter, FORWARD_EVENTS.length, "parent");
|
||||
|
||||
TelemetryTestUtils.assertEvents(FORWARD_EVENTS, filter, {
|
||||
clear: false,
|
||||
process: "parent",
|
||||
});
|
||||
|
||||
filter = {
|
||||
category: "pictureinpicture",
|
||||
method: "forward_sequence",
|
||||
object: "player",
|
||||
};
|
||||
await waitForTelemeryEvents(
|
||||
filter,
|
||||
FORWARD_SEQUENCE_EVENTS.length,
|
||||
"parent"
|
||||
);
|
||||
|
||||
TelemetryTestUtils.assertEvents(FORWARD_SEQUENCE_EVENTS, filter, {
|
||||
clear: true,
|
||||
process: "parent",
|
||||
});
|
||||
|
||||
Services.telemetry.clearEvents();
|
||||
|
||||
let seekedBackwardPromise = waitForVideoEvent("seeked");
|
||||
seekBackwardBtn.click();
|
||||
seekBackwardBtn.click();
|
||||
seekBackwardBtn.click();
|
||||
seekBackwardBtn.click();
|
||||
seekBackwardBtn.click();
|
||||
seekBackwardBtn.click();
|
||||
ok(await seekedBackwardPromise, "The Forward button triggers");
|
||||
|
||||
filter = {
|
||||
category: "pictureinpicture",
|
||||
method: "backward",
|
||||
object: "player",
|
||||
};
|
||||
await waitForTelemeryEvents(filter, BACKWARD_EVENTS.length, "parent");
|
||||
|
||||
TelemetryTestUtils.assertEvents(BACKWARD_EVENTS, filter, {
|
||||
clear: false,
|
||||
process: "parent",
|
||||
});
|
||||
|
||||
filter = {
|
||||
category: "pictureinpicture",
|
||||
method: "backward_sequence",
|
||||
object: "player",
|
||||
};
|
||||
await waitForTelemeryEvents(
|
||||
filter,
|
||||
BACKWARD_SEQUENCE_EVENTS.length,
|
||||
"parent"
|
||||
);
|
||||
|
||||
TelemetryTestUtils.assertEvents(BACKWARD_SEQUENCE_EVENTS, filter, {
|
||||
clear: true,
|
||||
process: "parent",
|
||||
});
|
||||
|
||||
await ensureMessageAndClosePiP(browser, videoID, pipWin, false);
|
||||
}
|
||||
);
|
||||
});
|
||||
|
@ -3,6 +3,18 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
const { TelemetryTestUtils } = ChromeUtils.importESModule(
|
||||
"resource://testing-common/TelemetryTestUtils.sys.mjs"
|
||||
);
|
||||
|
||||
const PIP_URLBAR_EVENTS = [
|
||||
{
|
||||
category: "pictureinpicture",
|
||||
method: "opened_method",
|
||||
object: "urlBar",
|
||||
},
|
||||
];
|
||||
|
||||
add_task(async function test_urlbar_toggle_multiple_contexts() {
|
||||
await BrowserTestUtils.withNewTab(
|
||||
{
|
||||
@ -10,6 +22,7 @@ add_task(async function test_urlbar_toggle_multiple_contexts() {
|
||||
gBrowser,
|
||||
},
|
||||
async browser => {
|
||||
Services.telemetry.clearEvents();
|
||||
await ensureVideosReady(browser);
|
||||
await ensureVideosReady(browser.browsingContext.children[0]);
|
||||
|
||||
@ -59,6 +72,18 @@ add_task(async function test_urlbar_toggle_multiple_contexts() {
|
||||
"video"
|
||||
);
|
||||
|
||||
let filter = {
|
||||
category: "pictureinpicture",
|
||||
method: "opened_method",
|
||||
object: "urlBar",
|
||||
};
|
||||
await waitForTelemeryEvents(filter, PIP_URLBAR_EVENTS.length, "content");
|
||||
|
||||
TelemetryTestUtils.assertEvents(PIP_URLBAR_EVENTS, filter, {
|
||||
clear: true,
|
||||
process: "content",
|
||||
});
|
||||
|
||||
let domWindowClosed = BrowserTestUtils.domWindowClosed(win);
|
||||
pipUrlbarToggle.click();
|
||||
await domWindowClosed;
|
||||
|
@ -1033,3 +1033,68 @@ function overrideSavedPosition(left, top, width, height) {
|
||||
xulStore.setValue(PLAYER_URI, "picture-in-picture", "width", width);
|
||||
xulStore.setValue(PLAYER_URI, "picture-in-picture", "height", height);
|
||||
}
|
||||
|
||||
/**
|
||||
* Function used to filter events when waiting for the correct number
|
||||
* telemetry events.
|
||||
* @param {String} expected The expected string or undefined
|
||||
* @param {String} actual The actual string
|
||||
* @returns true if the expected is undefined or if expected matches actual
|
||||
*/
|
||||
function matches(expected, actual) {
|
||||
if (expected === undefined) {
|
||||
return true;
|
||||
}
|
||||
return expected === actual;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that waits for the expected number of events aftering filtering.
|
||||
* @param {Object} filter An object containing optional filters
|
||||
* {
|
||||
* category: (optional) The category of the event. Ex. "pictureinpicture"
|
||||
* method: (optional) The method of the event. Ex. "create"
|
||||
* object: (optional) The object of the event. Ex. "player"
|
||||
* }
|
||||
* @param {Number} length The number of events to wait for
|
||||
* @param {String} process Should be "content" or "parent" depending on the event
|
||||
*/
|
||||
async function waitForTelemeryEvents(filter, length, process) {
|
||||
let {
|
||||
category: filterCategory,
|
||||
method: filterMethod,
|
||||
object: filterObject,
|
||||
} = filter;
|
||||
|
||||
let events = [];
|
||||
await TestUtils.waitForCondition(
|
||||
() => {
|
||||
events = Services.telemetry.snapshotEvents(
|
||||
Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
|
||||
false
|
||||
)[process];
|
||||
if (!events) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let filtered = events
|
||||
.map(([, /* timestamp */ category, method, object, value, extra]) => {
|
||||
// We don't care about the `timestamp` value.
|
||||
// Tests that examine that value should use `snapshotEvents` directly.
|
||||
return [category, method, object, value, extra];
|
||||
})
|
||||
.filter(([category, method, object]) => {
|
||||
return (
|
||||
matches(filterCategory, category) &&
|
||||
matches(filterMethod, method) &&
|
||||
matches(filterObject, object)
|
||||
);
|
||||
});
|
||||
info(JSON.stringify(filtered, null, 2));
|
||||
return filtered && filtered.length >= length;
|
||||
},
|
||||
"Waiting for one create pictureinpicture telemetry event.",
|
||||
200,
|
||||
100
|
||||
);
|
||||
}
|
||||
|
@ -2891,11 +2891,10 @@ pictureinpicture:
|
||||
expiry_version: "never"
|
||||
release_channel_collection: opt-out
|
||||
opened_method:
|
||||
objects: ["method"]
|
||||
objects: ["toggle", "contextMenu", "urlBar"]
|
||||
description: >
|
||||
Records the method for opening the Picture-in-Picture window.
|
||||
extra_keys:
|
||||
method: Will be "toggle" or "contextMenu"
|
||||
firstTimeToggle: If the user has used Picture-in-Picture before
|
||||
notification_emails:
|
||||
- mconley@mozilla.com
|
||||
@ -2907,11 +2906,9 @@ pictureinpicture:
|
||||
expiry_version: "never"
|
||||
release_channel_collection: opt-out
|
||||
closed_method:
|
||||
objects: ["method"]
|
||||
objects: ["closeButton", "unpip", "pagehide", "fullscreen", "setupFailure", "closePlayerShortcut", "contextMenu", "videoElRemove", "videoElEmptied", "urlBar"]
|
||||
description: >
|
||||
Records the method for closing the Picture-in-Picture window.
|
||||
extra_keys:
|
||||
reason: Will be "close-button", "unpip", "pagehide", "fullscreen", "setup-failure", "close-player-shortcut", "context-menu", "video-el-remove", or "video-el-emptied"
|
||||
notification_emails:
|
||||
- mconley@mozilla.com
|
||||
- mtigley@mozilla.com
|
||||
@ -2941,10 +2938,102 @@ pictureinpicture:
|
||||
- 1772546
|
||||
expiry_version: "never"
|
||||
release_channel_collection: opt-out
|
||||
backward:
|
||||
objects: ["player"]
|
||||
description: >
|
||||
Recorded when the user clicks the seek backward button
|
||||
notification_emails:
|
||||
- mconley@mozilla.com
|
||||
- mhowell@mozilla.com
|
||||
products:
|
||||
- "firefox"
|
||||
record_in_processes:
|
||||
- "main"
|
||||
bug_numbers:
|
||||
- 1822395
|
||||
expiry_version: "116"
|
||||
release_channel_collection: opt-out
|
||||
backward_sequence:
|
||||
objects: ["player"]
|
||||
description: >
|
||||
Recorded when the user clicks the seek backward button 3 times in a 2 second interval
|
||||
notification_emails:
|
||||
- mconley@mozilla.com
|
||||
- mhowell@mozilla.com
|
||||
products:
|
||||
- "firefox"
|
||||
record_in_processes:
|
||||
- "main"
|
||||
bug_numbers:
|
||||
- 1822395
|
||||
expiry_version: "116"
|
||||
release_channel_collection: opt-out
|
||||
forward:
|
||||
objects: ["player"]
|
||||
description: >
|
||||
Recorded when the user clicks the seek forward button
|
||||
notification_emails:
|
||||
- mconley@mozilla.com
|
||||
- mhowell@mozilla.com
|
||||
products:
|
||||
- "firefox"
|
||||
record_in_processes:
|
||||
- "main"
|
||||
bug_numbers:
|
||||
- 1822395
|
||||
expiry_version: "116"
|
||||
release_channel_collection: opt-out
|
||||
forward_sequence:
|
||||
objects: ["player"]
|
||||
description: >
|
||||
Recorded when the user clicks the seek forward button 3 times in a 2 second interval
|
||||
notification_emails:
|
||||
- mconley@mozilla.com
|
||||
- mhowell@mozilla.com
|
||||
products:
|
||||
- "firefox"
|
||||
record_in_processes:
|
||||
- "main"
|
||||
bug_numbers:
|
||||
- 1822395
|
||||
expiry_version: "116"
|
||||
release_channel_collection: opt-out
|
||||
seek:
|
||||
objects: ["player"]
|
||||
description: >
|
||||
Recorded when the user interacts with the seek bar
|
||||
notification_emails:
|
||||
- mconley@mozilla.com
|
||||
- mhowell@mozilla.com
|
||||
products:
|
||||
- "firefox"
|
||||
record_in_processes:
|
||||
- "main"
|
||||
bug_numbers:
|
||||
- 1822395
|
||||
expiry_version: "116"
|
||||
release_channel_collection: opt-out
|
||||
fullscreen:
|
||||
objects: ["player"]
|
||||
extra_keys:
|
||||
enter: true if entering fullscreen, false if exiting fullscreen
|
||||
description: >
|
||||
Recorded when the user clicks the fullscreen button
|
||||
notification_emails:
|
||||
- mconley@mozilla.com
|
||||
- mhowell@mozilla.com
|
||||
products:
|
||||
- "firefox"
|
||||
record_in_processes:
|
||||
- "main"
|
||||
bug_numbers:
|
||||
- 1822395
|
||||
expiry_version: "never"
|
||||
release_channel_collection: opt-out
|
||||
|
||||
pictureinpicture.settings:
|
||||
enable:
|
||||
objects: ["player"]
|
||||
objects: ["settings"]
|
||||
description: Recorded when Picture-in-Picture is enabled.
|
||||
notification_emails:
|
||||
- mconley@mozilla.com
|
||||
@ -2957,8 +3046,10 @@ pictureinpicture.settings:
|
||||
expiry_version: "never"
|
||||
release_channel_collection: opt-out
|
||||
disable:
|
||||
objects: ["player"]
|
||||
description: Recorded when Picture-in-Picture is disabled.
|
||||
objects: ["player", "settings"]
|
||||
description: >
|
||||
Recorded with "settings" object when Picture-in-Picture is disabled via settings
|
||||
Recorded with "player" object when Picture-in-Picture is disabled via PiP context menu
|
||||
notification_emails:
|
||||
- mconley@mozilla.com
|
||||
products:
|
||||
|
Loading…
Reference in New Issue
Block a user