Merge m-c to inbound
authorWes Kocher <wkocher@mozilla.com>
Thu, 10 Oct 2013 19:09:24 -0700
changeset 164229 027a7386406ff2ce0fc59e6afda43fd1d5c78179
parent 164228 bc0cd43eff34d7c6b3ccbd7512de85983ce11a46 (current diff)
parent 164221 672cd63528d3143db4c5fc4fb3ee11fa697f8489 (diff)
child 164230 d64b073e736edd1a59c96ec096f58f69ae15710b
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound
mobile/android/base/GeckoView.java.frag
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "ed107ffac75d42dbacc19f81d0e2650e34c83a16", 
+    "revision": "34b6a207b754b02ee836ed9c8293a47b997f714c", 
     "repo_path": "/integration/gaia-central"
 }
--- a/browser/devtools/framework/test/browser_toolbox_options.js
+++ b/browser/devtools/framework/test/browser_toolbox_options.js
@@ -1,163 +1,216 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
-let doc = null, toolbox = null, panelWin = null, index = 0, prefValues = [], prefNodes = [];
+let doc = null, toolbox = null, panelWin = null, modifiedPrefs = [];
 
 function test() {
   waitForExplicitFinish();
 
   gBrowser.selectedTab = gBrowser.addTab();
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
   gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
-    gDevTools.showToolbox(target).then(testSelectTool);
+    gDevTools.showToolbox(target)
+      .then(testSelectTool)
+      .then(testOptionsShortcut)
+      .then(testOptions)
+      .then(testToggleTools)
+      .then(cleanup, errorHandler);
   }, true);
 
   content.location = "data:text/html;charset=utf8,test for dynamically registering and unregistering tools";
 }
 
 function testSelectTool(aToolbox) {
+  let deferred = promise.defer();
+
   toolbox = aToolbox;
   doc = toolbox.doc;
-  toolbox.once("options-selected", testOptionsShortcut);
+  toolbox.once("options-selected", () => {
+    ok(true, "Toolbox selected via selectTool method");
+    deferred.resolve();
+  });
   toolbox.selectTool("options");
+
+  return deferred.promise;
 }
 
 function testOptionsShortcut() {
-  ok(true, "Toolbox selected via selectTool method");
-  toolbox.once("options-selected", testOptions);
+  let deferred = promise.defer();
+
+  toolbox.once("options-selected", (event, tool) => {
+    ok(true, "Toolbox selected via shortcut key");
+    deferred.resolve(tool);
+  });
   toolbox.selectTool("webconsole")
          .then(() => synthesizeKeyFromKeyTag("toolbox-options-key", doc));
+
+  return deferred.promise;
 }
 
