Skip to content

Commit

Permalink
Bug 1775328 - do not launch PiP if video controls text track list men…
Browse files Browse the repository at this point in the history
…u intersects the PiP toggle visually. r=cmkm,mhowell

Differential Revision: https://phabricator.services.mozilla.com/D161227
  • Loading branch information
kpatenio committed Nov 17, 2022
1 parent 24751e2 commit 0f8ac0d
Show file tree
Hide file tree
Showing 3 changed files with 179 additions and 12 deletions.
37 changes: 25 additions & 12 deletions toolkit/actors/PictureInPictureChild.sys.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -724,8 +724,13 @@ export class PictureInPictureToggleChild extends JSWindowActorChild {
}

let state = this.docState;
let toggle = this.getToggleElement(shadowRoot);

if (!this.isMouseOverToggle(toggle, event)) {
return;
}

let overVideo = (() => {
let canStartPictureInPicture = (() => {
let { clientX, clientY } = event;
let winUtils = this.contentWindow.windowUtils;
// We use winUtils.nodesFromRect instead of document.elementsFromPoint,
Expand All @@ -748,27 +753,35 @@ export class PictureInPictureToggleChild extends JSWindowActorChild {
state.toggleVisibilityThreshold
);

// Even if pointing down directly over the toggle, we cannot guarantee that
// we can start Picture-in-Picture, due to the toggle's visibilityThreshold.
// Only start Picture-in-Picture if we know that we're hovering over the video.
let overVideo = false;
for (let element of elements) {
if (element == video || element.containingShadowRoot == shadowRoot) {
return true;
let isInShadowRoot = element.containingShadowRoot == shadowRoot;

if (element == video || isInShadowRoot) {
overVideo = true;
}
// Do not launch Picture-in-Picture if video controls interfere
// with the toggle.
if (isInShadowRoot && element.parentElement?.id == "controlBar") {
return false;
}
}

return false;
return overVideo;
})();

if (!overVideo) {
if (!canStartPictureInPicture) {
return;
}

let toggle = this.getToggleElement(shadowRoot);
if (this.isMouseOverToggle(toggle, event)) {
state.isClickingToggle = true;
state.clickedElement = Cu.getWeakReference(event.originalTarget);
event.stopImmediatePropagation();
state.isClickingToggle = true;
state.clickedElement = Cu.getWeakReference(event.originalTarget);
event.stopImmediatePropagation();

this.startPictureInPicture(event, video, toggle);
}
this.startPictureInPicture(event, video, toggle);
}

startPictureInPicture(event, video, toggle) {
Expand Down
1 change: 1 addition & 0 deletions toolkit/components/pictureinpicture/tests/browser.ini
Original file line number Diff line number Diff line change
Expand Up @@ -135,5 +135,6 @@ skip-if =
[browser_toggleTransparentOverlay-2.js]
skip-if =
os == 'linux' && bits == 64 && os_version == '18.04' # Bug 1546930
[browser_toggle_videocontrols.js]
[browser_videoEmptied.js]
[browser_videoSelection.js]
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

/**
* Tests that the Picture-in-Picture does not start when clicking the toggle
* over interfering video controls settings, such as the subtitles/closed captions menu.
*/
add_task(async () => {
await SpecialPowers.pushPrefEnv({
set: [
[
"media.videocontrols.picture-in-picture.display-text-tracks.enabled",
true,
],
["media.videocontrols.picture-in-picture.video-toggle.enabled", true],
["media.videocontrols.picture-in-picture.video-toggle.position", "right"],
],
});
let videoID = "with-controls";

await BrowserTestUtils.withNewTab(
{
url: TEST_PAGE_WITH_WEBVTT,
gBrowser,
},
async browser => {
await prepareVideosAndWebVTTTracks(browser, videoID, -1);
await prepareForToggleClick(browser, videoID);

let toggleStyles = DEFAULT_TOGGLE_STYLES;
let stage = "hoverVideo";
let toggleStylesForStage = toggleStyles.stages[stage];
let toggleClientRect = await getToggleClientRect(browser, videoID);

let args = {
videoID,
toggleClientRect,
toggleStylesForStage,
};

await SpecialPowers.spawn(browser, [args], async args => {
// waitForToggleOpacity is based on toggleOpacityReachesThreshold.
// Waits for toggle to reach target opacity.
async function waitForToggleOpacity(shadowRoot, toggleStylesForStage) {
for (let hiddenElement of toggleStylesForStage.hidden) {
let el = shadowRoot.querySelector(hiddenElement);
ok(
ContentTaskUtils.is_hidden(el),
`Expected ${hiddenElement} to be hidden.`
);
}

for (let opacityElement in toggleStylesForStage.opacities) {
let opacityThreshold =
toggleStylesForStage.opacities[opacityElement];
let el = shadowRoot.querySelector(opacityElement);

await ContentTaskUtils.waitForCondition(
() => {
let opacity = parseFloat(
this.content.getComputedStyle(el).opacity
);
return opacity >= opacityThreshold;
},
`Toggle element ${opacityElement} should have eventually reached ` +
`target opacity ${opacityThreshold}`,
100,
100
);

ok(true, "Toggle reached target opacity.");
}
}
let { videoID, toggleClientRect, toggleStylesForStage } = args;
let video = this.content.document.getElementById(videoID);
let tracks = video.textTracks;
let shadowRoot = video.openOrClosedShadowRoot;
let closedCaptionButton = shadowRoot.querySelector(
"#closedCaptionButton"
);

info("Opening text track list from cc button");
EventUtils.synthesizeMouseAtCenter(
closedCaptionButton,
{},
this.content.window
);

let textTrackList = shadowRoot.querySelector("#textTrackList");
ok(textTrackList.children.length);

info("Hovering over video to show toggle");
await EventUtils.synthesizeMouseAtCenter(
video,
{ type: "mousemove" },
this.content.window
);
await EventUtils.synthesizeMouseAtCenter(
video,
{ type: "mouseover" },
this.content.window
);

let toggleCenterX = toggleClientRect.left + toggleClientRect.width / 2;
let toggleCenterY = toggleClientRect.top + toggleClientRect.height / 2;

info("Selecting a track over the toggle");
EventUtils.synthesizeMouseAtCenter(
closedCaptionButton,
{ type: "mouseover" },
this.content.window
);

// We want to wait for the toggle to reach opacity so that we can select it.
info("Waiting for toggle to become fully visible");
await waitForToggleOpacity(shadowRoot, toggleStylesForStage);

EventUtils.synthesizeMouseAtPoint(
toggleCenterX,
toggleCenterY,
{},
this.content.window
);

let tracksArray = Array.from(tracks);
let isTrackEnabled = tracksArray.find(track => track.mode == "showing");

ok(isTrackEnabled, "A track should be enabled");
});

try {
info("Verifying that no Picture-in-Picture window is open.");
// Since we expect this condition to fail, wait for only ~1 second.
await BrowserTestUtils.waitForCondition(
() => Services.wm.getEnumerator(WINDOW_TYPE).hasMoreElements(),
"Found a Picture-in-Picture window",
100,
10
);
for (let win of Services.wm.getEnumerator(WINDOW_TYPE)) {
if (!win.closed) {
ok(false, "Found a Picture-in-Picture window unexpectedly.");
return;
}
}
} catch (e) {
ok(true, "No Picture-in-Picture window found");
}
}
);
});

0 comments on commit 0f8ac0d

Please sign in to comment.