Merge m-c to m-i
authorPhil Ringnalda <philringnalda@gmail.com>
Mon, 07 Nov 2016 20:40:30 -0800
changeset 348304 4a7fb43aa9dc6e13dfa08288fa2d01f102e27b57
parent 348303 4a38ccb01816b597f7aee44f6895fd4d00b2f01f (current diff)
parent 348147 f13e90d496cf1bc6dfc4fd398da33e4afe785bde (diff)
child 348305 2da969fcf8424a525f9048eaf7da336db45c3e86
push id10298
push userraliiev@mozilla.com
push dateMon, 14 Nov 2016 12:33:03 +0000
treeherdermozilla-aurora@7e29173b1641 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone52.0a1
Merge m-c to m-i MozReview-Commit-ID: 8cFOopE3aHd
dom/base/nsGlobalWindow.cpp
dom/base/test/test_frameLoader_switchProcess.html
layout/base/ServoRestyleManager.cpp
testing/mozharness/mozharness/mozilla/googleplay.py
testing/mozharness/mozharness/mozilla/storel10n.py
testing/mozharness/scripts/get_apk.py
testing/mozharness/scripts/push_apk.py
testing/mozharness/scripts/update_apk_description.py
toolkit/components/extensions/ExtensionUtils.jsm
toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled.js
--- a/browser/components/sessionstore/test/browser_restore_cookies_noOriginAttributes.js
+++ b/browser/components/sessionstore/test/browser_restore_cookies_noOriginAttributes.js
@@ -101,17 +101,16 @@ const SESSION_DATA_OA = `
       "host": "www.example.com",
       "value": "yes1",
       "path": "/browser/browser/components/sessionstore/test/",
       "name": "test1",
       "originAttributes": {
         "addonId": "",
         "appId": 0,
         "inIsolatedMozBrowser": false,
-        "signedPkg": "",
         "userContextId": 0
       }
     }]
   }],
   "selectedWindow": 1,
   "_closedWindows": [],
   "session": {
     "lastUpdate": 1463893009801,
--- a/browser/extensions/pocket/bootstrap.js
+++ b/browser/extensions/pocket/bootstrap.js
@@ -172,17 +172,16 @@ var PocketContextMenu = {
         if (element)
           element.remove();
       }
     }
   },
   observe: function(aSubject, aTopic, aData) {
     let subject = aSubject.wrappedJSObject;
     let document = subject.menu.ownerDocument;
-    let window = document.defaultView;
     let pocketEnabled = CustomizableUI.getPlacementOfWidget("pocket-button");
 
     let showSaveCurrentPageToPocket = !(subject.onTextInput || subject.onLink ||
                                         subject.isContentSelected || subject.onImage ||
                                         subject.onCanvas || subject.onVideo || subject.onAudio);
     let targetUrl = subject.onLink ? subject.linkUrl : subject.pageUrl;
     let targetURI = Services.io.newURI(targetUrl, null, null);
     let canPocket = pocketEnabled && (targetURI.schemeIs("http") || targetURI.schemeIs("https") ||
--- a/browser/extensions/pocket/content/main.js
+++ b/browser/extensions/pocket/content/main.js
@@ -47,29 +47,24 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
   "resource://gre/modules/ReaderMode.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "pktApi",
   "chrome://pocket/content/pktApi.jsm");
 
 var pktUI = (function() {
 
     // -- Initialization (on startup and new windows) -- //
-    var inited = false;
     var _currentPanelDidShow;
     var _currentPanelDidHide;
-    var _isHidden = false;
-    var _notificationTimeout;
 
     // Init panel id at 0. The first actual panel id will have the number 1 so
     // in case at some point any panel has the id 0 we know there is something
     // wrong
     var _panelId = 0;
 
-    var prefBranch = Services.prefs.getBranch("extensions.pocket.settings.");
-
     var overflowMenuWidth = 230;
     var overflowMenuHeight = 475;
     var savePanelWidth = 350;
     var savePanelHeights = {collapsed: 153, expanded: 272};
 
     // -- Event Handling -- //
 
     /**
@@ -88,23 +83,16 @@ var pktUI = (function() {
             _currentPanelDidHide(event);
         }
 
         // clear the panel
         getPanelFrame().setAttribute('src', 'about:blank');
     }
 
 
-    /**
-     * Event handler when Pocket bookmark bar entry is pressed
-     */
-     function pocketBookmarkBarOpenPocketCommand(event) {
-        openTabWithUrl('https://getpocket.com/a/', true);
-     }
-
     // -- Communication to API -- //
 
     /**
      * Either save or attempt to log the user in
      */
     function tryToSaveCurrentPage() {
         tryToSaveUrl(getCurrentUrl(), getCurrentTitle());
     }
@@ -168,17 +156,17 @@ var pktUI = (function() {
             {
                 variant = 'overflow';
             }
             else
             {
                 variant = 'storyboard_lm';
             }
 
-            var panelId = showPanel("about:pocket-signup?pockethost="
+            showPanel("about:pocket-signup?pockethost="
                 + Services.prefs.getCharPref("extensions.pocket.site")
                 + "&fxasignedin="
                 + fxasignedin
                 + "&variant="
                 + variant
                 + '&controlvariant='
                 + controlvariant
                 + '&inoverflowmenu='
@@ -569,24 +557,16 @@ var pktUI = (function() {
         return null;
     }
 
     function isInOverflowMenu() {
         var subview = getSubview();
         return !!subview;
     }
 
-    function hasLegacyExtension() {
-        return !!document.getElementById('RIL_urlbar_add');
-    }
-
-    function isHidden() {
-        return _isHidden;
-    }
-
     function getFirefoxAccountSignedInUser(callback) {
         fxAccounts.getSignedInUser().then(userData => {
             callback(userData);
         }).then(null, error => {
             callback();
         });
     }
 
--- a/browser/extensions/pocket/content/panels/js/saved.js
+++ b/browser/extensions/pocket/content/panels/js/saved.js
@@ -23,24 +23,21 @@ var PKT_SAVED_OVERLAY = function (option
     this.mouseInside = false;
     this.userTags = [];
     this.cxt_suggested_available = 0;
     this.cxt_entered = 0;
     this.cxt_suggested = 0;
     this.cxt_removed = 0;
     this.justaddedsuggested = false;
     this.fillTagContainer = function(tags, container, tagclass) {
-        var newtagleft = 0;
         container.children().remove();
         for (var i = 0; i < tags.length; i++) {
             var newtag = $('<li><a href="#" class="token_tag ' + tagclass + '">' + tags[i] + '</a></li>');
             container.append(newtag);
-            var templeft = newtag.position().left;
             this.cxt_suggested_available++;
-            newtagleft = templeft;
         }
     };
     this.fillUserTags = function() {
         thePKT_SAVED.sendMessage("getTags", {}, function(resp)
         {
             if (typeof resp == 'object' && typeof resp.tags == 'object')
             {
                 myself.userTags = resp.tags;
@@ -602,9 +599,8 @@ PKT_SAVED.prototype = {
                 'https://'+ pocketHost +'/tos?s=ffi&t=tos&tv=panel_tryit',
                 'https://'+ pocketHost +'/privacy?s=ffi&t=privacypolicy&tv=panel_tryit'
             ]
         }, function(resp) {
         window.pocketStrings = resp.strings;
         window.thePKT_SAVED.create();
     });
 });
-
--- a/browser/extensions/pocket/content/panels/js/signup.js
+++ b/browser/extensions/pocket/content/panels/js/signup.js
@@ -58,18 +58,16 @@ var PKT_SIGNUP_OVERLAY = function (optio
         this.dictJSON = window.pocketStrings;
     };
 
 };
 
 PKT_SIGNUP_OVERLAY.prototype = {
     create : function()
     {
-        var myself = this;
-
         var controlvariant = window.location.href.match(/controlvariant=([\w|\.]*)&?/);
         if (controlvariant && controlvariant.length > 1)
         {
             this.controlvariant = controlvariant[1];
         }
         var variant = window.location.href.match(/variant=([\w|\.]*)&?/);
         if (variant && variant.length > 1)
         {
@@ -188,9 +186,8 @@ PKT_SIGNUP.prototype = {
                 'https://'+ pocketHost +'/tos?s=ffi&t=tos&tv=panel_tryit',
                 'https://'+ pocketHost +'/privacy?s=ffi&t=privacypolicy&tv=panel_tryit'
             ]
         }, function(resp) {
         window.pocketStrings = resp.strings;
         window.thePKT_SIGNUP.create();
     });
 });
-
--- a/browser/modules/test/browser.ini
+++ b/browser/modules/test/browser.ini
@@ -2,16 +2,17 @@
 support-files =
   head.js
 
 [browser_BrowserUITelemetry_buckets.js]
 [browser_BrowserUITelemetry_defaults.js]
 [browser_BrowserUITelemetry_sidebar.js]
 [browser_BrowserUITelemetry_syncedtabs.js]
 [browser_CaptivePortalWatcher.js]
+skip-if = os == "win" # Bug 1313894
 [browser_ContentSearch.js]
 support-files =
   contentSearch.js
   contentSearchBadImage.xml
   contentSearchSuggestions.sjs
   contentSearchSuggestions.xml
 [browser_NetworkPrioritizer.js]
 [browser_PermissionUI.js]
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -38,34 +38,28 @@ PrincipalOriginAttributes::InheritFromDo
                                                     const nsIURI* aURI)
 {
   mAppId = aAttrs.mAppId;
   mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser;
 
   // addonId is computed from the principal URI and never propagated
   mUserContextId = aAttrs.mUserContextId;
 
-  // TODO:
-  // Bug 1225349 - PrincipalOriginAttributes should inherit mSignedPkg
-  // accordingly by URI
-  mSignedPkg = aAttrs.mSignedPkg;
-
   mPrivateBrowsingId = aAttrs.mPrivateBrowsingId;
   mFirstPartyDomain = aAttrs.mFirstPartyDomain;
 }
 
 void
 PrincipalOriginAttributes::InheritFromNecko(const NeckoOriginAttributes& aAttrs)
 {
   mAppId = aAttrs.mAppId;
   mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser;
 
   // addonId is computed from the principal URI and never propagated
   mUserContextId = aAttrs.mUserContextId;
-  mSignedPkg = aAttrs.mSignedPkg;
 
   mPrivateBrowsingId = aAttrs.mPrivateBrowsingId;
   mFirstPartyDomain = aAttrs.mFirstPartyDomain;
 }
 
 void
 PrincipalOriginAttributes::StripUserContextIdAndFirstPartyDomain()
 {
@@ -77,57 +71,44 @@ void
 DocShellOriginAttributes::InheritFromDocToChildDocShell(const PrincipalOriginAttributes& aAttrs)
 {
   mAppId = aAttrs.mAppId;
   mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser;
 
   // addonId is computed from the principal URI and never propagated
   mUserContextId = aAttrs.mUserContextId;
 
-  // TODO:
-  // Bug 1225353 - DocShell/NeckoOriginAttributes should inherit
-  // mSignedPkg accordingly by mSignedPkgInBrowser
-  mSignedPkg = aAttrs.mSignedPkg;
-
   mPrivateBrowsingId = aAttrs.mPrivateBrowsingId;
   mFirstPartyDomain = aAttrs.mFirstPartyDomain;
 }
 
 void
 NeckoOriginAttributes::InheritFromDocToNecko(const PrincipalOriginAttributes& aAttrs)
 {
   mAppId = aAttrs.mAppId;
   mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser;
 
   // addonId is computed from the principal URI and never propagated
   mUserContextId = aAttrs.mUserContextId;
 
-  // TODO:
-  // Bug 1225353 - DocShell/NeckoOriginAttributes should inherit
-  // mSignedPkg accordingly by mSignedPkgInBrowser
-
   mPrivateBrowsingId = aAttrs.mPrivateBrowsingId;
   mFirstPartyDomain = aAttrs.mFirstPartyDomain;
 }
 
 void
 NeckoOriginAttributes::InheritFromDocShellToNecko(const DocShellOriginAttributes& aAttrs,
                                                   const bool aIsTopLevelDocument,
                                                   nsIURI* aURI)
 {
   mAppId = aAttrs.mAppId;
   mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser;
 
   // addonId is computed from the principal URI and never propagated
   mUserContextId = aAttrs.mUserContextId;
 
-  // TODO:
-  // Bug 1225353 - DocShell/NeckoOriginAttributes should inherit
-  // mSignedPkg accordingly by mSignedPkgInBrowser
-
   mPrivateBrowsingId = aAttrs.mPrivateBrowsingId;
 
   bool isFirstPartyEnabled = IsFirstPartyEnabled();
 
   // When the pref is on, we also compute the firstPartyDomain attribute
   // if this is for top-level document.
   if (isFirstPartyEnabled && aIsTopLevelDocument) {
     nsCOMPtr<nsIEffectiveTLDService> tldService = do_GetService(NS_EFFECTIVETLDSERVICE_CONTRACTID);
@@ -178,20 +159,16 @@ OriginAttributes::CreateSuffix(nsACStrin
   }
 
   if (mUserContextId != nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) {
     value.Truncate();
     value.AppendInt(mUserContextId);
     params->Set(NS_LITERAL_STRING("userContextId"), value);
   }
 
-  if (!mSignedPkg.IsEmpty()) {
-    MOZ_RELEASE_ASSERT(mSignedPkg.FindCharInSet(dom::quota::QuotaManager::kReplaceChars) == kNotFound);
-    params->Set(NS_LITERAL_STRING("signedPkg"), mSignedPkg);
-  }
 
   if (mPrivateBrowsingId) {
     value.Truncate();
     value.AppendInt(mPrivateBrowsingId);
     params->Set(NS_LITERAL_STRING("privateBrowsingId"), value);
   }
 
   if (!mFirstPartyDomain.IsEmpty()) {
@@ -264,22 +241,16 @@ public:
       int64_t val  = aValue.ToInteger64(&rv);
       NS_ENSURE_SUCCESS(rv, false);
       NS_ENSURE_TRUE(val <= UINT32_MAX, false);
       mOriginAttributes->mUserContextId  = static_cast<uint32_t>(val);
 
       return true;
     }
 
-    if (aName.EqualsLiteral("signedPkg")) {
-      MOZ_RELEASE_ASSERT(mOriginAttributes->mSignedPkg.IsEmpty());
-      mOriginAttributes->mSignedPkg.Assign(aValue);
-      return true;
-    }
-
     if (aName.EqualsLiteral("privateBrowsingId")) {
       nsresult rv;
       int64_t val = aValue.ToInteger64(&rv);
       NS_ENSURE_SUCCESS(rv, false);
       NS_ENSURE_TRUE(val >= 0 && val <= UINT32_MAX, false);
       mOriginAttributes->mPrivateBrowsingId = static_cast<uint32_t>(val);
 
       return true;
@@ -344,17 +315,16 @@ OriginAttributes::SyncAttributesWithPriv
 
 void
 OriginAttributes::SetFromGenericAttributes(const GenericOriginAttributes& aAttrs)
 {
   mAppId = aAttrs.mAppId;
   mInIsolatedMozBrowser = aAttrs.mInIsolatedMozBrowser;
   mAddonId = aAttrs.mAddonId;
   mUserContextId = aAttrs.mUserContextId;
-  mSignedPkg = aAttrs.mSignedPkg;
   mPrivateBrowsingId = aAttrs.mPrivateBrowsingId;
   mFirstPartyDomain = aAttrs.mFirstPartyDomain;
 }
 
 bool
 OriginAttributes::IsFirstPartyEnabled()
 {
   // Cache the privacy.firstparty.isolate pref.
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -31,17 +31,16 @@ class OriginAttributes : public dom::Ori
 {
 public:
   bool operator==(const OriginAttributes& aOther) const
   {
     return mAppId == aOther.mAppId &&
            mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser &&
            mAddonId == aOther.mAddonId &&
            mUserContextId == aOther.mUserContextId &&
-           mSignedPkg == aOther.mSignedPkg &&
            mPrivateBrowsingId == aOther.mPrivateBrowsingId &&
            mFirstPartyDomain == aOther.mFirstPartyDomain;
   }
   bool operator!=(const OriginAttributes& aOther) const
   {
     return !(*this == aOther);
   }
 
@@ -186,20 +185,16 @@ public:
     if (mAddonId.WasPassed() && mAddonId.Value() != aAttrs.mAddonId) {
       return false;
     }
 
     if (mUserContextId.WasPassed() && mUserContextId.Value() != aAttrs.mUserContextId) {
       return false;
     }
 
-    if (mSignedPkg.WasPassed() && mSignedPkg.Value() != aAttrs.mSignedPkg) {
-      return false;
-    }
-
     if (mPrivateBrowsingId.WasPassed() && mPrivateBrowsingId.Value() != aAttrs.mPrivateBrowsingId) {
       return false;
     }
 
     if (mFirstPartyDomain.WasPassed() && mFirstPartyDomain.Value() != aAttrs.mFirstPartyDomain) {
       return false;
     }
 
@@ -224,21 +219,16 @@ public:
       return false;
     }
 
     if (mUserContextId.WasPassed() && aOther.mUserContextId.WasPassed() &&
         mUserContextId.Value() != aOther.mUserContextId.Value()) {
       return false;
     }
 
-    if (mSignedPkg.WasPassed() && aOther.mSignedPkg.WasPassed() &&
-        mSignedPkg.Value() != aOther.mSignedPkg.Value()) {
-      return false;
-    }
-
     if (mPrivateBrowsingId.WasPassed() && aOther.mPrivateBrowsingId.WasPassed() &&
         mPrivateBrowsingId.Value() != aOther.mPrivateBrowsingId.Value()) {
       return false;
     }
 
     if (mFirstPartyDomain.WasPassed() && aOther.mFirstPartyDomain.WasPassed() &&
         mFirstPartyDomain.Value() != aOther.mFirstPartyDomain.Value()) {
       return false;
--- a/caps/tests/unit/test_origin.js
+++ b/caps/tests/unit/test_origin.js
@@ -44,31 +44,29 @@ function checkSandboxOriginAttributes(ar
 
 // utility function useful for debugging
 function printAttrs(name, attrs) {
   do_print(name + " {\n" +
            "\tappId: " + attrs.appId + ",\n" +
            "\tuserContextId: " + attrs.userContextId + ",\n" +
            "\tinIsolatedMozBrowser: " + attrs.inIsolatedMozBrowser + ",\n" +
            "\taddonId: '" + attrs.addonId + "',\n" +
-           "\tsignedPkg: '" + attrs.signedPkg + "',\n" +
            "\tprivateBrowsingId: '" + attrs.privateBrowsingId + "',\n" +
            "\tfirstPartyDomain: '" + attrs.firstPartyDomain + "'\n}");
 }
 
 
 function checkValues(attrs, values) {
   values = values || {};
   //printAttrs("attrs", attrs);
   //printAttrs("values", values);
   do_check_eq(attrs.appId, values.appId || 0);
   do_check_eq(attrs.userContextId, values.userContextId || 0);
   do_check_eq(attrs.inIsolatedMozBrowser, values.inIsolatedMozBrowser || false);
   do_check_eq(attrs.addonId, values.addonId || '');
-  do_check_eq(attrs.signedPkg, values.signedPkg || '');
   do_check_eq(attrs.privateBrowsingId, values.privateBrowsingId || '');
   do_check_eq(attrs.firstPartyDomain, values.firstPartyDomain || '');
 }
 
 function run_test() {
   // Attributeless origins.
   do_check_eq(ssm.getSystemPrincipal().origin, '[System Principal]');
   checkOriginAttributes(ssm.getSystemPrincipal());
@@ -166,70 +164,38 @@ function run_test() {
 
   // UserContext and App.
   var exampleOrg_userContextApp = ssm.createCodebasePrincipal(makeURI('http://example.org'), {appId: 24, userContextId: 42});
   var nullPrin_userContextApp = ssm.createNullPrincipal({appId: 24, userContextId: 42});
   checkOriginAttributes(exampleOrg_userContextApp, {appId: 24, userContextId: 42}, '^appId=24&userContextId=42');
   checkOriginAttributes(nullPrin_userContextApp, {appId: 24, userContextId: 42}, '^appId=24&userContextId=42');
   do_check_eq(exampleOrg_userContextApp.origin, 'http://example.org^appId=24&userContextId=42');
 
-  // Just signedPkg
-  var exampleOrg_signedPkg = ssm.createCodebasePrincipal(makeURI('http://example.org'), {signedPkg: 'whatever'});
-  checkOriginAttributes(exampleOrg_signedPkg, { signedPkg: 'whatever' }, '^signedPkg=whatever');
-  do_check_eq(exampleOrg_signedPkg.origin, 'http://example.org^signedPkg=whatever');
-
-  // signedPkg and browser
-  var exampleOrg_signedPkg_browser = ssm.createCodebasePrincipal(makeURI('http://example.org'), {signedPkg: 'whatever', inIsolatedMozBrowser: true});
-  checkOriginAttributes(exampleOrg_signedPkg_browser, { signedPkg: 'whatever', inIsolatedMozBrowser: true }, '^inBrowser=1&signedPkg=whatever');
-  do_check_eq(exampleOrg_signedPkg_browser.origin, 'http://example.org^inBrowser=1&signedPkg=whatever');
-
-  // Just signedPkg (but different value from 'exampleOrg_signedPkg_app')
-  var exampleOrg_signedPkg_another = ssm.createCodebasePrincipal(makeURI('http://example.org'), {signedPkg: 'whatup'});
-
   checkSandboxOriginAttributes(null, {});
   checkSandboxOriginAttributes('http://example.org', {});
   checkSandboxOriginAttributes('http://example.org', {}, {originAttributes: {}});
   checkSandboxOriginAttributes('http://example.org', {appId: 42}, {originAttributes: {appId: 42}});
   checkSandboxOriginAttributes(['http://example.org'], {});
   checkSandboxOriginAttributes(['http://example.org'], {}, {originAttributes: {}});
   checkSandboxOriginAttributes(['http://example.org'], {appId: 42}, {originAttributes: {appId: 42}});
-  checkSandboxOriginAttributes([exampleOrg_signedPkg, 'http://example.org'], {signedPkg: 'whatever'});
-  checkSandboxOriginAttributes(['http://example.org', exampleOrg_signedPkg], {signedPkg: 'whatever'});
-  checkSandboxOriginAttributes(['http://example.org', exampleOrg_app, exampleOrg_signedPkg], {signedPkg: 'whatever'}, {originAttributes: {signedPkg: 'whatever'}});
-  checkSandboxOriginAttributes(['http://example.org', exampleOrg_signedPkg, exampleOrg_app], {signedPkg: 'whatever'}, {originAttributes: {signedPkg: 'whatever'}});
-  checkSandboxOriginAttributes([exampleOrg_app, exampleOrg_signedPkg, 'http://example.org'], {signedPkg: 'whatever'}, {originAttributes: {signedPkg: 'whatever'}});
-  checkSandboxOriginAttributes([exampleOrg_app, 'http://example.org', exampleOrg_signedPkg], {signedPkg: 'whatever'}, {originAttributes: {signedPkg: 'whatever'}});
-  checkSandboxOriginAttributes([exampleOrg_signedPkg, exampleOrg_app, 'http://example.org'], {signedPkg: 'whatever'}, {originAttributes: {signedPkg: 'whatever'}});
-  checkSandboxOriginAttributes([exampleOrg_signedPkg, 'http://example.org', exampleOrg_app], {signedPkg: 'whatever'}, {originAttributes: {signedPkg: 'whatever'}});
-  checkThrows(() => Cu.Sandbox([exampleOrg_app, exampleOrg_signedPkg]));
-  checkThrows(() => Cu.Sandbox([exampleOrg_signedPkg, exampleOrg_app]));
-  checkThrows(() => Cu.Sandbox(['http://example.org', exampleOrg_app, exampleOrg_signedPkg]));
-  checkThrows(() => Cu.Sandbox(['http://example.org', exampleOrg_signedPkg, exampleOrg_app]));
-  checkThrows(() => Cu.Sandbox([exampleOrg_app, exampleOrg_signedPkg, 'http://example.org']));
-  checkThrows(() => Cu.Sandbox([exampleOrg_app, 'http://example.org', exampleOrg_signedPkg]));
-  checkThrows(() => Cu.Sandbox([exampleOrg_signedPkg, exampleOrg_app, 'http://example.org']));
-  checkThrows(() => Cu.Sandbox([exampleOrg_signedPkg, 'http://example.org', exampleOrg_app]));
 
   // Check that all of the above are cross-origin.
   checkCrossOrigin(exampleOrg_app, exampleOrg);
   checkCrossOrigin(exampleOrg_app, nullPrin_app);
   checkCrossOrigin(exampleOrg_browser, exampleOrg_app);
   checkCrossOrigin(exampleOrg_browser, nullPrin_browser);
   checkCrossOrigin(exampleOrg_appBrowser, exampleOrg_app);
   checkCrossOrigin(exampleOrg_appBrowser, nullPrin_appBrowser);
   checkCrossOrigin(exampleOrg_appBrowser, exampleCom_appBrowser);
   checkCrossOrigin(exampleOrg_addon, exampleOrg);
   checkCrossOrigin(exampleOrg_firstPartyDomain, exampleOrg);
   checkCrossOrigin(exampleOrg_userContext, exampleOrg);
   checkCrossOrigin(exampleOrg_userContextAddon, exampleOrg);
   checkCrossOrigin(exampleOrg_userContext, exampleOrg_userContextAddon);
   checkCrossOrigin(exampleOrg_userContext, exampleOrg_userContextApp);
-  checkCrossOrigin(exampleOrg_signedPkg, exampleOrg);
-  checkCrossOrigin(exampleOrg_signedPkg, exampleOrg_signedPkg_browser);
-  checkCrossOrigin(exampleOrg_signedPkg, exampleOrg_signedPkg_another);
 
   // Check Principal kinds.
   function checkKind(prin, kind) {
     do_check_eq(prin.isNullPrincipal, kind == 'nullPrincipal');
     do_check_eq(prin.isCodebasePrincipal, kind == 'codebasePrincipal');
     do_check_eq(prin.isExpandedPrincipal, kind == 'expandedPrincipal');
     do_check_eq(prin.isSystemPrincipal, kind == 'systemPrincipal');
   }
@@ -250,17 +216,16 @@ function run_test() {
   var uri = "http://example.org";
   var tests = [
     [ "", {} ],
     [ "^appId=5", {appId: 5} ],
     [ "^userContextId=3", {userContextId: 3} ],
     [ "^addonId=fooBar", {addonId: "fooBar"} ],
     [ "^inBrowser=1", {inIsolatedMozBrowser: true} ],
     [ "^firstPartyDomain=example.org", {firstPartyDomain: "example.org"} ],
-    [ "^signedPkg=bazQux", {signedPkg: "bazQux"} ],
     [ "^appId=3&inBrowser=1&userContextId=6",
       {appId: 3, userContextId: 6, inIsolatedMozBrowser: true} ] ];
 
   // check that we can create an origin attributes from an origin properly
   tests.forEach(t => {
     let attrs = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]);
     checkValues(attrs, t[1]);
     do_check_eq(ChromeUtils.originAttributesToSuffix(attrs), t[0]);
--- a/devtools/client/locales/en-US/responsive.properties
+++ b/devtools/client/locales/en-US/responsive.properties
@@ -64,8 +64,18 @@ responsive.remoteOnly=Responsive Design 
 # container tab.
 responsive.noContainerTabs=Responsive Design Mode is currently unavailable in container tabs.
 
 # LOCALIZATION NOTE (responsive.noThrottling): UI option in a menu to configure
 # network throttling.  This option is the default and disables throttling so you
 # just have normal network conditions.  There is not very much room in the UI
 # so a short string would be best if possible.
 responsive.noThrottling=No throttling
+
+# LOCALIZATION NOTE (responsive.devicePixelRatio): tooltip for the
+# DevicePixelRatio (DPR) dropdown when is enabled.
+responsive.devicePixelRatio=Device Pixel Ratio
+
+# LOCALIZATION NOTE (responsive.autoDPR): tooltip for the DevicePixelRatio
+# (DPR) dropdown when is disabled because a device is selected.
+# The argument (%1$S) is the selected device (e.g. iPhone 6) that set
+# automatically the DPR value.
+responsive.autoDPR=DPR automatically set by %1$S
--- a/devtools/client/netmonitor/har/test/browser_net_har_copy_all_as_har.js
+++ b/devtools/client/netmonitor/har/test/browser_net_har_copy_all_as_har.js
@@ -15,17 +15,17 @@ add_task(function* () {
   let { RequestsMenu } = NetMonitorView;
 
   RequestsMenu.lazyUpdate = false;
 
   let wait = waitForNetworkEvents(monitor, 1);
   tab.linkedBrowser.reload();
   yield wait;
 
-  yield RequestsMenu.copyAllAsHar();
+  yield RequestsMenu.contextMenu.copyAllAsHar();
 
   let jsonString = SpecialPowers.getClipboardData("text/unicode");
   let har = JSON.parse(jsonString);
 
   // Check out HAR log
   isnot(har.log, null, "The HAR log must exist");
   is(har.log.creator.name, "Firefox", "The creator field must be set");
   is(har.log.browser.name, "Firefox", "The browser field must be set");
--- a/devtools/client/netmonitor/har/test/browser_net_har_post_data.js
+++ b/devtools/client/netmonitor/har/test/browser_net_har_post_data.js
@@ -20,17 +20,17 @@ add_task(function* () {
   // Execute one POST request on the page and wait till its done.
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, {}, function* () {
     content.wrappedJSObject.executeTest();
   });
   yield wait;
 
   // Copy HAR into the clipboard (asynchronous).
-  let jsonString = yield RequestsMenu.copyAllAsHar();
+  let jsonString = yield RequestsMenu.contextMenu.copyAllAsHar();
   let har = JSON.parse(jsonString);
 
   // Check out the HAR log.
   isnot(har.log, null, "The HAR log must exist");
   is(har.log.pages.length, 1, "There must be one page");
   is(har.log.entries.length, 1, "There must be one request");
 
   let entry = har.log.entries[0];
--- a/devtools/client/netmonitor/har/test/browser_net_har_throttle_upload.js
+++ b/devtools/client/netmonitor/har/test/browser_net_har_throttle_upload.js
@@ -46,17 +46,17 @@ function* throttleUploadTest(actuallyThr
   // Execute one POST request on the page and wait till its done.
   let wait = waitForNetworkEvents(monitor, 0, 1);
   yield ContentTask.spawn(tab.linkedBrowser, { size }, function* (args) {
     content.wrappedJSObject.executeTest2(args.size);
   });
   yield wait;
 
   // Copy HAR into the clipboard (asynchronous).
-  let jsonString = yield RequestsMenu.copyAllAsHar();
+  let jsonString = yield RequestsMenu.contextMenu.copyAllAsHar();
   let har = JSON.parse(jsonString);
 
   // Check out the HAR log.
   isnot(har.log, null, "The HAR log must exist");
   is(har.log.pages.length, 1, "There must be one page");
   is(har.log.entries.length, 1, "There must be one request");
 
   let entry = har.log.entries[0];
--- a/devtools/client/netmonitor/moz.build
+++ b/devtools/client/netmonitor/moz.build
@@ -15,16 +15,17 @@ DevToolsModules(
     'constants.js',
     'custom-request-view.js',
     'events.js',
     'filter-predicates.js',
     'l10n.js',
     'panel.js',
     'performance-statistics-view.js',
     'prefs.js',
+    'request-list-context-menu.js',
     'request-utils.js',
     'requests-menu-view.js',
     'sort-predicates.js',
     'store.js',
     'toolbar-view.js',
 )
 
 BROWSER_CHROME_MANIFESTS += ['test/browser.ini']
--- a/devtools/client/netmonitor/performance-statistics-view.js
+++ b/devtools/client/netmonitor/performance-statistics-view.js
@@ -139,17 +139,17 @@ PerformanceStatisticsView.prototype = {
       data: data,
       strings: strings,
       totals: totals,
       sorted: sorted
     });
 
     chart.on("click", (_, item) => {
       // Reset FilterButtons and enable one filter exclusively
-      this.store.dispatch(Actions.enableFilterOnly(item.label));
+      this.store.dispatch(Actions.enableFilterTypeOnly(item.label));
       NetMonitorView.showNetworkInspectorView();
     });
 
     container.appendChild(chart.node);
   },
 
   /**
    * Sanitizes the data source used for creating charts, to follow the
new file mode 100644
--- /dev/null
+++ b/devtools/client/netmonitor/request-list-context-menu.js
@@ -0,0 +1,357 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* globals NetMonitorController, NetMonitorView, gNetwork */
+
+"use strict";
+
+const Services = require("Services");
+const { Task } = require("devtools/shared/task");
+const { Curl } = require("devtools/client/shared/curl");
+const { gDevTools } = require("devtools/client/framework/devtools");
+const Menu = require("devtools/client/framework/menu");
+const MenuItem = require("devtools/client/framework/menu-item");
+const { L10N } = require("./l10n");
+const { formDataURI, getFormDataSections } = require("./request-utils");
+
+loader.lazyRequireGetter(this, "HarExporter",
+  "devtools/client/netmonitor/har/har-exporter", true);
+
+loader.lazyServiceGetter(this, "clipboardHelper",
+  "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
+
+loader.lazyRequireGetter(this, "NetworkHelper",
+  "devtools/shared/webconsole/network-helper");
+
+function RequestListContextMenu() {}
+
+RequestListContextMenu.prototype = {
+  get selectedItem() {
+    return NetMonitorView.RequestsMenu.selectedItem;
+  },
+
+  get items() {
+    return NetMonitorView.RequestsMenu.items;
+  },
+
+  /**
+   * Handle the context menu opening. Hide items if no request is selected.
+   * Since visible attribute only accept boolean value but the method call may
+   * return undefined, we use !! to force convert any object to boolean
+   */
+  open({ screenX = 0, screenY = 0 } = {}) {
+    let selectedItem = this.selectedItem;
+
+    let menu = new Menu();
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-url",
+      label: L10N.getStr("netmonitor.context.copyUrl"),
+      accesskey: L10N.getStr("netmonitor.context.copyUrl.accesskey"),
+      visible: !!selectedItem,
+      click: () => this.copyUrl(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-url-params",
+      label: L10N.getStr("netmonitor.context.copyUrlParams"),
+      accesskey: L10N.getStr("netmonitor.context.copyUrlParams.accesskey"),
+      visible: !!(selectedItem &&
+               NetworkHelper.nsIURL(selectedItem.attachment.url).query),
+      click: () => this.copyUrlParams(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-post-data",
+      label: L10N.getStr("netmonitor.context.copyPostData"),
+      accesskey: L10N.getStr("netmonitor.context.copyPostData.accesskey"),
+      visible: !!(selectedItem && selectedItem.attachment.requestPostData),
+      click: () => this.copyPostData(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-as-curl",
+      label: L10N.getStr("netmonitor.context.copyAsCurl"),
+      accesskey: L10N.getStr("netmonitor.context.copyAsCurl.accesskey"),
+      visible: !!(selectedItem && selectedItem.attachment),
+      click: () => this.copyAsCurl(),
+    }));
+
+    menu.append(new MenuItem({
+      type: "separator",
+      visible: !!selectedItem,
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-request-headers",
+      label: L10N.getStr("netmonitor.context.copyRequestHeaders"),
+      accesskey: L10N.getStr("netmonitor.context.copyRequestHeaders.accesskey"),
+      visible: !!(selectedItem && selectedItem.attachment.requestHeaders),
+      click: () => this.copyRequestHeaders(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "response-menu-context-copy-response-headers",
+      label: L10N.getStr("netmonitor.context.copyResponseHeaders"),
+      accesskey: L10N.getStr("netmonitor.context.copyResponseHeaders.accesskey"),
+      visible: !!(selectedItem && selectedItem.attachment.responseHeaders),
+      click: () => this.copyResponseHeaders(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-response",
+      label: L10N.getStr("netmonitor.context.copyResponse"),
+      accesskey: L10N.getStr("netmonitor.context.copyResponse.accesskey"),
+      visible: !!(selectedItem &&
+               selectedItem.attachment.responseContent &&
+               selectedItem.attachment.responseContent.content.text &&
+               selectedItem.attachment.responseContent.content.text.length !== 0),
+      click: () => this.copyResponse(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-image-as-data-uri",
+      label: L10N.getStr("netmonitor.context.copyImageAsDataUri"),
+      accesskey: L10N.getStr("netmonitor.context.copyImageAsDataUri.accesskey"),
+      visible: !!(selectedItem &&
+               selectedItem.attachment.responseContent &&
+               selectedItem.attachment.responseContent.content
+                 .mimeType.includes("image/")),
+      click: () => this.copyImageAsDataUri(),
+    }));
+
+    menu.append(new MenuItem({
+      type: "separator",
+      visible: !!selectedItem,
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-copy-all-as-har",
+      label: L10N.getStr("netmonitor.context.copyAllAsHar"),
+      accesskey: L10N.getStr("netmonitor.context.copyAllAsHar.accesskey"),
+      visible: !!this.items.length,
+      click: () => this.copyAllAsHar(),
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-save-all-as-har",
+      label: L10N.getStr("netmonitor.context.saveAllAsHar"),
+      accesskey: L10N.getStr("netmonitor.context.saveAllAsHar.accesskey"),
+      visible: !!this.items.length,
+      click: () => this.saveAllAsHar(),
+    }));
+
+    menu.append(new MenuItem({
+      type: "separator",
+      visible: !!selectedItem,
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-resend",
+      label: L10N.getStr("netmonitor.context.editAndResend"),
+      accesskey: L10N.getStr("netmonitor.context.editAndResend.accesskey"),
+      visible: !!(NetMonitorController.supportsCustomRequest &&
+               selectedItem &&
+               !selectedItem.attachment.isCustom),
+      click: () => NetMonitorView.RequestsMenu.cloneSelectedRequest(),
+    }));
+
+    menu.append(new MenuItem({
+      type: "separator",
+      visible: !!selectedItem,
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-newtab",
+      label: L10N.getStr("netmonitor.context.newTab"),
+      accesskey: L10N.getStr("netmonitor.context.newTab.accesskey"),
+      visible: !!selectedItem,
+      click: () => this.openRequestInTab()
+    }));
+
+    menu.append(new MenuItem({
+      id: "request-menu-context-perf",
+      label: L10N.getStr("netmonitor.context.perfTools"),
+      accesskey: L10N.getStr("netmonitor.context.perfTools.accesskey"),
+      visible: !!NetMonitorController.supportsPerfStats,
+      click: () => NetMonitorView.toggleFrontendMode()
+    }));
+
+    menu.popup(screenX, screenY, NetMonitorController._toolbox);
+    return menu;
+  },
+
+  /**
+   * Opens selected item in a new tab.
+   */
+  openRequestInTab() {
+    let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
+    let { url } = this.selectedItem.attachment;
+    win.openUILinkIn(url, "tab", { relatedToCurrent: true });
+  },
+
+  /**
+   * Copy the request url from the currently selected item.
+   */
+  copyUrl() {
+    clipboardHelper.copyString(this.selectedItem.attachment.url);
+  },
+
+  /**
+   * Copy the request url query string parameters from the currently
+   * selected item.
+   */
+  copyUrlParams() {
+    let { url } = this.selectedItem.attachment;
+    let params = NetworkHelper.nsIURL(url).query.split("&");
+    let string = params.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
+    clipboardHelper.copyString(string);
+  },
+
+  /**
+   * Copy the request form data parameters (or raw payload) from
+   * the currently selected item.
+   */
+  copyPostData: Task.async(function* () {
+    let selected = this.selectedItem.attachment;
+
+    // Try to extract any form data parameters.
+    let formDataSections = yield getFormDataSections(
+      selected.requestHeaders,
+      selected.requestHeadersFromUploadStream,
+      selected.requestPostData,
+      gNetwork.getString.bind(gNetwork));
+
+    let params = [];
+    formDataSections.forEach(section => {
+      let paramsArray = NetworkHelper.parseQueryString(section);
+      if (paramsArray) {
+        params = [...params, ...paramsArray];
+      }
+    });
+
+    let string = params
+      .map(param => param.name + (param.value ? "=" + param.value : ""))
+      .join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
+
+    // Fall back to raw payload.
+    if (!string) {
+      let postData = selected.requestPostData.postData.text;
+      string = yield gNetwork.getString(postData);
+      if (Services.appinfo.OS !== "WINNT") {
+        string = string.replace(/\r/g, "");
+      }
+    }
+
+    clipboardHelper.copyString(string);
+  }),
+
+  /**
+   * Copy a cURL command from the currently selected item.
+   */
+  copyAsCurl: Task.async(function* () {
+    let selected = this.selectedItem.attachment;
+
+    // Create a sanitized object for the Curl command generator.
+    let data = {
+      url: selected.url,
+      method: selected.method,
+      headers: [],
+      httpVersion: selected.httpVersion,
+      postDataText: null
+    };
+
+    // Fetch header values.
+    for (let { name, value } of selected.requestHeaders.headers) {
+      let text = yield gNetwork.getString(value);
+      data.headers.push({ name: name, value: text });
+    }
+
+    // Fetch the request payload.
+    if (selected.requestPostData) {
+      let postData = selected.requestPostData.postData.text;
+      data.postDataText = yield gNetwork.getString(postData);
+    }
+
+    clipboardHelper.copyString(Curl.generateCommand(data));
+  }),
+
+  /**
+   * Copy the raw request headers from the currently selected item.
+   */
+  copyRequestHeaders() {
+    let selected = this.selectedItem.attachment;
+    let rawHeaders = selected.requestHeaders.rawHeaders.trim();
+    if (Services.appinfo.OS !== "WINNT") {
+      rawHeaders = rawHeaders.replace(/\r/g, "");
+    }
+    clipboardHelper.copyString(rawHeaders);
+  },
+
+  /**
+   * Copy the raw response headers from the currently selected item.
+   */
+  copyResponseHeaders() {
+    let selected = this.selectedItem.attachment;
+    let rawHeaders = selected.responseHeaders.rawHeaders.trim();
+    if (Services.appinfo.OS !== "WINNT") {
+      rawHeaders = rawHeaders.replace(/\r/g, "");
+    }
+    clipboardHelper.copyString(rawHeaders);
+  },
+
+  /**
+   * Copy image as data uri.
+   */
+  copyImageAsDataUri() {
+    let selected = this.selectedItem.attachment;
+    let { mimeType, text, encoding } = selected.responseContent.content;
+
+    gNetwork.getString(text).then(string => {
+      let data = formDataURI(mimeType, encoding, string);
+      clipboardHelper.copyString(data);
+    });
+  },
+
+  /**
+   * Copy response data as a string.
+   */
+  copyResponse() {
+    let selected = this.selectedItem.attachment;
+    let text = selected.responseContent.content.text;
+
+    gNetwork.getString(text).then(string => {
+      clipboardHelper.copyString(string);
+    });
+  },
+
+  /**
+   * Copy HAR from the network panel content to the clipboard.
+   */
+  copyAllAsHar() {
+    let options = this.getDefaultHarOptions();
+    return HarExporter.copy(options);
+  },
+
+  /**
+   * Save HAR from the network panel content to a file.
+   */
+  saveAllAsHar() {
+    let options = this.getDefaultHarOptions();
+    return HarExporter.save(options);
+  },
+
+  getDefaultHarOptions() {
+    let form = NetMonitorController._target.form;
+    let title = form.title || form.url;
+
+    return {
+      getString: gNetwork.getString.bind(gNetwork),
+      view: NetMonitorView.RequestsMenu,
+      items: NetMonitorView.RequestsMenu.items,
+      title: title
+    };
+  }
+};
+
+module.exports = RequestListContextMenu;
--- a/devtools/client/netmonitor/requests-menu-view.js
+++ b/devtools/client/netmonitor/requests-menu-view.js
@@ -1,47 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
 /* globals document, window, dumpn, $, gNetwork, EVENTS, Prefs,
            NetMonitorController, NetMonitorView */
+
 "use strict";
+
 /* eslint-disable mozilla/reject-some-requires */
 const { Cu } = require("chrome");
-const Services = require("Services");
 const {Task} = require("devtools/shared/task");
 const {DeferredTask} = Cu.import("resource://gre/modules/DeferredTask.jsm", {});
 /* eslint-disable mozilla/reject-some-requires */
 const {SideMenuWidget} = require("resource://devtools/client/shared/widgets/SideMenuWidget.jsm");
 const {HTMLTooltip} = require("devtools/client/shared/widgets/tooltip/HTMLTooltip");
 const {setImageTooltip, getImageDimensions} =
   require("devtools/client/shared/widgets/tooltip/ImageTooltipHelper");
 const {Heritage, WidgetMethods, setNamedTimeout} =
   require("devtools/client/shared/widgets/view-helpers");
-const {gDevTools} = require("devtools/client/framework/devtools");
-const Menu = require("devtools/client/framework/menu");
-const MenuItem = require("devtools/client/framework/menu-item");
-const {Curl, CurlUtils} = require("devtools/client/shared/curl");
+const {CurlUtils} = require("devtools/client/shared/curl");
 const {PluralForm} = require("devtools/shared/plural-form");
 const {Filters, isFreetextMatch} = require("./filter-predicates");
 const {Sorters} = require("./sort-predicates");
 const {L10N, WEBCONSOLE_L10N} = require("./l10n");
-const {getFormDataSections,
-       formDataURI,
+const {formDataURI,
        writeHeaderText,
        getKeyWithEvent,
        getAbbreviatedMimeType,
        getUriNameWithQuery,
        getUriHostPort,
        getUriHost,
        loadCauseString} = require("./request-utils");
 const Actions = require("./actions/index");
-
-loader.lazyServiceGetter(this, "clipboardHelper",
-  "@mozilla.org/widget/clipboardhelper;1", "nsIClipboardHelper");
-
-loader.lazyRequireGetter(this, "HarExporter",
-  "devtools/client/netmonitor/har/har-exporter", true);
+const RequestListContextMenu = require("./request-list-context-menu");
 
 loader.lazyRequireGetter(this, "NetworkHelper",
   "devtools/shared/webconsole/network-helper");
 
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 const EPSILON = 0.001;
 // ms
 const RESIZE_REFRESH_RATE = 50;
@@ -123,16 +119,18 @@ RequestsMenuView.prototype = Heritage.ex
   /**
    * Initialization function, called when the network monitor is started.
    */
   initialize: function (store) {
     dumpn("Initializing the RequestsMenuView");
 
     this.store = store;
 
+    this.contextMenu = new RequestListContextMenu();
+
     let widgetParentEl = $("#requests-menu-contents");
     this.widget = new SideMenuWidget(widgetParentEl);
     this._splitter = $("#network-inspector-view-splitter");
     this._summary = $("#requests-menu-network-summary-button");
     this._summary.setAttribute("label", L10N.getStr("networkMenu.empty"));
 
     // Create a tooltip for the newly appended network request item.
     this.tooltip = new HTMLTooltip(NetMonitorController._toolbox.doc, { type: "arrow" });
@@ -149,23 +147,16 @@ RequestsMenuView.prototype = Heritage.ex
     this.widget.addEventListener("select", this._onSelect, false);
     this.widget.addEventListener("swap", this._onSwap, false);
     this._splitter.addEventListener("mousemove", this._onResize, false);
     window.addEventListener("resize", this._onResize, false);
 
     this.requestsMenuSortEvent = getKeyWithEvent(this.sortBy.bind(this));
     this.requestsMenuSortKeyboardEvent = getKeyWithEvent(this.sortBy.bind(this), true);
     this._onContextMenu = this._onContextMenu.bind(this);
-    this._onContextNewTabCommand = this.openRequestInTab.bind(this);
-    this._onContextCopyUrlCommand = this.copyUrl.bind(this);
-    this._onContextCopyImageAsDataUriCommand =
-      this.copyImageAsDataUri.bind(this);
-    this._onContextCopyResponseCommand = this.copyResponse.bind(this);
-    this._onContextResendCommand = this.cloneSelectedRequest.bind(this);
-    this._onContextToggleRawHeadersCommand = this.toggleRawHeaders.bind(this);
     this._onContextPerfCommand = () => NetMonitorView.toggleFrontendMode();
     this._onReloadCommand = () => NetMonitorView.reloadPage();
     this._flushRequestsTask = new DeferredTask(this._flushRequests,
       REQUESTS_REFRESH_RATE);
 
     this.sendCustomRequestEvent = this.sendCustomRequest.bind(this);
     this.closeCustomRequestEvent = this.closeCustomRequest.bind(this);
     this.cloneSelectedRequestEvent = this.cloneSelectedRequest.bind(this);
@@ -344,191 +335,16 @@ RequestsMenuView.prototype = Heritage.ex
       return void this._flushRequests();
     }
 
     this._flushRequestsTask.arm();
     return undefined;
   },
 
   /**
-   * Opens selected item in a new tab.
-   */
-  openRequestInTab: function () {
-    let win = Services.wm.getMostRecentWindow(gDevTools.chromeWindowType);
-    let selected = this.selectedItem.attachment;
-    win.openUILinkIn(selected.url, "tab", { relatedToCurrent: true });
-  },
-
-  /**
-   * Copy the request url from the currently selected item.
-   */
-  copyUrl: function () {
-    let selected = this.selectedItem.attachment;
-    clipboardHelper.copyString(selected.url);
-  },
-
-  /**
-   * Copy the request url query string parameters from the currently
-   * selected item.
-   */
-  copyUrlParams: function () {
-    let selected = this.selectedItem.attachment;
-    let params = NetworkHelper.nsIURL(selected.url).query.split("&");
-    let string = params.join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
-    clipboardHelper.copyString(string);
-  },
-
-  /**
-   * Copy the request form data parameters (or raw payload) from
-   * the currently selected item.
-   */
-  copyPostData: Task.async(function* () {
-    let selected = this.selectedItem.attachment;
-
-    // Try to extract any form data parameters.
-    let formDataSections = yield getFormDataSections(
-      selected.requestHeaders,
-      selected.requestHeadersFromUploadStream,
-      selected.requestPostData,
-      gNetwork.getString.bind(gNetwork));
-
-    let params = [];
-    formDataSections.forEach(section => {
-      let paramsArray = NetworkHelper.parseQueryString(section);
-      if (paramsArray) {
-        params = [...params, ...paramsArray];
-      }
-    });
-
-    let string = params
-      .map(param => param.name + (param.value ? "=" + param.value : ""))
-      .join(Services.appinfo.OS === "WINNT" ? "\r\n" : "\n");
-
-    // Fall back to raw payload.
-    if (!string) {
-      let postData = selected.requestPostData.postData.text;
-      string = yield gNetwork.getString(postData);
-      if (Services.appinfo.OS !== "WINNT") {
-        string = string.replace(/\r/g, "");
-      }
-    }
-
-    clipboardHelper.copyString(string);
-  }),
-
-  /**
-   * Copy a cURL command from the currently selected item.
-   */
-  copyAsCurl: function () {
-    let selected = this.selectedItem.attachment;
-
-    Task.spawn(function* () {
-      // Create a sanitized object for the Curl command generator.
-      let data = {
-        url: selected.url,
-        method: selected.method,
-        headers: [],
-        httpVersion: selected.httpVersion,
-        postDataText: null
-      };
-
-      // Fetch header values.
-      for (let { name, value } of selected.requestHeaders.headers) {
-        let text = yield gNetwork.getString(value);
-        data.headers.push({ name: name, value: text });
-      }
-
-      // Fetch the request payload.
-      if (selected.requestPostData) {
-        let postData = selected.requestPostData.postData.text;
-        data.postDataText = yield gNetwork.getString(postData);
-      }
-
-      clipboardHelper.copyString(Curl.generateCommand(data));
-    });
-  },
-
-  /**
-   * Copy HAR from the network panel content to the clipboard.
-   */
-  copyAllAsHar: function () {
-    let options = this.getDefaultHarOptions();
-    return HarExporter.copy(options);
-  },
-
-  /**
-   * Save HAR from the network panel content to a file.
-   */
-  saveAllAsHar: function () {
-    let options = this.getDefaultHarOptions();
-    return HarExporter.save(options);
-  },
-
-  getDefaultHarOptions: function () {
-    let form = NetMonitorController._target.form;
-    let title = form.title || form.url;
-
-    return {
-      getString: gNetwork.getString.bind(gNetwork),
-      view: this,
-      items: NetMonitorView.RequestsMenu.items,
-      title: title
-    };
-  },
-
-  /**
-   * Copy the raw request headers from the currently selected item.
-   */
-  copyRequestHeaders: function () {
-    let selected = this.selectedItem.attachment;
-    let rawHeaders = selected.requestHeaders.rawHeaders.trim();
-    if (Services.appinfo.OS !== "WINNT") {
-      rawHeaders = rawHeaders.replace(/\r/g, "");
-    }
-    clipboardHelper.copyString(rawHeaders);
-  },
-
-  /**
-   * Copy the raw response headers from the currently selected item.
-   */
-  copyResponseHeaders: function () {
-    let selected = this.selectedItem.attachment;
-    let rawHeaders = selected.responseHeaders.rawHeaders.trim();
-    if (Services.appinfo.OS !== "WINNT") {
-      rawHeaders = rawHeaders.replace(/\r/g, "");
-    }
-    clipboardHelper.copyString(rawHeaders);
-  },
-
-  /**
-   * Copy image as data uri.
-   */
-  copyImageAsDataUri: function () {
-    let selected = this.selectedItem.attachment;
-    let { mimeType, text, encoding } = selected.responseContent.content;
-
-    gNetwork.getString(text).then(string => {
-      let data = formDataURI(mimeType, encoding, string);
-      clipboardHelper.copyString(data);
-    });
-  },
-
-  /**
-   * Copy response data as a string.
-   */
-  copyResponse: function () {
-    let selected = this.selectedItem.attachment;
-    let text = selected.responseContent.content.text;
-
-    gNetwork.getString(text).then(string => {
-      clipboardHelper.copyString(string);
-    });
-  },
-
-  /**
    * Create a new custom request form populated with the data from
    * the currently selected request.
    */
   cloneSelectedRequest: function () {
     let selected = this.selectedItem.attachment;
 
     // Create the element node for the network request item.
     let menuView = this._createMenuView(selected.method, selected.url,
@@ -1708,167 +1524,17 @@ RequestsMenuView.prototype = Heritage.ex
     this.tooltip.hide();
   },
 
   /**
    * Open context menu
    */
   _onContextMenu: function (e) {
     e.preventDefault();
-    this._openMenu({
-      screenX: e.screenX,
-      screenY: e.screenY,
-      target: e.target,
-    });
-  },
-
-  /**
-   * Handle the context menu opening. Hide items if no request is selected.
-   * Since visible attribute only accept boolean value but the method call may
-   * return undefined, we use !! to force convert any object to boolean
-   */
-  _openMenu: function ({ target, screenX = 0, screenY = 0 } = { }) {
-    let selectedItem = this.selectedItem;
-
-    let menu = new Menu();
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-url",
-      label: L10N.getStr("netmonitor.context.copyUrl"),
-      accesskey: L10N.getStr("netmonitor.context.copyUrl.accesskey"),
-      visible: !!selectedItem,
-      click: () => this._onContextCopyUrlCommand(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-url-params",
-      label: L10N.getStr("netmonitor.context.copyUrlParams"),
-      accesskey: L10N.getStr("netmonitor.context.copyUrlParams.accesskey"),
-      visible: !!(selectedItem &&
-               NetworkHelper.nsIURL(selectedItem.attachment.url).query),
-      click: () => this.copyUrlParams(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-post-data",
-      label: L10N.getStr("netmonitor.context.copyPostData"),
-      accesskey: L10N.getStr("netmonitor.context.copyPostData.accesskey"),
-      visible: !!(selectedItem && selectedItem.attachment.requestPostData),
-      click: () => this.copyPostData(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-as-curl",
-      label: L10N.getStr("netmonitor.context.copyAsCurl"),
-      accesskey: L10N.getStr("netmonitor.context.copyAsCurl.accesskey"),
-      visible: !!(selectedItem && selectedItem.attachment),
-      click: () => this.copyAsCurl(),
-    }));
-
-    menu.append(new MenuItem({
-      type: "separator",
-      visible: !!selectedItem,
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-request-headers",
-      label: L10N.getStr("netmonitor.context.copyRequestHeaders"),
-      accesskey: L10N.getStr("netmonitor.context.copyRequestHeaders.accesskey"),
-      visible: !!(selectedItem && selectedItem.attachment.requestHeaders),
-      click: () => this.copyRequestHeaders(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "response-menu-context-copy-response-headers",
-      label: L10N.getStr("netmonitor.context.copyResponseHeaders"),
-      accesskey: L10N.getStr("netmonitor.context.copyResponseHeaders.accesskey"),
-      visible: !!(selectedItem && selectedItem.attachment.responseHeaders),
-      click: () => this.copyResponseHeaders(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-response",
-      label: L10N.getStr("netmonitor.context.copyResponse"),
-      accesskey: L10N.getStr("netmonitor.context.copyResponse.accesskey"),
-      visible: !!(selectedItem &&
-               selectedItem.attachment.responseContent &&
-               selectedItem.attachment.responseContent.content.text &&
-               selectedItem.attachment.responseContent.content.text.length !== 0),
-      click: () => this._onContextCopyResponseCommand(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-image-as-data-uri",
-      label: L10N.getStr("netmonitor.context.copyImageAsDataUri"),
-      accesskey: L10N.getStr("netmonitor.context.copyImageAsDataUri.accesskey"),
-      visible: !!(selectedItem &&
-               selectedItem.attachment.responseContent &&
-               selectedItem.attachment.responseContent.content
-                 .mimeType.includes("image/")),
-      click: () => this._onContextCopyImageAsDataUriCommand(),
-    }));
-
-    menu.append(new MenuItem({
-      type: "separator",
-      visible: !!selectedItem,
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-copy-all-as-har",
-      label: L10N.getStr("netmonitor.context.copyAllAsHar"),
-      accesskey: L10N.getStr("netmonitor.context.copyAllAsHar.accesskey"),
-      visible: !!this.items.length,
-      click: () => this.copyAllAsHar(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-save-all-as-har",
-      label: L10N.getStr("netmonitor.context.saveAllAsHar"),
-      accesskey: L10N.getStr("netmonitor.context.saveAllAsHar.accesskey"),
-      visible: !!this.items.length,
-      click: () => this.saveAllAsHar(),
-    }));
-
-    menu.append(new MenuItem({
-      type: "separator",
-      visible: !!selectedItem,
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-resend",
-      label: L10N.getStr("netmonitor.context.editAndResend"),
-      accesskey: L10N.getStr("netmonitor.context.editAndResend.accesskey"),
-      visible: !!(NetMonitorController.supportsCustomRequest &&
-               selectedItem &&
-               !selectedItem.attachment.isCustom),
-      click: () => this._onContextResendCommand(),
-    }));
-
-    menu.append(new MenuItem({
-      type: "separator",
-      visible: !!selectedItem,
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-newtab",
-      label: L10N.getStr("netmonitor.context.newTab"),
-      accesskey: L10N.getStr("netmonitor.context.newTab.accesskey"),
-      visible: !!selectedItem,
-      click: () => this._onContextNewTabCommand(),
-    }));
-
-    menu.append(new MenuItem({
-      id: "request-menu-context-perf",
-      label: L10N.getStr("netmonitor.context.perfTools"),
-      accesskey: L10N.getStr("netmonitor.context.perfTools.accesskey"),
-      visible: !!NetMonitorController.supportsPerfStats,
-      click: () => this._onContextPerfCommand(),
-    }));
-
-    menu.popup(screenX, screenY, NetMonitorController._toolbox);
-    return menu;
+    this.contextMenu.open(e);
   },
 
   /**
    * Checks if the specified unix time is the first one to be known of,
    * and saves it if so.
    *
    * @param number unixTime
    *        The milliseconds to check and save.
--- a/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_as_curl.js
@@ -50,17 +50,17 @@ add_task(function* () {
     content.wrappedJSObject.performRequest(url);
   });
   yield wait;
 
   let requestItem = RequestsMenu.getItemAtIndex(0);
   RequestsMenu.selectedItem = requestItem;
 
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.copyAsCurl();
+    RequestsMenu.contextMenu.copyAsCurl();
   }, function validate(result) {
     if (typeof result !== "string") {
       return false;
     }
 
     // Different setups may produce the same command, but with the
     // parameters in a different order in the commandline (which is fine).
     // Here we confirm that the commands are the same even in that case.
--- a/devtools/client/netmonitor/test/browser_net_copy_headers.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_headers.js
@@ -34,17 +34,17 @@ add_task(function* () {
     "Accept-Encoding: gzip, deflate",
     "Connection: keep-alive",
     "Upgrade-Insecure-Requests: 1",
     "Pragma: no-cache",
     "Cache-Control: no-cache"
   ].join("\n");
 
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.copyRequestHeaders();
+    RequestsMenu.contextMenu.copyRequestHeaders();
   }, function validate(result) {
     // Sometimes, a "Cookie" header is left over from other tests. Remove it:
     result = String(result).replace(/Cookie: [^\n]+\n/, "");
     return result === EXPECTED_REQUEST_HEADERS;
   });
   info("Clipboard contains the currently selected item's request headers.");
 
   const EXPECTED_RESPONSE_HEADERS = [
@@ -53,17 +53,17 @@ add_task(function* () {
     "Content-Type: text/html",
     "Content-Length: 465",
     "Connection: close",
     "Server: httpd.js",
     "Date: Sun, 3 May 2015 11:11:11 GMT"
   ].join("\n");
 
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.copyResponseHeaders();
+    RequestsMenu.contextMenu.copyResponseHeaders();
   }, function validate(result) {
     // Fake the "Last-Modified" and "Date" headers because they will vary:
     result = String(result)
       .replace(/Last-Modified: [^\n]+ GMT/, "Last-Modified: Sun, 3 May 2015 11:11:11 GMT")
       .replace(/Date: [^\n]+ GMT/, "Date: Sun, 3 May 2015 11:11:11 GMT");
     return result === EXPECTED_RESPONSE_HEADERS;
   });
   info("Clipboard contains the currently selected item's response headers.");
--- a/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_image_as_data_uri.js
@@ -21,15 +21,15 @@ add_task(function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   let requestItem = RequestsMenu.getItemAtIndex(5);
   RequestsMenu.selectedItem = requestItem;
 
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.copyImageAsDataUri();
+    RequestsMenu.contextMenu.copyImageAsDataUri();
   }, TEST_IMAGE_DATA_URI);
 
   ok(true, "Clipboard contains the currently selected image as data uri.");
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_params.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_params.js
@@ -70,29 +70,29 @@ add_task(function* () {
       item.id === "request-menu-context-copy-url-params");
     is(copyUrlParamsNode.visible, !hidden,
       "The \"Copy URL Parameters\" context menu item should" + (hidden ? " " : " not ") +
         "be hidden.");
   }
 
   function* testCopyUrlParams(queryString) {
     yield waitForClipboardPromise(function setup() {
-      RequestsMenu.copyUrlParams();
+      RequestsMenu.contextMenu.copyUrlParams();
     }, queryString);
     ok(true, "The url query string copied from the selected item is correct.");
   }
 
   function testCopyPostDataHidden(hidden) {
     let allMenuItems = openContextMenuAndGetAllItems(NetMonitorView);
     let copyPostDataNode = allMenuItems.find(item =>
       item.id === "request-menu-context-copy-post-data");
     is(copyPostDataNode.visible, !hidden,
       "The \"Copy POST Data\" context menu item should" + (hidden ? " " : " not ") +
         "be hidden.");
   }
 
   function* testCopyPostData(postData) {
     yield waitForClipboardPromise(function setup() {
-      RequestsMenu.copyPostData();
+      RequestsMenu.contextMenu.copyPostData();
     }, postData);
     ok(true, "The post data string copied from the selected item is correct.");
   }
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_response.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_response.js
@@ -23,13 +23,13 @@ add_task(function* () {
     content.wrappedJSObject.performRequests();
   });
   yield wait;
 
   let requestItem = RequestsMenu.getItemAtIndex(3);
   RequestsMenu.selectedItem = requestItem;
 
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.copyResponse();
+    RequestsMenu.contextMenu.copyResponse();
   }, EXPECTED_RESULT);
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_svg_image_as_data_uri.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_svg_image_as_data_uri.js
@@ -23,15 +23,15 @@ add_task(function* () {
     content.wrappedJSObject.performRequest(url);
   });
   yield wait;
 
   let requestItem = RequestsMenu.getItemAtIndex(0);
   RequestsMenu.selectedItem = requestItem;
 
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.copyImageAsDataUri();
+    RequestsMenu.contextMenu.copyImageAsDataUri();
   }, function check(text) {
     return text.startsWith("data:") && !/undefined/.test(text);
   });
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_copy_url.js
+++ b/devtools/client/netmonitor/test/browser_net_copy_url.js
@@ -19,13 +19,13 @@ add_task(function* () {
     content.wrappedJSObject.performRequests(1);
   });
   yield wait;
 
   let requestItem = RequestsMenu.getItemAtIndex(0);
   RequestsMenu.selectedItem = requestItem;
 
   yield waitForClipboardPromise(function setup() {
-    RequestsMenu.copyUrl();
+    RequestsMenu.contextMenu.copyUrl();
   }, requestItem.attachment.url);
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
+++ b/devtools/client/netmonitor/test/browser_net_open_request_in_tab.js
@@ -21,17 +21,17 @@ add_task(function* () {
     content.wrappedJSObject.performRequests(1);
   });
   yield wait;
 
   let requestItem = RequestsMenu.getItemAtIndex(0);
   RequestsMenu.selectedItem = requestItem;
 
   let onTabOpen = once(gBrowser.tabContainer, "TabOpen", false);
-  RequestsMenu.openRequestInTab();
+  RequestsMenu.contextMenu.openRequestInTab();
   yield onTabOpen;
 
   ok(true, "A new tab has been opened");
 
   yield teardown(monitor);
 
   gBrowser.removeCurrentTab();
 });
--- a/devtools/client/netmonitor/test/browser_net_statistics-03.js
+++ b/devtools/client/netmonitor/test/browser_net_statistics-03.js
@@ -25,14 +25,21 @@ add_task(function* () {
 
   let onEvents = promise.all([
     panel.once(EVENTS.PRIMED_CACHE_CHART_DISPLAYED),
     panel.once(EVENTS.EMPTY_CACHE_CHART_DISPLAYED)
   ]);
   NetMonitorView.toggleFrontendMode();
   yield onEvents;
 
+  is(NetMonitorView.currentFrontendMode, "network-statistics-view",
+    "The frontend mode is switched to the statistics view.");
+
   EventUtils.sendMouseEvent({ type: "click" }, $(".pie-chart-slice"));
+
+  is(NetMonitorView.currentFrontendMode, "network-inspector-view",
+    "The frontend mode is switched back to the inspector view.");
+
   testFilterButtons(monitor, "html");
   info("The correct filtering predicate is used when exiting perf. analysis mode.");
 
   yield teardown(monitor);
 });
--- a/devtools/client/netmonitor/test/head.js
+++ b/devtools/client/netmonitor/test/head.js
@@ -381,27 +381,29 @@ function waitFor(subject, eventName) {
   let deferred = promise.defer();
   subject.once(eventName, deferred.resolve);
   return deferred.promise;
 }
 
 /**
  * Tests if a button for a filter of given type is the only one checked.
  *
- * @param string aFilterType
+ * @param string filterType
  *        The type of the filter that should be the only one checked.
  */
-function testFilterButtons(aMonitor, aFilterType) {
-  let doc = aMonitor.panelWin.document;
-  let target = doc.querySelector("#requests-menu-filter-" + aFilterType + "-button");
-  let buttons = doc.querySelectorAll(".requests-menu-footer-button");
+function testFilterButtons(monitor, filterType) {
+  let doc = monitor.panelWin.document;
+  let target = doc.querySelector("#requests-menu-filter-" + filterType + "-button");
+  ok(target, `Filter button '${filterType}' was found`);
+  let buttons = [...doc.querySelectorAll(".menu-filter-button")];
+  ok(buttons.length > 0, "More than zero filter buttons were found");
 
   // Only target should be checked.
-  let checkStatus = [...buttons].map(button => button == target ? 1 : 0);
-  testFilterButtonsCustom(aMonitor, checkStatus);
+  let checkStatus = buttons.map(button => button == target ? 1 : 0);
+  testFilterButtonsCustom(monitor, checkStatus);
 }
 
 /**
  * Tests if filter buttons have 'checked' attributes set correctly.
  *
  * @param array aIsChecked
  *        An array specifying if a button at given index should have a
  *        'checked' attribute. For example, if the third item of the array
@@ -494,21 +496,21 @@ function waitForContentMessage(name) {
     def.resolve(msg);
   });
   return def.promise;
 }
 
 /**
  * Open the requestMenu menu and return all of it's items in a flat array
  * @param {netmonitorPanel} netmonitor
- * @param {Object} options to pass into openMenu
+ * @param {Event} event mouse event with screenX and screenX coordinates
  * @return An array of MenuItems
  */
-function openContextMenuAndGetAllItems(netmonitor, options) {
-  let menu = netmonitor.RequestsMenu._openMenu(options);
+function openContextMenuAndGetAllItems(netmonitor, event) {
+  let menu = netmonitor.RequestsMenu.contextMenu.open(event);
 
   // Flatten all menu items into a single array to make searching through it easier
   let allItems = [].concat.apply([], menu.items.map(function addItem(item) {
     if (item.submenu) {
       return addItem(item.submenu.items);
     }
     return item;
   }));
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/actions/display-pixel-ratio.js
@@ -0,0 +1,23 @@
+/* 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 { CHANGE_DISPLAY_PIXEL_RATIO } = require("./index");
+
+module.exports = {
+
+  /**
+   * The pixel ratio of the display has changed. This may be triggered by the user
+   * when changing the monitor resolution, or when the window is dragged to a different
+   * display with a different pixel ratio.
+   */
+  changeDisplayPixelRatio(displayPixelRatio) {
+    return {
+      type: CHANGE_DISPLAY_PIXEL_RATIO,
+      displayPixelRatio,
+    };
+  },
+
+};
--- a/devtools/client/responsive.html/actions/index.js
+++ b/devtools/client/responsive.html/actions/index.js
@@ -23,19 +23,29 @@ createEnum([
 
   // Change the device displayed in the viewport.
   "CHANGE_DEVICE",
 
   // Change the location of the page.  This may be triggered by the user
   // directly entering a new URL, navigating with links, etc.
   "CHANGE_LOCATION",
 
+  // The pixel ratio of the display has changed. This may be triggered by the user
+  // when changing the monitor resolution, or when the window is dragged to a different
+  // display with a different pixel ratio.
+  "CHANGE_DISPLAY_PIXEL_RATIO",
+
   // Change the network throttling profile.
   "CHANGE_NETWORK_THROTTLING",
 
+  // The pixel ratio of the viewport has changed. This may be triggered by the user
+  // when changing the device displayed in the viewport, or when a pixel ratio is
+  // selected from the DPR dropdown.
+  "CHANGE_VIEWPORT_PIXEL_RATIO",
+
   // Indicates that the device list is being loaded
   "LOAD_DEVICE_LIST_START",
 
   // Indicates that the device list loading action threw an error
   "LOAD_DEVICE_LIST_ERROR",
 
   // Indicates that the device list has been loaded successfully
   "LOAD_DEVICE_LIST_END",
--- a/devtools/client/responsive.html/actions/moz.build
+++ b/devtools/client/responsive.html/actions/moz.build
@@ -1,15 +1,16 @@
 # -*- Mode: python; 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/.
 
 DevToolsModules(
     'devices.js',
+    'display-pixel-ratio.js',
     'index.js',
     'location.js',
     'network-throttling.js',
     'screenshot.js',
     'touch-simulation.js',
     'viewports.js',
 )
--- a/devtools/client/responsive.html/actions/viewports.js
+++ b/devtools/client/responsive.html/actions/viewports.js
@@ -2,16 +2,17 @@
  * 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 {
   ADD_VIEWPORT,
   CHANGE_DEVICE,
+  CHANGE_VIEWPORT_PIXEL_RATIO,
   RESIZE_VIEWPORT,
   ROTATE_VIEWPORT
 } = require("./index");
 
 module.exports = {
 
   /**
    * Add an additional viewport to display the document.
@@ -29,16 +30,27 @@ module.exports = {
     return {
       type: CHANGE_DEVICE,
       id,
       device,
     };
   },
 
   /**
+   * Change the viewport pixel ratio.
+   */
+  changeViewportPixelRatio(id, pixelRatio = 0) {
+    return {
+      type: CHANGE_VIEWPORT_PIXEL_RATIO,
+      id,
+      pixelRatio,
+    };
+  },
+
+  /**
    * Resize the viewport.
    */
   resizeViewport(id, width, height) {
     return {
       type: RESIZE_VIEWPORT,
       id,
       width,
       height,
--- a/devtools/client/responsive.html/app.js
+++ b/devtools/client/responsive.html/app.js
@@ -15,29 +15,31 @@ const {
   updateDeviceModalOpen,
   updatePreferredDevices,
 } = require("./actions/devices");
 const { changeNetworkThrottling } = require("./actions/network-throttling");
 const { takeScreenshot } = require("./actions/screenshot");
 const { updateTouchSimulationEnabled } = require("./actions/touch-simulation");
 const {
   changeDevice,
+  changeViewportPixelRatio,
   resizeViewport,
   rotateViewport
 } = require("./actions/viewports");
 const DeviceModal = createFactory(require("./components/device-modal"));
 const GlobalToolbar = createFactory(require("./components/global-toolbar"));
 const Viewports = createFactory(require("./components/viewports"));
 const Types = require("./types");
 
 let App = createClass({
   displayName: "App",
 
   propTypes: {
     devices: PropTypes.shape(Types.devices).isRequired,
+    displayPixelRatio: PropTypes.number.isRequired,
     location: Types.location.isRequired,
     networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
     screenshot: PropTypes.shape(Types.screenshot).isRequired,
     touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
     viewports: PropTypes.arrayOf(PropTypes.shape(Types.viewport)).isRequired,
   },
 
   onBrowserMounted() {
@@ -55,16 +57,26 @@ let App = createClass({
 
   onChangeViewportDevice(id, device) {
     window.postMessage({
       type: "change-viewport-device",
       device,
     }, "*");
     this.props.dispatch(changeDevice(id, device.name));
     this.props.dispatch(updateTouchSimulationEnabled(device.touch));
+    this.props.dispatch(changeViewportPixelRatio(id, device.pixelRatio));
+  },
+
+  onChangeViewportPixelRatio(pixelRatio) {
+    window.postMessage({
+      type: "change-viewport-pixel-ratio",
+      pixelRatio,
+    }, "*");
+
+    this.props.dispatch(changeViewportPixelRatio(0, pixelRatio));
   },
 
   onContentResize({ width, height }) {
     window.postMessage({
       type: "content-resize",
       width,
       height,
     }, "*");
@@ -105,47 +117,62 @@ let App = createClass({
     }, "*");
 
     this.props.dispatch(updateTouchSimulationEnabled(isEnabled));
   },
 
   render() {
     let {
       devices,
+      displayPixelRatio,
       location,
       networkThrottling,
       screenshot,
       touchSimulation,
       viewports,
     } = this.props;
 
     let {
       onBrowserMounted,
       onChangeNetworkThrottling,
       onChangeViewportDevice,
+      onChangeViewportPixelRatio,
       onContentResize,
       onDeviceListUpdate,
       onExit,
       onResizeViewport,
       onRotateViewport,
       onScreenshot,
       onUpdateDeviceDisplayed,
       onUpdateDeviceModalOpen,
       onUpdateTouchSimulation,
     } = this;
 
+    let selectedDevice = "";
+    let selectedPixelRatio = 0;
+
+    if (viewports.length) {
+      selectedDevice = viewports[0].device;
+      selectedPixelRatio = viewports[0].pixelRatio;
+    }
+
     return dom.div(
       {
         id: "app",
       },
       GlobalToolbar({
+        devices,
+        displayPixelRatio,
         networkThrottling,
         screenshot,
+        selectedDevice,
+        selectedPixelRatio,
         touchSimulation,
         onChangeNetworkThrottling,
+        onChangeViewportPixelRatio,
         onExit,
         onScreenshot,
         onUpdateTouchSimulation,
       }),
       Viewports({
         devices,
         location,
         screenshot,
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/components/dpr-selector.js
@@ -0,0 +1,131 @@
+/* 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/. */
+
+/* eslint-env browser */
+
+"use strict";
+
+const { DOM: dom, createClass, PropTypes, addons } =
+  require("devtools/client/shared/vendor/react");
+
+const Types = require("../types");
+const { getStr, getFormatStr } = require("../utils/l10n");
+
+const PIXEL_RATIO_PRESET = [1, 2, 3];
+
+const createVisibleOption = value =>
+  dom.option({
+    value,
+    title: value,
+    key: value,
+  }, value);
+
+const createHiddenOption = value =>
+  dom.option({
+    value,
+    title: value,
+    hidden: true,
+    disabled: true,
+  }, value);
+
+module.exports = createClass({
+  displayName: "DPRSelector",
+
+  propTypes: {
+    devices: PropTypes.shape(Types.devices).isRequired,
+    displayPixelRatio: PropTypes.number.isRequired,
+    selectedDevice: PropTypes.string.isRequired,
+    selectedPixelRatio: PropTypes.number.isRequired,
+    onChangeViewportPixelRatio: PropTypes.func.isRequired,
+  },
+
+  mixins: [ addons.PureRenderMixin ],
+
+  getInitialState() {
+    return {
+      isFocused: false
+    };
+  },
+
+  onFocusChange({type}) {
+    this.setState({
+      isFocused: type === "focus"
+    });
+  },
+
+  onSelectChange({ target }) {
+    this.props.onChangeViewportPixelRatio(+target.value);
+  },
+
+  render() {
+    let {
+      devices,
+      displayPixelRatio,
+      selectedDevice,
+      selectedPixelRatio,
+    } = this.props;
+
+    let hiddenOptions = [];
+
+    for (let type of devices.types) {
+      for (let device of devices[type]) {
+        if (device.displayed &&
+            !hiddenOptions.includes(device.pixelRatio) &&
+            !PIXEL_RATIO_PRESET.includes(device.pixelRatio)) {
+          hiddenOptions.push(device.pixelRatio);
+        }
+      }
+    }
+
+    if (!PIXEL_RATIO_PRESET.includes(displayPixelRatio)) {
+      hiddenOptions.push(displayPixelRatio);
+    }
+
+    let state = devices.listState;
+    let isDisabled = (state !== Types.deviceListState.LOADED) || (selectedDevice !== "");
+    let selectorClass = "";
+    let title;
+
+    if (isDisabled) {
+      selectorClass += " disabled";
+      title = getFormatStr("responsive.autoDPR", selectedDevice);
+    } else {
+      title = getStr("responsive.devicePixelRatio");
+
+      if (selectedPixelRatio) {
+        selectorClass += " selected";
+      }
+    }
+
+    if (this.state.isFocused) {
+      selectorClass += " focused";
+    }
+
+    let listContent = PIXEL_RATIO_PRESET.map(createVisibleOption);
+
+    if (state == Types.deviceListState.LOADED) {
+      listContent = listContent.concat(hiddenOptions.map(createHiddenOption));
+    }
+
+    return dom.label(
+      {
+        id: "global-dpr-selector",
+        className: selectorClass,
+        title,
+      },
+      "DPR",
+      dom.select(
+        {
+          value: selectedPixelRatio || displayPixelRatio,
+          disabled: isDisabled,
+          onChange: this.onSelectChange,
+          onFocus: this.onFocusChange,
+          onBlur: this.onFocusChange,
+        },
+        ...listContent
+      )
+    );
+  },
+
+});
--- a/devtools/client/responsive.html/components/global-toolbar.js
+++ b/devtools/client/responsive.html/components/global-toolbar.js
@@ -4,39 +4,50 @@
 
 "use strict";
 
 const { DOM: dom, createClass, createFactory, PropTypes, addons } =
   require("devtools/client/shared/vendor/react");
 
 const { getStr } = require("../utils/l10n");
 const Types = require("../types");
+const DPRSelector = createFactory(require("./dpr-selector"));
 const NetworkThrottlingSelector = createFactory(require("./network-throttling-selector"));
 
 module.exports = createClass({
   displayName: "GlobalToolbar",
 
   propTypes: {
+    devices: PropTypes.shape(Types.devices).isRequired,
+    displayPixelRatio: PropTypes.number.isRequired,
     networkThrottling: PropTypes.shape(Types.networkThrottling).isRequired,
     screenshot: PropTypes.shape(Types.screenshot).isRequired,
+    selectedDevice: PropTypes.string.isRequired,
+    selectedPixelRatio: PropTypes.number.isRequired,
     touchSimulation: PropTypes.shape(Types.touchSimulation).isRequired,
     onChangeNetworkThrottling: PropTypes.func.isRequired,
+    onChangeViewportPixelRatio: PropTypes.func.isRequired,
     onExit: PropTypes.func.isRequired,
     onScreenshot: PropTypes.func.isRequired,
     onUpdateTouchSimulation: PropTypes.func.isRequired,
   },
 
   mixins: [ addons.PureRenderMixin ],
 
   render() {
     let {
+      devices,
+      displayPixelRatio,
       networkThrottling,
       screenshot,
+      selectedDevice,
+      selectedPixelRatio,
       touchSimulation,
       onChangeNetworkThrottling,
+      onChangeViewportPixelRatio,
       onExit,
       onScreenshot,
       onUpdateTouchSimulation
     } = this.props;
 
     let touchButtonClass = "toolbar-button devtools-button";
     if (touchSimulation.enabled) {
       touchButtonClass += " active";
@@ -52,16 +63,23 @@ module.exports = createClass({
           className: "title",
         },
         getStr("responsive.title")
       ),
       NetworkThrottlingSelector({
         networkThrottling,
         onChangeNetworkThrottling,
       }),
+      DPRSelector({
+        devices,
+        displayPixelRatio,
+        selectedDevice,
+        selectedPixelRatio,
+        onChangeViewportPixelRatio,
+      }),
       dom.button({
         id: "global-touch-simulation-button",
         className: touchButtonClass,
         title: (touchSimulation.enabled ?
           getStr("responsive.disableTouch") : getStr("responsive.enableTouch")),
         onClick: () => onUpdateTouchSimulation(!touchSimulation.enabled),
       }),
       dom.button({
--- a/devtools/client/responsive.html/components/moz.build
+++ b/devtools/client/responsive.html/components/moz.build
@@ -3,16 +3,17 @@
 # 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/.
 
 DevToolsModules(
     'browser.js',
     'device-modal.js',
     'device-selector.js',
+    'dpr-selector.js',
     'global-toolbar.js',
     'network-throttling-selector.js',
     'resizable-viewport.js',
     'viewport-dimension.js',
     'viewport-toolbar.js',
     'viewport.js',
     'viewports.js',
 )
--- a/devtools/client/responsive.html/index.css
+++ b/devtools/client/responsive.html/index.css
@@ -81,28 +81,29 @@ select {
   -moz-appearance: none;
   background-color: var(--theme-toolbar-background);
   background-image: var(--viewport-selection-arrow);
   background-position: 100% 50%;
   background-repeat: no-repeat;
   background-size: 7px;
   border: none;
   color: var(--viewport-color);
+  height: 100%;
   padding: 0 8px;
   text-align: center;
   text-overflow: ellipsis;
   font-size: 11px;
 }
 
 select.selected {
   background-image: var(--viewport-selection-arrow-selected);
   color: var(--viewport-active-color);
 }
 
-select:hover {
+select:not(:disabled):hover {
   background-image: var(--viewport-selection-arrow-hovered);
   color: var(--viewport-hover-color);
 }
 
 /* This is (believed to be?) separate from the identical select.selected rule
    set so that it overrides select:hover because of file ordering once the
    select is focused.  It's unclear whether the visual effect that results here
    is intentional and desired. */
@@ -183,16 +184,53 @@ select > option.divider {
 }
 
 #global-network-throttling-selector {
   height: 15px;
   padding-left: 0;
   width: 103px;
 }
 
+#global-dpr-selector > select {
+  padding: 0 8px 0 0;
+  margin-left: 2px;
+}
+
+#global-dpr-selector {
+  margin: 0 8px;
+  -moz-user-select: none;
+  color: var(--viewport-color);
+  font-size: 11px;
+  height: 15px;
+}
+
+#global-dpr-selector.focused,
+#global-dpr-selector:not(.disabled):hover {
+  color: var(--viewport-hover-color);
+}
+
+#global-dpr-selector:not(.disabled):hover > select {
+  background-image: var(--viewport-selection-arrow-hovered);
+  color: var(--viewport-hover-color);
+}
+
+#global-dpr-selector:focus > select {
+  background-image: var(--viewport-selection-arrow-selected);
+  color: var(--viewport-active-color);
+}
+
+#global-dpr-selector.selected,
+#global-dpr-selector.selected > select {
+  color: var(--viewport-active-color);
+}
+
+#global-dpr-selector > select > option {
+  padding: 5px;
+}
+
 #viewports {
   /* Make sure left-most viewport is visible when there's horizontal overflow.
      That is, when the horizontal space become smaller than the viewports and a
      scrollbar appears, then the first viewport will still be visible */
   position: sticky;
   left: 0;
   /* Individual viewports are inline elements, make sure they stay on a single
      line */
--- a/devtools/client/responsive.html/index.js
+++ b/devtools/client/responsive.html/index.js
@@ -21,16 +21,17 @@ const { createFactory, createElement } =
   require("devtools/client/shared/vendor/react");
 const ReactDOM = require("devtools/client/shared/vendor/react-dom");
 const { Provider } = require("devtools/client/shared/vendor/react-redux");
 
 const message = require("./utils/message");
 const App = createFactory(require("./app"));
 const Store = require("./store");
 const { changeLocation } = require("./actions/location");
+const { changeDisplayPixelRatio } = require("./actions/display-pixel-ratio");
 const { addViewport, resizeViewport } = require("./actions/viewports");
 const { loadDevices } = require("./actions/devices");
 
 let bootstrap = {
 
   telemetry: new Telemetry(),
 
   store: null,
@@ -84,22 +85,40 @@ window.addEventListener("unload", functi
 window.dispatch = action => bootstrap.dispatch(action);
 
 // Expose the store on window for testing
 Object.defineProperty(window, "store", {
   get: () => bootstrap.store,
   enumerable: true,
 });
 
+// Dispatch a `changeDisplayPixelRatio` action when the browser's pixel ratio is changing.
+// This is usually triggered when the user changes the monitor resolution, or when the
+// browser's window is dragged to a different display with a different pixel ratio.
+function onDPRChange() {
+  let dpr = window.devicePixelRatio;
+  let mql = window.matchMedia(`(resolution: ${dpr}dppx)`);
+
+  function listener() {
+    bootstrap.dispatch(changeDisplayPixelRatio(window.devicePixelRatio));
+    mql.removeListener(listener);
+    onDPRChange();
+  }
+
+  mql.addListener(listener);
+}
+
 /**
  * Called by manager.js to add the initial viewport based on the original page.
  */
 window.addInitialViewport = contentURI => {
   try {
+    onDPRChange();
     bootstrap.dispatch(changeLocation(contentURI));
+    bootstrap.dispatch(changeDisplayPixelRatio(window.devicePixelRatio));
     bootstrap.dispatch(addViewport());
   } catch (e) {
     console.error(e);
   }
 };
 
 /**
  * Called by manager.js when tests want to check the viewport size.
--- a/devtools/client/responsive.html/manager.js
+++ b/devtools/client/responsive.html/manager.js
@@ -447,16 +447,19 @@ ResponsiveUI.prototype = {
 
     switch (event.data.type) {
       case "change-network-throtting":
         this.onChangeNetworkThrottling(event);
         break;
       case "change-viewport-device":
         this.onChangeViewportDevice(event);
         break;
+      case "change-viewport-pixel-ratio":
+        this.updateDPPX(event.data.pixelRatio);
+        break;
       case "content-resize":
         this.onContentResize(event);
         break;
       case "exit":
         this.onExit();
         break;
       case "update-touch-simulation":
         this.onUpdateTouchSimulation(event);
--- a/devtools/client/responsive.html/reducers.js
+++ b/devtools/client/responsive.html/reducers.js
@@ -1,12 +1,13 @@
 /* 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";
 
 exports.devices = require("./reducers/devices");
+exports.displayPixelRatio = require("./reducers/display-pixel-ratio");
 exports.location = require("./reducers/location");
 exports.networkThrottling = require("./reducers/network-throttling");
 exports.screenshot = require("./reducers/screenshot");
 exports.touchSimulation = require("./reducers/touch-simulation");
 exports.viewports = require("./reducers/viewports");
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/reducers/display-pixel-ratio.js
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* eslint-env browser */
+
+"use strict";
+
+const { CHANGE_DISPLAY_PIXEL_RATIO } = require("../actions/index");
+const INITIAL_DISPLAY_PIXEL_RATIO = 0;
+
+let reducers = {
+
+  [CHANGE_DISPLAY_PIXEL_RATIO](_, action) {
+    return action.displayPixelRatio;
+  },
+
+};
+
+module.exports = function (displayPixelRatio = INITIAL_DISPLAY_PIXEL_RATIO, action) {
+  let reducer = reducers[action.type];
+  if (!reducer) {
+    return displayPixelRatio;
+  }
+  return reducer(displayPixelRatio, action);
+};
--- a/devtools/client/responsive.html/reducers/moz.build
+++ b/devtools/client/responsive.html/reducers/moz.build
@@ -1,14 +1,15 @@
 # -*- Mode: python; 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/.
 
 DevToolsModules(
     'devices.js',
+    'display-pixel-ratio.js',
     'location.js',
     'network-throttling.js',
     'screenshot.js',
     'touch-simulation.js',
     'viewports.js',
 )
--- a/devtools/client/responsive.html/reducers/viewports.js
+++ b/devtools/client/responsive.html/reducers/viewports.js
@@ -2,28 +2,30 @@
  * 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 {
   ADD_VIEWPORT,
   CHANGE_DEVICE,
+  CHANGE_VIEWPORT_PIXEL_RATIO,
   RESIZE_VIEWPORT,
   ROTATE_VIEWPORT,
 } = require("../actions/index");
 
 let nextViewportId = 0;
 
 const INITIAL_VIEWPORTS = [];
 const INITIAL_VIEWPORT = {
   id: nextViewportId++,
   device: "",
   width: 320,
   height: 480,
+  pixelRatio: 0,
 };
 
 let reducers = {
 
   [ADD_VIEWPORT](viewports) {
     // For the moment, there can be at most one viewport.
     if (viewports.length === 1) {
       return viewports;
@@ -38,16 +40,28 @@ let reducers = {
       }
 
       return Object.assign({}, viewport, {
         device,
       });
     });
   },
 
+  [CHANGE_VIEWPORT_PIXEL_RATIO](viewports, {id, pixelRatio }) {
+    return viewports.map(viewport => {
+      if (viewport.id !== id) {
+        return viewport;
+      }
+
+      return Object.assign({}, viewport, {
+        pixelRatio,
+      });
+    });
+  },
+
   [RESIZE_VIEWPORT](viewports, { id, width, height }) {
     return viewports.map(viewport => {
       if (viewport.id !== id) {
         return viewport;
       }
 
       if (!width) {
         width = viewport.width;
--- a/devtools/client/responsive.html/test/browser/browser.ini
+++ b/devtools/client/responsive.html/test/browser/browser.ini
@@ -16,16 +16,17 @@ support-files =
   !/devtools/client/shared/test/test-actor.js
   !/devtools/client/shared/test/test-actor-registry.js
 
 [browser_device_change.js]
 [browser_device_modal_error.js]
 [browser_device_modal_exit.js]
 [browser_device_modal_submit.js]
 [browser_device_width.js]
+[browser_dpr_change.js]
 [browser_exit_button.js]
 [browser_frame_script_active.js]
 [browser_menu_item_01.js]
 [browser_menu_item_02.js]
 [browser_mouse_resize.js]
 [browser_navigation.js]
 [browser_network_throttling.js]
 [browser_page_state.js]
--- a/devtools/client/responsive.html/test/browser/browser_device_change.js
+++ b/devtools/client/responsive.html/test/browser/browser_device_change.js
@@ -38,34 +38,34 @@ addRDMTask(TEST_URL, function* ({ ui, ma
   // Test defaults
   testViewportDimensions(ui, 320, 480);
   yield testUserAgent(ui, DEFAULT_UA);
   yield testDevicePixelRatio(ui, DEFAULT_DPPX);
   yield testTouchEventsOverride(ui, false);
   testViewportSelectLabel(ui, "no device selected");
 
   // Test device with custom properties
-  yield switchDevice(ui, "Fake Phone RDM Test");
+  yield selectDevice(ui, "Fake Phone RDM Test");
   yield waitForViewportResizeTo(ui, testDevice.width, testDevice.height);
   yield testUserAgent(ui, testDevice.userAgent);
   yield testDevicePixelRatio(ui, testDevice.pixelRatio);
   yield testTouchEventsOverride(ui, true);
 
   // Test resetting device when resizing viewport
   let deviceChanged = once(ui, "viewport-device-changed");
   yield testViewportResize(ui, ".viewport-vertical-resize-handle",
     [-10, -10], [testDevice.width, testDevice.height - 10], [0, -10], ui);
   yield deviceChanged;
   yield testUserAgent(ui, DEFAULT_UA);
   yield testDevicePixelRatio(ui, DEFAULT_DPPX);
   yield testTouchEventsOverride(ui, false);
   testViewportSelectLabel(ui, "no device selected");
 
   // Test device with generic properties
-  yield switchDevice(ui, "Laptop (1366 x 768)");
+  yield selectDevice(ui, "Laptop (1366 x 768)");
   yield waitForViewportResizeTo(ui, 1366, 768);
   yield testUserAgent(ui, DEFAULT_UA);
   yield testDevicePixelRatio(ui, 1);
   yield testTouchEventsOverride(ui, false);
 });
 
 function testViewportDimensions(ui, w, h) {
   let viewport = ui.toolWindow.document.querySelector(".viewport-content");
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/browser/browser_dpr_change.js
@@ -0,0 +1,146 @@
+/* Any copyright is dedicated to the Public Domain.
+http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Tests changing viewport device
+const TEST_URL = "data:text/html;charset=utf-8,DPR list test";
+const DEFAULT_DPPX = window.devicePixelRatio;
+const VIEWPORT_DPPX = DEFAULT_DPPX + 2;
+const Types = require("devtools/client/responsive.html/types");
+
+const testDevice = {
+  "name": "Fake Phone RDM Test",
+  "width": 320,
+  "height": 470,
+  "pixelRatio": 5.5,
+  "userAgent": "Mozilla/5.0 (Mobile; rv:39.0) Gecko/39.0 Firefox/39.0",
+  "touch": true,
+  "firefoxOS": true,
+  "os": "custom",
+  "featured": true,
+};
+
+// Add the new device to the list
+addDeviceForTest(testDevice);
+
+addRDMTask(TEST_URL, function* ({ ui, manager }) {
+  yield waitStartup(ui);
+
+  yield testDefaults(ui);
+  yield testChangingDevice(ui);
+  yield testResetWhenResizingViewport(ui);
+  yield testChangingDPR(ui);
+});
+
+function* waitStartup(ui) {
+  let { store } = ui.toolWindow;
+
+  // Wait until the viewport has been added and the device list has been loaded
+  yield waitUntilState(store, state => state.viewports.length == 1
+    && state.devices.listState == Types.deviceListState.LOADED);
+}
+
+function* testDefaults(ui) {
+  info("Test Defaults");
+
+  yield testDevicePixelRatio(ui, window.devicePixelRatio);
+  testViewportDPRSelect(ui, {value: window.devicePixelRatio, disabled: false});
+  testViewportDeviceSelectLabel(ui, "no device selected");
+}
+
+function* testChangingDevice(ui) {
+  info("Test Changing Device");
+
+  let waitPixelRatioChange = onceDevicePixelRatioChange(ui);
+
+  yield selectDevice(ui, testDevice.name);
+  yield waitForViewportResizeTo(ui, testDevice.width, testDevice.height);
+  yield waitPixelRatioChange;
+  yield testDevicePixelRatio(ui, testDevice.pixelRatio);
+  testViewportDPRSelect(ui, {value: testDevice.pixelRatio, disabled: true});
+  testViewportDeviceSelectLabel(ui, testDevice.name);
+}
+
+function* testResetWhenResizingViewport(ui) {
+  info("Test reset when resizing the viewport");
+
+  let waitPixelRatioChange = onceDevicePixelRatioChange(ui);
+
+  yield testViewportResize(ui, ".viewport-vertical-resize-handle",
+    [-10, -10], [testDevice.width, testDevice.height - 10], [0, -10], ui);
+
+  yield waitPixelRatioChange;
+  yield testDevicePixelRatio(ui, window.devicePixelRatio);
+
+  testViewportDPRSelect(ui, {value: window.devicePixelRatio, disabled: false});
+  testViewportDeviceSelectLabel(ui, "no device selected");
+}
+
+function* testChangingDPR(ui) {
+  info("Test changing device pixel ratio");
+
+  let waitPixelRatioChange = onceDevicePixelRatioChange(ui);
+
+  yield selectDPR(ui, VIEWPORT_DPPX);
+  yield waitPixelRatioChange;
+  yield testDevicePixelRatio(ui, VIEWPORT_DPPX);
+  testViewportDPRSelect(ui, {value: VIEWPORT_DPPX, disabled: false});
+  testViewportDeviceSelectLabel(ui, "no device selected");
+}
+
+function testViewportDPRSelect(ui, expected) {
+  info("Test viewport's DPR Select");
+
+  let select = ui.toolWindow.document.querySelector("#global-dpr-selector > select");
+  is(select.value, expected.value,
+     `DPR Select value should be: ${expected.value}`);
+  is(select.disabled, expected.disabled,
+    `DPR Select should be ${expected.disabled ? "disabled" : "enabled"}.`);
+}
+
+function testViewportDeviceSelectLabel(ui, expected) {
+  info("Test viewport's device select label");
+
+  let select = ui.toolWindow.document.querySelector(".viewport-device-selector");
+  is(select.selectedOptions[0].textContent, expected,
+     `Device Select value should be: ${expected}`);
+}
+
+function* testDevicePixelRatio(ui, expected) {
+  info("Test device pixel ratio");
+
+  let dppx = yield getViewportDevicePixelRatio(ui);
+  is(dppx, expected, `devicePixelRatio should be: ${expected}`);
+}
+
+function* getViewportDevicePixelRatio(ui) {
+  return yield ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
+    return content.devicePixelRatio;
+  });
+}
+
+function onceDevicePixelRatioChange(ui) {
+  return ContentTask.spawn(ui.getViewportBrowser(), {}, function* () {
+    info(`Listening for a pixel ratio change (current: ${content.devicePixelRatio}dppx)`);
+
+    let pixelRatio = content.devicePixelRatio;
+    let mql = content.matchMedia(`(resolution: ${pixelRatio}dppx)`);
+
+    return new Promise(resolve => {
+      const onWindowCreated = () => {
+        if (pixelRatio !== content.devicePixelRatio) {
+          resolve();
+        }
+      };
+
+      addEventListener("DOMWindowCreated", onWindowCreated, {once: true});
+
+      mql.addListener(function listener() {
+        mql.removeListener(listener);
+        removeEventListener("DOMWindowCreated", onWindowCreated, {once: true});
+        resolve();
+      });
+    });
+  });
+}
--- a/devtools/client/responsive.html/test/browser/browser_network_throttling.js
+++ b/devtools/client/responsive.html/test/browser/browser_network_throttling.js
@@ -20,17 +20,17 @@ addRDMTask(TEST_URL, function* ({ ui, ma
 
   // Test a fast profile
   yield testThrottlingProfile(ui, "Wi-Fi");
 
   // Test a slower profile
   yield testThrottlingProfile(ui, "Regular 3G");
 
   // Test switching back to no throttling
-  yield switchNetworkThrottling(ui, "No throttling");
+  yield selectNetworkThrottling(ui, "No throttling");
   testNetworkThrottlingSelectorLabel(ui, "No throttling");
   yield testNetworkThrottlingState(ui, null);
 });
 
 function testNetworkThrottlingSelectorLabel(ui, expected) {
   let selector = "#global-network-throttling-selector";
   let select = ui.toolWindow.document.querySelector(selector);
   is(select.selectedOptions[0].textContent, expected,
@@ -39,17 +39,17 @@ function testNetworkThrottlingSelectorLa
 
 var testNetworkThrottlingState = Task.async(function* (ui, expected) {
   let state = yield ui.emulationFront.getNetworkThrottling();
   Assert.deepEqual(state, expected, "Network throttling state should be " +
                                     JSON.stringify(expected, null, 2));
 });
 
 var testThrottlingProfile = Task.async(function* (ui, profile) {
-  yield switchNetworkThrottling(ui, profile);
+  yield selectNetworkThrottling(ui, profile);
   testNetworkThrottlingSelectorLabel(ui, profile);
   let data = throttlingProfiles.find(({ id }) => id == profile);
   let { download, upload, latency } = data;
   yield testNetworkThrottlingState(ui, {
     downloadThroughput: download,
     uploadThroughput: upload,
     latency,
   });
--- a/devtools/client/responsive.html/test/browser/head.js
+++ b/devtools/client/responsive.html/test/browser/head.js
@@ -206,39 +206,43 @@ function* testViewportResize(ui, selecto
 
   let endRect = getElRect(selector, win);
   is(endRect.left - startRect.left, expectedHandleMove[0],
     `The x move of ${selector} is as expected`);
   is(endRect.top - startRect.top, expectedHandleMove[1],
     `The y move of ${selector} is as expected`);
 }
 
-function openDeviceModal(ui) {
-  let { document } = ui.toolWindow;
+function openDeviceModal({toolWindow}) {
+  let { document } = toolWindow;
   let select = document.querySelector(".viewport-device-selector");
   let modal = document.querySelector("#device-modal-wrapper");
-  let editDeviceOption = [...select.options].filter(o => {
-    return o.value === OPEN_DEVICE_MODAL_VALUE;
-  })[0];
 
   info("Checking initial device modal state");
   ok(modal.classList.contains("closed") && !modal.classList.contains("opened"),
     "The device modal is closed by default.");
 
   info("Opening device modal through device selector.");
-  EventUtils.synthesizeMouseAtCenter(select, {type: "mousedown"},
-    ui.toolWindow);
-  EventUtils.synthesizeMouseAtCenter(editDeviceOption, {type: "mouseup"},
-    ui.toolWindow);
+
+  let event = new toolWindow.UIEvent("change", {
+    view: toolWindow,
+    bubbles: true,
+    cancelable: true
+  });
+
+  select.value = OPEN_DEVICE_MODAL_VALUE;
+  select.dispatchEvent(event);
 
   ok(modal.classList.contains("opened") && !modal.classList.contains("closed"),
     "The device modal is displayed.");
 }
 
-function switchSelector({ toolWindow }, selector, value) {
+function changeSelectValue({ toolWindow }, selector, value) {
+  info(`Selecting ${value} in ${selector}.`);
+
   return new Promise(resolve => {
     let select = toolWindow.document.querySelector(selector);
     isnot(select, null, `selector "${selector}" should match an existing element.`);
 
     let option = [...select.options].find(o => o.value === String(value));
     isnot(option, undefined, `value "${value}" should match an existing option.`);
 
     let event = new toolWindow.UIEvent("change", {
@@ -253,27 +257,28 @@ function switchSelector({ toolWindow }, 
       resolve();
     }, { once: true });
 
     select.value = value;
     select.dispatchEvent(event);
   });
 }
 
-let switchDevice = Task.async(function* (ui, value) {
-  let changed = once(ui, "viewport-device-changed");
-  yield switchSelector(ui, ".viewport-device-selector", value);
-  yield changed;
-});
+const selectDevice = (ui, value) => Promise.all([
+  once(ui, "viewport-device-changed"),
+  changeSelectValue(ui, ".viewport-device-selector", value)
+]);
 
-let switchNetworkThrottling = Task.async(function* (ui, value) {
-  let changed = once(ui, "network-throttling-changed");
-  yield switchSelector(ui, "#global-network-throttling-selector", value);
-  yield changed;
-});
+const selectDPR = (ui, value) =>
+  changeSelectValue(ui, "#global-dpr-selector > select", value);
+
+const selectNetworkThrottling = (ui, value) => Promise.all([
+  once(ui, "network-throttling-changed"),
+  changeSelectValue(ui, "#global-network-throttling-selector", value)
+]);
 
 function getSessionHistory(browser) {
   return ContentTask.spawn(browser, {}, function* () {
     /* eslint-disable no-undef */
     let { interfaces: Ci } = Components;
     let webNav = docShell.QueryInterface(Ci.nsIWebNavigation);
     let sessionHistory = webNav.sessionHistory;
     let result = {
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/unit/test_change_display_pixel_ratio.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test changing the display pixel ratio.
+
+const { changeDisplayPixelRatio } =
+  require("devtools/client/responsive.html/actions/display-pixel-ratio");
+const NEW_PIXEL_RATIO = 5.5;
+
+add_task(function* () {
+  let store = Store();
+  const { getState, dispatch } = store;
+
+  equal(getState().displayPixelRatio, 0,
+        "Defaults to 0 at startup");
+
+  dispatch(changeDisplayPixelRatio(NEW_PIXEL_RATIO));
+  equal(getState().displayPixelRatio, NEW_PIXEL_RATIO,
+    `Display Pixel Ratio changed to ${NEW_PIXEL_RATIO}`);
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/responsive.html/test/unit/test_change_viewport_pixel_ratio.js
@@ -0,0 +1,22 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// Test changing the viewport pixel ratio.
+
+const { addViewport, changeViewportPixelRatio } =
+  require("devtools/client/responsive.html/actions/viewports");
+const NEW_PIXEL_RATIO = 5.5;
+
+add_task(function* () {
+  let store = Store();
+  const { getState, dispatch } = store;
+
+  dispatch(addViewport());
+  dispatch(changeViewportPixelRatio(0, NEW_PIXEL_RATIO));
+
+  let viewport = getState().viewports[0];
+  equal(viewport.pixelRatio, NEW_PIXEL_RATIO,
+    `Viewport's pixel ratio changed to ${NEW_PIXEL_RATIO}`);
+});
--- a/devtools/client/responsive.html/test/unit/xpcshell.ini
+++ b/devtools/client/responsive.html/test/unit/xpcshell.ini
@@ -2,15 +2,17 @@
 tags = devtools
 head = head.js ../../../framework/test/shared-redux-head.js
 tail =
 firefox-appdir = browser
 
 [test_add_device.js]
 [test_add_device_type.js]
 [test_add_viewport.js]
