Merge fx-team to m-c. a=merge
authorRyan VanderMeulen <ryanvm@gmail.com>
Fri, 06 Jun 2014 17:00:16 -0400
changeset 207503 ba028f407ebfd62dd7ab9bf3025c0d8031ac84c2
parent 207476 cb0c1f1666f7798be572a9836755162486a4a421 (current diff)
parent 207502 a33f76e3ff8afcf920fdff4b7cfbf06ce58db647 (diff)
child 207560 62d33e3ba5148906105567837faf78491802ed1d
push id494
push userraliiev@mozilla.com
push dateMon, 25 Aug 2014 18:42:16 +0000
treeherdermozilla-release@a3cc3e46b571 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone32.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 fx-team to m-c. a=merge
browser/base/content/fonts/ClearSans-Regular.woff
browser/base/content/fonts/FiraSans-Light.woff
browser/base/content/fonts/FiraSans-Regular.woff
--- a/addon-sdk/source/examples/toolbar-api/package.json
+++ b/addon-sdk/source/examples/toolbar-api/package.json
@@ -1,9 +1,12 @@
 {
   "name": "toolbar-api",
-  "title": "toolbar-api",
-  "id": "toolbar-api",
+  "title": "Toolbar API",
+  "main": "./lib/main.js",
   "description": "a toolbar api example",
   "author": "",
   "license": "MPL 2.0",
-  "version": "0.1"
+  "version": "0.1",
+  "engines": {
+    "firefox": ">=27.0 <=30.0"
+  }
 }
--- a/addon-sdk/source/lib/sdk/addon/runner.js
+++ b/addon-sdk/source/lib/sdk/addon/runner.js
@@ -7,21 +7,22 @@ module.metadata = {
 };
 
 const { Cc, Ci } = require('chrome');
 const { descriptor, Sandbox, evaluate, main, resolveURI } = require('toolkit/loader');
 const { once } = require('../system/events');
 const { exit, env, staticArgs } = require('../system');
 const { when: unload } = require('../system/unload');
 const { loadReason } = require('../self');
-const { rootURI, metadata: { preferences } } = require("@loader/options");
+const { rootURI, metadata } = require("@loader/options");
 const globals = require('../system/globals');
 const xulApp = require('../system/xul-app');
 const appShellService = Cc['@mozilla.org/appshell/appShellService;1'].
                         getService(Ci.nsIAppShellService);
+const { preferences } = metadata;
 
 const NAME2TOPIC = {
   'Firefox': 'sessionstore-windows-restored',
   'Fennec': 'sessionstore-windows-restored',
   'SeaMonkey': 'sessionstore-windows-restored',
   'Thunderbird': 'mail-startup-done'
 };
 
@@ -129,23 +130,23 @@ function run(options) {
     catch(error) {
       console.exception(error);
     }
 
     // native-options does stuff directly with preferences key from package.json
     if (preferences && preferences.length > 0) {
       try {
         require('../preferences/native-options').enable(preferences);
-      } 
+      }
       catch (error) {
-        console.exception(error); 
+        console.exception(error);
       }
-    } 
+    }
     else {
-      // keeping support for addons packaged with older SDK versions, 
+      // keeping support for addons packaged with older SDK versions,
       // when cfx didn't include the 'preferences' key in @loader/options
 
       // Initialize inline options localization, without preventing addon to be
       // run in case of error
       try {
         require('../l10n/prefs').enable();
       }
       catch(error) {
@@ -153,17 +154,17 @@ function run(options) {
       }
 
       // TODO: When bug 564675 is implemented this will no longer be needed
       // Always set the default prefs, because they disappear on restart
       if (options.prefsURI) {
         // Only set if `prefsURI` specified
         try {
           setDefaultPrefs(options.prefsURI);
-        } 
+        }
         catch (err) {
           // cfx bootstrap always passes prefsURI, even in addons without prefs
         }
       }
     }
 
     // this is where the addon's main.js finally run.
     let program = main(options.loader, options.main);
--- a/addon-sdk/source/lib/sdk/content/content.js
+++ b/addon-sdk/source/lib/sdk/content/content.js
@@ -2,18 +2,31 @@
  * 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";
 
 module.metadata = {
   "stability": "deprecated"
 };
 
-const { deprecateFunction } = require('../util/deprecate');
+const { deprecateUsage } = require('../util/deprecate');
+
+Object.defineProperty(exports, "Loader", { 
+  get: function() {
+    deprecateUsage('`sdk/content/content` is deprecated. Please use `sdk/content/loader` directly.');
+    return require('./loader').Loader;
+  }
+});
 
-exports.Loader = deprecateFunction(require('./loader').Loader,
-  '`sdk/content/content` is deprecated. Please use `sdk/content/loader` directly.');
-exports.Symbiont = deprecateFunction(require('../deprecated/symbiont').Symbiont,
-  'Both `sdk/content/content` and `sdk/deprecated/symbiont` are deprecated. ' +
-  '`sdk/core/heritage` supersedes Symbiont for inheritance.');
-exports.Worker = deprecateFunction(require('./worker').Worker,
-  '`sdk/content/content` is deprecated. Please use `sdk/content/worker` directly.');
+Object.defineProperty(exports, "Symbiont", { 
+  get: function() {
+    deprecateUsage('Both `sdk/content/content` and `sdk/deprecated/symbiont` are deprecated. ' +
+                   '`sdk/core/heritage` supersedes Symbiont for inheritance.');
+    return require('../deprecated/symbiont').Symbiont;
+  }
+});
 
