Bug 1543128 - Get rid of most of the AnonymousContent toggle implementation, but leave some things stubbed out for a later patch. r=jaws,zbraniecki
authorMike Conley <mconley@mozilla.com>
Mon, 15 Apr 2019 01:09:12 +0000
changeset 469462 1f09366dd6bd135b45779af7c43a5c5184f747ec
parent 469461 3ac660e7f45c91f5af86614696a1f7acc760049a
child 469463 47c6930938a779db5d239c1e94a26e5e381d2612
push id112792
push userncsoregi@mozilla.com
push dateMon, 15 Apr 2019 09:49:11 +0000
treeherdermozilla-inbound@a57f27d3ccd0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjaws, zbraniecki
bugs1543128
milestone68.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 1543128 - Get rid of most of the AnonymousContent toggle implementation, but leave some things stubbed out for a later patch. r=jaws,zbraniecki Depends on D26804 Differential Revision: https://phabricator.services.mozilla.com/D26805
browser/base/content/test/static/browser_parsable_css.js
toolkit/actors/PictureInPictureChild.jsm
toolkit/locales/en-US/toolkit/global/videocontrols.ftl
toolkit/themes/shared/jar.inc.mn
toolkit/themes/shared/pictureinpicture/toggle.css
--- a/browser/base/content/test/static/browser_parsable_css.js
+++ b/browser/base/content/test/static/browser_parsable_css.js
@@ -60,19 +60,16 @@ let whitelist = [
   {sourceName: /webide\/skin\/logs\.css$/i,
    intermittent: true,
    errorMessage: /Property contained reference to invalid variable.*color/i,
    isFromDevTools: true},
   {sourceName: /webide\/skin\/logs\.css$/i,
    intermittent: true,
    errorMessage: /Property contained reference to invalid variable.*background/i,
    isFromDevTools: true},
-  {sourceName: /pictureinpicture\/toggle.css$/i,
-   errorMessage: /Unknown pseudo-class.*moz-native-anonymous/i,
-   isFromDevTools: false},
 ];
 
 if (!Services.prefs.getBoolPref("layout.css.xul-box-display-values.content.enabled")) {
   // These are UA sheets which use non-content-exposed `display` values.
   whitelist.push({
     sourceName: /(skin\/shared\/Heartbeat|((?:res|gre-resources)\/(ua|html)))\.css$/i,
     errorMessage: /Error in parsing value for .*\bdisplay\b/i,
     isFromDevTools: false,
--- a/toolkit/actors/PictureInPictureChild.jsm
+++ b/toolkit/actors/PictureInPictureChild.jsm
@@ -5,71 +5,45 @@
 "use strict";
 
 var EXPORTED_SYMBOLS = ["PictureInPictureChild", "PictureInPictureToggleChild"];
 
 const {ActorChild} = ChromeUtils.import("resource://gre/modules/ActorChild.jsm");
 
 ChromeUtils.defineModuleGetter(this, "DeferredTask",
   "resource://gre/modules/DeferredTask.jsm");
-ChromeUtils.defineModuleGetter(this, "DOMLocalization",
-  "resource://gre/modules/DOMLocalization.jsm");
 ChromeUtils.defineModuleGetter(this, "Services",
   "resource://gre/modules/Services.jsm");
 
-const TOGGLE_STYLESHEET = "chrome://global/skin/pictureinpicture/toggle.css";
-const TOGGLE_ID = "picture-in-picture-toggle";
-const FLYOUT_TOGGLE_ID = "picture-in-picture-flyout-toggle";
-const FLYOUT_TOGGLE_CONTAINER = "picture-in-picture-flyout-container";
 const TOGGLE_ENABLED_PREF =
   "media.videocontrols.picture-in-picture.video-toggle.enabled";
-const FLYOUT_ENABLED_PREF =
-  "media.videocontrols.picture-in-picture.video-toggle.flyout-enabled";
-const FLYOUT_WAIT_MS_PREF =
-  "media.videocontrols.picture-in-picture.video-toggle.flyout-wait-ms";
-const FLYOUT_ANIMATION_RUNTIME_MS = 400;
 const MOUSEMOVE_PROCESSING_DELAY_MS = 50;
 
 // A weak reference to the most recent <video> in this content
 // process that is being viewed in Picture-in-Picture.
 var gWeakVideo = null;
 // A weak reference to the content window of the most recent
 // Picture-in-Picture window for this content process.
 var gWeakPlayerContent = null;
-// A process-global Promise that's set the first time the string for the
-// flyout toggle label is requested from Fluent.
-var gFlyoutLabelPromise = null;
-// A process-global for the width of the toggle icon. We stash this here after
-// computing it the first time to avoid repeatedly flushing styles.
-var gToggleWidth = 0;
 
 /**
  * The PictureInPictureToggleChild is responsible for displaying the overlaid
  * Picture-in-Picture toggle over top of <video> elements that the mouse is
  * hovering.
- *
- * It's also responsible for showing the "flyout" version of the toggle, which
- * currently displays on the first visible video per page.
  */
 class PictureInPictureToggleChild extends ActorChild {
   constructor(dispatcher) {
     super(dispatcher);
     // We need to maintain some state about various things related to the
     // Picture-in-Picture toggles - however, for now, the same
     // PictureInPictureToggleChild might be re-used for different documents.
     // We keep the state stashed inside of this WeakMap, keyed on the document
     // itself.
     this.weakDocStates = new WeakMap();
     this.toggleEnabled = Services.prefs.getBoolPref(TOGGLE_ENABLED_PREF);
-    this.flyoutEnabled = Services.prefs.getBoolPref(FLYOUT_ENABLED_PREF);
-    this.flyoutWaitMs = Services.prefs.getIntPref(FLYOUT_WAIT_MS_PREF);
-
-    this.l10n = new DOMLocalization([
-      "toolkit/global/videocontrols.ftl",
-    ]);
   }
 
   /**
    * Returns the state for the current document referred to via
    * this.content.document. If no such state exists, creates it, stores it
    * and returns it.
    */
   get docState() {
@@ -79,28 +53,22 @@ class PictureInPictureToggleChild extend
         // A reference to the IntersectionObserver that's monitoring for videos
         // to become visible.
         intersectionObserver: null,
         // A WeakSet of videos that are supposedly visible, according to the
         // IntersectionObserver.
         weakVisibleVideos: new WeakSet(),
         // The number of videos that are supposedly visible, according to the
         // IntersectionObserver
-        visibleVideos: 0,
+        visibleVideosCount: 0,
         // The DeferredTask that we'll arm every time a mousemove event occurs
         // on a page where we have one or more visible videos.
         mousemoveDeferredTask: null,
         // A weak reference to the last video we displayed the toggle over.
         weakOverVideo: null,
-        // A reference to the AnonymousContent returned after inserting the
-        // small toggle.
-        pipToggle: null,
-        // A reference to the AnonymousContent returned after inserting the
-        // flyout toggle.
-        flyoutToggle: null,
       };
       this.weakDocStates.set(this.content.document, state);
     }
 
     return state;
   }
 
   handleEvent(event) {
@@ -110,34 +78,17 @@ class PictureInPictureToggleChild extend
             event.target instanceof this.content.HTMLVideoElement &&
             !event.target.controls &&
             event.target.ownerDocument == this.content.document) {
           this.registerVideo(event.target);
         }
         break;
       }
       case "click": {
-        let state = this.docState;
-        let clickedFlyout = state.flyoutToggle &&
-          state.flyoutToggle.getTargetIdForEvent(event) == FLYOUT_TOGGLE_ID;
-        let clickedToggle = state.pipToggle &&
-          state.pipToggle.getTargetIdForEvent(event) == TOGGLE_ID;
-
-        if (clickedFlyout || clickedToggle) {
-          let video = state.weakOverVideo && state.weakOverVideo.get();
-          if (video) {
-            let pipEvent =
-              new this.content.CustomEvent("MozTogglePictureInPicture", {
-                bubbles: true,
-              });
-            video.dispatchEvent(pipEvent);
-            this.hideFlyout();
-            this.onMouseLeaveVideo(video);
-          }
-        }
+        // Stubbed out for later patch in this series
         break;
       }
       case "mousemove": {
         this.onMouseMove(event);
         break;
       }
     }
   }