+[test_change_display_pixel_ratio.js]
 [test_change_location.js]
 [test_change_network_throttling.js]
 [test_change_viewport_device.js]
+[test_change_viewport_pixel_ratio.js]
 [test_resize_viewport.js]
 [test_rotate_viewport.js]
 [test_update_device_displayed.js]
 [test_update_touch_simulation_enabled.js]
--- a/devtools/client/shared/components/test/mochitest/head.js
+++ b/devtools/client/shared/components/test/mochitest/head.js
@@ -1,10 +1,11 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/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/. */
 /* eslint no-unused-vars: [2, {"vars": "local"}] */
 
 "use strict";
 
 var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
 
 var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 var { Assert } = require("resource://testing-common/Assert.jsm");
--- a/devtools/client/shared/components/test/mochitest/test_HSplitBox_01.html
+++ b/devtools/client/shared/components/test/mochitest/test_HSplitBox_01.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Basic tests for the HSplitBox component.
 -->
 <head>
   <meta charset="utf-8">
   <title>Tree component test</title>
--- a/devtools/client/shared/components/test/mochitest/test_frame_01.html
+++ b/devtools/client/shared/components/test/mochitest/test_frame_01.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test the formatting of the file name, line and columns are correct in frame components,
 with optional columns, unknown and non-URL sources.
 -->
 <head>
   <meta charset="utf-8">
