Commit d1bba8d8 authored by Niklas Baumgardner's avatar Niklas Baumgardner
Browse files

Bug 1749586 - Add code for PiP first time toggle experiment. r=mhowell,kpatenio

parent 631334fa
Loading
Loading
Loading
Loading
+30 −0
Original line number Diff line number Diff line
@@ -54,6 +54,10 @@ XPCOMUtils.defineLazyModuleGetters(this, {
  AppConstants: "resource://gre/modules/AppConstants.jsm",
});

XPCOMUtils.defineLazyModuleGetters(this, {
  NimbusFeatures: "resource://nimbus/ExperimentAPI.jsm",
});

XPCOMUtils.defineLazyPreferenceGetter(
  this,
  "DISPLAY_TEXT_TRACKS_PREF",
@@ -930,6 +934,24 @@ class PictureInPictureToggleChild extends JSWindowActorChild {
      toggle.removeAttribute("policy");
    }

    const nimbusExperimentVariables = NimbusFeatures.pictureinpicture.getAllVariables(
      { defaultValues: { title: null, message: false, showIconOnly: false } }
    );
    // nimbusExperimentVariables will be defaultValues when the experiment is disabled
    if (nimbusExperimentVariables.title && nimbusExperimentVariables.message) {
      let pipExplainer = shadowRoot.querySelector(".pip-explainer");
      let pipLabel = shadowRoot.querySelector(".pip-label");

      pipExplainer.innerText = nimbusExperimentVariables.message;
      pipLabel.innerText = nimbusExperimentVariables.title;
    } else if (nimbusExperimentVariables.showIconOnly) {
      // We only want to show the PiP icon in this experiment scenario
      let pipExpanded = shadowRoot.querySelector(".pip-expanded");
      pipExpanded.style.display = "none";
      let pipIcon = shadowRoot.querySelector(".pip-icon");
      pipIcon.style.display = "block";
    }

    controlsOverlay.removeAttribute("hidetoggle");

    // The hideToggleDeferredTask we create here is for automatically hiding
@@ -966,6 +988,14 @@ class PictureInPictureToggleChild extends JSWindowActorChild {
      !toggle.hasAttribute("hidden")
    ) {
      Services.telemetry.scalarAdd("pictureinpicture.saw_toggle", 1);
      // only record if this is the first time seeing the toggle
      if (
        !Services.prefs.getBoolPref(
          "media.videocontrols.picture-in-picture.video-toggle.has-used"
        )
      ) {
        NimbusFeatures.pictureinpicture.recordExposureEvent();
      }
    }

    // Now that we're hovering the video, we'll check to see if we're
+14 −0
Original line number Diff line number Diff line
@@ -473,3 +473,17 @@ pbNewtab:
  schema: >-
    browser/components/newtab/content-src/asrouter/templates/PBNewtab/NewtabPromoMessage.schema.json
  variables: {}
pictureinpicture:
  description: Message for first time Picture-in-Picture users
  hasExposure: true
  exposureDescription: Exposure is sent when a user hovers over a video and Picture-in-Picture has not been used before
  variables:
    title:
      type: string
      description: The title to be used for the PiP toggle
    message:
      type: string
      description: The message to be used in the PiP toggle
    showIconOnly:
      type: boolean
      description: Whether to show the first time PiP toggle or show the PiP icon only
+2 −0
Original line number Diff line number Diff line
@@ -61,6 +61,8 @@ support-files =
skip-if =
  debug
  os == 'linux' && bits == 64 && !debug # Bug 1549875
[browser_nimbusMessageFirstTimePip.js]
[browser_nimbusShowIconOnly.js]
[browser_noPlayerControlsOnMiddleRightClick.js]
[browser_noToggleOnAudio.js]
[browser_playerControls.js]
+178 −0
Original line number Diff line number Diff line
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

const { ExperimentFakes } = ChromeUtils.import(
  "resource://testing-common/NimbusTestUtils.jsm"
);

const PIP_EXPERIMENT_MESSAGE = "Hello world message";
const PIP_EXPERIMENT_TITLE = "Hello world title";

/**
 * This function will hover over the middle of the video and then
 * hover over the toggle to reveal the first time PiP message
 * @param browser The current browser
 * @param videoID The video element id
 */
async function hoverToggle(browser, videoID) {
  await prepareForToggleClick(browser, videoID);

  // Hover the mouse over the video to reveal the toggle.
  await BrowserTestUtils.synthesizeMouseAtCenter(
    `#${videoID}`,
    {
      type: "mousemove",
    },
    browser
  );
  await BrowserTestUtils.synthesizeMouseAtCenter(
    `#${videoID}`,
    {
      type: "mouseover",
    },
    browser
  );

  info("Checking toggle policy");
  await assertTogglePolicy(browser, videoID, null);

  let toggleClientRect = await getToggleClientRect(browser, videoID);

  info("Hovering the toggle rect now.");
  let toggleCenterX = toggleClientRect.left + toggleClientRect.width / 2;
  let toggleCenterY = toggleClientRect.top + toggleClientRect.height / 2;

  await BrowserTestUtils.synthesizeMouseAtPoint(
    toggleCenterX,
    toggleCenterY,
    {
      type: "mousemove",
    },
    browser
  );
  await BrowserTestUtils.synthesizeMouseAtPoint(
    toggleCenterX,
    toggleCenterY,
    {
      type: "mouseover",
    },
    browser
  );
}

/**
 * This tests that the original DTD string is shown for the PiP toggle
 */
add_task(async function test_experiment_control() {
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: TEST_PAGE,
    },
    async browser => {
      const s = `<!DOCTYPE bindings [
        <!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
        %videocontrolsDTD;
        ]>
        <html xmlns=\"http://www.w3.org/1999/xhtml\">
          <head>
            <meta charset=\"utf-8\"/>
            <div class="pip-message">&pictureInPictureExplainer;</div>
          </head>
        </html>`;
      const parser = new DOMParser();

      parser.forceEnableDTD();
      let doc = parser.parseFromString(s, "application/xhtml+xml");
      const pipDTDMessage = doc.querySelector(".pip-message").innerHTML.trim();

      await SimpleTest.promiseFocus(browser);
      await ensureVideosReady(browser);

      const PIP_PREF =
        "media.videocontrols.picture-in-picture.video-toggle.has-used";
      await SpecialPowers.pushPrefEnv({
        set: [[PIP_PREF, false]],
      });

      let videoID = "with-controls";
      await hoverToggle(browser, videoID);

      await SpecialPowers.spawn(browser, [pipDTDMessage], async function(
        pipDTDMessage
      ) {
        let video = content.document.getElementById("with-controls");
        let shadowRoot = video.openOrClosedShadowRoot;
        let pipButton = shadowRoot.querySelector(".pip-explainer");

        Assert.equal(
          pipButton.textContent.trim(),
          pipDTDMessage,
          "The PiP explainer is default"
        );
      });
    }
  );
});

