merge fx-team to mozilla-central
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 12 Mar 2014 12:19:49 +0100
changeset 173154 a56837cfc67c11cc582e5109b0027e3f6b504faa
parent 173115 6d4044f9dfff1044a7e4293434bc9e6d1d287e0d (current diff)
parent 173153 e7f97fa8a86b2be9e451f33cb15d558134e16231 (diff)
child 173170 a1639dd9dd3dd7b77090039b55675086ddc3cbe6
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
milestone30.0a1
merge fx-team to mozilla-central
browser/base/content/browser.js
--- 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/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 0000000000000000000000000000000000000000..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 0000000000000000000000000000000000000000..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/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>
+<html>
+<!--
+Bug 965872 - Storage inspector actor with cookies, local storage and session storage.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Storage inspector blank html for tests</title>
+</head>
+<body>
+<script>
+
+window.addCookie = function(name, value, path, domain, expires, secure) {
+  var cookieString = name + "=" + value + ";";
+  if (path) {
+    cookieString += "path=" + path + ";";
+  }
+  if (domain) {
+    cookieString += "domain=" + domain + ";";
+  }
+  if (expires) {
+    cookieString += "expires=" + expires + ";";
+  }
+  if (secure) {
+    cookieString += "secure=true;";
+  }
+  document.cookie = cookieString;
+};
+
+window.removeCookie = function(name) {
+  document.cookie = name + "=;expires=Thu, 01 Jan 1970 00:00:00 GMT";
+}
+
+window.clear = function() {
+  var cookies = document.cookie;
+  for (var cookie of cookies.split(";")) {
+    removeCookie(cookie.split("=")[0]);
+  }
+  localStorage.clear();
+  sessionStorage.clear();
+}
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/ZipUtils.jsm
@@ -0,0 +1,223 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+this.EXPORTED_SYMBOLS = [ "ZipUtils" ];
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "OS",
+                                  "resource://gre/modules/osfile.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Promise",
+                                  "resource://gre/modules/Promise.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Task",
+                                  "resource://gre/modules/Task.jsm");
+
+
+// The maximum amount of file data to buffer at a time during file extraction
+const EXTRACTION_BUFFER               = 1024 * 512;
+
+
+/**
+ * Asynchronously writes data from an nsIInputStream to an OS.File instance.
+ * The source stream and OS.File are closed regardless of whether the operation
+ * succeeds or fails.
+ * Returns a promise that will be resolved when complete.
+ *
+ * @param  aPath
+ *         The name of the file being extracted for logging purposes.
+ * @param  aStream
+ *         The source nsIInputStream.
+ * @param  aFile
+ *         The open OS.File instance to write to.
+ */
+function saveStreamAsync(aPath, aStream, aFile) {
+  let deferred = Promise.defer();
+
+  // Read the input stream on a background thread
+  let sts = Cc["@mozilla.org/network/stream-transport-service;1"].
+            getService(Ci.nsIStreamTransportService);
+  let transport = sts.createInputTransport(aStream, -1, -1, true);
+  let input = transport.openInputStream(0, 0, 0)
+                       .QueryInterface(Ci.nsIAsyncInputStream);
+  let source = Cc["@mozilla.org/binaryinputstream;1"].
+               createInstance(Ci.nsIBinaryInputStream);
+  source.setInputStream(input);
+
+  let data = Uint8Array(EXTRACTION_BUFFER);
+
+  function readFailed(error) {
+    try {
+      aStream.close();
+    }
+    catch (e) {
+      logger.error("Failed to close JAR stream for " + aPath);
+    }
+
+    aFile.close().then(function() {
+      deferred.reject(error);
+    }, function(e) {
+      logger.error("Failed to close file for " + aPath);
+      deferred.reject(error);
+    });
+  }
+
+  function readData() {
+    try {
+      let count = Math.min(source.available(), data.byteLength);
+      source.readArrayBuffer(count, data.buffer);
+
+      aFile.write(data, { bytes: count }).then(function() {
+        input.asyncWait(readData, 0, 0, Services.tm.currentThread);
+      }, readFailed);
+    }
+    catch (e if e.result == Cr.NS_BASE_STREAM_CLOSED) {
+      deferred.resolve(aFile.close());
+    }
+    catch (e) {
+      readFailed(e);
+    }
+  }
+
+  input.asyncWait(readData, 0, 0, Services.tm.currentThread);
+
+  return deferred.promise;
+}
+
+
+this.ZipUtils = {
+
+  /**
+   * Asynchronously extracts files from a ZIP file into a directory.
+   * Returns a promise that will be resolved when the extraction is complete.
+   *
+   * @param  aZipFile
+   *         The source ZIP file that contains the add-on.
+   * @param  aDir
+   *         The nsIFile to extract to.
+   */
+  extractFilesAsync: function ZipUtils_extractFilesAsync(aZipFile, aDir) {
+    let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+                    createInstance(Ci.nsIZipReader);
+
+    try {
+      zipReader.open(aZipFile);
+    }
+    catch (e) {
+      return Promise.reject(e);
+    }
+
+    return Task.spawn(function() {
+      // Get all of the entries in the zip and sort them so we create directories
+      // before files
+      let entries = zipReader.findEntries(null);
+      let names = [];
+      while (entries.hasMore())
+        names.push(entries.getNext());
+      names.sort();
+
+      for (let name of names) {
+        let entryName = name;
+        let zipentry = zipReader.getEntry(name);
+        let path = OS.Path.join(aDir.path, ...name.split("/"));
+
+        if (zipentry.isDirectory) {
+          try {
+            yield OS.File.makeDir(path);
+          }
+          catch (e) {
+            dump("extractFilesAsync: failed to create directory " + path + "\n");
+            throw e;
+          }
+        }
+        else {
+          let options = { unixMode: zipentry.permissions | FileUtils.PERMS_FILE };
+          try {
+            let file = yield OS.File.open(path, { truncate: true }, options);
+            if (zipentry.realSize == 0)
+              yield file.close();
+            else
+              yield saveStreamAsync(path, zipReader.getInputStream(entryName), file);
+          }
+          catch (e) {
+            dump("extractFilesAsync: failed to extract file " + path + "\n");
+            throw e;
+          }
+        }
+      }
+
+      zipReader.close();
+    }).then(null, (e) => {
+      zipReader.close();
+      throw e;
+    });
+  },
+
+  /**
+   * Extracts files from a ZIP file into a directory.
+   *
+   * @param  aZipFile
+   *         The source ZIP file that contains the add-on.
+   * @param  aDir
+   *         The nsIFile to extract to.
+   */
+  extractFiles: function ZipUtils_extractFiles(aZipFile, aDir) {
+    function getTargetFile(aDir, entry) {
+      let target = aDir.clone();
+      entry.split("/").forEach(function(aPart) {
+        target.append(aPart);
+      });
+      return target;
+    }
+
+    let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
+                    createInstance(Ci.nsIZipReader);
+    zipReader.open(aZipFile);
+
+    try {
+      // create directories first
+      let entries = zipReader.findEntries("*/");
+      while (entries.hasMore()) {
+        let entryName = entries.getNext();
+        let target = getTargetFile(aDir, entryName);
+        if (!target.exists()) {
+          try {
+            target.create(Ci.nsIFile.DIRECTORY_TYPE,
+                          FileUtils.PERMS_DIRECTORY);
+          }
+          catch (e) {
+            dump("extractFiles: failed to create target directory for extraction file = " + target.path + "\n");
+          }
+        }
+      }
+
+      entries = zipReader.findEntries(null);
+      while (entries.hasMore()) {
+        let entryName = entries.getNext();
+        let target = getTargetFile(aDir, entryName);
+        if (target.exists())
+          continue;
+
+        zipReader.extract(entryName, target);
+        try {
+          target.permissions |= FileUtils.PERMS_FILE;
+        }
+        catch (e) {
+          dump("Failed to set permissions " + aPermissions.toString(8) + " on " + target.path + "\n");
+        }
+      }
+    }
+    finally {
+      zipReader.close();
+    }
+  }
+
+};
--- a/toolkit/modules/moz.build
+++ b/toolkit/modules/moz.build
@@ -46,16 +46,17 @@ EXTRA_JS_MODULES += [
     'sessionstore/XPathGenerator.jsm',
     'ShortcutUtils.jsm',
     'Sntp.jsm',
     'SpatialNavigation.jsm',
     'Sqlite.jsm',
     'Task.jsm',
     'TelemetryTimestamps.jsm',
     'Timer.jsm',
+    'ZipUtils.jsm',
 ]
 
 EXTRA_PP_JS_MODULES += [
     'CertUtils.jsm',
     'ResetProfile.jsm',
     'Services.jsm',
     'Troubleshoot.jsm',
     'UpdateChannel.jsm',
new file mode 100644
--- /dev/null
+++ b/toolkit/modules/tests/xpcshell/test_ZipUtils.js
@@ -0,0 +1,64 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const ARCHIVE = "zips/zen.zip";
+const SUBDIR = "zen";
+const ENTRIES = ["beyond.txt", "waterwood.txt"];
+
+Components.utils.import("resource://gre/modules/ZipUtils.jsm");
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+
+
+const archive = do_get_file(ARCHIVE, false);
+const dir = do_get_profile().clone();
+dir.append("test_ZipUtils");
+
+function run_test() {
+  run_next_test();
+}
+
+function ensureExtracted(target) {
+  target.append(SUBDIR);
+  do_check_true(target.exists());
+
+  for (let i = 0; i < ENTRIES.length; i++) {
+    let entry = target.clone();
+    entry.append(ENTRIES[i]);
+    do_print("ENTRY " + entry.path);
+    do_check_true(entry.exists());
+  }
+}
+
+
+add_task(function test_extractFiles() {
+  let target = dir.clone();
+  target.append("test_extractFiles");
+
+  try {
+    ZipUtils.extractFiles(archive, target);
+  } catch(e) {
+    do_throw("Failed to extract synchronously!");
+  }
+
+  ensureExtracted(target);
+});
+
+add_task(function test_extractFilesAsync() {
+  let target = dir.clone();
+  target.append("test_extractFilesAsync");
+  target.create(Components.interfaces.nsIFile.DIRECTORY_TYPE,
+    FileUtils.PERMS_DIRECTORY);
+
+  yield ZipUtils.extractFilesAsync(archive, target).then(
+    function success() {
+      do_print("SUCCESS");
+      ensureExtracted(target);
+    },
+    function failure() {
+      do_print("FAILURE");
+      do_throw("Failed to extract asynchronously!");
+    }
+  );
+});
--- a/toolkit/modules/tests/xpcshell/xpcshell.ini
+++ b/toolkit/modules/tests/xpcshell/xpcshell.ini
@@ -1,15 +1,16 @@
 [DEFAULT]
 head =
 tail =
 support-files =
   propertyLists/bug710259_propertyListBinary.plist
   propertyLists/bug710259_propertyListXML.plist
   chromeappsstore.sqlite
+  zips/zen.zip
 
 [test_AsyncShutdown.js]
 [test_DeferredTask.js]
 [test_dict.js]
 [test_FileUtils.js]
 [test_Http.js]
 [test_Log.js]
 [test_PermissionsUtils.js]
@@ -17,9 +18,9 @@ support-files =
 [test_Promise.js]
 [test_propertyListsUtils.js]
 [test_readCertPrefs.js]
 [test_Services.js]
 [test_sqlite.js]
 [test_task.js]
 [test_TelemetryTimestamps.js]
 [test_timer.js]
-
+[test_ZipUtils.js]
new file mode 100644
index 0000000000000000000000000000000000000000..372010a3cc72306dd133841b48cd4f65337944d2
GIT binary patch
literal 1058
zc$^FHW@h1H0D;qG=`IXNfQ3PZp(-^`KQx4sfw}j*WN;r4msW5yFtWU021<j70HAIW
zpjr-wtw7z9I{H2*fV>+(%m>w-lv<gem!elvQ35t64QNIhra7{Gd$X7vMOuGXc3oZ?
z7_SieW^r%AUW@W_wrkb~_NU5{a)OQ=YUogs@JJS(XtJqS|M9Lj(L1s$E#&W8#-8Hw
z=9y9^AvN1ean;RjF?wD9gG*;H|K*IVaFvKGl2fZ|JLvsj$=aBX9kRmex`n%I?wT);
zo)`W)|B77`*W(Vu+$AgC<`|ewvs$#HPGtY<qL0CKa*gvodKF(1P^(z3YRi7l^mKUP
z`Shpn*56v+^;xoFb-$Tx>Xio@{emaW$XY)kLTTOK#@Zz}OOG%k!ecuyJdPUk>eMkZ
zFuVt1L1=iCCzhlZmFMRpf+PzV9EF%c;v0B2OT<xN-}m4eet~bQ-<~-7GoF9z^WM+d
z|N4cykFB=<;b>#=6zE-(aZSd|YtaPW-3M7dW^PMQyMHLFSt-OhwDjuttKp_o&Y9lo
z`xwIZben<BidFxPxa<^o``hyNuYW75E9Ll~9Z>Gx<fykbGvSMeqR1&tkGg3NnoeO8
zg6GvQ5q<klXWpMz@v+D5-v0NBdqeC5|5&316GE5jFeR$=Hg0=kXtDiior2l?{p!C<
z#QObjUypB8@0q9eSSu}Iite&M(@$+ZzvQ&5eeLmU_x5z!hrix^`AS=l*E+AK62jHY
z7wck5POtv=X_xhmm&=W>9o=&N|K$az(%q)YnRqGp^6q@*cp)ucP`o>@RQh~);kNlK
zno~k<D{TGx;AUV(vW=RRNC>;fk1i*sQ`z!ti)PQyEwfuwA9JKT^ZT??+dPNJu-Oe=
zYCdxnYL?FB+iDV^KjpN^dL8B0Tlx2>ZM<olwSM}?9~*3cUOPAKz=!vu>My(aIehYp
zo(7dInLF!)E&C($0B=SnIc8iLLjuYYVEF3@q9NIY6_QObvIxi|T$u!75-@!*Y-zNE
snuMHXSRq*kkC~VW4cSaTMh0xjjun#ZFpOno18HRj!mmJDoEgLe0A|{NZU6uP
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -18,16 +18,18 @@ Components.utils.import("resource://gre/
 XPCOMUtils.defineLazyModuleGetter(this, "AddonRepository",
                                   "resource://gre/modules/addons/AddonRepository.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ChromeManifestParser",
                                   "resource://gre/modules/ChromeManifestParser.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
                                   "resource://gre/modules/LightweightThemeManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
                                   "resource://gre/modules/FileUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ZipUtils",
+                                  "resource://gre/modules/ZipUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PermissionsUtils",
                                   "resource://gre/modules/PermissionsUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
                                   "resource://gre/modules/Promise.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Task",
                                   "resource://gre/modules/Task.jsm");
@@ -107,19 +109,16 @@ const KEY_APP_SYSTEM_USER             = 
 const NOTIFICATION_FLUSH_PERMISSIONS  = "flush-pending-permissions";
 const XPI_PERMISSION                  = "install";
 
 const RDFURI_INSTALL_MANIFEST_ROOT    = "urn:mozilla:install-manifest";
 const PREFIX_NS_EM                    = "http://www.mozilla.org/2004/em-rdf#";
 
 const TOOLKIT_ID                      = "toolkit@mozilla.org";
 
-// The maximum amount of file data to buffer at a time during file extraction
-const EXTRACTION_BUFFER               = 1024 * 512;
-
 // The value for this is in Makefile.in
 #expand const DB_SCHEMA                       = __MOZ_EXTENSIONS_DB_SCHEMA__;
 
 // Properties that exist in the install manifest
 const PROP_METADATA      = ["id", "version", "type", "internalName", "updateURL",
                             "updateKey", "optionsURL", "optionsType", "aboutURL",
                             "iconURL", "icon64URL"];
 const PROP_LOCALE_SINGLE = ["name", "description", "creator", "homepageURL"];
@@ -1124,209 +1123,16 @@ function getTemporaryFile() {
   let random = Math.random().toString(36).replace(/0./, '').substr(-3);
   file.append("tmp-" + random + ".xpi");
   file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, FileUtils.PERMS_FILE);
 
   return file;
 }
 
 /**
- * Asynchronously writes data from an nsIInputStream to an OS.File instance.
- * The source stream and OS.File are closed regardless of whether the operation
- * succeeds or fails.
- * Returns a promise that will be resolved when complete.
- *
- * @param  aPath
- *         The name of the file being extracted for logging purposes.
- * @param  aStream
- *         The source nsIInputStream.
- * @param  aFile
- *         The open OS.File instance to write to.
- */
-function saveStreamAsync(aPath, aStream, aFile) {
-  let deferred = Promise.defer();
-
-  // Read the input stream on a background thread
-  let sts = Cc["@mozilla.org/network/stream-transport-service;1"].
-            getService(Ci.nsIStreamTransportService);
-  let transport = sts.createInputTransport(aStream, -1, -1, true);
-  let input = transport.openInputStream(0, 0, 0)
-                       .QueryInterface(Ci.nsIAsyncInputStream);
-  let source = Cc["@mozilla.org/binaryinputstream;1"].
-               createInstance(Ci.nsIBinaryInputStream);
-  source.setInputStream(input);
-
-  let data = Uint8Array(EXTRACTION_BUFFER);
-
-  function readFailed(error) {
-    try {
-      aStream.close();
-    }
-    catch (e) {
-      logger.error("Failed to close JAR stream for " + aPath);
-    }
-
-    aFile.close().then(function() {
-      deferred.reject(error);
-    }, function(e) {
-      logger.error("Failed to close file for " + aPath);
-      deferred.reject(error);
-    });
-  }
-
-  function readData() {
-    try {
-      let count = Math.min(source.available(), data.byteLength);
-      source.readArrayBuffer(count, data.buffer);
-
-      aFile.write(data, { bytes: count }).then(function() {
-        input.asyncWait(readData, 0, 0, Services.tm.currentThread);
-      }, readFailed);
-    }
-    catch (e if e.result == Cr.NS_BASE_STREAM_CLOSED) {
-      deferred.resolve(aFile.close());
-    }
-    catch (e) {
-      readFailed(e);
-    }
-  }
-
-  input.asyncWait(readData, 0, 0, Services.tm.currentThread);
-
-  return deferred.promise;
-}
-
-/**
- * Asynchronously extracts files from a ZIP file into a directory.
- * Returns a promise that will be resolved when the extraction is complete.
- *
- * @param  aZipFile
- *         The source ZIP file that contains the add-on.
- * @param  aDir
- *         The nsIFile to extract to.
- */
-function extractFilesAsync(aZipFile, aDir) {
-  let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
-                  createInstance(Ci.nsIZipReader);
-
-  try {
-    zipReader.open(aZipFile);
-  }
-  catch (e) {
-    return Promise.reject(e);
-  }
-
-  return Task.spawn(function() {
-    // Get all of the entries in the zip and sort them so we create directories
-    // before files
-    let entries = zipReader.findEntries(null);
-    let names = [];
-    while (entries.hasMore())
-      names.push(entries.getNext());
-    names.sort();
-
-    for (let name of names) {
-      let entryName = name;
-      let zipentry = zipReader.getEntry(name);
-      let path = OS.Path.join(aDir.path, ...name.split("/"));
-
-      if (zipentry.isDirectory) {
-        try {
-          yield OS.File.makeDir(path);
-        }
-        catch (e) {
-          logger.error("extractFilesAsync: failed to create directory " + path, e);
-          throw e;
-        }
-      }
-      else {
-        let options = { unixMode: zipentry.permissions | FileUtils.PERMS_FILE };
-        try {
-          let file = yield OS.File.open(path, { truncate: true }, options);
-          if (zipentry.realSize == 0)
-            yield file.close();
-          else
-            yield saveStreamAsync(path, zipReader.getInputStream(entryName), file);
-        }
-        catch (e) {
-          logger.error("extractFilesAsync: failed to extract file " + path, e);
-          throw e;
-        }
-      }
-    }
-
-    zipReader.close();
-  }).then(null, (e) => {
-    zipReader.close();
-    throw e;
-  });
-}
-
-/**
- * Extracts files from a ZIP file into a directory.
- *
- * @param  aZipFile
- *         The source ZIP file that contains the add-on.
- * @param  aDir
- *         The nsIFile to extract to.
- */
-function extractFiles(aZipFile, aDir) {
-  function getTargetFile(aDir, entry) {
-    let target = aDir.clone();
-    entry.split("/").forEach(function(aPart) {
-      target.append(aPart);
-    });
-    return target;
-  }
-
-  let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"].
-                  createInstance(Ci.nsIZipReader);
-  zipReader.open(aZipFile);
-
-  try {
-    // create directories first
-    let entries = zipReader.findEntries("*/");
-    while (entries.hasMore()) {
-      var entryName = entries.getNext();
-      let target = getTargetFile(aDir, entryName);
-      if (!target.exists()) {
-        try {
-          target.create(Ci.nsIFile.DIRECTORY_TYPE,
-                        FileUtils.PERMS_DIRECTORY);
-        }
-        catch (e) {
-          logger.error("extractFiles: failed to create target directory for " +
-                "extraction file = " + target.path, e);
-        }
-      }
-    }
-
-    entries = zipReader.findEntries(null);
-    while (entries.hasMore()) {
-      let entryName = entries.getNext();
-      let target = getTargetFile(aDir, entryName);
-      if (target.exists())
-        continue;
-
-      zipReader.extract(entryName, target);
-      try {
-        target.permissions |= FileUtils.PERMS_FILE;
-      }
-      catch (e) {
-        logger.warn("Failed to set permissions " + aPermissions.toString(8) + " on " +
-             target.path, e);
-      }
-    }
-  }
-  finally {
-    zipReader.close();
-  }
-}
-
-/**
  * Verifies that a zip file's contents are all signed by the same principal.
  * Directory entries and anything in the META-INF directory are not checked.
  *
  * @param  aZip
  *         A nsIZipReader to check
  * @param  aPrincipal
  *         The nsIPrincipal to compare against
  * @return true if all the contents that should be signed were signed by the
@@ -2420,17 +2226,17 @@ var XPIProvider = {
               targetDir.create(Ci.nsIFile.DIRECTORY_TYPE, FileUtils.PERMS_DIRECTORY);
             }
             catch (e) {
               logger.error("Failed to create staging directory for add-on " + addon.id, e);
               continue;
             }
 
             try {
-              extractFiles(stagedXPI, targetDir);
+              ZipUtils.extractFiles(stagedXPI, targetDir);
             }
             catch (e) {
               logger.error("Failed to extract staged XPI for add-on " + addon.id + " in " +
                     aLocation.name, e);
             }
           }
           else {
             try {
@@ -5546,17 +5352,17 @@ AddonInstall.prototype = {
 
       // First stage the file regardless of whether restarting is necessary
       if (this.addon.unpack || Prefs.getBoolPref(PREF_XPI_UNPACK, false)) {
         logger.debug("Addon " + this.addon.id + " will be installed as " +
             "an unpacked directory");
         stagedAddon.append(this.addon.id);
         yield removeAsync(stagedAddon);
         yield OS.File.makeDir(stagedAddon.path);
-        yield extractFilesAsync(this.file, stagedAddon);
+        yield ZipUtils.extractFilesAsync(this.file, stagedAddon);
         installedUnpacked = 1;
       }
       else {
         logger.debug("Addon " + this.addon.id + " will be installed as " +
             "a packed xpi");
         stagedAddon.append(this.addon.id + ".xpi");
         yield removeAsync(stagedAddon);
         yield OS.File.copy(this.file.path, stagedAddon.path);
--- a/toolkit/themes/linux/global/jar.mn
+++ b/toolkit/themes/linux/global/jar.mn
@@ -47,10 +47,10 @@ toolkit.jar:
 +  skin/classic/global/icons/loading_16.png                    (icons/loading_16.png)
 +  skin/classic/global/icons/panelarrow-horizontal.svg         (icons/panelarrow-horizontal.svg)
 +  skin/classic/global/icons/panelarrow-vertical.svg           (icons/panelarrow-vertical.svg)
 +  skin/classic/global/icons/resizer.png                       (icons/resizer.png)
 +  skin/classic/global/icons/sslWarning.png                    (icons/sslWarning.png)
 +  skin/classic/global/icons/wrap.png                          (icons/wrap.png)
 +  skin/classic/global/icons/webapps-16.png                    (icons/webapps-16.png)
 +  skin/classic/global/icons/webapps-64.png                    (icons/webapps-64.png)
-   skin/classic/global/menu/menu-check.png                     (../../shared/menu-check.png)
+   skin/classic/global/menu/shared-menu-check.png              (../../shared/menu-check.png)
 +  skin/classic/global/toolbar/spring.png                      (toolbar/spring.png)
--- a/toolkit/themes/osx/global/jar.mn
+++ b/toolkit/themes/osx/global/jar.mn
@@ -176,18 +176,20 @@ toolkit.jar:
   skin/classic/global/media/volume-empty.png                         (media/volume-empty.png)
   skin/classic/global/media/volume-empty@2x.png                      (media/volume-empty@2x.png)
   skin/classic/global/media/volume-full.png                          (media/volume-full.png)
   skin/classic/global/media/volume-full@2x.png                       (media/volume-full@2x.png)
   skin/classic/global/media/clicktoplay-bgtexture.png                (media/clicktoplay-bgtexture.png)
   skin/classic/global/media/videoClickToPlayButton.svg               (media/videoClickToPlayButton.svg)
   skin/classic/global/menu/menu-arrow.png                            (menu/menu-arrow.png)
   skin/classic/global/menu/menu-arrow@2x.png                         (menu/menu-arrow@2x.png)
-  skin/classic/global/menu/menu-check.png                            (../../shared/menu-check.png)
-  skin/classic/global/menu/menu-check@2x.png                         (../../shared/menu-check@2x.png)
+  skin/classic/global/menu/menu-check.png                            (menu/menu-check.png)
+  skin/classic/global/menu/menu-check@2x.png                         (menu/menu-check@2x.png)
+  skin/classic/global/menu/shared-menu-check.png                     (../../shared/menu-check.png)
+  skin/classic/global/menu/shared-menu-check@2x.png                  (../../shared/menu-check@2x.png)
   skin/classic/global/scale/scale-tray-horiz.gif                     (scale/scale-tray-horiz.gif)
   skin/classic/global/scale/scale-tray-vert.gif                      (scale/scale-tray-vert.gif)
   skin/classic/global/splitter/dimple.png                            (splitter/dimple.png)
   skin/classic/global/splitter/grip-bottom.gif                       (splitter/grip-bottom.gif)
   skin/classic/global/splitter/grip-top.gif                          (splitter/grip-top.gif)
   skin/classic/global/splitter/grip-left.gif                         (splitter/grip-left.gif)
   skin/classic/global/splitter/grip-right.gif                        (splitter/grip-right.gif)
   skin/classic/global/toolbar/spring.png                             (toolbar/spring.png)
--- a/toolkit/themes/windows/global/jar.mn
+++ b/toolkit/themes/windows/global/jar.mn
@@ -163,17 +163,17 @@ toolkit.jar:
         skin/classic/global/media/scrubberThumbWide.png          (media/scrubberThumbWide.png)
         skin/classic/global/media/throbber.png                   (media/throbber.png)
         skin/classic/global/media/stalled.png                    (media/stalled.png)
         skin/classic/global/media/volume-empty.png               (media/volume-empty.png)
         skin/classic/global/media/volume-full.png                (media/volume-full.png)
         skin/classic/global/media/error.png                      (media/error.png)
         skin/classic/global/media/clicktoplay-bgtexture.png      (media/clicktoplay-bgtexture.png)
         skin/classic/global/media/videoClickToPlayButton.svg     (media/videoClickToPlayButton.svg)
-        skin/classic/global/menu/menu-check.png                  (../../shared/menu-check.png)
+        skin/classic/global/menu/shared-menu-check.png           (../../shared/menu-check.png)
         skin/classic/global/printpreview/arrow-left.png          (printpreview/arrow-left.png)
         skin/classic/global/printpreview/arrow-left-end.png      (printpreview/arrow-left-end.png)
         skin/classic/global/printpreview/arrow-right.png         (printpreview/arrow-right.png)
         skin/classic/global/printpreview/arrow-right-end.png     (printpreview/arrow-right-end.png)
         skin/classic/global/radio/radio-check.gif                (radio/radio-check.gif)
         skin/classic/global/radio/radio-check-dis.gif            (radio/radio-check-dis.gif)
         skin/classic/global/scrollbar/slider.gif                 (scrollbar/slider.gif)
         skin/classic/global/splitter/grip-bottom.gif             (splitter/grip-bottom.gif)
@@ -346,17 +346,17 @@ toolkit.jar:
         skin/classic/aero/global/media/scrubberThumbWide.png             (media/scrubberThumbWide.png)
         skin/classic/aero/global/media/throbber.png                      (media/throbber.png)
         skin/classic/aero/global/media/stalled.png                       (media/stalled.png)
         skin/classic/aero/global/media/volume-empty.png                  (media/volume-empty.png)
         skin/classic/aero/global/media/volume-full.png                   (media/volume-full.png)
         skin/classic/aero/global/media/error.png                         (media/error.png)
         skin/classic/aero/global/media/clicktoplay-bgtexture.png         (media/clicktoplay-bgtexture.png)
         skin/classic/aero/global/media/videoClickToPlayButton.svg        (media/videoClickToPlayButton.svg)
-        skin/classic/aero/global/menu/menu-check.png                     (../../shared/menu-check.png)
+        skin/classic/aero/global/menu/shared-menu-check.png              (../../shared/menu-check.png)
         skin/classic/aero/global/printpreview/arrow-left.png             (printpreview/arrow-left-aero.png)
         skin/classic/aero/global/printpreview/arrow-left-end.png         (printpreview/arrow-left-end-aero.png)
         skin/classic/aero/global/printpreview/arrow-right.png            (printpreview/arrow-right-aero.png)
         skin/classic/aero/global/printpreview/arrow-right-end.png        (printpreview/arrow-right-end-aero.png)
         skin/classic/aero/global/radio/radio-check.gif                   (radio/radio-check.gif)
         skin/classic/aero/global/radio/radio-check-dis.gif               (radio/radio-check-dis.gif)
         skin/classic/aero/global/scrollbar/slider.gif                    (scrollbar/slider.gif)
         skin/classic/aero/global/splitter/grip-bottom.gif                (splitter/grip-bottom.gif)