--- a/devtools/client/shared/components/test/mochitest/test_notification_box_01.html
+++ b/devtools/client/shared/components/test/mochitest/test_notification_box_01.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test for Notification Box. The test is checking:
 * Basic rendering
 * Appending a notification
 * Notification priority
 * Closing notification
--- a/devtools/client/shared/components/test/mochitest/test_notification_box_02.html
+++ b/devtools/client/shared/components/test/mochitest/test_notification_box_02.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test for Notification Box. The test is checking:
 * Using custom callback in a notification
 -->
 <head>
   <meta charset="utf-8">
--- a/devtools/client/shared/components/test/mochitest/test_notification_box_03.html
+++ b/devtools/client/shared/components/test/mochitest/test_notification_box_03.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test for Notification Box. The test is checking:
 * Using custom buttons in a notification
 -->
 <head>
   <meta charset="utf-8">
--- a/devtools/client/shared/components/test/mochitest/test_reps_array.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_array.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test ArrayRep rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - ArrayRep</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_attribute.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_attribute.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test Attribute rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - Attribute</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_comment-node.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_comment-node.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test comment-node rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - comment-node</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_date-time.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_date-time.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test DateTime rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - DateTime</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_document.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_document.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test Document rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - Document</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_element-node.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_element-node.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test Element node rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - Element node</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_event.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_event.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test Event rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - Event</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_function.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_function.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test Func rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - Func</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_grip-array.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test GripArray rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - GripArray</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip-map.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_grip-map.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test GripMap rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - GripMap</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_grip.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_grip.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test grip rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - grip</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_infinity.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_infinity.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test Infinity rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - Infinity</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_nan.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_nan.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test NaN rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - NaN</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_null.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_null.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test Null rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - Null</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_number.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_number.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test Number rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - Number</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_object-with-text.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_object-with-text.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test ObjectWithText rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - ObjectWithText</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_object-with-url.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_object-with-url.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test ObjectWithURL rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - ObjectWithURL</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_object.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_object.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test Obj rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - Obj</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_promise.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_promise.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test Promise rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - Promise</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_regexp.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_regexp.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test RegExp rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - RegExp</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_string.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_string.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test String rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - String</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_stylesheet.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_stylesheet.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test Stylesheet rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - Stylesheet</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_symbol.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_symbol.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test Symbol rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - String</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_text-node.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_text-node.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test text-node rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - text-node</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_undefined.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_undefined.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test undefined rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep test - undefined</title>
--- a/devtools/client/shared/components/test/mochitest/test_reps_window.html
+++ b/devtools/client/shared/components/test/mochitest/test_reps_window.html
@@ -1,9 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test window rep
 -->
 <head>
   <meta charset="utf-8">
   <title>Rep tests - window</title>