/**
 * This tests that the experiment message is shown for the PiP toggle
 */
add_task(async function test_experiment_message() {
  let doExperimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
    featureId: "pictureinpicture",
    value: {
      title: PIP_EXPERIMENT_TITLE,
      message: PIP_EXPERIMENT_MESSAGE,
    },
  });

  registerCleanupFunction(async function() {
    await doExperimentCleanup();
  });

  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: TEST_PAGE,
    },
    async browser => {
      await SimpleTest.promiseFocus(browser);
      await ensureVideosReady(browser);

      const PIP_PREF =
        "media.videocontrols.picture-in-picture.video-toggle.has-used";
      await SpecialPowers.pushPrefEnv({
        set: [[PIP_PREF, false]],
      });

      let videoID = "with-controls";
      await hoverToggle(browser, videoID);

      await SpecialPowers.spawn(
        browser,
        [PIP_EXPERIMENT_MESSAGE, PIP_EXPERIMENT_TITLE],
        async function(PIP_EXPERIMENT_MESSAGE, PIP_EXPERIMENT_TITLE) {
          let video = content.document.getElementById("with-controls");
          let shadowRoot = video.openOrClosedShadowRoot;
          let pipExplainer = shadowRoot.querySelector(".pip-explainer");
          let pipLabel = shadowRoot.querySelector(".pip-label");

          Assert.equal(
            pipExplainer.textContent.trim(),
            PIP_EXPERIMENT_MESSAGE,
            "The PiP explainer is being overridden by the experiment"
          );

          Assert.equal(
            pipLabel.textContent.trim(),
            PIP_EXPERIMENT_TITLE,
            "The PiP label is being overridden by the experiment"
          );
        }
      );
    }
  );
});
+171 −0
Original line number Diff line number Diff line
/* Any copyright is dedicated to the Public Domain.
   http://creativecommons.org/publicdomain/zero/1.0/ */

"use strict";

const { ExperimentFakes } = ChromeUtils.import(
  "resource://testing-common/NimbusTestUtils.jsm"
);

const PIP_EXPERIMENT_MESSAGE = "Hello world message";
const PIP_EXPERIMENT_TITLE = "Hello world title";