-function testOptions(event, tool) {
-  ok(true, "Toolbox selected via button click");
+function testOptions(tool) {
   panelWin = tool.panelWin;
-  // Testing pref changes
-  let prefCheckboxes = tool.panelDoc.querySelectorAll("checkbox[data-pref]");
-  for (let checkbox of prefCheckboxes) {
-    prefNodes.push(checkbox);
-    prefValues.push(Services.prefs.getBoolPref(checkbox.getAttribute("data-pref")));
+  let prefNodes = tool.panelDoc.querySelectorAll("checkbox[data-pref]");
+
+  // Store modified pref names so that they can be cleared on error.
+  for (let node of prefNodes) {
+    let pref = node.getAttribute("data-pref");
+    modifiedPrefs.push(pref);
+  }
+
+  // Test each options pref
+  let p = promise.resolve();
+  for (let node of prefNodes) {
+    let prefValue = Services.prefs.getBoolPref(node.getAttribute("data-pref"));
+    p = p.then(testMouseClick.bind(null, node, prefValue));
   }
   // Do again with opposite values to reset prefs
-  for (let checkbox of prefCheckboxes) {
-    prefNodes.push(checkbox);
-    prefValues.push(!Services.prefs.getBoolPref(checkbox.getAttribute("data-pref")));
+  for (let node of prefNodes) {
+    let prefValue = !Services.prefs.getBoolPref(node.getAttribute("data-pref"));
+    p = p.then(testMouseClick.bind(null, node, prefValue));
   }
-  testMouseClicks();
+
+  return p;
 }
 
-function testMouseClicks() {
-  if (index == prefValues.length) {
-    checkTools();
-    return;
-  }
-  gDevTools.once("pref-changed", prefChanged);
-  info("Click event synthesized for index " + index);
-  prefNodes[index].scrollIntoView();
+function testMouseClick(node, prefValue) {
+  let deferred = promise.defer();
+
+  let pref = node.getAttribute("data-pref");
+  gDevTools.once("pref-changed", (event, data) => {
+    if (data.pref == pref) {
+      ok(true, "Correct pref was changed");
+      is(data.oldValue, prefValue, "Previous value is correct");
+      is(data.newValue, !prefValue, "New value is correct");
+    } else {
+      ok(false, "Pref " + pref + " was not changed correctly");
+    }
+    deferred.resolve();
+  });
+
+  node.scrollIntoView();
 
   // We use executeSoon here to ensure that the element is in view and
   // clickable.
   executeSoon(function() {
-    EventUtils.synthesizeMouseAtCenter(prefNodes[index], {}, panelWin);
+    info("Click event synthesized for pref " + pref);
+    EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
   });
-}
 
-function prefChanged(event, data) {
-  if (data.pref == prefNodes[index].getAttribute("data-pref")) {
-    ok(true, "Correct pref was changed");
-    is(data.oldValue, prefValues[index], "Previous value is correct");
-    is(data.newValue, !prefValues[index], "New value is correct");
-    index++;
-    testMouseClicks();
-    return;
-  }
-  ok(false, "Pref was not changed correctly");
-  cleanup();
-}
-
-function checkTools() {
-  let toolsPref = panelWin.document.querySelectorAll("#default-tools-box > checkbox:not([unsupported])");
-  prefNodes = [];
-  index = 0;
-  for (let tool of toolsPref) {
-    prefNodes.push(tool);
-  }
-  // Randomize the order in which we remove the tool and then add them back so
-  // that we get to know if the tabs are correctly placed as per their ordinals.
-  prefNodes = prefNodes.sort(() => Math.random() > 0.5 ? 1: -1);
-
-  // Wait for the next turn of the event loop to avoid stack overflow errors.
-  executeSoon(toggleTools);
+  return deferred.promise;
 }
 
-function toggleTools() {
-  if (index < prefNodes.length) {
-    gDevTools.once("tool-unregistered", checkUnregistered);
-    let node = prefNodes[index];
-    node.scrollIntoView();
-    EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
+function testToggleTools() {
+  let toolNodes = panelWin.document.querySelectorAll("#default-tools-box > checkbox:not([unsupported])");
+  let enabledTools = Array.prototype.filter.call(toolNodes, node => node.checked);
+
+  // Store modified pref names so that they can be cleared on error.
+  for (let tool of gDevTools.getDefaultTools()) {
+    let pref = tool.visibilityswitch;
+    modifiedPrefs.push(pref);
+  }
+
+  // Toggle each tool
+  let p = promise.resolve();
+  for (let node of toolNodes) {
+    p = p.then(toggleTool.bind(null, node));
+  }
+  // Toggle again to reset tool enablement state
+  for (let node of toolNodes) {
+    p = p.then(toggleTool.bind(null, node));
   }
-  else if (index < 2*prefNodes.length) {
-    gDevTools.once("tool-registered", checkRegistered);
-    let node = prefNodes[index - prefNodes.length];
-    node.scrollIntoView();
-    EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
+
+  // Test that a tool can still be added when no tabs are present:
+  // Disable all tools
+  for (let node of enabledTools) {
+    p = p.then(toggleTool.bind(null, node));
+  }
+  // Re-enable the tools which are enabled by default
+  for (let node of enabledTools) {
+    p = p.then(toggleTool.bind(null, node));
   }
-  else {
-    cleanup();
-  }
+
+  // Toggle first, middle, and last tools to ensure that toolbox tabs are
+  // inserted in order
+  let firstTool  = toolNodes[0],
+      middleTool = toolNodes[(toolNodes.length / 2) | 0],
+      lastTool   = toolNodes[toolNodes.length - 1];
+
+  p = p.then(toggleTool.bind(null, firstTool))
+       .then(toggleTool.bind(null, firstTool))
+       .then(toggleTool.bind(null, middleTool))
+       .then(toggleTool.bind(null, middleTool))
+       .then(toggleTool.bind(null, lastTool))
+       .then(toggleTool.bind(null, lastTool));
+
+  return p;
 }
 
-function checkUnregistered(event, data) {
-  if (data.id == prefNodes[index].getAttribute("id")) {
+function toggleTool(node) {
+  let deferred = promise.defer();
+
+  let toolId = node.getAttribute("id");
+  if (node.checked) {
+    gDevTools.once("tool-unregistered", checkUnregistered.bind(null, toolId, deferred));
+  } else {
+    gDevTools.once("tool-registered", checkRegistered.bind(null, toolId, deferred));
+  }
+  node.scrollIntoView();
+  EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
+
+  return deferred.promise;
+}
+
+function checkUnregistered(toolId, deferred, event, data) {
+  if (data.id == toolId) {
     ok(true, "Correct tool removed");
     // checking tab on the toolbox
-    ok(!doc.getElementById("toolbox-tab-" + data.id), "Tab removed for " +
-       data.id);
-    index++;
-    // Wait for the next turn of the event loop to avoid stack overflow errors.
-    executeSoon(toggleTools);
-    return;
+    ok(!doc.getElementById("toolbox-tab-" + toolId), "Tab removed for " + toolId);
+  } else {
+    ok(false, "Something went wrong, " + toolId + " was not unregistered");
   }
-  ok(false, "Something went wrong, " + data.id + " was not unregistered");
-  cleanup();
+  deferred.resolve();
 }
 
-function checkRegistered(event, data) {
-  if (data == prefNodes[index - prefNodes.length].getAttribute("id")) {
+function checkRegistered(toolId, deferred, event, data) {
+  if (data == toolId) {
     ok(true, "Correct tool added back");
     // checking tab on the toolbox
-    let radio = doc.getElementById("toolbox-tab-" + data);
-    ok(radio, "Tab added back for " + data);
+    let radio = doc.getElementById("toolbox-tab-" + toolId);
+    ok(radio, "Tab added back for " + toolId);
     if (radio.previousSibling) {
       ok(+radio.getAttribute("ordinal") >=
          +radio.previousSibling.getAttribute("ordinal"),
          "Inserted tab's ordinal is greater than equal to its previous tab." +
          "Expected " + radio.getAttribute("ordinal") + " >= " +
          radio.previousSibling.getAttribute("ordinal"));
     }
     if (radio.nextSibling) {
       ok(+radio.getAttribute("ordinal") <
          +radio.nextSibling.getAttribute("ordinal"),
          "Inserted tab's ordinal is less than its next tab. Expected " +
          radio.getAttribute("ordinal") + " < " +
          radio.nextSibling.getAttribute("ordinal"));
     }
-    index++;
-    // Wait for the next turn of the event loop to avoid stack overflow errors.
-    executeSoon(toggleTools);
-    return;
+  } else {
+    ok(false, "Something went wrong, " + toolId + " was not registered");
   }
-  ok(false, "Something went wrong, " + data + " was not registered back");
-  cleanup();
+  deferred.resolve();
 }
 
 function cleanup() {
   toolbox.destroy().then(function() {
     gBrowser.removeCurrentTab();
-    toolbox = doc = prefNodes = prefValues = panelWin = null;
+    for (let pref of modifiedPrefs) {
+      Services.prefs.clearUserPref(pref);
+    }
+    toolbox = doc = panelWin = modifiedPrefs = null;
     finish();
   });
 }
+
+function errorHandler(error) {
+  ok(false, "Unexpected error: " + error);
+  cleanup();
+}
--- a/browser/devtools/framework/test/browser_toolbox_window_shortcuts.js
+++ b/browser/devtools/framework/test/browser_toolbox_window_shortcuts.js
@@ -1,29 +1,41 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 let Toolbox = devtools.Toolbox;
 
-let toolbox, toolIDs, idIndex;
+let toolbox, toolIDs, idIndex, modifiedPrefs = [];
 
 function test() {
   waitForExplicitFinish();
 
   if (window.navigator.userAgent.indexOf("Mac OS X 10.8") != -1 ||
       window.navigator.userAgent.indexOf("Windows NT 5.1") != -1) {
     info("Skipping Mac OSX 10.8 and Windows xp, see bug 838069");
     finish();
     return;
   }
   addTab("about:blank", function() {
     toolIDs = [];
     for (let [id, definition] of gDevTools._tools) {
       if (definition.key) {
         toolIDs.push(id);
+
+        // Enable disabled tools
+        let pref = definition.visibilityswitch, prefValue;
+        try {
+          prefValue = Services.prefs.getBoolPref(pref);
+        } catch (e) {
+          continue;
+        }
+        if (!prefValue) {
+          modifiedPrefs.push(pref);
+          Services.prefs.setBoolPref(pref, true);
+        }
       }
     }
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     idIndex = 0;
     gDevTools.showToolbox(target, toolIDs[0], Toolbox.HostType.WINDOW)
              .then(testShortcuts);
   });
 }
@@ -66,12 +78,15 @@ function selectCB(event, id) {
 
   testShortcuts(toolbox, idIndex + 1);
 }
 
 function tidyUp() {
   toolbox.destroy().then(function() {
     gBrowser.removeCurrentTab();
 
-    toolbox = toolIDs = idIndex = Toolbox = null;
+    for (let pref of modifiedPrefs) {
+      Services.prefs.clearUserPref(pref);
+    }
+    toolbox = toolIDs = idIndex = modifiedPrefs = Toolbox = null;
     finish();
   });
 }
--- a/browser/metro/base/content/ContextCommands.js
+++ b/browser/metro/base/content/ContextCommands.js
@@ -254,16 +254,34 @@ var ContextCommands = {
   findInPage: function cc_findInPage() {
     FindHelperUI.show();
   },
 
   viewOnDesktop: function cc_viewOnDesktop() {
     Appbar.onViewOnDesktop();
   },
 
+  // Checks for MS app store specific meta data, and if present opens
+  // the Windows Store to the appropriate app
+  openWindowsStoreLink: function cc_openWindowsStoreLink() {
+    let storeLink = this.getStoreLink();
+    if (storeLink) {
+      Browser.selectedBrowser.contentWindow.document.location = storeLink;
+    }
+  },
+
+  getStoreLink: function cc_getStoreLink() {
+    let metaData = Browser.selectedBrowser.contentWindow.document.getElementsByTagName("meta");
+    let msApplicationName = metaData.namedItem("msApplication-PackageFamilyName");
+    if (msApplicationName) {
+      return "ms-windows-store:PDP?PFN=" + msApplicationName.getAttribute("content");
+    }
+    return null;
+  },
+
   /*
    * Utilities
    */
 
   saveToWinLibrary: function cc_saveToWinLibrary(aType) {
     let popupState = ContextMenuUI.popupState;
     let browser = popupState.target;
 
--- a/browser/metro/base/content/appbar.js
+++ b/browser/metro/base/content/appbar.js
@@ -112,16 +112,18 @@ var Appbar = {
     }
   },
 
   onMenuButton: function(aEvent) {
       let typesArray = [];
 
       if (!BrowserUI.isStartTabVisible)
         typesArray.push("find-in-page");
+      if (ContextCommands.getStoreLink())
+        typesArray.push("ms-meta-data");
       if (ConsolePanelView.enabled)
         typesArray.push("open-error-console");
       if (!Services.metro.immersive)
         typesArray.push("open-jsshell");
 
       try {
         // If we have a valid http or https URI then show the view on desktop
         // menu item.
--- a/browser/metro/base/content/browser.xul
+++ b/browser/metro/base/content/browser.xul
@@ -783,16 +783,19 @@
 
           <!-- standard buttons -->
           <richlistitem id="context-findinpage" type="find-in-page" onclick="ContextCommands.findInPage();">
             <label value="&appbarFindInPage2.label;"/>
           </richlistitem>
           <richlistitem id="context-viewondesktop" type="view-on-desktop" onclick="ContextCommands.viewOnDesktop();">
             <label value="&appbarViewOnDesktop2.label;"/>
           </richlistitem>
+          <richlistitem id="context-msmetadata" type="ms-meta-data" onclick="ContextCommands.openWindowsStoreLink();">
+            <label value="&appbarMSMetaData2.label;"/>
+          </richlistitem>
         </richlistbox>
       </vbox>
     </box>
 
     <vbox id="select-container" class="menu-container" hidden="true">
       <vbox id="select-popup" class="select-popup">
         <richlistbox id="select-commands" flex="1"/>
       </vbox>
--- a/browser/metro/locales/en-US/chrome/browser.dtd
+++ b/browser/metro/locales/en-US/chrome/browser.dtd
@@ -12,16 +12,17 @@
 <!ENTITY closetab.label        "Close Tab">
 
 <!ENTITY autocompleteResultsHeader.label  "Your Results">
 
 <!ENTITY appbarErrorConsole.label   "Open error console">
 <!ENTITY appbarJSShell.label        "Open JavaScript shell">
 <!ENTITY appbarFindInPage2.label    "Find in page">
 <!ENTITY appbarViewOnDesktop2.label "View on desktop">
+<!ENTITY appbarMSMetaData2.label    "Get app for this site">
 
 <!ENTITY topSitesHeader.label        "Top Sites">
 <!ENTITY bookmarksHeader.label       "Bookmarks">
 <!ENTITY recentHistoryHeader.label   "Recent History">
 <!ENTITY remoteTabsHeader.label      "Tabs from Other Devices">
 
 <!-- LOCALIZATION NOTE (narrowTopSitesHeader.label,
                         narrowBookmarksHeader.label,
--- a/build/docs/conf.py
+++ b/build/docs/conf.py
@@ -14,24 +14,24 @@ here = os.path.abspath(os.path.dirname(_
 mozilla_dir = os.path.normpath(os.path.join(here, '..', '..'))
 
 import mdn_theme
 
 extensions = [
     'sphinx.ext.autodoc',
     'sphinx.ext.graphviz',
     'sphinx.ext.todo',
+    'mozbuild.sphinx',
 ]
 
 templates_path = ['_templates']
 source_suffix = '.rst'
 master_doc = 'index'
 project = u'Mozilla Build System'
 year = datetime.now().year
-copyright = u'%s, Mozilla Foundation, CC BY-SA 3.0 or any later version' % year
 
 # Grab the version from the source tree's milestone.
 with open(os.path.join(mozilla_dir, 'config', 'milestone.txt'), 'rt') as fh:
     for line in fh:
         line = line.strip()
 
         if not line or line.startswith('#'):
             continue
--- a/build/docs/index.rst
+++ b/build/docs/index.rst
@@ -13,16 +13,17 @@ Overview
 Important Concepts
 ==================
 .. toctree::
    :maxdepth: 1
 
    build-overview
    Mozconfig Files <mozconfigs>
    mozbuild-files
+   mozbuild-symbols
    Profile Guided Optimization <pgo>
    slow
    environment-variables
    build-targets
    python
    test_manifests
    mozinfo
    preprocessor
@@ -35,16 +36,28 @@ mozbuild is a Python package containing 
 Mozilla build system.
 
 .. toctree::
    :maxdepth: 1
 
    mozbuild/index
    mozbuild/dumbmake
 
+Python Packages
+===============
+
+.. toctree::
+   :maxdepth: 2
+
+   python/codegen
+   python/makeutils
+   python/mozbuild
+   python/mozpack
+   python/mozversioncontrol
+
 
 Indices and tables
 ==================
 
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
 
new file mode 100644
--- /dev/null
+++ b/build/docs/mozbuild-symbols.rst
@@ -0,0 +1,7 @@
+.. _mozbuild_symbols:
+
+========================
+mozbuild Sandbox Symbols
+========================
+
+.. mozbuildsymbols:: mozbuild.frontend.sandbox_symbols
new file mode 100644
--- /dev/null
+++ b/build/docs/python/makeutils.rst
@@ -0,0 +1,7 @@
+makeutils Module
+================
+
+.. automodule:: makeutils
+    :members:
+    :undoc-members:
+    :show-inheritance:
new file mode 100644
--- /dev/null
+++ b/build/docs/python/mozbuild.action.rst
@@ -0,0 +1,35 @@
+action Package
+==============
+
+:mod:`link_deps` Module
+-----------------------
+
+.. automodule:: mozbuild.action.link_deps
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`process_install_manifest` Module
+--------------------------------------
+
+.. automodule:: mozbuild.action.process_install_manifest
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`xpccheck` Module
+----------------------
+
+.. automodule:: mozbuild.action.xpccheck
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`xpidl-process` Module
+---------------------------
+
+.. automodule:: mozbuild.action.xpidl-process
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
new file mode 100644
--- /dev/null
+++ b/build/docs/python/mozbuild.backend.rst
@@ -0,0 +1,35 @@
+backend Package
+===============
+
+:mod:`base` Module
+------------------
+
+.. automodule:: mozbuild.backend.base
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`common` Module
+--------------------
+
+.. automodule:: mozbuild.backend.common
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`configenvironment` Module
+-------------------------------
+
+.. automodule:: mozbuild.backend.configenvironment
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`recursivemake` Module
+---------------------------
+
+.. automodule:: mozbuild.backend.recursivemake
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
new file mode 100644
--- /dev/null
+++ b/build/docs/python/mozbuild.compilation.rst
@@ -0,0 +1,11 @@
+compilation Package
+===================
+
+:mod:`warnings` Module
+----------------------
+
+.. automodule:: mozbuild.compilation.warnings
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
new file mode 100644
--- /dev/null
+++ b/build/docs/python/mozbuild.controller.rst
@@ -0,0 +1,19 @@
+controller Package
+==================
+
+:mod:`building` Module
+----------------------
+
+.. automodule:: mozbuild.controller.building
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`clobber` Module
+---------------------
+
+.. automodule:: mozbuild.controller.clobber
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
new file mode 100644
--- /dev/null
+++ b/build/docs/python/mozbuild.frontend.rst
@@ -0,0 +1,51 @@
+frontend Package
+================
+
+:mod:`data` Module
+------------------
+
+.. automodule:: mozbuild.frontend.data
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`emitter` Module
+---------------------
+
+.. automodule:: mozbuild.frontend.emitter
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`mach_commands` Module
+---------------------------
+
+.. automodule:: mozbuild.frontend.mach_commands
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`reader` Module
+--------------------
+
+.. automodule:: mozbuild.frontend.reader
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`sandbox` Module
+---------------------
+
+.. automodule:: mozbuild.frontend.sandbox
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`sandbox_symbols` Module
+-----------------------------
+
+.. automodule:: mozbuild.frontend.sandbox_symbols
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
new file mode 100644
--- /dev/null
+++ b/build/docs/python/mozbuild.rst
@@ -0,0 +1,103 @@
+mozbuild Package
+================
+
+:mod:`base` Module
+------------------
+
+.. automodule:: mozbuild.base
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`config` Module
+--------------------
+
+.. automodule:: mozbuild.config
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`html_build_viewer` Module
+-------------------------------
+
+.. automodule:: mozbuild.html_build_viewer
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`mach_commands` Module
+---------------------------
+
+.. automodule:: mozbuild.mach_commands
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`makeutil` Module
+----------------------
+
+.. automodule:: mozbuild.makeutil
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`mozconfig` Module
+-----------------------
+
+.. automodule:: mozbuild.mozconfig
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`mozinfo` Module
+---------------------
+
+.. automodule:: mozbuild.mozinfo
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`pythonutil` Module
+------------------------
+
+.. automodule:: mozbuild.pythonutil
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`sphinx` Module
+--------------------
+
+.. automodule:: mozbuild.sphinx
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`util` Module
+------------------
+
+.. automodule:: mozbuild.util
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`virtualenv` Module
+------------------------
+
+.. automodule:: mozbuild.virtualenv
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+Subpackages
+-----------
+
+.. toctree::
+
+    mozbuild.action
+    mozbuild.backend
+    mozbuild.compilation
+    mozbuild.controller
+    mozbuild.frontend
+    mozbuild.test
+
new file mode 100644
--- /dev/null
+++ b/build/docs/python/mozpack.chrome.rst
@@ -0,0 +1,19 @@
+chrome Package
+==============
+
+:mod:`flags` Module
+-------------------
+
+.. automodule:: mozpack.chrome.flags
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`manifest` Module
+----------------------
+
+.. automodule:: mozpack.chrome.manifest
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
new file mode 100644
--- /dev/null
+++ b/build/docs/python/mozpack.packager.rst
@@ -0,0 +1,35 @@
+packager Package
+================
+
+:mod:`packager` Package
+-----------------------
+
+.. automodule:: mozpack.packager
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`formats` Module
+---------------------
+
+.. automodule:: mozpack.packager.formats
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`l10n` Module
+------------------
+
+.. automodule:: mozpack.packager.l10n
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`unpack` Module
+--------------------
+
+.. automodule:: mozpack.packager.unpack
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
new file mode 100644
--- /dev/null
+++ b/build/docs/python/mozpack.rst
@@ -0,0 +1,76 @@
+mozpack Package
+===============
+
+:mod:`copier` Module
+--------------------
+
+.. automodule:: mozpack.copier
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`errors` Module
+--------------------
+
+.. automodule:: mozpack.errors
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`executables` Module
+-------------------------
+
+.. automodule:: mozpack.executables
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`files` Module
+-------------------
+
+.. automodule:: mozpack.files
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`manifests` Module
+-----------------------
+
+.. automodule:: mozpack.manifests
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`mozjar` Module
+--------------------
+
+.. automodule:: mozpack.mozjar
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`path` Module
+------------------
+
+.. automodule:: mozpack.path
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+:mod:`unify` Module
+-------------------
+
+.. automodule:: mozpack.unify
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
+Subpackages
+-----------
+
+.. toctree::
+
+    mozpack.chrome
+    mozpack.packager
+    mozpack.test
+
new file mode 100644
--- /dev/null
+++ b/build/docs/python/mozversioncontrol.rst
@@ -0,0 +1,11 @@
+mozversioncontrol Package
+=========================
+
+:mod:`repoupdate` Module
+------------------------
+
+.. automodule:: mozversioncontrol.repoupdate
+    :members:
+    :undoc-members:
+    :show-inheritance:
+
--- a/build/mach_bootstrap.py
+++ b/build/mach_bootstrap.py
@@ -47,16 +47,17 @@ SEARCH_PATHS = [
     'testing/mozbase/mozhttpd',
     'testing/mozbase/mozlog',
     'testing/mozbase/moznetwork',
     'testing/mozbase/mozprocess',
     'testing/mozbase/mozprofile',
     'testing/mozbase/mozrunner',
     'testing/mozbase/mozsystemmonitor',
     'testing/mozbase/mozinfo',
+    'testing/mozbase/moztest',
     'testing/mozbase/manifestdestiny',
     'xpcom/idl-parser',
 ]
 
 # Individual files providing mach commands.
 MACH_MODULES = [
     'addon-sdk/mach_commands.py',
     'layout/tools/reftest/mach_commands.py',
--- a/content/base/src/nsDOMDataChannel.cpp
+++ b/content/base/src/nsDOMDataChannel.cpp
@@ -16,32 +16,39 @@
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* GetDataChannelLog();
 #endif
 #undef LOG
 #define LOG(args) PR_LOG(GetDataChannelLog(), PR_LOG_DEBUG, args)
 
 
 #include "nsDOMDataChannelDeclarations.h"
+#include "nsDOMDataChannel.h"
 #include "nsIDOMFile.h"
 #include "nsIDOMDataChannel.h"
 #include "nsIDOMMessageEvent.h"
 #include "nsDOMEventTargetHelper.h"
 
 #include "nsError.h"
 #include "nsAutoPtr.h"
 #include "nsContentUtils.h"
 #include "nsCxPusher.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsNetUtil.h"
 #include "nsDOMFile.h"
 
 #include "DataChannel.h"
 
+// Since we've moved the windows.h include down here, we have to explicitly
+// undef GetBinaryType, otherwise we'll get really odd conflicts
+#ifdef GetBinaryType
+#undef GetBinaryType
+#endif
+
 using namespace mozilla;
 using namespace mozilla::dom;
 
 nsDOMDataChannel::~nsDOMDataChannel()
 {
   // Don't call us anymore!  Likely isn't an issue (or maybe just less of
   // one) once we block GC until all the (appropriate) onXxxx handlers
   // are dropped. (See WebRTC spec)
@@ -68,16 +75,23 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ADDREF_INHERITED(nsDOMDataChannel, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(nsDOMDataChannel, nsDOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMDataChannel)
   NS_INTERFACE_MAP_ENTRY(nsIDOMDataChannel)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
+nsDOMDataChannel::nsDOMDataChannel(already_AddRefed<mozilla::DataChannel> aDataChannel)
+  : mDataChannel(aDataChannel)
+  , mBinaryType(DC_BINARY_TYPE_BLOB)
+{
+  SetIsDOMBinding();
+}
+
 nsresult
 nsDOMDataChannel::Init(nsPIDOMWindow* aDOMWindow)
 {
   nsresult rv;
   nsAutoString urlParam;
 
   MOZ_ASSERT(mDataChannel);
   mDataChannel->SetListener(this, nullptr);
--- a/content/base/src/nsDOMDataChannel.h
+++ b/content/base/src/nsDOMDataChannel.h
@@ -5,32 +5,32 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsDOMDataChannel_h
 #define nsDOMDataChannel_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/DataChannelBinding.h"
 #include "mozilla/dom/TypedArray.h"
-#include "mozilla/net/DataChannel.h"
+#include "mozilla/net/DataChannelListener.h"
 #include "nsDOMEventTargetHelper.h"
 #include "nsIDOMDataChannel.h"
+#include "nsIInputStream.h"
+
+
+namespace mozilla {
+class DataChannel;
+};
 
 class nsDOMDataChannel : public nsDOMEventTargetHelper,
                          public nsIDOMDataChannel,
                          public mozilla::DataChannelListener
 {
 public:
-  nsDOMDataChannel(already_AddRefed<mozilla::DataChannel> aDataChannel)
-    : mDataChannel(aDataChannel)
-    , mBinaryType(DC_BINARY_TYPE_BLOB)
-  {
-    SetIsDOMBinding();
-  }
-
+  nsDOMDataChannel(already_AddRefed<mozilla::DataChannel> aDataChannel);
   ~nsDOMDataChannel();
 
   nsresult Init(nsPIDOMWindow* aDOMWindow);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIDOMDATACHANNEL
 
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
--- a/dom/activities/src/ActivityProxy.js
+++ b/dom/activities/src/ActivityProxy.js
@@ -84,17 +84,17 @@ ActivityProxy.prototype = {
         break;
     }
     // We can only get one FireSuccess / FireError message, so cleanup as soon as possible.
     this.cleanup();
   },
 
   cleanup: function actProxy_cleanup() {
     debug("cleanup");
-    if (!this.cleanedUp) {
+    if (cpmm && !this.cleanedUp) {
       cpmm.removeMessageListener("Activity:FireSuccess", this);
       cpmm.removeMessageListener("Activity:FireError", this);
     }
     this.cleanedUp = true;
   },
 
   classID: Components.ID("{ba9bd5cb-76a0-4ecf-a7b3-d2f7c43c5949}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIActivityProxy])
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -1060,20 +1060,20 @@ let RIL = {
   },
 
   /**
    * Set screen state.
    *
    * @param on
    *        Boolean indicating whether the screen should be on or off.
    */
-  setScreenState: function setScreenState(on) {
+  setScreenState: function setScreenState(options) {
     Buf.newParcel(REQUEST_SCREEN_STATE);
     Buf.writeInt32(1);
-    Buf.writeInt32(on ? 1 : 0);
+    Buf.writeInt32(options.on ? 1 : 0);
     Buf.sendParcel();
   },
 
   getVoiceRegistrationState: function getVoiceRegistrationState() {
     Buf.simpleRequest(REQUEST_VOICE_REGISTRATION_STATE);
   },
 
   getVoiceRadioTechnology: function getVoiceRadioTechnology() {
new file mode 100644
--- /dev/null
+++ b/dom/system/gonk/tests/marionette/test_screen_state.js
@@ -0,0 +1,47 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 10000;
+
+let Services = SpecialPowers.Services;
+
+function testScreenState(on, expected, msg) {
+  // send event to RadioInterface
+  Services.obs.notifyObservers(null, 'screen-state-changed', on);
+  // maybe rild/qemu needs some time to process the event
+  window.setTimeout(function() {
+    runEmulatorCmd('gsm report creg', function(result) {
+      is(result.pop(), 'OK', '\'gsm report creg\' successful');
+      ok(result.indexOf(expected) !== -1, msg);
+      runNextTest();
+    })}, 1000);
+}
+
+function testScreenStateDisabled() {
+  testScreenState('off', '+CREG: 1', 'screen is disabled');
+}
+
+function testScreenStateEnabled() {
+  testScreenState('on', '+CREG: 2', 'screen is enabled');
+}
+
+let tests = [
+  testScreenStateDisabled,
+  testScreenStateEnabled
+];
+
+function runNextTest() {
+  let test = tests.shift();
+  if (!test) {
+    cleanUp();
+    return;
+  }
+
+  test();
+}
+
+function cleanUp() {
+  finish();
+}
+
+runNextTest();
--- a/gfx/layers/ipc/AsyncPanZoomController.cpp
+++ b/gfx/layers/ipc/AsyncPanZoomController.cpp
@@ -597,103 +597,85 @@ nsEventStatus AsyncPanZoomController::On
 
 nsEventStatus AsyncPanZoomController::OnScaleBegin(const PinchGestureInput& aEvent) {
   APZC_LOG("%p got a scale-begin in state %d\n", this, mState);
   if (!mAllowZoom) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
   SetState(PINCHING);
-  mLastZoomFocus = aEvent.mFocusPoint;
+  mLastZoomFocus = aEvent.mFocusPoint - mFrameMetrics.mCompositionBounds.TopLeft();
 
   return nsEventStatus_eConsumeNoDefault;
 }
 
 nsEventStatus AsyncPanZoomController::OnScale(const PinchGestureInput& aEvent) {
   APZC_LOG("%p got a scale in state %d\n", this, mState);
   if (mState != PINCHING) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
   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;
   }
 
-  ScreenToScreenScale spanRatio(aEvent.mCurrentSpan / aEvent.mPreviousSpan);
+  float spanRatio = aEvent.mCurrentSpan / aEvent.mPreviousSpan;
 
   {
     ReentrantMonitorAutoEnter lock(mMonitor);
 
     CSSToScreenScale userZoom = mFrameMetrics.mZoom;
-    ScreenPoint focusPoint = aEvent.mFocusPoint;
+    ScreenPoint focusPoint = aEvent.mFocusPoint - mFrameMetrics.mCompositionBounds.TopLeft();
+    CSSPoint cssFocusPoint = focusPoint / userZoom;
 
     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;
+    CSSPoint neededDisplacement;
 
-    bool doScale = (spanRatio > ScreenToScreenScale(1.0) && userZoom < mMaxZoom) ||
-                   (spanRatio < ScreenToScreenScale(1.0) && userZoom > mMinZoom);
+    CSSToScreenScale realMinZoom = mMinZoom;
+    CSSToScreenScale realMaxZoom = mMaxZoom;
+    realMinZoom.scale = std::max(realMinZoom.scale,
+                                 mFrameMetrics.mCompositionBounds.width / mFrameMetrics.mScrollableRect.width);
+    realMinZoom.scale = std::max(realMinZoom.scale,
+                                 mFrameMetrics.mCompositionBounds.height / mFrameMetrics.mScrollableRect.height);
+    if (realMaxZoom < realMinZoom) {
+      realMaxZoom = realMinZoom;
+    }
+
+    bool doScale = (spanRatio > 1.0 && userZoom < realMaxZoom) ||
+                   (spanRatio < 1.0 && userZoom > realMinZoom);
 
     if (doScale) {
-      spanRatio.scale = clamped(spanRatio.scale,
-                                mMinZoom.scale / userZoom.scale,
-                                mMaxZoom.scale / userZoom.scale);
+      spanRatio = clamped(spanRatio,
+                          realMinZoom.scale / userZoom.scale,
+                          realMaxZoom.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);
-          break;
-        case Axis::OVERSCROLL_BOTH:
-          // If scaling this way will make us overscroll in both directions, then
-          // we must already be at the maximum zoomed out amount. In this case, we
-          // don't want to allow this scaling to go through and instead clamp it
-          // here.
-          doScale = false;
-          break;
-      }
-    }
+      // Note that the spanRatio here should never put us into OVERSCROLL_BOTH because
+      // up above we clamped it.
+      neededDisplacement.x = -mX.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.x);
+      neededDisplacement.y = -mY.ScaleWillOverscrollAmount(spanRatio, cssFocusPoint.y);
 
-    if (doScale) {
-      switch (mY.ScaleWillOverscroll(spanRatio, focusPoint.y))
-      {
-        case Axis::OVERSCROLL_NONE:
-          break;
-        case Axis::OVERSCROLL_MINUS:
-        case Axis::OVERSCROLL_PLUS:
-          neededDisplacement.y = -mY.ScaleWillOverscrollAmount(spanRatio, focusPoint.y);
-          break;
-        case Axis::OVERSCROLL_BOTH:
-          doScale = false;
-          break;
-      }
-    }
+      ScaleWithFocus(spanRatio, cssFocusPoint);
 
-    if (doScale) {
-      ScaleWithFocus(userZoom * spanRatio, focusPoint);
-
-      if (neededDisplacement != gfx::Point()) {
-        ScrollBy(CSSPoint::FromUnknownPoint(neededDisplacement));
+      if (neededDisplacement != CSSPoint()) {
+        ScrollBy(neededDisplacement);
       }
 
       ScheduleComposite();
       // We don't want to redraw on every scale, so don't use
       // RequestContentRepaint()
     }
 
     mLastZoomFocus = focusPoint;
@@ -972,29 +954,24 @@ void AsyncPanZoomController::CancelAnima
 void AsyncPanZoomController::SetCompositorParent(CompositorParent* aCompositorParent) {
   mCompositorParent = aCompositorParent;
 }
 
 void AsyncPanZoomController::ScrollBy(const CSSPoint& aOffset) {
   mFrameMetrics.mScrollOffset += aOffset;
 }
 
-void AsyncPanZoomController::ScaleWithFocus(const CSSToScreenScale& aZoom,
-                                            const ScreenPoint& aFocus) {
-  ScreenToScreenScale zoomFactor(aZoom.scale / mFrameMetrics.mZoom.scale);
-  CSSToScreenScale resolution = mFrameMetrics.mZoom;
-
-  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) {
-    zoomFactor.scale -= 1.0;
-    mFrameMetrics.mScrollOffset += aFocus * zoomFactor / resolution;
-  }
+void AsyncPanZoomController::ScaleWithFocus(float aScale,
+                                            const CSSPoint& aFocus) {
+  SetZoomAndResolution(CSSToScreenScale(mFrameMetrics.mZoom.scale * aScale));
+  // We want to adjust the scroll offset such that the CSS point represented by aFocus remains
+  // at the same position on the screen before and after the change in zoom. The below code
+  // accomplishes this; see https://bugzilla.mozilla.org/show_bug.cgi?id=923431#c6 for an
+  // in-depth explanation of how.
+  mFrameMetrics.mScrollOffset = (mFrameMetrics.mScrollOffset + aFocus) - (aFocus / aScale);
 }
 
 bool AsyncPanZoomController::EnlargeDisplayPortAlongAxis(float aSkateSizeMultiplier,
                                                          double aEstimatedPaintDuration,
                                                          float aCompositionBounds,
                                                          float aVelocity,
                                                          float aAcceleration,
                                                          float* aDisplayPortOffset,
@@ -1345,18 +1322,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) {
-    ScreenToScreenScale adjustmentFactor(float(aCompositionBounds.width) / float(oldCompositionBounds.width));
-    SetZoomAndResolution(mFrameMetrics.mZoom * adjustmentFactor);
+    float adjustmentFactor = float(aCompositionBounds.width) / float(oldCompositionBounds.width);
+    SetZoomAndResolution(CSSToScreenScale(mFrameMetrics.mZoom.scale * adjustmentFactor));
 
     // Repaint on a rotation so that our new resolution gets properly updated.
     RequestContentRepaint();
   }
 }
 
 void AsyncPanZoomController::CancelDefaultPanZoom() {
   mDisableNextTouchBatch = true;
--- a/gfx/layers/ipc/AsyncPanZoomController.h
+++ b/gfx/layers/ipc/AsyncPanZoomController.h
@@ -354,22 +354,20 @@ protected:
   /**
    * Scrolls the viewport by an X,Y offset.
    */
   void ScrollBy(const CSSPoint& aOffset);
 
   /**
    * 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.
+   * point so that the page zooms inward/outward from that point.
    */
-  void ScaleWithFocus(const mozilla::CSSToScreenScale& aScale,
-                      const ScreenPoint& aFocus);
+  void ScaleWithFocus(float aScale,
+                      const CSSPoint& aFocus);
 
   /**
    * Schedules a composite on the compositor thread. Wrapper for
    * CompositorParent::ScheduleRenderOnCompositorThread().
    */
   void ScheduleComposite();
 
   /**
--- a/gfx/layers/ipc/Axis.cpp
+++ b/gfx/layers/ipc/Axis.cpp
@@ -266,44 +266,35 @@ 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(ScreenToScreenScale aScale, float aFocus) {
-  float originAfterScale = (GetOrigin() + aFocus) * aScale.scale - aFocus;
+float Axis::ScaleWillOverscrollAmount(float aScale, float aFocus) {
+  float originAfterScale = (GetOrigin() + aFocus) - (aFocus / aScale);
 
   bool both = ScaleWillOverscrollBothSides(aScale);
-  bool minus = originAfterScale < GetPageStart() * aScale.scale;
-  bool plus = (originAfterScale + GetCompositionLength()) > GetPageEnd() * aScale.scale;
+  bool minus = originAfterScale < GetPageStart();
+  bool plus = (originAfterScale + (GetCompositionLength() / aScale)) > GetPageEnd();
 
   if ((minus && plus) || both) {
-    return OVERSCROLL_BOTH;
+    // If we ever reach here it's a bug in the client code.
+    MOZ_ASSERT(false, "In an OVERSCROLL_BOTH condition in ScaleWillOverscrollAmount");
+    return 0;
   }
   if (minus) {
-    return OVERSCROLL_MINUS;
+    return originAfterScale - GetPageStart();
   }
   if (plus) {
-    return OVERSCROLL_PLUS;
+    return originAfterScale + (GetCompositionLength() / aScale) - GetPageEnd();
   }
-  return OVERSCROLL_NONE;
-}
-
-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.scale;
-  case OVERSCROLL_PLUS: return (originAfterScale + GetCompositionLength()) -
-                               NS_lround(GetPageEnd() * aScale.scale);
-  // Don't handle OVERSCROLL_BOTH. Client code is expected to deal with it.
-  default: return 0;
-  }
+  return 0;
 }
 
 float Axis::GetVelocity() {
   return mScrollingDisabled ? 0 : mVelocity;
 }
 
 float Axis::GetAccelerationFactor() {
   return powf(gAccelerationMultiplier, std::max(0, (mAcceleration - 4) * 3));
@@ -333,25 +324,23 @@ float Axis::GetPageStart() {
   return GetRectOffset(pageRect);
 }
 
 float Axis::GetPageLength() {
   CSSRect pageRect = mAsyncPanZoomController->GetFrameMetrics().mScrollableRect;
   return GetRectLength(pageRect);
 }
 
-bool Axis::ScaleWillOverscrollBothSides(ScreenToScreenScale aScale) {
+bool Axis::ScaleWillOverscrollBothSides(float aScale) {
   const FrameMetrics& metrics = mAsyncPanZoomController->GetFrameMetrics();
 
-  CSSRect cssContentRect = metrics.mScrollableRect;
+  CSSToScreenScale scale(metrics.mZoom.scale * aScale);
+  CSSRect cssCompositionBounds = metrics.mCompositionBounds / scale;
 
-  CSSToScreenScale scale = metrics.mZoom * aScale;
-  CSSIntRect cssCompositionBounds = RoundedIn(metrics.mCompositionBounds / scale);
-
-  return GetRectLength(cssContentRect) < GetRectLength(CSSRect(cssCompositionBounds));
+  return GetRectLength(metrics.mScrollableRect) < GetRectLength(cssCompositionBounds);
 }
 
 AxisX::AxisX(AsyncPanZoomController* aAsyncPanZoomController)
   : Axis(aAsyncPanZoomController)
 {
 
 }
 
--- a/gfx/layers/ipc/Axis.h
+++ b/gfx/layers/ipc/Axis.h
@@ -137,49 +137,38 @@ public:
    * Gets the overscroll state of the axis given an additional displacement.
    * That is to say, if the given displacement is applied, this will tell you
    * whether or not it will overscroll, and in what direction.
    */
   Overscroll DisplacementWillOverscroll(float aDisplacement);
 
   /**
    * If a displacement will overscroll the axis, this returns the amount and in
-   * what direction. Similar to getExcess() but takes a displacement to apply.
+   * what direction. Similar to GetExcess() but takes a displacement to apply.
    */
   float DisplacementWillOverscrollAmount(float aDisplacement);
 
   /**
-   * 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.
+   * 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.
    */
-  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(ScreenToScreenScale aScale, float aFocus);
+  float ScaleWillOverscrollAmount(float 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(ScreenToScreenScale aScale);
+  bool ScaleWillOverscrollBothSides(float 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
@@ -48,16 +48,21 @@ public:
   TestAsyncPanZoomController(uint64_t aLayersId, MockContentController* aMcc)
     : AsyncPanZoomController(aLayersId, nullptr, aMcc)
   {}
 
   void SetFrameMetrics(const FrameMetrics& metrics) {
     ReentrantMonitorAutoEnter lock(mMonitor);
     mFrameMetrics = metrics;
   }
+
+  FrameMetrics GetFrameMetrics() {
+    ReentrantMonitorAutoEnter lock(mMonitor);
+    return mFrameMetrics;
+  }
 };
 
 class TestAPZCTreeManager : public APZCTreeManager {
 protected:
   void AssertOnCompositorThread() MOZ_OVERRIDE { /* no-op */ }
 };
 
 static
@@ -110,16 +115,122 @@ void ApzcPan(AsyncPanZoomController* apz
 
 TEST(AsyncPanZoomController, Constructor) {
   // RefCounted class can't live in the stack
   nsRefPtr<MockContentController> mcc = new MockContentController();
   nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
   apzc->SetFrameMetrics(TestFrameMetrics());
 }
 
+TEST(AsyncPanZoomController, Pinch) {
+  nsRefPtr<MockContentController> mcc = new MockContentController();
+  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
+
+  FrameMetrics fm;
+  fm.mViewport = CSSRect(0, 0, 980, 480);
+  fm.mCompositionBounds = ScreenIntRect(200, 200, 100, 200);
+  fm.mScrollableRect = CSSRect(0, 0, 980, 1000);
+  fm.mScrollOffset = CSSPoint(300, 300);
+  fm.mZoom = CSSToScreenScale(2.0);
+  apzc->SetFrameMetrics(fm);
+  // the visible area of the document in CSS pixels is x=300 y=300 w=50 h=100
+
+  EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(2);
+  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
+
+  apzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_START,
+                                           0,
+                                           ScreenPoint(250, 300),
+                                           10.0,
+                                           10.0));
+  apzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE,
+                                           0,
+                                           ScreenPoint(250, 300),
+                                           12.5,
+                                           10.0));
+  apzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_END,
+                                           0,
+                                           ScreenPoint(250, 300),
+                                           12.5,
+                                           12.5));
+
+  // the visible area of the document in CSS pixels is now x=305 y=310 w=40 h=80
+  fm = apzc->GetFrameMetrics();
+  EXPECT_EQ(fm.mZoom.scale, 2.5f);
+  EXPECT_EQ(fm.mScrollOffset.x, 305);
+  EXPECT_EQ(fm.mScrollOffset.y, 310);
+
+  // part 2 of the test, move to the top-right corner of the page and pinch and
+  // make sure we stay in the correct spot
+  fm.mZoom = CSSToScreenScale(2.0);
+  fm.mScrollOffset = CSSPoint(930, 5);
+  apzc->SetFrameMetrics(fm);
+  // the visible area of the document in CSS pixels is x=930 y=5 w=50 h=100
+
+  apzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_START,
+                                           0,
+                                           ScreenPoint(250, 300),
+                                           10.0,
+                                           10.0));
+  apzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE,
+                                           0,
+                                           ScreenPoint(250, 300),
+                                           5.0,
+                                           10.0));
+  apzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_END,
+                                           0,
+                                           ScreenPoint(250, 300),
+                                           5.0,
+                                           5.0));
+
+  // the visible area of the document in CSS pixels is now x=880 y=0 w=100 h=200
+  fm = apzc->GetFrameMetrics();
+  EXPECT_EQ(fm.mZoom.scale, 1.0f);
+  EXPECT_EQ(fm.mScrollOffset.x, 880);
+  EXPECT_EQ(fm.mScrollOffset.y, 0);
+}
+
+TEST(AsyncPanZoomController, Overzoom) {
+  nsRefPtr<MockContentController> mcc = new MockContentController();
+  nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
+
+  FrameMetrics fm;
+  fm.mViewport = CSSRect(0, 0, 100, 100);
+  fm.mCompositionBounds = ScreenIntRect(0, 0, 100, 100);
+  fm.mScrollableRect = CSSRect(0, 0, 125, 150);
+  fm.mScrollOffset = CSSPoint(10, 0);
+  fm.mZoom = CSSToScreenScale(1.0);
+  apzc->SetFrameMetrics(fm);
+  // the visible area of the document in CSS pixels is x=10 y=0 w=100 h=100
+
+  EXPECT_CALL(*mcc, SendAsyncScrollDOMEvent(_,_,_)).Times(1);
+  EXPECT_CALL(*mcc, RequestContentRepaint(_)).Times(1);
+
+  apzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_START,
+                                           0,
+                                           ScreenPoint(50, 50),
+                                           10.0,
+                                           10.0));
+  apzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_SCALE,
+                                           0,
+                                           ScreenPoint(50, 50),
+                                           5.0,
+                                           10.0));
+  apzc->HandleInputEvent(PinchGestureInput(PinchGestureInput::PINCHGESTURE_END,
+                                           0,
+                                           ScreenPoint(50, 50),
+                                           5.0,
+                                           5.0));
+
+  fm = apzc->GetFrameMetrics();
+  EXPECT_EQ(fm.mZoom.scale, 0.8f);
+  EXPECT_EQ(fm.mScrollOffset.x, 0);
+  EXPECT_EQ(fm.mScrollOffset.y, 0);
+}
+
 TEST(AsyncPanZoomController, SimpleTransform) {
   TimeStamp testStartTime = TimeStamp::Now();
   // RefCounted class can't live in the stack
   nsRefPtr<MockContentController> mcc = new MockContentController();
   nsRefPtr<TestAsyncPanZoomController> apzc = new TestAsyncPanZoomController(0, mcc);
   apzc->SetFrameMetrics(TestFrameMetrics());
 
   ScreenPoint pointOut;
--- a/layout/base/Units.h
+++ b/layout/base/Units.h
@@ -64,17 +64,16 @@ typedef gfx::ScaleFactor<LayerPixel, CSS
 typedef gfx::ScaleFactor<CSSPixel, ScreenPixel> CSSToScreenScale;
 typedef gfx::ScaleFactor<ScreenPixel, CSSPixel> ScreenToCSSScale;
 typedef gfx::ScaleFactor<LayoutDevicePixel, LayerPixel> LayoutDeviceToLayerScale;
 typedef gfx::ScaleFactor<LayerPixel, LayoutDevicePixel> LayerToLayoutDeviceScale;
 typedef gfx::ScaleFactor<LayoutDevicePixel, ScreenPixel> LayoutDeviceToScreenScale;
 typedef gfx::ScaleFactor<ScreenPixel, LayoutDevicePixel> ScreenToLayoutDeviceScale;
 typedef gfx::ScaleFactor<LayerPixel, ScreenPixel> LayerToScreenScale;
 typedef gfx::ScaleFactor<ScreenPixel, LayerPixel> ScreenToLayerScale;
-typedef gfx::ScaleFactor<ScreenPixel, ScreenPixel> ScreenToScreenScale;
 
 /*
  * The pixels that content authors use to specify sizes in.
  */
 struct CSSPixel {
 
   // Conversions from app units
 
--- a/mobile/android/base/BrowserToolbar.java
+++ b/mobile/android/base/BrowserToolbar.java
@@ -16,16 +16,20 @@ import org.mozilla.gecko.PageActionLayou
 import org.mozilla.gecko.PrefsHelper;
 import org.mozilla.gecko.util.Clipboard;
 import org.mozilla.gecko.util.StringUtils;
 import org.mozilla.gecko.util.HardwareUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 import org.mozilla.gecko.util.UiAsyncTask;
 import org.mozilla.gecko.util.GeckoEventListener;
 import org.mozilla.gecko.util.StringUtils;
+import org.mozilla.gecko.widget.GeckoImageButton;
+import org.mozilla.gecko.widget.GeckoImageView;
+import org.mozilla.gecko.widget.GeckoRelativeLayout;
+import org.mozilla.gecko.widget.GeckoTextView;
 
 import org.json.JSONObject;
 
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.Color;
 import android.graphics.Rect;
--- a/mobile/android/base/BrowserToolbarBackground.java
+++ b/mobile/android/base/BrowserToolbarBackground.java
@@ -1,14 +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/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.widget.GeckoLinearLayout;
+
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Path;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.StateListDrawable;
--- a/mobile/android/base/CustomEditText.java
+++ b/mobile/android/base/CustomEditText.java
@@ -1,15 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.widget.GeckoEditText;
+
 import android.content.Context;
 import android.util.AttributeSet;
 import android.view.KeyEvent;
 import android.view.View;
 
 public class CustomEditText extends GeckoEditText {
     private OnKeyPreImeListener mOnKeyPreImeListener;
     private OnSelectionChangedListener mOnSelectionChangedListener;
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -293,31 +293,26 @@ WEBRTC_JAVA_FILES = \
   $(addprefix $(DEPTH)/media/webrtc/trunk/webrtc/modules/audio_device/android/org/webrtc/voiceengine/, $(WEBRTC_AUDIO_CAPTURE_JAVA_FILES)) \
   $(NULL)
 endif
 
 ifdef MOZ_ANDROID_ANR_REPORTER
 DEFINES += -DMOZ_ANDROID_ANR_REPORTER=1
 endif
 
-FENNEC_PP_JAVA_VIEW_FILES = \
-  GeckoButton.java \
-  GeckoImageButton.java \
-  GeckoImageView.java \
-  GeckoEditText.java \
-  GeckoFrameLayout.java \
-  GeckoLinearLayout.java \
-  GeckoRelativeLayout.java \
-  GeckoTextSwitcher.java \
-  GeckoTextView.java \
-  $(NULL)
-
-FENNEC_PP_JAVA_FILES = \
+FENNEC_PP_JAVA_FILES := \
   App.java \
   AppConstants.java \
+  widget/GeckoEditText.java \
+  widget/GeckoImageButton.java \
+  widget/GeckoImageView.java \
+  widget/GeckoLinearLayout.java \
+  widget/GeckoRelativeLayout.java \
+  widget/GeckoTextSwitcher.java \
+  widget/GeckoTextView.java \
   SysInfo.java \
   WebApp.java \
   WebApps.java \
   $(NULL)
 
 FENNEC_PP_XML_FILES = \
   res/xml/preferences.xml \
   res/xml/preferences_customize.xml \
@@ -390,17 +385,16 @@ ifdef MOZ_LINKER_EXTRACT
 DEFINES += -DMOZ_LINKER_EXTRACT=1
 endif
 
 GARBAGE += \
   AndroidManifest.xml  \
   classes.dex  \
   $(MOZGLUE_PP_JAVA_FILES) \
   $(FENNEC_PP_JAVA_FILES) \
-  $(FENNEC_PP_JAVA_VIEW_FILES) \
   $(SYNC_PP_JAVA_FILES) \
   gecko.ap_  \
   res/values/strings.xml \
   R.java \
   $(FENNEC_PP_XML_FILES) \
   $(SYNC_PP_RES_XML) \
   package-name.txt \
   fennec_ids.txt \
@@ -1240,17 +1234,16 @@ websockets_JAVAC_FLAGS := -Xlint:all,-se
 JAVA_JAR_TARGETS += gecko-browser
 gecko-browser_DEST := jars/gecko-browser.jar
 gecko-browser_JAVAFILES := \
   $(FENNEC_JAVA_FILES) \
   $(SYNC_JAVA_FILES) \
   $(NULL)
 gecko-browser_PP_JAVAFILES := \
   $(FENNEC_PP_JAVA_FILES) \
-  $(FENNEC_PP_JAVA_VIEW_FILES) \
   $(SYNC_PP_JAVA_FILES) \
   R.java \
   $(NULL)
 gecko-browser_EXTRA_JARS := \
   jars/gecko-mozglue.jar \
   jars/gecko-util.jar \
   jars/sync-thirdparty.jar \
   jars/websockets.jar \
@@ -1266,17 +1259,16 @@ gecko-mozglue_PP_JAVAFILES := \
   $(MOZGLUE_PP_JAVA_FILES) \
   $(NULL)
 gecko-mozglue_JAVAC_FLAGS := -Xlint:all
 
 JAVA_JAR_TARGETS += gecko-util
 gecko-util_DEST := jars/gecko-util.jar
 gecko-util_JAVAFILES := \
   $(UTIL_JAVA_FILES) \
-  $(UTIL_PP_JAVA_FILES) \
   $(NULL)
 gecko-util_EXTRA_JARS := \
   jars/gecko-mozglue.jar \
   $(NULL)
 gecko-util_JAVAC_FLAGS := -Xlint:all,-deprecation
 
 JAVA_JAR_TARGETS += sync-thirdparty
 sync-thirdparty_DEST := jars/sync-thirdparty.jar
@@ -1359,31 +1351,27 @@ android-tgts = \
   $(MOZGLUE_PP_JAVA_FILES) \
   $(FENNEC_PP_JAVA_FILES) \
   $(SYNC_PP_JAVA_FILES) \
   package-name.txt \
   $(NULL)
 
 android-preqs = \
   Makefile.in \
+  widget/GeckoView.java.frag \
   $(call mkdir_deps,$(sort $(dir $(MOZGLUE_PP_JAVA_FILES)))) \
   $(call mkdir_deps,$(sort $(dir $(FENNEC_PP_JAVA_FILES)))) \
   $(call mkdir_deps,$(sort $(dir $(SYNC_PP_JAVA_FILES)))) \
   $(SERVICES_MANIFEST_FRAGMENTS) \
   $(NULL)
 
 $(android-tgts): % : %.in $(android-preqs)
 	$(PYTHON) $(topsrcdir)/config/Preprocessor.py \
              $(AUTOMATION_PPARGS) $(DEFINES) $(ACDEFINES) $< > $@
 
-# Replace @VIEWTYPE@ with different View names.
-$(FENNEC_PP_JAVA_VIEW_FILES): GeckoView.java.frag
-	@rm -f $@
-	cat $< | sed s,@VIEWTYPE@,$(patsubst Gecko%.java,%,$@),g >> $@ ; \
-
 res/drawable-mdpi/icon.png: $(ICON_PATH)
 	$(NSINSTALL) -D res/drawable-mdpi
 	cp $(ICON_PATH) $@
 
 res/drawable-hdpi/icon.png: $(ICON_PATH_HDPI)
 	$(NSINSTALL) -D res/drawable-hdpi
 	cp $(ICON_PATH_HDPI) $@
 
--- a/mobile/android/base/ShapedButton.java
+++ b/mobile/android/base/ShapedButton.java
@@ -1,14 +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/. */
 
 package org.mozilla.gecko;
 
+import org.mozilla.gecko.widget.GeckoImageButton;
+
 import android.content.Context;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
 import android.graphics.Canvas;
 import android.graphics.Path;
 import android.graphics.PorterDuff.Mode;
 import android.graphics.drawable.ColorDrawable;
 import android.graphics.drawable.Drawable;
--- a/mobile/android/base/TabCounter.java
+++ b/mobile/android/base/TabCounter.java
@@ -1,16 +1,17 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * 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/. */
 
 package org.mozilla.gecko;
 
 import org.mozilla.gecko.animation.Rotate3DAnimation;
+import org.mozilla.gecko.widget.GeckoTextSwitcher;
 
 import android.content.Context;
 import android.os.Build;
 import android.view.accessibility.AccessibilityNodeInfo;
 import android.view.animation.Animation;
 import android.view.animation.AnimationSet;
 import android.view.animation.AlphaAnimation;
 import android.view.LayoutInflater;
--- a/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout-large-v11/browser_toolbar.xml
@@ -123,29 +123,29 @@
                      style="@style/UrlBar.ImageButton"
                      android:layout_width="@dimen/browser_toolbar_lock_width"
                      android:scaleType="fitCenter"
                      android:layout_marginLeft="-4dip"
                      android:src="@drawable/site_security_level"
                      android:contentDescription="@string/site_security"
                      android:visibility="gone"/>
 
-        <org.mozilla.gecko.GeckoTextView android:id="@+id/url_bar_title"
-                                         style="@style/UrlBar.Button"
-                                         android:layout_width="fill_parent"
-                                         android:layout_height="fill_parent"
-                                         android:layout_weight="1.0"
-                                         android:singleLine="true"
-                                         android:paddingRight="8dp"
-                                         android:textColor="@color/url_bar_title"
-                                         android:textColorHint="@color/url_bar_title_hint"
-                                         android:gravity="center_vertical|left"
-                                         android:hint="@string/url_bar_default_text"
-                                         android:layout_gravity="center_vertical"
-                                         gecko:autoUpdateTheme="false"/>
+        <org.mozilla.gecko.widget.GeckoTextView android:id="@+id/url_bar_title"
+                                                style="@style/UrlBar.Button"
+                                                android:layout_width="fill_parent"
+                                                android:layout_height="fill_parent"
+                                                android:layout_weight="1.0"
+                                                android:singleLine="true"
+                                                android:paddingRight="8dp"
+                                                android:textColor="@color/url_bar_title"
+                                                android:textColorHint="@color/url_bar_title_hint"
+                                                android:gravity="center_vertical|left"
+                                                android:hint="@string/url_bar_default_text"
+                                                android:layout_gravity="center_vertical"
+                                                gecko:autoUpdateTheme="false"/>
 
         <org.mozilla.gecko.PageActionLayout android:id="@+id/page_action_layout"
                                             android:layout_width="wrap_content"
                                             android:layout_height="match_parent"
                                             android:layout_marginRight="@dimen/browser_toolbar_button_padding"
                                             android:visibility="gone"
                                             android:orientation="horizontal"/>
 
@@ -160,31 +160,31 @@
     <LinearLayout android:id="@+id/menu_items"
                   android:layout_width="wrap_content"
                   android:layout_height="fill_parent"
                   android:layout_marginLeft="3dp"
                   android:orientation="horizontal"
                   android:layout_toLeftOf="@id/menu"
                   android:layout_alignWithParentIfMissing="true"/>
 
-    <org.mozilla.gecko.GeckoImageButton android:id="@+id/menu"
-                                        style="@style/UrlBar.ImageButton"
-                                        android:layout_width="56dip"
-                                        android:layout_alignParentRight="true"
-                                        android:contentDescription="@string/menu"
-                                        android:background="@drawable/action_bar_button"
-                                        android:visibility="gone"/>
+    <org.mozilla.gecko.widget.GeckoImageButton android:id="@+id/menu"
+                                               style="@style/UrlBar.ImageButton"
+                                               android:layout_width="56dip"
+                                               android:layout_alignParentRight="true"
+                                               android:contentDescription="@string/menu"
+                                               android:background="@drawable/action_bar_button"
+                                               android:visibility="gone"/>
 
-    <org.mozilla.gecko.GeckoImageView android:id="@+id/menu_icon"
-                                      style="@style/UrlBar.ImageButton"
-                                      android:layout_alignLeft="@id/menu"
-                                      android:layout_alignRight="@id/menu"
-                                      android:gravity="center_vertical"
-                                      android:src="@drawable/menu_level"
-                                      android:visibility="gone"/>
+    <org.mozilla.gecko.widget.GeckoImageView android:id="@+id/menu_icon"
+                                             style="@style/UrlBar.ImageButton"
+                                             android:layout_alignLeft="@id/menu"
+                                             android:layout_alignRight="@id/menu"
+                                             android:gravity="center_vertical"
+                                             android:src="@drawable/menu_level"
+                                             android:visibility="gone"/>
 
     <ImageView android:id="@+id/shadow"
                android:layout_width="fill_parent"
                android:layout_height="2dp"
                android:layout_alignParentBottom="true"
                android:background="@color/url_bar_shadow"
                android:contentDescription="@null"/>
 
--- a/mobile/android/base/resources/layout/browser_toolbar.xml
+++ b/mobile/android/base/resources/layout/browser_toolbar.xml
@@ -51,23 +51,23 @@
     <org.mozilla.gecko.ShapedButton android:id="@+id/menu"
                                     style="@style/UrlBar.ImageButton"
                                     android:layout_width="48dip"
                                     android:layout_alignParentRight="true"
                                     android:contentDescription="@string/menu"
                                     android:background="@drawable/shaped_button"
                                     android:visibility="gone"/>
 
-    <org.mozilla.gecko.GeckoImageView android:id="@+id/menu_icon"
-                                      style="@style/UrlBar.ImageButton"
-                                      android:layout_alignLeft="@id/menu"
-                                      android:layout_alignRight="@id/menu"
-                                      android:gravity="center_vertical"
-                                      android:src="@drawable/menu_level"
-                                      android:visibility="gone"/>
+    <org.mozilla.gecko.widget.GeckoImageView android:id="@+id/menu_icon"
+                                             style="@style/UrlBar.ImageButton"
+                                             android:layout_alignLeft="@id/menu"
+                                             android:layout_alignRight="@id/menu"
+                                             android:gravity="center_vertical"
+                                             android:src="@drawable/menu_level"
+                                             android:visibility="gone"/>
 
     <org.mozilla.gecko.ShapedButton android:id="@+id/tabs"
                                     style="@style/UrlBar.ImageButton"
                                     android:layout_width="72dip"
                                     android:layout_toLeftOf="@id/menu"
                                     android:layout_alignWithParentIfMissing="true"
                                     gecko:curveTowards="right"
                                     android:background="@drawable/shaped_button"
@@ -148,29 +148,29 @@
                      style="@style/UrlBar.ImageButton"
                      android:layout_width="@dimen/browser_toolbar_lock_width"
                      android:scaleType="fitCenter"
                      android:layout_marginLeft="-4dip"
                      android:src="@drawable/site_security_level"
                      android:contentDescription="@string/site_security"
                      android:visibility="gone"/>
 
-        <org.mozilla.gecko.GeckoTextView android:id="@+id/url_bar_title"
-                                         style="@style/UrlBar.Button"
-                                         android:layout_width="fill_parent"
-                                         android:layout_height="fill_parent"
-                                         android:layout_weight="1.0"
-                                         android:singleLine="true"
-                                         android:paddingRight="8dp"
-                                         android:textColor="@color/url_bar_title"
-                                         android:textColorHint="@color/url_bar_title_hint"
-                                         android:gravity="center_vertical|left"
-                                         android:hint="@string/url_bar_default_text"
-                                         android:layout_gravity="center_vertical"
-                                         gecko:autoUpdateTheme="false"/>
+        <org.mozilla.gecko.widget.GeckoTextView android:id="@+id/url_bar_title"
+                                                style="@style/UrlBar.Button"
+                                                android:layout_width="fill_parent"
+                                                android:layout_height="fill_parent"
+                                                android:layout_weight="1.0"
+                                                android:singleLine="true"
+                                                android:paddingRight="8dp"
+                                                android:textColor="@color/url_bar_title"
+                                                android:textColorHint="@color/url_bar_title_hint"
+                                                android:gravity="center_vertical|left"
+                                                android:hint="@string/url_bar_default_text"
+                                                android:layout_gravity="center_vertical"
+                                                gecko:autoUpdateTheme="false"/>
 
         <org.mozilla.gecko.PageActionLayout android:id="@+id/page_action_layout"
                                             android:layout_width="wrap_content"
                                             android:layout_height="match_parent"
                                             android:layout_marginRight="12dp"
                                             android:visibility="gone"
                                             android:orientation="horizontal"/>
 
--- a/mobile/android/base/resources/layout/tabs_counter.xml
+++ b/mobile/android/base/resources/layout/tabs_counter.xml
@@ -1,17 +1,17 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
 
-<org.mozilla.gecko.GeckoTextView xmlns:android="http://schemas.android.com/apk/res/android"
-                                 android:layout_width="24dip"
-                                 android:layout_height="24dip"
-                                 android:layout_margin="12dip"
-                                 android:paddingTop="2dip"
-                                 android:paddingLeft="4dip"
-                                 android:background="@drawable/tabs_count_foreground"
-                                 android:textAppearance="@style/TextAppearance.Micro"
-                                 android:textColor="#FF43484E"
-                                 android:textStyle="bold"
-                                 android:duplicateParentState="true"
-                                 android:gravity="center"/>
+<org.mozilla.gecko.widget.GeckoTextView xmlns:android="http://schemas.android.com/apk/res/android"
+                                        android:layout_width="24dip"
+                                        android:layout_height="24dip"
+                                        android:layout_margin="12dip"
+                                        android:paddingTop="2dip"
+                                        android:paddingLeft="4dip"
+                                        android:background="@drawable/tabs_count_foreground"
+                                        android:textAppearance="@style/TextAppearance.Micro"
+                                        android:textColor="#FF43484E"
+                                        android:textStyle="bold"
+                                        android:duplicateParentState="true"
+                                        android:gravity="center"/>
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoEditText.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE EditText
+#include GeckoView.java.frag
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoImageButton.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE ImageButton
+#include GeckoView.java.frag
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoImageView.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE ImageView
+#include GeckoView.java.frag
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoLinearLayout.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE LinearLayout
+#include GeckoView.java.frag
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoRelativeLayout.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE RelativeLayout
+#include GeckoView.java.frag
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoTextSwitcher.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE TextSwitcher
+#include GeckoView.java.frag
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/widget/GeckoTextView.java.in
@@ -0,0 +1,3 @@
+#filter substitution
+#define VIEWTYPE TextView
+#include GeckoView.java.frag
rename from mobile/android/base/GeckoView.java.frag
rename to mobile/android/base/widget/GeckoView.java.frag
--- a/mobile/android/base/GeckoView.java.frag
+++ b/mobile/android/base/widget/GeckoView.java.frag
@@ -1,13 +1,17 @@
 /* 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/. */
 
-package org.mozilla.gecko;
+package org.mozilla.gecko.widget;
+
+import org.mozilla.gecko.GeckoActivity;
+import org.mozilla.gecko.LightweightTheme;
+import org.mozilla.gecko.R;
 
 import android.content.Context;
 import android.content.res.TypedArray;
 import android.util.AttributeSet;
 import android.widget.@VIEWTYPE@;
 
 public class Gecko@VIEWTYPE@ extends @VIEWTYPE@
                              implements LightweightTheme.OnChangeListener { 
--- a/netwerk/sctp/datachannel/DataChannel.h
+++ b/netwerk/sctp/datachannel/DataChannel.h
@@ -19,16 +19,17 @@
 #include "nsString.h"
 #include "nsThreadUtils.h"
 #include "nsTArray.h"
 #include "nsDeque.h"
 #include "nsIInputStream.h"
 #include "nsITimer.h"
 #include "mozilla/Mutex.h"
 #include "DataChannelProtocol.h"
+#include "DataChannelListener.h"
 #ifdef SCTP_DTLS_SUPPORTED
 #include "mtransport/sigslot.h"
 #include "mtransport/transportflow.h"
 #include "mtransport/transportlayer.h"
 #include "mtransport/transportlayerdtls.h"
 #include "mtransport/transportlayerprsock.h"
 #endif
 
@@ -86,39 +87,16 @@ public:
   }
 
   uint16_t mStream;
   uint32_t mPpid;
   size_t   mLength;
   char     *mData;
 };
 
-// Implemented by consumers of a Channel to receive messages.
-// Can't nest it in DataChannelConnection because C++ doesn't allow forward
-// refs to embedded classes
-class DataChannelListener {
-public:
-  virtual ~DataChannelListener() {}
-
-  // Called when a DOMString message is received.
-  virtual nsresult OnMessageAvailable(nsISupports *aContext,
-                                      const nsACString& message) = 0;
-
-  // Called when a binary message is received.
-  virtual nsresult OnBinaryMessageAvailable(nsISupports *aContext,
-                                            const nsACString& message) = 0;
-
-  // Called when the channel is connected
-  virtual nsresult OnChannelConnected(nsISupports *aContext) = 0;
-
-  // Called when the channel is closed
-  virtual nsresult OnChannelClosed(nsISupports *aContext) = 0;
-};
-
-
 // One per PeerConnection
 class DataChannelConnection: public nsITimerCallback
 #ifdef SCTP_DTLS_SUPPORTED
                              , public sigslot::has_slots<>
 #endif
 {
 public:
   NS_DECL_THREADSAFE_ISUPPORTS
new file mode 100644
--- /dev/null
+++ b/netwerk/sctp/datachannel/DataChannelListener.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 8; 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/. */
+
+#ifndef NETWERK_SCTP_DATACHANNEL_DATACHANNELLISTENER_H_
+#define NETWERK_SCTP_DATACHANNEL_DATACHANNELLISTENER_H_
+
+#include "nsISupports.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+// Implemented by consumers of a Channel to receive messages.
+// Can't nest it in DataChannelConnection because C++ doesn't allow forward
+// refs to embedded classes
+class DataChannelListener {
+public:
+  virtual ~DataChannelListener() {}
+
+  // Called when a DOMString message is received.
+  virtual nsresult OnMessageAvailable(nsISupports *aContext,
+                                      const nsACString& message) = 0;
+
+  // Called when a binary message is received.
+  virtual nsresult OnBinaryMessageAvailable(nsISupports *aContext,
+                                            const nsACString& message) = 0;
+
+  // Called when the channel is connected
+  virtual nsresult OnChannelConnected(nsISupports *aContext) = 0;
+
+  // Called when the channel is closed
+  virtual nsresult OnChannelClosed(nsISupports *aContext) = 0;
+};
+
+}
+
+#endif
--- a/netwerk/sctp/datachannel/moz.build
+++ b/netwerk/sctp/datachannel/moz.build
@@ -5,17 +5,18 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 XPIDL_MODULE = 'necko_datachan'
 
 MODULE = 'necko'
 
 EXPORTS.mozilla.net += [
     'DataChannel.h',
-    'DataChannelProtocol.h',
+    'DataChannelListener.h',
+    'DataChannelProtocol.h'
 ]
 
 CPP_SOURCES += [
     'DataChannel.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
--- a/python/mozbuild/mozbuild/frontend/mach_commands.py
+++ b/python/mozbuild/mozbuild/frontend/mach_commands.py
@@ -1,172 +1,74 @@
 # 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 print_function, unicode_literals
 
-import textwrap
-
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command
 )
 
-from mozbuild.frontend.sandbox_symbols import (
-    FUNCTIONS,
-    SPECIAL_VARIABLES,
-    VARIABLES,
-    doc_to_paragraphs,
-)
-
-
-def get_doc(doc):
-    """Split documentation into summary line and everything else."""
-    paragraphs = doc_to_paragraphs(doc)
-
-    summary = paragraphs[0]
-    extra = paragraphs[1:]
-
-    return summary, extra
-
-def print_extra(extra):
-    """Prints the 'everything else' part of documentation intelligently."""
-    for para in extra:
-        for line in textwrap.wrap(para):
-            print(line)
-
-        print('')
-
-    if not len(extra):
-        print('')
+from mozbuild.base import MachCommandBase
 
 
 @CommandProvider
-class MozbuildFileCommands(object):
+class MozbuildFileCommands(MachCommandBase):
     @Command('mozbuild-reference', category='build-dev',
         description='View reference documentation on mozbuild files.')
     @CommandArgument('symbol', default=None, nargs='*',
         help='Symbol to view help on. If not specified, all will be shown.')
     @CommandArgument('--name-only', '-n', default=False, action='store_true',
         help='Print symbol names only.')
     def reference(self, symbol, name_only=False):
+        # mozbuild.sphinx imports some Sphinx modules, so we need to be sure
+        # the optional Sphinx package is installed.
+        self._activate_virtualenv()
+        self.virtualenv_manager.install_pip_package('Sphinx==1.1.3')
+
+        from mozbuild.sphinx import (
+            format_module,
+            function_reference,
+            special_reference,
+            variable_reference,
+        )
+
+        import mozbuild.frontend.sandbox_symbols as m
+
         if name_only:
-            for s in sorted(VARIABLES.keys()):
+            for s in sorted(m.VARIABLES.keys()):
                 print(s)
 
-            for s in sorted(FUNCTIONS.keys()):
+            for s in sorted(m.FUNCTIONS.keys()):
                 print(s)
 
-            for s in sorted(SPECIAL_VARIABLES.keys()):
+            for s in sorted(m.SPECIAL_VARIABLES.keys()):
                 print(s)
 
             return 0
 
         if len(symbol):
             for s in symbol:
-                if s in VARIABLES:
-                    self.variable_reference(s)
+                if s in m.VARIABLES:
+                    for line in variable_reference(s, *m.VARIABLES[s]):
+                        print(line)
                     continue
-                elif s in FUNCTIONS:
-                    self.function_reference(s)
+                elif s in m.FUNCTIONS:
+                    for line in function_reference(s, *m.FUNCTIONS[s]):
+                        print(line)
                     continue
-                elif s in SPECIAL_VARIABLES:
-                    self.special_reference(s)
+                elif s in m.SPECIAL_VARIABLES:
+                    for line in special_reference(s, *m.SPECIAL_VARIABLES[s]):
+                        print(line)
                     continue
 
                 print('Could not find symbol: %s' % s)
                 return 1
 
             return 0
 
-        print('=========')
-        print('VARIABLES')
-        print('=========')
-        print('')
-        print('This section lists all the variables that may be set ')
-        print('in moz.build files.')
-        print('')
-
-        for v in sorted(VARIABLES.keys()):
-            self.variable_reference(v)
-
-        print('=========')
-        print('FUNCTIONS')
-        print('=========')
-        print('')
-        print('This section lists all the functions that may be called ')
-        print('in moz.build files.')
-        print('')
-
-        for f in sorted(FUNCTIONS.keys()):
-            self.function_reference(f)
-
-        print('=================')
-        print('SPECIAL VARIABLES')
-        print('=================')
-        print('')
-
-        for v in sorted(SPECIAL_VARIABLES.keys()):
-            self.special_reference(v)
+        for line in format_module(m):
+            print(line)
 
         return 0
-
-    def variable_reference(self, v):
-        st_typ, in_type, default, doc, tier = VARIABLES[v]
-
-        print(v)
-        print('=' * len(v))
-        print('')
-
-        summary, extra = get_doc(doc)
-
-        print(summary)
-        print('')
-        print('Storage Type: %s' % st_typ.__name__)
-        print('Input Type: %s' % in_type.__name__)
-        print('Default Value: %s' % default)
-        print('')
-        print_extra(extra)
-
-    def function_reference(self, f):
-        attr, args, doc = FUNCTIONS[f]
-
-        print(f)
-        print('=' * len(f))
-        print('')
-
-        summary, extra = get_doc(doc)
-
-        print(summary)
-        print('')
-
-        arg_types = []
-
-        for t in args:
-            if isinstance(t, list):
-                inner_types = [t2.__name__ for t2 in t]
-                arg_types.append(' | ' .join(inner_types))
-                continue
-
-            arg_types.append(t.__name__)
-
-        arg_s = '(%s)' % ', '.join(arg_types)
-
-        print('Arguments: %s' % arg_s)
-        print('')
-        print_extra(extra)
-
-    def special_reference(self, v):
-        typ, doc = SPECIAL_VARIABLES[v]
-
-        print(v)
-        print('=' * len(v))
-        print('')
-
-        summary, extra = get_doc(doc)
-
-        print(summary)
-        print('')
-        print('Type: %s' % typ.__name__)
-        print('')
-        print_extra(extra)
--- a/python/mozbuild/mozbuild/frontend/sandbox_symbols.py
+++ b/python/mozbuild/mozbuild/frontend/sandbox_symbols.py
@@ -19,44 +19,16 @@ from __future__ import unicode_literals
 
 from collections import OrderedDict
 from mozbuild.util import (
     HierarchicalStringList,
     StrictOrderingOnAppendList,
 )
 
 
-def doc_to_paragraphs(doc):
-    """Take a documentation string and converts it to paragraphs.
-
-    This normalizes the inline strings in VARIABLES and elsewhere in this file.
-
-    It returns a list of paragraphs. It is up to the caller to insert newlines
-    or to wrap long lines (e.g. by using textwrap.wrap()).
-    """
-    lines = [line.strip() for line in doc.split('\n')]
-
-    paragraphs = []
-    current = []
-    for line in lines:
-        if not len(line):
-            if len(current):
-                paragraphs.append(' '.join(current))
-                current = []
-
-            continue
-
-        current.append(line)
-
-    if len(current):
-        paragraphs.append(' '.join(current))
-
-    return paragraphs
-
-
 # This defines the set of mutable global variables.
 #
 # Each variable is a tuple of:
 #
 #   (storage_type, input_types, default_value, docs, tier)
 #
 # Tier says for which specific tier the variable has an effect.
 # Valid tiers are:
@@ -87,91 +59,94 @@ VARIABLES = {
         """C code source files.
 
         This variable contains a list of C source files to compile.
         """, 'compile'),
 
     'DEFINES': (OrderedDict, dict, OrderedDict(),
         """Dictionary of compiler defines to declare.
 
-        These are passed in to the compiler as -Dkey='value' for string values,
-        -Dkey=value for numeric values, or -Dkey if the value is True. Note
-        that for string values, the outer-level of single-quotes will be
-        consumed by the shell. If you want to have a string-literal in the
-        program, the value needs to have double-quotes.
+        These are passed in to the compiler as ``-Dkey='value'`` for string
+        values, ``-Dkey=value`` for numeric values, or ``-Dkey`` if the
+        value is True. Note that for string values, the outer-level of
+        single-quotes will be consumed by the shell. If you want to have
+        a string-literal in the program, the value needs to have
+        double-quotes.
 
-        Example:
-        DEFINES['NS_NO_XPCOM'] = True
-        DEFINES['MOZ_EXTENSIONS_DB_SCHEMA'] = 15
-        DEFINES['DLL_SUFFIX'] = '".so"'
+        Example::
 
-        This will result in the compiler flags -DNS_NO_XPCOM,
-        -DMOZ_EXTENSIONS_DB_SCHEMA=15, and -DDLL_SUFFIX='".so"',
-        respectively. These could also be combined into a single
-        update:
+           DEFINES['NS_NO_XPCOM'] = True
+           DEFINES['MOZ_EXTENSIONS_DB_SCHEMA'] = 15
+           DEFINES['DLL_SUFFIX'] = '".so"'
 
-        DEFINES.update({
-            'NS_NO_XPCOM': True,
-            'MOZ_EXTENSIONS_DB_SCHEMA': 15,
-            'DLL_SUFFIX': '".so"',
-        })
+        This will result in the compiler flags ``-DNS_NO_XPCOM``,
+        ``-DMOZ_EXTENSIONS_DB_SCHEMA=15``, and ``-DDLL_SUFFIX='".so"'``,
+        respectively. These could also be combined into a single
+        update::
+
+           DEFINES.update({
+               'NS_NO_XPCOM': True,
+               'MOZ_EXTENSIONS_DB_SCHEMA': 15,
+               'DLL_SUFFIX': '".so"',
+           })
         """, None),
 
     'DIRS': (list, list, [],
         """Child directories to descend into looking for build frontend files.
 
-        This works similarly to the DIRS variable in make files. Each str value
-        in the list is the name of a child directory. When this file is done
-        parsing, the build reader will descend into each listed directory and
-        read the frontend file there. If there is no frontend file, an error
+        This works similarly to the ``DIRS`` variable in make files. Each str
+        value in the list is the name of a child directory. When this file is
+        done parsing, the build reader will descend into each listed directory
+        and read the frontend file there. If there is no frontend file, an error
         is raised.
 
         Values are relative paths. They can be multiple directory levels
-        above or below. Use ".." for parent directories and "/" for path
+        above or below. Use ``..`` for parent directories and ``/`` for path
         delimiters.
         """, None),
 
     'EXPORT_LIBRARY': (bool, bool, False,
         """Install the library to the static libraries folder.
         """, None),
 
     'EXTRA_COMPONENTS': (StrictOrderingOnAppendList, list, [],
         """Additional component files to distribute.
 
-       This variable contains a list of files to copy into $(FINAL_TARGET)/components/.
+       This variable contains a list of files to copy into
+       ``$(FINAL_TARGET)/components/``.
         """, 'libs'),
 
     'EXTRA_JS_MODULES': (StrictOrderingOnAppendList, list, [],
         """Additional JavaScript files to distribute.
 
         This variable contains a list of files to copy into
-        $(FINAL_TARGET)/$(JS_MODULES_PATH). JS_MODULES_PATH defaults to
-        "modules" if left undefined.
+        ``$(FINAL_TARGET)/$(JS_MODULES_PATH)``. ``JS_MODULES_PATH`` defaults
+        to ``modules`` if left undefined.
         """, 'libs'),
 
     'EXTRA_PP_JS_MODULES': (StrictOrderingOnAppendList, list, [],
         """Additional JavaScript files to distribute.
 
         This variable contains a list of files to copy into
-        $(FINAL_TARGET)/$(JS_MODULES_PATH), after preprocessing.
-        JS_MODULES_PATH defaults to "modules" if left undefined.
+        ``$(FINAL_TARGET)/$(JS_MODULES_PATH)``, after preprocessing.
+        ``JS_MODULES_PATH`` defaults to ``modules`` if left undefined.
         """, 'libs'),
 
     'EXTRA_PP_COMPONENTS': (StrictOrderingOnAppendList, list, [],
         """Javascript XPCOM files.
 
        This variable contains a list of files to preprocess.  Generated
-       files will be installed in the /components directory of the distribution.
+       files will be installed in the ``/components`` directory of the distribution.
         """, 'libs'),
 
     'CPP_UNIT_TESTS': (StrictOrderingOnAppendList, list, [],
         """C++ source files for unit tests.
 
         This is a list of C++ unit test sources. Entries must be files that
-        exist. These generally have .cpp extensions.
+        exist. These generally have ``.cpp`` extensions.
         """, 'binaries'),
 
     'FAIL_ON_WARNINGS': (bool, bool, False,
         """Whether to treat warnings as errors.
         """, None),
 
     'FORCE_SHARED_LIB': (bool, bool, False,
         """Whether the library in this directory is a shared library.
@@ -194,17 +169,18 @@ VARIABLES = {
         This variable contains a list of objective-C++ GTest unit test sources
         to compile.
         """, 'compile'),
 
     'GTEST_CPP_SOURCES': (list, list, [],
         """C++ source files for GTest unit tests.
 
         This is a list of C++ GTest unit test sources. Entries must be files
-        that exist. These generally have .cpp, .cc, or .cxx extensions.
+        that exist. These generally have ``.cpp``, ``.cc``, or ``.cxx``
+        extensions.
         """, 'compile'),
 
     'HOST_CPPSRCS': (StrictOrderingOnAppendList, list, [],
         """C++ source files to compile with the host compiler.
 
         This variable contains a list of C++ source files to compile.
         """, 'compile'),
 
@@ -214,57 +190,59 @@ VARIABLES = {
         This variable contains a list of C source files to compile.
         """, 'compile'),
 
     'IS_COMPONENT': (bool, bool, False,
         """Whether the library contains a binary XPCOM component manifest.
         """, None),
 
     'PARALLEL_DIRS': (list, list, [],
-        """A parallel version of DIRS.
+        """A parallel version of ``DIRS``.
 
         Ideally this variable does not exist. It is provided so a transition
         from recursive makefiles can be made. Once the build system has been
         converted to not use Makefile's for the build frontend, this will
         likely go away.
         """, None),
 
     'HOST_LIBRARY_NAME': (unicode, unicode, "",
         """Name of target library generated when cross compiling.
         """, 'binaries'),
 
     'JS_MODULES_PATH': (unicode, unicode, "",
-        """Sub-directory of $(FINAL_TARGET) to install EXTRA_JS_MODULES.
+        """Sub-directory of ``$(FINAL_TARGET)`` to install
+        ``EXTRA_JS_MODULES``.
 
-        EXTRA_JS_MODULES files are copied to
-        $(FINAL_TARGET)/$(JS_MODULES_PATH). This variable does not
+        ``EXTRA_JS_MODULES`` files are copied to
+        ``$(FINAL_TARGET)/$(JS_MODULES_PATH)``. This variable does not
         need to be defined if the desired destination directory is
-        $(FINAL_TARGET)/modules.
+        ``$(FINAL_TARGET)/modules``.
         """, None),
 
     'LIBRARY_NAME': (unicode, unicode, "",
         """The name of the library generated for a directory.
 
-        Example:
-        In example/components/moz.build,
-        LIBRARY_NAME = 'xpcomsample'
-        would generate example/components/libxpcomsample.so on Linux, or
-        example/components/xpcomsample.lib on Windows.
+        In ``example/components/moz.build``,::
+
+           LIBRARY_NAME = 'xpcomsample'
+
+        would generate ``example/components/libxpcomsample.so`` on Linux, or
+        ``example/components/xpcomsample.lib`` on Windows.
         """, 'binaries'),
 
     'LIBS': (StrictOrderingOnAppendList, list, [],
         """Linker libraries and flags.
 
         A list of libraries and flags to include when linking.
         """, None),
 
     'LIBXUL_LIBRARY': (bool, bool, False,
         """Whether the library in this directory is linked into libxul.
 
-        Implies MOZILLA_INTERNAL_API and FORCE_STATIC_LIB.
+        Implies ``MOZILLA_INTERNAL_API`` and ``FORCE_STATIC_LIB``.
         """, None),
 
     'LOCAL_INCLUDES': (StrictOrderingOnAppendList, list, [],
         """Additional directories to be searched for include files by the compiler.
         """, None),
 
     'MSVC_ENABLE_PGO': (bool, bool, False,
         """Whether profile-guided optimization is enabled in this directory.
@@ -274,29 +252,32 @@ VARIABLES = {
         """System link libraries.
 
         This variable contains a list of system libaries to link against.
         """, None),
 
     'SDK_LIBRARY': (StrictOrderingOnAppendList, list, [],
         """Elements of the distributed SDK.
 
-        Files on this list will be copied into SDK_LIB_DIR ($DIST/sdk/lib).
+        Files on this list will be copied into ``SDK_LIB_DIR``
+        (``$DIST/sdk/lib``).
         """, None),
 
     'SHARED_LIBRARY_LIBS': (StrictOrderingOnAppendList, list, [],
         """Libraries linked into a shared library.
 
-        A list of static library paths which should be linked into the current shared library.
+        A list of static library paths which should be linked into the
+        current shared library.
         """, None),
 
     'SIMPLE_PROGRAMS': (StrictOrderingOnAppendList, list, [],
         """Generate a list of binaries from source.
 
-        A list of sources, one per program, to compile & link with libs into standalone programs.
+        A list of sources, one per program, to compile & link with libs
+        into standalone programs.
         """, 'binaries'),
 
     'SSRCS': (StrictOrderingOnAppendList, list, [],
         """Assembly source files.
 
         This variable contains a list of files to invoke the assembler on.
         """, 'compile'),
 
@@ -329,142 +310,143 @@ VARIABLES = {
         major tiers (keys in this dict) that correspond roughly to applications
         or libraries being built. e.g. base, nspr, js, platform, app. Within
         each tier are phases like export, libs, and tools. The recursive make
         backend iterates over each phase in the first tier then proceeds to the
         next tier until all tiers are exhausted.
 
         Tiers are a way of working around deficiencies in recursive make. These
         will probably disappear once we no longer rely on recursive make for
-        the build backend. They will likely be replaced by DIRS.
+        the build backend. They will likely be replaced by ``DIRS``.
 
         This variable is typically not populated directly. Instead, it is
         populated by calling add_tier_dir().
         """, None),
 
     'EXTERNAL_MAKE_DIRS': (list, list, [],
         """Directories that build with make but don't use moz.build files.
 
-        This is like DIRS except it implies that |make| is used to build the
+        This is like ``DIRS`` except it implies that ``make`` is used to build the
         directory and that the directory does not define itself with moz.build
         files.
         """, None),
 
     'PARALLEL_EXTERNAL_MAKE_DIRS': (list, list, [],
-        """Parallel version of EXTERNAL_MAKE_DIRS.
+        """Parallel version of ``EXTERNAL_MAKE_DIRS``.
         """, None),
 
     'CONFIGURE_SUBST_FILES': (StrictOrderingOnAppendList, list, [],
         """Output files that will be generated using configure-like substitution.
 
-        This is a substitute for AC_OUTPUT in autoconf. For each path in this
+        This is a substitute for ``AC_OUTPUT`` in autoconf. For each path in this
         list, we will search for a file in the srcdir having the name
-        {path}.in. The contents of this file will be read and variable patterns
-        like @foo@ will be substituted with the values of the AC_SUBST
-        variables declared during configure.
+        ``{path}.in``. The contents of this file will be read and variable
+        patterns like ``@foo@`` will be substituted with the values of the
+        ``AC_SUBST`` variables declared during configure.
         """, None),
 
     'MODULE': (unicode, unicode, "",
         """Module name.
 
         Historically, this variable was used to describe where to install header
-        files, but that feature is now handled by EXPORTS_NAMESPACES. MODULE
-        will likely be removed in the future.
+        files, but that feature is now handled by ``EXPORTS_NAMESPACES``.
+        ``MODULE`` will likely be removed in the future.
         """, None),
 
     'EXPORTS': (HierarchicalStringList, list, HierarchicalStringList(),
         """List of files to be exported, and in which subdirectories.
 
-        EXPORTS is generally used to list the include files to be exported to
-        dist/include, but it can be used for other files as well. This variable
+        ``EXPORTS`` is generally used to list the include files to be exported to
+        ``dist/include``, but it can be used for other files as well. This variable
         behaves as a list when appending filenames for export in the top-level
         directory. Files can also be appended to a field to indicate which
-        subdirectory they should be exported to. For example, to export 'foo.h'
-        to the top-level directory, and 'bar.h' to mozilla/dom/, append to
-        EXPORTS like so:
+        subdirectory they should be exported to. For example, to export
+        ``foo.h`` to the top-level directory, and ``bar.h`` to ``mozilla/dom/``,
+        append to ``EXPORTS`` like so::
 
-        EXPORTS += ['foo.h']
-        EXPORTS.mozilla.dom += ['bar.h']
+           EXPORTS += ['foo.h']
+           EXPORTS.mozilla.dom += ['bar.h']
         """, None),
 
     'PROGRAM' : (unicode, unicode, "",
         """Compiled executable name.
 
-        If the configuration token 'BIN_SUFFIX' is set, its value will be
-        automatically appended to PROGRAM. If PROGRAM already ends with
-        BIN_SUFFIX, PROGRAM will remain unchanged.
+        If the configuration token ``BIN_SUFFIX`` is set, its value will be
+        automatically appended to ``PROGRAM``. If ``PROGRAM`` already ends with
+        ``BIN_SUFFIX``, ``PROGRAM`` will remain unchanged.
         """, 'binaries'),
 
     'CPP_SOURCES': (list, list, [],
         """C++ source file list.
 
         This is a list of C++ files to be compiled. Entries must be files that
-        exist. These generally have .cpp, .cc, or .cxx extensions.
+        exist. These generally have ``.cpp``, ``.cc``, or ``.cxx`` extensions.
         """, 'compile'),
 
     'NO_DIST_INSTALL': (bool, bool, False,
         """Disable installing certain files into the distribution directory.
 
         If present, some files defined by other variables won't be
         distributed/shipped with the produced build.
         """, None),
 
     # IDL Generation.
     'XPIDL_SOURCES': (StrictOrderingOnAppendList, list, [],
         """XPCOM Interface Definition Files (xpidl).
 
         This is a list of files that define XPCOM interface definitions.
-        Entries must be files that exist. Entries are almost certainly .idl
+        Entries must be files that exist. Entries are almost certainly ``.idl``
         files.
         """, 'libs'),
 
     'XPIDL_MODULE': (unicode, unicode, "",
         """XPCOM Interface Definition Module Name.
 
-        This is the name of the .xpt file that is created by linking
-        XPIDL_SOURCES together. If unspecified, it defaults to be the same as
-        MODULE.
+        This is the name of the ``.xpt`` file that is created by linking
+        ``XPIDL_SOURCES`` together. If unspecified, it defaults to be the same
+        as ``MODULE``.
         """, None),
 
     'IPDL_SOURCES': (StrictOrderingOnAppendList, list, [],
         """IPDL source files.
 
-        These are .ipdl files that will be parsed and converted to .cpp files.
+        These are ``.ipdl`` files that will be parsed and converted to
+        ``.cpp`` files.
         """, 'export'),
 
     'WEBIDL_FILES': (StrictOrderingOnAppendList, list, [],
         """WebIDL source files.
 
-        These will be parsed and converted to .cpp and .h files.
+        These will be parsed and converted to ``.cpp`` and ``.h`` files.
         """, 'export'),
 
     'GENERATED_EVENTS_WEBIDL_FILES': (StrictOrderingOnAppendList, list, [],
         """WebIDL source files for generated events.
 
-        These will be parsed and converted to .cpp and .h files.
+        These will be parsed and converted to ``.cpp`` and ``.h`` files.
         """, 'export'),
 
     'TEST_WEBIDL_FILES': (StrictOrderingOnAppendList, list, [],
          """Test WebIDL source files.
 
-         These will be parsed and converted to .cpp and .h files if tests are
-         enabled.
+         These will be parsed and converted to ``.cpp`` and ``.h`` files
+         if tests are enabled.
          """, 'export'),
 
     'GENERATED_WEBIDL_FILES': (StrictOrderingOnAppendList, list, [],
          """Generated WebIDL source files.
 
          These will be generated from some other files.
          """, 'export'),
 
     'PREPROCESSED_TEST_WEBIDL_FILES': (StrictOrderingOnAppendList, list, [],
          """Preprocessed test WebIDL source files.
 
          These will be preprocessed, then parsed and converted to .cpp
-         and .h files if tests are enabled.
+         and ``.h`` files if tests are enabled.
          """, 'export'),
 
     'PREPROCESSED_WEBIDL_FILES': (StrictOrderingOnAppendList, list, [],
          """Preprocessed WebIDL source files.
 
          These will be preprocessed before being parsed and converted.
          """, 'export'),
 
@@ -509,35 +491,38 @@ VARIABLES = {
 #  (method attribute, (argument types), docs)
 #
 # The first element is an attribute on Sandbox that should be a function type.
 #
 FUNCTIONS = {
     'include': ('_include', (str,),
         """Include another mozbuild file in the context of this one.
 
-        This is similar to a #include in C languages. The filename passed to
+        This is similar to a ``#include`` in C languages. The filename passed to
         the function will be read and its contents will be evaluated within the
         context of the calling file.
 
         If a relative path is given, it is evaluated as relative to the file
         currently being processed. If there is a chain of multiple include(),
         the relative path computation is from the most recent/active file.
 
-        If an absolute path is given, it is evaluated from TOPSRCDIR. In other
-        words, include('/foo') references the path TOPSRCDIR + '/foo'.
+        If an absolute path is given, it is evaluated from ``TOPSRCDIR``. In
+        other words, ``include('/foo')`` references the path
+        ``TOPSRCDIR + '/foo'``.
 
         Example usage
-        -------------
+        ^^^^^^^^^^^^^
+
+        Include ``sibling.build`` from the current directory.::
 
-        # Include "sibling.build" from the current directory.
-        include('sibling.build')
+           include('sibling.build')
 
-        # Include "foo.build" from a path within the top source directory.
-        include('/elsewhere/foo.build')
+        Include ``foo.build`` from a path within the top source directory::
+
+           include('/elsewhere/foo.build')
         """),
 
     'add_tier_dir': ('_add_tier_directory', (str, [str, list], bool, bool),
         """Register a directory for tier traversal.
 
         This is the preferred way to populate the TIERS variable.
 
         Tiers are how the build system is organized. The build process is
@@ -549,30 +534,34 @@ FUNCTIONS = {
         This function is typically only called by the main moz.build file or a
         file directly included by the main moz.build file. An error will be
         raised if it is called when it shouldn't be.
 
         An error will also occur if you attempt to add the same directory to
         the same tier multiple times.
 
         Example usage
-        -------------
+        ^^^^^^^^^^^^^
+
+        Register a single directory with the 'platform' tier::
 
-        # Register a single directory with the 'platform' tier.
-        add_tier_dir('platform', 'xul')
+           add_tier_dir('platform', 'xul')
+
+        Register multiple directories with the 'app' tier.::
 
-        # Register multiple directories with the 'app' tier.
-        add_tier_dir('app', ['components', 'base'])
+           add_tier_dir('app', ['components', 'base'])
+
+        Register a directory as having static content (no dependencies)::
 
-        # Register a directory as having static content (no dependencies).
-        add_tier_dir('base', 'foo', static=True)
+           add_tier_dir('base', 'foo', static=True)
 
-        # Register a directory as having external content (same as static
-        # content, but traversed with export, libs, and tools subtiers.
-        add_tier_dir('base', 'bar', external=True)
+        Register a directory as having external content (same as static
+        content, but traversed with export, libs, and tools subtiers::
+
+           add_tier_dir('base', 'bar', external=True)
         """),
 
     'warning': ('_warning', (str,),
         """Issue a warning.
 
         Warnings are string messages that are printed during execution.
 
         Warnings are ignored during execution.
@@ -601,48 +590,48 @@ SPECIAL_VARIABLES = {
         The top object directory is the parent directory which will contain
         the output of the build. This is commonly referred to as "the object
         directory."
         """),
 
     'RELATIVEDIR': (str,
         """Constant defining the relative path of this file.
 
-        The relative path is from TOPSRCDIR. This is defined as relative to the
-        main file being executed, regardless of whether additional files have
-        been included using include().
+        The relative path is from ``TOPSRCDIR``. This is defined as relative
+        to the main file being executed, regardless of whether additional
+        files have been included using ``include()``.
         """),
 
     'SRCDIR': (str,
         """Constant defining the source directory of this file.
 
-        This is the path inside TOPSRCDIR where this file is located. It is the
-        same as TOPSRCDIR + RELATIVEDIR.
+        This is the path inside ``TOPSRCDIR`` where this file is located. It
+        is the same as ``TOPSRCDIR + RELATIVEDIR``.
         """),
 
     'OBJDIR': (str,
         """The path to the object directory for this file.
 
-        Is is the same as TOPOBJDIR + RELATIVEDIR.
+        Is is the same as ``TOPOBJDIR + RELATIVEDIR``.
         """),
 
     'CONFIG': (dict,
         """Dictionary containing the current configuration variables.
 
         All the variables defined by the configuration system are available
-        through this object. e.g. ENABLE_TESTS, CFLAGS, etc.
+        through this object. e.g. ``ENABLE_TESTS``, ``CFLAGS``, etc.
 
         Values in this container are read-only. Attempts at changing values
         will result in a run-time error.
 
         Access to an unknown variable will return None.
         """),
 
     '__builtins__': (dict,
         """Exposes Python built-in types.
 
         The set of exposed Python built-ins is currently:
 
-            True
-            False
-            None
+        - True
+        - False
+        - None
         """),
 }
--- a/python/mozbuild/mozbuild/mach_commands.py
+++ b/python/mozbuild/mozbuild/mach_commands.py
@@ -918,25 +918,50 @@ class MachDebug(object):
 @CommandProvider
 class Documentation(MachCommandBase):
     """Helps manage in-tree documentation."""
 
     @Command('build-docs', category='build-dev',
         description='Generate documentation for the tree.')
     @CommandArgument('--format', default='html',
         help='Documentation format to write.')
+    @CommandArgument('--api-docs', action='store_true',
+        help='If specified, we will generate api docs templates for in-tree '
+            'Python. This will likely create and/or modify files in '
+            'build/docs. It is meant to be run by build maintainers when new '
+            'Python modules are added to the tree.')
     @CommandArgument('outdir', default='<DEFAULT>', nargs='?',
         help='Where to write output.')
-    def build_docs(self, format=None, outdir=None):
+    def build_docs(self, format=None, api_docs=False, outdir=None):
         self._activate_virtualenv()
 
         self.virtualenv_manager.install_pip_package('mdn-sphinx-theme==0.3')
 
+        if api_docs:
+            import sphinx.apidoc
+            outdir = os.path.join(self.topsrcdir, 'build', 'docs', 'python')
+
+            base_args = [sys.argv[0], '--no-toc', '-o', outdir]
+
+            packages = [
+                ('python/codegen',),
+                ('python/mozbuild/mozbuild', 'test'),
+                ('python/mozbuild/mozpack', 'test'),
+                ('python/mozversioncontrol/mozversioncontrol',),
+            ]
+
+            for package in packages:
+                extra_args = [os.path.join(self.topsrcdir, package[0])]
+                extra_args.extend(package[1:])
+
+                sphinx.apidoc.main(base_args + extra_args)
+
+            return 0
+
         import sphinx
-
         if outdir == '<DEFAULT>':
             outdir = os.path.join(self.topobjdir, 'docs', format)
 
         args = [
             sys.argv[0],
             '-b', format,
             os.path.join(self.topsrcdir, 'build', 'docs'),
             outdir,
new file mode 100644
--- /dev/null
+++ b/python/mozbuild/mozbuild/sphinx.py
@@ -0,0 +1,155 @@
+# 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 absolute_import
+
+import importlib
+
+from sphinx.util.compat import Directive
+from sphinx.util.docstrings import prepare_docstring
+
+
+def function_reference(f, attr, args, doc):
+    lines = []
+
+    lines.extend([
+        f,
+        '-' * len(f),
+        '',
+    ])
+
+    docstring = prepare_docstring(doc)
+
+    lines.extend([
+        docstring[0],
+        '',
+    ])
+
+    arg_types = []
+
+    for t in args:
+        if isinstance(t, list):
+            inner_types = [t2.__name__ for t2 in t]
+            arg_types.append(' | ' .join(inner_types))
+            continue
+
+        arg_types.append(t.__name__)
+
+    arg_s = '(%s)' % ', '.join(arg_types)
+
+    lines.extend([
+        ':Arguments: %s' % arg_s,
+        '',
+    ])
+
+    lines.extend(docstring[1:])
+    lines.append('')
+
+    return lines
+
+
+def variable_reference(v, st_type, in_type, default, doc, tier):
+    lines = [
+        v,
+        '-' * len(v),
+        '',
+    ]
+
+    docstring = prepare_docstring(doc)
+
+    lines.extend([
+        docstring[0],
+        '',
+    ])
+
+    lines.extend([
+        ':Storage Type: ``%s``' % st_type.__name__,
+        ':Input Type: ``%s``' % in_type.__name__,
+        ':Default Value: %s' % default,
+        '',
+    ])
+
+    lines.extend(docstring[1:])
+    lines.append('')
+
+    return lines
+
+
+def special_reference(v, typ, doc):
+    lines = [
+        v,
+        '-' * len(v),
+        '',
+    ]
+
+    docstring = prepare_docstring(doc)
+
+    lines.extend([
+        docstring[0],
+        '',
+        ':Type: ``%s``' % typ.__name__,
+        '',
+    ])
+
+    lines.extend(docstring[1:])
+    lines.append('')
+
+    return lines
+
+
+def format_module(m):
+    lines = []
+    lines.extend([
+        'Variables',
+        '=========',
+        '',
+    ])
+
+    for v in sorted(m.VARIABLES):
+        lines.extend(variable_reference(v, *m.VARIABLES[v]))
+
+    lines.extend([
+        'Functions',
+        '=========',
+        '',
+    ])
+
+    for func in sorted(m.FUNCTIONS):
+        lines.extend(function_reference(func, *m.FUNCTIONS[func]))
+
+    lines.extend([
+        'Special Variables',
+        '=================',
+        '',
+    ])
+
+    for v in sorted(m.SPECIAL_VARIABLES):
+        lines.extend(special_reference(v, *m.SPECIAL_VARIABLES[v]))
+
+    return lines
+
+
+class MozbuildSymbols(Directive):
+    """Directive to insert mozbuild sandbox symbol information."""
+
+    required_arguments = 1
+
+    def run(self):
+        module = importlib.import_module(self.arguments[0])
+        fname = module.__file__
+        if fname.endswith('.pyc'):
+            fname = fname[0:-1]
+
+        self.state.document.settings.record_dependencies.add(fname)
+
+        # We simply format out the documentation as rst then feed it back
+        # into the parser for conversion. We don't even emit ourselves, so
+        # there's no record of us.
+        self.state_machine.insert_input(format_module(module), fname)
+
+        return []
+
+
+def setup(app):
+    app.add_directive('mozbuildsymbols', MozbuildSymbols)
--- a/toolkit/components/jsdownloads/src/DownloadCore.jsm
+++ b/toolkit/components/jsdownloads/src/DownloadCore.jsm
@@ -1666,17 +1666,26 @@ DownloadCopySaver.prototype = {
         // up the chain of objects for the download.
         yield deferSaveComplete.promise;
       } catch (ex) {
         // Ensure we always remove the placeholder for the final target file on
         // failure, independently of which code path failed.  In some cases, the
         // background file saver may have already removed the file.
         try {
           yield OS.File.remove(targetPath);
-        } catch (e2 if e2 instanceof OS.File.Error && e2.becauseNoSuchFile) { }
+        } catch (e2) {
+          // If we failed during the operation, we report the error but use the
+          // original one as the failure reason of the download.  Note that on
+          // Windows we may get an access denied error instead of a no such file
+          // error if the file existed before, and was recently deleted.
+          if (!(e2 instanceof OS.File.Error &&
+                (e2.becauseNoSuchFile || e2.becauseAccessDenied))) {
+            Cu.reportError(e2);
+          }
+        }
         throw ex;
       }
     }.bind(this));
   },
 
   /**
    * Implements "DownloadSaver.cancel".
    */
@@ -1942,16 +1951,33 @@ DownloadLegacySaver.prototype = {
           // empty file is created as expected.
           try {
             // This atomic operation is more efficient than an existence check.
             let file = yield OS.File.open(this.download.target.path,
                                           { create: true });
             yield file.close();
           } catch (ex if ex instanceof OS.File.Error && ex.becauseExists) { }
         }
+      } catch (ex) {
+        // Ensure we always remove the final target file on failure,
+        // independently of which code path failed.  In some cases, the
+        // component executing the download may have already removed the file.
+        try {
+          yield OS.File.remove(this.download.target.path);
+        } catch (e2) {
+          // If we failed during the operation, we report the error but use the
+          // original one as the failure reason of the download.  Note that on
+          // Windows we may get an access denied error instead of a no such file
+          // error if the file existed before, and was recently deleted.
+          if (!(e2 instanceof OS.File.Error &&
+                (e2.becauseNoSuchFile || e2.becauseAccessDenied))) {
+            Cu.reportError(e2);
+          }
+        }
+        throw ex;
       } finally {
         // We don't need the reference to the request anymore.
         this.request = null;
         // Allow the download to restart through a DownloadCopySaver.
         this.firstExecutionFinished = true;
       }
     }.bind(this));
   },
--- a/toolkit/components/jsdownloads/test/unit/common_test_Download.js
+++ b/toolkit/components/jsdownloads/test/unit/common_test_Download.js
@@ -535,16 +535,38 @@ add_task(function test_cancel_midway()
     } catch (ex if ex instanceof Downloads.Error) {
       do_check_false(ex.becauseSourceFailed);
       do_check_false(ex.becauseTargetFailed);
     }
   }
 });
 
 /**
+ * Cancels a download while keeping partially downloaded data, and verifies that
+ * both the target file and the ".part" file are deleted.
+ */
+add_task(function test_cancel_midway_tryToKeepPartialData()
+{
+  let download = yield promiseStartDownload_tryToKeepPartialData();
+
+  do_check_true(yield OS.File.exists(download.target.path));
+  do_check_true(yield OS.File.exists(download.target.partFilePath));
+
+  yield download.cancel();
+  yield download.removePartialData();
+
+  do_check_true(download.stopped);
+  do_check_true(download.canceled);
+  do_check_true(download.error === null);
+
+  do_check_false(yield OS.File.exists(download.target.path));
+  do_check_false(yield OS.File.exists(download.target.partFilePath));
+});
+
+/**
  * Cancels a download right after starting it.
  */
 add_task(function test_cancel_immediately()
 {
   mustInterruptResponses();
 
   let download = yield promiseStartDownload(httpUrl("interruptible.txt"));
 
@@ -1027,16 +1049,18 @@ add_task(function test_error_source()
     }
 
     // Check the properties now that the download stopped.
     do_check_true(download.stopped);
     do_check_false(download.canceled);
     do_check_true(download.error !== null);
     do_check_true(download.error.becauseSourceFailed);
     do_check_false(download.error.becauseTargetFailed);
+
+    do_check_false(yield OS.File.exists(download.target.path));
   } finally {
     serverSocket.close();
   }
 });
 
 /**
  * Ensures download error details are reported on local writing failures.
  */
--- a/toolkit/components/jsdownloads/test/unit/head.js
+++ b/toolkit/components/jsdownloads/test/unit/head.js
@@ -810,17 +810,19 @@ add_task(function test_common_initialize
                              "Synchronous promptForSaveToFile not implemented.",
                              Cr.NS_ERROR_NOT_AVAILABLE);
         },
         promptForSaveToFileAsync: function (aLauncher, aWindowContext,
                                             aDefaultFileName,
                                             aSuggestedFileExtension,
                                             aForcePrompt)
         {
+          // The dialog should create the empty placeholder file.
           let file = getTempFile(TEST_TARGET_FILE_NAME);
+          file.create(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
           aLauncher.saveDestinationAvailable(file);
         },
       }.QueryInterface(aIid);
     }
   };
 
   let contractID = "@mozilla.org/helperapplauncherdialog;1";
   let cid = registrar.contractIDToCID(contractID);
--- a/toolkit/components/osfile/modules/osfile_async_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_async_front.jsm
@@ -766,16 +766,21 @@ File.writeAtomic = function writeAtomic(
   //   threads;
   // - we take care of any |byteOffset|.
   return Scheduler.post("writeAtomic",
     [Type.path.toMsg(path),
      Type.void_t.in_ptr.toMsg(buffer),
      options], [options, buffer]);
 };
 
+File.removeDir = function(path, options = {}) {
+  return Scheduler.post("removeDir",
+    [Type.path.toMsg(path), options], path);
+};
+
 /**
  * Information on a file, as returned by OS.File.stat or
  * OS.File.prototype.stat
  *
  * @constructor
  */
 File.Info = function Info(value) {
   // Note that we can't just do this[k] = value[k] because our
--- a/toolkit/components/osfile/modules/osfile_async_worker.js
+++ b/toolkit/components/osfile/modules/osfile_async_worker.js
@@ -281,16 +281,19 @@ if (this.Components) {
      if (options.tmpPath) {
        options.tmpPath = Type.path.fromMsg(options.tmpPath);
      }
      return File.writeAtomic(Type.path.fromMsg(path),
                              Type.voidptr_t.fromMsg(buffer),
                              options
                             );
    },
+   removeDir: function(path, options) {
+     return File.removeDir(Type.path.fromMsg(path), options);
+   },
    new_DirectoryIterator: function new_DirectoryIterator(path, options) {
      let directoryPath = Type.path.fromMsg(path);
      let iterator = new File.DirectoryIterator(directoryPath, options);
      return OpenedDirectoryIterators.add(iterator, {
        // Adding path information to keep track of opened directory
        // iterators to report leaks when debugging.
        path: directoryPath
      });
--- a/toolkit/components/osfile/modules/osfile_shared_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_shared_front.jsm
@@ -389,10 +389,44 @@ AbstractFile.writeAtomic =
   } finally {
     tmpFile.close();
   }
 
   OS.File.move(options.tmpPath, path, {noCopy: true});
   return bytesWritten;
 };
 
+/**
+  * Remove an existing directory and its contents.
+  *
+  * @param {string} path The name of the directory.
+  * @param {*=} options Additional options.
+  *   - {bool} ignoreAbsent If |false|, throw an error if the directory doesn't
+  *     exist. |true| by default.
+  *   - {boolean} ignorePermissions If |true|, remove the file even when lacking write
+  *     permission.
+  *
+  * @throws {OS.File.Error} In case of I/O error, in particular if |path| is
+            not a directory.
+  */
+AbstractFile.removeDir = function(path, options = {}) {
+  let iterator = new OS.File.DirectoryIterator(path);
+  if (!iterator.exists() && options.ignoreAbsent) {
+    return;
+  }
+
+  try {
+    for (let entry in iterator) {
+      if (entry.isDir) {
+        OS.File.removeDir(entry.path, options);
+      } else {
+        OS.File.remove(entry.path, options);
+      }
+    }
+  } finally {
+    iterator.close();
+  }
+
+  OS.File.removeEmptyDir(path);
+};
+
    exports.OS.Shared.AbstractFile = AbstractFile;
 })(this);
