Bug 1463919 - Tests for prompting for permission to autoplay. r=jya,mconley
authorChris Pearce <cpearce@mozilla.com>
Mon, 25 Jun 2018 15:35:33 +1200
changeset 479798 e780c6b275a2b26c3c12aff9f59893659f90b664
parent 479797 11017d3044e27c73c1923426ece0a53793020db4
child 479799 e242648cae8c3b1b89d44183a31230a9690584a5
push id9719
push userffxbld-merge
push dateFri, 24 Aug 2018 17:49:46 +0000
treeherdermozilla-beta@719ec98fba77 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjya, mconley
bugs1463919
milestone63.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1463919 - Tests for prompting for permission to autoplay. r=jya,mconley Test that a video which tries to autoplay via either a play() call or via an autoplay attribute: * Plays when it has a pre-existing "allow" autoplay-media permission. * Is blocked when it has a pre-existing "block" autoplay-media permission. * Plays when it doesn't have a pre-existing autoplay-media permission and "allow" is pressed on the door hanger. * Is blocked when it doesn't have a pre-existing autoplay-media permission and "block" is pressed on the door hanger. MozReview-Commit-ID: CpftV6RQbtU
dom/html/HTMLMediaElement.cpp
modules/libpref/init/StaticPrefList.h
toolkit/content/tests/browser/browser.ini
toolkit/content/tests/browser/browser_autoplay_policy_request_permission.js
toolkit/content/tests/browser/file_empty.html
--- a/dom/html/HTMLMediaElement.cpp
+++ b/dom/html/HTMLMediaElement.cpp
@@ -4051,30 +4051,36 @@ HTMLMediaElement::Play(ErrorResult& aRv)
     MaybeDoLoad();
     mPendingPlayPromises.AppendElement(promise);
     return promise.forget();
   }
 
   if (AudioChannelAgentBlockedPlay()) {
     LOG(LogLevel::Debug, ("%p play blocked by AudioChannelAgent.", this));
     promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
+    if (StaticPrefs::MediaBlockEventEnabled()) {
+      DispatchAsyncEvent(NS_LITERAL_STRING("blocked"));
+    }
     return promise.forget();
   }
 
   const bool handlingUserInput = EventStateManager::IsHandlingUserInput();
   switch (AutoplayPolicy::IsAllowedToPlay(*this)) {
     case Authorization::Allowed: {
       mPendingPlayPromises.AppendElement(promise);
       PlayInternal(handlingUserInput);
       UpdateCustomPolicyAfterPlayed();
       break;
     }
     case Authorization::Blocked: {
       LOG(LogLevel::Debug, ("%p play not blocked.", this));
       promise->MaybeReject(NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR);
+      if (StaticPrefs::MediaBlockEventEnabled()) {
+        DispatchAsyncEvent(NS_LITERAL_STRING("blocked"));
+      }
       break;
     }
     case Authorization::Prompt: {
       // Prompt the user for permission to play.
       mPendingPlayPromises.AppendElement(promise);
       EnsureAutoplayRequested(handlingUserInput);
       break;
     }
@@ -7878,16 +7884,21 @@ HTMLMediaElement::AsyncRejectPendingPlay
     mPaused = true;
     DispatchAsyncEvent(NS_LITERAL_STRING("pause"));
   }
 
   if (mShuttingDown) {
     return;
   }
 
+  if (aError == NS_ERROR_DOM_MEDIA_NOT_ALLOWED_ERR &&
+      Preferences::GetBool("media.autoplay.block-event.enabled", false)) {
+    DispatchAsyncEvent(NS_LITERAL_STRING("blocked"));
+  }
+
   nsCOMPtr<nsIRunnable> event = new nsResolveOrRejectPendingPlayPromisesRunner(
     this, TakePendingPlayPromises(), aError);
 
   mMainThreadEventTarget->Dispatch(event.forget());
 }
 
 void
 HTMLMediaElement::GetEMEInfo(nsString& aEMEInfo)
