Merge m-c to fx-team.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 26 Aug 2013 20:21:33 -0400
changeset 158248 e42dce3209da3d347c575bea7860aa692ef107a0
parent 158247 f30dadf4c9377ec1c2b516d72315eb5d200cfa48 (current diff)
parent 158239 c0d1baa50a64f37863228348e8ec16c409311de6 (diff)
child 158283 f57a6179a08568a7e93ddf0e4048d7eb5445f6b8
child 158360 5de48b36c5747af1ae008ecafceb88c23e64127c
child 171237 29e1fa1a1f72d5eeff1356b5ac67182e4c383d3a
child 178423 571b2854e11ff4479f8d05f92010d47af74b6ee6
push id407
push userlsblakk@mozilla.com
push dateTue, 03 Dec 2013 03:32:50 +0000
treeherdermozilla-release@babf8c9ebc52 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone26.0a1
first release with
nightly linux32
e42dce3209da / 26.0a1 / 20130827030201 / files
nightly linux64
e42dce3209da / 26.0a1 / 20130827030201 / files
nightly mac
e42dce3209da / 26.0a1 / 20130827030201 / files
nightly win32
e42dce3209da / 26.0a1 / 20130827030201 / files
nightly win64
e42dce3209da / 26.0a1 / 20130827030201 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to fx-team.
mobile/android/base/GeckoPreferences.java
mobile/android/chrome/content/browser.js
parser/htmlparser/public/nsIElementObserver.h
parser/htmlparser/public/nsIParserNode.h
parser/htmlparser/src/nsParserNode.cpp
parser/htmlparser/src/nsParserNode.h
python/mach/mach/test/common2.py
--- a/CLOBBER
+++ b/CLOBBER
@@ -13,9 +13,9 @@
 #          |               |
 #          O <-- Clobber   O  <-- Clobber
 #
 # Note: The description below will be part of the error message shown to users.
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
-Bug 908208 - ipdl dependencies are busted, so we must clobber.  See Bug 907394 for a solution.
+Bug 899210 - xpidl header generation is somehow busted so we need to clobber on windows
--- a/accessible/src/xul/XULTreeAccessible.cpp
+++ b/accessible/src/xul/XULTreeAccessible.cpp
@@ -774,17 +774,17 @@ XULTreeItemAccessibleBase::GetBounds(int
   *aHeight = presContext->CSSPixelsToDevPixels(height);
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 XULTreeItemAccessibleBase::SetSelected(bool aSelect)
 {
-  if (IsDefunct() || !mTreeView)
+  if (IsDefunct())
     return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsITreeSelection> selection;
   mTreeView->GetSelection(getter_AddRefs(selection));
   if (selection) {
     bool isSelected;
     selection->IsSelected(mRow, &isSelected);
     if (isSelected != aSelect)
@@ -792,33 +792,31 @@ XULTreeItemAccessibleBase::SetSelected(b
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 XULTreeItemAccessibleBase::TakeFocus()
 {
-  if (IsDefunct() || !mTreeView)
+  if (IsDefunct())
     return NS_ERROR_FAILURE;
 
   nsCOMPtr<nsITreeSelection> selection;
   mTreeView->GetSelection(getter_AddRefs(selection));
   if (selection)
     selection->SetCurrentIndex(mRow);
 
   // focus event will be fired here
   return Accessible::TakeFocus();
 }
 
 Relation
 XULTreeItemAccessibleBase::RelationByType(uint32_t aType)
 {
-  if (!mTreeView)
-    return Relation();
 
   switch (aType) {
     case nsIAccessibleRelation::RELATION_NODE_CHILD_OF: {
       int32_t parentIndex = -1;
       if (!NS_SUCCEEDED(mTreeView->GetParentIndex(mRow, &parentIndex)))
         return Relation();
 
       if (parentIndex == -1)
@@ -951,18 +949,16 @@ XULTreeItemAccessibleBase::GroupPosition
   groupPos.posInSet = topCount;
 
   return groupPos;
 }
 
 uint64_t
 XULTreeItemAccessibleBase::NativeState()
 {
-  if (!mTreeView)
-    return states::DEFUNCT;
 
   // focusable and selectable states
   uint64_t state = NativeInteractiveState();
 
   // expanded/collapsed state
   if (IsExpandable()) {
     bool isContainerOpen;
     mTreeView->IsContainerOpen(mRow, &isContainerOpen);
@@ -1057,18 +1053,16 @@ XULTreeItemAccessibleBase::GetSiblingAtO
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // XULTreeItemAccessibleBase: protected implementation
 
 bool
 XULTreeItemAccessibleBase::IsExpandable()
 {
-  if (!mTreeView)
-    return false;
 
   bool isContainer = false;
   mTreeView->IsContainer(mRow, &isContainer);
   if (isContainer) {
     bool isEmpty = false;
     mTreeView->IsContainerEmpty(mRow, &isEmpty);
     if (!isEmpty) {
       nsCOMPtr<nsITreeColumns> columns;
@@ -1084,18 +1078,16 @@ XULTreeItemAccessibleBase::IsExpandable(
   }
 
   return false;
 }
 
 void
 XULTreeItemAccessibleBase::GetCellName(nsITreeColumn* aColumn, nsAString& aName)
 {
-  if (!mTreeView)
-    return;
 
   mTreeView->GetCellText(mRow, aColumn, aName);
 
   // If there is still no name try the cell value:
   // This is for graphical cells. We need tree/table view implementors to
   // implement FooView::GetCellValue to return a meaningful string for cases
   // where there is something shown in the cell (non-text) such as a star icon;
   // in which case GetCellValue for that cell would return "starred" or
--- a/accessible/src/xul/XULTreeGridAccessible.cpp
+++ b/accessible/src/xul/XULTreeGridAccessible.cpp
@@ -773,18 +773,16 @@ XULTreeGridCellAccessible::RelationByTyp
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // XULTreeGridCellAccessible: public implementation
 
 void
 XULTreeGridCellAccessible::CellInvalidated()
 {
-  if (!mTreeView)
-    return;
 
   nsAutoString textEquiv;
 
   int16_t type;
   mColumn->GetType(&type);
   if (type == nsITreeColumn::TYPE_CHECKBOX) {
     mTreeView->GetCellValue(mRow, mColumn, textEquiv);
     if (mCachedTextEquiv != textEquiv) {
@@ -847,18 +845,16 @@ XULTreeGridCellAccessible::DispatchClick
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // XULTreeGridCellAccessible: protected implementation
 
 bool
 XULTreeGridCellAccessible::IsEditable() const
 {
-  if (!mTreeView)
-    return false;
 
   // XXX: logic corresponds to tree.xml, it's preferable to have interface
   // method to check it.
   bool isEditable = false;
   nsresult rv = mTreeView->IsEditable(mRow, mColumn, &isEditable);
   if (NS_FAILED(rv) || !isEditable)
     return false;
 
--- a/b2g/chrome/content/dbg-browser-actors.js
+++ b/b2g/chrome/content/dbg-browser-actors.js
@@ -18,21 +18,21 @@
  *   when it exits.
  *
  * * @param connection DebuggerServerConnection
  *        The conection to the client.
  */
 function createRootActor(connection)
 {
   let parameters = {
-#ifndef MOZ_WIDGET_GONK
-    tabList: new ContentTabList(connection),
-#else
-    tabList: [],
-#endif
+    tabList: {
+      getList: function() {
+        return promise.resolve([]);
+      }
+    },
     globalActorFactories: DebuggerServer.globalActorFactories,
     onShutdown: sendShutdownEvent
   };
   let root = new RootActor(connection, parameters);
   root.applicationType = "operating-system";
   return root;
 }
 
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "abcc2f6a74c69d6ed28ea29681f86ef1f4fd90ba", 
+    "revision": "22cb357f03833f5ce63ba567c2fd4610a4c8061c", 
     "repo_path": "/integration/gaia-central"
 }
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -1280,17 +1280,17 @@ nsContextMenu.prototype = {
         this.sendMedia();
   },
 
   sendMedia: function() {
     MailIntegration.sendMessage(this.mediaURL, "");
   },
 
   playPlugin: function() {
-    gPluginHandler.activateSinglePlugin(this.target.ownerDocument.defaultView.top, this.target);
+    gPluginHandler._showClickToPlayNotification(this.browser, this.target);
   },
 
   hidePlugin: function() {
     gPluginHandler.hideClickToPlayOverlay(this.target);
   },
 
   // Generate email address and put it on clipboard.
   copyEmail: function() {
--- a/browser/base/content/test/Makefile.in
+++ b/browser/base/content/test/Makefile.in
@@ -368,9 +368,16 @@ endif
 
 ifdef MOZ_CRASHREPORTER
 MOCHITEST_BROWSER_FILES += \
   browser_pluginCrashCommentAndURL.js \
   pluginCrashCommentAndURL.html \
   $(NULL)
 endif
 
+# browser_CTP_context_menu.js fails intermittently on Linux (bug 909342)
+ifndef MOZ_WIDGET_GTK
+MOCHITEST_BROWSER_FILES += \
+  browser_CTP_context_menu.js \
+  $(NULL)
+endif
+
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/browser_CTP_context_menu.js
@@ -0,0 +1,103 @@
+var rootDir = getRootDirectory(gTestPath);
+const gTestRoot = rootDir;
+const gHttpTestRoot = rootDir.replace("chrome://mochitests/content/", "http://127.0.0.1:8888/");
+
+var gTestBrowser = null;
+var gNextTest = null;
+var gPluginHost = Components.classes["@mozilla.org/plugin/host;1"].getService(Components.interfaces.nsIPluginHost);
+
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+function test() {
+  waitForExplicitFinish();
+  registerCleanupFunction(function() {
+    clearAllPluginPermissions();
+    Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
+    getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_ENABLED;
+  });
+  Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
+
+  let newTab = gBrowser.addTab();
+  gBrowser.selectedTab = newTab;
+  gTestBrowser = gBrowser.selectedBrowser;
+  gTestBrowser.addEventListener("load", pageLoad, true);
+
+  Services.prefs.setBoolPref("plugins.click_to_play", true);
+  getTestPlugin().enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
+
+  prepareTest(runAfterPluginBindingAttached(test1), gHttpTestRoot + "plugin_test.html");
+}
+
+function finishTest() {
+  clearAllPluginPermissions();
+  gTestBrowser.removeEventListener("load", pageLoad, true);
+  gBrowser.removeCurrentTab();
+  window.focus();
+  finish();
+}
+
+function pageLoad() {
+  // The plugin events are async dispatched and can come after the load event
+  // This just allows the events to fire before we then go on to test the states
+  executeSoon(gNextTest);
+}
+
+function prepareTest(nextTest, url) {
+  gNextTest = nextTest;
+  gTestBrowser.contentWindow.location = url;
+}
+
+// Due to layout being async, "PluginBindAttached" may trigger later.
+// This wraps a function to force a layout flush, thus triggering it,
+// and schedules the function execution so they're definitely executed
+// afterwards.
+function runAfterPluginBindingAttached(func) {
+  return function() {
+    let doc = gTestBrowser.contentDocument;
+    let elems = doc.getElementsByTagName('embed');
+    if (elems.length < 1) {
+      elems = doc.getElementsByTagName('object');
+    }
+    elems[0].clientTop;
+    executeSoon(func);
+  };
+}
+
+// Test that the activate action in content menus for CTP plugins works
+function test1() {
+  let popupNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(popupNotification, "Test 1, Should have a click-to-play notification");
+
+  let plugin = gTestBrowser.contentDocument.getElementById("test");
+  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  ok(!objLoadingContent.activated, "Test 1, Plugin should not be activated");
+
+  window.document.addEventListener("popupshown", test2, false);
+  EventUtils.synthesizeMouseAtCenter(plugin,
+                                     { type: "contextmenu", button: 2 },
+                                     gTestBrowser.contentWindow);
+}
+
+function test2() {
+  window.document.removeEventListener("popupshown", test2, false);
+  let activate = window.document.getElementById("context-ctp-play");
+  ok(activate, "Test 2, Should have a context menu entry for activating the plugin");
+
+  // Trigger the click-to-play popup
+  activate.doCommand();
+
+  let notification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
+  ok(notification, "Test 2, Should have a click-to-play notification");
+  ok(!notification.dismissed, "Test 2, The click-to-play notification should not be dismissed");
+
+  // Activate the plugin
+  PopupNotifications.panel.firstChild._primaryButton.click();
+
+  let plugin = gTestBrowser.contentDocument.getElementById("test");
+  let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
+  waitForCondition(() => objLoadingContent.activated, test3, "Waited too long for plugin to activate");
+}
+
+function test3() {
+  finishTest();
+}
--- a/browser/devtools/webconsole/test/Makefile.in
+++ b/browser/devtools/webconsole/test/Makefile.in
@@ -142,16 +142,18 @@ MOCHITEST_BROWSER_FILES = \
 	browser_console_dead_objects.js \
 	browser_console_iframe_messages.js \
 	browser_console_variables_view_while_debugging_and_inspecting.js \
 	browser_webconsole_bug_686937_autocomplete_JSTerm_helpers.js \
 	browser_webconsole_cached_autocomplete.js \
 	browser_console_navigation_marker.js \
 	browser_webconsole_bug_762593_insecure_passwords_web_console_warning.js \
 	browser_webconsole_bug_762593_insecure_passwords_about_blank_web_console_warning.js \
+	browser_webconsole_allow_mixedcontent_securityerrors.js \
+	browser_webconsole_block_mixedcontent_securityerrors.js \
 	head.js \
 	$(NULL)
 
 ifeq ($(OS_ARCH), Darwin)
 MOCHITEST_BROWSER_FILES += \
 	browser_webconsole_bug_804845_ctrl_key_nav.js \
         $(NULL)
 endif
@@ -249,11 +251,12 @@ MOCHITEST_BROWSER_FILES += \
 	test-iframe-762593-insecure-form-action.html \
 	test-iframe-762593-insecure-frame.html \
 	test-bug-762593-insecure-passwords-web-console-warning.html \
 	test-bug-762593-insecure-passwords-about-blank-web-console-warning.html \
 	test-consoleiframes.html \
 	test-iframe1.html \
 	test-iframe2.html \
 	test-iframe3.html \
+	test-mixedcontent-securityerrors.html \
 	$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_allow_mixedcontent_securityerrors.js
@@ -0,0 +1,69 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// The test loads a web page with mixed active and display content
+// on it while the "block mixed content" settings are _off_.
+// It then checks that the loading mixed content warning messages
+// are logged to the console and have the correct "Learn More"
+// url appended to them.
+// Bug 875456 - Log mixed content messages from the Mixed Content
+// Blocker to the Security Pane in the Web Console
+
+const TEST_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-mixedcontent-securityerrors.html";
+const LEARN_MORE_URI = "https://developer.mozilla.org/en/Security/MixedContent";
+
+function test()
+{
+  SpecialPowers.pushPrefEnv({"set":
+      [["security.mixed_content.block_active_content", false],
+       ["security.mixed_content.block_display_content", false]
+  ]}, loadingMixedContentTest);
+}
+
+function loadingMixedContentTest()
+{
+  addTab(TEST_URI);
+  browser.addEventListener("load", function onLoad(aEvent) {
+    browser.removeEventListener(aEvent.type, onLoad, true);
+    openConsole(null, function testSecurityErrorLogged (hud) {
+      waitForMessages({
+        webconsole: hud,
+        messages: [
+          {
+          name: "Logged mixed active content",
+          text: "Loading mixed (insecure) active content on a secure page \"http://example.com/\"",
+          category: CATEGORY_SECURITY,
+          severity: SEVERITY_WARNING
+        },
+        {
+          name: "Logged mixed passive content - image",
+          text: "Loading mixed (insecure) display content on a secure page \"http://example.com/tests/image/test/mochitest/blue.png\"",
+          category: CATEGORY_SECURITY,
+          severity: SEVERITY_WARNING
+        },
+        ],
+      }).then(() => testClickOpenNewTab(hud));
+    });
+  }, true);
+}
+
+function testClickOpenNewTab(hud) {
+  let warningNode = hud.outputNode.querySelector(".webconsole-learn-more-link");
+
+    // Invoke the click event and check if a new tab would
+    // open to the correct page.
+    let linkOpened = false;
+    let oldOpenUILinkIn = window.openUILinkIn;
+    window.openUILinkIn = function(aLink) {
+      if (aLink == LEARN_MORE_URI) {
+        linkOpened = true;
+      }
+    }
+
+    EventUtils.synthesizeMouse(warningNode, 2, 2, {},
+                               warningNode.ownerDocument.defaultView);
+    ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
+    window.openUILinkIn = oldOpenUILinkIn;
+
+    finishTest();
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_block_mixedcontent_securityerrors.js
@@ -0,0 +1,104 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// The test loads a web page with mixed active and display content
+// on it while the "block mixed content" settings are _on_.
+// It then checks that the blocked mixed content warning messages
+// are logged to the console and have the correct "Learn More"
+// url appended to them. After the first test finishes, it invokes
+// a second test that overrides the mixed content blocker settings
+// by clicking on the doorhanger shield and validates that the
+// appropriate messages are logged to console.
+// Bug 875456 - Log mixed content messages from the Mixed Content
+// Blocker to the Security Pane in the Web Console
+
+const TEST_URI = "https://example.com/browser/browser/devtools/webconsole/test/test-mixedcontent-securityerrors.html";
+const LEARN_MORE_URI = "https://developer.mozilla.org/en/Security/MixedContent";
+
+function test()
+{
+  SpecialPowers.pushPrefEnv({"set": [["security.mixed_content.block_active_content", true],
+                            ["security.mixed_content.block_display_content", true]]}, blockMixedContentTest1);
+}
+
+function blockMixedContentTest1()
+{
+  addTab(TEST_URI);
+  browser.addEventListener("load", function onLoad(aEvent) {
+    browser.removeEventListener(aEvent.type, onLoad, true);
+    openConsole(null, function testSecurityErrorLogged (hud) {
+      waitForMessages({
+        webconsole: hud,
+        messages: [
+          {
+            name: "Logged blocking mixed active content",
+            text: "Blocked loading mixed active content \"http://example.com/\"",
+            category: CATEGORY_SECURITY,
+            severity: SEVERITY_ERROR
+          },
+          {
+            name: "Logged blocking mixed passive content - image",
+            text: "Blocked loading mixed active content \"http://example.com/\"",
+            category: CATEGORY_SECURITY,
+            severity: SEVERITY_ERROR
+          },
+        ],
+      }).then(() => {
+        testClickOpenNewTab(hud);
+        // Call the second (MCB override) test.
+        mixedContentOverrideTest2(hud);
+      });
+    });
+  }, true);
+}
+
+function mixedContentOverrideTest2(hud)
+{
+  var notification = PopupNotifications.getNotification("mixed-content-blocked", browser);
+  ok(notification, "Mixed Content Doorhanger didn't appear");
+  // Click on the doorhanger.
+  notification.secondaryActions[0].callback();
+
+  waitForMessages({
+    webconsole: hud,
+    messages: [
+      {
+      name: "Logged blocking mixed active content",
+      text: "Loading mixed (insecure) active content on a secure"+
+        " page \"http://example.com/\"",
+      category: CATEGORY_SECURITY,
+      severity: SEVERITY_WARNING
+    },
+    {
+      name: "Logged blocking mixed passive content - image",
+      text: "Loading mixed (insecure) display content on a secure page"+
+        " \"http://example.com/tests/image/test/mochitest/blue.png\"",
+      category: CATEGORY_SECURITY,
+      severity: SEVERITY_WARNING
+    },
+    ],
+  }).then(() => {
+    testClickOpenNewTab(hud);
+    finishTest();
+  });
+}
+
+function testClickOpenNewTab(hud) {
+  let warningNode = hud.outputNode.querySelector(".webconsole-learn-more-link");
+
+    // Invoke the click event and check if a new tab would
+    // open to the correct page.
+    let linkOpened = false;
+    let oldOpenUILinkIn = window.openUILinkIn;
+    window.openUILinkIn = function(aLink) {
+      if (aLink == LEARN_MORE_URI) {
+        linkOpened = true;
+      }
+    }
+
+    EventUtils.synthesizeMouse(warningNode, 2, 2, {},
+                               warningNode.ownerDocument.defaultView);
+    ok(linkOpened, "Clicking the Learn More Warning node opens the desired page");
+    window.openUILinkIn = oldOpenUILinkIn;
+
+}
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -1082,17 +1082,17 @@ function waitForMessages(aOptions)
       // TODO: we keep this behavior until bug 778766 is fixed. After that we
       // will not require |type| to match newer types of messages.
       return false;
     }
 
     let partialMatch = !!(aRule.consoleTrace || aRule.consoleTime ||
                           aRule.consoleTimeEnd || aRule.type);
 
-    if (aRule.category && aElement.category != aRule.category) {
+    if ("category" in aRule && aElement.category != aRule.category) {
       if (partialMatch) {
         is(aElement.category, aRule.category,
            "message category for rule: " + displayRule(aRule));
         displayErrorContext(aRule, aElement);
       }
       return false;
     }
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/test-mixedcontent-securityerrors.html
@@ -0,0 +1,21 @@
+<!--
+  Bug 875456 - Log mixed content messages from the Mixed Content Blocker to the
+  Security Pane in the Web Console
+-->
+
+<!DOCTYPE HTML>
+<html dir="ltr" xml:lang="en-US" lang="en-US">
+  <head>
+    <meta charset="utf8">
+    <title>Mixed Content test - http on https</title>
+    <script src="testscript.js"></script>
+    <!--
+      Any copyright is dedicated to the Public Domain.
+      http://creativecommons.org/publicdomain/zero/1.0/
+    -->
+  </head>
+  <body>
+    <iframe src="http://example.com"></iframe>
+    <img src="http://example.com/tests/image/test/mochitest/blue.png"></img>
+  </body>
+</html>
--- a/browser/devtools/webconsole/webconsole.js
+++ b/browser/devtools/webconsole/webconsole.js
@@ -1441,51 +1441,66 @@ WebConsoleFrame.prototype = {
    *
    * @param aNode
    *        The node to which we will be adding the more info link node
    * @param aScriptError
    *        The script error object that we are reporting to the console
    */
   addMoreInfoLink: function WCF_addMoreInfoLink(aNode, aScriptError)
   {
-    // We have a single category for now, but more are to be
-    // expected soon
+    let url;
     if (aScriptError.category == "Insecure Password Field") {
-      this.addInsecurePasswordsWarningNode(aNode);
-    }
+      url = INSECURE_PASSWORDS_LEARN_MORE;
+    }
+    else if (aScriptError.category == "Mixed Content Message" ||
+               aScriptError.category == "Mixed Content Blocker") {
+      url = MIXED_CONTENT_LEARN_MORE;
+    }
+    else {
+      // Unknown category. Return without adding more info node.
+      return;
+    }
+
+    this.addLearnMoreWarningNode(aNode, url);
   },
 
   /*
-   * Appends a clickable insecure passwords warning node to the node passed
+   * Appends a clickable warning node to the node passed
    * as a parameter to the function. When a user clicks on the appended
-   * warning node, the browser navigates to a page where the user can learn
-   * more about security issues associated with insecure passwords.
+   * warning node, the browser navigates to the provided url.
+   *
+   * @param aNode
+   *        The node to which we will be adding a clickable warning node.
+   * @param aURL
+   *        The url which points to the page where the user can learn more
+   *        about security issues associated with the specific message that's
+   *        being logged.
    */
-  addInsecurePasswordsWarningNode:
-  function WCF_addInsecurePasswordsWarningNode(aNode)
+  addLearnMoreWarningNode:
+  function WCF_addLearnMoreWarningNode(aNode, aURL)
   {
     let moreInfoLabel =
       "[" + l10n.getStr("webConsoleMoreInfoLabel") + "]";
 
     // The node that holds the clickable warning node.
     let linkNode = this.document.createElementNS(XUL_NS, "hbox");
     linkNode.flex = 1;
     linkNode.classList.add("webconsole-msg-body-piece");
     linkNode.classList.add("webconsole-msg-link");
     aNode.appendChild(linkNode);
 
-    // Create the actual insecure passwords warning node and make it clickable
+    // Create the actual warning node and make it clickable
     let warningNode = this.document.createElement("label");
     warningNode.setAttribute("value", moreInfoLabel);
     warningNode.setAttribute("title", moreInfoLabel);
     warningNode.classList.add("hud-clickable");
     warningNode.classList.add("webconsole-learn-more-link");
 
     warningNode.addEventListener("click", function(aEvent) {
-      this.owner.openLink(INSECURE_PASSWORDS_LEARN_MORE);
+      this.owner.openLink(aURL);
       aEvent.preventDefault();
       aEvent.stopPropagation();
     }.bind(this));
 
     linkNode.appendChild(warningNode);
   },
 
   /**
@@ -4546,16 +4561,17 @@ var Utils = {
   categoryForScriptError: function Utils_categoryForScriptError(aScriptError)
   {
     switch (aScriptError.category) {
       case "CSS Parser":
       case "CSS Loader":
         return CATEGORY_CSS;
 
       case "Mixed Content Blocker":
+      case "Mixed Content Message":
       case "CSP":
       case "Invalid HSTS Headers":
       case "Insecure Password Field":
         return CATEGORY_SECURITY;
 
       default:
         return CATEGORY_JS;
     }
--- a/build/mobile/robocop/Actions.java.in
+++ b/build/mobile/robocop/Actions.java.in
@@ -44,32 +44,16 @@ public interface Actions {
     /**
      * Sends an event to Gecko.
      * 
      * @param geckoEvent The geckoEvent JSONObject's type
      */
     void sendGeckoEvent(String geckoEvent, String data);
 
     /**
-     * Sends a preferences get event to Gecko.
-     *
-     * @param requestId The id of this request.
-     * @param prefNames The preferences being requested.
-     */
-    void sendPreferencesGetEvent(int requestId, String[] prefNames);
-
-    /**
-     * Sends a preferences observe event to Gecko.
-     *
-     * @param requestId The id of this request.
-     * @param prefNames The preferences being requested.
-     */
-    void sendPreferencesObserveEvent(int requestId, String[] prefNames);
-
-    /**
      * Listens for a gecko event to be sent from the Gecko instance.
      * The returned object can be used to test if the event has been
      * received. Note that only one event is listened for.
      * 
      * @param geckoEvent The geckoEvent JSONObject's type
      */
     RepeatedEventExpecter expectGeckoEvent(String geckoEvent);
 
--- a/build/mobile/robocop/FennecNativeActions.java.in
+++ b/build/mobile/robocop/FennecNativeActions.java.in
@@ -39,18 +39,16 @@ public class FennecNativeActions impleme
     // Objects for reflexive access of fennec classes.
     private ClassLoader mClassLoader;
     private Class mApiClass;
     private Class mEventListenerClass;
     private Class mDrawListenerClass;
     private Method mRegisterEventListener;
     private Method mUnregisterEventListener;
     private Method mBroadcastEvent;
-    private Method mPreferencesGetEvent;
-    private Method mPreferencesObserveEvent;
     private Method mSetDrawListener;
     private Method mQuerySql;
     private Object mRobocopApi;
 
     private static final String LOGTAG = "FennecNativeActions";
 
     public FennecNativeActions(Activity activity, Solo robocop, Instrumentation instrumentation, Assert asserter) {
         mSolo = robocop;
@@ -63,18 +61,16 @@ public class FennecNativeActions impleme
 
             mApiClass = mClassLoader.loadClass("org.mozilla.gecko.RobocopAPI");
             mEventListenerClass = mClassLoader.loadClass("org.mozilla.gecko.util.GeckoEventListener");
             mDrawListenerClass = mClassLoader.loadClass("org.mozilla.gecko.gfx.GeckoLayerClient$DrawListener");
 
             mRegisterEventListener = mApiClass.getMethod("registerEventListener", String.class, mEventListenerClass);
             mUnregisterEventListener = mApiClass.getMethod("unregisterEventListener", String.class, mEventListenerClass);
             mBroadcastEvent = mApiClass.getMethod("broadcastEvent", String.class, String.class);
-            mPreferencesGetEvent = mApiClass.getMethod("preferencesGetEvent", Integer.TYPE, String[].class);
-            mPreferencesObserveEvent = mApiClass.getMethod("preferencesObserveEvent", Integer.TYPE, String[].class);
             mSetDrawListener = mApiClass.getMethod("setDrawListener", mDrawListenerClass);
             mQuerySql = mApiClass.getMethod("querySql", String.class, String.class);
 
             mRobocopApi = mApiClass.getConstructor(Activity.class).newInstance(activity);
         } catch (Exception e) {
             FennecNativeDriver.log(LogLevel.ERROR, e);
         }
     }
@@ -264,34 +260,16 @@ public class FennecNativeActions impleme
             mBroadcastEvent.invoke(mRobocopApi, geckoEvent, data);
         } catch (IllegalAccessException e) {
             FennecNativeDriver.log(LogLevel.ERROR, e);
         } catch (InvocationTargetException e) {
             FennecNativeDriver.log(LogLevel.ERROR, e);
         }
     }
 
-    private void sendPreferencesEvent(Method method, int requestId, String[] prefNames) {
-        try {
-            method.invoke(mRobocopApi, requestId, prefNames);
-        } catch (IllegalAccessException e) {
-            FennecNativeDriver.log(LogLevel.ERROR, e);
-        } catch (InvocationTargetException e) {
-            FennecNativeDriver.log(LogLevel.ERROR, e);
-        }
-    }
-
-    public void sendPreferencesGetEvent(int requestId, String[] prefNames) {
-        sendPreferencesEvent(mPreferencesGetEvent, requestId, prefNames);
-    }
-
-    public void sendPreferencesObserveEvent(int requestId, String[] prefNames) {
-        sendPreferencesEvent(mPreferencesObserveEvent, requestId, prefNames);
-    }
-
     class DrawListenerProxy implements InvocationHandler {
         private final PaintExpecter mPaintExpecter;
 
         DrawListenerProxy(PaintExpecter paintExpecter) {
             mPaintExpecter = paintExpecter;
         }
 
         public Object invoke(Object proxy, Method method, Object[] args) {
--- a/config/check_spidermonkey_style.py
+++ b/config/check_spidermonkey_style.py
@@ -195,17 +195,17 @@ class FileKind(object):
     @staticmethod
     def get(filename):
         if filename.endswith('.c'):
             return FileKind.C
 
         if filename.endswith('.cpp'):
             return FileKind.CPP
 
-        if filename.endswith(('inlines.h', '-inl.h')):
+        if filename.endswith(('inlines.h', '-inl.h', 'Inlines.h')):
             return FileKind.INL_H
 
         if filename.endswith('.h'):
             return FileKind.H
 
         if filename.endswith('.tbl'):
             return FileKind.TBL
 
--- a/content/base/src/nsMixedContentBlocker.cpp
+++ b/content/base/src/nsMixedContentBlocker.cpp
@@ -26,16 +26,21 @@
 #include "nsIWebNavigation.h"
 #include "nsLoadGroup.h"
 #include "nsIScriptError.h"
 
 #include "prlog.h"
 
 using namespace mozilla;
 
+enum nsMixedContentBlockerMessageType {
+  eBlocked = 0x00,
+  eUserOverride = 0x01
+};
+
 // Is mixed script blocking (fonts, plugin content, scripts, stylesheets,
 // iframes, websockets, XHR) enabled?
 bool nsMixedContentBlocker::sBlockMixedScript = false;
 
 // Is mixed display content blocking (images, audio, video, <a ping>) enabled?
 bool nsMixedContentBlocker::sBlockMixedDisplay = false;
 
 // Fired at the document that attempted to load mixed content.  The UI could
@@ -142,32 +147,52 @@ nsMixedContentBlocker::nsMixedContentBlo
 }
 
 nsMixedContentBlocker::~nsMixedContentBlocker()
 {
 }
 
 NS_IMPL_ISUPPORTS1(nsMixedContentBlocker, nsIContentPolicy)
 
-void
-LogBlockingMixedContent(MixedContentTypes classification,
-                        nsIURI* aContentLocation,
-                        nsIDocument* aRootDoc)
+static void
+LogMixedContentMessage(MixedContentTypes aClassification,
+                       nsIURI* aContentLocation,
+                       nsIDocument* aRootDoc,
+                       nsMixedContentBlockerMessageType aMessageType)
 {
+  nsAutoCString messageCategory;
+  uint32_t severityFlag;
+  nsAutoCString messageLookupKey;
+
+  if (aMessageType == eBlocked) {
+    severityFlag = nsIScriptError::errorFlag;
+    messageCategory.AssignLiteral("Mixed Content Blocker");
+    if (aClassification == eMixedDisplay) {
+      messageLookupKey.AssignLiteral("BlockMixedDisplayContent");
+    } else {
+      messageLookupKey.AssignLiteral("BlockMixedActiveContent");
+    }
+  } else {
+    severityFlag = nsIScriptError::warningFlag;
+    messageCategory.AssignLiteral("Mixed Content Message");
+    if (aClassification == eMixedDisplay) {
+      messageLookupKey.AssignLiteral("LoadingMixedDisplayContent");
+    } else {
+      messageLookupKey.AssignLiteral("LoadingMixedActiveContent");
+    }
+  }
+
   nsAutoCString locationSpec;
   aContentLocation->GetSpec(locationSpec);
   NS_ConvertUTF8toUTF16 locationSpecUTF16(locationSpec);
 
   const PRUnichar* strings[] = { locationSpecUTF16.get() };
-  nsContentUtils::ReportToConsole(nsIScriptError::errorFlag,
-                                  NS_LITERAL_CSTRING("Mixed Content Blocker"),
-                                  aRootDoc,
+  nsContentUtils::ReportToConsole(severityFlag, messageCategory, aRootDoc,
                                   nsContentUtils::eSECURITY_PROPERTIES,
-                                  classification == eMixedDisplay ? "BlockMixedDisplayContent" : "BlockMixedActiveContent",
-                                  strings, ArrayLength(strings));
+                                  messageLookupKey.get(), strings, ArrayLength(strings));
 }
 
 NS_IMETHODIMP
 nsMixedContentBlocker::ShouldLoad(uint32_t aContentType,
                                   nsIURI* aContentLocation,
                                   nsIURI* aRequestingLocation,
                                   nsISupports* aRequestingContext,
                                   const nsACString& aMimeGuess,
@@ -448,34 +473,36 @@ nsMixedContentBlocker::ShouldLoad(uint32
     *aDecision = nsIContentPolicy::ACCEPT;
     return NS_OK;
   }
   nsresult stateRV = securityUI->GetState(&State);
 
   // If the content is display content, and the pref says display content should be blocked, block it.
   if (sBlockMixedDisplay && classification == eMixedDisplay) {
     if (allowMixedContent) {
+      LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride);
       *aDecision = nsIContentPolicy::ACCEPT;
       rootDoc->SetHasMixedActiveContentLoaded(true);
       if (!rootDoc->GetHasMixedDisplayContentLoaded() && NS_SUCCEEDED(stateRV)) {
         eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_LOADED_MIXED_DISPLAY_CONTENT));
       }
     } else {
       *aDecision = nsIContentPolicy::REJECT_REQUEST;
-      LogBlockingMixedContent(classification, aContentLocation, rootDoc);
+      LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked);
       if (!rootDoc->GetHasMixedDisplayContentBlocked() && NS_SUCCEEDED(stateRV)) {
         eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_BLOCKED_MIXED_DISPLAY_CONTENT));
       }
     }
     return NS_OK;
 
   } else if (sBlockMixedScript && classification == eMixedScript) {
     // If the content is active content, and the pref says active content should be blocked, block it
     // unless the user has choosen to override the pref
     if (allowMixedContent) {
+       LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride);
        *aDecision = nsIContentPolicy::ACCEPT;
        // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
        if (rootDoc->GetHasMixedActiveContentLoaded()) {
          return NS_OK;
        }
        rootDoc->SetHasMixedActiveContentLoaded(true);
 
        if (rootHasSecureConnection) {
@@ -496,17 +523,17 @@ nsMixedContentBlocker::ShouldLoad(uint32
          if (NS_SUCCEEDED(stateRV)) {
            eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_LOADED_MIXED_ACTIVE_CONTENT));
          }
          return NS_OK;
        }
     } else {
        //User has not overriden the pref by Disabling protection. Reject the request and update the security state.
        *aDecision = nsIContentPolicy::REJECT_REQUEST;
-       LogBlockingMixedContent(classification, aContentLocation, rootDoc);
+       LogMixedContentMessage(classification, aContentLocation, rootDoc, eBlocked);
        // See if the pref will change here. If it will, only then do we need to call OnSecurityChange() to update the UI.
        if (rootDoc->GetHasMixedActiveContentBlocked()) {
          return NS_OK;
        }
        rootDoc->SetHasMixedActiveContentBlocked(true);
 
        // The user has not overriden the pref, so make sure they still have an option by calling eventSink
        // which will invoke the doorhanger
@@ -514,16 +541,19 @@ nsMixedContentBlocker::ShouldLoad(uint32
           eventSink->OnSecurityChange(aRequestingContext, (State | nsIWebProgressListener::STATE_BLOCKED_MIXED_ACTIVE_CONTENT));
        }
        return NS_OK;
     }
 
   } else {
     // The content is not blocked by the mixed content prefs.
 
+    // Log a message that we are loading mixed content.
+    LogMixedContentMessage(classification, aContentLocation, rootDoc, eUserOverride);
+
     // Fire the event from a script runner as it is unsafe to run script
     // from within ShouldLoad
     nsContentUtils::AddScriptRunner(
       new nsMixedContentEvent(aRequestingContext, classification));
     return NS_OK;
   }
 
   *aDecision = REJECT_REQUEST;
--- a/content/canvas/src/CanvasRenderingContext2D.cpp
+++ b/content/canvas/src/CanvasRenderingContext2D.cpp
@@ -542,37 +542,35 @@ CanvasRenderingContext2D::CanvasRenderin
   , mIPC(false)
   , mIsEntireFrameInvalid(false)
   , mPredictManyRedrawCalls(false), mPathTransformWillUpdate(false)
   , mInvalidateCount(0)
 {
   sNumLivingContexts++;
   SetIsDOMBinding();
 
-#if USE_SKIA_GPU
+#ifdef USE_SKIA_GPU
   mForceSoftware = false;
 #endif
 }
 
 CanvasRenderingContext2D::~CanvasRenderingContext2D()
 {
   Reset();
   // Drop references from all CanvasRenderingContext2DUserData to this context
   for (uint32_t i = 0; i < mUserDatas.Length(); ++i) {
     mUserDatas[i]->Forget();
   }
   sNumLivingContexts--;
   if (!sNumLivingContexts) {
     NS_IF_RELEASE(sErrorTarget);
   }
 
-#if USE_SKIA_GPU
-  std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), this);
-  if (iter != DemotableContexts().end())
-    DemotableContexts().erase(iter);
+#ifdef USE_SKIA_GPU
+  RemoveDemotableContext(this);
 #endif
 }
 
 JSObject*
 CanvasRenderingContext2D::WrapObject(JSContext *cx, JS::Handle<JSObject*> scope)
 {
   return CanvasRenderingContext2DBinding::Wrap(cx, scope, this);
 }
@@ -741,39 +739,50 @@ CanvasRenderingContext2D::RedrawUser(con
     return;
   }
 
   mgfx::Rect newr =
     mTarget->GetTransform().TransformBounds(ToRect(r));
   Redraw(newr);
 }
 
-#if USE_SKIA_GPU
-
 void CanvasRenderingContext2D::Demote()
 {
+#ifdef  USE_SKIA_GPU
   if (!IsTargetValid() || mForceSoftware)
     return;
 
+  RemoveDemotableContext(this);
+
   RefPtr<SourceSurface> snapshot = mTarget->Snapshot();
   RefPtr<DrawTarget> oldTarget = mTarget;
   mTarget = nullptr;
   mResetLayer = true;
   mForceSoftware = true;
 
   // Recreate target, now demoted to software only
   EnsureTarget();
   if (!IsTargetValid())
     return;
 
-  // Put back the content from the old DrawTarget
+  // Restore the content from the old DrawTarget
   mgfx::Rect r(0, 0, mWidth, mHeight);
   mTarget->DrawSurface(snapshot, r, r);
+
+  // Restore the clips and transform
+  for (uint32_t i = 0; i < CurrentState().clipsPushed.size(); i++) {
+    mTarget->PushClip(CurrentState().clipsPushed[i]);
+  }
+
+  mTarget->SetTransform(oldTarget->GetTransform());
+#endif
 }
 
+#ifdef USE_SKIA_GPU
+
 std::vector<CanvasRenderingContext2D*>&
 CanvasRenderingContext2D::DemotableContexts()
 {
   static std::vector<CanvasRenderingContext2D*> contexts;
   return contexts;
 }
 
 void
@@ -785,31 +794,37 @@ CanvasRenderingContext2D::DemoteOldestCo
   const size_t kMaxContexts = 16;
 #endif
 
   std::vector<CanvasRenderingContext2D*>& contexts = DemotableContexts();
   if (contexts.size() < kMaxContexts)
     return;
 
   CanvasRenderingContext2D* oldest = contexts.front();
-  contexts.erase(contexts.begin());
-
   oldest->Demote();
 }
 
 void
 CanvasRenderingContext2D::AddDemotableContext(CanvasRenderingContext2D* context)
 {
   std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), context);
   if (iter != DemotableContexts().end())
     return;
 
   DemotableContexts().push_back(context);
 }
 
+void
+CanvasRenderingContext2D::RemoveDemotableContext(CanvasRenderingContext2D* context)
+{
+  std::vector<CanvasRenderingContext2D*>::iterator iter = std::find(DemotableContexts().begin(), DemotableContexts().end(), context);
+  if (iter != DemotableContexts().end())
+    DemotableContexts().erase(iter);
+}
+
 #define MIN_SKIA_GL_DIMENSION 16
 
 bool
 CheckSizeForSkiaGL(IntSize size) {
   return size.width > MIN_SKIA_GL_DIMENSION && size.height > MIN_SKIA_GL_DIMENSION;
 }
 
 #endif
--- a/content/canvas/src/CanvasRenderingContext2D.h
+++ b/content/canvas/src/CanvasRenderingContext2D.h
@@ -362,16 +362,18 @@ public:
 
   void DrawWindow(nsIDOMWindow* window, double x, double y, double w, double h,
                   const nsAString& bgColor, uint32_t flags,
                   mozilla::ErrorResult& error);
   void AsyncDrawXULElement(nsXULElement& elem, double x, double y, double w,
                            double h, const nsAString& bgColor, uint32_t flags,
                            mozilla::ErrorResult& error);
 
+  void Demote();
+
   nsresult Redraw();
 
   // nsICanvasRenderingContextInternal
   NS_IMETHOD SetDimensions(int32_t width, int32_t height) MOZ_OVERRIDE;
   NS_IMETHOD InitializeWithSurface(nsIDocShell *shell, gfxASurface *surface, int32_t width, int32_t height) MOZ_OVERRIDE;
 
   NS_IMETHOD Render(gfxContext *ctx,
                     gfxPattern::GraphicsFilter aFilter,
@@ -562,23 +564,21 @@ protected:
   {
     /* will initilize the value if not set, else does nothing */
     GetCurrentFontStyle();
 
     return CurrentState().font;
   }
 
 #if USE_SKIA_GPU
-
-  // Recreate the DrawTarget in software mode
-  void Demote();
-
   static std::vector<CanvasRenderingContext2D*>& DemotableContexts();
   static void DemoteOldestContextIfNecessary();
+
   static void AddDemotableContext(CanvasRenderingContext2D* context);
+  static void RemoveDemotableContext(CanvasRenderingContext2D* context);
 
   // Do not use GL
   bool mForceSoftware;
 #endif
 
   // Member vars
   int32_t mWidth, mHeight;
 
--- a/content/canvas/src/WebGL2Context.cpp
+++ b/content/canvas/src/WebGL2Context.cpp
@@ -4,16 +4,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGL2Context.h"
 #include "mozilla/dom/WebGL2RenderingContextBinding.h"
 
 #include "mozilla/Telemetry.h"
 
 using namespace mozilla;
+using namespace mozilla::gl;
 
 // -----------------------------------------------------------------------------
 // CONSTRUCTOR & DESTRUCTOR
 
 WebGL2Context::WebGL2Context()
     : WebGLContext()
 {
     MOZ_ASSERT(IsSupported(), "not supposed to create a WebGL2Context"
@@ -46,8 +47,81 @@ WebGL2Context::Create()
 // IMPLEMENT nsWrapperCache
 
 JSObject*
 WebGL2Context::WrapObject(JSContext *cx, JS::Handle<JSObject*> scope)
 {
     return dom::WebGL2RenderingContextBinding::Wrap(cx, scope, this);
 }
 
+
+// -----------------------------------------------------------------------------
+// WebGL 2 initialisation
+
+bool
+WebGLContext::InitWebGL2()
+{
+    MOZ_ASSERT(IsWebGL2(), "WebGLContext is not a WebGL 2 context!");
+
+    const WebGLExtensionID sExtensionNativelySupportedArr[] = {
+        ANGLE_instanced_arrays,
+        OES_element_index_uint,
+        OES_standard_derivatives,
+        OES_texture_float,
+        OES_texture_float_linear,
+        OES_vertex_array_object,
+        WEBGL_depth_texture,
+        WEBGL_draw_buffers
+    };
+    const GLFeature::Enum sFeatureRequiredArr[] = {
+        GLFeature::blend_minmax,
+        GLFeature::transform_feedback
+    };
+
+    // check WebGL extensions that are supposed to be natively supported
+    for (size_t i = 0; i < size_t(MOZ_ARRAY_LENGTH(sExtensionNativelySupportedArr)); i++)
+    {
+        WebGLExtensionID extension = sExtensionNativelySupportedArr[i];
+
+        if (!IsExtensionSupported(extension)) {
+            GenerateWarning("WebGL 2 requires %s!", GetExtensionString(extension));
+            return false;
+        }
+    }
+
+    // check required OpenGL extensions
+    if (!gl->IsExtensionSupported(GLContext::EXT_gpu_shader4)) {
+        GenerateWarning("WebGL 2 requires GL_EXT_gpu_shader4!");
+        return false;
+    }
+
+    // check OpenGL features
+    if (!gl->IsSupported(GLFeature::occlusion_query) &&
+        !gl->IsSupported(GLFeature::occlusion_query_boolean))
+    {
+        /*
+         * on desktop, we fake occlusion_query_boolean with occlusion_query if
+         * necessary. See WebGLContextAsyncQueries.cpp.
+         */
+        GenerateWarning("WebGL 2 requires occlusion queries!");
+        return false;
+    }
+
+    for (size_t i = 0; i < size_t(MOZ_ARRAY_LENGTH(sFeatureRequiredArr)); i++)
+    {
+        if (!gl->IsSupported(sFeatureRequiredArr[i])) {
+            GenerateWarning("WebGL 2 requires GLFeature::%s!", GLContext::GetFeatureName(sFeatureRequiredArr[i]));
+            return false;
+        }
+    }
+
+    // ok WebGL 2 is compatible, we can enable natively supported extensions.
+    for (size_t i = 0; i < size_t(MOZ_ARRAY_LENGTH(sExtensionNativelySupportedArr)); i++) {
+        EnableExtension(sExtensionNativelySupportedArr[i]);
+
+        MOZ_ASSERT(IsExtensionEnabled(sExtensionNativelySupportedArr[i]));
+    }
+
+    // we initialise WebGL 2 related stuff.
+    gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &mGLMaxTransformFeedbackSeparateAttribs);
+
+    return true;
+}
--- a/content/canvas/src/WebGLContext.cpp
+++ b/content/canvas/src/WebGLContext.cpp
@@ -610,16 +610,17 @@ WebGLContext::Render(gfxContext *ctx, gf
     bool srcPremultAlpha = mOptions.premultipliedAlpha;
     bool dstPremultAlpha = aFlags & RenderFlagPremultAlpha;
 
     if (!srcPremultAlpha && dstPremultAlpha) {
         gfxUtils::PremultiplyImageSurface(surf);
     } else if (srcPremultAlpha && !dstPremultAlpha) {
         gfxUtils::UnpremultiplyImageSurface(surf);
     }
+    surf->MarkDirty();
 
     nsRefPtr<gfxPattern> pat = new gfxPattern(surf);
     pat->SetFilter(f);
 
     // Pixels from ReadPixels will be "upside down" compared to
     // what cairo wants, so draw with a y-flip and a translte to
     // flip them.
     gfxMatrix m;
@@ -908,21 +909,16 @@ WebGLContext::GetContextAttributes(Nulla
     result.mAlpha.Construct(format.alpha > 0);
     result.mDepth = format.depth > 0;
     result.mStencil = format.stencil > 0;
     result.mAntialias = format.samples > 1;
     result.mPremultipliedAlpha = mOptions.premultipliedAlpha;
     result.mPreserveDrawingBuffer = mOptions.preserveDrawingBuffer;
 }
 
-bool
-WebGLContext::IsExtensionEnabled(WebGLExtensionID ext) const {
-    return mExtensions.SafeElementAt(ext);
-}
-
 /* [noscript] DOMString mozGetUnderlyingParamString(in WebGLenum pname); */
 NS_IMETHODIMP
 WebGLContext::MozGetUnderlyingParamString(uint32_t pname, nsAString& retval)
 {
     if (!IsContextStable())
         return NS_OK;
 
     retval.SetIsVoid(true);
@@ -942,255 +938,16 @@ WebGLContext::MozGetUnderlyingParamStrin
 
     default:
         return NS_ERROR_INVALID_ARG;
     }
 
     return NS_OK;
 }
 
-bool WebGLContext::IsExtensionSupported(JSContext *cx, WebGLExtensionID ext) const
-{
-    // Chrome contexts need access to debug information even when
-    // webgl.disable-extensions is set. This is used in the graphics
-    // section of about:support.
-    if (xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) {
-        switch (ext) {
-            case WEBGL_debug_renderer_info:
-                return true;
-            default:
-                // For warnings-as-errors.
-                break;
-        }
-    }
-
-    return IsExtensionSupported(ext);
-}
-
-bool WebGLContext::IsExtensionSupported(WebGLExtensionID ext) const
-{
-    if (mDisableExtensions) {
-        return false;
-    }
-
-    switch (ext) {
-        case OES_element_index_uint:
-            return gl->IsSupported(GLFeature::element_index_uint);
-        case OES_standard_derivatives:
-            return gl->IsSupported(GLFeature::standard_derivatives);
-        case WEBGL_lose_context:
-            // We always support this extension.
-            return true;
-        case OES_texture_float:
-            return gl->IsSupported(GLFeature::texture_float);
-        case OES_texture_float_linear:
-            return gl->IsSupported(GLFeature::texture_float_linear);
-        case OES_vertex_array_object:
-            return WebGLExtensionVertexArray::IsSupported(this);
-        case EXT_texture_filter_anisotropic:
-            return gl->IsExtensionSupported(GLContext::EXT_texture_filter_anisotropic);
-        case WEBGL_compressed_texture_s3tc:
-            if (gl->IsExtensionSupported(GLContext::EXT_texture_compression_s3tc)) {
-                return true;
-            }
-            else if (gl->IsExtensionSupported(GLContext::EXT_texture_compression_dxt1) &&
-                     gl->IsExtensionSupported(GLContext::ANGLE_texture_compression_dxt3) &&
-                     gl->IsExtensionSupported(GLContext::ANGLE_texture_compression_dxt5))
-            {
-                return true;
-            }
-            return false;
-        case WEBGL_compressed_texture_atc:
-            return gl->IsExtensionSupported(GLContext::AMD_compressed_ATC_texture);
-        case WEBGL_compressed_texture_pvrtc:
-            return gl->IsExtensionSupported(GLContext::IMG_texture_compression_pvrtc);
-        case WEBGL_depth_texture:
-            return gl->IsSupported(GLFeature::packed_depth_stencil) &&
-                   gl->IsSupported(GLFeature::depth_texture);
-        case ANGLE_instanced_arrays:
-            return WebGLExtensionInstancedArrays::IsSupported(this);
-        default:
-            // For warnings-as-errors.
-            break;
-    }
-
-    if (Preferences::GetBool("webgl.enable-draft-extensions", false) || IsWebGL2()) {
-        switch (ext) {
-            case WEBGL_draw_buffers:
-                return WebGLExtensionDrawBuffers::IsSupported(this);
-            default:
-                // For warnings-as-errors.
-                break;
-        }
-    }
-
-    return false;
-}
-
-static bool
-CompareWebGLExtensionName(const nsACString& name, const char *other)
-{
-    return name.Equals(other, nsCaseInsensitiveCStringComparator());
-}
-
-JSObject*
-WebGLContext::GetExtension(JSContext *cx, const nsAString& aName, ErrorResult& rv)
-{
-    if (!IsContextStable())
-        return nullptr;
-
-    NS_LossyConvertUTF16toASCII name(aName);
-
-    WebGLExtensionID ext = WebGLExtensionID_unknown_extension;
-
-    // step 1: figure what extension is wanted
-    if (CompareWebGLExtensionName(name, "OES_element_index_uint"))
-    {
-        ext = OES_element_index_uint;
-    }
-    else if (CompareWebGLExtensionName(name, "OES_texture_float"))
-    {
-        ext = OES_texture_float;
-    }
-    else if (CompareWebGLExtensionName(name, "OES_texture_float_linear"))
-    {
-        ext = OES_texture_float_linear;
-    }
-    else if (CompareWebGLExtensionName(name, "OES_vertex_array_object"))
-    {
-        ext = OES_vertex_array_object;
-    }
-    else if (CompareWebGLExtensionName(name, "OES_standard_derivatives"))
-    {
-        ext = OES_standard_derivatives;
-    }
-    else if (CompareWebGLExtensionName(name, "EXT_texture_filter_anisotropic"))
-    {
-        ext = EXT_texture_filter_anisotropic;
-    }
-    else if (CompareWebGLExtensionName(name, "MOZ_WEBGL_lose_context"))
-    {
-        ext = WEBGL_lose_context;
-    }
-    else if (CompareWebGLExtensionName(name, "WEBGL_lose_context"))
-    {
-        ext = WEBGL_lose_context;
-    }
-    else if (CompareWebGLExtensionName(name, "MOZ_WEBGL_compressed_texture_s3tc"))
-    {
-        ext = WEBGL_compressed_texture_s3tc;
-    }
-    else if (CompareWebGLExtensionName(name, "WEBGL_compressed_texture_s3tc"))
-    {
-        ext = WEBGL_compressed_texture_s3tc;
-    }
-    else if (CompareWebGLExtensionName(name, "MOZ_WEBGL_compressed_texture_atc"))
-    {
-        ext = WEBGL_compressed_texture_atc;
-    }
-    else if (CompareWebGLExtensionName(name, "MOZ_WEBGL_compressed_texture_pvrtc"))
-    {
-        ext = WEBGL_compressed_texture_pvrtc;
-    }
-    else if (CompareWebGLExtensionName(name, "WEBGL_debug_renderer_info"))
-    {
-        ext = WEBGL_debug_renderer_info;
-    }
-    else if (CompareWebGLExtensionName(name, "MOZ_WEBGL_depth_texture"))
-    {
-        ext = WEBGL_depth_texture;
-    }
-    else if (CompareWebGLExtensionName(name, "WEBGL_depth_texture"))
-    {
-        ext = WEBGL_depth_texture;
-    }
-    else if (CompareWebGLExtensionName(name, "WEBGL_draw_buffers"))
-    {
-        ext = WEBGL_draw_buffers;
-    }
-    else if (CompareWebGLExtensionName(name, "ANGLE_instanced_arrays"))
-    {
-        ext = ANGLE_instanced_arrays;
-    }
-
-    if (ext == WebGLExtensionID_unknown_extension) {
-      return nullptr;
-    }
-
-    // step 2: check if the extension is supported
-    if (!IsExtensionSupported(cx, ext)) {
-        return nullptr;
-    }
-
-    // step 3: if the extension hadn't been previously been created, create it now, thus enabling it
-    if (!IsExtensionEnabled(ext)) {
-        EnableExtension(ext);
-    }
-
-    return WebGLObjectAsJSObject(cx, mExtensions[ext].get(), rv);
-}
-
-void
-WebGLContext::EnableExtension(WebGLExtensionID ext)
-{
-    mExtensions.EnsureLengthAtLeast(ext + 1);
-
-    MOZ_ASSERT(IsExtensionEnabled(ext) == false);
-
-    WebGLExtensionBase* obj = nullptr;
-    switch (ext) {
-        case OES_element_index_uint:
-            obj = new WebGLExtensionElementIndexUint(this);
-            break;
-        case OES_standard_derivatives:
-            obj = new WebGLExtensionStandardDerivatives(this);
-            break;
-        case EXT_texture_filter_anisotropic:
-            obj = new WebGLExtensionTextureFilterAnisotropic(this);
-            break;
-        case WEBGL_lose_context:
-            obj = new WebGLExtensionLoseContext(this);
-            break;
-        case WEBGL_compressed_texture_s3tc:
-            obj = new WebGLExtensionCompressedTextureS3TC(this);
-            break;
-        case WEBGL_compressed_texture_atc:
-            obj = new WebGLExtensionCompressedTextureATC(this);
-            break;
-        case WEBGL_compressed_texture_pvrtc:
-            obj = new WebGLExtensionCompressedTexturePVRTC(this);
-            break;
-        case WEBGL_debug_renderer_info:
-            obj = new WebGLExtensionDebugRendererInfo(this);
-            break;
-        case WEBGL_depth_texture:
-            obj = new WebGLExtensionDepthTexture(this);
-            break;
-        case OES_texture_float:
-            obj = new WebGLExtensionTextureFloat(this);
-            break;
-        case OES_texture_float_linear:
-            obj = new WebGLExtensionTextureFloatLinear(this);
-            break;
-        case WEBGL_draw_buffers:
-            obj = new WebGLExtensionDrawBuffers(this);
-            break;
-        case OES_vertex_array_object:
-            obj = new WebGLExtensionVertexArray(this);
-            break;
-        case ANGLE_instanced_arrays:
-            obj = new WebGLExtensionInstancedArrays(this);
-            break;
-        default:
-            MOZ_ASSERT(false, "should not get there.");
-    }
-
-    mExtensions[ext] = obj;
-}
-
 void
 WebGLContext::ClearScreen()
 {
     bool colorAttachmentsMask[WebGLContext::sMaxColorAttachments] = {false};
 
     MakeContextCurrent();
     ScopedBindFramebuffer autoFB(gl, 0);
 
@@ -1549,61 +1306,16 @@ WebGLContext::ForceLoseContext()
 }
 
 void
 WebGLContext::ForceRestoreContext()
 {
     mContextStatus = ContextLostAwaitingRestore;
 }
 
-void
-WebGLContext::GetSupportedExtensions(JSContext *cx, Nullable< nsTArray<nsString> > &retval)
-{
-    retval.SetNull();
-    if (!IsContextStable())
-        return;
-
-    nsTArray<nsString>& arr = retval.SetValue();
-
-    if (IsExtensionSupported(cx, OES_element_index_uint))
-        arr.AppendElement(NS_LITERAL_STRING("OES_element_index_uint"));
-    if (IsExtensionSupported(cx, OES_texture_float))
-        arr.AppendElement(NS_LITERAL_STRING("OES_texture_float"));
-    if (IsExtensionSupported(cx, OES_texture_float_linear))
-        arr.AppendElement(NS_LITERAL_STRING("OES_texture_float_linear"));
-    if (IsExtensionSupported(cx, OES_standard_derivatives))
-        arr.AppendElement(NS_LITERAL_STRING("OES_standard_derivatives"));
-    if (IsExtensionSupported(cx, EXT_texture_filter_anisotropic))
-        arr.AppendElement(NS_LITERAL_STRING("EXT_texture_filter_anisotropic"));
-    if (IsExtensionSupported(cx, WEBGL_lose_context))
-        arr.AppendElement(NS_LITERAL_STRING("MOZ_WEBGL_lose_context"));
-    if (IsExtensionSupported(cx, WEBGL_lose_context))
-        arr.AppendElement(NS_LITERAL_STRING("WEBGL_lose_context"));
-    if (IsExtensionSupported(cx, WEBGL_compressed_texture_s3tc))
-        arr.AppendElement(NS_LITERAL_STRING("MOZ_WEBGL_compressed_texture_s3tc"));
-    if (IsExtensionSupported(cx, WEBGL_compressed_texture_s3tc))
-        arr.AppendElement(NS_LITERAL_STRING("WEBGL_compressed_texture_s3tc"));
-    if (IsExtensionSupported(cx, WEBGL_compressed_texture_atc))
-        arr.AppendElement(NS_LITERAL_STRING("MOZ_WEBGL_compressed_texture_atc"));
-    if (IsExtensionSupported(cx, WEBGL_compressed_texture_pvrtc))
-        arr.AppendElement(NS_LITERAL_STRING("MOZ_WEBGL_compressed_texture_pvrtc"));
-    if (IsExtensionSupported(cx, WEBGL_debug_renderer_info))
-        arr.AppendElement(NS_LITERAL_STRING("WEBGL_debug_renderer_info"));
-    if (IsExtensionSupported(cx, WEBGL_depth_texture))
-        arr.AppendElement(NS_LITERAL_STRING("MOZ_WEBGL_depth_texture"));
-    if (IsExtensionSupported(cx, WEBGL_depth_texture))
-        arr.AppendElement(NS_LITERAL_STRING("WEBGL_depth_texture"));
-    if (IsExtensionSupported(cx, WEBGL_draw_buffers))
-        arr.AppendElement(NS_LITERAL_STRING("WEBGL_draw_buffers"));
-    if (IsExtensionSupported(cx, OES_vertex_array_object))
-        arr.AppendElement(NS_LITERAL_STRING("OES_vertex_array_object"));
-    if (IsExtensionSupported(cx, ANGLE_instanced_arrays))
-        arr.AppendElement(NS_LITERAL_STRING("ANGLE_instanced_arrays"));
-}
-
 //
 // XPCOM goop
 //
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(WebGLContext)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(WebGLContext)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_12(WebGLContext,
--- a/content/canvas/src/WebGLContext.h
+++ b/content/canvas/src/WebGLContext.h
@@ -144,18 +144,16 @@ public:
     NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(WebGLContext,
                                                            nsIDOMWebGLRenderingContext)
 
     virtual JSObject* WrapObject(JSContext *cx,
                                  JS::Handle<JSObject*> scope) = 0;
 
-    virtual bool IsWebGL2() const = 0;
-
     NS_DECL_NSIDOMWEBGLRENDERINGCONTEXT
 
     // nsICanvasRenderingContextInternal
     NS_IMETHOD SetDimensions(int32_t width, int32_t height) MOZ_OVERRIDE;
     NS_IMETHOD InitializeWithSurface(nsIDocShell *docShell, gfxASurface *surface, int32_t width, int32_t height) MOZ_OVERRIDE
         { return NS_ERROR_NOT_IMPLEMENTED; }
     NS_IMETHOD Reset() MOZ_OVERRIDE
         { /* (InitializeWithSurface) */ return NS_ERROR_NOT_IMPLEMENTED; }
@@ -956,48 +954,61 @@ protected:
         // informing it of this.
         ContextLost,
         // The context is lost, an event has been sent to the script, and the
         // script correctly handled the event. We are waiting for the context to
         // be restored.
         ContextLostAwaitingRestore
     };
 
-    // extensions
+    // -------------------------------------------------------------------------
+    // WebGL extensions (implemented in WebGLContextExtensions.cpp)
     enum WebGLExtensionID {
         EXT_texture_filter_anisotropic,
         OES_element_index_uint,
         OES_standard_derivatives,
         OES_texture_float,
         OES_texture_float_linear,
         OES_vertex_array_object,
         WEBGL_compressed_texture_atc,
         WEBGL_compressed_texture_pvrtc,
         WEBGL_compressed_texture_s3tc,
         WEBGL_debug_renderer_info,
         WEBGL_depth_texture,
         WEBGL_lose_context,
         WEBGL_draw_buffers,
         ANGLE_instanced_arrays,
+        WebGLExtensionID_max,
         WebGLExtensionID_unknown_extension
     };
     nsTArray<nsRefPtr<WebGLExtensionBase> > mExtensions;
 
     // enable an extension. the extension should not be enabled before.
     void EnableExtension(WebGLExtensionID ext);
 
     // returns true if the extension has been enabled by calling getExtension.
     bool IsExtensionEnabled(WebGLExtensionID ext) const;
 
     // returns true if the extension is supported for this JSContext (this decides what getSupportedExtensions exposes)
     bool IsExtensionSupported(JSContext *cx, WebGLExtensionID ext) const;
     bool IsExtensionSupported(WebGLExtensionID ext) const;
 
+    static const char* GetExtensionString(WebGLExtensionID ext);
+
     nsTArray<WebGLenum> mCompressedTextureFormats;
 
+
+    // -------------------------------------------------------------------------
+    // WebGL 2 specifics (implemented in WebGL2Context.cpp)
+
+    virtual bool IsWebGL2() const = 0;
+
+    bool InitWebGL2();
+
+
     // -------------------------------------------------------------------------
     // Validation functions (implemented in WebGLContextValidate.cpp)
     bool InitAndValidateGL();
     bool ValidateBlendEquationEnum(WebGLenum cap, const char *info);
     bool ValidateBlendFuncDstEnum(WebGLenum mode, const char *info);
     bool ValidateBlendFuncSrcEnum(WebGLenum mode, const char *info);
     bool ValidateBlendFuncEnumsCompatibility(WebGLenum sfactor, WebGLenum dfactor, const char *info);
     bool ValidateTextureTargetEnum(WebGLenum target, const char *info);
--- a/content/canvas/src/WebGLContextBuffers.cpp
+++ b/content/canvas/src/WebGLContextBuffers.cpp
@@ -54,31 +54,36 @@ WebGLContext::BindBufferBase(WebGLenum t
     if (!ValidateObjectAllowDeletedOrNull("bindBufferBase", buffer))
         return;
 
     // silently ignore a deleted buffer
     if (buffer && buffer->IsDeleted()) {
         return;
     }
 
-    WebGLRefPtr<WebGLBuffer>* bufferSlot = GetBufferSlotByTargetIndexed(target, index, "bindBufferBase");
+    WebGLRefPtr<WebGLBuffer>* indexedBufferSlot = GetBufferSlotByTargetIndexed(target, index, "bindBufferBase");
 
-    if (!bufferSlot) {
+    if (!indexedBufferSlot) {
         return;
     }
 
     if (buffer) {
         if (!buffer->Target()) {
             buffer->SetTarget(target);
             buffer->SetHasEverBeenBound(true);
         } else if (target != buffer->Target()) {
             return ErrorInvalidOperation("bindBuffer: buffer already bound to a different target");
         }
     }
 
+    WebGLRefPtr<WebGLBuffer>* bufferSlot = GetBufferSlotByTarget(target, "bindBuffer");
+
+    MOZ_ASSERT(bufferSlot, "GetBufferSlotByTarget(Indexed) mismatch");
+
+    *indexedBufferSlot = buffer;
     *bufferSlot = buffer;
 
     MakeContextCurrent();
 
     gl->fBindBufferBase(target, index, buffer ? buffer->GLName() : 0);
 }
 
 void
@@ -90,31 +95,36 @@ WebGLContext::BindBufferRange(WebGLenum 
 
     if (!ValidateObjectAllowDeletedOrNull("bindBufferRange", buffer))
         return;
 
     // silently ignore a deleted buffer
     if (buffer && buffer->IsDeleted())
         return;
 
-    WebGLRefPtr<WebGLBuffer>* bufferSlot = GetBufferSlotByTargetIndexed(target, index, "bindBufferBase");
+    WebGLRefPtr<WebGLBuffer>* indexedBufferSlot = GetBufferSlotByTargetIndexed(target, index, "bindBufferBase");
 
-    if (!bufferSlot) {
+    if (!indexedBufferSlot) {
         return;
     }
 
     if (buffer) {
         if (!buffer->Target()) {
             buffer->SetTarget(target);
             buffer->SetHasEverBeenBound(true);
         } else if (target != buffer->Target()) {
             return ErrorInvalidOperation("bindBuffer: buffer already bound to a different target");
         }
     }
 
+    WebGLRefPtr<WebGLBuffer>* bufferSlot = GetBufferSlotByTarget(target, "bindBuffer");
+
+    MOZ_ASSERT(bufferSlot, "GetBufferSlotByTarget(Indexed) mismatch");
+
+    *indexedBufferSlot = buffer;
     *bufferSlot = buffer;
 
     MakeContextCurrent();
 
     gl->fBindBufferRange(target, index, buffer ? buffer->GLName() : 0, offset, size);
 }
 
 void
new file mode 100644
--- /dev/null
+++ b/content/canvas/src/WebGLContextExtensions.cpp
@@ -0,0 +1,290 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* 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/. */
+
+#include "WebGLContext.h"
+#include "WebGLContextUtils.h"
+#include "WebGLExtensions.h"
+#include "GLContext.h"
+
+#include "nsString.h"
+
+#include "AccessCheck.h"
+
+using namespace mozilla;
+using namespace mozilla::gl;
+
+// must match WebGLContext::WebGLExtensionID
+static const char *sExtensionNames[] = {
+    "EXT_texture_filter_anisotropic",
+    "OES_element_index_uint",
+    "OES_standard_derivatives",
+    "OES_texture_float",
+    "OES_texture_float_linear",
+    "OES_vertex_array_object",
+    "WEBGL_compressed_texture_atc",
+    "WEBGL_compressed_texture_pvrtc",
+    "WEBGL_compressed_texture_s3tc",
+    "WEBGL_debug_renderer_info",
+    "WEBGL_depth_texture",
+    "WEBGL_lose_context",
+    "WEBGL_draw_buffers",
+    "ANGLE_instanced_arrays"
+};
+
+/* static */ const char*
+WebGLContext::GetExtensionString(WebGLExtensionID ext)
+{
+    static_assert(MOZ_ARRAY_LENGTH(sExtensionNames) == size_t(WebGLExtensionID_max),
+                  "Mismatched lengths for sFeatureInfoInfos and GLFeature enums");
+
+    MOZ_ASSERT(ext < WebGLExtensionID_max, "unknown extension!");
+
+    return sExtensionNames[ext];
+}
+
+bool
+WebGLContext::IsExtensionEnabled(WebGLExtensionID ext) const {
+    return mExtensions.SafeElementAt(ext);
+}
+
+bool WebGLContext::IsExtensionSupported(JSContext *cx, WebGLExtensionID ext) const
+{
+    // Chrome contexts need access to debug information even when
+    // webgl.disable-extensions is set. This is used in the graphics
+    // section of about:support.
+    if (xpc::AccessCheck::isChrome(js::GetContextCompartment(cx))) {
+        switch (ext) {
+            case WEBGL_debug_renderer_info:
+                return true;
+            default:
+                // For warnings-as-errors.
+                break;
+        }
+    }
+
+    return IsExtensionSupported(ext);
+}
+
+bool WebGLContext::IsExtensionSupported(WebGLExtensionID ext) const
+{
+    if (mDisableExtensions) {
+        return false;
+    }
+
+    switch (ext) {
+        case OES_element_index_uint:
+            return gl->IsSupported(GLFeature::element_index_uint);
+        case OES_standard_derivatives:
+            return gl->IsSupported(GLFeature::standard_derivatives);
+        case WEBGL_lose_context:
+            // We always support this extension.
+            return true;
+        case OES_texture_float:
+            return gl->IsSupported(GLFeature::texture_float);
+        case OES_texture_float_linear:
+            return gl->IsSupported(GLFeature::texture_float_linear);
+        case OES_vertex_array_object:
+            return WebGLExtensionVertexArray::IsSupported(this);
+        case EXT_texture_filter_anisotropic:
+            return gl->IsExtensionSupported(GLContext::EXT_texture_filter_anisotropic);
+        case WEBGL_compressed_texture_s3tc:
+            if (gl->IsExtensionSupported(GLContext::EXT_texture_compression_s3tc)) {
+                return true;
+            }
+            else if (gl->IsExtensionSupported(GLContext::EXT_texture_compression_dxt1) &&
+                     gl->IsExtensionSupported(GLContext::ANGLE_texture_compression_dxt3) &&
+                     gl->IsExtensionSupported(GLContext::ANGLE_texture_compression_dxt5))
+            {
+                return true;
+            }
+            return false;
+        case WEBGL_compressed_texture_atc:
+            return gl->IsExtensionSupported(GLContext::AMD_compressed_ATC_texture);
+        case WEBGL_compressed_texture_pvrtc:
+            return gl->IsExtensionSupported(GLContext::IMG_texture_compression_pvrtc);
+        case WEBGL_depth_texture:
+            return gl->IsSupported(GLFeature::packed_depth_stencil) &&
+            gl->IsSupported(GLFeature::depth_texture);
+        case ANGLE_instanced_arrays:
+            return WebGLExtensionInstancedArrays::IsSupported(this);
+        default:
+            // For warnings-as-errors.
+            break;
+    }
+
+    if (Preferences::GetBool("webgl.enable-draft-extensions", false) || IsWebGL2()) {
+        switch (ext) {
+            case WEBGL_draw_buffers:
+                return WebGLExtensionDrawBuffers::IsSupported(this);
+            default:
+                // For warnings-as-errors.
+                break;
+        }
+    }
+
+    return false;
+}
+
+static bool
+CompareWebGLExtensionName(const nsACString& name, const char *other)
+{
+    return name.Equals(other, nsCaseInsensitiveCStringComparator());
+}
+
+JSObject*
+WebGLContext::GetExtension(JSContext *cx, const nsAString& aName, ErrorResult& rv)
+{
+    if (!IsContextStable())
+        return nullptr;
+
+    NS_LossyConvertUTF16toASCII name(aName);
+
+    WebGLExtensionID ext = WebGLExtensionID_unknown_extension;
+
+    // step 1: figure what extension is wanted
+    for (size_t i = 0; i < size_t(WebGLExtensionID_max); i++)
+    {
+        WebGLExtensionID extension = WebGLExtensionID(i);
+        
+        if (CompareWebGLExtensionName(name, GetExtensionString(extension))) {
+            ext = extension;
+            break;
+        }
+    }
+
+    if (ext == WebGLExtensionID_unknown_extension)
+    {
+        /**
+         * We keep backward compatibility for these deprecated vendor-prefixed
+         * alias. Do not add new ones anymore. Hide it behind the
+         * webgl.enable-draft-extensions flag instead.
+         */
+        if (CompareWebGLExtensionName(name, "MOZ_WEBGL_lose_context")) {
+            ext = WEBGL_lose_context;
+        }
+        else if (CompareWebGLExtensionName(name, "MOZ_WEBGL_compressed_texture_s3tc")) {
+            ext = WEBGL_compressed_texture_s3tc;
+        }
+        else if (CompareWebGLExtensionName(name, "MOZ_WEBGL_compressed_texture_atc")) {
+            ext = WEBGL_compressed_texture_atc;
+        }
+        else if (CompareWebGLExtensionName(name, "MOZ_WEBGL_compressed_texture_pvrtc")) {
+            ext = WEBGL_compressed_texture_pvrtc;
+        }
+        else if (CompareWebGLExtensionName(name, "MOZ_WEBGL_depth_texture")) {
+            ext = WEBGL_depth_texture;
+        }
+    }
+
+    if (ext == WebGLExtensionID_unknown_extension) {
+        return nullptr;
+    }
+
+    // step 2: check if the extension is supported
+    if (!IsExtensionSupported(cx, ext)) {
+        return nullptr;
+    }
+
+    // step 3: if the extension hadn't been previously been created, create it now, thus enabling it
+    if (!IsExtensionEnabled(ext)) {
+        EnableExtension(ext);
+    }
+
+    return WebGLObjectAsJSObject(cx, mExtensions[ext].get(), rv);
+}
+
+void
+WebGLContext::EnableExtension(WebGLExtensionID ext)
+{
+    mExtensions.EnsureLengthAtLeast(ext + 1);
+
+    MOZ_ASSERT(IsExtensionEnabled(ext) == false);
+
+    WebGLExtensionBase* obj = nullptr;
+    switch (ext) {
+        case OES_element_index_uint:
+            obj = new WebGLExtensionElementIndexUint(this);
+            break;
+        case OES_standard_derivatives:
+            obj = new WebGLExtensionStandardDerivatives(this);
+            break;
+        case EXT_texture_filter_anisotropic:
+            obj = new WebGLExtensionTextureFilterAnisotropic(this);
+            break;
+        case WEBGL_lose_context:
+            obj = new WebGLExtensionLoseContext(this);
+            break;
+        case WEBGL_compressed_texture_s3tc:
+            obj = new WebGLExtensionCompressedTextureS3TC(this);
+            break;
+        case WEBGL_compressed_texture_atc:
+            obj = new WebGLExtensionCompressedTextureATC(this);
+            break;
+        case WEBGL_compressed_texture_pvrtc:
+            obj = new WebGLExtensionCompressedTexturePVRTC(this);
+            break;
+        case WEBGL_debug_renderer_info:
+            obj = new WebGLExtensionDebugRendererInfo(this);
+            break;
+        case WEBGL_depth_texture:
+            obj = new WebGLExtensionDepthTexture(this);
+            break;
+        case OES_texture_float:
+            obj = new WebGLExtensionTextureFloat(this);
+            break;
+        case OES_texture_float_linear:
+            obj = new WebGLExtensionTextureFloatLinear(this);
+            break;
+        case WEBGL_draw_buffers:
+            obj = new WebGLExtensionDrawBuffers(this);
+            break;
+        case OES_vertex_array_object:
+            obj = new WebGLExtensionVertexArray(this);
+            break;
+        case ANGLE_instanced_arrays:
+            obj = new WebGLExtensionInstancedArrays(this);
+            break;
+        default:
+            MOZ_ASSERT(false, "should not get there.");
+    }
+
+    mExtensions[ext] = obj;
+}
+
+void
+WebGLContext::GetSupportedExtensions(JSContext *cx, Nullable< nsTArray<nsString> > &retval)
+{
+    retval.SetNull();
+    if (!IsContextStable())
+        return;
+
+    nsTArray<nsString>& arr = retval.SetValue();
+
+    for (size_t i = 0; i < size_t(WebGLExtensionID_max); i++)
+    {
+        WebGLExtensionID extension = WebGLExtensionID(i);
+
+        if (IsExtensionSupported(cx, extension)) {
+            arr.AppendElement(NS_ConvertUTF8toUTF16(GetExtensionString(extension)));
+        }
+    }
+
+    /**
+     * We keep backward compatibility for these deprecated vendor-prefixed
+     * alias. Do not add new ones anymore. Hide it behind the
+     * webgl.enable-draft-extensions flag instead.
+     */
+    if (IsExtensionSupported(cx, WEBGL_lose_context))
+        arr.AppendElement(NS_LITERAL_STRING("MOZ_WEBGL_lose_context"));
+    if (IsExtensionSupported(cx, WEBGL_compressed_texture_s3tc))
+        arr.AppendElement(NS_LITERAL_STRING("MOZ_WEBGL_compressed_texture_s3tc"));
+    if (IsExtensionSupported(cx, WEBGL_compressed_texture_atc))
+        arr.AppendElement(NS_LITERAL_STRING("MOZ_WEBGL_compressed_texture_atc"));
+    if (IsExtensionSupported(cx, WEBGL_compressed_texture_pvrtc))
+        arr.AppendElement(NS_LITERAL_STRING("MOZ_WEBGL_compressed_texture_pvrtc"));
+    if (IsExtensionSupported(cx, WEBGL_depth_texture))
+        arr.AppendElement(NS_LITERAL_STRING("MOZ_WEBGL_depth_texture"));
+}
+
--- a/content/canvas/src/WebGLContextValidate.cpp
+++ b/content/canvas/src/WebGLContextValidate.cpp
@@ -906,20 +906,16 @@ WebGLContext::InitAndValidateGL()
                     break;
                 default:
                     GenerateWarning("GL error 0x%x occurred during WebGL context initialization!", error);
                     return false;
             }
         }
     }
 
-    if (IsWebGL2()) {
-        gl->GetUIntegerv(LOCAL_GL_MAX_TRANSFORM_FEEDBACK_SEPARATE_ATTRIBS, &mGLMaxTransformFeedbackSeparateAttribs);
-    }
-
     // Always 1 for GLES2
     mMaxFramebufferColorAttachments = 1;
 
     if (!gl->IsGLES2()) {
         // gl_PointSize is always available in ES2 GLSL, but has to be
         // specifically enabled on desktop GLSL.
         gl->fEnable(LOCAL_GL_VERTEX_PROGRAM_POINT_SIZE);
 
@@ -961,24 +957,17 @@ WebGLContext::InitAndValidateGL()
     // it is also to reset the error flags so that a subsequent WebGL getError call will give the correct result.
     error = gl->GetAndClearError();
     if (error != LOCAL_GL_NO_ERROR) {
         GenerateWarning("GL error 0x%x occurred during WebGL context initialization!", error);
         return false;
     }
 
     if (IsWebGL2() &&
-        (!IsExtensionSupported(OES_vertex_array_object) ||
-         !IsExtensionSupported(WEBGL_draw_buffers) ||
-         !IsExtensionSupported(ANGLE_instanced_arrays) ||
-         !gl->IsExtensionSupported(gl::GLContext::EXT_gpu_shader4) ||
-         !gl->IsExtensionSupported(gl::GLContext::EXT_blend_minmax) ||
-         (!gl->IsSupported(gl::GLFeature::occlusion_query) &&
-          !gl->IsSupported(gl::GLFeature::occlusion_query_boolean))
-        ))
+        !InitWebGL2())
     {
         // Todo: Bug 898404: Only allow WebGL2 on GL>=3.0 on desktop GL.
         return false;
     }
 
     mMemoryPressureObserver
         = new WebGLMemoryPressureObserver(this);
     nsCOMPtr<nsIObserverService> observerService
@@ -988,20 +977,10 @@ WebGLContext::InitAndValidateGL()
                                      "memory-pressure",
                                      false);
     }
 
     mDefaultVertexArray = new WebGLVertexArray(this);
     mDefaultVertexArray->mAttribBuffers.SetLength(mGLMaxVertexAttribs);
     mBoundVertexArray = mDefaultVertexArray;
 
-    if (IsWebGL2()) {
-        EnableExtension(OES_vertex_array_object);
-        EnableExtension(WEBGL_draw_buffers);
-        EnableExtension(ANGLE_instanced_arrays);
-
-        MOZ_ASSERT(IsExtensionEnabled(OES_vertex_array_object));
-        MOZ_ASSERT(IsExtensionEnabled(WEBGL_draw_buffers));
-        MOZ_ASSERT(IsExtensionEnabled(ANGLE_instanced_arrays));
-    }
-
     return true;
 }
--- a/content/canvas/src/moz.build
+++ b/content/canvas/src/moz.build
@@ -33,16 +33,17 @@ if CONFIG['MOZ_WEBGL']:
         'WebGLContext.cpp',
         'WebGLContextAsyncQueries.cpp',
         'WebGLContextBuffers.cpp',
         'WebGLContextGL.cpp',
         'WebGLContextUtils.cpp',
         'WebGLContextReporter.cpp',
         'WebGLContextState.cpp',
         'WebGLContextValidate.cpp',
+        'WebGLContextExtensions.cpp',
         'WebGLContextFramebufferOperations.cpp',
         'WebGLContextVertexArray.cpp',
         'WebGLContextVertices.cpp',
         'WebGLElementArrayCache.cpp',
         'WebGLExtensionBase.cpp',
         'WebGLExtensionCompressedTextureATC.cpp',
         'WebGLExtensionCompressedTexturePVRTC.cpp',
         'WebGLExtensionCompressedTextureS3TC.cpp',
--- a/content/canvas/test/Makefile.in
+++ b/content/canvas/test/Makefile.in
@@ -83,16 +83,17 @@ MOCHITEST_FILES = \
 	test_bug856472.html \
 	test_bug866575.html \
 	test_drawImage_edge_cases.html \
 	test_drawImage_document_domain.html \
 	test_mozDashOffset.html \
 	file_drawImage_document_domain.html \
 	test_windingRuleUndefined.html \
 	test_strokeText_throw.html \
+	test_bug902651.html \
 	$(NULL)
 
 # SkiaGL on Android/Gonk does not implement these composite ops yet
 
 MOCHITEST_FILES += \
 	test_2d.composite.canvas.color-burn.html \
 	test_2d.composite.canvas.color-dodge.html \
 	test_2d.composite.canvas.darken.html \
new file mode 100644
--- /dev/null
+++ b/content/canvas/test/test_bug902651.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>
+<title>Canvas test: canvas demotion</title>
+<script src="/MochiKit/MochiKit.js"></script>
+<script src="/tests/SimpleTest/SimpleTest.js"></script>
+<link rel="stylesheet" href="/tests/SimpleTest/test.css">
+<body>
+<canvas id="c" width="100" height="50"><p class="fallback">FAIL (fallback content)</p></canvas>
+<script>
+
+SimpleTest.waitForExplicitFinish();
+addLoadEvent(function () {
+
+var canvas = document.getElementById('c');
+var ctx = canvas.getContext('2d');
+
+ctx.fillStyle = 'rgb(50, 50, 50)';
+ctx.fillRect(0, 0, 100, 50);
+ctx.translate(25, 25);
+
+SpecialPowers.wrap(ctx).demote();
+
+setTimeout(function() {
+  ctx.fillStyle = 'rgb(127, 127, 127)';
+  ctx.fillRect(0, 0, 10, 10);
+
+  var pixels = ctx.getImageData(0, 0, 1, 1);
+
+  ok(pixels.data[0] === 50, "pixels.data[0] expected 50, got " + pixels.data[0]);
+  ok(pixels.data[1] === 50, "pixels.data[1] expected 50, got " + pixels.data[1]);
+  ok(pixels.data[2] === 50, "pixels.data[2] expected 50, got " + pixels.data[2]);
+
+  pixels = ctx.getImageData(25, 25, 1, 1);
+
+  ok(pixels.data[0] === 127, "pixels.data[0] expected 127, got " + pixels.data[0]);
+  ok(pixels.data[1] === 127, "pixels.data[1] expected 127, got " + pixels.data[1]);
+  ok(pixels.data[2] === 127, "pixels.data[2] expected 127, got " + pixels.data[2]);
+
+  SimpleTest.finish();
+}, 50);
+
+
+});
+</script>
+
--- a/content/html/document/src/nsHTMLContentSink.cpp
+++ b/content/html/document/src/nsHTMLContentSink.cpp
@@ -72,35 +72,24 @@
 #include "nsStyleLinkElement.h"
 
 #include "nsWeakReference.h" // nsHTMLElementFactory supports weak references
 #include "nsIPrompt.h"
 #include "nsLayoutCID.h"
 #include "nsIDocShellTreeItem.h"
 
 #include "nsEscape.h"
-#include "nsIElementObserver.h"
 #include "nsNodeInfoManager.h"
 #include "nsContentCreatorFunctions.h"
 #include "mozAutoDocUpdate.h"
 #include "nsTextNode.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
-#ifdef DEBUG
-static PRLogModuleInfo* gSinkLogModuleInfo;
-
-#define SINK_TRACE_NODE(_bit, _msg, _tag, _sp, _obj) \
-  _obj->SinkTraceNode(_bit, _msg, _tag, _sp, this)
-
-#else
-#define SINK_TRACE_NODE(_bit, _msg, _tag, _sp, _obj)
-#endif
-
 //----------------------------------------------------------------------
 
 typedef nsGenericHTMLElement*
   (*contentCreatorCallback)(already_AddRefed<nsINodeInfo>,
                             FromParser aFromParser);
 
 nsGenericHTMLElement*
 NS_NewHTMLNOTUSEDElement(already_AddRefed<nsINodeInfo> aNodeInfo,
@@ -125,19 +114,16 @@ static const contentCreatorCallback sCon
 class SinkContext;
 class HTMLContentSink;
 
 /**
  * This class is near-OBSOLETE. It is used for about:blank only.
  * Don't bother adding new stuff in this file.
  */
 class HTMLContentSink : public nsContentSink,
-#ifdef DEBUG
-                        public nsIDebugDumpContent,
-#endif
                         public nsIHTMLContentSink
 {
 public:
   friend class SinkContext;
 
   HTMLContentSink();
   virtual ~HTMLContentSink();
 
@@ -158,50 +144,20 @@ public:
   NS_IMETHOD WillResume(void);
   NS_IMETHOD SetParser(nsParserBase* aParser);
   virtual void FlushPendingNotifications(mozFlushType aType);
   NS_IMETHOD SetDocumentCharset(nsACString& aCharset);
   virtual nsISupports *GetTarget();
   virtual bool IsScriptExecuting();
 
   // nsIHTMLContentSink
-  NS_IMETHOD OpenContainer(const nsIParserNode& aNode);
-  NS_IMETHOD CloseContainer(const nsHTMLTag aTag);
-  NS_IMETHOD CloseMalformedContainer(const nsHTMLTag aTag);
-  NS_IMETHOD AddLeaf(const nsIParserNode& aNode);
-  NS_IMETHOD DidProcessTokens(void);
-  NS_IMETHOD WillProcessAToken(void);
-  NS_IMETHOD DidProcessAToken(void);
-  NS_IMETHOD BeginContext(int32_t aID);
-  NS_IMETHOD EndContext(int32_t aID);
-  NS_IMETHOD OpenHead();
-  NS_IMETHOD IsEnabled(int32_t aTag, bool* aReturn);
-
-#ifdef DEBUG
-  // nsIDebugDumpContent
-  NS_IMETHOD DumpContentModel();
-#endif
+  NS_IMETHOD OpenContainer(ElementType aNodeType);
+  NS_IMETHOD CloseContainer(ElementType aTag);
 
 protected:
-  // If aCheckIfPresent is true, will only set an attribute in cases
-  // when it's not already set.
-  nsresult AddAttributes(const nsIParserNode& aNode, nsIContent* aContent,
-                         bool aNotify = false,
-                         bool aCheckIfPresent = false);
-  already_AddRefed<nsGenericHTMLElement>
-  CreateContentObject(const nsIParserNode& aNode, nsHTMLTag aNodeType);
-
-#ifdef DEBUG
-  void SinkTraceNode(uint32_t aBit,
-                     const char* aMsg,
-                     const nsHTMLTag aTag,
-                     int32_t aStackPos,
-                     void* aThis);
-#endif
-
   nsCOMPtr<nsIHTMLDocument> mHTMLDocument;
 
   // The maximum length of a text run
   int32_t mMaxTextRun;
 
   nsRefPtr<nsGenericHTMLElement> mRoot;
   nsRefPtr<nsGenericHTMLElement> mBody;
   nsRefPtr<nsGenericHTMLElement> mHead;
@@ -223,241 +179,74 @@ protected:
   uint8_t unused : 6;  // bits available if someone needs one
 
   nsINodeInfo* mNodeInfoCache[NS_HTML_TAG_MAX + 1];
 
   nsresult FlushTags();
 
   // Routines for tags that require special handling
   nsresult CloseHTML();
-  nsresult OpenBody(const nsIParserNode& aNode);
+  nsresult OpenBody();
   nsresult CloseBody();
 
-  nsresult OpenHeadContext();
   void CloseHeadContext();
 
   // nsContentSink overrides
   void UpdateChildCounts();
 
   void NotifyInsert(nsIContent* aContent,
                     nsIContent* aChildContent,
                     int32_t aIndexInContainer);
   void NotifyRootInsertion();
-  
-  bool IsMonolithicContainer(nsHTMLTag aTag);
-
-#ifdef DEBUG
-  void ForceReflow();
-#endif
 };
 
 class SinkContext
 {
 public:
   SinkContext(HTMLContentSink* aSink);
   ~SinkContext();
 
   nsresult Begin(nsHTMLTag aNodeType, nsGenericHTMLElement* aRoot,
                  uint32_t aNumFlushed, int32_t aInsertionPoint);
-  nsresult OpenContainer(const nsIParserNode& aNode);
-  nsresult CloseContainer(const nsHTMLTag aTag);
-  nsresult AddLeaf(const nsIParserNode& aNode);
-  nsresult AddLeaf(nsIContent* aContent);
+  nsresult OpenBody();
+  nsresult CloseBody();
   nsresult End();
 
   nsresult GrowStack();
-  nsresult AddText(const nsAString& aText);
-  nsresult FlushText(bool* aDidFlush = nullptr,
-                     bool aReleaseLast = false);
-  nsresult FlushTextAndRelease(bool* aDidFlush = nullptr)
-  {
-    return FlushText(aDidFlush, true);
-  }
-
   nsresult FlushTags();
 
   bool     IsCurrentContainer(nsHTMLTag mType);
 
   void DidAddContent(nsIContent* aContent);
   void UpdateChildCounts();
 
 private:
   // Function to check whether we've notified for the current content.
   // What this actually does is check whether we've notified for all
   // of the parent's kids.
   bool HaveNotifiedForCurrentContent() const;
   
 public:
   HTMLContentSink* mSink;
   int32_t mNotifyLevel;
-  nsCOMPtr<nsIContent> mLastTextNode;
-  int32_t mLastTextNodeSize;
 
   struct Node {
     nsHTMLTag mType;
     nsGenericHTMLElement* mContent;
     uint32_t mNumFlushed;
     int32_t mInsertionPoint;
 
     nsIContent *Add(nsIContent *child);
   };
 
   Node* mStack;
   int32_t mStackSize;
   int32_t mStackPos;
-
-  PRUnichar* mText;
-  int32_t mTextLength;
-  int32_t mTextSize;
-
-private:
-  bool mLastTextCharWasCR;
 };
 
-//----------------------------------------------------------------------
-
-#ifdef DEBUG
-void
-HTMLContentSink::SinkTraceNode(uint32_t aBit,
-                               const char* aMsg,
-                               const nsHTMLTag aTag,
-                               int32_t aStackPos,
-                               void* aThis)
-{
-  if (SINK_LOG_TEST(gSinkLogModuleInfo, aBit)) {
-    nsIParserService *parserService = nsContentUtils::GetParserService();
-    if (!parserService)
-      return;
-
-    NS_ConvertUTF16toUTF8 tag(parserService->HTMLIdToStringTag(aTag));
-    PR_LogPrint("%s: this=%p node='%s' stackPos=%d", 
-                aMsg, aThis, tag.get(), aStackPos);
-  }
-}
-#endif
-
-nsresult
-HTMLContentSink::AddAttributes(const nsIParserNode& aNode,
-                               nsIContent* aContent, bool aNotify,
-                               bool aCheckIfPresent)
-{
-  // Add tag attributes to the content attributes
-
-  int32_t ac = aNode.GetAttributeCount();
-
-  if (ac == 0) {
-    // No attributes, nothing to do. Do an early return to avoid
-    // constructing the nsAutoString object for nothing.
-
-    return NS_OK;
-  }
-
-  nsHTMLTag nodeType = nsHTMLTag(aNode.GetNodeType());
-
-  // The attributes are on the parser node in the order they came in in the
-  // source.  What we want to happen if a single attribute is set multiple
-  // times on an element is that the first time should "win".  That is, <input
-  // value="foo" value="bar"> should show "foo".  So we loop over the
-  // attributes backwards; this ensures that the first attribute in the set
-  // wins.  This does mean that we do some extra work in the case when the same
-  // attribute is set multiple times, but we save a HasAttr call in the much
-  // more common case of reasonable HTML.  Note that if aCheckIfPresent is set
-  // then we actually want to loop _forwards_ to preserve the "first attribute
-  // wins" behavior.  That does mean that when aCheckIfPresent is set the order
-  // of attributes will get "reversed" from the point of view of the
-  // serializer.  But aCheckIfPresent is only true for malformed documents with
-  // multiple <html>, <head>, or <body> tags, so we're doing fixup anyway at
-  // that point.
-
-  int32_t i, limit, step;
-  if (aCheckIfPresent) {
-    i = 0;
-    limit = ac;
-    step = 1;
-  } else {
-    i = ac - 1;
-    limit = -1;
-    step = -1;
-  }
-  
-  nsAutoString key;
-  for (; i != limit; i += step) {
-    // Get lower-cased key
-    nsresult rv = nsContentUtils::ASCIIToLower(aNode.GetKeyAt(i), key);
-    if (NS_FAILED(rv)) {
-      return rv;
-    }
-
-    nsCOMPtr<nsIAtom> keyAtom = do_GetAtom(key);
-
-    if (aCheckIfPresent && aContent->HasAttr(kNameSpaceID_None, keyAtom)) {
-      continue;
-    }
-
-    // Get value and remove mandatory quotes
-    static const char* kWhitespace = "\n\r\t\b";
-
-    // Bug 114997: Don't trim whitespace on <input value="...">:
-    // Using ?: outside the function call would be more efficient, but
-    // we don't trust ?: with references.
-    const nsAString& v =
-      nsContentUtils::TrimCharsInSet(
-        (nodeType == eHTMLTag_input &&
-          keyAtom == nsGkAtoms::value) ?
-        "" : kWhitespace, aNode.GetValueAt(i));
-
-    if (nodeType == eHTMLTag_a && keyAtom == nsGkAtoms::name) {
-      NS_ConvertUTF16toUTF8 cname(v);
-      NS_ConvertUTF8toUTF16 uv(nsUnescape(cname.BeginWriting()));
-
-      // Add attribute to content
-      aContent->SetAttr(kNameSpaceID_None, keyAtom, uv, aNotify);
-    } else {
-      // Add attribute to content
-      aContent->SetAttr(kNameSpaceID_None, keyAtom, v, aNotify);
-    }
-  }
-
-  return NS_OK;
-}
-
-/**
- * Factory subroutine to create all of the html content objects.
- */
-already_AddRefed<nsGenericHTMLElement>
-HTMLContentSink::CreateContentObject(const nsIParserNode& aNode,
-                                     nsHTMLTag aNodeType)
-{
-  // Find/create atom for the tag name
-
-  nsCOMPtr<nsINodeInfo> nodeInfo;
-
-  MOZ_ASSERT(aNodeType != eHTMLTag_userdefined);
-  if (mNodeInfoCache[aNodeType]) {
-    nodeInfo = mNodeInfoCache[aNodeType];
-  }
-  else {
-    nsIParserService *parserService = nsContentUtils::GetParserService();
-    if (!parserService)
-      return nullptr;
-
-    nsIAtom *name = parserService->HTMLIdToAtomTag(aNodeType);
-    NS_ASSERTION(name, "What? Reverse mapping of id to string broken!!!");
-
-    nodeInfo = mNodeInfoManager->GetNodeInfo(name, nullptr, kNameSpaceID_XHTML,
-                                             nsIDOMNode::ELEMENT_NODE);
-    NS_IF_ADDREF(mNodeInfoCache[aNodeType] = nodeInfo);
-  }
-
-  NS_ENSURE_TRUE(nodeInfo, nullptr);
-
-  // Make the content object
-  return CreateHTMLElement(aNodeType, nodeInfo.forget(), FROM_PARSER_NETWORK);
-}
-
 nsresult
 NS_NewHTMLElement(nsIContent** aResult, already_AddRefed<nsINodeInfo> aNodeInfo,
                   FromParser aFromParser)
 {
   *aResult = nullptr;
 
   nsCOMPtr<nsINodeInfo> nodeInfo = aNodeInfo;
 
@@ -494,40 +283,33 @@ CreateHTMLElement(uint32_t aNodeType, al
   return result.forget();
 }
 
 //----------------------------------------------------------------------
 
 SinkContext::SinkContext(HTMLContentSink* aSink)
   : mSink(aSink),
     mNotifyLevel(0),
-    mLastTextNodeSize(0),
     mStack(nullptr),
     mStackSize(0),
-    mStackPos(0),
-    mText(nullptr),
-    mTextLength(0),
-    mTextSize(0),
-    mLastTextCharWasCR(false)
+    mStackPos(0)
 {
   MOZ_COUNT_CTOR(SinkContext);
 }
 
 SinkContext::~SinkContext()
 {
   MOZ_COUNT_DTOR(SinkContext);
 
   if (mStack) {
     for (int32_t i = 0; i < mStackPos; i++) {
       NS_RELEASE(mStack[i].mContent);
     }
     delete [] mStack;
   }
-
-  delete [] mText;
 }
 
 nsresult
 SinkContext::Begin(nsHTMLTag aNodeType,
                    nsGenericHTMLElement* aRoot,
                    uint32_t aNumFlushed,
                    int32_t aInsertionPoint)
 {
@@ -539,17 +321,16 @@ SinkContext::Begin(nsHTMLTag aNodeType,
   }
 
   mStack[0].mType = aNodeType;
   mStack[0].mContent = aRoot;
   mStack[0].mNumFlushed = aNumFlushed;
   mStack[0].mInsertionPoint = aInsertionPoint;
   NS_ADDREF(aRoot);
   mStackPos = 1;
-  mTextLength = 0;
 
   return NS_OK;
 }
 
 bool
 SinkContext::IsCurrentContainer(nsHTMLTag aTag)
 {
   if (aTag == mStack[mStackPos - 1].mType) {
@@ -570,119 +351,62 @@ SinkContext::DidAddContent(nsIContent* a
   // If we just added content to a node for which
   // an insertion happen, we need to do an immediate
   // notification for that insertion.
   if (0 < mStackPos &&
       mStack[mStackPos - 1].mInsertionPoint != -1 &&
       mStack[mStackPos - 1].mNumFlushed <
       mStack[mStackPos - 1].mContent->GetChildCount()) {
     nsIContent* parent = mStack[mStackPos - 1].mContent;
-
-#ifdef DEBUG
-    // Tracing code
-    nsIParserService *parserService = nsContentUtils::GetParserService();
-    if (parserService) {
-      nsHTMLTag tag = nsHTMLTag(mStack[mStackPos - 1].mType);
-      NS_ConvertUTF16toUTF8 str(parserService->HTMLIdToStringTag(tag));
-
-      SINK_TRACE(gSinkLogModuleInfo, SINK_TRACE_REFLOW,
-                 ("SinkContext::DidAddContent: Insertion notification for "
-                  "parent=%s at position=%d and stackPos=%d",
-                  str.get(), mStack[mStackPos - 1].mInsertionPoint - 1,
-                  mStackPos - 1));
-    }
-#endif
-
     int32_t childIndex = mStack[mStackPos - 1].mInsertionPoint - 1;
     NS_ASSERTION(parent->GetChildAt(childIndex) == aContent,
                  "Flushing the wrong child.");
     mSink->NotifyInsert(parent, aContent, childIndex);
     mStack[mStackPos - 1].mNumFlushed = parent->GetChildCount();
   } else if (mSink->IsTimeToNotify()) {
-    SINK_TRACE(gSinkLogModuleInfo, SINK_TRACE_REFLOW,
-               ("SinkContext::DidAddContent: Notification as a result of the "
-                "interval expiring; backoff count: %d", mSink->mBackoffCount));
     FlushTags();
   }
 }
 
 nsresult
-SinkContext::OpenContainer(const nsIParserNode& aNode)
+SinkContext::OpenBody()
 {
-  FlushTextAndRelease();
-
-  SINK_TRACE_NODE(SINK_TRACE_CALLS,
-                  "SinkContext::OpenContainer", 
-                  nsHTMLTag(aNode.GetNodeType()), 
-                  mStackPos, 
-                  mSink);
-
   if (mStackPos <= 0) {
     NS_ERROR("container w/o parent");
 
     return NS_ERROR_FAILURE;
   }
 
   nsresult rv;
   if (mStackPos + 1 > mStackSize) {
     rv = GrowStack();
     if (NS_FAILED(rv)) {
       return rv;
     }
   }
 
-  // Create new container content object
-  nsHTMLTag nodeType = nsHTMLTag(aNode.GetNodeType());
-  nsGenericHTMLElement* content =
-    mSink->CreateContentObject(aNode, nodeType).get();
-  if (!content) {
+    nsCOMPtr<nsINodeInfo> nodeInfo =
+      mSink->mNodeInfoManager->GetNodeInfo(nsGkAtoms::body, nullptr,
+                                           kNameSpaceID_XHTML,
+                                           nsIDOMNode::ELEMENT_NODE);
+  NS_ENSURE_TRUE(nodeInfo, NS_ERROR_UNEXPECTED);
+
+  // Make the content object
+  nsRefPtr<nsGenericHTMLElement> body =
+    NS_NewHTMLBodyElement(nodeInfo.forget(), FROM_PARSER_NETWORK);
+  if (!body) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
-  mStack[mStackPos].mType = nodeType;
-  mStack[mStackPos].mContent = content;
+  mStack[mStackPos].mType = eHTMLTag_body;
+  body.forget(&mStack[mStackPos].mContent);
   mStack[mStackPos].mNumFlushed = 0;
   mStack[mStackPos].mInsertionPoint = -1;
   ++mStackPos;
-
-  rv = mSink->AddAttributes(aNode, content);
-
-  mStack[mStackPos - 2].Add(content);
-
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (mSink->IsMonolithicContainer(nodeType)) {
-    mSink->mInMonolithicContainer++;
-  }
-
-  // Special handling for certain tags
-  switch (nodeType) {
-    case eHTMLTag_form:
-      MOZ_CRASH("Must not use HTMLContentSink for forms.");
-
-    case eHTMLTag_frameset:
-      MOZ_CRASH("Must not use HTMLContentSink for frames.");
-
-    case eHTMLTag_noembed:
-    case eHTMLTag_noframes:
-      MOZ_CRASH("Must not use HTMLContentSink for noembed/noframes.");
-
-    case eHTMLTag_script:
-    case eHTMLTag_style:
-      MOZ_CRASH("Must not use HTMLContentSink for styles and scripts.");
-
-    case eHTMLTag_button:
-    case eHTMLTag_audio:
-    case eHTMLTag_video:
-      content->DoneCreatingElement();
-      break;
-
-    default:
-      break;
-  }
+  mStack[mStackPos - 2].Add(mStack[mStackPos - 1].mContent);
 
   return NS_OK;
 }
 
 bool
 SinkContext::HaveNotifiedForCurrentContent() const
 {
   if (0 < mStackPos) {
@@ -703,208 +427,63 @@ SinkContext::Node::Add(nsIContent *child
     mContent->InsertChildAt(child, mInsertionPoint++, false);
   } else {
     mContent->AppendChildTo(child, false);
   }
   return child;
 }
 
 nsresult
-SinkContext::CloseContainer(const nsHTMLTag aTag)
+SinkContext::CloseBody()
 {
-  nsresult result = NS_OK;
-
-  // Flush any collected text content. Release the last text
-  // node to indicate that no more should be added to it.
-  FlushTextAndRelease();
-
-  SINK_TRACE_NODE(SINK_TRACE_CALLS,
-                  "SinkContext::CloseContainer", 
-                  aTag, mStackPos - 1, mSink);
-
   NS_ASSERTION(mStackPos > 0,
                "stack out of bounds. wrong context probably!");
 
   if (mStackPos <= 0) {
     return NS_OK; // Fix crash - Ref. bug 45975 or 45007
   }
 
   --mStackPos;
-  nsHTMLTag nodeType = mStack[mStackPos].mType;
-
-  NS_ASSERTION(nodeType == aTag,
+  NS_ASSERTION(mStack[mStackPos].mType == eHTMLTag_body,
                "Tag mismatch.  Closing tag on wrong context or something?");
 
   nsGenericHTMLElement* content = mStack[mStackPos].mContent;
 
   content->Compact();
 
   // If we're in a state where we do append notifications as
   // we go up the tree, and we're at the level where the next
   // notification needs to be done, do the notification.
   if (mNotifyLevel >= mStackPos) {
     // Check to see if new content has been added after our last
     // notification
 
     if (mStack[mStackPos].mNumFlushed < content->GetChildCount()) {
-#ifdef DEBUG
-      {
-        // Tracing code
-        SINK_TRACE(gSinkLogModuleInfo, SINK_TRACE_REFLOW,
-                   ("SinkContext::CloseContainer: reflow on notifyImmediate "
-                    "tag=%s newIndex=%d stackPos=%d",
-                    nsAtomCString(mStack[mStackPos].mContent->Tag()).get(),
-                    mStack[mStackPos].mNumFlushed, mStackPos));
-      }
-#endif
       mSink->NotifyAppend(content, mStack[mStackPos].mNumFlushed);
       mStack[mStackPos].mNumFlushed = content->GetChildCount();
     }
 
     // Indicate that notification has now happened at this level
     mNotifyLevel = mStackPos - 1;
   }
 
-  if (mSink->IsMonolithicContainer(nodeType)) {
-    --mSink->mInMonolithicContainer;
-  }
-
   DidAddContent(content);
-
-  // Special handling for certain tags
-  switch (nodeType) {
-  case eHTMLTag_noembed:
-  case eHTMLTag_noframes:
-    MOZ_CRASH("Must not use HTMLContentSink for noembed/noframes.");
-
-  case eHTMLTag_form:
-    MOZ_CRASH("Must not use HTMLContentSink for forms.");
-
-  case eHTMLTag_video:
-  case eHTMLTag_audio:
-  case eHTMLTag_select:
-  case eHTMLTag_textarea:
-  case eHTMLTag_object:
-  case eHTMLTag_applet:
-  case eHTMLTag_title:
-    content->DoneAddingChildren(HaveNotifiedForCurrentContent());
-    break;
-
-  case eHTMLTag_script:
-    MOZ_CRASH("Must not use HTMLContentSink to run scripts.");
-
-  case eHTMLTag_style:
-    MOZ_CRASH("Must not use HTMLContentSink for styles.");
-
-  default:
-    break;
-  }
-
   NS_IF_RELEASE(content);
 
-#ifdef DEBUG
-  if (SINK_LOG_TEST(gSinkLogModuleInfo, SINK_ALWAYS_REFLOW)) {
-    mSink->ForceReflow();
-  }
-#endif
-
-  return result;
-}
-
-nsresult
-SinkContext::AddLeaf(const nsIParserNode& aNode)
-{
-  SINK_TRACE_NODE(SINK_TRACE_CALLS,
-                  "SinkContext::AddLeaf", 
-                  nsHTMLTag(aNode.GetNodeType()), 
-                  mStackPos, mSink);
-
-  nsresult rv = NS_OK;
-
-  switch (aNode.GetTokenType()) {
-  case eToken_start:
-    {
-      FlushTextAndRelease();
-
-      // Create new leaf content object
-      nsHTMLTag nodeType = nsHTMLTag(aNode.GetNodeType());
-      nsRefPtr<nsGenericHTMLElement> content =
-        mSink->CreateContentObject(aNode, nodeType);
-      NS_ENSURE_TRUE(content, NS_ERROR_OUT_OF_MEMORY);
-
-      rv = mSink->AddAttributes(aNode, content);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      // Add new leaf to its parent
-      AddLeaf(content);
-
-      // Additional processing needed once the element is in the tree
-      switch (nodeType) {
-      case eHTMLTag_meta:
-        MOZ_CRASH("Must not use HTMLContentSink for metas.");
-
-      case eHTMLTag_input:
-        content->DoneCreatingElement();
-        break;
-
-      case eHTMLTag_menuitem:
-        content->DoneCreatingElement();
-        break;
-
-      default:
-        break;
-      }
-    }
-    break;
-
-  case eToken_text:
-  case eToken_whitespace:
-  case eToken_newline:
-    MOZ_CRASH();
-
-    break;
-  case eToken_entity:
-    MOZ_CRASH();
-
-    break;
-  default:
-    break;
-  }
-
-  return rv;
-}
-
-nsresult
-SinkContext::AddLeaf(nsIContent* aContent)
-{
-  NS_ASSERTION(mStackPos > 0, "leaf w/o container");
-  if (mStackPos <= 0) {
-    return NS_ERROR_FAILURE;
-  }
-  
-  DidAddContent(mStack[mStackPos - 1].Add(aContent));
-
-#ifdef DEBUG
-  if (SINK_LOG_TEST(gSinkLogModuleInfo, SINK_ALWAYS_REFLOW)) {
-    mSink->ForceReflow();
-  }
-#endif
-
   return NS_OK;
 }
 
 nsresult
 SinkContext::End()
 {
   for (int32_t i = 0; i < mStackPos; i++) {
     NS_RELEASE(mStack[i].mContent);
   }
 
   mStackPos = 0;
-  mTextLength = 0;
 
   return NS_OK;
 }
 
 nsresult
 SinkContext::GrowStack()
 {
   int32_t newSize = mStackSize * 2;
@@ -921,73 +500,16 @@ SinkContext::GrowStack()
 
   mStack = stack;
   mStackSize = newSize;
 
   return NS_OK;
 }
 
 /**
- * Add textual content to the current running text buffer. If the text buffer
- * overflows, flush out the text by creating a text content object and adding
- * it to the content tree.
- */
-
-// XXX If we get a giant string grow the buffer instead of chopping it
-// up???
-nsresult
-SinkContext::AddText(const nsAString& aText)
-{
-  int32_t addLen = aText.Length();
-  if (addLen == 0) {
-    return NS_OK;
-  }
-  
-  // Create buffer when we first need it
-  if (mTextSize == 0) {
-    mText = new PRUnichar[4096];
-    mTextSize = 4096;
-  }
-
-  // Copy data from string into our buffer; flush buffer when it fills up
-  int32_t offset = 0;
-
-  while (addLen != 0) {
-    int32_t amount = mTextSize - mTextLength;
-
-    if (amount > addLen) {
-      amount = addLen;
-    }
-
-    if (amount == 0) {
-      // Don't release last text node so we can add to it again
-      nsresult rv = FlushText();
-      if (NS_FAILED(rv)) {
-        return rv;
-      }
-
-      // Go back to the top of the loop so we re-calculate amount and
-      // don't fall through to CopyNewlineNormalizedUnicodeTo with a
-      // zero-length amount (which invalidates mLastTextCharWasCR).
-      continue;
-    }
-
-    mTextLength +=
-      nsContentUtils::CopyNewlineNormalizedUnicodeTo(aText, offset,
-                                                     &mText[mTextLength],
-                                                     amount,
-                                                     mLastTextCharWasCR);
-    offset += amount;
-    addLen -= amount;
-  }
-
-  return NS_OK;
-}
-
-/**
  * NOTE!! Forked into nsXMLContentSink. Please keep in sync.
  *
  * Flush all elements that have been seen so far such that
  * they are visible in the tree. Specifically, make sure
  * that they are all added to their respective parents.
  * Also, do notification at the top for all content that
  * has been newly added so that the frame tree is complete.
  */
@@ -1001,19 +523,16 @@ SinkContext::FlushTags()
   ++(mSink->mInNotification);
   mSink->mUpdatesInNotification = 0;
   {
     // Scope so we call EndUpdate before we decrease mInNotification
     mozAutoDocUpdate updateBatch(mSink->mDocument, UPDATE_CONTENT_MODEL,
                                  true);
     mSink->mBeganUpdate = true;
 
-    // Don't release last text node in case we need to add to it again
-    FlushText();
-
     // Start from the base of the stack (growing downward) and do
     // a notification from the node that is closest to the root of
     // tree for any content that has been added.
 
     // Note that we can start at stackPos == 0 here, because it's the caller's
     // responsibility to handle flushing interactions between contexts (see
     // HTMLContentSink::BeginContext).
     int32_t stackPos = 0;
@@ -1021,26 +540,16 @@ SinkContext::FlushTags()
     uint32_t childCount;
     nsGenericHTMLElement* content;
 
     while (stackPos < mStackPos) {
       content = mStack[stackPos].mContent;
       childCount = content->GetChildCount();
 
       if (!flushed && (mStack[stackPos].mNumFlushed < childCount)) {
-#ifdef DEBUG
-        {
-          // Tracing code
-          SINK_TRACE(gSinkLogModuleInfo, SINK_TRACE_REFLOW,
-                     ("SinkContext::FlushTags: tag=%s from newindex=%d at "
-                      "stackPos=%d",
-                      nsAtomCString(mStack[stackPos].mContent->Tag()).get(),
-                      mStack[stackPos].mNumFlushed, stackPos));
-        }
-#endif
         if (mStack[stackPos].mInsertionPoint != -1) {
           // We might have popped the child off our stack already
           // but not notified on it yet, which is why we have to get it
           // directly from its parent node.
 
           int32_t childIndex = mStack[stackPos].mInsertionPoint - 1;
           nsIContent* child = content->GetChildAt(childIndex);
           // Child not on stack anymore; can't assert it's correct
@@ -1089,90 +598,16 @@ SinkContext::UpdateChildCounts()
     node.mNumFlushed = node.mContent->GetChildCount();
 
     stackPos--;
   }
 
   mNotifyLevel = mStackPos - 1;
 }
 
-/**
- * Flush any buffered text out by creating a text content object and
- * adding it to the content.
- */
-nsresult
-SinkContext::FlushText(bool* aDidFlush, bool aReleaseLast)
-{
-  nsresult rv = NS_OK;
-  bool didFlush = false;
-
-  if (mTextLength != 0) {
-    if (mLastTextNode) {
-      if ((mLastTextNodeSize + mTextLength) > mSink->mMaxTextRun) {
-        mLastTextNodeSize = 0;
-        mLastTextNode = nullptr;
-        FlushText(aDidFlush, aReleaseLast);
-      } else {
-        bool notify = HaveNotifiedForCurrentContent();
-        // We could probably always increase mInNotification here since
-        // if AppendText doesn't notify it shouldn't trigger evil code.
-        // But just in case it does, we don't want to mask any notifications.
-        if (notify) {
-          ++mSink->mInNotification;
-        }
-        rv = mLastTextNode->AppendText(mText, mTextLength, notify);
-        if (notify) {
-          --mSink->mInNotification;
-        }
-
-        mLastTextNodeSize += mTextLength;
-        mTextLength = 0;
-        didFlush = true;
-      }
-    } else {
-      nsRefPtr<nsTextNode> textContent =
-        new nsTextNode(mSink->mNodeInfoManager);
-
-      mLastTextNode = textContent;
-
-      // Set the text in the text node
-      mLastTextNode->SetText(mText, mTextLength, false);
-
-      // Eat up the rest of the text up in state.
-      mLastTextNodeSize += mTextLength;
-      mTextLength = 0;
-
-      rv = AddLeaf(mLastTextNode);
-      NS_ENSURE_SUCCESS(rv, rv);
-
-      didFlush = true;
-    }
-  }
-
-  if (aDidFlush) {
-    *aDidFlush = didFlush;
-  }
-
-  if (aReleaseLast) {
-    mLastTextNodeSize = 0;
-    mLastTextNode = nullptr;
-    mLastTextCharWasCR = false;
-  }
-
-#ifdef DEBUG
-  if (didFlush &&
-      SINK_LOG_TEST(gSinkLogModuleInfo, SINK_ALWAYS_REFLOW)) {
-    mSink->ForceReflow();
-  }
-#endif
-
-  return rv;
-}
-
-
 nsresult
 NS_NewHTMLContentSink(nsIHTMLContentSink** aResult,
                       nsIDocument* aDoc,
                       nsIURI* aURI,
                       nsISupports* aContainer,
                       nsIChannel* aChannel)
 {
   NS_ENSURE_ARG_POINTER(aResult);
@@ -1187,23 +622,16 @@ NS_NewHTMLContentSink(nsIHTMLContentSink
   NS_ADDREF(*aResult);
 
   return NS_OK;
 }
 
 HTMLContentSink::HTMLContentSink()
 {
   // Note: operator new zeros our memory
-
-
-#ifdef DEBUG
-  if (!gSinkLogModuleInfo) {
-    gSinkLogModuleInfo = PR_NewLogModule("htmlcontentsink");
-  }
-#endif
 }
 
 HTMLContentSink::~HTMLContentSink()
 {
   if (mNotificationTimer) {
     mNotificationTimer->Cancel();
   }
 
@@ -1262,19 +690,16 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_
     cb.NoteXPCOMChild(tmp->mNodeInfoCache[i]);
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_INTERFACE_TABLE_HEAD_CYCLE_COLLECTION_INHERITED(HTMLContentSink)
   NS_INTERFACE_TABLE_BEGIN
     NS_INTERFACE_TABLE_ENTRY(HTMLContentSink, nsIContentSink)
     NS_INTERFACE_TABLE_ENTRY(HTMLContentSink, nsIHTMLContentSink)
-#if DEBUG
-    NS_INTERFACE_TABLE_ENTRY(HTMLContentSink, nsIDebugDumpContent)
-#endif
   NS_INTERFACE_TABLE_END
 NS_INTERFACE_TABLE_TAIL_INHERITING(nsContentSink)
 
 NS_IMPL_ADDREF_INHERITED(HTMLContentSink, nsContentSink)
 NS_IMPL_RELEASE_INHERITED(HTMLContentSink, nsContentSink)
 
 static bool
 IsScriptEnabled(nsIDocument *aDoc, nsIDocShell *aContainer)
@@ -1370,23 +795,16 @@ HTMLContentSink::Init(nsIDocument* aDoc,
   }
 
   mRoot->AppendChildTo(mHead, false);
 
   mCurrentContext = new SinkContext(this);
   mCurrentContext->Begin(eHTMLTag_html, mRoot, 0, -1);
   mContextStack.AppendElement(mCurrentContext);
 
-#ifdef DEBUG
-  nsAutoCString spec;
-  (void)aURI->GetSpec(spec);
-  SINK_TRACE(gSinkLogModuleInfo, SINK_TRACE_CALLS,
-             ("HTMLContentSink::Init: this=%p url='%s'",
-              this, spec.get()));
-#endif
   return NS_OK;
 }
 
 NS_IMETHODIMP
 HTMLContentSink::WillParse(void)
 {
   return WillParseImpl();
 }
@@ -1419,26 +837,20 @@ HTMLContentSink::WillBuildModel(nsDTDMod
 
 NS_IMETHODIMP
 HTMLContentSink::DidBuildModel(bool aTerminated)
 {
   DidBuildModelImpl(aTerminated);
 
   // Reflow the last batch of content
   if (mBody) {
-    SINK_TRACE(gSinkLogModuleInfo, SINK_TRACE_REFLOW,
-               ("HTMLContentSink::DidBuildModel: layout final content"));
     mCurrentContext->FlushTags();
   } else if (!mLayoutStarted) {
     // We never saw the body, and layout never got started. Force
     // layout *now*, to get an initial reflow.
-    SINK_TRACE(gSinkLogModuleInfo, SINK_TRACE_REFLOW,
-               ("HTMLContentSink::DidBuildModel: forcing reflow on empty "
-                "document"));
-
     // NOTE: only force the layout if we are NOT destroying the
     // docshell. If we are destroying it, then starting layout will
     // likely cause us to crash, or at best waste a lot of time as we
     // are just going to tear it down anyway.
     bool bDestroying = true;
     if (mDocShell) {
       mDocShell->IsBeingDestroyed(&bDestroying);
     }
@@ -1469,164 +881,48 @@ HTMLContentSink::DidBuildModel(bool aTer
 NS_IMETHODIMP
 HTMLContentSink::SetParser(nsParserBase* aParser)
 {
   NS_PRECONDITION(aParser, "Should have a parser here!");
   mParser = aParser;
   return NS_OK;
 }
 
-NS_IMETHODIMP
-HTMLContentSink::BeginContext(int32_t aPosition)
-{
-  NS_PRECONDITION(aPosition > -1, "out of bounds");
-
-  if (!mCurrentContext) {
-    NS_ERROR("Nonexistent context");
-
-    return NS_ERROR_FAILURE;
-  }
-
-  // Flush everything in the current context so that we don't have
-  // to worry about insertions resulting in inconsistent frame creation.
-  mCurrentContext->FlushTags();
-
-  // Sanity check.
-  if (mCurrentContext->mStackPos <= aPosition) {
-    NS_ERROR("Out of bounds position");
-    return NS_ERROR_FAILURE;
-  }
-
-  int32_t insertionPoint = -1;
-  nsHTMLTag nodeType      = mCurrentContext->mStack[aPosition].mType;
-  nsGenericHTMLElement* content = mCurrentContext->mStack[aPosition].mContent;
-
-  // If the content under which the new context is created
-  // has a child on the stack, the insertion point is
-  // before the last child.
-  if (aPosition < (mCurrentContext->mStackPos - 1)) {
-    insertionPoint = content->GetChildCount() - 1;
-  }
-
-  SinkContext* sc = new SinkContext(this);
-  sc->Begin(nodeType,
-            content,
-            mCurrentContext->mStack[aPosition].mNumFlushed,
-            insertionPoint);
-  NS_ADDREF(sc->mSink);
-
-  mContextStack.AppendElement(mCurrentContext);
-  mCurrentContext = sc;
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-HTMLContentSink::EndContext(int32_t aPosition)
-{
-  NS_PRECONDITION(mCurrentContext && aPosition > -1, "nonexistent context");
-
-  uint32_t n = mContextStack.Length() - 1;
-  SinkContext* sc = mContextStack.ElementAt(n);
-
-  const SinkContext::Node &bottom = mCurrentContext->mStack[0];
-  
-  NS_ASSERTION(sc->mStack[aPosition].mType == bottom.mType,
-               "ending a wrong context");
-
-  mCurrentContext->FlushTextAndRelease();
-  
-  NS_ASSERTION(bottom.mContent->GetChildCount() == bottom.mNumFlushed,
-               "Node at base of context stack not fully flushed.");
-
-  // Flushing tags before the assertion on the previous line would
-  // undoubtedly prevent the assertion from failing, but it shouldn't
-  // be failing anyway, FlushTags or no.  Flushing here is nevertheless
-  // a worthwhile precaution, since we lose some information (e.g.,
-  // mInsertionPoints) when we end the current context.
-  mCurrentContext->FlushTags();
-
-  sc->mStack[aPosition].mNumFlushed = bottom.mNumFlushed;
-
-  for (int32_t i = 0; i<mCurrentContext->mStackPos; i++) {
-    NS_IF_RELEASE(mCurrentContext->mStack[i].mContent);
-  }
-
-  delete [] mCurrentContext->mStack;
-
-  mCurrentContext->mStack      = nullptr;
-  mCurrentContext->mStackPos   = 0;
-  mCurrentContext->mStackSize  = 0;
-
-  delete [] mCurrentContext->mText;
-
-  mCurrentContext->mText       = nullptr;
-  mCurrentContext->mTextLength = 0;
-  mCurrentContext->mTextSize   = 0;
-
-  NS_IF_RELEASE(mCurrentContext->mSink);
-
-  delete mCurrentContext;
-
-  mCurrentContext = sc;
-  mContextStack.RemoveElementAt(n);
-  return NS_OK;
-}
-
 nsresult
 HTMLContentSink::CloseHTML()
 {
-  SINK_TRACE_NODE(SINK_TRACE_CALLS,
-                 "HTMLContentSink::CloseHTML", 
-                 eHTMLTag_html, 0, this);
-
   if (mHeadContext) {
     if (mCurrentContext == mHeadContext) {
       uint32_t numContexts = mContextStack.Length();
 
       // Pop off the second html context if it's not done earlier
       mCurrentContext = mContextStack.ElementAt(--numContexts);
       mContextStack.RemoveElementAt(numContexts);
     }
 
-    NS_ASSERTION(mHeadContext->mTextLength == 0, "Losing text");
-
     mHeadContext->End();
 
     delete mHeadContext;
     mHeadContext = nullptr;
   }
 
   return NS_OK;
 }
 
 nsresult
-HTMLContentSink::OpenHead()
+HTMLContentSink::OpenBody()
 {
-  nsresult rv = OpenHeadContext();
-  return rv;
-}
-
-nsresult
-HTMLContentSink::OpenBody(const nsIParserNode& aNode)
-{
-  SINK_TRACE_NODE(SINK_TRACE_CALLS,
-                  "HTMLContentSink::OpenBody", 
-                  eHTMLTag_body,
-                  mCurrentContext->mStackPos, 
-                  this);
-
   CloseHeadContext();  // do this just in case if the HEAD was left open!
 
-  // Add attributes, if any, to the current BODY node
+  // if we already have a body we're done
   if (mBody) {
-    AddAttributes(aNode, mBody, true, true);
     return NS_OK;
   }
 
-  nsresult rv = mCurrentContext->OpenContainer(aNode);
+  nsresult rv = mCurrentContext->OpenBody();
 
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   mBody = mCurrentContext->mStack[mCurrentContext->mStackPos - 1].mContent;
 
   if (mCurrentContext->mStackPos > 1) {
@@ -1660,237 +956,93 @@ HTMLContentSink::OpenBody(const nsIParse
   StartLayout(false);
 
   return NS_OK;
 }
 
 nsresult
 HTMLContentSink::CloseBody()
 {
-  SINK_TRACE_NODE(SINK_TRACE_CALLS,
-                  "HTMLContentSink::CloseBody", 
-                  eHTMLTag_body,
-                  mCurrentContext->mStackPos - 1, 
-                  this);
-
-  bool didFlush;
-  nsresult rv = mCurrentContext->FlushTextAndRelease(&didFlush);
-  if (NS_FAILED(rv)) {
-    return rv;
-  }
-
   // Flush out anything that's left
-  SINK_TRACE(gSinkLogModuleInfo, SINK_TRACE_REFLOW,
-             ("HTMLContentSink::CloseBody: layout final body content"));
-
   mCurrentContext->FlushTags();
-  mCurrentContext->CloseContainer(eHTMLTag_body);
+  mCurrentContext->CloseBody();
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-HTMLContentSink::IsEnabled(int32_t aTag, bool* aReturn)
-{
-  nsHTMLTag theHTMLTag = nsHTMLTag(aTag);
-
-  if (theHTMLTag == eHTMLTag_script) {
-    *aReturn = mScriptEnabled;
-  } else if (theHTMLTag == eHTMLTag_frameset) {
-    *aReturn = mFramesEnabled;
-  } else {
-    *aReturn = false;
-  }
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-HTMLContentSink::OpenContainer(const nsIParserNode& aNode)
+HTMLContentSink::OpenContainer(ElementType aElementType)
 {
   nsresult rv = NS_OK;
 
-  switch (aNode.GetNodeType()) {
-    case eHTMLTag_frameset:
-      MOZ_CRASH("Must not use HTMLContentSink for frames.");
-
-    case eHTMLTag_head:
-      rv = OpenHeadContext();
-      if (NS_SUCCEEDED(rv)) {
-        rv = AddAttributes(aNode, mHead, true, mHaveSeenHead);
-        mHaveSeenHead = true;
-      }
+  switch (aElementType) {
+    case eBody:
+      rv = OpenBody();
       break;
-    case eHTMLTag_body:
-      rv = OpenBody(aNode);
-      break;
-    case eHTMLTag_html:
+    case eHTML:
       if (mRoot) {
-        // If we've already hit this code once, need to check for
-        // already-present attributes on the root.
-        AddAttributes(aNode, mRoot, true, mNotifiedRootInsertion);
+        // If we've already hit this code once, then we're done
         if (!mNotifiedRootInsertion) {
           NotifyRootInsertion();
         }
         ProcessOfflineManifest(mRoot);
       }
       break;
-    case eHTMLTag_form:
-      MOZ_CRASH("Must not use HTMLContentSink for forms.");
-
-    default:
-      rv = mCurrentContext->OpenContainer(aNode);
-      break;
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
-HTMLContentSink::CloseContainer(const eHTMLTags aTag)
+HTMLContentSink::CloseContainer(const ElementType aTag)
 {
   nsresult rv = NS_OK;
 
   switch (aTag) {
-    case eHTMLTag_frameset:
-      MOZ_CRASH("Must not use HTMLContentSink for frames.");
-
-    case eHTMLTag_head:
-      CloseHeadContext();
-      break;
-    case eHTMLTag_body:
+    case eBody:
       rv = CloseBody();
       break;
-    case eHTMLTag_html:
+    case eHTML:
       rv = CloseHTML();
       break;
-    case eHTMLTag_form:
-      MOZ_CRASH("Must not use HTMLContentSink for forms.");
-
-    default:
-      rv = mCurrentContext->CloseContainer(aTag);
-      break;
   }
 
   return rv;
 }
 
 NS_IMETHODIMP
-HTMLContentSink::CloseMalformedContainer(const eHTMLTags aTag)
-{
-  return mCurrentContext->CloseContainer(aTag);
-}
-
-NS_IMETHODIMP
-HTMLContentSink::AddLeaf(const nsIParserNode& aNode)
-{
-  nsresult rv;
-
-  nsHTMLTag nodeType = nsHTMLTag(aNode.GetNodeType());
-  switch (nodeType) {
-  case eHTMLTag_link:
-    MOZ_CRASH("Must not use HTMLContentSink for links.");
-
-  default:
-    rv = mCurrentContext->AddLeaf(aNode);
-
-    break;
-  }
-
-  return rv;
-}
-
-NS_IMETHODIMP
-HTMLContentSink::DidProcessTokens(void)
-{
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-HTMLContentSink::WillProcessAToken(void)
-{
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-HTMLContentSink::DidProcessAToken(void)
-{
-  return DidProcessATokenImpl();
-}
-
-NS_IMETHODIMP
 HTMLContentSink::WillInterrupt()
 {
   return WillInterruptImpl();
 }
 
 NS_IMETHODIMP
 HTMLContentSink::WillResume()
 {
   return WillResumeImpl();
 }
 
-nsresult
-HTMLContentSink::OpenHeadContext()
-{
-  if (mCurrentContext && mCurrentContext->IsCurrentContainer(eHTMLTag_head))
-    return NS_OK;
-
-  // Flush everything in the current context so that we don't have
-  // to worry about insertions resulting in inconsistent frame creation.
-  //
-  // Try to do this only if needed (costly), i.e., only if we are sure
-  // we are changing contexts from some other context to the head.
-  //
-  // PERF: This call causes approximately a 2% slowdown in page load time
-  // according to jrgm's page load tests, but seems to be a necessary evil
-  if (mCurrentContext && (mCurrentContext != mHeadContext)) {
-    mCurrentContext->FlushTags();
-  }
-
-  if (!mHeadContext) {
-    mHeadContext = new SinkContext(this);
-
-    nsresult rv = mHeadContext->Begin(eHTMLTag_head, mHead, 0, -1);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
-
-  mContextStack.AppendElement(mCurrentContext);
-  mCurrentContext = mHeadContext;
-
-  return NS_OK;
-}
-
 void
 HTMLContentSink::CloseHeadContext()
 {
   if (mCurrentContext) {
     if (!mCurrentContext->IsCurrentContainer(eHTMLTag_head))
       return;
 
-    mCurrentContext->FlushTextAndRelease();
     mCurrentContext->FlushTags();
   }
 
   if (!mContextStack.IsEmpty())
   {
     uint32_t n = mContextStack.Length() - 1;
     mCurrentContext = mContextStack.ElementAt(n);
     mContextStack.RemoveElementAt(n);
   }
 }
 
-#ifdef DEBUG
-void
-HTMLContentSink::ForceReflow()
-{
-  mCurrentContext->FlushTags();
-}
-#endif
-
 void
 HTMLContentSink::NotifyInsert(nsIContent* aContent,
                               nsIContent* aChildContent,
                               int32_t aIndexInContainer)
 {
   if (aContent && aContent->GetCurrentDoc() != mDocument) {
     // aContent is not actually in our document anymore.... Just bail out of
     // here; notifying on our document for this insert would be wrong.
@@ -1929,29 +1081,16 @@ HTMLContentSink::NotifyRootInsertion()
   NotifyInsert(nullptr, mRoot, index);
 
   // Now update the notification information in all our
   // contexts, since we just inserted the root and notified on
   // our whole tree
   UpdateChildCounts();
 }
 
-bool
-HTMLContentSink::IsMonolithicContainer(nsHTMLTag aTag)
-{
-  if (aTag == eHTMLTag_tr     ||
-      aTag == eHTMLTag_select ||
-      aTag == eHTMLTag_applet ||
-      aTag == eHTMLTag_object) {
-    return true;
-  }
-
-  return false;
-}
-
 void
 HTMLContentSink::UpdateChildCounts()
 {
   uint32_t numContexts = mContextStack.Length();
   for (uint32_t i = 0; i < numContexts; i++) {
     SinkContext* sc = mContextStack.ElementAt(i);
 
     sc->UpdateChildCounts();
@@ -1967,19 +1106,16 @@ HTMLContentSink::FlushPendingNotificatio
   // (since we aren't reentrant)
   if (!mInNotification) {
     // Only flush if we're still a document observer (so that our child counts
     // should be correct).
     if (mIsDocumentObserver) {
       if (aType >= Flush_ContentAndNotify) {
         FlushTags();
       }
-      else if (mCurrentContext) {
-        mCurrentContext->FlushText();
-      }
     }
     if (aType >= Flush_InterruptibleLayout) {
       // Make sure that layout has started so that the reflow flush
       // will actually happen.
       StartLayout(true);
     }
   }
 }
@@ -2047,45 +1183,8 @@ HTMLContentSink::GetTarget()
   return mDocument;
 }
 
 bool
 HTMLContentSink::IsScriptExecuting()
 {
   return IsScriptExecutingImpl();
 }
-
-#ifdef DEBUG
-/**
- *  This will dump content model into the output file.
- *
- *  @update  harishd 05/25/00
- *  @param
- *  @return  NS_OK all went well, error on failure
- */
-
-NS_IMETHODIMP
-HTMLContentSink::DumpContentModel()
-{
-  FILE* out = ::fopen("rtest_html.txt", "a");
-  if (out) {
-    if (mDocument) {
-      Element* root = mDocument->GetRootElement();
-      if (root) {
-        if (mDocumentURI) {
-          nsAutoCString buf;
-          mDocumentURI->GetSpec(buf);
-          fputs(buf.get(), out);
-        }
-
-        fputs(";", out);
-        root->DumpContent(out, 0, false);
-        fputs(";\n", out);
-      }
-    }
-
-    fclose(out);
-  }
-
-  return NS_OK;
-}
-#endif
-
--- a/content/media/DOMMediaStream.cpp
+++ b/content/media/DOMMediaStream.cpp
@@ -235,16 +235,24 @@ DOMMediaStream::CreateSourceStream(nsIDO
 already_AddRefed<DOMMediaStream>
 DOMMediaStream::CreateTrackUnionStream(nsIDOMWindow* aWindow, TrackTypeHints aHintContents)
 {
   nsRefPtr<DOMMediaStream> stream = new DOMMediaStream();
   stream->InitTrackUnionStream(aWindow, aHintContents);
   return stream.forget();
 }
 
+void
+DOMMediaStream::SetTrackEnabled(TrackID aTrackID, bool aEnabled)
+{
+  if (mStream) {
+    mStream->SetTrackEnabled(aTrackID, aEnabled);
+  }
+}
+
 bool
 DOMMediaStream::CombineWithPrincipal(nsIPrincipal* aPrincipal)
 {
   return nsContentUtils::CombineResourcePrincipals(&mPrincipal, aPrincipal);
 }
 
 MediaStreamTrack*
 DOMMediaStream::CreateDOMTrack(TrackID aTrackID, MediaSegment::Type aType)
--- a/content/media/DOMMediaStream.h
+++ b/content/media/DOMMediaStream.h
@@ -79,16 +79,22 @@ public:
   /**
    * Overridden in DOMLocalMediaStreams to allow getUserMedia to pass
    * data directly to RTCPeerConnection without going through graph queuing.
    * Returns a bool to let us know if direct data will be delivered.
    */
   virtual bool AddDirectListener(MediaStreamDirectListener *aListener) { return false; }
   virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) {}
 
+  /**
+   * Overridden in DOMLocalMediaStreams to allow getUserMedia to disable
+   * media at the SourceMediaStream.
+   */
+  virtual void SetTrackEnabled(TrackID aTrackID, bool aEnabled);
+
   bool IsFinished();
   /**
    * Returns a principal indicating who may access this stream. The stream contents
    * can only be accessed by principals subsuming this principal.
    */
   nsIPrincipal* GetPrincipal() { return mPrincipal; }
 
   /**
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -134,22 +134,24 @@ void MediaDecoder::SetDormantIfNecessary
 
     mRequestedSeekTime = mCurrentTime;
     if (mPlayState == PLAY_STATE_PLAYING){
       mNextState = PLAY_STATE_PLAYING;
     } else {
       mNextState = PLAY_STATE_PAUSED;
     }
     mNextState = mPlayState;
-    mIsDormant = aDormant;
+    mIsDormant = true;
+    mIsExitingDormant = false;
     ChangeState(PLAY_STATE_LOADING);
   } else if ((aDormant != true) && (mPlayState == PLAY_STATE_LOADING)) {
     // exit dormant state
-    // just trigger to state machine.
+    // trigger to state machine.
     mDecoderStateMachine->SetDormant(false);
+    mIsExitingDormant = true;
   }
 }
 
 void MediaDecoder::Pause()
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
   if ((mPlayState == PLAY_STATE_LOADING && mIsDormant)  || mPlayState == PLAY_STATE_SEEKING || mPlayState == PLAY_STATE_ENDED) {
@@ -366,16 +368,17 @@ MediaDecoder::MediaDecoder() :
   mInitialPreservesPitch(true),
   mRequestedSeekTime(-1.0),
   mDuration(-1),
   mTransportSeekable(true),
   mMediaSeekable(true),
   mSameOriginMedia(false),
   mReentrantMonitor("media.decoder"),
   mIsDormant(false),
+  mIsExitingDormant(false),
   mPlayState(PLAY_STATE_PAUSED),
   mNextState(PLAY_STATE_PAUSED),
   mCalledResourceLoaded(false),
   mIgnoreProgressData(false),
   mInfiniteStream(false),
   mTriggerPlaybackEndedWhenSourceStreamFinishes(false),
   mOwner(nullptr),
   mFrameBufferLength(0),
@@ -719,18 +722,21 @@ void MediaDecoder::MetadataLoaded(int aC
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mShuttingDown) {
     return;
   }
 
   {
     ReentrantMonitorAutoEnter mon(GetReentrantMonitor());
-    if (mPlayState == PLAY_STATE_LOADING && mIsDormant) {
+    if (mPlayState == PLAY_STATE_LOADING && mIsDormant && !mIsExitingDormant) {
+      return;
+    } else if (mPlayState == PLAY_STATE_LOADING && mIsDormant && mIsExitingDormant) {
       mIsDormant = false;
+      mIsExitingDormant = false;
     }
     mDuration = mDecoderStateMachine ? mDecoderStateMachine->GetDuration() : -1;
     // Duration has changed so we should recompute playback rate
     UpdatePlaybackRate();
   }
 
   if (mDuration == -1) {
     SetInfinite(true);
@@ -1182,16 +1188,17 @@ void MediaDecoder::ChangeState(PlayState
     default:
       /* No action needed */
       break;
     }
   }
 
   if (aState!= PLAY_STATE_LOADING) {
     mIsDormant = false;
+    mIsExitingDormant = false;
   }
 
   GetReentrantMonitor().NotifyAll();
 }
 
 void MediaDecoder::PlaybackPositionChanged()
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/content/media/MediaDecoder.h
+++ b/content/media/MediaDecoder.h
@@ -1030,16 +1030,20 @@ public:
   // can be read on any thread while holding the monitor, or on the main thread
   // without holding the monitor.
   nsAutoPtr<DecodedStreamData> mDecodedStream;
 
   // True if this decoder is in dormant state.
   // Should be true only when PlayState is PLAY_STATE_LOADING.
   bool mIsDormant;
 
+  // True if this decoder is exiting from dormant state.
+  // Should be true only when PlayState is PLAY_STATE_LOADING.
+  bool mIsExitingDormant;
+
   // Set to one of the valid play states.
   // This can only be changed on the main thread while holding the decoder
   // monitor. Thus, it can be safely read while holding the decoder monitor
   // OR on the main thread.
   // Any change to the state on the main thread must call NotifyAll on the
   // monitor so the decode thread can wake up.
   PlayState mPlayState;
 
--- a/content/media/MediaSegment.h
+++ b/content/media/MediaSegment.h
@@ -96,16 +96,20 @@ public:
    * Insert aDuration of null data at the start of the segment.
    */
   virtual void InsertNullDataAtStart(TrackTicks aDuration) = 0;
   /**
    * Insert aDuration of null data at the end of the segment.
    */
   virtual void AppendNullData(TrackTicks aDuration) = 0;
   /**
+   * Replace contents with disabled data of the same duration
+   */
+  virtual void ReplaceWithDisabled() = 0;
+  /**
    * Remove all contents, setting duration to 0.
    */
   virtual void Clear() = 0;
 
 protected:
   MediaSegment(Type aType) : mDuration(0), mType(aType)
   {
     MOZ_COUNT_CTOR(MediaSegment);
@@ -185,16 +189,25 @@ public:
     }
     if (!mChunks.IsEmpty() && mChunks[mChunks.Length() - 1].IsNull()) {
       mChunks[mChunks.Length() - 1].mDuration += aDuration;
     } else {
       mChunks.AppendElement()->SetNull(aDuration);
     }
     mDuration += aDuration;
   }
+  virtual void ReplaceWithDisabled()
+  {
+    if (GetType() != AUDIO) {
+      MOZ_CRASH("Disabling unknown segment type");
+    }
+    TrackTicks duration = GetDuration();
+    Clear();
+    AppendNullData(duration);
+  }
   virtual void Clear()
   {
     mDuration = 0;
     mChunks.Clear();
   }
 
   class ChunkIterator {
   public:
--- a/content/media/MediaStreamGraph.cpp
+++ b/content/media/MediaStreamGraph.cpp
@@ -1907,39 +1907,25 @@ MediaStream::SetTrackEnabled(TrackID aTr
     }
     TrackID mTrackID;
     bool mEnabled;
   };
   GraphImpl()->AppendMessage(new Message(this, aTrackID, aEnabled));
 }
 
 void
-MediaStream::ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment)
+MediaStream::ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment, MediaSegment* aRawSegment)
 {
+  // mMutex must be owned here if this is a SourceMediaStream
   if (!mDisabledTrackIDs.Contains(aTrackID)) {
     return;
   }
-
-  switch (aSegment->GetType()) {
-  case MediaSegment::AUDIO: {
-    TrackTicks duration = aSegment->GetDuration();
-    aSegment->Clear();
-    aSegment->AppendNullData(duration);
-    break;
-  }
-  case MediaSegment::VIDEO: {
-    for (VideoSegment::ChunkIterator i(*static_cast<VideoSegment*>(aSegment));
-         !i.IsEnded(); i.Next()) {
-      VideoChunk& chunk = *i;
-      chunk.SetForceBlack(true);
-    }
-    break;
-  }
-  default:
-    MOZ_CRASH("Unknown track type");
+  aSegment->ReplaceWithDisabled();
+  if (aRawSegment) {
+    aRawSegment->ReplaceWithDisabled();
   }
 }
 
 void
 SourceMediaStream::DestroyImpl()
 {
   {
     MutexAutoLock lock(mMutex);
@@ -1985,16 +1971,20 @@ SourceMediaStream::AppendToTrack(TrackID
     TrackData *track = FindDataForTrack(aID);
     if (track) {
       // Data goes into mData, and on the next iteration of the MSG moves
       // into the track's segment after NotifyQueuedTrackChanges().  This adds
       // 0-10ms of delay before data gets to direct listeners.
       // Indirect listeners (via subsequent TrackUnion nodes) are synced to
       // playout time, and so can be delayed by buffering.
 
+      // Apply track disabling before notifying any consumers directly
+      // or inserting into the graph
+      ApplyTrackDisabling(aID, aSegment, aRawSegment);
+
       // Must notify first, since AppendFrom() will empty out aSegment
       NotifyDirectConsumers(track, aRawSegment ? aRawSegment : aSegment);
       track->mData->AppendFrom(aSegment); // note: aSegment is now dead
       appended = true;
     } else {
       aSegment->Clear();
     }
   }
--- a/content/media/MediaStreamGraph.h
+++ b/content/media/MediaStreamGraph.h
@@ -454,17 +454,17 @@ public:
    * Identify which graph update index we are currently processing.
    */
   int64_t GetProcessingGraphUpdateIndex();
 
   bool HasCurrentData() { return mHasCurrentData; }
 
   StreamBuffer::Track* EnsureTrack(TrackID aTrack, TrackRate aSampleRate);
 
-  void ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment);
+  void ApplyTrackDisabling(TrackID aTrackID, MediaSegment* aSegment, MediaSegment* aRawSegment = nullptr);
 
   DOMMediaStream* GetWrapper()
   {
     NS_ASSERTION(NS_IsMainThread(), "Only use DOMMediaStream on main thread");
     return mWrapper;
   }
 
   // Return true if the main thread needs to observe updates from this stream.
@@ -674,16 +674,21 @@ public:
    */
   void FinishWithLockHeld();
   void Finish()
     {
       MutexAutoLock lock(mMutex);
       FinishWithLockHeld();
     }
 
+  // Overriding allows us to hold the mMutex lock while changing the track enable status
+  void SetTrackEnabledImpl(TrackID aTrackID, bool aEnabled) {
+    MutexAutoLock lock(mMutex);
+    MediaStream::SetTrackEnabledImpl(aTrackID, aEnabled);
+  }
 
   /**
    * End all tracks and Finish() this stream.  Used to voluntarily revoke access
    * to a LocalMediaStream.
    */
   void EndAllTrackAndFinish();
 
   /**
@@ -932,16 +937,21 @@ public:
    * Also, we've produced output for all streams up to this one. If this stream
    * is not in a cycle, then all its source streams have produced data.
    * Generate output up to mStateComputedTime.
    * This is called only on streams that have not finished.
    */
   virtual void ProduceOutput(GraphTime aFrom, GraphTime aTo) = 0;
   void SetAutofinishImpl(bool aAutofinish) { mAutofinish = aAutofinish; }
 
+  /**
+   * Forward SetTrackEnabled() to the input MediaStream(s) and translate the ID
+   */
+  virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) {};
+
 protected:
   // This state is all accessed only on the media graph thread.
 
   // The list of all inputs that are currently enabled or waiting to be enabled.
   nsTArray<MediaInputPort*> mInputs;
   bool mAutofinish;
   // True if and only if this stream is in a cycle.
   // Updated by MediaStreamGraphImpl::UpdateStreamOrder.
--- a/content/media/MediaStreamTrack.cpp
+++ b/content/media/MediaStreamTrack.cpp
@@ -47,16 +47,13 @@ MediaStreamTrack::GetId(nsAString& aID)
   mID.ToProvidedString(chars);
   aID = NS_ConvertASCIItoUTF16(chars);
 }
 
 void
 MediaStreamTrack::SetEnabled(bool aEnabled)
 {
   mEnabled = aEnabled;
-  MediaStream* stream = mStream->GetStream();
-  if (stream) {
-    stream->SetTrackEnabled(mTrackID, aEnabled);
-  }
+  mStream->SetTrackEnabled(mTrackID, aEnabled);
 }
 
 }
 }
--- a/content/media/TrackUnionStream.h
+++ b/content/media/TrackUnionStream.h
@@ -110,16 +110,27 @@ public:
 
   // Consumers may specify a filtering callback to apply to every input track.
   // Returns true to allow the track to act as an input; false to reject it entirely.
   typedef bool (*TrackIDFilterCallback)(StreamBuffer::Track*);
   void SetTrackIDFilter(TrackIDFilterCallback aCallback) {
     mFilterCallback = aCallback;
   }
 
+  // Forward SetTrackEnabled(output_track_id, enabled) to the Source MediaStream,
+  // translating the output track ID into the correct ID in the source.
+  virtual void ForwardTrackEnabled(TrackID aOutputID, bool aEnabled) {
+    for (int32_t i = mTrackMap.Length() - 1; i >= 0; --i) {
+      if (mTrackMap[i].mOutputTrackID == aOutputID) {
+        mTrackMap[i].mInputPort->GetSource()->
+          SetTrackEnabled(mTrackMap[i].mInputTrackID, aEnabled);
+      }
+    }
+  }
+
 protected:
   TrackIDFilterCallback mFilterCallback;
 
   // Only non-ended tracks are allowed to persist in this map.
   struct TrackMapEntry {
     // mEndOfConsumedInputTicks is the end of the input ticks that we've consumed.
     // 0 if we haven't consumed any yet.
     TrackTicks mEndOfConsumedInputTicks;
--- a/content/media/VideoSegment.h
+++ b/content/media/VideoSegment.h
@@ -102,16 +102,24 @@ public:
     if (!c) {
       return nullptr;
     }
     if (aStart) {
       *aStart = mDuration - c->mDuration;
     }
     return &c->mFrame;
   }
+  // Override default impl
+  virtual void ReplaceWithDisabled() MOZ_OVERRIDE {
+    for (ChunkIterator i(*this);
+         !i.IsEnded(); i.Next()) {
+      VideoChunk& chunk = *i;
+      chunk.SetForceBlack(true);
+    }
+  }
 
   // Segment-generic methods not in MediaSegmentBase
   static Type StaticType() { return VIDEO; }
 };
 
 }
 
 #endif /* MOZILLA_VIDEOSEGMENT_H_ */
--- a/content/media/webrtc/MediaEngineWebRTCVideo.cpp
+++ b/content/media/webrtc/MediaEngineWebRTCVideo.cpp
@@ -607,17 +607,17 @@ MediaEngineWebRTCVideoSource::Shutdown()
 void
 MediaEngineWebRTCVideoSource::AllocImpl() {
   MOZ_ASSERT(NS_IsMainThread());
 
   mDOMCameraControl = new nsDOMCameraControl(mCaptureIndex,
                                              mCameraThread,
                                              this,
                                              this,
-                                             mWindowId);
+                                             nsGlobalWindow::GetInnerWindowWithId(mWindowId));
   mCameraManager->Register(mDOMCameraControl);
 }
 
 void
 MediaEngineWebRTCVideoSource::DeallocImpl() {
   MOZ_ASSERT(NS_IsMainThread());
 
   mNativeCameraControl->ReleaseHardware(this, this);
@@ -657,17 +657,17 @@ MediaEngineWebRTCVideoSource::SnapshotIm
   cameraPosition.altitude = NAN;
   cameraPosition.timestamp = NAN;
 
   mNativeCameraControl->TakePicture(size, 0, NS_LITERAL_STRING("jpeg"), cameraPosition, PR_Now() / 1000000, this, this);
 }
 
 // nsICameraGetCameraCallback
 nsresult
-MediaEngineWebRTCVideoSource::HandleEvent(nsICameraControl* camera) {
+MediaEngineWebRTCVideoSource::HandleEvent(nsISupports* /* unused */) {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter sync(mCallbackMonitor);
   mNativeCameraControl = static_cast<nsGonkCameraControl*>(mDOMCameraControl->GetNativeCameraControl().get());
   mState = kAllocated;
   mCallbackMonitor.Notify();
   return NS_OK;
 }
 
--- a/content/xml/document/public/nsIXMLContentSink.h
+++ b/content/xml/document/public/nsIXMLContentSink.h
@@ -1,17 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 nsIXMLContentSink_h___
 #define nsIXMLContentSink_h___
 
 #include "nsIContentSink.h"
-#include "nsIParserNode.h"
 #include "nsISupports.h"
 
 class nsIDocument;
 class nsIURI;
 class nsIChannel;
 
 #define NS_IXMLCONTENT_SINK_IID \
  { 0x63fedea0, 0x9b0f, 0x4d64, \
--- a/dom/base/WindowNamedPropertiesHandler.cpp
+++ b/dom/base/WindowNamedPropertiesHandler.cpp
@@ -21,17 +21,17 @@ WindowNamedPropertiesHandler::getOwnProp
                                                        JS::MutableHandle<JSPropertyDescriptor> aDesc,
                                                        unsigned aFlags)
 {
   if (!JSID_IS_STRING(aId)) {
     // Nothing to do if we're resolving a non-string property.
     return true;
   }
 
-  JSObject* global = JS_GetGlobalForObject(aCx, aProxy);
+  JS::Rooted<JSObject*> global(aCx, JS_GetGlobalForObject(aCx, aProxy));
   nsresult rv =
     nsDOMClassInfo::ScriptSecurityManager()->CheckPropertyAccess(aCx, global,
                                                                  "Window", aId,
                                                                  nsIXPCSecurityManager::ACCESS_GET_PROPERTY);
   if (NS_FAILED(rv)) {
     // The security check failed. The security manager set a JS exception for
     // us.
     return false;
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -549,18 +549,16 @@ static nsDOMClassInfoData sClassInfoData
                            EVENTTARGET_SCRIPTABLE_FLAGS)
 #endif
 
 #ifdef MOZ_B2G_BT
   NS_DEFINE_CLASSINFO_DATA(BluetoothDevice, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
 #endif
 
-  NS_DEFINE_CLASSINFO_DATA(CameraControl, nsDOMGenericSH,
-                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(CameraCapabilities, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
   NS_DEFINE_CLASSINFO_DATA(LockedFile, nsEventTargetSH,
                            EVENTTARGET_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(CSSFontFeatureValuesRule, nsDOMGenericSH,
                            DOM_DEFAULT_SCRIPTABLE_FLAGS)
 
@@ -1427,20 +1425,16 @@ nsDOMClassInfo::Init()
 #endif
 
 #ifdef MOZ_B2G_BT
   DOM_CLASSINFO_MAP_BEGIN(BluetoothDevice, nsIDOMBluetoothDevice)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMBluetoothDevice)
   DOM_CLASSINFO_MAP_END
 #endif
 
-  DOM_CLASSINFO_MAP_BEGIN(CameraControl, nsICameraControl)
-    DOM_CLASSINFO_MAP_ENTRY(nsICameraControl)
-  DOM_CLASSINFO_MAP_END
-
   DOM_CLASSINFO_MAP_BEGIN(CameraCapabilities, nsICameraCapabilities)
     DOM_CLASSINFO_MAP_ENTRY(nsICameraCapabilities)
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(LockedFile, nsIDOMLockedFile)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMLockedFile)
   DOM_CLASSINFO_MAP_END
 
@@ -2185,22 +2179,23 @@ nsWindowSH::PreCreate(nsISupports *nativ
     return NS_OK;
 
   return SetParentToWindow(win, parentObj);
 }
 
 NS_IMETHODIMP
 nsWindowSH::PostCreatePrototype(JSContext* aCx, JSObject* aProto)
 {
-  nsresult rv = nsDOMClassInfo::PostCreatePrototype(aCx, aProto);
+  JS::Rooted<JSObject*> proto(aCx, aProto);
+
+  nsresult rv = nsDOMClassInfo::PostCreatePrototype(aCx, proto);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // We should probably move this into the CreateInterfaceObjects for Window
   // once it is on WebIDL bindings.
-  JS::Rooted<JSObject*> proto(aCx, aProto);
   WindowNamedPropertiesHandler::Install(aCx, proto);
   return NS_OK;
 }
 
 static nsHTMLDocument*
 GetDocument(JSObject *obj)
 {
   MOZ_ASSERT(js::GetObjectJSClass(obj) == &sHTMLDocumentAllClass);
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -135,17 +135,16 @@ DOMCI_CLASS(MozIccManager)
 #ifdef MOZ_B2G_FM
 DOMCI_CLASS(FMRadio)
 #endif
 
 #ifdef MOZ_B2G_BT
 DOMCI_CLASS(BluetoothDevice)
 #endif
 
-DOMCI_CLASS(CameraControl)
 DOMCI_CLASS(CameraCapabilities)
 
 DOMCI_CLASS(LockedFile)
 
 DOMCI_CLASS(CSSFontFeatureValuesRule)
 
 DOMCI_CLASS(UserDataHandler)
 DOMCI_CLASS(LoadStatus)
--- a/dom/base/nsHistory.cpp
+++ b/dom/base/nsHistory.cpp
@@ -250,17 +250,17 @@ void
 nsHistory::ReplaceState(JSContext* aCx, JS::Handle<JS::Value> aData,
                         const nsAString& aTitle, const nsAString& aUrl,
                         ErrorResult& aRv)
 {
   PushOrReplaceState(aCx, aData, aTitle, aUrl, aRv, true);
 }
 
 void
-nsHistory::PushOrReplaceState(JSContext* aCx, JS::Value aData,
+nsHistory::PushOrReplaceState(JSContext* aCx, JS::Handle<JS::Value> aData,
                               const nsAString& aTitle, const nsAString& aUrl,
                               ErrorResult& aRv, bool aReplace)
 {
   nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mInnerWindow));
   if (!win) {
     aRv.Throw(NS_ERROR_NOT_AVAILABLE);
 
     return;
--- a/dom/base/nsHistory.h
+++ b/dom/base/nsHistory.h
@@ -51,17 +51,17 @@ public:
 protected:
   nsIDocShell *GetDocShell() const {
     nsCOMPtr<nsPIDOMWindow> win(do_QueryReferent(mInnerWindow));
     if (!win)
       return nullptr;
     return win->GetDocShell();
   }
 
-  void PushOrReplaceState(JSContext* aCx, JS::Value aData,
+  void PushOrReplaceState(JSContext* aCx, JS::Handle<JS::Value> aData,
                           const nsAString& aTitle, const nsAString& aUrl,
                           mozilla::ErrorResult& aRv, bool aReplace);
 
   already_AddRefed<nsISHistory> GetSessionHistory() const;
 
   nsCOMPtr<nsIWeakReference> mInnerWindow;
 };
 
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -171,16 +171,24 @@ DOMInterfaces = {
     'headerFile': 'CallEvent.h',
 },
 
 'CallsList': {
     'nativeType': 'mozilla::dom::telephony::CallsList',
     'headerFile': 'CallsList.h',
 },
 
+'CameraControl': {
+    'nativeType': 'mozilla::nsDOMCameraControl',
+    'headerFile': 'DOMCameraControl.h',
+    'binaryNames': {
+        "release": "ReleaseHardware"
+    }
+},
+
 'CameraManager': {
     'nativeType': 'nsDOMCameraManager',
     'headerFile': 'DOMCameraManager.h'
 },
 
 'CanvasRenderingContext2D': {
     'implicitJSContext': [
         'createImageData', 'getImageData', 'mozDash'
@@ -1810,8 +1818,18 @@ addExternalIface('UserDataHandler')
 addExternalIface('Window')
 addExternalIface('XPathResult', nativeType='nsISupports')
 addExternalIface('XPathExpression')
 addExternalIface('XPathNSResolver')
 addExternalIface('XULCommandDispatcher')
 addExternalIface('DataTransfer', notflattened=True)
 addExternalIface('GetCameraCallback', nativeType='nsICameraGetCameraCallback', headerFile='nsIDOMCameraManager.h')
 addExternalIface('CameraErrorCallback', nativeType='nsICameraErrorCallback', headerFile='nsIDOMCameraManager.h')
+addExternalIface('CameraCapabilities', nativeType='nsICameraCapabilities', headerFile='nsIDOMCameraManager.h')
+addExternalIface('CameraAutoFocusCallback', nativeType='nsICameraAutoFocusCallback', headerFile='nsIDOMCameraManager.h')
+addExternalIface('CameraShutterCallback', nativeType='nsICameraShutterCallback', headerFile='nsIDOMCameraManager.h')
+addExternalIface('CameraClosedCallback', nativeType='nsICameraClosedCallback', headerFile='nsIDOMCameraManager.h')
+addExternalIface('CameraTakePictureCallback', nativeType='nsICameraTakePictureCallback', headerFile='nsIDOMCameraManager.h')
+addExternalIface('CameraReleaseCallback', nativeType='nsICameraReleaseCallback', headerFile='nsIDOMCameraManager.h')
+addExternalIface('CameraStartRecordingCallback', nativeType='nsICameraStartRecordingCallback', headerFile='nsIDOMCameraManager.h')
+addExternalIface('CameraPreviewStateChange', nativeType='nsICameraPreviewStateChange', headerFile='nsIDOMCameraManager.h')
+addExternalIface('CameraPreviewStreamCallback', nativeType='nsICameraPreviewStreamCallback', headerFile='nsIDOMCameraManager.h')
+addExternalIface('CameraRecorderStateChange', nativeType='nsICameraRecorderStateChange', headerFile='nsIDOMCameraManager.h')
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -75,17 +75,24 @@ class CGNativePropertyHooks(CGThing):
     """
     def __init__(self, descriptor, properties):
         CGThing.__init__(self)
         self.descriptor = descriptor
         self.properties = properties
     def declare(self):
         if self.descriptor.workers:
             return ""
-        return "extern const NativePropertyHooks* sNativePropertyHooks;\n"
+        return """// We declare this as an array so that retrieving a pointer to this
+// binding's property hooks only requires compile/link-time resolvable
+// address arithmetic.  Declaring it as a pointer instead would require
+// doing a run-time load to fetch a pointer to this binding's property
+// hooks.  And then structures which embedded a pointer to this structure
+// would require a run-time load for proper initialization, which would
+// then induce static constructors.  Lots of static constructors.
+extern const NativePropertyHooks sNativePropertyHooks[];"""
     def define(self):
         if self.descriptor.workers:
             return ""
         if self.descriptor.concrete and self.descriptor.proxy:
             resolveOwnProperty = "ResolveOwnProperty"
             enumerateOwnProperties = "EnumerateOwnProperties"
         elif self.descriptor.interface.getExtendedAttribute("NeedNewResolve"):
             resolveOwnProperty = "ResolveOwnPropertyViaNewresolve"
@@ -120,20 +127,19 @@ class CGNativePropertyHooks(CGThing):
                                             CGWrapper(CGList([CGGeneric(regular),
                                                               CGGeneric(chrome)],
                                                              ", "),
                                                       pre="{ ", post=" }"),
                                             CGGeneric(prototypeID),
                                             CGGeneric(constructorID),
                                             CGGeneric(parentHooks)],
                                            ",\n")),
-                         pre="static const NativePropertyHooks sNativePropertyHooksStruct = {\n",
+                         pre="const NativePropertyHooks sNativePropertyHooks[] = { {\n",
                          post=("\n"
-                               "};\n"
-                               "const NativePropertyHooks* sNativePropertyHooks = &sNativePropertyHooksStruct;\n")).define()
+                               "} };\n")).define()
 
 def NativePropertyHooks(descriptor):
     return "&sWorkerNativePropertyHooks" if descriptor.workers else "sNativePropertyHooks"
 
 def DOMClass(descriptor):
         protoList = ['prototypes::id::' + proto for proto in descriptor.prototypeChain]
         # Pad out the list to the right length with _ID_Count so we
         # guarantee that all the lists are the same length.  _ID_Count
--- a/dom/camera/DOMCameraControl.cpp
+++ b/dom/camera/DOMCameraControl.cpp
@@ -2,280 +2,285 @@
  * 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/. */
 
 #include "base/basictypes.h"
 #include "nsCOMPtr.h"
 #include "nsDOMClassInfo.h"
 #include "jsapi.h"
 #include "nsThread.h"
+#include "DeviceStorage.h"
+#include "mozilla/dom/CameraControlBinding.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/Services.h"
 #include "mozilla/unused.h"
 #include "nsIObserverService.h"
 #include "nsIDOMDeviceStorage.h"
 #include "nsXULAppAPI.h"
 #include "DOMCameraManager.h"
 #include "DOMCameraCapabilities.h"
 #include "DOMCameraControl.h"
 #include "CameraCommon.h"
 #include "mozilla/dom/CameraManagerBinding.h"
 #include "mozilla/dom/BindingUtils.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
-DOMCI_DATA(CameraControl, nsICameraControl)
-
-NS_IMPL_CYCLE_COLLECTION_1(nsDOMCameraControl,
-                           mDOMCapabilities)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_2(nsDOMCameraControl, mDOMCapabilities, mWindow)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDOMCameraControl)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
-  NS_INTERFACE_MAP_ENTRY(nsICameraControl)
-  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CameraControl)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDOMCameraControl)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDOMCameraControl)
 
 nsDOMCameraControl::~nsDOMCameraControl()
 {
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 }
 
-/* readonly attribute nsICameraCapabilities capabilities; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetCapabilities(nsICameraCapabilities** aCapabilities)
+JSObject*
+nsDOMCameraControl::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return CameraControlBinding::Wrap(aCx, aScope, this);
+}
+
+nsICameraCapabilities*
+nsDOMCameraControl::Capabilities()
 {
   if (!mDOMCapabilities) {
     mDOMCapabilities = new DOMCameraCapabilities(mCameraControl);
   }
 
-  nsCOMPtr<nsICameraCapabilities> capabilities = mDOMCapabilities;
-  capabilities.forget(aCapabilities);
-  return NS_OK;
+  return mDOMCapabilities;
 }
 
-/* attribute DOMString effect; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetEffect(nsAString& aEffect)
+void
+nsDOMCameraControl::GetEffect(nsString& aEffect, ErrorResult& aRv)
 {
-  return mCameraControl->Get(CAMERA_PARAM_EFFECT, aEffect);
+  aRv = mCameraControl->Get(CAMERA_PARAM_EFFECT, aEffect);
 }
-NS_IMETHODIMP
-nsDOMCameraControl::SetEffect(const nsAString& aEffect)
+void
+nsDOMCameraControl::SetEffect(const nsAString& aEffect, ErrorResult& aRv)
 {
-  return mCameraControl->Set(CAMERA_PARAM_EFFECT, aEffect);
+  aRv = mCameraControl->Set(CAMERA_PARAM_EFFECT, aEffect);
 }
 
-/* attribute DOMString whiteBalanceMode; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetWhiteBalanceMode(nsAString& aWhiteBalanceMode)
+void
+nsDOMCameraControl::GetWhiteBalanceMode(nsString& aWhiteBalanceMode, ErrorResult& aRv)
 {
-  return mCameraControl->Get(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
+  aRv = mCameraControl->Get(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
 }
-NS_IMETHODIMP
-nsDOMCameraControl::SetWhiteBalanceMode(const nsAString& aWhiteBalanceMode)
+void
+nsDOMCameraControl::SetWhiteBalanceMode(const nsAString& aWhiteBalanceMode, ErrorResult& aRv)
 {
-  return mCameraControl->Set(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
+  aRv = mCameraControl->Set(CAMERA_PARAM_WHITEBALANCE, aWhiteBalanceMode);
 }
 
-/* attribute DOMString sceneMode; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetSceneMode(nsAString& aSceneMode)
+void
+nsDOMCameraControl::GetSceneMode(nsString& aSceneMode, ErrorResult& aRv)
+{
+  aRv = mCameraControl->Get(CAMERA_PARAM_SCENEMODE, aSceneMode);
+}
+void
+nsDOMCameraControl::SetSceneMode(const nsAString& aSceneMode, ErrorResult& aRv)
 {
-  return mCameraControl->Get(CAMERA_PARAM_SCENEMODE, aSceneMode);
+  aRv = mCameraControl->Set(CAMERA_PARAM_SCENEMODE, aSceneMode);
 }
-NS_IMETHODIMP
-nsDOMCameraControl::SetSceneMode(const nsAString& aSceneMode)
+
+void
+nsDOMCameraControl::GetFlashMode(nsString& aFlashMode, ErrorResult& aRv)
 {
-  return mCameraControl->Set(CAMERA_PARAM_SCENEMODE, aSceneMode);
+  aRv = mCameraControl->Get(CAMERA_PARAM_FLASHMODE, aFlashMode);
+}
+void
+nsDOMCameraControl::SetFlashMode(const nsAString& aFlashMode, ErrorResult& aRv)
+{
+  aRv = mCameraControl->Set(CAMERA_PARAM_FLASHMODE, aFlashMode);
 }
 
-/* attribute DOMString flashMode; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetFlashMode(nsAString& aFlashMode)
+void
+nsDOMCameraControl::GetFocusMode(nsString& aFocusMode, ErrorResult& aRv)
 {
-  return mCameraControl->Get(CAMERA_PARAM_FLASHMODE, aFlashMode);
+  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSMODE, aFocusMode);
 }
-NS_IMETHODIMP
-nsDOMCameraControl::SetFlashMode(const nsAString& aFlashMode)
+void
+nsDOMCameraControl::SetFocusMode(const nsAString& aFocusMode, ErrorResult& aRv)
 {
-  return mCameraControl->Set(CAMERA_PARAM_FLASHMODE, aFlashMode);
+  aRv = mCameraControl->Set(CAMERA_PARAM_FOCUSMODE, aFocusMode);
 }
 
-/* attribute DOMString focusMode; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetFocusMode(nsAString& aFocusMode)
+double
+nsDOMCameraControl::GetZoom(ErrorResult& aRv)
 {
-  return mCameraControl->Get(CAMERA_PARAM_FOCUSMODE, aFocusMode);
-}
-NS_IMETHODIMP
-nsDOMCameraControl::SetFocusMode(const nsAString& aFocusMode)
-{
-  return mCameraControl->Set(CAMERA_PARAM_FOCUSMODE, aFocusMode);
+  double zoom;
+  aRv = mCameraControl->Get(CAMERA_PARAM_ZOOM, &zoom);
+  return zoom;
 }
 
-/* attribute double zoom; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetZoom(double* aZoom)
+void
+nsDOMCameraControl::SetZoom(double aZoom, ErrorResult& aRv)
 {
-  return mCameraControl->Get(CAMERA_PARAM_ZOOM, aZoom);
-}
-NS_IMETHODIMP
-nsDOMCameraControl::SetZoom(double aZoom)
-{
-  return mCameraControl->Set(CAMERA_PARAM_ZOOM, aZoom);
+  aRv = mCameraControl->Set(CAMERA_PARAM_ZOOM, aZoom);
 }
 
 /* attribute jsval meteringAreas; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetMeteringAreas(JSContext* cx, JS::Value* aMeteringAreas)
+JS::Value
+nsDOMCameraControl::GetMeteringAreas(JSContext* cx, ErrorResult& aRv)
 {
-  return mCameraControl->Get(cx, CAMERA_PARAM_METERINGAREAS, aMeteringAreas);
+  JS::Rooted<JS::Value> areas(cx);
+  aRv = mCameraControl->Get(cx, CAMERA_PARAM_METERINGAREAS, areas.address());
+  return areas;
 }
-NS_IMETHODIMP
-nsDOMCameraControl::SetMeteringAreas(JSContext* cx, const JS::Value& aMeteringAreas)
+
+void
+nsDOMCameraControl::SetMeteringAreas(JSContext* cx, JS::Handle<JS::Value> aMeteringAreas, ErrorResult& aRv)
 {
-  return mCameraControl->SetMeteringAreas(cx, aMeteringAreas);
+  aRv = mCameraControl->SetMeteringAreas(cx, aMeteringAreas);
 }
 
-/* attribute jsval focusAreas; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetFocusAreas(JSContext* cx, JS::Value* aFocusAreas)
+JS::Value
+nsDOMCameraControl::GetFocusAreas(JSContext* cx, ErrorResult& aRv)
 {
-  return mCameraControl->Get(cx, CAMERA_PARAM_FOCUSAREAS, aFocusAreas);
+  JS::Rooted<JS::Value> value(cx);
+  aRv = mCameraControl->Get(cx, CAMERA_PARAM_FOCUSAREAS, value.address());
+  return value;
 }
-NS_IMETHODIMP
-nsDOMCameraControl::SetFocusAreas(JSContext* cx, const JS::Value& aFocusAreas)
+void
+nsDOMCameraControl::SetFocusAreas(JSContext* cx, JS::Handle<JS::Value> aFocusAreas, ErrorResult& aRv)
 {
-  return mCameraControl->SetFocusAreas(cx, aFocusAreas);
+  aRv = mCameraControl->SetFocusAreas(cx, aFocusAreas);
 }
 
-/* readonly attribute double focalLength; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetFocalLength(double* aFocalLength)
+double
+nsDOMCameraControl::GetFocalLength(ErrorResult& aRv)
 {
-  return mCameraControl->Get(CAMERA_PARAM_FOCALLENGTH, aFocalLength);
+  double focalLength;
+  aRv = mCameraControl->Get(CAMERA_PARAM_FOCALLENGTH, &focalLength);
+  return focalLength;
 }
 
-/* readonly attribute double focusDistanceNear; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetFocusDistanceNear(double* aFocusDistanceNear)
+double
+nsDOMCameraControl::GetFocusDistanceNear(ErrorResult& aRv)
 {
-  return mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCENEAR, aFocusDistanceNear);
+  double distance;
+  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCENEAR, &distance);
+  return distance;
 }
 
-/* readonly attribute double focusDistanceOptimum; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetFocusDistanceOptimum(double* aFocusDistanceOptimum)
+double
+nsDOMCameraControl::GetFocusDistanceOptimum(ErrorResult& aRv)
 {
-  return mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, aFocusDistanceOptimum);
+  double distance;
+  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEOPTIMUM, &distance);
+  return distance;
 }
 
-/* readonly attribute double focusDistanceFar; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetFocusDistanceFar(double* aFocusDistanceFar)
+double
+nsDOMCameraControl::GetFocusDistanceFar(ErrorResult& aRv)
 {
-  return mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEFAR, aFocusDistanceFar);
+  double distance;
+  aRv = mCameraControl->Get(CAMERA_PARAM_FOCUSDISTANCEFAR, &distance);
+  return distance;
 }
 
-/* void setExposureCompensation (const JS::Value& aCompensation, JSContext* cx); */
-NS_IMETHODIMP
-nsDOMCameraControl::SetExposureCompensation(const JS::Value& aCompensation, JSContext* cx)
+void
+nsDOMCameraControl::SetExposureCompensation(const Optional<double>& aCompensation, ErrorResult& aRv)
 {
-  if (aCompensation.isNullOrUndefined()) {
+  if (!aCompensation.WasPassed()) {
     // use NaN to switch the camera back into auto mode
-    return mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, NAN);
+    aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, NAN);
   }
 
+  aRv = mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, aCompensation.Value());
+}
+
+double
+nsDOMCameraControl::GetExposureCompensation(ErrorResult& aRv)
+{
   double compensation;
-  if (!JS_ValueToNumber(cx, aCompensation, &compensation)) {
-    return NS_ERROR_INVALID_ARG;
-  }
-
-  return mCameraControl->Set(CAMERA_PARAM_EXPOSURECOMPENSATION, compensation);
+  aRv = mCameraControl->Get(CAMERA_PARAM_EXPOSURECOMPENSATION, &compensation);
+  return compensation;
 }
 
-/* readonly attribute double exposureCompensation; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetExposureCompensation(double* aExposureCompensation)
+already_AddRefed<nsICameraShutterCallback>
+nsDOMCameraControl::GetOnShutter(ErrorResult& aRv)
 {
-  return mCameraControl->Get(CAMERA_PARAM_EXPOSURECOMPENSATION, aExposureCompensation);
+  nsCOMPtr<nsICameraShutterCallback> cb;
+  aRv = mCameraControl->Get(getter_AddRefs(cb));
+  return cb.forget();
 }
 
-/* attribute nsICameraShutterCallback onShutter; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetOnShutter(nsICameraShutterCallback** aOnShutter)
+void
+nsDOMCameraControl::SetOnShutter(nsICameraShutterCallback* aOnShutter,
+                                 ErrorResult& aRv)
 {
-  return mCameraControl->Get(aOnShutter);
-}
-NS_IMETHODIMP
-nsDOMCameraControl::SetOnShutter(nsICameraShutterCallback* aOnShutter)
-{
-  return mCameraControl->Set(aOnShutter);
+  aRv = mCameraControl->Set(aOnShutter);
 }
 
 /* attribute nsICameraClosedCallback onClosed; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetOnClosed(nsICameraClosedCallback** aOnClosed)
+already_AddRefed<nsICameraClosedCallback>
+nsDOMCameraControl::GetOnClosed(ErrorResult& aRv)
 {
-  return mCameraControl->Get(aOnClosed);
+  nsCOMPtr<nsICameraClosedCallback> onClosed;
+  aRv = mCameraControl->Get(getter_AddRefs(onClosed));
+  return onClosed.forget();
 }
-NS_IMETHODIMP
-nsDOMCameraControl::SetOnClosed(nsICameraClosedCallback* aOnClosed)
+
+void
+nsDOMCameraControl::SetOnClosed(nsICameraClosedCallback* aOnClosed,
+                                ErrorResult& aRv)
 {
-  return mCameraControl->Set(aOnClosed);
+  aRv = mCameraControl->Set(aOnClosed);
 }
 
-/* attribute nsICameraRecorderStateChange onRecorderStateChange; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetOnRecorderStateChange(nsICameraRecorderStateChange** aOnRecorderStateChange)
+already_AddRefed<nsICameraRecorderStateChange>
+nsDOMCameraControl::GetOnRecorderStateChange(ErrorResult& aRv)
 {
-  return mCameraControl->Get(aOnRecorderStateChange);
-}
-NS_IMETHODIMP
-nsDOMCameraControl::SetOnRecorderStateChange(nsICameraRecorderStateChange* aOnRecorderStateChange)
-{
-  return mCameraControl->Set(aOnRecorderStateChange);
+  nsCOMPtr<nsICameraRecorderStateChange> cb;
+  aRv = mCameraControl->Get(getter_AddRefs(cb));
+  return cb.forget();
 }
 
-/* attribute nsICameraPreviewStateChange onPreviewStateChange; */
-NS_IMETHODIMP
-nsDOMCameraControl::GetOnPreviewStateChange(nsICameraPreviewStateChange** aOnPreviewStateChange)
+void
+nsDOMCameraControl::SetOnRecorderStateChange(nsICameraRecorderStateChange* aOnRecorderStateChange,
+                                             ErrorResult& aRv)
 {
-  return mCameraControl->Get(aOnPreviewStateChange);
-}
-NS_IMETHODIMP
-nsDOMCameraControl::SetOnPreviewStateChange(nsICameraPreviewStateChange* aOnPreviewStateChange)
-{
-  return mCameraControl->Set(aOnPreviewStateChange);
+  aRv = mCameraControl->Set(aOnRecorderStateChange);
 }
 
-/* [implicit_jscontext] void startRecording (in jsval aOptions, in nsIDOMDeviceStorage storageArea, in DOMString filename, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
-NS_IMETHODIMP
-nsDOMCameraControl::StartRecording(const JS::Value& aOptions, nsIDOMDeviceStorage* storageArea, const nsAString& filename, nsICameraStartRecordingCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx)
+void
+nsDOMCameraControl::StartRecording(JSContext* aCx,
+                                   JS::Handle<JS::Value> aOptions,
+                                   nsDOMDeviceStorage& storageArea,
+                                   const nsAString& filename,
+                                   nsICameraStartRecordingCallback* onSuccess,
+                                   const Optional<nsICameraErrorCallback*>& onError,
+                                   ErrorResult& aRv)
 {
-  NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
-  NS_ENSURE_TRUE(storageArea, NS_ERROR_INVALID_ARG);
-
+  MOZ_ASSERT(onSuccess, "no onSuccess handler passed");
   mozilla::idl::CameraStartRecordingOptions options;
 
   // Default values, until the dictionary parser can handle them.
   options.rotation = 0;
   options.maxFileSizeBytes = 0;
   options.maxVideoLengthMs = 0;
-  nsresult rv = options.Init(cx, &aOptions);
-  NS_ENSURE_SUCCESS(rv, rv);
+  aRv = options.Init(aCx, aOptions.address());
+  if (aRv.Failed()) {
+    return;
+  }
 
-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   if (!obs) {
     NS_WARNING("Could not get the Observer service for CameraControl::StartRecording.");
-    return NS_ERROR_FAILURE;
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
   }
 
   obs->NotifyObservers(nullptr,
                        "recording-device-events",
                        NS_LITERAL_STRING("starting").get());
   // Forward recording events to parent process.
   // The events are gathered in chrome process and used for recording indicator
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
@@ -291,29 +296,31 @@ nsDOMCameraControl::StartRecording(const
       // Video recording doesn't output any sound, so it's not necessary to check canPlay.
       bool canPlay;
       mAudioChannelAgent->StartPlaying(&canPlay);
     }
   }
   #endif
 
   nsCOMPtr<nsIFile> folder;
-  rv = storageArea->GetRootDirectoryForFile(filename, getter_AddRefs(folder));
-  NS_ENSURE_SUCCESS(rv, rv);
-  return mCameraControl->StartRecording(&options, folder, filename, onSuccess, onError);
+  aRv = storageArea.GetRootDirectoryForFile(filename, getter_AddRefs(folder));
+  if (aRv.Failed()) {
+    return;
+  }
+  aRv = mCameraControl->StartRecording(&options, folder, filename, onSuccess,
+                                       onError.WasPassed() ? onError.Value() : nullptr);
 }
 
-/* void stopRecording (); */
-NS_IMETHODIMP
-nsDOMCameraControl::StopRecording()
+void
+nsDOMCameraControl::StopRecording(ErrorResult& aRv)
 {
-  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   if (!obs) {
     NS_WARNING("Could not get the Observer service for CameraControl::StopRecording.");
-    return NS_ERROR_FAILURE;
+    aRv.Throw(NS_ERROR_FAILURE);
   }
 
   obs->NotifyObservers(nullptr,
                        "recording-device-events",
                        NS_LITERAL_STRING("shutdown").get());
   // Forward recording events to parent process.
   // The events are gathered in chrome process and used for recording indicator
   if (XRE_GetProcessType() != GeckoProcessType_Default) {
@@ -322,96 +329,128 @@ nsDOMCameraControl::StopRecording()
 
   #ifdef MOZ_B2G
   if (mAudioChannelAgent) {
     mAudioChannelAgent->StopPlaying();
     mAudioChannelAgent = nullptr;
   }
   #endif
 
-  return mCameraControl->StopRecording();
+  aRv = mCameraControl->StopRecording();
 }
 
-/* [implicit_jscontext] void getPreviewStream (in jsval aOptions, in nsICameraPreviewStreamCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
-NS_IMETHODIMP
-nsDOMCameraControl::GetPreviewStream(const JS::Value& aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx)
+void
+nsDOMCameraControl::GetPreviewStream(JSContext* aCx,
+                                     JS::Handle<JS::Value> aOptions,
+                                     nsICameraPreviewStreamCallback* onSuccess,
+                                     const Optional<nsICameraErrorCallback*>& onError,
+                                     ErrorResult& aRv)
 {
-  NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
+  mozilla::idl::CameraSize size;
+  aRv = size.Init(aCx, aOptions.address());
+  if (aRv.Failed()) {
+    return;
+  }
 
-  mozilla::idl::CameraSize size;
-  nsresult rv = size.Init(cx, &aOptions);
-  NS_ENSURE_SUCCESS(rv, rv);
+  aRv = mCameraControl->GetPreviewStream(size, onSuccess,
+                                         onError.WasPassed()
+                                         ? onError.Value() : nullptr);
+}
 
-  return mCameraControl->GetPreviewStream(size, onSuccess, onError);
+void
+nsDOMCameraControl::ResumePreview(ErrorResult& aRv)
+{
+  aRv = mCameraControl->StartPreview(nullptr);
 }
 
-/* void resumePreview(); */
-NS_IMETHODIMP
-nsDOMCameraControl::ResumePreview()
+already_AddRefed<nsICameraPreviewStateChange>
+nsDOMCameraControl::GetOnPreviewStateChange() const
 {
-  return mCameraControl->StartPreview(nullptr);
+  nsCOMPtr<nsICameraPreviewStateChange> cb;
+  mCameraControl->Get(getter_AddRefs(cb));
+  return cb.forget();
+}
+
+void
+nsDOMCameraControl::SetOnPreviewStateChange(nsICameraPreviewStateChange* aCb)
+{
+  mCameraControl->Set(aCb);
 }
 
-/* void autoFocus (in nsICameraAutoFocusCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
-NS_IMETHODIMP
-nsDOMCameraControl::AutoFocus(nsICameraAutoFocusCallback* onSuccess, nsICameraErrorCallback* onError)
+void
+nsDOMCameraControl::AutoFocus(nsICameraAutoFocusCallback* onSuccess,
+                              const Optional<nsICameraErrorCallback*>& onError,
+                              ErrorResult& aRv)
 {
-  NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
-  return mCameraControl->AutoFocus(onSuccess, onError);
+  aRv = mCameraControl->AutoFocus(onSuccess,
+                                  onError.WasPassed() ? onError.Value() : nullptr);
 }
 
-/* void takePicture (in jsval aOptions, in nsICameraTakePictureCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
-NS_IMETHODIMP
-nsDOMCameraControl::TakePicture(const JS::Value& aOptions, nsICameraTakePictureCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx)
+void
+nsDOMCameraControl::TakePicture(JSContext* aCx,
+                                const CameraPictureOptions& aOptions,
+                                nsICameraTakePictureCallback* onSuccess,
+                                const Optional<nsICameraErrorCallback*>& aOnError,
+                                ErrorResult& aRv)
 {
-  NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
-
-  RootedDictionary<CameraPictureOptions> options(cx);
   mozilla::idl::CameraSize           size;
   mozilla::idl::CameraPosition       pos;
 
-  JS::Rooted<JS::Value> optionVal(cx, aOptions);
-  if (!options.Init(cx, optionVal)) {
-    return NS_ERROR_FAILURE;
+  aRv = size.Init(aCx, &aOptions.mPictureSize);
+  if (aRv.Failed()) {
+    return;
   }
 
-  nsresult rv = size.Init(cx, &options.mPictureSize);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   /**
    * Default values, until the dictionary parser can handle them.
    * NaN indicates no value provided.
    */
   pos.latitude = NAN;
   pos.longitude = NAN;
   pos.altitude = NAN;
   pos.timestamp = NAN;
-  rv = pos.Init(cx, &options.mPosition);
-  NS_ENSURE_SUCCESS(rv, rv);
+  aRv = pos.Init(aCx, &aOptions.mPosition);
+  if (aRv.Failed()) {
+    return;
+  }
 
-  return mCameraControl->TakePicture(size, options.mRotation, options.mFileFormat, pos, options.mDateTime, onSuccess, onError);
+  nsICameraErrorCallback* onError =
+    aOnError.WasPassed() ? aOnError.Value() : nullptr;
+  aRv = mCameraControl->TakePicture(size, aOptions.mRotation,
+                                    aOptions.mFileFormat, pos,
+                                    aOptions.mDateTime, onSuccess, onError);
 }
 
-/* [implicit_jscontext] void GetPreviewStreamVideoMode (in jsval aOptions, in nsICameraPreviewStreamCallback onSuccess, [optional] in nsICameraErrorCallback onError); */
-NS_IMETHODIMP
-nsDOMCameraControl::GetPreviewStreamVideoMode(const JS::Value& aOptions, nsICameraPreviewStreamCallback* onSuccess, nsICameraErrorCallback* onError, JSContext* cx)
+void
+nsDOMCameraControl::GetPreviewStreamVideoMode(JSContext* aCx,
+                                              JS::Handle<JS::Value> aOptions,
+                                              nsICameraPreviewStreamCallback* onSuccess,
+                                              const Optional<nsICameraErrorCallback*>& onError,
+                                              ErrorResult& aRv)
 {
-  NS_ENSURE_TRUE(onSuccess, NS_ERROR_INVALID_ARG);
+  mozilla::idl::CameraRecorderOptions options;
+  aRv = options.Init(aCx, aOptions.address());
+  if (aRv.Failed()) {
+    return;
+  }
 
-  mozilla::idl::CameraRecorderOptions options;
-  nsresult rv = options.Init(cx, &aOptions);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  return mCameraControl->GetPreviewStreamVideoMode(&options, onSuccess, onError);
+  aRv = mCameraControl->GetPreviewStreamVideoMode(&options, onSuccess,
+                                                  onError.WasPassed()
+                                                  ? onError.Value() : nullptr);
 }
 
-NS_IMETHODIMP
-nsDOMCameraControl::ReleaseHardware(nsICameraReleaseCallback* onSuccess, nsICameraErrorCallback* onError)
+void
+nsDOMCameraControl::ReleaseHardware(const Optional<nsICameraReleaseCallback*>& onSuccess,
+                                    const Optional<nsICameraErrorCallback*>& onError,
+                                    ErrorResult& aRv)
 {
-  return mCameraControl->ReleaseHardware(onSuccess, onError);
+  aRv =
+    mCameraControl->ReleaseHardware(
+        onSuccess.WasPassed() ? onSuccess.Value() : nullptr,
+        onError.WasPassed() ? onError.Value() : nullptr);
 }
 
 class GetCameraResult : public nsRunnable
 {
 public:
   GetCameraResult(nsDOMCameraControl* aDOMCameraControl,
     nsresult aResult,
     const nsMainThreadPtrHandle<nsICameraGetCameraCallback>& onSuccess,
--- a/dom/camera/DOMCameraControl.h
+++ b/dom/camera/DOMCameraControl.h
@@ -11,47 +11,104 @@
 #include "DictionaryHelpers.h"
 #include "ICameraControl.h"
 #include "DOMCameraPreview.h"
 #include "nsIDOMCameraManager.h"
 #include "CameraCommon.h"
 #include "AudioChannelAgent.h"
 #include "nsProxyRelease.h"
 
+class nsDOMDeviceStorage;
+class nsPIDOMWindow;
+
 namespace mozilla {
+namespace dom {
+class CameraPictureOptions;
+template<typename T> class Optional;
+}
+class ErrorResult;
 
 // Main camera control.
-class nsDOMCameraControl : public nsICameraControl
+class nsDOMCameraControl MOZ_FINAL : public nsISupports,
+                                     public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_CLASS(nsDOMCameraControl)
-  NS_DECL_NSICAMERACONTROL
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(nsDOMCameraControl)
 
   nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread,
                      nsICameraGetCameraCallback* onSuccess,
-                     nsICameraErrorCallback* onError, uint64_t aWindowId);
+                     nsICameraErrorCallback* onError, nsPIDOMWindow* aWindow);
   nsresult Result(nsresult aResult,
                   const nsMainThreadPtrHandle<nsICameraGetCameraCallback>& onSuccess,
                   const nsMainThreadPtrHandle<nsICameraErrorCallback>& onError,
                   uint64_t aWindowId);
   nsRefPtr<ICameraControl> GetNativeCameraControl();
 
   void Shutdown();
 
+  nsPIDOMWindow* GetParentObject() const { return mWindow; }
+
+  // WebIDL
+  nsICameraCapabilities* Capabilities();
+  void GetEffect(nsString& aEffect, ErrorResult& aRv);
+  void SetEffect(const nsAString& aEffect, ErrorResult& aRv);
+  void GetWhiteBalanceMode(nsString& aMode, ErrorResult& aRv);
+  void SetWhiteBalanceMode(const nsAString& aMode, ErrorResult& aRv);
+  void GetSceneMode(nsString& aMode, ErrorResult& aRv);
+  void SetSceneMode(const nsAString& aMode, ErrorResult& aRv);
+  void GetFlashMode(nsString& aMode, ErrorResult& aRv);
+  void SetFlashMode(const nsAString& aMode, ErrorResult& aRv);
+  void GetFocusMode(nsString& aMode, ErrorResult& aRv);
+  void SetFocusMode(const nsAString& aMode, ErrorResult& aRv);
+  double GetZoom(ErrorResult& aRv);
+  void SetZoom(double aZoom, ErrorResult& aRv);
+  JS::Value GetMeteringAreas(JSContext* aCx, ErrorResult& aRv);
+  void SetMeteringAreas(JSContext* aCx, JS::Handle<JS::Value> aAreas, ErrorResult& aRv);
+  JS::Value GetFocusAreas(JSContext* aCx, ErrorResult& aRv);
+  void SetFocusAreas(JSContext* aCx, JS::Handle<JS::Value> aAreas, ErrorResult& aRv);
+  double GetFocalLength(ErrorResult& aRv);
+  double GetFocusDistanceNear(ErrorResult& aRv);
+  double GetFocusDistanceOptimum(ErrorResult& aRv);
+  double GetFocusDistanceFar(ErrorResult& aRv);
+  void SetExposureCompensation(const dom::Optional<double>& aCompensation, ErrorResult& aRv);
+  double GetExposureCompensation(ErrorResult& aRv);
+  already_AddRefed<nsICameraShutterCallback> GetOnShutter(ErrorResult& aRv);
+  void SetOnShutter(nsICameraShutterCallback* aCb, ErrorResult& aRv);
+  already_AddRefed<nsICameraClosedCallback> GetOnClosed(ErrorResult& aRv);
+  void SetOnClosed(nsICameraClosedCallback* aCb, ErrorResult& aRv);
+  already_AddRefed<nsICameraRecorderStateChange> GetOnRecorderStateChange(ErrorResult& aRv);
+  void SetOnRecorderStateChange(nsICameraRecorderStateChange* aCb, ErrorResult& aRv);
+  void AutoFocus(nsICameraAutoFocusCallback* aOnSuccess, const dom::Optional<nsICameraErrorCallback*>& aOnErro, ErrorResult& aRvr);
+  void TakePicture(JSContext* aCx, const dom::CameraPictureOptions& aOptions,
+                   nsICameraTakePictureCallback* onSuccess,
+                   const dom::Optional<nsICameraErrorCallback* >& onError,
+                   ErrorResult& aRv);
+  already_AddRefed<nsICameraPreviewStateChange> GetOnPreviewStateChange() const;
+  void SetOnPreviewStateChange(nsICameraPreviewStateChange* aOnStateChange);
+  void GetPreviewStreamVideoMode(JSContext* cx, JS::Handle<JS::Value> aOptions, nsICameraPreviewStreamCallback* onSuccess, const dom::Optional<nsICameraErrorCallback* >& onError, ErrorResult& aRv);
+  void StartRecording(JSContext* cx, JS::Handle<JS::Value> aOptions, nsDOMDeviceStorage& storageArea, const nsAString& filename, nsICameraStartRecordingCallback* onSuccess, const dom::Optional<nsICameraErrorCallback* >& onError, ErrorResult& aRv);
+  void StopRecording(ErrorResult& aRv);
+  void GetPreviewStream(JSContext* cx, JS::Handle<JS::Value> aOptions, nsICameraPreviewStreamCallback* onSuccess, const dom::Optional<nsICameraErrorCallback* >& onError, ErrorResult& aRv);
+  void ResumePreview(ErrorResult& aRv);
+  void ReleaseHardware(const dom::Optional<nsICameraReleaseCallback* >& onSuccess, const dom::Optional<nsICameraErrorCallback* >& onError, ErrorResult& aRv);
+
 protected:
   virtual ~nsDOMCameraControl();
 
 private:
   nsDOMCameraControl(const nsDOMCameraControl&) MOZ_DELETE;
   nsDOMCameraControl& operator=(const nsDOMCameraControl&) MOZ_DELETE;
 
+  virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
 protected:
   /* additional members */
   nsRefPtr<ICameraControl>        mCameraControl; // non-DOM camera control
   nsCOMPtr<nsICameraCapabilities> mDOMCapabilities;
   // An agent used to join audio channel service.
   nsCOMPtr<nsIAudioChannelAgent>  mAudioChannelAgent;
+  nsCOMPtr<nsPIDOMWindow> mWindow;
 };
 
 } // namespace mozilla
 
 #endif // DOM_CAMERA_DOMCAMERACONTROL_H
--- a/dom/camera/DOMCameraManager.cpp
+++ b/dom/camera/DOMCameraManager.cpp
@@ -122,19 +122,19 @@ nsDOMCameraManager::GetCamera(const Came
     if (aRv.Failed()) {
       return;
     }
   }
 
   DOM_CAMERA_LOGT("%s:%d\n", __func__, __LINE__);
 
   // Creating this object will trigger the onSuccess handler
-  nsCOMPtr<nsDOMCameraControl> cameraControl =
+  nsRefPtr<nsDOMCameraControl> cameraControl =
     new nsDOMCameraControl(cameraId, mCameraThread,
-                           onSuccess, onError.WasPassed() ? onError.Value() : nullptr, mWindowId);
+                           onSuccess, onError.WasPassed() ? onError.Value() : nullptr, mWindow);
 
   Register(cameraControl);
 }
 
 void
 nsDOMCameraManager::Register(nsDOMCameraControl* aDOMCameraControl)
 {
   DOM_CAMERA_LOGI(">>> Register( aDOMCameraControl = %p ) mWindowId = 0x%llx\n", aDOMCameraControl, mWindowId);
--- a/dom/camera/FallbackCameraControl.cpp
+++ b/dom/camera/FallbackCameraControl.cpp
@@ -54,18 +54,20 @@ private:
 /**
  * Stub implementation of the DOM-facing camera control constructor.
  *
  * This should never get called--it exists to keep the linker happy; if
  * implemented, it should construct (e.g.) nsFallbackCameraControl and
  * store a reference in the 'mCameraControl' member (which is why it is
  * defined here).
  */
-nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
+nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, nsPIDOMWindow* aWindow) :
+  mWindow(aWindow)
 {
+  MOZ_ASSERT(aWindow, "shouldn't be created with null window!");
 }
 
 /**
  * Stub implemetations of the fallback camera control.
  *
  * None of these should ever get called--they exist to keep the linker happy,
  * and may be used as templates for new camera support classes.
  */
--- a/dom/camera/GonkCameraControl.cpp
+++ b/dom/camera/GonkCameraControl.cpp
@@ -138,35 +138,36 @@ static const char* getKeyText(uint32_t a
     case CAMERA_PARAM_SUPPORTED_JPEG_THUMBNAIL_SIZES:
       return CameraParameters::KEY_SUPPORTED_JPEG_THUMBNAIL_SIZES;
     default:
       return nullptr;
   }
 }
 
 // nsDOMCameraControl implementation-specific constructor
-nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, uint64_t aWindowId)
-  : mDOMCapabilities(nullptr)
+nsDOMCameraControl::nsDOMCameraControl(uint32_t aCameraId, nsIThread* aCameraThread, nsICameraGetCameraCallback* onSuccess, nsICameraErrorCallback* onError, nsPIDOMWindow* aWindow)
+  : mDOMCapabilities(nullptr), mWindow(aWindow)
 {
+  MOZ_ASSERT(aWindow, "shouldn't be created with null window!");
   DOM_CAMERA_LOGT("%s:%d : this=%p\n", __func__, __LINE__, this);
 
   /**
    * nsDOMCameraControl is a cycle-collection participant, which means it is
    * not threadsafe--so we need to bump up its reference count here to make
    * sure that it exists long enough to be initialized.
    *
    * Once it is initialized, the GetCameraResult main-thread runnable will
    * decrement it again to make sure it can be cleaned up.
    *
    * nsGonkCameraControl MUST NOT hold a strong reference to this
    * nsDOMCameraControl or memory will leak!
    */
   NS_ADDREF_THIS();
-  nsRefPtr<nsGonkCameraControl> control = new nsGonkCameraControl(aCameraId, aCameraThread, this, onSuccess, onError, aWindowId);
-  control->DispatchInit(this, onSuccess, onError, aWindowId);
+  nsRefPtr<nsGonkCameraControl> control = new nsGonkCameraControl(aCameraId, aCameraThread, this, onSuccess, onError, aWindow->WindowID());
+  control->DispatchInit(this, onSuccess, onError, aWindow->WindowID());
   mCameraControl = control;
 }
 
 // Gonk-specific CameraControl implementation.
 
 // Initialize nsGonkCameraControl instance--runs on camera thread.
 class InitGonkCameraControl : public nsRunnable
 {
--- a/dom/camera/nsIDOMCameraManager.idl
+++ b/dom/camera/nsIDOMCameraManager.idl
@@ -212,176 +212,13 @@ interface nsICameraReleaseCallback : nsI
 };
 
 [scriptable, function, uuid(a302c6c9-3776-4d1d-a395-f4105d47c3d3)]
 interface nsICameraErrorCallback : nsISupports
 {
     void handleEvent(in DOMString error);
 };
 
-/*
-    attributes here affect the preview, any pictures taken, and/or
-    any video recorded by the camera.
-*/
-[scriptable, uuid(74dc7f1f-c88f-4774-860b-44aef9de5dc8)]
-interface nsICameraControl : nsISupports
-{
-    readonly attribute nsICameraCapabilities capabilities;
-
-    /* one of the vales chosen from capabilities.effects;
-       default is "none" */
-    attribute DOMString         effect;
-
-    /* one of the values chosen from capabilities.whiteBalanceModes;
-       default is "auto" */
-    attribute DOMString         whiteBalanceMode;
-
-    /* one of the valus chosen from capabilities.sceneModes;
-       default is "auto" */
-    attribute DOMString         sceneMode;
-
-    /* one of the values chosen from capabilities.flashModes;
-       default is "auto" */
-    attribute DOMString         flashMode;
-
-    /* one of the values chosen from capabilities.focusModes;
-       default is "auto", if supported, or "fixed" */
-    attribute DOMString         focusMode;
-
-    /* one of the values chosen from capabilities.zoomRatios; other
-       values will be rounded to the nearest supported value;
-       default is 1.0 */
-    attribute double            zoom;
-
-    /* an array of one or more objects that define where the
-       camera will perform light metering, each defining the properties:
-        {
-            top: -1000,
-            left: -1000,
-            bottom: 1000,
-            right: 1000,
-            weight: 1000
-        }
-
-        'top', 'left', 'bottom', and 'right' all range from -1000 at
-        the top-/leftmost of the sensor to 1000 at the bottom-/rightmost
-        of the sensor.
-
-        objects missing one or more of these properties will be ignored;
-        if the array contains more than capabilities.maxMeteringAreas,
-        extra areas will be ignored.
-
-        this attribute can be set to null to allow the camera to determine
-        where to perform light metering. */
-    [implicit_jscontext]
-    attribute jsval             meteringAreas;
-
-    /* an array of one or more objects that define where the camera will
-       perform auto-focusing, with the same definition as meteringAreas.
-
-       if the array contains more than capabilities.maxFocusAreas, extra
-       areas will be ignored.
-
-       this attribute can be set to null to allow the camera to determine
-       where to focus. */
-    [implicit_jscontext]
-    attribute jsval             focusAreas;
-
-    /* focal length in millimetres */
-    readonly attribute double   focalLength;
-
-    /* the distances in metres to where the image subject appears to be
-       in focus.  'focusDistanceOptimum' is where the subject will appear
-       sharpest; the difference between 'focusDistanceFar' and
-       'focusDistanceNear' is the image's depth of field.
-
-       'focusDistanceFar' may be infinity. */
-    readonly attribute double   focusDistanceNear;
-    readonly attribute double   focusDistanceOptimum;
-    readonly attribute double   focusDistanceFar;
-
-    /* 'compensation' is optional, and if missing, will
-       set the camera to use automatic exposure compensation.
-
-       acceptable values must range from minExposureCompensation
-       to maxExposureCompensation in steps of stepExposureCompensation;
-       invalid values will be rounded to the nearest valid value. */
-    [implicit_jscontext]
-    void setExposureCompensation([optional] in jsval compensation);
-    readonly attribute double   exposureCompensation;
-
-    /* the function to call on the camera's shutter event, to trigger
-       a shutter sound and/or a visual shutter indicator. */
-    attribute nsICameraShutterCallback onShutter;
-
-    /* the function to call when the camera hardware is closed
-       by the underlying framework, e.g. when another app makes a more
-       recent call to get the camera. */
-    attribute nsICameraClosedCallback onClosed;
-
-    /* the function to call when the recorder changes state, either because
-       the recording process encountered an error, or because one of the
-       recording limits (see CameraStartRecordingOptions) was reached. */
-    attribute nsICameraRecorderStateChange onRecorderStateChange;
-
-    /* the function to call when the preview stream is actually started and
-       stopped; this is usually used to enable and disable the camera UI,
-       since the low-level hardware often does not support taking pictures
-       unless the preview is running. */
-    attribute nsICameraPreviewStateChange onPreviewStateChange;
-
-    /* tell the camera to attempt to focus the image */
-    void autoFocus(in nsICameraAutoFocusCallback onSuccess, [optional] in nsICameraErrorCallback onError);
-
-    /* capture an image and return it as a blob to the 'onSuccess' callback;
-       if the camera supports it, this may be invoked while the camera is
-       already recording video.
-
-       invoking this function will stop the preview stream, which must be
-       manually restarted (e.g. by calling .play() on it). */
-    [implicit_jscontext]
-    void takePicture(in jsval aOptions, in nsICameraTakePictureCallback onSuccess, [optional] in nsICameraErrorCallback onError);
-
-    /* get a media stream to be used as a camera viewfinder in video mode;
-       'aOptions' is an CameraRecorderOptions object. */
-    [implicit_jscontext]
-    void getPreviewStreamVideoMode(in jsval aOptions, in nsICameraPreviewStreamCallback onSuccess, [optional] in nsICameraErrorCallback onError);
-
-    /* start recording video; 'aOptions' is a
-       CameraStartRecordingOptions object. */
-    [implicit_jscontext]
-    void startRecording(in jsval aOptions, in nsIDOMDeviceStorage storageArea, in DOMString filename, in nsICameraStartRecordingCallback onSuccess, [optional] in nsICameraErrorCallback onError);
-
-    /* stop precording video. */
-    void stopRecording();
-
-    /* get a media stream to be used as a camera viewfinder; the options
-       define the desired frame size of the preview, chosen from
-       capabilities.previewSizes, e.g.:
-        {
-            height: 640,
-            width:  480,
-         }
-    */
-    [implicit_jscontext]
-    void getPreviewStream(in jsval aOptions, in nsICameraPreviewStreamCallback onSuccess, [optional] in nsICameraErrorCallback onError);
-
-    /* call in or after the takePicture() onSuccess callback to
-       resume the camera preview stream. */
-    void resumePreview();
-
-    /* release the camera so that other applications can use it; you should
-       probably call this whenever the camera is not longer in the foreground
-       (depending on your usage model).
-
-       the callbacks are optional, unless you really need to know when
-       the hardware is ultimately released.
-
-       once this is called, the camera control object is to be considered
-       defunct; a new instance will need to be created to access the camera. */
-    [binaryname(ReleaseHardware)] void release([optional] in nsICameraReleaseCallback onSuccess, [optional] in nsICameraErrorCallback onError);
-};
-
-[scriptable, function, uuid(a267afbc-d91c-413a-8de5-0b94aecffa3e)]
+[scriptable, function, uuid(16de7703-dc43-4766-99c5-ff30a9ab92d7)]
 interface nsICameraGetCameraCallback : nsISupports
 {
-    void handleEvent(in nsICameraControl camera);
+    void handleEvent(in nsISupports camera);
 };
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -622,37 +622,37 @@ InitDirs()
   dirService->Get(NS_WIN_VIDEOS_DIR,
                   NS_GET_IID(nsIFile),
                   getter_AddRefs(sDirs->videos));
   dirService->Get(NS_WIN_MUSIC_DIR,
                   NS_GET_IID(nsIFile),
                   getter_AddRefs(sDirs->music));
 #endif
 
+  // Eventually, on desktop, we want to do something smarter -- for example,
+  // detect when an sdcard is inserted, and use that instead of this.
+  dirService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile),
+                  getter_AddRefs(sDirs->sdcard));
+  if (sDirs->sdcard) {
+    sDirs->sdcard->AppendRelativeNativePath(NS_LITERAL_CSTRING("fake-sdcard"));
+  }
+#endif // !MOZ_WIDGET_GONK
+
 #ifdef MOZ_WIDGET_GONK
   NS_NewLocalFile(NS_LITERAL_STRING("/data"),
                   false,
                   getter_AddRefs(sDirs->apps));
 #else
   dirService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile),
                   getter_AddRefs(sDirs->apps));
   if (sDirs->apps) {
     sDirs->apps->AppendRelativeNativePath(NS_LITERAL_CSTRING("webapps"));
   }
 #endif
 
-  // Eventually, on desktop, we want to do something smarter -- for example,
-  // detect when an sdcard is inserted, and use that instead of this.
-  dirService->Get(NS_APP_USER_PROFILE_50_DIR, NS_GET_IID(nsIFile),
-                  getter_AddRefs(sDirs->sdcard));
-  if (sDirs->sdcard) {
-    sDirs->sdcard->AppendRelativeNativePath(NS_LITERAL_CSTRING("fake-sdcard"));
-  }
-#endif // !MOZ_WIDGET_GONK
-
   if (XRE_GetProcessType() == GeckoProcessType_Default) {
     NS_GetSpecialDirectory("UAppData", getter_AddRefs(sDirs->crashes));
     if (sDirs->crashes) {
       sDirs->crashes->Append(NS_LITERAL_STRING("Crash Reports"));
     }
   } else {
     // NS_GetSpecialDirectory("UAppData") fails in content processes because
     // gAppData from toolkit/xre/nsAppRunner.cpp is not initialized.
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -49,16 +49,17 @@ using nsQueryContentEvent;
 using nsRect;
 using nsSelectionEvent;
 using nsTextEvent;
 using nsTouchEvent;
 using RemoteDOMEvent;
 using mozilla::dom::ScreenOrientation;
 using mozilla::layers::TextureFactoryIdentifier;
 using mozilla::CSSIntPoint;
+using mozilla::CSSToScreenScale;
 namespace mozilla {
 namespace dom {
 
 rpc protocol PBrowser
 {
     manager PContent;
 
     manages PContentDialog;
@@ -282,17 +283,17 @@ parent:
      * preventDefault.
      */
     ContentReceivedTouch(bool aPreventDefault);
 
     /**
      * Updates any zoom constraints on the parent and anything tied to it. This
      * is useful for control logic that resides outside of the remote browser.
      */
-    UpdateZoomConstraints(bool aAllowZoom, float aMinZoom, float aMaxZoom);
+    UpdateZoomConstraints(bool aAllowZoom, CSSToScreenScale aMinZoom, CSSToScreenScale aMaxZoom);
 
     /**
      * Notifies the parent about a scroll event. The pres shell ID and
      * view ID identify which scrollable (sub-)frame was scrolled, and
      * the new scroll offset for that frame is sent.
      */
     UpdateScrollOffset(uint32_t aPresShellId, ViewID aViewId, CSSIntPoint aScrollOffset);
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -25,17 +25,16 @@ include DOMTypes;
 include JavaScriptTypes;
 include InputStreamParams;
 include PTabContext;
 include URIParams;
 
 include "mozilla/chrome/RegistryMessageUtils.h";
 include "mozilla/dom/PermissionMessageUtils.h";
 include "mozilla/dom/TabMessageUtils.h";
-include "mozilla/dom/Element.h";
 include "mozilla/HalTypes.h";
 include "mozilla/layout/RenderFrameUtils.h";
 include "mozilla/net/NeckoMessageUtils.h";
 include "nsGeoPositionIPCSerialiser.h";
 include "gfxPoint.h";
 
 using GeoPosition;
 using PrefTuple;
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -416,26 +416,25 @@ TabChild::Observe(nsISupports *aSubject,
         // Reset CSS viewport and zoom to default on new page, then
         // calculate them properly using the actual metadata from the
         // page.
         SetCSSViewport(kDefaultViewportSize);
 
         // Calculate a really simple resolution that we probably won't
         // be keeping, as well as putting the scroll offset back to
         // the top-left of the page.
-        mLastMetrics.mZoom = ScreenToScreenScale(1.0);
         mLastMetrics.mViewport = CSSRect(CSSPoint(), kDefaultViewportSize);
         mLastMetrics.mCompositionBounds = ScreenIntRect(ScreenIntPoint(), mInnerSize);
-        CSSToScreenScale resolution = mLastMetrics.CalculateResolution();
-        // We use ScreenToLayerScale(1) below in order to ask gecko to render
-        // what's currently visible on the screen. This is effectively turning
-        // the async zoom amount into the gecko zoom amount.
+        mLastMetrics.mZoom = mLastMetrics.CalculateIntrinsicScale();
+        // We use ScreenToLayerScale(1) below in order to turn the
+        // async zoom amount into the gecko zoom amount.
         mLastMetrics.mResolution =
-          resolution / mLastMetrics.mDevPixelsPerCSSPixel * ScreenToLayerScale(1);
+          mLastMetrics.mZoom / mLastMetrics.mDevPixelsPerCSSPixel * ScreenToLayerScale(1);
         mLastMetrics.mScrollOffset = CSSPoint(0, 0);
+
         utils->SetResolution(mLastMetrics.mResolution.scale,
                              mLastMetrics.mResolution.scale);
 
         HandlePossibleViewportChange();
       }
     }
   } else if (!strcmp(aTopic, DETECT_SCROLLABLE_SUBFRAME)) {
     nsCOMPtr<nsIDocShell> docShell(do_QueryInterface(aSubject));
@@ -567,18 +566,18 @@ TabChild::HandlePossibleViewportChange()
   mWebNav->GetDocument(getter_AddRefs(domDoc));
   nsCOMPtr<nsIDocument> document(do_QueryInterface(domDoc));
 
   nsCOMPtr<nsIDOMWindowUtils> utils(GetDOMWindowUtils());
 
   nsViewportInfo viewportInfo =
     nsContentUtils::GetViewportInfo(document, mInnerSize.width, mInnerSize.height);
   SendUpdateZoomConstraints(viewportInfo.IsZoomAllowed(),
-                            viewportInfo.GetMinZoom(),
-                            viewportInfo.GetMaxZoom());
+                            CSSToScreenScale(viewportInfo.GetMinZoom()),
+                            CSSToScreenScale(viewportInfo.GetMaxZoom()));
 
   float screenW = mInnerSize.width;
   float screenH = mInnerSize.height;
   CSSSize viewport(viewportInfo.GetWidth(), viewportInfo.GetHeight());
 
   // We're not being displayed in any way; don't bother doing anything because
   // that will just confuse future adjustments.
   if (!screenW || !screenH) {
@@ -642,67 +641,66 @@ TabChild::HandlePossibleViewportChange()
   minScale = mInnerSize.width / pageSize.width;
   minScale = clamped((double)minScale, viewportInfo.GetMinZoom(),
                      viewportInfo.GetMaxZoom());
   NS_ENSURE_TRUE_VOID(minScale); // (return early rather than divide by 0)
 
   viewport.height = std::max(viewport.height, screenH / minScale);
   SetCSSViewport(viewport);
 
+  float oldScreenWidth = mLastMetrics.mCompositionBounds.width;
+  if (!oldScreenWidth) {
+    oldScreenWidth = mInnerSize.width;
+  }
+
+  FrameMetrics metrics(mLastMetrics);
+  metrics.mViewport = CSSRect(CSSPoint(), viewport);
+  metrics.mScrollableRect = CSSRect(CSSPoint(), pageSize);
+  metrics.mCompositionBounds = ScreenIntRect(ScreenIntPoint(), mInnerSize);
+
   // This change to the zoom accounts for all types of changes I can conceive:
   // 1. screen size changes, CSS viewport does not (pages with no meta viewport
   //    or a fixed size viewport)
   // 2. screen size changes, CSS viewport also does (pages with a device-width
   //    viewport)
   // 3. screen size remains constant, but CSS viewport changes (meta viewport
   //    tag is added or removed)
   // 4. neither screen size nor CSS viewport changes
   //
   // In all of these cases, we maintain how much actual content is visible
   // within the screen width. Note that "actual content" may be different with
   // respect to CSS pixels because of the CSS viewport size changing.
-  int32_t oldScreenWidth = mLastMetrics.mCompositionBounds.width;
-  if (!oldScreenWidth) {
-    oldScreenWidth = mInnerSize.width;
-  }
-
-  FrameMetrics metrics(mLastMetrics);
-  metrics.mViewport = CSSRect(CSSPoint(), viewport);
-  metrics.mScrollableRect = CSSRect(CSSPoint(), pageSize);
-  metrics.mCompositionBounds = ScreenIntRect(ScreenIntPoint(), mInnerSize);
+  float oldIntrinsicScale = oldScreenWidth / oldBrowserWidth;
+  metrics.mZoom.scale *= metrics.CalculateIntrinsicScale().scale / oldIntrinsicScale;
 
   // Changing the zoom when we're not doing a first paint will get ignored
   // by AsyncPanZoomController and causes a blurry flash.
   bool isFirstPaint;
   nsresult rv = utils->GetIsFirstPaint(&isFirstPaint);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
   if (NS_FAILED(rv) || isFirstPaint) {
-    CSSToScreenScale intrinsicScale = metrics.CalculateIntrinsicScale();
     // FIXME/bug 799585(?): GetViewportInfo() returns a defaultZoom of
     // 0.0 to mean "did not calculate a zoom".  In that case, we default
     // it to the intrinsic scale.
     if (viewportInfo.GetDefaultZoom() < 0.01f) {
-      viewportInfo.SetDefaultZoom(intrinsicScale.scale);
+      viewportInfo.SetDefaultZoom(metrics.CalculateIntrinsicScale().scale);
     }
 
     double defaultZoom = viewportInfo.GetDefaultZoom();
     MOZ_ASSERT(viewportInfo.GetMinZoom() <= defaultZoom &&
                defaultZoom <= viewportInfo.GetMaxZoom());
-    // GetViewportInfo() returns a resolution-dependent scale factor.
-    // Convert that to a resolution-indepedent zoom.
-    metrics.mZoom = ScreenToScreenScale(defaultZoom / intrinsicScale.scale);
+    metrics.mZoom = CSSToScreenScale(defaultZoom);
   }
 
   metrics.mDisplayPort = AsyncPanZoomController::CalculatePendingDisplayPort(
     // The page must have been refreshed in some way such as a new document or
     // new CSS viewport, so we know that there's no velocity, acceleration, and
     // we have no idea how long painting will take.
     metrics, gfx::Point(0.0f, 0.0f), gfx::Point(0.0f, 0.0f), 0.0);
-  CSSToScreenScale resolution = metrics.CalculateResolution();
-  metrics.mResolution = resolution / metrics.mDevPixelsPerCSSPixel * ScreenToLayerScale(1);
+  metrics.mResolution = metrics.mZoom / metrics.mDevPixelsPerCSSPixel * ScreenToLayerScale(1);
   utils->SetResolution(metrics.mResolution.scale, metrics.mResolution.scale);
 
   // Force a repaint with these metrics. This, among other things, sets the
   // displayport, so we start with async painting.
   ProcessUpdateFrame(metrics);
 }
 
 nsresult
@@ -1612,17 +1610,17 @@ TabChild::ProcessUpdateFrame(const Frame
 
     // scroll the window to the desired spot
     nsIScrollableFrame* sf = static_cast<nsGlobalWindow*>(window.get())->GetScrollFrame();
     if (sf) {
         sf->ScrollToCSSPixelsApproximate(aFrameMetrics.mScrollOffset);
     }
 
     // set the resolution
-    LayoutDeviceToLayerScale resolution = aFrameMetrics.CalculateResolution()
+    LayoutDeviceToLayerScale resolution = aFrameMetrics.mZoom
       / aFrameMetrics.mDevPixelsPerCSSPixel * ScreenToLayerScale(1);
     utils->SetResolution(resolution.scale, resolution.scale);
 
     // and set the display port
     nsCOMPtr<nsIDOMDocument> domDoc;
     mWebNav->GetDocument(getter_AddRefs(domDoc));
     if (domDoc) {
       nsCOMPtr<nsIDOMElement> element;
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -295,17 +295,17 @@ public:
     nsIWebNavigation* WebNavigation() { return mWebNav; }
 
     nsIPrincipal* GetPrincipal() { return mPrincipal; }
 
     /** Return the DPI of the widget this TabChild draws to. */
     void GetDPI(float* aDPI);
     void GetDefaultScale(double *aScale);
 
-    ScreenToScreenScale GetZoom() { return mLastMetrics.mZoom; }
+    CSSToScreenScale GetZoom() { return mLastMetrics.mZoom; }
 
     ScreenOrientation GetOrientation() { return mOrientation; }
 
     void SetBackgroundColor(const nscolor& aColor);
 
     void NotifyPainted();
 
     bool IsAsyncPanZoomEnabled();
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -1547,18 +1547,18 @@ TabParent::RecvZoomToRect(const CSSRect&
   if (RenderFrameParent* rfp = GetRenderFrame()) {
     rfp->ZoomToRect(aRect);
   }
   return true;
 }
 
 bool
 TabParent::RecvUpdateZoomConstraints(const bool& aAllowZoom,
-                                     const float& aMinZoom,
-                                     const float& aMaxZoom)
+                                     const CSSToScreenScale& aMinZoom,
+                                     const CSSToScreenScale& aMaxZoom)
 {
   if (RenderFrameParent* rfp = GetRenderFrame()) {
     rfp->UpdateZoomConstraints(aAllowZoom, aMinZoom, aMaxZoom);
   }
   return true;
 }
 
 bool
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -153,18 +153,18 @@ public:
     virtual bool RecvSetCursor(const uint32_t& aValue);
     virtual bool RecvSetBackgroundColor(const nscolor& aValue);
     virtual bool RecvSetStatus(const uint32_t& aType, const nsString& aStatus);
     virtual bool RecvGetDPI(float* aValue);
     virtual bool RecvGetDefaultScale(double* aValue);
     virtual bool RecvGetWidgetNativeData(WindowsHandle* aValue);
     virtual bool RecvZoomToRect(const CSSRect& aRect);
     virtual bool RecvUpdateZoomConstraints(const bool& aAllowZoom,
-                                           const float& aMinZoom,
-                                           const float& aMaxZoom);
+                                           const CSSToScreenScale& aMinZoom,
+                                           const CSSToScreenScale& aMaxZoom);
     virtual bool RecvUpdateScrollOffset(const uint32_t& aPresShellId, const ViewID& aViewId, const CSSIntPoint& aScrollOffset);
     virtual bool RecvContentReceivedTouch(const bool& aPreventDefault);
     virtual PContentDialogParent* AllocPContentDialogParent(const uint32_t& aType,
                                                             const nsCString& aName,
                                                             const nsCString& aFeatures,
                                                             const InfallibleTArray<int>& aIntParams,
                                                             const InfallibleTArray<nsString>& aStringParams);
     virtual bool DeallocPContentDialogParent(PContentDialogParent* aDialog)
--- a/dom/locales/en-US/chrome/security/security.properties
+++ b/dom/locales/en-US/chrome/security/security.properties
@@ -9,8 +9,10 @@ ReportOnlyCSPIgnored=Report-only CSP pol
 OldCSPHeaderDeprecated=The X-Content-Security-Policy and X-Content-Security-Report-Only headers will be deprecated in the future. Please use the Content-Security-Policy and Content-Security-Report-Only headers with CSP spec compliant syntax instead.
 # LOCALIZATION NOTE: Do not translate "X-Content-Security-Policy/Report-Only" or "Content-Security-Policy/Report-Only"
 BothCSPHeadersPresent=This site specified both an X-Content-Security-Policy/Report-Only header and a Content-Security-Policy/Report-Only header. The X-Content-Security-Policy/Report-Only header(s) will be ignored.
 # LOCALIZATION NOTE: Do not translate "Strict-Transport-Security" or "HSTS"
 InvalidSTSHeaders=The site specified an invalid Strict-Transport-Security header.
 InsecurePasswordsPresentOnPage=Password fields present on an insecure (http://) page. This is a security risk that allows user login credentials to be stolen.
 InsecureFormActionPasswordsPresent=Password fields present in a form with an insecure (http://) form action. This is a security risk that allows user login credentials to be stolen.
 InsecurePasswordsPresentOnIframe=Password fields present on an insecure (http://) iframe. This is a security risk that allows user login credentials to be stolen.
+LoadingMixedActiveContent=Loading mixed (insecure) active content on a secure page "%1$S"
+LoadingMixedDisplayContent=Loading mixed (insecure) display content on a secure page "%1$S"
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -307,16 +307,27 @@ public:
 
   virtual void RemoveDirectListener(MediaStreamDirectListener *aListener) MOZ_OVERRIDE
   {
     if (mSourceStream) {
       mSourceStream->RemoveDirectListener(aListener);
     }
   }
 
+  // let us intervene for direct listeners when someone does track.enabled = false
+  virtual void SetTrackEnabled(TrackID aID, bool aEnabled) MOZ_OVERRIDE
+  {
+    // We encapsulate the SourceMediaStream and TrackUnion into one entity, so
+    // we can handle the disabling at the SourceMediaStream
+
+    // We need to find the input track ID for output ID aID, so we let the TrackUnion
+    // forward the request to the source and translate the ID
+    GetStream()->AsProcessedStream()->ForwardTrackEnabled(aID, aEnabled);
+  }
+
   // The actual MediaStream is a TrackUnionStream. But these resources need to be
   // explicitly destroyed too.
   nsRefPtr<SourceMediaStream> mSourceStream;
   nsRefPtr<MediaInputPort> mPort;
 };
 
 /**
  * Creates a MediaStream, attaches a listener and fires off a success callback
--- a/dom/media/PeerConnection.js
+++ b/dom/media/PeerConnection.js
@@ -1010,16 +1010,17 @@ PeerConnectionObserver.prototype = {
 
   onAddIceCandidateError: function(code, message) {
     this._dompc._pendingType = null;
     this.callCB(this._dompc._onAddIceCandidateError, new RTCError(code, message));
     this._dompc._executeNext();
   },
 
   handleIceStateChanges: function(iceState) {
+    var histogram = Services.telemetry.getHistogramById("WEBRTC_ICE_SUCCESS_RATE");
     switch (iceState) {
       case Ci.IPeerConnection.kIceWaiting:
         this._dompc.changeIceConnectionState("new");
         this.callCB(this._dompc.ongatheringchange, "complete");
         this.callCB(this._onicechange, "starting");
         // Now that the PC is ready to go, execute any pending operations.
         this._dompc._executeNext();
         break;
@@ -1028,20 +1029,22 @@ PeerConnectionObserver.prototype = {
         this.callCB(this._onicechange, "checking");
         break;
       case Ci.IPeerConnection.kIceGathering:
         this._dompc.changeIceGatheringState("gathering");
         this.callCB(this._ongatheringchange, "gathering");
         break;
       case Ci.IPeerConnection.kIceConnected:
         // ICE gathering complete.
+        histogram.add(true);
         this._dompc.changeIceConnectionState("connected");
         this.callCB(this._onicechange, "connected");
         break;
       case Ci.IPeerConnection.kIceFailed:
+        histogram.add(false);
         this._dompc.changeIceConnectionState("failed");
         this.callCB(this._onicechange, "failed");
         break;
       default:
         // Unknown ICE state!
         this._dompc.reportWarning("Unhandled ice state: " + iceState, null, 0);
         break;
     }
--- a/dom/permission/tests/test_permission_basics.html
+++ b/dom/permission/tests/test_permission_basics.html
@@ -1,121 +1,109 @@
 <!DOCTYPE html>
 <html>
 <!--
-https://bugzilla.mozilla.org/show_bug.cgi?id={770731}
+https://bugzilla.mozilla.org/show_bug.cgi?id=770731
 -->
 <head>
   <title>Test for Bug {770731} Permissions</title>
   <script type="text/javascript" src="/MochiKit/MochiKit.js"></script>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id={770731}">Mozilla Bug {674720}</a>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=770731">Mozilla Bug 770731</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script class="testbody" type="text/javascript">
 
 "use strict";
 
+SimpleTest.waitForExplicitFinish();
+
 var testPrivApp = {
   'manifestURL' : 'https://aprivileged.com/manifest.webapp'
 };
 
 var testCertApp = {
   'manifestURL' : 'https://acertified.com/manifest.webapp'
 };
 
-SpecialPowers.addPermission("permissions", true, document);
-SpecialPowers.pushPrefEnv({ "set": [["dom.mozPermissionSettings.enabled", true]] },
-                          function() {
-                            SpecialPowers.removePermission("permissions", document);
-                          });
+// Any permission explicit for privileged and implicit for certified serves
+var testPerm = "contacts-read";
+// Any permission explicit for privileged and certified apps
+var explicitPerm = "geolocation";
 
-SpecialPowers.Cu.import("resource://gre/modules/PermissionSettings.jsm");
-var mozPermissions = window.navigator.mozPermissionSettings;
+// Simulate that the app requested the permissions
+SpecialPowers.pushPermissions([{'type': 'permissions', 'allow': true, 'context': document}, {'type': testPerm, 'allow': true, 'context': testPrivApp}, {'type': testPerm, 'allow': true, 'context': testCertApp}, {'type': explicitPerm, 'allow': true, 'context': testPrivApp}, {'type': explicitPerm, 'allow': true, 'context': testCertApp}], function() {
+  SpecialPowers.pushPrefEnv({ "set": [["dom.mozPermissionSettings.enabled", true]] }, permissionTest);
+});
 
 function permissionTest() {
-  // Any permission explicit for privileged and implicit for certified serves
-  var testPerm = "contacts-read";
-  // Any permission explicit for privileged and certified apps
-  var explicitPerm = "geolocation";
+  if (SpecialPowers.isMainProcess()) {
+    SpecialPowers.Cu.import("resource://gre/modules/PermissionSettings.jsm");
+  }
 
-  // Simulate that the app requested the permissions
-  SpecialPowers.addPermission(testPerm, true, testPrivApp);
-  SpecialPowers.addPermission(testPerm, true, testCertApp);
-  SpecialPowers.addPermission(explicitPerm, true, testPrivApp);
-  SpecialPowers.addPermission(explicitPerm, true, testCertApp);
+  var mozPermissions = window.navigator.mozPermissionSettings;
+  isnot(mozPermissions, null, "mozPermissionSettings is null when not enabled.");
 
-  if (gPermissionsEnabled) {
-    var certAppManifest = testCertApp.manifestURL;
-    var privAppManifest = testPrivApp.manifestURL;
-    var originPriv = "https://aprivileged.com";
-    var originCert = "https://acertified.com";
-    var originOther = "http://test";
+  var certAppManifest = testCertApp.manifestURL;
+  var privAppManifest = testPrivApp.manifestURL;
+  var originPriv = "https://aprivileged.com";
+  var originCert = "https://acertified.com";
+  var originOther = "http://test";
 
-    // Trying to make any change to implicit permissions should fail
-    try {
-      mozPermissions.set(testPerm, "allow", certAppManifest, originCert, false);
-      ok(false, "Change implicit permission");
-    } catch (e) {
-      ok(true, "Change implicit permission");
-    }
+  // Trying to make any change to implicit permissions should fail
+  try {
+    mozPermissions.set(testPerm, "allow", certAppManifest, originCert, false);
+    ok(false, "Change implicit permission");
+  } catch (e) {
+    ok(true, "Change implicit permission");
+  }
 
-    var result=mozPermissions.get(testPerm, certAppManifest, originCert, false);
-    is(result, "allow", "same result");
+  var result=mozPermissions.get(testPerm, certAppManifest, originCert, false);
+  is(result, "allow", "same result");
 
-    // Removing a permission from the same origin, even an explicit one, should fail
-    try {
-      mozPermissions.set(testPerm, "unknown", privAppManifest, originPriv);
-      ok(false, "Setting a permission to unknown");
-    } catch (e) {
-      ok(true, "Setting a permission to unknown");
-    }
+  // Removing a permission from the same origin, even an explicit one, should fail
+  try {
+    mozPermissions.set(testPerm, "unknown", privAppManifest, originPriv);
+    ok(false, "Setting a permission to unknown");
+  } catch (e) {
+    ok(true, "Setting a permission to unknown");
+  }
 
-    // Removing an explicit permission from a different origin should work
-    var testRemove = function(aPerm, aManifest, aOrigin, aTestMsg) {
-      try {
-        mozPermissions.remove(aPerm, aManifest, aOrigin);
-        var status = mozPermissions.get(aPerm, aManifest, aOrigin, false);
-        is(status, "unknown", aTestMsg);
-      } catch (e) {
-        ok(false, aTestMsg);
-      }
+  // Removing an explicit permission from a different origin should work
+  var testRemove = function(aPerm, aManifest, aOrigin, aTestMsg) {
+    try {
+      mozPermissions.remove(aPerm, aManifest, aOrigin);
+      var status = mozPermissions.get(aPerm, aManifest, aOrigin, false);
+      is(status, "unknown", aTestMsg);
+    } catch (e) {
+      ok(false, aTestMsg);
     }
+  }
 
-    testRemove(explicitPerm, privAppManifest, originOther,
+  testRemove(explicitPerm, privAppManifest, originOther,
                "Remove explicit permission of privileged app");
-    testRemove(explicitPerm, certAppManifest, originOther,
+  testRemove(explicitPerm, certAppManifest, originOther,
                "Remove explicit permission of certified app");
 
-    mozPermissions.set(testPerm, "allow", privAppManifest, originPriv, false);
-    result = mozPermissions.get(testPerm, privAppManifest, originPriv, false);
-    is(result, "allow", "Set to allow");
-    mozPermissions.set(testPerm, "deny", privAppManifest, originPriv, false);
-    result = mozPermissions.get(testPerm, privAppManifest, originPriv, false);
-    is(result, "deny", "Set to deny");
-    mozPermissions.set(testPerm, "prompt", privAppManifest, originPriv, false);
-    result = mozPermissions.get(testPerm, privAppManifest, originPriv, false);
-    is(result, "prompt", "Set to prompt");
-    SimpleTest.finish();
-  } else {
-    is(mozPermissions, null, "mozPermissionSettings is null when not enabled.");
-    SimpleTest.finish();
-  }
-  SpecialPowers.removePermission(testPerm, testPrivApp);
-  SpecialPowers.removePermission(testPerm, testCertApp);
+  mozPermissions.set(testPerm, "allow", privAppManifest, originPriv, false);
+  result = mozPermissions.get(testPerm, privAppManifest, originPriv, false);
+  is(result, "allow", "Set to allow");
+  mozPermissions.set(testPerm, "deny", privAppManifest, originPriv, false);
+  result = mozPermissions.get(testPerm, privAppManifest, originPriv, false);
+  is(result, "deny", "Set to deny");
+  mozPermissions.set(testPerm, "prompt", privAppManifest, originPriv, false);
+  result = mozPermissions.get(testPerm, privAppManifest, originPriv, false);
+  is(result, "prompt", "Set to prompt");
+  SimpleTest.finish();
 }
 
-var gPermissionsEnabled = SpecialPowers.getBoolPref("dom.mozPermissionSettings.enabled");
-SimpleTest.waitForExplicitFinish();
-addLoadEvent(permissionTest);
-
 ok(true, "test passed");
 </script>
 </pre>
 </body>
-</html>
+</html>
\ No newline at end of file
--- a/dom/plugins/test/reftest/reftest.list
+++ b/dom/plugins/test/reftest/reftest.list
@@ -1,16 +1,16 @@
 # basic sanity checking
 random-if(!haveTestPlugin) != plugin-sanity.html about:blank
 random-if(!haveTestPlugin) != plugin-asyncbitmap-sanity.html about:blank
 fails-if(!haveTestPlugin) == plugin-sanity.html div-sanity.html
 fails-if(!haveTestPlugin) == plugin-asyncbitmap-sanity.html div-sanity.html
 fails-if(!haveTestPlugin) == plugin-asyncdxgi-sanity.html div-sanity.html
 skip-if(!haveTestPlugin) == plugin-asyncbitmap-update.html plugin-async-update-ref.html
-skip-if(!haveTestPlugin) == plugin-asyncdxgi-update.html plugin-async-update-ref.html
+random-if(OSX==10.6||OSX==10.7) skip-if(!haveTestPlugin) == plugin-asyncdxgi-update.html plugin-async-update-ref.html
 fails-if(!haveTestPlugin) == plugin-alpha-zindex.html div-alpha-zindex.html
 fails-if(!haveTestPlugin) == plugin-alpha-opacity.html div-alpha-opacity.html
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == windowless-clipping-1.html windowless-clipping-1-ref.html # bug 631832
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == border-padding-1.html border-padding-1-ref.html # bug 629430
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) == border-padding-2.html border-padding-2-ref.html # bug 629430
 random-if(/^Windows\x20NT\x206\.1/.test(http.oscpu)) fails-if(!haveTestPlugin) skip-if(Android||B2G) == border-padding-3.html border-padding-3-ref.html # bug 629430 # bug 773482
 # The following two "pluginproblemui-direction" tests are unreliable on all platforms. They should be re-written or replaced.
 #random-if(cocoaWidget||d2d||/^Windows\x20NT\x205\.1/.test(http.oscpu)) fails-if(!haveTestPlugin&&!Android) skip-if(!testPluginIsOOP()) == pluginproblemui-direction-1.html pluginproblemui-direction-1-ref.html # bug 567367
new file mode 100644
--- /dev/null
+++ b/dom/webidl/CameraControl.webidl
@@ -0,0 +1,195 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* 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/.
+ */
+
+interface CameraCapabilities;
+interface GetCameraCallback;
+interface CameraErrorCallback;
+interface CameraShutterCallback;
+interface CameraClosedCallback;
+interface CameraRecorderStateChange;
+interface CameraAutoFocusCallback;
+interface CameraTakePictureCallback;
+interface CameraPreviewStateChange;
+interface CameraPreviewStreamCallback;
+interface CameraStartRecordingCallback;
+interface CameraReleaseCallback;
+
+/*
+    attributes here affect the preview, any pictures taken, and/or
+    any video recorded by the camera.
+*/
+interface CameraControl {
+    readonly attribute CameraCapabilities capabilities;
+
+    /* one of the values chosen from capabilities.effects;
+       default is "none" */
+    [Throws]
+    attribute DOMString         effect;
+
+    /* one of the values chosen from capabilities.whiteBalanceModes;
+       default is "auto" */
+    [Throws]
+    attribute DOMString         whiteBalanceMode;
+
+    /* one of the values chosen from capabilities.sceneModes;
+       default is "auto" */
+    [Throws]
+    attribute DOMString         sceneMode;
+
+    /* one of the values chosen from capabilities.flashModes;
+       default is "auto" */
+    [Throws]
+    attribute DOMString         flashMode;
+
+    /* one of the values chosen from capabilities.focusModes;
+       default is "auto", if supported, or "fixed" */
+    [Throws]
+    attribute DOMString         focusMode;
+
+    /* one of the values chosen from capabilities.zoomRatios; other
+       values will be rounded to the nearest supported value;
+       default is 1.0 */
+    [Throws]
+    attribute double            zoom;
+
+    /* an array of one or more objects that define where the
+       camera will perform light metering, each defining the properties:
+        {
+            top: -1000,
+            left: -1000,
+            bottom: 1000,
+            right: 1000,
+            weight: 1000
+        }
+
+        'top', 'left', 'bottom', and 'right' all range from -1000 at
+        the top-/leftmost of the sensor to 1000 at the bottom-/rightmost
+        of the sensor.
+
+        objects missing one or more of these properties will be ignored;
+        if the array contains more than capabilities.maxMeteringAreas,
+        extra areas will be ignored.
+
+        this attribute can be set to null to allow the camera to determine
+        where to perform light metering. */
+    [Throws]
+    attribute any             meteringAreas;
+
+    /* an array of one or more objects that define where the camera will
+       perform auto-focusing, with the same definition as meteringAreas.
+
+       if the array contains more than capabilities.maxFocusAreas, extra
+       areas will be ignored.
+
+       this attribute can be set to null to allow the camera to determine
+       where to focus. */
+    [Throws]
+    attribute any             focusAreas;
+
+    /* focal length in millimetres */
+    [Throws]
+    readonly attribute double   focalLength;
+
+    /* the distances in metres to where the image subject appears to be
+       in focus.  'focusDistanceOptimum' is where the subject will appear
+       sharpest; the difference between 'focusDistanceFar' and
+       'focusDistanceNear' is the image's depth of field.
+
+       'focusDistanceFar' may be infinity. */
+    [Throws]
+    readonly attribute double   focusDistanceNear;
+    [Throws]
+    readonly attribute double   focusDistanceOptimum;
+    [Throws]
+    readonly attribute unrestricted double   focusDistanceFar;
+
+    /* 'compensation' is optional, and if missing, will
+       set the camera to use automatic exposure compensation.
+
+       acceptable values must range from minExposureCompensation
+       to maxExposureCompensation in steps of stepExposureCompensation;
+       invalid values will be rounded to the nearest valid value. */
+    [Throws]
+    void setExposureCompensation(optional double compensation);
+    [Throws]
+    readonly attribute unrestricted double   exposureCompensation;
+
+    /* the function to call on the camera's shutter event, to trigger
+       a shutter sound and/or a visual shutter indicator. */
+    [Throws]
+    attribute CameraShutterCallback? onShutter;
+
+    /* the function to call when the camera hardware is closed
+       by the underlying framework, e.g. when another app makes a more
+       recent call to get the camera. */
+    [Throws]
+    attribute CameraClosedCallback? onClosed;
+
+    /* the function to call when the recorder changes state, either because
+       the recording process encountered an error, or because one of the
+       recording limits (see CameraStartRecordingOptions) was reached. */
+    [Throws]
+    attribute CameraRecorderStateChange? onRecorderStateChange;
+    attribute CameraPreviewStateChange? onPreviewStateChange;
+
+    /* tell the camera to attempt to focus the image */
+    [Throws]
+    void autoFocus(CameraAutoFocusCallback onSuccess, optional CameraErrorCallback onError);
+
+    /* capture an image and return it as a blob to the 'onSuccess' callback;
+       if the camera supports it, this may be invoked while the camera is
+       already recording video.
+
+       invoking this function will stop the preview stream, which must be
+       manually restarted (e.g. by calling .play() on it). */
+    [Throws]
+    void takePicture(CameraPictureOptions aOptions,
+                     CameraTakePictureCallback onSuccess,
+                     optional CameraErrorCallback onError);
+
+    /* get a media stream to be used as a camera viewfinder in video mode;
+       'aOptions' is an CameraRecorderOptions object. */
+    [Throws]
+    void getPreviewStreamVideoMode(any aOptions, CameraPreviewStreamCallback onSuccess, optional CameraErrorCallback onError);
+
+    /* start recording video; 'aOptions' is a
+       CameraStartRecordingOptions object. */
+    [Throws]
+    void startRecording(any aOptions, DeviceStorage storageArea, DOMString filename, CameraStartRecordingCallback onSuccess, optional CameraErrorCallback onError);
+
+    /* stop precording video. */
+    [Throws]
+    void stopRecording();
+
+    /* get a media stream to be used as a camera viewfinder; the options
+       define the desired frame size of the preview, chosen from
+       capabilities.previewSizes, e.g.:
+        {
+            height: 640,
+            width:  480,
+         }
+    */
+    [Throws]
+    void getPreviewStream(any aOptions, CameraPreviewStreamCallback onSuccess, optional CameraErrorCallback onError);
+
+    /* call in or after the takePicture() onSuccess callback to
+       resume the camera preview stream. */
+    [Throws]
+    void resumePreview();
+
+    /* release the camera so that other applications can use it; you should
+       probably call this whenever the camera is not longer in the foreground
+       (depending on your usage model).
+
+       the callbacks are optional, unless you really need to know when
+       the hardware is ultimately released.
+
+       once this is called, the camera control object is to be considered
+       defunct; a new instance will need to be created to access the camera. */
+    [Throws]
+    void release(optional CameraReleaseCallback onSuccess, optional CameraErrorCallback onError);
+};
--- a/dom/webidl/CanvasRenderingContext2D.webidl
+++ b/dom/webidl/CanvasRenderingContext2D.webidl
@@ -202,16 +202,22 @@ interface CanvasRenderingContext2D {
    */
   [Throws, ChromeOnly]
   void drawWindow(Window window, double x, double y, double w, double h,
                   DOMString bgColor, optional unsigned long flags = 0);
   [Throws, ChromeOnly]
   void asyncDrawXULElement(XULElement elem, double x, double y, double w,
                            double h, DOMString bgColor,
                            optional unsigned long flags = 0);
+  /**
+   * This causes a context that is currently using a hardware-accelerated
+   * backend to fallback to a software one. All state should be preserved.
+   */
+  [ChromeOnly]
+  void demote();
 };
 CanvasRenderingContext2D implements CanvasDrawingStyles;
 CanvasRenderingContext2D implements CanvasPathMethods;
 
 [NoInterfaceObject]
 interface CanvasDrawingStyles {
   // line caps/joins
            [LenientFloat]
--- a/dom/webidl/DummyBinding.webidl
+++ b/dom/webidl/DummyBinding.webidl
@@ -14,17 +14,16 @@ interface DummyInterface {
   Function getFunction();
   void funcSocketsDict(optional SocketsDict arg);
   void funcHttpConnDict(optional HttpConnDict arg);
   void funcWebSocketDict(optional WebSocketDict arg);
   void funcDNSCacheDict(optional DNSCacheDict arg);
   void funcDNSLookupDict(optional DNSLookupDict arg);
   void funcConnStatusDict(optional ConnStatusDict arg);
   void frameRequestCallback(FrameRequestCallback arg);
-  void CameraPictureOptions(optional CameraPictureOptions arg);
   void MmsParameters(optional MmsParameters arg);
   void MmsAttachment(optional MmsAttachment arg);
   void AsyncScrollEventDetail(optional AsyncScrollEventDetail arg);
   void OpenWindowEventDetail(optional OpenWindowEventDetail arg);
 };
 
 interface DummyInterfaceWorkers {
   BlobPropertyBag blobBag();
--- a/dom/webidl/WebGL2RenderingContext.webidl
+++ b/dom/webidl/WebGL2RenderingContext.webidl
@@ -8,16 +8,20 @@
 
 [Pref="webgl.enable-prototype-webgl2"]
 interface WebGLQuery {
 };
 
 [Pref="webgl.enable-prototype-webgl2"]
 interface WebGL2RenderingContext : WebGLRenderingContext {
 
+    /* depth textures */
+    const GLenum UNSIGNED_INT_24_8                           = 0x84FA;
+
+
     /* draw buffers */
     const GLenum COLOR_ATTACHMENT1                           = 0x8CE1;
     const GLenum COLOR_ATTACHMENT2                           = 0x8CE2;
     const GLenum COLOR_ATTACHMENT3                           = 0x8CE3;
     const GLenum COLOR_ATTACHMENT4                           = 0x8CE4;
     const GLenum COLOR_ATTACHMENT5                           = 0x8CE5;
     const GLenum COLOR_ATTACHMENT6                           = 0x8CE6;
     const GLenum COLOR_ATTACHMENT7                           = 0x8CE7;
@@ -102,16 +106,20 @@ interface WebGL2RenderingContext : WebGL
 
 
     /* buffer objects */
     void bindBufferBase(GLenum target, GLuint index, WebGLBuffer? buffer);
     void bindBufferRange(GLenum target, GLuint index, WebGLBuffer? buffer,
                          GLintptr offset, GLsizeiptr size);
 
 
+    /* standart derivatives */
+    const GLenum FRAGMENT_SHADER_DERIVATIVE_HINT             = 0x8B8B;
+
+
     /* state requests */
     any getParameterIndexed(GLenum pname, GLuint index);
 
 
     /* vertex array objects */
     const GLenum VERTEX_ARRAY_BINDING                        = 0x85B5;
 
     void bindVertexArray(WebGLVertexArray? arrayObject);
--- a/dom/webidl/WebIDL.mk
+++ b/dom/webidl/WebIDL.mk
@@ -29,16 +29,17 @@ webidl_files = \
   AudioProcessingEvent.webidl \
   AudioStreamTrack.webidl \
   BarProp.webidl \
   BatteryManager.webidl \
   BeforeUnloadEvent.webidl \
   BiquadFilterNode.webidl \
   Blob.webidl \
   BrowserElementDictionaries.webidl \
+  CameraControl.webidl \
   CameraManager.webidl \
   CanvasRenderingContext2D.webidl \
   CaretPosition.webidl \
   CDATASection.webidl \
   ChannelMergerNode.webidl \
   ChannelSplitterNode.webidl \
   CharacterData.webidl \
   ChildNode.webidl \
--- a/gfx/2d/ScaleFactor.h
+++ b/gfx/2d/ScaleFactor.h
@@ -45,16 +45,32 @@ struct ScaleFactor {
   bool operator==(const ScaleFactor<src, dst>& aOther) const {
     return scale == aOther.scale;
   }
 
   bool operator!=(const ScaleFactor<src, dst>& aOther) const {
     return !(*this == aOther);
   }
 
+  bool operator<(const ScaleFactor<src, dst>& aOther) const {
+    return scale < aOther.scale;
+  }
+
+  bool operator<=(const ScaleFactor<src, dst>& aOther) const {
+    return scale <= aOther.scale;
+  }
+
+  bool operator>(const ScaleFactor<src, dst>& aOther) const {
+    return scale > aOther.scale;
+  }
+
+  bool operator>=(const ScaleFactor<src, dst>& aOther) const {
+    return scale >= aOther.scale;
+  }
+
   template<class other>
   ScaleFactor<other, dst> operator/(const ScaleFactor<src, other>& aOther) const {
     return ScaleFactor<other, dst>(scale / aOther.scale);
   }
 
   template<class other>
   ScaleFactor<src, other> operator/(const ScaleFactor<other, dst>& aOther) const {
     return ScaleFactor<src, other>(scale / aOther.scale);
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -85,16 +85,17 @@ namespace gl {
 
 /** GLFeature::Enum
  * We don't use typed enum to keep the implicit integer conversion.
  * This enum should be sorted by name.
  */
 namespace GLFeature {
     enum Enum {
         bind_buffer_offset,
+        blend_minmax,
         depth_texture,
         draw_buffers,
         draw_instanced,
         element_index_uint,
         ES2_compatibility,
         ES3_compatibility,
         framebuffer_blit,
         framebuffer_multisample,
--- a/gfx/gl/GLContextFeatures.cpp
+++ b/gfx/gl/GLContextFeatures.cpp
@@ -33,16 +33,25 @@ static const FeatureInfo sFeatureInfoArr
         0,   // OpenGL ES version
         {
             GLContext::EXT_transform_feedback,
             GLContext::NV_transform_feedback,
             GLContext::Extensions_End
         }
     },
     {
+        "blend_minmax",
+        200, // OpenGL version
+        300, // OpenGL ES version
+        {
+            GLContext::EXT_blend_minmax,
+            GLContext::Extensions_End
+        }
+    },
+    {
         "depth_texture",
         200, // OpenGL version
         300, // OpenGL ES version
         {
             GLContext::ARB_depth_texture,
             GLContext::OES_depth_texture,
             GLContext::Extensions_End
         }
--- a/gfx/layers/FrameMetrics.h
+++ b/gfx/layers/FrameMetrics.h
@@ -98,30 +98,19 @@ public:
    * Return the scale factor needed to fit the viewport
    * into its composition bounds.
    */
   CSSToScreenScale CalculateIntrinsicScale() const
   {
     return CSSToScreenScale(float(mCompositionBounds.width) / float(mViewport.width));
   }
 
-  /**
-   * Return the resolution that content should be rendered at given
-   * the configuration in this metrics object: viewport dimensions,
-   * zoom factor, etc. (The mResolution member of this metrics is
-   * ignored.)
-   */
-  CSSToScreenScale CalculateResolution() const
-  {
-    return CalculateIntrinsicScale() * mZoom;
-  }
-
   CSSRect CalculateCompositedRectInCssPixels() const
   {
-    return CSSRect(gfx::RoundedIn(mCompositionBounds / CalculateResolution()));
+    return CSSRect(gfx::RoundedIn(mCompositionBounds / mZoom));
   }
 
   // ---------------------------------------------------------------------------
   // The following metrics are all in widget space/device pixels.
   //
 
   // This is the area within the widget that we're compositing to, which means
   // that it is the visible region of this frame. It is not relative to
@@ -227,29 +216,21 @@ public:
   //
   // Every time this frame is composited and the compositor samples its
   // transform, this metric is used to create a transform which is
   // post-multiplied into the parent's transform. Since this only happens when
   // we walk the layer tree, the resulting transform isn't stored here. Thus the
   // resolution of parent layers is opaque to this metric.
   LayoutDeviceToLayerScale mResolution;
 
-  // The resolution-independent "user zoom".  For example, if a page
-  // configures the viewport to a zoom value of 2x, then this member
-  // will always be 2.0 no matter what the viewport or composition
-  // bounds.
-  //
-  // In the steady state (no animations), the following is usually true
-  //
-  //  intrinsicScale = (mCompositionBounds / mViewport)
-  //  mResolution = mZoom * intrinsicScale / mDevPixelsPerCSSPixel
-  //
-  // When this is not true, we're probably asynchronously sampling a
-  // zoom animation for content.
-  ScreenToScreenScale mZoom;
+  // The "user zoom". Content is painted by gecko at mResolution * mDevPixelsPerCSSPixel,
+  // but will be drawn to the screen at mZoom. In the steady state, the
+  // two will be the same, but during an async zoom action the two may
+  // diverge.
+  CSSToScreenScale mZoom;
 
   // The conversion factor between CSS pixels and device pixels for this frame.
   // This can vary based on a variety of things, such as reflowing-zoom. The
   // conversion factor for device pixels to layers pixels is just the
   // resolution.
   CSSToLayoutDeviceScale mDevPixelsPerCSSPixel;
 
   // Whether or not this frame may have touch listeners.
--- a/gfx/layers/client/TextureClient.cpp
+++ b/gfx/layers/client/TextureClient.cpp
@@ -178,17 +178,17 @@ BufferTextureClient::UpdateSurface(gfxAS
     return false;
   }
 
   nsRefPtr<gfxContext> tmpCtx = new gfxContext(surf.get());
   tmpCtx->SetOperator(gfxContext::OPERATOR_SOURCE);
   tmpCtx->DrawSurface(aSurface, gfxSize(serializer.GetSize().width,
                                         serializer.GetSize().height));
 
-  if (TextureRequiresLocking(mFlags)) {
+  if (TextureRequiresLocking(mFlags) && !ImplementsLocking()) {
     // We don't have support for proper locking yet, so we'll
     // have to be immutable instead.
     MarkImmutable();
   }
   return true;
 }
 
 already_AddRefed<gfxASurface>
--- a/gfx/layers/client/TextureClient.h
+++ b/gfx/layers/client/TextureClient.h
@@ -106,16 +106,26 @@ public:
 
   virtual bool Lock(OpenMode aMode)
   {
     return true;
   }
 
   virtual void Unlock() {}
 
+  /**
+   * Returns true if this texture has a lock/unlock mechanism.
+   * Textures that do not implement locking should be immutable or should
+   * use immediate uploads (see TextureFlags in CompositorTypes.h)
+   */
+  virtual bool ImplementsLocking() const
+  {
+    return false;
+  }
+
   void SetID(uint64_t aID)
   {
     MOZ_ASSERT(mID == 0 || aID == 0);
     mID = aID;
   }
 
   uint64_t GetID() const
   {
--- a/gfx/layers/composite/APZCTreeManager.cpp
+++ b/gfx/layers/composite/APZCTreeManager.cpp
@@ -396,18 +396,18 @@ APZCTreeManager::ContentReceivedTouch(co
   if (apzc) {
     apzc->ContentReceivedTouch(aPreventDefault);
   }
 }
 
 void
 APZCTreeManager::UpdateZoomConstraints(const ScrollableLayerGuid& aGuid,
                                        bool aAllowZoom,
-                                       float aMinScale,
-                                       float aMaxScale)
+                                       const CSSToScreenScale& aMinScale,
+                                       const CSSToScreenScale& aMaxScale)
 {
   nsRefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(aGuid);
   if (apzc) {
     apzc->UpdateZoomConstraints(aAllowZoom, aMinScale, aMaxScale);
   }
 }
 
 void
--- a/gfx/layers/composite/APZCTreeManager.h
+++ b/gfx/layers/composite/APZCTreeManager.h
@@ -203,18 +203,18 @@ public:
 
   /**
    * Updates any zoom constraints contained in the <meta name="viewport"> tag.
    * We try to obey everything it asks us elsewhere, but here we only handle
    * minimum-scale, maximum-scale, and user-scalable.
    */
   void UpdateZoomConstraints(const ScrollableLayerGuid& aGuid,
                              bool aAllowZoom,
-                             float aMinScale,
-                             float aMaxScale);
+                             const CSSToScreenScale& aMinScale,
+                             const CSSToScreenScale& aMaxScale);
 
   /**
    * Update mFrameMetrics.mScrollOffset to the given offset.
    * This is necessary in cases where a scroll is not caused by user
    * input (for example, a content scrollTo()).
    */
   void UpdateScrollOffset(const ScrollableLayerGuid& aGuid,
                           const CSSPoint& aScrollOffset);
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -93,22 +93,22 @@ static const TimeDuration ZOOM_TO_DURATI
 /**
  * Computed time function used for sampling frames of a zoom to animation.
  */
 StaticAutoPtr<ComputedTimingFunction> gComputedTimingFunction;
 
 /**
  * Maximum zoom amount, always used, even if a page asks for higher.
  */
-static const float MAX_ZOOM = 8.0f;
+static const CSSToScreenScale MAX_ZOOM(8.0f);
 
 /**
  * Minimum zoom amount, always used, even if a page asks for lower.
  */
-static const float MIN_ZOOM = 0.125f;
+static const CSSToScreenScale MIN_ZOOM(0.125f);
 
 /**
  * Amount of time before we timeout touch event listeners. For example, if
  * content is being unruly/slow and we don't get a response back within this
  * time, we will just pretend that content did not preventDefault any touch
  * events we dispatched to it.
  */
 static int gTouchListenerTimeout = 300;
@@ -531,53 +531,47 @@ nsEventStatus AsyncPanZoomController::On
   }
 
   float prevSpan = aEvent.mPreviousSpan;
   if (fabsf(prevSpan) <= EPSILON || fabsf(aEvent.mCurrentSpan) <= EPSILON) {
     // We're still handling it; we've just decided to throw this event away.
     return nsEventStatus_eConsumeNoDefault;
   }
 
-  float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan;
+  ScreenToScreenScale spanRatio(aEvent.mCurrentSpan / aEvent.mPreviousSpan);
 
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
-    CSSToScreenScale resolution = mFrameMetrics.CalculateResolution();
-    gfxFloat userZoom = mFrameMetrics.mZoom.scale;
+    CSSToScreenScale userZoom = mFrameMetrics.mZoom;
     ScreenPoint focusPoint = aEvent.mFocusPoint;
 
-    CSSPoint focusChange = (mLastZoomFocus - focusPoint) / resolution;
+    CSSPoint focusChange = (mLastZoomFocus - focusPoint) / userZoom;
     // If displacing by the change in focus point will take us off page bounds,
     // then reduce the displacement such that it doesn't.
     if (mX.DisplacementWillOverscroll(focusChange.x) != Axis::OVERSCROLL_NONE) {
       focusChange.x -= mX.DisplacementWillOverscrollAmount(focusChange.x);
     }
     if (mY.DisplacementWillOverscroll(focusChange.y) != Axis::OVERSCROLL_NONE) {
       focusChange.y -= mY.DisplacementWillOverscrollAmount(focusChange.y);
     }
     ScrollBy(focusChange);
 
     // When we zoom in with focus, we can zoom too much towards the boundaries
     // that we actually go over them. These are the needed displacements along
     // either axis such that we don't overscroll the boundaries when zooming.
     gfx::Point neededDisplacement;
 
-    float maxZoom = mMaxZoom / mFrameMetrics.CalculateIntrinsicScale().scale;
-    float minZoom = mMinZoom / mFrameMetrics.CalculateIntrinsicScale().scale;
-
-    bool doScale = (spanRatio > 1.0 && userZoom < maxZoom) ||
-                   (spanRatio < 1.0 && userZoom > minZoom);
+    bool doScale = (spanRatio > ScreenToScreenScale(1.0) && userZoom < mMaxZoom) ||
+                   (spanRatio < ScreenToScreenScale(1.0) && userZoom > mMinZoom);
 
     if (doScale) {
-      if (userZoom * spanRatio > maxZoom) {
-        spanRatio = maxZoom / userZoom;
-      } else if (userZoom * spanRatio < minZoom) {
-        spanRatio = minZoom / userZoom;
-      }
+      spanRatio.scale = clamped(spanRatio.scale,
+                                mMinZoom.scale / userZoom.scale,
+                                mMaxZoom.scale / userZoom.scale);
 
       switch (mX.ScaleWillOverscroll(spanRatio, focusPoint.x))
       {
         case Axis::OVERSCROLL_NONE:
           break;
         case Axis::OVERSCROLL_MINUS:
         case Axis::OVERSCROLL_PLUS:
           neededDisplacement.x = -mX.ScaleWillOverscrollAmount(spanRatio, focusPoint.x);
@@ -638,49 +632,46 @@ nsEventStatus AsyncPanZoomController::On
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnLongPress(const TapGestureInput& aEvent) {
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
-    CSSToScreenScale resolution = mFrameMetrics.CalculateResolution();
-    CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, resolution);
+    CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, mFrameMetrics.mZoom);
     controller->HandleLongTap(gfx::RoundedToInt(point));
     return nsEventStatus_eConsumeNoDefault;
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnSingleTapUp(const TapGestureInput& aEvent) {
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnSingleTapConfirmed(const TapGestureInput& aEvent) {
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
-    CSSToScreenScale resolution = mFrameMetrics.CalculateResolution();
-    CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, resolution);
+    CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, mFrameMetrics.mZoom);
     controller->HandleSingleTap(gfx::RoundedToInt(point));
     return nsEventStatus_eConsumeNoDefault;
   }
   return nsEventStatus_eIgnore;
 }
 
 nsEventStatus AsyncPanZoomController::OnDoubleTap(const TapGestureInput& aEvent) {
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     if (mAllowZoom) {
-      CSSToScreenScale resolution = mFrameMetrics.CalculateResolution();
-      CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, resolution);
+      CSSPoint point = WidgetSpaceToCompensatedViewportSpace(aEvent.mPoint, mFrameMetrics.mZoom);
       controller->HandleDoubleTap(gfx::RoundedToInt(point));
     }
 
     return nsEventStatus_eConsumeNoDefault;
   }
   return nsEventStatus_eIgnore;
 }
 
@@ -736,17 +727,17 @@ void AsyncPanZoomController::TrackTouch(
 
   UpdateWithTouchAtDevicePoint(aEvent);
 
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     // We want to inversely scale it because when you're zoomed further in, a
     // larger swipe should move you a shorter distance.
-    ScreenToCSSScale inverseResolution = mFrameMetrics.CalculateResolution().Inverse();
+    ScreenToCSSScale inverseResolution = mFrameMetrics.mZoom.Inverse();
 
     gfx::Point displacement(mX.GetDisplacementForDuration(inverseResolution.scale,
                                                           timeDelta),
                             mY.GetDisplacementForDuration(inverseResolution.scale,
                                                           timeDelta));
     if (fabs(displacement.x) <= EPSILON && fabs(displacement.y) <= EPSILON) {
       return;
     }
@@ -780,17 +771,17 @@ bool AsyncPanZoomController::DoFling(con
     SendAsyncScrollEvent();
     RequestContentRepaint();
     mState = NOTHING;
     return false;
   }
 
   // We want to inversely scale it because when you're zoomed further in, a
   // larger swipe should move you a shorter distance.
-  ScreenToCSSScale inverseResolution = mFrameMetrics.CalculateResolution().Inverse();
+  ScreenToCSSScale inverseResolution = mFrameMetrics.mZoom.Inverse();
 
   ScrollBy(CSSPoint::FromUnknownPoint(gfx::Point(
     mX.GetDisplacementForDuration(inverseResolution.scale, aDelta),
     mY.GetDisplacementForDuration(inverseResolution.scale, aDelta)
   )));
   TimeDuration timePaintDelta = mPaintThrottler.TimeSinceLastRequest(GetFrameTime());
   if (timePaintDelta.ToMilliseconds() > gFlingRepaintInterval) {
     RequestContentRepaint();
@@ -807,30 +798,28 @@ void AsyncPanZoomController::CancelAnima
 void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) {
   mCompositorParent = aCompositorParent;
 }
 
 void AsyncPanZoomController::ScrollBy(const CSSPoint& aOffset) {
   mFrameMetrics.mScrollOffset += aOffset;
 }
 
-void AsyncPanZoomController::ScaleWithFocus(float aZoom,
+void AsyncPanZoomController::ScaleWithFocus(const CSSToScreenScale& aZoom,
                                             const ScreenPoint& aFocus) {
-  float zoomFactor = aZoom / mFrameMetrics.mZoom.scale;
-  CSSToScreenScale resolution = mFrameMetrics.CalculateResolution();
+  ScreenToScreenScale zoomFactor(aZoom.scale / mFrameMetrics.mZoom.scale);
+  CSSToScreenScale resolution = mFrameMetrics.mZoom;
 
-  SetZoomAndResolution(ScreenToScreenScale(aZoom));
+  SetZoomAndResolution(aZoom);
 
   // If the new scale is very small, we risk multiplying in huge rounding
   // errors, so don't bother adjusting the scroll offset.
   if (resolution.scale >= 0.01f) {
-    mFrameMetrics.mScrollOffset.x +=
-      aFocus.x * (zoomFactor - 1.0) / resolution.scale;
-    mFrameMetrics.mScrollOffset.y +=
-      aFocus.y * (zoomFactor - 1.0) / resolution.scale;
+    zoomFactor.scale -= 1.0;
+    mFrameMetrics.mScrollOffset += aFocus * zoomFactor / resolution;
   }
 }
 
 bool AsyncPanZoomController::EnlargeDisplayPortAlongAxis(float aSkateSizeMultiplier,
                                                          double aEstimatedPaintDuration,
                                                          float aCompositionBounds,
                                                          float aVelocity,
                                                          float aAcceleration,
@@ -872,18 +861,17 @@ const CSSRect AsyncPanZoomController::Ca
   // If we don't get an estimated paint duration, we probably don't have any
   // data. In this case, we're dealing with either a stationary frame or a first
   // paint. In either of these cases, we can just assume it'll take 1 second to
   // paint. Getting this correct is not important anyways since it's only really
   // useful when accelerating, which can't be happening at this point.
   double estimatedPaintDuration =
     aEstimatedPaintDuration > EPSILON ? aEstimatedPaintDuration : 1.0;
 
-  CSSToScreenScale resolution = aFrameMetrics.CalculateResolution();
-  CSSIntRect compositionBounds = gfx::RoundedIn(aFrameMetrics.mCompositionBounds / resolution);
+  CSSIntRect compositionBounds = gfx::RoundedIn(aFrameMetrics.mCompositionBounds / aFrameMetrics.mZoom);
   CSSRect scrollableRect = aFrameMetrics.mScrollableRect;
 
   // Ensure the scrollableRect is at least as big as the compositionBounds
   // because the scrollableRect can be smaller if the content is not large
   // and the scrollableRect hasn't been updated yet.
   // We move the scrollableRect up because we don't know if we can move it
   // down. i.e. we know that scrollableRect can go back as far as zero.
   // but we don't know how much further ahead it can go.
@@ -978,21 +966,21 @@ void AsyncPanZoomController::RequestCont
       mFrameMetrics.mResolution == mLastPaintRequestMetrics.mResolution) {
     return;
   }
 
   SendAsyncScrollEvent();
 
   // Cache the zoom since we're temporarily changing it for
   // acceleration-scaled painting.
-  ScreenToScreenScale actualZoom = mFrameMetrics.mZoom;
+  CSSToScreenScale actualZoom = mFrameMetrics.mZoom;
   // Calculate the factor of acceleration based on the faster of the two axes.
   float accelerationFactor =
     clamped(std::max(mX.GetAccelerationFactor(), mY.GetAccelerationFactor()),
-            MIN_ZOOM / 2.0f, MAX_ZOOM);
+            MIN_ZOOM.scale / 2.0f, MAX_ZOOM.scale);
   // Scale down the resolution a bit based on acceleration.
   mFrameMetrics.mZoom.scale /= accelerationFactor;
 
   // This message is compressed, so fire whether or not we already have a paint
   // queued up. We need to know whether or not a paint was requested anyways,
   // for the purposes of content calling window.scrollTo().
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
@@ -1043,20 +1031,19 @@ bool AsyncPanZoomController::SampleConte
       double animPosition = (aSampleTime - mAnimationStartTime) / ZOOM_TO_DURATION;
       if (animPosition > 1.0) {
         animPosition = 1.0;
       }
       // Sample the zoom at the current time point.  The sampled zoom
       // will affect the final computed resolution.
       double sampledPosition = gComputedTimingFunction->GetValue(animPosition);
 
-      ScreenToScreenScale startZoom = mStartZoomToMetrics.mZoom;
-      ScreenToScreenScale endZoom = mEndZoomToMetrics.mZoom;
-      mFrameMetrics.mZoom = ScreenToScreenScale(endZoom.scale * sampledPosition +
-                                                startZoom.scale * (1 - sampledPosition));
+      mFrameMetrics.mZoom = CSSToScreenScale(
+        mEndZoomToMetrics.mZoom.scale * sampledPosition +
+          mStartZoomToMetrics.mZoom.scale * (1 - sampledPosition));
 
       mFrameMetrics.mScrollOffset = CSSPoint::FromUnknownPoint(gfx::Point(
         mEndZoomToMetrics.mScrollOffset.x * sampledPosition +
           mStartZoomToMetrics.mScrollOffset.x * (1 - sampledPosition),
         mEndZoomToMetrics.mScrollOffset.y * sampledPosition +
           mStartZoomToMetrics.mScrollOffset.y * (1 - sampledPosition)
       ));
 
@@ -1071,17 +1058,17 @@ bool AsyncPanZoomController::SampleConte
       }
 
       break;
     }
     default:
       break;
     }
 
-    aScrollOffset = mFrameMetrics.mScrollOffset * mFrameMetrics.CalculateResolution();
+    aScrollOffset = mFrameMetrics.mScrollOffset * mFrameMetrics.mZoom;
     *aNewTransform = GetCurrentAsyncTransform();
 
     mCurrentAsyncScrollOffset = mFrameMetrics.mScrollOffset;
   }
 
   // Cancel the mAsyncScrollTimeoutTask because we will fire a
   // mozbrowserasyncscroll event or renew the mAsyncScrollTimeoutTask again.
   if (mAsyncScrollTimeoutTask) {
@@ -1116,40 +1103,43 @@ bool AsyncPanZoomController::SampleConte
 
 ViewTransform AsyncPanZoomController::GetCurrentAsyncTransform() {
   ReentrantMonitorAutoEnter lock(mMonitor);
 
   CSSPoint lastPaintScrollOffset;
   if (mLastContentPaintMetrics.IsScrollable()) {
     lastPaintScrollOffset = mLastContentPaintMetrics.mScrollOffset;
   }
-  CSSToScreenScale localScale = mFrameMetrics.CalculateResolution();
   LayerPoint translation = (mFrameMetrics.mScrollOffset - lastPaintScrollOffset)
                          * mLastContentPaintMetrics.LayersPixelsPerCSSPixel();
-  return ViewTransform(-translation, localScale / mLastContentPaintMetrics.mDevPixelsPerCSSPixel);
+  return ViewTransform(-translation,
+                       mFrameMetrics.mZoom / mLastContentPaintMetrics.mDevPixelsPerCSSPixel);
 }
 
 void AsyncPanZoomController::NotifyLayersUpdated(const FrameMetrics& aLayerMetrics, bool aIsFirstPaint) {
   ReentrantMonitorAutoEnter lock(mMonitor);
 
   mLastContentPaintMetrics = aLayerMetrics;
 
   bool isDefault = mFrameMetrics.IsDefault();
   mFrameMetrics.mMayHaveTouchListeners = aLayerMetrics.mMayHaveTouchListeners;
 
   mPaintThrottler.TaskComplete(GetFrameTime());
   bool needContentRepaint = false;
   if (aLayerMetrics.mCompositionBounds.width == mFrameMetrics.mCompositionBounds.width &&
       aLayerMetrics.mCompositionBounds.height == mFrameMetrics.mCompositionBounds.height) {
     // Remote content has sync'd up to the composition geometry
     // change, so we can accept the viewport it's calculated.
-    CSSToScreenScale previousResolution = mFrameMetrics.CalculateResolution();
+    CSSToScreenScale previousResolution = mFrameMetrics.CalculateIntrinsicScale();
     mFrameMetrics.mViewport = aLayerMetrics.mViewport;
-    CSSToScreenScale newResolution = mFrameMetrics.CalculateResolution();
-    needContentRepaint |= (previousResolution != newResolution);
+    CSSToScreenScale newResolution = mFrameMetrics.CalculateIntrinsicScale();
+    if (previousResolution != newResolution) {
+      needContentRepaint = true;
+      mFrameMetrics.mZoom.scale *= newResolution.scale / previousResolution.scale;
+    }
   }
 
   if (aIsFirstPaint || isDefault) {
     mPaintThrottler.ClearHistory();
     mPaintThrottler.SetMaxDurations(gNumPaintDurationSamples);
 
     mX.CancelTouch();
     mY.CancelTouch();
@@ -1182,17 +1172,18 @@ void AsyncPanZoomController::UpdateCompo
   mFrameMetrics.mCompositionBounds = aCompositionBounds;
 
   // If the window had 0 dimensions before, or does now, we don't want to
   // repaint or update the zoom since we'll run into rendering issues and/or
   // divide-by-zero. This manifests itself as the screen flashing. If the page
   // has gone out of view, the buffer will be cleared elsewhere anyways.
   if (aCompositionBounds.width && aCompositionBounds.height &&
       oldCompositionBounds.width && oldCompositionBounds.height) {
-    SetZoomAndResolution(mFrameMetrics.mZoom);
+    ScreenToScreenScale adjustmentFactor(float(aCompositionBounds.width) / float(oldCompositionBounds.width));
+    SetZoomAndResolution(mFrameMetrics.mZoom * adjustmentFactor);
 
     // Repaint on a rotation so that our new resolution gets properly updated.
     RequestContentRepaint();
   }
 }
 
 void AsyncPanZoomController::CancelDefaultPanZoom() {
   mDisableNextTouchBatch = true;
@@ -1210,38 +1201,34 @@ void AsyncPanZoomController::ZoomToRect(
   SetState(ANIMATING_ZOOM);
 
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     ScreenIntRect compositionBounds = mFrameMetrics.mCompositionBounds;
     CSSRect cssPageRect = mFrameMetrics.mScrollableRect;
     CSSPoint scrollOffset = mFrameMetrics.mScrollOffset;
-    float currentZoom = mFrameMetrics.mZoom.scale;
-    float targetZoom;
-    float intrinsicScale = mFrameMetrics.CalculateIntrinsicScale().scale;
+    CSSToScreenScale currentZoom = mFrameMetrics.mZoom;
+    CSSToScreenScale targetZoom;
 
     // The minimum zoom to prevent over-zoom-out.
     // If the zoom factor is lower than this (i.e. we are zoomed more into the page),
     // then the CSS content rect, in layers pixels, will be smaller than the
     // composition bounds. If this happens, we can't fill the target composited
     // area with this frame.
-    float localMinZoom = std::max(mMinZoom,
-                         std::max(compositionBounds.width / cssPageRect.width,
-                                  compositionBounds.height / cssPageRect.height))
-                         / intrinsicScale;
-    float localMaxZoom = mMaxZoom / intrinsicScale;
+    CSSToScreenScale localMinZoom(std::max(mMinZoom.scale,
+                                  std::max(compositionBounds.width / cssPageRect.width,
+                                           compositionBounds.height / cssPageRect.height)));
+    CSSToScreenScale localMaxZoom = mMaxZoom;
 
     if (!aRect.IsEmpty()) {
       // Intersect the zoom-to-rect to the CSS rect to make sure it fits.
       aRect = aRect.Intersect(cssPageRect);
-      float targetResolution =
-        std::min(compositionBounds.width / aRect.width,
-                 compositionBounds.height / aRect.height);
-      targetZoom = targetResolution / intrinsicScale;
+      targetZoom = CSSToScreenScale(std::min(compositionBounds.width / aRect.width,
+                                             compositionBounds.height / aRect.height));
     }
     // 1. If the rect is empty, request received from browserElementScrolling.js
     // 2. currentZoom is equal to mMaxZoom and user still double-tapping it
     // 3. currentZoom is equal to localMinZoom and user still double-tapping it
     // Treat these three cases as a request to zoom out as much as possible.
     if (aRect.IsEmpty() ||
         (currentZoom == localMaxZoom && targetZoom >= localMaxZoom) ||
         (currentZoom == localMinZoom && targetZoom <= localMinZoom)) {
@@ -1251,24 +1238,22 @@ void AsyncPanZoomController::ZoomToRect(
         cssPageRect.width * (compositedRect.height / compositedRect.width);
       float dh = compositedRect.height - newHeight;
 
       aRect = CSSRect(0.0f,
                            y + dh/2,
                            cssPageRect.width,
                            newHeight);
       aRect = aRect.Intersect(cssPageRect);
-      float targetResolution =
-        std::min(compositionBounds.width / aRect.width,
-                 compositionBounds.height / aRect.height);
-      targetZoom = targetResolution / intrinsicScale;
+      targetZoom = CSSToScreenScale(std::min(compositionBounds.width / aRect.width,
+                                             compositionBounds.height / aRect.height));
     }
 
-    targetZoom = clamped(targetZoom, localMinZoom, localMaxZoom);
-    mEndZoomToMetrics.mZoom = ScreenToScreenScale(targetZoom);
+    targetZoom.scale = clamped(targetZoom.scale, localMinZoom.scale, localMaxZoom.scale);
+    mEndZoomToMetrics.mZoom = targetZoom;
 
     // Adjust the zoomToRect to a sensible position to prevent overscrolling.
     FrameMetrics metricsAfterZoom = mFrameMetrics;
     metricsAfterZoom.mZoom = mEndZoomToMetrics.mZoom;
     CSSRect rectAfterZoom = metricsAfterZoom.CalculateCompositedRectInCssPixels();
 
     // If either of these conditions are met, the page will be
     // overscrolled after zoomed
@@ -1355,32 +1340,31 @@ void AsyncPanZoomController::SetState(Pa
   }
 }
 
 void AsyncPanZoomController::TimeoutTouchListeners() {
   mTouchListenerTimeoutTask = nullptr;
   ContentReceivedTouch(false);
 }
 
-void AsyncPanZoomController::SetZoomAndResolution(const ScreenToScreenScale& aZoom) {
+void AsyncPanZoomController::SetZoomAndResolution(const CSSToScreenScale& aZoom) {
   mMonitor.AssertCurrentThreadIn();
   mFrameMetrics.mZoom = aZoom;
-  CSSToScreenScale resolution = mFrameMetrics.CalculateResolution();
   // We use ScreenToLayerScale(1) below in order to ask gecko to render
   // what's currently visible on the screen. This is effectively turning
   // the async zoom amount into the gecko zoom amount.
-  mFrameMetrics.mResolution = resolution / mFrameMetrics.mDevPixelsPerCSSPixel * ScreenToLayerScale(1);
+  mFrameMetrics.mResolution = aZoom / mFrameMetrics.mDevPixelsPerCSSPixel * ScreenToLayerScale(1);
 }
 
 void AsyncPanZoomController::UpdateZoomConstraints(bool aAllowZoom,
-                                                   float aMinZoom,
-                                                   float aMaxZoom) {
+                                                   const CSSToScreenScale& aMinZoom,
+                                                   const CSSToScreenScale& aMaxZoom) {
   mAllowZoom = aAllowZoom;
-  mMinZoom = std::max(MIN_ZOOM, aMinZoom);
-  mMaxZoom = std::min(MAX_ZOOM, aMaxZoom);
+  mMinZoom = (MIN_ZOOM > aMinZoom ? MIN_ZOOM : aMinZoom);
+  mMaxZoom = (MAX_ZOOM > aMaxZoom ? aMaxZoom : MAX_ZOOM);
 }
 
 void AsyncPanZoomController::PostDelayedTask(Task* aTask, int aDelayMs) {
   nsRefPtr<GeckoContentController> controller = GetGeckoContentController();
   if (controller) {
     controller->PostDelayedTask(aTask, aDelayMs);
   }
 }
--- a/gfx/layers/ipc/AsyncPanZoomController.h
+++ b/gfx/layers/ipc/AsyncPanZoomController.h
@@ -140,17 +140,19 @@ public:
    */
   void ContentReceivedTouch(bool aPreventDefault);
 
   /**
    * Updates any zoom constraints contained in the <meta name="viewport"> tag.
    * We try to obey everything it asks us elsewhere, but here we only handle
    * minimum-scale, maximum-scale, and user-scalable.
    */
-  void UpdateZoomConstraints(bool aAllowZoom, float aMinScale, float aMaxScale);
+  void UpdateZoomConstraints(bool aAllowZoom,
+                             const mozilla::CSSToScreenScale& aMinScale,
+                             const mozilla::CSSToScreenScale& aMaxScale);
 
   /**
    * Schedules a runnable to run on the controller/UI thread at some time
    * in the future.
    */
   void PostDelayedTask(Task* aTask, int aDelayMs);
 
   // --------------------------------------------------------------------------
@@ -343,17 +345,18 @@ protected:
 
   /**
    * Scales the viewport by an amount (note that it multiplies this scale in to
    * the current scale, it doesn't set it to |aScale|). Also considers a focus
    * point so that the page zooms outward from that point.
    *
    * XXX: Fix focus point calculations.
    */
-  void ScaleWithFocus(float aScale, const ScreenPoint& aFocus);
+  void ScaleWithFocus(const mozilla::CSSToScreenScale& aScale,
+                      const ScreenPoint& aFocus);
 
   /**
    * Schedules a composite on the compositor thread. Wrapper for
    * CompositorParent::ScheduleRenderOnCompositorThread().
    */
   void ScheduleComposite();
 
   /**
@@ -448,17 +451,17 @@ protected:
   void TimeoutTouchListeners();
 
   /**
    * Utility function that sets the zoom and resolution simultaneously. This is
    * useful when we want to repaint at the current zoom level.
    *
    * *** The monitor must be held while calling this.
    */
-  void SetZoomAndResolution(const ScreenToScreenScale& aZoom);
+  void SetZoomAndResolution(const mozilla::CSSToScreenScale& aZoom);
 
   /**
    * Timeout function for mozbrowserasyncscroll event. Because we throttle
    * mozbrowserasyncscroll events in some conditions, this function ensures
    * that the last mozbrowserasyncscroll event will be fired after a period of
    * time.
    */
   void FireAsyncScrollOnTimeout();
@@ -538,18 +541,18 @@ private:
 
   AxisX mX;
   AxisY mY;
 
   // Most up-to-date constraints on zooming. These should always be reasonable
   // values; for example, allowing a min zoom of 0.0 can cause very bad things
   // to happen.
   bool mAllowZoom;
-  float mMinZoom;
-  float mMaxZoom;
+  mozilla::CSSToScreenScale mMinZoom;
+  mozilla::CSSToScreenScale mMaxZoom;
 
   // The last time the compositor has sampled the content transform for this
   // frame.
   TimeStamp mLastSampleTime;
   // The last time a touch event came through on the UI thread.
   uint32_t mLastEventTime;
 
   // Start time of an animation. This is used for a zoom to animation to mark
--- a/gfx/layers/ipc/Axis.cpp
+++ b/gfx/layers/ipc/Axis.cpp
@@ -247,41 +247,41 @@ float Axis::DisplacementWillOverscrollAm
   case OVERSCROLL_MINUS: return (GetOrigin() + aDisplacement) - GetPageStart();
   case OVERSCROLL_PLUS: return (GetCompositionEnd() + aDisplacement) - GetPageEnd();
   // Don't handle overscrolled in both directions; a displacement can't cause
   // this, it must have already been zoomed out too far.
   default: return 0;
   }
 }
 
-Axis::Overscroll Axis::ScaleWillOverscroll(float aScale, float aFocus) {
-  float originAfterScale = (GetOrigin() + aFocus) * aScale - aFocus;
+Axis::Overscroll Axis::ScaleWillOverscroll(ScreenToScreenScale aScale, float aFocus) {
+  float originAfterScale = (GetOrigin() + aFocus) * aScale.scale - aFocus;
 
   bool both = ScaleWillOverscrollBothSides(aScale);
-  bool minus = originAfterScale < GetPageStart() * aScale;
-  bool plus = (originAfterScale + GetCompositionLength()) > GetPageEnd() * aScale;
+  bool minus = originAfterScale < GetPageStart() * aScale.scale;
+  bool plus = (originAfterScale + GetCompositionLength()) > GetPageEnd() * aScale.scale;
 
   if ((minus && plus) || both) {
     return OVERSCROLL_BOTH;
   }
   if (minus) {
     return OVERSCROLL_MINUS;
   }
   if (plus) {
     return OVERSCROLL_PLUS;
   }
   return OVERSCROLL_NONE;
 }
 
-float Axis::ScaleWillOverscrollAmount(float aScale, float aFocus) {
-  float originAfterScale = (GetOrigin() + aFocus) * aScale - aFocus;
+float Axis::ScaleWillOverscrollAmount(ScreenToScreenScale aScale, float aFocus) {
+  float originAfterScale = (GetOrigin() + aFocus) * aScale.scale - aFocus;
   switch (ScaleWillOverscroll(aScale, aFocus)) {
-  case OVERSCROLL_MINUS: return originAfterScale - GetPageStart() * aScale;
+  case OVERSCROLL_MINUS: return originAfterScale - GetPageStart() * aScale.scale;
   case OVERSCROLL_PLUS: return (originAfterScale + GetCompositionLength()) -
-                               NS_lround(GetPageEnd() * aScale);
+                               NS_lround(GetPageEnd() * aScale.scale);
   // Don't handle OVERSCROLL_BOTH. Client code is expected to deal with it.
   default: return 0;
   }
 }
 
 float Axis::GetVelocity() {
   return mVelocity;
 }
@@ -314,22 +314,22 @@ float Axis::GetPageStart() {
   return GetRectOffset(pageRect);
 }
 
 float Axis::GetPageLength() {
   CSSRect pageRect = mAsyncPanZoomController->GetFrameMetrics().mScrollableRect;
   return GetRectLength(pageRect);
 }
 
-bool Axis::ScaleWillOverscrollBothSides(float aScale) {
+bool Axis::ScaleWillOverscrollBothSides(ScreenToScreenScale aScale) {
   const FrameMetrics& metrics = mAsyncPanZoomController->GetFrameMetrics();
 
   CSSRect cssContentRect = metrics.mScrollableRect;
 
-  CSSToScreenScale scale(metrics.CalculateResolution().scale * aScale);
+  CSSToScreenScale scale = metrics.mZoom * aScale;
   CSSIntRect cssCompositionBounds = RoundedIn(metrics.mCompositionBounds / scale);
 
   return GetRectLength(cssContentRect) < GetRectLength(CSSRect(cssCompositionBounds));
 }
 
 AxisX::AxisX(AsyncPanZoomController* aAsyncPanZoomController)
   : Axis(aAsyncPanZoomController)
 {
--- a/gfx/layers/ipc/Axis.h
+++ b/gfx/layers/ipc/Axis.h
@@ -137,36 +137,36 @@ public:
    * Gets the overscroll state of the axis given a scaling of the page. That is
    * to say, if the given scale is applied, this will tell you whether or not
    * it will overscroll, and in what direction.
    *
    * |aFocus| is the point at which the scale is focused at. We will offset the
    * scroll offset in such a way that it remains in the same place on the page
    * relative.
    */
-  Overscroll ScaleWillOverscroll(float aScale, float aFocus);
+  Overscroll ScaleWillOverscroll(ScreenToScreenScale aScale, float aFocus);
 
   /**
    * If a scale will overscroll the axis, this returns the amount and in what
    * direction. Similar to getExcess() but takes a displacement to apply.
    *
    * |aFocus| is the point at which the scale is focused at. We will offset the
    * scroll offset in such a way that it remains in the same place on the page
    * relative.
    */
-  float ScaleWillOverscrollAmount(float aScale, float aFocus);
+  float ScaleWillOverscrollAmount(ScreenToScreenScale aScale, float aFocus);
 
   /**
    * Checks if an axis will overscroll in both directions by computing the
    * content rect and checking that its height/width (depending on the axis)
    * does not overextend past the viewport.
    *
    * This gets called by ScaleWillOverscroll().
    */
-  bool ScaleWillOverscrollBothSides(float aScale);
+  bool ScaleWillOverscrollBothSides(ScreenToScreenScale aScale);
 
   float GetOrigin();
   float GetCompositionLength();
   float GetPageStart();
   float GetPageLength();
   float GetCompositionEnd();
   float GetPageEnd();
 
--- a/gfx/tests/gtest/TestAsyncPanZoomController.cpp
+++ b/gfx/tests/gtest/TestAsyncPanZoomController.cpp
@@ -173,17 +173,17 @@ TEST(AsyncPanZoomController, ComplexTran
 
   FrameMetrics metrics;
   metrics.mCompositionBounds = ScreenIntRect(0, 0, 24, 24);
   metrics.mDisplayPort = CSSRect(-1, -1, 6, 6);
   metrics.mViewport = CSSRect(0, 0, 4, 4);
   metrics.mScrollOffset = CSSPoint(10, 10);
   metrics.mScrollableRect = CSSRect(0, 0, 50, 50);
   metrics.mResolution = LayoutDeviceToLayerScale(2);
-  metrics.mZoom = ScreenToScreenScale(1);
+  metrics.mZoom = CSSToScreenScale(6);
   metrics.mDevPixelsPerCSSPixel = CSSToLayoutDeviceScale(3);
   metrics.mScrollId = FrameMetrics::ROOT_SCROLL_ID;
 
   FrameMetrics childMetrics = metrics;
   childMetrics.mScrollId = FrameMetrics::START_SCROLL_ID;
 
   layers[0]->AsContainerLayer()->SetFrameMetrics(metrics);
   layers[1]->AsContainerLayer()->SetFrameMetrics(childMetrics);
--- a/js/src/config/check_spidermonkey_style.py
+++ b/js/src/config/check_spidermonkey_style.py
@@ -195,17 +195,17 @@ class FileKind(object):
     @staticmethod
     def get(filename):
         if filename.endswith('.c'):
             return FileKind.C
 
         if filename.endswith('.cpp'):
             return FileKind.CPP
 
-        if filename.endswith(('inlines.h', '-inl.h')):
+        if filename.endswith(('inlines.h', '-inl.h', 'Inlines.h')):
             return FileKind.INL_H
 
         if filename.endswith('.h'):
             return FileKind.H
 
         if filename.endswith('.tbl'):
             return FileKind.TBL
 
--- a/js/src/jit/ExecutionModeInlines.h
+++ b/js/src/jit/ExecutionModeInlines.h
@@ -6,16 +6,18 @@
 
 #ifndef jit_ExecutionModeInlines_h
 #define jit_ExecutionModeInlines_h
 
 #ifdef JS_ION
 
 #include "jit/CompileInfo.h"
 
+#include "jsscriptinlines.h"
+
 namespace js {
 namespace ion {
 
 static inline bool
 HasIonScript(JSScript *script, ExecutionMode cmode)
 {
     switch (cmode) {
       case SequentialExecution: return script->hasIonScript();
--- a/js/src/jscompartment.h
+++ b/js/src/jscompartment.h
@@ -274,76 +274,18 @@ struct JSCompartment
     ~JSCompartment();
 
     bool init(JSContext *cx);
 
     /* Mark cross-compartment wrappers. */
     void markCrossCompartmentWrappers(JSTracer *trc);
     void markAllCrossCompartmentWrappers(JSTracer *trc);
 
-    bool wrap(JSContext *cx, JS::MutableHandleValue vp,
-              JS::HandleObject existing = js::NullPtr())
-    {
-        JS_ASSERT_IF(existing, vp.isObject());
-
-        /* Only GC things have to be wrapped or copied. */
-        if (!vp.isMarkable())
-            return true;
-
-        /* Handle strings. */
-        if (vp.isString()) {
-            JSString *str = vp.toString();
-            if (!wrap(cx, &str))
-                return false;
-            vp.setString(str);
-            return true;
-        }
-
-        JS_ASSERT(vp.isObject());
-
-        /*
-         * All that's left are objects.
-         *
-         * Object wrapping isn't the fastest thing in the world, in part because
-         * we have to unwrap and invoke the prewrap hook to find the identity
-         * object before we even start checking the cache. Neither of these
-         * operations are needed in the common case, where we're just wrapping
-         * a plain JS object from the wrappee's side of the membrane to the
-         * wrapper's side.
-         *
-         * To optimize this, we note that the cache should only ever contain
-         * identity objects - that is to say, objects that serve as the
-         * canonical representation for a unique object identity observable by
-         * script. Unwrap and prewrap are both steps that we take to get to the
-         * identity of an incoming objects, and as such, they shuld never map
-         * one identity object to another object. This means that we can safely
-         * check the cache immediately, and only risk false negatives. Do this
-         * in opt builds, and do both in debug builds so that we can assert
-         * that we get the same answer.
-         */
-#ifdef DEBUG
-        JS::RootedObject cacheResult(cx);
-#endif
-        JS::RootedValue v(cx, vp);
-        if (js::WrapperMap::Ptr p = crossCompartmentWrappers.lookup(v)) {
-#ifdef DEBUG
-            cacheResult = &p->value.get().toObject();
-#else
-            vp.set(p->value);
-            return true;
-#endif
-        }
-
-        JS::RootedObject obj(cx, &vp.toObject());
-        if (!wrap(cx, &obj, existing))
-            return false;
-        vp.setObject(*obj);
-        JS_ASSERT_IF(cacheResult, obj == cacheResult);
-        return true;
-    }
+    inline bool wrap(JSContext *cx, JS::MutableHandleValue vp,
+                     JS::HandleObject existing = js::NullPtr());
 
     bool wrap(JSContext *cx, JSString **strp);
     bool wrap(JSContext *cx, js::HeapPtrString *strp);
     bool wrap(JSContext *cx, JS::MutableHandleObject obj,
               JS::HandleObject existingArg = js::NullPtr());
     bool wrapId(JSContext *cx, jsid *idp);
     bool wrap(JSContext *cx, js::PropertyOp *op);
     bool wrap(JSContext *cx, js::StrictPropertyOp *op);
--- a/js/src/jscompartmentinlines.h
+++ b/js/src/jscompartmentinlines.h
@@ -38,9 +38,70 @@ js::AutoCompartment::AutoCompartment(Exc
     cx_->enterCompartment(target);
 }
 
 js::AutoCompartment::~AutoCompartment()
 {
     cx_->leaveCompartment(origin_);
 }
 
+inline bool
+JSCompartment::wrap(JSContext *cx, JS::MutableHandleValue vp, JS::HandleObject existing)
+{
+    JS_ASSERT_IF(existing, vp.isObject());
+
+    /* Only GC things have to be wrapped or copied. */
+    if (!vp.isMarkable())
+        return true;
+
+    /* Handle strings. */
+    if (vp.isString()) {
+        JSString *str = vp.toString();
+        if (!wrap(cx, &str))
+            return false;
+        vp.setString(str);
+        return true;
+    }
+
+    JS_ASSERT(vp.isObject());
+
+    /*
+     * All that's left are objects.
+     *
+     * Object wrapping isn't the fastest thing in the world, in part because
+     * we have to unwrap and invoke the prewrap hook to find the identity
+     * object before we even start checking the cache. Neither of these
+     * operations are needed in the common case, where we're just wrapping
+     * a plain JS object from the wrappee's side of the membrane to the
+     * wrapper's side.
+     *
+     * To optimize this, we note that the cache should only ever contain
+     * identity objects - that is to say, objects that serve as the
+     * canonical representation for a unique object identity observable by
+     * script. Unwrap and prewrap are both steps that we take to get to the
+     * identity of an incoming objects, and as such, they shuld never map
+     * one identity object to another object. This means that we can safely
+     * check the cache immediately, and only risk false negatives. Do this
+     * in opt builds, and do both in debug builds so that we can assert
+     * that we get the same answer.
+     */
+#ifdef DEBUG
+    JS::RootedObject cacheResult(cx);
+#endif
+    JS::RootedValue v(cx, vp);
+    if (js::WrapperMap::Ptr p = crossCompartmentWrappers.lookup(v)) {
+#ifdef DEBUG
+        cacheResult = &p->value.get().toObject();
+#else
+        vp.set(p->value);
+        return true;
+#endif
+    }
+
+    JS::RootedObject obj(cx, &vp.toObject());
+    if (!wrap(cx, &obj, existing))
+        return false;
+    vp.setObject(*obj);
+    JS_ASSERT_IF(cacheResult, obj == cacheResult);
+    return true;
+}
+
 #endif /* jscompartmentinlines_h */
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -3904,21 +3904,16 @@ static const JSFunctionSpecWithHelp shel
 "read(filename, [\"binary\"])",
 "  Synonym for snarf."),
 
     JS_FN_HELP("readRelativeToScript", ReadRelativeToScript, 1, 0,
 "readRelativeToScript(filename, [\"binary\"])",
 "  Read filename into returned string. Filename is relative to the directory\n"
 "  containing the current script."),
 
-    JS_FN_HELP("redirect", RedirectOutput, 2, 0,
-"redirect(stdoutFilename[, stderrFilename])",
-"  Redirect stdout and/or stderr to the named file. Pass undefined to avoid\n"
-"   redirecting. Filenames are relative to the current working directory."),
-
     JS_FN_HELP("compile", Compile, 1, 0,
 "compile(code)",
 "  Compiles a string to bytecode, potentially throwing."),
 
     JS_FN_HELP("parse", Parse, 1, 0,
 "parse(code)",
 "  Parses a string, potentially throwing."),
 
@@ -4013,16 +4008,21 @@ static const JSFunctionSpecWithHelp fuzz
     JS_FN_HELP("line2pc", LineToPC, 0, 0,
 "line2pc([fun,] line)",
 "  Map line number to PC."),
 
     JS_FN_HELP("pc2line", PCToLine, 0, 0,
 "pc2line(fun[, pc])",
 "  Map PC to line number."),
 
+    JS_FN_HELP("redirect", RedirectOutput, 2, 0,
+"redirect(stdoutFilename[, stderrFilename])",
+"  Redirect stdout and/or stderr to the named file. Pass undefined to avoid\n"
+"   redirecting. Filenames are relative to the current working directory."),
+
     JS_FN_HELP("setThrowHook", SetThrowHook, 1, 0,
 "setThrowHook(f)",
 "  Set throw hook to f."),
 
     JS_FN_HELP("system", System, 1, 0,
 "system(command)",
 "  Execute command on the current host, returning result code."),
 
--- a/js/xpconnect/src/Sandbox.cpp
+++ b/js/xpconnect/src/Sandbox.cpp
@@ -1160,17 +1160,17 @@ GetExpandedPrincipal(JSContext *cx, Hand
  */
 nsresult
 GetPropFromOptions(JSContext *cx, HandleObject from, const char *name, MutableHandleValue prop,
                    bool *found)
 {
     if (!JS_HasProperty(cx, from, name, found))
         return NS_ERROR_INVALID_ARG;
 
-    if (found && !JS_GetProperty(cx, from, name, prop))
+    if (*found && !JS_GetProperty(cx, from, name, prop))
         return NS_ERROR_INVALID_ARG;
 
     return NS_OK;
 }
 
 /*
  * Helper that tries to get a boolean property form the options object.
  */
--- a/layout/base/tests/test_reftests_with_caret.html
+++ b/layout/base/tests/test_reftests_with_caret.html
@@ -113,17 +113,22 @@ var tests = [
     [ 'bug633044-1.html' , 'bug633044-1-ref.html' ] ,
 ];
 
 if (!isWindows) {
   tests.push([ 'bug240933-1.html' , 'bug240933-1-ref.html' ]); // bug 681144
   tests.push([ 'bug240933-2.html' , 'bug240933-1-ref.html' ]); // bug 681162
   tests.push([ 'bug389321-1.html' , 'bug389321-1-ref.html' ]); // bug 683163
   tests.push([ 'bug482484.html'   , 'bug482484-ref.html'   ]); // bug 688575
-  tests.push([ 'bug512295-2.html' , 'bug512295-2-ref.html' ]); // bug 681331
+  if (navigator.appVersion.indexOf("Android") == -1 && 
+    SpecialPowers.Services.appinfo.name != "B2G") {
+    tests.push([ 'bug512295-2.html' , 'bug512295-2-ref.html' ]); // bug 681331
+  } else {
+    is(SpecialPowers.getIntPref("layout.spellcheckDefault"), 0, "Spellcheck should be turned off for this platrom or this if..else check removed");
+  }
   tests.push([ 'bug597519-1.html' , 'bug597519-1-ref.html' ]); // bug 680579
   tests.push([ 'bug602141-1.html' , 'bug602141-1-ref.html' ]); // bug 681334
   tests.push([ 'bug602141-2.html' , 'bug602141-2-ref.html' ]); // bug 682836
   tests.push([ 'bug602141-3.html' , 'bug602141-3-ref.html' ]); // bug 683048
   tests.push([ 'bug602141-4.html' , 'bug602141-4-ref.html' ]); // bug 681167
   tests.push([ 'bug612271-1.html' , 'bug612271-ref.html' ]);   // bug 681032
   tests.push([ 'bug612271-2.html' , 'bug612271-ref.html' ]);   // bug 680581
   tests.push([ 'bug612271-3.html' , 'bug612271-ref.html' ]);   // bug 681035
--- a/layout/ipc/RenderFrameParent.cpp
+++ b/layout/ipc/RenderFrameParent.cpp
@@ -1003,17 +1003,19 @@ RenderFrameParent::ContentReceivedTouch(
 {
   if (GetApzcTreeManager()) {
     GetApzcTreeManager()->ContentReceivedTouch(ScrollableLayerGuid(mLayersId),
                                                aPreventDefault);
   }
 }
 
 void
-RenderFrameParent::UpdateZoomConstraints(bool aAllowZoom, float aMinZoom, float aMaxZoom)
+RenderFrameParent::UpdateZoomConstraints(bool aAllowZoom,
+                                         const CSSToScreenScale& aMinZoom,
+                                         const CSSToScreenScale& aMaxZoom)
 {
   if (GetApzcTreeManager()) {
     GetApzcTreeManager()->UpdateZoomConstraints(ScrollableLayerGuid(mLayersId),
                                                 aAllowZoom, aMinZoom, aMaxZoom);
   }
 }
 
 void
--- a/layout/ipc/RenderFrameParent.h
+++ b/layout/ipc/RenderFrameParent.h
@@ -97,17 +97,19 @@ public:
                         nsInputEvent* aOutEvent);
 
   void NotifyDimensionsChanged(ScreenIntSize size);
 
   void ZoomToRect(const CSSRect& aRect);
 
   void ContentReceivedTouch(bool aPreventDefault);
 
-  void UpdateZoomConstraints(bool aAllowZoom, float aMinZoom, float aMaxZoom);
+  void UpdateZoomConstraints(bool aAllowZoom,
+                             const CSSToScreenScale& aMinZoom,
+                             const CSSToScreenScale& aMaxZoom);
 
   void UpdateScrollOffset(uint32_t aPresShellId,
                           ViewID aViewId,
                           const CSSIntPoint& aScrollOffset);
 
   bool HitTest(const nsRect& aRect);
 
 protected:
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/css21/pagination/moz-css21-table-page-break-inside-avoid-6-ref.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html lang="en-US" class="reftest-print">
+<head>
+  <title>CSS Test: CSS 2.1 page-break-inside:avoid</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=685012">
+  <meta name="flags" content="paged">
+<style type="text/css">
+@page { size:5in 3in; margin:0.5in; }
+p { height: 1in; width: 1in; margin:0; background-color:blue; }
+</style>
+</head>
+<body>
+<table cellspacing="0" cellpadding="0"><caption><p>1</p></caption><tr><td><p>1</p><p>2</p></td></tr></table>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/css21/pagination/moz-css21-table-page-break-inside-avoid-6.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en-US" class="reftest-print">
+<head>
+  <title>CSS Test: CSS 2.1 page-break-inside:avoid</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=685012">
+  <link rel="help" href="http://www.w3.org/TR/CSS21/page.html#propdef-page-break-inside">
+  <meta name="flags" content="paged">
+<style type="text/css">
+@page { size:5in 3in; margin:0.5in; }
+p { height: 1in; width: 1in; margin:0; background-color:blue; }
+.test { page-break-inside:avoid; }
+</style>
+</head>
+<body>
+<table cellspacing="0" cellpadding="0" class="test"><caption><p>1</p></caption><tr><td><p>1</p><p>2</p></td></tr></table>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/css21/pagination/moz-css21-table-page-break-inside-avoid-7-ref.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en-US" class="reftest-print">
+<head>
+  <title>CSS Test: CSS 2.1 page-break-inside:avoid</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=685012">
+  <meta name="flags" content="paged">
+<style type="text/css">
+@page { size:5in 3in; margin:0.5in; }
+p { height: 1in; width: 1in; margin:0; background-color:blue; }
+div { page-break-after: always; }
+</style>
+</head>
+<body>
+<div>Text</div>
+<table cellspacing="0" cellpadding="0"><caption><p>1</p></caption><tr><td><p>1</p><p>2</p></td></tr></table>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/css21/pagination/moz-css21-table-page-break-inside-avoid-7.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html lang="en-US" class="reftest-print">
+<head>
+  <title>CSS Test: CSS 2.1 page-break-inside:avoid</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=685012">
+  <link rel="help" href="http://www.w3.org/TR/CSS21/page.html#propdef-page-break-inside">
+  <meta name="flags" content="paged">
+<style type="text/css">
+@page { size:5in 3in; margin:0.5in; }
+p { height: 1in; width: 1in; margin:0; background-color:blue; }
+.test { page-break-inside:avoid; }
+</style>
+</head>
+<body>
+<div>Text</div>
+<table cellspacing="0" cellpadding="0" class="test"><caption><p>1</p></caption><tr><td><p>1</p><p>2</p></td></tr></table>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/w3c-css/submitted/css21/pagination/moz-css21-table-page-break-inside-avoid-8.html
@@ -0,0 +1,17 @@
+<!DOCTYPE html>
+<html lang="en-US" class="reftest-print">
+<head>
+  <title>CSS Test: CSS 2.1 page-break-inside:avoid</title>
+  <link rel="author" title="Mats Palmgren" href="https://bugzilla.mozilla.org/show_bug.cgi?id=685012">
+  <link rel="help" href="http://www.w3.org/TR/CSS21/page.html#propdef-page-break-inside">
+  <meta name="flags" content="paged">
+<style type="text/css">
+@page { size:5in 3in; margin:0.5in; }
+p { height: 1in; width: 1in; margin:0; background-color:blue; }
+.test { page-break-inside:avoid; }
+</style>
+</head>
+<body>
+<table cellspacing="0" cellpadding="0"><caption><p>1</p></caption><tbody class="test"><tr><td><p>1</p><p>2</p></td></tr></tbody></table>
+</body>
+</html>
--- a/layout/reftests/w3c-css/submitted/css21/pagination/reftest.list
+++ b/layout/reftests/w3c-css/submitted/css21/pagination/reftest.list
@@ -13,16 +13,19 @@
 == moz-css21-block-page-break-inside-avoid-13.html moz-css21-block-page-break-inside-avoid-8-ref.html
 == moz-css21-block-page-break-inside-avoid-14.html moz-css21-block-page-break-inside-avoid-14-ref.html
 == moz-css21-block-page-break-inside-avoid-15.html moz-css21-block-page-break-inside-avoid-15-ref.html
 == moz-css21-table-page-break-inside-avoid-1.html moz-css21-table-page-break-inside-avoid-ref.html
 == moz-css21-table-page-break-inside-avoid-2.html moz-css21-table-page-break-inside-avoid-2-ref.html
 == moz-css21-table-page-break-inside-avoid-3.html moz-css21-table-page-break-inside-avoid-3-ref.html
 == moz-css21-table-page-break-inside-avoid-4.html moz-css21-table-page-break-inside-avoid-4-ref.html
 == moz-css21-table-page-break-inside-avoid-5.html moz-css21-table-page-break-inside-avoid-5-ref.html
+== moz-css21-table-page-break-inside-avoid-6.html moz-css21-table-page-break-inside-avoid-6-ref.html
+== moz-css21-table-page-break-inside-avoid-7.html moz-css21-table-page-break-inside-avoid-7-ref.html
+== moz-css21-table-page-break-inside-avoid-8.html moz-css21-table-page-break-inside-avoid-6-ref.html
 == moz-css21-float-page-break-inside-avoid-1.html moz-css21-table-page-break-inside-avoid-ref.html
 == moz-css21-float-page-break-inside-avoid-2.html moz-css21-float-page-break-inside-avoid-2-ref.html
 == moz-css21-float-page-break-inside-avoid-3.html moz-css21-block-page-break-inside-avoid-ref.html
 == moz-css21-float-page-break-inside-avoid-4.html moz-css21-block-page-break-inside-avoid-ref.html
 == moz-css21-float-page-break-inside-avoid-5.html moz-css21-float-page-break-inside-avoid-5-ref.html
 == moz-css21-float-page-break-inside-avoid-6.html moz-css21-float-page-break-inside-avoid-6-ref.html
 == moz-css21-float-page-break-inside-avoid-7.html moz-css21-float-page-break-inside-avoid-7-ref.html
 == moz-css21-float-page-break-inside-avoid-8.html moz-css21-float-page-break-inside-avoid-8-ref.html
--- a/layout/tables/nsTableOuterFrame.cpp
+++ b/layout/tables/nsTableOuterFrame.cpp
@@ -822,25 +822,22 @@ nsTableOuterFrame::OuterBeginReflowChild
   // create and init the child reflow state, using placement new on
   // stack space allocated by the caller, so that the caller can destroy
   // it
   nsHTMLReflowState &childRS = * new (aChildRSSpace)
     nsHTMLReflowState(aPresContext, aOuterRS, aChildFrame, availSize,
                       -1, -1, false);
   InitChildReflowState(*aPresContext, childRS);
 
-  // see if we need to reset top of page due to a caption
-  if (mCaptionFrames.NotEmpty()) {
+  // see if we need to reset top-of-page due to a caption
+  if (childRS.mFlags.mIsTopOfPage &&
+      mCaptionFrames.FirstChild() == aChildFrame) {
     uint8_t captionSide = GetCaptionSide();
-    if (((captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM ||
-          captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE) &&
-         mCaptionFrames.FirstChild() == aChildFrame) || 
-        ((captionSide == NS_STYLE_CAPTION_SIDE_TOP ||
-          captionSide == NS_STYLE_CAPTION_SIDE_TOP_OUTSIDE) &&
-         InnerTableFrame() == aChildFrame)) {
+    if (captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM ||
+        captionSide == NS_STYLE_CAPTION_SIDE_BOTTOM_OUTSIDE) {
       childRS.mFlags.mIsTopOfPage = false;
     }
   }
 }
 
 nsresult
 nsTableOuterFrame::OuterDoReflowChild(nsPresContext*             aPresContext,
                                       nsIFrame*                  aChildFrame,
--- a/memory/mozjemalloc/jemalloc.c
+++ b/memory/mozjemalloc/jemalloc.c
@@ -5510,17 +5510,17 @@ malloc_print_stats(void)
 
 		_malloc_message("Chunk size: ", umax2s(chunksize, 10, s), "",
 		    "");
 		_malloc_message(" (2^", umax2s(opt_chunk_2pow, 10, s), ")\n",
 		    "");
 
 #ifdef MALLOC_STATS
 		{
-			size_t allocated, mapped;
+			size_t allocated, mapped = 0;
 #ifdef MALLOC_BALANCE
 			uint64_t nbalance = 0;
 #endif
 			unsigned i;
 			arena_t *arena;
 
 			/* Calculate and print allocated/mapped stats. */
 
--- a/mobile/android/base/GeckoEvent.java
+++ b/mobile/android/base/GeckoEvent.java
@@ -62,19 +62,17 @@ public class GeckoEvent {
         COMPOSITOR_PAUSE(29),
         COMPOSITOR_RESUME(30),
         NATIVE_GESTURE_EVENT(31),
         IME_KEY_EVENT(32),
         CALL_OBSERVER(33),
         REMOVE_OBSERVER(34),
         LOW_MEMORY(35),
         NETWORK_LINK_CHANGE(36),
-        TELEMETRY_HISTOGRAM_ADD(37),
-        PREFERENCES_OBSERVE(38),
-        PREFERENCES_GET(39);
+        TELEMETRY_HISTOGRAM_ADD(37);
 
         public final int value;
 
         private NativeGeckoEvent(int value) {
             this.value = value;
          }
     }
 
@@ -183,18 +181,16 @@ public class GeckoEvent {
 
     private short mScreenOrientation;
 
     private ByteBuffer mBuffer;
 
     private int mWidth;
     private int mHeight;
 
-    private String[] mPrefNames;
-
     private GeckoEvent(NativeGeckoEvent event) {
         mType = event.value;
     }
 
     public static GeckoEvent createAppBackgroundingEvent() {
         return new GeckoEvent(NativeGeckoEvent.APP_BACKGROUNDING);
     }
 
@@ -688,30 +684,16 @@ public class GeckoEvent {
     }
 
     public static GeckoEvent createRemoveObserverEvent(String observerKey) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.REMOVE_OBSERVER);
         event.mCharacters = observerKey;
         return event;
     }
 
-    public static GeckoEvent createPreferencesObserveEvent(int requestId, String[] prefNames) {
-        GeckoEvent event = new GeckoEvent(NativeGeckoEvent.PREFERENCES_OBSERVE);
-        event.mCount = requestId;
-        event.mPrefNames = prefNames;
-        return event;
-    }
-
-    public static GeckoEvent createPreferencesGetEvent(int requestId, String[] prefNames) {
-        GeckoEvent event = new GeckoEvent(NativeGeckoEvent.PREFERENCES_GET);
-        event.mCount = requestId;
-        event.mPrefNames = prefNames;
-        return event;
-    }
-
     public static GeckoEvent createLowMemoryEvent(int level) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.LOW_MEMORY);
         event.mMetaState = level;
         return event;
     }
 
     public static GeckoEvent createNetworkLinkChangeEvent(String status) {
         GeckoEvent event = new GeckoEvent(NativeGeckoEvent.NETWORK_LINK_CHANGE);
--- a/mobile/android/base/GeckoPreferences.java
+++ b/mobile/android/base/GeckoPreferences.java
@@ -666,17 +666,19 @@ public class GeckoPreferences
                 return null;
         }
 
         return dialog;
     }
 
     // Initialize preferences by requesting the preference values from Gecko
     private int getGeckoPreferences(final PreferenceGroup screen, ArrayList<String> prefs) {
-        return PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
+        JSONArray jsonPrefs = new JSONArray(prefs);
+
+        return PrefsHelper.getPrefs(jsonPrefs, new PrefsHelper.PrefHandlerBase() {
             private Preference getField(String prefName) {
                 return screen.findPreference(prefName);
             }
 
             // Handle v14 TwoStatePreference with backwards compatibility.
             class CheckBoxPrefSetter {
                 public void setBooleanPref(Preference preference, boolean value) {
                     if ((preference instanceof CheckBoxPreference) &&
--- a/mobile/android/base/PrefsHelper.java
+++ b/mobile/android/base/PrefsHelper.java
@@ -8,58 +8,72 @@ package org.mozilla.gecko;
 import org.mozilla.gecko.util.GeckoEventListener;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 
 import android.util.Log;
 
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
 
 /**
  * Helper class to get/set gecko prefs.
  */
 public final class PrefsHelper {
     private static final String LOGTAG = "GeckoPrefsHelper";
 
     private static boolean sRegistered = false;
     private static final Map<Integer, PrefHandler> sCallbacks = new HashMap<Integer, PrefHandler>();
     private static int sUniqueRequestId = 1;
 
     public static int getPref(String prefName, PrefHandler callback) {
-        return getPrefsInternal(new String[] { prefName }, callback);
+        JSONArray prefs = new JSONArray();
+        prefs.put(prefName);
+        return getPrefs(prefs, callback);
     }
 
     public static int getPrefs(String[] prefNames, PrefHandler callback) {
-        return getPrefsInternal(prefNames, callback);
+        JSONArray prefs = new JSONArray();
+        for (String p : prefNames) {
+            prefs.put(p);
+        }
+        return getPrefs(prefs, callback);
     }
 
-    public static int getPrefs(ArrayList<String> prefNames, PrefHandler callback) {
-        return getPrefsInternal(prefNames.toArray(new String[prefNames.size()]), callback);
-    }
-
-    private static int getPrefsInternal(String[] prefNames, PrefHandler callback) {
+    public static int getPrefs(JSONArray prefNames, PrefHandler callback) {
         int requestId;
         synchronized (PrefsHelper.class) {
             ensureRegistered();
 
             requestId = sUniqueRequestId++;
             sCallbacks.put(requestId, callback);
         }
 
         GeckoEvent event;
-        if (callback.isObserver()) {
-            event = GeckoEvent.createPreferencesObserveEvent(requestId, prefNames);
-        } else {
-            event = GeckoEvent.createPreferencesGetEvent(requestId, prefNames);
+        try {
+            JSONObject message = new JSONObject();
+            message.put("requestId", Integer.toString(requestId));
+            message.put("preferences", prefNames);
+            event = GeckoEvent.createBroadcastEvent(callback.isObserver() ?
+                "Preferences:Observe" : "Preferences:Get", message.toString());
+            GeckoAppShell.sendEventToGecko(event);
+        } catch (Exception e) {
+            Log.e(LOGTAG, "Error while composing Preferences:" +
+                  (callback.isObserver() ? "Observe" : "Get") + " message", e);
+
+            // if we failed to send the message, drop our reference to the callback because
+            // otherwise it will leak since we will never get the response
+            synchronized (PrefsHelper.class) {
+                sCallbacks.remove(requestId);
+            }
+
+            return -1;
         }
-        GeckoAppShell.sendEventToGecko(event);
 
         return requestId;
     }
 
     private static void ensureRegistered() {
         if (sRegistered) {
             return;
         }
--- a/mobile/android/base/RobocopAPI.java
+++ b/mobile/android/base/RobocopAPI.java
@@ -30,24 +30,16 @@ public class RobocopAPI {
     public void unregisterEventListener(String event, GeckoEventListener listener) {
         GeckoAppShell.unregisterEventListener(event, listener);
     }
 
     public void broadcastEvent(String subject, String data) {
         GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent(subject, data));
     }
 
-    public void preferencesGetEvent(int requestId, String[] prefNames) {
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesGetEvent(requestId, prefNames));
-    }
-
-    public void preferencesObserveEvent(int requestId, String[] prefNames) {
-        GeckoAppShell.sendEventToGecko(GeckoEvent.createPreferencesObserveEvent(requestId, prefNames));
-    }
-
     public void setDrawListener(GeckoLayerClient.DrawListener listener) {
         GeckoAppShell.getLayerView().getLayerClient().setDrawListener(listener);
     }
 
     public Cursor querySql(String dbPath, String query) {
         GeckoLoader.loadSQLiteLibs(mGeckoApp, mGeckoApp.getApplication().getPackageResourcePath());
         return new SQLiteBridge(dbPath).rawQuery(query, null);
     }
--- a/mobile/android/base/gfx/Axis.java
+++ b/mobile/android/base/gfx/Axis.java
@@ -59,22 +59,23 @@ abstract class Axis {
     }
 
     private static int getIntPref(Map<String, Integer> prefs, String prefName, int defaultValue) {
         Integer value = (prefs == null ? null : prefs.get(prefName));
         return (value == null || value < 0 ? defaultValue : value);
     }
 
     static void initPrefs() {
-        final String[] prefs = { PREF_SCROLLING_FRICTION_FAST,
-                                 PREF_SCROLLING_FRICTION_SLOW,
-                                 PREF_SCROLLING_MAX_EVENT_ACCELERATION,
-                                 PREF_SCROLLING_OVERSCROLL_DECEL_RATE,
-                                 PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT,
-                                 PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE };
+        JSONArray prefs = new JSONArray();
+        prefs.put(PREF_SCROLLING_FRICTION_FAST);
+        prefs.put(PREF_SCROLLING_FRICTION_SLOW);
+        prefs.put(PREF_SCROLLING_MAX_EVENT_ACCELERATION);
+        prefs.put(PREF_SCROLLING_OVERSCROLL_DECEL_RATE);
+        prefs.put(PREF_SCROLLING_OVERSCROLL_SNAP_LIMIT);
+        prefs.put(PREF_SCROLLING_MIN_SCROLLABLE_DISTANCE);
 
         PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
             Map<String, Integer> mPrefs = new HashMap<String, Integer>();
 
             @Override public void prefValue(String name, int value) {
                 mPrefs.put(name, value);
             }
 
--- a/mobile/android/base/gfx/DisplayPortCalculator.java
+++ b/mobile/android/base/gfx/DisplayPortCalculator.java
@@ -56,28 +56,29 @@ final class DisplayPortCalculator {
         return sStrategy.drawTimeUpdate(millis, pixels);
     }
 
     static void resetPageState() {
         sStrategy.resetPageState();
     }
 
     static void initPrefs() {
-        final String[] prefs = { PREF_DISPLAYPORT_STRATEGY,
-                                 PREF_DISPLAYPORT_FM_MULTIPLIER,
-                                 PREF_DISPLAYPORT_FM_DANGER_X,
-                                 PREF_DISPLAYPORT_FM_DANGER_Y,
-                                 PREF_DISPLAYPORT_VB_MULTIPLIER,
-                                 PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD,
-                                 PREF_DISPLAYPORT_VB_REVERSE_BUFFER,
-                                 PREF_DISPLAYPORT_VB_DANGER_X_BASE,
-                                 PREF_DISPLAYPORT_VB_DANGER_Y_BASE,
-                                 PREF_DISPLAYPORT_VB_DANGER_X_INCR,
-                                 PREF_DISPLAYPORT_VB_DANGER_Y_INCR,
-                                 PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD };
+        JSONArray prefs = new JSONArray();
+        prefs.put(PREF_DISPLAYPORT_STRATEGY);
+        prefs.put(PREF_DISPLAYPORT_FM_MULTIPLIER);
+        prefs.put(PREF_DISPLAYPORT_FM_DANGER_X);
+        prefs.put(PREF_DISPLAYPORT_FM_DANGER_Y);
+        prefs.put(PREF_DISPLAYPORT_VB_MULTIPLIER);
+        prefs.put(PREF_DISPLAYPORT_VB_VELOCITY_THRESHOLD);
+        prefs.put(PREF_DISPLAYPORT_VB_REVERSE_BUFFER);
+        prefs.put(PREF_DISPLAYPORT_VB_DANGER_X_BASE);
+        prefs.put(PREF_DISPLAYPORT_VB_DANGER_Y_BASE);
+        prefs.put(PREF_DISPLAYPORT_VB_DANGER_X_INCR);
+        prefs.put(PREF_DISPLAYPORT_VB_DANGER_Y_INCR);
+        prefs.put(PREF_DISPLAYPORT_PB_VELOCITY_THRESHOLD);
 
         PrefsHelper.getPrefs(prefs, new PrefsHelper.PrefHandlerBase() {
             private Map<String, Integer> mValues = new HashMap<String, Integer>();
 
             @Override public void prefValue(String pref, int value) {
                 mValues.put(pref, value);
             }
 
--- a/mobile/android/base/tests/testAddonManager.java.in
+++ b/mobile/android/base/tests/testAddonManager.java.in
@@ -54,28 +54,31 @@ public class testAddonManager extends Pi
         JSONObject jsonPref = new JSONObject();
         try {
             jsonPref.put("name", "extensions.getAddons.browseAddons");
             jsonPref.put("type", "string");
             jsonPref.put("value", getAbsoluteUrl("/robocop/robocop_blank_01.html"));
             mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());
 
             // Wait for confirmation of the pref change before proceeding with the test.
-            final String[] prefNames = { "extensions.getAddons.browseAddons" };
-            final int ourRequestId = 0x7357;
+            JSONArray getPrefData = new JSONArray();
+            getPrefData.put("extensions.getAddons.browseAddons");
+            JSONObject message = new JSONObject();
+            message.put("requestId", "testAddonManager");
+            message.put("preferences", getPrefData);
             Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendPreferencesGetEvent(ourRequestId, prefNames);
+            mActions.sendGeckoEvent("Preferences:Get", message.toString());
 
             JSONObject data = null;
-            int requestId = -1;
+            String requestId = "";
 
             // Wait until we get the correct "Preferences:Data" event
-            while (requestId != ourRequestId) {
+            while (!requestId.equals("testAddonManager")) {
                 data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getInt("requestId");
+                requestId = data.getString("requestId");
             }
             eventExpecter.unregisterListener();
 
         } catch (Exception ex) { 
             mAsserter.ok(false, "exception in testAddonManager", ex.toString());
         }
 
         // Load AMO page by clicking the AMO icon
--- a/mobile/android/base/tests/testDistribution.java.in
+++ b/mobile/android/base/tests/testDistribution.java.in
@@ -24,17 +24,17 @@ import org.json.JSONObject;
  *     preferences.json
  *     bookmarks.json
  *     searchplugins/
  *       common/
  *         engine.xml
  */
 public class testDistribution extends ContentProviderTest {
     private static final String MOCK_PACKAGE = "mock-package.zip";
-    private static final int PREF_REQUEST_ID = 0x7357;
+    private static final String PREF_REQUEST_ID = "testDistribution";
 
     private Activity mActivity;
 
     @Override
     protected int getTestType() {
         return TEST_MOCHITEST;
     }
 
@@ -81,33 +81,38 @@ public class testDistribution extends Co
         String prefID = "distribution.id";
         String prefAbout = "distribution.about";
         String prefVersion = "distribution.version";
         String prefTestBoolean = "distribution.test.boolean";
         String prefTestString = "distribution.test.string";
         String prefTestInt = "distribution.test.int";
 
         try {
-            final String[] prefNames = { prefID,
-                                         prefAbout,
-                                         prefVersion,
-                                         prefTestBoolean,
-                                         prefTestString,
-                                         prefTestInt };
+            JSONArray getPrefData = new JSONArray();
+            getPrefData.put(prefID);
+            getPrefData.put(prefAbout);
+            getPrefData.put(prefVersion);
+            getPrefData.put(prefTestBoolean);
+            getPrefData.put(prefTestString);
+            getPrefData.put(prefTestInt);
+
+            JSONObject message = new JSONObject();
+            message.put("requestId", PREF_REQUEST_ID);
+            message.put("preferences", getPrefData);
 
             Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendPreferencesGetEvent(PREF_REQUEST_ID, prefNames);
+            mActions.sendGeckoEvent("Preferences:Get", message.toString());
 
             JSONObject data = null;
-            int requestId = -1;
+            String requestId = "";
 
             // Wait until we get the correct "Preferences:Data" event
-            while (requestId != PREF_REQUEST_ID) {
+            while (!requestId.equals(PREF_REQUEST_ID)) {
                 data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getInt("requestId");
+                requestId = data.getString("requestId");
             }
             eventExpecter.unregisterListener();
 
             JSONArray preferences = data.getJSONArray("preferences");
             for (int i = 0; i < preferences.length(); i++) {
                 JSONObject pref = (JSONObject) preferences.get(i);
                 String name = pref.getString("name");
 
@@ -162,55 +167,67 @@ public class testDistribution extends Co
         try {
             // Request the pref change to the locale.
             jsonPref.put("name", prefUseragentLocale);
             jsonPref.put("type", "string");
             jsonPref.put("value", aLocale);
             mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());
 
             // Wait for confirmation of the pref change.
-            final String[] prefNames = { prefUseragentLocale };
+            JSONArray getPrefData = new JSONArray();
+            getPrefData.put(prefUseragentLocale);
+
+            JSONObject message = new JSONObject();
+            message.put("requestId", PREF_REQUEST_ID);
+            message.put("preferences", getPrefData);
 
             Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendPreferencesGetEvent(PREF_REQUEST_ID, prefNames);
+            mActions.sendGeckoEvent("Preferences:Get", message.toString());
 
             JSONObject data = null;
-            int requestId = -1;
+            String requestId = "";
 
             // Wait until we get the correct "Preferences:Data" event
-            while (requestId != PREF_REQUEST_ID) {
+            while (!requestId.equals(PREF_REQUEST_ID)) {
                 data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getInt("requestId");
+                requestId = data.getString("requestId");
             }
             eventExpecter.unregisterListener();
 
         } catch (Exception e) {
             mAsserter.ok(false, "exception setting test locale", e.toString());
         }
     }
 
     // Test localized distribution and preferences values stored in preferences.json
     private void checkLocalizedPreferences(String aLocale) {
         String prefAbout = "distribution.about";
         String prefLocalizeable = "distribution.test.localizeable";
         String prefLocalizeableOverride = "distribution.test.localizeable-override";
 
         try {
-            final String[] prefNames = { prefAbout, prefLocalizeable, prefLocalizeableOverride };
+            JSONArray getPrefData = new JSONArray();
+            getPrefData.put(prefAbout);
+            getPrefData.put(prefLocalizeable);
+            getPrefData.put(prefLocalizeableOverride);
+
+            JSONObject message = new JSONObject();
+            message.put("requestId", PREF_REQUEST_ID);
+            message.put("preferences", getPrefData);
 
             Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendPreferencesGetEvent(PREF_REQUEST_ID, prefNames);
+            mActions.sendGeckoEvent("Preferences:Get", message.toString());
 
             JSONObject data = null;
-            int requestId = -1;
+            String requestId = "";
 
             // Wait until we get the correct "Preferences:Data" event
-            while (requestId != PREF_REQUEST_ID) {
+            while (!requestId.equals(PREF_REQUEST_ID)) {
                 data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getInt("requestId");
+                requestId = data.getString("requestId");
             }
             eventExpecter.unregisterListener();
 
             JSONArray preferences = data.getJSONArray("preferences");
             for (int i = 0; i < preferences.length(); i++) {
                 JSONObject pref = (JSONObject) preferences.get(i);
                 String name = pref.getString("name");
 
--- a/mobile/android/base/tests/testDoorHanger.java.in
+++ b/mobile/android/base/tests/testDoorHanger.java.in
@@ -76,29 +76,32 @@ public class testDoorHanger extends Base
         // Make sure doorhanger is hidden
         mAsserter.is(mSolo.searchText(GEO_MESSAGE), false, "Geolocation doorhanger notification is hidden when opening a new tab");
         */
 
 
         boolean offlineAllowedByDefault = true;
         try {
             // Save offline-allow-by-default preferences first
-            final String[] prefNames = { "offline-apps.allow_by_default" };
-            final int ourRequestId = 0x7357;
+            JSONArray getPrefData = new JSONArray();
+            getPrefData.put("offline-apps.allow_by_default");
+            JSONObject message = new JSONObject();
+            message.put("requestId", "testDoorHanger");
+            message.put("preferences", getPrefData);
 
             Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendPreferencesGetEvent(ourRequestId, prefNames);
+            mActions.sendGeckoEvent("Preferences:Get", message.toString());
 
             JSONObject data = null;
-            int requestId = -1;
+            String requestId = "";
 
             // Wait until we get the correct "Preferences:Data" event
-            while (requestId != ourRequestId) {
+            while (!requestId.equals("testDoorHanger")) {
                 data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getInt("requestId");
+                requestId = data.getString("requestId");
             }
             eventExpecter.unregisterListener();
 
             JSONArray preferences = data.getJSONArray("preferences");
             if (preferences.length() > 0) {
                 JSONObject pref = (JSONObject) preferences.get(0);
                 offlineAllowedByDefault = pref.getBoolean("value");
             }
--- a/mobile/android/base/tests/testPasswordEncrypt.java.in
+++ b/mobile/android/base/tests/testPasswordEncrypt.java.in
@@ -121,28 +121,31 @@ public class testPasswordEncrypt extends
         JSONObject jsonPref = new JSONObject();
         try {
             jsonPref.put("name", "privacy.masterpassword.enabled");
             jsonPref.put("type", "string");
             jsonPref.put("value", passwd);
             mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());
 
             // Wait for confirmation of the pref change before proceeding with the test.
-            final String[] prefNames = { "privacy.masterpassword.enabled" };
-            final int ourRequestId = 0x73577;
+            JSONArray getPrefData = new JSONArray();
+            getPrefData.put("privacy.masterpassword.enabled");
+            JSONObject message = new JSONObject();
+            message.put("requestId", "testPasswordEncrypt");
+            message.put("preferences", getPrefData);
             Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
-            mActions.sendPreferencesGetEvent(ourRequestId, prefNames);
+            mActions.sendGeckoEvent("Preferences:Get", message.toString());
 
             JSONObject data = null;
-            int requestId = -1;
+            String requestId = "";
 
             // Wait until we get the correct "Preferences:Data" event
-            while (requestId != ourRequestId) {
+            while (!requestId.equals("testPasswordEncrypt")) {
                 data = new JSONObject(eventExpecter.blockForEventData());
-                requestId = data.getInt("requestId");
+                requestId = data.getString("requestId");
             }
         } catch (Exception ex) { 
             mAsserter.ok(false, "exception in toggleMasterPassword", ex.toString());
         }
     }
 
     @Override
     public void tearDown() throws Exception {
--- a/mobile/android/base/tests/testPrefsObserver.java.in
+++ b/mobile/android/base/tests/testPrefsObserver.java.in
@@ -11,17 +11,16 @@ import org.json.JSONObject;
 /**
  * Basic test to check bounce-back from overscroll.
  * - Load the page and verify it draws
  * - Drag page downwards by 100 pixels into overscroll, verify it snaps back.
  * - Drag page rightwards by 100 pixels into overscroll, verify it snaps back.
  */
 public class testPrefsObserver extends BaseTest {
     private static final String PREF_TEST_PREF = "robocop.tests.dummy";
-    private static final int PREF_OBSERVE_REQUEST_ID = 0x7357;
     private static final String PREF_REQUEST_ID = "testPrefsObserver";
     private static final long PREF_TIMEOUT = 10000;
 
     private Actions.RepeatedEventExpecter mExpecter;
 
     @Override
     protected int getTestType() {
         return TEST_MOCHITEST;
@@ -36,25 +35,25 @@ public class testPrefsObserver extends B
         jsonPref.put("value", value);
         mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());
     }
 
     public void waitAndCheckPref(boolean value) throws JSONException {
         mAsserter.dumpLog("Waiting to check pref");
 
         JSONObject data = null;
-        int requestId = -1;
+        String requestId = "";
 
-        while (requestId != PREF_OBSERVE_REQUEST_ID) {
+        while (!requestId.equals(PREF_REQUEST_ID)) {
             data = new JSONObject(mExpecter.blockForEventData());
             if (!mExpecter.eventReceived()) {
                 mAsserter.ok(false, "Checking pref is correct value", "Didn't receive pref");
                 return;
             }
-            requestId = data.getInt("requestId");
+            requestId = data.getString("requestId");
         }
 
         JSONObject pref = data.getJSONArray("preferences").getJSONObject(0);
         mAsserter.is(pref.getString("name"), PREF_TEST_PREF, "Pref name is correct");
         mAsserter.is(pref.getString("type"), "bool", "Pref type is correct");
         mAsserter.is(pref.getBoolean("value"), value, "Pref value is correct");
     }
 
@@ -76,24 +75,29 @@ public class testPrefsObserver extends B
 
         mAsserter.ok(false, "Received unobserved pref change", "");
     }
 
     public void observePref() throws JSONException {
         mAsserter.dumpLog("Setting up pref observer");
 
         // Setup the pref observer
+        JSONArray getPrefData = new JSONArray();
+        getPrefData.put(PREF_TEST_PREF);
+        JSONObject message = new JSONObject();
+        message.put("requestId", PREF_REQUEST_ID);
+        message.put("preferences", getPrefData);
         mExpecter = mActions.expectGeckoEvent("Preferences:Data");
-        mActions.sendPreferencesObserveEvent(PREF_OBSERVE_REQUEST_ID, new String[] { PREF_TEST_PREF });
+        mActions.sendGeckoEvent("Preferences:Observe", message.toString());
     }
 
     public void removePrefObserver() {
         mAsserter.dumpLog("Removing pref observer");
 
-        mActions.sendGeckoEvent("Preferences:RemoveObservers", Integer.toString(PREF_OBSERVE_REQUEST_ID));
+        mActions.sendGeckoEvent("Preferences:RemoveObservers", PREF_REQUEST_ID);
     }
 
     public void testPrefsObserver() {
         blockForGeckoReady();
 
         try {
             setPref(false);
             observePref();
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -274,17 +274,19 @@ var BrowserApp = {
     Services.obs.addObserver(this, "Tab:Closed", false);
     Services.obs.addObserver(this, "Session:Back", false);
     Services.obs.addObserver(this, "Session:ShowHistory", false);
     Services.obs.addObserver(this, "Session:Forward", false);
     Services.obs.addObserver(this, "Session:Reload", false);
     Services.obs.addObserver(this, "Session:Stop", false);
     Services.obs.addObserver(this, "SaveAs:PDF", false);
     Services.obs.addObserver(this, "Browser:Quit", false);
+    Services.obs.addObserver(this, "Preferences:Get", false);
     Services.obs.addObserver(this, "Preferences:Set", false);
+    Services.obs.addObserver(this, "Preferences:Observe", false);
     Services.obs.addObserver(this, "Preferences:RemoveObservers", false);
     Services.obs.addObserver(this, "ScrollTo:FocusedInput", false);
     Services.obs.addObserver(this, "Sanitize:ClearData", false);
     Services.obs.addObserver(this, "FullScreen:Exit", false);
     Services.obs.addObserver(this, "Viewport:Change", false);
     Services.obs.addObserver(this, "Viewport:Flush", false);
     Services.obs.addObserver(this, "Viewport:FixedMarginsChanged", false);
     Services.obs.addObserver(this, "Passwords:Init", false);
@@ -984,38 +986,37 @@ var BrowserApp = {
                                   Services.io.newFileURI(file), "", mimeInfo,
                                   Date.now() * 1000, null, cancelable, isPrivate);
 
     webBrowserPrint.print(printSettings, download);
   },
 
   notifyPrefObservers: function(aPref) {
     this._prefObservers[aPref].forEach(function(aRequestId) {
-      this.getPreferences(aRequestId, [aPref], 1);
+      let request = { requestId : aRequestId,
+                      preferences : [aPref] };
+      this.getPreferences(request);
     }, this);
   },
 
-  handlePreferencesRequest: function handlePreferencesRequest(aRequestId,
-                                                              aPrefNames,
-                                                              aListen) {
-
+  getPreferences: function getPreferences(aPrefsRequest, aListen) {
     let prefs = [];
 
-    for (let prefName of aPrefNames) {
+    for (let prefName of aPrefsRequest.preferences) {
       let pref = {
         name: prefName,
         type: "",
         value: null
       };
 
       if (aListen) {
         if (this._prefObservers[prefName])
-          this._prefObservers[prefName].push(aRequestId);
+          this._prefObservers[prefName].push(aPrefsRequest.requestId);
         else
-          this._prefObservers[prefName] = [ aRequestId ];
+          this._prefObservers[prefName] = [ aPrefsRequest.requestId ];
         Services.prefs.addObserver(prefName, this, false);
       }
 
       // These pref names are not "real" pref names.
       // They are used in the setting menu,
       // and these are passed when initializing the setting menu.
       switch (prefName) {
         // The plugin pref is actually two separate prefs, so
@@ -1112,17 +1113,17 @@ var BrowserApp = {
           break;
       }
 
       prefs.push(pref);
     }
 
     sendMessageToJava({
       type: "Preferences:Data",
-      requestId: aRequestId,    // opaque request identifier, can be any string/int/whatever
+      requestId: aPrefsRequest.requestId,    // opaque request identifier, can be any string/int/whatever
       preferences: prefs
     });
   },
 
   removePreferenceObservers: function removePreferenceObservers(aRequestId) {
     let newPrefObservers = [];
     for (let prefName in this._prefObservers) {
       let requestIds = this._prefObservers[prefName];
@@ -1435,20 +1436,28 @@ var BrowserApp = {
       case "Browser:Quit":
         this.quit();
         break;
 
       case "SaveAs:PDF":
         this.saveAsPDF(browser);
         break;
 
+      case "Preferences:Get":
+        this.getPreferences(JSON.parse(aData));
+        break;
+
       case "Preferences:Set":
         this.setPreferences(aData);
         break;
 
+      case "Preferences:Observe":
+        this.getPreferences(JSON.parse(aData), true);
+        break;
+
       case "Preferences:RemoveObservers":
         this.removePreferenceObservers(aData);
         break;
 
       case "ScrollTo:FocusedInput":
         // these messages come from a change in the viewable area and not user interaction
         // we allow scrolling to the selected input, but not zooming the page
         this.scrollToFocusedInput(browser, false);
@@ -1516,24 +1525,16 @@ var BrowserApp = {
     return this.defaultBrowserWidth = width;
   },
 
   // nsIAndroidBrowserApp
   getBrowserTab: function(tabId) {
     return this.getTabForId(tabId);
   },
 
-  getPreferences: function getPreferences(requestId, prefNames, count) {
-    this.handlePreferencesRequest(requestId, prefNames, false);
-  },
-
-  observePreferences: function observePreferences(requestId, prefNames, count) {
-    this.handlePreferencesRequest(requestId, prefNames, true);
-  },
-
   // This method will print a list from fromIndex to toIndex, optionally
   // selecting selIndex(if fromIndex<=selIndex<=toIndex)
   showHistory: function(fromIndex, toIndex, selIndex) {
     let browser = this.selectedBrowser;
     let hist = browser.sessionHistory;
     let listitems = [];
     for (let i = toIndex; i >= fromIndex; i--) {
       let entry = hist.getEntryAtIndex(i, false);
--- a/parser/htmlparser/public/moz.build
+++ b/parser/htmlparser/public/moz.build
@@ -11,21 +11,19 @@ XPIDL_SOURCES += [
 
 MODULE = 'htmlparser'
 
 EXPORTS += [
     'nsHTMLTagList.h',
     'nsHTMLTags.h',
     'nsIContentSink.h',
     'nsIDTD.h',
-    'nsIElementObserver.h',
     'nsIFragmentContentSink.h',
     'nsIHTMLContentSink.h',
     'nsIParser.h',
-    'nsIParserNode.h',
     'nsIParserService.h',
     'nsITokenizer.h',
     'nsParserBase.h',
     'nsParserCIID.h',
     'nsParserConstants.h',
     'nsScannerString.h',
     'nsToken.h',
 ]
deleted file mode 100644
--- a/parser/htmlparser/public/nsIElementObserver.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-
-/**
- * MODULE NOTES:
- * @update  rickg 03.23.2000  //removed unused NS_PARSER_SUBJECT and predecl of nsString
- * 
- */
-
-#ifndef nsIElementObserver_h__
-#define nsIElementObserver_h__
-
-#include "nsISupports.h"
-#include "prtypes.h"
-#include "nsHTMLTags.h"
-#include "nsTArray.h"
-
-
-// {4672AA04-F6AE-11d2-B3B7-00805F8A6670}
-#define NS_IELEMENTOBSERVER_IID      \
-{ 0x4672aa04, 0xf6ae, 0x11d2, { 0xb3, 0xb7, 0x0, 0x80, 0x5f, 0x8a, 0x66, 0x70 } }
-
-
-class nsIElementObserver : public nsISupports {
-public:
-  NS_DECLARE_STATIC_IID_ACCESSOR(NS_IELEMENTOBSERVER_IID)
-
-  enum { IS_DOCUMENT_WRITE = 1U };
-
-  /* Subject call observer when the parser hit the tag */
-  NS_IMETHOD Notify(nsISupports* aDocShell, 
-                    nsISupports* aChannel,
-                    const PRUnichar* aTag, 
-                    const nsTArray<nsString>* aKeys, 
-                    const nsTArray<nsString>* aValues,
-                    const uint32_t aFlags) = 0;
-
-};
-
-NS_DEFINE_STATIC_IID_ACCESSOR(nsIElementObserver, NS_IELEMENTOBSERVER_IID)
-
-#endif /* nsIElementObserver_h__ */
-
--- a/parser/htmlparser/public/nsIHTMLContentSink.h
+++ b/parser/htmlparser/public/nsIHTMLContentSink.h
@@ -44,122 +44,46 @@
  *
  * NOTE: OpenHTML() and OpenBody() may get called multiple times
  *       in the same document. That's fine, and it doesn't mean
  *       that we have multiple bodies or HTML's.
  *
  * NOTE: I haven't figured out how sub-documents (non-frames)
  *       are going to be handled. Stay tuned.
  */
-#include "nsIParserNode.h"
 #include "nsIContentSink.h"
 #include "nsHTMLTags.h"
 
 #define NS_IHTML_CONTENT_SINK_IID \
-{ 0xb6d6ae00, 0x0884, 0x4a30, \
-  { 0xa8, 0xb4, 0xce, 0xca, 0x57, 0x27, 0x1a, 0x3e } }
+  {0xefc5af86, 0x5cfd, 0x4918, {0x9d, 0xd3, 0x5f, 0x7a, 0xb2, 0x88, 0xb2, 0x68}}
 
 /**
  * This interface is OBSOLETE and in the process of being REMOVED.
  * Do NOT implement!
  */
 class nsIHTMLContentSink : public nsIContentSink 
 {
 public:
 
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IHTML_CONTENT_SINK_IID)
 
-  /**
-   * This method is used to open the HEAD container. It is useful if a tag
-   * is forcing us to open the head (probably again), like if we find a <meta>
-   * tag in the body.
-   */
-  NS_IMETHOD OpenHead() = 0;
-
-  /**
-   * This gets called when handling illegal contents, especially
-   * in dealing with tables. This method creates a new context.
-   * 
-   * @update 04/04/99 harishd
-   * @param aPosition - The position from where the new context begins.
-   */
-  NS_IMETHOD BeginContext(int32_t aPosition) = 0;
-  
-  /**
-   * This method terminates any new context that got created by
-   * BeginContext and switches back to the main context.  
-   *
-   * @update 04/04/99 harishd
-   * @param aPosition - Validates the end of a context.
-   */
-  NS_IMETHOD EndContext(int32_t aPosition) = 0;
-  
-  /**
-   * @update 01/09/2003 harishd
-   * @param aTag - Check if this tag is enabled or not.
-   */
-  NS_IMETHOD IsEnabled(int32_t aTag, bool* aReturn) = 0;
-
-  /**
-   * This method is called when parser has
-   * completed processing a chunk of tokens. The processing of the
-   * tokens may be interrupted by returning NS_ERROR_HTMLPARSER_INTERRUPTED from
-   * DidProcessAToken.
-   */
-  NS_IMETHOD DidProcessTokens() = 0;
-
-  /**
-   * This method is called when parser is about to
-   * process a single token
-   */
-  NS_IMETHOD WillProcessAToken(void) = 0;
-
-  /**
-   * This method is called when parser has completed
-   * the processing for a single token.
-   * @return NS_OK if processing should not be interrupted
-   *         NS_ERROR_HTMLPARSER_INTERRUPTED if the parsing should be interrupted
-   */
-  NS_IMETHOD DidProcessAToken(void) = 0;
+  enum ElementType { eHTML, eBody };
 
     /**
    * This method is used to open a generic container in the sink.
    *
    * @update 4/1/98 gess
-   * @param  nsIParserNode reference to parser node interface
    */     
-  NS_IMETHOD OpenContainer(const nsIParserNode& aNode) = 0;
+  NS_IMETHOD OpenContainer(ElementType aNodeType) = 0;
 
   /**
    *  This method gets called by the parser when a close
    *  container tag has been consumed and needs to be closed.
    *
    * @param  aTag - The tag to be closed.
    */     
-  NS_IMETHOD CloseContainer(const nsHTMLTag aTag) = 0;
-
-  /**
-   * This method is used when we're closing a tag that was malformed
-   * in some way. This way, the content sink can do special processing
-   * (e.g., not execute a malformed script tag).
-   *
-   * @param aTag The tag to be closed.
-   */
-  NS_IMETHOD CloseMalformedContainer(const nsHTMLTag aTag)
-  {
-    return CloseContainer(aTag);
-  }
-
-  /**
-   * This gets called by the parser when you want to add
-   * a leaf node to the current container in the content
-   * model.
-   *
-   * @update 4/1/98 gess
-   * @param  nsIParserNode reference to parser node interface
-   */     
-  NS_IMETHOD AddLeaf(const nsIParserNode& aNode) = 0;
+  NS_IMETHOD CloseContainer(ElementType aTag) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIHTMLContentSink, NS_IHTML_CONTENT_SINK_IID)
 
 #endif /* nsIHTMLContentSink_h___ */
 
--- a/parser/htmlparser/public/nsIParser.h
+++ b/parser/htmlparser/public/nsIParser.h
@@ -53,33 +53,16 @@ enum eParserDocType {
   ePlainText = 0,
   eXML,
   eHTML_Quirks,
   eHTML_Strict
 };
 
 enum eStreamState {eNone,eOnStart,eOnDataAvail,eOnStop};
 
-/** 
- *  FOR DEBUG PURPOSE ONLY
- *
- *  Use this interface to query objects that contain content information.
- *  Ex. Parser can trigger dump content by querying the sink that has
- *      access to the content.
- *  
- *  @update  harishd 05/25/00
- */
-class nsIDebugDumpContent : public nsISupports {
-public:
-  NS_DECLARE_STATIC_IID_ACCESSOR(NS_IDEBUG_DUMP_CONTENT_IID)
-  NS_IMETHOD DumpContentModel()=0;
-};
-
-NS_DEFINE_STATIC_IID_ACCESSOR(nsIDebugDumpContent, NS_IDEBUG_DUMP_CONTENT_IID)
-
 /**
  * This GECKO-INTERNAL interface is on track to being REMOVED (or refactored
  * to the point of being near-unrecognizable).
  *
  * Please DO NOT #include this file in comm-central code, in your XULRunner
  * app or binary extensions.
  *
  * Please DO NOT #include this into new files even inside Gecko. It is more
deleted file mode 100644
--- a/parser/htmlparser/public/nsIParserNode.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-
-/**
- * MODULE NOTES:
- * @update  gess 4/1/98
- * 
- * This class is defines the basic interface between the
- * parser and the content sink. The parser will iterate
- * over the collection of tokens that it sees from the
- * tokenizer, coverting each related "group" into one of
- * these. This object gets passed to the sink, and is
- * then immediately reused.
- *
- * If you want to hang onto one of these, you should
- * make your own copy.
- *
- */
-
-#ifndef NS_IPARSERNODE__
-#define NS_IPARSERNODE__
-
-#include "nsStringGlue.h"
-
-class nsIAtom;
-
-/**
- *  Parser nodes are the unit of exchange between the 
- *  parser and the content sink. Nodes offer access to
- *  the current token, its attributes, and its skipped-
- *  content if applicable.
- *  
- *  @update  gess 3/25/98
- */
-class nsIParserNode {
-  public:
-    /**
-     * Retrieve the type of the parser node.
-     * @update	gess5/11/98
-     * @return  node type.
-     */
-    virtual int32_t GetNodeType()  const =0;
-
-    /**
-     * Retrieve token type of parser node
-     * @update	gess5/11/98
-     * @return  token type
-     */
-    virtual int32_t GetTokenType()  const =0;
-
-    /**
-     * Retrieve the number of attributes in this node.
-     * @update	gess5/11/98
-     * @return  count of attributes (may be 0)
-     */
-    virtual int32_t GetAttributeCount() const =0;
-
-    /**
-     * Retrieve the key (of key/value pair) at given index
-     * @update	gess5/11/98
-     * @param   anIndex is the index of the key you want
-     * @return  string containing key.
-     */
-    virtual const nsAString& GetKeyAt(uint32_t anIndex) const = 0;
-
-    /**
-     * Retrieve the value (of key/value pair) at given index
-     * @update	gess5/11/98
-     * @param   anIndex is the index of the value you want
-     * @return  string containing value.
-     */
-    virtual const nsAString& GetValueAt(uint32_t anIndex) const = 0;
-};
-
-#endif
--- a/parser/htmlparser/public/nsIParserService.h
+++ b/parser/htmlparser/public/nsIParserService.h
@@ -4,20 +4,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsIParserService_h__
 #define nsIParserService_h__
 
 #include "nsISupports.h"
 #include "nsStringGlue.h"
 #include "nsHTMLTags.h"
-#include "nsIElementObserver.h"
 
 class nsIParser;
-class nsIParserNode;
 
 #define NS_PARSERSERVICE_CONTRACTID "@mozilla.org/parser/parser-service;1"
 
 // {90a92e37-abd6-441b-9b39-4064d98e1ede}
 #define NS_IPARSERSERVICE_IID \
 { 0x90a92e37, 0xabd6, 0x441b, { 0x9b, 0x39, 0x40, 0x64, 0xd9, 0x8e, 0x1e, 0xde } }
 
 class nsIParserService : public nsISupports {
--- a/parser/htmlparser/public/nsToken.h
+++ b/parser/htmlparser/public/nsToken.h
@@ -1,23 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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 CTOKEN__
 #define CTOKEN__
 
-#include "prtypes.h"
-#include "nsString.h"
-#include "nsError.h"
-
-class nsScanner;
-class nsTokenAllocator;
-
 enum eHTMLTokenTypes {
   eToken_unknown=0,
   eToken_start=1,      eToken_end,          eToken_comment,         eToken_entity,
   eToken_whitespace,   eToken_newline,      eToken_text,            eToken_attribute,
   eToken_instruction,  eToken_cdatasection, eToken_doctypeDecl,     eToken_markupDecl,
   eToken_last //make sure this stays the last token...
 };
 
--- a/parser/htmlparser/src/CNavDTD.cpp
+++ b/parser/htmlparser/src/CNavDTD.cpp
@@ -6,17 +6,16 @@
 
 #include "mozilla/Util.h"
 
 #include "nsISupports.h"
 #include "nsISupportsImpl.h"
 #include "nsIParser.h"
 #include "CNavDTD.h"
 #include "nsIHTMLContentSink.h"
-#include "nsParserNode.h"
 
 NS_IMPL_ISUPPORTS1(CNavDTD, nsIDTD);
 
 CNavDTD::CNavDTD()
 {
 }
 
 CNavDTD::~CNavDTD()
@@ -37,27 +36,24 @@ CNavDTD::BuildModel(nsITokenizer* aToken
 {
   // NB: It is important to throw STOPPARSING if the sink is the wrong type in
   // order to make sure nsParser cleans up properly after itself.
   nsCOMPtr<nsIHTMLContentSink> sink = do_QueryInterface(aSink);
   if (!sink) {
     return NS_ERROR_HTMLPARSER_STOPPARSING;
   }
 
-  nsParserNode html(eHTMLTag_html);
-  nsParserNode body(eHTMLTag_body);
-
-  nsresult rv = sink->OpenContainer(html);
+  nsresult rv = sink->OpenContainer(nsIHTMLContentSink::eHTML);
   NS_ENSURE_SUCCESS(rv, rv);
-  rv = sink->OpenContainer(body);
+  rv = sink->OpenContainer(nsIHTMLContentSink::eBody);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  rv = sink->CloseContainer(eHTMLTag_body);
+  rv = sink->CloseContainer(nsIHTMLContentSink::eBody);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
-  rv = sink->CloseContainer(eHTMLTag_html);
+  rv = sink->CloseContainer(nsIHTMLContentSink::eHTML);
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 CNavDTD::DidBuildModel(nsresult anErrorCode)
 {
--- a/parser/htmlparser/src/CParserContext.cpp
+++ b/parser/htmlparser/src/CParserContext.cpp
@@ -68,22 +68,17 @@ CParserContext::GetTokenizer(nsIDTD* aDT
                              nsIContentSink* aSink,
                              nsITokenizer*& aTokenizer)
 {
   nsresult result = NS_OK;
   int32_t type = aDTD ? aDTD->GetType() : NS_IPARSER_FLAG_HTML;
 
   if (!mTokenizer) {
     if (type == NS_IPARSER_FLAG_HTML || mParserCommand == eViewSource) {
-      nsCOMPtr<nsIHTMLContentSink> theSink = do_QueryInterface(aSink);
-      mTokenizer = new nsHTMLTokenizer(mDTDMode, mDocType, mParserCommand,
-                                       nsHTMLTokenizer::GetFlags(aSink));
-      if (!mTokenizer) {
-        return NS_ERROR_OUT_OF_MEMORY;
-      }
+      mTokenizer = new nsHTMLTokenizer;
     }
     else if (type == NS_IPARSER_FLAG_XML) {
       mTokenizer = do_QueryInterface(aDTD, &result);
     }
   }
 
   aTokenizer = mTokenizer;
 
--- a/parser/htmlparser/src/moz.build
+++ b/parser/htmlparser/src/moz.build
@@ -12,17 +12,16 @@ CPP_SOURCES += [
     'nsElementTable.cpp',
     'nsExpatDriver.cpp',
     'nsHTMLEntities.cpp',
     'nsHTMLTags.cpp',
     'nsHTMLTokenizer.cpp',
     'nsParser.cpp',
     'nsParserModule.cpp',
     'nsParserMsgUtils.cpp',
-    'nsParserNode.cpp',
     'nsParserService.cpp',
     'nsScanner.cpp',
     'nsScannerString.cpp',
 ]
 
 LIBRARY_NAME = 'htmlpars'
 
 LIBXUL_LIBRARY = True
--- a/parser/htmlparser/src/nsHTMLTokenizer.cpp
+++ b/parser/htmlparser/src/nsHTMLTokenizer.cpp
@@ -9,71 +9,35 @@
  * @file nsHTMLTokenizer.cpp
  * This is an implementation of the nsITokenizer interface.
  * This file contains the implementation of a tokenizer to tokenize an HTML
  * document. It attempts to do so, making tradeoffs between compatibility with
  * older parsers and the SGML specification. Note that most of the real
  * "tokenization" takes place in nsHTMLTokens.cpp.
  */
 
-#include "nsIAtom.h"
 #include "nsHTMLTokenizer.h"
+#include "nsIParser.h"
 #include "nsParserConstants.h"
-#include "nsIHTMLContentSink.h"
 
 /************************************************************************
   And now for the main class -- nsHTMLTokenizer...
  ************************************************************************/
 
 /**
  * Satisfy the nsISupports interface.
  */
 NS_IMPL_ISUPPORTS1(nsHTMLTokenizer, nsITokenizer)
 
 /**
  * Default constructor
- * 
- * @param  aParseMode The current mode the document is in (quirks, etc.)
- * @param  aDocType The document type of the current document
- * @param  aCommand What we are trying to do (view-source, parse a fragment, etc.)
  */
-nsHTMLTokenizer::nsHTMLTokenizer(nsDTDMode aParseMode,
-                                 eParserDocType aDocType,
-                                 eParserCommands aCommand,
-                                 uint32_t aFlags)
+nsHTMLTokenizer::nsHTMLTokenizer()
 {
   // TODO Assert about:blank-ness.
-  MOZ_ASSERT(!(aFlags & NS_IPARSER_FLAG_XML));
-}
-
-/**
- * The destructor ensures that we don't leak any left over tokens.
- */
-nsHTMLTokenizer::~nsHTMLTokenizer()
-{
-}
-
-/*static*/ uint32_t
-nsHTMLTokenizer::GetFlags(const nsIContentSink* aSink)
-{
-  uint32_t flags = 0;
-  nsCOMPtr<nsIHTMLContentSink> sink =
-    do_QueryInterface(const_cast<nsIContentSink*>(aSink));
-  if (sink) {
-    bool enabled = true;
-    sink->IsEnabled(eHTMLTag_frameset, &enabled);
-    if (enabled) {
-      flags |= NS_IPARSER_FLAG_FRAMES_ENABLED;
-    }
-    sink->IsEnabled(eHTMLTag_script, &enabled);
-    if (enabled) {
-      flags |= NS_IPARSER_FLAG_SCRIPT_ENABLED;
-    }
-  }
-  return flags;
 }
 
 nsresult
 nsHTMLTokenizer::WillTokenize(bool aIsFinalChunk)
 {
   return NS_OK;
 }
 
--- a/parser/htmlparser/src/nsHTMLTokenizer.h
+++ b/parser/htmlparser/src/nsHTMLTokenizer.h
@@ -8,36 +8,27 @@
  * MODULE NOTES:
  * @update  gess 4/1/98
  * 
  */
 
 #ifndef __NSHTMLTOKENIZER
 #define __NSHTMLTOKENIZER
 
+#include "mozilla/Attributes.h"
 #include "nsISupports.h"
 #include "nsITokenizer.h"
-#include "nsIDTD.h"
-#include "prtypes.h"
-#include "nsDeque.h"
-#include "nsScanner.h"
 
 #ifdef _MSC_VER
 #pragma warning( disable : 4275 )
 #endif
 
-class nsHTMLTokenizer : public nsITokenizer {
+class nsHTMLTokenizer MOZ_FINAL : public nsITokenizer {
 public:
   
   NS_DECL_ISUPPORTS
   NS_DECL_NSITOKENIZER
-  nsHTMLTokenizer(nsDTDMode aParseMode = eDTDMode_quirks,
-                  eParserDocType aDocType = eHTML_Quirks,
-                  eParserCommands aCommand = eViewNormal,
-                  uint32_t aFlags = 0);
-  virtual ~nsHTMLTokenizer();
-
-  static uint32_t GetFlags(const nsIContentSink* aSink);
+  nsHTMLTokenizer();
 };
 
 #endif
 
 
--- a/parser/htmlparser/src/nsParser.cpp
+++ b/parser/htmlparser/src/nsParser.cpp
@@ -157,42 +157,32 @@ nsParser::Init()
  *  This gets called when the htmlparser module is shutdown.
  */
 // static
 void nsParser::Shutdown()
 {
   NS_IF_RELEASE(sCharsetConverterManager);
 }
 
-#ifdef DEBUG
-static bool gDumpContent=false;
-#endif
-
 /**
  *  default constructor
  */
 nsParser::nsParser()
 {
   Initialize(true);
 }
 
 nsParser::~nsParser()
 {
   Cleanup();
 }
 
 void
 nsParser::Initialize(bool aConstructor)
 {
-#ifdef DEBUG
-  if (!gDumpContent) {
-    gDumpContent = PR_GetEnv("PARSER_DUMP_CONTENT") != nullptr;
-  }
-#endif
-
   if (aConstructor) {
     // Raw pointer
     mParserContext = 0;
   }
   else {
     // nsCOMPtrs
     mObserver = nullptr;
     mUnusedInput.Truncate();
@@ -211,30 +201,16 @@ nsParser::Initialize(bool aConstructor)
   mProcessingNetworkData = false;
   mIsAboutBlank = false;
 }
 
 void
 nsParser::Cleanup()
 {
 #ifdef DEBUG
-  if (gDumpContent) {
-    if (mSink) {
-      // Sink (HTMLContentSink at this time) supports nsIDebugDumpContent
-      // interface. We can get to the content model through the sink.
-      nsresult result = NS_OK;
-      nsCOMPtr<nsIDebugDumpContent> trigger = do_QueryInterface(mSink, &result);
-      if (NS_SUCCEEDED(result)) {
-        trigger->DumpContentModel();
-      }
-    }
-  }
-#endif
-
-#ifdef DEBUG
   if (mParserContext && mParserContext->mPrevContext) {
     NS_WARNING("Extra parser contexts still on the parser stack");
   }
 #endif
 
   while (mParserContext) {
     CParserContext *pc = mParserContext->mPrevContext;
     delete mParserContext;
--- a/parser/htmlparser/src/nsParser.h
+++ b/parser/htmlparser/src/nsParser.h
@@ -38,17 +38,16 @@
  *         
  */
 
 #ifndef NS_PARSER__
 #define NS_PARSER__
 
 #include "nsIParser.h"
 #include "nsDeque.h"
-#include "nsParserNode.h"
 #include "nsIURL.h"
 #include "CParserContext.h"
 #include "nsParserCIID.h"
 #include "nsITokenizer.h"
 #include "nsHTMLTags.h"
 #include "nsIContentSink.h"
 #include "nsCOMArray.h"
 #include "nsCycleCollectionParticipant.h"
deleted file mode 100644
--- a/parser/htmlparser/src/nsParserNode.cpp
+++ /dev/null
@@ -1,102 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-
-#include "nsParserNode.h"
-#include "nsToken.h"
-
-/**
- *  Constructor
- *  
- *  @update  gess 3/25/98
- *  @param   aToken -- token to init internal token
- *  @return  
- */
-nsParserNode::nsParserNode(eHTMLTags aTag)
-  : mTag(aTag)
-{
-}
-
-/**
- *  destructor
- *  NOTE: We intentionally DON'T recycle mToken here.
- *        It may get cached for use elsewhere
- *  @update  gess 3/25/98
- *  @param   
- *  @return  
- */
-nsParserNode::~nsParserNode() {
-}
-
-
-/**
- *  Get node type, meaning, get the tag type of the 
- *  underlying token
- *  
- *  @update  gess 3/25/98
- *  @param   
- *  @return  int value that represents tag type
- */
-int32_t 
-nsParserNode::GetNodeType(void) const
-{
-   return mTag;
-}
-
-
-/**
- *  Gets the token type, which corresponds to a value from
- *  eHTMLTokens_xxx.
- *  
- *  @update  gess 3/25/98
- *  @param   
- *  @return  
- */
-int32_t 
-nsParserNode::GetTokenType(void) const
-{
-  return eToken_start;
-}
-
-
-/**
- *  Retrieve the number of attributes on this node
- *  
- *  @update  gess 3/25/98
- *  @param   
- *  @return  int -- representing attribute count
- */
-int32_t 
-nsParserNode::GetAttributeCount() const
-{
-  return 0;
-}
-
-/**
- *  Retrieve the string rep of the attribute key at the
- *  given index.
- *  
- *  @update  gess 3/25/98
- *  @param   anIndex-- offset of attribute to retrieve
- *  @return  string rep of given attribute text key
- */
-const nsAString&
-nsParserNode::GetKeyAt(uint32_t anIndex) const
-{
-  return EmptyString();
-}
-
-/**
- *  Retrieve the string rep of the attribute at given offset
- *  
- *  @update  gess 3/25/98
- *  @param   anIndex-- offset of attribute to retrieve
- *  @return  string rep of given attribute text value
- */
-const nsAString&
-nsParserNode::GetValueAt(uint32_t anIndex) const
-{
-  return EmptyString();
-}
deleted file mode 100644
--- a/parser/htmlparser/src/nsParserNode.h
+++ /dev/null
@@ -1,68 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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 NS_PARSERNODE__
-#define NS_PARSERNODE__
-
-#include "nscore.h"
-#include "nsIParserNode.h"
-#include "nsHTMLTags.h"
-
-class nsParserNode : public nsIParserNode
-{
-  public:
-    /**
-     * Constructor
-     */
-    nsParserNode(eHTMLTags aTag);
-
-    /**
-     * Destructor
-     */
-    virtual ~nsParserNode();
-
-    /**
-     * Retrieve the type of the parser node.
-     * @return  node type.
-     */
-    virtual int32_t GetNodeType()  const;
-
-    /**
-     * Retrieve token type of parser node
-     * @return  token type
-     */
-    virtual int32_t GetTokenType()  const;
-
-
-    //***************************************
-    //methods for accessing key/value pairs
-    //***************************************
-
-    /**
-     * Retrieve the number of attributes in this node.
-     * @return  0
-     */
-    virtual int32_t GetAttributeCount() const;
-
-    /**
-     * Retrieve the key (of key/value pair) at given index
-     * @param   anIndex is the index of the key you want
-     * @return  string containing key.
-     */
-    virtual const nsAString& GetKeyAt(uint32_t anIndex) const;
-
-    /**
-     * Retrieve the value (of key/value pair) at given index
-     * @update	gess5/11/98
-     * @param   anIndex is the index of the value you want
-     * @return  string containing value.
-     */
-    virtual const nsAString& GetValueAt(uint32_t anIndex) const;
-
-  private:
-    eHTMLTags mTag;
-};
-
-#endif
--- a/python/mach/README.rst
+++ b/python/mach/README.rst
@@ -50,16 +50,61 @@ hooks it up to the command line driver. 
 to the decorators are being used as arguments to
 *argparse.ArgumentParser.add_parser()* and
 *argparse.ArgumentParser.add_argument()*. See the documentation in the
 *mach.base* module for more.
 
 The Python modules defining mach commands do not need to live inside the
 main mach source tree.
 
+Conditionally Filtering Commands
+--------------------------------
+
+Sometimes it might only make sense to run a command given a certain
+context. For example, running tests only makes sense if the product
+they are testing has been built, and said build is available. To make
+sure a command is only runnable from within a correct context, you can
+define a series of conditions on the *Command* decorator.
+
+A condition is simply a function that takes an instance of the
+*CommandProvider* class as an argument, and returns True or False. If
+any of the conditions defined on a command return False, the command
+will not be runnable. The doc string of a condition function is used in
+error messages, to explain why the command cannot currently be run.
+
+Here is an example:
+
+    from mach.decorators import (
+        CommandProvider,
+        Command,
+    )
+
+    def build_available(cls):
+        """The build needs to be available."""
+        return cls.build_path is not None
+
+    @CommandProvider
+    class MyClass(MachCommandBase):
+        def __init__(self, build_path=None):
+            self.build_path = build_path
+
+        @Command('run_tests', conditions=[build_available])
+        def run_tests(self, force=False):
+            # Do stuff here.
+
+It is important to make sure that any state needed by the condition is
+available to instances of the command provider.
+
+By default all commands without any conditions applied will be runnable,
+but it is possible to change this behaviour by setting *require_conditions*
+to True:
+
+    m = mach.main.Mach()
+    m.require_conditions = True
+
 Minimizing Code in Mach
 -----------------------
 
 Mach is just a frontend. Therefore, code in this package should pertain to
 one of 3 areas:
 
 1. Obtaining user input (parsing arguments, prompting, etc)
 2. Calling into some other Python package
--- a/python/mach/mach/base.py
+++ b/python/mach/mach/base.py
@@ -72,25 +72,31 @@ class MethodHandler(object):
         'category',
 
         # Description of the purpose of this command.
         'description',
 
         # Whether to allow all arguments from the parser.
         'allow_all_arguments',
 
+        # Functions used to 'skip' commands if they don't meet the conditions
+        # in a given context.
+        'conditions',
+
         # Arguments added to this command's parser. This is a 2-tuple of
         # positional and named arguments, respectively.
         'arguments',
     )
 
     def __init__(self, cls, method, name, category=None, description=None,
-        allow_all_arguments=False, arguments=None, pass_context=False):
+        allow_all_arguments=False, conditions=None, arguments=None,
+        pass_context=False):
 
         self.cls = cls
         self.method = method
         self.name = name
         self.category = category
         self.description = description
         self.allow_all_arguments = allow_all_arguments
+        self.conditions = conditions or []
         self.arguments = arguments or []
         self.pass_context = pass_context
 
--- a/python/mach/mach/decorators.py
+++ b/python/mach/mach/decorators.py
@@ -1,14 +1,15 @@
 # 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/.
 
 from __future__ import unicode_literals
 
+import collections
 import inspect
 import types
 
 from .base import (
     MachError,
     MethodHandler
 )
 
@@ -50,27 +51,45 @@ def CommandProvider(cls):
     # define commands multiple times. We also sort keys so commands defined in
     # the same class are grouped in a sane order.
     for attr in sorted(cls.__dict__.keys()):
         value = cls.__dict__[attr]
 
         if not isinstance(value, types.FunctionType):
             continue
 
-        command_name, category, description, allow_all = getattr(value,
-            '_mach_command', (None, None, None, None))
+        command_name, category, description, allow_all, conditions = getattr(
+            value, '_mach_command', (None, None, None, None, None))
 
         if command_name is None:
             continue
 
+        if conditions is None and Registrar.require_conditions:
+            continue
+
+        msg = 'Mach command \'%s\' implemented incorrectly. ' + \
+              'Conditions argument must take a list ' + \
+              'of functions. Found %s instead.'
+
+        conditions = conditions or []
+        if not isinstance(conditions, collections.Iterable):
+            msg = msg % (command_name, type(conditions))
+            raise MachError(msg)
+
+        for c in conditions:
+            if not hasattr(c, '__call__'):
+                msg = msg % (command_name, type(c))
+                raise MachError(msg)
+
         arguments = getattr(value, '_mach_command_args', None)
 
         handler = MethodHandler(cls, attr, command_name, category=category,
             description=description, allow_all_arguments=allow_all,
-            arguments=arguments, pass_context=pass_context)
+            conditions=conditions, arguments=arguments,
+            pass_context=pass_context)
 
         Registrar.register_command_handler(handler)
 
     return cls
 
 
 class Command(object):
     """Decorator for functions or methods that provide a mach subcommand.
@@ -88,25 +107,26 @@ class Command(object):
 
     For example:
 
         @Command('foo', category='misc', description='Run the foo action')
         def foo(self):
             pass
     """
     def __init__(self, name, category=None, description=None,
-        allow_all_args=False):
+        allow_all_args=False, conditions=None):
         self._name = name
         self._category = category
         self._description = description
         self._allow_all_args = allow_all_args
+        self._conditions = conditions
 
     def __call__(self, func):
         func._mach_command = (self._name, self._category, self._description,
-            self._allow_all_args)
+            self._allow_all_args, self._conditions)
 
         return func
 
 
 class CommandArgument(object):
     """Decorator for additional arguments to mach subcommands.
 
     This decorator should be used to add arguments to mach commands. Arguments
--- a/python/mach/mach/dispatcher.py
+++ b/python/mach/mach/dispatcher.py
@@ -49,27 +49,28 @@ class CommandAction(argparse.Action):
     this class is instantiated. One of the subtle but important things it does
     is tell the argument parser that it's interested in *all* of the remaining
     program arguments. So, when the ArgumentParser calls this action, we will
     receive the command name plus all of its arguments.
 
     For more, read the docs in __call__.
     """
     def __init__(self, option_strings, dest, required=True, default=None,
-        registrar=None):
+        registrar=None, context=None):
         # A proper API would have **kwargs here. However, since we are a little
         # hacky, we intentionally omit it as a way of detecting potentially
         # breaking changes with argparse's implementation.
         #
         # In a similar vein, default is passed in but is not needed, so we drop
         # it.
         argparse.Action.__init__(self, option_strings, dest, required=required,
             help=argparse.SUPPRESS, nargs=argparse.REMAINDER)
 
         self._mach_registrar = registrar
+        self._context = context
 
     def __call__(self, parser, namespace, values, option_string=None):
         """This is called when the ArgumentParser has reached our arguments.
 
         Since we always register ourselves with nargs=argparse.REMAINDER,
         values should be a list of remaining arguments to parse. The first
         argument should be the name of the command to invoke and all remaining
         arguments are arguments for that command.
@@ -143,27 +144,43 @@ class CommandAction(argparse.Action):
         # we create groups in the ArgumentParser and populate each group with
         # arguments corresponding to command names. This has the side-effect
         # that argparse renders it nicely.
         r = self._mach_registrar
 
         cats = [(k, v[2]) for k, v in r.categories.items()]
         sorted_cats = sorted(cats, key=itemgetter(1), reverse=True)
         for category, priority in sorted_cats:
-            if not r.commands_by_category[category]:
-                continue
-
-            title, description, _priority = r.categories[category]
-
-            group = parser.add_argument_group(title, description)
+            group = None
 
             for command in sorted(r.commands_by_category[category]):
                 handler = r.command_handlers[command]
+
+                # Instantiate a handler class to see if it should be filtered
+                # out for the current context or not. Condition functions can be
+                # applied to the command's decorator.
+                if handler.conditions:
+                    if handler.pass_context:
+                        instance = handler.cls(self._context)
+                    else:
+                        instance = handler.cls()
+
+                    is_filtered = False
+                    for c in handler.conditions:
+                        if not c(instance):
+                            is_filtered = True
+                            break
+                    if is_filtered:
+                        continue
+
+                if group is None:
+                    title, description, _priority = r.categories[category]
+                    group = parser.add_argument_group(title, description)
+
                 description = handler.description
-
                 group.add_argument(command, help=description,
                     action='store_true')
 
         parser.print_help()
 
     def _handle_subcommand_help(self, parser, command):
         handler = self._mach_registrar.command_handlers.get(command)
 
--- a/python/mach/mach/main.py
+++ b/python/mach/mach/main.py
@@ -82,16 +82,23 @@ Run |mach help| to show a list of comman
 '''.lstrip()
 
 UNRECOGNIZED_ARGUMENT_ERROR = r'''
 It looks like you passed an unrecognized argument into mach.
 
 The %s command does not accept the arguments: %s
 '''.lstrip()
 
+INVALID_COMMAND_CONTEXT = r'''
+It looks like you tried to run a mach command from an invalid context. The %s
+command failed to meet the following conditions: %s
+
+Run |mach help| to show a list of all commands available to the current context.
+'''.lstrip()
+
 
 class ArgumentParser(argparse.ArgumentParser):
     """Custom implementation argument parser to make things look pretty."""
 
     def error(self, message):
         """Custom error reporter to give more helpful text on bad commands."""
         if not message.startswith('argument command: invalid choice'):
             argparse.ArgumentParser.error(self, message)
@@ -135,16 +142,20 @@ class Mach(object):
     The following attributes may be assigned to the instance to influence
     behavior:
 
         populate_context_handler -- If defined, it must be a callable. The
             callable will be called with the mach.base.CommandContext instance
             as its single argument right before command dispatch. This allows
             modification of the context instance and thus passing of
             arbitrary data to command handlers.
+
+        require_conditions -- If True, commands that do not have any condition
+            functions applied will be skipped. Defaults to False.
+
     """
 
     USAGE = """%(prog)s [global arguments] command [command arguments]
 
 mach (German for "do") is the main interface to the Mozilla build system and
 common developer tasks.
 
 You tell mach the command you want to perform and it does it for you.
@@ -204,16 +215,24 @@ To see more help for a specific command,
 
         imp.load_source(module_name, path)
 
     def define_category(self, name, title, description, priority=50):
         """Provide a description for a named command category."""
 
         Registrar.register_category(name, title, description, priority)
 
+    @property
+    def require_conditions(self):
+        return Registrar.require_conditions
+
+    @require_conditions.setter
+    def require_conditions(self, value):
+        Registrar.require_conditions = value
+
     def run(self, argv, stdin=None, stdout=None, stderr=None):
         """Runs mach with arguments provided from the command line.
 
         Returns the integer exit code that should be used. 0 means success. All
         other values indicate failure.
         """
 
         # If no encoding is defined, we default to UTF-8 because without this
@@ -265,17 +284,24 @@ To see more help for a specific command,
             return 1
 
         finally:
             sys.stdin = orig_stdin
             sys.stdout = orig_stdout
             sys.stderr = orig_stderr
 
     def _run(self, argv):
-        parser = self.get_argument_parser()
+        context = CommandContext(topdir=self.cwd, cwd=self.cwd,
+            settings=self.settings, log_manager=self.log_manager,
+            commands=Registrar)
+
+        if self.populate_context_handler:
+            self.populate_context_handler(context)
+
+        parser = self.get_argument_parser(context)
 
         if not len(argv):
             # We don't register the usage until here because if it is globally
             # registered, argparse always prints it. This is not desired when
             # running with --help.
             parser.usage = Mach.USAGE
             parser.print_usage()
             return 0
@@ -309,31 +335,34 @@ To see more help for a specific command,
         self.log_manager.add_terminal_logging(level=log_level,
             write_interval=args.log_interval)
 
         self.load_settings(args)
 
         if not hasattr(args, 'mach_handler'):
             raise MachError('ArgumentParser result missing mach handler info.')
 
-        context = CommandContext(topdir=self.cwd, cwd=self.cwd,
-            settings=self.settings, log_manager=self.log_manager,
-            commands=Registrar)
-
-        if self.populate_context_handler:
-            self.populate_context_handler(context)
-
         handler = getattr(args, 'mach_handler')
         cls = handler.cls
 
         if handler.pass_context:
             instance = cls(context)
         else:
             instance = cls()
 
+        if handler.conditions:
+            fail_conditions = []
+            for c in handler.conditions:
+                if not c(instance):
+                    fail_conditions.append(c)
+
+            if fail_conditions:
+                print(self._condition_failed_message(handler.name, fail_conditions))
+                return 1
+
         fn = getattr(instance, handler.method)
 
         try:
             result = fn(**vars(args.command_args))
 
             if not result:
                 result = 0
 
@@ -388,16 +417,26 @@ To see more help for a specific command,
 
             return 1
 
     def log(self, level, action, params, format_str):
         """Helper method to record a structured log event."""
         self.logger.log(level, format_str,
             extra={'action': action, 'params': params})
 
+    @classmethod
+    def _condition_failed_message(cls, name, conditions):
+        msg = ['\n']
+        for c in conditions:
+            part = ['  %s' % c.__name__]
+            if c.__doc__ is not None:
+                part.append(c.__doc__)
+            msg.append(' - '.join(part))
+        return INVALID_COMMAND_CONTEXT % (name, '\n'.join(msg))
+
     def _print_error_header(self, argv, fh):
         fh.write('Error running mach:\n\n')
         fh.write('    ')
         fh.write(repr(argv))
         fh.write('\n\n')
 
     def _print_exception(self, fh, exc_type, exc_value, stack):
         fh.write(ERROR_FOOTER)
@@ -443,17 +482,17 @@ To see more help for a specific command,
             p = args.settings_file
         elif 'MACH_SETTINGS_FILE' in os.environ:
             p = os.environ['MACH_SETTINGS_FILE']
 
         self.settings.load_file(p)
 
         return os.path.exists(p)
 
-    def get_argument_parser(self):
+    def get_argument_parser(self, context):
         """Returns an argument parser for the command-line interface."""
 
         parser = ArgumentParser(add_help=False,
             usage='%(prog)s [global arguments] command [command arguments]')
 
         # Order is important here as it dictates the order the auto-generated
         # help messages are printed.
         global_group = parser.add_argument_group('Global Arguments')
@@ -471,12 +510,12 @@ To see more help for a specific command,
             action='store_true', default=False,
             help='Prefix log line with interval from last message rather '
                 'than relative time. Note that this is NOT execution time '
                 'if there are parallel operations.')
 
         # We need to be last because CommandAction swallows all remaining
         # arguments and argparse parses arguments in the order they were added.
         parser.add_argument('command', action=CommandAction,
-            registrar=Registrar)
+            registrar=Registrar, context=context)
 
         return parser
 
--- a/python/mach/mach/registrar.py
+++ b/python/mach/mach/registrar.py
@@ -10,16 +10,17 @@ from .base import MachError
 class MachRegistrar(object):
     """Container for mach command and config providers."""
 
     def __init__(self):
         self.command_handlers = {}
         self.commands_by_category = {}
         self.settings_providers = set()
         self.categories = {}
+        self.require_conditions = False
 
     def register_command_handler(self, handler):
         name = handler.name
 
         if not handler.category:
             raise MachError('Cannot register a mach command without a '
                 'category: %s' % name)
 
--- a/python/mach/mach/test/common.py
+++ b/python/mach/mach/test/common.py
@@ -1,29 +1,36 @@
 # 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/.
 
 from __future__ import unicode_literals
 
-import time
+from StringIO import StringIO
+import os
+import unittest
 
-from mach.base import (
-    CommandArgument,
-    CommandProvider,
-    Command,
-)
+from mach.main import Mach
+from mach.base import CommandContext
 
-import mach.test.common2 as common2
+here = os.path.abspath(os.path.dirname(__file__))
 
+class TestBase(unittest.TestCase):
+    provider_dir = os.path.join(here, 'providers')
 
-@CommandProvider
-class TestCommandProvider(object):
-    @Command('throw')
-    @CommandArgument('--message', '-m', default='General Error')
-    def throw(self, message):
-        raise Exception(message)
+    def _run_mach(self, args, provider_file, context_handler=None):
+        m = Mach(os.getcwd())
+        m.define_category('testing', 'Mach unittest', 'Testing for mach core', 10)
+        m.populate_context_handler = context_handler
+
+        m.load_commands_from_file(os.path.join(self.provider_dir, provider_file))
 
-    @Command('throw_deep')
-    @CommandArgument('--message', '-m', default='General Error')
-    def throw_deep(self, message):
-        common2.throw_deep(message)
+        stdout = StringIO()
+        stderr = StringIO()
+        stdout.encoding = 'UTF-8'
+        stderr.encoding = 'UTF-8'
 
+        try:
+            result = m.run(args, stdout=stdout, stderr=stderr)
+        except SystemExit:
+            result = None
+
+        return (result, stdout.getvalue(), stderr.getvalue())
deleted file mode 100644
--- a/python/mach/mach/test/common2.py
+++ /dev/null
@@ -1,13 +0,0 @@
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-# This file exists to trigger the differences in mach error reporting between
-# exceptions that occur in mach command modules themselves and in the things
-# they call.
-
-def throw_deep(message):
-    return throw_real(message)
-
-def throw_real(message):
-    raise Exception(message)
new file mode 100644
--- /dev/null
+++ b/python/mach/mach/test/providers/conditions.py
@@ -0,0 +1,53 @@
+# 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/.
+
+from __future__ import unicode_literals
+
+from mach.decorators import (
+    CommandProvider,
+    Command,
+)
+
+def is_foo(cls):
+    """Foo must be true"""
+    return cls.foo
+
+def is_bar(cls):
+    """Bar must be true"""
+    return cls.bar
+
+@CommandProvider
+class ConditionsProvider(object):
+    foo = True
+    bar = False
+
+    @Command('cmd_foo', category='testing', conditions=[is_foo])
+    def run_foo(self):
+        pass
+
+    @Command('cmd_bar', category='testing', conditions=[is_bar])
+    def run_bar(self):
+        pass
+
+    @Command('cmd_foobar', category='testing', conditions=[is_foo, is_bar])
+    def run_foobar(self):
+        pass
+
+@CommandProvider
+class ConditionsContextProvider(object):
+    def __init__(self, context):
+        self.foo = context.foo
+        self.bar = context.bar
+
+    @Command('cmd_foo_ctx', category='testing', conditions=[is_foo])
+    def run_foo(self):
+        pass
+
+    @Command('cmd_bar_ctx', category='testing', conditions=[is_bar])
+    def run_bar(self):
+        pass
+
+    @Command('cmd_foobar_ctx', category='testing', conditions=[is_foo, is_bar])
+    def run_foobar(self):
+        pass
new file mode 100644
--- /dev/null
+++ b/python/mach/mach/test/providers/conditions_invalid.py
@@ -0,0 +1,16 @@
+# 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/.
+
+from __future__ import unicode_literals
+
+from mach.decorators import (
+    CommandProvider,
+    Command,
+)
+
+@CommandProvider
+class ConditionsProvider(object):
+    @Command('cmd_foo', category='testing', conditions=["invalid"])
+    def run_foo(self):
+        pass
new file mode 100644
--- /dev/null
+++ b/python/mach/mach/test/providers/throw.py
@@ -0,0 +1,29 @@
+# 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/.
+
+from __future__ import unicode_literals
+
+import time
+
+from mach.decorators import (
+    CommandArgument,
+    CommandProvider,
+    Command,
+)
+
+from mach.test.providers import throw2
+
+
+@CommandProvider
+class TestCommandProvider(object):
+    @Command('throw')
+    @CommandArgument('--message', '-m', default='General Error')
+    def throw(self, message):
+        raise Exception(message)
+
+    @Command('throw_deep')
+    @CommandArgument('--message', '-m', default='General Error')
+    def throw_deep(self, message):
+        throw2.throw_deep(message)
+
new file mode 100644
--- /dev/null
+++ b/python/mach/mach/test/providers/throw2.py
@@ -0,0 +1,13 @@
+# 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/.
+
+# This file exists to trigger the differences in mach error reporting between
+# exceptions that occur in mach command modules themselves and in the things
+# they call.
+
+def throw_deep(message):
+    return throw_real(message)
+
+def throw_real(message):
+    raise Exception(message)
new file mode 100644
--- /dev/null
+++ b/python/mach/mach/test/test_conditions.py
@@ -0,0 +1,73 @@
+# 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/.
+
+from __future__ import unicode_literals
+
+import os
+import unittest
+
+from StringIO import StringIO
+
+from mach.base import MachError
+from mach.main import Mach
+from mach.test.common import TestBase
+
+def _populate_context(context):
+    context.foo = True
+    context.bar = False
+
+class TestConditions(TestBase):
+    """Tests for conditionally filtering commands."""
+
+    def _run_mach(self, args, context_handler=None):
+        return TestBase._run_mach(self, args, 'conditions.py',
+                                  context_handler=context_handler)
+
+
+    def test_conditions_pass(self):
+        """Test that a command which passes its conditions is runnable."""
+
+        self.assertEquals((0, '', ''), self._run_mach(['cmd_foo']))
+        self.assertEquals((0, '', ''), self._run_mach(['cmd_foo_ctx'], _populate_context))
+
+    def test_invalid_context_message(self):
+        """Test that commands which do not pass all their conditions
+        print the proper failure message."""
+
+        def is_bar():
+            """Bar must be true"""
+        fail_conditions = [is_bar]
+
+        for name in ('cmd_bar', 'cmd_foobar'):
+            result, stdout, stderr = self._run_mach([name])
+            self.assertEquals(1, result)
+
+            fail_msg = Mach._condition_failed_message(name, fail_conditions)
+            self.assertEquals(fail_msg.rstrip(), stdout.rstrip())
+
+        for name in ('cmd_bar_ctx', 'cmd_foobar_ctx'):
+            result, stdout, stderr = self._run_mach([name], _populate_context)
+            self.assertEquals(1, result)
+
+            fail_msg = Mach._condition_failed_message(name, fail_conditions)
+            self.assertEquals(fail_msg.rstrip(), stdout.rstrip())
+
+    def test_invalid_type(self):
+        """Test that a condition which is not callable raises an exception."""
+
+        m = Mach(os.getcwd())
+        m.define_category('testing', 'Mach unittest', 'Testing for mach core', 10)
+        self.assertRaises(MachError, m.load_commands_from_file,
+                os.path.join(self.provider_dir, 'conditions_invalid.py'))
+
+    def test_help_message(self):
+        """Test that commands that are not runnable do not show up in help."""
+
+        result, stdout, stderr = self._run_mach(['help'], _populate_context)
+        self.assertIn('cmd_foo', stdout)
+        self.assertNotIn('cmd_bar', stdout)
+        self.assertNotIn('cmd_foobar', stdout)
+        self.assertIn('cmd_foo_ctx', stdout)
+        self.assertNotIn('cmd_bar_ctx', stdout)
+        self.assertNotIn('cmd_foobar_ctx', stdout)
--- a/python/mach/mach/test/test_error_output.py
+++ b/python/mach/mach/test/test_error_output.py
@@ -6,36 +6,27 @@ from __future__ import unicode_literals
 
 import imp
 import os
 import sys
 import unittest
 
 from StringIO import StringIO
 
-import mach.main
+from mach.main import (
+    COMMAND_ERROR,
+    MODULE_ERROR
+)
+from mach.test.common import TestBase
 
 
-class TestErrorOutput(unittest.TestCase):
-    @classmethod
-    def setUpClass(cls):
-        common_path = os.path.join(os.path.dirname(__file__), 'common.py')
-        imp.load_source('mach.commands.error_output_test', common_path)
+class TestErrorOutput(TestBase):
 
     def _run_mach(self, args):
-        m = mach.main.Mach(os.getcwd())
-
-        stdout = StringIO()
-        stderr = StringIO()
-        stdout.encoding = 'UTF-8'
-        stderr.encoding = 'UTF-8'
-
-        result = m.run(args, stdout=stdout, stderr=stderr)
-
-        return (result, stdout.getvalue(), stderr.getvalue())
+        return TestBase._run_mach(self, args, 'throw.py')
 
     def test_command_error(self):
         result, stdout, stderr = self._run_mach(['throw', '--message',
             'Command Error'])
 
         self.assertEqual(result, 1)
 
         self.assertIn(mach.main.COMMAND_ERROR, stdout)
--- a/python/mozboot/bin/bootstrap.py
+++ b/python/mozboot/bin/bootstrap.py
@@ -32,16 +32,17 @@ REPOSITORY_PATH_PREFIX = 'python/mozboot
 
 REPOSITORY_PATHS = [
     'mozboot/__init__.py',
     'mozboot/base.py',
     'mozboot/bootstrap.py',
     'mozboot/centos.py',
     'mozboot/debian.py',
     'mozboot/fedora.py',
+    'mozboot/freebsd.py',
     'mozboot/gentoo.py',
     'mozboot/mint.py',
     'mozboot/openbsd.py',
     'mozboot/osx.py',
     'mozboot/ubuntu.py',
 ]
 
 TEMPDIR = None
--- a/python/mozboot/mozboot/base.py
+++ b/python/mozboot/mozboot/base.py
@@ -91,17 +91,20 @@ class BaseBootstrapper(object):
             test = os.path.join(path, name)
             if os.path.exists(test) and os.access(test, os.X_OK):
                 return test
 
         return None
 
     def run_as_root(self, command):
         if os.geteuid() != 0:
-            command.insert(0, 'sudo')
+            if self.which('sudo'):
+                command.insert(0, 'sudo')
+            else:
+                command = ['su', 'root', '-c', ' '.join(command)]
 
         print('Executing as root:', subprocess.list2cmdline(command))
 
         subprocess.check_call(command, stdin=sys.stdin)
 
     def yum_install(self, *packages):
         command = ['yum', 'install']
         command.extend(packages)
--- a/python/mozboot/mozboot/bootstrap.py
+++ b/python/mozboot/mozboot/bootstrap.py
@@ -6,16 +6,17 @@
 from __future__ import p