Bug 865407 - Part 7: Update vtt.js to latest version. r=rillan
☠☠ backed out by 803536e9aac2 ☠ ☠
authorRick Eyre <rick.eyre@hotmail.com>
Tue, 17 Dec 2013 10:36:26 -0500
changeset 162958 d3e7f639267094b2ea372e7094a3a6c806a653a2
parent 162957 7ba94f08b7a51d2f0e617815c0cf54448ffb9e0a
child 162959 3f4308d223cf7c350c240f96f67c0680ae829799
push id25975
push userryanvm@gmail.com
push dateFri, 10 Jan 2014 19:46:47 +0000
treeherderautoland@e89afc241513 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrillan
bugs865407
milestone29.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 865407 - Part 7: Update vtt.js to latest version. r=rillan We now call WebVTTParser.processCues() on the vtt.jsm module in order to get the computed divs of the cues.
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)
   {
-    // TODO: Call prcoess cues on vtt.js
+    WebVTTParser.processCues(window, cues, null, 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,31 +3,38 @@
  * 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 355f375b6cf04763dcb1843d5683a7c489846425
+ * Code taken from commit 33a837b1ceef138a61a3b2f6fac90d5c70bd90d9
  */
+(function(global) {
 
-(function(global) {
+  function ParsingError(message) {
+    this.name = "ParsingError";
+    this.message = message || "";
+  }
+  ParsingError.prototype = Object.create(Error.prototype);
+  ParsingError.prototype.constructor = ParsingError;
 
   // Try to parse input as a time stamp.
   function parseTimeStamp(input) {
 
     function computeSeconds(h, m, s, f) {
       return (h | 0) * 3600 + (m | 0) * 60 + (s | 0) + (f | 0) / 1000;
     }
 
     var m = input.match(/^(\d+):(\d{2})(:\d{2})?\.(\d{3})/);
-    if (!m)
+    if (!m) {
       return null;
+    }
 
     if (m[3]) {
       // Timestamp takes the form of [hours]:[minutes]:[seconds].[milliseconds]
       return computeSeconds(m[1], m[2], m[3].replace(":", ""), m[4]);
     } else if (m[1] > 59) {
       // Timestamp takes the form of [hours]:[minutes].[milliseconds]
       // First position is hours as it's over 59.
       return computeSeconds(m[1], m[2], 0,  m[4]);
@@ -41,46 +48,49 @@ this.EXPORTED_SYMBOLS = ["WebVTTParser"]
   // assignment to a specific key.
   function Settings() {
     this.values = Object.create(null);
   }
 
   Settings.prototype = {
     // Only accept the first assignment to any key.
     set: function(k, v) {
-      if (!this.get(k) && v !== "")
+      if (!this.get(k) && v !== "") {
         this.values[k] = v;
+      }
     },
     // Return the value for a key, or a default value.
-    get: function(k, dflt) {
+    // If 'defaultKey' is passed then 'dflt' is assumed to be an object with
+    // a number of possible default values as properties where 'defaultKey' is
+    // the key of the property that will be chosen; otherwise it's assumed to be
+    // a single value.
+    get: function(k, dflt, defaultKey) {
+      if (defaultKey) {
+        return this.has(k) ? this.values[k] : dflt[defaultKey];
+      }
       return this.has(k) ? this.values[k] : dflt;
     },
     // Check whether we have a value for a key.
     has: function(k) {
       return k in this.values;
     },
     // Accept a setting if its one of the given alternatives.
     alt: function(k, v, a) {
       for (var n = 0; n < a.length; ++n) {
         if (v === a[n]) {
           this.set(k, v);
           break;
         }
       }
     },
-    // Accept a region if it doesn't have the special string '-->'
-    region: function(k, v) {
-      if (!v.match(/-->/)) {
-        this.set(k, v);
-      }
-    },
     // Accept a setting if its a valid (signed) integer.
     integer: function(k, v) {
-      if (/^-?\d+$/.test(v)) // integer
+      if (/^-?\d+$/.test(v)) { // integer
         this.set(k, parseInt(v, 10));
+      }
     },
     // Accept a setting if its a valid percentage.
     percent: function(k, v, frac) {
       var m;
       if ((m = v.match(/^([\d]{1,3})(\.[\d]*)?%$/))) {
         v = v.replace("%", "");
         if (!m[2] || (m[2] && frac)) {
           v = parseFloat(v);
@@ -94,83 +104,114 @@ this.EXPORTED_SYMBOLS = ["WebVTTParser"]
     }
   };
 
   // Helper function to parse input into groups separated by 'groupDelim', and
   // interprete each group as a key/value pair separated by 'keyValueDelim'.
   function parseOptions(input, callback, keyValueDelim, groupDelim) {
     var groups = groupDelim ? input.split(groupDelim) : [input];
     for (var i in groups) {
+      if (typeof groups[i] !== "string") {
+        continue;
+      }
       var kv = groups[i].split(keyValueDelim);
-      if (kv.length !== 2)
+      if (kv.length !== 2) {
         continue;
+      }
       var k = kv[0];
       var v = kv[1];
       callback(k, v);
     }
   }
 
   function parseCue(input, cue) {
     // 4.1 WebVTT timestamp
     function consumeTimeStamp() {
       var ts = parseTimeStamp(input);
-      if (ts === null)
-        throw "error";
+      if (ts === null) {
+        throw new ParsingError("Malformed time stamp.");
+      }
       // Remove time stamp from input.
       input = input.replace(/^[^\s-]+/, "");
       return ts;
     }
 
     // 4.4.2 WebVTT cue settings
     function consumeCueSettings(input, cue) {
       var settings = new Settings();
 
       parseOptions(input, function (k, v) {
         switch (k) {
         case "region":
-          settings.region(k, v);
+          settings.set(k, v);
           break;
         case "vertical":
           settings.alt(k, v, ["rl", "lr"]);
           break;
         case "line":
-          settings.integer(k, v);
-          settings.percent(k, v) ? settings.set("snapToLines", false) : null;
-          settings.alt(k, v, ["auto"]);
+          var vals = v.split(","),
+              vals0 = vals[0];
+          settings.integer(k, vals0);
+          settings.percent(k, vals0) ? settings.set("snapToLines", false) : null;
+          settings.alt(k, vals0, ["auto"]);
+          if (vals.length === 2) {
+            settings.alt("lineAlign", vals[1], ["start", "middle", "end"]);
+          }
           break;
         case "position":
+          vals = v.split(",");
+          settings.percent(k, vals[0]);
+          if (vals.length === 2) {
+            settings.alt("positionAlign", vals[1], ["start", "middle", "end"]);
+          }
+          break;
         case "size":
           settings.percent(k, v);
           break;
         case "align":
           settings.alt(k, v, ["start", "middle", "end", "left", "right"]);
           break;
         }
       }, /:/, /\s/);
 
       // Apply default values for any missing fields.
       cue.regionId = settings.get("region", "");
       cue.vertical = settings.get("vertical", "");
       cue.line = settings.get("line", "auto");
+      cue.lineAlign = settings.get("lineAlign", "start");
       cue.snapToLines = settings.get("snapToLines", true);
-      cue.position = settings.get("position", 50);
       cue.size = settings.get("size", 100);
       cue.align = settings.get("align", "middle");
+      cue.position = settings.get("position", {
+        start: 0,
+        left: 0,
+        middle: 50,
+        end: 100,
+        right: 100
+      }, cue.align);
+      cue.positionAlign = settings.get("positionAlign", {
+        start: "start",
+        left: "start",
+        middle: "middle",
+        end: "end",
+        right: "end"
+      }, cue.align);
     }
 
     function skipWhitespace() {
       input = input.replace(/^\s+/, "");
     }
 
     // 4.1 WebVTT cue timings.
     skipWhitespace();
     cue.startTime = consumeTimeStamp();   // (1) collect cue start time
     skipWhitespace();
-    if (input.substr(0, 3) !== "-->")     // (3) next characters must match "-->"
-      throw "error";
+    if (input.substr(0, 3) !== "-->") {     // (3) next characters must match "-->"
+      throw new ParsingError("Malformed time stamp (time stamps must be separated by '-->').");
+    }
     input = input.substr(3);
     skipWhitespace();
     cue.endTime = consumeTimeStamp();     // (5) collect cue end time
 
     // 4.1 WebVTT cue settings list.
     skipWhitespace();
     consumeCueSettings(input, cue);
   }
@@ -203,18 +244,19 @@ this.EXPORTED_SYMBOLS = ["WebVTTParser"]
   const NEEDS_PARENT = {
     rt: "ruby"
   };
 
   // Parse content into a document fragment.
   function parseContent(window, input) {
     function nextToken() {
       // Check for end-of-string.
-      if (!input)
+      if (!input) {
         return null;
+      }
 
       // Consume 'n' characters from the input.
       function consume(result) {
         input = input.substr(result.length);
         return result;
       }
 
       var m = input.match(/^([^<]*)(<[^>]+>?)?/);
@@ -223,36 +265,39 @@ this.EXPORTED_SYMBOLS = ["WebVTTParser"]
       return consume(m[1] ? m[1] : m[2]);
     }
 
     // Unescape a string 's'.
     function unescape1(e) {
       return ESCAPE[e];
     }
     function unescape(s) {
-      while ((m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/)))
+      while ((m = s.match(/&(amp|lt|gt|lrm|rlm|nbsp);/))) {
         s = s.replace(m[0], unescape1);
+      }
       return s;
     }
 
     function shouldAdd(current, element) {
       return !NEEDS_PARENT[element.localName] ||
              NEEDS_PARENT[element.localName] === current.localName;
     }
 
     // Create an element for this tag.
     function createElement(type, annotation) {
       var tagName = TAG_NAME[type];
-      if (!tagName)
+      if (!tagName) {
         return null;
+      }
       var element = window.document.createElement(tagName);
       element.localName = tagName;
       var name = TAG_ANNOTATION[type];
-      if (name && annotation)
+      if (name && annotation) {
         element[name] = annotation.trim();
+      }
       return element;
     }
 
     var rootDiv = window.document.createElement("div"),
         current = rootDiv,
         t,
         tagStack = [];
 
@@ -267,261 +312,667 @@ this.EXPORTED_SYMBOLS = ["WebVTTParser"]
           }
           // Otherwise just ignore the end tag.
           continue;
         }
         var ts = parseTimeStamp(t.substr(1, t.length - 2));
         var node;
         if (ts) {
           // Timestamps are lead nodes as well.
-          node = window.ProcessingInstruction();
-          node.target = "timestamp";
-          node.data = ts;
+          node = window.document.createProcessingInstruction("timestamp", ts);
           current.appendChild(node);
           continue;
         }
         var m = t.match(/^<([^.\s/0-9>]+)(\.[^\s\\>]+)?([^>\\]+)?(\\?)>?$/);
         // If we can't parse the tag, skip to the next tag.
-        if (!m)
+        if (!m) {
           continue;
+        }
         // Try to construct an element, and ignore the tag if we couldn't.
         node = createElement(m[1], m[3]);
-        if (!node)
+        if (!node) {
           continue;
+        }
         // Determine if the tag should be added based on the context of where it
         // is placed in the cuetext.
-        if (!shouldAdd(current, node))
+        if (!shouldAdd(current, node)) {
           continue;
+        }
         // Set the class list (as a list of classes, separated by space).
-        if (m[2])
+        if (m[2]) {
           node.className = m[2].substr(1).replace('.', ' ');
+        }
         // Append the node to the current node, and enter the scope of the new
         // node.
         tagStack.push(m[1]);
         current.appendChild(node);
         current = node;
         continue;
       }
 
       // Text nodes are leaf nodes.
       current.appendChild(window.document.createTextNode(unescape(t)));
     }
 
     return rootDiv;
   }
 
+  // This is a list of all the Unicode characters that have a strong
+  // right-to-left category. What this means is that these characters are
+  // written right-to-left for sure. It was generated by pulling all the strong
+  // right-to-left characters out of the Unicode data table. That table can
+  // found at: http://www.unicode.org/Public/UNIDATA/UnicodeData.txt
+  var strongRTLChars = [0x05BE, 0x05C0, 0x05C3, 0x05C6, 0x05D0, 0x05D1,
+      0x05D2, 0x05D3, 0x05D4, 0x05D5, 0x05D6, 0x05D7, 0x05D8, 0x05D9, 0x05DA,
+      0x05DB, 0x05DC, 0x05DD, 0x05DE, 0x05DF, 0x05E0, 0x05E1, 0x05E2, 0x05E3,
+      0x05E4, 0x05E5, 0x05E6, 0x05E7, 0x05E8, 0x05E9, 0x05EA, 0x05F0, 0x05F1,
+      0x05F2, 0x05F3, 0x05F4, 0x0608, 0x060B, 0x060D, 0x061B, 0x061E, 0x061F,
+      0x0620, 0x0621, 0x0622, 0x0623, 0x0624, 0x0625, 0x0626, 0x0627, 0x0628,
+      0x0629, 0x062A, 0x062B, 0x062C, 0x062D, 0x062E, 0x062F, 0x0630, 0x0631,
+      0x0632, 0x0633, 0x0634, 0x0635, 0x0636, 0x0637, 0x0638, 0x0639, 0x063A,
+      0x063B, 0x063C, 0x063D, 0x063E, 0x063F, 0x0640, 0x0641, 0x0642, 0x0643,
+      0x0644, 0x0645, 0x0646, 0x0647, 0x0648, 0x0649, 0x064A, 0x066D, 0x066E,
+      0x066F, 0x0671, 0x0672, 0x0673, 0x0674, 0x0675, 0x0676, 0x0677, 0x0678,
+      0x0679, 0x067A, 0x067B, 0x067C, 0x067D, 0x067E, 0x067F, 0x0680, 0x0681,
+      0x0682, 0x0683, 0x0684, 0x0685, 0x0686, 0x0687, 0x0688, 0x0689, 0x068A,
+      0x068B, 0x068C, 0x068D, 0x068E, 0x068F, 0x0690, 0x0691, 0x0692, 0x0693,
+      0x0694, 0x0695, 0x0696, 0x0697, 0x0698, 0x0699, 0x069A, 0x069B, 0x069C,
+      0x069D, 0x069E, 0x069F, 0x06A0, 0x06A1, 0x06A2, 0x06A3, 0x06A4, 0x06A5,
+      0x06A6, 0x06A7, 0x06A8, 0x06A9, 0x06AA, 0x06AB, 0x06AC, 0x06AD, 0x06AE,
+      0x06AF, 0x06B0, 0x06B1, 0x06B2, 0x06B3, 0x06B4, 0x06B5, 0x06B6, 0x06B7,
+      0x06B8, 0x06B9, 0x06BA, 0x06BB, 0x06BC, 0x06BD, 0x06BE, 0x06BF, 0x06C0,
+      0x06C1, 0x06C2, 0x06C3, 0x06C4, 0x06C5, 0x06C6, 0x06C7, 0x06C8, 0x06C9,
+      0x06CA, 0x06CB, 0x06CC, 0x06CD, 0x06CE, 0x06CF, 0x06D0, 0x06D1, 0x06D2,
+      0x06D3, 0x06D4, 0x06D5, 0x06E5, 0x06E6, 0x06EE, 0x06EF, 0x06FA, 0x06FB,
+      0x06FC, 0x06FD, 0x06FE, 0x06FF, 0x0700, 0x0701, 0x0702, 0x0703, 0x0704,
+      0x0705, 0x0706, 0x0707, 0x0708, 0x0709, 0x070A, 0x070B, 0x070C, 0x070D,
+      0x070F, 0x0710, 0x0712, 0x0713, 0x0714, 0x0715, 0x0716, 0x0717, 0x0718,
+      0x0719, 0x071A, 0x071B, 0x071C, 0x071D, 0x071E, 0x071F, 0x0720, 0x0721,
+      0x0722, 0x0723, 0x0724, 0x0725, 0x0726, 0x0727, 0x0728, 0x0729, 0x072A,
+      0x072B, 0x072C, 0x072D, 0x072E, 0x072F, 0x074D, 0x074E, 0x074F, 0x0750,
+      0x0751, 0x0752, 0x0753, 0x0754, 0x0755, 0x0756, 0x0757, 0x0758, 0x0759,
+      0x075A, 0x075B, 0x075C, 0x075D, 0x075E, 0x075F, 0x0760, 0x0761, 0x0762,
+      0x0763, 0x0764, 0x0765, 0x0766, 0x0767, 0x0768, 0x0769, 0x076A, 0x076B,
+      0x076C, 0x076D, 0x076E, 0x076F, 0x0770, 0x0771, 0x0772, 0x0773, 0x0774,
+      0x0775, 0x0776, 0x0777, 0x0778, 0x0779, 0x077A, 0x077B, 0x077C, 0x077D,
+      0x077E, 0x077F, 0x0780, 0x0781, 0x0782, 0x0783, 0x0784, 0x0785, 0x0786,
+      0x0787, 0x0788, 0x0789, 0x078A, 0x078B, 0x078C, 0x078D, 0x078E, 0x078F,
+      0x0790, 0x0791, 0x0792, 0x0793, 0x0794, 0x0795, 0x0796, 0x0797, 0x0798,
+      0x0799, 0x079A, 0x079B, 0x079C, 0x079D, 0x079E, 0x079F, 0x07A0, 0x07A1,
+      0x07A2, 0x07A3, 0x07A4, 0x07A5, 0x07B1, 0x07C0, 0x07C1, 0x07C2, 0x07C3,
+      0x07C4, 0x07C5, 0x07C6, 0x07C7, 0x07C8, 0x07C9, 0x07CA, 0x07CB, 0x07CC,
+      0x07CD, 0x07CE, 0x07CF, 0x07D0, 0x07D1, 0x07D2, 0x07D3, 0x07D4, 0x07D5,
+      0x07D6, 0x07D7, 0x07D8, 0x07D9, 0x07DA, 0x07DB, 0x07DC, 0x07DD, 0x07DE,
+      0x07DF, 0x07E0, 0x07E1, 0x07E2, 0x07E3, 0x07E4, 0x07E5, 0x07E6, 0x07E7,
+      0x07E8, 0x07E9, 0x07EA, 0x07F4, 0x07F5, 0x07FA, 0x0800, 0x0801, 0x0802,
+      0x0803, 0x0804, 0x0805, 0x0806, 0x0807, 0x0808, 0x0809, 0x080A, 0x080B,
+      0x080C, 0x080D, 0x080E, 0x080F, 0x0810, 0x0811, 0x0812, 0x0813, 0x0814,
+      0x0815, 0x081A, 0x0824, 0x0828, 0x0830, 0x0831, 0x0832, 0x0833, 0x0834,
+      0x0835, 0x0836, 0x0837, 0x0838, 0x0839, 0x083A, 0x083B, 0x083C, 0x083D,
+      0x083E, 0x0840, 0x0841, 0x0842, 0x0843, 0x0844, 0x0845, 0x0846, 0x0847,
+      0x0848, 0x0849, 0x084A, 0x084B, 0x084C, 0x084D, 0x084E, 0x084F, 0x0850,
+      0x0851, 0x0852, 0x0853, 0x0854, 0x0855, 0x0856, 0x0857, 0x0858, 0x085E,
+      0x08A0, 0x08A2, 0x08A3, 0x08A4, 0x08A5, 0x08A6, 0x08A7, 0x08A8, 0x08A9,
+      0x08AA, 0x08AB, 0x08AC, 0x200F, 0xFB1D, 0xFB1F, 0xFB20, 0xFB21, 0xFB22,
+      0xFB23, 0xFB24, 0xFB25, 0xFB26, 0xFB27, 0xFB28, 0xFB2A, 0xFB2B, 0xFB2C,
+      0xFB2D, 0xFB2E, 0xFB2F, 0xFB30, 0xFB31, 0xFB32, 0xFB33, 0xFB34, 0xFB35,
+      0xFB36, 0xFB38, 0xFB39, 0xFB3A, 0xFB3B, 0xFB3C, 0xFB3E, 0xFB40, 0xFB41,
+      0xFB43, 0xFB44, 0xFB46, 0xFB47, 0xFB48, 0xFB49, 0xFB4A, 0xFB4B, 0xFB4C,
+      0xFB4D, 0xFB4E, 0xFB4F, 0xFB50, 0xFB51, 0xFB52, 0xFB53, 0xFB54, 0xFB55,
+      0xFB56, 0xFB57, 0xFB58, 0xFB59, 0xFB5A, 0xFB5B, 0xFB5C, 0xFB5D, 0xFB5E,
+      0xFB5F, 0xFB60, 0xFB61, 0xFB62, 0xFB63, 0xFB64, 0xFB65, 0xFB66, 0xFB67,
+      0xFB68, 0xFB69, 0xFB6A, 0xFB6B, 0xFB6C, 0xFB6D, 0xFB6E, 0xFB6F, 0xFB70,
+      0xFB71, 0xFB72, 0xFB73, 0xFB74, 0xFB75, 0xFB76, 0xFB77, 0xFB78, 0xFB79,
+      0xFB7A, 0xFB7B, 0xFB7C, 0xFB7D, 0xFB7E, 0xFB7F, 0xFB80, 0xFB81, 0xFB82,
+      0xFB83, 0xFB84, 0xFB85, 0xFB86, 0xFB87, 0xFB88, 0xFB89, 0xFB8A, 0xFB8B,
+      0xFB8C, 0xFB8D, 0xFB8E, 0xFB8F, 0xFB90, 0xFB91, 0xFB92, 0xFB93, 0xFB94,
+      0xFB95, 0xFB96, 0xFB97, 0xFB98, 0xFB99, 0xFB9A, 0xFB9B, 0xFB9C, 0xFB9D,
+      0xFB9E, 0xFB9F, 0xFBA0, 0xFBA1, 0xFBA2, 0xFBA3, 0xFBA4, 0xFBA5, 0xFBA6,
+      0xFBA7, 0xFBA8, 0xFBA9, 0xFBAA, 0xFBAB, 0xFBAC, 0xFBAD, 0xFBAE, 0xFBAF,
+      0xFBB0, 0xFBB1, 0xFBB2, 0xFBB3, 0xFBB4, 0xFBB5, 0xFBB6, 0xFBB7, 0xFBB8,
+      0xFBB9, 0xFBBA, 0xFBBB, 0xFBBC, 0xFBBD, 0xFBBE, 0xFBBF, 0xFBC0, 0xFBC1,
+      0xFBD3, 0xFBD4, 0xFBD5, 0xFBD6, 0xFBD7, 0xFBD8, 0xFBD9, 0xFBDA, 0xFBDB,
+      0xFBDC, 0xFBDD, 0xFBDE, 0xFBDF, 0xFBE0, 0xFBE1, 0xFBE2, 0xFBE3, 0xFBE4,
+      0xFBE5, 0xFBE6, 0xFBE7, 0xFBE8, 0xFBE9, 0xFBEA, 0xFBEB, 0xFBEC, 0xFBED,
+      0xFBEE, 0xFBEF, 0xFBF0, 0xFBF1, 0xFBF2, 0xFBF3, 0xFBF4, 0xFBF5, 0xFBF6,
+      0xFBF7, 0xFBF8, 0xFBF9, 0xFBFA, 0xFBFB, 0xFBFC, 0xFBFD, 0xFBFE, 0xFBFF,
+      0xFC00, 0xFC01, 0xFC02, 0xFC03, 0xFC04, 0xFC05, 0xFC06, 0xFC07, 0xFC08,
+      0xFC09, 0xFC0A, 0xFC0B, 0xFC0C, 0xFC0D, 0xFC0E, 0xFC0F, 0xFC10, 0xFC11,
+      0xFC12, 0xFC13, 0xFC14, 0xFC15, 0xFC16, 0xFC17, 0xFC18, 0xFC19, 0xFC1A,
+      0xFC1B, 0xFC1C, 0xFC1D, 0xFC1E, 0xFC1F, 0xFC20, 0xFC21, 0xFC22, 0xFC23,
+      0xFC24, 0xFC25, 0xFC26, 0xFC27, 0xFC28, 0xFC29, 0xFC2A, 0xFC2B, 0xFC2C,
+      0xFC2D, 0xFC2E, 0xFC2F, 0xFC30, 0xFC31, 0xFC32, 0xFC33, 0xFC34, 0xFC35,
+      0xFC36, 0xFC37, 0xFC38, 0xFC39, 0xFC3A, 0xFC3B, 0xFC3C, 0xFC3D, 0xFC3E,
+      0xFC3F, 0xFC40, 0xFC41, 0xFC42, 0xFC43, 0xFC44, 0xFC45, 0xFC46, 0xFC47,
+      0xFC48, 0xFC49, 0xFC4A, 0xFC4B, 0xFC4C, 0xFC4D, 0xFC4E, 0xFC4F, 0xFC50,
+      0xFC51, 0xFC52, 0xFC53, 0xFC54, 0xFC55, 0xFC56, 0xFC57, 0xFC58, 0xFC59,
+      0xFC5A, 0xFC5B, 0xFC5C, 0xFC5D, 0xFC5E, 0xFC5F, 0xFC60, 0xFC61, 0xFC62,
+      0xFC63, 0xFC64, 0xFC65, 0xFC66, 0xFC67, 0xFC68, 0xFC69, 0xFC6A, 0xFC6B,
+      0xFC6C, 0xFC6D, 0xFC6E, 0xFC6F, 0xFC70, 0xFC71, 0xFC72, 0xFC73, 0xFC74,
+      0xFC75, 0xFC76, 0xFC77, 0xFC78, 0xFC79, 0xFC7A, 0xFC7B, 0xFC7C, 0xFC7D,
+      0xFC7E, 0xFC7F, 0xFC80, 0xFC81, 0xFC82, 0xFC83, 0xFC84, 0xFC85, 0xFC86,
+      0xFC87, 0xFC88, 0xFC89, 0xFC8A, 0xFC8B, 0xFC8C, 0xFC8D, 0xFC8E, 0xFC8F,
+      0xFC90, 0xFC91, 0xFC92, 0xFC93, 0xFC94, 0xFC95, 0xFC96, 0xFC97, 0xFC98,
+      0xFC99, 0xFC9A, 0xFC9B, 0xFC9C, 0xFC9D, 0xFC9E, 0xFC9F, 0xFCA0, 0xFCA1,
+      0xFCA2, 0xFCA3, 0xFCA4, 0xFCA5, 0xFCA6, 0xFCA7, 0xFCA8, 0xFCA9, 0xFCAA,
+      0xFCAB, 0xFCAC, 0xFCAD, 0xFCAE, 0xFCAF, 0xFCB0, 0xFCB1, 0xFCB2, 0xFCB3,
+      0xFCB4, 0xFCB5, 0xFCB6, 0xFCB7, 0xFCB8, 0xFCB9, 0xFCBA, 0xFCBB, 0xFCBC,
+      0xFCBD, 0xFCBE, 0xFCBF, 0xFCC0, 0xFCC1, 0xFCC2, 0xFCC3, 0xFCC4, 0xFCC5,
+      0xFCC6, 0xFCC7, 0xFCC8, 0xFCC9, 0xFCCA, 0xFCCB, 0xFCCC, 0xFCCD, 0xFCCE,
+      0xFCCF, 0xFCD0, 0xFCD1, 0xFCD2, 0xFCD3, 0xFCD4, 0xFCD5, 0xFCD6, 0xFCD7,
+      0xFCD8, 0xFCD9, 0xFCDA, 0xFCDB, 0xFCDC, 0xFCDD, 0xFCDE, 0xFCDF, 0xFCE0,
+      0xFCE1, 0xFCE2, 0xFCE3, 0xFCE4, 0xFCE5, 0xFCE6, 0xFCE7, 0xFCE8, 0xFCE9,
+      0xFCEA, 0xFCEB, 0xFCEC, 0xFCED, 0xFCEE, 0xFCEF, 0xFCF0, 0xFCF1, 0xFCF2,
+      0xFCF3, 0xFCF4, 0xFCF5, 0xFCF6, 0xFCF7, 0xFCF8, 0xFCF9, 0xFCFA, 0xFCFB,
+      0xFCFC, 0xFCFD, 0xFCFE, 0xFCFF, 0xFD00, 0xFD01, 0xFD02, 0xFD03, 0xFD04,
+      0xFD05, 0xFD06, 0xFD07, 0xFD08, 0xFD09, 0xFD0A, 0xFD0B, 0xFD0C, 0xFD0D,
+      0xFD0E, 0xFD0F, 0xFD10, 0xFD11, 0xFD12, 0xFD13, 0xFD14, 0xFD15, 0xFD16,
+      0xFD17, 0xFD18, 0xFD19, 0xFD1A, 0xFD1B, 0xFD1C, 0xFD1D, 0xFD1E, 0xFD1F,
+      0xFD20, 0xFD21, 0xFD22, 0xFD23, 0xFD24, 0xFD25, 0xFD26, 0xFD27, 0xFD28,
+      0xFD29, 0xFD2A, 0xFD2B, 0xFD2C, 0xFD2D, 0xFD2E, 0xFD2F, 0xFD30, 0xFD31,
+      0xFD32, 0xFD33, 0xFD34, 0xFD35, 0xFD36, 0xFD37, 0xFD38, 0xFD39, 0xFD3A,
+      0xFD3B, 0xFD3C, 0xFD3D, 0xFD50, 0xFD51, 0xFD52, 0xFD53, 0xFD54, 0xFD55,
+      0xFD56, 0xFD57, 0xFD58, 0xFD59, 0xFD5A, 0xFD5B, 0xFD5C, 0xFD5D, 0xFD5E,
+      0xFD5F, 0xFD60, 0xFD61, 0xFD62, 0xFD63, 0xFD64, 0xFD65, 0xFD66, 0xFD67,
+      0xFD68, 0xFD69, 0xFD6A, 0xFD6B, 0xFD6C, 0xFD6D, 0xFD6E, 0xFD6F, 0xFD70,
+      0xFD71, 0xFD72, 0xFD73, 0xFD74, 0xFD75, 0xFD76, 0xFD77, 0xFD78, 0xFD79,
+      0xFD7A, 0xFD7B, 0xFD7C, 0xFD7D, 0xFD7E, 0xFD7F, 0xFD80, 0xFD81, 0xFD82,
+      0xFD83, 0xFD84, 0xFD85, 0xFD86, 0xFD87, 0xFD88, 0xFD89, 0xFD8A, 0xFD8B,
+      0xFD8C, 0xFD8D, 0xFD8E, 0xFD8F, 0xFD92, 0xFD93, 0xFD94, 0xFD95, 0xFD96,
+      0xFD97, 0xFD98, 0xFD99, 0xFD9A, 0xFD9B, 0xFD9C, 0xFD9D, 0xFD9E, 0xFD9F,
+      0xFDA0, 0xFDA1, 0xFDA2, 0xFDA3, 0xFDA4, 0xFDA5, 0xFDA6, 0xFDA7, 0xFDA8,
+      0xFDA9, 0xFDAA, 0xFDAB, 0xFDAC, 0xFDAD, 0xFDAE, 0xFDAF, 0xFDB0, 0xFDB1,
+      0xFDB2, 0xFDB3, 0xFDB4, 0xFDB5, 0xFDB6, 0xFDB7, 0xFDB8, 0xFDB9, 0xFDBA,
+      0xFDBB, 0xFDBC, 0xFDBD, 0xFDBE, 0xFDBF, 0xFDC0, 0xFDC1, 0xFDC2, 0xFDC3,
+      0xFDC4, 0xFDC5, 0xFDC6, 0xFDC7, 0xFDF0, 0xFDF1, 0xFDF2, 0xFDF3, 0xFDF4,
+      0xFDF5, 0xFDF6, 0xFDF7, 0xFDF8, 0xFDF9, 0xFDFA, 0xFDFB, 0xFDFC, 0xFE70,
+      0xFE71, 0xFE72, 0xFE73, 0xFE74, 0xFE76, 0xFE77, 0xFE78, 0xFE79, 0xFE7A,
+      0xFE7B, 0xFE7C, 0xFE7D, 0xFE7E, 0xFE7F, 0xFE80, 0xFE81, 0xFE82, 0xFE83,
+      0xFE84, 0xFE85, 0xFE86, 0xFE87, 0xFE88, 0xFE89, 0xFE8A, 0xFE8B, 0xFE8C,
+      0xFE8D, 0xFE8E, 0xFE8F, 0xFE90, 0xFE91, 0xFE92, 0xFE93, 0xFE94, 0xFE95,
+      0xFE96, 0xFE97, 0xFE98, 0xFE99, 0xFE9A, 0xFE9B, 0xFE9C, 0xFE9D, 0xFE9E,
+      0xFE9F, 0xFEA0, 0xFEA1, 0xFEA2, 0xFEA3, 0xFEA4, 0xFEA5, 0xFEA6, 0xFEA7,
+      0xFEA8, 0xFEA9, 0xFEAA, 0xFEAB, 0xFEAC, 0xFEAD, 0xFEAE, 0xFEAF, 0xFEB0,
+      0xFEB1, 0xFEB2, 0xFEB3, 0xFEB4, 0xFEB5, 0xFEB6, 0xFEB7, 0xFEB8, 0xFEB9,
+      0xFEBA, 0xFEBB, 0xFEBC, 0xFEBD, 0xFEBE, 0xFEBF, 0xFEC0, 0xFEC1, 0xFEC2,
+      0xFEC3, 0xFEC4, 0xFEC5, 0xFEC6, 0xFEC7, 0xFEC8, 0xFEC9, 0xFECA, 0xFECB,
+      0xFECC, 0xFECD, 0xFECE, 0xFECF, 0xFED0, 0xFED1, 0xFED2, 0xFED3, 0xFED4,
+      0xFED5, 0xFED6, 0xFED7, 0xFED8, 0xFED9, 0xFEDA, 0xFEDB, 0xFEDC, 0xFEDD,
+      0xFEDE, 0xFEDF, 0xFEE0, 0xFEE1, 0xFEE2, 0xFEE3, 0xFEE4, 0xFEE5, 0xFEE6,
+      0xFEE7, 0xFEE8, 0xFEE9, 0xFEEA, 0xFEEB, 0xFEEC, 0xFEED, 0xFEEE, 0xFEEF,
+      0xFEF0, 0xFEF1, 0xFEF2, 0xFEF3, 0xFEF4, 0xFEF5, 0xFEF6, 0xFEF7, 0xFEF8,
+      0xFEF9, 0xFEFA, 0xFEFB, 0xFEFC, 0x10800, 0x10801, 0x10802, 0x10803,
+      0x10804, 0x10805, 0x10808, 0x1080A, 0x1080B, 0x1080C, 0x1080D, 0x1080E,
+      0x1080F, 0x10810, 0x10811, 0x10812, 0x10813, 0x10814, 0x10815, 0x10816,
+      0x10817, 0x10818, 0x10819, 0x1081A, 0x1081B, 0x1081C, 0x1081D, 0x1081E,
+      0x1081F, 0x10820, 0x10821, 0x10822, 0x10823, 0x10824, 0x10825, 0x10826,
+      0x10827, 0x10828, 0x10829, 0x1082A, 0x1082B, 0x1082C, 0x1082D, 0x1082E,
+      0x1082F, 0x10830, 0x10831, 0x10832, 0x10833, 0x10834, 0x10835, 0x10837,
+      0x10838, 0x1083C, 0x1083F, 0x10840, 0x10841, 0x10842, 0x10843, 0x10844,
+      0x10845, 0x10846, 0x10847, 0x10848, 0x10849, 0x1084A, 0x1084B, 0x1084C,
+      0x1084D, 0x1084E, 0x1084F, 0x10850, 0x10851, 0x10852, 0x10853, 0x10854,
+      0x10855, 0x10857, 0x10858, 0x10859, 0x1085A, 0x1085B, 0x1085C, 0x1085D,
+      0x1085E, 0x1085F, 0x10900, 0x10901, 0x10902, 0x10903, 0x10904, 0x10905,
+      0x10906, 0x10907, 0x10908, 0x10909, 0x1090A, 0x1090B, 0x1090C, 0x1090D,
+      0x1090E, 0x1090F, 0x10910, 0x10911, 0x10912, 0x10913, 0x10914, 0x10915,
+      0x10916, 0x10917, 0x10918, 0x10919, 0x1091A, 0x1091B, 0x10920, 0x10921,
+      0x10922, 0x10923, 0x10924, 0x10925, 0x10926, 0x10927, 0x10928, 0x10929,
+      0x1092A, 0x1092B, 0x1092C, 0x1092D, 0x1092E, 0x1092F, 0x10930, 0x10931,
+      0x10932, 0x10933, 0x10934, 0x10935, 0x10936, 0x10937, 0x10938, 0x10939,
+      0x1093F, 0x10980, 0x10981, 0x10982, 0x10983, 0x10984, 0x10985, 0x10986,
+      0x10987, 0x10988, 0x10989, 0x1098A, 0x1098B, 0x1098C, 0x1098D, 0x1098E,
+      0x1098F, 0x10990, 0x10991, 0x10992, 0x10993, 0x10994, 0x10995, 0x10996,
+      0x10997, 0x10998, 0x10999, 0x1099A, 0x1099B, 0x1099C, 0x1099D, 0x1099E,
+      0x1099F, 0x109A0, 0x109A1, 0x109A2, 0x109A3, 0x109A4, 0x109A5, 0x109A6,
+      0x109A7, 0x109A8, 0x109A9, 0x109AA, 0x109AB, 0x109AC, 0x109AD, 0x109AE,
+      0x109AF, 0x109B0, 0x109B1, 0x109B2, 0x109B3, 0x109B4, 0x109B5, 0x109B6,
+      0x109B7, 0x109BE, 0x109BF, 0x10A00, 0x10A10, 0x10A11, 0x10A12, 0x10A13,
+      0x10A15, 0x10A16, 0x10A17, 0x10A19, 0x10A1A, 0x10A1B, 0x10A1C, 0x10A1D,
+      0x10A1E, 0x10A1F, 0x10A20, 0x10A21, 0x10A22, 0x10A23, 0x10A24, 0x10A25,
+      0x10A26, 0x10A27, 0x10A28, 0x10A29, 0x10A2A, 0x10A2B, 0x10A2C, 0x10A2D,
+      0x10A2E, 0x10A2F, 0x10A30, 0x10A31, 0x10A32, 0x10A33, 0x10A40, 0x10A41,
+      0x10A42, 0x10A43, 0x10A44, 0x10A45, 0x10A46, 0x10A47, 0x10A50, 0x10A51,
+      0x10A52, 0x10A53, 0x10A54, 0x10A55, 0x10A56, 0x10A57, 0x10A58, 0x10A60,
+      0x10A61, 0x10A62, 0x10A63, 0x10A64, 0x10A65, 0x10A66, 0x10A67, 0x10A68,
+      0x10A69, 0x10A6A, 0x10A6B, 0x10A6C, 0x10A6D, 0x10A6E, 0x10A6F, 0x10A70,
+      0x10A71, 0x10A72, 0x10A73, 0x10A74, 0x10A75, 0x10A76, 0x10A77, 0x10A78,
+      0x10A79, 0x10A7A, 0x10A7B, 0x10A7C, 0x10A7D, 0x10A7E, 0x10A7F, 0x10B00,
+      0x10B01, 0x10B02, 0x10B03, 0x10B04, 0x10B05, 0x10B06, 0x10B07, 0x10B08,
+      0x10B09, 0x10B0A, 0x10B0B, 0x10B0C, 0x10B0D, 0x10B0E, 0x10B0F, 0x10B10,
+      0x10B11, 0x10B12, 0x10B13, 0x10B14, 0x10B15, 0x10B16, 0x10B17, 0x10B18,
+      0x10B19, 0x10B1A, 0x10B1B, 0x10B1C, 0x10B1D, 0x10B1E, 0x10B1F, 0x10B20,
+      0x10B21, 0x10B22, 0x10B23, 0x10B24, 0x10B25, 0x10B26, 0x10B27, 0x10B28,
+      0x10B29, 0x10B2A, 0x10B2B, 0x10B2C, 0x10B2D, 0x10B2E, 0x10B2F, 0x10B30,
+      0x10B31, 0x10B32, 0x10B33, 0x10B34, 0x10B35, 0x10B40, 0x10B41, 0x10B42,
+      0x10B43, 0x10B44, 0x10B45, 0x10B46, 0x10B47, 0x10B48, 0x10B49, 0x10B4A,
+      0x10B4B, 0x10B4C, 0x10B4D, 0x10B4E, 0x10B4F, 0x10B50, 0x10B51, 0x10B52,
+      0x10B53, 0x10B54, 0x10B55, 0x10B58, 0x10B59, 0x10B5A, 0x10B5B, 0x10B5C,
+      0x10B5D, 0x10B5E, 0x10B5F, 0x10B60, 0x10B61, 0x10B62, 0x10B63, 0x10B64,
+      0x10B65, 0x10B66, 0x10B67, 0x10B68, 0x10B69, 0x10B6A, 0x10B6B, 0x10B6C,
+      0x10B6D, 0x10B6E, 0x10B6F, 0x10B70, 0x10B71, 0x10B72, 0x10B78, 0x10B79,
+      0x10B7A, 0x10B7B, 0x10B7C, 0x10B7D, 0x10B7E, 0x10B7F, 0x10C00, 0x10C01,
+      0x10C02, 0x10C03, 0x10C04, 0x10C05, 0x10C06, 0x10C07, 0x10C08, 0x10C09,
+      0x10C0A, 0x10C0B, 0x10C0C, 0x10C0D, 0x10C0E, 0x10C0F, 0x10C10, 0x10C11,
+      0x10C12, 0x10C13, 0x10C14, 0x10C15, 0x10C16, 0x10C17, 0x10C18, 0x10C19,
+      0x10C1A, 0x10C1B, 0x10C1C, 0x10C1D, 0x10C1E, 0x10C1F, 0x10C20, 0x10C21,
+      0x10C22, 0x10C23, 0x10C24, 0x10C25, 0x10C26, 0x10C27, 0x10C28, 0x10C29,
+      0x10C2A, 0x10C2B, 0x10C2C, 0x10C2D, 0x10C2E, 0x10C2F, 0x10C30, 0x10C31,
+      0x10C32, 0x10C33, 0x10C34, 0x10C35, 0x10C36, 0x10C37, 0x10C38, 0x10C39,
+      0x10C3A, 0x10C3B, 0x10C3C, 0x10C3D, 0x10C3E, 0x10C3F, 0x10C40, 0x10C41,
+      0x10C42, 0x10C43, 0x10C44, 0x10C45, 0x10C46, 0x10C47, 0x10C48, 0x1EE00,
+      0x1EE01, 0x1EE02, 0x1EE03, 0x1EE05, 0x1EE06, 0x1EE07, 0x1EE08, 0x1EE09,
+      0x1EE0A, 0x1EE0B, 0x1EE0C, 0x1EE0D, 0x1EE0E, 0x1EE0F, 0x1EE10, 0x1EE11,
+      0x1EE12, 0x1EE13, 0x1EE14, 0x1EE15, 0x1EE16, 0x1EE17, 0x1EE18, 0x1EE19,
+      0x1EE1A, 0x1EE1B, 0x1EE1C, 0x1EE1D, 0x1EE1E, 0x1EE1F, 0x1EE21, 0x1EE22,
+      0x1EE24, 0x1EE27, 0x1EE29, 0x1EE2A, 0x1EE2B, 0x1EE2C, 0x1EE2D, 0x1EE2E,
+      0x1EE2F, 0x1EE30, 0x1EE31, 0x1EE32, 0x1EE34, 0x1EE35, 0x1EE36, 0x1EE37,
+      0x1EE39, 0x1EE3B, 0x1EE42, 0x1EE47, 0x1EE49, 0x1EE4B, 0x1EE4D, 0x1EE4E,
+      0x1EE4F, 0x1EE51, 0x1EE52, 0x1EE54, 0x1EE57, 0x1EE59, 0x1EE5B, 0x1EE5D,
+      0x1EE5F, 0x1EE61, 0x1EE62, 0x1EE64, 0x1EE67, 0x1EE68, 0x1EE69, 0x1EE6A,
+      0x1EE6C, 0x1EE6D, 0x1EE6E, 0x1EE6F, 0x1EE70, 0x1EE71, 0x1EE72, 0x1EE74,
+      0x1EE75, 0x1EE76, 0x1EE77, 0x1EE79, 0x1EE7A, 0x1EE7B, 0x1EE7C, 0x1EE7E,
+      0x1EE80, 0x1EE81, 0x1EE82, 0x1EE83, 0x1EE84, 0x1EE85, 0x1EE86, 0x1EE87,
+      0x1EE88, 0x1EE89, 0x1EE8B, 0x1EE8C, 0x1EE8D, 0x1EE8E, 0x1EE8F, 0x1EE90,
+      0x1EE91, 0x1EE92, 0x1EE93, 0x1EE94, 0x1EE95, 0x1EE96, 0x1EE97, 0x1EE98,
+      0x1EE99, 0x1EE9A, 0x1EE9B, 0x1EEA1, 0x1EEA2, 0x1EEA3, 0x1EEA5, 0x1EEA6,
+      0x1EEA7, 0x1EEA8, 0x1EEA9, 0x1EEAB, 0x1EEAC, 0x1EEAD, 0x1EEAE, 0x1EEAF,
+      0x1EEB0, 0x1EEB1, 0x1EEB2, 0x1EEB3, 0x1EEB4, 0x1EEB5, 0x1EEB6, 0x1EEB7,
+      0x1EEB8, 0x1EEB9, 0x1EEBA, 0x1EEBB, 0x10FFFD];
+
+  function determineBidi(cueDiv) {
+    var nodeStack = [],
+        text = "";
+
+    if (!cueDiv || !cueDiv.childNodes) {
+      return "ltr";
+    }
+
+    function pushNodes(nodeStack, node) {
+      for (var i = node.childNodes.length - 1; i >= 0; i--) {
+        nodeStack.push(node.childNodes[i]);
+      }
+    }
+
+    function nextTextNode(nodeStack) {
+      if (!nodeStack || !nodeStack.length) {
+        return null;
+      }
+
+      var node = nodeStack.pop();
+      if (node.textContent) {
+        // TODO: This should match all unicode type B characters (paragraph
+        // separator characters). See issue #115.
+        var m = node.textContent.match(/^.*(\n|\r)/);
+        if (m) {
+          nodeStack.length = 0;
+          return m[0];
+        }
+        return node.textContent;
+      }
+      if (node.tagName === "ruby") {
+        return nextTextNode(nodeStack);
+      }
+      if (node.childNodes) {
+        pushNodes(nodeStack, node);
+        return nextTextNode(nodeStack);
+      }
+    }
+
+    pushNodes(nodeStack, cueDiv);
+    while ((text = nextTextNode(nodeStack))) {
+      for (var i = 0; i < text.length; i++) {
+        if (strongRTLChars.indexOf(text.charCodeAt(i)) !== -1) {
+          return "rtl";
+        }
+      }
+    }
+    return "ltr";
+  }
+
   function computeLinePos(cue) {
     if (typeof cue.line === "number" &&
-        (cue.snapToLines || (cue.line >= 0 && cue.line <= 100)))
+        (cue.snapToLines || (cue.line >= 0 && cue.line <= 100))) {
       return cue.line;
-    if (!cue.track)
+    }
+    if (!cue.track) {
       return -1;
+    }
     // TODO: Have to figure out a way to determine what the position of the
     // Track is in the Media element's list of TextTracks and return that + 1,
     // negated.
     return 100;
   }
 
-  function CueBoundingBox(cue) {
-    // TODO: Apply unicode bidi algorithm and assign the result to 'direction'
-    this.direction = "ltr";
+  function BoundingBox() {
+  }
+
+  BoundingBox.prototype.applyStyles = function(styles) {
+    var div = this.div;
+    Object.keys(styles).forEach(function(style) {
+      div.style[style] = styles[style];
+    });
+  };
+
+  BoundingBox.prototype.formatStyle = function(val, unit) {
+    return val === 0 ? 0 : val + unit;
+  };
+
+  function BasicBoundingBox(window, cue) {
+    BoundingBox.call(this);
+
+    // Parse our cue's text into a DOM tree rooted at 'div'.
+    this.div = parseContent(window, cue.text);
 
-    var boxLen = (function(direction){
-      var maxLen = 0;
-      if ((cue.vertical === "" &&
-          (cue.align === "left" ||
-           (cue.align === "start" && direction === "ltr") ||
-           (cue.align === "end" && direction === "rtl"))) ||
-         ((cue.vertical === "rl" || cue.vertical === "lr") &&
-          (cue.align === "start" || cue.align === "left")))
-        maxLen = 100 - cue.position;
-      else if ((cue.vertical === "" &&
-                (cue.align === "right" ||
-                 (cue.align === "end" && direction === "ltr") ||
-                 (cue.align === "start" && direction === "rtl"))) ||
-               ((cue.vertical === "rl" || cue.vertical === "lr") &&
-                 (cue.align === "end" || cue.align === "right")))
-        maxLen = cue.position;
-      else if (cue.align === "middle") {
-        if (cue.position <= 50)
-          maxLen = cue.position * 2;
-        else
-          maxLen = (100 - cue.position) * 2;
-      }
-      return cue.size < maxLen ? cue.size : maxLen;
-    }(this.direction));
+    // 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;
+      break;
+    case "middle":
+      textPos = cue.position - (cue.size / 2);
+      break;
+    case "end":
+      textPos = cue.position - cue.size;
+      break;
+    }
+
+    // Horizontal box orientation; textPos is the distance from the left edge of the
+    // area to the left edge of the box and cue.size is the distance extending to
+    // the right from there.
+    if (cue.vertical === "") {
+      this.applyStyles({
+        left:  this.formatStyle(textPos, "%"),
+        width: this.formatStyle(cue.size, "%"),
+      });
+    // Vertical box orientation; textPos is the distance from the top edge of the
+    // area to the top edge of the box and cue.size is the height extending
+    // downwards from there.
+    } else {
+      this.applyStyles({
+        top: this.formatStyle(textPos, "%"),
+        height: this.formatStyle(cue.size, "%")
+      });
+    }
 
-    this.left = (function(direction) {
-      if (cue.vertical === "") {
-        if (direction === "ltr") {
-          if (cue.align === "start" || cue.align === "left")
-            return cue.position;
-          else if (cue.align === "end" || cue.align === "right")
-            return cue.position - boxLen;
-          else if (cue.align === "middle")
-            return cue.position - (boxLen / 2);
-        } else if (direction === "rtl") {
-          if (cue.align === "end" || cue.align === "left")
-            return 100 - cue.position;
-          else if (cue.align === "start" || cue.align === "right")
-            return 100 - cue.position - boxLen;
-          else if (cue.align === "middle")
-            return 100 - cue.position - (boxLen / 2);
-        }
-      }
-      return cue.snapToLines ? 0 : computeLinePos(cue);
-    }(this.direction));
+    // 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
+    });
+  }
+  BasicBoundingBox.prototype = Object.create(BoundingBox.prototype);
+  BasicBoundingBox.prototype.constructor = BasicBoundingBox;
+
+  const CUE_FONT_SIZE = 2.5;
+  const SCROLL_DURATION = 0.433;
+  const LINE_HEIGHT = 0.0533;
+  const REGION_FONT_SIZE = 1.3;
 
-    this.top = (function() {
-      if (cue.vertical === "rl" || cue.vertical === "lr") {
-        if (cue.align === "start" || cue.align === "left")
-          return cue.position;
-        else if (cue.align === "end" || cue.align === "right")
-          return cue.position - boxLen;
-        else if (cue.align === "middle")
-          return cue.position - (boxLen / 2);
-      }
-      return cue.snapToLines ? 0 : computeLinePos(cue);
-    }());
+  // 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"
+    });
+
+    // 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);
 
-    // Apply a margin to the edges of the bounding box. The margin is user agent
-    // defined and is expressed as a percentage of the containing box's width.
-    var edgeMargin = 10;
-    if (cue.snapToLines) {
-      if (cue.vertical === "") {
-        if (this.left < edgeMargin && this.left + boxLen > edgeMargin) {
-          this.left += edgeMargin;
-          boxLen -= edgeMargin;
-        }
-        var rightMargin = 100 - edgeMargin;
-        if (this.left < rightMargin && this.left + boxLen > rightMargin)
-          boxLen -= edgeMargin;
-      } else if (cue.vertical === "lr" || cue.vertical === "rl") {
-        if (this.top < edgeMargin && this.top + boxLen > edgeMargin) {
-          this.top += edgeMargin;
-          boxLen -= edgeMargin;
-        }
-        var bottomMargin = 100 - edgeMargin;
-        if (this.top < bottomMargin && this.top + boxLen > bottomMargin)
-          boxLen -= edgeMargin;
+    // 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;
+
+      switch (cue.lineAlign) {
+      case "start":
+        linePos = computedLinePos;
+        break;
+      case "middle":
+        linePos = computedLinePos - (calculatedPercentage / 2);
+        break;
+      case "end":
+        linePos = computedLinePos - calculatedPercentage;
+        break;
       }
     }
 
-    this.height = cue.vertical === "" ? "auto" : boxLen;
-    this.width = cue.vertical === "" ? boxLen : "auto";
+    switch (cue.vertical) {
+    case "":
+      this.applyStyles({
+        top: this.formatStyle(linePos, "%")
+      });
+      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;
 
-    this.writingMode = cue.vertical === "" ?
-                       "horizontal-tb" :
-                       cue.vertical === "lr" ? "vertical-lr" : "vertical-rl";
-    this.position = "absolute";
-    this.unicodeBidi = "plaintext";
-    this.textAlign = cue.align === "middle" ? "center" : cue.align;
-    this.font = "5vh sans-serif";
-    this.color = "rgba(255,255,255,1)";
-    this.whiteSpace = "pre-line";
+    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"
+    });
+
+    this.maybeAddCue = function(cue) {
+      if (region.id !== cue.regionId) {
+        return false;
+      }
+
+      var basicBox = new BasicBoundingBox(window, cue);
+      basicBox.applyStyles({
+        position: "relative",
+        unicodeBidi: "plaintext",
+        width: "auto"
+      });
+
+      if (this.div.childNodes.length === 1 && region.scroll === "up") {
+        this.applyStyles({
+          transitionProperty: "top",
+          transitionDuration: SCROLL_DURATION + "s"
+        });
+      }
+
+      this.div.appendChild(basicBox.div);
+      return true;
+    };
   }