--- a/modules/libpref/init/StaticPrefList.h
+++ b/modules/libpref/init/StaticPrefList.h
@@ -879,16 +879,22 @@ VARCACHE_PREF(
 
 // Media Seamless Looping
 VARCACHE_PREF(
   "media.seamless-looping",
    MediaSeamlessLooping,
   RelaxedAtomicBool, true
 )
 
+VARCACHE_PREF(
+  "media.autoplay.block-event.enabled",
+   MediaBlockEventEnabled,
+  bool, false
+)
+
 //---------------------------------------------------------------------------
 // Network prefs
 //---------------------------------------------------------------------------
 
 // Sub-resources HTTP-authentication:
 //   0 - don't allow sub-resources to open HTTP authentication credentials
 //       dialogs
 //   1 - allow sub-resources to open HTTP authentication credentials dialogs,
--- a/toolkit/content/tests/browser/browser.ini
+++ b/toolkit/content/tests/browser/browser.ini
@@ -37,16 +37,20 @@ support-files =
   file_autoplay_two_layers_frame1.html
   file_autoplay_two_layers_frame2.html
   file_video.html
   gizmo.mp4
 [browser_autoplay_policy_play_twice.js]
 support-files =
   gizmo.mp4
   file_video.html
+[browser_autoplay_policy_request_permission.js]
+support-files =
+  file_empty.html
+  gizmo.mp4
 [browser_autoplay_policy_user_gestures.js]
 support-files =
   gizmo.mp4
   file_video.html
 [browser_autoscroll_disabled.js]
 [browser_block_autoplay_media.js]
 tags = audiochannel
 [browser_block_autoplay_media_pausedAfterPlay.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/browser_autoplay_policy_request_permission.js
@@ -0,0 +1,203 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+"use strict";
+
+ChromeUtils.import("resource:///modules/SitePermissions.jsm", this);
+
+const VIDEO_PAGE = "https://example.com/browser/toolkit/content/tests/browser/file_empty.html";
+
+add_task(() => {
+  return SpecialPowers.pushPrefEnv({"set": [
+    ["media.autoplay.enabled", false],
+    ["media.autoplay.enabled.user-gestures-needed", true],
+    ["media.autoplay.ask-permission", true],
+    ["media.autoplay.block-event.enabled", true],
+  ]});
+});
+
+// Runs a content script that creates an autoplay video.
+//  browser: the browser to run the script in.
+//  args: test case definition, required members {
+//    mode: String, "autoplay attribute" or "call play".
+//  }
+function loadAutoplayVideo(browser, args) {
+  return ContentTask.spawn(browser, args, async (args) => {
+    info("- create a new autoplay video -");
+    let video = content.document.createElement("video");
+    video.id = "v1";
+    video.didPlayPromise = new Promise((resolve, reject) => {
+      video.addEventListener("play", (e) => {
+        video.didPlay = true;
+        resolve();
+      }, {once: true});
+      video.addEventListener("blocked", (e) => {
+        video.didPlay = false;
+        resolve();
+      }, {once: true});
+    });
+    if (args.mode == "autoplay attribute") {
+      info("autoplay attribute set to true");
+      video.autoplay = true;
+    } else if (args.mode == "call play") {
+      info("will call play() when reached loadedmetadata");
+      video.addEventListener("loadedmetadata", (e) => {
+        video.play().then(
+          () => {
+            info("video play() resolved");
+          },
+          () => {
+            info("video play() rejected");
+          });
+      }, {once: true});
+    } else {
+      ok(false, "Invalid 'mode' arg");
+    }
+    video.src = "gizmo.mp4";
+    content.document.body.appendChild(video);
+  });
+}
+
+// Runs a content script that checks whether the video created by
+// loadAutoplayVideo() started playing.
+// Parameters:
+//  browser: the browser to run the script in.
+//  args: test case definition, required members {
+//    name: String, description of test.
+//    mode: String, "autoplay attribute" or "call play".
+//    shouldPlay: boolean, whether video should play.
+//  }
+function checkVideoDidPlay(browser, args) {
+  return ContentTask.spawn(browser, args, async (args) => {
+    let video = content.document.getElementById("v1");
+    await video.didPlayPromise;
+    is(video.didPlay, args.shouldPlay,
+      args.name + " should " + (!args.shouldPlay ? "not " : "") + "be able to autoplay");
+    video.src = "";
+    content.document.body.remove(video);
+  });
+}
+
+async function testAutoplayExistingPermission(args) {
+  info("- Starting '" + args.name + "' -");
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: VIDEO_PAGE,
+  }, async (browser) => {
+    let promptShowing = () =>
+    PopupNotifications.getNotification("autoplay-media", browser);
+
+    SitePermissions.set(browser.currentURI, "autoplay-media", args.permission);
+    ok(!promptShowing(), "Should not be showing permission prompt yet");
+
+    await loadAutoplayVideo(browser, args);
+    await checkVideoDidPlay(browser, args);
+
+    // Reset permission.
+    SitePermissions.remove(browser.currentURI, "autoplay-media");
+
+    info("- Finished '" + args.name + "' -");
+  });
+}
+
+// Test the simple ALLOW/BLOCK cases; when permission is already set to ALLOW,
+// we shoud be able to autoplay via calling play(), or via the autoplay attribute,
+// and when it's set to BLOCK, we should not.
+add_task(async () => {
+  await testAutoplayExistingPermission({
+    name: "Prexisting allow permission autoplay attribute",
+    permission: SitePermissions.ALLOW,
+    shouldPlay: true,
+    mode: "autoplay attribute",
+  });
+  await testAutoplayExistingPermission({
+    name: "Prexisting allow permission call play",
+    permission: SitePermissions.ALLOW,
+    shouldPlay: true,
+    mode: "call play",
+  });
+  await testAutoplayExistingPermission({
+    name: "Prexisting block permission autoplay attribute",
+    permission: SitePermissions.BLOCK,
+    shouldPlay: false,
+    mode: "autoplay attribute",
+  });
+  await testAutoplayExistingPermission({
+    name: "Prexisting block permission call play",
+    permission: SitePermissions.BLOCK,
+    shouldPlay: false,
+    mode: "call play",
+  });
+});
+
+async function testAutoplayUnknownPermission(args) {
+  info("- Starting '" + args.name + "' -");
+  info("- open new tab -");
+
+  await BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: VIDEO_PAGE,
+  }, async (browser) => {
+    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, args);
+
+    info("Awaiting popupshown");
+    await popupshown;
+    ok(promptShowing(), "Should now be showing permission prompt");
+
+    // Click the appropriate doorhanger button.
+    if (args.button == "allow") {
+      info("Clicking allow button");
+      PopupNotifications.panel.firstElementChild.button.click();
+    } else if (args.button == "block") {
+      info("Clicking block button");
+      PopupNotifications.panel.firstChild.secondaryButton.click();
+    } else {
+      ok(false, "Invalid button field");
+    }
+    // Check that the video started playing.
+    await checkVideoDidPlay(browser, args);
+
+    // Reset permission.
+    SitePermissions.remove(browser.currentURI, "autoplay-media");
+    info("- Finished '" + args.name + "' -");
+  });
+}
+
+// Test the permission UNKNOWN case; we should prompt for permission, and
+// test pressing approve/block in both the autoplay attribute and call
+// play case.
+add_task(async () => {
+  await testAutoplayUnknownPermission({
+    name: "Unknown permission click allow autoplay attribute",
+    button: "allow",
+    shouldPlay: true,
+    mode: "autoplay attribute",
+  });
+  await testAutoplayUnknownPermission({
+    name: "Unknown permission click allow call play",
+    button: "allow",
+    shouldPlay: true,
+    mode: "call play",
+  });
+  await testAutoplayUnknownPermission({
+    name: "Unknown permission click block autoplay attribute",
+    button: "block",
+    shouldPlay: false,
+    mode: "autoplay attribute",
+  });
+  await testAutoplayUnknownPermission({
+    name: "Unknown permission click block call play",
+    button: "block",
+    shouldPlay: false,
+    mode: "call play",
+  });
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/content/tests/browser/file_empty.html
@@ -0,0 +1,8 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <title>Page left intentionally blank...</title>
+  </head>
+  <body>
+  </body>
+</html>