@@ -190,43 +141,35 @@ class PictureInPictureToggleChild extend
    * the viewport.
    */
   onIntersection(entries) {
     // The IntersectionObserver will also fire when a previously intersecting
     // element is removed from the DOM. We know, however, that the node is
     // still alive and referrable from the WeakSet because the
     // IntersectionObserverEntry holds a strong reference to the video.
     let state = this.docState;
-    let oldVisibleVideos = state.visibleVideos;
+    let oldVisibleVideosCount = state.visibleVideosCount;
     for (let entry of entries) {
       let video = entry.target;
       if (this.worthTracking(entry)) {
         if (!state.weakVisibleVideos.has(video)) {
           state.weakVisibleVideos.add(video);
-          state.visibleVideos++;
-
-          // The very first video that we notice is worth tracking, we'll show
-          // the flyout toggle on.
-          if (this.flyoutEnabled) {
-            this.content.requestIdleCallback(() => {
-              this.maybeShowFlyout(video);
-            });
-          }
+          state.visibleVideosCount++;
         }
       } else if (state.weakVisibleVideos.has(video)) {
         state.weakVisibleVideos.delete(video);
-        state.visibleVideos--;
+        state.visibleVideosCount--;
       }
     }
 
-    if (!oldVisibleVideos && state.visibleVideos) {
+    if (!oldVisibleVideosCount && state.visibleVideosCount) {
       this.content.requestIdleCallback(() => {
         this.beginTrackingMouseOverVideos();
       });
-    } else if (oldVisibleVideos && !state.visibleVideos) {
+    } else if (oldVisibleVideosCount && !state.visibleVideosCount) {
       this.content.requestIdleCallback(() => {
         this.stopTrackingMouseOverVideos();
       });
     }
   }
 
   /**
    * One of the challenges of displaying this toggle is that many sites put
@@ -324,224 +267,27 @@ class PictureInPictureToggleChild extend
   onMouseOverVideo(video) {
     let state = this.docState;
     let oldOverVideo = state.weakOverVideo && state.weakOverVideo.get();
     if (oldOverVideo && oldOverVideo == video) {
       return;
     }
 
     state.weakOverVideo = Cu.getWeakReference(video);
-    this.moveToggleToVideo(video);
+    // Stubbed out for a later patch in this series.
   }
 
   /**
    * Called once it has been determined that the mouse is no longer overlapping
    * a video that we'd previously called onMouseOverVideo with.
    *
    * @param {Element} video The video that the mouse left.
    */
   onMouseLeaveVideo(video) {
-    let state = this.docState;
-    state.weakOverVideo = null;
-    state.pipToggle.setAttributeForElement(TOGGLE_ID, "hidden", "true");
-  }
-
-  /**
-   * The toggle is injected as AnonymousContent that is positioned absolutely.
-   * This method takes the <video> that we want to display the toggle on and
-   * calculates where exactly we need to position the AnonymousContent in
-   * absolute coordinates.
-   *
-   * @param {Element} video The video to display the toggle on.
-   * @param {AnonymousContent} anonymousContent The anonymousContent associated
-   * with the toggle about to be shown.
-   * @param {String} toggleID The ID of the toggle element with the CSS
-   * variables defining the toggle width and padding.
-   *
-   * @return {Object} with the following properties:
-   *   {Number} top The top / y coordinate.
-   *   {Number} left The left / x coordinate.
-   *   {Number} width The width of the toggle icon, including padding.
-   */
-  calculateTogglePosition(video, anonymousContent, toggleID) {
-    let winUtils = this.content.windowUtils;
-
-    let scrollX = {}, scrollY = {};
-    winUtils.getScrollXY(false, scrollX, scrollY);
-
-    let rect = winUtils.getBoundsWithoutFlushing(video);
-
-    // For now, using AnonymousContent.getComputedStylePropertyValue causes
-    // a style flush, so we'll cache the value in this content process the
-    // first time we read it. See bug 1541207.
-    if (!gToggleWidth) {
-      let widthStr = anonymousContent.getComputedStylePropertyValue(toggleID,
-        "--pip-toggle-icon-width-height");
-      let paddingStr = anonymousContent.getComputedStylePropertyValue(toggleID,
-        "--pip-toggle-padding");
-      let iconWidth = parseInt(widthStr, 0);
-      let iconPadding = parseInt(paddingStr, 0);
-      gToggleWidth = iconWidth + (2 * iconPadding);
-    }
-
-    let originY = rect.top + scrollY.value;
-    let originX = rect.left + scrollX.value;
-
-    let top = originY + (rect.height / 2 - Math.round(gToggleWidth / 2));
-    let left = originX + (rect.width - gToggleWidth);
-
-    return { top, left, width: gToggleWidth };
-  }
-
-  /**
-   * Puts the small "Picture-in-Picture" toggle onto the passed in video.
-   *
-   * @param {Element} video The video to display the toggle on.
-   */
-  moveToggleToVideo(video) {
-    let state = this.docState;
-    let winUtils = this.content.windowUtils;
-
-    if (!state.pipToggle) {
-      try {
-        winUtils.loadSheetUsingURIString(TOGGLE_STYLESHEET,
-                                         winUtils.AGENT_SHEET);
-      } catch (e) {
-        // This method can fail with NS_ERROR_INVALID_ARG if the sheet is
-        // already loaded - for example, from the flyout toggle.
-        if (e.result != Cr.NS_ERROR_INVALID_ARG) {
-          throw e;
-        }
-      }
-      let toggle = this.content.document.createElement("button");
-      toggle.classList.add("picture-in-picture-toggle-button");
-      toggle.id = TOGGLE_ID;
-      let icon = this.content.document.createElement("div");
-      icon.classList.add("icon");
-      toggle.appendChild(icon);
-
-      state.pipToggle = this.content.document.insertAnonymousContent(toggle);
-    }
-
-    let { top, left } = this.calculateTogglePosition(video, state.pipToggle,
-                                                     TOGGLE_ID);
-
-    let styles = `
-      top: ${top}px;
-      left: ${left}px;
-    `;
-
-    let toggle = state.pipToggle;
-    toggle.setAttributeForElement(TOGGLE_ID, "style", styles);
-    // The toggle might have been hidden after a previous appearance.
-    toggle.removeAttributeForElement(TOGGLE_ID, "hidden");
-  }
-
-  /**
-   * Lazy getter that returns a Promise that resolves to the flyout toggle
-   * label string. Sets a process-global variable to the Promise so that
-   * subsequent calls within the same process don't cause us to go through
-   * the Fluent look-up path again.
-   */
-  get flyoutLabel() {
-    if (gFlyoutLabelPromise) {
-      return gFlyoutLabelPromise;
-    }
-
-    gFlyoutLabelPromise =
-      this.l10n.formatValue("picture-in-picture-flyout-toggle");
-    return gFlyoutLabelPromise;
-  }
-
-  /**
-   * If configured to, will display the "Picture-in-Picture" flyout toggle on
-   * the passed-in video. This is an asynchronous function that handles the
-   * entire lifecycle of the flyout animation. If a flyout toggle has already
-   * been seen on this page, this function does nothing.
-   *
-   * @param {Element} video The video to display the flyout on.
-   *
-   * @return {Promise}
-   * @resolves {undefined} Once the flyout toggle animation has completed.
-   */
-  async maybeShowFlyout(video) {
-    let state = this.docState;
-
-    if (state.flyoutToggle) {
-      return;
-    }
-
-    let winUtils = this.content.windowUtils;
-
-    try {
-      winUtils.loadSheetUsingURIString(TOGGLE_STYLESHEET, winUtils.AGENT_SHEET);
-    } catch (e) {
-      // This method can fail with NS_ERROR_INVALID_ARG if the sheet is
-      // already loaded.
-      if (e.result != Cr.NS_ERROR_INVALID_ARG) {
-        throw e;
-      }
-    }
-
-    let container = this.content.document.createElement("div");
-    container.id = FLYOUT_TOGGLE_CONTAINER;
-
-    let toggle = this.content.document.createElement("button");
-    toggle.classList.add("picture-in-picture-toggle-button");
-    toggle.id = FLYOUT_TOGGLE_ID;
-
-    let icon = this.content.document.createElement("div");
-    icon.classList.add("icon");
-    toggle.appendChild(icon);
-
-    let label = this.content.document.createElement("span");
-    label.classList.add("label");
-    label.textContent = await this.flyoutLabel;
-    toggle.appendChild(label);
-    container.appendChild(toggle);
-    state.flyoutToggle =
-      this.content.document.insertAnonymousContent(container);
-
-    let { top, left, width } =
-      this.calculateTogglePosition(video, state.flyoutToggle, FLYOUT_TOGGLE_ID);
-
-    let styles = `
-      top: ${top}px;
-      left: ${left}px;
-    `;
-
-    let flyout = state.flyoutToggle;
-    flyout.setAttributeForElement(FLYOUT_TOGGLE_CONTAINER, "style", styles);
-    let flyoutAnim = flyout.setAnimationForElement(FLYOUT_TOGGLE_ID, [
-      { transform: `translateX(calc(100% - ${width}px))`, opacity: "0.2" },
-      { transform: `translateX(calc(100% - ${width}px))`, opacity: "0.8" },
-      { transform: "translateX(0)", opacity: "1" },
-    ], FLYOUT_ANIMATION_RUNTIME_MS);
-
-    await flyoutAnim.finished;
-
-    await new Promise(resolve => this.content.setTimeout(resolve,
-                                                         this.flyoutWaitMs));
-
-    flyoutAnim.reverse();
-    await flyoutAnim.finished;
-
-    this.hideFlyout();
-  }
-
-  /**
-   * Once the flyout has finished animating, or Picture-in-Picture has been
-   * requested, this function can be called to hide it.
-   */
-  hideFlyout() {
-    let state = this.docState;
-    let flyout = state.flyoutToggle;
-    if (flyout) {
-      flyout.setAttributeForElement(FLYOUT_TOGGLE_CONTAINER, "hidden", "true");
-    }
+    // Stubbed out for a later patch in this series.
   }
 }
 
 class PictureInPictureChild extends ActorChild {
   static videoIsPlaying(video) {
     return !!(video.currentTime > 0 && !video.paused && !video.ended && video.readyState > 2);
   }
 
deleted file mode 100644
--- a/toolkit/locales/en-US/toolkit/global/videocontrols.ftl
+++ /dev/null
@@ -1,13 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-### These strings are used in the video controls.
-
-# This string is used when displaying the Picture-in-Picture "flyout" toggle.
-# The "flyout" toggle is a variation of the Picture-in-Picture video toggle that
-# appears in a ribbon over top of <video> elements when Picture-in-Picture is
-# enabled. This variation only appears on the first <video> that's displayed to
-# a user on a page. It animates out, displaying this string, and after 5
-# seconds, animates away again.
-picture-in-picture-flyout-toggle = Picture-in-Picture
--- a/toolkit/themes/shared/jar.inc.mn
+++ b/toolkit/themes/shared/jar.inc.mn
@@ -107,11 +107,10 @@ toolkit.jar:
   skin/classic/global/plugins/plugin.svg                    (../../shared/plugins/plugin.svg)
   skin/classic/global/plugins/plugin-blocked.svg            (../../shared/plugins/plugin-blocked.svg)
   skin/classic/global/plugins/pluginGeneric.svg             (../../shared/extensions/category-plugins.svg)
   skin/classic/global/plugins/pluginProblem.css             (../../shared/plugins/pluginProblem.css)
   skin/classic/global/plugins/contentPluginBlocked.png      (../../shared/plugins/contentPluginBlocked.png)
   skin/classic/global/plugins/contentPluginCrashed.png      (../../shared/plugins/contentPluginCrashed.png)
   skin/classic/global/plugins/contentPluginStripe.png       (../../shared/plugins/contentPluginStripe.png)
   skin/classic/global/pictureinpicture/player.css           (../../shared/pictureinpicture/player.css)
-  skin/classic/global/pictureinpicture/toggle.css           (../../shared/pictureinpicture/toggle.css)
   skin/classic/global/media/pictureinpicture.svg            (../../shared/media/pictureinpicture.svg)
 
deleted file mode 100644
--- a/toolkit/themes/shared/pictureinpicture/toggle.css
+++ /dev/null
@@ -1,83 +0,0 @@
-/**
- * We add the #picture-in-picture-flyout-container and
- * #picture-in-picture-toggle IDs here so that it's easier to read these
- * property values in script, since they're AnonymousContent, and we need
- * IDs and can't use classes to query AnonymousContent property values.
- */
-#picture-in-picture-flyout-container:-moz-native-anonymous,
-#picture-in-picture-toggle:-moz-native-anonymous,
-.picture-in-picture-toggle-button:-moz-native-anonymous {
-  --pip-toggle-bgcolor: rgb(0, 96, 223);
-  --pip-toggle-text-and-icon-color: rgb(255, 255, 255);
-  --pip-toggle-padding: 5px;
-  --pip-toggle-icon-width-height: 16px;
-}
-
-.picture-in-picture-toggle-button:-moz-native-anonymous {
-  -moz-appearance: none;
-  display: flex;
-  position: absolute;
-  background-color: var(--pip-toggle-bgcolor);
-  border: 0;
-  padding: var(--pip-toggle-padding);
-  color: var(--pip-toggle-text-and-icon-color);
-  transform: translateX(0);
-  transition: transform 350ms linear;
-  min-width: max-content;
-  pointer-events: auto;
-  opacity: 0.8;
-}
-
-.picture-in-picture-toggle-button:-moz-native-anonymous:hover,
-.picture-in-picture-toggle-button:-moz-native-anonymous:active {
-  opacity: 1;
-  background-color: var(--pip-toggle-bgcolor);
-  color: var(--pip-toggle-text-and-icon-color);
-  padding: var(--pip-toggle-padding);
-}
-
-#picture-in-picture-flyout-container[hidden]:-moz-native-anonymous,
-.picture-in-picture-toggle-button[hidden]:-moz-native-anonymous {
-  display: none;
-}
-
-.picture-in-picture-toggle-button:-moz-native-anonymous > .icon {
-  display: inline-block;
-  background-image: url(chrome://global/skin/media/pictureinpicture.svg);
-  background-position: center left;
-  background-repeat: no-repeat;
-  -moz-context-properties: fill, stroke;
-  fill: var(--pip-toggle-text-and-icon-color);
-  stroke: var(--pip-toggle-text-and-icon-color);
-  width: var(--pip-toggle-icon-width-height);
-  height: var(--pip-toggle-icon-width-height);
-  min-width: max-content;
-  pointer-events: none;
-}
-
-.picture-in-picture-toggle-button:-moz-native-anonymous > .label {
-  margin-left: var(--pip-toggle-padding);
-  min-width: max-content;
-  pointer-events: none;
-}
-
-#picture-in-picture-flyout-container:-moz-native-anonymous {
-  position: absolute;
-  /**
-   * A higher z-index makes sure that the flyout always appears on top of the
-   * other toggle, so that we avoid seeing double-toggles.
-   */
-  z-index: 2;
-  overflow: hidden;
-  /**
-   * This places the container for the flyout in the position where the flyout
-   * eventually ends up. This, coupled with the overflow: hidden, gives the
-   * effect that the flyout is sliding out from the edge of the video.
-   */
-  transform: translateX(calc(-100% + var(--pip-toggle-icon-width-height) + 2 * var(--pip-toggle-padding)));
-}
-
-#picture-in-picture-flyout-container:-moz-native-anonymous > .picture-in-picture-toggle-button {
-  position: relative;
-  opacity: 1;
-}