Merge mozilla-central to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 18 Nov 2014 17:37:40 +0100
changeset 216361 c43ca53cd42bf6b4a5488304271955d0852ba07b
parent 216360 0794e627093c3681aab8d0583dd6642189b9c635 (current diff)
parent 216266 084441e904d1225f15d5f641cc0efb82d4ead4cd (diff)
child 216362 a4b2e94cd438e83c9ff23c431575108a961f7c48
push id52026
push userkwierso@gmail.com
push dateWed, 19 Nov 2014 02:37:17 +0000
treeherdermozilla-inbound@d197d16c0caa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone36.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 mozilla-central to b2g-inbound
dom/apps/tests/file_test_widget.js
dom/apps/tests/test_widget_browser.html
dom/browser-element/BrowserElementParent.js
dom/browser-element/nsIBrowserElementAPI.idl
dom/html/nsBrowserElement.cpp
dom/html/nsBrowserElement.h
dom/webidl/BrowserElement.webidl
netwerk/protocol/http/SpdyPush3.cpp
netwerk/protocol/http/SpdyPush3.h
netwerk/protocol/http/SpdySession3.cpp
netwerk/protocol/http/SpdySession3.h
netwerk/protocol/http/SpdyStream3.cpp
netwerk/protocol/http/SpdyStream3.h
security/manager/ssl/tests/unit/test_ev_certs/cert8.db
security/manager/ssl/tests/unit/test_ev_certs/int-ev-valid.p12
security/manager/ssl/tests/unit/test_ev_certs/int-non-ev-root.p12
security/manager/ssl/tests/unit/test_ev_certs/key3.db
security/manager/ssl/tests/unit/test_ev_certs/non-evroot-ca.p12
security/manager/ssl/tests/unit/test_ev_certs/secmod.db
testing/xpcshell/node-spdy/lib/spdy/parser.js
testing/xpcshell/node-spdy/lib/spdy/protocol/generic.js
testing/xpcshell/node-spdy/lib/spdy/protocol/v2/dictionary.js
testing/xpcshell/node-spdy/lib/spdy/protocol/v2/framer.js
testing/xpcshell/node-spdy/lib/spdy/protocol/v2/index.js
testing/xpcshell/node-spdy/lib/spdy/protocol/v2/protocol.js
testing/xpcshell/node-spdy/lib/spdy/protocol/v3/dictionary.js
testing/xpcshell/node-spdy/lib/spdy/protocol/v3/framer.js
testing/xpcshell/node-spdy/lib/spdy/protocol/v3/index.js
testing/xpcshell/node-spdy/lib/spdy/protocol/v3/protocol.js
testing/xpcshell/node-spdy/test/unit/framer-test.js
testing/xpcshell/node-spdy/test/unit/parser-test.js
testing/xpcshell/node-spdy/test/unit/server-test.js
--- a/accessible/jsat/Presentation.jsm
+++ b/accessible/jsat/Presentation.jsm
@@ -446,16 +446,28 @@ AndroidPresenter.prototype.announce =
 
 AndroidPresenter.prototype.liveRegion =
   function AndroidPresenter_liveRegion(aContext, aIsPolite,
     aIsHide, aModifiedText) {
     return this.announce(
       UtteranceGenerator.genForLiveRegion(aContext, aIsHide, aModifiedText));
   };
 
+AndroidPresenter.prototype.noMove =
+  function AndroidPresenter_noMove(aMoveMethod) {
+    return {
+      type: this.type,
+      details: [
+      { eventType: this.ANDROID_VIEW_ACCESSIBILITY_FOCUSED,
+        exitView: aMoveMethod,
+        text: ['']
+      }]
+    };
+  };
+
 /**
  * A B2G presenter for Gaia.
  */
 function B2GPresenter() {}
 
 B2GPresenter.prototype = Object.create(Presenter.prototype);
 
 B2GPresenter.prototype.type = 'B2G';
--- a/accessible/tests/mochitest/textattrs/test_general.html
+++ b/accessible/tests/mochitest/textattrs/test_general.html
@@ -704,22 +704,22 @@
     <span style="font-family: monospace;">text</span>text
     <span style="font-family: serif;">text</span>text
     <span style="font-family: BodoniThatDoesntExist;">text</span>text
     <span style="font-family: Comic Sans MS, cursive;">text</span>text
     <span style="font-family: sans-serif, fantasy;">text</span>text
   </p>
 
   <p id="area17">
-    <span style="-moz-text-decoration-line: underline;">underline
-    </span><span style="text-decoration: underline; -moz-text-decoration-color: blue;">blue
-    </span><span style="text-decoration: underline; -moz-text-decoration-style: dotted;">dotted
-    </span><span style="-moz-text-decoration-line: line-through;">linethrough
-    </span><span style="text-decoration: line-through; -moz-text-decoration-color: blue;">blue
-    </span><span style="text-decoration: line-through; -moz-text-decoration-style: wavy;">wavy
+    <span style="text-decoration-line: underline;">underline
+    </span><span style="text-decoration: underline; text-decoration-color: blue;">blue
+    </span><span style="text-decoration: underline; text-decoration-style: dotted;">dotted
+    </span><span style="text-decoration-line: line-through;">linethrough
+    </span><span style="text-decoration: line-through; text-decoration-color: blue;">blue
+    </span><span style="text-decoration: line-through; text-decoration-style: wavy;">wavy
     </span>
   </p>
 
   <ul>
     <li id="area18" class="gencontent">item</li>
   </ul>
 
   <p id="area19">uncolored
--- a/addon-sdk/moz.build
+++ b/addon-sdk/moz.build
@@ -166,16 +166,17 @@ EXTRA_JS_MODULES.commonjs.diffpatcher.te
     'source/lib/diffpatcher/test/common.js',
     'source/lib/diffpatcher/test/diff.js',
     'source/lib/diffpatcher/test/index.js',
     'source/lib/diffpatcher/test/patch.js',
     'source/lib/diffpatcher/test/tap.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.framescript += [
+    'source/lib/framescript/contextmenu-events.js',
     'source/lib/framescript/FrameScriptManager.jsm',
     'source/lib/framescript/LoaderHelper.jsm',
     'source/lib/framescript/tab-events.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.method += [
     'source/lib/method/core.js',
 ]
@@ -230,16 +231,17 @@ EXTRA_JS_MODULES.commonjs.sdk.browser +=
 EXTRA_JS_MODULES.commonjs.sdk.console += [
     'source/lib/sdk/console/plain-text.js',
     'source/lib/sdk/console/traceback.js',
 ]
 
 EXTRA_JS_MODULES.commonjs.sdk.content += [
     'source/lib/sdk/content/content-worker.js',
     'source/lib/sdk/content/content.js',
+    'source/lib/sdk/content/context-menu.js',
     'source/lib/sdk/content/events.js',
     'source/lib/sdk/content/loader.js',
     'source/lib/sdk/content/mod.js',
     'source/lib/sdk/content/sandbox.js',
     'source/lib/sdk/content/thumbnail.js',
     'source/lib/sdk/content/utils.js',
     'source/lib/sdk/content/worker-child.js',
     'source/lib/sdk/content/worker-parent.js',
--- a/addon-sdk/source/lib/framescript/FrameScriptManager.jsm
+++ b/addon-sdk/source/lib/framescript/FrameScriptManager.jsm
@@ -10,16 +10,26 @@ const globalMM = Components.classes["@mo
 // Since this JSM will be loaded using require(), PATH will be
 // overridden while running tests, just like any other module.
 const PATH = __URI__.replace('FrameScriptManager.jsm', '');
 
 // ensure frame scripts are loaded only once
 let loadedTabEvents = false;
 
 function enableTabEvents() {
-  if (loadedTabEvents) 
+  if (loadedTabEvents)
     return;
 
   loadedTabEvents = true;
   globalMM.loadFrameScript(PATH + 'tab-events.js', true);
 }
 
-const EXPORTED_SYMBOLS = ['enableTabEvents'];
+let loadedCMEvents = false;
+
+function enableCMEvents() {
+  if (loadedCMEvents)
+    return;
+
+  loadedCMEvents = true;
+  globalMM.loadFrameScript(PATH + 'contextmenu-events.js', true);
+}
+
+const EXPORTED_SYMBOLS = ['enableTabEvents', 'enableCMEvents'];
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/framescript/contextmenu-events.js
@@ -0,0 +1,63 @@
+/* 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 { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+const { Services } = Cu.import("resource://gre/modules/Services.jsm", {});
+
+// Holds remote items for this frame.
+let keepAlive = new Map();
+
+// Called to create remote proxies for items. If they already exist we destroy
+// and recreate. This cna happen if the item changes in some way or in odd
+// timing cases where the frame script is create around the same time as the
+// item is created in the main process
+addMessageListener('sdk/contextmenu/createitems', ({ data: { items, addon }}) => {
+  let { loader } = Cu.import(addon.paths[''] + 'framescript/LoaderHelper.jsm', {});
+
+  for (let itemoptions of items) {
+    let { RemoteItem } = loader(addon).require('sdk/content/context-menu');
+    let item = new RemoteItem(itemoptions, this);
+
+    let oldItem = keepAlive.get(item.id);
+    if (oldItem) {
+      oldItem.destroy();
+    }
+
+    keepAlive.set(item.id, item);
+  }
+});
+
+addMessageListener('sdk/contextmenu/destroyitems', ({ data: { items }}) => {
+  for (let id of items) {
+    let item = keepAlive.get(id);
+    item.destroy();
+    keepAlive.delete(id);
+  }
+});
+
+sendAsyncMessage('sdk/contextmenu/requestitems');
+
+Services.obs.addObserver(function(subject, topic, data) {
+  // Many frame scripts run in the same process, check that the context menu
+  // node is in this frame
+  let { event: { target: popupNode }, addonInfo } = subject.wrappedJSObject;
+  if (popupNode.ownerDocument.defaultView.top != content)
+    return;
+
+  for (let item of keepAlive.values()) {
+    item.getContextState(popupNode, addonInfo);
+  }
+}, "content-contextmenu", false);
+
+addMessageListener('sdk/contextmenu/activateitems', ({ data: { items, data }, objects: { popupNode }}) => {
+  for (let id of items) {
+    let item = keepAlive.get(id);
+    if (!item)
+      continue;
+
+    item.activate(popupNode, data);
+  }
+});
new file mode 100644
--- /dev/null
+++ b/addon-sdk/source/lib/sdk/content/context-menu.js
@@ -0,0 +1,354 @@
+/* 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 { Class } = require("../core/heritage");
+const self = require("../self");
+const { WorkerChild } = require("./worker-child");
+const { getInnerId } = require("../window/utils");
+const { Ci } = require("chrome");
+const { Services } = require("resource://gre/modules/Services.jsm");
+
+// These functions are roughly copied from sdk/selection which doesn't work
+// in the content process
+function getElementWithSelection(window) {
+  let element = Services.focus.getFocusedElementForWindow(window, false, {});
+  if (!element)
+    return null;
+
+  try {
+    // Accessing selectionStart and selectionEnd on e.g. a button
+    // results in an exception thrown as per the HTML5 spec.  See
+    // http://www.whatwg.org/specs/web-apps/current-work/multipage/association-of-controls-and-forms.html#textFieldSelection
+
+    let { value, selectionStart, selectionEnd } = element;
+
+    let hasSelection = typeof value === "string" &&
+                      !isNaN(selectionStart) &&
+                      !isNaN(selectionEnd) &&
+                      selectionStart !== selectionEnd;
+
+    return hasSelection ? element : null;
+  }
+  catch (err) {
+    console.exception(err);
+    return null;
+  }
+}
+
+function safeGetRange(selection, rangeNumber) {
+  try {
+    let { rangeCount } = selection;
+    let range = null;
+
+    for (let rangeNumber = 0; rangeNumber < rangeCount; rangeNumber++ ) {
+      range = selection.getRangeAt(rangeNumber);
+
+      if (range && range.toString())
+        break;
+
+      range = null;
+    }
+
+    return range;
+  }
+  catch (e) {
+    return null;
+  }
+}
+
+function getSelection(window) {
+  let selection = window.getSelection();
+  let range = safeGetRange(selection);
+  if (range)
+    return range.toString();
+
+  let node = getElementWithSelection(window);
+  if (!node)
+    return null;
+
+  return node.value.substring(node.selectionStart, node.selectionEnd);
+}
+
+//These are used by PageContext.isCurrent below. If the popupNode or any of
+//its ancestors is one of these, Firefox uses a tailored context menu, and so
+//the page context doesn't apply.
+const NON_PAGE_CONTEXT_ELTS = [
+  Ci.nsIDOMHTMLAnchorElement,
+  Ci.nsIDOMHTMLAppletElement,
+  Ci.nsIDOMHTMLAreaElement,
+  Ci.nsIDOMHTMLButtonElement,
+  Ci.nsIDOMHTMLCanvasElement,
+  Ci.nsIDOMHTMLEmbedElement,
+  Ci.nsIDOMHTMLImageElement,
+  Ci.nsIDOMHTMLInputElement,
+  Ci.nsIDOMHTMLMapElement,
+  Ci.nsIDOMHTMLMediaElement,
+  Ci.nsIDOMHTMLMenuElement,
+  Ci.nsIDOMHTMLObjectElement,
+  Ci.nsIDOMHTMLOptionElement,
+  Ci.nsIDOMHTMLSelectElement,
+  Ci.nsIDOMHTMLTextAreaElement,
+];
+
+// List all editable types of inputs.  Or is it better to have a list
+// of non-editable inputs?
+let editableInputs = {
+  email: true,
+  number: true,
+  password: true,
+  search: true,
+  tel: true,
+  text: true,
+  textarea: true,
+  url: true
+};
+
+let CONTEXTS = {};
+
+let Context = Class({
+  initialize: function(id) {
+    this.id = id;
+  },
+
+  adjustPopupNode: function adjustPopupNode(popupNode) {
+    return popupNode;
+  },
+
+  // Gets state to pass through to the parent process for the node the user
+  // clicked on
+  getState: function(popupNode) {
+    return false;
+  }
+});
+
+// Matches when the context-clicked node doesn't have any of
+// NON_PAGE_CONTEXT_ELTS in its ancestors
+CONTEXTS.PageContext = Class({
+  extends: Context,
+
+  getState: function(popupNode) {
+    // If there is a selection in the window then this context does not match
+    if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed)
+      return false;
+
+    // If the clicked node or any of its ancestors is one of the blacklisted
+    // NON_PAGE_CONTEXT_ELTS then this context does not match
+    while (!(popupNode instanceof Ci.nsIDOMDocument)) {
+      if (NON_PAGE_CONTEXT_ELTS.some(function(type) popupNode instanceof type))
+        return false;
+
+      popupNode = popupNode.parentNode;
+    }
+
+    return true;
+  }
+});
+
+// Matches when there is an active selection in the window
+CONTEXTS.SelectionContext = Class({
+  extends: Context,
+
+  getState: function(popupNode) {
+    if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed)
+      return true;
+
+    try {
+      // The node may be a text box which has selectionStart and selectionEnd
+      // properties. If not this will throw.
+      let { selectionStart, selectionEnd } = popupNode;
+      return !isNaN(selectionStart) && !isNaN(selectionEnd) &&
+             selectionStart !== selectionEnd;
+    }
+    catch (e) {
+      return false;
+    }
+  }
+});
+
+// Matches when the context-clicked node or any of its ancestors matches the
+// selector given
+CONTEXTS.SelectorContext = Class({
+  extends: Context,
+
+  initialize: function initialize(id, selector) {
+    Context.prototype.initialize.call(this, id);
+    this.selector = selector;
+  },
+
+  adjustPopupNode: function adjustPopupNode(popupNode) {
+    let selector = this.selector;
+
+    while (!(popupNode instanceof Ci.nsIDOMDocument)) {
+      if (popupNode.mozMatchesSelector(selector))
+        return popupNode;
+
+      popupNode = popupNode.parentNode;
+    }
+
+    return null;
+  },
+
+  getState: function(popupNode) {
+    return !!this.adjustPopupNode(popupNode);
+  }
+});
+
+// Matches when the page url matches any of the patterns given
+CONTEXTS.URLContext = Class({
+  extends: Context,
+
+  getState: function(popupNode) {
+    return popupNode.ownerDocument.URL;
+  }
+});
+
+// Matches when the user-supplied predicate returns true
+CONTEXTS.PredicateContext = Class({
+  extends: Context,
+
+  getState: function(node) {
+    let window = node.ownerDocument.defaultView;
+    let data = {};
+
+    data.documentType = node.ownerDocument.contentType;
+
+    data.documentURL = node.ownerDocument.location.href;
+    data.targetName = node.nodeName.toLowerCase();
+    data.targetID = node.id || null ;
+
+    if ((data.targetName === 'input' && editableInputs[node.type]) ||
+        data.targetName === 'textarea') {
+      data.isEditable = !node.readOnly && !node.disabled;
+    }
+    else {
+      data.isEditable = node.isContentEditable;
+    }
+
+    data.selectionText = getSelection(window, "TEXT");
+
+    data.srcURL = node.src || null;
+    data.value = node.value || null;
+
+    while (!data.linkURL && node) {
+      data.linkURL = node.href || null;
+      node = node.parentNode;
+    }
+
+    return data;
+  },
+});
+
+function instantiateContext({ id, type, args }) {
+  if (!(type in CONTEXTS)) {
+    console.error("Attempt to use unknown context " + type);
+    return;
+  }
+  return new CONTEXTS[type](id, ...args);
+}
+
+let ContextWorker = Class({
+  implements: [ WorkerChild ],
+
+  // Calls the context workers context listeners and returns the first result
+  // that is either a string or a value that evaluates to true. If all of the
+  // listeners returned false then returns false. If there are no listeners,
+  // returns true (show the menu item by default).
+  getMatchedContext: function getCurrentContexts(popupNode) {
+    let results = this.sandbox.emitSync("context", popupNode);
+    if (!results.length)
+      return true;
+    return results.reduce((val, result) => val || result);
+  },
+
+  // Emits a click event in the worker's port. popupNode is the node that was
+  // context-clicked, and clickedItemData is the data of the item that was
+  // clicked.
+  fireClick: function fireClick(popupNode, clickedItemData) {
+    this.sandbox.emitSync("click", popupNode, clickedItemData);
+  }
+});
+
+// Gets the item's content script worker for a window, creating one if necessary
+// Once created it will be automatically destroyed when the window unloads.
+// If there is not content scripts for the item then null will be returned.
+function getItemWorkerForWindow(item, window) {
+  if (!item.contentScript && !item.contentScriptFile)
+    return null;
+
+  let id = getInnerId(window);
+  let worker = item.workerMap.get(id);
+
+  if (worker)
+    return worker;
+
+  worker = ContextWorker({
+    id: item.id,
+    window: id,
+    manager: item.manager,
+    contentScript: item.contentScript,
+    contentScriptFile: item.contentScriptFile,
+    onDetach: function() {
+      item.workerMap.delete(id);
+    }
+  });
+
+  item.workerMap.set(id, worker);
+
+  return worker;
+}
+
+// A very simple remote proxy for every item. It's job is to provide data for
+// the main process to use to determine visibility state and to call into
+// content scripts when clicked.
+let RemoteItem = Class({
+  initialize: function(options, manager) {
+    this.id = options.id;
+    this.contexts = [instantiateContext(c) for (c of options.contexts)];
+    this.contentScript = options.contentScript;
+    this.contentScriptFile = options.contentScriptFile;
+
+    this.manager = manager;
+
+    this.workerMap = new Map();
+  },
+
+  destroy: function() {
+    for (let worker of this.workerMap.values()) {
+      worker.destroy();
+    }
+  },
+
+  activate: function(popupNode, data) {
+    let worker = getItemWorkerForWindow(this, popupNode.ownerDocument.defaultView);
+    if (!worker)
+      return;
+
+    for (let context of this.contexts)
+      popupNode = context.adjustPopupNode(popupNode);
+
+    worker.fireClick(popupNode, data);
+  },
+
+  // Fills addonInfo with state data to send through to the main process
+  getContextState: function(popupNode, addonInfo) {
+    if (!(self.id in addonInfo))
+      addonInfo[self.id] = {};
+
+    let worker = getItemWorkerForWindow(this, popupNode.ownerDocument.defaultView);
+    let contextStates = {};
+    for (let context of this.contexts)
+      contextStates[context.id] = context.getState(popupNode);
+
+    addonInfo[self.id][this.id] = {
+      // It isn't ideal to create a PageContext for every item but there isn't
+      // a good shared place to do it.
+      pageContext: (new CONTEXTS.PageContext()).getState(popupNode),
+      contextStates,
+      hasWorker: !!worker,
+      workerContext: worker ? worker.getMatchedContext(popupNode) : true
+    }
+  }
+});
+exports.RemoteItem = RemoteItem;
--- a/addon-sdk/source/lib/sdk/context-menu.js
+++ b/addon-sdk/source/lib/sdk/context-menu.js
@@ -14,24 +14,30 @@ module.metadata = {
 
 const { Class, mix } = require("./core/heritage");
 const { addCollectionProperty } = require("./util/collection");
 const { ns } = require("./core/namespace");
 const { validateOptions, getTypeOf } = require("./deprecated/api-utils");
 const { URL, isValidURI } = require("./url");
 const { WindowTracker, browserWindowIterator } = require("./deprecated/window-utils");
 const { isBrowser, getInnerId } = require("./window/utils");
-const { Ci } = require("chrome");
+const { Ci, Cc, Cu } = require("chrome");
 const { MatchPattern } = require("./util/match-pattern");
 const { Worker } = require("./content/worker");
 const { EventTarget } = require("./event/target");
 const { emit } = require('./event/core');
 const { when } = require('./system/unload');
-const selection = require('./selection');
 const { contract: loaderContract } = require('./content/loader');
+const { omit } = require('./util/object');
+const self = require('./self')
+
+// null-out cycles in .modules to make @loader/options JSONable
+const ADDON = omit(require('@loader/options'), ['modules', 'globals']);
+
+require('../framescript/FrameScriptManager.jsm').enableCMEvents();
 
 // All user items we add have this class.
 const ITEM_CLASS = "addon-context-menu-item";
 
 // Items in the top-level context menu also have this class.
 const TOPLEVEL_ITEM_CLASS = "addon-context-menu-item-toplevel";
 
 // Items in the overflow submenu also have this class.
@@ -54,229 +60,166 @@ const OVERFLOW_MENU_LABEL = "Add-ons";
 const OVERFLOW_MENU_ACCESSKEY = "A";
 
 // The class of the overflow sub-xul:menu.
 const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu";
 
 // The class of the overflow submenu's xul:menupopup.
 const OVERFLOW_POPUP_CLASS = "addon-content-menu-overflow-popup";
 
-//These are used by PageContext.isCurrent below. If the popupNode or any of
-//its ancestors is one of these, Firefox uses a tailored context menu, and so
-//the page context doesn't apply.
-const NON_PAGE_CONTEXT_ELTS = [
-  Ci.nsIDOMHTMLAnchorElement,
-  Ci.nsIDOMHTMLAppletElement,
-  Ci.nsIDOMHTMLAreaElement,
-  Ci.nsIDOMHTMLButtonElement,
-  Ci.nsIDOMHTMLCanvasElement,
-  Ci.nsIDOMHTMLEmbedElement,
-  Ci.nsIDOMHTMLImageElement,
-  Ci.nsIDOMHTMLInputElement,
-  Ci.nsIDOMHTMLMapElement,
-  Ci.nsIDOMHTMLMediaElement,
-  Ci.nsIDOMHTMLMenuElement,
-  Ci.nsIDOMHTMLObjectElement,
-  Ci.nsIDOMHTMLOptionElement,
-  Ci.nsIDOMHTMLSelectElement,
-  Ci.nsIDOMHTMLTextAreaElement,
-];
-
 // Holds private properties for API objects
 let internal = ns();
 
+function uuid() {
+  return require('./util/uuid').uuid().toString();
+}
+
 function getScheme(spec) {
   try {
     return URL(spec).scheme;
   }
   catch(e) {
     return null;
   }
 }
 
+let MessageManager = Cc["@mozilla.org/globalmessagemanager;1"].
+                     getService(Ci.nsIMessageBroadcaster);
+
 let Context = Class({
+  initialize: function() {
+    internal(this).id = uuid();
+  },
+
   // Returns the node that made this context current
   adjustPopupNode: function adjustPopupNode(popupNode) {
     return popupNode;
   },
 
   // Returns whether this context is current for the current node
-  isCurrent: function isCurrent(popupNode) {
-    return false;
+  isCurrent: function isCurrent(state) {
+    return state;
   }
 });
 
 // Matches when the context-clicked node doesn't have any of
 // NON_PAGE_CONTEXT_ELTS in its ancestors
 let PageContext = Class({
   extends: Context,
 
-  isCurrent: function isCurrent(popupNode) {
-    // If there is a selection in the window then this context does not match
-    if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed)
-      return false;
-
-    // If the clicked node or any of its ancestors is one of the blacklisted
-    // NON_PAGE_CONTEXT_ELTS then this context does not match
-    while (!(popupNode instanceof Ci.nsIDOMDocument)) {
-      if (NON_PAGE_CONTEXT_ELTS.some(function(type) popupNode instanceof type))
-        return false;
-
-      popupNode = popupNode.parentNode;
+  serialize: function() {
+    return {
+      id: internal(this).id,
+      type: "PageContext",
+      args: []
     }
-
-    return true;
   }
 });
 exports.PageContext = PageContext;
 
 // Matches when there is an active selection in the window
 let SelectionContext = Class({
   extends: Context,
 
-  isCurrent: function isCurrent(popupNode) {
-    if (!popupNode.ownerDocument.defaultView.getSelection().isCollapsed)
-      return true;
-
-    try {
-      // The node may be a text box which has selectionStart and selectionEnd
-      // properties. If not this will throw.
-      let { selectionStart, selectionEnd } = popupNode;
-      return !isNaN(selectionStart) && !isNaN(selectionEnd) &&
-             selectionStart !== selectionEnd;
-    }
-    catch (e) {
-      return false;
+  serialize: function() {
+    return {
+      id: internal(this).id,
+      type: "SelectionContext",
+      args: []
     }
   }
 });
 exports.SelectionContext = SelectionContext;
 
 // Matches when the context-clicked node or any of its ancestors matches the
 // selector given
 let SelectorContext = Class({
   extends: Context,
 
   initialize: function initialize(selector) {
+    Context.prototype.initialize.call(this);
     let options = validateOptions({ selector: selector }, {
       selector: {
         is: ["string"],
         msg: "selector must be a string."
       }
     });
     internal(this).selector = options.selector;
   },
 
-  adjustPopupNode: function adjustPopupNode(popupNode) {
-    let selector = internal(this).selector;
-
-    while (!(popupNode instanceof Ci.nsIDOMDocument)) {
-      if (popupNode.mozMatchesSelector(selector))
-        return popupNode;
-
-      popupNode = popupNode.parentNode;
+  serialize: function() {
+    return {
+      id: internal(this).id,
+      type: "SelectorContext",
+      args: [internal(this).selector]
     }
-
-    return null;
-  },
-
-  isCurrent: function isCurrent(popupNode) {
-    return !!this.adjustPopupNode(popupNode);
   }
 });
 exports.SelectorContext = SelectorContext;
 
 // Matches when the page url matches any of the patterns given
 let URLContext = Class({
   extends: Context,
 
   initialize: function initialize(patterns) {
+    Context.prototype.initialize.call(this);
     patterns = Array.isArray(patterns) ? patterns : [patterns];
 
     try {
       internal(this).patterns = patterns.map(function (p) new MatchPattern(p));
     }
     catch (err) {
       throw new Error("Patterns must be a string, regexp or an array of " +
                       "strings or regexps: " + err);
     }
+  },
 
+  isCurrent: function isCurrent(url) {
+    return internal(this).patterns.some(function (p) p.test(url));
   },
 
-  isCurrent: function isCurrent(popupNode) {
-    let url = popupNode.ownerDocument.URL;
-    return internal(this).patterns.some(function (p) p.test(url));
+  serialize: function() {
+    return {
+      id: internal(this).id,
+      type: "URLContext",
+      args: []
+    }
   }
 });
 exports.URLContext = URLContext;
 
 // Matches when the user-supplied predicate returns true
 let PredicateContext = Class({
   extends: Context,
 
   initialize: function initialize(predicate) {
+    Context.prototype.initialize.call(this);
     let options = validateOptions({ predicate: predicate }, {
       predicate: {
         is: ["function"],
         msg: "predicate must be a function."
       }
     });
     internal(this).predicate = options.predicate;
   },
 
-  isCurrent: function isCurrent(popupNode) {
-    return internal(this).predicate(populateCallbackNodeData(popupNode));
+  isCurrent: function isCurrent(state) {
+    return internal(this).predicate(state);
+  },
+
+  serialize: function() {
+    return {
+      id: internal(this).id,
+      type: "PredicateContext",
+      args: []
+    }
   }
 });
 exports.PredicateContext = PredicateContext;
 
-// List all editable types of inputs.  Or is it better to have a list
-// of non-editable inputs?
-let editableInputs = {
-  email: true,
-  number: true,
-  password: true,
-  search: true,
-  tel: true,
-  text: true,
-  textarea: true,
-  url: true
-};
-
-function populateCallbackNodeData(node) {
-  let window = node.ownerDocument.defaultView;
-  let data = {};
-
-  data.documentType = node.ownerDocument.contentType;
-
-  data.documentURL = node.ownerDocument.location.href;
-  data.targetName = node.nodeName.toLowerCase();
-  data.targetID = node.id || null ;
-
-  if ((data.targetName === 'input' && editableInputs[node.type]) ||
-      data.targetName === 'textarea') {
-    data.isEditable = !node.readOnly && !node.disabled;
-  }
-  else {
-    data.isEditable = node.isContentEditable;
-  }
-
-  data.selectionText = selection.text;
-
-  data.srcURL = node.src || null;
-  data.value = node.value || null;
-
-  while (!data.linkURL && node) {
-    data.linkURL = node.href || null;
-    node = node.parentNode;
-  }
-
-  return data;
-}
-
 function removeItemFromArray(array, item) {
   return array.filter(function(i) i !== item);
 }
 
 // Converts anything that isn't false, null or undefined into a string
 function stringOrNull(val) val ? String(val) : val;
 
 // Shared option validation rules for Item, Menu, and Separator
@@ -357,183 +300,191 @@ let menuRules = mix(labelledItemRules, {
         return item instanceof BaseItem;
       });
     },
     msg: "items must be an array, and each element in the array must be an " +
          "Item, Menu, or Separator."
   }
 });
 
-let ContextWorker = Class({
-  implements: [ Worker ],
-
-  // Calls the context workers context listeners and returns the first result
-  // that is either a string or a value that evaluates to true. If all of the
-  // listeners returned false then returns false. If there are no listeners,
-  // returns true (show the menu item by default).
-  getMatchedContext: function getCurrentContexts(popupNode) {
-    let results = this.getSandbox().emitSync("context", popupNode);
-    if (!results.length)
-      return true;
-    return results.reduce((val, result) => val || result);
-  },
-
-  // Emits a click event in the worker's port. popupNode is the node that was
-  // context-clicked, and clickedItemData is the data of the item that was
-  // clicked.
-  fireClick: function fireClick(popupNode, clickedItemData) {
-    this.getSandbox().emitSync("click", popupNode, clickedItemData);
-  }
-});
-
 // Returns true if any contexts match. If there are no contexts then a
 // PageContext is tested instead
-function hasMatchingContext(contexts, popupNode) {
-  for (let context in contexts) {
-    if (!context.isCurrent(popupNode))
+function hasMatchingContext(contexts, addonInfo) {
+  for (let context of contexts) {
+    if (!(internal(context).id in addonInfo.contextStates)) {
+      console.error("Missing state for context " + internal(context).id + " this is an error in the SDK modules.");
+      return false;
+    }
+    if (!context.isCurrent(addonInfo.contextStates[internal(context).id]))
       return false;
   }
 
   return true;
 }
 
-// Gets the matched context from any worker for this item. If there is no worker
-// or no matched context then returns false.
-function getCurrentWorkerContext(item, popupNode) {
-  let worker = getItemWorkerForWindow(item, popupNode.ownerDocument.defaultView);
-  if (!worker)
-    return true;
-  return worker.getMatchedContext(popupNode);
-}
-
 // Tests whether an item should be visible or not based on its contexts and
 // content scripts
-function isItemVisible(item, popupNode, defaultVisibility) {
+function isItemVisible(item, addonInfo, usePageWorker) {
   if (!item.context.length) {
-    let worker = getItemWorkerForWindow(item, popupNode.ownerDocument.defaultView);
-    if (!worker)
-      return defaultVisibility;
+    if (!addonInfo.hasWorker)
+      return usePageWorker ? addonInfo.pageContext : true;
   }
 
-  if (!hasMatchingContext(item.context, popupNode))
+  if (!hasMatchingContext(item.context, addonInfo))
     return false;
 
-  let context = getCurrentWorkerContext(item, popupNode);
+  let context = addonInfo.workerContext;
   if (typeof(context) === "string" && context != "")
     item.label = context;
 
   return !!context;
 }
 
-// Gets the item's content script worker for a window, creating one if necessary
-// Once created it will be automatically destroyed when the window unloads.
-// If there is not content scripts for the item then null will be returned.
-function getItemWorkerForWindow(item, window) {
-  if (!item.contentScript && !item.contentScriptFile)
-    return null;
+// Called when an item is clicked to send out click events to the content
+// scripts
+function itemActivated(item, clickedNode) {
+  let data = {
+    items: [internal(item).id],
+    data: item.data,
+  }
 
-  let id = getInnerId(window);
-  let worker = internal(item).workerMap.get(id);
+  while (item.parentMenu) {
+    item = item.parentMenu;
+    data.items.push(internal(item).id);
+  }
 
-  if (worker)
-    return worker;
+  let menuData = clickedNode.ownerDocument.defaultView.gContextMenuContentData;
+  let messageManager = menuData.browser.messageManager;
+  messageManager.sendAsyncMessage('sdk/contextmenu/activateitems', data, {
+    popupNode: menuData.popupNode
+  });
+}
 
-  worker = ContextWorker({
-    window: window,
+function serializeItem(item) {
+  return {
+    id: internal(item).id,
+    contexts: [c.serialize() for (c of item.context)],
     contentScript: item.contentScript,
     contentScriptFile: item.contentScriptFile,
-    onMessage: function(msg) {
-      emit(item, "message", msg);
-    },
-    onDetach: function() {
-      internal(item).workerMap.delete(id);
-    }
-  });
-
-  internal(item).workerMap.set(id, worker);
-
-  return worker;
-}
-
-// Called when an item is clicked to send out click events to the content
-// scripts
-function itemActivated(item, clickedItem, popupNode) {
-  let worker = getItemWorkerForWindow(item, popupNode.ownerDocument.defaultView);
-
-  if (worker) {
-    let adjustedNode = popupNode;
-    for (let context in item.context)
-        adjustedNode = context.adjustPopupNode(adjustedNode);
-    worker.fireClick(adjustedNode, clickedItem.data);
-  }
-
-  if (item.parentMenu)
-    itemActivated(item.parentMenu, clickedItem, popupNode);
+  };
 }
 
 // All things that appear in the context menu extend this
 let BaseItem = Class({
   initialize: function initialize() {
-    addCollectionProperty(this, "context");
+    internal(this).id = uuid();
 
-    // Used to cache content script workers and the windows they have been
-    // created for
-    internal(this).workerMap = new Map();
-
+    internal(this).contexts = [];
     if ("context" in internal(this).options && internal(this).options.context) {
       let contexts = internal(this).options.context;
       if (Array.isArray(contexts)) {
         for (let context of contexts)
-          this.context.add(context);
+          internal(this).contexts.push(context);
       }
       else {
-        this.context.add(contexts);
+        internal(this).contexts.push(contexts);
       }
     }
 
     let parentMenu = internal(this).options.parentMenu;
     if (!parentMenu)
       parentMenu = contentContextMenu;
 
     parentMenu.addItem(this);
 
     Object.defineProperty(this, "contentScript", {
       enumerable: true,
       value: internal(this).options.contentScript
     });
 
+    // Resolve URIs here as tests may have overriden self
+    let files = internal(this).options.contentScriptFile;
+    if (files) {
+      if (!Array.isArray(files))
+        files = [files];
+      files = files.map(self.data.url);
+    }
+    internal(this).options.contentScriptFile = files;
     Object.defineProperty(this, "contentScriptFile", {
       enumerable: true,
       value: internal(this).options.contentScriptFile
     });
+
+    // Notify all frames of this new item
+    sendItems([serializeItem(this)]);
   },
 
   destroy: function destroy() {
+    if (internal(this).destroyed)
+      return;
+
+    // Tell all existing frames that this item has been destroyed
+    MessageManager.broadcastAsyncMessage("sdk/contextmenu/destroyitems", {
+      items: [internal(this).id]
+    });
+
     if (this.parentMenu)
       this.parentMenu.removeItem(this);
+
+    internal(this).destroyed = true;
+  },
+
+  get context() {
+    let contexts = internal(this).contexts.slice(0);
+    contexts.add = (context) => {
+      internal(this).contexts.push(context);
+      // Notify all frames that this item has changed
+      sendItems([serializeItem(this)]);
+    };
+    contexts.remove = (context) => {
+      internal(this).contexts = internal(this).contexts.filter(c => {
+        return c != context;
+      });
+      // Notify all frames that this item has changed
+      sendItems([serializeItem(this)]);
+    };
+    return contexts;
+  },
+
+  set context(val) {
+    internal(this).contexts = val.slice(0);
+    // Notify all frames that this item has changed
+    sendItems([serializeItem(this)]);
   },
 
   get parentMenu() {
     return internal(this).parentMenu;
   },
 });
 
+function workerMessageReceived({ data: { id, args } }) {
+  if (internal(this).id != id)
+    return;
+
+  emit(this, ...args);
+}
+
 // All things that have a label on the context menu extend this
 let LabelledItem = Class({
   extends: BaseItem,
   implements: [ EventTarget ],
 
   initialize: function initialize(options) {
     BaseItem.prototype.initialize.call(this);
     EventTarget.prototype.initialize.call(this, options);
+
+    internal(this).messageListener = workerMessageReceived.bind(this);
+    MessageManager.addMessageListener('sdk/worker/event', internal(this).messageListener);
   },
 
   destroy: function destroy() {
-    for (let [,worker] of internal(this).workerMap)
-      worker.destroy();
+    if (internal(this).destroyed)
+      return;
+
+    MessageManager.removeMessageListener('sdk/worker/event', internal(this).messageListener);
 
     BaseItem.prototype.destroy.call(this);
   },
 
   get label() {
     return internal(this).options.label;
   },
 
@@ -707,17 +658,49 @@ let Separator = Class({
   }
 });
 exports.Separator = Separator;
 
 // Holds items for the content area context menu
 let contentContextMenu = ItemContainer();
 exports.contentContextMenu = contentContextMenu;
 
+function getContainerItems(container) {
+  let items = [];
+  for (let item of internal(container).children) {
+    items.push(serializeItem(item));
+    if (item instanceof Menu)
+      items = items.concat(getContainerItems(item));
+  }
+  return items;
+}
+
+// Notify all frames of these new or changed items
+function sendItems(items) {
+  MessageManager.broadcastAsyncMessage("sdk/contextmenu/createitems", {
+    items,
+    addon: ADDON,
+  });
+}
+
+// Called when a new frame is created and wants to get the current list of items
+function remoteItemRequest({ target: { messageManager } }) {
+  let items = getContainerItems(contentContextMenu);
+  if (items.length == 0)
+    return;
+
+  messageManager.sendAsyncMessage("sdk/contextmenu/createitems", {
+    items,
+    addon: ADDON,
+  });
+}
+MessageManager.addMessageListener('sdk/contextmenu/requestitems', remoteItemRequest);
+
 when(function() {
+  MessageManager.removeMessageListener('sdk/contextmenu/requestitems', remoteItemRequest);
   contentContextMenu.destroy();
 });
 
 // App specific UI code lives here, it should handle populating the context
 // menu and passing clicks etc. through to the items.
 
 function countVisibleItems(nodes) {
   return Array.reduce(nodes, function(sum, node) {
@@ -795,26 +778,26 @@ let MenuWrapper = Class({
 
       if (item instanceof Menu)
         this.populate(item);
     }
   },
 
   // Recurses through the menu setting the visibility of items. Returns true
   // if any of the items in this menu were visible
-  setVisibility: function setVisibility(menu, popupNode, defaultVisibility) {
+  setVisibility: function setVisibility(menu, addonInfo, usePageWorker) {
     let anyVisible = false;
 
     for (let item of internal(menu).children) {
-      let visible = isItemVisible(item, popupNode, defaultVisibility);
+      let visible = isItemVisible(item, addonInfo[internal(item).id], usePageWorker);
 
       // Recurse through Menus, if none of the sub-items were visible then the
       // menu is hidden too.
       if (visible && (item instanceof Menu))
-        visible = this.setVisibility(item, popupNode, true);
+        visible = this.setVisibility(item, addonInfo, false);
 
       let xulNode = this.getXULNodeForItem(item);
       xulNode.hidden = !visible;
 
       anyVisible = anyVisible || visible;
     }
 
     return anyVisible;
@@ -907,17 +890,17 @@ let MenuWrapper = Class({
         xulNode.setAttribute("value", item.data);
 
       let self = this;
       xulNode.addEventListener("command", function(event) {
         // Only care about clicks directly on this item
         if (event.target !== xulNode)
           return;
 
-        itemActivated(item, item, self.contextMenu.triggerNode);
+        itemActivated(item, xulNode);
       }, false);
     }
 
     this.insertIntoXUL(item, xulNode, after);
     this.updateXULClass(xulNode);
     xulNode.data = item.data;
 
     if (item instanceof Menu) {
@@ -1022,18 +1005,24 @@ let MenuWrapper = Class({
       if (internal(this.items).children.length == 0)
         return;
 
       if (!this.populated) {
         this.populated = true;
         this.populate(this.items);
       }
 
-      let popupNode = event.target.triggerNode;
-      this.setVisibility(this.items, popupNode, PageContext().isCurrent(popupNode));
+      let mainWindow = event.target.ownerDocument.defaultView;
+      this.contextMenuContentData = mainWindow.gContextMenuContentData
+      let addonInfo = this.contextMenuContentData.addonInfo[self.id];
+      if (!addonInfo) {
+        console.warn("No context menu state data was provided.");
+        return;
+      }
+      this.setVisibility(this.items, addonInfo, true);
     }
     catch (e) {
       console.exception(e);
     }
   },
 
   // Counts the number of visible items across all modules and makes sure they
   // are in the right place between the top level context menu and the overflow
--- a/addon-sdk/source/test/test-context-menu.js
+++ b/addon-sdk/source/test/test-context-menu.js
@@ -6,16 +6,17 @@
 let { Cc, Ci } = require("chrome");
 
 require("sdk/context-menu");
 
 const { Loader } = require('sdk/test/loader');
 const timer = require("sdk/timers");
 const { merge } = require("sdk/util/object");
 const { defer } = require("sdk/core/promise");
+const observers = require("sdk/system/events");
 
 // These should match the same constants in the module.
 const ITEM_CLASS = "addon-context-menu-item";
 const SEPARATOR_CLASS = "addon-context-menu-separator";
 const OVERFLOW_THRESH_DEFAULT = 10;
 const OVERFLOW_THRESH_PREF =
   "extensions.addon-sdk.context-menu.overflowThreshold";
 const OVERFLOW_MENU_CLASS = "addon-content-menu-overflow-menu";
@@ -98,17 +99,17 @@ exports.testSelectorContextMatch = funct
 
   let item = new loader.cm.Item({
     label: "item",
     data: "item",
     context: loader.cm.SelectorContext("img")
   });
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("image"), function (popup) {
+    test.showMenu("#image", function (popup) {
       test.checkMenu([item], [], []);
       test.done();
     });
   });
 };
 
 
 // CSS selector contexts should cause their items to be present in the menu
@@ -120,17 +121,17 @@ exports.testSelectorAncestorContextMatch
 
   let item = new loader.cm.Item({
     label: "item",
     data: "item",
     context: loader.cm.SelectorContext("a[href]")
   });
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("span-link"), function (popup) {
+    test.showMenu("#span-link", function (popup) {
       test.checkMenu([item], [], []);
       test.done();
     });
   });
 };
 
 
 // CSS selector contexts should cause their items to be absent from the menu
@@ -204,17 +205,17 @@ exports.testPageContextNoMatch = functio
     }),
     new loader.cm.Item({
       label: "item 3",
       context: [loader.cm.PageContext()]
     })
   ];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("image"), function (popup) {
+    test.showMenu("#image", function (popup) {
       test.checkMenu(items, items, []);
       test.done();
     });
   });
 };
 
 
 // Selection contexts should cause items to appear when a selection exists.
@@ -244,19 +245,18 @@ exports.testSelectionContextMatchInTextF
   let loader = test.newLoader();
 
   let item = loader.cm.Item({
     label: "item",
     context: loader.cm.SelectionContext()
   });
 
   test.withTestDoc(function (window, doc) {
-    let textfield = doc.getElementById("textfield");
-    textfield.setSelectionRange(0, textfield.value.length);
-    test.showMenu(textfield, function (popup) {
+    test.selectRange("#textfield", 0, null);
+    test.showMenu("#textfield", function (popup) {
       test.checkMenu([item], [], []);
       test.done();
     });
   });
 };
 
 
 // Selection contexts should not cause items to appear when a selection does
@@ -266,19 +266,18 @@ exports.testSelectionContextNoMatchInTex
   let loader = test.newLoader();
 
   let item = loader.cm.Item({
     label: "item",
     context: loader.cm.SelectionContext()
   });
 
   test.withTestDoc(function (window, doc) {
-    let textfield = doc.getElementById("textfield");
-    textfield.setSelectionRange(0, 0);
-    test.showMenu(textfield, function (popup) {
+    test.selectRange("#textfield", 0, 0);
+    test.showMenu("#textfield", function (popup) {
       test.checkMenu([item], [item], []);
       test.done();
     });
   });
 };
 
 
 // Selection contexts should not cause items to appear when a selection does
@@ -309,53 +308,58 @@ exports.testSelectionContextInNewTab = f
     label: "item",
     context: loader.cm.SelectionContext()
   });
 
   test.withTestDoc(function (window, doc) {
     let link = doc.getElementById("targetlink");
     link.click();
 
-    test.delayedEventListener(this.tabBrowser, "load", function () {
-      let browser = test.tabBrowser.selectedBrowser;
-      let window = browser.contentWindow;
-      let doc = browser.contentDocument;
-      window.getSelection().selectAllChildren(doc.body);
-
-      test.showMenu(null, function (popup) {
-        test.checkMenu([item], [], []);
-        popup.hidePopup();
-
-        test.tabBrowser.removeTab(test.tabBrowser.selectedTab);
-        test.tabBrowser.selectedTab = test.tab;
+    let tablistener = event => {
+      this.tabBrowser.tabContainer.removeEventListener("TabOpen", tablistener, false);
+      let tab = event.target;
+      let browser = tab.linkedBrowser;
+      this.loadFrameScript(browser);
+      this.delayedEventListener(browser, "load", () => {
+        let window = browser.contentWindow;
+        let doc = browser.contentDocument;
+        window.getSelection().selectAllChildren(doc.body);
 
         test.showMenu(null, function (popup) {
-          test.checkMenu([item], [item], []);
-          test.done();
+          test.checkMenu([item], [], []);
+          popup.hidePopup();
+
+          test.tabBrowser.removeTab(test.tabBrowser.selectedTab);
+          test.tabBrowser.selectedTab = test.tab;
+
+          test.showMenu(null, function (popup) {
+            test.checkMenu([item], [item], []);
+            test.done();
+          });
         });
-      });
-    }, true);
+      }, true);
+    };
+    this.tabBrowser.tabContainer.addEventListener("TabOpen", tablistener, false);
   });
 };
 
 
 // Selection contexts should work when right clicking a form button
 exports.testSelectionContextButtonMatch = function (assert, done) {
   let test = new TestHelper(assert, done);
   let loader = test.newLoader();
 
   let item = loader.cm.Item({
     label: "item",
     context: loader.cm.SelectionContext()
   });
 
   test.withTestDoc(function (window, doc) {
     window.getSelection().selectAllChildren(doc.body);
-    let button = doc.getElementById("button");
-    test.showMenu(button, function (popup) {
+    test.showMenu("#button", function (popup) {
       test.checkMenu([item], [], []);
       test.done();
     });
   });
 };
 
 
 //Selection contexts should work when right clicking a form button
@@ -364,18 +368,17 @@ exports.testSelectionContextButtonNoMatc
   let loader = test.newLoader();
 
   let item = loader.cm.Item({
     label: "item",
     context: loader.cm.SelectionContext()
   });
 
   test.withTestDoc(function (window, doc) {
-    let button = doc.getElementById("button");
-    test.showMenu(button, function (popup) {
+    test.showMenu("#button", function (popup) {
       test.checkMenu([item], [item], []);
       test.done();
     });
   });
 };
 
 
 // URL contexts should cause items to appear on pages that match.
@@ -431,65 +434,16 @@ exports.testURLContextNoMatch = function
     test.showMenu(null, function (popup) {
       test.checkMenu(items, items, []);
       test.done();
     });
   });
 };
 
 
-// Removing a non-matching URL context after its item is created and the page is
-// loaded should cause the item's content script to be evaluated when the
-// context menu is next opened.
-exports.testURLContextRemove = function (assert, done) {
-  let test = new TestHelper(assert, done);
-  let loader = test.newLoader();
-
-  let shouldBeEvaled = false;
-  let context = loader.cm.URLContext("*.bogus.com");
-  let item = loader.cm.Item({
-    label: "item",
-    context: context,
-    contentScript: 'self.postMessage("ok"); self.on("context", function () true);',
-    onMessage: function (msg) {
-      assert.ok(shouldBeEvaled,
-                  "content script should be evaluated when expected");
-      assert.equal(msg, "ok", "Should have received the right message");
-      shouldBeEvaled = false;
-    }
-  });
-
-  test.withTestDoc(function (window, doc) {
-    test.showMenu(null, function (popup) {
-      test.checkMenu([item], [item], []);
-
-      item.context.remove(context);
-
-      shouldBeEvaled = true;
-
-      test.hideMenu(function () {
-        test.showMenu(null, function (popup) {
-          test.checkMenu([item], [], []);
-
-          assert.ok(!shouldBeEvaled,
-                      "content script should have been evaluated");
-
-          test.hideMenu(function () {
-            // Shouldn't get evaluated again
-            test.showMenu(null, function (popup) {
-              test.checkMenu([item], [], []);
-              test.done();
-            });
-          });
-        });
-      });
-    });
-  });
-};
-
 // Loading a new page in the same tab should correctly start a new worker for
 // any content scripts
 exports.testPageReload = function (assert, done) {
   let test = new TestHelper(assert, done);
   let loader = test.newLoader();
 
   let item = loader.cm.Item({
     label: "Item",
@@ -767,17 +721,17 @@ exports.testContentContextMatchActiveEle
     new loader.cm.Item({
       label: "item 4",
       context: [loader.cm.PageContext()],
       contentScript: 'self.on("context", function () true);'
     })
   ];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("image"), function (popup) {
+    test.showMenu("#image", function (popup) {
       test.checkMenu(items, [items[2], items[3]], []);
       test.done();
     });
   });
 };
 
 
 // Content contexts that return false should cause their items to be absent
@@ -805,17 +759,17 @@ exports.testContentContextNoMatchActiveE
     new loader.cm.Item({
       label: "item 4",
       context: [loader.cm.PageContext()],
       contentScript: 'self.on("context", function () false);'
     })
   ];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("image"), function (popup) {
+    test.showMenu("#image", function (popup) {
       test.checkMenu(items, items, []);
       test.done();
     });
   });
 };
 
 
 // Content contexts that return undefined should cause their items to be absent
@@ -843,17 +797,17 @@ exports.testContentContextNoMatchActiveE
     new loader.cm.Item({
       label: "item 4",
       context: [loader.cm.PageContext()],
       contentScript: 'self.on("context", function () {});'
     })
   ];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("image"), function (popup) {
+    test.showMenu("#image", function (popup) {
       test.checkMenu(items, items, []);
       test.done();
     });
   });
 };
 
 
 // Content contexts that return a string should cause their items to be present
@@ -910,17 +864,16 @@ exports.testContentScriptFile = function
     label: "item2",
     contentScriptFile: "./test-contentScriptFile.js",
     onMessage: (message) => {
       assert.equal(message, "msg from contentScriptFile",
         "contentScriptFile loaded with relative url");
       itemScript[1].resolve();
     }
   });
-  console.log(item.contentScriptFile, item2.contentScriptFile);
 
   test.showMenu(null, function (popup) {
     test.checkMenu([item, item2], [], []);
     menuShown.resolve();
   });
 
   all(menuPromises).then(() => test.done());
 };
@@ -944,33 +897,28 @@ exports.testContentContextArgs = functio
     }
   });
 
   test.showMenu(null, function () {
     if (++callbacks == 2) test.done();
   });
 };
 
-// Multiple contexts imply intersection, not union, and content context
-// listeners should not be called if all declarative contexts are not current.
+// Multiple contexts imply intersection, not union.
 exports.testMultipleContexts = function (assert, done) {
   let test = new TestHelper(assert, done);
   let loader = test.newLoader();
 
   let item = new loader.cm.Item({
     label: "item",
     context: [loader.cm.SelectorContext("a[href]"), loader.cm.PageContext()],
-    contentScript: 'self.on("context", function () self.postMessage());',
-    onMessage: function () {
-      test.fail("Context listener should not be called");
-    }
   });
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("span-link"), function (popup) {
+    test.showMenu("#span-link", function (popup) {
       test.checkMenu([item], [item], []);
       test.done();
     });
   });
 };
 
 // Once a context is removed, it should no longer cause its item to appear.
 exports.testRemoveContext = function (assert, done) {
@@ -979,32 +927,113 @@ exports.testRemoveContext = function (as
 
   let ctxt = loader.cm.SelectorContext("img");
   let item = new loader.cm.Item({
     label: "item",
     context: ctxt
   });
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("image"), function (popup) {
+    test.showMenu("#image", function (popup) {
 
       // The item should be present at first.
       test.checkMenu([item], [], []);
       popup.hidePopup();
 
       // Remove the img context and check again.
       item.context.remove(ctxt);
-      test.showMenu(doc.getElementById("image"), function (popup) {
+      test.showMenu("#image", function (popup) {
+        test.checkMenu([item], [item], []);
+        test.done();
+      });
+    });
+  });
+};
+
+// Once a context is removed, it should no longer cause its item to appear.
+exports.testSetContextRemove = function (assert, done) {
+  let test = new TestHelper(assert, done);
+  let loader = test.newLoader();
+
+  let ctxt = loader.cm.SelectorContext("img");
+  let item = new loader.cm.Item({
+    label: "item",
+    context: ctxt
+  });
+
+  test.withTestDoc(function (window, doc) {
+    test.showMenu("#image", function (popup) {
+
+      // The item should be present at first.
+      test.checkMenu([item], [], []);
+      popup.hidePopup();
+
+      // Remove the img context and check again.
+      item.context = [];
+      test.showMenu("#image", function (popup) {
         test.checkMenu([item], [item], []);
         test.done();
       });
     });
   });
 };
 
+// Once a context is added, it should affect whether the item appears.
+exports.testAddContext = function (assert, done) {
+  let test = new TestHelper(assert, done);
+  let loader = test.newLoader();
+
+  let ctxt = loader.cm.SelectorContext("img");
+  let item = new loader.cm.Item({
+    label: "item"
+  });
+
+  test.withTestDoc(function (window, doc) {
+    test.showMenu("#image", function (popup) {
+
+      // The item should not be present at first.
+      test.checkMenu([item], [item], []);
+      popup.hidePopup();
+
+      // Add the img context and check again.
+      item.context.add(ctxt);
+      test.showMenu("#image", function (popup) {
+        test.checkMenu([item], [], []);
+        test.done();
+      });
+    });
+  });
+};
+
+// Once a context is added, it should affect whether the item appears.
+exports.testSetContextAdd = function (assert, done) {
+  let test = new TestHelper(assert, done);
+  let loader = test.newLoader();
+
+  let ctxt = loader.cm.SelectorContext("img");
+  let item = new loader.cm.Item({
+    label: "item"
+  });
+
+  test.withTestDoc(function (window, doc) {
+    test.showMenu("#image", function (popup) {
+
+      // The item should not be present at first.
+      test.checkMenu([item], [item], []);
+      popup.hidePopup();
+
+      // Add the img context and check again.
+      item.context = [ctxt];
+      test.showMenu("#image", function (popup) {
+        test.checkMenu([item], [], []);
+        test.done();
+      });
+    });
+  });
+};
 
 // Lots of items should overflow into the overflow submenu.
 exports.testOverflow = function (assert, done) {
   let test = new TestHelper(assert, done);
   let loader = test.newLoader();
 
   let items = [];
   for (let i = 0; i < OVERFLOW_THRESH_DEFAULT + 1; i++) {
@@ -1631,47 +1660,47 @@ exports.testOverflowTransition = functio
       label: "item 3",
       context: loader.cm.SelectorContext("a")
     })
   ];
 
   let allItems = pItems.concat(aItems);
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("link"), function (popup) {
+    test.showMenu("#link", function (popup) {
       // The menu should contain all items and will overflow
       test.checkMenu(allItems, [], []);
       popup.hidePopup();
 
-      test.showMenu(doc.getElementById("text"), function (popup) {
+      test.showMenu("#text", function (popup) {
         // Only contains hald the items and will not overflow
         test.checkMenu(allItems, aItems, []);
         popup.hidePopup();
 
         test.showMenu(null, function (popup) {
           // None of the items will be visible
           test.checkMenu(allItems, allItems, []);
           popup.hidePopup();
 
-          test.showMenu(doc.getElementById("text"), function (popup) {
+          test.showMenu("#text", function (popup) {
             // Only contains hald the items and will not overflow
             test.checkMenu(allItems, aItems, []);
             popup.hidePopup();
 
-            test.showMenu(doc.getElementById("link"), function (popup) {
+            test.showMenu("#link", function (popup) {
               // The menu should contain all items and will overflow
               test.checkMenu(allItems, [], []);
               popup.hidePopup();
 
               test.showMenu(null, function (popup) {
                 // None of the items will be visible
                 test.checkMenu(allItems, allItems, []);
                 popup.hidePopup();
 
-                test.showMenu(doc.getElementById("link"), function (popup) {
+                test.showMenu("#link", function (popup) {
                   // The menu should contain all items and will overflow
                   test.checkMenu(allItems, [], []);
                   test.done();
                 });
               });
             });
           });
         });
@@ -1753,17 +1782,17 @@ exports.testMenuCommand = function (asse
                        "Clicked item data should be correct");
       test.done();
     },
     items: [submenu],
     context: loader.cm.SelectorContext("a")
   });
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("span-link"), function (popup) {
+    test.showMenu("#span-link", function (popup) {
       test.checkMenu([topMenu], [], []);
       let topMenuElt = test.getItemElt(popup, topMenu);
       let topMenuPopup = topMenuElt.firstChild;
       let submenuElt = test.getItemElt(topMenuPopup, submenu);
       let submenuPopup = submenuElt.firstChild;
       let itemElt = test.getItemElt(submenuPopup, item);
 
       // create a command event
@@ -1879,17 +1908,17 @@ exports.testMenuClick = function (assert
                        "Clicked item data should be correct");
       test.done();
     },
     items: [submenu],
     context: loader.cm.SelectorContext("a")
   });
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("span-link"), function (popup) {
+    test.showMenu("#span-link", function (popup) {
       test.checkMenu([topMenu], [], []);
       let topMenuElt = test.getItemElt(popup, topMenu);
       let topMenuPopup = topMenuElt.firstChild;
       let submenuElt = test.getItemElt(topMenuPopup, submenu);
       let submenuPopup = submenuElt.firstChild;
       let itemElt = test.getItemElt(submenuPopup, item);
       itemElt.click();
     });
@@ -2219,17 +2248,17 @@ exports.testDrawImageOnClickNode = funct
           self.postMessage("done");
         });
       },
       onMessage: function (msg) {
         if (msg === "done")
           test.done();
       }
     });
-    test.showMenu(doc.getElementById("image"), function (popup) {
+    test.showMenu("#image", function (popup) {
       test.checkMenu([item], [], []);
       test.getItemElt(popup, item).click();
     });
   });
 };
 
 
 // Setting an item's label before the menu is ever shown should correctly change
@@ -2555,17 +2584,17 @@ exports.testItemDataSetter = function (a
 // clicking the iframe.
 exports.testAlreadyOpenIframe = function (assert, done) {
   let test = new TestHelper(assert, done);
   test.withTestDoc(function (window, doc) {
     let loader = test.newLoader();
     let item = new loader.cm.Item({
       label: "item"
     });
-    test.showMenu(doc.getElementById("iframe"), function (popup) {
+    test.showMenu("#iframe", function (popup) {
       test.checkMenu([item], [], []);
       test.done();
     });
   });
 };
 
 
 // Tests that a missing label throws an exception
@@ -2999,17 +3028,17 @@ exports.testSubItemDefaultVisible = func
       ]
     })
   ];
 
   // subitem 3 will be hidden
   let hiddenItems = [items[0].items[2]];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("image"), function (popup) {
+    test.showMenu("#image", function (popup) {
       test.checkMenu(items, hiddenItems, []);
       test.done();
     });
   });
 };
 
 // Tests that the click event on sub menuitem
 // tiggers the click event for the sub menuitem and the parent menu
@@ -3170,17 +3199,17 @@ exports.testSelectionInInnerFrameMatch =
       context: loader.cm.SelectionContext()
     })
   ];
 
   test.withTestDoc(function (window, doc) {
     let frame = doc.getElementById("iframe");
     frame.contentWindow.getSelection().selectAllChildren(frame.contentDocument.body);
 
-    test.showMenu(frame.contentDocument.getElementById("text"), function (popup) {
+    test.showMenu(["#iframe", "#text"], function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Tests that opening a context menu for an inner frame when the outer frame
 // has a selection doesn't activate the SelectionContext
@@ -3196,17 +3225,17 @@ exports.testSelectionInOuterFrameNoMatch
       context: loader.cm.SelectionContext()
     })
   ];
 
   test.withTestDoc(function (window, doc) {
     let frame = doc.getElementById("iframe");
     window.getSelection().selectAllChildren(doc.body);
 
-    test.showMenu(frame.contentDocument.getElementById("text"), function (popup) {
+    test.showMenu(["#iframe", "#text"], function (popup) {
       test.checkMenu(items, items, []);
       test.done();
     });
   });
 };
 
 
 // Test that the return value of the predicate function determines if
@@ -3283,17 +3312,17 @@ exports.testPredicateContextTargetName =
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.targetName, "input");
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("button"), function (popup) {
+    test.showMenu("#button", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 
 // Test that the data object has the correct ID
@@ -3305,17 +3334,17 @@ exports.testPredicateContextTargetIDSet 
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.targetID, "button");
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("button"), function (popup) {
+    test.showMenu("#button", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Test that the data object has the correct ID
 exports.testPredicateContextTargetIDNotSet = function (assert, done) {
@@ -3326,17 +3355,17 @@ exports.testPredicateContextTargetIDNotS
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.targetID, null);
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) {
+    test.showMenu(".predicate-test-a", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Test that the data object is showing editable correctly for regular text inputs
 exports.testPredicateContextTextBoxIsEditable = function (assert, done) {
@@ -3347,17 +3376,17 @@ exports.testPredicateContextTextBoxIsEdi
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.isEditable, true);
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("textbox"), function (popup) {
+    test.showMenu("#textbox", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Test that the data object is showing editable correctly for readonly text inputs
 exports.testPredicateContextReadonlyTextBoxIsNotEditable = function (assert, done) {
@@ -3368,17 +3397,17 @@ exports.testPredicateContextReadonlyText
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.isEditable, false);
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("readonly-textbox"), function (popup) {
+    test.showMenu("#readonly-textbox", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Test that the data object is showing editable correctly for disabled text inputs
 exports.testPredicateContextDisabledTextBoxIsNotEditable = function (assert, done) {
@@ -3389,17 +3418,17 @@ exports.testPredicateContextDisabledText
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.isEditable, false);
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("disabled-textbox"), function (popup) {
+    test.showMenu("#disabled-textbox", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Test that the data object is showing editable correctly for text areas
 exports.testPredicateContextTextAreaIsEditable = function (assert, done) {
@@ -3410,17 +3439,17 @@ exports.testPredicateContextTextAreaIsEd
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.isEditable, true);
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("textfield"), function (popup) {
+    test.showMenu("#textfield", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Test that non-text inputs are not considered editable
 exports.testPredicateContextButtonIsNotEditable = function (assert, done) {
@@ -3431,17 +3460,17 @@ exports.testPredicateContextButtonIsNotE
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.isEditable, false);
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("button"), function (popup) {
+    test.showMenu("#button", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 
 // Test that the data object is showing editable correctly
@@ -3453,17 +3482,17 @@ exports.testPredicateContextNonInputIsNo
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.isEditable, false);
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("image"), function (popup) {
+    test.showMenu("#image", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 
 // Test that the data object is showing editable correctly for HTML contenteditable elements
@@ -3475,17 +3504,17 @@ exports.testPredicateContextEditableElem
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.isEditable, true);
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("editable"), function (popup) {
+    test.showMenu("#editable", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 
 // Test that the data object does not have a selection when there is none
@@ -3544,19 +3573,18 @@ exports.testPredicateContextSelectionInT
       // since we might get whitespace
       assert.strictEqual(data.selectionText, "t v");
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
     let textbox = doc.getElementById("textbox");
-    textbox.focus();
-    textbox.setSelectionRange(3, 6);
-    test.showMenu(textbox, function (popup) {
+    test.selectRange("#textbox", 3, 6);
+    test.showMenu("#textbox", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Test that the data object has the correct src for an image
 exports.testPredicateContextTargetSrcSet = function (assert, done) {
@@ -3569,17 +3597,17 @@ exports.testPredicateContextTargetSrcSet
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.srcURL, image.src);
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
     image = doc.getElementById("image");
-    test.showMenu(image, function (popup) {
+    test.showMenu("#image", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Test that the data object has no src for a link
 exports.testPredicateContextTargetSrcNotSet = function (assert, done) {
@@ -3590,17 +3618,17 @@ exports.testPredicateContextTargetSrcNot
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.srcURL, null);
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("link"), function (popup) {
+    test.showMenu("#link", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 
 // Test that the data object has the correct link set
@@ -3613,17 +3641,17 @@ exports.testPredicateContextTargetLinkSe
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.linkURL, TEST_DOC_URL + "#test");
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementsByClassName("predicate-test-a")[0], function (popup) {
+    test.showMenu(".predicate-test-a", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Test that the data object has no link for an image
 exports.testPredicateContextTargetLinkNotSet = function (assert, done) {
@@ -3634,17 +3662,17 @@ exports.testPredicateContextTargetLinkNo
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.linkURL, null);
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("image"), function (popup) {
+    test.showMenu("#image", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Test that the data object has the correct link for a nested image
 exports.testPredicateContextTargetLinkSetNestedImage = function (assert, done) {
@@ -3655,17 +3683,17 @@ exports.testPredicateContextTargetLinkSe
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.linkURL, TEST_DOC_URL + "#nested-image");
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("predicate-test-nested-image"), function (popup) {
+    test.showMenu("#predicate-test-nested-image", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Test that the data object has the correct link for a complex nested structure
 exports.testPredicateContextTargetLinkSetNestedStructure = function (assert, done) {
@@ -3676,17 +3704,17 @@ exports.testPredicateContextTargetLinkSe
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.linkURL, TEST_DOC_URL + "#nested-structure");
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("predicate-test-nested-structure"), function (popup) {
+    test.showMenu("#predicate-test-nested-structure", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Test that the data object has the value for an input textbox
 exports.testPredicateContextTargetValueSet = function (assert, done) {
@@ -3698,17 +3726,17 @@ exports.testPredicateContextTargetValueS
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.value, "test value");
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("textbox"), function (popup) {
+    test.showMenu("#textbox", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 // Test that the data object has no value for an image
 exports.testPredicateContextTargetValueNotSet = function (assert, done) {
@@ -3719,17 +3747,17 @@ exports.testPredicateContextTargetValueN
     label: "item",
     context: loader.cm.PredicateContext(function (data) {
       assert.strictEqual(data.value, null);
       return true;
     })
   })];
 
   test.withTestDoc(function (window, doc) {
-    test.showMenu(doc.getElementById("image"), function (popup) {
+    test.showMenu("#image", function (popup) {
       test.checkMenu(items, [], []);
       test.done();
     });
   });
 };
 
 
 // NO TESTS BELOW THIS LINE! ///////////////////////////////////////////////////
@@ -4093,98 +4121,162 @@ TestHelper.prototype = {
   shouldOverflow: function (count) {
     return count >
            (this.loaders.length ?
             this.loaders[0].loader.require("sdk/preferences/service").
               get(OVERFLOW_THRESH_PREF, OVERFLOW_THRESH_DEFAULT) :
             OVERFLOW_THRESH_DEFAULT);
   },
 
-  // Opens the context menu on the current page.  If targetNode is null, the
+  // Loads scripts necessary in the content process
+  loadFrameScript: function(browser = this.browserWindow.gBrowser.selectedBrowser) {
+    function frame_script() {
+      let { interfaces: Ci } = Components;
+      addMessageListener('test:contextmenu', ({ data: { selectors } }) => {
+        let targetNode = null;
+        let contentWin = content;
+        if (selectors) {
+          while (selectors.length) {
+            targetNode = contentWin.document.querySelector(selectors.shift());
+            if (selectors.length)
+              contentWin = targetNode.contentWindow;
+          }
+        }
+
+        let rect = targetNode ?
+                   targetNode.getBoundingClientRect() :
+                   { left: 0, top: 0, width: 0, height: 0 };
+        contentWin.QueryInterface(Ci.nsIInterfaceRequestor)
+                  .getInterface(Ci.nsIDOMWindowUtils)
+                  .sendMouseEvent('contextmenu',
+                  rect.left + (rect.width / 2),
+                  rect.top + (rect.height / 2),
+                  2, 1, 0);
+      });
+
+      addMessageListener('test:ping', () => {
+        sendAsyncMessage('test:pong');
+      });
+
+      addMessageListener('test:select', ({ data: { selector, start, end } }) => {
+        let element = content.document.querySelector(selector);
+        element.focus();
+        if (end === null)
+          end = element.value.length;
+        element.setSelectionRange(start, end);
+      });
+    }
+
+    let messageManager = browser.messageManager;
+    messageManager.loadFrameScript("data:,(" + frame_script.toString() + ")();", true);
+  },
+
+  selectRange: function(selector, start, end) {
+    let messageManager = this.browserWindow.gBrowser.selectedBrowser.messageManager;
+    messageManager.sendAsyncMessage('test:select', { selector, start, end });
+  },
+
+  // Opens the context menu on the current page.  If selectors is null, the
   // menu is opened in the top-left corner.  onShowncallback is passed the
-  // popup.
-  showMenu: function(targetNode, onshownCallback) {
+  // popup. selectors is an array of selectors. Starting from the main document
+  // each selector points to an iframe, the last selector gives the target node.
+  // In the simple case of a single selector just that string can be passed
+  // instead of an array
+  showMenu: function(selectors, onshownCallback) {
     let { promise, resolve } = defer();
 
-    function sendEvent() {
-      this.delayedEventListener(this.browserWindow, "popupshowing",
+    if (selectors && !Array.isArray(selectors))
+      selectors = [selectors];
+
+    let sendEvent = () => {
+      let menu = this.browserWindow.document.getElementById("contentAreaContextMenu");
+      this.delayedEventListener(menu, "popupshowing",
         function (e) {
           let popup = e.target;
           if (onshownCallback) {
             onshownCallback.call(this, popup);
           }
           resolve(popup);
         }, false);
 
-      let rect = targetNode ?
-                 targetNode.getBoundingClientRect() :
-                 { left: 0, top: 0, width: 0, height: 0 };
-      let contentWin = targetNode ? targetNode.ownerDocument.defaultView
-                                  : this.browserWindow.content;
-      contentWin.
-        QueryInterface(Ci.nsIInterfaceRequestor).
-        getInterface(Ci.nsIDOMWindowUtils).
-        sendMouseEvent("contextmenu",
-                       rect.left + (rect.width / 2),
-                       rect.top + (rect.height / 2),
-                       2, 1, 0);
+      let messageManager = this.browserWindow.gBrowser.selectedBrowser.messageManager;
+      messageManager.sendAsyncMessage('test:contextmenu', { selectors });
+    }
+
+    // Bounces an asynchronous message through the browser message manager.
+    // This ensures that any pending messages have been delivered to the frame
+    // scripts and so the remote proxies have been updated
+    let flushMessages = () => {
+      let listener = () => {
+        messageManager.removeMessageListener('test:pong', listener);
+        sendEvent();
+      };
+
+      let messageManager = this.browserWindow.gBrowser.selectedBrowser.messageManager;
+      messageManager.addMessageListener('test:pong', listener);
+      messageManager.sendAsyncMessage('test:ping');
     }
 
     // If a new tab or window has not yet been opened, open a new tab now.  For
     // some reason using the tab already opened when the test starts causes
     // leaks.  See bug 566351 for details.
-    if (!targetNode && !this.oldSelectedTab && !this.oldBrowserWindow) {
+    if (!selectors && !this.oldSelectedTab && !this.oldBrowserWindow) {
       this.oldSelectedTab = this.tabBrowser.selectedTab;
       this.tab = this.tabBrowser.addTab("about:blank");
       let browser = this.tabBrowser.getBrowserForTab(this.tab);
 
       this.delayedEventListener(browser, "load", function () {
         this.tabBrowser.selectedTab = this.tab;
-        sendEvent.call(this);
+        this.loadFrameScript();
+        flushMessages();
       }, true);
     }
-    else
-      sendEvent.call(this);
+    else {
+      flushMessages();
+    }
 
     return promise;
   },
 
   hideMenu: function(onhiddenCallback) {
     this.delayedEventListener(this.browserWindow, "popuphidden", onhiddenCallback);
 
     this.contextMenuPopup.hidePopup();
   },
 
   // Opens a new browser window.  The window will be closed automatically when
   // done() is called.
-  withNewWindow: function (onloadCallback) {
-    let win = this.browserWindow.OpenBrowserWindow();
-    this.delayedEventListener(win, "load", onloadCallback, true);
+  withNewWindow: function (onloadCallback, makePrivate = false) {
+    let win = this.browserWindow.OpenBrowserWindow({ private: makePrivate });
+    observers.once("browser-delayed-startup-finished", () => {
+      // Open a new tab so we can make sure it is remote and loaded
+      win.gBrowser.selectedTab = win.gBrowser.addTab();
+      this.loadFrameScript();
+      this.delayedEventListener(win.gBrowser.selectedBrowser, "load", onloadCallback, true);
+    });
     this.oldBrowserWindow = this.browserWindow;
     this.browserWindow = win;
   },
 
   // Opens a new private browser window.  The window will be closed
   // automatically when done() is called.
   withNewPrivateWindow: function (onloadCallback) {
-    let win = this.browserWindow.OpenBrowserWindow({private: true});
-    this.delayedEventListener(win, "load", onloadCallback, true);
-    this.oldBrowserWindow = this.browserWindow;
-    this.browserWindow = win;
+    this.withNewWindow(onloadCallback, true);
   },
 
   // Opens a new tab with our test page in the current window.  The tab will
   // be closed automatically when done() is called.
   withTestDoc: function (onloadCallback) {
     this.oldSelectedTab = this.tabBrowser.selectedTab;
     this.tab = this.tabBrowser.addTab(TEST_DOC_URL);
     let browser = this.tabBrowser.getBrowserForTab(this.tab);
 
     this.delayedEventListener(browser, "load", function () {
       this.tabBrowser.selectedTab = this.tab;
+      this.loadFrameScript();
       onloadCallback.call(this, browser.contentWindow, browser.contentDocument);
     }, true, function(evt) {
       return evt.target.location == TEST_DOC_URL;
     });
   }
 };
 
 require('sdk/test').run(exports);
--- a/b2g/config/mozconfigs/ics_armv7a_gecko/debug
+++ b/b2g/config/mozconfigs/ics_armv7a_gecko/debug
@@ -5,17 +5,16 @@ mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/ob
 ac_add_options --enable-application=b2g
 ac_add_options --enable-b2g-camera
 
 ac_add_options --target=arm-linux-androideabi
 ac_add_options --with-gonk="$topsrcdir/gonk-toolchain"
 export TOOLCHAIN_HOST=linux-x86
 export GONK_PRODUCT=generic
 ac_add_options --with-gonk-toolchain-prefix="$topsrcdir/gonk-toolchain/prebuilt/$TOOLCHAIN_HOST/toolchain/arm-linux-androideabi-4.4.x/bin/arm-linux-androideabi-"
-ac_add_options --disable-elf-hack
 ac_add_options --enable-debug-symbols
 ac_add_options --enable-debug
 #. "$topsrcdir/build/mozconfig.cache"
 ENABLE_MARIONETTE=1
 
 # Enable dump() from JS.
 export CXXFLAGS="-DMOZ_ENABLE_JS_DUMP -include $topsrcdir/gonk-toolchain/gonk-misc/Unicode.h -include $topsrcdir/gonk-toolchain/system/vold/ResponseCode.h"
 
--- a/b2g/config/mozconfigs/ics_armv7a_gecko/nightly
+++ b/b2g/config/mozconfigs/ics_armv7a_gecko/nightly
@@ -6,17 +6,16 @@ ac_add_options --enable-application=b2g
 ac_add_options --enable-b2g-camera
 ac_add_options --enable-updater
 
 ac_add_options --target=arm-linux-androideabi
 ac_add_options --with-gonk="$topsrcdir/gonk-toolchain"
 export TOOLCHAIN_HOST=linux-x86
 export GONK_PRODUCT=generic
 ac_add_options --with-gonk-toolchain-prefix="$topsrcdir/gonk-toolchain/prebuilt/$TOOLCHAIN_HOST/toolchain/arm-linux-androideabi-4.4.x/bin/arm-linux-androideabi-"
-ac_add_options --disable-elf-hack
 ac_add_options --enable-debug-symbols
 # ac_add_options --enable-profiling
 #. "$topsrcdir/build/mozconfig.cache"
 ENABLE_MARIONETTE=1
 
 # Enable dump() from JS.
 export CXXFLAGS="-DMOZ_ENABLE_JS_DUMP -include $topsrcdir/gonk-toolchain/gonk-misc/Unicode.h -include $topsrcdir/gonk-toolchain/system/vold/ResponseCode.h"
 
--- a/b2g/config/mozconfigs/linux32_gecko/debug
+++ b/b2g/config/mozconfigs/linux32_gecko/debug
@@ -24,17 +24,16 @@ export MOZ_TELEMETRY_REPORTING=1
 
 # Use sccache
 no_sccache=
 . "$topsrcdir/build/mozconfig.cache"
 
 #B2G options
 ac_add_options --enable-application=b2g
 ENABLE_MARIONETTE=1
-ac_add_options --disable-elf-hack
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
 
 # Include Firefox OS fonts.
 MOZTTDIR=$topsrcdir/moztt
 
 . "$topsrcdir/b2g/config/mozconfigs/common.override"
--- a/b2g/config/mozconfigs/linux32_gecko/nightly
+++ b/b2g/config/mozconfigs/linux32_gecko/nightly
@@ -22,17 +22,16 @@ export MOZ_TELEMETRY_REPORTING=1
 # DISABLED WHILE NOT ON TRY ac_add_options --enable-warnings-as-errors
 
 # Use sccache
 no_sccache=
 . "$topsrcdir/build/mozconfig.cache"
 
 #B2G options
 ac_add_options --enable-application=b2g
-ac_add_options --disable-elf-hack
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
 
 # Include Firefox OS fonts.
 MOZTTDIR=$topsrcdir/moztt
 
 # Build simulator xpi and phone tweaks for b2g-desktop
--- a/b2g/config/mozconfigs/linux64_gecko/debug
+++ b/b2g/config/mozconfigs/linux64_gecko/debug
@@ -24,17 +24,16 @@ export MOZ_TELEMETRY_REPORTING=1
 
 # Use sccache
 no_sccache=
 . "$topsrcdir/build/mozconfig.cache"
 
 #B2G options
 ac_add_options --enable-application=b2g
 ENABLE_MARIONETTE=1
-ac_add_options --disable-elf-hack
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
 
 # Include Firefox OS fonts.
 MOZTTDIR=$topsrcdir/moztt
 
 . "$topsrcdir/b2g/config/mozconfigs/common.override"
--- a/b2g/config/mozconfigs/linux64_gecko/nightly
+++ b/b2g/config/mozconfigs/linux64_gecko/nightly
@@ -22,17 +22,16 @@ export MOZ_TELEMETRY_REPORTING=1
 # DISABLED WHILE NOT ON TRY ac_add_options --enable-warnings-as-errors
 
 # Use sccache
 no_sccache=
 . "$topsrcdir/build/mozconfig.cache"
 
 #B2G options
 ac_add_options --enable-application=b2g
-ac_add_options --disable-elf-hack
 export CXXFLAGS=-DMOZ_ENABLE_JS_DUMP
 
 GAIADIR=$topsrcdir/gaia
 
 # Include Firefox OS fonts.
 MOZTTDIR=$topsrcdir/moztt
 
 # Build simulator xpi and phone tweaks for b2g-desktop
--- a/b2g/installer/Makefile.in
+++ b/b2g/installer/Makefile.in
@@ -78,19 +78,16 @@ endif
 ifneq (,$(filter rtsp,$(NECKO_PROTOCOLS)))
 DEFINES += -DMOZ_RTSP
 endif
 
 ifdef GKMEDIAS_SHARED_LIBRARY
 DEFINES += -DGKMEDIAS_SHARED_LIBRARY
 endif
 
-ifdef MOZ_REPLACE_MALLOC
-DEFINES += -DMOZ_REPLACE_MALLOC
-endif
 ifdef MOZ_JEMALLOC3
 DEFINES += -DMOZ_JEMALLOC3
 endif
 
 ifdef MOZ_WIDGET_GTK
 DEFINES += -DMOZ_GTK=1
 ifdef MOZ_ENABLE_GTK3
 DEFINES += -DMOZ_GTK3=1
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -127,17 +127,16 @@
 #endif
 @BINPATH@/components/accessibility.xpt
 #endif
 @BINPATH@/components/appshell.xpt
 @BINPATH@/components/appstartup.xpt
 @BINPATH@/components/autocomplete.xpt
 @BINPATH@/components/autoconfig.xpt
 @BINPATH@/components/browsercompsbase.xpt
-@BINPATH@/components/browser-element.xpt
 @BINPATH@/components/browser-feeds.xpt
 @BINPATH@/components/caps.xpt
 @BINPATH@/components/chardet.xpt
 @BINPATH@/components/chrome.xpt
 @BINPATH@/components/commandhandler.xpt
 @BINPATH@/components/commandlines.xpt
 @BINPATH@/components/composer.xpt
 @BINPATH@/components/content_events.xpt
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -5364,17 +5364,20 @@
               gBrowser._cancelTabSwitch(toTab);
             } else {
               this.setAttribute("selectedIndex", updatedTabIndex);
               gBrowser._finalizeTabSwitch(toTab, fromTab);
             }
           }, () => {
             // If the promise rejected, that means we don't want to actually
             // flip the deck, so we cancel the tab switch.
-            gBrowser._cancelTabSwitch(toTab);
+            // We need to nullcheck the method we're about to call because
+            // the binding might be dead at this point.
+            if (gBrowser._cancelTabSwitch)
+              gBrowser._cancelTabSwitch(toTab);
           });
 
           return val;
         ]]>
         </setter>
       </property>
     </implementation>
   </binding>
--- a/browser/base/content/test/general/browser.ini
+++ b/browser/base/content/test/general/browser.ini
@@ -119,17 +119,17 @@ skip-if = os == "linux" || e10s # Bug 10
 [browser_addKeywordSearch.js]
 [browser_search_favicon.js]
 skip-if = os == "linux" || e10s # Bug 1073339 - Investigate autocomplete test unreliability on Linux/e10s
 [browser_alltabslistener.js]
 skip-if = os == "linux" || e10s # Linux: Intermittent failures, bug 951680; e10s: Bug 1093594 - notifications for tabs come in in the wrong order / unexpectedly
 [browser_autocomplete_a11y_label.js]
 skip-if = e10s # Bug ????? - no e10s switch-to-tab support yet
 [browser_backButtonFitts.js]
-skip-if = os != "win" || e10s # The Fitts Law back button is only supported on Windows (bug 571454) / e10s - Bug ?????? test touches content (attempts to add an event listener directly to the contentWindow)
+skip-if = os != "win" || e10s # The Fitts Law back button is only supported on Windows (bug 571454) / e10s - Bug 1099154: test touches content (attempts to add an event listener directly to the contentWindow)
 [browser_blob-channelname.js]
 [browser_bookmark_titles.js]
 skip-if = buildapp == 'mulet' || toolkit == "windows" || e10s # Disabled on Windows due to frequent failures (bugs 825739, 841341) / e10s - Bug 1094205 - places doesn't return the right thing in e10s mode, for some reason
 [browser_bug304198.js]
 skip-if = e10s
 [browser_bug321000.js]
 skip-if = true # browser_bug321000.js is disabled because newline handling is shaky (bug 592528)
 [browser_bug329212.js]
@@ -160,17 +160,17 @@ skip-if = true # bug 428712
 skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
 [browser_bug427559.js]
 skip-if = e10s # Bug ?????? - "content window is focused - Got [object ChromeWindow], expected [object XrayWrapper [object Window]]"
 [browser_bug431826.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content (eg, var expertDiv = gBrowser.contentDocument.getElementById("expertContent");)
 [browser_bug432599.js]
 [browser_bug435035.js]
 [browser_bug435325.js]
-skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content
+skip-if = buildapp == 'mulet' || e10s # Bug 1099156 - test directly manipulates content
 [browser_bug441778.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug455852.js]
 skip-if = e10s
 [browser_bug460146.js]
 skip-if = e10s # Bug 866413 - PageInfo doesn't work in e10s
 [browser_bug462289.js]
 skip-if = toolkit == "cocoa" || e10s # Bug ?????? - not sure why this is timing out and crashing!!
@@ -260,17 +260,17 @@ skip-if = buildapp == 'mulet' || e10s # 
 [browser_bug655584.js]
 skip-if = e10s
 [browser_bug664672.js]
 [browser_bug676619.js]
 skip-if = buildapp == 'mulet' || os == "mac" # mac: Intermittent failures, bug 925225
 [browser_bug678392.js]
 skip-if = e10s # Bug ?????? - Obscure non-windows failures ("Snapshot array has correct length of 1 after loading one page. - Got 0, expected 1" and more)
 [browser_bug710878.js]
-skip-if = e10s # Bug ?????? - test directly manipulates content (doc.querySelector)
+skip-if = e10s # Bug 1100653 - test uses waitForFocus on content
 [browser_bug719271.js]
 skip-if = e10s # Bug 1056146 - zoom tests use FullZoomHelper and break in e10s
 [browser_bug724239.js]
 [browser_bug734076.js]
 skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
 [browser_bug735471.js]
 [browser_bug749738.js]
 skip-if = e10s # Bug 921935 - focusmanager issues with e10s
@@ -347,20 +347,20 @@ skip-if = toolkit == "windows" # Disable
 skip-if = os == "linux" || e10s # Linux: Intermittent failures, bug 917535; e10s: Bug 1094252 - Focus issues (There should be no focused element - Got [object XULElement], expected null)
 [browser_locationBarExternalLoad.js]
 skip-if = e10s
 [browser_menuButtonFitts.js]
 skip-if = os != "win" # The Fitts Law menu button is only supported on Windows (bug 969376)
 [browser_middleMouse_noJSPaste.js]
 skip-if = e10s # Bug 921952 - Content:Click event issues
 [browser_minimize.js]
-skip-if = e10s # Bug ?????? - test directly manipulates content (TypeError: gBrowser.docShell is null)
+skip-if = e10s # Bug 1100664 - test directly access content docShells (TypeError: gBrowser.docShell is null)
 [browser_mixedcontent_securityflags.js]
 [browser_notification_tab_switching.js]
-skip-if = buildapp == 'mulet' || e10s # Bug ?????? - uncaught exception - Error: cannot ipc non-cpow object at chrome://mochitests/content/browser/browser/base/content/test/general/browser_notification_tab_switching.js:32
+skip-if = buildapp == 'mulet' || e10s # Bug 1100662 - content access causing uncaught exception - Error: cannot ipc non-cpow object at chrome://mochitests/content/browser/browser/base/content/test/general/browser_notification_tab_switching.js:32 (or in RemoteAddonsChild.jsm)
 [browser_offlineQuotaNotification.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 1093603 - test breaks with PopupNotifications.panel.firstElementChild is null
 [browser_overflowScroll.js]
 [browser_pageInfo.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 866413 - PageInfo doesn't work in e10s
 [browser_page_style_menu.js]
 skip-if = e10s # Bug ?????? - test directly manipulates content
 
@@ -368,17 +368,17 @@ skip-if = e10s # Bug ?????? - test direc
 skip-if = e10s
 [browser_parsable_script.js]
 skip-if = asan # Disabled because it takes a long time (see test for more information)
 
 [browser_pinnedTabs.js]
 [browser_plainTextLinks.js]
 skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
 [browser_popupUI.js]
-skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (tries to get a popup element directly from content)
+skip-if = buildapp == 'mulet' || e10s # Bug 1100707 - test fails in e10s because it can't get accel-w to close the popup (?)
 [browser_popup_blocker.js]
 [browser_printpreview.js]
 skip-if = buildapp == 'mulet' || e10s # Bug ?????? - timeout after logging "Error: Channel closing: too late to send/recv, messages will be lost"
 [browser_private_browsing_window.js]
 skip-if = buildapp == 'mulet'
 [browser_private_no_prompt.js]
 skip-if = buildapp == 'mulet'
 [browser_relatedTabs.js]
@@ -397,21 +397,21 @@ skip-if = true # bug 432425
 skip-if = buildapp == 'mulet'
 [browser_sanitizeDialog.js]
 skip-if = buildapp == 'mulet'
 [browser_sanitizeDialog_treeView.js]
 skip-if = true  # disabled until the tree view is added
                 # back to the clear recent history dialog (sanitize.xul), if
                 # it ever is (bug 480169)
 [browser_save_link-perwindowpb.js]
-skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (event.target)
+skip-if = buildapp == 'mulet' || e10s # Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_private_link_perwindowpb.js]
 skip-if = buildapp == 'mulet' || e10s # e10s: Bug 933103 - mochitest's EventUtils.synthesizeMouse functions not e10s friendly
 [browser_save_video.js]
-skip-if = buildapp == 'mulet' || e10s # Bug ?????? - test directly manipulates content (event.target)
+skip-if = buildapp == 'mulet' || e10s # Bug 1100698 - test uses synthesizeMouse and then does a load of other stuff that breaks in e10s
 [browser_save_video_frame.js]
 [browser_scope.js]
 [browser_searchSuggestionUI.js]
 skip-if = e10s
 support-files =
   searchSuggestionUI.html
   searchSuggestionUI.js
 [browser_selectTabAtIndex.js]
@@ -434,37 +434,36 @@ skip-if = os == "linux" || os == "mac" #
                                        # Disabled on OS X because of bug 967917
 [browser_tabfocus.js]
 skip-if = e10s # Bug 921935 - focusmanager issues with e10s (test calls getFocusedElementForWindow with a content window)
 [browser_tabkeynavigation.js]
 skip-if = e10s
 [browser_tabopen_reflows.js]
 skip-if = e10s # Bug ?????? - test needs to be updated for e10s (captures a stack that isn't correct in e10s)
 [browser_tabs_isActive.js]
-skip-if = e10s # Bug ?????? - test directly manipulates content (tries to get/set attributes directly on content docshell)
+skip-if = e10s # Bug 1100664 - test relies on linkedBrowser.docShell
 [browser_tabs_owner.js]
 [browser_trackingUI.js]
 skip-if = e10s # Bug 1093155 - tries to use context menu from browser-chrome and gets in a mess when in e10s mode
 support-files =
   trackingPage.html
   benignPage.html
 [browser_typeAheadFind.js]
 skip-if = buildapp == 'mulet' || e10s # Bug 921935 - focusmanager issues with e10s (test calls waitForFocus)
 [browser_unloaddialogs.js]
-skip-if = e10s # Bug ?????? - test uses chrome windowMediator to try and see alert() from content
+skip-if = e10s # Bug 1100700 - test relies on unload event firing on closed tabs, which it doesn't
 [browser_urlHighlight.js]
 [browser_urlbarAutoFillTrimURLs.js]
 skip-if = e10s # Bug 1093941 - Waits indefinitely for onSearchComplete
 [browser_urlbarCopying.js]
 [browser_urlbarEnter.js]
 skip-if = e10s # Bug 1093941 - used to cause obscure non-windows child process crashes on try
 [browser_urlbarRevert.js]
 skip-if = e10s # Bug 1093941 - ESC reverted the location bar value - Got foobar, expected example.com
 [browser_urlbarSearchSingleWordNotification.js]
-skip-if = e10s # Bug 1093997 - intermittent failures in e10s-mode only
 [browser_urlbarStop.js]
 skip-if = e10s # Bug 1093941 - test calls gBrowser.contentWindow.stop
 [browser_urlbarTrimURLs.js]
 [browser_urlbar_search_healthreport.js]
 [browser_utilityOverlay.js]
 [browser_visibleFindSelection.js]
 skip-if = e10s # Bug 921935 - focusmanager issues with e10s (test calls waitForFocus)
 [browser_visibleLabel.js]
@@ -474,27 +473,27 @@ skip-if = true # Bug 1005420 - fails int
 [browser_visibleTabs_bookmarkAllTabs.js]
 [browser_visibleTabs_contextMenu.js]
 [browser_visibleTabs_tabPreview.js]
 skip-if = (os == "win" && !debug) || e10s # Bug 1007418
 [browser_web_channel.js]
 [browser_windowopen_reflows.js]
 skip-if = buildapp == 'mulet'
 [browser_wyciwyg_urlbarCopying.js]
-skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById)
+skip-if = e10s # Bug 1100703 - test directly manipulates content (content.document.getElementById)
 [browser_zbug569342.js]
 skip-if = e10s # Bug 1094240 - has findbar-related failures
 [browser_registerProtocolHandler_notification.js]
 skip-if = e10s # Bug 940206 - nsIWebContentHandlerRegistrar::registerProtocolHandler doesn't work in e10s
 [browser_no_mcb_on_http_site.js]
 [browser_bug1003461-switchtab-override.js]
 skip-if = e10s
 [browser_bug1024133-switchtab-override-keynav.js]
 skip-if = e10s
 [browser_bug1025195_switchToTabHavingURI_ignoreFragment.js]
 [browser_addCertException.js]
-skip-if = e10s # Bug ?????? - test directly manipulates content (content.document.getElementById)
+skip-if = e10s # Bug 1100687 - test directly manipulates content (content.document.getElementById)
 [browser_bug1045809.js]
 [browser_e10s_switchbrowser.js]
 [browser_blockHPKP.js]
-skip-if = e10s # bug ?????? - test directly manipulates content (content.document.getElementById)
+skip-if = e10s # bug 1100687 - test directly manipulates content (content.document.getElementById)
 [browser_mcb_redirect.js]
 skip-if = e10s # bug 1084504 - [e10s] Mixed content detection does not take redirection into account
--- a/browser/base/content/test/newtab/browser.ini
+++ b/browser/base/content/test/newtab/browser.ini
@@ -16,16 +16,17 @@ skip-if = os == "mac" # Intermittent fai
 [browser_newtab_bug752841.js]
 [browser_newtab_bug765628.js]
 [browser_newtab_bug876313.js]
 [browser_newtab_bug991111.js]
 [browser_newtab_bug991210.js]
 [browser_newtab_bug998387.js]
 [browser_newtab_disable.js]
 [browser_newtab_drag_drop.js]
+skip-if = os == "win" && debug # bug 1097056; test fails in --run-by-dir mode on win8 x64 debug
 [browser_newtab_drag_drop_ext.js]
 [browser_newtab_drop_preview.js]
 [browser_newtab_enhanced.js]
 [browser_newtab_focus.js]
 [browser_newtab_intro.js]
 [browser_newtab_perwindow_private_browsing.js]
 [browser_newtab_reportLinkAction.js]
 [browser_newtab_reflow_load.js]
--- a/browser/components/customizableui/CustomizableUI.jsm
+++ b/browser/components/customizableui/CustomizableUI.jsm
@@ -2051,46 +2051,47 @@ let CustomizableUIInternal = {
           break;
         }
       }
     }
 
     // If we're restoring the widget to it's old placement, fire off the
     // onWidgetAdded event - our own handler will take care of adding it to
     // any build areas.
-    if (widget.currentArea) {
-      this.notifyListeners("onWidgetAdded", widget.id, widget.currentArea,
-                           widget.currentPosition);
-    } else if (widgetMightNeedAutoAdding) {
-      let autoAdd = true;
-      try {
-        autoAdd = Services.prefs.getBoolPref(kPrefCustomizationAutoAdd);
-      } catch (e) {}
-
-      // If the widget doesn't have an existing placement, and it hasn't been
-      // seen before, then add it to its default area so it can be used.
-      // If the widget is not removable, we *have* to add it to its default
-      // area here.
-      let canBeAutoAdded = autoAdd && !gSeenWidgets.has(widget.id);
-      if (!widget.currentArea && (!widget.removable || canBeAutoAdded)) {
-        this.beginBatchUpdate();
+    this.beginBatchUpdate();
+    try {
+      if (widget.currentArea) {
+        this.notifyListeners("onWidgetAdded", widget.id, widget.currentArea,
+                             widget.currentPosition);
+      } else if (widgetMightNeedAutoAdding) {
+        let autoAdd = true;
         try {
-          gSeenWidgets.add(widget.id);
-
+          autoAdd = Services.prefs.getBoolPref(kPrefCustomizationAutoAdd);
+        } catch (e) {}
+
+        // If the widget doesn't have an existing placement, and it hasn't been
+        // seen before, then add it to its default area so it can be used.
+        // If the widget is not removable, we *have* to add it to its default
+        // area here.
+        let canBeAutoAdded = autoAdd && !gSeenWidgets.has(widget.id);
+        if (!widget.currentArea && (!widget.removable || canBeAutoAdded)) {
           if (widget.defaultArea) {
             if (this.isAreaLazy(widget.defaultArea)) {
               gFuturePlacements.get(widget.defaultArea).add(widget.id);
             } else {
               this.addWidgetToArea(widget.id, widget.defaultArea);
             }
           }
-        } finally {
-          this.endBatchUpdate(true);
         }
       }
+    } finally {
+      // Ensure we always have this widget in gSeenWidgets, and save
+      // state in case this needs to be done here.
+      gSeenWidgets.add(widget.id);
+      this.endBatchUpdate(true);
     }
 
     this.notifyListeners("onWidgetAfterCreation", widget.id, widget.currentArea);
     return widget.id;
   },
 
   createBuiltinWidget: function(aData) {
     // This should only ever be called on startup, before any windows are
@@ -2114,17 +2115,17 @@ let CustomizableUIInternal = {
     }
     return gAreas.get(aArea).has("legacy");
   },
 
   //XXXunf Log some warnings here, when the data provided isn't up to scratch.
   normalizeWidget: function(aData, aSource) {
     let widget = {
       implementation: aData,
-      source: aSource || "addon",
+      source: aSource || CustomizableUI.SOURCE_EXTERNAL,
       instances: new Map(),
       currentArea: null,
       removable: true,
       overflows: true,
       defaultArea: null,
       shortcutId: null,
       tooltiptext: null,
       showInPrivateBrowsing: true,
@@ -2318,16 +2319,25 @@ let CustomizableUIInternal = {
   reset: function() {
     gResetting = true;
     this._resetUIState();
 
     // Rebuild each registered area (across windows) to reflect the state that
     // was reset above.
     this._rebuildRegisteredAreas();
 
+    for (let [widgetId, widget] of gPalette) {
+      if (widget.source == CustomizableUI.SOURCE_EXTERNAL) {
+        gSeenWidgets.add(widgetId);
+      }
+    }
+    if (gSeenWidgets.size) {
+      gDirty = true;
+    }
+
     gResetting = false;
   },
 
   _resetUIState: function() {
     try {
       gUIStateBeforeReset.drawInTitlebar = Services.prefs.getBoolPref(kPrefDrawInTitlebar);
       gUIStateBeforeReset.deveditionTheme = Services.prefs.getBoolPref(kPrefDeveditionTheme);
       gUIStateBeforeReset.uiCustomizationState = Services.prefs.getCharPref(kPrefCustomizationState);
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -149,8 +149,9 @@ skip-if = e10s # Bug 1088710
 [browser_1042100_default_placements_update.js]
 [browser_1058573_showToolbarsDropdown.js]
 [browser_1087303_button_fullscreen.js]
 skip-if = os == "mac"
 [browser_1087303_button_preferences.js]
 [browser_bootstrapped_custom_toolbar.js]
 [browser_panel_toggle.js]
 [browser_1089591_still_customizable_after_reset.js]
+[browser_1096763_seen_widgets_post_reset.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_1096763_seen_widgets_post_reset.js
@@ -0,0 +1,30 @@
+"use strict";
+
+const BUTTONID = "test-seenwidget-post-reset";
+
+add_task(function*() {
+  let widget = CustomizableUI.createWidget({
+    id: BUTTONID,
+    label: "Test widget seen post reset",
+    defaultArea: CustomizableUI.AREA_NAVBAR
+  });
+
+  let bsPass = Cu.import("resource:///modules/CustomizableUI.jsm", {});
+  ok(bsPass.gSeenWidgets.has(BUTTONID), "Widget should be seen after createWidget is called.");
+  CustomizableUI.reset();
+  ok(bsPass.gSeenWidgets.has(BUTTONID), "Widget should still be seen after reset.");
+  ok(!Services.prefs.prefHasUserValue(bsPass.kPrefCustomizationState), "Pref shouldn't be set right now, because that'd break undo.");
+  CustomizableUI.addWidgetToArea(BUTTONID, CustomizableUI.AREA_NAVBAR);
+  gCustomizeMode.removeFromArea(document.getElementById(BUTTONID));
+  let hasUserValue = Services.prefs.prefHasUserValue(bsPass.kPrefCustomizationState);
+  ok(hasUserValue, "Pref should be set right now.");
+  if (hasUserValue) {
+    let seenArray = JSON.parse(Services.prefs.getCharPref(bsPass.kPrefCustomizationState)).seen;
+    isnot(seenArray.indexOf(BUTTONID), -1, "Widget should be in saved 'seen' list.");
+  }
+});
+
+registerCleanupFunction(function() {
+  CustomizableUI.destroyWidget(BUTTONID);
+  CustomizableUI.reset();
+});
--- a/browser/components/loop/LoopRooms.jsm
+++ b/browser/components/loop/LoopRooms.jsm
@@ -372,16 +372,44 @@ let LoopRoomsInternal = {
     }
     this._postToRoom(roomToken, {
       action: "leave",
       sessionToken: sessionToken
     }, callback);
   },
 
   /**
+   * Renames a room.
+   *
+   * @param {String} roomToken   The room token
+   * @param {String} newRoomName The new name for the room
+   * @param {Function} callback   Function that will be invoked once the operation
+   *                              finished. The first argument passed will be an
+   *                              `Error` object or `null`.
+   */
+  rename: function(roomToken, newRoomName, callback) {
+    let room = this.rooms.get(roomToken);
+    let url = "/rooms/" + encodeURIComponent(roomToken);
+
+    let origRoom = this.rooms.get(roomToken);
+    let patchData = {
+      roomName: newRoomName,
+      // XXX We have to supply the max size and room owner due to bug 1099063.
+      maxSize: origRoom.maxSize,
+      roomOwner: origRoom.roomOwner
+    };
+    MozLoopService.hawkRequest(this.sessionType, url, "PATCH", patchData)
+      .then(response => {
+        let data = JSON.parse(response.body);
+        extend(room, data);
+        callback(null, room);
+      }, error => callback(error)).catch(error => callback(error));
+  },
+
+  /**
    * Callback used to indicate changes to rooms data on the LoopServer.
    *
    * @param {String} version   Version number assigned to this change set.
    * @param {String} channelID Notification channel identifier.
    */
   onNotification: function(version, channelID) {
     gDirty = true;
     this.getAll(version, () => {});
@@ -438,16 +466,20 @@ this.LoopRooms = {
     return LoopRoomsInternal.refreshMembership(roomToken, sessionToken,
       callback);
   },
 
   leave: function(roomToken, sessionToken, callback) {
     return LoopRoomsInternal.leave(roomToken, sessionToken, callback);
   },
 
+  rename: function(roomToken, newRoomName, callback) {
+    return LoopRoomsInternal.rename(roomToken, newRoomName, callback);
+  },
+
   promise: function(method, ...params) {
     return new Promise((resolve, reject) => {
       this[method](...params, (error, result) => {
         if (error) {
           reject(error);
         } else {
           resolve(result);
         }
--- a/browser/components/loop/content/js/roomViews.js
+++ b/browser/components/loop/content/js/roomViews.js
@@ -54,31 +54,41 @@ loop.roomViews = (function(mozL10n) {
       }, storeState);
     }
   };
 
   /**
    * Desktop room invitation view (overlay).
    */
   var DesktopRoomInvitationView = React.createClass({displayName: 'DesktopRoomInvitationView',
-    mixins: [ActiveRoomStoreMixin],
+    mixins: [ActiveRoomStoreMixin, React.addons.LinkedStateMixin],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
     },
 
     getInitialState: function() {
       return {
-        copiedUrl: false
+        copiedUrl: false,
+        newRoomName: ""
       }
     },
 
     handleFormSubmit: function(event) {
       event.preventDefault();
-      // XXX
+
+      var newRoomName = this.state.newRoomName;
+
+      if (newRoomName && this.state.roomName != newRoomName) {
+        this.props.dispatcher.dispatch(
+          new sharedActions.RenameRoom({
+            roomToken: this.state.roomToken,
+            newRoomName: newRoomName
+          }));
+      }
     },
 
     handleEmailButtonClick: function(event) {
       event.preventDefault();
 
       this.props.dispatcher.dispatch(
         new sharedActions.EmailRoomUrl({roomUrl: this.state.roomUrl}));
     },
@@ -91,17 +101,19 @@ loop.roomViews = (function(mozL10n) {
 
       this.setState({copiedUrl: true});
     },
 
     render: function() {
       return (
         React.DOM.div({className: "room-invitation-overlay"}, 
           React.DOM.form({onSubmit: this.handleFormSubmit}, 
-            React.DOM.input({type: "text", ref: "roomName", 
+            React.DOM.input({type: "text", className: "input-room-name", 
+              valueLink: this.linkState("newRoomName"), 
+              onBlur: this.handleFormSubmit, 
               placeholder: mozL10n.get("rooms_name_this_room_label")})
           ), 
           React.DOM.p(null, mozL10n.get("invite_header_text")), 
           React.DOM.div({className: "btn-group call-action-group"}, 
             React.DOM.button({className: "btn btn-info btn-email", 
                     onClick: this.handleEmailButtonClick}, 
               mozL10n.get("share_button2")
             ), 
--- a/browser/components/loop/content/js/roomViews.jsx
+++ b/browser/components/loop/content/js/roomViews.jsx
@@ -54,31 +54,41 @@ loop.roomViews = (function(mozL10n) {
       }, storeState);
     }
   };
 
   /**
    * Desktop room invitation view (overlay).
    */
   var DesktopRoomInvitationView = React.createClass({
-    mixins: [ActiveRoomStoreMixin],
+    mixins: [ActiveRoomStoreMixin, React.addons.LinkedStateMixin],
 
     propTypes: {
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired
     },
 
     getInitialState: function() {
       return {
-        copiedUrl: false
+        copiedUrl: false,
+        newRoomName: ""
       }
     },
 
     handleFormSubmit: function(event) {
       event.preventDefault();
-      // XXX
+
+      var newRoomName = this.state.newRoomName;
+
+      if (newRoomName && this.state.roomName != newRoomName) {
+        this.props.dispatcher.dispatch(
+          new sharedActions.RenameRoom({
+            roomToken: this.state.roomToken,
+            newRoomName: newRoomName
+          }));
+      }
     },
 
     handleEmailButtonClick: function(event) {
       event.preventDefault();
 
       this.props.dispatcher.dispatch(
         new sharedActions.EmailRoomUrl({roomUrl: this.state.roomUrl}));
     },
@@ -91,17 +101,19 @@ loop.roomViews = (function(mozL10n) {
 
       this.setState({copiedUrl: true});
     },
 
     render: function() {
       return (
         <div className="room-invitation-overlay">
           <form onSubmit={this.handleFormSubmit}>
-            <input type="text" ref="roomName"
+            <input type="text" className="input-room-name"
+              valueLink={this.linkState("newRoomName")}
+              onBlur={this.handleFormSubmit}
               placeholder={mozL10n.get("rooms_name_this_room_label")} />
           </form>
           <p>{mozL10n.get("invite_header_text")}</p>
           <div className="btn-group call-action-group">
             <button className="btn btn-info btn-email"
                     onClick={this.handleEmailButtonClick}>
               {mozL10n.get("share_button2")}
             </button>
--- a/browser/components/loop/content/shared/js/actions.js
+++ b/browser/components/loop/content/shared/js/actions.js
@@ -238,16 +238,25 @@ loop.shared.actions = (function() {
      * Opens a room.
      * XXX: should move to some roomActions module - refs bug 1079284
      */
     OpenRoom: Action.define("openRoom", {
       roomToken: String
     }),
 
     /**
+     * Renames a room.
+     * XXX: should move to some roomActions module - refs bug 1079284
+     */
+    RenameRoom: Action.define("renameRoom", {
+      roomToken: String,
+      newRoomName: String
+    }),
+
+    /**
      * Copy a room url into the user's clipboard.
      * XXX: should move to some roomActions module - refs bug 1079284
      */
     CopyRoomUrl: Action.define("copyRoomUrl", {
       roomUrl: String
     }),
 
     /**
@@ -261,25 +270,37 @@ loop.shared.actions = (function() {
     /**
      * XXX: should move to some roomActions module - refs bug 1079284
      */
     RoomFailure: Action.define("roomFailure", {
       error: Object
     }),
 
     /**
+     * Sets up the room information when it is received.
+     * XXX: should move to some roomActions module - refs bug 1079284
+     *
+     * @see https://wiki.mozilla.org/Loop/Architecture/Rooms#GET_.2Frooms.2F.7Btoken.7D
+     */
+    SetupRoomInfo: Action.define("setupRoomInfo", {
+      roomName: String,
+      roomOwner: String,
+      roomToken: String,
+      roomUrl: String
+    }),
+
+    /**
      * Updates the room information when it is received.
      * XXX: should move to some roomActions module - refs bug 1079284
      *
      * @see https://wiki.mozilla.org/Loop/Architecture/Rooms#GET_.2Frooms.2F.7Btoken.7D
      */
     UpdateRoomInfo: Action.define("updateRoomInfo", {
       roomName: String,
       roomOwner: String,
-      roomToken: String,
       roomUrl: String
     }),
 
     /**
      * Starts the process for the user to join the room.
      * XXX: should move to some roomActions module - refs bug 1079284
      */
     JoinRoom: Action.define("joinRoom", {
--- a/browser/components/loop/content/shared/js/activeRoomStore.js
+++ b/browser/components/loop/content/shared/js/activeRoomStore.js
@@ -145,16 +145,17 @@ loop.store.ActiveRoomStore = (function()
 
     /**
      * Registers the actions with the dispatcher that this store is interested
      * in.
      */
     _registerActions: function() {
       this._dispatcher.register(this, [
         "roomFailure",
+        "setupRoomInfo",
         "updateRoomInfo",
         "joinRoom",
         "joinedRoom",
         "connectedToSdkServers",
         "connectionFailure",
         "setMute",
         "remotePeerDisconnected",
         "remotePeerConnected",
@@ -189,22 +190,22 @@ loop.store.ActiveRoomStore = (function()
           if (error) {
             this._dispatcher.dispatch(new sharedActions.RoomFailure({
               error: error
             }));
             return;
           }
 
           this._dispatcher.dispatch(
-            new sharedActions.UpdateRoomInfo({
-            roomToken: actionData.roomToken,
-            roomName: roomData.roomName,
-            roomOwner: roomData.roomOwner,
-            roomUrl: roomData.roomUrl
-          }));
+            new sharedActions.SetupRoomInfo({
+              roomToken: actionData.roomToken,
+              roomName: roomData.roomName,
+              roomOwner: roomData.roomOwner,
+              roomUrl: roomData.roomUrl
+            }));
 
           // For the conversation window, we need to automatically
           // join the room.
           this._dispatcher.dispatch(new sharedActions.JoinRoom());
         }.bind(this));
     },
 
     /**
@@ -222,35 +223,68 @@ loop.store.ActiveRoomStore = (function()
       }
 
       this._registerActions();
 
       this.setStoreState({
         roomToken: actionData.token,
         roomState: ROOM_STATES.READY
       });
+
+      this._mozLoop.rooms.on("update:" + actionData.roomToken,
+        this._handleRoomUpdate.bind(this));
     },
 
     /**
-     * Handles the updateRoomInfo action. Updates the room data and
+     * Handles the setupRoomInfo action. Sets up the initial room data and
      * sets the state to `READY`.
      *
+     * @param {sharedActions.SetupRoomInfo} actionData
+     */
+    setupRoomInfo: function(actionData) {
+      this.setStoreState({
+        roomName: actionData.roomName,
+        roomOwner: actionData.roomOwner,
+        roomState: ROOM_STATES.READY,
+        roomToken: actionData.roomToken,
+        roomUrl: actionData.roomUrl
+      });
+
+      this._mozLoop.rooms.on("update:" + actionData.roomToken,
+        this._handleRoomUpdate.bind(this));
+    },
+
+    /**
+     * Handles the updateRoomInfo action. Updates the room data.
+     *
      * @param {sharedActions.UpdateRoomInfo} actionData
      */
     updateRoomInfo: function(actionData) {
       this.setStoreState({
         roomName: actionData.roomName,
         roomOwner: actionData.roomOwner,
-        roomState: ROOM_STATES.READY,
-        roomToken: actionData.roomToken,
         roomUrl: actionData.roomUrl
       });
     },
 
     /**
+     * Handles room updates notified by the mozLoop rooms API.
+     *
+     * @param {String} eventName The name of the event
+     * @param {Object} roomData  The new roomData.
+     */
+    _handleRoomUpdate: function(eventName, roomData) {
+      this._dispatcher.dispatch(new sharedActions.UpdateRoomInfo({
+        roomName: roomData.roomName,
+        roomOwner: roomData.roomOwner,
+        roomUrl: roomData.roomUrl
+      }));
+    },
+
+    /**
      * Handles the action to join to a room.
      */
     joinRoom: function() {
       // Reset the failure reason if necessary.
       if (this.getStoreState().failureReason) {
         this.setStoreState({failureReason: undefined});
       }
 
@@ -346,16 +380,20 @@ loop.store.ActiveRoomStore = (function()
       });
     },
 
     /**
      * Handles the window being unloaded. Ensures the room is left.
      */
     windowUnload: function() {
       this._leaveRoom();
+
+      // If we're closing the window, we can stop listening to updates.
+      this._mozLoop.rooms.off("update:" + this.getStoreState().roomToken,
+        this._handleRoomUpdate.bind(this));
     },
 
     /**
      * Handles a room being left.
      */
     leaveRoom: function() {
       this._leaveRoom();
     },
--- a/browser/components/loop/content/shared/js/roomStore.js
+++ b/browser/components/loop/content/shared/js/roomStore.js
@@ -82,16 +82,17 @@ loop.store = loop.store || {};
       "createRoomError",
       "copyRoomUrl",
       "deleteRoom",
       "deleteRoomError",
       "emailRoomUrl",
       "getAllRooms",
       "getAllRoomsError",
       "openRoom",
+      "renameRoom",
       "updateRoomList"
     ]);
   }
 
   RoomStore.prototype = _.extend({
     /**
      * Maximum size given to createRoom; only 2 is supported (and is
      * always passed) because that's what the user-experience is currently
@@ -406,13 +407,28 @@ loop.store = loop.store || {};
 
     /**
      * Opens a room
      *
      * @param {sharedActions.OpenRoom} actionData The action data.
      */
     openRoom: function(actionData) {
       this._mozLoop.rooms.open(actionData.roomToken);
+    },
+
+    /**
+     * Renames a room.
+     *
+     * @param {sharedActions.RenameRoom} actionData
+     */
+    renameRoom: function(actionData) {
+      this._mozLoop.rooms.rename(actionData.roomToken, actionData.newRoomName,
+        function(err) {
+          if (err) {
+            // XXX Give this a proper UI - bug 1100595.
+            console.error("Failed to rename the room", err);
+          }
+        });
     }
   }, Backbone.Events);
 
   loop.store.RoomStore = RoomStore;
 })();
--- a/browser/components/loop/standalone/content/js/standaloneMozLoop.js
+++ b/browser/components/loop/standalone/content/js/standaloneMozLoop.js
@@ -165,17 +165,23 @@ loop.StandaloneMozLoop = (function(mozL1
           }
         };
       }
 
       this._postToRoom(roomToken, sessionToken, {
         action: "leave",
         sessionToken: sessionToken
       }, null, callback);
-    }
+    },
+
+    // Dummy functions to reflect those in the desktop mozLoop.rooms that we
+    // don't currently use.
+    on: function() {},
+    once: function() {},
+    off: function() {}
   };
 
   var StandaloneMozLoop = function(options) {
     options = options || {};
     if (!options.baseServerUrl) {
       throw new Error("missing required baseServerUrl");
     }
 
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -114,16 +114,58 @@ describe("loop.roomViews", function () {
 
         React.addons.TestUtils.Simulate.click(emailBtn);
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWith(dispatcher.dispatch,
           new sharedActions.EmailRoomUrl({roomUrl: "http://invalid"}));
       });
 
+    describe("Rename Room", function() {
+      var roomNameBox;
+
+      beforeEach(function() {
+        view = mountTestComponent();
+        view.setState({
+          roomToken: "fakeToken",
+          roomName: "fakeName"
+        });
+
+        roomNameBox = view.getDOMNode().querySelector('.input-room-name');
+
+        React.addons.TestUtils.Simulate.change(roomNameBox, { target: {
+          value: "reallyFake"
+        }});
+      });
+
+      it("should dispatch a RenameRoom action when the focus is lost",
+        function() {
+          React.addons.TestUtils.Simulate.blur(roomNameBox);
+
+          sinon.assert.calledOnce(dispatcher.dispatch);
+          sinon.assert.calledWithExactly(dispatcher.dispatch,
+            new sharedActions.RenameRoom({
+              roomToken: "fakeToken",
+              newRoomName: "reallyFake"
+            }));
+        });
+
+      it("should dispatch a RenameRoom action when enter is pressed",
+        function() {
+          React.addons.TestUtils.Simulate.submit(roomNameBox);
+
+          sinon.assert.calledOnce(dispatcher.dispatch);
+          sinon.assert.calledWithExactly(dispatcher.dispatch,
+            new sharedActions.RenameRoom({
+              roomToken: "fakeToken",
+              newRoomName: "reallyFake"
+            }));
+        });
+    });
+
     describe("Copy Button", function() {
       beforeEach(function() {
         view = mountTestComponent();
 
         view.setState({roomUrl: "http://invalid"});
       });
 
       it("should dispatch a CopyRoomUrl action when the copy button is " +
--- a/browser/components/loop/test/shared/activeRoomStore_test.js
+++ b/browser/components/loop/test/shared/activeRoomStore_test.js
@@ -16,20 +16,22 @@ describe("loop.store.ActiveRoomStore", f
     sandbox = sinon.sandbox.create();
     sandbox.useFakeTimers();
 
     dispatcher = new loop.Dispatcher();
     sandbox.stub(dispatcher, "dispatch");
 
     fakeMozLoop = {
       rooms: {
-        get: sandbox.stub(),
-        join: sandbox.stub(),
-        refreshMembership: sandbox.stub(),
-        leave: sandbox.stub()
+        get: sinon.stub(),
+        join: sinon.stub(),
+        refreshMembership: sinon.stub(),
+        leave: sinon.stub(),
+        on: sinon.stub(),
+        off: sinon.stub()
       }
     };
 
     fakeSdkDriver = {
       connectSession: sandbox.stub(),
       disconnectSession: sandbox.stub()
     };
 
@@ -156,27 +158,27 @@ describe("loop.store.ActiveRoomStore", f
           type: "room",
           roomToken: fakeToken
         }));
 
         expect(store.getStoreState()).
           to.have.property('roomState', ROOM_STATES.GATHER);
       });
 
-    it("should dispatch an UpdateRoomInfo action if the get is successful",
+    it("should dispatch an SetupRoomInfo action if the get is successful",
       function() {
         store.setupWindowData(new sharedActions.SetupWindowData({
           windowId: "42",
           type: "room",
           roomToken: fakeToken
         }));
 
         sinon.assert.calledTwice(dispatcher.dispatch);
         sinon.assert.calledWithExactly(dispatcher.dispatch,
-          new sharedActions.UpdateRoomInfo(_.extend({
+          new sharedActions.SetupRoomInfo(_.extend({
             roomToken: fakeToken
           }, fakeRoomData)));
       });
 
     it("should dispatch a JoinRoom action if the get is successful",
       function() {
         store.setupWindowData(new sharedActions.SetupWindowData({
           windowId: "42",
@@ -228,41 +230,62 @@ describe("loop.store.ActiveRoomStore", f
         windowType: "room",
         token: "fakeToken"
       }));
 
       expect(store.getStoreState().roomState).eql(ROOM_STATES.READY);
     });
   });
 
-  describe("#updateRoomInfo", function() {
+  describe("#setupRoomInfo", function() {
     var fakeRoomInfo;
 
     beforeEach(function() {
       fakeRoomInfo = {
         roomName: "Its a room",
         roomOwner: "Me",
         roomToken: "fakeToken",
         roomUrl: "http://invalid"
       };
     });
 
     it("should set the state to READY", function() {
-      store.updateRoomInfo(new sharedActions.UpdateRoomInfo(fakeRoomInfo));
+      store.setupRoomInfo(new sharedActions.SetupRoomInfo(fakeRoomInfo));
 
       expect(store._storeState.roomState).eql(ROOM_STATES.READY);
     });
 
     it("should save the room information", function() {
+      store.setupRoomInfo(new sharedActions.SetupRoomInfo(fakeRoomInfo));
+
+      var state = store.getStoreState();
+      expect(state.roomName).eql(fakeRoomInfo.roomName);
+      expect(state.roomOwner).eql(fakeRoomInfo.roomOwner);
+      expect(state.roomToken).eql(fakeRoomInfo.roomToken);
+      expect(state.roomUrl).eql(fakeRoomInfo.roomUrl);
+    });
+  });
+
+  describe("#updateRoomInfo", function() {
+    var fakeRoomInfo;
+
+    beforeEach(function() {
+      fakeRoomInfo = {
+        roomName: "Its a room",
+        roomOwner: "Me",
+        roomUrl: "http://invalid"
+      };
+    });
+
+    it("should save the room information", function() {
       store.updateRoomInfo(new sharedActions.UpdateRoomInfo(fakeRoomInfo));
 
       var state = store.getStoreState();
       expect(state.roomName).eql(fakeRoomInfo.roomName);
       expect(state.roomOwner).eql(fakeRoomInfo.roomOwner);
-      expect(state.roomToken).eql(fakeRoomInfo.roomToken);
       expect(state.roomUrl).eql(fakeRoomInfo.roomUrl);
     });
   });
 
   describe("#joinRoom", function() {
     beforeEach(function() {
       store.setStoreState({roomToken: "tokenFake"});
     });
@@ -591,9 +614,38 @@ describe("loop.store.ActiveRoomStore", f
     });
 
     it("should set the state to ready", function() {
       store.leaveRoom();
 
       expect(store._storeState.roomState).eql(ROOM_STATES.READY);
     });
   });
+
+  describe("Events", function() {
+    describe("update:{roomToken}", function() {
+      beforeEach(function() {
+        store.setupRoomInfo(new sharedActions.SetupRoomInfo({
+          roomName: "Its a room",
+          roomOwner: "Me",
+          roomToken: "fakeToken",
+          roomUrl: "http://invalid"
+        }));
+      });
+
+      it("should dispatch an UpdateRoomInfo action", function() {
+        sinon.assert.calledOnce(fakeMozLoop.rooms.on);
+
+        var fakeRoomData = {
+          roomName: "fakeName",
+          roomOwner: "you",
+          roomUrl: "original"
+        };
+
+        fakeMozLoop.rooms.on.callArgWith(1, "update", fakeRoomData);
+
+        sinon.assert.calledOnce(dispatcher.dispatch);
+        sinon.assert.calledWithExactly(dispatcher.dispatch,
+          new sharedActions.UpdateRoomInfo(fakeRoomData));
+      });
+    });
+  });
 });
--- a/browser/components/loop/test/shared/roomStore_test.js
+++ b/browser/components/loop/test/shared/roomStore_test.js
@@ -432,9 +432,36 @@ describe("loop.store.RoomStore", functio
 
     it("should open the room via mozLoop", function() {
       dispatcher.dispatch(new sharedActions.OpenRoom({roomToken: "42abc"}));
 
       sinon.assert.calledOnce(fakeMozLoop.rooms.open);
       sinon.assert.calledWithExactly(fakeMozLoop.rooms.open, "42abc");
     });
   });
+
+  describe("#renameRoom", function() {
+    var store, fakeMozLoop;
+
+    beforeEach(function() {
+      fakeMozLoop = {
+        rooms: {
+          rename: sinon.spy()
+        }
+      };
+      store = new loop.store.RoomStore({
+        dispatcher: dispatcher,
+        mozLoop: fakeMozLoop
+      });
+    });
+
+    it("should rename the room via mozLoop", function() {
+      dispatcher.dispatch(new sharedActions.RenameRoom({
+        roomToken: "42abc",
+        newRoomName: "silly name"
+      }));
+
+      sinon.assert.calledOnce(fakeMozLoop.rooms.rename);
+      sinon.assert.calledWith(fakeMozLoop.rooms.rename, "42abc",
+        "silly name");
+    });
+  });
 });
--- a/browser/components/loop/test/xpcshell/test_looprooms.js
+++ b/browser/components/loop/test/xpcshell/test_looprooms.js
@@ -204,26 +204,32 @@ add_task(function* setup_server() {
   function returnRoomDetails(res, roomName) {
     roomDetail.roomName = roomName;
     res.setStatusLine(null, 200, "OK");
     res.write(JSON.stringify(roomDetail));
     res.processAsync();
     res.finish();
   }
 
+  function getJSONData(body) {
+    return JSON.parse(CommonUtils.readBytesFromInputStream(body));
+  }
+
   // Add a request handler for each room in the list.
   [...kRooms.values()].forEach(function(room) {
     loopServer.registerPathHandler("/rooms/" + encodeURIComponent(room.roomToken), (req, res) => {
       if (req.method == "POST") {
-        let body = CommonUtils.readBytesFromInputStream(req.bodyInputStream);
-        let data = JSON.parse(body);
+        let data = getJSONData(req.bodyInputStream);
         res.setStatusLine(null, 200, "OK");
         res.write(JSON.stringify(data));
         res.processAsync();
         res.finish();
+      } else if (req.method == "PATCH") {
+        let data = getJSONData(req.bodyInputStream);
+        returnRoomDetails(res, data.roomName);
       } else {
         returnRoomDetails(res, room.roomName);
       }
     });
   });
 
   loopServer.registerPathHandler("/rooms/error401", (req, res) => {
     res.setStatusLine(null, 401, "Not Found");
@@ -358,16 +364,23 @@ add_task(function* test_refreshMembershi
 // Test if leaving a room works as expected.
 add_task(function* test_leaveRoom() {
   let roomToken = "_nxD4V4FflQ";
   let leaveData = yield LoopRooms.promise("leave", roomToken, "fakeLeaveSessionToken");
   Assert.equal(leaveData.action, "leave");
   Assert.equal(leaveData.sessionToken, "fakeLeaveSessionToken");
 });
 
+// Test if renaming a room works as expected.
+add_task(function* test_renameRoom() {
+  let roomToken = "_nxD4V4FflQ";
+  let renameData = yield LoopRooms.promise("rename", roomToken, "fakeName");
+  Assert.equal(renameData.roomName, "fakeName");
+});
+
 // Test if the event emitter implementation doesn't leak and is working as expected.
 add_task(function* () {
   Assert.strictEqual(gExpectedAdds.length, 0, "No room additions should be expected anymore");
   Assert.strictEqual(gExpectedUpdates.length, 0, "No room updates should be expected anymore");
  });
 
 function run_test() {
   setupFakeLoopServer();
--- a/browser/components/search/test/browser.ini
+++ b/browser/components/search/test/browser.ini
@@ -31,9 +31,9 @@ skip-if = e10s # Bug ?????? - some issue
 skip-if = e10s # Bug ?????? - some issue with progress listeners [JavaScript Error: "req.originalURI is null" {file: "chrome://mochitests/content/browser/browser/components/search/test/browser_bing_behavior.js" line: 127}]
 [browser_healthreport.js]
 [browser_private_search_perwindowpb.js]
 skip-if = e10s # Bug ?????? - Test uses load event and checks event.target.
 [browser_yahoo.js]
 [browser_yahoo_behavior.js]
 skip-if = e10s # Bug ?????? - some issue with progress listeners [JavaScript Error: "req.originalURI is null" {file: "chrome://mochitests/content/browser/browser/components/search/test/browser_bing_behavior.js" line: 127}]
 [browser_abouthome_behavior.js]
-skip-if = e10s # Bug ???????
+skip-if = e10s || true # Bug ??????, Bug 1100301 - leaks windows until shutdown when --run-by-dir
--- a/browser/components/tabview/test/browser.ini
+++ b/browser/components/tabview/test/browser.ini
@@ -14,17 +14,17 @@ support-files =
 [browser_tabview_alltabs.js]
 [browser_tabview_apptabs.js]
 [browser_tabview_bug580412.js]
 [browser_tabview_bug586553.js]
 [browser_tabview_bug587043.js]
 [browser_tabview_bug587231.js]
 skip-if = buildapp == 'mulet'
 [browser_tabview_bug587276.js]
-skip-if = e10s # Bug 1091200
+skip-if = e10s || true # Bug 1091200, bug 1096285
 [browser_tabview_bug587351.js]
 [browser_tabview_bug587503.js]
 [browser_tabview_bug587990.js]
 [browser_tabview_bug588265.js]
 [browser_tabview_bug589324.js]
 skip-if = e10s # Bug 1086190
 [browser_tabview_bug590606.js]
 [browser_tabview_bug591706.js]
--- a/browser/config/mozconfigs/linux32/nightly
+++ b/browser/config/mozconfigs/linux32/nightly
@@ -1,13 +1,12 @@
 . "$topsrcdir/browser/config/mozconfigs/linux32/common-opt"
 
 ac_add_options --enable-signmar
 ac_add_options --enable-profiling
-ac_add_options --disable-elf-hack # --enable-elf-hack conflicts with --enable-profiling
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 # This will overwrite the default of stripping everything and keep the symbol table.
 # This is useful for profiling and debugging and only increases the package size
 # by 2 MBs.
 STRIP_FLAGS="--strip-debug"
--- a/browser/config/mozconfigs/linux32/valgrind
+++ b/browser/config/mozconfigs/linux32/valgrind
@@ -1,14 +1,13 @@
 no_tooltool=1
 no_sccache=1
 
 . $topsrcdir/browser/config/mozconfigs/linux32/nightly
 
 ac_add_options --enable-valgrind
 ac_add_options --disable-jemalloc
-ac_add_options --disable-elf-hack
 ac_add_options --enable-optimize="-g -O -freorder-blocks"
 ac_add_options --disable-install-strip
 
 # Include the override mozconfig again (even though the above includes it)
 # since it's supposed to override everything.
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux64/nightly
+++ b/browser/config/mozconfigs/linux64/nightly
@@ -1,13 +1,12 @@
 . "$topsrcdir/browser/config/mozconfigs/linux64/common-opt"
 
 ac_add_options --enable-signmar
 ac_add_options --enable-profiling
-ac_add_options --disable-elf-hack # --enable-elf-hack conflicts with --enable-profiling
 
 # Nightlies only since this has a cost in performance
 ac_add_options --enable-js-diagnostics
 
 # This will overwrite the default of stripping everything and keep the symbol table.
 # This is useful for profiling and debugging and only increases the package size
 # by 2 MBs.
 STRIP_FLAGS="--strip-debug"
--- a/browser/config/mozconfigs/linux64/valgrind
+++ b/browser/config/mozconfigs/linux64/valgrind
@@ -1,14 +1,13 @@
 no_tooltool=1
 no_sccache=1
 
 . $topsrcdir/browser/config/mozconfigs/linux64/nightly
 
 ac_add_options --enable-valgrind
 ac_add_options --disable-jemalloc
-ac_add_options --disable-elf-hack
 ac_add_options --enable-optimize="-g -O -freorder-blocks"
 ac_add_options --disable-install-strip
 
 # Include the override mozconfig again (even though the above includes it)
 # since it's supposed to override everything.
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/whitelist
+++ b/browser/config/mozconfigs/whitelist
@@ -29,27 +29,25 @@ whitelist['nightly']['linux32'] += [
     'CC="ccache $REAL_CC"',
     'mk_add_options PROFILE_GEN_SCRIPT=@TOPSRCDIR@/build/profile_pageloader.pl',
     'ac_add_options --with-ccache=/usr/bin/ccache',
     '. "$topsrcdir/build/mozconfig.cache"',
     'export MOZILLA_OFFICIAL=1',
     'export MOZ_TELEMETRY_REPORTING=1',
     "mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
     'STRIP_FLAGS="--strip-debug"',
-    'ac_add_options --disable-elf-hack # --enable-elf-hack conflicts with --enable-profiling',
 ]
 
 whitelist['nightly']['linux64'] += [
     'export MOZILLA_OFFICIAL=1',
     'export MOZ_TELEMETRY_REPORTING=1',
     "mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
     'STRIP_FLAGS="--strip-debug"',
     'ac_add_options --with-ccache=/usr/bin/ccache',
     '. "$topsrcdir/build/mozconfig.cache"',
-    'ac_add_options --disable-elf-hack # --enable-elf-hack conflicts with --enable-profiling',
 ]
 
 whitelist['nightly']['macosx-universal'] += [
     'if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then',
     'ac_add_options --with-macbundlename-prefix=Firefox',
     'fi',
     'mk_add_options MOZ_MAKE_FLAGS="-j12"',
     'ac_add_options --with-ccache',
--- a/browser/devtools/debugger/debugger-controller.js
+++ b/browser/devtools/debugger/debugger-controller.js
@@ -193,24 +193,24 @@ let DebuggerController = {
    *         A promise that is resolved when the debugger finishes connecting.
    */
   connect: Task.async(function*() {
     if (this._connected) {
       return;
     }
 
     let target = this._target;
-    let { client, form: { chromeDebugger, traceActor, addonActor } } = target;
+    let { client, form: { chromeDebugger, traceActor, actor } } = target;
     target.on("close", this._onTabDetached);
     target.on("navigate", this._onTabNavigated);
     target.on("will-navigate", this._onTabNavigated);
     this.client = client;
 
-    if (addonActor) {
-      yield this._startAddonDebugging(addonActor);
+    if (target.isAddon) {
+      yield this._startAddonDebugging(actor);
     } else if (target.chrome) {
       yield this._startChromeDebugging(chromeDebugger);
     } else {
       yield this._startDebuggingTab();
 
       if (Prefs.tracerEnabled && traceActor) {
         yield this._startTracingTab(traceActor);
       }
--- a/browser/devtools/debugger/test/browser.ini
+++ b/browser/devtools/debugger/test/browser.ini
@@ -349,53 +349,53 @@ skip-if = e10s
 skip-if = e10s
 [browser_dbg_scripts-switching-01.js]
 skip-if = e10s
 [browser_dbg_scripts-switching-02.js]
 skip-if = e10s
 [browser_dbg_scripts-switching-03.js]
 skip-if = e10s
 [browser_dbg_search-autofill-identifier.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-basic-01.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-basic-02.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-basic-03.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-basic-04.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-global-01.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-global-02.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-global-03.js]
-skip-if = e10s
+skip-if = e10s # Bug 1093535
 [browser_dbg_search-global-04.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-global-05.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-global-06.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-popup-jank.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-sources-01.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-sources-02.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-sources-03.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_search-symbols.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_searchbox-help-popup-01.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_searchbox-help-popup-02.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_searchbox-parse.js]
-skip-if = e10s
+skip-if = e10s && debug
 [browser_dbg_source-maps-01.js]
 skip-if = e10s && debug
 [browser_dbg_source-maps-02.js]
 skip-if = e10s && debug
 [browser_dbg_source-maps-03.js]
 skip-if = e10s && debug
 [browser_dbg_source-maps-04.js]
 skip-if = e10s # Bug 1093535
--- a/browser/devtools/debugger/test/browser_dbg_search-autofill-identifier.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-autofill-identifier.js
@@ -5,17 +5,17 @@
  * Tests that Debugger Search uses the identifier under cursor if nothing is
  * selected or manually passed and searching using certain operators.
  */
 "use strict";
 
 function test() {
   const TAB_URL = EXAMPLE_URL + "doc_function-search.html";
 
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     let Source = 'code_function-search-01.js';
     let Debugger = aPanel.panelWin;
     let Editor = Debugger.DebuggerView.editor;
     let Filtering = Debugger.DebuggerView.Filtering;
 
     function doSearch(aOperator) {
       Editor.dropSelection();
       Filtering._doSearch(aOperator);
--- a/browser/devtools/debugger/test/browser_dbg_search-basic-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-basic-01.js
@@ -2,23 +2,22 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests basic search functionality (find token and jump to line).
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_recursion-stack.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gEditor, gSources, gFiltering, gSearchBox;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gFiltering = gDebugger.DebuggerView.Filtering;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceShown(gPanel, ".html").then(performTest);
@@ -304,16 +303,15 @@ function performTest() {
     "The search field has the right initial value (2).");
 
 
   closeDebuggerAndFinish(gPanel);
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
   gFiltering = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-basic-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-basic-02.js
@@ -2,23 +2,22 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests basic file search functionality.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gSources, gSearchBox;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(performSimpleSearch)
       .then(() => verifySourceAndCaret("-01.js", 1, 1, [1, 1]))
@@ -28,17 +27,17 @@ function test() {
       .then(() => verifySourceAndCaret("-01.js", 2, 48, [96, 100]))
       .then(combineWithTokenColonSearch)
       .then(() => verifySourceAndCaret("-01.js", 2, 11, [56, 63]))
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
-    gDebuggee.firstCall();
+    callInTab(gTab, "firstCall");
   });
 }
 
 function performSimpleSearch() {
   let finished = promise.all([
     ensureSourceIs(gPanel, "-02.js"),
     ensureCaretAt(gPanel, 1),
     waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FILE_SEARCH_MATCH_FOUND),
@@ -111,14 +110,13 @@ function verifySourceAndCaret(aUrl, aLin
   ok(isCaretPos(gPanel, aLine, aColumn),
     "The current caret position appears to be correct.");
   ok(isEditorSel(gPanel, aSelection),
     "The current editor selection appears to be correct.");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gSources = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-basic-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-basic-03.js
@@ -3,23 +3,22 @@
 
 /**
  * Tests that searches which cause a popup to be shown properly handle the
  * ESCAPE key.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gSources, gSearchBox;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(performFileSearch)
       .then(escapeAndHide)
@@ -32,17 +31,17 @@ function test() {
       .then(performGlobalSearch)
       .then(escapeAndClear)
       .then(() => verifySourceAndCaret("-01.js", 4, 10))
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
-    gDebuggee.firstCall();
+    callInTab(gTab, "firstCall");
   });
 }
 
 function performFileSearch() {
   let finished = promise.all([
     ensureSourceIs(gPanel, "-02.js"),
     ensureCaretAt(gPanel, 1),
     once(gDebugger, "popupshown"),
@@ -107,14 +106,13 @@ function verifySourceAndCaret(aUrl, aLin
   ok(gSources.selectedItem.value.contains(aUrl),
     "The selected item's value appears to be correct.");
   ok(isCaretPos(gPanel, aLine, aColumn),
     "The current caret position appears to be correct.");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gSources = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-basic-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-basic-04.js
@@ -3,23 +3,22 @@
 
 /**
  * Tests that the selection is dropped for line and token searches, after
  * pressing backspace enough times.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gEditor, gSources, gSearchBox;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceShown(gPanel, "-01.js")
       .then(testLineSearch)
@@ -118,15 +117,14 @@ function testTokenSearch() {
   is(gEditor.getSelection(), "",
     "The editor selected text appears to be correct (2.5).");
   is(gSearchBox.value, "",
     "The searchbox should have been cleared.");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-global-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-global-01.js
@@ -3,40 +3,39 @@
 
 /**
  * Tests basic functionality of global search (lowercase + upper case, expected
  * UI behavior, number of results found etc.)
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gEditor, gSources, gSearchView, gSearchBox;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gSearchView = gDebugger.DebuggerView.GlobalSearch;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(firstSearch)
       .then(secondSearch)
       .then(clearSearch)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
-    gDebuggee.firstCall();
+    callInTab(gTab, "firstCall");
   });
 }
 
 function firstSearch() {
   let deferred = promise.defer();
 
   is(gSearchView.itemCount, 0,
     "The global search pane shouldn't have any entries yet.");
@@ -259,16 +258,15 @@ function clearSearch() {
   is(gSearchView.widget._parent.hidden, true,
     "The global search pane shouldn't be visible after clearing.");
   is(gSearchView._splitter.hidden, true,
     "The global search pane splitter shouldn't be visible after clearing.");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
   gSearchView = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-global-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-global-02.js
@@ -3,23 +3,22 @@
 
 /**
  * Tests if the global search results switch back and forth, and wrap around
  * when switching between them.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gEditor, gSources, gSearchView, gSearchBox;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gSearchView = gDebugger.DebuggerView.GlobalSearch;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
@@ -29,17 +28,17 @@ function test() {
       .then(doWrapAroundJump)
       .then(doBackwardsWrapAroundJump)
       .then(testSearchTokenEmpty)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
-    gDebuggee.firstCall();
+    callInTab(gTab, "firstCall");
   });
 }
 
 function firstSearch() {
   let deferred = promise.defer();
 
   is(gSearchView.itemCount, 0,
     "The global search pane shouldn't have any entries yet.");
@@ -203,16 +202,15 @@ function testSearchTokenEmpty() {
   is(gSearchView.widget._parent.hidden, true,
     "The global search pane shouldn't be visible after clearing.");
   is(gSearchView._splitter.hidden, true,
     "The global search pane splitter shouldn't be visible after clearing.");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
   gSearchView = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-global-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-global-03.js
@@ -3,39 +3,38 @@
 
 /**
  * Tests if the global search results are cleared on location changes, and
  * the expected UI behaviors are triggered.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gEditor, gSources, gSearchView, gSearchBox;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gSearchView = gDebugger.DebuggerView.GlobalSearch;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(firstSearch)
       .then(performTest)
       .then(() => closeDebuggerAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
-    gDebuggee.firstCall();
+    callInTab(gTab, "firstCall");
   });
 }
 
 function firstSearch() {
   let deferred = promise.defer();
 
   is(gSearchView.itemCount, 0,
     "The global search pane shouldn't have any entries yet.");
@@ -91,16 +90,15 @@ function performTest() {
     deferred.resolve();
   });
 
   return deferred.promise;
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
   gSearchView = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-global-04.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-global-04.js
@@ -3,39 +3,38 @@
 
 /**
  * Tests if the global search results trigger MatchFound and NoMatchFound events
  * properly, and triggers the expected UI behavior.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gEditor, gSources, gSearchView, gSearchBox;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gSearchView = gDebugger.DebuggerView.GlobalSearch;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(firstSearch)
       .then(secondSearch)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
-    gDebuggee.firstCall();
+    callInTab(gTab, "firstCall");
   });
 }
 
 function firstSearch() {
   let deferred = promise.defer();
 
   gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
     // Some operations are synchronously dispatched on the main thread,
@@ -79,16 +78,15 @@ function secondSearch() {
 
   typeText(gSearchBox, "/");
 
   return deferred.promise;
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
   gSearchView = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-global-05.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-global-05.js
@@ -4,23 +4,22 @@
 /**
  * Tests if the global search results are expanded/collapsed on click, and
  * clicking matches makes the source editor shows the correct source and
  * makes a selection based on the match.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gEditor, gSources, gSearchView, gSearchBox;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gSearchView = gDebugger.DebuggerView.GlobalSearch;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
@@ -28,17 +27,17 @@ function test() {
       .then(testExpandCollapse)
       .then(testClickLineToJump)
       .then(testClickMatchToJump)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
-    gDebuggee.firstCall();
+    callInTab(gTab, "firstCall");
   });
 }
 
 function doSearch() {
   let deferred = promise.defer();
 
   gDebugger.once(gDebugger.EVENTS.GLOBAL_SEARCH_MATCH_FOUND, () => {
     // Some operations are synchronously dispatched on the main thread,
@@ -141,16 +140,15 @@ function testClickMatchToJump() {
 
   EventUtils.sendMouseEvent({ type: "click" }, lastMatch);
 
   return deferred.promise;
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
   gSearchView = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-global-06.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-global-06.js
@@ -3,23 +3,22 @@
 
 /**
  * Tests if the global search results are hidden when they're supposed to
  * (after a focus lost, or when ESCAPE is pressed).
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gEditor, gSources, gSearchView, gSearchBox;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gSearchView = gDebugger.DebuggerView.GlobalSearch;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
@@ -27,17 +26,17 @@ function test() {
       .then(testFocusLost)
       .then(doSearch)
       .then(testEscape)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
-    gDebuggee.firstCall();
+    callInTab(gTab, "firstCall");
   });
 }
 
 function doSearch() {
   let deferred = promise.defer();
 
   is(gSearchView.itemCount, 0,
     "The global search pane shouldn't have any entries yet.");
@@ -106,16 +105,15 @@ function testEscape() {
   is(gSearchView.widget._parent.hidden, true,
     "The global search pane shouldn't be visible after clearing.");
   is(gSearchView._splitter.hidden, true,
     "The global search pane splitter shouldn't be visible after clearing.");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
   gSearchView = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-popup-jank.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-popup-jank.js
@@ -2,23 +2,22 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that sources aren't selected by default when finding a match.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gSearchBox;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     gDebugger.DebuggerView.FilteredSources._autoSelectFirstItem = false;
     gDebugger.DebuggerView.FilteredFunctions._autoSelectFirstItem = false;
 
     waitForSourceShown(gPanel, "-01.js")
@@ -106,13 +105,12 @@ function pressKey(aKey) {
 function pressKeyToHide(aKey) {
   let finished = waitForResultsHidden();
   EventUtils.sendKey(aKey, gDebugger);
   return finished;
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-sources-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-sources-01.js
@@ -2,26 +2,25 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests basic functionality of sources filtering (file search).
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gSources, gSearchView, gSearchBox;
 
 function test() {
   // Debug test slaves are a bit slow at this test.
   requestLongerTimeout(3);
 
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
     gSearchView = gDebugger.DebuggerView.FilteredSources;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceShown(gPanel, "-01.js")
       .then(bogusSearch)
@@ -221,15 +220,14 @@ function verifyContents(aArgs) {
   is(gSearchView.itemCount, aArgs.itemCount,
     "No sources should be displayed in the sources container after a bogus search.");
   is(gSearchView.hidden, aArgs.hidden,
     "No sources should be displayed in the sources container after a bogus search.");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gSources = null;
   gSearchView = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-sources-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-sources-02.js
@@ -2,26 +2,25 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests more complex functionality of sources filtering (file search).
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gSources, gSourceUtils, gSearchView, gSearchBox;
 
 function test() {
   // Debug test slaves are a bit slow at this test.
   requestLongerTimeout(3);
 
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
     gSourceUtils = gDebugger.SourceUtils;
     gSearchView = gDebugger.DebuggerView.FilteredSources;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceShown(gPanel, "-01.js")
@@ -263,16 +262,15 @@ function verifyContents(aMatches) {
       "The filtered sources view should have the correct source labels.");
     ok(gSearchView.widget._parent.querySelector(".results-panel-item-label-below[value=\"" + trimmedLocation + "\"]"),
       "The filtered sources view should have the correct source locations.");
   }
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gSources = null;
   gSourceUtils = null;
   gSearchView = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-sources-03.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-sources-03.js
@@ -2,23 +2,22 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that while searching for files, the sources list remains unchanged.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_editor-mode.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gSources, gSearchBox;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceShown(gPanel, "-01.js")
       .then(superGenericSearch)
       .then(verifySourcesPane)
@@ -87,14 +86,13 @@ function verifySourcesPane() {
   ok(gSources.getItemForAttachment(e => e.label == "code_test-editor-mode"),
     "The second source's label should be correct.");
   ok(gSources.getItemForAttachment(e => e.label == "doc_editor-mode.html"),
     "The third source's label should be correct.");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gSources = null;
   gSearchBox = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_search-symbols.js
+++ b/browser/devtools/debugger/test/browser_dbg_search-symbols.js
@@ -2,23 +2,22 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests if the function searching works properly.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_function-search.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gEditor, gSources, gSearchBox, gFilteredFunctions;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
     gFilteredFunctions = gDebugger.DebuggerView.FilteredFunctions;
 
     waitForSourceShown(gPanel, "-01.js")
@@ -454,16 +453,15 @@ function saveSearch() {
 
 function writeInfo() {
   info("Current source url:\n" + gSources.selectedValue);
   info("Debugger editor text:\n" + gEditor.getText());
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSources = null;
   gSearchBox = null;
   gFilteredFunctions = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_searchbox-help-popup-01.js
+++ b/browser/devtools/debugger/test/browser_dbg_searchbox-help-popup-01.js
@@ -3,37 +3,36 @@
 
 /**
  * Make sure that the searchbox popup is displayed when focusing the searchbox,
  * and hidden when the user starts typing.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gSearchBox, gSearchBoxPanel;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
     gSearchBoxPanel = gDebugger.DebuggerView.Filtering._searchboxHelpPanel;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(showPopup)
       .then(hidePopup)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
-    gDebuggee.firstCall();
+    callInTab(gTab, "firstCall");
   });
 }
 
 function showPopup() {
   is(gSearchBoxPanel.state, "closed",
     "The search box panel shouldn't be visible yet.");
 
   let finished = once(gSearchBoxPanel, "popupshown");
@@ -47,14 +46,13 @@ function hidePopup() {
 
   let finished = once(gSearchBoxPanel, "popuphidden");
   setText(gSearchBox, "#");
   return finished;
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gSearchBox = null;
   gSearchBoxPanel = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_searchbox-help-popup-02.js
+++ b/browser/devtools/debugger/test/browser_dbg_searchbox-help-popup-02.js
@@ -3,23 +3,22 @@
 
 /**
  * Make sure that the searchbox popup isn't displayed when there's some text
  * already present.
  */
 
 const TAB_URL = EXAMPLE_URL + "doc_script-switching-01.html";
 
-let gTab, gDebuggee, gPanel, gDebugger;
+let gTab, gPanel, gDebugger;
 let gEditor, gSearchBox, gSearchBoxPanel;
 
 function test() {
-  initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger(TAB_URL).then(([aTab,, aPanel]) => {
     gTab = aTab;
-    gDebuggee = aDebuggee;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
     gSearchBoxPanel = gDebugger.DebuggerView.Filtering._searchboxHelpPanel;
 
     once(gSearchBoxPanel, "popupshown").then(() => {
       ok(false, "Damn it, this shouldn't have happened.");
@@ -29,17 +28,17 @@ function test() {
       .then(tryShowPopup)
       .then(focusEditor)
       .then(testFocusLost)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
       .then(null, aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
-    gDebuggee.firstCall();
+    callInTab(gTab, "firstCall");
   });
 }
 
 function tryShowPopup() {
   setText(gSearchBox, "#call()");
   ok(isCaretPos(gPanel, 4, 22),
     "The editor caret position appears to be correct.");
   ok(isEditorSel(gPanel, [125, 131]),
@@ -72,15 +71,14 @@ function testFocusLost() {
     "The editor selected text appears to be correct after gaining focus.");
 
   is(gSearchBoxPanel.state, "closed",
     "The search box panel should still not be visible.");
 }
 
 registerCleanupFunction(function() {
   gTab = null;
-  gDebuggee = null;
   gPanel = null;
   gDebugger = null;
   gEditor = null;
   gSearchBox = null;
   gSearchBoxPanel = null;
 });
--- a/browser/devtools/debugger/test/browser_dbg_searchbox-parse.js
+++ b/browser/devtools/debugger/test/browser_dbg_searchbox-parse.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 /**
  * Tests that text entered in the debugger's searchbox is properly parsed.
  */
 
 function test() {
-  initDebugger("about:blank").then(([aTab, aDebuggee, aPanel]) => {
+  initDebugger("about:blank").then(([aTab,, aPanel]) => {
     let filterView = aPanel.panelWin.DebuggerView.Filtering;
     let searchbox = aPanel.panelWin.DebuggerView.Filtering._searchbox;
 
     setText(searchbox, "");
     is(filterView.searchData.toSource(), '["", [""]]',
       "The searchbox data wasn't parsed correctly (1).");
 
     setText(searchbox, "#token");
--- a/browser/devtools/debugger/test/head.js
+++ b/browser/devtools/debugger/test/head.js
@@ -562,21 +562,17 @@ AddonDebugger.prototype = {
 
     let connected = promise.defer();
     this.client.connect(connected.resolve);
     yield connected.promise;
 
     let addonActor = yield getAddonActorForUrl(this.client, aUrl);
 
     let targetOptions = {
-      form: {
-        addonActor: addonActor.actor,
-        consoleActor: addonActor.consoleActor,
-        title: addonActor.name
-      },
+      form: addonActor,
       client: this.client,
       chrome: true
     };
 
     let toolboxOptions = {
       customIframe: this.frame
     };
 
--- a/browser/devtools/framework/connect/connect.js
+++ b/browser/devtools/framework/connect/connect.js
@@ -149,17 +149,17 @@ let onConnectionReady = Task.async(funct
 });
 
 /**
  * Build one button for an add-on actor.
  */
 function buildAddonLink(addon, parent) {
   let a = document.createElement("a");
   a.onclick = function() {
-    openToolbox({ addonActor: addon.actor, title: addon.name }, true, "jsdebugger");
+    openToolbox(addon, true, "jsdebugger");
   }
 
   a.textContent = addon.name;
   a.title = addon.id;
   a.href = "#";
 
   parent.appendChild(a);
 }
--- a/browser/devtools/framework/gDevTools.jsm
+++ b/browser/devtools/framework/gDevTools.jsm
@@ -775,17 +775,17 @@ let gDevToolsBrowser = {
       onCommand: function(aEvent) {
         gDevToolsBrowser.openWebIDE();
       }
     });
   },
 
   isWebIDEWidgetInstalled: function() {
     let widgetWrapper = CustomizableUI.getWidget("webide-button");
-    return !!(widgetWrapper && widgetWrapper.instances.some(i => !!i.node));
+    return !!(widgetWrapper && widgetWrapper.provider == CustomizableUI.PROVIDER_API);
   },
 
   /**
    * Uninstall WebIDE widget
    */
   uninstallWebIDEWidget: function() {
     if (this.isWebIDEWidgetInstalled()) {
       CustomizableUI.removeWidgetFromArea("webide-button");
--- a/browser/devtools/framework/target.js
+++ b/browser/devtools/framework/target.js
@@ -275,16 +275,23 @@ TabTarget.prototype = {
    *
    * @param {String} traitName
    * @return {Mixed}
    */
   getTrait: function (traitName) {
     if (!this.client) {
       throw new Error("TabTarget#getTrait() can only be called on remote tabs.");
     }
+
+    // If the targeted actor exposes traits and has a defined value for this traits,
+    // override the root actor traits
+    if (this.form.traits && traitName in this.form.traits) {
+      return this.form.traits[traitName];
+    }
+
     return this.client.traits[traitName];
   },
 
   get version() { return getVersion(); },
 
   get tab() {
     return this._tab;
   },
@@ -318,32 +325,37 @@ TabTarget.prototype = {
     // during shutdown.
     if (this._tab && this._tab.linkedBrowser) {
       return this._tab.linkedBrowser.contentWindow;
     }
     return null;
   },
 
   get name() {
-    return this._tab && this._tab.linkedBrowser.contentDocument ?
-           this._tab.linkedBrowser.contentDocument.title :
-           this._form.title;
+    if (this._tab && this._tab.linkedBrowser.contentDocument) {
+      return this._tab.linkedBrowser.contentDocument.title
+    } else if (this.isAddon) {
+      return this._form.name;
+    } else {
+      return this._form.title;
+    }
   },
 
   get url() {
     return this._tab ? this._tab.linkedBrowser.currentURI.spec :
                        this._form.url;
   },
 
   get isRemote() {
     return !this.isLocalTab;
   },
 
   get isAddon() {
-    return !!(this._form && this._form.addonActor);
+    return !!(this._form && this._form.actor &&
+              this._form.actor.match(/conn\d+\.addon\d+/));
   },
 
   get isLocalTab() {
     return !!this._tab;
   },
 
   get isMultiProcess() {
     return !this.window;
--- a/browser/devtools/framework/toolbox-process-window.js
+++ b/browser/devtools/framework/toolbox-process-window.js
@@ -32,21 +32,17 @@ function connect() {
   );
   gClient = new DebuggerClient(transport);
   gClient.connect(() => {
     let addonID = getParameterByName("addonID");
 
     if (addonID) {
       gClient.listAddons(({addons}) => {
         let addonActor = addons.filter(addon => addon.id === addonID).pop();
-        openToolbox({
-          addonActor: addonActor.actor,
-          consoleActor: addonActor.consoleActor,
-          title: addonActor.name
-        });
+        openToolbox(addonActor);
       });
     } else {
       gClient.listTabs(openToolbox);
     }
   });
 }
 
 // Certain options should be toggled since we can assume chrome debugging here
--- a/browser/devtools/framework/toolbox.js
+++ b/browser/devtools/framework/toolbox.js
@@ -48,21 +48,22 @@ loader.lazyGetter(this, "toolboxStrings"
 loader.lazyGetter(this, "Selection", () => require("devtools/framework/selection").Selection);
 loader.lazyGetter(this, "InspectorFront", () => require("devtools/server/actors/inspector").InspectorFront);
 
 // White-list buttons that can be toggled to prevent adding prefs for
 // addons that have manually inserted toolbarbuttons into DOM.
 // (By default, supported target is only local tab)
 const ToolboxButtons = [
   { id: "command-button-pick",
-    isTargetSupported: target => !target.isAddon },
+    isTargetSupported: target =>
+      target.getTrait("highlightable")
+  },
   { id: "command-button-frames",
-    isTargetSupported: target => (
-      !target.isAddon && target.activeTab && target.activeTab.traits.frames
-    )
+    isTargetSupported: target =>
+      ( target.activeTab && target.activeTab.traits.frames )
   },
   { id: "command-button-splitconsole",
     isTargetSupported: target => !target.isAddon },
   { id: "command-button-responsive" },
   { id: "command-button-paintflashing" },
   { id: "command-button-tilt" },
   { id: "command-button-scratchpad" },
   { id: "command-button-eyedropper" },
@@ -1244,22 +1245,25 @@ Toolbox.prototype = {
     let toolDef = gDevTools.getToolDefinition(this.currentToolId);
     if (toolDef) {
       toolName = toolDef.label;
     } else {
       // no tool is selected
       toolName = toolboxStrings("toolbox.defaultTitle");
     }
     let title = toolboxStrings("toolbox.titleTemplate",
-                               toolName, this.target.url || this.target.name);
+                               toolName,
+                               this.target.isAddon ?
+                               this.target.name :
+                               this.target.url || this.target.name);
     this._host.setTitle(title);
   },
 
   _listFrames: function (event) {
-    if (!this._target.form || !this._target.form.actor) {
+    if (!this._target.activeTab || !this._target.activeTab.traits.frames) {
       // We are not targetting a regular TabActor
       // it can be either an addon or browser toolbox actor
       return promise.resolve();
     }
     let packet = {
       to: this._target.form.actor,
       type: "listFrames"
     };
--- a/browser/devtools/main.js
+++ b/browser/devtools/main.js
@@ -112,17 +112,17 @@ Tools.inspector = {
   ],
 
   preventClosingOnKey: true,
   onkey: function(panel) {
     panel.toolbox.highlighterUtils.togglePicker();
   },
 
   isTargetSupported: function(target) {
-    return !target.isAddon && target.hasActor("inspector");
+    return target.hasActor("inspector");
   },
 
   build: function(iframeWindow, toolbox) {
     return new InspectorPanel(iframeWindow, toolbox);
   }
 };
 
 Tools.webConsole = {
@@ -194,18 +194,17 @@ Tools.styleEditor = {
   url: "chrome://browser/content/devtools/styleeditor.xul",
   label: l10n("ToolboxStyleEditor.label", styleEditorStrings),
   panelLabel: l10n("ToolboxStyleEditor.panelLabel", styleEditorStrings),
   tooltip: l10n("ToolboxStyleEditor.tooltip2", styleEditorStrings),
   inMenu: true,
   commands: "devtools/styleeditor/styleeditor-commands",
 
   isTargetSupported: function(target) {
-    return !target.isAddon &&
-      (target.hasActor("styleEditor") || target.hasActor("styleSheets"));
+    return target.hasActor("styleEditor") || target.hasActor("styleSheets");
   },
 
   build: function(iframeWindow, toolbox) {
     return new StyleEditorPanel(iframeWindow, toolbox);
   }
 };
 
 Tools.shaderEditor = {
@@ -215,17 +214,17 @@ Tools.shaderEditor = {
   icon: "chrome://browser/skin/devtools/tool-styleeditor.svg",
   invertIconForLightTheme: true,
   url: "chrome://browser/content/devtools/shadereditor.xul",
   label: l10n("ToolboxShaderEditor.label", shaderEditorStrings),
   panelLabel: l10n("ToolboxShaderEditor.panelLabel", shaderEditorStrings),
   tooltip: l10n("ToolboxShaderEditor.tooltip", shaderEditorStrings),
 
   isTargetSupported: function(target) {
-    return !target.isAddon;
+    return target.hasActor("webgl");
   },
 
   build: function(iframeWindow, toolbox) {
     return new ShaderEditorPanel(iframeWindow, toolbox);
   }
 };
 
 Tools.canvasDebugger = {
@@ -237,17 +236,17 @@ Tools.canvasDebugger = {
   url: "chrome://browser/content/devtools/canvasdebugger.xul",
   label: l10n("ToolboxCanvasDebugger.label", canvasDebuggerStrings),
   panelLabel: l10n("ToolboxCanvasDebugger.panelLabel", canvasDebuggerStrings),
   tooltip: l10n("ToolboxCanvasDebugger.tooltip", canvasDebuggerStrings),
 
   // Hide the Canvas Debugger in the Add-on Debugger and Browser Toolbox
   // (bug 1047520).
   isTargetSupported: function(target) {
-    return !target.isAddon && !target.chrome;
+    return target.hasActor("canvas") && !target.chrome;
   },
 
   build: function (iframeWindow, toolbox) {
     return new CanvasDebuggerPanel(iframeWindow, toolbox);
   }
 };
 
 Tools.jsprofiler = {
@@ -263,17 +262,17 @@ Tools.jsprofiler = {
   label: l10n("profiler.label2", profilerStrings),
   panelLabel: l10n("profiler.panelLabel2", profilerStrings),
   tooltip: l10n("profiler.tooltip2", profilerStrings),
   inMenu: true,
 
   isTargetSupported: function (target) {
     // Hide the profiler when debugging devices pre bug 1046394,
     // that don't expose profiler actor in content processes.
-    return !target.isAddon && target.hasActor("profiler");
+    return target.hasActor("profiler");
   },
 
   build: function (frame, target) {
     return new ProfilerPanel(frame, target);
   }
 };
 
 Tools.performance = {
@@ -287,17 +286,17 @@ Tools.performance = {
   panelLabel: "Performance++", //l10n("profiler.panelLabel2", profilerStrings),
   tooltip: l10n("profiler.tooltip2", profilerStrings),
   accesskey: l10n("profiler.accesskey", profilerStrings),
   key: l10n("profiler.commandkey2", profilerStrings),
   modifiers: "shift",
   inMenu: true,
 
   isTargetSupported: function (target) {
-    return !target.isAddon && target.hasActor("profiler");
+    return target.hasActor("profiler");
   },
 
   build: function (frame, target) {
     return new PerformancePanel(frame, target);
   }
 };
 
 Tools.timeline = {
@@ -307,17 +306,17 @@ Tools.timeline = {
   icon: "chrome://browser/skin/devtools/tool-network.svg",
   invertIconForLightTheme: true,
   url: "chrome://browser/content/devtools/timeline/timeline.xul",
   label: l10n("timeline.label", timelineStrings),
   panelLabel: l10n("timeline.panelLabel", timelineStrings),
   tooltip: l10n("timeline.tooltip", timelineStrings),
 
   isTargetSupported: function(target) {
-    return !target.isAddon && target.hasActor("timeline");
+    return target.hasActor("timeline");
   },
 
   build: function (iframeWindow, toolbox) {
     let panel = new TimelinePanel(iframeWindow, toolbox);
     return panel.open();
   }
 };
 
@@ -332,17 +331,17 @@ Tools.netMonitor = {
   invertIconForLightTheme: true,
   url: "chrome://browser/content/devtools/netmonitor.xul",
   label: l10n("netmonitor.label", netMonitorStrings),
   panelLabel: l10n("netmonitor.panelLabel", netMonitorStrings),
   tooltip: l10n("netmonitor.tooltip", netMonitorStrings),
   inMenu: true,
 
   isTargetSupported: function(target) {
-    return !target.isAddon && target.getTrait("networkMonitor");
+    return target.getTrait("networkMonitor");
   },
 
   build: function(iframeWindow, toolbox) {
     return new NetMonitorPanel(iframeWindow, toolbox);
   }
 };
 
 Tools.storage = {
@@ -358,17 +357,18 @@ Tools.storage = {
   label: l10n("storage.label", storageStrings),
   menuLabel: l10n("storage.menuLabel", storageStrings),
   panelLabel: l10n("storage.panelLabel", storageStrings),
   tooltip: l10n("storage.tooltip2", storageStrings),
   inMenu: true,
 
   isTargetSupported: function(target) {
     return target.isLocalTab ||
-           (target.client.traits.storageInspector && !target.isAddon);
+           ( target.hasActor("storage") &&
+             target.getTrait("storageInspector") );
   },
 
   build: function(iframeWindow, toolbox) {
     return new StoragePanel(iframeWindow, toolbox);
   }
 };
 
 Tools.webAudioEditor = {
@@ -378,17 +378,17 @@ Tools.webAudioEditor = {
   icon: "chrome://browser/skin/devtools/tool-webaudio.svg",
   invertIconForLightTheme: true,
   url: "chrome://browser/content/devtools/webaudioeditor.xul",
   label: l10n("ToolboxWebAudioEditor1.label", webAudioEditorStrings),
   panelLabel: l10n("ToolboxWebAudioEditor1.panelLabel", webAudioEditorStrings),
   tooltip: l10n("ToolboxWebAudioEditor1.tooltip", webAudioEditorStrings),
 
   isTargetSupported: function(target) {
-    return !target.isAddon && !target.chrome && target.hasActor("webaudio");
+    return !target.chrome && target.hasActor("webaudio");
   },
 
   build: function(iframeWindow, toolbox) {
     return new WebAudioEditorPanel(iframeWindow, toolbox);
   }
 };
 
 Tools.scratchpad = {
--- a/browser/installer/Makefile.in
+++ b/browser/installer/Makefile.in
@@ -125,19 +125,16 @@ DEFINES += -DAB=$(AB)
 
 DEFINES += -DMOZ_ICU_VERSION=$(MOZ_ICU_VERSION)
 ifdef MOZ_NATIVE_ICU
 DEFINES += -DMOZ_NATIVE_ICU
 endif
 ifdef MOZ_SHARED_ICU
 DEFINES += -DMOZ_SHARED_ICU
 endif
-ifdef MOZ_REPLACE_MALLOC
-DEFINES += -DMOZ_REPLACE_MALLOC
-endif
 ifdef MOZ_JEMALLOC3
 DEFINES += -DMOZ_JEMALLOC3
 endif
 DEFINES += -DMOZ_ICU_DBG_SUFFIX=$(MOZ_ICU_DBG_SUFFIX)
 ifdef CLANG_CXX
 DEFINES += -DCLANG_CXX
 endif
 ifdef CLANG_CL
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -172,17 +172,16 @@
 @BINPATH@/AccessibleMarshal.dll
 #endif
 @BINPATH@/components/accessibility.xpt
 #endif
 @BINPATH@/components/appshell.xpt
 @BINPATH@/components/appstartup.xpt
 @BINPATH@/components/autocomplete.xpt
 @BINPATH@/components/autoconfig.xpt
-@BINPATH@/components/browser-element.xpt
 @BINPATH@/browser/components/browsercompsbase.xpt
 @BINPATH@/browser/components/browser-feeds.xpt
 @BINPATH@/components/caps.xpt
 @BINPATH@/components/chrome.xpt
 @BINPATH@/components/commandhandler.xpt
 @BINPATH@/components/commandlines.xpt
 @BINPATH@/components/composer.xpt
 @BINPATH@/components/content_events.xpt
--- a/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/privacy.dtd
@@ -12,33 +12,33 @@
 <!ENTITY doNotTrackInfo.label           "Learn More">
 
 <!ENTITY  history.label                 "History">
 
 <!ENTITY  locationBar.label             "Location Bar">
 
 <!ENTITY  locbar.suggest.label          "When using the location bar, suggest:">
 <!ENTITY  locbar.history.label          "History">
-<!ENTITY  locbar.history.accesskey      "i">
+<!ENTITY  locbar.history.accesskey      "H">
 <!ENTITY  locbar.bookmarks.label        "Bookmarks">
-<!ENTITY  locbar.bookmarks.accesskey    "d">
+<!ENTITY  locbar.bookmarks.accesskey    "k">
 <!ENTITY  locbar.openpage.label         "Open tabs">
-<!ENTITY  locbar.openpage.accesskey     "g">
+<!ENTITY  locbar.openpage.accesskey     "O">
 
 <!ENTITY  acceptCookies.label           "Accept cookies from sites">
 <!ENTITY  acceptCookies.accesskey       "A">
 
 <!ENTITY  acceptThirdParty.pre.label      "Accept third-party cookies:">
 <!ENTITY  acceptThirdParty.pre.accesskey  "c">
 <!ENTITY  acceptThirdParty.always.label   "Always">
 <!ENTITY  acceptThirdParty.never.label    "Never">
 <!ENTITY  acceptThirdParty.visited.label  "From visited">
 
 <!ENTITY  keepUntil.label               "Keep until:">
-<!ENTITY  keepUntil.accesskey           "K">
+<!ENTITY  keepUntil.accesskey           "u">
 
 <!ENTITY  expire.label                  "they expire">
 <!ENTITY  close.label                   "I close &brandShortName;">
 <!ENTITY  askEachTime.label             "ask me every time">
 
 <!ENTITY  cookieExceptions.label        "Exceptions…">
 <!ENTITY  cookieExceptions.accesskey    "E">
 
--- a/browser/themes/shared/devtools/dark-theme.css
+++ b/browser/themes/shared/devtools/dark-theme.css
@@ -98,17 +98,17 @@
 }
 
 .CodeMirror-Tern-completion-object:before {
   background-color: #3689b2;
 }
 
 .cm-s-mozilla .cm-unused-line {
   text-decoration: line-through;
-  -moz-text-decoration-color: #5f88b0;
+  text-decoration-color: #5f88b0;
 }
 
 .cm-s-mozilla .cm-executed-line {
   background-color: #133c26;
 }
 
 .theme-fg-color3,
 .cm-s-mozilla .cm-builtin,
--- a/browser/themes/shared/devtools/light-theme.css
+++ b/browser/themes/shared/devtools/light-theme.css
@@ -71,17 +71,17 @@
 }
 
 .theme-separator { /* grey */
   border-color: #cddae5;
 }
 
 .cm-s-mozilla .cm-unused-line {
   text-decoration: line-through;
-  -moz-text-decoration-color: #5f88b0;
+  text-decoration-color: #5f88b0;
 }
 
 .cm-s-mozilla .cm-executed-line {
   background-color: #fcfffc;
 }
 
 .theme-fg-color1,
 .cm-s-mozilla .cm-number,
--- a/browser/themes/shared/devtools/ruleview.css
+++ b/browser/themes/shared/devtools/ruleview.css
@@ -193,17 +193,17 @@
   }
 }
 
 .ruleview-overridden {
   text-decoration: line-through;
 }
 
 .theme-light .ruleview-overridden {
-  -moz-text-decoration-color: #667380; /*  Content (Text) - Dark Grey */
+  text-decoration-color: #667380; /*  Content (Text) - Dark Grey */
 }
 
 .styleinspector-propertyeditor {
   border: 1px solid #CCC;
   padding: 0;
 }
 
 .ruleview-property {
--- a/browser/themes/shared/devtools/toolbars.inc.css
+++ b/browser/themes/shared/devtools/toolbars.inc.css
@@ -766,18 +766,18 @@
 .theme-light .devtools-tabbar .devtools-tab[selected] {
   color: #f5f7fa;
   background-color: #4c9ed9;
   box-shadow: 0 2px 0 #d7f1ff inset,
               0 8px 3px -5px #2b82bf inset,
               0 -2px 0 rgba(0,0,0,.06) inset;
 }
 
-#toolbox-tabs .devtools-tab[selected]:not(:first-child),
-#toolbox-tabs .devtools-tab[highlighted]:not(:first-child) {
+#toolbox-tabs .devtools-tab[selected],
+#toolbox-tabs .devtools-tab[highlighted] {
   border-width: 0;
   -moz-padding-start: 1px;
 }
 
 #toolbox-tabs .devtools-tab[selected]:last-child,
 #toolbox-tabs .devtools-tab[highlighted]:last-child {
   -moz-padding-end: 1px;
 }
--- a/configure.in
+++ b/configure.in
@@ -7156,16 +7156,17 @@ if test "$NS_TRACE_MALLOC" -a "$MOZ_REPL
     AC_MSG_ERROR([--enable-trace-malloc and --enable-replace-malloc are conflicting options])
 fi
 
 if test -n "$MOZ_REPLACE_MALLOC" -a -z "$MOZ_MEMORY"; then
     dnl We don't want to enable jemalloc unconditionally because it may be a
     dnl deliberate choice not to enable it (bug 702250, for instance)
     AC_MSG_ERROR([--enable-replace-malloc requires --enable-jemalloc])
 elif test -n "$MOZ_REPLACE_MALLOC"; then
+    AC_DEFINE(MOZ_REPLACE_MALLOC)
     MOZ_NATIVE_JEMALLOC=
 
     dnl Replace-malloc Mac linkage quirks
     if test -n "$MACOSX_DEPLOYMENT_TARGET"; then
         AC_CACHE_CHECK([how to do weak dynamic linking],
             ac_cv_weak_dynamic_linking,
             [echo 'extern void foo() __attribute__((weak_import));int bar() { if (foo) foo(); return 0; }' > conftest.c
              if AC_TRY_COMMAND([${CC-cc} -o conftest${DLL_SUFFIX} $CFLAGS -dynamiclib $LDFLAGS -Wl,-U,_foo conftest.c $LIBS 1>&5]) &&
@@ -7378,27 +7379,17 @@ MOZ_ARG_ENABLE_BOOL(install-strip,
 dnl ========================================================
 dnl = --disable-elf-hack
 dnl ========================================================
 
 USE_ELF_HACK=1
 MOZ_ARG_DISABLE_BOOL(elf-hack,
 [  --disable-elf-hack      Disable elf hacks],
     [USE_ELF_HACK=],
-    [USE_ELF_HACK=F # Force enable elf-hack])
-
-# Disable elf hack for profiling because the built in profiler
-# doesn't read the segments properly with elf hack. This is
-# temporary and should be fixed soon in the profiler.
-if test "$MOZ_PROFILING" = 1; then
-  if test "$USE_ELF_HACK" = F; then
-    AC_ERROR([--enable-elf-hack is not compatible with --enable-profiling])
-  fi
-  USE_ELF_HACK=
-fi
+    [USE_ELF_HACK=1])
 
 # Only enable elfhack where supported
 if test "$USE_ELF_HACK" = 1; then
     case "${HOST_OS_ARCH},${OS_ARCH}" in
     Linux,Linux)
         case "${CPU_ARCH}" in
         arm | x86 | x86_64)
             USE_ELF_HACK=1
--- a/docshell/test/browser/browser_timelineMarkers-04.js
+++ b/docshell/test/browser/browser_timelineMarkers-04.js
@@ -38,23 +38,23 @@ let test = Task.async(function*() {
   for (let {desc, setup, check} of TESTS) {
 
     info("Running test: " + desc);
 
     info("Flushing the previous markers if any");
     docShell.popProfileTimelineMarkers();
 
     info("Running the test setup function");
-    let onMarkers = waitForMarkers(docShell);
+    let onMarkers = waitForDOMMarkers(docShell, 5);
     setup();
     info("Waiting for new markers on the docShell");
     let markers = yield onMarkers;
 
     info("Running the test check function");
-    check(markers.filter(m => m.name == "DOMEvent"));
+    check(markers);
   }
 
   info("Stop recording");
   docShell.recordProfileTimelineMarkers = false;
 
   gBrowser.removeCurrentTab();
   finish();
 });
@@ -68,27 +68,26 @@ function openUrl(url) {
 
     linkedBrowser.addEventListener("load", function onload() {
       linkedBrowser.removeEventListener("load", onload, true);
       resolve(tab);
     }, true);
   });
 }
 
-function waitForMarkers(docshell) {
+function waitForDOMMarkers(docshell, numExpected) {
   return new Promise(function(resolve, reject) {
     let waitIterationCount = 0;
     let maxWaitIterationCount = 10; // Wait for 2sec maximum
+    let markers = [];
 
     let interval = setInterval(() => {
-      let markers = docshell.popProfileTimelineMarkers();
-      if (markers.length > 0) {
+      let newMarkers = docshell.popProfileTimelineMarkers();
+      markers = [...markers, ...newMarkers.filter(m => m.name == "DOMEvent")];
+      if (markers.length >= numExpected
+          || waitIterationCount > maxWaitIterationCount) {
         clearInterval(interval);
         resolve(markers);
       }
-      if (waitIterationCount > maxWaitIterationCount) {
-        clearInterval(interval);
-        resolve([]);
-      }
       waitIterationCount++;
     }, 200);
   });
 }
--- a/docshell/test/browser/timelineMarkers-04.html
+++ b/docshell/test/browser/timelineMarkers-04.html
@@ -9,17 +9,17 @@
   <p>Test page</p>
 
   <script>
     function do_xhr() {
       const theURL = "timelineMarkers-04.html";
 
       xhr = new XMLHttpRequest();
       xhr.onreadystatechange = function() {
-        // Nothing.
+        dump("ReadyState = " + xhr.readyState + "\n");
       };
       xhr.open("get", theURL, true);
       xhr.send();
     }
 
     window.addEventListener("dog", do_xhr, true);
   </script>
 
deleted file mode 100644
--- a/dom/apps/tests/file_test_widget.js
+++ /dev/null
@@ -1,231 +0,0 @@
-var gWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=widget&getmanifest=true';
-var gInvalidWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=invalidWidget&getmanifest=true';
-var gApp;
-var gHasBrowserPermission;
-
-function onError() {
-  ok(false, "Error callback invoked");
-  finish();
-}
-
-function installApp(path) {
-  var request = navigator.mozApps.install(path);
-  request.onerror = onError;
-  request.onsuccess = function() {
-    gApp = request.result;
-
-    runTest();
-  }
-}
-
-function uninstallApp() {
-  // Uninstall the app.
-  var request = navigator.mozApps.mgmt.uninstall(gApp);
-  request.onerror = onError;
-  request.onsuccess = function() {
-    // All done.
-    info("All done");
-
-    runTest();
-  }
-}
-
-function testApp(isValidWidget) {
-  info("Test widget feature. IsValidWidget: " + isValidWidget);
-
-  var ifr = document.createElement('iframe');
-  ifr.setAttribute('mozbrowser', 'true');
-  ifr.setAttribute('mozwidget', gApp.manifestURL);
-  ifr.setAttribute('src', gApp.origin+gApp.manifest.launch_path);
-
-  var domParent = document.getElementById('container');
-  domParent.appendChild(ifr);
-
-  var mm = SpecialPowers.getBrowserFrameMessageManager(ifr);
-  mm.addMessageListener('OK', function(msg) {
-    ok(isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
-  });
-  mm.addMessageListener('KO', function(msg) {
-    ok(!isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
-  });
-  mm.addMessageListener('DONE', function(msg) {
-    ok(true, "Message from widget complete: "+SpecialPowers.wrap(msg).json);
-    domParent.removeChild(ifr);
-    runTest();
-  });
-
-  ifr.addEventListener('mozbrowserloadend', function() {
-    ok(true, "receive mozbrowserloadend");
-
-    // Test limited browser API feature only for valid widget case
-    if (isValidWidget) {
-      testLimitedBrowserAPI(ifr);
-    }
-    SimpleTest.executeSoon(()=>loadFrameScript(mm));
-  }, false);
-
-  // Test limited browser API feature only for valid widget case
-  if (!isValidWidget) {
-    return;
-  }
-
-  [
-    'mozbrowsertitlechange',
-    'mozbrowseropenwindow',
-    'mozbrowserscroll',
-    'mozbrowserasyncscroll'
-  ].forEach( function(topic) {
-    ifr.addEventListener(topic, function() {
-      ok(false, topic + " should be hidden");
-    }, false);
-  });
-}
-
-function testLimitedBrowserAPI(ifr) {
-  var securitySensitiveCalls = [
-    { api: 'sendMouseEvent'      , args: ['mousedown', 0, 0, 0, 0, 0] },
-    { api: 'sendTouchEvent'      , args: ['touchstart', [0], [0], [0], [1], [1], [0], [1], 1, 0] },
-    { api: 'goBack'              , args: [] },
-    { api: 'goForward'           , args: [] },
-    { api: 'reload'              , args: [] },
-    { api: 'stop'                , args: [] },
-    { api: 'download'            , args: ['http://example.org'] },
-    { api: 'purgeHistory'        , args: [] },
-    { api: 'getScreenshot'       , args: [0, 0] },
-    { api: 'zoom'                , args: [0.1] },
-    { api: 'getCanGoBack'        , args: [] },
-    { api: 'getCanGoForward'     , args: [] },
-    { api: 'getContentDimensions', args: [] }
-  ];
-  securitySensitiveCalls.forEach( function(call) {
-    if (gHasBrowserPermission) {
-      isnot(typeof ifr[call.api], "undefined", call.api + " should be defined");
-      var didThrow;
-      try {
-        ifr[call.api].apply(ifr, call.args);
-      } catch (e) {
-        ok(e instanceof DOMException, "throw right exception type");
-        didThrow = e.code;
-      }
-      is(didThrow, DOMException.INVALID_NODE_TYPE_ERR, "call " + call.api + " should throw exception");
-    } else {
-      is(typeof ifr[call.api], "undefined", call.api + " should be hidden for widget");
-    }
-  });
-}
-
-function loadFrameScript(mm) {
-  var script = 'data:,\
-  function ok(p, msg) { \
-  if (p) { \
-  sendAsyncMessage("OK", msg); \
-} else { \
-  sendAsyncMessage("KO", msg); \
-} \
-} \
-  \
-  function is(a, b, msg) { \
-  if (a == b) { \
-  sendAsyncMessage("OK", a + " == " + b + " - " + msg); \
-} else { \
-  sendAsyncMessage("KO", a + " != " + b + " - " + msg); \
-} \
-} \
-  \
-  function finish() { \
-  sendAsyncMessage("DONE",""); \
-} \
-  \
-  function onError() { \
-  ok(false, "Error callback invoked"); \
-  finish(); \
-} \
-  \
-  function checkWidget(widget) { \
-  /*For invalid widget case, ignore the following check*/\
-  if (widget) { \
-  var widgetName = "Really Rapid Release (APPTYPETOKEN)"; \
-  is(widget.origin, "http://test", "Widget origin should be correct"); \
-  is(widget.installOrigin, "http://mochi.test:8888", "Install origin should be correct"); \
-} \
-  finish(); \
-} \
-  \
-  var request = content.window.navigator.mozApps.getSelf(); \
-  request.onsuccess = function() { \
-  var widget = request.result; \
-  ok(widget,"Should be a widget"); \
-  checkWidget(widget); \
-}; \
-  request.onerror = onError; \
-  content.window.open("about:blank"); /*test mozbrowseropenwindow*/ \
-  content.window.scrollTo(4000, 4000); /*test mozbrowser(async)scroll*/ \
-  ';
-  mm.loadFrameScript(script, /* allowDelayedLoad = */ false);
-}
-
-var tests = [
-  // Permissions
-  function() {
-    SpecialPowers.pushPermissions(
-      [{ "type": "browser", "allow": gHasBrowserPermission ? 1 : 0, "context": document },
-       { "type": "embed-widgets", "allow": 1, "context": document },
-       { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
-  },
-
-  // Preferences
-  function() {
-    SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true],
-                                       ["dom.enable_widgets", true],
-                                       ["dom.datastore.sysMsgOnChangeShortTimeoutSec", 1],
-                                       ["dom.datastore.sysMsgOnChangeLongTimeoutSec", 3]]}, runTest);
-  },
-
-  function() {
-    if (SpecialPowers.isMainProcess()) {
-      SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
-    }
-
-    SpecialPowers.setAllAppsLaunchable(true);
-    runTest();
-  },
-
-  // No confirmation needed when an app is installed
-  function() {
-    SpecialPowers.autoConfirmAppInstall(() => {
-      SpecialPowers.autoConfirmAppUninstall(runTest);
-    });
-  },
-
-  // Installing the app
-  ()=>installApp(gWidgetManifestURL),
-
-  // Run tests in app
-  ()=>testApp(true),
-
-  // Uninstall the app
-  uninstallApp,
-
-  // Installing the app for invalid widget case
-  ()=>installApp(gInvalidWidgetManifestURL),
-
-  // Run tests in app for invalid widget case
-  ()=>testApp(false),
-
-  // Uninstall the app
-  uninstallApp
-];
-
-function runTest() {
-  if (!tests.length) {
-    finish();
-    return;
-  }
-
-  var test = tests.shift();
-  test();
-}
-
-function finish() {
-  SimpleTest.finish();
-}
--- a/dom/apps/tests/mochitest.ini
+++ b/dom/apps/tests/mochitest.ini
@@ -12,17 +12,16 @@ support-files =
   file_manifest.json^headers^
   file_trusted_app.template.webapp
   file_invalidWidget_app.template.webapp
   file_packaged_app.sjs
   file_packaged_app.template.html
   file_packaged_app.template.webapp
   file_widget_app.template.webapp
   file_widget_app.template.html
-  file_test_widget.js
   signed_app.sjs
   signed_app_template.webapp
   signed/*
   test_packaged_app_common.js
   marketplace/*
   pkg_install_iframe.html
 
 [test_app_enabled.html]
@@ -40,10 +39,8 @@ skip-if = (toolkit == 'android' && proce
 skip-if = (toolkit == 'android' && processor == 'x86') #x86 only
 [test_receipt_operations.html]
 [test_signed_pkg_install.html]
 [test_uninstall_errors.html]
 [test_theme_role.html]
 [test_web_app_install.html]
 [test_widget.html]
 skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in mochitest app
-[test_widget_browser.html]
-skip-if = os == "android" || toolkit == "gonk" # embed-apps doesn't work in mochitest app
--- a/dom/apps/tests/test_widget.html
+++ b/dom/apps/tests/test_widget.html
@@ -1,18 +1,235 @@
 <!DOCTYPE HTML>
 <html>
 <head>
   <meta charset="utf-8">
   <title>Test for DataStore - basic operation on a readonly db</title>
   <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript" src="file_test_widget.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
 </head>
 <body>
 <div id="container"></div>
   <script type="application/javascript;version=1.7">
+
+  var gWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=widget&getmanifest=true';
+  var gInvalidWidgetManifestURL = 'http://test/tests/dom/apps/tests/file_app.sjs?apptype=invalidWidget&getmanifest=true';
+  var gApp;
+
+  function onError() {
+    ok(false, "Error callback invoked");
+    finish();
+  }
+
+  function installApp(path) {
+    var request = navigator.mozApps.install(path);
+    request.onerror = onError;
+    request.onsuccess = function() {
+      gApp = request.result;
+
+      runTest();
+    }
+  }
+
+  function uninstallApp() {
+    // Uninstall the app.
+    var request = navigator.mozApps.mgmt.uninstall(gApp);
+    request.onerror = onError;
+    request.onsuccess = function() {
+      // All done.
+      info("All done");
+
+      runTest();
+    }
+  }
+
+  function testApp(isValidWidget) {
+    info("Test widget feature. IsValidWidget: " + isValidWidget);
+
+    var ifr = document.createElement('iframe');
+    ifr.setAttribute('mozbrowser', 'true');
+    ifr.setAttribute('mozwidget', gApp.manifestURL);
+    ifr.setAttribute('src', gApp.origin+gApp.manifest.launch_path);
+
+    var domParent = document.getElementById('container');
+    domParent.appendChild(ifr);
+
+    var mm = SpecialPowers.getBrowserFrameMessageManager(ifr);
+    mm.addMessageListener('OK', function(msg) {
+      ok(isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
+    });
+    mm.addMessageListener('KO', function(msg) {
+      ok(!isValidWidget, "Message from widget: " + SpecialPowers.wrap(msg).json);
+    });
+    mm.addMessageListener('DONE', function(msg) {
+      ok(true, "Message from widget complete: "+SpecialPowers.wrap(msg).json);
+      domParent.removeChild(ifr);
+      runTest();
+    });
+
+    ifr.addEventListener('mozbrowserloadend', function() {
+      ok(true, "receive mozbrowserloadend");
+
+      // Test limited browser API feature only for valid widget case
+      if (isValidWidget) {
+        testLimitedBrowserAPI(ifr);
+      }
+      SimpleTest.executeSoon(()=>loadFrameScript(mm));
+    }, false);
+
+    // Test limited browser API feature only for valid widget case
+    if (!isValidWidget) {
+      return;
+    }
+
+    [
+      'mozbrowsertitlechange',
+      'mozbrowseropenwindow',
+      'mozbrowserscroll',
+      'mozbrowserasyncscroll'
+    ].forEach( function(topic) {
+      ifr.addEventListener(topic, function() {
+        ok(false, topic + " should be hidden");
+      }, false);
+    });
+  }
+
+  function testLimitedBrowserAPI(ifr) {
+    var securitySensitiveCalls = [
+      'sendMouseEvent',
+      'sendTouchEvent',
+      'goBack',
+      'goForward',
+      'reload',
+      'stop',
+      'download',
+      'purgeHistory',
+      'getScreenshot',
+      'zoom',
+      'getCanGoBack',
+      'getCanGoForward'
+    ];
+    securitySensitiveCalls.forEach( function(call) {
+      is(typeof ifr[call], "undefined", call + " should be hidden for widget");
+    });
+  }
+
+  function loadFrameScript(mm) {
+    var script = 'data:,\
+      function ok(p, msg) { \
+        if (p) { \
+          sendAsyncMessage("OK", msg); \
+        } else { \
+          sendAsyncMessage("KO", msg); \
+        } \
+      } \
+      \
+      function is(a, b, msg) { \
+        if (a == b) { \
+          sendAsyncMessage("OK", a + " == " + b + " - " + msg); \
+        } else { \
+          sendAsyncMessage("KO", a + " != " + b + " - " + msg); \
+        } \
+      } \
+      \
+      function finish() { \
+          sendAsyncMessage("DONE",""); \
+      } \
+      \
+      function onError() { \
+        ok(false, "Error callback invoked"); \
+        finish(); \
+      } \
+      \
+      function checkWidget(widget) { \
+        /*For invalid widget case, ignore the following check*/\
+        if (widget) { \
+          var widgetName = "Really Rapid Release (APPTYPETOKEN)"; \
+          is(widget.origin, "http://test", "Widget origin should be correct"); \
+          is(widget.installOrigin, "http://mochi.test:8888", "Install origin should be correct"); \
+        } \
+        finish(); \
+      } \
+      \
+      var request = content.window.navigator.mozApps.getSelf(); \
+      request.onsuccess = function() { \
+        var widget = request.result; \
+        ok(widget,"Should be a widget"); \
+        checkWidget(widget); \
+      }; \
+      request.onerror = onError; \
+      content.window.open("about:blank"); /*test mozbrowseropenwindow*/ \
+      content.window.scrollTo(4000, 4000); /*test mozbrowser(async)scroll*/ \
+      ';
+    mm.loadFrameScript(script, /* allowDelayedLoad = */ false);
+  }
+
+  var tests = [
+    // Permissions
+    function() {
+      SpecialPowers.pushPermissions(
+        [{ "type": "browser", "allow": 1, "context": document },
+         { "type": "embed-widgets", "allow": 1, "context": document },
+         { "type": "webapps-manage", "allow": 1, "context": document }], runTest);
+    },
+
+    // Preferences
+    function() {
+      SpecialPowers.pushPrefEnv({"set": [["dom.mozBrowserFramesEnabled", true],
+                                         ["dom.enable_widgets", true],
+                                         ["dom.datastore.sysMsgOnChangeShortTimeoutSec", 1],
+                                         ["dom.datastore.sysMsgOnChangeLongTimeoutSec", 3]]}, runTest);
+    },
+
+    function() {
+      if (SpecialPowers.isMainProcess()) {
+        SpecialPowers.Cu.import("resource://gre/modules/DataStoreChangeNotifier.jsm");
+      }
+
+      SpecialPowers.setAllAppsLaunchable(true);
+      runTest();
+    },
+
+    // No confirmation needed when an app is installed
+    function() {
+      SpecialPowers.autoConfirmAppInstall(() => {
+        SpecialPowers.autoConfirmAppUninstall(runTest);
+      });
+    },
+
+    // Installing the app
+    ()=>installApp(gWidgetManifestURL),
+
+    // Run tests in app
+    ()=>testApp(true),
+
+    // Uninstall the app
+    uninstallApp,
+
+    // Installing the app for invalid widget case
+    ()=>installApp(gInvalidWidgetManifestURL),
+
+    // Run tests in app for invalid widget case
+    ()=>testApp(false),
+
+    // Uninstall the app
+    uninstallApp
+  ];
+
+  function runTest() {
+    if (!tests.length) {
+      finish();
+      return;
+    }
+
+    var test = tests.shift();
+    test();
+  }
+
+  function finish() {
+    SimpleTest.finish();
+  }
+
   SimpleTest.waitForExplicitFinish();
-  gHasBrowserPermission = false;
   runTest();
   </script>
 </body>
 </html>
deleted file mode 100644
--- a/dom/apps/tests/test_widget_browser.html
+++ /dev/null
@@ -1,18 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <meta charset="utf-8">
-  <title>Test for DataStore - basic operation on a readonly db</title>
-  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript" src="file_test_widget.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
-</head>
-<body>
-<div id="container"></div>
-  <script type="application/javascript;version=1.7">
-  SimpleTest.waitForExplicitFinish();
-  gHasBrowserPermission = true;
-  runTest();
-  </script>
-</body>
-</html>
--- a/dom/base/EventSource.cpp
+++ b/dom/base/EventSource.cpp
@@ -57,17 +57,16 @@ namespace dom {
 EventSource::EventSource(nsPIDOMWindow* aOwnerWindow) :
   DOMEventTargetHelper(aOwnerWindow),
   mStatus(PARSE_STATE_OFF),
   mFrozen(false),
   mErrorLoadOnRedirect(false),
   mGoingToDispatchAllMessages(false),
   mWithCredentials(false),
   mWaitingForOnStopRequest(false),
-  mInterrupted(false),
   mLastConvertionResult(NS_OK),
   mReadyState(CONNECTING),
   mScriptLine(0),
   mInnerWindowID(0)
 {
 }
 
 EventSource::~EventSource()
@@ -336,39 +335,40 @@ EventSource::OnStartRequest(nsIRequest *
                             nsISupports *ctxt)
 {
   nsresult rv = CheckHealthOfRequestCallback(aRequest);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIHttpChannel> httpChannel = do_QueryInterface(aRequest, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  bool requestSucceeded;
-  rv = httpChannel->GetRequestSucceeded(&requestSucceeded);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsAutoCString contentType;
-  rv = httpChannel->GetContentType(contentType);
+  nsresult status;
+  rv = aRequest->GetStatus(&status);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  nsresult status;
-  aRequest->GetStatus(&status);
-
-  if (NS_FAILED(status) || !requestSucceeded ||
-      !contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
-    DispatchFailConnection();
-    return NS_ERROR_NOT_AVAILABLE;
+  if (NS_FAILED(status)) {
+    // EventSource::OnStopRequest will evaluate if it shall either reestablish
+    // or fail the connection
+    return NS_ERROR_ABORT;
   }
 
   uint32_t httpStatus;
   rv = httpChannel->GetResponseStatus(&httpStatus);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (httpStatus != 200) {
-    mInterrupted = true;
+    DispatchFailConnection();
+    return NS_ERROR_ABORT;
+  }
+
+  nsAutoCString contentType;
+  rv = httpChannel->GetContentType(contentType);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  if (!contentType.EqualsLiteral(TEXT_EVENT_STREAM)) {
     DispatchFailConnection();
     return NS_ERROR_ABORT;
   }
 
   nsCOMPtr<nsIRunnable> event =
     NS_NewRunnableMethod(this, &EventSource::AnnounceConnection);
   NS_ENSURE_STATE(event);
 
@@ -449,40 +449,48 @@ EventSource::OnStopRequest(nsIRequest *a
                            nsresult aStatusCode)
 {
   mWaitingForOnStopRequest = false;
 
   if (mReadyState == CLOSED) {
     return NS_ERROR_ABORT;
   }
 
-  if (NS_FAILED(aStatusCode)) {
+  // "Network errors that prevents the connection from being established in the
+  //  first place (e.g. DNS errors), must cause the user agent to asynchronously
+  //  reestablish the connection.
+  //
+  //  (...) the cancelation of the fetch algorithm by the user agent (e.g. in
+  //  response to window.stop() or the user canceling the network connection
+  //  manually) must cause the user agent to fail the connection.
+
+  if (NS_FAILED(aStatusCode) &&
+      aStatusCode != NS_ERROR_CONNECTION_REFUSED &&
+      aStatusCode != NS_ERROR_NET_TIMEOUT &&
+      aStatusCode != NS_ERROR_NET_RESET &&
+      aStatusCode != NS_ERROR_NET_INTERRUPT &&
+      aStatusCode != NS_ERROR_PROXY_CONNECTION_REFUSED &&
+      aStatusCode != NS_ERROR_DNS_LOOKUP_QUEUE_FULL) {
     DispatchFailConnection();
-    return aStatusCode;
+    return NS_ERROR_ABORT;
   }
 
-  nsresult rv;
-  nsresult healthOfRequestResult = CheckHealthOfRequestCallback(aRequest);
-  if (NS_SUCCEEDED(healthOfRequestResult) &&
-      mLastConvertionResult == NS_PARTIAL_MORE_INPUT) {
-    // we had an incomplete UTF8 char at the end of the stream
-    rv = ParseCharacter(REPLACEMENT_CHAR);
-    NS_ENSURE_SUCCESS(rv, rv);
-  }
+  nsresult rv = CheckHealthOfRequestCallback(aRequest);
+  NS_ENSURE_SUCCESS(rv, rv);
 
   ClearFields();
 
   nsCOMPtr<nsIRunnable> event =
     NS_NewRunnableMethod(this, &EventSource::ReestablishConnection);
   NS_ENSURE_STATE(event);
 
   rv = NS_DispatchToMainThread(event);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  return healthOfRequestResult;
+  return NS_OK;
 }
 
 /**
  * Simple helper class that just forwards the redirect callback back
  * to the EventSource.
  */
 class AsyncVerifyRedirectCallbackFwr MOZ_FINAL : public nsIAsyncVerifyRedirectCallback
 {
@@ -864,21 +872,16 @@ EventSource::ResetConnection()
 
 void
 EventSource::ReestablishConnection()
 {
   if (mReadyState == CLOSED) {
     return;
   }
 
-  if (mReadyState != OPEN) {
-    NS_WARNING("Unexpected mReadyState!!!");
-    return;
-  }
-
   nsresult rv = ResetConnection();
   if (NS_FAILED(rv)) {
     NS_WARNING("Failed to reset the connection!!!");
     return;
   }
 
   rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
@@ -989,17 +992,17 @@ EventSource::ConsoleError()
 {
   nsAutoCString targetSpec;
   nsresult rv = mSrc->GetSpec(targetSpec);
   NS_ENSURE_SUCCESS(rv, rv);
 
   NS_ConvertUTF8toUTF16 specUTF16(targetSpec);
   const char16_t *formatStrings[] = { specUTF16.get() };
 
-  if (mReadyState == CONNECTING && !mInterrupted) {
+  if (mReadyState == CONNECTING) {
     rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
                              MOZ_UTF16("connectionFailure"),
                              formatStrings, ArrayLength(formatStrings));
   } else {
     rv = PrintErrorOnConsole("chrome://global/locale/appstrings.properties",
                              MOZ_UTF16("netInterrupt"),
                              formatStrings, ArrayLength(formatStrings));
   }
--- a/dom/base/EventSource.h
+++ b/dom/base/EventSource.h
@@ -216,17 +216,16 @@ protected:
   };
   ParserStatus mStatus;
 
   bool mFrozen;
   bool mErrorLoadOnRedirect;
   bool mGoingToDispatchAllMessages;
   bool mWithCredentials;
   bool mWaitingForOnStopRequest;
-  bool mInterrupted;
 
   // used while reading the input streams
   nsCOMPtr<nsIUnicodeDecoder> mUnicodeDecoder;
   nsresult mLastConvertionResult;
   nsString mLastFieldName;
   nsString mLastFieldValue;
 
   nsCOMPtr<nsILoadGroup> mLoadGroup;
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -14089,18 +14089,22 @@ nsGlobalWindow::GetSidebar(OwningExterna
   aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
 #endif
 }
 
 void
 nsGlobalWindow::ClearDocumentDependentSlots(JSContext* aCx)
 {
   MOZ_ASSERT(IsInnerWindow());
-  WindowBinding::ClearCachedDocumentValue(aCx, this);
-  WindowBinding::ClearCachedPerformanceValue(aCx, this);
+
+  // If JSAPI OOMs here, there is basically nothing we can do to recover safely.
+  if (!WindowBinding::ClearCachedDocumentValue(aCx, this) ||
+      !WindowBinding::ClearCachedPerformanceValue(aCx, this)) {
+    MOZ_CRASH("Unhandlable OOM while clearing document dependent slots.");
+  }
 }
 
 /* static */
 JSObject*
 nsGlobalWindow::CreateNamedPropertiesObject(JSContext *aCx,
                                             JS::Handle<JSObject*> aProto)
 {
   return WindowNamedPropertiesHandler::Create(aCx, aProto);
--- a/dom/base/nsImageLoadingContent.cpp
+++ b/dom/base/nsImageLoadingContent.cpp
@@ -177,17 +177,17 @@ nsImageLoadingContent::Notify(imgIReques
           = do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
 
         nsIDocument *doc = GetOurOwnerDoc();
         doc->AddBlockedTrackingNode(thisNode);
       }
     }
     nsresult status =
         reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
-    return OnStopRequest(aRequest, status);
+    return OnLoadComplete(aRequest, status);
   }
 
   if (aType == imgINotificationObserver::DECODE_COMPLETE) {
     if (mFireEventsOnDecode) {
       mFireEventsOnDecode = false;
 
       uint32_t reqStatus;
       aRequest->GetImageStatus(&reqStatus);
@@ -200,18 +200,17 @@ nsImageLoadingContent::Notify(imgIReques
 
     UpdateImageState(true);
   }
 
   return NS_OK;
 }
 
 nsresult
-nsImageLoadingContent::OnStopRequest(imgIRequest* aRequest,
-                                     nsresult aStatus)
+nsImageLoadingContent::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus)
 {
   uint32_t oldStatus;
   aRequest->GetImageStatus(&oldStatus);
 
   //XXXjdm This occurs when we have a pending request created, then another
   //       pending request replaces it before the first one is finished.
   //       This begs the question of what the correct behaviour is; we used
   //       to not have to care because we ran this code in OnStopDecode which
--- a/dom/base/nsImageLoadingContent.h
+++ b/dom/base/nsImageLoadingContent.h
@@ -204,17 +204,17 @@ protected:
    */
   virtual mozilla::CORSMode GetCORSMode();
 
   // Subclasses are *required* to call BindToTree/UnbindFromTree.
   void BindToTree(nsIDocument* aDocument, nsIContent* aParent,
                   nsIContent* aBindingParent, bool aCompileEventHandlers);
   void UnbindFromTree(bool aDeep, bool aNullParent);
 
-  nsresult OnStopRequest(imgIRequest* aRequest, nsresult aStatus);
+  nsresult OnLoadComplete(imgIRequest* aRequest, nsresult aStatus);
   void OnUnlockedDraw();
   nsresult OnImageIsAnimated(imgIRequest *aRequest);
 
   // The nsContentPolicyType we would use for this ImageLoadType
   static nsContentPolicyType PolicyTypeForLoad(ImageLoadType aImageLoadType);
 
 private:
   /**
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -2407,24 +2407,16 @@ DOMGCSliceCallback(JSRuntime *aRt, JS::G
     nsCycleCollector_dispatchDeferredDeletion();
   }
 
   if (sPrevGCSliceCallback)
     (*sPrevGCSliceCallback)(aRt, aProgress, aDesc);
 }
 
 void
-nsJSContext::ReportPendingException()
-{
-  if (mIsInitialized) {
-    nsJSUtils::ReportPendingException(mContext);
-  }
-}
-
-void
 nsJSContext::SetWindowProxy(JS::Handle<JSObject*> aWindowProxy)
 {
   mWindowProxy = aWindowProxy;
 }
 
 JSObject*
 nsJSContext::GetWindowProxy()
 {
--- a/dom/base/nsJSEnvironment.h
+++ b/dom/base/nsJSEnvironment.h
@@ -141,21 +141,16 @@ protected:
 
   // Helper to convert xpcom datatypes to jsvals.
   nsresult ConvertSupportsTojsvals(nsISupports *aArgs,
                                    JS::Handle<JSObject*> aScope,
                                    JS::AutoValueVector &aArgsOut);
 
   nsresult AddSupportsPrimitiveTojsvals(nsISupports *aArg, JS::Value *aArgv);
 
-  // Report the pending exception on our mContext, if any.  This
-  // function will set aside the frame chain on mContext before
-  // reporting.
-  void ReportPendingException();
-
 private:
   void DestroyJSContext();
 
   nsrefcnt GetCCRefcnt();
 
   JSContext *mContext;
   JS::Heap<JSObject*> mWindowProxy;
 
--- a/dom/base/nsNodeInfoManager.cpp
+++ b/dom/base/nsNodeInfoManager.cpp
@@ -23,16 +23,17 @@
 #include "nsReadableUtils.h"
 #include "nsGkAtoms.h"
 #include "nsComponentManagerUtils.h"
 #include "nsLayoutStatics.h"
 #include "nsBindingManager.h"
 #include "nsHashKeys.h"
 #include "nsCCUncollectableMarker.h"
 #include "nsNameSpaceManager.h"
+#include "nsDocument.h"
 
 using namespace mozilla;
 using mozilla::dom::NodeInfo;
 
 #include "prlog.h"
 
 #ifdef PR_LOGGING
 static PRLogModuleInfo* gNodeInfoManagerLeakPRLog;
@@ -148,30 +149,43 @@ nsNodeInfoManager::~nsNodeInfoManager()
 
   nsLayoutStatics::Release();
 }
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(nsNodeInfoManager)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_0(nsNodeInfoManager)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(nsNodeInfoManager)
-  if (tmp->mDocument &&
-      nsCCUncollectableMarker::InGeneration(cb,
-                                            tmp->mDocument->GetMarkedCCGeneration())) {
-    return NS_SUCCESS_INTERRUPTED_TRAVERSE;
-  }
   if (tmp->mNonDocumentNodeInfos) {
     NS_IMPL_CYCLE_COLLECTION_TRAVERSE_RAWPTR(mDocument)
   }
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBindingManager)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(nsNodeInfoManager, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(nsNodeInfoManager, Release)
 
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_BEGIN(nsNodeInfoManager)
+  if (tmp->mDocument) {
+    return NS_CYCLE_COLLECTION_PARTICIPANT(nsDocument)->CanSkip(tmp->mDocument, aRemovingAllowed);
+  }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_BEGIN(nsNodeInfoManager)
+  if (tmp->mDocument) {
+    return NS_CYCLE_COLLECTION_PARTICIPANT(nsDocument)->CanSkipInCC(tmp->mDocument);
+  }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_IN_CC_END
+
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_BEGIN(nsNodeInfoManager)
+  if (tmp->mDocument) {
+    return NS_CYCLE_COLLECTION_PARTICIPANT(nsDocument)->CanSkipThis(tmp->mDocument);
+  }
+NS_IMPL_CYCLE_COLLECTION_CAN_SKIP_THIS_END
+
 nsresult
 nsNodeInfoManager::Init(nsIDocument *aDocument)
 {
   NS_ENSURE_TRUE(mNodeInfoHash, NS_ERROR_OUT_OF_MEMORY);
 
   NS_PRECONDITION(!mPrincipal,
                   "Being inited when we already have a principal?");
   nsresult rv;
--- a/dom/base/nsNodeInfoManager.h
+++ b/dom/base/nsNodeInfoManager.h
@@ -35,17 +35,17 @@ class NodeInfo;
 class nsNodeInfoManager MOZ_FINAL
 {
 private:
   ~nsNodeInfoManager();
 
 public:
   nsNodeInfoManager();
 
-  NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(nsNodeInfoManager)
+  NS_DECL_CYCLE_COLLECTION_SKIPPABLE_NATIVE_CLASS(nsNodeInfoManager)
 
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(nsNodeInfoManager)
 
   /**
    * Initialize the nodeinfo manager with a document.
    */
   nsresult Init(nsIDocument *aDocument);
 
--- a/dom/base/test/test_bug338583.html
+++ b/dom/base/test/test_bug338583.html
@@ -348,39 +348,39 @@ https://bugzilla.mozilla.org/show_bug.cg
       ok(gEventSourceObj3_g.hits['fn_onmessage'] == 0, "Test 3.g failed");
       gEventSourceObj3_g.close();
       setTestHasFinished(test_id);
     }, parseInt(1500*stress_factor));
   }
 
   function fnMessageListenerTest3h(e) {
     fnMessageListenerTest3h.msg_ok = (fnMessageListenerTest3h.msg_ok && e.data == "ok");
-    fnMessageListenerTest3h.id_ok = (fnMessageListenerTest3h.msg_ok && e.lastEventId == "");
+    fnMessageListenerTest3h.id_ok = (fnMessageListenerTest3h.id_ok && e.lastEventId == "");
   }
 
   function doTest3_h(test_id) {
     gEventSourceObj3_h = new EventSource("badMessageEvent2.eventsource");
 
-    gEventSourceObj3_h.addEventListener('message event', fnMessageListenerTest3h, true);
+    gEventSourceObj3_h.addEventListener('message', fnMessageListenerTest3h, true);
     fnMessageListenerTest3h.msg_ok = true;
     fnMessageListenerTest3h.id_ok = true;
 
     gEventSourceObj3_h.onmessage = fn_onmessage;
     gEventSourceObj3_h.hits = [];
     gEventSourceObj3_h.hits['fn_onmessage'] = 0;
 
     setTimeout(function() {
       ok(gEventSourceObj3_h.hits['fn_onmessage'] > 1, "Test 3.h.1 failed");
       if (gEventSourceObj3_h.hits['fn_onmessage'] > 1) {
         ok(fnMessageListenerTest3h.msg_ok, "Test 3.h.2 failed");
         ok(fnMessageListenerTest3h.id_ok, "Test 3.h.3 failed");
       }
       gEventSourceObj3_h.close();
       setTestHasFinished(test_id);
-    }, parseInt(3000*stress_factor));
+    }, parseInt(6000*stress_factor));
   }
 
 // in order to test (4)
 //   a) close the object when it is in use, which is being processed and that is expected
 //      to dispatch more eventlisteners
 //   b) remove an eventlistener in use
 
   function fn_onmessage4_a(e)
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -3469,17 +3469,16 @@ class CGClearCachedValueMethod(CGAbstrac
                 slotIndex)
             regetMember = fill(
                 """
                 JS::Rooted<JS::Value> temp(aCx);
                 JSJitGetterCallArgs args(&temp);
                 JSAutoCompartment ac(aCx, obj);
                 if (!get_${name}(aCx, obj, aObject, args)) {
                   js::SetReservedSlot(obj, ${slotIndex}, oldValue);
-                  nsJSUtils::ReportPendingException(aCx);
                   return false;
                 }
                 return true;
                 """,
                 name=self.member.identifier.name,
                 slotIndex=slotIndex)
         else:
             declObj = "JSObject* obj;\n"
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.js
@@ -1,913 +1,129 @@
 /* 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";
 
-let Cu = Components.utils;
-let Ci = Components.interfaces;
-let Cc = Components.classes;
-let Cr = Components.results;
-
-/* BrowserElementParent injects script to listen for certain events in the
- * child.  We then listen to messages from the child script and take
- * appropriate action here in the parent.
- */
+const {utils: Cu, interfaces: Ci} = Components;
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
+
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
+const BROWSER_FRAMES_ENABLED_PREF = "dom.mozBrowserFramesEnabled";
 
-XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function () {
-  Cu.import("resource://gre/modules/Webapps.jsm");
-  return DOMApplicationRegistry;
-});
+XPCOMUtils.defineLazyModuleGetter(this, "BrowserElementParentBuilder",
+                                  "resource://gre/modules/BrowserElementParent.jsm",
+                                  "BrowserElementParentBuilder");
 
 function debug(msg) {
-  //dump("BrowserElementParent - " + msg + "\n");
-}
-
-function getIntPref(prefName, def) {
-  try {
-    return Services.prefs.getIntPref(prefName);
-  }
-  catch(err) {
-    return def;
-  }
-}
-
-function visibilityChangeHandler(e) {
-  // The visibilitychange event's target is the document.
-  let win = e.target.defaultView;
-
-  if (!win._browserElementParents) {
-    return;
-  }
-
-  let beps = Cu.nondeterministicGetWeakMapKeys(win._browserElementParents);
-  if (beps.length == 0) {
-    win.removeEventListener('visibilitychange', visibilityChangeHandler);
-    return;
-  }
-
-  for (let i = 0; i < beps.length; i++) {
-    beps[i]._ownerVisibilityChange();
-  }
+  //dump("BrowserElementParent.js - " + msg + "\n");
 }
 
-function defineNoReturnMethod(fn) {
-  return function method() {
-    if (!this._domRequestReady) {
-      // Remote browser haven't been created, we just queue the API call.
-      let args = Array.slice(arguments);
-      args.unshift(this);
-      this._pendingAPICalls.push(method.bind.apply(fn, args));
-      return;
-    }
-    if (this._isAlive()) {
-      fn.apply(this, arguments);
-    }
-  };
-}
+/**
+ * BrowserElementParent implements one half of <iframe mozbrowser>.  (The other
+ * half is, unsurprisingly, BrowserElementChild.)
+ *
+ * BrowserElementParentFactory detects when we create a windows or docshell
+ * contained inside a <iframe mozbrowser> and creates a BrowserElementParent
+ * object for that window.
+ *
+ * It creates a BrowserElementParent that injects script to listen for
+ * certain event.
+ */
 
-function defineDOMRequestMethod(msgName) {
-  return function() {
-    return this._sendDOMRequest(msgName);
-  };
+function BrowserElementParentFactory() {
+  this._initialized = false;
 }
 
-function BrowserElementParent() {
-  debug("Creating new BrowserElementParent object");
-  this._domRequestCounter = 0;
-  this._domRequestReady = false;
-  this._pendingAPICalls = [];
-  this._pendingDOMRequests = {};
-  this._pendingSetInputMethodActive = [];
-  this._nextPaintListeners = [];
-
-  Services.obs.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true);
-  Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
-  Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true);
-}
-
-BrowserElementParent.prototype = {
-
-  classDescription: "BrowserElementAPI implementation",
-  classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"),
-  contractID: "@mozilla.org/dom/browser-element-api;1",
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserElementAPI,
-                                         Ci.nsIObserver,
+BrowserElementParentFactory.prototype = {
+  classID: Components.ID("{ddeafdac-cb39-47c4-9cb8-c9027ee36d26}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
-  setFrameLoader: function(frameLoader) {
-    this._frameLoader = frameLoader;
-    this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
-    if (!this._frameElement) {
-      debug("No frame element?");
-      return;
-    }
-    // Listen to visibilitychange on the iframe's owner window, and forward
-    // changes down to the child.  We want to do this while registering as few
-    // visibilitychange listeners on _window as possible, because such a listener
-    // may live longer than this BrowserElementParent object.
-    //
-    // To accomplish this, we register just one listener on the window, and have
-    // it reference a WeakMap whose keys are all the BrowserElementParent objects
-    // on the window.  Then when the listener fires, we iterate over the
-    // WeakMap's keys (which we can do, because we're chrome) to notify the
-    // BrowserElementParents.
-    if (!this._window._browserElementParents) {
-      this._window._browserElementParents = new WeakMap();
-      this._window.addEventListener('visibilitychange',
-                                    visibilityChangeHandler,
-                                    /* useCapture = */ false,
-                                    /* wantsUntrusted = */ false);
-    }
-
-    this._window._browserElementParents.set(this, null);
-
-    // Insert ourself into the prompt service.
-    BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
-    this._setupMessageListener();
-    this._registerAppManifest();
-  },
-
-  _runPendingAPICall: function() {
-    if (!this._pendingAPICalls) {
-      return;
-    }
-    for (let i = 0; i < this._pendingAPICalls.length; i++) {
-      try {
-        this._pendingAPICalls[i]();
-      } catch (e) {
-        // throw the expections from pending functions.
-        debug('Exception when running pending API call: ' +  e);
-      }
-    }
-    delete this._pendingAPICalls;
-  },
-
-  _registerAppManifest: function() {
-    // If this browser represents an app then let the Webapps module register for
-    // any messages that it needs.
-    let appManifestURL =
-          this._frameElement.QueryInterface(Ci.nsIMozBrowserFrame).appManifestURL;
-    if (appManifestURL) {
-      let inParent = Cc["@mozilla.org/xre/app-info;1"]
-                       .getService(Ci.nsIXULRuntime)
-                       .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-      if (inParent) {
-        DOMApplicationRegistry.registerBrowserElementParentForApp(
-          { manifestURL: appManifestURL }, this._mm);
-      } else {
-        this._mm.sendAsyncMessage("Webapps:RegisterBEP",
-                                  { manifestURL: appManifestURL });
-      }
-    }
-  },
-
-  _setupMessageListener: function() {
-    this._mm = this._frameLoader.messageManager;
-    let self = this;
-    let isWidget = this._frameLoader
-                       .QueryInterface(Ci.nsIFrameLoader)
-                       .ownerIsWidget;
-
-    // Messages we receive are handed to functions which take a (data) argument,
-    // where |data| is the message manager's data object.
-    // We use a single message and dispatch to various function based
-    // on data.msg_name
-    let mmCalls = {
-      "hello": this._recvHello,
-      "loadstart": this._fireProfiledEventFromMsg,
-      "loadend": this._fireProfiledEventFromMsg,
-      "close": this._fireEventFromMsg,
-      "error": this._fireEventFromMsg,
-      "firstpaint": this._fireProfiledEventFromMsg,
-      "documentfirstpaint": this._fireProfiledEventFromMsg,
-      "nextpaint": this._recvNextPaint,
-      "got-purge-history": this._gotDOMRequestResult,
-      "got-screenshot": this._gotDOMRequestResult,
-      "got-contentdimensions": this._gotDOMRequestResult,
-      "got-can-go-back": this._gotDOMRequestResult,
-      "got-can-go-forward": this._gotDOMRequestResult,
-      "fullscreen-origin-change": this._remoteFullscreenOriginChange,
-      "rollback-fullscreen": this._remoteFrameFullscreenReverted,
-      "exit-fullscreen": this._exitFullscreen,
-      "got-visible": this._gotDOMRequestResult,
-      "visibilitychange": this._childVisibilityChange,
-      "got-set-input-method-active": this._gotDOMRequestResult,
-      "selectionchange": this._handleSelectionChange,
-      "scrollviewchange": this._handleScrollViewChange,
-      "touchcarettap": this._handleTouchCaretTap
-    };
-
-    let mmSecuritySensitiveCalls = {
-      "showmodalprompt": this._handleShowModalPrompt,
-      "contextmenu": this._fireCtxMenuEvent,
-      "securitychange": this._fireEventFromMsg,
-      "locationchange": this._fireEventFromMsg,
-      "iconchange": this._fireEventFromMsg,
-      "scrollareachanged": this._fireEventFromMsg,
-      "titlechange": this._fireProfiledEventFromMsg,
-      "opensearch": this._fireEventFromMsg,
-      "manifestchange": this._fireEventFromMsg,
-      "metachange": this._fireEventFromMsg,
-      "resize": this._fireEventFromMsg,
-      "activitydone": this._fireEventFromMsg,
-      "scroll": this._fireEventFromMsg
-    };
-
-    this._mm.addMessageListener('browser-element-api:call', function(aMsg) {
-      if (!self._isAlive()) {
-        return;
-      }
-
-      if (aMsg.data.msg_name in mmCalls) {
-        return mmCalls[aMsg.data.msg_name].apply(self, arguments);
-      } else if (!isWidget && aMsg.data.msg_name in mmSecuritySensitiveCalls) {
-        return mmSecuritySensitiveCalls[aMsg.data.msg_name]
-                 .apply(self, arguments);
-      }
-    });
-  },
-
   /**
-   * You shouldn't touch this._frameElement or this._window if _isAlive is
-   * false.  (You'll likely get an exception if you do.)
+   * Called on app startup, and also when the browser frames enabled pref is
+   * changed.
    */
-  _isAlive: function() {
-    return !Cu.isDeadWrapper(this._frameElement) &&
-           !Cu.isDeadWrapper(this._frameElement.ownerDocument) &&
-           !Cu.isDeadWrapper(this._frameElement.ownerDocument.defaultView);
-  },
-
-  get _window() {
-    return this._frameElement.ownerDocument.defaultView;
-  },
-
-  get _windowUtils() {
-    return this._window.QueryInterface(Ci.nsIInterfaceRequestor)
-                       .getInterface(Ci.nsIDOMWindowUtils);
-  },
-
-  promptAuth: function(authDetail, callback) {
-    let evt;
-    let self = this;
-    let callbackCalled = false;
-    let cancelCallback = function() {
-      if (!callbackCalled) {
-        callbackCalled = true;
-        callback(false, null, null);
-      }
-    };
-
-    // 1. We don't handle password-only prompts.
-    // 2. We don't handle for widget case because of security concern.
-    if (authDetail.isOnlyPassword ||
-        this._frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsWidget) {
-      cancelCallback();
+  _init: function() {
+    if (this._initialized) {
       return;
     }
 
-    /* username and password */
-    let detail = {
-      host:     authDetail.host,
-      realm:    authDetail.realm
-    };
-
-    evt = this._createEvent('usernameandpasswordrequired', detail,
-                            /* cancelable */ true);
-    Cu.exportFunction(function(username, password) {
-      if (callbackCalled)
-        return;
-      callbackCalled = true;
-      callback(true, username, password);
-    }, evt.detail, { defineAs: 'authenticate' });
-
-    Cu.exportFunction(cancelCallback, evt.detail, { defineAs: 'cancel' });
-
-    this._frameElement.dispatchEvent(evt);
-
-    if (!evt.defaultPrevented) {
-      cancelCallback();
-    }
-  },
-
-  _sendAsyncMsg: function(msg, data) {
-    try {
-      if (!data) {
-        data = { };
-      }
-
-      data.msg_name = msg;
-      this._mm.sendAsyncMessage('browser-element-api:call', data);
-    } catch (e) {
-      return false;
-    }
-    return true;
-  },
-
-  _recvHello: function() {
-    debug("recvHello");
-
-    // Inform our child if our owner element's document is invisible.  Note
-    // that we must do so here, rather than in the BrowserElementParent
-    // constructor, because the BrowserElementChild may not be initialized when
-    // we run our constructor.
-    if (this._window.document.hidden) {
-      this._ownerVisibilityChange();
-    }
-
-    if (!this._domRequestReady) {
-      // At least, one message listener such as for hello is registered.
-      // So we can use sendAsyncMessage now.
-      this._domRequestReady = true;
-      this._runPendingAPICall();
-    }
-
-    return {
-      name: this._frameElement.getAttribute('name'),
-      fullscreenAllowed:
-        this._frameElement.hasAttribute('allowfullscreen') ||
-        this._frameElement.hasAttribute('mozallowfullscreen'),
-      isPrivate: this._frameElement.hasAttribute('mozprivatebrowsing')
-    };
-  },
-
-  _fireCtxMenuEvent: function(data) {
-    let detail = data.json;
-    let evtName = detail.msg_name;
-
-    debug('fireCtxMenuEventFromMsg: ' + evtName + ' ' + detail);
-    let evt = this._createEvent(evtName, detail, /* cancellable */ true);
-
-    if (detail.contextmenu) {
-      var self = this;
-      Cu.exportFunction(function(id) {
-        self._sendAsyncMsg('fire-ctx-callback', {menuitem: id});
-      }, evt.detail, { defineAs: 'contextMenuItemSelected' });
+    // If the pref is disabled, do nothing except wait for the pref to change.
+    // (This is important for tests, if nothing else.)
+    if (!this._browserFramesPrefEnabled()) {
+      Services.prefs.addObserver(BROWSER_FRAMES_ENABLED_PREF, this, /* ownsWeak = */ true);
+      return;
     }
 
-    // The embedder may have default actions on context menu events, so
-    // we fire a context menu event even if the child didn't define a
-    // custom context menu
-    return !this._frameElement.dispatchEvent(evt);
-  },
-
-  /**
-   * add profiler marker for each event fired.
-   */
-  _fireProfiledEventFromMsg: function(data) {
-    if (Services.profiler !== undefined) {
-      Services.profiler.AddMarker(data.json.msg_name);
-    }
-    this._fireEventFromMsg(data);
-  },
+    debug("_init");
+    this._initialized = true;
 
-  /**
-   * Fire either a vanilla or a custom event, depending on the contents of
-   * |data|.
-   */
-  _fireEventFromMsg: function(data) {
-    let detail = data.json;
-    let name = detail.msg_name;
+    // Maps frame elements to BrowserElementParent objects.  We never look up
+    // anything in this map; the purpose is to keep the BrowserElementParent
+    // alive for as long as its frame element lives.
+    this._bepMap = new WeakMap();
 
-    // For events that send a "_payload_" property, we just want to transmit
-    // this in the event.
-    if ("_payload_" in detail) {
-      detail = detail._payload_;
-    }
-
-    debug('fireEventFromMsg: ' + name + ', ' + JSON.stringify(detail));
-    let evt = this._createEvent(name, detail,
-                                /* cancelable = */ false);
-    this._frameElement.dispatchEvent(evt);
+    Services.obs.addObserver(this, 'remote-browser-pending', /* ownsWeak = */ true);
+    Services.obs.addObserver(this, 'inprocess-browser-shown', /* ownsWeak = */ true);
   },
 
-  _handleShowModalPrompt: function(data) {
-    // Fire a showmodalprmopt event on the iframe.  When this method is called,
-    // the child is spinning in a nested event loop waiting for an
-    // unblock-modal-prompt message.
-    //
-    // If the embedder calls preventDefault() on the showmodalprompt event,
-    // we'll block the child until event.detail.unblock() is called.
-    //
-    // Otherwise, if preventDefault() is not called, we'll send the
-    // unblock-modal-prompt message to the child as soon as the event is done
-    // dispatching.
-
-    let detail = data.json;
-    debug('handleShowPrompt ' + JSON.stringify(detail));
-
-    // Strip off the windowID property from the object we send along in the
-    // event.
-    let windowID = detail.windowID;
-    delete detail.windowID;
-    debug("Event will have detail: " + JSON.stringify(detail));
-    let evt = this._createEvent('showmodalprompt', detail,
-                                /* cancelable = */ true);
-
-    let self = this;
-    let unblockMsgSent = false;
-    function sendUnblockMsg() {
-      if (unblockMsgSent) {
-        return;
-      }
-      unblockMsgSent = true;
-
-      // We don't need to sanitize evt.detail.returnValue (e.g. converting the
-      // return value of confirm() to a boolean); Gecko does that for us.
-
-      let data = { windowID: windowID,
-                   returnValue: evt.detail.returnValue };
-      self._sendAsyncMsg('unblock-modal-prompt', data);
+  _browserFramesPrefEnabled: function() {
+    try {
+      return Services.prefs.getBoolPref(BROWSER_FRAMES_ENABLED_PREF);
     }
-
-    Cu.exportFunction(sendUnblockMsg, evt.detail, { defineAs: 'unblock' });
-
-    this._frameElement.dispatchEvent(evt);
-
-    if (!evt.defaultPrevented) {
-      // Unblock the inner frame immediately.  Otherwise we'll unblock upon
-      // evt.detail.unblock().
-      sendUnblockMsg();
+    catch(e) {
+      return false;
     }
   },
 
-  _handleSelectionChange: function(data) {
-    let evt = this._createEvent('selectionchange', data.json,
-                                /* cancelable = */ false);
-    this._frameElement.dispatchEvent(evt);
-  },
-
-  _handleScrollViewChange: function(data) {
-    let evt = this._createEvent("scrollviewchange", data.json,
-                                /* cancelable = */ false);
-    this._frameElement.dispatchEvent(evt);
-  },
-
-  _handleTouchCaretTap: function(data) {
-    let evt = this._createEvent("touchcarettap", data.json,
-                                /* cancelable = */ false);
-    this._frameElement.dispatchEvent(evt);
-  },
-
-  _createEvent: function(evtName, detail, cancelable) {
-    // This will have to change if we ever want to send a CustomEvent with null
-    // detail.  For now, it's OK.
-    if (detail !== undefined && detail !== null) {
-      detail = Cu.cloneInto(detail, this._window);
-      return new this._window.CustomEvent('mozbrowser' + evtName,
-                                          { bubbles: true,
-                                            cancelable: cancelable,
-                                            detail: detail });
+  _observeInProcessBrowserFrameShown: function(frameLoader) {
+    // Ignore notifications that aren't from a BrowserOrApp
+    if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
+      return;
     }
-
-    return new this._window.Event('mozbrowser' + evtName,
-                                  { bubbles: true,
-                                    cancelable: cancelable });
-  },
-
-  /**
-   * Kick off a DOMRequest in the child process.
-   *
-   * We'll fire an event called |msgName| on the child process, passing along
-   * an object with two fields:
-   *
-   *  - id:  the ID of this request.
-   *  - arg: arguments to pass to the child along with this request.
-   *
-   * We expect the child to pass the ID back to us upon completion of the
-   * request.  See _gotDOMRequestResult.
-   */
-  _sendDOMRequest: function(msgName, args) {
-    let id = 'req_' + this._domRequestCounter++;
-    let req = Services.DOMRequest.createRequest(this._window);
-    let self = this;
-    let send = function() {
-      if (!self._isAlive()) {
-        return;
-      }
-      if (self._sendAsyncMsg(msgName, {id: id, args: args})) {
-        self._pendingDOMRequests[id] = req;
-      } else {
-        Services.DOMRequest.fireErrorAsync(req, "fail");
-      }
-    };
-    if (this._domRequestReady) {
-      send();
-    } else {
-      // Child haven't been loaded.
-      this._pendingAPICalls.push(send);
-    }
-    return req;
-  },
-
-  /**
-   * Called when the child process finishes handling a DOMRequest.  data.json
-   * must have the fields [id, successRv], if the DOMRequest was successful, or
-   * [id, errorMsg], if the request was not successful.
-   *
-   * The fields have the following meanings:
-   *
-   *  - id:        the ID of the DOM request (see _sendDOMRequest)
-   *  - successRv: the request's return value, if the request succeeded
-   *  - errorMsg:  the message to pass to DOMRequest.fireError(), if the request
-   *               failed.
-   *
-   */
-  _gotDOMRequestResult: function(data) {
-    let req = this._pendingDOMRequests[data.json.id];
-    delete this._pendingDOMRequests[data.json.id];
-
-    if ('successRv' in data.json) {
-      debug("Successful gotDOMRequestResult.");
-      let clientObj = Cu.cloneInto(data.json.successRv, this._window);
-      Services.DOMRequest.fireSuccess(req, clientObj);
-    }
-    else {
-      debug("Got error in gotDOMRequestResult.");
-      Services.DOMRequest.fireErrorAsync(req,
-        Cu.cloneInto(data.json.errorMsg, this._window));
-    }
-  },
-
-  setVisible: defineNoReturnMethod(function(visible) {
-    this._sendAsyncMsg('set-visible', {visible: visible});
-    this._frameLoader.visible = visible;
-  }),
-
-  getVisible: defineDOMRequestMethod('get-visible'),
-
-  setActive: defineNoReturnMethod(function(active) {
-    this._frameLoader.visible = active;
-  }),
-
-  getActive: function() {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
-
-    return this._frameLoader.visible;
+    debug("In-process browser frame shown " + frameLoader);
+    this._createBrowserElementParent(frameLoader,
+                                     /* hasRemoteFrame = */ false,
+                                     /* pending frame */ false);
   },
 
-  sendMouseEvent: defineNoReturnMethod(function(type, x, y, button, clickCount, modifiers) {
-    this._sendAsyncMsg("send-mouse-event", {
-      "type": type,
-      "x": x,
-      "y": y,
-      "button": button,
-      "clickCount": clickCount,
-      "modifiers": modifiers
-    });
-  }),
-
-  sendTouchEvent: defineNoReturnMethod(function(type, identifiers, touchesX, touchesY,
-                                                radiisX, radiisY, rotationAngles, forces,
-                                                count, modifiers) {
-
-    let tabParent = this._frameLoader.tabParent;
-    if (tabParent && tabParent.useAsyncPanZoom) {
-      tabParent.injectTouchEvent(type,
-                                 identifiers,
-                                 touchesX,
-                                 touchesY,
-                                 radiisX,
-                                 radiisY,
-                                 rotationAngles,
-                                 forces,
-                                 count,
-                                 modifiers);
-    } else {
-      this._sendAsyncMsg("send-touch-event", {
-        "type": type,
-        "identifiers": identifiers,
-        "touchesX": touchesX,
-        "touchesY": touchesY,
-        "radiisX": radiisX,
-        "radiisY": radiisY,
-        "rotationAngles": rotationAngles,
-        "forces": forces,
-        "count": count,
-        "modifiers": modifiers
-      });
-    }
-  }),
-
-  getCanGoBack: defineDOMRequestMethod('get-can-go-back'),
-  getCanGoForward: defineDOMRequestMethod('get-can-go-forward'),
-  getContentDimensions: defineDOMRequestMethod('get-contentdimensions'),
-
-  goBack: defineNoReturnMethod(function() {
-    this._sendAsyncMsg('go-back');
-  }),
-
-  goForward: defineNoReturnMethod(function() {
-    this._sendAsyncMsg('go-forward');
-  }),
-
-  reload: defineNoReturnMethod(function(hardReload) {
-    this._sendAsyncMsg('reload', {hardReload: hardReload});
-  }),
-
-  stop: defineNoReturnMethod(function() {
-    this._sendAsyncMsg('stop');
-  }),
-
-  /*
-   * The valid range of zoom scale is defined in preference "zoom.maxPercent" and "zoom.minPercent".
-   */
-  zoom: defineNoReturnMethod(function(zoom) {
-    zoom *= 100;
-    zoom = Math.min(getIntPref("zoom.maxPercent", 300), zoom);
-    zoom = Math.max(getIntPref("zoom.minPercent", 50), zoom);
-    this._sendAsyncMsg('zoom', {zoom: zoom / 100.0});
-  }),
-
-  purgeHistory: defineDOMRequestMethod('purge-history'),
-
-
-  download: function(_url, _options) {
-    if (!this._isAlive()) {
-      return null;
-    }
-    let ioService =
-      Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
-    let uri = ioService.newURI(_url, null, null);
-    let url = uri.QueryInterface(Ci.nsIURL);
-
-    // Ensure we have _options, we always use it to send the filename.
-    _options = _options || {};
-    if (!_options.filename) {
-      _options.filename = url.fileName;
-    }
-
-    debug('_options = ' + uneval(_options));
-
-    // Ensure we have a filename.
-    if (!_options.filename) {
-      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
+  _observeRemoteBrowserFramePending: function(frameLoader) {
+    // Ignore notifications that aren't from a BrowserOrApp
+    if (!frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsBrowserOrAppFrame) {
+      return;
     }
-
-    let interfaceRequestor =
-      this._frameLoader.loadContext.QueryInterface(Ci.nsIInterfaceRequestor);
-    let req = Services.DOMRequest.createRequest(this._window);
-
-    function DownloadListener() {
-      debug('DownloadListener Constructor');
-    }
-    DownloadListener.prototype = {
-      extListener: null,
-      onStartRequest: function(aRequest, aContext) {
-        debug('DownloadListener - onStartRequest');
-        let extHelperAppSvc =
-          Cc['@mozilla.org/uriloader/external-helper-app-service;1'].
-          getService(Ci.nsIExternalHelperAppService);
-        let channel = aRequest.QueryInterface(Ci.nsIChannel);
-
-        // First, we'll ensure the filename doesn't have any leading
-        // periods. We have to do it here to avoid ending up with a filename
-        // that's only an extension with no extension (e.g. Sending in
-        // '.jpeg' without stripping the '.' would result in a filename of
-        // 'jpeg' where we want 'jpeg.jpeg'.
-        _options.filename = _options.filename.replace(/^\.+/, "");
-
-        let ext = null;
-        let mimeSvc = extHelperAppSvc.QueryInterface(Ci.nsIMIMEService);
-        try {
-          ext = '.' + mimeSvc.getPrimaryExtension(channel.contentType, '');
-        } catch (e) { ext = null; }
-
-        // Check if we need to add an extension to the filename.
-        if (ext && !_options.filename.endsWith(ext)) {
-          _options.filename += ext;
-        }
-        // Set the filename to use when saving to disk.
-        channel.contentDispositionFilename = _options.filename;
-
-        this.extListener =
-          extHelperAppSvc.doContent(
-              channel.contentType,
-              aRequest,
-              interfaceRequestor,
-              true);
-        this.extListener.onStartRequest(aRequest, aContext);
-      },
-      onStopRequest: function(aRequest, aContext, aStatusCode) {
-        debug('DownloadListener - onStopRequest (aStatusCode = ' +
-               aStatusCode + ')');
-        if (aStatusCode == Cr.NS_OK) {
-          // Everything looks great.
-          debug('DownloadListener - Download Successful.');
-          Services.DOMRequest.fireSuccess(req, aStatusCode);
-        }
-        else {
-          // In case of failure, we'll simply return the failure status code.
-          debug('DownloadListener - Download Failed!');
-          Services.DOMRequest.fireError(req, aStatusCode);
-        }
-
-        if (this.extListener) {
-          this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
-        }
-      },
-      onDataAvailable: function(aRequest, aContext, aInputStream,
-                                aOffset, aCount) {
-        this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
-                                         aOffset, aCount);
-      },
-      QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener,
-                                             Ci.nsIRequestObserver])
-    };
-
-    let channel = ioService.newChannelFromURI(url);
-
-    // XXX We would set private browsing information prior to calling this.
-    channel.notificationCallbacks = interfaceRequestor;
-
-    // Since we're downloading our own local copy we'll want to bypass the
-    // cache and local cache if the channel let's us specify this.
-    let flags = Ci.nsIChannel.LOAD_CALL_CONTENT_SNIFFERS |
-                Ci.nsIChannel.LOAD_BYPASS_CACHE;
-    if (channel instanceof Ci.nsICachingChannel) {
-      debug('This is a caching channel. Forcing bypass.');
-      flags |= Ci.nsICachingChannel.LOAD_BYPASS_LOCAL_CACHE_IF_BUSY;
-    }
-
-    channel.loadFlags |= flags;
-
-    if (channel instanceof Ci.nsIHttpChannel) {
-      debug('Setting HTTP referrer = ' + this._window.document.documentURIObject);
-      channel.referrer = this._window.document.documentURIObject;
-      if (channel instanceof Ci.nsIHttpChannelInternal) {
-        channel.forceAllowThirdPartyCookie = true;
-      }
-    }
-
-    // Set-up complete, let's get things started.
-    channel.asyncOpen(new DownloadListener(), null);
-
-    return req;
+    debug("Remote browser frame shown " + frameLoader);
+    this._createBrowserElementParent(frameLoader,
+                                     /* hasRemoteFrame = */ true,
+                                     /* pending frame */ true);
   },
 
-  getScreenshot: function(_width, _height, _mimeType) {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
-
-    let width = parseInt(_width);
-    let height = parseInt(_height);
-    let mimeType = (typeof _mimeType === 'string') ?
-      _mimeType.trim().toLowerCase() : 'image/jpeg';
-    if (isNaN(width) || isNaN(height) || width < 0 || height < 0) {
-      throw Components.Exception("Invalid argument",
-                                 Cr.NS_ERROR_INVALID_ARG);
-    }
-
-    return this._sendDOMRequest('get-screenshot',
-                                {width: width, height: height,
-                                 mimeType: mimeType});
-  },
-
-  _recvNextPaint: function(data) {
-    let listeners = this._nextPaintListeners;
-    this._nextPaintListeners = [];
-    for (let listener of listeners) {
-      try {
-        listener.recvNextPaint();
-      } catch (e) {
-        // If a listener throws we'll continue.
-      }
-    }
-  },
-
-  addNextPaintListener: function(listener) {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
-
-    let self = this;
-    let run = function() {
-      if (self._nextPaintListeners.push(listener) == 1)
-        self._sendAsyncMsg('activate-next-paint-listener');
-    };
-    if (!this._domRequestReady) {
-      this._pendingAPICalls.push(run);
-    } else {
-      run();
-    }
-  },
-
-  removeNextPaintListener: function(listener) {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
-
-    let self = this;
-    let run = function() {
-      for (let i = self._nextPaintListeners.length - 1; i >= 0; i--) {
-        if (self._nextPaintListeners[i] == listener) {
-          self._nextPaintListeners.splice(i, 1);
-          break;
-        }
-      }
-
-      if (self._nextPaintListeners.length == 0)
-        self._sendAsyncMsg('deactivate-next-paint-listener');
-    };
-    if (!this._domRequestReady) {
-      this._pendingAPICalls.push(run);
-    } else {
-      run();
-    }
-  },
-
-  setInputMethodActive: function(isActive) {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
-
-    if (typeof isActive !== 'boolean') {
-      throw Components.Exception("Invalid argument",
-                                 Cr.NS_ERROR_INVALID_ARG);
-    }
-
-    return this._sendDOMRequest('set-input-method-active',
-                                {isActive: isActive});
-  },
-
-  /**
-   * Called when the visibility of the window which owns this iframe changes.
-   */
-  _ownerVisibilityChange: function() {
-    this._sendAsyncMsg('owner-visibility-change',
-                       {visible: !this._window.document.hidden});
-  },
-
-  /*
-   * Called when the child notices that its visibility has changed.
-   *
-   * This is sometimes redundant; for example, the child's visibility may
-   * change in response to a setVisible request that we made here!  But it's
-   * not always redundant; for example, the child's visibility may change in
-   * response to its parent docshell being hidden.
-   */
-  _childVisibilityChange: function(data) {
-    debug("_childVisibilityChange(" + data.json.visible + ")");
-    this._frameLoader.visible = data.json.visible;
-
-    this._fireEventFromMsg(data);
-  },
-
-  _exitFullscreen: function() {
-    this._windowUtils.exitFullscreen();
-  },
-
-  _remoteFullscreenOriginChange: function(data) {
-    let origin = data.json._payload_;
-    this._windowUtils.remoteFrameFullscreenChanged(this._frameElement, origin);
-  },
-
-  _remoteFrameFullscreenReverted: function(data) {
-    this._windowUtils.remoteFrameFullscreenReverted();
-  },
-
-  _fireFatalError: function() {
-    let evt = this._createEvent('error', {type: 'fatal'},
-                                /* cancelable = */ false);
-    this._frameElement.dispatchEvent(evt);
+  _createBrowserElementParent: function(frameLoader, hasRemoteFrame, isPendingFrame) {
+    let frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
+    this._bepMap.set(frameElement, BrowserElementParentBuilder.create(
+      frameLoader, hasRemoteFrame, isPendingFrame));
   },
 
   observe: function(subject, topic, data) {
     switch(topic) {
-    case 'oop-frameloader-crashed':
-      if (this._isAlive() && subject == this._frameLoader) {
-        this._fireFatalError();
+    case 'app-startup':
+      this._init();
+      break;
+    case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
+      if (data == BROWSER_FRAMES_ENABLED_PREF) {
+        this._init();
       }
       break;
-    case 'ask-children-to-exit-fullscreen':
-      if (this._isAlive() &&
-          this._frameElement.ownerDocument == subject &&
-          this._frameLoader.QueryInterface(Ci.nsIFrameLoader).tabParent) {
-        this._sendAsyncMsg('exit-fullscreen');
-      }
+    case 'remote-browser-pending':
+      this._observeRemoteBrowserFramePending(subject);
       break;
-    case 'copypaste-docommand':
-      if (this._isAlive() && this._frameElement.isEqualNode(subject.wrappedJSObject)) {
-        this._sendAsyncMsg('do-command', { command: data });
-      }
+    case 'inprocess-browser-shown':
+      this._observeInProcessBrowserFrameShown(subject);
       break;
-    default:
-      debug('Unknown topic: ' + topic);
-      break;
-    };
+    }
   },
 };
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParent]);
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParentFactory]);
copy from dom/browser-element/BrowserElementParent.js
copy to dom/browser-element/BrowserElementParent.jsm
--- a/dom/browser-element/BrowserElementParent.js
+++ b/dom/browser-element/BrowserElementParent.jsm
@@ -9,27 +9,31 @@ let Ci = Components.interfaces;
 let Cc = Components.classes;
 let Cr = Components.results;
 
 /* BrowserElementParent injects script to listen for certain events in the
  * child.  We then listen to messages from the child script and take
  * appropriate action here in the parent.
  */
 
+this.EXPORTED_SYMBOLS = ["BrowserElementParentBuilder"];
+
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/BrowserElementPromptService.jsm");
 
 XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function () {
   Cu.import("resource://gre/modules/Webapps.jsm");
   return DOMApplicationRegistry;
 });
 
+const TOUCH_EVENTS_ENABLED_PREF = "dom.w3c_touch_events.enabled";
+
 function debug(msg) {
-  //dump("BrowserElementParent - " + msg + "\n");
+  //dump("BrowserElementParent.jsm - " + msg + "\n");
 }
 
 function getIntPref(prefName, def) {
   try {
     return Services.prefs.getIntPref(prefName);
   }
   catch(err) {
     return def;
@@ -50,93 +54,148 @@ function visibilityChangeHandler(e) {
     return;
   }
 
   for (let i = 0; i < beps.length; i++) {
     beps[i]._ownerVisibilityChange();
   }
 }
 
-function defineNoReturnMethod(fn) {
-  return function method() {
-    if (!this._domRequestReady) {
-      // Remote browser haven't been created, we just queue the API call.
-      let args = Array.slice(arguments);
-      args.unshift(this);
-      this._pendingAPICalls.push(method.bind.apply(fn, args));
-      return;
-    }
-    if (this._isAlive()) {
-      fn.apply(this, arguments);
-    }
-  };
+this.BrowserElementParentBuilder = {
+  create: function create(frameLoader, hasRemoteFrame, isPendingFrame) {
+    return new BrowserElementParent(frameLoader, hasRemoteFrame);
+  }
 }
 
-function defineDOMRequestMethod(msgName) {
-  return function() {
-    return this._sendDOMRequest(msgName);
-  };
-}
-
-function BrowserElementParent() {
-  debug("Creating new BrowserElementParent object");
+function BrowserElementParent(frameLoader, hasRemoteFrame, isPendingFrame) {
+  debug("Creating new BrowserElementParent object for " + frameLoader);
   this._domRequestCounter = 0;
   this._domRequestReady = false;
   this._pendingAPICalls = [];
   this._pendingDOMRequests = {};
   this._pendingSetInputMethodActive = [];
+  this._hasRemoteFrame = hasRemoteFrame;
   this._nextPaintListeners = [];
 
+  this._frameLoader = frameLoader;
+  this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
+  let self = this;
+  if (!this._frameElement) {
+    debug("No frame element?");
+    return;
+  }
+
   Services.obs.addObserver(this, 'ask-children-to-exit-fullscreen', /* ownsWeak = */ true);
   Services.obs.addObserver(this, 'oop-frameloader-crashed', /* ownsWeak = */ true);
   Services.obs.addObserver(this, 'copypaste-docommand', /* ownsWeak = */ true);
+
+  let defineMethod = function(name, fn) {
+    XPCNativeWrapper.unwrap(self._frameElement)[name] = Cu.exportFunction(function() {
+      if (self._isAlive()) {
+        return fn.apply(self, arguments);
+      }
+    }, self._frameElement);
+  }
+
+  let defineNoReturnMethod = function(name, fn) {
+    XPCNativeWrapper.unwrap(self._frameElement)[name] = Cu.exportFunction(function method() {
+      if (!self._domRequestReady) {
+        // Remote browser haven't been created, we just queue the API call.
+        let args = Array.slice(arguments);
+        args.unshift(self);
+        self._pendingAPICalls.push(method.bind.apply(fn, args));
+        return;
+      }
+      if (self._isAlive()) {
+        fn.apply(self, arguments);
+      }
+    }, self._frameElement);
+  };
+
+  let defineDOMRequestMethod = function(domName, msgName) {
+    XPCNativeWrapper.unwrap(self._frameElement)[domName] = Cu.exportFunction(function() {
+      return self._sendDOMRequest(msgName);
+    }, self._frameElement);
+  }
+
+  // Define methods on the frame element.
+  defineNoReturnMethod('setVisible', this._setVisible);
+  defineDOMRequestMethod('getVisible', 'get-visible');
+
+  // Not expose security sensitive browser API for widgets
+  if (!this._frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerIsWidget) {
+    defineNoReturnMethod('sendMouseEvent', this._sendMouseEvent);
+
+    // 0 = disabled, 1 = enabled, 2 - auto detect
+    if (getIntPref(TOUCH_EVENTS_ENABLED_PREF, 0) != 0) {
+      defineNoReturnMethod('sendTouchEvent', this._sendTouchEvent);
+    }
+    defineNoReturnMethod('goBack', this._goBack);
+    defineNoReturnMethod('goForward', this._goForward);
+    defineNoReturnMethod('reload', this._reload);
+    defineNoReturnMethod('stop', this._stop);
+    defineMethod('download', this._download);
+    defineDOMRequestMethod('purgeHistory', 'purge-history');
+    defineMethod('getScreenshot', this._getScreenshot);
+    defineNoReturnMethod('zoom', this._zoom);
+
+    defineDOMRequestMethod('getCanGoBack', 'get-can-go-back');
+    defineDOMRequestMethod('getCanGoForward', 'get-can-go-forward');
+    defineDOMRequestMethod('getContentDimensions', 'get-contentdimensions');
+  }
+
+  defineMethod('addNextPaintListener', this._addNextPaintListener);
+  defineMethod('removeNextPaintListener', this._removeNextPaintListener);
+  defineNoReturnMethod('setActive', this._setActive);
+  defineMethod('getActive', 'this._getActive');
+
+  let principal = this._frameElement.ownerDocument.nodePrincipal;
+  let perm = Services.perms
+             .testExactPermissionFromPrincipal(principal, "input-manage");
+  if (perm === Ci.nsIPermissionManager.ALLOW_ACTION) {
+    defineMethod('setInputMethodActive', this._setInputMethodActive);
+  }
+
+  // Listen to visibilitychange on the iframe's owner window, and forward
+  // changes down to the child.  We want to do this while registering as few
+  // visibilitychange listeners on _window as possible, because such a listener
+  // may live longer than this BrowserElementParent object.
+  //
+  // To accomplish this, we register just one listener on the window, and have
+  // it reference a WeakMap whose keys are all the BrowserElementParent objects
+  // on the window.  Then when the listener fires, we iterate over the
+  // WeakMap's keys (which we can do, because we're chrome) to notify the
+  // BrowserElementParents.
+  if (!this._window._browserElementParents) {
+    this._window._browserElementParents = new WeakMap();
+    this._window.addEventListener('visibilitychange',
+                                  visibilityChangeHandler,
+                                  /* useCapture = */ false,
+                                  /* wantsUntrusted = */ false);
+  }
+
+  this._window._browserElementParents.set(this, null);
+
+  // Insert ourself into the prompt service.
+  BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
+  if (!isPendingFrame) {
+    this._setupMessageListener();
+    this._registerAppManifest();
+  } else {
+    // if we are a pending frame, we setup message manager after
+    // observing remote-browser-frame-shown
+    Services.obs.addObserver(this, 'remote-browser-frame-shown', /* ownsWeak = */ true);
+  }
 }
 
 BrowserElementParent.prototype = {
 
-  classDescription: "BrowserElementAPI implementation",
-  classID: Components.ID("{9f171ac4-0939-4ef8-b360-3408aedc3060}"),
-  contractID: "@mozilla.org/dom/browser-element-api;1",
-  QueryInterface: XPCOMUtils.generateQI([Ci.nsIBrowserElementAPI,
-                                         Ci.nsIObserver,
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsISupportsWeakReference]),
 
-  setFrameLoader: function(frameLoader) {
-    this._frameLoader = frameLoader;
-    this._frameElement = frameLoader.QueryInterface(Ci.nsIFrameLoader).ownerElement;
-    if (!this._frameElement) {
-      debug("No frame element?");
-      return;
-    }
-    // Listen to visibilitychange on the iframe's owner window, and forward
-    // changes down to the child.  We want to do this while registering as few
-    // visibilitychange listeners on _window as possible, because such a listener
-    // may live longer than this BrowserElementParent object.
-    //
-    // To accomplish this, we register just one listener on the window, and have
-    // it reference a WeakMap whose keys are all the BrowserElementParent objects
-    // on the window.  Then when the listener fires, we iterate over the
-    // WeakMap's keys (which we can do, because we're chrome) to notify the
-    // BrowserElementParents.
-    if (!this._window._browserElementParents) {
-      this._window._browserElementParents = new WeakMap();
-      this._window.addEventListener('visibilitychange',
-                                    visibilityChangeHandler,
-                                    /* useCapture = */ false,
-                                    /* wantsUntrusted = */ false);
-    }
-
-    this._window._browserElementParents.set(this, null);
-
-    // Insert ourself into the prompt service.
-    BrowserElementPromptService.mapFrameToBrowserElementParent(this._frameElement, this);
-    this._setupMessageListener();
-    this._registerAppManifest();
-  },
-
   _runPendingAPICall: function() {
     if (!this._pendingAPICalls) {
       return;
     }
     for (let i = 0; i < this._pendingAPICalls.length; i++) {
       try {
         this._pendingAPICalls[i]();
       } catch (e) {
@@ -528,50 +587,43 @@ BrowserElementParent.prototype = {
     }
     else {
       debug("Got error in gotDOMRequestResult.");
       Services.DOMRequest.fireErrorAsync(req,
         Cu.cloneInto(data.json.errorMsg, this._window));
     }
   },
 
-  setVisible: defineNoReturnMethod(function(visible) {
+  _setVisible: function(visible) {
     this._sendAsyncMsg('set-visible', {visible: visible});
     this._frameLoader.visible = visible;
-  }),
+  },
 
-  getVisible: defineDOMRequestMethod('get-visible'),
-
-  setActive: defineNoReturnMethod(function(active) {
+  _setActive: function(active) {
     this._frameLoader.visible = active;
-  }),
+  },
 
-  getActive: function() {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
-
+  _getActive: function() {
     return this._frameLoader.visible;
   },
 
-  sendMouseEvent: defineNoReturnMethod(function(type, x, y, button, clickCount, modifiers) {
+  _sendMouseEvent: function(type, x, y, button, clickCount, modifiers) {
     this._sendAsyncMsg("send-mouse-event", {
       "type": type,
       "x": x,
       "y": y,
       "button": button,
       "clickCount": clickCount,
       "modifiers": modifiers
     });
-  }),
+  },
 
-  sendTouchEvent: defineNoReturnMethod(function(type, identifiers, touchesX, touchesY,
-                                                radiisX, radiisY, rotationAngles, forces,
-                                                count, modifiers) {
+  _sendTouchEvent: function(type, identifiers, touchesX, touchesY,
+                            radiisX, radiisY, rotationAngles, forces,
+                            count, modifiers) {
 
     let tabParent = this._frameLoader.tabParent;
     if (tabParent && tabParent.useAsyncPanZoom) {
       tabParent.injectTouchEvent(type,
                                  identifiers,
                                  touchesX,
                                  touchesY,
                                  radiisX,
@@ -589,55 +641,45 @@ BrowserElementParent.prototype = {
         "radiisX": radiisX,
         "radiisY": radiisY,
         "rotationAngles": rotationAngles,
         "forces": forces,
         "count": count,
         "modifiers": modifiers
       });
     }
-  }),
+  },
 
-  getCanGoBack: defineDOMRequestMethod('get-can-go-back'),
-  getCanGoForward: defineDOMRequestMethod('get-can-go-forward'),
-  getContentDimensions: defineDOMRequestMethod('get-contentdimensions'),
-
-  goBack: defineNoReturnMethod(function() {
+  _goBack: function() {
     this._sendAsyncMsg('go-back');
-  }),
+  },
 
-  goForward: defineNoReturnMethod(function() {
+  _goForward: function() {
     this._sendAsyncMsg('go-forward');
-  }),
+  },
 
-  reload: defineNoReturnMethod(function(hardReload) {
+  _reload: function(hardReload) {
     this._sendAsyncMsg('reload', {hardReload: hardReload});
-  }),
+  },
 
-  stop: defineNoReturnMethod(function() {
+  _stop: function() {
     this._sendAsyncMsg('stop');
-  }),
+  },
 
   /*
    * The valid range of zoom scale is defined in preference "zoom.maxPercent" and "zoom.minPercent".
    */
-  zoom: defineNoReturnMethod(function(zoom) {
+  _zoom: function(zoom) {
     zoom *= 100;
     zoom = Math.min(getIntPref("zoom.maxPercent", 300), zoom);
     zoom = Math.max(getIntPref("zoom.minPercent", 50), zoom);
     this._sendAsyncMsg('zoom', {zoom: zoom / 100.0});
-  }),
-
-  purgeHistory: defineDOMRequestMethod('purge-history'),
-
+  },
 
-  download: function(_url, _options) {
-    if (!this._isAlive()) {
-      return null;
-    }
+  _download: function(_url, _options) {
     let ioService =
       Cc['@mozilla.org/network/io-service;1'].getService(Ci.nsIIOService);
     let uri = ioService.newURI(_url, null, null);
     let url = uri.QueryInterface(Ci.nsIURL);
 
     // Ensure we have _options, we always use it to send the filename.
     _options = _options || {};
     if (!_options.filename) {
@@ -713,17 +755,17 @@ BrowserElementParent.prototype = {
           this.extListener.onStopRequest(aRequest, aContext, aStatusCode);
         }
       },
       onDataAvailable: function(aRequest, aContext, aInputStream,
                                 aOffset, aCount) {
         this.extListener.onDataAvailable(aRequest, aContext, aInputStream,
                                          aOffset, aCount);
       },
-      QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener,
+      QueryInterface: XPCOMUtils.generateQI([Ci.nsIStreamListener, 
                                              Ci.nsIRequestObserver])
     };
 
     let channel = ioService.newChannelFromURI(url);
 
     // XXX We would set private browsing information prior to calling this.
     channel.notificationCallbacks = interfaceRequestor;
 
@@ -747,22 +789,17 @@ BrowserElementParent.prototype = {
     }
 
     // Set-up complete, let's get things started.
     channel.asyncOpen(new DownloadListener(), null);
 
     return req;
   },
 
-  getScreenshot: function(_width, _height, _mimeType) {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
-
+  _getScreenshot: function(_width, _height, _mimeType) {
     let width = parseInt(_width);
     let height = parseInt(_height);
     let mimeType = (typeof _mimeType === 'string') ?
       _mimeType.trim().toLowerCase() : 'image/jpeg';
     if (isNaN(width) || isNaN(height) || width < 0 || height < 0) {
       throw Components.Exception("Invalid argument",
                                  Cr.NS_ERROR_INVALID_ARG);
     }
@@ -772,46 +809,42 @@ BrowserElementParent.prototype = {
                                  mimeType: mimeType});
   },
 
   _recvNextPaint: function(data) {
     let listeners = this._nextPaintListeners;
     this._nextPaintListeners = [];
     for (let listener of listeners) {
       try {
-        listener.recvNextPaint();
+        listener();
       } catch (e) {
         // If a listener throws we'll continue.
       }
     }
   },
 
-  addNextPaintListener: function(listener) {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
+  _addNextPaintListener: function(listener) {
+    if (typeof listener != 'function')
+      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
 
     let self = this;
     let run = function() {
       if (self._nextPaintListeners.push(listener) == 1)
         self._sendAsyncMsg('activate-next-paint-listener');
     };
     if (!this._domRequestReady) {
       this._pendingAPICalls.push(run);
     } else {
       run();
     }
   },
 
-  removeNextPaintListener: function(listener) {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
+  _removeNextPaintListener: function(listener) {
+    if (typeof listener != 'function')
+      throw Components.Exception("Invalid argument", Cr.NS_ERROR_INVALID_ARG);
 
     let self = this;
     let run = function() {
       for (let i = self._nextPaintListeners.length - 1; i >= 0; i--) {
         if (self._nextPaintListeners[i] == listener) {
           self._nextPaintListeners.splice(i, 1);
           break;
         }
@@ -822,22 +855,17 @@ BrowserElementParent.prototype = {
     };
     if (!this._domRequestReady) {
       this._pendingAPICalls.push(run);
     } else {
       run();
     }
   },
 
-  setInputMethodActive: function(isActive) {
-    if (!this._isAlive()) {
-      throw Components.Exception("Dead content process",
-                                 Cr.NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
-
+  _setInputMethodActive: function(isActive) {
     if (typeof isActive !== 'boolean') {
       throw Components.Exception("Invalid argument",
                                  Cr.NS_ERROR_INVALID_ARG);
     }
 
     return this._sendDOMRequest('set-input-method-active',
                                 {isActive: isActive});
   },
@@ -889,25 +917,31 @@ BrowserElementParent.prototype = {
     case 'oop-frameloader-crashed':
       if (this._isAlive() && subject == this._frameLoader) {
         this._fireFatalError();
       }
       break;
     case 'ask-children-to-exit-fullscreen':
       if (this._isAlive() &&
           this._frameElement.ownerDocument == subject &&
-          this._frameLoader.QueryInterface(Ci.nsIFrameLoader).tabParent) {
+          this._hasRemoteFrame) {
         this._sendAsyncMsg('exit-fullscreen');
       }
       break;
+    case 'remote-browser-frame-shown':
+      if (this._frameLoader == subject) {
+        if (!this._mm) {
+          this._setupMessageListener();
+          this._registerAppManifest();
+        }
+        Services.obs.removeObserver(this, 'remote-browser-frame-shown');
+      }
     case 'copypaste-docommand':
       if (this._isAlive() && this._frameElement.isEqualNode(subject.wrappedJSObject)) {
         this._sendAsyncMsg('do-command', { command: data });
       }
       break;
     default:
       debug('Unknown topic: ' + topic);
       break;
     };
   },
 };
-
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([BrowserElementParent]);
--- a/dom/browser-element/BrowserElementParent.manifest
+++ b/dom/browser-element/BrowserElementParent.manifest
@@ -1,2 +1,3 @@
-component {9f171ac4-0939-4ef8-b360-3408aedc3060} BrowserElementParent.js
-contract @mozilla.org/dom/browser-element-api;1 {9f171ac4-0939-4ef8-b360-3408aedc3060}
+component {ddeafdac-cb39-47c4-9cb8-c9027ee36d26} BrowserElementParent.js
+contract @mozilla.org/browser-element-parent-factory;1 {ddeafdac-cb39-47c4-9cb8-c9027ee36d26}
+category app-startup BrowserElementParentFactory service,@mozilla.org/browser-element-parent-factory;1
--- a/dom/browser-element/moz.build
+++ b/dom/browser-element/moz.build
@@ -7,28 +7,23 @@
 EXPORTS.mozilla += [
     'BrowserElementParent.h',
 ]
 
 SOURCES += [
     'BrowserElementParent.cpp',
 ]
 
-XPIDL_SOURCES += [
-    'nsIBrowserElementAPI.idl',
-]
-
-XPIDL_MODULE = 'browser-element'
-
 EXTRA_COMPONENTS += [
     'BrowserElementParent.js',
     'BrowserElementParent.manifest',
 ]
 
 EXTRA_JS_MODULES += [
+    'BrowserElementParent.jsm',
     'BrowserElementPromptService.jsm',
 ]
 
 FAIL_ON_WARNINGS = True
 
 LOCAL_INCLUDES += [
     '../bluetooth',
     '/dom/html',
deleted file mode 100644
--- a/dom/browser-element/nsIBrowserElementAPI.idl
+++ /dev/null
@@ -1,74 +0,0 @@
-/* -*- Mode: IDL; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsISupports.idl"
-
-interface nsIDOMDOMRequest;
-interface nsIFrameLoader;
-
-[scriptable, function, uuid(c0c2dd9b-41ef-42dd-a4c1-e456619c1941)]
-interface nsIBrowserElementNextPaintListener : nsISupports
-{
-  void recvNextPaint();
-};
-
-%{C++
-#define BROWSER_ELEMENT_API_CONTRACTID "@mozilla.org/dom/browser-element-api;1"
-#define BROWSER_ELEMENT_API_CID                                 \
-    { 0x651db7e3, 0x1734, 0x4536,                               \
-      { 0xb1, 0x5a, 0x5b, 0x3a, 0xe6, 0x44, 0x13, 0x4c } }
-%}
-
-/**
- * Interface to the BrowserElementParent implementation. All methods
- * but setFrameLoader throw when the remote process is dead.
- */
-[scriptable, uuid(abae4fb1-7d6f-4e3f-b435-6501f1d4c659)]
-interface nsIBrowserElementAPI : nsISupports
-{
-  void setFrameLoader(in nsIFrameLoader frameLoader);
-
-  void setVisible(in boolean visible);
-  nsIDOMDOMRequest getVisible();
-  void setActive(in boolean active);
-  boolean getActive();
-
-  void sendMouseEvent(in DOMString type,
-                      in uint32_t x,
-                      in uint32_t y,
-                      in uint32_t button,
-                      in uint32_t clickCount,
-                      in uint32_t mifiers);
-  void sendTouchEvent(in DOMString aType,
-                      [const, array, size_is(count)] in uint32_t aIdentifiers,
-                      [const, array, size_is(count)] in int32_t aXs,
-                      [const, array, size_is(count)] in int32_t aYs,
-                      [const, array, size_is(count)] in uint32_t aRxs,
-                      [const, array, size_is(count)] in uint32_t aRys,
-                      [const, array, size_is(count)] in float aRotationAngles,
-                      [const, array, size_is(count)] in float aForces,
-                      in uint32_t count,
-                      in long aModifiers);
-  void goBack();
-  void goForward();
-  void reload(in boolean hardReload);
-  void stop();
-  nsIDOMDOMRequest download(in DOMString url,
-                            [optional] in jsval options);
-  nsIDOMDOMRequest purgeHistory();
-  nsIDOMDOMRequest getScreenshot(in uint32_t width,
-                                 in uint32_t height,
-                                 [optional] in DOMString mimeType);
-  void zoom(in float zoom);
-  nsIDOMDOMRequest getCanGoBack();
-  nsIDOMDOMRequest getCanGoForward();
-  nsIDOMDOMRequest getContentDimensions();
-
-  void addNextPaintListener(in nsIBrowserElementNextPaintListener listener);
-  void removeNextPaintListener(in nsIBrowserElementNextPaintListener listener);
-
-  nsIDOMDOMRequest setInputMethodActive(in boolean isActive);
-};
--- a/dom/canvas/WebGL1Context.cpp
+++ b/dom/canvas/WebGL1Context.cpp
@@ -1,50 +1,51 @@
 /* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGL1Context.h"
+
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
-
 #include "mozilla/Telemetry.h"
 
-using namespace mozilla;
+namespace mozilla {
 
-// -----------------------------------------------------------------------------
-// CONSTRUCTOR & DESTRUCTOR
+/*static*/ WebGL1Context*
+WebGL1Context::Create()
+{
+    return new WebGL1Context();
+}
 
 WebGL1Context::WebGL1Context()
     : WebGLContext()
 {
-
 }
 
 WebGL1Context::~WebGL1Context()
 {
-
 }
 
-
-// -----------------------------------------------------------------------------
-// IMPLEMENT nsWrapperCache
+////////////////////////////////////////
+// nsWrapperCache
 
 JSObject*
-WebGL1Context::WrapObject(JSContext *cx)
+WebGL1Context::WrapObject(JSContext* cx)
 {
     return dom::WebGLRenderingContextBinding::Wrap(cx, this);
 }
 
+} // namespace mozilla
 
-// -----------------------------------------------------------------------------
-// INSTANCING nsIDOMWebGLRenderingContext
+////////////////////////////////////////
+// nsIDOMWebGLRenderingContext
 
 nsresult
-NS_NewCanvasRenderingContextWebGL(nsIDOMWebGLRenderingContext** aResult)
+NS_NewCanvasRenderingContextWebGL(nsIDOMWebGLRenderingContext** out_result)
 {
-    Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
-    nsIDOMWebGLRenderingContext* ctx = new WebGL1Context();
+    mozilla::Telemetry::Accumulate(mozilla::Telemetry::CANVAS_WEBGL_USED, 1);
 
-    NS_ADDREF(*aResult = ctx);
+    nsIDOMWebGLRenderingContext* ctx = mozilla::WebGL1Context::Create();
+
+    NS_ADDREF(*out_result = ctx);
     return NS_OK;
 }
-
--- a/dom/canvas/WebGL1Context.h
+++ b/dom/canvas/WebGL1Context.h
@@ -1,46 +1,35 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef WEBGL1CONTEXT_H_
-#define WEBGL1CONTEXT_H_
+#ifndef WEBGL_1_CONTEXT_H_
+#define WEBGL_1_CONTEXT_H_
 
 #include "WebGLContext.h"
 
 namespace mozilla {
 
 class WebGL1Context
     : public WebGLContext
 {
-// -----------------------------------------------------------------------------
-// PUBLIC
 public:
+    static WebGL1Context* Create();
 
-    // -------------------------------------------------------------------------
-    // CONSTRUCTOR & DESTRUCTOR
+private:
+    WebGL1Context();
 
-    WebGL1Context();
+public:
     virtual ~WebGL1Context();
 
-
-    // -------------------------------------------------------------------------
-    // IMPLEMENT WebGLContext
-
-    virtual bool IsWebGL2() const MOZ_OVERRIDE
-    {
+    virtual bool IsWebGL2() const MOZ_OVERRIDE {
         return false;
     }
 
-
-    // -------------------------------------------------------------------------
-    // IMPLEMENT nsWrapperCache
-
-    virtual JSObject* WrapObject(JSContext *cx) MOZ_OVERRIDE;
-
-
+    // nsWrapperCache
+    virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
 };
 
 } // namespace mozilla
 
-#endif
+#endif // WEBGL_1_CONTEXT_H_
--- a/dom/canvas/WebGL2ContextSamplers.cpp
+++ b/dom/canvas/WebGL2ContextSamplers.cpp
@@ -1,78 +1,218 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGL2Context.h"
+#include "WebGLSampler.h"
 #include "GLContext.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 already_AddRefed<WebGLSampler>
 WebGL2Context::CreateSampler()
 {
-    MOZ_CRASH("Not Implemented.");
-    return nullptr;
+    if (IsContextLost())
+        return nullptr;
+
+    GLuint sampler;
+    MakeContextCurrent();
+    gl->fGenSamplers(1, &sampler);
+
+    nsRefPtr<WebGLSampler> globj = new WebGLSampler(this, sampler);
+    return globj.forget();
 }
 
 void
 WebGL2Context::DeleteSampler(WebGLSampler* sampler)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    if (!ValidateObjectAllowDeletedOrNull("deleteSampler", sampler))
+        return;
+
+    if (!sampler || sampler->IsDeleted())
+        return;
+
+    sampler->RequestDelete();
 }
 
 bool
 WebGL2Context::IsSampler(WebGLSampler* sampler)
 {
-    MOZ_CRASH("Not Implemented.");
-    return false;
+    if (IsContextLost())
+        return false;
+
+    if (!sampler)
+        return false;
+
+    if (!ValidateObjectAllowDeleted("isSampler", sampler))
+        return false;
+
+    if (sampler->IsDeleted())
+        return false;
+
+    return !sampler->HasEverBeenBound();
 }
 
 void
 WebGL2Context::BindSampler(GLuint unit, WebGLSampler* sampler)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    if (!ValidateObjectAllowDeletedOrNull("bindSampler", sampler))
+        return;
+
+    if (GLint(unit) >= mGLMaxTextureUnits)
+        return ErrorInvalidValue("bindSampler: unit must be < %d", mGLMaxTextureUnits);
+
+    if (sampler && sampler->IsDeleted())
+        return ErrorInvalidOperation("bindSampler: binding deleted sampler");
+
+    WebGLContextUnchecked::BindSampler(unit, sampler);
 }
 
 void
 WebGL2Context::SamplerParameteri(WebGLSampler* sampler, GLenum pname, GLint param)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    if (!sampler || sampler->IsDeleted())
+        return ErrorInvalidOperation("samplerParameteri: invalid sampler");
+
+    if (!ValidateSamplerParameterParams(pname, WebGLIntOrFloat(param), "samplerParameteri"))
+        return;
+
+    WebGLContextUnchecked::SamplerParameteri(sampler, pname, param);
 }
 
 void
 WebGL2Context::SamplerParameteriv(WebGLSampler* sampler, GLenum pname, const dom::Int32Array& param)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    if (!sampler || sampler->IsDeleted())
+        return ErrorInvalidOperation("samplerParameteriv: invalid sampler");
+
+    param.ComputeLengthAndData();
+    if (param.Length() < 1)
+        return /* TODO(djg): Error message */;
+
+    /* TODO(djg): All of these calls in ES3 only take 1 param */
+    if (!ValidateSamplerParameterParams(pname, WebGLIntOrFloat(param.Data()[0]), "samplerParameteriv"))
+        return;
+
+    WebGLContextUnchecked::SamplerParameteriv(sampler, pname, param.Data());
 }
 
 void
 WebGL2Context::SamplerParameteriv(WebGLSampler* sampler, GLenum pname, const dom::Sequence<GLint>& param)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    if (!sampler || sampler->IsDeleted())
+        return ErrorInvalidOperation("samplerParameteriv: invalid sampler");
+
+    if (param.Length() < 1)
+        return /* TODO(djg): Error message */;
+
+    /* TODO(djg): All of these calls in ES3 only take 1 param */
+    if (!ValidateSamplerParameterParams(pname, WebGLIntOrFloat(param[0]), "samplerParameteriv"))
+        return;
+
+    WebGLContextUnchecked::SamplerParameteriv(sampler, pname, param.Elements());
 }
 
 void
 WebGL2Context::SamplerParameterf(WebGLSampler* sampler, GLenum pname, GLfloat param)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    if (!sampler || sampler->IsDeleted())
+        return ErrorInvalidOperation("samplerParameterf: invalid sampler");
+
+    if (!ValidateSamplerParameterParams(pname, WebGLIntOrFloat(param), "samplerParameterf"))
+        return;
+
+    WebGLContextUnchecked::SamplerParameterf(sampler, pname, param);
 }
 
 void
 WebGL2Context::SamplerParameterfv(WebGLSampler* sampler, GLenum pname, const dom::Float32Array& param)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    if (!sampler || sampler->IsDeleted())
+        return ErrorInvalidOperation("samplerParameterfv: invalid sampler");
+
+    param.ComputeLengthAndData();
+    if (param.Length() < 1)
+        return /* TODO(djg): Error message */;
+
+    /* TODO(djg): All of these calls in ES3 only take 1 param */
+    if (!ValidateSamplerParameterParams(pname, WebGLIntOrFloat(param.Data()[0]), "samplerParameterfv"))
+        return;
+
+    WebGLContextUnchecked::SamplerParameterfv(sampler, pname, param.Data());
 }
 
 void
 WebGL2Context::SamplerParameterfv(WebGLSampler* sampler, GLenum pname, const dom::Sequence<GLfloat>& param)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    if (!sampler || sampler->IsDeleted())
+        return ErrorInvalidOperation("samplerParameterfv: invalid sampler");
+
+    if (param.Length() < 1)
+        return /* TODO(djg): Error message */;
+
+    /* TODO(djg): All of these calls in ES3 only take 1 param */
+    if (!ValidateSamplerParameterParams(pname, WebGLIntOrFloat(param[0]), "samplerParameterfv"))
+        return;
+
+    WebGLContextUnchecked::SamplerParameterfv(sampler, pname, param.Elements());
 }
 
 void
 WebGL2Context::GetSamplerParameter(JSContext*, WebGLSampler* sampler, GLenum pname, JS::MutableHandleValue retval)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (IsContextLost())
+        return;
+
+    if (!sampler || sampler->IsDeleted())
+        return ErrorInvalidOperation("getSamplerParameter: invalid sampler");
+
+    if (!ValidateSamplerParameterName(pname, "getSamplerParameter"))
+        return;
+
+    retval.set(JS::NullValue());
+
+    switch (pname) {
+    case LOCAL_GL_TEXTURE_MIN_FILTER:
+    case LOCAL_GL_TEXTURE_MAG_FILTER:
+    case LOCAL_GL_TEXTURE_WRAP_S:
+    case LOCAL_GL_TEXTURE_WRAP_T:
+    case LOCAL_GL_TEXTURE_WRAP_R:
+    case LOCAL_GL_TEXTURE_COMPARE_MODE:
+    case LOCAL_GL_TEXTURE_COMPARE_FUNC:
+        retval.set(JS::Int32Value(
+            WebGLContextUnchecked::GetSamplerParameteriv(sampler, pname)));
+        return;
+
+    case LOCAL_GL_TEXTURE_MIN_LOD:
+    case LOCAL_GL_TEXTURE_MAX_LOD:
+        retval.set(JS::Float32Value(
+            WebGLContextUnchecked::GetSamplerParameterfv(sampler, pname)));
+        return;
+    }
 }
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -2,25 +2,26 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGLContext.h"
 
 #include "WebGLContextLossHandler.h"
 #include "WebGL1Context.h"
-#include "WebGLObjectModel.h"
+#include "WebGLBuffer.h"
+#include "WebGLContextUtils.h"
 #include "WebGLExtensions.h"
-#include "WebGLContextUtils.h"
-#include "WebGLBuffer.h"
+#include "WebGLFramebuffer.h"
+#include "WebGLMemoryTracker.h"
+#include "WebGLObjectModel.h"
+#include "WebGLQuery.h"
+#include "WebGLSampler.h"
+#include "WebGLVertexArray.h"
 #include "WebGLVertexAttribData.h"
-#include "WebGLMemoryTracker.h"
-#include "WebGLFramebuffer.h"
-#include "WebGLVertexArray.h"
-#include "WebGLQuery.h"
 
 #include "GLBlitHelper.h"
 #include "AccessCheck.h"
 #include "nsIConsoleService.h"
 #include "nsServiceManagerUtils.h"
 #include "nsIClassInfoImpl.h"
 #include "nsContentUtils.h"
 #include "nsIXPConnect.h"
@@ -221,17 +222,17 @@ WebGLContextOptions::WebGLContextOptions
       preserveDrawingBuffer(false)
 {
     // Set default alpha state based on preference.
     if (Preferences::GetBool("webgl.default-no-alpha", false))
         alpha = false;
 }
 
 WebGLContext::WebGLContext()
-    : gl(nullptr)
+    : WebGLContextUnchecked(nullptr)
     , mNeedsFakeNoAlpha(false)
 {
     mGeneration = 0;
     mInvalidated = false;
     mShouldPresent = true;
     mResetLayer = true;
     mOptionsFrozen = false;
 
@@ -366,16 +367,18 @@ WebGLContext::DestroyResourcesAndContext
     while (!mFramebuffers.isEmpty())
         mFramebuffers.getLast()->DeleteOnce();
     while (!mShaders.isEmpty())
         mShaders.getLast()->DeleteOnce();
     while (!mPrograms.isEmpty())
         mPrograms.getLast()->DeleteOnce();
     while (!mQueries.isEmpty())
         mQueries.getLast()->DeleteOnce();
+    while (!mSamplers.isEmpty())
+        mSamplers.getLast()->DeleteOnce();
 
     mBlackOpaqueTexture2D = nullptr;
     mBlackOpaqueTextureCubeMap = nullptr;
     mBlackTransparentTexture2D = nullptr;
     mBlackTransparentTextureCubeMap = nullptr;
 
     if (mFakeVertexAttrib0BufferObject) {
         gl->fDeleteBuffers(1, &mFakeVertexAttrib0BufferObject);
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -10,16 +10,17 @@
 #include "mozilla/CheckedInt.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WeakPtr.h"
 
 #include "GLDefs.h"
 #include "WebGLActiveInfo.h"
+#include "WebGLContextUnchecked.h"
 #include "WebGLObjectModel.h"
 #include "WebGLRenderbuffer.h"
 #include "WebGLTexture.h"
 #include "WebGLStrongTypes.h"
 #include <stdarg.h>
 
 #include "nsTArray.h"
 #include "nsCycleCollectionNoteChild.h"
@@ -73,20 +74,22 @@ class WebGLExtensionBase;
 class WebGLBuffer;
 struct WebGLVertexAttribData;
 class WebGLShader;
 class WebGLProgram;
 class WebGLQuery;
 class WebGLUniformLocation;
 class WebGLFramebuffer;
 class WebGLRenderbuffer;
+class WebGLSampler;
 class WebGLShaderPrecisionFormat;
 class WebGLTexture;
 class WebGLVertexArray;
 
+
 namespace dom {
 class ImageData;
 class Element;
 
 struct WebGLContextAttributes;
 template<typename> struct Nullable;
 }
 
@@ -122,20 +125,39 @@ struct WebGLContextOptions {
     bool premultipliedAlpha;
     bool antialias;
     bool preserveDrawingBuffer;
 };
 
 // From WebGLContextUtils
 TexTarget TexImageTargetToTexTarget(TexImageTarget texImageTarget);
 
+class WebGLIntOrFloat {
+    enum {
+        Int,
+        Float
+    } mType;
+    union {
+        GLint i;
+        GLfloat f;
+    } mValue;
+
+public:
+    explicit WebGLIntOrFloat(GLint i) : mType(Int) { mValue.i = i; }
+    explicit WebGLIntOrFloat(GLfloat f) : mType(Float) { mValue.f = f; }
+
+    GLint AsInt() const { return (mType == Int) ? mValue.i : NS_lroundf(mValue.f); }
+    GLfloat AsFloat() const { return (mType == Float) ? mValue.f : GLfloat(mValue.i); }
+};
+
 class WebGLContext :
     public nsIDOMWebGLRenderingContext,
     public nsICanvasRenderingContextInternal,
     public nsSupportsWeakReference,
+    public WebGLContextUnchecked,
     public WebGLRectangleObject,
     public nsWrapperCache,
     public SupportsWeakPtr<WebGLContext>
 {
     friend class WebGLContextUserData;
     friend class WebGLExtensionCompressedTextureATC;
     friend class WebGLExtensionCompressedTextureETC1;
     friend class WebGLExtensionCompressedTexturePVRTC;
@@ -1000,18 +1022,16 @@ protected:
 
     virtual JS::Value GetTexParameterInternal(const TexTarget& target, GLenum pname);
 
     // Returns x rounded to the next highest multiple of y.
     static CheckedUint32 RoundedToNextMultipleOf(CheckedUint32 x, CheckedUint32 y) {
         return ((x + y - 1) / y) * y;
     }
 
-    nsRefPtr<gl::GLContext> gl;
-
     CheckedUint32 mGeneration;
 
     WebGLContextOptions mOptions;
 
     bool mInvalidated;
     bool mResetLayer;
     bool mOptionsFrozen;
     bool mMinCapability;
@@ -1133,16 +1153,20 @@ protected:
 
     bool ValidateGLSLVariableName(const nsAString& name, const char *info);
     bool ValidateGLSLCharacter(char16_t c);
     bool ValidateGLSLString(const nsAString& string, const char *info);
 
     bool ValidateCopyTexImage(GLenum internalformat,
                               WebGLTexImageFunc func,
                               WebGLTexDimensions dims);
+
+    bool ValidateSamplerParameterName(GLenum pname, const char* info);
+    bool ValidateSamplerParameterParams(GLenum pname, const WebGLIntOrFloat& param, const char* info);
+
     bool ValidateTexImage(TexImageTarget texImageTarget,
                           GLint level, GLenum internalFormat,
                           GLint xoffset, GLint yoffset, GLint zoffset,
                           GLint width, GLint height, GLint depth,
                           GLint border, GLenum format, GLenum type,
                           WebGLTexImageFunc func,
                           WebGLTexDimensions dims);
     bool ValidateTexImageTarget(GLenum target,
@@ -1320,16 +1344,19 @@ protected:
     LinkedList<WebGLBuffer> mBuffers;
     LinkedList<WebGLProgram> mPrograms;
     LinkedList<WebGLQuery> mQueries;
     LinkedList<WebGLShader> mShaders;
     LinkedList<WebGLRenderbuffer> mRenderbuffers;
     LinkedList<WebGLFramebuffer> mFramebuffers;
     LinkedList<WebGLVertexArray> mVertexArrays;
 
+    // TODO(djg): Does this need a rethink? Should it be WebGL2Context?
+    LinkedList<WebGLSampler> mSamplers;
+
     WebGLRefPtr<WebGLVertexArray> mDefaultVertexArray;
 
     // PixelStore parameters
     uint32_t mPixelStorePackAlignment, mPixelStoreUnpackAlignment, mPixelStoreColorspaceConversion;
     bool mPixelStoreFlipY, mPixelStorePremultiplyAlpha;
 
     WebGLContextFakeBlackStatus mFakeBlackStatus;
 
@@ -1435,16 +1462,17 @@ public:
     void GenerateWarning(const char *fmt, va_list ap);
 
     friend class WebGLTexture;
     friend class WebGLFramebuffer;
     friend class WebGLRenderbuffer;
     friend class WebGLProgram;
     friend class WebGLQuery;
     friend class WebGLBuffer;
+    friend class WebGLSampler;
     friend class WebGLShader;
     friend class WebGLUniformLocation;
     friend class WebGLVertexArray;
     friend class WebGLVertexArrayFake;
     friend class WebGLVertexArrayGL;
 };
 
 // used by DOM bindings in conjunction with GetParentObject
new file mode 100644
--- /dev/null
+++ b/dom/canvas/WebGLContextUnchecked.cpp
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WebGLContextUnchecked.h"
+
+#include "GLContext.h"
+#include "WebGLSampler.h"
+
+namespace mozilla {
+
+WebGLContextUnchecked::WebGLContextUnchecked(gl::GLContext* gl)
+    : gl(gl)
+{ }
+
+
+// -----------------------------------------------------------------------------
+// Sampler Objects
+
+void
+WebGLContextUnchecked::BindSampler(GLuint unit, WebGLSampler* sampler)
+{
+    gl->MakeCurrent();
+    gl->fBindSampler(unit, sampler ? sampler->GLName() : 0);
+    if (sampler)
+        sampler->BindTo(LOCAL_GL_SAMPLER_BINDING);
+}
+
+GLint
+WebGLContextUnchecked::GetSamplerParameteriv(WebGLSampler* sampler,
+                                             GLenum pname)
+{
+    MOZ_ASSERT(sampler, "Did you validate?");
+
+    GLint param = 0;
+    gl->MakeCurrent();
+    gl->fGetSamplerParameteriv(sampler->GLName(), pname, &param);
+
+    return param;
+}
+
+GLfloat
+WebGLContextUnchecked::GetSamplerParameterfv(WebGLSampler* sampler,
+                                             GLenum pname)
+{
+    MOZ_ASSERT(sampler, "Did you validate?");
+
+    GLfloat param = 0.0f;
+    gl->MakeCurrent();
+    gl->fGetSamplerParameterfv(sampler->GLName(), pname, &param);
+    return param;
+}
+
+void
+WebGLContextUnchecked::SamplerParameteri(WebGLSampler* sampler,
+                                         GLenum pname,
+                                         GLint param)
+{
+    MOZ_ASSERT(sampler, "Did you validate?");
+    gl->MakeCurrent();
+    gl->fSamplerParameteri(sampler->GLName(), pname, param);
+}
+
+void
+WebGLContextUnchecked::SamplerParameteriv(WebGLSampler* sampler,
+                                          GLenum pname,
+                                          const GLint* param)
+{
+    MOZ_ASSERT(sampler, "Did you validate?");
+    gl->MakeCurrent();
+    gl->fSamplerParameteriv(sampler->GLName(), pname, param);
+}
+
+void
+WebGLContextUnchecked::SamplerParameterf(WebGLSampler* sampler,
+                                         GLenum pname,
+                                         GLfloat param)
+{
+    MOZ_ASSERT(sampler, "Did you validate?");
+    gl->MakeCurrent();
+    gl->fSamplerParameterf(sampler->GLName(), pname, param);
+}
+
+void
+WebGLContextUnchecked::SamplerParameterfv(WebGLSampler* sampler,
+                                          GLenum pname,
+                                          const GLfloat* param)
+{
+    MOZ_ASSERT(sampler, "Did you validate?");
+    gl->MakeCurrent();
+    gl->fSamplerParameterfv(sampler->GLName(), pname, param);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/canvas/WebGLContextUnchecked.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WEBGLCONTEXTUNCHECKED_H
+#define WEBGLCONTEXTUNCHECKED_H
+
+#include "GLDefs.h"
+#include "WebGLTypes.h"
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+
+class WebGLSampler;
+namespace gl {
+    class GLContext;
+}
+
+class WebGLContextUnchecked
+{
+public:
+    explicit WebGLContextUnchecked(gl::GLContext* gl);
+
+    // -------------------------------------------------------------------------
+    // Sampler Objects
+    void BindSampler(GLuint unit, WebGLSampler* sampler);
+
+    GLint   GetSamplerParameteriv(WebGLSampler* sampler, GLenum pname);
+    GLfloat GetSamplerParameterfv(WebGLSampler* sampler, GLenum pname);
+
+    void SamplerParameteri(WebGLSampler* sampler, GLenum pname, GLint param);
+    void SamplerParameteriv(WebGLSampler* sampler, GLenum pname, const GLint* param);
+    void SamplerParameterf(WebGLSampler* sampler, GLenum pname, GLfloat param);
+    void SamplerParameterfv(WebGLSampler* sampler, GLenum pname, const GLfloat* param);
+
+protected: // data
+    nsRefPtr<gl::GLContext> gl;
+};
+
+} // namespace mozilla
+
+#endif // !WEBGLCONTEXTUNCHECKED_H
--- a/dom/canvas/WebGLContextValidate.cpp
+++ b/dom/canvas/WebGLContextValidate.cpp
@@ -374,16 +374,128 @@ WebGLContext::ValidateFramebufferAttachm
         return true;
     }
 
     ErrorInvalidEnum("%s: attachment: invalid enum value 0x%x.", funcName, attachment);
     return false;
 }
 
 /**
+ * Return true if pname is valid for GetSamplerParameter calls.
+ */
+bool
+WebGLContext::ValidateSamplerParameterName(GLenum pname, const char* info)
+{
+    switch (pname) {
+    case LOCAL_GL_TEXTURE_MIN_FILTER:
+    case LOCAL_GL_TEXTURE_MAG_FILTER:
+    case LOCAL_GL_TEXTURE_WRAP_S:
+    case LOCAL_GL_TEXTURE_WRAP_T:
+    case LOCAL_GL_TEXTURE_WRAP_R:
+    case LOCAL_GL_TEXTURE_MIN_LOD:
+    case LOCAL_GL_TEXTURE_MAX_LOD:
+    case LOCAL_GL_TEXTURE_COMPARE_MODE:
+    case LOCAL_GL_TEXTURE_COMPARE_FUNC:
+        return true;
+
+    default:
+        ErrorInvalidEnum("%s: invalid pname: %s", info, EnumName(pname));
+        return false;
+    }
+}
+
+/**
+ * Return true if pname and param are valid combination for SamplerParameter calls.
+ */
+bool
+WebGLContext::ValidateSamplerParameterParams(GLenum pname, const WebGLIntOrFloat& param, const char* info)
+{
+    const GLenum p = param.AsInt();
+
+    switch (pname) {
+    case LOCAL_GL_TEXTURE_MIN_FILTER:
+        switch (p) {
+        case LOCAL_GL_NEAREST:
+        case LOCAL_GL_LINEAR:
+        case LOCAL_GL_NEAREST_MIPMAP_NEAREST:
+        case LOCAL_GL_NEAREST_MIPMAP_LINEAR:
+        case LOCAL_GL_LINEAR_MIPMAP_NEAREST:
+        case LOCAL_GL_LINEAR_MIPMAP_LINEAR:
+            return true;
+
+        default:
+            ErrorInvalidEnum("%s: invalid param: %s", info, EnumName(p));
+            return false;
+        }
+
+    case LOCAL_GL_TEXTURE_MAG_FILTER:
+        switch (p) {
+        case LOCAL_GL_NEAREST:
+        case LOCAL_GL_LINEAR:
+            return true;
+
+        default:
+            ErrorInvalidEnum("%s: invalid param: %s", info, EnumName(p));
+            return false;
+        }
+
+    case LOCAL_GL_TEXTURE_WRAP_S:
+    case LOCAL_GL_TEXTURE_WRAP_T:
+    case LOCAL_GL_TEXTURE_WRAP_R:
+        switch (p) {
+        case LOCAL_GL_CLAMP_TO_EDGE:
+        case LOCAL_GL_REPEAT:
+        case LOCAL_GL_MIRRORED_REPEAT:
+            return true;
+
+        default:
+            ErrorInvalidEnum("%s: invalid param: %s", info, EnumName(p));
+            return false;
+        }
+
+    case LOCAL_GL_TEXTURE_MIN_LOD:
+    case LOCAL_GL_TEXTURE_MAX_LOD:
+        return true;
+
+    case LOCAL_GL_TEXTURE_COMPARE_MODE:
+        switch (param.AsInt()) {
+        case LOCAL_GL_NONE:
+        case LOCAL_GL_COMPARE_REF_TO_TEXTURE:
+            return true;
+
+        default:
+            ErrorInvalidEnum("%s: invalid param: %s", info, EnumName(p));
+            return false;
+        }
+
+    case LOCAL_GL_TEXTURE_COMPARE_FUNC:
+        switch (p) {
+        case LOCAL_GL_LEQUAL:
+        case LOCAL_GL_GEQUAL:
+        case LOCAL_GL_LESS:
+        case LOCAL_GL_GREATER:
+        case LOCAL_GL_EQUAL:
+        case LOCAL_GL_NOTEQUAL:
+        case LOCAL_GL_ALWAYS:
+        case LOCAL_GL_NEVER:
+            return true;
+
+        default:
+            ErrorInvalidEnum("%s: invalid param: %s", info, EnumName(p));
+            return false;
+        }
+
+    default:
+        ErrorInvalidEnum("%s: invalid pname: %s", info, EnumName(pname));
+        return false;
+    }
+}
+
+
+/**
  * Return true if format is a valid texture image format for source,
  * taking into account enabled WebGL extensions.
  */
 bool
 WebGLContext::ValidateTexImageFormat(GLenum format, WebGLTexImageFunc func, WebGLTexDimensions dims)
 {
     /* Core WebGL texture formats */
     if (format == LOCAL_GL_ALPHA ||
@@ -1713,18 +1825,17 @@ WebGLContext::InitAndValidateGL()
         // Note that this used to cause crashes on old ATI drivers... hopefully not a significant
         // problem anymore. See bug 602183.
         gl->fEnable(LOCAL_GL_POINT_SPRITE);
     }
 
 #ifdef XP_MACOSX
     if (gl->WorkAroundDriverBugs() &&
         gl->Vendor() == gl::GLVendor::ATI &&
-        nsCocoaFeatures::OSXVersionMajor() == 10 &&
-        nsCocoaFeatures::OSXVersionMinor() < 9)
+        !nsCocoaFeatures::IsAtLeastVersion(10,9))
     {
         // The Mac ATI driver, in all known OSX version up to and including 10.8,
         // renders points sprites upside-down. Apple bug 11778921
         gl->fPointParameterf(LOCAL_GL_POINT_SPRITE_COORD_ORIGIN, LOCAL_GL_LOWER_LEFT);
     }
 #endif
 
     // Check the shader validator pref
--- a/dom/canvas/WebGLFramebuffer.cpp
+++ b/dom/canvas/WebGLFramebuffer.cpp
@@ -368,17 +368,17 @@ WebGLFramebuffer::Attachment::FinalizeAt
                                          LOCAL_GL_RENDERBUFFER, 0);
         }
 
         return;
     }
     MOZ_ASSERT(HasImage());
 
     if (Texture()) {
-        MOZ_ASSERT(gl == Texture()->Context()->gl);
+        MOZ_ASSERT(gl == Texture()->Context()->GL());
 
         const GLenum imageTarget = ImageTarget().get();
         const GLint mipLevel = MipLevel();
         const GLuint glName = Texture()->GLName();
 
         if (attachmentLoc == LOCAL_GL_DEPTH_STENCIL_ATTACHMENT) {
             gl->fFramebufferTexture2D(LOCAL_GL_FRAMEBUFFER, LOCAL_GL_DEPTH_ATTACHMENT,
                                       imageTarget, glName, mipLevel);
--- a/dom/canvas/WebGLSampler.cpp
+++ b/dom/canvas/WebGLSampler.cpp
@@ -7,42 +7,45 @@
 #include "WebGLSampler.h"
 
 #include "GLContext.h"
 
 #include "mozilla/dom/WebGL2RenderingContextBinding.h"
 
 using namespace mozilla;
 
-WebGLSampler::WebGLSampler(WebGLContext* context)
-    : WebGLBindableName<GLenum>(0),
+WebGLSampler::WebGLSampler(WebGLContext* context, GLuint sampler)
+    : WebGLBindableName<GLenum>(sampler),
       WebGLContextBoundObject(context)
 {
-    MOZ_CRASH("Not Implemented.");
+    mContext->mSamplers.insertBack(this);
 }
 
 WebGLSampler::~WebGLSampler()
-{}
+{
+    DeleteOnce();
+}
 
 void
 WebGLSampler::Delete()
 {
-    MOZ_CRASH("Not Implemented.");
+    mContext->MakeContextCurrent();
+    mContext->gl->fDeleteSamplers(1, &mGLName);
+
+    removeFrom(mContext->mSamplers);
 }
 
 WebGLContext*
 WebGLSampler::GetParentObject() const
 {
-    MOZ_CRASH("Not Implemented.");
-    return nullptr;
+    return Context();
 }
 
 JSObject*
 WebGLSampler::WrapObject(JSContext* cx)
 {
-    MOZ_CRASH("Not Implemented.");
     return dom::WebGLSamplerBinding::Wrap(cx, this);
 }
 
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(WebGLSampler)
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(WebGLSampler, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(WebGLSampler, Release)
--- a/dom/canvas/WebGLSampler.h
+++ b/dom/canvas/WebGLSampler.h
@@ -1,15 +1,15 @@
 /* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#ifndef WEBGL2SAMPLER_H_
-#define WEBGL2SAMPLER_H_
+#ifndef WEBGLSAMPLER_H_
+#define WEBGLSAMPLER_H_
 
 #include "WebGLBindableName.h"
 #include "WebGLObjectModel.h"
 
 #include "nsWrapperCache.h"
 
 #include "mozilla/LinkedList.h"
 
@@ -21,26 +21,28 @@ class WebGLSampler MOZ_FINAL
     , public WebGLRefCountedObject<WebGLSampler>
     , public LinkedListElement<WebGLSampler>
     , public WebGLContextBoundObject
 {
     friend class WebGLContext2;
 
 public:
 
-    explicit WebGLSampler(WebGLContext* aContext);
+    explicit WebGLSampler(WebGLContext* aContext, GLuint sampler);
 
     void Delete();
     WebGLContext* GetParentObject() const;
 
     virtual JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
 
+private:
+
     NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(WebGLSampler)
     NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_NATIVE_CLASS(WebGLSampler)
 
 private:
 
     ~WebGLSampler();
 };
 
 } // namespace mozilla
 
-#endif // !WEBGL2SAMPLER_H_
+#endif // !WEBGLSAMPLER_H_
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -59,16 +59,17 @@ UNIFIED_SOURCES += [
     'WebGLContextBuffers.cpp',
     'WebGLContextDraw.cpp',
     'WebGLContextExtensions.cpp',
     'WebGLContextFramebufferOperations.cpp',
     'WebGLContextGL.cpp',
     'WebGLContextLossHandler.cpp',
     'WebGLContextReporter.cpp',
     'WebGLContextState.cpp',
+    'WebGLContextUnchecked.cpp',
     'WebGLContextUtils.cpp',
     'WebGLContextValidate.cpp',
     'WebGLContextVertexArray.cpp',
     'WebGLContextVertices.cpp',
     'WebGLElementArrayCache.cpp',
     'WebGLExtensionBase.cpp',
     'WebGLExtensionBlendMinMax.cpp',
     'WebGLExtensionColorBufferFloat.cpp',
--- a/dom/canvas/test/webgl-mochitest/test_webgl_request_mismatch.html
+++ b/dom/canvas/test/webgl-mochitest/test_webgl_request_mismatch.html
@@ -1,35 +1,90 @@
 <!DOCTYPE HTML>
-<title>WebGL test: Mismatched 'webgl' and 'experimental-webgl' context requests</title>
+<html>
+<head>
 <script src="/tests/SimpleTest/SimpleTest.js"></script>
 <link rel="stylesheet" href="/tests/SimpleTest/test.css">
+</head>
 <body>
-<canvas id="c1"></canvas>
-<canvas id="c2"></canvas>
-<canvas id="c3"></canvas>
-<canvas id="c4"></canvas>
 <script>
 
-function testContextRetrieval(canvasId, creationId, requestId) {
-  var canvas = document.getElementById(canvasId);
-  ok(canvas, 'Invalid `canvasId`: ' + canvasId);
+WEBGL_TYPES = {};
+WEBGL_TYPES['experimental-webgl'] = true;
+WEBGL_TYPES['webgl'] = true;
 
-  var createdGL = canvas.getContext(creationId);
-  if (!createdGL)
-    return; // No WebGL on this machine?
+function AreBothIn(a, b, set) {
+    return (a in set) && (b in set);
+}
 
-  var requestedGL = canvas.getContext(requestId);
-  if (creationId == requestId) {
-    ok(requestedGL, 'Request for \'' + requestId + '\' on from \'' + creationId + '\' should succeed.');
-    ok(requestedGL == createdGL, 'Request for \'' + requestId + '\' on from \'' + creationId + '\' should match.');
-  } else {
-    ok(!requestedGL, 'Request for \'' + requestId + '\' on from \'' + creationId + '\' should fail.');
-  }
+function IsAlias(typeA, typeB) {
+    if (typeA == typeB)
+        return true;
+
+    if (AreBothIn(typeA, typeB, WEBGL_TYPES))
+        return true;
+
+    return false;
 }
 
-testContextRetrieval('c1', 'experimental-webgl', 'webgl');
-testContextRetrieval('c2', 'webgl', 'experimental-webgl');
-testContextRetrieval('c3', 'experimental-webgl', 'experimental-webgl');
-testContextRetrieval('c4', 'webgl', 'webgl');
+function TestContextRetrieval(creationType, requestType, functionalTypeSet) {
+    var canvas = document.createElement('canvas');
+    var createdGL = canvas.getContext(creationType);
+
+    var didCreationSucceed = (createdGL != null);
+    if (creationType in functionalTypeSet) {
+        ok(createdGL, 'Context creation should succeed for type \'' +
+                      creationType + '\'');
+    } else {
+        ok(!createdGL, 'Context creation should fail for type \'' +
+                       creationType + '\'');
+        return;
+    }
+
+    var requestedGL = canvas.getContext(requestType);
+
+    if (requestType in functionalTypeSet &&
+        IsAlias(creationType, requestType))
+    {
+        ok(requestedGL, 'Request for \'' + requestType + '\' from \'' +
+                        creationType + '\' should succeed.');
+        ok(requestedGL == createdGL, 'Request for \'' + requestType +
+                                     '\' from \'' + creationType +
+                                     '\' should match.');
+    } else {
+        ok(!requestedGL, 'Request for \'' + requestType + '\' from \'' +
+                         creationType + '\' should fail.');
+    }
+}
+
+function IsWebGLFunctional() {
+    var canvas = document.createElement('canvas');
+    return canvas.getContext('experimental-webgl') != null;
+}
+
+function IsWebGLConformant() {
+    var canvas = document.createElement('canvas');
+    return canvas.getContext('webgl') != null;
+}
+
+var typeList = ['2d', 'experimental-webgl', 'webgl'];
+var functionalTypeSet = {};
+functionalTypeSet['2d'] = true;
+
+if (IsWebGLFunctional())
+    functionalTypeSet['experimental-webgl'] = true;
+
+if (IsWebGLConformant())
+    functionalTypeSet['webgl'] = true;
+
+for (var i in typeList) {
+    var creationType = typeList[i];
+
+    for (var j in typeList) {
+        var requestType = typeList[j];
+
+        TestContextRetrieval(creationType, requestType, functionalTypeSet);
+    }
+}
 
 </script>
-
+</body>
+</html>
--- a/dom/html/HTMLCanvasElement.cpp
+++ b/dom/html/HTMLCanvasElement.cpp
@@ -29,16 +29,17 @@
 #include "nsIWritablePropertyBag2.h"
 #include "nsIXPConnect.h"
 #include "nsJSUtils.h"
 #include "nsLayoutUtils.h"
 #include "nsMathUtils.h"
 #include "nsNetUtil.h"
 #include "nsStreamUtils.h"
 #include "ActiveLayerTracker.h"
+#include "WebGL1Context.h"
 #include "WebGL2Context.h"
 
 using namespace mozilla::layers;
 using namespace mozilla::gfx;
 
 NS_IMPL_NS_NEW_HTML_ELEMENT(Canvas)
 
 namespace {
@@ -643,147 +644,131 @@ HTMLCanvasElement::MozGetAsFileImpl(cons
   nsRefPtr<File> file =
     File::CreateMemoryFile(win, imgData, (uint32_t)imgSize, aName, type,
                            PR_Now());
 
   file.forget(aResult);
   return NS_OK;
 }
 
-nsresult
-HTMLCanvasElement::GetContextHelper(const nsAString& aContextId,
-                                    nsICanvasRenderingContextInternal **aContext)
+static bool
+GetCanvasContextType(const nsAString& str, CanvasContextType* const out_type)
 {
-  NS_ENSURE_ARG(aContext);
+  if (str.EqualsLiteral("2d")) {
+    *out_type = CanvasContextType::Canvas2D;
+    return true;
+  }
 
-  if (aContextId.EqualsLiteral("2d")) {
-    Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1);
-    nsRefPtr<CanvasRenderingContext2D> ctx =
-      new CanvasRenderingContext2D();
-
-    ctx->SetCanvasElement(this);
-    ctx.forget(aContext);
-    return NS_OK;
+  if (str.EqualsLiteral("experimental-webgl")) {
+    *out_type = CanvasContextType::WebGL1;
+    return true;
   }
 
-  if (WebGL2Context::IsSupported() &&
-      aContextId.EqualsLiteral("experimental-webgl2"))
-  {
-    Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
-    nsRefPtr<WebGL2Context> ctx = WebGL2Context::Create();
-
-    if (ctx == nullptr) {
-      return NS_ERROR_NOT_IMPLEMENTED;
-    }
-
-    ctx->SetCanvasElement(this);
-    ctx.forget(aContext);
-    return NS_OK;
+#ifdef MOZ_WEBGL_CONFORMANT
+  if (str.EqualsLiteral("webgl")) {
+    /* WebGL 1.0, $2.1 "Context Creation":
+     *   If the user agent supports both the webgl and experimental-webgl
+     *   canvas context types, they shall be treated as aliases.
+     */
+    *out_type = CanvasContextType::WebGL1;
+    return true;
   }
-
-  NS_ConvertUTF16toUTF8 ctxId(aContextId);
+#endif
 
-  // check that ctxId is clamped to A-Za-z0-9_-
-  for (uint32_t i = 0; i < ctxId.Length(); i++) {
-    if ((ctxId[i] < 'A' || ctxId[i] > 'Z') &&
-        (ctxId[i] < 'a' || ctxId[i] > 'z') &&
-        (ctxId[i] < '0' || ctxId[i] > '9') &&
-        (ctxId[i] != '-') &&
-        (ctxId[i] != '_'))
-    {
-      // XXX ERRMSG we need to report an error to developers here! (bug 329026)
-      return NS_OK;
+  if (WebGL2Context::IsSupported()) {
+    if (str.EqualsLiteral("experimental-webgl2")) {
+      *out_type = CanvasContextType::WebGL2;
+      return true;
     }
   }
 
-  nsCString ctxString("@mozilla.org/content/canvas-rendering-context;1?id=");
-  ctxString.Append(ctxId);
+  return false;
+}
+
+static already_AddRefed<nsICanvasRenderingContextInternal>
+CreateContextForCanvas(CanvasContextType contextType, HTMLCanvasElement* canvas)
+{
+  nsRefPtr<nsICanvasRenderingContextInternal> ret;
+
+  switch (contextType) {
+  case CanvasContextType::Canvas2D:
+    Telemetry::Accumulate(Telemetry::CANVAS_2D_USED, 1);
+    ret = new CanvasRenderingContext2D();
+    break;
 
-  nsresult rv;
-  nsCOMPtr<nsICanvasRenderingContextInternal> ctx =
-    do_CreateInstance(ctxString.get(), &rv);
-  if (rv == NS_ERROR_OUT_OF_MEMORY) {
-    *aContext = nullptr;
-    return NS_ERROR_OUT_OF_MEMORY;
+  case CanvasContextType::WebGL1:
+    Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
+
+    ret = WebGL1Context::Create();
+    if (!ret)
+      return nullptr;
+    break;
+
+  case CanvasContextType::WebGL2:
+    Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);
+
+    ret = WebGL2Context::Create();
+    if (!ret)
+      return nullptr;
+    break;
   }
-  if (NS_FAILED(rv)) {
-    *aContext = nullptr;
-    // XXX ERRMSG we need to report an error to developers here! (bug 329026)
-    return NS_OK;
-  }
+  MOZ_ASSERT(ret);
 
-  ctx->SetCanvasElement(this);
-  ctx.forget(aContext);
-  return NS_OK;
+  ret->SetCanvasElement(canvas);
+  return ret.forget();
 }
 
 nsresult
 HTMLCanvasElement::GetContext(const nsAString& aContextId,
                               nsISupports** aContext)
 {
   ErrorResult rv;
-  *aContext =
-    GetContext(nullptr, aContextId, JS::NullHandleValue, rv).take();
+  *aContext = GetContext(nullptr, aContextId, JS::NullHandleValue, rv).take();
   return rv.ErrorCode();
 }
 
-static bool
-IsContextIdWebGL(const nsAString& str)
-{
-  return str.EqualsLiteral("webgl") ||
-         str.EqualsLiteral("experimental-webgl");
-}
-
 already_AddRefed<nsISupports>
 HTMLCanvasElement::GetContext(JSContext* aCx,
                               const nsAString& aContextId,
                               JS::Handle<JS::Value> aContextOptions,
                               ErrorResult& rv)
 {
-  if (mCurrentContextId.IsEmpty()) {
-    rv = GetContextHelper(aContextId, getter_AddRefs(mCurrentContext));
-    if (rv.Failed() || !mCurrentContext) {
+  CanvasContextType contextType;
+  if (!GetCanvasContextType(aContextId, &contextType))
+    return nullptr;
+
+  if (!mCurrentContext) {
+    // This canvas doesn't have a context yet.
+
+    nsRefPtr<nsICanvasRenderingContextInternal> context;
+    context = CreateContextForCanvas(contextType, this);
+    if (!context)
       return nullptr;
-    }
 
     // Ensure that the context participates in CC.  Note that returning a
     // CC participant from QI doesn't addref.
-    nsXPCOMCycleCollectionParticipant *cp = nullptr;
-    CallQueryInterface(mCurrentContext, &cp);
+    nsXPCOMCycleCollectionParticipant* cp = nullptr;
+    CallQueryInterface(context, &cp);
     if (!cp) {
-      mCurrentContext = nullptr;
       rv.Throw(NS_ERROR_FAILURE);
       return nullptr;
     }
 
+    mCurrentContext = context.forget();
+    mCurrentContextType = contextType;
+
     rv = UpdateContext(aCx, aContextOptions);
     if (rv.Failed()) {
       rv = NS_OK; // See bug 645792
       return nullptr;
     }
-    mCurrentContextId.Assign(aContextId);
-  }
-
-  if (!mCurrentContextId.Equals(aContextId)) {
-    if (IsContextIdWebGL(aContextId) &&
-        IsContextIdWebGL(mCurrentContextId))
-    {
-      // Warn when we get a request for a webgl context with an id that differs
-      // from the id it was created with.
-      nsCString creationId = NS_LossyConvertUTF16toASCII(mCurrentContextId);
-      nsCString requestId = NS_LossyConvertUTF16toASCII(aContextId);
-      JS_ReportWarning(aCx, "WebGL: Retrieving a WebGL context from a canvas "
-                            "via a request id ('%s') different from the id used "
-                            "to create the context ('%s') is not allowed.",
-                            requestId.get(),
-                            creationId.get());
-    }
-    
-    //XXX eventually allow for more than one active context on a given canvas
-    return nullptr;
+  } else {
+    // We already have a context of some type.
+    if (contextType != mCurrentContextType)
+      return nullptr;
   }
 
   nsCOMPtr<nsICanvasRenderingContextInternal> context = mCurrentContext;
   return context.forget();
 }
 
 NS_IMETHODIMP
 HTMLCanvasElement::MozGetIPCContext(const nsAString& aContextId,
@@ -793,32 +778,38 @@ HTMLCanvasElement::MozGetIPCContext(cons
     // XXX ERRMSG we need to report an error to developers here! (bug 329026)
     return NS_ERROR_DOM_SECURITY_ERR;
   }
 
   // We only support 2d shmem contexts for now.
   if (!aContextId.EqualsLiteral("2d"))
     return NS_ERROR_INVALID_ARG;
 
-  if (mCurrentContextId.IsEmpty()) {
-    nsresult rv = GetContextHelper(aContextId, getter_AddRefs(mCurrentContext));
-    NS_ENSURE_SUCCESS(rv, rv);
-    if (!mCurrentContext) {
+  CanvasContextType contextType = CanvasContextType::Canvas2D;
+
+  if (!mCurrentContext) {
+    // This canvas doesn't have a context yet.
+
+    nsRefPtr<nsICanvasRenderingContextInternal> context;
+    context = CreateContextForCanvas(contextType, this);
+    if (!context) {
+      *aContext = nullptr;
       return NS_OK;
     }
 
+    mCurrentContext = context;
     mCurrentContext->SetIsIPC(true);
-
-    rv = UpdateContext(nullptr, JS::NullHandleValue);
-    NS_ENSURE_SUCCESS(rv, rv);
+    mCurrentContextType = contextType;
 
-    mCurrentContextId.Assign(aContextId);
-  } else if (!mCurrentContextId.Equals(aContextId)) {
-    //XXX eventually allow for more than one active context on a given canvas
-    return NS_ERROR_INVALID_ARG;
+    nsresult rv = UpdateContext(nullptr, JS::NullHandleValue);
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    // We already have a context of some type.
+    if (contextType != mCurrentContextType)
+      return NS_ERROR_INVALID_ARG;
   }
 
   NS_ADDREF (*aContext = mCurrentContext);
   return NS_OK;
 }
 
 nsresult
 HTMLCanvasElement::UpdateContext(JSContext* aCx, JS::Handle<JS::Value> aNewContextOptions)
@@ -826,31 +817,28 @@ HTMLCanvasElement::UpdateContext(JSConte
   if (!mCurrentContext)
     return NS_OK;
 
   nsIntSize sz = GetWidthHeight();
 
   nsresult rv = mCurrentContext->SetIsOpaque(HasAttr(kNameSpaceID_None, nsGkAtoms::moz_opaque));
   if (NS_FAILED(rv)) {
     mCurrentContext = nullptr;
-    mCurrentContextId.Truncate();
     return rv;
   }
 
   rv = mCurrentContext->SetContextOptions(aCx, aNewContextOptions);
   if (NS_FAILED(rv)) {
     mCurrentContext = nullptr;
-    mCurrentContextId.Truncate();
     return rv;
   }
 
   rv = mCurrentContext->SetDimensions(sz.width, sz.height);
   if (NS_FAILED(rv)) {
     mCurrentContext = nullptr;
-    mCurrentContextId.Truncate();
     return rv;
   }
 
   return rv;
 }
 
 nsIntSize
 HTMLCanvasElement::GetSize()
--- a/dom/html/HTMLCanvasElement.h
+++ b/dom/html/HTMLCanvasElement.h
@@ -2,16 +2,17 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 #if !defined(mozilla_dom_HTMLCanvasElement_h)
 #define mozilla_dom_HTMLCanvasElement_h
 
 #include "mozilla/Attributes.h"
+#include "mozilla/TypedEnum.h"
 #include "nsIDOMHTMLCanvasElement.h"
 #include "nsGenericHTMLElement.h"
 #include "nsGkAtoms.h"
 #include "nsSize.h"
 #include "nsError.h"
 
 #include "mozilla/gfx/Rect.h"
 
@@ -30,16 +31,22 @@ class SourceSurface;
 
 namespace dom {
 
 class File;
 class FileCallback;
 class HTMLCanvasPrintState;
 class PrintCallback;
 
+MOZ_BEGIN_ENUM_CLASS(CanvasContextType, uint8_t)
+  Canvas2D,
+  WebGL1,
+  WebGL2
+MOZ_END_ENUM_CLASS(CanvasContextType)
+
 class HTMLCanvasElement MOZ_FINAL : public nsGenericHTMLElement,
                                     public nsIDOMHTMLCanvasElement
 {
   enum {
     DEFAULT_CANVAS_WIDTH = 300,
     DEFAULT_CANVAS_HEIGHT = 150
   };
 
@@ -224,21 +231,19 @@ protected:
                        nsIInputStream** aStream);
   nsresult ToDataURLImpl(JSContext* aCx,
                          const nsAString& aMimeType,
                          const JS::Value& aEncoderOptions,
                          nsAString& aDataURL);
   nsresult MozGetAsFileImpl(const nsAString& aName,
                             const nsAString& aType,
                             nsIDOMFile** aResult);
-  nsresult GetContextHelper(const nsAString& aContextId,
-                            nsICanvasRenderingContextInternal **aContext);
   void CallPrintCallback();
 
-  nsString mCurrentContextId;
+  CanvasContextType mCurrentContextType;
   nsRefPtr<HTMLCanvasElement> mOriginalCanvas;
   nsRefPtr<PrintCallback> mPrintCallback;
   nsCOMPtr<nsICanvasRenderingContextInternal> mCurrentContext;
   nsRefPtr<HTMLCanvasPrintState> mPrintState;
 
 public:
   // Record whether this canvas should be write-only or not.
   // We set this when script paints an image from a different origin.
--- a/dom/html/ImageDocument.cpp
+++ b/dom/html/ImageDocument.cpp
@@ -454,70 +454,48 @@ ImageDocument::DOMToggleImageSize()
 }
 
 NS_IMETHODIMP
 ImageDocument::Notify(imgIRequest* aRequest, int32_t aType, const nsIntRect* aData)
 {
   if (aType == imgINotificationObserver::SIZE_AVAILABLE) {
     nsCOMPtr<imgIContainer> image;
     aRequest->GetImage(getter_AddRefs(image));
-    return OnStartContainer(aRequest, image);
+    return OnSizeAvailable(aRequest, image);
   }
 
-  // Do these two off a script runner because decode complete notifications often
-  // come during painting and these will trigger invalidation.
-  if (aType == imgINotificationObserver::DECODE_COMPLETE) {
+  // Run this using a script runner because HAS_TRANSPARENCY notifications can
+  // come during painting and this will trigger invalidation.
+  if (aType == imgINotificationObserver::HAS_TRANSPARENCY) {
     nsCOMPtr<nsIRunnable> runnable =
-      NS_NewRunnableMethod(this, &ImageDocument::AddDecodedClass);
-    nsContentUtils::AddScriptRunner(runnable);
-  }
-
-  if (aType == imgINotificationObserver::DISCARD) {
-    nsCOMPtr<nsIRunnable> runnable =
-      NS_NewRunnableMethod(this, &ImageDocument::RemoveDecodedClass);
+      NS_NewRunnableMethod(this, &ImageDocument::OnHasTransparency);
     nsContentUtils::AddScriptRunner(runnable);
   }
 
   if (aType == imgINotificationObserver::LOAD_COMPLETE) {
     uint32_t reqStatus;
     aRequest->GetImageStatus(&reqStatus);
     nsresult status =
         reqStatus & imgIRequest::STATUS_ERROR ? NS_ERROR_FAILURE : NS_OK;
-    return OnStopRequest(aRequest, status);
+    return OnLoadComplete(aRequest, status);
   }
 
   return NS_OK;
 }
 
 void
-ImageDocument::AddDecodedClass()
+ImageDocument::OnHasTransparency()
 {
   if (!mImageContent || nsContentUtils::IsChildOfSameType(this)) {
     return;
   }
 
   nsDOMTokenList* classList = mImageContent->AsElement()->ClassList();
   mozilla::ErrorResult rv;
-  // Update the background-color of the image only after the
-  // image has been decoded to prevent flashes of just the
-  // background-color.
-  classList->Add(NS_LITERAL_STRING("decoded"), rv);
-}
-
-void
-ImageDocument::RemoveDecodedClass()
-{
-  if (!mImageContent || nsContentUtils::IsChildOfSameType(this)) {
-    return;
-  }
-
-  nsDOMTokenList* classList = mImageContent->AsElement()->ClassList();
-  mozilla::ErrorResult rv;
-  // Remove any decoded-related styling when the image is unloaded.
-  classList->Remove(NS_LITERAL_STRING("decoded"), rv);
+  classList->Add(NS_LITERAL_STRING("transparent"), rv);
 }
 
 void
 ImageDocument::SetModeClass(eModeClasses mode)
 {
   nsDOMTokenList* classList = mImageContent->AsElement()->ClassList();
   mozilla::ErrorResult rv;
 
@@ -530,34 +508,33 @@ ImageDocument::SetModeClass(eModeClasses
   if (mode == eOverflowing) {
     classList->Add(NS_LITERAL_STRING("overflowing"), rv);
   } else {
     classList->Remove(NS_LITERAL_STRING("overflowing"), rv);
   }
 }
 
 nsresult
-ImageDocument::OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage)
+ImageDocument::OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage)
 {
   // Styles have not yet been applied, so we don't know the final size. For now,
   // default to the image's intrinsic size.
   aImage->GetWidth(&mImageWidth);
   aImage->GetHeight(&mImageHeight);
 
   nsCOMPtr<nsIRunnable> runnable =
     NS_NewRunnableMethod(this, &ImageDocument::DefaultCheckOverflowing);
   nsContentUtils::AddScriptRunner(runnable);
   UpdateTitleAndCharset();
 
   return NS_OK;
 }
 
 nsresult
-ImageDocument::OnStopRequest(imgIRequest *aRequest,
-                             nsresult aStatus)
+ImageDocument::OnLoadComplete(imgIRequest* aRequest, nsresult aStatus)
 {
   UpdateTitleAndCharset();
 
   // mImageContent can be null if the document is already destroyed
   if (NS_FAILED(aStatus) && mStringBundle && mImageContent) {
     nsAutoCString src;
     mDocumentURI->GetSpec(src);
     NS_ConvertUTF8toUTF16 srcString(src);
--- a/dom/html/ImageDocument.h
+++ b/dom/html/ImageDocument.h
@@ -46,19 +46,16 @@ public:
   NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent) MOZ_OVERRIDE;
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(ImageDocument, MediaDocument)
 
   friend class ImageListener;
 
   void DefaultCheckOverflowing() { CheckOverflowing(mResizeImageByDefault); }
 
-  void AddDecodedClass();
-  void RemoveDecodedClass();
-
   // WebIDL API
   virtual JSObject* WrapNode(JSContext* aCx)
     MOZ_OVERRIDE;
 
   bool ImageResizingEnabled() const
   {
     return true;
   }
@@ -102,18 +99,19 @@ protected:
 
   enum eModeClasses {
     eNone,
     eShrinkToFit,
     eOverflowing
   };
   void SetModeClass(eModeClasses mode);
 
-  nsresult OnStartContainer(imgIRequest* aRequest, imgIContainer* aImage);
-  nsresult OnStopRequest(imgIRequest *aRequest, nsresult aStatus);
+  nsresult OnSizeAvailable(imgIRequest* aRequest, imgIContainer* aImage);
+  nsresult OnLoadComplete(imgIRequest* aRequest, nsresult aStatus);
+  void OnHasTransparency();
 
   nsCOMPtr<nsIContent>          mImageContent;
 
   float                         mVisibleWidth;
   float                         mVisibleHeight;
   int32_t                       mImageWidth;
   int32_t                       mImageHeight;
 
--- a/dom/html/moz.build
+++ b/dom/html/moz.build
@@ -107,17 +107,16 @@ EXPORTS.mozilla.dom += [
     'HTMLTextAreaElement.h',
     'HTMLTimeElement.h',
     'HTMLTitleElement.h',
     'HTMLTrackElement.h',
     'HTMLUnknownElement.h',
     'HTMLVideoElement.h',
     'ImageDocument.h',
     'MediaError.h',
-    'nsBrowserElement.h',
     'RadioNodeList.h',
     'TextTrackManager.h',
     'TimeRanges.h',
     'UndoManager.h',
     'ValidityState.h',
 ]
 
 UNIFIED_SOURCES += [
@@ -186,17 +185,16 @@ UNIFIED_SOURCES += [
     'HTMLTimeElement.cpp',
     'HTMLTitleElement.cpp',
     'HTMLTrackElement.cpp',
     'HTMLUnknownElement.cpp',
     'HTMLVideoElement.cpp',
     'ImageDocument.cpp',
     'MediaDocument.cpp',
     'MediaError.cpp',
-    'nsBrowserElement.cpp',
     'nsDOMStringMap.cpp',
     'nsFormSubmission.cpp',
     'nsGenericHTMLElement.cpp',
     'nsGenericHTMLFrameElement.cpp',
     'nsHTMLContentSink.cpp',
     'nsHTMLDNSPrefetch.cpp',
     'nsHTMLDocument.cpp',
     'nsIConstraintValidation.cpp',
deleted file mode 100644
--- a/dom/html/nsBrowserElement.cpp
+++ /dev/null
@@ -1,545 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsBrowserElement.h"
-
-#include "mozilla/Preferences.h"
-#include "mozilla/Services.h"
-#include "mozilla/dom/BrowserElementBinding.h"
-#include "mozilla/dom/DOMRequest.h"
-#include "mozilla/dom/ScriptSettings.h"
-#include "mozilla/dom/ToJSValue.h"
-
-#include "nsComponentManagerUtils.h"
-#include "nsContentUtils.h"
-#include "nsFrameLoader.h"
-#include "nsIDOMDOMRequest.h"
-#include "nsIDOMElement.h"
-#include "nsINode.h"
-#include "nsIObserver.h"
-#include "nsIObserverService.h"
-#include "nsIPrincipal.h"
-#include "nsWeakReference.h"
-
-using namespace mozilla::dom;
-
-namespace mozilla {
-
-static const char kRemoteBrowserPending[] = "remote-browser-pending";
-static const char kInprocessBrowserShown[] = "inprocess-browser-shown";
-
-class nsBrowserElement::BrowserShownObserver : public nsIObserver
-                                             , public nsSupportsWeakReference
-{
-public:
-  BrowserShownObserver(nsBrowserElement* aBrowserElement);
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-  void AddObserver();
-  void RemoveObserver();
-private:
-  virtual ~BrowserShownObserver();
-
-  // Weak reference to the browser element. nsBrowserElement has a
-  // reference to us. nsBrowserElement's destructor is responsible to
-  // null out this weak reference via RemoveObserver()
-  nsBrowserElement* mBrowserElement;
-};
-
-NS_IMPL_ISUPPORTS(nsBrowserElement::BrowserShownObserver, nsIObserver, nsISupportsWeakReference)
-
-nsBrowserElement::BrowserShownObserver::BrowserShownObserver(nsBrowserElement* aBrowserElement)
-  : mBrowserElement(aBrowserElement)
-{
-}
-
-nsBrowserElement::BrowserShownObserver::~BrowserShownObserver()
-{
-  RemoveObserver();
-}
-
-NS_IMETHODIMP
-nsBrowserElement::BrowserShownObserver::Observe(nsISupports* aSubject,
-                                                const char* aTopic,
-                                                const char16_t* aData)
-{
-  NS_ENSURE_TRUE(mBrowserElement, NS_OK);
-
-  if (!strcmp(aTopic, kRemoteBrowserPending) ||
-      !strcmp(aTopic, kInprocessBrowserShown)) {
-    nsCOMPtr<nsIFrameLoader> frameLoader = do_QueryInterface(aSubject);
-    nsCOMPtr<nsIFrameLoader> myFrameLoader = mBrowserElement->GetFrameLoader();
-    // The browser element API needs the frameloader to
-    // initialize. We still use the observer to get notified when the
-    // frameloader is created. So we check if the frameloader created
-    // is ours, then initialize the browser element API.
-    if (frameLoader && frameLoader == myFrameLoader) {
-      mBrowserElement->InitBrowserElementAPI();
-    }
-  }
-  return NS_OK;
-}
-
-void
-nsBrowserElement::BrowserShownObserver::AddObserver()
-{
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  if (obs) {
-    obs->AddObserver(this, kRemoteBrowserPending, true);
-    obs->AddObserver(this, kInprocessBrowserShown, true);
-  }
-}
-
-void
-nsBrowserElement::BrowserShownObserver::RemoveObserver()
-{
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  if (obs) {
-    obs->RemoveObserver(this, kRemoteBrowserPending);
-    obs->RemoveObserver(this, kInprocessBrowserShown);
-  }
-  mBrowserElement = nullptr;
-}
-
-bool
-nsBrowserElement::IsBrowserElementOrThrow(ErrorResult& aRv)
-{
-  if (mBrowserElementAPI) {
-    return true;
-  }
-  aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
-  return false;
-}
-
-bool
-nsBrowserElement::IsNotWidgetOrThrow(ErrorResult& aRv)
-{
-  if (!mOwnerIsWidget) {
-    return true;
-  }
-  aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
-  return false;
-}
-
-void
-nsBrowserElement::InitBrowserElementAPI()
-{
-  bool isBrowserOrApp;
-  nsCOMPtr<nsIFrameLoader> frameLoader = GetFrameLoader();
-  NS_ENSURE_TRUE_VOID(frameLoader);
-  nsresult rv = frameLoader->GetOwnerIsBrowserOrAppFrame(&isBrowserOrApp);
-  NS_ENSURE_SUCCESS_VOID(rv);
-  rv = frameLoader->GetOwnerIsWidget(&mOwnerIsWidget);
-  NS_ENSURE_SUCCESS_VOID(rv);
-
-  if (!isBrowserOrApp) {
-    return;
-  }
-
-  mBrowserElementAPI = do_CreateInstance("@mozilla.org/dom/browser-element-api;1");
-  if (mBrowserElementAPI) {
-    mBrowserElementAPI->SetFrameLoader(frameLoader);
-  }
-}
-
-nsBrowserElement::nsBrowserElement()
-  : mOwnerIsWidget(false)
-{
-  mObserver = new BrowserShownObserver(this);
-  mObserver->AddObserver();
-}
-
-nsBrowserElement::~nsBrowserElement()
-{
-  mObserver->RemoveObserver();
-}
-
-void
-nsBrowserElement::SetVisible(bool aVisible, ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
-
-  nsresult rv = mBrowserElementAPI->SetVisible(aVisible);
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-  }
-}
-
-already_AddRefed<DOMRequest>
-nsBrowserElement::GetVisible(ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
-
-  nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv = mBrowserElementAPI->GetVisible(getter_AddRefs(req));
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return nullptr;
-  }
-
-  return req.forget().downcast<DOMRequest>();
-}
-
-void
-nsBrowserElement::SetActive(bool aVisible, ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
-
-  nsresult rv = mBrowserElementAPI->SetActive(aVisible);
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-  }
-}
-
-bool
-nsBrowserElement::GetActive(ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), false);
-
-  bool isActive;
-  nsresult rv = mBrowserElementAPI->GetActive(&isActive);
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return false;
-  }
-
-  return isActive;
-}
-
-void
-nsBrowserElement::SendMouseEvent(const nsAString& aType,
-                                 uint32_t aX,
-                                 uint32_t aY,
-                                 uint32_t aButton,
-                                 uint32_t aClickCount,
-                                 uint32_t aModifiers,
-                                 ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
-  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
-
-  nsresult rv = mBrowserElementAPI->SendMouseEvent(aType,
-                                                   aX,
-                                                   aY,
-                                                   aButton,
-                                                   aClickCount,
-                                                   aModifiers);
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-  }
-}
-
-void
-nsBrowserElement::SendTouchEvent(const nsAString& aType,
-                                 const Sequence<uint32_t>& aIdentifiers,
-                                 const Sequence<int32_t>& aXs,
-                                 const Sequence<int32_t>& aYs,
-                                 const Sequence<uint32_t>& aRxs,
-                                 const Sequence<uint32_t>& aRys,
-                                 const Sequence<float>& aRotationAngles,
-                                 const Sequence<float>& aForces,
-                                 uint32_t aCount,
-                                 uint32_t aModifiers,
-                                 ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
-  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
-
-  if (aIdentifiers.Length() != aCount ||
-      aXs.Length() != aCount ||
-      aYs.Length() != aCount ||
-      aRxs.Length() != aCount ||
-      aRys.Length() != aCount ||
-      aRotationAngles.Length() != aCount ||
-      aForces.Length() != aCount) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
-    return;
-  }
-
-  nsresult rv = mBrowserElementAPI->SendTouchEvent(aType,
-                                                   aIdentifiers.Elements(),
-                                                   aXs.Elements(),
-                                                   aYs.Elements(),
-                                                   aRxs.Elements(),
-                                                   aRys.Elements(),
-                                                   aRotationAngles.Elements(),
-                                                   aForces.Elements(),
-                                                   aCount,
-                                                   aModifiers);
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-  }
-}
-
-void
-nsBrowserElement::GoBack(ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
-  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
-
-  nsresult rv = mBrowserElementAPI->GoBack();
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-  }
-}
-
-void
-nsBrowserElement::GoForward(ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
-  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
-
-  nsresult rv = mBrowserElementAPI->GoForward();
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-  }
-}
-
-void
-nsBrowserElement::Reload(bool aHardReload, ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
-  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
-
-  nsresult rv = mBrowserElementAPI->Reload(aHardReload);
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-  }
-}
-
-void
-nsBrowserElement::Stop(ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
-  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
-
-  nsresult rv = mBrowserElementAPI->Stop();
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-  }
-}
-
-already_AddRefed<DOMRequest>
-nsBrowserElement::Download(const nsAString& aUrl,
-                           const BrowserElementDownloadOptions& aOptions,
-                           ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
-  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
-
-  nsCOMPtr<nsIDOMDOMRequest> req;
-  nsCOMPtr<nsIXPConnectWrappedJS> wrappedObj = do_QueryInterface(mBrowserElementAPI);
-  MOZ_ASSERT(wrappedObj, "Failed to get wrapped JS from XPCOM component.");
-  AutoJSAPI jsapi;
-  jsapi.Init(wrappedObj->GetJSObject());
-  JSContext* cx = jsapi.cx();
-  JS::Rooted<JS::Value> options(cx);
-  if (!ToJSValue(cx, aOptions, &options)) {
-    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
-    return nullptr;
-  }
-  nsresult rv = mBrowserElementAPI->Download(aUrl, options, getter_AddRefs(req));
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return nullptr;
-  }
-
-  return req.forget().downcast<DOMRequest>();
-}
-
-already_AddRefed<DOMRequest>
-nsBrowserElement::PurgeHistory(ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
-  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
-
-  nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv = mBrowserElementAPI->PurgeHistory(getter_AddRefs(req));
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return nullptr;
-  }
-
-  return req.forget().downcast<DOMRequest>();
-}
-
-already_AddRefed<DOMRequest>
-nsBrowserElement::GetScreenshot(uint32_t aWidth,
-                                uint32_t aHeight,
-                                const nsAString& aMimeType,
-                                ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
-  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
-
-  nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv = mBrowserElementAPI->GetScreenshot(aWidth, aHeight, aMimeType,
-                                                  getter_AddRefs(req));
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    if (rv == NS_ERROR_INVALID_ARG) {
-      aRv.Throw(NS_ERROR_DOM_INVALID_ACCESS_ERR);
-    } else {
-      aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    }
-    return nullptr;
-  }
-
-  return req.forget().downcast<DOMRequest>();
-}
-
-void
-nsBrowserElement::Zoom(float aZoom, ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
-  NS_ENSURE_TRUE_VOID(IsNotWidgetOrThrow(aRv));
-
-  nsresult rv = mBrowserElementAPI->Zoom(aZoom);
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-  }
-}
-
-already_AddRefed<DOMRequest>
-nsBrowserElement::GetCanGoBack(ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
-  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
-
-  nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv = mBrowserElementAPI->GetCanGoBack(getter_AddRefs(req));
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return nullptr;
-  }
-
-  return req.forget().downcast<DOMRequest>();
-}
-
-already_AddRefed<DOMRequest>
-nsBrowserElement::GetCanGoForward(ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
-  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
-
-  nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv = mBrowserElementAPI->GetCanGoForward(getter_AddRefs(req));
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return nullptr;
-  }
-
-  return req.forget().downcast<DOMRequest>();
-}
-
-already_AddRefed<DOMRequest>
-nsBrowserElement::GetContentDimensions(ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE(IsBrowserElementOrThrow(aRv), nullptr);
-  NS_ENSURE_TRUE(IsNotWidgetOrThrow(aRv), nullptr);
-
-  nsCOMPtr<nsIDOMDOMRequest> req;
-  nsresult rv = mBrowserElementAPI->GetContentDimensions(getter_AddRefs(req));
-
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
-    return nullptr;
-  }
-
-  return req.forget().downcast<DOMRequest>();
-}
-
-void
-nsBrowserElement::AddNextPaintListener(BrowserElementNextPaintEventCallback& aListener,
-                                       ErrorResult& aRv)
-{
-  NS_ENSURE_TRUE_VOID(IsBrowserElementOrThrow(aRv));
-
-  CallbackObjectHolder<BrowserElementNextPaintEventCallback,
-                       nsIBrowserElementNextPaintListener> holder(&aListener);
-  nsCOMPtr<nsIBrowserElementNextPaintListener> listener = holder.ToXPCOMCallback();
-