--- a/toolkit/components/osfile/modules/osfile_unix_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_unix_front.jsm
@@ -812,16 +812,17 @@
        } else {
          throw_on_negative("stat", UnixFile.stat(path, gStatDataPtr));
        }
        return new File.Info(gStatData);
      };
 
      File.read = exports.OS.Shared.AbstractFile.read;
      File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
+     File.removeDir = exports.OS.Shared.AbstractFile.removeDir;
 
      /**
       * Get the current directory by getCurrentDirectory.
       */
      File.getCurrentDirectory = function getCurrentDirectory() {
        let path = UnixFile.get_current_dir_name?UnixFile.get_current_dir_name():
          UnixFile.getwd_auto(null);
        throw_on_null("getCurrentDirectory",path);
--- a/toolkit/components/osfile/modules/osfile_win_front.jsm
+++ b/toolkit/components/osfile/modules/osfile_win_front.jsm
@@ -751,16 +751,17 @@
        winAccess: 0,
        // Directories can only be opened with backup semantics(!)
        winFlags: OS.Constants.Win.FILE_FLAG_BACKUP_SEMANTICS,
        winDisposition: OS.Constants.Win.OPEN_EXISTING
      };
 
      File.read = exports.OS.Shared.AbstractFile.read;
      File.writeAtomic = exports.OS.Shared.AbstractFile.writeAtomic;
