Merge m-c to b2g-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 19 Sep 2014 14:43:09 +0200
changeset 206181 902fd282709e47220935481a1fe11ee31843ad9c
parent 206180 f2b6ec226159c5ae4c65693760ffeb64962d545b (current diff)
parent 206162 3475e6a1665a6339dcbdc220a06ebdcdd9a9293c (diff)
child 206182 9151d481214615b4013973b498f76e71b392ac7e
push id27515
push userryanvm@gmail.com
push dateFri, 19 Sep 2014 17:29:33 +0000
treeherdermozilla-central@74d4fc84e39e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone35.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 m-c to b2g-inbound
browser/themes/linux/pluginInstall-16.png
browser/themes/linux/pluginInstall-64.png
browser/themes/osx/pluginInstall-16.png
browser/themes/osx/pluginInstall-16@2x.png
browser/themes/osx/pluginInstall-64.png
browser/themes/osx/pluginInstall-64@2x.png
browser/themes/windows/pluginInstall-16.png
browser/themes/windows/pluginInstall-64.png
content/media/MediaDecoderStateMachine.cpp
toolkit/locales/en-US/chrome/global/xpinstall/xpinstall.properties
toolkit/locales/en-US/chrome/mozapps/plugins/plugins.properties
toolkit/mozapps/plugins/content/pluginInstallerDatasource.js
toolkit/mozapps/plugins/content/pluginInstallerService.js
toolkit/mozapps/plugins/content/pluginInstallerWizard.css
toolkit/mozapps/plugins/content/pluginInstallerWizard.js
toolkit/mozapps/plugins/content/pluginInstallerWizard.xul
toolkit/mozapps/plugins/service/PluginFinderService.java
toolkit/mozapps/plugins/service/PluginFinderService.php
toolkit/mozapps/plugins/service/PluginInfo.java
toolkit/mozapps/plugins/service/make.sh
toolkit/mozapps/plugins/tests/BadExtension.xpi
toolkit/mozapps/plugins/tests/BadPlugin.cpp
toolkit/mozapps/plugins/tests/GoodExtension.xpi
toolkit/mozapps/plugins/tests/GoodPlugin.cpp
toolkit/mozapps/plugins/tests/Makefile.in
toolkit/mozapps/plugins/tests/browser.ini
toolkit/mozapps/plugins/tests/browser_bug435788.js
toolkit/mozapps/plugins/tests/moz.build
toolkit/mozapps/plugins/tests/pfs_bug435788_1.rdf
toolkit/mozapps/plugins/tests/pfs_bug435788_2.rdf
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -659,18 +659,16 @@ pref("accessibility.typeaheadfind.flashB
 // plugin finder service url
 pref("pfs.datasource.url", "https://pfs.mozilla.org/plugins/PluginFinderService.php?mimetype=%PLUGIN_MIMETYPE%&appID=%APP_ID%&appVersion=%APP_VERSION%&clientOS=%CLIENT_OS%&chromeLocale=%CHROME_LOCALE%&appRelease=%APP_RELEASE%");
 
 pref("plugins.update.url", "https://www.mozilla.org/%LOCALE%/plugincheck/?utm_source=firefox-browser&utm_medium=firefox-browser&utm_campaign=plugincheck-update");
 pref("plugins.update.notifyUser", false);
 
 pref("plugins.click_to_play", true);
 
-pref("plugins.hideMissingPluginsNotification", false);
-
 pref("plugin.default.state", 1);
 
 // Plugins bundled in XPIs are enabled by default.
 pref("plugin.defaultXpi.state", 2);
 
 // Flash is enabled by default, and Java is click-to-activate by default on
 // all channels.
 pref("plugin.state.flash", 2);
@@ -840,19 +838,16 @@ pref("plugin.state.np_prsnl", 2);
 #endif
 #ifdef XP_MACOSX
 pref("plugin.state.personalplugin", 2);
 #endif
 #ifdef UNIX_BUT_NOT_MAC
 pref("plugin.state.libplugins", 2);
 #endif
 
-// display door hanger if flash not installed
-pref("plugins.notifyMissingFlash", true);
-
 #ifdef XP_MACOSX
 pref("browser.preferences.animateFadeIn", true);
 #else
 pref("browser.preferences.animateFadeIn", false);
 #endif
 
 // Toggles between the two Preferences implementations, pop-up window and in-content
 #ifdef NIGHTLY_BUILD
--- a/browser/base/content/browser-loop.js
+++ b/browser/base/content/browser-loop.js
@@ -27,16 +27,19 @@ XPCOMUtils.defineLazyModuleGetter(this, 
     openCallPanel: function(event) {
       let callback = iframe => {
         iframe.addEventListener("DOMContentLoaded", function documentDOMLoaded() {
           iframe.removeEventListener("DOMContentLoaded", documentDOMLoaded, true);
           injectLoopAPI(iframe.contentWindow);
         }, true);
       };
 
+      // Used to clear the temporary "login" state from the button.
+      Services.obs.notifyObservers(null, "loop-status-changed", null);
+
       PanelFrame.showPopup(window, event.target, "loop", null,
                            "about:looppanel", null, callback);
     },
 
     /**
      * Triggers the initialization of the loop service.  Called by
      * delayedStartup.
      */
@@ -64,22 +67,34 @@ XPCOMUtils.defineLazyModuleGetter(this, 
       Services.obs.removeObserver(this, "loop-status-changed");
     },
 
     // Implements nsIObserver
     observe: function(subject, topic, data) {
       if (topic != "loop-status-changed") {
         return;
       }
-      this.updateToolbarState();
+      this.updateToolbarState(data);
     },
 
-    updateToolbarState: function() {
+    /**
+     * Updates the toolbar/menu-button state to reflect Loop status.
+     *
+     * @param {string} [aReason] Some states are only shown if
+     *                           a related reason is provided.
+     *
+     *                 aReason="login": Used after a login is completed
+     *                   successfully. This is used so the state can be
+     *                   temporarily shown until the next state change.
+     */
+    updateToolbarState: function(aReason = null) {
       let state = "";
       if (MozLoopService.errors.size) {
         state = "error";
+      } else if (aReason == "login" && MozLoopService.userProfile) {
+        state = "active";
       } else if (MozLoopService.doNotDisturb) {
         state = "disabled";
       }
       this.toolbarButton.node.setAttribute("state", state);
     },
   };
 })();
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -1,16 +1,14 @@
 # -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 var gPluginHandler = {
-  PREF_NOTIFY_MISSING_FLASH: "plugins.notifyMissingFlash",
-  PREF_HIDE_MISSING_PLUGINS_NOTIFICATION: "plugins.hideMissingPluginsNotification",
   PREF_SESSION_PERSIST_MINUTES: "plugin.sessionPermissionNow.intervalInMinutes",
   PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays",
   MESSAGES: [
     "PluginContent:ShowClickToPlayNotification",
     "PluginContent:RemoveNotification",
     "PluginContent:UpdateHiddenPluginUI",
     "PluginContent:HideNotificationBar",
     "PluginContent:ShowInstallNotification",
@@ -88,104 +86,16 @@ var gPluginHandler = {
 #ifdef MOZ_CRASHREPORTER
   get CrashSubmit() {
     delete this.CrashSubmit;
     Cu.import("resource://gre/modules/CrashSubmit.jsm", this);
     return this.CrashSubmit;
   },
 #endif
 
-  supportedPlugins: {
-    "mimetypes": {
-      "application/x-shockwave-flash": "flash",
-      "application/futuresplash": "flash",
-      "application/x-java-.*": "java",
-      "application/x-director": "shockwave",
-      "application/(sdp|x-(mpeg|rtsp|sdp))": "quicktime",
-      "audio/(3gpp(2)?|AMR|aiff|basic|mid(i)?|mp4|mpeg|vnd\.qcelp|wav|x-(aiff|m4(a|b|p)|midi|mpeg|wav))": "quicktime",
-      "image/(pict|png|tiff|x-(macpaint|pict|png|quicktime|sgi|targa|tiff))": "quicktime",
-      "video/(3gpp(2)?|flc|mp4|mpeg|quicktime|sd-video|x-mpeg)": "quicktime",
-      "application/x-unknown": "test",
-    },
-
-    "plugins": {
-      "flash": {
-        "displayName": "Flash",
-        "installWINNT": true,
-        "installDarwin": true,
-        "installLinux": true,
-      },
-      "java": {
-        "displayName": "Java",
-        "installWINNT": true,
-        "installDarwin": true,
-        "installLinux": true,
-      },
-      "shockwave": {
-        "displayName": "Shockwave",
-        "installWINNT": true,
-        "installDarwin": true,
-      },
-      "quicktime": {
-        "displayName": "QuickTime",
-        "installWINNT": true,
-      },
-      "test": {
-        "displayName": "Test plugin",
-        "installWINNT": true,
-        "installLinux": true,
-        "installDarwin": true,
-      }
-    }
-  },
-
-  nameForSupportedPlugin: function (aMimeType) {
-    for (let type in this.supportedPlugins.mimetypes) {
-      let re = new RegExp(type);
-      if (re.test(aMimeType)) {
-        return this.supportedPlugins.mimetypes[type];
-      }
-    }
-    return null;
-  },
-
-  canInstallThisMimeType: function (aMimeType) {
-    let os = Services.appinfo.OS;
-    let pluginName = this.nameForSupportedPlugin(aMimeType);
-    if (pluginName && "install" + os in this.supportedPlugins.plugins[pluginName]) {
-      return true;
-    }
-    return false;
-  },
-
-  newPluginInstalled : function(event) {
-    // browser elements are anonymous so we can't just use target.
-    var browser = event.originalTarget;
-    // clear the plugin list, now that at least one plugin has been installed
-    browser.missingPlugins = null;
-
-    var notificationBox = gBrowser.getNotificationBox(browser);
-    var notification = notificationBox.getNotificationWithValue("missing-plugins");
-    if (notification)
-      notificationBox.removeNotification(notification);
-
-    // reload the browser to make the new plugin show.
-    browser.reload();
-  },
-
-  // Callback for user clicking on a missing (unsupported) plugin.
-  installSinglePlugin: function (pluginInfo) {
-    var missingPlugins = new Map();
-    missingPlugins.set(pluginInfo.mimetype, pluginInfo);
-
-    openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
-               "PFSWindow", "chrome,centerscreen,resizable=yes",
-               {plugins: missingPlugins, browser: gBrowser.selectedBrowser});
-  },
-
   // Callback for user clicking on a disabled plugin
   managePlugins: function () {
     BrowserOpenAddonsMgr("addons://list/plugin");
   },
 
   // Callback for user clicking on the link in a click-to-play plugin
   // (where the plugin has an update)
   openPluginUpdatePage: function () {
@@ -207,76 +117,16 @@ var gPluginHandler = {
     browser.reload();
   },
 
   // Callback for user clicking the help icon
   openHelpPage: function () {
     openHelpLink("plugin-crashed", false);
   },
 
-  showInstallNotification: function (browser, pluginInfo) {
-    let hideMissingPluginsNotification =
-      Services.prefs.getBoolPref(this.PREF_HIDE_MISSING_PLUGINS_NOTIFICATION);
-    if (hideMissingPluginsNotification) {
-      return false;
-    }
-
-    if (!browser.missingPlugins)
-      browser.missingPlugins = new Map();
-
-    browser.missingPlugins.set(pluginInfo.mimetype, pluginInfo);
-
-    // only show notification for small subset of plugins
-    let mimetype = pluginInfo.mimetype.split(";")[0];
-    if (!this.canInstallThisMimeType(mimetype))
-      return false;
-
-    let pluginIdentifier = this.nameForSupportedPlugin(mimetype);
-    if (!pluginIdentifier)
-      return false;
-
-    let displayName = this.supportedPlugins.plugins[pluginIdentifier].displayName;
-
-    // don't show several notifications
-    let notification = PopupNotifications.getNotification("plugins-not-found", browser);
-    if (notification)
-      return true;
-
-    let messageString = gNavigatorBundle.getString("installPlugin.message");
-    let mainAction = {
-      label: gNavigatorBundle.getFormattedString("installPlugin.button.label",
-                                                 [displayName]),
-      accessKey: gNavigatorBundle.getString("installPlugin.button.accesskey"),
-      callback: function () {
-        openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
-                   "PFSWindow", "chrome,centerscreen,resizable=yes",
-                   {plugins: browser.missingPlugins, browser: browser});
-      }
-    };
-    let secondaryActions = null;
-    let options = { dismissed: true };
-
-    let showForFlash = Services.prefs.getBoolPref(this.PREF_NOTIFY_MISSING_FLASH);
-    if (pluginIdentifier == "flash" && showForFlash) {
-      let prefNotifyMissingFlash = this.PREF_NOTIFY_MISSING_FLASH;
-      secondaryActions = [{
-        label: gNavigatorBundle.getString("installPlugin.ignoreButton.label"),
-        accessKey: gNavigatorBundle.getString("installPlugin.ignoreButton.accesskey"),
-        callback: function () {
-          Services.prefs.setBoolPref(prefNotifyMissingFlash, false);
-        }
-      }];
-      options.dismissed = false;
-    }
-    PopupNotifications.show(browser, "plugins-not-found",
-                            messageString, "plugin-install-notification-icon",
-                            mainAction, secondaryActions, options);
-    return true;
-  },
-
   _clickToPlayNotificationEventCallback: function PH_ctpEventCallback(event) {
     if (event == "showing") {
       Services.telemetry.getHistogramById("PLUGINS_NOTIFICATION_SHOWN")
         .add(!this.options.primaryPlugin);
       // Histograms always start at 0, even though our data starts at 1
       let histogramCount = this.options.pluginData.size - 1;
       if (histogramCount > 4) {
         histogramCount = 4;
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -769,18 +769,16 @@ function gKeywordURIFixup({ target: brow
 var gBrowserInit = {
   delayedStartupFinished: false,
 
   onLoad: function() {
     var mustLoadSidebar = false;
 
     gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver, false);
 
-    gBrowser.addEventListener("NewPluginInstalled", gPluginHandler.newPluginInstalled, true);
-
     Services.obs.addObserver(gPluginHandler.pluginCrashed, "plugin-crashed", false);
 
     window.addEventListener("AppCommand", HandleAppCommandEvent, true);
 
     // These routines add message listeners. They must run before
     // loading the frame script to ensure that we don't miss any
     // message sent between when the frame script is loaded and when
     // the listener is registered.
@@ -6075,18 +6073,20 @@ function GetSearchFieldBookmarkData(node
       }
     }
   }
 
   var postData;
 
   if (isURLEncoded)
     postData = formData.join("&");
-  else
-    spec += "?" + formData.join("&");
+  else {
+    let separator = spec.contains("?") ? "&" : "?";
+    spec += separator + formData.join("&");
+  }
 
   return {
     spec: spec,
     title: title,
     description: description,
     postData: postData,
     charSet: charset
   };
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -718,17 +718,16 @@
                 <image id="identity-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="geo-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="addons-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="indexedDB-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="password-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="webapps-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="plugins-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="web-notifications-notification-icon" class="notification-anchor-icon" role="button"/>
-                <image id="plugin-install-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="bad-content-blocked-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="bad-content-unblocked-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="webRTC-shareDevices-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="webRTC-sharingDevices-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="webRTC-shareMicrophone-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="webRTC-sharingMicrophone-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="webRTC-shareScreen-notification-icon" class="notification-anchor-icon" role="button"/>
                 <image id="webRTC-sharingScreen-notification-icon" class="notification-anchor-icon" role="button"/>
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -921,17 +921,17 @@ nsContextMenu.prototype = {
   },
 
   // Open clicked-in frame in the same window.
   showOnlyThisFrame: function() {
     var doc = this.target.ownerDocument;
     var frameURL = doc.location.href;
 
     urlSecurityCheck(frameURL,
-                     this._unremotePrincipal(this.browser.contentPrincipal),
+                     this.browser.contentPrincipal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
     var referrer = doc.referrer;
     openUILinkIn(frameURL, "current", { disallowInheritPrincipal: true,
                                         referrerURI: referrer ? makeURI(referrer) : null });
   },
 
   reload: function(event) {
     if (this.onSocial) {
@@ -982,45 +982,45 @@ nsContextMenu.prototype = {
   viewImageInfo: function() {
     BrowserPageInfo(this.target.ownerDocument.defaultView.top.document,
                     "mediaTab", this.target);
   },
 
   viewImageDesc: function(e) {
     var doc = this.target.ownerDocument;
     urlSecurityCheck(this.imageDescURL,
-                     this._unremotePrincipal(this.browser.contentPrincipal),
+                     this.browser.contentPrincipal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
     openUILink(this.imageDescURL, e, { disallowInheritPrincipal: true,
                              referrerURI: doc.documentURIObject });
   },
 
   viewFrameInfo: function() {
     BrowserPageInfo(this.target.ownerDocument);
   },
 
   reloadImage: function(e) {
     urlSecurityCheck(this.mediaURL,
-                     this._unremotePrincipal(this.browser.contentPrincipal),
+                     this.browser.contentPrincipal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
 
     if (this.target instanceof Ci.nsIImageLoadingContent)
       this.target.forceReload();
   },
 
   // Change current window to the URL of the image, video, or audio.
   viewMedia: function(e) {
     var viewURL;
 
     if (this.onCanvas)
       viewURL = this.target.toDataURL();
     else {
       viewURL = this.mediaURL;
       urlSecurityCheck(viewURL,
-                       this._unremotePrincipal(this.browser.contentPrincipal),
+                       this.browser.contentPrincipal,
                        Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
     }
 
     var doc = this.target.ownerDocument;
     openUILink(viewURL, e, { disallowInheritPrincipal: true,
                              referrerURI: doc.documentURIObject });
   },
 
@@ -1053,17 +1053,17 @@ nsContextMenu.prototype = {
 
   leaveDOMFullScreen: function() {
     document.mozCancelFullScreen();
   },
 
   // Change current window to the URL of the background image.
   viewBGImage: function(e) {
     urlSecurityCheck(this.bgImageURL,
-                     this._unremotePrincipal(this.browser.contentPrincipal),
+                     this.browser.contentPrincipal,
                      Ci.nsIScriptSecurityManager.DISALLOW_SCRIPT);
     var doc = this.target.ownerDocument;
     openUILink(this.bgImageURL, e, { disallowInheritPrincipal: true,
                                      referrerURI: doc.documentURIObject });
   },
 
   disableSetDesktopBackground: function() {
     // Disable the Set as Desktop Background menu item if we're still trying
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -757,20 +757,16 @@
                 // failed URI (particularly for SSL errors). However, don't clear the value
                 // if the error page's URI is about:blank, because that causes complete
                 // loss of urlbar contents for invalid URI errors (see bug 867957).
                 if (this.mBrowser.userTypedClear > 0 ||
                     ((aFlags & Ci.nsIWebProgressListener.LOCATION_CHANGE_ERROR_PAGE) &&
                      aLocation.spec != "about:blank"))
                   this.mBrowser.userTypedValue = null;
 
-                // Clear out the missing plugins list since it's related to the
-                // previous location.
-                this.mBrowser.missingPlugins = null;
-
                 if (this.mTabBrowser.isFindBarInitialized(this.mTab)) {
                   let findBar = this.mTabBrowser.getFindBar(this.mTab);
 
                   // Close the Find toolbar if we're in old-style TAF mode
                   if (findBar.findMode != findBar.FIND_NORMAL)
                     findBar.close();
 
                   // fix bug 253793 - turn off highlight when page changes
--- a/browser/base/content/test/general/browser_addKeywordSearch.js
+++ b/browser/base/content/test/general/browser_addKeywordSearch.js
@@ -29,16 +29,18 @@ function test() {
       doc.body.removeChild(form);
     }
 
     let testData = [
     /* baseURI, field name, expected */
       [ 'http://example.com/', 'q', 'http://example.com/?q=%s' ],
       [ 'http://example.com/new-path-here/', 'q', 'http://example.com/new-path-here/?q=%s' ],
       [ '', 'q', 'http://example.org/browser/browser/base/content/test/general/dummy_page.html?q=%s' ],
+      // Tests for proper behaviour when called on a form whose action contains a question mark.
+      [ 'http://example.com/search?oe=utf-8', 'q', 'http://example.com/search?oe=utf-8&q=%s' ],
     ]
 
     for (let data of testData) {
       check(data[0], data[1], data[2]);
     }
 
     // cleanup
     gBrowser.removeCurrentTab();
--- a/browser/base/content/test/plugins/browser_pluginnotification.js
+++ b/browser/base/content/test/plugins/browser_pluginnotification.js
@@ -56,17 +56,16 @@ TabOpenListener.prototype = {
 
 function test() {
   waitForExplicitFinish();
   SimpleTest.requestCompleteLog();
   requestLongerTimeout(2);
   registerCleanupFunction(function() {
     clearAllPluginPermissions();
     Services.prefs.clearUserPref("extensions.blocklist.suppressUI");
-    Services.prefs.clearUserPref("plugins.hideMissingPluginsNotification");
   });
   Services.prefs.setBoolPref("extensions.blocklist.suppressUI", true);
 
   setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY);
   setTestPluginEnabledState(Ci.nsIPluginTag.STATE_CLICKTOPLAY, "Second Test Plug-in");
 
   var newTab = gBrowser.addTab();
   gBrowser.selectedTab = newTab;
@@ -107,58 +106,42 @@ function runAfterPluginBindingAttached(f
     }
     elems[0].clientTop;
     executeSoon(func);
   };
 }
 
 // Tests a page with an unknown plugin in it.
 function test1a() {
-  ok(PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 1a, Should have displayed the missing plugin notification");
-  ok(gTestBrowser.missingPlugins, "Test 1a, Should be a missing plugin list");
-  ok(gTestBrowser.missingPlugins.has("application/x-unknown"), "Test 1a, Should know about application/x-unknown");
-  ok(!gTestBrowser.missingPlugins.has("application/x-test"), "Test 1a, Should not know about application/x-test");
-
   var pluginNode = gTestBrowser.contentDocument.getElementById("unknown");
   ok(pluginNode, "Test 1a, Found plugin in page");
   var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
   is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_UNSUPPORTED, "Test 1a, plugin fallback type should be PLUGIN_UNSUPPORTED");
 
-  Services.prefs.setBoolPref("plugins.hideMissingPluginsNotification", true);
   prepareTest(runAfterPluginBindingAttached(test1b), gTestRoot + "plugin_unknown.html");
 }
 
 
 function test1b() {
-  ok(!PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 1b, Should not have displayed the missing plugin notification");
-  ok(!gTestBrowser.missingPlugins, "Test 1b, Should not be a missing plugin list");
-  Services.prefs.clearUserPref("plugins.hideMissingPluginsNotification");
-
   var plugin = getTestPlugin();
   ok(plugin, "Test 1b, Should have a test plugin");
   plugin.enabledState = Ci.nsIPluginTag.STATE_ENABLED;
   prepareTest(runAfterPluginBindingAttached(test2), gTestRoot + "plugin_test.html");
 }
 
 // Tests a page with a working plugin in it.
 function test2() {
-  ok(!PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 2, Should not have displayed the missing plugin notification");
-  ok(!gTestBrowser.missingPlugins, "Test 2, Should not be a missing plugin list");
-
   var plugin = getTestPlugin();
   ok(plugin, "Should have a test plugin");
   plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
   prepareTest(runAfterPluginBindingAttached(test3), gTestRoot + "plugin_test.html");
 }
 
 // Tests a page with a disabled plugin in it.
 function test3() {
-  ok(!PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 3, Should not have displayed the missing plugin notification");
-  ok(!gTestBrowser.missingPlugins, "Test 3, Should not be a missing plugin list");
-
   new TabOpenListener("about:addons", test4, prepareTest5);
 
   var pluginNode = gTestBrowser.contentDocument.getElementById("test");
   ok(pluginNode, "Test 3, Found plugin in page");
   var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
   is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_DISABLED, "Test 3, plugin fallback type should be PLUGIN_DISABLED");
   var manageLink = gTestBrowser.contentDocument.getAnonymousElementByAttribute(pluginNode, "anonid", "managePluginsLink");
   ok(manageLink, "Test 3, found 'manage' link in plugin-problem binding");
@@ -180,64 +163,50 @@ function prepareTest5() {
       info("prepareTest5 callback");
       prepareTest(runAfterPluginBindingAttached(test5), gTestRoot + "plugin_test.html");
   });
 }
 
 // Tests a page with a blocked plugin in it.
 function test5() {
   info("test5");
-  ok(!PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 5, Should not have displayed the missing plugin notification");
   let notification = PopupNotifications.getNotification("click-to-play-plugins");
   ok(notification, "Test 5: There should be a plugin notification for blocked plugins");
   ok(notification.dismissed, "Test 5: The plugin notification should be dismissed by default");
 
   notification.reshow();
   is(notification.options.pluginData.size, 1, "Test 5: Only the blocked plugin should be present in the notification");
   ok(PopupNotifications.panel.firstChild._buttonContainer.hidden, "Part 5: The blocked plugins notification should not have any buttons visible.");
 
-  ok(!gTestBrowser.missingPlugins, "Test 5, Should not be a missing plugin list");
   var pluginNode = gTestBrowser.contentDocument.getElementById("test");
   ok(pluginNode, "Test 5, Found plugin in page");
   var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
   is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_BLOCKLISTED, "Test 5, plugin fallback type should be PLUGIN_BLOCKLISTED");
 
   prepareTest(runAfterPluginBindingAttached(test6), gTestRoot + "plugin_both.html");
 }
 
 // Tests a page with a blocked and unknown plugin in it.
 function test6() {
-  ok(PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 6, Should have displayed the missing plugin notification");
-  ok(gTestBrowser.missingPlugins, "Test 6, Should be a missing plugin list");
-  ok(gTestBrowser.missingPlugins.has("application/x-unknown"), "Test 6, Should know about application/x-unknown");
-  ok(!gTestBrowser.missingPlugins.has("application/x-test"), "Test 6, application/x-test should not be a missing plugin");
-
   prepareTest(runAfterPluginBindingAttached(test7), gTestRoot + "plugin_both2.html");
 }
 
 // Tests a page with a blocked and unknown plugin in it (alternate order to above).
 function test7() {
-  ok(PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 7, Should have displayed the missing plugin notification");
-  ok(gTestBrowser.missingPlugins, "Test 7, Should be a missing plugin list");
-  ok(gTestBrowser.missingPlugins.has("application/x-unknown"), "Test 7, Should know about application/x-unknown");
-  ok(!gTestBrowser.missingPlugins.has("application/x-test"), "Test 7, application/x-test should not be a missing plugin");
-
   var plugin = getTestPlugin();
   plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
   getTestPlugin("Second Test Plug-in").enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
 
   setAndUpdateBlocklist(gHttpTestRoot + "blockNoPlugins.xml", function() {
     prepareTest(runAfterPluginBindingAttached(test8), gTestRoot + "plugin_test.html");
   });
 }
 
 // Tests a page with a working plugin that is click-to-play
 function test8() {
-  ok(!PopupNotifications.getNotification("plugins-not-found", gTestBrowser), "Test 8, Should not have displayed the missing plugin notification");
-  ok(!gTestBrowser.missingPlugins, "Test 8, Should not be a missing plugin list");
   ok(PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser), "Test 8, Should have a click-to-play notification");
 
   var pluginNode = gTestBrowser.contentDocument.getElementById("test");
   ok(pluginNode, "Test 8, Found plugin in page");
   var objLoadingContent = pluginNode.QueryInterface(Ci.nsIObjectLoadingContent);
   is(objLoadingContent.pluginFallbackType, Ci.nsIObjectLoadingContent.PLUGIN_CLICK_TO_PLAY, "Test 8, plugin fallback type should be PLUGIN_CLICK_TO_PLAY");
 
   prepareTest(runAfterPluginBindingAttached(test11a), gTestRoot + "plugin_test3.html");
@@ -344,18 +313,16 @@ function test15() {
 
 // Test 16 removed
 
 // Tests that mContentType is used for click-to-play plugins, and not the
 // inspected type.
 function test17() {
   var clickToPlayNotification = PopupNotifications.getNotification("click-to-play-plugins", gTestBrowser);
   ok(clickToPlayNotification, "Test 17, Should have a click-to-play notification");
-  var missingNotification = PopupNotifications.getNotification("missing-plugins", gTestBrowser);
-  ok(!missingNotification, "Test 17, Should not have a missing plugin notification");
 
   setAndUpdateBlocklist(gHttpTestRoot + "blockPluginVulnerableUpdatable.xml",
   function() {
     prepareTest(runAfterPluginBindingAttached(test18a), gHttpTestRoot + "plugin_test.html");
   });
 }
 
 // Tests a vulnerable, updatable plugin
--- a/browser/base/content/test/plugins/browser_pluginplaypreview.js
+++ b/browser/base/content/test/plugins/browser_pluginplaypreview.js
@@ -183,17 +183,16 @@ function prepareTest(nextTest, url, skip
   gNextTest = nextTest;
   gNextTestSkip = skip;
   gTestBrowser.contentWindow.location = url;
 }
 
 // Tests a page with normal play preview registration (1/2)
 function test1a() {
   var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
-  ok(!notificationBox.getNotificationWithValue("missing-plugins"), "Test 1a, Should not have displayed the missing plugin notification");
   ok(!notificationBox.getNotificationWithValue("blocked-plugins"), "Test 1a, Should not have displayed the blocked plugin notification");
 
   var pluginInfo = getTestPlugin();
   ok(pluginInfo, "Should have a test plugin");
 
   var doc = gTestBrowser.contentDocument;
   var plugin = doc.getElementById("test");
   var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
--- a/browser/base/content/test/plugins/browser_pluginplaypreview2.js
+++ b/browser/base/content/test/plugins/browser_pluginplaypreview2.js
@@ -85,17 +85,16 @@ function prepareTest(nextTest, url, skip
   gNextTest = nextTest;
   gNextTestSkip = skip;
   gTestBrowser.contentWindow.location = url;
 }
 
 // Tests a page with normal play preview registration (1/2)
 function test1a() {
   var notificationBox = gBrowser.getNotificationBox(gTestBrowser);
-  ok(!notificationBox.getNotificationWithValue("missing-plugins"), "Test 1a, Should not have displayed the missing plugin notification");
   ok(!notificationBox.getNotificationWithValue("blocked-plugins"), "Test 1a, Should not have displayed the blocked plugin notification");
 
   var pluginInfo = getTestPlugin();
   ok(pluginInfo, "Should have a test plugin");
 
   var doc = gTestBrowser.contentDocument;
   var plugin = doc.getElementById("test");
   var objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
--- a/browser/components/loop/LoopStorage.jsm
+++ b/browser/components/loop/LoopStorage.jsm
@@ -1,16 +1,25 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
-Cu.importGlobalProperties(["indexedDB"]);
+// Make it possible to load LoopStorage.jsm in xpcshell tests
+try {
+  Cu.importGlobalProperties(["indexedDB"]);
+} catch (ex) {
+  // don't write this is out in xpcshell, since it's expected there
+  if (typeof window !== 'undefined' && "console" in window) {
+    console.log("Failed to import indexedDB; if this isn't a unit test," +
+                " something is wrong", ex);
+  }
+}
 
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 XPCOMUtils.defineLazyGetter(this, "eventEmitter", function() {
   const {EventEmitter} = Cu.import("resource://gre/modules/devtools/event-emitter.js", {});
   return new EventEmitter();
 });
 
--- a/browser/components/loop/MozLoopAPI.jsm
+++ b/browser/components/loop/MozLoopAPI.jsm
@@ -110,16 +110,18 @@ function injectLoopAPI(targetWindow) {
   let ringerStopper;
   let appVersionInfo;
   let contactsAPI;
 
   let api = {
     /**
      * Gets an object with data that represents the currently
      * authenticated user's identity.
+     *
+     * @return null if user not logged in; profile object otherwise
      */
     userProfile: {
       enumerable: true,
       get: function() {
         if (!MozLoopService.userProfile)
           return null;
         let userProfile = Cu.cloneInto({
           email: MozLoopService.userProfile.email,
@@ -373,29 +375,31 @@ function injectLoopAPI(targetWindow) {
      *    {
      *      code: 401,
      *      errno: 401,
      *      error: "Request failed",
      *      message: "invalid token"
      *    }
      *  - {String} The body of the response.
      *
+     * @param {LOOP_SESSION_TYPE} sessionType The type of session to use for
+     *                                        the request.  This is one of the
+     *                                        LOOP_SESSION_TYPE members
      * @param {String} path The path to make the request to.
      * @param {String} method The request method, e.g. 'POST', 'GET'.
      * @param {Object} payloadObj An object which is converted to JSON and
      *                            transmitted with the request.
      * @param {Function} callback Called when the request completes.
      */
     hawkRequest: {
       enumerable: true,
       writable: true,
-      value: function(path, method, payloadObj, callback) {
-        // XXX: Bug 1065153 - Should take a sessionType parameter instead of hard-coding GUEST
+      value: function(sessionType, path, method, payloadObj, callback) {
         // XXX Should really return a DOM promise here.
-        MozLoopService.hawkRequest(LOOP_SESSION_TYPE.GUEST, path, method, payloadObj).then((response) => {
+        MozLoopService.hawkRequest(sessionType, path, method, payloadObj).then((response) => {
           callback(null, response.body);
         }, hawkError => {
           // The hawkError.error property, while usually a string representing
           // an HTTP response status message, may also incorrectly be a native
           // error object that will cause the cloning function to fail.
           callback(Cu.cloneInto({
             error: (hawkError.error && typeof hawkError.error == "string")
                    ? hawkError.error : "Unexpected exception",
@@ -404,30 +408,37 @@ function injectLoopAPI(targetWindow) {
             errno: hawkError.errno,
           }, targetWindow));
         }).catch(Cu.reportError);
       }
     },
 
     LOOP_SESSION_TYPE: {
       enumerable: true,
-      writable: false,
-      value: function() {
-        return LOOP_SESSION_TYPE;
-      },
+      get: function() {
+        return Cu.cloneInto(LOOP_SESSION_TYPE, targetWindow);
+      }
     },
 
     logInToFxA: {
       enumerable: true,
       writable: true,
       value: function() {
         return MozLoopService.logInToFxA();
       }
     },
 
+    logOutFromFxA: {
+      enumerable: true,
+      writable: true,
+      value: function() {
+        return MozLoopService.logOutFromFxA();
+      }
+    },
+
     /**
      * Copies passed string onto the system clipboard.
      *
      * @param {String} str The string to copy
      */
     copyString: {
       enumerable: true,
       writable: true,
@@ -445,21 +456,31 @@ function injectLoopAPI(targetWindow) {
      *   - OS: The operating system the application is running on
      */
     appVersionInfo: {
       enumerable: true,
       get: function() {
         if (!appVersionInfo) {
           let defaults = Services.prefs.getDefaultBranch(null);
 
-          appVersionInfo = Cu.cloneInto({
-            channel: defaults.getCharPref("app.update.channel"),
-            version: appInfo.version,
-            OS: appInfo.OS
-          }, targetWindow);
+          // If the lazy getter explodes, we're probably loaded in xpcshell,
+          // which doesn't have what we need, so log an error.
+          try {
+            appVersionInfo = Cu.cloneInto({
+              channel: defaults.getCharPref("app.update.channel"),
+              version: appInfo.version,
+              OS: appInfo.OS
+            }, targetWindow);
+          } catch (ex) {
+            // only log outside of xpcshell to avoid extra message noise
+            if (typeof window !== 'undefined' && "console" in window) {
+              console.log("Failed to construct appVersionInfo; if this isn't " +
+                          "an xpcshell unit test, something is wrong", ex);
+            }
+          }
         }
         return appVersionInfo;
       }
     },
 
     /**
      * Composes an email via the external protocol service.
      *
@@ -505,27 +526,33 @@ function injectLoopAPI(targetWindow) {
 
   let contentObj = Cu.createObjectIn(targetWindow);
   Object.defineProperties(contentObj, api);
   Object.seal(contentObj);
   Cu.makeObjectPropsNormal(contentObj);
   Services.obs.addObserver(onStatusChanged, "loop-status-changed", false);
   Services.obs.addObserver(onDOMWindowDestroyed, "dom-window-destroyed", false);
 
-  targetWindow.navigator.wrappedJSObject.__defineGetter__("mozLoop", function() {
-    // We do this in a getter, so that we create these objects
-    // only on demand (this is a potential concern, since
-    // otherwise we might add one per iframe, and keep them
-    // alive for as long as the window is alive).
-    delete targetWindow.navigator.wrappedJSObject.mozLoop;
-    return targetWindow.navigator.wrappedJSObject.mozLoop = contentObj;
-  });
+  if ("navigator" in targetWindow) {
+    targetWindow.navigator.wrappedJSObject.__defineGetter__("mozLoop", function () {
+      // We do this in a getter, so that we create these objects
+      // only on demand (this is a potential concern, since
+      // otherwise we might add one per iframe, and keep them
+      // alive for as long as the window is alive).
+      delete targetWindow.navigator.wrappedJSObject.mozLoop;
+      return targetWindow.navigator.wrappedJSObject.mozLoop = contentObj;
+    });
 
-  // Handle window.close correctly on the panel and chatbox.
-  hookWindowCloseForPanelClose(targetWindow);
+    // Handle window.close correctly on the panel and chatbox.
+    hookWindowCloseForPanelClose(targetWindow);
+  } else {
+    // This isn't a window; but it should be a JS scope; used for testing
+    return targetWindow.mozLoop = contentObj;
+  }
+
 }
 
 function getChromeWindow(contentWin) {
   return contentWin.QueryInterface(Ci.nsIInterfaceRequestor)
                    .getInterface(Ci.nsIWebNavigation)
                    .QueryInterface(Ci.nsIDocShellTreeItem)
                    .rootTreeItem
                    .QueryInterface(Ci.nsIInterfaceRequestor)
--- a/browser/components/loop/MozLoopService.jsm
+++ b/browser/components/loop/MozLoopService.jsm
@@ -68,17 +68,16 @@ XPCOMUtils.defineLazyServiceGetter(this,
 
 
 // The current deferred for the registration process. This is set if in progress
 // or the registration was successful. This is null if a registration attempt was
 // unsuccessful.
 let gRegisteredDeferred = null;
 let gPushHandler = null;
 let gHawkClient = null;
-let gRegisteredLoopServer = false;
 let gLocalizedStrings =  null;
 let gInitializeTimer = null;
 let gFxAOAuthClientPromise = null;
 let gFxAOAuthClient = null;
 let gFxAOAuthTokenData = null;
 let gFxAOAuthProfile = null;
 let gErrors = new Map();
 
@@ -155,18 +154,18 @@ let MozLoopServiceInternal = {
    *
    * @param {Boolean} aFlag
    */
   set doNotDisturb(aFlag) {
     Services.prefs.setBoolPref("loop.do_not_disturb", Boolean(aFlag));
     this.notifyStatusChanged();
   },
 
-  notifyStatusChanged: function() {
-    Services.obs.notifyObservers(null, "loop-status-changed", null);
+  notifyStatusChanged: function(aReason = null) {
+    Services.obs.notifyObservers(null, "loop-status-changed", aReason);
   },
 
   /**
    * @param {String} errorType a key to identify the type of error. Only one
    *                           error of a type will be saved at a time.
    * @param {Object} error     an object describing the error in the format from Hawk errors
    */
   setError: function(errorType, error) {
@@ -287,16 +286,30 @@ let MozLoopServiceInternal = {
         gRegisteredDeferred.reject("session-token-wrong-size");
         gRegisteredDeferred = null;
         return false;
       }
     }
     return true;
   },
 
+
+  /**
+   * Clear the loop session token so we don't use it for Hawk Requests anymore.
+   *
+   * This should normally be used after unregistering with the server so it can
+   * clean up session state first.
+   *
+   * @param {LOOP_SESSION_TYPE} sessionType The type of session to use for the request.
+   *                                        One of the LOOP_SESSION_TYPE members.
+   */
+  clearSessionToken: function(sessionType) {
+    Services.prefs.clearUserPref(this.getSessionTokenPrefName(sessionType));
+  },
+
   /**
    * Callback from MozLoopPushHandler - The push server has been registered
    * and has given us a push url.
    *
    * @param {String} pushUrl The push url given by the push server.
    */
   onPushRegistered: function(err, pushUrl) {
     if (err) {
@@ -309,17 +322,17 @@ let MozLoopServiceInternal = {
       // storeSessionToken could have rejected and nulled the promise if the token was malformed.
       if (!gRegisteredDeferred) {
         return;
       }
       gRegisteredDeferred.resolve();
       // No need to clear the promise here, everything was good, so we don't need
       // to re-register.
     }, (error) => {
-      Cu.reportError("Failed to register with Loop server: " + error.errno);
+      console.error("Failed to register with Loop server: ", error);
       gRegisteredDeferred.reject(error.errno);
       gRegisteredDeferred = null;
     });
   },
 
   /**
    * Registers with the Loop server either as a guest or a FxA user.
    *
@@ -344,31 +357,61 @@ let MozLoopServiceInternal = {
         if (error.code === 401 && error.errno === INVALID_AUTH_TOKEN) {
           if (this.urlExpiryTimeIsInFuture()) {
             // XXX Should this be reported to the user is a visible manner?
             Cu.reportError("Loop session token is invalid, all previously "
                            + "generated urls will no longer work.");
           }
 
           // Authorization failed, invalid token, we need to try again with a new token.
-          Services.prefs.clearUserPref(this.getSessionTokenPrefName(sessionType));
+          this.clearSessionToken(sessionType);
           if (retry) {
             return this.registerWithLoopServer(sessionType, pushUrl, false);
           }
         }
 
         // XXX Bubble the precise details up to the UI somehow (bug 1013248).
-        Cu.reportError("Failed to register with the loop server. error: " + error);
+        console.error("Failed to register with the loop server. Error: ", error);
         this.setError("registration", error);
         throw error;
       }
     );
   },
 
   /**
+   * Unregisters from the Loop server either as a guest or a FxA user.
+   *
+   * This is normally only wanted for FxA users as we normally want to keep the
+   * guest session with the device.
+   *
+   * @param {LOOP_SESSION_TYPE} sessionType The type of session e.g. guest or FxA
+   * @param {String} pushURL The push URL previously given by the push server.
+   *                         This may not be necessary to unregister in the future.
+   * @return {Promise} resolving when the unregistration request finishes
+   */
+  unregisterFromLoopServer: function(sessionType, pushURL) {
+    let unregisterURL = "/registration?simplePushURL=" + encodeURIComponent(pushURL);
+    return this.hawkRequest(sessionType, unregisterURL, "DELETE")
+      .then(() => {
+        MozLoopServiceInternal.clearSessionToken(sessionType);
+      },
+      error => {
+        // Always clear the registration token regardless of whether the server acknowledges the logout.
+        MozLoopServiceInternal.clearSessionToken(sessionType);
+        if (error.code === 401 && error.errno === INVALID_AUTH_TOKEN) {
+          // Authorization failed, invalid token. This is fine since it may mean we already logged out.
+          return;
+        }
+
+        console.error("Failed to unregister with the loop server. Error: ", error);
+        throw error;
+      });
+  },
+
+  /**
    * Callback from MozLoopPushHandler - A push notification has been received from
    * the server.
    *
    * @param {String} version The version information from the server.
    */
   onHandleNotification: function(version) {
     if (this.doNotDisturb) {
       return;
@@ -698,16 +741,21 @@ this.MozLoopService = {
     gInitializeTimerFunc = value;
   },
 
   /**
    * Initialized the loop service, and starts registration with the
    * push and loop servers.
    */
   initialize: function() {
+
+    // Do this here, rather than immediately after definition, so that we can
+    // stub out API functions for unit testing
+    Object.freeze(this);
+
     // Don't do anything if loop is not enabled.
     if (!Services.prefs.getBoolPref("loop.enabled") ||
         Services.prefs.getBoolPref("loop.throttled")) {
       return;
     }
 
     // If expiresTime is in the future then kick-off registration.
     if (MozLoopServiceInternal.urlExpiryTimeIsInFuture()) {
@@ -1014,31 +1062,55 @@ this.MozLoopService = {
       }));
     }).then(tokenData => {
       let client = new FxAccountsProfileClient({
         serverURL: gFxAOAuthClient.parameters.profile_uri,
         token: tokenData.access_token
       });
       client.fetchProfile().then(result => {
         gFxAOAuthProfile = result;
-        MozLoopServiceInternal.notifyStatusChanged();
+        MozLoopServiceInternal.notifyStatusChanged("login");
       }, error => {
         console.error("Failed to retrieve profile", error);
         gFxAOAuthProfile = null;
         MozLoopServiceInternal.notifyStatusChanged();
       });
       return tokenData;
     }).catch(error => {
       gFxAOAuthTokenData = null;
       gFxAOAuthProfile = null;
       throw error;
     });
   },
 
   /**
+   * Logs the user out from FxA.
+   *
+   * Gracefully handles if the user is already logged out.
+   *
+   * @return {Promise} that resolves when the FxA logout flow is complete.
+   */
+  logOutFromFxA: Task.async(function*() {
+    yield MozLoopServiceInternal.unregisterFromLoopServer(LOOP_SESSION_TYPE.FXA,
+                                                          gPushHandler.pushUrl);
+
+    gFxAOAuthTokenData = null;
+    gFxAOAuthProfile = null;
+
+    // Reset the client since the initial promiseFxAOAuthParameters() call is
+    // what creates a new session.
+    gFxAOAuthClient = null;
+    gFxAOAuthClientPromise = null;
+
+    // clearError calls notifyStatusChanged so should be done last when the
+    // state is clean.
+    MozLoopServiceInternal.clearError("registration");
+  }),
+
+  /**
    * Performs a hawk based request to the loop server.
    *
    * @param {LOOP_SESSION_TYPE} sessionType The type of session to use for the request.
    *                                        One of the LOOP_SESSION_TYPE members.
    * @param {String} path The path to make the request to.
    * @param {String} method The request method, e.g. 'POST', 'GET'.
    * @param {Object} payloadObj An object which is converted to JSON and
    *                            transmitted with the request.
@@ -1047,9 +1119,8 @@ this.MozLoopService = {
    *        or is rejected with an error.  If the server response can be parsed
    *        as JSON and contains an 'error' property, the promise will be
    *        rejected with this JSON-parsed response.
    */
   hawkRequest: function(sessionType, path, method, payloadObj) {
     return MozLoopServiceInternal.hawkRequest(sessionType, path, method, payloadObj);
   },
 };
-Object.freeze(this.MozLoopService);
--- a/browser/components/loop/content/js/client.js
+++ b/browser/components/loop/content/js/client.js
@@ -103,39 +103,47 @@ loop.Client = (function($) {
      * - callUrlData an object of the obtained call url data if successful:
      * -- callUrl: The url of the call
      * -- expiresAt: The amount of hours until expiry of the url
      *
      * @param  {string} nickname the nickname of the future caller
      * @param  {Function} cb Callback(err, callUrlData)
      */
     _requestCallUrlInternal: function(nickname, cb) {
-      this.mozLoop.hawkRequest("/call-url/", "POST", {callerId: nickname},
-                               function (error, responseText) {
-        if (error) {
-          this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", false);
-          this._failureHandler(cb, error);
-          return;
-        }
-
-        try {
-          var urlData = JSON.parse(responseText);
+      var sessionType;
+      if (this.mozLoop.userProfile) {
+        sessionType = this.mozLoop.LOOP_SESSION_TYPE.FXA;
+      } else {
+        sessionType = this.mozLoop.LOOP_SESSION_TYPE.GUEST;
+      }
+      
+      this.mozLoop.hawkRequest(sessionType, "/call-url/", "POST",
+                               {callerId: nickname},
+        function (error, responseText) {
+          if (error) {
+            this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", false);
+            this._failureHandler(cb, error);
+            return;
+          }
 
-          // This throws if the data is invalid, in which case only the failure
-          // telementry will be recorded.
-          var returnData = this._validate(urlData, expectedCallUrlProperties);
+          try {
+            var urlData = JSON.parse(responseText);
+
+            // This throws if the data is invalid, in which case only the failure
+            // telemetry will be recorded.
+            var returnData = this._validate(urlData, expectedCallUrlProperties);
 
-          this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", true);
-          cb(null, returnData);
-        } catch (err) {
-          this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", false);
-          console.log("Error requesting call info", err);
-          cb(err);
-        }
-      }.bind(this));
+            this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", true);
+            cb(null, returnData);
+          } catch (err) {
+            this._telemetryAdd("LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS", false);
+            console.log("Error requesting call info", err);
+            cb(err);
+          }
+        }.bind(this));
     },
 
     /**
      * Block call URL based on the token identifier
      *
      * @param {string} token Conversation identifier used to block the URL
      * @param {function} cb Callback function used for handling an error
      *                      response. XXX The incoming call panel does not
@@ -149,35 +157,41 @@ loop.Client = (function($) {
           return;
         }
 
         this._deleteCallUrlInternal(token, cb);
       }.bind(this));
     },
 
     _deleteCallUrlInternal: function(token, cb) {
-      this.mozLoop.hawkRequest("/call-url/" + token, "DELETE", null,
-                               function (error, responseText) {
+      function deleteRequestCallback(error, responseText) {
         if (error) {
           this._failureHandler(cb, error);
           return;
         }
 
         try {
           cb(null);
         } catch (err) {
           console.log("Error deleting call info", err);
           cb(err);
         }
-      }.bind(this));
+      }
+
+      // XXX hard-coding of GUEST to be removed by 1065155
+      this.mozLoop.hawkRequest(this.mozLoop.LOOP_SESSION_TYPE.GUEST,
+                               "/call-url/" + token, "DELETE", null,
+                               deleteRequestCallback.bind(this));
     },
 
     /**
      * Requests a call URL from the Loop server. It will note the
-     * expiry time for the url with the mozLoop api.
+     * expiry time for the url with the mozLoop api.  It will select the
+     * appropriate hawk session to use based on whether or not the user
+     * is currently logged into a Firefox account profile.
      *
      * Callback parameters:
      * - err null on successful registration, non-null otherwise.
      * - callUrlData an object of the obtained call url data if successful:
      * -- callUrl: The url of the call
      * -- expiresAt: The amount of hours until expiry of the url
      *
      * @param  {String} simplepushUrl a registered Simple Push URL
--- a/browser/components/loop/content/js/panel.js
+++ b/browser/components/loop/content/js/panel.js
@@ -216,17 +216,16 @@ loop.panel = (function(_, mozL10n) {
     },
 
     handleClickAccountEntry: function() {
       // XXX to be implemented
     },
 
     handleClickAuthEntry: function() {
       if (this._isSignedIn()) {
-        // XXX to be implemented - bug 979845
         navigator.mozLoop.logOutFromFxA();
       } else {
         navigator.mozLoop.logInToFxA();
       }
     },
 
     _isSignedIn: function() {
       return !!navigator.mozLoop.userProfile;
--- a/browser/components/loop/content/js/panel.jsx
+++ b/browser/components/loop/content/js/panel.jsx
@@ -216,17 +216,16 @@ loop.panel = (function(_, mozL10n) {
     },
 
     handleClickAccountEntry: function() {
       // XXX to be implemented
     },
 
     handleClickAuthEntry: function() {
       if (this._isSignedIn()) {
-        // XXX to be implemented - bug 979845
         navigator.mozLoop.logOutFromFxA();
       } else {
         navigator.mozLoop.logInToFxA();
       }
     },
 
     _isSignedIn: function() {
       return !!navigator.mozLoop.userProfile;
--- a/browser/components/loop/test/desktop-local/client_test.js
+++ b/browser/components/loop/test/desktop-local/client_test.js
@@ -30,17 +30,22 @@ describe("loop.Client", function() {
     mozLoop = {
       getLoopCharPref: sandbox.stub()
         .returns(null)
         .withArgs("hawk-session-token")
         .returns(fakeToken),
       ensureRegistered: sinon.stub().callsArgWith(0, null),
       noteCallUrlExpiry: sinon.spy(),
       hawkRequest: sinon.stub(),
-      telemetryAdd: sinon.spy(),
+      LOOP_SESSION_TYPE: {
+        GUEST: 1,
+        FXA: 2
+      },
+      userProfile: null,
+      telemetryAdd: sinon.spy()
     };
     // Alias for clearer tests.
     hawkRequestStub = mozLoop.hawkRequest;
     client = new loop.Client({
       mozLoop: mozLoop
     });
   });
 
@@ -65,35 +70,36 @@ describe("loop.Client", function() {
         sinon.assert.calledWithExactly(callback, "offline");
       });
 
       it("should make a delete call to /call-url/{fakeToken}", function() {
         client.deleteCallUrl(fakeToken, callback);
 
         sinon.assert.calledOnce(hawkRequestStub);
         sinon.assert.calledWith(hawkRequestStub,
+                                mozLoop.LOOP_SESSION_TYPE.GUEST,
                                 "/call-url/" + fakeToken, "DELETE");
       });
 
       it("should call the callback with null when the request succeeds",
          function() {
 
            // Sets up the hawkRequest stub to trigger the callback with no error
            // and the url.
-           hawkRequestStub.callsArgWith(3, null);
+           hawkRequestStub.callsArgWith(4, null);
 
            client.deleteCallUrl(fakeToken, callback);
 
            sinon.assert.calledWithExactly(callback, null);
          });
 
       it("should send an error when the request fails", function() {
         // Sets up the hawkRequest stub to trigger the callback with
         // an error
-        hawkRequestStub.callsArgWith(3, fakeErrorRes);
+        hawkRequestStub.callsArgWith(4, fakeErrorRes);
 
         client.deleteCallUrl(fakeToken, callback);
 
         sinon.assert.calledOnce(callback);
         sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
           return /400.*invalid token/.test(err.message);
         }));
       });
@@ -114,64 +120,86 @@ describe("loop.Client", function() {
         sinon.assert.calledOnce(callback);
         sinon.assert.calledWithExactly(callback, "offline");
       });
 
       it("should post to /call-url/", function() {
         client.requestCallUrl("foo", callback);
 
         sinon.assert.calledOnce(hawkRequestStub);
-        sinon.assert.calledWith(hawkRequestStub,
-                                "/call-url/", "POST", {callerId: "foo"});
+        sinon.assert.calledWithExactly(hawkRequestStub, sinon.match.number,
+          "/call-url/", "POST", {callerId: "foo"}, sinon.match.func);
+      });
+
+      it("should send a sessionType of LOOP_SESSION_TYPE.GUEST when " +
+         "mozLoop.userProfile returns null", function() {
+        mozLoop.userProfile = null;
+
+        client.requestCallUrl("foo", callback);
+
+        sinon.assert.calledOnce(hawkRequestStub);
+        sinon.assert.calledWithExactly(hawkRequestStub,
+          mozLoop.LOOP_SESSION_TYPE.GUEST, "/call-url/", "POST",
+          {callerId: "foo"}, sinon.match.func);
+      });
+
+      it("should send a sessionType of LOOP_SESSION_TYPE.FXA when " +
+         "mozLoop.userProfile returns an object", function () {
+        mozLoop.userProfile = {};
+
+        client.requestCallUrl("foo", callback);
+
+        sinon.assert.calledOnce(hawkRequestStub);
+        sinon.assert.calledWithExactly(hawkRequestStub,
+          mozLoop.LOOP_SESSION_TYPE.FXA, "/call-url/", "POST",
+          {callerId: "foo"}, sinon.match.func);
       });
 
       it("should call the callback with the url when the request succeeds",
         function() {
           var callUrlData = {
             "callUrl": "fakeCallUrl",
             "expiresAt": 60
           };
 
           // Sets up the hawkRequest stub to trigger the callback with no error
           // and the url.
-          hawkRequestStub.callsArgWith(3, null,
-            JSON.stringify(callUrlData));
+          hawkRequestStub.callsArgWith(4, null, JSON.stringify(callUrlData));
 
           client.requestCallUrl("foo", callback);
 
           sinon.assert.calledWithExactly(callback, null, callUrlData);
         });
 
       it("should not update call url expiry when the request succeeds",
         function() {
           var callUrlData = {
             "callUrl": "fakeCallUrl",
             "expiresAt": 6000
           };
 
           // Sets up the hawkRequest stub to trigger the callback with no error
           // and the url.
-          hawkRequestStub.callsArgWith(3, null,
-            JSON.stringify(callUrlData));
+          hawkRequestStub.callsArgWith(4, null, JSON.stringify(callUrlData));
 
           client.requestCallUrl("foo", callback);
 
           sinon.assert.notCalled(mozLoop.noteCallUrlExpiry);
         });
 
       it("should call mozLoop.telemetryAdd when the request succeeds",
         function(done) {
           var callUrlData = {
             "callUrl": "fakeCallUrl",
             "expiresAt": 60
           };
 
           // Sets up the hawkRequest stub to trigger the callback with no error
           // and the url.
-          hawkRequestStub.callsArgWith(3, null,
+          hawkRequestStub.callsArgWith(4, null,
             JSON.stringify(callUrlData));
 
           client.requestCallUrl("foo", function(err) {
             expect(err).to.be.null;
 
             sinon.assert.calledOnce(mozLoop.telemetryAdd);
             sinon.assert.calledWith(mozLoop.telemetryAdd,
                                     "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS",
@@ -179,44 +207,44 @@ describe("loop.Client", function() {
 
             done();
           });
         });
 
       it("should send an error when the request fails", function() {
         // Sets up the hawkRequest stub to trigger the callback with
         // an error
-        hawkRequestStub.callsArgWith(3, fakeErrorRes);
+        hawkRequestStub.callsArgWith(4, fakeErrorRes);
 
         client.requestCallUrl("foo", callback);
 
         sinon.assert.calledOnce(callback);
         sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
           return /400.*invalid token/.test(err.message);
         }));
       });
 
       it("should send an error if the data is not valid", function() {
         // Sets up the hawkRequest stub to trigger the callback with
         // an error
-        hawkRequestStub.callsArgWith(3, null, "{}");
+        hawkRequestStub.callsArgWith(4, null, "{}");
 
         client.requestCallUrl("foo", callback);
 
         sinon.assert.calledOnce(callback);
         sinon.assert.calledWithMatch(callback, sinon.match(function(err) {
           return /Invalid data received/.test(err.message);
         }));
       });
 
       it("should call mozLoop.telemetryAdd when the request fails",
         function(done) {
           // Sets up the hawkRequest stub to trigger the callback with
           // an error
-          hawkRequestStub.callsArgWith(3, fakeErrorRes);
+          hawkRequestStub.callsArgWith(4, fakeErrorRes);
 
           client.requestCallUrl("foo", function(err) {
             expect(err).not.to.be.null;
 
             sinon.assert.calledOnce(mozLoop.telemetryAdd);
             sinon.assert.calledWith(mozLoop.telemetryAdd,
                                     "LOOP_CLIENT_CALL_URL_REQUESTS_SUCCESS",
                                     false);
--- a/browser/components/loop/test/mochitest/browser_fxa_login.js
+++ b/browser/components/loop/test/mochitest/browser_fxa_login.js
@@ -214,40 +214,66 @@ add_task(function* basicAuthorizationAnd
     oauth_uri: BASE_URL + "/oauth",
     profile_uri: BASE_URL + "/profile",
     state: "state",
   };
   yield promiseOAuthParamsSetup(BASE_URL, params);
 
   info("registering");
   mockPushHandler.pushUrl = "https://localhost/pushUrl/guest";
+  // Notification observed due to the error being cleared upon successful registration.
+  let statusChangedPromise = promiseObserverNotified("loop-status-changed");
   yield MozLoopService.register(mockPushHandler);
+  yield statusChangedPromise;
 
   // Normally the same pushUrl would be registered but we change it in the test
   // to be able to check for success on the second registration.
   mockPushHandler.pushUrl = "https://localhost/pushUrl/fxa";
 
+  statusChangedPromise = promiseObserverNotified("loop-status-changed");
   yield loadLoopPanel({loopURL: BASE_URL, stayOnline: true});
+  yield statusChangedPromise;
   let loopDoc = document.getElementById("loop").contentDocument;
   let visibleEmail = loopDoc.getElementsByClassName("user-identity")[0];
   is(visibleEmail.textContent, "Guest", "Guest should be displayed on the panel when not logged in");
   is(MozLoopService.userProfile, null, "profile should be null before log-in");
+  let loopButton = document.getElementById("loop-call-button");
+  is(loopButton.getAttribute("state"), "", "state of loop button should be empty when not logged in");
 
   let tokenData = yield MozLoopService.logInToFxA();
-  yield promiseObserverNotified("loop-status-changed");
+  yield promiseObserverNotified("loop-status-changed", "login");
   ise(tokenData.access_token, "code1_access_token", "Check access_token");
   ise(tokenData.scope, "profile", "Check scope");
   ise(tokenData.token_type, "bearer", "Check token_type");
 
   is(MozLoopService.userProfile.email, "test@example.com", "email should exist in the profile data");
   is(MozLoopService.userProfile.uid, "1234abcd", "uid should exist in the profile data");
   is(visibleEmail.textContent, "test@example.com", "the email should be correct on the panel");
+  is(loopButton.getAttribute("state"), "active", "state of loop button should be active when logged in");
 
   let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
-  ise(registrationResponse.response.simplePushURL, "https://localhost/pushUrl/fxa", "Check registered push URL");
+  ise(registrationResponse.response.simplePushURL, "https://localhost/pushUrl/fxa",
+      "Check registered push URL");
+
+  let loopPanel = document.getElementById("loop-notification-panel");
+  loopPanel.hidePopup();
+  statusChangedPromise = promiseObserverNotified("loop-status-changed");
+  yield loadLoopPanel({loopURL: BASE_URL, stayOnline: true});
+  yield statusChangedPromise;
+  is(loopButton.getAttribute("state"), "", "state of loop button should return to empty after panel is opened");
+  loopPanel.hidePopup();
+
+  info("logout");
+  yield MozLoopService.logOutFromFxA();
+  checkLoggedOutState();
+  registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
+  ise(registrationResponse.response, null,
+      "Check registration was deleted on the server");
+  is(visibleEmail.textContent, "Guest", "Guest should be displayed on the panel again after logout");
+  is(MozLoopService.userProfile, null, "userProfile should be null after logout");
 });
 
 add_task(function* loginWithParams401() {
   resetFxA();
   let params = {
     client_id: "client_id",
     content_uri: BASE_URL + "/content",
     oauth_uri: BASE_URL + "/oauth",
@@ -263,16 +289,62 @@ add_task(function* loginWithParams401() 
     ok(false, "Promise should have rejected");
   },
   error => {
     ise(error.code, 401, "Check error code");
     ise(gFxAOAuthTokenData, null, "Check there is no saved token data");
   });
 });
 
+add_task(function* logoutWithIncorrectPushURL() {
+  resetFxA();
+  let pushURL = "http://www.example.com/";
+  mockPushHandler.pushUrl = pushURL;
+
+  // Create a fake FxA hawk session token
+  const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
+  Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
+
+  yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, pushURL);
+  let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
+  ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL");
+  mockPushHandler.pushUrl = "http://www.example.com/invalid";
+  let caught = false;
+  yield MozLoopService.logOutFromFxA().catch((error) => {
+    caught = true;
+  });
+  ok(caught, "Should have caught an error logging out with a mismatched push URL");
+  checkLoggedOutState();
+  registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
+  ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL wasn't deleted");
+});
+
+add_task(function* logoutWithNoPushURL() {
+  resetFxA();
+  let pushURL = "http://www.example.com/";
+  mockPushHandler.pushUrl = pushURL;
+
+  // Create a fake FxA hawk session token
+  const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
+  Services.prefs.setCharPref(fxASessionPref, "X".repeat(HAWK_TOKEN_LENGTH));
+
+  yield MozLoopServiceInternal.registerWithLoopServer(LOOP_SESSION_TYPE.FXA, pushURL);
+  let registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
+  ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL");
+  mockPushHandler.pushUrl = null;
+  let caught = false;
+  yield MozLoopService.logOutFromFxA().catch((error) => {
+    caught = true;
+  });
+  ok(caught, "Should have caught an error logging out without a push URL");
+  checkLoggedOutState();
+  registrationResponse = yield promiseOAuthGetRegistration(BASE_URL);
+  ise(registrationResponse.response.simplePushURL, pushURL, "Check registered push URL wasn't deleted");
+});
+
 add_task(function* loginWithRegistration401() {
   resetFxA();
   let params = {
     client_id: "client_id",
     content_uri: BASE_URL + "/content",
     oauth_uri: BASE_URL + "/oauth",
     profile_uri: BASE_URL + "/profile",
     state: "state",
--- a/browser/components/loop/test/mochitest/browser_toolbarbutton.js
+++ b/browser/components/loop/test/mochitest/browser_toolbarbutton.js
@@ -4,24 +4,74 @@
 /**
  * Test the toolbar button states.
  */
 
 "use strict";
 
 registerCleanupFunction(function*() {
   MozLoopService.doNotDisturb = false;
+  setInternalLoopGlobal("gFxAOAuthProfile", null);
   yield MozLoopServiceInternal.clearError("testing");
 });
 
 add_task(function* test_doNotDisturb() {
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
   yield MozLoopService.doNotDisturb = true;
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state");
   yield MozLoopService.doNotDisturb = false;
   Assert.notStrictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is not in disabled state");
 });
 
+add_task(function* test_doNotDisturb_with_login() {
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  yield MozLoopService.doNotDisturb = true;
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state");
+  setInternalLoopGlobal("gFxAOAuthProfile", {email: "test@example.com", uid: "abcd1234"});
+  yield MozLoopServiceInternal.notifyStatusChanged("login");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
+  yield loadLoopPanel();
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "disabled", "Check button is in disabled state after opening panel");
+  let loopPanel = document.getElementById("loop-notification-panel");
+  loopPanel.hidePopup();
+  yield MozLoopService.doNotDisturb = false;
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  setInternalLoopGlobal("gFxAOAuthProfile", null);
+  yield MozLoopServiceInternal.notifyStatusChanged();
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+});
+
 add_task(function* test_error() {
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
   yield MozLoopServiceInternal.setError("testing", {});
   Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
   yield MozLoopServiceInternal.clearError("testing");
   Assert.notStrictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is not in error state");
 });
+
+add_task(function* test_error_with_login() {
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  yield MozLoopServiceInternal.setError("testing", {});
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
+  setInternalLoopGlobal("gFxAOAuthProfile", {email: "test@example.com", uid: "abcd1234"});
+  MozLoopServiceInternal.notifyStatusChanged("login");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "error", "Check button is in error state");
+  yield MozLoopServiceInternal.clearError("testing");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  setInternalLoopGlobal("gFxAOAuthProfile", null);
+  MozLoopServiceInternal.notifyStatusChanged();
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+});
+
+add_task(function* test_active() {
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+  setInternalLoopGlobal("gFxAOAuthProfile", {email: "test@example.com", uid: "abcd1234"});
+  yield MozLoopServiceInternal.notifyStatusChanged("login");
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "active", "Check button is in active state");
+  yield loadLoopPanel();
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state after opening panel");
+  let loopPanel = document.getElementById("loop-notification-panel");
+  loopPanel.hidePopup();
+  setInternalLoopGlobal("gFxAOAuthProfile", null);
+  MozLoopServiceInternal.notifyStatusChanged();
+  Assert.strictEqual(LoopUI.toolbarButton.node.getAttribute("state"), "", "Check button is in default state");
+});
+
--- a/browser/components/loop/test/mochitest/head.js
+++ b/browser/components/loop/test/mochitest/head.js
@@ -2,16 +2,21 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const HAWK_TOKEN_LENGTH = 64;
 const {
   LOOP_SESSION_TYPE,
   MozLoopServiceInternal,
 } = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
 
+// Cache this value only once, at the beginning of a
+// test run, so that it doesn't pick up the offline=true
+// if offline mode is requested multiple times in a test run.
+const WAS_OFFLINE = Services.io.offline;
+
 var gMozLoopAPI;
 
 function promiseGetMozLoopAPI() {
   let deferred = Promise.defer();
   let loopPanel = document.getElementById("loop-notification-panel");
   let btn = document.getElementById("loop-call-button");
 
   // Wait for the popup to be shown, then we can get the iframe and
@@ -41,17 +46,21 @@ function promiseGetMozLoopAPI() {
   // Now we're setup, click the button.
   btn.click();
 
   // Remove the iframe after each test. This also avoids mochitest complaining
   // about leaks on shutdown as we intentionally hold the iframe open for the
   // life of the application.
   registerCleanupFunction(function() {
     loopPanel.hidePopup();
-    loopPanel.removeChild(document.getElementById(btn.getAttribute("notificationFrameId")));
+    let frameId = btn.getAttribute("notificationFrameId");
+    let frame = document.getElementById(frameId);
+    if (frame) {
+      loopPanel.removeChild(frame);
+    }
   });
 
   return deferred.promise;
 }
 
 /**
  * Loads the loop panel by clicking the button and waits for its open to complete.
  * It also registers
@@ -62,25 +71,24 @@ function loadLoopPanel(aOverrideOptions 
   // Set prefs to ensure we don't access the network externally.
   Services.prefs.setCharPref("services.push.serverURL", aOverrideOptions.pushURL || "ws://localhost/");
   Services.prefs.setCharPref("loop.server", aOverrideOptions.loopURL || "http://localhost/");
 
   // Turn off the network for loop tests, so that we don't
   // try to access the remote servers. If we want to turn this
   // back on in future, be careful to check for intermittent
   // failures.
-  let wasOffline = Services.io.offline;
   if (!aOverrideOptions.stayOnline) {
     Services.io.offline = true;
   }
 
   registerCleanupFunction(function() {
     Services.prefs.clearUserPref("services.push.serverURL");
     Services.prefs.clearUserPref("loop.server");
-    Services.io.offline = wasOffline;
+    Services.io.offline = WAS_OFFLINE;
   });
 
   // Turn off animations to make tests quicker.
   let loopPanel = document.getElementById("loop-notification-panel");
   loopPanel.setAttribute("animate", "false");
 
   // Now get the actual API.
   yield promiseGetMozLoopAPI();
@@ -105,34 +113,51 @@ function resetFxA() {
   global.gFxAOAuthClientPromise = null;
   global.gFxAOAuthClient = null;
   global.gFxAOAuthTokenData = null;
   global.gFxAOAuthProfile = null;
   const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
   Services.prefs.clearUserPref(fxASessionPref);
 }
 
+function setInternalLoopGlobal(aName, aValue) {
+  let global = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
+  global[aName] = aValue;
+}
+
+function checkLoggedOutState() {
+  let global = Cu.import("resource:///modules/loop/MozLoopService.jsm", {});
+  ise(global.gFxAOAuthClientPromise, null, "gFxAOAuthClientPromise should be cleared");
+  ise(global.gFxAOAuthProfile, null, "gFxAOAuthProfile should be cleared");
+  ise(global.gFxAOAuthClient, null, "gFxAOAuthClient should be cleared");
+  ise(global.gFxAOAuthTokenData, null, "gFxAOAuthTokenData should be cleared");
+  const fxASessionPref = MozLoopServiceInternal.getSessionTokenPrefName(LOOP_SESSION_TYPE.FXA);
+  ise(Services.prefs.getPrefType(fxASessionPref), Services.prefs.PREF_INVALID,
+      "FxA hawk session should be cleared anyways");
+}
+
 function promiseDeletedOAuthParams(baseURL) {
   let deferred = Promise.defer();
   let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].
               createInstance(Ci.nsIXMLHttpRequest);
   xhr.open("DELETE", baseURL + "/setup_params", true);
   xhr.addEventListener("load", () => deferred.resolve(xhr));
   xhr.addEventListener("error", deferred.reject);
   xhr.send();
 
   return deferred.promise;
 }
 
-function promiseObserverNotified(aTopic) {
+function promiseObserverNotified(aTopic, aExpectedData = null) {
   let deferred = Promise.defer();
   Services.obs.addObserver(function onNotification(aSubject, aTopic, aData) {
     Services.obs.removeObserver(onNotification, aTopic);
-      deferred.resolve({subject: aSubject, data: aData});
-    }, aTopic, false);
+    is(aData, aExpectedData, "observer data should match expected data")
+    deferred.resolve({subject: aSubject, data: aData});
+  }, aTopic, false);
   return deferred.promise;
 }
 
 /**
  * Get the last registration on the test server.
  */
 function promiseOAuthGetRegistration(baseURL) {
   let deferred = Promise.defer();
--- a/browser/components/loop/test/mochitest/loop_fxa.sjs
+++ b/browser/components/loop/test/mochitest/loop_fxa.sjs
@@ -6,38 +6,44 @@
  */
 
 "use strict";
 
 const REQUIRED_PARAMS = ["client_id", "content_uri", "oauth_uri", "profile_uri", "state"];
 const HAWK_TOKEN_LENGTH = 64;
 
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
+Components.utils.importGlobalProperties(["URL"]);
 
 /**
  * Entry point for HTTP requests.
  */
 function handleRequest(request, response) {
-  // Look at the query string but ignore past the encoded ? when deciding on the handler.
-  dump("loop_fxa.sjs request for: " + request.queryString + "\n");
-  switch (request.queryString.replace(/%3F.*/,"")) {
+  // Convert the query string to a path with a placeholder base of example.com
+  let url = new URL(request.queryString.replace(/%3F.*/,""), "http://www.example.com");
+  dump("loop_fxa.sjs request for: " + url.pathname + "\n");
+  switch (url.pathname) {
     case "/setup_params": // Test-only
       setup_params(request, response);
       return;
     case "/fxa-oauth/params":
       params(request, response);
       return;
-    case encodeURIComponent("/oauth/authorization"):
+    case "/" + encodeURIComponent("/oauth/authorization"):
       oauth_authorization(request, response);
       return;
     case "/fxa-oauth/token":
       token(request, response);
       return;
     case "/registration":
-      registration(request, response);
+      if (request.method == "DELETE") {
+        delete_registration(request, response);
+      } else {
+        registration(request, response);
+      }
       return;
     case "/get_registration": // Test-only
       get_registration(request, response);
       return;
     case "/profile/profile":
       profile(request, response);
       return;
   }
@@ -197,16 +203,41 @@ function registration(request, response)
     response.setStatusLine(request.httpVersion, 401, "Missing Hawk");
     response.write("401 Missing Hawk Authorization header");
     return;
   }
   setSharedState("/registration", body);
 }
 
 /**
+ * DELETE /registration
+ *
+ * Hawk Authorization headers are required.
+ */
+function delete_registration(request, response) {
+  if (!request.hasHeader("Authorization") ||
+      !request.getHeader("Authorization").startsWith("Hawk")) {
+    response.setStatusLine(request.httpVersion, 401, "Missing Hawk");
+    response.write("401 Missing Hawk Authorization header");
+    return;
+  }
+
+  // Do some query string munging due to the SJS file using a base with a trailing "?"
+  // making the path become a query parameter. This is because we aren't actually
+  // registering endpoints at the root of the hostname e.g. /registration.
+  let url = new URL(request.queryString.replace(/%3F.*/,""), "http://www.example.com");
+  let registration = JSON.parse(getSharedState("/registration"));
+  if (registration.simplePushURL == url.searchParams.get("simplePushURL")) {
+    setSharedState("/registration", "");
+  } else {
+    response.setStatusLine(request.httpVersion, 400, "Bad Request");
+  }
+}
+
+/**
  * GET /get_registration
  *
  * Used for testing purposes to check if registration succeeded by returning the POST body.
  */
 function get_registration(request, response) {
   response.setHeader("Content-Type", "application/json; charset=utf-8", false);
   response.write(getSharedState("/registration"));
 }
new file mode 100644
--- /dev/null
+++ b/browser/components/loop/test/xpcshell/test_loopapi_hawk_request.js
@@ -0,0 +1,71 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Unit tests for the hawkRequest API
+ */
+
+"use strict";
+
+Cu.import("resource:///modules/loop/MozLoopAPI.jsm");
+
+let sandbox;
+function assertInSandbox(expr, msg_opt) {
+  Assert.ok(Cu.evalInSandbox(expr, sandbox), msg_opt);
+}
+
+sandbox = Cu.Sandbox("about:looppanel", { wantXrays: false } );
+injectLoopAPI(sandbox, true);
+
+add_task(function* hawk_session_scope_constants() {
+  assertInSandbox("typeof mozLoop.LOOP_SESSION_TYPE !== 'undefined'");
+
+  assertInSandbox("mozLoop.LOOP_SESSION_TYPE.GUEST === 1");
+
+  assertInSandbox("mozLoop.LOOP_SESSION_TYPE.FXA === 2");
+});
+
+function generateSessionTypeVerificationStub(desiredSessionType) {
+
+  function hawkRequestStub(sessionType, path, method, payloadObj, callback) {
+    return new Promise(function (resolve, reject) {
+      Assert.equal(desiredSessionType, sessionType);
+
+      resolve();
+    });
+  };
+
+  return hawkRequestStub;
+}
+
+const origHawkRequest = MozLoopService.oldHawkRequest;
+do_register_cleanup(function() {
+  MozLoopService.hawkRequest = origHawkRequest;
+});
+
+add_task(function* hawk_request_scope_passthrough() {
+
+  // add a stub that verifies the parameter we want
+  MozLoopService.hawkRequest =
+    generateSessionTypeVerificationStub(sandbox.mozLoop.LOOP_SESSION_TYPE.FXA);
+
+  // call mozLoop.hawkRequest, which calls MozLoopAPI.hawkRequest, which calls
+  // MozLoopService.hawkRequest
+  Cu.evalInSandbox(
+    "mozLoop.hawkRequest(mozLoop.LOOP_SESSION_TYPE.FXA," +
+                       " 'call-url/fakeToken', 'POST', {}, function() {})",
+    sandbox);
+
+  MozLoopService.hawkRequest =
+    generateSessionTypeVerificationStub(sandbox.mozLoop.LOOP_SESSION_TYPE.GUEST);
+
+  Cu.evalInSandbox(
+    "mozLoop.hawkRequest(mozLoop.LOOP_SESSION_TYPE.GUEST," +
+    " 'call-url/fakeToken', 'POST', {}, function() {})",
+    sandbox);
+
+});
+
+function run_test() {
+  run_next_test();
+}
--- a/browser/components/loop/test/xpcshell/xpcshell.ini
+++ b/browser/components/loop/test/xpcshell/xpcshell.ini
@@ -1,13 +1,14 @@
 [DEFAULT]
 head = head.js
 tail =
 firefox-appdir = browser
 
+[test_loopapi_hawk_request.js]
 [test_looppush_initialize.js]
 [test_loopservice_dnd.js]
 [test_loopservice_expiry.js]
 [test_loopservice_loop_prefs.js]
 [test_loopservice_initialize.js]
 [test_loopservice_locales.js]
 [test_loopservice_notification.js]
 [test_loopservice_registration.js]
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1475,21 +1475,17 @@ BrowserGlue.prototype = {
           currentset = currentset.replace(/(^|,)bookmarks-menu-button-container($|,)/,
                                           "$1bookmarks-menu-button$2");
           xulStore.setValue(BROWSER_DOCURL, "nav-bar", "currentset", currentset);
         }
       }
     }
 
     if (currentUIVersion < 13) {
-      try {
-        if (Services.prefs.getBoolPref("plugins.hide_infobar_for_missing_plugin"))
-          Services.prefs.setBoolPref("plugins.notifyMissingFlash", false);
-      }
-      catch (ex) {}
+      /* Obsolete */
     }
 
     if (currentUIVersion < 14) {
       // DOM Storage doesn't specially handle about: pages anymore.
       let path = OS.Path.join(OS.Constants.Path.profileDir,
                               "chromeappsstore.sqlite");
       OS.File.remove(path);
     }
--- a/browser/devtools/app-manager/app-projects.js
+++ b/browser/devtools/app-manager/app-projects.js
@@ -201,16 +201,24 @@ const AppProjects = {
       return store.object.projects[store.object.projects.length - 1];
     });
   },
 
   update: function (project) {
     return IDB.update(project);
   },
 
+  updateLocation: function(project, newLocation) {
+    return IDB.remove(project.location)
+              .then(() => {
+                project.location = newLocation;
+                return IDB.add(project);
+              });
+  },
+
   remove: function(location) {
     return IDB.remove(location).then(function () {
       let projects = store.object.projects;
       for (let i = 0; i < projects.length; i++) {
         if (projects[i].location == location) {
           projects.splice(i, 1);
           return;
         }
@@ -230,9 +238,8 @@ const AppProjects = {
   },
 
   store: store
 };
 
 EventEmitter.decorate(AppProjects);
 
 exports.AppProjects = AppProjects;
-
--- a/browser/devtools/app-manager/app-validator.js
+++ b/browser/devtools/app-manager/app-validator.js
@@ -3,16 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 "use strict";
 
 let {Ci,Cu,CC} = require("chrome");
 const promise = require("devtools/toolkit/deprecated-sync-thenables");
 
 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
+const {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 let XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
 let strings = Services.strings.createBundle("chrome://browser/locale/devtools/app-manager.properties");
 
 function AppValidator(project) {
   this.project = project;
   this.errors = [];
   this.warnings = [];
 }
@@ -46,50 +47,107 @@ AppValidator.prototype._getPackagedManif
 AppValidator.prototype._getPackagedManifestURL = function () {
   let manifestFile = this._getPackagedManifestFile();
   if (!manifestFile) {
     return null;
   }
   return Services.io.newFileURI(manifestFile).spec;
 };
 
+AppValidator.checkManifest = function(manifestURL) {
+  let deferred = promise.defer();
+  let error;
+
+  let req = new XMLHttpRequest();
+  req.overrideMimeType('text/plain');
+
+  try {
+    req.open("GET", manifestURL, true);
+  } catch(e) {
+    error = strings.formatStringFromName("validator.invalidManifestURL", [manifestURL], 1);
+    deferred.reject(error);
+    return deferred.promise;
+  }
+  req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
+
+  req.onload = function () {
+    let manifest = null;
+    try {
+      manifest = JSON.parse(req.responseText);
+    } catch(e) {
+      error = strings.formatStringFromName("validator.invalidManifestJSON", [e, manifestURL], 2);
+      deferred.reject(error);
+    }
+
+    deferred.resolve({manifest, manifestURL});
+  };
+
+  req.onerror = function () {
+    error = strings.formatStringFromName("validator.noAccessManifestURL", [req.statusText, manifestURL], 2);
+    deferred.reject(error);
+ };
+
+  try {
+    req.send(null);
+  } catch(e) {
+    error = strings.formatStringFromName("validator.noAccessManifestURL", [e, manifestURL], 2);
+    deferred.reject(error);
+  }
+
+  return deferred.promise;
+};
+
+AppValidator.findManifestAtOrigin = function(manifestURL) {
+  let fixedManifest = Services.io.newURI(manifestURL, null, null).prePath + '/manifest.webapp';
+  return AppValidator.checkManifest(fixedManifest);
+};
+
+AppValidator.findManifestPath = function(manifestURL) {
+  let deferred = promise.defer();
+
+  if (manifestURL.endsWith('manifest.webapp')) {
+    deferred.reject();
+  } else {
+    let fixedManifest = manifestURL + '/manifest.webapp';
+    deferred.resolve(AppValidator.checkManifest(fixedManifest));
+  }
+
+  return deferred.promise;
+};
+
+AppValidator.checkAlternateManifest = function(manifestURL) {
+  return Task.spawn(function*() {
+    let result;
+    try {
+      result = yield AppValidator.findManifestPath(manifestURL);
+    } catch(e) {
+      result = yield AppValidator.findManifestAtOrigin(manifestURL);
+    }
+
+    return result;
+  });
+};
+
 AppValidator.prototype._fetchManifest = function (manifestURL) {
   let deferred = promise.defer();
   this.manifestURL = manifestURL;
 
-  let req = new XMLHttpRequest();
-  req.overrideMimeType('text/plain');
-  try {
-    req.open("GET", manifestURL, true);
-  } catch(e) {
-    this.error(strings.formatStringFromName("validator.invalidManifestURL", [manifestURL], 1));
-    deferred.resolve(null);
-    return deferred.promise;
-  }
-  req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
-  req.onload = (function () {
-    let manifest = null;
-    try {
-      manifest = JSON.parse(req.responseText);
-    } catch(e) {
-      this.error(strings.formatStringFromName("validator.invalidManifestJSON", [e, manifestURL], 2));
-    }
-    deferred.resolve(manifest);
-  }).bind(this);
-  req.onerror = (function () {
-    this.error(strings.formatStringFromName("validator.noAccessManifestURL", [req.statusText, manifestURL], 2));
-    deferred.resolve(null);
-  }).bind(this);
-
-  try {
-    req.send(null);
-  } catch(e) {
-    this.error(strings.formatStringFromName("validator.noAccessManifestURL", [e, manifestURL], 2));
-    deferred.resolve();
-  }
+  AppValidator.checkManifest(manifestURL)
+              .then(({manifest, manifestURL}) => {
+                deferred.resolve(manifest);
+              }, error => {
+                AppValidator.checkAlternateManifest(manifestURL)
+                            .then(({manifest, manifestURL}) => {
+                              this.manifestURL = manifestURL;
+                              deferred.resolve(manifest);
+                            }, () => {
+                              this.error(error);
+                              deferred.resolve(null);
+                            });
+                });
 
   return deferred.promise;
 };
 
 AppValidator.prototype._getManifest = function () {
   let manifestURL;
   if (this.project.type == "packaged") {
     manifestURL = this._getPackagedManifestURL();
--- a/browser/devtools/app-manager/test/test_app_validator.html
+++ b/browser/devtools/app-manager/test/test_app_validator.html
@@ -35,22 +35,22 @@
 
       httpserver = new HttpServer();
       httpserver.start(-1);
       origin = "http://localhost:" + httpserver.identity.primaryPort + "/";
 
       next();
     }
 
-    function createHosted(path) {
+    function createHosted(path, manifestFile="/manifest.webapp") {
       let dirPath = getTestFilePath("validator/" + path);
       httpserver.registerDirectory("/", nsFile(dirPath));
       return new AppValidator({
         type: "hosted",
-        location: origin + "/manifest.webapp"
+        location: origin + manifestFile
       });
     }
 
     function createPackaged(path) {
       let dirPath = getTestFilePath("validator/" + path);
       return new AppValidator({
         type: "packaged",
         location: dirPath
@@ -149,16 +149,48 @@
           checkNoNameOrIcon(validator);
         });
       },
       function () {
         let validator = createPackaged("no-name-or-icon");
         validator.validate().then(() => {
           checkNoNameOrIcon(validator);
         });
+      },
+
+      // Test a regular URL instead of a direct link to the manifest
+      function () {
+        let validator = createHosted("valid", "/");
+        validator.validate().then(() => {
+          is(validator.warnings.length, 0, "manifest found got no warning");
+          is(validator.errors.length, 0, "manifest found got no error");
+
+          next();
+        });
+      },
+
+      // Test finding a manifest at origin's root
+      function () {
+        let validator = createHosted("valid", "/unexisting-dir");
+        validator.validate().then(() => {
+          is(validator.warnings.length, 0, "manifest found at origin root got no warning");
+          is(validator.errors.length, 0, "manifest found at origin root got no error");
+
+          next();
+        });
+      },
+
+      // Test priorization of manifest.webapp at provided location instead of a manifest located at origin's root
+      function() {
+        let validator = createHosted("valid", "/alsoValid");
+        validator.validate().then(() => {
+          is(validator.manifest.name, "valid at subfolder", "manifest at subfolder was used");
+
+          next();
+        });
       }
     ];
 
     function checkNoNameOrIcon(validator) {
       is(validator.errors.length, 1, "app with no name has an error");
       is(validator.errors[0],
          strings.GetStringFromName("validator.missNameManifestProperty"),
          "with expected message");
new file mode 100644
--- /dev/null
+++ b/browser/devtools/app-manager/test/validator/valid/alsoValid/manifest.webapp
@@ -0,0 +1,7 @@
+{
+  "name": "valid at subfolder",
+  "launch_path": "/home.html",
+  "icons": {
+    "128": "/icon.png"
+  }
+}
--- a/browser/devtools/projecteditor/lib/shells.js
+++ b/browser/devtools/projecteditor/lib/shells.js
@@ -28,16 +28,17 @@ var Shell = Class({
    * @param ProjectEditor host
    * @param Resource resource
    */
   initialize: function(host, resource) {
     this.host = host;
     this.doc = host.document;
     this.resource = resource;
     this.elt = this.doc.createElement("vbox");
+    this.elt.classList.add("view-project-detail");
     this.elt.shell = this;
 
     let constructor = this._editorTypeForResource();
 
     this.editor = constructor(this.host);
     this.editor.shell = this;
     this.editorAppended = this.editor.appended;
 
--- a/browser/devtools/timeline/test/browser_timeline_waterfall-generic.js
+++ b/browser/devtools/timeline/test/browser_timeline_waterfall-generic.js
@@ -46,23 +46,23 @@ let test = Task.async(function*() {
 
   ok($(".timeline-marker-container"),
     "A marker container should have been created.");
 
   // Test the markers sidebar (left).
 
   ok($$(".timeline-marker-sidebar").length,
     "Some marker sidebar nodes should have been created.");
-  ok($$(".timeline-marker-sidebar > .timeline-marker-bullet").length,
+  ok($$(".timeline-marker-sidebar:not(spacer) > .timeline-marker-bullet").length,
     "Some marker color bullets should have been created inside the sidebar.");
-  ok($$(".timeline-marker-sidebar > .timeline-marker-name").length,
+  ok($$(".timeline-marker-sidebar:not(spacer) > .timeline-marker-name").length,
     "Some marker name labels should have been created inside the sidebar.");
 
   // Test the markers waterfall (right).
 
   ok($$(".timeline-marker-waterfall").length,
     "Some marker waterfall nodes should have been created.");
-  ok($$(".timeline-marker-waterfall > .timeline-marker-bar").length,
+  ok($$(".timeline-marker-waterfall:not(spacer) > .timeline-marker-bar").length,
     "Some marker color bars should have been created inside the waterfall.");
 
   yield teardown(panel);
   finish();
 });
--- a/browser/devtools/webide/modules/app-manager.js
+++ b/browser/devtools/webide/modules/app-manager.js
@@ -614,17 +614,19 @@ exports.AppManager = AppManager = {
         project.errors = "";
         project.errorsCount = 0;
       }
 
       if (project.warningsCount && project.errorsCount) {
         project.validationStatus = "error warning";
       }
 
-      if (AppProjects.get(project.location)) {
+      if (project.type === "hosted" && project.location !== validation.manifestURL) {
+        yield AppProjects.updateLocation(project, validation.manifestURL);
+      } else if (AppProjects.get(project.location)) {
         yield AppProjects.update(project);
       }
 
       if (AppManager.selectedProject === project) {
         AppManager.update("project-validated");
       }
     });
   },
--- a/browser/devtools/webide/test/test_import.html
+++ b/browser/devtools/webide/test/test_import.html
@@ -40,24 +40,32 @@
 
             let hostedAppManifest = TEST_BASE + "hosted_app.manifest";
             yield win.Cmds.importHostedApp(hostedAppManifest);
 
             project = win.AppManager.selectedProject;
             is(project.location, hostedAppManifest, "Location is valid");
             is(project.name, "hosted manifest name property", "name field has been updated");
 
+            yield nextTick();
+
+            hostedAppManifest = TEST_BASE + "/app";
+            yield win.Cmds.importHostedApp(hostedAppManifest);
+
+            project = win.AppManager.selectedProject;
+            ok(project.location.endsWith('manifest.webapp'), "The manifest was found and the project was updated");
+
             info("opening panel");
             yield win.Cmds.showProjectPanel();
             info("panel open");
 
             let panelNode = win.document.querySelector("#project-panel");
             let items = panelNode.querySelectorAll(".panel-item");
             // 3 controls, + 2 projects
-            is(items.length, 5, "5 projects in panel");
+            is(items.length, 6, "6 projects in panel");
             is(items[3].getAttribute("label"), "A name (in app directory)", "Panel label is correct");
             is(items[4].getAttribute("label"), "hosted manifest name property", "Panel label is correct");
 
             yield closeWebIDE(win);
 
             yield removeAllProjects();
 
             SimpleTest.finish();
@@ -66,9 +74,8 @@
           SimpleTest.finish();
         });
       }
 
 
     </script>
   </body>
 </html>
-
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -98,23 +98,16 @@ popupWarningDontShowFromMessage=Don't sh
 popupWarningDontShowFromLocationbar=Don't show info bar when pop-ups are blocked
 popupShowPopupPrefix=Show '%S'
 
 # Bad Content Blocker Doorhanger Notification
 # %S is brandShortName
 badContentBlocked.blocked.message=%S is blocking content on this page.
 badContentBlocked.notblocked.message=%S is not blocking any content on this page.
 
-# missing plugin installer
-installPlugin.message = Would you like to install the plugin needed to display the media on this page?
-installPlugin.button.label=Install %S
-installPlugin.button.accesskey=I
-installPlugin.ignoreButton.label=Don't ask again
-installPlugin.ignoreButton.accesskey=N
-
 crashedpluginsMessage.title=The %S plugin has crashed.
 crashedpluginsMessage.reloadButton.label=Reload page
 crashedpluginsMessage.reloadButton.accesskey=R
 crashedpluginsMessage.submitButton.label=Submit a crash report
 crashedpluginsMessage.submitButton.accesskey=S
 crashedpluginsMessage.learnMore=Learn More…
 
 # Keyword fixup messages
--- a/browser/modules/PluginContent.jsm
+++ b/browser/modules/PluginContent.jsm
@@ -328,30 +328,17 @@ PluginContent.prototype = {
 
     let shouldShowNotification = false;
     switch (eventType) {
       case "PluginCrashed":
         this.pluginInstanceCrashed(plugin, event);
         break;
 
       case "PluginNotFound": {
-        let installable = this.showInstallNotification(plugin, eventType);
-        let contentWindow = plugin.ownerDocument.defaultView;
-        // For non-object plugin tags, register a click handler to install the
-        // plugin. Object tags can, and often do, deal with that themselves,
-        // so don't stomp on the page developers toes.
-        if (installable && !(plugin instanceof contentWindow.HTMLObjectElement)) {
-          let installStatus = this.getPluginUI(plugin, "installStatus");
-          installStatus.setAttribute("installable", "true");
-          let iconStatus = this.getPluginUI(plugin, "icon");
-          iconStatus.setAttribute("installable", "true");
-
-          let installLink = this.getPluginUI(plugin, "installPluginLink");
-          this.addLinkClickCallback(installLink, "installSinglePlugin", plugin);
-        }
+        /* NOP */
         break;
       }
 
       case "PluginBlocklisted":
       case "PluginOutdated":
         shouldShowNotification = true;
         break;
 
@@ -466,23 +453,16 @@ PluginContent.prototype = {
       return;
 
     if (playPlugin)
       objLoadingContent.playPlugin();
     else
       objLoadingContent.cancelPlayPreview();
   },
 
-  // Callback for user clicking on a missing (unsupported) plugin.
-  installSinglePlugin: function (plugin) {
-    this.global.sendAsyncMessage("PluginContent:InstallSinglePlugin", {
-      pluginInfo: this._getPluginInfo(plugin),
-    });
-  },
-
   // Forward a link click callback to the chrome process.
   forwardCallback: function (name) {
     this.global.sendAsyncMessage("PluginContent:LinkClickCallback", { name: name });
   },
 
 #ifdef MOZ_CRASHREPORTER
   submitReport: function submitReport(pluginDumpID, browserDumpID, plugin) {
     let keyVals = {};
@@ -501,23 +481,16 @@ PluginContent.prototype = {
     });
   },
 #endif
 
   reloadPage: function () {
     this.global.content.location.reload();
   },
 
-  showInstallNotification: function (plugin) {
-    let [shown] = this.global.sendSyncMessage("PluginContent:ShowInstallNotification", {
-      pluginInfo: this._getPluginInfo(plugin),
-    });
-    return shown;
-  },
-
   // Event listener for click-to-play plugins.
   _handleClickToPlayEvent: function (plugin) {
     let doc = plugin.ownerDocument;
     let pluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
     let objLoadingContent = plugin.QueryInterface(Ci.nsIObjectLoadingContent);
     // guard against giving pluginHost.getPermissionStringForType a type
     // not associated with any known plugin
     if (!this.isKnownPlugin(objLoadingContent))
--- a/browser/modules/UITour.jsm
+++ b/browser/modules/UITour.jsm
@@ -11,16 +11,18 @@ const {classes: Cc, interfaces: Ci, util
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Promise.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "LightweightThemeManager",
   "resource://gre/modules/LightweightThemeManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PermissionsUtils",
   "resource://gre/modules/PermissionsUtils.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ResetProfile",
+  "resource://gre/modules/ResetProfile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
   "resource://gre/modules/UITelemetry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
   "resource:///modules/BrowserUITelemetry.jsm");
 
 
@@ -422,16 +424,22 @@ this.UITour = {
 
       case "showFirefoxAccounts": {
         // 'signup' is the only action that makes sense currently, so we don't
         // accept arbitrary actions just to be safe...
         // We want to replace the current tab.
         contentDocument.location.href = "about:accounts?action=signup&entrypoint=uitour";
         break;
       }
+
+      case "resetFirefox": {
+        // Open a reset profile dialog window.
+        ResetProfile.openConfirmationDialog(window);
+        break;
+      }
     }
 
     if (!this.originTabs.has(window))
       this.originTabs.set(window, new Set());
 
     this.originTabs.get(window).add(tab);
     tab.addEventListener("TabClose", this);
     tab.addEventListener("TabBecomingWindow", this);
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -29,11 +29,13 @@ skip-if = e10s # Bug 941428 - UITour.jsm
 [browser_UITour_annotation_size_attributes.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly.
 [browser_UITour_panel_close_annotation.js]
 skip-if = true # Disabled due to frequent failures, bugs 1026310 and 1032137
 [browser_UITour_registerPageID.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_UITour_sync.js]
 skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
+[browser_UITour_resetProfile.js]
+skip-if = e10s # Bug 941428 - UITour.jsm not e10s friendly
 [browser_taskbar_preview.js]
 run-if = os == "win"
 skip-if = e10s # Bug 666808 - AeroPeek support for e10s
new file mode 100644
--- /dev/null
+++ b/browser/modules/test/browser_UITour_resetProfile.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+let gTestTab;
+let gContentAPI;
+let gContentWindow;
+
+Components.utils.import("resource:///modules/UITour.jsm");
+
+function test() {
+  UITourTest();
+}
+
+let tests = [
+  // Test that a reset profile dialog appears when "resetFirefox" event is triggered
+  function test_resetFirefox(done) {
+    let winWatcher = Cc["@mozilla.org/embedcomp/window-watcher;1"].
+                     getService(Ci.nsIWindowWatcher);
+    winWatcher.registerNotification(function onOpen(subj, topic, data) {
+      if (topic == "domwindowopened" && subj instanceof Ci.nsIDOMWindow) {
+        subj.addEventListener("load", function onLoad() {
+          subj.removeEventListener("load", onLoad);
+          if (subj.document.documentURI ==
+              "chrome://global/content/resetProfile.xul") {
+            winWatcher.unregisterNotification(onOpen);
+            ok(true, "Observed search manager window open");
+            is(subj.opener, window,
+               "Reset Firefox event opened a reset profile window.");
+            subj.close();
+            done();
+          }
+        });
+      }
+    });
+    gContentAPI.resetFirefox();
+  },
+];
--- a/browser/modules/test/uitour.js
+++ b/browser/modules/test/uitour.js
@@ -174,9 +174,13 @@ if (typeof Mozilla == 'undefined') {
 			configuration: configName,
 		});
 	};
 
 	Mozilla.UITour.showFirefoxAccounts = function() {
 		_sendEvent('showFirefoxAccounts');
 	};
 
+	Mozilla.UITour.resetFirefox = function() {
+		_sendEvent('resetFirefox');
+	};
+
 })();
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -1127,20 +1127,16 @@ toolbarbutton[sdk-button="true"][cui-are
   width: 32px;
   height: 32px;
 }
 
 .popup-notification-icon[popupid="click-to-play-plugins"] {
   list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
 }
 
-.popup-notification-icon[popupid="plugins-not-found"] {
-  list-style-image: url(chrome://browser/skin/pluginInstall-64.png);
-}
-
 .popup-notification-icon[popupid="web-notifications"] {
   list-style-image: url(chrome://browser/skin/notification-64.png);
 }
 
 .addon-progress-description {
   width: 350px;
   max-width: 350px;
 }
@@ -1296,20 +1292,16 @@ toolbarbutton[sdk-button="true"][cui-are
 #plugins-notification-icon:hover {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
 #plugins-notification-icon:active {
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
 
-#plugin-install-notification-icon {
-  list-style-image: url(chrome://browser/skin/pluginInstall-16.png);
-}
-
 #notification-popup-box[hidden] {
   /* Override display:none to make the pluginBlockedNotification animation work
      when showing the notification repeatedly. */
   display: -moz-box;
   visibility: collapse;
 }
 
 #plugins-notification-icon.plugin-blocked[showing] {
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -49,18 +49,16 @@ browser.jar:
   skin/classic/browser/bad-content-unblocked-64.png
   skin/classic/browser/monitor.png
   skin/classic/browser/monitor_16-10.png
   skin/classic/browser/notification-16.png
   skin/classic/browser/notification-64.png
 * skin/classic/browser/pageInfo.css
   skin/classic/browser/pageInfo.png
   skin/classic/browser/page-livemarks.png
-  skin/classic/browser/pluginInstall-16.png
-  skin/classic/browser/pluginInstall-64.png
   skin/classic/browser/pointerLock-16.png
   skin/classic/browser/pointerLock-64.png
   skin/classic/browser/Privacy-16.png
   skin/classic/browser/privatebrowsing-mask.png
   skin/classic/browser/reload-stop-go.png
   skin/classic/browser/searchbar.css
   skin/classic/browser/Secure.png
   skin/classic/browser/Security-broken.png
deleted file mode 100644
index a72a2839c99b5ff619a7cf83d5fb196f1edf5864..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 6a41aa05963f69c5edcee028d34443ab3e1a31f3..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -3639,25 +3639,16 @@ toolbarbutton.chevron > .toolbarbutton-m
     -moz-image-region: rect(0, 64px, 32px, 32px);
   }
 
   #plugins-notification-icon:active {
     -moz-image-region: rect(0, 96px, 32px, 64px);
   }
 }
 
-#plugin-install-notification-icon {
-  list-style-image: url(chrome://browser/skin/pluginInstall-16.png);
-}
-@media (min-resolution: 2dppx) {
-  #plugin-install-notification-icon {
-    list-style-image: url(chrome://browser/skin/pluginInstall-16@2x.png);
-  }
-}
-
 #notification-popup-box[hidden] {
   /* Override display:none to make the pluginBlockedNotification animation work
      when showing the notification repeatedly. */
   display: -moz-box;
   visibility: collapse;
 }
 
 #plugins-notification-icon.plugin-blocked[showing] {
@@ -3973,25 +3964,16 @@ menulist.translate-infobar-element > .me
   width: 32px;
   height: 32px;
 }
 
 .popup-notification-icon[popupid="click-to-play-plugins"] {
   list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
 }
 
-.popup-notification-icon[popupid="plugins-not-found"] {
-  list-style-image: url(chrome://browser/skin/pluginInstall-64.png);
-}
-@media (min-resolution: 2dppx) {
-  .popup-notification-icon[popupid="plugins-not-found"] {
-    list-style-image: url(chrome://browser/skin/pluginInstall-64\@2x.png);
-  }
-}
-
 .addon-progress-description {
   width: 350px;
   max-width: 350px;
 }
 
 .popup-progress-label,
 .popup-progress-meter {
   -moz-margin-start: 0;
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -77,20 +77,16 @@ browser.jar:
   skin/classic/browser/panel-expander-closed.png
   skin/classic/browser/panel-expander-closed@2x.png
   skin/classic/browser/panel-expander-open.png
   skin/classic/browser/panel-expander-open@2x.png
   skin/classic/browser/panel-plus-sign.png
   skin/classic/browser/page-livemarks.png
   skin/classic/browser/page-livemarks@2x.png
   skin/classic/browser/pageInfo.css
-  skin/classic/browser/pluginInstall-16.png
-  skin/classic/browser/pluginInstall-16@2x.png
-  skin/classic/browser/pluginInstall-64.png
-  skin/classic/browser/pluginInstall-64@2x.png
   skin/classic/browser/pointerLock-16.png
   skin/classic/browser/pointerLock-16@2x.png
   skin/classic/browser/pointerLock-64.png
   skin/classic/browser/pointerLock-64@2x.png
   skin/classic/browser/Privacy-16.png
   skin/classic/browser/privatebrowsing-mask.png
   skin/classic/browser/privatebrowsing-mask@2x.png
   skin/classic/browser/privatebrowsing-mask-short.png
deleted file mode 100644
index a72a2839c99b5ff619a7cf83d5fb196f1edf5864..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 6d064a3153455783dbddec50d824f5ccd46f7e81..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 6a41aa05963f69c5edcee028d34443ab3e1a31f3..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 564641bf6c21ce354fc6aaef598bcad284489bba..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/themes/shared/devtools/projecteditor/projecteditor.css
+++ b/browser/themes/shared/devtools/projecteditor/projecteditor.css
@@ -2,16 +2,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/. */
 
  :root {
   color: #18191a;
 }
 
+.view-project-detail {
+  overflow: auto;
+}
+
 .plugin-hidden {
   display: none;
 }
 
 .arrow {
   -moz-appearance: treetwisty;
   width: 20px;
   height: 20px;
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2137,20 +2137,16 @@ toolbarbutton.bookmark-item[dragover="tr
   width: 32px;
   height: 32px;
 }
 
 .popup-notification-icon[popupid="click-to-play-plugins"] {
   list-style-image: url(chrome://mozapps/skin/plugins/pluginBlocked-64.png);
 }
 
-.popup-notification-icon[popupid="plugins-not-found"] {
-  list-style-image: url(chrome://browser/skin/pluginInstall-64.png);
-}
-
 .popup-notification-icon[popupid="web-notifications"] {
   list-style-image: url(chrome://browser/skin/notification-64.png);
 }
 
 .addon-progress-description {
   width: 350px;
   max-width: 350px;
 }
@@ -2308,20 +2304,16 @@ toolbarbutton.bookmark-item[dragover="tr
 #plugins-notification-icon:hover {
   -moz-image-region: rect(0, 32px, 16px, 16px);
 }
 
 #plugins-notification-icon:active {
   -moz-image-region: rect(0, 48px, 16px, 32px);
 }
 
-#plugin-install-notification-icon {
-  list-style-image: url(chrome://browser/skin/pluginInstall-16.png);
-}
-
 #notification-popup-box[hidden] {
   /* Override display:none to make the pluginBlockedNotification animation work
      when showing the notification repeatedly. */
   display: -moz-box;
   visibility: collapse;
 }
 
 #plugins-notification-icon.plugin-blocked[showing] {
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -59,18 +59,16 @@ browser.jar:
         skin/classic/browser/bad-content-unblocked-64.png
         skin/classic/browser/monitor.png
         skin/classic/browser/monitor_16-10.png
         skin/classic/browser/notification-16.png
         skin/classic/browser/notification-64.png
         skin/classic/browser/pageInfo.css
         skin/classic/browser/pageInfo.png
         skin/classic/browser/page-livemarks.png                      (feeds/feedIcon16.png)
-        skin/classic/browser/pluginInstall-16.png
-        skin/classic/browser/pluginInstall-64.png
         skin/classic/browser/pointerLock-16.png
         skin/classic/browser/pointerLock-64.png
         skin/classic/browser/Privacy-16.png
         skin/classic/browser/Privacy-32.png
         skin/classic/browser/Privacy-48.png
         skin/classic/browser/privatebrowsing-mask-tabstrip-XPVista7.png
         skin/classic/browser/privatebrowsing-mask-titlebar-XPVista7.png
         skin/classic/browser/privatebrowsing-mask-titlebar-XPVista7-tall.png
@@ -482,18 +480,16 @@ browser.jar:
         skin/classic/aero/browser/bad-content-unblocked-64.png
         skin/classic/aero/browser/monitor.png
         skin/classic/aero/browser/monitor_16-10.png
         skin/classic/aero/browser/notification-16.png
         skin/classic/aero/browser/notification-64.png
         skin/classic/aero/browser/pageInfo.css
         skin/classic/aero/browser/pageInfo.png                       (pageInfo-aero.png)
         skin/classic/aero/browser/page-livemarks.png                 (feeds/feedIcon16-aero.png)
-        skin/classic/aero/browser/pluginInstall-16.png
-        skin/classic/aero/browser/pluginInstall-64.png
         skin/classic/aero/browser/pointerLock-16.png
         skin/classic/aero/browser/pointerLock-64.png
         skin/classic/aero/browser/Privacy-16.png                     (Privacy-16-aero.png)
         skin/classic/aero/browser/Privacy-32.png                     (Privacy-32-aero.png)
         skin/classic/aero/browser/privatebrowsing-mask-tabstrip.png
         skin/classic/aero/browser/privatebrowsing-mask-tabstrip-XPVista7.png
         skin/classic/aero/browser/privatebrowsing-mask-titlebar.png
         skin/classic/aero/browser/privatebrowsing-mask-titlebar-XPVista7.png
deleted file mode 100644
index a72a2839c99b5ff619a7cf83d5fb196f1edf5864..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 6a41aa05963f69c5edcee028d34443ab3e1a31f3..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/content/media/fmp4/wmf/WMFAudioMFTManager.cpp
+++ b/content/media/fmp4/wmf/WMFAudioMFTManager.cpp
@@ -303,9 +303,15 @@ WMFAudioMFTManager::Output(int64_t aStre
   #ifdef LOG_SAMPLE_DECODE
   LOG("Decoded audio sample! timestamp=%lld duration=%lld currentLength=%u",
       timestamp, duration, currentLength);
   #endif
 
   return S_OK;
 }
 
+void
+WMFAudioMFTManager::Shutdown()
+{
+  mDecoder = nullptr;
+}
+
 } // namespace mozilla
--- a/content/media/fmp4/wmf/WMFAudioMFTManager.h
+++ b/content/media/fmp4/wmf/WMFAudioMFTManager.h
@@ -24,16 +24,19 @@ public:
 
   virtual HRESULT Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE;
 
   // Note WMF's AAC decoder sometimes output negatively timestamped samples,
   // presumably they're the preroll samples, and we strip them. We may return
   // a null aOutput in this case.
   virtual HRESULT Output(int64_t aStreamOffset,
                          nsAutoPtr<MediaData>& aOutput) MOZ_OVERRIDE;
+
+  virtual void Shutdown() MOZ_OVERRIDE;
+
 private:
 
   HRESULT UpdateOutputType();
 
   // IMFTransform wrapper that performs the decoding.
   RefPtr<MFTDecoder> mDecoder;
 
   uint32_t mAudioChannels;
--- a/content/media/fmp4/wmf/WMFMediaDataDecoder.cpp
+++ b/content/media/fmp4/wmf/WMFMediaDataDecoder.cpp
@@ -43,18 +43,26 @@ WMFMediaDataDecoder::Init()
   NS_ENSURE_TRUE(mDecoder, NS_ERROR_FAILURE);
 
   return NS_OK;
 }
 
 nsresult
 WMFMediaDataDecoder::Shutdown()
 {
+  mTaskQueue->FlushAndDispatch(NS_NewRunnableMethod(this, &WMFMediaDataDecoder::ProcessShutdown));
+  return NS_OK;
+}
+
+void
+WMFMediaDataDecoder::ProcessShutdown()
+{
+  mMFTManager->Shutdown();
+  mMFTManager = nullptr;
   mDecoder = nullptr;
-  return NS_OK;
 }
 
 // Inserts data into the decoder's pipeline.
 nsresult
 WMFMediaDataDecoder::Input(mp4_demuxer::MP4Sample* aSample)
 {
   mTaskQueue->Dispatch(
     NS_NewRunnableMethodWithArg<nsAutoPtr<mp4_demuxer::MP4Sample>>(
--- a/content/media/fmp4/wmf/WMFMediaDataDecoder.h
+++ b/content/media/fmp4/wmf/WMFMediaDataDecoder.h
@@ -38,16 +38,19 @@ public:
   // Produces decoded output, if possible. Blocks until output can be produced,
   // or until no more is able to be produced.
   // Returns S_OK on success, or MF_E_TRANSFORM_NEED_MORE_INPUT if there's not
   // enough data to produce more output. If this returns a failure code other
   // than MF_E_TRANSFORM_NEED_MORE_INPUT, an error will be reported to the
   // MP4Reader.
   virtual HRESULT Output(int64_t aStreamOffset,
                          nsAutoPtr<MediaData>& aOutput) = 0;
+
+  // Destroys all resources.
+  virtual void Shutdown() = 0;
 };
 
 // Decodes audio and video using Windows Media Foundation. Samples are decoded
 // using the MFTDecoder created by the MFTManager. This class implements
 // the higher-level logic that drives mapping the MFT to the async
 // MediaDataDecoder interface. The specifics of decoding the exact stream
 // type are handled by MFTManager and the MFTDecoder it creates.
 class WMFMediaDataDecoder : public MediaDataDecoder {
@@ -76,16 +79,18 @@ private:
   // Called on the task queue. Extracts output if available, and delivers
   // it to the reader. Called after ProcessDecode() and ProcessDrain().
   void ProcessOutput();
 
   // Called on the task queue. Orders the MFT to drain, and then extracts
   // all available output.
   void ProcessDrain();
 
+  void ProcessShutdown();
+
   RefPtr<MediaTaskQueue> mTaskQueue;
   MediaDataDecoderCallback* mCallback;
 
   RefPtr<MFTDecoder> mDecoder;
   nsAutoPtr<MFTManager> mMFTManager;
 
   // The last offset into the media resource that was passed into Input().
   // This is used to approximate the decoder's position in the media resource.
--- a/content/media/fmp4/wmf/WMFVideoMFTManager.cpp
+++ b/content/media/fmp4/wmf/WMFVideoMFTManager.cpp
@@ -394,9 +394,15 @@ WMFVideoMFTManager::Output(int64_t aStre
   NS_ENSURE_TRUE(SUCCEEDED(hr), hr);
   NS_ENSURE_TRUE(frame, E_FAIL);
 
   aOutData = frame;
 
   return S_OK;
 }
 
+void
+WMFVideoMFTManager::Shutdown()
+{
+  mDecoder = nullptr;
+}
+
 } // namespace mozilla
--- a/content/media/fmp4/wmf/WMFVideoMFTManager.h
+++ b/content/media/fmp4/wmf/WMFVideoMFTManager.h
@@ -28,16 +28,18 @@ public:
 
   virtual TemporaryRef<MFTDecoder> Init() MOZ_OVERRIDE;
 
   virtual HRESULT Input(mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE;
 
   virtual HRESULT Output(int64_t aStreamOffset,
                          nsAutoPtr<MediaData>& aOutput) MOZ_OVERRIDE;
 
+  virtual void Shutdown() MOZ_OVERRIDE;
+
 private:
 
   bool InitializeDXVA();
 
   HRESULT ConfigureVideoFrameGeometry();
 
   HRESULT CreateBasicVideoFrame(IMFSample* aSample,
                                 int64_t aStreamOffset,
--- a/content/media/mediasource/MediaSource.cpp
+++ b/content/media/mediasource/MediaSource.cpp
@@ -333,17 +333,17 @@ MediaSource::Detach()
             this, mDecoder.get(), mDecoder ? mDecoder->GetOwner() : nullptr);
   if (!mDecoder) {
     MOZ_ASSERT(mReadyState == MediaSourceReadyState::Closed);
     MOZ_ASSERT(mActiveSourceBuffers->IsEmpty() && mSourceBuffers->IsEmpty());
     return;
   }
   mDecoder->DetachMediaSource();
   mDecoder = nullptr;
-  mFirstSourceBufferInitialization = false;
+  mFirstSourceBufferInitialized = false;
   SetReadyState(MediaSourceReadyState::Closed);
   mDuration = UnspecifiedNaN<double>();
   if (mActiveSourceBuffers) {
     mActiveSourceBuffers->Clear();
   }
   if (mSourceBuffers) {
     mSourceBuffers->Clear();
   }
@@ -390,17 +390,17 @@ MediaSource::GetBuffered(TimeRanges* aBu
   MSE_DEBUG("MediaSource(%p)::GetBuffered ranges=%s", this, DumpTimeRanges(intersectionRanges).get());
 }
 
 MediaSource::MediaSource(nsPIDOMWindow* aWindow)
   : DOMEventTargetHelper(aWindow)
   , mDuration(UnspecifiedNaN<double>())
   , mDecoder(nullptr)
   , mReadyState(MediaSourceReadyState::Closed)
-  , mFirstSourceBufferInitialization(false)
+  , mFirstSourceBufferInitialized(false)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mSourceBuffers = new SourceBufferList(this);
   mActiveSourceBuffers = new SourceBufferList(this);
   MSE_API("MediaSource(%p)::MediaSource(aWindow=%p) mSourceBuffers=%p mActiveSourceBuffers=%p",
           this, aWindow, mSourceBuffers.get(), mActiveSourceBuffers.get());
 }
 
@@ -482,19 +482,20 @@ MediaSource::NotifyEvicted(double aStart
   // the given range.
   mSourceBuffers->Evict(aStart, aEnd);
 }
 
 void
 MediaSource::QueueInitializationEvent()
 {
   MOZ_ASSERT(NS_IsMainThread());
-  if (!mFirstSourceBufferInitialization) {
-    mFirstSourceBufferInitialization = true;
+  if (mFirstSourceBufferInitialized) {
+    return;
   }
+  mFirstSourceBufferInitialized = true;
   MSE_DEBUG("MediaSource(%p)::QueueInitializationEvent()", this);
   nsRefPtr<nsIRunnable> task =
     NS_NewRunnableMethod(this, &MediaSource::InitializationEvent);
   NS_DispatchToMainThread(task);
 }
 
 void
 MediaSource::InitializationEvent()
--- a/content/media/mediasource/MediaSource.h
+++ b/content/media/mediasource/MediaSource.h
@@ -87,17 +87,17 @@ public:
 
   // Called by SourceBuffers to notify this MediaSource that data has
   // been evicted from the buffered data. The start and end times
   // that were evicted are provided.
   void NotifyEvicted(double aStart, double aEnd);
 
   // Queue InitializationEvent to run on the main thread.  Called when a
   // SourceBuffer has an initialization segment appended, but only
-  // dispatched the first time (using mFirstSourceBufferInitialization).
+  // dispatched the first time (using mFirstSourceBufferInitialized).
   // Demarcates the point in time at which only currently registered
   // TrackBuffers are treated as essential by the MediaSourceReader for
   // initialization.
   void QueueInitializationEvent();
 
 #if defined(DEBUG)
   // Dump the contents of each SourceBuffer to a series of files under aPath.
   // aPath must exist.  Debug only, invoke from your favourite debugger.
@@ -125,17 +125,17 @@ private:
 
   nsRefPtr<SourceBufferList> mSourceBuffers;
   nsRefPtr<SourceBufferList> mActiveSourceBuffers;
 
   nsRefPtr<MediaSourceDecoder> mDecoder;
 
   MediaSourceReadyState mReadyState;
 
-  bool mFirstSourceBufferInitialization;
+  bool mFirstSourceBufferInitialized;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(MediaSource, MOZILLA_DOM_MEDIASOURCE_IMPLEMENTATION_IID)
 
 } // namespace dom
 
 } // namespace mozilla
 #endif /* mozilla_dom_MediaSource_h_ */
--- a/content/media/mediasource/MediaSourceReader.cpp
+++ b/content/media/mediasource/MediaSourceReader.cpp
@@ -468,17 +468,20 @@ MediaSourceReader::Seek(int64_t aTime, i
 
 nsresult
 MediaSourceReader::ReadMetadata(MediaInfo* aInfo, MetadataTags** aTags)
 {
   MSE_DEBUG("MediaSourceReader(%p)::ReadMetadata tracks=%u/%u audio=%p video=%p",
             this, mEssentialTrackBuffers.Length(), mTrackBuffers.Length(),
             mAudioTrack.get(), mVideoTrack.get());
 
-  mEssentialTrackBuffers.Clear();
+  {
+    ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
+    mEssentialTrackBuffers.Clear();
+  }
   if (!mAudioTrack && !mVideoTrack) {
     MSE_DEBUG("MediaSourceReader(%p)::ReadMetadata missing track: mAudioTrack=%p mVideoTrack=%p",
               this, mAudioTrack.get(), mVideoTrack.get());
     return NS_ERROR_FAILURE;
   }
 
   int64_t maxDuration = -1;
 
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..64a24ec898e75252663cd366ffd88f61b7c6f983
GIT binary patch
literal 18442
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
new file mode 100644
--- /dev/null
+++ b/content/media/test/bug1066943.webm^headers^
@@ -0,0 +1,1 @@
+Cache-Control: no-store
--- a/content/media/test/manifest.js
+++ b/content/media/test/manifest.js
@@ -182,16 +182,17 @@ var gPlayTests = [
   // we can handle playing files when only push very little audio data to the
   // hardware.
   { name:"spacestorm-1000Hz-100ms.ogg", type:"audio/ogg", duration:0.099 },
 
   // Opus data in an ogg container
   { name:"detodos.opus", type:"audio/ogg; codecs=opus", duration:2.9135 },
   // Opus data in a webm container
   { name:"detodos.webm", type:"audio/webm; codecs=opus", duration:2.9135 },
+  { name:"bug1066943.webm", type:"audio/webm; codecs=opus", duration:1.383 },
 
   // Multichannel Opus in an ogg container
   { name:"test-1-mono.opus", type:"audio/ogg; codecs=opus", duration:1.044 },
   { name:"test-2-stereo.opus", type:"audio/ogg; codecs=opus", duration:2.925 },
   { name:"test-3-LCR.opus", type:"audio/ogg; codecs=opus", duration:4.214 },
   { name:"test-4-quad.opus", type:"audio/ogg; codecs=opus", duration:6.234 },
   { name:"test-5-5.0.opus", type:"audio/ogg; codecs=opus", duration:7.558 },
   { name:"test-6-5.1.opus", type:"audio/ogg; codecs=opus", duration:10.333 },
--- a/content/media/test/mochitest.ini
+++ b/content/media/test/mochitest.ini
@@ -102,16 +102,18 @@ support-files =
   bug557094.ogv^headers^
   bug580982.webm
   bug580982.webm^headers^
   bug603918.webm
   bug603918.webm^headers^
   bug604067.webm
   bug604067.webm^headers^
   bug883173.vtt
+  bug1066943.webm
+  bug1066943.webm^headers^
   can_play_type_dash.js
   can_play_type_ogg.js
   can_play_type_wave.js
   can_play_type_webm.js
   cancellable_request.sjs
   chain.ogg
   chain.ogg^headers^
   chain.ogv
--- a/content/media/webm/WebMReader.cpp
+++ b/content/media/webm/WebMReader.cpp
@@ -551,18 +551,18 @@ bool WebMReader::DecodeAudioPacket(neste
   if (!decoded_frames.isValid()) {
     NS_WARNING("Int overflow adding decoded_frames");
     return false;
   }
   if (tstamp_frames.value() > decoded_frames.value()) {
 #ifdef DEBUG
     CheckedInt64 usecs = FramesToUsecs(tstamp_frames.value() - decoded_frames.value(), rate);
     LOG(PR_LOG_DEBUG, ("WebMReader detected gap of %lld, %lld frames, in audio stream\n",
-      usecs.isValid() ? usecs.value() : -1,
-      tstamp_frames.value() - decoded_frames.value()));
+                       usecs.isValid() ? usecs.value() : -1,
+                       tstamp_frames.value() - decoded_frames.value()));
 #endif
     mPacketCount++;
     mAudioStartUsec = tstamp_usecs;
     mAudioFrames = 0;
   }
 
   int32_t total_frames = 0;
   for (uint32_t i = 0; i < count; ++i) {
@@ -666,47 +666,45 @@ bool WebMReader::DecodeAudioPacket(neste
           mSkip -= frames;
           LOG(PR_LOG_DEBUG, ("Opus decoder skipping %d frames"
                              " (whole packet)", frames));
           return true;
         }
         int32_t keepFrames = frames - skipFrames;
         int samples = keepFrames * channels;
         nsAutoArrayPtr<AudioDataValue> trimBuffer(new AudioDataValue[samples]);
-        for (int i = 0; i < samples; i++)
-          trimBuffer[i] = buffer[skipFrames*channels + i];
+        PodCopy(trimBuffer.get(), buffer.get() + skipFrames*channels, samples);
         startTime = startTime + FramesToUsecs(skipFrames, rate);
         frames = keepFrames;
         buffer = trimBuffer;
 
         mSkip -= skipFrames;
         LOG(PR_LOG_DEBUG, ("Opus decoder skipping %d frames", skipFrames));
       }
 
       int64_t discardPadding = 0;
       r = nestegg_packet_discard_padding(aPacket, &discardPadding);
       if (discardPadding > 0) {
-        CheckedInt64 discardFrames = UsecsToFrames(discardPadding * NS_PER_USEC, rate);
+        CheckedInt64 discardFrames = UsecsToFrames(discardPadding / NS_PER_USEC, rate);
         if (!discardFrames.isValid()) {
           NS_WARNING("Int overflow in DiscardPadding");
           return false;
         }
-        int32_t keepFrames = frames - discardFrames.value();
-        if (keepFrames > 0) {
-          int samples = keepFrames * channels;
-          nsAutoArrayPtr<AudioDataValue> trimBuffer(new AudioDataValue[samples]);
-          for (int i = 0; i < samples; i++)
-            trimBuffer[i] = buffer[i];
-          frames = keepFrames;
-          buffer = trimBuffer;
-        } else {
+        if (discardFrames.value() >= frames) {
           LOG(PR_LOG_DEBUG, ("Opus decoder discarding whole packet"
-                             " ( %d frames) as padding", frames));
+                             " (%d frames) as padding (%lld discarded)",
+                             frames, discardFrames.value()));
           return true;
         }
+        int32_t keepFrames = frames - discardFrames.value();
+        int32_t samples = keepFrames * channels;
+        nsAutoArrayPtr<AudioDataValue> trimBuffer(new AudioDataValue[samples]);
+        PodCopy(trimBuffer.get(), buffer.get(), samples);
+        frames = keepFrames;
+        buffer = trimBuffer;
       }
 
       // Apply the header gain if one was specified.
 #ifdef MOZ_SAMPLE_TYPE_FLOAT32
       if (mOpusParser->mGain != 1.0f) {
         float gain = mOpusParser->mGain;
         int samples = frames * channels;
         for (int i = 0; i < samples; i++) {
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -3400,16 +3400,17 @@ class IDLArgument(IDLObjectWithIdentifie
         self.variadic = variadic
         self.dictionaryMember = dictionaryMember
         self._isComplete = False
         self.enforceRange = False
         self.clamp = False
         self._allowTreatNonCallableAsNull = False
 
         assert not variadic or optional
+        assert not variadic or not defaultValue
 
     def addExtendedAttributes(self, attrs):
         attrs = self.checkForStringHandlingExtendedAttributes(
             attrs,
             isDictionaryMember=self.dictionaryMember,
             isOptional=self.optional)
         for attribute in attrs:
             identifier = attribute.identifier()
@@ -3448,19 +3449,19 @@ class IDLArgument(IDLObjectWithIdentifie
             type = self.type.complete(scope)
             assert not isinstance(type, IDLUnresolvedType)
             assert not isinstance(type, IDLTypedefType)
             assert not isinstance(type.name, IDLUnresolvedIdentifier)
             self.type = type
 
         if ((self.type.isDictionary() or
              self.type.isUnion() and self.type.unroll().hasDictionaryType) and
-            self.optional and not self.defaultValue):
-            # Default optional dictionaries to null, for simplicity,
-            # so the codegen doesn't have to special-case this.
+            self.optional and not self.defaultValue and not self.variadic):
+            # Default optional non-variadic dictionaries to null,
+            # for simplicity, so the codegen doesn't have to special-case this.
             self.defaultValue = IDLNullValue(self.location)
         elif self.type.isAny():
             assert (self.defaultValue is None or
                     isinstance(self.defaultValue, IDLNullValue))
             # optional 'any' values always have a default value
             if self.optional and not self.defaultValue and not self.variadic:
                 # Set the default value to undefined, for simplicity, so the
                 # codegen doesn't have to special-case this.
--- a/dom/bindings/test/TestBindingHeader.h
+++ b/dom/bindings/test/TestBindingHeader.h
@@ -709,16 +709,17 @@ public:
   void PassOtherDictionary(const GrandparentDict&);
   void PassSequenceOfDictionaries(JSContext*, const Sequence<Dict>&);
   void PassMozMapOfDictionaries(const MozMap<GrandparentDict>&);
   void PassDictionaryOrLong(JSContext*, const Dict&);
   void PassDictionaryOrLong(int32_t);
   void PassDictContainingDict(JSContext*, const DictContainingDict&);
   void PassDictContainingSequence(JSContext*, const DictContainingSequence&);
   void ReceiveDictContainingSequence(JSContext*, DictContainingSequence&);
+  void PassVariadicDictionary(JSContext*, const Sequence<Dict>&);
 
   // Typedefs
   void ExerciseTypedefInterfaces1(TestInterface&);
   already_AddRefed<TestInterface> ExerciseTypedefInterfaces2(TestInterface*);
   void ExerciseTypedefInterfaces3(TestInterface&);
 
   // Static methods and attributes
   static void StaticMethod(const GlobalObject&, bool);
--- a/dom/bindings/test/TestCodeGen.webidl
+++ b/dom/bindings/test/TestCodeGen.webidl
@@ -681,16 +681,17 @@ interface TestInterface {
   // No support for nullable dictionaries inside a sequence (nor should there be)
   //  void passSequenceOfNullableDictionaries(sequence<Dict?> x);
   void passDictionaryOrLong(optional Dict x);
   void passDictionaryOrLong(long x);
 
   void passDictContainingDict(optional DictContainingDict arg);
   void passDictContainingSequence(optional DictContainingSequence arg);
   DictContainingSequence receiveDictContainingSequence();
+  void passVariadicDictionary(Dict... arg);
 
   // EnforceRange/Clamp tests
   void dontEnforceRangeOrClamp(byte arg);
   void doEnforceRange([EnforceRange] byte arg);
   void doClamp([Clamp] byte arg);
   [EnforceRange] attribute byte enforcedByte;
   [Clamp] attribute byte clampedByte;
 
--- a/dom/bindings/test/TestExampleGen.webidl
+++ b/dom/bindings/test/TestExampleGen.webidl
@@ -545,16 +545,17 @@ interface TestExampleInterface {
   // No support for nullable dictionaries inside a sequence (nor should there be)
   //  void passSequenceOfNullableDictionaries(sequence<Dict?> x);
   void passDictionaryOrLong(optional Dict x);
   void passDictionaryOrLong(long x);
 
   void passDictContainingDict(optional DictContainingDict arg);
   void passDictContainingSequence(optional DictContainingSequence arg);
   DictContainingSequence receiveDictContainingSequence();
+  void passVariadicDictionary(Dict... arg);
 
   // EnforceRange/Clamp tests
   void dontEnforceRangeOrClamp(byte arg);
   void doEnforceRange([EnforceRange] byte arg);
   void doClamp([Clamp] byte arg);
   [EnforceRange] attribute byte enforcedByte;
   [Clamp] attribute byte clampedByte;
 
--- a/dom/bindings/test/TestJSImplGen.webidl
+++ b/dom/bindings/test/TestJSImplGen.webidl
@@ -131,19 +131,18 @@ interface TestJSImplInterface {
   [LenientFloat]
   attribute double lenientDoubleAttr;
 
   // Castable interface types
   // XXXbz add tests for throwing versions of all the castable interface stuff
   TestJSImplInterface receiveSelf();
   TestJSImplInterface? receiveNullableSelf();
 
-  // Callback interface ignores 'resultNotAddRefed'. See bug 843272.
-  //TestJSImplInterface receiveWeakSelf();
-  //TestJSImplInterface? receiveWeakNullableSelf();
+  TestJSImplInterface receiveWeakSelf();
+  TestJSImplInterface? receiveWeakNullableSelf();
 
   // A version to test for casting to TestJSImplInterface&
   void passSelf(TestJSImplInterface arg);
   void passNullableSelf(TestJSImplInterface? arg);
   attribute TestJSImplInterface nonNullSelf;
   attribute TestJSImplInterface? nullableSelf;
   [Cached, Pure]
   readonly attribute TestJSImplInterface cachedSelf;
@@ -165,50 +164,47 @@ interface TestJSImplInterface {
   [NewObject]
   sequence<TestNonWrapperCacheInterface>? receiveNonWrapperCacheInterfaceNullableSequence();
   [NewObject]
   sequence<TestNonWrapperCacheInterface?>? receiveNullableNonWrapperCacheInterfaceNullableSequence();
 
   // Non-castable interface types
   IndirectlyImplementedInterface receiveOther();
   IndirectlyImplementedInterface? receiveNullableOther();
-  // Callback interface ignores 'resultNotAddRefed'. See bug 843272.
-  //IndirectlyImplementedInterface receiveWeakOther();
-  //IndirectlyImplementedInterface? receiveWeakNullableOther();
+  IndirectlyImplementedInterface receiveWeakOther();
+  IndirectlyImplementedInterface? receiveWeakNullableOther();
 
   void passOther(IndirectlyImplementedInterface arg);
   void passNullableOther(IndirectlyImplementedInterface? arg);
   attribute IndirectlyImplementedInterface nonNullOther;
   attribute IndirectlyImplementedInterface? nullableOther;
   // Optional arguments
   void passOptionalOther(optional IndirectlyImplementedInterface? arg);
   void passOptionalNonNullOther(optional IndirectlyImplementedInterface arg);
   void passOptionalOtherWithDefault(optional IndirectlyImplementedInterface? arg = null);
 
   // External interface types
   TestExternalInterface receiveExternal();
   TestExternalInterface? receiveNullableExternal();
-  // Callback interface ignores 'resultNotAddRefed'. See bug 843272.
-  //TestExternalInterface receiveWeakExternal();
-  //TestExternalInterface? receiveWeakNullableExternal();
+  TestExternalInterface receiveWeakExternal();
+  TestExternalInterface? receiveWeakNullableExternal();
   void passExternal(TestExternalInterface arg);
   void passNullableExternal(TestExternalInterface? arg);
   attribute TestExternalInterface nonNullExternal;
   attribute TestExternalInterface? nullableExternal;
   // Optional arguments
   void passOptionalExternal(optional TestExternalInterface? arg);
   void passOptionalNonNullExternal(optional TestExternalInterface arg);
   void passOptionalExternalWithDefault(optional TestExternalInterface? arg = null);
 
   // Callback interface types
   TestCallbackInterface receiveCallbackInterface();
   TestCallbackInterface? receiveNullableCallbackInterface();
-  // Callback interface ignores 'resultNotAddRefed'. See bug 843272.
-  //TestCallbackInterface receiveWeakCallbackInterface();
-  //TestCallbackInterface? receiveWeakNullableCallbackInterface();
+  TestCallbackInterface receiveWeakCallbackInterface();
+  TestCallbackInterface? receiveWeakNullableCallbackInterface();
   void passCallbackInterface(TestCallbackInterface arg);
   void passNullableCallbackInterface(TestCallbackInterface? arg);
   attribute TestCallbackInterface nonNullCallbackInterface;
   attribute TestCallbackInterface? nullableCallbackInterface;
   // Optional arguments
   void passOptionalCallbackInterface(optional TestCallbackInterface? arg);
   void passOptionalNonNullCallbackInterface(optional TestCallbackInterface arg);
   void passOptionalCallbackInterfaceWithDefault(optional TestCallbackInterface? arg = null);
@@ -565,16 +561,17 @@ interface TestJSImplInterface {
   // No support for nullable dictionaries inside a sequence (nor should there be)
   //  void passSequenceOfNullableDictionaries(sequence<Dict?> x);
   void passDictionaryOrLong(optional Dict x);
   void passDictionaryOrLong(long x);
 
   void passDictContainingDict(optional DictContainingDict arg);
   void passDictContainingSequence(optional DictContainingSequence arg);
   DictContainingSequence receiveDictContainingSequence();
+  void passVariadicDictionary(Dict... arg);
 
   // EnforceRange/Clamp tests
   void dontEnforceRangeOrClamp(byte arg);
   void doEnforceRange([EnforceRange] byte arg);
   void doClamp([Clamp] byte arg);
   [EnforceRange] attribute byte enforcedByte;
   [Clamp] attribute byte clampedByte;
 
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -140,24 +140,30 @@ public:
 
 
     // ------------------------------------------------------------------------
     // Multiple Render Targets - WebGL2ContextMRTs.cpp
     // TODO(djg): Implemented in WebGLContext
 /*
     void DrawBuffers(const dom::Sequence<GLenum>& buffers);
 */
+
+    void ClearBufferiv_base(GLenum buffer, GLint drawbuffer, const GLint* value);
+    void ClearBufferuiv_base(GLenum buffer, GLint drawbuffer, const GLuint* value);
+    void ClearBufferfv_base(GLenum buffer, GLint drawbuffer, const GLfloat* value);
+
     void ClearBufferiv(GLenum buffer, GLint drawbuffer, const dom::Int32Array& value);
     void ClearBufferiv(GLenum buffer, GLint drawbuffer, const dom::Sequence<GLint>& value);
     void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const dom::Uint32Array& value);
     void ClearBufferuiv(GLenum buffer, GLint drawbuffer, const dom::Sequence<GLuint>& value);
     void ClearBufferfv(GLenum buffer, GLint drawbuffer, const dom::Float32Array& value);
     void ClearBufferfv(GLenum buffer, GLint drawbuffer, const dom::Sequence<GLfloat>& value);
     void ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
 
+    bool ValidateClearBuffer(const char* info, GLenum buffer, GLint drawbuffer, size_t elemCount);
 
     // -------------------------------------------------------------------------
     // Query Objects - WebGL2ContextQueries.cpp
     // TODO(djg): Implemented in WebGLContext
     /* already_AddRefed<WebGLQuery> CreateQuery();
     void DeleteQuery(WebGLQuery* query);
     bool IsQuery(WebGLQuery* query);
     void BeginQuery(GLenum target, WebGLQuery* query);
--- a/dom/canvas/WebGL2ContextMRTs.cpp
+++ b/dom/canvas/WebGL2ContextMRTs.cpp
@@ -4,49 +4,139 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "WebGL2Context.h"
 #include "GLContext.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
+bool WebGL2Context::ValidateClearBuffer(const char* info, GLenum buffer, GLint drawbuffer, size_t elemCount)
+{
+    size_t requiredElements = -1;
+    GLint maxDrawbuffer = -1;
+    switch (buffer) {
+      case LOCAL_GL_COLOR:
+      case LOCAL_GL_FRONT:
+      case LOCAL_GL_BACK:
+      case LOCAL_GL_LEFT:
+      case LOCAL_GL_RIGHT:
+      case LOCAL_GL_FRONT_AND_BACK:
+          requiredElements = 4;
+          maxDrawbuffer = mGLMaxDrawBuffers - 1;
+          break;
+
+      case LOCAL_GL_DEPTH:
+      case LOCAL_GL_STENCIL:
+          requiredElements = 1;
+          maxDrawbuffer = 0;
+          break;
+
+      default:
+          ErrorInvalidEnumInfo(info, buffer);
+          return false;
+    }
+
+    if (drawbuffer < 0 || drawbuffer > maxDrawbuffer) {
+        ErrorInvalidValue("%s: invalid drawbuffer %d. This buffer only supports drawbuffer values between 0 and %d",
+                          info, drawbuffer, maxDrawbuffer);
+        return false;
+    }
+
+    if (elemCount < requiredElements) {
+        ErrorInvalidValue("%s: Not enough elements. Require %u. Given %u.",
+                          info, requiredElements, elemCount);
+        return false;
+    }
+    return true;
+}
+
+// Common base functionality. These a good candidates to be moved into WebGLContextUnchecked.cpp
+void
+WebGL2Context::ClearBufferiv_base(GLenum buffer, GLint drawbuffer, const GLint* value)
+{
+    MakeContextCurrent();
+    gl->fClearBufferiv(buffer, drawbuffer, value);
+}
+
+void
+WebGL2Context::ClearBufferuiv_base(GLenum buffer, GLint drawbuffer, const GLuint* value)
+{
+    MakeContextCurrent();
+    gl->fClearBufferuiv(buffer, drawbuffer, value);
+}
+
+void
+WebGL2Context::ClearBufferfv_base(GLenum buffer, GLint drawbuffer, const GLfloat* value)
+{
+    MakeContextCurrent();
+    gl->fClearBufferfv(buffer, drawbuffer, value);
+}
+
 void
 WebGL2Context::ClearBufferiv(GLenum buffer, GLint drawbuffer, const dom::Int32Array& value)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (!ValidateClearBuffer("clearBufferiv", buffer, drawbuffer, value.Length())) {
+        return;
+    }
+
+    ClearBufferiv_base(buffer, drawbuffer, value.Data());
 }
 
 void
 WebGL2Context::ClearBufferiv(GLenum buffer, GLint drawbuffer, const dom::Sequence<GLint>& value)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (!ValidateClearBuffer("clearBufferiv", buffer, drawbuffer, value.Length())) {
+        return;
+    }
+
+    ClearBufferiv_base(buffer, drawbuffer, value.Elements());
 }
 
 void
 WebGL2Context::ClearBufferuiv(GLenum buffer, GLint drawbuffer, const dom::Uint32Array& value)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (!ValidateClearBuffer("clearBufferuiv", buffer, drawbuffer, value.Length())) {
+        return;
+    }
+
+    ClearBufferuiv_base(buffer, drawbuffer, value.Data());
 }
 
 void
 WebGL2Context::ClearBufferuiv(GLenum buffer, GLint drawbuffer, const dom::Sequence<GLuint>& value)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (!ValidateClearBuffer("clearBufferuiv", buffer, drawbuffer, value.Length())) {
+        return;
+    }
+
+    ClearBufferuiv_base(buffer, drawbuffer, value.Elements());
 }
 
 void
 WebGL2Context::ClearBufferfv(GLenum buffer, GLint drawbuffer, const dom::Float32Array& value)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (!ValidateClearBuffer("clearBufferfv", buffer, drawbuffer, value.Length())) {
+        return;
+    }
+
+    ClearBufferfv_base(buffer, drawbuffer, value.Data());
 }
 
 void
 WebGL2Context::ClearBufferfv(GLenum buffer, GLint drawbuffer, const dom::Sequence<GLfloat>& value)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (!ValidateClearBuffer("clearBufferfv", buffer, drawbuffer, value.Length())) {
+        return;
+    }
+
+    ClearBufferfv_base(buffer, drawbuffer, value.Elements());
 }
 
 void
 WebGL2Context::ClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil)
 {
-    MOZ_CRASH("Not Implemented.");
+    if (buffer != LOCAL_GL_DEPTH_STENCIL) {
+        return ErrorInvalidEnumInfo("clearBufferfi: buffer", buffer);
+    }
+    MakeContextCurrent();
+    gl->fClearBufferfi(buffer, drawbuffer, depth, stencil);
 }
--- a/gfx/2d/DrawTargetCairo.cpp
+++ b/gfx/2d/DrawTargetCairo.cpp
@@ -1383,16 +1383,23 @@ DrawTargetCairo::CreateSimilarDrawTarget
   gfxCriticalError() << "Failed to create similar cairo surface! Size: " << aSize << " Status: " << cairo_surface_status(similar);
 
   return nullptr;
 }
 
 bool
 DrawTargetCairo::InitAlreadyReferenced(cairo_surface_t* aSurface, const IntSize& aSize, SurfaceFormat* aFormat)
 {
+  if (cairo_surface_status(aSurface)) {
+    gfxCriticalError() << "Attempt to create DrawTarget for invalid surface. "
+                       << aSize << " Cairo Status: " << cairo_surface_status(aSurface);
+    cairo_surface_destroy(aSurface);
+    return false;
+  }
+
   mContext = cairo_create(aSurface);
   mSurface = aSurface;
   mSize = aSize;
   mFormat = aFormat ? *aFormat : CairoContentToGfxFormat(cairo_surface_get_content(aSurface));
 
   // Cairo image surface have a bug where they will allocate a mask surface (for clipping)
   // the size of the clip extents, and don't take the surface extents into account.
   // Add a manual clip to the surface extents to prevent this.
--- a/gfx/2d/DrawTargetD2D.cpp
+++ b/gfx/2d/DrawTargetD2D.cpp
@@ -1343,31 +1343,31 @@ bool
 DrawTargetD2D::Init(const IntSize &aSize, SurfaceFormat aFormat)
 {
   HRESULT hr;
 
   mSize = aSize;
   mFormat = aFormat;
 
   if (!Factory::GetDirect3D10Device()) {
-    gfxDebug() << "Failed to Init Direct2D DrawTarget (No D3D10 Device set.)";
+    gfxCriticalError() << "Failed to Init Direct2D DrawTarget (No D3D10 Device set.)";
     return false;
   }
   mDevice = Factory::GetDirect3D10Device();
 
   CD3D10_TEXTURE2D_DESC desc(DXGIFormat(aFormat),
                              mSize.width,
                              mSize.height,
                              1, 1);
   desc.BindFlags = D3D10_BIND_RENDER_TARGET | D3D10_BIND_SHADER_RESOURCE;
 
   hr = mDevice->CreateTexture2D(&desc, nullptr, byRef(mTexture));
 
   if (FAILED(hr)) {
-    gfxDebug() << "Failed to init Direct2D DrawTarget. Size: " << mSize << " Code: " << hr;
+    gfxCriticalError() << "Failed to init Direct2D DrawTarget. Size: " << mSize << " Code: " << hr;
     return false;
   }
 
   if (!InitD2DRenderTarget()) {
     return false;
   }
 
   mRT->Clear(D2D1::ColorF(0, 0));
@@ -1388,17 +1388,17 @@ DrawTargetD2D::Init(ID3D10Texture2D *aTe
   }
 
   RefPtr<ID3D10Device> device;
   mTexture->GetDevice(byRef(device));
 
   hr = device->QueryInterface((ID3D10Device1**)byRef(mDevice));
 
   if (FAILED(hr)) {
-    gfxWarning() << "Failed to get D3D10 device from texture.";
+    gfxCriticalError() << "Failed to get D3D10 device from texture.";
     return false;
   }
 
   D3D10_TEXTURE2D_DESC desc;
   mTexture->GetDesc(&desc);
   mSize.width = desc.Width;
   mSize.height = desc.Height;
 
@@ -1507,16 +1507,17 @@ DrawTargetD2D::PopCachedLayer(ID2D1Rende
   aRT->PopLayer();
   mCurrentCachedLayer--;
 }
 
 bool
 DrawTargetD2D::InitD2DRenderTarget()
 {
   if (!factory()) {
+    gfxCriticalError() << "No valid D2D factory available.";
     return false;
   }
 
   mRT = CreateRTForTexture(mTexture, mFormat);
 
   if (!mRT) {
     return false;
   }
@@ -1955,17 +1956,17 @@ DrawTargetD2D::CreateRTForTexture(ID3D10
   HRESULT hr;
 
   RefPtr<IDXGISurface> surface;
   RefPtr<ID2D1RenderTarget> rt;
 
   hr = aTexture->QueryInterface((IDXGISurface**)byRef(surface));
 
   if (FAILED(hr)) {
-    gfxWarning() << "Failed to QI texture to surface.";
+    gfxCriticalError() << "Failed to QI texture to surface.";
     return nullptr;
   }
 
   D3D10_TEXTURE2D_DESC desc;
   aTexture->GetDesc(&desc);
 
   D2D1_ALPHA_MODE alphaMode = D2D1_ALPHA_MODE_PREMULTIPLIED;
 
@@ -1973,17 +1974,17 @@ DrawTargetD2D::CreateRTForTexture(ID3D10
     alphaMode = D2D1_ALPHA_MODE_IGNORE;
   }
 
   D2D1_RENDER_TARGET_PROPERTIES props =
     D2D1::RenderTargetProperties(D2D1_RENDER_TARGET_TYPE_DEFAULT, D2D1::PixelFormat(desc.Format, alphaMode));
   hr = factory()->CreateDxgiSurfaceRenderTarget(surface, props, byRef(rt));
 
   if (FAILED(hr)) {
-    gfxWarning() << "Failed to create D2D render target for texture.";
+    gfxCriticalError() << "Failed to create D2D render target for texture.";
     return nullptr;
   }
 
   return rt.forget();
 }
 
 void
 DrawTargetD2D::EnsureViews()
--- a/gfx/gl/GLContext.cpp
+++ b/gfx/gl/GLContext.cpp
@@ -1007,16 +1007,33 @@ GLContext::InitWithPrefix(const char *pr
             if (!LoadSymbols(useCore ? coreSymbols : extSymbols, trygl, prefix)) {
                 NS_ERROR("GL supports query objects iv getter without supplying its function.");
 
                 MarkUnsupported(GLFeature::get_query_object_iv);
                 ClearSymbols(coreSymbols);
             }
         }
 
+        if (IsSupported(GLFeature::clear_buffers)) {
+            SymLoadStruct clearBuffersSymbols[] = {
+                { (PRFuncPtr*) &mSymbols.fClearBufferfi,  { "ClearBufferfi",  nullptr } },
+                { (PRFuncPtr*) &mSymbols.fClearBufferfv,  { "ClearBufferfv",  nullptr } },
+                { (PRFuncPtr*) &mSymbols.fClearBufferiv,  { "ClearBufferiv",  nullptr } },
+                { (PRFuncPtr*) &mSymbols.fClearBufferuiv, { "ClearBufferuiv", nullptr } },
+                END_SYMBOLS
+            };
+
+            if (!LoadSymbols(clearBuffersSymbols, trygl, prefix)) {
+                NS_ERROR("GL supports clear_buffers without supplying its functions.");
+
+                MarkUnsupported(GLFeature::clear_buffers);
+                ClearSymbols(clearBuffersSymbols);
+            }
+        }
+
         if (IsSupported(GLFeature::draw_buffers)) {
             SymLoadStruct coreSymbols[] = {
                 { (PRFuncPtr*) &mSymbols.fDrawBuffers, { "DrawBuffers", nullptr } },
                 END_SYMBOLS
             };
 
             SymLoadStruct extSymbols[] = {
                 { (PRFuncPtr*) &mSymbols.fDrawBuffers, { "DrawBuffersARB", "DrawBuffersEXT", nullptr } },
--- a/gfx/gl/GLContext.h
+++ b/gfx/gl/GLContext.h
@@ -75,16 +75,17 @@ namespace mozilla {
 }
 
 namespace mozilla {
 namespace gl {
 
 MOZ_BEGIN_ENUM_CLASS(GLFeature)
     bind_buffer_offset,
     blend_minmax,
+    clear_buffers,
     depth_texture,
     draw_buffers,
     draw_instanced,
     draw_range_elements,
     element_index_uint,
     ES2_compatibility,
     ES3_compatibility,
     frag_color_float,
@@ -941,16 +942,40 @@ private:
 
 public:
     void fClear(GLbitfield mask) {
         BeforeGLDrawCall();
         raw_fClear(mask);
         AfterGLDrawCall();
     }
 
+    void fClearBufferfi(GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil) {
+        BEFORE_GL_CALL;
+        mSymbols.fClearBufferfi(buffer, drawbuffer, depth, stencil);
+        AFTER_GL_CALL;
+    }
+
+    void fClearBufferfv(GLenum buffer, GLint drawbuffer, const GLfloat* value) {
+        BEFORE_GL_CALL;
+        mSymbols.fClearBufferfv(buffer, drawbuffer, value);
+        AFTER_GL_CALL;
+    }
+
+    void fClearBufferiv(GLenum buffer, GLint drawbuffer, const GLint* value) {
+        BEFORE_GL_CALL;
+        mSymbols.fClearBufferiv(buffer, drawbuffer, value);
+        AFTER_GL_CALL;
+    }
+
+    void fClearBufferuiv(GLenum buffer, GLint drawbuffer, const GLuint* value) {
+        BEFORE_GL_CALL;
+        mSymbols.fClearBufferuiv(buffer, drawbuffer, value);
+        AFTER_GL_CALL;
+    }
+
     void fClearColor(GLclampf r, GLclampf g, GLclampf b, GLclampf a) {
         BEFORE_GL_CALL;
         mSymbols.fClearColor(r, g, b, a);
         AFTER_GL_CALL;
     }
 
     void fClearStencil(GLint s) {
         BEFORE_GL_CALL;
--- a/gfx/gl/GLContextFeatures.cpp
+++ b/gfx/gl/GLContextFeatures.cpp
@@ -67,16 +67,25 @@ static const FeatureInfo sFeatureInfoArr
         300, // OpenGL ES version
         GLContext::Extension_None,
         {
             GLContext::EXT_blend_minmax,
             GLContext::Extensions_End
         }
     },
     {
+        "clear_buffers",
+        300, // OpenGL version
+        300, // OpenGL ES version
+        GLContext::Extension_None,
+        {
+            GLContext::Extensions_End
+        }
+    },
+    {
         "depth_texture",
         200, // OpenGL version
         300, // OpenGL ES version
         GLContext::Extension_None,
         {
             GLContext::ARB_depth_texture,
             GLContext::OES_depth_texture,
             // Intentionally avoid putting ANGLE_depth_texture here,
--- a/gfx/gl/GLContextSymbols.h
+++ b/gfx/gl/GLContextSymbols.h
@@ -60,16 +60,24 @@ struct GLContextSymbols
     typedef void (GLAPIENTRY * PFNGLBLENDFUNCSEPARATEPROC) (GLenum sfactorRGB, GLenum dfactorRGB, GLenum sfactorAlpha, GLenum dfactorAlpha);
     PFNGLBLENDFUNCSEPARATEPROC fBlendFuncSeparate;
     typedef void (GLAPIENTRY * PFNGLBUFFERDATAPROC) (GLenum target, GLsizeiptr size, const GLvoid* data, GLenum usage);
     PFNGLBUFFERDATAPROC fBufferData;
     typedef void (GLAPIENTRY * PFNGLBUFFERSUBDATAPROC) (GLenum target, GLintptr offset, GLsizeiptr size, const GLvoid* data);
     PFNGLBUFFERSUBDATAPROC fBufferSubData;
     typedef void (GLAPIENTRY * PFNGLCLEARPROC) (GLbitfield);
     PFNGLCLEARPROC fClear;
+    typedef void (GLAPIENTRY * PFNGLCLEARBUFFERFIPROC) (GLenum buffer, GLint drawbuffer, GLfloat depth, GLint stencil);
+    PFNGLCLEARBUFFERFIPROC fClearBufferfi;
+    typedef void (GLAPIENTRY * PFNGLCLEARBUFFERFVPROC) (GLenum buffer, GLint drawbuffer, const GLfloat* value);
+    PFNGLCLEARBUFFERFVPROC fClearBufferfv;
+    typedef void (GLAPIENTRY * PFNGLCLEARBUFFERIVPROC) (GLenum buffer, GLint drawbuffer, const GLint* value);
+    PFNGLCLEARBUFFERIVPROC fClearBufferiv;
+    typedef void (GLAPIENTRY * PFNGLCLEARBUFFERUIVPROC) (GLenum buffer, GLint drawbuffer, const GLuint* value);
+    PFNGLCLEARBUFFERUIVPROC fClearBufferuiv;
     typedef void (GLAPIENTRY * PFNGLCLEARCOLORPROC) (GLclampf, GLclampf, GLclampf, GLclampf);
     PFNGLCLEARCOLORPROC fClearColor;
     typedef void (GLAPIENTRY * PFNGLCLEARSTENCILPROC) (GLint);
     PFNGLCLEARSTENCILPROC fClearStencil;
     typedef void (GLAPIENTRY * PFNGLCOLORMASKPROC) (realGLboolean red, realGLboolean green, realGLboolean blue, realGLboolean alpha);
     PFNGLCOLORMASKPROC fColorMask;
     typedef void (GLAPIENTRY * PFNGLCOMPRESSEDTEXIMAGE2D) (GLenum target, GLint level, GLenum internalformat, GLsizei width, GLsizei height, GLint border, GLsizei imageSize, const GLvoid *pixels);
     PFNGLCOMPRESSEDTEXIMAGE2D fCompressedTexImage2D;
--- a/image/src/RasterImage.cpp
+++ b/image/src/RasterImage.cpp
@@ -247,17 +247,18 @@ public:
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     nsRefPtr<RasterImage> image = weakImage.get();
     if (image) {
       image->UnlockImage();
     }
 
-    if (DiscardingEnabled() && dstFrame) {
+    if (dstFrame) {
+      dstFrame->SetOptimizable();
       dstFrame->SetDiscardable();
     }
 
     // Release everything except dstFrame, which we keep around for RasterImage
     // to retrieve.
     srcRef.reset();
     dstRef.reset();
   }
@@ -1417,31 +1418,27 @@ RasterImage::DecodingComplete()
   // We now have one of the qualifications for discarding. Re-evaluate.
   if (CanDiscard()) {
     NS_ABORT_IF_FALSE(!DiscardingActive(),
                       "We shouldn't have been discardable before this");
     rv = DiscardTracker::Reset(&mDiscardTrackerNode);
     CONTAINER_ENSURE_SUCCESS(rv);
   }
 
-  // If there's only 1 frame, optimize it. Optimizing animated images
+  // If there's only 1 frame, mark it as optimizable. Optimizing animated images
   // is not supported.
   //
   // We don't optimize the frame for multipart images because we reuse
   // the frame.
   if ((GetNumFrames() == 1) && !mMultipart) {
-    // CanForciblyDiscard is used instead of CanForciblyDiscardAndRedecode
-    // because we know decoding is complete at this point and this is not
-    // an animation
     nsRefPtr<imgFrame> firstFrame = mFrameBlender.RawGetFrame(0);
+    firstFrame->SetOptimizable();
     if (DiscardingEnabled() && CanForciblyDiscard()) {
       firstFrame->SetDiscardable();
     }
-    rv = firstFrame->Optimize();
-    NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Double-buffer our frame in the multipart case, since we'll start decoding
   // into the first frame again immediately and this produces severe tearing.
   if (mMultipart) {
     if (GetNumFrames() == 1) {
       mMultipartDecodedFrame = mFrameBlender.SwapFrame(GetCurrentFrameIndex(),
                                                        mMultipartDecodedFrame);
@@ -2602,16 +2599,21 @@ void
 RasterImage::RequestScale(imgFrame* aFrame, nsIntSize aSize)
 {
   // We can't store more than one scaled version of an image right now, so if
   // there's more than one instance of this image, bail.
   if (mLockCount != 1) {
     return;
   }
 
+  // We don't scale frames which aren't fully decoded.
+  if (!aFrame->ImageComplete()) {
+    return;
+  }
+
   // We also can't scale if we can't lock the image data for this frame.
   RawAccessFrameRef frameRef = aFrame->RawAccessRef();
   if (!frameRef) {
     return;
   }
 
   // If we have an outstanding request, signal it to stop (if it can).
   if (mScaleRequest) {
--- a/image/src/imgFrame.cpp
+++ b/image/src/imgFrame.cpp
@@ -117,16 +117,17 @@ imgFrame::imgFrame() :
   mTimeout(100),
   mDisposalMethod(0), /* imgIContainer::kDisposeNotSpecified */
   mLockCount(0),
   mBlendMethod(1), /* imgIContainer::kBlendOver */
   mSinglePixel(false),
   mCompositingFailed(false),
   mNonPremult(false),
   mDiscardable(false),
+  mOptimizable(false),
   mInformedDiscardTracker(false)
 {
   static bool hasCheckedOptimize = false;
   if (!hasCheckedOptimize) {
     if (PR_GetEnv("MOZ_DISABLE_IMAGE_OPTIMIZE")) {
       gDisableOptimize = true;
     }
     hasCheckedOptimize = true;
@@ -293,18 +294,20 @@ imgFrame::InitWithDrawable(gfxDrawable* 
   }
 
   return NS_OK;
 }
 
 nsresult imgFrame::Optimize()
 {
   MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(mLockCount == 1,
+             "Should only optimize when holding the lock exclusively");
 
-  if (gDisableOptimize)
+  if (!mOptimizable || gDisableOptimize)
     return NS_OK;
 
   if (mPalettedImageData || mOptSurface || mSinglePixel)
     return NS_OK;
 
   // Don't do single-color opts on non-premult data.
   // Cairo doesn't support non-premult single-colors.
   if (mNonPremult)
@@ -396,16 +399,25 @@ nsresult imgFrame::Optimize()
 #endif
 
   if (mOptSurface) {
     mVBuf = nullptr;
     mVBufPtr = nullptr;
     mImageSurface = nullptr;
   }
 
+#ifdef MOZ_WIDGET_ANDROID
+  // On Android, free mImageSurface unconditionally if we're discardable.
+  // XXX(seth): We'd eventually like to do this on all platforms, but right now
+  // we'd read back from the GPU too much to make it worthwhile.
+  if (mDiscardable) {
+    mImageSurface = nullptr;
+  }
+#endif
+
   return NS_OK;
 }
 
 DrawableFrameRef
 imgFrame::DrawableRef()
 {
   return DrawableFrameRef(this);
 }
@@ -447,17 +459,17 @@ imgFrame::SurfaceForDrawing(bool        
     // Fill 'available' with whatever we've got
     if (mSinglePixel) {
       target->FillRect(ToRect(aRegion.Intersect(available).Rect()),
                        ColorPattern(mSinglePixelColor),
                        DrawOptions(1.0f, CompositionOp::OP_SOURCE));
     } else {
       SurfacePattern pattern(aSurface,
                              ExtendMode::REPEAT,
-                             ToMatrix(aContext->CurrentMatrix()));
+                             Matrix::Translation(mDecoded.x, mDecoded.y));
       target->FillRect(ToRect(aRegion.Intersect(available).Rect()), pattern);
     }
 
     RefPtr<SourceSurface> newsurf = target->Snapshot();
     return SurfaceWithFormat(new gfxSurfaceDrawable(newsurf, ThebesIntSize(size)), target->GetFormat());
   }
 
   // Not tiling, and we have a surface, so we can account for
@@ -714,52 +726,50 @@ nsresult imgFrame::LockImageData()
   mVBufPtr = mVBuf;
   return NS_OK;
 }
 
 nsresult imgFrame::UnlockImageData()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  NS_ABORT_IF_FALSE(mLockCount != 0, "Unlocking an unlocked image!");
-  if (mLockCount == 0) {
+  MOZ_ASSERT(mLockCount > 0, "Unlocking an unlocked image!");
+  if (mLockCount <= 0) {
     return NS_ERROR_FAILURE;
   }
 
+  // If we're about to become unlocked, we don't need to hold on to our data
+  // surface anymore. (But we don't need to do anything for paletted images,
+  // which don't have surfaces.)
+  if (mLockCount == 1 && !mPalettedImageData) {
+    // Convert the data surface to a GPU surface or a single color if possible.
+    // This will also release mImageSurface if possible.
+    Optimize();
+    
+    // Allow the OS to release our data surface.
+    mVBufPtr = nullptr;
+  }
+
   mLockCount--;
 
-  NS_ABORT_IF_FALSE(mLockCount >= 0, "Unbalanced locks and unlocks");
-  if (mLockCount < 0) {
-    return NS_ERROR_FAILURE;
-  }
-
-  // If we are not the last lock, there's nothing to do.
-  if (mLockCount != 0) {
-    return NS_OK;
-  }
-
-  // Paletted images don't have surfaces, so there's nothing to do.
-  if (mPalettedImageData)
-    return NS_OK;
-
-  mVBufPtr = nullptr;
-  if (mVBuf && mDiscardable) {
-    mImageSurface = nullptr;
-  }
-
   return NS_OK;
 }
 
-void imgFrame::SetDiscardable()
+void
+imgFrame::SetDiscardable()
 {
   MOZ_ASSERT(mLockCount, "Expected to be locked when SetDiscardable is called");
-  // Disabled elsewhere due to the cost of calling GetSourceSurfaceForSurface.
-#ifdef MOZ_WIDGET_ANDROID
   mDiscardable = true;
-#endif
+}
+
+void
+imgFrame::SetOptimizable()
+{
+  MOZ_ASSERT(mLockCount, "Expected to be locked when SetOptimizable is called");
+  mOptimizable = true;
 }
 
 TemporaryRef<SourceSurface>
 imgFrame::GetSurface()
 {
   if (mOptSurface) {
     if (mOptSurface->IsValid())
       return mOptSurface;
--- a/image/src/imgFrame.h
+++ b/image/src/imgFrame.h
@@ -69,18 +69,16 @@ public:
    * case.
    */
   nsresult InitWithDrawable(gfxDrawable* aDrawable,
                             const nsIntSize& aSize,
                             const SurfaceFormat aFormat,
                             GraphicsFilter aFilter,
                             uint32_t aImageFlags);
 
-  nsresult Optimize();
-
   DrawableFrameRef DrawableRef();
   RawAccessFrameRef RawAccessRef();
 
   bool Draw(gfxContext* aContext, const ImageRegion& aRegion,
             const nsIntMargin& aPadding, GraphicsFilter aFilter,
             uint32_t aImageFlags);
 
   nsresult ImageUpdated(const nsIntRect &aUpdateRect);
@@ -113,16 +111,17 @@ public:
 
   bool GetCompositingFailed() const;
   void SetCompositingFailed(bool val);
 
   nsresult LockImageData();
   nsresult UnlockImageData();
 
   void SetDiscardable();
+  void SetOptimizable();
 
   TemporaryRef<SourceSurface> GetSurface();
   TemporaryRef<DrawTarget> GetDrawTarget();
 
   Color
   SinglePixelColor()
   {
     return mSinglePixelColor;
@@ -146,16 +145,18 @@ public:
 
     return ((1 << mPaletteDepth) * sizeof(uint32_t));
   }
 
 private: // methods
 
   ~imgFrame();
 
+  nsresult Optimize();
+
   struct SurfaceWithFormat {
     nsRefPtr<gfxDrawable> mDrawable;
     SurfaceFormat mFormat;
     SurfaceWithFormat() {}
     SurfaceWithFormat(gfxDrawable* aDrawable, SurfaceFormat aFormat)
      : mDrawable(aDrawable), mFormat(aFormat) {}
     bool IsValid() { return !!mDrawable; }
   };
@@ -200,16 +201,17 @@ private: // data
 
   SurfaceFormat mFormat;
   uint8_t      mPaletteDepth;
   int8_t       mBlendMethod;
   bool mSinglePixel;
   bool mCompositingFailed;
   bool mNonPremult;
   bool mDiscardable;
+  bool mOptimizable;
 
   /** Have we called DiscardTracker::InformAllocation()? */
   bool mInformedDiscardTracker;
 
   friend class DrawableFrameRef;
   friend class RawAccessFrameRef;
 };
 
--- a/image/test/reftest/gif/reftest.list
+++ b/image/test/reftest/gif/reftest.list
@@ -17,16 +17,19 @@
 random == delaytest.html?transparent-animation.gif transparent-animation-finalframe.gif # incorrect timing dependence (bug 558678)
 
 # test for bug 641198
 skip-if(B2G) random-if(Android) == test_bug641198.html animation2a-finalframe.gif # bug 773482
 
 # Bug 1062886: a gif with a single color and an offset
 == one-color-offset.gif one-color-offset-ref.gif
 
+# Bug 1068230
+== tile-transform.html tile-transform-ref.html
+
 # webcam-simulacrum.mgif is a hand-edited file containing red.gif and blue.gif,
 # concatenated together with the relevant headers for
 # multipart/x-mixed-replace. Specifically, with the headers in
 # webcam-simulacrum.mjpg^headers^, the web browser will get the following:
 #
 # HTTP 200 OK
 # Content-Type: multipart/x-mixed-replace;boundary=BOUNDARYOMG
 # 
new file mode 100644
--- /dev/null
+++ b/image/test/reftest/gif/tile-transform-ref.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1068230
+-->
+<html>
+<head>
+  <title>Intermediate surface should be transformed correctly when tiling an image</title>
+</head>
+<body>
+<button style="margin: 10px; padding: 10px; border: none; background: url('tiletest-ref.png');"></button>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/image/test/reftest/gif/tile-transform.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1068230
+-->
+<html>
+<head>
+  <title>Intermediate surface should be transformed correctly when tiling an image</title>
+</head>
+<body>
+<button style="margin: 10px; padding: 10px; border: none; background: url('tiletest.gif');"></button>
+</body>
+</html>
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..b493899cc87a4e872d18b27927b4e40eb630bab3
GIT binary patch
literal 282
zc%17D@N?(olHy`uVBq!ia0vp^4j|0I3?%1nZ+ru!5(0ceTp1WFIleOpd|;4x&Y<vs
zLE}1u!3743;|vb_89cT#_^)ROU%`;DfFWZ#Lw-L)MLR=%14DZ`Lw^Co^bCgi@eIo&
z7&Zhj>~Lo|;J|Rgg5iPz!*vaY`w9%t#Tnl7Gkj-fuz09)251*!NswPKgTu2MX+Tbf
zr;B4q#NoHs9GMy!7+5cQx(aEwbiJ$huD*TqUwIdMnBvCC8md~IQAzrR6Eal#gw`JM
zTr}CjJ4U6dbM+AgL$5n3?}Yd}Js)X)oK)d`MyUIU*2jrIymEwoI@V2)f5Hx>>g(jV
We=vT$$h87!CxfS}pUXO@geCxKJz+Zl
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..7a04c9654a671361133f554050efda4b2c12ffc2
GIT binary patch
literal 156
zc${<hbhEHbWMoia_`<-z@tr~71B1kK289O<8rK;NE-+XeXK>ih;IW;-e?3F^3WkIQ
z3>niI^7|Po+8OE_7~0Dj`U@DQXE4l<XILJ=upxk9hdaXo2Zj?C3>OR-u4^#dS73N9
z&hVa};X6A65Gej+0Z|M(3=E7wD9^yaDybmA!5k>TK1qZ7vIW10r*LSb__R#v>y?TO
F)&TJcDXstj
--- a/layout/base/RestyleTracker.cpp
+++ b/layout/base/RestyleTracker.cpp
@@ -44,19 +44,23 @@ CollectLaterSiblings(nsISupports* aEleme
       element->HasFlag(collector->tracker->RestyleBit()) &&
       (aData->mRestyleHint & eRestyle_LaterSiblings)) {
     collector->elements->AppendElement(element);
   }
 
   return PL_DHASH_NEXT;
 }
 
+struct RestyleEnumerateData : RestyleTracker::Hints {
+  nsRefPtr<dom::Element> mElement;
+};
+
 struct RestyleCollector {
   RestyleTracker* tracker;
-  RestyleTracker::RestyleEnumerateData** restyleArrayPtr;
+  RestyleEnumerateData** restyleArrayPtr;
 };
 
 static PLDHashOperator
 CollectRestyles(nsISupports* aElement,
                 nsAutoPtr<RestyleTracker::RestyleData>& aData,
                 void* aRestyleCollector)
 {
   dom::Element* element =
@@ -86,20 +90,18 @@ CollectRestyles(nsISupports* aElement,
                (aData->mChangeHint & nsChangeHint_ReconstructFrame),
                "Why did this not get handled while processing mRestyleRoots?");
 
   // Unset the restyle bits now, so if they get readded later as we
   // process we won't clobber that adding of the bit.
   element->UnsetFlags(collector->tracker->RestyleBit() |
                       collector->tracker->RootBit());
 
-  RestyleTracker::RestyleEnumerateData** restyleArrayPtr =
-    collector->restyleArrayPtr;
-  RestyleTracker::RestyleEnumerateData* currentRestyle =
-    *restyleArrayPtr;
+  RestyleEnumerateData** restyleArrayPtr = collector->restyleArrayPtr;
+  RestyleEnumerateData* currentRestyle = *restyleArrayPtr;
   currentRestyle->mElement = element;
   currentRestyle->mRestyleHint = aData->mRestyleHint;
   currentRestyle->mChangeHint = aData->mChangeHint;
 
   // Increment to the next slot in the array
   *restyleArrayPtr = currentRestyle + 1;
 
   return PL_DHASH_NEXT;
@@ -226,18 +228,16 @@ RestyleTracker::DoProcessRestyles()
       mPendingRestyles.Clear();
 
       for (RestyleEnumerateData* currentRestyle = restylesToProcess;
            currentRestyle != lastRestyle;
            ++currentRestyle) {
         ProcessOneRestyle(currentRestyle->mElement,
                           currentRestyle->mRestyleHint,
                           currentRestyle->mChangeHint);
-
-        MOZ_ASSERT(currentRestyle->mDescendants.IsEmpty());
       }
     }
   }
 
   mRestyleManager->EndProcessingRestyles();
 }
 
 bool
--- a/layout/base/RestyleTracker.h
+++ b/layout/base/RestyleTracker.h
@@ -282,30 +282,32 @@ public:
     return mRestyleBits & ELEMENT_PENDING_RESTYLE_FLAGS;
   }
 
   // Return our ELEMENT_IS_POTENTIAL_(ANIMATION_)RESTYLE_ROOT bit
   Element::FlagsType RootBit() const {
     return mRestyleBits & ~ELEMENT_PENDING_RESTYLE_FLAGS;
   }
 
-  struct RestyleData {
+  struct Hints {
+    nsRestyleHint mRestyleHint;       // What we want to restyle
+    nsChangeHint mChangeHint;         // The minimal change hint for "self"
+  };
+
+  struct RestyleData : Hints {
     RestyleData() {
       mRestyleHint = nsRestyleHint(0);
       mChangeHint = NS_STYLE_HINT_NONE;
     }
 
     RestyleData(nsRestyleHint aRestyleHint, nsChangeHint aChangeHint) {
       mRestyleHint = aRestyleHint;
       mChangeHint = aChangeHint;
     }
 
-    nsRestyleHint mRestyleHint;       // What we want to restyle
-    nsChangeHint mChangeHint;         // The minimal change hint for "self"
-
     // Descendant elements we must check that we ended up restyling, ordered
     // with the same invariant as mRestyleRoots.  The elements here are those
     // that we called AddPendingRestyle for and found the element this is
     // the RestyleData for as its nearest restyle root.
     nsTArray<nsRefPtr<Element>> mDescendants;
   };
 
   /**
@@ -335,20 +337,16 @@ public:
   void AddRestyleRootsIfAwaitingRestyle(
                                   const nsTArray<nsRefPtr<Element>>& aElements);
 
   /**
    * The document we're associated with.
    */
   inline nsIDocument* Document() const;
 
-  struct RestyleEnumerateData : public RestyleData {
-    nsRefPtr<Element> mElement;
-  };
-
 private:
   bool AddPendingRestyleToTable(Element* aElement, nsRestyleHint aRestyleHint,
                                 nsChangeHint aMinChangeHint);
 
   /**
    * Handle a single mPendingRestyles entry.  aRestyleHint must not
    * include eRestyle_LaterSiblings; that needs to be dealt with
    * before calling this function.
@@ -469,17 +467,22 @@ RestyleTracker::AddPendingRestyle(Elemen
       //
       // As with the mRestyleRoots array, mDescendants maintains the
       // invariant that if two elements appear in the array and one
       // is an ancestor of the other, that the ancestor appears after
       // the descendant.
       RestyleData* curData;
       mPendingRestyles.Get(cur, &curData);
       NS_ASSERTION(curData, "expected to find a RestyleData for cur");
-      curData->mDescendants.AppendElement(aElement);
+      // If cur has an eRestyle_ForceDescendants restyle hint, then we
+      // know that we will get to all descendants.  Don't bother
+      // recording the descendant to restyle in that case.
+      if (!(curData->mRestyleHint & eRestyle_ForceDescendants)) {
+        curData->mDescendants.AppendElement(aElement);
+      }
     }
   }
 
   mHaveLaterSiblingRestyles =
     mHaveLaterSiblingRestyles || (aRestyleHint & eRestyle_LaterSiblings) != 0;
   return hadRestyleLaterSiblings;
 }
 
--- a/layout/base/nsDocumentViewer.cpp
+++ b/layout/base/nsDocumentViewer.cpp
@@ -4462,27 +4462,34 @@ nsDocumentViewer::IsInitializedForPrintP
 void
 nsDocumentViewer::InitializeForPrintPreview()
 {
   mInitializedForPrintPreview = true;
 }
 
 void
 nsDocumentViewer::SetPrintPreviewPresentation(nsViewManager* aViewManager,
-                                                nsPresContext* aPresContext,
-                                                nsIPresShell* aPresShell)
+                                              nsPresContext* aPresContext,
+                                              nsIPresShell* aPresShell)
 {
   if (mPresShell) {
     DestroyPresShell();
   }
 
   mWindow = nullptr;
   mViewManager = aViewManager;
   mPresContext = aPresContext;
   mPresShell = aPresShell;
+
+  if (ShouldAttachToTopLevel()) {
+    DetachFromTopLevelWidget();
+    nsView* rootView = mViewManager->GetRootView();
+    rootView->AttachToTopLevelWidget(mParentWidget);
+    mAttachedToParent = true;
+  }
 }
 
 // Fires the "document-shown" event so that interested parties are aware of it.
 NS_IMETHODIMP
 nsDocumentShownDispatcher::Run()
 {
   nsCOMPtr<nsIObserverService> observerService =
     mozilla::services::GetObserverService();
--- a/layout/style/ImportRule.h
+++ b/layout/style/ImportRule.h
@@ -29,17 +29,18 @@ class ImportRule MOZ_FINAL : public Rule
 public:
   ImportRule(nsMediaList* aMedia, const nsString& aURLSpec,
              uint32_t aLineNumber, uint32_t aColumnNumber);
 private:
   // for |Clone|
   ImportRule(const ImportRule& aCopy);
   ~ImportRule();
 public:
-  NS_DECL_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS_AMBIGUOUS(ImportRule, nsIStyleRule)
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
 
   DECL_STYLE_RULE_INHERIT
 
 #ifdef HAVE_CPP_AMBIGUITY_RESOLVING_USING
   using Rule::GetStyleSheet; // unhide since nsIDOMCSSImportRule has its own GetStyleSheet
 #endif
 
   // nsIStyleRule methods
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -365,21 +365,23 @@ ImportRule::ImportRule(const ImportRule&
 
 ImportRule::~ImportRule()
 {
   if (mChildSheet) {
     mChildSheet->SetOwnerRule(nullptr);
   }
 }
 
-NS_IMPL_ADDREF(ImportRule)
-NS_IMPL_RELEASE(ImportRule)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(ImportRule)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(ImportRule)
+
+NS_IMPL_CYCLE_COLLECTION(ImportRule, mMedia, mChildSheet)
 
 // QueryInterface implementation for ImportRule
-NS_INTERFACE_MAP_BEGIN(ImportRule)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ImportRule)
   NS_INTERFACE_MAP_ENTRY(nsIStyleRule)
   NS_INTERFACE_MAP_ENTRY(nsIDOMCSSRule)
   NS_INTERFACE_MAP_ENTRY(nsIDOMCSSImportRule)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStyleRule)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(CSSImportRule)
 NS_INTERFACE_MAP_END
 
 IMPL_STYLE_RULE_INHERIT(ImportRule, Rule)
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -138,17 +138,19 @@ endif
 
 # We touch the target file before invoking Proguard so that Proguard's
 # outputs are fresher than the target, preventing a subsequent
 # invocation from thinking Proguard's outputs are stale.  This is safe
 # because Make removes the target file if any recipe command fails.
 .proguard.deps: $(ALL_JARS)
 	$(REPORT_BUILD)
 	@$(TOUCH) $@
-	java -jar $(ANDROID_SDK_ROOT)/tools/proguard/lib/proguard.jar \
+	java \
+		-Xmx512m -Xms128m \
+		-jar $(ANDROID_SDK_ROOT)/tools/proguard/lib/proguard.jar \
 		@$(topsrcdir)/mobile/android/config/proguard.cfg \
 		-optimizationpasses $(PROGUARD_PASSES) \
 		-injars $(subst ::,:,$(subst $(NULL) ,:,$(strip $(ALL_JARS)))) \
 		-outjars jars-proguarded \
 		-libraryjars $(library_jars)
 
 CLASSES_WITH_JNI= \
     org.mozilla.gecko.ANRReporter \
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -533,17 +533,17 @@ moz_native_devices_sources = [
     'ChromeCast.java',
     'GeckoMediaPlayer.java',
     'MediaPlayerManager.java',
 ]
 if CONFIG['MOZ_NATIVE_DEVICES']:
     gbjar.extra_jars += moz_native_devices_jars
     gbjar.sources += moz_native_devices_sources
 
-gbjar.javac_flags += ['-Xlint:all,-deprecation,-fallthrough']
+gbjar.javac_flags += ['-Xlint:all,-deprecation,-fallthrough', '-J-Xmx512m', '-J-Xms128m']
 
 noajar = add_java_jar('nineoldandroids')
 noajar.sources += [ thirdparty_source_dir + f for f in [
     'com/nineoldandroids/animation/Animator.java',
     'com/nineoldandroids/animation/AnimatorInflater.java',
     'com/nineoldandroids/animation/AnimatorListenerAdapter.java',
     'com/nineoldandroids/animation/AnimatorSet.java',
     'com/nineoldandroids/animation/ArgbEvaluator.java',
--- a/mobile/android/locales/jar.mn
+++ b/mobile/android/locales/jar.mn
@@ -49,17 +49,16 @@ relativesrcdir toolkit/locales:
   locale/@AB_CD@/browser/overrides/commonDialogs.properties        (%chrome/global/commonDialogs.properties)
   locale/@AB_CD@/browser/overrides/intl.properties                 (%chrome/global/intl.properties)
   locale/@AB_CD@/browser/overrides/intl.css                        (%chrome/global/intl.css)
   locale/@AB_CD@/browser/overrides/passwordmgr.properties          (%chrome/passwordmgr/passwordmgr.properties)
   locale/@AB_CD@/browser/overrides/search/search.properties        (%chrome/search/search.properties)
   locale/@AB_CD@/browser/overrides/update/updates.properties       (%chrome/mozapps/update/updates.properties)
 # plugins
   locale/@AB_CD@/browser/overrides/plugins/plugins.dtd             (%chrome/mozapps/plugins/plugins.dtd)
-  locale/@AB_CD@/browser/overrides/plugins/plugins.properties      (%chrome/mozapps/plugins/plugins.properties)
 # about:support
   locale/@AB_CD@/browser/overrides/global/aboutSupport.dtd         (%chrome/global/aboutSupport.dtd)
   locale/@AB_CD@/browser/overrides/global/aboutSupport.properties  (%chrome/global/aboutSupport.properties)
 #about:crashes
   locale/@AB_CD@/browser/overrides/crashreporter/crashes.dtd         (%crashreporter/crashes.dtd)
   locale/@AB_CD@/browser/overrides/crashreporter/crashes.properties  (%crashreporter/crashes.properties)
 #about:mozilla
   locale/@AB_CD@/browser/overrides/global/mozilla.dtd                (%chrome/global/mozilla.dtd)
@@ -74,17 +73,16 @@ relativesrcdir toolkit/locales:
 % override chrome://global/locale/commonDialogs.properties chrome://browser/locale/overrides/commonDialogs.properties
 % override chrome://mozapps/locale/handling/handling.properties chrome://browser/locale/handling.properties
 % override chrome://global/locale/intl.properties chrome://browser/locale/overrides/intl.properties
 % override chrome://global/locale/intl.css chrome://browser/locale/overrides/intl.css
 % override chrome://passwordmgr/locale/passwordmgr.properties chrome://browser/locale/overrides/passwordmgr/passwordmgr.properties
 % override chrome://global/locale/search/search.properties chrome://browser/locale/overrides/search/search.properties
 % override chrome://mozapps/locale/update/updates.properties chrome://browser/locale/overrides/update/updates.properties
 % override chrome://mozapps/locale/plugins/plugins.dtd chrome://browser/locale/overrides/plugins/plugins.dtd
-% override chrome://mozapps/locale/plugins/plugins.properties chrome://browser/locale/overrides/plugins/plugins.properties
 % override chrome://global/locale/aboutSupport.dtd chrome://browser/locale/overrides/global/aboutSupport.dtd
 % override chrome://global/locale/aboutSupport.properties chrome://browser/locale/overrides/global/aboutSupport.properties
 % override chrome://global/locale/crashes.dtd chrome://browser/locale/overrides/crashreporter/crashes.dtd
 % override chrome://global/locale/crashes.properties chrome://browser/locale/overrides/crashreporter/crashes.properties
 % override chrome://global/locale/mozilla.dtd chrome://browser/locale/overrides/global/mozilla.dtd
 % override chrome://global/locale/aboutTelemetry.dtd chrome://browser/locale/overrides/global/aboutTelemetry.dtd
 % override chrome://global/locale/aboutTelemetry.properties chrome://browser/locale/overrides/global/aboutTelemetry.properties
 
--- a/mozglue/build/WindowsDllBlocklist.cpp
+++ b/mozglue/build/WindowsDllBlocklist.cpp
@@ -151,16 +151,19 @@ static DllBlockInfo sWindowsDllBlocklist
   // Topcrash with V-bates, bug 1002748 and bug 1023239
   { "libinject.dll", UNVERSIONED },
   { "libinject2.dll", 0x537DDC93, DllBlockInfo::USE_TIMESTAMP },
   { "libredir2.dll", 0x5385B7ED, DllBlockInfo::USE_TIMESTAMP },
 
   // Crashes with RoboForm2Go written against old SDK, bug 988311
   { "rf-firefox-22.dll", ALL_VERSIONS },
 
+  // Crashes with DesktopTemperature, bug 1046382
+  { "dtwxsvc.dll", 0x53153234, DllBlockInfo::USE_TIMESTAMP },
+
   { nullptr, 0 }
 };
 
 #ifndef STATUS_DLL_NOT_FOUND
 #define STATUS_DLL_NOT_FOUND ((DWORD)0xC0000135L)
 #endif
 
 // define this for very verbose dll load debug spew
--- a/testing/marionette/client/marionette/tests/unit/test_set_window_size.py
+++ b/testing/marionette/client/marionette/tests/unit/test_set_window_size.py
@@ -17,17 +17,17 @@ class TestSetWindowSize(MarionetteTestCa
         # error is returned if that is the case; therefore if the window is maximized
         # at the start of this test, returning to the original size via set_window_size
         # size will result in error; so reset to original size minus 1 pixel width
         if self.start_size['width'] == self.max_width and self.start_size['height'] == self.max_height:
             self.start_size['width']-=1
         self.marionette.set_window_size(self.start_size['width'], self.start_size['height'])
         super(MarionetteTestCase, self).tearDown()
 
-    def test_set_window_size(self):
+    def test_that_we_can_get_and_set_window_size(self):
         # event handler
         self.marionette.execute_script("""
         window.wrappedJSObject.rcvd_event = false;
         window.onresize = function() {
             window.wrappedJSObject.rcvd_event = true;
         };
         """)
 
@@ -37,14 +37,19 @@ class TestSetWindowSize(MarionetteTestCa
         self.marionette.set_window_size(width, height)
         self.wait_for_condition(lambda m: m.execute_script("return window.wrappedJSObject.rcvd_event;"))
         size = self.marionette.window_size
         self.assertEqual(size['width'], width,
                          "Window width is %s but should be %s" % (size['width'], width))
         self.assertEqual(size['height'], height,
                          "Window height is %s but should be %s" % (size['height'], height))
 
+    def test_that_we_throw_an_error_when_trying_to_set_maximum_size(self):
+        # valid size
+        width = self.max_width - 100
+        height = self.max_height - 100
+        self.marionette.set_window_size(width, height)
         # invalid size (cannot maximize)
         with self.assertRaisesRegexp(MarionetteException, "Invalid requested size"):
             self.marionette.set_window_size(self.max_width, self.max_height)
         size = self.marionette.window_size
         self.assertEqual(size['width'], width, "Window width should not have changed")
         self.assertEqual(size['height'], height, "Window height should not have changed")
--- a/testing/marionette/client/marionette/tests/unit/test_typing.py
+++ b/testing/marionette/client/marionette/tests/unit/test_typing.py
@@ -163,67 +163,75 @@ class TestTyping(MarionetteTestCase):
         self.assertTrue("up: 37" in result.text.strip())
 
         element.send_keys(Keys.ARROW_RIGHT)
         self.assertTrue("down: 39" in result.text.strip())
         self.assertTrue("up: 39" in result.text.strip())
 
         #  And leave no rubbish/printable keys in the "keyReporter"
         self.assertEqual(element.get_attribute("value"), "")
-  
+
+    ''' Disabled. Reenable in Bug 1068728
     def testNumericShiftKeys(self):
         test_html = self.marionette.absolute_url("javascriptPage.html")
         self.marionette.navigate(test_html)
 
         result = self.marionette.find_element("id", "result")
         element = self.marionette.find_element("id", "keyReporter")
         numericShiftsEtc = "~!@#$%^&*()_+{}:i\"<>?|END~"
         element.send_keys(numericShiftsEtc)
         self.assertEqual(element.get_attribute("value"), numericShiftsEtc)
         self.assertTrue(" up: 16" in result.text.strip())
+    '''
 
     def testLowerCaseAlphaKeys(self):
         test_html = self.marionette.absolute_url("javascriptPage.html")
         self.marionette.navigate(test_html)
 
         element = self.marionette.find_element("id", "keyReporter")
         lowerAlphas = "abcdefghijklmnopqrstuvwxyz"
         element.send_keys(lowerAlphas)
         self.assertEqual(element.get_attribute("value"), lowerAlphas)
 
+    ''' Disabled. Reenable in Bug 1068735
     def testUppercaseAlphaKeys(self):
         test_html = self.marionette.absolute_url("javascriptPage.html")
         self.marionette.navigate(test_html)
 
         result = self.marionette.find_element("id", "result")
         element = self.marionette.find_element("id", "keyReporter")
         upperAlphas = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
         element.send_keys(upperAlphas)
         self.assertEqual(element.get_attribute("value"), upperAlphas)
         self.assertTrue(" up: 16" in result.text.strip())
+    '''
 
+    ''' Disabled. Reenable in Bug 1068726
     def testAllPrintableKeys(self):
         test_html = self.marionette.absolute_url("javascriptPage.html")
         self.marionette.navigate(test_html)
 
         result = self.marionette.find_element("id", "result")
         element = self.marionette.find_element("id", "keyReporter")
         allPrintable = "!\"#$%&'()*+,-./0123456789:<=>?@ ABCDEFGHIJKLMNOPQRSTUVWXYZ [\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"
         element.send_keys(allPrintable)
 
         self.assertTrue(element.get_attribute("value"), allPrintable)
         self.assertTrue(" up: 16" in result.text.strip())
+    '''
 
+    ''' Disabled. Reenable in Bug 1068733
     def testSpecialSpaceKeys(self):
         test_html = self.marionette.absolute_url("javascriptPage.html")
         self.marionette.navigate(test_html)
 
         element = self.marionette.find_element("id", "keyReporter")
         element.send_keys("abcd" + Keys.SPACE + "fgh" + Keys.SPACE + "ij")
         self.assertEqual(element.get_attribute("value"), "abcd fgh ij")
+    '''
 
     def testShouldTypeAnInteger(self):
         test_html = self.marionette.absolute_url("javascriptPage.html")
         self.marionette.navigate(test_html)
 
         element = self.marionette.find_element("id", "keyReporter")
         element.send_keys(1234)
         self.assertEqual(element.get_attribute("value"), "1234")
--- a/testing/marionette/client/marionette/tests/unit/unit-tests.ini
+++ b/testing/marionette/client/marionette/tests/unit/unit-tests.ini
@@ -37,17 +37,16 @@ b2g = false
 [test_elementState_chrome.py]
 b2g = false
 [test_text.py]
 [test_text_chrome.py]
 disabled = "Bug 896046"
 
 [test_clearing.py]
 [test_typing.py]
-disabled = "Bug 123456"
 
 [test_log.py]
 [test_emulator.py]
 browser = false
 qemu = true
 
 [test_execute_async_script.py]
 [test_execute_script.py]
--- a/testing/marionette/marionette-listener.js
+++ b/testing/marionette/marionette-listener.js
@@ -1585,203 +1585,19 @@ function isElementSelected(msg) {
 
 /**
  * Send keys to element
  */
 function sendKeysToElement(msg) {
   let command_id = msg.json.command_id;
 
   let el = elementManager.getKnownElement(msg.json.id, curFrame);
-  if (checkVisible(el)) {
-    el.focus();
-    var value = msg.json.value.join("");
-    let hasShift = null;
-    let hasCtrl = null;
-    let hasAlt = null;
-    let hasMeta = null;
-    for (var i = 0; i < value.length; i++) {
-      let upper = value.charAt(i).toUpperCase();
-      var keyCode = null;
-      var c = value.charAt(i);
-      switch (c) {
-        case '\uE001':
-          keyCode = "VK_CANCEL";
-          break;
-        case '\uE002':
-          keyCode = "VK_HELP";
-          break;
-        case '\uE003':
-          keyCode = "VK_BACK_SPACE";
-          break;
-        case '\uE004':
-          keyCode = "VK_TAB";
-          break;
-        case '\uE005':
-          keyCode = "VK_CLEAR";
-          break;
-        case '\uE006':
-        case '\uE007':
-          keyCode = "VK_RETURN";
-          break;
-        case '\uE008':
-          keyCode = "VK_SHIFT";
-          hasShift = !hasShift;
-          break;
-        case '\uE009':
-          keyCode = "VK_CONTROL";
-          controlKey = !controlKey;
-          break;
-        case '\uE00A':
-          keyCode = "VK_ALT";
-          altKey = !altKey;
-          break;
-        case '\uE03D':
-          keyCode = "VK_META";
-          metaKey = !metaKey;
-          break;
-        case '\uE00B':
-          keyCode = "VK_PAUSE";
-          break;
-        case '\uE00C':
-          keyCode = "VK_ESCAPE";
-          break;
-        case '\uE00D':
-          keyCode = "VK_Space";  // printable
-          break;
-        case '\uE00E':
-          keyCode = "VK_PAGE_UP";
-          break;
-        case '\uE00F':
-          keyCode = "VK_PAGE_DOWN";
-          break;
-        case '\uE010':
-          keyCode = "VK_END";
-          break;
-        case '\uE011':
-          keyCode = "VK_HOME";
-          break;
-        case '\uE012':
-          keyCode = "VK_LEFT";
-          break;
-        case '\uE013':
-          keyCode = "VK_UP";
-          break;
-        case '\uE014':
-          keyCode = "VK_RIGHT";
-          break;
-        case '\uE015':
-          keyCode = "VK_DOWN";
-          break;
-        case '\uE016':
-          keyCode = "VK_INSERT";
-          break;
-        case '\uE017':
-          keyCode = "VK_DELETE";
-          break;
-        case '\uE018':
-          keyCode = "VK_SEMICOLON";
-          break;
-        case '\uE019':
-          keyCode = "VK_EQUALS";
-          break;
-        case '\uE01A':
-          keyCode = "VK_NUMPAD0";
-          break;
-        case '\uE01B':
-          keyCode = "VK_NUMPAD1";
-          break;
-        case '\uE01C':
-          keyCode = "VK_NUMPAD2";
-          break;
-        case '\uE01D':
-          keyCode = "VK_NUMPAD3";
-          break;
-        case '\uE01E':
-          keyCode = "VK_NUMPAD4";
-          break;
-        case '\uE01F':
-          keyCode = "VK_NUMPAD5";
-          break;
-        case '\uE020':
-          keyCode = "VK_NUMPAD6";
-          break;
-        case '\uE021':
-          keyCode = "VK_NUMPAD7";
-          break;
-        case '\uE022':
-          keyCode = "VK_NUMPAD8";
-          break;
-        case '\uE023':
-          keyCode = "VK_NUMPAD9";
-          break;
-        case '\uE024':
-          keyCode = "VK_MULTIPLY";
-          break;
-        case '\uE025':
-          keyCode = "VK_ADD";
-          break;
-        case '\uE026':
-          keyCode = "VK_SEPARATOR";
-          break;
-        case '\uE027':
-          keyCode = "VK_SUBTRACT";
-          break;
-        case '\uE028':
-          keyCode = "VK_DECIMAL";
-          break;
-        case '\uE029':
-          keyCode = "VK_DIVIDE";
-          break;
-        case '\uE031':
-          keyCode = "VK_F1";
-          break;
-        case '\uE032':
-          keyCode = "VK_F2";
-          break;
-        case '\uE033':
-          keyCode = "VK_F3";
-          break;
-        case '\uE034':
-          keyCode = "VK_F4";
-          break;
-        case '\uE035':
-          keyCode = "VK_F5";
-          break;
-        case '\uE036':
-          keyCode = "VK_F6";
-          break;
-        case '\uE037':
-          keyCode = "VK_F7";
-          break;
-        case '\uE038':
-          keyCode = "VK_F8";
-          break;
-        case '\uE039':
-          keyCode = "VK_F9";
-          break;
-        case '\uE03A':
-          keyCode = "VK_F10";
-          break;
-        case '\uE03B':
-          keyCode = "VK_F11";
-          break;
-        case '\uE03C':
-          keyCode = "VK_F12";
-          break;
-      }
-      hasShift = value.charAt(i) == upper;
-      utils.synthesizeKey(keyCode || value[i],
-                          { shiftKey: hasShift, ctrlKey: hasCtrl, altKey: hasAlt, metaKey: hasMeta },
-                          curFrame);
-    };
-    sendOk(command_id);
-  }
-  else {
-    sendError("Element is not visible", 11, null, command_id)
-  }
+  let keysToSend = msg.json.value;
+
+  utils.sendKeysToElement(curFrame, el, keysToSend, sendOk, sendError, command_id);
 }
 
 /**
  * Get the element's top left-hand corner point.
  */
 function getElementLocation(msg) {
   let command_id = msg.json.command_id;
   try {
--- a/testing/marionette/marionette-sendkeys.js
+++ b/testing/marionette/marionette-sendkeys.js
@@ -11,370 +11,205 @@
  *
  *  Unless required by applicable law or agreed to in writing, software
  *  distributed under the License is distributed on an "AS IS" BASIS,
  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  *  See the License for the specific language governing permissions and
  *  limitations under the License.
  */
 
-
-var type = function(doc, element, text, releaseModifiers,
-    opt_keysState) {
-
-  var currentTextLength = element.value ? element.value.length : 0;
-  element.selectionStart = currentTextLength;
-  element.selectionEnd = currentTextLength;
-
-  // For consistency between native and synthesized events, convert common
-  // escape sequences to their Key enum aliases.
-  text = text.replace(new RegExp('\b', 'g'), '\uE003').   // DOM_VK_BACK_SPACE
-      replace(/\t/g, '\uE004').                           // DOM_VK_TAB
-      replace(/(\r\n|\n|\r)/g, '\uE006');                 // DOM_VK_RETURN
-
-  var controlKey = false;
-  var shiftKey = false;
-  var altKey = false;
-  var metaKey = false;
-  if (opt_keysState) {
-    controlKey = opt_keysState.control;
-    shiftKey = opt_keysState.shiftKey;
-    altKey = opt_keysState.alt;
-    metaKey = opt_keysState.meta;
-  }
-
-  shiftCount = 0;
-
-  var upper = text.toUpperCase();
-
-  for (var i = 0; i < text.length; i++) {
-    var c = text.charAt(i);
-
-    // NULL key: reset modifier key states, and continue
+let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
+               .getService(Ci.mozIJSSubScriptLoader);
 
-    if (c == '\uE000') {
-      if (controlKey) {
-        var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CONTROL;
-        keyEvent(doc, element, "keyup", kCode, 0,
-            controlKey = false, shiftKey, altKey, metaKey, false);
-      }
-
-      if (shiftKey) {
-        var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT;
-        keyEvent(doc, element, "keyup", kCode, 0,
-            controlKey, shiftKey = false, altKey, metaKey, false);
-      }
-
-      if (altKey) {
-        var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ALT;
-        keyEvent(doc, element, "keyup", kCode, 0,
-            controlKey, shiftKey, altKey = false, metaKey, false);
-      }
-
-      if (metaKey) {
-        var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_META;
-        keyEvent(doc, element, "keyup", kCode, 0,
-            controlKey, shiftKey, altKey, metaKey = false, false);
-      }
-
-      continue;
-    }
-
-    // otherwise decode keyCode, charCode, modifiers ...
-
-    var modifierEvent = "";
-    var charCode = 0;
-    var keyCode = 0;
+let utils = {};
+loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
+loader.loadSubScript("chrome://marionette/content/ChromeUtils.js", utils);
 
-    if (c == '\uE001') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CANCEL;
-    } else if (c == '\uE002') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_HELP;
-    } else if (c == '\uE003') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_BACK_SPACE;
-    } else if (c == '\uE004') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_TAB;
-    } else if (c == '\uE005') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CLEAR;
-    } else if (c == '\uE006' || c == '\uE007') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_RETURN;
-    } else if (c == '\uE008') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT;
-      shiftKey = !shiftKey;
-      modifierEvent = shiftKey ? "keydown" : "keyup";
-    } else if (c == '\uE009') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CONTROL;
-      controlKey = !controlKey;
-      modifierEvent = controlKey ? "keydown" : "keyup";
-    } else if (c == '\uE00A') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ALT;
-      altKey = !altKey;
-      modifierEvent = altKey ? "keydown" : "keyup";
-    } else if (c == '\uE03D') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_META;
-      metaKey = !metaKey;
-      modifierEvent = metaKey ? "keydown" : "keyup";
-    } else if (c == '\uE00B') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_PAUSE;
-    } else if (c == '\uE00C') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ESCAPE;
-    } else if (c == '\uE00D') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SPACE;
-      keyCode = charCode = ' '.charCodeAt(0);  // printable
-    } else if (c == '\uE00E') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_PAGE_UP;
-    } else if (c == '\uE00F') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_PAGE_DOWN;
-    } else if (c == '\uE010') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_END;
-    } else if (c == '\uE011') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_HOME;
-    } else if (c == '\uE012') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_LEFT;
-    } else if (c == '\uE013') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_UP;
-    } else if (c == '\uE014') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_RIGHT;
-    } else if (c == '\uE015') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_DOWN;
-    } else if (c == '\uE016') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_INSERT;
-    } else if (c == '\uE017') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_DELETE;
-    } else if (c == '\uE018') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SEMICOLON;
-      charCode = ';'.charCodeAt(0);
-    } else if (c == '\uE019') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_EQUALS;
-      charCode = '='.charCodeAt(0);
-    } else if (c == '\uE01A') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD0;
-      charCode = '0'.charCodeAt(0);
-    } else if (c == '\uE01B') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD1;
-      charCode = '1'.charCodeAt(0);
-    } else if (c == '\uE01C') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD2;
-      charCode = '2'.charCodeAt(0);
-    } else if (c == '\uE01D') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD3;
-      charCode = '3'.charCodeAt(0);
-    } else if (c == '\uE01E') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD4;
-      charCode = '4'.charCodeAt(0);
-    } else if (c == '\uE01F') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD5;
-      charCode = '5'.charCodeAt(0);
-    } else if (c == '\uE020') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD6;
-      charCode = '6'.charCodeAt(0);
-    } else if (c == '\uE021') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD7;
-      charCode = '7'.charCodeAt(0);
-    } else if (c == '\uE022') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD8;
-      charCode = '8'.charCodeAt(0);
-    } else if (c == '\uE023') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_NUMPAD9;
-      charCode = '9'.charCodeAt(0);
-    } else if (c == '\uE024') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_MULTIPLY;
-      charCode = '*'.charCodeAt(0);
-    } else if (c == '\uE025') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ADD;
-      charCode = '+'.charCodeAt(0);
-    } else if (c == '\uE026') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SEPARATOR;
-      charCode = ','.charCodeAt(0);
-    } else if (c == '\uE027') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SUBTRACT;
-      charCode = '-'.charCodeAt(0);
-    } else if (c == '\uE028') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_DECIMAL;
-      charCode = '.'.charCodeAt(0);
-    } else if (c == '\uE029') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_DIVIDE;
-      charCode = '/'.charCodeAt(0);
-    } else if (c == '\uE031') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F1;
-    } else if (c == '\uE032') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F2;
-    } else if (c == '\uE033') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F3;
-    } else if (c == '\uE034') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F4;
-    } else if (c == '\uE035') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F5;
-    } else if (c == '\uE036') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F6;
-    } else if (c == '\uE037') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F7;
-    } else if (c == '\uE038') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F8;
-    } else if (c == '\uE039') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F9;
-    } else if (c == '\uE03A') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F10;
-    } else if (c == '\uE03B') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F11;
-    } else if (c == '\uE03C') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_F12;
-    } else if (c == ',' || c == '<') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_COMMA;
-      charCode = c.charCodeAt(0);
-    } else if (c == '.' || c == '>') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_PERIOD;
-      charCode = c.charCodeAt(0);
-    } else if (c == '/' || c == '?') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SLASH;
-      charCode = text.charCodeAt(i);
-    } else if (c == '`' || c == '~') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_BACK_QUOTE;
-      charCode = c.charCodeAt(0);
-    } else if (c == '{' || c == '[') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_OPEN_BRACKET;
-      charCode = c.charCodeAt(0);
-    } else if (c == '\\' || c == '|') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_BACK_SLASH;
-      charCode = c.charCodeAt(0);
-    } else if (c == '}' || c == ']') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CLOSE_BRACKET;
-      charCode = c.charCodeAt(0);
-    } else if (c == '\'' || c == '"') {
-      keyCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_QUOTE;
-      charCode = c.charCodeAt(0);
-    } else {
-      keyCode = upper.charCodeAt(i);
-      charCode = text.charCodeAt(i);
+function sendKeysToElement (document, element, keysToSend, successCallback, errorCallback, command_id) {
+  if (checkVisible(element)) {
+    element.focus();
+    var value = keysToSend.join("");
+    let hasShift = null;
+    let hasCtrl = null;
+    let hasAlt = null;
+    let hasMeta = null;
+    for (var i = 0; i < value.length; i++) {
+      let upper = value.charAt(i).toUpperCase();
+      var keyCode = null;
+      var c = value.charAt(i);
+      switch (c) {
+        case '\uE001':
+          keyCode = "VK_CANCEL";
+          break;
+        case '\uE002':
+          keyCode = "VK_HELP";
+          break;
+        case '\uE003':
+          keyCode = "VK_BACK_SPACE";
+          break;
+        case '\uE004':
+          keyCode = "VK_TAB";
+          break;
+        case '\uE005':
+          keyCode = "VK_CLEAR";
+          break;
+        case '\uE006':
+        case '\uE007':
+          keyCode = "VK_RETURN";
+          break;
+        case '\uE008':
+          keyCode = "VK_SHIFT";
+          hasShift = !hasShift;
+          break;
+        case '\uE009':
+          keyCode = "VK_CONTROL";
+          controlKey = !controlKey;
+          break;
+        case '\uE00A':
+          keyCode = "VK_ALT";
+          altKey = !altKey;
+          break;
+        case '\uE03D':
+          keyCode = "VK_META";
+          metaKey = !metaKey;
+          break;
+        case '\uE00B':
+          keyCode = "VK_PAUSE";
+          break;
+        case '\uE00C':
+          keyCode = "VK_ESCAPE";
+          break;
+        case '\uE00D':
+          keyCode = "VK_SPACE";  // printable
+          break;
+        case '\uE00E':
+          keyCode = "VK_PAGE_UP";
+          break;
+        case '\uE00F':
+          keyCode = "VK_PAGE_DOWN";
+          break;
+        case '\uE010':
+          keyCode = "VK_END";
+          break;
+        case '\uE011':
+          keyCode = "VK_HOME";
+          break;
+        case '\uE012':
+          keyCode = "VK_LEFT";
+          break;
+        case '\uE013':
+          keyCode = "VK_UP";
+          break;
+        case '\uE014':
+          keyCode = "VK_RIGHT";
+          break;
+        case '\uE015':
+          keyCode = "VK_DOWN";
+          break;
+        case '\uE016':
+          keyCode = "VK_INSERT";
+          break;
+        case '\uE017':
+          keyCode = "VK_DELETE";
+          break;
+        case '\uE018':
+          keyCode = "VK_SEMICOLON";
+          break;
+        case '\uE019':
+          keyCode = "VK_EQUALS";
+          break;
+        case '\uE01A':
+          keyCode = "VK_NUMPAD0";
+          break;
+        case '\uE01B':
+          keyCode = "VK_NUMPAD1";
+          break;
+        case '\uE01C':
+          keyCode = "VK_NUMPAD2";
+          break;
+        case '\uE01D':
+          keyCode = "VK_NUMPAD3";
+          break;
+        case '\uE01E':
+          keyCode = "VK_NUMPAD4";
+          break;
+        case '\uE01F':
+          keyCode = "VK_NUMPAD5";
+          break;
+        case '\uE020':
+          keyCode = "VK_NUMPAD6";
+          break;
+        case '\uE021':
+          keyCode = "VK_NUMPAD7";
+          break;
+        case '\uE022':
+          keyCode = "VK_NUMPAD8";
+          break;
+        case '\uE023':
+          keyCode = "VK_NUMPAD9";
+          break;
+        case '\uE024':
+          keyCode = "VK_MULTIPLY";
+          break;
+        case '\uE025':
+          keyCode = "VK_ADD";
+          break;
+        case '\uE026':
+          keyCode = "VK_SEPARATOR";
+          break;
+        case '\uE027':
+          keyCode = "VK_SUBTRACT";
+          break;
+        case '\uE028':
+          keyCode = "VK_DECIMAL";
+          break;
+        case '\uE029':
+          keyCode = "VK_DIVIDE";
+          break;
+        case '\uE031':
+          keyCode = "VK_F1";
+          break;
+        case '\uE032':
+          keyCode = "VK_F2";
+          break;
+        case '\uE033':
+          keyCode = "VK_F3";
+          break;
+        case '\uE034':
+          keyCode = "VK_F4";
+          break;
+        case '\uE035':
+          keyCode = "VK_F5";
+          break;
+        case '\uE036':
+          keyCode = "VK_F6";
+          break;
+        case '\uE037':
+          keyCode = "VK_F7";
+          break;
+        case '\uE038':
+          keyCode = "VK_F8";
+          break;
+        case '\uE039':
+          keyCode = "VK_F9";
+          break;
+        case '\uE03A':
+          keyCode = "VK_F10";
+          break;
+        case '\uE03B':
+          keyCode = "VK_F11";
+          break;
+        case '\uE03C':
+          keyCode = "VK_F12";
+          break;
+      }
+      hasShift = value.charAt(i) == upper;
+      utils.synthesizeKey(keyCode || value[i],
+                          { shiftKey: hasShift, ctrlKey: hasCtrl, altKey: hasAlt, metaKey: hasMeta },
+                          document);
     }
-
-    // generate modifier key event if needed, and continue
-
-    if (modifierEvent) {
-      keyEvent(doc, element, modifierEvent, keyCode, 0,
-          controlKey, shiftKey, altKey, metaKey, false);
-      continue;
-    }
-
-    // otherwise, shift down if needed
-
-    var needsShift = false;
-    if (charCode) {
-      needsShift = /[A-Z\!\$\^\*\(\)\+\{\}\:\?\|~@#%&_"<>]/.test(c);
-    }
-
-    if (needsShift && !shiftKey) {
-      var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT;
-      keyEvent(doc, element, "keydown", kCode, 0,
-          controlKey, true, altKey, metaKey, false);
-      shiftCount += 1;
-    }
-
-    // generate key[down/press/up] for key
-
-    var pressCode = keyCode;
-    if (charCode >= 32 && charCode < 127) {
-      pressCode = 0;
-      if (!needsShift && shiftKey && charCode > 32) {
-        // If typing a lowercase character key and the shiftKey is down, the
-        // charCode should be mapped to the shifted key value. This assumes
-        // a default 104 international keyboard layout.
-        if (charCode >= 97 && charCode <= 122) {
-          charCode = charCode + 65 - 97;  // [a-z] -> [A-Z]
-        } else {
-          var mapFrom = '`1234567890-=[]\\;\',./';
-          var mapTo = '~!@#$%^&*()_+{}|:"<>?';
-
-          var value = String.fromCharCode(charCode).
-              replace(/([\[\\\.])/g, '\\$1');
-          var index = mapFrom.search(value);
-          if (index >= 0) {
-            charCode = mapTo.charCodeAt(index);
-          }
-        }
-      }
-    }
-
-    var accepted =
-        keyEvent(doc, element, "keydown", keyCode, 0,
-            controlKey, needsShift || shiftKey, altKey, metaKey, false);
-
-    keyEvent(doc, element, "keypress", pressCode, charCode,
-        controlKey, needsShift || shiftKey, altKey, metaKey, !accepted);
-
-    keyEvent(doc, element, "keyup", keyCode, 0,
-        controlKey, needsShift || shiftKey, altKey, metaKey, false);
-
-    // shift up if needed
-
-    if (needsShift && !shiftKey) {
-      var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT;
-      keyEvent(doc, element, "keyup", kCode, 0,
-          controlKey, false, altKey, metaKey, false);
-    }
+    successCallback(command_id);
   }
-
-  // exit cleanup: keyup active modifier keys
-
-  if (controlKey && releaseModifiers) {
-    var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_CONTROL;
-    keyEvent(doc, element, "keyup", kCode, 0,
-        controlKey = false, shiftKey, altKey, metaKey, false);
-  }
-
-  if (shiftKey && releaseModifiers) {
-    var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_SHIFT;
-    keyEvent(doc, element, "keyup", kCode, 0,
-        controlKey, shiftKey = false, altKey, metaKey, false);
-  }
-
-  if (altKey && releaseModifiers) {
-    var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_ALT;
-    keyEvent(doc, element, "keyup", kCode, 0,
-        controlKey, shiftKey, altKey = false, metaKey, false);
-  }
-
-  if (metaKey && releaseModifiers) {
-    var kCode = Components.interfaces.nsIDOMKeyEvent.DOM_VK_META;
-    keyEvent(doc, element, "keyup", kCode, 0,
-        controlKey, shiftKey, altKey, metaKey = false, false);
+  else {
+    errorCallback("Element is not visible", 11, null, command_id);
   }
-
-  return {
-    shiftKey: shiftKey,
-    alt: altKey,
-    meta: metaKey,
-    control: controlKey
-  };
-};
-
-
-var keyEvent = function(doc, element, type, keyCode, charCode,
-                          controlState, shiftState, altState, metaState,
-                          shouldPreventDefault) {
-  var preventDefault = shouldPreventDefault == undefined ? false
-      : shouldPreventDefault;
-
-  var keyboardEvent = doc.createEvent("KeyEvents");
-  var currentView = doc.defaultView;
-
-  keyboardEvent.initKeyEvent(
-      type, //  in DOMString typeArg,
-      true, //  in boolean canBubbleArg
-      true, //  in boolean cancelableArg
-      currentView, //  in nsIDOMAbstractView viewArg
-      controlState, //  in boolean ctrlKeyArg
-      altState, //  in boolean altKeyArg
-      shiftState, //  in boolean shiftKeyArg
-      metaState, //  in boolean metaKeyArg
-      keyCode, //  in unsigned long keyCodeArg
-      charCode);    //  in unsigned long charCodeArg
-
-  if (preventDefault) {
-    keyboardEvent.preventDefault();
-  }
-
-  var win = doc.defaultView;
-  var domUtil = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
-                .getInterface(Components.interfaces.nsIDOMWindowUtils);
-  return domUtil.dispatchDOMEventViaPresShell(element, keyboardEvent, true);
-};
-
+};
\ No newline at end of file
--- a/testing/marionette/marionette-server.js
+++ b/testing/marionette/marionette-server.js
@@ -2415,18 +2415,18 @@ MarionetteServerConnection.prototype = {
    * bars, title bars, etc.
    *
    * An error will be returned if the requested window size would result
    * in the window being in the maximized state.
    */
   setWindowSize: function MDA_setWindowSize(aRequest) {
     this.command_id = this.getCommandId();
 
-    if (appName == "B2G") {
-      this.sendError("Not supported on B2G", 405, null, this.command_id);
+    if (appName !== "Firefox") {
+      this.sendError("Not supported on mobile", 405, null, this.command_id);
       return;
     }
 
     try {
       var width = parseInt(aRequest.parameters.width);
       var height = parseInt(aRequest.parameters.height);
     }
     catch(e) {
--- a/testing/mochitest/runtestsremote.py
+++ b/testing/mochitest/runtestsremote.py
@@ -451,17 +451,17 @@ class MochiRemote(Mochitest):
             if message['action'] == 'test_start':
                 start_found = True
             if 'expected' in message:
                 fail_found = True
         result = 0
         if fail_found:
             result = 1
         if not end_found:
-            self.log.error("Automation Error: Missing end of test marker (process crashed?)")
+            self.log.info("PROCESS-CRASH | Automation Error: Missing end of test marker (process crashed?)")
             result = 1
         return result
 
     def printLog(self):
         passed = 0
         failed = 0
         todo = 0
         incr = 1
--- a/toolkit/components/aboutmemory/content/aboutMemory.js
+++ b/toolkit/components/aboutmemory/content/aboutMemory.js
@@ -816,30 +816,51 @@ function makeDReportMap(aJSONReports)
 
     assert(jr.process     !== undefined, "Missing process");
     assert(jr.path        !== undefined, "Missing path");
     assert(jr.kind        !== undefined, "Missing kind");
     assert(jr.units       !== undefined, "Missing units");
     assert(jr.amount      !== undefined, "Missing amount");
     assert(jr.description !== undefined, "Missing description");
 
-    // Strip out some non-deterministic stuff that prevents clean diffs --
-    // e.g. PIDs, addresses, null principal UUIDs. (Note that we don't strip
-    // out all UUIDs because some of them -- such as those used by add-ons --
-    // are deterministic.)
+    // Strip out some non-deterministic stuff that prevents clean diffs.
+    // Ideally the memory reports themselves would contain information about
+    // which parts of the the process and path need to be stripped -- saving us
+    // from hardwiring knowledge of specific reporters here -- but we have no
+    // mechanism for that. (Any future redesign of how memory reporters work
+    // should include such a mechanism.)
+
+    // Strip PIDs:
+    // - pid 123
+    // - pid=123
     let pidRegex = /pid([ =])\d+/g;
     let pidSubst = "pid$1NNN";
-    let strippedProcess = jr.process.replace(pidRegex, pidSubst);
-    let strippedPath = jr.path.replace(/0x[0-9A-Fa-f]+/g, "0xNNN");
-    strippedPath = strippedPath.replace(pidRegex, pidSubst);
-    strippedPath = strippedPath.replace(
+    let process = jr.process.replace(pidRegex, pidSubst);
+    let path = jr.path.replace(pidRegex, pidSubst);
+
+    // Strip addresses:
+    // - .../js-zone(0x12345678)/...
+    // - .../zone(0x12345678)/...
+    // - .../worker(<URL>, 0x12345678)/...
+    path = path.replace(/zone\(0x[0-9A-Fa-f]+\)\//, "zone(0xNNN)/");
+    path = path.replace(/\/worker\((.+), 0x[0-9A-Fa-f]+\)\//,
+                        "/worker($1, 0xNNN)/");
+
+    // Strip top window IDs:
+    // - explicit/window-objects/top(<URL>, id=123)/...
+    path = path.replace(/^(explicit\/window-objects\/top\(.*, id=)\d+\)/,
+                        "$1NNN)");
+
+    // Strip null principal UUIDs (but not other UUIDs, because they may be
+    // deterministic, such as those used by add-ons).
+    path = path.replace(
       /moz-nullprincipal:{........-....-....-....-............}/g,
       "moz-nullprincipal:{NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN}");
-    let processPath = strippedProcess + kProcessPathSep + strippedPath;
 
+    let processPath = process + kProcessPathSep + path;
     let rOld = dreportMap[processPath];
     if (rOld === undefined) {
       dreportMap[processPath] =
         new DReport(jr.kind, jr.units, jr.amount, jr.description, 1, undefined);
     } else {
       rOld.merge(jr);
     }
   }
--- a/toolkit/components/aboutmemory/tests/memory-reports-diff1.json
+++ b/toolkit/components/aboutmemory/tests/memory-reports-diff1.json
@@ -15,17 +15,23 @@
 
     {"process": "P", "path": "a/b", "kind": 2, "units": 0, "amount": 1000000, "description": "Desc."},
     {"process": "P", "path": "a/c/d", "kind": 2, "units": 0, "amount": 2000000, "description": "Desc."},
     {"process": "P", "path": "a/c/e", "kind": 2, "units": 0, "amount": 2000000, "description": "Desc."},
     {"process": "P", "path": "a/c/f", "kind": 2, "units": 0, "amount": 3000000, "description": "Desc."},
     {"process": "P", "path": "a/c/g", "kind": 2, "units": 0, "amount": 3000000, "description": "Desc."},
     {"process": "P", "path": "a/h", "kind": 2, "units": 0, "amount": 1000, "description": "Desc."},
 
-    {"process": "P2 (pid 22)", "path": "z-moz-nullprincipal:{85e250f3-57ae-46c4-a11e-4176dd39d9c5} 0x1234-blah(0x2345) pid 123 pid=45678", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "p1 (pid 123)", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "p2 (blah, pid=123)", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "p3/zone(0x1234)/p3", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "p4/js-zone(0x1234)/p4", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "p5/worker(foo.com, 0x1234)/p5", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "explicit/window-objects/top(bar.com, id=123)/...", "kind": 0, "units": 0, "amount": 33, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "p6/z-moz-nullprincipal:{85e250f3-57ae-46c4-a11e-4176dd39d9c5}/p6", "kind": 2, "units": 0, "amount": 33, "description": "Desc."},
 
     {"process": "P3", "path": "p3", "kind": 2, "units": 0, "amount": 55, "description": "Desc."},
 
     {"process": "P5", "path": "p5", "kind": 2, "units": 0, "amount": 0, "description": "Desc."},
 
     {"process": "P7", "path": "p7", "kind": 2, "units": 0, "amount": 5, "description": "Desc."},
 
     {"process": "P8", "path": "p8/a/b/c/d", "kind": 2, "units": 0, "amount": 3, "description": "Desc."},
--- a/toolkit/components/aboutmemory/tests/memory-reports-diff2.json
+++ b/toolkit/components/aboutmemory/tests/memory-reports-diff2.json
@@ -16,17 +16,23 @@
 
     {"process": "P", "path": "a/b", "kind": 2, "units": 0, "amount": 2000000, "description": "Desc."},
     {"process": "P", "path": "a/c/d", "kind": 2, "units": 0, "amount": 2998000, "description": "Desc."},
     {"process": "P", "path": "a/c/e", "kind": 2, "units": 0, "amount": 1001000, "description": "Desc."},
     {"process": "P", "path": "a/c/f", "kind": 2, "units": 0, "amount": 3001000, "description": "Desc."},
     {"process": "P", "path": "a/c/g", "kind": 2, "units": 0, "amount": 3001000, "description": "Desc."},
     {"process": "P", "path": "a/h", "kind": 2, "units": 0, "amount": 2000, "description": "Desc."},
 
-    {"process": "P2 (pid 33)", "path": "z-moz-nullprincipal:{161effaa-c1f7-4010-a08e-e7c9aea01aed} 0x5678-blah(0x6789) pid 456 pid=7890", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "p1 (pid 456)", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "p2 (blah, pid=456)", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "p3/zone(0x5678)/p3", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "p4/js-zone(0x5678)/p4", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "p5/worker(foo.com, 0x5678)/p5", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "explicit/window-objects/top(bar.com, id=456)/...", "kind": 0, "units": 0, "amount": 44, "description": "Desc."},
+    {"process": "P2 (pid 22)", "path": "p6/z-moz-nullprincipal:{161effaa-c1f7-4010-a08e-e7c9aea01aed}/p6", "kind": 2, "units": 0, "amount": 44, "description": "Desc."},
 
     {"process": "P4", "path": "p4", "kind": 2, "units": 0, "amount": 66, "description": "Desc."},
 
     {"process": "P6", "path": "p6", "kind": 2, "units": 0, "amount": 0, "description": "Desc."},
 
     {"process": "P7", "path": "p7/b", "kind": 2, "units": 0, "amount": 3, "description": "Desc."},
     {"process": "P7", "path": "p7/c", "kind": 2, "units": 0, "amount": 4, "description": "Desc."},
 
--- a/toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
+++ b/toolkit/components/aboutmemory/tests/test_aboutmemory3.xul
@@ -300,19 +300,39 @@ 0.96 MB (100.0%) -- a\n\
 │  └──0.00 MB (00.20%) ++ (2 tiny)\n\
 └──0.00 MB (00.10%) ── h\n\
 \n\
  0.00 MB ── canvas-2d-pixel-bytes [2] [+]\n\
 -0.00 MB ── foobar [-]\n\
 \n\
 End of P\n\
 P2 (pid NNN)\n\
+\n\
+WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
+Explicit Allocations\n\
+\n\
+0.00 MB (100.0%) -- explicit\n\
+└──0.00 MB (100.0%) ── window-objects/top(bar.com, id=NNN)/...\n\
+\n\
 Other Measurements\n\
 \n\
-0.00 MB ── z-moz-nullprincipal:{NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN} 0xNNN-blah(0xNNN) pid NNN pid=NNN\n\
+0.00 MB (100.0%) -- p3\n\
+└──0.00 MB (100.0%) ── zone(0xNNN)/p3\n\
+\n\
+0.00 MB (100.0%) -- p4\n\
+└──0.00 MB (100.0%) ── js-zone(0xNNN)/p4\n\
+\n\
+0.00 MB (100.0%) -- p5\n\
+└──0.00 MB (100.0%) ── worker(foo.com, 0xNNN)/p5\n\
+\n\
+0.00 MB (100.0%) -- p6\n\
+└──0.00 MB (100.0%) ── z-moz-nullprincipal:{NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN}/p6\n\
+\n\
+0.00 MB ── p1 (pid NNN)\n\
+0.00 MB ── p2 (blah, pid=NNN)\n\
 \n\
 End of P2 (pid NNN)\n\
 P3\n\
 Other Measurements\n\
 \n\
 -0.00 MB ── p3 [-]\n\
 \n\
 End of P3\n\
@@ -375,19 +395,39 @@ 1,002,000 B (100.0%) -- a\n\
 │      └──1,000 B (00.10%) ── g\n\
 └──────1,000 B (00.10%) ── h\n\
 \n\
 3,000 B ── canvas-2d-pixel-bytes [2] [+]\n\
  -100 B ── foobar [-]\n\
 \n\
 End of P\n\
 P2 (pid NNN)\n\
+\n\
+WARNING: the 'heap-allocated' memory reporter does not work for this platform and/or configuration. This means that 'heap-unclassified' is not shown and the 'explicit' tree shows less memory than it should.\n\
+Explicit Allocations\n\
+\n\
+11 B (100.0%) -- explicit\n\
+└──11 B (100.0%) ── window-objects/top(bar.com, id=NNN)/...\n\
+\n\
 Other Measurements\n\
 \n\
-11 B ── z-moz-nullprincipal:{NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN} 0xNNN-blah(0xNNN) pid NNN pid=NNN\n\
+11 B (100.0%) -- p3\n\
+└──11 B (100.0%) ── zone(0xNNN)/p3\n\
+\n\
+11 B (100.0%) -- p4\n\
+└──11 B (100.0%) ── js-zone(0xNNN)/p4\n\
+\n\
+11 B (100.0%) -- p5\n\
+└──11 B (100.0%) ── worker(foo.com, 0xNNN)/p5\n\
+\n\
+11 B (100.0%) -- p6\n\
+└──11 B (100.0%) ── z-moz-nullprincipal:{NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN}/p6\n\
+\n\
+11 B ── p1 (pid NNN)\n\
+11 B ── p2 (blah, pid=NNN)\n\
 \n\
 End of P2 (pid NNN)\n\
 P3\n\
 Other Measurements\n\
 \n\
 -55 B ── p3 [-]\n\
 \n\
 End of P3\n\
--- a/toolkit/content/browser-child.js
+++ b/toolkit/content/browser-child.js
@@ -87,16 +87,17 @@ let WebProgressListener = {
     // These properties can change even for a sub-frame navigation.
     json.canGoBack = docShell.canGoBack;
     json.canGoForward = docShell.canGoForward;
 
     if (json.isTopLevel) {
       json.documentURI = content.document.documentURIObject.spec;
       json.charset = content.document.characterSet;
       json.mayEnableCharacterEncodingMenu = docShell.mayEnableCharacterEncodingMenu;
+      json.principal = content.document.nodePrincipal;
     }
 
     sendAsyncMessage("Content:LocationChange", json, objects);
   },
 
   onStatusChange: function onStatusChange(aWebProgress, aRequest, aStatus, aMessage) {
     let json = this._setupJSON(aWebProgress, aRequest);
     let objects = this._setupObjects(aWebProgress);
--- a/toolkit/content/widgets/browser.xml
+++ b/toolkit/content/widgets/browser.xml
@@ -442,17 +442,17 @@
         ]]></setter>
       </property>
 
       <property name="mayEnableCharacterEncodingMenu"
                 onget="return this.docShell.mayEnableCharacterEncodingMenu;"
                 readonly="true"/>
 
       <property name="contentPrincipal"
-                onget="return this.contentDocumentAsCPOW.nodePrincipal;"
+                onget="return this.contentDocument.nodePrincipal;"
                 readonly="true"/>
 
       <property name="showWindowResizer"
                 onset="if (val) this.setAttribute('showresizer', 'true');
                        else this.removeAttribute('showresizer');
                        return val;"
                 onget="return this.getAttribute('showresizer') == 'true';"/>
 
--- a/toolkit/content/widgets/remote-browser.xml
+++ b/toolkit/content/widgets/remote-browser.xml
@@ -130,16 +130,22 @@
       <property name="contentWindowAsCPOW"
                 onget="return this._contentWindow"
                 readonly="true"/>
 
       <property name="contentDocument"
                 onget="return null"
                 readonly="true"/>
 
+      <field name="_contentPrincipal">null</field>
+
+      <property name="contentPrincipal"
+                onget="return this._contentPrincipal"
+                readonly="true"/>
+
       <property name="contentDocumentAsCPOW"
                 onget="return this.contentWindowAsCPOW ? this.contentWindowAsCPOW.document : null"
                 readonly="true"/>
 
       <field name="_syncHandler">null</field>
 
       <property name="syncHandler"
                 onget="return this._syncHandler"
--- a/toolkit/devtools/server/actors/memory.js
+++ b/toolkit/devtools/server/actors/memory.js
@@ -1,18 +1,19 @@
 /* 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 { Cc, Ci, Cu } = require("chrome");
 let protocol = require("devtools/server/protocol");
-let { method, RetVal } = protocol;
+let { method, RetVal, Arg } = protocol;
 const { reportException } = require("devtools/toolkit/DevToolsUtils");
+loader.lazyRequireGetter(this, "events", "sdk/event/core");
 
 /**
  * A method decorator that ensures the actor is in the expected state before
  * proceeding. If the actor is not in the expected state, the decorated method
  * returns a rejected promise.
  *
  * @param String expectedState
  *        The expected state.
@@ -54,55 +55,317 @@ let MemoryActor = protocol.ActorClass({
 
   initialize: function(conn, parent) {
     protocol.Actor.prototype.initialize.call(this, conn);
     this.parent = parent;
     this._mgr = Cc["@mozilla.org/memory-reporter-manager;1"]
                   .getService(Ci.nsIMemoryReporterManager);
     this.state = "detached";
     this._dbg = null;
+    this._framesToCounts = null;
+    this._framesToIndices = null;
+    this._framesToForms = null;
+
+    this._onWindowReady = this._onWindowReady.bind(this);
+
+    events.on(this.parent, "window-ready", this._onWindowReady);
   },
 
   destroy: function() {
+    events.off(this.parent, "window-ready", this._onWindowReady);
+
     this._mgr = null;
     if (this.state === "attached") {
       this.detach();
     }
     protocol.Actor.prototype.destroy.call(this);
   },
 
   /**
    * Attach to this MemoryActor.
    */
   attach: method(expectState("detached", function() {
     this.dbg.addDebuggees();
-    this.dbg.enabled = true;
     this.state = "attached";
   }), {
     request: {},
     response: {
       type: "attached"
     }
   }),
 
   /**
    * Detach from this MemoryActor.
    */
   detach: method(expectState("attached", function() {
-    this.dbg.removeAllDebuggees();
+    this._clearDebuggees();
     this.dbg.enabled = false;
     this._dbg = null;
     this.state = "detached";
   }), {
     request: {},
     response: {
       type: "detached"
     }
   }),
 
+  _clearDebuggees: function() {
+    if (this._dbg) {
+      if (this.dbg.memory.trackingAllocationSites) {
+        this.dbg.memory.drainAllocationsLog();
+      }
+      this._clearFrames();
+      this.dbg.removeAllDebuggees();
+    }
+  },
+
+  _initFrames: function() {
+    this._framesToCounts = new Map();
+    this._framesToIndices = new Map();
+    this._framesToForms = new Map();
+  },
+
+  _clearFrames: function() {
+    if (this.dbg.memory.trackingAllocationSites) {
+      this._framesToCounts.clear();
+      this._framesToCounts = null;
+      this._framesToIndices.clear();
+      this._framesToIndices = null;
+      this._framesToForms.clear();
+      this._framesToForms = null;
+    }
+  },
+
+  /**
+   * Handler for the parent actor's "window-ready" event.
+   */
+  _onWindowReady: function({ isTopLevel }) {
+    if (this.state == "attached") {
+      if (isTopLevel && this.dbg.memory.trackingAllocationSites) {
+        this._clearDebuggees();
+        this._initFrames();
+      }
+      this.dbg.addDebuggees();
+    }
+  },
+
+  /**
+   * Take a census of the heap. See js/src/doc/Debugger/Debugger.Memory.md for
+   * more information.
+   */
+  takeCensus: method(expectState("attached", function() {
+    return this.dbg.memory.takeCensus();
+  }), {
+    request: {},
+    response: RetVal("json")
+  }),
+
+  /**
+   * Start recording allocation sites.
+   */
+  startRecordingAllocations: method(expectState("attached", function() {
+    this._initFrames();
+    this.dbg.memory.trackingAllocationSites = true;
+  }), {
+    request: {},
+    response: {}
+  }),
+
+  /**
+   * Stop recording allocation sites.
+   */
+  stopRecordingAllocations: method(expectState("attached", function(shouldRecord) {
+    this.dbg.memory.trackingAllocationSites = false;
+    this._clearFrames();
+  }), {
+    request: {},
+    response: {}
+  }),
+
+  /**
+   * Get a list of the most recent allocations since the last time we got
+   * allocations, as well as a summary of all allocations since we've been
+   * recording.
+   *
+   * @returns Object
+   *          An object of the form:
+   *
+   *            {
+   *              allocations: [<index into "frames" below> ...],
+   *              frames: [
+   *                {
+   *                  line: <line number for this frame>,
+   *                  column: <column number for this frame>,
+   *                  source: <filename string for this frame>,
+   *                  functionDisplayName: <this frame's inferred function name function or null>,
+   *                  parent: <index into "frames">
+   *                }
+   *                ...
+   *              ],
+   *              counts: [
+   *                <number of allocations in frames[0]>,
+   *                <number of allocations in frames[1]>,
+   *                <number of allocations in frames[2]>,
+   *                ...
+   *              ]
+   *            }
+   *
+   *          Subsequent `getAllocations` request within the same recording and
+   *          tab navigation will always place the same stack frames at the same
+   *          indices as previous `getAllocations` requests in the same
+   *          recording. In other words, it is safe to use the index as a
+   *          unique, persistent id for its frame.
+   *
+   *          Additionally, the root node (null) is always at index 0.
+   *
+   *          Note that the allocation counts include "self" allocations only,
+   *          and don't account for allocations in child frames.
+   *
+   *          We use the indices into the "frames" array to avoid repeating the
+   *          description of duplicate stack frames both when listing
+   *          allocations, and when many stacks share the same tail of older
+   *          frames. There shouldn't be any duplicates in the "frames" array,
+   *          as that would defeat the purpose of this compression trick.
+   *
+   *          In the future, we might want to split out a frame's "source" and
+   *          "functionDisplayName" properties out the same way we have split
+   *          frames out with the "frames" array. While this would further
+   *          compress the size of the response packet, it would increase CPU
+   *          usage to build the packet, and it should, of course, be guided by
+   *          profiling and done only when necessary.
+   */
+  getAllocations: method(expectState("attached", function() {
+    const allocations = this.dbg.memory.drainAllocationsLog()
+    const packet = {
+      allocations: []
+    };
+
+    for (let stack of allocations) {
+      if (stack && Cu.isDeadWrapper(stack)) {
+        continue;
+      }
+
+      // Safe because SavedFrames are frozen/immutable.
+      let waived = Cu.waiveXrays(stack);
+
+      // Ensure that we have a form, count, and index for new allocations
+      // because we potentially haven't seen some or all of them yet. After this
+      // loop, we can rely on the fact that every frame we deal with already has
+      // its metadata stored.
+      this._assignFrameIndices(waived);
+      this._createFrameForms(waived);
+      this._countFrame(waived);
+
+      packet.allocations.push(this._framesToIndices.get(waived));
+    }
+
+    // Now that we are guaranteed to have a form for every frame, we know the
+    // size the "frames" property's array must be. We use that information to
+    // create dense arrays even though we populate them out of order.
+    const size = this._framesToForms.size;
+    packet.frames = Array(size).fill(null);
+    packet.counts = Array(size).fill(0);
+
+    // Populate the "frames" and "counts" properties.
+    for (let [stack, index] of this._framesToIndices) {
+      packet.frames[index] = this._framesToForms.get(stack);
+      packet.counts[index] = this._framesToCounts.get(stack) || 0;
+    }
+
+    return packet;
+  }), {
+    request: {},
+    response: RetVal("json")
+  }),
+
+  /**
+   * Assigns an index to the given frame and its parents, if an index is not
+   * already assigned.
+   *
+   * @param SavedFrame frame
+   *        A frame to assign an index to.
+   */
+  _assignFrameIndices: function(frame) {
+    if (this._framesToIndices.has(frame)) {
+      return;
+    }
+
+    if (frame) {
+      this._assignFrameIndices(frame.parent);
+    }
+
+    const index = this._framesToIndices.size;
+    this._framesToIndices.set(frame, index);
+  },
+
+  /**
+   * Create the form for the given frame, if one doesn't already exist.
+   *
+   * @param SavedFrame frame
+   *        A frame to create a form for.
+   */
+  _createFrameForms: function(frame) {
+    if (this._framesToForms.has(frame)) {
+      return;
+    }
+
+    let form = null;
+    if (frame) {
+      form = {
+        line: frame.line,
+        column: frame.column,
+        source: frame.source,
+        functionDisplayName: frame.functionDisplayName,
+        parent: this._framesToIndices.get(frame.parent)
+      };
+      this._createFrameForms(frame.parent);
+    }
+
+    this._framesToForms.set(frame, form);
+  },
+
+  /**
+   * Increment the allocation count for the provided frame.
+   *
+   * @param SavedFrame frame
+   *        The frame whose allocation count should be incremented.
+   */
+  _countFrame: function(frame) {
+    if (!this._framesToCounts.has(frame)) {
+      this._framesToCounts.set(frame, 1);
+    } else {
+      let count = this._framesToCounts.get(frame);
+      this._framesToCounts.set(frame, count + 1);
+    }
+  },
+
+  /*
+   * Force a browser-wide GC.
+   */
+  forceGarbageCollection: method(function() {
+    for (let i = 0; i < 3; i++) {
+      Cu.forceGC();
+    }
+  }, {
+    request: {},
+    response: {}
+  }),
+
+  /**
+   * Force an XPCOM cycle collection. For more information on XPCOM cycle
+   * collection, see
+   * https://developer.mozilla.org/en-US/docs/Interfacing_with_the_XPCOM_cycle_collector#What_the_cycle_collector_does
+   */
+  forceCycleCollection: method(function() {
+    Cu.forceCC();
+  }, {
+    request: {},
+    response: {}
+  }),
+
   /**
    * A method that returns a detailed breakdown of the memory consumption of the
    * associated window.
    *
    * @returns object
    */
   measure: method(function() {
     let result = {};
--- a/toolkit/devtools/server/tests/mochitest/chrome.ini
+++ b/toolkit/devtools/server/tests/mochitest/chrome.ini
@@ -69,13 +69,18 @@ skip-if = buildapp == 'mulet'
 [test_styles-matched.html]
 [test_styles-modify.html]
 [test_styles-svg.html]
 [test_unsafeDereference.html]
 [test_evalInGlobal-outerized_this.html]
 [test_inspector_getImageData.html]
 skip-if = buildapp == 'mulet'
 [test_memory.html]
+[test_memory_allocations_01.html]
+[test_memory_allocations_02.html]
+[test_memory_allocations_03.html]
 [test_memory_attach_01.html]
 [test_memory_attach_02.html]
+[test_memory_census.html]
+[test_memory_gc_01.html]
 [test_preference.html]
 [test_connectToChild.html]
 skip-if = buildapp == 'mulet'
--- a/toolkit/devtools/server/tests/mochitest/memory-helpers.js
+++ b/toolkit/devtools/server/tests/mochitest/memory-helpers.js
@@ -47,8 +47,14 @@ function startServerAndGetSelectedTabMem
 }
 
 function destroyServerAndFinish(client) {
   client.close(() => {
     DebuggerServer.destroy();
     SimpleTest.finish()
   });
 }
+
+function waitForTime(ms) {
+  return new Promise((resolve, reject) => {
+    setTimeout(resolve, ms);
+  });
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_memory_allocations_01.html
@@ -0,0 +1,98 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test recording allocations.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Memory monitoring actor 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="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+  SimpleTest.waitForExplicitFinish();
+
+  Task.spawn(function* () {
+    var { memory, client } = yield startServerAndGetSelectedTabMemory();
+    yield memory.attach();
+
+    yield memory.startRecordingAllocations();
+    ok(true, "Can start recording allocations");
+
+    // Allocate some objects.
+
+    var alloc1, alloc2, alloc3;
+    (function outer() {
+      (function middle() {
+        (function inner() {
+          alloc1 = {};                alloc1.line = Error().lineNumber;
+          alloc2 = [];                alloc2.line = Error().lineNumber;
+          alloc3 = new function() {}; alloc3.line = Error().lineNumber;
+        }());
+      }());
+    }());
+
+    var response = yield memory.getAllocations();
+
+    yield memory.stopRecordingAllocations();
+    ok(true, "Can stop recording allocations");
+
+    // Filter out allocations by library and test code, and get only the
+    // allocations that occurred in our test case above.
+
+    function isTestAllocation(alloc) {
+      var frame = response.frames[alloc];
+      return frame
+        && frame.functionDisplayName === "inner"
+        && (frame.line === alloc1.line
+            || frame.line === alloc2.line
+            || frame.line === alloc3.line);
+    }
+
+    var testAllocations = response.allocations.filter(isTestAllocation);
+    ok(testAllocations.length >= 3,
+       "Should find our 3 test allocations (plus some allocations for the error "
+       + "objects used to get line numbers)");
+
+    // For each of the test case's allocations, ensure that the parent frame
+    // indices are correct. Also test that we did get an allocation at each
+    // line we expected (rather than a bunch on the first line and none on the
+    // others, etc).
+
+    var expectedLines = new Set([alloc1.line, alloc2.line, alloc3.line]);
+
+    for (var alloc of testAllocations) {
+      var innerFrame = response.frames[alloc];
+      ok(innerFrame, "Should get the inner frame");
+      is(innerFrame.functionDisplayName, "inner");
+      expectedLines.delete(innerFrame.line);
+
+      var middleFrame = response.frames[innerFrame.parent];
+      ok(middleFrame, "Should get the middle frame");
+      is(middleFrame.functionDisplayName, "middle");
+
+      var outerFrame = response.frames[middleFrame.parent];
+      ok(outerFrame, "Should get the outer frame");
+      is(outerFrame.functionDisplayName, "outer");
+
+      // Not going to test the rest of the frames because they are Task.jsm
+      // and promise frames and it gets gross. Plus, I wouldn't want this test
+      // to start failing if they changed their implementations in a way that
+      // added or removed stack frames here.
+    }
+
+    is(expectedLines.size, 0,
+       "Should have found all the expected lines");
+
+    yield memory.detach();
+    destroyServerAndFinish(client);
+  });
+};
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_memory_allocations_02.html
@@ -0,0 +1,64 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test aggregating allocation counts.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Memory monitoring actor 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="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+  SimpleTest.waitForExplicitFinish();
+
+  Task.spawn(function* () {
+    var { memory, client } = yield startServerAndGetSelectedTabMemory();
+    yield memory.attach();
+
+    yield memory.startRecordingAllocations();
+    ok(true, "Can start recording allocations");
+
+    // Allocate some objects.
+
+    var allocs = [];
+    (function allocator() {
+      for (var i = 0; i < 10; i++) {
+        allocs.push({});
+      }
+    }());
+
+    var response = yield memory.getAllocations();
+
+    yield memory.stopRecordingAllocations();
+    ok(true, "Can stop recording allocations");
+
+    // Find the index of our 10 allocations, and then assert that it is in the
+    // `allocator` frame.
+
+    var index = 0;
+    var found = false;
+    for (var count of response.counts) {
+      if (count === 10) {
+        found = true;
+        break;
+      }
+      index++;
+    }
+    ok(found, "Should find the 10 allocations.");
+
+    is(response.frames[index].functionDisplayName, "allocator",
+       "Should have found the allocator frame.");
+
+    yield memory.detach();
+    destroyServerAndFinish(client);
+  });
+};
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_memory_allocations_03.html
@@ -0,0 +1,78 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test that frames keep the same index while we are recording.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Memory monitoring actor 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="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+  SimpleTest.waitForExplicitFinish();
+
+  Task.spawn(function* () {
+    var { memory, client } = yield startServerAndGetSelectedTabMemory();
+    yield memory.attach();
+
+    yield memory.startRecordingAllocations();
+
+    // Allocate twice with the exact same stack (hence setTimeout rather than
+    // allocating directly in the generator), but with getAllocations() calls in
+    // between.
+
+    var allocs = [];
+    function allocator() {
+      allocs.push({});
+    }
+
+    setTimeout(allocator, 1);
+    yield waitForTime(2);
+    var first = yield memory.getAllocations();
+
+    setTimeout(allocator, 1);
+    yield waitForTime(2);
+    var second = yield memory.getAllocations();
+
+    yield memory.stopRecordingAllocations();
+
+    // Assert that each frame in the first response has the same index in the
+    // second response. This isn't commutative, so we don't check that all
+    // of the second response's frames are the same in the first response,
+    // because there might be new allocations that happen after the first query
+    // but before the second.
+
+    function assertSameFrame(a, b) {
+      info("Checking frames at index " + i + ":");
+      info("  First frame = " + JSON.stringify(a, null, 4));
+      info("  Second frame = " + JSON.stringify(b, null, 4));
+
+      is(!!a, !!b);
+      if (!a || !b) {
+        return;
+      }
+
+      is(a.source, b.source);
+      is(a.line, b.line);
+      is(a.column, b.column);
+      is(a.functionDisplayName, b.functionDisplayName);
+      is(a.parent, b.parent);
+    }
+
+    for (var i = 0; i < first.frames.length; i++) {
+      assertSameFrame(first.frames[i], second.frames[i]);
+    }
+
+    yield memory.detach();
+    destroyServerAndFinish(client);
+  });
+};
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_memory_census.html
@@ -0,0 +1,33 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test taking a census over the RDP.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Memory monitoring actor 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="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+  SimpleTest.waitForExplicitFinish();
+
+  Task.spawn(function* () {
+    var { memory, client } = yield startServerAndGetSelectedTabMemory();
+    yield memory.attach();
+
+    var census = yield memory.takeCensus();
+    is(typeof census, "object");
+
+    yield memory.detach();
+    destroyServerAndFinish(client);
+  });
+};
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/toolkit/devtools/server/tests/mochitest/test_memory_gc_01.html
@@ -0,0 +1,43 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+Bug 1067491 - Test forcing a gc.
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Memory monitoring actor 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="memory-helpers.js" type="application/javascript;version=1.8"></script>
+<script>
+window.onload = function() {
+  SimpleTest.waitForExplicitFinish();
+
+  Task.spawn(function* () {
+    var { memory, client } = yield startServerAndGetSelectedTabMemory();
+
+    var objects = [];
+    for (var i = 0; i < 1000; i++) {
+      var o = {};
+      o[Math.random] = 1;
+      objects.push(o);
+    }
+
+    objects = null;
+    var { total: beforeGC } = yield memory.measure();
+
+    yield memory.forceGarbageCollection();
+    var { total: afterGC } = yield memory.measure();
+
+    ok(beforeGC > afterGC);
+
+    destroyServerAndFinish(client);
+  });
+};
+</script>
+</pre>
+</body>
+</html>
deleted file mode 100644
--- a/toolkit/locales/en-US/chrome/global/xpinstall/xpinstall.properties
+++ /dev/null
@@ -1,95 +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/.
-
-#--------------------------------------------------------------------
-#   Install Actions
-#--------------------------------------------------------------------
-InstallFile=Installing: %s
-InstallSharedFile=Installing Shared File: %s
-ReplaceFile=Replacing: %s
-ReplaceSharedFile=Replacing Shared File: %s
-SkipFile=Skipping: %s
-SkipSharedFile=Skipping Shared File: %s
-
-DeleteFile=Deleting file: %s
-DeleteComponent=Deleting component: %s
-
-Execute=Executing: %s
-ExecuteWithArgs=Executing: %s with argument: %s
-
-CopyFile=Copy File: %s to %s
-ExecuteFile=Execute File: %s
-ExecuteFileWithArgs=Execute File: %s with argument: %s
-MoveFile=Move File: %s to %s
-RenameFile=Rename File: %s to %s
-CreateFolder=Create Folder: %s
-RemoveFolder=Remove Folder: %s
-RenameFolder=Rename Folder: %s to %s
-WindowsShortcut=Windows Shortcut: %s
-MacAlias=Mac Alias: %s
-WindowsRegisterServer=Windows Register Server: %s
-UnknownFileOpCommand=Unknown file operation command!
-
-Patch=Patching: %s
-
-Uninstall=Uninstalling: %s
-
-RegSkin=Register Skin: %s
-RegLocale=Register Locale: %s
-RegContent=Register Content: %s
-RegPackage=Register Package: %s
-
-#--------------------------------------------------------------------
-#   Dialog Messages
-#--------------------------------------------------------------------
-
-ApplyNowSkin=Use this theme
-ApplyNowLocale=Use this locale
-
-ConfirmSkin=Install the theme "%1$S" from %2$S?
-ConfirmLocale=Install the locale "%1$S" from %2$S?
-
-OK=Install
-
-progress.queued=Queued
-progress.downloading=Downloading…
-progress.downloaded=Downloaded
-progress.installing=Installing…
-
-Unsigned=Unsigned
-
-#--------------------------------------------------------------------
-#   Miscellaneous
-#--------------------------------------------------------------------
-ERROR=ERROR
-
-error0=Success
-error999=Restart to complete
-error-202=Access denied
-error-203=Unexpected installation error\nReview the Error Console log for more details.
-error-204=Install script not found
-error-207=Not a valid install package
-error-208=Invalid argument
-error-210=User canceled
-error-214=Required file does not exist
-error-215=Read only
-error-218=AppleSingle extraction error
-error-219=Invalid path
-error-225=EXTRACTION_FAILED
-error-227=Canceled
-error-228=Download error
-error-229=Script error
-error-230=Already exists
-error-235=Out of space
-error-239=Chrome registration failed
-error-240=Unfinished install
-error-244=Unsupported package
-error-260=Signing could not be verified.
-error-261=Invalid file hash (possible download corruption)
-error-262=Unknown or invalid file hash type
-error-299=Out of memory
-
-# there are other error codes, either rare or obsolete,
-# that are not worth translating at this time.
-unknown.error=Unexpected error %S
--- a/toolkit/locales/en-US/chrome/mozapps/plugins/plugins.dtd
+++ b/toolkit/locales/en-US/chrome/mozapps/plugins/plugins.dtd
@@ -1,32 +1,11 @@
 <!-- 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/. -->
-<!ENTITY pluginWizard.title                                  "Plugin Finder Service">
-<!ENTITY pluginWizard.firstPage.title                        "Welcome to the &pluginWizard.title;">
-
-<!ENTITY pluginWizard.checkingForPlugins.description.label   "&brandShortName; is now checking for available plugins…">
-
-<!ENTITY pluginWizard.availablePluginsPage.title             "Available Plugin Downloads">
-<!ENTITY pluginWizard.availablePluginsPage.description.label "The following plugins are available:">
-<!ENTITY pluginWizard.availablePluginsPage.continueMsg.label "Press Next to install these plugins.">
-<!ENTITY pluginWizard.availablePluginsPage.installerUI       "Some plugins may require additional information from you during installation.">
-
-<!ENTITY pluginWizard.licensePage.title                      "Plugin Licenses">
-<!ENTITY pluginWizard.licensePage.accept.label               "I agree.">
-<!ENTITY pluginWizard.licensePage.deny.label                 "I do not agree (plugin will not be installed).">
-
-<!ENTITY pluginWizard.installPluginsPage.title               "Installing Plugins">
-<!ENTITY pluginWizard.installPluginsPage.description.label   "&brandShortName; is installing plugins…">
-
-<!ENTITY pluginWizard.finalPage.description.label            "&brandShortName; finished installing the missing plugins:">
-
-<!ENTITY pluginWizard.finalPage.moreInfo.label               "Find out more about Plugins or manually find missing plugins.">
-<!ENTITY pluginWizard.finalPage.restart.label                "&brandShortName; needs to be restarted for the plugin(s) to work.">
 
 <!-- LOCALIZATION NOTE (unsupportedPlatform.pre): Mobile only. Flash (the only plugin available on mobile)
      is not supported on some devices. Include a trailing space as needed. -->
 <!ENTITY unsupportedPlatform.pre                             "We're very sorry, but &brandShortName; can't play Flash on this device. ">
 <!-- LOCALIZATION NOTE (unsupportedPlatform.learnMore): Mobile only. This text is used to link to a SUMO page explaining why Flash is not
      supported on this device. Use the unicode ellipsis char, \u2026, or use "..." if \u2026 doesn't suit traditions in your locale. -->
 <!ENTITY unsupportedPlatform.learnMore                       "Learn More…">
 <!-- LOCALIZATION NOTE (unsupportedPlatform.post): Mobile only. Include text here if needed for your locale. -->
@@ -35,17 +14,16 @@
 <!ENTITY missingPlugin                                       "A plugin is needed to display this content.">
 <!-- LOCALIZATION NOTE (tapToPlayPlugin): Mobile (used for touch interfaces) only has one type of plugin possible. -->
 <!ENTITY tapToPlayPlugin                                     "Tap here to activate plugin.">
 <!ENTITY clickToActivatePlugin                               "Activate plugin.">
 <!ENTITY checkForUpdates                                     "Check for updates…">
 <!ENTITY disabledPlugin                                      "This plugin is disabled.">
 <!ENTITY blockedPlugin.label                                 "This plugin has been blocked for your protection.">
 <!ENTITY hidePluginBtn.label                                 "Hide plugin">
-<!ENTITY installPlugin                                       "Install plugin…">
 <!ENTITY managePlugins                                       "Manage plugins…">
 
 <!-- LOCALIZATION NOTE (reloadPlugin.pre): include a trailing space as needed -->
 <!-- LOCALIZATION NOTE (reloadPlugin.middle): avoid leading/trailing spaces, this text is a link -->
 <!-- LOCALIZATION NOTE (reloadPlugin.post): include a starting space as needed -->
 <!ENTITY reloadPlugin.pre                                    "">
 <!ENTITY reloadPlugin.middle                                 "Reload the page">
 <!ENTITY reloadPlugin.post                                   " to try again.">
deleted file mode 100644
--- a/toolkit/locales/en-US/chrome/mozapps/plugins/plugins.properties
+++ /dev/null
@@ -1,30 +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/.
-
-pluginLicenseAgreement.label=To install %S, you need to agree to the following:
-
-pluginInstallation.download.start=Downloading %S…
-pluginInstallation.download.finish=Finished downloading %S.
-
-pluginInstallation.install.start=Installing %S…
-pluginInstallation.install.finish=Successfully installed %S.
-pluginInstallation.install.error=Failed to install %S (%S).
-
-pluginInstallation.complete=Finished installing plugins.
-
-pluginInstallationSummary.success=Installed
-pluginInstallationSummary.failed=Failed
-pluginInstallationSummary.licenseNotAccepted=License not accepted
-pluginInstallationSummary.notAvailable=Not Available
-pluginInstallationSummary.manualInstall.label=Manual Install
-pluginInstallationSummary.manualInstall.tooltip=Manually install the plugin.
-
-pluginInstallation.noPluginsFound=No suitable plugins were found.
-pluginInstallation.noPluginsInstalled=No plugins were installed.
-pluginInstallation.unknownPlugin=Unknown Plugin (%S)
-
-pluginInstallation.restart.label=Restart %S
-pluginInstallation.restart.accesskey=R
-pluginInstallation.close.label=Close
-pluginInstallation.close.accesskey=C
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -67,17 +67,16 @@
   locale/@AB_CD@/global/tree.dtd                        (%chrome/global/tree.dtd)
   locale/@AB_CD@/global/textcontext.dtd                 (%chrome/global/textcontext.dtd)
   locale/@AB_CD@/global/videocontrols.dtd               (%chrome/global/videocontrols.dtd)
   locale/@AB_CD@/global/viewSource.dtd                  (%chrome/global/viewSource.dtd)
   locale/@AB_CD@/global/viewSource.properties           (%chrome/global/viewSource.properties)
   locale/@AB_CD@/global/webapps.properties              (%chrome/global/webapps.properties)
   locale/@AB_CD@/global/wizard.dtd                      (%chrome/global/wizard.dtd)
   locale/@AB_CD@/global/wizard.properties               (%chrome/global/wizard.properties)
-  locale/@AB_CD@/global/xpinstall/xpinstall.properties  (%chrome/global/xpinstall/xpinstall.properties)
   locale/@AB_CD@/global/crashes.dtd                     (%crashreporter/crashes.dtd)
   locale/@AB_CD@/global/crashes.properties              (%crashreporter/crashes.properties)
 % locale global-region @AB_CD@ %locale/@AB_CD@/global-region/
   locale/@AB_CD@/global-region/region.properties        (%chrome/global-region/region.properties)
 % locale global-platform @AB_CD@ %locale/@AB_CD@/global-platform/
   locale/@AB_CD@/global-platform/mac/platformKeys.properties  (%chrome/global-platform/mac/platformKeys.properties)
   locale/@AB_CD@/global-platform/unix/platformKeys.properties (%chrome/global-platform/unix/platformKeys.properties)
   locale/@AB_CD@/global-platform/win/platformKeys.properties  (%chrome/global-platform/win/platformKeys.properties)
@@ -98,17 +97,16 @@
   locale/@AB_CD@/mozapps/extensions/selectAddons.properties       (%chrome/mozapps/extensions/selectAddons.properties)
   locale/@AB_CD@/mozapps/extensions/update.dtd                    (%chrome/mozapps/extensions/update.dtd)
   locale/@AB_CD@/mozapps/extensions/update.properties             (%chrome/mozapps/extensions/update.properties)
   locale/@AB_CD@/mozapps/extensions/newaddon.dtd                  (%chrome/mozapps/extensions/newaddon.dtd)
   locale/@AB_CD@/mozapps/extensions/newaddon.properties           (%chrome/mozapps/extensions/newaddon.properties)
   locale/@AB_CD@/mozapps/handling/handling.dtd                    (%chrome/mozapps/handling/handling.dtd)
   locale/@AB_CD@/mozapps/handling/handling.properties             (%chrome/mozapps/handling/handling.properties)
   locale/@AB_CD@/mozapps/plugins/plugins.dtd                      (%chrome/mozapps/plugins/plugins.dtd)
-  locale/@AB_CD@/mozapps/plugins/plugins.properties               (%chrome/mozapps/plugins/plugins.properties)
   locale/@AB_CD@/mozapps/preferences/changemp.dtd                 (%chrome/mozapps/preferences/changemp.dtd)
   locale/@AB_CD@/mozapps/preferences/removemp.dtd                 (%chrome/mozapps/preferences/removemp.dtd)
   locale/@AB_CD@/mozapps/preferences/preferences.properties       (%chrome/mozapps/preferences/preferences.properties)
   locale/@AB_CD@/mozapps/profile/createProfileWizard.dtd          (%chrome/mozapps/profile/createProfileWizard.dtd)
   locale/@AB_CD@/mozapps/profile/profileSelection.properties      (%chrome/mozapps/profile/profileSelection.properties)
   locale/@AB_CD@/mozapps/profile/profileSelection.dtd             (%chrome/mozapps/profile/profileSelection.dtd)
   locale/@AB_CD@/mozapps/update/updates.dtd                       (%chrome/mozapps/update/updates.dtd)
   locale/@AB_CD@/mozapps/update/updates.properties                (%chrome/mozapps/update/updates.properties)
--- a/toolkit/modules/RemoteWebProgress.jsm
+++ b/toolkit/modules/RemoteWebProgress.jsm
@@ -171,16 +171,17 @@ RemoteWebProgressManager.prototype = {
       this._browser.webNavigation.canGoForward = json.canGoForward;
 
       if (json.isTopLevel) {
         this._browser.webNavigation._currentURI = location;
         this._browser._characterSet = json.charset;
         this._browser._documentURI = newURI(json.documentURI);
         this._browser._imageDocument = null;
         this._browser._mayEnableCharacterEncodingMenu = json.mayEnableCharacterEncodingMenu;
+        this._browser._contentPrincipal = json.principal;
       }
 
       this._callProgressListeners("onLocationChange", webProgress, request, location, flags);
       break;
 
     case "Content:SecurityChange":
       let [status, state] = this._fixSSLStatusAndState(json.status, json.state);
 
deleted file mode 100644
--- a/toolkit/mozapps/plugins/content/pluginInstallerDatasource.js
+++ /dev/null
@@ -1,150 +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/. */
-
-const PFS_NS = "http://www.mozilla.org/2004/pfs-rdf#";
-
-function nsRDFItemUpdater(aClientOS, aChromeLocale) {
-  this._rdfService = Components.classes["@mozilla.org/rdf/rdf-service;1"]
-                               .getService(Components.interfaces.nsIRDFService);
-  this._os = Components.classes["@mozilla.org/observer-service;1"]
-                       .getService(Components.interfaces.nsIObserverService);
-
-  var app = Components.classes["@mozilla.org/xre/app-info;1"]
-                      .getService(Components.interfaces.nsIXULAppInfo);
-  this.appID = app.ID;
-  this.buildID = app.platformBuildID;
-  this.appRelease = app.version;
-
-  this.clientOS = aClientOS;
-  this.chromeLocale = aChromeLocale;
-
-  var prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
-                             .getService(Components.interfaces.nsIPrefBranch);
-  this.dsURI = prefBranch.getCharPref("pfs.datasource.url");
-}
-
-nsRDFItemUpdater.prototype = {
-  checkForPlugin: function (aPluginRequestItem) {
-    var dsURI = this.dsURI;
-    // escape the mimetype as mimetypes can contain '+', which will break pfs.
-    dsURI = dsURI.replace(/%PLUGIN_MIMETYPE%/g, encodeURIComponent(aPluginRequestItem.mimetype));
-    dsURI = dsURI.replace(/%APP_ID%/g, this.appID);
-    dsURI = dsURI.replace(/%APP_VERSION%/g, this.buildID);
-    dsURI = dsURI.replace(/%APP_RELEASE%/g, this.appRelease);
-    dsURI = dsURI.replace(/%CLIENT_OS%/g, this.clientOS);
-    dsURI = dsURI.replace(/%CHROME_LOCALE%/g, this.chromeLocale);
-
-    var ds = this._rdfService.GetDataSource(dsURI);
-    var rds = ds.QueryInterface(Components.interfaces.nsIRDFRemoteDataSource)
-    if (rds.loaded)
-      this.onDatasourceLoaded(ds, aPluginRequestItem);
-    else {
-      var sink = ds.QueryInterface(Components.interfaces.nsIRDFXMLSink);
-      sink.addXMLSinkObserver(new nsPluginXMLRDFDSObserver(this, aPluginRequestItem));
-    }
-  },
-
-  onDatasourceLoaded: function pfs_onDatasourceLoaded (aDatasource, aPluginRequestItem) {
-    var container = Components.classes["@mozilla.org/rdf/container;1"]
-                              .createInstance(Components.interfaces.nsIRDFContainer);
-    var resultRes = this._rdfService.GetResource("urn:mozilla:plugin-results:" + aPluginRequestItem.mimetype);
-    var pluginList = aDatasource.GetTarget(resultRes, this._rdfService.GetResource(PFS_NS+"plugins"), true);
-
-    var pluginInfo = null;
-  
-    try {
-      container.Init(aDatasource, pluginList);
-
-      var children = container.GetElements();
-      var target;
-
-      // get the first item
-      var child = children.getNext();
-      if (child instanceof Components.interfaces.nsIRDFResource) {
-        var name = this._rdfService.GetResource("http://www.mozilla.org/2004/pfs-rdf#updates");
-        target = aDatasource.GetTarget(child, name, true);
-      }
-
-      try {
-        container.Init(aDatasource, target);
-        target = null;
-        children = container.GetElements();
-
-        var child = children.getNext();
-        if (child instanceof Components.interfaces.nsIRDFResource) {
-          target = child;
-        }
-
-        var rdfs = this._rdfService;
-
-        function getPFSValueFromRDF(aValue) {
-          var rv = null;
-
-          var myTarget = aDatasource.GetTarget(target, rdfs.GetResource(PFS_NS + aValue), true);
-          if (myTarget)
-            rv = myTarget.QueryInterface(Components.interfaces.nsIRDFLiteral).Value;
-
-          return rv;
-        }
-
-        pluginInfo = {
-          name: getPFSValueFromRDF("name"),
-          pid: getPFSValueFromRDF("guid"),
-          version: getPFSValueFromRDF("version"),
-          IconUrl: getPFSValueFromRDF("IconUrl"),
-          InstallerLocation: getPFSValueFromRDF("InstallerLocation"),
-          InstallerHash: getPFSValueFromRDF("InstallerHash"),
-          XPILocation: getPFSValueFromRDF("XPILocation"),
-          XPIHash: getPFSValueFromRDF("XPIHash"),
-          InstallerShowsUI: getPFSValueFromRDF("InstallerShowsUI"),
-          manualInstallationURL: getPFSValueFromRDF("manualInstallationURL"),
-          requestedMimetype: getPFSValueFromRDF("requestedMimetype"),
-          licenseURL: getPFSValueFromRDF("licenseURL"),
-          needsRestart: getPFSValueFromRDF("needsRestart")
-        };
-      }
-      catch (ex) {
-        Components.utils.reportError(ex);
-      }
-    }
-    catch (ex) {
-      Components.utils.reportError(ex);
-    }
-    
-    gPluginInstaller.pluginInfoReceived(aPluginRequestItem, pluginInfo);
-  },
-
-  onDatasourceError: function pfs_onDatasourceError (aPluginRequestItem, aError) {
-    this._os.notifyObservers(aPluginRequestItem, "error", aError);
-    Components.utils.reportError(aError);
-    gPluginInstaller.pluginInfoReceived(aPluginRequestItem, null);
-  }
-};
-
-function nsPluginXMLRDFDSObserver(aUpdater, aPluginRequestItem) {
-  this._updater = aUpdater;
-  this._item    = aPluginRequestItem;
-}
-
-nsPluginXMLRDFDSObserver.prototype = 
-{ 
-  _updater  : null,
-  _item     : null,
-
-  // nsIRDFXMLSinkObserver
-  onBeginLoad: function(aSink) {},
-  onInterrupt: function(aSink) {},
-  onResume: function(aSink) {},
-  onEndLoad: function(aSink) {
-    aSink.removeXMLSinkObserver(this);
-    
-    var ds = aSink.QueryInterface(Components.interfaces.nsIRDFDataSource);
-    this._updater.onDatasourceLoaded(ds, this._item);
-  },
-  
-  onError: function(aSink, aStatus, aErrorMsg) {  
-    aSink.removeXMLSinkObserver(this);   
-    this._updater.onDatasourceError(this._item, aStatus.toString());
-  }
-};
deleted file mode 100644
--- a/toolkit/mozapps/plugins/content/pluginInstallerService.js
+++ /dev/null
@@ -1,299 +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/. */
-
-Components.utils.import("resource://gre/modules/AddonManager.jsm");
-
-const DOWNLOAD_STARTED = 0;
-const DOWNLOAD_FINISHED = 1;
-const INSTALL_STARTED = 2;
-const INSTALL_FINISHED = 3;
-const INSTALLS_COMPLETE = 4;
-
-function getLocalizedError(key)
-{
-  return document.getElementById("xpinstallStrings").getString(key);
-}
-
-function binaryToHex(input)
-{
-  return [('0' + input.charCodeAt(i).toString(16)).slice(-2)
-          for (i in input)].join('');
-}
-
-function verifyHash(aFile, aHash)
-{
-  try {
-    var [, method, hash] = /^([A-Za-z0-9]+):(.*)$/.exec(aHash);
-
-    var fis = Components.classes['@mozilla.org/network/file-input-stream;1'].
-      createInstance(Components.interfaces.nsIFileInputStream);
-    fis.init(aFile, -1, -1, 0);
-
-    var hasher = Components.classes['@mozilla.org/security/hash;1'].
-      createInstance(Components.interfaces.nsICryptoHash);
-    hasher.initWithString(method);
-    hasher.updateFromStream(fis, -1);
-    dlhash = binaryToHex(hasher.finish(false));
-    return dlhash == hash;
-  }
-  catch (e) {
-    Components.utils.reportError(e);
-    return false;
-  }
-}
-
-function InstallerObserver(aPlugin)
-{
-  this._plugin = aPlugin;
-  this._init();
-}
-
-InstallerObserver.prototype = {
-  _init: function()
-  {
-    try {
-      var ios = Components.classes["@mozilla.org/network/io-service;1"].
-        getService(Components.interfaces.nsIIOService);
-      var uri = ios.newURI(this._plugin.InstallerLocation, null, null);
-      uri.QueryInterface(Components.interfaces.nsIURL);
-
-      // Use a local filename appropriate for the OS
-      var leafName = uri.fileName;
-      var os = Components.classes["@mozilla.org/xre/app-info;1"]
-                         .getService(Components.interfaces.nsIXULRuntime)
-                         .OS;
-      if (os == "WINNT" && leafName.indexOf(".") < 0)
-        leafName += ".exe";
-
-      var dirs = Components.classes["@mozilla.org/file/directory_service;1"].
-        getService(Components.interfaces.nsIProperties);
-
-      var resultFile = dirs.get("TmpD", Components.interfaces.nsIFile);
-      resultFile.append(leafName);
-      resultFile.createUnique(Components.interfaces.nsIFile.NORMAL_FILE_TYPE,
-                              0770);
-
-      var channel = ios.newChannelFromURI(uri);
-      this._downloader =
-        Components.classes["@mozilla.org/network/downloader;1"].
-          createInstance(Components.interfaces.nsIDownloader);
-      this._downloader.init(this, resultFile);
-      channel.notificationCallbacks = this;
-
-      this._fireNotification(DOWNLOAD_STARTED, null);
-
-      channel.asyncOpen(this._downloader, null);
-    }
-    catch (e) {
-      Components.utils.reportError(e);
-      this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-228"));
-      if (resultFile && resultFile.exists())
-        resultfile.remove(false);
-    }
-  },
-
-  /**
-   * Inform the gPluginInstaller about what's going on.
-   */
-  _fireNotification: function(aStatus, aErrorMsg)
-  {
-    gPluginInstaller.pluginInstallationProgress(this._plugin.pid,
-                                                aStatus, aErrorMsg);
-
-    if (aStatus == INSTALL_FINISHED) {
-      --PluginInstallService._installersPending;
-      PluginInstallService._fireFinishedNotification();
-    }
-  },
-
-  QueryInterface: function(iid)
-  {
-    if (iid.equals(Components.interfaces.nsISupports) ||
-        iid.equals(Components.interfaces.nsIInterfaceRequestor) ||
-        iid.equals(Components.interfaces.nsIDownloadObserver) ||
-        iid.equals(Components.interfaces.nsIProgressEventSink))
-      return this;
-
-    throw Components.results.NS_ERROR_NO_INTERFACE;
-  },
-
-  getInterface: function(iid)
-  {
-    if (iid.equals(Components.interfaces.nsIProgressEventSink))
-      return this;
-
-    return null;
-  },
-
-  onDownloadComplete: function(downloader, request, ctxt, status, result)
-  {
-    if (!Components.isSuccessCode(status)) {
-      // xpinstall error 228 is "Download Error"
-      this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-228"));
-      result.remove(false);
-      return;
-    }
-
-    this._fireNotification(DOWNLOAD_FINISHED);
-
-    if (this._plugin.InstallerHash &&
-        !verifyHash(result, this._plugin.InstallerHash)) {
-      // xpinstall error 261 is "Invalid file hash..."
-      this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-261"));
-      result.remove(false);
-      return;
-    }
-
-    this._fireNotification(INSTALL_STARTED);
-
-    result.QueryInterface(Components.interfaces.nsILocalFile);
-    try {
-      // Make sure the file is executable
-      result.permissions = 0770;
-      var process = Components.classes["@mozilla.org/process/util;1"]
-                              .createInstance(Components.interfaces.nsIProcess);
-      process.init(result);
-      var self = this;
-      process.runAsync([], 0, {
-        observe: function(subject, topic, data) {
-          if (topic != "process-finished") {
-            Components.utils.reportError("Failed to launch installer");
-            self._fireNotification(INSTALL_FINISHED,
-                                   getLocalizedError("error-207"));
-          }
-          else if (process.exitValue != 0) {
-            Components.utils.reportError("Installer returned exit code " + process.exitValue);
-            self._fireNotification(INSTALL_FINISHED,
-                                   getLocalizedError("error-203"));
-          }
-          else {
-            self._fireNotification(INSTALL_FINISHED, null);
-          }
-          result.remove(false);
-        }
-      });
-    }
-    catch (e) {
-      Components.utils.reportError(e);
-      this._fireNotification(INSTALL_FINISHED, getLocalizedError("error-207"));
-      result.remove(false);
-    }
-  },
-
-  onProgress: function(aRequest, aContext, aProgress, aProgressMax)
-  {
-    gPluginInstaller.pluginInstallationProgressMeter(this._plugin.pid,
-                                                     aProgress,
-                                                     aProgressMax);
-  },
-
-  onStatus: function(aRequest, aContext, aStatus, aStatusArg)
-  {
-    /* pass */
-  }
-};
-
-var PluginInstallService = {
-
-  /**
-   * Start installation of installers and XPI plugins.
-   * @param aInstallerPlugins An array of objects which should have the
-   *                          properties "pid", "InstallerLocation",
-   *                          and "InstallerHash"
-   * @param aXPIPlugins       An array of objects which should have the
-   *                          properties "pid", "XPILocation",
-   *                          and "XPIHash"
-   */
-  startPluginInstallation: function (aInstallerPlugins,
-                                     aXPIPlugins)
-  {
-    this._xpiPlugins = aXPIPlugins;
-    this._xpisPending = aXPIPlugins.length;
-
-    aXPIPlugins.forEach(function(plugin) {
-      AddonManager.getInstallForURL(plugin.XPILocation, function(install) {
-        install.addListener(PluginInstallService);
-        install.install();
-      }, "application/x-xpinstall", plugin.XPIHash);
-    });
-
-    // InstallerObserver may finish immediately so we must initialise the
-    // installers after setting the number of installers and xpis pending
-    this._installersPending = aInstallerPlugins.length;
-    this._installerPlugins = [new InstallerObserver(plugin)
-                              for each (plugin in aInstallerPlugins)];
-  },
-
-  _fireFinishedNotification: function()
-  {
-    if (this._installersPending == 0 && this._xpisPending == 0)
-      gPluginInstaller.pluginInstallationProgress(null, INSTALLS_COMPLETE, null);
-  },
-
-  getPidForInstall: function(install) {
-    for (let i = 0; i < this._xpiPlugins.length; i++) {
-      if (install.sourceURI.spec == this._xpiPlugins[i].XPILocation)
-        return this._xpiPlugins[i].pid;
-    }
-    return -1;
-  },
-
-  // InstallListener interface
-  onDownloadStarted: function(install) {
-    var pid = this.getPidForInstall(install);
-    gPluginInstaller.pluginInstallationProgress(pid, DOWNLOAD_STARTED, null);
-  },
-
-  onDownloadProgress: function(install) {
-    var pid = this.getPidForInstall(install);
-    gPluginInstaller.pluginInstallationProgressMeter(pid, install.progress,
-                                                     install.maxProgress);
-  },
-
-  onDownloadEnded: function(install) {
-    var pid = this.getPidForInstall(install);
-    gPluginInstaller.pluginInstallationProgress(pid, DOWNLOAD_FINISHED, null);
-  },
-
-  onDownloadFailed: function(install) {
-    var pid = this.getPidForInstall(install);
-    switch (install.error) {
-    case AddonManager.ERROR_NETWORK_FAILURE:
-      var errorMsg = getLocalizedError("error-228");
-      break;
-    case AddonManager.ERROR_INCORRECT_HASH:
-      var errorMsg = getLocalizedError("error-261");
-      break;
-    case AddonManager.ERROR_CORRUPT_FILE:
-      var errorMsg = getLocalizedError("error-207");
-      break;
-    }
-    gPluginInstaller.pluginInstallationProgress(pid, INSTALL_FINISHED, errorMsg);
-
-    this._xpisPending--;
-    this._fireFinishedNotification();
-  },
-
-  onInstallStarted: function(install) {
-    var pid = this.getPidForInstall(install);
-    gPluginInstaller.pluginInstallationProgress(pid, INSTALL_STARTED, null);
-  },
-
-  onInstallEnded: function(install, addon) {
-    var pid = this.getPidForInstall(install);
-    gPluginInstaller.pluginInstallationProgress(pid, INSTALL_FINISHED, null);
-
-    this._xpisPending--;
-    this._fireFinishedNotification();
-  },
-
-  onInstallFailed: function(install) {
-    var pid = this.getPidForInstall(install);
-    gPluginInstaller.pluginInstallationProgress(pid, INSTALL_FINISHED,
-                                                getLocalizedError("error-203"));
-
-    this._xpisPending--;
-    this._fireFinishedNotification();
-  }
-}
deleted file mode 100644
--- a/toolkit/mozapps/plugins/content/pluginInstallerWizard.css
+++ /dev/null
@@ -1,9 +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/. */
-
-
-.wizard-header-description {
-  display: none;
-}
-
deleted file mode 100644
--- a/toolkit/mozapps/plugins/content/pluginInstallerWizard.js
+++ /dev/null
@@ -1,659 +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 nsPluginInstallerWizard(){
-
-  // create the request array
-  this.mPluginRequests = new Map();
-
-  // create the plugin info array.
-  // a hash indexed by plugin id so we don't install 
-  // the same plugin more than once.
-  this.mPluginInfoArray = new Object();
-  this.mPluginInfoArrayLength = 0;
-
-  // holds plugins we couldn't find
-  this.mPluginNotFoundArray = new Object();
-  this.mPluginNotFoundArrayLength = 0;
-
-  // array holding pids of plugins that require a license
-  this.mPluginLicenseArray = new Array();
-
-  // how many plugins are to be installed
-  this.pluginsToInstallNum = 0;
-
-  this.mBrowser = null;
-  this.mSuccessfullPluginInstallation = 0;
-  this.mNeedsRestart = false;
-
-  // arguments[0] is an object that contains two items:
-  //     a mimetype->pluginInfo map of missing plugins,
-  //     a reference to the browser that needs them, 
-  //        so we can notify which browser can be reloaded.
-
-  if ("arguments" in window) {
-    for (let [mimetype, pluginInfo] of window.arguments[0].plugins){
-      this.mPluginRequests.set(mimetype, new nsPluginRequest(pluginInfo));
-    }
-
-    this.mBrowser = window.arguments[0].browser;
-  }
-
-  this.WSPluginCounter = 0;
-  this.licenseAcceptCounter = 0;
-
-  this.prefBranch = null;
-}
-
-nsPluginInstallerWizard.prototype.getPluginData = function (){
-  // for each mPluginRequests item, call the datasource
-  this.WSPluginCounter = 0;
-
-  // initiate the datasource call
-  var rdfUpdater = new nsRDFItemUpdater(this.getOS(), this.getChromeLocale());
-
-  for (let [mimetype, pluginRequest] of this.mPluginRequests) {
-    rdfUpdater.checkForPlugin(pluginRequest);
-  }
-}
-
-// aPluginInfo is null if the datasource call failed, and pid is -1 if
-// no matching plugin was found.
-nsPluginInstallerWizard.prototype.pluginInfoReceived = function (aPluginRequestItem, aPluginInfo){
-  this.WSPluginCounter++;
-
-  if (aPluginInfo && (aPluginInfo.pid != -1) ) {
-    // hash by id
-    this.mPluginInfoArray[aPluginInfo.pid] = new PluginInfo(aPluginInfo);
-    this.mPluginInfoArrayLength++;
-  } else {
-    this.mPluginNotFoundArray[aPluginRequestItem.mimetype] = aPluginRequestItem;
-    this.mPluginNotFoundArrayLength++;
-  }
-
-  var progressMeter = document.getElementById("ws_request_progress");
-
-  if (progressMeter.getAttribute("mode") == "undetermined")
-    progressMeter.setAttribute("mode", "determined");
-
-  progressMeter.setAttribute("value",
-      ((this.WSPluginCounter / this.mPluginRequests.size) * 100) + "%");
-
-  if (this.WSPluginCounter == this.mPluginRequests.size) {
-    // check if no plugins were found
-    if (this.mPluginInfoArrayLength == 0) {
-      this.advancePage("lastpage");
-    } else {
-      this.advancePage(null);
-    }
-  } else {
-    // process more.
-  }
-}
-
-nsPluginInstallerWizard.prototype.showPluginList = function (){
-  var myPluginList = document.getElementById("pluginList");
-  var hasPluginWithInstallerUI = false;
-
-  // clear children
-  for (var run = myPluginList.childNodes.length; run > 0; run--)
-    myPluginList.removeChild(myPluginList.childNodes.item(run));
-
-  this.pluginsToInstallNum = 0;
-
-  for (var pluginInfoItem in this.mPluginInfoArray){
-    // [plugin image] [Plugin_Name Plugin_Version]
-
-    var pluginInfo = this.mPluginInfoArray[pluginInfoItem];
-
-    // create the checkbox
-    var myCheckbox = document.createElement("checkbox");
-    myCheckbox.setAttribute("checked", "true");
-    myCheckbox.setAttribute("oncommand", "gPluginInstaller.toggleInstallPlugin('" + pluginInfo.pid + "', this)");
-    // XXXlocalize (nit)
-    myCheckbox.setAttribute("label", pluginInfo.name + " " + (pluginInfo.version ? pluginInfo.version : ""));
-    myCheckbox.setAttribute("src", pluginInfo.IconUrl);
-
-    myPluginList.appendChild(myCheckbox);
-
-    if (pluginInfo.InstallerShowsUI == "true")
-      hasPluginWithInstallerUI = true;
-
-    // keep a running count of plugins the user wants to install
-    this.pluginsToInstallNum++;
-  }
-
-  if (hasPluginWithInstallerUI)
-    document.getElementById("installerUI").hidden = false;
-
-  this.canAdvance(true);
-  this.canRewind(false);
-}
-
-nsPluginInstallerWizard.prototype.toggleInstallPlugin = function (aPid, aCheckbox) {
-  this.mPluginInfoArray[aPid].toBeInstalled = aCheckbox.checked;
-
-  // if no plugins are checked, don't allow to advance
-  this.pluginsToInstallNum = 0;
-  for (var pluginInfoItem in this.mPluginInfoArray){
-    if (this.mPluginInfoArray[pluginInfoItem].toBeInstalled)
-      this.pluginsToInstallNum++;
-  }
-
-  if (this.pluginsToInstallNum > 0)
-    this.canAdvance(true);
-  else
-    this.canAdvance(false);
-}
-
-nsPluginInstallerWizard.prototype.canAdvance = function (aBool){
-  document.getElementById("plugin-installer-wizard").canAdvance = aBool;
-}
-
-nsPluginInstallerWizard.prototype.canRewind = function (aBool){
-  document.getElementById("plugin-installer-wizard").canRewind = aBool;
-}
-
-nsPluginInstallerWizard.prototype.canCancel = function (aBool){
-  document.documentElement.getButton("cancel").disabled = !aBool;
-}
-
-nsPluginInstallerWizard.prototype.showLicenses = function (){
-  this.canAdvance(false);
-  this.canRewind(false);
-
-  // only add if a license is provided and the plugin was selected to
-  // be installed
-  for (var pluginInfoItem in this.mPluginInfoArray){
-    var myPluginInfoItem = this.mPluginInfoArray[pluginInfoItem];
-    if (myPluginInfoItem.toBeInstalled && myPluginInfoItem.licenseURL && (myPluginInfoItem.licenseURL != ""))
-      this.mPluginLicenseArray.push(myPluginInfoItem.pid);
-  }
-
-  if (this.mPluginLicenseArray.length == 0) {
-    // no plugins require licenses
-    this.advancePage(null);
-  } else {
-    this.licenseAcceptCounter = 0;
-
-    // add a nsIWebProgress listener to the license iframe.
-    var docShell = document.getElementById("licenseIFrame").docShell;
-    var iiReq = docShell.QueryInterface(Components.interfaces.nsIInterfaceRequestor);
-    var webProgress = iiReq.getInterface(Components.interfaces.nsIWebProgress);
-    webProgress.addProgressListener(gPluginInstaller.progressListener,
-                                    Components.interfaces.nsIWebProgress.NOTIFY_ALL);
-
-    this.showLicense();
-  }
-}
-
-nsPluginInstallerWizard.prototype.enableNext = function (){
-  // if only one plugin exists, don't enable the next button until
-  // the license is accepted
-  if (gPluginInstaller.pluginsToInstallNum > 1)
-    gPluginInstaller.canAdvance(true);
-
-  document.getElementById("licenseRadioGroup1").disabled = false;
-  document.getElementById("licenseRadioGroup2").disabled = false;
-}
-
-const nsIWebProgressListener = Components.interfaces.nsIWebProgressListener;
-nsPluginInstallerWizard.prototype.progressListener = {
-  onStateChange : function(aWebProgress, aRequest, aStateFlags, aStatus)
-  {
-    if ((aStateFlags & nsIWebProgressListener.STATE_STOP) &&
-       (aStateFlags & nsIWebProgressListener.STATE_IS_NETWORK)) {
-      // iframe loaded
-      gPluginInstaller.enableNext();
-    }
-  },
-
-  onProgressChange : function(aWebProgress, aRequest, aCurSelfProgress,
-                              aMaxSelfProgress, aCurTotalProgress, aMaxTotalProgress)
-  {},
-  onStatusChange : function(aWebProgress, aRequest, aStatus, aMessage)
-  {},
-
-  QueryInterface : function(aIID)
-  {
-     if (aIID.equals(Components.interfaces.nsIWebProgressListener) ||
-         aIID.equals(Components.interfaces.nsISupportsWeakReference) ||
-         aIID.equals(Components.interfaces.nsISupports))
-       return this;
-     throw Components.results.NS_NOINTERFACE;
-   }
-}
-
-nsPluginInstallerWizard.prototype.showLicense = function (){
-  var pluginInfo = this.mPluginInfoArray[this.mPluginLicenseArray[this.licenseAcceptCounter]];
-
-  this.canAdvance(false);
-
-  var loadFlags = Components.interfaces.nsIWebNavigation.LOAD_FLAGS_NONE;
-  document.getElementById("licenseIFrame").webNavigation.loadURI(pluginInfo.licenseURL, loadFlags, null, null, null);
-
-  document.getElementById("pluginLicenseLabel").firstChild.nodeValue = 
-    this.getFormattedString("pluginLicenseAgreement.label", [pluginInfo.name]);
-
-  document.getElementById("licenseRadioGroup1").disabled = true;
-  document.getElementById("licenseRadioGroup2").disabled = true;
-  document.getElementById("licenseRadioGroup").selectedIndex = 
-    pluginInfo.licenseAccepted ? 0 : 1;
-}
-
-nsPluginInstallerWizard.prototype.showNextLicense = function (){
-  var rv = true;
-
-  if (this.mPluginLicenseArray.length > 0) {
-    this.storeLicenseRadioGroup();
-
-    this.licenseAcceptCounter++;
-
-    if (this.licenseAcceptCounter < this.mPluginLicenseArray.length) {
-      this.showLicense();
-
-      rv = false;
-      this.canRewind(true);
-    }
-  }
-
-  return rv;
-}
-
-nsPluginInstallerWizard.prototype.showPreviousLicense = function (){
-  this.storeLicenseRadioGroup();
-  this.licenseAcceptCounter--;
-
-  if (this.licenseAcceptCounter > 0)
-    this.canRewind(true);
-  else
-    this.canRewind(false);
-
-  this.showLicense();
-  
-  // don't allow to return from the license screens
-  return false;
-}
-
-nsPluginInstallerWizard.prototype.storeLicenseRadioGroup = function (){
-  var pluginInfo = this.mPluginInfoArray[this.mPluginLicenseArray[this.licenseAcceptCounter]];
-  pluginInfo.licenseAccepted = !document.getElementById("licenseRadioGroup").selectedIndex;
-}
-
-nsPluginInstallerWizard.prototype.licenseRadioGroupChange = function(aAccepted) {
-  // only if one plugin is to be installed should selection change the next button
-  if (this.pluginsToInstallNum == 1)
-    this.canAdvance(aAccepted);
-}
-
-nsPluginInstallerWizard.prototype.advancePage = function (aPageId){
-  this.canAdvance(true);
-  document.getElementById("plugin-installer-wizard").advance(aPageId);
-}
-
-nsPluginInstallerWizard.prototype.startPluginInstallation = function (){
-  this.canAdvance(false);
-  this.canRewind(false);
-
-  var installerPlugins = [];
-  var xpiPlugins = [];
-
-  for (var pluginInfoItem in this.mPluginInfoArray){
-    var pluginItem = this.mPluginInfoArray[pluginInfoItem];
-
-    if (pluginItem.toBeInstalled && pluginItem.licenseAccepted) {
-      if (pluginItem.InstallerLocation)
-        installerPlugins.push(pluginItem);
-      else if (pluginItem.XPILocation)
-        xpiPlugins.push(pluginItem);
-    }
-  }
-
-  if (installerPlugins.length > 0 || xpiPlugins.length > 0)
-    PluginInstallService.startPluginInstallation(installerPlugins,
-                                                 xpiPlugins);
-  else
-    this.advancePage(null);
-}
-
-/*
-  0    starting download
-  1    download finished
-  2    starting installation
-  3    finished installation
-  4    all done
-*/
-nsPluginInstallerWizard.prototype.pluginInstallationProgress = function (aPid, aProgress, aError) {
-
-  var statMsg = null;
-  var pluginInfo = gPluginInstaller.mPluginInfoArray[aPid];
-
-  switch (aProgress) {
-
-    case 0:
-      statMsg = this.getFormattedString("pluginInstallation.download.start", [pluginInfo.name]);
-      break;
-
-    case 1:
-      statMsg = this.getFormattedString("pluginInstallation.download.finish", [pluginInfo.name]);
-      break;
-
-    case 2:
-      statMsg = this.getFormattedString("pluginInstallation.install.start", [pluginInfo.name]);
-      var progressElm = document.getElementById("plugin_install_progress");
-      progressElm.setAttribute("mode", "undetermined");
-      break;
-
-    case 3:
-      if (aError) {
-        statMsg = this.getFormattedString("pluginInstallation.install.error", [pluginInfo.name, aError]);
-        pluginInfo.error = aError;
-      } else {
-        statMsg = this.getFormattedString("pluginInstallation.install.finish", [pluginInfo.name]);
-        pluginInfo.error = null;
-      }
-      break;
-
-    case 4:
-      statMsg = this.getString("pluginInstallation.complete");
-      break;
-  }
-
-  if (statMsg)
-    document.getElementById("plugin_install_progress_message").value = statMsg;
-
-  if (aProgress == 4) {
-    this.advancePage(null);
-  }
-}
-
-nsPluginInstallerWizard.prototype.pluginInstallationProgressMeter = function (aPid, aValue, aMaxValue){
-  var progressElm = document.getElementById("plugin_install_progress");
-
-  if (progressElm.getAttribute("mode") == "undetermined")
-    progressElm.setAttribute("mode", "determined");
-  
-  progressElm.setAttribute("value", Math.ceil((aValue / aMaxValue) * 100) + "%")
-}
-
-nsPluginInstallerWizard.prototype.addPluginResultRow = function (aImgSrc, aName, aNameTooltip, aStatus, aStatusTooltip, aManualUrl){
-  var myRows = document.getElementById("pluginResultList");
-
-  var myRow = document.createElement("row");
-  myRow.setAttribute("align", "center");
-
-  // create the image
-  var myImage = document.createElement("image");
-  myImage.setAttribute("src", aImgSrc);
-  myImage.setAttribute("height", "16px");
-  myImage.setAttribute("width", "16px");
-  myRow.appendChild(myImage)
-
-  // create the labels
-  var myLabel = document.createElement("label");
-  myLabel.setAttribute("value", aName);
-  if (aNameTooltip)
-    myLabel.setAttribute("tooltiptext", aNameTooltip);
-  myRow.appendChild(myLabel);
-
-  if (aStatus) {
-    myLabel = document.createElement("label");
-    myLabel.setAttribute("value", aStatus);
-    myRow.appendChild(myLabel);
-  }
-
-  // manual install
-  if (aManualUrl) {
-    var myButton = document.createElement("button");
-
-    var manualInstallLabel = this.getString("pluginInstallationSummary.manualInstall.label");
-    var manualInstallTooltip = this.getString("pluginInstallationSummary.manualInstall.tooltip");
-
-    myButton.setAttribute("label", manualInstallLabel);
-    myButton.setAttribute("tooltiptext", manualInstallTooltip);
-
-    myRow.appendChild(myButton);
-
-    // XXX: XUL sucks, need to add the listener after it got added into the document
-    if (aManualUrl)
-      myButton.addEventListener("command", function() { gPluginInstaller.loadURL(aManualUrl) }, false);
-  }
-
-  myRows.appendChild(myRow);
-}
-
-nsPluginInstallerWizard.prototype.showPluginResults = function (){
-  var notInstalledList = "?action=missingplugins";
-  var myRows = document.getElementById("pluginResultList");
-
-  // clear children
-  for (var run = myRows.childNodes.length; run--; run > 0)
-    myRows.removeChild(myRows.childNodes.item(run));
-
-  for (var pluginInfoItem in this.mPluginInfoArray){
-    // [plugin image] [Plugin_Name Plugin_Version] [Success/Failed] [Manual Install (if Failed)]
-
-    var myPluginItem = this.mPluginInfoArray[pluginInfoItem];
-
-    if (myPluginItem.toBeInstalled) {
-      var statusMsg;
-      var statusTooltip;
-      if (myPluginItem.error){
-        statusMsg = this.getString("pluginInstallationSummary.failed");
-        statusTooltip = myPluginItem.error;
-        notInstalledList += "&mimetype=" + pluginInfoItem;
-      } else if (!myPluginItem.licenseAccepted) {
-        statusMsg = this.getString("pluginInstallationSummary.licenseNotAccepted");
-      } else if (!myPluginItem.XPILocation && !myPluginItem.InstallerLocation) {
-        statusMsg = this.getString("pluginInstallationSummary.notAvailable");
-        notInstalledList += "&mimetype=" + pluginInfoItem;
-      } else {
-        this.mSuccessfullPluginInstallation++;
-        statusMsg = this.getString("pluginInstallationSummary.success");
-
-        // only check needsRestart if the plugin was successfully installed.
-        if (myPluginItem.needsRestart)
-          this.mNeedsRestart = true;
-      }
-
-      // manual url - either returned from the webservice or the pluginspage attribute
-      var manualUrl;
-      if ((myPluginItem.error || (!myPluginItem.XPILocation && !myPluginItem.InstallerLocation)) &&
-          (myPluginItem.manualInstallationURL || this.mPluginRequests.get(myPluginItem.requestedMimetype).pluginsPage)){
-        manualUrl = myPluginItem.manualInstallationURL ? myPluginItem.manualInstallationURL : this.mPluginRequests.get(myPluginItem.requestedMimetype).pluginsPage;
-      }
-
-      this.addPluginResultRow(
-          myPluginItem.IconUrl, 
-          myPluginItem.name + " " + (myPluginItem.version ? myPluginItem.version : ""),
-          null,
-          statusMsg, 
-          statusTooltip,
-          manualUrl);
-    }
-  }
-
-  // handle plugins we couldn't find
-  for (pluginInfoItem in this.mPluginNotFoundArray){
-    var pluginRequest = this.mPluginNotFoundArray[pluginInfoItem];
-
-    // if there is a pluginspage, show UI
-    if (pluginRequest.pluginsPage) {
-      this.addPluginResultRow(
-          "",
-          this.getFormattedString("pluginInstallation.unknownPlugin", [pluginInfoItem]),
-          null,
-          null,
-          null,
-          pluginRequest.pluginsPage);
-    }
-
-    notInstalledList += "&mimetype=" + pluginInfoItem;
-  }
-
-  // no plugins were found, so change the description of the final page.
-  if (this.mPluginInfoArrayLength == 0) {
-    var noPluginsFound = this.getString("pluginInstallation.noPluginsFound");
-    document.getElementById("pluginSummaryDescription").setAttribute("value", noPluginsFound);
-  } else if (this.mSuccessfullPluginInstallation == 0) {
-    // plugins found, but none were installed.
-    var noPluginsInstalled = this.getString("pluginInstallation.noPluginsInstalled");
-    document.getElementById("pluginSummaryDescription").setAttribute("value", noPluginsInstalled);
-  }
-
-  document.getElementById("pluginSummaryRestartNeeded").hidden = !this.mNeedsRestart;
-
-  var app = Components.classes["@mozilla.org/xre/app-info;1"]
-                      .getService(Components.interfaces.nsIXULAppInfo);
-
-  // set the get more info link to contain the mimetypes we couldn't install.
-  notInstalledList +=
-    "&appID=" + app.ID +
-    "&appVersion=" + app.platformBuildID +
-    "&clientOS=" + this.getOS() +
-    "&chromeLocale=" + this.getChromeLocale() +
-    "&appRelease=" + app.version;
-
-  document.getElementById("moreInfoLink").addEventListener("click", function() { gPluginInstaller.loadURL("https://pfs.mozilla.org/plugins/" + notInstalledList) }, false);
-
-  if (this.mNeedsRestart) {
-    var cancel = document.getElementById("plugin-installer-wizard").getButton("cancel");
-    cancel.label = this.getString("pluginInstallation.close.label");
-    cancel.accessKey = this.getString("pluginInstallation.close.accesskey");
-    var finish = document.getElementById("plugin-installer-wizard").getButton("finish");
-    finish.label = this.getFormattedString("pluginInstallation.restart.label", [app.name]);
-    finish.accessKey = this.getString("pluginInstallation.restart.accesskey");
-    this.canCancel(true);
-  }
-  else {
-    this.canCancel(false);
-  }
-  this.canAdvance(true);
-  this.canRewind(false);
-}
-
-nsPluginInstallerWizard.prototype.loadURL = function (aUrl){
-  // Check if the page where the plugin came from can load aUrl before
-  // loading it, and do *not* allow loading URIs that would inherit our
-  // principal.
-  
-  var pluginPagePrincipal =
-    window.opener.content.document.nodePrincipal;
-
-  const nsIScriptSecurityManager =
-    Components.interfaces.nsIScriptSecurityManager;
-  var secMan = Components.classes["@mozilla.org/scriptsecuritymanager;1"]
-                         .getService(nsIScriptSecurityManager);
-
-  secMan.checkLoadURIStrWithPrincipal(pluginPagePrincipal, aUrl,
-    nsIScriptSecurityManager.DISALLOW_INHERIT_PRINCIPAL);
-
-  window.opener.open(aUrl);
-}
-
-nsPluginInstallerWizard.prototype.getString = function (aName){
-  return document.getElementById("pluginWizardString").getString(aName);
-}
-
-nsPluginInstallerWizard.prototype.getFormattedString = function (aName, aArray){
-  return document.getElementById("pluginWizardString").getFormattedString(aName, aArray);
-}
-
-nsPluginInstallerWizard.prototype.getOS = function (){
-  var httpService = Components.classes["@mozilla.org/network/protocol;1?name=http"]
-                              .getService(Components.interfaces.nsIHttpProtocolHandler);
-  return httpService.oscpu;
-}
-
-nsPluginInstallerWizard.prototype.getChromeLocale = function (){
-  var chromeReg = Components.classes["@mozilla.org/chrome/chrome-registry;1"]
-                            .getService(Components.interfaces.nsIXULChromeRegistry);
-  return chromeReg.getSelectedLocale("global");
-}
-
-nsPluginInstallerWizard.prototype.getPrefBranch = function (){
-  if (!this.prefBranch)
-    this.prefBranch = Components.classes["@mozilla.org/preferences-service;1"]
-                                .getService(Components.interfaces.nsIPrefBranch);
-  return this.prefBranch;
-}
-function nsPluginRequest(aPlugRequest){
-  this.mimetype = encodeURI(aPlugRequest.mimetype);
-  this.pluginsPage = aPlugRequest.pluginsPage;
-}
-
-function PluginInfo(aResult) {
-  this.name = aResult.name;
-  this.pid = aResult.pid;
-  this.version = aResult.version;
-  this.IconUrl = aResult.IconUrl;
-  this.InstallerLocation = aResult.InstallerLocation;
-  this.InstallerHash = aResult.InstallerHash;
-  this.XPILocation = aResult.XPILocation;
-  this.XPIHash = aResult.XPIHash;
-  this.InstallerShowsUI = aResult.InstallerShowsUI;
-  this.manualInstallationURL = aResult.manualInstallationURL;
-  this.requestedMimetype = aResult.requestedMimetype;
-  this.licenseURL = aResult.licenseURL;
-  this.needsRestart = (aResult.needsRestart == "true");
-
-  this.error = null;
-  this.toBeInstalled = true;
-
-  // no license provided, make it accepted
-  this.licenseAccepted = this.licenseURL ? false : true;
-}
-
-var gPluginInstaller;
-
-function wizardInit(){
-  gPluginInstaller = new nsPluginInstallerWizard();
-  gPluginInstaller.canAdvance(false);
-  gPluginInstaller.getPluginData();
-}
-
-function wizardFinish(){
-  if (gPluginInstaller.mNeedsRestart) {
-    // Notify all windows that an application quit has been requested.
-    var os = Components.classes["@mozilla.org/observer-service;1"]
-                       .getService(Components.interfaces.nsIObserverService);
-    var cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
-                               .createInstance(Components.interfaces.nsISupportsPRBool);
-    os.notifyObservers(cancelQuit, "quit-application-requested", "restart");
-
-    // Something aborted the quit process.
-    if (!cancelQuit.data) {
-      var nsIAppStartup = Components.interfaces.nsIAppStartup;
-      var appStartup = Components.classes["@mozilla.org/toolkit/app-startup;1"]
-                                 .getService(nsIAppStartup);
-      appStartup.quit(nsIAppStartup.eAttemptQuit | nsIAppStartup.eRestart);
-      return true;
-    }
-  }
-
-  // don't refresh if no plugins were found or installed
-  if ((gPluginInstaller.mSuccessfullPluginInstallation > 0) &&
-      (gPluginInstaller.mPluginInfoArray.length != 0)) {
-
-    // reload plugins so JS detection works immediately
-    try {
-      var ph = Components.classes["@mozilla.org/plugin/host;1"]
-                         .getService(Components.interfaces.nsIPluginHost);
-      ph.reloadPlugins(false);
-    }
-    catch (e) {
-      // reloadPlugins throws an exception if there were no plugins to load
-    }
-
-    if (gPluginInstaller.mBrowser) {
-      // notify listeners that a plugin is installed,
-      // so that they can reset the UI and update the browser.
-      var event = document.createEvent("Events");
-      event.initEvent("NewPluginInstalled", true, true);
-      gPluginInstaller.mBrowser.dispatchEvent(event);
-    }
-  }
-
-  return true;
-}
deleted file mode 100644
--- a/toolkit/mozapps/plugins/content/pluginInstallerWizard.xul
+++ /dev/null
@@ -1,115 +0,0 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
-<?xml-stylesheet href="chrome://mozapps/content/plugins/pluginInstallerWizard.css"?>
-<?xml-stylesheet href="chrome://mozapps/skin/plugins/pluginInstallerWizard.css"?>
-
-<!DOCTYPE wizard [
-<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
-%brandDTD;
-<!ENTITY % pluginsDTD SYSTEM "chrome://mozapps/locale/plugins/plugins.dtd">
-%pluginsDTD;
-]>
-
-<wizard id="plugin-installer-wizard" title="&pluginWizard.title;"
-        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        persist="width height screenX screenY sizemode"
-        style="width: 50em; min-height: 40em;"
-        onload="wizardInit()"
-        onwizardfinish="return wizardFinish();">
-
-  <script type="application/javascript" src="chrome://mozapps/content/plugins/pluginInstallerWizard.js"/>
-  <script type="application/javascript" src="chrome://mozapps/content/plugins/pluginInstallerDatasource.js"/>
-  <script type="application/javascript" src="chrome://mozapps/content/plugins/pluginInstallerService.js"/>
-
-  <stringbundleset id="pluginSet">
-    <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
-    <stringbundle id="xpinstallStrings" src="chrome://global/locale/xpinstall/xpinstall.properties"/>
-    <stringbundle id="pluginWizardString" src="chrome://mozapps/locale/plugins/plugins.properties"/>
-  </stringbundleset>
-
-  <wizardpage label="&pluginWizard.firstPage.title;">
-    <description value="&pluginWizard.checkingForPlugins.description.label;"/>
-    <separator />
-    <progressmeter id="ws_request_progress" mode="undetermined"/>
-  </wizardpage>
-
-  <wizardpage label="&pluginWizard.availablePluginsPage.title;"
-              onpageshow="gPluginInstaller.showPluginList()">
-
-    <description value="&pluginWizard.availablePluginsPage.description.label;"/>
-
-    <vbox id="pluginList" flex="1" align="left" style="overflow: auto;"/>
-
-    <vbox id="installerUI" hidden="true">
-      <separator />
-      <description>&pluginWizard.availablePluginsPage.installerUI;</description>
-    </vbox>
-
-    <separator />
-    <description value="&pluginWizard.availablePluginsPage.continueMsg.label;"/>
-  </wizardpage>
-
-  <wizardpage label="&pluginWizard.licensePage.title;" 
-              onpageshow="gPluginInstaller.showLicenses()"
-              onpageadvanced="return gPluginInstaller.showNextLicense();"
-              onpagerewound="return gPluginInstaller.showPreviousLicense();">
-    <description id="pluginLicenseLabel"> </description>
-
-    <vbox id="licenseContainer" flex="1">
-      <iframe id="licenseIFrame" flex="1" src="" />
-    </vbox>
-
-    <radiogroup id="licenseRadioGroup">
-      <radio id="licenseRadioGroup1" disabled="true"
-             oncommand="gPluginInstaller.licenseRadioGroupChange(true)"
-             label="&pluginWizard.licensePage.accept.label;"/>
-      <radio id="licenseRadioGroup2" disabled="true" selected="true"
-             oncommand="gPluginInstaller.licenseRadioGroupChange(false)"
-             label="&pluginWizard.licensePage.deny.label;"/>
-    </radiogroup>
-  </wizardpage>
-
-  <wizardpage label="&pluginWizard.installPluginsPage.title;" 
-              onpageshow="gPluginInstaller.startPluginInstallation()">
-    <description value="&pluginWizard.installPluginsPage.description.label;"/>
-
-    <separator />
-
-    <description id="plugin_install_progress_message" value=""/>
-    <progressmeter id="plugin_install_progress" mode="undetermined"/>
-  </wizardpage>
-
-  <wizardpage id="lastpage" pageid="lastpage" 
-              onpageshow="gPluginInstaller.showPluginResults()">
-
-    <description id="pluginSummaryDescription" 
-                 value="&pluginWizard.finalPage.description.label;"/>
-
-    <separator />
-
-    <grid class="indent">
-      <columns>
-        <column/>
-        <column style="margin-right:15px;"/>
-        <column style="margin-right:15px;"/>
-        <column />
-      </columns>
-
-      <rows id="pluginResultList">
-      </rows>
-    </grid>
-
-    <description id="pluginSummaryRestartNeeded" style="padding-top:10px;"
-                 value="&pluginWizard.finalPage.restart.label;"/>
-    <spacer flex="1" />
-    <description id="moreInfoLink"
-      style="color:rgb(0, 0, 255); cursor:pointer; text-decoration:underline;"
-      value="&pluginWizard.finalPage.moreInfo.label;"/>
-  </wizardpage>
-
-</wizard>
-
--- a/toolkit/mozapps/plugins/content/pluginProblem.xml
+++ b/toolkit/mozapps/plugins/content/pluginProblem.xml
@@ -36,21 +36,16 @@
                 <html:div class="msg msgDisabled">&disabledPlugin;</html:div>
                 <html:div class="msg msgBlocked">&blockedPlugin.label;</html:div>
                 <html:div class="msg msgCrashed">
                     <html:div class="msgCrashedText" anonid="crashedText"><!-- set at runtime --></html:div>
                     <!-- link href set at runtime -->
                     <html:div class="msgReload">&reloadPlugin.pre;<html:a class="reloadLink" anonid="reloadLink" href="">&reloadPlugin.middle;</html:a>&reloadPlugin.post;</html:div>
                 </html:div>
 
-                <html:div class="installStatus" anonid="installStatus">
-                    <html:div class="msg msgInstallPlugin">
-		        <html:a class="action-link" anonid="installPluginLink" href="">&installPlugin;</html:a>
-		    </html:div>
-                </html:div>
                 <html:div class="msg msgManagePlugins"><html:a class="action-link" anonid="managePluginsLink" href="">&managePlugins;</html:a></html:div>
                 <html:div class="submitStatus" anonid="submitStatus">
                     <html:div class="msg msgPleaseSubmit" anonid="pleaseSubmit">
                         <html:textarea class="submitComment"
                                        anonid="submitComment"
                                        placeholder="&report.comment;"/>
                         <html:div class="submitURLOptInBox">
                             <html:label><html:input class="submitURLOptIn" anonid="submitURLOptIn" type="checkbox"/> &report.pageURL;</html:label>
--- a/toolkit/mozapps/plugins/content/pluginProblemContent.css
+++ b/toolkit/mozapps/plugins/content/pluginProblemContent.css
@@ -120,20 +120,16 @@ html|applet:not([height]), html|applet[h
 :-moz-handler-clicktoplay .msgTapToPlay,
 :-moz-handler-disabled .msgDisabled,
 :-moz-handler-disabled .msgManagePlugins,
 :-moz-handler-blocked .msgBlocked,
 :-moz-handler-crashed .msgCrashed {
   display: block;
 }
 
-.installStatus[installable] .msgInstallPlugin {
-  display: block;
-}
-
 .submitStatus[status] {
   display: -moz-box;
   -moz-box-align: center;
   -moz-box-pack: center;
   height: 160px;
 }
 
 .submitStatus[status="noReport"]   .msgNoCrashReport,
--- a/toolkit/mozapps/plugins/jar.mn
+++ b/toolkit/mozapps/plugins/jar.mn
@@ -1,15 +1,10 @@
 # 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/.
 
 toolkit.jar:
 % content mozapps %content/mozapps/
-  content/mozapps/plugins/pluginInstallerWizard.xul             (content/pluginInstallerWizard.xul)
-  content/mozapps/plugins/pluginInstallerWizard.js              (content/pluginInstallerWizard.js)
-  content/mozapps/plugins/pluginInstallerWizard.css             (content/pluginInstallerWizard.css)
-  content/mozapps/plugins/pluginInstallerDatasource.js          (content/pluginInstallerDatasource.js)
-  content/mozapps/plugins/pluginInstallerService.js             (content/pluginInstallerService.js)
   content/mozapps/plugins/pluginProblem.xml                     (content/pluginProblem.xml)
   content/mozapps/plugins/pluginProblemContent.css              (content/pluginProblemContent.css)
   content/mozapps/plugins/pluginProblemBinding.css              (content/pluginProblemBinding.css)
   content/mozapps/plugins/pluginFinderBinding.css               (content/pluginFinderBinding.css)
--- a/toolkit/mozapps/plugins/moz.build
+++ b/toolkit/mozapps/plugins/moz.build
@@ -1,13 +1,11 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
-TEST_DIRS += ['tests']
-
 EXTRA_COMPONENTS += [
     'pluginGlue.manifest',
 ]
 
-JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file
+JAR_MANIFESTS += ['jar.mn']
deleted file mode 100644
--- a/toolkit/mozapps/plugins/service/PluginFinderService.java
+++ /dev/null
@@ -1,40 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.pfs;
-
-public class PluginFinderService {
-
-  public org.mozilla.pfs.PluginInfo getPluginInfo(java.lang.String aMimetype, java.lang.String aClientOS, java.lang.String aLocale) {
-    org.mozilla.pfs.PluginInfo response = new org.mozilla.pfs.PluginInfo();
-    
-    if (aMimetype.equals("application/x-shockwave-flash")) {
-      response.setPid(1);
-      response.setName("Flash Player");
-      response.setVersion("7");
-      response.setIconUrl("http://goat.austin.ibm.com:8080/flash.gif");
-      response.setXPILocation("http://www.nexgenmedia.net/flashlinux/flash-linux.xpi");
-      response.setInstallerShowsUI(false);
-      response.setManualInstallationURL("");
-      response.setLicenseURL("");
-    } else if (aMimetype.equals("application/x-mtx")) {
-      response.setPid(2);
-      response.setName("Viewpoint Media Player");
-      response.setVersion("5");
-      response.setIconUrl(null);
-      response.setXPILocation("http://www.nexgenmedia.net/flashlinux/invalid.xpi");
-      response.setInstallerShowsUI(false);
-      response.setManualInstallationURL("http://www.viewpoint.com/pub/products/vmp.html");   
-      response.setLicenseURL("http://www.viewpoint.com/pub/privacy.html");
-    } else {
-      response.setPid(-1);
-    }
-    
-    response.setRequestedMimetype(aMimetype);
-    return response;
-    
-  }
-}
-
deleted file mode 100644
--- a/toolkit/mozapps/plugins/service/PluginFinderService.php
+++ /dev/null
@@ -1,163 +0,0 @@
-<?php
-/* -*- Mode: php; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-/// config bits:
-$db_server = "";
-$db_user = "";
-$db_pass = "";
-$db_name = "";
-
-// error handling
-function bail ($errstr) {
-    die("Error: " . $errstr);
-}
-
-
-// major.minor.release.build[+]
-// make sure this is a valid version
-function expandversion ($vstr) {
-    $v = explode('.', $vstr);
-
-    if ($vstr == '' || count($v) == 0 || count($v) > 4) {
-        bail ('Bogus version.');
-    }
-
-    $vlen = count($v);
-    $ret = array();
-    $hasplus = 0;
-
-    for ($i = 0; $i < 4; $i++) {
-        if ($i > $vlen-1) {
-            // this version chunk was not specified; give 0
-            $ret[] = 0;
-        } else {
-            $s = $v[$i];
-            if ($i == 3) {
-                // need to check for +
-                $slen = strlen($s);
-                if ($s{$slen-1} == '+') {
-                    $s = substr($s, 0, $slen-1);
-                    $hasplus = 1;
-                }
-            }
-
-            $ret[] = intval($s);
-        }
-    }
-
-    $ret[] = $hasplus;
-
-    return $ret;
-}
-
-function vercmp ($a, $b) {
-    if ($a == $b)
-        return 0;
-
-    $va = expandversion($a);
-    $vb = expandversion($b);
-
-    for ($i = 0; $i < 5; $i++)
-        if ($va[$i] != $vb[$i])
-            return ($vb[$i] - $va[$i]);
-
-    return 0;
-}
-
-
-//
-// These are passed in the GET string
-//
-
-if (!array_key_exists('mimetype', $_GET))
-    bail ("Invalid request.");
-
-$mimetype = $_GET['mimetype'];
-
-if (!array_key_exists('appID', $_GET) 
-    || !array_key_exists('appVersion', $_GET)
-    || !array_key_exists('clientOS', $_GET))    
-    || !array_key_exists('chromeLocale', $_GET))
-   )
-    bail ("Invalid request.");
-
-$reqTargetAppGuid = $_GET['appID'];
-$reqTargetAppVersion = $_GET['appVersion'];
-$clientOS = $_GET['clientOS'];
-$chromeLocale = $_GET['chromeLocale'];
-
-// check args
-if (empty($reqTargetAppVersion) || empty($reqTargetAppGuid)) {
-    bail ("Invalid request.");
-}
-
-//
-// Now to spit out the RDF.  We hand-generate because the data is pretty simple.
-//
-
-if ($mimetype == "application/x-mtx") {
-  $name = "Viewpoint Media Player";
-  $guid = "{03F998B2-0E00-11D3-A498-00104B6EB52E}";
-  $version = "5.0";
-  $iconUrl = "";
-  $XPILocation = "http://www.nexgenmedia.net/flashlinux/invalid.xpi";
-  $InstallerShowsUI = false;
-  $manualInstallationURL = "http://www.viewpoint.com/pub/products/vmp.html";
-  $licenseURL = "http://www.viewpoint.com/pub/privacy.html";
-} else if ($mimetype == "application/x-shockwave-flash") {
-  $name = "Flash Player";
-  $guid = "{D27CDB6E-AE6D-11cf-96B8-444553540000}";
-  $version = "7.0.16";
-  $iconUrl = "http://goat.austin.ibm.com:8080/flash.gif";
-  $XPILocation = "http://www.nexgenmedia.net/flashlinux/flash-linux.xpi";
-  $InstallerShowsUI = "false";
-  $manualInstallationURL = "http://www.macromedia.com/go/getflashplayer";
-  $licenseURL = "http://www.macromedia.com/shockwave/download/license/desktop/";
-} else {
-  $name = "";
-  $guid = "-1";
-  $version = "";
-  $iconUrl = "";
-  $XPILocation = "";
-  $InstallerShowsUI = "";
-  $manualInstallationURL = "";
-  $licenseURL = "";
-}
-
-header("Content-type: application/xml");
-print "<?xml version=\"1.0\"?>\n";
-print "<RDF:RDF xmlns:RDF=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\" xmlns:pfs=\"http://www.mozilla.org/2004/pfs-rdf#\">\n\n";
-
-print "<RDF:Description about=\"urn:mozilla:plugin-results:{$mimetype}\">\n";
-
-// output list of matching plugins
-print " <pfs:plugins><RDF:Seq>\n";
-print "   <RDF:li resource=\"urn:mozilla:plugin:{$guid}\"/>\n";
-print " </RDF:Seq></pfs:plugins>\n";
-print "</RDF:Description>\n\n";
-
-print "<RDF:Description about=\"urn:mozilla:plugin:{$guid}\">\n";
-print " <pfs:updates><RDF:Seq>\n";
-print "   <RDF:li resource=\"urn:mozilla:plugin:{$guid}:{$version}\"/>\n";
-print " </RDF:Seq></pfs:updates>\n";
-print "</RDF:Description>\n\n";
-
-print "<RDF:Description about=\"urn:mozilla:plugin:{$guid}:{$version}\">\n";
-print " <pfs:name>{$name}</pfs:name>\n";
-print " <pfs:requestedMimetype>{$mimetype}</pfs:requestedMimetype>\n";
-print " <pfs:guid>{$guid}</pfs:guid>\n";
-print " <pfs:version>{$version}</pfs:version>\n";
-print " <pfs:IconUrl>{$iconUrl}</pfs:IconUrl>\n";
-print " <pfs:XPILocation>{$XPILocation}</pfs:XPILocation>\n";
-print " <pfs:InstallerShowsUI>{$InstallerShowsUI}</pfs:InstallerShowsUI>\n";
-print " <pfs:manualInstallationURL>{$manualInstallationURL}</pfs:manualInstallationURL>\n";
-print " <pfs:licenseURL>{$licenseURL}</pfs:licenseURL>\n";
-print "</RDF:Description>\n\n";
-
-print "</RDF:RDF>\n";
-
-?>
-
deleted file mode 100644
--- a/toolkit/mozapps/plugins/service/PluginInfo.java
+++ /dev/null
@@ -1,94 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.pfs;
-
-public class PluginInfo {
-  private java.lang.String name;
-  private int pid;
-  private java.lang.String version;
-  private java.lang.String iconUrl;
-  private java.lang.String XPILocation;
-  private boolean installerShowsUI;
-  private java.lang.String manualInstallationURL;
-  private java.lang.String requestedMimetype;
-  private java.lang.String licenseURL;
-
-  public PluginInfo() {
-  }
-
-  public java.lang.String getName() {
-    return name;
-  }
-
-  public void setName(java.lang.String name) {
-    this.name = name;
-  }
-
-  public int getPid() {
-    return pid;
-  }
-
-  public void setPid(int pid) {
-    this.pid = pid;
-  }
-
-  public java.lang.String getVersion() {
-    return version;
-  }
-
-  public void setVersion(java.lang.String version) {
-    this.version = version;
-  }
-
-  public java.lang.String getIconUrl() {
-    return iconUrl;
-  }
-
-  public void setIconUrl(java.lang.String iconUrl) {
-    this.iconUrl = iconUrl;
-  }
-
-  public java.lang.String getXPILocation() {
-    return XPILocation;
-  }
-
-  public void setXPILocation(java.lang.String XPILocation) {
-    this.XPILocation = XPILocation;
-  }
-
-  public boolean isInstallerShowsUI() {
-    return installerShowsUI;
-  }
-
-  public void setInstallerShowsUI(boolean installerShowsUI) {
-    this.installerShowsUI = installerShowsUI;
-  }
-
-  public java.lang.String getManualInstallationURL() {
-    return manualInstallationURL;
-  }
-
-  public void setManualInstallationURL(java.lang.String manualInstallationURL) {
-    this.manualInstallationURL = manualInstallationURL;
-  }
-
-  public java.lang.String getRequestedMimetype() {
-    return requestedMimetype;
-  }
-
-  public void setRequestedMimetype(java.lang.String requestedMimetype) {
-    this.requestedMimetype = requestedMimetype;
-  }
-
-  public java.lang.String getLicenseURL() {
-    return licenseURL;
-  }
-
-  public void setLicenseURL(java.lang.String licenseURL) {
-    this.licenseURL = licenseURL;
-  }
-}
-
deleted file mode 100644
--- a/toolkit/mozapps/plugins/service/make.sh
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/bin/sh
-# 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/.
-
-
-export CATALINA_HOME=/var/tomcat-4.1.27
-export AXIS_HOME=/var/tomcat-4.1.27/axis-1_1
-export CLASSPATH=$AXIS_HOME/lib/axis.jar:$AXIS_HOME/lib/commons-discovery.jar:$AXIS_HOME/lib/commons-logging.jar:$AXIS_HOME/lib/jaxrpc.jar:$AXIS_HOME/lib/saaj.jar:$AXIS_HOME/lib/log4j-1.2.4.jar:$AXIS_HOME/lib/wsdl4j.jar:$CATALINA_HOME/webapps/axis/WEB-INF/classes
-
-name="PluginFinderService"
-pkg="org.mozilla.pfs"
-pkg_dir=$CATALINA_HOME/webapps/axis/WEB-INF/classes/org/mozilla/pfs
-
-echo "Copying Source..."
-
-rm -f $pkg_dir/*.*
-cp -f *.java $pkg_dir
-
-echo "Compiling Source..."
-javac -g $pkg_dir/*.java
-
-echo "Generating WSDL..."
-java org.apache.axis.wsdl.Java2WSDL -o $name.wsdl \
-      -l"http://localhost:8080/axis/services/$name" \
-      -n "urn:$name" -p"$pkg" "urn:$name" $pkg.$name
-      
-echo "Generating Stubs from WSDL..."
-rm -f org/mozilla/pfs/*.*
-java org.apache.axis.wsdl.WSDL2Java -o . -s -S true -Nurn:$name $pkg $name.wsdl
-
-# make our PluginFinderService.java looking the SoapBindingImpl syntax and replace
-cp $name.java $name.temp
-regexp="s/$name/${name}SoapBindingImpl/g"
-sed -e $regexp $name.temp > $name.temp2
-rm $name.temp
-
-regexp="s/public class ${name}SoapBindingImpl/public class ${name}SoapBindingImpl implements ${pkg}.${name}/g"
-sed -e "$regexp" $name.temp2 > $name.temp
-rm $name.temp2
-mv $name.temp org/mozilla/pfs/${name}SoapBindingImpl.java
-
-rm -f $pkg_dir/*.java
-cp org/mozilla/pfs/* $pkg_dir/
-javac $pkg_dir/*.java
-
-echo "Deploying Web Service..."
-java org.apache.axis.client.AdminClient -p 8080 $pkg_dir/deploy.wsdd  
-
-echo "All Done. Hoorah!"
deleted file mode 100644
index 1122537f7af6f3d430ba421830afb18b36756385..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/toolkit/mozapps/plugins/tests/BadPlugin.cpp
+++ /dev/null
@@ -1,4 +0,0 @@
-int main(int argc, char** argv)
-{
-    return 1;
-}
deleted file mode 100644
index c4ade90d5bda85d83ec3ade8ee2b58f59d9d3eed..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
--- a/toolkit/mozapps/plugins/tests/GoodPlugin.cpp
+++ /dev/null
@@ -1,4 +0,0 @@
-int main(int argc, char** argv)
-{
-    return 0;
-}
deleted file mode 100644
--- a/toolkit/mozapps/plugins/tests/Makefile.in
+++ /dev/null
@@ -1,9 +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/.
-
-TESTROOT = $(DEPTH)/_tests/testing/mochitest/browser/$(relativesrcdir)
-
-PROGRAMS_DEST = $(TESTROOT)
-
-include $(topsrcdir)/config/rules.mk
deleted file mode 100644
--- a/toolkit/mozapps/plugins/tests/browser.ini
+++ /dev/null
@@ -1,9 +0,0 @@
-[DEFAULT]
-support-files =
-  pfs_bug435788_1.rdf
-  pfs_bug435788_2.rdf
-  GoodExtension.xpi
-  BadExtension.xpi
-
-[browser_bug435788.js]
-disabled = disabled for leaks--bug-751100
deleted file mode 100644
--- a/toolkit/mozapps/plugins/tests/browser_bug435788.js
+++ /dev/null
@@ -1,461 +0,0 @@
-const TEST_ROOT = "http://example.com/browser/toolkit/mozapps/plugins/tests/";
-
-let tmp = {};
-Components.utils.import("resource://gre/modules/AddonManager.jsm", tmp);
-let AddonManager = tmp.AddonManager;
-
-var gPFS;
-
-function test() {
-  waitForExplicitFinish();
-
-  prepare_test_1();
-}
-
-function finishTest() {
-  Services.prefs.clearUserPref("pfs.datasource.url");
-  finish();
-}
-
-// Gets the number of plugin items in the detected list
-function getListCount() {
-  var list = gPFS.document.getElementById("pluginList");
-  return list.childNodes.length;
-}
-
-// Gets wether the list contains a particular plugin name
-function hasListItem(name, version) {
-  var label = name + " " + (version ? version : "");
-  var list = gPFS.document.getElementById("pluginList");
-  for (var i = 0; i < list.childNodes.length; i++) {
-    if (list.childNodes[i].label == label)
-      return true;
-  }
-  return false;
-}
-
-// Gets the number of plugin results
-function getResultCount() {
-  var list = gPFS.document.getElementById("pluginResultList");
-  return list.childNodes.length;
-}
-
-// Gets the plugin result for a particular plugin name
-function getResultItem(name, version) {
-  var label = name + " " + (version ? version : "");
-  var list = gPFS.document.getElementById("pluginResultList");
-  for (var i = 0; i < list.childNodes.length; i++) {
-    if (list.childNodes[i].childNodes[1].value == label) {
-      var item = {
-        name: name,
-        version: version,
-        status: null
-      };
-      if (list.childNodes[i].childNodes[2].tagName == "label")
-        item.status = list.childNodes[i].childNodes[2].value;
-      return item;
-    }
-  }
-  return null;
-}
-
-// Logs the currently displaying wizard page
-function page_shown() {
-  function show_button_state(name) {
-    var button = gPFS.document.documentElement.getButton(name);
-    info("Button " + name + ". hidden: " + button.hidden +
-         ", disabled: " + button.disabled);
-  }
-
-  info("Page shown: " +
-       gPFS.document.documentElement.currentPage.getAttribute("label"));
-  show_button_state("next");
-  show_button_state("finish");
-}
-
-function pfs_loaded() {
-  info("PFS loaded");
-  var docEle = gPFS.document.documentElement;
-
-  var onwizardfinish = function () {
-    info("wizardfinish event");
-  };
-  var onwizardnext = function () {
-    info("wizardnext event");
-  };
-
-  docEle.addEventListener("pageshow", page_shown, false);
-  docEle.addEventListener("wizardfinish", onwizardfinish, false);
-  docEle.addEventListener("wizardnext", onwizardnext, false);
-
-  gPFS.addEventListener("unload", function() {
-    info("unload event");
-    gPFS.removeEventListener("unload", arguments.callee, false);
-    docEle.removeEventListener("pageshow", page_shown, false);
-    docEle.removeEventListener("wizardfinish", onwizardfinish, false);
-    docEle.removeEventListener("wizardnext", onwizardnext, false);
-  }, false);
-
-  page_shown();
-}
-
-function startTest(num, missingPluginsArray) {
-  info("Test " + num);
-
-  gPFS = window.openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
-                           "PFSWindow", "chrome,centerscreen,resizable=yes",
-                           {plugins: missingPluginsArray});
-
-  var testScope = this;
-
-  gPFS.addEventListener("load", function () {
-    gPFS.removeEventListener("load", arguments.callee, false);
-
-    pfs_loaded();
-
-    var seenAvailable = false;
-    var expectAvailable = typeof testScope["test_" + num + "_available"] == "function";
-
-    function availableListener() {
-      seenAvailable = true;
-
-      if (expectAvailable) {
-        executeSoon(function () {
-          testScope["test_" + num + "_available"]();
-          gPFS.document.documentElement.getButton("next").click();
-        });
-      } else {
-        ok(false, "Should not have found plugins to install");
-      }
-    }
-
-    function completeListener() {
-      if (expectAvailable)
-        ok(seenAvailable, "Should have seen the list of available plugins");
-
-      executeSoon(testScope["test_" + num + "_complete"]);
-    }
-
-    gPFS.document.documentElement.wizardPages[1].addEventListener("pageshow", availableListener);
-    gPFS.document.documentElement.wizardPages[4].addEventListener("pageshow", completeListener);
-
-    gPFS.addEventListener("unload", function () {
-      gPFS.removeEventListener("unload", arguments.callee, false);
-      gPFS.document.documentElement.wizardPages[1].removeEventListener("pageshow", availableListener, false);
-      gPFS.document.documentElement.wizardPages[4].removeEventListener("pageshow", completeListener, false);
-
-      num++;
-      if (typeof testScope["prepare_test_" + num] == "function")
-        testScope["prepare_test_" + num]();
-      else
-        finishTest();
-    });
-  });
-}
-
-function clickFinish() {
-  var finish = gPFS.document.documentElement.getButton("finish");
-  ok(!finish.hidden, "Finish button should not be hidden");
-  ok(!finish.disabled, "Finish button should not be disabled");
-  finish.click();
-}
-
-// Test a working installer
-function prepare_test_1() {
-  Services.prefs.setCharPref("pfs.datasource.url", TEST_ROOT + "pfs_bug435788_1.rdf");
-
-  var missingPluginsArray = {
-    "application/x-working-plugin": {
-      mimetype: "application/x-working-plugin",
-      pluginsPage: ""
-    }
-  };
-
-  startTest(1, missingPluginsArray);
-}
-
-function test_1_available() {
-  is(getListCount(), 1, "Should have found 1 plugin to install");
-  ok(hasListItem("Test plugin 1", null), "Should have seen the right plugin name");
-}
-
-function test_1_complete() {
-  is(getResultCount(), 1, "Should have attempted to install 1 plugin");
-  var item = getResultItem("Test plugin 1", null);
-  ok(item, "Should have seen the installed item");
-  is(item.status, "Installed", "Should have been a successful install");
-
-  clickFinish();
-}
-
-// Test a broken installer (returns exit code 1)
-function prepare_test_2() {
-  var missingPluginsArray = {
-    "application/x-broken-installer": {
-      mimetype: "application/x-broken-installer",
-      pluginsPage: ""
-    }
-  };
-
-  startTest(2, missingPluginsArray);
-}
-
-function test_2_available() {
-  is(getListCount(), 1, "Should have found 1 plugin to install");
-  ok(hasListItem("Test plugin 2", null), "Should have seen the right plugin name");
-}
-
-function test_2_complete() {
-  is(getResultCount(), 1, "Should have attempted to install 1 plugin");
-  var item = getResultItem("Test plugin 2", null);
-  ok(item, "Should have seen the installed item");
-  is(item.status, "Failed", "Should have been a failed install");
-
-  clickFinish();
-}
-
-// Test both working and broken together
-function prepare_test_3() {
-  var missingPluginsArray = {
-    "application/x-working-plugin": {
-      mimetype: "application/x-working-plugin",
-      pluginsPage: ""
-    },
-    "application/x-broken-installer": {
-      mimetype: "application/x-broken-installer",
-      pluginsPage: ""
-    }
-  };
-
-  startTest(3, missingPluginsArray);
-}
-
-function test_3_available() {
-  is(getListCount(), 2, "Should have found 2 plugins to install");
-  ok(hasListItem("Test plugin 1", null), "Should have seen the right plugin name");
-  ok(hasListItem("Test plugin 2", null), "Should have seen the right plugin name");
-}
-
-function test_3_complete() {
-  is(getResultCount(), 2, "Should have attempted to install 2 plugins");
-  var item = getResultItem("Test plugin 1", null);
-  ok(item, "Should have seen the installed item");
-  is(item.status, "Installed", "Should have been a successful install");
-  item = getResultItem("Test plugin 2", null);
-  ok(item, "Should have seen the installed item");
-  is(item.status, "Failed", "Should have been a failed install");
-
-  clickFinish();
-}
-
-// Test an installer with a bad hash
-function prepare_test_4() {
-  var missingPluginsArray = {
-    "application/x-broken-plugin-hash": {
-      mimetype: "application/x-broken-plugin-hash",
-      pluginsPage: ""
-    }
-  };
-
-  startTest(4, missingPluginsArray);
-}
-
-function test_4_available() {
-  is(getListCount(), 1, "Should have found 1 plugin to install");
-  ok(hasListItem("Test plugin 3", null), "Should have seen the right plugin name");
-}
-
-function test_4_complete() {
-  is(getResultCount(), 1, "Should have attempted to install 1 plugin");
-  var item = getResultItem("Test plugin 3", null);
-  ok(item, "Should have seen the installed item");
-  is(item.status, "Failed", "Should have not been a successful install");
-
-  clickFinish();
-}
-
-// Test a working xpi
-function prepare_test_5() {
-  var missingPluginsArray = {
-    "application/x-working-extension": {
-      mimetype: "application/x-working-extension",
-      pluginsPage: ""
-    }
-  };
-
-  startTest(5, missingPluginsArray);
-}
-
-function test_5_available() {
-  is(getListCount(), 1, "Should have found 1 plugin to install");
-  ok(hasListItem("Test extension 1", null), "Should have seen the right plugin name");
-}
-
-function test_5_complete() {
-  is(getResultCount(), 1, "Should have attempted to install 1 plugin");
-  var item = getResultItem("Test extension 1", null);
-  ok(item, "Should have seen the installed item");
-  is(item.status, "Installed", "Should have been a successful install");
-
-  AddonManager.getAllInstalls(function(installs) {
-    is(installs.length, 1, "Should be just one install");
-    is(installs[0].state, AddonManager.STATE_INSTALLED, "Should be fully installed");
-    is(installs[0].addon.id, "bug435788_1@tests.mozilla.org", "Should have installed the extension");
-    installs[0].cancel();
-
-    clickFinish();
-  });
-}
-
-// Test a broke xpi (no install.rdf)
-function prepare_test_6() {
-  var missingPluginsArray = {
-    "application/x-broken-extension": {
-      mimetype: "application/x-broken-extension",
-      pluginsPage: ""
-    }
-  };
-
-  startTest(6, missingPluginsArray);
-}
-
-function test_6_available() {
-  is(getListCount(), 1, "Should have found 1 plugin to install");
-  ok(hasListItem("Test extension 2", null), "Should have seen the right plugin name");
-}
-
-function test_6_complete() {
-  is(getResultCount(), 1, "Should have attempted to install 1 plugin");
-  var item = getResultItem("Test extension 2", null);
-  ok(item, "Should have seen the installed item");
-  is(item.status, "Failed", "Should have been a failed install");
-
-  clickFinish();
-}
-
-// Test both working and broken xpi
-function prepare_test_7() {
-  var missingPluginsArray = {
-    "application/x-working-extension": {
-      mimetype: "application/x-working-extension",
-      pluginsPage: ""
-    },
-    "application/x-broken-extension": {
-      mimetype: "application/x-broken-extension",
-      pluginsPage: ""
-    }
-  };
-
-  startTest(7, missingPluginsArray);
-}
-
-function test_7_available() {
-  is(getListCount(), 2, "Should have found 2 plugins to install");
-  ok(hasListItem("Test extension 1", null), "Should have seen the right plugin name");
-  ok(hasListItem("Test extension 2", null), "Should have seen the right plugin name");
-}
-
-function test_7_complete() {
-  is(getResultCount(), 2, "Should have attempted to install 2 plugins");
-  var item = getResultItem("Test extension 1", null);
-  ok(item, "Should have seen the installed item");
-  is(item.status, "Installed", "Should have been a failed install");
-  item = getResultItem("Test extension 2", null);
-  ok(item, "Should have seen the installed item");
-  is(item.status, "Failed", "Should have been a failed install");
-
-  AddonManager.getAllInstalls(function(installs) {
-    is(installs.length, 1, "Should be one active installs");
-    installs[0].cancel();
-
-    clickFinish();
-  });
-}
-
-// Test an xpi with a bad hash
-function prepare_test_8() {
-  var missingPluginsArray = {
-    "application/x-broken-extension-hash": {
-      mimetype: "application/x-broken-extension-hash",
-      pluginsPage: ""
-    }
-  };
-
-  startTest(8, missingPluginsArray);
-}
-
-function test_8_available() {
-  is(getListCount(), 1, "Should have found 1 plugin to install");
-  ok(hasListItem("Test extension 3", null), "Should have seen the right plugin name");
-}
-
-function test_8_complete() {
-  is(getResultCount(), 1, "Should have attempted to install 1 plugin");
-  var item = getResultItem("Test extension 3", null);
-  ok(item, "Should have seen the installed item");
-  is(item.status, "Failed", "Should have not been a successful install");
-
-  AddonManager.getAllInstalls(function(installs) {
-    is(installs.length, 0, "Should not be any installs");
-
-    clickFinish();
-  });
-}
-
-// Test when no plugin exists in the datasource
-function prepare_test_9() {
-  var missingPluginsArray = {
-    "application/x-unknown-plugin": {
-      mimetype: "application/x-unknown-plugin",
-      pluginsPage: ""
-    }
-  };
-
-  startTest(9, missingPluginsArray);
-}
-
-function test_9_complete() {
-  is(getResultCount(), 0, "Should have found no plugins");
-
-  clickFinish();
-}
-
-// Test when the datasource is invalid xml
-function prepare_test_10() {
-  Services.prefs.setCharPref("pfs.datasource.url", TEST_ROOT + "pfs_bug435788_2.rdf");
-
-  var missingPluginsArray = {
-    "application/x-broken-xml": {
-      mimetype: "application/x-broken-xml",
-      pluginsPage: ""
-    }
-  };
-
-  startTest(10, missingPluginsArray);
-}
-
-function test_10_complete() {
-  is(getResultCount(), 0, "Should have found no plugins");
-
-  clickFinish();
-}
-
-// Test when no datasource is returned
-function prepare_test_11() {
-  Services.prefs.setCharPref("pfs.datasource.url", TEST_ROOT + "pfs_bug435788_foo.rdf");
-
-  var missingPluginsArray = {
-    "application/x-missing-xml": {
-      mimetype: "application/x-missing-xml",
-      pluginsPage: ""
-    }
-  };
-
-  startTest(11, missingPluginsArray);
-}
-
-function test_11_complete() {
-  is(getResultCount(), 0, "Should have found no plugins");
-
-  clickFinish();
-}
deleted file mode 100644
--- a/toolkit/mozapps/plugins/tests/moz.build
+++ /dev/null
@@ -1,13 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-SimplePrograms([
-    'BadPlugin',
-    'GoodPlugin',
-])
-
-BROWSER_CHROME_MANIFESTS += ['browser.ini']
-USE_STATIC_LIBS = True
deleted file mode 100644
--- a/toolkit/mozapps/plugins/tests/pfs_bug435788_1.rdf
+++ /dev/null
@@ -1,161 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:pfs="http://www.mozilla.org/2004/pfs-rdf#">
-
-<RDF:Description about="urn:mozilla:plugin-results:application/x-working-plugin">
- <pfs:plugins><RDF:Seq>
-  <RDF:li resource="urn:mozilla:plugin:{8d3ab839-e03e-41a5-acd3-be1eabf94810}"/>
- </RDF:Seq></pfs:plugins>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin:{8d3ab839-e03e-41a5-acd3-be1eabf94810}">
- <pfs:updates><RDF:Seq>
-  <RDF:li resource="urn:mozilla:plugin:{8d3ab839-e03e-41a5-acd3-be1eabf94810}:"/>
- </RDF:Seq></pfs:updates>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin:{8d3ab839-e03e-41a5-acd3-be1eabf94810}:">
- <pfs:name>Test plugin 1</pfs:name>
- <pfs:requestedMimetype>application/x-working-plugin</pfs:requestedMimetype>
- <pfs:guid>{8d3ab839-e03e-41a5-acd3-be1eabf94810}</pfs:guid>
- <pfs:version/>
- <pfs:IconUrl/>
- <pfs:InstallerLocation>http://example.com/browser/toolkit/mozapps/plugins/tests/GoodPlugin</pfs:InstallerLocation>
- <pfs:InstallerHash/>
- <pfs:InstallerShowsUI>false</pfs:InstallerShowsUI>
- <pfs:manualInstallationURL>http://www.mozilla.com</pfs:manualInstallationURL>
- <pfs:licenseURL/>
- <pfs:needsRestart>false</pfs:needsRestart>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin-results:application/x-broken-installer">
- <pfs:plugins><RDF:Seq>
-  <RDF:li resource="urn:mozilla:plugin:{949a51e0-c633-4d9e-bf3a-6e0c2bc6e508}"/>
- </RDF:Seq></pfs:plugins>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin:{949a51e0-c633-4d9e-bf3a-6e0c2bc6e508}">
- <pfs:updates><RDF:Seq>
-  <RDF:li resource="urn:mozilla:plugin:{949a51e0-c633-4d9e-bf3a-6e0c2bc6e508}:"/>
- </RDF:Seq></pfs:updates>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin:{949a51e0-c633-4d9e-bf3a-6e0c2bc6e508}:">
- <pfs:name>Test plugin 2</pfs:name>
- <pfs:requestedMimetype>application/x-broken-installer</pfs:requestedMimetype>
- <pfs:guid>{949a51e0-c633-4d9e-bf3a-6e0c2bc6e508}</pfs:guid>
- <pfs:version/>
- <pfs:IconUrl/>
- <pfs:InstallerLocation>http://example.com/browser/toolkit/mozapps/plugins/tests/BadPlugin</pfs:InstallerLocation>
- <pfs:InstallerHash/>
- <pfs:InstallerShowsUI>false</pfs:InstallerShowsUI>
- <pfs:manualInstallationURL>http://www.mozilla.com</pfs:manualInstallationURL>
- <pfs:licenseURL/>
- <pfs:needsRestart>false</pfs:needsRestart>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin-results:application/x-broken-plugin-hash">
- <pfs:plugins><RDF:Seq>
-  <RDF:li resource="urn:mozilla:plugin:{cf1765cc-f962-4680-8a4d-2ba13971a24f}"/>
- </RDF:Seq></pfs:plugins>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin:{cf1765cc-f962-4680-8a4d-2ba13971a24f}">
- <pfs:updates><RDF:Seq>
-  <RDF:li resource="urn:mozilla:plugin:{cf1765cc-f962-4680-8a4d-2ba13971a24f}:"/>
- </RDF:Seq></pfs:updates>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin:{cf1765cc-f962-4680-8a4d-2ba13971a24f}:">
- <pfs:name>Test plugin 3</pfs:name>
- <pfs:requestedMimetype>application/x-broken-plugin-hash</pfs:requestedMimetype>
- <pfs:guid>{8d3ab839-e03e-41a5-acd3-be1eabf94810}</pfs:guid>
- <pfs:version/>
- <pfs:IconUrl/>
- <pfs:InstallerLocation>http://example.com/browser/toolkit/mozapps/plugins/tests/GoodPlugin</pfs:InstallerLocation>
- <pfs:InstallerHash>sha1:foo</pfs:InstallerHash>
- <pfs:InstallerShowsUI>false</pfs:InstallerShowsUI>
- <pfs:manualInstallationURL>http://www.mozilla.com</pfs:manualInstallationURL>
- <pfs:licenseURL/>
- <pfs:needsRestart>false</pfs:needsRestart>
-</RDF:Description>
-
-
-<RDF:Description about="urn:mozilla:plugin-results:application/x-working-extension">
- <pfs:plugins><RDF:Seq>
-  <RDF:li resource="urn:mozilla:plugin:{2fdd6830-66fe-43e7-8ef6-ce49998c8926}"/>
- </RDF:Seq></pfs:plugins>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin:{2fdd6830-66fe-43e7-8ef6-ce49998c8926}">
- <pfs:updates><RDF:Seq>
-  <RDF:li resource="urn:mozilla:plugin:{2fdd6830-66fe-43e7-8ef6-ce49998c8926}:"/>
- </RDF:Seq></pfs:updates>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin:{2fdd6830-66fe-43e7-8ef6-ce49998c8926}:">
- <pfs:name>Test extension 1</pfs:name>
- <pfs:requestedMimetype>application/x-working-extension</pfs:requestedMimetype>
- <pfs:guid>{2fdd6830-66fe-43e7-8ef6-ce49998c8926}</pfs:guid>
- <pfs:version/>
- <pfs:IconUrl/>
- <pfs:XPILocation>http://example.com/browser/toolkit/mozapps/plugins/tests/GoodExtension.xpi</pfs:XPILocation>
- <pfs:XPIHash>sha1:ccda7915ba891cc6fd9d0874b8042be2ecaa2d65</pfs:XPIHash>
- <pfs:InstallerShowsUI>false</pfs:InstallerShowsUI>
- <pfs:manualInstallationURL>http://www.mozilla.com</pfs:manualInstallationURL>
- <pfs:licenseURL/>
- <pfs:needsRestart>false</pfs:needsRestart>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin-results:application/x-broken-extension">
- <pfs:plugins><RDF:Seq>
-  <RDF:li resource="urn:mozilla:plugin:{91fdf294-3a51-48c6-b5e8-423b0eb4e84e}"/>
- </RDF:Seq></pfs:plugins>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin:{91fdf294-3a51-48c6-b5e8-423b0eb4e84e}">
- <pfs:updates><RDF:Seq>
-  <RDF:li resource="urn:mozilla:plugin:{91fdf294-3a51-48c6-b5e8-423b0eb4e84e}:"/>
- </RDF:Seq></pfs:updates>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin:{91fdf294-3a51-48c6-b5e8-423b0eb4e84e}:">
- <pfs:name>Test extension 2</pfs:name>
- <pfs:requestedMimetype>application/x-broken-extension</pfs:requestedMimetype>
- <pfs:guid>{91fdf294-3a51-48c6-b5e8-423b0eb4e84e}</pfs:guid>
- <pfs:version/>
- <pfs:IconUrl/>
- <pfs:XPILocation>http://example.com/browser/toolkit/mozapps/plugins/tests/BadExtension.xpi</pfs:XPILocation>
- <pfs:XPIHash/>
- <pfs:InstallerShowsUI>false</pfs:InstallerShowsUI>
- <pfs:manualInstallationURL>http://www.mozilla.com</pfs:manualInstallationURL>
- <pfs:licenseURL/>
- <pfs:needsRestart>false</pfs:needsRestart>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin-results:application/x-broken-extension-hash">
- <pfs:plugins><RDF:Seq>
-  <RDF:li resource="urn:mozilla:plugin:{889d0f32-4c77-4a57-8be1-44d109a210e4}"/>
- </RDF:Seq></pfs:plugins>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin:{889d0f32-4c77-4a57-8be1-44d109a210e4}">
- <pfs:updates><RDF:Seq>
-  <RDF:li resource="urn:mozilla:plugin:{889d0f32-4c77-4a57-8be1-44d109a210e4}:"/>
- </RDF:Seq></pfs:updates>
-</RDF:Description>
-
-<RDF:Description about="urn:mozilla:plugin:{889d0f32-4c77-4a57-8be1-44d109a210e4}:">
- <pfs:name>Test extension 3</pfs:name>
- <pfs:requestedMimetype>application/x-broken-extension-hash</pfs:requestedMimetype>
- <pfs:guid>{889d0f32-4c77-4a57-8be1-44d109a210e4}</pfs:guid>
- <pfs:version/>
- <pfs:IconUrl/>
- <pfs:XPILocation>http://example.com/browser/toolkit/mozapps/plugins/tests/GoodExtension.xpi</pfs:XPILocation>
- <pfs:XPIHash>sha1:3504e7bd87bad1246b7a016b29bdb50fe41dbc83</pfs:XPIHash>
- <pfs:InstallerShowsUI>false</pfs:InstallerShowsUI>
- <pfs:manualInstallationURL>http://www.mozilla.com</pfs:manualInstallationURL>
- <pfs:licenseURL/>
- <pfs:needsRestart>false</pfs:needsRestart>
-</RDF:Description>
-
-</RDF:RDF>
deleted file mode 100644
--- a/toolkit/mozapps/plugins/tests/pfs_bug435788_2.rdf
+++ /dev/null
@@ -1,4 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<RDF:RDF xmlns:RDF="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:pfs="http://www.mozilla.org/2004/pfs-rdf#">
-
-</RDF>
--- a/toolkit/themes/windows/mozapps/jar.mn
+++ b/toolkit/themes/windows/mozapps/jar.mn
@@ -74,17 +74,16 @@ toolkit.jar:
         skin/classic/mozapps/plugins/contentPluginStripe.png       (../../shared/plugins/contentPluginStripe.png)
         skin/classic/mozapps/plugins/notifyPluginCrashed.png       (plugins/pluginGeneric-16.png)
         skin/classic/mozapps/plugins/notifyPluginGeneric.png       (plugins/pluginGeneric-16.png)
         skin/classic/mozapps/plugins/pluginGeneric.png             (plugins/pluginGeneric.png)
         skin/classic/mozapps/plugins/pluginBlocked.png             (plugins/pluginBlocked.png)
         skin/classic/mozapps/plugins/pluginBlocked-64.png          (plugins/pluginBlocked-64.png)
         skin/classic/mozapps/plugins/pluginGeneric-16.png          (plugins/pluginGeneric-16.png)
         skin/classic/mozapps/plugins/pluginHelp-16.png             (plugins/pluginHelp-16.png)
-        skin/classic/mozapps/plugins/pluginInstallerWizard.css     (plugins/pluginInstallerWizard.css)
         skin/classic/mozapps/profile/profileicon.png               (profile/profileicon.png)
         skin/classic/mozapps/profile/profileSelection.css          (profile/profileSelection.css)
         skin/classic/mozapps/update/downloadButtons.png            (update/downloadButtons.png)
         skin/classic/mozapps/update/updates.css                    (update/updates.css)
         skin/classic/mozapps/viewsource/viewsource.css             (viewsource/viewsource.css)
 *       skin/classic/mozapps/xpinstall/xpinstallConfirm.css        (extensions/xpinstallConfirm.css)
         skin/classic/mozapps/xpinstall/xpinstallItemGeneric.png    (extensions/extensionGeneric.png)
 
@@ -158,17 +157,16 @@ toolkit.jar:
         skin/classic/aero/mozapps/plugins/contentPluginStripe.png          (../../shared/plugins/contentPluginStripe.png)
         skin/classic/aero/mozapps/plugins/notifyPluginCrashed.png          (plugins/pluginGeneric-16-aero.png)
         skin/classic/aero/mozapps/plugins/notifyPluginGeneric.png          (plugins/pluginGeneric-16-aero.png)
         skin/classic/aero/mozapps/plugins/pluginGeneric.png                (plugins/pluginGeneric-aero.png)
         skin/classic/aero/mozapps/plugins/pluginBlocked.png                (plugins/pluginBlocked-aero.png)
         skin/classic/aero/mozapps/plugins/pluginBlocked-64.png             (plugins/pluginBlocked-64.png)
         skin/classic/aero/mozapps/plugins/pluginGeneric-16.png             (plugins/pluginGeneric-16-aero.png)
         skin/classic/aero/mozapps/plugins/pluginHelp-16.png                (plugins/pluginHelp-16.png)
-        skin/classic/aero/mozapps/plugins/pluginInstallerWizard.css        (plugins/pluginInstallerWizard.css)
         skin/classic/aero/mozapps/profile/profileicon.png                  (profile/profileicon-aero.png)
         skin/classic/aero/mozapps/profile/profileSelection.css             (profile/profileSelection.css)
         skin/classic/aero/mozapps/update/downloadButtons.png               (update/downloadButtons-aero.png)
         skin/classic/aero/mozapps/update/updates.css                       (update/updates.css)
         skin/classic/aero/mozapps/viewsource/viewsource.css                (viewsource/viewsource.css)
 *       skin/classic/aero/mozapps/xpinstall/xpinstallConfirm.css           (extensions/xpinstallConfirm.css)
         skin/classic/aero/mozapps/xpinstall/xpinstallItemGeneric.png       (extensions/extensionGeneric-aero.png)
 #endif
--- a/xpcom/base/SystemMemoryReporter.cpp
+++ b/xpcom/base/SystemMemoryReporter.cpp
@@ -538,16 +538,18 @@ private:
         }
 
       } else {
         aName.AppendLiteral("other-files");
         if (EndsWithLiteral(basename, ".xpi")) {
           aName.AppendLiteral("/extensions");
         } else if (dirname.Find("/fontconfig") != -1) {
           aName.AppendLiteral("/fontconfig");
+        } else {
+          aName.AppendLiteral("/misc");
         }
         aTag = aName;
         aName.Append('/');
       }
 
       aName.Append(basename);
       aDesc.Append(absPath);
     } else {