/**
 * This function will hover over the middle of the video and then
 * hover over the toggle to reveal the first time PiP message
 * @param browser The current browser
 * @param videoID The video element id
 */
async function hoverToggle(browser, videoID) {
  await prepareForToggleClick(browser, videoID);

  // Hover the mouse over the video to reveal the toggle.
  await BrowserTestUtils.synthesizeMouseAtCenter(
    `#${videoID}`,
    {
      type: "mousemove",
    },
    browser
  );
  await BrowserTestUtils.synthesizeMouseAtCenter(
    `#${videoID}`,
    {
      type: "mouseover",
    },
    browser
  );

  info("Checking toggle policy");
  await assertTogglePolicy(browser, videoID, null);

  let toggleClientRect = await getToggleClientRect(browser, videoID);

  info("Hovering the toggle rect now.");
  let toggleCenterX = toggleClientRect.left + toggleClientRect.width / 2;
  let toggleCenterY = toggleClientRect.top + toggleClientRect.height / 2;

  await BrowserTestUtils.synthesizeMouseAtPoint(
    toggleCenterX,
    toggleCenterY,
    {
      type: "mousemove",
    },
    browser
  );
  await BrowserTestUtils.synthesizeMouseAtPoint(
    toggleCenterX,
    toggleCenterY,
    {
      type: "mouseover",
    },
    browser
  );
}

/**
 * This tests that the original DTD string is shown for the PiP toggle
 */
add_task(async function test_experiment_control() {
  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: TEST_PAGE,
    },
    async browser => {
      const s = `<!DOCTYPE bindings [
        <!ENTITY % videocontrolsDTD SYSTEM "chrome://global/locale/videocontrols.dtd">
        %videocontrolsDTD;
        ]>
        <html xmlns=\"http://www.w3.org/1999/xhtml\">
          <head>
            <meta charset=\"utf-8\"/>
            <div class="pip-message">&pictureInPictureExplainer;</div>
          </head>
        </html>`;
      const parser = new DOMParser();

      parser.forceEnableDTD();
      let doc = parser.parseFromString(s, "application/xhtml+xml");
      const pipDTDMessage = doc.querySelector(".pip-message").innerHTML.trim();

      await SimpleTest.promiseFocus(browser);
      await ensureVideosReady(browser);

      const PIP_PREF =
        "media.videocontrols.picture-in-picture.video-toggle.has-used";
      await SpecialPowers.pushPrefEnv({
        set: [[PIP_PREF, false]],
      });

      let videoID = "with-controls";
      await hoverToggle(browser, videoID);

      await SpecialPowers.spawn(browser, [pipDTDMessage], async function(
        pipDTDMessage
      ) {
        let video = content.document.getElementById("with-controls");
        let shadowRoot = video.openOrClosedShadowRoot;
        let pipButton = shadowRoot.querySelector(".pip-explainer");

        Assert.equal(
          pipButton.textContent.trim(),
          pipDTDMessage,
          "The PiP explainer is default"
        );
      });
    }
  );
});

/**
 * This tests that the experiment is showing the icon only
 */
add_task(async function test_experiment_iconOnly() {
  let experimentCleanup = await ExperimentFakes.enrollWithFeatureConfig({
    featureId: "pictureinpicture",
    value: {
      showIconOnly: true,
    },
  });

  registerCleanupFunction(async function() {
    await experimentCleanup();
  });

  await BrowserTestUtils.withNewTab(
    {
      gBrowser,
      url: TEST_PAGE,
    },
    async browser => {
      await SimpleTest.promiseFocus(browser);
      await ensureVideosReady(browser);

      const PIP_PREF =
        "media.videocontrols.picture-in-picture.video-toggle.has-used";
      await SpecialPowers.pushPrefEnv({
        set: [[PIP_PREF, false]],
      });

      let videoID = "with-controls";
      await hoverToggle(browser, videoID);

      await SpecialPowers.spawn(browser, [], async function() {
        let video = content.document.getElementById("with-controls");
        let shadowRoot = video.openOrClosedShadowRoot;
        let pipExpanded = shadowRoot.querySelector(".pip-expanded");
        let pipIcon = shadowRoot.querySelector("div.pip-icon");

        Assert.ok(
          ContentTaskUtils.is_hidden(pipExpanded),
          "The PiP explainer hidden by the experiment"
        );

        Assert.ok(
          ContentTaskUtils.is_visible(pipIcon),
          "The PiP icon is visible by the experiment"
        );
      });
    }
  );
});