Bug 1488673 - part3 : only use BoxPosition format for position calculation r=heycam
authorAlastor Wu <alwu@mozilla.com>
Tue, 19 Mar 2019 03:29:29 +0000
changeset 464983 e89cf185fc33bf7deb0edea5d42ca918301aa797
parent 464982 367a2a68fb2dfeef6e945b01263335124f05ca12
child 464984 9e2f80f5ec79e1d33826065ef40fb799da3ad574
push id112486
push useropoprus@mozilla.com
push dateTue, 19 Mar 2019 16:41:04 +0000
treeherdermozilla-inbound@ee866fb50236 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs1488673
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 1488673 - part3 : only use BoxPosition format for position calculation r=heycam It's confused that we have both simpleBoxPosition object and BoxPosition object, we should only use one format to perform all box related operations. Therefore, BoxPosition should be able to be initiaized by StyleBox, HTMLElement or BoxPosition. In addition, as `right` and `bottom` can be calculated from other attributes, we remove these two attributes from BoxPosition, and use getter to get the correct value, which can reduce some unnessary modification when we changes the `height` or `width`. In order to implement a more readable getter, so we change `BoxPosition` to class-based. Differential Revision: https://phabricator.services.mozilla.com/D22809
dom/media/webvtt/vtt.jsm
--- a/dom/media/webvtt/vtt.jsm
+++ b/dom/media/webvtt/vtt.jsm
@@ -514,42 +514,68 @@ XPCOMUtils.defineLazyPreferenceGetter(th
     constructor(window, cue, containerBox) {
       super();
       this.cue = cue;
       this.div = window.document.createElement("div");
       this.cueDiv = parseContent(window, cue.text, supportPseudo ?
         PARSE_CONTENT_MODE.PSUEDO_CUE : PARSE_CONTENT_MODE.NORMAL_CUE);
       this.div.appendChild(this.cueDiv);
 
+      this.containerHeight = containerBox.height;
+      this.containerWidth = containerBox.width;
       this.fontSize = this._getFontSize(containerBox);
+      this.isCueStyleBox = true;
+
       // As pseudo element won't inherit the parent div's style, so we have to
       // set the font size explicitly.
       if (supportPseudo) {
         this.cueDiv.style.setProperty("--cue-font-size", this.fontSize);
       } else {
         this._applyNonPseudoCueStyles();
       }
       this.applyStyles(this._getNodeDefaultStyles(cue));
     }
 
+    getCueBoxPositionAndSize() {
+      // As `top`, `left`, `width` and `height` are all represented by the
+      // percentage of the container, we need to convert them to the actual
+      // number according to the container's size.
+      const isWritingDirectionHorizontal = this.cue.vertical == "";
+      let top =
+            this.containerHeight * this._tranferPercentageToFloat(this.div.style.top),
+          left =
+            this.containerWidth * this._tranferPercentageToFloat(this.div.style.left),
+          width = isWritingDirectionHorizontal ?
+            this.containerWidth * this._tranferPercentageToFloat(this.div.style.width) :
+            this.div.offsetWidth,
+          height = isWritingDirectionHorizontal ?
+            this.div.offsetHeight :
+            this.containerHeight * this._tranferPercentageToFloat(this.div.style.height);
+      return { top, left, width, height };
+    }
+
     move(box) {
       this.applyStyles({
         top: this.formatStyle(box.top, "px"),
         bottom: this.formatStyle(box.bottom, "px"),
         left: this.formatStyle(box.left, "px"),
         right: this.formatStyle(box.right, "px"),
         height: this.formatStyle(box.height, "px"),
         width: this.formatStyle(box.width, "px")
       });
     }
 
     /**
      * Following methods are private functions, should not use them outside this
      * class.
      */
+    _tranferPercentageToFloat(input) {
+      return input.replace("%", "") / 100.0;
+    }
+
     _getFontSize(containerBox) {
       // In https://www.w3.org/TR/webvtt1/#applying-css-properties, the spec
       // said the font size is '5vh', which means 5% of the viewport height.
       // However, if we use 'vh' as a basic unit, it would eventually become
       // 5% of screen height, instead of video's viewport height. Therefore, we
       // have to use 'px' here to make sure we have the correct font size.
       return containerBox.height * 0.05 + "px";
     }
@@ -746,160 +772,136 @@ XPCOMUtils.defineLazyPreferenceGetter(th
     this.div = window.document.createElement("div");
     this.applyStyles(regionCueStyles);
     this.div.appendChild(this.cueDiv);
   }
   RegionCueStyleBox.prototype = _objCreate(StyleBox.prototype);
   RegionCueStyleBox.prototype.constructor = RegionCueStyleBox;
 
   // Represents the co-ordinates of an Element in a way that we can easily
-  // compute things with such as if it overlaps or intersects with another Element.
-  // Can initialize it with either a StyleBox or another BoxPosition.
-  function BoxPosition(obj) {
-    // Either a BoxPosition was passed in and we need to copy it, or a StyleBox
-    // was passed in and we need to copy the results of 'getBoundingClientRect'
-    // as the object returned is readonly. All co-ordinate values are in reference
-    // to the viewport origin (top left).
-    var lh, height, width, top;
-    if (obj.div) {
-      height = obj.div.offsetHeight;
-      width = obj.div.offsetWidth;
-      top = obj.div.offsetTop;
+  // compute things with such as if it overlaps or intersects with other boxes.
+  class BoxPosition {
+    constructor(obj) {
+      // Get dimensions by calling getCueBoxPositionAndSize on a CueStyleBox, by
+      // getting offset properties from an HTMLElement (from the object or its
+      // `div` property), otherwise look at the regular box properties on the
+      // object.
+      const isHTMLElement = !obj.isCueStyleBox && (obj.div || obj.tagName);
+      obj = obj.isCueStyleBox ? obj.getCueBoxPositionAndSize() : obj.div || obj;
+      this.top = isHTMLElement ? obj.offsetTop : obj.top;
+      this.left = isHTMLElement ? obj.offsetLeft : obj.left;
+      this.width = isHTMLElement ? obj.offsetWidth : obj.width;
+      this.height = isHTMLElement ? obj.offsetHeight : obj.height;
+    }
 
-      var rects = (rects = obj.div.childNodes) && (rects = rects[0]) &&
-                  rects.getClientRects && rects.getClientRects();
-      obj = obj.div.getBoundingClientRect();
-      // In certain cases the outter div will be slightly larger then the sum of
-      // the inner div's lines. This could be due to bold text, etc, on some platforms.
-      // In this case we should get the average line height and use that. This will
-      // result in the desired behaviour.
-      lh = rects ? Math.max((rects[0] && rects[0].height) || 0, obj.height / rects.length)
-                 : 0;
-
+    get bottom() {
+      return this.top + this.height;
     }
-    this.left = obj.left;
-    this.right = obj.right;
-    this.top = obj.top || top;
-    this.height = obj.height || height;
-    this.bottom = obj.bottom || (top + (obj.height || height));
-    this.width = obj.width || width;
-    this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
-  }
 
-  // Move the box along a particular axis. Optionally pass in an amount to move
-  // the box. If no amount is passed then the default is the line height of the
-  // box.
-  BoxPosition.prototype.move = function(axis, toMove) {
-    toMove = toMove !== undefined ? toMove : this.lineHeight;
-    switch (axis) {
-    case "+x":
-      this.left += toMove;
-      this.right += toMove;
-      break;
-    case "-x":
-      this.left -= toMove;
-      this.right -= toMove;
-      break;
-    case "+y":
-      this.top += toMove;
-      this.bottom += toMove;
-      break;
-    case "-y":
-      this.top -= toMove;
-      this.bottom -= toMove;
-      break;
+    get right() {
+      return this.left + this.width;
     }
-  };
 
-  // Check if this box overlaps another box, b2.
-  BoxPosition.prototype.overlaps = function(b2) {
-    return this.left < b2.right &&
-           this.right > b2.left &&
-           this.top < b2.bottom &&
-           this.bottom > b2.top;
-  };
-
-  // Check if this box overlaps any other boxes in boxes.
-  BoxPosition.prototype.overlapsAny = function(boxes) {
-    for (var i = 0; i < boxes.length; i++) {
-      if (this.overlaps(boxes[i])) {
-        return true;
+    // Move the box along a particular axis. Optionally pass in an amount to move
+    // the box. If no amount is passed then the default is the line height of the
+    // box.
+    move(axis, toMove) {
+      switch (axis) {
+      case "+x":
+        this.left += toMove;
+        break;
+      case "-x":
+        this.left -= toMove;
+        break;
+      case "+y":
+        this.top += toMove;
+        break;
+      case "-y":
+        this.top -= toMove;
+        break;
       }
     }
-    return false;
-  };
 
-  // Check if this box is within another box.
-  BoxPosition.prototype.within = function(container) {
-    return this.top >= container.top &&
-           this.bottom <= container.bottom &&
-           this.left >= container.left &&
-           this.right <= container.right;
-  };
+    // Check if this box overlaps another box, b2.
+    overlaps(b2) {
+      return this.left < b2.right &&
+             this.right > b2.left &&
+             this.top < b2.bottom &&
+             this.bottom > b2.top;
+    }
 
-  // Check if this box is entirely within the container or it is overlapping
-  // on the edge opposite of the axis direction passed. For example, if "+x" is
-  // passed and the box is overlapping on the left edge of the container, then
-  // return true.
-  BoxPosition.prototype.overlapsOppositeAxis = function(container, axis) {
-    switch (axis) {
-    case "+x":
-      return this.left < container.left;
-    case "-x":
-      return this.right > container.right;
-    case "+y":
-      return this.top < container.top;
-    case "-y":
-      return this.bottom > container.bottom;
+    // Check if this box overlaps any other boxes in boxes.
+    overlapsAny(boxes) {
+      for (let i = 0; i < boxes.length; i++) {
+        if (this.overlaps(boxes[i])) {
+          return true;
+        }
+      }
+      return false;
     }
-  };
+
+    // Check if this box overlaps any other boxes in boxes.
+    overlapsAny(boxes) {
+      for (let i = 0; i < boxes.length; i++) {
+        if (this.overlaps(boxes[i])) {
+          return true;
+        }
+      }
+      return false;
+    }
+
+    // Check if this box is within another box.
+    within(container) {
+      return this.top >= container.top &&
+             this.bottom <= container.bottom &&
+             this.left >= container.left &&
+             this.right <= container.right;
+    }
 
-  // Find the percentage of the area that this box is overlapping with another
-  // box.
-  BoxPosition.prototype.intersectPercentage = function(b2) {
-    var x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
-        y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
-        intersectArea = x * y;
-    return intersectArea / (this.height * this.width);
-  };
+    // Check if this box is entirely within the container or it is overlapping
+    // on the edge opposite of the axis direction passed. For example, if "+x" is
+    // passed and the box is overlapping on the left edge of the container, then
+    // return true.
+    overlapsOppositeAxis(container, axis) {
+      switch (axis) {
+      case "+x":
+        return this.left < container.left;
+      case "-x":
+        return this.right > container.right;
+      case "+y":
+        return this.top < container.top;
+      case "-y":
+        return this.bottom > container.bottom;
+      }
+    }
 
-  // Convert the positions from this box to CSS compatible positions using
-  // the reference container's positions. This has to be done because this
-  // box's positions are in reference to the viewport origin, whereas, CSS
-  // values are in referecne to their respective edges.
-  BoxPosition.prototype.toCSSCompatValues = function(reference) {
-    return {
-      top: this.top - reference.top,
-      bottom: reference.bottom - this.bottom,
-      left: this.left - reference.left,
-      right: reference.right - this.right,
-      height: this.height,
-      width: this.width
-    };
-  };
+    // Find the percentage of the area that this box is overlapping with another
+    // box.
+    intersectPercentage(b2) {
+      let x = Math.max(0, Math.min(this.right, b2.right) - Math.max(this.left, b2.left)),
+          y = Math.max(0, Math.min(this.bottom, b2.bottom) - Math.max(this.top, b2.top)),
+          intersectArea = x * y;
+      return intersectArea / (this.height * this.width);
+    }
 
-  // Get an object that represents the box's position without anything extra.
-  // Can pass a StyleBox, HTMLElement, or another BoxPositon.
-  BoxPosition.getSimpleBoxPosition = function(obj) {
-    var height = obj.div ? obj.div.offsetHeight : obj.tagName ? obj.offsetHeight : 0;
-    var width = obj.div ? obj.div.offsetWidth : obj.tagName ? obj.offsetWidth : 0;
-    var top = obj.div ? obj.div.offsetTop : obj.tagName ? obj.offsetTop : 0;
-
-    obj = obj.div ? obj.div.getBoundingClientRect() :
-                  obj.tagName ? obj.getBoundingClientRect() : obj;
-    var ret = {
-      left: obj.left,
-      right: obj.right,
-      top: obj.top || top,
-      height: obj.height || height,
-      bottom: obj.bottom || (top + (obj.height || height)),
-      width: obj.width || width
-    };
-    return ret;
-  };
+    // Convert the positions from this box to CSS compatible positions using
+    // the reference container's positions. This has to be done because this
+    // box's positions are in reference to the viewport origin, whereas, CSS
+    // values are in referecne to their respective edges.
+    toCSSCompatValues(reference) {
+      return {
+        top: this.top - reference.top,
+        bottom: reference.bottom - this.bottom,
+        left: this.left - reference.left,
+        right: reference.right - this.right,
+        height: this.height,
+        width: this.width
+      };
+    }
+  }
 
   // Move a StyleBox to its specified, or next best, position. The containerBox
   // is the box that contains the StyleBox, such as a div. boxPositions are
   // a list of other boxes that the styleBox can't overlap with.
   function moveBoxToLinePosition(window, styleBox, containerBox, boxPositions) {
 
     // Find the best position for a cue box, b, on the video. The axis parameter
     // is a list of axis, the order of which, it will move the box along. For example:
@@ -1108,23 +1110,23 @@ XPCOMUtils.defineLazyPreferenceGetter(th
     rootOfCues.style.position = "absolute";
     rootOfCues.style.left = "0";
     rootOfCues.style.right = "0";
     rootOfCues.style.top = "0";
     rootOfCues.style.bottom = "0";
     overlay.appendChild(rootOfCues);
 
     var boxPositions = [],
-        containerBox = BoxPosition.getSimpleBoxPosition(rootOfCues);
+        containerBox = new BoxPosition(rootOfCues);
 
     (function() {
       var styleBox, cue, controlBarBox;
 
       if (controlBarShown) {
-        controlBarBox = BoxPosition.getSimpleBoxPosition(controlBar);
+        controlBarBox = new BoxPosition(controlBar);
         // Add an empty output box that cover the same region as video control bar.
         boxPositions.push(controlBarBox);
       }
 
       // https://w3c.github.io/webvtt/#processing-model 6.1.12.1
       // Create regionNode
       var regionNodeBoxes = {};
       var regionNodeBox;
@@ -1133,17 +1135,17 @@ XPCOMUtils.defineLazyPreferenceGetter(th
         cue = cues[i];
         if (cue.region != null) {
          // 6.1.14.1
           styleBox = new RegionCueStyleBox(window, cue);
 
           if (!regionNodeBoxes[cue.region.id]) {
             // create regionNode
             // Adjust the container hieght to exclude the controlBar
-            var adjustContainerBox = BoxPosition.getSimpleBoxPosition(rootOfCues);
+            var adjustContainerBox = new BoxPosition(rootOfCues);
             if (controlBarShown) {
               adjustContainerBox.height -= controlBarBox.height;
               adjustContainerBox.bottom += controlBarBox.height;
             }
             regionNodeBox = new RegionNodeBox(window, cue.region, adjustContainerBox);
             regionNodeBoxes[cue.region.id] = regionNodeBox;
           }
           // 6.1.14.3
@@ -1155,30 +1157,30 @@ XPCOMUtils.defineLazyPreferenceGetter(th
           if (cue.region.scroll == "up" && currentRegionNodeDiv.childElementCount > 0) {
             styleBox.div.style.transitionProperty = "top";
             styleBox.div.style.transitionDuration = "0.433s";
           }
 
           currentRegionNodeDiv.appendChild(styleBox.div);
           rootOfCues.appendChild(currentRegionNodeDiv);
           cue.displayState = styleBox.div;
-          boxPositions.push(BoxPosition.getSimpleBoxPosition(currentRegionBox));
+          boxPositions.push(new BoxPosition(currentRegionBox));
         } else {
           // Compute the intial position and styles of the cue div.
           styleBox = new CueStyleBox(window, cue, containerBox);
           rootOfCues.appendChild(styleBox.div);
 
           // Move the cue div to it's correct line position.
           moveBoxToLinePosition(window, styleBox, containerBox, boxPositions);
 
           // Remember the computed div so that we don't have to recompute it later
           // if we don't have too.
           cue.displayState = styleBox.div;
 
-          boxPositions.push(BoxPosition.getSimpleBoxPosition(styleBox));
+          boxPositions.push(new BoxPosition(styleBox));
         }
       }
     })();
   };
 
   WebVTT.Parser = function(window, decoder) {
     this.window = window;
     this.state = "INITIAL";