Merge m-c to b-i
authorPhil Ringnalda <philringnalda@gmail.com>
Sat, 07 Dec 2013 08:28:10 -0800
changeset 174127 4b6889b0418d62053e24f41f372fe626b774cc7d
parent 174126 b7eab78d2f2c4154dd28b34b77a0c40eb8b932ac (current diff)
parent 174069 393f409b98ecb0b45f17c0c8cd224aa70184d2c6 (diff)
child 174128 b59fb194a3aef4aad12c44a02dab86d88fcadfb5
push id3224
push userlsblakk@mozilla.com
push dateTue, 04 Feb 2014 01:06:49 +0000
treeherdermozilla-beta@60c04d0987f1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.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
Merge m-c to b-i
dom/media/MediaManager.cpp
--- a/accessible/src/base/nsAccessibilityService.cpp
+++ b/accessible/src/base/nsAccessibilityService.cpp
@@ -1248,21 +1248,17 @@ nsAccessibilityService::CreateAccessible
 
   } else if (role.EqualsLiteral("xul:listitem")) {
     accessible = new XULListitemAccessible(aContent, aDoc);
 
   } else if (role.EqualsLiteral("xul:menubar")) {
     accessible = new XULMenubarAccessible(aContent, aDoc);
 
   } else if (role.EqualsLiteral("xul:menulist")) {
-      if (aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::droppable,
-                                nsGkAtoms::_false, eCaseMatters))
-      accessible = new XULTextFieldAccessible(aContent, aDoc);
-      else
-      accessible = new XULComboboxAccessible(aContent, aDoc);
+    accessible = new XULComboboxAccessible(aContent, aDoc);
 
   } else if (role.EqualsLiteral("xul:menuitem")) {
     accessible = new XULMenuitemAccessibleWrap(aContent, aDoc);
 
   } else if (role.EqualsLiteral("xul:menupopup")) {
 #ifdef MOZ_ACCESSIBILITY_ATK
     // ATK considers this node to be redundant when within menubars, and it makes menu
     // navigation with assistive technologies more difficult
--- a/accessible/src/xul/XULFormControlAccessible.cpp
+++ b/accessible/src/xul/XULFormControlAccessible.cpp
@@ -688,31 +688,18 @@ XULTextFieldAccessible::NativeState()
 
   nsCOMPtr<nsIContent> inputField(GetInputField());
   NS_ENSURE_TRUE(inputField, state);
 
   // Create a temporary accessible from the HTML text field to get
   // the accessible state from. Doesn't add to cache into document cache.
   nsRefPtr<HTMLTextFieldAccessible> tempAccessible =
     new HTMLTextFieldAccessible(inputField, mDoc);
-  if (!tempAccessible)
-    return state;
-
-  state |= tempAccessible->NativeState();
-
-  nsCOMPtr<nsIDOMXULMenuListElement> menuList(do_QueryInterface(mContent));
-  if (menuList) {
-    // <xul:menulist droppable="false">
-    if (!mContent->AttrValueIs(kNameSpaceID_None,
-                               nsGkAtoms::editable,
-                               nsGkAtoms::_true, eIgnoreCase)) {
-      state |= states::READONLY;
-    }
-  }
-
+  if (tempAccessible)
+    return state | tempAccessible->NativeState();
   return state;
 }
 
 role
 XULTextFieldAccessible::NativeRole()
 {
   if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
                             nsGkAtoms::password, eIgnoreCase))
@@ -825,23 +812,16 @@ XULTextFieldAccessible::FrameSelection()
 ////////////////////////////////////////////////////////////////////////////////
 // XULTextFieldAccessible protected
 
 already_AddRefed<nsIContent>
 XULTextFieldAccessible::GetInputField() const
 {
   nsCOMPtr<nsIDOMNode> inputFieldDOMNode;
   nsCOMPtr<nsIDOMXULTextBoxElement> textBox = do_QueryInterface(mContent);
-  if (textBox) {
+  if (textBox)
     textBox->GetInputField(getter_AddRefs(inputFieldDOMNode));
 
-  } else {
-    // <xul:menulist droppable="false">
-    nsCOMPtr<nsIDOMXULMenuListElement> menuList = do_QueryInterface(mContent);
-    if (menuList)
-      menuList->GetInputField(getter_AddRefs(inputFieldDOMNode));
-  }
-
   NS_ASSERTION(inputFieldDOMNode, "No input field for XULTextFieldAccessible");
 
   nsCOMPtr<nsIContent> inputField = do_QueryInterface(inputFieldDOMNode);
   return inputField.forget();
 }
--- a/addon-sdk/source/doc/dev-guide-source/tutorials/reusable-modules.md
+++ b/addon-sdk/source/doc/dev-guide-source/tutorials/reusable-modules.md
@@ -30,23 +30,23 @@ independently of your add-on, making the
 and effectively extending the SDK itself.
 
 In this tutorial we'll do exactly that with a module that exposes the
 geolocation API in Firefox.
 
 ## Using Geolocation in an Add-on ##
 
 Suppose we want to use the
-[geolocation API built into Firefox](https://developer.mozilla.org/en/using_geolocation).
+[geolocation API built into Firefox](https://developer.mozilla.org/en-US/docs/WebAPI/Using_geolocation).
 The SDK doesn't provide an API to access geolocation, but we can
 [access the underlying XPCOM API using `require("chrome")`](dev-guide/guides/xul-migration.html#xpcom).
 
 The following add-on adds a [button to the toolbar](dev-guide/tutorials/adding-toolbar-button.html):
 when the user clicks the button, it loads the
-[XPCOM nsIDOMGeoGeolocation](https://developer.mozilla.org/en/XPCOM_Interface_Reference/NsIDOMGeoGeolocation)
+[XPCOM nsIDOMGeoGeolocation](https://developer.mozilla.org/en-US/docs/XPCOM_Interface_Reference/NsIDOMGeoGeolocation)
 object, and retrieves the user's current position:
 
     var {Cc, Ci} = require("chrome");
 
     // Implement getCurrentPosition by loading the nsIDOMGeoGeolocation
     // XPCOM object.
     function getCurrentPosition(callback) {
       var xpcomGeolocation = Cc["@mozilla.org/geolocation;1"]
@@ -83,17 +83,17 @@ Click the button, and after a short dela
 this in the console:
 
 <pre>
 info: latitude:  29.45799999
 info: longitude:  93.0785269
 </pre>
 
 So far, so good. But the geolocation guide on MDN tells us that we must
-[ask the user for permission](https://developer.mozilla.org/en/using_geolocation#Prompting_for_permission)
+[ask the user for permission](https://developer.mozilla.org/en-US/docs/WebAPI/Using_geolocation#Prompting_for_permission)
 before using the API.
 
 So we'll extend the add-on to include an adapted version of the code in
 that MDN page:
 
 <pre><code>
 var activeBrowserWindow = require("sdk/window/utils").getMostRecentBrowserWindow();
 var {Cc, Ci} = require("chrome");
@@ -379,9 +379,9 @@ for your package. See the
 full details. If you intend to distribute the package, this is a good place
 to add your name as the author, choose a distribution license, and so on.
 
 ## Learning More ##
 
 To see some of the modules people have already developed, see the page of
 [community-developed modules](https://github.com/mozilla/addon-sdk/wiki/Community-developed-modules).
 To learn how to use third-party modules in your own code, see the
-[tutorial on adding menu items](dev-guide/tutorials/adding-menus.html).
\ No newline at end of file
+[tutorial on adding menu items](dev-guide/tutorials/adding-menus.html).
--- a/addon-sdk/source/doc/module-source/sdk/lang/functional.md
+++ b/addon-sdk/source/doc/module-source/sdk/lang/functional.md
@@ -8,25 +8,27 @@ and all credits go to him and his contri
 
 <api name="method">
 @function
 Takes a function and returns a method associated with an object.
 When the method is invoked on an instance of the object, the original
 function is called. It is passed the object instance (i.e. `this`) as
 the first parameter, followed by any parameters passed into the method.
 
-    let { method } = require("sdk/lang/functional");
-    let myNumber = {
+    const { method } = require("sdk/lang/functional");
+
+    const times = (target, x) => target.number *= x;
+    const add = (target, x) => target.number += x;
+
+    const myNumber = {
       times: method(times),
       add: method(add),
       number: 0
     };
 
-    function times (target, x) { return target.number *= x; }
-    function add (target, x) { return target.number += x; }
 
     console.log(myNumber.number); // 0
     myNumber.add(10);      // 10
     myNumber.times(2);     // 20
     myNumber.add(3);       // 23
 
 @param lambda {function}
   The function to be wrapped and returned.
@@ -39,18 +41,18 @@ the first parameter, followed by any par
 @function
 Takes a function and returns a wrapped version of the function. Calling the
 wrapped version will call the original function during the next event loop.
 This is similar to calling [setTimeout](modules/sdk/timers.html#setTimeout(callback%2C ms)) with no
 wait (i.e. `setTimeout(function () { ... }, 0)`), except that the wrapped function
 may be reused and does not need to be repeated each time. This also enables you
 to use these functions as event listeners.
 
-    let { defer } = require("sdk/lang/functional");
-    let fn = defer(function myEvent (event, value) {
+    const { defer } = require("sdk/lang/functional");
+    const fn = defer((event, value) => {
       console.log(event + " : " + value);
     });
 
     fn("click", "#home");
     console.log("done");
 
     // This will print 'done' before 'click : #home' since
     // we deferred the execution of the wrapped `myEvent`
@@ -69,43 +71,38 @@ to use these functions as event listener
 An alias for [defer](modules/sdk/lang/functional.html#defer(fn)).
 </api>
 
 <api name="invoke">
 @function
 Invokes `callee`, passing `params` as an argument and `self` as `this`.
 Returns the value that is returned by `callee`.
 
-    let { invoke } = require("sdk/lang/functional");
-
-    invoke(sum, [1,2,3,4,5], null); // 15
+    const { invoke } = require("sdk/lang/functional");
 
-    function sum () {
-      return Array.slice(arguments).reduce(function (a, b) {
-        return a + b;
-      });
-    }
+    const sum = (...args) => args.reduce((a, b) => a + b);
+    invoke(sum, [1,2,3,4,5], null); // 15
 
 @param callee {function}
   Function to invoke.
 @param params {Array}
   Parameters to be passed into `callee`.
 @param self {mixed}
   Object to be passed as the `this` context to `callee`.
 @returns {mixed}
   Returns the return value of `callee`.
 </api>
 
 <api name="partial">
 @function
 Takes a function and bind values to one or more arguments, returning a new function of smaller arity.
 
-    let { partial } = require("sdk/lang/functional");
-    let add = function add (x, y) { return x + y; }
-    let addOne = partial(add, 1);
+    const { partial } = require("sdk/lang/functional");
+    const add = (x, y) => x + y;
+    const addOne = partial(add, 1);
 
     addOne(5); // 6
     addOne(10); // 11
     partial(add, addOne(20))(2); // 23
 
 @param fn {function}
   Function on which partial application is to be performed.
 
@@ -117,66 +114,66 @@ Takes a function and bind values to one 
 </api>
 
 <api name="compose">
 @function
 Returns the [composition](http://en.wikipedia.org/wiki/Function_composition_(computer_science)) of a list of functions, where each function consumes the
 return value of the function that follows. In math terms, composing the functions
 `f()`, `g()`, and `h()` produces `f(g(h()))`.
 
-    let { compose } = require("sdk/lang/functional");
+    const { compose } = require("sdk/lang/functional");
 
-    let welcome = compose(exclaim, greet);
+    const square = x => x * x;
+    const increment = x => x + 1;
 
-    welcome('moe'); // "hi: moe!";
+    const f1 = compose(increment, square);
+    f1(5); // => 26
 
-    function greet (name) { return "hi: " + name; }
-    function exclaim (statement) { return statement + "!"; }
+    const f2 = compose(square, increment);
+    f2(5); // => 36
 
 @param fn... {function}
   Takes a variable number of functions as arguments and composes them from right to left.
 
 @returns {function}
   The composed function.
 </api>
 
 <api name="wrap">
 @function
 Returns the first function passed as an argument to the second,
 allowing you to adjust arguments, run code before and after, and
 conditionally execute the original function.
 
-    let { wrap } = require("sdk/lang/functional");
+    const { wrap } = require("sdk/lang/functional");
 
-    let wrappedHello = wrap(hello, function (fn, name) {
-      return "before, " + fn(name) + "after";
-    });
+    const hello = name => "hello: " + name;
+    const wrappedHello = wrap(hello, (fn, name) =>
+      "before, " + fn(name) + "after");
 
     wrappedHello("moe"); // "before, hello: moe, after"
 
-    function hello (name) { return "hello: " + name; }
-
 @param fn {function}
   The function to be passed into the `wrapper` function.
 
 @param wrapper {function}
   The function that is called when the return function is executed,
   taking the wrapped `fn` as the first parameter.
 
 @returns {function}
   A function which, when called, executes `wrapper` with `fn` as the first parameter,
   and passes in any additional parameters to the `wrapper` function.
 </api>
 
 <api name="identity">
 @function
 Returns the same value that is used as the argument. In math: f(x) = x.
 
-    let { identity } = require("sdk/lang/functional");
-    let x = 5;
+    const { identity } = require("sdk/lang/functional");
+    const x = 5;
     identity(x); // 5
 
 @param value {mixed}
   The value to be returned.
 
 @returns {mixed}
   The value that was originally passed in.
 </api>
@@ -185,45 +182,43 @@ Returns the same value that is used as t
 @function
 [Memoizes](http://en.wikipedia.org/wiki/Memoization) a given function by caching
 the computed result. Useful for speeding up slow-running computations. If
 passed an optional `hashFunction`, it will be used to compute the hash key for
 storing the result, based on the arguments to the original function. The
 default `hashFunction` just uses the first argument to the memoized function as
 the key.
 
-    let { memoize } = require("sdk/lang/functional");
+    const { memoize } = require("sdk/lang/functional");
 
-    let memoizedFn = memoize(primeFactorization);
+    const memoizedFn = memoize(primeFactorization);
 
     memoizedFn(50); // Returns [2, 5, 5], had to compute
     memoizedFn(100); // Returns [2, 2, 5, 5], had to compute
     memoizedFn(50); // Returns [2, 5, 5] again, but pulled from cache
 
-    function primeFactorization (x) {
+    const primeFactorization = x => {
       // Some tricky stuff
     }
 
     // We can also use a hash function to compute a different
     // hash value. In this example, we'll fabricate a function
     // that takes a string of first and last names that
     // somehow computes the lineage of that name. Our hash
     // function will just parse the last name, as our naive
     // implementation assumes that they will share the same lineage
 
-    let getLineage = memoize(function (name) {
+    const getLineage = memoize(name => {
       // computes lineage
       return data;
     }, hasher);
 
     // Hashing function takes a string of first and last name
     // and returns the last name.
-    function hasher (input) {
-      return input.split(" ")[1];
-    }
+    const hasher = input => input.split(" ")[1];
 
     getLineage("homer simpson"); // Computes and returns information for "simpson"
     getLineage("lisa simpson"); // Returns cached for "simpson"
 
 @param fn {function}
   The function that becomes memoized.
 
 @param hasher {function}
@@ -235,22 +230,21 @@ the key.
 </api>
 
 <api name="delay">
 @function
 Much like `setTimeout`, `delay` invokes a function after waiting a set number of
 milliseconds. If you pass additional, optional, arguments, they will be forwarded
 on to the function when it is invoked.
 
-    let { delay } = require("sdk/lang/functional");
+    const { delay } = require("sdk/lang/functional");
 
+    const printAdd = (a, b) console.log(a + "+" + b + "=" + (a+b));
     delay(printAdd, 2000, 5, 10);
-
     // Prints "5+10=15" in two seconds (2000ms)
-    function printAdd (a, b) { console.log(a + "+" + b + "=" + (a+b)); }
 
 @param fn {function}
   A function to be delayed.
 
 @param ms {number}
   Number of milliseconds to delay the execution of `fn`.
 
 @param arguments {mixed}
@@ -259,59 +253,318 @@ on to the function when it is invoked.
 
 <api name="once">
 @function
 Creates a version of the input function that can only be called one time.
 Repeated calls to the modified function will have no effect, returning
 the value from the original call. Useful for initialization functions, instead
 of having to set a boolean flag and checking it later.
 
-    let { once } = require("sdk/lang/functional");
-    let setup = once(function (env) {
+    const { once } = require("sdk/lang/functional");
+    const setup = once(env => {
       // initializing important things
       console.log("successfully initialized " + env);
       return 1; // Assume success and return 1
     });
 
     setup('dev'); // returns 1
     // prints "successfully initialized dev"
-    
+
     // Future attempts to call this function just return the cached
     // value that was returned previously
     setup('production'); // Returns 1
     // No print message is displayed since the function isn't executed
 
 @param fn {function}
   The function that will be executed only once inside the once wrapper.
 
 @returns {function}
   The wrapped `fn` that can only be executed once.
 </api>
 
-<api name="chain">
+<api name="cache">
+@function
+An alias for [once](modules/sdk/lang/functional.html#once(fn)).
+</api>
+
+<api name="complement">
+@function
+Takes a `f` function and returns a function that takes the same
+arguments as `f`, has the same effects, if any, and returns the
+opposite truth value.
+
+    const { complement } = require("sdk/lang/functional");
+
+    let isOdd = x => Boolean(x % 2);
+
+    isOdd(1)     // => true
+    isOdd(2)     // => false
+
+    let isEven = complement(isOdd);
+
+    isEven(1)     // => false
+    isEven(2)     // => true
+
+@param lambda {function}
+  The function to compose from
+
+@returns {boolean}
+  `!lambda(...)`
+</api>
+
+<api name="constant">
+@function
+Constructs function that returns `x` no matter what is it
+invoked with.
+
+    const { constant } = require("sdk/lang/functional");
+
+    const one = constant(1);
+
+    one();              // => 1
+    one(2);             // => 1
+    one.apply({}, 3);   // => 1
+
+@param x {object}
+  Value that will be returned by composed function
+
+@returns {function}
+</api>
+
+
+<api name="apply">
+@function
+Apply function that behaves like `apply` in other functional
+languages:
+
+    const { apply } = require("sdk/lang/functional");
+
+    const dashify = (...args) => args.join("-");
+
+    apply(dashify, 1, [2, 3]);        // => "1-2-3"
+    apply(dashify, "a");              // => "a"
+    apply(dashify, ["a", "b"]);       // => "a-b"
+    apply(dashify, ["a", "b"], "c");  // => "a,b-c"
+    apply(dashify, [1, 2], [3, 4]);   // => "1,2-3-4"
+
+@param f {function}
+  function to be invoked
+</api>
+
+<api name="flip">
+@function
+Returns function identical to given `f` but with flipped order
+of arguments.
+
+    const { flip } = require("sdk/lang/functional");
+
+    const append = (left, right) => left + " " + right;
+    const prepend = flip(append);
+
+    append("hello", "world")       // => "hello world"
+    prepend("hello", "world")      // => "world hello"
+
+@param f {function}
+  function whose arguments should to be flipped
+
+@returns {function}
+  function with flipped arguments
+</api>
+
+<api name="when">
+@function
+Takes `p` predicate, `consequent` function and an optional
+`alternate` function and composes function that returns
+application of arguments over `consequent` if application over
+`p` is `true` otherwise returns application over `alternate`.
+If `alternate` is not a function returns `undefined`.
+
+    const { when } = require("sdk/lang/functional");
+
+    function Point(x, y) {
+      this.x = x
+      this.y = y
+    }
+
+    const isPoint = x => x instanceof Point;
+
+    const inc = when(isPoint, ({x, y}) => new Point(x + 1, y + 1));
+
+    inc({});                 // => undefined
+    inc(new Point(0, 0));    // => { x: 1, y: 1 }
+
+    const axis = when(isPoint,
+                      ({ x, y }) => [x, y],
+                      _ => [0, 0]);
+
+    axis(new Point(1, 4));   // => [1, 4]
+    axis({ foo: "bar" });    // => [0, 0]
+
+@param p {function}
+  predicate function whose return value determines to which
+  function be delegated control.
+
+@param consequent {function}
+  function to which arguments are applied if `predicate` returned
+  `true`.
+
+@param alternate {function}
+  function to which arguments are applied if `predicate` returned
+  `false`.
+
+@returns {object|string|number|function}
+  Return value from `consequent` if `p` returned `true` or return
+  value from `alternate` if `p` returned `false`. If `alternate`
+  is not provided and `p` returned `false` then `undefined` is
+  returned.
+</api>
+
+
+<api name="chainable">
 @function
 Creates a version of the input function that will return `this`.
 
-    let { chain } = require("sdk/lang/functional");
+    const { chainable } = require("sdk/lang/functional");
 
     function Person (age) { this.age = age; }
-    Person.prototype.happyBirthday = chain(function () this.age++);
+    Person.prototype.happyBirthday = chainable(function() {
+      return this.age++
+    });
 
-    let person = new Person(30);
+    const person = new Person(30);
 
     person
       .happyBirthday()
       .happyBirthday()
       .happyBirthday()
 
     console.log(person.age); // 33
 
 @param fn {function}
   The function that will be wrapped by the chain function.
 
 @returns {function}
   The wrapped function that executes `fn` and returns `this`.
 </api>
 
-<api name="cache">
+<api name="field">
+@function
+
+Takes field `name` and `target` and returns value of that field.
+If `target` is `null` or `undefined` it would be returned back
+instead of attempt to access it's field. Function is implicitly
+curried, this allows accessor function generation by calling it
+with only `name` argument.
+
+    const { field } = require("sdk/lang/functional");
+
+    field("x", { x: 1, y: 2});     // => 1
+    field("x")({ x: 1 });          // => 1
+    field("x", { y: 2 });          // => undefiend
+
+    const getX = field("x");
+    getX({ x: 1 });               // => 1
+    getX({ y: 1 });               // => undefined
+    getX(null);                   // => null
+
+@param name {string}
+  Name of the field to be returned
+
+@param target {object}
+  Target to get a field by the given `name` from
+
+@returns {object|function|string|number|boolean}
+  Field value
+</api>
+
+<api name="query">
+@function
+
+Takes `.` delimited string representing `path` to a nested field
+and a `target` to get it from. For convinience function is
+implicitly curried, there for accessors can be created by invoking
+it with just a `path` argument.
+
+    const { query } = require("sdk/lang/functional");
+
+    query("x", { x: 1, y: 2});           // => 1
+    query("top.x", { x: 1 });            // => undefiend
+    query("top.x", { top: { x: 2 } });   // => 2
+
+    const topX = query("top.x");
+    topX({ top: { x: 1 } });             // => 1
+    topX({ y: 1 });                      // => undefined
+    topX(null);                          // => null
+
+@param path {string}
+  `.` delimited path to a field
+
+@param target {object}
+  Target to get a field by the given `name` from
+
+@returns {object|function|string|number|boolean}
+  Field value
+</api>
+
+<api name="isInstance">
 @function
-An alias for [once](modules/sdk/lang/functional.html#once(fn)).
+
+Takes `Type` (constructor function) and a `value` and returns
+`true` if `value` is instance of the given `Type`. Function is
+implicitly curried this allows predicate generation by calling
+function with just first argument.
+
+    const { isInstance } = require("sdk/lang/functional");
+
+    function X() {}
+    function Y() {}
+    let isX = isInstance(X);
+
+    isInstance(X, new X);     // true
+    isInstance(X)(new X);     // true
+    isInstance(X, new Y);     // false
+    isInstance(X)(new Y);     // false
+
+    isX(new X);               // true
+    isX(new Y);               // false
+
+@param Type {function}
+  Type (constructor function)
+
+@param instance {object}
+  Instance to test
+
+@returns {boolean}
 </api>
+
+<api name="is">
+@function
+
+Functions takes `expected` and `actual` values and returns `true` if
+`expected === actual`. If invoked with just one argument returns pratially
+applied function, which can be invoked to provide a second argument, this
+is handy with `map`, `filter` and other high order functions:
+
+    const { is } = require("sdk/util/oops");
+    [ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ]
+
+@param expected {object|string|number|boolean}
+@param actual {object|string|number|boolean}
+
+@returns {boolean}
+</api>
+
+<api name="isnt">
+@function
+
+Functions takes `expected` and `actual` values and returns `true` if
+`expected !== actual`. If invoked with just one argument returns pratially
+applied function, which can be invoked with a second argument, which is
+handy with `map`, `filter` and other high order functions:
+
+    const { isnt } = require("sdk/util/oops");
+    [ 1, 0, 1, 0, 1 ].map(isnt(0)) // => [ true, false, true, false, true ]
+
+@param expected {object|string|number|boolean}
+@param actual {object|string|number|boolean}
+
+@returns {boolean}
+</api>
--- a/addon-sdk/source/lib/sdk/event/target.js
+++ b/addon-sdk/source/lib/sdk/event/target.js
@@ -4,17 +4,17 @@
 
 'use strict';
 
 module.metadata = {
   "stability": "stable"
 };
 
 const { on, once, off, setListeners } = require('./core');
-const { method, chain } = require('../lang/functional');
+const { method, chainable } = require('../lang/functional');
 const { Class } = require('../core/heritage');
 
 /**
  * `EventTarget` is an exemplar for creating an objects that can be used to
  * add / remove event listeners on them. Events on these objects may be emitted
  * via `emit` function exported by 'event/core' module.
  */
 const EventTarget = Class({
@@ -38,26 +38,26 @@ const EventTarget = Class({
    *    The type of event.
    * @param {Function} listener
    *    The listener function that processes the event.
    * @example
    *      worker.on('message', function (data) {
    *        console.log('data received: ' + data)
    *      })
    */
-  on: chain(method(on)),
+  on: chainable(method(on)),
   /**
    * Registers an event `listener` that is called once the next time an event
    * of the specified `type` is emitted.
    * @param {String} type
    *    The type of the event.
    * @param {Function} listener
    *    The listener function that processes the event.
    */
-  once: chain(method(once)),
+  once: chainable(method(once)),
   /**
    * Removes an event `listener` for the given event `type`.
    * @param {String} type
    *    The type of event.
    * @param {Function} listener
    *    The listener function that processes the event.
    */
   removeListener: function removeListener(type, listener) {
--- a/addon-sdk/source/lib/sdk/lang/functional.js
+++ b/addon-sdk/source/lib/sdk/lang/functional.js
@@ -1,147 +1,152 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-// Disclaimer: Most of the functions in this module implement APIs from
+// Disclaimer: Some of the functions in this module implement APIs from
 // Jeremy Ashkenas's http://underscorejs.org/ library and all credits for
 // those goes to him.
 
 "use strict";
 
 module.metadata = {
   "stability": "unstable"
 };
 
+const { deprecateFunction } = require("../util/deprecate");
 const { setImmediate, setTimeout } = require("../timers");
-const { deprecateFunction } = require("../util/deprecate");
+
+const arity = f => f.arity || f.length;
+
+const name = f => f.displayName || f.name;
+
+const derive = (f, source) => {
+  f.displayName = name(source);
+  f.arity = arity(source);
+  return f;
+};
 
 /**
- * Takes `lambda` function and returns a method. When returned method is
- * invoked it calls wrapped `lambda` and passes `this` as a first argument
- * and given argument as rest.
+ * Takes variadic numeber of functions and returns composed one.
+ * Returned function pushes `this` pseudo-variable to the head
+ * of the passed arguments and invokes all the functions from
+ * left to right passing same arguments to them. Composite function
+ * returns return value of the right most funciton.
  */
-function method(lambda) {
-  return function method() {
-    return lambda.apply(null, [this].concat(Array.slice(arguments)));
-  }
-}
+const method = (...lambdas) => {
+  return function method(...args) {
+    args.unshift(this);
+    return lambdas.reduce((_, lambda) => lambda.apply(this, args),
+                          void(0));
+  };
+};
 exports.method = method;
 
 /**
  * Takes a function and returns a wrapped one instead, calling which will call
  * original function in the next turn of event loop. This is basically utility
  * to do `setImmediate(function() { ... })`, with a difference that returned
  * function is reused, instead of creating a new one each time. This also allows
  * to use this functions as event listeners.
  */
-function defer(f) {
-  return function deferred() setImmediate(invoke, f, arguments, this);
-}
+const defer = f => derive(function(...args) {
+  setImmediate(invoke, f, args, this);
+}, f);
 exports.defer = defer;
 // Exporting `remit` alias as `defer` may conflict with promises.
 exports.remit = defer;
 
-/*
- * Takes a funtion and returns a wrapped function that returns `this`
- */
-function chain(f) {
-  return function chainable(...args) {
-    f.apply(this, args);
-    return this;
-  };
-}
-exports.chain = chain;
-
 /**
  * Invokes `callee` by passing `params` as an arguments and `self` as `this`
  * pseudo-variable. Returns value that is returned by a callee.
  * @param {Function} callee
  *    Function to invoke.
  * @param {Array} params
  *    Arguments to invoke function with.
  * @param {Object} self
  *    Object to be passed as a `this` pseudo variable.
  */
-function invoke(callee, params, self) callee.apply(self, params);
+const invoke = (callee, params, self) => callee.apply(self, params);
 exports.invoke = invoke;
 
 /**
  * Takes a function and bind values to one or more arguments, returning a new
  * function of smaller arity.
  *
  * @param {Function} fn
  *    The function to partial
  *
  * @returns The new function with binded values
  */
-function partial(fn) {
-  if (typeof fn !== "function")
-    throw new TypeError(String(fn) + " is not a function");
+const partial = (f, ...curried) => {
+  if (typeof(f) !== "function")
+    throw new TypeError(String(f) + " is not a function");
 
-  let args = Array.slice(arguments, 1);
-
-  return function() fn.apply(this, args.concat(Array.slice(arguments)));
-}
+  let fn = derive(function(...args) {
+    return f.apply(this, curried.concat(args));
+  }, f);
+  fn.arity = arity(f) - curried.length;
+  return fn;
+};
 exports.partial = partial;
 
 /**
  * Returns function with implicit currying, which will continue currying until
  * expected number of argument is collected. Expected number of arguments is
  * determined by `fn.length`. Using this with variadic functions is stupid,
  * so don't do it.
  *
  * @examples
  *
  * var sum = curry(function(a, b) {
  *   return a + b
  * })
  * console.log(sum(2, 2)) // 4
  * console.log(sum(2)(4)) // 6
  */
-var curry = new function() {
-  function currier(fn, arity, params) {
+const curry = new function() {
+  const currier = (fn, arity, params) => {
     // Function either continues to curry arguments or executes function
     // if desired arguments have being collected.
-    return function curried() {
-      var input = Array.slice(arguments);
+    const curried = function(...input) {
       // Prepend all curried arguments to the given arguments.
       if (params) input.unshift.apply(input, params);
       // If expected number of arguments has being collected invoke fn,
       // othrewise return curried version Otherwise continue curried.
       return (input.length >= arity) ? fn.apply(this, input) :
              currier(fn, arity, input);
     };
-  }
+    curried.arity = arity - (params ? params.length : 0);
 
-  return function curry(fn) {
-    return currier(fn, fn.length);
-  }
+    return curried;
+  };
+
+  return fn => currier(fn, arity(fn));
 };
 exports.curry = curry;
 
 /**
  * Returns the composition of a list of functions, where each function consumes
  * the return value of the function that follows. In math terms, composing the
  * functions `f()`, `g()`, and `h()` produces `f(g(h()))`.
  * @example
  *
  *   var greet = function(name) { return "hi: " + name; };
  *   var exclaim = function(statement) { return statement + "!"; };
  *   var welcome = compose(exclaim, greet);
  *
  *   welcome('moe');    // => 'hi: moe!'
  */
-function compose() {
-  let lambdas = Array.slice(arguments);
-  return function composed() {
-    let args = Array.slice(arguments), index = lambdas.length;
+function compose(...lambdas) {
+  return function composed(...args) {
+    let index = lambdas.length;
     while (0 <= --index)
-      args = [ lambdas[index].apply(this, args) ];
+      args = [lambdas[index].apply(this, args)];
+
     return args[0];
   };
 }
 exports.compose = compose;
 
 /*
  * Returns the first function passed as an argument to the second,
  * allowing you to adjust arguments, run code before and after, and
@@ -150,61 +155,176 @@ exports.compose = compose;
  *
  *  var hello = function(name) { return "hello: " + name; };
  *  hello = wrap(hello, function(f) {
  *    return "before, " + f("moe") + ", after";
  *  });
  *
  *  hello();    // => 'before, hello: moe, after'
  */
-function wrap(f, wrapper) {
-  return function wrapped()
-    wrapper.apply(this, [ f ].concat(Array.slice(arguments)))
-};
+const wrap = (f, wrapper) => derive(function wrapped(...args) {
+  return wrapper.apply(this, [f].concat(args));
+}, f);
 exports.wrap = wrap;
 
 /**
  * Returns the same value that is used as the argument. In math: f(x) = x
  */
-function identity(value) value
+const identity = value => value;
 exports.identity = identity;
 
 /**
  * Memoizes a given function by caching the computed result. Useful for
  * speeding up slow-running computations. If passed an optional hashFunction,
  * it will be used to compute the hash key for storing the result, based on
  * the arguments to the original function. The default hashFunction just uses
  * the first argument to the memoized function as the key.
  */
-function memoize(f, hasher) {
+const memoize = (f, hasher) => {
   let memo = Object.create(null);
+  let cache = new WeakMap();
   hasher = hasher || identity;
-  return function memoizer() {
-    let key = hasher.apply(this, arguments);
-    return key in memo ? memo[key] : (memo[key] = f.apply(this, arguments));
-  };
-}
+  return derive(function memoizer(...args) {
+    const key = hasher.apply(this, args);
+    const type = typeof(key);
+    if (key && (type === "object" || type === "function")) {
+      if (!cache.has(key))
+        cache.set(key, f.apply(this, args));
+      return cache.get(key);
+    }
+    else {
+      if (!(key in memo))
+        memo[key] = f.apply(this, args);
+      return memo[key];
+    }
+  }, f);
+};
 exports.memoize = memoize;
 
 /**
  * Much like setTimeout, invokes function after wait milliseconds. If you pass
  * the optional arguments, they will be forwarded on to the function when it is
  * invoked.
  */
-function delay(f, ms) {
-  let args = Array.slice(arguments, 2);
-  setTimeout(function(context) { return f.apply(context, args); }, ms, this);
+const delay = function delay(f, ms, ...args) {
+  setTimeout(() => f.apply(this, args), ms);
 };
 exports.delay = delay;
 
 /**
  * Creates a version of the function that can only be called one time. Repeated
  * calls to the modified function will have no effect, returning the value from
  * the original call. Useful for initialization functions, instead of having to
  * set a boolean flag and then check it later.
  */
-function once(f) {
+const once = f => {
   let ran = false, cache;
-  return function() ran ? cache : (ran = true, cache = f.apply(this, arguments))
+  return derive(function(...args) {
+    return ran ? cache : (ran = true, cache = f.apply(this, args));
+  }, f);
 };
 exports.once = once;
 // export cache as once will may be conflicting with event once a lot.
 exports.cache = once;
+
+// Takes a `f` function and returns a function that takes the same
+// arguments as `f`, has the same effects, if any, and returns the
+// opposite truth value.
+const complement = f => derive(function(...args) {
+  return args.length < arity(f) ? complement(partial(f, ...args)) :
+         !f.apply(this, args);
+}, f);
+exports.complement = complement;
+
+// Constructs function that returns `x` no matter what is it
+// invoked with.
+const constant = x => _ => x;
+exports.constant = constant;
+
+// Takes `p` predicate, `consequent` function and an optional
+// `alternate` function and composes function that returns
+// application of arguments over `consequent` if application over
+// `p` is `true` otherwise returns application over `alternate`.
+// If `alternate` is not a function returns `undefined`.
+const when = (p, consequent, alternate) => {
+  if (typeof(alternate) !== "function" && alternate !== void(0))
+    throw TypeError("alternate must be a function");
+  if (typeof(consequent) !== "function")
+    throw TypeError("consequent must be a function");
+
+  return function(...args) {
+    return p.apply(this, args) ?
+           consequent.apply(this, args) :
+           alternate && alternate.apply(this, args);
+  };
+};
+exports.when = when;
+
+// Apply function that behaves as `apply` does in lisp:
+// apply(f, x, [y, z]) => f.apply(f, [x, y, z])
+// apply(f, x) => f.apply(f, [x])
+const apply = (f, ...rest) => f.apply(f, rest.concat(rest.pop()));
+exports.apply = apply;
+
+// Returns function identical to given `f` but with flipped order
+// of arguments.
+const flip = f => derive(function(...args) {
+  return f.apply(this, args.reverse());
+}, f);
+exports.flip = flip;
+
+
+// Takes field `name` and `target` and returns value of that field.
+// If `target` is `null` or `undefined` it would be returned back
+// instead of attempt to access it's field. Function is implicitly
+// curried, this allows accessor function generation by calling it
+// with only `name` argument.
+const field = curry((name, target) =>
+  // Note: Permisive `==` is intentional.
+  target == null ? target : target[name]);
+exports.field = field;
+
+// Takes `.` delimited string representing `path` to a nested field
+// and a `target` to get it from. For convinience function is
+// implicitly curried, there for accessors can be created by invoking
+// it with just a `path` argument.
+const query = curry((path, target) => {
+  const names = path.split(".");
+  const count = names.length;
+  let index = 0;
+  let result = target;
+  // Note: Permisive `!=` is intentional.
+  while (result != null && index < count) {
+    result = result[names[index]];
+    index = index + 1;
+  }
+  return result;
+});
+exports.query = query;
+
+// Takes `Type` (constructor function) and a `value` and returns
+// `true` if `value` is instance of the given `Type`. Function is
+// implicitly curried this allows predicate generation by calling
+// function with just first argument.
+const isInstance = curry((Type, value) => value instanceof Type);
+exports.isInstance = isInstance;
+
+/*
+ * Takes a funtion and returns a wrapped function that returns `this`
+ */
+const chainable = f => derive(function(...args) {
+  f.apply(this, args);
+  return this;
+}, f);
+exports.chainable = chainable;
+exports.chain =
+  deprecateFunction(chainable, "Function `chain` was renamed to `chainable`");
+
+// Functions takes `expected` and `actual` values and returns `true` if
+// `expected === actual`. Returns curried function if called with less then
+// two arguments.
+//
+// [ 1, 0, 1, 0, 1 ].map(is(1)) // => [ true, false, true, false, true ]
+const is = curry((expected, actual) => actual === expected);
+exports.is = is;
+
+const isnt = complement(is);
+exports.isnt = isnt;
--- a/addon-sdk/source/test/test-functional.js
+++ b/addon-sdk/source/test/test-functional.js
@@ -1,43 +1,44 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
+"use strict";
 
 const { setTimeout } = require('sdk/timers');
 const utils = require('sdk/lang/functional');
-const { invoke, defer, partial, compose, memoize, once, delay, wrap, curry, chain } = utils;
+const { invoke, defer, partial, compose, memoize, once, is, isnt,
+        delay, wrap, curry, chainable, field, query, isInstance } = utils;
 const { LoaderWithHookedConsole } = require('sdk/test/loader');
 
 exports['test forwardApply'] = function(assert) {
-  function sum(b, c) this.a + b + c
+  function sum(b, c) { return this.a + b + c; }
   assert.equal(invoke(sum, [2, 3], { a: 1 }), 6,
                'passed arguments and pseoude-variable are used');
 
   assert.equal(invoke(sum.bind({ a: 2 }), [2, 3], { a: 1 }), 7,
                'bounded `this` pseoudo variable is used');
-}
+};
 
 exports['test deferred function'] = function(assert, done) {
   let nextTurn = false;
   function sum(b, c) {
     assert.ok(nextTurn, 'enqueued is called in next turn of event loop');
     assert.equal(this.a + b + c, 6,
                  'passed arguments an pseoude-variable are used');
     done();
   }
 
-  let fixture = { a: 1, method: defer(sum) }
+  let fixture = { a: 1, method: defer(sum) };
   fixture.method(2, 3);
   nextTurn = true;
 };
 
 exports['test partial function'] = function(assert) {
-  function sum(b, c) this.a + b + c;
+  function sum(b, c) { return this.a + b + c; }
 
   let foo = { a : 5 };
 
   foo.sum7 = partial(sum, 7);
   foo.sum8and4 = partial(sum, 8, 4);
 
   assert.equal(foo.sum7(2), 14, 'partial one arguments works');
 
@@ -63,21 +64,21 @@ exports['test compose'] = function(asser
                'can compose a function that takes another');
 
   assert.equal(compose(greet, exclaim)('moe'), 'hi: moe!',
                'in this case, the functions are also commutative');
 
   let target = {
     name: 'Joe',
     greet: compose(function exclaim(sentence) {
-      return sentence + '!'
+      return sentence + '!';
     }, function(title) {
       return 'hi : ' + title + ' ' + this.name;
     })
-  }
+  };
 
   assert.equal(target.greet('Mr'), 'hi : Mr Joe!',
                'this can be passed in');
   assert.equal(target.greet.call({ name: 'Alex' }, 'Dr'), 'hi : Dr Alex!',
                'this can be applied');
 
   let single = compose(function(value) {
     return value + ':suffix';
@@ -101,113 +102,321 @@ exports['test wrap'] = function(assert) 
   let inner = function () { return 'Hello '; };
   let target = {
     name: 'Matteo',
     hi: wrap(inner, function(f) { return f() + this.name; })
   };
 
   assert.equal(target.hi(), 'Hello Matteo', 'works with this');
 
-  function noop() { };
+  function noop() { }
   let wrapped = wrap(noop, function(f) {
     return Array.slice(arguments);
   });
 
   let actual = wrapped([ 'whats', 'your' ], 'vector', 'victor');
   assert.deepEqual(actual, [ noop, ['whats', 'your'], 'vector', 'victor' ],
                    'works with fancy stuff');
 };
 
 exports['test memoize'] = function(assert) {
-  function fib(n) n < 2 ? n : fib(n - 1) + fib(n - 2)
+  const fib = n => n < 2 ? n : fib(n - 1) + fib(n - 2);
   let fibnitro = memoize(fib);
 
   assert.equal(fib(10), 55,
         'a memoized version of fibonacci produces identical results');
   assert.equal(fibnitro(10), 55,
         'a memoized version of fibonacci produces identical results');
 
-  function o(key, value) { return value; };
+  function o(key, value) { return value; }
   let oo = memoize(o), v1 = {}, v2 = {};
 
 
   assert.equal(oo(1, v1), v1, 'returns value back');
   assert.equal(oo(1, v2), v1, 'memoized by a first argument');
   assert.equal(oo(2, v2), v2, 'returns back value if not memoized');
   assert.equal(oo(2), v2, 'memoized new value');
   assert.notEqual(oo(1), oo(2), 'values do not override');
   assert.equal(o(3, v2), oo(2, 3), 'returns same value as un-memoized');
 
-  let get = memoize(function(attribute) this[attribute])
-  let target = { name: 'Bob', get: get }
+  let get = memoize(function(attribute) { return this[attribute]; });
+  let target = { name: 'Bob', get: get };
 
   assert.equal(target.get('name'), 'Bob', 'has correct `this`');
   assert.equal(target.get.call({ name: 'Jack' }, 'name'), 'Bob',
-               'name is memoized')
+               'name is memoized');
   assert.equal(get('name'), 'Bob', 'once memoized can be called without this');
 };
 
 exports['test delay'] = function(assert, done) {
   let delayed = false;
   delay(function() {
     assert.ok(delayed, 'delayed the function');
     done();
   }, 1);
   delayed = true;
 };
 
 exports['test delay with this'] = function(assert, done) {
-  let context = {}
+  let context = {};
   delay.call(context, function(name) {
     assert.equal(this, context, 'this was passed in');
     assert.equal(name, 'Tom', 'argument was passed in');
     done();
   }, 10, 'Tom');
-}
+};
 
 exports['test once'] = function(assert) {
   let n = 0;
   let increment = once(function() { n ++; });
 
   increment();
   increment();
 
   assert.equal(n, 1, 'only incremented once');
 
-  let target = { state: 0, update: once(function() this.state ++ ) };
+  let target = {
+    state: 0,
+    update: once(function() {
+      return this.state ++;
+    })
+  };
 
   target.update();
   target.update();
 
   assert.equal(target.state, 1, 'this was passed in and called only once');
 };
 
 exports['test once with argument'] = function(assert) {
   let n = 0;
-  let increment = once(function(a) n++);
+  let increment = once(a => n++);
 
   increment();
   increment('foo');
 
   assert.equal(n, 1, 'only incremented once');
 
   increment();
   increment('foo');
 
   assert.equal(n, 1, 'only incremented once');
 };
 
-exports['test chain'] = function (assert) {
+exports['test complement'] = assert => {
+  let { complement } = require("sdk/lang/functional");
+
+  let isOdd = x => Boolean(x % 2);
+
+  assert.equal(isOdd(1), true);
+  assert.equal(isOdd(2), false);
+
+  let isEven = complement(isOdd);
+
+  assert.equal(isEven(1), false);
+  assert.equal(isEven(2), true);
+
+  let foo = {};
+  let isFoo = function() { return this === foo; };
+  let insntFoo = complement(isFoo);
+
+  assert.equal(insntFoo.call(foo), false);
+  assert.equal(insntFoo.call({}), true);
+};
+
+exports['test constant'] = assert => {
+  let { constant } = require("sdk/lang/functional");
+
+  let one = constant(1);
+
+  assert.equal(one(1), 1);
+  assert.equal(one(2), 1);
+};
+
+exports['test apply'] = assert => {
+  let { apply } = require("sdk/lang/functional");
+
+  let dashify = (...args) => args.join("-");
+
+  assert.equal(apply(dashify, 1, [2, 3]), "1-2-3");
+  assert.equal(apply(dashify, "a"), "a");
+  assert.equal(apply(dashify, ["a", "b"]), "a-b");
+  assert.equal(apply(dashify, ["a", "b"], "c"), "a,b-c");
+  assert.equal(apply(dashify, [1, 2], [3, 4]), "1,2-3-4");
+};
+
+exports['test flip'] = assert => {
+  let { flip } = require("sdk/lang/functional");
+
+  let append = (left, right) => left + " " + right;
+  let prepend = flip(append);
+
+  assert.equal(append("hello", "world"), "hello world");
+  assert.equal(prepend("hello", "world"), "world hello");
+
+  let wrap = function(left, right) {
+    return left + " " + this + " " + right;
+  };
+  let invertWrap = flip(wrap);
+
+  assert.equal(wrap.call("@", "hello", "world"), "hello @ world");
+  assert.equal(invertWrap.call("@", "hello", "world"), "world @ hello");
+
+  let reverse = flip((...args) => args);
+
+  assert.deepEqual(reverse(1, 2, 3, 4), [4, 3, 2, 1]);
+  assert.deepEqual(reverse(1), [1]);
+  assert.deepEqual(reverse(), []);
+
+  // currying still works
+  let prependr = curry(prepend);
+
+  assert.equal(prependr("hello", "world"), "world hello");
+  assert.equal(prependr("hello")("world"), "world hello");
+};
+
+exports["test when"] = assert => {
+  let { when } = require("sdk/lang/functional");
+
+  let areNums = (...xs) => xs.every(x => typeof(x) === "number");
+
+  let sum = when(areNums, (...xs) => xs.reduce((y, x) => x + y, 0));
+
+  assert.equal(sum(1, 2, 3), 6);
+  assert.equal(sum(1, 2, "3"), undefined);
+
+  let multiply = when(areNums,
+                      (...xs) => xs.reduce((y, x) => x * y, 1),
+                      (...xs) => xs);
+
+  assert.equal(multiply(2), 2);
+  assert.equal(multiply(2, 3), 6);
+  assert.deepEqual(multiply(2, "4"), [2, "4"]);
+
+  function Point(x, y) {
+    this.x = x;
+    this.y = y;
+  }
+
+  let isPoint = x => x instanceof Point;
+
+  let inc = when(isPoint, ({x, y}) => new Point(x + 1, y + 1));
+
+  assert.equal(inc({}), undefined);
+  assert.deepEqual(inc(new Point(0, 0)), { x: 1, y: 1 });
+
+  let axis = when(isPoint,
+                  ({ x, y }) => [x, y],
+                  _ => [0, 0]);
+
+  assert.deepEqual(axis(new Point(1, 4)), [1, 4]);
+  assert.deepEqual(axis({ foo: "bar" }), [0, 0]);
+};
+
+exports["test chainable"] = function(assert) {
   let Player = function () { this.volume = 5; };
   Player.prototype = {
-    setBand: chain(function (band) this.band = band),
-    incVolume: chain(function () this.volume++)
+    setBand: chainable(function (band) { return (this.band = band); }),
+    incVolume: chainable(function () { return this.volume++; })
   };
   let player = new Player();
   player
     .setBand('Animals As Leaders')
     .incVolume().incVolume().incVolume().incVolume().incVolume().incVolume();
 
   assert.equal(player.band, 'Animals As Leaders', 'passes arguments into chained');
   assert.equal(player.volume, 11, 'accepts no arguments in chain');
 };
 
+exports["test field"] = assert => {
+  let Num = field("constructor", 0);
+  assert.equal(Num.name, Number.name);
+  assert.ok(typeof(Num), "function");
+
+  let x = field("x");
+
+  [
+    [field("foo", { foo: 1 }), 1],
+    [field("foo")({ foo: 1 }), 1],
+    [field("bar", {}), undefined],
+    [field("bar")({}), undefined],
+    [field("hey", undefined), undefined],
+    [field("hey")(undefined), undefined],
+    [field("how", null), null],
+    [field("how")(null), null],
+    [x(1), undefined],
+    [x(undefined), undefined],
+    [x(null), null],
+    [x({ x: 1 }), 1],
+    [x({ x: 2 }), 2],
+  ].forEach(([actual, expected]) => assert.equal(actual, expected));
+};
+
+exports["test query"] = assert => {
+  let Num = query("constructor", 0);
+  assert.equal(Num.name, Number.name);
+  assert.ok(typeof(Num), "function");
+
+  let x = query("x");
+  let xy = query("x.y");
+
+  [
+    [query("foo", { foo: 1 }), 1],
+    [query("foo")({ foo: 1 }), 1],
+    [query("foo.bar", { foo: { bar: 2 } }), 2],
+    [query("foo.bar")({ foo: { bar: 2 } }), 2],
+    [query("foo.bar", { foo: 1 }), undefined],
+    [query("foo.bar")({ foo: 1 }), undefined],
+    [x(1), undefined],
+    [x(undefined), undefined],
+    [x(null), null],
+    [x({ x: 1 }), 1],
+    [x({ x: 2 }), 2],
+    [xy(1), undefined],
+    [xy(undefined), undefined],
+    [xy(null), null],
+    [xy({ x: 1 }), undefined],
+    [xy({ x: 2 }), undefined],
+    [xy({ x: { y: 1 } }), 1],
+    [xy({ x: { y: 2 } }), 2]
+  ].forEach(([actual, expected]) => assert.equal(actual, expected));
+};
+
+exports["test isInstance"] = assert => {
+  function X() {}
+  function Y() {}
+  let isX = isInstance(X);
+
+  [
+    isInstance(X, new X()),
+    isInstance(X)(new X()),
+    !isInstance(X, new Y()),
+    !isInstance(X)(new Y()),
+    isX(new X()),
+    !isX(new Y())
+  ].forEach(x => assert.ok(x));
+};
+
+exports["test is"] = assert => {
+
+  assert.deepEqual([ 1, 0, 1, 0, 1 ].map(is(1)),
+                   [ true, false, true, false, true ],
+                   "is can be partially applied");
+
+  assert.ok(is(1, 1));
+  assert.ok(!is({}, {}));
+  assert.ok(is()(1)()(1), "is is curried");
+  assert.ok(!is()(1)()(2));
+};
+
+exports["test isnt"] = assert => {
+
+  assert.deepEqual([ 1, 0, 1, 0, 1 ].map(isnt(0)),
+                   [ true, false, true, false, true ],
+                   "is can be partially applied");
+
+  assert.ok(!isnt(1, 1));
+  assert.ok(isnt({}, {}));
+  assert.ok(!isnt()(1)()(1));
+  assert.ok(isnt()(1)()(2));
+};
+
 require('test').run(exports);
--- a/browser/app/blocklist.xml
+++ b/browser/app/blocklist.xml
@@ -1,10 +1,10 @@
 <?xml version="1.0"?>
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1385765544000">
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist" lastupdate="1386360478000">
   <emItems>
       <emItem  blockID="i454" id="sqlmoz@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                                 <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i58" id="webmaster@buzzzzvideos.info">
@@ -82,16 +82,20 @@
                     </versionRange>
                                 <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i406" id="{bf7380fa-e3b4-4db2-af3e-9d8783a45bfc}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i492" id="{af95cc15-3b9b-45ae-8d9b-98d08eda3111}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i24" id="{6E19037A-12E3-4295-8915-ED48BC341614}">
                         <versionRange  minVersion="0.1" maxVersion="1.3.328.4" severity="1">
                       <targetApplication  id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
                               <versionRange  minVersion="3.7a1pre" maxVersion="*" />
                           </targetApplication>
                     </versionRange>
                   </emItem>
       <emItem  blockID="i64" id="royal@facebook.com">
@@ -109,16 +113,20 @@
       <emItem  blockID="i40" id="{28387537-e3f9-4ed7-860c-11e69af4a8a0}">
                         <versionRange  minVersion="0.1" maxVersion="4.3.1.00" severity="1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i20" id="{AB2CE124-6272-4b12-94A9-7303C7397BD1}">
                         <versionRange  minVersion="0.1" maxVersion="5.2.0.7164" severity="1">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i498" id="hoverst@facebook.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i430" id="1chtw@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i78" id="socialnetworktools@mozilla.doslash.org">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
@@ -148,20 +156,32 @@
       <emItem  blockID="i172" id="info@bflix.info">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i140" id="mozillahmpg@mozilla.org">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i500" id="{2aab351c-ad56-444c-b935-38bffe18ad26}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
+      <emItem  blockID="i494" id="/^({e9df9360-97f8-4690-afe6-996c80790da4}|{687578b9-7132-4a7a-80e4-30ee31099e03}|{46a3135d-3683-48cf-b94c-82655cbc0e8a}|{49c795c2-604a-4d18-aeb1-b3eba27e5ea2}|{7473b6bd-4691-4744-a82b-7854eb3d70b6}|{96f454ea-9d38-474f-b504-56193e00c1a5})$/">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i467" id="plugin@analytic-s.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i496" id="{ACAA314B-EEBA-48e4-AD47-84E31C44796C}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i360" id="ytd@mybrowserbar.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i5" id="support@daemon-tools.cc">
                         <versionRange  minVersion=" " maxVersion="1.0.0.5">
                     </versionRange>
                   </emItem>
@@ -211,16 +231,20 @@
       <emItem  blockID="i438" id="{02edb56b-9b33-435b-b7df-b2843273a694}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i450" id="{dff137ae-1ffd-11e3-8277-b8ac6f996f26}">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i499" id="{babb9931-ad56-444c-b935-38bffe18ad26}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i111" os="WINNT" id="{C3949AC2-4B17-43ee-B4F1-D26B9D42404D}">
                         <versionRange  minVersion="0" maxVersion="15.0.5" severity="1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i136" id="Adobe@flash.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
@@ -251,19 +275,21 @@
       <emItem  blockID="i42" id="{D19CA586-DD6C-4a0a-96F8-14644F340D60}">
                         <versionRange  minVersion="0.1" maxVersion="14.4.0" severity="1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i449" id="gystqfr@ylgga.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
-      <emItem  blockID="i487" id="{df6bb2ec-333b-4267-8c4f-3f27dc8c6e07}">
+      <emItem  blockID="i502" id="{df6bb2ec-333b-4267-8c4f-3f27dc8c6e07}">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
+                                <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
                   </emItem>
       <emItem  blockID="i142" id="{a3a5c777-f583-4fef-9380-ab4add1bc2a8}">
                         <versionRange  minVersion="2.0.3" maxVersion="2.0.3">
                     </versionRange>
                                 <versionRange  minVersion="4.2" maxVersion="4.2" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i306" id="{ADFA33FD-16F5-4355-8504-DF4D664CFE10}">
@@ -380,16 +406,20 @@
                     </versionRange>
                   </emItem>
       <emItem  blockID="i376" id="{9e09ac65-43c0-4b9d-970f-11e2e9616c55}">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i44" id="sigma@labs.mozilla">
                         </emItem>
+      <emItem  blockID="i501" id="xivars@aol.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i482" id="brasilescapeeight@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i246" id="support@vide1flash2.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
@@ -432,16 +462,20 @@
       <emItem  blockID="i52" id="ff-ext@youtube">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i471" id="firefox@luckyleap.net">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i495" id="kallow@facebook.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i483" id="brasilescapefive@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i43" id="supportaccessplugin@gmail.com">
                         </emItem>
       <emItem  blockID="i340" id="chiang@programmer.net">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
@@ -450,16 +484,20 @@
       <emItem  blockID="i54" id="applebeegifts@mozilla.doslash.org">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i16" id="{27182e60-b5f3-411c-b545-b44205977502}">
                         <versionRange  minVersion="1.0" maxVersion="1.0">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i504" id="aytac@abc.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i92" id="play5@vide04flash.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i474" id="{906000a4-88d9-4d52-b209-7a772970d91f}">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
@@ -492,16 +530,20 @@
                     </versionRange>
                   </emItem>
       <emItem  blockID="i13" id="{E8E88AB0-7182-11DF-904E-6045E0D72085}">
                         </emItem>
       <emItem  blockID="i394" id="{7D4F1959-3F72-49d5-8E59-F02F8AA6815D}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i493" id="12x3q@3244516.com">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i344" id="lrcsTube@hansanddeta.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i461" id="{8E9E3331-D360-4f87-8803-52DE43566502}">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
@@ -677,16 +719,20 @@
       <emItem  blockID="i312" id="extension21804@extension21804.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="1">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i324" id="/^((34qEOefiyYtRJT@IM5Munavn\.com)|(Mro5Fm1Qgrmq7B@ByrE69VQfZvZdeg\.com)|(KtoY3KGxrCe5ie@yITPUzbBtsHWeCdPmGe\.com)|(9NgIdLK5Dq4ZMwmRo6zk@FNt2GCCLGyUuOD\.com)|(NNux7bWWW@RBWyXdnl6VGls3WAwi\.com)|(E3wI2n@PEHTuuNVu\.com)|(2d3VuWrG6JHBXbQdbr@3BmSnQL\.com))$/">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i503" id="{9CE11043-9A15-4207-A565-0C94C42D590D}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="3">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i431" id="chinaescapeone@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i108" id="{28bfb930-7620-11e1-b0c4-0800200c9a66}">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
@@ -786,16 +832,20 @@
       <emItem  blockID="i90" id="videoplugin@player.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i489" id="astrovia@facebook.com">
                         <versionRange  minVersion="0" maxVersion="*" severity="3">
                     </versionRange>
                   </emItem>
+      <emItem  blockID="i497" id="{872b5b88-9db5-4310-bdd0-ac189557e5f5}">
+                        <versionRange  minVersion="0" maxVersion="*" severity="1">
+                    </versionRange>
+                  </emItem>
       <emItem  blockID="i68" id="flashupdate@adobe.com">
                         <versionRange  minVersion="0" maxVersion="*">
                     </versionRange>
                   </emItem>
       <emItem  blockID="i445" id="firefoxaddon@youtubeenhancer.com">
                         <versionRange  minVersion="208.7.0" maxVersion="208.7.0" severity="3">
                     </versionRange>
                   </emItem>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1088,17 +1088,17 @@ pref("devtools.appmanager.enabled", true
 pref("devtools.appmanager.lastTab", "help");
 pref("devtools.appmanager.manifestEditor.enabled", true);
 
 // Toolbox preferences
 pref("devtools.toolbox.footer.height", 250);
 pref("devtools.toolbox.sidebar.width", 500);
 pref("devtools.toolbox.host", "bottom");
 pref("devtools.toolbox.selectedTool", "webconsole");
-pref("devtools.toolbox.toolbarSpec", '["paintflashing toggle","tilt toggle","scratchpad","resize toggle"]');
+pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","tilt toggle","scratchpad","resize toggle"]');
 pref("devtools.toolbox.sideEnabled", true);
 pref("devtools.toolbox.zoomValue", "1");
 
 // Enable the Inspector
 pref("devtools.inspector.enabled", true);
 pref("devtools.inspector.activeSidebar", "ruleview");
 pref("devtools.inspector.markupPreview", false);
 pref("devtools.inspector.remote", false);
--- a/browser/components/tabview/test/browser.ini
+++ b/browser/components/tabview/test/browser.ini
@@ -36,16 +36,17 @@ support-files =
 [browser_tabview_bug595965.js]
 [browser_tabview_bug596781.js]
 [browser_tabview_bug597360.js]
 [browser_tabview_bug597399.js]
 [browser_tabview_bug598375.js]
 [browser_tabview_bug598600.js]
 [browser_tabview_bug599048.js]
 [browser_tabview_bug599626.js]
+skip-if = os == 'linux' # Bug 939620, much fail, so amaze
 [browser_tabview_bug600645.js]
 [browser_tabview_bug600812.js]
 [browser_tabview_bug604098.js]
 [browser_tabview_bug606657.js]
 [browser_tabview_bug606905.js]
 [browser_tabview_bug607108.js]
 [browser_tabview_bug608037.js]
 [browser_tabview_bug608153.js]
--- a/browser/devtools/commandline/BuiltinCommands.jsm
+++ b/browser/devtools/commandline/BuiltinCommands.jsm
@@ -2267,8 +2267,73 @@ gcli.addCommand({
     manual: gcli.lookup("mediaEmulateManual"),
     exec: function(args, context) {
       let markupDocumentViewer = context.environment.chromeWindow
                                         .gBrowser.markupDocumentViewer;
       markupDocumentViewer.stopEmulatingMedium();
     }
   });
 }(this));
+
+/* CmdSplitConsole ------------------------------------------------------- */
+
+(function(module) {
+  /**
+   * 'splitconsole' command (hidden)
+   */
+
+  gcli.addCommand({
+    name: 'splitconsole',
+    hidden: true,
+    buttonId: "command-button-splitconsole",
+    buttonClass: "command-button",
+    tooltipText: gcli.lookup("splitconsoleTooltip"),
+    state: {
+      isChecked: function(aTarget) {
+        let toolbox = gDevTools.getToolbox(aTarget);
+        return toolbox &&
+          toolbox.splitConsole;
+      },
+      onChange: function(aTarget, aChangeHandler) {
+        eventEmitter.on("changed", aChangeHandler);
+      },
+      offChange: function(aTarget, aChangeHandler) {
+        eventEmitter.off("changed", aChangeHandler);
+      },
+    },
+    exec: function(args, context) {
+      toggleSplitConsole(context);
+    }
+  });
+
+  function toggleSplitConsole(context) {
+    let gBrowser = context.environment.chromeDocument.defaultView.gBrowser;
+    let target = devtools.TargetFactory.forTab(gBrowser.selectedTab);
+    let toolbox = gDevTools.getToolbox(target);
+
+    if (!toolbox) {
+      gDevTools.showToolbox(target, "inspector").then((toolbox) => {
+        toolbox.toggleSplitConsole();
+      });
+    } else {
+      toolbox.toggleSplitConsole();
+    }
+  }
+
+  let eventEmitter = new EventEmitter();
+  function fireChange(tab) {
+    eventEmitter.emit("changed", tab);
+  }
+
+  gDevTools.on("toolbox-ready", (e, toolbox) => {
+    if (!toolbox.target) {
+      return;
+    }
+    let fireChangeForTab = fireChange.bind(this, toolbox.target.tab);
+    toolbox.on("split-console", fireChangeForTab);
+    toolbox.on("select", fireChangeForTab);
+    toolbox.once("destroyed", () => {
+      toolbox.off("split-console", fireChangeForTab);
+      toolbox.off("select", fireChangeForTab);
+    });
+  });
+
+}(this));
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -80,18 +80,16 @@ support-files =
 [browser_dbg_breakpoints-contextmenu.js]
 [browser_dbg_breakpoints-disabled-reload.js]
 [browser_dbg_breakpoints-editor.js]
 [browser_dbg_breakpoints-highlight.js]
 [browser_dbg_breakpoints-new-script.js]
 [browser_dbg_breakpoints-pane.js]
 [browser_dbg_chrome-debugging.js]
 [browser_dbg_clean-exit-window.js]
-# Disabled because of leaks - See Bug 933950.
-skip-if = true
 [browser_dbg_clean-exit.js]
 [browser_dbg_closure-inspection.js]
 [browser_dbg_cmd-blackbox.js]
 [browser_dbg_cmd-break.js]
 [browser_dbg_cmd-dbg.js]
 [browser_dbg_conditional-breakpoints-01.js]
 [browser_dbg_conditional-breakpoints-02.js]
 [browser_dbg_controller-evaluate-01.js]
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -178,16 +178,23 @@ Toolbox.prototype = {
   /**
    * Get current zoom level of toolbox
    */
   get zoomValue() {
     return parseFloat(Services.prefs.getCharPref(ZOOM_PREF));
   },
 
   /**
+   * Get the toggled state of the split console
+   */
+  get splitConsole() {
+    return this._splitConsole;
+  },
+
+  /**
    * Open the toolbox
    */
   open: function() {
     let deferred = promise.defer();
 
     return this._host.create().then(iframe => {
       let deferred = promise.defer();
 
--- a/browser/devtools/webconsole/test/browser_webconsole_split.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_split.js
@@ -58,32 +58,34 @@ function test()
       yield checkWebconsolePanelOpened();
       testBottomHost();
     });
   }
 
   function getCurrentUIState()
   {
     let win = toolbox.doc.defaultView;
-    let deck = toolbox.doc.getElementById("toolbox-deck");
-    let webconsolePanel = toolbox.doc.getElementById("toolbox-panel-webconsole");
-    let splitter = toolbox.doc.getElementById("toolbox-console-splitter");
+    let deck = toolbox.doc.querySelector("#toolbox-deck");
+    let webconsolePanel = toolbox.doc.querySelector("#toolbox-panel-webconsole");
+    let splitter = toolbox.doc.querySelector("#toolbox-console-splitter");
 
     let containerHeight = parseFloat(win.getComputedStyle(deck.parentNode).getPropertyValue("height"));
     let deckHeight = parseFloat(win.getComputedStyle(deck).getPropertyValue("height"));
     let webconsoleHeight = parseFloat(win.getComputedStyle(webconsolePanel).getPropertyValue("height"));
     let splitterVisibility = !splitter.getAttribute("hidden");
     let openedConsolePanel = toolbox.currentToolId === "webconsole";
+    let cmdButton = toolbox.doc.querySelector("#command-button-splitconsole");
 
     return {
       deckHeight: deckHeight,
       containerHeight: containerHeight,
       webconsoleHeight: webconsoleHeight,
       splitterVisibility: splitterVisibility,
-      openedConsolePanel: openedConsolePanel
+      openedConsolePanel: openedConsolePanel,
+      buttonSelected: cmdButton.hasAttribute("checked")
     };
   }
 
   function checkWebconsolePanelOpened()
   {
     info("About to check special cases when webconsole panel is open.");
 
     let deferred = promise.defer();
@@ -92,43 +94,47 @@ function test()
     toolbox.toggleSplitConsole();
 
     let currentUIState = getCurrentUIState();
 
     ok (currentUIState.splitterVisibility, "Splitter is visible when console is split");
     ok (currentUIState.deckHeight > 0, "Deck has a height > 0 when console is split");
     ok (currentUIState.webconsoleHeight > 0, "Web console has a height > 0 when console is split");
     ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
+    ok (currentUIState.buttonSelected, "The command button is selected");
 
     openPanel("webconsole").then(() => {
 
       let currentUIState = getCurrentUIState();
 
       ok (!currentUIState.splitterVisibility, "Splitter is hidden when console is opened.");
       is (currentUIState.deckHeight, 0, "Deck has a height == 0 when console is opened.");
       is (currentUIState.webconsoleHeight, currentUIState.containerHeight, "Web console is full height.");
       ok (currentUIState.openedConsolePanel, "The console panel is the current tool");
+      ok (currentUIState.buttonSelected, "The command button is still selected.");
 
       // Make sure splitting console does nothing while webconsole is opened
       toolbox.toggleSplitConsole();
 
       let currentUIState = getCurrentUIState();
 
       ok (!currentUIState.splitterVisibility, "Splitter is hidden when console is opened.");
       is (currentUIState.deckHeight, 0, "Deck has a height == 0 when console is opened.");
       is (currentUIState.webconsoleHeight, currentUIState.containerHeight, "Web console is full height.");
       ok (currentUIState.openedConsolePanel, "The console panel is the current tool");
+      ok (currentUIState.buttonSelected, "The command button is still selected.");
 
       // Make sure that split state is saved after opening another panel
       openPanel("inspector").then(() => {
         let currentUIState = getCurrentUIState();
         ok (currentUIState.splitterVisibility, "Splitter is visible when console is split");
         ok (currentUIState.deckHeight > 0, "Deck has a height > 0 when console is split");
         ok (currentUIState.webconsoleHeight > 0, "Web console has a height > 0 when console is split");
         ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
+        ok (currentUIState.buttonSelected, "The command button is still selected.");
 
         toolbox.toggleSplitConsole();
         deferred.resolve();
 
       });
     });
     return deferred.promise;
   }
@@ -158,40 +164,42 @@ function test()
   function checkToolboxUI()
   {
     let currentUIState = getCurrentUIState();
 
     ok (!currentUIState.splitterVisibility, "Splitter is hidden by default");
     is (currentUIState.deckHeight, currentUIState.containerHeight, "Deck has a height > 0 by default");
     is (currentUIState.webconsoleHeight, 0, "Web console is collapsed by default");
     ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
+    ok (!currentUIState.buttonSelected, "The command button is not selected.");
 
     toolbox.toggleSplitConsole();
 
     let currentUIState = getCurrentUIState();
 
     ok (currentUIState.splitterVisibility, "Splitter is visible when console is split");
     ok (currentUIState.deckHeight > 0, "Deck has a height > 0 when console is split");
     ok (currentUIState.webconsoleHeight > 0, "Web console has a height > 0 when console is split");
     is (currentUIState.deckHeight + currentUIState.webconsoleHeight,
           currentUIState.containerHeight,
         "Everything adds up to container height");
     ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
+    ok (currentUIState.buttonSelected, "The command button is selected.");
 
     toolbox.toggleSplitConsole();
 
     let currentUIState = getCurrentUIState();
 
     ok (!currentUIState.splitterVisibility, "Splitter is hidden after toggling");
     is (currentUIState.deckHeight, currentUIState.containerHeight, "Deck has a height > 0 after toggling");
     is (currentUIState.webconsoleHeight, 0, "Web console is collapsed after toggling");
     ok (!currentUIState.openedConsolePanel, "The console panel is not the current tool");
+    ok (!currentUIState.buttonSelected, "The command button is not selected.");
   }
 
-
   function testBottomHost()
   {
     checkHostType(Toolbox.HostType.BOTTOM);
 
     checkToolboxUI();
 
     toolbox.switchHost(Toolbox.HostType.SIDE).then(testSidebarHost);
   }
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,4 +1,4 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 0.8.681
+Current extension version is: 0.8.759
 
--- a/browser/extensions/pdfjs/components/PdfStreamConverter.js
+++ b/browser/extensions/pdfjs/components/PdfStreamConverter.js
@@ -11,39 +11,43 @@
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 /* jshint esnext:true */
 /* globals Components, Services, XPCOMUtils, NetUtil, PrivateBrowsingUtils,
-           dump, NetworkManager, PdfJsTelemetry */
+           dump, NetworkManager, PdfJsTelemetry, DEFAULT_PREFERENCES */
 
 'use strict';
 
 var EXPORTED_SYMBOLS = ['PdfStreamConverter'];
 
 const Cc = Components.classes;
 const Ci = Components.interfaces;
 const Cr = Components.results;
 const Cu = Components.utils;
 // True only if this is the version of pdf.js that is included with firefox.
 const MOZ_CENTRAL = JSON.parse('true');
 const PDFJS_EVENT_ID = 'pdf.js.message';
 const PDF_CONTENT_TYPE = 'application/pdf';
 const PREF_PREFIX = 'pdfjs';
 const PDF_VIEWER_WEB_PAGE = 'resource://pdf.js/web/viewer.html';
 const MAX_DATABASE_LENGTH = 4096;
+const MAX_STRING_PREF_LENGTH = 4096;
 
 Cu.import('resource://gre/modules/XPCOMUtils.jsm');
 Cu.import('resource://gre/modules/Services.jsm');
 Cu.import('resource://gre/modules/NetUtil.jsm');
 Cu.import('resource://pdf.js/network.js');
 
+// Load the default preferences.
+Cu.import('resource://pdf.js/default_preferences.js');
+
 XPCOMUtils.defineLazyModuleGetter(this, 'PrivateBrowsingUtils',
   'resource://gre/modules/PrivateBrowsingUtils.jsm');
 
 XPCOMUtils.defineLazyModuleGetter(this, 'PdfJsTelemetry',
   'resource://pdf.js/PdfJsTelemetry.jsm');
 
 var Svc = {};
 XPCOMUtils.defineLazyServiceGetter(Svc, 'mime',
@@ -53,24 +57,32 @@ XPCOMUtils.defineLazyServiceGetter(Svc, 
 function getChromeWindow(domWindow) {
   var containingBrowser = domWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                    .getInterface(Ci.nsIWebNavigation)
                                    .QueryInterface(Ci.nsIDocShell)
                                    .chromeEventHandler;
   return containingBrowser.ownerDocument.defaultView;
 }
 
+function setBoolPref(pref, value) {
+  Services.prefs.setBoolPref(pref, value);
+}
+
 function getBoolPref(pref, def) {
   try {
     return Services.prefs.getBoolPref(pref);
   } catch (ex) {
     return def;
   }
 }
 
+function setIntPref(pref, value) {
+  Services.prefs.setIntPref(pref, value);
+}
+
 function getIntPref(pref, def) {
   try {
     return Services.prefs.getIntPref(pref);
   } catch (ex) {
     return def;
   }
 }
 
@@ -426,27 +438,86 @@ ChromeActions.prototype = {
     var findPrevious = data.findPrevious;
     var findPreviousType = typeof findPrevious;
     if ((typeof result !== 'number' || result < 0 || result > 3) ||
         (findPreviousType !== 'undefined' && findPreviousType !== 'boolean')) {
       return;
     }
     getChromeWindow(this.domWindow).gFindBar
                                    .updateControlState(result, findPrevious);
+  },
+  setPreferences: function(prefs) {
+    var prefValue, defaultValue, prefName, prefType, defaultType;
+
+    for (var key in DEFAULT_PREFERENCES) {
+      prefValue = prefs[key];
+      defaultValue = DEFAULT_PREFERENCES[key];
+      prefName = (PREF_PREFIX + '.' + key);
+
+      if (prefValue === undefined || prefValue === defaultValue) {
+        Services.prefs.clearUserPref(prefName);
+      } else {
+        prefType = typeof prefValue;
+        defaultType = typeof defaultValue;
+
+        if (prefType !== defaultType) {
+          continue;
+        }
+        switch (defaultType) {
+          case 'boolean':
+            setBoolPref(prefName, prefValue);
+            break;
+          case 'number':
+            setIntPref(prefName, prefValue);
+            break;
+          case 'string':
+            // Protect against adding arbitrarily long strings in about:config.
+            if (prefValue.length <= MAX_STRING_PREF_LENGTH) {
+              setStringPref(prefName, prefValue);
+            }
+            break;
+        }
+      }
+    }
+  },
+  getPreferences: function() {
+    var currentPrefs = {};
+    var defaultValue, prefName;
+
+    for (var key in DEFAULT_PREFERENCES) {
+      defaultValue = DEFAULT_PREFERENCES[key];
+      prefName = (PREF_PREFIX + '.' + key);
+
+      switch (typeof defaultValue) {
+        case 'boolean':
+          currentPrefs[key] = getBoolPref(prefName, defaultValue);
+          break;
+        case 'number':
+          currentPrefs[key] = getIntPref(prefName, defaultValue);
+          break;
+        case 'string':
+          currentPrefs[key] = getStringPref(prefName, defaultValue);
+          break;
+      }
+    }
+    return JSON.stringify(currentPrefs);
   }
 };
 
 var RangedChromeActions = (function RangedChromeActionsClosure() {
   /**
    * This is for range requests
    */
   function RangedChromeActions(
-              domWindow, contentDispositionFilename, originalRequest) {
+              domWindow, contentDispositionFilename, originalRequest,
+              dataListener) {
 
     ChromeActions.call(this, domWindow, contentDispositionFilename);
+    this.dataListener = dataListener;
+    this.originalRequest = originalRequest;
 
     this.pdfUrl = originalRequest.URI.spec;
     this.contentLength = originalRequest.contentLength;
 
     // Pass all the headers from the original request through
     var httpHeaderVisitor = {
       headers: {},
       visitHeader: function(aHeader, aValue) {
@@ -482,21 +553,25 @@ var RangedChromeActions = (function Rang
     });
   }
 
   RangedChromeActions.prototype = Object.create(ChromeActions.prototype);
   var proto = RangedChromeActions.prototype;
   proto.constructor = RangedChromeActions;
 
   proto.initPassiveLoading = function RangedChromeActions_initPassiveLoading() {
+    this.originalRequest.cancel(Cr.NS_BINDING_ABORTED);
+    this.originalRequest = null;
     this.domWindow.postMessage({
       pdfjsLoadAction: 'supportsRangedLoading',
       pdfUrl: this.pdfUrl,
-      length: this.contentLength
+      length: this.contentLength,
+      data: this.dataListener.getData()
     }, '*');
+    this.dataListener = null;
 
     return true;
   };
 
   proto.requestDataRange = function RangedChromeActions_requestDataRange(args) {
     var begin = args.begin;
     var end = args.end;
     var domWindow = this.domWindow;
@@ -687,18 +762,18 @@ PdfStreamConverter.prototype = {
       Ci.nsIRequestObserver
   ]),
 
   /*
    * This component works as such:
    * 1. asyncConvertData stores the listener
    * 2. onStartRequest creates a new channel, streams the viewer
    * 3. If range requests are supported:
-   *      3.1. Suspends and cancels the request so we can issue range
-   *          requests instead.
+   *      3.1. Leave the request open until the viewer is ready to switch to
+   *           range requests.
    *
    *    If range rquests are not supported:
    *      3.1. Read the stream as it's loaded in onDataAvailable to send
    *           to the viewer
    *
    * The convert function just returns the stream, it's just the synchronous
    * version of asyncConvertData.
    */
@@ -774,28 +849,22 @@ PdfStreamConverter.prototype = {
       aRequest.setResponseHeader('X-Content-Security-Policy', '', false);
       aRequest.setResponseHeader('X-Content-Security-Policy-Report-Only', '',
                                  false);
     }
 
     PdfJsTelemetry.onViewerIsUsed();
     PdfJsTelemetry.onDocumentSize(aRequest.contentLength);
 
-    if (!rangeRequest) {
-      // Creating storage for PDF data
-      var contentLength = aRequest.contentLength;
-      this.dataListener = new PdfDataListener(contentLength);
-      this.binaryStream = Cc['@mozilla.org/binaryinputstream;1']
-                          .createInstance(Ci.nsIBinaryInputStream);
-    } else {
-      // Suspend the request so we're not consuming any of the stream,
-      // but we can't cancel the request yet. Otherwise, the original
-      // listener will think we do not want to go the new PDF url
-      aRequest.suspend();
-    }
+
+    // Creating storage for PDF data
+    var contentLength = aRequest.contentLength;
+    this.dataListener = new PdfDataListener(contentLength);
+    this.binaryStream = Cc['@mozilla.org/binaryinputstream;1']
+                        .createInstance(Ci.nsIBinaryInputStream);
 
     // Create a new channel that is viewer loaded as a resource.
     var ioService = Services.io;
     var channel = ioService.newChannel(
                     PDF_VIEWER_WEB_PAGE, null, null);
 
     var listener = this.listener;
     var dataListener = this.dataListener;
@@ -811,22 +880,18 @@ PdfStreamConverter.prototype = {
         listener.onDataAvailable(aRequest, context, inputStream, offset, count);
       },
       onStopRequest: function(request, context, statusCode) {
         // We get the DOM window here instead of before the request since it
         // may have changed during a redirect.
         var domWindow = getDOMWindow(channel);
         var actions;
         if (rangeRequest) {
-          // We are going to be issuing range requests, so cancel the
-          // original request
-          aRequest.resume();
-          aRequest.cancel(Cr.NS_BINDING_ABORTED);
-          actions = new RangedChromeActions(domWindow,
-              contentDispositionFilename, aRequest);
+          actions = new RangedChromeActions(
+              domWindow, contentDispositionFilename, aRequest, dataListener);
         } else {
           actions = new StandardChromeActions(
               domWindow, contentDispositionFilename, dataListener);
         }
         var requestListener = new RequestListener(actions);
         domWindow.addEventListener(PDFJS_EVENT_ID, function(event) {
           requestListener.receive(event);
         }, false, true);
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -15,18 +15,18 @@
  * limitations under the License.
  */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '0.8.681';
-PDFJS.build = '48c672b';
+PDFJS.version = '0.8.759';
+PDFJS.build = 'd3b5aa3';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -38,17 +38,17 @@ PDFJS.build = '48c672b';
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref */
+/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL */
 
 'use strict';
 
 var globalScope = (typeof window === 'undefined') ? this : window;
 
 var isWorker = (typeof window == 'undefined');
 
 var ERRORS = 0, WARNINGS = 1, INFOS = 5;
@@ -73,16 +73,108 @@ var TextRenderingMode = {
 // In production, it will be declared outside a global wrapper
 // In development, it will be declared here
 if (!globalScope.PDFJS) {
   globalScope.PDFJS = {};
 }
 
 globalScope.PDFJS.pdfBug = false;
 
+// All the possible operations for an operator list.
+var OPS = PDFJS.OPS = {
+  // Intentionally start from 1 so it is easy to spot bad operators that will be
+  // 0's.
+  dependency: 1,
+  setLineWidth: 2,
+  setLineCap: 3,
+  setLineJoin: 4,
+  setMiterLimit: 5,
+  setDash: 6,
+  setRenderingIntent: 7,
+  setFlatness: 8,
+  setGState: 9,
+  save: 10,
+  restore: 11,
+  transform: 12,
+  moveTo: 13,
+  lineTo: 14,
+  curveTo: 15,
+  curveTo2: 16,
+  curveTo3: 17,
+  closePath: 18,
+  rectangle: 19,
+  stroke: 20,
+  closeStroke: 21,
+  fill: 22,
+  eoFill: 23,
+  fillStroke: 24,
+  eoFillStroke: 25,
+  closeFillStroke: 26,
+  closeEOFillStroke: 27,
+  endPath: 28,
+  clip: 29,
+  eoClip: 30,
+  beginText: 31,
+  endText: 32,
+  setCharSpacing: 33,
+  setWordSpacing: 34,
+  setHScale: 35,
+  setLeading: 36,
+  setFont: 37,
+  setTextRenderingMode: 38,
+  setTextRise: 39,
+  moveText: 40,
+  setLeadingMoveText: 41,
+  setTextMatrix: 42,
+  nextLine: 43,
+  showText: 44,
+  showSpacedText: 45,
+  nextLineShowText: 46,
+  nextLineSetSpacingShowText: 47,
+  setCharWidth: 48,
+  setCharWidthAndBounds: 49,
+  setStrokeColorSpace: 50,
+  setFillColorSpace: 51,
+  setStrokeColor: 52,
+  setStrokeColorN: 53,
+  setFillColor: 54,
+  setFillColorN: 55,
+  setStrokeGray: 56,
+  setFillGray: 57,
+  setStrokeRGBColor: 58,
+  setFillRGBColor: 59,
+  setStrokeCMYKColor: 60,
+  setFillCMYKColor: 61,
+  shadingFill: 62,
+  beginInlineImage: 63,
+  beginImageData: 64,
+  endInlineImage: 65,
+  paintXObject: 66,
+  markPoint: 67,
+  markPointProps: 68,
+  beginMarkedContent: 69,
+  beginMarkedContentProps: 70,
+  endMarkedContent: 71,
+  beginCompat: 72,
+  endCompat: 73,
+  paintFormXObjectBegin: 74,
+  paintFormXObjectEnd: 75,
+  beginGroup: 76,
+  endGroup: 77,
+  beginAnnotations: 78,
+  endAnnotations: 79,
+  beginAnnotation: 80,
+  endAnnotation: 81,
+  paintJpegXObject: 82,
+  paintImageMaskXObject: 83,
+  paintImageMaskXObjectGroup: 84,
+  paintImageXObject: 85,
+  paintInlineImageXObject: 86,
+  paintInlineImageXObjectGroup: 87
+};
 
 // Use only for debugging purposes. This should not be used in any code that is
 // in mozilla master.
 var log = (function() {
   if ('console' in globalScope && 'log' in globalScope['console']) {
     return globalScope['console']['log'].bind(globalScope['console']);
   } else {
     return function nop() {
@@ -287,17 +379,17 @@ var NotImplementedException = (function 
 
   return NotImplementedException;
 })();
 
 var MissingDataException = (function MissingDataExceptionClosure() {
   function MissingDataException(begin, end) {
     this.begin = begin;
     this.end = end;
-    this.message = 'Missing data [begin, end)';
+    this.message = 'Missing data [' + begin + ', ' + end + ')';
   }
 
   MissingDataException.prototype = new Error();
   MissingDataException.prototype.name = 'MissingDataException';
   MissingDataException.constructor = MissingDataException;
 
   return MissingDataException;
 })();
@@ -860,17 +952,17 @@ var Promise = PDFJS.Promise = (function 
 
   function Promise() {
     this._status = STATUS_PENDING;
     this._handlers = [];
   }
   /**
    * Builds a promise that is resolved when all the passed in promises are
    * resolved.
-   * @param {Promise[]} promises Array of promises to wait for.
+   * @param {array} array of data and/or promises to wait for.
    * @return {Promise} New dependant promise.
    */
   Promise.all = function Promise_all(promises) {
     var deferred = new Promise();
     var unresolved = promises.length;
     var results = [];
     if (unresolved === 0) {
       deferred.resolve(results);
@@ -880,45 +972,58 @@ var Promise = PDFJS.Promise = (function 
       if (deferred._status === STATUS_REJECTED) {
         return;
       }
       results = [];
       deferred.reject(reason);
     }
     for (var i = 0, ii = promises.length; i < ii; ++i) {
       var promise = promises[i];
-      promise.then((function(i) {
+      var resolve = (function(i) {
         return function(value) {
           if (deferred._status === STATUS_REJECTED) {
             return;
           }
           results[i] = value;
           unresolved--;
           if (unresolved === 0)
             deferred.resolve(results);
         };
-      })(i), reject);
+      })(i);
+      if (Promise.isPromise(promise)) {
+        promise.then(resolve, reject);
+      } else {
+        resolve(promise);
+      }
     }
     return deferred;
   };
 
+  /**
+   * Checks if the value is likely a promise (has a 'then' function).
+   * @return {boolean} true if x is thenable
+   */
+  Promise.isPromise = function Promise_isPromise(value) {
+    return value && typeof value.then === 'function';
+  };
+
   Promise.prototype = {
     _status: null,
     _value: null,
     _handlers: null,
     _unhandledRejection: null,
 
     _updateStatus: function Promise__updateStatus(status, value) {
       if (this._status === STATUS_RESOLVED ||
           this._status === STATUS_REJECTED) {
         return;
       }
 
       if (status == STATUS_RESOLVED &&
-          value && typeof(value.then) === 'function') {
+          Promise.isPromise(value)) {
         value.then(this._updateStatus.bind(this, STATUS_RESOLVED),
                    this._updateStatus.bind(this, STATUS_REJECTED));
         return;
       }
 
       this._status = status;
       this._value = value;
 
@@ -1011,28 +1116,56 @@ var StatTimer = (function StatTimerClosu
       }
       return out;
     }
   };
   return StatTimer;
 })();
 
 PDFJS.createBlob = function createBlob(data, contentType) {
-  if (typeof Blob === 'function')
+  if (typeof Blob !== 'undefined')
     return new Blob([data], { type: contentType });
   // Blob builder is deprecated in FF14 and removed in FF18.
   var bb = new MozBlobBuilder();
   bb.append(data);
   return bb.getBlob(contentType);
 };
 
+PDFJS.createObjectURL = (function createObjectURLClosure() {
+  if (typeof URL !== 'undefined' && URL.createObjectURL) {
+    return function createObjectURL(data, contentType) {
+      var blob = PDFJS.createBlob(data, contentType);
+      return URL.createObjectURL(blob);
+    };
+  }
+
+  // Blob/createObjectURL is not available, falling back to data schema.
+  var digits =
+    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+
+  return function createObjectURL(data, contentType) {
+    var buffer = 'data:' + contentType + ';base64,';
+    for (var i = 0, ii = data.length; i < ii; i += 3) {
+      var b1 = data[i] & 0xFF;
+      var b2 = data[i + 1] & 0xFF;
+      var b3 = data[i + 2] & 0xFF;
+      var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
+      var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
+      var d4 = i + 2 < ii ? (b3 & 0x3F) : 64;
+      buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
+    }
+    return buffer;
+  };
+})();
+
 function MessageHandler(name, comObj) {
   this.name = name;
   this.comObj = comObj;
   this.callbackIndex = 1;
+  this.postMessageTransfers = true;
   var callbacks = this.callbacks = {};
   var ah = this.actionHandler = {};
 
   ah['console_log'] = [function ahConsoleLog(data) {
     log.apply(null, data);
   }];
   // If there's no console available, console_error in the
   // action handler will do nothing.
@@ -1089,37 +1222,42 @@ MessageHandler.prototype = {
     }
     ah[actionName] = [handler, scope];
   },
   /**
    * Sends a message to the comObj to invoke the action with the supplied data.
    * @param {String} actionName Action to call.
    * @param {JSON} data JSON data to send.
    * @param {function} [callback] Optional callback that will handle a reply.
+   * @param {Array} [transfers] Optional list of transfers/ArrayBuffers
    */
-  send: function messageHandlerSend(actionName, data, callback) {
+  send: function messageHandlerSend(actionName, data, callback, transfers) {
     var message = {
       action: actionName,
       data: data
     };
     if (callback) {
       var callbackId = this.callbackIndex++;
       this.callbacks[callbackId] = callback;
       message.callbackId = callbackId;
     }
-    this.comObj.postMessage(message);
+    if (transfers && this.postMessageTransfers) {
+      this.comObj.postMessage(message, transfers);
+    } else {
+      this.comObj.postMessage(message);
+    }
   }
 };
 
-function loadJpegStream(id, imageData, objs) {
+function loadJpegStream(id, imageUrl, objs) {
   var img = new Image();
   img.onload = (function loadJpegStream_onloadClosure() {
     objs.resolve(id, img);
   });
-  img.src = 'data:image/jpeg;base64,' + window.btoa(imageData);
+  img.src = imageUrl;
 }
 
 
 var ColorSpace = (function ColorSpaceClosure() {
   // Constructor should define this.numComps, this.defaultColor, this.name
   function ColorSpace() {
     error('should not call ColorSpace constructor');
   }
@@ -3425,19 +3563,19 @@ var Annotation = (function AnnotationClo
       var bbox = appearanceDict.get('BBox') || [0, 0, 1, 1];
       var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0];
       var transform = getTransformMatrix(data.rect, bbox, matrix);
 
       var border = data.border;
 
       resourcesPromise.then(function(resources) {
         var opList = new OperatorList();
-        opList.addOp('beginAnnotation', [data.rect, transform, matrix]);
+        opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
         evaluator.getOperatorList(this.appearance, resources, opList);
-        opList.addOp('endAnnotation', []);
+        opList.addOp(OPS.endAnnotation, []);
         promise.resolve(opList);
       }.bind(this));
 
       return promise;
     }
   };
 
   Annotation.getConstructor =
@@ -3521,22 +3659,22 @@ var Annotation = (function AnnotationClo
 
     var annotationsReadyPromise = new Promise();
 
     var annotationPromises = [];
     for (var i = 0, n = annotations.length; i < n; ++i) {
       annotationPromises.push(annotations[i].getOperatorList(partialEvaluator));
     }
     Promise.all(annotationPromises).then(function(datas) {
-      opList.addOp('beginAnnotations', []);
+      opList.addOp(OPS.beginAnnotations, []);
       for (var i = 0, n = datas.length; i < n; ++i) {
         var annotOpList = datas[i];
         opList.addOpList(annotOpList);
       }
-      opList.addOp('endAnnotations', []);
+      opList.addOp(OPS.endAnnotations, []);
       annotationsReadyPromise.resolve();
     }, reject);
 
     return annotationsReadyPromise;
   };
 
   return Annotation;
 })();
@@ -3702,32 +3840,32 @@ var TextWidgetAnnotation = (function Tex
       var appearanceArgsArray = opList.argsArray;
       var fnArray = [];
       var argsArray = [];
 
       // TODO(mack): Add support for stroke color
       data.rgb = [0, 0, 0];
       // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY!
       for (var i = 0, n = fnArray.length; i < n; ++i) {
-        var fnName = appearanceFnArray[i];
+        var fnId = appearanceFnArray[i];
         var args = appearanceArgsArray[i];
 
-        if (fnName === 'setFont') {
+        if (fnId === OPS.setFont) {
           data.fontRefName = args[0];
           var size = args[1];
           if (size < 0) {
             data.fontDirection = -1;
             data.fontSize = -size;
           } else {
             data.fontDirection = 1;
             data.fontSize = size;
           }
-        } else if (fnName === 'setFillRGBColor') {
+        } else if (fnId === OPS.setFillRGBColor) {
           data.rgb = args;
-        } else if (fnName === 'setFillGray') {
+        } else if (fnId === OPS.setFillGray) {
           var rgbValue = args[0] * 255;
           data.rgb = [rgbValue, rgbValue, rgbValue];
         }
       }
       promise.resolve(opList);
       return promise;
     }
   });
@@ -3968,17 +4106,19 @@ PDFJS.imageResourcesPath = PDFJS.imageRe
  * automatically if the browser doesn't support workers or sending typed arrays
  * to workers.
  * @var {Boolean}
  */
 PDFJS.disableWorker = PDFJS.disableWorker === undefined ?
                       false : PDFJS.disableWorker;
 
 /**
- * Path and filename of the worker file. Required when the worker is enabled.
+ * Path and filename of the worker file. Required when the worker is enabled in
+ * development mode. If unspecified in the production build, the worker will be
+ * loaded based on the location of the pdf.js file.
  * @var {String}
  */
 PDFJS.workerSrc = PDFJS.workerSrc === undefined ? null : PDFJS.workerSrc;
 
 /**
  * Disable range request loading of PDF files. When enabled and if the server
  * supports partial content requests then the PDF will be fetched in chunks.
  * Enabled (false) by default.
@@ -3998,28 +4138,37 @@ PDFJS.disableAutoFetch = PDFJS.disableAu
 
 /**
  * Enables special hooks for debugging PDF.js.
  * @var {Boolean}
  */
 PDFJS.pdfBug = PDFJS.pdfBug === undefined ? false : PDFJS.pdfBug;
 
 /**
+ * Enables transfer usage in postMessage for ArrayBuffers.
+ * @var {boolean}
+ */
+PDFJS.postMessageTransfers = PDFJS.postMessageTransfers === undefined ?
+                             true : PDFJS.postMessageTransfers;
+/**
  * This is the main entry point for loading a PDF and interacting with it.
  * NOTE: If a URL is used to fetch the PDF data a standard XMLHttpRequest(XHR)
  * is used, which means it must follow the same origin rules that any XHR does
  * e.g. No cross domain requests without CORS.
  *
  * @param {string|TypedAray|object} source Can be an url to where a PDF is
  * located, a typed array (Uint8Array) already populated with data or
  * and parameter object with the following possible fields:
  *  - url   - The URL of the PDF.
  *  - data  - A typed array with PDF data.
  *  - httpHeaders - Basic authentication headers.
  *  - password - For decrypting password-protected PDFs.
+ *  - initialData - A typed array with the first portion or all of the pdf data.
+ *                  Used by the extension since some data is already loaded
+ *                  before the switch to range requests. 
  *
  * @param {object} pdfDataRangeTransport is optional. It is used if you want
  * to manually serve range requests for data in the PDF. See viewer.js for
  * an example of pdfDataRangeTransport's interface.
  *
  * @param {function} passwordCallback is optional. It is used to request a
  * password if wrong or no password was provided. The callback receives two
  * parameters: function that needs to be called with new password and reason
@@ -4100,16 +4249,24 @@ var PDFDocumentProxy = (function PDFDocu
      * @param {number} The page number to get. The first page is 1.
      * @return {Promise} A promise that is resolved with a {PDFPageProxy}
      * object.
      */
     getPage: function PDFDocumentProxy_getPage(number) {
       return this.transport.getPage(number);
     },
     /**
+     * @param {object} Must have 'num' and 'gen' properties.
+     * @return {Promise} A promise that is resolved with the page index that is
+     * associated with the reference.
+     */
+    getPageIndex: function PDFDocumentProxy_getPageIndex(ref) {
+      return this.transport.getPageIndex(ref);
+    },
+    /**
      * @return {Promise} A promise that is resolved with a lookup table for
      * mapping named destinations to reference numbers.
      */
     getDestinations: function PDFDocumentProxy_getDestinations() {
       return this.transport.getDestinations();
     },
     /**
      * @return {Promise} A promise that is resolved with an array of all the
@@ -4174,16 +4331,19 @@ var PDFDocumentProxy = (function PDFDocu
     },
     /**
      * @return {Promise} A promise that is resolved when the document's data
      * is loaded
      */
     dataLoaded: function PDFDocumentProxy_dataLoaded() {
       return this.transport.dataLoaded();
     },
+    cleanup: function PDFDocumentProxy_cleanup() {
+      this.transport.startCleanup();
+    },
     destroy: function PDFDocumentProxy_destroy() {
       this.transport.destroy();
     }
   };
   return PDFDocumentProxy;
 })();
 
 var PDFPageProxy = (function PDFPageProxyClosure() {
@@ -4393,20 +4553,20 @@ var PDFPageProxy = (function PDFPageProx
     _startRenderPage: function PDFPageProxy_startRenderPage(transparency) {
       this.displayReadyPromise.resolve(transparency);
     },
     /**
      * For internal use only.
      */
     _renderPageChunk: function PDFPageProxy_renderPageChunk(operatorListChunk) {
       // Add the new chunk to the current operator list.
-      Util.concatenateToArray(this.operatorList.fnArray,
-                              operatorListChunk.fnArray);
-      Util.concatenateToArray(this.operatorList.argsArray,
-                              operatorListChunk.argsArray);
+      for (var i = 0, ii = operatorListChunk.length; i < ii; i++) {
+        this.operatorList.fnArray.push(operatorListChunk.fnArray[i]);
+        this.operatorList.argsArray.push(operatorListChunk.argsArray[i]);
+      }
       this.operatorList.lastChunk = operatorListChunk.lastChunk;
 
       // Notify all the rendering tasks there are more operators to be consumed.
       for (var i = 0; i < this.renderTasks.length; i++) {
         this.renderTasks[i].operatorListChanged();
       }
 
       if (operatorListChunk.lastChunk) {
@@ -4448,34 +4608,44 @@ var WorkerTransport = (function WorkerTr
 
       try {
         // Some versions of FF can't create a worker on localhost, see:
         // https://bugzilla.mozilla.org/show_bug.cgi?id=683280
         var worker = new Worker(workerSrc);
         var messageHandler = new MessageHandler('main', worker);
         this.messageHandler = messageHandler;
 
-        messageHandler.on('test', function transportTest(supportTypedArray) {
+        messageHandler.on('test', function transportTest(data) {
+          var supportTypedArray = data && data.supportTypedArray;
           if (supportTypedArray) {
             this.worker = worker;
+            if (!data.supportTransfers) {
+              PDFJS.postMessageTransfers = false;
+            }
             this.setupMessageHandler(messageHandler);
             workerInitializedPromise.resolve();
           } else {
             globalScope.PDFJS.disableWorker = true;
             this.loadFakeWorkerFiles().then(function() {
               this.setupFakeWorker();
               workerInitializedPromise.resolve();
             }.bind(this));
           }
         }.bind(this));
 
-        var testObj = new Uint8Array(1);
-        // Some versions of Opera throw a DATA_CLONE_ERR on
-        // serializing the typed array.
-        messageHandler.send('test', testObj);
+        var testObj = new Uint8Array([PDFJS.postMessageTransfers ? 255 : 0]);
+        // Some versions of Opera throw a DATA_CLONE_ERR on serializing the
+        // typed array. Also, checking if we can use transfers.
+        try {
+          messageHandler.send('test', testObj, null, [testObj.buffer]);
+        } catch (ex) {
+          info('Cannot use postMessage transfers');
+          testObj[0] = 0;
+          messageHandler.send('test', testObj);
+        }
         return;
       } catch (e) {
         info('The worker has been disabled.');
       }
     }
     // Either workers are disabled, not supported or have thrown an exception.
     // Thus, we fallback to a faked worker.
     globalScope.PDFJS.disableWorker = true;
@@ -4699,17 +4869,17 @@ var WorkerTransport = (function WorkerTr
         var page = this.pageCache[data.pageNum - 1];
         if (page.displayReadyPromise)
           page.displayReadyPromise.reject(data.error);
         else
           error(data.error);
       }, this);
 
       messageHandler.on('JpegDecode', function(data, promise) {
-        var imageData = data[0];
+        var imageUrl = data[0];
         var components = data[1];
         if (components != 3 && components != 1)
           error('Only 3 component or 1 component can be returned');
 
         var img = new Image();
         img.onload = (function messageHandler_onloadClosure() {
           var width = img.width;
           var height = img.height;
@@ -4729,18 +4899,17 @@ var WorkerTransport = (function WorkerTr
             }
           } else if (components == 1) {
             for (var i = 0, j = 0; i < rgbaLength; i += 4, j++) {
               buf[j] = data[i];
             }
           }
           promise.resolve({ data: buf, width: width, height: height});
         }).bind(this);
-        var src = 'data:image/jpeg;base64,' + window.btoa(imageData);
-        img.src = src;
+        img.src = imageUrl;
       });
     },
 
     fetchDocument: function WorkerTransport_fetchDocument(source) {
       source.disableAutoFetch = PDFJS.disableAutoFetch;
       source.chunkedViewerLoading = !!this.pdfDataRangeTransport;
       this.messageHandler.send('GetDocRequest', {
         source: source,
@@ -4769,29 +4938,54 @@ var WorkerTransport = (function WorkerTr
       if (pageIndex in this.pagePromises)
         return this.pagePromises[pageIndex];
       var promise = new PDFJS.Promise('Page ' + pageNumber);
       this.pagePromises[pageIndex] = promise;
       this.messageHandler.send('GetPageRequest', { pageIndex: pageIndex });
       return promise;
     },
 
+    getPageIndex: function WorkerTransport_getPageIndexByRef(ref) {
+      var promise = new PDFJS.Promise();
+      this.messageHandler.send('GetPageIndex', { ref: ref },
+        function (pageIndex) {
+          promise.resolve(pageIndex);
+        }
+      );
+      return promise;
+    },
+
     getAnnotations: function WorkerTransport_getAnnotations(pageIndex) {
       this.messageHandler.send('GetAnnotationsRequest',
         { pageIndex: pageIndex });
     },
 
     getDestinations: function WorkerTransport_getDestinations() {
       var promise = new PDFJS.Promise();
       this.messageHandler.send('GetDestinations', null,
         function transportDestinations(destinations) {
           promise.resolve(destinations);
         }
       );
       return promise;
+    },
+
+    startCleanup: function WorkerTransport_startCleanup() {
+      this.messageHandler.send('Cleanup', null,
+        function endCleanup() {
+          for (var i = 0, ii = this.pageCache.length; i < ii; i++) {
+            var page = this.pageCache[i];
+            if (page) {
+              page.destroy();
+            }
+          }
+          this.commonObjs.clear();
+          FontLoader.clear();
+        }.bind(this)
+      );
     }
   };
   return WorkerTransport;
 
 })();
 
 /**
  * A PDF document and page is built of many objects. E.g. there are objects
@@ -5002,17 +5196,17 @@ var InternalRenderTask = (function Inter
     _next: function InternalRenderTask__next() {
       if (this.cancelled) {
         return;
       }
       this.operatorListIdx = this.gfx.executeOperatorList(this.operatorList,
                                         this.operatorListIdx,
                                         this._continue.bind(this),
                                         this.stepper);
-      if (this.operatorListIdx === this.operatorList.fnArray.length) {
+      if (this.operatorListIdx === this.operatorList.argsArray.length) {
         this.running = false;
         if (this.operatorList.lastChunk) {
           this.gfx.endDrawing();
           this.callback();
         }
       }
     }
 
@@ -5531,47 +5725,16 @@ var CanvasGraphics = (function CanvasGra
   }
 
   var LINE_CAP_STYLES = ['butt', 'round', 'square'];
   var LINE_JOIN_STYLES = ['miter', 'round', 'bevel'];
   var NORMAL_CLIP = {};
   var EO_CLIP = {};
 
   CanvasGraphics.prototype = {
-    slowCommands: {
-      'stroke': true,
-      'closeStroke': true,
-      'fill': true,
-      'eoFill': true,
-      'fillStroke': true,
-      'eoFillStroke': true,
-      'closeFillStroke': true,
-      'closeEOFillStroke': true,
-      'showText': true,
-      'showSpacedText': true,
-      'setStrokeColorSpace': true,
-      'setFillColorSpace': true,
-      'setStrokeColor': true,
-      'setStrokeColorN': true,
-      'setFillColor': true,
-      'setFillColorN': true,
-      'setStrokeGray': true,
-      'setFillGray': true,
-      'setStrokeRGBColor': true,
-      'setFillRGBColor': true,
-      'setStrokeCMYKColor': true,
-      'setFillCMYKColor': true,
-      'paintJpegXObject': true,
-      'paintImageXObject': true,
-      'paintInlineImageXObject': true,
-      'paintInlineImageXObjectGroup': true,
-      'paintImageMaskXObject': true,
-      'paintImageMaskXObjectGroup': true,
-      'shadingFill': true
-    },
 
     beginDrawing: function CanvasGraphics_beginDrawing(viewport, transparency) {
       // For pdfs that use blend modes we have to clear the canvas else certain
       // blend modes can look wrong since we'd be blending with a white
       // backdrop. The problem with a transparent backdrop though is we then
       // don't get sub pixel anti aliasing on text, so we fill with white if
       // we can.
       var width = this.ctx.canvas.width;
@@ -5613,29 +5776,28 @@ var CanvasGraphics = (function CanvasGra
         return i;
       }
 
       var executionEndIdx;
       var endTime = Date.now() + EXECUTION_TIME;
 
       var commonObjs = this.commonObjs;
       var objs = this.objs;
-      var fnName;
-      var slowCommands = this.slowCommands;
+      var fnId;
 
       while (true) {
         if (stepper && i === stepper.nextBreakPoint) {
           stepper.breakIt(i, continueCallback);
           return i;
         }
 
-        fnName = fnArray[i];
-
-        if (fnName !== 'dependency') {
-          this[fnName].apply(this, argsArray[i]);
+        fnId = fnArray[i];
+
+        if (fnId !== OPS.dependency) {
+          this[fnId].apply(this, argsArray[i]);
         } else {
           var deps = argsArray[i];
           for (var n = 0, nn = deps.length; n < nn; n++) {
             var depObjId = deps[n];
             var common = depObjId.substring(0, 2) == 'g_';
 
             // If the promise isn't resolved yet, add the continueCallback
             // to the promise and bail out.
@@ -5652,20 +5814,20 @@ var CanvasGraphics = (function CanvasGra
 
         i++;
 
         // If the entire operatorList was executed, stop as were done.
         if (i == argsArrayLen) {
           return i;
         }
 
-        // If the execution took longer then a certain amount of time, shedule
+        // If the execution took longer then a certain amount of time, schedule
         // to continue exeution after a short delay.
         // However, this is only possible if a 'continueCallback' is passed in.
-        if (continueCallback && slowCommands[fnName] && Date.now() > endTime) {
+        if (continueCallback && Date.now() > endTime) {
           setTimeout(continueCallback, 0);
           return i;
         }
 
         // If the operatorList isn't executed completely yet OR the execution
         // time was short enough, do another execution round.
       }
     },
@@ -6942,16 +7104,20 @@ var CanvasGraphics = (function CanvasGra
         var transform = this.ctx.mozCurrentTransform;
         return [
           transform[0] * x + transform[2] * y + transform[4],
           transform[1] * x + transform[3] * y + transform[5]
         ];
     }
   };
 
+  for (var op in OPS) {
+    CanvasGraphics.prototype[OPS[op]] = CanvasGraphics.prototype[op];
+  }
+
   return CanvasGraphics;
 })();
 
 
 
 PDFJS.disableFontFace = false;
 
 var FontLoader = {
@@ -6962,16 +7128,22 @@ var FontLoader = {
         styleElement.id = 'PDFJS_FONT_STYLE_TAG';
         document.documentElement.getElementsByTagName('head')[0].appendChild(
           styleElement);
     }
 
     var styleSheet = styleElement.sheet;
     styleSheet.insertRule(rule, styleSheet.cssRules.length);
   },
+  clear: function fontLoaderClear() {
+    var styleElement = document.getElementById('PDFJS_FONT_STYLE_TAG');
+    if (styleElement) {
+      styleElement.parentNode.removeChild(styleElement);
+    }
+  },
   bind: function fontLoaderBind(fonts, callback) {
     assert(!isWorker, 'bind() shall be called from main thread');
   
     for (var i = 0, ii = fonts.length; i < ii; i++) {
       var font = fonts[i];
       if (font.attached)
         continue;
   
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -15,18 +15,18 @@
  * limitations under the License.
  */
 
 // Initializing PDFJS global object (if still undefined)
 if (typeof PDFJS === 'undefined') {
   (typeof window !== 'undefined' ? window : this).PDFJS = {};
 }
 
-PDFJS.version = '0.8.681';
-PDFJS.build = '48c672b';
+PDFJS.version = '0.8.759';
+PDFJS.build = 'd3b5aa3';
 
 (function pdfjsWrapper() {
   // Use strict in our context only - users might not want it
   'use strict';
 
 /* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
 /* Copyright 2012 Mozilla Foundation
@@ -38,17 +38,17 @@ PDFJS.build = '48c672b';
  *     http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref */
+/* globals Cmd, ColorSpace, Dict, MozBlobBuilder, Name, PDFJS, Ref, URL */
 
 'use strict';
 
 var globalScope = (typeof window === 'undefined') ? this : window;
 
 var isWorker = (typeof window == 'undefined');
 
 var ERRORS = 0, WARNINGS = 1, INFOS = 5;
@@ -73,16 +73,108 @@ var TextRenderingMode = {
 // In production, it will be declared outside a global wrapper
 // In development, it will be declared here
 if (!globalScope.PDFJS) {
   globalScope.PDFJS = {};
 }
 
 globalScope.PDFJS.pdfBug = false;
 
+// All the possible operations for an operator list.
+var OPS = PDFJS.OPS = {
+  // Intentionally start from 1 so it is easy to spot bad operators that will be
+  // 0's.
+  dependency: 1,
+  setLineWidth: 2,
+  setLineCap: 3,
+  setLineJoin: 4,
+  setMiterLimit: 5,
+  setDash: 6,
+  setRenderingIntent: 7,
+  setFlatness: 8,
+  setGState: 9,
+  save: 10,
+  restore: 11,
+  transform: 12,
+  moveTo: 13,
+  lineTo: 14,
+  curveTo: 15,
+  curveTo2: 16,
+  curveTo3: 17,
+  closePath: 18,
+  rectangle: 19,
+  stroke: 20,
+  closeStroke: 21,
+  fill: 22,
+  eoFill: 23,
+  fillStroke: 24,
+  eoFillStroke: 25,
+  closeFillStroke: 26,
+  closeEOFillStroke: 27,
+  endPath: 28,
+  clip: 29,
+  eoClip: 30,
+  beginText: 31,
+  endText: 32,
+  setCharSpacing: 33,
+  setWordSpacing: 34,
+  setHScale: 35,
+  setLeading: 36,
+  setFont: 37,
+  setTextRenderingMode: 38,
+  setTextRise: 39,
+  moveText: 40,
+  setLeadingMoveText: 41,
+  setTextMatrix: 42,
+  nextLine: 43,
+  showText: 44,
+  showSpacedText: 45,
+  nextLineShowText: 46,
+  nextLineSetSpacingShowText: 47,
+  setCharWidth: 48,
+  setCharWidthAndBounds: 49,
+  setStrokeColorSpace: 50,
+  setFillColorSpace: 51,
+  setStrokeColor: 52,
+  setStrokeColorN: 53,
+  setFillColor: 54,
+  setFillColorN: 55,
+  setStrokeGray: 56,
+  setFillGray: 57,
+  setStrokeRGBColor: 58,
+  setFillRGBColor: 59,
+  setStrokeCMYKColor: 60,
+  setFillCMYKColor: 61,
+  shadingFill: 62,
+  beginInlineImage: 63,
+  beginImageData: 64,
+  endInlineImage: 65,
+  paintXObject: 66,
+  markPoint: 67,
+  markPointProps: 68,
+  beginMarkedContent: 69,
+  beginMarkedContentProps: 70,
+  endMarkedContent: 71,
+  beginCompat: 72,
+  endCompat: 73,
+  paintFormXObjectBegin: 74,
+  paintFormXObjectEnd: 75,
+  beginGroup: 76,
+  endGroup: 77,
+  beginAnnotations: 78,
+  endAnnotations: 79,
+  beginAnnotation: 80,
+  endAnnotation: 81,
+  paintJpegXObject: 82,
+  paintImageMaskXObject: 83,
+  paintImageMaskXObjectGroup: 84,
+  paintImageXObject: 85,
+  paintInlineImageXObject: 86,
+  paintInlineImageXObjectGroup: 87
+};
 
 // Use only for debugging purposes. This should not be used in any code that is
 // in mozilla master.
 var log = (function() {
   if ('console' in globalScope && 'log' in globalScope['console']) {
     return globalScope['console']['log'].bind(globalScope['console']);
   } else {
     return function nop() {
@@ -287,17 +379,17 @@ var NotImplementedException = (function 
 
   return NotImplementedException;
 })();
 
 var MissingDataException = (function MissingDataExceptionClosure() {
   function MissingDataException(begin, end) {
     this.begin = begin;
     this.end = end;
-    this.message = 'Missing data [begin, end)';
+    this.message = 'Missing data [' + begin + ', ' + end + ')';
   }
 
   MissingDataException.prototype = new Error();
   MissingDataException.prototype.name = 'MissingDataException';
   MissingDataException.constructor = MissingDataException;
 
   return MissingDataException;
 })();
@@ -860,17 +952,17 @@ var Promise = PDFJS.Promise = (function 
 
   function Promise() {
     this._status = STATUS_PENDING;
     this._handlers = [];
   }
   /**
    * Builds a promise that is resolved when all the passed in promises are
    * resolved.
-   * @param {Promise[]} promises Array of promises to wait for.
+   * @param {array} array of data and/or promises to wait for.
    * @return {Promise} New dependant promise.
    */
   Promise.all = function Promise_all(promises) {
     var deferred = new Promise();
     var unresolved = promises.length;
     var results = [];
     if (unresolved === 0) {
       deferred.resolve(results);
@@ -880,45 +972,58 @@ var Promise = PDFJS.Promise = (function 
       if (deferred._status === STATUS_REJECTED) {
         return;
       }
       results = [];
       deferred.reject(reason);
     }
     for (var i = 0, ii = promises.length; i < ii; ++i) {
       var promise = promises[i];
-      promise.then((function(i) {
+      var resolve = (function(i) {
         return function(value) {
           if (deferred._status === STATUS_REJECTED) {
             return;
           }
           results[i] = value;
           unresolved--;
           if (unresolved === 0)
             deferred.resolve(results);
         };
-      })(i), reject);
+      })(i);
+      if (Promise.isPromise(promise)) {
+        promise.then(resolve, reject);
+      } else {
+        resolve(promise);
+      }
     }
     return deferred;
   };
 
+  /**
+   * Checks if the value is likely a promise (has a 'then' function).
+   * @return {boolean} true if x is thenable
+   */
+  Promise.isPromise = function Promise_isPromise(value) {
+    return value && typeof value.then === 'function';
+  };
+
   Promise.prototype = {
     _status: null,
     _value: null,
     _handlers: null,
     _unhandledRejection: null,
 
     _updateStatus: function Promise__updateStatus(status, value) {
       if (this._status === STATUS_RESOLVED ||
           this._status === STATUS_REJECTED) {
         return;
       }
 
       if (status == STATUS_RESOLVED &&
-          value && typeof(value.then) === 'function') {
+          Promise.isPromise(value)) {
         value.then(this._updateStatus.bind(this, STATUS_RESOLVED),
                    this._updateStatus.bind(this, STATUS_REJECTED));
         return;
       }
 
       this._status = status;
       this._value = value;
 
@@ -1011,28 +1116,56 @@ var StatTimer = (function StatTimerClosu
       }
       return out;
     }
   };
   return StatTimer;
 })();
 
 PDFJS.createBlob = function createBlob(data, contentType) {
-  if (typeof Blob === 'function')
+  if (typeof Blob !== 'undefined')
     return new Blob([data], { type: contentType });
   // Blob builder is deprecated in FF14 and removed in FF18.
   var bb = new MozBlobBuilder();
   bb.append(data);
   return bb.getBlob(contentType);
 };
 
+PDFJS.createObjectURL = (function createObjectURLClosure() {
+  if (typeof URL !== 'undefined' && URL.createObjectURL) {
+    return function createObjectURL(data, contentType) {
+      var blob = PDFJS.createBlob(data, contentType);
+      return URL.createObjectURL(blob);
+    };
+  }
+
+  // Blob/createObjectURL is not available, falling back to data schema.
+  var digits =
+    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
+
+  return function createObjectURL(data, contentType) {
+    var buffer = 'data:' + contentType + ';base64,';
+    for (var i = 0, ii = data.length; i < ii; i += 3) {
+      var b1 = data[i] & 0xFF;
+      var b2 = data[i + 1] & 0xFF;
+      var b3 = data[i + 2] & 0xFF;
+      var d1 = b1 >> 2, d2 = ((b1 & 3) << 4) | (b2 >> 4);
+      var d3 = i + 1 < ii ? ((b2 & 0xF) << 2) | (b3 >> 6) : 64;
+      var d4 = i + 2 < ii ? (b3 & 0x3F) : 64;
+      buffer += digits[d1] + digits[d2] + digits[d3] + digits[d4];
+    }
+    return buffer;
+  };
+})();
+
 function MessageHandler(name, comObj) {
   this.name = name;
   this.comObj = comObj;
   this.callbackIndex = 1;
+  this.postMessageTransfers = true;
   var callbacks = this.callbacks = {};
   var ah = this.actionHandler = {};
 
   ah['console_log'] = [function ahConsoleLog(data) {
     log.apply(null, data);
   }];
   // If there's no console available, console_error in the
   // action handler will do nothing.
@@ -1089,37 +1222,42 @@ MessageHandler.prototype = {
     }
     ah[actionName] = [handler, scope];
   },
   /**
    * Sends a message to the comObj to invoke the action with the supplied data.
    * @param {String} actionName Action to call.
    * @param {JSON} data JSON data to send.
    * @param {function} [callback] Optional callback that will handle a reply.
+   * @param {Array} [transfers] Optional list of transfers/ArrayBuffers
    */
-  send: function messageHandlerSend(actionName, data, callback) {
+  send: function messageHandlerSend(actionName, data, callback, transfers) {
     var message = {
       action: actionName,
       data: data
     };
     if (callback) {
       var callbackId = this.callbackIndex++;
       this.callbacks[callbackId] = callback;
       message.callbackId = callbackId;
     }
-    this.comObj.postMessage(message);
+    if (transfers && this.postMessageTransfers) {
+      this.comObj.postMessage(message, transfers);
+    } else {
+      this.comObj.postMessage(message);
+    }
   }
 };
 
-function loadJpegStream(id, imageData, objs) {
+function loadJpegStream(id, imageUrl, objs) {
   var img = new Image();
   img.onload = (function loadJpegStream_onloadClosure() {
     objs.resolve(id, img);
   });
-  img.src = 'data:image/jpeg;base64,' + window.btoa(imageData);
+  img.src = imageUrl;
 }
 
 
 var PatternType = {
   AXIAL: 2,
   RADIAL: 3
 };
 
@@ -2561,19 +2699,19 @@ var Annotation = (function AnnotationClo
       var bbox = appearanceDict.get('BBox') || [0, 0, 1, 1];
       var matrix = appearanceDict.get('Matrix') || [1, 0, 0, 1, 0 ,0];
       var transform = getTransformMatrix(data.rect, bbox, matrix);
 
       var border = data.border;
 
       resourcesPromise.then(function(resources) {
         var opList = new OperatorList();
-        opList.addOp('beginAnnotation', [data.rect, transform, matrix]);
+        opList.addOp(OPS.beginAnnotation, [data.rect, transform, matrix]);
         evaluator.getOperatorList(this.appearance, resources, opList);
-        opList.addOp('endAnnotation', []);
+        opList.addOp(OPS.endAnnotation, []);
         promise.resolve(opList);
       }.bind(this));
 
       return promise;
     }
   };
 
   Annotation.getConstructor =
@@ -2657,22 +2795,22 @@ var Annotation = (function AnnotationClo
 
     var annotationsReadyPromise = new Promise();
 
     var annotationPromises = [];
     for (var i = 0, n = annotations.length; i < n; ++i) {
       annotationPromises.push(annotations[i].getOperatorList(partialEvaluator));
     }
     Promise.all(annotationPromises).then(function(datas) {
-      opList.addOp('beginAnnotations', []);
+      opList.addOp(OPS.beginAnnotations, []);
       for (var i = 0, n = datas.length; i < n; ++i) {
         var annotOpList = datas[i];
         opList.addOpList(annotOpList);
       }
-      opList.addOp('endAnnotations', []);
+      opList.addOp(OPS.endAnnotations, []);
       annotationsReadyPromise.resolve();
     }, reject);
 
     return annotationsReadyPromise;
   };
 
   return Annotation;
 })();
@@ -2838,32 +2976,32 @@ var TextWidgetAnnotation = (function Tex
       var appearanceArgsArray = opList.argsArray;
       var fnArray = [];
       var argsArray = [];
 
       // TODO(mack): Add support for stroke color
       data.rgb = [0, 0, 0];
       // TODO THIS DOESN'T MAKE ANY SENSE SINCE THE fnArray IS EMPTY!
       for (var i = 0, n = fnArray.length; i < n; ++i) {
-        var fnName = appearanceFnArray[i];
+        var fnId = appearanceFnArray[i];
         var args = appearanceArgsArray[i];
 
-        if (fnName === 'setFont') {
+        if (fnId === OPS.setFont) {
           data.fontRefName = args[0];
           var size = args[1];
           if (size < 0) {
             data.fontDirection = -1;
             data.fontSize = -size;
           } else {
             data.fontDirection = 1;
             data.fontSize = size;
           }
-        } else if (fnName === 'setFillRGBColor') {
+        } else if (fnId === OPS.setFillRGBColor) {
           data.rgb = args;
-        } else if (fnName === 'setFillGray') {
+        } else if (fnId === OPS.setFillGray) {
           var rgbValue = args[0] * 255;
           data.rgb = [rgbValue, rgbValue, rgbValue];
         }
       }
       promise.resolve(opList);
       return promise;
     }
   });
@@ -3081,16 +3219,17 @@ var ChunkedStream = (function ChunkedStr
     this.start = 0;
     this.pos = 0;
     this.end = length;
     this.chunkSize = chunkSize;
     this.loadedChunks = [];
     this.numChunksLoaded = 0;
     this.numChunks = Math.ceil(length / chunkSize);
     this.manager = manager;
+    this.initialDataLength = 0;
   }
 
   // required methods for a stream. if a particular stream does not
   // implement these, an error should be thrown
   ChunkedStream.prototype = {
 
     getMissingChunks: function ChunkedStream_getMissingChunks() {
       var chunks = [];
@@ -3128,21 +3267,36 @@ var ChunkedStream = (function ChunkedStr
       for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
         if (!(chunk in this.loadedChunks)) {
           this.loadedChunks[chunk] = true;
           ++this.numChunksLoaded;
         }
       }
     },
 
+    onReceiveInitialData: function (data) {
+      this.bytes.set(data);
+      this.initialDataLength = data.length;
+      var endChunk = this.end === data.length ?
+                     this.numChunks : Math.floor(data.length / this.chunkSize);
+      for (var i = 0; i < endChunk; i++) {
+        this.loadedChunks[i] = true;
+        ++this.numChunksLoaded;
+      }
+    },
+
     ensureRange: function ChunkedStream_ensureRange(begin, end) {
       if (begin >= end) {
         return;
       }
 
+      if (end <= this.initialDataLength) {
+        return;
+      }
+
       var chunkSize = this.chunkSize;
       var beginChunk = Math.floor(begin / chunkSize);
       var endChunk = Math.floor((end - 1) / chunkSize) + 1;
       for (var chunk = beginChunk; chunk < endChunk; ++chunk) {
         if (!(chunk in this.loadedChunks)) {
           throw new MissingDataException(begin, end);
         }
       }
@@ -3290,20 +3444,35 @@ var ChunkedStreamManager = (function Chu
 
     this.currRequestId = 0;
 
     this.chunksNeededByRequest = {};
     this.requestsByChunk = {};
     this.callbacksByRequest = {};
 
     this.loadedStream = new Promise();
+    if (args.initialData) {
+      this.setInitialData(args.initialData);
+    }
   }
 
   ChunkedStreamManager.prototype = {
 
+    setInitialData: function ChunkedStreamManager_setInitialData(data) {
+      this.stream.onReceiveInitialData(data);
+      if (this.stream.allChunksLoaded()) {
+        this.loadedStream.resolve(this.stream);
+      } else if (this.msgHandler) {
+        this.msgHandler.send('DocProgress', {
+          loaded: data.length,
+          total: this.length
+        });
+      }
+    },
+
     onLoadedStream: function ChunkedStreamManager_getLoadedStream() {
       return this.loadedStream;
     },
 
     // Get all the chunks that are not yet loaded and groups them into
     // contiguous ranges to load in as few requests as possible
     requestAllChunks: function ChunkedStreamManager_requestAllChunks() {
       var missingChunks = this.stream.getMissingChunks();
@@ -3395,26 +3564,26 @@ var ChunkedStreamManager = (function Chu
       chunksToRequest.sort(function(a, b) { return a - b; });
       this.requestChunks(chunksToRequest, callback);
     },
 
     // Groups a sorted array of chunks into as few continguous larger
     // chunks as possible
     groupChunks: function ChunkedStreamManager_groupChunks(chunks) {
       var groupedChunks = [];
-      var beginChunk;
-      var prevChunk;
+      var beginChunk = -1;
+      var prevChunk = -1;
       for (var i = 0; i < chunks.length; ++i) {
         var chunk = chunks[i];
 
-        if (!beginChunk) {
+        if (beginChunk < 0) {
           beginChunk = chunk;
         }
 
-        if (prevChunk && prevChunk + 1 !== chunk) {
+        if (prevChunk >= 0 && prevChunk + 1 !== chunk) {
           groupedChunks.push({
             beginChunk: beginChunk, endChunk: prevChunk + 1});
           beginChunk = chunk;
         }
         if (i + 1 === chunks.length) {
           groupedChunks.push({
             beginChunk: beginChunk, endChunk: chunk + 1});
         }
@@ -3550,16 +3719,20 @@ var BasePdfManager = (function BasePdfMa
     ensureCatalog: function BasePdfManager_ensureCatalog(prop, args) {
       return this.ensure(this.pdfModel.catalog, prop, args);
     },
 
     getPage: function BasePdfManager_pagePage(pageIndex) {
       return this.pdfModel.getPage(pageIndex);
     },
 
+    cleanup: function BasePdfManager_cleanup() {
+      return this.pdfModel.cleanup();
+    },
+
     ensure: function BasePdfManager_ensure(obj, prop, args) {
       return new NotImplementedException();
     },
 
     requestRange: function BasePdfManager_ensure(begin, end) {
       return new NotImplementedException();
     },
 
@@ -3643,17 +3816,18 @@ var NetworkPdfManager = (function Networ
   function NetworkPdfManager(args, msgHandler) {
 
     this.msgHandler = msgHandler;
 
     var params = {
       msgHandler: msgHandler,
       httpHeaders: args.httpHeaders,
       chunkedViewerLoading: args.chunkedViewerLoading,
-      disableAutoFetch: args.disableAutoFetch
+      disableAutoFetch: args.disableAutoFetch,
+      initialData: args.initialData
     };
     this.streamManager = new ChunkedStreamManager(args.length, CHUNK_SIZE,
                                                   args.url, params);
 
     this.pdfModel = new PDFDocument(this, this.streamManager.getStream(),
                                     args.password);
   }
 
@@ -3717,22 +3891,23 @@ var NetworkPdfManager = (function Networ
 
   return NetworkPdfManager;
 })();
 
 
 
 var Page = (function PageClosure() {
 
-  function Page(pdfManager, xref, pageIndex, pageDict, ref) {
+  function Page(pdfManager, xref, pageIndex, pageDict, ref, fontCache) {
     this.pdfManager = pdfManager;
     this.pageIndex = pageIndex;
     this.pageDict = pageDict;
     this.xref = xref;
     this.ref = ref;
+    this.fontCache = fontCache;
     this.idCounters = {
       obj: 0
     };
     this.resourcesPromise = null;
   }
 
   Page.prototype = {
     getPageProp: function Page_getPageProp(key) {
@@ -3852,17 +4027,17 @@ var Page = (function PageClosure() {
         'Font',
         // ProcSet
         // Properties
       ]);
 
       var partialEvaluator = new PartialEvaluator(
             pdfManager, this.xref, handler,
             this.pageIndex, 'p' + this.pageIndex + '_',
-            this.idCounters);
+            this.idCounters, this.fontCache);
 
       var dataPromises = Promise.all(
           [contentStreamPromise, resourcesPromise], reject);
       dataPromises.then(function(data) {
         var contentStream = data[0];
 
 
         var opList = new OperatorList(handler, self.pageIndex);
@@ -3876,26 +4051,24 @@ var Page = (function PageClosure() {
       });
 
       var annotationsPromise = pdfManager.ensure(this, 'annotations');
       Promise.all([pageListPromise, annotationsPromise]).then(function(datas) {
         var pageOpList = datas[0];
         var annotations = datas[1];
 
         if (annotations.length === 0) {
-          PartialEvaluator.optimizeQueue(pageOpList);
           pageOpList.flush(true);
           promise.resolve(pageOpList);
           return;
         }
 
         var annotationsReadyPromise = Annotation.appendToOperatorList(
           annotations, pageOpList, pdfManager, partialEvaluator);
         annotationsReadyPromise.then(function () {
-          PartialEvaluator.optimizeQueue(pageOpList);
           pageOpList.flush(true);
           promise.resolve(pageOpList);
         }, reject);
       }, reject);
 
       return promise;
     },
     extractTextContent: function Page_extractTextContent() {
@@ -3920,17 +4093,17 @@ var Page = (function PageClosure() {
 
       var dataPromises = Promise.all([contentStreamPromise,
                                       resourcesPromise]);
       dataPromises.then(function(data) {
         var contentStream = data[0];
         var partialEvaluator = new PartialEvaluator(
               pdfManager, self.xref, handler,
               self.pageIndex, 'p' + self.pageIndex + '_',
-              self.idCounters);
+              self.idCounters, self.fontCache);
 
         var bidiTexts = partialEvaluator.getTextContent(contentStream,
                                                         self.resources);
         textContentPromise.resolve(bidiTexts);
       });
 
       return textContentPromise;
     },
@@ -4188,22 +4361,22 @@ var PDFDocument = (function PDFDocumentC
 
       for (var i = 0, n = hash.length; i < n; i++) {
         fileID += hash[i].toString(16);
       }
 
       return shadow(this, 'fingerprint', fileID);
     },
 
-    traversePages: function PDFDocument_traversePages() {
-      this.catalog.traversePages();
-    },
-
     getPage: function PDFDocument_getPage(pageIndex) {
       return this.catalog.getPage(pageIndex);
+    },
+
+    cleanup: function PDFDocument_cleanup() {
+      return this.catalog.cleanup();
     }
   };
 
   return PDFDocument;
 })();
 
 
 
@@ -4368,52 +4541,56 @@ var RefSet = (function RefSetClosure() {
     }
   };
 
   return RefSet;
 })();
 
 var RefSetCache = (function RefSetCacheClosure() {
   function RefSetCache() {
-    this.dict = {};
+    this.dict = Object.create(null);
   }
 
   RefSetCache.prototype = {
     get: function RefSetCache_get(ref) {
       return this.dict['R' + ref.num + '.' + ref.gen];
     },
 
     has: function RefSetCache_has(ref) {
       return ('R' + ref.num + '.' + ref.gen) in this.dict;
     },
 
     put: function RefSetCache_put(ref, obj) {
       this.dict['R' + ref.num + '.' + ref.gen] = obj;
+    },
+
+    forEach: function RefSetCache_forEach(fn, thisArg) {
+      for (var i in this.dict) {
+        fn.call(thisArg, this.dict[i]);
+      }
+    },
+
+    clear: function RefSetCache_clear() {
+      this.dict = Object.create(null);
     }
   };
 
   return RefSetCache;
 })();
 
 var Catalog = (function CatalogClosure() {
   function Catalog(pdfManager, xref) {
     this.pdfManager = pdfManager;
     this.xref = xref;
     this.catDict = xref.getCatalogObj();
+    this.fontCache = new RefSetCache();
     assertWellFormed(isDict(this.catDict),
       'catalog object is not a dictionary');
 
-    // Stores state as we traverse the pages catalog so that we can resume
-    // parsing if an exception is thrown
-    this.traversePagesQueue = [{
-      pagesDict: this.toplevelPagesDict,
-      posInKids: 0
-    }];
     this.pagePromises = [];
-    this.currPageIndex = 0;
   }
 
   Catalog.prototype = {
     get metadata() {
       var streamRef = this.catDict.getRaw('Metadata');
       if (!isRef(streamRef))
         return shadow(this, 'metadata', null);
 
@@ -4586,69 +4763,166 @@ var Catalog = (function CatalogClosure()
             js = bytesToString(js.getBytes());
           }
           javaScript.push(stringToPDFString(js));
         }
       }
       return shadow(this, 'javaScript', javaScript);
     },
 
+    cleanup: function Catalog_cleanup() {
+      this.fontCache.forEach(function (font) {
+        delete font.sent;
+        delete font.translated;
+      });
+      this.fontCache.clear();
+    },
+
     getPage: function Catalog_getPage(pageIndex) {
-      if (pageIndex < 0 || pageIndex >= this.numPages ||
-          (pageIndex|0) !== pageIndex) {
-        var pagePromise = new Promise();
-        pagePromise.reject(new Error('Invalid page index'));
-        return pagePromise;
-      }
       if (!(pageIndex in this.pagePromises)) {
-        this.pagePromises[pageIndex] = new Promise();
+        this.pagePromises[pageIndex] = this.getPageDict(pageIndex).then(
+          function (a) {
+            var dict = a[0];
+            var ref = a[1];
+            return new Page(this.pdfManager, this.xref, pageIndex, dict, ref,
+                            this.fontCache);
+          }.bind(this)
+        );
       }
       return this.pagePromises[pageIndex];
     },
 
-    // Traverses pages in DFS order so that pages are processed in increasing
-    // order
-    traversePages: function Catalog_traversePages() {
-      var queue = this.traversePagesQueue;
-      while (queue.length) {
-        var queueItem = queue[queue.length - 1];
-        var pagesDict = queueItem.pagesDict;
-
-        var kids = pagesDict.get('Kids');
-        assert(isArray(kids), 'page dictionary kids object is not an array');
-        if (queueItem.posInKids >= kids.length) {
-          queue.pop();
-          continue;
-        }
-        var kidRef = kids[queueItem.posInKids];
-        assert(isRef(kidRef), 'page dictionary kid is not a reference');
-
-        var kid = this.xref.fetch(kidRef);
-        if (isDict(kid, 'Page') || (isDict(kid) && !kid.has('Kids'))) {
-          var pageIndex = this.currPageIndex++;
-          var page = new Page(this.pdfManager, this.xref, pageIndex, kid,
-                              kidRef);
-          if (!(pageIndex in this.pagePromises)) {
-            this.pagePromises[pageIndex] = new Promise();
-          }
-          this.pagePromises[pageIndex].resolve(page);
-
-        } else { // must be a child page dictionary
+    getPageDict: function Catalog_getPageDict(pageIndex) {
+      var promise = new Promise();
+      var nodesToVisit = [this.catDict.getRaw('Pages')];
+      var currentPageIndex = 0;
+      var xref = this.xref;
+
+      function next() {
+        while (nodesToVisit.length) {
+          var currentNode = nodesToVisit.pop();
+
+          if (isRef(currentNode)) {
+            xref.fetchAsync(currentNode).then(function (obj) {
+              if ((isDict(obj, 'Page') || (isDict(obj) && !obj.has('Kids')))) {
+                if (pageIndex === currentPageIndex) {
+                  promise.resolve([obj, currentNode]);
+                } else {
+                  currentPageIndex++;
+                  next();
+                }
+                return;
+              }
+              nodesToVisit.push(obj);
+              next();
+            }.bind(this), promise.reject.bind(promise));
+            return;
+          }
+
+          // must be a child page dictionary
           assert(
-            isDict(kid),
+            isDict(currentNode),
             'page dictionary kid reference points to wrong type of object'
           );
-
-          queue.push({
-            pagesDict: kid,
-            posInKids: 0
+          var count = currentNode.get('Count');
+          // Skip nodes where the page can't be.
+          if (currentPageIndex + count <= pageIndex) {
+            currentPageIndex += count;
+            continue;
+          }
+
+          var kids = currentNode.get('Kids');
+          assert(isArray(kids), 'page dictionary kids object is not an array');
+          if (count === kids.length) {
+            // Nodes that don't have the page have been skipped and this is the
+            // bottom of the tree which means the page requested must be a
+            // descendant of this pages node. Ideally we would just resolve the
+            // promise with the page ref here, but there is the case where more
+            // pages nodes could link to single a page (see issue 3666 pdf). To
+            // handle this push it back on the queue so if it is a pages node it
+            // will be descended into.
+            nodesToVisit = [kids[pageIndex - currentPageIndex]];
+            currentPageIndex = pageIndex;
+            continue;
+          } else {
+            for (var last = kids.length - 1; last >= 0; last--) {
+              nodesToVisit.push(kids[last]);
+            }
+          }
+        }
+        promise.reject('Page index ' + pageIndex + ' not found.');
+      }
+      next();
+      return promise;
+    },
+
+    getPageIndex: function Catalog_getPageIndex(ref) {
+      // The page tree nodes have the count of all the leaves below them. To get
+      // how many pages are before we just have to walk up the tree and keep
+      // adding the count of siblings to the left of the node.
+      var xref = this.xref;
+      function pagesBeforeRef(kidRef) {
+        var total = 0;
+        var parentRef;
+        return xref.fetchAsync(kidRef).then(function (node) {
+          if (!node) {
+            return null;
+          }
+          parentRef = node.getRaw('Parent');
+          return node.getAsync('Parent');
+        }).then(function (parent) {
+          if (!parent) {
+            return null;
+          }
+          return parent.getAsync('Kids');
+        }).then(function (kids) {
+          if (!kids) {
+            return null;
+          }
+          var kidPromises = [];
+          var found = false;
+          for (var i = 0; i < kids.length; i++) {
+            var kid = kids[i];
+            assert(isRef(kid), 'kids must be an ref');
+            if (kid.num == kidRef.num) {
+              found = true;
+              break;
+            }
+            kidPromises.push(xref.fetchAsync(kid).then(function (kid) {
+              if (kid.has('Count')) {
+                var count = kid.get('Count');
+                total += count;
+              } else { // page leaf node
+                total++;
+              }
+            }));
+          }
+          if (!found) {
+            error('kid ref not found in parents kids');
+          }
+          return Promise.all(kidPromises).then(function () {
+            return [total, parentRef];
           });
-        }
-        ++queueItem.posInKids;
-      }
+        });
+      }
+
+      var total = 0;
+      function next(ref) {
+        return pagesBeforeRef(ref).then(function (args) {
+          if (!args) {
+            return total;
+          }
+          var count = args[0];
+          var parentRef = args[1];
+          total += count;
+          return next(parentRef);
+        });
+      }
+
+      return next(ref);
     }
   };
 
   return Catalog;
 })();
 
 var XRef = (function XRefClosure() {
   function XRef(stream, password) {
@@ -5055,16 +5329,18 @@ var XRef = (function XRefClosure() {
             }
             dict = this.processXRefStream(obj);
             if (!this.topDict) {
               this.topDict = dict;
             }
 
             if (!dict)
               error('Failed to read XRef stream');
+          } else {
+            error('Invalid XRef stream header');
           }
 
           // Recursively get previous dictionary, if any
           obj = dict.get('Prev');
           if (isInt(obj)) {
             this.startXRefQueue.push(obj);
           } else if (isRef(obj)) {
             // The spec says Prev must not be a reference, i.e. "/Prev NNN"
@@ -13751,17 +14027,18 @@ var CipherTransformFactory = (function C
   var defaultPasswordBytes = new Uint8Array([
     0x28, 0xBF, 0x4E, 0x5E, 0x4E, 0x75, 0x8A, 0x41,
     0x64, 0x00, 0x4E, 0x56, 0xFF, 0xFA, 0x01, 0x08,
     0x2E, 0x2E, 0x00, 0xB6, 0xD0, 0x68, 0x3E, 0x80,
     0x2F, 0x0C, 0xA9, 0xFE, 0x64, 0x53, 0x69, 0x7A]);
 
   function prepareKeyData(fileId, password, ownerPassword, userPassword,
                           flags, revision, keyLength, encryptMetadata) {
-    var hashData = new Uint8Array(100), i = 0, j, n;
+    var hashDataSize = 40 + ownerPassword.length + fileId.length;
+    var hashData = new Uint8Array(hashDataSize), i = 0, j, n;
     if (password) {
       n = Math.min(32, password.length);
       for (; i < n; ++i)
         hashData[i] = password[i];
     }
     j = 0;
     while (i < 32) {
       hashData[i++] = defaultPasswordBytes[j++];
@@ -13866,18 +14143,18 @@ var CipherTransformFactory = (function C
       (algorithm != 1 && algorithm != 2 && algorithm != 4))
       error('unsupported encryption algorithm');
     this.algorithm = algorithm;
     var keyLength = dict.get('Length') || 40;
     if (!isInt(keyLength) ||
       keyLength < 40 || (keyLength % 8) !== 0)
       error('invalid key length');
     // prepare keys
-    var ownerPassword = stringToBytes(dict.get('O'));
-    var userPassword = stringToBytes(dict.get('U'));
+    var ownerPassword = stringToBytes(dict.get('O')).subarray(0, 32);
+    var userPassword = stringToBytes(dict.get('U')).subarray(0, 32);
     var flags = dict.get('P');
     var revision = dict.get('R');
     var encryptMetadata = algorithm == 4 &&  // meaningful when V is 4
       dict.get('EncryptMetadata') !== false; // makes true as default value
     this.encryptMetadata = encryptMetadata;
 
     var fileIdBytes = stringToBytes(fileId);
     var passwordBytes;
@@ -13978,127 +14255,128 @@ var CipherTransformFactory = (function C
 
   return CipherTransformFactory;
 })();
 
 
 
 var PartialEvaluator = (function PartialEvaluatorClosure() {
   function PartialEvaluator(pdfManager, xref, handler, pageIndex,
-                            uniquePrefix, idCounters) {
+                            uniquePrefix, idCounters, fontCache) {
     this.state = new EvalState();
     this.stateStack = [];
 
     this.pdfManager = pdfManager;
     this.xref = xref;
     this.handler = handler;
     this.pageIndex = pageIndex;
     this.uniquePrefix = uniquePrefix;
     this.idCounters = idCounters;
-    this.fontCache = new RefSetCache();
+    this.fontCache = fontCache;
   }
 
   // Specifies properties for each command
   //
   // If variableArgs === true: [0, `numArgs`] expected
   // If variableArgs === false: exactly `numArgs` expected
   var OP_MAP = {
     // Graphic state
-    w: { fnName: 'setLineWidth', numArgs: 1, variableArgs: false },
-    J: { fnName: 'setLineCap', numArgs: 1, variableArgs: false },
-    j: { fnName: 'setLineJoin', numArgs: 1, variableArgs: false },
-    M: { fnName: 'setMiterLimit', numArgs: 1, variableArgs: false },
-    d: { fnName: 'setDash', numArgs: 2, variableArgs: false },
-    ri: { fnName: 'setRenderingIntent', numArgs: 1, variableArgs: false },
-    i: { fnName: 'setFlatness', numArgs: 1, variableArgs: false },
-    gs: { fnName: 'setGState', numArgs: 1, variableArgs: false },
-    q: { fnName: 'save', numArgs: 0, variableArgs: false },
-    Q: { fnName: 'restore', numArgs: 0, variableArgs: false },
-    cm: { fnName: 'transform', numArgs: 6, variableArgs: false },
+    w: { id: OPS.setLineWidth, numArgs: 1, variableArgs: false },
+    J: { id: OPS.setLineCap, numArgs: 1, variableArgs: false },
+    j: { id: OPS.setLineJoin, numArgs: 1, variableArgs: false },
+    M: { id: OPS.setMiterLimit, numArgs: 1, variableArgs: false },
+    d: { id: OPS.setDash, numArgs: 2, variableArgs: false },
+    ri: { id: OPS.setRenderingIntent, numArgs: 1, variableArgs: false },
+    i: { id: OPS.setFlatness, numArgs: 1, variableArgs: false },
+    gs: { id: OPS.setGState, numArgs: 1, variableArgs: false },
+    q: { id: OPS.save, numArgs: 0, variableArgs: false },
+    Q: { id: OPS.restore, numArgs: 0, variableArgs: false },
+    cm: { id: OPS.transform, numArgs: 6, variableArgs: false },
 
     // Path
-    m: { fnName: 'moveTo', numArgs: 2, variableArgs: false },
-    l: { fnName: 'lineTo', numArgs: 2, variableArgs: false },
-    c: { fnName: 'curveTo', numArgs: 6, variableArgs: false },
-    v: { fnName: 'curveTo2', numArgs: 4, variableArgs: false },
-    y: { fnName: 'curveTo3', numArgs: 4, variableArgs: false },
-    h: { fnName: 'closePath', numArgs: 0, variableArgs: false },
-    re: { fnName: 'rectangle', numArgs: 4, variableArgs: false },
-    S: { fnName: 'stroke', numArgs: 0, variableArgs: false },
-    s: { fnName: 'closeStroke', numArgs: 0, variableArgs: false },
-    f: { fnName: 'fill', numArgs: 0, variableArgs: false },
-    F: { fnName: 'fill', numArgs: 0, variableArgs: false },
-    'f*': { fnName: 'eoFill', numArgs: 0, variableArgs: false },
-    B: { fnName: 'fillStroke', numArgs: 0, variableArgs: false },
-    'B*': { fnName: 'eoFillStroke', numArgs: 0, variableArgs: false },
-    b: { fnName: 'closeFillStroke', numArgs: 0, variableArgs: false },
-    'b*': { fnName: 'closeEOFillStroke', numArgs: 0, variableArgs: false },
-    n: { fnName: 'endPath', numArgs: 0, variableArgs: false },
+    m: { id: OPS.moveTo, numArgs: 2, variableArgs: false },
+    l: { id: OPS.lineTo, numArgs: 2, variableArgs: false },
+    c: { id: OPS.curveTo, numArgs: 6, variableArgs: false },
+    v: { id: OPS.curveTo2, numArgs: 4, variableArgs: false },
+    y: { id: OPS.curveTo3, numArgs: 4, variableArgs: false },
+    h: { id: OPS.closePath, numArgs: 0, variableArgs: false },
+    re: { id: OPS.rectangle, numArgs: 4, variableArgs: false },
+    S: { id: OPS.stroke, numArgs: 0, variableArgs: false },
+    s: { id: OPS.closeStroke, numArgs: 0, variableArgs: false },
+    f: { id: OPS.fill, numArgs: 0, variableArgs: false },
+    F: { id: OPS.fill, numArgs: 0, variableArgs: false },
+    'f*': { id: OPS.eoFill, numArgs: 0, variableArgs: false },
+    B: { id: OPS.fillStroke, numArgs: 0, variableArgs: false },
+    'B*': { id: OPS.eoFillStroke, numArgs: 0, variableArgs: false },
+    b: { id: OPS.closeFillStroke, numArgs: 0, variableArgs: false },
+    'b*': { id: OPS.closeEOFillStroke, numArgs: 0, variableArgs: false },
+    n: { id: OPS.endPath, numArgs: 0, variableArgs: false },
 
     // Clipping
-    W: { fnName: 'clip', numArgs: 0, variableArgs: false },
-    'W*': { fnName: 'eoClip', numArgs: 0, variableArgs: false },
+    W: { id: OPS.clip, numArgs: 0, variableArgs: false },
+    'W*': { id: OPS.eoClip, numArgs: 0, variableArgs: false },
 
     // Text
-    BT: { fnName: 'beginText', numArgs: 0, variableArgs: false },
-    ET: { fnName: 'endText', numArgs: 0, variableArgs: false },
-    Tc: { fnName: 'setCharSpacing', numArgs: 1, variableArgs: false },
-    Tw: { fnName: 'setWordSpacing', numArgs: 1, variableArgs: false },
-    Tz: { fnName: 'setHScale', numArgs: 1, variableArgs: false },
-    TL: { fnName: 'setLeading', numArgs: 1, variableArgs: false },
-    Tf: { fnName: 'setFont', numArgs: 2, variableArgs: false },
-    Tr: { fnName: 'setTextRenderingMode', numArgs: 1, variableArgs: false },
-    Ts: { fnName: 'setTextRise', numArgs: 1, variableArgs: false },
-    Td: { fnName: 'moveText', numArgs: 2, variableArgs: false },
-    TD: { fnName: 'setLeadingMoveText', numArgs: 2, variableArgs: false },
-    Tm: { fnName: 'setTextMatrix', numArgs: 6, variableArgs: false },
-    'T*': { fnName: 'nextLine', numArgs: 0, variableArgs: false },
-    Tj: { fnName: 'showText', numArgs: 1, variableArgs: false },
-    TJ: { fnName: 'showSpacedText', numArgs: 1, variableArgs: false },
-    '\'': { fnName: 'nextLineShowText', numArgs: 1, variableArgs: false },
-    '"': { fnName: 'nextLineSetSpacingShowText', numArgs: 3,
+    BT: { id: OPS.beginText, numArgs: 0, variableArgs: false },
+    ET: { id: OPS.endText, numArgs: 0, variableArgs: false },
+    Tc: { id: OPS.setCharSpacing, numArgs: 1, variableArgs: false },
+    Tw: { id: OPS.setWordSpacing, numArgs: 1, variableArgs: false },
+    Tz: { id: OPS.setHScale, numArgs: 1, variableArgs: false },
+    TL: { id: OPS.setLeading, numArgs: 1, variableArgs: false },
+    Tf: { id: OPS.setFont, numArgs: 2, variableArgs: false },
+    Tr: { id: OPS.setTextRenderingMode, numArgs: 1, variableArgs: false },
+    Ts: { id: OPS.setTextRise, numArgs: 1, variableArgs: false },
+    Td: { id: OPS.moveText, numArgs: 2, variableArgs: false },
+    TD: { id: OPS.setLeadingMoveText, numArgs: 2, variableArgs: false },
+    Tm: { id: OPS.setTextMatrix, numArgs: 6, variableArgs: false },
+    'T*': { id: OPS.nextLine, numArgs: 0, variableArgs: false },
+    Tj: { id: OPS.showText, numArgs: 1, variableArgs: false },
+    TJ: { id: OPS.showSpacedText, numArgs: 1, variableArgs: false },
+    '\'': { id: OPS.nextLineShowText, numArgs: 1, variableArgs: false },
+    '"': { id: OPS.nextLineSetSpacingShowText, numArgs: 3,
       variableArgs: false },
 
     // Type3 fonts
-    d0: { fnName: 'setCharWidth', numArgs: 2, variableArgs: false },
-    d1: { fnName: 'setCharWidthAndBounds', numArgs: 6, variableArgs: false },
+    d0: { id: OPS.setCharWidth, numArgs: 2, variableArgs: false },
+    d1: { id: OPS.setCharWidthAndBounds, numArgs: 6, variableArgs: false },
 
     // Color
-    CS: { fnName: 'setStrokeColorSpace', numArgs: 1, variableArgs: false },
-    cs: { fnName: 'setFillColorSpace', numArgs: 1, variableArgs: false },
-    SC: { fnName: 'setStrokeColor', numArgs: 4, variableArgs: true },
-    SCN: { fnName: 'setStrokeColorN', numArgs: 33, variableArgs: true },
-    sc: { fnName: 'setFillColor', numArgs: 4, variableArgs: true },
-    scn: { fnName: 'setFillColorN', numArgs: 33, variableArgs: true },
-    G: { fnName: 'setStrokeGray', numArgs: 1, variableArgs: false },
-    g: { fnName: 'setFillGray', numArgs: 1, variableArgs: false },
-    RG: { fnName: 'setStrokeRGBColor', numArgs: 3, variableArgs: false },
-    rg: { fnName: 'setFillRGBColor', numArgs: 3, variableArgs: false },
-    K: { fnName: 'setStrokeCMYKColor', numArgs: 4, variableArgs: false },
-    k: { fnName: 'setFillCMYKColor', numArgs: 4, variableArgs: false },
+    CS: { id: OPS.setStrokeColorSpace, numArgs: 1, variableArgs: false },
+    cs: { id: OPS.setFillColorSpace, numArgs: 1, variableArgs: false },
+    SC: { id: OPS.setStrokeColor, numArgs: 4, variableArgs: true },
+    SCN: { id: OPS.setStrokeColorN, numArgs: 33, variableArgs: true },
+    sc: { id: OPS.setFillColor, numArgs: 4, variableArgs: true },
+    scn: { id: OPS.setFillColorN, numArgs: 33, variableArgs: true },
+    G: { id: OPS.setStrokeGray, numArgs: 1, variableArgs: false },
+    g: { id: OPS.setFillGray, numArgs: 1, variableArgs: false },
+    RG: { id: OPS.setStrokeRGBColor, numArgs: 3, variableArgs: false },
+    rg: { id: OPS.setFillRGBColor, numArgs: 3, variableArgs: false },
+    K: { id: OPS.setStrokeCMYKColor, numArgs: 4, variableArgs: false },
+    k: { id: OPS.setFillCMYKColor, numArgs: 4, variableArgs: false },
 
     // Shading
-    sh: { fnName: 'shadingFill', numArgs: 1, variableArgs: false },
+    sh: { id: OPS.shadingFill, numArgs: 1, variableArgs: false },
 
     // Images
-    BI: { fnName: 'beginInlineImage', numArgs: 0, variableArgs: false },
-    ID: { fnName: 'beginImageData', numArgs: 0, variableArgs: false },
-    EI: { fnName: 'endInlineImage', numArgs: 1, variableArgs: false },
+    BI: { id: OPS.beginInlineImage, numArgs: 0, variableArgs: false },
+    ID: { id: OPS.beginImageData, numArgs: 0, variableArgs: false },
+    EI: { id: OPS.endInlineImage, numArgs: 1, variableArgs: false },
 
     // XObjects
-    Do: { fnName: 'paintXObject', numArgs: 1, variableArgs: false },
-    MP: { fnName: 'markPoint', numArgs: 1, variableArgs: false },
-    DP: { fnName: 'markPointProps', numArgs: 2, variableArgs: false },
-    BMC: { fnName: 'beginMarkedContent', numArgs: 1, variableArgs: false },
-    BDC: { fnName: 'beginMarkedContentProps', numArgs: 2, variableArgs: false },
-    EMC: { fnName: 'endMarkedContent', numArgs: 0, variableArgs: false },
+    Do: { id: OPS.paintXObject, numArgs: 1, variableArgs: false },
+    MP: { id: OPS.markPoint, numArgs: 1, variableArgs: false },
+    DP: { id: OPS.markPointProps, numArgs: 2, variableArgs: false },
+    BMC: { id: OPS.beginMarkedContent, numArgs: 1, variableArgs: false },
+    BDC: { id: OPS.beginMarkedContentProps, numArgs: 2,
+      variableArgs: false },
+    EMC: { id: OPS.endMarkedContent, numArgs: 0, variableArgs: false },
 
     // Compatibility
-    BX: { fnName: 'beginCompat', numArgs: 0, variableArgs: false },
-    EX: { fnName: 'endCompat', numArgs: 0, variableArgs: false },
+    BX: { id: OPS.beginCompat, numArgs: 0, variableArgs: false },
+    EX: { id: OPS.endCompat, numArgs: 0, variableArgs: false },
 
     // (reserved partial commands for the lexer)
     BM: null,
     BD: null,
     'true': null,
     fa: null,
     fal: null,
     fals: null,
@@ -14170,27 +14448,27 @@ var PartialEvaluator = (function Partial
 
         var groupSubtype = group.get('S');
         if (isName(groupSubtype) && groupSubtype.name === 'Transparency') {
           groupOptions.isolated = group.get('I') || false;
           groupOptions.knockout = group.get('K') || false;
           // There is also a group colorspace, but since we put everything in
           // RGB I'm not sure we need it.
         }
-        operatorList.addOp('beginGroup', [groupOptions]);
-      }
-
-      operatorList.addOp('paintFormXObjectBegin', [matrix, bbox]);
+        operatorList.addOp(OPS.beginGroup, [groupOptions]);
+      }
+
+      operatorList.addOp(OPS.paintFormXObjectBegin, [matrix, bbox]);
 
       this.getOperatorList(xobj, xobj.dict.get('Resources') || resources,
                            operatorList);
-      operatorList.addOp('paintFormXObjectEnd', []);
+      operatorList.addOp(OPS.paintFormXObjectEnd, []);
 
       if (group) {
-        operatorList.addOp('endGroup', [groupOptions]);
+        operatorList.addOp(OPS.endGroup, [groupOptions]);
       }
     },
 
     buildPaintImageXObject: function PartialEvaluator_buildPaintImageXObject(
                                 resources, image, inline, operatorList) {
       var self = this;
       var dict = image.dict;
       var w = dict.get('Width', 'W');
@@ -14211,17 +14489,17 @@ var PartialEvaluator = (function Partial
 
         var width = dict.get('Width', 'W');
         var height = dict.get('Height', 'H');
         var bitStrideLength = (width + 7) >> 3;
         var imgArray = image.getBytes(bitStrideLength * height);
         var decode = dict.get('Decode', 'D');
         var inverseDecode = !!decode && decode[0] > 0;
 
-        operatorList.addOp('paintImageMaskXObject',
+        operatorList.addOp(OPS.paintImageMaskXObject,
           [PDFImage.createMask(imgArray, width, height,
                                             inverseDecode)]
         );
         return;
       }
 
       var softMask = dict.get('SMask', 'SM') || false;
       var mask = dict.get('Mask') || false;
@@ -14229,43 +14507,44 @@ var PartialEvaluator = (function Partial
       var SMALL_IMAGE_DIMENSIONS = 200;
       // Inlining small images into the queue as RGB data
       if (inline && !softMask && !mask &&
           !(image instanceof JpegStream) &&
           (w + h) < SMALL_IMAGE_DIMENSIONS) {
         var imageObj = new PDFImage(this.xref, resources, image,
                                     inline, null, null);
         var imgData = imageObj.getImageData();
-        operatorList.addOp('paintInlineImageXObject', [imgData]);
+        operatorList.addOp(OPS.paintInlineImageXObject, [imgData]);
         return;
       }
 
       // If there is no imageMask, create the PDFImage and a lot
       // of image processing can be done here.
       var uniquePrefix = this.uniquePrefix || '';
       var objId = 'img_' + uniquePrefix + (++this.idCounters.obj);
       operatorList.addDependency(objId);
       var args = [objId, w, h];
 
       if (!softMask && !mask && image instanceof JpegStream &&
           image.isNativelySupported(this.xref, resources)) {
         // These JPEGs don't need any more processing so we can just send it.
-        operatorList.addOp('paintJpegXObject', args);
+        operatorList.addOp(OPS.paintJpegXObject, args);
         this.handler.send(
             'obj', [objId, this.pageIndex, 'JpegStream', image.getIR()]);
         return;
       }
 
 
       PDFImage.buildImage(function(imageObj) {
           var imgData = imageObj.getImageData();
-          self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData]);
+          self.handler.send('obj', [objId, self.pageIndex, 'Image', imgData],
+                            null, [imgData.data.buffer]);
         }, self.handler, self.xref, resources, image, inline);
 
-      operatorList.addOp('paintImageXObject', args);
+      operatorList.addOp(OPS.paintImageXObject, args);
     },
 
     handleTilingType: function PartialEvaluator_handleTilingType(
                           fn, args, resources, pattern, patternDict,
                           operatorList) {
       // Create an IR of the pattern code.
       var tilingOpList = this.getOperatorList(pattern,
                                   patternDict.get('Resources') || resources);
@@ -14394,17 +14673,17 @@ var PartialEvaluator = (function Partial
       // This array holds the converted/processed state data.
       var gStateObj = [];
       var gStateMap = gState.map;
       for (var key in gStateMap) {
         var value = gStateMap[key];
         setGStateForKey(gStateObj, key, value);
       }
 
-      operatorList.addOp('setGState', [gStateObj]);
+      operatorList.addOp(OPS.setGState, [gStateObj]);
     },
 
     loadFont: function PartialEvaluator_loadFont(fontName, font, xref,
                                                  resources,
                                                  parentOperatorList) {
 
       function errorFont() {
         return {
@@ -14490,17 +14769,16 @@ var PartialEvaluator = (function Partial
       var xobjs = resources.get('XObject') || new Dict();
       var patterns = resources.get('Pattern') || new Dict();
       // TODO(mduan): pass array of knownCommands rather than OP_MAP
       // dictionary
       var parser = new Parser(new Lexer(stream, OP_MAP), false, xref);
 
       var promise = new Promise();
       var args = [];
-      nextOp:
       while (true) {
 
         var obj = parser.getObj();
 
         if (isEOF(obj)) {
           break;
         }
 
@@ -14509,17 +14787,17 @@ var PartialEvaluator = (function Partial
 
           // Check that the command is valid
           var opSpec = OP_MAP[cmd];
           if (!opSpec) {
             warn('Unknown command "' + cmd + '"');
             continue;
           }
 
-          var fn = opSpec.fnName;
+          var fn = opSpec.id;
 
           // Validate the number of arguments for the command
           if (opSpec.variableArgs) {
             if (args.length > opSpec.numArgs) {
               info('Command ' + fn + ': expected [0,' + opSpec.numArgs +
                   '] args, but received ' + args.length + ' args');
             }
           } else {
@@ -14534,139 +14812,154 @@ var PartialEvaluator = (function Partial
             } else if (args.length > opSpec.numArgs) {
               info('Command ' + fn + ': expected ' + opSpec.numArgs +
                   ' args, but received ' + args.length + ' args');
             }
           }
 
           // TODO figure out how to type-check vararg functions
 
-          if ((cmd == 'SCN' || cmd == 'scn') &&
-               !args[args.length - 1].code) {
-            // compile tiling patterns
-            var patternName = args[args.length - 1];
-            // SCN/scn applies patterns along with normal colors
-            var pattern;
-            if (isName(patternName) &&
-                (pattern = patterns.get(patternName.name))) {
-
-              var dict = isStream(pattern) ? pattern.dict : pattern;
-              var typeNum = dict.get('PatternType');
-
-              if (typeNum == TILING_PATTERN) {
-                self.handleTilingType(fn, args, resources, pattern, dict,
-                                      operatorList);
-                args = [];
-                continue;
-              } else if (typeNum == SHADING_PATTERN) {
-                var shading = dict.get('Shading');
-                var matrix = dict.get('Matrix');
-                var pattern = Pattern.parseShading(shading, matrix, xref,
-                                                    resources);
-                args = pattern.getIR();
-              } else {
-                error('Unkown PatternType ' + typeNum);
-              }
-            }
-          } else if (cmd == 'Do' && !args[0].code) {
-            // eagerly compile XForm objects
-            var name = args[0].name;
-            var xobj = xobjs.get(name);
-            if (xobj) {
-              assertWellFormed(
-                  isStream(xobj), 'XObject should be a stream');
-
-              var type = xobj.dict.get('Subtype');
-              assertWellFormed(
-                isName(type),
-                'XObject should have a Name subtype'
-              );
-
-              if ('Form' == type.name) {
-                self.buildFormXObject(resources, xobj, null, operatorList);
-                args = [];
-                continue;
-              } else if ('Image' == type.name) {
-                self.buildPaintImageXObject(resources, xobj, false,
-                                            operatorList);
-                args = [];
-                continue;
-              } else {
-                error('Unhandled XObject subtype ' + type.name);
-              }
-            }
-          } else if (cmd == 'Tf') { // eagerly collect all fonts
-            var loadedName = self.handleSetFont(resources, args, null,
-                                                operatorList);
-            operatorList.addDependency(loadedName);
-            fn = 'setFont';
-            args[0] = loadedName;
-          } else if (cmd == 'EI') {
-            self.buildPaintImageXObject(resources, args[0], true, operatorList);
-            args = [];
-            continue;
-          } else if (cmd === 'q') { // save
-            var old = this.state;
-            this.stateStack.push(this.state);
-            this.state = old.clone();
-          } else if (cmd === 'Q') { // restore
-            var prev = this.stateStack.pop();
-            if (prev) {
-              this.state = prev;
-            }
-          } else if (cmd === 'Tj') { // showText
-            args[0] = this.handleText(args[0]);
-          } else if (cmd === 'TJ') { // showSpacedText
-            var arr = args[0];
-            var arrLength = arr.length;
-            for (var i = 0; i < arrLength; ++i) {
-              if (isString(arr[i])) {
-                arr[i] = this.handleText(arr[i]);
-              }
-            }
-          } else if (cmd === '\'') { // nextLineShowText
-            args[0] = this.handleText(args[0]);
-          } else if (cmd === '"') { // nextLineSetSpacingShowText
-            args[2] = this.handleText(args[2]);
-          } else if (cmd === 'Tr') { // setTextRenderingMode
-            this.state.textRenderingMode = args[0];
-          }
-
           switch (fn) {
+            case OPS.setStrokeColorN:
+            case OPS.setFillColorN:
+              if (args[args.length - 1].code) {
+                break;
+              }
+              // compile tiling patterns
+              var patternName = args[args.length - 1];
+              // SCN/scn applies patterns along with normal colors
+              var pattern;
+              if (isName(patternName) &&
+                  (pattern = patterns.get(patternName.name))) {
+
+                var dict = isStream(pattern) ? pattern.dict : pattern;
+                var typeNum = dict.get('PatternType');
+
+                if (typeNum == TILING_PATTERN) {
+                  self.handleTilingType(fn, args, resources, pattern, dict,
+                                        operatorList);
+                  args = [];
+                  continue;
+                } else if (typeNum == SHADING_PATTERN) {
+                  var shading = dict.get('Shading');
+                  var matrix = dict.get('Matrix');
+                  var pattern = Pattern.parseShading(shading, matrix, xref,
+                                                      resources);
+                  args = pattern.getIR();
+                } else {
+                  error('Unkown PatternType ' + typeNum);
+                }
+              }
+              break;
+            case OPS.paintXObject:
+              if (args[0].code) {
+                break;
+              }
+              // eagerly compile XForm objects
+              var name = args[0].name;
+              var xobj = xobjs.get(name);
+              if (xobj) {
+                assertWellFormed(
+                    isStream(xobj), 'XObject should be a stream');
+
+                var type = xobj.dict.get('Subtype');
+                assertWellFormed(
+                  isName(type),
+                  'XObject should have a Name subtype'
+                );
+
+                if ('Form' == type.name) {
+                  self.buildFormXObject(resources, xobj, null, operatorList);
+                  args = [];
+                  continue;
+                } else if ('Image' == type.name) {
+                  self.buildPaintImageXObject(resources, xobj, false,
+                                              operatorList);
+                  args = [];
+                  continue;
+                } else {
+                  error('Unhandled XObject subtype ' + type.name);
+                }
+              }
+              break;
+            case OPS.setFont:
+              // eagerly collect all fonts
+              var loadedName = self.handleSetFont(resources, args, null,
+                                                  operatorList);
+              operatorList.addDependency(loadedName);
+              args[0] = loadedName;
+              break;
+            case OPS.endInlineImage:
+              self.buildPaintImageXObject(resources, args[0], true,
+                                          operatorList);
+              args = [];
+              continue;
+            case OPS.save:
+              var old = this.state;
+              this.stateStack.push(this.state);
+              this.state = old.clone();
+              break;
+            case OPS.restore:
+              var prev = this.stateStack.pop();
+              if (prev) {
+                this.state = prev;
+              }
+              break;
+            case OPS.showText:
+              args[0] = this.handleText(args[0]);
+              break;
+            case OPS.showSpacedText:
+              var arr = args[0];
+              var arrLength = arr.length;
+              for (var i = 0; i < arrLength; ++i) {
+                if (isString(arr[i])) {
+                  arr[i] = this.handleText(arr[i]);
+                }
+              }
+              break;
+            case OPS.nextLineShowText:
+              args[0] = this.handleText(args[0]);
+              break;
+            case OPS.nextLineSetSpacingShowText:
+              args[2] = this.handleText(args[2]);
+              break;
+            case OPS.setTextRenderingMode:
+              this.state.textRenderingMode = args[0];
+              break;
             // Parse the ColorSpace data to a raw format.
-            case 'setFillColorSpace':
-            case 'setStrokeColorSpace':
+            case OPS.setFillColorSpace:
+            case OPS.setStrokeColorSpace:
               args = [ColorSpace.parseToIR(args[0], xref, resources)];
               break;
-            case 'shadingFill':
+            case OPS.shadingFill:
               var shadingRes = resources.get('Shading');
               if (!shadingRes)
                 error('No shading resource found');
 
               var shading = shadingRes.get(args[0].name);
               if (!shading)
                 error('No shading object found');
 
               var shadingFill = Pattern.parseShading(
                   shading, null, xref, resources);
               var patternIR = shadingFill.getIR();
               args = [patternIR];
-              fn = 'shadingFill';
-              break;
-            case 'setGState':
+              fn = OPS.shadingFill;
+              break;
+            case OPS.setGState:
               var dictName = args[0];
               var extGState = resources.get('ExtGState');
 
               if (!isDict(extGState) || !extGState.has(dictName.name))
                 break;
 
               var gState = extGState.get(dictName.name);
               self.setGState(resources, gState, operatorList);
               args = [];
-              continue nextOp;
+              continue;
           } // switch
 
           operatorList.addOp(fn, args);
           args = [];
           parser.saveState();
         } else if (obj !== null && obj !== undefined) {
           args.push(obj instanceof Dict ? obj.getAll() : obj);
           assertWellFormed(args.length <= 33, 'Too many arguments');
@@ -15282,27 +15575,39 @@ var PartialEvaluator = (function Partial
 
       return new Font(fontName.name, fontFile, properties);
     }
   };
 
   PartialEvaluator.optimizeQueue =
       function PartialEvaluator_optimizeQueue(queue) {
 
+    function squash(array, index, howMany, element) {
+      if (isArray(array)) {
+        array.splice(index, howMany, element);
+      } else {
+        // Replace the element.
+        array[index] = element;
+        // Shift everything after the element up.
+        var sub = array.subarray(index + howMany);
+        array.set(sub, index + 1);
+      }
+    }
+
     var fnArray = queue.fnArray, argsArray = queue.argsArray;
     // grouping paintInlineImageXObject's into paintInlineImageXObjectGroup
     // searching for (save, transform, paintInlineImageXObject, restore)+
     var MIN_IMAGES_IN_INLINE_IMAGES_BLOCK = 10;
     var MAX_IMAGES_IN_INLINE_IMAGES_BLOCK = 200;
     var MAX_WIDTH = 1000;
     var IMAGE_PADDING = 1;
-    for (var i = 0, ii = fnArray.length; i < ii; i++) {
-      if (fnArray[i] === 'paintInlineImageXObject' &&
-          fnArray[i - 2] === 'save' && fnArray[i - 1] === 'transform' &&
-          fnArray[i + 1] === 'restore') {
+    for (var i = 0, ii = argsArray.length; i < ii; i++) {
+      if (fnArray[i] === OPS.paintInlineImageXObject &&
+          fnArray[i - 2] === OPS.save && fnArray[i - 1] === OPS.transform &&
+          fnArray[i + 1] === OPS.restore) {
         var j = i - 2;
         for (i += 2; i < ii && fnArray[i - 4] === fnArray[i]; i++) {
         }
         var count = Math.min((i - j) >> 2,
                              MAX_IMAGES_IN_INLINE_IMAGES_BLOCK);
         if (count < MIN_IMAGES_IN_INLINE_IMAGES_BLOCK) {
           continue;
         }
@@ -15357,143 +15662,182 @@ var PartialEvaluator = (function Partial
             data[offset + rowSize] = data[offset + rowSize - 4];
             data[offset + rowSize + 1] = data[offset + rowSize - 3];
             data[offset + rowSize + 2] = data[offset + rowSize - 2];
             data[offset + rowSize + 3] = data[offset + rowSize - 1];
             offset -= imgRowSize;
           }
         }
         // replacing queue items
-        fnArray.splice(j, count * 4, ['paintInlineImageXObjectGroup']);
+        squash(fnArray, j, count * 4, OPS.paintInlineImageXObjectGroup);
         argsArray.splice(j, count * 4,
           [{width: imgWidth, height: imgHeight, data: imgData}, map]);
         i = j;
-        ii = fnArray.length;
+        ii = argsArray.length;
       }
     }
     // grouping paintImageMaskXObject's into paintImageMaskXObjectGroup
     // searching for (save, transform, paintImageMaskXObject, restore)+
     var MIN_IMAGES_IN_MASKS_BLOCK = 10;
     var MAX_IMAGES_IN_MASKS_BLOCK = 100;
-    for (var i = 0, ii = fnArray.length; i < ii; i++) {
-      if (fnArray[i] === 'paintImageMaskXObject' &&
-          fnArray[i - 2] === 'save' && fnArray[i - 1] === 'transform' &&
-          fnArray[i + 1] === 'restore') {
+    for (var i = 0, ii = argsArray.length; i < ii; i++) {
+      if (fnArray[i] === OPS.paintImageMaskXObject &&
+          fnArray[i - 2] === OPS.save && fnArray[i - 1] === OPS.transform &&
+          fnArray[i + 1] === OPS.restore) {
         var j = i - 2;
         for (i += 2; i < ii && fnArray[i - 4] === fnArray[i]; i++) {
         }
         var count = Math.min((i - j) >> 2,
                              MAX_IMAGES_IN_MASKS_BLOCK);
         if (count < MIN_IMAGES_IN_MASKS_BLOCK) {
           continue;
         }
         var images = [];
         for (var q = 0; q < count; q++) {
           var transform = argsArray[j + (q << 2) + 1];
           var maskParams = argsArray[j + (q << 2) + 2][0];
           images.push({data: maskParams.data, width: maskParams.width,
             height: maskParams.height, transform: transform});
         }
         // replacing queue items
-        fnArray.splice(j, count * 4, ['paintImageMaskXObjectGroup']);
+        squash(fnArray, j, count * 4, OPS.paintImageMaskXObjectGroup);
         argsArray.splice(j, count * 4, [images]);
         i = j;
-        ii = fnArray.length;
-      }
-    }
-  };
-
+        ii = argsArray.length;
+      }
+    }
+  };
 
   return PartialEvaluator;
 })();
 
-
 var OperatorList = (function OperatorListClosure() {
   var CHUNK_SIZE = 100;
 
-  function OperatorList(messageHandler, pageIndex) {
+    function getTransfers(queue) {
+      var transfers = [];
+      var fnArray = queue.fnArray, argsArray = queue.argsArray;
+      for (var i = 0, ii = queue.length; i < ii; i++) {
+        switch (fnArray[i]) {
+          case OPS.paintInlineImageXObject:
+          case OPS.paintInlineImageXObjectGroup:
+          case OPS.paintImageMaskXObject:
+            var arg = argsArray[i][0]; // first param in imgData
+            transfers.push(arg.data.buffer);
+            break;
+        }
+      }
+      return transfers;
+    }
+
+
+    function OperatorList(messageHandler, pageIndex) {
     this.messageHandler = messageHandler;
-    this.fnArray = [];
+    // When there isn't a message handler the fn array needs to be able to grow
+    // since we can't flush the operators.
+    if (messageHandler) {
+      this.fnArray = new Uint8Array(CHUNK_SIZE);
+    } else {
+      this.fnArray = [];
+    }
     this.argsArray = [];
     this.dependencies = {},
     this.pageIndex = pageIndex;
+    this.fnIndex = 0;
   }
 
   OperatorList.prototype = {
 
+    get length() {
+      return this.argsArray.length;
+    },
+
     addOp: function(fn, args) {
-      this.fnArray.push(fn);
-      this.argsArray.push(args);
-      if (this.messageHandler && this.fnArray.length >= CHUNK_SIZE) {
-        this.flush();
+      if (this.messageHandler) {
+        this.fnArray[this.fnIndex++] = fn;
+        this.argsArray.push(args);
+        if (this.fnIndex >= CHUNK_SIZE) {
+          this.flush();
+        }
+      } else {
+        this.fnArray.push(fn);
+        this.argsArray.push(args);
       }
     },
 
     addDependency: function(dependency) {
       if (dependency in this.dependencies) {
         return;
       }
       this.dependencies[dependency] = true;
-      this.addOp('dependency', [dependency]);
+      this.addOp(OPS.dependency, [dependency]);
     },
 
     addDependencies: function(dependencies) {
       for (var key in dependencies) {
         this.addDependency(key);
       }
     },
 
     addOpList: function(opList) {
-      Util.concatenateToArray(this.fnArray, opList.fnArray);
-      Util.concatenateToArray(this.argsArray, opList.argsArray);
       Util.extendObj(this.dependencies, opList.dependencies);
+      for (var i = 0, ii = opList.length; i < ii; i++) {
+        this.addOp(opList.fnArray[i], opList.argsArray[i]);
+      }
     },
 
     getIR: function() {
       return {
         fnArray: this.fnArray,
-        argsArray: this.argsArray
+        argsArray: this.argsArray,
+        length: this.length
       };
     },
 
     flush: function(lastChunk) {
       PartialEvaluator.optimizeQueue(this);
+      var transfers = getTransfers(this);
       this.messageHandler.send('RenderPageChunk', {
         operatorList: {
           fnArray: this.fnArray,
           argsArray: this.argsArray,
-          lastChunk: lastChunk
+          lastChunk: lastChunk,
+          length: this.length
         },
         pageIndex: this.pageIndex
-      });
+      }, null, transfers);
       this.dependencies = [];
-      this.fnArray = [];
+      this.fnIndex = 0;
       this.argsArray = [];
     }
   };
 
   return OperatorList;
 })();
+
 var TextState = (function TextStateClosure() {
   function TextState() {
     this.fontSize = 0;
     this.ctm = [1, 0, 0, 1, 0, 0];
     this.textMatrix = [1, 0, 0, 1, 0, 0];
     this.stateStack = [];
     //textState variables
     this.leading = 0;
     this.textHScale = 1;
     this.textRise = 0;
   }
   TextState.prototype = {
     push: function TextState_push() {
       this.stateStack.push(this.ctm.slice());
     },
-    pop: function TextStae_pop() {
-      this.ctm = this.stateStack.pop();
+    pop: function TextState_pop() {
+      var prev = this.stateStack.pop();
+      if (prev) {
+        this.ctm = prev;
+      }
     },
     initialiseTextObj: function TextState_initialiseTextObj() {
       var m = this.textMatrix;
       m[0] = 1, m[1] = 0, m[2] = 0, m[3] = 1, m[4] = 0, m[5] = 0;
     },
     setTextMatrix: function TextState_setTextMatrix(a, b, c, d, e, f) {
       var m = this.textMatrix;
       m[0] = a, m[1] = b, m[2] = c, m[3] = d, m[4] = e, m[5] = f;
@@ -18696,18 +19040,18 @@ var Font = (function FontClosure() {
         var instructionsLength = (glyf[j] << 8) | glyf[j + 1];
         j += 2 + instructionsLength;
         var instructionsEnd = j;
         // validating flags
         var coordinatesLength = 0;
         for (var i = 0; i < flagsCount; i++) {
           var flag = glyf[j++];
           if (flag & 0xC0) {
-            // reserved flags must be zero, rejecting
-            return 0;
+            // reserved flags must be zero, cleaning up
+            glyf[j - 1] = flag & 0x3F;
           }
           var xyLength = ((flag & 2) ? 1 : (flag & 16) ? 0 : 2) +
                          ((flag & 4) ? 1 : (flag & 32) ? 0 : 2);
           coordinatesLength += xyLength;
           if (flag & 8) {
             var repeat = glyf[j++];
             i += repeat;
             coordinatesLength += repeat * xyLength;
@@ -21144,16 +21488,19 @@ var CFFFont = (function CFFFontClosure()
       var cff = this.cff;
       var charsets = cff.charset.charset;
       var encoding = cff.encoding ? cff.encoding.encoding : null;
       var charstrings = [];
       var unicodeUsed = [];
       var unassignedUnicodeItems = [];
       var inverseEncoding = [];
       var gidStart = 0;
+      if (charsets[0] === '.notdef') {
+        gidStart = 1;
+      }
       // According to section 9.7.4.2 CIDFontType0C glyph selection should be
       // handled differently.
       if (this.properties.subtype === 'CIDFontType0C') {
         if (this.cff.isCIDFont) {
           // If the font is actually a CID font then we should use the charset
           // to map CIDs to GIDs.
           inverseEncoding = charsets;
         } else {
@@ -21176,19 +21523,16 @@ var CFFFont = (function CFFFontClosure()
             if (!isArray(previousCharcode)) {
               inverseEncoding[gid] = [previousCharcode];
             }
             inverseEncoding[gid].push(charcode | 0);
           } else {
             inverseEncoding[gid] = charcode | 0;
           }
         }
-        if (charsets[0] === '.notdef') {
-          gidStart = 1;
-        }
       }
 
       for (var i = gidStart, ii = charsets.length; i < ii; i++) {
         var glyph = charsets[i];
 
         var codes = inverseEncoding[i];
         if (!isArray(codes)) {
           codes = [codes];
@@ -32499,17 +32843,17 @@ var JpegStream = (function JpegStreamClo
       this.buffer = data;
       this.bufferLength = data.length;
       this.eof = true;
     } catch (e) {
       error('JPEG error: ' + e);
     }
   };
   JpegStream.prototype.getIR = function JpegStream_getIR() {
-    return bytesToString(this.bytes);
+    return PDFJS.createObjectURL(this.bytes, 'image/jpeg');
   };
   /**
    * Checks if the image can be decoded and displayed by the browser without any
    * further processing such as color space conversions.
    */
   JpegStream.prototype.isNativelySupported =
     function JpegStream_isNativelySupported(xref, res) {
     var cs = ColorSpace.parse(this.dict.get('ColorSpace', 'CS'), xref, res);
@@ -34141,37 +34485,42 @@ var WorkerMessageHandler = PDFJS.WorkerM
     }
 
     handler.on('test', function wphSetupTest(data) {
       // check if Uint8Array can be sent to worker
       if (!(data instanceof Uint8Array)) {
         handler.send('test', false);
         return;
       }
+      // making sure postMessage transfers are working
+      var supportTransfers = data[0] === 255;
+      handler.postMessageTransfers = supportTransfers;
       // check if the response property is supported by xhr
       var xhr = new XMLHttpRequest();
       var responseExists = 'response' in xhr;
       // check if the property is actually implemented
       try {
         var dummy = xhr.responseType;
       } catch (e) {
         responseExists = false;
       }
       if (!responseExists) {
         handler.send('test', false);
         return;
       }
-      handler.send('test', true);
+      handler.send('test', {
+        supportTypedArray: true,
+        supportTransfers: supportTransfers
+      });
     });
 
     handler.on('GetDocRequest', function wphSetupDoc(data) {
 
       var onSuccess = function(doc) {
         handler.send('GetDoc', { pdfInfo: doc });
-        pdfManager.ensureModel('traversePages', []).then(null, onFailure);
       };
 
       var onFailure = function(e) {
         if (e instanceof PasswordException) {
           if (e.code === PasswordResponses.NEED_PASSWORD) {
             handler.send('NeedPassword', {
               exception: e
             });
@@ -34238,16 +34587,23 @@ var WorkerMessageHandler = PDFJS.WorkerM
             view: results[2]
           };
 
           handler.send('GetPage', { pageInfo: page });
         });
       });
     });
 
+    handler.on('GetPageIndex', function wphSetupGetPageIndex(data, promise) {
+      var ref = new Ref(data.ref.num, data.ref.gen);
+      pdfManager.pdfModel.catalog.getPageIndex(ref).then(function (pageIndex) {
+        promise.resolve(pageIndex);
+      }, promise.reject.bind(promise));
+    });
+
     handler.on('GetDestinations',
       function wphSetupGetDestinations(data, promise) {
         pdfManager.ensureCatalog('destinations').then(function(destinations) {
           promise.resolve(destinations);
         });
       }
     );
 
@@ -34335,16 +34691,21 @@ var WorkerMessageHandler = PDFJS.WorkerM
               Date.now() - start);
         }, function (e) {
           // Skip errored pages
           promise.reject(e);
         });
       });
     });
 
+    handler.on('Cleanup', function wphCleanup(data, promise) {
+      pdfManager.cleanup();
+      promise.resolve(true);
+    });
+
     handler.on('Terminate', function wphTerminate(data, promise) {
       pdfManager.terminate();
       promise.resolve();
     });
   }
 };
 
 var consoleTimer = {};
new file mode 100644
--- /dev/null
+++ b/browser/extensions/pdfjs/content/default_preferences.js
@@ -0,0 +1,27 @@
+/* -*- Mode: Java; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set shiftwidth=2 tabstop=2 autoindent cindent expandtab: */
+/* Copyright 2013 Mozilla Foundation
+ *
+ * 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
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+/* globals */
+
+'use strict';
+
+var EXPORTED_SYMBOLS = ['DEFAULT_PREFERENCES'];
+
+var DEFAULT_PREFERENCES = {
+  showPreviousViewOnLoad: true,
+  defaultZoomValue: '',
+  ifAvailableShowOutlineOnLoad: false
+};
--- a/browser/extensions/pdfjs/content/web/debugger.js
+++ b/browser/extensions/pdfjs/content/web/debugger.js
@@ -235,16 +235,18 @@ var Stepper = (function StepperClosure()
         out += ' ';
       } else {
         out += glyphs[i].fontChar;
       }
     }
     return out;
   }
 
+  var opMap = null;
+
   var glyphCommands = {
     'showText': 0,
     'showSpacedText': 0,
     'nextLineShowText': 0,
     'nextLineSetSpacingShowText': 2
   };
 
   function Stepper(panel, pageIndex, initialBreakPoints) {
@@ -266,16 +268,22 @@ var Stepper = (function StepperClosure()
       var headerRow = c('tr');
       table.appendChild(headerRow);
       headerRow.appendChild(c('th', 'Break'));
       headerRow.appendChild(c('th', 'Idx'));
       headerRow.appendChild(c('th', 'fn'));
       headerRow.appendChild(c('th', 'args'));
       panel.appendChild(content);
       this.table = table;
+      if (!opMap) {
+        opMap = Object.create(null);
+        for (var key in PDFJS.OPS) {
+          opMap[PDFJS.OPS[key]] = key;
+        }
+      }
     },
     updateOperatorList: function updateOperatorList(operatorList) {
       var self = this;
       for (var i = this.operatorListIdx; i < operatorList.fnArray.length; i++) {
         var line = c('tr');
         line.className = 'line';
         line.dataset.idx = i;
         this.table.appendChild(line);
@@ -295,17 +303,17 @@ var Stepper = (function StepperClosure()
               self.breakPoints.splice(self.breakPoints.indexOf(x), 1);
             StepperManager.saveBreakPoints(self.pageIndex, self.breakPoints);
           };
         })(i);
 
         breakCell.appendChild(cbox);
         line.appendChild(breakCell);
         line.appendChild(c('td', i.toString()));
-        var fn = operatorList.fnArray[i];
+        var fn = opMap[operatorList.fnArray[i]];
         var decArgs = args;
         if (fn in glyphCommands) {
           var glyphIndex = glyphCommands[fn];
           var glyphs = args[glyphIndex];
           var decArgs = args.slice();
           var newArg;
           if (fn === 'showSpacedText') {
             newArg = [];
--- a/browser/extensions/pdfjs/content/web/viewer.html
+++ b/browser/extensions/pdfjs/content/web/viewer.html
@@ -139,17 +139,16 @@ limitations under the License.
                   </button>
                   <div class="splitToolbarButtonSeparator"></div>
                   <button class="toolbarButton pageDown" title="Next Page" id="next" tabindex="7" data-l10n-id="next">
                     <span data-l10n-id="next_label">Next</span>
                   </button>
                 </div>
                 <label id="pageNumberLabel" class="toolbarLabel" for="pageNumber" data-l10n-id="page_label">Page: </label>
                 <input type="number" id="pageNumber" class="toolbarField pageNumber" value="1" size="4" min="1" tabindex="8">
-                </input>
                 <span id="numPages" class="toolbarLabel"></span>
               </div>
               <div id="toolbarViewerRight">
                 <button id="presentationMode" class="toolbarButton presentationMode hiddenLargeView" title="Switch to Presentation Mode" tabindex="12" data-l10n-id="presentation_mode">
                   <span data-l10n-id="presentation_mode_label">Presentation Mode</span>
                 </button>
 
                 <button id="openFile" class="toolbarButton openFile hiddenLargeView" title="Open File" tabindex="13" data-l10n-id="open_file">
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -11,18 +11,18 @@
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 /* globals PDFJS, PDFBug, FirefoxCom, Stats, Cache, PDFFindBar, CustomStyle,
            PDFFindController, ProgressBar, TextLayerBuilder, DownloadManager,
-           getFileName, getOutputScale, scrollIntoView, getPDFFileNameFromURL,
-           PDFHistory, Settings, PageView, ThumbnailView, noContextMenuHandler,
+           getFileName, scrollIntoView, getPDFFileNameFromURL, PDFHistory,
+           Preferences, Settings, PageView, ThumbnailView, noContextMenuHandler,
            SecondaryToolbar, PasswordPrompt, PresentationMode */
 
 'use strict';
 
 var DEFAULT_URL = 'compressed.tracemonkey-pldi-09.pdf';
 var DEFAULT_SCALE = 'auto';
 var DEFAULT_SCALE_DELTA = 1.1;
 var UNKNOWN_SCALE = 0;
@@ -33,16 +33,17 @@ var VERTICAL_PADDING = 5;
 var MAX_AUTO_SCALE = 1.25;
 var MIN_SCALE = 0.25;
 var MAX_SCALE = 4.0;
 var SETTINGS_MEMORY = 20;
 var SCALE_SELECT_CONTAINER_PADDING = 8;
 var SCALE_SELECT_PADDING = 22;
 var THUMBNAIL_SCROLL_MARGIN = -19;
 var USE_ONLY_CSS_ZOOM = false;
+var CLEANUP_TIMEOUT = 30000;
 var RenderingStates = {
   INITIAL: 0,
   RUNNING: 1,
   PAUSED: 2,
   FINISHED: 3
 };
 var FindStates = {
   FIND_FOUND: 0,
@@ -149,16 +150,19 @@ function scrollIntoView(element, spot) {
   // producing the error. See also animationStartedClosure.
   var parent = element.offsetParent;
   var offsetY = element.offsetTop + element.clientTop;
   if (!parent) {
     console.error('offsetParent is not set -- cannot scroll');
     return;
   }
   while (parent.clientHeight == parent.scrollHeight) {
+    if (parent.dataset._scaleY) {
+      offsetY /= parent.dataset._scaleY;
+    }
     offsetY += parent.offsetTop;
     parent = parent.offsetParent;
     if (!parent)
       return; // no need to scroll
   }
   if (spot)
     offsetY += spot.top;
   parent.scrollTop = offsetY;
@@ -279,16 +283,102 @@ var Cache = function cacheCache(size) {
     if (data.length > size)
       data.shift().destroy();
   };
 };
 
 
 
 
+var EXPORTED_SYMBOLS = ['DEFAULT_PREFERENCES'];
+
+var DEFAULT_PREFERENCES = {
+  showPreviousViewOnLoad: true,
+  defaultZoomValue: '',
+  ifAvailableShowOutlineOnLoad: false
+};
+
+
+var Preferences = (function PreferencesClosure() {
+  function Preferences() {
+    this.prefs = {};
+    this.initializedPromise = this.readFromStorage().then(function(prefObj) {
+      if (prefObj) {
+        this.prefs = prefObj;
+      }
+    }.bind(this));
+  }
+
+  Preferences.prototype = {
+    writeToStorage: function Preferences_writeToStorage(prefObj) {
+      return;
+    },
+
+    readFromStorage: function Preferences_readFromStorage() {
+      var readFromStoragePromise = new PDFJS.Promise();
+      return readFromStoragePromise;
+    },
+
+    reset: function Preferences_reset() {
+      if (this.initializedPromise.isResolved) {
+        this.prefs = {};
+        this.writeToStorage(this.prefs);
+      }
+    },
+
+    set: function Preferences_set(name, value) {
+      if (!this.initializedPromise.isResolved) {
+        return;
+      } else if (DEFAULT_PREFERENCES[name] === undefined) {
+        console.error('Preferences_set: \'' + name + '\' is undefined.');
+        return;
+      } else if (value === undefined) {
+        console.error('Preferences_set: no value is specified.');
+        return;
+      }
+      var valueType = typeof value;
+      var defaultType = typeof DEFAULT_PREFERENCES[name];
+
+      if (valueType !== defaultType) {
+        if (valueType === 'number' && defaultType === 'string') {
+          value = value.toString();
+        } else {
+          console.error('Preferences_set: \'' + value + '\' is a \"' +
+                        valueType + '\", expected a \"' + defaultType + '\".');
+          return;
+        }
+      }
+      this.prefs[name] = value;
+      this.writeToStorage(this.prefs);
+    },
+
+    get: function Preferences_get(name) {
+      var defaultPref = DEFAULT_PREFERENCES[name];
+
+      if (defaultPref === undefined) {
+        console.error('Preferences_get: \'' + name + '\' is undefined.');
+        return;
+      } else if (this.initializedPromise.isResolved) {
+        var pref = this.prefs[name];
+
+        if (pref !== undefined) {
+          return pref;
+        }
+      }
+      return defaultPref;
+    }
+  };
+
+  return Preferences;
+})();
+
+
+
+
+
 
 
 var FirefoxCom = (function FirefoxComClosure() {
   return {
     /**
      * Creates an event that the extension is listening for and will
      * synchronously respond to.
      * NOTE: It is reccomended to use request() instead since one day we may not
@@ -368,16 +458,27 @@ var DownloadManager = (function Download
         }.bind(this)
       );
     }
   };
 
   return DownloadManager;
 })();
 
+Preferences.prototype.writeToStorage = function(prefObj) {
+  FirefoxCom.requestSync('setPreferences', prefObj);
+};
+
+Preferences.prototype.readFromStorage = function() {
+  var readFromStoragePromise = new PDFJS.Promise();
+  var readPrefs = JSON.parse(FirefoxCom.requestSync('getPreferences'));
+  readFromStoragePromise.resolve(readPrefs);
+  return readFromStoragePromise;
+};
+
 
 var cache = new Cache(CACHE_SIZE);
 var currentPageNumber = 1;
 
 
 /**
  * Settings Manager - This is a utility for saving settings.
  *
@@ -575,21 +676,22 @@ var PDFFindBar = {
       this.findField.classList.remove('notFound');
     }
 
     this.findField.setAttribute('data-status', status);
     this.findMsg.textContent = findMsg;
   },
 
   open: function() {
-    if (this.opened) return;
-
-    this.opened = true;
-    this.toggleButton.classList.add('toggled');
-    this.bar.classList.remove('hidden');
+    if (!this.opened) {
+      this.opened = true;
+      this.toggleButton.classList.add('toggled');
+      this.bar.classList.remove('hidden');
+    }
+
     this.findField.select();
     this.findField.focus();
   },
 
   close: function() {
     if (!this.opened) return;
 
     this.opened = false;
@@ -792,27 +894,27 @@ var PDFFindController = {
     }
 
     if (page.textLayer) {
       page.textLayer.updateMatches();
     }
   },
 
   nextMatch: function() {
-    var pages = this.pdfPageSource.pages;
     var previous = this.state.findPrevious;
+    var currentPageIndex = this.pdfPageSource.page - 1;
     var numPages = this.pdfPageSource.pages.length;
 
     this.active = true;
 
     if (this.dirtyMatch) {
       // Need to recalculate the matches, reset everything.
       this.dirtyMatch = false;
       this.selected.pageIdx = this.selected.matchIdx = -1;
-      this.offset.pageIdx = previous ? numPages - 1 : 0;
+      this.offset.pageIdx = currentPageIndex;
       this.offset.matchIdx = null;
       this.hadMatch = false;
       this.resumeCallback = null;
       this.resumePageIdx = null;
       this.pageMatches = [];
       var self = this;
 
       for (var i = 0; i < numPages; i++) {
@@ -993,18 +1095,17 @@ var PDFHistory = {
     } else {
       // This corresponds to the loading of a new document.
       if (state && state.fingerprint &&
           this.fingerprint !== state.fingerprint) {
         // Reinitialize the browsing history when a new document
         // is opened in the web viewer.
         this.reInitialized = true;
       }
-      window.history.replaceState({ fingerprint: this.fingerprint }, '',
-          document.URL);
+      this._pushOrReplaceState({ fingerprint: this.fingerprint }, true);
     }
 
     var self = this;
     window.addEventListener('popstate', function pdfHistoryPopstate(evt) {
       evt.preventDefault();
       evt.stopPropagation();
 
       if (!self.historyUnlocked) {
@@ -1059,16 +1160,25 @@ var PDFHistory = {
   },
 
   _isStateObjectDefined: function pdfHistory_isStateObjectDefined(state) {
     return (state && state.uid >= 0 &&
             state.fingerprint && this.fingerprint === state.fingerprint &&
             state.target && state.target.hash) ? true : false;
   },
 
+  _pushOrReplaceState: function pdfHistory_pushOrReplaceState(stateObj,
+                                                              replace) {
+    if (replace) {
+      window.history.replaceState(stateObj, '');
+    } else {
+      window.history.pushState(stateObj, '');
+    }
+  },
+
   get isHashChangeUnlocked() {
     if (!this.initialized) {
       return true;
     }
     // If the current hash changes when moving back/forward in the history,
     // this will trigger a 'popstate' event *as well* as a 'hashchange' event.
     // Since the hash generally won't correspond to the exact the position
     // stored in the history's state object, triggering the 'hashchange' event
@@ -1219,21 +1329,18 @@ var PDFHistory = {
     if (addPrevious && !overwrite) {
       var previousParams = this._getPreviousParams();
       if (previousParams) {
         var replacePrevious = (!this.current.dest &&
                                this.current.hash !== this.previousHash);
         this._pushToHistory(previousParams, false, replacePrevious);
       }
     }
-    if (overwrite || this.uid === 0) {
-      window.history.replaceState(this._stateObj(params), '', document.URL);
-    } else {
-      window.history.pushState(this._stateObj(params), '', document.URL);
-    }
+    this._pushOrReplaceState(this._stateObj(params),
+                             (overwrite || this.uid === 0));
     this.currentUid = this.uid++;
     this.current = params;
     this.updatePreviousBookmark = true;
   },
 
   _goTo: function pdfHistory_goTo(state) {
     if (!(this.initialized && this.historyUnlocked &&
           this._isStateObjectDefined(state))) {
@@ -1684,16 +1791,17 @@ var PDFView = {
   pageViewScroll: null,
   thumbnailViewScroll: null,
   pageRotation: 0,
   mouseScrollTimeStamp: 0,
   mouseScrollDelta: 0,
   lastScroll: 0,
   previousPageNumber: 1,
   isViewerEmbedded: (window.parent !== window),
+  idleTimeout: null,
 
   // called once when the document is loaded
   initialize: function pdfViewInitialize() {
     var self = this;
     var container = this.container = document.getElementById('viewerContainer');
     this.pageViewScroll = {};
     this.watchScroll(container, this.pageViewScroll, updateViewarea);
 
@@ -1784,17 +1892,17 @@ var PDFView = {
     if (value === 'custom') {
       return;
     }
     var pages = this.pages;
     var currentPage = pages[this.page - 1];
     var number = parseFloat(value);
     var scale;
 
-    if (number) {
+    if (number > 0) {
       scale = number;
       resetAutoSettings = true;
     } else {
       if (!currentPage) {
         return;
       }
       var pageWidthScale = (this.container.clientWidth - SCROLLBAR_PADDING) /
                             currentPage.width * currentPage.scale;
@@ -1811,16 +1919,20 @@ var PDFView = {
           scale = pageHeightScale;
           break;
         case 'page-fit':
           scale = Math.min(pageWidthScale, pageHeightScale);
           break;
         case 'auto':
           scale = Math.min(MAX_AUTO_SCALE, pageWidthScale);
           break;
+        default:
+          console.error('pdfViewSetScale: \'' + value +
+                        '\' is an unknown zoom value.');
+          return;
       }
     }
     this.currentScaleValue = value;
 
     if (scale === this.currentScale) {
       return;
     }
     for (var i = 0, ii = pages.length; i < ii; i++) {
@@ -2014,17 +2126,18 @@ var PDFView = {
     window.addEventListener('message', function windowMessage(e) {
       var args = e.data;
 
       if (typeof args !== 'object' || !('pdfjsLoadAction' in args))
         return;
       switch (args.pdfjsLoadAction) {
         case 'supportsRangedLoading':
           PDFView.open(args.pdfUrl, 0, undefined, pdfDataRangeTransport, {
-            length: args.length
+            length: args.length,
+            initialData: args.data
           });
           break;
         case 'range':
           pdfDataRangeTransport.onDataRange(args.begin, args.chunk);
           break;
         case 'rangeProgress':
           pdfDataRangeTransport.onDataProgress(args.loaded);
           break;
@@ -2177,19 +2290,19 @@ var PDFView = {
           pageNumber = self.pages.length;
         }
         var currentPage = self.pages[pageNumber - 1];
         currentPage.scrollIntoView(dest);
 
         // Update the browsing history.
         PDFHistory.push({ dest: dest, hash: destString, page: pageNumber });
       } else {
-        self.pendingRefStrLoaded = new PDFJS.Promise();
-        self.pendingRefStr = destRef.num + ' ' + destRef.gen + ' R';
-        self.pendingRefStrLoaded.then(function() {
+        self.pdfDocument.getPageIndex(destRef).then(function (pageIndex) {
+          var pageNum = pageIndex + 1;
+          self.pagesRefMap[destRef.num + ' ' + destRef.gen + ' R'] = pageNum;
           goToDestination(destRef);
         });
       }
     };
 
     this.destinationsPromise.then(function() {
       if (typeof dest === 'string') {
         destString = dest;
@@ -2284,19 +2397,24 @@ var PDFView = {
     // bar to move backwards. So prevent this by only updating the bar if it
     // increases.
     if (percent > PDFView.loadingBar.percent) {
       PDFView.loadingBar.percent = percent;
     }
   },
 
   load: function pdfViewLoad(pdfDocument, scale) {
+    var self = this;
+    var onePageRendered = new PDFJS.Promise();
     function bindOnAfterDraw(pageView, thumbnailView) {
       // when page is painted, using the image as thumbnail base
       pageView.onAfterDraw = function pdfViewLoadOnAfterDraw() {
+        if (!onePageRendered.isResolved) {
+          onePageRendered.resolve();
+        }
         thumbnailView.setImage(pageView.canvas);
       };
     }
 
     PDFFindController.reset();
 
     this.pdfDocument = pdfDocument;
 
@@ -2324,97 +2442,99 @@ var PDFView = {
 
     var pagesCount = pdfDocument.numPages;
 
     var id = pdfDocument.fingerprint;
     document.getElementById('numPages').textContent =
       mozL10n.get('page_of', {pageCount: pagesCount}, 'of {{pageCount}}');
     document.getElementById('pageNumber').max = pagesCount;
 
+    var prefs = PDFView.prefs = new Preferences();
     PDFView.documentFingerprint = id;
     var store = PDFView.store = new Settings(id);
 
     this.pageRotation = 0;
 
     var pages = this.pages = [];
     var pagesRefMap = this.pagesRefMap = {};
     var thumbnails = this.thumbnails = [];
 
     var pagesPromise = this.pagesPromise = new PDFJS.Promise();
-    var self = this;
 
     var firstPagePromise = pdfDocument.getPage(1);
 
     // Fetch a single page so we can get a viewport that will be the default
     // viewport for all pages
     firstPagePromise.then(function(pdfPage) {
       var viewport = pdfPage.getViewport((scale || 1.0) * CSS_UNITS);
-      var pagePromises = [];
       for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
         var viewportClone = viewport.clone();
         var pageView = new PageView(container, pageNum, scale,
                                     self.navigateTo.bind(self),
                                     viewportClone);
         var thumbnailView = new ThumbnailView(thumbsView, pageNum,
                                               viewportClone);
         bindOnAfterDraw(pageView, thumbnailView);
         pages.push(pageView);
         thumbnails.push(thumbnailView);
       }
 
+      // Fetch all the pages since the viewport is needed before printing
+      // starts to create the correct size canvas. Wait until one page is
+      // rendered so we don't tie up too many resources early on.
+      onePageRendered.then(function () {
+        if (!PDFJS.disableAutoFetch) {
+          var getPagesLeft = pagesCount;
+          for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
+            pdfDocument.getPage(pageNum).then(function (pageNum, pdfPage) {
+              var pageView = pages[pageNum - 1];
+              if (!pageView.pdfPage) {
+                pageView.setPdfPage(pdfPage);
+              }
+              var refStr = pdfPage.ref.num + ' ' + pdfPage.ref.gen + ' R';
+              pagesRefMap[refStr] = pageNum;
+              getPagesLeft--;
+              if (!getPagesLeft) {
+                pagesPromise.resolve();
+              }
+            }.bind(null, pageNum));
+          }
+        } else {
+          // XXX: Printing is semi-broken with auto fetch disabled.
+          pagesPromise.resolve();
+        }
+      });
+
       var event = document.createEvent('CustomEvent');
       event.initCustomEvent('documentload', true, true, {});
       window.dispatchEvent(event);
 
       PDFView.loadingBar.setWidth(container);
 
-      for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
-        var pagePromise = pdfDocument.getPage(pageNum);
-        pagePromise.then(function(pdfPage) {
-          var pageNum = pdfPage.pageNumber;
-          var pageView = pages[pageNum - 1];
-          if (!pageView.pdfPage) {
-            // The pdfPage might already be set if we've already entered
-            // pageView.draw()
-            pageView.setPdfPage(pdfPage);
-          }
-          var thumbnailView = thumbnails[pageNum - 1];
-          if (!thumbnailView.pdfPage) {
-            thumbnailView.setPdfPage(pdfPage);
-          }
-
-          var pageRef = pdfPage.ref;
-          var refStr = pageRef.num + ' ' + pageRef.gen + ' R';
-          pagesRefMap[refStr] = pdfPage.pageNumber;
-
-          if (self.pendingRefStr && self.pendingRefStr === refStr) {
-            self.pendingRefStrLoaded.resolve();
-          }
-        });
-        pagePromises.push(pagePromise);
-      }
-
       PDFFindController.firstPagePromise.resolve();
-
-      PDFJS.Promise.all(pagePromises).then(function(pages) {
-        pagesPromise.resolve(pages);
-      });
     });
 
+    var prefsPromise = prefs.initializedPromise;
     var storePromise = store.initializedPromise;
-    PDFJS.Promise.all([firstPagePromise, storePromise]).then(function() {
+    PDFJS.Promise.all([firstPagePromise, prefsPromise, storePromise]).
+        then(function() {
+      var showPreviousViewOnLoad = prefs.get('showPreviousViewOnLoad');
+      var defaultZoomValue = prefs.get('defaultZoomValue');
+
       var storedHash = null;
-      if (store.get('exists', false)) {
+      if (showPreviousViewOnLoad && store.get('exists', false)) {
         var pageNum = store.get('page', '1');
-        var zoom = store.get('zoom', PDFView.currentScale);
+        var zoom = defaultZoomValue || store.get('zoom', PDFView.currentScale);
         var left = store.get('scrollLeft', '0');
         var top = store.get('scrollTop', '0');
 
         storedHash = 'page=' + pageNum + '&zoom=' + zoom + ',' +
                      left + ',' + top;
+      } else if (defaultZoomValue) {
+        storedHash = 'page=1&zoom=' + defaultZoomValue;
       }
       // Initialize the browsing history.
       PDFHistory.initialize(self.documentFingerprint);
 
       self.setInitialView(storedHash, scale);
 
       // Make all navigation keys work on document load,
       // unless the viewer is embedded in a web page.
@@ -2454,16 +2574,23 @@ var PDFView = {
 
     // outline depends on destinations and pagesRefMap
     var promises = [pagesPromise, destinationsPromise,
                     PDFView.animationStartedPromise];
     PDFJS.Promise.all(promises).then(function() {
       pdfDocument.getOutline().then(function(outline) {
         self.outline = new DocumentOutlineView(outline);
         document.getElementById('viewOutline').disabled = !outline;
+
+        if (outline && prefs.get('ifAvailableShowOutlineOnLoad')) {
+          if (!self.sidebarOpen) {
+            document.getElementById('sidebarToggle').click();
+          }
+          self.switchSidebarView('outline');
+        }
       });
     });
 
     pdfDocument.getMetadata().then(function(data) {
       var info = data.info, metadata = data.metadata;
       self.documentInfo = info;
       self.metadata = metadata;
 
@@ -2545,33 +2672,54 @@ var PDFView = {
     if (PDFView.currentScale === UNKNOWN_SCALE) {
       // Scale was not initialized: invalid bookmark or scale was not specified.
       // Setting the default one.
       this.setScale(DEFAULT_SCALE, true);
     }
   },
 
   renderHighestPriority: function pdfViewRenderHighestPriority() {
+    if (PDFView.idleTimeout) {
+      clearTimeout(PDFView.idleTimeout);
+      PDFView.idleTimeout = null;
+    }
+
     // Pages have a higher priority than thumbnails, so check them first.
     var visiblePages = this.getVisiblePages();
     var pageView = this.getHighestPriority(visiblePages, this.pages,
                                            this.pageViewScroll.down);
     if (pageView) {
       this.renderView(pageView, 'page');
       return;
     }
     // No pages needed rendering so check thumbnails.
     if (this.sidebarOpen) {
       var visibleThumbs = this.getVisibleThumbs();
       var thumbView = this.getHighestPriority(visibleThumbs,
                                               this.thumbnails,
                                               this.thumbnailViewScroll.down);
-      if (thumbView)
+      if (thumbView) {
         this.renderView(thumbView, 'thumbnail');
+        return;
+      }
     }
+
+    PDFView.idleTimeout = setTimeout(function () {
+      PDFView.cleanup();
+    }, CLEANUP_TIMEOUT);
+  },
+
+  cleanup: function pdfViewCleanup() {
+    for (var i = 0, ii = this.pages.length; i < ii; i++) {
+      if (this.pages[i] &&
+          this.pages[i].renderingState !== RenderingStates.FINISHED) {
+        this.pages[i].reset();
+      }
+    }
+    this.pdfDocument.cleanup();
   },
 
   getHighestPriority: function pdfViewGetHighestPriority(visible, views,
                                                          scrolledDown) {
     // The state has changed figure out which page has the highest priority to
     // render next (if any).
     // Priority:
     // 1 visible pages
@@ -2638,38 +2786,41 @@ var PDFView = {
     if (hash.indexOf('=') >= 0) {
       var params = PDFView.parseQueryString(hash);
       // borrowing syntax from "Parameters for Opening PDF Files"
       if ('nameddest' in params) {
         PDFHistory.updateNextHashParam(params.nameddest);
         PDFView.navigateTo(params.nameddest);
         return;
       }
+      var pageNumber, dest;
       if ('page' in params) {
-        var pageNumber = (params.page | 0) || 1;
-        if ('zoom' in params) {
-          var zoomArgs = params.zoom.split(','); // scale,left,top
-          // building destination array
-
-          // If the zoom value, it has to get divided by 100. If it is a string,
-          // it should stay as it is.
-          var zoomArg = zoomArgs[0];
-          var zoomArgNumber = parseFloat(zoomArg);
-          if (zoomArgNumber)
-            zoomArg = zoomArgNumber / 100;
-
-          var dest = [null, {name: 'XYZ'},
-                      zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
-                      zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
-                      zoomArg];
-          var currentPage = this.pages[pageNumber - 1];
-          currentPage.scrollIntoView(dest);
-        } else {
-          this.page = pageNumber; // simple page
+        pageNumber = (params.page | 0) || 1;
+      }
+      if ('zoom' in params) {
+        var zoomArgs = params.zoom.split(','); // scale,left,top
+        // building destination array
+
+        // If the zoom value, it has to get divided by 100. If it is a string,
+        // it should stay as it is.
+        var zoomArg = zoomArgs[0];
+        var zoomArgNumber = parseFloat(zoomArg);
+        if (zoomArgNumber) {
+          zoomArg = zoomArgNumber / 100;
         }
+        dest = [null, {name: 'XYZ'},
+                zoomArgs.length > 1 ? (zoomArgs[1] | 0) : null,
+                zoomArgs.length > 2 ? (zoomArgs[2] | 0) : null,
+                zoomArg];
+      }
+      if (dest) {
+        var currentPage = this.pages[(pageNumber || this.page) - 1];
+        currentPage.scrollIntoView(dest);
+      } else if (pageNumber) {
+        this.page = pageNumber; // simple page
       }
       if ('pagemode' in params) {
         var toggle = document.getElementById('sidebarToggle');
         if (params.pagemode === 'thumbs' || params.pagemode === 'bookmarks') {
           if (!this.sidebarOpen) {
             toggle.click();
           }
           this.switchSidebarView(params.pagemode === 'thumbs' ?
@@ -2930,22 +3081,21 @@ var PDFView = {
 
 var PageView = function pageView(container, id, scale,
                                  navigateTo, defaultViewport) {
   this.id = id;
 
   this.rotation = 0;
   this.scale = scale || 1.0;
   this.viewport = defaultViewport;
-  this.pdfPageRotate = defaultViewport.rotate;
+  this.pdfPageRotate = defaultViewport.rotation;
 
   this.renderingState = RenderingStates.INITIAL;
   this.resume = null;
 
-  this.textContent = null;
   this.textLayer = null;
 
   this.zoomLayer = null;
 
   this.annotationLayer = null;
 
   var anchor = document.createElement('a');
   anchor.name = '' + this.id;
@@ -2957,17 +3107,18 @@ var PageView = function pageView(contain
   div.style.height = Math.floor(this.viewport.height) + 'px';
 
   container.appendChild(anchor);
   container.appendChild(div);
 
   this.setPdfPage = function pageViewSetPdfPage(pdfPage) {
     this.pdfPage = pdfPage;
     this.pdfPageRotate = pdfPage.rotate;
-    this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS);
+    var totalRotation = (this.rotation + this.pdfPageRotate) % 360;
+    this.viewport = pdfPage.getViewport(this.scale * CSS_UNITS, totalRotation);
     this.stats = pdfPage.stats;
     this.reset();
   };
 
   this.destroy = function pageViewDestroy() {
     this.zoomLayer = null;
     this.reset();
     if (this.pdfPage) {
@@ -3274,17 +3425,17 @@ var PageView = function pageView(contain
         x = dest[2];
         y = dest[3];
         width = dest[4] - x;
         height = dest[5] - y;
         widthScale = (PDFView.container.clientWidth - SCROLLBAR_PADDING) /
           width / CSS_UNITS;
         heightScale = (PDFView.container.clientHeight - SCROLLBAR_PADDING) /
           height / CSS_UNITS;
-        scale = Math.min(widthScale, heightScale);
+        scale = Math.min(Math.abs(widthScale), Math.abs(heightScale));
         break;
       default:
         return;
     }
 
     if (scale && scale !== PDFView.currentScale) {
       PDFView.setScale(scale, true, true);
     } else if (PDFView.currentScale === UNKNOWN_SCALE) {
@@ -3308,20 +3459,19 @@ var PageView = function pageView(contain
       var width = Math.abs(boundingRect[0][0] - boundingRect[1][0]);
       var height = Math.abs(boundingRect[0][1] - boundingRect[1][1]);
 
       scrollIntoView(div, {left: x, top: y, width: width, height: height});
     }, 0);
   };
 
   this.getTextContent = function pageviewGetTextContent() {
-    if (!this.textContent) {
-      this.textContent = this.pdfPage.getTextContent();
-    }
-    return this.textContent;
+    return PDFView.getPage(this.id).then(function(pdfPage) {
+      return pdfPage.getTextContent();
+    });
   };
 
   this.draw = function pageviewDraw(callback) {
     var pdfPage = this.pdfPage;
 
     if (!pdfPage) {
       var promise = PDFView.getPage(this.id);
       promise.then(function(pdfPage) {
@@ -3359,18 +3509,18 @@ var PageView = function pageView(contain
       var actualSizeViewport = viewport.clone({ scale: CSS_UNITS });
       // Use a scale that will make the canvas be the original intended size
       // of the page.
       outputScale.sx *= actualSizeViewport.width / viewport.width;
       outputScale.sy *= actualSizeViewport.height / viewport.height;
       outputScale.scaled = true;
     }
 
-    canvas.width = Math.floor(viewport.width * outputScale.sx);
-    canvas.height = Math.floor(viewport.height * outputScale.sy);
+    canvas.width = (Math.floor(viewport.width) * outputScale.sx) | 0;
+    canvas.height = (Math.floor(viewport.height) * outputScale.sy) | 0;
     canvas.style.width = Math.floor(viewport.width) + 'px';
     canvas.style.height = Math.floor(viewport.height) + 'px';
     // Add the viewport so it's known what it was originally drawn with.
     canvas._viewport = viewport;
 
     var textLayerDiv = null;
     if (!PDFJS.disableTextLayer) {
       textLayerDiv = document.createElement('div');
@@ -3393,16 +3543,18 @@ var PageView = function pageView(contain
     if (outputScale.scaled) {
       ctx.scale(outputScale.sx, outputScale.sy);
     }
     if (outputScale.scaled && textLayerDiv) {
       var cssScale = 'scale(' + (1 / outputScale.sx) + ', ' +
                                 (1 / outputScale.sy) + ')';
       CustomStyle.setProp('transform' , textLayerDiv, cssScale);
       CustomStyle.setProp('transformOrigin' , textLayerDiv, '0% 0%');
+      textLayerDiv.dataset._scaleX = outputScale.sx;
+      textLayerDiv.dataset._scaleY = outputScale.sy;
     }
 
     // Checking if document fonts are used only once
     var checkIfDocumentFontsUsed = !PDFView.pdfDocument.embeddedFontsUsed;
 
     // Rendering area
 
     var self = this;
@@ -3808,37 +3960,34 @@ var TextLayerBuilder = function textLaye
     if (textDivs.length > MAX_TEXT_DIVS_TO_RENDER)
       return;
 
     for (var i = 0, ii = textDivs.length; i < ii; i++) {
       var textDiv = textDivs[i];
       if ('isWhitespace' in textDiv.dataset) {
         continue;
       }
-      textLayerFrag.appendChild(textDiv);
 
       ctx.font = textDiv.style.fontSize + ' ' + textDiv.style.fontFamily;
       var width = ctx.measureText(textDiv.textContent).width;
 
       if (width > 0) {
+        textLayerFrag.appendChild(textDiv);
         var textScale = textDiv.dataset.canvasWidth / width;
         var rotation = textDiv.dataset.angle;
         var transform = 'scale(' + textScale + ', 1)';
         transform = 'rotate(' + rotation + 'deg) ' + transform;
         CustomStyle.setProp('transform' , textDiv, transform);
         CustomStyle.setProp('transformOrigin' , textDiv, '0% 0%');
-
-        textLayerDiv.appendChild(textDiv);
       }
     }
 
+    textLayerDiv.appendChild(textLayerFrag);
     this.renderingDone = true;
     this.updateMatches();
-
-    textLayerDiv.appendChild(textLayerFrag);
   };
 
   this.setupRenderLayoutTimer = function textLayerSetupRenderLayoutTimer() {
     // Schedule renderLayout() if user has been scrolling, otherwise
     // run it right away
     var RENDER_DELAY = 200; // in ms
     var self = this;
     var lastScroll = this.lastScrollSource === null ?
@@ -4582,17 +4731,17 @@ window.addEventListener('keydown', funct
 
   // First, handle the key bindings that are independent whether an input
   // control is selected or not.
   if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
     // either CTRL or META key with optional SHIFT.
     switch (evt.keyCode) {
       case 70: // f
         if (!PDFView.supportsIntegratedFind) {
-          PDFFindBar.toggle();
+          PDFFindBar.open();
           handled = true;
         }
         break;
       case 71: // g
         if (!PDFView.supportsIntegratedFind) {
           PDFFindBar.dispatchEvent('again', cmd === 5 || cmd === 12);
           handled = true;
         }
@@ -4624,16 +4773,21 @@ window.addEventListener('keydown', funct
 
   // CTRL+ALT or Option+Command
   if (cmd === 3 || cmd === 10) {
     switch (evt.keyCode) {
       case 80: // p
         SecondaryToolbar.presentationModeClick();
         handled = true;
         break;
+      case 71: // g
+        // focuses input#pageNumber field
+        document.getElementById('pageNumber').select();
+        handled = true;
+        break;
     }
   }
 
   if (handled) {
     evt.preventDefault();
     return;
   }
 
--- a/browser/extensions/pdfjs/extension-files
+++ b/browser/extensions/pdfjs/extension-files
@@ -1,15 +1,16 @@
 chrome.manifest
 components/PdfRedirector.js
 components/PdfStreamConverter.js
 content/PdfJs.jsm
 content/PdfJsTelemetry.jsm
 content/build/pdf.js
 content/build/pdf.worker.js
+content/default_preferences.js
 content/network.js
 content/web/debugger.js
 content/web/images/annotation-check.svg
 content/web/images/annotation-comment.svg
 content/web/images/annotation-help.svg
 content/web/images/annotation-insert.svg
 content/web/images/annotation-key.svg
 content/web/images/annotation-newparagraph.svg
--- a/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/gclicommands.properties
@@ -1182,20 +1182,24 @@ paintflashingChromeDesc=chrome frames
 # LOCALIZATION NOTE (paintflashingManual) A longer description describing the
 # set of commands that control paint flashing.
 paintflashingManual=Draw repainted areas in different colors
 
 # LOCALIZATION NOTE (paintflashingTooltip) A string displayed as the
 # tooltip of button in devtools toolbox which toggles paint flashing.
 paintflashingTooltip=Highlight painted area
 
-# LOCALIZATION NOTE (paintflashingOnDesc) A very short string used to describe the
-# function of the "paintflashing on" command.
+# LOCALIZATION NOTE (paintflashingToggleDesc) A very short string used to describe the
+# function of the "paintflashing toggle" command.
 paintflashingToggleDesc=Toggle paint flashing
 
+# LOCALIZATION NOTE (splitconsoleTooltip) A string displayed as the
+# tooltip of button in devtools toolbox which toggles the split webconsole.
+splitconsoleTooltip=Toggle split console
+
 # LOCALIZATION NOTE (appCacheDesc) A very short string used to describe the
 # function of the "appcache" command
 appCacheDesc=Application cache utilities
 
 # LOCALIZATION NOTE (appCacheValidateDesc) A very short string used to describe
 # the function of the "appcache validate" command.
 appCacheValidateDesc=Validate cache manifest
 
--- a/browser/metro/base/content/appbar.js
+++ b/browser/metro/base/content/appbar.js
@@ -110,20 +110,21 @@ var Appbar = {
         Appbar._updateStarButton();
       });
     }
   },
 
   onMenuButton: function(aEvent) {
       let typesArray = [];
 
-      if (!BrowserUI.isStartTabVisible)
+      if (!BrowserUI.isStartTabVisible) {
         typesArray.push("find-in-page");
-      if (ContextCommands.getPageSource())
-        typesArray.push("view-page-source");
+        if (ContextCommands.getPageSource())
+          typesArray.push("view-page-source");
+      }
       if (ContextCommands.getStoreLink())
         typesArray.push("ms-meta-data");
       if (ConsolePanelView.enabled)
         typesArray.push("open-error-console");
       if (!Services.metro.immersive)
         typesArray.push("open-jsshell");
 
       typesArray.push("view-on-desktop");
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -260,20 +260,20 @@ Desktop browser's sync prefs.
           </hbox>
 
           <stack id="toolbar-contextual">
             <observes element="bcast_windowState" attribute="*"/>
             <observes element="bcast_urlbarState" attribute="*"/>
             <hbox id="toolbar-context-page" pack="end">
               <circularprogressindicator id="download-progress" class="appbar-primary"
                                      oncommand="MetroDownloadsView.onDownloadButton()"/>
-              <toolbarbutton id="star-button" class="appbar-primary"
+              <toolbarbutton id="star-button" class="appbar-primary hide-on-start"
                              type="checkbox"
                              oncommand="Appbar.onStarButton()"/>
-              <toolbarbutton id="pin-button" class="appbar-primary"
+              <toolbarbutton id="pin-button" class="appbar-primary hide-on-start"
                              type="checkbox"
                              oncommand="Appbar.onPinButton()"/>
               <toolbarbutton id="menu-button" class="appbar-primary"
                              oncommand="Appbar.onMenuButton(event)"/>
             </hbox>
 
             <hbox id="toolbar-context-autocomplete" pack="end">
               <toolbarbutton id="close-button" class="appbar-primary"
--- a/browser/metro/base/content/contenthandlers/ContextMenuHandler.js
+++ b/browser/metro/base/content/contenthandlers/ContextMenuHandler.js
@@ -113,32 +113,46 @@ var ContextMenuHandler = {
     // paste text if this is an input control
     if (Util.isTextInput(this._target)) {
       let edit = this._target.QueryInterface(Ci.nsIDOMNSEditableElement);
       if (edit) {
         edit.editor.paste(Ci.nsIClipboard.kGlobalClipboard);
       } else {
         Util.dumpLn("error: target element does not support nsIDOMNSEditableElement");
       }
+    } else if (this._target.isContentEditable) {
+      try {
+        this._target.ownerDocument.execCommand("paste",
+                                               false,
+                                               Ci.nsIClipboard.kGlobalClipboard);
+      } catch (ex) {
+        dump("ContextMenuHandler: exception pasting into contentEditable: " + ex.message + "\n");
+      }
     }
     this.reset();
   },
 
   _onCopyImage: function _onCopyImage() {
     Util.copyImageToClipboard(this._target);
   },
 
   _onCut: function _onCut() {
     if (Util.isTextInput(this._target)) {
       let edit = this._target.QueryInterface(Ci.nsIDOMNSEditableElement);
       if (edit) {
         edit.editor.cut();
       } else {
         Util.dumpLn("error: target element does not support nsIDOMNSEditableElement");
       }
+    } else if (this._target.isContentEditable) {
+      try {
+        this._target.ownerDocument.execCommand("cut", false);
+      } catch (ex) {
+        dump("ContextMenuHandler: exception cutting from contentEditable: " + ex.message + "\n");
+      }
     }
     this.reset();
   },
 
   _onCopy: function _onCopy() {
     if (Util.isTextInput(this._target)) {
       let edit = this._target.QueryInterface(Ci.nsIDOMNSEditableElement);
       if (edit) {
@@ -183,22 +197,23 @@ var ContextMenuHandler = {
       linkURL: "",
       linkTitle: "",
       linkProtocol: null,
       mediaURL: "",
       contentType: "",
       contentDisposition: "",
       string: "",
     };
+    let uniqueStateTypes = new Set();
 
     // Do checks for nodes that never have children.
     if (popupNode.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
       // See if the user clicked on an image.
       if (popupNode instanceof Ci.nsIImageLoadingContent && popupNode.currentURI) {
-        state.types.push("image");
+        uniqueStateTypes.add("image");
         state.label = state.mediaURL = popupNode.currentURI.spec;
         imageUrl = state.mediaURL;
         this._target = popupNode;
 
         // Retrieve the type of image from the cache since the url can fail to
         // provide valuable informations
         try {
           let imageCache = Cc["@mozilla.org/image/cache;1"].getService(Ci.imgICache);
@@ -213,116 +228,147 @@ var ContextMenuHandler = {
           Util.dumpLn(ex.message);
           // Failure to get type and content-disposition off the image is non-fatal
         }
       }
     }
 
     let elem = popupNode;
     let isText = false;
+    let isEditableText = false;
 
     while (elem) {
       if (elem.nodeType == Ci.nsIDOMNode.ELEMENT_NODE) {
         // is the target a link or a descendant of a link?
         if (Util.isLink(elem)) {
           // If this is an image that links to itself, don't include both link and
           // image otpions.
           if (imageUrl == this._getLinkURL(elem)) {
             elem = elem.parentNode;
             continue;
           }
 
-          state.types.push("link");
+          uniqueStateTypes.add("link");
           state.label = state.linkURL = this._getLinkURL(elem);
           linkUrl = state.linkURL;
           state.linkTitle = popupNode.textContent || popupNode.title;
           state.linkProtocol = this._getProtocol(this._getURI(state.linkURL));
           // mark as text so we can pickup on selection below
           isText = true;
           break;
-        } else if (Util.isTextInput(elem)) {
+        }
+        // is the target contentEditable (not just inheriting contentEditable)
+        else if (elem.contentEditable == "true") {
+          this._target = elem;
+          isEditableText = true;
+          isText = true;
+          uniqueStateTypes.add("input-text");
+
+          if (elem.textContent.length) {
+            uniqueStateTypes.add("selectable");
+          } else {
+            uniqueStateTypes.add("input-empty");
+          }
+          break;
+        }
+        // is the target a text input
+        else if (Util.isTextInput(elem)) {
+          this._target = elem;
+          isEditableText = true;
+          uniqueStateTypes.add("input-text");
+
           let selectionStart = elem.selectionStart;
           let selectionEnd = elem.selectionEnd;
 
-          state.types.push("input-text");
-          this._target = elem;
-
           // Don't include "copy" for password fields.
           if (!(elem instanceof Ci.nsIDOMHTMLInputElement) || elem.mozIsTextField(true)) {
             // If there is a selection add cut and copy
             if (selectionStart != selectionEnd) {
-              state.types.push("cut");
-              state.types.push("copy");
+              uniqueStateTypes.add("cut");
+              uniqueStateTypes.add("copy");
               state.string = elem.value.slice(selectionStart, selectionEnd);
             } else if (elem.value && elem.textLength) {
               // There is text and it is not selected so add selectable items
-              state.types.push("selectable");
+              uniqueStateTypes.add("selectable");
               state.string = elem.value;
             }
           }
 
           if (!elem.textLength) {
-            state.types.push("input-empty");
-          }
-
-          let flavors = ["text/unicode"];
-          let cb = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
-          let hasData = cb.hasDataMatchingFlavors(flavors,
-                                                  flavors.length,
-                                                  Ci.nsIClipboard.kGlobalClipboard);
-          if (hasData && !elem.readOnly) {
-            state.types.push("paste");
+            uniqueStateTypes.add("input-empty");
           }
           break;
-        } else if (Util.isText(elem)) {
+        }
+        // is the target an element containing text content
+        else if (Util.isText(elem)) {
           isText = true;
-        } else if (elem instanceof Ci.nsIDOMHTMLMediaElement ||
+        }
+        // is the target a media element
+        else if (elem instanceof Ci.nsIDOMHTMLMediaElement ||
                    elem instanceof Ci.nsIDOMHTMLVideoElement) {
           state.label = state.mediaURL = (elem.currentSrc || elem.src);
-          state.types.push((elem.paused || elem.ended) ?
+          uniqueStateTypes.add((elem.paused || elem.ended) ?
             "media-paused" : "media-playing");
           if (elem instanceof Ci.nsIDOMHTMLVideoElement) {
-            state.types.push("video");
+            uniqueStateTypes.add("video");
           }
         }
       }
 
       elem = elem.parentNode;
     }
 
     // Over arching text tests
     if (isText) {
       // If this is text and has a selection, we want to bring
       // up the copy option on the context menu.
       let selection = targetWindow.getSelection();
       if (selection && this._tapInSelection(selection, aX, aY)) {
         state.string = targetWindow.getSelection().toString();
-        state.types.push("copy");
-        state.types.push("selected-text");
+        uniqueStateTypes.add("copy");
+        uniqueStateTypes.add("selected-text");
+        if (isEditableText) {
+          uniqueStateTypes.add("cut");
+        }
       } else {
         // Add general content text if this isn't anything specific
-        if (state.types.indexOf("image") == -1 &&
-            state.types.indexOf("media") == -1 &&
-            state.types.indexOf("video") == -1 &&
-            state.types.indexOf("link") == -1 &&
-            state.types.indexOf("input-text") == -1) {
-          state.types.push("content-text");
+        if (!(
+            uniqueStateTypes.has("image") ||
+            uniqueStateTypes.has("media") ||
+            uniqueStateTypes.has("video") ||
+            uniqueStateTypes.has("link") ||
+            uniqueStateTypes.has("input-text")
+        )) {
+          uniqueStateTypes.add("content-text");
         }
       }
     }
 
+    // Is paste applicable here?
+    if (isEditableText) {
+      let flavors = ["text/unicode"];
+      let cb = Cc["@mozilla.org/widget/clipboard;1"].getService(Ci.nsIClipboard);
+      let hasData = cb.hasDataMatchingFlavors(flavors,
+                                              flavors.length,
+                                              Ci.nsIClipboard.kGlobalClipboard);
+      // add paste if there's data
+      if (hasData && !elem.readOnly) {
+        uniqueStateTypes.add("paste");
+      }
+    }
     // populate position and event source
     state.xPos = offsetX + aX;
     state.yPos = offsetY + aY;
     state.source = aInputSrc;
 
     for (let i = 0; i < this._types.length; i++)
       if (this._types[i].handler(state, popupNode))
-        state.types.push(this._types[i].name);
+        uniqueStateTypes.add(this._types[i].name);
 
+    state.types = [type for (type of uniqueStateTypes)];
     this._previousState = state;
 
     sendAsyncMessage("Content:ContextMenu", state);
   },
 
   _tapInSelection: function (aSelection, aX, aY) {
     if (!aSelection || !aSelection.rangeCount) {
       return false;
--- a/browser/metro/locales/en-US/chrome/browser.dtd
+++ b/browser/metro/locales/en-US/chrome/browser.dtd
@@ -104,8 +104,29 @@
 <!ENTITY newTab2.key "n">
 <!ENTITY closeTab.key "w">
 
 <!-- DEVELOPER SHORTCUTS (control+shift+key) -->
 <!ENTITY jsConsole.key "j">
 <!ENTITY optionsFlyout.key "o">
 <!ENTITY syncFlyout.key "s">
 <!ENTITY aboutFlyout.key "a">
+
+<!-- FIRT RUN EXPERIENCE -->
+<!-- LOCALIZATION NOTE (firstRunTabs.label,
+                        firstRunTopSites.label,
+                        firstRunBookmarks.label,
+                        firstRunMenu.label,
+                        firstRunHistory.label )
+     These strings appear on the Firefox Start page the first time
+     Firefox for Windows 8 Touch (Metro) is launched.  Each one
+     has an arrow pointing toward the feature it references.  The code
+     to display these strings is not enabled yet, but will be soon.
+     For now, you can see this mockup for an example of how they are
+     used: https://bug941284.bugzilla.mozilla.org/attachment.cgi?id=8344046
+     -->
+<!ENTITY firstRunTabs.label "Looking for your tabs? Just pull down or right-click">
+<!ENTITY firstRunTopSites.label "Go to the sites you visit most">
+<!ENTITY firstRunBookmarks.label "Find pages you've saved for later">
+<!ENTITY firstRunMenu.label "Access more features and options">
+<!ENTITY firstRunHistory.label "See where you've been on the Web">
+<!ENTITY firstRunWelcome.label "Welcome to &brandShortName;">
+<!ENTITY firstRunDifferent.label "Different by design">
--- a/browser/metro/theme/browser.css
+++ b/browser/metro/theme/browser.css
@@ -709,24 +709,25 @@ documenttab[selected] .documenttab-selec
 #urlbar:not([editing])[loading] > #stop-button,
 #urlbar:not([editing]):not([loading]) > #reload-button {
   visibility: visible;
 }
 
 /* Contextual toolbar controls */
 
 #toolbar-context-autocomplete,
+.hide-on-start,
 #toolbar-context-page {
   transition-property: opacity, visibility;
   transition-duration: @forward_transition_length@;
   transition-timing-function: @metro_animation_easing@;
 }
 
 #toolbar-contextual:not([autocomplete]) #toolbar-context-autocomplete,
-#toolbar-contextual[startpage] #toolbar-context-page,
+#toolbar-contextual[startpage] .hide-on-start,
 #toolbar-contextual[autocomplete] #toolbar-context-page {
   opacity: 0;
   visibility: hidden;
   pointer-events: none;
 }
 
 #toolbar[viewstate="snapped"] #toolbar-contextual {
   visibility: collapse;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..265ab5c7b1968f393fb91c99d85bd1868e790685
GIT binary patch
literal 695
zc$@*Z0!aOdP)<h;3K|Lk000e1NJLTq002M$000mO1^@s6rssJn0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!SxH1eRCwBA`Tzev!%zn5p))Ab&jKRY
zx%tR8;OEy*1FB!as|!Sl0kJNWCKkK`;>#>B-p3E`=a6bSzkpy0lKyjK=r=;r|A`F!
zu#nT`Hdo(6s{XGhZ@RD`@&5n~EG1Sm(81kkLVtn!ok-BX4^8MFP`@k*`X3{4#lC#~
z&hYc_k#pZaJibdvFQ<U4GP|vf5hG9^asd1T;?E=m;6E(<0n^X;?;it)pj;0SFDF88
zB*VXd%Rme!JPsnq1C;3hOQL>M_5Vn;9;$vA1ptZKq3Q=y0Q@0QKZ<(Bzoco0sUJQm
z;4g{Vq3T(v<^am|Q<egNNgCLsb0$)+GgLiF0Q>`1ExqJA0IGi<B`E-?|2Fw4;12_z
zns73Zl}wE0FJHf5qySJo+yG*{c>XMuU|q_|sfwi@4+Jq@zIahdNIwS$y9s)L0`;4N
zn`kdz^bpd|&dzR&ny3FVyg7IM=AWOR-*PJ|=@HV;#Kq2oQ3e2GpaxVIOGrvBXJlmh
zj}K!j1AzM1g7k|^NHjAsG5*7caioCbApK(E5(P|5On>oVY_))>p0YZZuyXsi`wzA;
zGc*3hhq2ZIz!+$Owz5K@4kU!pQ$R3Oe>sr)PXPbJ(vq}<>hC1NZj5%o>q~cU0$EO?
zI$HJ&|A1ED#s4s~0MOzJm}7uRl;m0fsDCAt{hy7EjU4@lVQf}bR#No80<{Fiv=r1q
z9mF3$elW1GGvhD7{(!=d@f8bF$D)UU0szz>Nuhpw3bbE-adgK5W)41XqD#;ppT3G{
ddR_(yFaXq#Q#&@V%y|F+002ovPDHLkV1i|uIRO9w
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -151,16 +151,17 @@ browser.jar:
 * skin/classic/browser/devtools/light-theme.css       (../shared/devtools/light-theme.css)
   skin/classic/browser/devtools/controls.png          (../shared/devtools/controls.png)
 * skin/classic/browser/devtools/widgets.css           (devtools/widgets.css)
   skin/classic/browser/devtools/commandline-icon.png  (devtools/commandline-icon.png)
   skin/classic/browser/devtools/command-paintflashing.png  (devtools/command-paintflashing.png)
   skin/classic/browser/devtools/command-responsivemode.png (devtools/command-responsivemode.png)
   skin/classic/browser/devtools/command-scratchpad.png (devtools/command-scratchpad.png)
   skin/classic/browser/devtools/command-tilt.png      (devtools/command-tilt.png)
+  skin/classic/browser/devtools/command-console.png   (devtools/command-console.png)
   skin/classic/browser/devtools/alerticon-warning.png (devtools/alerticon-warning.png)
   skin/classic/browser/devtools/ruleview.css          (devtools/ruleview.css)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
   skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
   skin/classic/browser/devtools/webconsole.png                  (devtools/webconsole.png)
   skin/classic/browser/devtools/commandline.css              (devtools/commandline.css)
   skin/classic/browser/devtools/markup-view.css       (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/editor-error.png       (devtools/editor-error.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..265ab5c7b1968f393fb91c99d85bd1868e790685
GIT binary patch
literal 695
zc$@*Z0!aOdP)<h;3K|Lk000e1NJLTq002M$000mO1^@s6rssJn0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!SxH1eRCwBA`Tzev!%zn5p))Ab&jKRY
zx%tR8;OEy*1FB!as|!Sl0kJNWCKkK`;>#>B-p3E`=a6bSzkpy0lKyjK=r=;r|A`F!
zu#nT`Hdo(6s{XGhZ@RD`@&5n~EG1Sm(81kkLVtn!ok-BX4^8MFP`@k*`X3{4#lC#~
z&hYc_k#pZaJibdvFQ<U4GP|vf5hG9^asd1T;?E=m;6E(<0n^X;?;it)pj;0SFDF88
zB*VXd%Rme!JPsnq1C;3hOQL>M_5Vn;9;$vA1ptZKq3Q=y0Q@0QKZ<(Bzoco0sUJQm
z;4g{Vq3T(v<^am|Q<egNNgCLsb0$)+GgLiF0Q>`1ExqJA0IGi<B`E-?|2Fw4;12_z
zns73Zl}wE0FJHf5qySJo+yG*{c>XMuU|q_|sfwi@4+Jq@zIahdNIwS$y9s)L0`;4N
zn`kdz^bpd|&dzR&ny3FVyg7IM=AWOR-*PJ|=@HV;#Kq2oQ3e2GpaxVIOGrvBXJlmh
zj}K!j1AzM1g7k|^NHjAsG5*7caioCbApK(E5(P|5On>oVY_))>p0YZZuyXsi`wzA;
zGc*3hhq2ZIz!+$Owz5K@4kU!pQ$R3Oe>sr)PXPbJ(vq}<>hC1NZj5%o>q~cU0$EO?
zI$HJ&|A1ED#s4s~0MOzJm}7uRl;m0fsDCAt{hy7EjU4@lVQf}bR#No80<{Fiv=r1q
z9mF3$elW1GGvhD7{(!=d@f8bF$D)UU0szz>Nuhpw3bbE-adgK5W)41XqD#;ppT3G{
ddR_(yFaXq#Q#&@V%y|F+002ovPDHLkV1i|uIRO9w
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -253,16 +253,17 @@ browser.jar:
 * skin/classic/browser/devtools/light-theme.css             (../shared/devtools/light-theme.css)
   skin/classic/browser/devtools/controls.png                (../shared/devtools/controls.png)
 * skin/classic/browser/devtools/widgets.css                 (devtools/widgets.css)
   skin/classic/browser/devtools/commandline-icon.png        (devtools/commandline-icon.png)
   skin/classic/browser/devtools/command-paintflashing.png   (devtools/command-paintflashing.png)
   skin/classic/browser/devtools/command-responsivemode.png  (devtools/command-responsivemode.png)
   skin/classic/browser/devtools/command-scratchpad.png      (devtools/command-scratchpad.png)
   skin/classic/browser/devtools/command-tilt.png            (devtools/command-tilt.png)
+  skin/classic/browser/devtools/command-console.png         (devtools/command-console.png)
   skin/classic/browser/devtools/alerticon-warning.png       (devtools/alerticon-warning.png)
   skin/classic/browser/devtools/ruleview.css                (devtools/ruleview.css)
   skin/classic/browser/devtools/commandline.css             (devtools/commandline.css)
   skin/classic/browser/devtools/markup-view.css             (../shared/devtools/markup-view.css)
   skin/classic/browser/devtools/editor-error.png             (devtools/editor-error.png)
   skin/classic/browser/devtools/editor-breakpoint.png        (devtools/editor-breakpoint.png)
   skin/classic/browser/devtools/editor-debug-location.png    (devtools/editor-debug-location.png)
 * skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
--- a/browser/themes/shared/devtools/toolbars.inc.css
+++ b/browser/themes/shared/devtools/toolbars.inc.css
@@ -436,16 +436,33 @@
 #command-button-scratchpad:hover {
   -moz-image-region: rect(0px, 32px, 16px, 16px);
 }
 
 #command-button-scratchpad:hover:active {
   -moz-image-region: rect(0px, 48px, 16px, 32px);
 }
 
+#command-button-splitconsole {
+  list-style-image: url("chrome://browser/skin/devtools/command-console.png");
+  -moz-image-region: rect(0px, 16px, 16px, 0px);
+}
+
+#command-button-splitconsole:hover {
+  -moz-image-region: rect(0px, 32px, 16px, 16px);
+}
+
+#command-button-splitconsole:hover:active {
+  -moz-image-region: rect(0px, 48px, 16px, 32px);
+}
+
+#command-button-splitconsole[checked=true] {
+  -moz-image-region: rect(0px, 64px, 16px, 48px);
+}
+
 /* Tabs */
 
 .devtools-tabbar {
   -moz-appearance: none;
   background-image: url("background-noise-toolbar.png"),
                     linear-gradient(#303840, #2d3640);
   border-color: #060a0d;
   border-style: solid;
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..265ab5c7b1968f393fb91c99d85bd1868e790685
GIT binary patch
literal 695
zc$@*Z0!aOdP)<h;3K|Lk000e1NJLTq002M$000mO1^@s6rssJn0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!SxH1eRCwBA`Tzev!%zn5p))Ab&jKRY
zx%tR8;OEy*1FB!as|!Sl0kJNWCKkK`;>#>B-p3E`=a6bSzkpy0lKyjK=r=;r|A`F!
zu#nT`Hdo(6s{XGhZ@RD`@&5n~EG1Sm(81kkLVtn!ok-BX4^8MFP`@k*`X3{4#lC#~
z&hYc_k#pZaJibdvFQ<U4GP|vf5hG9^asd1T;?E=m;6E(<0n^X;?;it)pj;0SFDF88
zB*VXd%Rme!JPsnq1C;3hOQL>M_5Vn;9;$vA1ptZKq3Q=y0Q@0QKZ<(Bzoco0sUJQm
z;4g{Vq3T(v<^am|Q<egNNgCLsb0$)+GgLiF0Q>`1ExqJA0IGi<B`E-?|2Fw4;12_z
zns73Zl}wE0FJHf5qySJo+yG*{c>XMuU|q_|sfwi@4+Jq@zIahdNIwS$y9s)L0`;4N
zn`kdz^bpd|&dzR&ny3FVyg7IM=AWOR-*PJ|=@HV;#Kq2oQ3e2GpaxVIOGrvBXJlmh
zj}K!j1AzM1g7k|^NHjAsG5*7caioCbApK(E5(P|5On>oVY_))>p0YZZuyXsi`wzA;
zGc*3hhq2ZIz!+$Owz5K@4kU!pQ$R3Oe>sr)PXPbJ(vq}<>hC1NZj5%o>q~cU0$EO?
zI$HJ&|A1ED#s4s~0MOzJm}7uRl;m0fsDCAt{hy7EjU4@lVQf}bR#No80<{Fiv=r1q
z9mF3$elW1GGvhD7{(!=d@f8bF$D)UU0szz>Nuhpw3bbE-adgK5W)41XqD#;ppT3G{
ddR_(yFaXq#Q#&@V%y|F+002ovPDHLkV1i|uIRO9w
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -181,16 +181,17 @@ browser.jar:
         skin/classic/browser/devtools/commandline-icon.png          (devtools/commandline-icon.png)
         skin/classic/browser/devtools/alerticon-warning.png         (devtools/alerticon-warning.png)
         skin/classic/browser/devtools/ruleview.css                  (devtools/ruleview.css)
         skin/classic/browser/devtools/commandline.css               (devtools/commandline.css)
         skin/classic/browser/devtools/command-paintflashing.png     (devtools/command-paintflashing.png)
         skin/classic/browser/devtools/command-responsivemode.png    (devtools/command-responsivemode.png)
         skin/classic/browser/devtools/command-scratchpad.png        (devtools/command-scratchpad.png)
         skin/classic/browser/devtools/command-tilt.png              (devtools/command-tilt.png)
+        skin/classic/browser/devtools/command-console.png           (devtools/command-console.png)
         skin/classic/browser/devtools/markup-view.css               (../shared/devtools/markup-view.css)
         skin/classic/browser/devtools/editor-error.png               (devtools/editor-error.png)
         skin/classic/browser/devtools/editor-breakpoint.png          (devtools/editor-breakpoint.png)
         skin/classic/browser/devtools/editor-debug-location.png      (devtools/editor-debug-location.png)
 *       skin/classic/browser/devtools/webconsole.css                  (devtools/webconsole.css)
         skin/classic/browser/devtools/webconsole_networkpanel.css     (devtools/webconsole_networkpanel.css)
         skin/classic/browser/devtools/webconsole.png                  (devtools/webconsole.png)
         skin/classic/browser/devtools/breadcrumbs-scrollbutton.png                 (devtools/breadcrumbs-scrollbutton.png)
@@ -482,16 +483,17 @@ browser.jar:
 *       skin/classic/aero/browser/devtools/light-theme.css           (../shared/devtools/light-theme.css)
         skin/classic/aero/browser/devtools/controls.png              (../shared/devtools/controls.png)
 *       skin/classic/aero/browser/devtools/widgets.css               (devtools/widgets.css)
         skin/classic/aero/browser/devtools/commandline-icon.png      (devtools/commandline-icon.png)
         skin/classic/aero/browser/devtools/command-paintflashing.png  (devtools/command-paintflashing.png)
         skin/classic/aero/browser/devtools/command-responsivemode.png (devtools/command-responsivemode.png)
         skin/classic/aero/browser/devtools/command-scratchpad.png    (devtools/command-scratchpad.png)
         skin/classic/aero/browser/devtools/command-tilt.png          (devtools/command-tilt.png)
+        skin/classic/aero/browser/devtools/command-console.png       (devtools/command-console.png)
         skin/classic/aero/browser/devtools/alerticon-warning.png     (devtools/alerticon-warning.png)
         skin/classic/aero/browser/devtools/ruleview.css              (devtools/ruleview.css)
         skin/classic/aero/browser/devtools/commandline.css           (devtools/commandline.css)
         skin/classic/aero/browser/devtools/markup-view.css           (../shared/devtools/markup-view.css)
         skin/classic/aero/browser/devtools/editor-error.png           (devtools/editor-error.png)
         skin/classic/aero/browser/devtools/editor-breakpoint.png      (devtools/editor-breakpoint.png)
         skin/classic/aero/browser/devtools/editor-debug-location.png  (devtools/editor-debug-location.png)
 *       skin/classic/aero/browser/devtools/webconsole.css                  (devtools/webconsole.css)
--- a/build/clang-plugin/Makefile.in
+++ b/build/clang-plugin/Makefile.in
@@ -10,16 +10,17 @@ VPATH		 := @srcdir@
 # Helper for end
 NULL :=
 
 CPPSRCS := \
 	clang-plugin.cpp \
 	$(NULL)
 
 TESTSRCS := \
+	TestCustomHeap.cpp \
 	TestMustOverride.cpp \
 	TestNonHeapClass.cpp \
 	TestStackClass.cpp \
 	$(NULL)
 
 OBJS := $(patsubst %.cpp,%.o,$(CPPSRCS))
 TESTS := $(patsubst %.cpp,test-%,$(TESTSRCS))
 
--- a/build/clang-plugin/clang-plugin.cpp
+++ b/build/clang-plugin/clang-plugin.cpp
@@ -219,35 +219,60 @@ AST_MATCHER(QualType, stackClassAggregat
   return getClassAttrs(Node) == StackClass;
 }
 
 /// This matcher will match any class with the stack class assertion or an
 /// array of such classes.
 AST_MATCHER(QualType, nonheapClassAggregate) {
   return getClassAttrs(Node) == NonHeapClass;
 }
+
+/// This matcher will match any function declaration that is declared as a heap
+/// allocator.
+AST_MATCHER(FunctionDecl, heapAllocator) {
+  return MozChecker::hasCustomAnnotation(&Node, "moz_heap_allocator");
+}
 }
 }
 
 namespace {
 
+bool isPlacementNew(const CXXNewExpr *expr) {
+  // Regular new expressions aren't placement new
+  if (expr->getNumPlacementArgs() == 0)
+    return false;
+  if (MozChecker::hasCustomAnnotation(expr->getOperatorNew(),
+      "moz_heap_allocator"))
+    return false;
+  return true;
+}
+
 DiagnosticsMatcher::DiagnosticsMatcher() {
   // Stack class assertion: non-local variables of a stack class are forbidden
   // (non-localness checked in the callback)
   astMatcher.addMatcher(varDecl(hasType(stackClassAggregate())).bind("node"),
     &stackClassChecker);
   // Stack class assertion: new stack class is forbidden (unless placement new)
   astMatcher.addMatcher(newExpr(hasType(pointerType(
       pointee(stackClassAggregate())
     ))).bind("node"), &stackClassChecker);
   // Non-heap class assertion: new non-heap class is forbidden (unless placement
   // new)
   astMatcher.addMatcher(newExpr(hasType(pointerType(
       pointee(nonheapClassAggregate())
     ))).bind("node"), &nonheapClassChecker);
+
+  // Any heap allocation function that returns a non-heap or a stack class is
+  // definitely doing something wrong
+  astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(),
+      returns(pointerType(pointee(nonheapClassAggregate()))))))).bind("node"),
+    &nonheapClassChecker);
+  astMatcher.addMatcher(callExpr(callee(functionDecl(allOf(heapAllocator(),
+      returns(pointerType(pointee(stackClassAggregate()))))))).bind("node"),
+    &stackClassChecker);
 }
 
 void DiagnosticsMatcher::StackClassChecker::run(
     const MatchFinder::MatchResult &Result) {
   DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
   unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID(
     DiagnosticIDs::Error, "variable of type %0 only valid on the stack");
   if (const VarDecl *d = Result.Nodes.getNodeAs<VarDecl>("node")) {
@@ -255,20 +280,25 @@ void DiagnosticsMatcher::StackClassCheck
     if (d->hasLocalStorage())
       return;
 
     Diag.Report(d->getLocation(), stackID) << d->getType();
     noteInferred(d->getType(), Diag);
   } else if (const CXXNewExpr *expr =
       Result.Nodes.getNodeAs<CXXNewExpr>("node")) {
     // If it's placement new, then this match doesn't count.
-    if (expr->getNumPlacementArgs() > 0)
+    if (isPlacementNew(expr))
       return;
     Diag.Report(expr->getStartLoc(), stackID) << expr->getAllocatedType();
     noteInferred(expr->getAllocatedType(), Diag);
+  } else if (const CallExpr *expr =
+      Result.Nodes.getNodeAs<CallExpr>("node")) {
+    QualType badType = expr->getCallReturnType()->getPointeeType();
+    Diag.Report(expr->getLocStart(), stackID) << badType;
+    noteInferred(badType, Diag);
   }
 }
 
 void DiagnosticsMatcher::StackClassChecker::noteInferred(QualType T,
     DiagnosticsEngine &Diag) {
   unsigned inheritsID = Diag.getDiagnosticIDs()->getCustomDiagID(
     DiagnosticIDs::Note,
     "%0 is a stack class because it inherits from a stack class %1");
@@ -296,22 +326,27 @@ void DiagnosticsMatcher::StackClassCheck
   noteInferred(cast<ValueDecl>(cause)->getType(), Diag);
 }
 
 void DiagnosticsMatcher::NonHeapClassChecker::run(
     const MatchFinder::MatchResult &Result) {
   DiagnosticsEngine &Diag = Result.Context->getDiagnostics();
   unsigned stackID = Diag.getDiagnosticIDs()->getCustomDiagID(
     DiagnosticIDs::Error, "variable of type %0 is not valid on the heap");
-  const CXXNewExpr *expr = Result.Nodes.getNodeAs<CXXNewExpr>("node");
-  // If it's placement new, then this match doesn't count.
-  if (expr->getNumPlacementArgs() > 0)
-    return;
-  Diag.Report(expr->getStartLoc(), stackID) << expr->getAllocatedType();
-  noteInferred(expr->getAllocatedType(), Diag);
+  if (const CXXNewExpr *expr = Result.Nodes.getNodeAs<CXXNewExpr>("node")) {
+    // If it's placement new, then this match doesn't count.
+    if (isPlacementNew(expr))
+      return;
+    Diag.Report(expr->getStartLoc(), stackID) << expr->getAllocatedType();
+    noteInferred(expr->getAllocatedType(), Diag);
+  } else if (const CallExpr *expr = Result.Nodes.getNodeAs<CallExpr>("node")) {
+    QualType badType = expr->getCallReturnType()->getPointeeType();
+    Diag.Report(expr->getLocStart(), stackID) << badType;
+    noteInferred(badType, Diag);
+  }
 }
 
 void DiagnosticsMatcher::NonHeapClassChecker::noteInferred(QualType T,
     DiagnosticsEngine &Diag) {
   unsigned inheritsID = Diag.getDiagnosticIDs()->getCustomDiagID(
     DiagnosticIDs::Note,
     "%0 is a non-heap class because it inherits from a non-heap class %1");
   unsigned memberID = Diag.getDiagnosticIDs()->getCustomDiagID(
new file mode 100644
--- /dev/null
+++ b/build/clang-plugin/tests/TestCustomHeap.cpp
@@ -0,0 +1,28 @@
+#define MOZ_NONHEAP_CLASS __attribute__((annotate("moz_nonheap_class")))
+#define MOZ_HEAP_ALLOCATOR \
+  _Pragma("GCC diagnostic push") \
+  _Pragma("GCC diagnostic ignored \"-Wgcc-compat\"") \
+  __attribute__((annotate("moz_heap_allocator"))) \
+  _Pragma("GCC diagnostic pop")
+
+#include <stdlib.h>
+#include <memory>
+
+struct MOZ_NONHEAP_CLASS X {
+};
+
+void *operator new(size_t x, int qual) MOZ_HEAP_ALLOCATOR {
+  return ::operator new(x);
+}
+
+template <typename T>
+T *customAlloc() MOZ_HEAP_ALLOCATOR {
+  T *arg =  static_cast<T*>(malloc(sizeof(T)));
+  return new (arg) T();
+}
+
+template <typename T>
+void misuseX(T q) {
+  X *foo = customAlloc<X>(); // expected-error {{variable of type 'X' is not valid on the heap}}
+  X *foo2 = new (100) X(); // expected-error {{variable of type 'X' is not valid on the heap}}
+}
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -2160,17 +2160,16 @@ GK_ATOM(columnheader, "columnheader")
 GK_ATOM(containerAtomic, "container-atomic")
 GK_ATOM(containerBusy, "container-busy")
 GK_ATOM(containerLive, "container-live")
 GK_ATOM(containerLiveRole, "container-live-role")
 GK_ATOM(containerRelevant, "container-relevant")
 GK_ATOM(cycles, "cycles")
 GK_ATOM(datatable, "datatable")
 GK_ATOM(directory, "directory")
-GK_ATOM(droppable, "droppable")
 GK_ATOM(eventFromInput, "event-from-input")
 GK_ATOM(grammar, "grammar")
 GK_ATOM(gridcell, "gridcell")
 GK_ATOM(heading, "heading")
 GK_ATOM(InlineBlockFrame, "InlineBlockFrame")
 GK_ATOM(inlinevalue, "inline")
 GK_ATOM(invalid, "invalid")
 GK_ATOM(item, "item")
--- a/content/base/test/test_bug631615.html
+++ b/content/base/test/test_bug631615.html
@@ -27,12 +27,15 @@ function doTest() {
   } catch (e) {
     results = "throws: " + e + "\n";
   }
 
   monitor.appendChild(document.createTextNode(results));
   is(results.slice(0, 6), "throws", "looking for an exception");
 }
 
-SimpleTest.runTestExpectingConsoleMessages(doTest, []);
+SimpleTest.runTestExpectingConsoleMessages(doTest, [{
+  forbid: true,
+  message: /An invalid or illegal string was specified/
+}]);
 </script>
 </body>
 </html>
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -1278,17 +1278,17 @@ nsGlobalWindow::~nsGlobalWindow()
     // If our outer window's inner window is this window, null out the
     // outer window's reference to this window that's being deleted.
     nsGlobalWindow *outer = GetOuterWindowInternal();
     if (outer) {
       outer->MaybeClearInnerWindow(this);
     }
   }
 
-  mDoc = nullptr;
+  ClearDelayedEventsAndDropDocument();
 
   // Outer windows are always supposed to call CleanUp before letting themselves
   // be destroyed. And while CleanUp generally seems to be intended to clean up
   // outers, we've historically called it for both. Changing this would probably
   // involve auditing all of the references that inners and outers can have, and
   // separating the handling into CleanUp() and FreeInnerObjects.
   if (IsInnerWindow()) {
     CleanUp();
@@ -1350,16 +1350,25 @@ nsGlobalWindow::MaybeForgiveSpamCount()
     SetPopupSpamWindow(false);
     --gOpenPopupSpamCount;
     NS_ASSERTION(gOpenPopupSpamCount >= 0,
                  "Unbalanced decrement of gOpenPopupSpamCount");
   }
 }
 
 void
+nsGlobalWindow::ClearDelayedEventsAndDropDocument()
+{
+  if (mDoc && mDoc->EventHandlingSuppressed()) {
+    mDoc->UnsuppressEventHandlingAndFireEvents(false);
+  }
+  mDoc = nullptr;
+}
+
+void
 nsGlobalWindow::CleanUp()
 {
   // Guarantee idempotence.
   if (mCleanedUp)
     return;
   mCleanedUp = true;
 
   mEventTargetObjects.EnumerateEntries(DisconnectEventTargetObjects, nullptr);
@@ -2768,17 +2777,17 @@ nsGlobalWindow::DetachFromDocShell()
     NS_ASSERTION(mDoc, "Must have doc!");
 
     // Remember the document's principal and URI.
     mDocumentPrincipal = mDoc->NodePrincipal();
     mDocumentURI = mDoc->GetDocumentURI();
     mDocBaseURI = mDoc->GetDocBaseURI();
 
     // Release our document reference
-    mDoc = nullptr;
+    ClearDelayedEventsAndDropDocument();
     mFocusedNode = nullptr;
   }
 
   ClearControllers();
 
   mChromeEventHandler = nullptr; // force release now
 
   if (mContext) {
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -973,16 +973,17 @@ protected:
 
   static bool sIdleObserversAPIFuzzTimeDisabled;
 
   friend class HashchangeCallback;
   friend class mozilla::dom::BarProp;
 
   // Object Management
   virtual ~nsGlobalWindow();
+  void ClearDelayedEventsAndDropDocument();
   void CleanUp();
   void ClearControllers();
   nsresult FinalClose();
 
   inline void MaybeClearInnerWindow(nsGlobalWindow* aExpectedInner)
   {
     if(mInnerWindow == aExpectedInner) {
       mInnerWindow = nullptr;
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1306,16 +1306,19 @@ MediaManager::GetUserMedia(JSContext* aC
 
   // No need for locking because we always do this in the main thread.
   listeners->AppendElement(listener);
 
   // Developer preference for turning off permission check.
   if (Preferences::GetBool("media.navigator.permission.disabled", false)) {
     aPrivileged = true;
   }
+  if (!Preferences::GetBool("media.navigator.video.enabled", true)) {
+    c.mVideo = false;
+  }
 
   /**
    * Pass runnables along to GetUserMediaRunnable so it can add the
    * MediaStreamListener to the runnable list.
    */
   if (c.mFake) {
     // Fake stream from default backend.
     gUMRunnable = new GetUserMediaRunnable(c, onSuccess.forget(),
--- a/js/public/Class.h
+++ b/js/public/Class.h
@@ -562,17 +562,17 @@ struct JSClass {
 // member initial value.  The "original ... value" verbiage is there because
 // in ECMA-262, global properties naming class objects are read/write and
 // deleteable, for the most part.
 //
 // Implementing this efficiently requires that global objects have classes
 // with the following flags. Failure to use JSCLASS_GLOBAL_FLAGS was
 // previously allowed, but is now an ES5 violation and thus unsupported.
 //
-#define JSCLASS_GLOBAL_SLOT_COUNT      (3 + JSProto_LIMIT * 3 + 28)
+#define JSCLASS_GLOBAL_SLOT_COUNT      (3 + JSProto_LIMIT * 3 + 27)
 #define JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(n)                                    \
     (JSCLASS_IS_GLOBAL | JSCLASS_HAS_RESERVED_SLOTS(JSCLASS_GLOBAL_SLOT_COUNT + (n)))
 #define JSCLASS_GLOBAL_FLAGS                                                  \
     JSCLASS_GLOBAL_FLAGS_WITH_SLOTS(0)
 #define JSCLASS_HAS_GLOBAL_FLAG_AND_SLOTS(clasp)                              \
   (((clasp)->flags & JSCLASS_IS_GLOBAL)                                       \
    && JSCLASS_RESERVED_SLOTS(clasp) >= JSCLASS_GLOBAL_SLOT_COUNT)
 
--- a/js/public/Utility.h
+++ b/js/public/Utility.h
@@ -242,143 +242,143 @@ static inline void js_free(void* p)
  * adds news with up to 12 parameters. Add more versions of new below if
  * you need more than 12 parameters.
  *
  * Note: Do not add a ; at the end of a use of JS_DECLARE_NEW_METHODS,
  * or the build will break.
  */
 #define JS_DECLARE_NEW_METHODS(NEWNAME, ALLOCATOR, QUALIFIERS)\
     template <class T>\
-    QUALIFIERS T *NEWNAME() {\
+    QUALIFIERS T *NEWNAME() MOZ_HEAP_ALLOCATOR {\
         JS_NEW_BODY(ALLOCATOR, T, ())\
     }\
 \
     template <class T, class P1>\
-    QUALIFIERS T *NEWNAME(P1 &&p1) {\
+    QUALIFIERS T *NEWNAME(P1 &&p1) MOZ_HEAP_ALLOCATOR {\
         JS_NEW_BODY(ALLOCATOR, T,\
                     (mozilla::Forward<P1>(p1)))\
     }\
 \
     template <class T, class P1, class P2>\
-    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2) {\
+    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2) MOZ_HEAP_ALLOCATOR {\
         JS_NEW_BODY(ALLOCATOR, T,\
                     (mozilla::Forward<P1>(p1),\
                      mozilla::Forward<P2>(p2)))\
     }\
 \
     template <class T, class P1, class P2, class P3>\
-    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3) {\
+    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3) MOZ_HEAP_ALLOCATOR {\
         JS_NEW_BODY(ALLOCATOR, T,\
                     (mozilla::Forward<P1>(p1),\
                      mozilla::Forward<P2>(p2),\
                      mozilla::Forward<P3>(p3)))\
     }\
 \
     template <class T, class P1, class P2, class P3, class P4>\
-    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4) {\
+    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4) MOZ_HEAP_ALLOCATOR {\
         JS_NEW_BODY(ALLOCATOR, T,\
                     (mozilla::Forward<P1>(p1),\
                      mozilla::Forward<P2>(p2),\
                      mozilla::Forward<P3>(p3),\
                      mozilla::Forward<P4>(p4)))\
     }\
 \
     template <class T, class P1, class P2, class P3, class P4, class P5>\
-    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5) {\
+    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5) MOZ_HEAP_ALLOCATOR {\
         JS_NEW_BODY(ALLOCATOR, T,\
                     (mozilla::Forward<P1>(p1),\
                      mozilla::Forward<P2>(p2),\
                      mozilla::Forward<P3>(p3),\
                      mozilla::Forward<P4>(p4),\
                      mozilla::Forward<P5>(p5)))\
     }\
 \
     template <class T, class P1, class P2, class P3, class P4, class P5, class P6>\
-    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6) {\
+    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6) MOZ_HEAP_ALLOCATOR {\
         JS_NEW_BODY(ALLOCATOR, T,\
                     (mozilla::Forward<P1>(p1),\
                      mozilla::Forward<P2>(p2),\
                      mozilla::Forward<P3>(p3),\
                      mozilla::Forward<P4>(p4),\
                      mozilla::Forward<P5>(p5),\
                      mozilla::Forward<P6>(p6)))\
     }\
 \
     template <class T, class P1, class P2, class P3, class P4, class P5, class P6, class P7>\
-    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6, P7 &&p7) {\
+    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6, P7 &&p7) MOZ_HEAP_ALLOCATOR {\
         JS_NEW_BODY(ALLOCATOR, T,\
                     (mozilla::Forward<P1>(p1),\
                      mozilla::Forward<P2>(p2),\
                      mozilla::Forward<P3>(p3),\
                      mozilla::Forward<P4>(p4),\
                      mozilla::Forward<P5>(p5),\
                      mozilla::Forward<P6>(p6),\
                      mozilla::Forward<P7>(p7)))\
     }\
 \
     template <class T, class P1, class P2, class P3, class P4, class P5, class P6, class P7, class P8>\
-    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6, P7 &&p7, P8 &&p8) {\
+    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6, P7 &&p7, P8 &&p8) MOZ_HEAP_ALLOCATOR {\
         JS_NEW_BODY(ALLOCATOR, T,\
                     (mozilla::Forward<P1>(p1),\
                      mozilla::Forward<P2>(p2),\
                      mozilla::Forward<P3>(p3),\
                      mozilla::Forward<P4>(p4),\
                      mozilla::Forward<P5>(p5),\
                      mozilla::Forward<P6>(p6),\
                      mozilla::Forward<P7>(p7),\
                      mozilla::Forward<P8>(p8)))\
     }\
 \
     template <class T, class P1, class P2, class P3, class P4, class P5, class P6, class P7, class P8, class P9>\
-    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6, P7 &&p7, P8 &&p8, P9 &&p9) {\
+    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6, P7 &&p7, P8 &&p8, P9 &&p9) MOZ_HEAP_ALLOCATOR {\
         JS_NEW_BODY(ALLOCATOR, T,\
                     (mozilla::Forward<P1>(p1),\
                      mozilla::Forward<P2>(p2),\
                      mozilla::Forward<P3>(p3),\
                      mozilla::Forward<P4>(p4),\
                      mozilla::Forward<P5>(p5),\
                      mozilla::Forward<P6>(p6),\
                      mozilla::Forward<P7>(p7),\
                      mozilla::Forward<P8>(p8),\
                      mozilla::Forward<P9>(p9)))\
     }\
 \
     template <class T, class P1, class P2, class P3, class P4, class P5, class P6, class P7, class P8, class P9, class P10>\
-    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6, P7 &&p7, P8 &&p8, P9 &&p9, P10 &&p10) {\
+    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6, P7 &&p7, P8 &&p8, P9 &&p9, P10 &&p10) MOZ_HEAP_ALLOCATOR {\
         JS_NEW_BODY(ALLOCATOR, T,\
                     (mozilla::Forward<P1>(p1),\
                      mozilla::Forward<P2>(p2),\
                      mozilla::Forward<P3>(p3),\
                      mozilla::Forward<P4>(p4),\
                      mozilla::Forward<P5>(p5),\
                      mozilla::Forward<P6>(p6),\
                      mozilla::Forward<P7>(p7),\
                      mozilla::Forward<P8>(p8),\
                      mozilla::Forward<P9>(p9),\
                      mozilla::Forward<P10>(p10)))\
     }\
 \
     template <class T, class P1, class P2, class P3, class P4, class P5, class P6, class P7, class P8, class P9, class P10, class P11>\
-    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6, P7 &&p7, P8 &&p8, P9 &&p9, P10 &&p10, P11 &&p11) {\
+    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6, P7 &&p7, P8 &&p8, P9 &&p9, P10 &&p10, P11 &&p11) MOZ_HEAP_ALLOCATOR {\
         JS_NEW_BODY(ALLOCATOR, T,\
                     (mozilla::Forward<P1>(p1),\
                      mozilla::Forward<P2>(p2),\
                      mozilla::Forward<P3>(p3),\
                      mozilla::Forward<P4>(p4),\
                      mozilla::Forward<P5>(p5),\
                      mozilla::Forward<P6>(p6),\
                      mozilla::Forward<P7>(p7),\
                      mozilla::Forward<P8>(p8),\
                      mozilla::Forward<P9>(p9),\
                      mozilla::Forward<P10>(p10),\
                      mozilla::Forward<P11>(p11)))\
     }\
 \
     template <class T, class P1, class P2, class P3, class P4, class P5, class P6, class P7, class P8, class P9, class P10, class P11, class P12>\
-    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6, P7 &&p7, P8 &&p8, P9 &&p9, P10 &&p10, P11 &&p11, P12 &&p12) {\
+    QUALIFIERS T *NEWNAME(P1 &&p1, P2 &&p2, P3 &&p3, P4 &&p4, P5 &&p5, P6 &&p6, P7 &&p7, P8 &&p8, P9 &&p9, P10 &&p10, P11 &&p11, P12 &&p12) MOZ_HEAP_ALLOCATOR {\
         JS_NEW_BODY(ALLOCATOR, T,\
                     (mozilla::Forward<P1>(p1),\
                      mozilla::Forward<P2>(p2),\
                      mozilla::Forward<P3>(p3),\
                      mozilla::Forward<P4>(p4),\
                      mozilla::Forward<P5>(p5),\
                      mozilla::Forward<P6>(p6),\
                      mozilla::Forward<P7>(p7),\
--- a/js/src/builtin/TypeRepresentation.cpp
+++ b/js/src/builtin/TypeRepresentation.cpp
@@ -7,16 +7,17 @@
 #include "builtin/TypeRepresentation.h"
 
 #include "mozilla/HashFunctions.h"
 
 #include "jscntxt.h"
 #include "jsnum.h"
 #include "jsutil.h"
 
+#include "builtin/TypedObject.h"
 #include "js/HashTable.h"
 #include "vm/Runtime.h"
 #include "vm/StringBuffer.h"
 
 #include "jsgcinlines.h"
 #include "jsobjinlines.h"
 
 using namespace js;
@@ -63,18 +64,23 @@ TypeRepresentationHasher::match(TypeRepr
         return matchReferences(key1->asReference(), key2->asReference());
 
       case TypeRepresentation::X4:
         return matchX4s(key1->asX4(), key2->asX4());
 
       case TypeRepresentation::Struct:
         return matchStructs(key1->asStruct(), key2->asStruct());
 
-      case TypeRepresentation::Array:
-        return matchArrays(key1->asArray(), key2->asArray());
+      case TypeRepresentation::SizedArray:
+        return matchSizedArrays(key1->asSizedArray(),
+                                key2->asSizedArray());
+
+      case TypeRepresentation::UnsizedArray:
+        return matchUnsizedArrays(key1->asUnsizedArray(),
+                                  key2->asUnsizedArray());
     }
 
     MOZ_ASSUME_UNREACHABLE("Invalid kind");
 }
 
 bool
 TypeRepresentationHasher::matchScalars(ScalarTypeRepresentation *key1,
                                        ScalarTypeRepresentation *key2)
@@ -99,52 +105,63 @@ TypeRepresentationHasher::matchX4s(X4Typ
 bool
 TypeRepresentationHasher::matchStructs(StructTypeRepresentation *key1,
                                        StructTypeRepresentation *key2)
 {
     if (key1->fieldCount() != key2->fieldCount())
         return false;
 
     for (size_t i = 0; i < key1->fieldCount(); i++) {
-        if (key1->field(i).id != key2->field(i).id)
+        if (key1->field(i).propertyName != key2->field(i).propertyName)
             return false;
 
         if (key1->field(i).typeRepr != key2->field(i).typeRepr)
             return false;
     }
 
     return true;
 }
 
 bool
-TypeRepresentationHasher::matchArrays(ArrayTypeRepresentation *key1,
-                                      ArrayTypeRepresentation *key2)
+TypeRepresentationHasher::matchSizedArrays(SizedArrayTypeRepresentation *key1,
+                                           SizedArrayTypeRepresentation *key2)
 {
     // We assume that these pointers have been canonicalized:
     return key1->element() == key2->element() &&
            key1->length() == key2->length();
 }
 
+bool
+TypeRepresentationHasher::matchUnsizedArrays(UnsizedArrayTypeRepresentation *key1,
+                                             UnsizedArrayTypeRepresentation *key2)
+{
+    // We assume that these pointers have been canonicalized:
+    return key1->element() == key2->element();
+}
+
 HashNumber
 TypeRepresentationHasher::hash(TypeRepresentation *key) {
     switch (key->kind()) {
       case TypeRepresentation::Scalar:
         return hashScalar(key->asScalar());
 
       case TypeRepresentation::Reference:
         return hashReference(key->asReference());
 
       case TypeRepresentation::X4:
         return hashX4(key->asX4());
 
       case TypeRepresentation::Struct:
         return hashStruct(key->asStruct());
 
-      case TypeRepresentation::Array:
-        return hashArray(key->asArray());
+      case TypeRepresentation::UnsizedArray:
+        return hashUnsizedArray(key->asUnsizedArray());
+
+      case TypeRepresentation::SizedArray:
+        return hashSizedArray(key->asSizedArray());
     }
 
     MOZ_ASSUME_UNREACHABLE("Invalid kind");
 }
 
 HashNumber
 TypeRepresentationHasher::hashScalar(ScalarTypeRepresentation *key)
 {
@@ -163,146 +180,164 @@ TypeRepresentationHasher::hashX4(X4TypeR
     return HashGeneric(key->kind(), key->type());
 }
 
 HashNumber
 TypeRepresentationHasher::hashStruct(StructTypeRepresentation *key)
 {
     HashNumber hash = HashGeneric(key->kind());
     for (HashNumber i = 0; i < key->fieldCount(); i++) {
-        hash = AddToHash(hash, JSID_BITS(key->field(i).id.get()));
+        hash = AddToHash(hash, key->field(i).propertyName.get());
         hash = AddToHash(hash, key->field(i).typeRepr);
     }
     return hash;
 }
 
 HashNumber
-TypeRepresentationHasher::hashArray(ArrayTypeRepresentation *key)
+TypeRepresentationHasher::hashSizedArray(SizedArrayTypeRepresentation *key)
 {
     return HashGeneric(key->kind(), key->element(), key->length());
 }
 
+HashNumber
+TypeRepresentationHasher::hashUnsizedArray(UnsizedArrayTypeRepresentation *key)
+{
+    return HashGeneric(key->kind(), key->element());
+}
+
 ///////////////////////////////////////////////////////////////////////////
 // Constructors
 
-TypeRepresentation::TypeRepresentation(Kind kind, size_t size,
-                                       size_t align, bool opaque)
-  : size_(size),
-    alignment_(align),
-    kind_(kind),
+TypeRepresentation::TypeRepresentation(Kind kind, bool opaque)
+  : kind_(kind),
     opaque_(opaque)
 {}
 
+SizedTypeRepresentation::SizedTypeRepresentation(Kind kind, bool opaque,
+                                                 size_t size, size_t align)
+  : TypeRepresentation(kind, opaque),
+    size_(size),
+    alignment_(align)
+{}
+
 static size_t ScalarSizes[] = {
 #define SCALAR_SIZE(_kind, _type, _name)                        \
     sizeof(_type),
     JS_FOR_EACH_SCALAR_TYPE_REPR(SCALAR_SIZE) 0
 #undef SCALAR_SIZE
 };
 
 ScalarTypeRepresentation::ScalarTypeRepresentation(Type type)
-  : TypeRepresentation(Scalar, ScalarSizes[type], ScalarSizes[type], false),
+  : SizedTypeRepresentation(Scalar, false, ScalarSizes[type], ScalarSizes[type]),
     type_(type)
 {
 }
 
 static size_t X4Sizes[] = {
 #define X4_SIZE(_kind, _type, _name)                        \
     sizeof(_type) * 4,
     JS_FOR_EACH_X4_TYPE_REPR(X4_SIZE) 0
 #undef X4_SIZE
 };
 
 X4TypeRepresentation::X4TypeRepresentation(Type type)
-  : TypeRepresentation(X4, X4Sizes[type], X4Sizes[type], false),
+  : SizedTypeRepresentation(X4, false, X4Sizes[type], X4Sizes[type]),
     type_(type)
 {
 }
 
 ReferenceTypeRepresentation::ReferenceTypeRepresentation(Type type)
-  : TypeRepresentation(Reference, 0, 1, true),
+  : SizedTypeRepresentation(Reference, true, 0, 1),
     type_(type)
 {
     switch (type) {
       case TYPE_ANY:
         size_ = sizeof(js::HeapValue);
         alignment_ = MOZ_ALIGNOF(js::HeapValue);
         break;
 
       case TYPE_OBJECT:
       case TYPE_STRING:
         size_ = sizeof(js::HeapPtrObject);
         alignment_ = MOZ_ALIGNOF(js::HeapPtrObject);
         break;
     }
 }
 
-ArrayTypeRepresentation::ArrayTypeRepresentation(TypeRepresentation *element,
-                                                 size_t length)
-  : TypeRepresentation(Array, element->size() * length,
-                       element->alignment(), element->opaque()),
+SizedArrayTypeRepresentation::SizedArrayTypeRepresentation(SizedTypeRepresentation *element,
+                                                           size_t length)
+  : SizedTypeRepresentation(SizedArray, element->opaque(),
+                            element->size() * length, element->alignment()),
     element_(element),
     length_(length)
 {
 }
 
+UnsizedArrayTypeRepresentation::UnsizedArrayTypeRepresentation(SizedTypeRepresentation *element)
+  : TypeRepresentation(UnsizedArray, element->opaque()),
+    element_(element)
+{
+}
+
 static inline size_t alignTo(size_t address, size_t align) {
     JS_ASSERT(IsPowerOfTwo(align));
     return (address + align - 1) & -align;
 }
 
 StructField::StructField(size_t index,
-                         jsid &id,
-                         TypeRepresentation *typeRepr,
+                         PropertyName *propertyName,
+                         SizedTypeRepresentation *typeRepr,
                          size_t offset)
   : index(index),
-    id(id),
+    propertyName(propertyName),
     typeRepr(typeRepr),
     offset(offset)
 {}
 
 StructTypeRepresentation::StructTypeRepresentation()
-  : TypeRepresentation(Struct, 0, 1, false),
+  : SizedTypeRepresentation(Struct, false, 0, 1),
     fieldCount_(0) // see ::init() below!
 {
     // note: size_, alignment_, and opaque_ are computed in ::init() below
 }
 
 bool
 StructTypeRepresentation::init(JSContext *cx,
-                               AutoIdVector &ids,
+                               AutoPropertyNameVector &names,
                                AutoObjectVector &typeReprOwners)
 {
-    JS_ASSERT(ids.length() == typeReprOwners.length());
-    fieldCount_ = ids.length();
+    JS_ASSERT(names.length() == typeReprOwners.length());
+    fieldCount_ = names.length();
 
     // We compute alignment into the field `align_` directly in the
     // loop below, but not `size_` because we have to very careful
     // about overflow. For now, we always use a uint32_t for
     // consistency across build environments.
     uint32_t totalSize = 0;
 
     // These will be adjusted in the loop below:
     alignment_ = 1;
     opaque_ = false;
 
-    for (size_t i = 0; i < ids.length(); i++) {
-        TypeRepresentation *fieldTypeRepr = fromOwnerObject(*typeReprOwners[i]);
+    for (size_t i = 0; i < names.length(); i++) {
+        SizedTypeRepresentation *fieldTypeRepr =
+            fromOwnerObject(*typeReprOwners[i])->asSized();
 
         if (fieldTypeRepr->opaque())
             opaque_ = true;
 
         uint32_t alignedSize = alignTo(totalSize, fieldTypeRepr->alignment());
         if (alignedSize < totalSize) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                                  JSMSG_TYPEDOBJECT_TOO_BIG);
             return false;
         }
 
-        new(fields() + i) StructField(i, ids[i], fieldTypeRepr, alignedSize);
+        new(fields() + i) StructField(i, names[i],
+                                      fieldTypeRepr, alignedSize);
         alignment_ = js::Max(alignment_, fieldTypeRepr->alignment());
 
         uint32_t incrementedSize = alignedSize + fieldTypeRepr->size();
         if (incrementedSize < alignedSize) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                                  JSMSG_TYPEDOBJECT_TOO_BIG);
             return false;
         }
@@ -324,19 +359,35 @@ StructTypeRepresentation::init(JSContext
 ///////////////////////////////////////////////////////////////////////////
 // Interning
 
 JSObject *
 TypeRepresentation::addToTableOrFree(JSContext *cx,
                                      TypeRepresentationHash::AddPtr &p)
 {
     JS_ASSERT(!ownerObject_);
-
+    Rooted<GlobalObject*> global(cx, cx->global());
     JSCompartment *comp = cx->compartment();
 
+    // First, try to create the typed object to associate with this
+    // type representation. Since nothing is in the table yet, if this
+    // fails we can just return and pretend this whole endeavor was
+    // just a bad dream.
+    RootedObject proto(cx);
+    const Class *clasp;
+    if (!global->getTypedObjectModule().getSuitableClaspAndProto(cx, kind(),
+                                                                 &clasp, &proto))
+    {
+        return nullptr;
+    }
+    RootedTypeObject typeObject(cx, comp->types.newTypeObject(cx, clasp, proto));
+    if (!typeObject)
+        return nullptr;
+
+    // Next, attempt to add the type representation to the table.
     if (!comp->typeReprs.add(p, this)) {
         js_ReportOutOfMemory(cx);
         js_free(this); // do not finalize, not present in the table
         return nullptr;
     }
 
     // Now that the object is in the table, try to make the owner
     // object. If this succeeds, then the owner will remove from the
@@ -351,23 +402,31 @@ TypeRepresentation::addToTableOrFree(JSC
         js_free(this);
         return nullptr;
     }
 
     ownerObject->setPrivate(this);
 
     // Assign the various reserved slots:
     ownerObject->initReservedSlot(JS_TYPEREPR_SLOT_KIND, Int32Value(kind()));
-    ownerObject->initReservedSlot(JS_TYPEREPR_SLOT_SIZE, Int32Value(size()));
-    ownerObject->initReservedSlot(JS_TYPEREPR_SLOT_ALIGNMENT, Int32Value(alignment()));
+
+    if (isSized()) {
+        ownerObject->initReservedSlot(JS_TYPEREPR_SLOT_SIZE,
+                                      Int32Value(asSized()->size()));
+        ownerObject->initReservedSlot(JS_TYPEREPR_SLOT_ALIGNMENT,
+                                      Int32Value(asSized()->alignment()));
+    }
 
     switch (kind()) {
-      case Array:
+      case UnsizedArray:
+        break;
+
+      case SizedArray:
         ownerObject->initReservedSlot(JS_TYPEREPR_SLOT_LENGTH,
-                                      Int32Value(asArray()->length()));
+                                      Int32Value(asSizedArray()->length()));
         break;
 
       case Scalar:
         ownerObject->initReservedSlot(JS_TYPEREPR_SLOT_TYPE,
                                       Int32Value(asScalar()->type()));
         break;
 
       case Reference:
@@ -380,16 +439,17 @@ TypeRepresentation::addToTableOrFree(JSC
                                       Int32Value(asX4()->type()));
         break;
 
       case Struct:
         break;
     }
 
     ownerObject_.init(ownerObject);
+    typeObject_.init(typeObject);
     return &*ownerObject;
 }
 
 namespace js {
 class TypeRepresentationHelper {
   public:
     template<typename T>
     static JSObject *CreateSimple(JSContext *cx, typename T::Type type) {
@@ -447,63 +507,87 @@ ReferenceTypeRepresentation::Create(JSCo
         return nullptr;
     new(ptr) ReferenceTypeRepresentation(type);
 
     return ptr->addToTableOrFree(cx, p);
 }
 
 /*static*/
 JSObject *
-ArrayTypeRepresentation::Create(JSContext *cx,
-                                TypeRepresentation *element,
-                                size_t length)
+SizedArrayTypeRepresentation::Create(JSContext *cx,
+                                     SizedTypeRepresentation *element,
+                                     size_t length)
 {
     JSCompartment *comp = cx->compartment();
 
     // Overly conservative, since we are using `size_t` to represent
     // size, but `SafeMul` operators on `int32_t` types. Still, it
     // should be good enough for now.
     int32_t temp;
     if (!SafeMul(element->size(), length, &temp)) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                              JSMSG_TYPEDOBJECT_TOO_BIG);
         return nullptr;
     }
 
-    ArrayTypeRepresentation sample(element, length);
+    SizedArrayTypeRepresentation sample(element, length);
     TypeRepresentationHash::AddPtr p = comp->typeReprs.lookupForAdd(&sample);
     if (p)
         return (*p)->ownerObject();
 
     // Note: cannot use cx->new_ because constructor is private.
-    ArrayTypeRepresentation *ptr =
-        (ArrayTypeRepresentation *) cx->malloc_(
-            sizeof(ArrayTypeRepresentation));
+    SizedArrayTypeRepresentation *ptr =
+        (SizedArrayTypeRepresentation *) cx->malloc_(
+            sizeof(SizedArrayTypeRepresentation));
     if (!ptr)
         return nullptr;
-    new(ptr) ArrayTypeRepresentation(element, length);
+    new(ptr) SizedArrayTypeRepresentation(element, length);
+
+    return ptr->addToTableOrFree(cx, p);
+}
+
+
+/*static*/
+JSObject *
+UnsizedArrayTypeRepresentation::Create(JSContext *cx,
+                                       SizedTypeRepresentation *element)
+{
+    JSCompartment *comp = cx->compartment();
+
+    UnsizedArrayTypeRepresentation sample(element);
+    TypeRepresentationHash::AddPtr p = comp->typeReprs.lookupForAdd(&sample);
+    if (p)
+        return (*p)->ownerObject();
+
+    // Note: cannot use cx->new_ because constructor is private.
+    UnsizedArrayTypeRepresentation *ptr =
+        (UnsizedArrayTypeRepresentation *) cx->malloc_(
+            sizeof(UnsizedArrayTypeRepresentation));
+    if (!ptr)
+        return nullptr;
+    new(ptr) UnsizedArrayTypeRepresentation(element);
 
     return ptr->addToTableOrFree(cx, p);
 }
 
 /*static*/
 JSObject *
 StructTypeRepresentation::Create(JSContext *cx,
-                                 AutoIdVector &ids,
+                                 AutoPropertyNameVector &names,
                                  AutoObjectVector &typeReprOwners)
 {
-    size_t count = ids.length();
+    size_t count = names.length();
     JSCompartment *comp = cx->compartment();
 
     // Note: cannot use cx->new_ because constructor is private.
     size_t size = sizeof(StructTypeRepresentation) + count * sizeof(StructField);
     StructTypeRepresentation *ptr =
         (StructTypeRepresentation *) cx->malloc_(size);
     new(ptr) StructTypeRepresentation();
-    if (!ptr->init(cx, ids, typeReprOwners))
+    if (!ptr->init(cx, names, typeReprOwners))
         return nullptr;
 
     TypeRepresentationHash::AddPtr p = comp->typeReprs.lookupForAdd(ptr);
     if (p) {
         js_free(ptr); // do not finalize, not present in the table
         return (*p)->ownerObject();
     }
 
@@ -516,16 +600,17 @@ StructTypeRepresentation::Create(JSConte
 void
 TypeRepresentation::mark(JSTracer *trace)
 {
     // Push our owner object onto the mark stack. When our owner
     // object's trace callback is called, we will trace its
     // contents. This is the typical scheme for marking objects.  See
     // gc/Marking.cpp for more details.
     gc::MarkObject(trace, &ownerObject_, "typeRepresentation_ownerObject");
+    gc::MarkTypeObject(trace, &typeObject_, "typeRepresentation_typeObject");
 }
 
 /*static*/ void
 TypeRepresentation::obj_trace(JSTracer *trace, JSObject *object)
 {
     fromOwnerObject(*object)->traceFields(trace);
 }
 
@@ -539,33 +624,44 @@ TypeRepresentation::traceFields(JSTracer
       case Reference:
       case X4:
         break;
 
       case Struct:
         asStruct()->traceStructFields(trace);
         break;
 
-      case Array:
-        asArray()->traceArrayFields(trace);
+      case SizedArray:
+        asSizedArray()->traceSizedArrayFields(trace);
+        break;
+
+      case UnsizedArray:
+        asUnsizedArray()->traceUnsizedArrayFields(trace);
         break;
     }
 }
 
 void
 StructTypeRepresentation::traceStructFields(JSTracer *trace)
 {
     for (size_t i = 0; i < fieldCount(); i++) {
-        gc::MarkId(trace, &fields()[i].id, "typerepr_field_id");
+        gc::MarkString(trace, &fields()[i].propertyName, "typerepr_field_propertyName");
         fields()[i].typeRepr->mark(trace);
     }
 }
 
 void
-ArrayTypeRepresentation::traceArrayFields(JSTracer *trace)
+SizedArrayTypeRepresentation::traceSizedArrayFields(JSTracer *trace)
+{
+    this->mark(trace);
+    element_->mark(trace);
+}
+
+void
+UnsizedArrayTypeRepresentation::traceUnsizedArrayFields(JSTracer *trace)
 {
     this->mark(trace);
     element_->mark(trace);
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // Finalization
 
@@ -589,18 +685,21 @@ TypeRepresentation::appendString(JSConte
         return asScalar()->appendStringScalar(cx, contents);
 
       case Reference:
         return asReference()->appendStringReference(cx, contents);
 
       case X4:
         return asX4()->appendStringX4(cx, contents);
 
-      case Array:
-        return asArray()->appendStringArray(cx, contents);
+      case SizedArray:
+        return asSizedArray()->appendStringSizedArray(cx, contents);
+
+      case UnsizedArray:
+        return asUnsizedArray()->appendStringUnsizedArray(cx, contents);
 
       case Struct:
         return asStruct()->appendStringStruct(cx, contents);
     }
 
     MOZ_ASSUME_UNREACHABLE("Invalid kind");
     return false;
 }
@@ -657,31 +756,52 @@ X4TypeRepresentation::appendStringX4(JSC
         return contents.append("float32x4");
       case TYPE_INT32:
         return contents.append("int32x4");
     }
     MOZ_ASSUME_UNREACHABLE("Invalid type");
 }
 
 bool
-ArrayTypeRepresentation::appendStringArray(JSContext *cx, StringBuffer &contents)
+SizedArrayTypeRepresentation::appendStringSizedArray(JSContext *cx, StringBuffer &contents)
 {
-    if (!contents.append("ArrayType("))
+    SizedTypeRepresentation *elementType = element();
+    while (elementType->isSizedArray())
+        elementType = elementType->asSizedArray()->element();
+    if (!elementType->appendString(cx, contents))
         return false;
 
+    contents.append(".array(");
+    SizedArrayTypeRepresentation *arrayType = this;
+    while (arrayType != NULL) {
+        if (!NumberValueToStringBuffer(cx, NumberValue(length()), contents))
+            return false;
+
+        if (arrayType->element()->isSizedArray()) {
+            if (!contents.append(","))
+                return false;
+            arrayType = arrayType->element()->asSizedArray();
+        } else {
+            break;
+        }
+    }
+
+    if (!contents.append(")"))
+        return false;
+
+    return true;
+}
+
+bool
+UnsizedArrayTypeRepresentation::appendStringUnsizedArray(JSContext *cx, StringBuffer &contents)
+{
     if (!element()->appendString(cx, contents))
         return false;
 
-    if (!contents.append(", "))
-        return false;
-
-    if (!NumberValueToStringBuffer(cx, NumberValue(length()), contents))
-        return false;
-
-    if (!contents.append(")"))
+    if (!contents.append(".array()"))
         return false;
 
     return true;
 }
 
 bool
 StructTypeRepresentation::appendStringStruct(JSContext *cx, StringBuffer &contents)
 {
@@ -689,17 +809,17 @@ StructTypeRepresentation::appendStringSt
         return false;
 
     for (size_t i = 0; i < fieldCount(); i++) {
         const StructField &fld = field(i);
 
         if (i > 0)
             contents.append(", ");
 
-        RootedString idString(cx, IdToString(cx, fld.id));
+        RootedString idString(cx, fld.propertyName);
         if (!idString)
             return false;
 
         if (!contents.append(idString))
             return false;
 
         if (!contents.append(": "))
             return false;
@@ -714,41 +834,48 @@ StructTypeRepresentation::appendStringSt
     return true;
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // Walking memory
 
 template<typename V>
 static void
-visitReferences(TypeRepresentation *repr, uint8_t *mem, V& visitor)
+visitReferences(SizedTypeRepresentation *repr,
+                uint8_t *mem,
+                V& visitor)
 {
     if (repr->transparent())
         return;
 
     switch (repr->kind()) {
       case TypeRepresentation::Scalar:
       case TypeRepresentation::X4:
         return;
 
       case TypeRepresentation::Reference:
         visitor.visitReference(repr->asReference(), mem);
         return;
 
-      case TypeRepresentation::Array:
+      case TypeRepresentation::SizedArray:
       {
-        ArrayTypeRepresentation *arrayRepr = repr->asArray();
-        TypeRepresentation *elementRepr = arrayRepr->element();
+        SizedArrayTypeRepresentation *arrayRepr = repr->asSizedArray();
+        SizedTypeRepresentation *elementRepr = arrayRepr->element();
         for (size_t i = 0; i < arrayRepr->length(); i++) {
             visitReferences(elementRepr, mem, visitor);
             mem += elementRepr->size();
         }
         return;
       }
 
+      case TypeRepresentation::UnsizedArray:
+      {
+        MOZ_ASSUME_UNREACHABLE("Only Sized Type representations");
+      }
+
       case TypeRepresentation::Struct:
       {
         StructTypeRepresentation *structRepr = repr->asStruct();
         for (size_t i = 0; i < structRepr->fieldCount(); i++) {
             const StructField &f = structRepr->field(i);
             visitReferences(f.typeRepr, mem + f.offset, visitor);
         }
         return;
@@ -801,22 +928,33 @@ js::MemoryInitVisitor::visitReference(Re
         return;
       }
     }
 
     MOZ_ASSUME_UNREACHABLE("Invalid kind");
 }
 
 void
-TypeRepresentation::initInstance(const JSRuntime *rt, uint8_t *mem)
+SizedTypeRepresentation::initInstance(const JSRuntime *rt,
+                                      uint8_t *mem,
+                                      size_t length)
 {
     MemoryInitVisitor visitor(rt);
+
+    // Initialize the 0th instance
     memset(mem, 0, size());
     if (opaque())
         visitReferences(this, mem, visitor);
+
+    // Stamp out N copies of later instances
+    uint8_t *target = mem;
+    for (size_t i = 1; i < length; i++) {
+        target += size();
+        memcpy(target, mem, size());
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // Tracing instances
 
 namespace js {
 class MemoryTracingVisitor {
     JSTracer *trace_;
@@ -860,30 +998,46 @@ js::MemoryTracingVisitor::visitReference
         return;
       }
     }
 
     MOZ_ASSUME_UNREACHABLE("Invalid kind");
 }
 
 void
-TypeRepresentation::traceInstance(JSTracer *trace, uint8_t *mem)
+SizedTypeRepresentation::traceInstance(JSTracer *trace,
+                                       uint8_t *mem,
+                                       size_t length)
 {
     MemoryTracingVisitor visitor(trace);
-    visitReferences(this, mem, visitor);
+
+    for (size_t i = 0; i < length; i++) {
+        visitReferences(this, mem, visitor);
+        mem += size();
+    }
 }
 
 ///////////////////////////////////////////////////////////////////////////
 // Misc
 
 const StructField *
 StructTypeRepresentation::fieldNamed(jsid id) const
 {
+    if (!JSID_IS_ATOM(id))
+        return nullptr;
+
+    uint32_t unused;
+    JSAtom *atom = JSID_TO_ATOM(id);
+    if (atom->isIndex(&unused))
+        return nullptr;
+
+    PropertyName *name = atom->asPropertyName();
+
     for (size_t i = 0; i < fieldCount(); i++) {
-        if (field(i).id.get() == id)
+        if (field(i).propertyName.get() == name)
             return &field(i);
     }
     return nullptr;
 }
 
 /*static*/ bool
 TypeRepresentation::isOwnerObject(JSObject &obj)
 {
--- a/js/src/builtin/TypeRepresentation.h
+++ b/js/src/builtin/TypeRepresentation.h
@@ -3,216 +3,247 @@
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef builtin_TypeRepresentation_h
 #define builtin_TypeRepresentation_h
 
 /*
-
-  Type Representations are the way that the compiler stores
-  information about binary data types that the user defines.  They are
-  always interned into a hashset in the compartment (typeReprs),
-  meaning that you can compare two type representations for equality
-  just using `==`.
-
-  # Creation and canonicalization:
-
-  Each kind of `TypeRepresentation` object includes a `New` method
-  that will create a canonical instance of it. So, for example, you
-  can do `ScalarTypeRepresentation::Create(Uint8)` to get the
-  canonical representation of uint8. The object that is returned is
-  designed to be immutable, and the API permits only read access.
-
-  # Memory management:
-
-  Each TypeRepresentations has a representative JSObject, called its
-  owner object. When this object is finalized, the TypeRepresentation*
-  will be freed (and removed from the interning table, see
-  below). Therefore, if you reference a TypeRepresentation*, you must
-  ensure this owner object is traced. In type objects, this is done by
-  invoking TypeRepresentation::mark(); in binary data type descriptors,
-  the owner object for the type is stored in `SLOT_TYPE_REPR`.
-
-  The canonicalization table maintains *weak references* to the
-  TypeRepresentation* pointers. That is, the table is not traced.
-  Instead, whenever an object is created, it is paired with its owner
-  object, and the finalizer of the owner object removes the pointer
-  from the table and then frees the pointer.
-
-  # Opacity
-
-  A type representation is considered "opaque" if it contains
-  references (strings, objects, any). In those cases we have to be
-  more limited with respect to aliasing etc to preserve portability
-  across engines (for example, we don't want to expose sizeof(Any))
-  and memory safety.
-
-  # Future plans:
-
-  The owner object will eventually be exposed to self-hosted script
-  and will offer methods for querying and manipulating the binary data
-  it describes.
-
+ *  Type Representations are canonical versions of user-defined type
+ *  descriptors. Using the typed objects API, users are free to define
+ *  new type descriptors representing C-like type definitions. Each such
+ *  type descriptor has a distinct prototype and identity, even if it
+ *  describes the same logical type as another type
+ *  descriptor. Generally speaking, though, the only thing the compiler
+ *  cares about is the binary data layout implied by a type descriptor.
+ *
+ *  Therefore, we link each such type descriptor to one canonical *type
+ *  representation*. The type representation is itself an object, but
+ *  this object is never exposed to user code (it is used by self-hosted
+ *  code). Type representations are interned into a hashset in the
+ *  compartment (typeReprs), meaning that you can compare two type
+ *  representations for equality just using `==`. If they are equal,
+ *  it means that they represent the same binary layout.
+ *
+ *  # Creation and canonicalization:
+ *
+ *  Each kind of `TypeRepresentation` object includes a `New` method
+ *  that will create a canonical instance of it. So, for example, you
+ *  can do `ScalarTypeRepresentation::Create(Uint8)` to get the
+ *  canonical representation of uint8. The object that is returned is
+ *  designed to be immutable, and the API permits only read access.
+ *
+ *  # Integration with TI:
+ *
+ *  Each TypeRepresentation has an associated Type Object. This Type
+ *  Object is used as the type object for all type descriptors with this
+ *  representation. The type object has an associated addendum linking
+ *  to the type representation, thus allowing the jit to deduce
+ *  information about type descriptors that appear in expressions.
+ *
+ *  # Memory management:
+ *
+ *  Each TypeRepresentations has an associated JSObject, called its
+ *  owned object. When this object is finalized, the TypeRepresentation*
+ *  will be freed (and removed from the interning table, see
+ *  below). Therefore, if you reference a TypeRepresentation*, you must
+ *  ensure this owner object is traced. In type objects, this is done by
+ *  invoking TypeRepresentation::mark(); in binary data type
+ *  descriptors, the owner object for the type is stored in
+ *  `SLOT_TYPE_REPR`.
+ *
+ *  The canonicalization table maintains *weak references* to the
+ *  TypeRepresentation* pointers. That is, the table is not traced.
+ *  Instead, whenever an object is created, it is paired with its owner
+ *  object, and the finalizer of the owner object removes the pointer
+ *  from the table and then frees the pointer.
+ *
+ *  # Opacity
+ *
+ *  A type representation is considered "opaque" if it contains
+ *  references (strings, objects, any). In those cases we have to be
+ *  more limited with respect to aliasing etc to preserve portability
+ *  across engines (for example, we don't want to expose sizeof(Any))
+ *  and memory safety.
  */
 
 #include "jsalloc.h"
 #include "jscntxt.h"
 #include "jspubtd.h"
 
 #include "builtin/TypedObjectConstants.h"
 #include "gc/Barrier.h"
 #include "js/HashTable.h"
 
 namespace js {
 
 class TypeRepresentation;
+class SizedTypeRepresentation;
 class ScalarTypeRepresentation;
 class ReferenceTypeRepresentation;
 class X4TypeRepresentation;
-class ArrayTypeRepresentation;
+class SizedArrayTypeRepresentation;
+class UnsizedArrayTypeRepresentation;
 class StructTypeRepresentation;
 
 struct Class;
 class StringBuffer;
 
 struct TypeRepresentationHasher
 {
     typedef TypeRepresentation *Lookup;
     static HashNumber hash(TypeRepresentation *key);
     static bool match(TypeRepresentation *key1, TypeRepresentation *key2);
 
   private:
     static HashNumber hashScalar(ScalarTypeRepresentation *key);
     static HashNumber hashReference(ReferenceTypeRepresentation *key);
     static HashNumber hashX4(X4TypeRepresentation *key);
     static HashNumber hashStruct(StructTypeRepresentation *key);
-    static HashNumber hashArray(ArrayTypeRepresentation *key);
+    static HashNumber hashUnsizedArray(UnsizedArrayTypeRepresentation *key);
+    static HashNumber hashSizedArray(SizedArrayTypeRepresentation *key);
 
     static bool matchScalars(ScalarTypeRepresentation *key1,
                              ScalarTypeRepresentation *key2);
     static bool matchReferences(ReferenceTypeRepresentation *key1,
                                 ReferenceTypeRepresentation *key2);
     static bool matchX4s(X4TypeRepresentation *key1,
                          X4TypeRepresentation *key2);
     static bool matchStructs(StructTypeRepresentation *key1,
                              StructTypeRepresentation *key2);
-    static bool matchArrays(ArrayTypeRepresentation *key1,
-                            ArrayTypeRepresentation *key2);
+    static bool matchSizedArrays(SizedArrayTypeRepresentation *key1,
+                                 SizedArrayTypeRepresentation *key2);
+    static bool matchUnsizedArrays(UnsizedArrayTypeRepresentation *key1,
+                                   UnsizedArrayTypeRepresentation *key2);
 };
 
 typedef js::HashSet<TypeRepresentation *,
                     TypeRepresentationHasher,
                     RuntimeAllocPolicy> TypeRepresentationHash;
 
 class TypeRepresentationHelper;
 
 class TypeRepresentation {
   public:
     enum Kind {
         Scalar = JS_TYPEREPR_SCALAR_KIND,
         Reference = JS_TYPEREPR_REFERENCE_KIND,
         X4 = JS_TYPEREPR_X4_KIND,
         Struct = JS_TYPEREPR_STRUCT_KIND,
-        Array = JS_TYPEREPR_ARRAY_KIND
+        SizedArray = JS_TYPEREPR_SIZED_ARRAY_KIND,
+        UnsizedArray = JS_TYPEREPR_UNSIZED_ARRAY_KIND,
     };
 
   protected:
-    TypeRepresentation(Kind kind, size_t size, size_t align, bool opaque);
+    TypeRepresentation(Kind kind, bool opaque);
 
     // in order to call addToTableOrFree()
     friend class TypeRepresentationHelper;
 
-    size_t size_;
-    size_t alignment_;
     Kind kind_;
     bool opaque_;
 
     JSObject *addToTableOrFree(JSContext *cx, TypeRepresentationHash::AddPtr &p);
 
   private:
     static const Class class_;
     static void obj_trace(JSTracer *trace, JSObject *object);
     static void obj_finalize(js::FreeOp *fop, JSObject *object);
 
     HeapPtrObject ownerObject_;
+    HeapPtrTypeObject typeObject_;
     void traceFields(JSTracer *tracer);
 
   public:
-    size_t size() const { return size_; }
-    size_t alignment() const { return alignment_; }
     Kind kind() const { return kind_; }
     bool opaque() const { return opaque_; }
     bool transparent() const { return !opaque_; }
     JSObject *ownerObject() const { return ownerObject_.get(); }
+    types::TypeObject *typeObject() const { return typeObject_.get(); }
 
     // Appends a stringified form of this type representation onto
     // buffer, for use in error messages and the like.
     bool appendString(JSContext *cx, StringBuffer &buffer);
 
-    // Initializes memory that contains an instance of this type
-    // with appropriate default values (typically 0).
-    void initInstance(const JSRuntime *rt, uint8_t *mem);
-
-    // Traces memory that contains an instance of this type.
-    void traceInstance(JSTracer *trace, uint8_t *mem);
-
     static bool isOwnerObject(JSObject &obj);
     static TypeRepresentation *fromOwnerObject(JSObject &obj);
 
+    static bool isSized(Kind kind) {
+        return kind > JS_TYPEREPR_MAX_UNSIZED_KIND;
+    }
+
+    bool isSized() const {
+        return isSized(kind());
+    }
+
+    inline SizedTypeRepresentation *asSized();
+
     bool isScalar() const {
         return kind() == Scalar;
     }
 
-    ScalarTypeRepresentation *asScalar() {
-        JS_ASSERT(isScalar());
-        return (ScalarTypeRepresentation*) this;
-    }
+    inline ScalarTypeRepresentation *asScalar();
 
     bool isReference() const {
         return kind() == Reference;
     }
 
-    ReferenceTypeRepresentation *asReference() {
-        JS_ASSERT(isReference());
-        return (ReferenceTypeRepresentation*) this;
-    }
+    inline ReferenceTypeRepresentation *asReference();
 
     bool isX4() const {
         return kind() == X4;
     }
 
-    X4TypeRepresentation *asX4() {
-        JS_ASSERT(isX4());
-        return (X4TypeRepresentation*) this;
+    inline X4TypeRepresentation *asX4();
+
+    bool isSizedArray() const {
+        return kind() == SizedArray;
     }
 
-    bool isArray() const {
-        return kind() == Array;
+    inline SizedArrayTypeRepresentation *asSizedArray();
+
+    bool isUnsizedArray() const {
+        return kind() == UnsizedArray;
     }
 
-    ArrayTypeRepresentation *asArray() {
-        JS_ASSERT(isArray());
-        return (ArrayTypeRepresentation*) this;
+    inline UnsizedArrayTypeRepresentation *asUnsizedArray();
+
+    bool isAnyArray() const {
+        return isSizedArray() || isUnsizedArray();
     }
 
     bool isStruct() const {
         return kind() == Struct;
     }
 
-    StructTypeRepresentation *asStruct() {
-        JS_ASSERT(isStruct());
-        return (StructTypeRepresentation*) this;
-    }
+    inline StructTypeRepresentation *asStruct();
 
     void mark(JSTracer *tracer);
 };
 
-class ScalarTypeRepresentation : public TypeRepresentation {
+class SizedTypeRepresentation : public TypeRepresentation {
+  protected:
+    SizedTypeRepresentation(Kind kind, bool opaque, size_t size, size_t align);
+
+    size_t size_;
+    size_t alignment_;
+
+  public:
+    size_t size() const { return size_; }
+    size_t alignment() const { return alignment_; }
+
+    // Initializes memory that contains `count` instances of this type
+    void initInstance(const JSRuntime *rt, uint8_t *mem, size_t count);
+
+    // Traces memory that contains `count` instances of this type.
+    void traceInstance(JSTracer *trace, uint8_t *mem, size_t count);
+};
+
+class ScalarTypeRepresentation : public SizedTypeRepresentation {
   public:
     // Must match order of JS_FOR_EACH_SCALAR_TYPE_REPR below
     enum Type {
         TYPE_INT8 = JS_SCALARTYPEREPR_INT8,
         TYPE_UINT8 = JS_SCALARTYPEREPR_UINT8,
         TYPE_INT16 = JS_SCALARTYPEREPR_INT16,
         TYPE_UINT16 = JS_SCALARTYPEREPR_UINT16,
         TYPE_INT32 = JS_SCALARTYPEREPR_INT32,
@@ -268,17 +299,17 @@ class ScalarTypeRepresentation : public 
     macro_(ScalarTypeRepresentation::TYPE_FLOAT32, float,    float32)         \
     macro_(ScalarTypeRepresentation::TYPE_FLOAT64, double,   float64)
 
 // Must be in same order as the enum ScalarTypeRepresentation::Type:
 #define JS_FOR_EACH_SCALAR_TYPE_REPR(macro_)                                    \
     JS_FOR_EACH_UNIQUE_SCALAR_TYPE_REPR_CTYPE(macro_)                           \
     macro_(ScalarTypeRepresentation::TYPE_UINT8_CLAMPED, uint8_t, uint8Clamped)
 
-class ReferenceTypeRepresentation : public TypeRepresentation {
+class ReferenceTypeRepresentation : public SizedTypeRepresentation {
   public:
     // Must match order of JS_FOR_EACH_REFERENCE_TYPE_REPR below
     enum Type {
         TYPE_ANY = JS_REFERENCETYPEREPR_ANY,
         TYPE_OBJECT = JS_REFERENCETYPEREPR_OBJECT,
         TYPE_STRING = JS_REFERENCETYPEREPR_STRING,
     };
     static const int32_t TYPE_MAX = TYPE_STRING + 1;
@@ -307,17 +338,17 @@ class ReferenceTypeRepresentation : publ
     static JSObject *Create(JSContext *cx, Type type);
 };
 
 #define JS_FOR_EACH_REFERENCE_TYPE_REPR(macro_)                             \
     macro_(ReferenceTypeRepresentation::TYPE_ANY,    HeapValue, Any)        \
     macro_(ReferenceTypeRepresentation::TYPE_OBJECT, HeapPtrObject, Object) \
     macro_(ReferenceTypeRepresentation::TYPE_STRING, HeapPtrString, string)
 
-class X4TypeRepresentation : public TypeRepresentation {
+class X4TypeRepresentation : public SizedTypeRepresentation {
   public:
     enum Type {
         TYPE_INT32 = JS_X4TYPEREPR_INT32,
         TYPE_FLOAT32 = JS_X4TYPEREPR_FLOAT32,
     };
 
   private:
     // so TypeRepresentation can call appendStringScalar() etc
@@ -341,60 +372,84 @@ class X4TypeRepresentation : public Type
     static JSObject *Create(JSContext *cx, Type type);
 };
 
 // Must be in same order as the enum ScalarTypeRepresentation::Type:
 #define JS_FOR_EACH_X4_TYPE_REPR(macro_)                                      \
     macro_(X4TypeRepresentation::TYPE_INT32, int32_t, int32)                  \
     macro_(X4TypeRepresentation::TYPE_FLOAT32, float, float32)
 
-class ArrayTypeRepresentation : public TypeRepresentation {
+class UnsizedArrayTypeRepresentation : public TypeRepresentation {
   private:
     // so TypeRepresentation can call appendStringArray() etc
     friend class TypeRepresentation;
 
-    TypeRepresentation *element_;
-    size_t length_;
+    SizedTypeRepresentation *element_;
 
-    ArrayTypeRepresentation(TypeRepresentation *element,
-                            size_t length);
+    UnsizedArrayTypeRepresentation(SizedTypeRepresentation *element);
 
     // See TypeRepresentation::traceFields()
-    void traceArrayFields(JSTracer *trace);
+    void traceUnsizedArrayFields(JSTracer *trace);
 
     // See TypeRepresentation::appendString()
-    bool appendStringArray(JSContext *cx, StringBuffer &buffer);
+    bool appendStringUnsizedArray(JSContext *cx, StringBuffer &buffer);
 
   public:
-    TypeRepresentation *element() {
+    SizedTypeRepresentation *element() {
+        return element_;
+    }
+
+    static JSObject *Create(JSContext *cx,
+                            SizedTypeRepresentation *elementTypeRepr);
+};
+
+class SizedArrayTypeRepresentation : public SizedTypeRepresentation {
+  private:
+    // so TypeRepresentation can call appendStringSizedArray() etc
+    friend class TypeRepresentation;
+
+    SizedTypeRepresentation *element_;
+    size_t length_;
+
+    SizedArrayTypeRepresentation(SizedTypeRepresentation *element,
+                                 size_t length);
+
+    // See TypeRepresentation::traceFields()
+    void traceSizedArrayFields(JSTracer *trace);
+
+    // See TypeRepresentation::appendString()
+    bool appendStringSizedArray(JSContext *cx, StringBuffer &buffer);
+
+  public:
+    SizedTypeRepresentation *element() {
         return element_;
     }
 
     size_t length() {
         return length_;
     }
 
     static JSObject *Create(JSContext *cx,
-                            TypeRepresentation *elementTypeRepr,
+                            SizedTypeRepresentation *elementTypeRepr,
                             size_t length);
 };
 
 struct StructField {
     size_t index;
-    HeapId id;
-    TypeRepresentation *typeRepr;
+    HeapPtrPropertyName propertyName;
+    SizedTypeRepresentation *typeRepr;
     size_t offset;
 
     explicit StructField(size_t index,
-                         jsid &id,
-                         TypeRepresentation *typeRepr,
+                         PropertyName *propertyName,
+                         SizedTypeRepresentation *typeRepr,
                          size_t offset);
 };
 
-class StructTypeRepresentation : public TypeRepresentation {
+class StructTypeRepresentation : public SizedTypeRepresentation {
   private:
     // so TypeRepresentation can call appendStringStruct() etc
     friend class TypeRepresentation;
 
     size_t fieldCount_;
 
     // StructTypeRepresentations are allocated with extra space to
     // store the contents of the fields array.
@@ -402,17 +457,17 @@ class StructTypeRepresentation : public 
         return (StructField*) (this+1);
     }
     const StructField* fields() const {
         return (StructField*) (this+1);
     }
 
     StructTypeRepresentation();
     bool init(JSContext *cx,
-              AutoIdVector &ids,
+              AutoPropertyNameVector &names,
               AutoObjectVector &typeReprOwners);
 
     // See TypeRepresentation::traceFields()
     void traceStructFields(JSTracer *trace);
 
     // See TypeRepresentation::appendString()
     bool appendStringStruct(JSContext *cx, StringBuffer &buffer);
 
@@ -423,16 +478,66 @@ class StructTypeRepresentation : public 
 
     const StructField &field(size_t i) const {
         JS_ASSERT(i < fieldCount());
         return fields()[i];
     }
 
     const StructField *fieldNamed(jsid id) const;
 
+    // Creates a struct type from two parallel arrays:
+    // - `names`: the names of the struct type fields.
+    // - `typeReprOwners`: the types of each field, which are assumed
+    //   to be the owner objects for sized type representations.
     static JSObject *Create(JSContext *cx,
-                            AutoIdVector &ids,
+                            AutoPropertyNameVector &names,
                             AutoObjectVector &typeReprOwners);
 };
 
+// Definitions of the casting methods. These are pulled out of the
+// main class definition because both the super- and subtypes must be
+// defined for C++ to permit the static_cast.
+
+SizedTypeRepresentation *
+TypeRepresentation::asSized() {
+    JS_ASSERT(isSized());
+    return static_cast<SizedTypeRepresentation*>(this);
+}
+
+ScalarTypeRepresentation *
+TypeRepresentation::asScalar() {
+    JS_ASSERT(isScalar());
+    return static_cast<ScalarTypeRepresentation*>(this);
+}
+
+ReferenceTypeRepresentation *
+TypeRepresentation::asReference() {
+    JS_ASSERT(isReference());
+    return static_cast<ReferenceTypeRepresentation*>(this);
+}
+
+X4TypeRepresentation *
+TypeRepresentation::asX4() {
+    JS_ASSERT(isX4());
+    return static_cast<X4TypeRepresentation*>(this);
+}
+
+SizedArrayTypeRepresentation *
+TypeRepresentation::asSizedArray() {
+    JS_ASSERT(isSizedArray());
+    return static_cast<SizedArrayTypeRepresentation*>(this);
+}
+
+UnsizedArrayTypeRepresentation *
+TypeRepresentation::asUnsizedArray() {
+    JS_ASSERT(isUnsizedArray());
+    return static_cast<UnsizedArrayTypeRepresentation*>(this);
+}
+
+StructTypeRepresentation *
+TypeRepresentation::asStruct() {
+    JS_ASSERT(isStruct());
+    return static_cast<StructTypeRepresentation*>(this);
+}
+
 } // namespace js
 
 #endif
--- a/js/src/builtin/TypedObject.cpp
+++ b/js/src/builtin/TypedObject.cpp
@@ -24,18 +24,19 @@
 #include "jsobjinlines.h"
 
 #include "vm/Shape-inl.h"
 
 using mozilla::DebugOnly;
 
 using namespace js;
 
-const Class js::TypedObjectClass = {
+const Class js::TypedObjectModuleObject::class_ = {
     "TypedObject",
+    JSCLASS_HAS_RESERVED_SLOTS(SlotCount) |
     JSCLASS_HAS_CACHED_PROTO(JSProto_TypedObject),
     JS_PropertyStub,         /* addProperty */
     JS_DeletePropertyStub,   /* delProperty */
     JS_PropertyStub,         /* getProperty */
     JS_StrictPropertyStub,   /* setProperty */
     JS_EnumerateStub,
     JS_ResolveStub,
     JS_ConvertStub
@@ -48,68 +49,64 @@ static const JSFunctionSpec TypedObjectM
 
 static void
 ReportCannotConvertTo(JSContext *cx, HandleValue fromValue, const char *toType)
 {
     JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_CANT_CONVERT_TO,
                          InformalValueTypeName(fromValue), toType);
 }
 
-static void
-ReportCannotConvertTo(JSContext *cx, HandleObject fromObject, const char *toType)
-{
-    RootedValue fromValue(cx, ObjectValue(*fromObject));
-    ReportCannotConvertTo(cx, fromValue, toType);
-}
-
-static void
-ReportCannotConvertTo(JSContext *cx, HandleValue fromValue, HandleString toType)
-{
-    const char *fnName = JS_EncodeString(cx, toType);
-    if (!fnName)
-        return;
-    ReportCannotConvertTo(cx, fromValue, fnName);
-    JS_free(cx, (void *) fnName);
-}
-
-static void
-ReportCannotConvertTo(JSContext *cx, HandleValue fromValue, TypeRepresentation *toType)
-{
-    StringBuffer contents(cx);
-    if (!toType->appendString(cx, contents))
-        return;
-
-    RootedString result(cx, contents.finishString());
-    if (!result)
-        return;
-
-    ReportCannotConvertTo(cx, fromValue, result);
-}
-
-static int32_t
-Clamp(int32_t value, int32_t min, int32_t max)
-{
-    JS_ASSERT(min < max);
-    if (value < min)
-        return min;
-    if (value > max)
-        return max;
-    return value;
-}
-
 static inline JSObject *
 ToObjectIfObject(HandleValue value)
 {
     if (!value.isObject())
         return nullptr;
 
     return &value.toObject();
 }
 
 static inline bool
+IsTypedDatum(JSObject &obj)
+{
+    return obj.is<TypedObject>() || obj.is<TypedHandle>();
+}
+
+static inline uint8_t *
+TypedMem(JSObject &val)
+{
+    JS_ASSERT(IsTypedDatum(val));
+    return (uint8_t*) val.getPrivate();
+}
+
+static inline bool IsTypeObject(JSObject &type);
+
+/*
+ * Given a user-visible type descriptor object, returns the
+ * owner object for the TypeRepresentation* that we use internally.
+ */
+static JSObject *
+typeRepresentationOwnerObj(JSObject &typeObj)
+{
+    JS_ASSERT(IsTypeObject(typeObj));
+    return &typeObj.getReservedSlot(JS_TYPEOBJ_SLOT_TYPE_REPR).toObject();
+}
+
+/*
+ * Given a user-visible type descriptor object, returns the
+ * TypeRepresentation* that we use internally.
+ *
+ * Note: this pointer is valid only so long as `typeObj` remains rooted.
+ */
+static TypeRepresentation *
+typeRepresentation(JSObject &typeObj)
+{
+    return TypeRepresentation::fromOwnerObject(*typeRepresentationOwnerObj(typeObj));
+}
+
+static inline bool
 IsScalarTypeObject(JSObject &type)
 {
     return type.hasClass(&ScalarType::class_);
 }
 
 static inline bool
 IsReferenceTypeObject(JSObject &type)
 {
@@ -151,49 +148,31 @@ IsTypeObject(JSObject &type)
 {
     return IsScalarTypeObject(type) ||
            IsReferenceTypeObject(type) ||
            IsX4TypeObject(type) ||
            IsComplexTypeObject(type);
 }
 
 static inline bool
-IsTypedDatum(JSObject &obj)
+IsTypeObjectOfKind(JSObject &type, TypeRepresentation::Kind kind)
 {
-    return obj.is<TypedObject>() || obj.is<TypedHandle>();
-}
-
-static inline uint8_t *
-TypedMem(JSObject &val)
-{
-    JS_ASSERT(IsTypedDatum(val));
-    return (uint8_t*) val.getPrivate();
+    if (!IsTypeObject(type))
+        return false;
+
+    return typeRepresentation(type)->kind() == kind;
 }
 
-/*
- * Given a user-visible type descriptor object, returns the
- * owner object for the TypeRepresentation* that we use internally.
- */
-static JSObject *
-typeRepresentationOwnerObj(JSObject &typeObj)
+static inline bool
+IsSizedTypeObject(JSObject &type)
 {
-    JS_ASSERT(IsTypeObject(typeObj));
-    return &typeObj.getReservedSlot(JS_TYPEOBJ_SLOT_TYPE_REPR).toObject();
-}
-
-/*
- * Given a user-visible type descriptor object, returns the
- * TypeRepresentation* that we use internally.
- *
- * Note: this pointer is valid only so long as `typeObj` remains rooted.
- */
-static TypeRepresentation *
-typeRepresentation(JSObject &typeObj)
-{
-    return TypeRepresentation::fromOwnerObject(*typeRepresentationOwnerObj(typeObj));
+    if (!IsTypeObject(type))
+        return false;
+
+    return typeRepresentation(type)->isSized();
 }
 
 static inline JSObject *
 GetType(JSObject &datum)
 {
     JS_ASSERT(IsTypedDatum(datum));
     return &datum.getReservedSlot(JS_DATUM_SLOT_TYPE_OBJ).toObject();
 }
@@ -318,34 +297,83 @@ Reify(JSContext *cx,
 
     to.set(args.rval());
     return true;
 }
 
 static inline size_t
 TypeSize(JSObject &type)
 {
-    return typeRepresentation(type)->size();
+    return typeRepresentation(type)->asSized()->size();
+}
+
+static inline size_t
+DatumLength(JSObject &val)
+{
+    JS_ASSERT(IsTypedDatum(val));
+    JS_ASSERT(typeRepresentation(*GetType(val))->isAnyArray());
+    return val.getReservedSlot(JS_DATUM_SLOT_LENGTH).toInt32();
 }
 
 static inline size_t
 DatumSize(JSObject &val)
 {
-    return TypeSize(*GetType(val));
+    JSObject *typeObj = GetType(val);
+    TypeRepresentation *typeRepr = typeRepresentation(*typeObj);
+    switch (typeRepr->kind()) {
+      case TypeRepresentation::Scalar:
+      case TypeRepresentation::X4:
+      case TypeRepresentation::Reference:
+      case TypeRepresentation::Struct:
+      case TypeRepresentation::SizedArray:
+        return typeRepr->asSized()->size();
+
+      case TypeRepresentation::UnsizedArray:
+        return typeRepr->asUnsizedArray()->element()->size() * DatumLength(val);
+    }
+    MOZ_ASSUME_UNREACHABLE("unhandled typerepresentation kind");
 }
 
 static inline bool
 IsTypedDatumOfKind(JSObject &obj, TypeRepresentation::Kind kind)
 {
     if (!IsTypedDatum(obj))
         return false;
     TypeRepresentation *repr = typeRepresentation(*GetType(obj));
     return repr->kind() == kind;
 }
 
+static inline bool
+IsArrayTypedDatum(JSObject &obj)
+{
+    if (!IsTypedDatum(obj))
+        return false;
+    TypeRepresentation *repr = typeRepresentation(*GetType(obj));
+    return repr->isAnyArray();
+}
+
+// Extracts the `prototype` property from `obj`, throwing if it is
+// missing or not an object.
+static JSObject *
+GetPrototype(JSContext *cx, HandleObject obj)
+{
+    RootedValue prototypeVal(cx);
+    if (!JSObject::getProperty(cx, obj, obj, cx->names().prototype,
+                               &prototypeVal))
+    {
+        return nullptr;
+    }
+    if (!prototypeVal.isObject()) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
+                             JSMSG_INVALID_PROTOTYPE);
+        return nullptr;
+    }
+    return &prototypeVal.toObject();
+}
+
 /***************************************************************************
  * Scalar type objects
  *
  * Scalar type objects like `uint8`, `uint16`, are all instances of
  * the ScalarType class. Like all type objects, they have a reserved
  * slot pointing to a TypeRepresentation object, which is used to
  * distinguish which scalar type object this actually is.
  */
@@ -366,16 +394,17 @@ const Class js::ScalarType::class_ = {
     nullptr,
     nullptr,
     nullptr
 };
 
 const JSFunctionSpec js::ScalarType::typeObjectMethods[] = {
     JS_FN("toSource", TypeObjectToSource, 0, 0),
     {"handle", {nullptr, nullptr}, 2, 0, "HandleCreate"},
+    {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
     {"equivalent", {nullptr, nullptr}, 1, 0, "TypeObjectEquivalent"},
     JS_FS_END
 };
 
 template <typename T>
 static T ConvertScalar(double d)
 {
     if (TypeIsFloatingPoint<T>()) {
@@ -450,16 +479,17 @@ const Class js::ReferenceType::class_ = 
     nullptr,
     nullptr,
     nullptr
 };
 
 const JSFunctionSpec js::ReferenceType::typeObjectMethods[] = {
     JS_FN("toSource", TypeObjectToSource, 0, 0),
     {"handle", {nullptr, nullptr}, 2, 0, "HandleCreate"},
+    {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
     {"equivalent", {nullptr, nullptr}, 1, 0, "TypeObjectEquivalent"},
     JS_FS_END
 };
 
 bool
 js::ReferenceType::call(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
@@ -537,41 +567,25 @@ js::ReferenceType::call(JSContext *cx, u
  * (And, again, same with respect to s and S.)
  *
  * This function creates the A.prototype/S.prototype object.  It takes
  * as an argument either the TypedObject.ArrayType or the
  * TypedObject.StructType constructor function, then returns an empty
  * object with the .prototype.prototype object as its [[Prototype]].
  */
 static JSObject *
-CreateComplexTypeInstancePrototype(JSContext *cx,
-                                   HandleObject typeObjectCtor)
+CreatePrototypeObjectForComplexTypeInstance(JSContext *cx,
+                                            HandleObject ctorPrototype)
 {
-    RootedValue ctorPrototypeVal(cx);
-    if (!JSObject::getProperty(cx, typeObjectCtor, typeObjectCtor,
-                               cx->names().prototype,
-                               &ctorPrototypeVal))
-    {
+    RootedObject ctorPrototypePrototype(cx, GetPrototype(cx, ctorPrototype));
+    if (!ctorPrototypePrototype)
         return nullptr;
-    }
-    JS_ASSERT(ctorPrototypeVal.isObject()); // immutable binding
-    RootedObject ctorPrototypeObj(cx, &ctorPrototypeVal.toObject());
-
-    RootedValue ctorPrototypePrototypeVal(cx);
-    if (!JSObject::getProperty(cx, ctorPrototypeObj, ctorPrototypeObj,
-                               cx->names().prototype,
-                               &ctorPrototypePrototypeVal))
-    {
-        return nullptr;
-    }
-
-    JS_ASSERT(ctorPrototypePrototypeVal.isObject()); // immutable binding
-    RootedObject proto(cx, &ctorPrototypePrototypeVal.toObject());
-    return NewObjectWithGivenProto(cx, &JSObject::class_, proto,
-                                   &typeObjectCtor->global());
+
+    return NewObjectWithGivenProto(cx, &JSObject::class_,
+                                   &*ctorPrototypePrototype, nullptr);
 }
 
 const Class ArrayType::class_ = {
     "ArrayType",
     JSCLASS_HAS_RESERVED_SLOTS(JS_TYPEOBJ_ARRAY_SLOTS),
     JS_PropertyStub,
     JS_DeletePropertyStub,
     JS_PropertyStub,
@@ -588,384 +602,254 @@ const Class ArrayType::class_ = {
 };
 
 const JSPropertySpec ArrayType::typeObjectProperties[] = {
     JS_PS_END
 };
 
 const JSFunctionSpec ArrayType::typeObjectMethods[] = {
     {"handle", {nullptr, nullptr}, 2, 0, "HandleCreate"},
-    JS_FN("repeat", ArrayType::repeat, 1, 0),
+    {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
+    JS_FN("dimension", ArrayType::dimension, 1, 0),
     JS_FN("toSource", TypeObjectToSource, 0, 0),
     {"equivalent", {nullptr, nullptr}, 1, 0, "TypeObjectEquivalent"},
     JS_FS_END
 };
 
 const JSPropertySpec ArrayType::typedObjectProperties[] = {
     JS_PS_END
 };
 
 const JSFunctionSpec ArrayType::typedObjectMethods[] = {
-    JS_FN("subarray", ArrayType::subarray, 2, 0),
     {"forEach", {nullptr, nullptr}, 1, 0, "ArrayForEach"},
     {"redimension", {nullptr, nullptr}, 1, 0, "TypedArrayRedimension"},
     JS_FS_END
 };
 
 static JSObject *
-ArrayElementType(HandleObject array)
-{
-    JS_ASSERT(IsArrayTypeObject(*array));
-    return &array->getReservedSlot(JS_TYPEOBJ_SLOT_ARRAY_ELEM_TYPE).toObject();
-}
-
-static bool
-FillTypedArrayWithValue(JSContext *cx, HandleObject array, HandleValue val)
-{
-    JS_ASSERT(IsTypedDatumOfKind(*array, TypeRepresentation::Array));
-
-    RootedFunction func(
-        cx, SelfHostedFunction(cx, cx->names().FillTypedArrayWithValue));
-    if (!func)
-        return false;
-
-    InvokeArgs args(cx);
-    if (!args.init(2))
-        return false;
-
-    args.setCallee(ObjectValue(*func));
-    args[0].setObject(*array);
-    args[1].set(val);
-    return Invoke(cx, args);
-}
-
-bool
-ArrayType::repeat(JSContext *cx, unsigned int argc, Value *vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    if (args.length() < 1) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage,
-                             nullptr, JSMSG_MORE_ARGS_NEEDED,
-                             "repeat()", "0", "s");
-        return false;
-    }
-
-    RootedObject thisObj(cx, ToObjectIfObject(args.thisv()));
-    if (!thisObj || !IsArrayTypeObject(*thisObj)) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
-                             JSMSG_INCOMPATIBLE_PROTO,
-                             "ArrayType", "repeat",
-                             InformalValueTypeName(args.thisv()));
-        return false;
-    }
-
-    RootedObject binaryArray(cx, TypedObject::createZeroed(cx, thisObj));
-    if (!binaryArray)
-        return false;
-
-    RootedValue val(cx, args[0]);
-    if (!FillTypedArrayWithValue(cx, binaryArray, val))
-        return false;
-
-    args.rval().setObject(*binaryArray);
-    return true;
-}
-
-/**
- * The subarray function first creates an ArrayType instance
- * which will act as the elementType for the subarray.
- *
- * var MA = new ArrayType(elementType, 10);
- * var mb = MA.repeat(val);
- *
- * mb.subarray(begin, end=mb.length) => (Only for +ve)
- *     var internalSA = new ArrayType(elementType, end-begin);
- *     var ret = new internalSA()
- *     for (var i = begin; i < end; i++)
- *         ret[i-begin] = ret[i]
- *     return ret
- *
- * The range specified by the begin and end values is clamped to the valid
- * index range for the current array. If the computed length of the new
- * TypedArray would be negative, it is clamped to zero.
- * see: http://www.khronos.org/registry/typedarray/specs/latest/#7
- *
- */
-/* static */ bool
-ArrayType::subarray(JSContext *cx, unsigned int argc, Value *vp)
+ArrayElementType(JSObject &array)
 {
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    if (args.length() < 1) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage,
-                             nullptr, JSMSG_MORE_ARGS_NEEDED,
-                             "subarray()", "0", "s");
-        return false;
-    }
-
-    if (!args[0].isInt32()) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
-                             JSMSG_TYPEDOBJECT_SUBARRAY_INTEGER_ARG, "1");
-        return false;
-    }
-
-    RootedObject thisObj(cx, &args.thisv().toObject());
-    if (!IsTypedDatumOfKind(*thisObj, TypeRepresentation::Array)) {
-        ReportCannotConvertTo(cx, thisObj, "binary array");
-        return false;
-    }
-
-    RootedObject type(cx, GetType(*thisObj));
-    ArrayTypeRepresentation *typeRepr = typeRepresentation(*type)->asArray();
-    size_t length = typeRepr->length();
-
-    int32_t begin = args[0].toInt32();
-    int32_t end = length;
-
-    if (args.length() >= 2) {
-        if (!args[1].isInt32()) {
-            JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
-                    JSMSG_TYPEDOBJECT_SUBARRAY_INTEGER_ARG, "2");
-            return false;
-        }
-
-        end = args[1].toInt32();
-    }
-
-    if (begin < 0)
-        begin = length + begin;
-    if (end < 0)
-        end = length + end;
-
-    begin = Clamp(begin, 0, length);
-    end = Clamp(end, 0, length);
-
-    int32_t sublength = end - begin; // end exclusive
-    sublength = Clamp(sublength, 0, length);
-
-    RootedObject globalObj(cx, cx->compartment()->maybeGlobal());
-    JS_ASSERT(globalObj);
-    Rooted<GlobalObject*> global(cx, &globalObj->as<GlobalObject>());
-    RootedObject arrayTypeGlobal(cx, global->getArrayType(cx));
-
-    RootedObject elementType(cx, ArrayElementType(type));
-    RootedObject subArrayType(cx, ArrayType::create(cx, arrayTypeGlobal,
-                                                    elementType, sublength));
-    if (!subArrayType)
-        return false;
-
-    int32_t elementSize = typeRepr->element()->size();
-    size_t offset = elementSize * begin;
-
-    RootedObject subarray(
-        cx, TypedDatum::createDerived(cx, subArrayType, thisObj, offset));
-    if (!subarray)
-        return false;
-
-    args.rval().setObject(*subarray);
-    return true;
-}
-
-static bool
-ArrayFillSubarray(JSContext *cx, unsigned int argc, Value *vp)
-{
-    CallArgs args = CallArgsFromVp(argc, vp);
-
-    if (args.length() < 1) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage,
-                             nullptr, JSMSG_MORE_ARGS_NEEDED,
-                             "fill()", "0", "s");
-        return false;
-    }
-
-    RootedObject thisObj(cx, ToObjectIfObject(args.thisv()));
-    if (!thisObj || !IsTypedDatumOfKind(*thisObj, TypeRepresentation::Array)) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage,
-                             nullptr, JSMSG_INCOMPATIBLE_PROTO,
-                             "ArrayType", "fill",
-                             InformalValueTypeName(args.thisv()));
-        return false;
-    }
-
-    Value funArrayTypeVal = GetFunctionNativeReserved(&args.callee(), 0);
-    JS_ASSERT(funArrayTypeVal.isObject());
-
-    RootedObject type(cx, GetType(*thisObj));
-    TypeRepresentation *typeRepr = typeRepresentation(*type);
-    RootedObject funArrayType(cx, &funArrayTypeVal.toObject());
-    TypeRepresentation *funArrayTypeRepr = typeRepresentation(*funArrayType);
-    if (typeRepr != funArrayTypeRepr) {
-        RootedValue thisObjValue(cx, ObjectValue(*thisObj));
-        ReportCannotConvertTo(cx, thisObjValue, funArrayTypeRepr);
-        return false;
-    }
-
-    args.rval().setUndefined();
-    RootedValue val(cx, args[0]);
-    return FillTypedArrayWithValue(cx, thisObj, val);
+    JS_ASSERT(IsArrayTypeObject(array));
+    return &array.getReservedSlot(JS_TYPEOBJ_SLOT_ARRAY_ELEM_TYPE).toObject();
 }
 
 static bool
 InitializeCommonTypeDescriptorProperties(JSContext *cx,
                                          HandleObject obj,
                                          HandleObject typeReprOwnerObj)
 {
     TypeRepresentation *typeRepr =
         TypeRepresentation::fromOwnerObject(*typeReprOwnerObj);
 
-    if (typeRepr->transparent()) {
+    if (typeRepr->transparent() && typeRepr->isSized()) {
+        SizedTypeRepresentation *sizedTypeRepr = typeRepr->asSized();
+
         // byteLength
-        RootedValue typeByteLength(cx, NumberValue(typeRepr->size()));
+        RootedValue typeByteLength(cx, NumberValue(sizedTypeRepr->size()));
         if (!JSObject::defineProperty(cx, obj, cx->names().byteLength,
                                       typeByteLength,
                                       nullptr, nullptr,
                                       JSPROP_READONLY | JSPROP_PERMANENT))
         {
             return false;
         }
 
         // byteAlignment
-        RootedValue typeByteAlignment(cx, NumberValue(typeRepr->alignment()));
+        RootedValue typeByteAlignment(
+            cx, NumberValue(sizedTypeRepr->alignment()));
         if (!JSObject::defineProperty(cx, obj, cx->names().byteAlignment,
                                       typeByteAlignment,
                                       nullptr, nullptr,
                                       JSPROP_READONLY | JSPROP_PERMANENT))
         {
             return false;
         }
+    } else {
+        RootedValue undef(cx, UndefinedValue());
+
+        // byteLength
+        if (!JSObject::defineProperty(cx, obj, cx->names().byteLength,
+                                      undef,
+                                      nullptr, nullptr,
+                                      JSPROP_READONLY | JSPROP_PERMANENT))
+        {
+            return false;
+        }
+
+        // byteAlignment
+        if (!JSObject::defineProperty(cx, obj, cx->names().byteAlignment,
+                                      undef,
+                                      nullptr, nullptr,
+                                      JSPROP_READONLY | JSPROP_PERMANENT))
+        {
+            return false;
+        }
     }
 
-    // variable -- always false since we do not yet support variable-size types
-    RootedValue variable(cx, JSVAL_FALSE);
+    // variable -- true for unsized arrays
+    RootedValue variable(cx, BooleanValue(!typeRepr->isSized()));
     if (!JSObject::defineProperty(cx, obj, cx->names().variable,
                                   variable,
                                   nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return false;
     }
 
     return true;
 }
 
 JSObject *
 ArrayType::create(JSContext *cx,
-                  HandleObject metaTypeObject,
-                  HandleObject elementType,
-                  size_t length)
+                  HandleObject arrayTypePrototype,
+                  HandleObject arrayTypeReprObj,
+                  HandleObject elementType)
 {
-    JS_ASSERT(elementType);
+    JS_ASSERT(TypeRepresentation::isOwnerObject(*arrayTypeReprObj));
     JS_ASSERT(IsTypeObject(*elementType));
 
-    TypeRepresentation *elementTypeRepr = typeRepresentation(*elementType);
-    RootedObject typeReprObj(
-        cx, ArrayTypeRepresentation::Create(cx, elementTypeRepr, length));
-    if (!typeReprObj)
-        return nullptr;
-
-    RootedValue prototypeVal(cx);
-    if (!JSObject::getProperty(cx, metaTypeObject, metaTypeObject,
-                               cx->names().prototype,
-                               &prototypeVal))
-    {
-        return nullptr;
-    }
-    JS_ASSERT(prototypeVal.isObject()); // immutable binding
-
     RootedObject obj(
-        cx, NewObjectWithGivenProto(cx, &ArrayType::class_,
-                                    &prototypeVal.toObject(), cx->global()));
+        cx, NewObjectWithClassProto(cx, &ArrayType::class_,
+                                    arrayTypePrototype, nullptr));
     if (!obj)
         return nullptr;
-    obj->initReservedSlot(JS_TYPEOBJ_SLOT_TYPE_REPR, ObjectValue(*typeReprObj));
+    obj->initReservedSlot(JS_TYPEOBJ_SLOT_TYPE_REPR,
+                          ObjectValue(*arrayTypeReprObj));
 
     RootedValue elementTypeVal(cx, ObjectValue(*elementType));
     if (!JSObject::defineProperty(cx, obj, cx->names().elementType,
                                   elementTypeVal, nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
         return nullptr;
 
     obj->initReservedSlot(JS_TYPEOBJ_SLOT_ARRAY_ELEM_TYPE, elementTypeVal);
 
-    RootedValue lengthVal(cx, Int32Value(length));
-    if (!JSObject::defineProperty(cx, obj, cx->names().length,
-                                  lengthVal, nullptr, nullptr,
-                                  JSPROP_READONLY | JSPROP_PERMANENT))
-        return nullptr;
-
-    if (!InitializeCommonTypeDescriptorProperties(cx, obj, typeReprObj))
+    if (!InitializeCommonTypeDescriptorProperties(cx, obj, arrayTypeReprObj))
         return nullptr;
 
     RootedObject prototypeObj(
-        cx, CreateComplexTypeInstancePrototype(cx, metaTypeObject));
+        cx, CreatePrototypeObjectForComplexTypeInstance(cx, arrayTypePrototype));
     if (!prototypeObj)
         return nullptr;
 
     if (!LinkConstructorAndPrototype(cx, obj, prototypeObj))
         return nullptr;
 
-    JSFunction *fillFun = DefineFunctionWithReserved(cx, prototypeObj, "fill", ArrayFillSubarray, 1, 0);
-    if (!fillFun)
-        return nullptr;
-
-    // This is important
-    // so that A.prototype.fill.call(b, val)
-    // where b.type != A raises an error
-    SetFunctionNativeReserved(fillFun, 0, ObjectValue(*obj));
-
     return obj;
 }
 
 bool
 ArrayType::construct(JSContext *cx, unsigned argc, Value *vp)
 {
     CallArgs args = CallArgsFromVp(argc, vp);
 
     if (!args.isConstructing()) {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                              JSMSG_NOT_FUNCTION, "ArrayType");
         return false;
     }
 
-    if (args.length() != 2 ||
-        !args[0].isObject() ||
-        !args[1].isNumber() ||
-        args[1].toNumber() < 0)
+    RootedObject arrayTypeGlobal(cx, &args.callee());
+
+    // Expect one argument which is a sized type object
+    if (args.length() < 1) {
+        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr, JSMSG_MORE_ARGS_NEEDED,
+                             "ArrayType", "0", "");
+        return false;
+    }
+
+    if (!args[0].isObject() || !IsSizedTypeObject(args[0].toObject())) {
+        ReportCannotConvertTo(cx, args[0], "ArrayType element specifier");
+        return false;
+    }
+
+    RootedObject elementType(cx, &args[0].toObject());
+    SizedTypeRepresentation *elementTypeRepr =
+        typeRepresentation(*elementType)->asSized();
+
+    // construct the type repr
+    RootedObject arrayTypeReprObj(
+        cx, UnsizedArrayTypeRepresentation::Create(cx, elementTypeRepr));
+    if (!arrayTypeReprObj)
+        return false;
+
+    // Extract ArrayType.prototype
+    RootedObject arrayTypePrototype(cx, GetPrototype(cx, arrayTypeGlobal));
+    if (!arrayTypePrototype)
+        return nullptr;
+
+    // Create the instance of ArrayType
+    RootedObject obj(
+        cx, create(cx, arrayTypePrototype, arrayTypeReprObj, elementType));
+    if (!obj)
+        return false;
+
+    // Add `length` property, which is undefined for an unsized array.
+    RootedValue lengthVal(cx, UndefinedValue());
+    if (!JSObject::defineProperty(cx, obj, cx->names().length,
+                                  lengthVal, nullptr, nullptr,
+                                  JSPROP_READONLY | JSPROP_PERMANENT))
+        return nullptr;
+
+    args.rval().setObject(*obj);
+    return true;
+}
+
+/*static*/ bool
+ArrayType::dimension(JSContext *cx, unsigned int argc, jsval *vp)
+{
+    // Expect that the `this` pointer is an unsized array type
+    // and the first argument is an integer size.
+    CallArgs args = CallArgsFromVp(argc, vp);
+    if (args.length() != 1 ||
+        !args.thisv().isObject() ||
+        !IsTypeObjectOfKind(args.thisv().toObject(), TypeRepresentation::UnsizedArray) ||
+        !args[0].isInt32() ||
+        args[0].toInt32() < 0)
     {
         JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
                              JSMSG_TYPEDOBJECT_ARRAYTYPE_BAD_ARGS);
         return false;
     }
 
-    RootedObject arrayTypeGlobal(cx, &args.callee());
-    RootedObject elementType(cx, &args[0].toObject());
-
-    if (!IsTypeObject(*elementType)) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
-                             JSMSG_TYPEDOBJECT_ARRAYTYPE_BAD_ARGS);
+    // Extract arguments.
+    RootedObject unsizedTypeObj(cx, &args.thisv().toObject());
+    UnsizedArrayTypeRepresentation *unsizedTypeRepr =
+        typeRepresentation(*unsizedTypeObj)->asUnsizedArray();
+    int32_t length = args[0].toInt32();
+
+    // Create sized type representation.
+    RootedObject sizedTypeReprObj(
+        cx, SizedArrayTypeRepresentation::Create(
+            cx, unsizedTypeRepr->element(), length));
+    if (!sizedTypeReprObj)
         return false;
-    }
-
-    if (!args[1].isInt32()) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
-                             JSMSG_TYPEDOBJECT_ARRAYTYPE_BAD_ARGS);
-        return false;
-    }
-
-    int32_t length = args[1].toInt32();
-    if (length < 0) {
-        JS_ReportErrorNumber(cx, js_GetErrorMessage, nullptr,
-                             JSMSG_TYPEDOBJECT_ARRAYTYPE_BAD_ARGS);
-        return false;
-    }
-
-    RootedObject obj(cx, create(cx, arrayTypeGlobal, elementType, length));
+
+    // Create the sized type object.
+    RootedObject elementType(cx, ArrayElementType(*unsizedTypeObj));
+    RootedObject obj(
+        cx, create(cx, unsizedTypeObj, sizedTypeReprObj, elementType));
     if (!obj)
         return false;
+
+    // Add `length` property.
+    RootedValue lengthVal(cx, Int32Value(length));
+    if (!JSObject::defineProperty(cx, obj, cx->names().length,
+                                  lengthVal, nullptr, nullptr,
+                                  JSPROP_READONLY | JSPROP_PERMANENT))
+        return nullptr;
+
+    // Add `unsized` property, which is a link from the sized
+    // array to the unsized array.
+    RootedValue unsizedTypeObjValue(cx, ObjectValue(*unsizedTypeObj));
+    if (!JSObject::defineProperty(cx, obj, cx->names().unsized,
+                                  unsizedTypeObjValue, nullptr, nullptr,
+                                  JSPROP_READONLY | JSPROP_PERMANENT))
+        return nullptr;
+
     args.rval().setObject(*obj);
     return true;
 }
 
 /*********************************
  * StructType class
  */
 
@@ -989,16 +873,17 @@ const Class StructType::class_ = {
 };
 
 const JSPropertySpec StructType::typeObjectProperties[] = {
     JS_PS_END
 };
 
 const JSFunctionSpec StructType::typeObjectMethods[] = {
     {"handle", {nullptr, nullptr}, 2, 0, "HandleCreate"},
+    {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
     JS_FN("toSource", TypeObjectToSource, 0, 0),
     {"equivalent", {nullptr, nullptr}, 1, 0, "TypeObjectEquivalent"},
     JS_FS_END
 };
 
 const JSPropertySpec StructType::typedObjectProperties[] = {
     JS_PS_END
 };
@@ -1014,54 +899,61 @@ const JSFunctionSpec StructType::typedOb
  */
 bool
 StructType::layout(JSContext *cx, HandleObject structType, HandleObject fields)
 {
     AutoIdVector ids(cx);
     if (!GetPropertyNames(cx, fields, JSITER_OWNONLY, &ids))
         return false;
 
+    AutoPropertyNameVector fieldNames(cx);
     AutoValueVector fieldTypeObjs(cx);
     AutoObjectVector fieldTypeReprObjs(cx);
 
     RootedValue fieldTypeVal(cx);
     RootedId id(cx);
     for (unsigned int i = 0; i < ids.length(); i++) {
         id = ids[i];
 
-        if (!JSObject::getGeneric(cx, fields, fields, id, &fieldTypeVal))
-            return false;
-
-        uint32_t index;
-        if (js_IdIsIndex(id, &index)) {
+        // Check that all the property names are non-numeric strings.
+        uint32_t unused;
+        if (!JSID_IS_ATOM(id) || JSID_TO_ATOM(id)->isIndex(&unused)) {
             RootedValue idValue(cx, IdToJsval(id));
             ReportCannotConvertTo(cx, idValue, "StructType field name");
             return false;
         }
 
+        if (!fieldNames.append(JSID_TO_ATOM(id)->asPropertyName())) {
+            js_ReportOutOfMemory(cx);
+            return false;
+        }
+
+        if (!JSObject::getGeneric(cx, fields, fields, id, &fieldTypeVal))
+            return false;
+
         RootedObject fieldType(cx, ToObjectIfObject(fieldTypeVal));
-        if (!fieldType || !IsTypeObject(*fieldType)) {
+        if (!fieldType || !IsSizedTypeObject(*fieldType)) {
             ReportCannotConvertTo(cx, fieldTypeVal, "StructType field specifier");
             return false;
         }
 
         if (!fieldTypeObjs.append(fieldTypeVal)) {
             js_ReportOutOfMemory(cx);
             return false;
         }
 
         if (!fieldTypeReprObjs.append(typeRepresentationOwnerObj(*fieldType))) {
             js_ReportOutOfMemory(cx);
             return false;
         }
     }
 
     // Construct the `TypeRepresentation*`.
-    RootedObject typeReprObj(
-        cx, StructTypeRepresentation::Create(cx, ids, fieldTypeReprObjs));
+    RootedObject typeReprObj(cx);
+    typeReprObj = StructTypeRepresentation::Create(cx, fieldNames, fieldTypeReprObjs);
     if (!typeReprObj)
         return false;
     StructTypeRepresentation *typeRepr =
         TypeRepresentation::fromOwnerObject(*typeReprObj)->asStruct();
     structType->initReservedSlot(JS_TYPEOBJ_SLOT_TYPE_REPR,
                                  ObjectValue(*typeReprObj));
 
     // Construct for internal use an array with the type object for each field.
@@ -1097,17 +989,17 @@ StructType::layout(JSContext *cx, Handle
     // fieldOffsets : { string: integer, ... }
     // fieldTypes : { string: Type, ... }
     RootedObject fieldOffsets(
         cx, NewObjectWithClassProto(cx, &JSObject::class_, nullptr, nullptr));
     RootedObject fieldTypes(
         cx, NewObjectWithClassProto(cx, &JSObject::class_, nullptr, nullptr));
     for (size_t i = 0; i < typeRepr->fieldCount(); i++) {
         const StructField &field = typeRepr->field(i);
-        RootedId fieldId(cx, field.id);
+        RootedId fieldId(cx, NameToId(field.propertyName));
 
         // fieldOffsets[id] = offset
         RootedValue offset(cx, NumberValue(field.offset));
         if (!JSObject::defineGeneric(cx, fieldOffsets, fieldId,
                                      offset, nullptr, nullptr,
                                      JSPROP_READONLY | JSPROP_PERMANENT))
             return false;
 
@@ -1129,45 +1021,43 @@ StructType::layout(JSContext *cx, Handle
                                   fieldTypesValue, nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
         return false;
 
     return true;
 }
 
 JSObject *
-StructType::create(JSContext *cx, HandleObject metaTypeObject,
+StructType::create(JSContext *cx,
+                   HandleObject metaTypeObject,
                    HandleObject fields)
 {
-    RootedValue prototypeVal(cx);
-    if (!JSObject::getProperty(cx, metaTypeObject, metaTypeObject,
-                               cx->names().prototype,
-                               &prototypeVal))
+    RootedObject structTypePrototype(cx, GetPrototype(cx, metaTypeObject));
+    if (!structTypePrototype)
         return nullptr;
-    JS_ASSERT(prototypeVal.isObject()); // immutable binding
 
     RootedObject obj(
-        cx, NewObjectWithGivenProto(cx, &StructType::class_,
-                                    &prototypeVal.toObject(), cx->global()));
+        cx, NewObjectWithClassProto(cx, &StructType::class_,
+                                    structTypePrototype, nullptr));
     if (!obj)
         return nullptr;
 
     if (!StructType::layout(cx, obj, fields))
         return nullptr;
 
     RootedObject fieldsProto(cx);
     if (!JSObject::getProto(cx, fields, &fieldsProto))
         return nullptr;
 
     RootedObject typeReprObj(cx, typeRepresentationOwnerObj(*obj));
     if (!InitializeCommonTypeDescriptorProperties(cx, obj, typeReprObj))
         return nullptr;
 
     RootedObject prototypeObj(
-        cx, CreateComplexTypeInstancePrototype(cx, metaTypeObject));
+        cx, CreatePrototypeObjectForComplexTypeInstance(cx, structTypePrototype));
     if (!prototypeObj)
         return nullptr;
 
     if (!LinkConstructorAndPrototype(cx, obj, prototypeObj))
         return nullptr;
 
     return obj;
 }
@@ -1305,40 +1195,58 @@ const Class X4Type::class_ = {
     nullptr
 };
 
 // These classes just exist to group together various properties and so on.
 namespace js {
 class Int32x4Defn {
   public:
     static const X4TypeRepresentation::Type type = X4TypeRepresentation::TYPE_INT32;
+    static const JSFunctionSpec TypeDescriptorMethods[];
     static const JSPropertySpec TypedObjectProperties[];
     static const JSFunctionSpec TypedObjectMethods[];
 };
 class Float32x4Defn {
   public:
     static const X4TypeRepresentation::Type type = X4TypeRepresentation::TYPE_FLOAT32;
+    static const JSFunctionSpec TypeDescriptorMethods[];
     static const JSPropertySpec TypedObjectProperties[];
     static const JSFunctionSpec TypedObjectMethods[];
 };
 } // namespace js
 
+const JSFunctionSpec js::Int32x4Defn::TypeDescriptorMethods[] = {
+    JS_FN("toSource", TypeObjectToSource, 0, 0),
+    {"handle", {nullptr, nullptr}, 2, 0, "HandleCreate"},
+    {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
+    {"equivalent", {nullptr, nullptr}, 1, 0, "TypeObjectEquivalent"},
+    JS_FS_END
+};
+
 const JSPropertySpec js::Int32x4Defn::TypedObjectProperties[] = {
     JS_SELF_HOSTED_GET("x", "Int32x4Lane0", JSPROP_PERMANENT),
     JS_SELF_HOSTED_GET("y", "Int32x4Lane1", JSPROP_PERMANENT),
     JS_SELF_HOSTED_GET("z", "Int32x4Lane2", JSPROP_PERMANENT),
     JS_SELF_HOSTED_GET("w", "Int32x4Lane3", JSPROP_PERMANENT),
     JS_PS_END
 };
 
 const JSFunctionSpec js::Int32x4Defn::TypedObjectMethods[] = {
     JS_SELF_HOSTED_FN("toSource", "X4ToSource", 0, 0),
     JS_FS_END
 };
 
+const JSFunctionSpec js::Float32x4Defn::TypeDescriptorMethods[] = {
+    JS_FN("toSource", TypeObjectToSource, 0, 0),
+    {"handle", {nullptr, nullptr}, 2, 0, "HandleCreate"},
+    {"array", {nullptr, nullptr}, 1, 0, "ArrayShorthand"},
+    {"equivalent", {nullptr, nullptr}, 1, 0, "TypeObjectEquivalent"},
+    JS_FS_END
+};
+
 const JSPropertySpec js::Float32x4Defn::TypedObjectProperties[] = {
     JS_SELF_HOSTED_GET("x", "Float32x4Lane0", JSPROP_PERMANENT),
     JS_SELF_HOSTED_GET("y", "Float32x4Lane1", JSPROP_PERMANENT),
     JS_SELF_HOSTED_GET("z", "Float32x4Lane2", JSPROP_PERMANENT),
     JS_SELF_HOSTED_GET("w", "Float32x4Lane3", JSPROP_PERMANENT),
     JS_PS_END
 };
 
@@ -1384,16 +1292,19 @@ CreateX4Class(JSContext *cx, Handle<Glob
     }
 
     // Link type constructor to the type representation
 
     x4->initReservedSlot(JS_TYPEOBJ_SLOT_TYPE_REPR, ObjectValue(*typeReprObj));
 
     // Link constructor to prototype and install properties
 
+    if (!JS_DefineFunctions(cx, x4, T::TypeDescriptorMethods))
+        return nullptr;
+
     if (!LinkConstructorAndPrototype(cx, x4, proto) ||
         !DefinePropertiesAndBrand(cx, proto, T::TypedObjectProperties,
                                   T::TypedObjectMethods))
     {
         return nullptr;
     }
 
     return x4;
@@ -1413,17 +1324,17 @@ X4Type::call(JSContext *cx, unsigned arg
 
     double values[LANES];
     for (uint32_t i = 0; i < LANES; i++) {
         if (!ToNumber(cx, args[i], &values[i]))
             return false;
     }
 
     RootedObject typeObj(cx, &args.callee());
-    RootedObject result(cx, TypedObject::createZeroed(cx, typeObj));
+    RootedObject result(cx, TypedObject::createZeroed(cx, typeObj, 0));
     if (!result)
         return false;
 
     X4TypeRepresentation *typeRepr = typeRepresentation(*typeObj)->asX4();
     switch (typeRepr->type()) {
 #define STORE_LANES(_constant, _type, _name)                                  \
       case _constant:                                                         \
       {                                                                       \
@@ -1440,17 +1351,19 @@ X4Type::call(JSContext *cx, unsigned arg
     return true;
 }
 
 ///////////////////////////////////////////////////////////////////////////
 
 template<typename T>
 static JSObject *
 DefineMetaTypeObject(JSContext *cx,
-                     Handle<GlobalObject*> global)
+                     Handle<GlobalObject*> global,
+                     HandleObject module,
+                     TypedObjectModuleObject::Slot protoSlot)
 {
     RootedAtom className(cx, Atomize(cx, T::class_.name,
                                      strlen(T::class_.name)));
     if (!className)
         return nullptr;
 
     RootedObject funcProto(cx, global->getOrCreateFunctionPrototype(cx));
     if (!funcProto)
@@ -1480,60 +1393,74 @@ DefineMetaTypeObject(JSContext *cx,
                                   protoProtoValue,
                                   nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
         return nullptr;
 
     // Create ctor itself
 
     const int constructorLength = 2;
-    RootedFunction ctor(cx,
-                        global->createConstructor(cx, T::construct,
-                                                  className, constructorLength));
+    RootedFunction ctor(cx);
+    ctor = global->createConstructor(cx, T::construct, className, constructorLength);
     if (!ctor ||
         !LinkConstructorAndPrototype(cx, ctor, proto) ||
         !DefinePropertiesAndBrand(cx, proto,
                                   T::typeObjectProperties,
                                   T::typeObjectMethods) ||
         !DefinePropertiesAndBrand(cx, protoProto,
                                   T::typedObjectProperties,
                                   T::typedObjectMethods))
     {
         return nullptr;
     }
 
+    module->initReservedSlot(protoSlot, ObjectValue(*proto));
+
     return ctor;
 }
 
+bool
+GlobalObject::initTypedObjectModule(JSContext *cx, Handle<GlobalObject*> global)
+{
+    RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
+    if (!objProto)
+        return false;
+
+    RootedObject module(cx, NewObjectWithClassProto(cx, &TypedObjectModuleObject::class_,
+                                                    objProto, global));
+    if (!module)
+        return false;
+
+    if (!JS_DefineFunctions(cx, module, TypedObjectMethods))
+        return false;
+
+    RootedValue moduleValue(cx, ObjectValue(*module));
+
+    global->setConstructor(JSProto_TypedObject, moduleValue);
+    return true;
+}
+
 JSObject *
-js_InitTypedObjectClass(JSContext *cx, HandleObject obj)
+js_InitTypedObjectModuleObject(JSContext *cx, HandleObject obj)
 {
     /*
      * The initialization strategy for TypedObjects is mildly unusual
      * compared to other classes. Because all of the types are members
      * of a single global, `TypedObject`, we basically make the
      * initialized for the `TypedObject` class populate the
      * `TypedObject` global (which is referred to as "module" herein).
      */
 
     JS_ASSERT(obj->is<GlobalObject>());
     Rooted<GlobalObject *> global(cx, &obj->as<GlobalObject>());
 
-    RootedObject objProto(cx, global->getOrCreateObjectPrototype(cx));
-    if (!objProto)
-        return nullptr;
-
-    RootedObject module(cx, NewObjectWithGivenProto(cx, &JSObject::class_,
-                                                    objProto, global));
+    RootedObject module(cx, global->getOrCreateTypedObjectModule(cx));
     if (!module)
         return nullptr;
 
-    if (!JS_DefineFunctions(cx, module, TypedObjectMethods))
-        return nullptr;
-
     // Define TypedObject global.
 
     RootedValue moduleValue(cx, ObjectValue(*module));
 
     // uint8, uint16, any, etc
 
 #define BINARYDATA_SCALAR_DEFINE(constant_, type_, name_)                       \
     if (!DefineSimpleTypeObject<ScalarType>(cx, global, module, constant_,      \
@@ -1578,49 +1505,44 @@ js_InitTypedObjectClass(JSContext *cx, H
                                   nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return nullptr;
     }
 
     // ArrayType.
 
-    RootedObject arrayType(cx, DefineMetaTypeObject<ArrayType>(cx, global));
+    RootedObject arrayType(cx);
+    arrayType = DefineMetaTypeObject<ArrayType>(
+        cx, global, module, TypedObjectModuleObject::ArrayTypePrototype);
     if (!arrayType)
         return nullptr;
 
     RootedValue arrayTypeValue(cx, ObjectValue(*arrayType));
     if (!JSObject::defineProperty(cx, module, cx->names().ArrayType,
                                   arrayTypeValue,
                                   nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
         return nullptr;
 
     // StructType.
 
-    RootedObject structType(cx, DefineMetaTypeObject<StructType>(cx, global));
+    RootedObject structType(cx);
+    structType = DefineMetaTypeObject<StructType>(
+        cx, global, module, TypedObjectModuleObject::StructTypePrototype);
     if (!structType)
         return nullptr;
 
     RootedValue structTypeValue(cx, ObjectValue(*structType));
     if (!JSObject::defineProperty(cx, module, cx->names().StructType,
                                   structTypeValue,
                                   nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
         return nullptr;
 
-    // Everything is setup, install module on the global object:
-    if (!JSObject::defineProperty(cx, global, cx->names().TypedObject,
-                                  moduleValue,
-                                  nullptr, nullptr,
-                                  0))
-        return nullptr;
-    global->setConstructor(JSProto_TypedObject, moduleValue);
-    global->setArrayType(arrayType);
-
     //  Handle
 
     RootedObject handle(cx, NewBuiltinClassInstance(cx, &JSObject::class_));
     if (!module)
         return nullptr;
 
     if (!JS_DefineFunctions(cx, handle, TypedHandle::handleStaticMethods))
         return nullptr;
@@ -1629,57 +1551,108 @@ js_InitTypedObjectClass(JSContext *cx, H
     if (!JSObject::defineProperty(cx, module, cx->names().Handle,
                                   handleValue,
                                   nullptr, nullptr,
                                   JSPROP_READONLY | JSPROP_PERMANENT))
     {
         return nullptr;
     }
 
+    // Everything is setup, install module on the global object:
+    if (!JSObject::defineProperty(cx, global, cx->names().TypedObject,
+                                  moduleValue,
+                                  nullptr, nullptr,
+                                  0))
+        return nullptr;
+
     return module;
 }
 
 JSObject *
 js_InitTypedObjectDummy(JSContext *cx, HandleObject obj)
 {
     /*
      * This function is entered into the jsprototypes.h table
      * as the initializer for `TypedObject`. It should not
      * be executed via the `standard_class_atoms` mechanism.
      */
 
     MOZ_ASSUME_UNREACHABLE("shouldn't be initializing TypedObject via the JSProtoKey initializer mechanism");
 }
 
+bool
+TypedObjectModuleObject::getSuitableClaspAndProto(JSContext *cx,
+                                                  TypeRepresentation::Kind kind,
+                                                  const Class **clasp,
+                                                  MutableHandleObject proto)
+{
+    switch (kind) {
+      case TypeRepresentation::Scalar:
+        *clasp = &ScalarType::class_;
+        proto.set(global().getOrCreateFunctionPrototype(cx));
+        break;
+
+      case TypeRepresentation::Reference:
+        *clasp = &ReferenceType::class_;
+        proto.set(global().getOrCreateFunctionPrototype(cx));
+        break;
+
+      case TypeRepresentation::X4:
+        *clasp = &X4Type::class_;
+        proto.set(global().getOrCreateFunctionPrototype(cx));
+        break;
+
+      case TypeRepresentation::Struct:
+        *clasp = &StructType::class_;
+        proto.set(&getSlot(StructTypePrototype).toObject());
+        break;
+
+      case TypeRepresentation::SizedArray:
+        *clasp = &ArrayType::class_;
+        proto.set(&getSlot(ArrayTypePrototype).toObject());
+        break;
+
+      case TypeRepresentation::UnsizedArray:
+        *clasp = &ArrayType::class_;
+        proto.set(&getSlot(ArrayTypePrototype).toObject());
+        break;
+    }
+
+    return !!proto;
+}
+
 /******************************************************************************
  * Typed datums
  *
  * Datums represent either typed objects or handles. See comment in
  * TypedObject.h.
  */
 
 template<class T>
 /*static*/ T *
 TypedDatum::createUnattached(JSContext *cx,
-                             HandleObject type)
+                             HandleObject type,
+                             int32_t length)
 {
     JS_STATIC_ASSERT(T::IsTypedDatumClass);
     JS_ASSERT(IsTypeObject(*type));
 
-    RootedObject obj(cx, createUnattachedWithClass(cx, &T::class_, type));
+    RootedObject obj(
+        cx, createUnattachedWithClass(cx, &T::class_, type, length));
     if (!obj)
         return nullptr;
 
     return &obj->as<T>();
 }
 
 /*static*/ TypedDatum *
 TypedDatum::createUnattachedWithClass(JSContext *cx,
                                       const Class *clasp,
-                                      HandleObject type)
+                                      HandleObject type,
+                                      int32_t length)
 {
     JS_ASSERT(IsTypeObject(*type));
     JS_ASSERT(clasp == &TypedObject::class_ || clasp == &TypedHandle::class_);
     JS_ASSERT(JSCLASS_RESERVED_SLOTS(clasp) == JS_DATUM_SLOTS);
     JS_ASSERT(clasp->hasPrivate());
 
     RootedObject proto(cx);
     if (IsSimpleTypeObject(*type)) {
@@ -1698,26 +1671,31 @@ TypedDatum::createUnattachedWithClass(JS
     RootedObject obj(
         cx, NewObjectWithClassProto(cx, clasp, &*proto, nullptr));
     if (!obj)
         return nullptr;
 
     obj->setPrivate(nullptr);
     obj->initReservedSlot(JS_DATUM_SLOT_TYPE_OBJ, ObjectValue(*type));
     obj->initReservedSlot(JS_DATUM_SLOT_OWNER, NullValue());
+    obj->initReservedSlot(JS_DATUM_SLOT_LENGTH, Int32Value(length));
 
     // Tag the type object for this instance with the type
     // representation, if that has not been done already.
     if (cx->typeInferenceEnabled() && !IsSimpleTypeObject(*type)) {
         // FIXME Bug 929651           ^~~~~~~~~~~~~~~~~~~~~~~~~~~
         RootedTypeObject typeObj(cx, obj->getType(cx));
         if (typeObj) {
             TypeRepresentation *typeRepr = typeRepresentation(*type);
-            if (!typeObj->addTypedObjectAddendum(cx, typeRepr))
+            if (!typeObj->addTypedObjectAddendum(cx,
+                                                 types::TypeTypedObject::Datum,
+                                                 typeRepr))
+            {
                 return nullptr;
+            }
         }
     }
 
     return static_cast<TypedDatum*>(&*obj);
 }
 
 /*static*/ void
 TypedDatum::attach(uint8_t *memory)
@@ -1737,26 +1715,52 @@ TypedDatum::attach(JSObject &datum, uint
 
     // find the owner, which is often but not always `datum`
     JSObject &owner = datum.getReservedSlot(JS_DATUM_SLOT_OWNER).toObject();
 
     setPrivate(mem);
     setReservedSlot(JS_DATUM_SLOT_OWNER, ObjectValue(owner));
 }
 
+// Returns a suitable JS_DATUM_SLOT_LENGTH value for an instance of
+// the type `type`. `type` must not be an unsized array.
+static int32_t
+DatumLengthFromType(JSObject &type)
+{
+    TypeRepresentation *typeRepr = typeRepresentation(type);
+    switch (typeRepr->kind()) {
+      case TypeRepresentation::Scalar:
+      case TypeRepresentation::Reference:
+      case TypeRepresentation::Struct:
+      case TypeRepresentation::X4:
+        return 0;
+
+      case TypeRepresentation::SizedArray:
+        return typeRepr->asSizedArray()->length();
+
+      case TypeRepresentation::UnsizedArray:
+        MOZ_ASSUME_UNREACHABLE("DatumLengthFromType() invoked on unsized type");
+    }
+    MOZ_ASSUME_UNREACHABLE("Invalid kind");
+}
+
 /*static*/ TypedDatum *
 TypedDatum::createDerived(JSContext *cx, HandleObject type,
                           HandleObject datum, size_t offset)
 {
+    JS_ASSERT(IsTypeObject(*type));
     JS_ASSERT(IsTypedDatum(*datum));
     JS_ASSERT(offset <= DatumSize(*datum));
     JS_ASSERT(offset + TypeSize(*type) <= DatumSize(*datum));
 
+    int32_t length = DatumLengthFromType(*type);
+
     const js::Class *clasp = datum->getClass();
-    Rooted<TypedDatum*> obj(cx, createUnattachedWithClass(cx, clasp, type));
+    Rooted<TypedDatum*> obj(
+        cx, createUnattachedWithClass(cx, clasp, type, length));
     if (!obj)
         return nullptr;
 
     obj->attach(*datum, offset);
     return obj;
 }
 
 static bool
@@ -1791,20 +1795,34 @@ ReportDatumTypeError(JSContext *cx,
 /*static*/ void
 TypedDatum::obj_trace(JSTracer *trace, JSObject *object)
 {
     JS_ASSERT(IsTypedDatum(*object));
 
     for (size_t i = 0; i < JS_DATUM_SLOTS; i++)
         gc::MarkSlot(trace, &object->getReservedSlotRef(i), "TypedObjectSlot");
 
-    uint8_t *mem = TypedMem(*object);
     TypeRepresentation *repr = typeRepresentation(*GetType(*object));
-    if (repr->opaque())
-        repr->traceInstance(trace, mem);
+    if (repr->opaque()) {
+        uint8_t *mem = TypedMem(*object);
+        switch (repr->kind()) {
+          case TypeRepresentation::Scalar:
+          case TypeRepresentation::Reference:
+          case TypeRepresentation::Struct:
+          case TypeRepresentation::SizedArray:
+          case TypeRepresentation::X4:
+            repr->asSized()->traceInstance(trace, mem, 1);
+            break;
+
+          case TypeRepresentation::UnsizedArray:
+            repr->asUnsizedArray()->element()->traceInstance(
+                trace, mem, DatumLength(*object));
+            break;
+        }
+    }
 }
 
 /*static*/ void
 TypedDatum::obj_finalize(js::FreeOp *op, JSObject *obj)
 {
     // if the obj owns the memory, indicating by the owner slot being
     // set to itself, then we must free it when finalized.
 
@@ -1829,17 +1847,18 @@ TypedDatum::obj_lookupGeneric(JSContext 
     TypeRepresentation *typeRepr = typeRepresentation(*type);
 
     switch (typeRepr->kind()) {
       case TypeRepresentation::Scalar:
       case TypeRepresentation::Reference:
       case TypeRepresentation::X4:
         break;
 
-      case TypeRepresentation::Array:
+      case TypeRepresentation::SizedArray:
+      case TypeRepresentation::UnsizedArray:
       {
         uint32_t index;
         if (js_IdIsIndex(id, &index))
             return obj_lookupElement(cx, obj, index, objp, propp);
 
         if (JSID_IS_ATOM(id, cx->names().length)) {
             MarkNonNativePropertyFound(propp);
             objp.set(obj);
@@ -2008,19 +2027,27 @@ TypedDatum::obj_getGeneric(JSContext *cx
     switch (typeRepr->kind()) {
       case TypeRepresentation::Scalar:
       case TypeRepresentation::Reference:
         break;
 
       case TypeRepresentation::X4:
         break;
 
-      case TypeRepresentation::Array:
+      case TypeRepresentation::SizedArray:
+      case TypeRepresentation::UnsizedArray:
         if (JSID_IS_ATOM(id, cx->names().length)) {
-            vp.setInt32(typeRepr->asArray()->length());
+            if (!TypedMem(*obj)) { // unattached
+                JS_ReportErrorNumber(
+                    cx, js_GetErrorMessage,
+                    nullptr, JSMSG_TYPEDOBJECT_HANDLE_UNATTACHED);
+                return false;
+            }
+
+            vp.setInt32(DatumLength(*obj));
             return true;
         }
         break;
 
       case TypeRepresentation::Struct: {
         StructTypeRepresentation *structTypeRepr = typeRepr->asStruct();
         const StructField *field = structTypeRepr->fieldNamed(id);
         if (!field)
@@ -2069,28 +2096,33 @@ TypedDatum::obj_getElementIfPresent(JSCo
 
     switch (typeRepr->kind()) {
       case TypeRepresentation::Scalar:
       case TypeRepresentation::Reference:
       case TypeRepresentation::X4:
       case TypeRepresentation::Struct:
         break;
 
-      case TypeRepresentation::Array: {
+      case TypeRepresentation::SizedArray:
+      case TypeRepresentation::UnsizedArray:
+      {
+        JS_ASSERT(IsArrayTypedDatum(*obj));
+
         *present = true;
-        ArrayTypeRepresentation *arrayTypeRepr = typeRepr->asArray();
-
-        if (index >= arrayTypeRepr->length()) {
+
+        if (index >= DatumLength(*obj)) {
             vp.setUndefined();
             return true;
         }
 
-        RootedObject elementType(cx, ArrayElementType(type));
-        size_t offset = arrayTypeRepr->element()->size() * index;
-        return Reify(cx, arrayTypeRepr->element(), elementType, obj, offset, vp);
+        RootedObject elementType(cx, ArrayElementType(*GetType(*obj)));
+        SizedTypeRepresentation *elementTypeRepr =
+            typeRepresentation(*elementType)->asSized();
+        size_t offset = elementTypeRepr->size() * index;
+        return Reify(cx, elementTypeRepr, elementType, obj, offset, vp);
       }
     }
 
     RootedObject proto(cx, obj->getProto());
     if (!proto) {
         *present = false;
         vp.setUndefined();
         return true;
@@ -2122,17 +2154,18 @@ TypedDatum::obj_setGeneric(JSContext *cx
     switch (typeRepr->kind()) {
       case ScalarTypeRepresentation::Scalar:
       case TypeRepresentation::Reference:
         break;
 
       case ScalarTypeRepresentation::X4:
         break;
 
-      case ScalarTypeRepresentation::Array:
+      case ScalarTypeRepresentation::SizedArray:
+      case ScalarTypeRepresentation::UnsizedArray:
         if (JSID_IS_ATOM(id, cx->names().length)) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage,
                                  nullptr, JSMSG_CANT_REDEFINE_ARRAY_LENGTH);
             return false;
         }
         break;
 
       case ScalarTypeRepresentation::Struct: {
@@ -2157,39 +2190,41 @@ TypedDatum::obj_setProperty(JSContext *c
                              bool strict)
 {
     RootedId id(cx, NameToId(name));
     return obj_setGeneric(cx, obj, id, vp, strict);
 }
 
 bool
 TypedDatum::obj_setElement(JSContext *cx, HandleObject obj, uint32_t index,
-                            MutableHandleValue vp, bool strict)
+                           MutableHandleValue vp, bool strict)
 {
     RootedObject type(cx, GetType(*obj));
     TypeRepresentation *typeRepr = typeRepresentation(*type);
 
     switch (typeRepr->kind()) {
       case TypeRepresentation::Scalar:
       case TypeRepresentation::Reference:
       case TypeRepresentation::X4:
       case TypeRepresentation::Struct:
         break;
 
-      case TypeRepresentation::Array: {
-        ArrayTypeRepresentation *arrayTypeRepr = typeRepr->asArray();
-
-        if (index >= arrayTypeRepr->length()) {
+      case TypeRepresentation::SizedArray:
+      case TypeRepresentation::UnsizedArray:
+      {
+        if (index >= DatumLength(*obj)) {
             JS_ReportErrorNumber(cx, js_GetErrorMessage,
                                  nullptr, JSMSG_TYPEDOBJECT_BINARYARRAY_BAD_INDEX);
             return false;
         }
 
-        RootedObject elementType(cx, ArrayElementType(type));
-        size_t offset = arrayTypeRepr->element()->size() * index;
+        RootedObject elementType(cx, ArrayElementType(*GetType(*obj)));
+        SizedTypeRepresentation *elementTypeRepr =
+            typeRepresentation(*elementType)->asSized();
+        size_t offset = elementTypeRepr->size() * index;
         return ConvertAndCopyTo(cx, elementType, obj, offset, vp);
       }
     }
 
     return ReportDatumTypeError(cx, JSMSG_OBJECT_NOT_EXTENSIBLE, obj);
 }
 
 bool
@@ -2212,17 +2247,18 @@ TypedDatum::obj_getGenericAttributes(JSC
     switch (typeRepr->kind()) {
       case TypeRepresentation::Scalar:
       case TypeRepresentation::Reference:
         break;
 
       case TypeRepresentation::X4:
         break;
 
-      case TypeRepresentation::Array:
+      case TypeRepresentation::SizedArray:
+      case TypeRepresentation::UnsizedArray:
         if (js_IdIsIndex(id, &index)) {
             *attrsp = JSPROP_ENUMERATE | JSPROP_PERMANENT;
             return true;
         }
         if (JSID_IS_ATOM(id, cx->names().length)) {
             *attrsp = JSPROP_READONLY | JSPROP_PERMANENT;
             return true;
         }
@@ -2253,17 +2289,18 @@ IsOwnId(JSContext *cx, HandleObject obj,
     TypeRepresentation *typeRepr = typeRepresentation(*type);
 
     switch (typeRepr->kind()) {
       case TypeRepresentation::Scalar:
       case TypeRepresentation::Reference:
       case TypeRepresentation::X4:
         return false;
 
-      case TypeRepresentation::Array:
+      case TypeRepresentation::SizedArray:
+      case TypeRepresentation::UnsizedArray:
         return js_IdIsIndex(id, &index) || JSID_IS_ATOM(id, cx->names().length);
 
       case TypeRepresentation::Struct:
         return typeRepr->asStruct()->fieldNamed(id) != nullptr;
     }
 
     return false;
 }
@@ -2357,32 +2394,33 @@ TypedDatum::obj_enumerate(JSContext *cx,
 
           case JSENUMERATE_NEXT:
           case JSENUMERATE_DESTROY:
             statep.setNull();
             break;
         }
         break;
 
-      case TypeRepresentation::Array:
+      case TypeRepresentation::SizedArray:
+      case TypeRepresentation::UnsizedArray:
         switch (enum_op) {
           case JSENUMERATE_INIT_ALL:
           case JSENUMERATE_INIT:
             statep.setInt32(0);
-            idp.set(INT_TO_JSID(typeRepr->asArray()->length()));
+            idp.set(INT_TO_JSID(DatumLength(*obj)));
             break;
 
           case JSENUMERATE_NEXT:
             index = static_cast<int32_t>(statep.toInt32());
 
-            if (index < typeRepr->asArray()->length()) {
+            if (index < DatumLength(*obj)) {
                 idp.set(INT_TO_JSID(index));
                 statep.setInt32(index + 1);
             } else {
-                JS_ASSERT(index == typeRepr->asArray()->length());
+                JS_ASSERT(index == DatumLength(*obj));
                 statep.setNull();
             }
 
             break;
 
           case JSENUMERATE_DESTROY:
             statep.setNull();
             break;
@@ -2396,17 +2434,17 @@ TypedDatum::obj_enumerate(JSContext *cx,
             statep.setInt32(0);
             idp.set(INT_TO_JSID(typeRepr->asStruct()->fieldCount()));
             break;
 
           case JSENUMERATE_NEXT:
             index = static_cast<uint32_t>(statep.toInt32());
 
             if (index < typeRepr->asStruct()->fieldCount()) {
-                idp.set(typeRepr->asStruct()->field(index).id);
+                idp.set(NameToId(typeRepr->asStruct()->field(index).propertyName));
                 statep.setInt32(index + 1);
             } else {
                 statep.setNull();
             }
 
             break;
 
           case JSENUMERATE_DESTROY:
@@ -2417,17 +2455,17 @@ TypedDatum::obj_enumerate(JSContext *cx,
     }
 
     return true;
 }
 
 /* static */ size_t
 TypedDatum::dataOffset()
 {
-    return JSObject::getPrivateDataOffset(JS_DATUM_SLOTS + 1);
+    return JSObject::getPrivateDataOffset(JS_DATUM_SLOTS);
 }
 
 /******************************************************************************
  * Typed Objects
  */
 
 const Class TypedObject::class_ = {
     "TypedObject",
@@ -2473,47 +2511,122 @@ const Class TypedObject::class_ = {
         TypedDatum::obj_deleteElement,
         TypedDatum::obj_deleteSpecial,
         nullptr, nullptr, // watch/unwatch
         TypedDatum::obj_enumerate,
         nullptr, /* thisObject */
     }
 };
 
-/*static*/ JSObject *
-TypedObject::createZeroed(JSContext *cx, HandleObject type)
+/*static*/ TypedObject *
+TypedObject::createZeroed(JSContext *cx,
+                          HandleObject typeObj,
+                          int32_t length)
 {
-    Rooted<TypedObject*> obj(cx, createUnattached<TypedObject>(cx, type));
+    // Create unattached wrapper object.
+    Rooted<TypedObject*> obj(cx);
+    obj = createUnattached<TypedObject>(cx, typeObj, length);
     if (!obj)
         return nullptr;
 
-    TypeRepresentation *typeRepr = typeRepresentation(*type);
-    size_t memsize = typeRepr->size();
-    uint8_t *memory = (uint8_t*) cx->malloc_(memsize);
-    if (!memory)
-        return nullptr;
-    typeRepr->initInstance(cx->runtime(), memory);
-    obj->attach(memory);
-    return obj;
+    // Allocate and initialize the memory for this instance.
+    // Also initialize the JS_DATUM_SLOT_LENGTH slot.
+    TypeRepresentation *typeRepr = typeRepresentation(*typeObj);
+    switch (typeRepr->kind()) {
+      case TypeRepresentation::Scalar:
+      case TypeRepresentation::Reference:
+      case TypeRepresentation::Struct:
+      case TypeRepresentation::X4:
+      {
+        uint8_t *memory = (uint8_t*) cx->malloc_(typeRepr->asSized()->size());
+        if (!memory)
+            return nullptr;
+        typeRepr->asSized()->initInstance(cx->runtime(), memory, 1);
+        obj->attach(memory);
+        return obj;
+      }
+
+      case TypeRepresentation::SizedArray:
+      {
+        uint8_t *memory = (uint8_t*) cx->malloc_(typeRepr->asSizedArray()->size());
+        if (!memory)
+            return nullptr;
+        typeRepr->asSizedArray()->initInstance(cx->runtime(), memory, 1);
+        obj->attach(memory);
+        return obj;
+      }
+
+      case TypeRepresentation::UnsizedArray:
+      {
+        SizedTypeRepresentation *elementTypeRepr =
+            typeRepr->asUnsizedArray()->element();
+