+     File.removeDir = exports.OS.Shared.AbstractFile.removeDir;
 
      /**
       * Get the current directory by getCurrentDirectory.
       */
      File.getCurrentDirectory = function getCurrentDirectory() {
            // This function is more complicated than one could hope.
            //
            // This is due to two facts:
new file mode 100644
--- /dev/null
+++ b/toolkit/components/osfile/tests/xpcshell/test_removeDir.js
@@ -0,0 +1,88 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+"use strict";
+
+Components.utils.import("resource://gre/modules/osfile.jsm");
+Components.utils.import("resource://gre/modules/Task.jsm");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+do_register_cleanup(function() {
+  Services.prefs.setBoolPref("toolkit.osfile.log", false);
+});
+
+function run_test() {
+  Services.prefs.setBoolPref("toolkit.osfile.log", true);
+
+  run_next_test();
+}
+
+add_task(function() {
+  // Set up profile. We create the directory in the profile, because the profile
+  // is removed after every test run.
+  do_get_profile();
+
+  let file = OS.Path.join(OS.Constants.Path.profileDir, "file");
+  let dir = OS.Path.join(OS.Constants.Path.profileDir, "directory");
+  let file1 = OS.Path.join(dir, "file1");
+  let file2 = OS.Path.join(dir, "file2");
+  let subDir = OS.Path.join(dir, "subdir");
+  let fileInSubDir = OS.Path.join(subDir, "file");
+
+  // Sanity checking for the test
+  do_check_false((yield OS.File.exists(dir)));
+
+  // Remove non-existent directory
+  let exception = null;
+  try {
+    yield OS.File.removeDir(dir);
+  } catch (ex) {
+    exception = ex;
+  }
+
+  do_check_true(!!exception);
+  do_check_true(exception instanceof OS.File.Error);
+
+  // Remove non-existent directory with ignoreAbsent
+  yield OS.File.removeDir(dir, {ignoreAbsent: true});
+
+  // Remove file
+  yield OS.File.writeAtomic(file, "content", { tmpPath: file + ".tmp" });
+  exception = null;
+  try {
+    yield OS.File.removeDir(file);
+  } catch (ex) {
+    exception = ex;
+  }
+
+  do_check_true(!!exception);
+  do_check_true(exception instanceof OS.File.Error);
+
+  // Remove empty directory
+  yield OS.File.makeDir(dir);
+  yield OS.File.removeDir(dir);
+  do_check_false((yield OS.File.exists(dir)));
+
+  // Remove directory that contains one file
+  yield OS.File.makeDir(dir);
+  yield OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" });
+  //yield OS.File.open(file1, {create:true});
+  yield OS.File.removeDir(dir)
+  do_check_false((yield OS.File.exists(dir)));
+
+  // Remove directory that contains multiple files
+  yield OS.File.makeDir(dir);
+  yield OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" });
+  yield OS.File.writeAtomic(file2, "content", { tmpPath: file2 + ".tmp" });
+  yield OS.File.removeDir(dir)
+  do_check_false((yield OS.File.exists(dir)));
+
+  // Remove directory that contains a file and a directory
+  yield OS.File.makeDir(dir);
+  yield OS.File.writeAtomic(file1, "content", { tmpPath: file1 + ".tmp" });
+  yield OS.File.makeDir(subDir);
+  yield OS.File.writeAtomic(fileInSubDir, "content", { tmpPath: fileInSubDir + ".tmp" });
+  yield OS.File.removeDir(dir);
+  do_check_false((yield OS.File.exists(dir)));
+});
--- a/toolkit/components/osfile/tests/xpcshell/xpcshell.ini
+++ b/toolkit/components/osfile/tests/xpcshell/xpcshell.ini
@@ -4,8 +4,9 @@ tail =
 
 [test_osfile_closed.js]
 [test_path.js]
 [test_osfile_async.js]
 [test_profiledir.js]
 [test_logging.js]
 [test_creationDate.js]
 [test_path_constants.js]
+[test_removeDir.js]
--- a/toolkit/devtools/LayoutHelpers.jsm
+++ b/toolkit/devtools/LayoutHelpers.jsm
@@ -363,42 +363,22 @@ LayoutHelpers.prototype = {
       return win.parent;
     }
   },
 
   /**
    * like win.frameElement, but goes through mozbrowsers and mozapps iframes.
    *
    * @param DOMWindow win The window to get the frame for
-   * @return DOMElement The element(only <iframe> for now) in which the window
-   * is embedded. A null return from this function does not imply that it is a
-   * top level window. Use isTopLevelWindow(win) if needed.
+   * @return DOMElement The element in which the window is embedded.
    */
   getFrameElement: function LH_getFrameElement(win) {
     if (this.isTopLevelWindow(win)) {
       return null;
     }
 
-    // Get the docShell for that window
-    let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
-                   .getInterface(Ci.nsIWebNavigation)
-                   .QueryInterface(Ci.nsIDocShell);
+    let winUtils = win.
+      QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+      getInterface(Components.interfaces.nsIDOMWindowUtils);
 
-    if (docShell.isBrowserOrApp) {
-      // Get the docShell's same-type parent ignoring mozBrowser and mozApp
-      // boundaries
-      let parentDocShell = docShell.getSameTypeParentIgnoreBrowserAndAppBoundaries();
-      // Once we have a parent, get all its iframes and loop through them
-      // to find `win`. If we do, it means win is a nested iframe
-      let parentDoc = parentDocShell.contentViewer.DOMDocument;
-      let allIframes = parentDoc.querySelectorAll("iframe");
-      for (let f of allIframes) {
-        if (f.contentWindow === win) {
-          return f;
-        }
-      }
-
-      return null;
-    } else {
-      return win.frameElement;
-    }
+    return winUtils.containerElement;
   },
 };
--- a/toolkit/modules/PermissionsUtils.jsm
+++ b/toolkit/modules/PermissionsUtils.jsm
@@ -32,17 +32,17 @@ function importPrefBranch(aPrefBranch, a
       } catch (e) {}
     }
 
     Services.prefs.setCharPref(pref, "");
   }
 }
 
 
-let PermissionsUtils = {
+this.PermissionsUtils = {
   /**
    * Import permissions from perferences to the Permissions Manager. After being
    * imported, all processed permissions will be set to an empty string.
    * Perferences are only processed once during the application's
    * lifetime - it's safe to call this multiple times without worrying about
    * doing unnecessary work, as the preferences branch will only be processed
    * the first time.
    *