-
-  const WEBVTT = "WEBVTT";
+  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 || TextDecoder("utf8");
+    this.decoder = decoder || new TextDecoder("utf8");
   }
 
   // Helper to allow strings to be decoded instead of the default binary utf8 data.
   WebVTTParser.StringDecoder = function() {
     return {
       decode: function(data) {
-        if (!data) return "";
-        if (typeof data !== "string") throw "[StringDecoder] Error - expected string data";
-
+        if (!data) {
+          return "";
+        }
+        if (typeof data !== "string") {
+          throw new Error("Error - expected string data.");
+        }
         return decodeURIComponent(escape(data));
       }
     };
   };
 
   WebVTTParser.convertCueToDOMTree = function(window, cuetext) {
-    if (!window || !cuetext)
+    if (!window || !cuetext) {
       return null;
+    }
     return parseContent(window, cuetext);
   };
 
-  WebVTTParser.processCues = function(window, cues) {
-    if (!window || !cues)
+  // 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) {
+    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;
 
-    return cues.map(function(cue) {
-      var div = parseContent(window, cue.text);
-      div.style = new CueBoundingBox(cue);
-      // TODO: Adjust divs based on other cues already processed.
-      // TODO: Account for regions.
-      return div;
-    });
+    function mapCueToRegion(cue) {
+      for (var i = 0; i < regionBoxes.length; i++) {
+        if (regionBoxes[i].maybeAddCue(cue)) {
+          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);
+    }
   };
 
   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
       // example when flush() is called.
       if (data) {
         // Try to decode the data that we received.
         self.buffer += self.decoder.decode(data, {stream: true});
       }
 
-      // Advance tells whether or not to remove the collected line from the buffer
-      // after it is read.
-      function collectNextLine(advance) {
+      function collectNextLine() {
         var buffer = self.buffer;
         var pos = 0;
-        advance = typeof advance === "undefined" ? true : advance;
-        while (pos < buffer.length && buffer[pos] != '\r' && buffer[pos] != '\n')
+        while (pos < buffer.length && buffer[pos] !== '\r' && buffer[pos] !== '\n') {
           ++pos;
+        }
         var line = buffer.substr(0, pos);
         // Advance the buffer early in case we fail below.
-        if (buffer[pos] === '\r')
+        if (buffer[pos] === '\r') {
           ++pos;
-        if (buffer[pos] === '\n')
+        }
+        if (buffer[pos] === '\n') {
           ++pos;
-        if (advance)
-          self.buffer = buffer.substr(pos);
+        }
+        self.buffer = buffer.substr(pos);
         return line;
       }
 
       // 3.4 WebVTT region and WebVTT region settings syntax
       function parseRegion(input) {
         var settings = new Settings();
 
         parseOptions(input, function (k, v) {
           switch (k) {
           case "id":
-            settings.region(k, v);
+            settings.set(k, v);
             break;
           case "width":
             settings.percent(k, v, true);
             break;
           case "lines":
             settings.integer(k, v);
             break;
           case "regionanchor":
           case "viewportanchor":
             var xy = v.split(',');
-            if (xy.length !== 2)
+            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);
-            if (!anchor.has("x") || !anchor.has("y"))
+            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"]);
             break;
           }
         }, /=/, /\s/);
@@ -532,17 +983,17 @@ this.EXPORTED_SYMBOLS = ["WebVTTParser"]
           var region = new self.window.VTTRegion();
           region.id = settings.get("id");
           region.width = settings.get("width", 100);
           region.lines = settings.get("lines", 3);
           region.regionAnchorX = settings.get("regionanchorX", 0);
           region.regionAnchorY = settings.get("regionanchorY", 100);
           region.viewportAnchorX = settings.get("viewportanchorX", 0);
           region.viewportAnchorY = settings.get("viewportanchorY", 100);
-          region.scroll = settings.get("scroll", "none");
+          region.scroll = settings.get("scroll", "");
           self.onregion(region);
         }
       }
 
       // 3.2 WebVTT metadata header syntax
       function parseHeader(input) {
         parseOptions(input, function (k, v) {
           switch (k) {
@@ -553,43 +1004,34 @@ this.EXPORTED_SYMBOLS = ["WebVTTParser"]
           }
         }, /:/);
       }
 
       // 5.1 WebVTT file parsing.
       try {
         var line;
         if (self.state === "INITIAL") {
-          // Wait until we have enough data to parse the header.
-          if (self.buffer.length <= WEBVTT.length)
+          // We can't start parsing until we have the first line.
+          if (!/\r\n|\n/.test(self.buffer)) {
             return this;
+          }
 
-          // Collect the next line, but do not remove the collected line from the
-          // buffer as we may not have the full WEBVTT signature yet when
-          // incrementally parsing.
-          line = collectNextLine(false);
-          // (4-12) - Check for the "WEBVTT" identifier followed by an optional space or tab,
-          // and ignore the rest of the line.
-          if (line.substr(0, WEBVTT.length) !== WEBVTT ||
-              line.length > WEBVTT.length && !/[ \t]/.test(line[WEBVTT.length])) {
-            throw "error";
+          line = collectNextLine();
+
+          var m = line.match(/^WEBVTT([ \t].*)?$/);
+          if (!m || !m[0]) {
+            throw new ParsingError("Malformed WebVTT signature.");
           }
-          // Now that we've read the WEBVTT signature we can remove it from
-          // the buffer.
-          collectNextLine(true);
+
           self.state = "HEADER";
         }
 
         while (self.buffer) {
           // We can't parse a line until we have the full line.
-          if (!/[\r\n]/.test(self.buffer)) {
-            // If we are in the midst of parsing a cue, report it early. We will report it
-            // again when updates come in.
-            if (self.state === "CUETEXT" && self.cue && self.onpartialcue)
-              self.onpartialcue(self.cue);
+          if (!/\r\n|\n/.test(self.buffer)) {
             return this;
           }
 
           line = collectNextLine();
 
           switch (self.state) {
           case "HEADER":
             // 13-18 - Allow a header (metadata) under the WEBVTT line.
@@ -597,79 +1039,90 @@ this.EXPORTED_SYMBOLS = ["WebVTTParser"]
               parseHeader(line);
             } else if (!line) {
               // An empty line terminates the header and starts the body (cues).
               self.state = "ID";
             }
             continue;
           case "NOTE":
             // Ignore NOTE blocks.
-            if (!line)
+            if (!line) {
               self.state = "ID";
+            }
             continue;
           case "ID":
             // Check for the start of NOTE blocks.
             if (/^NOTE($|[ \t])/.test(line)) {
               self.state = "NOTE";
               break;
             }
             // 19-29 - Allow any number of line terminators, then initialize new cue values.
-            if (!line)
+            if (!line) {
               continue;
+            }
             self.cue = new self.window.VTTCue(0, 0, "");
             self.state = "CUE";
             // 30-39 - Check if self line contains an optional identifier or timing data.
-            if (line.indexOf("-->") == -1) {
+            if (line.indexOf("-->") === -1) {
               self.cue.id = line;
               continue;
             }
             // Process line as start of a cue.
             /*falls through*/
           case "CUE":
             // 40 - Collect cue timings and settings.
             try {
               parseCue(line, self.cue);
             } catch (e) {
+              // If it's not a parsing error then throw it to the consumer.
+              if (!(e instanceof ParsingError)) {
+                throw e;
+              }
               // In case of an error ignore rest of the cue.
               self.cue = null;
               self.state = "BADCUE";
               continue;
             }
             self.state = "CUETEXT";
             continue;
           case "CUETEXT":
             // 41-53 - Collect the cue text, create a cue, and add it to the output.
             if (!line) {
               // We are done parsing self cue.
               self.oncue && self.oncue(self.cue);
               self.cue = null;
               self.state = "ID";
               continue;
             }
-            if (self.cue.text)
+            if (self.cue.text) {
               self.cue.text += "\n";
+            }
             self.cue.text += line;
             continue;
-          default: // BADCUE
+          case "BADCUE": // BADCUE
             // 54-62 - Collect and discard the remaining cue.
             if (!line) {
               self.state = "ID";
             }
             continue;
           }
         }
       } catch (e) {
+        // If it's not a parsing error then throw it to the consumer.
+        if (!(e instanceof ParsingError)) {
+          throw e;
+        }
         // If we are currently parsing a cue, report what we have, and then the error.
-        if (self.state === "CUETEXT" && self.cue && self.oncue)
+        if (self.state === "CUETEXT" && self.cue && self.oncue) {
           self.oncue(self.cue);
+        }
         self.cue = null;
-        // Report the error and enter the BADCUE state, except if we haven't even made
-        // it through the header yet.
-        if (self.state !== "INITIAL")
-          self.state = "BADCUE";
+        // Enter BADWEBVTT state if header was not parsed correctly otherwise
+        // another exception occurred so enter BADCUE state.
+        self.state = self.state === "INITIAL" ? "BADWEBVTT" : "BADCUE";
       }
       return this;
     },
     flush: function () {
       var self = this;
       // Finish decoding the stream.
       self.buffer += self.decoder.decode();
       // Synthesize the end of the current cue or region.