Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 18 Mar 2016 16:10:42 +0100
changeset 289360 5096e12520cd2ac31f554d7b2d7df40671f992ec
parent 289359 476c67fdc36c72c4d7efac7bb4506625093f29b1 (current diff)
parent 289323 9c5d494d05485aebf3fedf649abc0e7ae9d2dcf2 (diff)
child 289361 dc66a53a75fdd729307c7c675d0141d7392da994
push id30102
push userryanvm@gmail.com
push dateSat, 19 Mar 2016 15:23:17 +0000
treeherdermozilla-central@720fb3d55e28 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
browser/base/content/test/social/browser_social_multiworker.js
browser/base/content/test/social/browser_social_perwindowPB.js
browser/base/content/test/social/browser_social_workercrash.js
browser/base/content/test/social/social_window.html
browser/base/content/test/social/social_worker.js
devtools/client/framework/toolbox-options.xul
devtools/client/shared/components/test/mochitest/test_frame_02.html
dom/workers/Navigator.cpp
dom/workers/Navigator.h
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/ReaderModeUtils.java
mobile/android/base/java/org/mozilla/gecko/ReadingListHelper.java
mobile/android/thirdparty/com/nineoldandroids/animation/Animator.java
mobile/android/thirdparty/com/nineoldandroids/animation/AnimatorInflater.java
mobile/android/thirdparty/com/nineoldandroids/animation/AnimatorListenerAdapter.java
mobile/android/thirdparty/com/nineoldandroids/animation/AnimatorSet.java
mobile/android/thirdparty/com/nineoldandroids/animation/ArgbEvaluator.java
mobile/android/thirdparty/com/nineoldandroids/animation/FloatEvaluator.java
mobile/android/thirdparty/com/nineoldandroids/animation/FloatKeyframeSet.java
mobile/android/thirdparty/com/nineoldandroids/animation/IntEvaluator.java
mobile/android/thirdparty/com/nineoldandroids/animation/IntKeyframeSet.java
mobile/android/thirdparty/com/nineoldandroids/animation/Keyframe.java
mobile/android/thirdparty/com/nineoldandroids/animation/KeyframeSet.java
mobile/android/thirdparty/com/nineoldandroids/animation/ObjectAnimator.java
mobile/android/thirdparty/com/nineoldandroids/animation/PreHoneycombCompat.java
mobile/android/thirdparty/com/nineoldandroids/animation/PropertyValuesHolder.java
mobile/android/thirdparty/com/nineoldandroids/animation/TimeAnimator.java
mobile/android/thirdparty/com/nineoldandroids/animation/TypeEvaluator.java
mobile/android/thirdparty/com/nineoldandroids/animation/ValueAnimator.java
mobile/android/thirdparty/com/nineoldandroids/util/FloatProperty.java
mobile/android/thirdparty/com/nineoldandroids/util/IntProperty.java
mobile/android/thirdparty/com/nineoldandroids/util/NoSuchPropertyException.java
mobile/android/thirdparty/com/nineoldandroids/util/Property.java
mobile/android/thirdparty/com/nineoldandroids/util/ReflectiveProperty.java
mobile/android/thirdparty/com/nineoldandroids/view/ViewHelper.java
mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimator.java
mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimatorHC.java
mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimatorICS.java
mobile/android/thirdparty/com/nineoldandroids/view/ViewPropertyAnimatorPreHC.java
mobile/android/thirdparty/com/nineoldandroids/view/animation/AnimatorProxy.java
toolkit/components/social/FrameWorker.jsm
toolkit/components/social/FrameWorkerContent.js
toolkit/components/social/MessagePortBase.jsm
toolkit/components/social/MessagePortWorker.js
toolkit/components/social/WorkerAPI.jsm
toolkit/components/social/test/browser/.eslintrc
toolkit/components/social/test/browser/browser.ini
toolkit/components/social/test/browser/browser_SocialProvider.js
toolkit/components/social/test/browser/browser_frameworker.js
toolkit/components/social/test/browser/browser_frameworker_sandbox.js
toolkit/components/social/test/browser/browser_notifications.js
toolkit/components/social/test/browser/browser_workerAPI.js
toolkit/components/social/test/browser/data.json
toolkit/components/social/test/browser/echo.sjs
toolkit/components/social/test/browser/eventsource.resource
toolkit/components/social/test/browser/eventsource.resource^headers^
toolkit/components/social/test/browser/head.js
toolkit/components/social/test/browser/relative_import.js
toolkit/components/social/test/browser/worker_eventsource.js
toolkit/components/social/test/browser/worker_relative.js
toolkit/components/social/test/browser/worker_social.js
toolkit/components/social/test/browser/worker_xhr.js
toolkit/components/viewsource/test/test_428653.xul
toolkit/content/tests/chrome/test_mousecapture.xul
--- a/.eslintignore
+++ b/.eslintignore
@@ -85,32 +85,51 @@ devtools/client/eyedropper/**
 devtools/client/framework/**
 # devtools/client/inspector/shared/*.js files are eslint-clean, so they aren't
 # included in the ignore list.
 devtools/client/inspector/computed/**
 devtools/client/inspector/fonts/**
 devtools/client/inspector/shared/test/**
 devtools/client/inspector/test/**
 devtools/client/inspector/*.js
-devtools/client/jsonview/**
+devtools/client/jsonview/lib/**
 devtools/client/memory/**
-devtools/client/netmonitor/**
+devtools/client/netmonitor/test/**
+devtools/client/netmonitor/har/test/**
 devtools/client/performance/**
 devtools/client/projecteditor/**
 devtools/client/promisedebugger/**
 devtools/client/responsivedesign/**
 devtools/client/scratchpad/**
 devtools/client/shadereditor/**
 devtools/client/shared/**
 devtools/client/sourceeditor/**
 devtools/client/webaudioeditor/**
 devtools/client/webconsole/**
 devtools/client/webide/**
 devtools/server/**
-devtools/shared/**
+devtools/shared/*.js
+devtools/shared/*.jsm
+devtools/shared/apps/**
+devtools/shared/client/**
+devtools/shared/discovery/**
+devtools/shared/gcli/**
+devtools/shared/heapsnapshot/**
+devtools/shared/inspector/**
+devtools/shared/layout/**
+devtools/shared/locales/**
+devtools/shared/performance/**
+devtools/shared/qrcode/**
+devtools/shared/security/**
+devtools/shared/shims/**
+devtools/shared/tests/**
+devtools/shared/touch/**
+devtools/shared/transport/**
+devtools/shared/webconsole/test/**
+devtools/shared/worker/**
 
 # Ignore devtools pre-processed files
 devtools/client/framework/toolbox-process-window.js
 devtools/client/performance/system.js
 devtools/client/webide/webide-prefs.js
 devtools/client/preferences/**
 
 # Ignore devtools third-party libs
--- a/addon-sdk/source/lib/sdk/system/xul-app.jsm
+++ b/addon-sdk/source/lib/sdk/system/xul-app.jsm
@@ -40,17 +40,18 @@ var platformVersion = exports.platformVe
 // Firefox, Minefield, Iceweasel, and Shiretoko all have the same
 // GUID.
 
 var ids = exports.ids = {
   Firefox: "{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
   Mozilla: "{86c18b42-e466-45a9-ae7a-9b95ba6f5640}",
   SeaMonkey: "{92650c4d-4b8e-4d2a-b7eb-24ecf4f6b63a}",
   Fennec: "{aa3c5121-dab2-40e2-81ca-7ea25febc110}",
-  Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}"
+  Thunderbird: "{3550f703-e582-4d05-9a08-453d09bdfdc6}",
+  Instantbird: "{33cb9019-c295-46dd-be21-8c4936574bee}"
 };
 
 function is(name) {
   if (!(name in ids))
     throw new Error("Unkown Mozilla Application: " + name);
   return ID == ids[name];
 };
 exports.is = is;
--- a/addon-sdk/source/modules/system/Startup.js
+++ b/addon-sdk/source/modules/system/Startup.js
@@ -13,17 +13,18 @@ const { XulApp } = Cu.import("resource:/
 
 const appStartupSrv = Cc["@mozilla.org/toolkit/app-startup;1"]
                        .getService(Ci.nsIAppStartup);
 
 const NAME2TOPIC = {
   'Firefox': 'sessionstore-windows-restored',
   'Fennec': 'sessionstore-windows-restored',
   'SeaMonkey': 'sessionstore-windows-restored',
-  'Thunderbird': 'mail-startup-done'
+  'Thunderbird': 'mail-startup-done',
+  'Instantbird': 'xul-window-visible'
 };
 
 var Startup = {
   initialized: !appStartupSrv.startingUp
 };
 var exports = Startup;
 
 var gOnceInitializedDeferred = defer();
--- a/browser/base/content/aboutSocialError.xhtml
+++ b/browser/base/content/aboutSocialError.xhtml
@@ -64,19 +64,16 @@
           document.getElementById("btnTryAgain").style.display = 'none';
           break;
         case "tryAgainOnly":
           //intentional fall-through
         case "tryAgain":
           config.tryAgainCallback = loadQueryURL;
           config.queryURL = url;
           break;
-        case "workerFailure":
-          config.tryAgainCallback = reloadProvider;
-          break;
         default:
           break;
       }
     }
 
     function setUpStrings() {
       let brandBundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
       let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
@@ -99,28 +96,16 @@
       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.
       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();
-        }
-      }
     }
 
     parseQueryString();
     setUpStrings();
   ]]></script>
 </html>
--- a/browser/base/content/browser-social.js
+++ b/browser/base/content/browser-social.js
@@ -60,27 +60,26 @@ SocialUI = {
     if (this._initialized) {
       return;
     }
     let mm = window.getGroupMessageManager("social");
     mm.loadFrameScript("chrome://browser/content/content.js", true);
     mm.loadFrameScript("chrome://browser/content/social-content.js", true);
 
     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: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.toast-notifications.enabled", this, false);
 
     CustomizableUI.addListener(this);
     SocialActivationListener.init();
+    messageManager.addMessageListener("Social:Notification", this);
 
     // 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);
 
     Social.init().then((update) => {
       if (update)
@@ -95,33 +94,41 @@ SocialUI = {
   // Called on window unload
   uninit: function SocialUI_uninit() {
     if (!this._initialized) {
       return;
     }
     SocialSidebar.saveWindowState();
 
     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: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.toast-notifications.enabled", this);
     CustomizableUI.removeListener(this);
     SocialActivationListener.uninit();
+    messageManager.removeMessageListener("Social:Notification", this);
 
     document.getElementById("viewSidebarMenu").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
     document.getElementById("social-statusarea-popup").removeEventListener("popupshowing", SocialSidebar.populateSidebarMenu, true);
 
     this._initialized = false;
   },
 
+  receiveMessage: function(aMessage) {
+    if (aMessage.name == "Social:Notification") {
+      let provider = Social._getProviderFromOrigin(aMessage.data.origin);
+      if (provider) {
+        provider.setAmbientNotification(aMessage.data.detail);
+      }
+    }
+  },
+
   observe: function SocialUI_observe(subject, topic, data) {
     switch (topic) {
       case "social:provider-enabled":
         SocialMarks.populateToolbarPalette();
         SocialStatus.populateToolbarPalette();
         break;
       case "social:provider-disabled":
         SocialMarks.removeProvider(data);
@@ -144,27 +151,16 @@ SocialUI = {
         // is correctly reflected in any UI and the multi-provider menu
       case "social:providers-changed":
         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 && SocialSidebar.provider && SocialSidebar.provider.origin == data) {
-          SocialSidebar.loadFrameworkerFailure();
-        }
-        break;
       case "nsPref:changed":
         if (data == "social.toast-notifications.enabled") {
           SocialSidebar.updateToggleNotifications();
         }
         break;
     }
   },
 
@@ -213,27 +209,22 @@ SocialUI = {
     // multiple times...
     delete this._chromeless;
     this._chromeless = chromeless;
     return chromeless;
   },
 
   get enabled() {
     // Returns whether social is enabled *for this window*.
-    if (this._chromeless || PrivateBrowsingUtils.isWindowPrivate(window))
+    if (this._chromeless)
       return false;
     return Social.providers.length > 0;
   },
 
   canShareOrMarkPage: function(aURI) {
-    // Bug 898706 we do not enable social in private sessions since frameworker
-    // would be shared between private and non-private windows
-    if (PrivateBrowsingUtils.isWindowPrivate(window))
-      return false;
-
     return (aURI && (aURI.schemeIs('http') || aURI.schemeIs('https')));
   },
 
   onCustomizeEnd: function(aWindow) {
     if (aWindow != window)
       return;
     // customization mode gets buttons out of sync with command updating, fix
     // the disabled state
@@ -282,20 +273,16 @@ SocialActivationListener = {
     // if the source if the message is the share panel, we do a one-click
     // installation. The source of activations is controlled by the
     // social.directories preference
     let options;
     if (browser == SocialShare.iframe && Services.prefs.getBoolPref("social.share.activationPanelEnabled")) {
       options = { bypassContentCheck: true, bypassInstallPanel: true };
     }
 
-    // If we are in PB mode, we silently do nothing (bug 829404 exists to
-    // do something sensible here...)
-    if (PrivateBrowsingUtils.isWindowPrivate(window))
-      return;
     Social.installProvider(data, function(manifest) {
       Social.activateFromOrigin(manifest.origin, function(provider) {
         if (provider.sidebarURL) {
           SocialSidebar.show(provider.origin);
         }
         if (provider.shareURL) {
           // Ensure that the share button is somewhere usable.
           // SocialShare.shareButton may return null if it is in the menu-panel
@@ -356,16 +343,17 @@ SocialFlyout = {
       return;
     // create and initialize the panel for this window
     let iframe = document.createElement("browser");
     iframe.setAttribute("type", "content");
     iframe.setAttribute("class", "social-panel-frame");
     iframe.setAttribute("flex", "1");
     iframe.setAttribute("message", "true");
     iframe.setAttribute("messagemanagergroup", "social");
+    iframe.setAttribute("disablehistory", "true");
     iframe.setAttribute("tooltip", "aHTMLTooltip");
     iframe.setAttribute("context", "contentAreaContextMenu");
     iframe.setAttribute("origin", SocialSidebar.provider.origin);
     panel.appendChild(iframe);
     // the xbl bindings for the iframe probably don't exist yet, so we can't
     // access iframe.messageManager directly - but can get at it with this dance.
     let mm = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
     mm.sendAsyncMessage("Social:SetErrorURL",
@@ -383,36 +371,33 @@ SocialFlyout = {
 
   onShown: function(aEvent) {
     let panel = this.panel;
     let iframe = this.iframe;
     this._dynamicResizer = new DynamicResizeWatcher();
     iframe.docShellIsActive = true;
     if (iframe.contentDocument.readyState == "complete") {
       this._dynamicResizer.start(panel, iframe);
-      this.dispatchPanelEvent("socialFrameShow");
     } else {
       // first time load, wait for load and dispatch after load
       iframe.addEventListener("load", function panelBrowserOnload(e) {
         iframe.removeEventListener("load", panelBrowserOnload, true);
         setTimeout(function() {
           if (SocialFlyout._dynamicResizer) { // may go null if hidden quickly
             SocialFlyout._dynamicResizer.start(panel, iframe);
-            SocialFlyout.dispatchPanelEvent("socialFrameShow");
           }
         }, 0);
       }, true);
     }
   },
 
   onHidden: function(aEvent) {
     this._dynamicResizer.stop();
     this._dynamicResizer = null;
     this.iframe.docShellIsActive = false;
-    this.dispatchPanelEvent("socialFrameHide");
   },
 
   load: function(aURL, cb) {
     if (!SocialSidebar.provider)
       return;
 
     this.panel.hidden = false;
     let iframe = this.iframe;
@@ -499,16 +484,17 @@ SocialShare = {
       return;
     this.panel.hidden = false;
     // create and initialize the panel for this window
     let iframe = document.createElement("browser");
     iframe.setAttribute("type", "content");
     iframe.setAttribute("class", "social-share-frame");
     iframe.setAttribute("context", "contentAreaContextMenu");
     iframe.setAttribute("tooltip", "aHTMLTooltip");
+    iframe.setAttribute("disablehistory", "true");
     iframe.setAttribute("disableglobalhistory", "true");
     iframe.setAttribute("flex", "1");
     iframe.setAttribute("message", "true");
     iframe.setAttribute("messagemanagergroup", "social");
     panel.lastChild.appendChild(iframe);
     let mm = iframe.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
     mm.sendAsyncMessage("Social:SetErrorURL",
                         { template: "about:socialerror?mode=compactInfo&origin=%{origin}&url=%{url}" });
@@ -590,21 +576,17 @@ SocialShare = {
     (this._currentAnchor || this.anchor).removeAttribute("open");
     this._currentAnchor = null;
     this.iframe.removeEventListener("click", this._onclick, true);
     this.iframe.setAttribute("src", "data:text/plain;charset=utf8,");
     // make sure that the frame is unloaded after it is hidden
     this.iframe.docShell.createAboutBlankContentViewer(null);
     this.currentShare = null;
     // share panel use is over, purge any history
-    if (this.iframe.sessionHistory) {
-      let purge = this.iframe.sessionHistory.count;
-      if (purge > 0)
-        this.iframe.sessionHistory.PurgeHistory(purge);
-    }
+    this.iframe.purgeSessionHistory();
   },
 
   sharePage: function(providerOrigin, graphData, target, anchor) {
     // if providerOrigin is undefined, we use the last-used provider, or the
     // current/default provider.  The provider selection in the share panel
     // will call sharePage with an origin for us to switch to.
     this._createFrame();
     let iframe = this.iframe;
@@ -703,21 +685,17 @@ SocialShare = {
 
         let evt = iframe.contentDocument.createEvent("CustomEvent");
         evt.initCustomEvent("OpenGraphData", true, true, JSON.stringify(pageData));
         iframe.contentDocument.documentElement.dispatchEvent(evt);
       }, true);
     }
     // if the user switched between share providers we do not want that history
     // available.
-    if (iframe.sessionHistory) {
-      let purge = iframe.sessionHistory.count;
-      if (purge > 0)
-        iframe.sessionHistory.PurgeHistory(purge);
-    }
+    iframe.purgeSessionHistory();
 
     // always ensure that origin belongs to the endpoint
     let uri = Services.io.newURI(shareEndpoint, null, null);
     iframe.setAttribute("origin", provider.origin);
     iframe.setAttribute("src", shareEndpoint);
     this._openPanel(anchor);
   },
 
@@ -852,19 +830,16 @@ SocialSidebar = {
 
   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;
     sbrowser.docShellIsActive = aEnabled;
-    let evt = sbrowser.contentDocument.createEvent("CustomEvent");
-    evt.initCustomEvent(aEnabled ? "socialFrameShow" : "socialFrameHide", true, true, {});
-    sbrowser.contentDocument.documentElement.dispatchEvent(evt);
   },
 
   updateToggleNotifications: function() {
     let command = document.getElementById("Social:ToggleNotifications");
     command.setAttribute("checked", Services.prefs.getBoolPref("social.toast-notifications.enabled"));
     command.setAttribute("hidden", !SocialUI.enabled);
   },
 
@@ -951,26 +926,16 @@ SocialSidebar = {
     // doesn't get destroyed until about:blank has loaded (which does not happen
     // as long as the element is hidden).
     sbrowser.docShell.createAboutBlankContentViewer(null);
     SocialFlyout.unload();
   },
 
   _unloadTimeoutId: 0,
 
-  loadFrameworkerFailure: function() {
-    if (this.provider && this.provider.errorState == "frameworker-error") {
-      // we have to explicitly load this error page since it is not being
-      // handled via the normal error page paths.
-      let sbrowser = document.getElementById("social-sidebar-browser");
-      sbrowser.setAttribute("src", "about:socialerror?mode=workerFailure&origin=" +
-                            encodeURIComponent(this.provider.origin));
-    }
-  },
-
   _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");
@@ -1242,18 +1207,18 @@ SocialStatus = {
     let button = widget.forWindow(window).node;
     if (button) {
       // we only grab the first notification, ignore all others
       let provider = Social._getProviderFromOrigin(origin);
       let icons = provider.ambientNotificationIcons;
       let iconNames = Object.keys(icons);
       let notif = icons[iconNames[0]];
 
-      // The image and tooltip need to be updated for both
-      // ambient notification and profile changes.
+      // The image and tooltip need to be updated for
+      // ambient notification changes.
       let iconURL = provider.icon32URL || provider.iconURL;
       let tooltiptext;
       if (!notif || !widget.areaType) {
         button.style.listStyleImage = "url(" + iconURL + ")";
         button.setAttribute("badge", "");
         button.setAttribute("aria-label", "");
         button.setAttribute("tooltiptext", provider.name);
         return;
--- a/browser/base/content/social-content.js
+++ b/browser/base/content/social-content.js
@@ -30,16 +30,25 @@ addEventListener("DOMTitleChanged", func
     sendAsyncMessage("Social:DOMTitleChanged", {
       title: e.target.title
     });
   }
   gDOMTitleChangedByUs = false;
 });
 var gHookedWindowCloseForPanelClose = false;
 
+addEventListener("Social:Notification", function(event) {
+  let frame = docShell.chromeEventHandler;
+  let origin = frame.getAttribute("origin");
+  sendAsyncMessage("Social:Notification", {
+    "origin": origin,
+    "detail": JSON.parse(event.detail)
+  });
+});
+
 // Error handling class used to listen for network errors in the social frames
 // and replace them with a social-specific error page
 SocialErrorListener = {
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMEventListener,
                                          Ci.nsIWebProgressListener,
                                          Ci.nsISupportsWeakReference,
                                          Ci.nsISupports]),
 
@@ -204,16 +213,18 @@ SocialErrorListener = {
     sendAsyncMessage("Social:ErrorPageNotify", {
         origin: origin,
         url: src
     });
   },
 
   onStateChange(aWebProgress, aRequest, aState, aStatus) {
     let failure = false;
+    if ((aState & Ci.nsIWebProgressListener.STATE_IS_REQUEST))
+      return;
     if ((aState & Ci.nsIWebProgressListener.STATE_STOP)) {
       if (aRequest instanceof Ci.nsIHttpChannel) {
         try {
           // Change the frame to an error page on 4xx (client errors)
           // and 5xx (server errors).  responseStatus throws if it is not set.
           failure = aRequest.responseStatus >= 400 &&
                     aRequest.responseStatus < 600;
         } catch (e) {
--- a/browser/base/content/socialchat.xml
+++ b/browser/base/content/socialchat.xml
@@ -147,16 +147,18 @@
 
       <property name="isActive">
         <getter>
           return this.content.docShellIsActive;
         </getter>
         <setter>
           this.content.docShellIsActive = !!val;
 
+          // Bug 1256431 to remove socialFrameShow/Hide from hello, keep this
+          // until that is complete.
           // let the chat frame know if it is being shown or hidden
           this.content.messageManager.sendAsyncMessage("Social:CustomEvent", {
             name: val ? "socialFrameShow" : "socialFrameHide"
           });
         </setter>
       </property>
 
       <field name="_remote">false</field>
--- a/browser/base/content/socialmarks.xml
+++ b/browser/base/content/socialmarks.xml
@@ -241,17 +241,17 @@
         ]]></body>
        </method>
 
       <method name="markCurrentPage">
         <parameter name="aOpenPanel"/>
         <body><![CDATA[
         // we always set the src on click if it has not been set for this tab,
         // but we only want to open the panel if it was previously annotated.
-        let openPanel = this.isMarked || aOpenPanel || !this.provider.haveLoggedInUser();
+        let openPanel = this.isMarked || aOpenPanel;
         let src = this.content.getAttribute("src");
         if (!src || src == "about:blank") {
           this.loadPanel();
         }
         if (openPanel)
           this.openPanel();
         ]]></body>
       </method>
@@ -286,23 +286,21 @@
 
       <method name="onShown">
         <body><![CDATA[
         // because the panel may be preloaded, we need to size the panel when
         // showing as well as after load
         let sizeSocialPanelToContent = Cu.import("resource:///modules/Social.jsm", {}).sizeSocialPanelToContent;
         if (!this._loading && this.contentDocument &&
             this.contentDocument.readyState == "complete") {
-          this.dispatchPanelEvent("socialFrameShow");
           if (this._useDynamicResizer)
             sizeSocialPanelToContent(this.panel, this.content);
         } else {
           let panelBrowserOnload = (e) => {
             this.content.removeEventListener("load", panelBrowserOnload, true);
-            this.dispatchPanelEvent("socialFrameShow");
             if (this._useDynamicResizer)
               sizeSocialPanelToContent(this.panel, this.content);
           };
           this.content.addEventListener("load", panelBrowserOnload, true);
         }
         ]]></body>
       </method>
 
@@ -345,17 +343,16 @@
       <handler event="popupshowing"><![CDATA[
         this._anchor.setAttribute("open", "true");
         this.content.addEventListener("click", this);
       ]]></handler>
       <handler event="popupshown"><![CDATA[
         this.onShown();
       ]]></handler>
       <handler event="popuphidden"><![CDATA[
-        this.dispatchPanelEvent("socialFrameHide");
         this._anchor.removeAttribute("open");
         this.update();
         this.content.removeEventListener("click", this);
       ]]></handler>
       <handler event="command"><![CDATA[
         this.markCurrentPage();
       ]]></handler>
     </handlers>
--- a/browser/base/content/test/social/browser.ini
+++ b/browser/base/content/test/social/browser.ini
@@ -18,18 +18,16 @@ support-files =
   social_chat.html
   social_crash_content_helper.js
   social_flyout.html
   social_mark.html
   social_panel.html
   social_postActivation.html
   social_sidebar.html
   social_sidebar_empty.html
-  social_window.html
-  social_worker.js
   unchecked.jpg
 
 [browser_aboutHome_activation.js]
 [browser_addons.js]
 [browser_blocklist.js]
 [browser_share.js]
 skip-if = true # bug 1115131
 [browser_social_activation.js]
@@ -39,17 +37,11 @@ skip-if = true # bug 1115131
 [browser_social_contextmenu.js]
 skip-if = (os == 'linux' && e10s) # Bug 1072669 context menu relies on target element
 [browser_social_errorPage.js]
 [browser_social_flyout.js]
 [browser_social_isVisible.js]
 [browser_social_marks.js]
 [browser_social_marks_context.js]
 [browser_social_multiprovider.js]
-[browser_social_multiworker.js]
-[browser_social_perwindowPB.js]
 [browser_social_sidebar.js]
 [browser_social_status.js]
-skip-if = true # Bug 1245800 'onoffline' and 'ononline' not defined JS errors
 [browser_social_window.js]
-[browser_social_workercrash.js]
-#skip-if = !crashreporter
-skip-if = true # Bug 1060813 - frequent leaks on all platforms
--- a/browser/base/content/test/social/browser_addons.js
+++ b/browser/base/content/test/social/browser_addons.js
@@ -13,17 +13,16 @@ var manifest2 = { // used for testing in
   sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar_empty.html",
   iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
   version: "1.0"
 };
 var manifestUpgrade = { // used for testing install
   name: "provider 3",
   origin: "https://test2.example.com",
   sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html",
-  workerURL: "https://test2.example.com/browser/browser/base/content/test/social/social_worker.js",
   iconURL: "https://test2.example.com/browser/browser/base/content/test/general/moz.png",
   version: "1.0"
 };
 
 function test() {
   waitForExplicitFinish();
 
   let prefname = getManifestPrefname(manifest);
@@ -206,77 +205,10 @@ var tests = {
       window: window
     }
     Social.installProvider(data, function(addonManifest) {
       Services.prefs.clearUserPref("social.directories");
       SocialService.enableProvider(addonManifest.origin, function(provider) {
         Social.uninstallProvider(addonManifest.origin);
       });
     });
-  },
-  testUpgradeProviderFromWorker: function(next) {
-    // add the provider, change the pref, add it again. The provider at that
-    // point should be upgraded
-    let activationURL = manifestUpgrade.origin + "/browser/browser/base/content/test/social/social_activate.html"
-    ensureEventFired(PopupNotifications.panel, "popupshown").then(() => {
-      let panel = document.getElementById("servicesInstall-notification");
-      info("servicesInstall-notification panel opened");
-      panel.button.click();
-    });
-
-    addTab(activationURL, function(tab) {
-      let doc = tab.linkedBrowser.contentDocument;
-      let installFrom = doc.nodePrincipal.origin;
-      Services.prefs.setCharPref("social.whitelist", installFrom);
-      let data = {
-        origin: installFrom,
-        url: doc.location.href,
-        manifest: manifestUpgrade,
-        window: window
-      }
-      Social.installProvider(data, function(addonManifest) {
-        SocialService.enableProvider(addonManifest.origin, function(provider) {
-          is(provider.manifest.version, 1, "manifest version is 1");
-
-          // watch for the provider-update and test the new version
-          SocialService.registerProviderListener(function providerListener(topic, origin, providers) {
-            if (topic != "provider-update")
-              return;
-            // The worker will have reloaded and the current provider instance
-            // disabled, removed from the provider list. We have a reference
-            // here, check it is is disabled.
-            is(provider.enabled, false, "old provider instance is disabled")
-            is(origin, addonManifest.origin, "provider manifest updated")
-            SocialService.unregisterProviderListener(providerListener);
-
-            // Get the new provider instance, fetch the manifest via workerapi
-            // and validate that data as well.
-            let p = Social._getProviderFromOrigin(origin);
-            is(p.manifest.version, 2, "manifest version is 2");
-            let port = p.getWorkerPort();
-            ok(port, "got a new port");
-            port.onmessage = function (e) {
-              let topic = e.data.topic;
-              switch (topic) {
-                case "social.manifest":
-                  let manifest = e.data.data;
-                  is(manifest.version, 2, "manifest version is 2");
-                  port.close();
-                  Social.uninstallProvider(origin, function() {
-                    Services.prefs.clearUserPref("social.whitelist");
-                    ensureBrowserTabClosed(tab).then(next);
-                  });
-                  break;
-              }
-            }
-            port.postMessage({topic: "test-init"});
-            port.postMessage({topic: "manifest-get"});
-
-          });
-
-          let port = provider.getWorkerPort();
-          port.postMessage({topic: "worker.update", data: true});
-
-        });
-      });
-    });
   }
 }
--- a/browser/base/content/test/social/browser_blocklist.js
+++ b/browser/base/content/test/social/browser_blocklist.js
@@ -8,24 +8,22 @@ var SocialService = Cu.import("resource:
 
 const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
 var blocklistURL = "http://example.com/browser/browser/base/content/test/social/blocklist.xml";
 
 var manifest = { // normal provider
   name: "provider ok",
   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"
 };
 var manifest_bad = { // normal provider
   name: "provider blocked",
   origin: "https://test1.example.com",
   sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html",
-  workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
   iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png"
 };
 
 function test() {
   waitForExplicitFinish();
   // turn on logging for nsBlocklistService.js
   Services.prefs.setBoolPref("extensions.logging.enabled", true);
   registerCleanupFunction(function () {
--- a/browser/base/content/test/social/browser_share.js
+++ b/browser/base/content/test/social/browser_share.js
@@ -1,17 +1,16 @@
 
 var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
 
 var baseURL = "https://example.com/browser/browser/base/content/test/social/";
 
 var manifest = { // normal provider
   name: "provider 1",
   origin: "https://example.com",
-  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",
   shareURL: "https://example.com/browser/browser/base/content/test/social/share.html"
 };
 var activationPage = "https://example.com/browser/browser/base/content/test/social/share_activate.html";
 
 function sendActivationEvent(subframe) {
   // hack Social.lastEventReceived so we don't hit the "too many events" check.
   Social.lastEventReceived = 0;
@@ -19,30 +18,40 @@ function sendActivationEvent(subframe) {
   // if our test has a frame, use it
   let button = doc.getElementById("activation");
   ok(!!button, "got the activation button");
   EventUtils.synthesizeMouseAtCenter(button, {}, doc.defaultView);
 }
 
 function promiseShareFrameEvent(iframe, eventName) {
   let deferred = Promise.defer();
-  iframe.addEventListener(eventName, function load() {
+  iframe.addEventListener(eventName, function load(event) {
     info("page load is " + iframe.contentDocument.location.href);
     if (iframe.contentDocument.location.href != "data:text/plain;charset=utf8,") {
       iframe.removeEventListener(eventName, load, true);
-      deferred.resolve();
+      deferred.resolve(event);
     }
   }, true);
   return deferred.promise;
 }
 
 function test() {
   waitForExplicitFinish();
   Services.prefs.setCharPref("social.shareDirectory", activationPage);
+
+  let frameScript = "data:,(" + function frame_script() {
+    addEventListener("OpenGraphData", function (aEvent) {
+      sendAsyncMessage("sharedata", aEvent.detail);
+    }, true, true);
+  }.toString() + ")();";
+  let mm = getGroupMessageManager("social");
+  mm.loadFrameScript(frameScript, true);
+
   registerCleanupFunction(function () {
+    mm.removeDelayedFrameScript(frameScript);
     Services.prefs.clearUserPref("social.directories");
     Services.prefs.clearUserPref("social.shareDirectory");
     Services.prefs.clearUserPref("social.share.activationPanelEnabled");
   });
   runSocialTests(tests, undefined, function(next) {
     let shareButton = SocialShare.shareButton;
     if (shareButton) {
       CustomizableUI.removeWidgetFromArea("social-share-button", CustomizableUI.AREA_NAVBAR)
@@ -169,50 +178,44 @@ var tests = {
         is(shareButton.hidden, false, "share button is visible");
         gBrowser.removeTab(tab);
         next();
       });
     });
   },
   testSharePage: function(next) {
     let provider = Social._getProviderFromOrigin(manifest.origin);
-    let port = provider.getWorkerPort();
-    ok(port, "provider has a port");
+
     let testTab;
     let testIndex = 0;
     let testData = corpus[testIndex++];
 
+    let mm = getGroupMessageManager("social");
+    mm.addMessageListener("sharedata", function handler(msg) {
+      gBrowser.removeTab(testTab);
+      hasoptions(testData.options, JSON.parse(msg.data));
+      testData = corpus[testIndex++];
+      if (testData) {
+        executeSoon(runOneTest);
+      } else {
+        mm.removeMessageListener("sharedata", handler);
+        SocialService.disableProvider(manifest.origin, next);
+      }
+    });
+
     function runOneTest() {
       addTab(testData.url, function(tab) {
         testTab = tab;
         SocialShare.sharePage(manifest.origin);
       });
     }
-
-    port.onmessage = function (e) {
-      let topic = e.data.topic;
-      switch (topic) {
-        case "got-share-data-message":
-          gBrowser.removeTab(testTab);
-          hasoptions(testData.options, e.data.result);
-          testData = corpus[testIndex++];
-          if (testData) {
-            executeSoon(runOneTest);
-          } else {
-            SocialService.disableProvider(manifest.origin, next);
-          }
-          break;
-      }
-    }
-    port.postMessage({topic: "test-init"});
     executeSoon(runOneTest);
   },
   testShareMicroformats: function(next) {
     SocialService.addProvider(manifest, function(provider) {
-      let port = provider.getWorkerPort();
       let target, testTab;
 
       let expecting = JSON.stringify({
         "url": "https://example.com/browser/browser/base/content/test/social/microformats.html",
         "title": "Raspberry Pi Page",
         "previews": ["https://example.com/someimage.jpg"],
         "microformats": {
           "items": [{
@@ -221,17 +224,17 @@ var tests = {
                 "name": ["Raspberry Pi"],
                 "photo": ["https://example.com/someimage.jpg"],
                 "description": [{
                     "value": "The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.",
                     "html": "The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming."
                   }
                 ],
                 "url": ["https://example.com/"],
-                "price": ["29.95"],
+                "price": ["29.95"],
                 "review": [{
                     "value": "4.5 out of 5",
                     "type": ["h-review"],
                     "properties": {
                       "rating": ["4.5"]
                     }
                   }
                 ],
@@ -250,27 +253,23 @@ var tests = {
             "https://example.com/wiki/education": {
               "text": "Education",
               "rels": ["tag"]
             }
           }
         }
       });
 
-      port.onmessage = function (e) {
-        let topic = e.data.topic;
-        switch (topic) {
-          case "got-share-data-message":
-            is(JSON.stringify(e.data.result), expecting, "microformats data ok");
-            gBrowser.removeTab(testTab);
-            SocialService.disableProvider(manifest.origin, next);
-            break;
-        }
-      }
-      port.postMessage({topic: "test-init"});
+      let mm = getGroupMessageManager("social");
+      mm.addMessageListener("sharedata", function handler(msg) {
+        is(msg.data, expecting, "microformats data ok");
+        gBrowser.removeTab(testTab);
+        mm.removeMessageListener("sharedata", handler);
+        SocialService.disableProvider(manifest.origin, next);
+      });
 
       let url = "https://example.com/browser/browser/base/content/test/social/microformats.html"
       addTab(url, function(tab) {
         testTab = tab;
         let doc = tab.linkedBrowser.contentDocument;
         target = doc.getElementById("simple-hcard");
         SocialShare.sharePage(manifest.origin, null, target);
       });
@@ -291,30 +290,23 @@ var tests = {
           // sometimes the iframe is ready before the panel is open, we need to
           // wait for both conditions
           return SocialShare.panel.state == "open"
                  && subframe.contentDocument
                  && subframe.contentDocument.readyState == "complete";
         }, () => {
         is(subframe.contentDocument.location.href, activationPage, "activation page loaded");
         promiseObserverNotified("social:provider-enabled").then(() => {
-          let provider = Social._getProviderFromOrigin(manifest.origin);
-          let port = provider.getWorkerPort();
-          ok(!!port, "got port");
-          port.onmessage = function (e) {
-            let topic = e.data.topic;
-            switch (topic) {
-              case "got-share-data-message":
-                ok(true, "share completed");
-                gBrowser.removeTab(testTab);
-                SocialService.uninstallProvider(manifest.origin, next);
-                break;
-            }
-          }
-          port.postMessage({topic: "test-init"});
+          let mm = getGroupMessageManager("social");
+          mm.addMessageListener("sharedata", function handler(msg) {
+            ok(true, "share completed");
+            gBrowser.removeTab(testTab);
+            mm.removeMessageListener("sharedata", handler);
+            SocialService.uninstallProvider(manifest.origin, next);
+          });
         });
         sendActivationEvent(subframe);
       }, "share panel did not open and load share page");
     });
     addTab(activationPage, function(tab) {
       testTab = tab;
       SocialShare.sharePage();
     });
--- a/browser/base/content/test/social/browser_social_chatwindow.js
+++ b/browser/base/content/test/social/browser_social_chatwindow.js
@@ -4,187 +4,115 @@
 
 var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
 
 var manifests = [
   {
     name: "provider@example.com",
     origin: "https://example.com",
     sidebarURL: "https://example.com/browser/browser/base/content/test/social/social_sidebar.html?example.com",
-    workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
     iconURL: "chrome://branding/content/icon48.png"
   },
   {
     name: "provider@test1",
     origin: "https://test1.example.com",
     sidebarURL: "https://test1.example.com/browser/browser/base/content/test/social/social_sidebar.html?test1",
-    workerURL: "https://test1.example.com/browser/browser/base/content/test/social/social_worker.js",
     iconURL: "chrome://branding/content/icon48.png"
   },
   {
     name: "provider@test2",
     origin: "https://test2.example.com",
     sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html?test2",
-    workerURL: "https://test2.example.com/browser/browser/base/content/test/social/social_worker.js",
     iconURL: "chrome://branding/content/icon48.png"
   }
 ];
 
-var ports = [];
-function getProviderPort(provider) {
-  let port = provider.getWorkerPort();
-  ok(port, "provider has a port");
-  ports.push(port);
-  return port;
-}
 var chatId = 0;
-function openChat(provider, callback) {
+function openChat(provider) {
+  let deferred = Promise.defer();
+  SocialSidebar.provider = provider;
   let chatUrl = provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
-  let port = getProviderPort(provider);
-  port.onmessage = function(e) {
-    if (e.data.topic == "got-chatbox-message") {
-      callback();
-    }
-  }
-  let url = chatUrl + "?" + (chatId++);
-  port.postMessage({topic: "test-init"});
-  port.postMessage({topic: "test-worker-chat", data: url});
-  gURLsNotRemembered.push(url);
-  return port;
+  let url = chatUrl + "?id=" + (chatId++);
+  makeChat("normal", "chat " + chatId, (cb) => { deferred.resolve(cb); });
+  return deferred.promise;
 }
 
 function windowHasChats(win) {
   return !!getChatBar().firstElementChild;
 }
 
 function test() {
   requestLongerTimeout(2); // only debug builds seem to need more time...
   waitForExplicitFinish();
 
+  let frameScript = "data:,(" + function frame_script() {
+    addMessageListener("socialTest-CloseSelf", function(e) {
+      content.close();
+    });
+    addEventListener("visibilitychange", function() {
+      sendAsyncMessage("chatbox-visibility", content.document.hidden ? "hidden" : "shown");
+    });
+  }.toString() + ")();";
+  let mm = getGroupMessageManager("social");
+  mm.loadFrameScript(frameScript, true);
+
   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) {
-    // ensure ports are closed
-    for (let port of ports) {
-      port.close()
-      ok(port._closed, "port closed");
-    }
-    ports = [];
-
     let chats = document.getElementById("pinnedchats");
     ok(chats.children.length == 0, "no chatty children left behind");
     cb();
   };
   runSocialTestWithProvider(manifests, function (finishcb) {
     ok(Social.enabled, "Social is enabled");
-    ok(getProviderPort(Social.providers[0]), "provider 0 has port");
-    ok(getProviderPort(Social.providers[1]), "provider 1 has port");
-    ok(getProviderPort(Social.providers[2]), "provider 2 has port");
     SocialSidebar.show();
     runSocialTests(tests, undefined, postSubTest, function() {
       window.moveTo(oldleft, window.screenY)
       window.resizeTo(oldwidth, window.outerHeight);
+      mm.removeDelayedFrameScript(frameScript);
       finishcb();
     });
   });
 }
 
 var tests = {
   testOpenCloseChat: function(next) {
-    let chats = document.getElementById("pinnedchats");
-    let port = getProviderPort(SocialSidebar.provider);
-    port.onmessage = function (e) {
-      let topic = e.data.topic;
-      switch (topic) {
-        case "got-sidebar-message":
-          openChatViaUser();
-          break;
-        case "got-chatbox-visibility":
-          let selectedChat = chats.selectedChat || chats.lastElementChild;
-          if (e.data.result == "hidden") {
-            ok(true, "chatbox got minimized");
-            selectedChat.toggle();
-          } else if (e.data.result == "shown") {
-            ok(true, "chatbox got shown");
-            // close it now
-            let content = selectedChat.content;
-            content.addEventListener("unload", function chatUnload() {
-              content.removeEventListener("unload", chatUnload, true);
-              ok(true, "got chatbox unload on close");
-              next();
-            }, true);
-            selectedChat.close();
-          }
-          break;
-        case "got-chatbox-message":
-          ok(true, "got chatbox message");
-          ok(e.data.result == "ok", "got chatbox windowRef result: "+e.data.result);
-          selectedChat.toggle();
-          break;
-      }
-    }
-    port.postMessage({topic: "test-init", data: { id: 1 }});
-  },
-  testWorkerChatWindow: function(next) {
-    const chatUrl = SocialSidebar.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
-    let chats = document.getElementById("pinnedchats");
-    let port = getProviderPort(SocialSidebar.provider);
-    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");
-          while (chats.selectedChat) {
-            chats.selectedChat.close();
-          }
-          ok(!chats.selectedChat, "chats are all closed");
-          gURLsNotRemembered.push(chatUrl);
-          next();
-          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 = getProviderPort(SocialSidebar.provider);
-    port.onmessage = function (e) {
-      let topic = e.data.topic;
-      switch (topic) {
-        case "test-init-done":
-          openChatViaUser();
-          break;
-        case "got-chatbox-visibility":
-          is(e.data.result, "shown", "chatbox shown");
-          port.close(); // don't want any more visibility messages.
-          let chat = chats.selectedChat;
-          ok(chat.parentNode, "chat has a parent node before it is closed");
-          // ask it to close itself.
-          let mm = chat.content.messageManager;
-          mm.sendAsyncMessage("Social:CustomEvent", { name: "socialTest-CloseSelf" });
-          waitForCondition(() => !chat.parentNode, next, "chat should close");
-          break;
-      }
-    }
-    port.postMessage({topic: "test-init", data: { id: 1 }});
+    openChat(SocialSidebar.provider).then((cb) => {
+      let smm = getGroupMessageManager("social");
+      // Sometimes we'll get the initial shown visibility from opening the chat
+      // box, so we ensure that we get hidden first.
+      let minimized = false;
+      smm.addMessageListener("chatbox-visibility", function handler(msg) {
+        if (minimized && msg.data == "shown") {
+          ok(true, "chatbox got shown");
+          smm.removeMessageListener("chatbox-visibility", handler);
+          // test the chatbox content closing itself
+          promiseNodeRemoved(cb).then(next);
+          let mm = cb.content.messageManager;
+          mm.sendAsyncMessage("socialTest-CloseSelf", {});
+        } else if (!minimized && msg.data == "hidden") {
+          minimized = true;
+          ok(true, "chatbox got minimized");
+          // toggle to maximize chat
+          cb.toggle();
+        }
+      });
+      // toggle to minimize chat
+      cb.toggle();
+    });
   },
 
   // Check what happens when you close the only visible chat.
   testCloseOnlyVisible: function(next) {
     let chatbar = getChatBar();
     let chatWidth = undefined;
     let num = 0;
     is(chatbar.childNodes.length, 0, "chatbar starting empty");
     is(chatbar.menupopup.childNodes.length, 0, "popup starting empty");
-    let port = getProviderPort(SocialSidebar.provider);
-    port.postMessage({topic: "test-init"});
 
     makeChat("normal", "first chat", function() {
       // got the first one.
       checkPopup();
       ok(chatbar.menupopup.parentNode.collapsed, "menu selection isn't visible");
       // we kinda cheat here and get the width of the first chat, assuming
       // that all future chats will have the same width when open.
       chatWidth = chatbar.calcTotalWidthOf(chatbar.selectedChat);
@@ -204,105 +132,17 @@ var tests = {
           is(chatbar.selectedChat, second, "second chat is selected");
           Task.spawn(closeAllChats).then(next);
         });
       });
     });
   },
 
   testShowWhenCollapsed: function(next) {
-    let port = getProviderPort(SocialSidebar.provider);
-    port.postMessage({topic: "test-init"});
     get3ChatsForCollapsing("normal", function(first, second, third) {
       let chatbar = getChatBar();
       chatbar.showChat(first);
       ok(!first.collapsed, "first should no longer be collapsed");
       is(second.collapsed ||  third.collapsed, true, "one of the others should be collapsed");
       Task.spawn(closeAllChats).then(next);
     });
-  },
-
-  testOnlyOneCallback: function(next) {
-    let chats = document.getElementById("pinnedchats");
-    let port = getProviderPort(SocialSidebar.provider);
-    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":
-          numOpened += 1;
-          port.postMessage({topic: "ping"});
-          break;
-        case "pong":
-          executeSoon(function() {
-            is(numOpened, 1, "only got one open message");
-            chats.selectedChat.close();
-            next();
-          });
-      }
-    }
-    port.postMessage({topic: "test-init", data: { id: 1 }});
-  },
-
-  testMultipleProviderChat: function(next) {
-    // test incomming chats from all providers
-    let port0 = openChat(Social.providers[0], function() {
-      let port1 = openChat(Social.providers[1], function() {
-        let port2 = openChat(Social.providers[2], function() {
-          let chats = document.getElementById("pinnedchats");
-          waitForCondition(() => chats.children.length == Social.providers.length,
-            function() {
-              ok(true, "one chat window per provider opened");
-              // test logout of a single provider
-              port2.postMessage({topic: "test-logout"});
-              waitForCondition(() => chats.children.length == Social.providers.length - 1,
-                function() {
-                  Task.spawn(closeAllChats).then(next);
-                },
-                "chat window didn't close");
-            }, "chat windows did not open");
-        });
-      });
-    });
-  },
-
-  // XXX - note this must be the last test until we restore the login state
-  // between tests...
-  testCloseOnLogout: function(next) {
-    const chatUrl = SocialSidebar.provider.origin + "/browser/browser/base/content/test/social/social_chat.html";
-    let port = SocialSidebar.provider.getWorkerPort();
-    ports.push(port);
-    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});
-          break;
-        case "got-chatbox-message":
-          ok(true, "got a chat window opened");
-          if (opened) {
-            port.postMessage({topic: "test-logout"});
-            waitForCondition(() => document.getElementById("pinnedchats").firstChild == null,
-                             function() {
-                              next();
-                             },
-                             "chat windows didn't close");
-          } else {
-            // open a second chat window
-            opened = true;
-            port.postMessage({topic: "test-worker-chat", data: chatUrl+"?id=1"});
-          }
-          break;
-      }
-    }
-    // make sure a user profile is set for this provider as chat windows are
-    // only closed on *change* of the profile data rather than merely setting
-    // profile data.
-    port.postMessage({topic: "test-set-profile"});
-    port.postMessage({topic: "test-init"});
   }
 }
--- a/browser/base/content/test/social/browser_social_chatwindowfocus.js
+++ b/browser/base/content/test/social/browser_social_chatwindowfocus.js
@@ -1,81 +1,20 @@
 /* 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 isChatFocused(chat) {
   return getChatBar()._isChatFocused(chat);
 }
 
-function openChatViaSidebarMessage(port, data, callback) {
-  port.onmessage = function (e) {
-    if (e.data.topic == "chatbox-opened")
-      callback();
-  }
-  port.postMessage({topic: "test-chatbox-open", data: data});
-}
-
-function openChatViaWorkerMessage(port, data, callback) {
-  // sadly there is no message coming back to tell us when the chat has
-  // been opened, so we wait until one appears.
-  let chatbar = getChatBar();
-  let numExpected = chatbar.childElementCount + 1;
-  port.postMessage({topic: "test-worker-chat", data: data});
-  waitForCondition(() => 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.
-                      chatbar.openChat({
-                        origin: SocialSidebar.provider.origin,
-                        title: SocialSidebar.provider.name,
-                        url: data,
-                        mode: "minimized"
-                      }, function() { callback(); });
-                   },
-                   "No new chat appeared");
-}
-
-
-var isSidebarLoaded = false;
-
-function startTestAndWaitForSidebar(callback) {
-  let doneCallback;
-  let port = SocialSidebar.provider.getWorkerPort();
-  function maybeCallback() {
-    if (!doneCallback)
-      callback(port);
-    doneCallback = true;
-  }
-  port.onmessage = function(e) {
-    let topic = e.data.topic;
-    switch (topic) {
-      case "got-sidebar-message":
-        // if sidebar loaded too fast, we need a backup ping
-      case "got-isVisible-response":
-        isSidebarLoaded = true;
-        maybeCallback();
-        break;
-      case "test-init-done":
-        if (isSidebarLoaded)
-          maybeCallback();
-        else
-          port.postMessage({topic: "test-isVisible"});
-        break;
-    }
-  }
-  port.postMessage({topic: "test-init"});
-}
-
 var 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();
 
   // Note that (probably) due to bug 604289, if a tab is focused but the
   // focused element is null, our chat windows can "steal" focus.  This is
@@ -98,68 +37,27 @@ function test() {
     }
     let postSubTest = function(cb) {
       Task.spawn(closeAllChats).then(cb);
     }
     // and run the tests.
     runSocialTestWithProvider(manifest, function (finishcb) {
       SocialSidebar.show();
       runSocialTests(tests, preSubTest, postSubTest, function () {
-        finishcb();
+        ensureBrowserTabClosed(tab).then(finishcb);
       });
     });
   }, true);
-  registerCleanupFunction(function() {
-    gBrowser.removeTab(tab);
-  });
-
 }
 
 var tests = {
-  // In this test the worker asks the sidebar to open a chat.  As that means
-  // we aren't handling user-input we will not focus the chatbar.
-  // Then we do it again - should still not be focused.
-  // Then we perform a user-initiated request - it should get focus.
-  testNoFocusWhenViaWorker: function(next) {
-    let chatbar = getChatBar();
-    startTestAndWaitForSidebar(function(port) {
-      openChatViaSidebarMessage(port, {stealFocus: 1}, function() {
-        ok(true, "got chatbox message");
-        is(chatbar.childElementCount, 1, "exactly 1 chat open");
-
-        let browser = gBrowser.selectedTab.linkedBrowser;
-        ContentTask.spawn(browser, null, function* () {
-          is(Services.focus.focusedWindow, content, "tab should still be focused");
-        }).then(() => {
-          // re-request the same chat via a message.
-          openChatViaSidebarMessage(port, {stealFocus: 1}, function() {
-            is(chatbar.childElementCount, 1, "still exactly 1 chat open");
-
-            ContentTask.spawn(browser, null, function* () {
-              is(Services.focus.focusedWindow, content, "tab should still be focused");
-            }).then(() => {
-              // re-request the same chat via user event.
-              openChatViaUser();
-              waitForCondition(() => isChatFocused(chatbar.selectedChat), function() {
-                is(chatbar.childElementCount, 1, "still exactly 1 chat open");
-                is(chatbar.selectedChat, chatbar.firstElementChild,
-                  "chat should be selected");
-                next();
-              }, "chat should be focused");
-            });
-          });
-        });
-      });
-    });
-  },
-
   // In this test we arrange for the sidebar to open the chat via a simulated
   // click.  This should cause the new chat to be opened and focused.
   testFocusWhenViaUser: function(next) {
-    startTestAndWaitForSidebar(function(port) {
+    ensureFrameLoaded(document.getElementById("social-sidebar-browser")).then(() => {
       let chatbar = getChatBar();
       openChatViaUser();
       ok(chatbar.firstElementChild, "chat opened");
       waitForCondition(() => isChatFocused(chatbar.selectedChat), function() {
         is(chatbar.selectedChat, chatbar.firstElementChild, "chat is selected");
         next();
       }, "chat should be focused");
     });
--- a/browser/base/content/test/social/browser_social_contextmenu.js
+++ b/browser/base/content/test/social/browser_social_contextmenu.js
@@ -2,40 +2,46 @@
  * 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/. */
 
 var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
 
 var manifest = { // 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",
   markURL: "https://test1.example.com/browser/browser/base/content/test/social/social_mark.html?url=%{url}",
   markedIcon: "https://test1.example.com/browser/browser/base/content/test/social/unchecked.jpg",
   unmarkedIcon: "https://test1.example.com/browser/browser/base/content/test/social/checked.jpg",
 
   iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
   version: "1.0"
 };
 
 function test() {
   waitForExplicitFinish();
+  let frameScript = "data:,(" + function frame_script() {
+    addEventListener("OpenGraphData", function (aEvent) {
+      sendAsyncMessage("sharedata", aEvent.detail);
+    }, true, true);
+  }.toString() + ")();";
+  let mm = getGroupMessageManager("social");
+  mm.loadFrameScript(frameScript, true);
 
   runSocialTestWithProvider(manifest, function (finishcb) {
     runSocialTests(tests, undefined, undefined, function () {
+      mm.removeDelayedFrameScript(frameScript);
       finishcb();
     });
   });
 }
 
 var tests = {
   testMarkMicroformats: function(next) {
     // emulates context menu action using target element, calling SocialMarks.markLink
     let provider = Social._getProviderFromOrigin(manifest.origin);
-    let port = provider.getWorkerPort();
     let target, testTab;
 
     // browser_share tests microformats on the full page, this is testing a
     // specific target element.
     let expecting = JSON.stringify({
       "url": "https://example.com/browser/browser/base/content/test/social/microformats.html",
       "microformats": {
         "items": [{
@@ -45,28 +51,23 @@ var tests = {
             }
           }
         ],
         "rels": {},
         "rel-urls": {}
       }
     });
 
-    port.onmessage = function (e) {
-      let topic = e.data.topic;
-      switch (topic) {
-        case "got-share-data-message":
-          is(JSON.stringify(e.data.result), expecting, "microformats data ok");
-          gBrowser.removeTab(testTab);
-          port.close();
-          next();
-          break;
-      }
-    }
-    port.postMessage({topic: "test-init"});
+    let mm = getGroupMessageManager("social");
+    mm.addMessageListener("sharedata", function handler(msg) {
+      gBrowser.removeTab(testTab);
+      is(msg.data, expecting, "microformats data ok");
+      mm.removeMessageListener("sharedata", handler);
+      next();
+    });
 
     let url = "https://example.com/browser/browser/base/content/test/social/microformats.html"
     addTab(url, function(tab) {
       testTab = tab;
       let doc = tab.linkedBrowser.contentDocument;
       target = doc.getElementById("test-review");
       SocialMarks.markLink(manifest.origin, url, target);
     });
--- a/browser/base/content/test/social/browser_social_flyout.js
+++ b/browser/base/content/test/social/browser_social_flyout.js
@@ -1,170 +1,129 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 function test() {
   waitForExplicitFinish();
+  let frameScript = "data:,(" + function frame_script() {
+    addMessageListener("socialTest-CloseSelf", function(e) {
+      content.close();
+    });
+    addEventListener("visibilitychange", function() {
+      sendAsyncMessage("social-visibility", content.document.hidden ? "hidden" : "shown");
+    });
+    addMessageListener("socialTest-sendEvent", function(msg) {
+      let data = msg.data;
+      let evt = content.document.createEvent("CustomEvent");
+      evt.initCustomEvent(data.name, true, true, JSON.stringify(data.data));
+      content.document.documentElement.dispatchEvent(evt);
+    });
+
+  }.toString() + ")();";
+  let mm = getGroupMessageManager("social");
+  mm.loadFrameScript(frameScript, true);
 
   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();
-    // disable transitions for the test
-    let panel = document.getElementById("social-flyout-panel");
-    registerCleanupFunction(function () {
-      panel.removeAttribute("animate");
+    ensureFrameLoaded(document.getElementById("social-sidebar-browser")).then(() => {
+      // disable transitions for the test
+      let panel = document.getElementById("social-flyout-panel");
+      registerCleanupFunction(function () {
+        panel.removeAttribute("animate");
+      });
+      panel.setAttribute("animate", "false");
+      runSocialTests(tests, undefined, undefined, finishcb);
     });
-    panel.setAttribute("animate", "false");
-    runSocialTests(tests, undefined, undefined, finishcb);
   });
 }
 
 var tests = {
   testOpenCloseFlyout: function(next) {
     let panel = document.getElementById("social-flyout-panel");
     ensureEventFired(panel, "popupshown").then(() => {
       is(panel.firstChild.contentDocument.readyState, "complete", "panel is loaded prior to showing");
     });
-    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":
-          if (e.data.result == "hidden") {
-            ok(true, "flyout visibility is 'hidden'");
-            is(panel.state, "closed", "panel really is closed");
-            port.close();
-            next();
-          } else if (e.data.result == "shown") {
-            ok(true, "flyout visibility is 'shown");
-            port.postMessage({topic: "test-flyout-close"});
-          }
-          break;
-        case "got-flyout-message":
-          ok(e.data.result == "ok", "got flyout message");
-          break;
+    let sidebar = document.getElementById("social-sidebar-browser")
+    let mm = getGroupMessageManager("social");
+    mm.addMessageListener("social-visibility", function handler(msg) {
+      if (msg.data == "shown") {
+        sidebar.messageManager.sendAsyncMessage("socialTest-sendEvent", { name: "test-flyout-close", data: {} });
+      } else if (msg.data == "hidden") {
+        mm.removeMessageListener("social-visibility", handler);
+        next();
       }
-    }
-    port.postMessage({topic: "test-init"});
+    });
+    sidebar.messageManager.sendAsyncMessage("socialTest-sendEvent", { name: "test-flyout-open", data: {} });
   },
 
   testResizeFlyout: function(next) {
     let panel = document.getElementById("social-flyout-panel");
-    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":
-          if (e.data.result != "shown")
-            return;
-          // The width of the flyout should be 400px initially
-          let iframe = panel.firstChild;
-          let body = iframe.contentDocument.body;
-          let cs = iframe.contentWindow.getComputedStyle(body);
+
+    ensureEventFired(panel, "popupshown").then(() => {
+      is(panel.firstChild.contentDocument.readyState, "complete", "panel is loaded prior to showing");
+      // The width of the flyout should be 400px initially
+      let iframe = panel.firstChild;
+      let body = iframe.contentDocument.body;
+      let cs = iframe.contentWindow.getComputedStyle(body);
+
+      is(cs.width, "400px", "should be 400px wide");
+      is(iframe.boxObject.width, 400, "iframe should now be 400px wide");
+      is(cs.height, "400px", "should be 400px high");
+      is(iframe.boxObject.height, 400, "iframe should now be 400px high");
 
-          is(cs.width, "400px", "should be 400px wide");
-          is(iframe.boxObject.width, 400, "iframe should now be 400px wide");
-          is(cs.height, "400px", "should be 400px high");
-          is(iframe.boxObject.height, 400, "iframe should now be 400px high");
-
-          ensureEventFired(iframe.contentWindow, "resize").then(() => {
-            cs = iframe.contentWindow.getComputedStyle(body);
+      ensureEventFired(iframe.contentWindow, "resize").then(() => {
+        cs = iframe.contentWindow.getComputedStyle(body);
 
-            is(cs.width, "500px", "should now be 500px wide");
-            is(iframe.boxObject.width, 500, "iframe should now be 500px wide");
-            is(cs.height, "500px", "should now be 500px high");
-            is(iframe.boxObject.height, 500, "iframe should now be 500px high");
-            panel.hidePopup();
-            port.close();
-            next();
-          });
-          SocialFlyout.dispatchPanelEvent("socialTest-MakeWider");
-          break;
-      }
-    }
-    port.postMessage({topic: "test-init"});
+        is(cs.width, "500px", "should now be 500px wide");
+        is(iframe.boxObject.width, 500, "iframe should now be 500px wide");
+        is(cs.height, "500px", "should now be 500px high");
+        is(iframe.boxObject.height, 500, "iframe should now be 500px high");
+        ensureEventFired(panel, "popuphidden").then(next);
+        panel.hidePopup();
+      });
+      SocialFlyout.dispatchPanelEvent("socialTest-MakeWider");
+    });
+
+    let sidebar = document.getElementById("social-sidebar-browser");
+    sidebar.messageManager.sendAsyncMessage("socialTest-sendEvent", { name: "test-flyout-open", data: {} });
   },
 
   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);
-    Services.prefs.setBoolPref(ALLOW_SCRIPTS_TO_CLOSE_PREF, false);
     let panel = document.getElementById("social-flyout-panel");
-    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":
-          if (e.data.result != "shown")
-            return;
-          let iframe = panel.firstChild;
-          ensureEventFired(iframe.contentDocument, "SocialTest-DoneCloseSelf").then(() => {
-            port.close();
-            is(panel.state, "closed", "flyout should have closed itself");
-            Services.prefs.setBoolPref(ALLOW_SCRIPTS_TO_CLOSE_PREF, oldAllowScriptsToClose);
-            next();
-          });
-          is(panel.state, "open", "flyout should be open");
-          SocialFlyout.dispatchPanelEvent("socialTest-CloseSelf");
-          break;
-      }
-    }
-    port.postMessage({topic: "test-init"});
+    ensureEventFired(panel, "popupshown").then(() => {
+      is(panel.firstChild.contentDocument.readyState, "complete", "panel is loaded prior to showing");
+      ensureEventFired(panel, "popuphidden").then(next);
+      let mm = panel.firstChild.messageManager;
+      mm.sendAsyncMessage("socialTest-CloseSelf", {});
+    });
+    let sidebar = document.getElementById("social-sidebar-browser");
+    sidebar.messageManager.sendAsyncMessage("socialTest-sendEvent", { name: "test-flyout-open", data: {} });
   },
 
   testCloseOnLinkTraversal: function(next) {
 
     function onTabOpen(event) {
       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 = 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":
-          if (e.data.result == "shown") {
-            // click on our test link
-            is(panel.state, "open", "flyout should be open");
-            gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen, true);
-            let iframe = panel.firstChild;
-            iframe.contentDocument.getElementById('traversal').click();
-          }
-          break;
-      }
-    }
-    port.postMessage({topic: "test-init"});
+    ensureEventFired(panel, "popupshown").then(() => {
+      is(panel.firstChild.contentDocument.readyState, "complete", "panel is loaded prior to showing");
+      is(panel.state, "open", "flyout should be open");
+      gBrowser.tabContainer.addEventListener("TabOpen", onTabOpen, true);
+      let iframe = panel.firstChild;
+      iframe.contentDocument.getElementById('traversal').click();
+    });
+    let sidebar = document.getElementById("social-sidebar-browser");
+    sidebar.messageManager.sendAsyncMessage("socialTest-sendEvent", { name: "test-flyout-open", data: {} });
   }
 }
--- a/browser/base/content/test/social/browser_social_isVisible.js
+++ b/browser/base/content/test/social/browser_social_isVisible.js
@@ -4,64 +4,48 @@
 
 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"
   };
+
+  let frameScript = "data:,(" + function frame_script() {
+    addEventListener("visibilitychange", function() {
+      sendAsyncMessage("visibility", content.document.hidden ? "hidden" : "shown");
+    });
+  }.toString() + ")();";
+  let mm = getGroupMessageManager("social");
+  mm.loadFrameScript(frameScript, true);
+
+  registerCleanupFunction(function () {
+    mm.removeDelayedFrameScript(frameScript);
+  });
+
   runSocialTestWithProvider(manifest, function (finishcb) {
-    SocialSidebar.show();
     runSocialTests(tests, undefined, undefined, finishcb);
   });
 }
 
 var tests = {
-  testSidebarMessage: function(next) {
-    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 = 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");
-          SocialSidebar.toggleSidebar();
-          port.close();
-          next();
-      }
-    };
-    port.postMessage({topic: "test-isVisible"});
+    let mm = getGroupMessageManager("social");
+    mm.addMessageListener("visibility", function handler(msg) {
+      mm.removeMessageListener("visibility", handler);
+      is(msg.data, "shown", "sidebar is visible");
+      next();
+    });
+    SocialSidebar.show();
   },
   testIsNotVisible: function(next) {
-    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");
-          port.close();
-          next();
-      }
-    };
-    port.postMessage({topic: "test-isVisible"});
+    let mm = getGroupMessageManager("social");
+    mm.addMessageListener("visibility", function handler(msg) {
+      mm.removeMessageListener("visibility", handler);
+      is(msg.data, "hidden", "sidebar is hidden");
+      next();
+    });
+    SocialSidebar.hide();
   }
 }
--- a/browser/base/content/test/social/browser_social_marks.js
+++ b/browser/base/content/test/social/browser_social_marks.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/. */
 
 var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
 
 var 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",
   markURL: "https://test1.example.com/browser/browser/base/content/test/social/social_mark.html?url=%{url}",
   markedIcon: "https://test1.example.com/browser/browser/base/content/test/social/unchecked.jpg",
   unmarkedIcon: "https://test1.example.com/browser/browser/base/content/test/social/checked.jpg",
 
   iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
   version: "1.0"
 };
 var manifest3 = { // used for testing install
@@ -21,16 +20,28 @@ var manifest3 = { // used for testing in
   sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html",
   iconURL: "https://test2.example.com/browser/browser/base/content/test/general/moz.png",
   version: "1.0"
 };
 
 function test() {
   waitForExplicitFinish();
 
+  let frameScript = "data:,(" + function frame_script() {
+    addEventListener("visibilitychange", function() {
+      sendAsyncMessage("visibility", content.document.hidden ? "hidden" : "shown");
+    });
+  }.toString() + ")();";
+  let mm = getGroupMessageManager("social");
+  mm.loadFrameScript(frameScript, true);
+
+  registerCleanupFunction(function () {
+    mm.removeDelayedFrameScript(frameScript);
+  });
+
   runSocialTests(tests, undefined, undefined, finish);
 }
 
 var tests = {
   testButtonDisabledOnActivate: function(next) {
     // starting on about:blank page, share should be visible but disabled when
     // adding provider
     is(gBrowser.selectedBrowser.currentURI.spec, "about:blank");
@@ -129,64 +140,47 @@ var tests = {
   testMarkPanel: function(next) {
     // click on panel to open and wait for visibility
     let provider = Social._getProviderFromOrigin(manifest2.origin);
     ok(provider.enabled, "provider is enabled");
     let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin);
     let widget = CustomizableUI.getWidget(id);
     let btn = widget.forWindow(window).node;
     ok(btn, "got a mark button");
-    let port = provider.getWorkerPort();
-    ok(port, "got a port");
+    let ourTab;
+
+    ensureEventFired(btn.panel, "popupshown").then(() => {
+      info("marks panel shown");
+      let doc = btn.contentDocument;
+      let unmarkBtn = doc.getElementById("unmark");
+      ok(unmarkBtn, "testMarkPanel - got the panel unmark button");
+      EventUtils.sendMouseEvent({type: "click"}, unmarkBtn, btn.contentWindow);
+    });
+
+    ensureEventFired(btn.panel, "popuphidden").then(() => {
+      ensureBrowserTabClosed(ourTab).then(() => {
+        ok(btn.disabled, "button is disabled");
+        next();
+      });
+    });
 
     // verify markbutton is disabled when there is no browser url
     ok(btn.disabled, "button is disabled");
     let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
     addTab(activationURL, function(tab) {
+      ourTab = tab;
       ok(!btn.disabled, "button is enabled");
-      port.onmessage = function (e) {
-        let topic = e.data.topic;
-        switch (topic) {
-          case "test-init-done":
-            ok(true, "test-init-done received");
-            ok(provider.profile.userName, "profile was set by test worker");
-            // first click marks the page, second click opens the page. We have to
-            // synthesize so the command event happens
-            EventUtils.synthesizeMouseAtCenter(btn, {});
-            // wait for the button to be marked, click to open panel
-            is(btn.panel.state, "closed", "panel should not be visible yet");
-            waitForCondition(() => btn.isMarked, function() {
-              EventUtils.synthesizeMouseAtCenter(btn, {});
-            }, "button is marked");
-            break;
-          case "got-social-panel-visibility":
-            ok(true, "got the panel message " + e.data.result);
-            if (e.data.result == "shown") {
-              // unmark the page via the button in the page
-              ensureFrameLoaded(btn.content).then(() => {
-                let doc = btn.contentDocument;
-                let unmarkBtn = doc.getElementById("unmark");
-                ok(unmarkBtn, "testMarkPanel - got the panel unmark button");
-                EventUtils.sendMouseEvent({type: "click"}, unmarkBtn, btn.contentWindow);
-              });
-            } else {
-              // page should no longer be marked
-              port.close();
-              waitForCondition(() => !btn.isMarked, function() {
-                // cleanup after the page has been unmarked
-                ensureBrowserTabClosed(tab).then(() => {
-                  ok(btn.disabled, "button is disabled");
-                  next();
-                });
-              }, "button unmarked");
-            }
-            break;
-        }
-      };
-      port.postMessage({topic: "test-init"});
+      // first click marks the page, second click opens the page. We have to
+      // synthesize so the command event happens
+      EventUtils.synthesizeMouseAtCenter(btn, {});
+      // wait for the button to be marked, click to open panel
+      is(btn.panel.state, "closed", "panel should not be visible yet");
+      waitForCondition(() => btn.isMarked, function() {
+        EventUtils.synthesizeMouseAtCenter(btn, {});
+      }, "button is marked");
     });
   },
 
   testMarkPanelOffline: function(next) {
     // click on panel to open and wait for visibility
     let provider = Social._getProviderFromOrigin(manifest2.origin);
     ok(provider.enabled, "provider is enabled");
     let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin);
@@ -213,77 +207,16 @@ var tests = {
             });
           });
         });
         btn.markCurrentPage();
       });
     });
   },
 
-  testMarkPanelLoggedOut: function(next) {
-    // click on panel to open and wait for visibility
-    let provider = Social._getProviderFromOrigin(manifest2.origin);
-    ok(provider.enabled, "provider is enabled");
-    let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin);
-    let widget = CustomizableUI.getWidget(id);
-    let btn = widget.forWindow(window).node;
-    ok(btn, "got a mark button");
-    let port = provider.getWorkerPort();
-    ok(port, "got a port");
-
-    // verify markbutton is disabled when there is no browser url
-    ok(btn.disabled, "button is disabled");
-    let activationURL = manifest2.origin + "/browser/browser/base/content/test/social/social_activate.html"
-    addTab(activationURL, function(tab) {
-      ok(!btn.disabled, "button is enabled");
-      port.onmessage = function (e) {
-        let topic = e.data.topic;
-        switch (topic) {
-          case "test-init-done":
-            ok(true, "test-init-done received");
-            ok(provider.profile.userName, "profile was set by test worker");
-            port.postMessage({topic: "test-logout"});
-            waitForCondition(() => !provider.profile.userName,
-                function() {
-                  // when the provider has not indicated to us that a user is
-                  // logged in, the first click opens the page.
-                  EventUtils.synthesizeMouseAtCenter(btn, {});
-                },
-                "profile was unset by test worker");
-            break;
-          case "got-social-panel-visibility":
-            ok(true, "got the panel message " + e.data.result);
-            if (e.data.result == "shown") {
-              // our test marks the page during the load event (see
-              // social_mark.html) regardless of login state, unmark the page
-              // via the button in the page
-              ensureFrameLoaded(btn.content).then(() => {
-                let doc = btn.contentDocument;
-                let unmarkBtn = doc.getElementById("unmark");
-                ok(unmarkBtn, "testMarkPanelLoggedOut - got the panel unmark button");
-                EventUtils.sendMouseEvent({type: "click"}, unmarkBtn, btn.contentWindow);
-              });
-            } else {
-              // page should no longer be marked
-              port.close();
-              waitForCondition(() => !btn.isMarked, function() {
-                // cleanup after the page has been unmarked
-                ensureBrowserTabClosed(tab).then(() => {
-                  ok(btn.disabled, "button is disabled");
-                  next();
-                });
-              }, "button unmarked");
-            }
-            break;
-        }
-      };
-      port.postMessage({topic: "test-init"});
-    });
-  },
-
   testButtonOnDisable: function(next) {
     // enable the provider now
     let provider = Social._getProviderFromOrigin(manifest2.origin);
     ok(provider, "provider is installed");
     SocialService.disableProvider(manifest2.origin, function() {
       let id = SocialMarks._toolbarHelper.idFromOrigin(manifest2.origin);
       waitForCondition(function() {
                         // getWidget now returns null since we've destroyed the widget
--- a/browser/base/content/test/social/browser_social_multiprovider.js
+++ b/browser/base/content/test/social/browser_social_multiprovider.js
@@ -12,85 +12,67 @@ function test() {
   });
 }
 
 var gProviders = [
   {
     name: "provider 1",
     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://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 sbrowser = document.getElementById("social-sidebar-browser");
     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() {
-      menu.removeEventListener("popupshown", theTest, true);
+    ensureEventFired(menu, "popupshown").then(()=>{
       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 activate "provider 2"
-      onSidebarLoad(function() {
+      ensureEventFired(sbrowser, "load").then(()=>{
         checkUIStateMatchesProvider(Social.providers[1]);
 
-        onSidebarLoad(function() {
+        ensureEventFired(sbrowser, "load").then(()=>{
           checkUIStateMatchesProvider(Social.providers[0]);
           next();
         });
 
         // show the menu again so the menu is updated with the correct commands
-        function doClick() {
+        ensureEventFired(menu, "popupshown").then(()=>{
           // 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);
+    });
     EventUtils.synthesizeMouseAtCenter(button, {});
   }
 }
 
 function checkUIStateMatchesProvider(provider) {
   // Sidebar
   is(document.getElementById("social-sidebar-browser").getAttribute("src"), provider.sidebarURL, "side bar URL is set");
 }
-
-function onSidebarLoad(callback) {
-  let sbrowser = document.getElementById("social-sidebar-browser");
-  sbrowser.addEventListener("load", function load(evt) {
-    if (evt.target != sbrowser.contentDocument) {
-      return;
-    }
-    sbrowser.removeEventListener("load", load, true);
-    // give the load a chance to finish before pulling the rug (ie. calling
-    // next)
-    executeSoon(callback);
-  }, true);
-}
deleted file mode 100644
--- a/browser/base/content/test/social/browser_social_multiworker.js
+++ /dev/null
@@ -1,70 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-function test() {
-  waitForExplicitFinish();
-
-  runSocialTestWithProvider(gProviders, function (finishcb) {
-    runSocialTests(tests, undefined, undefined, function() {
-      finishcb();
-    });
-  });
-}
-
-var 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",
-    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",
-    iconURL: "chrome://branding/content/icon48.png"
-  }
-];
-
-var tests = {
-  testWorkersAlive: function(next) {
-    // verify we can get a message from all providers that are enabled
-    let messageReceived = 0;
-    function oneWorkerTest(provider) {
-      let port = provider.getWorkerPort();
-      port.onmessage = function (e) {
-        let topic = e.data.topic;
-        switch (topic) {
-          case "test-init-done":
-            ok(true, "got message from provider " + provider.name);
-            port.close();
-            messageReceived++;
-            break;
-        }
-      };
-      port.postMessage({topic: "test-init"});
-    }
-
-    for (let p of Social.providers) {
-      oneWorkerTest(p);
-    }
-
-    waitForCondition(() => messageReceived == Social.providers.length,
-                     next, "received messages from all workers",
-                     /* increase timeout because shutting down a child process is slow */ 60);
-  },
-
-   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();
-   }
-}
deleted file mode 100644
--- a/browser/base/content/test/social/browser_social_perwindowPB.js
+++ /dev/null
@@ -1,100 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-function openTab(win, url, callback) {
-  let newTab = win.gBrowser.addTab(url);
-  let tabBrowser = win.gBrowser.getBrowserForTab(newTab);
-  tabBrowser.addEventListener("load", function tabLoadListener() {
-    tabBrowser.removeEventListener("load", tabLoadListener, true);
-    win.gBrowser.selectedTab = newTab;
-    callback(newTab);
-  }, true)
-}
-
-// Tests for per-window private browsing.
-function openPBWindow(callback) {
-  let w = OpenBrowserWindow({private: true});
-  w.addEventListener("load", function loadListener() {
-    w.removeEventListener("load", loadListener);
-    openTab(w, "http://example.com", function() {
-      callback(w);
-    });
-  });
-}
-
-function postAndReceive(port, postTopic, receiveTopic, callback) {
-  port.onmessage = function(e) {
-    if (e.data.topic == receiveTopic)
-      callback();
-  }
-  port.postMessage({topic: postTopic});
-}
-
-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",
-    statusURL: "https://example.com/browser/browser/base/content/test/social/social_panel.html",
-    workerURL: "https://example.com/browser/browser/base/content/test/social/social_worker.js",
-    markURL: "https://example.com/browser/browser/base/content/test/social/social_mark.html?url=%{url}",
-    iconURL: "https://example.com/browser/browser/base/content/test/general/moz.png"
-  };
-  runSocialTestWithProvider(manifest, function (finishcb) {
-    openTab(window, "http://example.com", function(newTab) {
-      runSocialTests(tests, undefined, undefined, function() {
-        window.gBrowser.removeTab(newTab);
-        finishcb();
-      });
-    });
-  });
-}
-
-var tests = {
-  testPrivateBrowsing: function(next) {
-    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) {
-        // The provider should remain alive.
-        postAndReceive(port, "ping", "pong", function() {
-          // the new window should have no social features at all.
-          info("checking private window ui");
-          ok(!pbwin.SocialUI.enabled, "social is disabled in a PB window");
-          checkSocialUI(pbwin);
-
-          // but they should all remain enabled in the initial window
-          info("checking main window ui");
-          ok(window.SocialUI.enabled, "social is still enabled in normal window");
-          checkSocialUI(window);
-
-          // Check that the status button is disabled on the private
-          // browsing window and not on the normal window.
-          let id = SocialStatus._toolbarHelper.idFromOrigin("https://example.com");
-          let widget = CustomizableUI.getWidget(id);
-          ok(widget.forWindow(pbwin).node.disabled, "status button disabled on private window");
-          ok(!widget.forWindow(window).node.disabled, "status button enabled on normal window");
-
-          // Check that the mark button is disabled on the private
-          // browsing window and not on the normal window.
-          id = SocialMarks._toolbarHelper.idFromOrigin("https://example.com");
-          widget = CustomizableUI.getWidget(id);
-          ok(widget.forWindow(pbwin).node.disabled, "mark button disabled on private window");
-          ok(!widget.forWindow(window).node.disabled, "mark button enabled on normal window");
-
-          // that's all folks...
-          pbwin.close();
-          next();
-        })
-      });
-    });
-  },
-}
--- a/browser/base/content/test/social/browser_social_sidebar.js
+++ b/browser/base/content/test/social/browser_social_sidebar.js
@@ -3,43 +3,57 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
 
 var 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 frameScript = "data:,(" + function frame_script() {
+    addEventListener("visibilitychange", function() {
+      sendAsyncMessage("visibility", content.document.hidden ? "hidden" : "shown");
+    });
+  }.toString() + ")();";
+  let mm = getGroupMessageManager("social");
+  mm.loadFrameScript(frameScript, true);
+
+  registerCleanupFunction(function () {
+    mm.removeDelayedFrameScript(frameScript);
+  });
+
   SocialService.addProvider(manifest, function() {
     // the test will remove the provider
     doTest();
   });
 }
 
 function doTest() {
   ok(SocialSidebar.canShow, "social sidebar should be able to be shown");
   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;
+  ok(!browser.docShellIsActive, "sidebar is not active");
+  is(sidebar.hidden, true, "sidebar should be hidden");
+  is(command.getAttribute("checked"), "false", "toggle command should be unchecked");
 
   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"));
+    is(browser.docShellIsActive, shouldBeShown, "sidebar isActive in correct state");
     if (shouldBeShown) {
       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");
@@ -49,51 +63,36 @@ function doTest() {
         // should not have unloaded so will still be the provider URL.
         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
-  ok(!command.hidden, "toggle command should be visible");
-  checkShown(true);
-
-  browser.addEventListener("socialFrameHide", function sidebarhide() {
-    browser.removeEventListener("socialFrameHide", sidebarhide);
-
-    checkShown(false);
-
-    browser.addEventListener("socialFrameShow", function sidebarshow() {
-      browser.removeEventListener("socialFrameShow", sidebarshow);
-
-      checkShown(true);
-
-      // disable social.
-      SocialService.disableProvider(SocialSidebar.provider.origin, function() {
-        checkShown(false);
-        is(Social.providers.length, 0, "no providers left");
-        defaultFinishChecks();
-        // Finish the test
-        executeSoon(finish);
-      });
+  ensureEventFired(browser, "load").then(() => {
+    // First check the the sidebar is initially visible, and loaded
+    ok(!command.hidden, "toggle command should be visible");
+    let mm = getGroupMessageManager("social");
+    mm.addMessageListener("visibility", function shown(msg) {
+      if (msg.data == "shown") {
+        mm.removeMessageListener("visibility", shown);
+        checkShown(true);
+        info("Toggling sidebar to closed");
+        SocialSidebar.toggleSidebar();
+      }
     });
-
-    // Toggle it back on
-    info("Toggling sidebar back on");
-    SocialSidebar.toggleSidebar();
+    mm.addMessageListener("visibility", function handler(msg) {
+      if (msg.data == "hidden") {
+        mm.removeMessageListener("visibility", handler);
+        // disable social.
+        SocialService.disableProvider(SocialSidebar.provider.origin, function() {
+          checkShown(false);
+          is(Social.providers.length, 0, "no providers left");
+          defaultFinishChecks();
+          // Finish the test
+          executeSoon(finish);
+        });
+      }
+    });
   });
-
-  // use port messaging to ensure that the sidebar is both loaded and
-  // ready before we run other tests
-  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");
-        SocialSidebar.toggleSidebar();
-    }
-  };
+  SocialSidebar.show();
 }
--- a/browser/base/content/test/social/browser_social_status.js
+++ b/browser/base/content/test/social/browser_social_status.js
@@ -3,23 +3,21 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 var SocialService = Cu.import("resource://gre/modules/SocialService.jsm", {}).SocialService;
 
 var manifest = { // builtin provider
   name: "provider example.com",
   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"
 };
 var 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",
   statusURL: "https://test1.example.com/browser/browser/base/content/test/social/social_panel.html",
   iconURL: "https://test1.example.com/browser/browser/base/content/test/general/moz.png",
   version: "1.0"
 };
 var manifest3 = { // used for testing install
   name: "provider test2",
   origin: "https://test2.example.com",
   sidebarURL: "https://test2.example.com/browser/browser/base/content/test/social/social_sidebar.html",
@@ -35,16 +33,31 @@ function openWindowAndWaitForInit(callba
     Services.obs.removeObserver(providerSet, topic);
     executeSoon(() => callback(w));
   }, topic, false);
 }
 
 function test() {
   waitForExplicitFinish();
 
+  let frameScript = "data:,(" + function frame_script() {
+    addMessageListener("socialTest-sendEvent", function(msg) {
+      let data = msg.data;
+      let evt = content.document.createEvent("CustomEvent");
+      evt.initCustomEvent(data.name, true, true, JSON.stringify(data.data));
+      content.document.documentElement.dispatchEvent(evt);
+    });
+  }.toString() + ")();";
+  let mm = getGroupMessageManager("social");
+  mm.loadFrameScript(frameScript, true);
+
+  registerCleanupFunction(function () {
+    mm.removeDelayedFrameScript(frameScript);
+  });
+
   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");
       CustomizableUI.reset();
       finishcb();
     });
@@ -118,78 +131,71 @@ var tests = {
   },
   testStatusPanel: function(next) {
     let icon = {
       name: "testIcon",
       iconURL: "chrome://browser/skin/Info.png",
       counter: 1
     };
 
-    // Disable the transition
-    let panel = document.getElementById("social-notification-panel");
-    panel.setAttribute("animate", "false");
-
     // click on panel to open and wait for visibility
     let provider = Social._getProviderFromOrigin(manifest2.origin);
     let id = SocialStatus._toolbarHelper.idFromOrigin(manifest2.origin);
     let widget = CustomizableUI.getWidget(id);
     let btn = widget.forWindow(window).node;
-    ok(btn, "got a status button");
-    let port = provider.getWorkerPort();
 
-    port.onmessage = function (e) {
-      let topic = e.data.topic;
-      switch (topic) {
-        case "test-init-done":
-          ok(true, "test-init-done received");
-          ok(provider.profile.userName, "profile was set by test worker");
-          btn.click();
-          break;
-        case "got-social-panel-visibility":
-          ok(true, "got the panel message " + e.data.result);
-          if (e.data.result == "shown") {
-            panel.hidePopup();
-            panel.removeAttribute("animate");
-          } else {
-            port.postMessage({topic: "test-ambient-notification", data: icon});
-            port.close();
-            waitForCondition(function() { return btn.getAttribute("badge"); },
-                       function() {
-                         is(btn.style.listStyleImage, "url(\"" + icon.iconURL + "\")", "notification icon updated");
-                         next();
-                       }, "button updated by notification");
-          }
-          break;
-      }
-    };
-    port.postMessage({topic: "test-init"});
+    // Disable the transition
+    let panel = document.getElementById("social-notification-panel");
+    panel.setAttribute("animate", "false");
+    ensureEventFired(panel, "popupshown").then(() => {
+      ensureFrameLoaded(panel.firstChild).then(() => {
+        let mm = panel.firstChild.messageManager;
+        mm.sendAsyncMessage("socialTest-sendEvent", { name: "Social:Notification", data: icon });
+        waitForCondition(function() { return btn.getAttribute("badge"); },
+                   function() {
+                     is(btn.style.listStyleImage, "url(\"" + icon.iconURL + "\")", "notification icon updated");
+                     panel.hidePopup();
+                   }, "button updated by notification");
+        });
+    });
+    ensureEventFired(panel, "popuphidden").then(() => {
+      panel.removeAttribute("animate");
+      next();
+    });
+    btn.click(); // open the panel
   },
 
   testPanelOffline: function(next) {
     // click on panel to open and wait for visibility
     let provider = Social._getProviderFromOrigin(manifest2.origin);
     ok(provider.enabled, "provider is enabled");
     let id = SocialStatus._toolbarHelper.idFromOrigin(manifest2.origin);
     let widget = CustomizableUI.getWidget(id);
     let btn = widget.forWindow(window).node;
     ok(btn, "got a status button");
     let frameId = btn.getAttribute("notificationFrameId");
     let frame = document.getElementById(frameId);
-    let port = provider.getWorkerPort();
-    port.postMessage({topic: "test-init"});
 
     goOffline().then(function() {
       info("testing offline error page");
       // wait for popupshown
       let panel = document.getElementById("social-notification-panel");
       ensureEventFired(panel, "popupshown").then(() => {
         ensureFrameLoaded(frame).then(() => {
           is(frame.contentDocument.documentURI.indexOf("about:socialerror?mode=tryAgainOnly"), 0, "social error page is showing "+frame.contentDocument.documentURI);
-          panel.hidePopup();
-          goOnline().then(next);
+          // We got our error page, reset to avoid test leak.
+          ensureEventFired(frame, "load").then(() => {
+            is(frame.contentDocument.documentURI, "about:blank", "closing error panel");
+            ensureEventFired(panel, "popuphidden").then(next);
+            panel.hidePopup();
+          });
+          goOnline().then(() => {
+            info("resetting error panel");
+            frame.setAttribute("src", "about:blank");
+          });
         });
       });
       // reload after going offline, wait for unload to open panel
       ensureEventFired(frame, "unload").then(() => {
         btn.click();
       });
       frame.contentDocument.location.reload();
     });
--- a/browser/base/content/test/social/browser_social_window.js
+++ b/browser/base/content/test/social/browser_social_window.js
@@ -50,23 +50,21 @@ function closeOneWindow(cb) {
 function postTestCleanup(cb) {
   closeOneWindow(cb);
 }
 
 var 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"
 };
 var 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();
   requestLongerTimeout(2);
   runSocialTests(tests, undefined, postTestCleanup);
deleted file mode 100644
--- a/browser/base/content/test/social/browser_social_workercrash.js
+++ /dev/null
@@ -1,157 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-// This tests our recovery if a child content process hosting providers
-// crashes.
-
-// A content script we inject into one of our browsers
-const TEST_CONTENT_HELPER = "chrome://mochitests/content/browser/browser/base/content/test/social/social_crash_content_helper.js";
-
-var {getFrameWorkerHandle} = Cu.import("resource://gre/modules/FrameWorker.jsm", {});
-var {Promise} = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
-
-function test() {
-  waitForExplicitFinish();
-
-  // We need to ensure all our workers are in the same content process.
-  Services.prefs.setIntPref("dom.ipc.processCount", 1);
-
-  // This test generates many uncaught promises that should not cause failures.
-  Promise.Debugging.clearUncaughtErrorObservers();
-
-  runSocialTestWithProvider(gProviders, function (finishcb) {
-    runSocialTests(tests, undefined, undefined, function() {
-      Services.prefs.clearUserPref("dom.ipc.processCount");
-      finishcb();
-    });
-  });
-}
-
-var 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",
-    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",
-    iconURL: "chrome://branding/content/icon48.png"
-  }
-];
-
-var tests = {
-  testCrash: function(next) {
-    // open the sidebar, then crash the child.
-    let sbrowser = document.getElementById("social-sidebar-browser");
-    onSidebarLoad(function() {
-      // get the browser element for our provider.
-      let fw = getFrameWorkerHandle(gProviders[0].workerURL);
-      fw.port.close();
-      fw._worker.browserPromise.then(browser => {
-        let mm = browser.messageManager;
-        mm.loadFrameScript(TEST_CONTENT_HELPER, false);
-        // add an observer for the crash - after it sees the crash we attempt
-        // a reload.
-        let observer = new crashObserver(function() {
-          info("Saw the process crash.")
-          Services.obs.removeObserver(observer, 'ipc:content-shutdown');
-          // Add another sidebar load listener - it should be the error page.
-          onSidebarLoad(function() {
-            ok(sbrowser.contentDocument.location.href.indexOf("about:socialerror?mode=workerFailure")==0, "is on social error page");
-            // after reloading, the sidebar should reload
-            onSidebarLoad(function() {
-              // now ping both workers - they should both be alive.
-              ensureWorkerLoaded(gProviders[0], function() {
-                ensureWorkerLoaded(gProviders[1], function() {
-                  // and we are done!
-                  next();
-                });
-              });
-            });
-            // click the try-again button.
-            sbrowser.contentDocument.getElementById("btnTryAgain").click();
-          });
-        });
-        Services.obs.addObserver(observer, 'ipc:content-shutdown', false);
-        // and cause the crash.
-        mm.sendAsyncMessage("social-test:crash");
-      });
-    })
-    SocialSidebar.show();
-  },
-}
-
-function onSidebarLoad(callback) {
-  let sbrowser = document.getElementById("social-sidebar-browser");
-  sbrowser.addEventListener("load", function load() {
-    sbrowser.removeEventListener("load", load, true);
-    callback();
-  }, true);
-}
-
-function ensureWorkerLoaded(manifest, callback) {
-  let fw = getFrameWorkerHandle(manifest.workerURL);
-  // once the worker responds to a ping we know it must be up.
-  let port = fw.port;
-  port.onmessage = function(msg) {
-    if (msg.data.topic == "pong") {
-      port.close();
-      callback();
-    }
-  }
-  port.postMessage({topic: "ping"})
-}
-
-// More duplicated code from browser_thumbnails_brackground_crash.
-// Bug 915518 exists to unify these.
-
-// This observer is needed so we can clean up all evidence of the crash so
-// the testrunner thinks things are peachy.
-var crashObserver = function(callback) {
-  this.callback = callback;
-}
-crashObserver.prototype = {
-  observe: function(subject, topic, data) {
-    is(topic, 'ipc:content-shutdown', 'Received correct observer topic.');
-    ok(subject instanceof Components.interfaces.nsIPropertyBag2,
-       'Subject implements nsIPropertyBag2.');
-    // we might see this called as the process terminates due to previous tests.
-    // We are only looking for "abnormal" exits...
-    if (!subject.hasKey("abnormal")) {
-      info("This is a normal termination and isn't the one we are looking for...");
-      return;
-    }
-
-    var dumpID;
-    if ('nsICrashReporter' in Components.interfaces) {
-      dumpID = subject.getPropertyAsAString('dumpID');
-      ok(dumpID, "dumpID is present and not an empty string");
-    }
-
-    if (dumpID) {
-      var minidumpDirectory = getMinidumpDirectory();
-      removeFile(minidumpDirectory, dumpID + '.dmp');
-      removeFile(minidumpDirectory, dumpID + '.extra');
-    }
-    this.callback();
-  }
-}
-
-function getMinidumpDirectory() {
-  var dir = Services.dirsvc.get('ProfD', Components.interfaces.nsIFile);
-  dir.append("minidumps");
-  return dir;
-}
-function removeFile(directory, filename) {
-  var file = directory.clone();
-  file.append(filename);
-  if (file.exists()) {
-    file.remove(false);
-  }
-}
--- a/browser/base/content/test/social/head.js
+++ b/browser/base/content/test/social/head.js
@@ -84,17 +84,17 @@ function runSocialTestWithProvider(manif
   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', 'shareURL', 'markURL']) {
+      for (let what of ['sidebarURL', 'iconURL', 'shareURL', 'markURL']) {
         if (m[what]) {
           yield promiseSocialUrlNotRemembered(m[what]);
         }
       };
     }
     for (let i = 0; i < gURLsNotRemembered.length; i++) {
       yield promiseSocialUrlNotRemembered(gURLsNotRemembered[i]);
     }
@@ -189,18 +189,17 @@ function runSocialTests(tests, cbPreTest
     if (result.done) {
       // out of items:
       (cbFinish || defaultFinishChecks)();
       is(providersAtStart, Social.providers.length,
          "runSocialTests: finish test run with " + Social.providers.length + " providers");
       return;
     }
     let [name, func] = result.value;
-    // We run on a timeout as the frameworker also makes use of timeouts, so
-    // this helps keep the debug messages sane.
+    // We run on a timeout to help keep the debug messages sane.
     executeSoon(function() {
       function cleanupAndRunNextTest() {
         info("sub-test " + name + " complete");
         cbPostTest(runNextTest);
       }
       cbPreTest(function() {
         info("pre-test: starting with " + Social.providers.length + " providers");
         info("sub-test " + name + " starting");
@@ -436,19 +435,16 @@ function ensureFrameLoaded(frame) {
 // chat test help functions
 
 // And lots of helpers for the resize tests.
 function get3ChatsForCollapsing(mode, cb) {
   // We make one chat, then measure its size.  We then resize the browser to
   // ensure a second can be created fully visible but a third can not - then
   // create the other 2.  first will will be collapsed, second fully visible
   // and the third also visible and the "selected" one.
-  // To make our life easier we don't go via the worker and ports so we get
-  // more control over creation *and* to make the code much simpler.  We
-  // assume the worker/port stuff is individually tested above.
   let chatbar = getChatBar();
   let chatWidth = undefined;
   let num = 0;
   is(chatbar.childNodes.length, 0, "chatbar starting empty");
   is(chatbar.menupopup.childNodes.length, 0, "popup starting empty");
 
   makeChat(mode, "first chat", function() {
     // got the first one.
@@ -503,17 +499,17 @@ function makeChat(mode, uniqueid, cb) {
     mode: mode
   });
   chatbox.promiseChatLoaded.then(
     () => {
     info("chat window has opened");
     chatbox.content.messageManager.sendAsyncMessage("Social:SetDocumentTitle", {
       title: uniqueid
     });
-    cb();
+    cb(chatbox);
   });
 }
 
 function checkPopup() {
   // popup only showing if any collapsed popup children.
   let chatbar = getChatBar();
   let numCollapsed = 0;
   for (let chat of chatbar.childNodes) {
@@ -625,35 +621,40 @@ function getPopupWidth() {
   let chatbar = getChatBar();
   let popup = chatbar.menupopup;
   ok(!popup.parentNode.collapsed, "asking for popup width when it is visible");
   let cs = document.defaultView.getComputedStyle(popup.parentNode);
   let margins = parseInt(cs.marginLeft) + parseInt(cs.marginRight);
   return popup.parentNode.getBoundingClientRect().width + margins;
 }
 
-function promiseCloseChat(chat) {
+function promiseNodeRemoved(aNode) {
   let deferred = Promise.defer();
-  let parent = chat.parentNode;
+  let parent = aNode.parentNode;
 
   let observer = new MutationObserver(function onMutatations(mutations) {
     for (let mutation of mutations) {
       for (let i = 0; i < mutation.removedNodes.length; i++) {
         let node = mutation.removedNodes.item(i);
-        if (node != chat) {
+        if (node != aNode) {
           continue;
         }
         observer.disconnect();
         deferred.resolve();
       }
     }
   });
   observer.observe(parent, {childList: true});
+  return deferred.promise;
+}
+
+function promiseCloseChat(chat) {
+  let promise = promiseNodeRemoved(chat);
   chat.close();
-  return deferred.promise;
+  return promise;
 }
 
 function closeAllChats() {
   let chatbar = getChatBar();
   while (chatbar.selectedChat) {
     yield promiseCloseChat(chatbar.selectedChat);
   }
 }
--- a/browser/base/content/test/social/microformats.html
+++ b/browser/base/content/test/social/microformats.html
@@ -2,17 +2,17 @@
 <html>
   <body>
     <head><title>Raspberry Pi Page</title></head>
     <div class="hproduct">
       <h2 class="fn">Raspberry Pi</h2>
       <img class="photo" src="https://example.com/someimage.jpg" />
       <p class="description">The Raspberry Pi is a credit-card sized computer that plugs into your TV and a keyboard. It's a capable little PC which can be used for many of the things that your desktop PC does, like spreadsheets, word-processing and games. It also plays high-definition video. We want to see it being used by kids all over the world to learn programming.</p>
       <a class="url" href="https://example.com/">More info about the Raspberry Pi</a>
-      <p class="price">29.95</p>
+      <p class="price">29.95</p>
       <p class="review hreview"><span id="test-review" class="rating">4.5</span> out of 5</p>
       <p>Categories:
         <a rel="tag" href="https://example.com/wiki/computer" class="category">Computer</a>,
         <a rel="tag" href="https://example.com/wiki/education" class="category">Education</a>
       </p>
     </div>
   </body>
 </html>
--- a/browser/base/content/test/social/share.html
+++ b/browser/base/content/test/social/share.html
@@ -1,17 +1,14 @@
 <html>
   <head>
     <meta charset="utf-8">
     <script>
-      var shareData;
       addEventListener("OpenGraphData", function(e) {
-        shareData = JSON.parse(e.detail);
-        var port = navigator.mozSocial.getWorker().port;
-        port.postMessage({topic: "share-data-message", result: shareData});
+        // frame scripts handle test data
         // share windows self-close
         history.back(); // bug 1042991, ensure history is available
         window.close();
       })
     </script>
   </head>
   <body>
     <p>This is a test social share window.</p>
--- a/browser/base/content/test/social/share_activate.html
+++ b/browser/base/content/test/social/share_activate.html
@@ -10,17 +10,16 @@
 var data = {
   // currently required
   "name": "Demo Social Service",
   // browser_share.js serves this page from "https://example.com"
   "origin": "https://example.com",
   "iconURL": "chrome://branding/content/icon16.png",
   "icon32URL": "chrome://branding/content/favicon32.png",
   "icon64URL": "chrome://branding/content/icon64.png",
-  "workerURL": "/browser/browser/base/content/test/social/social_worker.js",
   "shareURL": "/browser/browser/base/content/test/social/share.html"
 }
 
 function activate(node) {
   node.setAttribute("data-service", JSON.stringify(data));
   var event = new CustomEvent("ActivateSocialFeature");
   node.dispatchEvent(event);
 }
--- a/browser/base/content/test/social/social_activate.html
+++ b/browser/base/content/test/social/social_activate.html
@@ -8,17 +8,16 @@ var data = {
   // currently required
   "name": "Demo Social Service",
   "iconURL": "chrome://branding/content/icon16.png",
   "icon32URL": "chrome://branding/content/favicon32.png",
   "icon64URL": "chrome://branding/content/icon64.png",
 
   // at least one of these must be defined
   "sidebarURL": "/browser/browser/base/content/test/social/social_sidebar.html",
-  "workerURL": "/browser/browser/base/content/test/social/social_worker.js",
   "statusURL": "/browser/browser/base/content/test/social/social_panel.html",
   "postActivationURL": "/browser/browser/base/content/test/social/social_postActivation.html",
 
   // should be available for display purposes
   "description": "A short paragraph about this provider",
   "author": "Shane Caraveo, Mozilla",
 
   // optional
--- a/browser/base/content/test/social/social_chat.html
+++ b/browser/base/content/test/social/social_chat.html
@@ -1,31 +1,14 @@
 <html>
   <head>
     <meta charset="utf-8">
-    <script>
-      function pingWorker() {
-        var port = navigator.mozSocial.getWorker().port;
-        port.postMessage({topic: "chatbox-message", result: "ok"});
-      }
-      window.addEventListener("socialFrameShow", function(e) {
-        var port = navigator.mozSocial.getWorker().port;
-        port.postMessage({topic: "chatbox-visibility", result: "shown"});
-      }, false);
-      window.addEventListener("socialFrameHide", function(e) {
-        var port = navigator.mozSocial.getWorker().port;
-        port.postMessage({topic: "chatbox-visibility", result: "hidden"});
-      }, false);
-      window.addEventListener("socialTest-CloseSelf", function(e) {
-        window.close();
-      }, false);
-    </script>
     <title>test chat window</title>
   </head>
-  <body onload="pingWorker();">
+  <body>
     <p>This is a test social chat window.</p>
     <!-- a couple of input fields to help with focus testing -->
     <input id="input1"/>
     <input id="input2"/>
 
     <!-- an iframe here so this one page generates multiple load events -->
     <iframe id="iframe" src="data:text/plain:this is an iframe"></iframe>
   </body>
--- a/browser/base/content/test/social/social_flyout.html
+++ b/browser/base/content/test/social/social_flyout.html
@@ -1,37 +1,25 @@
 <html>
   <head>
     <meta charset="utf-8">
     <script>
-      function pingWorker() {
-        var port = navigator.mozSocial.getWorker().port;
-        port.postMessage({topic: "flyout-message", result: "ok"});
-      }
-      window.addEventListener("socialFrameShow", function(e) {
-        var port = navigator.mozSocial.getWorker().port;
-        port.postMessage({topic: "flyout-visibility", result: "shown"});
-      }, false);
-      window.addEventListener("socialFrameHide", function(e) {
-        var port = navigator.mozSocial.getWorker().port;
-        port.postMessage({topic: "flyout-visibility", result: "hidden"});
-      }, false);
       window.addEventListener("socialTest-MakeWider", function(e) {
         document.body.setAttribute("style", "width: 500px; height: 500px; margin: 0; overflow: hidden;");
         document.body.offsetWidth; // force a layout flush
         var evt = document.createEvent("CustomEvent");
         evt.initCustomEvent("SocialTest-DoneMakeWider", true, true, {});
         document.documentElement.dispatchEvent(evt);
       }, false);
       window.addEventListener("socialTest-CloseSelf", function(e) {
         window.close();
         var evt = document.createEvent("CustomEvent");
         evt.initCustomEvent("SocialTest-DoneCloseSelf", true, true, {});
         document.documentElement.dispatchEvent(evt);
       }, false);
     </script>
   </head>
-  <body style="width: 400px; height: 400px; margin: 0; overflow: hidden;" onload="pingWorker();">
+  <body style="width: 400px; height: 400px; margin: 0; overflow: hidden;">
     <p>This is a test social flyout panel.</p>
     <a id="traversal" href="https://test.example.com">test link</a>
   </body>
 </html>
 
--- a/browser/base/content/test/social/social_mark.html
+++ b/browser/base/content/test/social/social_mark.html
@@ -1,22 +1,14 @@
 <!DOCTYPE html>
 <html>
 <head>
     <link id="siteicon" rel="icon" href="./icon.png"/>
     <title>Demo Mark Window</title>
     <script type="text/javascript">
-    window.addEventListener("socialFrameShow", function(e) {
-      var port = navigator.mozSocial.getWorker().port;
-      port.postMessage({topic: "status-panel-visibility", result: "shown"});
-    }, false);
-    window.addEventListener("socialFrameHide", function(e) {
-      var port = navigator.mozSocial.getWorker().port;
-      port.postMessage({topic: "status-panel-visibility", result: "hidden"});
-    }, false);
 
     function updateTextNode(parent, text) {
       var textNode = parent.childNodes[0];
       if (textNode)
         parent.removeChild(textNode);
       textNode = document.createTextNode(text);
       parent.appendChild(textNode);
     }
@@ -29,18 +21,16 @@
         evt.initCustomEvent("socialMarkUpdate", true, true, JSON.stringify({marked: isMarked}));
         document.documentElement.dispatchEvent(evt);
     }
     var shareData;
     addEventListener("OpenGraphData", function(e) {
       shareData = JSON.parse(e.detail);
       updateTextNode(document.getElementById("shared"), shareData.url);
       socialMarkUpdate(true);
-      var port = navigator.mozSocial.getWorker().port;
-      port.postMessage({topic: "share-data-message", result: shareData});
     });
     </script>
 </head>
 
 <body onload="onLoad()">
   <div id="content">
     <h3>This window shows the mark data</h3>
     <div>Page Marked: <div id="shared" class="textbox"></div></div>
--- a/browser/base/content/test/social/social_panel.html
+++ b/browser/base/content/test/social/social_panel.html
@@ -1,24 +1,8 @@
 <html>
   <head>
     <meta charset="utf-8">
-    <script>
-      function pingWorker() {
-        var port = navigator.mozSocial.getWorker().port;
-        port.postMessage({topic: "panel-message",
-                          result: "ok",
-                          location: window.location.href});
-      }
-      window.addEventListener("socialFrameShow", function(e) {
-        var port = navigator.mozSocial.getWorker().port;
-        port.postMessage({topic: "status-panel-visibility", result: "shown"});
-      }, false);
-      window.addEventListener("socialFrameHide", function(e) {
-        var port = navigator.mozSocial.getWorker().port;
-        port.postMessage({topic: "status-panel-visibility", result: "hidden"});
-      }, false);
-    </script>
   </head>
-  <body onload="pingWorker();">
+  <body>
     <p>This is a test social panel.</p>
   </body>
 </html>
--- a/browser/base/content/test/social/social_sidebar.html
+++ b/browser/base/content/test/social/social_sidebar.html
@@ -1,42 +1,17 @@
 <html>
   <head>
     <meta charset="utf-8">
     <script>
-      var testwindow;
-      function pingWorker() {
-        var port = navigator.mozSocial.getWorker().port;
-        port.onmessage = function(e) {
-          var topic = e.data.topic;
-          switch (topic) {
-            case "test-flyout-open":
-              navigator.mozSocial.openPanel("social_flyout.html");
-              break;
-            case "test-flyout-close":
-              navigator.mozSocial.closePanel();
-              break;
-            case "test-chatbox-open":
-              var url = "social_chat.html";
-              var data = e.data.data;
-              if (data && data.id) {
-                url = url + "?id="+data.id;
-              }
-              navigator.mozSocial.openChatWindow(url, function(success) {
-                port.postMessage({topic: "chatbox-opened",
-                                  result: success ? "ok" : "failed"});
-              });
-              break;
-            case "test-isVisible":
-              port.postMessage({topic: "test-isVisible-response",
-                                result: navigator.mozSocial.isVisible});
-              break;
-          }
-        }
-        port.postMessage({topic: "sidebar-message", result: "ok"});
-      }
+      addEventListener("test-flyout-open", function(e) {
+        navigator.mozSocial.openPanel("social_flyout.html");
+      }, false);
+      addEventListener("test-flyout-close", function(e) {
+        navigator.mozSocial.closePanel();
+      }, false);
     </script>
   </head>
-  <body onload="pingWorker();">
+  <body>
     <p>This is a test social sidebar.</p>
     <button id="chat-opener" onclick="navigator.mozSocial.openChatWindow('./social_chat.html');"/>
   </body>
 </html>
deleted file mode 100644
--- a/browser/base/content/test/social/social_window.html
+++ /dev/null
@@ -1,17 +0,0 @@
-<html>
-  <head>
-    <meta charset="utf-8">
-    <script>
-      function pingWorker() {
-        var port = navigator.mozSocial.getWorker().port;
-        port.postMessage({topic: "service-window-message",
-                          location: window.location.href,
-                          result: "ok"
-                         });
-      }
-    </script>
-  </head>
-  <body onload="pingWorker();">
-    <p>This is a test social service window.</p>
-  </body>
-</html>
deleted file mode 100644
--- a/browser/base/content/test/social/social_worker.js
+++ /dev/null
@@ -1,137 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-var testPort, sidebarPort, apiPort, updatingManifest=false;
-
-onconnect = function(e) {
-  let port = e.ports[0];
-  port.onmessage = function onMessage(event) {
-    let topic = event.data.topic;
-    switch (topic) {
-      case "test-init":
-        testPort = port;
-        port.postMessage({topic: "test-init-done"});
-        break;
-      case "ping":
-        port.postMessage({topic: "pong"});
-        break;
-      case "test-logout":
-        apiPort.postMessage({topic: "social.user-profile", data: {}});
-        break;
-      case "sidebar-message":
-        sidebarPort = port;
-        if (testPort && event.data.result == "ok")
-          testPort.postMessage({topic:"got-sidebar-message"});
-        break;
-      case "service-window-message":
-        testPort.postMessage({topic:"got-service-window-message",
-                              location: event.data.location});
-        break;
-      case "service-window-closed-message":
-        testPort.postMessage({topic:"got-service-window-closed-message"});
-        break;
-      case "test-service-window":
-        sidebarPort.postMessage({topic:"test-service-window"});
-        break;
-      case "test-service-window-twice":
-        sidebarPort.postMessage({topic:"test-service-window-twice"});
-        break;
-      case "test-service-window-twice-result":
-        testPort.postMessage({topic: "test-service-window-twice-result", result: event.data.result })
-        break;
-      case "test-close-service-window":
-        sidebarPort.postMessage({topic:"test-close-service-window"});
-        break;
-      case "panel-message":
-        if (testPort && event.data.result == "ok")
-          testPort.postMessage({topic:"got-panel-message",
-                                location: event.data.location
-                               });
-        break;
-      case "status-panel-visibility":
-        testPort.postMessage({topic:"got-social-panel-visibility", result: event.data.result });
-        break;
-      case "test-chatbox-open":
-        sidebarPort.postMessage(event.data);
-        break;
-      case "chatbox-opened":
-        testPort.postMessage(event.data);
-        break;
-      case "chatbox-message":
-        testPort.postMessage({topic:"got-chatbox-message", result: event.data.result});
-        break;
-      case "chatbox-visibility":
-        testPort.postMessage({topic:"got-chatbox-visibility", result: event.data.result});
-        break;
-      case "test-flyout-open":
-        sidebarPort.postMessage({topic:"test-flyout-open"});
-        break;
-      case "flyout-message":
-        testPort.postMessage({topic:"got-flyout-message", result: event.data.result});
-        break;
-      case "flyout-visibility":
-        testPort.postMessage({topic:"got-flyout-visibility", result: event.data.result});
-        break;
-      case "test-flyout-close":
-        sidebarPort.postMessage({topic:"test-flyout-close"});
-        break;
-      case "test-worker-chat":
-        apiPort.postMessage({topic: "social.request-chat", data: event.data.data });
-        break;
-      case "social.initialize":
-        // This is the workerAPI port, respond and set up a notification icon.
-        // For multiprovider tests, we support acting like different providers
-        // based on the domain we load from.
-        apiPort = port;
-        // purposely fall through and set the profile on initialization
-      case "test-set-profile":
-        let profile;
-        if (location.href.indexOf("https://test1.example.com") == 0) {
-          profile = {
-            portrait: "https://test1.example.com/portrait.jpg",
-            userName: "tester",
-            displayName: "Test1 User",
-          };
-        } else {
-          profile = {
-            portrait: "https://example.com/portrait.jpg",
-            userName: "trickster",
-            displayName: "Kuma Lisa",
-            profileURL: "http://en.wikipedia.org/wiki/Kuma_Lisa"
-          };
-        }
-        apiPort.postMessage({topic: "social.user-profile", data: profile});
-        break;
-      case "test-ambient-notification":
-        apiPort.postMessage({topic: "social.ambient-notification", data: event.data.data});
-        break;
-      case "test-isVisible":
-        sidebarPort.postMessage({topic: "test-isVisible"});
-        break;
-      case "test-isVisible-response":
-        testPort.postMessage({topic: "got-isVisible-response", result: event.data.result});
-        break;
-      case "share-data-message":
-        if (testPort)
-          testPort.postMessage({topic:"got-share-data-message", result: event.data.result});
-        break;
-      case "manifest-get":
-        apiPort.postMessage({topic: 'social.manifest-get'});
-        break;
-      case "worker.update":
-        updatingManifest = true;
-        apiPort.postMessage({topic: 'social.manifest-get'});
-        break;
-      case "social.manifest":
-        if (updatingManifest) {
-          updatingManifest = false;
-          event.data.data.version = "2.0";
-          apiPort.postMessage({topic: 'social.manifest-set', data: event.data.data});
-        } else if (testPort) {
-          testPort.postMessage({topic:"social.manifest", data: event.data.data});
-        }
-        break;
-    }
-  }
-}
--- a/browser/components/uitour/test/uitour.html
+++ b/browser/components/uitour/test/uitour.html
@@ -1,42 +1,42 @@
-<!DOCTYPE html>
-<html>
-  <head>
-    <meta charset="utf-8" />
-    <title>UITour test</title>
-    <script type="application/javascript" src="UITour-lib.js">
-    </script>
-    <script type="application/javascript">
-      var callbackResult, callbackData;
-      function makeCallback(name) {
-        return (function(data) {
-          callbackResult = name;
-          callbackData = data;
-        });
-      }
-
-      // Defined in content to avoid weird issues when crossing between chrome/content.
-      function makeButtons() {
-        return [
-          {label: "Regular text", style: "text"},
-          {label: "Link", callback: makeCallback("link"), style: "link"},
-          {label: "Button 1", callback: makeCallback("button1")},
-          {label: "Button 2", callback: makeCallback("button2"), icon: "image.png",
-           style: "primary"}
-        ];
-      }
-
-      function makeInfoOptions() {
-        return {
-          closeButtonCallback: makeCallback("closeButton"),
-          targetCallback: makeCallback("target"),
-        };
-      }
-    </script>
-  </head>
-  <body>
-    <h1>UITour tests</h1>
-    <p>Because Firefox is...</p>
-    <p>Never gonna let you down</p>
-    <p>Never gonna give you up</p>
-  </body>
-</html>
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8" />
+    <title>UITour test</title>
+    <script type="application/javascript" src="UITour-lib.js">
+    </script>
+    <script type="application/javascript">
+      var callbackResult, callbackData;
+      function makeCallback(name) {
+        return (function(data) {
+          callbackResult = name;
+          callbackData = data;
+        });
+      }
+
+      // Defined in content to avoid weird issues when crossing between chrome/content.
+      function makeButtons() {
+        return [
+          {label: "Regular text", style: "text"},
+          {label: "Link", callback: makeCallback("link"), style: "link"},
+          {label: "Button 1", callback: makeCallback("button1")},
+          {label: "Button 2", callback: makeCallback("button2"), icon: "image.png",
+           style: "primary"}
+        ];
+      }
+
+      function makeInfoOptions() {
+        return {
+          closeButtonCallback: makeCallback("closeButton"),
+          targetCallback: makeCallback("target"),
+        };
+      }
+    </script>
+  </head>
+  <body>
+    <h1>UITour tests</h1>
+    <p>Because Firefox is...</p>
+    <p>Never gonna let you down</p>
+    <p>Never gonna give you up</p>
+  </body>
+</html>
--- a/browser/experiments/Experiments.jsm
+++ b/browser/experiments/Experiments.jsm
@@ -318,21 +318,20 @@ Experiments.Experiments = function (poli
   let log = Log.repository.getLoggerWithMessagePrefix(
       "Browser.Experiments.Experiments",
       "Experiments #" + gExperimentsCounter++ + "::");
 
   // At the time of this writing, Experiments.jsm has severe
   // crashes. For forensics purposes, keep the last few log
   // messages in memory and upload them in case of crash.
   this._forensicsLogs = [];
-  this._forensicsLogs.length = 20;
+  this._forensicsLogs.length = 30;
   this._log = Object.create(log);
   this._log.log = (level, string, params) => {
-    this._forensicsLogs.shift();
-    this._forensicsLogs.push(level + ": " + string);
+    this._addToForensicsLog("Experiments", string);
     log.log(level, string, params);
   };
 
   this._log.trace("constructor");
 
   // Capture the latest error, for forensics purposes.
   this._latestError = null;
 
@@ -459,55 +458,64 @@ Experiments.Experiments.prototype = {
           // pass
         }
       }
       try {
         this._log.trace("uninit: waiting on _mainTask");
         yield this._mainTask;
       } catch (e) {
         // We error out of tasks after shutdown via this exception.
+        this._log.trace(`uninit: caught error - ${e}`);
         if (!(e instanceof AlreadyShutdownError)) {
           this._latestError = e;
           throw e;
         }
       }
     }
 
     this._log.info("Completed uninitialization.");
   }),
 
   // Return state information, for debugging purposes.
   _getState: function() {
+    let activeExperiment = this._getActiveExperiment();
     let state = {
       isShutdown: this._shutdown,
       isEnabled: gExperimentsEnabled,
       isRefresh: this._refresh,
       isDirty: this._dirty,
       isFirstEvaluate: this._firstEvaluate,
       hasLoadTask: !!this._loadTask,
       hasMainTask: !!this._mainTask,
       hasTimer: !!this._hasTimer,
       hasAddonProvider: !!gAddonProvider,
       latestLogs: this._forensicsLogs,
-      experiments: this._experiments ? this._experiments.keys() : null,
+      experiments: this._experiments ? [...this._experiments.keys()] : null,
       terminateReason: this._terminateReason,
+      activeExperiment: !!activeExperiment ? activeExperiment.id : null,
     };
     if (this._latestError) {
       if (typeof this._latestError == "object") {
         state.latestError = {
           message: this._latestError.message,
           stack: this._latestError.stack
         };
       } else {
         state.latestError = "" + this._latestError;
       }
     }
     return state;
   },
 
+  _addToForensicsLog: function (what, string) {
+    this._forensicsLogs.shift();
+    let timeInSec = Math.floor(Services.telemetry.msSinceProcessStart() / 1000);
+    this._forensicsLogs.push(`${timeInSec}: ${what} - ${string}`);
+  },
+
   _registerWithAddonManager: function (previousExperimentsProvider) {
     this._log.trace("Registering instance with Addon Manager.");
 
     AddonManager.addAddonListener(this);
     AddonManager.addInstallListener(this);
 
     if (!gAddonProvider) {
       // The properties of this AddonType should be kept in sync with the
@@ -1359,19 +1367,26 @@ Experiments.Experiments.prototype = {
 
 
 /*
  * Represents a single experiment.
  */
 
 Experiments.ExperimentEntry = function (policy) {
   this._policy = policy || new Experiments.Policy();
-  this._log = Log.repository.getLoggerWithMessagePrefix(
+  let log = Log.repository.getLoggerWithMessagePrefix(
     "Browser.Experiments.Experiments",
     "ExperimentEntry #" + gExperimentEntryCounter++ + "::");
+  this._log = Object.create(log);
+  this._log.log = (level, string, params) => {
+    if (gExperiments) {
+      gExperiments._addToForensicsLog("ExperimentEntry", string);
+    }
+    log.log(level, string, params);
+  };
 
   // Is the experiment supposed to be running.
   this._enabled = false;
   // When this experiment was started, if ever.
   this._startDate = null;
   // When this experiment was ended, if ever.
   this._endDate = null;
   // The condition data from the manifest.
@@ -1630,18 +1645,17 @@ Experiments.ExperimentEntry.prototype = 
     let data = this._manifestData;
 
     let now = this._policy.now() / 1000; // The manifest times are in seconds.
     let minActive = MIN_EXPERIMENT_ACTIVE_SECONDS;
     let maxActive = data.maxActiveSeconds || 0;
     let startSec = (this.startDate || 0) / 1000;
 
     this._log.trace("isApplicable() - now=" + now
-                    + ", randomValue=" + this._randomValue
-                    + ", data=" + JSON.stringify(this._manifestData));
+                    + ", randomValue=" + this._randomValue);
 
     // Not applicable if it already ran.
 
     if (!this.enabled && this._endDate) {
       return Promise.reject(["was-active"]);
     }
 
     // Define and run the condition checks.
--- a/browser/extensions/loop/chrome/content/preferences/prefs.js
+++ b/browser/extensions/loop/chrome/content/preferences/prefs.js
@@ -1,10 +1,10 @@
 pref("loop.enabled", true);
-pref("loop.remote.autostart", false);
+pref("loop.remote.autostart", true);
 pref("loop.server", "https://loop.services.mozilla.com/v0");
 pref("loop.linkClicker.url", "https://hello.firefox.com/");
 pref("loop.gettingStarted.latestFTUVersion", 1);
 pref("loop.gettingStarted.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/hello/start/");
 pref("loop.gettingStarted.resumeOnFirstJoin", false);
 pref("loop.legal.ToS_url", "https://www.mozilla.org/about/legal/terms/firefox-hello/");
 pref("loop.legal.privacy_url", "https://www.mozilla.org/privacy/firefox-hello/");
 pref("loop.do_not_disturb", false);
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,3 +1,3 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.4.121
+Current extension version is: 1.4.137
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf', ['exports
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdf = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.4.121';
-var pdfjsBuild = '51f6aba';
+var pdfjsVersion = '1.4.137';
+var pdfjsBuild = '122d473';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -4682,17 +4682,20 @@ var CanvasGraphics = (function CanvasGra
       }
     },
 
     endDrawing: function CanvasGraphics_endDrawing() {
       this.ctx.restore();
 
       if (this.transparentCanvas) {
         this.ctx = this.compositeCtx;
+        this.ctx.save();
+        this.ctx.setTransform(1, 0, 0, 1, 0, 0); // Avoid apply transform twice
         this.ctx.drawImage(this.transparentCanvas, 0, 0);
+        this.ctx.restore();
         this.transparentCanvas = null;
       }
 
       this.cachedCanvases.clear();
       WebGLUtils.clear();
 
       if (this.imageLayer) {
         this.imageLayer.endLayout();
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -23,18 +23,18 @@ define('pdfjs-dist/build/pdf.worker', ['
     factory(exports);
   } else {
 factory((root.pdfjsDistBuildPdfWorker = {}));
   }
 }(this, function (exports) {
   // Use strict in our context only - users might not want it
   'use strict';
 
-var pdfjsVersion = '1.4.121';
-var pdfjsBuild = '51f6aba';
+var pdfjsVersion = '1.4.137';
+var pdfjsBuild = '122d473';
 
   var pdfjsFilePath =
     typeof document !== 'undefined' && document.currentScript ?
       document.currentScript.src : null;
 
   var pdfjsLibs = {};
 
   (function pdfjsWrapper() {
@@ -15653,16 +15653,46 @@ exports.isStream = isStream;
     if (code >= 0xFFF0 && code <= 0xFFFF) { // Specials unicode block.
       return 0;
     } else if (code >= 0xF600 && code <= 0xF8FF) {
       return (getSpecialPUASymbols()[code] || code);
     }
     return code;
   }
 
+  function getUnicodeForGlyph(name, glyphsUnicodeMap) {
+    var unicode = glyphsUnicodeMap[name];
+    if (unicode !== undefined) {
+      return unicode;
+    }
+    if (!name) {
+      return -1;
+    }
+    // Try to recover valid Unicode values from 'uniXXXX'/'uXXXX{XX}' glyphs.
+    if (name[0] === 'u') {
+      var nameLen = name.length, hexStr;
+
+      if (nameLen === 7 && name[1] === 'n' && name[2] === 'i') { // 'uniXXXX'
+        hexStr = name.substr(3);
+      } else if (nameLen >= 5 && nameLen <= 7) { // 'uXXXX{XX}'
+        hexStr = name.substr(1);
+      } else {
+        return -1;
+      }
+      // Check for upper-case hexadecimal characters, to avoid false positives.
+      if (hexStr === hexStr.toUpperCase()) {
+        unicode = parseInt(hexStr, 16);
+        if (unicode >= 0) {
+          return unicode;
+        }
+      }
+    }
+    return -1;
+  }
+
   var UnicodeRanges = [
     { 'begin': 0x0000, 'end': 0x007F }, // Basic Latin
     { 'begin': 0x0080, 'end': 0x00FF }, // Latin-1 Supplement
     { 'begin': 0x0100, 'end': 0x017F }, // Latin Extended-A
     { 'begin': 0x0180, 'end': 0x024F }, // Latin Extended-B
     { 'begin': 0x0250, 'end': 0x02AF }, // IPA Extensions
     { 'begin': 0x02B0, 'end': 0x02FF }, // Spacing Modifier Letters
     { 'begin': 0x0300, 'end': 0x036F }, // Combining Diacritical Marks
@@ -17200,16 +17230,17 @@ exports.isStream = isStream;
     }
     return s;
   }
 
   exports.mapSpecialUnicodeValues = mapSpecialUnicodeValues;
   exports.reverseIfRtl = reverseIfRtl;
   exports.getUnicodeRangeFor = getUnicodeRangeFor;
   exports.getNormalizedUnicodes = getNormalizedUnicodes;
+  exports.getUnicodeForGlyph = getUnicodeForGlyph;
 }));
 
 
 (function (root, factory) {
   {
     factory((root.pdfjsCoreStream = {}), root.pdfjsSharedUtil,
       root.pdfjsCorePrimitives, root.pdfjsCoreJbig2, root.pdfjsCoreJpg,
       root.pdfjsCoreJpx);
@@ -24890,16 +24921,17 @@ var ExpertEncoding = coreEncodings.Exper
 var getEncoding = coreEncodings.getEncoding;
 var getStdFontMap = coreStandardFonts.getStdFontMap;
 var getNonStdFontMap = coreStandardFonts.getNonStdFontMap;
 var getGlyphMapForStandardFonts = coreStandardFonts.getGlyphMapForStandardFonts;
 var getSupplementalGlyphMapForArialBlack =
   coreStandardFonts.getSupplementalGlyphMapForArialBlack;
 var getUnicodeRangeFor = coreUnicode.getUnicodeRangeFor;
 var mapSpecialUnicodeValues = coreUnicode.mapSpecialUnicodeValues;
+var getUnicodeForGlyph = coreUnicode.getUnicodeForGlyph;
 
 // Unicode Private Use Area
 var PRIVATE_USE_OFFSET_START = 0xE000;
 var PRIVATE_USE_OFFSET_END = 0xF8FF;
 var SKIP_PRIVATE_USE_RANGE_F000_TO_F01F = false;
 
 // PDF Glyph Space Units are one Thousandth of a TextSpace Unit
 // except for Type 3 fonts
@@ -25275,17 +25307,17 @@ var ProblematicCharRanges = new Int32Arr
  * decoding logics whatever type it is (assuming the font type is supported).
  *
  * For example to read a Type1 font and to attach it to the document:
  *   var type1Font = new Font("MyFontName", binaryFile, propertiesObject);
  *   type1Font.bind();
  */
 var Font = (function FontClosure() {
   function Font(name, file, properties) {
-    var charCode, glyphName, fontChar;
+    var charCode, glyphName, unicode, fontChar;
 
     this.name = name;
     this.loadedName = properties.loadedName;
     this.isType3Font = properties.isType3Font;
     this.sizes = [];
     this.missingFile = false;
 
     this.glyphCache = Object.create(null);
@@ -25419,31 +25451,35 @@ var Font = (function FontClosure() {
         for (charCode in properties.differences) {
           fontChar = glyphsUnicodeMap[properties.differences[charCode]];
           if (!fontChar) {
             continue;
           }
           this.toFontChar[charCode] = fontChar;
         }
       } else if (isStandardFont) {
-        this.toFontChar = [];
         glyphsUnicodeMap = getGlyphsUnicode();
         for (charCode in properties.defaultEncoding) {
           glyphName = (properties.differences[charCode] ||
                        properties.defaultEncoding[charCode]);
-          this.toFontChar[charCode] = glyphsUnicodeMap[glyphName];
-        }
-      } else {
-        var unicodeCharCode, notCidFont = (type.indexOf('CIDFontType') === -1);
+          unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
+          if (unicode !== -1) {
+            this.toFontChar[charCode] = unicode;
+          }
+        }
+      } else {
         glyphsUnicodeMap = getGlyphsUnicode();
         this.toUnicode.forEach(function(charCode, unicodeCharCode) {
-          if (notCidFont) {
+          if (!this.composite) {
             glyphName = (properties.differences[charCode] ||
                          properties.defaultEncoding[charCode]);
-            unicodeCharCode = (glyphsUnicodeMap[glyphName] || unicodeCharCode);
+            unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
+            if (unicode !== -1) {
+              unicodeCharCode = unicode;
+            }
           }
           this.toFontChar[charCode] = unicodeCharCode;
         }.bind(this));
       }
       this.loadedName = fontName.split('-')[0];
       this.loading = false;
       this.fontType = getFontType(type, subtype);
       return;
@@ -25533,16 +25569,21 @@ var Font = (function FontClosure() {
       return String(ID++);
     };
   })();
 
   function int16(b0, b1) {
     return (b0 << 8) + b1;
   }
 
+  function signedInt16(b0, b1) {
+    var value = (b0 << 8) + b1;
+    return value & (1 << 15) ? value - 0x10000 : value;
+  }
+
   function int32(b0, b1, b2, b3) {
     return (b0 << 24) + (b1 << 16) + (b2 << 8) + b3;
   }
 
   function string16(value) {
     return String.fromCharCode((value >> 8) & 0xff, value & 0xff);
   }
 
@@ -27050,19 +27091,19 @@ var Font = (function FontClosure() {
         tables['hhea'].data[11] = 0xFF;
       }
 
       // Extract some more font properties from the OpenType head and
       // hhea tables; yMin and descent value are always negative.
       var metricsOverride = {
         unitsPerEm: int16(tables['head'].data[18], tables['head'].data[19]),
         yMax: int16(tables['head'].data[42], tables['head'].data[43]),
-        yMin: int16(tables['head'].data[38], tables['head'].data[39]) - 0x10000,
+        yMin: signedInt16(tables['head'].data[38], tables['head'].data[39]),
         ascent: int16(tables['hhea'].data[4], tables['hhea'].data[5]),
-        descent: int16(tables['hhea'].data[6], tables['hhea'].data[7]) - 0x10000
+        descent: signedInt16(tables['hhea'].data[6], tables['hhea'].data[7])
       };
 
       // PDF FontDescriptor metrics lie -- using data from actual font.
       this.ascent = metricsOverride.ascent / metricsOverride.unitsPerEm;
       this.descent = metricsOverride.descent / metricsOverride.unitsPerEm;
 
       // The 'post' table has glyphs names.
       if (tables['post']) {
@@ -27088,16 +27129,36 @@ var Font = (function FontClosure() {
           return true;
         }
         if (widths && widthCode >= 0 && isNum(widths[widthCode])) {
           return true;
         }
         return false;
       }
 
+      // Some bad PDF generators, e.g. Scribus PDF, include glyph names
+      // in a 'uniXXXX' format -- attempting to recover proper ones.
+      function recoverGlyphName(name, glyphsUnicodeMap) {
+        if (glyphsUnicodeMap[name] !== undefined) {
+          return name;
+        }
+        // The glyph name is non-standard, trying to recover.
+        var unicode = getUnicodeForGlyph(name, glyphsUnicodeMap);
+        if (unicode !== -1) {
+          for (var key in glyphsUnicodeMap) {
+            if (glyphsUnicodeMap[key] === unicode) {
+              return key;
+            }
+          }
+        }
+        warn('Unable to recover a standard glyph name for: ' + name);
+        return name;
+      }
+
+
       if (properties.type === 'CIDFontType2') {
         var cidToGidMap = properties.cidToGidMap || [];
         var isCidToGidMapEmpty = cidToGidMap.length === 0;
 
         properties.cMap.forEach(function(charCode, cid) {
           assert(cid <= 0xffff, 'Max size of CID is 65,535');
           var glyphId = -1;
           if (isCidToGidMapEmpty) {
@@ -27142,35 +27203,38 @@ var Font = (function FontClosure() {
 
           var baseEncoding = [];
           if (properties.baseEncodingName === 'MacRomanEncoding' ||
               properties.baseEncodingName === 'WinAnsiEncoding') {
             baseEncoding = getEncoding(properties.baseEncodingName);
           }
           var glyphsUnicodeMap = getGlyphsUnicode();
           for (charCode = 0; charCode < 256; charCode++) {
-            var glyphName;
+            var glyphName, standardGlyphName;
             if (this.differences && charCode in this.differences) {
               glyphName = this.differences[charCode];
             } else if (charCode in baseEncoding &&
                        baseEncoding[charCode] !== '') {
               glyphName = baseEncoding[charCode];
             } else {
               glyphName = StandardEncoding[charCode];
             }
             if (!glyphName) {
               continue;
             }
+            // Ensure that non-standard glyph names are resolved to valid ones.
+            standardGlyphName = recoverGlyphName(glyphName, glyphsUnicodeMap);
+
             var unicodeOrCharCode, isUnicode = false;
             if (cmapPlatformId === 3 && cmapEncodingId === 1) {
-              unicodeOrCharCode = glyphsUnicodeMap[glyphName];
+              unicodeOrCharCode = glyphsUnicodeMap[standardGlyphName];
               isUnicode = true;
             } else if (cmapPlatformId === 1 && cmapEncodingId === 0) {
               // TODO: the encoding needs to be updated with mac os table.
-              unicodeOrCharCode = MacRomanEncoding.indexOf(glyphName);
+              unicodeOrCharCode = MacRomanEncoding.indexOf(standardGlyphName);
             }
 
             var found = false;
             for (i = 0; i < cmapMappingsLength; ++i) {
               if (cmapMappings[i].charCode !== unicodeOrCharCode) {
                 continue;
               }
               var code = isUnicode ? charCode : unicodeOrCharCode;
@@ -27178,16 +27242,21 @@ var Font = (function FontClosure() {
                 charCodeToGlyphId[charCode] = cmapMappings[i].glyphId;
                 found = true;
                 break;
               }
             }
             if (!found && properties.glyphNames) {
               // Try to map using the post table.
               var glyphId = properties.glyphNames.indexOf(glyphName);
+              // The post table ought to use the same kind of glyph names as the
+              // `differences` array, but check the standard ones as a fallback.
+              if (glyphId === -1 && standardGlyphName !== glyphName) {
+                glyphId = properties.glyphNames.indexOf(standardGlyphName);
+              }
               if (glyphId > 0 && hasGlyph(glyphId, -1, -1)) {
                 charCodeToGlyphId[charCode] = glyphId;
                 found = true;
               }
             }
             if (!found) {
               charCodeToGlyphId[charCode] = 0; // notdef
             }
@@ -27491,16 +27560,22 @@ var Font = (function FontClosure() {
                 }
                 break;
               case 'C': // Cddd glyph
               case 'c': // cddd glyph
                 if (glyphName.length >= 3) {
                   code = +glyphName.substr(1);
                 }
                 break;
+              default:
+                // 'uniXXXX'/'uXXXX{XX}' glyphs
+                var unicode = getUnicodeForGlyph(glyphName, glyphsUnicodeMap);
+                if (unicode !== -1) {
+                  code = unicode;
+                }
             }
             if (code) {
               // If |baseEncodingName| is one the predefined encodings,
               // and |code| equals |charcode|, using the glyph defined in the
               // baseEncoding seems to yield a better |toUnicode| mapping
               // (fixes issue 5070).
               if (baseEncodingName && code === +charcode) {
                 var baseEncoding = getEncoding(baseEncodingName);
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -710,17 +710,17 @@ Preferences._readFromStorage = function 
 
 var DEFAULT_VIEW_HISTORY_CACHE_SIZE = 20;
 
 /**
  * View History - This is a utility for saving various view parameters for
  *                recently opened files.
  *
  * The way that the view parameters are stored depends on how PDF.js is built,
- * for 'node make <flag>' the following cases exist:
+ * for 'gulp <flag>' the following cases exist:
  *  - FIREFOX or MOZCENTRAL - uses sessionStorage.
  *  - GENERIC or CHROME     - uses localStorage, if it is available.
  */
 var ViewHistory = (function ViewHistoryClosure() {
   function ViewHistory(fingerprint, cacheSize) {
     this.fingerprint = fingerprint;
     this.cacheSize = cacheSize || DEFAULT_VIEW_HISTORY_CACHE_SIZE;
     this.isInitializedPromiseResolved = false;
@@ -5175,16 +5175,19 @@ var THUMBNAIL_CANVAS_BORDER_WIDTH = 1; /
 
 /**
  * @typedef {Object} PDFThumbnailViewOptions
  * @property {HTMLDivElement} container - The viewer element.
  * @property {number} id - The thumbnail's unique ID (normally its number).
  * @property {PageViewport} defaultViewport - The page viewport.
  * @property {IPDFLinkService} linkService - The navigation/linking service.
  * @property {PDFRenderingQueue} renderingQueue - The rendering queue object.
+ * @property {boolean} disableCanvasToImageConversion - (optional) Don't convert
+ *   the canvas thumbnails to images. This prevents `toDataURL` calls,
+ *   but increases the overall memory usage. The default value is false.
  */
 
 /**
  * @class
  * @implements {IRenderableView}
  */
 var PDFThumbnailView = (function PDFThumbnailViewClosure() {
   function getTempCanvas(width, height) {
@@ -5192,17 +5195,17 @@ var PDFThumbnailView = (function PDFThum
     if (!tempCanvas) {
       tempCanvas = document.createElement('canvas');
       PDFThumbnailView.tempImageCache = tempCanvas;
     }
     tempCanvas.width = width;
     tempCanvas.height = height;
 
     // Since this is a temporary canvas, we need to fill the canvas with a white
-    // background ourselves. |_getPageDrawContext| uses CSS rules for this.
+    // background ourselves. `_getPageDrawContext` uses CSS rules for this.
     tempCanvas.mozOpaque = true;
     var ctx = tempCanvas.getContext('2d', {alpha: false});
     ctx.save();
     ctx.fillStyle = 'rgb(255, 255, 255)';
     ctx.fillRect(0, 0, width, height);
     ctx.restore();
     return tempCanvas;
   }
@@ -5212,30 +5215,33 @@ var PDFThumbnailView = (function PDFThum
    * @param {PDFThumbnailViewOptions} options
    */
   function PDFThumbnailView(options) {
     var container = options.container;
     var id = options.id;
     var defaultViewport = options.defaultViewport;
     var linkService = options.linkService;
     var renderingQueue = options.renderingQueue;
+    var disableCanvasToImageConversion =
+      options.disableCanvasToImageConversion || false;
 
     this.id = id;
     this.renderingId = 'thumbnail' + id;
 
     this.pdfPage = null;
     this.rotation = 0;
     this.viewport = defaultViewport;
     this.pdfPageRotate = defaultViewport.rotation;
 
     this.linkService = linkService;
     this.renderingQueue = renderingQueue;
 
     this.resume = null;
     this.renderingState = RenderingStates.INITIAL;
+    this.disableCanvasToImageConversion = disableCanvasToImageConversion;
 
     this.pageWidth = this.viewport.width;
     this.pageHeight = this.viewport.height;
     this.pageRatio = this.pageWidth / this.pageHeight;
 
     this.canvasWidth = THUMBNAIL_WIDTH;
     this.canvasHeight = (this.canvasWidth / this.pageRatio) | 0;
     this.scale = this.canvasWidth / this.pageWidth;
@@ -5330,56 +5336,72 @@ var PDFThumbnailView = (function PDFThum
     },
 
     /**
      * @private
      */
     _getPageDrawContext:
         function PDFThumbnailView_getPageDrawContext(noCtxScale) {
       var canvas = document.createElement('canvas');
+      // Keep the no-thumbnail outline visible, i.e. `data-loaded === false`,
+      // until rendering/image conversion is complete, to avoid display issues.
       this.canvas = canvas;
 
       canvas.mozOpaque = true;
       var ctx = canvas.getContext('2d', {alpha: false});
       var outputScale = getOutputScale(ctx);
 
       canvas.width = (this.canvasWidth * outputScale.sx) | 0;
       canvas.height = (this.canvasHeight * outputScale.sy) | 0;
       canvas.style.width = this.canvasWidth + 'px';
       canvas.style.height = this.canvasHeight + 'px';
 
       if (!noCtxScale && outputScale.scaled) {
         ctx.scale(outputScale.sx, outputScale.sy);
       }
-
-      var image = document.createElement('img');
-      this.image = image;
-
-      image.id = this.renderingId;
-      image.className = 'thumbnailImage';
-      image.setAttribute('aria-label', mozL10n.get('thumb_page_canvas',
-        { page: this.id }, 'Thumbnail of Page {{page}}'));
-
-      image.style.width = canvas.style.width;
-      image.style.height = canvas.style.height;
-
       return ctx;
     },
 
     /**
      * @private
      */
     _convertCanvasToImage: function PDFThumbnailView_convertCanvasToImage() {
       if (!this.canvas) {
         return;
       }
-      this.image.src = this.canvas.toDataURL();
+      if (this.renderingState !== RenderingStates.FINISHED) {
+        return;
+      }
+      var id = this.renderingId;
+      var className = 'thumbnailImage';
+      var ariaLabel = mozL10n.get('thumb_page_canvas', { page: this.id },
+                                  'Thumbnail of Page {{page}}');
+
+      if (this.disableCanvasToImageConversion) {
+        this.canvas.id = id;
+        this.canvas.className = className;
+        this.canvas.setAttribute('aria-label', ariaLabel);
+
+        this.div.setAttribute('data-loaded', true);
+        this.ring.appendChild(this.canvas);
+        return;
+      }
+      var image = document.createElement('img');
+      image.id = id;
+      image.className = className;
+      image.setAttribute('aria-label', ariaLabel);
+
+      image.style.width = this.canvasWidth + 'px';
+      image.style.height = this.canvasHeight + 'px';
+
+      image.src = this.canvas.toDataURL();
+      this.image = image;
 
       this.div.setAttribute('data-loaded', true);
-      this.ring.appendChild(this.image);
+      this.ring.appendChild(image);
 
       // Zeroing the width and height causes Firefox to release graphics
       // resources immediately, which can greatly reduce memory consumption.
       this.canvas.width = 0;
       this.canvas.height = 0;
       delete this.canvas;
     },
 
@@ -5628,17 +5650,18 @@ var PDFThumbnailViewer = (function PDFTh
         var pagesCount = pdfDocument.numPages;
         var viewport = firstPage.getViewport(1.0);
         for (var pageNum = 1; pageNum <= pagesCount; ++pageNum) {
           var thumbnail = new PDFThumbnailView({
             container: this.container,
             id: pageNum,
             defaultViewport: viewport.clone(),
             linkService: this.linkService,
-            renderingQueue: this.renderingQueue
+            renderingQueue: this.renderingQueue,
+            disableCanvasToImageConversion: false,
           });
           this.thumbnails.push(thumbnail);
         }
       }.bind(this));
     },
 
     /**
      * @param {PDFPageView} pageView
--- a/browser/modules/Social.jsm
+++ b/browser/modules/Social.jsm
@@ -23,18 +23,16 @@ Cu.import("resource://gre/modules/XPCOMU
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "SocialService",
   "resource://gre/modules/SocialService.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PageMetadata",
   "resource://gre/modules/PageMetadata.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils",
   "resource://gre/modules/PlacesUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
-  "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Promise",
   "resource://gre/modules/Promise.jsm");
 
 
 function promiseSetAnnotation(aURI, providerList) {
   let deferred = Promise.defer();
 
   // Delaying to catch issues with asynchronous behavior while waiting
@@ -90,17 +88,17 @@ this.Social = {
     }
     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);
+        Social._updateEnabledState(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) {
@@ -108,40 +106,39 @@ this.Social = {
       // 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(true);
+        Social._updateEnabledState(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
+        // a provider was removed from the list of providers, update states
         Social._updateProviderCache(providers);
-        Social._updateWorkerState(providers.length > 0);
+        Social._updateEnabledState(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) {
+  _updateEnabledState: function(enable) {
     for (let p of Social.providers) {
       p.enabled = enable;
     }
   },
 
   // Called to update our cache of providers and set the current provider
   _updateProviderCache: function (providers) {
     this.providers = providers;
@@ -270,19 +267,16 @@ function CreateSocialStatusWidget(aId, a
       node.setAttribute("class", "toolbarbutton-1 chromeclass-toolbar-additional social-status-button badged-button");
       node.style.listStyleImage = "url(" + (aProvider.icon32URL || aProvider.iconURL) + ")";
       node.setAttribute("origin", aProvider.origin);
       node.setAttribute("label", aProvider.name);
       node.setAttribute("tooltiptext", aProvider.name);
       node.setAttribute("oncommand", "SocialStatus.showPopup(this);");
       node.setAttribute("constrain-size", "true");
 
-      if (PrivateBrowsingUtils.isWindowPrivate(aDocument.defaultView))
-        node.setAttribute("disabled", "true");
-
       return node;
     }
   });
 }
 
 function CreateSocialMarkWidget(aId, aProvider) {
   if (!aProvider.markURL)
     return;
--- a/devtools/client/debugger/content/views/sources-view.js
+++ b/devtools/client/debugger/content/views/sources-view.js
@@ -1175,19 +1175,20 @@ SourcesView.prototype = Heritage.extend(
     }
   },
 
   /**
    * Called when the add conditional breakpoint key sequence was pressed.
    */
   _onCmdAddConditionalBreakpoint: function(e) {
     let actor = this.selectedValue;
-    let line = (e && e.sourceEvent.target.tagName == 'menuitem' ?
+    let line = (this.DebuggerView.clickedLine ?
                 this.DebuggerView.clickedLine + 1 :
                 this.DebuggerView.editor.getCursor().line + 1);
+
     let location = { actor, line };
     let bp = getBreakpoint(this.getState(), location);
 
     // If a breakpoint already existed or wasn't a conditional, morph it now.
     if (bp) {
       this.highlightBreakpoint(bp.location, { openPopup: true });
     }
     // No breakpoint existed at the required location, add one now.
--- a/devtools/client/definitions.js
+++ b/devtools/client/definitions.js
@@ -55,17 +55,17 @@ loader.lazyGetter(this, "memoryStrings",
 
 var Tools = {};
 exports.Tools = Tools;
 
 // Definitions
 Tools.options = {
   id: "options",
   ordinal: 0,
-  url: "chrome://devtools/content/framework/toolbox-options.xul",
+  url: "chrome://devtools/content/framework/toolbox-options.xhtml",
   icon: "chrome://devtools/skin/images/tool-options.svg",
   invertIconForLightTheme: true,
   bgTheme: "theme-body",
   label: l10n("options.label", toolboxStrings),
   iconOnly: true,
   panelLabel: l10n("options.panelLabel", toolboxStrings),
   tooltip: l10n("optionsButton.tooltip", toolboxStrings),
   inMenu: false,
--- a/devtools/client/framework/options-panel.css
+++ b/devtools/client/framework/options-panel.css
@@ -1,80 +1,107 @@
 /* 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/. */
+:root{
+  -moz-user-select: none;
+}
 
 #options-panel-container {
   overflow: auto;
 }
 
 #options-panel {
   display: block;
 }
 
 .options-vertical-pane {
   display: inline;
   float: left;
 }
 
-.options-vertical-pane > label {
-  display: block;
-}
-
 .options-vertical-pane {
   margin: 5px;
-  width: calc(100%/3 - 30px);
+  width: calc(100%/3 - 10px);
   min-width: 320px;
-  -moz-padding-start: 5px;
-}
-
-#devtools-theme-box {
-  margin-left: 0px;
-  padding-left: 0px;
+  padding-inline-start: 5px;
+  box-sizing: border-box;
 }
 
 /* Snap to 50% width once there is not room for 3 columns anymore.
    This prevents having 2 columns showing in a row, but taking up
    only ~66% of the available space. */
 @media (max-width: 1000px) {
   .options-vertical-pane {
-    width: calc(100%/2 - 30px);
+    width: calc(100%/2 - 10px);
   }
 }
 
-.options-vertical-pane > label {
-  padding: 2px 0;
+.options-vertical-pane fieldset {
+  border: none;
+}
+
+.options-vertical-pane fieldset legend {
   font-size: 1.4rem;
+  margin-inline-start: -15px;
+  margin-bottom: 3px;
+  cursor: default;
+}
+
+.options-vertical-pane fieldset + fieldset {
+  margin-top: 1rem;
 }
 
 .options-groupbox {
-  -moz-margin-start: 15px;
+  margin-inline-start: 15px;
   padding: 2px;
 }
 
+.options-groupbox label {
+  display: flex;
+  padding: 4px 0;
+  align-items: center;
+}
+
+/* Add padding for label of select inputs in order to
+   align it with surrounding checkboxes */
+.options-groupbox label span:first-child {
+  padding-inline-start: 5px;
+}
+
+.options-groupbox label span + select {
+  margin-inline-start: 4px;
+}
+
+.options-groupbox.horizontal-options-groupbox label {
+  display: inline-flex;
+  align-items: baseline;
+}
+
+.options-groupbox.horizontal-options-groupbox label + label {
+  margin-inline-start: 4px;
+}
+
 .options-groupbox > *,
 .options-groupbox > .hidden-labels-box > checkbox {
   padding: 2px;
 }
 
 .options-groupbox > .hidden-labels-box {
   padding: 0;
 }
 
 .options-citation-label {
-  font-size: 1rem !important;
-  /* !important is required otherwise font-size will still be 1.4rem */
+  display: inline-block;
+  font-size: 1rem;
   font-style: italic;
-  padding: 4px 0 0; /* To align it with the checkbox */
+   /* To align it with the checkbox */
+  padding: 4px 0 0;
+  padding-inline-end: 4px;
 }
 
-.hidden-labels-box:not(.visible) > label,
-.hidden-labels-box.visible ~ .hidden-labels-box > label:last-child {
-  display: none;
+#devtools-sourceeditor-keybinding-select {
+  min-width: 130px;
 }
 
-#devtools-sourceeditor-keybinding-menulist {
-  min-width: 100px;
+#devtools-sourceeditor-tabsize-select {
+  min-width: 80px;
 }
-
-#devtools-sourceeditor-tabsize-menulist {
-  min-width: 50px;
-}
--- a/devtools/client/framework/test/browser_toolbox_options.js
+++ b/devtools/client/framework/test/browser_toolbox_options.js
@@ -1,20 +1,24 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* import-globals-from shared-head.js */
+"use strict";
+
 // Tests that changing preferences in the options panel updates the prefs
 // and toggles appropriate things in the toolbox.
 
 var doc = null, toolbox = null, panelWin = null, modifiedPrefs = [];
 
 add_task(function*() {
-  const URL = "data:text/html;charset=utf8,test for dynamically registering and unregistering tools";
+  const URL = "data:text/html;charset=utf8,test for dynamically registering " +
+              "and unregistering tools";
   registerNewTool();
   let tab = yield addTab(URL);
   let target = TargetFactory.forTab(tab);
   toolbox = yield gDevTools.showToolbox(target);
   doc = toolbox.doc;
   yield testSelectTool();
   yield testOptionsShortcut();
   yield testOptions();
@@ -36,26 +40,26 @@ function registerNewTool() {
     "The tool is not registered");
 
   gDevTools.registerTool(toolDefinition);
   ok(gDevTools.getToolDefinitionMap().has("test-tool"),
     "The tool is registered");
 }
 
 function* testSelectTool() {
-  info ("Checking to make sure that the options panel can be selected.");
+  info("Checking to make sure that the options panel can be selected.");
 
   let onceSelected = toolbox.once("options-selected");
   toolbox.selectTool("options");
   yield onceSelected;
   ok(true, "Toolbox selected via selectTool method");
 }
 
 function* testOptionsShortcut() {
-  info ("Selecting another tool, then reselecting options panel with keyboard.");
+  info("Selecting another tool, then reselecting options panel with keyboard.");
 
   yield toolbox.selectTool("webconsole");
   is(toolbox.currentToolId, "webconsole", "webconsole is selected");
   synthesizeKeyFromKeyTag(doc.getElementById("toolbox-options-key"));
   is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key (1)");
   synthesizeKeyFromKeyTag(doc.getElementById("toolbox-options-key"));
   is(toolbox.currentToolId, "webconsole", "webconsole is selected (1)");
 
@@ -67,17 +71,18 @@ function* testOptionsShortcut() {
   is(toolbox.currentToolId, "webconsole", "webconsole is reselected (2)");
   synthesizeKeyFromKeyTag(doc.getElementById("toolbox-options-key2"));
   is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key (2)");
 }
 
 function* testOptions() {
   let tool = toolbox.getPanel("options");
   panelWin = tool.panelWin;
-  let prefNodes = tool.panelDoc.querySelectorAll("checkbox[data-pref]");
+  let prefNodes = tool.panelDoc.querySelectorAll(
+    "input[type=checkbox][data-pref]");
 
   // Store modified pref names so that they can be cleared on error.
   for (let node of tool.panelDoc.querySelectorAll("[data-pref]")) {
     let pref = node.getAttribute("data-pref");
     modifiedPrefs.push(pref);
   }
 
   for (let node of prefNodes) {
@@ -85,50 +90,49 @@ function* testOptions() {
 
     // Test clicking the checkbox for each options pref
     yield testMouseClick(node, prefValue);
 
     // Do again with opposite values to reset prefs
     yield testMouseClick(node, !prefValue);
   }
 
-  let prefDropdowns = tool.panelDoc.querySelectorAll("menulist[data-pref]");
-  for (let node of prefDropdowns) {
-    yield testMenuList(node);
+  let prefSelects = tool.panelDoc.querySelectorAll("select[data-pref]");
+  for (let node of prefSelects) {
+    yield testSelect(node);
   }
 }
 
-function* testMenuList(menulist) {
-  let pref = menulist.getAttribute("data-pref");
-  let menuitems = menulist.querySelectorAll("menuitem");
-  info ("Checking menu list for: " + pref);
+function* testSelect(select) {
+  let pref = select.getAttribute("data-pref");
+  let options = Array.from(select.options);
+  info("Checking select for: " + pref);
 
-  is (menulist.selectedItem.value, GetPref(pref), "Menu starts out selected");
+  is(select.options[select.selectedIndex].value, GetPref(pref),
+    "select starts out selected");
 
-  for (let menuitem of menuitems) {
-    if (menuitem === menulist.selectedItem) {
+  for (let option of options) {
+    if (options.indexOf(option) === select.selectedIndex) {
       continue;
     }
 
     let deferred = promise.defer();
     gDevTools.once("pref-changed", (event, data) => {
       if (data.pref == pref) {
         ok(true, "Correct pref was changed");
-        is (GetPref(pref), menuitem.value, "Preference been switched for " + pref);
+        is(GetPref(pref), option.value, "Preference been switched for " + pref);
       } else {
         ok(false, "Pref " + pref + " was not changed correctly");
       }
       deferred.resolve();
     });
 
-    menulist.selectedItem = menuitem;
-    let commandEvent = menulist.ownerDocument.createEvent("XULCommandEvent");
-    commandEvent.initCommandEvent("command", true, true, window, 0, false, false,
-                                  false, false, null);
-    menulist.dispatchEvent(commandEvent);
+    select.selectedIndex = options.indexOf(option);
+    let changeEvent = new Event("change");
+    select.dispatchEvent(changeEvent);
 
     yield deferred.promise;
   }
 }
 
 function* testMouseClick(node, prefValue) {
   let deferred = promise.defer();
 
@@ -152,26 +156,28 @@ function* testMouseClick(node, prefValue
     info("Click event synthesized for pref " + pref);
     EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
   });
 
   yield deferred.promise;
 }
 
 function* testToggleTools() {
-  let toolNodes = panelWin.document.querySelectorAll("#default-tools-box > checkbox:not([unsupported]), #additional-tools-box > checkbox:not([unsupported])");
+  let toolNodes = panelWin.document.querySelectorAll(
+    "#default-tools-box input[type=checkbox]:not([data-unsupported])," +
+    "#additional-tools-box input[type=checkbox]:not([data-unsupported])");
   let enabledTools = [...toolNodes].filter(node => node.checked);
 
   let toggleableTools = gDevTools.getDefaultTools().filter(tool => {
     return tool.visibilityswitch;
   }).concat(gDevTools.getAdditionalTools());
 
   for (let node of toolNodes) {
     let id = node.getAttribute("id");
-    ok (toggleableTools.some(tool => tool.id === id),
+    ok(toggleableTools.some(tool => tool.id === id),
       "There should be a toggle checkbox for: " + id);
   }
 
   // Store modified pref names so that they can be cleared on error.
   for (let tool of toggleableTools) {
     let pref = tool.visibilityswitch;
     modifiedPrefs.push(pref);
   }
@@ -192,49 +198,51 @@ function* testToggleTools() {
   }
   // Re-enable the tools which are enabled by default
   for (let node of enabledTools) {
     yield toggleTool(node);
   }
 
   // Toggle first, middle, and last tools to ensure that toolbox tabs are
   // inserted in order
-  let firstTool  = toolNodes[0],
-      middleTool = toolNodes[(toolNodes.length / 2) | 0],
-      lastTool   = toolNodes[toolNodes.length - 1];
+  let firstTool = toolNodes[0];
+  let middleTool = toolNodes[(toolNodes.length / 2) | 0];
+  let lastTool = toolNodes[toolNodes.length - 1];
 
   yield toggleTool(firstTool);
   yield toggleTool(firstTool);
   yield toggleTool(middleTool);
   yield toggleTool(middleTool);
   yield toggleTool(lastTool);
   yield toggleTool(lastTool);
 }
 
 function* toggleTool(node) {
   let deferred = promise.defer();
 
   let toolId = node.getAttribute("id");
-  let onRegistrationChange;
   if (node.checked) {
-    gDevTools.once("tool-unregistered", checkUnregistered.bind(null, toolId, deferred));
+    gDevTools.once("tool-unregistered",
+      checkUnregistered.bind(null, toolId, deferred));
   } else {
-    gDevTools.once("tool-registered", checkRegistered.bind(null, toolId, deferred));
+    gDevTools.once("tool-registered",
+      checkRegistered.bind(null, toolId, deferred));
   }
   node.scrollIntoView();
   EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
 
   yield deferred.promise;
 }
 
 function checkUnregistered(toolId, deferred, event, data) {
   if (data.id == toolId) {
     ok(true, "Correct tool removed");
     // checking tab on the toolbox
-    ok(!doc.getElementById("toolbox-tab-" + toolId), "Tab removed for " + toolId);
+    ok(!doc.getElementById("toolbox-tab-" + toolId),
+      "Tab removed for " + toolId);
   } else {
     ok(false, "Something went wrong, " + toolId + " was not unregistered");
   }
   deferred.resolve();
 }
 
 function checkRegistered(toolId, deferred, event, data) {
   if (data == toolId) {
--- a/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js
+++ b/devtools/client/framework/test/browser_toolbox_options_disable_buttons.js
@@ -1,148 +1,159 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* import-globals-from shared-head.js */
+"use strict";
+
 var doc = null, toolbox = null, panelWin = null, modifiedPrefs = [];
 
 function test() {
   gBrowser.selectedTab = gBrowser.addTab();
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
   gBrowser.selectedBrowser.addEventListener("load", function onLoad(evt) {
     gBrowser.selectedBrowser.removeEventListener(evt.type, onLoad, true);
     gDevTools.showToolbox(target)
       .then(testSelectTool)
       .then(testToggleToolboxButtons)
       .then(testPrefsAreRespectedWhenReopeningToolbox)
       .then(cleanup, errorHandler);
   }, true);
 
-  content.location = "data:text/html;charset=utf8,test for dynamically registering and unregistering tools";
+  content.location = "data:text/html;charset=utf8,test for dynamically " +
+                     "registering and unregistering tools";
 }
 
 function testPrefsAreRespectedWhenReopeningToolbox() {
   let deferred = promise.defer();
   let target = TargetFactory.forTab(gBrowser.selectedTab);
 
-  info ("Closing toolbox to test after reopening");
+  info("Closing toolbox to test after reopening");
   gDevTools.closeToolbox(target).then(() => {
-    let target = TargetFactory.forTab(gBrowser.selectedTab);
-    gDevTools.showToolbox(target)
+    let tabTarget = TargetFactory.forTab(gBrowser.selectedTab);
+    gDevTools.showToolbox(tabTarget)
       .then(testSelectTool)
       .then(() => {
-        info ("Toolbox has been reopened.  Checking UI state.");
+        info("Toolbox has been reopened.  Checking UI state.");
         testPreferenceAndUIStateIsConsistent();
         deferred.resolve();
       });
   });
 
   return deferred.promise;
 }
 
-function testSelectTool(aToolbox) {
+function testSelectTool(devtoolsToolbox) {
   let deferred = promise.defer();
-  info ("Selecting the options panel");
+  info("Selecting the options panel");
 
-  toolbox = aToolbox;
+  toolbox = devtoolsToolbox;
   doc = toolbox.doc;
   toolbox.once("options-selected", (event, tool) => {
     ok(true, "Options panel selected via selectTool method");
     panelWin = tool.panelWin;
     deferred.resolve();
   });
   toolbox.selectTool("options");
 
   return deferred.promise;
 }
 
 function testPreferenceAndUIStateIsConsistent() {
-  let checkNodes = [...panelWin.document.querySelectorAll("#enabled-toolbox-buttons-box > checkbox")];
+  let checkNodes = [...panelWin.document.querySelectorAll(
+    "#enabled-toolbox-buttons-box input[type=checkbox]")];
   let toolboxButtonNodes = [...doc.querySelectorAll(".command-button")];
   toolboxButtonNodes.push(doc.getElementById("command-button-frames"));
   let toggleableTools = toolbox.toolboxButtons;
 
   // The noautohide button is only displayed in the browser toolbox
-  toggleableTools = toggleableTools.filter(tool => tool.id != "command-button-noautohide");
+  toggleableTools = toggleableTools.filter(
+    tool => tool.id != "command-button-noautohide");
 
   for (let tool of toggleableTools) {
     let isVisible = getBoolPref(tool.visibilityswitch);
 
-    let button = toolboxButtonNodes.filter(button => button.id === tool.id)[0];
-    is (!button.hasAttribute("hidden"), isVisible, "Button visibility matches pref for " + tool.id);
+    let button = toolboxButtonNodes.filter(
+      toolboxButton => toolboxButton.id === tool.id)[0];
+    is(!button.hasAttribute("hidden"), isVisible,
+      "Button visibility matches pref for " + tool.id);
 
-    let check = checkNodes.filter(node=>node.id === tool.id)[0];
-    is (check.checked, isVisible, "Checkbox should be selected based on current pref for " + tool.id);
+    let check = checkNodes.filter(node => node.id === tool.id)[0];
+    is(check.checked, isVisible,
+      "Checkbox should be selected based on current pref for " + tool.id);
   }
 }
 
 function testToggleToolboxButtons() {
-  let checkNodes = [...panelWin.document.querySelectorAll("#enabled-toolbox-buttons-box > checkbox")];
+  let checkNodes = [...panelWin.document.querySelectorAll(
+    "#enabled-toolbox-buttons-box input[type=checkbox]")];
   let toolboxButtonNodes = [...doc.querySelectorAll(".command-button")];
   let toggleableTools = toolbox.toolboxButtons;
 
   // The noautohide button is only displayed in the browser toolbox
-  toggleableTools = toggleableTools.filter(tool => tool.id != "command-button-noautohide");
-  toolboxButtonNodes = toolboxButtonNodes.filter(btn => btn.id != "command-button-noautohide");
+  toggleableTools = toggleableTools.filter(
+    tool => tool.id != "command-button-noautohide");
+  toolboxButtonNodes = toolboxButtonNodes.filter(
+    btn => btn.id != "command-button-noautohide");
 
-  is (checkNodes.length, toggleableTools.length, "All of the buttons are toggleable." );
-  is (checkNodes.length, toolboxButtonNodes.length, "All of the DOM buttons are toggleable." );
+  is(checkNodes.length, toggleableTools.length,
+    "All of the buttons are toggleable.");
+  is(checkNodes.length, toolboxButtonNodes.length,
+    "All of the DOM buttons are toggleable.");
 
   for (let tool of toggleableTools) {
     let id = tool.id;
-    let matchedCheckboxes = checkNodes.filter(node=>node.id === id);
-    let matchedButtons = toolboxButtonNodes.filter(button=>button.id === id);
-    is (matchedCheckboxes.length, 1,
+    let matchedCheckboxes = checkNodes.filter(node => node.id === id);
+    let matchedButtons = toolboxButtonNodes.filter(button => button.id === id);
+    is(matchedCheckboxes.length, 1,
       "There should be a single toggle checkbox for: " + id);
-    is (matchedButtons.length, 1,
+    is(matchedButtons.length, 1,
       "There should be a DOM button for: " + id);
-    is (matchedButtons[0], tool.button,
+    is(matchedButtons[0], tool.button,
       "DOM buttons should match for: " + id);
 
-    is (matchedCheckboxes[0].getAttribute("label"), tool.label,
+    is(matchedCheckboxes[0].nextSibling.textContent, tool.label,
       "The label for checkbox matches the tool definition.");
-    is (matchedButtons[0].getAttribute("tooltiptext"), tool.label,
+    is(matchedButtons[0].getAttribute("tooltiptext"), tool.label,
       "The tooltip for button matches the tool definition.");
   }
 
   // Store modified pref names so that they can be cleared on error.
   for (let tool of toggleableTools) {
     let pref = tool.visibilityswitch;
     modifiedPrefs.push(pref);
   }
 
   // Try checking each checkbox, making sure that it changes the preference
   for (let node of checkNodes) {
-    let tool = toggleableTools.filter(tool=>tool.id === node.id)[0];
+    let tool = toggleableTools.filter(
+      toggleableTool => toggleableTool.id === node.id)[0];
     let isVisible = getBoolPref(tool.visibilityswitch);
 
     testPreferenceAndUIStateIsConsistent();
-    toggleButton(node);
+    node.click();
     testPreferenceAndUIStateIsConsistent();
 
     let isVisibleAfterClick = getBoolPref(tool.visibilityswitch);
 
-    is (isVisible, !isVisibleAfterClick,
-      "Clicking on the node should have toggled visibility preference for " + tool.visibilityswitch);
+    is(isVisible, !isVisibleAfterClick,
+      "Clicking on the node should have toggled visibility preference for " +
+      tool.visibilityswitch);
   }
 
   return promise.resolve();
 }
 
 function getBoolPref(key) {
   return Services.prefs.getBoolPref(key);
 }
 
-function toggleButton(node) {
-  node.scrollIntoView();
-  EventUtils.synthesizeMouseAtCenter(node, {}, panelWin);
-}
-
 function cleanup() {
   toolbox.destroy().then(function() {
     gBrowser.removeCurrentTab();
     for (let pref of modifiedPrefs) {
       Services.prefs.clearUserPref(pref);
     }
     toolbox = doc = panelWin = modifiedPrefs = null;
     finish();
--- a/devtools/client/framework/test/browser_toolbox_theme_registration.js
+++ b/devtools/client/framework/test/browser_toolbox_theme_registration.js
@@ -1,87 +1,100 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
+/* import-globals-from shared-head.js */
+"use strict";
+
 // Test for dynamically registering and unregistering themes
 const CHROME_URL = "chrome://mochitests/content/browser/devtools/client/framework/test/";
 
 var toolbox;
 
 add_task(function* themeRegistration() {
   let tab = yield addTab("data:text/html,test");
   let target = TargetFactory.forTab(tab);
   toolbox = yield gDevTools.showToolbox(target, "options");
 
   let themeId = yield new Promise(resolve => {
-    gDevTools.once("theme-registered", (e, themeId) => {
-      resolve(themeId);
+    gDevTools.once("theme-registered", (e, registeredThemeId) => {
+      resolve(registeredThemeId);
     });
 
     gDevTools.registerTheme({
       id: "test-theme",
       label: "Test theme",
       stylesheets: [CHROME_URL + "doc_theme.css"],
       classList: ["theme-test"],
     });
   });
 
   is(themeId, "test-theme", "theme-registered event handler sent theme id");
 
   ok(gDevTools.getThemeDefinitionMap().has(themeId), "theme added to map");
 });
 
 add_task(function* themeInOptionsPanel() {
-  let panel = toolbox.getCurrentPanel();
   let panelWin = toolbox.getCurrentPanel().panelWin;
   let doc = panelWin.frameElement.contentDocument;
-  let themeOption = doc.querySelector("#devtools-theme-box > radio[value=test-theme]");
+  let themeBox = doc.getElementById("devtools-theme-box");
+  let testThemeOption = themeBox.querySelector(
+    "input[type=radio][value=test-theme]");
 
-  ok(themeOption, "new theme exists in the Options panel");
+  ok(testThemeOption, "new theme exists in the Options panel");
 
-  let testThemeOption = doc.querySelector("#devtools-theme-box > radio[value=test-theme]");
-  let lightThemeOption = doc.querySelector("#devtools-theme-box > radio[value=light]");
+  let lightThemeOption = themeBox.querySelector(
+    "input[type=radio][value=light]");
 
-  let color = panelWin.getComputedStyle(testThemeOption).color;
+  let color = panelWin.getComputedStyle(themeBox).color;
   isnot(color, "rgb(255, 0, 0)", "style unapplied");
 
+  let onThemeSwithComplete = once(panelWin, "theme-switch-complete");
+
   // Select test theme.
   testThemeOption.click();
 
   info("Waiting for theme to finish loading");
-  yield once(panelWin, "theme-switch-complete");
+  yield onThemeSwithComplete;
 
-  color = panelWin.getComputedStyle(testThemeOption).color;
+  color = panelWin.getComputedStyle(themeBox).color;
   is(color, "rgb(255, 0, 0)", "style applied");
 
+  onThemeSwithComplete = once(panelWin, "theme-switch-complete");
+
   // Select light theme
   lightThemeOption.click();
 
   info("Waiting for theme to finish loading");
-  yield once(panelWin, "theme-switch-complete");
+  yield onThemeSwithComplete;
 
-  color = panelWin.getComputedStyle(testThemeOption).color;
+  color = panelWin.getComputedStyle(themeBox).color;
   isnot(color, "rgb(255, 0, 0)", "style unapplied");
 
+  onThemeSwithComplete = once(panelWin, "theme-switch-complete");
   // Select test theme again.
   testThemeOption.click();
+  yield onThemeSwithComplete;
 });
 
 add_task(function* themeUnregistration() {
+  let onUnRegisteredTheme = once(gDevTools, "theme-unregistered");
   gDevTools.unregisterTheme("test-theme");
+  yield onUnRegisteredTheme;
 
-  ok(!gDevTools.getThemeDefinitionMap().has("test-theme"), "theme removed from map");
+  ok(!gDevTools.getThemeDefinitionMap().has("test-theme"),
+    "theme removed from map");
 
   let panelWin = toolbox.getCurrentPanel().panelWin;
   let doc = panelWin.frameElement.contentDocument;
-  let themeBox = doc.querySelector("#devtools-theme-box");
+  let themeBox = doc.getElementById("devtools-theme-box");
 
   // The default light theme must be selected now.
-  is(themeBox.selectedItem, themeBox.querySelector("[value=light]"),
-    "theme light must be selected");
+  is(themeBox.querySelector("#devtools-theme-box [value=light]").checked, true,
+    "light theme must be selected");
 });
 
 add_task(function* cleanup() {
   yield toolbox.destroy();
   toolbox = null;
 });
--- a/devtools/client/framework/test/doc_theme.css
+++ b/devtools/client/framework/test/doc_theme.css
@@ -1,3 +1,3 @@
-.theme-test #devtools-theme-box radio {
+.theme-test #devtools-theme-box {
   color: red !important;
 }
--- a/devtools/client/framework/toolbox-options.js
+++ b/devtools/client/framework/toolbox-options.js
@@ -1,36 +1,35 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {Cu, Cc, Ci} = require("chrome");
+const {Cu} = require("chrome");
 const Services = require("Services");
 const promise = require("promise");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Task.jsm");
 const {gDevTools} = require("devtools/client/framework/devtools");
 
 exports.OptionsPanel = OptionsPanel;
 
 XPCOMUtils.defineLazyGetter(this, "l10n", function() {
   let bundle = Services.strings.createBundle("chrome://devtools/locale/toolbox.properties");
-  let l10n = function(aName, ...aArgs) {
+  let l10n = function(name, ...aArgs) {
     try {
       if (aArgs.length == 0) {
-        return bundle.GetStringFromName(aName);
-      } else {
-        return bundle.formatStringFromName(aName, aArgs, aArgs.length);
+        return bundle.GetStringFromName(name);
       }
+      return bundle.formatStringFromName(name, aArgs, aArgs.length);
     } catch (ex) {
-      Services.console.logStringMessage("Error reading '" + aName + "'");
+      Services.console.logStringMessage("Error reading '" + name + "'");
     }
   };
   return l10n;
 });
 
 function GetPref(name) {
   let type = Services.prefs.getPrefType(name);
   switch (type) {
@@ -62,33 +61,33 @@ function SetPref(name, value) {
 function InfallibleGetBoolPref(key) {
   try {
     return Services.prefs.getBoolPref(key);
   } catch (ex) {
     return true;
   }
 }
 
-
 /**
  * Represents the Options Panel in the Toolbox.
  */
 function OptionsPanel(iframeWindow, toolbox) {
   this.panelDoc = iframeWindow.document;
   this.panelWin = iframeWindow;
 
   this.toolbox = toolbox;
   this.isReady = false;
 
   this._prefChanged = this._prefChanged.bind(this);
   this._themeRegistered = this._themeRegistered.bind(this);
   this._themeUnregistered = this._themeUnregistered.bind(this);
   this._disableJSClicked = this._disableJSClicked.bind(this);
 
-  this.disableJSNode = this.panelDoc.getElementById("devtools-disable-javascript");
+  this.disableJSNode = this.panelDoc.getElementById(
+    "devtools-disable-javascript");
 
   this._addListeners();
 
   const EventEmitter = require("devtools/shared/event-emitter");
   EventEmitter.decorate(this);
 }
 
 OptionsPanel.prototype = {
@@ -101,17 +100,16 @@ OptionsPanel.prototype = {
     // For local debugging we need to make the target remote.
     if (!this.target.isRemote) {
       yield this.target.makeRemote();
     }
 
     this.setupToolsList();
     this.setupToolbarButtonsList();
     this.setupThemeList();
-    this.updateDefaultTheme();
     yield this.populatePreferences();
     this.isReady = true;
     this.emit("ready");
     return this;
   }),
 
   _addListeners: function() {
     gDevTools.on("pref-changed", this._prefChanged);
@@ -126,104 +124,124 @@ OptionsPanel.prototype = {
   },
 
   _prefChanged: function(event, data) {
     if (data.pref === "devtools.cache.disabled") {
       let cacheDisabled = data.newValue;
       let cbx = this.panelDoc.getElementById("devtools-disable-cache");
 
       cbx.checked = cacheDisabled;
-    }
-    else if (data.pref === "devtools.theme") {
+    } else if (data.pref === "devtools.theme") {
       this.updateCurrentTheme();
     }
   },
 
   _themeRegistered: function(event, themeId) {
     this.setupThemeList();
   },
 
   _themeUnregistered: function(event, theme) {
     let themeBox = this.panelDoc.getElementById("devtools-theme-box");
-    let themeOption = themeBox.querySelector("[value=" + theme.id + "]");
+    let themeInput = themeBox.querySelector(`[value=${theme.id}]`);
 
-    if (themeOption) {
-      themeBox.removeChild(themeOption);
+    if (themeInput) {
+      themeInput.parentNode.remove();
     }
   },
 
   setupToolbarButtonsList: function() {
-    let enabledToolbarButtonsBox = this.panelDoc.getElementById("enabled-toolbox-buttons-box");
-    enabledToolbarButtonsBox.textContent = "";
+    let enabledToolbarButtonsBox = this.panelDoc.getElementById(
+      "enabled-toolbox-buttons-box");
 
     let toggleableButtons = this.toolbox.toolboxButtons;
     let setToolboxButtonsVisibility =
       this.toolbox.setToolboxButtonsVisibility.bind(this.toolbox);
 
     let onCheckboxClick = (checkbox) => {
-      let toolDefinition = toggleableButtons.filter(tool => tool.id === checkbox.id)[0];
-      Services.prefs.setBoolPref(toolDefinition.visibilityswitch, checkbox.checked);
+      let toolDefinition = toggleableButtons.filter(
+        toggleableButton => toggleableButton.id === checkbox.id)[0];
+      Services.prefs.setBoolPref(
+        toolDefinition.visibilityswitch, checkbox.checked);
       setToolboxButtonsVisibility();
     };
 
     let createCommandCheckbox = tool => {
-      let checkbox = this.panelDoc.createElement("checkbox");
-      checkbox.setAttribute("id", tool.id);
-      checkbox.setAttribute("label", tool.label);
-      checkbox.setAttribute("checked", InfallibleGetBoolPref(tool.visibilityswitch));
-      checkbox.addEventListener("command", onCheckboxClick.bind(this, checkbox));
-      return checkbox;
+      let checkboxLabel = this.panelDoc.createElement("label");
+      let checkboxSpanLabel = this.panelDoc.createElement("span");
+      checkboxSpanLabel.textContent = tool.label;
+      let checkboxInput = this.panelDoc.createElement("input");
+      checkboxInput.setAttribute("type", "checkbox");
+      checkboxInput.setAttribute("id", tool.id);
+      if (InfallibleGetBoolPref(tool.visibilityswitch)) {
+        checkboxInput.setAttribute("checked", true);
+      }
+      checkboxInput.addEventListener("change",
+        onCheckboxClick.bind(this, checkboxInput));
+
+      checkboxLabel.appendChild(checkboxInput);
+      checkboxLabel.appendChild(checkboxSpanLabel);
+      return checkboxLabel;
     };
 
     for (let tool of toggleableButtons) {
       if (!tool.isTargetSupported(this.toolbox.target)) {
         continue;
       }
 
       enabledToolbarButtonsBox.appendChild(createCommandCheckbox(tool));
     }
   },
 
   setupToolsList: function() {
     let defaultToolsBox = this.panelDoc.getElementById("default-tools-box");
-    let additionalToolsBox = this.panelDoc.getElementById("additional-tools-box");
-    let toolsNotSupportedLabel = this.panelDoc.getElementById("tools-not-supported-label");
+    let additionalToolsBox = this.panelDoc.getElementById(
+      "additional-tools-box");
+    let toolsNotSupportedLabel = this.panelDoc.getElementById(
+      "tools-not-supported-label");
     let atleastOneToolNotSupported = false;
 
-    defaultToolsBox.textContent = "";
-    additionalToolsBox.textContent = "";
-
     let onCheckboxClick = function(id) {
       let toolDefinition = gDevTools._tools.get(id);
       // Set the kill switch pref boolean to true
       Services.prefs.setBoolPref(toolDefinition.visibilityswitch, this.checked);
       if (this.checked) {
         gDevTools.emit("tool-registered", id);
-      }
-      else {
+      } else {
         gDevTools.emit("tool-unregistered", toolDefinition);
       }
     };
 
     let createToolCheckbox = tool => {
-      let checkbox = this.panelDoc.createElement("checkbox");
-      checkbox.setAttribute("id", tool.id);
-      checkbox.setAttribute("tooltiptext", tool.tooltip || "");
+      let checkboxLabel = this.panelDoc.createElement("label");
+      let checkboxInput = this.panelDoc.createElement("input");
+      checkboxInput.setAttribute("type", "checkbox");
+      checkboxInput.setAttribute("id", tool.id);
+      checkboxInput.setAttribute("title", tool.tooltip || "");
+
+      let checkboxSpanLabel = this.panelDoc.createElement("span");
       if (tool.isTargetSupported(this.target)) {
-        checkbox.setAttribute("label", tool.label);
-      }
-      else {
+        checkboxSpanLabel.textContent = tool.label;
+      } else {
         atleastOneToolNotSupported = true;
-        checkbox.setAttribute("label",
-                              l10n("options.toolNotSupportedMarker", tool.label));
-        checkbox.setAttribute("unsupported", "");
+        checkboxSpanLabel.textContent = l10n(
+          "options.toolNotSupportedMarker", tool.label);
+        checkboxInput.setAttribute("data-unsupported", "true");
+        checkboxInput.setAttribute("disabled", "true");
       }
-      checkbox.setAttribute("checked", InfallibleGetBoolPref(tool.visibilityswitch));
-      checkbox.addEventListener("command", onCheckboxClick.bind(checkbox, tool.id));
-      return checkbox;
+
+      if (InfallibleGetBoolPref(tool.visibilityswitch)) {
+        checkboxInput.setAttribute("checked", "true");
+      }
+
+      checkboxInput.addEventListener("change",
+        onCheckboxClick.bind(checkboxInput, tool.id));
+
+      checkboxLabel.appendChild(checkboxInput);
+      checkboxLabel.appendChild(checkboxSpanLabel);
+      return checkboxLabel;
     };
 
     // Populating the default tools lists
     let toggleableTools = gDevTools.getDefaultTools().filter(tool => {
       return tool.visibilityswitch && !tool.hiddenInOptions;
     });
 
     for (let tool of toggleableTools) {
@@ -234,111 +252,128 @@ OptionsPanel.prototype = {
     let atleastOneAddon = false;
     for (let tool of gDevTools.getAdditionalTools()) {
       atleastOneAddon = true;
       additionalToolsBox.appendChild(createToolCheckbox(tool));
     }
 
     if (!atleastOneAddon) {
       additionalToolsBox.style.display = "none";
-      additionalToolsBox.previousSibling.style.display = "none";
     }
 
     if (!atleastOneToolNotSupported) {
       toolsNotSupportedLabel.style.display = "none";
     }
 
     this.panelWin.focus();
   },
 
   setupThemeList: function() {
     let themeBox = this.panelDoc.getElementById("devtools-theme-box");
-    themeBox.textContent = "";
 
     let createThemeOption = theme => {
-      let radio = this.panelDoc.createElement("radio");
-      radio.setAttribute("value", theme.id);
-      radio.setAttribute("label", theme.label);
-      return radio;
+      let inputLabel = this.panelDoc.createElement("label");
+      let inputRadio = this.panelDoc.createElement("input");
+      inputRadio.setAttribute("type", "radio");
+      inputRadio.setAttribute("value", theme.id);
+      inputRadio.setAttribute("name", "devtools-theme-item");
+      inputRadio.addEventListener("change", function(e) {
+        setPrefAndEmit(themeBox.getAttribute("data-pref"),
+          e.target.value);
+      });
+
+      let inputSpanLabel = this.panelDoc.createElement("span");
+      inputSpanLabel.textContent = theme.label;
+      inputLabel.appendChild(inputRadio);
+      inputLabel.appendChild(inputSpanLabel);
+
+      return inputLabel;
     };
 
     // Populating the default theme list
     let themes = gDevTools.getThemeDefinitionArray();
     for (let theme of themes) {
       themeBox.appendChild(createThemeOption(theme));
     }
 
     this.updateCurrentTheme();
   },
 
   populatePreferences: function() {
-    let prefCheckboxes = this.panelDoc.querySelectorAll("checkbox[data-pref]");
-    for (let checkbox of prefCheckboxes) {
-      checkbox.checked = GetPref(checkbox.getAttribute("data-pref"));
-      checkbox.addEventListener("command", function() {
-        setPrefAndEmit(this.getAttribute("data-pref"), this.checked);
-      }.bind(checkbox));
-    }
-    let prefRadiogroups = this.panelDoc.querySelectorAll("radiogroup[data-pref]");
-    for (let radiogroup of prefRadiogroups) {
-      let selectedValue = GetPref(radiogroup.getAttribute("data-pref"));
-      for (let radio of radiogroup.childNodes) {
-        radiogroup.selectedIndex = -1;
-        if (radio.getAttribute("value") == selectedValue) {
-          radiogroup.selectedItem = radio;
-          break;
-        }
+    let prefCheckboxes = this.panelDoc.querySelectorAll(
+      "input[type=checkbox][data-pref]");
+    for (let prefCheckbox of prefCheckboxes) {
+      if (GetPref(prefCheckbox.getAttribute("data-pref"))) {
+        prefCheckbox.setAttribute("checked", true);
       }
-      radiogroup.addEventListener("select", function() {
-        setPrefAndEmit(this.getAttribute("data-pref"), this.selectedItem.getAttribute("value"));
-      }.bind(radiogroup));
+      prefCheckbox.addEventListener("change", function(e) {
+        let checkbox = e.target;
+        setPrefAndEmit(checkbox.getAttribute("data-pref"), checkbox.checked);
+      });
     }
-    let prefMenulists = this.panelDoc.querySelectorAll("menulist[data-pref]");
-    for (let menulist of prefMenulists) {
-      let pref = GetPref(menulist.getAttribute("data-pref"));
-      let menuitems = menulist.querySelectorAll("menuitem");
-      for (let menuitem of menuitems) {
-        let value = menuitem.value;
-        if (value == pref) { // non strict check to allow int values.
-          menulist.selectedItem = menuitem;
-          break;
+    // Themes radio inputs are handled in setupThemeList
+    let prefRadiogroups = this.panelDoc.querySelectorAll(
+      ".radiogroup[data-pref]:not(#devtools-theme-box)");
+    for (let radioGroup of prefRadiogroups) {
+      let selectedValue = GetPref(radioGroup.getAttribute("data-pref"));
+
+      for (let radioInput of radioGroup.querySelectorAll("input[type=radio]")) {
+        if (radioInput.getAttribute("value") == selectedValue) {
+          radioInput.setAttribute("checked", true);
         }
+
+        radioInput.addEventListener("change", function(e) {
+          setPrefAndEmit(radioGroup.getAttribute("data-pref"),
+            e.target.value);
+        });
       }
-      menulist.addEventListener("command", function() {
-        setPrefAndEmit(this.getAttribute("data-pref"), this.value);
-      }.bind(menulist));
+    }
+    let prefSelects = this.panelDoc.querySelectorAll("select[data-pref]");
+    for (let prefSelect of prefSelects) {
+      let pref = GetPref(prefSelect.getAttribute("data-pref"));
+      let options = [...prefSelect.options];
+      options.some(function(option) {
+        let value = option.value;
+        // non strict check to allow int values.
+        if (value == pref) {
+          prefSelect.selectedIndex = options.indexOf(option);
+          return true;
+        }
+      });
+
+      prefSelect.addEventListener("change", function(e) {
+        let select = e.target;
+        setPrefAndEmit(select.getAttribute("data-pref"),
+          select.options[select.selectedIndex].value);
+      });
     }
 
     if (this.target.activeTab) {
-      return this.target.client.attachTab(this.target.activeTab._actor).then(([response,client]) => {
-        this._origJavascriptEnabled = !response.javascriptEnabled;
-        this.disableJSNode.checked = this._origJavascriptEnabled;
-        this.disableJSNode.addEventListener("click", this._disableJSClicked, false);
-      });
-    } else {
-      this.disableJSNode.hidden = true;
+      return this.target.client.attachTab(this.target.activeTab._actor)
+        .then(([response, client]) => {
+          this._origJavascriptEnabled = !response.javascriptEnabled;
+          this.disableJSNode.checked = this._origJavascriptEnabled;
+          this.disableJSNode.addEventListener("click",
+            this._disableJSClicked, false);
+        });
     }
-  },
-
-  updateDefaultTheme: function() {
-    // Make sure a theme is set in case the previous one coming from
-    // an extension isn't available anymore.
-    let themeBox = this.panelDoc.getElementById("devtools-theme-box");
-    if (themeBox.selectedIndex == -1) {
-      themeBox.selectedItem = themeBox.querySelector("[value=light]");
-    }
+    this.disableJSNode.hidden = true;
   },
 
   updateCurrentTheme: function() {
     let currentTheme = GetPref("devtools.theme");
     let themeBox = this.panelDoc.getElementById("devtools-theme-box");
-    let themeOption = themeBox.querySelector("[value=" + currentTheme + "]");
+    let themeRadioInput = themeBox.querySelector(`[value=${currentTheme}]`);
 
-    if (themeOption) {
-      themeBox.selectedItem = themeOption;
+    if (themeRadioInput) {
+      themeRadioInput.click();
+    } else {
+      // If the current theme does not exist anymore, switch to light theme
+      let lightThemeInputRadio = themeBox.querySelector("[value=light]");
+      lightThemeInputRadio.click();
     }
   },
 
   /**
    * Disables JavaScript for the currently loaded tab. We force a page refresh
    * here because setting docShell.allowJavascript to true fails to block JS
    * execution from event listeners added using addEventListener(), AJAX calls
    * and timers. The page refresh prevents these things from being added in the
rename from devtools/client/framework/toolbox-options.xul
rename to devtools/client/framework/toolbox-options.xhtml
--- a/devtools/client/framework/toolbox-options.xul
+++ b/devtools/client/framework/toolbox-options.xhtml
@@ -1,175 +1,191 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<!DOCTYPE window [
+<!DOCTYPE html [
 <!ENTITY % toolboxDTD SYSTEM "chrome://devtools/locale/toolbox.dtd" >
  %toolboxDTD;
 ]>
-<?xml-stylesheet rel="stylesheet" href="chrome://global/skin/" type="text/css"?>
-<?xml-stylesheet rel="stylesheet" href="chrome://devtools/content/framework/options-panel.css" type="text/css"?>
-
-<window xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>Toolbox option</title>
+    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+    <link rel="stylesheet" href="chrome://devtools/content/framework/options-panel.css" type="text/css"/>
+    <script type="application/javascript;version=1.8" src="chrome://devtools/content/shared/theme-switching.js"/>
+  </head>
+  <body role="application" class="theme-body">
+  <form id="options-panel">
+    <div id="tools-box" class="options-vertical-pane">
+      <fieldset id="default-tools-box" class="options-groupbox" tabindex="0">
+        <legend>&options.selectDefaultTools.label;</legend>
+      </fieldset>
 
-  <script type="application/javascript;version=1.8"
-          src="chrome://devtools/content/shared/theme-switching.js"/>
-  <hbox id="options-panel-container" flex="1">
-    <hbox id="options-panel" class="theme-body" flex="1">
-      <vbox id="tools-box" class="options-vertical-pane" flex="1">
-        <label>&options.selectDefaultTools.label;</label>
-        <vbox id="default-tools-box" class="options-groupbox" tabindex="0"/>
-        <label>&options.selectAdditionalTools.label;</label>
-        <vbox id="additional-tools-box" class="options-groupbox"/>
-        <label>&options.selectEnabledToolboxButtons.label;</label>
-        <vbox id="enabled-toolbox-buttons-box" class="options-groupbox"/>
-        <label id="tools-not-supported-label"
-               class="options-citation-label theme-comment"
-        >&options.toolNotSupported.label;</label>
+      <fieldset id="additional-tools-box" class="options-groupbox">
+        <legend>&options.selectAdditionalTools.label;</legend>
+      </fieldset>
+
+      <fieldset id="enabled-toolbox-buttons-box" class="options-groupbox">
+        <legend>&options.selectEnabledToolboxButtons.label;</legend>
+        <span id="tools-not-supported-label"
+              class="options-citation-label theme-comment">
+          &options.toolNotSupported.label;</span>
+      </fieldset>
+    </div>
+
+    <div class="options-vertical-pane">
+      <fieldset id="devtools-theme-box"
+                class="options-groupbox
+                       horizontal-options-groupbox
+                       radiogroup"
+                data-pref="devtools.theme">
+        <legend>&options.selectDevToolsTheme.label2;</legend>
+      </fieldset>
 
-      </vbox>
-      <vbox class="options-vertical-pane" flex="1">
-        <label>&options.selectDevToolsTheme.label2;</label>
-        <vbox id="theme-options" class="options-groupbox">
-          <radiogroup id="devtools-theme-box"
-                      class="options-groupbox"
-                      data-pref="devtools.theme"
-                      orient="horizontal">
-          </radiogroup>
-        </vbox>
-        <label>&options.commonPrefs.label;</label>
-        <vbox id="commonprefs-options" class="options-groupbox">
-          <checkbox label="&options.enablePersistentLogs.label;"
-                    tooltiptext="&options.enablePersistentLogs.tooltip;"
-                    data-pref="devtools.webconsole.persistlog"/>
-        </vbox>
-        <label>&options.context.inspector;</label>
-        <vbox id="inspector-options" class="options-groupbox">
-          <checkbox label="&options.showUserAgentStyles.label;"
-                    tooltiptext="&options.showUserAgentStyles.tooltip;"
-                    data-pref="devtools.inspector.showUserAgentStyles"/>
-          <checkbox label="&options.collapseAttrs.label;"
-                    tooltiptext="&options.collapseAttrs.tooltip;"
-                    data-pref="devtools.markup.collapseAttributes"/>
-          <description>
-            <label control="defaultColorUnitMenuList"
-                   accesskey="&options.defaultColorUnit.accesskey;"
-            >&options.defaultColorUnit.label;</label>
-            <hbox>
-              <menulist id="defaultColorUnitMenuList"
-                        data-pref="devtools.defaultColorUnit">
-                <menupopup>
-                  <menuitem label="&options.defaultColorUnit.authored;" value="authored"/>
-                  <menuitem label="&options.defaultColorUnit.hex;" value="hex"/>
-                  <menuitem label="&options.defaultColorUnit.hsl;" value="hsl"/>
-                  <menuitem label="&options.defaultColorUnit.rgb;" value="rgb"/>
-                  <menuitem label="&options.defaultColorUnit.name;" value="name"/>
-                </menupopup>
-              </menulist>
-            </hbox>
-          </description>
-        </vbox>
-        <label>&options.webconsole.label;</label>
-        <vbox id="webconsole-options" class="options-groupbox">
-          <checkbox id="webconsole-timestamp-messages"
-                    label="&options.timestampMessages.label;"
-                    tooltiptext="&options.timestampMessages.tooltip;"
-                    data-pref="devtools.webconsole.timestampMessages"/>
-        </vbox>
-        <label>&options.styleeditor.label;</label>
-        <vbox id="styleeditor-options" class="options-groupbox">
-          <checkbox label="&options.stylesheetSourceMaps.label;"
-                    tooltiptext="&options.stylesheetSourceMaps.tooltip;"
-                    data-pref="devtools.styleeditor.source-maps-enabled"/>
-          <checkbox label="&options.stylesheetAutocompletion.label;"
-                    tooltiptext="&options.stylesheetAutocompletion.tooltip;"
-                    data-pref="devtools.styleeditor.autocompletion-enabled"/>
-        </vbox>
-      </vbox>
+      <fieldset id="commonprefs-options" class="options-groupbox">
+        <legend>&options.commonPrefs.label;</legend>
+        <label title="&options.enablePersistentLogs.tooltip;">
+          <input type="checkbox" data-pref="devtools.webconsole.persistlog" />
+          &options.enablePersistentLogs.label;
+        </label>
+      </fieldset>
+
+      <fieldset id="inspector-options" class="options-groupbox">
+        <legend>&options.context.inspector;</legend>
+        <label title="&options.showUserAgentStyles.tooltip;">
+          <input type="checkbox"
+                 data-pref="devtools.inspector.showUserAgentStyles"/>
+          <span>&options.showUserAgentStyles.label;</span>
+        </label>
+        <label title="&options.collapseAttrs.tooltip;">
+          <input type="checkbox"
+                 data-pref="devtools.markup.collapseAttributes"/>
+          <span>&options.collapseAttrs.label;</span>
+        </label>
+        <label>
+          <span>&options.defaultColorUnit.label;</span>
+          <select id="defaultColorUnitMenuList"
+                  data-pref="devtools.defaultColorUnit">
+            <option value="authored">&options.defaultColorUnit.authored;</option>
+            <option value="hex">&options.defaultColorUnit.hex;</option>
+            <option value="hsl">&options.defaultColorUnit.hsl;</option>
+            <option value="rgb">&options.defaultColorUnit.rgb;</option>
+            <option value="name">&options.defaultColorUnit.name;</option>
+          </select>
+        </label>
+      </fieldset>
+
+      <fieldset id="webconsole-options" class="options-groupbox">
+        <legend>&options.webconsole.label;</legend>
+        <label title="&options.timestampMessages.tooltip;">
+          <input type="checkbox"
+                 id="webconsole-timestamp-messages"
+                 data-pref="devtools.webconsole.timestampMessages"/>
+          <span>&options.timestampMessages.label;</span>
+        </label>
+      </fieldset>
 
-      <vbox id="sourceeditor-box" class="options-vertical-pane" flex="1">
-        <label>&options.sourceeditor.label;</label>
-        <vbox id="sourceeditor-options" class="options-groupbox">
-          <checkbox id="devtools-sourceeditor-detectindentation"
-                    label="&options.sourceeditor.detectindentation.label;"
-                    tooltiptext="&options.sourceeditor.detectindentation.tooltip;"
-                    data-pref="devtools.editor.detectindentation"/>
-          <checkbox id="devtools-sourceeditor-autoclosebrackets"
-                    label="&options.sourceeditor.autoclosebrackets.label;"
-                    tooltiptext="&options.sourceeditor.autoclosebrackets.tooltip;"
-                    data-pref="devtools.editor.autoclosebrackets"/>
-          <checkbox id="devtools-sourceeditor-expandtab"
-                    label="&options.sourceeditor.expandtab.label;"
-                    tooltiptext="&options.sourceeditor.expandtab.tooltip;"
-                    data-pref="devtools.editor.expandtab"/>
-          <description>
-            <label control="devtools-sourceeditor-tabsize-menulist"
-                   accesskey="&options.sourceeditor.tabsize.accesskey;"
-            >&options.sourceeditor.tabsize.label;</label>
-            <hbox>
-              <menulist id="devtools-sourceeditor-tabsize-menulist"
-                        data-pref="devtools.editor.tabsize">
-                <menupopup>
-                  <menuitem label="2" value="2"/>
-                  <menuitem label="4" value="4"/>
-                  <menuitem label="8" value="8"/>
-                </menupopup>
-              </menulist>
-            </hbox>
-          </description>
-          <description>
-            <label control="devtools-sourceeditor-keybinding-menulist"
-                   accesskey="&options.sourceeditor.keybinding.accesskey;"
-            >&options.sourceeditor.keybinding.label;</label>
-            <hbox>
-              <menulist id="devtools-sourceeditor-keybinding-menulist"
-                        data-pref="devtools.editor.keymap">
-                <menupopup>
-                  <menuitem value="default"
-                            label="&options.sourceeditor.keybinding.default.label;"/>
-                  <menuitem label="Vim" value="vim"/>
-                  <menuitem label="Emacs" value="emacs"/>
-                  <menuitem label="Sublime Text" value="sublime"/>
-                </menupopup>
-              </menulist>
-            </hbox>
-          </description>
-        </vbox>
-        <label>&options.context.advancedSettings;</label>
-        <vbox id="context-options" class="options-groupbox">
-          <checkbox id="devtools-show-gecko-data"
-                    label="&options.showPlatformData.label;"
-                    tooltiptext="&options.showPlatformData.tooltip;"
-                    data-pref="devtools.performance.ui.show-platform-data"/>
-          <checkbox id="devtools-disable-cache"
-                    label="&options.disableCache.label2;"
-                    tooltiptext="&options.disableCache.tooltip2;"
-                    data-pref="devtools.cache.disabled"/>
-          <checkbox id="devtools-disable-javascript"
-                    label="&options.disableJavaScript.label;"
-                    tooltiptext="&options.disableJavaScript.tooltip;"/>
-          <checkbox id="devtools-enable-serviceWorkersTesting"
-                    label="&options.enableServiceWorkersHTTP.label;"
-                    tooltiptext="&options.enableServiceWorkersHTTP.tooltip;"
-                    data-pref="devtools.serviceWorkers.testing.enabled"/>
-          <hbox class="hidden-labels-box">
-            <checkbox label="&options.enableChrome.label5;"
-                      tooltiptext="&options.enableChrome.tooltip3;"
-                      data-pref="devtools.chrome.enabled"/>
-          </hbox>
-          <hbox class="hidden-labels-box">
-            <checkbox label="&options.enableRemote.label3;"
-                      tooltiptext="&options.enableRemote.tooltip;"
-                      data-pref="devtools.debugger.remote-enabled"/>
-          </hbox>
-          <hbox class="hidden-labels-box">
-            <checkbox label="&options.enableWorkers.label;"
-                      tooltiptext="&options.enableWorkers.tooltip;"
-                      data-pref="devtools.debugger.workers"/>
-          </hbox>
-          <label class="options-citation-label theme-comment"
-          >&options.context.triggersPageRefresh;</label>
-        </vbox>
-      </vbox>
-    </hbox>
-  </hbox>
-</window>
+      <fieldset id="styleeditor-options" class="options-groupbox">
+        <legend>&options.styleeditor.label;</legend>
+        <label title="&options.stylesheetSourceMaps.tooltip;">
+          <input type="checkbox"
+                 data-pref="devtools.styleeditor.source-maps-enabled"/>
+          <span>&options.stylesheetSourceMaps.label;</span>
+        </label>
+        <label title="&options.stylesheetAutocompletion.tooltip;">
+          <input type="checkbox"
+                 data-pref="devtools.styleeditor.autocompletion-enabled"/>
+          <span>&options.stylesheetAutocompletion.label;</span>
+        </label>
+      </fieldset>
+    </div>
+
+    <div class="options-vertical-pane">
+      <fieldset id="sourceeditor-options" class="options-groupbox">
+        <legend>&options.sourceeditor.label;</legend>
+        <label title="&options.sourceeditor.detectindentation.tooltip;">
+          <input type="checkbox"
+                 id="devtools-sourceeditor-detectindentation"
+                 data-pref="devtools.editor.detectindentation"/>
+          <span>&options.sourceeditor.detectindentation.label;</span>
+        </label>
+        <label title="&options.sourceeditor.autoclosebrackets.tooltip;">
+          <input type="checkbox"
+                 id="devtools-sourceeditor-autoclosebrackets"
+                 data-pref="devtools.editor.autoclosebrackets"/>
+          <span>&options.sourceeditor.autoclosebrackets.label;</span>
+        </label>
+        <label title="&options.sourceeditor.expandtab.tooltip;">
+          <input type="checkbox"
+                 id="devtools-sourceeditor-expandtab"
+                 data-pref="devtools.editor.expandtab"/>
+          <span>&options.sourceeditor.expandtab.label;</span>
+        </label>
+        <label>
+          <span>&options.sourceeditor.tabsize.label;</span>
+          <select id="devtools-sourceeditor-tabsize-select"
+                  data-pref="devtools.editor.tabsize">
+            <option label="2">2</option>
+            <option label="4">4</option>
+            <option label="8">8</option>
+          </select>
+        </label>
+        <label>
+          <span>&options.sourceeditor.keybinding.label;</span>
+          <select id="devtools-sourceeditor-keybinding-select"
+                  data-pref="devtools.editor.keymap">
+            <option value="default">&options.sourceeditor.keybinding.default.label;</option>
+            <option value="vim">Vim</option>
+            <option value="emacs">Emacs</option>
+            <option value="sublime">Sublime Text</option>
+          </select>
+        </label>
+      </fieldset>
+
+      <fieldset id="context-options" class="options-groupbox">
+        <legend>&options.context.advancedSettings;</legend>
+         <label title="&options.showPlatformData.tooltip;">
+            <input type="checkbox"
+                   id="devtools-show-gecko-data"
+                   data-pref="devtools.performance.ui.show-platform-data"/>
+            <span>&options.showPlatformData.label;</span>
+          </label>
+          <label title="&options.disableCache.tooltip2;">
+            <input type="checkbox"
+                   id="devtools-disable-cache"
+                   data-pref="devtools.cache.disabled"/>
+            <span>&options.disableCache.label2;</span>
+          </label>
+          <label title="&options.disableJavaScript.tooltip;">
+            <input type="checkbox"
+                   id="devtools-disable-javascript"/>
+            <span>&options.disableJavaScript.label;</span>
+          </label>
+          <label title="&options.enableServiceWorkersHTTP.tooltip;">
+            <input type="checkbox"
+                   id="devtools-enable-serviceWorkersTesting"
+                   data-pref="devtools.serviceWorkers.testing.enabled"/>
+            <span>&options.enableServiceWorkersHTTP.label;</span>
+          </label>
+          <label title="&options.enableChrome.tooltip3;">
+            <input type="checkbox"
+                   data-pref="devtools.chrome.enabled"/>
+            <span>&options.enableChrome.label5;</span>
+          </label>
+          <label title="&options.enableRemote.tooltip;">
+            <input type="checkbox"
+                   data-pref="devtools.debugger.remote-enabled"/>
+            <span>&options.enableRemote.label3;</span>
+          </label>
+          <label title="&options.enableWorkers.tooltip;">
+            <input type="checkbox"
+                   data-pref="devtools.debugger.workers"/>
+            <span>&options.enableWorkers.label;</span>
+          </label>
+          <span class="options-citation-label theme-comment"
+          >&options.context.triggersPageRefresh;</span>
+      </fieldset>
+    </div>
+
+  </form>
+  </body>
+</html>
--- a/devtools/client/jar.mn
+++ b/devtools/client/jar.mn
@@ -105,17 +105,17 @@ devtools.jar:
     content/memory/initializer.js (memory/initializer.js)
     content/promisedebugger/promise-controller.js (promisedebugger/promise-controller.js)
     content/promisedebugger/promise-panel.js (promisedebugger/promise-panel.js)
     content/promisedebugger/promise-debugger.xhtml (promisedebugger/promise-debugger.xhtml)
     content/commandline/commandline.css (commandline/commandline.css)
     content/commandline/commandlineoutput.xhtml (commandline/commandlineoutput.xhtml)
     content/commandline/commandlinetooltip.xhtml (commandline/commandlinetooltip.xhtml)
     content/framework/toolbox-window.xul (framework/toolbox-window.xul)
-    content/framework/toolbox-options.xul (framework/toolbox-options.xul)
+    content/framework/toolbox-options.xhtml (framework/toolbox-options.xhtml)
     content/framework/toolbox-options.js (framework/toolbox-options.js)
     content/framework/toolbox.xul (framework/toolbox.xul)
     content/framework/toolbox-init.js (framework/toolbox-init.js)
     content/framework/options-panel.css (framework/options-panel.css)
     content/framework/toolbox-process-window.xul (framework/toolbox-process-window.xul)
 *   content/framework/toolbox-process-window.js (framework/toolbox-process-window.js)
     content/framework/dev-edition-promo/dev-edition-promo.xul (framework/dev-edition-promo/dev-edition-promo.xul)
 *   content/framework/dev-edition-promo/dev-edition-promo.css (framework/dev-edition-promo/dev-edition-promo.css)
new file mode 100644
--- /dev/null
+++ b/devtools/client/jsonview/.eslintrc
@@ -0,0 +1,9 @@
+{
+  "globals": {
+    "define": true,
+    "document": true,
+    "window": true,
+    "CustomEvent": true,
+    "Locale": true
+  }
+}
--- a/devtools/client/jsonview/components/headers-panel.js
+++ b/devtools/client/jsonview/components/headers-panel.js
@@ -1,69 +1,79 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-define(function(require, exports, module) {
+"use strict";
 
-const React = require("devtools/client/shared/vendor/react");
-const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
-const { Headers } = createFactories(require("./headers"));
-const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
+define(function(require, exports, module) {
+  const React = require("devtools/client/shared/vendor/react");
+  const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+  const { Headers } = createFactories(require("./headers"));
+  const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
 
-const DOM = React.DOM;
+  const DOM = React.DOM;
 
-/**
- * This template represents the 'Headers' panel
- * s responsible for rendering its content.
- */
-var HeadersPanel = React.createClass({
-  displayName: "HeadersPanel",
+  /**
+   * This template represents the 'Headers' panel
+   * s responsible for rendering its content.
+   */
+  let HeadersPanel = React.createClass({
+    propTypes: {
+      actions: React.PropTypes.object,
+      data: React.PropTypes.object,
+    },
+
+    displayName: "HeadersPanel",
 
-  getInitialState: function() {
-    return {
-      data: {}
-    };
-  },
+    getInitialState: function() {
+      return {
+        data: {}
+      };
+    },
 
-  render: function() {
-    var data = this.props.data;
+    render: function() {
+      let data = this.props.data;
 
-    return (
-      DOM.div({className: "headersPanelBox"},
-        HeadersToolbar({actions: this.props.actions}),
-        DOM.div({className: "panelContent"},
-          Headers({data: data})
+      return (
+        DOM.div({className: "headersPanelBox"},
+          HeadersToolbar({actions: this.props.actions}),
+          DOM.div({className: "panelContent"},
+            Headers({data: data})
+          )
         )
-      )
-    );
-  }
-});
+      );
+    }
+  });
 
-/**
- * This template is responsible for rendering a toolbar
- * within the 'Headers' panel.
- */
-var HeadersToolbar = React.createFactory(React.createClass({
-  displayName: "HeadersToolbar",
+  /**
+   * This template is responsible for rendering a toolbar
+   * within the 'Headers' panel.
+   */
+  let HeadersToolbar = React.createFactory(React.createClass({
+    propTypes: {
+      actions: React.PropTypes.object,
+    },
+
+    displayName: "HeadersToolbar",
+
+    // Commands
 
-  render: function() {
-    return (
-      Toolbar({},
-        ToolbarButton({className: "btn copy", onClick: this.onCopy},
-          Locale.$STR("jsonViewer.Copy")
-        )
-      )
-    )
-  },
+    onCopy: function(event) {
+      this.props.actions.onCopyHeaders();
+    },
 
-  // Commands
+    render: function() {
+      return (
+        Toolbar({},
+          ToolbarButton({className: "btn copy", onClick: this.onCopy},
+            Locale.$STR("jsonViewer.Copy")
+          )
+        )
+      );
+    },
+  }));
 
-  onCopy: function(event) {
-    this.props.actions.onCopyHeaders();
-  },
-}));
-
-// Exports from this module
-exports.HeadersPanel = HeadersPanel;
+  // Exports from this module
+  exports.HeadersPanel = HeadersPanel;
 });
--- a/devtools/client/jsonview/components/headers.js
+++ b/devtools/client/jsonview/components/headers.js
@@ -1,100 +1,113 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
+
 define(function(require, exports, module) {
+  const React = require("devtools/client/shared/vendor/react");
+
+  // Constants
+  const DOM = React.DOM;
+  const PropTypes = React.PropTypes;
 
-const React = require("devtools/client/shared/vendor/react");
-
-// Constants
-const DOM = React.DOM;
+  /**
+   * This template is responsible for rendering basic layout
+   * of the 'Headers' panel. It displays HTTP headers groups such as
+   * received or response headers.
+   */
+  let Headers = React.createClass({
+    propTypes: {
+      data: PropTypes.object,
+    },
 
-/**
- * This template is responsible for rendering basic layout
- * of the 'Headers' panel. It displays HTTP headers groups such as
- * received or response headers.
- */
-var Headers = React.createClass({
-  displayName: "Headers",
+    displayName: "Headers",
+
+    getInitialState: function() {
+      return {};
+    },
+
+    render: function() {
+      let data = this.props.data;
 
-  getInitialState: function() {
-    return {};
-  },
-
-  render: function() {
-    var data = this.props.data;
-
-    return (
-      DOM.div({className: "netInfoHeadersTable"},
-        DOM.div({className: "netHeadersGroup"},
-          DOM.div({className: "netInfoHeadersGroup"},
-            DOM.span({className: "netHeader twisty"},
-              Locale.$STR("jsonViewer.responseHeaders")
+      return (
+        DOM.div({className: "netInfoHeadersTable"},
+          DOM.div({className: "netHeadersGroup"},
+            DOM.div({className: "netInfoHeadersGroup"},
+              DOM.span({className: "netHeader twisty"},
+                Locale.$STR("jsonViewer.responseHeaders")
+              )
+            ),
+            DOM.table({cellPadding: 0, cellSpacing: 0},
+              HeaderList({headers: data.response})
             )
           ),
-          DOM.table({cellPadding: 0, cellSpacing: 0},
-            HeaderList({headers: data.response})
-          )
-        ),
-        DOM.div({className: "netHeadersGroup"},
-          DOM.div({className: "netInfoHeadersGroup"},
-            DOM.span({className: "netHeader twisty"},
-              Locale.$STR("jsonViewer.requestHeaders")
+          DOM.div({className: "netHeadersGroup"},
+            DOM.div({className: "netInfoHeadersGroup"},
+              DOM.span({className: "netHeader twisty"},
+                Locale.$STR("jsonViewer.requestHeaders")
+              )
+            ),
+            DOM.table({cellPadding: 0, cellSpacing: 0},
+              HeaderList({headers: data.request})
             )
-          ),
-          DOM.table({cellPadding: 0, cellSpacing: 0},
-            HeaderList({headers: data.request})
           )
         )
-      )
-    );
-  }
-});
-
-/**
- * This template renders headers list,
- * name + value pairs.
- */
-var HeaderList = React.createFactory(React.createClass({
-  displayName: "HeaderList",
+      );
+    }
+  });
 
-  getInitialState: function() {
-    return {
-      headers: []
-    };
-  },
+  /**
+   * This template renders headers list,
+   * name + value pairs.
+   */
+  let HeaderList = React.createFactory(React.createClass({
+    propTypes: {
+      headers: PropTypes.arrayOf(PropTypes.shape({
+        name: PropTypes.string,
+        value: PropTypes.string
+      }))
+    },
 
-  render: function() {
-    var headers = this.props.headers;
+    displayName: "HeaderList",
 
-    headers.sort(function(a, b) {
-      return a.name > b.name ? 1 : -1;
-    });
+    getInitialState: function() {
+      return {
+        headers: []
+      };
+    },
+
+    render: function() {
+      let headers = this.props.headers;
 
-    var rows = [];
-    headers.forEach(header => {
-      rows.push(
-        DOM.tr({key: header.name},
-          DOM.td({className: "netInfoParamName"},
-            DOM.span({title: header.name}, header.name)
-          ),
-          DOM.td({className: "netInfoParamValue"},
-            DOM.code({}, header.value)
+      headers.sort(function(a, b) {
+        return a.name > b.name ? 1 : -1;
+      });
+
+      let rows = [];
+      headers.forEach(header => {
+        rows.push(
+          DOM.tr({key: header.name},
+            DOM.td({className: "netInfoParamName"},
+              DOM.span({title: header.name}, header.name)
+            ),
+            DOM.td({className: "netInfoParamValue"},
+              DOM.code({}, header.value)
+            )
           )
+        );
+      });
+
+      return (
+        DOM.tbody({},
+          rows
         )
-      )
-    });
+      );
+    }
+  }));
 
-    return (
-      DOM.tbody({},
-        rows
-      )
-    )
-  }
-}));
-
-// Exports from this module
-exports.Headers = Headers;
+  // Exports from this module
+  exports.Headers = Headers;
 });
--- a/devtools/client/jsonview/components/json-panel.js
+++ b/devtools/client/jsonview/components/json-panel.js
@@ -1,108 +1,123 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-define(function(require, exports, module) {
+"use strict";
 
-const React = require("devtools/client/shared/vendor/react");
-const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
-const { TreeView } = createFactories(require("./reps/tree-view"));
-const { SearchBox } = createFactories(require("./search-box"));
-const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
-const DOM = React.DOM;
+define(function(require, exports, module) {
+  const React = require("devtools/client/shared/vendor/react");
+  const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+  const { TreeView } = createFactories(require("./reps/tree-view"));
+  const { SearchBox } = createFactories(require("./search-box"));
+  const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
+  const DOM = React.DOM;
 
-/**
- * This template represents the 'JSON' panel. The panel is
- * responsible for rendering an expandable tree that allows simple
- * inspection of JSON structure.
- */
-var JsonPanel = React.createClass({
-  displayName: "JsonPanel",
+  /**
+   * This template represents the 'JSON' panel. The panel is
+   * responsible for rendering an expandable tree that allows simple
+   * inspection of JSON structure.
+   */
+  let JsonPanel = React.createClass({
+    propTypes: {
+      data: React.PropTypes.oneOfType([
+        React.PropTypes.string,
+        React.PropTypes.array,
+        React.PropTypes.object
+      ]),
+      searchFilter: React.PropTypes.string,
+      actions: React.PropTypes.object,
+    },
 
-  getInitialState: function() {
-    return {};
-  },
+    displayName: "JsonPanel",
+
+    getInitialState: function() {
+      return {};
+    },
 
-  componentDidMount: function() {
-    document.addEventListener("keypress", this.onKeyPress, true);
-  },
+    componentDidMount: function() {
+      document.addEventListener("keypress", this.onKeyPress, true);
+    },
+
+    componentWillUnmount: function() {
+      document.removeEventListener("keypress", this.onKeyPress, true);
+    },
 
-  componentWillUnmount: function() {
-    document.removeEventListener("keypress", this.onKeyPress, true);
-  },
+    onKeyPress: function(e) {
+      // XXX shortcut for focusing the Filter field (see Bug 1178771).
+    },
 
-  onKeyPress: function(e) {
-    // XXX shortcut for focusing the Filter field (see Bug 1178771).
-  },
+    render: function() {
+      let content;
+      let data = this.props.data;
 
-  render: function() {
-    var content;
-    var data = this.props.data;
-
-    try {
-      if (typeof data == "object") {
-        content = TreeView({
-          data: this.props.data,
-          mode: "tiny",
-          searchFilter: this.props.searchFilter
-        });
-      } else {
+      try {
+        if (typeof data == "object") {
+          content = TreeView({
+            data: this.props.data,
+            mode: "tiny",
+            searchFilter: this.props.searchFilter
+          });
+        } else {
+          content = DOM.div({className: "jsonParseError"},
+            data + ""
+          );
+        }
+      } catch (err) {
         content = DOM.div({className: "jsonParseError"},
-          data + ""
+          err + ""
         );
       }
-    } catch (err) {
-      content = DOM.div({className: "jsonParseError"},
-        err + ""
+
+      return (
+        DOM.div({className: "jsonPanelBox"},
+          JsonToolbar({actions: this.props.actions}),
+          DOM.div({className: "panelContent"},
+            content
+          )
+        )
       );
     }
+  });
 
-    return (
-      DOM.div({className: "jsonPanelBox"},
-        JsonToolbar({actions: this.props.actions}),
-        DOM.div({className: "panelContent"},
-          content
-        )
-      )
-    );
-  }
-});
+  /**
+   * This template represents a toolbar within the 'JSON' panel.
+   */
+  let JsonToolbar = React.createFactory(React.createClass({
+    propTypes: {
+      actions: React.PropTypes.object,
+    },
 
-/**
- * This template represents a toolbar within the 'JSON' panel.
- */
-var JsonToolbar = React.createFactory(React.createClass({
-  displayName: "JsonToolbar",
+    displayName: "JsonToolbar",
+
+    // Commands
+
+    onSave: function(event) {
+      this.props.actions.onSaveJson();
+    },
 
-  render: function() {
-    return (
-      Toolbar({},
-        ToolbarButton({className: "btn save", onClick: this.onSave},
-          Locale.$STR("jsonViewer.Save")
-        ),
-        ToolbarButton({className: "btn copy", onClick: this.onCopy},
-          Locale.$STR("jsonViewer.Copy")
-        ),
-        SearchBox({
-          actions: this.props.actions
-        })
-      )
-    )
-  },
+    onCopy: function(event) {
+      this.props.actions.onCopyJson();
+    },
 
-  // Commands
-
-  onSave: function(event) {
-    this.props.actions.onSaveJson();
-  },
+    render: function() {
+      return (
+        Toolbar({},
+          ToolbarButton({className: "btn save", onClick: this.onSave},
+            Locale.$STR("jsonViewer.Save")
+          ),
+          ToolbarButton({className: "btn copy", onClick: this.onCopy},
+            Locale.$STR("jsonViewer.Copy")
+          ),
+          SearchBox({
+            actions: this.props.actions
+          })
+        )
+      );
+    },
+  }));
 
-  onCopy: function(event) {
-    this.props.actions.onCopyJson();
-  },
-}));
-
-// Exports from this module
-exports.JsonPanel = JsonPanel;
+  // Exports from this module
+  exports.JsonPanel = JsonPanel;
 });
--- a/devtools/client/jsonview/components/main-tabbed-area.js
+++ b/devtools/client/jsonview/components/main-tabbed-area.js
@@ -2,67 +2,87 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function(require, exports, module) {
-
-const React = require("devtools/client/shared/vendor/react");
-const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
-const { JsonPanel } = createFactories(require("./json-panel"));
-const { TextPanel } = createFactories(require("./text-panel"));
-const { HeadersPanel } = createFactories(require("./headers-panel"));
-const { Tabs, TabPanel } = createFactories(require("./reps/tabs"));
+  const React = require("devtools/client/shared/vendor/react");
+  const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+  const { JsonPanel } = createFactories(require("./json-panel"));
+  const { TextPanel } = createFactories(require("./text-panel"));
+  const { HeadersPanel } = createFactories(require("./headers-panel"));
+  const { Tabs, TabPanel } = createFactories(require("./reps/tabs"));
 
-/**
- * This object represents the root application template
- * responsible for rendering the basic tab layout.
- */
-var MainTabbedArea = React.createClass({
-  displayName: "MainTabbedArea",
+  /**
+   * This object represents the root application template
+   * responsible for rendering the basic tab layout.
+   */
+  let MainTabbedArea = React.createClass({
+    propTypes: {
+      jsonText: React.PropTypes.string,
+      tabActive: React.PropTypes.number,
+      actions: React.PropTypes.object,
+      headers: React.PropTypes.object,
+      searchFilter: React.PropTypes.string,
+      json: React.PropTypes.oneOfType([
+        React.PropTypes.string,
+        React.PropTypes.object,
+        React.PropTypes.array
+      ])
+    },
 
-  getInitialState: function() {
-    return {
-      json: {},
-      headers: {},
-      jsonText: this.props.jsonText,
-      tabActive: this.props.tabActive
-   };
-  },
+    displayName: "MainTabbedArea",
 
-  onTabChanged: function(index) {
-    this.setState({tabActive: index});
-  },
+    getInitialState: function() {
+      return {
+        json: {},
+        headers: {},
+        jsonText: this.props.jsonText,
+        tabActive: this.props.tabActive
+      };
+    },
+
+    onTabChanged: function(index) {
+      this.setState({tabActive: index});
+    },
 
-  render: function() {
-    return (
-      Tabs({tabActive: this.state.tabActive, onAfterChange: this.onTabChanged},
-        TabPanel({className: "json", title: Locale.$STR("jsonViewer.tab.JSON")},
-          JsonPanel({
-            data: this.props.json,
-            actions: this.props.actions,
-            searchFilter: this.state.searchFilter
-          })
-        ),
-        TabPanel({className: "rawdata", title: Locale.$STR("jsonViewer.tab.RawData")},
-          TextPanel({
-            data: this.state.jsonText,
-            actions: this.props.actions
-          })
-        ),
-        TabPanel({className: "headers", title: Locale.$STR("jsonViewer.tab.Headers")},
-          HeadersPanel({
-            data: this.props.headers,
-            actions: this.props.actions,
-            searchFilter: this.props.searchFilter
-          })
+    render: function() {
+      return (
+        Tabs({
+          tabActive: this.state.tabActive,
+          onAfterChange: this.onTabChanged},
+          TabPanel({
+            className: "json",
+            title: Locale.$STR("jsonViewer.tab.JSON")},
+            JsonPanel({
+              data: this.props.json,
+              actions: this.props.actions,
+              searchFilter: this.state.searchFilter
+            })
+          ),
+          TabPanel({
+            className: "rawdata",
+            title: Locale.$STR("jsonViewer.tab.RawData")},
+            TextPanel({
+              data: this.state.jsonText,
+              actions: this.props.actions
+            })
+          ),
+          TabPanel({
+            className: "headers",
+            title: Locale.$STR("jsonViewer.tab.Headers")},
+            HeadersPanel({
+              data: this.props.headers,
+              actions: this.props.actions,
+              searchFilter: this.props.searchFilter
+            })
+          )
         )
-      )
-    )
-  }
+      );
+    }
+  });
+
+  // Exports from this module
+  exports.MainTabbedArea = MainTabbedArea;
 });
-
-// Exports from this module
-exports.MainTabbedArea = MainTabbedArea;
-});
--- a/devtools/client/jsonview/components/reps/tabs.js
+++ b/devtools/client/jsonview/components/reps/tabs.js
@@ -2,191 +2,190 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function(require, exports, module) {
-
-const React = require("devtools/client/shared/vendor/react");
-const DOM = React.DOM;
+  const React = require("devtools/client/shared/vendor/react");
+  const DOM = React.DOM;
 
-/**
- * Renders simple 'tab' widget.
- *
- * Based on ReactSimpleTabs component
- * https://github.com/pedronauck/react-simpletabs
- *
- * Component markup (+CSS) example:
- *
- * <div class='tabs'>
- *  <nav class='tabs-navigation'>
- *    <ul class='tabs-menu'>
- *      <li class='tabs-menu-item is-active'>Tab #1</li>
- *      <li class='tabs-menu-item'>Tab #2</li>
- *    </ul>
- *  </nav>
- *  <article class='tab-panel'>
- *    The content of active panel here
- *  </article>
- * <div>
- */
-var Tabs = React.createClass({
-  displayName: "Tabs",
+  /**
+   * Renders simple 'tab' widget.
+   *
+   * Based on ReactSimpleTabs component
+   * https://github.com/pedronauck/react-simpletabs
+   *
+   * Component markup (+CSS) example:
+   *
+   * <div class='tabs'>
+   *  <nav class='tabs-navigation'>
+   *    <ul class='tabs-menu'>
+   *      <li class='tabs-menu-item is-active'>Tab #1</li>
+   *      <li class='tabs-menu-item'>Tab #2</li>
+   *    </ul>
+   *  </nav>
+   *  <article class='tab-panel'>
+   *    The content of active panel here
+   *  </article>
+   * <div>
+   */
+  let Tabs = React.createClass({
+    propTypes: {
+      className: React.PropTypes.oneOfType([
+        React.PropTypes.array,
+        React.PropTypes.string,
+        React.PropTypes.object
+      ]),
+      tabActive: React.PropTypes.number,
+      onMount: React.PropTypes.func,
+      onBeforeChange: React.PropTypes.func,
+      onAfterChange: React.PropTypes.func,
+      children: React.PropTypes.oneOfType([
+        React.PropTypes.array,
+        React.PropTypes.element
+      ]).isRequired
+    },
 
-  propTypes: {
-    className: React.PropTypes.oneOfType([
-      React.PropTypes.array,
-      React.PropTypes.string,
-      React.PropTypes.object
-    ]),
-    tabActive: React.PropTypes.number,
-    onMount: React.PropTypes.func,
-    onBeforeChange: React.PropTypes.func,
-    onAfterChange: React.PropTypes.func,
-    children: React.PropTypes.oneOfType([
-      React.PropTypes.array,
-      React.PropTypes.element
-    ]).isRequired
-  },
+    displayName: "Tabs",
 
-  getDefaultProps: function () {
-    return {
-      tabActive: 1
-    };
-  },
+    getDefaultProps: function() {
+      return {
+        tabActive: 1
+      };
+    },
 
-  getInitialState: function () {
-    return {
-      tabActive: this.props.tabActive
-    };
-  },
+    getInitialState: function() {
+      return {
+        tabActive: this.props.tabActive
+      };
+    },
 
-  componentDidMount: function() {
-    var index = this.state.tabActive;
-    if (this.props.onMount) {
-      this.props.onMount(index);
-    }
-  },
+    componentDidMount: function() {
+      let index = this.state.tabActive;
+      if (this.props.onMount) {
+        this.props.onMount(index);
+      }
+    },
 
-  componentWillReceiveProps: function(newProps){
-    if (newProps.tabActive) {
-      this.setState({tabActive: newProps.tabActive})
-    }
-  },
+    componentWillReceiveProps: function(newProps) {
+      if (newProps.tabActive) {
+        this.setState({tabActive: newProps.tabActive});
+      }
+    },
 
-  render: function () {
-    var classNames = ["tabs", this.props.className].join(" ");
+    setActive: function(index, e) {
+      let onAfterChange = this.props.onAfterChange;
+      let onBeforeChange = this.props.onBeforeChange;
 
-    return (
-      DOM.div({className: classNames},
-        this.getMenuItems(),
-        this.getSelectedPanel()
-      )
-    );
-  },
+      if (onBeforeChange) {
+        let cancel = onBeforeChange(index);
+        if (cancel) {
+          return;
+        }
+      }
 
-  setActive: function(index, e) {
-    var onAfterChange = this.props.onAfterChange;
-    var onBeforeChange = this.props.onBeforeChange;
+      let newState = {
+        tabActive: index
+      };
 
-    if (onBeforeChange) {
-      var cancel = onBeforeChange(index);
-      if (cancel) {
-        return;
-      }
-    }
+      this.setState(newState, () => {
+        if (onAfterChange) {
+          onAfterChange(index);
+        }
+      });
+
+      e.preventDefault();
+    },
 
-    var newState = {
-      tabActive: index
-    };
+    getMenuItems: function() {
+      if (!this.props.children) {
+        throw new Error("Tabs must contain at least one Panel");
+      }
 
-    this.setState(newState, () => {
-      if (onAfterChange) {
-        onAfterChange(index);
+      if (!Array.isArray(this.props.children)) {
+        this.props.children = [this.props.children];
       }
-    });
 
-    e.preventDefault();
-  },
-
-  getMenuItems: function () {
-    if (!this.props.children) {
-      throw new Error("Tabs must contain at least one Panel");
-    }
-
-    if (!Array.isArray(this.props.children)) {
-      this.props.children = [this.props.children];
-    }
+      let menuItems = this.props.children
+        .map(function(panel) {
+          return typeof panel === "function" ? panel() : panel;
+        }).filter(function(panel) {
+          return panel;
+        }).map(function(panel, index) {
+          let ref = ("tab-menu-" + (index + 1));
+          let title = panel.props.title;
+          let tabClassName = panel.props.className;
 
-    var menuItems = this.props.children
-      .map(function(panel) {
-        return typeof panel === "function" ? panel() : panel;
-      }).filter(function(panel) {
-        return panel;
-      }).map(function(panel, index) {
-        var ref = ("tab-menu-" + (index + 1));
-        var title = panel.props.title;
-        var tabClassName = panel.props.className;
+          let classes = [
+            "tabs-menu-item",
+            tabClassName,
+            this.state.tabActive === (index + 1) && "is-active"
+          ].join(" ");
 
-        var classes = [
-          "tabs-menu-item",
-          tabClassName,
-          this.state.tabActive === (index + 1) && "is-active"
-        ].join(" ");
+          return (
+            DOM.li({ref: ref, key: index, className: classes},
+              DOM.a({href: "#", onClick: this.setActive.bind(this, index + 1)},
+                title
+              )
+            )
+          );
+        }.bind(this));
 
-        return (
-          DOM.li({ref: ref, key: index, className: classes},
-            DOM.a({href: "#", onClick: this.setActive.bind(this, index + 1)},
-              title
-            )
+      return (
+        DOM.nav({className: "tabs-navigation"},
+          DOM.ul({className: "tabs-menu"},
+            menuItems
           )
-        );
-      }.bind(this));
+        )
+      );
+    },
 
-    return (
-      DOM.nav({className: "tabs-navigation"},
-        DOM.ul({className: "tabs-menu"},
-          menuItems
+    getSelectedPanel: function() {
+      let index = this.state.tabActive - 1;
+      let panel = this.props.children[index];
+
+      return (
+        DOM.article({ref: "tab-panel", className: "tab-panel"},
+          panel
         )
-      )
-    );
-  },
+      );
+    },
 
-  getSelectedPanel: function () {
-    var index = this.state.tabActive - 1;
-    var panel = this.props.children[index];
+    render: function() {
+      let classNames = ["tabs", this.props.className].join(" ");
 
-    return (
-      DOM.article({ref: "tab-panel", className: "tab-panel"},
-        panel
-      )
-    );
-  }
-});
+      return (
+        DOM.div({className: classNames},
+          this.getMenuItems(),
+          this.getSelectedPanel()
+        )
+      );
+    },
+  });
 
-/**
- * Renders simple tab 'panel'.
- */
-var Panel = React.createClass({
-  displayName: "Panel",
+  /**
+   * Renders simple tab 'panel'.
+   */
+  let Panel = React.createClass({
+    propTypes: {
+      title: React.PropTypes.string.isRequired,
+      children: React.PropTypes.oneOfType([
+        React.PropTypes.array,
+        React.PropTypes.element
+      ]).isRequired
+    },
 
-  propTypes: {
-    title: React.PropTypes.string.isRequired,
-    children: React.PropTypes.oneOfType([
-      React.PropTypes.array,
-      React.PropTypes.element
-    ]).isRequired
-  },
+    displayName: "Panel",
 
-  render: function () {
-    return DOM.div({},
-      this.props.children
-    );
-  }
+    render: function() {
+      return DOM.div({},
+        this.props.children
+      );
+    }
+  });
+
+  // Exports from this module
+  exports.TabPanel = Panel;
+  exports.Tabs = Tabs;
 });
-
-// Exports from this module
-exports.TabPanel = Panel;
-exports.Tabs = Tabs;
-});
--- a/devtools/client/jsonview/components/reps/toolbar.js
+++ b/devtools/client/jsonview/components/reps/toolbar.js
@@ -2,50 +2,57 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function(require, exports, module) {
-
-const React = require("devtools/client/shared/vendor/react");
-const DOM = React.DOM;
+  const React = require("devtools/client/shared/vendor/react");
+  const DOM = React.DOM;
 
-/**
- * Renders a simple toolbar.
- */
-var Toolbar = React.createClass({
-  displayName: "Toolbar",
+  /**
+   * Renders a simple toolbar.
+   */
+  let Toolbar = React.createClass({
+    propTypes: {
+      children: React.PropTypes.oneOfType([
+        React.PropTypes.array,
+        React.PropTypes.element
+      ])
+    },
 
-  render: function() {
-    return (
-      DOM.div({className: "toolbar"},
-        this.props.children
-      )
-    );
-  }
-});
+    displayName: "Toolbar",
+
+    render: function() {
+      return (
+        DOM.div({className: "toolbar"},
+          this.props.children
+        )
+      );
+    }
+  });
 
-/**
- * Renders a simple toolbar button.
- */
-var ToolbarButton = React.createClass({
-  displayName: "ToolbarButton",
+  /**
+   * Renders a simple toolbar button.
+   */
+  let ToolbarButton = React.createClass({
+    propTypes: {
+      active: React.PropTypes.bool,
+      disabled: React.PropTypes.bool,
+      children: React.PropTypes.string,
+    },
 
-  propTypes: {
-    active: React.PropTypes.bool,
-    disabled: React.PropTypes.bool,
-  },
+    displayName: "ToolbarButton",
 
-  render: function() {
-    var props = Object.assign({className: "btn"}, this.props);
-    return (
-      DOM.button(props, this.props.children)
-    );
-  },
+    render: function() {
+      let props = Object.assign({className: "btn"}, this.props);
+      return (
+        DOM.button(props, this.props.children)
+      );
+    },
+  });
+
+  // Exports from this module
+  exports.Toolbar = Toolbar;
+  exports.ToolbarButton = ToolbarButton;
 });
-
-// Exports from this module
-exports.Toolbar = Toolbar;
-exports.ToolbarButton = ToolbarButton;
-});
--- a/devtools/client/jsonview/components/reps/tree-view.js
+++ b/devtools/client/jsonview/components/reps/tree-view.js
@@ -1,261 +1,267 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+"use strict";
+
 define(function(require, exports, module) {
+  // Dependencies
+  const React = require("devtools/client/shared/vendor/react");
+  const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+  const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
+  const { StringRep } = require("devtools/client/shared/components/reps/string");
+  const DOM = React.DOM;
+
+  let uid = 0;
+
+  /**
+   * Renders a tree view with expandable/collapsible items.
+   */
+  let TreeView = React.createClass({
+    propTypes: {
+      searchFilter: React.PropTypes.string,
+      data: React.PropTypes.any,
+      mode: React.PropTypes.string,
+    },
+
+    displayName: "TreeView",
 
-// Dependencies
-const React = require("devtools/client/shared/vendor/react");
-const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
-const { Rep } = createFactories(require("devtools/client/shared/components/reps/rep"));
-const { StringRep } = require("devtools/client/shared/components/reps/string");
-const DOM = React.DOM;
+    getInitialState: function() {
+      return {
+        data: {},
+        searchFilter: null
+      };
+    },
+
+    // Data
 
-var uid = 0;
+    componentDidMount: function() {
+      let members = initMembers(this.props.data, 0);
+      this.setState({ // eslint-disable-line
+        data: members,
+        searchFilter:
+        this.props.searchFilter
+      });
+    },
+
+    componentWillReceiveProps: function(nextProps) {
+      let updatedState = {
+        searchFilter: nextProps.searchFilter
+      };
+
+      if (this.props.data !== nextProps.data) {
+        updatedState.data = initMembers(nextProps.data, 0);
+      }
+
+      this.setState(updatedState);
+    },
+
+    // Rendering
 
-/**
- * Renders a tree view with expandable/collapsible items.
- */
-var TreeView = React.createClass({
-  displayName: "TreeView",
+    render: function() {
+      let mode = this.props.mode;
+      let root = this.state.data;
+
+      let children = [];
+
+      if (Array.isArray(root)) {
+        for (let i = 0; i < root.length; i++) {
+          let child = root[i];
+          children.push(TreeNode({
+            key: child.key,
+            data: child,
+            mode: mode,
+            searchFilter: this.state.searchFilter || this.props.searchFilter
+          }));
+        }
+      } else {
+        children.push(React.addons.createFragment(root));
+      }
+
+      return (
+        DOM.div({className: "domTable", cellPadding: 0, cellSpacing: 0},
+          children
+        )
+      );
+    },
+  });
 
-  getInitialState: function() {
-    return {
-      data: {},
-      searchFilter: null
-    };
-  },
+  /**
+   * Represents a node within the tree.
+   */
+  let TreeNode = React.createFactory(React.createClass({
+    propTypes: {
+      searchFilter: React.PropTypes.string,
+      data: React.PropTypes.object,
+      mode: React.PropTypes.string,
+    },
+
+    displayName: "TreeNode",
 
-  // Rendering
+    getInitialState: function() {
+      return {
+        data: this.props.data,
+        searchFilter: null
+      };
+    },
 
-  render: function() {
-    var mode = this.props.mode;
-    var root = this.state.data;
+    onClick: function(e) {
+      let member = this.state.data;
+      member.open = !member.open;
 
-    var children = [];
+      this.setState({data: member});
+
+      e.stopPropagation();
+    },
 
-    if (Array.isArray(root)) {
-      for (var i=0; i<root.length; i++) {
-        var child = root[i];
-        children.push(TreeNode({
-          key: child.key,
-          data: child,
-          mode: mode,
-          searchFilter: this.state.searchFilter || this.props.searchFilter
-        }));
+    render: function() {
+      let member = this.state.data;
+      let mode = this.props.mode;
+
+      let classNames = ["memberRow"];
+      classNames.push(member.type + "Row");
+
+      if (member.hasChildren) {
+        classNames.push("hasChildren");
+      }
+
+      if (member.open) {
+        classNames.push("opened");
+      }
+
+      if (!member.children) {
+        // Cropped strings are expandable, but they don't have children.
+        let isString = typeof (member.value) == "string";
+        if (member.hasChildren && !isString) {
+          member.children = initMembers(member.value);
+        } else {
+          member.children = [];
+        }
       }
-    } else {
-      children.push(React.addons.createFragment(root));
-    }
+
+      let children = [];
+      if (member.open && member.children.length) {
+        for (let i in member.children) {
+          let child = member.children[i];
+          children.push(TreeNode({
+            key: child.key,
+            data: child,
+            mode: mode,
+            searchFilter: this.state.searchFilter || this.props.searchFilter
+          }));
+        }
+      }
+
+      let filter = this.props.searchFilter || "";
+      let name = member.name || "";
+      let value = member.value || "";
+
+      // Filtering is case-insensitive
+      filter = filter.toLowerCase();
+      name = name.toLowerCase();
+
+      if (filter && (name.indexOf(filter) < 0)) {
+        // Cache the stringify result, so the filtering is fast
+        // the next time.
+        if (!member.valueString) {
+          member.valueString = JSON.stringify(value).toLowerCase();
+        }
+
+        if (member.valueString && member.valueString.indexOf(filter) < 0) {
+          classNames.push("hidden");
+        }
+      }
 
-    return (
-      DOM.div({className: "domTable", cellPadding: 0, cellSpacing: 0},
-        children
-      )
-    );
-  },
+      return (
+        DOM.div({className: classNames.join(" ")},
+          DOM.span({className: "memberLabelCell", onClick: this.onClick},
+            DOM.span({className: "memberIcon"}),
+            DOM.span({className: "memberLabel " + member.type + "Label"},
+              member.name)
+          ),
+          DOM.span({className: "memberValueCell"},
+            DOM.span({},
+              Rep({
+                object: member.value,
+                mode: this.props.mode,
+                member: member
+              })
+            )
+          ),
+          DOM.div({className: "memberChildren"},
+            children
+          )
+        )
+      );
+    },
+  }));
 
-  // Data
+  // Helpers
 
-  componentDidMount: function() {
-    var members = initMembers(this.props.data, 0);
-    this.setState({data: members, searchFilter: this.props.searchFilter});
-  },
+  function initMembers(parent) {
+    let members = getMembers(parent);
+    return members;
+  }
+
+  function getMembers(object) {
+    let members = [];
+    getObjectProperties(object, function(prop, value) {
+      let valueType = typeof (value);
+      let hasChildren = (valueType === "object" && hasProperties(value));
+
+      // Cropped strings are expandable, so the user can see the
+      // entire original value.
+      if (StringRep.isCropped(value)) {
+        hasChildren = true;
+      }
 
-  componentWillReceiveProps: function(nextProps) {
-    var updatedState = {
-      searchFilter: nextProps.searchFilter
+      let type = getType(value);
+      let member = createMember(type, prop, value, hasChildren);
+      members.push(member);
+    });
+
+    return members;
+  }
+
+  function createMember(type, name, value, hasChildren) {
+    let member = {
+      name: name,
+      type: type,
+      rowClass: "memberRow-" + type,
+      hasChildren: hasChildren,
+      value: value,
+      open: false,
+      key: uid++
     };
 
-    if (this.props.data !== nextProps.data) {
-      updatedState.data = initMembers(nextProps.data, 0);
-    }
-
-    this.setState(updatedState);
+    return member;
   }
-});
-
-/**
- * Represents a node within the tree.
- */
-var TreeNode = React.createFactory(React.createClass({
-  displayName: "TreeNode",
-
-  getInitialState: function() {
-    return { data: {}, searchFilter: null };
-  },
-
-  componentDidMount: function() {
-    this.setState({data: this.props.data});
-  },
 
-  render: function() {
-    var member = this.state.data;
-    var mode = this.props.mode;
-
-    var classNames = ["memberRow"];
-    classNames.push(member.type + "Row");
-
-    if (member.hasChildren) {
-      classNames.push("hasChildren");
-    }
-
-    if (member.open) {
-      classNames.push("opened");
-    }
-
-    if (!member.children) {
-      // Cropped strings are expandable, but they don't have children.
-      var isString = typeof(member.value) == "string";
-      if (member.hasChildren && !isString) {
-        member.children = initMembers(member.value);
-      } else {
-        member.children = [];
+  function getObjectProperties(obj, callback) {
+    for (let p in obj) {
+      try {
+        callback.call(this, p, obj[p]);
+      } catch (e) {
+        // Ignore
       }
     }
+  }
 
-    var children = [];
-    if (member.open && member.children.length) {
-      for (var i in member.children) {
-        var child = member.children[i];
-        children.push(TreeNode({
-          key: child.key,
-          data: child,
-          mode: mode,
-          searchFilter: this.state.searchFilter || this.props.searchFilter
-        }));
-      };
+  function hasProperties(obj) {
+    if (typeof (obj) == "string") {
+      return false;
     }
 
-    var filter = this.props.searchFilter || "";
-    var name = member.name || "";
-    var value = member.value || "";
-
-    // Filtering is case-insensitive
-    filter = filter.toLowerCase();
-    name = name.toLowerCase();
-
-    if (filter && (name.indexOf(filter) < 0)) {
-      // Cache the stringify result, so the filtering is fast
-      // the next time.
-      if (!member.valueString) {
-        member.valueString = JSON.stringify(value).toLowerCase();
-      }
-
-      if (member.valueString && member.valueString.indexOf(filter) < 0) {
-        classNames.push("hidden");
-      }
-    }
-
-    return (
-      DOM.div({className: classNames.join(" ")},
-        DOM.span({className: "memberLabelCell", onClick: this.onClick},
-          DOM.span({className: "memberIcon"}),
-          DOM.span({className: "memberLabel " + member.type + "Label"},
-            member.name)
-        ),
-        DOM.span({className: "memberValueCell"},
-          DOM.span({},
-            Rep({
-              object: member.value,
-              mode: this.props.mode,
-              member: member
-            })
-          )
-        ),
-        DOM.div({className: "memberChildren"},
-          children
-        )
-      )
-    )
-  },
-
-  onClick: function(e) {
-    var member = this.state.data;
-    member.open = !member.open;
-
-    this.setState({data: member});
-
-    e.stopPropagation();
-  },
-}));
-
-// Helpers
-
-function initMembers(parent) {
-  var members = getMembers(parent);
-  return members;
-}
-
-function getMembers(object) {
-  var members = [];
-  getObjectProperties(object, function(prop, value) {
-    var valueType = typeof(value);
-    var hasChildren = (valueType === "object" && hasProperties(value));
-
-    // Cropped strings are expandable, so the user can see the
-    // entire original value.
-    if (StringRep.isCropped(value)) {
-      hasChildren = true;
-    }
-
-    var type = getType(value);
-    var member = createMember(type, prop, value, hasChildren);
-    members.push(member);
-  });
-
-  return members;
-}
-
-function createMember(type, name, value, hasChildren) {
-  var member = {
-    name: name,
-    type: type,
-    rowClass: "memberRow-" + type,
-    open: "",
-    hasChildren: hasChildren,
-    value: value,
-    open: false,
-    key: uid++
-  };
-
-  return member;
-}
-
-function getObjectProperties(obj, callback) {
-  for (var p in obj) {
-    try {
-      callback.call(this, p, obj[p]);
-    }
-    catch (e) {
-      console.error(e)
-    }
-  }
-}
-
-function hasProperties(obj) {
-  if (typeof(obj) == "string") {
-    return false;
+    return Object.keys(obj).length > 1;
   }
 
-  try {
-    for (var name in obj) {
-      return true;
-    }
-  }
-  catch (exc) {
+  function getType(object) {
+    // A type provider (or a decorator) should be used here.
+    return "dom";
   }
 
-  return false;
-}
-
-function getType(object) {
-  // A type provider (or a decorator) should be used here.
-  return "dom";
-}
-
-// Exports from this module
-exports.TreeView = TreeView;
+  // Exports from this module
+  exports.TreeView = TreeView;
 });
--- a/devtools/client/jsonview/components/search-box.js
+++ b/devtools/client/jsonview/components/search-box.js
@@ -2,51 +2,54 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 define(function(require, exports, module) {
+  const React = require("devtools/client/shared/vendor/react");
 
-const React = require("devtools/client/shared/vendor/react");
+  const DOM = React.DOM;
 
-const DOM = React.DOM;
+  // For smooth incremental searching (in case the user is typing quickly).
+  const searchDelay = 250;
 
-// For smooth incremental searching (in case the user is typing quickly).
-const searchDelay = 250;
+  /**
+   * This object represents a search box located at the
+   * top right corner of the application.
+   */
+  let SearchBox = React.createClass({
+    propTypes: {
+      actions: React.PropTypes.object,
+    },
 
-/**
- * This object represents a search box located at the
- * top right corner of the application.
- */
-var SearchBox = React.createClass({
-  displayName: "SearchBox",
+    displayName: "SearchBox",
+
+    onSearch: function(event) {
+      let searchBox = event.target;
+      let win = searchBox.ownerDocument.defaultView;
 
-  render: function() {
-    return (
-      DOM.input({className: "searchBox",
-        placeholder: Locale.$STR("jsonViewer.filterJSON"),
-        onChange: this.onSearch})
-    )
-  },
+      if (this.searchTimeout) {
+        win.clearTimeout(this.searchTimeout);
+      }
 
-  onSearch: function(event) {
-    var searchBox = event.target;
-    var win = searchBox.ownerDocument.defaultView;
+      let callback = this.doSearch.bind(this, searchBox);
+      this.searchTimeout = win.setTimeout(callback, searchDelay);
+    },
+
+    doSearch: function(searchBox) {
+      this.props.actions.onSearch(searchBox.value);
+    },
 
-    if (this.searchTimeout) {
-      win.clearTimeout(this.searchTimeout);
-    }
-
-    var callback = this.doSearch.bind(this, searchBox);
-    this.searchTimeout = win.setTimeout(callback, searchDelay);
-  },
+    render: function() {
+      return (
+        DOM.input({className: "searchBox",
+          placeholder: Locale.$STR("jsonViewer.filterJSON"),
+          onChange: this.onSearch})
+      );
+    },
+  });
 
-  doSearch: function(searchBox) {
-    this.props.actions.onSearch(searchBox.value);
-  }
+  // Exports from this module
+  exports.SearchBox = SearchBox;
 });
-
-// Exports from this module
-exports.SearchBox = SearchBox;
-});
--- a/devtools/client/jsonview/components/text-panel.js
+++ b/devtools/client/jsonview/components/text-panel.js
@@ -1,79 +1,95 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-define(function(require, exports, module) {
+"use strict";
 
-const React = require("devtools/client/shared/vendor/react");
-const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
-const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
-const DOM = React.DOM;
+define(function(require, exports, module) {
+  const React = require("devtools/client/shared/vendor/react");
+  const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+  const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
+  const DOM = React.DOM;
 
-/**
- * This template represents the 'Raw Data' panel displaying
- * JSON as a text received from the server.
- */
-var TextPanel = React.createClass({
-  displayName: "TextPanel",
+  /**
+   * This template represents the 'Raw Data' panel displaying
+   * JSON as a text received from the server.
+   */
+  let TextPanel = React.createClass({
+    propTypes: {
+      actions: React.PropTypes.object,
+      data: React.PropTypes.string
+    },
+
+    displayName: "TextPanel",
 
-  getInitialState: function() {
-    return {};
-  },
+    getInitialState: function() {
+      return {};
+    },
 
-  render: function() {
-    return (
-      DOM.div({className: "textPanelBox"},
-        TextToolbar({actions: this.props.actions}),
-        DOM.div({className: "panelContent"},
-          DOM.pre({className: "data"},
-            this.props.data
+    render: function() {
+      return (
+        DOM.div({className: "textPanelBox"},
+          TextToolbar({actions: this.props.actions}),
+          DOM.div({className: "panelContent"},
+            DOM.pre({className: "data"},
+              this.props.data
+            )
           )
         )
-      )
-    );
-  }
-});
+      );
+    }
+  });
 
-/**
- * This object represents a toolbar displayed within the
- * 'Raw Data' panel.
- */
-var TextToolbar = React.createFactory(React.createClass({
-  displayName: "TextToolbar",
+  /**
+   * This object represents a toolbar displayed within the
+   * 'Raw Data' panel.
+   */
+  let TextToolbar = React.createFactory(React.createClass({
+    propTypes: {
+      actions: React.PropTypes.object,
+    },
+
+    displayName: "TextToolbar",
+
+    // Commands
+
+    onPrettify: function(event) {
+      this.props.actions.onPrettify();
+    },
+
+    onSave: function(event) {
+      this.props.actions.onSaveJson();
+    },
 
-  render: function() {
-    return (
-      Toolbar({},
-        ToolbarButton({className: "btn prettyprint",onClick: this.onPrettify},
-          Locale.$STR("jsonViewer.PrettyPrint")
-        ),
-        ToolbarButton({className: "btn save", onClick: this.onSave},
-          Locale.$STR("jsonViewer.Save")
-        ),
-        ToolbarButton({className: "btn copy", onClick: this.onCopy},
-          Locale.$STR("jsonViewer.Copy")
-        )
-      )
-    )
-  },
+    onCopy: function(event) {
+      this.props.actions.onCopyJson();
+    },
 
-  // Commands
-
-  onPrettify: function(event) {
-    this.props.actions.onPrettify();
-  },
+    render: function() {
+      return (
+        Toolbar({},
+          ToolbarButton({
+            className: "btn prettyprint",
+            onClick: this.onPrettify},
+            Locale.$STR("jsonViewer.PrettyPrint")
+          ),
+          ToolbarButton({
+            className: "btn save",
+            onClick: this.onSave},
+            Locale.$STR("jsonViewer.Save")
+          ),
+          ToolbarButton({
+            className: "btn copy",
+            onClick: this.onCopy},
+            Locale.$STR("jsonViewer.Copy")
+          )
+        )
+      );
+    },
+  }));
 
-  onSave: function(event) {
-    this.props.actions.onSaveJson();
-  },
-
-  onCopy: function(event) {
-    this.props.actions.onCopyJson();
-  },
-}));
-
-// Exports from this module
-exports.TextPanel = TextPanel;
+  // Exports from this module
+  exports.TextPanel = TextPanel;
 });
--- a/devtools/client/jsonview/converter-child.js
+++ b/devtools/client/jsonview/converter-child.js
@@ -14,119 +14,122 @@ const xpcom = require("sdk/platform/xpco
 const Events = require("sdk/dom/events");
 const Clipboard = require("sdk/clipboard");
 
 loader.lazyRequireGetter(this, "NetworkHelper",
                                "devtools/shared/webconsole/network-helper");
 loader.lazyRequireGetter(this, "JsonViewUtils",
                                "devtools/client/jsonview/utils");
 
-
 const childProcessMessageManager =
-  Cc["@mozilla.org/childprocessmessagemanager;1"].
-    getService(Ci.nsISyncMessageSender);
+  Cc["@mozilla.org/childprocessmessagemanager;1"]
+    .getService(Ci.nsISyncMessageSender);
 
 // Amount of space that will be allocated for the stream's backing-store.
 // Must be power of 2. Used to copy the data stream in onStopRequest.
 const SEGMENT_SIZE = Math.pow(2, 17);
 
 const JSON_VIEW_MIME_TYPE = "application/vnd.mozilla.json.view";
-const CONTRACT_ID = "@mozilla.org/streamconv;1?from=" + JSON_VIEW_MIME_TYPE + "&to=*/*";
+const CONTRACT_ID = "@mozilla.org/streamconv;1?from=" +
+  JSON_VIEW_MIME_TYPE + "&to=*/*";
 const CLASS_ID = "{d8c9acee-dec5-11e4-8c75-1681e6b88ec1}";
 
 // Localization
-var jsonViewStrings = Services.strings.createBundle(
+let jsonViewStrings = Services.strings.createBundle(
   "chrome://devtools/locale/jsonview.properties");
 
 /**
  * This object detects 'application/vnd.mozilla.json.view' content type
  * and converts it into a JSON Viewer application that allows simple
  * JSON inspection.
  *
  * Inspired by JSON View: https://github.com/bhollis/jsonview/
  */
-var Converter = Class({
+let Converter = Class({
   extends: Unknown,
 
   interfaces: [
     "nsIStreamConverter",
     "nsIStreamListener",
     "nsIRequestObserver"
   ],
 
   get wrappedJSObject() {
     return this;
   },
 
   /**
    * This component works as such:
    * 1. asyncConvertData captures the listener
-   * 2. onStartRequest fires, initializes stuff, modifies the listener to match our output type
+   * 2. onStartRequest fires, initializes stuff, modifies the listener
+   *    to match our output type
    * 3. onDataAvailable transcodes the data into a UTF-8 string
-   * 4. onStopRequest gets the collected data and converts it, spits it to the listener
-   * 5. convert does nothing, it's just the synchronous version of asyncConvertData
+   * 4. onStopRequest gets the collected data and converts it,
+   *    spits it to the listener
+   * 5. convert does nothing, it's just the synchronous version
+   *    of asyncConvertData
    */
-  convert: function(aFromStream, aFromType, aToType, aCtxt) {
-    return aFromStream;
+  convert: function(fromStream, fromType, toType, ctx) {
+    return fromStream;
   },
 
-  asyncConvertData: function(aFromType, aToType, aListener, aCtxt) {
-    this.listener = aListener;
+  asyncConvertData: function(fromType, toType, listener, ctx) {
+    this.listener = listener;
   },
 
-  onDataAvailable: function(aRequest, aContext, aInputStream, aOffset, aCount) {
+  onDataAvailable: function(request, context, inputStream, offset, count) {
     // From https://developer.mozilla.org/en/Reading_textual_data
-    var is = Cc["@mozilla.org/intl/converter-input-stream;1"].
-      createInstance(Ci.nsIConverterInputStream);
-    is.init(aInputStream, this.charset, -1,
+    let is = Cc["@mozilla.org/intl/converter-input-stream;1"]
+      .createInstance(Ci.nsIConverterInputStream);
+    is.init(inputStream, this.charset, -1,
       Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER);
 
     // Seed it with something positive
-    var bytesRead = 1;
-    while (aCount) {
-      var str = {};
-      var bytesRead = is.readString(aCount, str);
+    while (count) {
+      let str = {};
+      let bytesRead = is.readString(count, str);
       if (!bytesRead) {
         break;
       }
-      aCount -= bytesRead;
+      count -= bytesRead;
       this.data += str.value;
     }
   },
 
-  onStartRequest: function(aRequest, aContext) {
+  onStartRequest: function(request, context) {
     this.data = "";
-    this.uri = aRequest.QueryInterface(Ci.nsIChannel).URI.spec;
+    this.uri = request.QueryInterface(Ci.nsIChannel).URI.spec;
 
     // Sets the charset if it is available. (For documents loaded from the
     // filesystem, this is not set.)
-    this.charset = aRequest.QueryInterface(Ci.nsIChannel).contentCharset || 'UTF-8';
+    this.charset =
+      request.QueryInterface(Ci.nsIChannel).contentCharset || "UTF-8";
 
-    this.channel = aRequest;
+    this.channel = request;
     this.channel.contentType = "text/html";
     this.channel.contentCharset = "UTF-8";
 
-    this.listener.onStartRequest(this.channel, aContext);
+    this.listener.onStartRequest(this.channel, context);
   },
 
   /**
    * This should go something like this:
    * 1. Make sure we have a unicode string.
    * 2. Convert it to a Javascript object.
    * 2.1 Removes the callback
    * 3. Convert that to HTML? Or XUL?
    * 4. Spit it back out at the listener
    */
-  onStopRequest: function(aRequest, aContext, aStatusCode) {
+  onStopRequest: function(request, context, statusCode) {
     let headers = {
       response: [],
       request: []
-    }
+    };
 
-    let win = NetworkHelper.getWindowForRequest(aRequest);
+    let win = NetworkHelper.getWindowForRequest(request);
 
     let Locale = {
       $STR: key => {
         try {
           return jsonViewStrings.GetStringFromName(key);
         } catch (err) {
           Cu.reportError(err);
         }
@@ -134,159 +137,165 @@ var Converter = Class({
     };
 
     JsonViewUtils.exportIntoContentScope(win, Locale, "Locale");
 
     Events.once(win, "DOMContentLoaded", event => {
       Cu.exportFunction(this.postChromeMessage.bind(this), win, {
         defineAs: "postChromeMessage"
       });
-    })
+    });
 
     // The request doesn't have to be always nsIHttpChannel
     // (e.g. in case of data: URLs)
-    if (aRequest instanceof Ci.nsIHttpChannel) {
-      aRequest.visitResponseHeaders({
+    if (request instanceof Ci.nsIHttpChannel) {
+      request.visitResponseHeaders({
         visitHeader: function(name, value) {
           headers.response.push({name: name, value: value});
         }
       });
 
-      aRequest.visitRequestHeaders({
+      request.visitRequestHeaders({
         visitHeader: function(name, value) {
           headers.request.push({name: name, value: value});
         }
       });
     }
 
     let outputDoc = "";
 
     try {
       headers = JSON.stringify(headers);
       outputDoc = this.toHTML(this.data, headers, this.uri);
     } catch (e) {
       Cu.reportError("JSON Viewer ERROR " + e);
       outputDoc = this.toErrorPage(e, this.data, this.uri);
     }
 
-    var storage = Cc["@mozilla.org/storagestream;1"].createInstance(Ci.nsIStorageStream);
+    let storage = Cc["@mozilla.org/storagestream;1"]
+      .createInstance(Ci.nsIStorageStream);
+
     storage.init(SEGMENT_SIZE, 0xffffffff, null);
-    var out = storage.getOutputStream(0);
+    let out = storage.getOutputStream(0);
 
-    var binout = Cc["@mozilla.org/binaryoutputstream;1"]
+    let binout = Cc["@mozilla.org/binaryoutputstream;1"]
       .createInstance(Ci.nsIBinaryOutputStream);
 
     binout.setOutputStream(out);
     binout.writeUtf8Z(outputDoc);
     binout.close();
 
     // We need to trim 4 bytes off the front (this could be underlying bug).
-    var trunc = 4;
-    var instream = storage.newInputStream(trunc);
+    let trunc = 4;
+    let instream = storage.newInputStream(trunc);
 
     // Pass the data to the main content listener
-    this.listener.onDataAvailable(this.channel, aContext, instream, 0,
+    this.listener.onDataAvailable(this.channel, context, instream, 0,
       instream.available());
 
-    this.listener.onStopRequest(this.channel, aContext, aStatusCode);
+    this.listener.onStopRequest(this.channel, context, statusCode);
 
     this.listener = null;
   },
 
   htmlEncode: function(t) {
-    return t !== null ? t.toString().replace(/&/g,"&amp;").
-      replace(/"/g,"&quot;").replace(/</g,"&lt;").replace(/>/g,"&gt;") : '';
+    return t !== null ? t.toString()
+      .replace(/&/g, "&amp;")
+      .replace(/"/g, "&quot;")
+      .replace(/</g, "&lt;")
+      .replace(/>/g, "&gt;") : "";
   },
 
   toHTML: function(json, headers, title) {
-    var themeClassName = "theme-" + JsonViewUtils.getCurrentTheme();
-    var clientBaseUrl = "resource://devtools/client/";
-    var baseUrl = clientBaseUrl + "jsonview/";
-    var themeVarsUrl = clientBaseUrl + "themes/variables.css";
+    let themeClassName = "theme-" + JsonViewUtils.getCurrentTheme();
+    let clientBaseUrl = "resource://devtools/client/";
+    let baseUrl = clientBaseUrl + "jsonview/";
+    let themeVarsUrl = clientBaseUrl + "themes/variables.css";
 
-    return '<!DOCTYPE html>\n' +
-      '<html class="' + themeClassName + '">' +
-      '<head><title>' + this.htmlEncode(title) + '</title>' +
-      '<base href="' + this.htmlEncode(baseUrl) + '">' +
-      '<link rel="stylesheet" type="text/css" href="' + themeVarsUrl + '">' +
-      '<link rel="stylesheet" type="text/css" href="css/main.css">' +
-      '<script data-main="viewer-config" src="lib/require.js"></script>' +
-      '</head><body>' +
-      '<div id="content"></div>' +
-      '<div id="json">' + this.htmlEncode(json) + '</div>' +
-      '<div id="headers">' + this.htmlEncode(headers) + '</div>' +
-      '</body></html>';
+    return "<!DOCTYPE html>\n" +
+      "<html class=\"" + themeClassName + "\">" +
+      "<head><title>" + this.htmlEncode(title) + "</title>" +
+      "<base href=\"" + this.htmlEncode(baseUrl) + "\">" +
+      "<link rel=\"stylesheet\" type=\"text/css\" href=\"" +
+        themeVarsUrl + "\">" +
+      "<link rel=\"stylesheet\" type=\"text/css\" href=\"css/main.css\">" +
+      "<script data-main=\"viewer-config\" src=\"lib/require.js\"></script>" +
+      "</head><body>" +
+      "<div id=\"content\"></div>" +
+      "<div id=\"json\">" + this.htmlEncode(json) + "</div>" +
+      "<div id=\"headers\">" + this.htmlEncode(headers) + "</div>" +
+      "</body></html>";
   },
 
   toErrorPage: function(error, data, uri) {
     // Escape unicode nulls
     data = data.replace("\u0000", "\uFFFD");
 
-    var errorInfo = error + "";
+    let errorInfo = error + "";
 
-    var output = '<div id="error">' + _('errorParsing')
+    let output = "<div id=\"error\">" + "error parsing";
     if (errorInfo.message) {
-      output += '<div class="errormessage">' + errorInfo.message + '</div>';
+      output += "<div class=\"errormessage\">" + errorInfo.message + "</div>";
     }
 
-    output += '</div><div id="json">' + this.highlightError(data,
-      errorInfo.line, errorInfo.column) + '</div>';
+    output += "</div><div id=\"json\">" + this.highlightError(data,
+      errorInfo.line, errorInfo.column) + "</div>";
 
-    return '<!DOCTYPE html>\n' +
-      '<html><head><title>' + this.htmlEncode(uri + ' - Error') + '</title>' +
-      '<base href="' + this.htmlEncode(self.data.url()) + '">' +
-      '</head><body>' +
+    return "<!DOCTYPE html>\n" +
+      "<html><head><title>" + this.htmlEncode(uri + " - Error") + "</title>" +
+      "<base href=\"" + this.htmlEncode(this.data.url()) + "\">" +
+      "</head><body>" +
       output +
-      '</body></html>';
+      "</body></html>";
   },
 
   // Chrome <-> Content communication
 
   postChromeMessage: function(type, args, objects) {
-    var value = args;
+    let value = args;
 
     switch (type) {
-    case "copy":
-      Clipboard.set(value, "text");
-      break;
+      case "copy":
+        Clipboard.set(value, "text");
+        break;
 
-    case "copy-headers":
-      this.copyHeaders(value);
-      break;
+      case "copy-headers":
+        this.copyHeaders(value);
+        break;
 
-    case "save":
-      childProcessMessageManager.sendAsyncMessage(
-        "devtools:jsonview:save", value);
+      case "save":
+        childProcessMessageManager.sendAsyncMessage(
+          "devtools:jsonview:save", value);
     }
   },
 
   copyHeaders: function(headers) {
-    var value = "";
-    var eol = (Services.appinfo.OS !== "WINNT") ? "\n" : "\r\n";
+    let value = "";
+    let eol = (Services.appinfo.OS !== "WINNT") ? "\n" : "\r\n";
 
-    var responseHeaders = headers.response;
-    for (var i=0; i<responseHeaders.length; i++) {
-      var header = responseHeaders[i];
+    let responseHeaders = headers.response;
+    for (let i = 0; i < responseHeaders.length; i++) {
+      let header = responseHeaders[i];
       value += header.name + ": " + header.value + eol;
     }
 
     value += eol;
 
-    var requestHeaders = headers.request;
-    for (var i=0; i<requestHeaders.length; i++) {
-      var header = requestHeaders[i];
+    let requestHeaders = headers.request;
+    for (let i = 0; i < requestHeaders.length; i++) {
+      let header = requestHeaders[i];
       value += header.name + ": " + header.value + eol;
     }
 
     Clipboard.set(value, "text");
   }
 });
 
 // Stream converter component definition
-var service = xpcom.Service({
+let service = xpcom.Service({
   id: components.ID(CLASS_ID),
   contract: CONTRACT_ID,
   Component: Converter,
   register: false,
   unregister: false
 });
 
 function register() {
@@ -303,9 +312,9 @@ function unregister() {
     return true;
   }
   return false;
 }
 
 exports.JsonViewService = {
   register: register,
   unregister: unregister
-}
+};
--- a/devtools/client/jsonview/converter-observer.js
+++ b/devtools/client/jsonview/converter-observer.js
@@ -2,18 +2,16 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 const Cu = Components.utils;
-const Cc = Components.classes;
-const Ci = Components.interfaces;
 
 const {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
 const {Services} = Cu.import("resource://gre/modules/Services.jsm", {});
 
 // Load devtools module lazily.
 XPCOMUtils.defineLazyGetter(this, "devtools", function() {
   const {devtools} = Cu.import("resource://devtools/shared/Loader.jsm", {});
   return devtools;
@@ -56,17 +54,17 @@ ConverterObserver.prototype = {
   observe: function(subject, topic, data) {
     switch (topic) {
       case "xpcom-shutdown":
         this.onShutdown();
         break;
       case "nsPref:changed":
         this.onPrefChanged();
         break;
-    };
+    }
   },
 
   onShutdown: function() {
     Services.prefs.removeObserver(JSON_VIEW_PREF, observer);
     Services.obs.removeObserver(observer, "xpcom-shutdown");
   },
 
   onPrefChanged: function() {
--- a/devtools/client/jsonview/converter-sniffer.js
+++ b/devtools/client/jsonview/converter-sniffer.js
@@ -6,20 +6,21 @@
 
 "use strict";
 
 const {Cc, Ci, components} = require("chrome");
 const xpcom = require("sdk/platform/xpcom");
 const {Unknown} = require("sdk/platform/xpcom");
 const {Class} = require("sdk/core/heritage");
 
-const categoryManager = Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
+const categoryManager = Cc["@mozilla.org/categorymanager;1"]
+  .getService(Ci.nsICategoryManager);
 
 loader.lazyRequireGetter(this, "NetworkHelper",
-                               "devtools/shared/webconsole/network-helper");
+  "devtools/shared/webconsole/network-helper");
 
 // Constants
 const JSON_TYPE = "application/json";
 const CONTRACT_ID = "@mozilla.org/devtools/jsonview-sniffer;1";
 const CLASS_ID = "{4148c488-dca1-49fc-a621-2a0097a62422}";
 const JSON_VIEW_MIME_TYPE = "application/vnd.mozilla.json.view";
 const JSON_VIEW_TYPE = "JSON View";
 const CONTENT_SNIFFER_CATEGORY = "net-content-sniffers";
@@ -38,34 +39,35 @@ var Sniffer = Class({
   interfaces: [
     "nsIContentSniffer",
   ],
 
   get wrappedJSObject() {
     return this;
   },
 
-  getMIMETypeFromContent: function(aRequest, aData, aLength) {
+  getMIMETypeFromContent: function(request, data, length) {
     // JSON View is enabled only for top level loads only.
-    if (!NetworkHelper.isTopLevelLoad(aRequest)) {
+    if (!NetworkHelper.isTopLevelLoad(request)) {
       return "";
     }
 
-    if (aRequest instanceof Ci.nsIChannel) {
+    if (request instanceof Ci.nsIChannel) {
       try {
-        if (aRequest.contentDisposition == Ci.nsIChannel.DISPOSITION_ATTACHMENT) {
+        if (request.contentDisposition ==
+          Ci.nsIChannel.DISPOSITION_ATTACHMENT) {
           return "";
         }
       } catch (e) {
         // Channel doesn't support content dispositions
       }
 
       // Check the response content type and if it's application/json
       // change it to new internal type consumed by JSON View.
-      if (aRequest.contentType == JSON_TYPE) {
+      if (request.contentType == JSON_TYPE) {
         return JSON_VIEW_MIME_TYPE;
       }
     }
 
     return "";
   }
 });
 
@@ -86,19 +88,19 @@ function register() {
   }
 
   return false;
 }
 
 function unregister() {
   if (xpcom.isRegistered(service)) {
     categoryManager.deleteCategoryEntry(CONTENT_SNIFFER_CATEGORY,
-      JSON_VIEW_TYPE, false)
+      JSON_VIEW_TYPE, false);
     xpcom.unregister(service);
     return true;
   }
   return false;
 }
 
 exports.JsonViewSniffer = {
   register: register,
   unregister: unregister
-}
+};
--- a/devtools/client/jsonview/json-viewer.js
+++ b/devtools/client/jsonview/json-viewer.js
@@ -1,98 +1,98 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* global postChromeMessage */
+
+"use strict";
 
 define(function(require, exports, module) {
+  // ReactJS
+  const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+  const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
+  const { MainTabbedArea } = createFactories(require("./components/main-tabbed-area"));
 
-// ReactJS
-const ReactDOM = require("devtools/client/shared/vendor/react-dom");
-const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
-const { MainTabbedArea } = createFactories(require("./components/main-tabbed-area"));
+  const json = document.getElementById("json");
+  const headers = document.getElementById("headers");
 
-const json = document.getElementById("json");
-const headers = document.getElementById("headers");
-
-var jsonData;
+  let jsonData;
 
-try {
-  jsonData = JSON.parse(json.textContent);
-} catch (err) {
-  jsonData = err + "";
-}
+  try {
+    jsonData = JSON.parse(json.textContent);
+  } catch (err) {
+    jsonData = err + "";
+  }
 
-// Application state object.
-var input = {
-  jsonText: json.textContent,
-  jsonPretty : null,
-  json: jsonData,
-  headers: JSON.parse(headers.textContent),
-  tabActive: 1,
-  prettified: false
-}
-
-json.remove();
-headers.remove();
+  // Application state object.
+  let input = {
+    jsonText: json.textContent,
+    jsonPretty: null,
+    json: jsonData,
+    headers: JSON.parse(headers.textContent),
+    tabActive: 1,
+    prettified: false
+  };
 
-/**
- * Application actions/commands. This list implements all commands
- * available for the JSON viewer.
- */
-input.actions = {
-  onCopyJson: function() {
-    var value = input.prettified ? input.jsonPretty : input.jsonText;
-    postChromeMessage("copy", value);
-  },
+  json.remove();
+  headers.remove();
 
-  onSaveJson: function() {
-    var value = input.prettified ? input.jsonPretty : input.jsonText;
-    postChromeMessage("save", value);
-  },
+  /**
+   * Application actions/commands. This list implements all commands
+   * available for the JSON viewer.
+   */
+  input.actions = {
+    onCopyJson: function() {
+      let value = input.prettified ? input.jsonPretty : input.jsonText;
+      postChromeMessage("copy", value);
+    },
 
-  onCopyHeaders: function() {
-    postChromeMessage("copy-headers", input.headers);
-  },
+    onSaveJson: function() {
+      let value = input.prettified ? input.jsonPretty : input.jsonText;
+      postChromeMessage("save", value);
+    },
 
-  onSearch: function(value) {
-    theApp.setState({searchFilter: value});
-  },
+    onCopyHeaders: function() {
+      postChromeMessage("copy-headers", input.headers);
+    },
 
-  onPrettify: function(data) {
-    if (input.prettified) {
-      theApp.setState({jsonText: input.jsonText});
-    } else {
-      if (!input.jsonPretty) {
-        input.jsonPretty = JSON.stringify(jsonData, null, "  ");
+    onSearch: function(value) {
+      theApp.setState({searchFilter: value});
+    },
+
+    onPrettify: function(data) {
+      if (input.prettified) {
+        theApp.setState({jsonText: input.jsonText});
+      } else {
+        if (!input.jsonPretty) {
+          input.jsonPretty = JSON.stringify(jsonData, null, "  ");
+        }
+        theApp.setState({jsonText: input.jsonPretty});
       }
-      theApp.setState({jsonText: input.jsonPretty});
-    }
-
-    input.prettified = !input.prettified;
-  },
-}
 
-/**
- * Render the main application component. It's the main tab bar displayed
- * at the top of the window. This component also represents ReacJS root.
- */
-var content = document.getElementById("content");
-var theApp = ReactDOM.render(MainTabbedArea(input), content);
+      input.prettified = !input.prettified;
+    },
+  };
+
+  /**
+   * Render the main application component. It's the main tab bar displayed
+   * at the top of the window. This component also represents ReacJS root.
+   */
+  let content = document.getElementById("content");
+  let theApp = ReactDOM.render(MainTabbedArea(input), content);
 
-var onResize = event => {
-  window.document.body.style.height = window.innerHeight + "px";
-  window.document.body.style.width = window.innerWidth + "px";
-}
-
-window.addEventListener("resize", onResize);
-onResize();
+  let onResize = event => {
+    window.document.body.style.height = window.innerHeight + "px";
+    window.document.body.style.width = window.innerWidth + "px";
+  };
 
-// Send notification event to the window. Can be useful for
-// tests as well as extensions.
-var event = new CustomEvent("JSONViewInitialized", {});
-window.jsonViewInitialized = true;
-window.dispatchEvent(event);
+  window.addEventListener("resize", onResize);
+  onResize();
 
-// End of json-viewer.js
+  // Send notification event to the window. Can be useful for
+  // tests as well as extensions.
+  let event = new CustomEvent("JSONViewInitialized", {});
+  window.jsonViewInitialized = true;
+  window.dispatchEvent(event);
 });
 
--- a/devtools/client/jsonview/main.js
+++ b/devtools/client/jsonview/main.js
@@ -1,20 +1,21 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* globals JsonViewUtils*/
 
 "use strict";
 
-const {Cu, Ci, Cc} = require("chrome");
+const { Cu } = require("chrome");
 const Services = require("Services");
 
-const {XPCOMUtils} = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
+const { XPCOMUtils } = Cu.import("resource://gre/modules/XPCOMUtils.jsm", {});
 
 XPCOMUtils.defineLazyGetter(this, "JsonViewService", function() {
   return require("devtools/client/jsonview/utils");
 });
 
 /**
  * Singleton object that represents the JSON View in-content tool.
  * It has the same lifetime as the browser. Initialization done by
--- a/devtools/client/jsonview/test/browser_jsonview_copy_headers.js
+++ b/devtools/client/jsonview/test/browser_jsonview_copy_headers.js
@@ -5,29 +5,29 @@
 
 "use strict";
 
 const TEST_JSON_URL = URL_ROOT + "valid_json.json";
 
 add_task(function* () {
   info("Test valid JSON started");
 
-  let tab = yield addJsonViewTab(TEST_JSON_URL);
+  yield addJsonViewTab(TEST_JSON_URL);
 
   // Select the RawData tab
   yield selectJsonViewContentTab("headers");
 
   // Check displayed headers
   let count = yield getElementCount(".headersPanelBox .netHeadersGroup");
   is(count, 2, "There must be two header groups");
 
   let text = yield getElementText(".headersPanelBox .netInfoHeadersTable");
   isnot(text, "", "Headers text must not be empty");
 
-  let browser = gBrowser.selectedBrowser
+  let browser = gBrowser.selectedBrowser;
 
   // Verify JSON copy into the clipboard.
   yield waitForClipboardPromise(function setup() {
     BrowserTestUtils.synthesizeMouseAtCenter(
       ".headersPanelBox .toolbar button.copy",
       {}, browser);
   }, function validator(value) {
     return value.indexOf("application/json") > 0;
--- a/devtools/client/jsonview/test/browser_jsonview_copy_json.js
+++ b/devtools/client/jsonview/test/browser_jsonview_copy_json.js
@@ -5,27 +5,27 @@
 
 "use strict";
 
 const TEST_JSON_URL = URL_ROOT + "simple_json.json";
 
 add_task(function* () {
   info("Test copy JSON started");
 
-  let tab = yield addJsonViewTab(TEST_JSON_URL);
+  yield addJsonViewTab(TEST_JSON_URL);
 
   let countBefore = yield getElementCount(".jsonPanelBox .domTable .memberRow");
   ok(countBefore == 1, "There must be one row");
 
   let text = yield getElementText(".jsonPanelBox .domTable .memberRow");
   is(text, "name\"value\"", "There must be proper JSON displayed");
 
   // Verify JSON copy into the clipboard.
   let value = "{\"name\": \"value\"}\n";
-  let browser = gBrowser.selectedBrowser
+  let browser = gBrowser.selectedBrowser;
   let selector = ".jsonPanelBox .toolbar button.copy";
   yield waitForClipboardPromise(function setup() {
     BrowserTestUtils.synthesizeMouseAtCenter(selector, {}, browser);
   }, function validator(result) {
     let str = normalizeNewLines(result);
     return str == value;
   });
 });
--- a/devtools/client/jsonview/test/browser_jsonview_copy_rawdata.js
+++ b/devtools/client/jsonview/test/browser_jsonview_copy_rawdata.js
@@ -8,42 +8,43 @@
 const TEST_JSON_URL = URL_ROOT + "simple_json.json";
 
 let jsonText = "{\"name\": \"value\"}\n";
 let prettyJson = "{\n  \"name\": \"value\"\n}";
 
 add_task(function* () {
   info("Test copy raw data started");
 
-  let tab = yield addJsonViewTab(TEST_JSON_URL);
+  yield addJsonViewTab(TEST_JSON_URL);
 
   // Select the RawData tab
   yield selectJsonViewContentTab("rawdata");
 
   // Check displayed JSON
   let text = yield getElementText(".textPanelBox .data");
   is(text, jsonText, "Proper JSON must be displayed in DOM");
 
-  let browser = gBrowser.selectedBrowser
+  let browser = gBrowser.selectedBrowser;
 
   // Verify JSON copy into the clipboard.
   yield waitForClipboardPromise(function setup() {
     BrowserTestUtils.synthesizeMouseAtCenter(
       ".textPanelBox .toolbar button.copy",
       {}, browser);
   }, jsonText);
 
   // Click 'Pretty Print' button
   yield BrowserTestUtils.synthesizeMouseAtCenter(
     ".textPanelBox .toolbar button.prettyprint",
     {}, browser);
 
   let prettyText = yield getElementText(".textPanelBox .data");
   prettyText = normalizeNewLines(prettyText);
-  ok(prettyText.startsWith(prettyJson), "Pretty printed JSON must be displayed");
+  ok(prettyText.startsWith(prettyJson),
+    "Pretty printed JSON must be displayed");
 
   // Verify JSON copy into the clipboard.
   yield waitForClipboardPromise(function setup() {
     BrowserTestUtils.synthesizeMouseAtCenter(
       ".textPanelBox .toolbar button.copy",
       {}, browser);
   }, function validator(value) {
     let str = normalizeNewLines(value);
--- a/devtools/client/jsonview/test/browser_jsonview_filter.js
+++ b/devtools/client/jsonview/test/browser_jsonview_filter.js
@@ -5,23 +5,24 @@
 
 "use strict";
 
 const TEST_JSON_URL = URL_ROOT + "array_json.json";
 
 add_task(function* () {
   info("Test valid JSON started");
 
-  let tab = yield addJsonViewTab(TEST_JSON_URL);
+  yield addJsonViewTab(TEST_JSON_URL);
 
   let count = yield getElementCount(".jsonPanelBox .domTable .memberRow");
   is(count, 3, "There must be three rows");
 
   // XXX use proper shortcut to focus the filter box
   // as soon as bug Bug 1178771 is fixed.
   yield sendString("h", ".jsonPanelBox .searchBox");
 
   // The filtering is done asynchronously so, we need to wait.
   yield waitForFilter();
 
-  let hiddenCount = yield getElementCount(".jsonPanelBox .domTable .memberRow.hidden");
+  let hiddenCount = yield getElementCount(
+    ".jsonPanelBox .domTable .memberRow.hidden");
   is(hiddenCount, 2, "There must be two hidden rows");
 });
--- a/devtools/client/jsonview/test/browser_jsonview_invalid_json.js
+++ b/devtools/client/jsonview/test/browser_jsonview_invalid_json.js
@@ -5,16 +5,16 @@
 
 "use strict";
 
 const TEST_JSON_URL = URL_ROOT + "invalid_json.json";
 
 add_task(function* () {
   info("Test invalid JSON started");
 
-  let tab = yield addJsonViewTab(TEST_JSON_URL);
+  yield addJsonViewTab(TEST_JSON_URL);
 
   let count = yield getElementCount(".jsonPanelBox .domTable .memberRow");
   ok(count == 0, "There must be no row");
 
   let text = yield getElementText(".jsonPanelBox .jsonParseError");
   ok(text, "There must be an error description");
 });
--- a/devtools/client/jsonview/test/browser_jsonview_valid_json.js
+++ b/devtools/client/jsonview/test/browser_jsonview_valid_json.js
@@ -5,17 +5,17 @@
 
 "use strict";
 
 const TEST_JSON_URL = URL_ROOT + "valid_json.json";
 
 add_task(function* () {
   info("Test valid JSON started");
 
-  let tab = yield addJsonViewTab(TEST_JSON_URL);
+  yield addJsonViewTab(TEST_JSON_URL);
 
   let countBefore = yield getElementCount(".jsonPanelBox .domTable .memberRow");
   ok(countBefore == 1, "There must be one row");
 
   yield expandJsonNode(".jsonPanelBox .domTable .memberLabel");
 
   let countAfter = yield getElementCount(".jsonPanelBox .domTable .memberRow");
   ok(countAfter == 3, "There must be three rows");
--- a/devtools/client/jsonview/test/doc_frame_script.js
+++ b/devtools/client/jsonview/test/doc_frame_script.js
@@ -1,27 +1,28 @@
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 /* globals Services, sendAsyncMessage, addMessageListener */
 
-// XXX Some helper API could go to testing/mochitest/tests/SimpleTest/AsyncContentUtils.js
+// XXX Some helper API could go to:
+// testing/mochitest/tests/SimpleTest/AsyncContentUtils.js
 // (or at least to share test API in devtools)
 
 // Set up a dummy environment so that EventUtils works. We need to be careful to
 // pass a window object into each EventUtils method we call rather than having
 // it rely on the |window| global.
 let EventUtils = {};
 EventUtils.window = content;
 EventUtils.parent = EventUtils.window;
-EventUtils._EU_Ci = Components.interfaces;
-EventUtils._EU_Cc = Components.classes;
+EventUtils._EU_Ci = Components.interfaces; // eslint-disable-line
+EventUtils._EU_Cc = Components.classes; // eslint-disable-line
 EventUtils.navigator = content.navigator;
 EventUtils.KeyboardEvent = content.KeyboardEvent;
 
 Services.scriptloader.loadSubScript(
   "chrome://mochikit/content/tests/SimpleTest/EventUtils.js", EventUtils);
 
 /**
  * When the JSON View is done rendering it triggers custom event
@@ -75,17 +76,17 @@ addMessageListener("Test:JsonView:WaitFo
 
   // Check if the filter is already set.
   if (firstRow.classList.contains("hidden")) {
     sendAsyncMessage(msg.name);
     return;
   }
 
   // Wait till the first row has 'hidden' class set.
-  var observer = new content.MutationObserver(function(mutations) {
+  let observer = new content.MutationObserver(function(mutations) {
     for (let i = 0; i < mutations.length; i++) {
       let mutation = mutations[i];
       if (mutation.attributeName == "class") {
         if (firstRow.classList.contains("hidden")) {
           observer.disconnect();
           sendAsyncMessage(msg.name);
           break;
         }
--- a/devtools/client/jsonview/test/head.js
+++ b/devtools/client/jsonview/test/head.js
@@ -1,11 +1,14 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
+/* eslint no-unused-vars: [2, {"vars": "local", "args": "none"}] */
+/* import-globals-from ../../framework/test/shared-head.js */
+/* import-globals-from ../../framework/test/head.js */
 
 "use strict";
 
 // shared-head.js handles imports, constants, and utility functions
 Services.scriptloader.loadSubScript(
   "chrome://mochitests/content/browser/devtools/client/framework/test/head.js", this);
 
 const JSON_VIEW_PREF = "devtools.jsonview.enabled";
@@ -76,29 +79,31 @@ function selectJsonViewContentTab(name) 
 
 function getElementCount(selector) {
   info("Get element count: '" + selector + "'");
 
   let data = {
     selector: selector
   };
 
-  return executeInContent("Test:JsonView:GetElementCount", data).then(result => {
+  return executeInContent("Test:JsonView:GetElementCount", data)
+  .then(result => {
     return result.count;
   });
 }
 
 function getElementText(selector) {
   info("Get element text: '" + selector + "'");
 
   let data = {
     selector: selector
   };
 
-  return executeInContent("Test:JsonView:GetElementText", data).then(result => {
+  return executeInContent("Test:JsonView:GetElementText", data)
+  .then(result => {
     return result.text;
   });
 }
 
 function focusElement(selector) {
   info("Focus element: '" + selector + "'");
 
   let data = {
@@ -120,19 +125,19 @@ function sendString(str, selector) {
   let data = {
     selector: selector,
     str: str
   };
 
   return executeInContent("Test:JsonView:SendString", data);
 }
 
-function waitForTime(aDelay) {
+function waitForTime(delay) {
   let deferred = promise.defer();
-  setTimeout(deferred.resolve, aDelay);
+  setTimeout(deferred.resolve, delay);
   return deferred.promise;
 }
 
 function waitForClipboardPromise(setup, expected) {
   return new Promise((resolve, reject) => {
     SimpleTest.waitForClipboard(expected, setup, resolve, reject);
   });
 }
--- a/devtools/client/jsonview/utils.js
+++ b/devtools/client/jsonview/utils.js
@@ -1,101 +1,101 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
-const {Cu, Cc, Ci} = require("chrome");
+const { Cu, Cc, Ci } = require("chrome");
 const Services = require("Services");
-const {getMostRecentBrowserWindow} = require("sdk/window/utils");
+const { getMostRecentBrowserWindow } = require("sdk/window/utils");
 
 const OPEN_FLAGS = {
-  RDONLY: parseInt("0x01"),
-  WRONLY: parseInt("0x02"),
-  CREATE_FILE: parseInt("0x08"),
-  APPEND: parseInt("0x10"),
-  TRUNCATE: parseInt("0x20"),
-  EXCL: parseInt("0x80")
+  RDONLY: parseInt("0x01", 16),
+  WRONLY: parseInt("0x02", 16),
+  CREATE_FILE: parseInt("0x08", 16),
+  APPEND: parseInt("0x10", 16),
+  TRUNCATE: parseInt("0x20", 16),
+  EXCL: parseInt("0x80", 16)
 };
 
 /**
  * Open File Save As dialog and let the user to pick proper file location.
  */
 exports.getTargetFile = function() {
-  var fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+  let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
 
-  var win = getMostRecentBrowserWindow();
+  let win = getMostRecentBrowserWindow();
   fp.init(win, null, Ci.nsIFilePicker.modeSave);
-  fp.appendFilter("JSON Files","*.json; *.jsonp;");
+  fp.appendFilter("JSON Files", "*.json; *.jsonp;");
   fp.appendFilters(Ci.nsIFilePicker.filterText);
   fp.appendFilters(Ci.nsIFilePicker.filterAll);
   fp.filterIndex = 0;
 
-  var rv = fp.show();
+  let rv = fp.show();
   if (rv == Ci.nsIFilePicker.returnOK || rv == Ci.nsIFilePicker.returnReplace) {
     return fp.file;
   }
 
   return null;
-}
+};
 
 /**
  * Save JSON to a file
  */
 exports.saveToFile = function(file, jsonString) {
-  var foStream = Cc["@mozilla.org/network/file-output-stream;1"].
-    createInstance(Ci.nsIFileOutputStream);
+  let foStream = Cc["@mozilla.org/network/file-output-stream;1"]
+    .createInstance(Ci.nsIFileOutputStream);
 
   // write, create, truncate
   let openFlags = OPEN_FLAGS.WRONLY | OPEN_FLAGS.CREATE_FILE |
     OPEN_FLAGS.TRUNCATE;
 
   let permFlags = parseInt("0666", 8);
   foStream.init(file, openFlags, permFlags, 0);
 
-  var converter = Cc["@mozilla.org/intl/converter-output-stream;1"].
-    createInstance(Ci.nsIConverterOutputStream);
+  let converter = Cc["@mozilla.org/intl/converter-output-stream;1"]
+    .createInstance(Ci.nsIConverterOutputStream);
 
   converter.init(foStream, "UTF-8", 0, 0);
 
   // The entire jsonString can be huge so, write the data in chunks.
-  var chunkLength = 1024*1204;
-  for (var i=0; i<=jsonString.length; i++) {
-    var data = jsonString.substr(i, chunkLength+1);
+  let chunkLength = 1024 * 1204;
+  for (let i = 0; i <= jsonString.length; i++) {
+    let data = jsonString.substr(i, chunkLength + 1);
     if (data) {
       converter.writeString(data);
     }
     i = i + chunkLength;
   }
 
   // this closes foStream
   converter.close();
-}
+};
 
 /**
  * Get the current theme from preferences.
  */
 exports.getCurrentTheme = function() {
   return Services.prefs.getCharPref("devtools.theme");
-}
+};
 
 /**
  * Export given object into the target window scope.
  */
 exports.exportIntoContentScope = function(win, obj, defineAs) {
-  var clone = Cu.createObjectIn(win, {
+  let clone = Cu.createObjectIn(win, {
     defineAs: defineAs
   });
 
-  var props = Object.getOwnPropertyNames(obj);
-  for (var i=0; i<props.length; i++) {
-    var propName = props[i];
-    var propValue = obj[propName];
+  let props = Object.getOwnPropertyNames(obj);
+  for (let i = 0; i < props.length; i++) {
+    let propName = props[i];
+    let propValue = obj[propName];
     if (typeof propValue == "function") {
       Cu.exportFunction(propValue, clone, {
         defineAs: propName
       });
     }
   }
-}
+};
--- a/devtools/client/jsonview/viewer-config.js
+++ b/devtools/client/jsonview/viewer-config.js
@@ -1,13 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/* global requirejs */
+
+"use strict";
 
 /**
  * RequireJS configuration for JSON Viewer.
  *
  * ReactJS library is shared among DevTools. Both, the minified (production)
  * version and developer versions of the library are available.
  *
  * In order to use the developer version you need to specify the following
--- a/devtools/client/netmonitor/netmonitor-view.js
+++ b/devtools/client/netmonitor/netmonitor-view.js
@@ -1196,17 +1196,19 @@ RequestsMenuView.prototype = Heritage.ex
   },
 
   /**
    * Removes all network requests and closes the sidebar if open.
    */
   clear: function() {
     NetMonitorController.NetworkEventsHandler.clearMarkers();
     NetMonitorView.Sidebar.toggle(false);
+
     $("#details-pane-toggle").disabled = true;
+    $("#requests-menu-empty-notice").hidden = false;
 
     this.empty();
     this.refreshSummary();
   },
 
   /**
    * Predicates used when filtering items.
    *
--- a/devtools/client/netmonitor/test/browser_net_simple-request.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request.js
@@ -1,15 +1,21 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
+"use strict";
 
 /**
- * Tests if requests are handled correctly.
+ * Test whether the UI state properly reflects existence of requests
+ * displayed in the Net panel. The following parts of the UI are
+ * tested:
+ * 1) Side panel visibility
+ * 2) Side panel toggle button
+ * 3) Empty user message visibility
+ * 4) Number of requests displayed
  */
-
 function test() {
   initNetMonitor(SIMPLE_URL).then(([aTab, aDebuggee, aMonitor]) => {
     info("Starting test... ");
 
     let { document, NetMonitorView } = aMonitor.panelWin;
     let { RequestsMenu } = NetMonitorView;
 
     RequestsMenu.lazyUpdate = false;
@@ -44,16 +50,29 @@ function test() {
         is(document.querySelector("#requests-menu-empty-notice")
           .hasAttribute("hidden"), true,
           "The empty notice should be still hidden after a reload.");
         is(RequestsMenu.itemCount, 1,
           "The requests menu should not be empty after a reload.");
         is(NetMonitorView.detailsPaneHidden, true,
           "The details pane should still be hidden after a reload.");
 
+        RequestsMenu.clear();
+
+        is(document.querySelector("#details-pane-toggle")
+          .hasAttribute("disabled"), true,
+          "The pane toggle button should be disabled when after clear.");
+        is(document.querySelector("#requests-menu-empty-notice")
+          .hasAttribute("hidden"), false,
+          "An empty notice should be displayed again after clear.");
+        is(RequestsMenu.itemCount, 0,
+          "The requests menu should be empty after clear.");
+        is(NetMonitorView.detailsPaneHidden, true,
+          "The details pane should be hidden after clear.");
+
         teardown(aMonitor).then(finish);
       });
 
       aDebuggee.location.reload();
     });
 
     aDebuggee.location.reload();
   });
--- a/devtools/client/performance/test/head.js
+++ b/devtools/client/performance/test/head.js
@@ -65,18 +65,26 @@ const key = (id, win = window) => {
 
   // By default, enable memory flame graphs for tests for now.
   // TODO: remove when we have flame charts via bug 1148663.
   Services.prefs.setBoolPref(PrefUtils.UI_ENABLE_MEMORY_FLAME_CHART, true);
 
   registerCleanupFunction(() => {
     info(`finish() was called, cleaning up...`);
     DevToolsUtils.testing = false;
+
     PrefUtils.rollbackPrefsToDefault();
     stopObservingPrefs();
 
+    // Manually stop the profiler module at the end of all tests, to hopefully
+    // avoid at least some leaks on OSX. Theoretically the module should never
+    // be active at this point. We shouldn't have to do this, but rather
+    // find and fix the leak in the module itself. Bug 1257439.
+    let nsIProfilerModule = Cc["@mozilla.org/tools/profiler;1"].getService(Ci.nsIProfiler);
+    nsIProfilerModule.StopProfiler();
+
     // Forces GC, CC and shrinking GC to get rid of disconnected docshells
     // and windows.
     Cu.forceGC();
     Cu.forceCC();
     Cu.forceShrinkingGC();
   });
 })();
--- a/devtools/client/shared/components/frame.js
+++ b/devtools/client/shared/components/frame.js
@@ -43,72 +43,78 @@ module.exports = createClass({
 
     const { short, long, host } = getSourceNames(source);
     // Reparse the URL to determine if we should link this; `getSourceNames`
     // has already cached this indirectly. We don't want to attempt to
     // link to "self-hosted" and "(unknown)". However, we do want to link
     // to Scratchpad URIs.
     const isLinkable = !!(isScratchpadScheme(source) || parseURL(source));
     const elements = [];
+    const sourceElements = [];
+    let sourceEl;
 
     let tooltip = long;
     // Exclude all falsy values, including `0`, as even
     // a number 0 for line doesn't make sense, and should not be displayed.
     // If source isn't linkable, don't attempt to append line and column
     // info, as this probably doesn't make sense.
     if (isLinkable && line) {
       tooltip += `:${line}`;
       // Intentionally exclude 0
       if (column) {
         tooltip += `:${column}`;
       }
     }
 
-    let onClickTooltipString = l10n.getFormatStr("frame.viewsourceindebugger", tooltip);
     let attributes = {
       "data-url": long,
       className: "frame-link",
-      title: tooltip,
     };
 
-    if (isLinkable) {
-      elements.push(dom.a({
-        className: "frame-link-filename",
-        onClick,
-        title: onClickTooltipString
-      }, short));
-    } else {
-      // If source is not a URL (self-hosted, eval, etc.), don't make
-      // it an anchor link, as we can't link to it.
-      elements.push(dom.span({
-        className: "frame-link-filename"
-      }, short));
+    if (showFunctionName && frame.functionDisplayName) {
+      elements.push(
+        dom.span({ className: "frame-link-function-display-name" }, frame.functionDisplayName)
+      );
     }
 
+    sourceElements.push(dom.span({
+      className: "frame-link-filename",
+    }, short));
+
     // If source is linkable, and we have a line number > 0
     if (isLinkable && line) {
-      elements.push(dom.span({ className: "frame-link-colon" }, ":"));
-      elements.push(dom.span({ className: "frame-link-line" }, line));
+      sourceElements.push(dom.span({ className: "frame-link-colon" }, ":"));
+      sourceElements.push(dom.span({ className: "frame-link-line" }, line));
       // Intentionally exclude 0
       if (column) {
-        elements.push(dom.span({ className: "frame-link-colon" }, ":"));
-        elements.push(dom.span({ className: "frame-link-column" }, column));
+        sourceElements.push(dom.span({ className: "frame-link-colon" }, ":"));
+        sourceElements.push(dom.span({ className: "frame-link-column" }, column));
         // Add `data-column` attribute for testing
         attributes["data-column"] = column;
       }
 
       // Add `data-line` attribute for testing
       attributes["data-line"] = line;
     }
 
-    if (showFunctionName && frame.functionDisplayName) {
-      elements.unshift(
-        dom.span({ className: "frame-link-function-display-name" }, frame.functionDisplayName)
-      );
+    // If source is not a URL (self-hosted, eval, etc.), don't make
+    // it an anchor link, as we can't link to it.
+    if (isLinkable) {
+      sourceEl = dom.a({
+        onClick,
+        className: "frame-link-source",
+        title: l10n.getFormatStr("frame.viewsourceindebugger", tooltip)
+      }, sourceElements);
+    } else {
+      sourceEl = dom.span({
+        className: "frame-link-source",
+        title: tooltip,
+      }, sourceElements);
     }
+    elements.push(sourceEl);
 
     if (showHost && host) {
       elements.push(dom.span({ className: "frame-link-host" }, host));
     }
 
     return dom.span(attributes, ...elements);
   }
 });
--- a/devtools/client/shared/components/test/mochitest/chrome.ini
+++ b/devtools/client/shared/components/test/mochitest/chrome.ini
@@ -1,15 +1,14 @@
 [DEFAULT]
 support-files =
   head.js
 
 [test_HSplitBox_01.html]
 [test_frame_01.html]
-[test_frame_02.html]
 [test_tree_01.html]
 [test_tree_02.html]
 [test_tree_03.html]
 [test_tree_04.html]
 [test_tree_05.html]
 [test_tree_06.html]
 [test_tree_07.html]
 [test_tree_08.html]
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -132,44 +132,35 @@ var TEST_TREE = {
     O: "N"
   },
   expanded: new Set(),
 };
 
 /**
  * Frame
  */
-function checkFrameString({ frame, file, line, column, shouldLink }) {
+function checkFrameString({ frame, file, line, column, shouldLink, tooltip }) {
   let el = frame.getDOMNode();
   let $ = selector => el.querySelector(selector);
 
+  let $source = $(".frame-link-source");
+  let $filename = $(".frame-link-filename");
   let $line = $(".frame-link-line");
   let $column = $(".frame-link-column");
 
-  is($(".frame-link-filename").textContent, file);
-
+  is($filename.textContent, file, "Correct filename");
   is(el.getAttribute("data-line"), line ? `${line}` : null, "Expected `data-line` found");
   is(el.getAttribute("data-column"), column ? `${column}` : null, "Expected `data-column` found");
-
-  if (shouldLink) {
-    ok($("a.frame-link-filename"), "Should have a linkable filename");
-  } else {
-    ok(!$("a.frame-link-filename"), "Should not have a linkable filename");
-  }
+  is($source.getAttribute("title"), tooltip, "Correct tooltip");
+  is($source.tagName, shouldLink ? "A" : "SPAN", "Correct linkable status");
 
   if (line != null) {
     is(+$line.textContent, +line);
   } else {
     ok(!$line, "Should not have an element for `line`");
   }
 
   if (column != null) {
     is(+$column.textContent, +column);
   } else {
     ok(!$column, "Should not have an element for `column`");
   }
 }
-
-function checkFrameTooltips (component, mainTooltip, linkTooltip) {
-  let el = component.getDOMNode();
-  is(el.getAttribute("title"), mainTooltip);
-  is(el.querySelector(".frame-link-filename").getAttribute("title"), linkTooltip);
-}
--- a/devtools/client/shared/components/test/mochitest/test_frame_01.html
+++ b/devtools/client/shared/components/test/mochitest/test_frame_01.html
@@ -18,184 +18,143 @@ window.onload = Task.async(function* () 
   try {
     let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
     let React = browserRequire("devtools/client/shared/vendor/react");
     let Frame = React.createFactory(browserRequire("devtools/client/shared/components/frame"));
     ok(Frame, "Should get Frame");
     let frame;
 
     // Check when there's a column
-    frame = ReactDOM.render(Frame({
-      frame: {
-        source: "http://myfile.com/mahscripts.js",
-        line: 55,
-        column: 10,
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameString({
-      frame,
+    yield checkFrameComponent({
+      source: "http://myfile.com/mahscripts.js",
+      line: 55,
+      column: 10,
+    }, {
       file: "mahscripts.js",
       line: 55,
       column: 10,
       shouldLink: true,
+      tooltip: "View source in Debugger → http://myfile.com/mahscripts.js:55:10",
     });
 
     // Check when there's no column
-    frame = ReactDOM.render(Frame({
-      frame: {
-        source: "http://myfile.com/mahscripts.js",
-        line: 55,
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameString({
-      frame,
+    yield checkFrameComponent({
+      source: "http://myfile.com/mahscripts.js",
+      line: 55,
+    }, {
       file: "mahscripts.js",
       line: 55,
       shouldLink: true,
+      tooltip: "View source in Debugger → http://myfile.com/mahscripts.js:55",
     });
 
     // Check when column === 0
-    frame = ReactDOM.render(Frame({
-      frame: {
-        source: "http://myfile.com/mahscripts.js",
-        line: 55,
-        column: 0,
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameString({
-      frame,
+    yield checkFrameComponent({
+      source: "http://myfile.com/mahscripts.js",
+      line: 55,
+      column: 0,
+    }, {
       file: "mahscripts.js",
       line: 55,
       shouldLink: true,
+      tooltip: "View source in Debugger → http://myfile.com/mahscripts.js:55",
     });
 
     // Check when there's no parseable URL source;
     // should not render line/columns
-    frame = ReactDOM.render(Frame({
-      frame: {
-        source: "self-hosted",
-        line: 1,
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameString({
-      frame,
+    yield checkFrameComponent({
+      source: "self-hosted",
+      line: 1,
+    }, {
       file: "self-hosted",
       shouldLink: false,
+      tooltip: "self-hosted",
     });
 
     // Check when there's no source;
     // should not render line/columns
-    frame = ReactDOM.render(Frame({
-      frame: {
-        line: 1,
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameString({
-      frame,
+    yield checkFrameComponent({
+      line: 1,
+    }, {
       file: "(unknown)",
       shouldLink: false,
+      tooltip: "(unknown)",
     });
 
     // Check when there's a column, but no number;
     // no line/column info should render
-    frame = ReactDOM.render(Frame({
-      frame: {
-        source: "http://myfile.com/mahscripts.js",
-        column: 55,
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameString({
-      frame,
+    yield checkFrameComponent({
+      source: "http://myfile.com/mahscripts.js",
+      column: 55,
+    }, {
       file: "mahscripts.js",
       shouldLink: true,
+      tooltip: "View source in Debugger → http://myfile.com/mahscripts.js",
     });
 
     // Check when line is 0; this should be an invalid
     // line option, so don't render line/column
-    frame = ReactDOM.render(Frame({
-      frame: {
-        source: "http://myfile.com/mahscripts.js",
-        line: 0,
-        column: 55,
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameString({
-      frame,
+    yield checkFrameComponent({
+      source: "http://myfile.com/mahscripts.js",
+      line: 0,
+      column: 55,
+    }, {
       file: "mahscripts.js",
       shouldLink: true,
+      tooltip: "View source in Debugger → http://myfile.com/mahscripts.js",
     });
 
     // Check when source is via Scratchpad; we should render out the
     // lines and columns as this is linkable.
-    frame = ReactDOM.render(Frame({
-      frame: {
-        source: "Scratchpad/1",
-        line: 10,
-        column: 50,
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameString({
-      frame,
+    yield checkFrameComponent({
+      source: "Scratchpad/1",
+      line: 10,
+      column: 50,
+    }, {
       file: "Scratchpad/1",
       line: 10,
       column: 50,
       shouldLink: true,
+      tooltip: "View source in Debugger → Scratchpad/1:10:50",
     });
 
     // Check that line and column can be strings
-    frame = ReactDOM.render(Frame({
-      frame: {
-        source: "http://myfile.com/mahscripts.js",
-        line: "10",
-        column: "55",
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameString({
-      frame,
+    yield checkFrameComponent({
+      source: "http://myfile.com/mahscripts.js",
+      line: "10",
+      column: "55",
+    }, {
       file: "mahscripts.js",
       line: 10,
       column: 55,
       shouldLink: true,
+      tooltip: "View source in Debugger → http://myfile.com/mahscripts.js:10:55",
     });
 
     // Check that line and column can be strings,
     // and that the `0` rendering rules apply when they are strings as well
-    frame = ReactDOM.render(Frame({
-      frame: {
-        source: "http://myfile.com/mahscripts.js",
-        line: "0",
-        column: "55",
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameString({
-      frame,
+    yield checkFrameComponent({
+      source: "http://myfile.com/mahscripts.js",
+      line: "0",
+      column: "55",
+    }, {
       file: "mahscripts.js",
       shouldLink: true,
+      tooltip: "View source in Debugger → http://myfile.com/mahscripts.js",
     });
 
+    function* checkFrameComponent (input, expected) {
+      let frame = ReactDOM.render(Frame({
+        frame: input,
+        onClick: () => {},
+      }), window.document.body);
+      yield forceRender(frame);
+      checkFrameString(Object.assign({ frame }, expected));
+    }
+
   } catch (e) {
     ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
   } finally {
     SimpleTest.finish();
   }
 });
 </script>
 </pre>
deleted file mode 100644
--- a/devtools/client/shared/components/test/mochitest/test_frame_02.html
+++ /dev/null
@@ -1,85 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-Test the formatting of the tooltips in the frame component.
--->
-<head>
-  <meta charset="utf-8">
-  <title>Frame component test</title>
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
-</head>
-<body>
-<pre id="test">
-<script src="head.js" type="application/javascript;version=1.8"></script>
-<script type="application/javascript;version=1.8">
-window.onload = Task.async(function* () {
-  try {
-    let ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
-    let React = browserRequire("devtools/client/shared/vendor/react");
-    let Frame = React.createFactory(browserRequire("devtools/client/shared/components/frame"));
-    ok(Frame, "Should get Frame");
-    let frame;
-
-    // Check when there's a column
-    frame = ReactDOM.render(Frame({
-      frame: {
-        source: "http://myfile.com/mahscripts.js",
-        line: 55,
-        column: 10,
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameTooltips(frame,
-      "http://myfile.com/mahscripts.js:55:10",
-      "View source in Debugger → http://myfile.com/mahscripts.js:55:10");
-
-    // Check when there's no column
-    frame = ReactDOM.render(Frame({
-      frame: {
-        source: "http://myfile.com/mahscripts.js",
-        line: 55,
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameTooltips(frame,
-      "http://myfile.com/mahscripts.js:55",
-      "View source in Debugger → http://myfile.com/mahscripts.js:55");
-
-    // Check when there's no parseable URL source
-    frame = ReactDOM.render(Frame({
-      frame: {
-        source: "self-hosted",
-        line: 1,
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameTooltips(frame,
-      "self-hosted",
-      null);
-
-    // Check when there's no source
-    frame = ReactDOM.render(Frame({
-      frame: {
-        line: 1,
-      },
-      onClick: ()=>{},
-    }), window.document.body);
-    yield forceRender(frame);
-    checkFrameTooltips(frame,
-      "(unknown)",
-      null);
-
-  } catch(e) {
-    ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e));
-  } finally {
-    SimpleTest.finish();
-  }
-});
-</script>
-</pre>
-</body>
-</html>
--- a/devtools/client/themes/components-frame.css
+++ b/devtools/client/themes/components-frame.css
@@ -8,32 +8,37 @@
  * Styles for React component at `devtools/client/shared/components/frame.js`
  */
 
 .frame-link {
   margin-left: 7px;
   display: flex;
 }
 
+.frame-link .frame-link-source {
+  display: flex;
+}
+.frame-link a.frame-link-source {
+  cursor: pointer;
+  text-decoration: none;
+}
+.frame-link a.frame-link-source:hover {
+  text-decoration: underline;
+}
+
 .frame-link .frame-link-filename {
+  color: var(--theme-highlight-blue);
   text-overflow: ellipsis;
   overflow: hidden;
   flex: 1;
   text-align: right;
   /* overrides styling some tools have with anchors */
   text-decoration: none;
   font-style: normal;
 }
-.frame-link a.frame-link-filename {
-  cursor: pointer;
-  color: var(--theme-highlight-blue);
-}
-.frame-link a.frame-link-filename:hover {
-  text-decoration: underline;
-}
 
 .frame-link .frame-link-host {
   margin-inline-start: 5px;
   font-size: 90%;
   color: var(--theme-content-color2);
 }
 
 .frame-link .frame-link-function-display-name {
@@ -42,16 +47,15 @@
 
 .frame-link .frame-link-column,
 .frame-link .frame-link-line,
 .frame-link .frame-link-colon {
   color: var(--theme-highlight-orange);
   display: block;
 }
 
-.focused a.frame-link-filename,
-.focused span.frame-link-filename,
-.focused .frame-link-column,
-.focused .frame-link-line,
-.focused .frame-link-host,
-.focused .frame-link-colon {
+.focused .frame-link .frame-link-filename,
+.focused .frame-link .frame-link-column,
+.focused .frame-link .frame-link-line,
+.focused .frame-link .frame-link-host,
+.focused .frame-link .frame-link-colon {
   color: var(--theme-selection-color);
 }
index 11fd2cd062914d65583956a072ec2b0d01e02473..eca7be8964ffe4dfd9685f2d72ed487a99d28d38
GIT binary patch
literal 983
zc$|Gx&ui2`6b`hAmKJ-|UL2=_3f&}=ZnE9bSh~rs4Vbd*F1RP@Y%<#o+hk%g?WR3?
zD-;zG&mQ#ZSx`{$qEIg${R6ys@ZisbSSQ==R)p;^zux2f-n@D9&2Dw|`qaeCgdhl0
z_KH>G>vT3wpW^d{uLqbf=UKVQ>a@*zI3~ifM_UBg0bVCHg1znA@5rJcj9u~@P1ba7
z=q?RJoMFT?h=i;yE~OE6yM%!jS@%N&etGc<0^c*>P1TW|$Rr(qWhW-}oz;fB({+m;
zT)GApQ=JD02*V%^Ho`<t4cL#X^L^%)Am~F_*MNsfHJvIjX-t4B&gWbiA)sj@%Bz}&
z=71t2MUr{fawxB>nywVUQG@)n*lX)Gt8#P}w+z@}EYc;Z*XxPBd6CBJ5-JvpSqw$V
zaYQcJ4jE2!VKO_2V3EX){fPNA1euLnbdwnn4z3VH!?0m;R3=_9DaDb5L|OU==r|`s
zgJ6hGSdFw#MkkH!h)6Y((9PK857(Z}$dPWw1Tz{pDBT!TvD%@GCLJ09v#x<F4tD)8
zb6o8+97ngqgyGO7wq-!>5q;m&^NK7N<|~#}uvCP~YDrlzD`pW@@)}agsxshO)ZGk7
z$Oc^Rm|Gsn&BSoXCb7Royh=<1(7&|qkItnuI+sJPH#!${Bv<0eNdG<cz{T~GZHL0;
z#!&hs<a&>}u+Kbl@A6M!+tzX;{n+|_aIiEs`SQ<?pYZ+%_w`wEH%6b|EX;KtfcJaj
zlfUlm<JRNpy%~9Ka_!>7>RYt-<f-*der{RdR_T*4eoolhpZb1TsqbZ9%`UH6&q`}|
F{sOU0G_C*u
index dda6a952f82c83fab473c907cff4b15db36ee3f3..67aad813d229ad0d6de8ed2083b656b34281aed1
GIT binary patch
literal 1045
zc$|GxO=uHA6yA{1)?y141T6@|R;-04`_p98EKUEi**4;aG!2-7(r$Jq&64g<cPGs@
zh*UvKK@?Oxcq_g1<gph)EhwUAMNq+`*h@W#2T|&5+C&l3VP<FE+wXhx=FK<j>C|Lr
zM^6Vq5S_^>A%kb&jrLajb*k0xcsh*|IW%JyP(`*OF|L|<2$H(I05eclSC(GGVS@0V
z)v`I1lP>a#sRv~b6LfWp@Y?XGYspFpB9Mm*nh_)So;)LgrpCxiVTqQkI4o*YRU6J!
zQ(2{2Qlct3Is%4W9tY4Nl7Xu)8V>Kq$XZ+;uRS+Kf*J&sV&p+mIVla|rVT+jI2cf9
zh5;NGWI|z%V+H_AGb}~p&IOndALe*A0uCFAceB+3pAp2vy|5J{iwIdfMO7-5U}Z39
z+6xpDjYho~EE~XxfU{yC*$o&@Z#{wl9mUoxq?rcrY&mb1QH&((N6@WCSi?Ci6D}C#
z$`-{0Y3d)KBpnUa^#<BO8CW<P?POOhNM)d7mTd)JT%p$^TYTJxGBWL~X)e~Qm@b;g
zbc&`0;ximLFUg8#c#i%WLz4KU;UL*iU{Z*Y*b~$=l@GBr9T^m(LPQ8NOd>qN4#mZI
zlo3N5!zRLPohz71S%(IybJf4x@upl)3<qpzYs*j-ZBqxeL-ShmTq4bLIpC_za~W#N
zrEoITe^*_1Vf}c^hH$abkUli9-fb-G-zS&0@uOHv3gcP#L;lzP{-~e(_VeRs8@y|I
zv3UH!^|`UjeQ#cVT9@v1-}OKB^`}~QSNF!+EcyQY&gQW~wD;!2{a0VQ&)xF1Zg!n2
zKi~3;XXxG4>9*Va{9E6Euw4|F+tZuU(4+QA@5jb$*VVNjU$>@DT<hy=>Djnay0AlS
d&E|Y#t3HBz-!r1jiFdq*noOjG#}o56{s4EgOymFn
--- a/devtools/client/themes/webconsole.css
+++ b/devtools/client/themes/webconsole.css
@@ -97,17 +97,17 @@ a {
   white-space: nowrap;
 }
 
 .message-location:hover,
 .message-location:focus {
   text-decoration: underline;
 }
 
-.message-location > .frame-link {
+.message-location > .frame-link .frame-link-source {
   width: 10em;
 }
 
 .message-flex-body {
   display: flex;
 }
 
 .message-body > * {
--- a/devtools/client/webconsole/webconsole.js
+++ b/devtools/client/webconsole/webconsole.js
@@ -10,17 +10,16 @@ const {Cc, Ci, Cu} = require("chrome");
 
 const {Utils: WebConsoleUtils, CONSOLE_WORKER_IDS} =
   require("devtools/shared/webconsole/utils");
 const { getSourceNames } = require("devtools/client/shared/source-utils");
 const BrowserLoaderModule = {};
 Cu.import("resource://devtools/client/shared/browser-loader.js", BrowserLoaderModule);
 
 const promise = require("promise");
-const Debugger = require("Debugger");
 const Services = require("Services");
 
 loader.lazyServiceGetter(this, "clipboardHelper",
                          "@mozilla.org/widget/clipboardhelper;1",
                          "nsIClipboardHelper");
 loader.lazyRequireGetter(this, "EventEmitter", "devtools/shared/event-emitter");
 loader.lazyRequireGetter(this, "AutocompletePopup", "devtools/client/shared/autocomplete-popup", true);
 loader.lazyRequireGetter(this, "ToolSidebar", "devtools/client/framework/sidebar", true);
@@ -657,17 +656,17 @@ WebConsoleFrame.prototype = {
    * all the listener is not registered.
    *
    * @param function [callback=null]
    *        Optional function to invoke when the listener has been
    *        added/removed.
    */
   _updateServerLoggingListener: function(callback) {
     if (!this.webConsoleClient) {
-      return;
+      return null;
     }
 
     let startListener = false;
     let prefs = ["servererror", "serverwarn", "serverinfo", "serverlog"];
     for (let i = 0; i < prefs.length; i++) {
       if (this.filterPrefs[prefs[i]]) {
         startListener = true;
         break;
@@ -2527,23 +2526,29 @@ WebConsoleFrame.prototype = {
     let fullURL = url.split(" -> ").pop();
     let locationNode = this.document.createElementNS(XHTML_NS, "a");
     locationNode.draggable = false;
     locationNode.className = "message-location devtools-monospace";
 
     // Make the location clickable.
     let onClick = () => {
       let category = locationNode.parentNode.category;
-      let target = category === CATEGORY_CSS ? "styleeditor" :
-                   category === CATEGORY_JS ? "jsdebugger" :
-                   category === CATEGORY_WEBDEV ? "jsdebugger" :
-                   /^Scratchpad\/\d+$/.test(url) ? "scratchpad" :
-                   // If it ends in .js, let's attempt to open in debugger
-                   // anyway, as this falls back to normal view-source.
-                   /\.js$/.test(fullURL) ? "jsdebugger" : null;
+      let target = null;
+
+      if (category === CATEGORY_CSS) {
+        target = "styleeditor";
+      } else if (category === CATEGORY_JS || category === CATEGORY_WEBDEV) {
+        target = "jsdebugger";
+      } else if (/^Scratchpad\/\d+$/.test(url)) {
+        target = "scratchpad";
+      } else if (/\.js$/.test(fullURL)) {
+        // If it ends in .js, let's attempt to open in debugger
+        // anyway, as this falls back to normal view-source.
+        target = "jsdebugger";
+      }
 
       switch (target) {
         case "scratchpad":
           this.owner.viewSourceInScratchpad(url, line);
           return;
         case "jsdebugger":
           this.owner.viewSourceInDebugger(fullURL, line);