Bug 1471485 - Hide autoplay permission doorhanger if user plays video. r=johannh

If we're showing a permission UI prompt for "autoplay-media", the user can
still actually play media without interacting with the doorhanger; if they
click a "play" button in the document, they'll "gesture activate" the document
and unblock autoplay and be able to start playback.

It doesn't make sense to keep showing the permission doorhanger to approve
autoplay when the page is already playing, as playback has already started, and
if they clicked on "block" then the site would receive a promise reject on the
promise returned on the first call to HTMLMediaElement.play() for which we were
showing the permission prompt for, even though the media is actually playing.
This will likely confuse JS video players.

So we should hide the permission prompt when playback in the page starts.

MozReview-Commit-ID: 1XU47AfT6vf

--HG--
extra : rebase_source : 3d5c164527e26ba8f58f0baac3474d5b2d7fb587
This commit is contained in:
Chris Pearce 2018-07-03 11:17:16 +12:00
parent 56f41b91a9
commit 5d98d9108c
2 changed files with 98 additions and 4 deletions

View File

@ -245,6 +245,12 @@ var PermissionPromptPrototype = {
*/
onBeforeShow() {},
/**
* If the prompt was be shown to the user, this callback will
* be called just after its been hidden.
*/
onAfterShow() {},
/**
* Will determine if a prompt should be shown to the user, and if so,
* will show it.
@ -358,10 +364,19 @@ var PermissionPromptPrototype = {
// Permission prompts are always persistent; the close button is controlled by a pref.
options.persistent = true;
options.hideClose = !Services.prefs.getBoolPref("privacy.permissionPrompts.showCloseButton");
// When the docshell of the browser is aboout to be swapped to another one,
// the "swapping" event is called. Returning true causes the notification
// to be moved to the new browser.
options.eventCallback = topic => topic == "swapping";
options.eventCallback = (topic) => {
// When the docshell of the browser is aboout to be swapped to another one,
// the "swapping" event is called. Returning true causes the notification
// to be moved to the new browser.
if (topic == "swapping") {
return true;
}
// The prompt has been removed, notify the PermissionUI.
if (topic == "removed") {
this.onAfterShow();
}
return false;
};
this.onBeforeShow();
chromeWin.PopupNotifications.show(this.browser,
@ -817,7 +832,27 @@ AutoplayPermissionPrompt.prototype = {
}];
},
onAfterShow() {
// Remove the event listener to prevent any leaks.
this.browser.removeEventListener(
"DOMAudioPlaybackStarted", this.handlePlaybackStart);
},
onBeforeShow() {
// Hide the prompt if the tab starts playing media.
this.handlePlaybackStart = () => {
let chromeWin = this.browser.ownerGlobal;
if (!chromeWin.PopupNotifications) {
return;
}
let notification = chromeWin.PopupNotifications.getNotification(
this.notificationID, this.browser);
if (notification) {
chromeWin.PopupNotifications.remove(notification);
}
};
this.browser.addEventListener(
"DOMAudioPlaybackStarted", this.handlePlaybackStart);
},
};

View File

@ -201,3 +201,62 @@ add_task(async () => {
mode: "call play",
});
});
// Test that if playback starts while the permission prompt is shown,
// that the prompt is hidden.
add_task(async () => {
await BrowserTestUtils.withNewTab({
gBrowser,
url: VIDEO_PAGE,
}, async (browser) => {
info("- Started test prompt hides upon play -");
let promptShowing = () =>
PopupNotifications.getNotification("autoplay-media", browser);
// Set this site to ask permission to autoplay.
SitePermissions.set(browser.currentURI, "autoplay-media", SitePermissions.UNKNOWN);
ok(!promptShowing(), "Should not be showing permission prompt");
let popupshown = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
await loadAutoplayVideo(browser, { mode: "call play" });
info("Awaiting popupshown");
await popupshown;
ok(promptShowing(), "Should now be showing permission prompt");
// Check that the video didn't start playing.
await ContentTask.spawn(browser, null,
async () => {
let video = content.document.getElementById("v1");
ok(video.paused && !video.didPlay, "Video should not be playing");
});
let popuphidden = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden");
await ContentTask.spawn(browser, null,
async () => {
// Gesture activate the document, i.e. simulate a click in the document,
// to unblock autoplay,
content.document.notifyUserGestureActivation();
let video = content.document.getElementById("v1");
// Gesture activating in itself should not cause the previous pending
// play to proceed.
ok(video.paused && !video.didPlay, "Video should not have played yet");
// But trying to play again now that we're gesture activated will work...
let played = await video.play().then(() => true, () => false);
ok(played, "Should have played as now gesture activated");
// And because we started playing, the previous promise returned in the
// first call to play() above should also resolve too.
await video.didPlayPromise;
ok(video.didPlay, "Existing promise should resolve when media starts playing");
});
info("Awaiting popuphidden");
await popuphidden;
ok(!promptShowing(), "Permission prompt should have hidden when media started playing");
// Reset permission.
SitePermissions.remove(browser.currentURI, "autoplay-media");
info("- Finished test prompt hides upon play -");
});
});