Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 12 Mar 2014 12:27:31 +0100
changeset 191390 f9d3af502654d59c4fc04c0e6bfeec848bc11439
parent 191389 6df586c17279231c5c8385a751767f4b2dcd4208 (current diff)
parent 191386 a1639dd9dd3dd7b77090039b55675086ddc3cbe6 (diff)
child 191391 22d46873261d68701f83a1bdea5d693622a2f2ba
push id474
push userasasaki@mozilla.com
push dateMon, 02 Jun 2014 21:01:02 +0000
treeherdermozilla-release@967f4cf1b31c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone30.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 mozilla-inbound
--- a/b2g/chrome/content/devtools.js
+++ b/b2g/chrome/content/devtools.js
@@ -35,16 +35,17 @@ XPCOMUtils.defineLazyGetter(this, 'Memor
  */
 let developerHUD = {
 
   _targets: new Map(),
   _frames: new Map(),
   _client: null,
   _webappsActor: null,
   _watchers: [],
+  _logging: true,
 
   /**
    * This method registers a metric watcher that will watch one or more metrics
    * of apps that are being tracked. A watcher must implement the trackApp(app)
    * and untrackApp(app) methods, add entries to the app.metrics map, keep them
    * up-to-date, and call app.display() when values were changed.
    */
   registerWatcher: function dwp_registerWatcher(watcher) {
@@ -80,16 +81,20 @@ let developerHUD = {
         this.trackFrame(systemapp);
 
         let frames = systemapp.contentWindow.document.querySelectorAll('iframe[mozapp]');
         for (let frame of frames) {
           this.trackFrame(frame);
         }
       });
     });
+
+    SettingsListener.observe('hud.logging', enabled => {
+      this._logging = enabled;
+    });
   },
 
   uninit: function dwp_uninit() {
     if (!this._client)
       return;
 
     for (let frame of this._targets.keys()) {
       this.untrackFrame(frame);
@@ -132,20 +137,17 @@ let developerHUD = {
 
   untrackFrame: function dwp_untrackFrame(frame) {
     let target = this._targets.get(frame);
     if (target) {
       for (let w of this._watchers) {
         w.untrackTarget(target);
       }
 
-      // Delete the metrics and call display() to clean up the front-end.
-      delete target.metrics;
-      target.display();
-
+      target.destroy();
       this._targets.delete(frame);
     }
   },
 
   observe: function dwp_observe(subject, topic, data) {
     if (!this._client)
       return;
 
@@ -178,17 +180,19 @@ let developerHUD = {
           return;
         this.untrackFrame(frame);
         this._frames.delete(mm);
         break;
     }
   },
 
   log: function dwp_log(message) {
-    dump(DEVELOPER_HUD_LOG_PREFIX + ': ' + message + '\n');
+    if (this._logging) {
+      dump(DEVELOPER_HUD_LOG_PREFIX + ': ' + message + '\n');
+    }
   }
 
 };
 
 
 /**
  * An App object represents all there is to know about a Firefox OS app that is
  * being tracked, e.g. its manifest information, current values of watched
@@ -196,32 +200,80 @@ let developerHUD = {
  */
 function Target(frame, actor) {
   this.frame = frame;
   this.actor = actor;
   this.metrics = new Map();
 }
 
 Target.prototype = {
-  display: function target_display() {
+
+  /**
+   * Register a metric that can later be updated. Does not update the front-end.
+   */
+  register: function target_register(metric) {
+    this.metrics.set(metric, 0);
+  },
+
+  /**
+   * Modify one of a target's metrics, and send out an event to notify relevant
+   * parties (e.g. the developer HUD, automated tests, etc).
+   */
+  update: function target_update(metric, value = 0, message) {
+    let metrics = this.metrics;
+    metrics.set(metric, value);
+
     let data = {
-      metrics: []
+      metrics: [], // FIXME(Bug 982066) Remove this field.
+      manifest: this.frame.appManifestURL,
+      metric: metric,
+      value: value,
+      message: message
     };
 
-    let metrics = this.metrics;
+    // FIXME(Bug 982066) Remove this loop.
     if (metrics && metrics.size > 0) {
       for (let name of metrics.keys()) {
         data.metrics.push({name: name, value: metrics.get(name)});
       }
     }
 
-    shell.sendEvent(this.frame, 'developer-hud-update', Cu.cloneInto(data, this.frame));
+    if (message) {
+      developerHUD.log('[' + data.manifest + '] ' + data.message);
+    }
+    this._send(data);
+  },
+
+  /**
+   * Nicer way to call update() when the metric value is a number that needs
+   * to be incremented.
+   */
+  bump: function target_bump(metric, message) {
+    this.update(metric, this.metrics.get(metric) + 1, message);
+  },
 
-    // FIXME(after bug 963239 lands) return event.isDefaultPrevented();
-    return false;
+  /**
+   * Void a metric value and make sure it isn't displayed on the front-end
+   * anymore.
+   */
+  clear: function target_clear(metric) {
+    this.update(metric, 0);
+  },
+
+  /**
+   * Tear everything down, including the front-end by sending a message without
+   * widgets.
+   */
+  destroy: function target_destroy() {
+    delete this.metrics;
+    this._send({});
+  },
+
+  _send: function target_send(data) {
+    shell.sendEvent(this.frame, 'developer-hud-update', Cu.cloneInto(data, this.frame));
   }
 
 };
 
 
 /**
  * The Console Watcher tracks the following metrics in apps: reflows, warnings,
  * and errors.
@@ -247,32 +299,31 @@ let consoleWatcher = {
       SettingsListener.observe('hud.' + metric, false, watch => {
         // Watch or unwatch the metric.
         if (watching[metric] = watch) {
           return;
         }
 
         // If unwatched, remove any existing widgets for that metric.
         for (let target of this._targets.values()) {
-          target.metrics.set(metric, 0);
-          target.display();
+          target.clear(metric);
         }
       });
     }
 
     client.addListener('logMessage', this.consoleListener);
     client.addListener('pageError', this.consoleListener);
     client.addListener('consoleAPICall', this.consoleListener);
     client.addListener('reflowActivity', this.consoleListener);
   },
 
   trackTarget: function cw_trackTarget(target) {
-    target.metrics.set('reflows', 0);
-    target.metrics.set('warnings', 0);
-    target.metrics.set('errors', 0);
+    target.register('reflows');
+    target.register('warnings');
+    target.register('errors');
 
     this._client.request({
       to: target.actor.consoleActor,
       type: 'startListeners',
       listeners: ['LogMessage', 'PageError', 'ConsoleAPI', 'ReflowActivity']
     }, (res) => {
       this._targets.set(target.actor.consoleActor, target);
     });
@@ -283,92 +334,74 @@ let consoleWatcher = {
       to: target.actor.consoleActor,
       type: 'stopListeners',
       listeners: ['LogMessage', 'PageError', 'ConsoleAPI', 'ReflowActivity']
     }, (res) => { });
 
     this._targets.delete(target.actor.consoleActor);
   },
 
-  bump: function cw_bump(target, metric) {
-    if (!this._watching[metric]) {
-      return false;
-    }
-
-    let metrics = target.metrics;
-    metrics.set(metric, metrics.get(metric) + 1);
-    return true;
-  },
-
   consoleListener: function cw_consoleListener(type, packet) {
     let target = this._targets.get(packet.from);
+    let metric;
     let output = '';
 
     switch (packet.type) {
 
       case 'pageError':
         let pageError = packet.pageError;
 
         if (pageError.warning || pageError.strict) {
-          if (!this.bump(target, 'warnings')) {
-            return;
-          }
-          output = 'warning (';
+          metric = 'warnings';
+          output += 'warning (';
         } else {
-          if (!this.bump(target, 'errors')) {
-            return;
-          }
+          metric = 'errors';
           output += 'error (';
         }
 
         let {errorMessage, sourceName, category, lineNumber, columnNumber} = pageError;
         output += category + '): "' + (errorMessage.initial || errorMessage) +
           '" in ' + sourceName + ':' + lineNumber + ':' + columnNumber;
         break;
 
       case 'consoleAPICall':
-        switch (packet.message.level) {
+        switch (packet.output.level) {
 
           case 'error':
-            if (!this.bump(target, 'errors')) {
-              return;
-            }
-            output = 'error (console)';
+            metric = 'errors';
+            output += 'error (console)';
             break;
 
           case 'warn':
-            if (!this.bump(target, 'warnings')) {
-              return;
-            }
-            output = 'warning (console)';
+            metric = 'warnings';
+            output += 'warning (console)';
             break;
 
           default:
             return;
         }
         break;
 
       case 'reflowActivity':
-        if (!this.bump(target, 'reflows')) {
-          return;
-        }
+        metric = 'reflows';
 
         let {start, end, sourceURL} = packet;
         let duration = Math.round((end - start) * 100) / 100;
-        output = 'reflow: ' + duration + 'ms';
+        output += 'reflow: ' + duration + 'ms';
         if (sourceURL) {
           output += ' ' + this.formatSourceURL(packet);
         }
         break;
     }
 
-    if (!target.display()) {
-      // If the information was not displayed, log it.
-      developerHUD.log(output);
+    if (!this._watching[metric]) {
+      return;
     }
+
+    target.bump(metric, output);
   },
 
   formatSourceURL: function cw_formatSourceURL(packet) {
     // Abbreviate source URL
     let source = WebConsoleUtils.abbreviateSourceURL(packet.sourceURL);
 
     // Add function name and line number
     let {functionName, sourceLine} = packet;
@@ -400,34 +433,29 @@ let eventLoopLagWatcher = {
 
     // Toggle the state of existing fronts.
     let fronts = this._fronts;
     for (let target of fronts.keys()) {
       if (value) {
         fronts.get(target).start();
       } else {
         fronts.get(target).stop();
-        target.metrics.set('jank', 0);
-        target.display();
+        target.clear('jank');
       }
     }
   },
 
   trackTarget: function(target) {
-    target.metrics.set('jank', 0);
+    target.register('jank');
 
     let front = new EventLoopLagFront(this._client, target.actor);
     this._fronts.set(target, front);
 
     front.on('event-loop-lag', time => {
-      target.metrics.set('jank', time);
-
-      if (!target.display()) {
-        developerHUD.log('jank: ' + time + 'ms');
-      }
+      target.update('jank', time, 'jank: ' + time + 'ms');
     });
 
     if (this._active) {
       front.start();
     }
   },
 
   untrackTarget: function(target) {
@@ -473,18 +501,17 @@ let memoryWatcher = {
     SettingsListener.observe('hud.appmemory', false, enabled => {
       if (this._active = enabled) {
         for (let target of this._fronts.keys()) {
           this.measure(target);
         }
       } else {
         for (let target of this._fronts.keys()) {
           clearTimeout(this._timers.get(target));
-          target.metrics.set('memory', 0);
-          target.display();
+          target.clear('memory');
         }
       }
     });
   },
 
   measure: function mw_measure(target) {
 
     // TODO Also track USS (bug #976024).
@@ -510,29 +537,28 @@ let memoryWatcher = {
       if (watch.style) {
         total += parseInt(data.styleSize);
       }
       if (watch.other) {
         total += parseInt(data.otherSize);
       }
       // TODO Also count images size (bug #976007).
 
-      target.metrics.set('memory', total);
-      target.display();
+      target.update('memory', total);
       let duration = parseInt(data.jsMilliseconds) + parseInt(data.nonJSMilliseconds);
       let timer = setTimeout(() => this.measure(target), 100 * duration);
       this._timers.set(target, timer);
     }, (err) => {
       console.error(err);
     });
   },
 
   trackTarget: function mw_trackTarget(target) {
-    target.metrics.set('uss', 0);
-    target.metrics.set('memory', 0);
+    target.register('uss');
+    target.register('memory');
     this._fronts.set(target, MemoryFront(this._client, target.actor));
     if (this._active) {
       this.measure(target);
     }
   },
 
   untrackTarget: function mw_untrackTarget(target) {
     let front = this._fronts.get(target);
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6c93da506e79bd7641f2cc0958b531ab84ceefe1"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e61dc0019d9d6135d88ba15153c37f73a952567e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="456499c44d1ef39b602ea02e9ed460b6aab85b44"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="97a5b461686757dbb8ecab2aac5903e41d2e1afe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="6c93da506e79bd7641f2cc0958b531ab84ceefe1"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e61dc0019d9d6135d88ba15153c37f73a952567e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="a9e08b91e9cd1f0930f16cfc49ec72f63575d5fe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="6c93da506e79bd7641f2cc0958b531ab84ceefe1"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e61dc0019d9d6135d88ba15153c37f73a952567e"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6c93da506e79bd7641f2cc0958b531ab84ceefe1"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e61dc0019d9d6135d88ba15153c37f73a952567e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="d11f524d00cacf5ba0dfbf25e4aa2158b1c3a036"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="456499c44d1ef39b602ea02e9ed460b6aab85b44"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "remote": "", 
         "branch": "", 
         "revision": ""
     }, 
-    "revision": "82537f452462222ac77bb22f19a6ceb89aeade95", 
+    "revision": "1450b977cb8e074f2e9fe2a12049005204febf72", 
     "repo_path": "/integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6c93da506e79bd7641f2cc0958b531ab84ceefe1"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e61dc0019d9d6135d88ba15153c37f73a952567e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6c93da506e79bd7641f2cc0958b531ab84ceefe1"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e61dc0019d9d6135d88ba15153c37f73a952567e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6c93da506e79bd7641f2cc0958b531ab84ceefe1"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e61dc0019d9d6135d88ba15153c37f73a952567e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6c93da506e79bd7641f2cc0958b531ab84ceefe1"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e61dc0019d9d6135d88ba15153c37f73a952567e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="97a5b461686757dbb8ecab2aac5903e41d2e1afe">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="6c93da506e79bd7641f2cc0958b531ab84ceefe1"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="e61dc0019d9d6135d88ba15153c37f73a952567e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="1ad48c4be51b279f7f63c1a13025b52fe087d231">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="6c93da506e79bd7641f2cc0958b531ab84ceefe1"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="e61dc0019d9d6135d88ba15153c37f73a952567e"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="353d422fcbd0b41b76e1262f0992a832420a7567"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="cf1dcc0704c0c1845f8a0a0b44838f7e0c0362c9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="485846b2a40d8ac7d6c1c5f8af6d15b0c10af19d"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1348,17 +1348,16 @@ pref("shumway.disabled", true);
 // The maximum amount of decoded image data we'll willingly keep around (we
 // might keep around more than this, but we'll try to get down to this value).
 // (This is intentionally on the high side; see bug 746055.)
 pref("image.mem.max_decoded_image_kb", 256000);
 
 // Default social providers
 pref("social.manifest.facebook", "{\"origin\":\"https://www.facebook.com\",\"name\":\"Facebook Share\",\"shareURL\":\"https://www.facebook.com/sharer/sharer.php?u=%{url}\",\"iconURL\":\"data:image/x-icon;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8%2F9hAAAAX0lEQVQ4jWP4%2F%2F8%2FAyUYTFhHzjgDxP9JxGeQDSBVMxgTbUBCxer%2Fr999%2BQ8DJBuArJksA9A10s8AXIBoA0B%2BR%2FY%2FjD%2BEwoBoA1yT5v3PbdmCE8MAshhID%2FUMoDgzUYIBj0Cgi7ar4coAAAAASUVORK5CYII%3D\",\"icon32URL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAADbklEQVRYCc1Xv08UQRj99tctexAuCEFjRE0kGBEtLDSGqIWNxkYKbTAxNlY2JhaGWltNtNFeKgsKKxITK43/gCYW+IsoRhA4D47bH7fn9+bcvdm5JR7sefolC3Ozu9978+bNN7PayUv3HN3umdY0Y6IWBtSJ0HSTarXqTOiuTep6Lj+tdxAcA8RAgSmwdd2aCDs0clldYALb/FvgYVhjmfliVA2XpjEgWo0Attn42Z6WH1RFor5ehwo9XQIUZMoVn4qlCoVMSo62EvD8Kh0b3U2Xz43R2PBO6mUCGDlAf65V6MadZzT/rUimoccc2kYA4BfPHqJb105RzjJigKhRq9kEJUBIjgYVuXeL7SAI6eD+Abp5dTwVHOmEHxT50d8WBYJqSOdPj5BjW8gZR8UNqFR2xagx/65XFYaMH+BGWwiYpi4UkBPPLxTp9v1Z+lHc4DWvCQXWmIy6EjITgKowVd5Jjv7N3Hd6y5esigoOwpkJIAmMpZpLJGdiaaC4F0UmAj6bD84GCEwmB/qxMmRilmnwb/mpjAocHh4UEoNAt5NLZB7oy9OJo0PxqkAtePdhiSqunyC1LQUwWMPQaOr6GRre258Ajn4cP7KHcEXhsxpXbj+lT19X2TMNGTLVAcjcalS8gDwsQ2UOMhH4k8FkcrEn5E5ub2sKohxLK2VR77Hl9RUcsrgeRIEiVOT6z+tDbIeLy+vk+kGTCbXxycet6xhl//3f6bJEkdHYhA+mLtDIvoH4ieev5+juoxdk5+pjhALYEdXIpEB5w+NlSKSzqVQ/+H7IO6BLtl3fngGMiqhGJgIwlM6qpyUGFjySdk8m0Zg0ubeD7X9OIDEFajltRQgUJaUKx69tdgaQa0FMADuahZPMFtcEwNPm2hA7ZI5sK4aoE2NvYI+o8hkCIe7CwTv68zS0q9Dk5vpbm/8FXxitSzmMFHpsGj0wyLUheTwD2Y9fVgh1Ae0EPUgD9241ZEnld+v5kgnVZ/8fE0brVh5BK+1oCqKKF72Dk7HwBsssB/pklU1dfChy3S659H5+uelgIb+8WRv1/uGTV9Sdb5wJFlfW6fPCalMhwhSU1j2xKwKbP838GcOwJja4TqO0bjdmXxYTy1EYjFdCWoCEYZhseH/GDL3yJPHnuW6YmT7P1SlIA4768Hke4vOcsX8BE346lLHhDUQAAAAASUVORK5CYII=\", \"icon64URL\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAAAAadEVYdFNvZnR3YXJlAFBhaW50Lk5FVCB2My41LjEwMPRyoQAACNNJREFUeNrtm3tw1NUVxz/399hHHkgCaCBGEFEEREVFYFQcSoOKdkZay4z+4dDpYIsjHWx1WoTMhFi1gzBSpVgVGbU4U1sHfPESKODwEEnRYDFAICEIeZIQshs2u/v73ds/drMsyW7YLEkl2Z6Z32yy+9v7u+fc7znne8+5KzgvAjDunzlv0M13PjDZ6c4cARj0WhEoaZ1tOn3yq9XLf/tNU0O1D5Ad7wq/OpxpaXOL1j5uZAwuaGlVgwNBhULRm0XXBG6HZrlNa9uRrzfM+3DlgjIgGMsA7rl/XDdHOnNf9vosTfVuvTsaQhdkZ4iykh2rHtqydvkxwI58BhjTfv7MmP55E9/1nLNdfU15ACkVvoAaMCRvRPa+re9+DgTaPjMAx+DrJv3M67Mz+6LybWLb4NfTHhxzx31DDhZvOtqGAgNwWbjGICV9XQJB0e/KobcOP1i8qTzaAEYgaDtNU/V5A9hSaUFLuQEt2gVQSgml+j4CUAIppYgK/m0GkCjZ9xGAUNAu0LUhgJRAAAIVzwBSqVRQH4hlAClRKZAFhOgEASoFECBR8QwgUyQGdJT/B8HzCEiBNKhUJzEgBYIgQsTJAkohe9oFZHgHKvQoHtZ9K3tewfiixXABLdoFeuSSEmkF+PH4QTz7+M3o+ENptzvGtS36uSwmjMpAYF10XCllHCYoe84FlLS555Zs5jx6J6ahY+iCl98pJiDNS1hwSZop+cm91zJmxEBefGsPlu1AxKC67V3gf5oGlZSMuz6Dp2fdhWnoAEwaN5T5hsYLb+4hKB1dcgelFDpB8ifk8thDt3DO5+fZxRvxBV0IjQR0EB3KfD1GhJS0GZnnYuGcKTgdF9ZWx4/No/BJjUUrdtJqm4iL+K5SCmSAiWMHMevhcQzNzaa6ron5SzfQ7HeiaSKx+au4m6HupcJKSYZdZVI4dypuV2yo3zoql0VP3cOiFV/Q4jdiGkGhQFqMGpbJL346kbE3DEYIQWNTC39Ysp4Gr4HQtZDyiRhA0NlmSHZbRM7pr1H0m6lckeHqdGXG3jCYoqfupeC17bT49fNRXIFSNrkDTGbNGM9dtw1D10M1DI/Xx3NLP6OqETRdDy1eglPT4rqA7K56gCIrXfHCvHwGZqUnBMtR113FS/N+xHPLtuJpDa1mVobg0emjmX7vqEjsUErhaw1Q8Mo6yk4F0A1HeOW7kIlFx/u7jworRabLpmhuPjmD+iG7YNDrrhnIS09P5cW/buOeO67lkftvJt3tDE06PE7Qsnh++QYOHPOim86wcVUS0+whJug0ghTMmcK1V2eH8m2UHP++nrwhAyIwjiXDcrN5vXAGhqGhFBeMYUvJ0re2sPfgGTTDGUZrEogVopMgmGQWUCgMEWTBE5MZPSLngnGqas/w9j92s31fJfmThvPM7HyMMKRj+qgmOiBHSsnr73/B5r1V6A53KD4k3bFS8dNgckFQoWPxu1kTGDc6N7JqzR4ff/+smE+2H8FSLkx3FluLawhaG3n2iXwcppGoV/Hemt18tK0c3UwLIfUS2nVafBeQJNUXkAF+/dht3H37cKSUBC2bTTv+w98++YazPg1dT0NoIUhruoMd+2sJrtjI7381rQM3iCVrN33N++tL0c30xFNdp0GQeFRYQhcRIO0gv5xxE/fdfSO2bfNVSTmrPtxLZW0A3XSh6VporaLG1XQHu0pOU/TaOhY8+QAuZ3w6vHnnQd74536EkZ50wOsSE0zcugolbWZOG8GM/LGUVdSw6sM97D/UgGa60QxXzMJDZAq6yb7SJgr//CkLn5pOmsvR4Z5dxUdZ9t6XoKfFjNyXkqZjuoBUCpGgCyhp8eDdQ5k++UaWrdrMlr2VSFxoZlpE8YtNWGgGXx9ppnDZpxTMfTCS8gAOlJ5g8ds7kCItTIi6j6FqMVxAC2sV2RB1ekmLCTcNpH+myeyFH7BxT1Voopoe4RKJXQqhmxw45mXh0o/xeH0opThcXs2iv2wmoFyhAnbC4yX+3PgISMDShrA5XHGa3d9UITRniIeTfHASmsGhEz7mL/mI2TPv4sU3t+KzHAnu7JKpCosOkcSIICAB5hZE0OiRCM0Iwb0b6LPQdI5W+Zn/yucoYYayRk+16eK1xqRMDAHtA0r3lep0lNAjO8kfpCpMqpTF4xZEUqA7rIlOCiKpgADVWXc4FQwgEfHPB5AiByTixIDUCYJx+wJoqdIcJV5VOAWygEZcF7BToT2upFDKtuz2BrAtf8v3mju972cBJX2exso6ok6N64BhOtM11xXXPBz6v6340PcuO+DZfaJkzWqgqY3L64Bqaaz0ZV45Mkc308dG2kd97FLSaq4v317gazr5HeCLRoACFTxbfeBw+oDhWYYj4/rw+30H+rb/VMPxXQsbKnbuABqJOi4vogyRiRB5/XNvvz3zytFTDEf61eF9b0dCKTS36c4afymTsgLeQ9Ly13X/aYnzE1Uy6PV7679trNy1xe+tKwPqAH/0Vla0qw65gH7AFeG/Y3Uy9P45o0bm3PTIaplM6lTK9jWf/OBUyQcrpdXaTIyfsXQb9QcLaAn7vJd2vxY5XxBpo8pwDmgFGsLKx1oeh8OVmUUSLXUlrWZPzbdLag9v+BjUqfDzepKAyDDcZbznGHG+1NmqSKHpVlfbadJqLW+o2LHobNX+PUB1WPkfnHwYyTmX6lI7Lehr3F576NM/+T3V3wH17f2w1xkg2ggXuSvga6p8p+bgmpVKWpXAmVh+2AsNEKogdYYAJa0GT03J4obyf60HTgKe6PTTqw0QOpcQ3wXs4LlDZyq2FXrrS4uBmjDxuCw3G5eIgA46yeC5ho11pWsWW35PWTibBC4Xf+9eBLRPg0q2+s5UvHG6bMNqJYPHw7nXutxZYvIIiMoCSgbrPVX/fv7syS+3AKfC5MOmF4iRpP6RjrId8O5vrNhS1NpUWQLUholUr6muXEoatP3emrWNR9e/avk9R8P+HuxNypPkrk93pGdnK0VtXemaN6UdOHo55vdE5b/0NKx+K4AxtAAAAABJRU5ErkJggg==\", \"description\":\"Easily share the web to your Facebook friends.\",\"author\":\"Facebook\",\"homepageURL\":\"https://www.facebook.com\",\"builtin\":\"true\",\"version\":1}");
 
-pref("social.sidebar.open", true);
 pref("social.sidebar.unload_timeout_ms", 10000);
 
 pref("dom.identity.enabled", false);
 
 // Turn on the CSP 1.0 parser for Content Security Policy headers
 pref("security.csp.speccompliant", true);
 
 // Block insecure active content on https pages
@@ -1379,30 +1378,29 @@ pref("geo.wifi.uri", "https://www.google
 pref("network.disable.ipc.security", true);
 
 // CustomizableUI debug logging.
 pref("browser.uiCustomization.debug", false);
 
 // CustomizableUI state of the browser's user interface
 pref("browser.uiCustomization.state", "");
 
-// The URL where remote content that composes the UI for Firefox Accounts should
-// be fetched. Must use HTTPS.
-pref("identity.fxaccounts.remote.uri", "https://accounts.firefox.com/?service=sync&context=fx_desktop_v1");
+// The remote content URL shown for FxA signup. Must use HTTPS.
+pref("identity.fxaccounts.remote.signup.uri", "https://accounts.firefox.com/signup?service=sync&context=fx_desktop_v1");
 
 // The URL where remote content that forces re-authentication for Firefox Accounts
 // should be fetched.  Must use HTTPS.
 pref("identity.fxaccounts.remote.force_auth.uri", "https://accounts.firefox.com/force_auth?service=sync&context=fx_desktop_v1");
 
 // The remote content URL shown for signin in. Must use HTTPS.
 pref("identity.fxaccounts.remote.signin.uri", "https://accounts.firefox.com/signin?service=sync&context=fx_desktop_v1");
 
 // The URL we take the user to when they opt to "manage" their Firefox Account.
 // Note that this will always need to be in the same TLD as the
-// "identity.fxaccounts.remote.uri" pref.
+// "identity.fxaccounts.remote.signup.uri" pref.
 pref("identity.fxaccounts.settings.uri", "https://accounts.firefox.com/settings");
 
 // On GTK, we now default to showing the menubar only when alt is pressed:
 #ifdef MOZ_WIDGET_GTK
 pref("ui.key.menuAccessKeyFocuses", true);
 #endif
 
 
--- a/browser/base/content/aboutSocialError.xhtml
+++ b/browser/base/content/aboutSocialError.xhtml
@@ -74,20 +74,17 @@
       }
     }
 
     function setUpStrings() {
       let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
       let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
 
       let productName = brandBundle.GetStringFromName("brandShortName");
-      let provider = Social && Social.provider;
-      if (config.origin) {
-        provider = Social && Social._getProviderFromOrigin(config.origin);
-      }
+      let provider = Social._getProviderFromOrigin(config.origin);
       let providerName = provider && provider.name;
 
       // Sets up the error message
       let msg = browserBundle.formatStringFromName("social.error.message", [productName, providerName], 2);
       document.getElementById("main-error-msg").textContent = msg;
 
       // Sets up the buttons' labels and accesskeys
       let btnTryAgain = document.getElementById("btnTryAgain");
@@ -95,31 +92,32 @@
       btnTryAgain.accessKey = browserBundle.GetStringFromName("social.error.tryAgain.accesskey");
 
       let btnCloseSidebar = document.getElementById("btnCloseSidebar");
       btnCloseSidebar.textContent = browserBundle.GetStringFromName("social.error.closeSidebar.label");
       btnCloseSidebar.accessKey = browserBundle.GetStringFromName("social.error.closeSidebar.accesskey");
     }
 
     function closeSidebarButton() {
-      Social.toggleSidebar();
+      SocialSidebar.toggleSidebar();
     }
 
     function tryAgainButton() {
       config.tryAgainCallback();
     }
 
     function loadQueryURL() {
       window.location.href = config.queryURL;
     }
 
     function reloadProvider() {
       // Just incase the current provider *isn't* in a frameworker-error
       // state, reload the current one.
-      Social.provider.reload();
+      let provider = Social._getProviderFromOrigin(config.origin);
+      provider.reload();
       // If the problem is a frameworker-error, it may be that the child
       // process crashed - and if that happened, then *all* providers in that
       // process will have crashed.  However, only the current provider is
       // likely to have the error surfaced in the UI - so we reload *all*
       // providers that are in a frameworker-error state.
       for (let provider of Social.providers) {
         if (provider.enabled && provider.errorState == "frameworker-error") {
           provider.reload();
--- a/browser/base/content/aboutaccounts/aboutaccounts.js
+++ b/browser/base/content/aboutaccounts/aboutaccounts.js
@@ -95,17 +95,17 @@ let wrapper = {
       return;
     }
 
     let iframe = document.getElementById("remote");
     this.iframe = iframe;
     iframe.addEventListener("load", this);
 
     try {
-      iframe.src = url || fxAccounts.getAccountsURI();
+      iframe.src = url || fxAccounts.getAccountsSignUpURI();
     } catch (e) {
       error("Couldn't init Firefox Account wrapper: " + e.message);
     }
   },
 
   handleEvent: function (evt) {
     switch (evt.type) {
       case "load":
@@ -218,17 +218,17 @@ let wrapper = {
         log("Unexpected remote command received: " + evt.detail.command + ". Ignoring command.");
         break;
     }
   },
 
   injectData: function (type, content) {
     let authUrl;
     try {
-      authUrl = fxAccounts.getAccountsURI();
+      authUrl = fxAccounts.getAccountsSignUpURI();
     } catch (e) {
       error("Couldn't inject data: " + e.message);
       return;
     }
     let data = {
       type: type,
       content: content
     };
--- a/browser/base/content/browser-places.js
+++ b/browser/base/content/browser-places.js
@@ -1319,16 +1319,32 @@ let BookmarkingUI = {
   },
 
   onMainMenuPopupShowing: function BUI_onMainMenuPopupShowing(event) {
     this._updateBookmarkPageMenuItem();
     PlacesCommandHook.updateBookmarkAllTabsCommand();
   },
 
   _showBookmarkedNotification: function BUI_showBookmarkedNotification() {
+    /*
+     * We're dynamically setting pointer-events to none here for the duration
+     * of the bookmark menu button's dropmarker animation in order to avoid
+     * having it end up in the overflow menu. This happens because it gaining
+     * focus triggers a style change which triggers an overflow event, even
+     * though this does not happen if no focus change occurs. The core issue
+     * is tracked in https://bugzilla.mozilla.org/show_bug.cgi?id=981637
+     */
+    let onDropmarkerAnimationEnd = () => {
+      this.button.removeEventListener("animationend", onDropmarkerAnimationEnd);
+      this.button.style.removeProperty("pointer-events");
+    };
+    let onDropmarkerAnimationStart = () => {
+      this.button.removeEventListener("animationstart", onDropmarkerAnimationStart);
+      this.button.style.pointerEvents = 'none';
+    };
 
     if (this._notificationTimeout) {
       clearTimeout(this._notificationTimeout);
     }
 
     if (this.notifier.style.transform == '') {
       let isRTL = getComputedStyle(this.button).direction == "rtl";
       let buttonRect = this.button.getBoundingClientRect();
@@ -1349,23 +1365,26 @@ let BookmarkingUI = {
     let isInBookmarksToolbar = this.button.classList.contains("bookmark-item");
     if (isInBookmarksToolbar)
       this.notifier.setAttribute("in-bookmarks-toolbar", true);
 
     let isInOverflowPanel = this.button.getAttribute("overflowedItem") == "true";
     if (!isInOverflowPanel) {
       this.notifier.setAttribute("notification", "finish");
       this.button.setAttribute("notification", "finish");
+      this.button.addEventListener('animationstart', onDropmarkerAnimationStart);
+      this.button.addEventListener("animationend", onDropmarkerAnimationEnd);
     }
 
     this._notificationTimeout = setTimeout( () => {
       this.notifier.removeAttribute("notification");
       this.notifier.removeAttribute("in-bookmarks-toolbar");
       this.button.removeAttribute("notification");
       this.notifier.style.transform = '';
+      this.button.style.removeProperty("pointer-events");
     }, 1000);
   },
 
   _showSubview: function() {
     let view = document.getElementById("PanelUI-bookmarks");
     view.addEventListener("ViewShowing", this);
     view.addEventListener("ViewHiding", this);
     let anchor = document.getElementById(this.BOOKMARK_BUTTON_ID);
--- a/browser/base/content/browser-sets.inc
+++ b/browser/base/content/browser-sets.inc
@@ -111,20 +111,19 @@
       oncommand="OpenBrowserWindow({private: true});"/>
     <command id="Tools:RemoteWindow"
       oncommand="OpenBrowserWindow({remote: true});"/>
     <command id="Tools:NonRemoteWindow"
       oncommand="OpenBrowserWindow({remote: false});"/>
     <command id="History:UndoCloseTab" oncommand="undoCloseTab();"/>
     <command id="History:UndoCloseWindow" oncommand="undoCloseWindow();"/>
     <command id="Social:SharePage" oncommand="SocialShare.sharePage();" disabled="true"/>
-    <command id="Social:ToggleSidebar" oncommand="Social.toggleSidebar();" hidden="true"/>
+    <command id="Social:ToggleSidebar" oncommand="SocialSidebar.toggleSidebar();" hidden="true"/>
     <command id="Social:ToggleNotifications" oncommand="Social.toggleNotifications();" hidden="true"/>
     <command id="Social:FocusChat" oncommand="SocialChatBar.focus();" hidden="true" disabled="true"/>
-    <command id="Social:Toggle" oncommand="Social.toggle();" hidden="true"/>
     <command id="Social:Addons" oncommand="BrowserOpenAddonsMgr('addons://list/service');"/>
   </commandset>
 
   <commandset id="placesCommands">
     <command id="Browser:ShowAllBookmarks"
              oncommand="PlacesCommandHook.showPlacesOrganizer('AllBookmarks');"/>
     <command id="Browser:ShowAllHistory"
              oncommand="PlacesCommandHook.showPlacesOrganizer('History');"/>
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -46,177 +46,142 @@ XPCOMUtils.defineLazyGetter(this, "Creat
 
 XPCOMUtils.defineLazyGetter(this, "CreateSocialMarkWidget", function() {
   let tmp = {};
   Cu.import("resource:///modules/Social.jsm", tmp);
   return tmp.CreateSocialMarkWidget;
 });
 
 SocialUI = {
+  _initialized: false,
+
   // Called on delayed startup to initialize the UI
   init: function SocialUI_init() {
+    if (this._initialized) {
+      return;
+    }
+
     Services.obs.addObserver(this, "social:ambient-notification-changed", false);
     Services.obs.addObserver(this, "social:profile-changed", false);
     Services.obs.addObserver(this, "social:frameworker-error", false);
-    Services.obs.addObserver(this, "social:provider-set", false);
     Services.obs.addObserver(this, "social:providers-changed", false);
     Services.obs.addObserver(this, "social:provider-reload", false);
     Services.obs.addObserver(this, "social:provider-enabled", false);
     Services.obs.addObserver(this, "social:provider-disabled", false);
 
-    Services.prefs.addObserver("social.sidebar.open", this, false);
     Services.prefs.addObserver("social.toast-notifications.enabled", this, false);
 
     gBrowser.addEventListener("ActivateSocialFeature", this._activationEventHandler.bind(this), true, true);
     document.getElementById("PanelUI-popup").addEventListener("popupshown", SocialMarks.updatePanelButtons, true);
 
     // menupopups that list social providers. we only populate them when shown,
     // and if it has not been done already.
     document.getElementById("viewSidebarMenu").addEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
     document.getElementById("social-statusarea-popup").addEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
 
-    if (!Social.initialized) {
-      Social.init();
-    } else if (Social.providers.length > 0) {
-      // Social was initialized during startup in a previous window. If we have
-      // providers enabled initialize the UI for this window.
-      this.observe(null, "social:providers-changed", null);
-      this.observe(null, "social:provider-set", Social.provider ? Social.provider.origin : null);
-    }
+    Social.init().then((update) => {
+      if (update)
+        this._providersChanged();
+      // handle SessionStore for the sidebar state
+      SocialSidebar.restoreWindowState();
+    });
+
+    this._initialized = true;
   },
 
   // Called on window unload
   uninit: function SocialUI_uninit() {
+    if (!this._initialized) {
+      return;
+    }
+
     Services.obs.removeObserver(this, "social:ambient-notification-changed");
     Services.obs.removeObserver(this, "social:profile-changed");
     Services.obs.removeObserver(this, "social:frameworker-error");
-    Services.obs.removeObserver(this, "social:provider-set");
     Services.obs.removeObserver(this, "social:providers-changed");
     Services.obs.removeObserver(this, "social:provider-reload");
     Services.obs.removeObserver(this, "social:provider-enabled");
     Services.obs.removeObserver(this, "social:provider-disabled");
 
-    Services.prefs.removeObserver("social.sidebar.open", this);
     Services.prefs.removeObserver("social.toast-notifications.enabled", this);
 
     document.getElementById("PanelUI-popup").removeEventListener("popupshown", SocialMarks.updatePanelButtons, true);
     document.getElementById("viewSidebarMenu").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
     document.getElementById("social-statusarea-popup").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
-  },
 
-  _matchesCurrentProvider: function (origin) {
-    return Social.provider && Social.provider.origin == origin;
+    this._initialized = false;
   },
 
   observe: function SocialUI_observe(subject, topic, data) {
     // Exceptions here sometimes don't get reported properly, report them
     // manually :(
     try {
       switch (topic) {
         case "social:provider-enabled":
           SocialMarks.populateToolbarPalette();
           SocialStatus.populateToolbarPalette();
           break;
         case "social:provider-disabled":
           SocialMarks.removeProvider(data);
           SocialStatus.removeProvider(data);
+          SocialSidebar.disableProvider(data);
           break;
         case "social:provider-reload":
           SocialStatus.reloadProvider(data);
           // if the reloaded provider is our current provider, fall through
-          // to social:provider-set so the ui will be reset
-          if (!Social.provider || Social.provider.origin != data)
+          // to social:providers-changed so the ui will be reset
+          if (!SocialSidebar.provider || SocialSidebar.provider.origin != data)
             return;
-          // be sure to unload the sidebar as it will not reload if the origin
-          // has not changed, it will be loaded in provider-set below. Other
-          // panels will be unloaded or handle reload.
+          // currently only the sidebar and flyout have a selected provider.
+          // sidebar provider has changed (possibly to null), ensure the content
+          // is unloaded and the frames are reset, they will be loaded in
+          // providers-changed below if necessary.
           SocialSidebar.unloadSidebar();
-          // fall through to social:provider-set
-        case "social:provider-set":
-          // Social.provider has changed (possibly to null), update any state
-          // which depends on it.
-          this._updateActiveUI();
-
           SocialFlyout.unload();
-          SocialChatBar.update();
-          SocialShare.update();
-          SocialSidebar.update();
-          SocialStatus.populateToolbarPalette();
-          SocialMarks.populateToolbarPalette();
-          break;
+          // fall through to providers-changed to ensure the reloaded provider
+          // is correctly reflected in any UI and the multi-provider menu
         case "social:providers-changed":
-          // the list of providers changed - this may impact the "active" UI.
-          this._updateActiveUI();
-          // and the multi-provider menu
-          SocialSidebar.clearProviderMenus();
-          SocialShare.populateProviderMenu();
-          SocialStatus.populateToolbarPalette();
-          SocialMarks.populateToolbarPalette();
+          this._providersChanged();
           break;
 
         // Provider-specific notifications
         case "social:ambient-notification-changed":
           SocialStatus.updateButton(data);
           break;
         case "social:profile-changed":
           // make sure anything that happens here only affects the provider for
           // which the profile is changing, and that anything we call actually
           // needs to change based on profile data.
           SocialStatus.updateButton(data);
           break;
         case "social:frameworker-error":
-          if (this.enabled && Social.provider.origin == data) {
+          if (this.enabled && SocialSidebar.provider && SocialSidebar.provider.origin == data) {
             SocialSidebar.setSidebarErrorMessage();
           }
           break;
 
         case "nsPref:changed":
-          if (data == "social.sidebar.open") {
-            SocialSidebar.update();
-          } else if (data == "social.toast-notifications.enabled") {
+          if (data == "social.toast-notifications.enabled") {
             SocialSidebar.updateToggleNotifications();
           }
           break;
       }
     } catch (e) {
       Components.utils.reportError(e + "\n" + e.stack);
       throw e;
     }
   },
 
-  _updateActiveUI: function SocialUI_updateActiveUI() {
-    // The "active" UI isn't dependent on there being a provider, just on
-    // social being "active" (but also chromeless/PB)
-    let enabled = Social.providers.length > 0 && !this._chromeless &&
-                  !PrivateBrowsingUtils.isWindowPrivate(window);
-
-    let toggleCommand = document.getElementById("Social:Toggle");
-    toggleCommand.setAttribute("hidden", enabled ? "false" : "true");
-
-    if (enabled) {
-      // enabled == true means we at least have a defaultProvider
-      let provider = Social.provider || Social.defaultProvider;
-      // We only need to update the command itself - all our menu items use it.
-      let label;
-      if (Social.providers.length == 1) {
-        label = gNavigatorBundle.getFormattedString(Social.provider
-                                                    ? "social.turnOff.label"
-                                                    : "social.turnOn.label",
-                                                    [provider.name]);
-      } else {
-        label = gNavigatorBundle.getString(Social.provider
-                                           ? "social.turnOffAll.label"
-                                           : "social.turnOnAll.label");
-      }
-      let accesskey = gNavigatorBundle.getString(Social.provider
-                                                 ? "social.turnOff.accesskey"
-                                                 : "social.turnOn.accesskey");
-      toggleCommand.setAttribute("label", label);
-      toggleCommand.setAttribute("accesskey", accesskey);
-    }
+  _providersChanged: function() {
+    SocialSidebar.clearProviderMenus();
+    SocialSidebar.update();
+    SocialChatBar.update();
+    SocialShare.populateProviderMenu();
+    SocialStatus.populateToolbarPalette();
+    SocialMarks.populateToolbarPalette();
   },
 
   // This handles "ActivateSocialFeature" events fired against content documents
   // in this window.
   _activationEventHandler: function SocialUI_activationHandler(e) {
     let targetDoc;
     let node;
     if (e.target instanceof HTMLDocument) {
@@ -258,17 +223,21 @@ SocialUI = {
       try {
         data = JSON.parse(data);
       } catch(e) {
         Cu.reportError("Social Service manifest parse error: "+e);
         return;
       }
     }
     Social.installProvider(targetDoc, data, function(manifest) {
-      Social.activateFromOrigin(manifest.origin);
+      Social.activateFromOrigin(manifest.origin, function(provider) {
+        if (provider.sidebarURL) {
+          SocialSidebar.show(provider.origin);
+        }
+      });
     });
   },
 
   showLearnMore: function() {
     let url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api";
     openUILinkIn(url, "tab");
   },
 
@@ -306,17 +275,17 @@ SocialUI = {
     this._chromeless = chromeless;
     return chromeless;
   },
 
   get enabled() {
     // Returns whether social is enabled *for this window*.
     if (this._chromeless || PrivateBrowsingUtils.isWindowPrivate(window))
       return false;
-    return !!Social.provider;
+    return Social.providers.length > 0;
   },
 
   // called on tab/urlbar/location changes and after customization. Update
   // anything that is tab specific.
   updateState: function() {
     if (!this.enabled)
       return;
     SocialMarks.update();
@@ -333,16 +302,17 @@ SocialChatBar = {
   get isAvailable() {
     return SocialUI.enabled;
   },
   // Does this chatbar have any chats (whether minimized, collapsed or normal)
   get hasChats() {
     return !!this.chatbar.firstElementChild;
   },
   openChat: function(aProvider, aURL, aCallback, aMode) {
+    this.update();
     if (!this.isAvailable)
       return false;
     this.chatbar.openChat(aProvider, aURL, aCallback, aMode);
     // We only want to focus the chat if it is as a result of user input.
     let dwu = window.QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIDOMWindowUtils);
     if (dwu.isHandlingUserInput)
       this.chatbar.focus();
@@ -385,23 +355,25 @@ SocialFlyout = {
     if (!SocialUI.enabled || panel.firstChild)
       return;
     // create and initialize the panel for this window
     let iframe = document.createElement("iframe");
     iframe.setAttribute("type", "content");
     iframe.setAttribute("class", "social-panel-frame");
     iframe.setAttribute("flex", "1");
     iframe.setAttribute("tooltip", "aHTMLTooltip");
-    iframe.setAttribute("origin", Social.provider.origin);
+    iframe.setAttribute("origin", SocialSidebar.provider.origin);
     panel.appendChild(iframe);
   },
 
   setFlyoutErrorMessage: function SF_setFlyoutErrorMessage() {
     this.iframe.removeAttribute("src");
-    this.iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo", null, null, null, null);
+    this.iframe.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
+                                 encodeURIComponent(this.iframe.getAttribute("origin")),
+                                 null, null, null, null);
     sizeSocialPanelToContent(this.panel, this.iframe);
   },
 
   unload: function() {
     let panel = this.panel;
     panel.hidePopup();
     if (!panel.firstChild)
       return
@@ -437,17 +409,17 @@ SocialFlyout = {
   onHidden: function(aEvent) {
     this._dynamicResizer.stop();
     this._dynamicResizer = null;
     this.iframe.docShell.isActive = false;
     this.dispatchPanelEvent("socialFrameHide");
   },
 
   load: function(aURL, cb) {
-    if (!Social.provider)
+    if (!SocialSidebar.provider)
       return;
 
     this.panel.hidden = false;
     let iframe = this.iframe;
     // same url with only ref difference does not cause a new load, so we
     // want to go right to the callback
     let src = iframe.contentDocument && iframe.contentDocument.documentURIObject;
     if (!src || !src.equalsExceptRef(Services.io.newURI(aURL, null, null))) {
@@ -532,18 +504,20 @@ SocialShare = {
   },
 
   getSelectedProvider: function() {
     let provider;
     let lastProviderOrigin = this.iframe && this.iframe.getAttribute("origin");
     if (lastProviderOrigin) {
       provider = Social._getProviderFromOrigin(lastProviderOrigin);
     }
+    // if they have a provider selected in the sidebar use that for the initial
+    // default in share
     if (!provider)
-      provider = Social.provider || Social.defaultProvider;
+      provider = SocialSidebar.provider;
     // if our provider has no shareURL, select the first one that does
     if (provider && !provider.shareURL) {
       let providers = [p for (p of Social.providers) if (p.shareURL)];
       provider = providers.length > 0  && providers[0];
     }
     return provider;
   },
 
@@ -717,24 +691,73 @@ SocialShare = {
     this.panel.openPopup(anchor, "bottomcenter topright", 0, 0, false, false);
     Social.setErrorListener(iframe, this.setErrorMessage.bind(this));
   }
 };
 
 SocialSidebar = {
   // Whether the sidebar can be shown for this window.
   get canShow() {
-    if (PrivateBrowsingUtils.isWindowPrivate(window))
+    if (!SocialUI.enabled || document.mozFullScreen)
       return false;
-    return [p for (p of Social.providers) if (p.enabled && p.sidebarURL)].length > 0;
+    return Social.providers.some(p => p.sidebarURL);
   },
 
   // Whether the user has toggled the sidebar on (for windows where it can appear)
   get opened() {
-    return Services.prefs.getBoolPref("social.sidebar.open") && !document.mozFullScreen;
+    let broadcaster = document.getElementById("socialSidebarBroadcaster");
+    return !broadcaster.hidden;
+  },
+
+  restoreWindowState: function() {
+    this._initialized = true;
+    if (!this.canShow)
+      return;
+
+    if (Services.prefs.prefHasUserValue("social.provider.current")) {
+      // "upgrade" when the first window opens if we have old prefs.  We get the
+      // values from prefs this one time, window state will be saved when this
+      // window is closed.
+      let origin = Services.prefs.getCharPref("social.provider.current");
+      Services.prefs.clearUserPref("social.provider.current");
+      // social.sidebar.open default was true, but we only opened if there was
+      // a current provider
+      let opened = origin && true;
+      if (Services.prefs.prefHasUserValue("social.sidebar.open")) {
+        opened = origin && Services.prefs.getBoolPref("social.sidebar.open");
+        Services.prefs.clearUserPref("social.sidebar.open");
+      }
+      let data = {
+        "hidden": !opened,
+        "origin": origin
+      };
+      SessionStore.setWindowValue(window, "socialSidebar", JSON.stringify(data));
+    }
+
+    let data = SessionStore.getWindowValue(window, "socialSidebar");
+    // if this window doesn't have it's own state, use the state from the opener
+    if (!data && window.opener && !window.opener.closed) {
+      data = SessionStore.getWindowValue(window.opener, "socialSidebar");
+    }
+    if (data) {
+      data = JSON.parse(data);
+      document.getElementById("social-sidebar-browser").setAttribute("origin", data.origin);
+      if (!data.hidden)
+        this.show(data.origin);
+    }
+  },
+
+  saveWindowState: function() {
+    let broadcaster = document.getElementById("socialSidebarBroadcaster");
+    let sidebarOrigin = document.getElementById("social-sidebar-browser").getAttribute("origin");
+    let data = {
+      "hidden": broadcaster.hidden,
+      "origin": sidebarOrigin
+    };
+    SessionStore.setWindowValue(window, "socialSidebar", JSON.stringify(data));
   },
 
   setSidebarVisibilityState: function(aEnabled) {
     let sbrowser = document.getElementById("social-sidebar-browser");
     // it's possible we'll be called twice with aEnabled=false so let's
     // just assume we may often be called with the same state.
     if (aEnabled == sbrowser.docShellIsActive)
       return;
@@ -746,16 +769,20 @@ SocialSidebar = {
 
   updateToggleNotifications: function() {
     let command = document.getElementById("Social:ToggleNotifications");
     command.setAttribute("checked", Services.prefs.getBoolPref("social.toast-notifications.enabled"));
     command.setAttribute("hidden", !SocialUI.enabled);
   },
 
   update: function SocialSidebar_update() {
+    // ensure we never update before restoreWindowState
+    if (!this._initialized)
+      return;
+    this.ensureProvider();
     this.updateToggleNotifications();
     this._updateHeader();
     clearTimeout(this._unloadTimeoutId);
     // Hide the toggle menu item if the sidebar cannot appear
     let command = document.getElementById("Social:ToggleSidebar");
     command.setAttribute("hidden", this.canShow ? "false" : "true");
 
     // Hide the sidebar if it cannot appear, or has been toggled off.
@@ -777,31 +804,31 @@ SocialSidebar = {
         this.unloadSidebar();
       } else {
         this._unloadTimeoutId = setTimeout(
           this.unloadSidebar,
           Services.prefs.getIntPref("social.sidebar.unload_timeout_ms")
         );
       }
     } else {
-      sbrowser.setAttribute("origin", Social.provider.origin);
-      if (Social.provider.errorState == "frameworker-error") {
+      sbrowser.setAttribute("origin", this.provider.origin);
+      if (this.provider.errorState == "frameworker-error") {
         SocialSidebar.setSidebarErrorMessage();
         return;
       }
 
       // Make sure the right sidebar URL is loaded
-      if (sbrowser.getAttribute("src") != Social.provider.sidebarURL) {
+      if (sbrowser.getAttribute("src") != this.provider.sidebarURL) {
         // we check readyState right after setting src, we need a new content
         // viewer to ensure we are checking against the correct document.
         sbrowser.docShell.createAboutBlankContentViewer(null);
         Social.setErrorListener(sbrowser, this.setSidebarErrorMessage.bind(this));
         // setting isAppTab causes clicks on untargeted links to open new tabs
         sbrowser.docShell.isAppTab = true;
-        sbrowser.setAttribute("src", Social.provider.sidebarURL);
+        sbrowser.setAttribute("src", this.provider.sidebarURL);
         PopupNotifications.locationChange(sbrowser);
       }
 
       // if the document has not loaded, delay until it is
       if (sbrowser.contentDocument.readyState != "complete") {
         document.getElementById("social-sidebar-button").setAttribute("loading", "true");
         sbrowser.addEventListener("load", SocialSidebar._loadListener, true);
       } else {
@@ -833,33 +860,65 @@ SocialSidebar = {
     SocialFlyout.unload();
   },
 
   _unloadTimeoutId: 0,
 
   setSidebarErrorMessage: function() {
     let sbrowser = document.getElementById("social-sidebar-browser");
     // a frameworker error "trumps" a sidebar error.
-    if (Social.provider.errorState == "frameworker-error") {
-      sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure");
+    let origin = sbrowser.getAttribute("origin");
+    if (origin) {
+      origin = "&origin=" + encodeURIComponent(origin);
+    }
+    if (this.provider.errorState == "frameworker-error") {
+      sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure" + origin);
     } else {
-      let url = encodeURIComponent(Social.provider.sidebarURL);
-      sbrowser.loadURI("about:socialerror?mode=tryAgain&url=" + url, null, null);
+      let url = encodeURIComponent(this.provider.sidebarURL);
+      sbrowser.loadURI("about:socialerror?mode=tryAgain&url=" + url + origin, null, null);
     }
   },
 
-  // provider will move to a sidebar specific member in bug 894806
-  get provider() {
-    return Social.provider;
+  _provider: null,
+  ensureProvider: function() {
+    if (this._provider)
+      return;
+    // origin for sidebar is persisted, so get the previously selected sidebar
+    // first, otherwise fallback to the first provider in the list
+    let sbrowser = document.getElementById("social-sidebar-browser");
+    let origin = sbrowser.getAttribute("origin");
+    let providers = [p for (p of Social.providers) if (p.sidebarURL)];
+    let provider;
+    if (origin)
+      provider = Social._getProviderFromOrigin(origin);
+    if (!provider && providers.length > 0)
+      provider = providers[0];
+    if (provider)
+      this.provider = provider;
   },
 
-  setProvider: function(origin) {
-    Social.setProviderByOrigin(origin);
-    this._updateHeader();
-    this._updateCheckedMenuItems(origin);
+  get provider() {
+    return this._provider;
+  },
+
+  set provider(provider) {
+    if (!provider || provider.sidebarURL) {
+      this._provider = provider;
+      this._updateHeader();
+      this._updateCheckedMenuItems(provider && provider.origin);
+      this.update();
+    }
+  },
+
+  disableProvider: function(origin) {
+    if (this._provider && this._provider.origin == origin) {
+      this._provider = null;
+      // force a selection of the next provider if there is one
+      this.ensureProvider();
+    }
   },
 
   _updateHeader: function() {
     let provider = this.provider;
     let image, title;
     if (provider) {
       image = "url(" + (provider.icon32URL || provider.iconURL) + ")";
       title = provider.name;
@@ -879,23 +938,40 @@ SocialSidebar = {
         mi.removeAttribute("checked");
         mi.setAttribute("oncommand", "SocialSidebar.show(this.getAttribute('origin'));");
       }
     }
   },
 
   show: function(origin) {
     // always show the sidebar, and set the provider
-    this.setProvider(origin);
-    Services.prefs.setBoolPref("social.sidebar.open", true);
+    let broadcaster = document.getElementById("socialSidebarBroadcaster");
+    broadcaster.hidden = false;
+    if (origin)
+      this.provider = Social._getProviderFromOrigin(origin);
+    else
+      SocialSidebar.update();
+    this.saveWindowState();
   },
 
   hide: function() {
-    Services.prefs.setBoolPref("social.sidebar.open", false);
+    let broadcaster = document.getElementById("socialSidebarBroadcaster");
+    broadcaster.hidden = true;
     this._updateCheckedMenuItems();
+    this.clearProviderMenus();
+    SocialSidebar.update();
+    this.saveWindowState();
+  },
+
+  toggleSidebar: function SocialSidebar_toggle() {
+    let broadcaster = document.getElementById("socialSidebarBroadcaster");
+    if (broadcaster.hidden)
+      this.show();
+    else
+      this.hide();
   },
 
   populateSidebarMenu: function(event) {
     // Providers are removed from the view->sidebar menu when there is a change
     // in providers, so we only have to populate onshowing if there are no
     // provider menus. We populate this menu so long as there are enabled
     // providers with sidebars.
     let popup = event.target;
@@ -1256,19 +1332,21 @@ SocialStatus = {
   },
 
   setPanelErrorMessage: function(aNotificationFrame) {
     if (!aNotificationFrame)
       return;
 
     let src = aNotificationFrame.getAttribute("src");
     aNotificationFrame.removeAttribute("src");
+    let origin = aNotificationFrame.getAttribute("origin");
     aNotificationFrame.webNavigation.loadURI("about:socialerror?mode=tryAgainOnly&url=" +
-                                             encodeURIComponent(src),
-                                             null, null, null, null);
+                                            encodeURIComponent(src) + "&origin=" +
+                                            encodeURIComponent(origin),
+                                            null, null, null, null);
     let panel = aNotificationFrame.parentNode;
     sizeSocialPanelToContent(panel, aNotificationFrame);
   },
 
 };
 
 
 /**
--- a/browser/base/content/browser-syncui.js
+++ b/browser/base/content/browser-syncui.js
@@ -83,42 +83,74 @@ let gSyncUI = {
 
     // notificationbox will listen to observers from now on.
     Services.obs.removeObserver(this, "weave:notification:added");
   },
 
   _wasDelayed: false,
 
   _needsSetup: function SUI__needsSetup() {
+    // We want to treat "account needs verification" as "needs setup". We don't
+    // know what the user's verified state is until Sync is initialized, though,
+    // and we need to get an answer here synchronously (can't wait for
+    // getSignedInUser). So "reach in" to Weave.Service.identity to get the
+    // answer here, and we'll just have to deal with this not having an answer
+    // before Sync is initialized.
+
+    // Referencing Weave.Service will implicitly initialize sync, and we don't
+    // want to force that - so first check if it is ready.
+    let service = Cc["@mozilla.org/weave/service;1"]
+                  .getService(Components.interfaces.nsISupports)
+                  .wrappedJSObject;
+    if (service.ready && Weave.Service.identity._signedInUser) {
+      // If we have a signed in user already, and that user is not verified,
+      // revert to the "needs setup" state.
+      if (!Weave.Service.identity._signedInUser.verified) {
+        return true;
+      }
+    }
+
     let firstSync = "";
     try {
       firstSync = Services.prefs.getCharPref("services.sync.firstSync");
     } catch (e) { }
+
     return Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED ||
            firstSync == "notReady";
   },
 
   _loginFailed: function () {
-    // Referencing Weave.Service will implicitly initialize sync, and we don't
+    // Referencing Weave.Status will import a bunch of modules, and we don't
     // want to force that - so first check if it is ready.
     let service = Cc["@mozilla.org/weave/service;1"]
                   .getService(Components.interfaces.nsISupports)
                   .wrappedJSObject;
     if (!service.ready) {
       return false;
     }
+
     return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
   },
 
   updateUI: function SUI_updateUI() {
     let needsSetup = this._needsSetup();
     let loginFailed = this._loginFailed();
-    document.getElementById("sync-setup-state").hidden = loginFailed || !needsSetup;
-    document.getElementById("sync-syncnow-state").hidden = loginFailed || needsSetup;
-    document.getElementById("sync-reauth-state").hidden = !loginFailed;
+
+    // Start off with a clean slate
+    document.getElementById("sync-reauth-state").hidden = true;
+    document.getElementById("sync-setup-state").hidden = true;
+    document.getElementById("sync-syncnow-state").hidden = true;
+
+    if (loginFailed) {
+      document.getElementById("sync-reauth-state").hidden = false;
+    } else if (needsSetup) {
+      document.getElementById("sync-setup-state").hidden = false;
+    } else {
+      document.getElementById("sync-syncnow-state").hidden = false;
+    }
 
     if (!gBrowser)
       return;
 
     let syncButton = document.getElementById("sync-button");
     let panelHorizontalButton = document.getElementById("PanelUI-fxa-status");
     [syncButton, panelHorizontalButton].forEach(function(button) {
       if (!button)
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1029,17 +1029,16 @@ var gBrowserInit = {
     Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
     Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
     Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false);
 
     BrowserOffline.init();
     OfflineApps.init();
     IndexedDBPromptHelper.init();
     gFormSubmitObserver.init();
-    SocialUI.init();
     gRemoteTabsUI.init();
     gPageStyleMenu.init();
 
     // Initialize the full zoom setting.
     // We do this before the session restore service gets initialized so we can
     // apply full zoom settings to tabs restored by the session restore service.
     FullZoom.init();
     PanelUI.init();
@@ -1188,19 +1187,25 @@ var gBrowserInit = {
     if (gMultiProcessBrowser) {
       // Bug 862519 - Backspace doesn't work in electrolysis builds.
       // We bypass the problem by disabling the backspace-to-go-back command.
       document.getElementById("cmd_handleBackspace").setAttribute("disabled", true);
       document.getElementById("key_delete").setAttribute("disabled", true);
     }
 
     SessionStore.promiseInitialized.then(() => {
+      // Bail out if the window has been closed in the meantime.
+      if (window.closed) {
+        return;
+      }
+
       // Enable the Restore Last Session command if needed
       RestoreLastSessionObserver.init();
 
+      SocialUI.init();
       TabView.init();
 
       setTimeout(function () { BrowserChromeTest.markAsReady(); }, 0);
     });
     this.delayedStartupFinished = true;
 
     Services.obs.notifyObservers(window, "browser-delayed-startup-finished", "");
     TelemetryTimestamps.add("delayedStartupFinished");
@@ -1288,16 +1293,17 @@ var gBrowserInit = {
       this._cancelDelayedStartup();
     } else {
       if (Win7Features)
         Win7Features.onCloseWindow();
 
       gPrefService.removeObserver(ctrlTab.prefName, ctrlTab);
       ctrlTab.uninit();
       TabView.uninit();
+      SocialUI.uninit();
       gBrowserThumbnails.uninit();
       FullZoom.destroy();
 
       Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-started");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
       Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
@@ -1312,17 +1318,16 @@ var gBrowserInit = {
 
       if (typeof WindowsPrefSync !== 'undefined') {
         WindowsPrefSync.uninit();
       }
 
       BrowserOffline.uninit();
       OfflineApps.uninit();
       IndexedDBPromptHelper.uninit();
-      SocialUI.uninit();
       LightweightThemeListener.uninit();
       PanelUI.uninit();
     }
 
     // Final window teardown, do this last.
     window.XULBrowserWindow = null;
     window.QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIWebNavigation)
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -626,16 +626,17 @@
              aria-label="&navbarCmd.label;"
              fullscreentoolbar="true" mode="icons" customizable="true"
              iconsize="small"
              defaultset="urlbar-container,search-container,webrtc-status-button,bookmarks-menu-button,downloads-button,home-button,social-share-button,social-toolbar-item"
              customizationtarget="nav-bar-customization-target"
              overflowable="true"
              overflowbutton="nav-bar-overflow-button"
              overflowtarget="widget-overflow-list"
+             overflowpanel="widget-overflow"
              context="toolbar-context-menu">
 
       <hbox id="nav-bar-customization-target" flex="1">
         <toolbaritem id="urlbar-container" flex="400" persist="width"
                      forwarddisabled="true" title="&locationItem.title;" removable="false"
                      cui-areatype="toolbar"
                      class="chromeclass-location" overflows="false">
           <toolbarbutton id="back-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
@@ -1074,17 +1075,16 @@
                         label="&social.toggleSidebar.label;"
                         accesskey="&social.toggleSidebar.accesskey;"/>
               <menuitem class="social-toggle-notifications-menuitem"
                         type="checkbox"
                         autocheck="false"
                         command="Social:ToggleNotifications"
                         label="&social.toggleNotifications.label;"
                         accesskey="&social.toggleNotifications.accesskey;"/>
-              <menuitem class="social-toggle-menuitem" command="Social:Toggle"/>
               <menuseparator/>
               <menuseparator class="social-provider-menu" hidden="true"/>
               <menuitem class="social-addons-menuitem" command="Social:Addons"
                         label="&social.addons.label;"/>
               <menuitem label="&social.learnMore.label;"
                         accesskey="&social.learnMore.accesskey;"
                         oncommand="SocialUI.showLearnMore();"/>
             </menupopup>
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -29,17 +29,19 @@
     </content>
 
     <implementation implements="nsIDOMEventListener">
       <constructor><![CDATA[
         let Social = Components.utils.import("resource:///modules/Social.jsm", {}).Social;
         this.content.__defineGetter__("popupnotificationanchor",
                                       () => document.getAnonymousElementByAttribute(this, "anonid", "notification-icon"));
         Social.setErrorListener(this.content, function(aBrowser) {
-          aBrowser.webNavigation.loadURI("about:socialerror?mode=compactInfo", null, null, null, null);
+          aBrowser.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
+                                 encodeURIComponent(aBrowser.getAttribute("origin")),
+                                 null, null, null, null);
         });
         if (!this.chatbar) {
           document.getAnonymousElementByAttribute(this, "anonid", "minimize").hidden = true;
           document.getAnonymousElementByAttribute(this, "anonid", "close").hidden = true;
         }
         let contentWindow = this.contentWindow;
         this.addEventListener("DOMContentLoaded", function DOMContentLoaded(event) {
           if (event.target != this.contentDocument)
@@ -146,17 +148,19 @@
           aTarget.src = this.src;
           aTarget.content.setAttribute("origin", this.content.getAttribute("origin"));
           aTarget.content.popupnotificationanchor.className = this.content.popupnotificationanchor.className;
           this.content.socialErrorListener.remove();
           aTarget.content.socialErrorListener.remove();
           this.content.swapDocShells(aTarget.content);
           Social.setErrorListener(this.content, function(aBrowser) {}); // 'this' will be destroyed soon.
           Social.setErrorListener(aTarget.content, function(aBrowser) {
-            aBrowser.webNavigation.loadURI("about:socialerror?mode=compactInfo", null, null, null, null);
+            aBrowser.webNavigation.loadURI("about:socialerror?mode=compactInfo&origin=" +
+                                 encodeURIComponent(aBrowser.getAttribute("origin")),
+                                 null, null, null, null);
           });
         ]]></body>
       </method>
 
       <method name="onTitlebarClick">
         <parameter name="aEvent"/>
         <body><![CDATA[
           if (!this.chatbar)
--- a/browser/base/content/test/general/browser_aboutAccounts.js
+++ b/browser/base/content/test/general/browser_aboutAccounts.js
@@ -29,17 +29,17 @@ let gTests = [
 {
   desc: "Test the remote commands",
   teardown: function* () {
     gBrowser.removeCurrentTab();
     yield fxAccounts.signOut();
   },
   run: function* ()
   {
-    setPref("identity.fxaccounts.remote.uri",
+    setPref("identity.fxaccounts.remote.signup.uri",
             "https://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
     yield promiseNewTabLoadEvent("about:accounts");
 
     let deferred = Promise.defer();
 
     let results = 0;
     try {
       let win = gBrowser.contentWindow;
@@ -110,17 +110,17 @@ let gTests = [
   }
 },
 {
   desc: "Test action=signup - no user logged in",
   teardown: () => gBrowser.removeCurrentTab(),
   run: function* ()
   {
     const expected_url = "https://example.com/?is_sign_up";
-    setPref("identity.fxaccounts.remote.uri", expected_url);
+    setPref("identity.fxaccounts.remote.signup.uri", expected_url);
     let [tab, url] = yield promiseNewTabWithIframeLoadEvent("about:accounts?action=signup");
     is(url, expected_url, "action=signup got the expected URL");
     // we expect the remote iframe to be shown.
     yield checkVisibilities(tab, {
       stage: false, // parent of 'manage' and 'intro'
       manage: false,
       intro: false, // this is  "get started"
       remote: true
@@ -128,17 +128,17 @@ let gTests = [
   },
 },
 {
   desc: "Test action=signup - user logged in",
   teardown: () => gBrowser.removeCurrentTab(),
   run: function* ()
   {
     const expected_url = "https://example.com/?is_sign_up";
-    setPref("identity.fxaccounts.remote.uri", expected_url);
+    setPref("identity.fxaccounts.remote.signup.uri", expected_url);
     yield setSignedInUser();
     let tab = yield promiseNewTabLoadEvent("about:accounts?action=signup");
     yield fxAccounts.getSignedInUser();
     // we expect "manage" to be shown.
     yield checkVisibilities(tab, {
       stage: true, // parent of 'manage' and 'intro'
       manage: true,
       intro: false, // this is  "get started"
@@ -174,17 +174,17 @@ let gTests = [
   },
 },
 {
   desc: "Test observers about:accounts",
   teardown: function() {
     gBrowser.removeCurrentTab();
   },
   run: function* () {
-    setPref("identity.fxaccounts.remote.uri", "https://example.com/");
+    setPref("identity.fxaccounts.remote.signup.uri", "https://example.com/");
     yield setSignedInUser();
     let tab = yield promiseNewTabLoadEvent("about:accounts");
     // sign the user out - the tab should have action=signin
     yield signOut();
     // wait for the new load.
     yield promiseOneMessage(tab, "test:document:load");
     is(tab.linkedBrowser.contentDocument.location.href, "about:accounts?action=signin");
   }
--- a/browser/base/content/test/social/browser_addons.js
+++ b/browser/base/content/test/social/browser_addons.js
@@ -319,17 +319,16 @@ var tests = {
 
     addTab(activationURL, function(tab) {
       let doc = tab.linkedBrowser.contentDocument;
       let installFrom = doc.nodePrincipal.origin;
       Services.prefs.setCharPref("social.whitelist", installFrom);
       Social.installProvider(doc, manifest2, function(addonManifest) {
         SocialService.addBuiltinProvider(addonManifest.origin, function(provider) {
           is(provider.manifest.version, 1, "manifest version is 1");
-          Social.enabled = true;
 
           // watch for the provider-update and test the new version
           SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
             if (topic != "provider-update")
               return;
             is(origin, addonManifest.origin, "provider updated")
             SocialService.unregisterProviderListener(providerListener);
             Services.prefs.clearUserPref("social.whitelist");
@@ -337,24 +336,15 @@ var tests = {
             is(provider.manifest.version, 2, "manifest version is 2");
             Social.uninstallProvider(origin, function() {
               gBrowser.removeTab(tab);
               next();
             });
           });
 
           let port = provider.getWorkerPort();
-          port.onmessage = function (e) {
-            let topic = e.data.topic;
-            switch (topic) {
-              case "got-sidebar-message":
-                ok(true, "got the sidebar message from provider 1");
-                port.postMessage({topic: "worker.update", data: true});
-                break;
-            }
-          };
-          port.postMessage({topic: "test-init"});
+          port.postMessage({topic: "worker.update", data: true});
 
         });
       });
     });
   }
 }
--- a/browser/base/content/test/social/browser_chat_tearoff.js
+++ b/browser/base/content/test/social/browser_chat_tearoff.js
@@ -15,27 +15,29 @@ function test() {
   };
 
   let postSubTest = function(cb) {
     let chats = document.getElementById("pinnedchats");
     ok(chats.children.length == 0, "no chatty children left behind");
     cb();
   };
   runSocialTestWithProvider(manifest, function (finishcb) {
+    SocialSidebar.show();
+    ok(SocialSidebar.provider, "sidebar provider exists");
     runSocialTests(tests, undefined, postSubTest, function() {
       finishcb();
     });
   });
 }
 
 var tests = {
   testTearoffChat: function(next) {
     let chats = document.getElementById("pinnedchats");
     let chatTitle;
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "got-sidebar-message":
           port.postMessage({topic: "test-chatbox-open"});
           break;
         case "got-chatbox-visibility":
@@ -128,17 +130,17 @@ var tests = {
     });
 
     port.postMessage({topic: "test-init", data: { id: 1 }});
   },
 
   testCloseOnLogout: function(next) {
     let chats = document.getElementById("pinnedchats");
     const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.postMessage({topic: "test-init"});
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "got-chatbox-visibility":
           // chatbox is open, lets detach. The new chat window will be caught in
           // the window watcher below
@@ -199,17 +201,17 @@ var tests = {
 
     port.postMessage({topic: "test-worker-chat", data: chatUrl});
   },
 
   testReattachTwice: function(next) {
     let chats = document.getElementById("pinnedchats");
     const chatUrl = "https://example.com/browser/browser/base/content/test/social/social_chat.html";
     let chatBoxCount = 0, reattachCount = 0;
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.postMessage({topic: "test-init"});
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "got-chatbox-visibility":
           // chatbox is open, lets detach. The new chat window will be caught in
           // the window watcher below
--- a/browser/base/content/test/social/browser_share.js
+++ b/browser/base/content/test/social/browser_share.js
@@ -98,22 +98,23 @@ function hasoptions(testOptions, options
       is(message_data, data, "option "+option);
     }
   }
 }
 
 var tests = {
   testSharePage: function(next) {
     let panel = document.getElementById("social-flyout-panel");
-    let port = Social.provider.getWorkerPort();
+    SocialSidebar.show();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     let testTab;
     let testIndex = 0;
     let testData = corpus[testIndex++];
-    
+
     function runOneTest() {
       loadURLInTab(testData.url, function(tab) {
         testTab = tab;
         SocialShare.sharePage();
       });
     }
 
     port.onmessage = function (e) {
--- a/browser/base/content/test/social/browser_social_activation.js
+++ b/browser/base/content/test/social/browser_social_activation.js
@@ -2,17 +2,16 @@
  * 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/. */
 
 let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
 
 let tabsToRemove = [];
 
 function postTestCleanup(callback) {
-  Social.provider = null;
   // any tabs opened by the test.
   for (let tab of tabsToRemove)
     gBrowser.removeTab(tab);
   tabsToRemove = [];
   // theses tests use the notification panel but don't bother waiting for it
   // to fully open - the end result is that the panel might stay open
   //SocialUI.activationPanel.hidePopup();
 
@@ -81,30 +80,31 @@ function activateIFrameProvider(domain, 
   let activationURL = domain+"/browser/browser/base/content/test/social/social_activate_iframe.html"
   addTab(activationURL, function(tab) {
     sendActivationEvent(tab, callback, false);
   });
 }
 
 function waitForProviderLoad(cb) {
   Services.obs.addObserver(function providerSet(subject, topic, data) {
-    Services.obs.removeObserver(providerSet, "social:provider-set");
-    info("social:provider-set observer was notified");
+    Services.obs.removeObserver(providerSet, "social:provider-enabled");
+    info("social:provider-enabled observer was notified");
     waitForCondition(function() {
       let sbrowser = document.getElementById("social-sidebar-browser");
-      return Social.provider &&
-             Social.provider.profile &&
-             Social.provider.profile.displayName &&
+      let provider = SocialSidebar.provider;
+      return provider &&
+             provider.profile &&
+             provider.profile.displayName &&
              sbrowser.docShellIsActive;
     }, function() {
       // executeSoon to let the browser UI observers run first
       executeSoon(cb);
     },
     "waitForProviderLoad: provider profile was not set");
-  }, "social:provider-set", false);
+  }, "social:provider-enabled", false);
 }
 
 
 function getAddonItemInList(aId, aList) {
   var item = aList.firstChild;
   while (item) {
     if ("mAddon" in item && item.mAddon.id == aId) {
       aList.ensureElementIsVisible(item);
@@ -152,17 +152,18 @@ function activateOneProvider(manifest, f
   });
 
   activateProvider(manifest.origin, function() {
     if (!finishActivation) {
       ok(panel.hidden, "activation panel is not showing");
       executeSoon(aCallback);
     } else {
       waitForProviderLoad(function() {
-        is(Social.provider.origin, manifest.origin, "new provider is active");
+        is(SocialSidebar.provider.origin, manifest.origin, "new provider is active");
+        ok(SocialSidebar.opened, "sidebar is open");
         checkSocialUI();
         executeSoon(aCallback);
       });
     }
   });
 }
 
 let gTestDomains = ["https://example.com", "https://test1.example.com", "https://test2.example.com"];
@@ -209,66 +210,65 @@ var tests = {
       next();
     });
   },
 
   testIFrameActivation: function(next) {
     Services.prefs.setCharPref("social.whitelist", gTestDomains.join(","));
     activateIFrameProvider(gTestDomains[0], function() {
       is(SocialUI.enabled, false, "SocialUI is not enabled");
-      ok(!Social.provider, "provider is not installed");
+      ok(!SocialSidebar.provider, "provider is not installed");
       let panel = document.getElementById("servicesInstall-notification");
       ok(panel.hidden, "activation panel still hidden");
       checkSocialUI();
       Services.prefs.clearUserPref("social.whitelist");
       next();
     });
   },
 
   testActivationFirstProvider: function(next) {
     Services.prefs.setCharPref("social.whitelist", gTestDomains.join(","));
     // first up we add a manifest entry for a single provider.
     activateOneProvider(gProviders[0], false, function() {
       // we deactivated leaving no providers left, so Social is disabled.
-      ok(!Social.provider, "should be no provider left after disabling");
+      ok(!SocialSidebar.provider, "should be no provider left after disabling");
       checkSocialUI();
       Services.prefs.clearUserPref("social.whitelist");
       next();
     });
   },
 
   testActivationBuiltin: function(next) {
     let prefname = addBuiltinManifest(gProviders[0]);
     is(SocialService.getOriginActivationType(gTestDomains[0]), "builtin", "manifest is builtin");
     // first up we add a manifest entry for a single provider.
     activateOneProvider(gProviders[0], false, function() {
       // we deactivated leaving no providers left, so Social is disabled.
-      ok(!Social.provider, "should be no provider left after disabling");
+      ok(!SocialSidebar.provider, "should be no provider left after disabling");
       checkSocialUI();
       resetBuiltinManifestPref(prefname);
       next();
     });
   },
 
   testActivationMultipleProvider: function(next) {
     // The trick with this test is to make sure that Social.providers[1] is
     // the current provider when doing the undo - this makes sure that the
     // Social code doesn't fallback to Social.providers[0], which it will
     // do in some cases (but those cases do not include what this test does)
     // first enable the 2 providers
     Services.prefs.setCharPref("social.whitelist", gTestDomains.join(","));
     SocialService.addProvider(gProviders[0], function() {
       SocialService.addProvider(gProviders[1], function() {
-        Social.provider = Social.providers[1];
         checkSocialUI();
         // activate the last provider.
         let prefname = addBuiltinManifest(gProviders[2]);
         activateOneProvider(gProviders[2], false, function() {
           // we deactivated - the first provider should be enabled.
-          is(Social.provider.origin, Social.providers[1].origin, "original provider should have been reactivated");
+          is(SocialSidebar.provider.origin, Social.providers[1].origin, "original provider should have been reactivated");
           checkSocialUI();
           Services.prefs.clearUserPref("social.whitelist");
           resetBuiltinManifestPref(prefname);
           next();
         });
       });
     });
   },
@@ -284,22 +284,24 @@ var tests = {
 
     gBrowser.selectedBrowser.addEventListener("load", function tabLoad() {
       gBrowser.selectedBrowser.removeEventListener("load", tabLoad, true);
       let browser = blanktab.linkedBrowser;
       is(browser.currentURI.spec, "about:addons", "about:addons should load into blank tab.");
 
       let prefname = addBuiltinManifest(gProviders[0]);
       activateOneProvider(gProviders[0], true, function() {
+        info("first activation completed");
         gBrowser.removeTab(gBrowser.selectedTab);
         tabsToRemove.pop();
         // uninstall the provider
         clickAddonRemoveButton(blanktab, function(addon) {
           checkSocialUI();
           activateOneProvider(gProviders[0], true, function() {
+            info("second activation completed");
 
             // after closing the addons tab, verify provider is still installed
             gBrowser.tabContainer.addEventListener("TabClose", function onTabClose() {
               gBrowser.tabContainer.removeEventListener("TabClose", onTabClose);
               AddonManager.getAddonsByTypes(["service"], function(aAddons) {
                 is(aAddons.length, 1, "there can be only one");
                 Services.prefs.clearUserPref("social.whitelist");
                 resetBuiltinManifestPref(prefname);
--- a/browser/base/content/test/social/browser_social_chatwindow.js
+++ b/browser/base/content/test/social/browser_social_chatwindow.js
@@ -39,23 +39,16 @@ function openChat(provider, callback) {
     }
   }
   let url = chatUrl + "?" + (chatId++);
   port.postMessage({topic: "test-init"});
   port.postMessage({topic: "test-worker-chat", data: url});
   gURLsNotRemembered.push(url);
 }
 
-function waitPrefChange(cb) {
-  Services.prefs.addObserver("social.enabled", function prefObserver(subject, topic, data) {
-    Services.prefs.removeObserver("social.enabled", prefObserver);
-    executeSoon(cb);
-  }, false);
-}
-
 function test() {
   requestLongerTimeout(2); // only debug builds seem to need more time...
   waitForExplicitFinish();
 
   let oldwidth = window.outerWidth; // we futz with these, so we restore them
   let oldleft = window.screenX;
   window.moveTo(0, window.screenY)
   let postSubTest = function(cb) {
@@ -63,28 +56,29 @@ function test() {
     ok(chats.children.length == 0, "no chatty children left behind");
     cb();
   };
   runSocialTestWithProvider(manifests, function (finishcb) {
     ok(Social.enabled, "Social is enabled");
     ok(Social.providers[0].getWorkerPort(), "provider 0 has port");
     ok(Social.providers[1].getWorkerPort(), "provider 1 has port");
     ok(Social.providers[2].getWorkerPort(), "provider 2 has port");
+    SocialSidebar.show();
     runSocialTests(tests, undefined, postSubTest, function() {
       window.moveTo(oldleft, window.screenY)
       window.resizeTo(oldwidth, window.outerHeight);
       finishcb();
     });
   });
 }
 
 var tests = {
   testOpenCloseChat: function(next) {
     let chats = document.getElementById("pinnedchats");
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "got-sidebar-message":
           port.postMessage({topic: "test-chatbox-open"});
           break;
         case "got-chatbox-visibility":
@@ -114,17 +108,17 @@ var tests = {
     port.postMessage({topic: "test-init", data: { id: 1 }});
   },
   testOpenMinimized: function(next) {
     // In this case the sidebar opens a chat (without specifying minimized).
     // We then minimize it and have the sidebar reopen the chat (again without
     // minimized).  On that second call the chat should open and no longer
     // be minimized.
     let chats = document.getElementById("pinnedchats");
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     let seen_opened = false;
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "test-init-done":
           port.postMessage({topic: "test-chatbox-open"});
           break;
         case "chatbox-opened":
@@ -149,17 +143,17 @@ var tests = {
           }
       }
     }
     port.postMessage({topic: "test-init", data: { id: 1 }});
   },
   testManyChats: function(next) {
     // open enough chats to overflow the window, then check
     // if the menupopup is visible
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     let chats = document.getElementById("pinnedchats");
     ok(port, "provider has a port");
     ok(chats.menupopup.parentNode.collapsed, "popup nub collapsed at start");
     port.postMessage({topic: "test-init"});
     // we should *never* find a test box that needs more than this to cause
     // an overflow!
     let maxToOpen = 20;
     let numOpened = 0;
@@ -188,19 +182,19 @@ var tests = {
           port.close();
           next();
           break;
       }
     }
     maybeOpenAnother();
   },
   testWorkerChatWindow: function(next) {
-    const chatUrl = Social.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
+    const chatUrl = SocialSidebar.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
     let chats = document.getElementById("pinnedchats");
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.postMessage({topic: "test-init"});
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "got-chatbox-message":
           ok(true, "got a chat window opened");
           ok(chats.selectedChat, "chatbox from worker opened");
@@ -214,17 +208,17 @@ var tests = {
           break;
       }
     }
     ok(!chats.selectedChat, "chats are all closed");
     port.postMessage({topic: "test-worker-chat", data: chatUrl});
   },
   testCloseSelf: function(next) {
     let chats = document.getElementById("pinnedchats");
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "test-init-done":
           port.postMessage({topic: "test-chatbox-open"});
           break;
         case "got-chatbox-visibility":
@@ -242,17 +236,17 @@ var tests = {
           next();
           break;
       }
     }
     port.postMessage({topic: "test-init", data: { id: 1 }});
   },
   testSameChatCallbacks: function(next) {
     let chats = document.getElementById("pinnedchats");
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     let seen_opened = false;
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "test-init-done":
           port.postMessage({topic: "test-chatbox-open"});
           break;
         case "chatbox-opened":
@@ -273,17 +267,17 @@ var tests = {
           }
       }
     }
     port.postMessage({topic: "test-init", data: { id: 1 }});
   },
 
   // check removeAll does the right thing
   testRemoveAll: function(next, mode) {
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     port.postMessage({topic: "test-init"});
     get3ChatsForCollapsing(mode || "normal", function() {
       let chatbar = window.SocialChatBar.chatbar;
       chatbar.removeAll();
       // should be no evidence of any chats left.
       is(chatbar.childNodes.length, 0, "should be no chats left");
       checkPopup();
       is(chatbar.selectedChat, null, "nothing should be selected");
@@ -329,31 +323,31 @@ var tests = {
           closeAllChats();
           next();
         });
       });
     });
   },
 
   testShowWhenCollapsed: function(next) {
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     port.postMessage({topic: "test-init"});
     get3ChatsForCollapsing("normal", function(first, second, third) {
       let chatbar = window.SocialChatBar.chatbar;
       chatbar.showChat(first);
       ok(!first.collapsed, "first should no longer be collapsed");
       ok(second.collapsed ||  third.collapsed, false, "one of the others should be collapsed");
       closeAllChats();
       port.close();
       next();
     });
   },
 
   testActivity: function(next) {
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     port.postMessage({topic: "test-init"});
     get3ChatsForCollapsing("normal", function(first, second, third) {
       let chatbar = window.SocialChatBar.chatbar;
       is(chatbar.selectedChat, third, "third chat should be selected");
       ok(!chatbar.selectedChat.hasAttribute("activity"), "third chat should have no activity");
       // send an activity message to the second.
       ok(!second.hasAttribute("activity"), "second chat should have no activity");
       let chat2 = second.content;
@@ -369,17 +363,17 @@ var tests = {
       ok(!first.hasAttribute("activity"), "first chat should have no activity");
       let chat1 = first.content;
       let evt = chat1.contentDocument.createEvent("CustomEvent");
       evt.initCustomEvent("socialChatActivity", true, true, {});
       chat1.contentDocument.documentElement.dispatchEvent(evt);
       ok(first.hasAttribute("activity"), "first chat should now have activity");
       ok(chatbar.nub.hasAttribute("activity"), "nub should also have activity");
       // first is collapsed, so use openChat to get it.
-      chatbar.openChat(Social.provider, first.getAttribute("src"));
+      chatbar.openChat(SocialSidebar.provider, first.getAttribute("src"));
       ok(!first.hasAttribute("activity"), "first chat should no longer have activity");
       // The nub should lose the activity flag here too
       todo(!chatbar.nub.hasAttribute("activity"), "Bug 806266 - nub should no longer have activity");
       // TODO: tests for bug 806266 should arrange to have 2 chats collapsed
       // then open them checking the nub is updated correctly.
       // Now we will go and change the embedded browser in the second chat and
       // ensure the activity magic still works (ie, check that the unload for
       // the browser didn't cause our event handlers to be removed.)
@@ -399,17 +393,17 @@ var tests = {
         })
       })
       subiframe.setAttribute("src", "data:text/plain:new location for iframe");
     });
   },
 
   testOnlyOneCallback: function(next) {
     let chats = document.getElementById("pinnedchats");
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     let numOpened = 0;
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "test-init-done":
           port.postMessage({topic: "test-chatbox-open"});
           break;
         case "chatbox-opened":
@@ -425,18 +419,18 @@ var tests = {
           });
       }
     }
     port.postMessage({topic: "test-init", data: { id: 1 }});
   },
 
   testSecondTopLevelWindow: function(next) {
     // Bug 817782 - check chats work in new top-level windows.
-    const chatUrl = Social.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
-    let port = Social.provider.getWorkerPort();
+    const chatUrl = SocialSidebar.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
+    let port = SocialSidebar.provider.getWorkerPort();
     let secondWindow;
     port.onmessage = function(e) {
       if (e.data.topic == "test-init-done") {
         secondWindow = OpenBrowserWindow();
         secondWindow.addEventListener("load", function loadListener() {
           secondWindow.removeEventListener("load", loadListener);
           port.postMessage({topic: "test-worker-chat", data: chatUrl});
         });
@@ -450,50 +444,50 @@ var tests = {
     port.postMessage({topic: "test-init"});
   },
 
   testChatWindowChooser: function(next) {
     // Tests that when a worker creates a chat, it is opened in the correct
     // window.
     // open a chat (it will open in the main window)
     ok(!window.SocialChatBar.hasChats, "first window should start with no chats");
-    openChat(Social.provider, function() {
+    openChat(SocialSidebar.provider, function() {
       ok(window.SocialChatBar.hasChats, "first window has the chat");
       // create a second window - this will be the "most recent" and will
       // therefore be the window that hosts the new chat (see bug 835111)
       let secondWindow = OpenBrowserWindow();
       secondWindow.addEventListener("load", function loadListener() {
         secondWindow.removeEventListener("load", loadListener);
         ok(!secondWindow.SocialChatBar.hasChats, "second window has no chats");
-        openChat(Social.provider, function() {
+        openChat(SocialSidebar.provider, function() {
           ok(secondWindow.SocialChatBar.hasChats, "second window now has chats");
           is(window.SocialChatBar.chatbar.childElementCount, 1, "first window still has 1 chat");
           window.SocialChatBar.chatbar.removeAll();
           // now open another chat - it should still open in the second.
-          openChat(Social.provider, function() {
+          openChat(SocialSidebar.provider, function() {
             ok(!window.SocialChatBar.hasChats, "first window has no chats");
             ok(secondWindow.SocialChatBar.hasChats, "second window has a chat");
 
             // focus the first window, and open yet another chat - it
             // should open in the first window.
             waitForFocus(function() {
-              openChat(Social.provider, function() {
+              openChat(SocialSidebar.provider, function() {
                 ok(window.SocialChatBar.hasChats, "first window has chats");
                 window.SocialChatBar.chatbar.removeAll();
                 ok(!window.SocialChatBar.hasChats, "first window has no chats");
 
                 let privateWindow = OpenBrowserWindow({private: true});
                 privateWindow.addEventListener("load", function loadListener() {
                   privateWindow.removeEventListener("load", loadListener);
 
                   // open a last chat - the focused window can't accept
                   // chats (it's a private window), so the chat should open
                   // in the window that was selected before. This is known
                   // to be broken on Linux.
-                  openChat(Social.provider, function() {
+                  openChat(SocialSidebar.provider, function() {
                     let os = Services.appinfo.OS;
                     const BROKEN_WM_Z_ORDER = os != "WINNT" && os != "Darwin";
                     let fn = BROKEN_WM_Z_ORDER ? todo : ok;
                     fn(window.SocialChatBar.hasChats, "first window has a chat");
                     window.SocialChatBar.chatbar.removeAll();
 
                     privateWindow.close();
                     secondWindow.close();
@@ -537,18 +531,18 @@ var tests = {
         });
       });
     });
   },
 
   // XXX - note this must be the last test until we restore the login state
   // between tests...
   testCloseOnLogout: function(next) {
-    const chatUrl = Social.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
-    let port = Social.provider.getWorkerPort();
+    const chatUrl = SocialSidebar.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     let opened = false;
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "test-init-done":
           info("open first chat window");
           port.postMessage({topic: "test-worker-chat", data: chatUrl});
--- a/browser/base/content/test/social/browser_social_chatwindow_resize.js
+++ b/browser/base/content/test/social/browser_social_chatwindow_resize.js
@@ -20,25 +20,26 @@ function test() {
   window.moveTo(0, window.screenY)
   let postSubTest = function(cb) {
     let chats = document.getElementById("pinnedchats");
     ok(chats.children.length == 0, "no chatty children left behind");
     cb();
   };
 
   runSocialTestWithProvider(manifest, function (finishcb) {
-    let port = Social.provider.getWorkerPort();
+    SocialSidebar.show();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.postMessage({topic: "test-init"});
     // we require a logged in user for chats, wait for that
     waitForCondition(function() {
       let sbrowser = document.getElementById("social-sidebar-browser");
-      return Social.provider &&
-             Social.provider.profile &&
-             Social.provider.profile.displayName &&
+      return SocialSidebar.provider &&
+             SocialSidebar.provider.profile &&
+             SocialSidebar.provider.profile.displayName &&
              sbrowser.docShellIsActive;
     }, function() {
       // executeSoon to let the browser UI observers run first
       runSocialTests(tests, undefined, postSubTest, function() {
         window.moveTo(oldleft, window.screenY)
         window.resizeTo(oldwidth, window.outerHeight);
         port.close();
         finishcb();
--- a/browser/base/content/test/social/browser_social_chatwindowfocus.js
+++ b/browser/base/content/test/social/browser_social_chatwindowfocus.js
@@ -35,32 +35,32 @@ function openChatViaWorkerMessage(port, 
   let chatbar = SocialChatBar.chatbar;
   let numExpected = chatbar.childElementCount + 1;
   port.postMessage({topic: "test-worker-chat", data: data});
   waitForCondition(function() chatbar.childElementCount == numExpected,
                    function() {
                       // so the child has been added, but we don't know if it
                       // has been intialized - re-request it and the callback
                       // means it's done.  Minimized, same as the worker.
-                      SocialChatBar.openChat(Social.provider,
+                      SocialChatBar.openChat(SocialSidebar.provider,
                                              data,
                                              function() {
                                                 callback();
                                              },
                                              "minimized");
                    },
                    "No new chat appeared");
 }
 
 
 let isSidebarLoaded = false;
 
 function startTestAndWaitForSidebar(callback) {
   let doneCallback;
-  let port = Social.provider.getWorkerPort();
+  let port = SocialSidebar.provider.getWorkerPort();
   function maybeCallback() {
     if (!doneCallback)
       callback(port);
     doneCallback = true;
   }
   port.onmessage = function(e) {
     let topic = e.data.topic;
     switch (topic) {
@@ -109,16 +109,17 @@ function test() {
       waitForCondition(function() isTabFocused(), cb, "tab should have focus");
     }
     let postSubTest = function(cb) {
       window.SocialChatBar.chatbar.removeAll();
       cb();
     }
     // and run the tests.
     runSocialTestWithProvider(manifest, function (finishcb) {
+      SocialSidebar.show();
       runSocialTests(tests, preSubTest, postSubTest, function () {
         finishcb();
       });
     });
   }, true);
   registerCleanupFunction(function() {
     gBrowser.removeTab(tab);
   });
--- a/browser/base/content/test/social/browser_social_errorPage.js
+++ b/browser/base/content/test/social/browser_social_errorPage.js
@@ -37,17 +37,17 @@ function openPanel(url, panelCallback, l
   SocialFlyout.panel.firstChild.addEventListener("load", function panelLoad() {
     SocialFlyout.panel.firstChild.removeEventListener("load", panelLoad, true);
     loadCallback();
   }, true);
 }
 
 function openChat(url, panelCallback, loadCallback) {
   // open a chat window
-  SocialChatBar.openChat(Social.provider, url, panelCallback);
+  SocialChatBar.openChat(SocialSidebar.provider, url, panelCallback);
   SocialChatBar.chatbar.firstChild.addEventListener("DOMContentLoaded", function panelLoad() {
     SocialChatBar.chatbar.firstChild.removeEventListener("DOMContentLoaded", panelLoad, true);
     loadCallback();
   }, true);
 }
 
 function onSidebarLoad(callback) {
   let sbrowser = document.getElementById("social-sidebar-browser");
@@ -74,21 +74,16 @@ let manifest = { // normal provider
   origin: "https://example.com",
   sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
   workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
   iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
 };
 
 function test() {
   waitForExplicitFinish();
-  // we don't want the sidebar to auto-load in these tests..
-  Services.prefs.setBoolPref("social.sidebar.open", false);
-  registerCleanupFunction(function() {
-    Services.prefs.clearUserPref("social.sidebar.open");
-  });
 
   runSocialTestWithProvider(manifest, function (finishcb) {
     runSocialTests(tests, undefined, goOnline, finishcb);
   });
 }
 
 var tests = {
   testSidebar: function(next) {
@@ -108,20 +103,20 @@ var tests = {
           next();
         });
         sbrowser.contentDocument.getElementById("btnTryAgain").click();
       });
       sbrowser.contentDocument.getElementById("btnTryAgain").click();
     });
     // we want the worker to be fully loaded before going offline, otherwise
     // it might fail due to going offline.
-    ensureWorkerLoaded(Social.provider, function() {
+    ensureWorkerLoaded(SocialSidebar.provider, function() {
       // go offline then attempt to load the sidebar - it should fail.
       goOffline();
-      Services.prefs.setBoolPref("social.sidebar.open", true);
+      SocialSidebar.show();
   });
   },
 
   testFlyout: function(next) {
     let panelCallbackCount = 0;
     let panel = document.getElementById("social-flyout-panel");
     // go offline and open a flyout.
     goOffline();
--- a/browser/base/content/test/social/browser_social_flyout.js
+++ b/browser/base/content/test/social/browser_social_flyout.js
@@ -8,28 +8,29 @@ function test() {
   let manifest = { // normal provider
     name: "provider 1",
     origin: "https://example.com",
     sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
     workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
     iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
   };
   runSocialTestWithProvider(manifest, function (finishcb) {
+    SocialSidebar.show();
     runSocialTests(tests, undefined, undefined, finishcb);
   });
 }
 
 var tests = {
   testOpenCloseFlyout: function(next) {
     let panel = document.getElementById("social-flyout-panel");
     panel.addEventListener("popupshowing", function onShowing() {
       panel.removeEventListener("popupshowing", onShowing);
       is(panel.firstChild.contentDocument.readyState, "complete", "panel is loaded prior to showing");
     });
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "got-sidebar-message":
           port.postMessage({topic: "test-flyout-open"});
           break;
         case "got-flyout-visibility":
@@ -48,17 +49,17 @@ var tests = {
           break;
       }
     }
     port.postMessage({topic: "test-init"});
   },
 
   testResizeFlyout: function(next) {
     let panel = document.getElementById("social-flyout-panel");
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "test-init-done":
           port.postMessage({topic: "test-flyout-open"});
           break;
         case "got-flyout-visibility":
@@ -96,20 +97,20 @@ var tests = {
   testCloseSelf: function(next) {
     // window.close is affected by the pref dom.allow_scripts_to_close_windows,
     // which defaults to false, but is set to true by the test harness.
     // so temporarily set it back.
     const ALLOW_SCRIPTS_TO_CLOSE_PREF = "dom.allow_scripts_to_close_windows";
     // note clearUserPref doesn't do what we expect, as the test harness itself
     // changes the pref value - so clearUserPref resets it to false rather than
     // the true setup by the test harness.
-    let oldAllowScriptsToClose = Services.prefs.getBoolPref(ALLOW_SCRIPTS_TO_CLOSE_PREF);    
+    let oldAllowScriptsToClose = Services.prefs.getBoolPref(ALLOW_SCRIPTS_TO_CLOSE_PREF);
     Services.prefs.setBoolPref(ALLOW_SCRIPTS_TO_CLOSE_PREF, false);
     let panel = document.getElementById("social-flyout-panel");
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "test-init-done":
           port.postMessage({topic: "test-flyout-open"});
           break;
         case "got-flyout-visibility":
@@ -137,17 +138,17 @@ var tests = {
       gBrowser.tabContainer.removeEventListener("TabOpen", onTabOpen, true);
       waitForCondition(function() { return panel.state == "closed" }, function() {
         gBrowser.removeTab(event.target);
         next();
       }, "panel should close after tab open");
     }
 
     let panel = document.getElementById("social-flyout-panel");
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "test-init-done":
           port.postMessage({topic: "test-flyout-open"});
           break;
         case "got-flyout-visibility":
--- a/browser/base/content/test/social/browser_social_isVisible.js
+++ b/browser/base/content/test/social/browser_social_isVisible.js
@@ -8,60 +8,60 @@ function test() {
   let manifest = { // normal provider
     name: "provider 1",
     origin: "https://example.com",
     sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
     workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
     iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
   };
   runSocialTestWithProvider(manifest, function (finishcb) {
+    SocialSidebar.show();
     runSocialTests(tests, undefined, undefined, finishcb);
   });
 }
 
 var tests = {
   testSidebarMessage: function(next) {
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     port.postMessage({topic: "test-init"});
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "got-sidebar-message":
           // The sidebar message will always come first, since it loads by default
           ok(true, "got sidebar message");
           port.close();
           next();
           break;
       }
     };
   },
   testIsVisible: function(next) {
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     port.postMessage({topic: "test-init"});
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "got-isVisible-response":
           is(e.data.result, true, "Sidebar should be visible by default");
-          Social.toggleSidebar();
+          SocialSidebar.toggleSidebar();
           port.close();
           next();
       }
     };
     port.postMessage({topic: "test-isVisible"});
   },
   testIsNotVisible: function(next) {
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     port.postMessage({topic: "test-init"});
     port.onmessage = function (e) {
       let topic = e.data.topic;
       switch (topic) {
         case "got-isVisible-response":
           is(e.data.result, false, "Sidebar should be hidden");
-          Services.prefs.clearUserPref("social.sidebar.open");
           port.close();
           next();
       }
     };
     port.postMessage({topic: "test-isVisible"});
   }
 }
--- a/browser/base/content/test/social/browser_social_marks.js
+++ b/browser/base/content/test/social/browser_social_marks.js
@@ -52,24 +52,24 @@ function openWindowAndWaitForInit(callba
   }, topic, false);
 }
 
 function test() {
   waitForExplicitFinish();
 
   let toolbar = document.getElementById("nav-bar");
   let currentsetAtStart = toolbar.currentSet;
-  runSocialTestWithProvider(manifest, function () {
+  runSocialTestWithProvider(manifest, function (finishcb) {
     runSocialTests(tests, undefined, undefined, function () {
       Services.prefs.clearUserPref("social.remote-install.enabled");
       // just in case the tests failed, clear these here as well
       Services.prefs.clearUserPref("social.whitelist");
       ok(CustomizableUI.inDefaultState, "Should be in the default state when we finish");
       CustomizableUI.reset();
-      finish();
+      finishcb();
     });
   });
 }
 
 var tests = {
   testNoButtonOnEnable: function(next) {
     // we expect the addon install dialog to appear, we need to accept the
     // install from the dialog.
--- a/browser/base/content/test/social/browser_social_multiprovider.js
+++ b/browser/base/content/test/social/browser_social_multiprovider.js
@@ -1,96 +1,93 @@
 /* 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/. */
 
 function test() {
   waitForExplicitFinish();
-
   runSocialTestWithProvider(gProviders, function (finishcb) {
+    SocialSidebar.provider = Social.providers[0];
+    SocialSidebar.show();
+    is(Social.providers[0].origin, SocialSidebar.provider.origin, "selected provider in sidebar");
     runSocialTests(tests, undefined, undefined, finishcb);
   });
 }
 
 let gProviders = [
   {
     name: "provider 1",
-    origin: "https://example.com",
-    sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html?provider1",
-    workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+    origin: "https://test1.example.com",
+    sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider1",
+    workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
     iconURL: "chrome://branding/content/icon48.png"
   },
   {
     name: "provider 2",
-    origin: "https://test1.example.com",
-    sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider2",
-    workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
+    origin: "https://test2.example.com",
+    sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html?provider2",
+    workerURL: "https://test2.example.com/browser/browser/base/content/test/social/social_worker.js",
     iconURL: "chrome://branding/content/icon48.png"
   }
 ];
 
 var tests = {
   testProviderSwitch: function(next) {
     let menu = document.getElementById("social-statusarea-popup");
+    let button = document.getElementById("social-sidebar-button");
     function checkProviderMenu(selectedProvider) {
       let menuProviders = menu.querySelectorAll(".social-provider-menuitem");
       is(menuProviders.length, gProviders.length, "correct number of providers listed in the menu");
       // Find the selectedProvider's menu item
       let el = menu.getElementsByAttribute("origin", selectedProvider.origin);
       is(el.length, 1, "selected provider menu item exists");
       is(el[0].getAttribute("checked"), "true", "selected provider menu item is checked");
     }
 
     // the menu is not populated until onpopupshowing, so wait for popupshown
     function theTest() {
-      checkProviderMenu(gProviders[0]);
+      menu.removeEventListener("popupshown", theTest, true);
+      menu.hidePopup(); // doesn't need visibility
+      // first provider should already be visible in the sidebar
+      is(Social.providers[0].origin, SocialSidebar.provider.origin, "selected provider in sidebar");
+      checkProviderMenu(Social.providers[0]);
 
-      // Now wait for the initial provider profile to be set
-      waitForProviderLoad(function() {
-        menu.removeEventListener("popupshown", theTest, true);
-        checkUIStateMatchesProvider(gProviders[0]);
+      // Now activate "provider 2"
+      onSidebarLoad(function() {
+        checkUIStateMatchesProvider(Social.providers[1]);
 
-        // Now activate "provider 2"
-        observeProviderSet(function () {
-          waitForProviderLoad(function() {
-            checkUIStateMatchesProvider(gProviders[1]);
-            // disable social, click on the provider menuitem to switch providers
-            Social.enabled = false;
-            let el = menu.getElementsByAttribute("origin", gProviders[0].origin);
-            is(el.length, 1, "selected provider menu item exists");
-            el[0].click();
-            waitForProviderLoad(function() {
-              checkUIStateMatchesProvider(gProviders[0]);
-              next();
-            });
-          });
+        onSidebarLoad(function() {
+          checkUIStateMatchesProvider(Social.providers[0]);
+          next();
         });
-        Social.activateFromOrigin("https://test1.example.com");
+
+        // show the menu again so the menu is updated with the correct commands
+        function doClick() {
+          // click on the provider menuitem to switch providers
+          let el = menu.getElementsByAttribute("origin", Social.providers[0].origin);
+          is(el.length, 1, "selected provider menu item exists");
+          EventUtils.synthesizeMouseAtCenter(el[0], {});
+        }
+        menu.addEventListener("popupshown", doClick, true);
+        EventUtils.synthesizeMouseAtCenter(button, {});
+
       });
+      SocialSidebar.provider = Social.providers[1];
     };
     menu.addEventListener("popupshown", theTest, true);
-    let button = document.getElementById("social-sidebar-button");
     EventUtils.synthesizeMouseAtCenter(button, {});
   }
 }
 
 function checkUIStateMatchesProvider(provider) {
   // Sidebar
   is(document.getElementById("social-sidebar-browser").getAttribute("src"), provider.sidebarURL, "side bar URL is set");
 }
 
-function observeProviderSet(cb) {
-  Services.obs.addObserver(function providerSet(subject, topic, data) {
-    Services.obs.removeObserver(providerSet, "social:provider-set");
-    info("social:provider-set observer was notified");
-    // executeSoon to let the browser UI observers run first
-    executeSoon(cb);
-  }, "social:provider-set", false);
+function onSidebarLoad(callback) {
+  let sbrowser = document.getElementById("social-sidebar-browser");
+  sbrowser.addEventListener("load", function load() {
+    sbrowser.removeEventListener("load", load, true);
+    // give the load a chance to finish before pulling the rug (ie. calling
+    // next)
+    executeSoon(callback);
+  }, true);
 }
-
-function waitForProviderLoad(cb) {
-  waitForCondition(function() {
-    let sbrowser = document.getElementById("social-sidebar-browser");
-    return Social.provider.profile &&
-           Social.provider.profile.displayName &&
-           sbrowser.docShellIsActive;
-  }, cb, "waitForProviderLoad: provider profile was not set");
-}
--- a/browser/base/content/test/social/browser_social_multiworker.js
+++ b/browser/base/content/test/social/browser_social_multiworker.js
@@ -1,17 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function test() {
   waitForExplicitFinish();
 
   runSocialTestWithProvider(gProviders, function (finishcb) {
-    Social.enabled = true;
     runSocialTests(tests, undefined, undefined, function() {
       finishcb();
     });
   });
 }
 
 let gProviders = [
   {
@@ -52,18 +51,19 @@ var tests = {
     for (let p of Social.providers) {
       oneWorkerTest(p);
     }
 
     waitForCondition(function() messageReceived == Social.providers.length,
                      next, "received messages from all workers");
   },
 
-  testWorkerDisabling: function(next) {
-    Social.enabled = false;
-    is(Social.providers.length, gProviders.length, "providers still available");
-    for (let p of Social.providers) {
-      ok(!p.enabled, "provider disabled");
-      ok(!p.getWorkerPort(), "worker disabled");
-    }
-    next();
-  }
+   testMultipleWorkerEnabling: function(next) {
+     // test that all workers are enabled when we allow multiple workers
+     for (let p of Social.providers) {
+       ok(p.enabled, "provider enabled");
+       let port = p.getWorkerPort();
+       ok(port, "worker enabled");
+       port.close();
+     }
+     next();
+   }
 }
--- a/browser/base/content/test/social/browser_social_perwindowPB.js
+++ b/browser/base/content/test/social/browser_social_perwindowPB.js
@@ -50,17 +50,17 @@ function test() {
         finishcb();
       });
     });
   });
 }
 
 var tests = {
   testPrivateBrowsing: function(next) {
-    let port = Social.provider.getWorkerPort();
+    let port = SocialSidebar.provider.getWorkerPort();
     ok(port, "provider has a port");
     postAndReceive(port, "test-init", "test-init-done", function() {
       // social features should all be enabled in the existing window.
       info("checking main window ui");
       ok(window.SocialUI.enabled, "social is enabled in normal window");
       checkSocialUI(window);
       // open a new private-window
       openPBWindow(function(pbwin) {
--- a/browser/base/content/test/social/browser_social_sidebar.js
+++ b/browser/base/content/test/social/browser_social_sidebar.js
@@ -1,56 +1,58 @@
 /* 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/. */
 
+let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+
+let manifest = { // normal provider
+  name: "provider 1",
+  origin: "https://example.com",
+  sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
+  workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
+  iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
+};
+
 function test() {
   waitForExplicitFinish();
 
-  let manifest = { // normal provider
-    name: "provider 1",
-    origin: "https://example.com",
-    sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
-    workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
-    iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
-  };
-  runSocialTestWithProvider(manifest, doTest);
+  SocialService.addProvider(manifest, function() {
+    // the test will remove the provider
+    doTest();
+  });
 }
 
-function doTest(finishcb) {
+function doTest() {
   ok(SocialSidebar.canShow, "social sidebar should be able to be shown");
-  ok(SocialSidebar.opened, "social sidebar should be open by default");
+  ok(!SocialSidebar.opened, "social sidebar should not be open by default");
+  SocialSidebar.show();
 
   let command = document.getElementById("Social:ToggleSidebar");
   let sidebar = document.getElementById("social-sidebar-box");
   let browser = sidebar.lastChild;
 
   function checkShown(shouldBeShown) {
     is(command.getAttribute("checked"), shouldBeShown ? "true" : "false",
        "toggle command should be " + (shouldBeShown ? "checked" : "unchecked"));
     is(sidebar.hidden, !shouldBeShown,
        "sidebar should be " + (shouldBeShown ? "visible" : "hidden"));
-    // The sidebar.open pref only reflects the actual state of the sidebar
-    // when social is enabled.
-    if (Social.enabled)
-      is(Services.prefs.getBoolPref("social.sidebar.open"), shouldBeShown,
-         "sidebar open pref should be " + shouldBeShown);
     if (shouldBeShown) {
-      is(browser.getAttribute('src'), Social.provider.sidebarURL, "sidebar url should be set");
+      is(browser.getAttribute('src'), SocialSidebar.provider.sidebarURL, "sidebar url should be set");
       // We don't currently check docShellIsActive as this is only set
       // after load event fires, and the tests below explicitly wait for this
       // anyway.
     }
     else {
       ok(!browser.docShellIsActive, "sidebar should have an inactive docshell");
       // sidebar will only be immediately unloaded (and thus set to
       // about:blank) when canShow is false.
       if (SocialSidebar.canShow) {
         // should not have unloaded so will still be the provider URL.
-        is(browser.getAttribute('src'), Social.provider.sidebarURL, "sidebar url should be set");
+        is(browser.getAttribute('src'), SocialSidebar.provider.sidebarURL, "sidebar url should be set");
       } else {
         // should have been an immediate unload.
         is(browser.getAttribute('src'), "about:blank", "sidebar url should be blank");
       }
     }
   }
 
   // First check the the sidebar is initially visible, and loaded
@@ -62,44 +64,36 @@ function doTest(finishcb) {
 
     checkShown(false);
 
     browser.addEventListener("socialFrameShow", function sidebarshow() {
       browser.removeEventListener("socialFrameShow", sidebarshow);
 
       checkShown(true);
 
-      // Set Social.enabled = false and check everything is as expected.
-      Social.enabled = false;
-      checkShown(false);
-
-      Social.enabled = true;
-      checkShown(true);
-
-      // And an edge-case - disable social and reset the provider.
-      Social.provider = null;
-      Social.enabled = false;
-      checkShown(false);
-
-      // Finish the test
-      finishcb();
+      // disable social.
+      SocialService.removeProvider(SocialSidebar.provider.origin, function() {
+        checkShown(false);
+        is(Social.providers.length, 0, "no providers left");
+        defaultFinishChecks();
+        // Finish the test
+        executeSoon(finish);
+      });
     });
 
     // Toggle it back on
     info("Toggling sidebar back on");
-    Social.toggleSidebar();
+    SocialSidebar.toggleSidebar();
   });
 
   // use port messaging to ensure that the sidebar is both loaded and
   // ready before we run other tests
-  let port = Social.provider.getWorkerPort();
+  let port = SocialSidebar.provider.getWorkerPort();
   port.postMessage({topic: "test-init"});
   port.onmessage = function (e) {
     let topic = e.data.topic;
     switch (topic) {
       case "got-sidebar-message":
         ok(true, "sidebar is loaded and ready");
-        Social.toggleSidebar();
+        SocialSidebar.toggleSidebar();
     }
   };
 }
-
-// XXX test sidebar in popup
--- a/browser/base/content/test/social/browser_social_window.js
+++ b/browser/base/content/test/social/browser_social_window.js
@@ -6,156 +6,200 @@
 
 let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
 
 // This function should "reset" Social such that the next time Social.init()
 // is called (eg, when a new window is opened), it re-performs all
 // initialization.
 function resetSocial() {
   Social.initialized = false;
-  Social._provider = null;
   Social.providers = [];
   // *sob* - listeners keep getting added...
   SocialService._providerListeners.clear();
 }
 
 let createdWindows = [];
 
-function openWindowAndWaitForInit(callback) {
+function openWindowAndWaitForInit(parentWin, callback) {
   // this notification tells us SocialUI.init() has been run...
   let topic = "browser-delayed-startup-finished";
-  let w = OpenBrowserWindow();
+  let w = parentWin.OpenBrowserWindow();
   createdWindows.push(w);
   Services.obs.addObserver(function providerSet(subject, topic, data) {
     Services.obs.removeObserver(providerSet, topic);
     info(topic + " observer was notified - continuing test");
     executeSoon(() => callback(w));
   }, topic, false);
 }
 
 function closeOneWindow(cb) {
   let w = createdWindows.pop();
   if (!w) {
     cb();
     return;
   }
   waitForCondition(function() w.closed,
                    function() {
+                    info("window closed, " + createdWindows.length + " windows left");
                     closeOneWindow(cb);
                     }, "window did not close");
   w.close();
 }
 
 function postTestCleanup(cb) {
-  closeOneWindow(function() {
-    Services.prefs.clearUserPref("social.enabled");
-    cb();
-  });
+  closeOneWindow(cb);
 }
 
 let manifest = { // normal provider
   name: "provider 1",
   origin: "https://example.com",
   sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html",
   workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
   iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
 };
+let manifest2 = { // used for testing install
+  name: "provider test1",
+  origin: "https://test1.example.com",
+  workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
+  sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html",
+  iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
+};
 
 function test() {
   waitForExplicitFinish();
   runSocialTests(tests, undefined, postTestCleanup);
 }
 
 let tests = {
   // check when social is totally disabled at startup (ie, no providers enabled)
   testInactiveStartup: function(cbnext) {
     is(Social.providers.length, 0, "needs zero providers to start this test.");
     ok(!SocialService.hasEnabledProviders, "no providers are enabled");
     resetSocial();
-    openWindowAndWaitForInit(function(w1) {
+    openWindowAndWaitForInit(window, function(w1) {
       checkSocialUI(w1);
       // Now social is (re-)initialized, open a secondary window and check that.
-      openWindowAndWaitForInit(function(w2) {
+      openWindowAndWaitForInit(window, function(w2) {
         checkSocialUI(w2);
         checkSocialUI(w1);
         cbnext();
       });
     });
   },
 
   // Check when providers are enabled and social is turned on at startup.
   testEnabledStartup: function(cbnext) {
-    runSocialTestWithProvider(manifest, function (finishcb) {
-      resetSocial();
-      openWindowAndWaitForInit(function(w1) {
-        ok(Social.enabled, "social is enabled");
-        ok(SocialService.hasEnabledProviders, "providers are enabled");
-        checkSocialUI(w1);
-        // now init is complete, open a second window
-        openWindowAndWaitForInit(function(w2) {
-          checkSocialUI(w2);
-          checkSocialUI(w1);
-          // disable social and re-check
-          Services.prefs.setBoolPref("social.enabled", false);
-          executeSoon(function() { // let all the UI observers run...
-            ok(!Social.enabled, "social is disabled");
-            checkSocialUI(w2);
+    setManifestPref("social.manifest.test", manifest);
+    ok(!SocialSidebar.opened, "sidebar is closed initially");
+    SocialService.addProvider(manifest, function() {
+      SocialService.addProvider(manifest2, function (provider) {
+        SocialSidebar.show();
+        waitForCondition(function() SocialSidebar.opened,
+                     function() {
+          ok(SocialSidebar.opened, "first window sidebar is open");
+          openWindowAndWaitForInit(window, function(w1) {
+            ok(w1.SocialSidebar.opened, "new window sidebar is open");
+            ok(SocialService.hasEnabledProviders, "providers are enabled");
             checkSocialUI(w1);
-            finishcb();
+            // now init is complete, open a second window
+            openWindowAndWaitForInit(window, function(w2) {
+              ok(w1.SocialSidebar.opened, "w1 sidebar is open");
+              ok(w2.SocialSidebar.opened, "w2 sidebar is open");
+              checkSocialUI(w2);
+              checkSocialUI(w1);
+
+              // disable social and re-check
+              SocialService.removeProvider(manifest.origin, function() {
+                SocialService.removeProvider(manifest2.origin, function() {
+                  ok(!Social.enabled, "social is disabled");
+                  is(Social.providers.length, 0, "no providers");
+                  ok(!w1.SocialSidebar.opened, "w1 sidebar is closed");
+                  ok(!w2.SocialSidebar.opened, "w2 sidebar is closed");
+                  checkSocialUI(w2);
+                  checkSocialUI(w1);
+                  Services.prefs.clearUserPref("social.manifest.test");
+                  cbnext();
+                });
+              });
+            });
           });
-        });
-      });
+        }, "sidebar did not open");
+      }, cbnext);
     }, cbnext);
   },
 
-  // Check when providers are enabled but social is turned off at startup.
-  testDisabledStartup: function(cbnext) {
-    setManifestPref("social.manifest.test", manifest);
-    SocialService.addProvider(manifest, function (provider) {
-      Services.prefs.setBoolPref("social.enabled", false);
-      resetSocial();
-      ok(SocialService.hasEnabledProviders, "providers are enabled");
-      openWindowAndWaitForInit(function(w1) {
-        ok(!Social.enabled, "social is disabled");
-        checkSocialUI(w1);
-        // now init is complete, open a second window
-        openWindowAndWaitForInit(function(w2) {
-          checkSocialUI(w2);
-          checkSocialUI(w1);
-          // enable social and re-check
-          Services.prefs.setBoolPref("social.enabled", true);
-          executeSoon(function() { // let all the UI observers run...
-            ok(Social.enabled, "social is enabled");
-            checkSocialUI(w2);
-            checkSocialUI(w1);
-            SocialService.removeProvider(manifest.origin, function() {
-              Services.prefs.clearUserPref("social.manifest.test");
-              cbnext();
-            });
-          });
+  // Check per window sidebar functionality, including migration from using
+  // prefs to using session state, and state inheritance of windows (new windows
+  // inherit state from the opener).
+  testPerWindowSidebar: function(cbnext) {
+    function finishCheck() {
+      // disable social and re-check
+      SocialService.removeProvider(manifest.origin, function() {
+        SocialService.removeProvider(manifest2.origin, function() {
+          ok(!Social.enabled, "social is disabled");
+          is(Social.providers.length, 0, "no providers");
+          Services.prefs.clearUserPref("social.manifest.test");
+          cbnext();
         });
       });
-    }, cbnext);
-  },
+    }
 
-  // Check when the last provider is disabled.
-  testRemoveProvider: function(cbnext) {
     setManifestPref("social.manifest.test", manifest);
-    SocialService.addProvider(manifest, function (provider) {
-      openWindowAndWaitForInit(function(w1) {
-        checkSocialUI(w1);
-        // now init is complete, open a second window
-        openWindowAndWaitForInit(function(w2) {
-          checkSocialUI(w2);
-          // disable the current provider.
-          SocialService.removeProvider(manifest.origin, function() {
-            ok(!Social.enabled, "social is disabled");
-            is(Social.providers.length, 0, "no providers");
+    ok(!SocialSidebar.opened, "sidebar is closed initially");
+    SocialService.addProvider(manifest, function() {
+      SocialService.addProvider(manifest2, function (provider) {
+        // test the migration of the social.sidebar.open pref. We'll set a user
+        // level pref to indicate it was open (along with the old
+        // social.provider.current pref), then we'll open a window. During the
+        // restoreState of the window, those prefs should be migrated, and the
+        // sidebar should be opened.  Both prefs are then removed.
+        Services.prefs.setCharPref("social.provider.current", "https://example.com");
+        Services.prefs.setBoolPref("social.sidebar.open", true);
+
+        openWindowAndWaitForInit(window, function(w1) {
+          ok(w1.SocialSidebar.opened, "new window sidebar is open");
+          ok(SocialService.hasEnabledProviders, "providers are enabled");
+          ok(!Services.prefs.prefHasUserValue("social.provider.current"), "social.provider.current pref removed");
+          ok(!Services.prefs.prefHasUserValue("social.sidebar.open"), "social.sidebar.open pref removed");
+          checkSocialUI(w1);
+          // now init is complete, open a second window, it's state should be the same as the opener
+          openWindowAndWaitForInit(w1, function(w2) {
+            ok(w1.SocialSidebar.opened, "w1 sidebar is open");
+            ok(w2.SocialSidebar.opened, "w2 sidebar is open");
             checkSocialUI(w2);
             checkSocialUI(w1);
-            Services.prefs.clearUserPref("social.manifest.test");
-            cbnext();
+
+            // change the sidebar in w2
+            w2.SocialSidebar.show(manifest2.origin);
+            let sbrowser1 = w1.document.getElementById("social-sidebar-browser");
+            is(manifest.origin, sbrowser1.getAttribute("origin"), "w1 sidebar origin matches");
+            let sbrowser2 = w2.document.getElementById("social-sidebar-browser");
+            is(manifest2.origin, sbrowser2.getAttribute("origin"), "w2 sidebar origin matches");
+
+            // hide sidebar, w1 sidebar should still be open
+            w2.SocialSidebar.hide();
+            ok(w1.SocialSidebar.opened, "w1 sidebar is opened");
+            ok(!w2.SocialSidebar.opened, "w2 sidebar is closed");
+            ok(sbrowser2.parentNode.hidden, "w2 sidebar is hidden");
+
+            // open a 3rd window from w2, it should inherit the state of w2
+            openWindowAndWaitForInit(w2, function(w3) {
+              // since the sidebar is not open, we need to ensure the provider
+              // is selected to test we inherited the provider from the opener
+              w3.SocialSidebar.ensureProvider();
+              is(w3.SocialSidebar.provider, w2.SocialSidebar.provider, "w3 has same provider as w2");
+              ok(!w3.SocialSidebar.opened, "w2 sidebar is closed");
+
+              // open a 4th window from w1, it should inherit the state of w1
+              openWindowAndWaitForInit(w1, function(w4) {
+                is(w4.SocialSidebar.provider, w1.SocialSidebar.provider, "w4 has same provider as w1");
+                ok(w4.SocialSidebar.opened, "w4 sidebar is opened");
+
+                finishCheck();
+              });
+            });
+
           });
         });
-      });
+      }, cbnext);
     }, cbnext);
-  },
+  }
 }
--- a/browser/base/content/test/social/browser_social_workercrash.js
+++ b/browser/base/content/test/social/browser_social_workercrash.js
@@ -12,20 +12,18 @@ let {getFrameWorkerHandle} = Cu.import("
 
 function test() {
   waitForExplicitFinish();
 
   // We need to ensure all our workers are in the same content process.
   Services.prefs.setIntPref("dom.ipc.processCount", 1);
 
   runSocialTestWithProvider(gProviders, function (finishcb) {
-    Social.enabled = true;
     runSocialTests(tests, undefined, undefined, function() {
       Services.prefs.clearUserPref("dom.ipc.processCount");
-      Services.prefs.clearUserPref("social.sidebar.open");
       finishcb();
     });
   });
 }
 
 let gProviders = [
   {
     name: "provider 1",
@@ -76,17 +74,17 @@ var tests = {
             sbrowser.contentDocument.getElementById("btnTryAgain").click();
           });
         });
         Services.obs.addObserver(observer, 'ipc:content-shutdown', false);
         // and cause the crash.
         mm.sendAsyncMessage("social-test:crash");
       });
     })
-    Services.prefs.setBoolPref("social.sidebar.open", true);
+    SocialSidebar.show();
   },
 }
 
 function onSidebarLoad(callback) {
   let sbrowser = document.getElementById("social-sidebar-browser");
   sbrowser.addEventListener("load", function load() {
     sbrowser.removeEventListener("load", load, true);
     callback();
--- a/browser/base/content/test/social/head.js
+++ b/browser/base/content/test/social/head.js
@@ -69,16 +69,18 @@ function defaultFinishChecks() {
 
 function runSocialTestWithProvider(manifest, callback, finishcallback) {
   let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
 
   let manifests = Array.isArray(manifest) ? manifest : [manifest];
 
   // Check that none of the provider's content ends up in history.
   function finishCleanUp() {
+    ok(!SocialSidebar.provider, "no provider in sidebar");
+    SessionStore.setWindowValue(window, "socialSidebar", "");
     for (let i = 0; i < manifests.length; i++) {
       let m = manifests[i];
       for (let what of ['sidebarURL', 'workerURL', 'iconURL']) {
         if (m[what]) {
           yield promiseSocialUrlNotRemembered(m[what]);
         }
       };
     }
@@ -113,19 +115,16 @@ function runSocialTestWithProvider(manif
             info("Failed to clean up provider " + origin + ": " + ex);
           }
         }
       }
       removeProvider(m.origin, callback);
     });
   }
   function finishSocialTest(cleanup) {
-    // disable social before removing the providers to avoid providers
-    // being activated immediately before we get around to removing it.
-    Services.prefs.clearUserPref("social.enabled");
     removeAddedProviders(cleanup);
   }
 
   let providersAdded = 0;
   let firstProvider;
 
   manifests.forEach(function (m) {
     SocialService.addProvider(m, function(provider) {
@@ -136,23 +135,24 @@ function runSocialTestWithProvider(manif
       // we want to set the first specified provider as the UI's provider
       if (provider.origin == manifests[0].origin) {
         firstProvider = provider;
       }
 
       // If we've added all the providers we need, call the callback to start
       // the tests (and give it a callback it can call to finish them)
       if (providersAdded == manifests.length) {
-        // Set the UI's provider (which enables the feature)
-        Social.provider = firstProvider;
-
         registerCleanupFunction(function () {
           finishSocialTest(true);
         });
-        callback(finishSocialTest);
+        waitForCondition(function() provider.enabled,
+                         function() {
+          info("provider has been enabled");
+          callback(finishSocialTest);
+        }, "providers added and enabled");
       }
     });
   });
 }
 
 function runSocialTests(tests, cbPreTest, cbPostTest, cbFinish) {
   let testIter = Iterator(tests);
   let providersAtStart = Social.providers.length;
@@ -199,17 +199,16 @@ function runSocialTests(tests, cbPreTest
 }
 
 // A fairly large hammer which checks all aspects of the SocialUI for
 // internal consistency.
 function checkSocialUI(win) {
   let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
   win = win || window;
   let doc = win.document;
-  let provider = Social.provider;
   let enabled = win.SocialUI.enabled;
   let active = Social.providers.length > 0 && !win.SocialUI._chromeless &&
                !PrivateBrowsingUtils.isWindowPrivate(win);
 
   // if we have enabled providers, we should also have instances of those
   // providers
   if (SocialService.hasEnabledProviders) {
     ok(Social.providers.length > 0, "providers are enabled");
@@ -232,62 +231,56 @@ function checkSocialUI(win) {
       is(a, b, msg)
     else
       ++numGoodTests;
   }
   function isbool(a, b, msg) {
     _is(!!a, !!b, msg);
   }
   isbool(win.SocialSidebar.canShow, enabled, "social sidebar active?");
-  if (enabled)
-    isbool(win.SocialSidebar.opened, enabled, "social sidebar open?");
   isbool(win.SocialChatBar.isAvailable, enabled, "chatbar available?");
   isbool(!win.SocialChatBar.chatbar.hidden, enabled, "chatbar visible?");
 
-  // the menus should always have the provider name
-  if (provider) {
-    let contextMenus = [
-      {
-        type: "link",
-        id: "context-marklinkMenu",
-        label: "social.marklinkMenu.label"
-      },
-      {
-        type: "page",
-        id: "context-markpageMenu",
-        label: "social.markpageMenu.label"
-      }
-    ];
+  let contextMenus = [
+    {
+      type: "link",
+      id: "context-marklinkMenu",
+      label: "social.marklinkMenu.label"
+    },
+    {
+      type: "page",
+      id: "context-markpageMenu",
+      label: "social.markpageMenu.label"
+    }
+  ];
 
-    for (let c of contextMenus) {
-      let leMenu = document.getElementById(c.id);
-      let parent, menus;
-      let markProviders = SocialMarks.getProviders();
-      if (markProviders.length > SocialMarks.MENU_LIMIT) {
-        // menus should be in a submenu, not in the top level of the context menu
-        parent = leMenu.firstChild;
-        menus = document.getElementsByClassName("context-mark" + c.type);
-        _is(menus.length, 0, "menu's are not in main context menu\n");
-        menus = parent.childNodes;
-        _is(menus.length, markProviders.length, c.id + " menu exists for each mark provider");
-      } else {
-        // menus should be in the top level of the context menu, not in a submenu
-        parent = leMenu.parentNode;
-        menus = document.getElementsByClassName("context-mark" + c.type);
-        _is(menus.length, markProviders.length, c.id + " menu exists for each mark provider");
-        menus = leMenu.firstChild.childNodes;
-        _is(menus.length, 0, "menu's are not in context submenu\n");
-      }
-      for (let m of menus)
-        _is(m.parentNode, parent, "menu has correct parent");
+  for (let c of contextMenus) {
+    let leMenu = document.getElementById(c.id);
+    let parent, menus;
+    let markProviders = SocialMarks.getProviders();
+    if (markProviders.length > SocialMarks.MENU_LIMIT) {
+      // menus should be in a submenu, not in the top level of the context menu
+      parent = leMenu.firstChild;
+      menus = document.getElementsByClassName("context-mark" + c.type);
+      _is(menus.length, 0, "menu's are not in main context menu\n");
+      menus = parent.childNodes;
+      _is(menus.length, markProviders.length, c.id + " menu exists for each mark provider");
+    } else {
+      // menus should be in the top level of the context menu, not in a submenu
+      parent = leMenu.parentNode;
+      menus = document.getElementsByClassName("context-mark" + c.type);
+      _is(menus.length, markProviders.length, c.id + " menu exists for each mark provider");
+      menus = leMenu.firstChild.childNodes;
+      _is(menus.length, 0, "menu's are not in context submenu\n");
     }
+    for (let m of menus)
+      _is(m.parentNode, parent, "menu has correct parent");
   }
 
   // and for good measure, check all the social commands.
-  isbool(!doc.getElementById("Social:Toggle").hidden, active, "Social:Toggle visible?");
   isbool(!doc.getElementById("Social:ToggleSidebar").hidden, enabled, "Social:ToggleSidebar visible?");
   isbool(!doc.getElementById("Social:ToggleNotifications").hidden, enabled, "Social:ToggleNotifications visible?");
   isbool(!doc.getElementById("Social:FocusChat").hidden, enabled, "Social:FocusChat visible?");
   isbool(doc.getElementById("Social:FocusChat").getAttribute("disabled"), enabled ? "false" : "true", "Social:FocusChat disabled?");
 
   // and report on overall success of failure of the various checks here.
   is(numGoodTests, numTests, "The Social UI tests succeeded.")
 }
@@ -446,17 +439,17 @@ function get3ChatsForCollapsing(mode, cb
         }, mode);
       }, mode);
     });
   }, mode);
 }
 
 function makeChat(mode, uniqueid, cb) {
   info("making a chat window '" + uniqueid +"'");
-  let provider = Social.provider;
+  let provider = SocialSidebar.provider;
   const chatUrl = provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
   let isOpened = window.SocialChatBar.openChat(provider, chatUrl + "?id=" + uniqueid, function(chat) {
     info("chat window has opened");
     // we can't callback immediately or we might close the chat during
     // this event which upsets the implementation - it is only 1/2 way through
     // handling the load event.
     chat.document.title = uniqueid;
     executeSoon(cb);
--- a/browser/components/customizableui/src/CustomizableUI.jsm
+++ b/browser/components/customizableui/src/CustomizableUI.jsm
@@ -3444,17 +3444,22 @@ function OverflowableToolbar(aToolbarNod
 
   this._toolbar.setAttribute("overflowable", "true");
   let doc = this._toolbar.ownerDocument;
   this._target = this._toolbar.customizationTarget;
   this._list = doc.getElementById(this._toolbar.getAttribute("overflowtarget"));
   this._list.toolbox = this._toolbar.toolbox;
   this._list.customizationTarget = this._list;
 
-  Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
+  let window = this._toolbar.ownerDocument.defaultView;
+  if (window.gBrowserInit.delayedStartupFinished) {
+    this.init();
+  } else {
+    Services.obs.addObserver(this, "browser-delayed-startup-finished", false);
+  }
 }
 
 OverflowableToolbar.prototype = {
   initialized: false,
   _forceOnOverflow: false,
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "browser-delayed-startup-finished" &&
@@ -3470,17 +3475,18 @@ OverflowableToolbar.prototype = {
     window.addEventListener("resize", this);
     window.gNavToolbox.addEventListener("customizationstarting", this);
     window.gNavToolbox.addEventListener("aftercustomization", this);
 
     let chevronId = this._toolbar.getAttribute("overflowbutton");
     this._chevron = doc.getElementById(chevronId);
     this._chevron.addEventListener("command", this);
 
-    this._panel = doc.getElementById("widget-overflow");
+    let panelId = this._toolbar.getAttribute("overflowpanel");
+    this._panel = doc.getElementById(panelId);
     this._panel.addEventListener("popuphiding", this);
     CustomizableUIInternal.addPanelCloseListeners(this._panel);
 
     CustomizableUI.addListener(this);
 
     // The 'overflow' event may have been fired before init was called.
     if (this._toolbar.overflowedDuringConstruction) {
       this.onOverflow(this._toolbar.overflowedDuringConstruction);
--- a/browser/components/customizableui/src/CustomizeMode.jsm
+++ b/browser/components/customizableui/src/CustomizeMode.jsm
@@ -332,16 +332,17 @@ CustomizeMode.prototype = {
     }
 
     this._removeExtraToolbarsIfEmpty();
 
     CustomizableUI.removeListener(this);
 
     this.document.removeEventListener("keypress", this);
     this.window.PanelUI.menuButton.removeEventListener("mousedown", this);
+    this.window.PanelUI.menuButton.open = false;
 
     this.window.PanelUI.beginBatchUpdate();
 
     this._removePanelCustomizationPlaceholders();
 
     let window = this.window;
     let document = this.document;
     let documentElement = document.documentElement;
@@ -420,17 +421,16 @@ CustomizeMode.prototype = {
       // And drop all area references.
       this.areas = [];
 
       // Let everybody in this window know that we're starting to
       // exit customization mode.
       CustomizableUI.dispatchToolboxEvent("customizationending", {}, window);
 
       window.PanelUI.setMainView(window.PanelUI.mainView);
-      window.PanelUI.menuButton.open = false;
       window.PanelUI.menuButton.disabled = false;
 
       let customizeButton = document.getElementById("PanelUI-customize");
       customizeButton.setAttribute("exitLabel", customizeButton.getAttribute("label"));
       customizeButton.setAttribute("label", customizeButton.getAttribute("enterLabel"));
       customizeButton.setAttribute("exitTooltiptext", customizeButton.getAttribute("tooltiptext"));
       customizeButton.setAttribute("tooltiptext", customizeButton.getAttribute("enterTooltiptext"));
 
@@ -661,17 +661,17 @@ CustomizeMode.prototype = {
   },
 
   //XXXunf Maybe this should use -moz-element instead of wrapping the node?
   //       Would ensure no weird interactions/event handling from original node,
   //       and makes it possible to put this in a lazy-loaded iframe/real tab
   //       while still getting rid of the need for overlays.
   makePaletteItem: function(aWidget, aPlace) {
     let widgetNode = aWidget.forWindow(this.window).node;
-    let wrapper = this.createWrapper(widgetNode, aPlace);
+    let wrapper = this.createOrUpdateWrapper(widgetNode, aPlace);
     wrapper.appendChild(widgetNode);
     return wrapper;
   },
 
   depopulatePalette: function() {
     return Task.spawn(function() {
       this.visiblePalette.hidden = true;
       let paletteChild = this.visiblePalette.firstChild;
@@ -723,36 +723,43 @@ CustomizeMode.prototype = {
 
     return deferred.promise;
   },
 
   wrapToolbarItem: function(aNode, aPlace) {
     if (!this.isCustomizableItem(aNode)) {
       return aNode;
     }
-    let wrapper = this.createWrapper(aNode, aPlace);
+    let wrapper = this.createOrUpdateWrapper(aNode, aPlace);
+
     // It's possible that this toolbar node is "mid-flight" and doesn't have
     // a parent, in which case we skip replacing it. This can happen if a
     // toolbar item has been dragged into the palette. In that case, we tell
     // CustomizableUI to remove the widget from its area before putting the
     // widget in the palette - so the node will have no parent.
     if (aNode.parentNode) {
       aNode = aNode.parentNode.replaceChild(wrapper, aNode);
     }
     wrapper.appendChild(aNode);
     return wrapper;
   },
 
-  createWrapper: function(aNode, aPlace) {
-    let wrapper = this.document.createElement("toolbarpaletteitem");
+  createOrUpdateWrapper: function(aNode, aPlace, aIsUpdate) {
+    let wrapper;
+    if (aIsUpdate && aNode.parentNode && aNode.parentNode.localName == "toolbarpaletteitem") {
+      wrapper = aNode.parentNode;
+      aPlace = wrapper.getAttribute("place");
+    } else {
+      wrapper = this.document.createElement("toolbarpaletteitem");
+      // "place" is used by toolkit to add the toolbarpaletteitem-palette
+      // binding to a toolbarpaletteitem, which gives it a label node for when
+      // it's sitting in the palette.
+      wrapper.setAttribute("place", aPlace);
+    }
 
-    // "place" is used by toolkit to add the toolbarpaletteitem-palette
-    // binding to a toolbarpaletteitem, which gives it a label node for when
-    // it's sitting in the palette.
-    wrapper.setAttribute("place", aPlace);
 
     // Ensure the wrapped item doesn't look like it's in any special state, and
     // can't be interactved with when in the customization palette.
     if (aNode.hasAttribute("command")) {
       wrapper.setAttribute("itemcommand", aNode.getAttribute("command"));
       aNode.removeAttribute("command");
     }
 
@@ -797,18 +804,21 @@ CustomizeMode.prototype = {
         currentContextMenu != contextMenuForPlace) {
       aNode.setAttribute("wrapped-context", currentContextMenu);
       aNode.setAttribute("wrapped-contextAttrName", contextMenuAttrName)
       aNode.removeAttribute(contextMenuAttrName);
     } else if (currentContextMenu == contextMenuForPlace) {
       aNode.removeAttribute(contextMenuAttrName);
     }
 
-    wrapper.addEventListener("mousedown", this);
-    wrapper.addEventListener("mouseup", this);
+    // Only add listeners for newly created wrappers:
+    if (!aIsUpdate) {
+      wrapper.addEventListener("mousedown", this);
+      wrapper.addEventListener("mouseup", this);
+    }
 
     return wrapper;
   },
 
   deferredUnwrapToolbarItem: function(aWrapper) {
     let deferred = Promise.defer();
     dispatchFunction(function() {
       let item = null;
@@ -1339,16 +1349,18 @@ CustomizeMode.prototype = {
 
     if (this._dragOverItem && dragOverItem != this._dragOverItem) {
       this._cancelDragActive(this._dragOverItem, dragOverItem);
     }
 
     if (dragOverItem != this._dragOverItem || dragValue != dragOverItem.getAttribute("dragover")) {
       if (dragOverItem != targetArea.customizationTarget) {
         this._setDragActive(dragOverItem, dragValue, draggedItemId, targetIsToolbar);
+      } else if (targetIsToolbar) {
+        this._updateToolbarCustomizationOutline(this.window, targetArea);
       }
       this._dragOverItem = dragOverItem;
     }
 
     aEvent.preventDefault();
     aEvent.stopPropagation();
   },
 
@@ -1738,27 +1750,25 @@ CustomizeMode.prototype = {
     let rect = aDraggedItem.parentNode.getBoundingClientRect();
     size = {width: rect.width, height: rect.height};
     // Cache the found value of size for this target.
     itemMap.set(targetArea, size);
 
     if (targetArea != currentArea) {
       this.unwrapToolbarItem(aDraggedItem.parentNode);
       // Put the item back into its previous position.
-      if (currentSibling)
-        currentParent.insertBefore(aDraggedItem, currentSibling);
-      else
-        currentParent.appendChild(aDraggedItem);
+      currentParent.insertBefore(aDraggedItem, currentSibling);
       // restore the areaType
       if (areaType) {
         if (currentType === false)
           aDraggedItem.removeAttribute(kAreaType);
         else
           aDraggedItem.setAttribute(kAreaType, currentType);
       }
+      this.createOrUpdateWrapper(aDraggedItem, null, true);
       CustomizableUI.onWidgetDrag(aDraggedItem.id);
     } else {
       aDraggedItem.parentNode.hidden = true;
     }
     return size;
   },
 
   _getCustomizableParent: function(aElement) {
--- a/browser/components/customizableui/test/browser.ini
+++ b/browser/components/customizableui/test/browser.ini
@@ -69,9 +69,10 @@ skip-if = os == "linux"
 [browser_968565_insert_before_hidden_items.js]
 [browser_969427_recreate_destroyed_widget_after_reset.js]
 [browser_969661_character_encoding_navbar_disabled.js]
 [browser_970511_undo_restore_default.js]
 [browser_972267_customizationchange_events.js]
 [browser_973932_addonbar_currentset.js]
 [browser_975719_customtoolbars_behaviour.js]
 [browser_978084_dragEnd_after_move.js]
+[browser_980155_add_overflow_toolbar.js]
 [browser_panel_toggle.js]
--- a/browser/components/customizableui/test/browser_880164_customization_context_menus.js
+++ b/browser/components/customizableui/test/browser_880164_customization_context_menus.js
@@ -335,8 +335,37 @@ add_task(function() {
   contextMenu.hidePopup();
   yield hiddenContextPromise;
 
   let hiddenPromise = promisePanelHidden(window);
   PanelUI.hide();
   yield hiddenPromise;
 });
 
+
+// Bug 982027 - moving icon around removes custom context menu.
+add_task(function() {
+  let widgetId = "custom-context-menu-toolbarbutton";
+  let expectedContext = "myfancycontext";
+  let widget = createDummyXULButton(widgetId, "Test ctxt menu");
+  widget.setAttribute("context", expectedContext);
+  CustomizableUI.addWidgetToArea(widgetId, CustomizableUI.AREA_NAVBAR);
+  is(widget.getAttribute("context"), expectedContext, "Should have context menu when added to the toolbar.");
+
+  yield startCustomizing();
+  is(widget.getAttribute("context"), "", "Should not have own context menu in the toolbar now that we're customizing.");
+  is(widget.getAttribute("wrapped-context"), expectedContext, "Should keep own context menu wrapped when in toolbar.");
+
+  let panel = PanelUI.contents;
+  simulateItemDrag(widget, panel);
+  is(widget.getAttribute("context"), "", "Should not have own context menu when in the panel.");
+  is(widget.getAttribute("wrapped-context"), expectedContext, "Should keep own context menu wrapped now that we're in the panel.");
+
+  simulateItemDrag(widget, document.getElementById("nav-bar").customizationTarget);
+  is(widget.getAttribute("context"), "", "Should not have own context menu when back in toolbar because we're still customizing.");
+  is(widget.getAttribute("wrapped-context"), expectedContext, "Should keep own context menu wrapped now that we're back in the toolbar.");
+
+  yield endCustomizing();
+  is(widget.getAttribute("context"), expectedContext, "Should have context menu again now that we're out of customize mode.");
+  CustomizableUI.removeWidgetFromArea(widgetId);
+  widget.remove();
+  ok(CustomizableUI.inDefaultState, "Should be in default state after removing button.");
+});
new file mode 100644
--- /dev/null
+++ b/browser/components/customizableui/test/browser_980155_add_overflow_toolbar.js
@@ -0,0 +1,51 @@
+/* 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 kToolbarName = "test-new-overflowable-toolbar";
+const kTestWidgetPrefix = "test-widget-for-overflowable-toolbar-";
+
+add_task(function addOverflowingToolbar() {
+  let originalWindowWidth = window.outerWidth;
+
+  let widgetIds = [];
+  for (let i = 0; i < 10; i++) {
+    let id = kTestWidgetPrefix + i;
+    widgetIds.push(id);
+    let spec = {id: id, type: "button", removable: true, label: "test", tooltiptext: "" + i};
+    CustomizableUI.createWidget(spec);
+  }
+
+  let toolbarNode = createOverflowableToolbarWithPlacements(kToolbarName, widgetIds);
+  assertAreaPlacements(kToolbarName, widgetIds);
+
+  for (let id of widgetIds) {
+    document.getElementById(id).style.minWidth = "200px";
+  }
+
+  isnot(toolbarNode.overflowable, null, "Toolbar should have overflowable controller");
+  isnot(toolbarNode.customizationTarget, null, "Toolbar should have customization target");
+  isnot(toolbarNode.customizationTarget, toolbarNode, "Customization target should not be toolbar node");
+
+  let oldChildCount = toolbarNode.customizationTarget.childElementCount;
+  let overflowableList = document.getElementById(kToolbarName + "-overflow-list");
+  let oldOverflowCount = overflowableList.childElementCount;
+
+  isnot(oldChildCount, 0, "Toolbar should have non-overflowing widgets");
+
+  window.resizeTo(400, window.outerHeight);
+  yield waitForCondition(() => toolbarNode.hasAttribute("overflowing"));
+  ok(toolbarNode.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
+  ok(toolbarNode.customizationTarget.childElementCount < oldChildCount, "Should have fewer children.");
+  ok(overflowableList.childElementCount > oldOverflowCount, "Should have more overflowed widgets.");
+
+  window.resizeTo(originalWindowWidth, window.outerHeight);
+});
+
+
+add_task(function asyncCleanup() {
+  removeCustomToolbars();
+  yield resetCustomization();
+});
--- a/browser/components/customizableui/test/head.js
+++ b/browser/components/customizableui/test/head.js
@@ -40,21 +40,67 @@ function createToolbarWithPlacements(id,
   CustomizableUI.registerArea(id, {
     type: CustomizableUI.TYPE_TOOLBAR,
     defaultPlacements: placements
   });
   gNavToolbox.appendChild(tb);
   return tb;
 }
 
+function createOverflowableToolbarWithPlacements(id, placements) {
+  gAddedToolbars.add(id);
+
+  let tb = document.createElementNS(kNSXUL, "toolbar");
+  tb.id = id;
+  tb.setAttribute("customizationtarget", id + "-target");
+
+  let customizationtarget = document.createElementNS(kNSXUL, "hbox");
+  customizationtarget.id = id + "-target";
+  customizationtarget.setAttribute("flex", "1");
+  tb.appendChild(customizationtarget);
+
+  let overflowPanel = document.createElementNS(kNSXUL, "panel");
+  overflowPanel.id = id + "-overflow";
+  document.getElementById("mainPopupSet").appendChild(overflowPanel);
+
+  let overflowList = document.createElementNS(kNSXUL, "vbox");
+  overflowList.id = id + "-overflow-list";
+  overflowPanel.appendChild(overflowList);
+
+  let chevron = document.createElementNS(kNSXUL, "toolbarbutton");
+  chevron.id = id + "-chevron";
+  tb.appendChild(chevron);
+
+  CustomizableUI.registerArea(id, {
+    type: CustomizableUI.TYPE_TOOLBAR,
+    defaultPlacements: placements,
+    overflowable: true,
+  });
+
+  tb.setAttribute("customizable", "true");
+  tb.setAttribute("overflowable", "true");
+  tb.setAttribute("overflowpanel", overflowPanel.id);
+  tb.setAttribute("overflowtarget", overflowList.id);
+  tb.setAttribute("overflowbutton", chevron.id);
+
+  gNavToolbox.appendChild(tb);
+  return tb;
+}
+
 function removeCustomToolbars() {
   CustomizableUI.reset();
   for (let toolbarId of gAddedToolbars) {
     CustomizableUI.unregisterArea(toolbarId, true);
-    document.getElementById(toolbarId).remove();
+    let tb = document.getElementById(toolbarId);
+    if (tb.hasAttribute("overflowpanel")) {
+      let panel = document.getElementById(tb.getAttribute("overflowpanel"));
+      if (panel)
+        panel.remove();
+    }
+    tb.remove();
   }
   gAddedToolbars.clear();
 }
 
 function getToolboxCustomToolbarId(toolbarName) {
   return "__customToolbar_" + toolbarName.replace(" ", "_");
 }
 
--- a/browser/devtools/commandline/BuiltinCommands.jsm
+++ b/browser/devtools/commandline/BuiltinCommands.jsm
@@ -952,17 +952,17 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   /**
    * Check if a given cookie matches a given host
    */
   function isCookieAtHost(cookie, host) {
     if (cookie.host == null) {
       return host == null;
     }
     if (cookie.host.startsWith(".")) {
-      return cookie.host === "." + host;
+      return host.endsWith(cookie.host);
     }
     else {
       return cookie.host == host;
     }
   }
 
   /**
    * 'cookie' command
--- a/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
+++ b/browser/devtools/debugger/test/browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js
@@ -16,52 +16,69 @@ function test() {
 
   initDebugger(TAB_URL).then(([aTab, aDebuggee, aPanel]) => {
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gThreadClient = gDebugger.gThreadClient;
     gEvents = gDebugger.EVENTS;
 
     Task.spawn(function* () {
-      yield waitForSourceShown(gPanel, CODE_URL);
+      try {
+
+        yield waitForSourceShown(gPanel, CODE_URL);
+
+        // Pause and set our breakpoints.
+        yield doInterrupt();
+        const [bp1, bp2, bp3] = yield promise.all([
+          setBreakpoint({
+            url: CODE_URL,
+            line: 2
+          }),
+          setBreakpoint({
+            url: CODE_URL,
+            line: 3
+          }),
+          setBreakpoint({
+            url: CODE_URL,
+            line: 4
+          })
+        ]);
+
+        // Should hit the first breakpoint on reload.
+        yield promise.all([
+          reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN),
+          waitForCaretUpdated(gPanel, 2)
+        ]);
 
-      // Pause and set our breakpoints.
-      yield doInterrupt();
-      yield promise.all([
-        gPanel.addBreakpoint({
-          url: CODE_URL,
-          line: 2
-        }),
-        gPanel.addBreakpoint({
-          url: CODE_URL,
-          line: 3
-        }),
-        gPanel.addBreakpoint({
-          url: CODE_URL,
-          line: 4
-        })
-      ]);
+        // And should hit the other breakpoints as we resume.
+        yield promise.all([
+          doResume(),
+          waitForCaretUpdated(gPanel, 3)
+        ]);
+        yield promise.all([
+          doResume(),
+          waitForCaretUpdated(gPanel, 4)
+        ]);
 
-      // Should hit the first breakpoint on reload.
-      yield promise.all([
-        reloadActiveTab(gPanel, gEvents.SOURCE_SHOWN),
-        waitForCaretUpdated(gPanel, 2)
-      ]);
+        // Clean up the breakpoints.
+        yield promise.all([
+          rdpInvoke(bp1, bp1.remove),
+          rdpInvoke(bp2, bp1.remove),
+          rdpInvoke(bp3, bp1.remove),
+        ]);
 
-      // And should hit the other breakpoints as we resume.
-      yield promise.all([
-        doResume(),
-        waitForCaretUpdated(gPanel, 3)
-      ]);
-      yield promise.all([
-        doResume(),
-        waitForCaretUpdated(gPanel, 4)
-      ]);
+        yield resumeDebuggerThenCloseAndFinish(gPanel);
 
-      yield resumeDebuggerThenCloseAndFinish(gPanel);
+      } catch (e) {
+        DevToolsUtils.reportException(
+          "browser_dbg_breakpoints-break-on-last-line-of-script-on-reload.js",
+          e
+        );
+        ok(false);
+      }
     });
   });
 
   function rdpInvoke(obj, method) {
     return promiseInvoke(obj, method)
       .then(({error, message }) => {
         if (error) {
           throw new Error(error + ": " + message);
@@ -71,9 +88,20 @@ function test() {
 
   function doResume() {
     return rdpInvoke(gThreadClient, gThreadClient.resume);
   }
 
   function doInterrupt() {
     return rdpInvoke(gThreadClient, gThreadClient.interrupt);
   }
+
+  function setBreakpoint(location) {
+    let deferred = promise.defer();
+    gThreadClient.setBreakpoint(location, ({ error, message }, bpClient) => {
+      if (error) {
+        deferred.reject(error + ": " + message);
+      }
+      deferred.resolve(bpClient);
+    });
+    return deferred.promise;
+  }
 }
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -437,25 +437,16 @@ fullscreenButton.tooltip=Display the win
 service.toolbarbutton.label=Services
 service.toolbarbutton.tooltiptext=Services
 
 # LOCALIZATION NOTE (social.install.description): %1$S is the hostname of the social provider, %2$S is brandShortName (e.g. Firefox)
 service.install.description=Would you like to enable services from %1$S to display in your %2$S toolbar and sidebar?
 service.install.ok.label=Enable Services
 service.install.ok.accesskey=E
 
-# LOCALIZATION NOTE (social.turnOff.label): %S is the name of the social provider
-social.turnOff.label=Turn off %S
-social.turnOff.accesskey=T
-# LOCALIZATION NOTE (social.turnOn.label): %S is the name of the social provider
-social.turnOn.label=Turn on %S
-social.turnOn.accesskey=T
-social.turnOffAll.label=Turn off all Services
-social.turnOnAll.label=Turn on all Services
-
 # LOCALIZATION NOTE (social.markpageMenu.label): %S is the name of the social provider
 social.markpageMenu.label=Save Page to %S
 # LOCALIZATION NOTE (social.marklinkMenu.label): %S is the name of the social provider
 social.marklinkMenu.label=Save Link to %S
 
 # LOCALIZATION NOTE (social.error.message): %1$S is brandShortName (e.g. Firefox), %2$S is the name of the social provider
 social.error.message=%1$S is unable to connect with %2$S right now.
 social.error.tryAgain.label=Try Again
--- a/browser/metro/base/content/ContextUI.js
+++ b/browser/metro/base/content/ContextUI.js
@@ -168,21 +168,30 @@ var ContextUI = {
     aDelay = aDelay || kForegroundTabAnimationDelay;
     this._clearDelayedTimeout();
     this._lastTimeoutDelay = aDelay;
     this._hidingId = setTimeout(function () {
         ContextUI.dismissTabs();
       }, aDelay);
   },
 
-  // Display the nav bar
+  /*
+   * Display the nav bar.
+   *
+   * @return false if we were already visible, and didn't do anything.
+   */
   displayNavbar: function () {
+    if (Elements.chromeState.getAttribute("navbar") == "visible") {
+      return false;
+    }
+
     Elements.navbar.show();
     Elements.chromeState.setAttribute("navbar", "visible");
     ContentAreaObserver.updateContentArea();
+    return true;
   },
 
   // Display the tab tray
   displayTabs: function () {
     this._clearDelayedTimeout();
     this._setIsExpanded(true);
   },
 
--- a/browser/metro/base/content/bindings/urlbar.xml
+++ b/browser/metro/base/content/bindings/urlbar.xml
@@ -279,18 +279,25 @@
         <body>
           <![CDATA[
             if (this.isEditing)
               return;
 
             Elements.urlbarState.setAttribute("editing", true);
             this._lastKnownGoodURL = this.value;
 
-            if (!this.focused)
+            if (!this.focused) {
               this.focus();
+              // If we force focus, ensure we're visible.
+              if (ContextUI.displayNavbar()) {
+                // If we forced visibility, ensure we're positioned above keyboard.
+                // (Previous "blur" may have forced us down behind it.)
+                ContentAreaObserver.updateAppBarPosition();
+              }
+            }
 
             this._clearFormatting();
             this.select();
 
             if (aShouldDismiss)
               ContextUI.dismissTabs();
 
             if (!InputSourceHelper.isPrecise) {
--- a/browser/metro/base/content/contenthandlers/SelectionHandler.js
+++ b/browser/metro/base/content/contenthandlers/SelectionHandler.js
@@ -113,23 +113,34 @@ var SelectionHandler = {
     }
     
     // Sanity check to be sure we are initialized
     if (!this._targetElement) {
       this._onFail("not initialized");
       return;
     }
 
-    // Similar to _onSelectionStart - we need to create initial selection
-    // but without the initialization bits.
-    let framePoint = this._clientPointToFramePoint({ xPos: aX, yPos: aY });
-    if (!this._domWinUtils.selectAtPoint(framePoint.xPos, framePoint.yPos,
-                                         Ci.nsIDOMWindowUtils.SELECT_CHARACTER)) {
-      this._onFail("failed to set selection at point");
-      return;
+    // Only use selectAtPoint for editable content and avoid that for inputs,
+    // as we can expand caret to selection manually more precisely. We can use
+    // selectAtPoint for inputs too though, but only once bug 881938 is fully
+    // resolved.
+    if(Util.isEditableContent(this._targetElement)) {
+      // Similar to _onSelectionStart - we need to create initial selection
+      // but without the initialization bits.
+      let framePoint = this._clientPointToFramePoint({ xPos: aX, yPos: aY });
+      if (!this._domWinUtils.selectAtPoint(framePoint.xPos, framePoint.yPos,
+                                           Ci.nsIDOMWindowUtils.SELECT_CHARACTER)) {
+        this._onFail("failed to set selection at point");
+        return;
+      }
+    } else if (this._targetElement.selectionStart == 0 || aMarker == "end") {
+      // Expand caret forward or backward depending on direction
+      this._targetElement.selectionEnd++;
+    } else {
+      this._targetElement.selectionStart--;
     }
 
     // We bail if things get out of sync here implying we missed a message.
     this._selectionMoveActive = true;
 
     // Update the position of the selection marker that is *not*
     // being dragged.
     this._updateSelectionUI("update", aMarker == "end", aMarker == "start");
@@ -173,49 +184,34 @@ var SelectionHandler = {
       return;
     }
 
     if (!this._selectionMoveActive) {
       this._onFail("mouse isn't down for drag move?");
       return;
     }
 
-    // Update selection in the doc
-    let pos = null;
-    if (aMsg.change == "start") {
-      pos = aMsg.start;
-    } else {
-      pos = aMsg.end;
-    }
-    this._handleSelectionPoint(aMsg.change, pos, false);
+    this._handleSelectionPoint(aMsg, false);
   },
 
   /*
    * Selection monocle move finished event handler
    */
   _onSelectionMoveEnd: function _onSelectionMoveComplete(aMsg) {
     if (!this._contentWindow) {
       this._onFail("_onSelectionMove was called without proper view set up");
       return;
     }
 
     if (!this._selectionMoveActive) {
       this._onFail("mouse isn't down for drag move?");
       return;
     }
 
-    // Update selection in the doc
-    let pos = null;
-    if (aMsg.change == "start") {
-      pos = aMsg.start;
-    } else {
-      pos = aMsg.end;
-    }
-
-    this._handleSelectionPoint(aMsg.change, pos, true);
+    this._handleSelectionPoint(aMsg, true);
     this._selectionMoveActive = false;
     
     // _handleSelectionPoint may set a scroll timer, so this must
     // be reset after the last call.
     this._clearTimers();
 
     // Update the position of our selection monocles
     this._updateSelectionUI("end", true, true);
--- a/browser/metro/base/content/helperui/ChromeSelectionHandler.js
+++ b/browser/metro/base/content/helperui/ChromeSelectionHandler.js
@@ -107,49 +107,34 @@ var ChromeSelectionHandler = {
       return;
     }
 
     if (!this._selectionMoveActive) {
       this._onFail("mouse isn't down for drag move?");
       return;
     }
 
-    // Update selection in the doc
-    let pos = null;
-    if (aMsg.change == "start") {
-      pos = aMsg.start;
-    } else {
-      pos = aMsg.end;
-    }
-    this._handleSelectionPoint(aMsg.change, pos, false);
+    this._handleSelectionPoint(aMsg, false);
   },
 
   /*
    * Selection monocle move finished event handler
    */
   _onSelectionMoveEnd: function _onSelectionMoveComplete(aMsg) {
     if (!this.targetIsEditable) {
       this._onFail("_onSelectionMoveEnd with bad targetElement.");
       return;
     }
 
     if (!this._selectionMoveActive) {
       this._onFail("mouse isn't down for drag move?");
       return;
     }
 
-    // Update selection in the doc
-    let pos = null;
-    if (aMsg.change == "start") {
-      pos = aMsg.start;
-    } else {
-      pos = aMsg.end;
-    }
-
-    this._handleSelectionPoint(aMsg.change, pos, true);
+    this._handleSelectionPoint(aMsg, true);
     this._selectionMoveActive = false;
     
     // Clear any existing scroll timers
     this._clearTimers();
 
     // Update the position of our selection monocles
     this._updateSelectionUI("end", true, true);
   },
--- a/browser/metro/base/content/helperui/SelectionHelperUI.js
+++ b/browser/metro/base/content/helperui/SelectionHelperUI.js
@@ -106,21 +106,22 @@ Marker.prototype = {
   _selectionHelperUI: null,
   _xPos: 0,
   _yPos: 0,
   _xDrag: 0,
   _yDrag: 0,
   _tag: "",
   _hPlane: 0,
   _vPlane: 0,
+  _restrictedToBounds: false,
 
   // Tweak me if the monocle graphics change in any way
   _monocleRadius: 8,
   _monocleXHitTextAdjust: -2, 
-  _monocleYHitTextAdjust: -10, 
+  _monocleYHitTextAdjust: -10,
 
   get xPos() {
     return this._xPos;
   },
 
   get yPos() {
     return this._yPos;
   },
@@ -132,16 +133,23 @@ Marker.prototype = {
   set tag(aVal) {
     this._tag = aVal;
   },
 
   get dragging() {
     return this._element.customDragger.dragging;
   },
 
+  // Indicates that marker's position doesn't reflect real selection boundary
+  // but rather boundary of input control while actual selection boundaries are
+  // not visible (ex. due scrolled content).
+  get restrictedToBounds() {
+    return this._restrictedToBounds;
+  },
+
   shutdown: function shutdown() {
     this._element.hidden = true;
     this._element.customDragger.shutdown = true;
     delete this._element.customDragger;
     this._selectionHelperUI = null;
     this._element = null;
   },
 
@@ -158,19 +166,20 @@ Marker.prototype = {
   hide: function hide() {
     this._element.hidden = true;
   },
 
   get visible() {
     return this._element.hidden == false;
   },
 
-  position: function position(aX, aY) {
+  position: function position(aX, aY, aRestrictedToBounds) {
     this._xPos = aX;
     this._yPos = aY;
+    this._restrictedToBounds = !!aRestrictedToBounds;
     this._setPosition();
   },
 
   _setPosition: function _setPosition() {
     this._element.left = this._xPos + "px";
     this._element.top = this._yPos + "px";
   },
 
@@ -578,16 +587,17 @@ var SelectionHelperUI = {
 
     // SelectionHandler messages
     messageManager.addMessageListener("Content:SelectionRange", this);
     messageManager.addMessageListener("Content:SelectionCopied", this);
     messageManager.addMessageListener("Content:SelectionFail", this);
     messageManager.addMessageListener("Content:SelectionDebugRect", this);
     messageManager.addMessageListener("Content:HandlerShutdown", this);
     messageManager.addMessageListener("Content:SelectionHandlerPong", this);
+    messageManager.addMessageListener("Content:SelectionSwap", this);
 
     // capture phase
     window.addEventListener("keypress", this, true);
     window.addEventListener("MozPrecisePointer", this, true);
     window.addEventListener("MozDeckOffsetChanging", this, true);
     window.addEventListener("MozDeckOffsetChanged", this, true);
     window.addEventListener("KeyboardChanged", this, true);
 
@@ -607,16 +617,17 @@ var SelectionHelperUI = {
 
   _shutdown: function _shutdown() {
     messageManager.removeMessageListener("Content:SelectionRange", this);
     messageManager.removeMessageListener("Content:SelectionCopied", this);
     messageManager.removeMessageListener("Content:SelectionFail", this);
     messageManager.removeMessageListener("Content:SelectionDebugRect", this);
     messageManager.removeMessageListener("Content:HandlerShutdown", this);
     messageManager.removeMessageListener("Content:SelectionHandlerPong", this);
+    messageManager.removeMessageListener("Content:SelectionSwap", this);
 
     window.removeEventListener("keypress", this, true);
     window.removeEventListener("MozPrecisePointer", this, true);
     window.removeEventListener("MozDeckOffsetChanging", this, true);
     window.removeEventListener("MozDeckOffsetChanged", this, true);
     window.removeEventListener("KeyboardChanged", this, true);
 
     window.removeEventListener("click", this, false);
@@ -932,37 +943,43 @@ var SelectionHelperUI = {
     this.overlay.addDebugRect(aMsg.left, aMsg.top, aMsg.right, aMsg.bottom,
                               aMsg.color, aMsg.fill, aMsg.id);
   },
 
   _selectionHandlerShutdown: function _selectionHandlerShutdown() {
     this._shutdown();
   },
 
+  _selectionSwap: function _selectionSwap() {
+    [this.startMark.tag, this.endMark.tag] = [this.endMark.tag,
+        this.startMark.tag];
+    [this._startMark, this._endMark] = [this.endMark, this.startMark];
+  },
+
   /*
    * Message handlers
    */
 
   _onSelectionCopied: function _onSelectionCopied(json) {
     this.closeEditSession(true);
   },
 
   _onSelectionRangeChange: function _onSelectionRangeChange(json) {
     let haveSelectionRect = true;
 
     if (json.updateStart) {
       let x = this._msgTarget.btocx(json.start.xPos, true);
       let y = this._msgTarget.btocy(json.start.yPos, true);
-      this.startMark.position(x, y);
+      this.startMark.position(x, y, json.start.restrictedToBounds);
     }
 
     if (json.updateEnd) {
       let x = this._msgTarget.btocx(json.end.xPos, true);
       let y = this._msgTarget.btocy(json.end.yPos, true);
-      this.endMark.position(x, y);
+      this.endMark.position(x, y, json.end.restrictedToBounds);
     }
 
     if (json.updateCaret) {
       let x = this._msgTarget.btocx(json.caret.xPos, true);
       let y = this._msgTarget.btocy(json.caret.yPos, true);
       // If selectionRangeFound is set SelectionHelper found a range we can
       // attach to. If not, there's no text in the control, and hence no caret
       // position information we can use.
@@ -1085,78 +1102,81 @@ var SelectionHelperUI = {
         this._onSelectionCopied(json);
         break;
       case "Content:SelectionDebugRect":
         this._onDebugRectRequest(json);
         break;
       case "Content:HandlerShutdown":
         this._selectionHandlerShutdown();
         break;
+      case "Content:SelectionSwap":
+        this._selectionSwap();
+        break;
       case "Content:SelectionHandlerPong":
         this._onPong(json.id);
         break;
     }
   },
 
   /*
    * Callbacks from markers
    */
 
   _getMarkerBaseMessage: function _getMarkerBaseMessage(aMarkerTag) {
     return {
       change: aMarkerTag,
       start: {
         xPos: this._msgTarget.ctobx(this.startMark.xPos, true),
-        yPos: this._msgTarget.ctoby(this.startMark.yPos, true)
+        yPos: this._msgTarget.ctoby(this.startMark.yPos, true),
+        restrictedToBounds: this.startMark.restrictedToBounds
       },
       end: {
         xPos: this._msgTarget.ctobx(this.endMark.xPos, true),
-        yPos: this._msgTarget.ctoby(this.endMark.yPos, true)
+        yPos: this._msgTarget.ctoby(this.endMark.yPos, true),
+        restrictedToBounds: this.endMark.restrictedToBounds
       },
       caret: {
         xPos: this._msgTarget.ctobx(this.caretMark.xPos, true),
         yPos: this._msgTarget.ctoby(this.caretMark.yPos, true)
       },
     };
   },
 
   markerDragStart: function markerDragStart(aMarker) {
     let json = this._getMarkerBaseMessage(aMarker.tag);
     if (aMarker.tag == "caret") {
-      this._cachedCaretPos = null;
-      this._sendAsyncMessage("Browser:CaretMove", json);
+      // Cache for when we start the drag in _transitionFromCaretToSelection.
+      if (!this._cachedCaretPos) {
+        this._cachedCaretPos = this._getMarkerBaseMessage(aMarker.tag).caret;
+      }
       return;
     }
     this._sendAsyncMessage("Browser:SelectionMoveStart", json);
   },
 
   markerDragStop: function markerDragStop(aMarker) {
     let json = this._getMarkerBaseMessage(aMarker.tag);
     if (aMarker.tag == "caret") {
-      this._sendAsyncMessage("Browser:CaretUpdate", json);
+      this._cachedCaretPos = null;
       return;
     }
     this._sendAsyncMessage("Browser:SelectionMoveEnd", json);
   },
 
   markerDragMove: function markerDragMove(aMarker, aDirection) {
     if (aMarker.tag == "caret") {
       // If direction is "tbd" the drag monocle hasn't determined which
       // direction the user is dragging.
       if (aDirection != "tbd") {
         // We are going to transition from caret browsing mode to selection
         // mode on drag. So swap the caret monocle for a start or end monocle
         // depending on the direction of the drag, and start selecting text.
         this._transitionFromCaretToSelection(aDirection);
         return false;
       }
-      // Cache for when we start the drag in _transitionFromCaretToSelection.
-      if (!this._cachedCaretPos) {
-        this._cachedCaretPos = this._getMarkerBaseMessage(aMarker.tag).caret;
-      }
       return true;
     }
     this._cachedCaretPos = null;
 
     // We'll re-display these after the drag is complete.
     this._hideMonocles();
 
     let json = this._getMarkerBaseMessage(aMarker.tag);
--- a/browser/metro/base/content/library/SelectionPrototype.js
+++ b/browser/metro/base/content/library/SelectionPrototype.js
@@ -237,55 +237,117 @@ SelectionPrototype.prototype = {
     this._cache.updateCaret = aUpdateCaret || false;
     this._cache.targetIsEditable = this._targetIsEditable;
 
     // Get monocles positioned correctly
     this.sendAsync("Content:SelectionRange", this._cache);
   },
 
   /*
-   * _handleSelectionPoint(aMarker, aPoint, aEndOfSelection) 
+   * _handleSelectionPoint(aSelectionInfo, aEndOfSelection)
    *
    * After a monocle moves to a new point in the document, determines
    * what the target is and acts on its selection accordingly. If the
    * monocle is within the bounds of the target, adds or subtracts selection
    * at the monocle coordinates appropriately and then merges selection ranges
    * into a single continuous selection. If the monocle is outside the bounds
    * of the target and the underlying target is editable, uses the selection
    * controller to advance selection and visibility within the control.
    */
-  _handleSelectionPoint: function _handleSelectionPoint(aMarker, aClientPoint,
+  _handleSelectionPoint: function _handleSelectionPoint(aSelectionInfo,
                                                         aEndOfSelection) {
     let selection = this._getSelection();
 
-    let clientPoint = { xPos: aClientPoint.xPos, yPos: aClientPoint.yPos };
+    let markerToChange = aSelectionInfo.change == "start" ?
+        aSelectionInfo.start : aSelectionInfo.end;
 
     if (selection.rangeCount == 0) {
       this._onFail("selection.rangeCount == 0");
       return;
     }
 
     // We expect continuous selection ranges.
     if (selection.rangeCount > 1) {
       this._setContinuousSelection();
     }
 
     // Adjust our y position up such that we are sending coordinates on
     // the text line vs. below it where the monocle is positioned.
-    let halfLineHeight = this._queryHalfLineHeight(aMarker, selection);
-    clientPoint.yPos -= halfLineHeight;
+    let halfLineHeight = this._queryHalfLineHeight(aSelectionInfo.start,
+        selection);
+    aSelectionInfo.start.yPos -= halfLineHeight;
+    aSelectionInfo.end.yPos -= halfLineHeight;
+
+    let isSwapNeeded = false;
+    if (this._isSelectionSwapNeeded(aSelectionInfo.start, aSelectionInfo.end,
+        halfLineHeight)) {
+      [aSelectionInfo.start, aSelectionInfo.end] =
+          [aSelectionInfo.end, aSelectionInfo.start];
+
+      isSwapNeeded = true;
+      this.sendAsync("Content:SelectionSwap");
+    }
 
     // Modify selection based on monocle movement
     if (this._targetIsEditable && !Util.isEditableContent(this._targetElement)) {
-      this._adjustEditableSelection(aMarker, clientPoint, aEndOfSelection);
+      if (isSwapNeeded) {
+        this._adjustEditableSelection("start", aSelectionInfo.start,
+            aEndOfSelection);
+        this._adjustEditableSelection("end", aSelectionInfo.end,
+            aEndOfSelection);
+      } else {
+        this._adjustEditableSelection(aSelectionInfo.change, markerToChange,
+            aEndOfSelection);
+      }
     } else {
-      this._adjustSelectionAtPoint(aMarker, clientPoint, aEndOfSelection);
+      if (isSwapNeeded) {
+        this._adjustSelectionAtPoint("start", aSelectionInfo.start,
+            aEndOfSelection, true);
+        this._adjustSelectionAtPoint("end", aSelectionInfo.end,
+            aEndOfSelection, true);
+      } else {
+        this._adjustSelectionAtPoint(aSelectionInfo.change, markerToChange,
+            aEndOfSelection);
+      }
     }
   },
 
+  /**
+   * Checks whether we need to swap start and end markers depending the target
+   * element and monocle position.
+   * @param aStart Start monocle coordinates.
+   * @param aEnd End monocle coordinates
+   * @param aYThreshold Y-coordinate threshold used to eliminate slight
+   * differences in monocle vertical positions.
+   */
+  _isSelectionSwapNeeded: function(aStart, aEnd, aYThreshold) {
+    let isSwapNeededByX = aStart.xPos > aEnd.xPos;
+    let isSwapNeededByY = aStart.yPos - aEnd.yPos > aYThreshold;
+    let onTheSameLine = Math.abs(aStart.yPos - aEnd.yPos) <= aYThreshold;
+
+    if (this._targetIsEditable &&
+        !Util.isEditableContent(this._targetElement)) {
+
+      // If one of the markers is restricted to edit bounds, then we shouldn't
+      // swap it until we know its real position
+      if (aStart.restrictedToBounds && aEnd.restrictedToBounds) {
+        return false;
+      }
+
+      // For multi line we should respect Y-coordinate
+      if (Util.isMultilineInput(this._targetElement)) {
+        return isSwapNeededByY || (isSwapNeededByX && onTheSameLine);
+      }
+
+      return isSwapNeededByX;
+    }
+
+    return isSwapNeededByY || (isSwapNeededByX && onTheSameLine);
+  },
+
   /*
    * _handleSelectionPoint helper methods
    */
 
   /*
    * _adjustEditableSelection
    *
    * Based on a monocle marker and position, adds or subtracts from the
@@ -370,19 +432,22 @@ SelectionPrototype.prototype = {
    * Note: we are trying to move away from this api due to the overhead. 
    *
    * @param the marker currently being manipulated
    * @param aClientPoint the point designating the new start or end
    * position for the selection.
    * @param aEndOfSelection indicates if this is the end of a selection
    * move, in which case we may want to snap to the end of a word or
    * sentence.
+   * @param suppressSelectionUIUpdate Indicates that we don't want to update
+   * static monocle automatically as it's going to be be updated explicitly like
+   * in case with monocle swapping.
    */
-  _adjustSelectionAtPoint: function _adjustSelectionAtPoint(aMarker, aClientPoint,
-                                                            aEndOfSelection) {
+  _adjustSelectionAtPoint: function _adjustSelectionAtPoint(aMarker,
+      aClientPoint, aEndOfSelection, suppressSelectionUIUpdate) {
     // Make a copy of the existing range, we may need to reset it.
     this._backupRangeList();
 
     // shrinkSelectionFromPoint takes sub-frame relative coordinates.
     let framePoint = this._clientPointToFramePoint(aClientPoint);
 
     // Tests to see if the user is trying to shrink the selection, and if so
     // collapses it down to the appropriate side such that our calls below
@@ -392,20 +457,41 @@ SelectionPrototype.prototype = {
     let selectResult = false;
     try {
       // If we're at the end of a selection (touchend) snap to the word.
       let type = ((aEndOfSelection && this._snap) ?
         Ci.nsIDOMWindowUtils.SELECT_WORD :
         Ci.nsIDOMWindowUtils.SELECT_CHARACTER);
 
       // Select a character at the point.
-      selectResult = 
-        this._domWinUtils.selectAtPoint(framePoint.xPos,
-                                        framePoint.yPos,
-                                        type);
+      selectResult = this._domWinUtils.selectAtPoint(framePoint.xPos,
+          framePoint.yPos, type);
+
+      // selectAtPoint selects char back and forward and apparently can select
+      // content that is beyond selection boundaries that we had before it was
+      // shrunk that forces selection to always move forward or backward
+      // preventing monocle swapping.
+      if (selectResult && shrunk && this._rangeBackup) {
+        let selection = this._getSelection();
+
+        let currentSelection = this._extractUIRects(
+            selection.getRangeAt(selection.rangeCount - 1)).selection;
+        let previousSelection = this._extractUIRects(
+            this._rangeBackup[0]).selection;
+
+        if (aMarker == "start" &&
+            currentSelection.right > previousSelection.right) {
+          selectResult = false;
+        }
+
+        if (aMarker == "end"  &&
+            currentSelection.left < previousSelection.left) {
+          selectResult = false;
+        }
+      }
     } catch (ex) {
     }
 
     // If selectAtPoint failed (which can happen if there's nothing to select)
     // reset our range back before we shrunk it.
     if (!selectResult) {
       this._restoreRangeList();
     }
@@ -413,17 +499,19 @@ SelectionPrototype.prototype = {
     this._freeRangeList();
 
     // Smooth over the selection between all existing ranges.
     this._setContinuousSelection();
 
     // Update the other monocle's position. We do this because the dragging
     // monocle may reset the static monocle to a new position if the dragging
     // monocle drags ahead or behind the other.
-    this._updateSelectionUI("update", aMarker == "end", aMarker == "start");
+    this._updateSelectionUI("update",
+      aMarker == "end" && !suppressSelectionUIUpdate,
+      aMarker == "start" && !suppressSelectionUIUpdate);
   },
 
   /*
    * _backupRangeList, _restoreRangeList, and _freeRangeList
    *
    * Utilities that manage a cloned copy of the existing selection.
    */
 
@@ -432,16 +520,20 @@ SelectionPrototype.prototype = {
     for (let idx = 0; idx < this._getSelection().rangeCount; idx++) {
       this._rangeBackup.push(this._getSelection().getRangeAt(idx).cloneRange());
     }
   },
 
   _restoreRangeList: function _restoreRangeList() {
     if (this._rangeBackup == null)
       return;
+
+    // Remove every previously created selection range
+    this._getSelection().removeAllRanges();
+
     for (let idx = 0; idx < this._rangeBackup.length; idx++) {
       this._getSelection().addRange(this._rangeBackup[idx]);
     }
     this._freeRangeList();
   },
 
   _freeRangeList: function _restoreRangeList() {
     this._rangeBackup = null;
@@ -813,51 +905,60 @@ SelectionPrototype.prototype = {
     seldata.element.bottom = r.bottom + this._contentOffset.y;
 
     // If we don't have a range we can attach to let SelectionHelperUI know.
     seldata.selectionRangeFound = !!rects.length;
 
     return seldata;
   },
 
+  /**
+   * Updates point's coordinate with max\min available in accordance with the
+   * bounding rectangle. Returns true if point coordinates were actually updated,
+   * and false - otherwise.
+   * @param aPoint Target point which coordinates will be analyzed.
+   * @param aRectangle Target rectangle to bound to.
+   */
+  _restrictPointToRectangle: function(aPoint, aRectangle) {
+    let restrictionWasRequired = false;
+
+    if (aPoint.xPos < aRectangle.left) {
+      aPoint.xPos = aRectangle.left;
+      restrictionWasRequired = true;
+    } else if (aPoint.xPos > aRectangle.right) {
+      aPoint.xPos = aRectangle.right;
+      restrictionWasRequired = true;
+    }
+
+    if (aPoint.yPos < aRectangle.top) {
+      aPoint.yPos = aRectangle.top;
+      restrictionWasRequired = true;
+    } else if (aPoint.yPos > aRectangle.bottom) {
+      aPoint.yPos = aRectangle.bottom;
+      restrictionWasRequired = true;
+    }
+
+    return restrictionWasRequired;
+  },
+
   /*
    * Selection bounds will fall outside the bound of a control if the control
    * can scroll. Clip UI cache data to the bounds of the target so monocles
    * don't draw outside the control.
    */
   _restrictSelectionRectToEditBounds: function _restrictSelectionRectToEditBounds() {
     if (!this._targetIsEditable)
       return;
 
-    let bounds = this._getTargetBrowserRect();
-    if (this._cache.start.xPos < bounds.left)
-      this._cache.start.xPos = bounds.left;
-    if (this._cache.end.xPos < bounds.left)
-      this._cache.end.xPos = bounds.left;
-    if (this._cache.caret.xPos < bounds.left)
-      this._cache.caret.xPos = bounds.left;
-    if (this._cache.start.xPos > bounds.right)
-      this._cache.start.xPos = bounds.right;
-    if (this._cache.end.xPos > bounds.right)
-      this._cache.end.xPos = bounds.right;
-    if (this._cache.caret.xPos > bounds.right)
-      this._cache.caret.xPos = bounds.right;
-
-    if (this._cache.start.yPos < bounds.top)
-      this._cache.start.yPos = bounds.top;
-    if (this._cache.end.yPos < bounds.top)
-      this._cache.end.yPos = bounds.top;
-    if (this._cache.caret.yPos < bounds.top)
-      this._cache.caret.yPos = bounds.top;
-    if (this._cache.start.yPos > bounds.bottom)
-      this._cache.start.yPos = bounds.bottom;
-    if (this._cache.end.yPos > bounds.bottom)
-      this._cache.end.yPos = bounds.bottom;
-    if (this._cache.caret.yPos > bounds.bottom)
-      this._cache.caret.yPos = bounds.bottom;
+    let targetRectangle = this._getTargetBrowserRect();
+    this._cache.start.restrictedToBounds = this._restrictPointToRectangle(
+        this._cache.start, targetRectangle);
+    this._cache.end.restrictedToBounds = this._restrictPointToRectangle(
+        this._cache.end, targetRectangle);
+    this._restrictPointToRectangle(this._cache.caret, targetRectangle);
   },
 
   _restrictCoordinateToEditBounds: function _restrictCoordinateToEditBounds(aX, aY) {
     let result = {
       xPos: aX,
       yPos: aY
     };
     if (!this._targetIsEditable)
--- a/browser/metro/base/tests/mochitest/browser_selection_contenteditable.js
+++ b/browser/metro/base/tests/mochitest/browser_selection_contenteditable.js
@@ -32,17 +32,17 @@ gTests.push({
 });
 
 gTests.push({
   desc: "simple test to make sure content editable selection works",
   run: function test() {
     let div = gWindow.document.getElementById("testdiv");
     ok(div, "have the div");
 
-    sendElementTap(gWindow, div, 287); // end of 'outlook.com'
+    sendElementTap(gWindow, div, 284); // end of 'outlook.com'
 
     yield waitForCondition(function () {
         return SelectionHelperUI.isCaretUIVisible;
       }, kCommonWaitMs, kCommonPollMs);
 
     let xpos = SelectionHelperUI.caretMark.xPos;
     let ypos = SelectionHelperUI.caretMark.yPos + 10;
     var touchdrag = new TouchDragAndHold();
--- a/browser/metro/base/tests/mochitest/browser_selection_inputs.js
+++ b/browser/metro/base/tests/mochitest/browser_selection_inputs.js
@@ -190,16 +190,106 @@ gTests.push({
     yield touchdrag.move(135, ystartpos);
     touchdrag.end();
 
     yield SelectionHelperUI.pingSelectionHandler();
     is(getTrimmedSelection(gInput).toString(), "straight on like a tunnel for", "selection test");
   },
 });
 
+gTests.push({
+  desc: "Bug 858206 - Drag selection monocles should not push other monocles " +
+        "out of the way.",
+  setUp: setUpAndTearDown,
+  tearDown: setUpAndTearDown,
+  run: function test() {
+    let inputOriginalValue = gInput.value;
+
+    gInput.value = "The rabbit-hole went straight on";
+
+    let promise = waitForEvent(document, "popupshown");
+    sendContextMenuClickToElement(gWindow, gInput, 150);
+    yield promise;
+
+    // Make initial selection
+    promise = waitForEvent(document, "popuphidden");
+    sendElementTap(gWindow, document.getElementById("context-select"));
+    yield promise;
+
+    yield waitForCondition(() => SelectionHelperUI.isSelectionUIVisible,
+        kCommonWaitMs, kCommonPollMs);
+    is(getTrimmedSelection(gInput).toString(), "straight");
+
+    // Swap monocles when dragging with end monocle
+    let startXPos = SelectionHelperUI.endMark.xPos;
+    let startYPos = SelectionHelperUI.endMark.yPos + 10;
+    let touchDrag = new TouchDragAndHold();
+    yield touchDrag.start(gWindow, startXPos, startYPos, startXPos - 300,
+        startYPos);
+
+    yield waitForCondition(() => getTrimmedSelection(gInput).toString() ==
+        "The rabbit-hole went", kCommonWaitMs, kCommonPollMs);
+    touchDrag.end();
+    yield waitForCondition(() => !SelectionHelperUI.hasActiveDrag,
+        kCommonWaitMs, kCommonPollMs);
+    yield SelectionHelperUI.pingSelectionHandler();
+
+     // Swap monocles when dragging with start monocle
+    startXPos = SelectionHelperUI.startMark.xPos;
+    startYPos = SelectionHelperUI.startMark.yPos + 10;
+    yield touchDrag.start(gWindow, startXPos, startYPos, startXPos + 300,
+        startYPos);
+    yield waitForCondition(() => getTrimmedSelection(gInput).toString() ==
+        "straight on", kCommonWaitMs, kCommonPollMs);
+    touchDrag.end();
+    yield waitForCondition(() => !SelectionHelperUI.hasActiveDrag,
+        kCommonWaitMs, kCommonPollMs);
+    yield SelectionHelperUI.pingSelectionHandler();
+
+    // Swap monocles right after caret-to-selection mode switch from start
+    gInput.selectionStart = gInput.selectionEnd = 0;
+    sendElementTap(gWindow, gInput, 0, 0);
+
+    yield waitForCondition(() => !SelectionHelperUI.isSelectionUIVisible &&
+        SelectionHelperUI.isCaretUIVisible);
+
+    startXPos = SelectionHelperUI.caretMark.xPos;
+    startYPos = SelectionHelperUI.caretMark.yPos + 10;
+
+    yield touchDrag.start(gWindow, startXPos, startYPos, startXPos + 300,
+        startYPos);
+    yield waitForCondition(() => getTrimmedSelection(gInput).toString() ==
+        "The rabbit-hole went straight on", kCommonWaitMs, kCommonPollMs);
+    touchDrag.end();
+
+    sendTap(gWindow, 10, 10);
+    yield waitForCondition(() => !SelectionHelperUI.isSelectionUIVisible);
+
+    // Swap monocles right after caret-to-selection mode switch from end
+    gInput.selectionStart = gInput.selectionEnd = gInput.value.length;
+    let inputSelectionRectangle = gInput.QueryInterface(Ci.nsIDOMNSEditableElement).
+        editor.selection.getRangeAt(0).getClientRects()[0];
+    sendTap(gWindow, inputSelectionRectangle.right,
+        inputSelectionRectangle.top);
+
+    yield waitForCondition(() => SelectionHelperUI.isCaretUIVisible);
+
+    startXPos = SelectionHelperUI.caretMark.xPos;
+    startYPos = SelectionHelperUI.caretMark.yPos + 10;
+
+    yield touchDrag.start(gWindow, startXPos, startYPos, startXPos - 300,
+        startYPos);
+    yield waitForCondition(() => getTrimmedSelection(gInput).toString() ==
+        "The rabbit-hole went straight on", kCommonWaitMs, kCommonPollMs);
+    touchDrag.end();
+
+    gInput.value = inputOriginalValue;
+  }
+});
+
 function test() {
   if (!isLandscapeMode()) {
     todo(false, "browser_selection_tests need landscape mode to run.");
     return;
   }
   // XXX need this until bugs 886624 and 859742 are fully resolved
   setDevPixelEqualToPx();
   runTests();
--- a/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
+++ b/browser/metro/base/tests/mochitest/browser_selection_urlbar.js
@@ -310,18 +310,104 @@ gTests.push({
     ok(!SelectHelperUI.isSelectionUIVisible && !SelectHelperUI.isCaretUIVisible,
         "Neither CaretUI nor SelectionUI is visible on empty input.");
 
     inputField.value = "Test text";
 
     sendTap(window, inputFieldRectangle.left + 10, inputFieldRectangle.top + 5);
 
     yield waitForCondition(() => SelectionHelperUI.isCaretUIVisible);
+    chromeHandlerSpy.restore();
+    inputField.blur();
+  }
+});
 
-    chromeHandlerSpy.restore();
+gTests.push({
+  desc: "Bug 858206 - Drag selection monocles should not push other monocles " +
+        "out of the way.",
+  run: function test() {
+    yield showNavBar();
+
+    let edit = document.getElementById("urlbar-edit");
+    edit.value = "about:mozilla";
+
+    let editRectangle = edit.getBoundingClientRect();
+
+    sendTap(window, editRectangle.left, editRectangle.top);
+
+    yield waitForCondition(() => SelectionHelperUI.isSelectionUIVisible);
+
+    let selection = edit.QueryInterface(
+        Components.interfaces.nsIDOMXULTextBoxElement).editor.selection;
+    let selectionRectangle = selection.getRangeAt(0).getClientRects()[0];
+
+    // Place caret to the input start
+    sendTap(window, selectionRectangle.left + 2, selectionRectangle.top + 2);
+    yield waitForCondition(() => !SelectionHelperUI.isSelectionUIVisible &&
+        SelectionHelperUI.isCaretUIVisible);
+
+    let startXPos = SelectionHelperUI.caretMark.xPos;
+    let startYPos = SelectionHelperUI.caretMark.yPos + 10;
+    let touchDrag = new TouchDragAndHold();
+    yield touchDrag.start(gWindow, startXPos, startYPos, startXPos + 200,
+        startYPos);
+    yield waitForCondition(() => getTrimmedSelection(edit).toString() ==
+        "about:mozilla", kCommonWaitMs, kCommonPollMs);
+
+    touchDrag.end();
+    yield waitForCondition(() => !SelectionHelperUI.hasActiveDrag,
+        kCommonWaitMs, kCommonPollMs);
+
+    // Place caret to the input end
+    sendTap(window, selectionRectangle.right - 2, selectionRectangle.top + 2);
+    yield waitForCondition(() => !SelectionHelperUI.isSelectionUIVisible &&
+        SelectionHelperUI.isCaretUIVisible);
+
+    startXPos = SelectionHelperUI.caretMark.xPos;
+    startYPos = SelectionHelperUI.caretMark.yPos + 10;
+    yield touchDrag.start(gWindow, startXPos, startYPos, startXPos - 200,
+        startYPos);
+    yield waitForCondition(() => getTrimmedSelection(edit).toString() ==
+        "about:mozilla", kCommonWaitMs, kCommonPollMs);
+
+    touchDrag.end();
+    yield waitForCondition(() => !SelectionHelperUI.hasActiveDrag,
+        kCommonWaitMs, kCommonPollMs);
+
+    // Place caret in the middle
+    let midX = Math.ceil(((selectionRectangle.right - selectionRectangle.left) *
+        .5) + selectionRectangle.left);
+    let midY = Math.ceil(((selectionRectangle.bottom - selectionRectangle.top) *
+        .5) + selectionRectangle.top);
+
+    sendTap(window, midX, midY);
+    yield waitForCondition(() => !SelectionHelperUI.isSelectionUIVisible &&
+        SelectionHelperUI.isCaretUIVisible);
+
+    startXPos = SelectionHelperUI.caretMark.xPos;
+    startYPos = SelectionHelperUI.caretMark.yPos + 10;
+    yield touchDrag.start(gWindow, startXPos, startYPos, startXPos - 200,
+        startYPos);
+    yield waitForCondition(() => getTrimmedSelection(edit).toString() ==
+        "about:", kCommonWaitMs, kCommonPollMs);
+
+    touchDrag.end();
+    yield waitForCondition(() => !SelectionHelperUI.hasActiveDrag,
+        kCommonWaitMs, kCommonPollMs);
+
+    // Now try to swap monocles
+    startXPos = SelectionHelperUI.startMark.xPos;
+    startYPos = SelectionHelperUI.startMark.yPos + 10;
+    yield touchDrag.start(gWindow, startXPos, startYPos, startXPos + 200,
+        startYPos);
+    yield waitForCondition(() => getTrimmedSelection(edit).toString() ==
+        "mozilla", kCommonWaitMs, kCommonPollMs);
+      touchDrag.end();
+    yield waitForCondition(() => !SelectionHelperUI.hasActiveDrag &&
+        SelectionHelperUI.isSelectionUIVisible, kCommonWaitMs, kCommonPollMs);
   }
 });
 
 function test() {
   if (!isLandscapeMode()) {
     todo(false, "browser_selection_tests need landscape mode to run.");
     return;
   }
--- a/browser/modules/Social.jsm
+++ b/browser/modules/Social.jsm
@@ -29,36 +29,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "unescapeService",
                                    "@mozilla.org/feed-unescapehtml;1",
                                    "nsIScriptableUnescapeHTML");
 
-// Add a pref observer for the enabled state
-function prefObserver(subject, topic, data) {
-  let enable = Services.prefs.getBoolPref("social.enabled");
-  if (enable && !Social.provider) {
-    // this will result in setting Social.provider
-    SocialService.getOrderedProviderList(function(providers) {
-      Social.enabled = true;
-      Social._updateProviderCache(providers);
-    });
-  } else if (!enable && Social.provider) {
-    Social.provider = null;
-  }
-}
-
-Services.prefs.addObserver("social.enabled", prefObserver, false);
-Services.obs.addObserver(function xpcomShutdown() {
-  Services.obs.removeObserver(xpcomShutdown, "xpcom-shutdown");
-  Services.prefs.removeObserver("social.enabled", prefObserver);
-}, "xpcom-shutdown", false);
-
 function promiseSetAnnotation(aURI, providerList) {
   let deferred = Promise.defer();
 
   // Delaying to catch issues with asynchronous behavior while waiting
   // to implement asynchronous annotations in bug 699844.
   Services.tm.mainThread.dispatch(function() {
     try {
       if (providerList && providerList.length > 0) {
@@ -95,172 +75,91 @@ function promiseGetAnnotation(aURI) {
 }
 
 this.Social = {
   initialized: false,
   lastEventReceived: 0,
   providers: [],
   _disabledForSafeMode: false,
 
-  get _currentProviderPref() {
-    try {
-      return Services.prefs.getComplexValue("social.provider.current",
-                                            Ci.nsISupportsString).data;
-    } catch (ex) {}
-    return null;
-  },
-  set _currentProviderPref(val) {
-    let string = Cc["@mozilla.org/supports-string;1"].
-                 createInstance(Ci.nsISupportsString);
-    string.data = val;
-    Services.prefs.setComplexValue("social.provider.current",
-                                   Ci.nsISupportsString, string);
-  },
-
-  _provider: null,
-  get provider() {
-    return this._provider;
-  },
-  set provider(val) {
-    this._setProvider(val);
-  },
-
-  // Sets the current provider and notifies observers of the change.
-  _setProvider: function (provider) {
-    if (this._provider == provider)
-      return;
-
-    this._provider = provider;
-
-    if (this._provider) {
-      this._provider.enabled = true;
-      this._currentProviderPref = this._provider.origin;
-    }
-    let enabled = !!provider;
-    if (enabled != SocialService.enabled) {
-      SocialService.enabled = enabled;
-      this._updateWorkerState(enabled);
-    }
-
-    let origin = this._provider && this._provider.origin;
-    Services.obs.notifyObservers(null, "social:provider-set", origin);
-  },
-
-  get defaultProvider() {
-    if (this.providers.length == 0)
-      return null;
-    let provider = this._getProviderFromOrigin(this._currentProviderPref);
-    return provider || this.providers[0];
-  },
-
   init: function Social_init() {
     this._disabledForSafeMode = Services.appinfo.inSafeMode && this.enabled;
+    let deferred = Promise.defer();
 
     if (this.initialized) {
-      return;
+      deferred.resolve(true);
+      return deferred.promise;
     }
     this.initialized = true;
     // if SocialService.hasEnabledProviders, retreive the providers so the
     // front-end can generate UI
     if (SocialService.hasEnabledProviders) {
       // Retrieve the current set of providers, and set the current provider.
       SocialService.getOrderedProviderList(function (providers) {
         Social._updateProviderCache(providers);
         Social._updateWorkerState(SocialService.enabled);
+        deferred.resolve(false);
       });
+    } else {
+      deferred.resolve(false);
     }
 
     // Register an observer for changes to the provider list
     SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
       // An engine change caused by adding/removing a provider should notify.
       // any providers we receive are enabled in the AddonsManager
       if (topic == "provider-installed" || topic == "provider-uninstalled") {
         // installed/uninstalled do not send the providers param
         Services.obs.notifyObservers(null, "social:" + topic, origin);
         return;
       }
       if (topic == "provider-enabled") {
         Social._updateProviderCache(providers);
-        Social._updateWorkerState(Social.enabled);
+        Social._updateWorkerState(true);
         Services.obs.notifyObservers(null, "social:" + topic, origin);
         return;
       }
       if (topic == "provider-disabled") {
         // a provider was removed from the list of providers, that does not
         // affect worker state for other providers
         Social._updateProviderCache(providers);
+        Social._updateWorkerState(providers.length > 0);
         Services.obs.notifyObservers(null, "social:" + topic, origin);
         return;
       }
       if (topic == "provider-update") {
         // a provider has self-updated its manifest, we need to update our cache
         // and reload the provider.
         Social._updateProviderCache(providers);
         let provider = Social._getProviderFromOrigin(origin);
         provider.reload();
       }
     });
+    return deferred.promise;
   },
 
   _updateWorkerState: function(enable) {
     [p.enabled = enable for (p of Social.providers) if (p.enabled != enable)];
   },
 
   // Called to update our cache of providers and set the current provider
   _updateProviderCache: function (providers) {
     this.providers = providers;
     Services.obs.notifyObservers(null, "social:providers-changed", null);
-
-    // If social is currently disabled there's nothing else to do other than
-    // to notify about the lack of a provider.
-    if (!SocialService.enabled) {
-      Services.obs.notifyObservers(null, "social:provider-set", null);
-      return;
-    }
-    // Otherwise set the provider.
-    this._setProvider(this.defaultProvider);
-  },
-
-  set enabled(val) {
-    // Setting .enabled is just a shortcut for setting the provider to either
-    // the default provider or null...
-
-    this._updateWorkerState(val);
-
-    if (val) {
-      if (!this.provider)
-        this.provider = this.defaultProvider;
-    } else {
-      this.provider = null;
-    }
   },
 
   get enabled() {
-    return this.provider != null;
-  },
-
-  toggle: function Social_toggle() {
-    this.enabled = this._disabledForSafeMode ? false : !this.enabled;
-    this._disabledForSafeMode = false;
-  },
-
-  toggleSidebar: function SocialSidebar_toggle() {
-    let prefValue = Services.prefs.getBoolPref("social.sidebar.open");
-    Services.prefs.setBoolPref("social.sidebar.open", !prefValue);
+    return !this._disabledForSafeMode && this.providers.length > 0;
   },
 
   toggleNotifications: function SocialNotifications_toggle() {
     let prefValue = Services.prefs.getBoolPref("social.toast-notifications.enabled");
     Services.prefs.setBoolPref("social.toast-notifications.enabled", !prefValue);
   },
 
-  setProviderByOrigin: function (origin) {
-    this.provider = this._getProviderFromOrigin(origin);
-  },
-
   _getProviderFromOrigin: function (origin) {
     for (let p of this.providers) {
       if (p.origin == origin) {
         return p;
       }
     }
     return null;
   },
@@ -276,37 +175,17 @@ this.Social = {
   uninstallProvider: function(origin, aCallback) {
     SocialService.uninstallProvider(origin, aCallback);
   },
 
   // Activation functionality
   activateFromOrigin: function (origin, callback) {
     // For now only "builtin" providers can be activated.  It's OK if the
     // provider has already been activated - we still get called back with it.
-    SocialService.addBuiltinProvider(origin, function(provider) {
-      if (provider) {
-        // No need to activate again if we're already active
-        if (provider == this.provider)
-          return;
-        this.provider = provider;
-      }
-      if (callback)
-        callback(provider);
-    }.bind(this));
-  },
-
-  deactivateFromOrigin: function (origin, oldOrigin) {
-    // if we have the old provider, always set that before trying removal
-    let provider = this._getProviderFromOrigin(origin);
-    let oldProvider = this._getProviderFromOrigin(oldOrigin);
-    if (!oldProvider && this.providers.length)
-      oldProvider = this.providers[0];
-    this.provider = oldProvider;
-    if (provider)
-      SocialService.removeProvider(origin);
+    SocialService.addBuiltinProvider(origin, callback);
   },
 
   // Page Marking functionality
   isURIMarked: function(origin, aURI, aCallback) {
     promiseGetAnnotation(aURI).then(function(val) {
       if (val) {
         let providerList = JSON.parse(val);
         val = providerList.indexOf(origin) >= 0;
@@ -479,27 +358,29 @@ SocialErrorListener.prototype = {
         } catch (e) {}
       }
     }
 
     // Calling cancel() will raise some OnStateChange notifications by itself,
     // so avoid doing that more than once
     if (failure && aStatus != Components.results.NS_BINDING_ABORTED) {
       aRequest.cancel(Components.results.NS_BINDING_ABORTED);
-      Social.provider.errorState = "content-error";
+      let provider = Social._getProviderFromOrigin(this.iframe.getAttribute("origin"));
+      provider.errorState = "content-error";
       this.setErrorMessage(aWebProgress.QueryInterface(Ci.nsIDocShell)
                               .chromeEventHandler);
     }
   },
 
   onLocationChange: function SPL_onLocationChange(aWebProgress, aRequest, aLocation, aFlags) {
     if (aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) {
       aRequest.cancel(Components.results.NS_BINDING_ABORTED);
-      if (!Social.provider.errorState)
-        Social.provider.errorState = "content-error";
+      let provider = Social._getProviderFromOrigin(this.iframe.getAttribute("origin"));
+      if (!provider.errorState)
+        provider.errorState = "content-error";
       schedule(function() {
         this.setErrorMessage(aWebProgress.QueryInterface(Ci.nsIDocShell)
                               .chromeEventHandler);
       }.bind(this));
     }
   },
 
   onProgressChange: function SPL_onProgressChange() {},
--- a/browser/modules/test/unit/social/head.js
+++ b/browser/modules/test/unit/social/head.js
@@ -80,16 +80,23 @@ function initApp() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
   // prepare a blocklist file for the blocklist service
   var blocklistFile = gProfD.clone();
   blocklistFile.append("blocklist.xml");
   if (blocklistFile.exists())
     blocklistFile.remove(false);
   var source = do_get_file("blocklist.xml");
   source.copyTo(gProfD, "blocklist.xml");
+
+
+  let internalManager = Cc["@mozilla.org/addons/integration;1"].
+                     getService(Ci.nsIObserver).
+                     QueryInterface(Ci.nsITimerCallback);
+
+  internalManager.observe(null, "addons-startup", null);
 }
 
 function setManifestPref(manifest) {
   let string = Cc["@mozilla.org/supports-string;1"].
                createInstance(Ci.nsISupportsString);
   string.data = JSON.stringify(manifest);
   Services.prefs.setComplexValue("social.manifest." + manifest.origin, Ci.nsISupportsString, string);
 }
@@ -97,50 +104,61 @@ function setManifestPref(manifest) {
 function do_wait_observer(topic, cb) {
   function observer(subject, topic, data) {
     Services.obs.removeObserver(observer, topic);
     cb();
   }
   Services.obs.addObserver(observer, topic, false);
 }
 
+function do_add_providers(cb) {
+  // run only after social is already initialized
+  SocialService.addProvider(manifests[0], function() {
+    do_wait_observer("social:providers-changed", function() {
+      do_check_eq(Social.providers.length, 2, "2 providers installed");
+      do_execute_soon(cb);
+    });
+    SocialService.addProvider(manifests[1]);
+  });
+}
+
 function do_initialize_social(enabledOnStartup, cb) {
   initApp();
 
-  manifests.forEach(function (manifest) {
-    setManifestPref(manifest);
-  });
-  // Set both providers active and flag the first one as "current"
-  let activeVal = Cc["@mozilla.org/supports-string;1"].
-             createInstance(Ci.nsISupportsString);
-  let active = {};
-  for (let m of manifests)
-    active[m.origin] = 1;
-  activeVal.data = JSON.stringify(active);
-  Services.prefs.setComplexValue("social.activeProviders",
-                                 Ci.nsISupportsString, activeVal);
-  Services.prefs.setCharPref("social.provider.current", manifests[0].origin);
-  Services.prefs.setBoolPref("social.enabled", enabledOnStartup);
+  if (enabledOnStartup) {
+    // set prefs before initializing social
+    manifests.forEach(function (manifest) {
+      setManifestPref(manifest);
+    });
+    // Set both providers active and flag the first one as "current"
+    let activeVal = Cc["@mozilla.org/supports-string;1"].
+               createInstance(Ci.nsISupportsString);
+    let active = {};
+    for (let m of manifests)
+      active[m.origin] = 1;
+    activeVal.data = JSON.stringify(active);
+    Services.prefs.setComplexValue("social.activeProviders",
+                                   Ci.nsISupportsString, activeVal);
 
-  do_register_cleanup(function() {
-    manifests.forEach(function (manifest) {
-      Services.prefs.clearUserPref("social.manifest." + manifest.origin);
+    do_register_cleanup(function() {
+      manifests.forEach(function (manifest) {
+        Services.prefs.clearUserPref("social.manifest." + manifest.origin);
+      });
+      Services.prefs.clearUserPref("social.activeProviders");
     });
-    Services.prefs.clearUserPref("social.enabled");
-    Services.prefs.clearUserPref("social.provider.current");
-    Services.prefs.clearUserPref("social.activeProviders");
-  });
 
-  // expecting 2 providers installed
-  do_wait_observer("social:providers-changed", function() {
-    do_check_eq(Social.providers.length, 2, "2 providers installed");
-    cb();
-  });
+    // expecting 2 providers installed
+    do_wait_observer("social:providers-changed", function() {
+      do_check_eq(Social.providers.length, 2, "2 providers installed");
+      do_execute_soon(cb);
+    });
+  }
 
   // import and initialize everything
   SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
-  do_check_eq(SocialService.enabled, enabledOnStartup, "service is doing its thing");
-  do_check_true(SocialService.hasEnabledProviders, "Service has enabled providers");
+  do_check_eq(enabledOnStartup, SocialService.hasEnabledProviders, "Service has enabled providers");
   Social = Cu.import("resource:///modules/Social.jsm", {}).Social;
   do_check_false(Social.initialized, "Social is not initialized");
   Social.init();
   do_check_true(Social.initialized, "Social is initialized");
+  if (!enabledOnStartup)
+    do_execute_soon(cb);
 }
--- a/browser/modules/test/unit/social/test_social.js
+++ b/browser/modules/test/unit/social/test_social.js
@@ -8,23 +8,25 @@ function run_test() {
   add_test(testStartupEnabled);
   add_test(testDisableAfterStartup);
   do_initialize_social(true, run_next_test);
 }
 
 function testStartupEnabled() {
   // wait on startup before continuing
   do_check_eq(Social.providers.length, 2, "two social providers enabled");
-  do_check_true(Social.providers[0].enabled, "provider is enabled");
-  do_check_true(Social.providers[1].enabled, "provider is enabled");
+  do_check_true(Social.providers[0].enabled, "provider 0 is enabled");
+  do_check_true(Social.providers[1].enabled, "provider 1 is enabled");
   run_next_test();
 }
 
 function testDisableAfterStartup() {
-  do_wait_observer("social:provider-set", function() {
-    do_check_eq(Social.enabled, false, "Social is disabled");
-    do_check_false(Social.providers[0].enabled, "provider is enabled");
-    do_check_false(Social.providers[1].enabled, "provider is enabled");
-    do_test_finished();
-    run_next_test();
+  let SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
+  SocialService.removeProvider(Social.providers[0].origin, function() {
+    do_wait_observer("social:providers-changed", function() {
+      do_check_eq(Social.enabled, false, "Social is disabled");
+      do_check_eq(Social.providers.length, 0, "no social providers available");
+      do_test_finished();
+      run_next_test();
+    });
+    SocialService.removeProvider(Social.providers[0].origin)
   });
-  Social.enabled = false;
 }
--- a/browser/modules/test/unit/social/test_socialDisabledStartup.js
+++ b/browser/modules/test/unit/social/test_socialDisabledStartup.js
@@ -7,25 +7,23 @@ function run_test() {
   do_test_pending();
   add_test(testStartupDisabled);
   add_test(testEnableAfterStartup);
   do_initialize_social(false, run_next_test);
 }
 
 function testStartupDisabled() {
   // wait on startup before continuing
-  do_check_eq(Social.providers.length, 2, "two social providers available");
-  do_check_false(Social.providers[0].enabled, "provider is enabled");
-  do_check_false(Social.providers[1].enabled, "provider is enabled");
+  do_check_false(Social.enabled, "Social is disabled");
+  do_check_eq(Social.providers.length, 0, "zero social providers available");
   run_next_test();
 }
 
 function testEnableAfterStartup() {
-  do_wait_observer("social:provider-set", function() {
+  do_add_providers(function () {
     do_check_true(Social.enabled, "Social is enabled");
     do_check_eq(Social.providers.length, 2, "two social providers available");
-    do_check_true(Social.providers[0].enabled, "provider is enabled");
-    do_check_true(Social.providers[1].enabled, "provider is enabled");
+    do_check_true(Social.providers[0].enabled, "provider 0 is enabled");
+    do_check_true(Social.providers[1].enabled, "provider 1 is enabled");
     do_test_finished();
     run_next_test();
   });
-  Social.enabled = true;
 }
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -169,20 +169,16 @@ toolbarpaletteitem[place="palette"] > #p
   animation: animation-bookmarkAdded 800ms;
   animation-timing-function: ease, ease, ease;
 }
 
 #bookmarked-notification-anchor[notification="finish"][in-bookmarks-toolbar=true] > #bookmarked-notification {
   animation: animation-bookmarkAddedToBookmarksBar 800ms;
 }
 
-#bookmarks-menu-button[notification="finish"] {
-  pointer-events: none;
-}
-
 #bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
   animation: animation-bookmarkPulse 300ms;
   animation-delay: 600ms;
   animation-timing-function: ease-out;
 }
 
 /* Bookmark menus */
 menu.bookmark-item,
--- a/browser/themes/linux/customizableui/panelUIOverlay.css
+++ b/browser/themes/linux/customizableui/panelUIOverlay.css
@@ -36,14 +36,24 @@ toolbarbutton.social-provider-menuitem >
   width: 16px;
   height: 16px;
 }
 
 .subviewbutton:-moz-any([image],[targetURI],.cui-withicon, .restoreallitem, .bookmark-item)[checked="true"] > .toolbarbutton-icon {
   visibility: hidden;
 }
 
+menu.subviewbutton > .menu-right {
+  -moz-appearance: none;
+  list-style-image: url(chrome://browser/skin/places/bookmarks-menu-arrow.png);
+  -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+menu[disabled="true"].subviewbutton > .menu-right {
+  -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
 .PanelUI-subView toolbarseparator,
 .PanelUI-subView menuseparator,
 .cui-widget-panelview menuseparator,
 #PanelUI-footer-inner > toolbarseparator {
   -moz-appearance: none !important;
 }
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -100,18 +100,19 @@ browser.jar:
   skin/classic/browser/feeds/audioFeedIcon.png        (feeds/feedIcon.png)
   skin/classic/browser/feeds/audioFeedIcon16.png      (feeds/feedIcon16.png)
   skin/classic/browser/feeds/subscribe.css            (feeds/subscribe.css)
   skin/classic/browser/feeds/subscribe-ui.css         (feeds/subscribe-ui.css)
   skin/classic/browser/newtab/newTab.css              (newtab/newTab.css)
   skin/classic/browser/newtab/controls.png            (newtab/controls.png)
   skin/classic/browser/places/bookmarksMenu.png       (places/bookmarksMenu.png)
   skin/classic/browser/places/bookmarksToolbar.png    (places/bookmarksToolbar.png)
+  skin/classic/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel.png)
   skin/classic/browser/places/bookmarks-notification-finish.png  (places/bookmarks-notification-finish.png)
-  skin/classic/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel.png)
+  skin/classic/browser/places/bookmarks-menu-arrow.png           (places/bookmarks-menu-arrow.png)
   skin/classic/browser/places/calendar.png            (places/calendar.png)
 * skin/classic/browser/places/editBookmarkOverlay.css (places/editBookmarkOverlay.css)
   skin/classic/browser/places/livemark-item.png       (places/livemark-item.png)
   skin/classic/browser/places/star-icons.png          (places/star-icons.png)
   skin/classic/browser/places/starred48.png           (places/starred48.png)
   skin/classic/browser/places/unstarred48.png         (places/unstarred48.png)
   skin/classic/browser/places/places.css              (places/places.css)
   skin/classic/browser/places/organizer.css           (places/organizer.css)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..616f16b7fface8c53d164bb8fd7a221d646b4bc7
GIT binary patch
literal 183
zc%17D@N?(olHy`uVBq!ia0vp^3P3Et!3HGD8EPYeRGp`bV~EG`<OB)UW>E<V366k8
z`xh>3RQk&!B_$PZYHIrQ<kkC^ZWV4_y3}=^5c?nYN8+zuy;6H(x!|+?1wnnOs3;$e
zLI#d{R;xqvZP-ix<>$TpaqGsVhyI<+4nOoS2v$C^V0hKB-&c2TrSmmLW+_(YSsd(U
h(tH(;CR{IA8BR~l5VXvgyB6pY22WQ%mvv4FO#q;wKPUhI
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -399,30 +399,22 @@ toolbarpaletteitem[place="palette"] > #p
 }
 
 #bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
   background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
   animation: animation-bookmarkAdded 800ms;
   animation-timing-function: ease, ease, ease;
 }
 
-#bookmarked-notification-anchor[notification="finish"][in-bookmarks-toolbar=true] > #bookmarked-notification {
-  animation: animation-bookmarkAdded 800ms;
-}
-
 @media (min-resolution: 2dppx) {
   #bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
     background-image: url("chrome://browser/skin/places/bookmarks-notification-finish@2x.png");
   }
 }
 
-#bookmarks-menu-button[notification="finish"] {
-  pointer-events: none;
-}
-
 #bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
   animation: animation-bookmarkPulse 300ms;
   animation-delay: 600ms;
   animation-timing-function: ease-out;
 }
 
 /* ----- BOOKMARK MENUS ----- */
 
--- a/browser/themes/osx/customizableui/panelUIOverlay.css
+++ b/browser/themes/osx/customizableui/panelUIOverlay.css
@@ -55,17 +55,17 @@
 
   #PanelUI-customize:hover:active,
   #PanelUI-help:not([disabled]):hover:active,
   #PanelUI-quit:not([disabled]):hover:active {
     -moz-image-region: rect(0, 96px, 32px, 64px);
   }
 
   .subviewbutton[checked="true"] {
-    background-image: url("chrome://global/skin/menu/menu-check@2x.png");
+    background-image: url("chrome://global/skin/menu/shared-menu-check@2x.png");
   }
 
 }
 
 .panelUI-grid .toolbarbutton-1 {
   margin-right: 0;
   margin-left: 0;
   margin-bottom: 0;
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -887,17 +887,17 @@ toolbaritem[overflowedItem=true],
   background-clip: padding-box;
   background-position: center;
   background-repeat: no-repeat;
   background-size: 1px 18px;
   box-shadow: 0 0 0 1px hsla(0,0%,100%,.2);
 }
 
 .subviewbutton[checked="true"] {
-  background: url("chrome://global/skin/menu/menu-check.png") top 7px left 7px / 11px 11px no-repeat transparent;
+  background: url("chrome://global/skin/menu/shared-menu-check.png") top 7px left 7px / 11px 11px no-repeat transparent;
 }
 
 .PanelUI-subView > menu > .menu-iconic-left,
 .PanelUI-subView > menuitem > .menu-iconic-left {
   -moz-appearance: none;
   -moz-margin-end: 3px;
 }
 
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -359,20 +359,16 @@ toolbarpaletteitem[place="palette"] > #p
 }
 
 #bookmarked-notification-anchor[notification="finish"] > #bookmarked-notification {
   background-image: url("chrome://browser/skin/places/bookmarks-notification-finish.png");
   animation: animation-bookmarkAdded 800ms;
   animation-timing-function: ease, ease, ease;
 }
 
-#bookmarks-menu-button[notification="finish"] {
-  pointer-events: none;
-}
-
 #bookmarks-menu-button[notification="finish"] > .toolbarbutton-menubutton-dropmarker > .dropmarker-icon {
   animation: animation-bookmarkPulse 300ms;
   animation-delay: 600ms;
   animation-timing-function: ease-out;
 }
 
 /* ::::: bookmark menus ::::: */
 
--- a/browser/themes/windows/customizableui/panelUIOverlay.css
+++ b/browser/themes/windows/customizableui/panelUIOverlay.css
@@ -54,16 +54,26 @@ toolbarbutton.social-provider-menuitem >
   width: 16px;
   height: 16px;
 }
 
 .subviewbutton:-moz-any([image],[targetURI],.cui-withicon, .restoreallitem, .bookmark-item)[checked="true"] > .toolbarbutton-icon {
   visibility: hidden;
 }
 
+menu.subviewbutton > .menu-right {
+  -moz-appearance: none;
+  list-style-image: url(chrome://browser/skin/places/bookmarks-menu-arrow.png);
+  -moz-image-region: rect(0, 16px, 16px, 0);
+}
+
+menu[disabled="true"].subviewbutton > .menu-right {
+  -moz-image-region: rect(0, 32px, 16px, 16px);
+}
+
 %ifdef WINDOWS_AERO
 /* Win8 and beyond. */
 @media not all and (-moz-os-version: windows-vista) {
   @media not all and (-moz-os-version: windows-win7) {
     panelview .toolbarbutton-1,
     .subviewbutton,
     .widget-overflow-list .toolbarbutton-1,
     .panelUI-grid .toolbarbutton-1 > .toolbarbutton-menubutton-button,
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -125,16 +125,17 @@ browser.jar:
         skin/classic/browser/places/places.css                       (places/places.css)
 *       skin/classic/browser/places/organizer.css                    (places/organizer.css)
         skin/classic/browser/places/bookmark.png                     (places/bookmark.png)
         skin/classic/browser/places/query.png                        (places/query.png)
         skin/classic/browser/places/bookmarksMenu.png                (places/bookmarksMenu.png)
         skin/classic/browser/places/bookmarksToolbar.png             (places/bookmarksToolbar.png)
         skin/classic/browser/places/bookmarksToolbar-menuPanel.png   (places/bookmarksToolbar-menuPanel.png)
         skin/classic/browser/places/bookmarks-notification-finish.png (places/bookmarks-notification-finish.png)
+        skin/classic/browser/places/bookmarks-menu-arrow.png         (places/bookmarks-menu-arrow.png)
         skin/classic/browser/places/calendar.png                     (places/calendar.png)
         skin/classic/browser/places/toolbarDropMarker.png            (places/toolbarDropMarker.png)
         skin/classic/browser/places/editBookmarkOverlay.css          (places/editBookmarkOverlay.css)
         skin/classic/browser/places/libraryToolbar.png               (places/libraryToolbar.png)
         skin/classic/browser/places/starred48.png                    (places/starred48.png)
         skin/classic/browser/places/unstarred48.png                  (places/unstarred48.png)
         skin/classic/browser/places/tag.png                          (places/tag.png)
         skin/classic/browser/places/history.png                      (places/history.png)
@@ -453,16 +454,17 @@ browser.jar:
 *       skin/classic/aero/browser/places/places.css                  (places/places-aero.css)
 *       skin/classic/aero/browser/places/organizer.css               (places/organizer-aero.css)
         skin/classic/aero/browser/places/bookmark.png                (places/bookmark.png)
         skin/classic/aero/browser/places/query.png                   (places/query-aero.png)
         skin/classic/aero/browser/places/bookmarksMenu.png           (places/bookmarksMenu-aero.png)
         skin/classic/aero/browser/places/bookmarksToolbar.png        (places/bookmarksToolbar-aero.png)
         skin/classic/aero/browser/places/bookmarksToolbar-menuPanel.png (places/bookmarksToolbar-menuPanel-aero.png)
         skin/classic/aero/browser/places/bookmarks-notification-finish.png   (places/bookmarks-notification-finish.png)
+        skin/classic/aero/browser/places/bookmarks-menu-arrow.png    (places/bookmarks-menu-arrow.png)
         skin/classic/aero/browser/places/calendar.png                (places/calendar-aero.png)
         skin/classic/aero/browser/places/toolbarDropMarker.png       (places/toolbarDropMarker-aero.png)
         skin/classic/aero/browser/places/editBookmarkOverlay.css     (places/editBookmarkOverlay.css)
         skin/classic/aero/browser/places/libraryToolbar.png          (places/libraryToolbar-aero.png)
         skin/classic/aero/browser/places/starred48.png               (places/starred48-aero.png)
         skin/classic/aero/browser/places/unstarred48.png             (places/unstarred48.png)
         skin/classic/aero/browser/places/tag.png                     (places/tag-aero.png)
         skin/classic/aero/browser/places/history.png                 (places/history-aero.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..616f16b7fface8c53d164bb8fd7a221d646b4bc7
GIT binary patch
literal 183
zc%17D@N?(olHy`uVBq!ia0vp^3P3Et!3HGD8EPYeRGp`bV~EG`<OB)UW>E<V366k8
z`xh>3RQk&!B_$PZYHIrQ<kkC^ZWV4_y3}=^5c?nYN8+zuy;6H(x!|+?1wnnOs3;$e
zLI#d{R;xqvZP-ix<>$TpaqGsVhyI<+4nOoS2v$C^V0hKB-&c2TrSmmLW+_(YSsd(U
h(tH(;CR{IA8BR~l5VXvgyB6pY22WQ%mvv4FO#q;wKPUhI
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -1200,17 +1200,17 @@ MappedAttrParser::ParseMappedAttrValue(n
   if (!mDecl) {
     mDecl = new css::Declaration();
     mDecl->InitializeEmpty();
   }
 
   // Get the nsCSSProperty ID for our mapped attribute.
   nsCSSProperty propertyID =
     nsCSSProps::LookupProperty(nsDependentAtomString(aMappedAttrName),
-                               nsCSSProps::eEnabledForAllContent);
+                               nsCSSProps::eEnabled);
   if (propertyID != eCSSProperty_UNKNOWN) {
     bool changed; // outparam for ParseProperty. (ignored)
     mParser.ParseProperty(propertyID, aMappedAttrValue, mDocURI, mBaseURI,
                           mNodePrincipal, mDecl, &changed, false, true);
     return;
   }
   NS_ABORT_IF_FALSE(aMappedAttrName == nsGkAtoms::lang,
                     "Only 'lang' should be unrecognized!");
@@ -2534,17 +2534,17 @@ nsSVGElement::GetAnimatedAttr(int32_t aN
     // targeting width/height on outer-<svg> don't appear to be ignored
     // because we returned a nsISMILAttr for the corresponding
     // SVGAnimatedLength.
 
     // Mapped attributes:
     if (IsAttributeMapped(aName)) {
       nsCSSProperty prop =
         nsCSSProps::LookupProperty(nsDependentAtomString(aName),
-                                   nsCSSProps::eEnabledForAllContent);
+                                   nsCSSProps::eEnabled);
       // Check IsPropertyAnimatable to avoid attributes that...
       //  - map to explicitly unanimatable properties (e.g. 'direction')
       //  - map to unsupported attributes (e.g. 'glyph-orientation-horizontal')
       if (nsSMILCSSProperty::IsPropertyAnimatable(prop)) {
         return new nsSMILMappedAttribute(prop, this);
       }
     }
 
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2662,18 +2662,18 @@ nsDOMWindowUtils::ComputeAnimationDistan
   }
 
   nsresult rv;
   nsCOMPtr<nsIContent> content = do_QueryInterface(aElement, &rv);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Convert direction-dependent properties as appropriate, e.g.,
   // border-left to border-left-value.
-  nsCSSProperty property =
-    nsCSSProps::LookupProperty(aProperty, nsCSSProps::eIgnoreEnabledState);
+  nsCSSProperty property = nsCSSProps::LookupProperty(aProperty,
+                                                      nsCSSProps::eAny);
   if (property != eCSSProperty_UNKNOWN && nsCSSProps::IsShorthand(property)) {
     nsCSSProperty subprop0 = *nsCSSProps::SubpropertyEntryFor(property);
     if (nsCSSProps::PropHasFlags(subprop0, CSS_PROPERTY_REPORT_OTHER_NAME) &&
         nsCSSProps::OtherNameFor(subprop0) == property) {
       property = subprop0;
     } else {
       property = eCSSProperty_UNKNOWN;
     }
--- a/dom/bindings/GenerateCSS2PropertiesWebIDL.py
+++ b/dom/bindings/GenerateCSS2PropertiesWebIDL.py
@@ -2,28 +2,19 @@
 # License, v. 2.0. If a copy of the MPL was not distributed with this file,
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import sys
 import string
 
 propList = eval(sys.stdin.read())
 props = ""
-for [prop, id, flags, pref] in propList:
+for [prop, pref] in propList:
     extendedAttrs = ["Throws", "TreatNullAs=EmptyString"]
-    # To limit the overhead of Func= annotations, we only generate them when
-    # necessary, which is when the
-    # CSS_PROPERTY_ALWAYS_ENABLED_IN_CHROME_OR_CERTIFIED_APP flag is set.
-    # Otherwise, we try to get by with just a Pref= annotation or no annotation
-    # at all.
-    if "CSS_PROPERTY_ALWAYS_ENABLED_IN_CHROME_OR_CERTIFIED_APP" in flags:
-        extendedAttrs.append('Func="IsCSSPropertyExposedToJS<eCSSProperty_%s>"' % id)
-    # The following is an 'elif' because it is the responsibility of
-    # IsCSSPropertyExposedToJS to handle the pref if there is one.
-    elif pref is not "":
+    if pref is not "":
         extendedAttrs.append('Pref="%s"' % pref)
     if not prop.startswith("Moz"):
         prop = prop[0].lower() + prop[1:]
     # Unfortunately, even some of the getters here are fallible
     # (e.g. on nsComputedDOMStyle).
     props += "  [%s] attribute DOMString %s;\n" % (", ".join(extendedAttrs),
                                                    prop)
 
--- a/dom/ipc/moz.build
+++ b/dom/ipc/moz.build
@@ -36,36 +36,37 @@ EXPORTS.mozilla += [
     'AppProcessChecker.h',
     'PreallocatedProcessManager.h',
     'ProcessPriorityManager.h',
 ]
 
 UNIFIED_SOURCES += [
     'AppProcessChecker.cpp',
     'ColorPickerParent.cpp',
-    'ContentChild.cpp',
     'ContentParent.cpp',
     'ContentProcess.cpp',
     'CrashReporterParent.cpp',
     'FilePickerParent.cpp',
     'PermissionMessageUtils.cpp',
     'PreallocatedProcessManager.cpp',
     'ProcessPriorityManager.cpp',
     'StructuredCloneUtils.cpp',
     'TabChild.cpp',
     'TabContext.cpp',
     'TabMessageUtils.cpp',
     'TabParent.cpp',
 ]
 
 # Blob.cpp cannot be compiled in unified mode because it triggers a fatal gcc warning.
+# ContentChild.cpp cannot be compiled in unified mode because it forces NSPR logging.
 # CrashReporterChild.cpp cannot be compiled in unified mode because of name clashes
 # in OS X headers.
 SOURCES += [
     'Blob.cpp',
+    'ContentChild.cpp',
     'CrashReporterChild.cpp',
 ]
 
 IPDL_SOURCES += [
     'DOMTypes.ipdlh',
     'PBlob.ipdl',
     'PBlobStream.ipdl',
     'PBrowser.ipdl',
--- a/dom/smil/nsSMILAnimationController.cpp
+++ b/dom/smil/nsSMILAnimationController.cpp
@@ -789,17 +789,17 @@ nsSMILAnimationController::GetTargetIden
       // width/height are special as they may be attributes or for
       // outer-<svg> elements, mapped into style.
       if (attributeName == nsGkAtoms::width ||
           attributeName == nsGkAtoms::height) {
         isCSS = targetElem->GetNameSpaceID() != kNameSpaceID_SVG;
       } else {
         nsCSSProperty prop =
           nsCSSProps::LookupProperty(nsDependentAtomString(attributeName),
-                                     nsCSSProps::eEnabledForAllContent);
+                                     nsCSSProps::eEnabled);
         isCSS = nsSMILCSSProperty::IsPropertyAnimatable(prop);
       }
     }
   } else {
     isCSS = (attributeType == eSMILTargetAttrType_CSS);
   }
 
   // Construct the key
--- a/dom/smil/nsSMILCompositor.cpp
+++ b/dom/smil/nsSMILCompositor.cpp
@@ -120,17 +120,17 @@ nsSMILCompositor::ClearAnimationEffects(
 // Protected Helper Functions
 // --------------------------
 nsISMILAttr*
 nsSMILCompositor::CreateSMILAttr()
 {
   if (mKey.mIsCSS) {
     nsCSSProperty propId =
       nsCSSProps::LookupProperty(nsDependentAtomString(mKey.mAttributeName),
-                                 nsCSSProps::eEnabledForAllContent);
+                                 nsCSSProps::eEnabled);
     if (nsSMILCSSProperty::IsPropertyAnimatable(propId)) {
       return new nsSMILCSSProperty(propId, mKey.mElement.get());
     }
   } else {
     return mKey.mElement->GetAnimatedAttr(mKey.mAttributeNamespaceID,
                                           mKey.mAttributeName);
   }
   return nullptr;
--- a/dom/webidl/CSS2PropertiesProps.h
+++ b/dom/webidl/CSS2PropertiesProps.h
@@ -1,39 +1,34 @@
 /* A file meant as input to the preprocessor only */
 
 /* DO_PROP serves as an extra level of indirection to allow expansion
    of CSS_PROP_DOMPROP_PREFIXED */
 
 [
 
-#define PROP_STRINGIFY_INTERNAL(X) #X
-#define PROP_STRINGIFY(X) PROP_STRINGIFY_INTERNAL(X)
-
-#define DO_PROP(method, id, flags, pref) \
-  [ #method, #id, PROP_STRINGIFY(flags), pref ],
+#define DO_PROP(method, pref) \
+  [ #method, pref ],
 #define CSS_PROP(name, id, method, flags, pref, parsevariant, kwtable, \
 		 stylestruct, stylestructofset, animtype) \
-  DO_PROP(method, id, flags, pref)
+  DO_PROP(method, pref)
 #define CSS_PROP_SHORTHAND(name, id, method, flags, pref) \
-  DO_PROP(method, id, flags, pref)
+  DO_PROP(method, pref)
 #define CSS_PROP_PUBLIC_OR_PRIVATE(publicname_, privatename_) publicname_
 #define CSS_PROP_LIST_EXCLUDE_INTERNAL
 
 #include "nsCSSPropList.h"
 
 #undef CSS_PROP_LIST_EXCLUDE_INTERNAL
 #undef CSS_PROP_PUBLIC_OR_PRIVATE
 #undef CSS_PROP_SHORTHAND
 #undef CSS_PROP
 
 #define CSS_PROP_ALIAS(name, id, method, pref) \
-  DO_PROP(method, id, 0, pref)
+  DO_PROP(method, pref)
 
 #include "nsCSSPropAliasList.h"
 
 #undef CSS_PROP_ALIAS
 
 #undef DO_PROP
-#undef PROP_STRINGIFY
-#undef PROP_STRINGIFY_INTERNAL
 
 ]
--- a/editor/libeditor/html/nsHTMLCSSUtils.cpp
+++ b/editor/libeditor/html/nsHTMLCSSUtils.cpp
@@ -560,17 +560,17 @@ nsHTMLCSSUtils::GetCSSInlinePropertyBase
 
   MOZ_ASSERT(aStyleType == eSpecified);
   nsRefPtr<css::StyleRule> rule = element->GetInlineStyleRule();
   if (!rule) {
     return NS_OK;
   }
   nsCSSProperty prop =
     nsCSSProps::LookupProperty(nsDependentAtomString(aProperty),
-                               nsCSSProps::eEnabledForAllContent);
+                               nsCSSProps::eEnabled);
   MOZ_ASSERT(prop != eCSSProperty_UNKNOWN);
   rule->GetDeclaration()->GetValue(prop, aValue);
 
   return NS_OK;
 }
 
 already_AddRefed<nsComputedDOMStyle>
 nsHTMLCSSUtils::GetComputedStyle(nsIDOMElement* aElement)
--- a/layout/inspector/inCSSValueSearch.cpp
+++ b/layout/inspector/inCSSValueSearch.cpp
@@ -222,17 +222,17 @@ inCSSValueSearch::SetNormalizeChromeURLs
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 inCSSValueSearch::AddPropertyCriteria(const char16_t *aPropName)
 {
   nsCSSProperty prop =
     nsCSSProps::LookupProperty(nsDependentString(aPropName),
-                               nsCSSProps::eIgnoreEnabledState);
+                               nsCSSProps::eAny);
   mProperties[mPropertyCount] = prop;
   mPropertyCount++;
   return NS_OK;
 }
 
 NS_IMETHODIMP 
 inCSSValueSearch::GetTextCriteria(char16_t** aTextCriteria)
 {
--- a/layout/inspector/inDOMUtils.cpp
+++ b/layout/inspector/inDOMUtils.cpp
@@ -378,18 +378,18 @@ inDOMUtils::SelectorMatchesElement(nsIDO
   *aMatches = nsCSSRuleProcessor::SelectorListMatches(element, matchingContext,
                                                       sel);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 inDOMUtils::IsInheritedProperty(const nsAString &aPropertyName, bool *_retval)
 {
-  nsCSSProperty prop =
-    nsCSSProps::LookupProperty(aPropertyName, nsCSSProps::eIgnoreEnabledState);
+  nsCSSProperty prop = nsCSSProps::LookupProperty(aPropertyName,
+                                                  nsCSSProps::eAny);
   if (prop == eCSSProperty_UNKNOWN) {
     *_retval = false;
     return NS_OK;
   }
 
   if (prop == eCSSPropertyExtra_variable) {
     *_retval = true;
     return NS_OK;
@@ -560,17 +560,17 @@ static void GetOtherValuesForProperty(co
 }
 
 NS_IMETHODIMP
 inDOMUtils::GetCSSValuesForProperty(const nsAString& aProperty,
                                     uint32_t* aLength,
                                     char16_t*** aValues)
 {
   nsCSSProperty propertyID = nsCSSProps::LookupProperty(aProperty,
-                                                        nsCSSProps::eEnabledForAllContent);
+                                                        nsCSSProps::eEnabled);
   if (propertyID == eCSSProperty_UNKNOWN) {
     return NS_ERROR_FAILURE;
   }
 
   nsTArray<nsString> array;
   // We start collecting the values, BUT colors need to go in first, because array
   // needs to stay sorted, and the colors are sorted, so we just append them.
   if (propertyID == eCSSPropertyExtra_variable) {
--- a/layout/style/Declaration.cpp
+++ b/layout/style/Declaration.cpp
@@ -954,18 +954,17 @@ Declaration::GetValue(nsCSSProperty aPro
 }
 
 // Length of the "var-" prefix of custom property names.
 #define VAR_PREFIX_LENGTH 4
 
 bool
 Declaration::GetValueIsImportant(const nsAString& aProperty) const
 {
-  nsCSSProperty propID =
-    nsCSSProps::LookupProperty(aProperty, nsCSSProps::eIgnoreEnabledState);
+  nsCSSProperty propID = nsCSSProps::LookupProperty(aProperty, nsCSSProps::eAny);
   if (propID == eCSSProperty_UNKNOWN) {
     return false;
   }
   if (propID == eCSSPropertyExtra_variable) {
     return GetVariableValueIsImportant(Substring(aProperty, VAR_PREFIX_LENGTH));
   }
   return GetValueIsImportant(propID);
 }
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -40,17 +40,16 @@
 #include "nsContentUtils.h"
 #include "nsAutoPtr.h"
 #include "CSSCalc.h"
 #include "nsMediaFeatures.h"
 #include "nsLayoutUtils.h"
 #include "mozilla/Preferences.h"
 #include "nsRuleData.h"
 #include "mozilla/CSSVariableValues.h"
-#include "mozilla/dom/URL.h"
 
 using namespace mozilla;
 
 typedef nsCSSProps::KTableValue KTableValue;
 
 const uint32_t
 nsCSSProps::kParserVariantTable[eCSSProperty_COUNT_no_shorthands] = {
 #define CSS_PROP(name_, id_, method_, flags_, pref_, parsevariant_, kwtable_, \
@@ -244,27 +243,19 @@ public:
                                            nsIURI* aDocURL,
                                            nsIURI* aBaseURL,
                                            nsIPrincipal* aDocPrincipal,
                                            nsCSSStyleSheet* aSheet,
                                            uint32_t aLineNumber,
                                            uint32_t aLineOffset);
 
   nsCSSProperty LookupEnabledProperty(const nsAString& aProperty) {
-    static_assert(nsCSSProps::eEnabledForAllContent == 0,
-                  "nsCSSProps::eEnabledForAllContent should be zero for "
-                  "this bitfield to work");
-    nsCSSProps::EnabledState enabledState = nsCSSProps::eEnabledForAllContent;
-    if (mUnsafeRulesEnabled) {
-      enabledState |= nsCSSProps::eEnabledInUASheets;
-    }
-    if (mIsChromeOrCertifiedApp) {
-      enabledState |= nsCSSProps::eEnabledInChromeOrCertifiedApp;
-    }
-    return nsCSSProps::LookupProperty(aProperty, enabledState);
+    return nsCSSProps::LookupProperty(aProperty, mUnsafeRulesEnabled ?
+                                                   nsCSSProps::eEnabledInUASheets :
+                                                   nsCSSProps::eEnabled);
   }
 
 protected:
   class nsAutoParseCompoundProperty;
   friend class nsAutoParseCompoundProperty;
 
   class nsAutoFailingSupportsRule;
   friend class nsAutoFailingSupportsRule;
@@ -917,22 +908,16 @@ protected:
   bool mHashlessColorQuirk : 1;
 
   // True when the unitless length quirk applies.
   bool mUnitlessLengthQuirk : 1;
 
   // True if unsafe rules should be allowed
   bool mUnsafeRulesEnabled : 1;
 
-  // True if we are in parsing rules for Chrome or Certified App content,
-  // in which case CSS properties with the
-  // CSS_PROPERTY_ALWAYS_ENABLED_IN_CHROME_OR_CERTIFIED_APP
-  // flag should be allowed.
-  bool mIsChromeOrCertifiedApp : 1;
-
   // True if viewport units should be allowed.
   bool mViewportUnitsEnabled : 1;
 
   // True for parsing media lists for HTML attributes, where we have to
   // ignore CSS comments.
   bool mHTMLMediaMode : 1;
 
   // This flag is set when parsing a non-box shorthand; it's used to not apply
@@ -1017,17 +1002,16 @@ CSSParserImpl::CSSParserImpl()
     mChildLoader(nullptr),
     mSection(eCSSSection_Charset),
     mNameSpaceMap(nullptr),
     mHavePushBack(false),
     mNavQuirkMode(false),
     mHashlessColorQuirk(false),
     mUnitlessLengthQuirk(false),
     mUnsafeRulesEnabled(false),
-    mIsChromeOrCertifiedApp(false),
     mViewportUnitsEnabled(true),
     mHTMLMediaMode(false),
     mParsingCompoundProperty(false),
     mInSupportsCondition(false),
     mInFailingSupportsRule(false),
     mSuppressErrors(false),
     mNextFree(nullptr)
 {
@@ -1158,19 +1142,16 @@ CSSParserImpl::ParseSheet(const nsAStrin
       }
     }
   }
   else {
     mSection = eCSSSection_Charset; // sheet is empty, any rules are fair
   }
 
   mUnsafeRulesEnabled = aAllowUnsafeRules;
-  mIsChromeOrCertifiedApp =
-    dom::IsChromeURI(aSheetURI) ||
-    aSheetPrincipal->GetAppStatus() == nsIPrincipal::APP_STATUS_CERTIFIED;
 
   nsCSSToken* tk = &mToken;
   for (;;) {
     // Get next non-whitespace token
     if (!GetToken(true)) {
       OUTPUT_ERROR();
       break;
     }
@@ -1184,17 +1165,16 @@ CSSParserImpl::ParseSheet(const nsAStrin
     UngetToken();
     if (ParseRuleSet(AppendRuleToSheet, this)) {
       mSection = eCSSSection_General;
     }
   }
   ReleaseScanner();
 
   mUnsafeRulesEnabled = false;
-  mIsChromeOrCertifiedApp = false;
 
   // XXX check for low level errors
   return NS_OK;
 }
 
 /**
  * Determines whether the identifier contained in the given string is a
  * vendor-specific identifier, as described in CSS 2.1 section 4.1.2.1.
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -3902,18 +3902,17 @@ CSS_PROP_SVGRESET(
     offsetof(nsStyleSVGReset, mVectorEffect),
     eStyleAnimType_EnumU8)
 
 CSS_PROP_DISPLAY(
     will-change,
     will_change,
     WillChange,
     CSS_PROPERTY_PARSE_FUNCTION |
-        CSS_PROPERTY_VALUE_LIST_USES_COMMAS |
-        CSS_PROPERTY_ALWAYS_ENABLED_IN_CHROME_OR_CERTIFIED_APP,
+        CSS_PROPERTY_VALUE_LIST_USES_COMMAS,
     "layout.css.will-change.enabled",
     0,
     nullptr,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_None)
 
 // The shorthands below are essentially aliases, but they require different
 // parsing rules, and are therefore implemented as shorthands.
--- a/layout/style/nsCSSProps.cpp
+++ b/layout/style/nsCSSProps.cpp
@@ -390,23 +390,23 @@ nsCSSProps::LookupProperty(const nsACStr
   if (MOZ_LIKELY(res < eCSSProperty_COUNT)) {
     if (res != eCSSProperty_UNKNOWN && !IsEnabled(res, aEnabled)) {
       res = eCSSProperty_UNKNOWN;
     }
     return res;
   }
   MOZ_ASSERT(eCSSAliasCount != 0,
              "'res' must be an alias at this point so we better have some!");
-  // We intentionally don't support eEnabledInUASheets or eEnabledInChromeOrCertifiedApp
-  // for aliases yet because it's unlikely there will be a need for it.
-  if (IsEnabled(res) || aEnabled == eIgnoreEnabledState) {
+  // We intentionally don't support eEnabledInUASheets for aliases yet
+  // because it's unlikely there will be a need for it.
+  if (IsEnabled(res) || aEnabled == eAny) {
     res = gAliases[res - eCSSProperty_COUNT];
     NS_ABORT_IF_FALSE(0 <= res && res < eCSSProperty_COUNT,
                       "aliases must not point to other aliases");
-    if (IsEnabled(res) || aEnabled == eIgnoreEnabledState) {
+    if (IsEnabled(res) || aEnabled == eAny) {
       return res;
     }
   }
   return eCSSProperty_UNKNOWN;
 }
 
 nsCSSProperty
 nsCSSProps::LookupProperty(const nsAString& aProperty, EnabledState aEnabled)
@@ -426,21 +426,21 @@ nsCSSProps::LookupProperty(const nsAStri
       res = eCSSProperty_UNKNOWN;
     }
     return res;
   }
   MOZ_ASSERT(eCSSAliasCount != 0,
              "'res' must be an alias at this point so we better have some!");
   // We intentionally don't support eEnabledInUASheets for aliases yet
   // because it's unlikely there will be a need for it.
-  if (IsEnabled(res) || aEnabled == eIgnoreEnabledState) {
+  if (IsEnabled(res) || aEnabled == eAny) {
     res = gAliases[res - eCSSProperty_COUNT];
     NS_ABORT_IF_FALSE(0 <= res && res < eCSSProperty_COUNT,
                       "aliases must not point to other aliases");
-    if (IsEnabled(res) || aEnabled == eIgnoreEnabledState) {
+    if (IsEnabled(res) || aEnabled == eAny) {
       return res;
     }
   }
   return eCSSProperty_UNKNOWN;
 }
 
 nsCSSFontDesc
 nsCSSProps::LookupFontDesc(const nsACString& aFontDesc)
--- a/layout/style/nsCSSProps.h
+++ b/layout/style/nsCSSProps.h
@@ -196,24 +196,16 @@ static_assert((CSS_PROPERTY_PARSE_PROPER
 
 // This property is always enabled in UA sheets.  This is meant to be used
 // together with a pref that enables the property for non-UA sheets.
 // Note that if such a property has an alias, then any use of that alias
 // in an UA sheet will still be ignored unless the pref is enabled.
 // In other words, this bit has no effect on the use of aliases.
 #define CSS_PROPERTY_ALWAYS_ENABLED_IN_UA_SHEETS  (1<<22)
 
-// This property is always enabled in chrome and in certified apps. This is
-// meant to be used together with a pref that enables the property for
-// non-privileged content. Note that if such a property has an alias, then any
-// use of that alias in privileged content will still be ignored unless the
-// pref is enabled. In other words, this bit has no effect on the use of
-// aliases.
-#define CSS_PROPERTY_ALWAYS_ENABLED_IN_CHROME_OR_CERTIFIED_APP (1<<23)
-
 /**
  * Types of animatable values.
  */
 enum nsStyleAnimType {
   // requires a custom implementation in
   // nsStyleAnimation::ExtractComputedValue
   eStyleAnimType_Custom,
 
@@ -261,31 +253,22 @@ enum nsStyleAnimType {
 
 class nsCSSProps {
 public:
   typedef int16_t KTableValue;
 
   static void AddRefTable(void);
   static void ReleaseTable(void);
 
+  // Given a property string, return the enum value
   enum EnabledState {
-    // The default EnabledState: only enable what's enabled for all content,
-    // given the current values of preferences.
-    eEnabledForAllContent = 0,
-    // Enable a property in UA sheets.
-    eEnabledInUASheets    = 0x01,
-    // Enable a property in privileged content, i.e. chrome or Certified Apps
-    eEnabledInChromeOrCertifiedApp = 0x02,
-    // Special value to unconditionally enable a property. This implies all the
-    // bits above, but is strictly more than just their OR-ed union.
-    // This just skips any test so a property will be enabled even if it would
-    // have been disabled with all the bits above set.
-    eIgnoreEnabledState   = 0xff
+    eEnabled,
+    eEnabledInUASheets,
+    eAny
   };
-
   // Looks up the property with name aProperty and returns its corresponding
   // nsCSSProperty value.  If aProperty is the name of a custom property,
   // then eCSSPropertyExtra_variable will be returned.
   static nsCSSProperty LookupProperty(const nsAString& aProperty,
                                       EnabledState aEnabled);
   static nsCSSProperty LookupProperty(const nsACString& aProperty,
                                       EnabledState aEnabled);
   // Returns whether aProperty is a custom property name, i.e. begins with
@@ -459,35 +442,21 @@ public:
 
   static bool IsEnabled(nsCSSProperty aProperty) {
     NS_ABORT_IF_FALSE(0 <= aProperty &&
                       aProperty < eCSSProperty_COUNT_with_aliases,
                       "out of range");
     return gPropertyEnabled[aProperty];
   }
 
-  static bool IsEnabled(nsCSSProperty aProperty, EnabledState aEnabled)
-  {
-    if (IsEnabled(aProperty)) {
-      return true;
-    }
-    if (aEnabled == eIgnoreEnabledState) {
-      return true;
-    }
-    if ((aEnabled & eEnabledInUASheets) &&
-        PropHasFlags(aProperty, CSS_PROPERTY_ALWAYS_ENABLED_IN_UA_SHEETS))
-    {
-      return true;
-    }
-    if ((aEnabled & eEnabledInChromeOrCertifiedApp) &&
-        PropHasFlags(aProperty, CSS_PROPERTY_ALWAYS_ENABLED_IN_CHROME_OR_CERTIFIED_APP))
-    {
-      return true;
-    }
-    return false;
+  static bool IsEnabled(nsCSSProperty aProperty, EnabledState aEnabled) {
+    return IsEnabled(aProperty) ||
+      (aEnabled == eEnabledInUASheets &&
+       PropHasFlags(aProperty, CSS_PROPERTY_ALWAYS_ENABLED_IN_UA_SHEETS)) ||
+      aEnabled == eAny;
   }
 
 public:
 
 #define CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(iter_, prop_)                    \
   for (const nsCSSProperty* iter_ = nsCSSProps::SubpropertyEntryFor(prop_);   \
        *iter_ != eCSSProperty_UNKNOWN; ++iter_) \
     if (nsCSSProps::IsEnabled(*iter_))
@@ -635,33 +604,9 @@ public:
   static const KTableValue kWidthKTable[]; // also min-width, max-width
   static const KTableValue kWindowShadowKTable[];
   static const KTableValue kWordBreakKTable[];
   static const KTableValue kWordWrapKTable[];
   static const KTableValue kWritingModeKTable[];
   static const KTableValue kHyphensKTable[];
 };
 
-inline nsCSSProps::EnabledState operator|(nsCSSProps::EnabledState a,
-                                          nsCSSProps::EnabledState b)
-{
-  return nsCSSProps::EnabledState(int(a) | int(b));
-}
-
-inline nsCSSProps::EnabledState operator&(nsCSSProps::EnabledState a,
-                                          nsCSSProps::EnabledState b)
-{
-  return nsCSSProps::EnabledState(int(a) & int(b));
-}
-
-inline nsCSSProps::EnabledState& operator|=(nsCSSProps::EnabledState& a,
-                                            nsCSSProps::EnabledState b)
-{
-  return a = a | b;
-}
-
-inline nsCSSProps::EnabledState& operator&=(nsCSSProps::EnabledState& a,
-                                            nsCSSProps::EnabledState b)
-{
-  return a = a & b;
-}
-
 #endif /* nsCSSProps_h___ */
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -713,17 +713,17 @@ nsComputedDOMStyle::ClearCurrentStyleSou
   // whenever a frame is not available.
   mStyleContextHolder = nullptr;
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::GetPropertyCSSValue(const nsAString& aPropertyName, ErrorResult& aRv)
 {
   nsCSSProperty prop = nsCSSProps::LookupProperty(aPropertyName,
-                                                  nsCSSProps::eEnabledForAllContent);
+                                                  nsCSSProps::eEnabled);
 
   bool needsLayoutFlush;
   nsComputedStyleMap::Entry::ComputeMethod getter;
 
   if (prop == eCSSPropertyExtra_variable) {
     needsLayoutFlush = false;
     getter = nullptr;
   } else {
--- a/layout/style/nsDOMCSSDeclaration.cpp
+++ b/layout/style/nsDOMCSSDeclaration.cpp
@@ -11,18 +11,16 @@
 #include "nsCSSStyleSheet.h"
 #include "mozilla/css/Rule.h"
 #include "mozilla/css/Declaration.h"
 #include "mozilla/dom/CSS2PropertiesBinding.h"
 #include "nsCSSProps.h"
 #include "nsCOMPtr.h"
 #include "mozAutoDocUpdate.h"
 #include "nsIURI.h"
-#include "mozilla/dom/BindingUtils.h"
-#include "nsContentUtils.h"
 
 using namespace mozilla;
 
 nsDOMCSSDeclaration::~nsDOMCSSDeclaration()
 {
 }
 
 /* virtual */ JSObject*
@@ -166,19 +164,18 @@ nsDOMCSSDeclaration::IndexedGetter(uint3
   css::Declaration* decl = GetCSSDeclaration(false);
   aFound = decl && decl->GetNthProperty(aIndex, aPropName);
 }
 
 NS_IMETHODIMP
 nsDOMCSSDeclaration::GetPropertyValue(const nsAString& aPropertyName,
                                       nsAString& aReturn)
 {
-  const nsCSSProperty propID =
-    nsCSSProps::LookupProperty(aPropertyName,
-                               nsCSSProps::eEnabledForAllContent);
+  const nsCSSProperty propID = nsCSSProps::LookupProperty(aPropertyName,
+                                                          nsCSSProps::eEnabled);
   if (propID == eCSSProperty_UNKNOWN) {
     aReturn.Truncate();
     return NS_OK;
   }
 
   if (propID == eCSSPropertyExtra_variable) {
     GetCustomPropertyValue(aPropertyName, aReturn);
     return NS_OK;
@@ -186,19 +183,18 @@ nsDOMCSSDeclaration::GetPropertyValue(co
 
   return GetPropertyValue(propID, aReturn);
 }
 
 NS_IMETHODIMP
 nsDOMCSSDeclaration::GetAuthoredPropertyValue(const nsAString& aPropertyName,
                                               nsAString& aReturn)
 {
-  const nsCSSProperty propID =
-    nsCSSProps::LookupProperty(aPropertyName,
-                               nsCSSProps::eEnabledForAllContent);
+  const nsCSSProperty propID = nsCSSProps::LookupProperty(aPropertyName,
+                                                          nsCSSProps::eEnabled);
   if (propID == eCSSProperty_UNKNOWN) {
     aReturn.Truncate();
     return NS_OK;
   }
 
   if (propID == eCSSPropertyExtra_variable) {
     GetCustomPropertyValue(aPropertyName, aReturn);
     return NS_OK;
@@ -228,19 +224,18 @@ nsDOMCSSDeclaration::GetPropertyPriority
 }
 
 NS_IMETHODIMP
 nsDOMCSSDeclaration::SetProperty(const nsAString& aPropertyName,
                                  const nsAString& aValue,
                                  const nsAString& aPriority)
 {
   // In the common (and fast) cases we can use the property id
-  nsCSSProperty propID =
-    nsCSSProps::LookupProperty(aPropertyName,
-                               nsCSSProps::eEnabledForAllContent);
+  nsCSSProperty propID = nsCSSProps::LookupProperty(aPropertyName,
+                                                    nsCSSProps::eEnabled);
   if (propID == eCSSProperty_UNKNOWN) {
     return NS_OK;
   }
 
   if (aValue.IsEmpty()) {
     // If the new value of the property is an empty string we remove the
     // property.
     // XXX this ignores the priority string, should it?
@@ -265,19 +260,18 @@ nsDOMCSSDeclaration::SetProperty(const n
   }
   return ParsePropertyValue(propID, aValue, important);
 }
 
 NS_IMETHODIMP
 nsDOMCSSDeclaration::RemoveProperty(const nsAString& aPropertyName,
                                     nsAString& aReturn)
 {
-  const nsCSSProperty propID =
-    nsCSSProps::LookupProperty(aPropertyName,
-                               nsCSSProps::eEnabledForAllContent);
+  const nsCSSProperty propID = nsCSSProps::LookupProperty(aPropertyName,
+                                                          nsCSSProps::eEnabled);
   if (propID == eCSSProperty_UNKNOWN) {
     aReturn.Truncate();
     return NS_OK;
   }
 
   if (propID == eCSSPropertyExtra_variable) {
     RemoveCustomProperty(aPropertyName);
     return NS_OK;
@@ -426,29 +420,8 @@ nsDOMCSSDeclaration::RemoveCustomPropert
   // between when we mutate the declaration and when we set the new
   // rule (see stack in bug 209575).
   mozAutoDocConditionalContentUpdateBatch autoUpdate(DocToUpdate(), true);
 
   decl = decl->EnsureMutable();
   decl->RemoveVariableDeclaration(Substring(aPropertyName, VAR_PREFIX_LENGTH));
   return SetCSSDeclaration(decl);
 }
-
-bool IsCSSPropertyExposedToJS(nsCSSProperty aProperty, JSContext* cx, JSObject* obj)
-{
-  nsCSSProps::EnabledState enabledState = nsCSSProps::eEnabledForAllContent;
-
-  // Optimization: we skip checking properties of the JSContext
-  // in the majority case where the property does not have the
-  // CSS_PROPERTY_ALWAYS_ENABLED_IN_PRIVILEGED_CONTENT flag.
-  bool isEnabledInChromeOrCertifiedApp
-    = nsCSSProps::PropHasFlags(aProperty,
-                               CSS_PROPERTY_ALWAYS_ENABLED_IN_CHROME_OR_CERTIFIED_APP);
-
-  if (isEnabledInChromeOrCertifiedApp) {
-    if (dom::IsInCertifiedApp(cx, obj) ||
-        nsContentUtils::ThreadsafeIsCallerChrome())
-    {
-      enabledState |= nsCSSProps::eEnabledInChromeOrCertifiedApp;
-    }
-  }
-  return nsCSSProps::IsEnabled(aProperty, enabledState);
-}
--- a/layout/style/nsDOMCSSDeclaration.h
+++ b/layout/style/nsDOMCSSDeclaration.h
@@ -10,18 +10,16 @@
 
 #include "nsICSSDeclaration.h"
 
 #include "mozilla/Attributes.h"
 #include "nsCOMPtr.h"
 
 class nsIPrincipal;
 class nsIDocument;
-struct JSContext;
-class JSObject;
 
 namespace mozilla {
 namespace css {
 class Declaration;
 class Loader;
 class Rule;
 }
 }
@@ -149,17 +147,9 @@ protected:
 protected:
   virtual ~nsDOMCSSDeclaration();
   nsDOMCSSDeclaration()
   {
     SetIsDOMBinding();
   }
 };
 
-bool IsCSSPropertyExposedToJS(nsCSSProperty aProperty, JSContext* cx, JSObject* obj);
-
-template <nsCSSProperty Property>
-MOZ_ALWAYS_INLINE bool IsCSSPropertyExposedToJS(JSContext* cx, JSObject* obj)
-{
-  return IsCSSPropertyExposedToJS(Property, cx, obj);
-}
-
 #endif // nsDOMCSSDeclaration_h___
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -4854,19 +4854,18 @@ nsRuleNode::ComputeDisplayData(void* aSt
     } else if (property.unit == eCSSUnit_None) {
       transition->SetProperty(eCSSPropertyExtra_no_properties);
     } else if (property.list) {
       const nsCSSValue &val = property.list->mValue;
 
       if (val.GetUnit() == eCSSUnit_Ident) {
         nsDependentString
           propertyStr(property.list->mValue.GetStringBufferValue());
-        nsCSSProperty prop =
-          nsCSSProps::LookupProperty(propertyStr,
-                                     nsCSSProps::eEnabledForAllContent);
+        nsCSSProperty prop = nsCSSProps::LookupProperty(propertyStr,
+                                                        nsCSSProps::eEnabled);
         if (prop == eCSSProperty_UNKNOWN) {
           transition->SetUnknownProperty(propertyStr);
         } else {
           transition->SetProperty(prop);
         }
       } else {
         NS_ABORT_IF_FALSE(val.GetUnit() == eCSSUnit_All,
                           nsPrintfCString("Invalid transition property unit %d",
@@ -5499,18 +5498,17 @@ nsRuleNode::ComputeDisplayData(void* aSt
         if (buffer.EqualsLiteral("opacity")) {
           display->mWillChangeBitField |= NS_STYLE_WILL_CHANGE_OPACITY;
         }
         if (buffer.EqualsLiteral("scroll-position")) {
           display->mWillChangeBitField |= NS_STYLE_WILL_CHANGE_SCROLL;
         }
 
         nsCSSProperty prop =
-          nsCSSProps::LookupProperty(buffer,
-                                     nsCSSProps::eEnabledForAllContent);
+          nsCSSProps::LookupProperty(buffer, nsCSSProps::eEnabled);
         if (prop != eCSSProperty_UNKNOWN &&
             nsCSSProps::PropHasFlags(prop,
                                      CSS_PROPERTY_CREATES_STACKING_CONTEXT))
         {
           display->mWillChangeBitField |= NS_STYLE_WILL_CHANGE_STACKING_CONTEXT;
         }
       }
     }
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -2298,17 +2298,17 @@ void nsTransition::SetInitialValues()
   mDuration = 0.0;
   mDelay = 0.0;
   mProperty = eCSSPropertyExtra_all_properties;
 }
 
 void nsTransition::SetUnknownProperty(const nsAString& aUnknownProperty)
 {
   NS_ASSERTION(nsCSSProps::LookupProperty(aUnknownProperty,
-                                          nsCSSProps::eEnabledForAllContent) ==
+                                          nsCSSProps::eEnabled) ==
                  eCSSProperty_UNKNOWN,
                "should be unknown property");
   mProperty = eCSSProperty_UNKNOWN;
   mUnknownProperty = do_GetAtom(aUnknownProperty);
 }
 
 nsAnimation::nsAnimation(const nsAnimation& aCopy)
   : mTimingFunction(aCopy.mTimingFunction)
--- a/layout/style/test/TestCSSPropertyLookup.cpp
+++ b/layout/style/test/TestCSSPropertyLookup.cpp
@@ -35,49 +35,47 @@ TestProps()
   const char*const* et = &kCSSRawProperties[0];
   const char*const* end = &kCSSRawProperties[eCSSProperty_COUNT];
   index = eCSSProperty_UNKNOWN;
   while (et < end) {
     char tagName[100];
     PL_strcpy(tagName, *et);
     index = nsCSSProperty(int32_t(index) + 1);
 
-    id = nsCSSProps::LookupProperty(nsCString(tagName),
-                                    nsCSSProps::eIgnoreEnabledState);
+    id = nsCSSProps::LookupProperty(nsCString(tagName), nsCSSProps::eAny);
     if (id == eCSSProperty_UNKNOWN) {
       printf("bug: can't find '%s'\n", tagName);
       success = false;
     }
     if (id != index) {
       printf("bug: name='%s' id=%d index=%d\n", tagName, id, index);
       success = false;
     }
 
     // fiddle with the case to make sure we can still find it
     if (('a' <= tagName[0]) && (tagName[0] <= 'z')) {
       tagName[0] = tagName[0] - 32;
     }
     id = nsCSSProps::LookupProperty(NS_ConvertASCIItoUTF16(tagName),
-                                    nsCSSProps::eIgnoreEnabledState);
+                                    nsCSSProps::eAny);
     if (id < 0) {
       printf("bug: can't find '%s'\n", tagName);
       success = false;
     }
     if (index != id) {
       printf("bug: name='%s' id=%d index=%d\n", tagName, id, index);
       success = false;
     }
     et++;
   }
 
   // Now make sure we don't find some garbage
   for (int i = 0; i < (int) (sizeof(kJunkNames) / sizeof(const char*)); i++) {
     const char* const tag = kJunkNames[i];
-    id = nsCSSProps::LookupProperty(nsAutoCString(tag),
-                                    nsCSSProps::eIgnoreEnabledState);
+    id = nsCSSProps::LookupProperty(nsAutoCString(tag), nsCSSProps::eAny);
     if (id >= 0) {
       printf("bug: found '%s'\n", tag ? tag : "(null)");
       success = false;
     }
   }
 
   nsCSSProps::ReleaseTable();
   return success;
--- a/mobile/android/base/GeckoApp.java
+++ b/mobile/android/base/GeckoApp.java
@@ -365,19 +365,19 @@ public abstract class GeckoApp
 
     @Override
     public void openMenu() {
         openOptionsMenu();
     }
 
     @Override
     public void showMenu(View menu) {
-        // Hide the menu only if we are showing the MenuPopup.
-        if (!HardwareUtils.hasMenuButton())
-            closeMenu();
+        // Hide the menu before we reshow it to avoid platform specific bugs like
+        // bug 794581 and bug 968182.
+        closeMenu();
 
         mMenuPanel.removeAllViews();
         mMenuPanel.addView(menu);
 
         openOptionsMenu();
     }
 
     @Override
--- a/mobile/android/base/home/HomePager.java
+++ b/mobile/android/base/home/HomePager.java
@@ -249,16 +249,20 @@ public class HomePager extends ViewPager
         if (mHomeBanner != null) {
             mHomeBanner.handleHomeTouch(event);
         }
 
         return super.dispatchTouchEvent(event);
     }
 
     public void onToolbarFocusChange(boolean hasFocus) {
+        if (mHomeBanner == null) {
+            return;
+        }
+
         // We should only make the banner active if the toolbar is not focused and we are on the default page
         final boolean active = !hasFocus && getCurrentItem() == mDefaultPageIndex;
         mHomeBanner.setActive(active);
     }
 
     private void updateUiFromConfigState(HomeConfig.State configState) {
         // We only care about the adapter if HomePager is currently
         // loaded, which means it's visible in the activity.
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -63,16 +63,17 @@
 <!ENTITY num_tabs2 "&formatD; tabs">
 <!ENTITY new_tab_opened "New tab opened">
 
 <!ENTITY settings "Settings">
 <!ENTITY settings_title "Settings">
 <!ENTITY pref_category_advanced "Advanced">
 <!ENTITY pref_category_customize "Customize">
 <!ENTITY pref_category_search3 "Search">
+<!ENTITY pref_category_search_summary "Customize your search providers">
 <!ENTITY pref_category_display "Display">
 <!ENTITY pref_category_privacy_short "Privacy">
 <!ENTITY pref_category_vendor "&vendorShortName;">
 <!ENTITY pref_category_datareporting "Data choices">
 <!ENTITY pref_category_installed_search_engines "Installed search engines">
 <!ENTITY pref_category_add_search_providers "Add more search providers">
 <!ENTITY pref_category_search_restore_defaults "Restore search engines">
 <!ENTITY pref_search_restore_defaults "Restore defaults">
@@ -84,16 +85,17 @@
      it is applicable. -->
 <!ENTITY pref_search_hint "TIP: Add any website to your list of search providers by long-pressing on its search field and then tapping the &formatI; icon.">
 <!ENTITY pref_category_devtools "Developer tools">
 <!ENTITY pref_developer_remotedebugging "Remote debugging">
 <!ENTITY pref_developer_remotedebugging_docs "Learn more">
 <!ENTITY pref_remember_signons "Remember passwords">
 
 <!ENTITY pref_category_home "Home">
+<!ENTITY pref_category_home_summary "Customize your homepage">
 <!ENTITY pref_category_home_panels "Panels">
 <!ENTITY pref_home_add_panel "Add panel">
 <!ENTITY home_add_panel_title "Add new panel">
 <!ENTITY home_add_panel_empty "Sorry, we couldn\'t find any panels for you to add.">
 <!-- Localization note (home_add_panel_installed):
      The &formatS; will be replaced with the name of the new panel the user just
      selected to be added to the home page. -->
 <!ENTITY home_add_panel_installed "\'&formatS;\' added to homepage">
--- a/mobile/android/base/resources/xml-v11/preferences_customize.xml
+++ b/mobile/android/base/resources/xml-v11/preferences_customize.xml
@@ -5,22 +5,24 @@
 
 <!-- Changes should be mirrored to preferences_customize_tablet.xml. -->
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                   xmlns:gecko="http://schemas.android.com/apk/res-auto"
                   android:enabled="false">
 
     <PreferenceScreen android:title="@string/pref_category_home"
+                      android:summary="@string/pref_category_home_summary"
                       android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
             <extra android:name="resource"
                    android:value="preferences_home" />
     </PreferenceScreen>
 
      <PreferenceScreen android:title="@string/pref_category_search"
+                       android:summary="@string/pref_category_search_summary"
                        android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
          <extra android:name="resource"
                 android:value="preferences_search"/>
      </PreferenceScreen>
 
     <ListPreference android:key="android.not_a_preference.restoreSession3"
                     android:title="@string/pref_restore"
                     android:defaultValue="quit"
--- a/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml
+++ b/mobile/android/base/resources/xml-v11/preferences_customize_tablet.xml
@@ -12,22 +12,24 @@
                   android:title="@string/pref_category_customize"
                   android:enabled="false">
 
     <org.mozilla.gecko.preferences.SyncPreference android:key="android.not_a_preference.sync"
                                                   android:title="@string/pref_sync"
                                                   android:persistent="false" />
 
     <PreferenceScreen android:title="@string/pref_category_home"
+                      android:summary="@string/pref_category_home_summary"
                       android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
             <extra android:name="resource"
                    android:value="preferences_home" />
     </PreferenceScreen>
 
     <PreferenceScreen android:title="@string/pref_category_search"
+                      android:summary="@string/pref_category_search_summary"
                       android:fragment="org.mozilla.gecko.preferences.GeckoPreferenceFragment" >
         <extra android:name="resource"
                android:value="preferences_search"/>
     </PreferenceScreen>
 
     <ListPreference android:key="android.not_a_preference.restoreSession3"
                     android:title="@string/pref_restore"
                     android:defaultValue="quit"
--- a/mobile/android/base/resources/xml/preferences_customize.xml
+++ b/mobile/android/base/resources/xml/preferences_customize.xml
@@ -2,27 +2,30 @@
 <!-- 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/. -->
 
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
                   xmlns:gecko="http://schemas.android.com/apk/res-auto"
                   android:enabled="false">
 
-    <PreferenceScreen android:title="@string/pref_category_home" >
+    <PreferenceScreen android:title="@string/pref_category_home"
+                      android:summary="@string/pref_category_home_summary" >
         <intent android:action="android.intent.action.VIEW"
                 android:targetPackage="@string/android_package_name"
                 android:targetClass="org.mozilla.gecko.preferences.GeckoPreferences" >
             <extra
                 android:name="resource"
                 android:value="preferences_home" />
         </intent>
     </PreferenceScreen>
 
-    <PreferenceScreen android:title="@string/pref_category_search" >
+    <PreferenceScreen android:title="@string/pref_category_search"
+                      android:summary="@string/pref_category_search_summary" >
+
         <intent android:action="android.intent.action.VIEW"
                 android:targetPackage="@string/android_package_name"
                 android:targetClass="org.mozilla.gecko.preferences.GeckoPreferences" >
             <extra
                 android:name="resource"
                 android:value="preferences_search" />
         </intent>
     </PreferenceScreen>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -91,32 +91,34 @@
   <string name="media_pause">&media_pause;</string>
   <string name="media_stop">&media_stop;</string>
 
   <string name="settings">&settings;</string>
   <string name="settings_title">&settings_title;</string>
   <string name="pref_category_advanced">&pref_category_advanced;</string>
   <string name="pref_category_customize">&pref_category_customize;</string>
   <string name="pref_category_search">&pref_category_search3;</string>
+  <string name="pref_category_search_summary">&pref_category_search_summary;</string>
   <string name="pref_category_display">&pref_category_display;</string>
   <string name="pref_category_privacy_short">&pref_category_privacy_short;</string>
   <string name="pref_category_vendor">&pref_category_vendor;</string>
   <string name="pref_category_datareporting">&pref_category_datareporting;</string>
   <string name="pref_category_installed_search_engines">&pref_category_installed_search_engines;</string>
   <string name="pref_category_add_search_providers">&pref_category_add_search_providers;</string>
   <string name="pref_category_search_restore_defaults">&pref_category_search_restore_defaults;</string>
   <string name="pref_search_restore_defaults">&pref_search_restore_defaults;</string>
   <string name="pref_search_restore_defaults_summary">&pref_search_restore_defaults_summary;</string>
   <string name="pref_search_hint">&pref_search_hint;</string>
 
   <string name="pref_category_devtools">&pref_category_devtools;</string>
   <string name="pref_developer_remotedebugging">&pref_developer_remotedebugging;</string>
   <string name="pref_developer_remotedebugging_docs">&pref_developer_remotedebugging_docs;</string>
 
   <string name="pref_category_home">&pref_category_home;</string>
+  <string name="pref_category_home_summary">&pref_category_home_summary;</string>
   <string name="pref_category_home_panels">&pref_category_home_panels;</string>
   <string name="pref_home_add_panel">&pref_home_add_panel;</string>
   <string name="home_add_panel_title">&home_add_panel_title;</string>
   <string name="home_add_panel_empty">&home_add_panel_empty;</string>
   <string name="home_add_panel_installed">&home_add_panel_installed;</string>
   <string name="pref_category_home_content_settings">&pref_category_home_content_settings;</string>
   <string name="pref_home_updates">&pref_home_updates;</string>
   <string name="pref_home_updates_enabled">&pref_home_updates_enabled;</string>
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -4159,17 +4159,16 @@ pref("memory.system_memory_reporter", fa
 #endif
 
 // Don't dump memory reports on OOM, by default.
 pref("memory.dump_reports_on_oom", false);
 
 // Number of stack frames to capture in createObjectURL for about:memory.
 pref("memory.blob_report.stack_frames", 0);
 
-pref("social.enabled", false);
 // comma separated list of domain origins (e.g. https://domain.com) for
 // providers that can install from their own website without user warnings.
 // entries are
 pref("social.whitelist", "https://mozsocial.cliqz.com,https://now.msn.com,https://mixi.jp");
 // comma separated list of domain origins (e.g. https://domain.com) for
 // directory websites (e.g. AMO) that can install providers for other sites
 pref("social.directories", "https://activations.mozilla.org");
 // remote-install allows any website to activate a provider, with extended UI
--- a/services/fxaccounts/FxAccounts.jsm
+++ b/services/fxaccounts/FxAccounts.jsm
@@ -21,17 +21,17 @@ Cu.import("resource://gre/modules/FxAcco
 
 XPCOMUtils.defineLazyModuleGetter(this, "jwcrypto",
   "resource://gre/modules/identity/jwcrypto.jsm");
 
 // All properties exposed by the public FxAccounts API.
 let publicProperties = [
   "getAccountsClient",
   "getAccountsSignInURI",
-  "getAccountsURI",
+  "getAccountsSignUpURI",
   "getAssertion",
   "getKeys",
   "getSignedInUser",
   "loadAndPoll",
   "localtimeOffsetMsec",
   "now",
   "promiseAccountsForceSigninURI",
   "resendVerificationEmail",
@@ -668,18 +668,18 @@ FxAccountsInternal.prototype = {
               delete currentState.whenVerifiedDeferred;
             }
           }
         }
       });
     },
 
   // Return the URI of the remote UI flows.
-  getAccountsURI: function() {
-    let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.uri");
+  getAccountsSignUpURI: function() {
+    let url = Services.urlFormatter.formatURLPref("identity.fxaccounts.remote.signup.uri");
     if (!/^https:/.test(url)) { // Comment to un-break emacs js-mode highlighting
       throw new Error("Firefox Accounts server must use HTTPS");
     }
     return url;
   },
 
   // Return the URI of the remote UI flows.
   getAccountsSignInURI: function() {
--- a/services/fxaccounts/tests/xpcshell/test_accounts.js
+++ b/services/fxaccounts/tests/xpcshell/test_accounts.js
@@ -117,23 +117,23 @@ function MockFxAccounts() {
       return this._d_signCertificate.promise;
     },
     fxAccountsClient: new MockFxAccountsClient()
   });
 }
 
 add_test(function test_non_https_remote_server_uri() {
   Services.prefs.setCharPref(
-    "identity.fxaccounts.remote.uri",
+    "identity.fxaccounts.remote.signup.uri",
     "http://example.com/browser/browser/base/content/test/general/accounts_testRemoteCommands.html");
   do_check_throws_message(function () {
-    fxAccounts.getAccountsURI();
+    fxAccounts.getAccountsSignUpURI();
   }, "Firefox Accounts server must use HTTPS");
 
-  Services.prefs.clearUserPref("identity.fxaccounts.remote.uri");
+  Services.prefs.clearUserPref("identity.fxaccounts.remote.signup.uri");
 
   run_next_test();
 });
 
 add_task(function test_get_signed_in_user_initially_unset() {
   // This test, unlike the rest, uses an un-mocked FxAccounts instance.
   // However, we still need to pass an object to the constructor to
   // force it to expose "internal", so we can test the disk storage.
--- a/services/sync/modules/browserid_identity.js
+++ b/services/sync/modules/browserid_identity.js
@@ -29,17 +29,16 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "BulkKeyBundle",
                                   "resource://services-sync/keys.js");
 
 XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
                                   "resource://gre/modules/FxAccounts.jsm");
 
 XPCOMUtils.defineLazyGetter(this, 'log', function() {
   let log = Log.repository.getLogger("Sync.BrowserIDManager");
-  log.addAppender(new Log.DumpAppender());
   log.level = Log.Level[Svc.Prefs.get("log.logger.identity")] || Log.Level.Error;
   return log;
 });
 
 // FxAccountsCommon.js doesn't use a "namespace", so create one here.
 let fxAccountsCommon = {};
 Cu.import("resource://gre/modules/FxAccountsCommon.js", fxAccountsCommon);
 
--- a/services/sync/modules/util.js
+++ b/services/sync/modules/util.js
@@ -615,17 +615,18 @@ this.Utils = {
       return this._syncCredentialsHosts;
     }
     let result = new Set();
     // the legacy sync host.
     result.add(PWDMGR_HOST);
     // The FxA hosts - these almost certainly all have the same hostname, but
     // better safe than sorry...
     for (let prefName of ["identity.fxaccounts.remote.force_auth.uri",
-                          "identity.fxaccounts.remote.uri",
+                          "identity.fxaccounts.remote.signup.uri",
+                          "identity.fxaccounts.remote.signin.uri",
                           "identity.fxaccounts.settings.uri"]) {
       let prefVal;
       try {
         prefVal = Services.prefs.getCharPref(prefName);
       } catch (_) {
         continue;
       }
       let uri = Services.io.newURI(prefVal, null, null);
--- a/services/sync/services-sync.js
+++ b/services/sync/services-sync.js
@@ -64,15 +64,16 @@ pref("services.sync.log.logger.engine.bo
 pref("services.sync.log.logger.engine.clients", "Debug");
 pref("services.sync.log.logger.engine.forms", "Debug");
 pref("services.sync.log.logger.engine.history", "Debug");
 pref("services.sync.log.logger.engine.passwords", "Debug");
 pref("services.sync.log.logger.engine.prefs", "Debug");
 pref("services.sync.log.logger.engine.tabs", "Debug");
 pref("services.sync.log.logger.engine.addons", "Debug");
 pref("services.sync.log.logger.engine.apps", "Debug");
+pref("services.sync.log.logger.identity", "Debug");
 pref("services.sync.log.logger.userapi", "Debug");
 pref("services.sync.log.cryptoDebug", false);
 
 pref("services.sync.tokenServerURI", "https://token.services.mozilla.com/1.0/sync/1.5");
 
 pref("services.sync.fxa.termsURL", "https://accounts.firefox.com/legal/terms");
 pref("services.sync.fxa.privacyURL", "https://accounts.firefox.com/legal/privacy");
--- a/toolkit/components/social/SocialService.jsm
+++ b/toolkit/components/social/SocialService.jsm
@@ -29,22 +29,17 @@ XPCOMUtils.defineLazyServiceGetter(this,
 /**
  * The SocialService is the public API to social providers - it tracks which
  * providers are installed and enabled, and is the entry-point for access to
  * the provider itself.
  */
 
 // Internal helper methods and state
 let SocialServiceInternal = {
-  _enabled: Services.prefs.getBoolPref("social.enabled"),
-  get enabled() this._enabled,
-  set enabled(val) {
-    this._enabled = !!val;
-    Services.prefs.setBoolPref("social.enabled", !!val);
-  },
+  get enabled() this.providerArray.length > 0,
 
   get providerArray() {
     return [p for ([, p] of Iterator(this.providers))];
   },
   get manifests() {
     // Retrieve the manifests of installed providers from prefs
     let MANIFEST_PREFS = Services.prefs.getBranch("social.manifest.");
     let prefs = MANIFEST_PREFS.getChildList("", []);
@@ -137,16 +132,18 @@ let SocialServiceInternal = {
 };
 
 XPCOMUtils.defineLazyGetter(SocialServiceInternal, "providers", function () {
   initService();
   let providers = {};
   for (let manifest of this.manifests) {
     try {
       if (ActiveProviders.has(manifest.origin)) {
+        // enable the api when a provider is enabled
+        MozSocialAPI.enabled = true;
         let provider = new SocialProvider(manifest);
         providers[provider.origin] = provider;
       }
     } catch (err) {
       Cu.reportError("SocialService: failed to load provider: " + manifest.origin +
                      ", exception: " + err);
     }
   }
@@ -210,22 +207,25 @@ let ActiveProviders = {
                  createInstance(Ci.nsISupportsString);
     string.data = JSON.stringify(this._providers);
     Services.prefs.setComplexValue("social.activeProviders",
                                    Ci.nsISupportsString, string);
   }
 };
 
 function migrateSettings() {
-  let activeProviders;
+  let activeProviders, enabled;
   try {
     activeProviders = Services.prefs.getCharPref("social.activeProviders");
   } catch(e) {
     // not set, we'll check if we need to migrate older prefs
   }
+  if (Services.prefs.prefHasUserValue("social.enabled")) {
+    enabled = Services.prefs.getBoolPref("social.enabled");
+  }
   if (activeProviders) {
     // migration from fx21 to fx22 or later
     // ensure any *builtin* provider in activeproviders is in user level prefs
     for (let origin in ActiveProviders._providers) {
       let prefname;
       let manifest;
       let defaultManifest;
       try {
@@ -268,17 +268,24 @@ function migrateSettings() {
         if (!manifest.installDate)
           manifest.installDate = 0; // we don't know when it was installed
 
         let string = Cc["@mozilla.org/supports-string;1"].
                      createInstance(Ci.nsISupportsString);
         string.data = JSON.stringify(manifest);
         Services.prefs.setComplexValue(prefname, Ci.nsISupportsString, string);
       }
+      // as of fx 29, we no longer rely on social.enabled. migration from prior
+      // versions should disable all service addons if social.enabled=false
+      if (enabled === false) {
+        ActiveProviders.delete(origin);
+      }
     }
+    ActiveProviders.flush();
+    Services.prefs.clearUserPref("social.enabled");
     return;
   }
 
   // primary migration from pre-fx21
   let active;
   try {
     active = Services.prefs.getBoolPref("social.active");
   } catch(e) {}
@@ -333,19 +340,16 @@ function initService() {
   try {
     migrateSettings();
   } catch(e) {
     // no matter what, if migration fails we do not want to render social
     // unusable. Worst case scenario is that, when upgrading Firefox, previously
     // enabled providers are not migrated.
     Cu.reportError("Error migrating social settings: " + e);
   }
-  // Initialize the MozSocialAPI
-  if (SocialServiceInternal.enabled)
-    MozSocialAPI.enabled = true;
 }
 
 function schedule(callback) {
   Services.tm.mainThread.dispatch(callback, Ci.nsIThread.DISPATCH_NORMAL);
 }
 
 // Public API
 this.SocialService = {
@@ -358,31 +362,17 @@ this.SocialService = {
       return true;
     };
     return false;
   },
   get enabled() {
     return SocialServiceInternal.enabled;
   },
   set enabled(val) {
-    let enable = !!val;
-
-    // Allow setting to the same value when in safe mode so the
-    // feature can be force enabled.
-    if (enable == SocialServiceInternal.enabled &&
-        !Services.appinfo.inSafeMode)
-      return;
-
-    // if disabling, ensure all providers are actually disabled
-    if (!enable)
-      SocialServiceInternal.providerArray.forEach(function (p) p.enabled = false);
-
-    SocialServiceInternal.enabled = enable;
-    MozSocialAPI.enabled = enable;
-    Services.telemetry.getHistogramById("SOCIAL_TOGGLED").add(enable);
+    throw new Error("not allowed to set SocialService.enabled");
   },
 
   // Adds and activates a builtin provider. The provider may or may not have
   // previously been added.  onDone is always called - with null if no such
   // provider exists, or the activated provider on success.
   addBuiltinProvider: function addBuiltinProvider(origin, onDone) {
     if (SocialServiceInternal.providers[origin]) {
       schedule(function() {
@@ -405,16 +395,18 @@ this.SocialService = {
     });
   },
 
   // Adds a provider given a manifest, and returns the added provider.
   addProvider: function addProvider(manifest, onDone) {
     if (SocialServiceInternal.providers[manifest.origin])
       throw new Error("SocialService.addProvider: provider with this origin already exists");
 
+    // enable the api when a provider is enabled
+    MozSocialAPI.enabled = true;
     let provider = new SocialProvider(manifest);
     SocialServiceInternal.providers[provider.origin] = provider;
     ActiveProviders.add(provider.origin);
 
     this.getOrderedProviderList(function (providers) {
       this._notifyProviderListeners("provider-enabled", provider.origin, providers);
       if (onDone)
         onDone(provider);
@@ -434,16 +426,18 @@ this.SocialService = {
       AddonManagerPrivate.callAddonListeners("onDisabling", addon, false);
       addon.pendingOperations |= AddonManager.PENDING_DISABLE;
     }
     provider.enabled = false;
 
     ActiveProviders.delete(provider.origin);
 
     delete SocialServiceInternal.providers[origin];
+    // disable the api if we have no enabled providers
+    MozSocialAPI.enabled = SocialServiceInternal.enabled;
 
     if (addon) {
       // we have to do this now so the addon manager ui will update an uninstall
       // correctly.
       addon.pendingOperations -= AddonManager.PENDING_DISABLE;
       AddonManagerPrivate.callAddonListeners("onDisabled", addon);
       AddonManagerPrivate.notifyAddonChanged(addon.id, ADDON_TYPE_SERVICE, false);
     }
--- a/toolkit/components/social/test/browser/browser_SocialProvider.js
+++ b/toolkit/components/social/test/browser/browser_SocialProvider.js
@@ -8,18 +8,16 @@ function test() {
   waitForExplicitFinish();
 
   let manifest = {
     origin: 'http://example.com',
     name: "Example Provider",
     workerURL: "http://example.com/browser/toolkit/components/social/test/browser/worker_social.js"
   };
 
-  ensureSocialEnabled();
-
   SocialService.addProvider(manifest, function (p) {
     provider = p;
     runTests(tests, undefined, undefined, function () {
       SocialService.removeProvider(p.origin, function() {
         ok(!provider.enabled, "removing an enabled provider should have disabled the provider");
         let port = provider.getWorkerPort();
         ok(!port, "should not be able to get a port after removing the provider");
         provider = null;
--- a/toolkit/components/social/test/browser/browser_notifications.js
+++ b/toolkit/components/social/test/browser/browser_notifications.js
@@ -9,19 +9,17 @@ Cu.import("resource://gre/modules/Servic
 function ensureProvider(workerFunction, cb) {
   let manifest = {
     origin: TEST_PROVIDER_ORIGIN,
     name: "Example Provider",
     workerURL: "http://example.com/browser/toolkit/components/social/test/browser/echo.sjs?"
                + encodeURI("let run=" + workerFunction.toSource()) + ";run();"
   };
 
-  ensureSocialEnabled();
   SocialService.addProvider(manifest, function (p) {
-    p.enabled = true;
     cb(p);
   });
 }
 
 function test() {
   waitForExplicitFinish();
 
   let cbPostTest = function(cb) {
--- a/toolkit/components/social/test/browser/browser_workerAPI.js
+++ b/toolkit/components/social/test/browser/browser_workerAPI.js
@@ -11,19 +11,19 @@ function test() {
   registerCleanupFunction(restoreAlertsService);
 
   let manifest = {
     origin: 'http://example.com',
     name: "Example Provider",
     workerURL: "http://example.com/browser/toolkit/components/social/test/browser/worker_social.js"
   };
 
-  ensureSocialEnabled();
-
   SocialService.addProvider(manifest, function (p) {
+    // toolkit does not enable providers by default, that is handled in the
+    // browser, we need to enable it in our tests.
     p.enabled = true;
     provider = p;
     runTests(tests, undefined, undefined, function () {
       SocialService.removeProvider(provider.origin, finish);
     });
   });
 }
 
--- a/toolkit/components/social/test/browser/head.js
+++ b/toolkit/components/social/test/browser/head.js
@@ -1,22 +1,14 @@
 /* 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/. */
 
 let SocialService = Components.utils.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
 
-function ensureSocialEnabled() {
-  let initiallyEnabled = SocialService.enabled;
-  SocialService.enabled = true;
-  registerCleanupFunction(function () {
-    SocialService.enabled = initiallyEnabled;
-  });
-}
-
 // A helper to run a suite of tests.
 // The "test object" should be an object with function names as keys and a
 // function as the value.  The functions will be called with a "cbnext" param
 // which should be called when the test is complete.
 // eg:
 // test = {
 //   foo: function(cbnext) {... cbnext();}
 // }
--- a/toolkit/components/social/test/xpcshell/test_SocialService.js
+++ b/toolkit/components/social/test/xpcshell/test_SocialService.js
@@ -18,46 +18,49 @@ function run_test() {
     },
     { // provider without workerURL
       name: "provider 2",
       origin: "https://example2.com",
       sidebarURL: "https://example2.com/sidebar/",
     }
   ];
 
-  manifests.forEach(function (manifest) {
-    MANIFEST_PREFS.setCharPref(manifest.origin, JSON.stringify(manifest));
-  });
-  // Set both providers active and flag the first one as "current"
-  let activeVal = Cc["@mozilla.org/supports-string;1"].
-             createInstance(Ci.nsISupportsString);
-  let active = {};
-  for (let m of manifests)
-    active[m.origin] = 1;
-  activeVal.data = JSON.stringify(active);
-  Services.prefs.setComplexValue("social.activeProviders",
-                                 Ci.nsISupportsString, activeVal);
-  Services.prefs.setCharPref("social.provider.current", manifests[0].origin);
-
-  // Enable the service for this test
-  Services.prefs.setBoolPref("social.enabled", true);
   Cu.import("resource://gre/modules/SocialService.jsm");
+  Cu.import("resource://gre/modules/MozSocialAPI.jsm");
 
   let runner = new AsyncRunner();
   let next = runner.next.bind(runner);
+  runner.appendIterator(testAddProviders(manifests, next));
   runner.appendIterator(testGetProvider(manifests, next));
   runner.appendIterator(testGetProviderList(manifests, next));
-  runner.appendIterator(testEnabled(manifests, next));
   runner.appendIterator(testAddRemoveProvider(manifests, next));
   runner.appendIterator(testIsSameOrigin(manifests, next));
   runner.appendIterator(testResolveUri  (manifests, next));
   runner.appendIterator(testOrderedProviders(manifests, next));
+  runner.appendIterator(testRemoveProviders(manifests, next));
   runner.next();
 }
 
+function testAddProviders(manifests, next) {
+  do_check_false(SocialService.enabled);
+  let provider = yield SocialService.addProvider(manifests[0], next);
+  do_check_true(SocialService.enabled);
+  do_check_true(MozSocialAPI._enabled);
+  do_check_false(provider.enabled);
+  provider = yield SocialService.addProvider(manifests[1], next);
+  do_check_false(provider.enabled);
+}
+
+function testRemoveProviders(manifests, next) {
+  do_check_true(SocialService.enabled);
+  yield SocialService.removeProvider(manifests[0].origin, next);
+  yield SocialService.removeProvider(manifests[1].origin, next);
+  do_check_false(SocialService.enabled);
+}
+
 function testGetProvider(manifests, next) {
   for (let i = 0; i < manifests.length; i++) {
     let manifest = manifests[i];
     let provider = yield SocialService.getProvider(manifest.origin, next);
     do_check_neq(provider, null);
     do_check_eq(provider.name, manifest.name);
     do_check_eq(provider.workerURL, manifest.workerURL);
     do_check_eq(provider.origin, manifest.origin);
@@ -73,56 +76,16 @@ function testGetProviderList(manifests, 
     let provider = providers[providerIdx];
     do_check_true(!!provider);
     do_check_false(provider.enabled);
     do_check_eq(provider.workerURL, manifests[i].workerURL);
     do_check_eq(provider.name, manifests[i].name);
   }
 }
 
-
-function testEnabled(manifests, next) {
-  // Check that providers are disabled by default
-  let providers = yield SocialService.getProviderList(next);
-  do_check_true(providers.length >= manifests.length);
-  do_check_true(SocialService.enabled, "social enabled at test start");
-  providers.forEach(function (provider) {
-    do_check_false(provider.enabled);
-  });
-
-  do_test_pending();
-  function waitForEnableObserver(cb) {
-    Services.prefs.addObserver("social.enabled", function prefObserver(subj, topic, data) {
-      Services.prefs.removeObserver("social.enabled", prefObserver);
-      cb();
-    }, false);
-  }
-
-  // enable one of the added providers
-  providers[providers.length-1].enabled = true;
-
-  // now disable the service and check that it disabled that provider (and all others for good measure)
-  waitForEnableObserver(function() {
-    do_check_true(!SocialService.enabled);
-    providers.forEach(function (provider) {
-      do_check_false(provider.enabled);
-    });
-    waitForEnableObserver(function() {
-      do_check_true(SocialService.enabled);
-      // Enabling the service should not enable providers
-      providers.forEach(function (provider) {
-        do_check_false(provider.enabled);
-      });
-      do_test_finished();
-    });
-    SocialService.enabled = true;
-  });
-  SocialService.enabled = false;
-}
-
 function testAddRemoveProvider(manifests, next) {
   var threw;
   try {
     // Adding a provider whose origin already exists should fail
     SocialService.addProvider(manifests[0]);
   } catch (ex) {
     threw = ex;
   }
--- a/toolkit/components/social/test/xpcshell/test_SocialServiceMigration21.js
+++ b/toolkit/components/social/test/xpcshell/test_SocialServiceMigration21.js
@@ -17,18 +17,16 @@ function run_test() {
     name: "provider 1",
     origin: "https://example1.com",
     builtin: true // as of fx22 this should be true for default prefs
   };
 
   DEFAULT_PREFS.setCharPref(manifest.origin, JSON.stringify(manifest));
   Services.prefs.setBoolPref("social.active", true);
 
-  // Enable the service for this test
-  Services.prefs.setBoolPref("social.enabled", true);
   Cu.import("resource://gre/modules/SocialService.jsm");
 
   let runner = new AsyncRunner();
   let next = runner.next.bind(runner);
   runner.appendIterator(testMigration(manifest, next));
   runner.next();
 }
 
--- a/toolkit/components/social/test/xpcshell/test_SocialServiceMigration22.js
+++ b/toolkit/components/social/test/xpcshell/test_SocialServiceMigration22.js
@@ -26,20 +26,17 @@ function run_test() {
              createInstance(Ci.nsISupportsString);
   let active = {};
   active[manifest.origin] = 1;
   // bad.origin tests that a missing manifest does not break migration, bug 859715
   active["bad.origin"] = 1;
   activeVal.data = JSON.stringify(active);
   Services.prefs.setComplexValue("social.activeProviders",
                                  Ci.nsISupportsString, activeVal);
-  Services.prefs.setCharPref("social.provider.current", manifest.origin);
 
-  // Enable the service for this test
-  Services.prefs.setBoolPref("social.enabled", true);
   Cu.import("resource://gre/modules/SocialService.jsm");
 
   let runner = new AsyncRunner();
   let next = runner.next.bind(runner);
   runner.appendIterator(testMigration(manifest, next));
   runner.next();
 }
 
new file mode 100644
--- /dev/null
+++ b/toolkit/components/social/test/xpcshell/test_SocialServiceMigration29.js
@@ -0,0 +1,61 @@
+/* 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/. */
+
+Cu.import("resource://gre/modules/Services.jsm");
+
+
+function run_test() {
+  // Test must run at startup for migration to occur, so we can only test
+  // one migration per test file
+  initApp();
+
+  // NOTE: none of the manifests here can have a workerURL set, or we attempt
+  // to create a FrameWorker and that fails under xpcshell...
+  let manifest = { // normal provider
+    name: "provider 1",
+    origin: "https://example1.com",
+  };
+
+  MANIFEST_PREFS.setCharPref(manifest.origin, JSON.stringify(manifest));
+
+  // Set both providers active and flag the first one as "current"
+  let activeVal = Cc["@mozilla.org/supports-string;1"].
+             createInstance(Ci.nsISupportsString);
+  let active = {};
+  active[manifest.origin] = 1;
+  activeVal.data = JSON.stringify(active);
+  Services.prefs.setComplexValue("social.activeProviders",
+                                 Ci.nsISupportsString, activeVal);
+
+  // social.enabled pref is the key focus of this test. We set the user pref,
+  // and then migration should a) remove the provider from activeProviders and
+  // b) unset social.enabled
+  Services.prefs.setBoolPref("social.enabled", false);
+
+  Cu.import("resource://gre/modules/SocialService.jsm");
+
+  let runner = new AsyncRunner();
+  let next = runner.next.bind(runner);
+  runner.appendIterator(testMigration(manifest, next));
+  runner.next();
+}
+
+function testMigration(manifest, next) {
+  // look at social.activeProviders, we should have migrated into that, and
+  // we should be set as a user level pref after migration
+  do_check_true(Services.prefs.prefHasUserValue("social.enabled"));
+  do_check_true(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
+  // we need to access the providers for everything to initialize
+  yield SocialService.getProviderList(next);
+  do_check_false(SocialService.enabled);
+  do_check_false(Services.prefs.prefHasUserValue("social.enabled"));
+  do_check_true(Services.prefs.prefHasUserValue("social.activeProviders"));
+
+  let activeProviders;
+  let pref = Services.prefs.getComplexValue("social.activeProviders",
+                                            Ci.nsISupportsString).data;
+  activeProviders = JSON.parse(pref);
+  do_check_true(activeProviders[manifest.origin] == undefined);
+  do_check_true(MANIFEST_PREFS.prefHasUserValue(manifest.origin));
+}
--- a/toolkit/components/social/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/social/test/xpcshell/xpcshell.ini
@@ -3,8 +3,10 @@ head = head.js
 tail =
 support-files = blocklist.xml
 
 [test_SocialService.js]
 
 [test_SocialServiceMigration21.js]
 
 [test_SocialServiceMigration22.js]
+
+[test_SocialServiceMigration29.js]
--- a/toolkit/devtools/server/actors/root.js
+++ b/toolkit/devtools/server/actors/root.js
@@ -180,16 +180,20 @@ RootActor.prototype = {
         // Wether the server-side highlighter actor exists and can be used to
         // remotely highlight nodes (see server/actors/highlighter.js)
         highlightable: true,
         // Wether the inspector actor implements the getImageDataFromURL
         // method that returns data-uris for image URLs. This is used for image
         // tooltips for instance
         urlToImageDataResolver: true,
         networkMonitor: true,
+        // Wether the storage inspector actor to inspect cookies, etc.
+        storageInspector: true,
+        // Wether storage inspector is read only
+        storageInspectorReadOnly: true,
       }
     };
   },
 
   /**
    * This is true for the root actor only, used by some child actors
    */
   get isRootActor() true,
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/actors/storage.js
@@ -0,0 +1,1085 @@
+/* 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 {Cu, Cc, Ci} = require("chrome");
+const events = require("sdk/event/core");
+const protocol = require("devtools/server/protocol");
+const {Arg, Option, method, RetVal, types} = protocol;
+const {LongStringActor, ShortLongString} = require("devtools/server/actors/string");
+Cu.import("resource://gre/modules/devtools/LayoutHelpers.jsm");
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+exports.register = function(handle) {
+  handle.addTabActor(StorageActor, "storageActor");
+};
+
+exports.unregister = function(handle) {
+  handle.removeTabActor(StorageActor);
+};
+
+// Maximum number of cookies/local storage key-value-pairs that can be sent
+// over the wire to the client in one request.
+const MAX_STORE_OBJECT_COUNT = 30;
+// Interval for the batch job that sends the accumilated update packets to the
+// client.
+const UPDATE_INTERVAL = 500; // ms
+
+// Holder for all the registered storage actors.
+let storageTypePool = new Map();
+
+/**
+ * Gets an accumulated list of all storage actors registered to be used to
+ * create a RetVal to define the return type of StorageActor.listStores method.
+ */
+function getRegisteredTypes() {
+  let registeredTypes = {};
+  for (let store of storageTypePool.keys()) {
+    registeredTypes[store] = store;
+  }
+  return registeredTypes;
+}
+
+// Cookies store object
+types.addDictType("cookieobject", {
+  name: "string",
+  value: "longstring",
+  path: "nullable:string",
+  host: "string",
+  isDomain: "boolean",
+  isSecure: "boolean",
+  isHttpOnly: "boolean",
+  creationTime: "number",
+  lastAccessed: "number",
+  expires: "number"
+});
+
+// Array of cookie store objects
+types.addDictType("cookiestoreobject", {
+  total: "number",
+  offset: "number",
+  data: "array:nullable:cookieobject"
+});
+
+// Local Storage / Session Storage store object
+types.addDictType("storageobject", {
+  name: "string",
+  value: "longstring"
+});
+
+// Array of Local Storage / Session Storage store objects
+types.addDictType("storagestoreobject", {
+  total: "number",
+  offset: "number",
+  data: "array:nullable:storageobject"
+});
+
+// Update notification object
+types.addDictType("storeUpdateObject", {
+  changed: "nullable:json",
+  deleted: "nullable:json",
+  added: "nullable:json"
+});
+
+// Helper methods to create a storage actor.
+let StorageActors = {};
+
+/**
+ * Creates a default object with the common methods required by all storage
+ * actors.
+ *
+ * This default object is missing a couple of required methods that should be
+ * implemented seperately for each actor. They are namely:
+ *   - observe : Method which gets triggered on the notificaiton of the watched
+ *               topic.
+ *   - getNamesForHost : Given a host, get list of all known store names.
+ *   - getValuesForHost : Given a host (and optianally a name) get all known
+ *                        store objects.
+ *   - toStoreObject : Given a store object, convert it to the required format
+ *                     so that it can be transferred over wire.
+ *   - populateStoresForHost : Given a host, populate the map of all store
+ *                             objects for it
+ *
+ * @param {string} typeName
+ *        The typeName of the actor.
+ * @param {string} observationTopic
+ *        The topic which this actor listens to via Notification Observers.
+ * @param {string} storeObjectType
+ *        The RetVal type of the store object of this actor.
+ */
+StorageActors.defaults = function(typeName, observationTopic, storeObjectType) {
+  return {
+    typeName: typeName,
+
+    get conn() {
+      return this.storageActor.conn;
+    },
+
+    /**
+     * Returns a list of currently knwon hosts for the target window. This list
+     * contains unique hosts from the window + all inner windows.
+     */
+    get hosts() {
+      let hosts = new Set();
+      for (let {location} of this.storageActor.windows) {
+        hosts.add(this.getHostName(location));
+      }
+      return hosts;
+    },
+
+    /**
+     * Returns all the windows present on the page. Includes main window + inner
+     * iframe windows.
+     */
+    get windows() {
+      return this.storageActor.windows;
+    },
+
+    /**
+     * Converts the window.location object into host.
+     */
+    getHostName: function(location) {
+      return location.hostname || location.href;
+    },
+
+    initialize: function(storageActor) {
+      protocol.Actor.prototype.initialize.call(this, null);
+
+      this.storageActor = storageActor;
+
+      this.populateStoresForHosts();
+      Services.obs.addObserver(this, observationTopic, false);
+      this.onWindowReady = this.onWindowReady.bind(this);
+      this.onWindowDestroyed = this.onWindowDestroyed.bind(this);
+      events.on(this.storageActor, "window-ready", this.onWindowReady);
+      events.on(this.storageActor, "window-destroyed", this.onWindowDestroyed);
+    },
+
+    destroy: function() {
+      this.hostVsStores = null;
+      Services.obs.removeObserver(this, observationTopic, false);
+      events.off(this.storageActor, "window-ready", this.onWindowReady);
+      events.off(this.storageActor, "window-destroyed", this.onWindowDestroyed);
+    },
+
+    /**
+     * When a new window is added to the page. This generally means that a new
+     * iframe is created, or the current window is completely reloaded.
+     *
+     * @param {window} window
+     *        The window which was added.
+     */
+    onWindowReady: function(window) {
+      let host = this.getHostName(window.location);
+      if (!this.hostVsStores.has(host)) {
+        this.populateStoresForHost(host, window);
+        let data = {};
+        data[host] = this.getNamesForHost(host);
+        this.storageActor.update("added", typeName, data);
+      }
+    },
+
+    /**
+     * When a window is removed from the page. This generally means that an
+     * iframe was removed, or the current window reload is triggered.
+     *
+     * @param {window} window
+     *        The window which was removed.
+     */
+    onWindowDestroyed: function(window) {
+      let host = this.getHostName(window.location);
+      if (!this.hosts.has(host)) {
+        this.hostVsStores.delete(host);
+        let data = {};
+        data[host] = [];
+        this.storageActor.update("deleted", typeName, data);
+      }
+    },
+
+    form: function(form, detail) {
+      if (detail === "actorid") {
+        return this.actorID;
+      }
+
+      let hosts = {};
+      for (let host of this.hosts) {
+        hosts[host] = [];
+      }
+
+      return {
+        actor: this.actorID,
+        hosts: hosts
+      };
+    },
+
+    /**
+     * Populates a map of known hosts vs a map of stores vs value.
+     */
+    populateStoresForHosts: function() {
+      this.hostVsStores = new Map();
+      for (let host of this.hosts) {
+        this.populateStoresForHost(host);
+      }
+    },
+
+    /**
+     * Returns a list of requested store objects. Maximum values returned are
+     * MAX_STORE_OBJECT_COUNT. This method returns paginated values whose
+     * starting index and total size can be controlled via the options object
+     *
+     * @param {string} host
+     *        The host name for which the store values are required.
+     * @param {array:string} names
+     *        Array containing the names of required store objects. Empty if all
+     *        items are required.
+     * @param {object} options
+     *        Additional options for the request containing following properties:
+     *         - offset {number} : The begin index of the returned array amongst
+     *                  the total values
+     *         - size {number} : The number of values required.
+     *         - sortOn {string} : The values should be sorted on this property.
+     *
+     * @return {object} An object containing following properties:
+     *          - offset - The actual offset of the returned array. This might
+     *                     be different from the requested offset if that was
+     *                     invalid
+     *          - size - The actual size of the returned array.
+     *          - data - The requested values.
+     */
+    getStoreObjects: method(function(host, names, options = {}) {
+      let offset = options.offset || 0;
+      let size = options.offset || MAX_STORE_OBJECT_COUNT;
+      let sortOn = options.sortOn || "name";
+
+      let toReturn = {
+        offset: offset,
+        total: -1,
+        data: []
+      };
+
+      if (names) {
+        for (let name of names) {
+          toReturn.data.push(
+            this.toStoreObject(this.getValuesForHost(host, name))
+          );
+        }
+      }
+      else {
+        let total = this.getValuesForHost(host);
+        toReturn.total = total.length;
+        if (offset > toReturn.total) {
+          toReturn.offset = offset = 0;
+        }
+
+        toReturn.data = total.sort((a,b) => {
+          return a[sortOn] - b[sortOn];
+        }).slice(offset, size).map(object => this.toStoreObject(object));
+      }
+
+      return toReturn;
+    }, {
+      request: {
+        host: Arg(0),
+        names: Arg(1, "nullable:array:string"),
+        options: Arg(2, "nullable:json")
+      },
+      response: RetVal(storeObjectType)
+    })
+  }
+};
+
+/**
+ * Creates an actor and its corresponding front and registers it to the Storage
+ * Actor.
+ *
+ * @See StorageActors.defaults()
+ *
+ * @param {object} options
+ *        Options required by StorageActors.defaults method which are :
+ *         - typeName {string}
+ *                    The typeName of the actor.
+ *         - observationTopic {string}
+ *                            The topic which this actor listens to via
+ *                            Notification Observers.
+ *         - storeObjectType {string}
+ *                           The RetVal type of the store object of this actor.
+ * @param {object} overrides
+ *        All the methods which you want to be differnt from the ones in
+ *        StorageActors.defaults method plus the required ones described there.
+ */
+StorageActors.createActor = function(options = {}, overrides = {}) {
+  let actorObject = StorageActors.defaults(
+    options.typeName,
+    options.observationTopic,
+    options.storeObjectType
+  );
+  for (let key in overrides) {
+    actorObject[key] = overrides[key];
+  }
+
+  let actor = protocol.ActorClass(actorObject);
+  let front = protocol.FrontClass(actor, {
+    form: function(form, detail) {
+      if (detail === "actorid") {
+        this.actorID = form;
+        return null;
+      }
+
+      this.actorID = form.actor;
+      this.hosts = form.hosts;
+      return null;
+    }
+  });
+  storageTypePool.set(actorObject.typeName, actor);
+}
+
+/**
+ * The Cookies actor and front.
+ */
+StorageActors.createActor({
+  typeName: "cookies",
+  observationTopic: "cookie-changed",
+  storeObjectType: "cookiestoreobject"
+}, {
+  initialize: function(storageActor) {
+    protocol.Actor.prototype.initialize.call(this, null);
+
+    this.storageActor = storageActor;
+
+    this.populateStoresForHosts();
+    Services.obs.addObserver(this, "cookie-changed", false);
+    Services.obs.addObserver(this, "http-on-response-set-cookie", false);
+    this.onWindowReady = this.onWindowReady.bind(this);
+    this.onWindowDestroyed = this.onWindowDestroyed.bind(this);
+    events.on(this.storageActor, "window-ready", this.onWindowReady);
+    events.on(this.storageActor, "window-destroyed", this.onWindowDestroyed);
+  },
+
+  destroy: function() {
+    this.hostVsStores = null;
+    Services.obs.removeObserver(this, "cookie-changed", false);
+    Services.obs.removeObserver(this, "http-on-response-set-cookie", false);
+    events.off(this.storageActor, "window-ready", this.onWindowReady);
+    events.off(this.storageActor, "window-destroyed", this.onWindowDestroyed);
+  },
+
+  getNamesForHost: function(host) {
+    return [...this.hostVsStores.get(host).keys()];
+  },
+
+  getValuesForHost: function(host, name) {
+    if (name) {
+      return this.hostVsStores.get(host).get(name);
+    }
+    return [...this.hostVsStores.get(host).values()];
+  },
+
+  /**
+   * Given a cookie object, figure out all the matching hosts from the page that
+   * the cookie belong to.
+   */
+  getMatchingHosts: function(cookies) {
+    if (!cookies.length) {
+      cookies = [cookies];
+    }
+    let hosts = new Set();
+    for (let host of this.hosts) {
+      for (let cookie of cookies) {
+        if (this.isCookieAtHost(cookie, host)) {
+          hosts.add(host);
+        }
+      }
+    }
+    return [...hosts];
+  },
+
+  /**
+   * Given a cookie object and a host, figure out if the cookie is valid for
+   * that host.
+   */
+  isCookieAtHost: function(cookie, host) {
+    try {
+      cookie = cookie.QueryInterface(Ci.nsICookie)
+                     .QueryInterface(Ci.nsICookie2);
+    } catch(ex) {
+      return false;
+    }
+    if (cookie.host == null) {
+      return host == null;
+    }
+    if (cookie.host.startsWith(".")) {
+      return host.endsWith(cookie.host);
+    }
+    else {
+      return cookie.host == host;
+    }
+  },
+
+  toStoreObject: function(cookie) {
+    if (!cookie) {
+      return null;
+    }
+
+    return {
+      name: cookie.name,
+      path: cookie.path || "",
+      host: cookie.host || "",
+      expires: (cookie.expires || 0) * 1000, // because expires is in seconds
+      creationTime: cookie.creationTime / 1000, // because it is in micro seconds
+      lastAccessed: cookie.lastAccessed / 1000, // - do -
+      value: new LongStringActor(this.conn, cookie.value || ""),
+      isDomain: cookie.isDomain,
+      isSecure: cookie.isSecure,
+      isHttpOnly: cookie.isHttpOnly
+    }
+  },
+
+  populateStoresForHost: function(host) {
+    this.hostVsStores.set(host, new Map());
+    let cookies = Services.cookies.getCookiesFromHost(host);
+    while (cookies.hasMoreElements()) {
+      let cookie = cookies.getNext().QueryInterface(Ci.nsICookie)
+                          .QueryInterface(Ci.nsICookie2);
+      if (this.isCookieAtHost(cookie, host)) {
+        this.hostVsStores.get(host).set(cookie.name, cookie);
+      }
+    }
+  },
+
+  /**
+   * Converts the raw cookie string returned in http request's response header
+   * to a nsICookie compatible object.
+   *
+   * @param {string} cookieString
+   *        The raw cookie string coming from response header.
+   * @param {string} domain
+   *        The domain of the url of the nsiChannel the cookie corresponds to.
+   *        This will be used when the cookie string does not have a domain.
+   *
+   * @returns {[object]}
+   *          An array of nsICookie like objects representing the cookies.
+   */
+  parseCookieString: function(cookieString, domain) {
+    /**
+     * Takes a date string present in raw cookie string coming from http
+     * request's response headers and returns the number of milliseconds elapsed
+     * since epoch. If the date string is undefined, its probably a session
+     * cookie so return 0.
+     */
+    let parseDateString = dateString => {
+      return dateString ? new Date(dateString.replace(/-/g, " ")).getTime(): 0;
+    };
+
+    let cookies = [];
+    for (let string of cookieString.split("\n")) {
+      let keyVals = {}, name = null;
+      for (let keyVal of string.split(/;\s*/)) {
+        let tokens = keyVal.split(/\s*=\s*/);
+        if (!name) {
+          name = tokens[0];
+        }
+        else {
+          tokens[0] = tokens[0].toLowerCase();
+        }
+        keyVals[tokens.splice(0, 1)[0]] = tokens.join("=");
+      }
+      let expiresTime = parseDateString(keyVals.expires);
+      keyVals.domain = keyVals.domain || domain;
+      cookies.push({
+        name: name,
+        value: keyVals[name] || "",
+        path: keyVals.path,
+        host: keyVals.domain,
+        expires: expiresTime/1000, // seconds, to match with nsiCookie.expires
+        lastAccessed: expiresTime * 1000,
+        // microseconds, to match with nsiCookie.lastAccessed
+        creationTime: expiresTime * 1000,
+        // microseconds, to match with nsiCookie.creationTime
+        isHttpOnly: true,
+        isSecure: keyVals.secure != null,
+        isDomain: keyVals.domain.startsWith("."),
+      });
+    }
+    return cookies;
+  },
+
+  /**
+   * Notification observer for topics "http-on-response-set-cookie" and
+   * "cookie-change".
+   *
+   * @param subject
+   *        {nsiChannel} The channel associated to the SET-COOKIE response
+   *        header in case of "http-on-response-set-cookie" topic.
+   *        {nsiCookie|[nsiCookie]} A single nsiCookie object or a list of it
+   *        depending on the action. Array is only in case of "batch-deleted"
+   *        action.
+   * @param {string} topic
+   *        The topic of the notification.
+   * @param {string} action
+   *        Additional data associated with the notification. Its the type of
+   *        cookie change in case of "cookie-change" topic and the cookie string
+   *        in case of "http-on-response-set-cookie" topic.
+   */
+  observe: function(subject, topic, action) {
+    if (topic == "http-on-response-set-cookie") {
+      // Some cookies got created as a result of http response header SET-COOKIE
+      // subject here is an nsIChannel object referring to the http request.
+      // We get the requestor of this channel and thus the content window.
+      let channel = subject.QueryInterface(Ci.nsIChannel);
+      let requestor = channel.notificationCallbacks ||
+                      channel.loadGroup.notificationCallbacks;
+      // requester can be null sometimes.
+      let window = requestor ? requestor.getInterface(Ci.nsIDOMWindow): null;
+      // Proceed only if this window is present on the currently targetted tab
+      if (window && this.storageActor.isIncludedInTopLevelWindow(window)) {
+        let host = this.getHostName(window.location);
+        if (this.hostVsStores.has(host)) {
+          let cookies = this.parseCookieString(action, channel.URI.host);
+          let data = {};
+          data[host] =  [];
+          for (let cookie of cookies) {
+            if (this.hostVsStores.get(host).has(cookie.name)) {
+              continue;
+            }
+            this.hostVsStores.get(host).set(cookie.name, cookie);
+            data[host].push(cookie.name);
+          }
+          if (data[host]) {
+            this.storageActor.update("added", "cookies", data);
+          }
+        }
+      }
+      return null;
+    }
+
+    if (topic != "cookie-changed") {
+      return null;
+    }
+
+    let hosts = this.getMatchingHosts(subject);
+    let data = {};
+
+    switch(action) {
+      case "added":
+      case "changed":
+        if (hosts.length) {
+          subject = subject.QueryInterface(Ci.nsICookie)
+                           .QueryInterface(Ci.nsICookie2);
+          for (let host of hosts) {
+            this.hostVsStores.get(host).set(subject.name, subject);
+            data[host] = [subject.name];
+          }
+          this.storageActor.update(action, "cookies", data);
+        }
+        break;
+
+      case "deleted":
+        if (hosts.length) {
+          subject = subject.QueryInterface(Ci.nsICookie)
+                           .QueryInterface(Ci.nsICookie2);
+          for (let host of hosts) {
+            this.hostVsStores.get(host).delete(subject.name);
+            data[host] = [subject.name];
+          }
+          this.storageActor.update("deleted", "cookies", data);
+        }
+        break;
+
+      case "batch-deleted":
+        if (hosts.length) {
+          for (let host of hosts) {
+            let stores = [];
+            for (let cookie of subject) {
+              cookie = cookie.QueryInterface(Ci.nsICookie)
+                             .QueryInterface(Ci.nsICookie2);
+              this.hostVsStores.get(host).delete(cookie.name);
+              stores.push(cookie.name);
+            }
+            data[host] = stores;
+          }
+          this.storageActor.update("deleted", "cookies", data);
+        }
+        break;
+
+      case "cleared":
+        this.storageActor.update("cleared", "cookies", hosts);
+        break;
+
+      case "reload":
+        this.storageActor.update("reloaded", "cookies", hosts);
+        break;
+    }
+    return null;
+  },
+});
+
+
+/**
+ * Helper method to create the overriden object required in
+ * StorageActors.createActor for Local Storage and Session Storage.
+ * This method exists as both Local Storage and Session Storage have almost
+ * identical actors.
+ */
+function getObjectForLocalOrSessionStorage(type) {
+  return {
+    getNamesForHost: function(host) {
+      let storage = this.hostVsStores.get(host);
+      return [key for (key in storage)];
+    },
+
+    getValuesForHost: function(host, name) {
+      let storage = this.hostVsStores.get(host);
+      if (name) {
+        return {name: name, value: storage.getItem(name)};
+      }
+      return [{name: name, value: storage.getItem(name)} for (name in storage)];
+    },
+
+    getHostName: function(location) {
+      if (!location.host) {
+        return location.href;
+      }
+      return location.protocol + "//" + location.host;
+    },
+
+    populateStoresForHost: function(host, window) {
+      try {
+        this.hostVsStores.set(host, window[type]);
+      } catch(ex) {
+        // Exceptions happen when local or session storage is inaccessible
+      }
+      return null;
+    },
+
+    populateStoresForHosts: function() {
+      this.hostVsStores = new Map();
+      try {
+        for (let window of this.windows) {
+          this.hostVsStores.set(this.getHostName(window.location), window[type]);
+        }
+      } catch(ex) {
+        // Exceptions happen when local or session storage is inaccessible
+      }
+      return null;
+    },
+
+    observe: function(subject, topic, data) {
+      if (topic != "dom-storage2-changed" || data != type) {
+        return null;
+      }
+
+      let host = this.getSchemaAndHost(subject.url);
+
+      if (!this.hostVsStores.has(host)) {
+        return null;
+      }
+
+      let action = "changed";
+      if (subject.key == null) {
+        return this.storageActor.update("cleared", type, [host]);
+      }
+      else if (subject.oldValue == null) {
+        action = "added";
+      }
+      else if (subject.newValue == null) {
+        action = "deleted";
+      }
+      let updateData = {};
+      updateData[host] = [subject.key];
+      return this.storageActor.update(action, type, updateData);
+    },
+
+    /**
+     * Given a url, correctly determine its protocol + hostname part.
+     */
+    getSchemaAndHost: function(url) {
+      let uri = Services.io.newURI(url, null, null);
+      return uri.scheme + "://" + uri.hostPort;
+    },
+
+    toStoreObject: function(item) {
+      if (!item) {
+        return null;
+      }
+
+      return {
+        name: item.name,
+        value: new LongStringActor(this.conn, item.value || "")
+      };
+    },
+  }
+};
+
+/**
+ * The Local Storage actor and front.
+ */
+StorageActors.createActor({
+  typeName: "localStorage",
+  observationTopic: "dom-storage2-changed",
+  storeObjectType: "storagestoreobject"
+}, getObjectForLocalOrSessionStorage("localStorage"));
+
+/**
+ * The Session Storage actor and front.
+ */
+StorageActors.createActor({
+  typeName: "sessionStorage",
+  observationTopic: "dom-storage2-changed",
+  storeObjectType: "storagestoreobject"
+}, getObjectForLocalOrSessionStorage("sessionStorage"));
+
+
+/**
+ * The main Storage Actor.
+ */
+let StorageActor = exports.StorageActor = protocol.ActorClass({
+  typeName: "storage",
+
+  get window() {
+    return this.parentActor.window;
+  },
+
+  get document() {
+    return this.parentActor.window.document;
+  },
+
+  get windows() {
+    return this.childWindowPool;
+  },
+
+  /**
+   * List of event notifications that the server can send to the client.
+   *
+   *  - stores-update : When any store object in any storage type changes.
+   *  - stores-cleared : When all the store objects are removed.
+   *  - stores-reloaded : When all stores are reloaded. This generally mean that
+   *                      we should refetch everything again.
+   */
+  events: {
+    "stores-update": {
+      type: "storesUpdate",
+      data: Arg(0, "storeUpdateObject")
+    },
+    "stores-cleared": {
+      type: "storesCleared",
+      data: Arg(0, "json")
+    },
+    "stores-reloaded": {
+      type: "storesRelaoded",
+      data: Arg(0, "json")
+    }
+  },
+
+  initialize: function (conn, tabActor) {
+    protocol.Actor.prototype.initialize.call(this, null);
+
+    this.conn = conn;
+    this.parentActor = tabActor;
+
+    this.childActorPool = new Map();
+    this.childWindowPool = new Set();
+
+    // Get the top level content docshell for the tab we are targetting.
+    this.topDocshell = tabActor.window
+      .QueryInterface(Ci.nsIInterfaceRequestor)
+      .getInterface(Ci.nsIWebNavigation)
+      .QueryInterface(Ci.nsIDocShell)
+      .QueryInterface(Ci.nsIDocShellTreeItem);
+    // Fetch all the inner iframe windows in this tab.
+    this.fetchChildWindows(this.topDocshell);
+
+    // Initialize the registered store types
+    for (let [store, actor] of storageTypePool) {
+      this.childActorPool.set(store, new actor(this));
+    }
+
+    // Notifications that help us keep track of newly added windows and windows
+    // that got removed
+    Services.obs.addObserver(this, "content-document-global-created", false);
+    Services.obs.addObserver(this, "inner-window-destroyed", false);
+    this.onPageChange = this.onPageChange.bind(this);
+    tabActor.browser.addEventListener("pageshow", this.onPageChange, true);
+    tabActor.browser.addEventListener("pagehide", this.onPageChange, true);
+
+    this.boundUpdate = {};
+    // The time which periodically flushes and transfers the updated store
+    // objects.
+    this.updateTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+    this.updateTimer.initWithCallback(this , UPDATE_INTERVAL,
+      Ci.nsITimer.TYPE_REPEATING_PRECISE_CAN_SKIP);
+
+    // Layout helper for window.parent and window.top helper methods that work
+    // accross devices.
+    this.layoutHelper = new LayoutHelpers(this.window);
+  },
+
+  destroy: function() {
+    this.updateTimer.cancel();
+    this.updateTimer = null;
+    // Remove observers
+    Services.obs.removeObserver(this, "content-document-global-created", false);
+    Services.obs.removeObserver(this, "inner-window-destroyed", false);
+    if (this.parentActor.browser) {
+      this.parentActor.browser.removeEventListener(
+        "pageshow", this.onPageChange, true);
+      this.parentActor.browser.removeEventListener(
+        "pagehide", this.onPageChange, true);
+    }
+    // Destroy the registered store types
+    for (let actor of this.childActorPool.values()) {
+      actor.destroy();
+    }
+    this.childActorPool.clear();
+    this.topDocshell = null;
+  },
+
+  /**
+   * Given a docshell, recursively find otu all the child windows from it.
+   *
+   * @param {nsIDocShell} item
+   *        The docshell from which all inner windows need to be extracted.
+   */
+  fetchChildWindows: function(item) {
+    let docShell = item.QueryInterface(Ci.nsIDocShell)
+                       .QueryInterface(Ci.nsIDocShellTreeItem);
+    if (!docShell.contentViewer) {
+      return null;
+    }
+    let window = docShell.contentViewer.DOMDocument.defaultView;
+    if (window.location.href == "about:blank") {
+      // Skip out about:blank windows as Gecko creates them multiple times while
+      // creating any global.
+      return null;
+    }
+    this.childWindowPool.add(window);
+    for (let i = 0; i < docShell.childCount; i++) {
+      let child = docShell.getChildAt(i);
+      this.fetchChildWindows(child);
+    }
+    return null;
+  },
+
+  isIncludedInTopLevelWindow: function(window) {
+    return this.layoutHelper.isIncludedInTopLevelWindow(window);
+  },
+
+  getWindowFromInnerWindowID: function(innerID) {
+    innerID = innerID.QueryInterface(Ci.nsISupportsPRUint64).data;
+    for (let win of this.childWindowPool.values()) {
+      let id = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                   .getInterface(Ci.nsIDOMWindowUtils)
+                   .currentInnerWindowID;
+      if (id == innerID) {
+        return win;
+      }
+    }
+    return null;
+  },
+
+  /**
+   * Event handler for any docshell update. This lets us figure out whenever
+   * any new window is added, or an existing window is removed.
+   */
+  observe: function(subject, topic, data) {
+    if (subject.location &&
+        (!subject.location.href || subject.location.href == "about:blank")) {
+      return null;
+    }
+    if (topic == "content-document-global-created" &&
+        this.isIncludedInTopLevelWindow(subject)) {
+      this.childWindowPool.add(subject);
+      events.emit(this, "window-ready", subject);
+    }
+    else if (topic == "inner-window-destroyed") {
+      let window = this.getWindowFromInnerWindowID(subject);
+      if (window) {
+        this.childActorPool.delete(window);
+        events.emit(this, "window-destroyed", window);
+      }
+    }
+    return null;
+  },
+
+  onPageChange: function({target, type}) {
+    let window = target.defaultView;
+    if (type == "pagehide" && this.childWindowPool.delete(window)) {
+      events.emit(this, "window-destroyed", window)
+    }
+    else if (type == "pageshow" && window.location.href &&
+             window.location.href != "about:blank" &&
+             this.isIncludedInTopLevelWindow(window)) {
+      this.childWindowPool.add(window);
+      events.emit(this, "window-ready", window);
+    }
+  },
+
+  /**
+   * Lists the availabel hosts for all the registered storage types.
+   *
+   * @returns {object} An object containing with the following structure:
+   *  - <storageType> : [{
+   *      actor: <actorId>,
+   *      host: <hostname>
+   *    }]
+   */
+  listStores: method(function() {
+    // Explictly recalculate the window-tree
+    this.childWindowPool.clear();
+    this.fetchChildWindows(this.topDocshell);
+
+    let toReturn = {};
+    for (let [name, value] of this.childActorPool) {
+      toReturn[name] = value;
+    }
+    return toReturn;
+  }, {
+    response: RetVal(types.addDictType("storelist", getRegisteredTypes()))
+  }),
+
+  /**
+   * Notifies the client front with the updates in stores at regular intervals.
+   */
+  notify: function() {
+    if (!this.updatePending || this.updatingUpdateObject) {
+      return null;
+    }
+    events.emit(this, "stores-update", this.boundUpdate);
+    this.boundUpdate = {};
+    this.updatePending = false;
+    return null;
+  },
+
+  /**
+   * This method is called by the registered storage types so as to tell the
+   * Storage Actor that there are some changes in the stores. Storage Actor then
+   * notifies the client front about these changes at regular (UPDATE_INTERVAL)
+   * interval.
+   *
+   * @param {string} action
+   *        The type of change. One of "added", "changed" or "deleted"
+   * @param {string} storeType
+   *        The storage actor in which this change has occurred.
+   * @param {object} data
+   *        The update object. This object is of the following format:
+   *         - {
+   *             <host1>: [<store_names1>, <store_name2>...],
+   *             <host2>: [<store_names34>...],
+   *           }
+   *           Where host1, host2 are the host in which this change happened and
+   *           [<store_namesX] is an array of the names of the changed store
+   *           objects. Leave it empty if the host was completely removed.
+   *        When the action is "reloaded" or "cleared", `data` is an array of
+   *        hosts for which the stores were cleared or reloaded.
+   */
+  update: function(action, storeType, data) {
+    if (action == "cleared" || action == "reloaded") {
+      let toSend = {};
+      toSend[storeType] = data
+      events.emit(this, "stores-" + action, toSend);
+      return null;
+    }
+
+    this.updatingUpdateObject = true;
+    if (!this.boundUpdate[action]) {
+      this.boundUpdate[action] = {};
+    }
+    if (!this.boundUpdate[action][storeType]) {
+      this.boundUpdate[action][storeType] = {};
+    }
+    this.updatePending = true;
+    for (let host in data) {
+      if (!this.boundUpdate[action][storeType][host] || action == "deleted") {
+        this.boundUpdate[action][storeType][host] = data[host];
+      }
+      else {
+        this.boundUpdate[action][storeType][host] =
+        this.boundUpdate[action][storeType][host].concat(data[host]);
+      }
+    }
+    if (action == "added") {
+      // If the same store name was previously deleted or changed, but now is
+      // added somehow, dont send the deleted or changed update.
+      this.removeNamesFromUpdateList("deleted", storeType, data);
+      this.removeNamesFromUpdateList("changed", storeType, data);
+    }
+    else if (action == "changed" && this.boundUpdate.added &&
+             this.boundUpdate.added[storeType]) {
+      // If something got added and changed at the same time, then remove those
+      // items from changed instead.
+      this.removeNamesFromUpdateList("changed", storeType,
+                                     this.boundUpdate.added[storeType]);
+    }
+    else if (action == "deleted") {
+      // If any item got delete, or a host got delete, no point in sending
+      // added or changed update
+      this.removeNamesFromUpdateList("added", storeType, data);
+      this.removeNamesFromUpdateList("changed", storeType, data);
+      for (let host in data) {
+        if (data[host].length == 0 && this.boundUpdate.added &&
+            this.boundUpdate.added[storeType] &&
+            this.boundUpdate.added[storeType][host]) {
+          delete this.boundUpdate.added[storeType][host];
+        }
+        if (data[host].length == 0 && this.boundUpdate.changed &&
+            this.boundUpdate.changed[storeType] &&
+            this.boundUpdate.changed[storeType][host]) {
+          delete this.boundUpdate.changed[storeType][host];
+        }
+      }
+    }
+    this.updatingUpdateObject = false;
+    return null;
+  },
+
+  /**
+   * This method removes data from the this.boundUpdate object in the same
+   * manner like this.update() adds data to it.
+   *
+   * @param {string} action
+   *        The type of change. One of "added", "changed" or "deleted"
+   * @param {string} storeType
+   *        The storage actor for which you want to remove the updates data.
+   * @param {object} data
+   *        The update object. This object is of the following format:
+   *         - {
+   *             <host1>: [<store_names1>, <store_name2>...],
+   *             <host2>: [<store_names34>...],
+   *           }
+   *           Where host1, host2 are the hosts which you want to remove and
+   *           [<store_namesX] is an array of the names of the store objects.
+   */
+  removeNamesFromUpdateList: function(action, storeType, data) {
+    for (let host in data) {
+      if (this.boundUpdate[action] && this.boundUpdate[action][storeType] &&
+          this.boundUpdate[action][storeType][host]) {
+        for (let name in data[host]) {
+          let index = this.boundUpdate[action][storeType][host].indexOf(name);
+          if (index > -1) {
+            this.boundUpdate[action][storeType][host].splice(index, 1);
+          }
+        }
+        if (!this.boundUpdate[action][storeType][host].length) {
+          delete this.boundUpdate[action][storeType][host];
+        }
+      }
+    }
+    return null;
+  }
+});
+
+/**
+ * Front for the Storage Actor.
+ */
+let StorageFront = exports.StorageFront = protocol.FrontClass(StorageActor, {
+  initialize: function(client, tabForm) {
+    protocol.Front.prototype.initialize.call(this, client);
+    this.actorID = tabForm.storageActor;
+
+    client.addActorPool(this);
+    this.manage(this);
+  }
+});
--- a/toolkit/devtools/server/main.js
+++ b/toolkit/devtools/server/main.js
@@ -386,16 +386,17 @@ var DebuggerServer = {
    */
   addTabActors: function() {
     this.addActors("resource://gre/modules/devtools/server/actors/script.js");
     this.addActors("resource://gre/modules/devtools/server/actors/webconsole.js");
     this.registerModule("devtools/server/actors/inspector");
     this.registerModule("devtools/server/actors/webgl");
     this.registerModule("devtools/server/actors/stylesheets");
     this.registerModule("devtools/server/actors/styleeditor");
+    this.registerModule("devtools/server/actors/storage");
     this.registerModule("devtools/server/actors/gcli");
     this.registerModule("devtools/server/actors/tracer");
     this.registerModule("devtools/server/actors/memory");
     this.registerModule("devtools/server/actors/eventlooplag");
     if ("nsIProfiler" in Ci)
       this.addActors("resource://gre/modules/devtools/server/actors/profiler.js");
   },
 
--- a/toolkit/devtools/server/moz.build
+++ b/toolkit/devtools/server/moz.build
@@ -1,14 +1,15 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+BROWSER_CHROME_MANIFESTS += ['tests/browser/browser.ini']
 MOCHITEST_CHROME_MANIFESTS += ['tests/mochitest/chrome.ini']
 XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
 
 XPIDL_SOURCES += [
     'nsIJSInspector.idl',
 ]
 
 XPIDL_MODULE = 'jsinspector'
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/browser.ini
@@ -0,0 +1,12 @@
+[DEFAULT]
+support-files =
+  head.js
+  storage-dynamic-windows.html
+  storage-listings.html
+  storage-unsecured-iframe.html
+  storage-updates.html
+  storage-secured-iframe.html
+
+[browser_storage_dynamic_windows.js]
+[browser_storage_listings.js]
+[browser_storage_updates.js]
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/browser_storage_dynamic_windows.js
@@ -0,0 +1,289 @@
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/Services.jsm");
+let tempScope = {};
+Cu.import("resource://gre/modules/devtools/dbg-client.jsm", tempScope);
+Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
+Cu.import("resource://gre/modules/Promise.jsm", tempScope);
+let {DebuggerServer, DebuggerClient, Promise} = tempScope;
+tempScope = null;
+
+const {StorageFront} = require("devtools/server/actors/storage");
+let gFront;
+
+const beforeReload = {
+  cookies: {
+    "test1.example.org": ["c1", "cs2", "c3", "uc1"],
+    "sectest1.example.org": ["uc1", "cs2"]
+  },
+  localStorage: {
+    "http://test1.example.org": ["ls1", "ls2"],
+    "http://sectest1.example.org": ["iframe-u-ls1"]
+  },
+  sessionStorage: {
+    "http://test1.example.org": ["ss1"],
+    "http://sectest1.example.org": ["iframe-u-ss1", "iframe-u-ss2"]
+  }
+};
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+registerCleanupFunction(function() {
+  Services.prefs.clearUserPref("devtools.debugger.log");
+});
+
+function finishTests(client) {
+  client.close(() => {
+    DebuggerServer.destroy();
+    DebuggerClient = DebuggerServer = gFront = null;
+    finish();
+  });
+}
+
+function testStores(data, client) {
+  testWindowsBeforeReload(data);
+  testReload().then(() =>
+  testAddIframe()).then(() =>
+  testRemoveIframe()).then(() =>
+  finishTests(client));
+}
+
+function testWindowsBeforeReload(data) {
+  for (let storageType in beforeReload) {
+    ok(data[storageType], storageType + " storage actor is present");
+    is(Object.keys(data[storageType].hosts).length,
+       Object.keys(beforeReload[storageType]).length,
+       "Number of hosts for " + storageType + "match");
+    for (let host in beforeReload[storageType]) {
+      ok(data[storageType].hosts[host], "Host " + host + " is present");
+    }
+  }
+}
+
+function markOutMatched(toBeEmptied, data, deleted) {
+  if (!Object.keys(toBeEmptied).length) {
+    info("Object empty")
+    return;
+  }
+  ok(Object.keys(data).length,
+     "Atleast some storage types should be present in deleted");
+  for (let storageType in toBeEmptied) {
+    if (!data[storageType]) {
+      continue;
+    }
+    info("Testing for " + storageType);
+    for (let host in data[storageType]) {
+      ok(toBeEmptied[storageType][host], "Host " + host + " found");
+      if (!deleted) {
+        for (let item of data[storageType][host]) {
+          let index = toBeEmptied[storageType][host].indexOf(item);
+          ok(index > -1, "Item found - " + item);
+          if (index > -1) {
+            toBeEmptied[storageType][host].splice(index, 1);
+          }
+        }
+        if (!toBeEmptied[storageType][host].length) {
+          delete toBeEmptied[storageType][host];
+        }
+      }
+      else {
+        delete toBeEmptied[storageType][host];
+      }
+    }
+    if (!Object.keys(toBeEmptied[storageType]).length) {
+      delete toBeEmptied[storageType];
+    }
+  }
+}
+
+function testReload() {
+  info("Testing if reload works properly");
+
+  let shouldBeEmptyFirst = Cu.cloneInto(beforeReload,  {});
+  let shouldBeEmptyLast = Cu.cloneInto(beforeReload,  {});
+  let reloaded = Promise.defer();
+
+  let onStoresUpdate = data => {
+    info("in stores update of testReload");
+    // This might be second time stores update is happening, in which case,
+    // data.deleted will be null
+    if (data.deleted) {
+      markOutMatched(shouldBeEmptyFirst, data.deleted, true);
+    }
+
+    if (!Object.keys(shouldBeEmptyFirst).length) {
+      info("shouldBeEmptyFirst is empty now");
+    }
+    else if (!data.added || !Object.keys(data.added).length) {
+      ok(false, "deleted object should have something if an added object " +
+         "does not have anything");
+      endTestReloaded();
+    }
+
+    // stores-update call might not have data.added for the first time on slow
+    // machines, in which case, data.added will be null
+    if (data.added) {
+      markOutMatched(shouldBeEmptyLast, data.added);
+    }
+
+    if (!Object.keys(shouldBeEmptyLast).length) {
+      info("Everything to be received is received.");
+      endTestReloaded();
+    }
+  };
+
+  let endTestReloaded = () => {
+    gFront.off("stores-update", onStoresUpdate);
+    reloaded.resolve();
+  };
+
+  gFront.on("stores-update", onStoresUpdate);
+
+  content.location.reload();
+  return reloaded.promise;
+}
+
+function testAddIframe() {
+  info("Testing if new iframe addition works properly");
+  let reloaded = Promise.defer();
+
+  let shouldBeEmpty = {
+    localStorage: {
+      "https://sectest1.example.org": ["iframe-s-ls1"]
+    },
+    sessionStorage: {
+      "https://sectest1.example.org": ["iframe-s-ss1"]
+    },
+    cookies: {
+      "sectest1.example.org": ["sc1"]
+    }
+  };
+
+  let onStoresUpdate = data => {
+    info("checking if the hosts list is correct for this iframe addition");
+
+    markOutMatched(shouldBeEmpty, data.added);
+
+    ok(!data.changed || !data.changed.cookies ||
+       !data.changed.cookies["https://sectest1.example.org"],
+       "Nothing got changed for cookies");
+    ok(!data.changed || !data.changed.localStorage ||
+       !data.changed.localStorage["https://sectest1.example.org"],
+       "Nothing got changed for local storage");
+    ok(!data.changed || !data.changed.sessionStorage ||
+       !data.changed.sessionStorage["https://sectest1.example.org"],
+       "Nothing got changed for session storage");
+
+    ok(!data.deleted || !data.deleted.cookies ||
+       !data.deleted.cookies["https://sectest1.example.org"],
+       "Nothing got deleted for cookies");
+    ok(!data.deleted || !data.deleted.localStorage ||
+       !data.deleted.localStorage["https://sectest1.example.org"],
+       "Nothing got deleted for local storage");
+    ok(!data.deleted || !data.deleted.sessionStorage ||
+       !data.deleted.sessionStorage["https://sectest1.example.org"],
+       "Nothing got deleted for session storage");
+
+    if (!Object.keys(shouldBeEmpty).length) {
+      info("Everything to be received is received.");
+      endTestReloaded();
+    }
+  };
+
+  let endTestReloaded = () => {
+    gFront.off("stores-update", onStoresUpdate);
+    reloaded.resolve();
+  };
+
+  gFront.on("stores-update", onStoresUpdate);
+
+  let iframe = content.document.createElement("iframe");
+  iframe.src = ALT_DOMAIN_SECURED + "storage-secured-iframe.html";
+  content.document.querySelector("body").appendChild(iframe);
+  return reloaded.promise;
+}
+
+function testRemoveIframe() {
+  info("Testing if iframe removal works properly");
+  let reloaded = Promise.defer();
+
+  let shouldBeEmpty = {
+    localStorage: {
+      "http://sectest1.example.org": []
+    },
+    sessionStorage: {
+      "http://sectest1.example.org": []
+    }
+  };
+
+  let onStoresUpdate = data => {
+    info("checking if the hosts list is correct for this iframe deletion");
+
+    markOutMatched(shouldBeEmpty, data.deleted, true);
+
+    ok(!data.deleted.cookies || !data.deleted.cookies["sectest1.example.org"],
+       "Nothing got deleted for Cookies as the same hostname is still present");
+
+    ok(!data.changed || !data.changed.cookies ||
+       !data.changed.cookies["http://sectest1.example.org"],
+       "Nothing got changed for cookies");
+    ok(!data.changed || !data.changed.localStorage ||
+       !data.changed.localStorage["http://sectest1.example.org"],
+       "Nothing got changed for local storage");
+    ok(!data.changed || !data.changed.sessionStorage ||
+       !data.changed.sessionStorage["http://sectest1.example.org"],
+       "Nothing got changed for session storage");
+
+    ok(!data.added || !data.added.cookies ||
+       !data.added.cookies["http://sectest1.example.org"],
+       "Nothing got added for cookies");
+    ok(!data.added || !data.added.localStorage ||
+       !data.added.localStorage["http://sectest1.example.org"],
+       "Nothing got added for local storage");
+    ok(!data.added || !data.added.sessionStorage ||
+       !data.added.sessionStorage["http://sectest1.example.org"],
+       "Nothing got added for session storage");
+
+    if (!Object.keys(shouldBeEmpty).length) {
+      info("Everything to be received is received.");
+      endTestReloaded();
+    }
+  };
+
+  let endTestReloaded = () => {
+    gFront.off("stores-update", onStoresUpdate);
+    reloaded.resolve();
+  };
+
+  gFront.on("stores-update", onStoresUpdate);
+
+  for (let iframe of content.document.querySelectorAll("iframe")) {
+    if (iframe.src.startsWith("http:")) {
+      iframe.remove();
+      break;
+    }
+  }
+  return reloaded.promise;
+}
+
+function test() {
+  waitForExplicitFinish();
+  addTab(MAIN_DOMAIN + "storage-dynamic-windows.html", function() {
+    try {
+      // Sometimes debugger server does not get destroyed correctly by previous
+      // tests.
+      DebuggerServer.destroy();
+    } catch (ex) { }
+    DebuggerServer.init(function () { return true; });
+    DebuggerServer.addBrowserActors();
+
+    let client = new DebuggerClient(DebuggerServer.connectPipe());
+    client.connect(function onConnect() {
+      client.listTabs(function onListTabs(aResponse) {
+        let form = aResponse.tabs[aResponse.selected];
+        gFront = StorageFront(client, form);
+
+        gFront.listStores().then(data => testStores(data, client));
+      });
+    });
+  })
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/browser_storage_listings.js
@@ -0,0 +1,291 @@
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/Services.jsm");
+let tempScope = {};
+Cu.import("resource://gre/modules/devtools/dbg-client.jsm", tempScope);
+Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
+let {DebuggerServer, DebuggerClient} = tempScope;
+tempScope = null;
+
+const {StorageFront} = require("devtools/server/actors/storage");
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+registerCleanupFunction(function() {
+  Services.prefs.clearUserPref("devtools.debugger.log");
+});
+
+const storeMap = {
+  cookies: {
+    "test1.example.org": [
+      {
+        name: "c1",
+        value: "foobar",
+        expires: 2000000000000,
+        path: "/browser",
+        host: "test1.example.org",
+        isDomain: false,
+        isSecure: false,
+      },
+      {
+        name: "cs2",
+        value: "sessionCookie",
+        path: "/",
+        host: ".example.org",
+        expires: 0,
+        isDomain: true,
+        isSecure: false,
+      },
+      {
+        name: "c3",
+        value: "foobar-2",
+        expires: 2000000001000,
+        path: "/",
+        host: "test1.example.org",
+        isDomain: false,
+        isSecure: true,
+      },
+      {
+        name: "uc1",
+        value: "foobar",
+        host: ".example.org",
+        path: "/",
+        expires: 0,
+        isDomain: true,
+        isSecure: true,
+      }
+    ],
+    "sectest1.example.org": [
+      {
+        name: "uc1",
+        value: "foobar",
+        host: ".example.org",
+        path: "/",
+        expires: 0,
+        isDomain: true,
+        isSecure: true,
+      },
+      {
+        name: "cs2",
+        value: "sessionCookie",
+        path: "/",
+        host: ".example.org",
+        expires: 0,
+        isDomain: true,
+        isSecure: false,
+      },
+      {
+        name: "sc1",
+        value: "foobar",
+        path: "/browser/toolkit/devtools/server/tests/browser/",
+        host: "sectest1.example.org",
+        expires: 0,
+        isDomain: false,
+        isSecure: false,
+      }
+    ]
+  },
+  localStorage: {
+    "http://test1.example.org": [
+      {
+        name: "ls1",
+        value: "foobar"
+      },
+      {
+        name: "ls2",
+        value: "foobar-2"
+      }
+    ],
+    "http://sectest1.example.org": [
+      {
+        name: "iframe-u-ls1",
+        value: "foobar"
+      }
+    ],
+    "https://sectest1.example.org": [
+      {
+        name: "iframe-s-ls1",
+        value: "foobar"
+      }
+    ]
+  },
+  sessionStorage: {
+    "http://test1.example.org": [
+      {
+        name: "ss1",
+        value: "foobar-3"
+      }
+    ],
+    "http://sectest1.example.org": [
+      {
+        name: "iframe-u-ss1",
+        value: "foobar1"
+      },
+      {
+        name: "iframe-u-ss2",
+        value: "foobar2"
+      }
+    ],
+    "https://sectest1.example.org": [
+      {
+        name: "iframe-s-ss1",
+        value: "foobar-2"
+      }
+    ]
+  }
+};
+
+function finishTests(client) {
+  client.close(() => {
+    DebuggerServer.destroy();
+    DebuggerClient = DebuggerServer = null;
+    finish();
+  });
+}
+
+function testStores(data, client) {
+  ok(data.cookies, "Cookies storage actor is present");
+  ok(data.localStorage, "Local Storage storage actor is present");
+  ok(data.sessionStorage, "Session Storage storage actor is present");
+  testCookies(data.cookies).then(() =>
+  testLocalStorage(data.localStorage)).then(() =>
+  testSessionStorage(data.sessionStorage)).then(() =>
+  finishTests(client));
+}
+
+function testCookies(cookiesActor) {
+  is(Object.keys(cookiesActor.hosts).length, 2, "Correct number of host entries for cookies");
+  return testCookiesObjects(0, cookiesActor.hosts, cookiesActor);
+}
+
+function testCookiesObjects(index, hosts, cookiesActor) {
+  let host = Object.keys(hosts)[index];
+  let matchItems = data => {
+    is(data.total, storeMap.cookies[host].length,
+       "Number of cookies in host " + host + " matches");
+    for (let item of data.data) {
+      let found = false;
+      for (let toMatch of storeMap.cookies[host]) {
+        if (item.name == toMatch.name) {
+          found = true;
+          ok(true, "Found cookie " + item.name + " in response");
+          is(item.value.str, toMatch.value, "The value matches.");
+          is(item.expires, toMatch.expires, "The expiry time matches.");
+          is(item.path, toMatch.path, "The path matches.");
+          is(item.host, toMatch.host, "The host matches.");
+          is(item.isSecure, toMatch.isSecure, "The isSecure value matches.");
+          is(item.isDomain, toMatch.isDomain, "The isDomain value matches.");
+          break;
+        }
+      }
+      if (!found) {
+        ok(false, "cookie " + item.name + " should not exist in response;");
+      }
+    }
+  };
+
+  ok(!!storeMap.cookies[host], "Host is present in the list : " + host);
+  if (index == Object.keys(hosts).length - 1) {
+    return cookiesActor.getStoreObjects(host).then(matchItems);
+  }
+  return cookiesActor.getStoreObjects(host).then(matchItems).then(() => {
+    return testCookiesObjects(++index, hosts, cookiesActor);
+  });
+}
+
+function testLocalStorage(localStorageActor) {
+  is(Object.keys(localStorageActor.hosts).length, 3,
+     "Correct number of host entries for local storage");
+  return testLocalStorageObjects(0, localStorageActor.hosts, localStorageActor);
+}
+
+function testLocalStorageObjects(index, hosts, localStorageActor) {
+  let host = Object.keys(hosts)[index];
+  let matchItems = data => {
+    is(data.total, storeMap.localStorage[host].length,
+       "Number of local storage items in host " + host + " matches");
+    for (let item of data.data) {
+      let found = false;
+      for (let toMatch of storeMap.localStorage[host]) {
+        if (item.name == toMatch.name) {
+          found = true;
+          ok(true, "Found local storage item " + item.name + " in response");
+          is(item.value.str, toMatch.value, "The value matches.");
+          break;
+        }
+      }
+      if (!found) {
+        ok(false, "local storage item " + item.name +
+                  " should not exist in response;");
+      }
+    }
+  };
+
+  ok(!!storeMap.localStorage[host], "Host is present in the list : " + host);
+  if (index == Object.keys(hosts).length - 1) {
+    return localStorageActor.getStoreObjects(host).then(matchItems);
+  }
+  return localStorageActor.getStoreObjects(host).then(matchItems).then(() => {
+    return testLocalStorageObjects(++index, hosts, localStorageActor);
+  });
+}
+
+function testSessionStorage(sessionStorageActor) {
+  is(Object.keys(sessionStorageActor.hosts).length, 3,
+     "Correct number of host entries for session storage");
+  return testSessionStorageObjects(0, sessionStorageActor.hosts,
+                                   sessionStorageActor);
+}
+
+function testSessionStorageObjects(index, hosts, sessionStorageActor) {
+  let host = Object.keys(hosts)[index];
+  let matchItems = data => {
+    is(data.total, storeMap.sessionStorage[host].length,
+       "Number of session storage items in host " + host + " matches");
+    for (let item of data.data) {
+      let found = false;
+      for (let toMatch of storeMap.sessionStorage[host]) {
+        if (item.name == toMatch.name) {
+          found = true;
+          ok(true, "Found session storage item " + item.name + " in response");
+          is(item.value.str, toMatch.value, "The value matches.");
+          break;
+        }
+      }
+      if (!found) {
+        ok(false, "session storage item " + item.name +
+                  " should not exist in response;");
+      }
+    }
+  };
+
+  ok(!!storeMap.sessionStorage[host], "Host is present in the list : " + host);
+  if (index == Object.keys(hosts).length - 1) {
+    return sessionStorageActor.getStoreObjects(host).then(matchItems);
+  }
+  return sessionStorageActor.getStoreObjects(host).then(matchItems).then(() => {
+    return testSessionStorageObjects(++index, hosts, sessionStorageActor);
+  });
+}
+
+function test() {
+  waitForExplicitFinish();
+  addTab(MAIN_DOMAIN + "storage-listings.html", function() {
+    try {
+      // Sometimes debugger server does not get destroyed correctly by previous
+      // tests.
+      DebuggerServer.destroy();
+    } catch (ex) { }
+    DebuggerServer.init(function () { return true; });
+    DebuggerServer.addBrowserActors();
+
+    let client = new DebuggerClient(DebuggerServer.connectPipe());
+    client.connect(function onConnect() {
+      client.listTabs(function onListTabs(aResponse) {
+        let form = aResponse.tabs[aResponse.selected];
+        let front = StorageFront(client, form);
+
+        front.listStores().then(data => testStores(data, client));
+      });
+    });
+  })
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/browser_storage_updates.js
@@ -0,0 +1,258 @@
+const Cu = Components.utils;
+Cu.import("resource://gre/modules/Services.jsm");
+let tempScope = {};
+Cu.import("resource://gre/modules/devtools/dbg-client.jsm", tempScope);
+Cu.import("resource://gre/modules/devtools/dbg-server.jsm", tempScope);
+Cu.import("resource://gre/modules/Promise.jsm", tempScope);
+let {DebuggerServer, DebuggerClient, Promise} = tempScope;
+tempScope = null;
+
+const {StorageFront} = require("devtools/server/actors/storage");
+let gTests;
+let gExpected;
+let index = 0;
+
+const beforeReload = {
+  cookies: ["test1.example.org", "sectest1.example.org"],
+  localStorage: ["http://test1.example.org", "http://sectest1.example.org"],
+  sessionStorage: ["http://test1.example.org", "http://sectest1.example.org"],
+};
+
+// Always log packets when running tests.
+Services.prefs.setBoolPref("devtools.debugger.log", true);
+registerCleanupFunction(function() {
+  Services.prefs.clearUserPref("devtools.debugger.log");
+});
+
+function finishTests(client) {
+  client.close(() => {
+    DebuggerServer.destroy();
+    DebuggerClient = DebuggerServer = gTests = null;
+    finish();
+  });
+}
+
+function markOutMatched(toBeEmptied, data, deleted) {
+  if (!Object.keys(toBeEmptied).length) {
+    info("Object empty")
+    return;
+  }
+  ok(Object.keys(data).length,
+     "Atleast some storage types should be present in deleted");
+  for (let storageType in toBeEmptied) {
+    if (!data[storageType]) {
+      continue;
+    }
+    info("Testing for " + storageType);
+    for (let host in data[storageType]) {
+      ok(toBeEmptied[storageType][host], "Host " + host + " found");
+
+      for (let item of data[storageType][host]) {
+        let index = toBeEmptied[storageType][host].indexOf(item);
+        ok(index > -1, "Item found - " + item);
+        if (index > -1) {
+          toBeEmptied[storageType][host].splice(index, 1);
+        }
+      }
+      if (!toBeEmptied[storageType][host].length) {
+        delete toBeEmptied[storageType][host];
+      }
+    }
+    if (!Object.keys(toBeEmptied[storageType]).length) {
+      delete toBeEmptied[storageType];
+    }
+  }
+}
+
+function onStoresCleared(data) {
+  if (data.sessionStorage || data.localStorage) {
+    let hosts = data.sessionStorage || data.localStorage;
+    info("Stores cleared required for session storage");
+    is(hosts.length, 1, "number of hosts is 1");
+    is(hosts[0], "http://test1.example.org",
+       "host matches for " + Object.keys(data)[0]);
+    gTests.next();
+  }
+  else {
+    ok(false, "Stores cleared should only be for local and sesion storage");
+  }
+
+}
+
+function onStoresUpdate({added, changed, deleted}) {
+  info("inside stores update for index " + index);
+
+  // Here, added, changed and deleted might be null even if they are required as
+  // per gExpected. This is fine as they might come in the next stores-update
+  // call or have already come in the previous one.
+  if (added) {
+    info("matching added object for index " + index);
+    markOutMatched(gExpected.added, added);
+  }
+  if (changed) {
+    info("matching changed object for index " + index);
+    markOutMatched(gExpected.changed, changed);
+  }
+  if (deleted) {
+    info("matching deleted object for index " + index);
+    markOutMatched(gExpected.deleted, deleted);
+  }
+
+  if ((!gExpected.added || !Object.keys(gExpected.added).length) &&
+      (!gExpected.changed || !Object.keys(gExpected.changed).length) &&
+      (!gExpected.deleted || !Object.keys(gExpected.deleted).length)) {
+    info("Everything expected has been received for index " + index);
+    index++;
+    gTests.next();
+  }
+  else {
+    info("Still some updates pending for index " + index);
+  }
+}
+
+function* UpdateTests(front, win, client) {
+  front.on("stores-update", onStoresUpdate);
+
+  // index 0
+  gExpected = {
+    added: {
+      cookies: {
+        "test1.example.org": ["c1", "c2"]
+      },
+      localStorage: {
+        "http://test1.example.org": ["l1"]
+      }
+    }
+  };
+  win.addCookie("c1", "foobar1");
+  win.addCookie("c2", "foobar2");
+  win.localStorage.setItem("l1", "foobar1");
+  yield undefined;
+
+  // index 1
+  gExpected = {
+    changed: {
+      cookies: {
+        "test1.example.org": ["c1"]
+      }
+    },
+    added: {
+      localStorage: {
+        "http://test1.example.org": ["l2"]
+      }
+    }
+  };
+  win.addCookie("c1", "new_foobar1");
+  win.localStorage.setItem("l2", "foobar2");
+  yield undefined;
+
+  // index 2
+  gExpected = {
+    deleted: {
+      cookies: {
+        "test1.example.org": ["c2"]
+      },
+      localStorage: {
+        "http://test1.example.org": ["l1"]
+      }
+    },
+    added: {
+      localStorage: {
+        "http://test1.example.org": ["l3"]
+      }
+    }
+  };
+  win.removeCookie("c2");
+  win.localStorage.removeItem("l1");
+  win.localStorage.setItem("l3", "foobar3");
+  yield undefined;
+
+  // index 3
+  gExpected = {
+    added: {
+      cookies: {
+        "test1.example.org": ["c3"]
+      },
+      sessionStorage: {
+        "http://test1.example.org": ["s1", "s2"]
+      }
+    },
+    changed: {
+      localStorage: {
+        "http://test1.example.org": ["l3"]
+      }
+    },
+    deleted: {
+      cookies: {
+        "test1.example.org": ["c1"]
+      },
+      localStorage: {
+        "http://test1.example.org": ["l2"]
+      }
+    }
+  };
+  win.removeCookie("c1");
+  win.addCookie("c3", "foobar3");
+  win.localStorage.removeItem("l2");
+  win.sessionStorage.setItem("s1", "foobar1");
+  win.sessionStorage.setItem("s2", "foobar2");
+  win.localStorage.setItem("l3", "new_foobar3");
+  yield undefined;
+
+  // index 4
+  gExpected = {
+    deleted: {
+      sessionStorage: {
+        "http://test1.example.org": ["s1"]
+      }
+    }
+  };
+  win.sessionStorage.removeItem("s1");
+  yield undefined;
+
+  // index 5
+  gExpected = {
+    deleted: {
+      cookies: {
+        "test1.example.org": ["c3"]
+      }
+    }
+  };
+  front.on("stores-cleared", onStoresCleared);
+  win.clear();
+  yield undefined;
+  // Another 2 more yield undefined s so as to wait for the "stores-cleared" to
+  // fire. One for Local Storage and other for Session Storage
+  yield undefined;
+  yield undefined;
+
+  front.off("stores-cleared", onStoresCleared);
+  front.off("stores-update", onStoresUpdate);
+  finishTests(client);
+}
+
+
+function test() {
+  waitForExplicitFinish();
+  addTab(MAIN_DOMAIN + "storage-updates.html", function(doc) {
+    try {
+      // Sometimes debugger server does not get destroyed correctly by previous
+      // tests.
+      DebuggerServer.destroy();
+    } catch (ex) { }
+    DebuggerServer.init(function () { return true; });
+    DebuggerServer.addBrowserActors();
+
+    let client = new DebuggerClient(DebuggerServer.connectPipe());
+    client.connect(function onConnect() {
+      client.listTabs(function onListTabs(aResponse) {
+        let form = aResponse.tabs[aResponse.selected];
+        let front = StorageFront(client, form);
+        gTests = UpdateTests(front, doc.defaultView.wrappedJSObject,
+                             client);
+        // Make an initial call to initialize the actor
+        front.listStores().then(() => gTests.next());
+      });
+    });
+  })
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/head.js
@@ -0,0 +1,43 @@
+/* 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/. */
+let tempScope = {};
+Cu.import("resource://gre/modules/devtools/Loader.jsm", tempScope);
+Cu.import("resource://gre/modules/devtools/Console.jsm", tempScope);
+const require = tempScope.devtools.require;
+const console = tempScope.console;
+tempScope = null;
+const PATH = "browser/toolkit/devtools/server/tests/browser/";
+const MAIN_DOMAIN = "http://test1.example.org/" + PATH;
+const ALT_DOMAIN = "http://sectest1.example.org/" + PATH;
+const ALT_DOMAIN_SECURED = "https://sectest1.example.org:443/" + PATH;
+
+/**
+ * Open a new tab at a URL and call a callback on load
+ */
+function addTab(aURL, aCallback)
+{
+  waitForExplicitFinish();
+
+  gBrowser.selectedTab = gBrowser.addTab();
+  content.location = aURL;
+
+  let tab = gBrowser.selectedTab;
+  let browser = gBrowser.getBrowserForTab(tab);
+
+  function onTabLoad(event) {
+    if (event.originalTarget.location.href != aURL) {
+      return;
+    }
+    browser.removeEventListener("load", onTabLoad, true);
+    aCallback(browser.contentDocument);
+  }
+
+  browser.addEventListener("load", onTabLoad, true);
+}
+
+registerCleanupFunction(function tearDown() {
+  while (gBrowser.tabs.length > 1) {
+    gBrowser.removeCurrentTab();
+  }
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/storage-dynamic-windows.html
@@ -0,0 +1,39 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 965872 - Storage inspector actor with cookies, local storage and session storage.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Storage inspector test for listing hosts and storages</title>
+</head>
+<body>
+<iframe src="http://sectest1.example.org/browser/toolkit/devtools/server/tests/browser/storage-unsecured-iframe.html"></iframe>
+<script>
+
+var partialHostname = location.hostname.match(/^[^.]+(\..*)$/)[1];
+var cookieExpiresTime1 = 2000000000000;
+var cookieExpiresTime2 = 2000000001000;
+// Setting up some cookies to eat.
+document.cookie = "c1=foobar; expires=" +
+  new Date(cookieExpiresTime1).toGMTString() + "; path=/browser";
+document.cookie = "cs2=sessionCookie; path=/; domain=" + partialHostname;
+document.cookie = "c3=foobar-2; expires=" +
+  new Date(cookieExpiresTime2).toGMTString() + "; path=/";
+// ... and some local storage items ..
+localStorage.setItem("ls1", "foobar");
+localStorage.setItem("ls2", "foobar-2");
+// ... and finally some session storage items too
+sessionStorage.setItem("ss1", "foobar-3");
+console.log("added cookies and stuff from main page");
+
+window.onunload = function() {
+  document.cookie = "c1=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
+  document.cookie = "c3=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
+  localStorage.clear();
+  sessionStorage.clear();
+  console.log("removed cookies and stuff from main page");
+}
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/storage-listings.html
@@ -0,0 +1,40 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 965872 - Storage inspector actor with cookies, local storage and session storage.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Storage inspector test for listing hosts and storages</title>
+</head>
+<body>
+<iframe src="http://sectest1.example.org/browser/toolkit/devtools/server/tests/browser/storage-unsecured-iframe.html"></iframe>
+<iframe src="https://sectest1.example.org:443/browser/toolkit/devtools/server/tests/browser/storage-secured-iframe.html"></iframe>
+<script>
+
+var partialHostname = location.hostname.match(/^[^.]+(\..*)$/)[1];
+var cookieExpiresTime1 = 2000000000000;
+var cookieExpiresTime2 = 2000000001000;
+// Setting up some cookies to eat.
+document.cookie = "c1=foobar; expires=" +
+  new Date(cookieExpiresTime1).toGMTString() + "; path=/browser";
+document.cookie = "cs2=sessionCookie; path=/; domain=" + partialHostname;
+document.cookie = "c3=foobar-2; secure=true; expires=" +
+  new Date(cookieExpiresTime2).toGMTString() + "; path=/";
+// ... and some local storage items ..
+localStorage.setItem("ls1", "foobar");
+localStorage.setItem("ls2", "foobar-2");
+// ... and finally some session storage items too
+sessionStorage.setItem("ss1", "foobar-3");
+console.log("added cookies and stuff from main page");
+
+window.onunload = function() {
+  document.cookie = "c1=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
+  document.cookie = "c3=; expires=Thu, 01 Jan 1970 00:00:00 GMT";
+  localStorage.clear();
+  sessionStorage.clear();
+  console.log("removed cookies and stuff from main page");
+}
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/storage-secured-iframe.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Iframe for testing multiple host detetion in storage actor
+-->
+<head>
+  <meta charset="utf-8">
+</head>
+<body>
+<script>
+
+document.cookie = "sc1=foobar;";
+localStorage.setItem("iframe-s-ls1", "foobar");
+sessionStorage.setItem("iframe-s-ss1", "foobar-2");
+console.log("added cookies and stuff from secured iframe");
+
+window.onunload = function() {
+  localStorage.clear();
+  sessionStorage.clear();
+  console.log("removed cookies and stuff from secured iframe");
+}
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/storage-unsecured-iframe.html
@@ -0,0 +1,25 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Iframe for testing multiple host detetion in storage actor
+-->
+<head>
+  <meta charset="utf-8">
+</head>
+<body>
+<script>
+
+document.cookie = "uc1=foobar; domain=.example.org; path=/; secure=true";
+localStorage.setItem("iframe-u-ls1", "foobar");
+sessionStorage.setItem("iframe-u-ss1", "foobar1");
+sessionStorage.setItem("iframe-u-ss2", "foobar2");
+console.log("added cookies and stuff from unsecured iframe");
+
+window.onunload = function() {
+  localStorage.clear();
+  sessionStorage.clear();
+  console.log("removed cookies and stuff from unsecured iframe");
+}
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/browser/storage-updates.html
@@ -0,0 +1,44 @@
+<!DOCTYPE HTML>