Bug 865407 - Part 8: Update vtt.js to latest version. r=rillian
authorRick Eyre <rick.eyre@hotmail.com>
Fri, 21 Feb 2014 22:11:06 -0500
changeset 170530 60bbb4d3ed09df9263be60f299b8020ad7a7f9da
parent 170529 ca9e7deea2fab6bd732b203730a544745ed3de7b
child 170531 e2cf0a2fa73714c3751c27a6637dd26c3b5e2418
push id270
push userpvanderbeken@mozilla.com
push dateThu, 06 Mar 2014 09:24:21 +0000
reviewersrillian
bugs865407
milestone30.0a1
Bug 865407 - Part 8: Update vtt.js to latest version. r=rillian This completes the initial implementation of the processing algorithm. The algorithm doesn't support vertical text yet, but that's not a big issue since Firefox doesn't support it.
content/media/webvtt/WebVTTParserWrapper.js
content/media/webvtt/vtt.jsm
--- a/content/media/webvtt/WebVTTParserWrapper.js
+++ b/content/media/webvtt/WebVTTParserWrapper.js
@@ -47,17 +47,17 @@ WebVTTParserWrapper.prototype =
 
   convertCueToDOMTree: function(window, cue)
   {
     return WebVTTParser.convertCueToDOMTree(window, cue.text);
   },
 
   processCues: function(window, cues, overlay)
   {
-    WebVTTParser.processCues(window, cues, null, overlay);
+    WebVTTParser.processCues(window, cues, overlay);
   },
 
   classDescription: "Wrapper for the JS WebVTTParser (vtt.js)",
   classID: Components.ID(WEBVTTPARSERWRAPPER_CID),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebVTTParserWrapper]),
   classInfo: XPCOMUtils.generateCI({
     classID:    WEBVTTPARSERWRAPPER_CID,
     contractID: WEBVTTPARSERWRAPPER_CONTRACTID,
--- a/content/media/webvtt/vtt.jsm
+++ b/content/media/webvtt/vtt.jsm
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 this.EXPORTED_SYMBOLS = ["WebVTTParser"];
 
 /**
  * Code below is vtt.js the JS WebVTTParser.
  * Current source code can be found at http://github.com/andreasgal/vtt.js
  *
- * Code taken from commit d819872e198d051dfcebcfb7ecf260462c9a9c6f
+ * Code taken from commit b812cd783d4284de1bc6b0349b7bda151052a1df
  */
 /**
  * Copyright 2013 vtt.js Contributors
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
@@ -96,30 +96,28 @@ this.EXPORTED_SYMBOLS = ["WebVTTParser"]
           this.set(k, v);
           break;
         }
       }
     },
     // Accept a setting if its a valid (signed) integer.
     integer: function(k, v) {
       if (/^-?\d+$/.test(v)) { // integer
-        this.set(k, parseInt(v, 10));
+        // Only take values in the range of -1000 ~ 1000
+        this.set(k, Math.min(Math.max(parseInt(v, 10), -1000), 1000));
       }
     },
     // Accept a setting if its a valid percentage.
-    percent: function(k, v, frac) {
+    percent: function(k, v) {
       var m;
       if ((m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/))) {
-        v = v.replace("%", "");
-        if (!m[2] || (m[2] && frac)) {
-          v = parseFloat(v);
-          if (v >= 0 && v <= 100) {
-            this.set(k, v);
-            return true;
-          }
+        v = parseFloat(v);
+        if (v >= 0 && v <= 100) {
+          this.set(k, v);
+          return true;
         }
       }
       return false;
     }
   };
 
   // Helper function to parse input into groups separated by 'groupDelim', and
   // interprete each group as a key/value pair separated by 'keyValueDelim'.
@@ -659,35 +657,69 @@ this.EXPORTED_SYMBOLS = ["WebVTTParser"]
     for (var i = 0; i < trackList.length && trackList[i] !== track; i++) {
       if (trackList[i].mode === "showing") {
         count++;
       }
     }
     return ++count * -1;
   }
 
-  function BoundingBox() {
+  function StyleBox() {
   }
 
-  BoundingBox.prototype.applyStyles = function(styles) {
-    var div = this.div;
+  // Apply styles to a div. If there is no div passed then it defaults to the
+  // div on 'this'.
+  StyleBox.prototype.applyStyles = function(styles, div) {
+    div = div || this.div;
     Object.keys(styles).forEach(function(style) {
       div.style[style] = styles[style];
     });
   };
 
-  BoundingBox.prototype.formatStyle = function(val, unit) {
+  StyleBox.prototype.formatStyle = function(val, unit) {
     return val === 0 ? 0 : val + unit;
   };
 
-  function BasicBoundingBox(window, cue) {
-    BoundingBox.call(this);
+  // Constructs the computed display state of the cue (a div). Places the div
+  // into the overlay which should be a block level element (usually a div).
+  function CueStyleBox(window, cue, styleOptions) {
+    StyleBox.call(this);
+    this.cue = cue;
 
-    // Parse our cue's text into a DOM tree rooted at 'div'.
-    this.div = parseContent(window, cue.text);
+    // Parse our cue's text into a DOM tree rooted at 'cueDiv'. This div will
+    // have inline positioning and will function as the cue background box.
+    this.cueDiv = parseContent(window, cue.text);
+    this.applyStyles({
+      color: "rgba(255, 255, 255, 1)",
+      backgroundColor: "rgba(0, 0, 0, 0.8)",
+      position: "relative",
+      left: 0,
+      right: 0,
+      top: 0,
+      bottom: 0,
+      display: "inline"
+    }, this.cueDiv);
+
+    // Create an absolutely positioned div that will be used to position the cue
+    // div. Note, all WebVTT cue-setting alignments are equivalent to the CSS
+    // mirrors of them except "middle" which is "center" in CSS.
+    this.div = window.document.createElement("div");
+    this.applyStyles({
+      textAlign: cue.align === "middle" ? "center" : cue.align,
+      direction: determineBidi(this.cueDiv),
+      writingMode: cue.vertical === "" ? "horizontal-tb"
+                                       : cue.vertical === "lr" ? "vertical-lr"
+                                                               : "vertical-rl",
+      unicodeBidi: "plaintext",
+      font: styleOptions.font,
+      whiteSpace: "pre-line",
+      position: "absolute"
+    });
+
+    this.div.appendChild(this.cueDiv);
 
     // Calculate the distance from the reference edge of the viewport to the text
     // position of the cue box. The reference edge will be resolved later when
     // the box orientation styles are applied.
     var textPos = 0;
     switch (cue.positionAlign) {
     case "start":
       textPos = cue.position;
@@ -713,159 +745,287 @@ this.EXPORTED_SYMBOLS = ["WebVTTParser"]
     // downwards from there.
     } else {
       this.applyStyles({
         top: this.formatStyle(textPos, "%"),
         height: this.formatStyle(cue.size, "%")
       });
     }
 
-    // All WebVTT cue-setting alignments are equivalent to the CSS mirrors of
-    // them except "middle" which is "center" in CSS.
-    this.applyStyles({
-      "textAlign": cue.align === "middle" ? "center" : cue.align
-    });
+    this.move = function(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"),
+      });
+    };
   }
-  BasicBoundingBox.prototype = Object.create(BoundingBox.prototype);
-  BasicBoundingBox.prototype.constructor = BasicBoundingBox;
+  CueStyleBox.prototype = Object.create(StyleBox.prototype);
+  CueStyleBox.prototype.constructor = CueStyleBox;
+
+  // 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) {
+    var self = this;
+
+    // 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;
+    if (obj.div) {
+      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;
+    }
+    this.left = obj.left;
+    this.right = obj.right;
+    this.top = obj.top;
+    this.height = obj.height;
+    this.bottom = obj.bottom;
+    this.width = obj.width;
+    this.lineHeight = lh !== undefined ? lh : obj.lineHeight;
+  }
 
-  const CUE_FONT_SIZE = 2.5;
-  const SCROLL_DURATION = 0.433;
-  const LINE_HEIGHT = 0.0533;
-  const REGION_FONT_SIZE = 1.3;
+  // Move the box along a particular axis. If no amount to move is passed, via
+  // the val parameter, then the default amount is the line height of the box.
+  BoxPosition.prototype.move = function(axis, val) {
+    val = val !== undefined ? val : this.lineHeight;
+    switch (axis) {
+    case "+x":
+      this.left += val;
+      this.right += val;
+      break;
+    case "-x":
+      this.left -= val;
+      this.right -= val;
+      break;
+    case "+y":
+      this.top += val;
+      this.bottom += val;
+      break;
+    case "-y":
+      this.top -= val;
+      this.bottom -= val;
+      break;
+    }
+  };
+
+  // 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;
+      }
+    }
+    return false;
+  };
 
-  // Constructs the computed display state of the cue (a div). Places the div
-  // into the overlay which should be a block level element (usually a div).
-  function CueBoundingBox(window, cue, overlay) {
-    BasicBoundingBox.call(this, window, cue);
-    this.applyStyles({
-      direction: determineBidi(this.div),
-      writingMode: cue.vertical === "" ? "horizontal-tb"
-                                       : cue.vertical === "lr" ? "vertical-lr"
-                                                               : "vertical-rl",
-      position: "absolute",
-      unicodeBidi: "plaintext",
-      fontSize: CUE_FONT_SIZE + "vh",
-      fontFamily: "sans-serif",
-      color: "rgba(255, 255, 255, 1)",
-      backgroundColor: "rgba(0, 0, 0, 0.8)",
-      whiteSpace: "pre-line"
-    });
+  // 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;
+  };
 
-    // Append the div to the overlay so we can get the computed styles of the
-    // element in order to position for overlap avoidance.
-    overlay.appendChild(this.div);
+  // 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;
+    }
+  };
+
+  // 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);
+  };
+
+  // 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
+    };
+  };
 
-    // Calculate the distance from the reference edge of the viewport to the line
-    // position of the cue box. The reference edge will be resolved later when
-    // the box orientation styles are applied. Default if snapToLines is not set
-    // is 85.
-    var linePos = 85;
-    if (!cue.snapToLines) {
-      var computedLinePos = computeLinePos(cue),
-          boxComputedStyle = window.getComputedStyle(this.div),
-          size = cue.vertical === "" ? boxComputedStyle.getPropertyValue("height") :
-                                       boxComputedStyle.getPropertyValue("width"),
-          // Get the percentage value of the computed height as getPropertyValue
-          // returns pixels.
-          overlayHeight = window.getComputedStyle(overlay).getPropertyValue("height"),
-          calculatedPercentage = (size.replace("px", "") / overlayHeight.replace("px", "")) * 100;
+  // Get an object that represents the box's position without anything extra.
+  // Can pass a StyleBox, HTMLElement, or another BoxPositon.
+  BoxPosition.getSimpleBoxPosition = function(obj) {
+    obj = obj.div ? obj.div.getBoundingClientRect() :
+                  obj.tagName ? obj.getBoundingClientRect() : obj;
+    return {
+      left: obj.left,
+      right: obj.right,
+      top: obj.top,
+      height: obj.height,
+      bottom: obj.bottom,
+      width: obj.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) {
 
-      switch (cue.lineAlign) {
-      case "start":
-        linePos = computedLinePos;
-        break;
-      case "middle":
-        linePos = computedLinePos - (calculatedPercentage / 2);
-        break;
-      case "end":
-        linePos = computedLinePos - calculatedPercentage;
-        break;
+    // 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:
+    // Passing ["+x", "-x"] will move the box first along the x axis in the positive
+    // direction. If it doesn't find a good position for it there it will then move
+    // it along the x axis in the negative direction.
+    function findBestPosition(b, axis) {
+      var bestPosition,
+          specifiedPosition = new BoxPosition(b),
+          percentage = 1; // Highest possible so the first thing we get is better.
+
+      for (var i = 0; i < axis.length; i++) {
+        while (b.overlapsOppositeAxis(containerBox, axis[i]) ||
+               (b.within(containerBox) && b.overlapsAny(boxPositions))) {
+          b.move(axis[i]);
+        }
+        // We found a spot where we aren't overlapping anything. This is our
+        // best position.
+        if (b.within(containerBox)) {
+          return b;
+        }
+        var p = b.intersectPercentage(containerBox);
+        // If we're outside the container box less then we were on our last try
+        // then remember this position as the best position.
+        if (percentage > p) {
+          bestPosition = new BoxPosition(b);
+          percentage = p;
+        }
+        // Reset the box position to the specified position.
+        b = new BoxPosition(specifiedPosition);
       }
+      return bestPosition || specifiedPosition;
     }
 
-    switch (cue.vertical) {
-    case "":
-      this.applyStyles({
-        top: this.formatStyle(linePos, "%")
+    function reverseAxis(axis) {
+      return axis.map(function(a) {
+        return a.indexOf("+") !== -1 ? a.replace("+", "-") : a.replace("-", "+");
       });
-      break;
-    case "rl":
-      this.applyStyles({
-        left: this.formatStyle(linePos, "%")
-      });
-      break;
-    case "lr":
-      this.applyStyles({
-        right: this.formatStyle(linePos, "%")
-      });
-      break;
     }
 
-    cue.displayState = this.div;
-  }
-  CueBoundingBox.prototype = Object.create(BasicBoundingBox.prototype);
-  CueBoundingBox.prototype.constuctor = CueBoundingBox;
-
-  function RegionBoundingBox(window, region) {
-    BoundingBox.call(this);
-    this.div = window.document.createElement("div");
-
-    var left = region.viewportAnchorX -
-               region.regionAnchorX * region.width / 100,
-        top = region.viewportAnchorY -
-              region.regionAnchorY * region.lines * LINE_HEIGHT / 100;
+    var boxPosition = new BoxPosition(styleBox),
+        cue = styleBox.cue,
+        linePos = computeLinePos(cue),
+        axis = [];
 
-    this.applyStyles({
-      position: "absolute",
-      writingMode: "horizontal-tb",
-      backgroundColor: "rgba(0, 0, 0, 0.8)",
-      wordWrap: "break-word",
-      overflowWrap: "break-word",
-      font: REGION_FONT_SIZE + "vh/" + LINE_HEIGHT + "vh sans-serif",
-      lineHeight: LINE_HEIGHT + "vh",
-      color: "rgba(255, 255, 255, 1)",
-      overflow: "hidden",
-      width: this.formatStyle(region.width, "%"),
-      minHeight: "0",
-      // TODO: This value is undefined in the spec, but I am assuming that they
-      // refer to lines * line height to get the max height See issue #107.
-      maxHeight: this.formatStyle(region.lines * LINE_HEIGHT, "px"),
-      left: this.formatStyle(left, "%"),
-      top: this.formatStyle(top, "%"),
-      display: "inline-flex",
-      flexFlow: "column",
-      justifyContent: "flex-end"
-    });
+    // If we have a line number to align the cue to.
+    if (cue.snapToLines) {
+      switch (cue.vertical) {
+      case "":
+        axis = [ "+y", "-y" ];
+        break;
+      case "rl":
+        axis = [ "+x", "-x" ];
+        break;
+      case "lr":
+        axis = [ "-x", "+x" ];
+        break;
+      }
 
-    this.maybeAddCue = function(cue) {
-      if (region.id !== cue.regionId) {
-        return false;
+      // If computed line position returns negative then line numbers are
+      // relative to the bottom of the video instead of the top. Therefore, we
+      // need to increase our initial position by the length or width of the
+      // video, depending on the writing direction, and reverse our axis directions.
+      var initialPosition = boxPosition.lineHeight * Math.floor(linePos + 0.5),
+          initialAxis = axis[0];
+      if (linePos < 0) {
+        initialPosition += cue.vertical === "" ? containerBox.height : containerBox.width;
+        axis = reverseAxis(axis);
       }
 
-      var basicBox = new BasicBoundingBox(window, cue);
-      basicBox.applyStyles({
-        position: "relative",
-        unicodeBidi: "plaintext",
-        width: "auto"
-      });
+      // Move the box to the specified position. This may not be its best
+      // position.
+      boxPosition.move(initialAxis, initialPosition);
+
+    } else {
+      // If we have a percentage line value for the cue.
+      var calculatedPercentage = (boxPosition.lineHeight / containerBox.height) * 100;
 
-      if (this.div.childNodes.length === 1 && region.scroll === "up") {
-        this.applyStyles({
-          transitionProperty: "top",
-          transitionDuration: SCROLL_DURATION + "s"
-        });
+      switch (cue.lineAlign) {
+      case "middle":
+        linePos -= (calculatedPercentage / 2);
+        break;
+      case "end":
+        linePos -= calculatedPercentage;
+        break;
       }
 
-      this.div.appendChild(basicBox.div);
-      return true;
-    };
+      // Apply initial line position to the cue box.
+      switch (cue.vertical) {
+      case "":
+        styleBox.applyStyles({
+          top: styleBox.formatStyle(linePos, "%")
+        });
+        break;
+      case "rl":
+        styleBox.applyStyles({
+          left: styleBox.formatStyle(linePos, "%")
+        });
+        break;
+      case "lr":
+        styleBox.applyStyles({
+          right: styleBox.formatStyle(linePos, "%")
+        });
+        break;
+      }
+
+      axis = [ "+y", "-x", "+x", "-y" ];
+
+      // Get the box position again after we've applied the specified positioning
+      // to it.
+      boxPosition = new BoxPosition(styleBox);
+    }
+
+    var bestPosition = findBestPosition(boxPosition, axis);
+    styleBox.move(bestPosition.toCSSCompatValues(containerBox));
   }
-  RegionBoundingBox.prototype = Object.create(BoundingBox.prototype);
-  RegionBoundingBox.prototype.constructor = RegionBoundingBox;
 
   function WebVTTParser(window, decoder) {
     this.window = window;
     this.state = "INITIAL";
     this.buffer = "";
     this.decoder = decoder || new TextDecoder("utf8");
   }
 
@@ -886,55 +1046,83 @@ this.EXPORTED_SYMBOLS = ["WebVTTParser"]
 
   WebVTTParser.convertCueToDOMTree = function(window, cuetext) {
     if (!window || !cuetext) {
       return null;
     }
     return parseContent(window, cuetext);
   };
 
+  const FONT_SIZE_PERCENT = 0.05;
+  const FONT_STYLE = "sans-serif";
+  const CUE_BACKGROUND_PADDING = "1.5%";
+
   // Runs the processing model over the cues and regions passed to it.
   // @param overlay A block level element (usually a div) that the computed cues
   //                and regions will be placed into.
-  WebVTTParser.processCues = function(window, cues, regions, overlay) {
+  WebVTTParser.processCues = function(window, cues, overlay) {
     if (!window || !cues || !overlay) {
       return null;
     }
 
     // Remove all previous children.
     while (overlay.firstChild) {
       overlay.removeChild(overlay.firstChild);
     }
 
-    var regionBoxes = regions ? regions.map(function(region) {
-      return new RegionBoundingBox(window, region);
-    }) : null;
+    var paddedOverlay = window.document.createElement("div");
+    paddedOverlay.style.position = "absolute";
+    paddedOverlay.style.left = "0";
+    paddedOverlay.style.right = "0";
+    paddedOverlay.style.top = "0";
+    paddedOverlay.style.bottom = "0";
+    paddedOverlay.style.margin = CUE_BACKGROUND_PADDING;
+    overlay.appendChild(paddedOverlay);
 
-    function mapCueToRegion(cue) {
-      for (var i = 0; i < regionBoxes.length; i++) {
-        if (regionBoxes[i].maybeAddCue(cue)) {
+    // Determine if we need to compute the display states of the cues. This could
+    // be the case if a cue's state has been changed since the last computation or
+    // if it has not been computed yet.
+    function shouldCompute(cues) {
+      for (var i = 0; i < cues.length; i++) {
+        if (cues[i].hasBeenReset || !cues[i].displayState) {
           return true;
         }
       }
       return false;
     }
 
-    for (var i = 0; i < cues.length; i++) {
-      // Check to see if this cue is contained within a VTTRegion.
-      if (regionBoxes && mapCueToRegion(cues[i])) {
-        continue;
-      }
-      // Check to see if we can just reuse the last computed styles of the cue.
-      if (cues[i].hasBeenReset !== true && cues[i].displayState) {
-        overlay.appendChild(cues[i].displayState);
-        continue;
-      }
-      // Compute the position of the cue box on the cue overlay.
-      var cueBox = new CueBoundingBox(window, cues[i], overlay);
+    // We don't need to recompute the cues' display states. Just reuse them.
+    if (!shouldCompute(cues)) {
+      cues.forEach(function(cue) {
+        paddedOverlay.appendChild(cue.displayState);
+      });
+      return;
     }
+
+    var boxPositions = [],
+        containerBox = BoxPosition.getSimpleBoxPosition(paddedOverlay),
+        fontSize = Math.round(containerBox.height * FONT_SIZE_PERCENT * 100) / 100;
+    var styleOptions = {
+      font: fontSize + "px " + FONT_STYLE
+    };
+
+    cues.forEach(function(cue) {
+      // Compute the intial position and styles of the cue div.
+      var styleBox = new CueStyleBox(window, cue, styleOptions);
+      paddedOverlay.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));
+    });
   };
 
   WebVTTParser.prototype = {
     parse: function (data) {
       var self = this;
 
       // If there is no data then we won't decode it, but will just try to parse
       // whatever is in buffer already. This may occur in circumstances, for
@@ -967,32 +1155,32 @@ this.EXPORTED_SYMBOLS = ["WebVTTParser"]
         var settings = new Settings();
 
         parseOptions(input, function (k, v) {
           switch (k) {
           case "id":
             settings.set(k, v);
             break;
           case "width":
-            settings.percent(k, v, true);
+            settings.percent(k, v);
             break;
           case "lines":
             settings.integer(k, v);
             break;
           case "regionanchor":
           case "viewportanchor":
             var xy = v.split(',');
             if (xy.length !== 2) {
               break;
             }
             // We have to make sure both x and y parse, so use a temporary
             // settings object here.
             var anchor = new Settings();
-            anchor.percent("x", xy[0], true);
-            anchor.percent("y", xy[1], true);
+            anchor.percent("x", xy[0]);
+            anchor.percent("y", xy[1]);
             if (!anchor.has("x") || !anchor.has("y")) {
               break;
             }
             settings.set(k + "X", anchor.get("x"));
             settings.set(k + "Y", anchor.get("y"));
             break;
           case "scroll":
             settings.alt(k, v, ["up"]);