+Object.defineProperty(exports, "Worker", { 
+  get: function() {
+    deprecateUsage('`sdk/content/content` is deprecated. Please use `sdk/content/worker` directly.');
+    return require('./worker').Worker;
+  }
+});
--- a/addon-sdk/source/lib/sdk/request.js
+++ b/addon-sdk/source/lib/sdk/request.js
@@ -39,36 +39,42 @@ const { validateOptions, validateSingleO
   },
   contentType: {
     map: function (v) v || "application/x-www-form-urlencoded",
     is:  ["string"],
   },
   overrideMimeType: {
     map: function(v) v || null,
     is: ["string", "null"],
+  },
+  anonymous: {
+    map: function(v) v || false,
+    is: ["boolean", "null"],
   }
 });
 
 const REUSE_ERROR = "This request object has been used already. You must " +
                     "create a new one to make a new request."
 
 // Utility function to prep the request since it's the same between
 // request types
 function runRequest(mode, target) {
   let source = request(target)
-  let { xhr, url, content, contentType, headers, overrideMimeType } = source;
+  let { xhr, url, content, contentType, headers, overrideMimeType, anonymous } = source;
 
   let isGetOrHead = (mode == "GET" || mode == "HEAD");
 
   // If this request has already been used, then we can't reuse it.
   // Throw an error.
   if (xhr)
     throw new Error(REUSE_ERROR);
 
-  xhr = source.xhr = new XMLHttpRequest();
+  xhr = source.xhr = new XMLHttpRequest({
+    mozAnon: anonymous
+  });
 
   // Build the data to be set. For GET or HEAD requests, we want to append that
   // to the URL before opening the request.
   let data = stringify(content);
   // If the URL already has ? in it, then we want to just use &
   if (isGetOrHead && data)
     url = url + (/\?/.test(url) ? "&" : "?") + data;
 
@@ -124,16 +130,17 @@ const Request = Class({
   get content() { return request(this).content; },
   set content(value) {
     request(this).content = validateSingleOption('content', value);
   },
   get contentType() { return request(this).contentType; },
   set contentType(value) {
     request(this).contentType = validateSingleOption('contentType', value);
   },
+  get anonymous() { return request(this).anonymous; },
   get response() { return request(this).response; },
   delete: function() {
     runRequest('DELETE', this);
     return this;
   },
   get: function() {
     runRequest('GET', this);
     return this;
@@ -197,17 +204,18 @@ const Response = Class({
         headers[key] = val;
         lastKey = key;
       }
       else {
         headers[lastKey] += "\n" + val;
       }
     });
     return headers;
-  }
+  },
+  get anonymous() response(this).request.mozAnon
 });
 
 // apiUtils.validateOptions doesn't give the ability to easily validate single
 // options, so this is a wrapper that provides that ability.
 function OptionsValidator(rules) {
   return {
     validateOptions: function (options) {
       return apiUtils.validateOptions(options, rules);
--- a/addon-sdk/source/lib/sdk/self.js
+++ b/addon-sdk/source/lib/sdk/self.js
@@ -3,37 +3,48 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 module.metadata = {
   "stability": "stable"
 };
 
 const { CC } = require('chrome');
-const { id, name, prefixURI, rootURI, metadata,
-        version, loadReason, preferencesBranch } = require('@loader/options');
+const options = require('@loader/options');
 
+const { get } = require("./preferences/service");
 const { readURISync } = require('./net/url');
 
-const addonDataURI = prefixURI + name + '/data/';
+const id = options.id;
+
+const readPref = key => get("extensions." + id + ".sdk." + key);
+
+const name = readPref("name") || options.name;
+const version = readPref("version") || options.version;
+const loadReason = readPref("load.reason") || options.loadReason;
+const rootURI = readPref("rootURI") || options.rootURI || "";
+const baseURI = readPref("baseURI") || options.prefixURI + name + "/";
+const addonDataURI = baseURI + "data/";
+const metadata = options.metadata || {};
+const permissions = metadata.permissions || {};
+const isPacked = rootURI && rootURI.indexOf("jar:") === 0;
 
 const uri = (path="") =>
   path.contains(":") ? path : addonDataURI + path;
 
 
 // Some XPCOM APIs require valid URIs as an argument for certain operations
 // (see `nsILoginManager` for example). This property represents add-on
 // associated unique URI string that can be used for that.
 exports.uri = 'addon:' + id;
 exports.id = id;
-exports.preferencesBranch = preferencesBranch || id;
+exports.preferencesBranch = options.preferencesBranch || id;
 exports.name = name;
 exports.loadReason = loadReason;
 exports.version = version;
-// If `rootURI` is jar:file://...!/ than add-on is packed.
-exports.packed = (rootURI || '').indexOf('jar:') === 0;
+exports.packed = isPacked;
 exports.data = Object.freeze({
   url: uri,
   load: function read(path) {
     return readURISync(uri(path));
   }
 });
-exports.isPrivateBrowsingSupported = ((metadata || {}).permissions || {})['private-browsing'] === true;
+exports.isPrivateBrowsingSupported = permissions['private-browsing'] === true;
--- a/addon-sdk/source/lib/sdk/tabs/utils.js
+++ b/addon-sdk/source/lib/sdk/tabs/utils.js
@@ -56,17 +56,18 @@ function activateTab(tab, window) {
   else if (window && window.BrowserApp) {
     window.BrowserApp.selectTab(tab);
   }
   return null;
 }
 exports.activateTab = activateTab;
 
 function getTabBrowser(window) {
-  return window.gBrowser;
+  // bug 1009938 - may be null in SeaMonkey
+  return window.gBrowser || window.getBrowser();
 }
 exports.getTabBrowser = getTabBrowser;
 
 function getTabContainer(window) {
   return getTabBrowser(window).tabContainer;
 }
 exports.getTabContainer = getTabContainer;
 
@@ -235,73 +236,20 @@ exports.getTabContentWindow = getTabCont
  */
 function getAllTabContentWindows() {
   return getTabs().map(getTabContentWindow);
 }
 exports.getAllTabContentWindows = getAllTabContentWindows;
 
 // gets the tab containing the provided window
 function getTabForContentWindow(window) {
-  // Retrieve the topmost frame container. It can be either <xul:browser>,
-  // <xul:iframe/> or <html:iframe/>. But in our case, it should be xul:browser.
-  let browser;
-  try {
-    browser = window.QueryInterface(Ci.nsIInterfaceRequestor)
-                    .getInterface(Ci.nsIWebNavigation)
-                    .QueryInterface(Ci.nsIDocShell)
-                    .chromeEventHandler;
-  } catch(e) {
-    // Bug 699450: The tab may already have been detached so that `window` is
-    // in a almost destroyed state and can't be queryinterfaced anymore.
-  }
-
-  // Is null for toplevel documents
-  if (!browser) {
-    return null;
-  }
-
-  // Retrieve the owner window, should be browser.xul one
-  let chromeWindow = browser.ownerDocument.defaultView;
-
-  // Ensure that it is top-level browser window.
-  // We need extra checks because of Mac hidden window that has a broken
-  // `gBrowser` global attribute.
-  if ('gBrowser' in chromeWindow && chromeWindow.gBrowser &&
-      'browsers' in chromeWindow.gBrowser) {
-    // Looks like we are on Firefox Desktop
-    // Then search for the position in tabbrowser in order to get the tab object
-    let browsers = chromeWindow.gBrowser.browsers;
-    let i = browsers.indexOf(browser);
-    if (i !== -1)
-      return chromeWindow.gBrowser.tabs[i];
-    return null;
-  }
-  // Fennec
-  else if ('BrowserApp' in chromeWindow) {
-    return getTabForWindow(window);
-  }
-
-  return null;
+  return getTabs().find(tab => getTabContentWindow(tab) === window.top) || null;
 }
 exports.getTabForContentWindow = getTabForContentWindow;
 
-// used on fennec
-function getTabForWindow(window) {
-  for each (let { BrowserApp } in getWindows()) {
-    if (!BrowserApp)
-      continue;
-
-    for each (let tab in BrowserApp.tabs) {
-      if (tab.browser.contentWindow == window.top)
-        return tab;
-    }
-  }
-  return null;
-}
-
 function getTabURL(tab) {
   if (tab.browser) // fennec
     return String(tab.browser.currentURI.spec);
   return String(getBrowserForTab(tab).currentURI.spec);
 }
 exports.getTabURL = getTabURL;
 
 function setTabURL(tab, url) {
--- a/addon-sdk/source/lib/toolkit/loader.js
+++ b/addon-sdk/source/lib/toolkit/loader.js
@@ -379,33 +379,32 @@ const resolve = iced(function resolve(id
 });
 exports.resolve = resolve;
 
 // Node-style module lookup
 // Takes an id and path and attempts to load a file using node's resolving
 // algorithm.
 // `id` should already be resolved relatively at this point.
 // http://nodejs.org/api/modules.html#modules_all_together
-const nodeResolve = iced(function nodeResolve(id, requirer, { manifest, rootURI }) {
+const nodeResolve = iced(function nodeResolve(id, requirer, { rootURI }) {
   // Resolve again
   id = exports.resolve(id, requirer);
 
   // we assume that extensions are correct, i.e., a directory doesnt't have '.js'
   // and a js file isn't named 'file.json.js'
-
   let fullId = join(rootURI, id);
 
   let resolvedPath;
   if (resolvedPath = loadAsFile(fullId))
     return stripBase(rootURI, resolvedPath);
   else if (resolvedPath = loadAsDirectory(fullId))
     return stripBase(rootURI, resolvedPath);
   // If manifest has dependencies, attempt to look up node modules
   // in the `dependencies` list
-  else if (manifest.dependencies) {
+  else {
     let dirs = getNodeModulePaths(dirname(join(rootURI, requirer))).map(dir => join(dir, id));
     for (let i = 0; i < dirs.length; i++) {
       if (resolvedPath = loadAsFile(dirs[i]))
         return stripBase(rootURI, resolvedPath);
       if (resolvedPath = loadAsDirectory(dirs[i]))
         return stripBase(rootURI, resolvedPath);
     }
   }
@@ -528,17 +527,16 @@ const Require = iced(function Require(lo
       throw Error('you must provide a module name when calling require() from '
                   + requirer.id, requirer.uri);
 
     let requirement;
     let uri;
 
     // TODO should get native Firefox modules before doing node-style lookups
     // to save on loading time
-
     if (isNative) {
       // If a requireMap is available from `generateMap`, use that to
       // immediately resolve the node-style mapping.
       if (requireMap && requireMap[requirer.id])
         requirement = requireMap[requirer.id][id];
 
       // For native modules, we want to check if it's a module specified
       // in 'modules', like `chrome`, or `@loader` -- if it exists,
@@ -688,17 +686,18 @@ exports.unload = unload;
 //   If `resolve` does not returns `uri` string exception will be thrown by
 //   an associated `require` call.
 const Loader = iced(function Loader(options) {
   let console = new ConsoleAPI({
     consoleID: options.id ? "addon/" + options.id : ""
   });
 
   let {
-    modules, globals, resolve, paths, rootURI, manifest, requireMap, isNative
+    modules, globals, resolve, paths, rootURI,
+    manifest, requireMap, isNative, metadata
   } = override({
     paths: {},
     modules: {},
     globals: {
       console: console
     },
     resolve: options.isNative ?
       exports.nodeResolve :
@@ -743,16 +742,17 @@ const Loader = iced(function Loader(opti
   // state. We freeze it and mark make it's properties non-enumerable
   // as they are pure implementation detail that no one should rely upon.
   let returnObj = {
     destructor: { enumerable: false, value: destructor },
     globals: { enumerable: false, value: globals },
     mapping: { enumerable: false, value: mapping },
     // Map of module objects indexed by module URIs.
     modules: { enumerable: false, value: modules },
+    metadata: { enumerable: false, value: metadata },
     // Map of module sandboxes indexed by module URIs.
     sandboxes: { enumerable: false, value: {} },
     resolve: { enumerable: false, value: resolve },
     // ID of the addon, if provided.
     id: { enumerable: false, value: options.id },
     // Whether the modules loaded should be ignored by the debugger
     invisibleToDebugger: { enumerable: false,
                            value: options.invisibleToDebugger || false },
--- a/addon-sdk/source/python-lib/cuddlefish/__init__.py
+++ b/addon-sdk/source/python-lib/cuddlefish/__init__.py
@@ -1,16 +1,15 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import sys
 import os
 import optparse
-import webbrowser
 import time
 
 from copy import copy
 import simplejson as json
 from cuddlefish import packaging
 from cuddlefish._version import get_versions
 
 MOZRUNNER_BIN_NOT_FOUND = 'Mozrunner could not locate your binary'
--- a/addon-sdk/source/python-lib/cuddlefish/tests/test_xpi.py
+++ b/addon-sdk/source/python-lib/cuddlefish/tests/test_xpi.py
@@ -143,22 +143,22 @@ class SmallXPI(unittest.TestCase):
                      ]]
 
         add_addon_sdk= lambda path: os.path.join(addon_sdk_dir, path)
         expected.extend([add_addon_sdk(module) for module in [
             os.path.join("sdk", "self.js"),
             os.path.join("sdk", "core", "promise.js"),
             os.path.join("sdk", "net", "url.js"),
             os.path.join("sdk", "util", "object.js"),
-            os.path.join("sdk", "util", "array.js")
+            os.path.join("sdk", "util", "array.js"),
+            os.path.join("sdk", "preferences", "service.js")
             ]])
 
         missing = set(expected) - set(used_files)
         extra = set(used_files) - set(expected)
-
         self.failUnlessEqual(list(missing), [])
         self.failUnlessEqual(list(extra), [])
         used_deps = m.get_used_packages()
 
         build = packaging.generate_build_for_target(pkg_cfg, target_cfg.name,
                                                     used_deps,
                                                     include_tests=False)
         options = {'main': target_cfg.main}
@@ -185,16 +185,18 @@ class SmallXPI(unittest.TestCase):
                     "resources/addon-sdk/lib/sdk/self.js",
                     "resources/addon-sdk/lib/sdk/core/",
                     "resources/addon-sdk/lib/sdk/util/",
                     "resources/addon-sdk/lib/sdk/net/",
                     "resources/addon-sdk/lib/sdk/core/promise.js",
                     "resources/addon-sdk/lib/sdk/util/object.js",
                     "resources/addon-sdk/lib/sdk/util/array.js",
                     "resources/addon-sdk/lib/sdk/net/url.js",
+                    "resources/addon-sdk/lib/sdk/preferences/",
+                    "resources/addon-sdk/lib/sdk/preferences/service.js",
                     "resources/three/",
                     "resources/three/lib/",
                     "resources/three/lib/main.js",
                     "resources/three/data/",
                     "resources/three/data/msg.txt",
                     "resources/three/data/subdir/",
                     "resources/three/data/subdir/submsg.txt",
                     "resources/three-a/",
--- a/addon-sdk/source/test/addons/content-permissions/main.js
+++ b/addon-sdk/source/test/addons/content-permissions/main.js
@@ -1,9 +1,9 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
+/* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { PageMod } = require("sdk/page-mod");
 const tabs = require("sdk/tabs");
 const { startServerAsync } = require("sdk/test/httpd");
 
--- a/addon-sdk/source/test/addons/l10n-properties/main.js
+++ b/addon-sdk/source/test/addons/l10n-properties/main.js
@@ -1,9 +1,9 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
+/* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const prefs = require("sdk/preferences/service");
 const { Loader } = require('sdk/test/loader');
 const { resolveURI } = require('toolkit/loader');
 const { rootURI } = require("@loader/options");
--- a/addon-sdk/source/test/addons/l10n/main.js
+++ b/addon-sdk/source/test/addons/l10n/main.js
@@ -1,9 +1,9 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
+/* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const prefs = require("sdk/preferences/service");
 const { Loader } = require('sdk/test/loader');
 const { resolveURI } = require('toolkit/loader');
 const { rootURI } = require("@loader/options");
--- a/addon-sdk/source/test/addons/layout-change/main.js
+++ b/addon-sdk/source/test/addons/layout-change/main.js
@@ -1,9 +1,9 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
+/* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const { LoaderWithHookedConsole } = require('sdk/test/loader');
 const { loader } = LoaderWithHookedConsole(module);
 const app = require("sdk/system/xul-app");
--- a/addon-sdk/source/test/addons/packed/main.js
+++ b/addon-sdk/source/test/addons/packed/main.js
@@ -1,9 +1,9 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
+/* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { packed } = require("sdk/self");
 const url = require("sdk/url");
 
 exports["test self.packed"] = function (assert) {
--- a/addon-sdk/source/test/addons/require/main.js
+++ b/addon-sdk/source/test/addons/require/main.js
@@ -1,9 +1,9 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
+/* 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";
 
 exports["test local vs sdk module"] = function (assert) {
   assert.notEqual(require("memory"),
                   require("sdk/deprecated/memory"),
                   "Local module takes the priority over sdk modules");
--- a/addon-sdk/source/test/addons/simple-prefs/lib/main.js
+++ b/addon-sdk/source/test/addons/simple-prefs/lib/main.js
@@ -129,17 +129,17 @@ if (app.is('Firefox')) {
 
               tab.close(done);
             }
           });
       	}
       });
   }
 
-  // run it again, to test against inline options document caching 
+  // run it again, to test against inline options document caching
   // and duplication of <setting> nodes upon re-entry to about:addons
   exports.testAgainstDocCaching = exports.testAOM;
 
 }
 
 exports.testDefaultPreferencesBranch = function(assert) {
   assert.equal(preferencesBranch, self.id, 'preferencesBranch default the same as self.id');
 }
--- a/addon-sdk/source/test/addons/unpacked/main.js
+++ b/addon-sdk/source/test/addons/unpacked/main.js
@@ -1,9 +1,9 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
+/* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const { packed } = require("sdk/self");
 const url = require("sdk/url");
 
 exports["test self.packed"] = function (assert) {
--- a/addon-sdk/source/test/test-child_process.js
+++ b/addon-sdk/source/test/test-child_process.js
@@ -535,8 +535,11 @@ exports.testFork = function (assert) {
   assert.throws(function () {
     fork();
   }, /not currently supported/, 'fork() correctly throws an unsupported error');
 };
 
 after(exports, cleanUp);
 
 require("test").run(exports);
+
+// Test disabled because of bug 979675
+module.exports = {};
--- a/addon-sdk/source/test/test-content-symbiont.js
+++ b/addon-sdk/source/test/test-content-symbiont.js
@@ -4,16 +4,20 @@
 "use strict";
 
 const { Cc, Ci } = require('chrome');
 const { Symbiont } = require('sdk/deprecated/symbiont');
 const self = require('sdk/self');
 const fixtures = require("./fixtures");
 const { close } = require('sdk/window/helpers');
 const app = require("sdk/system/xul-app");
+const { LoaderWithHookedConsole } = require('sdk/test/loader');
+const { set: setPref, get: getPref } = require("sdk/preferences/service");
+
+const DEPRECATE_PREF = "devtools.errorconsole.deprecation_warnings";
 
 function makeWindow() {
   let content =
     '<?xml version="1.0"?>' +
     '<window ' +
     'xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">' +
     '<iframe id="content" type="content"/>' +
     '</window>';
@@ -152,9 +156,30 @@ exports["test:document element present o
         assert.ok(message, "document element present on 'start'");
       else
         assert.pass("document element not necessarily present on 'start'");
       done();
     }
   });
 };
 
+exports["test:content/content deprecation"] = function(assert) {
+  let pref = getPref(DEPRECATE_PREF, false);
+  setPref(DEPRECATE_PREF, true);
+
+  const { loader, messages } = LoaderWithHookedConsole(module);
+  const { Loader, Symbiont, Worker } = loader.require("sdk/content/content");
+
+  assert.equal(messages.length, 3, "Should see three warnings");
+
+  assert.strictEqual(Loader, loader.require('sdk/content/loader').Loader,
+    "Loader from content/content is the exact same object as the one from content/loader");
+
+  assert.strictEqual(Symbiont, loader.require('sdk/deprecated/symbiont').Symbiont,
+    "Symbiont from content/content is the exact same object as the one from deprecated/symbiont");
+
+  assert.strictEqual(Worker, loader.require('sdk/content/worker').Worker,
+    "Worker from content/content is the exact same object as the one from content/worker");
+
+  setPref(DEPRECATE_PREF, pref);
+}
+
 require("test").run(exports);
--- a/addon-sdk/source/test/test-request.js
+++ b/addon-sdk/source/test/test-request.js
@@ -38,16 +38,33 @@ exports.testOptionsValidator = function(
     url: "http://playground.zpao.com/jetpack/request/text.php",
     onComplete: function () {}
   });
   assert.throws(function () {
     req.url = 'www.mozilla.org';
   }, /The option "url" is invalid/);
   // The url shouldn't have changed, so check that
   assert.equal(req.url, "http://playground.zpao.com/jetpack/request/text.php");
+
+  // Test default anonymous parameter value
+  assert.equal(req.anonymous, false);
+  // Test set anonymous parameter value
+  req = Request({
+    url: "http://playground.zpao.com/jetpack/request/text.php",
+    anonymous: true,
+    onComplete: function () {}
+  });
+  assert.equal(req.anonymous, true);
+  // Test wrong value as anonymous parameter value
+  assert.throws(function() {
+    Request({
+      url: "http://playground.zpao.com/jetpack/request/text.php",
+      anonymous: "invalidvalue"
+    });
+  }, /The option "anonymous" must be one of the following types/);
 };
 
 exports.testContentValidator = function(assert, done) {
   runMultipleURLs(null, assert, done, {
     url: "data:text/html;charset=utf-8,response",
     content: { 'key1' : null, 'key2' : 'some value' },
     onComplete: function(response) {
       assert.equal(response.text, "response?key1=null&key2=some+value");
@@ -178,16 +195,70 @@ exports.test3rdPartyCookies = function (
           assert.equal(response.headers['x-jetpack-3rd-party'], 'true');
           srv.stop(done);
         }
       }).get();
     }
   }).get();
 };
 
+// Test anonymous request behavior
+exports.testAnonymousRequest = function(assert, done) {
+  let srv = startServerAsync(port, basePath);
+  let basename = "test-anonymous-request.sjs";
+  let testUrl = "http://localhost:" + port + "/" + basename;
+  // Function to handle the requests in the server
+  let content = function handleRequest(request, response) {
+    // Request to store cookie
+    response.setHeader("Set-Cookie", "anonymousKey=anonymousValue;", "true");
+    // Set response content type
+    response.setHeader("Content-Type", "application/json");
+    // Check if cookie was send during request
+    var cookiePresent = request.hasHeader("Cookie");
+    // Create server respone content
+    response.write(JSON.stringify({ "hasCookie": cookiePresent }));
+  }.toString();
+  prepareFile(basename, content);
+  // Create request callbacks
+  var checkCookieCreated = function (response) {
+    // Check that the server created the cookie
+    assert.equal(response.headers['Set-Cookie'], 'anonymousKey=anonymousValue;');
+    // Make an other request and check that the server this time got the cookie
+    Request({
+      url: testUrl,
+      onComplete: checkCookieSend
+    }).get();
+  },
+  checkCookieSend = function (response) {
+    // Check the response sent headers and cookies
+    assert.equal(response.anonymous, false);
+    // Check the server got the created cookie
+    assert.equal(response.json.hasCookie, true);
+    // Make a anonymous request and check the server did not get the cookie
+    Request({
+      url: testUrl,
+      anonymous: true,
+      onComplete: checkCookieNotSend
+    }).get();
+  },
+  checkCookieNotSend = function (response) {
+    // Check the response is anonymous
+    assert.equal(response.anonymous, true);
+    // Check the server did not get the cookie
+    assert.equal(response.json.hasCookie, false);
+    // Stop the server
+    srv.stop(done);
+  };
+  // Make the first request to create cookie
+  Request({
+    url: testUrl,
+    onComplete: checkCookieCreated
+  }).get();
+};
+
 exports.testSimpleJSON = function (assert, done) {
   let srv = startServerAsync(port, basePath);
   let json = { foo: "bar" };
   let basename = "test-request.json";
   prepareFile(basename, JSON.stringify(json));
 
   runMultipleURLs(srv, assert, done, {
     url: "http://localhost:" + port + "/" + basename,
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1232,22 +1232,18 @@ pref("devtools.toolbar.enabled", true);
 pref("devtools.toolbar.visible", false);
 pref("devtools.commands.dir", "");
 
 // Enable the app manager
 pref("devtools.appmanager.enabled", true);
 pref("devtools.appmanager.lastTab", "help");
 pref("devtools.appmanager.manifestEditor.enabled", true);
 
-// Enable devtools webide
-#ifdef MOZ_DEVTOOLS_WEBIDE
-pref("devtools.webide.enabled", true);
-#else
+// Disable devtools webide until bug 1007059
 pref("devtools.webide.enabled", false);
-#endif
 
 // Toolbox preferences
 pref("devtools.toolbox.footer.height", 250);
 pref("devtools.toolbox.sidebar.width", 500);
 pref("devtools.toolbox.host", "bottom");
 pref("devtools.toolbox.selectedTool", "webconsole");
 pref("devtools.toolbox.toolbarSpec", '["splitconsole", "paintflashing toggle","tilt toggle","scratchpad","resize toggle","eyedropper","screenshot --fullpage"]');
 pref("devtools.toolbox.sideEnabled", true);
--- a/browser/base/content/abouthome/aboutHome.xhtml
+++ b/browser/base/content/abouthome/aboutHome.xhtml
@@ -57,16 +57,20 @@
 
     <div id="launcher">
       <button class="launchButton" id="downloads">&abouthome.downloadsButton.label;</button>
       <button class="launchButton" id="bookmarks">&abouthome.bookmarksButton.label;</button>
       <button class="launchButton" id="history">&abouthome.historyButton.label;</button>
       <button class="launchButton" id="apps" hidden="true">&abouthome.appsButton.label;</button>
       <button class="launchButton" id="addons">&abouthome.addonsButton.label;</button>
       <button class="launchButton" id="sync">&abouthome.syncButton.label;</button>
-      <button class="launchButton" id="settings">&abouthome.settingsButton.label;</button>
+#ifdef XP_WIN
+      <button class="launchButton" id="settings">&abouthome.preferencesButtonWin.label;</button>
+#else
+      <button class="launchButton" id="settings">&abouthome.preferencesButtonUnix.label;</button>
+#endif
       <div id="restorePreviousSessionSeparator"/>
       <button class="launchButton" id="restorePreviousSession">&historyRestoreLastSession.label;</button>
     </div>
 
     <a id="aboutMozilla" href="https://www.mozilla.org/about/?utm_source=about-home&amp;utm_medium=Referral"/>
   </body>
 </html>
deleted file mode 100644
index 00c279d2e9f4b95f230b58ef897dfe689e6c36cb..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 979d0a5018299955cebb5378c941f72c907769cb..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 15605473f77ac5371ca3757e2a13a0e7c1427e5c..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/base/content/sync/customize.xul
+++ b/browser/base/content/sync/customize.xul
@@ -33,33 +33,36 @@
     <description id="sync-customize-subtitle"
 #ifdef XP_UNIX
                  value="&syncCustomizeUnix.description;"
 #else
                  value="&syncCustomize.description;"
 #endif
                  />
 
-    <checkbox label="&engine.tabs.label;"
-              accesskey="&engine.tabs.accesskey;"
-              preference="engine.tabs"/>
-    <checkbox label="&engine.bookmarks.label;"
-              accesskey="&engine.bookmarks.accesskey;"
-              preference="engine.bookmarks"/>
-    <checkbox id="fxa-pweng-chk"
-              label="&engine.passwords.label;"
-              accesskey="&engine.passwords.accesskey;"
-              preference="engine.passwords"/>
-    <checkbox label="&engine.history.label;"
-              accesskey="&engine.history.accesskey;"
-              preference="engine.history"/>
-    <checkbox label="&engine.addons.label;"
-              accesskey="&engine.addons.accesskey;"
-              preference="engine.addons"/>
-    <checkbox label="&engine.prefs.label;"
-              accesskey="&engine.prefs.accesskey;"
-              preference="engine.prefs"/>
+  <vbox align="start">
+      <checkbox label="&engine.tabs.label;"
+                accesskey="&engine.tabs.accesskey;"
+                preference="engine.tabs"/>
+      <checkbox label="&engine.bookmarks.label;"
+                accesskey="&engine.bookmarks.accesskey;"
+                preference="engine.bookmarks"/>
+      <checkbox id="fxa-pweng-chk"
+                label="&engine.passwords.label;"
+                accesskey="&engine.passwords.accesskey;"
+                preference="engine.passwords"/>
+      <checkbox label="&engine.history.label;"
+                accesskey="&engine.history.accesskey;"
+                preference="engine.history"/>
+      <checkbox label="&engine.addons.label;"
+                accesskey="&engine.addons.accesskey;"
+                preference="engine.addons"/>
+      <checkbox label="&engine.prefs.label;"
+                accesskey="&engine.prefs.accesskey;"
+                preference="engine.prefs"/>
+  </vbox>
+
   </prefpane>
 
   <script type="application/javascript"
           src="chrome://browser/content/sync/customize.js" />
 
 </dialog>
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -13,17 +13,17 @@ browser.jar:
 #endif
 %  overlay chrome://global/content/viewSource.xul chrome://browser/content/viewSourceOverlay.xul
 %  overlay chrome://global/content/viewPartialSource.xul chrome://browser/content/viewSourceOverlay.xul
 
 *       content/browser/aboutDialog.xul               (content/aboutDialog.xul)
 *       content/browser/aboutDialog.js                (content/aboutDialog.js)
         content/browser/aboutDialog.css               (content/aboutDialog.css)
         content/browser/aboutRobots.xhtml             (content/aboutRobots.xhtml)
-        content/browser/abouthome/aboutHome.xhtml     (content/abouthome/aboutHome.xhtml)
+*       content/browser/abouthome/aboutHome.xhtml     (content/abouthome/aboutHome.xhtml)
         content/browser/abouthome/aboutHome.js        (content/abouthome/aboutHome.js)
 *       content/browser/abouthome/aboutHome.css       (content/abouthome/aboutHome.css)
         content/browser/abouthome/snippet1.png        (content/abouthome/snippet1.png)
         content/browser/abouthome/snippet2.png        (content/abouthome/snippet2.png)
         content/browser/abouthome/downloads.png       (content/abouthome/downloads.png)
         content/browser/abouthome/bookmarks.png       (content/abouthome/bookmarks.png)
         content/browser/abouthome/history.png         (content/abouthome/history.png)
         content/browser/abouthome/apps.png            (content/abouthome/apps.png)
@@ -73,19 +73,16 @@ browser.jar:
         content/browser/aboutTabCrashed.js            (content/aboutTabCrashed.js)
         content/browser/aboutTabCrashed.xhtml         (content/aboutTabCrashed.xhtml)
 *       content/browser/browser.css                   (content/browser.css)
 *       content/browser/browser.js                    (content/browser.js)
 *       content/browser/browser.xul                   (content/browser.xul)
 *       content/browser/browser-tabPreviews.xml       (content/browser-tabPreviews.xml)
 *       content/browser/chatWindow.xul                (content/chatWindow.xul)
         content/browser/content.js                    (content/content.js)
-        content/browser/fonts/ClearSans-Regular.woff  (content/fonts/ClearSans-Regular.woff)
-        content/browser/fonts/FiraSans-Regular.woff   (content/fonts/FiraSans-Regular.woff)
-        content/browser/fonts/FiraSans-Light.woff     (content/fonts/FiraSans-Light.woff)
         content/browser/newtab/newTab.xul             (content/newtab/newTab.xul)
 *       content/browser/newtab/newTab.js              (content/newtab/newTab.js)
         content/browser/newtab/newTab.css             (content/newtab/newTab.css)
 *       content/browser/pageinfo/pageInfo.xul         (content/pageinfo/pageInfo.xul)
         content/browser/pageinfo/pageInfo.js          (content/pageinfo/pageInfo.js)
         content/browser/pageinfo/pageInfo.css         (content/pageinfo/pageInfo.css)
         content/browser/pageinfo/pageInfo.xml         (content/pageinfo/pageInfo.xml)
         content/browser/pageinfo/feeds.js             (content/pageinfo/feeds.js)
--- a/browser/branding/aurora/pref/firefox-branding.js
+++ b/browser/branding/aurora/pref/firefox-branding.js
@@ -7,18 +7,18 @@
 // We don't have pages ready for this, so leave these prefs empty for now (bug 648330)
 pref("startup.homepage_override_url","");
 pref("startup.homepage_welcome_url","");
 // The time interval between checks for a new version (in seconds)
 pref("app.update.interval", 28800); // 8 hours
 // The time interval between the downloading of mar file chunks in the
 // background (in seconds)
 pref("app.update.download.backgroundInterval", 60);
-// Give the user x seconds to react before showing the big UI. default=24 hours
-pref("app.update.promptWaitTime", 86400);
+// Give the user x seconds to react before showing the big UI. default=168 hours
+pref("app.update.promptWaitTime", 604800);
 // URL user can browse to manually if for some reason all update installation
 // attempts fail.
 pref("app.update.url.manual", "https://www.mozilla.org/firefox/aurora/");
 // A default value for the "More information about this update" link
 // supplied in the "An update is available" page of the update wizard. 
 pref("app.update.url.details", "https://www.mozilla.org/firefox/aurora/");
 
 // The number of days a binary is permitted to be old
--- a/browser/components/preferences/sync.xul
+++ b/browser/components/preferences/sync.xul
@@ -180,44 +180,45 @@
 
         <!-- These panels are for the Firefox Accounts identity provider -->
         <vbox id="fxaDeterminingStatus" align="center">
           <spacer flex="1"/>
           <p>&determiningAcctStatus.label;</p>
           <spacer flex="1"/>
         </vbox>
 
-        <vbox id="noFxaAccount">
+        <vbox id="noFxaAccount" align="start">
           <label>&welcome.description;</label>
-          <label class="text-link"
-                 onclick="gSyncPane.signUp(); return false;"
-                 value="&welcome.createAccount.label;"/>
-          <label class="text-link"
-                 onclick="gSyncPane.signIn(); return false;"
-                 value="&welcome.signIn.label;"/>
-          <separator/>
-          <label class="text-link"
-                 onclick="gSyncPane.openOldSyncSupportPage(); return false;"
-                 value="&welcome.useOldSync.label;"/>
+            <label class="text-link"
+                   onclick="gSyncPane.signUp(); return false;"
+                   value="&welcome.createAccount.label;"/>
+            <label class="text-link"
+                   onclick="gSyncPane.signIn(); return false;"
+                   value="&welcome.signIn.label;"/>
+            <separator/>
+            <label class="text-link"
+                   onclick="gSyncPane.openOldSyncSupportPage(); return false;"
+                   value="&welcome.useOldSync.label;"/>
         </vbox>
 
         <vbox id="hasFxaAccount">
           <groupbox id="fxaGroup">
             <caption label="&syncBrand.fxAccount.label;"/>
 
             <deck id="fxaLoginStatus">
 
               <!-- logged in and verified and all is good -->
               <hbox>
                 <label id="fxaEmailAddress1"/>
-                <vbox flex="1">
+                <vbox>
                   <label class="text-link"
                          onclick="gSyncPane.manageFirefoxAccount();"
                          value="&manage.label;"/>
                 </vbox>
+                <spacer flex="1"/>
                 <vbox>
                   <button id="fxaUnlinkButton"
                           oncommand="gSyncPane.unlinkFirefoxAccount(true);"
                           label="&disconnect.label;"/>
                 </vbox>
               </hbox>
 
               <!-- logged in to an unverified account -->
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -439,17 +439,16 @@ ThreadState.prototype = {
    * Connect to the current thread client.
    */
   connect: function() {
     dumpn("ThreadState is connecting...");
     this.activeThread.addListener("paused", this._update);
     this.activeThread.addListener("resumed", this._update);
     this.activeThread.pauseOnExceptions(Prefs.pauseOnExceptions,
                                         Prefs.ignoreCaughtExceptions);
-    this.handleTabNavigation();
   },
 
   /**
    * Disconnect from the client.
    */
   disconnect: function() {
     if (!this.activeThread) {
       return;
--- a/browser/devtools/moz.build
+++ b/browser/devtools/moz.build
@@ -23,19 +23,17 @@ DIRS += [
     'shadereditor',
     'shared',
     'sourceeditor',
     'styleeditor',
     'styleinspector',
     'tilt',
     'webaudioeditor',
     'webconsole',
+    'webide',
 ]
 
-if CONFIG['MOZ_DEVTOOLS_WEBIDE']:
-    DIRS += ['webide']
-
 EXTRA_COMPONENTS += [
     'devtools-clhandler.js',
     'devtools-clhandler.manifest',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
--- a/browser/devtools/projecteditor/chrome/content/projecteditor-loader.js
+++ b/browser/devtools/projecteditor/chrome/content/projecteditor-loader.js
@@ -2,19 +2,19 @@ const Cu = Components.utils;
 const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
 const {NetUtil} = Cu.import("resource://gre/modules/NetUtil.jsm", {});
 const require = devtools.require;
 const promise = require("projecteditor/helpers/promise");
 const ProjectEditor = require("projecteditor/projecteditor");
 
 const SAMPLE_PATH = buildTempDirectoryStructure();
-const SAMPLE_NAME = "DevTools Content";
+const SAMPLE_NAME = "DevTools Content Application Name";
 const SAMPLE_PROJECT_URL = "http://mozilla.org";
-const SAMPLE_ICON = "chrome://browser/skin/devtools/tool-options.svg";
+const SAMPLE_ICON = "chrome://browser/skin/devtools/tool-debugger.svg";
 
 /**
  * Create a workspace for working on projecteditor, available at
  * chrome://browser/content/devtools/projecteditor-loader.xul.
  * This emulates the integration points that the app manager uses.
  */
 document.addEventListener("DOMContentLoaded", function onDOMReady(e) {
   document.removeEventListener("DOMContentLoaded", onDOMReady, false);
@@ -51,21 +51,23 @@ document.addEventListener("DOMContentLoa
   projecteditor.on("onCommand", (cmd) => {
     console.log("Command: " + cmd);
   });
 
   projecteditor.loaded.then(() => {
     projecteditor.setProjectToAppPath(SAMPLE_PATH, {
       name: SAMPLE_NAME,
       iconUrl: SAMPLE_ICON,
-      projectOverviewURL: SAMPLE_PROJECT_URL
+      projectOverviewURL: SAMPLE_PROJECT_URL,
+      validationStatus: "valid"
     }).then(() => {
       let allResources = projecteditor.project.allResources();
       console.log("All resources have been loaded", allResources, allResources.map(r=>r.basename).join("|"));
     });
+
   });
 
 }, false);
 
 /**
  * Build a temporary directory as a workspace for this loader
  * https://developer.mozilla.org/en-US/Add-ons/Code_snippets/File_I_O
  */
--- a/browser/devtools/projecteditor/chrome/content/projecteditor.xul
+++ b/browser/devtools/projecteditor/chrome/content/projecteditor.xul
@@ -1,18 +1,16 @@
 <?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 <?xml-stylesheet href="chrome://browser/skin/devtools/light-theme.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/projecteditor/projecteditor.css" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/content/devtools/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/devtools/debugger.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/common.css" type="text/css"?>
-<?xml-stylesheet href="chrome://browser/skin/devtools/widgets.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/content/devtools/markup-view.css" type="text/css"?>
 <?xml-stylesheet href="chrome://browser/skin/devtools/markup-view.css" type="text/css"?>
 
 <?xul-overlay href="chrome://global/content/editMenuOverlay.xul"?>
 
 <!DOCTYPE window [
 <!ENTITY % scratchpadDTD SYSTEM "chrome://browser/locale/devtools/scratchpad.dtd" >
  %scratchpadDTD;
--- a/browser/devtools/projecteditor/lib/plugins/app-manager/plugin.js
+++ b/browser/devtools/projecteditor/lib/plugins/app-manager/plugin.js
@@ -1,15 +1,16 @@
 const { Cu } = require("chrome");
 const { Class } = require("sdk/core/heritage");
 const { EventTarget } = require("sdk/event/target");
 const { emit } = require("sdk/event/core");
 const promise = require("projecteditor/helpers/promise");
 var { registerPlugin, Plugin } = require("projecteditor/plugins/core");
 const { AppProjectEditor } = require("./app-project-editor");
+const OPTION_URL = "chrome://browser/skin/devtools/tool-options.svg";
 
 var AppManagerRenderer = Class({
   extends: Plugin,
 
   isAppManagerProject: function() {
     return !!this.host.project.appManagerOpts;
   },
   editorForResource: function(resource) {
@@ -20,28 +21,40 @@ var AppManagerRenderer = Class({
   onAnnotate: function(resource, editor, elt) {
     if (resource.parent || !this.isAppManagerProject()) {
       return;
     }
 
     let {appManagerOpts} = this.host.project;
     let doc = elt.ownerDocument;
     let image = doc.createElement("image");
-    let label = doc.createElement("label");
+    let optionImage = doc.createElement("image");
+    let flexElement = doc.createElement("div");
+    let nameLabel = doc.createElement("span");
+    let statusElement = doc.createElement("div");
 
-    label.className = "project-name-label";
     image.className = "project-image";
+    optionImage.className = "project-options";
+    nameLabel.className = "project-name-label";
+    statusElement.className = "project-status";
+    flexElement.className = "project-flex";
 
     let name = appManagerOpts.name || resource.basename;
     let url = appManagerOpts.iconUrl || "icon-sample.png";
+    let status = appManagerOpts.validationStatus || "unknown";
 
-    label.textContent = name;
+    nameLabel.textContent = name;
     image.setAttribute("src", url);
+    optionImage.setAttribute("src", OPTION_URL);
+    statusElement.setAttribute("status", status)
 
     elt.innerHTML = "";
     elt.appendChild(image);
-    elt.appendChild(label);
+    elt.appendChild(nameLabel);
+    elt.appendChild(flexElement);
+    elt.appendChild(statusElement);
+    elt.appendChild(optionImage);
     return true;
   }
 });
 
 exports.AppManagerRenderer = AppManagerRenderer;
 registerPlugin(AppManagerRenderer);
--- a/browser/devtools/projecteditor/lib/project.js
+++ b/browser/devtools/projecteditor/lib/project.js
@@ -131,23 +131,21 @@ var Project = Class({
     for (let [path, store] of this.localStores) {
       yield store;
     }
   },
 
   /**
    * Get every file path used inside of the project.
    *
-   * @returns generator-iterator<string>
+   * @returns Array<string>
    *          A list of all file paths
    */
-  allPaths: function*() {
-    for (let [path, store] of this.localStores) {
-      yield path;
-    }
+  allPaths: function() {
+    return [path for (path of this.localStores.keys())];
   },
 
   /**
    * Get the store that contains a path.
    *
    * @returns Store
    *          The store, if any.  Will return null if no store
    *          contains the given path.
--- a/browser/devtools/projecteditor/lib/projecteditor.js
+++ b/browser/devtools/projecteditor/lib/projecteditor.js
@@ -259,24 +259,39 @@ var ProjectEditor = Class({
 
   /**
    * Set the current project viewed by the projecteditor to a single path,
    * used by the app manager.
    *
    * @param string path
    *               The file path to set
    * @param Object opts
-   *               Custom options used by the project. See plugins/app-manager.
+   *               Custom options used by the project.
+   *                - name: display name for project
+   *                - iconUrl: path to icon for project
+   *                - validationStatus: one of 'unknown|error|warning|valid'
+   *                - projectOverviewURL: path to load for iframe when project
+   *                    is selected in the tree.
    * @param Promise
    *        Promise that is resolved once the project is ready to be used.
    */
   setProjectToAppPath: function(path, opts = {}) {
     this.project.appManagerOpts = opts;
-    this.project.removeAllStores();
-    this.project.addPath(path);
+
+    let existingPaths = this.project.allPaths();
+    if (existingPaths.length !== 1 || existingPaths[0] !== path) {
+      // Only fully reset if this is a new path.
+      this.project.removeAllStores();
+      this.project.addPath(path);
+    } else {
+      // Otherwise, just ask for the root to be redrawn
+      let rootResource = this.project.localStores.get(path).root;
+      emit(rootResource, "label-change", rootResource);
+    }
+
     return this.project.refresh();
   },
 
   /**
    * Open a resource in a particular shell.
    *
    * @param Resource resource
    *                 The file to be opened.
--- a/browser/devtools/projecteditor/lib/tree.js
+++ b/browser/devtools/projecteditor/lib/tree.js
@@ -34,17 +34,17 @@ var ResourceContainer = Class({
 
     let doc = tree.doc;
 
     this.elt = doc.createElementNS(HTML_NS, "li");
     this.elt.classList.add("child");
 
     this.line = doc.createElementNS(HTML_NS, "div");
     this.line.classList.add("child");
-    this.line.classList.add("side-menu-widget-item");
+    this.line.classList.add("entry");
     this.line.setAttribute("theme", "dark");
     this.line.setAttribute("tabindex", "0");
 
     this.elt.appendChild(this.line);
 
     this.highlighter = doc.createElementNS(HTML_NS, "span");
     this.highlighter.classList.add("highlighter");
     this.line.appendChild(this.highlighter);
@@ -218,25 +218,24 @@ var TreeView = Class({
     this.options = merge({
       resourceFormatter: function(resource, elt) {
         elt.textContent = resource.toString();
       }
     }, options);
     this.models = new Set();
     this.roots = new Set();
     this._containers = new Map();
-    this.elt = document.createElement("vbox");
+    this.elt = document.createElementNS(HTML_NS, "div");
     this.elt.tree = this;
-    this.elt.className = "side-menu-widget-container sources-tree";
+    this.elt.className = "sources-tree";
     this.elt.setAttribute("with-arrows", "true");
     this.elt.setAttribute("theme", "dark");
     this.elt.setAttribute("flex", "1");
 
     this.children = document.createElementNS(HTML_NS, "ul");
-    this.children.setAttribute("flex", "1");
     this.elt.appendChild(this.children);
 
     this.resourceChildrenChanged = this.resourceChildrenChanged.bind(this);
     this.updateResource = this.updateResource.bind(this);
   },
 
   destroy: function() {
     this._destroyed = true;
@@ -310,17 +309,17 @@ var TreeView = Class({
     this.roots.add(model.root);
     model.root.refresh().then(root => {
       if (this._destroyed || !this.models.has(model)) {
         // model may have been removed during the initial refresh.
         // In this case, do not import the resource or add to DOM, just leave it be.
         return;
       }
       let container = this.importResource(root);
-      container.line.classList.add("side-menu-widget-group-title");
+      container.line.classList.add("entry-group-title");
       container.line.setAttribute("theme", "dark");
       this.selectContainer(container);
 
       this.children.insertBefore(container.elt, placeholder);
       this.children.removeChild(placeholder);
     });
   },
 
--- a/browser/devtools/projecteditor/test/browser.ini
+++ b/browser/devtools/projecteditor/test/browser.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 skip-if = os == "win" && !debug # Bug 1014046
 subsuite = devtools
 support-files =
   head.js
   helper_homepage.html
 
+[browser_projecteditor_app_options.js]
 [browser_projecteditor_delete_file.js]
 [browser_projecteditor_editing_01.js]
 [browser_projecteditor_immediate_destroy.js]
 [browser_projecteditor_init.js]
 [browser_projecteditor_new_file.js]
 [browser_projecteditor_stores.js]
 [browser_projecteditor_tree_selection.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/projecteditor/test/browser_projecteditor_app_options.js
@@ -0,0 +1,102 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test that options can be changed without resetting the whole
+// editor.
+let test = asyncTest(function*() {
+
+  let TEMP_PATH = buildTempDirectoryStructure();
+  let projecteditor = yield addProjectEditorTab();
+
+  let resourceBeenAdded = promise.defer();
+  projecteditor.project.once("resource-added", () => {
+    info ("A resource has been added");
+    resourceBeenAdded.resolve();
+  });
+
+  info ("About to set project to: " + TEMP_PATH);
+  yield projecteditor.setProjectToAppPath(TEMP_PATH, {
+    name: "Test",
+    iconUrl: "chrome://browser/skin/devtools/tool-options.svg",
+    projectOverviewURL: SAMPLE_WEBAPP_URL
+  });
+
+  info ("Making sure a resource has been added before continuing");
+  yield resourceBeenAdded.promise;
+
+  info ("From now on, if a resource is added it should fail");
+  projecteditor.project.on("resource-added", failIfResourceAdded);
+
+  info ("Getting ahold and validating the project header DOM");
+  let header = projecteditor.document.querySelector(".entry-group-title");
+  let image = header.querySelector(".project-image");
+  let nameLabel = header.querySelector(".project-name-label");
+  let statusElement = header.querySelector(".project-status");
+  is (statusElement.getAttribute("status"), "unknown", "The status starts out as unknown.");
+  is (nameLabel.textContent, "Test", "The name label has been set correctly");
+  is (image.getAttribute("src"), "chrome://browser/skin/devtools/tool-options.svg", "The icon has been set correctly");
+
+  info ("About to set project with new options.");
+  yield projecteditor.setProjectToAppPath(TEMP_PATH, {
+    name: "Test2",
+    iconUrl: "chrome://browser/skin/devtools/tool-inspector.svg",
+    projectOverviewURL: SAMPLE_WEBAPP_URL,
+    validationStatus: "error"
+  });
+
+  ok (!nameLabel.parentNode, "The old elements have been removed");
+
+  info ("Getting ahold of and validating the project header DOM");
+  let image = header.querySelector(".project-image");
+  let nameLabel = header.querySelector(".project-name-label");
+  let statusElement = header.querySelector(".project-status");
+  is (statusElement.getAttribute("status"), "error", "The status has been set correctly.");
+  is (nameLabel.textContent, "Test2", "The name label has been set correctly");
+  is (image.getAttribute("src"), "chrome://browser/skin/devtools/tool-inspector.svg", "The icon has been set correctly");
+
+  info ("About to set project with new options.");
+  yield projecteditor.setProjectToAppPath(TEMP_PATH, {
+    name: "Test3",
+    iconUrl: "chrome://browser/skin/devtools/tool-webconsole.svg",
+    projectOverviewURL: SAMPLE_WEBAPP_URL,
+    validationStatus: "warning"
+  });
+
+  ok (!nameLabel.parentNode, "The old elements have been removed");
+
+  info ("Getting ahold of and validating the project header DOM");
+  let image = header.querySelector(".project-image");
+  let nameLabel = header.querySelector(".project-name-label");
+  let statusElement = header.querySelector(".project-status");
+  is (statusElement.getAttribute("status"), "warning", "The status has been set correctly.");
+  is (nameLabel.textContent, "Test3", "The name label has been set correctly");
+  is (image.getAttribute("src"), "chrome://browser/skin/devtools/tool-webconsole.svg", "The icon has been set correctly");
+
+  info ("About to set project with new options.");
+  yield projecteditor.setProjectToAppPath(TEMP_PATH, {
+    name: "Test4",
+    iconUrl: "chrome://browser/skin/devtools/tool-debugger.svg",
+    projectOverviewURL: SAMPLE_WEBAPP_URL,
+    validationStatus: "valid"
+  });
+
+  ok (!nameLabel.parentNode, "The old elements have been removed");
+
+  info ("Getting ahold of and validating the project header DOM");
+  let image = header.querySelector(".project-image");
+  let nameLabel = header.querySelector(".project-name-label");
+  let statusElement = header.querySelector(".project-status");
+  is (statusElement.getAttribute("status"), "valid", "The status has been set correctly.");
+  is (nameLabel.textContent, "Test4", "The name label has been set correctly");
+  is (image.getAttribute("src"), "chrome://browser/skin/devtools/tool-debugger.svg", "The icon has been set correctly");
+
+  info ("Test finished, cleaning up");
+  projecteditor.project.off("resource-added", failIfResourceAdded);
+});
+
+function failIfResourceAdded() {
+  ok (false, "A resource has been added, but it shouldn't have been");
+}
--- a/browser/devtools/projecteditor/test/browser_projecteditor_editing_01.js
+++ b/browser/devtools/projecteditor/test/browser_projecteditor_editing_01.js
@@ -2,17 +2,17 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test ProjectEditor basic functionality
 let test = asyncTest(function*() {
   let projecteditor = yield addProjectEditorTabForTempDirectory();
-  let TEMP_PATH = [...projecteditor.project.allPaths()][0];
+  let TEMP_PATH = projecteditor.project.allPaths()[0];
 
   is (getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
 
   ok (projecteditor.currentEditor, "There is an editor for projecteditor");
   let resources = projecteditor.project.allResources();
 
   resources.forEach((r, i) => {
     console.log("Resource detected", r.path, i);
--- a/browser/devtools/projecteditor/test/browser_projecteditor_stores.js
+++ b/browser/devtools/projecteditor/test/browser_projecteditor_stores.js
@@ -2,15 +2,15 @@
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test ProjectEditor basic functionality
 let test = asyncTest(function*() {
   let projecteditor = yield addProjectEditorTabForTempDirectory();
-  let TEMP_PATH = [...projecteditor.project.allPaths()][0];
+  let TEMP_PATH = projecteditor.project.allPaths()[0];
   is (getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
 
-  is ([...projecteditor.project.allPaths()].length, 1, "1 path is set");
+  is (projecteditor.project.allPaths().length, 1, "1 path is set");
   projecteditor.project.removeAllStores();
-  is ([...projecteditor.project.allPaths()].length, 0, "No paths are remaining");
+  is (projecteditor.project.allPaths().length, 0, "No paths are remaining");
 });
--- a/browser/devtools/projecteditor/test/browser_projecteditor_tree_selection.js
+++ b/browser/devtools/projecteditor/test/browser_projecteditor_tree_selection.js
@@ -3,17 +3,17 @@
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 // Test tree selection functionality
 
 let test = asyncTest(function*() {
   let projecteditor = yield addProjectEditorTabForTempDirectory();
-  let TEMP_PATH = [...projecteditor.project.allPaths()][0];
+  let TEMP_PATH = projecteditor.project.allPaths()[0];
 
   is (getTempFile("").path, TEMP_PATH, "Temp path is set correctly.");
 
   ok (projecteditor.currentEditor, "There is an editor for projecteditor");
   let resources = projecteditor.project.allResources();
 
   is (
     resources.map(r=>r.basename).join("|"),
--- a/browser/devtools/styleeditor/StyleEditorUI.jsm
+++ b/browser/devtools/styleeditor/StyleEditorUI.jsm
@@ -449,36 +449,33 @@ StyleEditorUI.prototype = {
 
         summary.addEventListener("focus", function onSummaryFocus(event) {
           if (event.target == summary) {
             // autofocus the stylesheet name
             summary.querySelector(".stylesheet-name").focus();
           }
         }, false);
 
-        Task.spawn(function* () {
-          // autofocus if it's a new user-created stylesheet
-          if (editor.isNew) {
-            yield this._selectEditor(editor);
-          }
+        // autofocus if it's a new user-created stylesheet
+        if (editor.isNew) {
+          this._selectEditor(editor);
+        }
 
-          if (this._styleSheetToSelect
-              && this._styleSheetToSelect.stylesheet == editor.styleSheet.href) {
-            yield this.switchToSelectedSheet();
-          }
+        if (this._styleSheetToSelect
+            && this._styleSheetToSelect.stylesheet == editor.styleSheet.href) {
+          this.switchToSelectedSheet();
+        }
 
-          // If this is the first stylesheet and there is no pending request to
-          // select a particular style sheet, select this sheet.
-          if (!this.selectedEditor && !this._styleSheetBoundToSelect
-              && editor.styleSheet.styleSheetIndex == 0) {
-            yield this._selectEditor(editor);
-          }
-
-          this.emit("editor-added", editor);
-        }.bind(this)).then(null, Cu.reportError);
+        // If this is the first stylesheet and there is no pending request to
+        // select a particular style sheet, select this sheet.
+        if (!this.selectedEditor && !this._styleSheetBoundToSelect
+            && editor.styleSheet.styleSheetIndex == 0) {
+          this._selectEditor(editor);
+        }
+        this.emit("editor-added", editor);
       }.bind(this),
 
       onShow: function(summary, details, data) {
         let editor = data.editor;
         this.selectedEditor = editor;
 
         Task.spawn(function* () {
           if (!editor.sourceEditor) {
@@ -707,60 +704,84 @@ StyleEditorUI.prototype = {
    * Update the @media rules sidebar for an editor. Hide if there are no rules
    * Display a list of the @media rules in the editor's associated style sheet.
    * Emits a 'media-list-changed' event after updating the UI.
    *
    * @param  {StyleSheetEditor} editor
    *         Editor to update @media sidebar of
    */
   _updateMediaList: function(editor) {
-    this.getEditorDetails(editor).then((details) => {
+    Task.spawn(function* () {
+      let details = yield this.getEditorDetails(editor);
       let list = details.querySelector(".stylesheet-media-list");
 
       while (list.firstChild) {
         list.removeChild(list.firstChild);
       }
 
       let rules = editor.mediaRules;
       let showSidebar = Services.prefs.getBoolPref(PREF_MEDIA_SIDEBAR);
       let sidebar = details.querySelector(".stylesheet-sidebar");
-      sidebar.hidden = !showSidebar || !rules.length;
+
+      let inSource = false;
 
       for (let rule of rules) {
+        let {line, column, parentStyleSheet} = rule;
+
+        let location = {
+          line: line,
+          column: column,
+          source: editor.styleSheet.href,
+          styleSheet: parentStyleSheet
+        };
+        if (editor.styleSheet.isOriginalSource) {
+          location = yield editor.cssSheet.getOriginalLocation(line, column);
+        }
+
+        // this @media rule is from a different original source
+        if (location.source != editor.styleSheet.href) {
+          continue;
+        }
+        inSource = true;
+
         let div = this._panelDoc.createElement("div");
         div.className = "media-rule-label";
-        div.addEventListener("click", this._jumpToMediaRule.bind(this, rule));
+        div.addEventListener("click", this._jumpToLocation.bind(this, location));
 
         let cond = this._panelDoc.createElement("div");
         cond.textContent = rule.conditionText;
         cond.className = "media-rule-condition"
         if (!rule.matches) {
           cond.classList.add("media-condition-unmatched");
         }
         div.appendChild(cond);
 
-        let line = this._panelDoc.createElement("div");
-        line.className = "media-rule-line theme-link";
-        line.textContent = ":" + rule.line;
-        div.appendChild(line);
+        let link = this._panelDoc.createElement("div");
+        link.className = "media-rule-line theme-link";
+        link.textContent = ":" + location.line;
+        div.appendChild(link);
 
         list.appendChild(div);
       }
+
+      sidebar.hidden = !showSidebar || !inSource;
+
       this.emit("media-list-changed", editor);
-    });
+    }.bind(this)).then(null, Cu.reportError);
   },
 
   /**
    * Jump cursor to the editor for a stylesheet and line number for a rule.
    *
-   * @param  {MediaRuleFront} rule
-   *         Rule to jump to.
+   * @param  {object} location
+   *         Location object with 'line', 'column', and 'source' properties.
    */
-  _jumpToMediaRule: function(rule) {
-    this.selectStyleSheet(rule.parentStyleSheet, rule.line - 1, rule.column - 1);
+  _jumpToLocation: function(location) {
+    let source = location.styleSheet || location.source;
+    this.selectStyleSheet(source, location.line - 1, location.column - 1);
   },
 
   destroy: function() {
     this._clearStyleSheetEditors();
 
     this._optionsMenu.removeEventListener("popupshowing",
                                           this._updateOptionsMenu);
 
--- a/browser/devtools/styleeditor/StyleSheetEditor.jsm
+++ b/browser/devtools/styleeditor/StyleSheetEditor.jsm
@@ -91,31 +91,24 @@ function StyleSheetEditor(styleSheet, wi
 
   this._onPropertyChange = this._onPropertyChange.bind(this);
   this._onError = this._onError.bind(this);
   this._onMediaRuleMatchesChange = this._onMediaRuleMatchesChange.bind(this);
   this._onMediaRulesChanged = this._onMediaRulesChanged.bind(this)
   this.checkLinkedFileForChanges = this.checkLinkedFileForChanges.bind(this);
   this.markLinkedFileBroken = this.markLinkedFileBroken.bind(this);
 
+  this._focusOnSourceEditorReady = false;
+  this.cssSheet.on("property-change", this._onPropertyChange);
+  this.styleSheet.on("error", this._onError);
   this.mediaRules = [];
-  if (this.styleSheet.getMediaRules) {
-    this.styleSheet.getMediaRules().then(this._onMediaRulesChanged);
+  if (this.cssSheet.getMediaRules) {
+    this.cssSheet.getMediaRules().then(this._onMediaRulesChanged);
   }
-  this.styleSheet.on("media-rules-changed", this._onMediaRulesChanged);
-
-  this._focusOnSourceEditorReady = false;
-
-  let relatedSheet = this.styleSheet.relatedStyleSheet;
-  if (relatedSheet) {
-    relatedSheet.on("property-change", this._onPropertyChange);
-  }
-  this.styleSheet.on("property-change", this._onPropertyChange);
-  this.styleSheet.on("error", this._onError);
-
+  this.cssSheet.on("media-rules-changed", this._onMediaRulesChanged);
   this.savedFile = file;
   this.linkCSSFile();
 }
 
 StyleSheetEditor.prototype = {
   /**
    * Whether there are unsaved changes in the editor
    */
@@ -126,16 +119,27 @@ StyleSheetEditor.prototype = {
   /**
    * Whether the editor is for a stylesheet created by the user
    * through the style editor UI.
    */
   get isNew() {
     return this._isNew;
   },
 
+  /**
+   * The style sheet or the generated style sheet for this source if it's an
+   * original source.
+   */
+  get cssSheet() {
+    if (this.styleSheet.isOriginalSource) {
+      return this.styleSheet.relatedStyleSheet;
+    }
+    return this.styleSheet;
+  },
+
   get savedFile() {
     return this._savedFile;
   },
 
   set savedFile(name) {
     this._savedFile = name;
 
     this.linkCSSFile();
@@ -525,17 +529,19 @@ StyleSheetEditor.prototype = {
 
   /**
    * Called when this source has been successfully saved to disk.
    */
   onFileSaved: function(returnFile) {
     this._friendlyName = null;
     this.savedFile = returnFile;
 
-    this.sourceEditor.setClean();
+    if (this.sourceEditor) {
+      this.sourceEditor.setClean();
+    }
 
     this.emit("property-change");
 
     // TODO: replace with file watching
     this._modCheckCount = 0;
     this._window.clearTimeout(this._timeout);
 
     if (this.linkedCSSFile && !this.linkedCSSFileError) {
@@ -623,18 +629,18 @@ StyleSheetEditor.prototype = {
 
   /**
    * Clean up for this editor.
    */
   destroy: function() {
     if (this.sourceEditor) {
       this.sourceEditor.destroy();
     }
-    this.styleSheet.off("media-rules-changed", this._onMediaRulesChanged);
-    this.styleSheet.off("property-change", this._onPropertyChange);
+    this.cssSheet.off("property-change", this._onPropertyChange);
+    this.cssSheet.off("media-rules-changed", this._onMediaRulesChanged);
     this.styleSheet.off("error", this._onError);
   }
 }
 
 
 const TAB_CHARS = "\t";
 
 const CURRENT_OS = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
--- a/browser/devtools/styleeditor/test/browser.ini
+++ b/browser/devtools/styleeditor/test/browser.ini
@@ -11,31 +11,35 @@ support-files =
   import2.css
   inline-1.html
   inline-2.html
   longload.html
   media-small.css
   media.html
   media-rules.html
   media-rules.css
+  media-rules-sourcemaps.html
   minified.html
   nostyle.html
   pretty.css
   resources_inpage.jsi
   resources_inpage1.css
   resources_inpage2.css
   simple.css
   simple.css.gz
   simple.css.gz^headers^
   simple.gz.html
   simple.html
   sourcemap-css/contained.css
   sourcemap-css/sourcemaps.css
   sourcemap-css/sourcemaps.css.map
+  sourcemap-css/media-rules.css
+  sourcemap-css/media-rules.css.map
   sourcemap-sass/sourcemaps.scss
+  sourcemap-sass/media-rules.scss
   sourcemaps.html
   test_private.css
   test_private.html
 
 [browser_styleeditor_autocomplete.js]
 [browser_styleeditor_bug_740541_iframes.js]
 skip-if = os == "linux" || "mac" # bug 949355
 [browser_styleeditor_bug_851132_middle_click.js]
@@ -44,16 +48,17 @@ skip-if = os == "linux" || "mac" # bug 9
 [browser_styleeditor_enabled.js]
 [browser_styleeditor_filesave.js]
 [browser_styleeditor_import.js]
 [browser_styleeditor_import_rule.js]
 [browser_styleeditor_init.js]
 [browser_styleeditor_inline_friendly_names.js]
 [browser_styleeditor_loading.js]
 [browser_styleeditor_media_sidebar.js]
+[browser_styleeditor_media_sidebar_sourcemaps.js]
 [browser_styleeditor_new.js]
 [browser_styleeditor_nostyle.js]
 [browser_styleeditor_pretty.js]
 [browser_styleeditor_private_perwindowpb.js]
 [browser_styleeditor_reload.js]
 [browser_styleeditor_sv_keynav.js]
 [browser_styleeditor_sv_resize.js]
 [browser_styleeditor_selectstylesheet.js]
--- a/browser/devtools/styleeditor/test/browser_styleeditor_init.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_init.js
@@ -15,17 +15,17 @@ function test()
   content.location = TESTCASE_URI;
 }
 
 let gEditorAddedCount = 0;
 function testEditorAdded(aEditor)
 {
   if (aEditor.styleSheet.styleSheetIndex == 0) {
     gEditorAddedCount++;
-    testFirstStyleSheetEditor(aEditor);
+    gUI.editors[0].getSourceEditor().then(testFirstStyleSheetEditor);
   }
   if (aEditor.styleSheet.styleSheetIndex == 1) {
     gEditorAddedCount++;
     testSecondStyleSheetEditor(aEditor);
   }
 
   if (gEditorAddedCount == 2) {
     gUI = null;
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_media_sidebar_sourcemaps.js
@@ -0,0 +1,72 @@
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+// https rather than chrome to improve coverage
+const TESTCASE_URI = TEST_BASE_HTTPS + "media-rules-sourcemaps.html";
+const MEDIA_PREF = "devtools.styleeditor.showMediaSidebar";
+const MAP_PREF = "devtools.styleeditor.source-maps-enabled";
+
+const LABELS = ["screen and (max-width: 320px)",
+                "screen and (min-width: 1200px)"];
+const LINE_NOS = [4, 4];
+
+waitForExplicitFinish();
+
+let test = asyncTest(function*() {
+  Services.prefs.setBoolPref(MEDIA_PREF, true);
+  Services.prefs.setBoolPref(MAP_PREF, true);
+
+  let {UI} = yield addTabAndOpenStyleEditors(2, null, TESTCASE_URI);
+
+  yield listenForMediaChange(UI);
+
+  is(UI.editors.length, 1, "correct number of editors");
+
+  // Test editor with @media rules
+  let mediaEditor = UI.editors[0];
+  yield openEditor(mediaEditor);
+  testMediaEditor(mediaEditor);
+
+  Services.prefs.clearUserPref(MEDIA_PREF);
+  Services.prefs.clearUserPref(MAP_PREF);
+});
+
+function testMediaEditor(editor) {
+  let sidebar = editor.details.querySelector(".stylesheet-sidebar");
+  is(sidebar.hidden, false, "sidebar is showing on editor with @media");
+
+  let entries = [...sidebar.querySelectorAll(".media-rule-label")];
+  is(entries.length, 2, "two @media rules displayed in sidebar");
+
+  testRule(entries[0], LABELS[0], LINE_NOS[0]);
+  testRule(entries[1], LABELS[1], LINE_NOS[1]);
+}
+
+function testRule(rule, text, lineno) {
+  let cond = rule.querySelector(".media-rule-condition");
+  is(cond.textContent, text, "media label is correct for " + text);
+
+  let line = rule.querySelector(".media-rule-line");
+  is(line.textContent, ":" + lineno, "correct line number shown");
+}
+
+/* Helpers */
+
+function openEditor(editor) {
+  getLinkFor(editor).click();
+
+  return editor.getSourceEditor();
+}
+
+function listenForMediaChange(UI) {
+  let deferred = promise.defer();
+  UI.once("media-list-changed", () => {
+    deferred.resolve();
+  })
+  return deferred.promise;
+}
+
+function getLinkFor(editor) {
+  return editor.summary.querySelector(".stylesheet-name");
+}
--- a/browser/devtools/styleeditor/test/browser_styleeditor_reload.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_reload.js
@@ -22,31 +22,35 @@ function test()
   });
 
   content.location = TESTCASE_URI;
 }
 
 function runTests()
 {
   let count = 0;
-  gUI.once("editor-selected", (event, editor) => {
+  gUI.on("editor-selected", function editorSelected(event, editor) {
+    if (editor.styleSheet != gUI.editors[1].styleSheet) {
+      return;
+    }
+    gUI.off("editor-selected", editorSelected);
     editor.getSourceEditor().then(() => {
       info("selected second editor, about to reload page");
       reloadPage();
 
       gUI.on("editor-added", function editorAdded(event, editor) {
         if (++count == 2) {
           info("all editors added after reload");
           gUI.off("editor-added", editorAdded);
           gUI.editors[1].getSourceEditor().then(testRemembered);
         }
       })
     });
   });
-  gUI.selectStyleSheet(gUI.editors[1].styleSheet.href, LINE_NO, COL_NO);
+  gUI.selectStyleSheet(gUI.editors[1].styleSheet, LINE_NO, COL_NO);
 }
 
 function testRemembered()
 {
   is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
 
   let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
   is(line, LINE_NO, "correct line selected");
--- a/browser/devtools/styleeditor/test/browser_styleeditor_selectstylesheet.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_selectstylesheet.js
@@ -25,17 +25,22 @@ function test()
 }
 
 function runTests()
 {
   let count = 0;
 
   // Make sure Editor doesn't go into an infinite loop when
   // column isn't passed. See bug 941018.
-  gUI.once("editor-selected", (event, editor) => {
+  gUI.on("editor-selected", function editorSelected(event, editor) {
+    if (editor.styleSheet != gUI.editors[1].styleSheet) {
+      return;
+    }
+    gUI.off("editor-selected", editorSelected);
+
     editor.getSourceEditor().then(() => {
       is(gUI.selectedEditor, gUI.editors[1], "second editor is selected");
       let {line, ch} = gUI.selectedEditor.sourceEditor.getCursor();
 
       is(line, LINE_NO, "correct line selected");
       is(ch, COL_NO, "correct column selected");
 
       gUI = null;
--- a/browser/devtools/styleeditor/test/browser_styleeditor_sourcemaps.js
+++ b/browser/devtools/styleeditor/test/browser_styleeditor_sourcemaps.js
@@ -99,19 +99,23 @@ function togglePref(UI) {
   let deferred = promise.defer();
   let count = 0;
 
   UI.on("editor-added", (event, editor) => {
     if (++count == 3) {
       deferred.resolve();
     }
   })
+  let editorsPromise = deferred.promise;
+
+  let selectedPromise = UI.once("editor-selected");
 
   Services.prefs.setBoolPref(PREF, false);
-  return deferred.promise;
+
+  return promise.all([editorsPromise, selectedPromise]);
 }
 
 function openEditor(editor) {
   getLinkFor(editor).click();
 
   return editor.getSourceEditor();
 }
 
--- a/browser/devtools/styleeditor/test/head.js
+++ b/browser/devtools/styleeditor/test/head.js
@@ -40,19 +40,20 @@ function cleanup()
     gBrowser.removeCurrentTab();
   }
 }
 
 function addTabAndOpenStyleEditors(count, callback, uri) {
   let deferred = promise.defer();
   let currentCount = 0;
   let panel;
-  addTabAndCheckOnStyleEditorAdded(p => panel = p, function () {
+  addTabAndCheckOnStyleEditorAdded(p => panel = p, function (editor) {
     currentCount++;
-    info(currentCount + " of " + count + " editors opened");
+    info(currentCount + " of " + count + " editors opened: "
+         + editor.styleSheet.href);
     if (currentCount == count) {
       if (callback) {
         callback(panel);
       }
       deferred.resolve(panel);
     }
   });
 
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/media-rules-sourcemaps.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html>
+<head>
+  <meta charset="UTF-8">
+  <link rel="stylesheet" type="text/css" href="sourcemap-css/media-rules.css"
+</head>
+<body>
+  <div>
+    Testing style editor media sidebar with source maps
+  </div>
+</body>
+</html>
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/sourcemap-css/media-rules.css
@@ -0,0 +1,8 @@
+@media screen and (max-width: 320px) {
+  div {
+    width: 100px; } }
+@media screen and (min-width: 1200px) {
+  div {
+    width: 400px; } }
+
+/*# sourceMappingURL=media-rules.css.map */
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/sourcemap-css/media-rules.css.map
@@ -0,0 +1,6 @@
+{
+"version": 3,
+"mappings": "AAIE,oCAA4C;EAD9C,GAAI;IAEA,KAAK,EAAE,KAAK;AAEd,qCAA4C;EAJ9C,GAAI;IAKA,KAAK,EAAE,KAAK",
+"sources": ["../sourcemap-sass/media-rules.scss"],
+"file": "media-rules.css"
+}
new file mode 100644
--- /dev/null
+++ b/browser/devtools/styleeditor/test/sourcemap-sass/media-rules.scss
@@ -0,0 +1,11 @@
+$break-small: 320px;
+$break-large: 1200px;
+
+div {
+  @media screen and (max-width: $break-small) {
+    width: 100px;
+  }
+  @media screen and (min-width: $break-large) {
+    width: 400px;
+  }
+}
--- a/browser/devtools/webconsole/console-output.js
+++ b/browser/devtools/webconsole/console-output.js
@@ -8,16 +8,17 @@
 const {Cc, Ci, Cu} = require("chrome");
 
 loader.lazyImporter(this, "VariablesView", "resource:///modules/devtools/VariablesView.jsm");
 loader.lazyImporter(this, "escapeHTML", "resource:///modules/devtools/VariablesView.jsm");
 loader.lazyImporter(this, "gDevTools", "resource:///modules/devtools/gDevTools.jsm");
 loader.lazyImporter(this, "Task","resource://gre/modules/Task.jsm");
 
 const Heritage = require("sdk/core/heritage");
+const URI = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
 const XHTML_NS = "http://www.w3.org/1999/xhtml";
 const XUL_NS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 const STRINGS_URI = "chrome://browser/locale/devtools/webconsole.properties";
 
 const WebConsoleUtils = require("devtools/toolkit/webconsole/utils").Utils;
 const l10n = new WebConsoleUtils.l10n(STRINGS_URI);
 
 // Constants for compatibility with the Web Console output implementation before
@@ -1076,16 +1077,21 @@ Messages.Extended.prototype = Heritage.e
       if (grip.type == "longString") {
         let widget = new Widgets.LongString(this, grip, options).render();
         return widget.element;
       }
     }
 
     let result = this.document.createElementNS(XHTML_NS, "span");
     if (isPrimitive) {
+      if (Widgets.URLString.prototype.containsURL.call(Widgets.URLString.prototype, grip)) {
+        let widget = new Widgets.URLString(this, grip, options).render();
+        return widget.element;
+      }
+
       let className = this.getClassNameForValueGrip(grip);
       if (className) {
         result.className = className;
       }
 
       result.textContent = VariablesView.getString(grip, {
         noStringQuotes: noStringQuotes,
         concise: options.concise,
@@ -1753,16 +1759,135 @@ Widgets.MessageTimestamp.prototype = Her
     this.element.textContent = l10n.timestampString(this.timestamp) + " ";
 
     return this;
   },
 }); // Widgets.MessageTimestamp.prototype
 
 
 /**
+ * The URLString widget, for rendering strings where at least one token is a
+ * URL.
+ *
+ * @constructor
+ * @param object message
+ *        The owning message.
+ * @param string str
+ *        The string, which contains at least one valid URL.
+ */
+Widgets.URLString = function(message, str)
+{
+  Widgets.BaseWidget.call(this, message);
+  this.str = str;
+};
+
+Widgets.URLString.prototype = Heritage.extend(Widgets.BaseWidget.prototype,
+{
+  /**
+   * The string to format, which contains at least one valid URL.
+   * @type string
+   */
+  str: "",
+
+  render: function()
+  {
+    if (this.element) {
+      return this;
+    }
+
+    // The rendered URLString will be a <span> containing a number of text
+    // <spans> for non-URL tokens and <a>'s for URL tokens.
+    this.element = this.el("span", {
+      class: "console-string"
+    });
+    this.element.appendChild(this._renderText("\""));
+
+    // As we walk through the tokens of the source string, we make sure to preserve
+    // the original whitespace that seperated the tokens.
+    let tokens = this.str.split(/\s+/);
+    let textStart = 0;
+    let tokenStart;
+    for (let token of tokens) {
+      tokenStart = this.str.indexOf(token, textStart);
+      if (this._isURL(token)) {
+        this.element.appendChild(this._renderText(this.str.slice(textStart, tokenStart)));
+        textStart = tokenStart + token.length;
+        this.element.appendChild(this._renderURL(token));
+      }
+    }
+
+    // Clean up any non-URL text at the end of the source string.
+    this.element.appendChild(this._renderText(this.str.slice(textStart, this.str.length)));
+    this.element.appendChild(this._renderText("\""));
+
+    return this;
+  },
+
+  /**
+   * Determines whether a grip is a string containing a URL.
+   *
+   * @param string grip
+   *        The grip, which may contain a URL.
+   * @return boolean
+   *         Whether the grip is a string containing a URL.
+   */
+  containsURL: function(grip)
+  {
+    if (typeof grip != "string") {
+      return false;
+    }
+
+    let tokens = grip.split(/\s+/);
+    return tokens.some(this._isURL);
+  },
+
+  /**
+   * Determines whether a string token is a valid URL.
+   *
+   * @param string token
+   *        The token.
+   * @return boolean
+   *         Whenther the token is a URL.
+   */
+  _isURL: function(token) {
+    try {
+      let uri = URI.newURI(token, null, null);
+      let url = uri.QueryInterface(Ci.nsIURL);
+      return true;
+    } catch (e) {
+      return false;
+    }
+  },
+
+  /**
+   * Renders a string as a URL.
+   *
+   * @param string url
+   *        The string to be rendered as a url.
+   * @return DOMElement
+   *         An element containing the rendered string.
+   */
+  _renderURL: function(url)
+  {
+    let result = this.el("a", {
+      class: "url",
+      title: url,
+      href: url,
+      draggable: false
+    }, url);
+    this.message._addLinkCallback(result);
+    return result;
+  },
+
+  _renderText: function(text) {
+    return this.el("span", text);
+  },
+}); // Widgets.URLString.prototype
+
+/**
  * Widget used for displaying ObjectActors that have no specialised renderers.
  *
  * @constructor
  * @param object message
  *        The owning message.
  * @param object objectActor
  *        The ObjectActor to display.
  * @param object [options]
--- a/browser/devtools/webconsole/test/browser.ini
+++ b/browser/devtools/webconsole/test/browser.ini
@@ -240,16 +240,17 @@ run-if = os == "mac"
 [browser_webconsole_bug_837351_securityerrors.js]
 [browser_webconsole_bug_846918_hsts_invalid-headers.js]
 [browser_webconsole_bug_915141_toggle_response_logging_with_keyboard.js]
 [browser_webconsole_bug_1006027_message_timestamps_incorrect.js]
 [browser_webconsole_bug_1010953_cspro.js]
 [browser_webconsole_cached_autocomplete.js]
 [browser_webconsole_change_font_size.js]
 [browser_webconsole_chrome.js]
+[browser_webconsole_clickable_urls.js]
 [browser_webconsole_closure_inspection.js]
 [browser_webconsole_completion.js]
 [browser_webconsole_console_extras.js]
 [browser_webconsole_console_logging_api.js]
 [browser_webconsole_count.js]
 [browser_webconsole_dont_navigate_on_doubleclick.js]
 [browser_webconsole_execution_scope.js]
 [browser_webconsole_for_of.js]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/webconsole/test/browser_webconsole_clickable_urls.js
@@ -0,0 +1,83 @@
+/*
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// When strings containing URLs are entered into the webconsole,
+// check its output and ensure that the output can be clicked to open those URLs.
+
+const TEST_URI = "data:text/html;charset=utf8,Bug 1005909 - Clickable URLS";
+
+let inputTests = [
+
+  // 0: URL opens page when clicked.
+  {
+    input: "'http://example.com'",
+    output: "http://example.com",
+    expectedTab: "http://example.com/",
+  },
+
+  // 1: URL opens page using https when clicked.
+  {
+    input: "'https://example.com'",
+    output: "https://example.com",
+    expectedTab: "https://example.com/",
+  },
+
+  // 2: URL with port opens page when clicked.
+  {
+    input: "'https://example.com:443'",
+    output: "https://example.com:443",
+    expectedTab: "https://example.com/",
+  },
+
+  // 3: URL containing non-empty path opens page when clicked.
+  {
+    input: "'http://example.com/foo'",
+    output: "http://example.com/foo",
+    expectedTab: "http://example.com/foo",
+  },
+
+  // 4: URL opens page when clicked, even when surrounded by non-URL tokens.
+  {
+  	input: "'foo http://example.com bar'",
+  	output: "foo http://example.com bar",
+  	expectedTab: "http://example.com/",
+  },
+
+  // 5: URL opens page when clicked, and whitespace is be preserved.
+  {
+  	input: "'foo\\nhttp://example.com\\nbar'",
+  	output: "foo\nhttp://example.com\nbar",
+  	expectedTab: "http://example.com/",
+  },
+
+  // 6: URL opens page when clicked when multiple links are present.
+  {
+  	input: "'http://example.com http://example.com'",
+  	output: "http://example.com http://example.com",
+  	expectedTab: "http://example.com/",
+  },
+
+  // 7: URL without scheme does not open page when clicked.
+  {
+    input: "'example.com'",
+    output: "example.com",
+  },
+
+  // 8: URL with invalid scheme does not open page when clicked.
+  {
+    input: "'foo://example.com'",
+    output: "foo://example.com",
+  },
+
+];
+
+function test() {
+  Task.spawn(function*() {
+    let {tab} = yield loadTab(TEST_URI);
+    let hud = yield openConsole(tab);
+    yield checkOutputForInputs(hud, inputTests);
+    inputTests = null;
+  }).then(finishTest);
+}
--- a/browser/devtools/webconsole/test/browser_webconsole_output_03.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_03.js
@@ -3,16 +3,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Test the webconsole output for various types of objects.
 
 const TEST_URI = "http://example.com/browser/browser/devtools/webconsole/test/test-console-output-03.html";
 
 let inputTests = [
+
   // 0
   {
     input: "document",
     output: "HTMLDocument \u2192 " + TEST_URI,
     printOutput: "[object HTMLDocument]",
     inspectable: true,
     noClick: true,
   },
@@ -52,16 +53,17 @@ let inputTests = [
     inspectable: true,
     variablesViewLabel: "DOMTokenList[0]",
   },
 
   // 5
   {
     input: "window.location.href",
     output: '"' + TEST_URI + '"',
+    noClick: true,
   },
 
   // 6
   {
     input: "window.location",
     output: "Location \u2192 " + TEST_URI,
     printOutput: TEST_URI,
     inspectable: true,
--- a/browser/devtools/webconsole/test/browser_webconsole_output_04.js
+++ b/browser/devtools/webconsole/test/browser_webconsole_output_04.js
@@ -105,16 +105,15 @@ let inputTests = [
     output: 'CSSMediaRule "print"',
     printOutput: "[object CSSMediaRule",
     inspectable: true,
     variablesViewLabel: "CSSMediaRule",
   },
 ];
 
 function test() {
-  addTab(TEST_URI);
-  browser.addEventListener("load", function onLoad() {
-    browser.removeEventListener("load", onLoad, true);
-    openConsole().then((hud) => {
-      return checkOutputForInputs(hud, inputTests);
-    }).then(finishTest);
-  }, true);
+  Task.spawn(function*() {
+    const {tab} = yield loadTab(TEST_URI);
+    const hud = yield openConsole(tab);
+    yield checkOutputForInputs(hud, inputTests);
+    inputTests = null;
+  }).then(finishTest);
 }
--- a/browser/devtools/webconsole/test/head.js
+++ b/browser/devtools/webconsole/test/head.js
@@ -79,16 +79,42 @@ function loadTab(url) {
   browser.addEventListener("load", function onLoad() {
     browser.removeEventListener("load", onLoad, true);
     deferred.resolve({tab: tab, browser: browser});
   }, true);
 
   return deferred.promise;
 }
 
+function loadBrowser(browser) {
+  let deferred = promise.defer();
+
+  browser.addEventListener("load", function onLoad() {
+    browser.removeEventListener("load", onLoad, true);
+    deferred.resolve(null)
+  }, true);
+
+  return deferred.promise;
+}
+
+function closeTab(tab) {
+  let deferred = promise.defer();
+
+  let container = gBrowser.tabContainer;
+
+  container.addEventListener("TabClose", function onTabClose() {
+    container.removeEventListener("TabClose", onTabClose, true);
+    deferred.resolve(null);
+  }, true);
+
+  gBrowser.removeTab(tab);
+
+  return deferred.promise;
+}
+
 function afterAllTabsLoaded(callback, win) {
   win = win || window;
 
   let stillToLoad = 0;
 
   function onLoad() {
     this.removeEventListener("load", onLoad, true);
     stillToLoad--;
@@ -1377,31 +1403,32 @@ function whenDelayedStartupFinished(aWin
  *
  *        - variablesViewLabel: string|RegExp, optional, the expected variables
  *        view label when the object is inspected. If this is not provided, then
  *        |output| is used.
  *
  *        - inspectorIcon: boolean, when true, the test runner expects the
  *        result widget to contain an inspectorIcon element (className
  *        open-inspector).
+ *
+ *        - expectedTab: string, optional, the full URL of the new tab which must
+ *        open. If this is not provided, any new tabs that open will cause a test
+ *        failure.
  */
 function checkOutputForInputs(hud, inputTests)
 {
-  let eventHandlers = new Set();
+  let container = gBrowser.tabContainer;
 
   function* runner()
   {
     for (let [i, entry] of inputTests.entries()) {
       info("checkInput(" + i + "): " + entry.input);
       yield checkInput(entry);
     }
-
-    for (let fn of eventHandlers) {
-      hud.jsterm.off("variablesview-open", fn);
-    }
+    container = null;
   }
 
   function* checkInput(entry)
   {
     yield checkConsoleLog(entry);
     yield checkPrintOutput(entry);
     yield checkJSEval(entry);
   }
@@ -1462,37 +1489,49 @@ function checkOutputForInputs(hud, input
     if (!entry.noClick) {
       yield checkObjectClick(entry, msg);
     }
     if (typeof entry.inspectorIcon == "boolean") {
       yield checkLinkToInspector(entry, msg);
     }
   }
 
-  function checkObjectClick(entry, msg)
+  function* checkObjectClick(entry, msg)
   {
     let body = msg.querySelector(".message-body a") ||
                msg.querySelector(".message-body");
     ok(body, "the message body");
 
-    let deferred = promise.defer();
+    let deferredVariablesView = promise.defer();
+    entry._onVariablesViewOpen = onVariablesViewOpen.bind(null, entry, deferredVariablesView);
+    hud.jsterm.on("variablesview-open", entry._onVariablesViewOpen);
 
-    entry._onVariablesViewOpen = onVariablesViewOpen.bind(null, entry, deferred);
-    hud.jsterm.on("variablesview-open", entry._onVariablesViewOpen);
-    eventHandlers.add(entry._onVariablesViewOpen);
+    let deferredTab = promise.defer();
+    entry._onTabOpen = onTabOpen.bind(null, entry, deferredTab);
+    container.addEventListener("TabOpen", entry._onTabOpen, true);
 
     body.scrollIntoView();
     EventUtils.synthesizeMouse(body, 2, 2, {}, hud.iframeWindow);
 
     if (entry.inspectable) {
       info("message body tagName '" + body.tagName +  "' className '" + body.className + "'");
-      return deferred.promise; // wait for the panel to open if we need to.
+      yield deferredVariablesView.promise;
+    } else {
+      hud.jsterm.off("variablesview-open", entry._onVariablesView);
+      entry._onVariablesView = null;
     }
 
-    return promise.resolve(null);
+    if (entry.expectedTab) {
+      yield deferredTab.promise;
+    } else {
+      container.removeEventListener("TabOpen", entry._onTabOpen, true);
+      entry._onTabOpen = null;
+    }
+
+    yield promise.resolve(null);
   }
 
   function checkLinkToInspector(entry, msg)
   {
     let elementNodeWidget = [...msg._messageObject.widgets][0];
     if (!elementNodeWidget) {
       ok(!entry.inspectorIcon, "The message has no ElementNode widget");
       return;
@@ -1508,29 +1547,42 @@ function checkOutputForInputs(hud, input
           "The ElementNode widget isn't linked to the inspector");
       }
     }, () => {
       // linkToInspector promise rejected, node not linked to inspector
       ok(!entry.inspectorIcon, "The ElementNode widget isn't linked to the inspector");
     });
   }
 
-  function onVariablesViewOpen(entry, deferred, event, view, options)
+  function onVariablesViewOpen(entry, {resolve, reject}, event, view, options)
   {
     let label = entry.variablesViewLabel || entry.output;
     if (typeof label == "string" && options.label != label) {
       return;
     }
     if (label instanceof RegExp && !label.test(options.label)) {
       return;
     }
 
     hud.jsterm.off("variablesview-open", entry._onVariablesViewOpen);
-    eventHandlers.delete(entry._onVariablesViewOpen);
     entry._onVariablesViewOpen = null;
-
     ok(entry.inspectable, "variables view was shown");
 
-    deferred.resolve(null);
+    resolve(null);
+  }
+
+  function onTabOpen(entry, {resolve, reject}, event)
+  {
+    container.removeEventListener("TabOpen", entry._onTabOpen, true);
+    entry._onTabOpen = null;
+
+    let tab = event.target;
+    let browser = gBrowser.getBrowserForTab(tab);
+    loadBrowser(browser).then(() => {
+      let uri = content.location.href;
+      ok(entry.expectedTab && entry.expectedTab == uri,
+        "opened tab '" + uri +  "', expected tab '" + entry.expectedTab + "'");
+      return closeTab(tab);
+    }).then(resolve, reject);
   }
 
   return Task.spawn(runner);
 }
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -152,22 +152,24 @@ let UI = {
     this.hidePanels();
     document.querySelector("window").classList.add("busy")
     this.updateCommands();
   },
 
   unbusy: function() {
     document.querySelector("window").classList.remove("busy")
     this.updateCommands();
+    this._busyPromise = null;
   },
 
   busyUntil: function(promise, operationDescription) {
     // Freeze the UI until the promise is resolved. A 30s timeout
     // will unfreeze the UI, just in case the promise never gets
     // resolved.
+    this._busyPromise = promise;
     let timeout = setTimeout(() => {
       this.unbusy();
       UI.reportError("error_operationTimeout", operationDescription);
     }, 30000);
     this.busy();
     promise.then(() => {
       clearTimeout(timeout);
       this.unbusy();
@@ -611,21 +613,20 @@ let Cmds = {
       let projects = AppProjects.store.object.projects;
       for (let i = 0; i < projects.length; i++) {
         let project = projects[i];
         let panelItemNode = document.createElement("toolbarbutton");
         panelItemNode.className = "panel-item";
         projectsNode.appendChild(panelItemNode);
         panelItemNode.setAttribute("label", project.name || AppManager.DEFAULT_PROJECT_NAME);
         panelItemNode.setAttribute("image", project.icon || AppManager.DEFAULT_PROJECT_ICON);
-        if (!project.validationStatus) {
-          // The result of the validation process (storing names, icons, …) has never been
-          // stored in the IndexedDB database. This happens when the project has been created
-          // from the old app manager. We need to run the validation again and update the name
-          // and icon of the app
+        if (!project.name || !project.icon) {
+          // The result of the validation process (storing names, icons, …) is not stored in
+          // the IndexedDB database when App Manager v1 is used.
+          // We need to run the validation again and update the name and icon of the app.
           AppManager.validateProject(project).then(() => {
             panelItemNode.setAttribute("label", project.name);
             panelItemNode.setAttribute("image", project.icon);
           });
         }
         panelItemNode.addEventListener("click", () => {
           UI.hidePanels();
           AppManager.selectedProject = project;
--- a/browser/devtools/webide/test/head.js
+++ b/browser/devtools/webide/test/head.js
@@ -57,27 +57,25 @@ function closeWebIDE(win) {
   });
 
   win.close();
 
   return deferred.promise;
 }
 
 function removeAllProjects() {
-  let deferred = promise.defer();
-  AppProjects.load().then(() => {
+  return Task.spawn(function* () {
+    yield AppProjects.load();
     let projects = AppProjects.store.object.projects;
     for (let i = 0; i < projects.length; i++) {
-      AppProjects.remove(projects[i].location);
+      yield AppProjects.remove(projects[i].location);
     }
-    deferred.resolve();
   });
+}
 
-  return deferred.promise;
-}
 function nextTick() {
   let deferred = promise.defer();
   SimpleTest.executeSoon(() => {
     deferred.resolve();
   });
 
   return deferred.promise;
 }
--- a/browser/devtools/webide/test/test_cli.html
+++ b/browser/devtools/webide/test/test_cli.html
@@ -14,25 +14,22 @@
 
   <body>
 
     <script type="application/javascript;version=1.8">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         Task.spawn(function* () {
-          let clClass = Components.classes["@mozilla.org/toolkit/command-line;1"].createInstance();
-
           Cu.import("resource://gre/modules/devtools/dbg-server.jsm");
           DebuggerServer.init(function () { return true; });
           DebuggerServer.addBrowserActors();
 
           let win = yield openWebIDE();
 
-
           let packagedAppLocation = getTestFilePath("app");
 
           let cli = "actions=addPackagedApp&location=" + packagedAppLocation;
           yield win.handleCommandline(cli);
 
           let project = win.AppManager.selectedProject;
           is(project.location, packagedAppLocation, "Project imported");
 
--- a/browser/devtools/webide/test/test_import.html
+++ b/browser/devtools/webide/test/test_import.html
@@ -17,16 +17,19 @@
     <script type="application/javascript;version=1.8">
       window.onload = function() {
         SimpleTest.waitForExplicitFinish();
 
         Task.spawn(function* () {
             let win = yield openWebIDE();
             let packagedAppLocation = getTestFilePath("app");
 
+            yield win.AppProjects.load();
+            is(win.AppProjects.store.object.projects.length, 0, "IDB is empty");
+
             yield win.Cmds.importPackagedApp(packagedAppLocation);
 
             let project = win.AppManager.selectedProject;
             is(project.location, packagedAppLocation, "Location is valid");
             is(project.name, "A name (in app directory)", "name field has been updated");
             is(project.manifest.launch_path, "/index.html", "manifest found. launch_path valid.");
             is(project.manifest.description, "desc", "manifest found. description valid");
 
--- a/browser/devtools/webide/test/test_runtime.html
+++ b/browser/devtools/webide/test/test_runtime.html
@@ -44,71 +44,61 @@
 
             getName: function() {
               return "fakeRuntime";
             }
           });
 
           win.AppManager.update("runtimelist");
 
-          let hostedAppManifest = TEST_BASE + "hosted_app.manifest";
-          yield win.Cmds.importHostedApp(hostedAppManifest);
+          let packagedAppLocation = getTestFilePath("app");
 
-          yield win.Cmds.showRuntimePanel();
+          yield win.Cmds.importPackagedApp(packagedAppLocation);
 
           let panelNode = win.document.querySelector("#runtime-panel");
           let items = panelNode.querySelectorAll(".runtime-panel-item-usb");
           is(items.length, 1, "Found one runtime button");
 
           let deferred = promise.defer();
           win.AppManager.connection.once(
               win.Connection.Events.CONNECTED,
               () => deferred.resolve());
 
           items[0].click();
 
-          yield deferred.promise;
+          ok(win.document.querySelector("window").className, "busy", "UI is busy");
+          yield win.UI._busyPromise;
 
           is(Object.keys(DebuggerServer._connections).length, 1, "Connected");
 
+          ok(isPlayActive(), "play button is enabled 1");
+          ok(!isStopActive(), "stop button is disabled 1");
+          let oldProject = win.AppManager.selectedProject;
+          win.AppManager.selectedProject = null;
+
           yield nextTick();
 
           ok(!isPlayActive(), "play button is disabled 2");
           ok(!isStopActive(), "stop button is disabled 2");
-
-          win.AppManager.selectedProject.errorsCount = 0;
+          win.AppManager._selectedProject = oldProject;
           win.UI.updateCommands();
 
           yield nextTick();
 
           ok(isPlayActive(), "play button is enabled 3");
           ok(!isStopActive(), "stop button is disabled 3");
-          let oldProject = win.AppManager.selectedProject;
-          win.AppManager.selectedProject = null;
-
-          yield nextTick();
-
-          ok(!isPlayActive(), "play button is disabled 4");
-          ok(!isStopActive(), "stop button is disabled 4");
-          win.AppManager._selectedProject = oldProject;
-          win.UI.updateCommands();
-
-          yield nextTick();
-
-          ok(isPlayActive(), "play button is enabled 5");
-          ok(!isStopActive(), "stop button is disabled 5");
 
 
           yield win.Cmds.disconnectRuntime();
 
           is(Object.keys(DebuggerServer._connections).length, 0, "Disconnected");
 
           ok(win.AppManager.selectedProject, "A project is still selected");
-          ok(!isPlayActive(), "play button is disabled 6");
-          ok(!isStopActive(), "stop button is disabled 6");
+          ok(!isPlayActive(), "play button is disabled 4");
+          ok(!isStopActive(), "stop button is disabled 4");
 
           deferred = promise.defer();
           win.AppManager.connection.once(
               win.Connection.Events.CONNECTED,
               () => deferred.resolve());
 
           win.document.querySelectorAll(".runtime-panel-item-custom")[1].click();
 
@@ -117,16 +107,18 @@
           is(Object.keys(DebuggerServer._connections).length, 1, "Locally connected");
 
           yield win.Cmds.disconnectRuntime();
 
           yield closeWebIDE(win);
 
           DebuggerServer.destroy();
 
+          yield removeAllProjects();
+
           SimpleTest.finish();
 
         });
       }
 
 
     </script>
   </body>
--- a/browser/devtools/webide/webide-prefs.js
+++ b/browser/devtools/webide/webide-prefs.js
@@ -1,9 +1,9 @@
 # -*- Mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 pref("devtools.webide.showProjectEditor", true);
-pref("devtools.webide.templatesURL", "http://fixme/");
+pref("devtools.webide.templatesURL", "http://people.mozilla.org/~prouget/webidetemplates/template.json"); // See bug 1021504
 pref("devtools.webide.lastprojectlocation", "");
 pref("devtools.webide.enableLocalRuntime", false);
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -631,21 +631,19 @@
 @BINPATH@/chrome/recording/*
 #ifdef MOZ_GTK
 @BINPATH@/browser/chrome/icons/default/default16.png
 @BINPATH@/browser/chrome/icons/default/default32.png
 @BINPATH@/browser/chrome/icons/default/default48.png
 #endif
 
 ; [Webide Files]
-#ifdef MOZ_DEVTOOLS_WEBIDE
 @BINPATH@/browser/chrome/webide@JAREXT@
 @BINPATH@/browser/chrome/webide.manifest
 @BINPATH@/browser/@PREF_DIR@/webide-prefs.js
-#endif
 
 ; shell icons
 #ifdef XP_UNIX
 #ifndef XP_MACOSX
 ; shell icons
 @BINPATH@/browser/icons/*.png
 #ifdef MOZ_UPDATER
 ; updater icon
--- a/browser/locales/en-US/chrome/browser/aboutHome.dtd
+++ b/browser/locales/en-US/chrome/browser/aboutHome.dtd
@@ -21,13 +21,18 @@
      text in <a/> will be linked to the featured add-ons on addons.mozilla.org
 -->
 <!ENTITY abouthome.defaultSnippet2.v1 "It's easy to customize your Firefox exactly the way you want it. <a>Choose from thousands of add-ons</a>.">
 <!-- LOCALIZATION NOTE (abouthome.rightsSnippet): text in <a/> will be linked to about:rights -->
 <!ENTITY abouthome.rightsSnippet "&brandFullName; is free and open source software from the non-profit Mozilla Foundation. <a>Know your rights…</a>">
 
 <!ENTITY abouthome.bookmarksButton.label "Bookmarks">
 <!ENTITY abouthome.historyButton.label   "History">
-<!ENTITY abouthome.settingsButton.label  "Settings">
+<!-- LOCALIZATION NOTE (abouthome.preferencesButtonWin.label): The label for the
+     preferences/options item on about:home on Windows -->
+<!ENTITY abouthome.preferencesButtonWin.label  "Options">
+<!-- LOCALIZATION NOTE (abouthome.preferencesButtonUnix.label): The label for the
+     preferences/options item on about:home on Linux and OS X -->
+<!ENTITY abouthome.preferencesButtonUnix.label  "Preferences">
 <!ENTITY abouthome.addonsButton.label    "Add-ons">
 <!ENTITY abouthome.appsButton.label      "Marketplace">
 <!ENTITY abouthome.downloadsButton.label "Downloads">
 <!ENTITY abouthome.syncButton.label      "&syncBrand.shortName.label;">
--- a/browser/locales/filter.py
+++ b/browser/locales/filter.py
@@ -1,24 +1,24 @@
 # 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/.
 
 def test(mod, path, entity = None):
   import re
   # ignore anything but Firefox
   if mod not in ("netwerk", "dom", "toolkit", "security/manager",
-                 "browser", "browser/metro", "webapprt",
+                 "browser", "webapprt",
                  "extensions/reporter", "extensions/spellcheck",
                  "other-licenses/branding/firefox",
                  "browser/branding/official",
                  "services/sync"):
     return "ignore"
-  if mod not in ("browser", "browser/metro", "extensions/spellcheck"):
-    # we only have exceptions for browser, metro and extensions/spellcheck
+  if mod not in ("browser", "extensions/spellcheck"):
+    # we only have exceptions for browser and extensions/spellcheck
     return "error"
   if not entity:
     # the only files to ignore are spell checkers and search
     if mod == "extensions/spellcheck":
       return "ignore"
     # browser
     return "ignore" if re.match(r"searchplugins\/.+\.xml", path) else "error"
   if mod == "extensions/spellcheck":
@@ -30,13 +30,9 @@ def test(mod, path, entity = None):
   if mod == "browser" and path == "chrome/browser-region/region.properties":
     # only region.properties exceptions remain, compare all others
     return ("ignore"
             if (re.match(r"browser\.search\.order\.[1-9]", entity) or
                 re.match(r"browser\.contentHandlers\.types\.[0-5]", entity) or
                 re.match(r"gecko\.handlerService\.schemes\.", entity) or
                 re.match(r"gecko\.handlerService\.defaultHandlersVersion", entity))
             else "error")
-  if mod == "browser/metro" and path == "chrome/region.properties":
-      return ("ignore"
-              if re.match(r"browser\.search\.order\.[1-9]", entity)
-              else "error")
   return "error"
--- a/browser/locales/l10n.ini
+++ b/browser/locales/l10n.ini
@@ -3,17 +3,16 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 [general]
 depth = ../..
 all = browser/locales/all-locales
 
 [compare]
 dirs = browser
-     browser/metro
      extensions/reporter
      other-licenses/branding/firefox
      browser/branding/official
 
 [includes]
 # non-central apps might want to use %(topsrcdir)s here, or other vars
 # RFE: that needs to be supported by compare-locales, too, though
 toolkit = toolkit/locales/l10n.ini
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -104,22 +104,16 @@ toolbarseparator {
   min-height: 22px;
 }
 
 #navigator-toolbox > toolbar:not(#TabsToolbar):not(#nav-bar):not(:-moz-lwtheme) {
   -moz-appearance: none;
   background: url(chrome://browser/skin/Toolbar-background-noise.png) hsl(0,0%,83%);
 }
 
-#TabsToolbar:not([collapsed="true"]) + #nav-bar {
-  /* Position the toolbar above the bottom of background tabs */
-  position: relative;
-  z-index: 1;
-}
-
 #nav-bar {
   -moz-appearance: none;
   background: url(chrome://browser/skin/Toolbar-background-noise.png),
               linear-gradient(hsl(0,0%,93%), hsl(0,0%,83%));
   background-clip: border-box;
   background-origin: border-box !important;
 
   /* Move the noise texture out of the top 1px strip because that overlaps
@@ -133,16 +127,29 @@ toolbarseparator {
 }
 
 @media (min-resolution: 2dppx) {
   #nav-bar {
     background-size: 100px 100px, auto;
   }
 }
 
+/* Draw the bottom border of the tabs toolbar when it's not using
+   -moz-appearance: toolbar. */
+#main-window:-moz-any([sizemode="fullscreen"],[customize-entered]) #TabsToolbar:not([collapsed="true"]) + #nav-bar,
+#main-window:not([tabsintitlebar]) #TabsToolbar:not([collapsed="true"]) + #nav-bar,
+#TabsToolbar:not([collapsed="true"]) + #nav-bar:-moz-lwtheme {
+  border-top: 1px solid hsla(0,0%,0%,.3);
+  background-clip: padding-box;
+  margin-top: -@tabToolbarNavbarOverlap@;
+  /* Position the toolbar above the bottom of background tabs */
+  position: relative;
+  z-index: 1;
+}
+
 #nav-bar-customization-target {
   padding: 4px;
 }
 
 #PersonalToolbar {
   padding: 0 4px 4px;
   /* 4px padding ^  plus 19px personal-bookmarks (see below) */
   min-height: 23px;
@@ -2765,76 +2772,35 @@ toolbarbutton.chevron > .toolbarbutton-m
   border: solid transparent;
   border-width: 0 11px;
 }
 
 .tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-label {
   box-shadow: @focusRingShadow@;
 }
 
-/* We want the titlebar to be unified, but we still want to be able
- * to give #TabsToolbar a background. So we can't set -moz-appearance:
- * toolbar on #TabsToolbar itself. Instead, we set it on a box of the
- * right size which is put underneath #TabsToolbar.
- *
- * Because of Bug 941309, we make sure this pseudoelement always exists,
- * but we only make it visible when we need it.
- */
-#navigator-toolbox::before {
-  content: '';
-  display: block;
+#TabsToolbar {
+  -moz-appearance: none;
+  margin-bottom: -1px; /* Overlap the inner highlight at the top of the nav-bar */
+}
+
+#main-window:not([customizing]) #navigator-toolbox[inFullscreen] > #TabsToolbar:not(:-moz-lwtheme),
+#main-window:not(:-moz-any([customizing],[tabsintitlebar])) #navigator-toolbox > #TabsToolbar:not(:-moz-lwtheme) {
   -moz-appearance: toolbar;
-  height: @tabMinHeight@;
-  margin-bottom: -@tabMinHeight@;
-  visibility: hidden;
-}
-
-#main-window:not([customizing]) #navigator-toolbox[inFullscreen]:not(:-moz-lwtheme)::before,
-#main-window:not(:-moz-any([customizing],[tabsintitlebar])) #navigator-toolbox:not(:-moz-lwtheme)::before {
-  visibility: visible;
-}
-
-#TabsToolbar {
-  position: relative;
-  -moz-appearance: none;
-  background-repeat: repeat-x;
-  margin-bottom: -@tabToolbarNavbarOverlap@;
 }
 
 #TabsToolbar:not(:-moz-lwtheme) {
   color: #333;
   text-shadow: @loweredShadow@;
 }
 
-/*
- * Draw the bottom border of the tabstrip when core doesn't do it for us.
- * Because of Bug 941309, we make sure this pseudoelement always exists,
- * but we only make it visible when we need it.
- */
-#TabsToolbar::after {
-  content: '';
-  /* Because we use placeholders for window controls etc. in the tabstrip,
-   * and position those with ordinal attributes, and because our layout code
-   * expects :before/:after nodes to come first/last in the frame list,
-   * we have to reorder this element to come last, hence the
-   * ordinal group value (see bug 853415). */
-  -moz-box-ordinal-group: 1001;
-  position: absolute;
-  bottom: @tabToolbarNavbarOverlap@;
-  left: 0;
-  right: 0;
-  z-index: 0;
-  border-bottom: 1px solid hsla(0,0%,0%,.3);
-  visibility: hidden;
-}
-
-#main-window:-moz-any([sizemode="fullscreen"],[customize-entered]) #TabsToolbar::after,
-#main-window:not([tabsintitlebar]) #TabsToolbar::after,
-#TabsToolbar:-moz-lwtheme::after {
-  visibility: visible;
+@media (-moz-mac-lion-theme) {
+  #navigator-toolbox[inFullscreen] > #TabsToolbar {
+    padding-top: @spaceAboveTabbar@;
+  }
 }
 
 #tabbrowser-tabs {
   -moz-box-align: stretch;
 }
 
 .tabs-newtab-button > .toolbarbutton-icon {
   padding: 6px 0 4px;
@@ -4053,27 +4019,16 @@ menulist.translate-infobar-element > .me
 
 .statuspanel-label:-moz-locale-dir(rtl):not([mirror]),
 .statuspanel-label:-moz-locale-dir(ltr)[mirror] {
   border-left-style: solid;
   border-top-left-radius: .3em;
   margin-left: 1em;
 }
 
-/* Lion Fullscreen window styling */
-@media (-moz-mac-lion-theme) {
-  #navigator-toolbox[inFullscreen]::before {
-    /* Adjust by the full element height of #titlebar, since that element is
-     * not displayed in native full-screen.
-     * Also add the height of the tabs, since we're calculating the
-     * total height of this pseudo-element, not just the top-padding. */
-    height: calc(@tabMinHeight@ + @spaceAboveTabbar@) !important;
-  }
-}
-
 #full-screen-warning-message {
   background-image: url("chrome://browser/skin/fullscreen-darknoise.png");
   color: white;
   border-radius: 4px;
   margin-top: 30px;
   padding: 30px 50px;
   box-shadow: 0 0 2px white;
 }
@@ -4416,16 +4371,21 @@ window > chatbox {
   -moz-border-left-colors: hsla(0,0%,0%,.05) hsla(0,0%,0%,.1) hsla(0,0%,0%,.2);
 }
 
 #main-window[customize-entered] #customization-container,
 #main-window[customize-entered] #navigator-toolbox > toolbar:not(#TabsToolbar) {
   border-bottom-width: 0;
 }
 
+#main-window[customize-entered] #nav-bar {
+  border-top-left-radius: 2.5px;
+  border-top-right-radius: 2.5px;
+}
+
 /* Compensate for the border set above for this horizontal line. */
 #main-window[customize-entered] #navigator-toolbox::after {
   margin-left: 3px;
   margin-right: 3px;
 }
 
 #main-window[customize-entered] #TabsToolbar {
   background-clip: padding-box;
--- a/browser/themes/shared/devtools/projecteditor/projecteditor.css
+++ b/browser/themes/shared/devtools/projecteditor/projecteditor.css
@@ -6,130 +6,151 @@
  :root {
   color: #18191a;
 }
 
 .plugin-hidden {
   display: none;
 }
 
+.arrow {
+  -moz-appearance: treetwisty;
+  width: 20px;
+  height: 20px;
+}
+
+.arrow[open] {
+  -moz-appearance: treetwistyopen;
+}
+
+.arrow[invisible] {
+  visibility: hidden;
+}
+
 #projecteditor-menubar {
   /* XXX: Hide menu bar until we have option to add menu items
      to an existing one. */
   display: none;
 }
 
 #projecteditor-toolbar,
 #projecteditor-toolbar-bottom {
   display: none; /* For now don't show the status bars */
   min-height: 22px;
   height: 22px;
   background: rgb(237, 237, 237);
 }
 
 .sources-tree {
   overflow:auto;
+  overflow-x: hidden;
   -moz-user-focus: normal;
+
+  /* Allows this to expand inside of parent xul element, while
+     still supporting child flexbox elements, including ellipses. */
+  -moz-box-flex: 1;
+  display: block;
 }
 
 .sources-tree input {
   margin: 2px;
   border: 1px solid gray;
 }
 
 #main-deck .sources-tree {
   background: rgb(225, 225, 225);
-  min-width: 50px;
-}
-
-#main-deck .sources-tree .side-menu-widget-item {
-  color: #18191A;
+  min-width: 100px;
 }
 
-#main-deck .sources-tree .side-menu-widget-item .file-label {
-  vertical-align: middle;
-  display: inline-block;
+.entry {
+  color: #18191A;
+  display: flex;
+  align-items: center;
 }
 
-#main-deck .sources-tree .side-menu-widget-item .file-icon {
+.entry .file-label {
+  display: flex;
+  flex: 1;
+  align-items: center;
+}
+
+.entry .file-icon {
   display: inline-block;
   background: url(file-icons-sheet@2x.png);
   background-size: 140px 15px;
   background-repeat: no-repeat;
   width: 20px;
   height: 15px;
-  vertical-align: middle;
   background-position: -40px 0;
+  flex-shrink: 0;
 }
 
-#main-deck .sources-tree .side-menu-widget-item .file-icon.icon-none {
+.entry .file-icon.icon-none {
   display: none;
 }
 
-#main-deck .sources-tree .side-menu-widget-item .icon-css {
+.entry .icon-css {
   background-position: 0 0;
 }
 
-#main-deck .sources-tree .side-menu-widget-item .icon-js {
+.entry .icon-js {
   background-position: -20px 0;
 }
 
-#main-deck .sources-tree .side-menu-widget-item .icon-html {
+.entry .icon-html {
   background-position: -40px 0;
 }
 
-#main-deck .sources-tree .side-menu-widget-item .icon-file {
+.entry .icon-file {
   background-position: -60px 0;
 }
 
-#main-deck .sources-tree .side-menu-widget-item .icon-folder {
+.entry .icon-folder {
   background-position: -80px 0;
 }
 
-#main-deck .sources-tree .side-menu-widget-item .icon-img {
+.entry .icon-img {
   background-position: -100px 0;
 }
 
-#main-deck .sources-tree .side-menu-widget-item .icon-manifest {
+.entry .icon-manifest {
   background-position: -120px 0;
 }
 
-#main-deck .sources-tree .side-menu-widget-item:hover {
-  background: rgba(0, 0, 0, .05);
+.entry {
+  border: none;
+  box-shadow: none;
+  white-space: nowrap;
   cursor: pointer;
 }
 
-#main-deck .sources-tree .side-menu-widget-item {
-  border: none;
-  box-shadow: none;
-  line-height: 20px;
-  vertical-align: middle;
-  white-space: nowrap;
+.entry:hover:not(.entry-group-title):not(.selected) {
+  background: rgba(0, 0, 0, .05);
 }
 
-#main-deck .sources-tree .side-menu-widget-item.selected {
-  background: #3875D7;
+.entry.selected {
+  background: rgba(56, 117, 215, 1);
   color: #F5F7FA;
   outline: none;
 }
 
-#main-deck .sources-tree .side-menu-widget-group-title,
-#main-deck .sources-tree .side-menu-widget-group-title:hover:not(.selected) {
-  background: #B4D7EB;
-  color: #222;
+.entry-group-title {
+  background: rgba(56, 117, 215, 0.8);
+  color: #F5F7FA;
   font-weight: bold;
   font-size: 1.05em;
-  cursor: default;
   line-height: 35px;
+  padding: 0 10px;
 }
 
-#main-deck .sources-tree li.child:only-child .side-menu-widget-group-title .expander {
+.sources-tree .entry-group-title .expander {
   display: none;
 }
-#main-deck .sources-tree .side-menu-widget-item .expander {
+
+.entry .expander {
   width: 16px;
   padding: 0;
 }
 
 .tree-collapsed .children {
   display: none;
 }
 
@@ -138,35 +159,67 @@
 #projecteditor-toolbar textbox {
   margin: 0;
 }
 
 .projecteditor-basic-display {
   padding: 0 3px;
 }
 
+/* App Manager */
 .project-name-label {
   font-weight: bold;
   padding-left: 10px;
+  overflow: hidden;
+  text-overflow: ellipsis;
 }
 
-.project-version-label {
-  color: #666;
-  padding-left: 5px;
-  font-size: .9em;
+.project-flex {
+  flex: 1;
 }
 
 .project-image {
-  max-height: 28px;
-  margin-left: -.5em;
-  vertical-align: middle;
+  max-height: 25px;
+  margin-left: -10px;
+}
+
+.project-image,
+.project-status,
+.project-options {
+  flex-shrink: 0;
+}
+
+.project-status {
+  width: 10px;
+  height: 10px;
+  border-radius: 50%;
+  border: solid 1px rgba(255, 255, 255, .5);
+  margin-right: 10px;
+  visibility: hidden;
 }
 
+.project-status[status=valid] {
+  background: #70bf53;
+  visibility: visible;
+}
+
+.project-status[status=warning] {
+  background: #d99b28;
+  visibility: visible;
+}
+
+.project-status[status=error] {
+  background: #ed2655;
+  visibility: visible;
+}
+
+/* Status Bar */
+.projecteditor-file-label {
+  font-weight: bold;
+  padding-left: 29px;
+  padding-right: 10px;
+  flex: 1;
+}
+
+/* Image View */
 .editor-image {
   padding: 10px;
 }
-
-.projecteditor-file-label {
-  font-weight: bold;
-  padding-left: 29px;
-  vertical-align: middle;
-}
-
--- a/browser/themes/shared/incontentprefs/preferences.css
+++ b/browser/themes/shared/incontentprefs/preferences.css
@@ -1,20 +1,15 @@
 %if 0
 /* - This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this file,
    - You can obtain one at http://mozilla.org/MPL/2.0/. */
 %endif
 @namespace html "http://www.w3.org/1999/xhtml";
 
-@font-face {
-  font-family: "Clear Sans";
-  src: url("chrome://browser/content/fonts/ClearSans-Regular.woff") format('woff');
-}
-
 page {
   -moz-appearance: none;
   background-color: white;
 }
 
 * {
   -moz-user-select: text;
 }
@@ -34,17 +29,17 @@ caption > label {
 .main-content {
   padding: 40px 48px 48px;
   overflow: auto;
 }
 
 prefpane {
   max-width: 800px;
   padding: 0;
-  font-family: "Clear Sans", sans-serif;
+  font: message-box;
   font-size: 1.25rem;
   line-height: 22px;
   color: #424E5A;
 }
 
 prefpane > .content-box {
   overflow: visible;
 }
@@ -287,17 +282,16 @@ button[type="menu"] > menupopup {
   background-color: #FFFFFF;
 }
 
 menulist > menupopup menu,
 menulist > menupopup menuitem,
 button[type="menu"] > menupopup menu,
 button[type="menu"] > menupopup menuitem {
   -moz-appearance: none;
-  font-family: "Clear Sans", sans-serif;
   font-size: 1.25rem;
   line-height: 22px;
   height: 40px;
   color: #333333;
   -moz-padding-start: 10px;
   -moz-padding-end: 30px;
 }
 
@@ -483,17 +477,16 @@ radio[disabled="true"] > .radio-check {
 .category[selected] {
   background-color: #343f48;
   color: #f2f2f2;
   box-shadow: inset 4px 0 0 0 #FF9500;
 }
 
 .category-name {
   line-height: 22px;
-  font-family: "Clear Sans", sans-serif;
   font-size: 1.25rem;
   padding-bottom: 2px;
   -moz-padding-start: 9px;
   margin: 0;
 }
 
 .category-icon {
   width: 24px;
@@ -689,17 +682,16 @@ filefield {
   border-radius: 2px;
   background-color: #FBFBFB;
   overflow-y: auto;
 }
 
 #typeColumn,
 #actionColumn {
   -moz-appearance: none;
-  font-family: "Clear Sans", sans-serif;
   line-height: 20px;
   color: #333333;
   height: 36px;
   padding: 0 10px;
   background-color: #FBFBFB;
   border: 1px solid #C1C1C1;
   -moz-border-top-colors: none;
   -moz-border-right-colors: none;
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -435,21 +435,23 @@ browser.jar:
         skin/classic/aero/browser/identity-icons-https-mixed-active.png
         skin/classic/aero/browser/identity-icons-https-mixed-display.png
         skin/classic/aero/browser/in-content/common.css              (../shared/in-content/common.css)
         skin/classic/aero/browser/keyhole-forward-mask.svg
         skin/classic/aero/browser/KUI-background.png
         skin/classic/aero/browser/livemark-folder.png                (livemark-folder-aero.png)
         skin/classic/aero/browser/menu-back.png                      (menu-back-aero.png)
         skin/classic/aero/browser/menu-forward.png                   (menu-forward-aero.png)
-        skin/classic/aero/browser/menuPanel.png                      (menuPanel-aero.png)
+        skin/classic/aero/browser/menuPanel.png
+        skin/classic/aero/browser/menuPanel-aero.png
         skin/classic/aero/browser/menuPanel-customize.png
         skin/classic/aero/browser/menuPanel-exit.png
         skin/classic/aero/browser/menuPanel-help.png
-        skin/classic/aero/browser/menuPanel-small.png                (menuPanel-small-aero.png)
+        skin/classic/aero/browser/menuPanel-small.png
+        skin/classic/aero/browser/menuPanel-small-aero.png
         skin/classic/aero/browser/Metro_Glyph.png                    (Metro_Glyph-aero.png)
         skin/classic/aero/browser/Metro_Glyph-inverted.png
         skin/classic/aero/browser/Metro_Glyph-menuPanel.png
         skin/classic/aero/browser/mixed-content-blocked-16.png
         skin/classic/aero/browser/mixed-content-blocked-64.png
         skin/classic/aero/browser/monitor.png
         skin/classic/aero/browser/monitor_16-10.png
         skin/classic/aero/browser/notification-16.png
@@ -801,10 +803,16 @@ browser.jar:
         skin/classic/aero/browser/devtools/tooltip/arrow-horizontal-light.png   (../shared/devtools/tooltip/arrow-horizontal-light.png)
         skin/classic/aero/browser/devtools/tooltip/arrow-horizontal-light@2x.png   (../shared/devtools/tooltip/arrow-horizontal-light@2x.png)
         skin/classic/aero/browser/devtools/tooltip/arrow-vertical-light.png   (../shared/devtools/tooltip/arrow-vertical-light.png)
         skin/classic/aero/browser/devtools/tooltip/arrow-vertical-light@2x.png   (../shared/devtools/tooltip/arrow-vertical-light@2x.png)
 
 % override chrome://browser/skin/Toolbar.png               chrome://browser/skin/Toolbar-aero.png                  os=WINNT osversion=6
 % override chrome://browser/skin/Toolbar.png               chrome://browser/skin/Toolbar-aero.png                  os=WINNT osversion=6.1
 
+% override chrome://browser/skin/menuPanel.png             chrome://browser/skin/menuPanel-aero.png                os=WINNT osversion=6
+% override chrome://browser/skin/menuPanel.png             chrome://browser/skin/menuPanel-aero.png                os=WINNT osversion=6.1
+
+% override chrome://browser/skin/menuPanel-small.png       chrome://browser/skin/menuPanel-small-aero.png          os=WINNT osversion=6
+% override chrome://browser/skin/menuPanel-small.png       chrome://browser/skin/menuPanel-small-aero.png          os=WINNT osversion=6.1
+
 % override chrome://browser/skin/sync-horizontalbar.png          chrome://browser/skin/sync-horizontalbar-XPVista7.png          os=WINNT osversion<6.2
 % override chrome://browser/skin/syncProgress-horizontalbar.png  chrome://browser/skin/syncProgress-horizontalbar-XPVista7.png  os=WINNT osversion<6.2
--- a/configure.in
+++ b/configure.in
@@ -7675,29 +7675,16 @@ if test "$MOZ_CHROME_FILE_FORMAT" = "sym
 fi
 
 if test "$MOZ_CHROME_FILE_FORMAT" != "jar" &&
     test "$MOZ_CHROME_FILE_FORMAT" != "flat" &&
     test "$MOZ_CHROME_FILE_FORMAT" != "omni"; then
     AC_MSG_ERROR([--enable-chrome-format must be set to either jar, flat, or omni])
 fi
 
-dnl ========================================================
-dnl = Enable Support for devtools webide
-dnl ========================================================
-MOZ_ARG_ENABLE_BOOL(devtools-webide,
-[  --enable-devtools-webide Set compile flags necessary for compiling devtools webide ],
-MOZ_DEVTOOLS_WEBIDE=1,
-MOZ_DEVTOOLS_WEBIDE= )
-
-if test -n "$MOZ_DEVTOOLS_WEBIDE"; then
-    AC_DEFINE(MOZ_DEVTOOLS_WEBIDE)
-fi
-AC_SUBST(MOZ_DEVTOOLS_WEBIDE)
-
 dnl =========================================================
 dnl Omnijar packaging (bug 552121)
 dnl =========================================================
 dnl Omnijar packaging is compatible with flat packaging.
 dnl In unpackaged builds, omnijar looks for files as if
 dnl things were flat packaged. After packaging, all files
 dnl are loaded from a single jar. MOZ_CHROME_FILE_FORMAT
 dnl is set to flat since putting files into jars is only
--- a/mobile/android/base/health/BrowserHealthRecorder.java
+++ b/mobile/android/base/health/BrowserHealthRecorder.java
@@ -520,26 +520,27 @@ public class BrowserHealthRecorder imple
         // Otherwise, let's initialize it from scratch.
         this.profileCache.beginInitialization();
         this.profileCache.setProfileCreationTime(getAndPersistProfileInitTime(context, profilePath));
         this.profileCache.setOSLocale(osLocale);
         this.profileCache.setAppLocale(appLocale);
 
         // Because the distribution lookup can take some time, do it at the end of
         // our background startup work, along with the Gecko snapshot fetch.
-        final GeckoEventListener self = this;
-        ThreadUtils.postToBackgroundThread(new Runnable() {
+        final Distribution distribution = Distribution.getInstance(context);
+        distribution.addOnDistributionReadyCallback(new Runnable() {
             @Override
             public void run() {
-                final DistributionDescriptor desc = new Distribution(context).getDescriptor();
+                Log.d(LOG_TAG, "Running post-distribution task: health recorder.");
+                final DistributionDescriptor desc = distribution.getDescriptor();
                 if (desc != null && desc.valid) {
                     profileCache.setDistributionString(desc.id, desc.version);
                 }
                 Log.d(LOG_TAG, "Requesting all add-ons and FHR prefs from Gecko.");
-                dispatcher.registerGeckoThreadListener(self, EVENT_SNAPSHOT);
+                dispatcher.registerGeckoThreadListener(BrowserHealthRecorder.this, EVENT_SNAPSHOT);
                 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("HealthReport:RequestSnapshot", null));
             }
         });
     }
 
     /**
      * Invoked in the background whenever the environment transitions between
      * two valid values.
--- a/mobile/android/base/resources/values/colors.xml
+++ b/mobile/android/base/resources/values/colors.xml
@@ -88,20 +88,25 @@
   <color name="url_bar_domaintext_private">#FFF</color>
   <color name="url_bar_blockedtext">#b14646</color>
   <color name="url_bar_shadow">#12000000</color>
 
   <color name="home_button_bar_bg">#FFF5F7F9</color>
 
   <color name="panel_image_item_background">#D1D9E1</color>
 
-  <!-- Swipe to refresh colors -->
+  <!-- Swipe to refresh colors for dynamic panel -->
   <color name="swipe_refresh_orange">#FFFFC26C</color>
   <color name="swipe_refresh_white">#FFFFFFFF</color>
-  <color name="swipe_refresh_orange_dark">#FF9500</color>
+
+  <!-- Swipe to refresh colors for remote tabs -->
+  <color name="swipe_refresh_orange1">#EE6700</color>
+  <color name="swipe_refresh_orange2">#FF9400</color>
+  <color name="swipe_refresh_orange3">#F57900</color>
+  <color name="swipe_refresh_orange4">#FFB44C</color>
 
   <!-- Remote tabs setup -->
   <color name="remote_tabs_setup_button_background">#E66000</color>
   <color name="remote_tabs_setup_button_background_hit">#D95300</color>
 
   <!-- Button toast colors. -->
   <color name="toast_button_background">#FF2A2A2A</color>
   <color name="toast_button_pressed">#FF3E6784</color>
--- a/mobile/android/base/tabspanel/RemoteTabsContainerPanel.java
+++ b/mobile/android/base/tabspanel/RemoteTabsContainerPanel.java
@@ -55,18 +55,18 @@ public class RemoteTabsContainerPanel ex
 
     @Override
     public void addView(View child, int index, ViewGroup.LayoutParams params) {
         super.addView(child, index, params);
 
         list = (RemoteTabsList) child;
 
         // Must be called after the child view has been added.
-        setColorScheme(R.color.swipe_refresh_orange_dark, R.color.background_tabs,
-                       R.color.swipe_refresh_orange_dark, R.color.background_tabs);
+        setColorScheme(R.color.swipe_refresh_orange1, R.color.swipe_refresh_orange2,
+                       R.color.swipe_refresh_orange3, R.color.swipe_refresh_orange4);
     }
 
 
     @Override
     public boolean canChildScrollUp() {
         // We are not supporting swipe-to-refresh for old sync. This disables
         // the swipe gesture if no FxA are detected.
         if (FirefoxAccounts.firefoxAccountsExist(getContext())) {
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -986,20 +986,25 @@ var BrowserApp = {
     aTab.destroy();
     this._tabs.splice(tabIndex, 1);
 
     if (aShowUndoToast) {
       // Get a title for the undo close toast. Fall back to the URL if there is no title.
       let ss = Cc["@mozilla.org/browser/sessionstore;1"].getService(Ci.nsISessionStore);
       let closedTabData = ss.getClosedTabs(window)[0];
 
-      let historyEntry = closedTabData.entries[closedTabData.index - 1];
-      let title = historyEntry.title || historyEntry.url;
-
-      let message = Strings.browser.formatStringFromName("undoCloseToast.message", [title], 1);
+      let message;
+      let title = closedTabData.entries[closedTabData.index - 1].title;
+
+      if (title) {
+        message = Strings.browser.formatStringFromName("undoCloseToast.message", [title], 1);
+      } else {
+        message = Strings.browser.GetStringFromName("undoCloseToast.messageDefault");
+      }
+
       NativeWindow.toast.show(message, "short", {
         button: {
           icon: "drawable://undo_button_icon",
           label: Strings.browser.GetStringFromName("undoCloseToast.action2"),
           callback: function() {
             UITelemetry.addEvent("undo.1", "toast", null, "closetab");
             ss.undoCloseTab(window, 0);
           }
--- a/mobile/android/locales/en-US/chrome/browser.properties
+++ b/mobile/android/locales/en-US/chrome/browser.properties
@@ -127,16 +127,20 @@ newprivatetabpopup.opened=New private ta
 # button label) and upper-case, to match Google and Android's convention.
 newtabpopup.switch=SWITCH
 
 # Undo close tab toast
 # LOCALIZATION NOTE (undoCloseToast.message): This message appears in a toast
 # when the user closes a tab. %S is the title of the tab that was closed.
 undoCloseToast.message=Closed %S
 
+# LOCALIZATION NOTE (undoCloseToast.messageDefault): This message appears in a
+# toast when the user closes a tab if there is no title to display.
+undoCloseToast.messageDefault=Closed tab
+
 # LOCALIZATION NOTE (undoCloseToast.action2): Ideally, this string is short (it's a
 # button label) and upper-case, to match Google and Android's convention.
 undoCloseToast.action2=UNDO
 
 # Offline web applications
 offlineApps.ask=Allow %S to store data on your device for offline use?
 offlineApps.dontAskAgain=Don't ask again for this site
 offlineApps.allow=Allow
--- a/toolkit/devtools/styleinspector/css-logic.js
+++ b/toolkit/devtools/styleinspector/css-logic.js
@@ -912,20 +912,23 @@ CssLogic.findCssSelector = function CssL
       selector = selector + ':nth-child(' + index + ')';
       matches = document.querySelectorAll(selector);
       if (matches.length === 1) {
         return selector;
       }
     }
   }
 
-  // So we can be unique w.r.t. our parent, and use recursion
-  index = positionInNodeList(ele, ele.parentNode.children) + 1;
-  selector = CssLogic_findCssSelector(ele.parentNode) + ' > ' +
-          tagName + ':nth-child(' + index + ')';
+  // Not unique enough yet.  As long as it's not a child of the document,
+  // continue recursing up until it is unique enough.
+  if (ele.parentNode !== document) {
+    index = positionInNodeList(ele, ele.parentNode.children) + 1;
+    selector = CssLogic_findCssSelector(ele.parentNode) + ' > ' +
+            tagName + ':nth-child(' + index + ')';
+  }
 
   return selector;
 };
 
 /**
  * A safe way to access cached bits of information about a stylesheet.
  *
  * @constructor
--- a/toolkit/devtools/transport/packets.js
+++ b/toolkit/devtools/transport/packets.js
@@ -20,17 +20,16 @@
  *     Called when the output stream is ready to write
  *   * get done()
  *     Returns true once the packet is done being read / written
  *   * destroy()
  *     Called to clean up at the end of use
  */
 
 const { Cc, Ci, Cu } = require("chrome");
-const Heritage = require("sdk/core/heritage");
 const DevToolsUtils = require("devtools/toolkit/DevToolsUtils");
 const { dumpn, dumpv } = DevToolsUtils;
 const StreamUtils = require("devtools/toolkit/transport/stream-utils");
 
 DevToolsUtils.defineLazyGetter(this, "unicodeConverter", () => {
   const unicodeConverter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
                            .createInstance(Ci.nsIScriptableUnicodeConverter);
   unicodeConverter.charset = "UTF-8";
@@ -123,93 +122,95 @@ JSONPacket.fromHeader = function(header,
   dumpv("Header matches JSON packet");
   let packet = new JSONPacket(transport);
   packet.length = +match[1];
   return packet;
 };
 
 JSONPacket.HEADER_PATTERN = /^(\d+):$/;
 
-JSONPacket.prototype = Heritage.extend(Packet.prototype, {
+JSONPacket.prototype = Object.create(Packet.prototype);
 
+Object.defineProperty(JSONPacket.prototype, "object", {
   /**
    * Gets the object (not the serialized string) being read or written.
    */
-  get object() { return this._object; },
+  get: function() { return this._object; },
 
   /**
    * Sets the object to be sent when write() is called.
    */
-  set object(object) {
+  set: function(object) {
     this._object = object;
     let data = JSON.stringify(object);
     this._data = unicodeConverter.ConvertFromUnicode(data);
     this.length = this._data.length;
-  },
-
-  read: function(stream, scriptableStream) {
-    dumpv("Reading JSON packet");
-
-    // Read in more packet data.
-    this._readData(stream, scriptableStream);
-
-    if (!this.done) {
-      // Don't have a complete packet yet.
-      return;
-    }
+  }
+});
 
-    let json = this._data;
-    try {
-      json = unicodeConverter.ConvertToUnicode(json);
-      this._object = JSON.parse(json);
-    } catch(e) {
-      let msg = "Error parsing incoming packet: " + json + " (" + e +
-                " - " + e.stack + ")";
-      if (Cu.reportError) {
-        Cu.reportError(msg);
-      }
-      dumpn(msg);
-      return;
-    }
+JSONPacket.prototype.read = function(stream, scriptableStream) {
+  dumpv("Reading JSON packet");
+
+  // Read in more packet data.
+  this._readData(stream, scriptableStream);
 
-    this._transport._onJSONObjectReady(this._object);
-  },
-
-  _readData: function(stream, scriptableStream) {
-    if (dumpv.wantVerbose) {
-      dumpv("Reading JSON data: _l: " + this.length + " dL: " +
-            this._data.length + " sA: " + stream.available());
-    }
-    let bytesToRead = Math.min(this.length - this._data.length,
-                               stream.available());
-    this._data += scriptableStream.readBytes(bytesToRead);
-    this._done = this._data.length === this.length;
-  },
+  if (!this.done) {
+    // Don't have a complete packet yet.
+    return;
+  }
 
-  write: function(stream) {
-    dumpv("Writing JSON packet");
-
-    if (this._outgoing === undefined) {
-      // Format the serialized packet to a buffer
-      this._outgoing = this.length + ":" + this._data;
+  let json = this._data;
+  try {
+    json = unicodeConverter.ConvertToUnicode(json);
+    this._object = JSON.parse(json);
+  } catch(e) {
+    let msg = "Error parsing incoming packet: " + json + " (" + e +
+              " - " + e.stack + ")";
+    if (Cu.reportError) {
+      Cu.reportError(msg);
     }
-
-    let written = stream.write(this._outgoing, this._outgoing.length);
-    this._outgoing = this._outgoing.slice(written);
-    this._done = !this._outgoing.length;
-  },
-
-  get done() { return this._done; },
-
-  toString: function() {
-    return JSON.stringify(this._object, null, 2);
+    dumpn(msg);
+    return;
   }
 
+  this._transport._onJSONObjectReady(this._object);
+}
+
+JSONPacket.prototype._readData = function(stream, scriptableStream) {
+  if (dumpv.wantVerbose) {
+    dumpv("Reading JSON data: _l: " + this.length + " dL: " +
+          this._data.length + " sA: " + stream.available());
+  }
+  let bytesToRead = Math.min(this.length - this._data.length,
+                             stream.available());
+  this._data += scriptableStream.readBytes(bytesToRead);
+  this._done = this._data.length === this.length;
+}
+
+JSONPacket.prototype.write = function(stream) {
+  dumpv("Writing JSON packet");
+
+  if (this._outgoing === undefined) {
+    // Format the serialized packet to a buffer
+    this._outgoing = this.length + ":" + this._data;
+  }
+
+  let written = stream.write(this._outgoing, this._outgoing.length);
+  this._outgoing = this._outgoing.slice(written);
+  this._done = !this._outgoing.length;
+}
+
+Object.defineProperty(JSONPacket.prototype, "done", {
+  get: function() { return this._done; }
 });
 
+JSONPacket.prototype.toString = function() {
+  return JSON.stringify(this._object, null, 2);
+}
+
 exports.JSONPacket = JSONPacket;
 
 /**
  * With a bulk packet, data is transferred by temporarily handing over the
  * transport's input or output stream to the application layer for writing data
  * directly.  This can be much faster for large data sets, and avoids various
  * stages of copies and data duplication inherent in the JSON packet type.  The
  * bulk packet looks like:
@@ -252,126 +253,131 @@ BulkPacket.fromHeader = function(header,
     type: match[2],
     length: +match[3]
   };
   return packet;
 };
 
 BulkPacket.HEADER_PATTERN = /^bulk ([^: ]+) ([^: ]+) (\d+):$/;
 
-BulkPacket.prototype = Heritage.extend(Packet.prototype, {
+BulkPacket.prototype = Object.create(Packet.prototype);
+
+BulkPacket.prototype.read = function(stream) {
+  dumpv("Reading bulk packet, handing off input stream");
 
-  read: function(stream) {
-    dumpv("Reading bulk packet, handing off input stream");
+  // Temporarily pause monitoring of the input stream
+  this._transport.pauseIncoming();
+
+  let deferred = promise.defer();
 
-    // Temporarily pause monitoring of the input stream
-    this._transport.pauseIncoming();
-
-    let deferred = promise.defer();
+  this._transport._onBulkReadReady({
+    actor: this.actor,
+    type: this.type,
+    length: this.length,
+    copyTo: (output) => {
+      dumpv("CT length: " + this.length);
+      deferred.resolve(StreamUtils.copyStream(stream, output, this.length));
+      return deferred.promise;
+    },
+    stream: stream,
+    done: deferred
+  });
 
-    this._transport._onBulkReadReady({
-      actor: this.actor,
-      type: this.type,
-      length: this.length,
-      copyTo: (output) => {
-        dumpv("CT length: " + this.length);
-        deferred.resolve(StreamUtils.copyStream(stream, output, this.length));
-        return deferred.promise;
-      },
-      stream: stream,
-      done: deferred
-    });
+  // Await the result of reading from the stream
+  deferred.promise.then(() => {
+    dumpv("onReadDone called, ending bulk mode");
+    this._done = true;
+    this._transport.resumeIncoming();
+  }, this._transport.close);
 
-    // Await the result of reading from the stream
-    deferred.promise.then(() => {
-      dumpv("onReadDone called, ending bulk mode");
-      this._done = true;
-      this._transport.resumeIncoming();
-    }, this._transport.close);
+  // Ensure this is only done once
+  this.read = () => {
+    throw new Error("Tried to read() a BulkPacket's stream multiple times.");
+  };
+}
 
-    // Ensure this is only done once
-    this.read = () => {
-      throw new Error("Tried to read() a BulkPacket's stream multiple times.");
-    };
-  },
+BulkPacket.prototype.write = function(stream) {
+  dumpv("Writing bulk packet");
 
-  write: function(stream) {
-    dumpv("Writing bulk packet");
+  if (this._outgoingHeader === undefined) {
+    dumpv("Serializing bulk packet header");
+    // Format the serialized packet header to a buffer
+    this._outgoingHeader = "bulk " + this.actor + " " + this.type + " " +
+                           this.length + ":";
+  }
 
-    if (this._outgoingHeader === undefined) {
-      dumpv("Serializing bulk packet header");
-      // Format the serialized packet header to a buffer
-      this._outgoingHeader = "bulk " + this.actor + " " + this.type + " " +
-                             this.length + ":";
-    }
+  // Write the header, or whatever's left of it to write.
+  if (this._outgoingHeader.length) {
+    dumpv("Writing bulk packet header");
+    let written = stream.write(this._outgoingHeader,
+                               this._outgoingHeader.length);
+    this._outgoingHeader = this._outgoingHeader.slice(written);
+    return;
+  }
 
-    // Write the header, or whatever's left of it to write.
-    if (this._outgoingHeader.length) {
-      dumpv("Writing bulk packet header");
-      let written = stream.write(this._outgoingHeader,
-                                 this._outgoingHeader.length);
-      this._outgoingHeader = this._outgoingHeader.slice(written);
-      return;
-    }
+  dumpv("Handing off output stream");
 
-    dumpv("Handing off output stream");
+  // Temporarily pause the monitoring of the output stream
+  this._transport.pauseOutgoing();
 
-    // Temporarily pause the monitoring of the output stream
-    this._transport.pauseOutgoing();
-
-    let deferred = promise.defer();
+  let deferred = promise.defer();
 
-    this._readyForWriting.resolve({
-      copyFrom: (input) => {
-        dumpv("CF length: " + this.length);
-        deferred.resolve(StreamUtils.copyStream(input, stream, this.length));
-        return deferred.promise;
-      },
-      stream: stream,
-      done: deferred
-    });
+  this._readyForWriting.resolve({
+    copyFrom: (input) => {
+      dumpv("CF length: " + this.length);
+      deferred.resolve(StreamUtils.copyStream(input, stream, this.length));
+      return deferred.promise;
+    },
+    stream: stream,
+    done: deferred
+  });
 
-    // Await the result of writing to the stream
-    deferred.promise.then(() => {
-      dumpv("onWriteDone called, ending bulk mode");
-      this._done = true;
-      this._transport.resumeOutgoing();
-    }, this._transport.close);
+  // Await the result of writing to the stream
+  deferred.promise.then(() => {
+    dumpv("onWriteDone called, ending bulk mode");
+    this._done = true;
+    this._transport.resumeOutgoing();
+  }, this._transport.close);
 
-    // Ensure this is only done once
-    this.write = () => {
-      throw new Error("Tried to write() a BulkPacket's stream multiple times.");
-    };
-  },
+  // Ensure this is only done once
+  this.write = () => {
+    throw new Error("Tried to write() a BulkPacket's stream multiple times.");
+  };
+}
 
-  get streamReadyForWriting() {
+Object.defineProperty(BulkPacket.prototype, "streamReadyForWriting", {
+  get: function() {
     return this._readyForWriting.promise;
-  },
+  }
+});
 
-  get header() {
+Object.defineProperty(BulkPacket.prototype, "header", {
+  get: function() {
     return {
       actor: this.actor,
       type: this.type,
       length: this.length
     };
   },
 
-  set header(header) {
+  set: function(header) {
     this.actor = header.actor;
     this.type = header.type;
     this.length = header.length;
   },
-
-  get done() { return this._done; },
+});
 
-  toString: function() {
-    return "Bulk: " + JSON.stringify(this.header, null, 2);
-  }
+Object.defineProperty(BulkPacket.prototype, "done", {
+  get: function() { return this._done; },
+});
 
-});
+
+BulkPacket.prototype.toString = function() {
+  return "Bulk: " + JSON.stringify(this.header, null, 2);
+}
 
 exports.BulkPacket = BulkPacket;
 
 /**
  * RawPacket is used to test the transport's error handling of malformed
  * packets, by writing data directly onto the stream.
  * @param transport DebuggerTransport
  *        The transport instance that will own the packet.
@@ -380,26 +386,26 @@ exports.BulkPacket = BulkPacket;
  */
 function RawPacket(transport, data) {
   Packet.call(this, transport);
   this._data = data;
   this.length = data.length;
   this._done = false;
 }
 
-RawPacket.prototype = Heritage.extend(Packet.prototype, {
+RawPacket.prototype = Object.create(Packet.prototype);
 
-  read: function(stream) {
-    // This hasn't yet been needed for testing.
-    throw Error("Not implmented.");
-  },
+RawPacket.prototype.read = function(stream) {
+  // This hasn't yet been needed for testing.
+  throw Error("Not implmented.");
+}
 
-  write: function(stream) {
-    let written = stream.write(this._data, this._data.length);
-    this._data = this._data.slice(written);
-    this._done = !this._data.length;
-  },
+RawPacket.prototype.write = function(stream) {
+  let written = stream.write(this._data, this._data.length);
+  this._data = this._data.slice(written);
+  this._done = !this._data.length;
+}
 
-  get done() { return this._done; }
-
+Object.defineProperty(RawPacket.prototype, "done", {
+  get: function() { return this._done; }
 });
 
 exports.RawPacket = RawPacket;
--- a/toolkit/modules/tests/xpcshell/test_DirectoryLinksProvider.js
+++ b/toolkit/modules/tests/xpcshell/test_DirectoryLinksProvider.js
@@ -200,17 +200,17 @@ add_task(function test_fetchAndCacheLink
   // File should be empty.
   let data = yield readJsonFile();
   isIdentical(data, "");
 });
 
 add_task(function test_fetchAndCacheLinks_unknownHost() {
   yield DirectoryLinksProvider.init();
   yield cleanJsonFile();
-  let nonExistentServer = "http://nosuchhost";
+  let nonExistentServer = "http://nosuchhost.localhost";
   try {
     yield DirectoryLinksProvider._fetchAndCacheLinks(nonExistentServer);
     do_throw("BAD URIs should fail");
   } catch (e) {
     do_check_true(e.startsWith("Fetching " + nonExistentServer + " results in error code: "))
   }
 
   // File should be empty.
@@ -281,17 +281,17 @@ add_task(function test_DirectoryLinksPro
   let links = yield fetchData();
   do_check_eq(links.length, 1);
   let expectedData =  [{url: "http://example.com", title: "LocalSource", frecency: DIRECTORY_FRECENCY, lastVisitDate: 1, directoryIndex: 0}];
   isIdentical(links, expectedData);
 
   // tests these 2 things:
   // 1. _linksURL is properly set after the pref change
   // 2. invalid source url is correctly handled
-  let exampleUrl = 'http://nosuchhost/bad';
+  let exampleUrl = 'http://nosuchhost.localhost/bad';
   yield promiseDirectoryDownloadOnPrefChange(kSourceUrlPref, exampleUrl);
   do_check_eq(DirectoryLinksProvider._linksURL, exampleUrl);
 
   // since the download fail, the directory file must remain the same
   let newLinks = yield fetchData();
   isIdentical(newLinks, expectedData);
 
   // now remove the file, and re-download