--- a/devtools/client/shared/components/test/mochitest/test_sidebar_toggle.html
+++ b/devtools/client/shared/components/test/mochitest/test_sidebar_toggle.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test sidebar toggle button
 -->
 <head>
   <meta charset="utf-8">
   <title>Sidebar toggle button test</title>
--- a/devtools/client/shared/components/test/mochitest/test_stack-trace.html
+++ b/devtools/client/shared/components/test/mochitest/test_stack-trace.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test the rendering of a stack trace
 -->
 <head>
   <meta charset="utf-8">
   <title>StackTrace component test</title>
--- a/devtools/client/shared/components/test/mochitest/test_tabs_accessibility.html
+++ b/devtools/client/shared/components/test/mochitest/test_tabs_accessibility.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test tabs accessibility.
 -->
 <head>
   <meta charset="utf-8">
   <title>Tabs component accessibility test</title>
--- a/devtools/client/shared/components/test/mochitest/test_tabs_menu.html
+++ b/devtools/client/shared/components/test/mochitest/test_tabs_menu.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html class="theme-light">
 <!--
 Test all-tabs menu.
 -->
 <head>
   <meta charset="utf-8">
   <title>Tabs component All-tabs menu test</title>
--- a/devtools/client/shared/components/test/mochitest/test_tree_01.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_01.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test trees get displayed with the items in correct order and at the correct
 depth.
 -->
 <head>
   <meta charset="utf-8">
--- a/devtools/client/shared/components/test/mochitest/test_tree_02.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_02.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test that collapsed subtrees aren't rendered.
 -->
 <head>
   <meta charset="utf-8">
   <title>Tree component test</title>
--- a/devtools/client/shared/components/test/mochitest/test_tree_03.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_03.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test Tree's autoExpandDepth.
 -->
 <head>
   <meta charset="utf-8">
   <title>Tree component test</title>
--- a/devtools/client/shared/components/test/mochitest/test_tree_04.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_04.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test that we only render visible tree items.
 -->
 <head>
   <meta charset="utf-8">
   <title>Tree component test</title>
--- a/devtools/client/shared/components/test/mochitest/test_tree_05.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_05.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test focusing with the Tree component.
 -->
 <head>
   <meta charset="utf-8">
   <title>Tree component test</title>
--- a/devtools/client/shared/components/test/mochitest/test_tree_06.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_06.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test keyboard navigation with the Tree component.
 -->
 <head>
   <meta charset="utf-8">
   <title>Tree component test</title>
--- a/devtools/client/shared/components/test/mochitest/test_tree_07.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_07.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test that arrows get the open attribute when their item's children are expanded.
 -->
 <head>
   <meta charset="utf-8">
   <title>Tree component test</title>
--- a/devtools/client/shared/components/test/mochitest/test_tree_08.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_08.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test that when an item in the Tree component is clicked, it steals focus from
 other inputs.
 -->
 <head>
   <meta charset="utf-8">
--- a/devtools/client/shared/components/test/mochitest/test_tree_09.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_09.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test that when an item in the Tree component is expanded or collapsed the appropriate event handler fires.
 -->
 <head>
   <meta charset="utf-8">
   <title>Tree component test</title>
--- a/devtools/client/shared/components/test/mochitest/test_tree_10.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_10.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test that when an item in the Tree component is expanded or collapsed the appropriate event handler fires.
 -->
 <head>
   <meta charset="utf-8">
   <title>Tree component test</title>
--- a/devtools/client/shared/components/test/mochitest/test_tree_11.html
+++ b/devtools/client/shared/components/test/mochitest/test_tree_11.html
@@ -1,8 +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/. -->
 <!DOCTYPE HTML>
 <html>
 <!--
 Test that when an item in the Tree component is focused by arrow key, the view is scrolled.
 -->
 <head>
   <meta charset="utf-8">
   <title>Tree component test</title>
