Backed out 4 changesets (bug 1320404) for xperf failures on permissions.sqlite-journal.
authorCosmin Sabou <csabou@mozilla.com>
Thu, 02 May 2019 04:51:18 +0300
changeset 531041 ad04ccedc21ed4373acff2d310388bc55725182b
parent 531040 8a7e44a884fc0f719fe953fcca70183f7f87d4ec
child 531042 849b94e8913547d915ef4e54a0f0f2f479f579d0
push id11265
push userffxbld-merge
push dateMon, 13 May 2019 10:53:39 +0000
treeherdermozilla-beta@77e0fe8dbdd3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1320404
milestone68.0a1
backs outfbacf18b653259954711b20fcefad7c8a82ce2b1
fed7c475d75c645701b7b520bed98fedfba67eae
557b586f774a9ce81cc2ef79bbd8c991e9d4ecc8
5a20b5f43280e18252fc1637a82f7dfa69e920fd
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 4 changesets (bug 1320404) for xperf failures on permissions.sqlite-journal. Backed out changeset fbacf18b6532 (bug 1320404) Backed out changeset fed7c475d75c (bug 1320404) Backed out changeset 557b586f774a (bug 1320404) Backed out changeset 5a20b5f43280 (bug 1320404)
browser/base/content/test/caps/browser_principalSerialization_version1.js
browser/components/sessionstore/test/browser_restore_cookies_noOriginAttributes.js
caps/BasePrincipal.cpp
caps/BasePrincipal.h
caps/OriginAttributes.cpp
caps/OriginAttributes.h
caps/nsIPrincipal.idl
caps/nsIScriptSecurityManager.idl
caps/tests/gtest/TestOriginAttributes.cpp
caps/tests/unit/test_origin.js
docshell/base/LoadContext.h
docshell/base/nsDocShell.h
dom/base/ChromeUtils.h
dom/base/Document.cpp
dom/base/PostMessageEvent.cpp
dom/base/nsFrameLoader.cpp
dom/base/test/test_messagemanager_principal.html
dom/base/test/test_messagemanager_send_principal.html
dom/base/test/test_messagemanager_targetchain.html
dom/browser-element/mochitest/browserElement_Auth.js
dom/browser-element/mochitest/browserElement_CopyPaste.js
dom/chrome-webidl/ChromeUtils.webidl
dom/ipc/BrowserBridgeParent.cpp
dom/ipc/PTabContext.ipdlh
dom/ipc/TabContext.cpp
dom/presentation/tests/mochitest/test_presentation_mixed_security_contexts.html
dom/push/test/xpcshell/test_clear_origin_data.js
dom/push/test/xpcshell/test_notification_http2.js
dom/push/test/xpcshell/test_reconnect_retry.js
dom/push/test/xpcshell/test_register_5xxCode_http2.js
dom/push/test/xpcshell/test_register_case.js
dom/push/test/xpcshell/test_register_error_http2.js
dom/push/test/xpcshell/test_register_invalid_channel.js
dom/push/test/xpcshell/test_register_invalid_endpoint.js
dom/push/test/xpcshell/test_register_invalid_json.js
dom/push/test/xpcshell/test_register_no_id.js
dom/push/test/xpcshell/test_register_request_queue.js
dom/push/test/xpcshell/test_register_rollback.js
dom/push/test/xpcshell/test_register_success.js
dom/push/test/xpcshell/test_register_success_http2.js
dom/push/test/xpcshell/test_register_timeout.js
dom/push/test/xpcshell/test_register_wrong_id.js
dom/push/test/xpcshell/test_register_wrong_type.js
dom/push/test/xpcshell/test_registration_error.js
dom/push/test/xpcshell/test_registration_error_http2.js
dom/push/test/xpcshell/test_registration_none.js
dom/push/test/xpcshell/test_registration_success.js
dom/push/test/xpcshell/test_registration_success_http2.js
dom/push/test/xpcshell/test_unregister_empty_scope.js
dom/push/test/xpcshell/test_unregister_invalid_json.js
dom/push/test/xpcshell/test_unregister_not_found.js
dom/push/test/xpcshell/test_unregister_success_http2.js
dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js
dom/quota/ActorsParent.cpp
dom/quota/SerializationHelpers.h
dom/quota/nsIQuotaManagerService.idl
dom/quota/test/gtest/TestQuotaManager.cpp
dom/quota/test/unit/removeAppsUpgrade_profile.zip
dom/quota/test/unit/test_removeAppsUpgrade.js
dom/quota/test/unit/xpcshell.ini
dom/serviceworkers/ServiceWorkerManager.cpp
dom/serviceworkers/ServiceWorkerManager.h
dom/serviceworkers/test/gtest/TestReadWrite.cpp
dom/storage/StorageDBUpdater.cpp
dom/storage/StorageUtils.cpp
dom/workers/WorkerLoadInfo.cpp
extensions/permissions/nsPermissionManager.cpp
extensions/permissions/test/unit/test_permmanager_cleardata.js
extensions/permissions/test/unit/test_permmanager_defaults.js
extensions/permissions/test/unit/test_permmanager_load_invalid_entries.js
extensions/permissions/test/unit/test_permmanager_matches.js
extensions/permissions/test/unit/test_permmanager_matchesuri.js
extensions/permissions/test/unit/test_permmanager_migrate_4-7.js
extensions/permissions/test/unit/test_permmanager_migrate_4-7_no_history.js
extensions/permissions/test/unit/test_permmanager_migrate_5-7a.js
extensions/permissions/test/unit/test_permmanager_migrate_5-7b.js
extensions/permissions/test/unit/test_permmanager_migrate_6-7a.js
extensions/permissions/test/unit/test_permmanager_migrate_6-7b.js
extensions/permissions/test/unit/test_permmanager_migrate_7-8.js
extensions/permissions/test/unit/test_permmanager_migrate_9-10.js
extensions/permissions/test/unit/test_permmanager_removeforapp.js
extensions/permissions/test/unit/xpcshell.ini
ipc/glue/BackgroundUtils.cpp
ipc/glue/ProtocolUtils.h
mobile/android/components/ContentPermissionPrompt.js
netwerk/base/nsILoadContextInfo.idl
netwerk/base/nsNetUtil.h
netwerk/cache2/CacheFileUtils.cpp
netwerk/cache2/CacheStorageService.cpp
netwerk/cookie/nsCookieService.cpp
netwerk/ipc/NeckoParent.cpp
netwerk/protocol/about/nsAboutCache.cpp
netwerk/protocol/http/nsHttpChannel.h
netwerk/test/unit/head_channels.js
netwerk/test/unit/test_auth_jar.js
netwerk/test/unit/test_cache_jar.js
testing/specialpowers/content/SpecialPowersObserver.jsm
testing/specialpowers/content/specialpowersAPI.js
toolkit/components/aboutcache/content/aboutCache.js
toolkit/content/aboutServiceWorkers.js
toolkit/locales/en-US/toolkit/about/aboutServiceWorkers.ftl
--- a/browser/base/content/test/caps/browser_principalSerialization_version1.js
+++ b/browser/base/content/test/caps/browser_principalSerialization_version1.js
@@ -108,81 +108,87 @@ add_task(async function test_realHistory
   */
 
   let serializedPrincipalsFromFirefox = [
     {
       "input": "SmIS26zLEdO3ZQBgsLbOywAAAAAAAAAAwAAAAAAAAEY=",
       "output": {
         "URI": false,
         "originAttributes": {
+          "appId": 0,
           "firstPartyDomain": "",
           "inIsolatedMozBrowser": false,
           "privateBrowsingId": 0,
           "userContextId": 0,
         },
         "cspJSON": "{}",
       },
     },
     {
       "input": "ZT4OTT7kRfqycpfCC8AeuAAAAAAAAAAAwAAAAAAAAEYB3pRy0IA0EdOTmQAQS6D9QJIHOlRteE8wkTq4cYEyCMYAAAAC/////wAAAbsBAAAAHmh0dHBzOi8vd3d3Lm1vemlsbGEub3JnL2VuLVVTLwAAAAAAAAAFAAAACAAAAA8AAAAA/////wAAAAD/////AAAACAAAAA8AAAAXAAAABwAAABcAAAAHAAAAFwAAAAcAAAAeAAAAAAAAAAD/////AAAAAP////8AAAAA/////wAAAAD/////AQAAAAAAAAAAAAAAAQnZ7Rrl1EAEv+Anzrkj2ayzxMCuvV5MrYfgjSENuz+fAd6UctCANBHTk5kAEEug/UCSBzpUbXhPMJE6uHGBMgjGAAAAAv////8AAAG7AQAAAB5odHRwczovL3d3dy5tb3ppbGxhLm9yZy9lbi1VUy8AAAAAAAAABQAAAAgAAAAPAAAAAP////8AAAAA/////wAAAAgAAAAPAAAAFwAAAAcAAAAXAAAABwAAABcAAAAHAAAAHgAAAAAAAAAA/////wAAAAD/////AAAAAP////8AAAAA/////wEAAAAAAAAAAAABAAAFtgBzAGMAcgBpAHAAdAAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AIAAnAHUAbgBzAGEAZgBlAC0AaQBuAGwAaQBuAGUAJwAgACcAdQBuAHMAYQBmAGUALQBlAHYAYQBsACcAIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQB0AGEAZwBtAGEAbgBhAGcAZQByAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQAtAGEAbgBhAGwAeQB0AGkAYwBzAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdABhAGcAbQBhAG4AYQBnAGUAcgAuAGcAbwBvAGcAbABlAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgB5AG8AdQB0AHUAYgBlAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AcwAuAHkAdABpAG0AZwAuAGMAbwBtADsAIABpAG0AZwAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AIABkAGEAdABhADoAIABoAHQAdABwAHMAOgAvAC8AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUAdABhAGcAbQBhAG4AYQBnAGUAcgAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALQBhAG4AYQBsAHkAdABpAGMAcwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAGEAZABzAGUAcgB2AGkAYwBlAC4AZwBvAG8AZwBsAGUALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGQAcwBlAHIAdgBpAGMAZQAuAGcAbwBvAGcAbABlAC4AZABlACAAaAB0AHQAcABzADoALwAvAGEAZABzAGUAcgB2AGkAYwBlAC4AZwBvAG8AZwBsAGUALgBkAGsAIABoAHQAdABwAHMAOgAvAC8AYwByAGUAYQB0AGkAdgBlAGMAbwBtAG0AbwBuAHMALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwBhAGQALgBkAG8AdQBiAGwAZQBjAGwAaQBjAGsALgBuAGUAdAA7ACAAZABlAGYAYQB1AGwAdAAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AOwAgAGYAcgBhAG0AZQAtAHMAcgBjACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUAdABhAGcAbQBhAG4AYQBnAGUAcgAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALQBhAG4AYQBsAHkAdABpAGMAcwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AeQBvAHUAdAB1AGIAZQAtAG4AbwBjAG8AbwBrAGkAZQAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHQAcgBhAGMAawBlAHIAdABlAHMAdAAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AcwB1AHIAdgBlAHkAZwBpAHoAbQBvAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAGEAYwBjAG8AdQBuAHQAcwAuAGYAaQByAGUAZgBvAHgALgBjAG8AbQAuAGMAbgAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQA7ACAAcwB0AHkAbABlAC0AcwByAGMAIAAnAHMAZQBsAGYAJwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG4AZQB0ACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbwByAGcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBjAG8AbQAgACcAdQBuAHMAYQBmAGUALQBpAG4AbABpAG4AZQAnADsAIABjAG8AbgBuAGUAYwB0AC0AcwByAGMAIAAnAHMAZQBsAGYAJwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG4AZQB0ACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbwByAGcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAHQAYQBnAG0AYQBuAGEAZwBlAHIALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC0AYQBuAGEAbAB5AHQAaQBjAHMALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGMAYwBvAHUAbgB0AHMALgBmAGkAcgBlAGYAbwB4AC4AYwBvAG0ALwAgAGgAdAB0AHAAcwA6AC8ALwBhAGMAYwBvAHUAbgB0AHMALgBmAGkAcgBlAGYAbwB4AC4AYwBvAG0ALgBjAG4ALwA7ACAAYwBoAGkAbABkAC0AcwByAGMAIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQB0AGEAZwBtAGEAbgBhAGcAZQByAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQAtAGEAbgBhAGwAeQB0AGkAYwBzAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgB5AG8AdQB0AHUAYgBlAC0AbgBvAGMAbwBvAGsAaQBlAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdAByAGEAYwBrAGUAcgB0AGUAcwB0AC4AbwByAGcAIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBzAHUAcgB2AGUAeQBnAGkAegBtAG8ALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGMAYwBvAHUAbgB0AHMALgBmAGkAcgBlAGYAbwB4AC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtAC4AYwBuACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AeQBvAHUAdAB1AGIAZQAuAGMAbwBtAAA=",
       "output": {
         "cspJSON": "{\"csp-policies\":[{\"child-src\":[\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://www.youtube-nocookie.com\",\"https://trackertest.org\",\"https://www.surveygizmo.com\",\"https://accounts.firefox.com\",\"https://accounts.firefox.com.cn\",\"https://www.youtube.com\"],\"connect-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://accounts.firefox.com/\",\"https://accounts.firefox.com.cn/\"],\"default-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\"],\"frame-src\":[\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://www.youtube-nocookie.com\",\"https://trackertest.org\",\"https://www.surveygizmo.com\",\"https://accounts.firefox.com\",\"https://accounts.firefox.com.cn\",\"https://www.youtube.com\"],\"img-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"data:\",\"https://mozilla.org\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://adservice.google.com\",\"https://adservice.google.de\",\"https://adservice.google.dk\",\"https://creativecommons.org\",\"https://ad.doubleclick.net\"],\"report-only\":false,\"script-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"'unsafe-inline'\",\"'unsafe-eval'\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://tagmanager.google.com\",\"https://www.youtube.com\",\"https://s.ytimg.com\"],\"style-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"'unsafe-inline'\"]}]}",
         "URISpec": "https://www.mozilla.org/en-US/",
         "originAttributes": {
+          "appId": 0,
           "firstPartyDomain": "",
           "inIsolatedMozBrowser": false,
           "privateBrowsingId": 0,
           "userContextId": 0,
         },
       },
     },
     {
       "input": "ZT4OTT7kRfqycpfCC8AeuAAAAAAAAAAAwAAAAAAAAEYB3pRy0IA0EdOTmQAQS6D9QJIHOlRteE8wkTq4cYEyCMYAAAAC/////wAAAbsBAAAAL2h0dHBzOi8vd3d3Lm1vemlsbGEub3JnL2VuLVVTL2ZpcmVmb3gvYWNjb3VudHMvAAAAAAAAAAUAAAAIAAAADwAAAAj/////AAAACP////8AAAAIAAAADwAAABcAAAAYAAAAFwAAABgAAAAXAAAAGAAAAC8AAAAAAAAAL/////8AAAAA/////wAAABf/////AAAAF/////8BAAAAAAAAAAAAAAABCdntGuXUQAS/4CfOuSPZrLPEwK69Xkyth+CNIQ27P58B3pRy0IA0EdOTmQAQS6D9QJIHOlRteE8wkTq4cYEyCMYAAAAC/////wAAAbsBAAAAL2h0dHBzOi8vd3d3Lm1vemlsbGEub3JnL2VuLVVTL2ZpcmVmb3gvYWNjb3VudHMvAAAAAAAAAAUAAAAIAAAADwAAAAj/////AAAACP////8AAAAIAAAADwAAABcAAAAYAAAAFwAAABgAAAAXAAAAGAAAAC8AAAAAAAAAL/////8AAAAA/////wAAABf/////AAAAF/////8BAAAAAAAAAAAAAQAABbYAcwBjAHIAaQBwAHQALQBzAHIAYwAgACcAcwBlAGwAZgAnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbgBlAHQAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAGMAbwBtACAAJwB1AG4AcwBhAGYAZQAtAGkAbgBsAGkAbgBlACcAIAAnAHUAbgBzAGEAZgBlAC0AZQB2AGEAbAAnACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUAdABhAGcAbQBhAG4AYQBnAGUAcgAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALQBhAG4AYQBsAHkAdABpAGMAcwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHQAYQBnAG0AYQBuAGEAZwBlAHIALgBnAG8AbwBnAGwAZQAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AeQBvAHUAdAB1AGIAZQAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHMALgB5AHQAaQBtAGcALgBjAG8AbQA7ACAAaQBtAGcALQBzAHIAYwAgACcAcwBlAGwAZgAnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbgBlAHQAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAGMAbwBtACAAZABhAHQAYQA6ACAAaAB0AHQAcABzADoALwAvAG0AbwB6AGkAbABsAGEALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAHQAYQBnAG0AYQBuAGEAZwBlAHIALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC0AYQBuAGEAbAB5AHQAaQBjAHMALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGQAcwBlAHIAdgBpAGMAZQAuAGcAbwBvAGcAbABlAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBkAHMAZQByAHYAaQBjAGUALgBnAG8AbwBnAGwAZQAuAGQAZQAgAGgAdAB0AHAAcwA6AC8ALwBhAGQAcwBlAHIAdgBpAGMAZQAuAGcAbwBvAGcAbABlAC4AZABrACAAaAB0AHQAcABzADoALwAvAGMAcgBlAGEAdABpAHYAZQBjAG8AbQBtAG8AbgBzAC4AbwByAGcAIABoAHQAdABwAHMAOgAvAC8AYQBkAC4AZABvAHUAYgBsAGUAYwBsAGkAYwBrAC4AbgBlAHQAOwAgAGQAZQBmAGEAdQBsAHQALQBzAHIAYwAgACcAcwBlAGwAZgAnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AbgBlAHQAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAGMAbwBtADsAIABmAHIAYQBtAGUALQBzAHIAYwAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAHQAYQBnAG0AYQBuAGEAZwBlAHIALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAGcAbwBvAGcAbABlAC0AYQBuAGEAbAB5AHQAaQBjAHMALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALQBuAG8AYwBvAG8AawBpAGUALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwB0AHIAYQBjAGsAZQByAHQAZQBzAHQALgBvAHIAZwAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAHMAdQByAHYAZQB5AGcAaQB6AG0AbwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAGEAYwBjAG8AdQBuAHQAcwAuAGYAaQByAGUAZgBvAHgALgBjAG8AbQAgAGgAdAB0AHAAcwA6AC8ALwBhAGMAYwBvAHUAbgB0AHMALgBmAGkAcgBlAGYAbwB4AC4AYwBvAG0ALgBjAG4AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgB5AG8AdQB0AHUAYgBlAC4AYwBvAG0AOwAgAHMAdAB5AGwAZQAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AIAAnAHUAbgBzAGEAZgBlAC0AaQBuAGwAaQBuAGUAJwA7ACAAYwBvAG4AbgBlAGMAdAAtAHMAcgBjACAAJwBzAGUAbABmACcAIABoAHQAdABwAHMAOgAvAC8AKgAuAG0AbwB6AGkAbABsAGEALgBuAGUAdAAgAGgAdAB0AHAAcwA6AC8ALwAqAC4AbQBvAHoAaQBsAGwAYQAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvACoALgBtAG8AegBpAGwAbABhAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQB0AGEAZwBtAGEAbgBhAGcAZQByAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AdwB3AHcALgBnAG8AbwBnAGwAZQAtAGEAbgBhAGwAeQB0AGkAYwBzAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtAC8AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtAC4AYwBuAC8AOwAgAGMAaABpAGwAZAAtAHMAcgBjACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUAdABhAGcAbQBhAG4AYQBnAGUAcgAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AZwBvAG8AZwBsAGUALQBhAG4AYQBsAHkAdABpAGMAcwAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AeQBvAHUAdAB1AGIAZQAtAG4AbwBjAG8AbwBrAGkAZQAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAHQAcgBhAGMAawBlAHIAdABlAHMAdAAuAG8AcgBnACAAaAB0AHQAcABzADoALwAvAHcAdwB3AC4AcwB1AHIAdgBlAHkAZwBpAHoAbQBvAC4AYwBvAG0AIABoAHQAdABwAHMAOgAvAC8AYQBjAGMAbwB1AG4AdABzAC4AZgBpAHIAZQBmAG8AeAAuAGMAbwBtACAAaAB0AHQAcABzADoALwAvAGEAYwBjAG8AdQBuAHQAcwAuAGYAaQByAGUAZgBvAHgALgBjAG8AbQAuAGMAbgAgAGgAdAB0AHAAcwA6AC8ALwB3AHcAdwAuAHkAbwB1AHQAdQBiAGUALgBjAG8AbQAA",
       "output": {
         "URISpec": "https://www.mozilla.org/en-US/firefox/accounts/",
         "originAttributes": {
+          "appId": 0,
           "firstPartyDomain": "",
           "inIsolatedMozBrowser": false,
           "privateBrowsingId": 0,
           "userContextId": 0,
         },
         "cspJSON": "{\"csp-policies\":[{\"child-src\":[\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://www.youtube-nocookie.com\",\"https://trackertest.org\",\"https://www.surveygizmo.com\",\"https://accounts.firefox.com\",\"https://accounts.firefox.com.cn\",\"https://www.youtube.com\"],\"connect-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://accounts.firefox.com/\",\"https://accounts.firefox.com.cn/\"],\"default-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\"],\"frame-src\":[\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://www.youtube-nocookie.com\",\"https://trackertest.org\",\"https://www.surveygizmo.com\",\"https://accounts.firefox.com\",\"https://accounts.firefox.com.cn\",\"https://www.youtube.com\"],\"img-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"data:\",\"https://mozilla.org\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://adservice.google.com\",\"https://adservice.google.de\",\"https://adservice.google.dk\",\"https://creativecommons.org\",\"https://ad.doubleclick.net\"],\"report-only\":false,\"script-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"'unsafe-inline'\",\"'unsafe-eval'\",\"https://www.googletagmanager.com\",\"https://www.google-analytics.com\",\"https://tagmanager.google.com\",\"https://www.youtube.com\",\"https://s.ytimg.com\"],\"style-src\":[\"'self'\",\"https://*.mozilla.net\",\"https://*.mozilla.org\",\"https://*.mozilla.com\",\"'unsafe-inline'\"]}]}",
       },
     },
     {
       "input": "ZT4OTT7kRfqycpfCC8AeuAAAAAAAAAAAwAAAAAAAAEYB3pRy0IA0EdOTmQAQS6D9QJIHOlRteE8wkTq4cYEyCMYAAAAC/////wAAAbsBAAAAe2h0dHBzOi8vZGV2ZWxvcGVyLm1vemlsbGEub3JnL2VuLVVTLz91dG1fc291cmNlPXd3dy5tb3ppbGxhLm9yZyZ1dG1fbWVkaXVtPXJlZmVycmFsJnV0bV9jYW1wYWlnbj1uYXYmdXRtX2NvbnRlbnQ9ZGV2ZWxvcGVycwAAAAAAAAAFAAAACAAAABUAAAAA/////wAAAAD/////AAAACAAAABUAAAAdAAAAXgAAAB0AAAAHAAAAHQAAAAcAAAAkAAAAAAAAAAD/////AAAAAP////8AAAAlAAAAVgAAAAD/////AQAAAAAAAAAAAAAAAA==",
       "output": {
         "URISpec": "https://developer.mozilla.org/en-US/?utm_source=www.mozilla.org&utm_medium=referral&utm_campaign=nav&utm_content=developers",
         "originAttributes": {
+          "appId": 0,
           "firstPartyDomain": "",
           "inIsolatedMozBrowser": false,
           "privateBrowsingId": 0,
           "userContextId": 0,
         },
         "cspJSON": "{}",
       },
     },
     {
       "input": "SmIS26zLEdO3ZQBgsLbOywAAAAAAAAAAwAAAAAAAAEY=",
       "output": {
         "URI": false,
         "originAttributes": {
+          "appId": 0,
           "firstPartyDomain": "",
           "inIsolatedMozBrowser": false,
           "privateBrowsingId": 0,
           "userContextId": 0,
         },
         "cspJSON": "{}",
       },
     },
     {
       "input": "vQZuXxRvRHKDMXv9BbHtkAAAAAAAAAAAwAAAAAAAAEYAAAA4bW96LW51bGxwcmluY2lwYWw6ezA0NWNhMThkLTQzNmMtNDc0NC1iYmI2LWIxYTE1MzY2ZGY3OX0AAAAA",
       "output": {
         "URISpec": "moz-nullprincipal:{045ca18d-436c-4744-bbb6-b1a15366df79}",
         "originAttributes": {
+          "appId": 0,
           "firstPartyDomain": "",
           "inIsolatedMozBrowser": false,
           "privateBrowsingId": 0,
           "userContextId": 0,
         },
         "cspJSON": "{}",
       },
     },
--- a/browser/components/sessionstore/test/browser_restore_cookies_noOriginAttributes.js
+++ b/browser/components/sessionstore/test/browser_restore_cookies_noOriginAttributes.js
@@ -102,16 +102,17 @@ const SESSION_DATA_OA = JSON.stringify(
     sizemode: "normal",
     cookies: [{
       host: "www.example.com",
       value: "yes1",
       path: "/browser/browser/components/sessionstore/test/",
       name: "test1",
       originAttributes: {
         addonId: "",
+        appId: 0,
         inIsolatedMozBrowser: false,
         userContextId: 0,
       },
     }],
   }],
   selectedWindow: 1,
   _closedWindows: [],
   session: {
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -289,16 +289,28 @@ BasePrincipal::GetOriginAttributes(JSCon
 NS_IMETHODIMP
 BasePrincipal::GetOriginSuffix(nsACString& aOriginAttributes) {
   MOZ_ASSERT(mOriginSuffix);
   mOriginSuffix->ToUTF8String(aOriginAttributes);
   return NS_OK;
 }
 
 NS_IMETHODIMP
+BasePrincipal::GetAppId(uint32_t* aAppId) {
+  if (AppId() == nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+    MOZ_ASSERT(false);
+    *aAppId = nsIScriptSecurityManager::NO_APP_ID;
+    return NS_OK;
+  }
+
+  *aAppId = AppId();
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 BasePrincipal::GetUserContextId(uint32_t* aUserContextId) {
   *aUserContextId = UserContextId();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 BasePrincipal::GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) {
   *aPrivateBrowsingId = PrivateBrowsingId();
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -117,16 +117,17 @@ class BasePrincipal : public nsJSPrincip
   NS_IMETHOD GetIsNullPrincipal(bool* aResult) override;
   NS_IMETHOD GetIsCodebasePrincipal(bool* aResult) override;
   NS_IMETHOD GetIsExpandedPrincipal(bool* aResult) override;
   NS_IMETHOD GetIsSystemPrincipal(bool* aResult) override;
   NS_IMETHOD GetIsAddonOrExpandedAddonPrincipal(bool* aResult) override;
   NS_IMETHOD GetOriginAttributes(JSContext* aCx,
                                  JS::MutableHandle<JS::Value> aVal) final;
   NS_IMETHOD GetOriginSuffix(nsACString& aOriginSuffix) final;
+  NS_IMETHOD GetAppId(uint32_t* aAppId) final;
   NS_IMETHOD GetIsInIsolatedMozBrowserElement(
       bool* aIsInIsolatedMozBrowserElement) final;
   NS_IMETHOD GetUserContextId(uint32_t* aUserContextId) final;
   NS_IMETHOD GetPrivateBrowsingId(uint32_t* aPrivateBrowsingId) final;
   NS_IMETHOD GetSiteOrigin(nsACString& aOrigin) override;
 
   virtual bool AddonHasPermission(const nsAtom* aPerm);
 
@@ -148,16 +149,17 @@ class BasePrincipal : public nsJSPrincip
   // happens, a NullPrincipal is returned.
 
   static already_AddRefed<BasePrincipal> CreateCodebasePrincipal(
       nsIURI* aURI, const OriginAttributes& aAttrs);
 
   const OriginAttributes& OriginAttributesRef() final {
     return mOriginAttributes;
   }
+  uint32_t AppId() const { return mOriginAttributes.mAppId; }
   extensions::WebExtensionPolicy* AddonPolicy();
   uint32_t UserContextId() const { return mOriginAttributes.mUserContextId; }
   uint32_t PrivateBrowsingId() const {
     return mOriginAttributes.mPrivateBrowsingId;
   }
   bool IsInIsolatedMozBrowserElement() const {
     return mOriginAttributes.mInIsolatedMozBrowser;
   }
--- a/caps/OriginAttributes.cpp
+++ b/caps/OriginAttributes.cpp
@@ -128,16 +128,21 @@ void OriginAttributes::CreateSuffix(nsAC
 
   //
   // Important: While serializing any string-valued attributes, perform a
   // release-mode assertion to make sure that they don't contain characters that
   // will break the quota manager when it uses the serialization for file
   // naming.
   //
 
+  if (mAppId != nsIScriptSecurityManager::NO_APP_ID) {
+    value.AppendInt(mAppId);
+    params.Set(NS_LITERAL_STRING("appId"), value);
+  }
+
   if (mInIsolatedMozBrowser) {
     params.Set(NS_LITERAL_STRING("inBrowser"), NS_LITERAL_STRING("1"));
   }
 
   if (mUserContextId != nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID) {
     value.Truncate();
     value.AppendInt(mUserContextId);
     params.Set(NS_LITERAL_STRING("userContextId"), value);
@@ -197,26 +202,36 @@ class MOZ_STACK_CLASS PopulateFromSuffix
     // If mPrivateBrowsingId is passed in as >0 and is not present in the
     // suffix, then it will remain >0 when it should be 0 according to the
     // suffix. Set to 0 before iterating to fix this.
     mOriginAttributes->mPrivateBrowsingId = 0;
   }
 
   bool URLParamsIterator(const nsAString& aName,
                          const nsAString& aValue) override {
+    if (aName.EqualsLiteral("appId")) {
+      nsresult rv;
+      int64_t val = aValue.ToInteger64(&rv);
+      NS_ENSURE_SUCCESS(rv, false);
+      NS_ENSURE_TRUE(val <= UINT32_MAX, false);
+      mOriginAttributes->mAppId = static_cast<uint32_t>(val);
+
+      return true;
+    }
+
     if (aName.EqualsLiteral("inBrowser")) {
       if (!aValue.EqualsLiteral("1")) {
         return false;
       }
 
       mOriginAttributes->mInIsolatedMozBrowser = true;
       return true;
     }
 
-    if (aName.EqualsLiteral("addonId") || aName.EqualsLiteral("appId")) {
+    if (aName.EqualsLiteral("addonId")) {
       // No longer supported. Silently ignore so that legacy origin strings
       // don't cause failures.
       return true;
     }
 
     if (aName.EqualsLiteral("userContextId")) {
       nsresult rv;
       int64_t val = aValue.ToInteger64(&rv);
--- a/caps/OriginAttributes.h
+++ b/caps/OriginAttributes.h
@@ -12,17 +12,18 @@
 #include "nsIScriptSecurityManager.h"
 
 namespace mozilla {
 
 class OriginAttributes : public dom::OriginAttributesDictionary {
  public:
   OriginAttributes() {}
 
-  explicit OriginAttributes(bool aInIsolatedMozBrowser) {
+  OriginAttributes(uint32_t aAppId, bool aInIsolatedMozBrowser) {
+    mAppId = aAppId;
     mInIsolatedMozBrowser = aInIsolatedMozBrowser;
   }
 
   explicit OriginAttributes(const OriginAttributesDictionary& aOther)
       : OriginAttributesDictionary(aOther) {}
 
   void SetFirstPartyDomain(const bool aIsTopLevelDocument, nsIURI* aURI,
                            bool aForced = false);
@@ -40,28 +41,30 @@ class OriginAttributes : public dom::Ori
     }
 
     if (aFlags & STRIP_USER_CONTEXT_ID) {
       mUserContextId = nsIScriptSecurityManager::DEFAULT_USER_CONTEXT_ID;
     }
   }
 
   bool operator==(const OriginAttributes& aOther) const {
-    return mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser &&
+    return mAppId == aOther.mAppId &&
+           mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser &&
            mUserContextId == aOther.mUserContextId &&
            mPrivateBrowsingId == aOther.mPrivateBrowsingId &&
            mFirstPartyDomain == aOther.mFirstPartyDomain;
   }
 
   bool operator!=(const OriginAttributes& aOther) const {
     return !(*this == aOther);
   }
 
   MOZ_MUST_USE bool EqualsIgnoringFPD(const OriginAttributes& aOther) const {
-    return mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser &&
+    return mAppId == aOther.mAppId &&
+           mInIsolatedMozBrowser == aOther.mInIsolatedMozBrowser &&
            mUserContextId == aOther.mUserContextId &&
            mPrivateBrowsingId == aOther.mPrivateBrowsingId;
   }
 
   // Serializes/Deserializes non-default values into the suffix format, i.e.
   // |!key1=value1&key2=value2|. If there are no non-default attributes, this
   // returns an empty string.
   void CreateSuffix(nsACString& aStr) const;
@@ -121,16 +124,20 @@ class OriginAttributesPattern : public d
   OriginAttributesPattern() {}
 
   explicit OriginAttributesPattern(
       const OriginAttributesPatternDictionary& aOther)
       : OriginAttributesPatternDictionary(aOther) {}
 
   // Performs a match of |aAttrs| against this pattern.
   bool Matches(const OriginAttributes& aAttrs) const {
+    if (mAppId.WasPassed() && mAppId.Value() != aAttrs.mAppId) {
+      return false;
+    }
+
     if (mInIsolatedMozBrowser.WasPassed() &&
         mInIsolatedMozBrowser.Value() != aAttrs.mInIsolatedMozBrowser) {
       return false;
     }
 
     if (mUserContextId.WasPassed() &&
         mUserContextId.Value() != aAttrs.mUserContextId) {
       return false;
@@ -145,16 +152,21 @@ class OriginAttributesPattern : public d
         mFirstPartyDomain.Value() != aAttrs.mFirstPartyDomain) {
       return false;
     }
 
     return true;
   }
 
   bool Overlaps(const OriginAttributesPattern& aOther) const {
+    if (mAppId.WasPassed() && aOther.mAppId.WasPassed() &&
+        mAppId.Value() != aOther.mAppId.Value()) {
+      return false;
+    }
+
     if (mInIsolatedMozBrowser.WasPassed() &&
         aOther.mInIsolatedMozBrowser.WasPassed() &&
         mInIsolatedMozBrowser.Value() != aOther.mInIsolatedMozBrowser.Value()) {
       return false;
     }
 
     if (mUserContextId.WasPassed() && aOther.mUserContextId.WasPassed() &&
         mUserContextId.Value() != aOther.mUserContextId.Value()) {
--- a/caps/nsIPrincipal.idl
+++ b/caps/nsIPrincipal.idl
@@ -263,16 +263,34 @@ interface nsIPrincipal : nsISerializable
     /**
      * The base domain of the codebase URI to which this principal pertains
      * (generally the document URI), handling null principals and
      * non-hierarchical schemes correctly.
      */
     readonly attribute ACString baseDomain;
 
     /**
+     * Gets the id of the app this principal is inside.  If this principal is
+     * not inside an app, returns nsIScriptSecurityManager::NO_APP_ID.
+     *
+     * Note that this principal does not necessarily have the permissions of
+     * the app identified by appId.  For example, this principal might
+     * correspond to an iframe whose origin differs from that of the app frame
+     * containing it.  In this case, the iframe will have the appId of its
+     * containing app frame, but the iframe must not run with the app's
+     * permissions.
+     *
+     * Similarly, this principal might correspond to an <iframe mozbrowser>
+     * inside an app frame; in this case, the content inside the iframe should
+     * not have any of the app's permissions, even if the iframe is at the same
+     * origin as the app.
+     */
+    [infallible] readonly attribute unsigned long appId;
+
+    /**
      * Gets the ID of the add-on this principal belongs to.
      */
     readonly attribute AString addonId;
 
     readonly attribute nsISupports addonPolicy;
 
     /**
      * Gets the id of the user context this principal is inside.  If this
--- a/caps/nsIScriptSecurityManager.idl
+++ b/caps/nsIScriptSecurityManager.idl
@@ -241,16 +241,19 @@ interface nsIScriptSecurityManager : nsI
     nsIPrincipal getChannelResultPrincipalIfNotSandboxed(in nsIChannel aChannel);
 
     /**
      * Get the codebase principal for the channel's URI.
      * aChannel must not be null.
      */
     nsIPrincipal getChannelURIPrincipal(in nsIChannel aChannel);
 
+    const unsigned long NO_APP_ID = 0;
+    const unsigned long UNKNOWN_APP_ID = 4294967295; // UINT32_MAX
+
     const unsigned long DEFAULT_USER_CONTEXT_ID = 0;
 
     /**
      * Per-domain controls to enable and disable script. This system is designed
      * to be used by at most one consumer, and enforces this with its semantics.
      *
      * Initially, domainPolicyActive is false. When activateDomainPolicy() is
      * invoked, domainPolicyActive becomes true, and subsequent calls to
--- a/caps/tests/gtest/TestOriginAttributes.cpp
+++ b/caps/tests/gtest/TestOriginAttributes.cpp
@@ -29,19 +29,25 @@ static void TestFPD(const nsAString& spe
 }
 
 TEST(OriginAttributes, Suffix_default)
 {
   OriginAttributes attrs;
   TestSuffix(attrs);
 }
 
-TEST(OriginAttributes, Suffix_inIsolatedMozBrowser)
+TEST(OriginAttributes, Suffix_appId_inIsolatedMozBrowser)
 {
-  OriginAttributes attrs(true);
+  OriginAttributes attrs(1, true);
+  TestSuffix(attrs);
+}
+
+TEST(OriginAttributes, Suffix_maxAppId_inIsolatedMozBrowser)
+{
+  OriginAttributes attrs(4294967295, true);
   TestSuffix(attrs);
 }
 
 TEST(OriginAttributes, FirstPartyDomain_default)
 {
   static const char prefKey[] = "privacy.firstparty.isolate";
   bool oldPref = Preferences::GetBool(prefKey);
   Preferences::SetBool(prefKey, true);
--- a/caps/tests/unit/test_origin.js
+++ b/caps/tests/unit/test_origin.js
@@ -14,16 +14,17 @@ function checkCrossOrigin(a, b) {
   Assert.ok(!a.subsumes(b));
   Assert.ok(!a.subsumesConsideringDomain(b));
   Assert.ok(!b.subsumes(a));
   Assert.ok(!b.subsumesConsideringDomain(a));
 }
 
 function checkOriginAttributes(prin, attrs, suffix) {
   attrs = attrs || {};
+  Assert.equal(prin.originAttributes.appId, attrs.appId || 0);
   Assert.equal(prin.originAttributes.inIsolatedMozBrowser, attrs.inIsolatedMozBrowser || false);
   Assert.equal(prin.originSuffix, suffix || "");
   Assert.equal(ChromeUtils.originAttributesToSuffix(attrs), suffix || "");
   Assert.ok(ChromeUtils.originAttributesMatchPattern(prin.originAttributes, attrs));
   if (!prin.isNullPrincipal && !prin.origin.startsWith("[")) {
     Assert.ok(ssm.createCodebasePrincipalFromOrigin(prin.origin).equals(prin));
   } else {
     checkThrows(() => ssm.createCodebasePrincipalFromOrigin(prin.origin));
@@ -35,27 +36,29 @@ function checkSandboxOriginAttributes(ar
   var sandbox = Cu.Sandbox(arr, options);
   checkOriginAttributes(Cu.getObjectPrincipal(sandbox), attrs,
                         ChromeUtils.originAttributesToSuffix(attrs));
 }
 
 // utility function useful for debugging
 function printAttrs(name, attrs) {
   info(name + " {\n" +
+       "\tappId: " + attrs.appId + ",\n" +
        "\tuserContextId: " + attrs.userContextId + ",\n" +
        "\tinIsolatedMozBrowser: " + attrs.inIsolatedMozBrowser + ",\n" +
        "\tprivateBrowsingId: '" + attrs.privateBrowsingId + "',\n" +
        "\tfirstPartyDomain: '" + attrs.firstPartyDomain + "'\n}");
 }
 
 
 function checkValues(attrs, values) {
   values = values || {};
   // printAttrs("attrs", attrs);
   // printAttrs("values", values);
+  Assert.equal(attrs.appId, values.appId || 0);
   Assert.equal(attrs.userContextId, values.userContextId || 0);
   Assert.equal(attrs.inIsolatedMozBrowser, values.inIsolatedMozBrowser || false);
   Assert.equal(attrs.privateBrowsingId, values.privateBrowsingId || "");
   Assert.equal(attrs.firstPartyDomain, values.firstPartyDomain || "");
 }
 
 function run_test() {
   // Attributeless origins.
@@ -86,43 +89,92 @@ function run_test() {
 
   // Make sure createCodebasePrincipal does what the rest of gecko does.
   Assert.ok(exampleOrg.equals(Cu.getObjectPrincipal(new Cu.Sandbox("http://example.org"))));
 
   //
   // Test origin attributes.
   //
 
+  // Just app.
+  var exampleOrg_app = ssm.createCodebasePrincipal(makeURI("http://example.org"), {appId: 42});
+  var nullPrin_app = ssm.createNullPrincipal({appId: 42});
+  checkOriginAttributes(exampleOrg_app, {appId: 42}, "^appId=42");
+  checkOriginAttributes(nullPrin_app, {appId: 42}, "^appId=42");
+  Assert.equal(exampleOrg_app.origin, "http://example.org^appId=42");
+
   // Just browser.
   var exampleOrg_browser = ssm.createCodebasePrincipal(makeURI("http://example.org"), {inIsolatedMozBrowser: true});
   var nullPrin_browser = ssm.createNullPrincipal({inIsolatedMozBrowser: true});
   checkOriginAttributes(exampleOrg_browser, {inIsolatedMozBrowser: true}, "^inBrowser=1");
   checkOriginAttributes(nullPrin_browser, {inIsolatedMozBrowser: true}, "^inBrowser=1");
   Assert.equal(exampleOrg_browser.origin, "http://example.org^inBrowser=1");
 
+  // App and browser.
+  var exampleOrg_appBrowser = ssm.createCodebasePrincipal(makeURI("http://example.org"), {inIsolatedMozBrowser: true, appId: 42});
+  var nullPrin_appBrowser = ssm.createNullPrincipal({inIsolatedMozBrowser: true, appId: 42});
+  checkOriginAttributes(exampleOrg_appBrowser, {appId: 42, inIsolatedMozBrowser: true}, "^appId=42&inBrowser=1");
+  checkOriginAttributes(nullPrin_appBrowser, {appId: 42, inIsolatedMozBrowser: true}, "^appId=42&inBrowser=1");
+  Assert.equal(exampleOrg_appBrowser.origin, "http://example.org^appId=42&inBrowser=1");
+
+  // App and browser, different domain.
+  var exampleCom_appBrowser = ssm.createCodebasePrincipal(makeURI("https://www.example.com:123"), {appId: 42, inIsolatedMozBrowser: true});
+  checkOriginAttributes(exampleCom_appBrowser, {appId: 42, inIsolatedMozBrowser: true}, "^appId=42&inBrowser=1");
+  Assert.equal(exampleCom_appBrowser.origin, "https://www.example.com:123^appId=42&inBrowser=1");
+
   // First party Uri
   var exampleOrg_firstPartyDomain = ssm.createCodebasePrincipal(makeURI("http://example.org"), {firstPartyDomain: "example.org"});
   checkOriginAttributes(exampleOrg_firstPartyDomain, { firstPartyDomain: "example.org" }, "^firstPartyDomain=example.org");
   Assert.equal(exampleOrg_firstPartyDomain.origin, "http://example.org^firstPartyDomain=example.org");
 
+  // Make sure we don't crash when serializing principals with UNKNOWN_APP_ID.
+  try {
+    let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
+                       createInstance(Ci.nsIObjectOutputStream);
+    let pipe = Cc["@mozilla.org/pipe;1"].createInstance(Ci.nsIPipe);
+    pipe.init(false, false, 0, 0xffffffff, null);
+    binaryStream.setOutputStream(pipe.outputStream);
+    binaryStream.writeCompoundObject(simplePrin, Ci.nsISupports, true); // eslint-disable-line no-undef
+    binaryStream.close();
+  } catch (e) {
+    Assert.ok(true);
+  }
+
+
   // Just userContext.
   var exampleOrg_userContext = ssm.createCodebasePrincipal(makeURI("http://example.org"), {userContextId: 42});
   checkOriginAttributes(exampleOrg_userContext, { userContextId: 42 }, "^userContextId=42");
   Assert.equal(exampleOrg_userContext.origin, "http://example.org^userContextId=42");
 
+  // 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");
+  Assert.equal(exampleOrg_userContextApp.origin, "http://example.org^appId=24&userContextId=42");
+
   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}});
 
   // 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_firstPartyDomain, exampleOrg);
   checkCrossOrigin(exampleOrg_userContext, exampleOrg);
+  checkCrossOrigin(exampleOrg_userContext, exampleOrg_userContextApp);
 
   // Check Principal kinds.
   function checkKind(prin, kind) {
     Assert.equal(prin.isNullPrincipal, kind == "nullPrincipal");
     Assert.equal(prin.isCodebasePrincipal, kind == "codebasePrincipal");
     Assert.equal(prin.isExpandedPrincipal, kind == "expandedPrincipal");
     Assert.equal(prin.isSystemPrincipal, kind == "systemPrincipal");
   }
@@ -138,42 +190,71 @@ function run_test() {
   // check that we can create an empty origin attributes dict with default
   // members and values.
   var emptyAttrs = ChromeUtils.fillNonDefaultOriginAttributes({});
   checkValues(emptyAttrs);
 
   var uri = "http://example.org";
   var tests = [
     [ "", {} ],
+    [ "^appId=5", {appId: 5} ],
     [ "^userContextId=3", {userContextId: 3} ],
     [ "^inBrowser=1", {inIsolatedMozBrowser: true} ],
-    [ "^firstPartyDomain=example.org", {firstPartyDomain: "example.org"} ] ];
+    [ "^firstPartyDomain=example.org", {firstPartyDomain: "example.org"} ],
+    [ "^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]);
     Assert.equal(ChromeUtils.originAttributesToSuffix(attrs), t[0]);
   });
 
   // check that we can create an origin attributes from a dict properly
   tests.forEach(t => {
     let attrs = ChromeUtils.fillNonDefaultOriginAttributes(t[1]);
     checkValues(attrs, t[1]);
     Assert.equal(ChromeUtils.originAttributesToSuffix(attrs), t[0]);
   });
 
+  // each row in the set_tests array has these values:
+  // [0] - the suffix used to create an origin attribute from
+  // [1] - the expected result of creating an origin attribute from [0]
+  // [2] - the pattern to set on the origin attributes
+  // [3] - the expected result of setting [2] values on [1]
+  // [4] - the expected result of creating a suffix from [3]
+  var set_tests = [
+    [ "", {}, {appId: 5}, {appId: 5}, "^appId=5" ],
+    [ "^appId=5", {appId: 5}, {appId: 3}, {appId: 3}, "^appId=3" ],
+    [ "^appId=5", {appId: 5}, {userContextId: 3}, {appId: 5, userContextId: 3}, "^appId=5&userContextId=3" ],
+    [ "^appId=5", {appId: 5}, {appId: 3, userContextId: 7}, {appId: 3, userContextId: 7}, "^appId=3&userContextId=7" ] ];
+
+  // check that we can set origin attributes values properly
+  set_tests.forEach(t => {
+    let orig = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]);
+    checkValues(orig, t[1]);
+    let mod = orig;
+    for (var key in t[2]) {
+      mod[key] = t[2][key];
+    }
+    checkValues(mod, t[3]);
+    Assert.equal(ChromeUtils.originAttributesToSuffix(mod), t[4]);
+  });
+
   // each row in the dflt_tests array has these values:
   // [0] - the suffix used to create an origin attribute from
   // [1] - the expected result of creating an origin attributes from [0]
   // [2] - the expected result after setting userContextId to the default
   // [3] - the expected result of creating a suffix from [2]
   var dflt_tests = [
     [ "", {}, {}, "" ],
-    [ "^userContextId=3", {userContextId: 3}, {}, "" ] ];
+    [ "^userContextId=3", {userContextId: 3}, {}, "" ],
+    [ "^appId=5", {appId: 5}, {appId: 5}, "^appId=5" ],
+    [ "^appId=5&userContextId=3", {appId: 5, userContextId: 3}, {appId: 5}, "^appId=5" ] ];
 
   // check that we can set the userContextId to default properly
   dflt_tests.forEach(t => {
     let orig = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]);
     checkValues(orig, t[1]);
     let mod = orig;
     mod.userContextId = 0;
     checkValues(mod, t[2]);
@@ -182,17 +263,19 @@ function run_test() {
 
   // each row in the dflt2_tests array has these values:
   // [0] - the suffix used to create an origin attribute from
   // [1] - the expected result of creating an origin attributes from [0]
   // [2] - the expected result after setting firstPartyUri to the default
   // [3] - the expected result of creating a suffix from [2]
   var dflt2_tests = [
     [ "", {}, {}, "" ],
-    [ "^firstPartyDomain=foo.com", {firstPartyDomain: "foo.com"}, {}, "" ] ];
+    [ "^firstPartyDomain=foo.com", {firstPartyDomain: "foo.com"}, {}, "" ],
+    [ "^appId=5", {appId: 5}, {appId: 5}, "^appId=5" ],
+    [ "^appId=5&firstPartyDomain=foo.com", {appId: 5, firstPartyDomain: "foo.com"}, {appId: 5}, "^appId=5" ] ];
 
   // check that we can set the userContextId to default properly
   dflt2_tests.forEach(t => {
     let orig = ChromeUtils.createOriginAttributesFromOrigin(uri + t[0]);
     checkValues(orig, t[1]);
     let mod = orig;
     mod.firstPartyDomain = "";
     checkValues(mod, t[2]);
--- a/docshell/base/LoadContext.h
+++ b/docshell/base/LoadContext.h
@@ -28,33 +28,33 @@ namespace mozilla {
  */
 
 class LoadContext final : public nsILoadContext, public nsIInterfaceRequestor {
  public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSILOADCONTEXT
   NS_DECL_NSIINTERFACEREQUESTOR
 
-  // inIsolatedMozBrowser argumentsoverrides that in
+  // appId/inIsolatedMozBrowser arguments override those in
   // SerializedLoadContext provided by child process.
   LoadContext(const IPC::SerializedLoadContext& aToCopy,
               dom::Element* aTopFrameElement, OriginAttributes& aAttrs)
       : mTopFrameElement(do_GetWeakReference(aTopFrameElement)),
         mNestedFrameId(0),
         mIsContent(aToCopy.mIsContent),
         mUseRemoteTabs(aToCopy.mUseRemoteTabs),
         mUseRemoteSubframes(aToCopy.mUseRemoteSubframes),
         mUseTrackingProtection(aToCopy.mUseTrackingProtection),
 #ifdef DEBUG
         mIsNotNull(aToCopy.mIsNotNull),
 #endif
         mOriginAttributes(aAttrs) {
   }
 
-  // inIsolatedMozBrowser argument overrides that in
+  // appId/inIsolatedMozBrowser arguments override those in
   // SerializedLoadContext provided by child process.
   LoadContext(const IPC::SerializedLoadContext& aToCopy,
               uint64_t aNestedFrameId, OriginAttributes& aAttrs)
       : mTopFrameElement(nullptr),
         mNestedFrameId(aNestedFrameId),
         mIsContent(aToCopy.mIsContent),
         mUseRemoteTabs(aToCopy.mUseRemoteTabs),
         mUseRemoteSubframes(aToCopy.mUseRemoteSubframes),
@@ -92,17 +92,18 @@ class LoadContext final : public nsILoad
         mUseRemoteSubframes(false),
         mUseTrackingProtection(false),
 #ifdef DEBUG
         mIsNotNull(true),
 #endif
         mOriginAttributes(aAttrs) {
   }
 
-  // Constructor for creating a LoadContext with a given browser flag.
+  // Constructor for creating a LoadContext with a given principal's appId and
+  // browser flag.
   explicit LoadContext(nsIPrincipal* aPrincipal,
                        nsILoadContext* aOptionalBase = nullptr);
 
  private:
   ~LoadContext() {}
 
   nsWeakPtr mTopFrameElement;
   uint64_t mNestedFrameId;
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -227,17 +227,17 @@ class nsDocShell final : public nsDocLoa
       bool aIsUserTriggered = false,
       nsIPrincipal* aTriggeringPrincipal = nullptr,
       nsIContentSecurityPolicy* aCsp = nullptr) override;
   NS_IMETHOD OnOverLink(nsIContent* aContent, nsIURI* aURI,
                         const nsAString& aTargetSpec) override;
   NS_IMETHOD OnLeaveLink() override;
 
   // Don't use NS_DECL_NSILOADCONTEXT because some of nsILoadContext's methods
-  // are shared with nsIDocShell and can't be declared twice.
+  // are shared with nsIDocShell (appID, etc.) and can't be declared twice.
   NS_IMETHOD GetAssociatedWindow(mozIDOMWindowProxy**) override;
   NS_IMETHOD GetTopWindow(mozIDOMWindowProxy**) override;
   NS_IMETHOD GetTopFrameElement(mozilla::dom::Element**) override;
   NS_IMETHOD GetNestedFrameId(uint64_t*) override;
   NS_IMETHOD GetIsContent(bool*) override;
   NS_IMETHOD GetUsePrivateBrowsing(bool*) override;
   NS_IMETHOD SetUsePrivateBrowsing(bool) override;
   NS_IMETHOD SetPrivateBrowsing(bool) override;
--- a/dom/base/ChromeUtils.h
+++ b/dom/base/ChromeUtils.h
@@ -95,17 +95,18 @@ class ChromeUtils {
 
   static bool IsOriginAttributesEqual(
       const dom::OriginAttributesDictionary& aA,
       const dom::OriginAttributesDictionary& aB);
 
   static bool IsOriginAttributesEqualIgnoringFPD(
       const dom::OriginAttributesDictionary& aA,
       const dom::OriginAttributesDictionary& aB) {
-    return aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
+    return aA.mAppId == aB.mAppId &&
+           aA.mInIsolatedMozBrowser == aB.mInIsolatedMozBrowser &&
            aA.mUserContextId == aB.mUserContextId &&
            aA.mPrivateBrowsingId == aB.mPrivateBrowsingId;
   }
 
   // Implemented in js/xpconnect/loader/ChromeScriptLoader.cpp
   static already_AddRefed<Promise> CompileScript(
       GlobalObject& aGlobal, const nsAString& aUrl,
       const dom::CompileScriptOptionsDictionary& aOptions, ErrorResult& aRv);
--- a/dom/base/Document.cpp
+++ b/dom/base/Document.cpp
@@ -2580,16 +2580,20 @@ nsresult Document::StartDocumentLoad(con
   if (MOZ_LOG_TEST(gDocumentLeakPRLog, LogLevel::Debug)) {
     nsCOMPtr<nsIURI> uri;
     aChannel->GetURI(getter_AddRefs(uri));
     MOZ_LOG(gDocumentLeakPRLog, LogLevel::Debug,
             ("DOCUMENT %p StartDocumentLoad %s", this,
              uri ? uri->GetSpecOrDefault().get() : ""));
   }
 
+  MOZ_ASSERT(
+      NodePrincipal()->GetAppId() != nsIScriptSecurityManager::UNKNOWN_APP_ID,
+      "Document should never have UNKNOWN_APP_ID");
+
   MOZ_ASSERT(GetReadyStateEnum() == Document::READYSTATE_UNINITIALIZED,
              "Bad readyState");
   SetReadyStateInternal(READYSTATE_LOADING);
 
   if (nsCRT::strcmp(kLoadAsData, aCommand) == 0) {
     mLoadedAsData = true;
     // We need to disable script & style loading in this case.
     // We leave them disabled even in EndLoad(), and let anyone
--- a/dom/base/PostMessageEvent.cpp
+++ b/dom/base/PostMessageEvent.cpp
@@ -94,16 +94,19 @@ PostMessageEvent::Run() {
     //       don't do that in other places it seems better to hold the line for
     //       now.  Long-term, we want HTML5 to address this so that we can
     //       be compliant while being safer.
     if (!targetPrin->Equals(mProvidedPrincipal)) {
       OriginAttributes sourceAttrs = mProvidedPrincipal->OriginAttributesRef();
       OriginAttributes targetAttrs = targetPrin->OriginAttributesRef();
 
       MOZ_DIAGNOSTIC_ASSERT(
+          sourceAttrs.mAppId == targetAttrs.mAppId,
+          "Target and source should have the same mAppId attribute.");
+      MOZ_DIAGNOSTIC_ASSERT(
           sourceAttrs.mUserContextId == targetAttrs.mUserContextId,
           "Target and source should have the same userContextId attribute.");
       MOZ_DIAGNOSTIC_ASSERT(sourceAttrs.mInIsolatedMozBrowser ==
                                 targetAttrs.mInIsolatedMozBrowser,
                             "Target and source should have the same "
                             "inIsolatedMozBrowser attribute.");
 
       nsAutoString providedOrigin, targetOrigin;
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -2213,30 +2213,33 @@ nsresult nsFrameLoader::MaybeCreateDocSh
       !OwnerIsMozBrowserFrame()) {
     OriginAttributes oa = doc->NodePrincipal()->OriginAttributesRef();
 
     // Assert on the firstPartyDomain from top-level docshell should be empty
     MOZ_ASSERT_IF(mIsTopLevelContent, attrs.mFirstPartyDomain.IsEmpty());
 
     // So far we want to make sure Inherit doesn't override any other origin
     // attribute than firstPartyDomain.
+    MOZ_ASSERT(attrs.mAppId == oa.mAppId,
+               "docshell and document should have the same appId attribute.");
     MOZ_ASSERT(
         attrs.mUserContextId == oa.mUserContextId,
         "docshell and document should have the same userContextId attribute.");
     MOZ_ASSERT(attrs.mInIsolatedMozBrowser == oa.mInIsolatedMozBrowser,
                "docshell and document should have the same "
                "inIsolatedMozBrowser attribute.");
     MOZ_ASSERT(attrs.mPrivateBrowsingId == oa.mPrivateBrowsingId,
                "docshell and document should have the same privateBrowsingId "
                "attribute.");
 
     attrs = oa;
   }
 
   if (OwnerIsMozBrowserFrame()) {
+    attrs.mAppId = nsIScriptSecurityManager::NO_APP_ID;
     attrs.mInIsolatedMozBrowser = OwnerIsIsolatedMozBrowserFrame();
     docShell->SetFrameType(nsIDocShell::FRAME_TYPE_BROWSER);
   } else {
     nsCOMPtr<nsIDocShellTreeItem> parentCheck;
     docShell->GetSameTypeParent(getter_AddRefs(parentCheck));
     if (!!parentCheck) {
       docShell->SetIsFrame();
     }
@@ -3371,16 +3374,18 @@ void nsFrameLoader::MaybeUpdatePrimaryBr
 }
 
 nsresult nsFrameLoader::GetNewTabContext(MutableTabContext* aTabContext,
                                          nsIURI* aURI) {
   OriginAttributes attrs;
   attrs.mInIsolatedMozBrowser = OwnerIsIsolatedMozBrowserFrame();
   nsresult rv;
 
+  attrs.mAppId = nsIScriptSecurityManager::NO_APP_ID;
+
   // 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,
                          presentationURLStr);
--- a/dom/base/test/test_messagemanager_principal.html
+++ b/dom/base/test/test_messagemanager_principal.html
@@ -19,16 +19,19 @@
       "data:text/html,<!DOCTYPE HTML><html><body></body></html>";
 
     function childFrameScript() {
       "use strict";
 
       addMessageListener("test:ipcMessage", function(message) {
         sendAsyncMessage(message.name, "principal: " + (message.principal ? "OK" : "KO"));
 
+        sendAsyncMessage(message.name, "principal.appId: " +
+                         ("appId" in message.principal ? "OK" : "KO"));
+
         sendAsyncMessage(message.name, "principal.origin: " +
                          ("origin" in message.principal ? "OK" : "KO"));
 
         sendAsyncMessage(message.name, "principal.isInIsolatedMozBrowserElement: " +
                          ("isInIsolatedMozBrowserElement" in message.principal ? "OK" : "KO"));
 
         sendAsyncMessage(message.name, "DONE");
       });
--- a/dom/base/test/test_messagemanager_send_principal.html
+++ b/dom/base/test/test_messagemanager_send_principal.html
@@ -21,16 +21,19 @@
     function childFrameScript() {
       "use strict";
 
 
       addMessageListener("test:content", function(message) {
         sendAsyncMessage("test:result", "is nsIPrincipal: " +
                          (message.data instanceof Ci.nsIPrincipal ? "OK" : "KO"));
 
+        sendAsyncMessage("test:result", "principal.appId: " +
+                         ("appId" in message.data ? "OK" : "KO"));
+
         sendAsyncMessage("test:result", "principal.origin: " +
                          ("origin" in message.data ? "OK" : "KO"));
 
         sendAsyncMessage("test:result", "principal.isInIsolatedMozBrowserElement: " +
                          ("isInIsolatedMozBrowserElement" in message.data ? "OK" : "KO"));
       });
 
       addMessageListener("test:system", function(message) {
--- a/dom/base/test/test_messagemanager_targetchain.html
+++ b/dom/base/test/test_messagemanager_targetchain.html
@@ -97,19 +97,22 @@
       }, true);
       document.body.appendChild(iframe);
     }
 
     addEventListener("load", function() {
       var principal = SpecialPowers.wrap(document).nodePrincipal;
       SpecialPowers.pushPermissions([
         { type: "browser", allow: 1, context: { url: principal.URI.spec,
-                                                originAttributes: {}}},
+                                                originAttributes: {
+                                                  appId: principal.appId
+                                                }}},
         { type: "browser", allow: 1, context: { url: principal.URI.spec,
                                                 originAttributes: {
+                                                  appId: principal.appId,
                                                   inIsolatedMozBrowser: true }}}
       ], () => {
         SpecialPowers.pushPrefEnv({
           set: [
             ["dom.mozBrowserFramesEnabled", true],
             ["network.disable.ipc.security", true],
             ["dom.ipc.browser_frames.oop_by_default", false],
           ]
--- a/dom/browser-element/mochitest/browserElement_Auth.js
+++ b/dom/browser-element/mochitest/browserElement_Auth.js
@@ -154,22 +154,22 @@ function testAuthJarNoInterfere(e) {
   let authMgr = SpecialPowers.Cc["@mozilla.org/network/http-auth-manager;1"]
     .getService(SpecialPowers.Ci.nsIHttpAuthManager);
   let secMan = SpecialPowers.Services.scriptSecurityManager;
   let ioService = SpecialPowers.Services.io;
   var uri = ioService.newURI("http://test/tests/dom/browser-element/mochitest/file_http_401_response.sjs");
 
   // Set a bunch of auth data that should not conflict with the correct auth data already
   // stored in the cache.
-  var attrs = {userContextId: 1};
+  var attrs = {appId: 1};
   var principal = secMan.createCodebasePrincipal(uri, attrs);
   authMgr.setAuthIdentity("http", "test", -1, "basic", "http_realm",
                           "tests/dom/browser-element/mochitest/file_http_401_response.sjs",
                           "", "httpuser", "wrongpass", false, principal);
-  attrs = {userContextId: 1, inIsolatedMozBrowser: true};
+  attrs = {appId: 1, inIsolatedMozBrowser: true};
   principal = secMan.createCodebasePrincipal(uri, attrs);
   authMgr.setAuthIdentity("http", "test", -1, "basic", "http_realm",
                           "tests/dom/browser-element/mochitest/file_http_401_response.sjs",
                           "", "httpuser", "wrongpass", false, principal);
   principal = secMan.createCodebasePrincipal(uri, {});
   authMgr.setAuthIdentity("http", "test", -1, "basic", "http_realm",
                           "tests/dom/browser-element/mochitest/file_http_401_response.sjs",
                           "", "httpuser", "wrongpass", false, principal);
--- a/dom/browser-element/mochitest/browserElement_CopyPaste.js
+++ b/dom/browser-element/mochitest/browserElement_CopyPaste.js
@@ -323,15 +323,16 @@ function testCut2(e) {
 
   mm.loadFrameScript(getScriptForGetContent(), false);
 }
 
 // Give our origin permission to open browsers, and remove it when the test is complete.
 var principal = SpecialPowers.wrap(document).nodePrincipal;
 var context = { url: SpecialPowers.wrap(principal.URI).spec,
                 originAttributes: {
+                  appId: principal.appId,
                   inIsolatedMozBrowser: true }};
 
 addEventListener("testready", function() {
   SpecialPowers.pushPermissions([
     {type: "browser", allow: 1, context},
   ], runTest);
 });
--- a/dom/chrome-webidl/ChromeUtils.webidl
+++ b/dom/chrome-webidl/ChromeUtils.webidl
@@ -515,22 +515,24 @@ dictionary IOActivityDataDictionary {
  *
  * IMPORTANT: If you add any members here, you need to do the following:
  * (1) Add them to both dictionaries.
  * (2) Update the methods on mozilla::OriginAttributes, including equality,
  *     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;
   unsigned long privateBrowsingId = 0;
   DOMString firstPartyDomain = "";
 };
 dictionary OriginAttributesPatternDictionary {
+  unsigned long appId;
   unsigned long userContextId;
   boolean inIsolatedMozBrowser;
   unsigned long privateBrowsingId;
   DOMString firstPartyDomain;
 };
 
 dictionary CompileScriptOptionsDictionary {
   /**
--- a/dom/ipc/BrowserBridgeParent.cpp
+++ b/dom/ipc/BrowserBridgeParent.cpp
@@ -26,16 +26,17 @@ nsresult BrowserBridgeParent::Init(const
                                    CanonicalBrowsingContext* aBrowsingContext,
                                    const uint32_t& aChromeFlags) {
   mIPCOpen = true;
 
   // FIXME: This should actually use a non-bogus TabContext, probably inherited
   // from our Manager().
   OriginAttributes attrs;
   attrs.mInIsolatedMozBrowser = false;
+  attrs.mAppId = nsIScriptSecurityManager::NO_APP_ID;
   attrs.SyncAttributesWithPrivateBrowsing(false);
   MutableTabContext tabContext;
   tabContext.SetTabContext(false, 0, UIStateChangeType_Set,
                            UIStateChangeType_Set, attrs, aPresentationURL);
 
   ProcessPriority initialPriority = PROCESS_PRIORITY_FOREGROUND;
 
   // Get our ConstructorSender object.
--- a/dom/ipc/PTabContext.ipdlh
+++ b/dom/ipc/PTabContext.ipdlh
@@ -15,17 +15,21 @@ using mozilla::OriginAttributes from "mo
 namespace mozilla {
 namespace dom {
 
 // An IPCTabContext which corresponds to a PBrowser opened by a child when it
 // receives window.open().
 //
 // If isMozBrowserElement is false, this PopupIPCTabContext is either a
 // <xul:browser> or an app frame.  The frame's app-id and app-frame-owner-app-id
-// will be equal to the opener's values.
+// will be equal to the opener's values.  For a <xul:browser>, those app IDs
+// will be NO_APP_ID.
+//
+// If isMozBrowserElement is true, the frame's browserFrameOwnerAppId will be
+// equal to the opener's app-id.
 //
 // It's an error to set isMozBrowserElement == false if opener is a mozbrowser
 // element.  Such a PopupIPCTabContext should be rejected by code which receives
 // it.
 struct PopupIPCTabContext
 {
   PBrowserOrId opener;
   bool isMozBrowserElement;
--- a/dom/ipc/TabContext.cpp
+++ b/dom/ipc/TabContext.cpp
@@ -7,16 +7,18 @@
 #include "mozilla/dom/TabContext.h"
 #include "mozilla/dom/PTabContext.h"
 #include "mozilla/dom/BrowserParent.h"
 #include "mozilla/dom/BrowserChild.h"
 #include "mozilla/StaticPrefs.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsServiceManagerUtils.h"
 
+#define NO_APP_ID (nsIScriptSecurityManager::NO_APP_ID)
+
 using namespace mozilla::dom::ipc;
 using namespace mozilla::layout;
 
 namespace mozilla {
 namespace dom {
 
 TabContext::TabContext()
     : mInitialized(false),
@@ -88,16 +90,19 @@ UIStateChangeType TabContext::ShowFocusR
 bool TabContext::SetTabContext(bool aIsMozBrowserElement,
                                uint64_t aChromeOuterWindowID,
                                UIStateChangeType aShowAccelerators,
                                UIStateChangeType aShowFocusRings,
                                const OriginAttributes& aOriginAttributes,
                                const nsAString& aPresentationURL) {
   NS_ENSURE_FALSE(mInitialized, false);
 
+  // Veryify that app id matches mAppId passed in originAttributes
+  MOZ_RELEASE_ASSERT(aOriginAttributes.mAppId == NO_APP_ID);
+
   mInitialized = true;
   mIsMozBrowserElement = aIsMozBrowserElement;
   mChromeOuterWindowID = aChromeOuterWindowID;
   mOriginAttributes = aOriginAttributes;
   mPresentationURL = aPresentationURL;
   mShowAccelerators = aShowAccelerators;
   mShowFocusRings = aShowFocusRings;
   return true;
--- a/dom/presentation/tests/mochitest/test_presentation_mixed_security_contexts.html
+++ b/dom/presentation/tests/mochitest/test_presentation_mixed_security_contexts.html
@@ -18,16 +18,17 @@
 var iframe = document.getElementById("iframe");
 var readyToStart = false;
 var testSetuped = false;
 
 function setup() {
   SpecialPowers.addPermission("presentation",
                               true, { url: "https://example.com/tests/dom/presentation/tests/mochitest/file_presentation_mixed_security_contexts.html",
                                       originAttributes: {
+                                        appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
                                         inIsolatedMozBrowser: false }});
 
   return new Promise(function(aResolve, aReject) {
     addEventListener("message", function listener(event) {
       var message = event.data;
       if (/^OK /.exec(message)) {
         ok(true, message.replace(/^OK /, ""));
       } else if (/^KO /.exec(message)) {
--- a/dom/push/test/xpcshell/test_clear_origin_data.js
+++ b/dom/push/test/xpcshell/test_clear_origin_data.js
@@ -45,22 +45,42 @@ function run_test() {
 }
 
 add_task(async function test_webapps_cleardata() {
   let db = PushServiceWebSocket.newPushDB();
   registerCleanupFunction(() => { return db.drop().then(_ => db.close()); });
 
   let testRecords = [{
     scope: "https://example.org/1",
-    originAttributes: {},
-    clearIf: { inIsolatedMozBrowser: false },
+    originAttributes: { appId: 1 },
+    clearIf: { appId: 1, inIsolatedMozBrowser: false },
+  }, {
+    scope: "https://example.org/1",
+    originAttributes: { appId: 1, inIsolatedMozBrowser: true },
+    clearIf: { appId: 1 },
   }, {
     scope: "https://example.org/1",
-    originAttributes: { inIsolatedMozBrowser: true },
-    clearIf: {},
+    originAttributes: { appId: 2, inIsolatedMozBrowser: true },
+    clearIf: { appId: 2, inIsolatedMozBrowser: true },
+  }, {
+    scope: "https://example.org/2",
+    originAttributes: { appId: 1 },
+    clearIf: { appId: 1, inIsolatedMozBrowser: false },
+  }, {
+    scope: "https://example.org/2",
+    originAttributes: { appId: 2, inIsolatedMozBrowser: true },
+    clearIf: { appId: 2, inIsolatedMozBrowser: true },
+  }, {
+    scope: "https://example.org/3",
+    originAttributes: { appId: 3, inIsolatedMozBrowser: true },
+    clearIf: { inIsolatedMozBrowser: true },
+  }, {
+    scope: "https://example.org/3",
+    originAttributes: { appId: 4, inIsolatedMozBrowser: true },
+    clearIf: { inIsolatedMozBrowser: true },
   }];
 
   let unregisterDone;
   let unregisterPromise = new Promise(resolve =>
     unregisterDone = after(testRecords.length, resolve));
 
   PushService.init({
     serverURI: "wss://push.example.org",
@@ -97,17 +117,25 @@ add_task(async function test_webapps_cle
   await Promise.all(testRecords.map(test =>
     PushService.register({
       scope: test.scope,
       originAttributes: ChromeUtils.originAttributesToSuffix(
         test.originAttributes),
     })
   ));
 
-  // Removes all the records, Excluding where `inIsolatedMozBrowser` is true.
-  await clearForPattern(testRecords, { inIsolatedMozBrowser: false });
+  // Removes records for all scopes with the same app ID. Excludes records
+  // where `inIsolatedMozBrowser` is true.
+  await clearForPattern(testRecords, { appId: 1, inIsolatedMozBrowser: false });
+
+  // Removes the remaining record for app ID 1, where `inIsolatedMozBrowser` is true.
+  await clearForPattern(testRecords, { appId: 1 });
 
-  // Removes the all the remaining records where `inIsolatedMozBrowser` is true.
-  await clearForPattern(testRecords, {});
+  // Removes all records for all scopes with the same app ID, where
+  // `inIsolatedMozBrowser` is true.
+  await clearForPattern(testRecords, { appId: 2, inIsolatedMozBrowser: true });
+
+  // Removes all records where `inIsolatedMozBrowser` is true.
+  await clearForPattern(testRecords, { inIsolatedMozBrowser: true });
 
   equal(testRecords.length, 0, "Should remove all test records");
   await unregisterPromise;
 });
--- a/dom/push/test/xpcshell/test_notification_http2.js
+++ b/dom/push/test/xpcshell/test_notification_http2.js
@@ -55,17 +55,17 @@ add_task(async function test_pushNotific
       d: "1jUPhzVsRkzV0vIzwL4ZEsOlKdNOWm7TmaTfzitJkgM",
       ext: true,
       key_ops: ["deriveBits"],
       kty: "EC",
       x: "8J3iA1CSPBFqHrUul0At3NkosudTlQDAPO1Dn-HRCxM",
       y: "26jk0IFbqcK6-JxhHAm-rsHEwy0CyVJjtnfOcqc1tgA",
     },
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     quota: Infinity,
     systemRecord: true,
   }, {
     subscriptionUri: serverURL + "/pushNotifications/subscription2",
     pushEndpoint: serverURL + "/pushEndpoint2",
     pushReceiptEndpoint: serverURL + "/pushReceiptEndpoint2",
     scope: "https://example.com/page/2",
     p256dhPublicKey: "BPnWyUo7yMnuMlyKtERuLfWE8a09dtdjHSW2lpC9_BqR5TZ1rK8Ldih6ljyxVwnBA-nygQHGRpEmu1jV5K8437E",
@@ -74,17 +74,17 @@ add_task(async function test_pushNotific
       d: "lFm4nPsUKYgNGBJb5nXXKxl8bspCSp0bAhCYxbveqT4",
       ext: true,
       key_ops: ["deriveBits"],
       kty: "EC",
       x: "-dbJSjvIye4yXIq0RG4t9YTxrT1212MdJbaWkL38GpE",
       y: "5TZ1rK8Ldih6ljyxVwnBA-nygQHGRpEmu1jV5K8437E",
     },
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     quota: Infinity,
     systemRecord: true,
   }, {
     subscriptionUri: serverURL + "/pushNotifications/subscription3",
     pushEndpoint: serverURL + "/pushEndpoint3",
     pushReceiptEndpoint: serverURL + "/pushReceiptEndpoint3",
     scope: "https://example.com/page/3",
     p256dhPublicKey: "BDhUHITSeVrWYybFnb7ylVTCDDLPdQWMpf8gXhcWwvaaJa6n3YH8TOcH8narDF6t8mKVvg2ioLW-8MH5O4dzGcI",
@@ -93,17 +93,17 @@ add_task(async function test_pushNotific
       d: "Q1_SE1NySTYzjbqgWwPgrYh7XRg3adqZLkQPsy319G8",
       ext: true,
       key_ops: ["deriveBits"],
       kty: "EC",
       x: "OFQchNJ5WtZjJsWdvvKVVMIMMs91BYyl_yBeFxbC9po",
       y: "Ja6n3YH8TOcH8narDF6t8mKVvg2ioLW-8MH5O4dzGcI",
     },
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     quota: Infinity,
     systemRecord: true,
   }, {
     subscriptionUri: serverURL + "/pushNotifications/subscription4",
     pushEndpoint: serverURL + "/pushEndpoint4",
     pushReceiptEndpoint: serverURL + "/pushReceiptEndpoint4",
     scope: "https://example.com/page/4",
     p256dhPublicKey: ChromeUtils.base64URLDecode("BEcvDzkWCrUtjU_wygL98sbQCQrW1lY9irtgGnlCc4B0JJXLCHB9MTM73qD6GZYfL0YOvKo8XLOflh-J4dMGklU", {
@@ -117,17 +117,17 @@ add_task(async function test_pushNotific
       kty: "EC",
       x: "Ry8PORYKtS2NT_DKAv3yxtAJCtbWVj2Ku2AaeUJzgHQ",
       y: "JJXLCHB9MTM73qD6GZYfL0YOvKo8XLOflh-J4dMGklU",
     },
     authenticationSecret: ChromeUtils.base64URLDecode("cwDVC1iwAn8E37mkR3tMSg", {
       padding: "reject",
     }),
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     quota: Infinity,
     systemRecord: true,
   }];
 
   for (let record of records) {
     await db.put(record);
   }
 
--- a/dom/push/test/xpcshell/test_reconnect_retry.js
+++ b/dom/push/test/xpcshell/test_reconnect_retry.js
@@ -52,22 +52,22 @@ add_task(async function test_reconnect_r
         },
       });
     },
   });
 
   let registration = await PushService.register({
     scope: "https://example.com/page/1",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
   let retryEndpoint = "https://example.org/push/" + channelID;
   equal(registration.endpoint, retryEndpoint, "Wrong endpoint for retried request");
 
   registration = await PushService.register({
     scope: "https://example.com/page/2",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
   notEqual(registration.endpoint, retryEndpoint, "Wrong endpoint for new request");
 
   equal(registers, 3, "Wrong registration count");
 });
--- a/dom/push/test/xpcshell/test_register_5xxCode_http2.js
+++ b/dom/push/test/xpcshell/test_register_5xxCode_http2.js
@@ -73,16 +73,17 @@ add_task(async function test1() {
   var serverURL = "http://localhost:" + httpServer.identity.primaryPort;
 
   PushService.init({
     serverURI: serverURL + "/subscribe5xxCode",
     db,
   });
 
   let originAttributes = ChromeUtils.originAttributesToSuffix({
+    appId: Ci.nsIScriptSecurityManager.NO_APP_ID,
     inIsolatedMozBrowser: false,
   });
   let newRecord = await PushService.register({
     scope: "https://example.com/retry5xxCode",
     originAttributes,
   });
 
   var subscriptionUri = serverURL + "/subscription";
--- a/dom/push/test/xpcshell/test_register_case.js
+++ b/dom/push/test/xpcshell/test_register_case.js
@@ -40,17 +40,17 @@ add_task(async function test_register_ca
         },
       });
     },
   });
 
   let newRecord = await PushService.register({
     scope: "https://example.net/case",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
   equal(newRecord.endpoint, "https://example.com/update/case",
     "Wrong push endpoint in registration record");
 
   let record = await db.getByPushEndpoint("https://example.com/update/case");
   equal(record.scope, "https://example.net/case",
     "Wrong scope in database record");
 });
--- a/dom/push/test/xpcshell/test_register_error_http2.js
+++ b/dom/push/test/xpcshell/test_register_error_http2.js
@@ -30,17 +30,17 @@ add_task(async function test_pushSubscri
     serverURI: serverURL + "/pushSubscriptionNoConnection/subscribe",
     db,
   });
 
   await Assert.rejects(
     PushService.register({
       scope: "https://example.net/page/invalid-response",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Registration error/,
     "Expected error for not being able to establish connecion."
   );
 
   let record = await db.getAllKeyIDs();
   ok(record.length === 0, "Should not store records when connection couldn't be established.");
   PushService.uninit();
@@ -66,17 +66,17 @@ add_task(async function test_pushSubscri
     serverURI: serverURL + "/pushSubscriptionMissingLocation/subscribe",
     db,
   });
 
   await Assert.rejects(
     PushService.register({
       scope: "https://example.net/page/invalid-response",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Registration error/,
     "Expected error for the missing location header."
   );
 
   let record = await db.getAllKeyIDs();
   ok(record.length === 0, "Should not store records when the location header is missing.");
   PushService.uninit();
@@ -92,17 +92,17 @@ add_task(async function test_pushSubscri
     serverURI: serverURL + "/pushSubscriptionMissingLink/subscribe",
     db,
   });
 
   await Assert.rejects(
     PushService.register({
       scope: "https://example.net/page/invalid-response",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Registration error/,
     "Expected error for the missing link header."
   );
 
   let record = await db.getAllKeyIDs();
   ok(record.length === 0, "Should not store records when a link header is missing.");
   PushService.uninit();
@@ -118,17 +118,17 @@ add_task(async function test_pushSubscri
     serverURI: serverURL + "/pushSubscriptionMissingLink1/subscribe",
     db,
   });
 
   await Assert.rejects(
     PushService.register({
       scope: "https://example.net/page/invalid-response",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Registration error/,
     "Expected error for the missing push endpoint."
   );
 
   let record = await db.getAllKeyIDs();
   ok(record.length === 0, "Should not store records when the push endpoint is missing.");
   PushService.uninit();
@@ -144,17 +144,17 @@ add_task(async function test_pushSubscri
     serverURI: serverURL + "/pushSubscriptionLocationBogus/subscribe",
     db,
   });
 
   await Assert.rejects(
     PushService.register({
       scope: "https://example.net/page/invalid-response",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Registration error/,
     "Expected error for the bogus location"
   );
 
   let record = await db.getAllKeyIDs();
   ok(record.length === 0, "Should not store records when location header is bogus.");
   PushService.uninit();
@@ -170,17 +170,17 @@ add_task(async function test_pushSubscri
     serverURI: serverURL + "/pushSubscriptionNot201Code/subscribe",
     db,
   });
 
   await Assert.rejects(
     PushService.register({
       scope: "https://example.net/page/invalid-response",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Registration error/,
     "Expected error for not 201 responce code."
   );
 
   let record = await db.getAllKeyIDs();
   ok(record.length === 0, "Should not store records when respons code is not 201.");
 });
--- a/dom/push/test/xpcshell/test_register_invalid_channel.js
+++ b/dom/push/test/xpcshell/test_register_invalid_channel.js
@@ -42,17 +42,17 @@ add_task(async function test_register_in
       });
     },
   });
 
   await Assert.rejects(
     PushService.register({
       scope: "https://example.com/invalid-channel",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Registration error/,
     "Expected error for invalid channel ID"
   );
 
   let record = await db.getByKeyID(channelID);
   ok(!record, "Should not store records for error responses");
 });
--- a/dom/push/test/xpcshell/test_register_invalid_endpoint.js
+++ b/dom/push/test/xpcshell/test_register_invalid_endpoint.js
@@ -43,17 +43,17 @@ add_task(async function test_register_in
       });
     },
   });
 
   await Assert.rejects(
     PushService.register({
       scope: "https://example.net/page/invalid-endpoint",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Registration error/,
     "Expected error for invalid endpoint"
   );
 
   let record = await db.getByKeyID(channelID);
   ok(!record, "Should not store records with invalid endpoints");
 });
--- a/dom/push/test/xpcshell/test_register_invalid_json.js
+++ b/dom/push/test/xpcshell/test_register_invalid_json.js
@@ -43,17 +43,17 @@ add_task(async function test_register_in
       });
     },
   });
 
   await Assert.rejects(
     PushService.register({
       scope: "https://example.net/page/invalid-json",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Registration error/,
     "Expected error for invalid JSON response"
   );
 
   await helloPromise;
   equal(registers, 1, "Wrong register count");
 });
--- a/dom/push/test/xpcshell/test_register_no_id.js
+++ b/dom/push/test/xpcshell/test_register_no_id.js
@@ -47,17 +47,17 @@ add_task(async function test_register_no
       });
     },
   });
 
   await Assert.rejects(
     PushService.register({
       scope: "https://example.com/incomplete",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Registration error/,
     "Expected error for incomplete register response"
   );
 
   await helloPromise;
   equal(registers, 1, "Wrong register count");
 });
--- a/dom/push/test/xpcshell/test_register_request_queue.js
+++ b/dom/push/test/xpcshell/test_register_request_queue.js
@@ -39,22 +39,22 @@ add_task(async function test_register_re
         },
       });
     },
   });
 
   let firstRegister = PushService.register({
     scope: "https://example.com/page/1",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
   let secondRegister = PushService.register({
     scope: "https://example.com/page/1",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
 
   await Promise.all([
     // eslint-disable-next-line mozilla/rejects-requires-await
     Assert.rejects(firstRegister, /Registration error/, "Should time out the first request"),
     // eslint-disable-next-line mozilla/rejects-requires-await
     Assert.rejects(secondRegister, /Registration error/, "Should time out the second request"),
   ]);
--- a/dom/push/test/xpcshell/test_register_rollback.js
+++ b/dom/push/test/xpcshell/test_register_rollback.js
@@ -70,17 +70,17 @@ add_task(async function test_register_ro
     },
   });
 
   // Should return a rejected promise if storage fails.
   await Assert.rejects(
     PushService.register({
       scope: "https://example.com/storage-error",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /universe has imploded/,
     "Expected error for unregister database failure"
   );
 
   // Should send an out-of-band unregister request.
   await unregisterPromise;
   equal(handshakes, 1, "Wrong handshake count");
--- a/dom/push/test/xpcshell/test_register_success.js
+++ b/dom/push/test/xpcshell/test_register_success.js
@@ -53,17 +53,17 @@ add_task(async function test_register_su
   });
 
   let subModifiedPromise = promiseObserverNotification(
     PushServiceComponent.subscriptionModifiedTopic);
 
   let newRecord = await PushService.register({
     scope: "https://example.org/1",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
   equal(newRecord.endpoint, "https://example.com/update/1",
     "Wrong push endpoint in registration record");
 
   let {data: subModifiedScope} = await subModifiedPromise;
   equal(subModifiedScope, "https://example.org/1",
     "Should fire a subscription modified event after subscribing");
 
--- a/dom/push/test/xpcshell/test_register_success_http2.js
+++ b/dom/push/test/xpcshell/test_register_success_http2.js
@@ -44,17 +44,17 @@ add_task(async function test_pushSubscri
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionSuccess/subscribe",
     db,
   });
 
   let newRecord = await PushService.register({
     scope: "https://example.org/1",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
 
   var subscriptionUri = serverURL + "/pushSubscriptionSuccesss";
   var pushEndpoint = serverURL + "/pushEndpointSuccess";
   var pushReceiptEndpoint = serverURL + "/receiptPushEndpointSuccess";
   equal(newRecord.endpoint, pushEndpoint,
     "Wrong push endpoint in registration record");
 
@@ -78,17 +78,17 @@ add_task(async function test_pushSubscri
   PushService.init({
     serverURI: serverURL + "/pushSubscriptionMissingLink2/subscribe",
     db,
   });
 
   let newRecord = await PushService.register({
     scope: "https://example.org/no_receiptEndpoint",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
 
   var subscriptionUri = serverURL + "/subscriptionMissingLink2";
   var pushEndpoint = serverURL + "/pushEndpointMissingLink2";
   var pushReceiptEndpoint = "";
   equal(newRecord.endpoint, pushEndpoint,
     "Wrong push endpoint in registration record");
 
--- a/dom/push/test/xpcshell/test_register_timeout.js
+++ b/dom/push/test/xpcshell/test_register_timeout.js
@@ -70,17 +70,17 @@ add_task(async function test_register_ti
       });
     },
   });
 
   await Assert.rejects(
     PushService.register({
       scope: "https://example.net/page/timeout",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Registration error/,
     "Expected error for request timeout"
   );
 
   let record = await db.getByKeyID(channelID);
   ok(!record, "Should not store records for timed-out responses");
 
--- a/dom/push/test/xpcshell/test_register_wrong_id.js
+++ b/dom/push/test/xpcshell/test_register_wrong_id.js
@@ -53,17 +53,17 @@ add_task(async function test_register_wr
       });
     },
   });
 
   await Assert.rejects(
     PushService.register({
       scope: "https://example.com/mismatched",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Registration error/,
     "Expected error for mismatched register reply"
   );
 
   await helloPromise;
   equal(registers, 1, "Wrong register count");
 });
--- a/dom/push/test/xpcshell/test_register_wrong_type.js
+++ b/dom/push/test/xpcshell/test_register_wrong_type.js
@@ -47,17 +47,17 @@ add_task(async function test_register_wr
       });
     },
   });
 
   await Assert.rejects(
     PushService.register({
       scope: "https://example.com/mistyped",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Registration error/,
     "Expected error for non-string channel ID"
   );
 
   await helloPromise;
   equal(registers, 1, "Wrong register count");
 });
--- a/dom/push/test/xpcshell/test_registration_error.js
+++ b/dom/push/test/xpcshell/test_registration_error.js
@@ -28,16 +28,16 @@ add_task(async function test_registratio
       return new MockWebSocket(uri);
     },
   });
 
   await Assert.rejects(
     PushService.registration({
       scope: "https://example.net/1",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     function(error) {
       return error == "Database error";
     },
     "Wrong message"
   );
 });
--- a/dom/push/test/xpcshell/test_registration_error_http2.js
+++ b/dom/push/test/xpcshell/test_registration_error_http2.js
@@ -22,16 +22,16 @@ add_task(async function test_registratio
       },
     }),
   });
 
   await Assert.rejects(
     PushService.registration({
       scope: "https://example.net/1",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     function(error) {
       return error == "Database error";
     },
     "Wrong message"
   );
 });
--- a/dom/push/test/xpcshell/test_registration_none.js
+++ b/dom/push/test/xpcshell/test_registration_none.js
@@ -20,12 +20,12 @@ add_task(async function test_registratio
     makeWebSocket(uri) {
       return new MockWebSocket(uri);
     },
   });
 
   let registration = await PushService.registration({
     scope: "https://example.net/1",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
   ok(!registration, "Should not open a connection without registration");
 });
--- a/dom/push/test/xpcshell/test_registration_success.js
+++ b/dom/push/test/xpcshell/test_registration_success.js
@@ -18,16 +18,32 @@ add_task(async function test_registratio
   registerCleanupFunction(() => { return db.drop().then(_ => db.close()); });
   let records = [{
     channelID: "bf001fe0-2684-42f2-bc4d-a3e14b11dd5b",
     pushEndpoint: "https://example.com/update/same-manifest/1",
     scope: "https://example.net/a",
     originAttributes: "",
     version: 5,
     quota: Infinity,
+  }, {
+    channelID: "f6edfbcd-79d6-49b8-9766-48b9dcfeff0f",
+    pushEndpoint: "https://example.com/update/same-manifest/2",
+    scope: "https://example.net/b",
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: 42 }),
+    version: 10,
+    quota: Infinity,
+  }, {
+    channelID: "b1cf38c9-6836-4d29-8a30-a3e98d59b728",
+    pushEndpoint: "https://example.org/update/different-manifest",
+    scope: "https://example.org/c",
+    originAttributes: ChromeUtils.originAttributesToSuffix(
+      { appId: 42, inIsolatedMozBrowser: true }),
+    version: 15,
+    quota: Infinity,
   }];
   for (let record of records) {
     await db.put(record);
   }
 
   let handshakeDone;
   let handshakePromise = new Promise(resolve => handshakeDone = resolve);
   PushService.init({
--- a/dom/push/test/xpcshell/test_registration_success_http2.js
+++ b/dom/push/test/xpcshell/test_registration_success_http2.js
@@ -24,48 +24,48 @@ add_task(async function test_pushNotific
   var serverURL = "https://localhost:" + serverPort;
 
   let records = [{
     subscriptionUri: serverURL + "/subscriptionA",
     pushEndpoint: serverURL + "/pushEndpointA",
     pushReceiptEndpoint: serverURL + "/pushReceiptEndpointA",
     scope: "https://example.net/a",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     quota: Infinity,
   }, {
     subscriptionUri: serverURL + "/subscriptionB",
     pushEndpoint: serverURL + "/pushEndpointB",
     pushReceiptEndpoint: serverURL + "/pushReceiptEndpointB",
     scope: "https://example.net/b",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     quota: Infinity,
   }, {
     subscriptionUri: serverURL + "/subscriptionC",
     pushEndpoint: serverURL + "/pushEndpointC",
     pushReceiptEndpoint: serverURL + "/pushReceiptEndpointC",
     scope: "https://example.net/c",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     quota: Infinity,
   }];
 
   for (let record of records) {
     await db.put(record);
   }
 
   PushService.init({
     serverURI: serverURL,
     db,
   });
 
   let registration = await PushService.registration({
     scope: "https://example.net/a",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
   equal(
     registration.endpoint,
     serverURL + "/pushEndpointA",
     "Wrong push endpoint for scope"
   );
 });
--- a/dom/push/test/xpcshell/test_unregister_empty_scope.js
+++ b/dom/push/test/xpcshell/test_unregister_empty_scope.js
@@ -26,14 +26,14 @@ add_task(async function test_unregister_
       });
     },
   });
 
   await Assert.rejects(
     PushService.unregister({
       scope: "",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Invalid page record/,
     "Expected error for empty endpoint"
   );
 });
--- a/dom/push/test/xpcshell/test_unregister_invalid_json.js
+++ b/dom/push/test/xpcshell/test_unregister_invalid_json.js
@@ -74,17 +74,17 @@ add_task(async function test_unregister_
   let record = await db.getByKeyID(
     "87902e90-c57e-4d18-8354-013f4a556559");
   ok(!record, "Failed to delete unregistered record");
 
   await Assert.rejects(
     PushService.unregister({
       scope: "https://example.net/page/1",
       originAttributes: ChromeUtils.originAttributesToSuffix(
-        { inIsolatedMozBrowser: false }),
+        { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     }),
     /Request timed out/,
     "Expected error for second invalid JSON response"
   );
 
   record = await db.getByKeyID(
     "057caa8f-9b99-47ff-891c-adad18ce603e");
   ok(!record,
--- a/dom/push/test/xpcshell/test_unregister_not_found.js
+++ b/dom/push/test/xpcshell/test_unregister_not_found.js
@@ -25,12 +25,12 @@ add_task(async function test_unregister_
         },
       });
     },
   });
 
   let result = await PushService.unregister({
     scope: "https://example.net/nonexistent",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
   ok(result === false, "unregister should resolve with false for nonexistent scope");
 });
--- a/dom/push/test/xpcshell/test_unregister_success_http2.js
+++ b/dom/push/test/xpcshell/test_unregister_success_http2.js
@@ -39,29 +39,29 @@ add_task(async function test_pushUnsubsc
   var serverURL = "https://localhost:" + serverPort;
 
   await db.put({
     subscriptionUri: serverURL + "/subscriptionUnsubscriptionSuccess",
     pushEndpoint: serverURL + "/pushEndpointUnsubscriptionSuccess",
     pushReceiptEndpoint: serverURL + "/receiptPushEndpointUnsubscriptionSuccess",
     scope: "https://example.com/page/unregister-success",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
     quota: Infinity,
   });
 
   PushService.init({
     serverURI: serverURL,
     db,
   });
 
   await PushService.unregister({
     scope: "https://example.com/page/unregister-success",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
   let record = await db.getByKeyID(serverURL + "/subscriptionUnsubscriptionSuccess");
   ok(!record, "Unregister did not remove record");
 });
 
 add_task(async function test_complete() {
   Services.prefs.setBoolPref("dom.push.enabled", pushEnabled);
   Services.prefs.setBoolPref("dom.push.connection.enabled", pushConnectionEnabled);
--- a/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js
+++ b/dom/push/test/xpcshell/test_updateRecordNoEncryptionKeys_ws.js
@@ -65,17 +65,17 @@ add_task(async function test_with_data_e
         },
       });
     },
   });
 
   let newRecord = await PushService.register({
     scope: "https://example.com/page/3",
     originAttributes: ChromeUtils.originAttributesToSuffix(
-      { inIsolatedMozBrowser: false }),
+      { appId: Ci.nsIScriptSecurityManager.NO_APP_ID, inIsolatedMozBrowser: false }),
   });
   ok(newRecord.p256dhKey, "Should generate public keys for new records");
 
   let record = await db.getByKeyID("eb18f12a-cc42-4f14-accb-3bfc1227f1aa");
   ok(record.p256dhPublicKey, "Should add public key to partial record");
   ok(record.p256dhPrivateKey, "Should add private key to partial record");
 
   record = await db.getByKeyID("0d8886b9-8da1-4778-8f5d-1cf93a877ed6");
--- a/dom/quota/ActorsParent.cpp
+++ b/dom/quota/ActorsParent.cpp
@@ -199,16 +199,21 @@ const char kResourceOriginPrefix[] = "re
 
 #define INDEXEDDB_DIRECTORY_NAME "indexedDB"
 #define STORAGE_DIRECTORY_NAME "storage"
 #define PERSISTENT_DIRECTORY_NAME "persistent"
 #define PERMANENT_DIRECTORY_NAME "permanent"
 #define TEMPORARY_DIRECTORY_NAME "temporary"
 #define DEFAULT_DIRECTORY_NAME "default"
 
+enum AppId {
+  kNoAppId = nsIScriptSecurityManager::NO_APP_ID,
+  kUnknownAppId = nsIScriptSecurityManager::UNKNOWN_APP_ID
+};
+
 #define STORAGE_FILE_NAME "storage.sqlite"
 
 // The name of the file that we use to load/save the last access time of an
 // origin.
 // XXX We should get rid of old metadata files at some point, bug 1343576.
 #define METADATA_FILE_NAME ".metadata"
 #define METADATA_TMP_FILE_NAME ".metadata-tmp"
 #define METADATA_V2_FILE_NAME ".metadata-v2"
@@ -1646,16 +1651,17 @@ class MOZ_STACK_CLASS OriginParser final
     eComplete,
     eHandledTrailingSeparator
   };
 
   const nsCString mOrigin;
   const OriginAttributes mOriginAttributes;
   Tokenizer mTokenizer;
 
+  uint32_t mAppId;
   nsCString mScheme;
   nsCString mHost;
   Nullable<uint32_t> mPort;
   nsTArray<nsCString> mPathnameComponents;
   nsCString mHandledTokens;
 
   SchemeType mSchemeType;
   State mState;
@@ -1669,16 +1675,17 @@ class MOZ_STACK_CLASS OriginParser final
   uint8_t mIPGroup;
 
  public:
   OriginParser(const nsACString& aOrigin,
                const OriginAttributes& aOriginAttributes)
       : mOrigin(aOrigin),
         mOriginAttributes(aOriginAttributes),
         mTokenizer(aOrigin, '+'),
+        mAppId(kNoAppId),
         mPort(),
         mSchemeType(eNone),
         mState(eExpectingAppIdOrScheme),
         mInIsolatedMozBrowser(false),
         mUniversalFileOrigin(false),
         mMaybeDriveLetter(false),
         mError(false),
         mMaybeObsolete(false),
@@ -2018,28 +2025,33 @@ nsresult GetBinaryOutputStream(nsIFile* 
 
   nsCOMPtr<nsIObjectOutputStream> objectOutputStream =
       NS_NewObjectOutputStream(outputStream);
 
   objectOutputStream.forget(aStream);
   return NS_OK;
 }
 
-void GetJarPrefix(bool aInIsolatedMozBrowser, nsACString& aJarPrefix) {
+void GetJarPrefix(uint32_t aAppId, bool aInIsolatedMozBrowser,
+                  nsACString& aJarPrefix) {
+  MOZ_ASSERT(aAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
+
+  if (aAppId == nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+    aAppId = nsIScriptSecurityManager::NO_APP_ID;
+  }
+
   aJarPrefix.Truncate();
 
   // Fallback.
-  if (!aInIsolatedMozBrowser) {
+  if (aAppId == nsIScriptSecurityManager::NO_APP_ID && !aInIsolatedMozBrowser) {
     return;
   }
 
-  // AppId is an unused b2g identifier. Let's set it to 0 all the time (see bug
-  // 1320404).
   // aJarPrefix = appId + "+" + { 't', 'f' } + "+";
-  aJarPrefix.AppendInt(0);  // TODO: this is the appId, to be removed.
+  aJarPrefix.AppendInt(aAppId);
   aJarPrefix.Append('+');
   aJarPrefix.Append(aInIsolatedMozBrowser ? 't' : 'f');
   aJarPrefix.Append('+');
 }
 
 nsresult CreateDirectoryMetadata(nsIFile* aDirectory, int64_t aTimestamp,
                                  const nsACString& aSuffix,
                                  const nsACString& aGroup,
@@ -2050,30 +2062,32 @@ nsresult CreateDirectoryMetadata(nsIFile
 
   nsCString groupNoSuffix;
   bool ok = groupAttributes.PopulateFromOrigin(aGroup, groupNoSuffix);
   if (!ok) {
     return NS_ERROR_FAILURE;
   }
 
   nsCString groupPrefix;
-  GetJarPrefix(groupAttributes.mInIsolatedMozBrowser, groupPrefix);
+  GetJarPrefix(groupAttributes.mAppId, groupAttributes.mInIsolatedMozBrowser,
+               groupPrefix);
 
   nsCString group = groupPrefix + groupNoSuffix;
 
   OriginAttributes originAttributes;
 
   nsCString originNoSuffix;
   ok = originAttributes.PopulateFromOrigin(aOrigin, originNoSuffix);
   if (!ok) {
     return NS_ERROR_FAILURE;
   }
 
   nsCString originPrefix;
-  GetJarPrefix(originAttributes.mInIsolatedMozBrowser, originPrefix);
+  GetJarPrefix(originAttributes.mAppId, originAttributes.mInIsolatedMozBrowser,
+               originPrefix);
 
   nsCString origin = originPrefix + originNoSuffix;
 
   MOZ_ASSERT(groupPrefix == originPrefix);
 
   nsCOMPtr<nsIFile> file;
   nsresult rv = aDirectory->Clone(getter_AddRefs(file));
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -9010,17 +9024,24 @@ auto OriginParser::Parse(nsACString& aSp
     return mSchemeType == eChrome ? ObsoleteOrigin : InvalidOrigin;
   }
 
   MOZ_ASSERT(mState == eComplete || mState == eHandledTrailingSeparator);
 
   // For IPv6 URL, it should at least have three groups.
   MOZ_ASSERT_IF(mIPGroup > 0, mIPGroup >= 3);
 
-  *aAttrs = mOriginAttributes;
+  if (mAppId == kNoAppId) {
+    *aAttrs = mOriginAttributes;
+  } else {
+    MOZ_ASSERT(mOriginAttributes.mAppId == kNoAppId);
+
+    *aAttrs = OriginAttributes(mAppId, mInIsolatedMozBrowser);
+  }
+
   nsAutoCString spec(mScheme);
 
   if (mSchemeType == eFile) {
     spec.AppendLiteral("://");
 
     if (mUniversalFileOrigin) {
       MOZ_ASSERT(mPathnameComponents.Length() == 1);
 
@@ -9126,18 +9147,19 @@ void OriginParser::HandleToken(const nsD
         return;
       }
 
       if (IsAsciiDigit(aToken.First())) {
         // nsDependentCSubstring doesn't provice ToInteger()
         nsCString token(aToken);
 
         nsresult rv;
-        Unused << token.ToInteger(&rv);
+        uint32_t appId = token.ToInteger(&rv);
         if (NS_SUCCEEDED(rv)) {
+          mAppId = appId;
           mState = eExpectingInMozBrowser;
           return;
         }
       }
 
       HandleScheme(aToken);
 
       return;
@@ -9902,16 +9924,30 @@ nsresult UpgradeStorageFrom1_0To2_0Helpe
   }
 
   return NS_OK;
 }
 
 nsresult UpgradeStorageFrom1_0To2_0Helper::MaybeRemoveAppsData(
     const OriginProps& aOriginProps, bool* aRemoved) {
   AssertIsOnIOThread();
+
+  // XXX This will need to be reworked as part of bug 1320404 (appId is
+  //     going to be removed from origin attributes).
+  if (aOriginProps.mAttrs.mAppId != kNoAppId &&
+      aOriginProps.mAttrs.mAppId != kUnknownAppId) {
+    nsresult rv = RemoveObsoleteOrigin(aOriginProps);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return rv;
+    }
+
+    *aRemoved = true;
+    return NS_OK;
+  }
+
   *aRemoved = false;
   return NS_OK;
 }
 
 nsresult UpgradeStorageFrom1_0To2_0Helper::MaybeStripObsoleteOriginAttributes(
     const OriginProps& aOriginProps, bool* aStripped) {
   AssertIsOnIOThread();
   MOZ_ASSERT(aOriginProps.mDirectory);
--- a/dom/quota/SerializationHelpers.h
+++ b/dom/quota/SerializationHelpers.h
@@ -28,25 +28,27 @@ struct ParamTraits<mozilla::dom::quota::
                                       mozilla::dom::quota::Client::IDB,
                                       mozilla::dom::quota::Client::TYPE_MAX> {};
 
 template <>
 struct ParamTraits<mozilla::OriginAttributesPattern> {
   typedef mozilla::OriginAttributesPattern paramType;
 
   static void Write(Message* aMsg, const paramType& aParam) {
+    WriteParam(aMsg, aParam.mAppId);
     WriteParam(aMsg, aParam.mFirstPartyDomain);
     WriteParam(aMsg, aParam.mInIsolatedMozBrowser);
     WriteParam(aMsg, aParam.mPrivateBrowsingId);
     WriteParam(aMsg, aParam.mUserContextId);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter,
                    paramType* aResult) {
-    return ReadParam(aMsg, aIter, &aResult->mFirstPartyDomain) &&
+    return ReadParam(aMsg, aIter, &aResult->mAppId) &&
+           ReadParam(aMsg, aIter, &aResult->mFirstPartyDomain) &&
            ReadParam(aMsg, aIter, &aResult->mInIsolatedMozBrowser) &&
            ReadParam(aMsg, aIter, &aResult->mPrivateBrowsingId) &&
            ReadParam(aMsg, aIter, &aResult->mUserContextId);
   }
 };
 
 }  // namespace IPC
 
--- a/dom/quota/nsIQuotaManagerService.idl
+++ b/dom/quota/nsIQuotaManagerService.idl
@@ -104,24 +104,22 @@ interface nsIQuotaManagerService : nsISu
    * If the dom.quotaManager.testing preference is not true the call will be
    * a no-op.
    */
   [must_use] nsIQuotaRequest
   clear();
 
   /**
    * Removes all storages stored for the given pattern. The files may not be
-   * deleted immediately depending on prohibitive concurrent operations.  In
-   * terms of locks, it will get an exclusive multi directory lock for given
-   * pattern.  For example, given pattern {"userContextId":1007} and set of 3
-   * origins ["http://www.mozilla.org^userContextId=1007",
-   * "http://www.example.org^userContextId=1007",
-   * "http://www.example.org^userContextId=1008"], the method will only lock 2
-   * origins ["http://www.mozilla.org^userContextId=1007",
-   * "http://www.example.org^userContextId=1007"].
+   * deleted immediately depending on prohibitive concurrent operations.
+   * In terms of locks, it will get an exclusive multi directory lock for given
+   * pattern.  For example, given pattern {"appId":1007} and set of 3 origins
+   * ["http://www.mozilla.org^appId=1007", "http://www.example.org^appId=1007",
+   * "http://www.example.org^appId=1008"], the method will only lock 2 origins
+   * ["http://www.mozilla.org^appId=1007", "http://www.example.org^appId=1007"].
    *
    * @param aPattern
    *        A pattern for the origins whose storages are to be cleared.
    *        Currently this is expected to be a JSON representation of the
    *        OriginAttributesPatternDictionary defined in ChromeUtils.webidl.
    */
   [must_use] nsIQuotaRequest
   clearStoragesForOriginAttributesPattern(in AString aPattern);
--- a/dom/quota/test/gtest/TestQuotaManager.cpp
+++ b/dom/quota/test/gtest/TestQuotaManager.cpp
@@ -44,16 +44,23 @@ TEST(QuotaManager, OriginScope)
   {
     NS_NAMED_LITERAL_CSTRING(prefix, "http://www.mozilla.org");
     originScope.SetFromPrefix(prefix);
     EXPECT_TRUE(originScope.IsPrefix());
     EXPECT_TRUE(originScope.GetOriginNoSuffix().Equals(prefix));
   }
 
   {
+    NS_NAMED_LITERAL_STRING(pattern, "{\"appId\":1007}");
+    originScope.SetFromJSONPattern(pattern);
+    EXPECT_TRUE(originScope.IsPattern());
+    EXPECT_TRUE(originScope.GetJSONPattern().Equals(pattern));
+  }
+
+  {
     originScope.SetFromNull();
     EXPECT_TRUE(originScope.IsNull());
   }
 
   // Test each origin scope type against particular origins.
 
   {
     originScope.SetFromOrigin(NS_LITERAL_CSTRING("http://www.mozilla.org"));
@@ -78,21 +85,38 @@ TEST(QuotaManager, OriginScope)
     };
 
     for (const auto& test : tests) {
       CheckOriginScopeMatchesOrigin(originScope, test.mOrigin, test.mMatch);
     }
   }
 
   {
+    originScope.SetFromJSONPattern(NS_LITERAL_STRING("{\"appId\":1007}"));
+
+    static const OriginTest tests[] = {
+        {"http+++www.mozilla.org^appId=1007", true},
+        {"http+++www.example.org^appId=1007", true},
+        {"http+++www.example.org^appId=1008", false},
+    };
+
+    for (const auto& test : tests) {
+      CheckOriginScopeMatchesOrigin(originScope, test.mOrigin, test.mMatch);
+    }
+  }
+
+  {
     originScope.SetFromNull();
 
     static const OriginTest tests[] = {
         {"http://www.mozilla.org", true},
         {"http://www.mozilla.org^userContextId=1", true},
         {"http://www.example.org^userContextId=1", true},
+        {"http+++www.mozilla.org^appId=1007", true},
+        {"http+++www.example.org^appId=1007", true},
+        {"http+++www.example.org^appId=1008", true},
     };
 
     for (const auto& test : tests) {
       CheckOriginScopeMatchesOrigin(originScope, test.mOrigin, test.mMatch);
     }
   }
 }
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..582edb43af082bc2b9ceed98b02eb6418f149a81
GIT binary patch
literal 3145
zc$^FHW@h1H0D)@(eqIbnfP+DXp|~W!C^0=%KQx4sfqC(XMG^0TxU_<sfsy4aBLf4A
z2vA)B&;${%`o=&%uj3}0-Wh?kGchpmAxzLKF3ia+Nd=qp=;WdZ5JoeHoA1j>9nVmY
z)B2};bpj325|R>9QVIeB5`ds3=?=qw9hX+Q)x!E6M_mHmYDhqBhPa)(KMcj~0!VI8
zNli;E%_)KSVe6r_5g?4k4-k`1PD3@x9E(Yb1qIsL+QpT{C8@c3>4}+%x%o+%IjMU2
zMd@)sInNYZLjwbIh=pDk)<%FZ&OifMI9cfQS92iG1Be~Sw@@!PwInemu_O_n&B+O0
zLoR*(ck{r30}5Fg2?^`wE}J`V#>^SB=TDhAdDZmzDST4`n!HrRL?T|k77rJjzU0}f
zg)5oR9Q?r4%-;&g%K&0$DmYlT%!r_yySG=JYhc;&Scsd;COC(em*-4l%*iiD&K$Y(
zXUUm4A$$KUDhcs4RXFbzzkkQ39V({b{M)^dB6i^d)QDBb;_Qr)5|F#g%ggn0^Q$s*
zauPu<hD7Au2Wulh7-vL+EYN#%^`0h>2g(5k#9M$GaX4&BPAL7csIVZ5kC~adJg+V^
z&ChR9TwS2m1np^oK|u*i*T=`jt!vj|Msw2kd*;a+K%PAi8<XKASkw@7mDGcCXV;%P
zbJFw5X?-vDqN7G$UR-Owd_8^o^w$ZgND-007&Rh1aX6|N=&F>|vecaXg480t<di(P
z`|?stkW!IaW}Z_~etB_fk*y&ls+69sjR0YsQ3Y~>1-soJTOiLHh=Zu<1k}jH;TA}+
zX(%=|HaecTaNt19ZWn9w{aVKA>Vf+DTUj(#F^Znm(A3n}cImpmr>^(ellmUIm%RN>
zUUm-SM)F7j&?8HC1&a6qdHp~fN)wO3l7KSMAIf<A!pPZiTvbe@V%ZHTDTx`YQ=eIi
zKAkSUT<oS4)6ywLpH~|d7YTj+6y#m7deaH4o>O+x2YQZ?Nsbv;l_UW#;NLoeXha3Y
z3aOwl;v8feuIfnwZgNAT6T&ojWyK1qtgxDitKvbp@POkUBo`v8Ay!B=gvV6ON(k9h
zHbzWSiLR1ZA%!fSV8X1Bkb|k5e9MtC263)Ht!0p1p}|DCE5NB5pF?of8_02SAB#h9
z)*`HsQWTG`G3yXyUpp`pZ5e7<6JaZAHGypFWio7qg)L5taa9G#VVl8%-C{B;23AO6
zj3*p0s|Dn6cuh^`pvDgYcVU+D$nM(0N(*<vVh~GtAK=Z(2C|w52p0h@p9tmw0N^lq
ALI3~&
new file mode 100644
--- /dev/null
+++ b/dom/quota/test/unit/test_removeAppsUpgrade.js
@@ -0,0 +1,66 @@
+/**
+ * Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+var testGenerator = testSteps();
+
+function* testSteps()
+{
+  const origins = [
+    {
+      path: "storage/default/http+++www.mozilla.org",
+      obsolete: false
+    },
+
+    {
+      path: "storage/default/app+++system.gaiamobile.org^appId=1007",
+      obsolete: true
+    },
+
+    {
+      path: "storage/default/https+++developer.cdn.mozilla.net^appId=1007&inBrowser=1",
+      obsolete: true
+    }
+  ];
+
+  info("Clearing");
+
+  clear(continueToNextStepSync);
+  yield undefined;
+
+  info("Installing package");
+
+  installPackage("removeAppsUpgrade_profile");
+
+  info("Checking origin directories");
+
+  for (let origin of origins) {
+    let originDir = getRelativeFile(origin.path);
+
+    let exists = originDir.exists();
+    ok(exists, "Origin directory does exist");
+  }
+
+  info("Initializing");
+
+  let request = init(continueToNextStepSync);
+  yield undefined;
+
+  ok(request.resultCode == NS_OK, "Initialization succeeded");
+
+  info("Checking origin directories");
+
+  for (let origin of origins) {
+    let originDir = getRelativeFile(origin.path);
+
+    let exists = originDir.exists();
+    if (origin.obsolete) {
+      ok(!exists, "Origin directory doesn't exist");
+    } else {
+      ok(exists, "Origin directory does exist");
+    }
+  }
+
+  finishTest();
+}
--- a/dom/quota/test/unit/xpcshell.ini
+++ b/dom/quota/test/unit/xpcshell.ini
@@ -13,16 +13,17 @@ support-files =
   groupMismatch_profile.zip
   idbSubdirUpgrade1_profile.zip
   idbSubdirUpgrade2_profile.zip
   localStorageArchive1upgrade_profile.zip
   localStorageArchiveDowngrade_profile.zip
   morgueCleanup_profile.zip
   obsoleteOriginAttributes_profile.zip
   originAttributesUpgrade_profile.zip
+  removeAppsUpgrade_profile.zip
   removeLocalStorage1_profile.zip
   removeLocalStorage2_profile.zip
   storagePersistentUpgrade_profile.zip
   tempMetadataCleanup_profile.zip
   version2_1upgrade_profile.zip
   version2_2upgrade_profile.zip
 
 [test_basics.js]
@@ -40,16 +41,17 @@ support-files =
 [test_morgueCleanup.js]
 [test_obsoleteOriginAttributesUpgrade.js]
 [test_obsoleteOrigins.js]
 [test_originAttributesUpgrade.js]
 [test_persist.js]
 [test_persist_eviction.js]
 [test_persist_globalLimit.js]
 [test_persist_groupLimit.js]
+[test_removeAppsUpgrade.js]
 [test_removeLocalStorage.js]
 [test_simpledb.js]
 [test_specialOrigins.js]
 [test_storagePersistentUpgrade.js]
 [test_storagePressure.js]
 [test_tempMetadataCleanup.js]
 [test_unknownFiles.js]
 [test_validOrigins.js]
--- a/dom/serviceworkers/ServiceWorkerManager.cpp
+++ b/dom/serviceworkers/ServiceWorkerManager.cpp
@@ -1499,16 +1499,22 @@ ServiceWorkerManager::GetServiceWorkerRe
 }
 
 already_AddRefed<ServiceWorkerRegistrationInfo>
 ServiceWorkerManager::GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal,
                                                        nsIURI* aURI) const {
   MOZ_ASSERT(aPrincipal);
   MOZ_ASSERT(aURI);
 
+  // XXXnsm Temporary fix until Bug 1171432 is fixed.
+  if (NS_WARN_IF(BasePrincipal::Cast(aPrincipal)->AppId() ==
+                 nsIScriptSecurityManager::UNKNOWN_APP_ID)) {
+    return nullptr;
+  }
+
   nsAutoCString scopeKey;
   nsresult rv = PrincipalToScopeKey(aPrincipal, scopeKey);
   if (NS_FAILED(rv)) {
     return nullptr;
   }
 
   return GetServiceWorkerRegistrationInfo(scopeKey, aURI);
 }
--- a/dom/serviceworkers/ServiceWorkerManager.h
+++ b/dom/serviceworkers/ServiceWorkerManager.h
@@ -323,18 +323,19 @@ class ServiceWorkerManager final : publi
   already_AddRefed<ServiceWorkerRegistrationInfo>
   GetServiceWorkerRegistrationInfo(nsIPrincipal* aPrincipal,
                                    nsIURI* aURI) const;
 
   already_AddRefed<ServiceWorkerRegistrationInfo>
   GetServiceWorkerRegistrationInfo(const nsACString& aScopeKey,
                                    nsIURI* aURI) const;
 
-  // This method generates a key using isInElementBrowser from the principal. We
-  // don't use the origin because it can change during the loading.
+  // This method generates a key using appId and isInElementBrowser from the
+  // principal. We don't use the origin because it can change during the
+  // loading.
   static nsresult PrincipalToScopeKey(nsIPrincipal* aPrincipal,
                                       nsACString& aKey);
 
   static nsresult PrincipalInfoToScopeKey(
       const mozilla::ipc::PrincipalInfo& aPrincipalInfo, nsACString& aKey);
 
   static void AddScopeAndRegistration(
       const nsACString& aScope, ServiceWorkerRegistrationInfo* aRegistation);
--- a/dom/serviceworkers/test/gtest/TestReadWrite.cpp
+++ b/dom/serviceworkers/test/gtest/TestReadWrite.cpp
@@ -154,17 +154,17 @@ TEST(ServiceWorkerRegistrar, TestWrongVe
   ASSERT_EQ((uint32_t)0, data.Length())
       << "No data should be found in an empty file";
 }
 
 TEST(ServiceWorkerRegistrar, TestReadData)
 {
   nsAutoCString buffer(SERVICEWORKERREGISTRAR_VERSION "\n");
 
-  buffer.AppendLiteral("^inBrowser=1\n");
+  buffer.AppendLiteral("^appId=123&inBrowser=1\n");
   buffer.AppendLiteral("https://scope_0.org\ncurrentWorkerURL 0\n");
   buffer.Append(SERVICEWORKERREGISTRAR_TRUE "\n");
   buffer.AppendLiteral("cacheName 0\n");
   buffer.AppendInt(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
                    16);
   buffer.AppendLiteral("\n");
   buffer.AppendInt(0);
   buffer.AppendLiteral("\n");
@@ -203,17 +203,17 @@ TEST(ServiceWorkerRegistrar, TestReadDat
   const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
   ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo)
       << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
 
   nsAutoCString suffix0;
   cInfo0.attrs().CreateSuffix(suffix0);
 
-  ASSERT_STREQ("^inBrowser=1", suffix0.get());
+  ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
   ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
             data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
@@ -260,17 +260,17 @@ TEST(ServiceWorkerRegistrar, TestDeleteD
   << "The file should not exist after a DeleteData().";
 }
 
 TEST(ServiceWorkerRegistrar, TestWriteData)
 {
   {
     RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
 
-    for (int i = 0; i < 2; ++i) {
+    for (int i = 0; i < 10; ++i) {
       ServiceWorkerRegistrationData reg;
 
       reg.scope() = nsPrintfCString("https://scope_write_%d.org", i);
       reg.currentWorkerURL() = nsPrintfCString("currentWorkerURL write %d", i);
       reg.currentWorkerHandlesFetch() = true;
       reg.cacheName() =
           NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
       reg.updateViaCache() =
@@ -280,42 +280,42 @@ TEST(ServiceWorkerRegistrar, TestWriteDa
       reg.currentWorkerActivatedTime() = PR_Now();
       reg.lastUpdateTime() = PR_Now();
 
       nsAutoCString spec;
       spec.AppendPrintf("spec write %d", i);
 
       nsTArray<mozilla::ipc::ContentSecurityPolicy> policies;
       reg.principal() = mozilla::ipc::ContentPrincipalInfo(
-          mozilla::OriginAttributes(i % 2), spec, spec, mozilla::Nothing(),
+          mozilla::OriginAttributes(i, i % 2), spec, spec, mozilla::Nothing(),
           std::move(policies), spec);
 
       swr->TestRegisterServiceWorker(reg);
     }
 
     nsresult rv = swr->TestWriteData();
     ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
   }
 
   RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
 
   nsresult rv = swr->TestReadData();
   ASSERT_EQ(NS_OK, rv) << "ReadData() should not fail";
 
   const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
-  ASSERT_EQ((uint32_t)2, data.Length()) << "2 entries should be found";
+  ASSERT_EQ((uint32_t)10, data.Length()) << "10 entries should be found";
 
-  for (int i = 0; i < 2; ++i) {
+  for (int i = 0; i < 10; ++i) {
     nsAutoCString test;
 
     ASSERT_EQ(data[i].principal().type(),
               mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
     const mozilla::ipc::ContentPrincipalInfo& cInfo = data[i].principal();
 
-    mozilla::OriginAttributes attrs(i % 2);
+    mozilla::OriginAttributes attrs(i, i % 2);
     nsAutoCString suffix, expectSuffix;
     attrs.CreateSuffix(expectSuffix);
     cInfo.attrs().CreateSuffix(suffix);
 
     ASSERT_STREQ(expectSuffix.get(), suffix.get());
 
     test.AppendPrintf("https://scope_write_%d.org", i);
     ASSERT_STREQ(test.get(), cInfo.spec().get());
@@ -375,17 +375,17 @@ TEST(ServiceWorkerRegistrar, TestVersion
   const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
   ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo)
       << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
 
   nsAutoCString suffix0;
   cInfo0.attrs().CreateSuffix(suffix0);
 
-  ASSERT_STREQ("^inBrowser=1", suffix0.get());
+  ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
   ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("activeCache 0",
                NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
             data[0].updateViaCache());
@@ -445,17 +445,17 @@ TEST(ServiceWorkerRegistrar, TestVersion
   const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
   ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo)
       << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
 
   nsAutoCString suffix0;
   cInfo0.attrs().CreateSuffix(suffix0);
 
-  ASSERT_STREQ("^inBrowser=1", suffix0.get());
+  ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
   ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
             data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
@@ -513,17 +513,17 @@ TEST(ServiceWorkerRegistrar, TestVersion
   const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
   ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo)
       << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
 
   nsAutoCString suffix0;
   cInfo0.attrs().CreateSuffix(suffix0);
 
-  ASSERT_STREQ("^inBrowser=1", suffix0.get());
+  ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
   ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   // default is true
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
             data[0].updateViaCache());
@@ -585,17 +585,17 @@ TEST(ServiceWorkerRegistrar, TestVersion
   const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
   ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo)
       << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
 
   nsAutoCString suffix0;
   cInfo0.attrs().CreateSuffix(suffix0);
 
-  ASSERT_STREQ("^inBrowser=1", suffix0.get());
+  ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
   ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
             data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
@@ -659,17 +659,17 @@ TEST(ServiceWorkerRegistrar, TestVersion
   const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
   ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo)
       << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
 
   nsAutoCString suffix0;
   cInfo0.attrs().CreateSuffix(suffix0);
 
-  ASSERT_STREQ("^inBrowser=1", suffix0.get());
+  ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
   ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL,
             data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
@@ -746,17 +746,17 @@ TEST(ServiceWorkerRegistrar, TestVersion
   const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
   ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo)
       << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
 
   nsAutoCString suffix0;
   cInfo0.attrs().CreateSuffix(suffix0);
 
-  ASSERT_STREQ("^inBrowser=1", suffix0.get());
+  ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
   ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_TRUE(data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_ALL,
             data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
@@ -786,33 +786,33 @@ TEST(ServiceWorkerRegistrar, TestVersion
 
 TEST(ServiceWorkerRegistrar, TestDedupeRead)
 {
   nsAutoCString buffer(
       "3"
       "\n");
 
   // unique entries
-  buffer.AppendLiteral("^inBrowser=1\n");
+  buffer.AppendLiteral("^appId=123&inBrowser=1\n");
   buffer.AppendLiteral(
       "spec 0\nhttps://scope_0.org\ncurrentWorkerURL 0\ncacheName 0\n");
   buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
 
   buffer.AppendLiteral("\n");
   buffer.AppendLiteral(
       "spec 1\nhttps://scope_1.org\ncurrentWorkerURL 1\ncacheName 1\n");
   buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
 
   // dupe entries
-  buffer.AppendLiteral("^inBrowser=1\n");
+  buffer.AppendLiteral("^appId=123&inBrowser=1\n");
   buffer.AppendLiteral(
       "spec 1\nhttps://scope_0.org\ncurrentWorkerURL 0\ncacheName 0\n");
   buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
 
-  buffer.AppendLiteral("^inBrowser=1\n");
+  buffer.AppendLiteral("^appId=123&inBrowser=1\n");
   buffer.AppendLiteral(
       "spec 2\nhttps://scope_0.org\ncurrentWorkerURL 0\ncacheName 0\n");
   buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
 
   buffer.AppendLiteral("\n");
   buffer.AppendLiteral(
       "spec 3\nhttps://scope_1.org\ncurrentWorkerURL 1\ncacheName 1\n");
   buffer.AppendLiteral(SERVICEWORKERREGISTRAR_TERMINATOR "\n");
@@ -831,17 +831,17 @@ TEST(ServiceWorkerRegistrar, TestDedupeR
   const mozilla::ipc::PrincipalInfo& info0 = data[0].principal();
   ASSERT_EQ(info0.type(), mozilla::ipc::PrincipalInfo::TContentPrincipalInfo)
       << "First principal must be content";
   const mozilla::ipc::ContentPrincipalInfo& cInfo0 = data[0].principal();
 
   nsAutoCString suffix0;
   cInfo0.attrs().CreateSuffix(suffix0);
 
-  ASSERT_STREQ("^inBrowser=1", suffix0.get());
+  ASSERT_STREQ("^appId=123&inBrowser=1", suffix0.get());
   ASSERT_STREQ("https://scope_0.org", cInfo0.spec().get());
   ASSERT_STREQ("https://scope_0.org", data[0].scope().get());
   ASSERT_STREQ("currentWorkerURL 0", data[0].currentWorkerURL().get());
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
   ASSERT_STREQ("cacheName 0", NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
             data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
@@ -869,33 +869,33 @@ TEST(ServiceWorkerRegistrar, TestDedupeR
   ASSERT_EQ((int64_t)0, data[1].lastUpdateTime());
 }
 
 TEST(ServiceWorkerRegistrar, TestDedupeWrite)
 {
   {
     RefPtr<ServiceWorkerRegistrarTest> swr = new ServiceWorkerRegistrarTest;
 
-    for (int i = 0; i < 2; ++i) {
+    for (int i = 0; i < 10; ++i) {
       ServiceWorkerRegistrationData reg;
 
       reg.scope() = NS_LITERAL_CSTRING("https://scope_write.dedupe");
       reg.currentWorkerURL() = nsPrintfCString("currentWorkerURL write %d", i);
       reg.currentWorkerHandlesFetch() = true;
       reg.cacheName() =
           NS_ConvertUTF8toUTF16(nsPrintfCString("cacheName write %d", i));
       reg.updateViaCache() =
           nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS;
 
       nsAutoCString spec;
       spec.AppendPrintf("spec write dedupe/%d", i);
 
       nsTArray<mozilla::ipc::ContentSecurityPolicy> policies;
       reg.principal() = mozilla::ipc::ContentPrincipalInfo(
-          mozilla::OriginAttributes(false), spec, spec, mozilla::Nothing(),
+          mozilla::OriginAttributes(0, false), spec, spec, mozilla::Nothing(),
           std::move(policies), spec);
 
       swr->TestRegisterServiceWorker(reg);
     }
 
     nsresult rv = swr->TestWriteData();
     ASSERT_EQ(NS_OK, rv) << "WriteData() should not fail";
   }
@@ -908,29 +908,29 @@ TEST(ServiceWorkerRegistrar, TestDedupeW
   // Duplicate entries should be removed.
   const nsTArray<ServiceWorkerRegistrationData>& data = swr->TestGetData();
   ASSERT_EQ((uint32_t)1, data.Length()) << "1 entry should be found";
 
   ASSERT_EQ(data[0].principal().type(),
             mozilla::ipc::PrincipalInfo::TContentPrincipalInfo);
   const mozilla::ipc::ContentPrincipalInfo& cInfo = data[0].principal();
 
-  mozilla::OriginAttributes attrs(false);
+  mozilla::OriginAttributes attrs(0, false);
   nsAutoCString suffix, expectSuffix;
   attrs.CreateSuffix(expectSuffix);
   cInfo.attrs().CreateSuffix(suffix);
 
   // Last entry passed to RegisterServiceWorkerInternal() should overwrite
-  // previous values.  So expect "1" in values here.
+  // previous values.  So expect "9" in values here.
   ASSERT_STREQ(expectSuffix.get(), suffix.get());
   ASSERT_STREQ("https://scope_write.dedupe", cInfo.spec().get());
   ASSERT_STREQ("https://scope_write.dedupe", data[0].scope().get());
-  ASSERT_STREQ("currentWorkerURL write 1", data[0].currentWorkerURL().get());
+  ASSERT_STREQ("currentWorkerURL write 9", data[0].currentWorkerURL().get());
   ASSERT_EQ(true, data[0].currentWorkerHandlesFetch());
-  ASSERT_STREQ("cacheName write 1",
+  ASSERT_STREQ("cacheName write 9",
                NS_ConvertUTF16toUTF8(data[0].cacheName()).get());
   ASSERT_EQ(nsIServiceWorkerRegistrationInfo::UPDATE_VIA_CACHE_IMPORTS,
             data[0].updateViaCache());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerInstalledTime());
   ASSERT_EQ((int64_t)0, data[0].currentWorkerActivatedTime());
   ASSERT_EQ((int64_t)0, data[0].lastUpdateTime());
 }
 
--- a/dom/storage/StorageDBUpdater.cpp
+++ b/dom/storage/StorageDBUpdater.cpp
@@ -65,17 +65,16 @@ class ExtractOriginData : protected mozi
 
     // Parse optional appId:isInIsolatedMozBrowserElement: string, in case
     // we don't find it, the scope is our new origin key and suffix
     // is empty.
     suffix.Truncate();
     origin.Assign(scope);
 
     // Bail out if it isn't appId.
-    // AppId doesn't exist any more but we could have old storage data...
     uint32_t appId;
     if (!ReadInteger(&appId)) {
       return;
     }
 
     // Should be followed by a colon.
     if (!CheckChar(':')) {
       return;
@@ -126,17 +125,17 @@ class ExtractOriginData : protected mozi
       Token t;
       while (Next(t)) {
         if (t.Equals(Token::Char(':'))) {
           Claim(suffix);
           break;
         }
       }
     } else {
-      OriginAttributes attrs(inIsolatedMozBrowser);
+      OriginAttributes attrs(appId, inIsolatedMozBrowser);
       attrs.CreateSuffix(suffix);
     }
 
     // Consume the rest of the input as "origin".
     origin.Assign(Substring(mCursor, mEnd));
   }
 };
 
--- a/dom/storage/StorageUtils.cpp
+++ b/dom/storage/StorageUtils.cpp
@@ -113,29 +113,31 @@ nsCString Scheme0Scope(const nsACString&
   nsCString result;
 
   OriginAttributes oa;
   if (!aOriginSuffix.IsEmpty()) {
     DebugOnly<bool> success = oa.PopulateFromSuffix(aOriginSuffix);
     MOZ_ASSERT(success);
   }
 
-  if (oa.mInIsolatedMozBrowser) {
-    result.AppendInt(0);  // This is the appId to be removed.
+  if (oa.mAppId != nsIScriptSecurityManager::NO_APP_ID ||
+      oa.mInIsolatedMozBrowser) {
+    result.AppendInt(oa.mAppId);
     result.Append(':');
     result.Append(oa.mInIsolatedMozBrowser ? 't' : 'f');
     result.Append(':');
   }
 
   // If there is more than just appid and/or inbrowser stored in origin
   // attributes, put it to the schema 0 scope as well.  We must do that
   // to keep the scope column unique (same resolution as schema 1 has
   // with originAttributes and originKey columns) so that switch between
   // schema 1 and 0 always works in both ways.
   nsAutoCString remaining;
+  oa.mAppId = 0;
   oa.mInIsolatedMozBrowser = false;
   oa.CreateSuffix(remaining);
   if (!remaining.IsEmpty()) {
     MOZ_ASSERT(!aOriginSuffix.IsEmpty());
 
     if (result.IsEmpty()) {
       // Must contain the old prefix, otherwise we won't search for the whole
       // origin attributes suffix.
--- a/dom/workers/WorkerLoadInfo.cpp
+++ b/dom/workers/WorkerLoadInfo.cpp
@@ -208,17 +208,17 @@ nsresult WorkerLoadInfo::GetPrincipalsAn
         channelStoragePrincipal = mLoadingPrincipal;
       } else {
         return NS_ERROR_DOM_BAD_URI;
       }
     }
   }
 
   // The principal can change, but it should still match the original
-  // load group's browser element flag.
+  // load group's appId and browser element flag.
   MOZ_ASSERT(NS_LoadGroupMatchesPrincipal(channelLoadGroup, channelPrincipal));
 
   channelPrincipal.forget(aPrincipalOut);
   channelStoragePrincipal.forget(aStoragePrincipalOut);
   channelLoadGroup.forget(aLoadGroupOut);
 
   return NS_OK;
 }
--- a/extensions/permissions/nsPermissionManager.cpp
+++ b/extensions/permissions/nsPermissionManager.cpp
@@ -194,19 +194,20 @@ nsresult GetPrincipalFromOrigin(const ns
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsCOMPtr<nsIPrincipal> principal =
       mozilla::BasePrincipal::CreateCodebasePrincipal(uri, attrs);
   principal.forget(aPrincipal);
   return NS_OK;
 }
 
-nsresult GetPrincipal(nsIURI* aURI, bool aIsInIsolatedMozBrowserElement,
+nsresult GetPrincipal(nsIURI* aURI, uint32_t aAppId,
+                      bool aIsInIsolatedMozBrowserElement,
                       nsIPrincipal** aPrincipal) {
-  mozilla::OriginAttributes attrs(aIsInIsolatedMozBrowserElement);
+  mozilla::OriginAttributes attrs(aAppId, aIsInIsolatedMozBrowserElement);
   nsCOMPtr<nsIPrincipal> principal =
       mozilla::BasePrincipal::CreateCodebasePrincipal(aURI, attrs);
   NS_ENSURE_TRUE(principal, NS_ERROR_FAILURE);
 
   principal.forget(aPrincipal);
   return NS_OK;
 }
 
@@ -459,17 +460,18 @@ class MOZ_STACK_CLASS UpgradeIPHostToOri
   nsCOMPtr<mozIStorageStatement> mLookupStmt;
   nsCOMPtr<mozIStorageConnection> mDBConn;
   int64_t* mID;
 };
 
 nsresult UpgradeHostToOriginAndInsert(
     const nsACString& aHost, const nsCString& aType, uint32_t aPermission,
     uint32_t aExpireType, int64_t aExpireTime, int64_t aModificationTime,
-    bool aIsInIsolatedMozBrowserElement, UpgradeHostToOriginHelper* aHelper) {
+    uint32_t aAppId, bool aIsInIsolatedMozBrowserElement,
+    UpgradeHostToOriginHelper* aHelper) {
   if (aHost.EqualsLiteral("<file>")) {
     // We no longer support the magic host <file>
     NS_WARNING(
         "The magic host <file> is no longer supported. "
         "It is being removed from the permissions database.");
     return NS_OK;
   }
 
@@ -484,17 +486,17 @@ nsresult UpgradeHostToOriginAndInsert(
     bool nullpScheme = false;
     if (NS_SUCCEEDED(uri->SchemeIs("moz-nullprincipal", &nullpScheme)) &&
         nullpScheme) {
       NS_WARNING("A moz-nullprincipal: permission is being discarded.");
       return NS_OK;
     }
 
     nsCOMPtr<nsIPrincipal> principal;
-    rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
+    rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement,
                       getter_AddRefs(principal));
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsAutoCString origin;
     rv = GetOriginFromPrincipal(principal, origin);
     NS_ENSURE_SUCCESS(rv, rv);
 
     return aHelper->Insert(origin, aType, aPermission, aExpireType, aExpireTime,
@@ -599,17 +601,17 @@ nsresult UpgradeHostToOriginAndInsert(
 
       // Use the provided host - this URI may be for a subdomain, rather than
       // the host we care about.
       rv = NS_MutateURI(uri).SetHost(aHost).Finalize(uri);
       if (NS_WARN_IF(NS_FAILED(rv))) continue;
 
       // We now have a URI which we can make a nsIPrincipal out of
       nsCOMPtr<nsIPrincipal> principal;
-      rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
+      rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement,
                         getter_AddRefs(principal));
       if (NS_WARN_IF(NS_FAILED(rv))) continue;
 
       nsAutoCString origin;
       rv = GetOriginFromPrincipal(principal, origin);
       if (NS_WARN_IF(NS_FAILED(rv))) continue;
 
       // Ensure that we don't insert the same origin repeatedly
@@ -647,32 +649,32 @@ nsresult UpgradeHostToOriginAndInsert(
       hostSegment.Assign(aHost);
     }
 
     // http:// URI default
     rv = NS_NewURI(getter_AddRefs(uri),
                    NS_LITERAL_CSTRING("http://") + hostSegment);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
+    rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement,
                       getter_AddRefs(principal));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = GetOriginFromPrincipal(principal, origin);
     NS_ENSURE_SUCCESS(rv, rv);
 
     aHelper->Insert(origin, aType, aPermission, aExpireType, aExpireTime,
                     aModificationTime);
 
     // https:// URI default
     rv = NS_NewURI(getter_AddRefs(uri),
                    NS_LITERAL_CSTRING("https://") + hostSegment);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    rv = GetPrincipal(uri, aIsInIsolatedMozBrowserElement,
+    rv = GetPrincipal(uri, aAppId, aIsInIsolatedMozBrowserElement,
                       getter_AddRefs(principal));
     NS_ENSURE_SUCCESS(rv, rv);
 
     rv = GetOriginFromPrincipal(principal, origin);
     NS_ENSURE_SUCCESS(rv, rv);
 
     aHelper->Insert(origin, aType, aPermission, aExpireType, aExpireTime,
                     aModificationTime);
@@ -882,17 +884,17 @@ void nsPermissionManager::Startup() {
   nsCOMPtr<nsIPermissionManager> permManager =
       do_GetService("@mozilla.org/permissionmanager;1");
 }
 
 ////////////////////////////////////////////////////////////////////////////////
 // nsPermissionManager Implementation
 
 #define PERMISSIONS_FILE_NAME "permissions.sqlite"
-#define HOSTS_SCHEMA_VERSION 10
+#define HOSTS_SCHEMA_VERSION 9
 
 #define HOSTPERM_FILE_NAME "hostperm.1"
 
 // Default permissions are read from a URL - this is the preference we read
 // to find that URL. If not set, don't use any default permissions.
 static const char kDefaultsUrlPrefName[] = "permissions.manager.defaultsUrl";
 
 static const char kPermissionChangeNotification[] = PERM_CHANGE_NOTIFICATION;
@@ -1296,26 +1298,27 @@ nsresult nsPermissionManager::InitDB(boo
                                  ",modificationTime INTEGER"
                                  ")"));
           NS_ENSURE_SUCCESS(rv, rv);
 
           nsCOMPtr<mozIStorageStatement> stmt;
           rv = mDBConn->CreateStatement(
               NS_LITERAL_CSTRING(
                   "SELECT host, type, permission, expireType, expireTime, "
-                  "modificationTime, isInBrowserElement FROM moz_hosts"),
+                  "modificationTime, appId, isInBrowserElement FROM moz_hosts"),
               getter_AddRefs(stmt));
           NS_ENSURE_SUCCESS(rv, rv);
 
           int64_t id = 0;
           nsAutoCString host, type;
           uint32_t permission;
           uint32_t expireType;
           int64_t expireTime;
           int64_t modificationTime;
+          uint32_t appId;
           bool isInBrowserElement;
           bool hasResult;
 
           while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
             // Read in the old row
             rv = stmt->GetUTF8String(0, host);
             if (NS_WARN_IF(NS_FAILED(rv))) {
               migrationError = true;
@@ -1325,24 +1328,29 @@ nsresult nsPermissionManager::InitDB(boo
             if (NS_WARN_IF(NS_FAILED(rv))) {
               migrationError = true;
               continue;
             }
             permission = stmt->AsInt32(2);
             expireType = stmt->AsInt32(3);
             expireTime = stmt->AsInt64(4);
             modificationTime = stmt->AsInt64(5);
-            isInBrowserElement = static_cast<bool>(stmt->AsInt32(6));
+            if (NS_WARN_IF(stmt->AsInt64(6) < 0)) {
+              migrationError = true;
+              continue;
+            }
+            appId = static_cast<uint32_t>(stmt->AsInt64(6));
+            isInBrowserElement = static_cast<bool>(stmt->AsInt32(7));
 
             // Perform the meat of the migration by deferring to the
             // UpgradeHostToOriginAndInsert function.
             UpgradeHostToOriginDBMigration upHelper(mDBConn, &id);
             rv = UpgradeHostToOriginAndInsert(
                 host, type, permission, expireType, expireTime,
-                modificationTime, isInBrowserElement, &upHelper);
+                modificationTime, appId, isInBrowserElement, &upHelper);
             if (NS_FAILED(rv)) {
               NS_WARNING(
                   "Unexpected failure when upgrading migrating permission "
                   "from host to origin");
               migrationError = true;
             }
           }
 
@@ -1469,17 +1477,17 @@ nsresult nsPermissionManager::InitDB(boo
 
         // Only perform this migration if the original schema version was 7, and
         // the moz_hosts table is a backup.
         if (dbSchemaVersion == 7 && hostsIsBackupExists) {
           nsCOMPtr<mozIStorageStatement> stmt;
           rv = mDBConn->CreateStatement(
               NS_LITERAL_CSTRING(
                   "SELECT host, type, permission, expireType, expireTime, "
-                  "modificationTime, isInBrowserElement FROM moz_hosts"),
+                  "modificationTime, appId, isInBrowserElement FROM moz_hosts"),
               getter_AddRefs(stmt));
           NS_ENSURE_SUCCESS(rv, rv);
 
           nsCOMPtr<mozIStorageStatement> idStmt;
           rv = mDBConn->CreateStatement(
               NS_LITERAL_CSTRING("SELECT MAX(id) FROM moz_hosts"),
               getter_AddRefs(idStmt));
           int64_t id = 0;
@@ -1489,16 +1497,17 @@ nsresult nsPermissionManager::InitDB(boo
             id = idStmt->AsInt32(0) + 1;
           }
 
           nsAutoCString host, type;
           uint32_t permission;
           uint32_t expireType;
           int64_t expireTime;
           int64_t modificationTime;
+          uint32_t appId;
           bool isInBrowserElement;
 
           while (NS_SUCCEEDED(stmt->ExecuteStep(&hasResult)) && hasResult) {
             // Read in the old row
             rv = stmt->GetUTF8String(0, host);
             if (NS_WARN_IF(NS_FAILED(rv))) {
               continue;
             }
@@ -1514,24 +1523,28 @@ nsresult nsPermissionManager::InitDB(boo
             rv = stmt->GetUTF8String(1, type);
             if (NS_WARN_IF(NS_FAILED(rv))) {
               continue;
             }
             permission = stmt->AsInt32(2);
             expireType = stmt->AsInt32(3);
             expireTime = stmt->AsInt64(4);
             modificationTime = stmt->AsInt64(5);
-            isInBrowserElement = static_cast<bool>(stmt->AsInt32(6));
+            if (NS_WARN_IF(stmt->AsInt64(6) < 0)) {
+              continue;
+            }
+            appId = static_cast<uint32_t>(stmt->AsInt64(6));
+            isInBrowserElement = static_cast<bool>(stmt->AsInt32(7));
 
             // Perform the meat of the migration by deferring to the
             // UpgradeHostToOriginAndInsert function.
             UpgradeIPHostToOriginDB upHelper(mDBConn, &id);
             rv = UpgradeHostToOriginAndInsert(
                 host, type, permission, expireType, expireTime,
-                modificationTime, isInBrowserElement, &upHelper);
+                modificationTime, appId, isInBrowserElement, &upHelper);
             if (NS_FAILED(rv)) {
               NS_WARNING(
                   "Unexpected failure when upgrading migrating permission "
                   "from host to origin");
             }
           }
         }
 
@@ -1568,71 +1581,16 @@ nsresult nsPermissionManager::InitDB(boo
 
         rv = mDBConn->SetSchemaVersion(9);
         NS_ENSURE_SUCCESS(rv, rv);
       }
 
         // fall through to the next upgrade
         MOZ_FALLTHROUGH;
 
-      // Version 10 removes appId from moz_hosts. SQLite doesn't support the
-      // dropping of columns from existing tables. We need to create a temporary
-      // table, copy the data, drop the old table, rename the new one.
-      case 9: {
-        rv = mDBConn->BeginTransaction();
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        bool tableExists = false;
-        mDBConn->TableExists(NS_LITERAL_CSTRING("moz_hosts_v9"), &tableExists);
-        if (tableExists) {
-          NS_WARNING(
-              "The temporary database moz_hosts_v9 already exists, dropping "
-              "it.");
-          rv = mDBConn->ExecuteSimpleSQL(
-              NS_LITERAL_CSTRING("DROP TABLE moz_hosts_v9"));
-          NS_ENSURE_SUCCESS(rv, rv);
-        }
-
-        rv = mDBConn->ExecuteSimpleSQL(
-            NS_LITERAL_CSTRING("CREATE TABLE moz_hosts_v9 ("
-                               " id INTEGER PRIMARY KEY"
-                               ",host TEXT"
-                               ",type TEXT"
-                               ",permission INTEGER"
-                               ",expireType INTEGER"
-                               ",expireTime INTEGER"
-                               ",modificationTime INTEGER"
-                               ",isInBrowserElement INTEGER"
-                               ")"));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        rv = mDBConn->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
-            "INSERT INTO moz_hosts_v9 "
-            "(id, host, type, permission, expireType, "
-            "expireTime, modificationTime, isInBrowserElement) "
-            "SELECT id, host, type, permission, expireType, expireTime, "
-            "modificationTime, isInBrowserElement FROM moz_hosts WHERE appId = "
-            "0"));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        rv = mDBConn->ExecuteSimpleSQL(
-            NS_LITERAL_CSTRING("DROP TABLE moz_hosts"));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        rv = mDBConn->ExecuteSimpleSQL(
-            NS_LITERAL_CSTRING("ALTER TABLE moz_hosts_v9 RENAME TO moz_hosts"));
-        NS_ENSURE_SUCCESS(rv, rv);
-
-        rv = mDBConn->CommitTransaction();
-        NS_ENSURE_SUCCESS(rv, rv);
-      }
-
-        // fall through to the next upgrade
-        MOZ_FALLTHROUGH;
-
       // current version.
       case HOSTS_SCHEMA_VERSION:
         break;
 
       // downgrading.
       // if columns have been added to the table, we can still use the ones we
       // understand safely. if columns have been deleted or altered, just
       // blow away the table and start from scratch! if you change the way
@@ -1715,16 +1673,17 @@ nsresult nsPermissionManager::CreateTabl
       NS_LITERAL_CSTRING("CREATE TABLE moz_hosts ("
                          " id INTEGER PRIMARY KEY"
                          ",host TEXT"
                          ",type TEXT"
                          ",permission INTEGER"
                          ",expireType INTEGER"
                          ",expireTime INTEGER"
                          ",modificationTime INTEGER"
+                         ",appId INTEGER"
                          ",isInBrowserElement INTEGER"
                          ")"));
 }
 
 NS_IMETHODIMP
 nsPermissionManager::Add(nsIURI* aURI, const nsACString& aType,
                          uint32_t aPermission, uint32_t aExpireType,
                          int64_t aExpireTime) {
@@ -2450,17 +2409,17 @@ nsresult nsPermissionManager::CommonTest
 
   *aPermission = aIncludingSession
                      ? entry->GetPermission(aTypeIndex).mPermission
                      : entry->GetPermission(aTypeIndex).mNonSessionPermission;
 
   return NS_OK;
 }
 
-// Returns PermissionHashKey for a given { host, isInBrowserElement }
+// Returns PermissionHashKey for a given { host, appId, isInBrowserElement }
 // tuple. This is not simply using PermissionKey because we will walk-up domains
 // in case of |host| contains sub-domains. Returns null if nothing found. Also
 // accepts host on the format "<foo>". This will perform an exact match lookup
 // as the string doesn't contain any dots.
 nsPermissionManager::PermissionHashKey*
 nsPermissionManager::GetPermissionHashKey(nsIPrincipal* aPrincipal,
                                           uint32_t aType,
                                           bool aExactHostMatch) {
@@ -2503,17 +2462,17 @@ nsPermissionManager::GetPermissionHashKe
       return GetPermissionHashKey(principal, aType, aExactHostMatch);
     }
   }
 
   // No entry, really...
   return nullptr;
 }
 
-// Returns PermissionHashKey for a given { host, isInBrowserElement }
+// Returns PermissionHashKey for a given { host, appId, isInBrowserElement }
 // tuple. This is not simply using PermissionKey because we will walk-up domains
 // in case of |host| contains sub-domains. Returns null if nothing found. Also
 // accepts host on the format "<foo>". This will perform an exact match lookup
 // as the string doesn't contain any dots.
 nsPermissionManager::PermissionHashKey*
 nsPermissionManager::GetPermissionHashKey(nsIURI* aURI,
                                           const nsACString& aOriginNoSuffix,
                                           uint32_t aType,
@@ -3038,20 +2997,20 @@ nsresult nsPermissionManager::_DoImport(
       uint32_t permission = lineArray[2].ToInteger(&error);
       if (NS_FAILED(error)) continue;
 
       // the import file format doesn't handle modification times, so we use
       // 0, which AddInternal will convert to now()
       int64_t modificationTime = 0;
 
       UpgradeHostToOriginHostfileImport upHelper(this, operation, id);
-      error =
-          UpgradeHostToOriginAndInsert(lineArray[3], lineArray[1], permission,
-                                       nsIPermissionManager::EXPIRE_NEVER, 0,
-                                       modificationTime, false, &upHelper);
+      error = UpgradeHostToOriginAndInsert(
+          lineArray[3], lineArray[1], permission,
+          nsIPermissionManager::EXPIRE_NEVER, 0, modificationTime,
+          nsIScriptSecurityManager::NO_APP_ID, false, &upHelper);
       if (NS_FAILED(error)) {
         NS_WARNING("There was a problem importing a host permission");
       }
     } else if (lineArray[0].EqualsLiteral(kMatchTypeOrigin) &&
                lineArray.Length() == 4) {
       nsresult error = NS_OK;
       uint32_t permission = lineArray[2].ToInteger(&error);
       if (NS_FAILED(error)) continue;
--- a/extensions/permissions/test/unit/test_permmanager_cleardata.js
+++ b/extensions/permissions/test/unit/test_permmanager_cleardata.js
@@ -48,19 +48,21 @@ function test(aEntries, aData, aResults)
 function run_test()
 {
   do_get_profile();
 
   pm = Cc["@mozilla.org/permissionmanager;1"]
          .getService(Ci.nsIPermissionManager);
 
   let entries = [
+    { origin: 'http://example.com', originAttributes: { appId: 1 } },
+    { origin: 'http://example.com', originAttributes: { appId: 1, inIsolatedMozBrowser: true } },
     { origin: 'http://example.com', originAttributes: {} },
-    { origin: 'http://example.com', originAttributes: { inIsolatedMozBrowser: true } },
+    { origin: 'http://example.com', originAttributes: { appId: 2 } },
   ];
 
-  // In that case, all permissions should be removed.
-  test(entries, getData({}), [ pm.UNKNOWN_ACTION, pm.UNKNOWN_ACTION, pm.ALLOW_ACTION, pm.ALLOW_ACTION ]);
+  // In that case, all permissions from app 1 should be removed but not the other ones.
+  test(entries, getData({appId: 1}), [ pm.UNKNOWN_ACTION, pm.UNKNOWN_ACTION, pm.ALLOW_ACTION, pm.ALLOW_ACTION ]);
 
-  // In that case, only the permissions related to a browserElement should be removed.
+  // In that case, only the permissions of app 1 related to a browserElement should be removed.
   // All the other permissions should stay.
-  test(entries, getData({ inIsolatedMozBrowser: true}), [ pm.ALLOW_ACTION, pm.UNKNOWN_ACTION, pm.ALLOW_ACTION, pm.ALLOW_ACTION ]);
+  test(entries, getData({appId: 1, inIsolatedMozBrowser: true}), [ pm.ALLOW_ACTION, pm.UNKNOWN_ACTION, pm.ALLOW_ACTION, pm.ALLOW_ACTION ]);
 }
--- a/extensions/permissions/test/unit/test_permmanager_defaults.js
+++ b/extensions/permissions/test/unit/test_permmanager_defaults.js
@@ -34,17 +34,17 @@ add_task(async function do_test() {
              createInstance(Ci.nsIConverterOutputStream);
   conv.init(ostream, "UTF-8");
 
   conv.writeString("# this is a comment\n");
   conv.writeString("\n"); // a blank line!
   conv.writeString("host\t" + TEST_PERMISSION + "\t1\t" + TEST_ORIGIN.host + "\n");
   conv.writeString("host\t" + TEST_PERMISSION + "\t1\t" + TEST_ORIGIN_2.host + "\n");
   conv.writeString("origin\t" + TEST_PERMISSION + "\t1\t" + TEST_ORIGIN_3.spec + "\n");
-  conv.writeString("origin\t" + TEST_PERMISSION + "\t1\t" + TEST_ORIGIN.spec + "^inBrowser=1\n");
+  conv.writeString("origin\t" + TEST_PERMISSION + "\t1\t" + TEST_ORIGIN.spec + "^appId=1000&inBrowser=1\n");
   ostream.close();
 
   // Set the preference used by the permission manager so the file is read.
   Services.prefs.setCharPref("permissions.manager.defaultsUrl", "file://" + file.path);
 
   // This will force the permission-manager to reload the data.
   Services.obs.notifyObservers(null, "testonly-reload-permissions-from-disk", "");
 
@@ -52,17 +52,17 @@ add_task(async function do_test() {
            getService(Ci.nsIPermissionManager);
 
   // test the default permission was applied.
   let principal = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN, {});
   let principalHttps = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN_HTTPS, {});
   let principal2 = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN_2, {});
   let principal3 = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN_3, {});
 
-  let attrs = {inIsolatedMozBrowser: true};
+  let attrs = {appId: 1000, inIsolatedMozBrowser: true};
   let principal4 = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN, attrs);
   let principal5 = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN_3, attrs);
 
   attrs = {userContextId: 1};
   let principal6 = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN, attrs);
   attrs = {firstPartyDomain: "cnn.com"};
   let principal7 = Services.scriptSecurityManager.createCodebasePrincipal(TEST_ORIGIN, attrs);
   attrs = {userContextId: 1, firstPartyDomain: "cnn.com"};
--- a/extensions/permissions/test/unit/test_permmanager_load_invalid_entries.js
+++ b/extensions/permissions/test/unit/test_permmanager_load_invalid_entries.js
@@ -32,86 +32,91 @@ function run_test() {
       ",appId INTEGER" +
       ",isInBrowserElement INTEGER" +
     ")");
 
   // Now we can inject garbadge in the database.
   var garbadge = [
     // Regular entry.
     { host: '42', type: '0', permission: 1, expireType: 0, expireTime: 0,
-      isInBrowserElement: 0 },
+      appId: 0, isInBrowserElement: 0 },
 
     // Special values in host (some being invalid).
     { host: 'scheme:file', type: '1', permission: 0, expireType: 0,
-      expireTime: 0, isInBrowserElement: 0 },
+      expireTime: 0, appId: 0, isInBrowserElement: 0 },
     { host: '192.168.0.1', type: '2', permission: 0, expireType: 0,
-      expireTime: 0, isInBrowserElement: 0 },
+      expireTime: 0, appId: 0, isInBrowserElement: 0 },
     { host: '2001:0db8:0000:0000:0000:ff00:0042:8329', type: '3', permission: 0,
-      expireType: 0, expireTime: 0, isInBrowserElement: 0 },
+      expireType: 0, expireTime: 0, appId: 0, isInBrowserElement: 0 },
     { host: '::1', type: '4', permission: 0, expireType: 0, expireTime: 0,
-      isInBrowserElement: 0 },
+      appId: 0, isInBrowserElement: 0 },
 
     // Permission is UNKNOWN_ACTION.
     { host: '42', type: '5', permission: Ci.nsIPermissionManager.UNKNOWN_ACTION,
-      expireType: 0, expireTime: 0, isInBrowserElement: 0 },
+      expireType: 0, expireTime: 0, appId: 0, isInBrowserElement: 0 },
 
     // Permission is out of range.
     { host: '42', type: '6', permission: 100, expireType: 0, expireTime: 0,
-      isInBrowserElement: 0 },
+      appId: 0, isInBrowserElement: 0 },
     { host: '42', type: '7', permission: -100, expireType: 0, expireTime: 0,
-      isInBrowserElement: 0 },
+      appId: 0, isInBrowserElement: 0 },
 
     // ExpireType is out of range.
     { host: '42', type: '8', permission: 1, expireType: -100, expireTime: 0,
-      isInBrowserElement: 0 },
+      appId: 0, isInBrowserElement: 0 },
     { host: '42', type: '9', permission: 1, expireType: 100, expireTime: 0,
-      isInBrowserElement: 0 },
+      appId: 0, isInBrowserElement: 0 },
 
     // ExpireTime is at 0 with ExpireType = Time.
     { host: '42', type: '10', permission: 1,
-      expireType: Ci.nsIPermissionManager.EXPIRE_TIME, expireTime: 0,
+      expireType: Ci.nsIPermissionManager.EXPIRE_TIME, expireTime: 0, appId: 0,
       isInBrowserElement: 0 },
 
     // ExpireTime has a value with ExpireType != Time
     { host: '42', type: '11', permission: 1,
       expireType: Ci.nsIPermissionManager.EXPIRE_SESSION, expireTime: 1000,
-      isInBrowserElement: 0 },
+      appId: 0, isInBrowserElement: 0 },
     { host: '42', type: '12', permission: 1,
       expireType: Ci.nsIPermissionManager.EXPIRE_NEVER, expireTime: 1000,
-      isInBrowserElement: 0 },
+      appId: 0, isInBrowserElement: 0 },
 
     // ExpireTime is negative.
     { host: '42', type: '13', permission: 1,
       expireType: Ci.nsIPermissionManager.EXPIRE_TIME, expireTime: -1,
-      isInBrowserElement: 0 },
+      appId: 0, isInBrowserElement: 0 },
+
+    // AppId is negative.
+    { host: '42', type: '14', permission: 1, expireType: 0, expireTime: 0,
+      appId: -1, isInBrowserElement: 0 },
 
     // IsInBrowserElement is negative or higher than 1.
     { host: '42', type: '15', permission: 1, expireType: 0, expireTime: 0,
-      isInBrowserElement: -1 },
+      appId: 0, isInBrowserElement: -1 },
     { host: '42', type: '16', permission: 1, expireType: 0, expireTime: 0,
-      isInBrowserElement: 10 },
+      appId: 0, isInBrowserElement: 10 },
 
     // This insertion should be the last one. It is used to make sure we always
     // load it regardless of the previous entries validities.
     { host: 'example.org', type: 'test-load-invalid-entries',
       permission: Ci.nsIPermissionManager.ALLOW_ACTION, expireType: 0,
-      expireTime: 0, isInBrowserElement: 0 },
+      expireTime: 0, appId: 0, isInBrowserElement: 0 },
   ];
 
   for (var i=0; i<garbadge.length; ++i) {
     if (DEBUG_TEST) {
       dump("\n value #" + i + "\n\n");
     }
     var data = garbadge[i];
     connection.executeSimpleSQL(
       "INSERT INTO moz_hosts " +
-      " (id, host, type, permission, expireType, expireTime, isInBrowserElement, appId) " +
+      " (id, host, type, permission, expireType, expireTime, appId, isInBrowserElement) " +
       "VALUES (" + i + ", '" + data.host + "', '" + data.type + "', "
                  + data.permission + ", " + data.expireType + ", "
-                 + data.expireTime + ", " + data.isInBrowserElement + ", 0)"
+                 + data.expireTime + ", " + data.appId + ", "
+                 + data.isInBrowserElement + ")"
     );
   }
 
   // This will force the permission-manager to reload the data.
   Services.obs.notifyObservers(null, "testonly-reload-permissions-from-disk", "");
 
   let earliestNow = Number(Date.now());
   // Initialize the permission manager service
--- a/extensions/permissions/test/unit/test_permmanager_matches.js
+++ b/extensions/permissions/test/unit/test_permmanager_matches.js
@@ -33,30 +33,54 @@ function run_test() {
   // Add some permissions
   let uri0 = NetUtil.newURI("http://google.com/search?q=foo#hashtag");
   let uri1 = NetUtil.newURI("http://hangouts.google.com/subdir");
   let uri2 = NetUtil.newURI("http://google.org/");
   let uri3 = NetUtil.newURI("https://google.com/some/random/subdirectory");
   let uri4 = NetUtil.newURI("https://hangouts.google.com/#!/hangout");
   let uri5 = NetUtil.newURI("http://google.com:8096/");
 
-  let uri0_n = secMan.createCodebasePrincipal(uri0, {});
-  let uri1_n = secMan.createCodebasePrincipal(uri1, {});
-  let uri2_n = secMan.createCodebasePrincipal(uri2, {});
-  let uri3_n = secMan.createCodebasePrincipal(uri3, {});
-  let uri4_n = secMan.createCodebasePrincipal(uri4, {});
-  let uri5_n = secMan.createCodebasePrincipal(uri5, {});
+  let uri0_n_n = secMan.createCodebasePrincipal(uri0, {});
+  let uri1_n_n = secMan.createCodebasePrincipal(uri1, {});
+  let uri2_n_n = secMan.createCodebasePrincipal(uri2, {});
+  let uri3_n_n = secMan.createCodebasePrincipal(uri3, {});
+  let uri4_n_n = secMan.createCodebasePrincipal(uri4, {});
+  let uri5_n_n = secMan.createCodebasePrincipal(uri5, {});
+
+  let attrs = {appId: 1000};
+  let uri0_1000_n = secMan.createCodebasePrincipal(uri0, attrs);
+  let uri1_1000_n = secMan.createCodebasePrincipal(uri1, attrs);
+  let uri2_1000_n = secMan.createCodebasePrincipal(uri2, attrs);
+  let uri3_1000_n = secMan.createCodebasePrincipal(uri3, attrs);
+  let uri4_1000_n = secMan.createCodebasePrincipal(uri4, attrs);
+  let uri5_1000_n = secMan.createCodebasePrincipal(uri5, attrs);
 
-  attrs = {inIsolatedMozBrowser: true};
-  let uri0_y_ = secMan.createCodebasePrincipal(uri0, attrs);
-  let uri1_y_ = secMan.createCodebasePrincipal(uri1, attrs);
-  let uri2_y_ = secMan.createCodebasePrincipal(uri2, attrs);
-  let uri3_y_ = secMan.createCodebasePrincipal(uri3, attrs);
-  let uri4_y_ = secMan.createCodebasePrincipal(uri4, attrs);
-  let uri5_y_ = secMan.createCodebasePrincipal(uri5, attrs);
+  attrs = {appId: 1000, inIsolatedMozBrowser: true};
+  let uri0_1000_y = secMan.createCodebasePrincipal(uri0, attrs);
+  let uri1_1000_y = secMan.createCodebasePrincipal(uri1, attrs);
+  let uri2_1000_y = secMan.createCodebasePrincipal(uri2, attrs);
+  let uri3_1000_y = secMan.createCodebasePrincipal(uri3, attrs);
+  let uri4_1000_y = secMan.createCodebasePrincipal(uri4, attrs);
+  let uri5_1000_y = secMan.createCodebasePrincipal(uri5, attrs);
+
+  attrs = {appId: 2000};
+  let uri0_2000_n = secMan.createCodebasePrincipal(uri0, attrs);
+  let uri1_2000_n = secMan.createCodebasePrincipal(uri1, attrs);
+  let uri2_2000_n = secMan.createCodebasePrincipal(uri2, attrs);
+  let uri3_2000_n = secMan.createCodebasePrincipal(uri3, attrs);
+  let uri4_2000_n = secMan.createCodebasePrincipal(uri4, attrs);
+  let uri5_2000_n = secMan.createCodebasePrincipal(uri5, attrs);
+
+  attrs = {appId: 2000, inIsolatedMozBrowser: true};
+  let uri0_2000_y = secMan.createCodebasePrincipal(uri0, attrs);
+  let uri1_2000_y = secMan.createCodebasePrincipal(uri1, attrs);
+  let uri2_2000_y = secMan.createCodebasePrincipal(uri2, attrs);
+  let uri3_2000_y = secMan.createCodebasePrincipal(uri3, attrs);
+  let uri4_2000_y = secMan.createCodebasePrincipal(uri4, attrs);
+  let uri5_2000_y = secMan.createCodebasePrincipal(uri5, attrs);
 
   attrs = {userContextId: 1};
   let uri0_1 = secMan.createCodebasePrincipal(uri0, attrs);
   let uri1_1 = secMan.createCodebasePrincipal(uri1, attrs);
   let uri2_1 = secMan.createCodebasePrincipal(uri2, attrs);
   let uri3_1 = secMan.createCodebasePrincipal(uri3, attrs);
   let uri4_1 = secMan.createCodebasePrincipal(uri4, attrs);
   let uri5_1 = secMan.createCodebasePrincipal(uri5, attrs);
@@ -64,48 +88,96 @@ function run_test() {
   attrs = {firstPartyDomain: "cnn.com"};
   let uri0_cnn = secMan.createCodebasePrincipal(uri0, attrs);
   let uri1_cnn = secMan.createCodebasePrincipal(uri1, attrs);
   let uri2_cnn = secMan.createCodebasePrincipal(uri2, attrs);
   let uri3_cnn = secMan.createCodebasePrincipal(uri3, attrs);
   let uri4_cnn = secMan.createCodebasePrincipal(uri4, attrs);
   let uri5_cnn = secMan.createCodebasePrincipal(uri5, attrs);
 
-  pm.addFromPrincipal(uri0_n, "test/matches", pm.ALLOW_ACTION);
-  let perm_n = pm.getPermissionObject(uri0_n, "test/matches", true);
-  pm.addFromPrincipal(uri0_y_, "test/matches", pm.ALLOW_ACTION);
-  let perm_y_ = pm.getPermissionObject(uri0_y_, "test/matches", true);
+  pm.addFromPrincipal(uri0_n_n, "test/matches", pm.ALLOW_ACTION);
+  let perm_n_n = pm.getPermissionObject(uri0_n_n, "test/matches", true);
+  pm.addFromPrincipal(uri0_1000_n, "test/matches", pm.ALLOW_ACTION);
+  let perm_1000_n = pm.getPermissionObject(uri0_1000_n, "test/matches", true);
+  pm.addFromPrincipal(uri0_1000_y, "test/matches", pm.ALLOW_ACTION);
+  let perm_1000_y = pm.getPermissionObject(uri0_1000_y, "test/matches", true);
+  pm.addFromPrincipal(uri0_2000_n, "test/matches", pm.ALLOW_ACTION);
+  let perm_2000_n = pm.getPermissionObject(uri0_2000_n, "test/matches", true);
+  pm.addFromPrincipal(uri0_2000_y, "test/matches", pm.ALLOW_ACTION);
+  let perm_2000_y = pm.getPermissionObject(uri0_2000_y, "test/matches", true);
   pm.addFromPrincipal(uri0_1, "test/matches", pm.ALLOW_ACTION);
-  let perm_1 = pm.getPermissionObject(uri0_n, "test/matches", true);
+  let perm_1 = pm.getPermissionObject(uri0_n_n, "test/matches", true);
   pm.addFromPrincipal(uri0_cnn, "test/matches", pm.ALLOW_ACTION);
-  let perm_cnn = pm.getPermissionObject(uri0_n, "test/matches", true);
+  let perm_cnn = pm.getPermissionObject(uri0_n_n, "test/matches", true);
 
-  matches_always(perm_n, [uri0_n, uri0_1, uri0_cnn]);
-  matches_weak(perm_n, [uri1_n, uri1_1, uri1_cnn]);
-  matches_never(perm_n, [uri2_n, uri3_n, uri4_n, uri5_n,
-                           uri0_y_, uri1_y_, uri2_y_, uri3_y_, uri4_y_, uri5_y_,
+  matches_always(perm_n_n, [uri0_n_n, uri0_1, uri0_cnn]);
+  matches_weak(perm_n_n, [uri1_n_n, uri1_1, uri1_cnn]);
+  matches_never(perm_n_n, [uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+                           uri0_1000_n, uri1_1000_n, uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+                           uri0_1000_y, uri1_1000_y, uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+                           uri0_2000_n, uri1_2000_n, uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+                           uri0_2000_y, uri1_2000_y, uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
                            uri2_1, uri3_1, uri4_1, uri5_1,
                            uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
 
-  matches_always(perm_y_, [uri0_y_]);
-  matches_weak(perm_y_, [uri1_y_]);
-  matches_never(perm_y_, [uri2_y_, uri3_y_, uri4_y_, uri5_y_,
-                              uri0_n, uri1_n, uri2_n, uri3_n, uri4_n, uri5_n,
+  matches_always(perm_1000_n, [uri0_1000_n]);
+  matches_weak(perm_1000_n, [uri1_1000_n]);
+  matches_never(perm_1000_n, [uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+                              uri0_n_n, uri1_n_n, uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+                              uri0_1000_y, uri1_1000_y, uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+                              uri0_2000_n, uri1_2000_n, uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+                              uri0_2000_y, uri1_2000_y, uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
+                              uri0_1, uri1_1, uri2_1, uri3_1, uri4_1, uri5_1,
+                              uri0_cnn, uri1_cnn, uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
+
+  matches_always(perm_1000_y, [uri0_1000_y]);
+  matches_weak(perm_1000_y, [uri1_1000_y]);
+  matches_never(perm_1000_y, [uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+                              uri0_n_n, uri1_n_n, uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+                              uri0_1000_n, uri1_1000_n, uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+                              uri0_2000_n, uri1_2000_n, uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+                              uri0_2000_y, uri1_2000_y, uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
                               uri0_1, uri1_1, uri2_1, uri3_1, uri4_1, uri5_1,
                               uri0_cnn, uri1_cnn, uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
 
-  matches_always(perm_1, [uri0_n, uri0_1, uri0_cnn]);
-  matches_weak(perm_1, [uri1_n, uri1_1, uri1_cnn]);
-  matches_never(perm_1, [uri2_n, uri3_n, uri4_n, uri5_n,
-                         uri0_y_, uri1_y_, uri2_y_, uri3_y_, uri4_y_, uri5_y_,
+  matches_always(perm_2000_n, [uri0_2000_n]);
+  matches_weak(perm_2000_n, [uri1_2000_n]);
+  matches_never(perm_2000_n, [uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+                              uri0_n_n, uri1_n_n, uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+                              uri0_2000_y, uri1_2000_y, uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
+                              uri0_1000_n, uri1_1000_n, uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+                              uri0_1000_y, uri1_1000_y, uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+                              uri0_1, uri1_1, uri2_1, uri3_1, uri4_1, uri5_1,
+                              uri0_cnn, uri1_cnn, uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
+
+  matches_always(perm_2000_y, [uri0_2000_y]);
+  matches_weak(perm_2000_y, [uri1_2000_y]);
+  matches_never(perm_2000_y, [uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
+                              uri0_n_n, uri1_n_n, uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+                              uri0_2000_n, uri1_2000_n, uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+                              uri0_1000_n, uri1_1000_n, uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+                              uri0_1000_y, uri1_1000_y, uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+                              uri0_1, uri1_1, uri2_1, uri3_1, uri4_1, uri5_1,
+                              uri0_cnn, uri1_cnn, uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
+
+  matches_always(perm_1, [uri0_n_n, uri0_1, uri0_cnn]);
+  matches_weak(perm_1, [uri1_n_n, uri1_1, uri1_cnn]);
+  matches_never(perm_1, [uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+                         uri0_1000_n, uri1_1000_n, uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+                         uri0_1000_y, uri1_1000_y, uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+                         uri0_2000_n, uri1_2000_n, uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+                         uri0_2000_y, uri1_2000_y, uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
                          uri2_1, uri3_1, uri4_1, uri5_1,
                          uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
 
-  matches_always(perm_cnn, [uri0_n, uri0_1, uri0_cnn]);
-  matches_weak(perm_cnn, [uri1_n, uri1_1, uri1_cnn]);
-  matches_never(perm_cnn, [uri2_n, uri3_n, uri4_n, uri5_n,
-                           uri0_y_, uri1_y_, uri2_y_, uri3_y_, uri4_y_, uri5_y_,
+  matches_always(perm_cnn, [uri0_n_n, uri0_1, uri0_cnn]);
+  matches_weak(perm_cnn, [uri1_n_n, uri1_1, uri1_cnn]);
+  matches_never(perm_cnn, [uri2_n_n, uri3_n_n, uri4_n_n, uri5_n_n,
+                           uri0_1000_n, uri1_1000_n, uri2_1000_n, uri3_1000_n, uri4_1000_n, uri5_1000_n,
+                           uri0_1000_y, uri1_1000_y, uri2_1000_y, uri3_1000_y, uri4_1000_y, uri5_1000_y,
+                           uri0_2000_n, uri1_2000_n, uri2_2000_n, uri3_2000_n, uri4_2000_n, uri5_2000_n,
+                           uri0_2000_y, uri1_2000_y, uri2_2000_y, uri3_2000_y, uri4_2000_y, uri5_2000_y,
                            uri2_1, uri3_1, uri4_1, uri5_1,
                            uri2_cnn, uri3_cnn, uri4_cnn, uri5_cnn]);
 
   // Clean up!
   pm.removeAll();
 }
--- a/extensions/permissions/test/unit/test_permmanager_matchesuri.js
+++ b/extensions/permissions/test/unit/test_permmanager_matchesuri.js
@@ -17,25 +17,27 @@ function matches_weak(perm, uris) {
 
 function matches_never(perm, uris) {
   uris.forEach((uri) => {
     Assert.ok(!perm.matchesURI(uri, true), "perm: " + perm.principal.origin + ", URI: " + uri.spec);
     Assert.ok(!perm.matchesURI(uri, false), "perm: " + perm.principal.origin + ", URI: " + uri.spec);
   });
 }
 
-function mk_permission(uri) {
+function mk_permission(uri, isAppPermission = false) {
   let pm = Cc["@mozilla.org/permissionmanager;1"].
         getService(Ci.nsIPermissionManager);
 
   let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
         .getService(Ci.nsIScriptSecurityManager);
 
   // Get the permission from the principal!
-  let principal = secMan.createCodebasePrincipal(uri, {});
+  let attrs = {appId: 1000};
+  let principal =
+    secMan.createCodebasePrincipal(uri, isAppPermission ? attrs : {});
 
   pm.addFromPrincipal(principal, "test/matchesuri", pm.ALLOW_ACTION);
   let permission = pm.getPermissionObject(principal, "test/matchesuri", true);
 
   return permission;
 }
 
 function run_test() {
--- a/extensions/permissions/test/unit/test_permmanager_migrate_4-7.js
+++ b/extensions/permissions/test/unit/test_permmanager_migrate_4-7.js
@@ -104,42 +104,47 @@ add_task(async function test() {
   db.close();
   stmtInsert = null;
   db = null;
 
   let expected = [
     // The http:// entries under foo.com won't be inserted, as there are history entries for foo.com,
     // and http://foo.com or a subdomain are never visited.
     // ["http://foo.com", "A", 1, 0, 0],
-    // ["http://foo.com^inBrowser=1", "A", 1, 0, 0],
+    // ["http://foo.com^appId=1000", "A", 1, 0, 0],
+    // ["http://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
     //
     // Because we search for port/scheme combinations under eTLD+1, we should not have http:// entries
     // for subdomains of foo.com either
     // ["http://sub.foo.com", "B", 1, 0, 0],
     // ["http://subber.sub.foo.com", "B", 1, 0, 0],
 
     ["https://foo.com", "A", 1, 0, 0],
     ["https://foo.com", "C", 1, 0, 0],
-    ["https://foo.com^inBrowser=1", "A", 1, 0, 0],
+    ["https://foo.com^appId=1000", "A", 1, 0, 0],
+    ["https://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
     ["https://sub.foo.com", "B", 1, 0, 0],
     ["https://subber.sub.foo.com", "B", 1, 0, 0],
 
     // bar.ca will have both http:// and https:// for all entries, because there are no associated history entries
     ["http://bar.ca", "B", 1, 0, 0],
     ["https://bar.ca", "B", 1, 0, 0],
-    ["http://bar.ca^inBrowser=1", "A", 1, 0, 0],
-    ["https://bar.ca^inBrowser=1", "A", 1, 0, 0],
+    ["http://bar.ca^appId=1000", "B", 1, 0, 0],
+    ["https://bar.ca^appId=1000", "B", 1, 0, 0],
+    ["http://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
+    ["https://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
     ["file:///some/path/to/file.html", "A", 1, 0, 0],
     ["file:///another/file.html", "A", 1, 0, 0],
 
     // Because we put ftp://some.subdomain.of.foo.com:8000/some/subdirectory in the history, we should
     // also have these entries
     ["ftp://foo.com:8000", "A", 1, 0, 0],
     ["ftp://foo.com:8000", "C", 1, 0, 0],
-    ["ftp://foo.com:8000^inBrowser=1", "A", 1, 0, 0],
+    ["ftp://foo.com:8000^appId=1000", "A", 1, 0, 0],
+    ["ftp://foo.com:8000^appId=2000&inBrowser=1", "A", 1, 0, 0],
 
     // In addition, because we search for port/scheme combinations under eTLD+1, we should have the
     // following entries
     ["ftp://sub.foo.com:8000", "B", 1, 0, 0],
     ["ftp://subber.sub.foo.com:8000", "B", 1, 0, 0],
 
     // Make sure that we also support localhost, and IP addresses
     ["http://localhost", "A", 1, 0, 0],
--- a/extensions/permissions/test/unit/test_permmanager_migrate_4-7_no_history.js
+++ b/extensions/permissions/test/unit/test_permmanager_migrate_4-7_no_history.js
@@ -139,31 +139,35 @@ add_task(function test() {
   stmtInsert.finalize();
   db.close();
   stmtInsert = null;
   db = null;
 
   let expected = [
     ["http://foo.com", "A", 1, 0, 0],
     ["http://foo.com", "C", 1, 0, 0],
-    ["http://foo.com^inBrowser=1", "A", 1, 0, 0],
+    ["http://foo.com^appId=1000", "A", 1, 0, 0],
+    ["http://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
     ["http://sub.foo.com", "B", 1, 0, 0],
     ["http://subber.sub.foo.com", "B", 1, 0, 0],
 
     ["https://foo.com", "A", 1, 0, 0],
     ["https://foo.com", "C", 1, 0, 0],
-    ["https://foo.com^inBrowser=1", "A", 1, 0, 0],
+    ["https://foo.com^appId=1000", "A", 1, 0, 0],
+    ["https://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
     ["https://sub.foo.com", "B", 1, 0, 0],
     ["https://subber.sub.foo.com", "B", 1, 0, 0],
 
     // bar.ca will have both http:// and https:// for all entries, because there are no associated history entries
     ["http://bar.ca", "B", 1, 0, 0],
     ["https://bar.ca", "B", 1, 0, 0],
-    ["http://bar.ca^inBrowser=1", "A", 1, 0, 0],
-    ["https://bar.ca^inBrowser=1", "A", 1, 0, 0],
+    ["http://bar.ca^appId=1000", "B", 1, 0, 0],
+    ["https://bar.ca^appId=1000", "B", 1, 0, 0],
+    ["http://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
+    ["https://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
     ["file:///some/path/to/file.html", "A", 1, 0, 0],
     ["file:///another/file.html", "A", 1, 0, 0],
 
     // Make sure that we also support localhost, and IP addresses
     ["http://localhost", "A", 1, 0, 0],
     ["https://localhost", "A", 1, 0, 0],
     ["http://127.0.0.1", "A", 1, 0, 0],
     ["https://127.0.0.1", "A", 1, 0, 0],
--- a/extensions/permissions/test/unit/test_permmanager_migrate_5-7a.js
+++ b/extensions/permissions/test/unit/test_permmanager_migrate_5-7a.js
@@ -127,17 +127,17 @@ add_task(async function test() {
       appId: appId,
       isInBrowserElement: isInBrowserElement
     };
   }
 
   let created5 = [
     insertOrigin("https://foo.com", "A", 2, 0, 0, 0),
     insertOrigin("http://foo.com", "A", 2, 0, 0, 0),
-    insertOrigin("http://foo.com^inBrowser=1", "A", 2, 0, 0, 0),
+    insertOrigin("http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0),
   ];
 
   // Add some rows to the database
   let created = [
     insertHost("foo.com", "A", 1, 0, 0, 0, 0, false),
     insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
     insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
     insertHost("foo.com", "A", 1, 0, 0, 0, 2000, true),
@@ -163,42 +163,47 @@ add_task(async function test() {
   db.close();
   stmtInsert = null;
   db = null;
 
   let expected = [
     // The http:// entries under foo.com won't be inserted, as there are history entries for foo.com,
     // and http://foo.com or a subdomain are never visited.
     // ["http://foo.com", "A", 1, 0, 0],
-    // ["http://foo.com^inBrowser=1", "A", 1, 0, 0],
+    // ["http://foo.com^appId=1000", "A", 1, 0, 0],
+    // ["http://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
     //
     // Because we search for port/scheme combinations under eTLD+1, we should not have http:// entries
     // for subdomains of foo.com either
     // ["http://sub.foo.com", "B", 1, 0, 0],
     // ["http://subber.sub.foo.com", "B", 1, 0, 0],
 
     ["https://foo.com", "A", 1, 0, 0],
     ["https://foo.com", "C", 1, 0, 0],
-    ["https://foo.com^inBrowser=1", "A", 1, 0, 0],
+    ["https://foo.com^appId=1000", "A", 1, 0, 0],
+    ["https://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
     ["https://sub.foo.com", "B", 1, 0, 0],
     ["https://subber.sub.foo.com", "B", 1, 0, 0],
 
     // bar.ca will have both http:// and https:// for all entries, because there are no associated history entries
     ["http://bar.ca", "B", 1, 0, 0],
     ["https://bar.ca", "B", 1, 0, 0],
-    ["http://bar.ca^inBrowser=1", "A", 1, 0, 0],
-    ["https://bar.ca^inBrowser=1", "A", 1, 0, 0],
+    ["http://bar.ca^appId=1000", "B", 1, 0, 0],
+    ["https://bar.ca^appId=1000", "B", 1, 0, 0],
+    ["http://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
+    ["https://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
     ["file:///some/path/to/file.html", "A", 1, 0, 0],
     ["file:///another/file.html", "A", 1, 0, 0],
 
     // Because we put ftp://some.subdomain.of.foo.com:8000/some/subdirectory in the history, we should
     // also have these entries
     ["ftp://foo.com:8000", "A", 1, 0, 0],
     ["ftp://foo.com:8000", "C", 1, 0, 0],
-    ["ftp://foo.com:8000^inBrowser=1", "A", 1, 0, 0],
+    ["ftp://foo.com:8000^appId=1000", "A", 1, 0, 0],
+    ["ftp://foo.com:8000^appId=2000&inBrowser=1", "A", 1, 0, 0],
 
     // In addition, because we search for port/scheme combinations under eTLD+1, we should have the
     // following entries
     ["ftp://sub.foo.com:8000", "B", 1, 0, 0],
     ["ftp://subber.sub.foo.com:8000", "B", 1, 0, 0],
 
     // Make sure that we also support localhost, and IP addresses
     ["http://localhost", "A", 1, 0, 0],
--- a/extensions/permissions/test/unit/test_permmanager_migrate_5-7b.js
+++ b/extensions/permissions/test/unit/test_permmanager_migrate_5-7b.js
@@ -89,17 +89,17 @@ add_task(function test() {
   stmt5Insert.finalize();
   db.close();
   stmt5Insert = null;
   db = null;
 
   let expected = [
     ["https://foo.com", "A", 2, 0, 0, 0],
     ["http://foo.com", "A", 2, 0, 0, 0],
-    ["http://foo.com^inBrowser=1", "A", 2, 0, 0, 0],
+    ["http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0],
 
     ["http://127.0.0.1", "B", 2, 0, 0, 0],
     ["http://localhost", "B", 2, 0, 0, 0],
   ];
 
   let found = expected.map((it) => 0);
 
   // This will force the permission-manager to reload the data.
@@ -139,31 +139,32 @@ add_task(function test() {
     let db = Services.storage.openDatabase(GetPermissionsFile(profile));
     Assert.ok(db.tableExists("moz_perms"));
     Assert.ok(db.tableExists("moz_hosts"));
     Assert.ok(!db.tableExists("moz_hosts_is_backup"));
     Assert.ok(!db.tableExists("moz_perms_v6"));
 
     let mozHostsStmt = db.createStatement("SELECT " +
                                           "host, type, permission, expireType, expireTime, " +
-                                          "modificationTime, isInBrowserElement " +
+                                          "modificationTime, appId, isInBrowserElement " +
                                           "FROM moz_hosts WHERE id = :id");
     try {
       // Check that the moz_hosts table still contains the correct values.
       created4.forEach((it) => {
         mozHostsStmt.reset();
         mozHostsStmt.bindByName("id", it.id);
         mozHostsStmt.executeStep();
         Assert.equal(mozHostsStmt.getUTF8String(0), it.host);
         Assert.equal(mozHostsStmt.getUTF8String(1), it.type);
         Assert.equal(mozHostsStmt.getInt64(2), it.permission);
         Assert.equal(mozHostsStmt.getInt64(3), it.expireType);
         Assert.equal(mozHostsStmt.getInt64(4), it.expireTime);
         Assert.equal(mozHostsStmt.getInt64(5), it.modificationTime);
-        Assert.equal(mozHostsStmt.getInt64(6), it.isInBrowserElement);
+        Assert.equal(mozHostsStmt.getInt64(6), it.appId);
+        Assert.equal(mozHostsStmt.getInt64(7), it.isInBrowserElement);
       });
     } finally {
       mozHostsStmt.finalize();
     }
 
     // Check that there are the right number of values
     let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
     try {
--- a/extensions/permissions/test/unit/test_permmanager_migrate_6-7a.js
+++ b/extensions/permissions/test/unit/test_permmanager_migrate_6-7a.js
@@ -127,17 +127,17 @@ add_task(async function test() {
       appId: appId,
       isInBrowserElement: isInBrowserElement
     };
   }
 
   let created6 = [
     insertOrigin("https://foo.com", "A", 2, 0, 0, 0),
     insertOrigin("http://foo.com", "A", 2, 0, 0, 0),
-    insertOrigin("http://foo.com^inBrowser=1", "A", 2, 0, 0, 0),
+    insertOrigin("http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0),
   ];
 
   // Add some rows to the database
   let created = [
     insertHost("foo.com", "A", 1, 0, 0, 0, 0, false),
     insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
     insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
     insertHost("foo.com", "A", 1, 0, 0, 0, 2000, true),
@@ -163,42 +163,47 @@ add_task(async function test() {
   db.close();
   stmtInsert = null;
   db = null;
 
   let expected = [
     // The http:// entries under foo.com won't be inserted, as there are history entries for foo.com,
     // and http://foo.com or a subdomain are never visited.
     // ["http://foo.com", "A", 1, 0, 0],
-    // ["http://foo.com^inBrowser=1", "A", 1, 0, 0],
+    // ["http://foo.com^appId=1000", "A", 1, 0, 0],
+    // ["http://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
     //
     // Because we search for port/scheme combinations under eTLD+1, we should not have http:// entries
     // for subdomains of foo.com either
     // ["http://sub.foo.com", "B", 1, 0, 0],
     // ["http://subber.sub.foo.com", "B", 1, 0, 0],
 
     ["https://foo.com", "A", 1, 0, 0],
     ["https://foo.com", "C", 1, 0, 0],
-    ["https://foo.com^inBrowser=1", "A", 1, 0, 0],
+    ["https://foo.com^appId=1000", "A", 1, 0, 0],
+    ["https://foo.com^appId=2000&inBrowser=1", "A", 1, 0, 0],
     ["https://sub.foo.com", "B", 1, 0, 0],
     ["https://subber.sub.foo.com", "B", 1, 0, 0],
 
     // bar.ca will have both http:// and https:// for all entries, because there are no associated history entries
     ["http://bar.ca", "B", 1, 0, 0],
     ["https://bar.ca", "B", 1, 0, 0],
-    ["http://bar.ca^inBrowser=1", "A", 1, 0, 0],
-    ["https://bar.ca^inBrowser=1", "A", 1, 0, 0],
+    ["http://bar.ca^appId=1000", "B", 1, 0, 0],
+    ["https://bar.ca^appId=1000", "B", 1, 0, 0],
+    ["http://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
+    ["https://bar.ca^appId=1000&inBrowser=1", "A", 1, 0, 0],
     ["file:///some/path/to/file.html", "A", 1, 0, 0],
     ["file:///another/file.html", "A", 1, 0, 0],
 
     // Because we put ftp://some.subdomain.of.foo.com:8000/some/subdirectory in the history, we should
     // also have these entries
     ["ftp://foo.com:8000", "A", 1, 0, 0],
     ["ftp://foo.com:8000", "C", 1, 0, 0],
-    ["ftp://foo.com:8000^inBrowser=1", "A", 1, 0, 0],
+    ["ftp://foo.com:8000^appId=1000", "A", 1, 0, 0],
+    ["ftp://foo.com:8000^appId=2000&inBrowser=1", "A", 1, 0, 0],
 
     // In addition, because we search for port/scheme combinations under eTLD+1, we should have the
     // following entries
     ["ftp://sub.foo.com:8000", "B", 1, 0, 0],
     ["ftp://subber.sub.foo.com:8000", "B", 1, 0, 0],
 
     // Make sure that we also support localhost, and IP addresses
     ["http://localhost", "A", 1, 0, 0],
--- a/extensions/permissions/test/unit/test_permmanager_migrate_6-7b.js
+++ b/extensions/permissions/test/unit/test_permmanager_migrate_6-7b.js
@@ -86,17 +86,17 @@ add_task(function test() {
   stmt6Insert.finalize();
   db.close();
   stmt6Insert = null;
   db = null;
 
   let expected = [
     ["https://foo.com", "A", 2, 0, 0, 0],
     ["http://foo.com", "A", 2, 0, 0, 0],
-    ["http://foo.com^inBrowser=1", "A", 2, 0, 0, 0]
+    ["http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0]
   ];
 
   let found = expected.map((it) => 0);
 
   // This will force the permission-manager to reload the data.
   Services.obs.notifyObservers(null, "testonly-reload-permissions-from-disk", "");
 
   // Force initialization of the nsPermissionManager
@@ -133,31 +133,32 @@ add_task(function test() {
     let db = Services.storage.openDatabase(GetPermissionsFile(profile));
     Assert.ok(db.tableExists("moz_perms"));
     Assert.ok(db.tableExists("moz_hosts"));
     Assert.ok(!db.tableExists("moz_hosts_is_backup"));
     Assert.ok(!db.tableExists("moz_perms_v6"));
 
     let mozHostsStmt = db.createStatement("SELECT " +
                                           "host, type, permission, expireType, expireTime, " +
-                                          "modificationTime, isInBrowserElement " +
+                                          "modificationTime, appId, isInBrowserElement " +
                                           "FROM moz_hosts WHERE id = :id");
     try {
       // Check that the moz_hosts table still contains the correct values.
       created4.forEach((it) => {
         mozHostsStmt.reset();
         mozHostsStmt.bindByName("id", it.id);
         mozHostsStmt.executeStep();
         Assert.equal(mozHostsStmt.getUTF8String(0), it.host);
         Assert.equal(mozHostsStmt.getUTF8String(1), it.type);
         Assert.equal(mozHostsStmt.getInt64(2), it.permission);
         Assert.equal(mozHostsStmt.getInt64(3), it.expireType);
         Assert.equal(mozHostsStmt.getInt64(4), it.expireTime);
         Assert.equal(mozHostsStmt.getInt64(5), it.modificationTime);
-        Assert.equal(mozHostsStmt.getInt64(6), it.isInBrowserElement);
+        Assert.equal(mozHostsStmt.getInt64(6), it.appId);
+        Assert.equal(mozHostsStmt.getInt64(7), it.isInBrowserElement);
       });
     } finally {
       mozHostsStmt.finalize();
     }
 
     // Check that there are the right number of values
     let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
     try {
--- a/extensions/permissions/test/unit/test_permmanager_migrate_7-8.js
+++ b/extensions/permissions/test/unit/test_permmanager_migrate_7-8.js
@@ -132,17 +132,17 @@ add_task(async function test() {
       appId: appId,
       isInBrowserElement: isInBrowserElement
     };
   }
 
   let created7 = [
     insertOrigin("https://foo.com", "A", 2, 0, 0, 0),
     insertOrigin("http://foo.com", "A", 2, 0, 0, 0),
-    insertOrigin("http://foo.com^inBrowser=1", "A", 2, 0, 0, 0),
+    insertOrigin("http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0),
     insertOrigin("https://192.0.2.235", "A", 2, 0, 0),
   ];
 
   // Add some rows to the database
   let created = [
     insertHost("foo.com", "A", 1, 0, 0, 0, 0, false),
     insertHost("foo.com", "C", 1, 0, 0, 0, 0, false),
     insertHost("foo.com", "A", 1, 0, 0, 0, 1000, false),
@@ -173,17 +173,17 @@ add_task(async function test() {
   db.close();
   stmtInsert = null;
   db = null;
 
   let expected = [
     // We should have kept the previously migrated entries
     ["https://foo.com", "A", 2, 0, 0, 0],
     ["http://foo.com", "A", 2, 0, 0, 0],
-    ["http://foo.com^inBrowser=1", "A", 2, 0, 0, 0],
+    ["http://foo.com^appId=1000&inBrowser=1", "A", 2, 0, 0, 0],
 
     // Make sure that we also support localhost, and IP addresses
     ["https://localhost:8080", "A", 1, 0, 0],
     ["ftp://127.0.0.1:8080", "A", 1, 0, 0],
 
     ["http://[2001:db8::ff00:42:8329]", "C", 1, 0, 0],
     ["https://[2001:db8::ff00:42:8329]", "C", 1, 0, 0],
     ["http://192.0.2.235", "A", 1, 0, 0],
deleted file mode 100644
--- a/extensions/permissions/test/unit/test_permmanager_migrate_9-10.js
+++ /dev/null
@@ -1,217 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-ChromeUtils.defineModuleGetter(this, "PlacesTestUtils",
-                               "resource://testing-common/PlacesTestUtils.jsm");
-
-var PERMISSIONS_FILE_NAME = "permissions.sqlite";
-
-function GetPermissionsFile(profile)
-{
-  let file = profile.clone();
-  file.append(PERMISSIONS_FILE_NAME);
-  return file;
-}
-
-add_task(async function test() {
-  /* Create and set up the permissions database */
-  let profile = do_get_profile();
-  Services.prefs.setCharPref("permissions.manager.defaultsUrl", "");
-
-  let db = Services.storage.openDatabase(GetPermissionsFile(profile));
-  db.schemaVersion = 9;
-  db.executeSimpleSQL("DROP TABLE moz_perms");
-  db.executeSimpleSQL("DROP TABLE moz_hosts");
-
-  db.executeSimpleSQL(
-    "CREATE TABLE moz_perms (" +
-      " id INTEGER PRIMARY KEY" +
-      ",origin TEXT" +
-      ",type TEXT" +
-      ",permission INTEGER" +
-      ",expireType INTEGER" +
-      ",expireTime INTEGER" +
-      ",modificationTime INTEGER" +
-    ")");
-
-  let stmt6Insert = db.createStatement(
-    "INSERT INTO moz_perms (" +
-      "id, origin, type, permission, expireType, expireTime, modificationTime" +
-    ") VALUES (" +
-      ":id, :origin, :type, :permission, :expireType, :expireTime, :modificationTime" +
-    ")");
-
-  db.executeSimpleSQL(
-    "CREATE TABLE moz_hosts (" +
-      " id INTEGER PRIMARY KEY" +
-      ",host TEXT" +
-      ",type TEXT" +
-      ",permission INTEGER" +
-      ",expireType INTEGER" +
-      ",expireTime INTEGER" +
-      ",modificationTime INTEGER" +
-      ",appId INTEGER" +
-      ",isInBrowserElement INTEGER" +
-    ")");
-
-  let stmtInsert = db.createStatement(
-    "INSERT INTO moz_hosts (" +
-      "id, host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement" +
-    ") VALUES (" +
-      ":id, :host, :type, :permission, :expireType, :expireTime, :modificationTime, :appId, :isInBrowserElement" +
-    ")");
-
-  let id = 0;
-
-  function insertOrigin(origin, type, permission, expireType, expireTime, modificationTime) {
-    let thisId = id++;
-
-    stmt6Insert.bindByName("id", thisId);
-    stmt6Insert.bindByName("origin", origin);
-    stmt6Insert.bindByName("type", type);
-    stmt6Insert.bindByName("permission", permission);
-    stmt6Insert.bindByName("expireType", expireType);
-    stmt6Insert.bindByName("expireTime", expireTime);
-    stmt6Insert.bindByName("modificationTime", modificationTime);
-
-    try {
-      stmt6Insert.execute();
-    } finally {
-      stmt6Insert.reset();
-    }
-
-    return {
-      id: thisId,
-      origin: origin,
-      type: type,
-      permission: permission,
-      expireType: expireType,
-      expireTime: expireTime,
-      modificationTime: modificationTime
-    };
-  }
-
-  function insertHost(host, type, permission, expireType, expireTime, modificationTime, appId, isInBrowserElement) {
-    let thisId = id++;
-
-    stmtInsert.bindByName("id", thisId);
-    stmtInsert.bindByName("host", host);
-    stmtInsert.bindByName("type", type);
-    stmtInsert.bindByName("permission", permission);
-    stmtInsert.bindByName("expireType", expireType);
-    stmtInsert.bindByName("expireTime", expireTime);
-    stmtInsert.bindByName("modificationTime", modificationTime);
-    stmtInsert.bindByName("appId", appId);
-    stmtInsert.bindByName("isInBrowserElement", isInBrowserElement);
-
-    try {
-      stmtInsert.execute();
-    } finally {
-      stmtInsert.reset();
-    }
-
-    return {
-      id: thisId,
-      host: host,
-      type: type,
-      permission: permission,
-      expireType: expireType,
-      expireTime: expireTime,
-      modificationTime: modificationTime,
-      appId: appId,
-      isInBrowserElement: isInBrowserElement
-    };
-  }
-
-  let created7 = [
-    insertOrigin("https://foo.com", "A", 2, 0, 0, 0),
-    insertOrigin("http://foo.com", "A", 2, 0, 0, 0),
-    insertOrigin("http://foo.com^inBrowser=1", "A", 2, 0, 0, 0),
-  ];
-
-  // Add some rows to the database
-  let created = [
-    insertHost("foo.com", "A", 1, 0, 0, 0, 0, false),
-    insertHost("foo.com", "B", 1, 0, 0, 0, 1000, false),
-    insertHost("foo.com", "C", 1, 0, 0, 0, 2000, true),
-  ];
-
-  // CLose the db connection
-  stmt6Insert.finalize();
-  stmtInsert.finalize();
-  db.close();
-  stmtInsert = null;
-  db = null;
-
-  let expected = [
-    ["https://foo.com", "A", 2, 0, 0, 0],
-    ["http://foo.com", "A", 2, 0, 0, 0],
-    ["http://foo.com^inBrowser=1", "A", 2, 0, 0, 0],
-  ];
-
-  let found = expected.map((it) => 0);
-
-  // Add some places to the places database
-  await PlacesTestUtils.addVisits(Services.io.newURI("https://foo.com/some/other/subdirectory"));
-  await PlacesTestUtils.addVisits(Services.io.newURI("ftp://some.subdomain.of.foo.com:8000/some/subdirectory"));
-  await PlacesTestUtils.addVisits(Services.io.newURI("ftp://127.0.0.1:8080"));
-  await PlacesTestUtils.addVisits(Services.io.newURI("https://localhost:8080"));
-
-  // This will force the permission-manager to reload the data.
-  Services.obs.notifyObservers(null, "testonly-reload-permissions-from-disk", "");
-
-  // Force initialization of the nsPermissionManager
-  for (let permission of Services.perms.enumerator) {
-    let isExpected = false;
-
-    expected.forEach((it, i) => {
-      if (permission.principal.origin == it[0] &&
-          permission.type == it[1] &&
-          permission.capability == it[2] &&
-          permission.expireType == it[3] &&
-          permission.expireTime == it[4]) {
-        isExpected = true;
-        found[i]++;
-      }
-    });
-
-    Assert.ok(isExpected,
-              "Permission " + (isExpected ? "should" : "shouldn't") +
-              " be in permission database: " +
-              permission.principal.origin + ", " +
-              permission.type + ", " +
-              permission.capability + ", " +
-              permission.expireType + ", " +
-              permission.expireTime);
-  }
-
-  found.forEach((count, i) => {
-    Assert.ok(count == 1, "Expected count = 1, got count = " + count + " for permission " + expected[i]);
-  });
-
-  // Check to make sure that all of the tables which we care about are present
-  {
-    let db = Services.storage.openDatabase(GetPermissionsFile(profile));
-    Assert.ok(db.tableExists("moz_perms"));
-    Assert.ok(db.tableExists("moz_hosts"));
-    Assert.ok(!db.tableExists("moz_perms_v6"));
-
-    let mozHostsCount = db.createStatement("SELECT count(*) FROM moz_hosts");
-    try {
-      mozHostsCount.executeStep();
-      Assert.equal(mozHostsCount.getInt64(0), 1);
-    } finally {
-      mozHostsCount.finalize();
-    }
-
-    let mozPermsCount = db.createStatement("SELECT count(*) FROM moz_perms");
-    try {
-      mozPermsCount.executeStep();
-      Assert.equal(mozPermsCount.getInt64(0), expected.length);
-    } finally {
-      mozPermsCount.finalize();
-    }
-
-    db.close();
-  }
-});
new file mode 100644
--- /dev/null
+++ b/extensions/permissions/test/unit/test_permmanager_removeforapp.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+  // initialize the permission manager service
+  let ssm = Services.scriptSecurityManager;
+  let pm = Services.perms;
+
+  function mkPrin(uri, appId, inIsolatedMozBrowser) {
+    return ssm.createCodebasePrincipal(Services.io.newURI(uri),
+                                       {appId: appId, inIsolatedMozBrowser: inIsolatedMozBrowser});
+  }
+
+  function checkPerms(perms) {
+    perms.forEach((perm) => {
+      // Look up the expected permission
+      Assert.equal(pm.getPermissionObject(mkPrin(perm[0],  perm[1], perm[2]),
+                                          perm[3], true).capability,
+                   perm[4], "Permission is expected in the permission database");
+    });
+
+    // Count the entries
+    let count = 0;
+    let enumerator = Services.perms.enumerator;
+    while (enumerator.hasMoreElements()) { enumerator.getNext(); count++; }
+
+    Assert.equal(count, perms.length, "There should be the right number of permissions in the DB");
+  }
+
+  checkPerms([]);
+
+  let permissions = [
+    ['http://google.com',  1001, false, 'a', 1],
+    ['http://google.com',  1001, false, 'b', 1],
+    ['http://mozilla.com', 1001, false, 'b', 1],
+    ['http://mozilla.com', 1001, false, 'a', 1],
+
+    ['http://google.com',  1001, true, 'a', 1],
+    ['http://google.com',  1001, true, 'b', 1],
+    ['http://mozilla.com', 1001, true, 'b', 1],
+    ['http://mozilla.com', 1001, true, 'a', 1],
+
+    ['http://google.com',  1011, false, 'a', 1],
+    ['http://google.com',  1011, false, 'b', 1],
+    ['http://mozilla.com', 1011, false, 'b', 1],
+    ['http://mozilla.com', 1011, false, 'a', 1],
+  ];
+
+  permissions.forEach((perm) => {
+    pm.addFromPrincipal(mkPrin(perm[0], perm[1], perm[2]), perm[3], perm[4]);
+  });
+
+  checkPerms(permissions);
+
+  let remove_false_perms = [
+    ['http://google.com',  1011, false, 'a', 1],
+    ['http://google.com',  1011, false, 'b', 1],
+    ['http://mozilla.com', 1011, false, 'b', 1],
+    ['http://mozilla.com', 1011, false, 'a', 1],
+  ];
+
+  let attrs = { appId: 1001 };
+  pm.removePermissionsWithAttributes(JSON.stringify(attrs));
+  checkPerms(remove_false_perms);
+
+  let restore = [
+    ['http://google.com',  1001, false, 'a', 1],
+    ['http://google.com',  1001, false, 'b', 1],
+    ['http://mozilla.com', 1001, false, 'b', 1],
+    ['http://mozilla.com', 1001, false, 'a', 1],
+
+    ['http://google.com',  1001, true, 'a', 1],
+    ['http://google.com',  1001, true, 'b', 1],
+    ['http://mozilla.com', 1001, true, 'b', 1],
+    ['http://mozilla.com', 1001, true, 'a', 1],
+  ];
+
+  restore.forEach((perm) => {
+    pm.addFromPrincipal(mkPrin(perm[0],  perm[1], perm[2]), perm[3], perm[4]);
+  });
+  checkPerms(permissions);
+
+  let remove_true_perms = [
+    ['http://google.com',  1001, false, 'a', 1],
+    ['http://google.com',  1001, false, 'b', 1],
+    ['http://mozilla.com', 1001, false, 'b', 1],
+    ['http://mozilla.com', 1001, false, 'a', 1],
+
+    ['http://google.com',  1011, false, 'a', 1],
+    ['http://google.com',  1011, false, 'b', 1],
+    ['http://mozilla.com', 1011, false, 'b', 1],
+    ['http://mozilla.com', 1011, false, 'a', 1],
+  ];
+
+  attrs = { appId: 1001,
+            inIsolatedMozBrowser: true };
+  pm.removePermissionsWithAttributes(JSON.stringify(attrs));
+  checkPerms(remove_true_perms);
+}
--- a/extensions/permissions/test/unit/xpcshell.ini
+++ b/extensions/permissions/test/unit/xpcshell.ini
@@ -8,25 +8,25 @@ skip-if = toolkit == 'android'
 [test_permmanager_getAllForURI.js]
 [test_permmanager_getAllWithTypePrefix.js]
 [test_permmanager_getPermissionObject.js]
 [test_permmanager_notifications.js]
 [test_permmanager_removeall.js]
 [test_permmanager_removebytype.js]
 [test_permmanager_removebytypesince.js]
 [test_permmanager_removesince.js]
+[test_permmanager_removeforapp.js]
 [test_permmanager_load_invalid_entries.js]
 skip-if = debug == true
 [test_permmanager_idn.js]
 [test_permmanager_subdomains.js]
 [test_permmanager_local_files.js]
 [test_permmanager_cleardata.js]
 [test_permmanager_removepermission.js]
 [test_permmanager_matchesuri.js]
 [test_permmanager_matches.js]
 [test_permmanager_migrate_4-7.js]
 [test_permmanager_migrate_5-7a.js]
 [test_permmanager_migrate_5-7b.js]
 [test_permmanager_migrate_6-7a.js]
 [test_permmanager_migrate_6-7b.js]
 [test_permmanager_migrate_4-7_no_history.js]
 [test_permmanager_migrate_7-8.js]
-[test_permmanager_migrate_9-10.js]
--- a/ipc/glue/BackgroundUtils.cpp
+++ b/ipc/glue/BackgroundUtils.cpp
@@ -80,17 +80,21 @@ already_AddRefed<nsIPrincipal> Principal
           aPrincipalInfo.get_ContentPrincipalInfo();
 
       nsCOMPtr<nsIURI> uri;
       rv = NS_NewURI(getter_AddRefs(uri), info.spec());
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return nullptr;
       }
 
-      principal = BasePrincipal::CreateCodebasePrincipal(uri, info.attrs());
+      OriginAttributes attrs;
+      if (info.attrs().mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+        attrs = info.attrs();
+      }
+      principal = BasePrincipal::CreateCodebasePrincipal(uri, attrs);
       if (NS_WARN_IF(!principal)) {
         return nullptr;
       }
 
       // Origin must match what the_new_principal.getOrigin returns.
       nsAutoCString originNoSuffix;
       rv = principal->GetOriginNoSuffix(originNoSuffix);
       if (NS_WARN_IF(NS_FAILED(rv)) ||
--- a/ipc/glue/ProtocolUtils.h
+++ b/ipc/glue/ProtocolUtils.h
@@ -964,16 +964,19 @@ Protocol* LoneManagedOrNullAsserts(
     const ManagedContainer<Protocol>& aManagees) {
   if (aManagees.IsEmpty()) {
     return nullptr;
   }
   MOZ_ASSERT(aManagees.Count() == 1);
   return aManagees.ConstIter().Get()->GetKey();
 }
 
+// appId's are for B2G only currently, where managees.Count() == 1. This is
+// not guaranteed currently in Desktop, so for paths used for desktop,
+// don't assert there's one managee.
 template <typename Protocol>
 Protocol* SingleManagedOrNull(const ManagedContainer<Protocol>& aManagees) {
   if (aManagees.Count() != 1) {
     return nullptr;
   }
   return aManagees.ConstIter().Get()->GetKey();
 }
 
--- a/mobile/android/components/ContentPermissionPrompt.js
+++ b/mobile/android/components/ContentPermissionPrompt.js
@@ -19,28 +19,33 @@ const kEntities = {
 
 function ContentPermissionPrompt() {}
 
 ContentPermissionPrompt.prototype = {
   classID: Components.ID("{C6E8C44D-9F39-4AF7-BCC0-76E38A8310F5}"),
 
   QueryInterface: ChromeUtils.generateQI([Ci.nsIContentPermissionPrompt]),
 
-  handleExistingPermission: function handleExistingPermission(request, type, callback) {
+  handleExistingPermission: function handleExistingPermission(request, type, isApp, callback) {
     let result = Services.perms.testExactPermissionFromPrincipal(request.principal, type);
     if (result == Ci.nsIPermissionManager.ALLOW_ACTION) {
       callback(/* allow */ true);
       return true;
     }
 
     if (result == Ci.nsIPermissionManager.DENY_ACTION) {
       callback(/* allow */ false);
       return true;
     }
 
+    if (isApp && result == Ci.nsIPermissionManager.UNKNOWN_ACTION) {
+      callback(/* allow */ false);
+      return true;
+    }
+
     return false;
   },
 
   getChromeWindow: function getChromeWindow(aWindow) {
      let chromeWin = aWindow.docShell.rootTreeItem.domWindow
                             .QueryInterface(Ci.nsIDOMChromeWindow);
      return chromeWin;
   },
@@ -49,16 +54,18 @@ ContentPermissionPrompt.prototype = {
     if (request.window) {
       let requestingWindow = request.window.top;
       return this.getChromeWindow(requestingWindow).wrappedJSObject;
     }
     return request.element.ownerGlobal;
   },
 
   prompt: function(request) {
+    let isApp = request.principal.appId !== Ci.nsIScriptSecurityManager.NO_APP_ID && request.principal.appId !== Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID;
+
     // Only allow exactly one permission rquest here.
     let types = request.types.QueryInterface(Ci.nsIArray);
     if (types.length != 1) {
       request.cancel();
       return;
     }
 
     let perm = types.queryElementAt(0, Ci.nsIContentPermissionType);
@@ -74,17 +81,17 @@ ContentPermissionPrompt.prototype = {
             (granted ? request.allow : request.cancel)();
           });
         return;
       }
       request.allow();
     };
 
     // Returns true if the request was handled
-    if (this.handleExistingPermission(request, perm.type, callback)) {
+    if (this.handleExistingPermission(request, perm.type, isApp, callback)) {
        return;
     }
 
     if (perm.type === "desktop-notification" &&
         Services.prefs.getBoolPref("dom.webnotifications.requireuserinteraction", false) &&
         !request.isHandlingUserInput) {
       request.cancel();
       return;
@@ -104,16 +111,19 @@ ContentPermissionPrompt.prototype = {
       },
     },
     {
       label: browserBundle.GetStringFromName(entityName + ".allow"),
       callback: function(aChecked) {
         // If the user checked "Don't ask again" or this is a desktopNotification, make a permanent exception
         if (aChecked || entityName == "desktopNotification2") {
           Services.perms.addFromPrincipal(request.principal, perm.type, Ci.nsIPermissionManager.ALLOW_ACTION);
+        } else if (isApp) {
+          // Otherwise allow the permission for the current session if the request comes from an app
+          Services.perms.addFromPrincipal(request.principal, perm.type, Ci.nsIPermissionManager.ALLOW_ACTION, Ci.nsIPermissionManager.EXPIRE_SESSION);
         }
 
         callback(/* allow */ true);
       },
       positive: true,
     }];
 
     let chromeWin = this.getChromeForRequest(request);
--- a/netwerk/base/nsILoadContextInfo.idl
+++ b/netwerk/base/nsILoadContextInfo.idl
@@ -18,16 +18,19 @@ interface nsIDOMWindow;
  * encapsulating origin attributes and IsAnonymous, IsPrivite properties.
  * It shall be used where nsILoadContext cannot be used or is not
  * available.
  */
 
 [scriptable, builtinclass, uuid(555e2f8a-a1f6-41dd-88ca-ed4ed6b98a22)]
 interface nsILoadContextInfo : nsISupports
 {
+  const unsigned long NO_APP_ID = 0;
+  const unsigned long UNKNOWN_APP_ID = 4294967295; // UINT32_MAX
+
   /**
    * Whether the context is in a Private Browsing mode
    */
   readonly attribute boolean isPrivate;
 
   /**
    * Whether the load is initiated as anonymous
    */
--- a/netwerk/base/nsNetUtil.h
+++ b/netwerk/base/nsNetUtil.h
@@ -346,17 +346,17 @@ nsresult NS_NewInputStreamPump(nsIInputS
 
 nsresult NS_NewLoadGroup(nsILoadGroup** result, nsIRequestObserver* obs);
 
 // Create a new nsILoadGroup that will match the given principal.
 nsresult NS_NewLoadGroup(nsILoadGroup** aResult, nsIPrincipal* aPrincipal);
 
 // Determine if the given loadGroup/principal pair will produce a principal
 // with similar permissions when passed to NS_NewChannel().  This checks for
-// things like making sure the browser element flag matches.  Without
+// things like making sure the appId and browser element flags match.  Without
 // an appropriate load group these values can be lost when getting the result
 // principal back out of the channel.  Null principals are also always allowed
 // as they do not have permissions to actually use the load group.
 bool NS_LoadGroupMatchesPrincipal(nsILoadGroup* aLoadGroup,
                                   nsIPrincipal* aPrincipal);
 
 nsresult NS_NewDownloader(nsIStreamListener** result,
                           nsIDownloadObserver* observer,
@@ -630,16 +630,21 @@ bool NS_IsSafeTopLevelNav(nsIChannel* aC
 
 /**
  * Returns true if the channel is a foreign with respect to the host-uri.
  * For loads of TYPE_DOCUMENT, this function returns true if it's a
  * cross origin navigation.
  */
 bool NS_IsSameSiteForeign(nsIChannel* aChannel, nsIURI* aHostURI);
 
+// Constants duplicated from nsIScriptSecurityManager so we avoid having necko
+// know about script security manager.
+#define NECKO_NO_APP_ID 0
+#define NECKO_UNKNOWN_APP_ID UINT32_MAX
+
 // Unique first-party domain for separating the safebrowsing cookie.
 // Note if this value is changed, code in test_cookiejars_safebrowsing.js and
 // nsUrlClassifierHashCompleter.js should also be changed.
 #define NECKO_SAFEBROWSING_FIRST_PARTY_DOMAIN \
   "safebrowsing.86868755-6b82-4842-b301-72671a0db32e.mozilla"
 
 // Unique first-party domain for separating about uri.
 #define ABOUT_URI_FIRST_PARTY_DOMAIN \
@@ -937,19 +942,19 @@ uint32_t NS_GetDefaultReferrerPolicy(nsI
 /**
  * Return true if this channel should be classified by the URL classifier.
  */
 bool NS_ShouldClassifyChannel(nsIChannel* aChannel);
 
 /**
  * Helper to set the blocking reason on loadinfo of the channel.
  */
-nsresult NS_SetRequestBlockingReason(nsIChannel* channel, uint32_t reason);
-nsresult NS_SetRequestBlockingReason(nsILoadInfo* loadInfo, uint32_t reason);
-nsresult NS_SetRequestBlockingReasonIfNull(nsILoadInfo* loadInfo,
+nsresult NS_SetRequestBlockingReason(nsIChannel *channel, uint32_t reason);
+nsresult NS_SetRequestBlockingReason(nsILoadInfo *loadInfo, uint32_t reason);
+nsresult NS_SetRequestBlockingReasonIfNull(nsILoadInfo *loadInfo,
                                            uint32_t reason);
 
 namespace mozilla {
 namespace net {
 
 const static uint64_t kJS_MAX_SAFE_UINTEGER = +9007199254740991ULL;
 const static int64_t kJS_MIN_SAFE_INTEGER = -9007199254740991LL;
 const static int64_t kJS_MAX_SAFE_INTEGER = +9007199254740991LL;
--- a/netwerk/cache2/CacheFileUtils.cpp
+++ b/netwerk/cache2/CacheFileUtils.cpp
@@ -95,18 +95,17 @@ class KeyParser : protected Tokenizer {
         // Leaving to be able to read and understand oldformatted entries
         originAttribs.mInIsolatedMozBrowser = true;
         break;
       case 'a':
         isAnonymous = true;
         break;
       case 'i': {
         // Leaving to be able to read and understand oldformatted entries
-        uint32_t deprecatedAppId = 0;
-        if (!ReadInteger(&deprecatedAppId)) {
+        if (!ReadInteger(&originAttribs.mAppId)) {
           return false;  // not a valid 32-bit integer
         }
         break;
       }
       case '~':
         if (!ParseValue(&idEnhance)) {
           return false;
         }
--- a/netwerk/cache2/CacheStorageService.cpp
+++ b/netwerk/cache2/CacheStorageService.cpp
@@ -52,17 +52,17 @@ void AppendMemoryStorageTag(nsAutoCStrin
 // Not defining as static or class member of CacheStorageService since
 // it would otherwise need to include CacheEntry.h and that then would
 // need to be exported to make nsNetModule.cpp compilable.
 typedef nsClassHashtable<nsCStringHashKey, CacheEntryTable> GlobalEntryTables;
 
 /**
  * Keeps tables of entries.  There is one entries table for each distinct load
  * context type.  The distinction is based on following load context info
- * states: <isPrivate|isAnon|inIsolatedMozBrowser> which builds a mapping
+ * states: <isPrivate|isAnon|appId|inIsolatedMozBrowser> which builds a mapping
  * key.
  *
  * Thread-safe to access, protected by the service mutex.
  */
 static GlobalEntryTables* sGlobalEntryTables;
 
 CacheMemoryConsumer::CacheMemoryConsumer(uint32_t aFlags)
     : mReportedMemoryConsumption(0), mFlags(aFlags) {}
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -736,17 +736,18 @@ ConvertAppIdToOriginAttrsSQLFunction::On
   nsresult rv;
   int32_t inIsolatedMozBrowser;
 
   rv = aFunctionArguments->GetInt32(1, &inIsolatedMozBrowser);
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Create an originAttributes object by inIsolatedMozBrowser.
   // Then create the originSuffix string from this object.
-  OriginAttributes attrs(inIsolatedMozBrowser ? true : false);
+  OriginAttributes attrs(nsIScriptSecurityManager::NO_APP_ID,
+                         (inIsolatedMozBrowser ? true : false));
   nsAutoCString suffix;
   attrs.CreateSuffix(suffix);
 
   RefPtr<nsVariant> outVar(new nsVariant());
   rv = outVar->SetAsAUTF8String(suffix);
   NS_ENSURE_SUCCESS(rv, rv);
 
   outVar.forget(aResult);
@@ -771,17 +772,17 @@ SetAppIdFromOriginAttributesSQLFunction:
   OriginAttributes attrs;
 
   rv = aFunctionArguments->GetUTF8String(0, suffix);
   NS_ENSURE_SUCCESS(rv, rv);
   bool success = attrs.PopulateFromSuffix(suffix);
   NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
 
   RefPtr<nsVariant> outVar(new nsVariant());
-  rv = outVar->SetAsInt32(0);  // deprecated appId!
+  rv = outVar->SetAsInt32(attrs.mAppId);
   NS_ENSURE_SUCCESS(rv, rv);
 
   outVar.forget(aResult);
   return NS_OK;
 }
 
 class SetInBrowserFromOriginAttributesSQLFunction final
     : public mozIStorageFunction {
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -152,17 +152,17 @@ static MOZ_COLD void CrashWithReason(con
 
 const char* NeckoParent::GetValidatedOriginAttributes(
     const SerializedLoadContext& aSerialized, PContentParent* aContent,
     nsIPrincipal* aRequestingPrincipal, OriginAttributes& aAttrs) {
   if (!UsingNeckoIPCSecurity()) {
     if (!aSerialized.IsNotNull()) {
       // If serialized is null, we cannot validate anything. We have to assume
       // that this requests comes from a SystemPrincipal.
-      aAttrs = OriginAttributes(false);
+      aAttrs = OriginAttributes(NECKO_NO_APP_ID, false);
     } else {
       aAttrs = aSerialized.mOriginAttributes;
     }
     return nullptr;
   }
 
   if (!aSerialized.IsNotNull()) {
     CrashWithReason(
--- a/netwerk/protocol/about/nsAboutCache.cpp
+++ b/netwerk/protocol/about/nsAboutCache.cpp
@@ -95,20 +95,22 @@ nsresult nsAboutCache::Channel::Init(nsI
       "<body class=\"aboutPageWideContainer\">\n"
       "<h1>Information about the Network Cache Storage Service</h1>\n");
 
   // Add the context switch controls
   mBuffer.AppendLiteral(
       "<label><input id='priv' type='checkbox'/> Private</label>\n"
       "<label><input id='anon' type='checkbox'/> Anonymous</label>\n");
 
-  // Visit scoping by browseris not implemented for the old cache, simply don't
-  // add these controls.  The inbrowser entries are already mixed in the
-  // default view anyway.
+  // Visit scoping by browser and appid is not implemented for
+  // the old cache, simply don't add these controls.
+  // The appid/inbrowser entries are already mixed in the default
+  // view anyway.
   mBuffer.AppendLiteral(
+      "<label><input id='appid' type='text' size='6'/> AppID</label>\n"
       "<label><input id='inbrowser' type='checkbox'/> In Browser "
       "Element</label>\n");
 
   mBuffer.AppendLiteral(
       "<label><input id='submit' type='button' value='Update'/></label>\n");
 
   if (!mOverview) {
     mBuffer.AppendLiteral("<a href=\"about:cache?storage=&amp;context=");
--- a/netwerk/protocol/http/nsHttpChannel.h
+++ b/netwerk/protocol/http/nsHttpChannel.h
@@ -543,17 +543,17 @@ class nsHttpChannel final : public HttpB
 
   void SetLoadGroupUserAgentOverride();
 
   void SetOriginHeader();
   void SetDoNotTrack();
 
   bool IsIsolated();
 
-  const nsCString& GetTopWindowOrigin();
+  const nsCString &GetTopWindowOrigin();
 
   already_AddRefed<nsChannelClassifier> GetOrCreateChannelClassifier();
 
   // Start an internal redirect to a new InterceptedHttpChannel which will
   // resolve in firing a ServiceWorker FetchEvent.
   MOZ_MUST_USE nsresult RedirectToInterceptedChannel();
 
   // Determines and sets content type in the cache entry. It's called when
@@ -692,17 +692,17 @@ class nsHttpChannel final : public HttpB
   // had been loaded from cache. If not, then an error has to be propagated
   // to the consumer.
   uint32_t mConcurrentCacheAccess : 1;
   // whether the request is setup be byte-range
   uint32_t mIsPartialRequest : 1;
   // true iff there is AutoRedirectVetoNotifier on the stack
   uint32_t mHasAutoRedirectVetoNotifier : 1;
   // consumers set this to true to use cache pinning, this has effect
-  // only when the channel is in an app context
+  // only when the channel is in an app context (load context has an appid)
   uint32_t mPinCacheContent : 1;
   // True if CORS preflight has been performed
   uint32_t mIsCorsPreflightDone : 1;
 
   // if the http transaction was performed (i.e. not cached) and
   // the result in OnStopRequest was known to be correctly delimited
   // by chunking, content-length, or h2 end-stream framing
   uint32_t mStronglyFramed : 1;
--- a/netwerk/test/unit/head_channels.js
+++ b/netwerk/test/unit/head_channels.js
@@ -222,21 +222,23 @@ ChannelEventSink.prototype = {
 
     callback.onRedirectVerifyCallback(Cr.NS_OK);
   }
 };
 
 /**
  * A helper class to construct origin attributes.
  */
-function OriginAttributes(inIsolatedMozBrowser, privateId) {
+function OriginAttributes(appId, inIsolatedMozBrowser, privateId) {
+  this.appId = appId;
   this.inIsolatedMozBrowser = inIsolatedMozBrowser;
   this.privateBrowsingId = privateId;
 }
 OriginAttributes.prototype = {
+  appId: 0,
   inIsolatedMozBrowser: false,
   privateBrowsingId: 0
 };
 
 function readFile(file) {
   let fstream = Cc["@mozilla.org/network/file-input-stream;1"]
                   .createInstance(Ci.nsIFileInputStream);
   fstream.init(file, -1, 0, 0);
--- a/netwerk/test/unit/test_auth_jar.js
+++ b/netwerk/test/unit/test_auth_jar.js
@@ -5,33 +5,41 @@ function createURI(s) {
 }
  
 function run_test() {
   // Set up a profile.
   do_get_profile();
 
   var secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
   const kURI1 = "http://example.com";
-  var app = secMan.createCodebasePrincipal(createURI(kURI1), {});
-  var appbrowser = secMan.createCodebasePrincipal(createURI(kURI1), {inIsolatedMozBrowser: true});
+  var app1 = secMan.createCodebasePrincipal(createURI(kURI1), {appId: 1});
+  var app10 = secMan.createCodebasePrincipal(createURI(kURI1),{appId: 10});
+  var app1browser = secMan.createCodebasePrincipal(createURI(kURI1), {appId: 1, inIsolatedMozBrowser: true});
 
   var am = Cc["@mozilla.org/network/http-auth-manager;1"].
            getService(Ci.nsIHttpAuthManager);
-  am.setAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", "example.com", "user", "pass", false, app);
-  am.setAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", "example.com", "user3", "pass3", false, appbrowser);
+  am.setAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", "example.com", "user", "pass", false, app1);
+  am.setAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", "example.com", "user3", "pass3", false, app1browser);
+  am.setAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", "example.com", "user2", "pass2", false, app10);
 
-  Services.clearData.deleteDataFromOriginAttributesPattern({ inIsolatedMozBrowser:true });
+  Services.clearData.deleteDataFromOriginAttributesPattern({ appId:1, inIsolatedMozBrowser:true });
   
   var domain = {value: ""}, user = {value: ""}, pass = {value: ""};
   try {
-    am.getAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", domain, user, pass, false, appbrowser);
+    am.getAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", domain, user, pass, false, app1browser);
     Assert.equal(false, true); // no identity should be present
   } catch (x) {
     Assert.equal(domain.value, "");
     Assert.equal(user.value, "");
     Assert.equal(pass.value, "");
   }
 
-  am.getAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", domain, user, pass, false, app);
+  am.getAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", domain, user, pass, false, app1);
   Assert.equal(domain.value, "example.com");
   Assert.equal(user.value, "user");
   Assert.equal(pass.value, "pass");
+
+
+  am.getAuthIdentity("http", "a.example.com", -1, "basic", "realm", "", domain, user, pass, false, app10);
+  Assert.equal(domain.value, "example.com");
+  Assert.equal(user.value, "user2");
+  Assert.equal(pass.value, "pass2");
 }
--- a/netwerk/test/unit/test_cache_jar.js
+++ b/netwerk/test/unit/test_cache_jar.js
@@ -11,48 +11,77 @@ function cached_handler(metadata, respon
   response.setHeader("Content-Type", "text/plain", false);
   response.setHeader("Cache-Control", "max-age=10000", false);
   response.setStatusLine(metadata.httpVersion, 200, "OK");
   var body = "0123456789";
   response.bodyOutputStream.write(body, body.length);
   handlers_called++;
 }
 
-function makeChan(url, inIsolatedMozBrowser, userContextId) {
+function makeChan(url, appId, inIsolatedMozBrowser, userContextId) {
   var chan = NetUtil.newChannel({uri: url, loadUsingSystemPrincipal: true})
                     .QueryInterface(Ci.nsIHttpChannel);
-  chan.loadInfo.originAttributes = { inIsolatedMozBrowser: inIsolatedMozBrowser,
+  chan.loadInfo.originAttributes = { appId: appId,
+                                     inIsolatedMozBrowser: inIsolatedMozBrowser,
                                      userContextId: userContextId,
                                    };
   return chan;
 }
 
-// [inIsolatedMozBrowser, userContextId, expected_handlers_called]
+// [appId, inIsolatedMozBrowser, userContextId, expected_handlers_called]
 var firstTests = [
-  [false, 0, 1], [true, 0, 1], [false, 1, 1], [true, 1, 1],
+  [0, false, 0, 1], [0, true, 0, 1], [1, false, 0, 1], [1, true, 0, 1],
+  [0, false, 1, 1], [0, true, 1, 1], [1, false, 1, 1], [1, true, 1, 1]
 ];
 var secondTests = [
-  [false, 0, 0], [true, 0, 0], [false, 1, 1], [true, 1, 0],
+  [0, false, 0, 0], [0, true, 0, 0], [1, false, 0, 0], [1, true, 0, 1],
+  [0, false, 1, 0], [0, true, 1, 0], [1, false, 1, 0], [1, true, 1, 0]
+];
+var thirdTests = [
+  [0, false, 0, 0], [0, true, 0, 0], [1, false, 0, 1], [1, true, 0, 1],
+  [0, false, 1, 0], [0, true, 1, 0], [1, false, 1, 0], [1, true, 1, 0]
+];
+var fourthTests = [
+  [0, false, 0, 0], [0, true, 0, 0], [1, false, 0, 0], [1, true, 0, 0],
+  [0, false, 1, 1], [0, true, 1, 0], [1, false, 1, 0], [1, true, 1, 0]
 ];
 
 async function run_all_tests() {
   for (let test of firstTests) {
     handlers_called = 0;
     await test_channel(...test);
   }
 
   // We can't easily cause webapp data to be cleared from the child process, so skip
   // the rest of these tests.
   let procType = Cc["@mozilla.org/xre/runtime;1"].getService(Ci.nsIXULRuntime).processType;
   if (procType != Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT)
     return;
 
+  let attrs_inBrowser = { appId:1, inIsolatedMozBrowser:true };
+  let attrs_notInBrowser = { appId:1 };
+
+  Services.clearData.deleteDataFromOriginAttributesPattern(attrs_inBrowser);
+
+  for (let test of secondTests) {
+    handlers_called = 0;
+    await test_channel(...test);
+  }
+
+  Services.clearData.deleteDataFromOriginAttributesPattern(attrs_notInBrowser);
+  Services.clearData.deleteDataFromOriginAttributesPattern(attrs_inBrowser);
+
+  for (let test of thirdTests) {
+    handlers_called = 0;
+    await test_channel(...test);
+  }
+
   Services.clearData.deleteDataFromOriginAttributesPattern({ userContextId: 1 });
 
-  for (let test of secondTests) {
+  for (let test of fourthTests) {
     handlers_called = 0;
     await test_channel(...test);
   }
 }
 
 function run_test() {
   do_get_profile();
 
@@ -63,26 +92,26 @@ function run_test() {
   httpserv = new HttpServer();
   httpserv.registerPathHandler("/cached", cached_handler);
   httpserv.start(-1);
   run_all_tests().then(() => {
     do_test_finished();
   });
 }
 
-function test_channel(inIsolatedMozBrowser, userContextId, expected) {
+function test_channel(appId, inIsolatedMozBrowser, userContextId, expected) {
   return new Promise(resolve => {
-    var chan = makeChan(URL, inIsolatedMozBrowser, userContextId);
+    var chan = makeChan(URL, appId, inIsolatedMozBrowser, userContextId);
     chan.asyncOpen(new ChannelListener(doneFirstLoad.bind(null, resolve), expected));
   });
 }
 
 function doneFirstLoad(resolve, req, buffer, expected) {
   // Load it again, make sure it hits the cache
   var oa = req.loadInfo.originAttributes;
-  var chan = makeChan(URL, oa.isInIsolatedMozBrowserElement, oa.userContextId);
+  var chan = makeChan(URL, oa.appId, oa.isInIsolatedMozBrowserElement, oa.userContextId);
   chan.asyncOpen(new ChannelListener(doneSecondLoad.bind(null, resolve), expected));
 }
 
 function doneSecondLoad(resolve, req, buffer, expected) {
   Assert.equal(handlers_called, expected);
   resolve();
 }
--- a/testing/specialpowers/content/SpecialPowersObserver.jsm
+++ b/testing/specialpowers/content/SpecialPowersObserver.jsm
@@ -214,23 +214,23 @@ SpecialPowersObserver.prototype._registe
   },
   observe(aSubject, aTopic, aData) {
     var msg = { aData };
     switch (aTopic) {
       case "perm-changed":
         var permission = aSubject.QueryInterface(Ci.nsIPermission);
 
         // specialPowersAPI will consume this value, and it is used as a
-        // fake permission, but only type will be used.
+        // fake permission, but only type and principal.appId will be used.
         //
         // We need to ensure that it looks the same as a real permission,
         // so we fake these properties.
         msg.permission = {
           principal: {
-            originAttributes: {},
+            originAttributes: {appId: permission.principal.appId},
           },
           type: permission.type,
         };
       default:
         this._self._sendAsyncMessage("specialpowers-" + aTopic, msg);
     }
   },
 };
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -974,16 +974,17 @@ SpecialPowersAPI.prototype = {
         }
       } else {
         var found = false;
         for (var i = 0; !found && i < this._self._permissionsUndoStack.length; i++) {
           var undos = this._self._permissionsUndoStack[i];
           for (var j = 0; j < undos.length; j++) {
             var undo = undos[j];
             if (undo.op == this._obsDataMap[aData] &&
+                undo.principal.originAttributes.appId == permission.principal.originAttributes.appId &&
                 undo.type == permission.type) {
               // Remove this undo item if it has been done by others(not
               // specialpowers itself.)
               undos.splice(j, 1);
               found = true;
               break;
             }
           }
--- a/toolkit/components/aboutcache/content/aboutCache.js
+++ b/toolkit/components/aboutcache/content/aboutCache.js
@@ -6,35 +6,41 @@
 // Note: window.location.search doesn't work with nsSimpleURIs used for about:* addresses.
 var search = window.location.href.match(/^.*\?(.*)$/);
 var searchParams = new URLSearchParams(search ? search[1] : "");
 var storage = searchParams.get("storage") || "";
 var cacheContext = searchParams.get("context");
 
 // The context is in a format as used by the HTTP cache v2 back end
 if (cacheContext)
-  var [context, isAnon, isInBrowser, isPrivate] = cacheContext.match(/(a,)?(b,)?(p,)?/);
+  var [context, isAnon, isInBrowser, appId, isPrivate] = cacheContext.match(/(a,)?(b,)?(i\d+,)?(p,)?/);
+if (appId)
+  appId = appId.match(/i(\d+),/)[1];
+
 
 function $(id) { return document.getElementById(id) || {}; }
 
 // Initialize the context UI controls at the start according what we got in the "context=" argument
 addEventListener("DOMContentLoaded", function() {
   $("anon").checked = !!isAnon;
   $("inbrowser").checked = !!isInBrowser;
+  $("appid").value = appId || "";
   $("priv").checked = !!isPrivate;
 }, false);
 
 // When user presses the [Update] button, we build a new context key according the UI control
 // values and navigate to a new about:cache?storage=<name>&context=<key> URL.
 function navigate() {
   context = "";
   if ($("anon").checked)
     context += "a,";
   if ($("inbrowser").checked)
     context += "b,";
+  if ($("appid").value)
+    context += "i" + $("appid").value + ",";
   if ($("priv").checked)
     context += "p,";
 
   window.location.href = "about:cache?storage=" + storage + "&context=" + context;
 }
 
 let submitButton = document.getElementById("submit");
 submitButton.addEventListener("click", navigate);
--- a/toolkit/content/aboutServiceWorkers.js
+++ b/toolkit/content/aboutServiceWorkers.js
@@ -61,16 +61,23 @@ async function display(info, pushService
 
   let div = document.createElement("div");
   parent.appendChild(div);
 
   let title = document.createElement("h2");
   document.l10n.setAttributes(title, "origin-title", { originTitle: info.principal.origin });
   div.appendChild(title);
 
+  if (info.principal.appId) {
+    let b2gtitle = document.createElement("h3");
+    let trueFalse = info.principal.isInIsolatedMozBrowserElement ? "true" : "false";
+    document.l10n.setAttributes(b2gtitle, "app-title", { appId: info.principal.appId, isInIsolatedElement: trueFalse });
+    div.appendChild(b2gtitle);
+  }
+
   let list = document.createElement("ul");
   div.appendChild(list);
 
   function createItem(l10nId, value, makeLink) {
     let item = document.createElement("li");
     list.appendChild(item);
     let bold = document.createElement("strong");
     bold.setAttribute("data-l10n-name", "item-label");
--- a/toolkit/locales/en-US/toolkit/about/aboutServiceWorkers.ftl
+++ b/toolkit/locales/en-US/toolkit/about/aboutServiceWorkers.ftl
@@ -10,16 +10,23 @@ about-service-workers-warning-not-enable
 about-service-workers-warning-no-service-workers = No Service Workers registered.
 
 # The original title of service workers' information
 #
 # Variables:
 #   $originTitle: original title
 origin-title = Origin: { $originTitle }
 
+# Show if app id is in isolated browser element, the term "InBrowserElement" should not be translated
+#
+# Variables:
+#   $appId: the application ID
+#   $isInIsolatedElement: "true" or "false" based on if the app id is in isolate element
+app-title = { -brand-short-name } Application ID { $appId } - InBrowserElement { $isInIsolatedElement }
+
 ## These strings are for showing the information of workers.
 ##
 ## Variables:
 ##  $name: the name of scope, active cache, waiting cache and the push end point.
 ##  $url: the url of script specification and current worker.
 scope = <strong>Scope:</strong> { $name }
 script-spec = <strong>Script Spec:</strong> <a data-l10n-name="link">{ $url }</a>
 current-worker-url = <strong>Current Worker URL:</strong> <a data-l10n-name="link">{ $url }</a>