--- a/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js
+++ b/devtools/client/webconsole/new-console-output/test/fixtures/stubs/consoleApi.js
@@ -653,17 +653,16 @@ stubPackets.set("console.log('foobar', '
 		"level": "log",
 		"lineNumber": 1,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"styles": [],
 		"timeStamp": 1477086261590,
 		"timer": null,
 		"workerType": "none",
 		"category": "webdev"
@@ -687,17 +686,16 @@ stubPackets.set("console.log(undefined)"
 		"level": "log",
 		"lineNumber": 1,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"styles": [],
 		"timeStamp": 1477086264886,
 		"timer": null,
 		"workerType": "none",
 		"category": "webdev"
@@ -719,17 +717,16 @@ stubPackets.set("console.warn('danger, w
 		"level": "warn",
 		"lineNumber": 1,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"styles": [],
 		"timeStamp": 1477086267284,
 		"timer": null,
 		"workerType": "none",
 		"category": "webdev"
@@ -753,17 +750,16 @@ stubPackets.set("console.log(NaN)", {
 		"level": "log",
 		"lineNumber": 1,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"styles": [],
 		"timeStamp": 1477086269484,
 		"timer": null,
 		"workerType": "none",
 		"category": "webdev"
@@ -787,17 +783,16 @@ stubPackets.set("console.log(null)", {
 		"level": "log",
 		"lineNumber": 1,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"styles": [],
 		"timeStamp": 1477086271418,
 		"timer": null,
 		"workerType": "none",
 		"category": "webdev"
@@ -819,17 +814,16 @@ stubPackets.set("console.log('鼬')", {
 		"level": "log",
 		"lineNumber": 1,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"styles": [],
 		"timeStamp": 1477086273549,
 		"timer": null,
 		"workerType": "none",
 		"category": "webdev"
@@ -849,17 +843,16 @@ stubPackets.set("console.clear()", {
 		"level": "clear",
 		"lineNumber": 1,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086275587,
 		"timer": null,
 		"workerType": "none",
 		"styles": [],
 		"category": "webdev"
@@ -884,17 +877,16 @@ stubPackets.set("console.count('bar')", 
 		"level": "count",
 		"lineNumber": 1,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086277812,
 		"timer": null,
 		"workerType": "none",
 		"styles": [],
 		"category": "webdev"
@@ -937,17 +929,16 @@ stubPackets.set("console.assert(false, {
 		"level": "assert",
 		"lineNumber": 1,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"styles": [],
 		"timeStamp": 1477086280131,
 		"timer": null,
 		"stacktrace": [
 			{
@@ -978,17 +969,16 @@ stubPackets.set("console.log('hello \nfr
 		"level": "log",
 		"lineNumber": 1,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"styles": [],
 		"timeStamp": 1477086281936,
 		"timer": null,
 		"workerType": "none",
 		"category": "webdev"
@@ -1010,17 +1000,16 @@ stubPackets.set("console.log('úṇĩçödê țĕșť')", {
 		"level": "log",
 		"lineNumber": 1,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"styles": [],
 		"timeStamp": 1477086283713,
 		"timer": null,
 		"workerType": "none",
 		"category": "webdev"
@@ -1054,17 +1043,16 @@ stubPackets.set("console.dirxml(window)"
 		"level": "dirxml",
 		"lineNumber": 1,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086285483,
 		"timer": null,
 		"workerType": "none",
 		"styles": [],
 		"category": "webdev"
@@ -1084,17 +1072,16 @@ stubPackets.set("console.trace()", {
 		"level": "trace",
 		"lineNumber": 3,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086287286,
 		"timer": null,
 		"stacktrace": [
 			{
 				"columnNumber": 3,
@@ -1139,17 +1126,16 @@ stubPackets.set("console.time('bar')", {
 		"level": "time",
 		"lineNumber": 2,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086289137,
 		"timer": {
 			"name": "bar",
 			"started": 1166.305
 		},
@@ -1174,17 +1160,16 @@ stubPackets.set("console.timeEnd('bar')"
 		"level": "timeEnd",
 		"lineNumber": 3,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086289138,
 		"timer": {
 			"duration": 1.3550000000000182,
 			"name": "bar"
 		},
@@ -1209,17 +1194,16 @@ stubPackets.set("console.table('bar')", 
 		"level": "table",
 		"lineNumber": 2,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086290984,
 		"timer": null,
 		"workerType": "none",
 		"styles": [],
 		"category": "webdev"
@@ -1258,17 +1242,16 @@ stubPackets.set("console.table(['a', 'b'
 		"level": "table",
 		"lineNumber": 2,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086292762,
 		"timer": null,
 		"workerType": "none",
 		"styles": [],
 		"category": "webdev"
@@ -1290,17 +1273,16 @@ stubPackets.set("console.group('bar')", 
 		"level": "group",
 		"lineNumber": 2,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086294628,
 		"timer": null,
 		"workerType": "none",
 		"styles": [],
 		"category": "webdev"
@@ -1322,17 +1304,16 @@ stubPackets.set("console.groupEnd('bar')
 		"level": "groupEnd",
 		"lineNumber": 3,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086294630,
 		"timer": null,
 		"workerType": "none",
 		"styles": [],
 		"category": "webdev"
@@ -1354,17 +1335,16 @@ stubPackets.set("console.groupCollapsed(
 		"level": "groupCollapsed",
 		"lineNumber": 2,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086296567,
 		"timer": null,
 		"workerType": "none",
 		"styles": [],
 		"category": "webdev"
@@ -1386,17 +1366,16 @@ stubPackets.set("console.groupEnd('foo')
 		"level": "groupEnd",
 		"lineNumber": 3,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086296570,
 		"timer": null,
 		"workerType": "none",
 		"styles": [],
 		"category": "webdev"
@@ -1416,17 +1395,16 @@ stubPackets.set("console.group()", {
 		"level": "group",
 		"lineNumber": 2,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086298462,
 		"timer": null,
 		"workerType": "none",
 		"styles": [],
 		"category": "webdev"
@@ -1446,17 +1424,16 @@ stubPackets.set("console.groupEnd()", {
 		"level": "groupEnd",
 		"lineNumber": 3,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"timeStamp": 1477086298464,
 		"timer": null,
 		"workerType": "none",
 		"styles": [],
 		"category": "webdev"
@@ -1479,17 +1456,16 @@ stubPackets.set("console.log(%cfoobar)",
 		"level": "log",
 		"lineNumber": 2,
 		"originAttributes": {
 			"addonId": "",
 			"appId": 0,
 			"firstPartyDomain": "",
 			"inIsolatedMozBrowser": false,
 			"privateBrowsingId": 0,
-			"signedPkg": "",
 			"userContextId": 0
 		},
 		"private": false,
 		"styles": [
 			"color:blue;font-size:1.3em;background:url('http://example.com/test');position:absolute;top:10px",
 			"color:red;background:url('http://example.com/test')"
 		],
 		"timeStamp": 1477086300265,
--- a/dom/base/ChromeUtils.cpp
+++ b/dom/base/ChromeUtils.cpp
@@ -173,26 +173,24 @@ ChromeUtils::FillNonDefaultOriginAttribu
 /* static */ bool
 ChromeUtils::IsOriginAttributesEqual(dom::GlobalObject& aGlobal,
                                      const dom::OriginAttributesDictionary& aA,
                                      const dom::OriginAttributesDictionary& aB)
 {
   return aA.mAddonId == aB.mAddonId &&
          aA.mAppId == aB.mAppId &&
          aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
-         aA.mSignedPkg == aB.mSignedPkg &&
          aA.mUserContextId == aB.mUserContextId &&
          aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
 }
 
 /* static */ bool
 ChromeUtils::IsOriginAttributesEqualIgnoringAddonId(const dom::OriginAttributesDictionary& aA,
                                                     const dom::OriginAttributesDictionary& aB)
 {
   return aA.mAppId == aB.mAppId &&
          aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
-         aA.mSignedPkg == aB.mSignedPkg &&
          aA.mUserContextId == aB.mUserContextId &&
          aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -324,40 +324,16 @@ nsFrameLoader::LoadURI(nsIURI* aURI)
   rv = doc->InitializeFrameLoader(this);
   if (NS_FAILED(rv)) {
     mURIToLoad = nullptr;
   }
   return rv;
 }
 
 NS_IMETHODIMP
-nsFrameLoader::SwitchProcessAndLoadURI(nsIURI* aURI, const nsACString& aPackageId)
-{
-  RefPtr<TabParent> tp = nullptr;
-
-  MutableTabContext context;
-  nsresult rv = GetNewTabContext(&context, aURI, aPackageId);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  nsCOMPtr<Element> ownerElement = mOwnerContent;
-  tp = ContentParent::CreateBrowserOrApp(context, ownerElement, nullptr);
-  if (!tp) {
-    return NS_ERROR_FAILURE;
-  }
-  mRemoteBrowserShown = false;
-
-  rv = SwapRemoteBrowser(tp);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
-  }
-  LoadURI(aURI);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
 nsFrameLoader::SetIsPrerendered()
 {
   MOZ_ASSERT(!mDocShell, "Please call SetIsPrerendered before docShell is created");
   mIsPrerendered = true;
 
   return NS_OK;
 }
 
@@ -3088,62 +3064,16 @@ nsFrameLoader::SetRemoteBrowser(nsITabPa
   mRemoteBrowser = TabParent::GetFrom(aTabParent);
   mChildID = mRemoteBrowser ? mRemoteBrowser->Manager()->ChildID() : 0;
   MaybeUpdatePrimaryTabParent(eTabParentChanged);
   ReallyLoadFrameScripts();
   InitializeBrowserAPI();
   ShowRemoteFrame(ScreenIntSize(0, 0));
 }
 
-nsresult
-nsFrameLoader::SwapRemoteBrowser(nsITabParent* aTabParent)
-{
-  RefPtr<TabParent> newParent = TabParent::GetFrom(aTabParent);
-  if (!newParent || !mRemoteBrowser) {
-    return NS_ERROR_DOM_INVALID_STATE_ERR;
-  }
-  if (!IsRemoteFrame()) {
-    NS_WARNING("Switching from in-process to out-of-process is not supported.");
-    return NS_ERROR_NOT_IMPLEMENTED;
-  }
-  if (!OwnerIsMozBrowserOrAppFrame()) {
-    NS_WARNING("Switching process for non-mozbrowser/app frame is not supported.");
-    return NS_ERROR_NOT_IMPLEMENTED;
-  }
-  if (newParent == mRemoteBrowser) {
-    return NS_OK;
-  }
-
-  MaybeUpdatePrimaryTabParent(eTabParentRemoved);
-  mRemoteBrowser->CacheFrameLoader(nullptr);
-  mRemoteBrowser->SetOwnerElement(nullptr);
-  mRemoteBrowser->Detach();
-  mRemoteBrowser->Destroy();
-
-  mRemoteBrowser = newParent;
-  mRemoteBrowser->Attach(this);
-  mChildID = mRemoteBrowser->Manager()->ChildID();
-
-  MaybeUpdatePrimaryTabParent(eTabParentChanged);
-
-  // Force the new remote frame manager to load pending scripts
-  mMessageManager->LoadPendingScripts();
-
-  nsCOMPtr<nsIObserverService> os = services::GetObserverService();
-  if (os) {
-    os->NotifyObservers(NS_ISUPPORTS_CAST(nsIFrameLoader*, this),
-                        "remote-browser-swapped", nullptr);
-  }
-  if (!mRemoteBrowserShown) {
-    ShowRemoteFrame(ScreenIntSize(0, 0));
-  }
-
-  return NS_OK;
-}
-
 void
 nsFrameLoader::SetDetachedSubdocFrame(nsIFrame* aDetachedFrame,
                                       nsIDocument* aContainerDoc)
 {
   mDetachedSubdocFrame = aDetachedFrame;
   mContainerDocWhileDetached = aContainerDoc;
 }
 
@@ -3457,48 +3387,37 @@ nsFrameLoader::MaybeUpdatePrimaryTabPare
                                    eIgnoreCase);
       parentTreeOwner->TabParentAdded(mRemoteBrowser, isPrimary);
     }
   }
 }
 
 nsresult
 nsFrameLoader::GetNewTabContext(MutableTabContext* aTabContext,
-                                nsIURI* aURI,
-                                const nsACString& aPackageId)
+                                nsIURI* aURI)
 {
   nsCOMPtr<mozIApplication> ownApp = GetOwnApp();
   nsCOMPtr<mozIApplication> containingApp = GetContainingApp();
   DocShellOriginAttributes attrs;
   attrs.mInIsolatedMozBrowser = OwnerIsIsolatedMozBrowserFrame();
   nsresult rv;
 
-  nsCString signedPkgOrigin;
-  if (!aPackageId.IsEmpty()) {
-    // Only when aPackageId is not empty would signed package origin
-    // be meaningful.
-    nsPrincipal::GetOriginForURI(aURI, signedPkgOrigin);
-  }
-
   // Get the AppId from ownApp
   uint32_t appId = nsIScriptSecurityManager::NO_APP_ID;
   if (ownApp) {
     rv = ownApp->GetLocalId(&appId);
     NS_ENSURE_SUCCESS(rv, rv);
     NS_ENSURE_STATE(appId != nsIScriptSecurityManager::NO_APP_ID);
   } else if (containingApp) {
     rv = containingApp->GetLocalId(&appId);
     NS_ENSURE_SUCCESS(rv, rv);
     NS_ENSURE_STATE(appId != nsIScriptSecurityManager::NO_APP_ID);
   }
   attrs.mAppId = appId;
 
-  // Populate packageId to signedPkg.
-  attrs.mSignedPkg = NS_ConvertUTF8toUTF16(aPackageId);
-
   // set the userContextId on the attrs before we pass them into
   // the tab context
   rv = PopulateUserContextIdFromAttribute(attrs);
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsAutoString presentationURLStr;
   mOwnerContent->GetAttr(kNameSpaceID_None,
                          nsGkAtoms::mozpresentation,
@@ -3527,17 +3446,16 @@ nsFrameLoader::GetNewTabContext(MutableT
   bool tabContextUpdated =
     aTabContext->SetTabContext(OwnerIsMozBrowserFrame(),
                                mIsPrerendered,
                                ownApp,
                                containingApp,
                                showAccelerators,
                                showFocusRings,
                                attrs,
-                               signedPkgOrigin,
                                presentationURLStr);
   NS_ENSURE_STATE(tabContextUpdated);
 
   return NS_OK;
 }
 
 nsresult
 nsFrameLoader::PopulateUserContextIdFromAttribute(DocShellOriginAttributes& aAttr)
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -185,18 +185,16 @@ public:
    *
    * This will assert if mRemoteBrowser is non-null.  In practice,
    * this means you can't have successfully run TryRemoteBrowser() on
    * this object, which means you can't have called ShowRemoteFrame()
    * or ReallyStartLoading().
    */
   void SetRemoteBrowser(nsITabParent* aTabParent);
 
-  nsresult SwapRemoteBrowser(nsITabParent* aTabParent);
-
   /**
    * Stashes a detached nsIFrame on the frame loader. We do this when we're
    * destroying the nsSubDocumentFrame. If the nsSubdocumentFrame is
    * being reframed we'll restore the detached nsIFrame when it's recreated,
    * otherwise we'll discard the old presentation and set the detached
    * subdoc nsIFrame to null. aContainerDoc is the document containing the
    * the subdoc frame. This enables us to detect when the containing
    * document has changed during reframe, so we can discard the presentation
@@ -322,18 +320,17 @@ private:
     return mOwnerContent->IsXULElement()
              ? nsGkAtoms::type : nsGkAtoms::mozframetype;
   }
 
   void InitializeBrowserAPI();
   void DestroyBrowserFrameScripts();
 
   nsresult GetNewTabContext(mozilla::dom::MutableTabContext* aTabContext,
-                            nsIURI* aURI = nullptr,
-                            const nsACString& aPackageId = EmptyCString());
+                            nsIURI* aURI = nullptr);
 
   enum TabParentChange {
     eTabParentRemoved,
     eTabParentChanged
   };
   void MaybeUpdatePrimaryTabParent(TabParentChange aChange);
 
   nsresult
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -46,16 +46,17 @@
 #include "nsIController.h"
 #include "nsScriptNameSpaceManager.h"
 #include "nsISlowScriptDebug.h"
 #include "nsWindowMemoryReporter.h"
 #include "WindowNamedPropertiesHandler.h"
 #include "nsFrameSelection.h"
 #include "nsNetUtil.h"
 #include "nsVariant.h"
+#include "nsPrintfCString.h"
 
 // Helper Classes
 #include "nsJSUtils.h"
 #include "jsapi.h"              // for JSAutoRequest
 #include "jswrapper.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsReadableUtils.h"
 #include "nsDOMClassInfo.h"
@@ -8531,16 +8532,45 @@ nsGlobalWindow::PostMessageMozOuter(JSCo
 
     if (NS_FAILED(originURI->SetUserPass(EmptyCString())) ||
         NS_FAILED(originURI->SetPath(EmptyCString()))) {
       return;
     }
 
     PrincipalOriginAttributes attrs =
       BasePrincipal::Cast(&aSubjectPrincipal)->OriginAttributesRef();
+    if (aSubjectPrincipal.GetIsSystemPrincipal()) {
+      auto principal = BasePrincipal::Cast(GetPrincipal());
+
+      if (attrs != principal->OriginAttributesRef()) {
+        nsCOMPtr<nsIURI> targetURI;
+        nsAutoCString targetURL;
+        nsAutoCString sourceOrigin;
+        nsAutoCString targetOrigin;
+
+        if (NS_FAILED(principal->GetURI(getter_AddRefs(targetURI))) ||
+            NS_FAILED(targetURI->GetAsciiSpec(targetURL)) ||
+            NS_FAILED(principal->GetOrigin(targetOrigin)) ||
+            NS_FAILED(aSubjectPrincipal.GetOrigin(sourceOrigin))) {
+          NS_WARNING("Failed to get source and target origins");
+          return;
+        }
+
+        nsContentUtils::LogSimpleConsoleError(
+          NS_ConvertUTF8toUTF16(nsPrintfCString(
+            "Attempting to post a message to window with url \"%s\" and "
+            "origin \"%s\" from a system principal scope with mismatched "
+            "origin \"%s\".",
+            targetURL.get(), targetOrigin.get(), sourceOrigin.get())),
+          "DOM");
+
+        attrs = principal->OriginAttributesRef();
+      }
+    }
+
     // Create a nsIPrincipal inheriting the app/browser attributes from the
     // caller.
     providedPrincipal = BasePrincipal::CreateCodebasePrincipal(originURI, attrs);
     if (NS_WARN_IF(!providedPrincipal)) {
       return;
     }
   }
 
--- a/dom/base/nsIFrameLoader.idl
+++ b/dom/base/nsIFrameLoader.idl
@@ -50,25 +50,16 @@ interface nsIFrameLoader : nsISupports
 
   /**
    * Loads the specified URI in this frame. Behaves identically to loadFrame,
    * except that this method allows specifying the URI to load.
    */
   void loadURI(in nsIURI aURI);
 
   /**
-   * Loads the specified URI in this frame but using a different process.
-   * Behaves identically to loadURI, except that this method only works
-   * with remote frame. For a signed package, we need to specifiy the
-   * package identifier.
-   * Throws an exception with non-remote frames.
-   */
-  void switchProcessAndLoadURI(in nsIURI aURI, in ACString aPackageId);
-
-  /**
    * Puts the frameloader in prerendering mode.
    */
   void setIsPrerendered();
 
   /**
    * Make the prerendered frameloader being active (and clear isPrerendered flag).
    */
   void makePrerenderedLoaderActive();
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -646,18 +646,16 @@ skip-if = toolkit == 'android' || e10s #
 [test_error.html]
 [test_EventSource_redirects.html]
 [test_explicit_user_agent.html]
 [test_file_from_blob.html]
 [test_file_negative_date.html]
 [test_fileapi.html]
 [test_fileapi_slice.html]
 skip-if = (toolkit == 'android') # Android: Bug 775227
-[test_frameLoader_switchProcess.html]
-skip-if = e10s || os != 'linux' || buildapp != 'browser' # Already tests multiprocess
 [test_getAttribute_after_createAttribute.html]
 [test_getElementById.html]
 [test_getTranslationNodes.html]
 [test_getTranslationNodes_limit.html]
 [test_gsp-qualified.html]
 [test_gsp-quirks.html]
 [test_gsp-standards.html]
 [test_history_document_open.html]
deleted file mode 100644
--- a/dom/base/test/test_frameLoader_switchProcess.html
+++ /dev/null
@@ -1,75 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<head>
-  <title>Test frameLoader SwitchProcessAndLoadURI</title>
-  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
-  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
-</head>
-
-<body>
-<script type="application/javascript;version=1.7">
-  SimpleTest.waitForExplicitFinish();
-
-  var Ci = SpecialPowers.Ci;
-  var Cc = SpecialPowers.Cc;
-
-  function expectProcessCreated() {
-    return new Promise((resolve, reject) => {
-      var topic = "process-priority-manager:TEST-ONLY:process-created";
-      function observer() {
-        SpecialPowers.removeObserver(observer, topic);
-        ok(true, "Expect process created");
-        resolve();
-      }
-      SpecialPowers.addObserver(observer, topic, /* weak = */ false);
-    });
-  }
-
-  function switchProcessAndLoadURI(iframe, url) {
-    var fl = SpecialPowers.wrap(iframe)
-                          .QueryInterface(Ci.nsIFrameLoaderOwner)
-                          .frameLoader;
-    var uri = SpecialPowers.Services.io.newURI(url, null, null);
-    fl.switchProcessAndLoadURI(uri, "");
-  }
-
-  function runTest() {
-    ok(true, "Run Test");
-    var iframe = document.createElement("iframe");
-    iframe.setAttribute("mozbrowser", "true");
-    iframe.setAttribute("remote", "true");
-    iframe.setAttribute("src", "http://example.org");
-
-    expectProcessCreated()
-    .then(() => new Promise(next => {
-      iframe.addEventListener("mozbrowserloadend", function loadend(e) {
-        iframe.removeEventListener("mozbrowserloadend", loadend);
-        ok(true, "Got mozbrowserloadend");
-        expectProcessCreated().then(next);
-        switchProcessAndLoadURI(iframe, "data:text/html,%3Cscript%3Ealert(true)%3C/script%3E");
-      });
-    }))
-    .then(() => new Promise(next => {
-      iframe.addEventListener("mozbrowsershowmodalprompt", function prompt(e) {
-        iframe.removeEventListener("mozbrowsershowmodalprompt", prompt);
-        ok(true, "Browser API still works after process switch");
-        next();
-      });
-    }))
-    .then(SimpleTest.finish);
-
-    document.body.appendChild(iframe);
-  }
-
-  SpecialPowers.pushPrefEnv(
-    { "set": [["dom.ipc.processPriorityManager.testMode", true],
-              ["dom.ipc.processPriorityManager.enabled", true],
-              ["dom.ipc.tabs.disabled", false],
-              ["dom.ipc.processCount", 3],
-              ["dom.mozBrowserFramesEnabled", true]] },
-    () => SpecialPowers.pushPermissions([
-      { "type": "browser", "allow": 1, "context": document }
-    ], runTest));
-</script>
-</body>
-</html>
--- a/dom/ipc/PTabContext.ipdlh
+++ b/dom/ipc/PTabContext.ipdlh
@@ -37,21 +37,16 @@ struct PopupIPCTabContext
 struct FrameIPCTabContext
 {
   // The originAttributes dictionary.
   DocShellOriginAttributes originAttributes;
 
   // The ID of the app containing this app/browser frame, if applicable.
   uint32_t frameOwnerAppId;
 
-  // The origin without originAttribute suffix for a signed package.
-  // This value would be empty if the TabContext doesn't own a signed
-  // package.
-  nsCString signedPkgOriginNoSuffix;
-
   // Whether this is a mozbrowser frame.  <iframe mozbrowser mozapp> and
   // <xul:browser> are not considered to be mozbrowser frames.
   bool isMozBrowserElement;
 
   // Whether this TabContext should work in prerender mode.
   bool isPrerendered;
 
   // The requested presentation URL.
--- a/dom/ipc/TabContext.cpp
+++ b/dom/ipc/TabContext.cpp
@@ -172,37 +172,30 @@ TabContext::UpdateTabContextAfterSwap(co
 {
   // This is only used after already initialized.
   MOZ_ASSERT(mInitialized);
 
   // The only permissable change is to `mIsMozBrowserElement`.  All other fields
   // must match for the change to be accepted.
   if (aContext.OwnAppId() != OwnAppId() ||
       aContext.mContainingAppId != mContainingAppId ||
-      aContext.mOriginAttributes != mOriginAttributes ||
-      aContext.mSignedPkgOriginNoSuffix != mSignedPkgOriginNoSuffix) {
+      aContext.mOriginAttributes != mOriginAttributes) {
     return false;
   }
 
   mIsMozBrowserElement = aContext.mIsMozBrowserElement;
   return true;
 }
 
 const DocShellOriginAttributes&
 TabContext::OriginAttributesRef() const
 {
   return mOriginAttributes;
 }
 
-const nsACString&
-TabContext::SignedPkgOriginNoSuffix() const
-{
-  return mSignedPkgOriginNoSuffix;
-}
-
 const nsAString&
 TabContext::PresentationURL() const
 {
   return mPresentationURL;
 }
 
 UIStateChangeType
 TabContext::ShowAccelerators() const
@@ -219,17 +212,16 @@ TabContext::ShowFocusRings() const
 bool
 TabContext::SetTabContext(bool aIsMozBrowserElement,
                           bool aIsPrerendered,
                           mozIApplication* aOwnApp,
                           mozIApplication* aAppFrameOwnerApp,
                           UIStateChangeType aShowAccelerators,
                           UIStateChangeType aShowFocusRings,
                           const DocShellOriginAttributes& aOriginAttributes,
-                          const nsACString& aSignedPkgOriginNoSuffix,
                           const nsAString& aPresentationURL)
 {
   NS_ENSURE_FALSE(mInitialized, false);
 
   // Get ids for both apps and only write to our member variables after we've
   // verified that this worked.
   uint32_t ownAppId = NO_APP_ID;
   if (aOwnApp) {
@@ -252,29 +244,27 @@ TabContext::SetTabContext(bool aIsMozBro
 
   mInitialized = true;
   mIsMozBrowserElement = aIsMozBrowserElement;
   mIsPrerendered = aIsPrerendered;
   mOriginAttributes = aOriginAttributes;
   mContainingAppId = containingAppId;
   mOwnApp = aOwnApp;
   mContainingApp = aAppFrameOwnerApp;
-  mSignedPkgOriginNoSuffix = aSignedPkgOriginNoSuffix;
   mPresentationURL = aPresentationURL;
   mShowAccelerators = aShowAccelerators;
   mShowFocusRings = aShowFocusRings;
   return true;
 }
 
 IPCTabContext
 TabContext::AsIPCTabContext() const
 {
   return IPCTabContext(FrameIPCTabContext(mOriginAttributes,
                                           mContainingAppId,
-                                          mSignedPkgOriginNoSuffix,
                                           mIsMozBrowserElement,
                                           mIsPrerendered,
                                           mPresentationURL,
                                           mShowAccelerators,
                                           mShowFocusRings));
 }
 
 static already_AddRefed<mozIApplication>
@@ -291,17 +281,16 @@ GetAppForId(uint32_t aAppId)
 
 MaybeInvalidTabContext::MaybeInvalidTabContext(const IPCTabContext& aParams)
   : mInvalidReason(nullptr)
 {
   bool isMozBrowserElement = false;
   bool isPrerendered = false;
   uint32_t containingAppId = NO_APP_ID;
   DocShellOriginAttributes originAttributes;
-  nsAutoCString signedPkgOriginNoSuffix;
   nsAutoString presentationURL;
   UIStateChangeType showAccelerators = UIStateChangeType_NoChange;
   UIStateChangeType showFocusRings = UIStateChangeType_NoChange;
 
   switch(aParams.type()) {
     case IPCTabContext::TPopupIPCTabContext: {
       const PopupIPCTabContext &ipcContext = aParams.get_PopupIPCTabContext();
 
@@ -355,17 +344,16 @@ MaybeInvalidTabContext::MaybeInvalidTabC
     }
     case IPCTabContext::TFrameIPCTabContext: {
       const FrameIPCTabContext &ipcContext =
         aParams.get_FrameIPCTabContext();
 
       isMozBrowserElement = ipcContext.isMozBrowserElement();
       isPrerendered = ipcContext.isPrerendered();
       containingAppId = ipcContext.frameOwnerAppId();
-      signedPkgOriginNoSuffix = ipcContext.signedPkgOriginNoSuffix();
       presentationURL = ipcContext.presentationURL();
       showAccelerators = ipcContext.showAccelerators();
       showFocusRings = ipcContext.showFocusRings();
       originAttributes = ipcContext.originAttributes();
       break;
     }
     case IPCTabContext::TUnsafeIPCTabContext: {
       // XXXcatalinb: This used *only* by ServiceWorkerClients::OpenWindow.
@@ -409,17 +397,16 @@ MaybeInvalidTabContext::MaybeInvalidTabC
   bool rv;
   rv = mTabContext.SetTabContext(isMozBrowserElement,
                                  isPrerendered,
                                  ownApp,
                                  containingApp,
                                  showAccelerators,
                                  showFocusRings,
                                  originAttributes,
-                                 signedPkgOriginNoSuffix,
                                  presentationURL);
   if (!rv) {
     mInvalidReason = "Couldn't initialize TabContext.";
   }
 }
 
 bool
 MaybeInvalidTabContext::IsValid()
--- a/dom/ipc/TabContext.h
+++ b/dom/ipc/TabContext.h
@@ -122,22 +122,16 @@ public:
   /**
    * OriginAttributesRef() returns the DocShellOriginAttributes of this frame to
    * the caller. This is used to store any attribute associated with the frame's
    * docshell, such as the AppId.
    */
   const DocShellOriginAttributes& OriginAttributesRef() const;
 
   /**
-   * Returns the origin associated with the tab (w/o suffix) if this tab owns
-   * a signed packaged content.
-   */
-  const nsACString& SignedPkgOriginNoSuffix() const;
-
-  /**
    * Returns the presentation URL associated with the tab if this tab is
    * created for presented content
    */
   const nsAString& PresentationURL() const;
 
   UIStateChangeType ShowAccelerators() const;
   UIStateChangeType ShowFocusRings() const;
 
@@ -172,17 +166,16 @@ protected:
    */
   bool SetTabContext(bool aIsMozBrowserElement,
                      bool aIsPrerendered,
                      mozIApplication* aOwnApp,
                      mozIApplication* aAppFrameOwnerApp,
                      UIStateChangeType aShowAccelerators,
                      UIStateChangeType aShowFocusRings,
                      const DocShellOriginAttributes& aOriginAttributes,
-                     const nsACString& aSignedPkgOriginNoSuffix,
                      const nsAString& aPresentationURL);
 
   /**
    * Modify this TabContext to match the given TabContext.  This is a special
    * case triggered by nsFrameLoader::SwapWithOtherRemoteLoader which may have
    * caused the owner content to change.
    *
    * This special case only allows the field `mIsMozBrowserElement` to be
@@ -229,24 +222,16 @@ private:
   uint32_t mContainingAppId;
 
   /**
    * DocShellOriginAttributes of the top level tab docShell
    */
   DocShellOriginAttributes mOriginAttributes;
 
   /**
-   * The signed package origin without suffix. Since the signed packaged
-   * web content is always loaded in a separate process, it makes sense
-   * that we store this immutable value in TabContext. If the TabContext
-   * doesn't own a signed package, this value would be empty.
-   */
-  nsCString mSignedPkgOriginNoSuffix;
-
-  /**
    * The requested presentation URL.
    */
   nsString mPresentationURL;
 
   /**
    * Keyboard indicator state (focus rings, accelerators).
    */
   UIStateChangeType mShowAccelerators;
@@ -269,27 +254,25 @@ public:
   bool
   SetTabContext(bool aIsMozBrowserElement,
                 bool aIsPrerendered,
                 mozIApplication* aOwnApp,
                 mozIApplication* aAppFrameOwnerApp,
                 UIStateChangeType aShowAccelerators,
                 UIStateChangeType aShowFocusRings,
                 const DocShellOriginAttributes& aOriginAttributes,
-                const nsACString& aSignedPkgOriginNoSuffix = EmptyCString(),
                 const nsAString& aPresentationURL = EmptyString())
   {
     return TabContext::SetTabContext(aIsMozBrowserElement,
                                      aIsPrerendered,
                                      aOwnApp,
                                      aAppFrameOwnerApp,
                                      aShowAccelerators,
                                      aShowFocusRings,
                                      aOriginAttributes,
-                                     aSignedPkgOriginNoSuffix,
                                      aPresentationURL);
   }
 };
 
 /**
  * MaybeInvalidTabContext is a simple class that lets you transform an
  * IPCTabContext into a TabContext.
  *
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -146,17 +146,16 @@ TabParent::TabParent(nsIContentParent* a
   , mRounding(0)
   , mDefaultScale(0)
   , mUpdatedDimensions(false)
   , mSizeMode(nsSizeMode_Normal)
   , mManager(aManager)
   , mDocShellIsActive(false)
   , mMarkedDestroying(false)
   , mIsDestroyed(false)
-  , mIsDetached(true)
   , mChromeFlags(aChromeFlags)
   , mDragValid(false)
   , mInitedByParent(false)
   , mTabId(aTabId)
   , mCreatingWindow(false)
   , mCursor(nsCursor(-1))
   , mTabSetsCursor(false)
   , mHasContentOpener(false)
@@ -403,45 +402,16 @@ TabParent::Destroy()
     ContentParent::NotifyTabDestroying(this->GetTabId(), Manager()->AsContentParent()->ChildID());
   } else {
     ContentParent::NotifyTabDestroying(this->GetTabId(), Manager()->ChildID());
   }
 
   mMarkedDestroying = true;
 }
 
-void
-TabParent::Detach()
-{
-  if (mIsDetached) {
-    return;
-  }
-  RemoveWindowListeners();
-  if (RenderFrameParent* frame = GetRenderFrame()) {
-    RemoveTabParentFromTable(frame->GetLayersId());
-  }
-  mIsDetached = true;
-}
-
-void
-TabParent::Attach(nsFrameLoader* aFrameLoader)
-{
-  MOZ_ASSERT(mIsDetached);
-  if (!mIsDetached) {
-    return;
-  }
-  Element* ownerElement = aFrameLoader->GetOwnerContent();
-  SetOwnerElement(ownerElement);
-  if (RenderFrameParent* frame = GetRenderFrame()) {
-    AddTabParentToTable(frame->GetLayersId(), this);
-    frame->OwnerContentChanged(ownerElement);
-  }
-  mIsDetached = false;
-}
-
 bool
 TabParent::RecvEnsureLayersConnected()
 {
   if (RenderFrameParent* frame = GetRenderFrame()) {
     frame->EnsureLayersConnected();
   }
   return true;
 }
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -147,20 +147,16 @@ public:
   already_AddRefed<nsILoadContext> GetLoadContext();
 
   already_AddRefed<nsIWidget> GetTopLevelWidget();
 
   nsIXULBrowserWindow* GetXULBrowserWindow();
 
   void Destroy();
 
-  void Detach();
-
-  void Attach(nsFrameLoader* aFrameLoader);
-
   void RemoveWindowListeners();
 
   void AddWindowListeners();
 
   void DidRefresh() override;
 
   virtual bool RecvMoveFocus(const bool& aForward,
                              const bool& aForDocumentNavigation) override;
@@ -676,18 +672,16 @@ private:
   void ApzAwareEventRoutingToChild(ScrollableLayerGuid* aOutTargetGuid,
                                    uint64_t* aOutInputBlockId,
                                    nsEventStatus* aOutApzResponse);
 
   // When true, we've initiated normal shutdown and notified our managing PContent.
   bool mMarkedDestroying;
   // When true, the TabParent is invalid and we should not send IPC messages anymore.
   bool mIsDestroyed;
-  // When true, the TabParent is detached from the frame loader.
-  bool mIsDetached;
 
   uint32_t mChromeFlags;
 
   nsTArray<nsTArray<IPCDataTransferItem>> mInitialDataTransferItems;
 
   RefPtr<gfx::DataSourceSurface> mDnDVisualization;
   bool mDragValid;
   LayoutDeviceIntRect mDragRect;
--- a/dom/media/mediasource/test/test_BufferingWait.html
+++ b/dom/media/mediasource/test/test_BufferingWait.html
@@ -15,46 +15,33 @@ var receivedSourceOpen = false;
 runWithMSE(function(ms, v) {
   ms.addEventListener("sourceopen", function() {
     ok(true, "Receive a sourceopen event");
     ok(!receivedSourceOpen, "Should only receive one sourceopen for this test");
     receivedSourceOpen = true;
     var sb = ms.addSourceBuffer("video/webm");
     ok(sb, "Create a SourceBuffer");
 
-    function waitUntilTime(targetTime) {
-      return new Promise(function(resolve, reject) {
-        v.addEventListener("waiting", function onwaiting() {
-          info("Got a waiting event at " + v.currentTime);
-          if (v.currentTime >= targetTime) {
-            ok(true, "Reached target time of: " + targetTime);
-            v.removeEventListener("waiting", onwaiting);
-            resolve();
-          }
-        });
-      });
-    }
-
     fetchWithXHR("seek.webm", function(arrayBuffer) {
       sb.addEventListener('error', (e) => { ok(false, "Got Error: " + e); SimpleTest.finish(); });
       loadSegment.bind(null, sb, new Uint8Array(arrayBuffer, 0, 318))().then(
       loadSegment.bind(null, sb, new Uint8Array(arrayBuffer, 318, 25523-318))).then(
       loadSegment.bind(null, sb, new Uint8Array(arrayBuffer, 25523, 46712-25523))).then(
       /* Note - Missing |46712, 67833 - 46712| segment here corresponding to (0.8, 1.2] */
       /* Note - Missing |67833, 88966 - 67833| segment here corresponding to (1.2, 1.6]  */
       loadSegment.bind(null, sb, new Uint8Array(arrayBuffer, 88966))).then(function() {
         // 0.767 is the time of the last video sample +- 40ms.
-        var promise = waitUntilTime(.767-0.04);
+        var promise = waitUntilTime(v, .767-0.04);
         info("Playing video. It should play for a bit, then fire 'waiting'");
         v.play();
         return promise;
       }).then(function() {
         window.firstStop = Date.now();
         loadSegment(sb, new Uint8Array(arrayBuffer, 46712, 67833 - 46712));
-        return waitUntilTime(1.167-0.04);
+        return waitUntilTime(v, 1.167-0.04);
       }).then(function() {
         var waitDuration = (Date.now() - window.firstStop) / 1000;
         ok(waitDuration < 15, "Should not spend an inordinate amount of time buffering: " + waitDuration);
         SimpleTest.finish();
         /* If we allow the rest of the stream to be played, we get stuck at
            around 2s. See bug 1093133.
         once(v, 'ended', SimpleTest.finish.bind(SimpleTest));
         return loadSegment(sb, new Uint8Array(arrayBuffer, 67833, 88966 - 67833));
--- a/dom/media/mediasource/test/test_BufferingWait_mp4.html
+++ b/dom/media/mediasource/test/test_BufferingWait_mp4.html
@@ -15,47 +15,34 @@ var receivedSourceOpen = false;
 runWithMSE(function(ms, v) {
   ms.addEventListener("sourceopen", function() {
     ok(true, "Receive a sourceopen event");
     ok(!receivedSourceOpen, "Should only receive one sourceopen for this test");
     receivedSourceOpen = true;
     var sb = ms.addSourceBuffer("video/mp4");
     ok(sb, "Create a SourceBuffer");
 
-    function waitUntilTime(targetTime) {
-      return new Promise(function(resolve, reject) {
-        v.addEventListener("waiting", function onwaiting() {
-          info("Got a waiting event at " + v.currentTime);
-          if (v.currentTime >= targetTime) {
-            ok(true, "Reached target time of: " + targetTime);
-            v.removeEventListener("waiting", onwaiting);
-            resolve();
-          }
-        });
-      });
-    }
-
     sb.addEventListener('error', (e) => { ok(false, "Got Error: " + e); SimpleTest.finish(); });
     fetchAndLoad(sb, 'bipbop/bipbop', ['init'], '.mp4')
     .then(fetchAndLoad.bind(null, sb, 'bipbop/bipbop', ['1'], '.m4s'))
     .then(fetchAndLoad.bind(null, sb, 'bipbop/bipbop', ['2'], '.m4s'))
     /* Note - Missing |bipbop3| segment here corresponding to (1.62, 2.41] */
     /* Note - Missing |bipbop4| segment here corresponding to (2.41, 3.20]  */
     .then(fetchAndLoad.bind(null, sb, 'bipbop/bipbop', ['5'], '.m4s'))
     .then(function() {
         // last audio sample has a start time of 1.578956s
-        var promise = waitUntilTime(1.57895);
+        var promise = waitUntilTime(v, 1.57895);
         info("Playing video. It should play for a bit, then fire 'waiting'");
         v.play();
         return promise;
       }).then(function() {
         window.firstStop = Date.now();
         fetchAndLoad(sb, 'bipbop/bipbop', ['3'], '.m4s');
         // last audio sample has a start time of 2.368435
-        return waitUntilTime(2.36843);
+        return waitUntilTime(v, 2.36843);
       }).then(function() {
         var waitDuration = (Date.now() - window.firstStop) / 1000;
         ok(waitDuration < 15, "Should not spend an inordinate amount of time buffering: " + waitDuration);
         once(v, 'ended', SimpleTest.finish.bind(SimpleTest));
         return fetchAndLoad(sb, 'bipbop/bipbop', ['4'], '.m4s');
       }).then(function() {
         ms.endOfStream();
       });;
--- a/dom/webidl/ChromeUtils.webidl
+++ b/dom/webidl/ChromeUtils.webidl
@@ -74,21 +74,19 @@ interface ChromeUtils : ThreadSafeChrome
  *     serialization, deserialization, and inheritance.
  * (3) Update the methods on mozilla::OriginAttributesPattern, including matching.
  */
 dictionary OriginAttributesDictionary {
   unsigned long appId = 0;
   unsigned long userContextId = 0;
   boolean inIsolatedMozBrowser = false;
   DOMString addonId = "";
-  DOMString signedPkg = "";
   unsigned long privateBrowsingId = 0;
   DOMString firstPartyDomain = "";
 };
 dictionary OriginAttributesPatternDictionary {
   unsigned long appId;
   unsigned long userContextId;
   boolean inIsolatedMozBrowser;
   DOMString addonId;
-  DOMString signedPkg;
   unsigned long privateBrowsingId;
   DOMString firstPartyDomain;
 };
--- a/js/src/jit/MIR.cpp
+++ b/js/src/jit/MIR.cpp
@@ -2794,21 +2794,20 @@ MBinaryBitwiseInstruction::foldUnnecessa
 {
     if (specialization_ != MIRType::Int32)
         return this;
 
     // Fold unsigned shift right operator when the second operand is zero and
     // the only use is an unsigned modulo. Thus, the expression
     // |(x >>> 0) % y| becomes |x % y|.
     if (isUrsh() && hasOneDefUse() && IsUint32Type(this)) {
-        for (MUseDefIterator use(this); use; use++) {
-            if (use.def()->isMod() && use.def()->toMod()->isUnsigned())
-                return getOperand(0);
-            break;
-        }
+        MUseDefIterator use(this);
+        if (use.def()->isMod() && use.def()->toMod()->isUnsigned())
+            return getOperand(0);
+        MOZ_ASSERT(!(++use));
     }
 
     // Eliminate bitwise operations that are no-ops when used on integer
     // inputs, such as (x | 0).
 
     MDefinition* lhs = getOperand(0);
     MDefinition* rhs = getOperand(1);
 
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -136,17 +136,21 @@ ServoRestyleManager::RecreateStyleContex
     // Add the new change hint to the list of elements to process if
     // we need to do any work.
     if (changeHint) {
       aChangeListToProcess.AppendChange(primaryFrame, element, changeHint);
     }
 
     // The frame reconstruction step (if needed) will ask for the descendants'
     // style correctly. If not needed, we're done too.
-    if (!primaryFrame) {
+    //
+    // Note that we must leave the old style on an existing frame that is
+    // about to be reframed, since some frame constructor code wants to
+    // inspect the old style to work out what to do.
+    if (!primaryFrame || (changeHint & nsChangeHint_ReconstructFrame)) {
       aContent->UnsetIsDirtyForServo();
       return;
     }
 
     // Hold the old style context alive, because it could become a dangling
     // pointer during the replacement. In practice it's not a huge deal (on
     // GetNextContinuationWithSameStyle the pointer is not dereferenced, only
     // compared), but better not playing with dangling pointers if not needed.
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1315632-1-ref.html
@@ -0,0 +1,5 @@
+<!DOCTYPE html>
+<style>
+.y { color: green; }
+</style>
+<div class=y>hello</div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/bugs/1315632-1.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<style>
+.x { position: absolute; color: red; }
+.y { color: green; }
+</style>
+<div class=x>hello</div>
+<script>
+document.body.offsetHeight;
+document.querySelector(".x").className = "y";
+document.body.offsetHeight;
+</script>
--- a/layout/reftests/bugs/reftest-stylo.list
+++ b/layout/reftests/bugs/reftest-stylo.list
@@ -2367,13 +2367,14 @@ random-if(!winWidget) == 1273154-1.html 
 # depends on Windows font
 random-if(!winWidget) == 1273154-2.html 1273154-2.html
 # depends on Windows font
 == 1274368-1.html 1274368-1.html
 == 1276161-1a.html 1276161-1a.html
 == 1276161-1b.html 1276161-1b.html
 == 1276161-1a.html 1276161-1a.html
 == 1275411-1.html 1275411-1.html
+== 1315632-1.html 1315632-1-ref.html
 
 HTTP == 652991-1a.html 652991-1a.html
 HTTP == 652991-1b.html 652991-1b.html
 HTTP == 652991-2.html 652991-2.html
 HTTP == 652991-3.html 652991-3.html
--- a/layout/reftests/bugs/reftest.list
+++ b/layout/reftests/bugs/reftest.list
@@ -1968,14 +1968,15 @@ random-if(!winWidget) == 1273154-2.html 
 == 1275411-1.html 1275411-1-ref.html
 == 1288255.html 1288255-ref.html
 fuzzy(8,1900) == 1291528.html 1291528-ref.html
 # Buttons in 2 pages have different position and the rendering result can be
 # different, but they should use the same button style and the background color
 # should be same.  |fuzzy()| here allows the difference in border, but not
 # background color.
 fuzzy(255,1000) skip-if(!cocoaWidget) == 1294102-1.html 1294102-1-ref.html
+== 1315632-1.html 1315632-1-ref.html
 
 HTTP == 652991-1a.html 652991-1-ref.html
 HTTP == 652991-1b.html 652991-1-ref.html
 HTTP == 652991-2.html 652991-2-ref.html
 HTTP == 652991-3.html 652991-3-ref.html
 HTTP == 652991-4.html 652991-4-ref.html
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -6,25 +6,23 @@
 package org.mozilla.gecko;
 
 import android.Manifest;
 import android.annotation.TargetApi;
 import android.app.DownloadManager;
 import android.content.ContentProviderClient;
 import android.os.Environment;
 import android.os.Process;
-import android.support.annotation.CheckResult;
 import android.support.annotation.NonNull;
 
 import android.graphics.Rect;
 
 import org.json.JSONArray;
 import org.mozilla.gecko.activitystream.ActivityStream;
-import org.mozilla.gecko.adjust.AdjustHelperInterface;
-import org.mozilla.gecko.adjust.AttributionHelperListener;
+import org.mozilla.gecko.adjust.AdjustBrowserAppDelegate;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.AppConstants.Versions;
 import org.mozilla.gecko.DynamicToolbar.VisibilityTransition;
 import org.mozilla.gecko.Tabs.TabEvents;
 import org.mozilla.gecko.animation.PropertyAnimator;
 import org.mozilla.gecko.animation.ViewHelper;
 import org.mozilla.gecko.cleanup.FileCleanupController;
 import org.mozilla.gecko.db.BrowserContract;
@@ -317,24 +315,25 @@ public class BrowserApp extends GeckoApp
     // race by determining if the web content should be hidden at the animation's end.
     private boolean mHideWebContentOnAnimationEnd;
 
     private final DynamicToolbar mDynamicToolbar = new DynamicToolbar();
 
     private final TelemetryCorePingDelegate mTelemetryCorePingDelegate = new TelemetryCorePingDelegate();
 
     private final List<BrowserAppDelegate> delegates = Collections.unmodifiableList(Arrays.asList(
-            (BrowserAppDelegate) new AddToHomeScreenPromotion(),
-            (BrowserAppDelegate) new ScreenshotDelegate(),
-            (BrowserAppDelegate) new BookmarkStateChangeDelegate(),
-            (BrowserAppDelegate) new ReaderViewBookmarkPromotion(),
-            (BrowserAppDelegate) new ContentNotificationsDelegate(),
-            (BrowserAppDelegate) new PostUpdateHandler(),
+            new AddToHomeScreenPromotion(),
+            new ScreenshotDelegate(),
+            new BookmarkStateChangeDelegate(),
+            new ReaderViewBookmarkPromotion(),
+            new ContentNotificationsDelegate(),
+            new PostUpdateHandler(),
             mTelemetryCorePingDelegate,
-            new OfflineTabStatusDelegate()
+            new OfflineTabStatusDelegate(),
+            new AdjustBrowserAppDelegate(mTelemetryCorePingDelegate)
     ));
 
     @NonNull
     private SearchEngineManager mSearchEngineManager; // Contains reference to Context - DO NOT LEAK!
 
     private boolean mHasResumed;
 
     @Override
@@ -598,17 +597,17 @@ public class BrowserApp extends GeckoApp
     public void onCreate(Bundle savedInstanceState) {
         if (!HardwareUtils.isSupportedSystem()) {
             // This build does not support the Android version of the device; Exit early.
             super.onCreate(savedInstanceState);
             return;
         }
 
         final SafeIntent intent = new SafeIntent(getIntent());
-        final boolean isInAutomation = getIsInAutomationFromEnvironment(intent);
+        final boolean isInAutomation = IntentUtils.getIsInAutomationFromEnvironment(intent);
 
         // This has to be prepared prior to calling GeckoApp.onCreate, because
         // widget code and BrowserToolbar need it, and they're created by the
         // layout, which GeckoApp takes care of.
         ((GeckoApplication) getApplication()).prepareLightweightTheme();
 
         super.onCreate(savedInstanceState);
 
@@ -767,18 +766,16 @@ public class BrowserApp extends GeckoApp
         final BrowserDB db = BrowserDB.from(profile);
         db.setSuggestedSites(suggestedSites);
 
         JavaAddonManager.getInstance().init(appContext);
         mSharedPreferencesHelper = new SharedPreferencesHelper(appContext);
         mReadingListHelper = new ReadingListHelper(appContext, profile);
         mAccountsHelper = new AccountsHelper(appContext, profile);
 
-        initAdjustSDK(this, isInAutomation, mTelemetryCorePingDelegate);
-
         if (AppConstants.MOZ_ANDROID_BEAM) {
             NfcAdapter nfc = NfcAdapter.getDefaultAdapter(this);
             if (nfc != null) {
                 nfc.setNdefPushMessageCallback(new NfcAdapter.CreateNdefMessageCallback() {
                     @Override
                     public NdefMessage createNdefMessage(NfcEvent event) {
                         Tab tab = Tabs.getInstance().getSelectedTab();
                         if (tab == null || tab.isPrivate()) {
@@ -826,32 +823,16 @@ public class BrowserApp extends GeckoApp
         }
 
         // We want to get an understanding of how our user base is spread (bug 1221646).
         final String installerPackageName = getPackageManager().getInstallerPackageName(getPackageName());
         Telemetry.sendUIEvent(TelemetryContract.Event.LAUNCH, TelemetryContract.Method.SYSTEM, "installer_" + installerPackageName);
     }
 
     /**
-     * Gets whether or not we're in automation from the passed in environment variables.
-     *
-     * We need to read environment variables from the intent string
-     * extra because environment variables from our test harness aren't set
-     * until Gecko is loaded, and we need to know this before then.
-     *
-     * The return value of this method should be used early since other
-     * initialization may depend on its results.
-     */
-    @CheckResult
-    private boolean getIsInAutomationFromEnvironment(final SafeIntent intent) {
-        final HashMap<String, String> envVars = IntentUtils.getEnvVarMap(intent);
-        return !TextUtils.isEmpty(envVars.get(IntentUtils.ENV_VAR_IN_AUTOMATION));
-    }
-
-    /**
      * Initializes the default Switchboard URLs the first time.
      * @param intent
      */
     private static void initSwitchboard(final Context context, final SafeIntent intent, final boolean isInAutomation) {
         if (isInAutomation) {
             Log.d(LOGTAG, "Switchboard disabled - in automation");
             return;
         } else if (!AppConstants.MOZ_SWITCHBOARD) {
@@ -863,29 +844,16 @@ public class BrowserApp extends GeckoApp
         final String serverUrl = TextUtils.isEmpty(serverExtra) ? SWITCHBOARD_SERVER : serverExtra;
         new AsyncConfigLoader(context, serverUrl).execute();
     }
 
     private static void initTelemetryUploader(final boolean isInAutomation) {
         TelemetryUploadService.setDisabled(isInAutomation);
     }
 
-    private static void initAdjustSDK(final Context context, final boolean isInAutomation, final AttributionHelperListener listener) {
-        final AdjustHelperInterface adjustHelper = AdjustConstants.getAdjustHelper();
-        adjustHelper.onCreate(context, AdjustConstants.MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN, listener);
-
-        // Adjust stores enabled state so this is only necessary because users may have set
-        // their data preferences before this feature was implemented and we need to respect
-        // those before upload can occur in Adjust.onResume.
-        final SharedPreferences prefs = GeckoSharedPrefs.forApp(context);
-        final boolean enabled = !isInAutomation &&
-                prefs.getBoolean(GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true);
-        adjustHelper.setEnabled(enabled);
-    }
-
     private void showUpdaterPermissionSnackbar() {
         SnackbarBuilder.SnackbarCallback allowCallback = new SnackbarBuilder.SnackbarCallback() {
             @Override
             public void onClick(View v) {
                 Permissions.from(BrowserApp.this)
                         .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                         .run();
             }
@@ -1086,19 +1054,16 @@ public class BrowserApp extends GeckoApp
 
     @Override
     public void onResume() {
         super.onResume();
         if (mIsAbortingAppLaunch) {
             return;
         }
 
-        // Needed for Adjust to get accurate session measurements
-        AdjustConstants.getAdjustHelper().onResume();
-
         if (!mHasResumed) {
             EventDispatcher.getInstance().unregisterGeckoThreadListener((GeckoEventListener) this,
                     "Prompt:ShowTop");
             mHasResumed = true;
         }
 
         processTabQueue();
 
@@ -1109,19 +1074,16 @@ public class BrowserApp extends GeckoApp
 
     @Override
     public void onPause() {
         super.onPause();
         if (mIsAbortingAppLaunch) {
             return;
         }
 
-        // Needed for Adjust to get accurate session measurements
-        AdjustConstants.getAdjustHelper().onPause();
-
         if (mHasResumed) {
             // Register for Prompt:ShowTop so we can foreground this activity even if it's hidden.
             EventDispatcher.getInstance().registerGeckoThreadListener((GeckoEventListener) this,
                 "Prompt:ShowTop");
             mHasResumed = false;
         }
 
         for (BrowserAppDelegate delegate : delegates) {
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/adjust/AdjustBrowserAppDelegate.java
@@ -0,0 +1,52 @@
+package org.mozilla.gecko.adjust;
+
+import android.content.SharedPreferences;
+import android.os.Bundle;
+
+import org.mozilla.gecko.AdjustConstants;
+import org.mozilla.gecko.BrowserApp;
+import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.delegates.BrowserAppDelegate;
+import org.mozilla.gecko.mozglue.SafeIntent;
+import org.mozilla.gecko.preferences.GeckoPreferences;
+import org.mozilla.gecko.util.IntentUtils;
+
+public class AdjustBrowserAppDelegate extends BrowserAppDelegate {
+    private final AdjustHelperInterface adjustHelper;
+    private final AttributionHelperListener attributionHelperListener;
+
+    public AdjustBrowserAppDelegate(AttributionHelperListener attributionHelperListener) {
+        this.adjustHelper = AdjustConstants.getAdjustHelper();
+        this.attributionHelperListener = attributionHelperListener;
+    }
+
+    @Override
+    public void onCreate(BrowserApp browserApp, Bundle savedInstanceState) {
+        adjustHelper.onCreate(browserApp,
+                AdjustConstants.MOZ_INSTALL_TRACKING_ADJUST_SDK_APP_TOKEN,
+                attributionHelperListener);
+
+        final boolean isInAutomation = IntentUtils.getIsInAutomationFromEnvironment(
+                new SafeIntent(browserApp.getIntent()));
+
+        final SharedPreferences prefs = GeckoSharedPrefs.forApp(browserApp);
+
+        // Adjust stores enabled state so this is only necessary because users may have set
+        // their data preferences before this feature was implemented and we need to respect
+        // those before upload can occur in Adjust.onResume.
+        adjustHelper.setEnabled(!isInAutomation
+                && prefs.getBoolean(GeckoPreferences.PREFS_HEALTHREPORT_UPLOAD_ENABLED, true));
+    }
+
+    @Override
+    public void onResume(BrowserApp browserApp) {
+        // Needed for Adjust to get accurate session measurements
+        adjustHelper.onResume();
+    }
+
+    @Override
+    public void onPause(BrowserApp browserApp) {
+        // Needed for Adjust to get accurate session measurements
+        adjustHelper.onPause();
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/ActivityStreamContextMenu.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/activitystream/menu/ActivityStreamContextMenu.java
@@ -240,16 +240,17 @@ public class ActivityStreamContextMenu
                 ThreadUtils.postToBackgroundThread(new Runnable() {
                     @Override
                     public void run() {
                         BrowserDB.from(context)
                                 .removeHistoryEntry(context.getContentResolver(),
                                         url);
                     }
                 });
+                break;
 
             default:
                 throw new IllegalArgumentException("Menu item with ID=" + item.getItemId() + " not handled");
         }
 
         dismiss();
         return true;
     }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -310,16 +310,17 @@ gbjar = add_java_jar('gecko-browser')
 gbjar.sources += ['java/org/mozilla/gecko/' + x for x in [
     'AboutPages.java',
     'AccountsHelper.java',
     'ActionBarTextSelection.java',
     'ActionModeCompat.java',
     'ActionModeCompatView.java',
     'ActivityHandlerHelper.java',
     'activitystream/ActivityStream.java',
+    'adjust/AdjustBrowserAppDelegate.java',
     'animation/AnimationUtils.java',
     'animation/HeightChangeAnimation.java',
     'animation/PropertyAnimator.java',
     'animation/Rotate3DAnimation.java',
     'animation/ViewHelper.java',
     'ANRReporter.java',
     'BootReceiver.java',
     'BrowserApp.java',
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/IntentUtils.java
@@ -3,17 +3,20 @@
  * 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.gecko.util;
 
 import android.content.Intent;
 import android.os.Bundle;
+import android.support.annotation.CheckResult;
 import android.support.annotation.NonNull;
+import android.text.TextUtils;
+
 import org.mozilla.gecko.mozglue.SafeIntent;
 
 import java.util.HashMap;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 /**
  * Utilities for Intents.
@@ -82,9 +85,25 @@ public class IntentUtils {
 
     public static String getStringExtraSafe(final Intent intent, final String name) {
         return new SafeIntent(intent).getStringExtra(name);
     }
 
     public static boolean getBooleanExtraSafe(final Intent intent, final String name, final boolean defaultValue) {
         return new SafeIntent(intent).getBooleanExtra(name, defaultValue);
     }
+
+    /**
+     * Gets whether or not we're in automation from the passed in environment variables.
+     *
+     * We need to read environment variables from the intent string
+     * extra because environment variables from our test harness aren't set
+     * until Gecko is loaded, and we need to know this before then.
+     *
+     * The return value of this method should be used early since other
+     * initialization may depend on its results.
+     */
+    @CheckResult
+    public static boolean getIsInAutomationFromEnvironment(final SafeIntent intent) {
+        final HashMap<String, String> envVars = IntentUtils.getEnvVarMap(intent);
+        return !TextUtils.isEmpty(envVars.get(IntentUtils.ENV_VAR_IN_AUTOMATION));
+    }
 }
--- a/mozglue/build/WindowsDllBlocklist.cpp
+++ b/mozglue/build/WindowsDllBlocklist.cpp
@@ -217,16 +217,19 @@ static DllBlockInfo sWindowsDllBlocklist
   { "prls.dll", ALL_VERSIONS },
   { "prls64.dll", ALL_VERSIONS },
   { "rlls.dll", ALL_VERSIONS },
   { "rlls64.dll", ALL_VERSIONS },
 
   // Vorbis DirectShow filters, bug 1239690.
   { "vorbis.acm", MAKE_VERSION(0, 0, 3, 6) },
 
+  // AhnLab Internet Security, bug 1311969
+  { "nzbrcom.dll", ALL_VERSIONS },
+
   { 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/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -186,33 +186,27 @@ NeckoParent::GetValidatedAppInfo(const S
     }
     // We may get appID=NO_APP if child frame is neither a browser nor an app
     if (appId == NECKO_NO_APP_ID && tabContext.HasOwnApp()) {
       // NECKO_NO_APP_ID but also is an app?  Weird, skip.
       debugString.Append("h,");
       continue;
     }
 
-    if (!aSerialized.mOriginAttributes.mSignedPkg.IsEmpty() &&
-        aSerialized.mOriginAttributes.mSignedPkg != tabContext.OriginAttributesRef().mSignedPkg) {
-      debugString.Append("s,");
-      continue;
-    }
     if (aSerialized.mOriginAttributes.mUserContextId != tabContext.OriginAttributesRef().mUserContextId) {
       debugString.Append("(");
       debugString.AppendInt(aSerialized.mOriginAttributes.mUserContextId);
       debugString.Append(",");
       debugString.AppendInt(tabContext.OriginAttributesRef().mUserContextId);
       debugString.Append(")");
       continue;
     }
     aAttrs = DocShellOriginAttributes();
     aAttrs.mAppId = appId;
     aAttrs.mInIsolatedMozBrowser = inBrowserElement;
-    aAttrs.mSignedPkg = aSerialized.mOriginAttributes.mSignedPkg;
     aAttrs.mUserContextId = aSerialized.mOriginAttributes.mUserContextId;
     aAttrs.mPrivateBrowsingId = aSerialized.mOriginAttributes.mPrivateBrowsingId;
     aAttrs.mFirstPartyDomain = aSerialized.mOriginAttributes.mFirstPartyDomain;
 
     return nullptr;
   }
 
   // This may be a ServiceWorker: when a push notification is received, FF wakes
--- a/testing/marionette/accessibility.js
+++ b/testing/marionette/accessibility.js
@@ -4,44 +4,57 @@
 
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Log.jsm");
 
+const logger = Log.repository.getLogger("Marionette");
+
 Cu.import("chrome://marionette/content/error.js");
 
 XPCOMUtils.defineLazyModuleGetter(
     this, "setInterval", "resource://gre/modules/Timer.jsm");
 XPCOMUtils.defineLazyModuleGetter(
     this, "clearInterval", "resource://gre/modules/Timer.jsm");
 
-XPCOMUtils.defineLazyGetter(this, "service",
-    () => Cc["@mozilla.org/accessibilityService;1"].getService(Ci.nsIAccessibilityService));
+XPCOMUtils.defineLazyGetter(this, "service", () => {
+  let service;
+  try {
+    service = Cc["@mozilla.org/accessibilityService;1"].getService(
+      Ci.nsIAccessibilityService);
+  } catch (e) {
+    logger.warn("Accessibility module is not present");
+  } finally {
+    return service;
+  }
+});
 
 this.EXPORTED_SYMBOLS = ["accessibility"];
 
-const logger = Log.repository.getLogger("Marionette");
-
 /**
  * Number of attempts to get an accessible object for an element.
  * We attempt more than once because accessible tree can be out of sync
  * with the DOM tree for a short period of time.
  */
 const GET_ACCESSIBLE_ATTEMPTS = 100;
 
 /**
  * An interval between attempts to retrieve an accessible object for an
  * element.
  */
 const GET_ACCESSIBLE_ATTEMPT_INTERVAL = 10;
 
-this.accessibility = {};
+this.accessibility = {
+  get service() {
+    return service;
+  }
+};
 
 /**
  * Accessible states used to check element"s state from the accessiblity API
  * perspective.
  * Note: if gecko is built with --disable-accessibility, the interfaces are not
  * defined. This is why we use getters instead to be able to use these
  * statically.
  */
@@ -118,40 +131,42 @@ accessibility.Checks = class {
    * Get an accessible object for an element.
    *
    * @param {DOMElement|XULElement} element
    *     Element to get the accessible object for.
    * @param {boolean=} mustHaveAccessible
    *     Flag indicating that the element must have an accessible object.
    *     Defaults to not require this.
    *
-   * @return {nsIAccessible}
-   *     Accessibility object for the given element.
+   * @return {Promise: nsIAccessible}
+   *     Promise with an accessibility object for the given element.
    */
   getAccessible(element, mustHaveAccessible = false) {
-    return new Promise((resolve, reject) => {
-      let acc = service.getAccessibleFor(element);
+    if (!this.strict) {
+      return Promise.resolve();
+    }
 
-      // if accessible object is found, return it;
-      // if it is not required, also resolve
-      if (acc || !mustHaveAccessible) {
-        resolve(acc);
+    return new Promise((resolve, reject) => {
+      if (!accessibility.service) {
+        reject();
+        return;
+      }
 
-      // if we must have an accessible but are strict,
-      // reject now and avoid polling for an accessible object
-      } else if (mustHaveAccessible && !this.strict) {
-        reject();
-
-      // if we require an accessible object, we need to poll for it
-      // because accessible tree might be
-      // out of sync with DOM tree for a short time
+      let acc = accessibility.service.getAccessibleFor(element);
+      if (acc || !mustHaveAccessible) {
+        // if accessible object is found, return it;
+        // if it is not required, also resolve
+        resolve(acc);
       } else {
+        // if we require an accessible object, we need to poll for it
+        // because accessible tree might be
+        // out of sync with DOM tree for a short time
         let attempts = GET_ACCESSIBLE_ATTEMPTS;
         let intervalId = setInterval(() => {
-          let acc = service.getAccessibleFor(element);
+          let acc = accessibility.service.getAccessibleFor(element);
           if (acc || --attempts <= 0) {
             clearInterval(intervalId);
             if (acc) {
               resolve(acc);
             } else {
               reject();
             }
           }
@@ -169,17 +184,17 @@ accessibility.Checks = class {
    *     Accessible object.
    *
    * @return {boolean}
    *     True if an actionable role is found on the accessible, false
    *     otherwise.
    */
   isActionableRole(accessible) {
     return accessibility.ActionableRoles.has(
-        service.getStringRole(accessible.role));
+        accessibility.service.getStringRole(accessible.role));
   }
 
   /**
    * Test if an accessible has at least one action that it supports.
    *
    * @param {nsIAccessible} accessible
    *     Accessible object.
    *
@@ -407,22 +422,20 @@ accessibility.Checks = class {
    * @param {string} message
    * @param {DOMElement|XULElement} element
    *     Element that caused an error.
    *
    * @throws ElementNotAccessibleError
    *     If |strict| is true.
    */
   error(message, element) {
-    if (!message) {
+    if (!message || !this.strict) {
       return;
     }
     if (element) {
       let {id, tagName, className} = element;
       message += `: id: ${id}, tagName: ${tagName}, className: ${className}`;
     }
-    if (this.strict) {
-      throw new ElementNotAccessibleError(message);
-    }
-    logger.debug(message);
+
+    throw new ElementNotAccessibleError(message);
   }
 
 };
--- a/testing/marionette/driver.js
+++ b/testing/marionette/driver.js
@@ -12,16 +12,17 @@ var loader = Cc["@mozilla.org/moz/jssubs
 Cu.import("resource://gre/modules/Log.jsm");
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(
     this, "cookieManager", "@mozilla.org/cookiemanager;1", "nsICookieManager2");
 
+Cu.import("chrome://marionette/content/accessibility.js");
 Cu.import("chrome://marionette/content/atom.js");
 Cu.import("chrome://marionette/content/browser.js");
 Cu.import("chrome://marionette/content/element.js");
 Cu.import("chrome://marionette/content/error.js");
 Cu.import("chrome://marionette/content/evaluate.js");
 Cu.import("chrome://marionette/content/event.js");
 Cu.import("chrome://marionette/content/interaction.js");
 Cu.import("chrome://marionette/content/legacyaction.js");
@@ -479,16 +480,24 @@ GeckoDriver.prototype.newSession = funct
     throw new SessionNotCreatedError("Maximum number of active sessions.")
   }
   this.sessionId = cmd.parameters.sessionId ||
       cmd.parameters.session_id ||
       element.generateUUID();
 
   this.newSessionCommandId = cmd.id;
   this.setSessionCapabilities(cmd.parameters.capabilities);
+  // If we are testing accessibility with marionette, start a11y service in
+  // chrome first. This will ensure that we do not have any content-only
+  // services hanging around.
+  if (this.sessionCapabilities.raisesAccessibilityExceptions &&
+      accessibility.service) {
+    logger.info("Preemptively starting accessibility service in Chrome");
+  }
+
   this.scriptTimeout = 10000;
 
   let registerBrowsers = this.registerPromise();
   let browserListening = this.listeningPromise();
 
   let waitForWindow = function() {
     let win = this.getCurrentWindow();
     if (!win) {
deleted file mode 100644
--- a/testing/mozharness/mozharness/mozilla/googleplay.py
+++ /dev/null
@@ -1,40 +0,0 @@
-""" googleplay.py
-
-    The way to get the API access is to
-      1) login in in the Google play admin
-      2) Settings
-      3) API Access
-      4) go in the Google Developers Console
-      5) Create "New client ID"
-         or download the p12 key (it should remain
-         super private)
-      6) Move the file in this directory with the name
-         'key.p12' or use the --credentials option
-"""
-
-import httplib2
-from oauth2client.service_account import ServiceAccountCredentials
-from apiclient.discovery import build
-from oauth2client import client
-
-
-# GooglePlayMixin {{{1
-class GooglePlayMixin(object):
-
-    def connect_to_play(self):
-        """ Connect to the google play interface
-        """
-
-        # Create an httplib2.Http object to handle our HTTP requests an
-        # authorize it with the Credentials. Note that the first parameter,
-        # service_account_name, is the Email address created for the Service
-        # account. It must be the email address associated with the key that
-        # was created.
-        scope = 'https://www.googleapis.com/auth/androidpublisher'
-        credentials = ServiceAccountCredentials.from_p12_keyfile(self.config["service_account"], self.config["google_play_credentials_file"], scopes=scope)
-        http = httplib2.Http()
-        http = credentials.authorize(http)
-
-        service = build('androidpublisher', 'v2', http=http)
-
-        return service
deleted file mode 100644
--- a/testing/mozharness/mozharness/mozilla/storel10n.py
+++ /dev/null
@@ -1,48 +0,0 @@
-""" storel10n.py
-
-    Manage the localization of the Google play store
-
-"""
-
-from mozharness.base.log import LogMixin
-from mozharness.base.script import ScriptMixin
-
-
-class storel10n(ScriptMixin, LogMixin):
-
-    l10n_api_url = "https://l10n.mozilla-community.org/stores_l10n/"
-    all_locales_url = l10n_api_url + "api/google/listing/{channel}/"
-    locale_url = l10n_api_url + "api/google/translation/{channel}/{locale}/"
-    mapping_url = l10n_api_url + "api/google/localesmapping/?reverse"
-
-    def __init__(self, config, log):
-        self.config = config
-        self.log_obj = log
-        self.mappings = []
-
-    def get_list_locales(self, package_name):
-
-        """ Get all the translated locales supported by Google play
-        So, locale unsupported by Google play won't be downloaded
-        Idem for not translated locale
-        """
-        return self.load_json_url(self.all_locales_url.format(channel=package_name))
-
-    def get_translation(self, package_name, locale):
-        """ Get the translation for a locale
-        """
-        return self.load_json_url(self.locale_url.format(channel=package_name, locale=locale))
-
-    def load_mapping(self):
-        """ Download and load the locale mapping
-        """
-        self.mappings = self.load_json_url(self.mapping_url)
-
-    def locale_mapping(self, locale):
-        """ Google play and Mozilla don't have the exact locale code
-        Translate them
-        """
-        if locale in self.mappings:
-            return self.mappings[locale]
-        else:
-            return locale
deleted file mode 100644
--- a/testing/mozharness/scripts/get_apk.py
+++ /dev/null
@@ -1,275 +0,0 @@
-#!/usr/bin/env python
-
-import sys
-import os
-import re
-import signal
-
-# load modules from parent dir
-sys.path.insert(1, os.path.dirname(sys.path[0]))
-
-# import the guts
-from mozharness.base.script import BaseScript
-from mozharness.base.python import VirtualenvMixin
-from mozharness.base.script import ScriptMixin
-
-
-class GetAPK(BaseScript, VirtualenvMixin):
-    all_actions = [
-        'create-virtualenv',
-        'download-apk'
-    ]
-
-    default_actions = [
-        'create-virtualenv',
-        'test'
-    ]
-
-    config_options = [
-        [["--build"], {
-            "dest": "build",
-            "help": "Specify build number (default 1)",
-            "default": "1"
-        }],
-        [["--version"], {
-            "dest": "version",
-            "help": "Specify version number to download (e.g. 23.0b7)",
-            "default": "None"
-        }],
-        [["--latest-nightly"], {
-            "dest": "latest_nightly",
-            "help": "Download the latest nightly version",
-            "action": "store_true",
-            "default": False
-        }],
-        [["--latest-aurora"], {
-            "dest": "latest_aurora",
-            "help": "Download the latest aurora version",
-            "action": "store_true",
-            "default": False
-        }],
-        [["--arch"], {
-            "dest": "arch",
-            "help": "Specify which architecture to get the apk for",
-            "default": "all"
-        }],
-        [["--locale"], {
-            "dest": "locale",
-            "help": "Specify which locale to get the apk for",
-            "default": "multi"
-        }],
-        [["--clean"], {
-            "dest": "clean",
-            "help": "Use this option to clean the download directory",
-            "action": "store_true",
-            "default": False
-        }]
-    ]
-
-    arch_values = ["arm", "x86"]
-    multi_api_archs = ["arm"]
-    multi_apis = ["api-15"]  # v11 has been dropped in fx 46 (1155801)
-    # v9 has been dropped in fx 48 (1220184)
-
-    download_dir = "apk-download"
-
-    apk_ext = ".apk"
-    checksums_ext = ".checksums"
-    android_prefix = "android-"
-
-    base_url = "https://ftp.mozilla.org/pub/mobile"
-    json_version_url = "https://product-details.mozilla.org/1.0/firefox_versions.json"
-
-    # Cleanup half downloaded files on Ctrl+C
-    def signal_handler(self, signal, frame):
-        print("You pressed Ctrl+C!")
-        self.cleanup()
-        sys.exit(1)
-
-    def cleanup(self):
-        ScriptMixin.rmtree(self, self.download_dir)
-        self.info("Download directory cleaned")
-
-    def __init__(self, require_config_file=False, config={},
-                 all_actions=all_actions,
-                 default_actions=default_actions):
-        default_config = {
-            # the path inside the work_dir ('build') of where we will install the env.
-            # pretty sure it's the default and not needed.
-            'virtualenv_path': 'venv',
-        }
-
-        default_config.update(config)
-
-        BaseScript.__init__(
-            self,
-            config_options=self.config_options,
-            require_config_file=require_config_file,
-            config=default_config,
-            all_actions=all_actions,
-        )
-
-    # Gets called once download is complete
-    def download_complete(self, apk_file, checksum_file):
-        self.info(apk_file + " has been downloaded successfully")
-        ScriptMixin.rmtree(self, checksum_file)
-
-    # Called if download fails due to 404 error or some other connection failure
-    def download_error(self):
-        self.cleanup()
-        self.fatal("Download failed!")
-
-    # Check the given values are correct
-    def check_argument(self):
-        if self.config["clean"]:
-            self.cleanup()
-        if self.config["version"] == "None":
-            if self.config["clean"]:
-                sys.exit(0)
-        if self.config["version"] != "None" and (self.config["latest_nightly"] or self.config["latest_aurora"]):
-            self.fatal("Cannot set a version and --latest-nightly or --latest-aurora")
-
-        if self.config["arch"] not in self.arch_values and not self.config["arch"] == "all":
-            error = self.config["arch"] + " is not a valid arch.  " \
-                                          "Try one of the following:"+os.linesep
-            for arch in self.arch_values:
-                error += arch + os.linesep
-            error += "Or don't use the --arch option to download all the archs"
-            self.fatal(error)
-
-        if self.config["latest_nightly"] and self.config["latest_aurora"]:
-            self.fatal("Conflicting options. Cannot use --latest-nightly with --latest-aurora")
-
-    # Checksum check the APK
-    def check_apk(self, apk_file, checksum_file):
-        self.info("The checksum for the APK is being checked....")
-        checksum = ScriptMixin.read_from_file(self, checksum_file, False)
-        checksum = re.sub("\s(.*)", "", checksum.splitlines()[0])
-
-        apk_checksum = self.file_sha512sum(apk_file)
-
-        if checksum == apk_checksum:
-            self.info("APK checksum check succeeded!")
-            self.download_complete(apk_file, checksum_file)
-        else:
-            ScriptMixin.rmtree(self, self.download_dir)
-            self.fatal("Downloading " + apk_file + " failed!")
-
-    # Helper functions
-    def generate_url(self, version, build, locale, api_suffix, arch_file):
-        if self.config["latest_nightly"] or self.config["latest_aurora"]:
-            code = "central" if self.config["latest_nightly"] else "aurora"
-            return ("%s/nightly/latest-mozilla-%s-android-%s/fennec-%s.%s.android-%s") % (self.base_url, code, api_suffix, version, locale, arch_file)
-
-        return ("%s/candidates/%s-candidates/build%s/%s%s/%s/fennec-%s.%s.%s%s") % (self.base_url, version, build, self.android_prefix, api_suffix, locale, version, locale, self.android_prefix, arch_file)
-
-
-    def get_api_suffix(self, arch):
-        if arch in self.multi_api_archs:
-            return self.multi_apis
-        else:
-            return [arch]
-
-    def get_arch_file(self, arch):
-        if arch == "x86":
-            # the filename contains i386 instead of x86
-            return "i386"
-        else:
-            return arch
-
-    def get_common_file_name(self, version, locale):
-        return "fennec-" + version + "." + locale + "." + self.android_prefix
-
-    # Function that actually downloads the file
-    def download(self, version, build, arch, locale):
-        ScriptMixin.mkdir_p(self, self.download_dir)
-
-        common_filename = self.get_common_file_name(version, locale)
-        arch_file = self.get_arch_file(arch)
-
-        for api_suffix in self.get_api_suffix(arch):
-            url = self.generate_url(version, build, locale, api_suffix, arch_file)
-            apk_url = url + self.apk_ext
-            checksum_url = url + self.checksums_ext
-            if arch in self.multi_api_archs:
-                filename = common_filename + arch_file + "-" + api_suffix
-            else:
-                filename = common_filename + arch_file
-
-            filename_apk = os.path.join(self.download_dir, filename + self.apk_ext)
-            filename_checksums = os.path.join(self.download_dir, filename + self.checksums_ext)
-
-            # Download the APK
-            retry_config = {'attempts': 1, 'cleanup': self.download_error}
-            ScriptMixin.download_file(self, apk_url, filename_apk, retry_config=retry_config)
-
-            # Download the checksum of the APK
-            retry_config = {'attempts': 1, 'cleanup': self.download_error}
-            ScriptMixin.download_file(self, checksum_url, filename_checksums, retry_config=retry_config)
-
-            self.check_apk(filename_apk, filename_checksums)
-
-    def get_version_name(self):
-        if self.config["latest_nightly"] or self.config["latest_aurora"]:
-            json = self.load_json_url(self.json_version_url)
-            version_code = json['FIREFOX_NIGHTLY'] if self.config["latest_nightly"] else json['FIREFOX_AURORA']
-            return version_code
-        return self.config["version"]
-
-    # Download all the archs if none is given
-    def download_all(self, version, build, locale):
-        for arch in self.arch_values:
-            self.download(version, build, arch, locale)
-
-    # Download apk initial action
-    def download_apk(self):
-        self.check_argument()
-        version = self.get_version_name()
-        arch = self.config["arch"]
-        build = str(self.config["build"])
-        locale = self.config["locale"]
-
-        self.info("Downloading version " + version + " build #" + build
-                  + " for arch " + arch + " (locale " + locale + ")")
-        if arch == "all":
-            self.download_all(version, build, locale)
-        else:
-            self.download(version, build, arch, locale)
-
-    # Test the helper, cleanup and check functions
-    def test(self):
-        ScriptMixin.mkdir_p(self, self.download_dir)
-        testfile = os.path.join(self.download_dir, "testfile")
-        testchecksums = os.path.join(self.download_dir, "testchecksums")
-        ScriptMixin.write_to_file(self, testfile, "This is a test file!")
-        ScriptMixin.write_to_file(self, testchecksums, self.file_sha512sum(testfile))
-        self.check_apk(testfile, testchecksums)
-        self.download_complete(testfile, testchecksums)
-        if os.path.isfile(testchecksums):
-            self.fatal("download_complete test failed!")
-
-        self.cleanup()
-        if os.path.isdir(self.download_dir):
-            self.fatal("cleanup test failed")
-
-        url = self.generate_url("43.0", "2", "multi", "x86", "i386")
-        correcturl = "https://ftp.mozilla.org/pub/mozilla.org/mobile/candidates/43.0-candidates/build2/"\
-                     + self.android_prefix + "x86/multi/fennec-43.0.multi." + self.android_prefix + "i386"
-
-        if not url == correcturl:
-            self.fatal(("get_url test failed! %s != %s") % (url, correcturl))
-
-        if not self.get_api_suffix(self.multi_api_archs[0]) == self.multi_apis:
-            self.fatal("get_api_suffix test failed!")
-
-        if not self.get_arch_file("x86") == "i386":
-            self.fatal("get_arch_file test failed!")
-
-        if not self.get_common_file_name("43.0", "multi") == "fennec-43.0.multi." + self.android_prefix:
-            self.fatal("get_common_file_name test failed!")
-
-# main {{{1
-if __name__ == '__main__':
-    myScript = GetAPK()
-    signal.signal(signal.SIGINT, myScript.signal_handler)
-    myScript.run_and_exit()
deleted file mode 100644
--- a/testing/mozharness/scripts/push_apk.py
+++ /dev/null
@@ -1,239 +0,0 @@
-#!/usr/bin/env python
-""" push_apk.py
-
-    Upload the apk of a Firefox app on Google play
-    Example for a beta upload:
-    $ python push_apk.py --package-name org.mozilla.firefox_beta --service-account foo@developer.gserviceaccount.com --credentials key.p12 --apk-x86=/path/to/fennec-XX.0bY.multi.android-i386.apk --apk-armv7-v15=/path/to/fennec-XX.0bY.multi.android-arm-v15.apk --track production --push_apk
-
-    Debian/Ubuntu dependencies: python-googleapi python-oauth2client
-"""
-import sys
-import os
-
-from oauth2client import client
-
-# load modules from parent dir
-sys.path.insert(1, os.path.dirname(sys.path[0]))
-
-# import the guts
-from mozharness.base.script import BaseScript
-from mozharness.mozilla.googleplay import GooglePlayMixin
-from mozharness.mozilla.storel10n import storel10n
-from mozharness.base.python import VirtualenvMixin
-
-
-class PushAPK(BaseScript, GooglePlayMixin, VirtualenvMixin):
-    all_actions = [
-        'create-virtualenv',
-        'push_apk',
-        'test',
-    ]
-
-    default_actions = [
-        'create-virtualenv',
-        'test',
-    ]
-    config_options = [
-        [["--track"], {
-            "dest": "track",
-            "help": "Track on which to upload "
-            "(production, beta, alpha, rollout)",
-            # We are not using alpha but we default to it to avoid mistake
-            "default": "alpha"
-        }],
-        [["--service-account"], {
-            "dest": "service_account",
-            "help": "The service account email",
-        }],
-        [["--credentials"], {
-            "dest": "google_play_credentials_file",
-            "help": "The p12 authentication file",
-            "default": "key.p12"
-        }],
-        [["--package-name"], {
-            "dest": "package_name",
-            "help": "The Google play name of the app",
-        }],
-        [["--apk-x86"], {
-            "dest": "apk_file_x86",
-            "help": "The path to the x86 APK file",
-        }],
-        [["--apk-armv7-v15"], {
-            "dest": "apk_file_armv7_v15",
-            "help": "The path to the ARM v7 API v15 APK file",
-        }],
-        [["--rollout-percentage"], {
-            "dest": "rollout_percentage",
-            "help": "The rollout percentage (update percentage)",
-            "default": "None"
-        }],
-
-    ]
-
-    # Google play has currently 3 tracks. Rollout deploys
-    # to a limited percentage of users
-    track_values = ("production", "beta", "alpha", "rollout")
-
-    # We have 3 apps. Make sure that their names are correct
-    package_name_values = {"org.mozilla.fennec_aurora": "aurora",
-                           "org.mozilla.firefox_beta": "beta",
-                           "org.mozilla.firefox": "release"}
-
-    def __init__(self, require_config_file=False, config={},
-                 all_actions=all_actions,
-                 default_actions=default_actions):
-
-        # Default configuration
-        default_config = {
-            'debug_build': False,
-            'pip_index': True,
-            # this will pip install it automajically when we call the create-virtualenv action
-            'virtualenv_modules': ['google-api-python-client'],
-            "find_links": [   # so mozharness knows where to look for the package
-                "http://pypi.pvt.build.mozilla.org/pub",
-                "http://pypi.pub.build.mozilla.org/pub",
-            ],
-            # the path inside the work_dir ('build') of where we will install the env.
-            # pretty sure it's the default and not needed.
-            'virtualenv_path': 'venv',
-        }
-        default_config.update(config)
-
-        BaseScript.__init__(
-            self,
-            config_options=self.config_options,
-            require_config_file=require_config_file,
-            config=default_config,
-            all_actions=all_actions,
-            default_actions=default_actions,
-        )
-
-        self.translationMgmt = storel10n(config, {})
-
-    def check_argument(self):
-        """ Check that the given values are correct,
-        files exists, etc
-        """
-        if "package_name" not in self.config:
-            self.fatal("--package-name is mandatory")
-
-        if self.config['track'] not in self.track_values:
-            self.fatal("Unknown track value " + self.config['track'])
-
-        if self.config['package_name'] not in self.package_name_values:
-            self.fatal("Unknown package name value " +
-                       self.config['package_name'])
-
-        if self.config['track'] == "rollout" and self.config["rollout_percentage"] is "None":
-            self.fatal("When using track='rollout', --rollout-percentage must be provided too")
-
-        if self.config["rollout_percentage"] is not "None":
-            self.percentage = float(self.config["rollout_percentage"])
-            if self.percentage < 0 or self.percentage > 100:
-                self.fatal("Percentage should be between 0 and 100")
-
-        if not os.path.isfile(self.config['apk_file_x86']):
-            self.fatal("Could not find " + self.config['apk_file_x86'])
-
-        if not os.path.isfile(self.config['apk_file_armv7_v15']):
-            self.fatal("Could not find " + self.config['apk_file_armv7_v15'])
-
-        if not os.path.isfile(self.config['google_play_credentials_file']):
-            self.fatal("Could not find " + self.config['google_play_credentials_file'])
-
-    def upload_apks(self, service, apk_files):
-        """ Upload the APK to google play
-
-        service -- The session to Google play
-        apk_files -- The files
-        """
-        edit_request = service.edits().insert(body={},
-                                              packageName=self.config['package_name'])
-        package_code = self.package_name_values[self.config['package_name']]
-        result = edit_request.execute()
-        edit_id = result['id']
-        # Store all the versions to set the tracks (needs to happen
-        # at the same time
-        versions = []
-
-        # Retrieve the mapping
-        self.translationMgmt.load_mapping()
-
-        # For each files, upload it
-        for apk_file in apk_files:
-            try:
-                # Upload the file
-                apk_response = service.edits().apks().upload(
-                    editId=edit_id,
-                    packageName=self.config['package_name'],
-                    media_body=apk_file).execute()
-                self.log('Version code %d has been uploaded. '
-                         'Filename "%s" edit_id %s' %
-                         (apk_response['versionCode'], apk_file, edit_id))
-
-                versions.append(apk_response['versionCode'])
-
-                if 'aurora' in self.config['package_name']:
-                    self.warning('Aurora is not supported by store_l10n. Skipping what\'s new.')
-                else:
-                    self._push_whats_new(package_code, service, edit_id, apk_response)
-
-            except client.AccessTokenRefreshError:
-                self.log('The credentials have been revoked or expired,'
-                         'please re-run the application to re-authorize')
-
-        upload_body = {u'versionCodes': versions}
-        if self.config["rollout_percentage"] is not "None":
-            upload_body[u'userFraction'] = self.percentage/100
-
-        # Set the track for all apk
-        service.edits().tracks().update(
-            editId=edit_id,
-            track=self.config['track'],
-            packageName=self.config['package_name'],
-            body=upload_body).execute()
-        self.log('Application "%s" set to track "%s" for versions %s' %
-                 (self.config['package_name'], self.config['track'], versions))
-
-        # Commit our changes
-        commit_request = service.edits().commit(
-            editId=edit_id, packageName=self.config['package_name']).execute()
-        self.log('Edit "%s" has been committed' % (commit_request['id']))
-
-    def _push_whats_new(self, package_code, service, edit_id, apk_response):
-        locales = self.translationMgmt.get_list_locales(package_code)
-        locales.append(u'en-US')
-
-        for locale in locales:
-            translation = self.translationMgmt.get_translation(package_code, locale)
-            whatsnew = translation.get("whatsnew")
-            if locale == "en-GB":
-                self.log("Ignoring en-GB as locale")
-                continue
-            locale = self.translationMgmt.locale_mapping(locale)
-            self.log('Locale "%s" what\'s new has been updated to "%s"'
-                     % (locale, whatsnew))
-
-            listing_response = service.edits().apklistings().update(
-                editId=edit_id, packageName=self.config['package_name'], language=locale,
-                apkVersionCode=apk_response['versionCode'],
-                body={'recentChanges': whatsnew}).execute()
-
-            self.log('Listing for language %s was updated.' % listing_response['language'])
-
-    def push_apk(self):
-        """ Upload the APK files """
-        self.check_argument()
-        service = self.connect_to_play()
-        apks = [self.config['apk_file_armv7_v15'], self.config['apk_file_x86']]
-        self.upload_apks(service, apks)
-
-    def test(self):
-        """ Test if the connexion can be done """
-        self.check_argument()
-        self.connect_to_play()
-
-# main {{{1
-if __name__ == '__main__':
-    myScript = PushAPK()
-    myScript.run_and_exit()
deleted file mode 100644
--- a/testing/mozharness/scripts/update_apk_description.py
+++ /dev/null
@@ -1,224 +0,0 @@
-#!/usr/bin/env python
-""" update_apk_description.py
-
-    Update the descriptions of an application (multilang)
-    Example of beta update:
-    $ python update_apk_description.py --service-account foo@developer.gserviceaccount.com --package-name org.mozilla.firefox --credentials key.p12 --update-apk-description
-"""
-import sys
-import os
-import urllib2
-import json
-
-from oauth2client import client
-
-# load modules from parent dir
-sys.path.insert(1, os.path.dirname(sys.path[0]))
-
-# import the guts
-from mozharness.base.script import BaseScript
-from mozharness.mozilla.googleplay import GooglePlayMixin
-from mozharness.mozilla.storel10n import storel10n
-from mozharness.base.python import VirtualenvMixin
-
-
-class UpdateDescriptionAPK(BaseScript, GooglePlayMixin, VirtualenvMixin):
-    all_actions = [
-        'create-virtualenv',
-        'update-apk-description',
-        'test',
-    ]
-
-    default_actions = [
-        'create-virtualenv',
-        'test',
-    ]
-
-    config_options = [
-        [["--service-account"], {
-            "dest": "service_account",
-            "help": "The service account email",
-        }],
-        [["--credentials"], {
-            "dest": "google_play_credentials_file",
-            "help": "The p12 authentication file",
-            "default": "key.p12"
-        }],
-        [["--package-name"], {
-            "dest": "package_name",
-            "help": "The Google play name of the app",
-        }],
-        [["--l10n-api-url"], {
-            "dest": "l10n_api_url",
-            "help": "The L10N URL",
-            "default": "https://l10n.mozilla-community.org/stores_l10n/"
-        }],
-        [["--force-locale"], {
-            "dest": "force_locale",
-            "help": "Force a specific locale (instead of all)",
-        }],
-    ]
-
-    # We have 3 apps. Make sure that their names are correct
-    package_name_values = {"org.mozilla.fennec_aurora": "aurora",
-                           "org.mozilla.firefox_beta": "beta",
-                           "org.mozilla.firefox": "release"}
-
-    def __init__(self, require_config_file=False, config={},
-                 all_actions=all_actions,
-                 default_actions=default_actions):
-
-        # Default configuration
-        default_config = {
-            'debug_build': False,
-            'pip_index': True,
-            # this will pip install it automajically when we call the
-            # create-virtualenv action
-            'virtualenv_modules': ['google-api-python-client'],
-            "find_links": [
-                "http://pypi.pvt.build.mozilla.org/pub",
-                "http://pypi.pub.build.mozilla.org/pub",
-            ],
-            'virtualenv_path': 'venv',
-        }
-        default_config.update(config)
-
-        BaseScript.__init__(
-            self,
-            config_options=self.config_options,
-            require_config_file=require_config_file,
-            config=default_config,
-            all_actions=all_actions,
-            default_actions=default_actions,
-        )
-
-        self.all_locales_url = self.config['l10n_api_url'] + "api/?done&channel={channel}"
-        self.locale_url = self.config['l10n_api_url'] + "api/?locale={locale}&channel={channel}"
-        self.mapping_url = self.config['l10n_api_url'] + "api/?locale_mapping&reverse"
-        self.translationMgmt = storel10n(config, {})
-
-    def check_argument(self):
-        """ Check that the given values are correct,
-        files exists, etc
-        """
-        if self.config['package_name'] not in self.package_name_values:
-            self.fatal("Unknown package name value " +
-                       self.config['package_name'])
-
-        if not os.path.isfile(self.config['google_play_credentials_file']):
-            self.fatal("Could not find " + self.config['google_play_credentials_file'])
-
-
-    def update_desc(self, service, package_name):
-        """ Update the desc on google play
-
-        service -- The session to Google play
-        package_name -- The name of the package
-        locale -- The locale to update
-        description -- The new description
-        """
-
-        edit_request = service.edits().insert(body={},
-                                              packageName=package_name)
-        result = edit_request.execute()
-        edit_id = result['id']
-
-        # Retrieve the mapping
-        self.translationMgmt.load_mapping()
-        package_code = self.package_name_values[self.config['package_name']]
-
-        if self.config.get("force_locale"):
-            # The user forced a locale, don't need to retrieve the full list
-            locales = [self.config.get("force_locale")]
-        else:
-            # Get all the locales from the web interface
-            locales = self.translationMgmt.get_list_locales(package_code)
-        nb_locales = 0
-        for locale in locales:
-            translation = self.translationMgmt.get_translation(package_code, locale)
-            title = translation.get("title")
-            short_desc = translation.get("short_desc")
-            long_desc = translation.get("long_desc")
-
-            # Google play expects some locales codes (de-DE instead of de)
-            locale = self.translationMgmt.locale_mapping(locale)
-
-            try:
-                self.log("Updating " + package_code + " for '" + locale +
-                         "' /  title: '" + title + "', short_desc: '" +
-                         short_desc[0:20] + "'..., long_desc: '" +
-                         long_desc[0:20] + "...'")
-                service.edits().listings().update(
-                    editId=edit_id, packageName=package_name, language=locale,
-                    body={'fullDescription': long_desc,
-                          'shortDescription': short_desc,
-                          'title': title}).execute()
-                nb_locales += 1
-            except client.AccessTokenRefreshError:
-                self.log('The credentials have been revoked or expired,'
-                         'please re-run the application to re-authorize')
-
-        # Commit our changes
-        commit_request = service.edits().commit(
-            editId=edit_id, packageName=package_name).execute()
-        self.log('Edit "%s" has been committed. %d locale(s) updated.' % (commit_request['id'], nb_locales))
-
-    def update_apk_description(self):
-        """ Update the description """
-        self.check_argument()
-        service = self.connect_to_play()
-        self.update_desc(service, self.config['package_name'])
-
-    def test(self):
-        """
-        Test if the connexion can be done and if the various method
-        works as expected
-        """
-        self.check_argument()
-        self.connect_to_play()
-        package_name = 'org.mozilla.fennec_aurora'
-        locales = self.translationMgmt.get_list_locales(package_name)
-        if not locales:
-            self.fatal("get_list_locales() failed")
-
-        self.translationMgmt.get_mapping()
-        if not self.mappings:
-            self.fatal("get_mapping() failed")
-
-        loca = self.translationMgmt.locale_mapping("fr")
-        if loca != "fr-FR":
-            self.fatal("fr locale_mapping failed")
-        loca = self.translationMgmt.locale_mapping("hr")
-        if loca != "hr":
-            self.fatal("hr locale_mapping failed")
-
-        translation = self.translationMgmt.get_translation(package_name, 'cs')
-        if len(translation.get('title')) < 5:
-            self.fatal("get_translation title failed for the 'cs' locale")
-        if len(translation.get('short_desc')) < 5:
-            self.fatal("get_translation short_desc failed for the 'cs' locale")
-        if len(translation.get('long_desc')) < 5:
-            self.fatal("get_translation long_desc failed for the 'cs' locale")
-
-        package_name = "org.mozilla.firefox_beta"
-        translation = self.translationMgmt.get_translation(package_name, 'fr')
-        if len(translation.get('title')) < 5:
-            self.fatal("get_translation title failed for the 'fr' locale")
-        if len(translation.get('short_desc')) < 5:
-            self.fatal("get_translation short_desc failed for the 'fr' locale")
-        if len(translation.get('long_desc')) < 5:
-            self.fatal("get_translation long_desc failed for the 'fr' locale")
-
-        package_name = "org.mozilla.firefox"
-        translation = self.translationMgmt.get_translation(package_name, 'de')
-        if len(translation.get('title')) < 5:
-            self.fatal("get_translation title failed for the 'de' locale")
-        if len(translation.get('short_desc')) < 5:
-            self.fatal("get_translation short_desc failed for the 'de' locale")
-        if len(translation.get('long_desc')) < 5:
-            self.fatal("get_translation long_desc failed for the 'de' locale")
-
-# main {{{1
-if __name__ == '__main__':
-    myScript = UpdateDescriptionAPK()
-    myScript.run_and_exit()
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -278,16 +278,18 @@ class BrowserDocshellFollower {
 }
 
 class ProxyContext extends BaseContext {
   constructor(envType, extension, params, xulBrowser, principal) {
     super(envType, extension);
 
     this.uri = NetUtil.newURI(params.url);
 
+    this.incognito = params.incognito;
+
     // This message manager is used by ParentAPIManager to send messages and to
     // close the ProxyContext if the underlying message manager closes. This
     // message manager object may change when `xulBrowser` swaps docshells, e.g.
     // when a tab is moved to a different window.
     this.currentMessageManager = xulBrowser.messageManager;
     this._docShellTracker = new BrowserDocshellFollower(xulBrowser,
         this.onBrowserChange.bind(this));
 
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -247,16 +247,17 @@ defineLazyGetter(ExtensionContext.protot
   if (this.viewType == "background") {
     apiManager.global.initializeBackgroundPage(this.contentWindow);
   }
 
   let childManager = new WannabeChildAPIManager(this, this.messageManager, localApis, {
     envType: "addon_parent",
     viewType: this.viewType,
     url: this.uri.spec,
+    incognito: this.incognito,
   });
 
   this.callOnClose(childManager);
 
   return childManager;
 });
 
 // All subframes in a tab, background page, popup, etc. have the same view type.
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -27,16 +27,18 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "Locale",
                                   "resource://gre/modules/Locale.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
                                   "resource://gre/modules/MessageChannel.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                   "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
                                   "resource://gre/modules/Preferences.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
+                                  "resource://gre/modules/PrivateBrowsingUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PromiseUtils",
                                   "resource://gre/modules/PromiseUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Schemas",
                                   "resource://gre/modules/Schemas.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "styleSheetService",
                                    "@mozilla.org/content/style-sheet-service;1",
                                    "nsIStyleSheetService");
@@ -189,32 +191,36 @@ class BaseContext {
     this.onClose = new Set();
     this.checkedLastError = false;
     this._lastError = null;
     this.contextId = `${++gContextId}-${Services.appinfo.uniqueProcessID}`;
     this.unloaded = false;
     this.extension = extension;
     this.jsonSandbox = null;
     this.active = true;
-
+    this.incognito = null;
     this.messageManager = null;
     this.docShell = null;
     this.contentWindow = null;
     this.innerWindowID = 0;
   }
 
   setContentWindow(contentWindow) {
     let {document} = contentWindow;
     let docShell = contentWindow.QueryInterface(Ci.nsIInterfaceRequestor)
                                 .getInterface(Ci.nsIDocShell);
 
     this.innerWindowID = getInnerWindowID(contentWindow);
     this.messageManager = docShell.QueryInterface(Ci.nsIInterfaceRequestor)
                                   .getInterface(Ci.nsIContentFrameMessageManager);
 
+    if (this.incognito == null) {
+      this.incognito = PrivateBrowsingUtils.isContentWindowPrivate(contentWindow);
+    }
+
     MessageChannel.setupMessageManagers([this.messageManager]);
 
     let onPageShow = event => {
       if (!event || event.target === document) {
         this.docShell = docShell;
         this.contentWindow = contentWindow;
         this.active = true;
       }
--- a/toolkit/components/extensions/ext-c-extension.js
+++ b/toolkit/components/extensions/ext-c-extension.js
@@ -10,17 +10,17 @@ function extensionApiFactory(context) {
         return context.extension.baseURI.resolve(url);
       },
 
       get lastError() {
         return context.lastError;
       },
 
       get inIncognitoContext() {
-        return PrivateBrowsingUtils.isContentWindowPrivate(context.contentWindow);
+        return context.incognito;
       },
     },
   };
 }
 
 extensions.registerSchemaAPI("extension", "addon_child", extensionApiFactory);
 extensions.registerSchemaAPI("extension", "content_child", extensionApiFactory);
 extensions.registerSchemaAPI("extension", "addon_child", context => {
--- a/toolkit/components/extensions/ext-runtime.js
+++ b/toolkit/components/extensions/ext-runtime.js
@@ -9,25 +9,38 @@ Cu.import("resource://gre/modules/Extens
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Extension",
                                   "resource://gre/modules/Extension.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ExtensionManagement",
                                   "resource://gre/modules/ExtensionManagement.jsm");
 
 var {
-  ignoreEvent,
   SingletonEventManager,
 } = ExtensionUtils;
 
 extensions.registerSchemaAPI("runtime", "addon_parent", context => {
   let {extension} = context;
   return {
     runtime: {
-      onStartup: ignoreEvent(context, "runtime.onStartup"),
+      onStartup: new SingletonEventManager(context, "runtime.onStartup", fire => {
+        if (context.incognito) {
+          // This event should not fire if we are operating in a private profile.
+          return () => {};
+        }
+        let listener = () => {
+          if (extension.startupReason === "APP_STARTUP") {
+            fire();
+          }
+        };
+        extension.on("startup", listener);
+        return () => {
+          extension.off("startup", listener);
+        };
+      }).api(),
 
       onInstalled: new SingletonEventManager(context, "runtime.onInstalled", fire => {
         let listener = () => {
           switch (extension.startupReason) {
             case "APP_STARTUP":
               if (Extension.browserUpdated) {
                 fire({reason: "browser_update"});
               }
--- a/toolkit/components/extensions/schemas/runtime.json
+++ b/toolkit/components/extensions/schemas/runtime.json
@@ -448,19 +448,18 @@
             ]
           }
         ]
       }
     ],
     "events": [
       {
         "name": "onStartup",
-        "unsupported": true,
         "type": "function",
-        "description": "Fired when a profile that has this extension installed first starts up. This event is not fired when an incognito profile is started, even if this extension is operating in 'split' incognito mode."
+        "description": "Fired when a profile that has this extension installed first starts up. This event is not fired for incognito profiles."
       },
       {
         "name": "onInstalled",
         "type": "function",
         "description": "Fired when the extension is first installed, when the extension is updated to a new version, and when the browser is updated to a new version.",
         "parameters": [
           {
             "type": "object",
--- a/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
+++ b/toolkit/components/extensions/test/mochitest/test_ext_all_apis.js
@@ -69,16 +69,17 @@ let expectedBackgroundApis = [
   "management.ExtensionInstallType",
   "management.ExtensionType",
   "management.getSelf",
   "management.uninstallSelf",
   "runtime.getBackgroundPage",
   "runtime.getBrowserInfo",
   "runtime.getPlatformInfo",
   "runtime.onInstalled",
+  "runtime.onStartup",
   "runtime.onUpdateAvailable",
   "runtime.openOptionsPage",
   "runtime.reload",
   "runtime.setUninstallURL",
 ];
 
 function sendAllApis() {
   function isEvent(key, val) {
rename from toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled.js
rename to toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js
--- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_onInstalled_and_onStartup.js
@@ -34,16 +34,59 @@ function awaitEvent(eventName) {
         resolve(...args);
       }
     };
 
     Management.on(eventName, listener);
   });
 }
 
+function background() {
+  let onInstalledDetails = null;
+  let onStartupFired = false;
+
+  browser.runtime.onInstalled.addListener(details => {
+    onInstalledDetails = details;
+  });
+
+  browser.runtime.onStartup.addListener(() => {
+    onStartupFired = true;
+  });
+
+  browser.test.onMessage.addListener(message => {
+    if (message === "get-on-installed-details") {
+      onInstalledDetails = onInstalledDetails || {fired: false};
+      browser.test.sendMessage("on-installed-details", onInstalledDetails);
+    } else if (message === "did-on-startup-fire") {
+      browser.test.sendMessage("on-startup-fired", onStartupFired);
+    } else if (message === "reload-extension") {
+      browser.runtime.reload();
+    }
+  });
+
+  browser.runtime.onUpdateAvailable.addListener(details => {
+    browser.test.sendMessage("reloading");
+    browser.runtime.reload();
+  });
+}
+
+function* expectEvents(extension, {onStartupFired, onInstalledFired, onInstalledReason}) {
+  extension.sendMessage("get-on-installed-details");
+  let details = yield extension.awaitMessage("on-installed-details");
+  if (onInstalledFired) {
+    equal(details.reason, onInstalledReason, "runtime.onInstalled fired with the correct reason");
+  } else {
+    equal(details.fired, onInstalledFired, "runtime.onInstalled should not have fired");
+  }
+
+  extension.sendMessage("did-on-startup-fire");
+  let fired = yield extension.awaitMessage("on-startup-fired");
+  equal(fired, onStartupFired, `Expected runtime.onStartup to ${onStartupFired ? "" : "not "} fire`);
+}
+
 add_task(function* test_should_fire_on_addon_update() {
   const EXTENSION_ID = "test_runtime_on_installed_addon_update@tests.mozilla.org";
 
   const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
 
   // The test extension uses an insecure update url.
   Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
 
@@ -56,26 +99,17 @@ add_task(function* test_should_fire_on_a
       "version": "1.0",
       "applications": {
         "gecko": {
           "id": EXTENSION_ID,
           "update_url": `http://localhost:${port}/test_update.json`,
         },
       },
     },
-    background() {
-      browser.runtime.onUpdateAvailable.addListener(details => {
-        browser.test.sendMessage("reloading");
-        browser.runtime.reload();
-      });
-
-      browser.runtime.onInstalled.addListener(details => {
-        browser.test.sendMessage("installed", details);
-      });
-    },
+    background,
   });
 
   testServer.registerPathHandler("/test_update.json", (request, response) => {
     response.write(`{
       "addons": {
         "${EXTENSION_ID}": {
           "updates": [
             {
@@ -92,30 +126,30 @@ add_task(function* test_should_fire_on_a
     manifest: {
       version: "2.0",
       applications: {
         gecko: {
           id: EXTENSION_ID,
         },
       },
     },
-    background() {
-      browser.runtime.onInstalled.addListener(details => {
-        browser.test.sendMessage("installed", details);
-      });
-    },
+    background,
   });
 
   testServer.registerFile("/addons/test_runtime_on_installed-2.0.xpi", webExtensionFile);
 
   yield promiseStartupManager();
 
   yield extension.startup();
-  let details = yield extension.awaitMessage("installed");
-  equal(details.reason, "install", "runtime.onInstalled fired with the correct reason");
+
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: true,
+    onInstalledReason: "install",
+  });
 
   let addon = yield promiseAddonByID(EXTENSION_ID);
   equal(addon.version, "1.0", "The installed addon has the correct version");
 
   let update = yield promiseFindAddonUpdates(addon);
   let install = update.updateAvailable;
 
   let promiseInstalled = promiseAddonEvent("onInstalled");
@@ -126,18 +160,21 @@ add_task(function* test_should_fire_on_a
   let startupPromise = awaitEvent("ready");
 
   let [updated_addon] = yield promiseInstalled;
   equal(updated_addon.version, "2.0", "The updated addon has the correct version");
 
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  details = yield extension.awaitMessage("installed");
-  equal(details.reason, "update", "runtime.onInstalled fired with the correct reason");
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: true,
+    onInstalledReason: "update",
+  });
 
   yield extension.unload();
 
   yield updated_addon.uninstall();
   yield promiseShutdownManager();
 });
 
 add_task(function* test_should_fire_on_browser_update() {
@@ -150,75 +187,71 @@ add_task(function* test_should_fire_on_b
     manifest: {
       "version": "1.0",
       "applications": {
         "gecko": {
           "id": EXTENSION_ID,
         },
       },
     },
-    background() {
-      let onInstalledDetails = null;
-
-      browser.runtime.onInstalled.addListener(details => {
-        onInstalledDetails = details;
-      });
-
-      browser.test.onMessage.addListener(message => {
-        if (message == "get-on-installed-details") {
-          browser.test.sendMessage("on-installed-details", onInstalledDetails);
-        }
-      });
-    },
+    background,
   });
 
   yield extension.startup();
 
-  extension.sendMessage("get-on-installed-details");
-  let details = yield extension.awaitMessage("on-installed-details");
-  equal(details.reason, "install", "runtime.onInstalled fired with the correct reason");
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: true,
+    onInstalledReason: "install",
+  });
 
   let startupPromise = awaitEvent("ready");
   yield promiseRestartManager("1");
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  extension.sendMessage("get-on-installed-details");
-  details = yield extension.awaitMessage("on-installed-details");
-  equal(details, null, "runtime.onInstalled should not have fired");
+  yield expectEvents(extension, {
+    onStartupFired: true,
+    onInstalledFired: false,
+  });
 
   // Update the browser.
   startupPromise = awaitEvent("ready");
   yield promiseRestartManager("2");
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  extension.sendMessage("get-on-installed-details");
-  details = yield extension.awaitMessage("on-installed-details");
-  equal(details.reason, "browser_update", "runtime.onInstalled fired with the correct reason");
+  yield expectEvents(extension, {
+    onStartupFired: true,
+    onInstalledFired: true,
+    onInstalledReason: "browser_update",
+  });
 
   // Restart the browser.
   startupPromise = awaitEvent("ready");
   yield promiseRestartManager("2");
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  extension.sendMessage("get-on-installed-details");
-  details = yield extension.awaitMessage("on-installed-details");
-  equal(details, null, "runtime.onInstalled should not have fired");
+  yield expectEvents(extension, {
+    onStartupFired: true,
+    onInstalledFired: false,
+  });
 
   // Update the browser again.
   startupPromise = awaitEvent("ready");
   yield promiseRestartManager("3");
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  extension.sendMessage("get-on-installed-details");
-  details = yield extension.awaitMessage("on-installed-details");
-  equal(details.reason, "browser_update", "runtime.onInstalled fired with the correct reason");
+  yield expectEvents(extension, {
+    onStartupFired: true,
+    onInstalledFired: true,
+    onInstalledReason: "browser_update",
+  });
 
   yield extension.unload();
 
   yield promiseShutdownManager();
 });
 
 add_task(function* test_should_not_fire_on_reload() {
   const EXTENSION_ID = "test_runtime_on_installed_reload@tests.mozilla.org";
@@ -230,47 +263,36 @@ add_task(function* test_should_not_fire_
     manifest: {
       "version": "1.0",
       "applications": {
         "gecko": {
           "id": EXTENSION_ID,
         },
       },
     },
-    background() {
-      let onInstalledDetails = null;
-
-      browser.runtime.onInstalled.addListener(details => {
-        onInstalledDetails = details;
-      });
-
-      browser.test.onMessage.addListener(message => {
-        if (message == "reload-extension") {
-          browser.runtime.reload();
-        } else if (message == "get-on-installed-details") {
-          browser.test.sendMessage("on-installed-details", onInstalledDetails);
-        }
-      });
-    },
+    background,
   });
 
   yield extension.startup();
 
-  extension.sendMessage("get-on-installed-details");
-  let details = yield extension.awaitMessage("on-installed-details");
-  equal(details.reason, "install", "runtime.onInstalled fired with the correct reason");
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: true,
+    onInstalledReason: "install",
+  });
 
   let startupPromise = awaitEvent("ready");
   extension.sendMessage("reload-extension");
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  extension.sendMessage("get-on-installed-details");
-  details = yield extension.awaitMessage("on-installed-details");
-  equal(details, null, "runtime.onInstalled should not have fired");
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: false,
+  });
 
   yield extension.unload();
   yield promiseShutdownManager();
 });
 
 add_task(function* test_should_not_fire_on_restart() {
   const EXTENSION_ID = "test_runtime_on_installed_restart@tests.mozilla.org";
 
@@ -281,44 +303,35 @@ add_task(function* test_should_not_fire_
     manifest: {
       "version": "1.0",
       "applications": {
         "gecko": {
           "id": EXTENSION_ID,
         },
       },
     },
-    background() {
-      let onInstalledDetails = null;
-
-      browser.runtime.onInstalled.addListener(details => {
-        onInstalledDetails = details;
-      });
-
-      browser.test.onMessage.addListener(message => {
-        if (message == "get-on-installed-details") {
-          browser.test.sendMessage("on-installed-details", onInstalledDetails);
-        }
-      });
-    },
+    background,
   });
 
   yield extension.startup();
 
-  extension.sendMessage("get-on-installed-details");
-  let details = yield extension.awaitMessage("on-installed-details");
-  equal(details.reason, "install", "runtime.onInstalled fired with the correct reason");
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: true,
+    onInstalledReason: "install",
+  });
 
   let addon = yield promiseAddonByID(EXTENSION_ID);
   addon.userDisabled = true;
 
   let startupPromise = awaitEvent("ready");
   addon.userDisabled = false;
   extension.extension = yield startupPromise;
   extension.attachListeners();
 
-  extension.sendMessage("get-on-installed-details");
-  details = yield extension.awaitMessage("on-installed-details");
-  equal(details, null, "runtime.onInstalled should not have fired");
+  yield expectEvents(extension, {
+    onStartupFired: false,
+    onInstalledFired: false,
+  });
 
   yield extension.markUnloaded();
   yield promiseShutdownManager();
 });
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -41,17 +41,17 @@ skip-if = release_or_beta
 [test_ext_management_uninstall_self.js]
 [test_ext_manifest_content_security_policy.js]
 [test_ext_manifest_incognito.js]
 [test_ext_manifest_minimum_chrome_version.js]
 [test_ext_onmessage_removelistener.js]
 [test_ext_runtime_connect_no_receiver.js]
 [test_ext_runtime_getBrowserInfo.js]
 [test_ext_runtime_getPlatformInfo.js]
-[test_ext_runtime_onInstalled.js]
+[test_ext_runtime_onInstalled_and_onStartup.js]
 [test_ext_runtime_sendMessage.js]
 [test_ext_runtime_sendMessage_errors.js]
 [test_ext_runtime_sendMessage_no_receiver.js]
 [test_ext_runtime_sendMessage_self.js]
 [test_ext_schemas.js]
 [test_ext_schemas_api_injection.js]
 [test_ext_schemas_async.js]
 [test_ext_schemas_allowed_contexts.js]