Backed out changeset 7d1f7dd996f7 (bug 1310845)
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 16 Nov 2016 14:50:44 +0100
changeset 322837 5bfd79155fff3f7bef6eee90509c7167edd4b634
parent 322836 b23408de27e4103ea5fee42b2876fe78c7d0ea3b
child 322838 b8a81bf64c4ef4f1b83f20c611fa515d132e615c
push id30961
push userkwierso@gmail.com
push dateThu, 17 Nov 2016 01:08:03 +0000
treeherdermozilla-central@c27117f67fa3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1310845
milestone53.0a1
backs out7d1f7dd996f7fd220781aa93f1ac82f98971631c
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 changeset 7d1f7dd996f7 (bug 1310845)
accessible/jsat/AccessFu.jsm
b2g/chrome/content/devtools/hud.js
b2g/chrome/content/shell.js
b2g/chrome/content/shell_remote.js
b2g/components/DebuggerActors.js
b2g/components/ErrorPage.jsm
b2g/components/Frames.jsm
b2g/components/SafeMode.jsm
b2g/simulator/custom-prefs.js
caps/nsIPrincipal.idl
caps/tests/mochitest/mochitest.ini
caps/tests/mochitest/test_app_principal_equality.html
devtools/server/actors/webconsole.js
devtools/shared/layout/utils.js
devtools/shared/webconsole/network-monitor.js
docshell/base/LoadContext.cpp
docshell/base/nsDSURIContentListener.cpp
docshell/base/nsDocShell.cpp
docshell/base/nsDocShell.h
docshell/base/nsIDocShell.idl
docshell/base/nsIDocShellTreeItem.idl
docshell/base/nsILoadContext.idl
dom/apps/PermissionsTable.jsm
dom/base/Navigator.cpp
dom/base/ThirdPartyUtil.cpp
dom/base/nsContentUtils.cpp
dom/base/nsFrameLoader.cpp
dom/base/nsFrameLoader.h
dom/base/nsGkAtomList.h
dom/base/nsGlobalWindow.cpp
dom/base/nsIFrameLoader.idl
dom/base/nsInProcessTabChildGlobal.cpp
dom/base/nsInProcessTabChildGlobal.h
dom/base/nsObjectLoadingContent.cpp
dom/base/nsXHTMLContentSerializer.cpp
dom/browser-element/BrowserElementChild.js
dom/browser-element/BrowserElementCopyPaste.js
dom/browser-element/BrowserElementParent.cpp
dom/browser-element/mochitest/browserElement_SetInputMethodActive.js
dom/browser-element/mochitest/file_browserElement_AudioChannel_nested.html
dom/devicestorage/test/test_app_permissions.html
dom/devicestorage/test/test_fs_app_permissions.html
dom/events/EventStateManager.cpp
dom/html/nsBrowserElement.cpp
dom/html/nsBrowserElement.h
dom/html/nsGenericHTMLFrameElement.cpp
dom/indexedDB/test/file_app_isolation.html
dom/indexedDB/test/file_app_isolation.js
dom/indexedDB/test/mochitest.ini
dom/indexedDB/test/test_app_isolation_inproc.html
dom/indexedDB/test/test_app_isolation_oop.html
dom/inputmethod/Keyboard.jsm
dom/interfaces/html/nsIMozBrowserFrame.idl
dom/ipc/ContentBridgeChild.cpp
dom/ipc/ContentBridgeChild.h
dom/ipc/ContentBridgeParent.cpp
dom/ipc/ContentBridgeParent.h
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/ContentParent.cpp
dom/ipc/ContentParent.h
dom/ipc/ContentProcessManager.cpp
dom/ipc/ContentProcessManager.h
dom/ipc/PContent.ipdl
dom/ipc/PContentBridge.ipdl
dom/ipc/PTabContext.ipdlh
dom/ipc/ProcessPriorityManager.cpp
dom/ipc/TabChild.cpp
dom/ipc/TabChild.h
dom/ipc/TabContext.cpp
dom/ipc/TabContext.h
dom/ipc/TabParent.cpp
dom/ipc/nsIContentChild.cpp
dom/ipc/nsIContentChild.h
dom/ipc/nsIContentParent.cpp
dom/ipc/nsIContentParent.h
dom/media/MediaManager.cpp
dom/network/TCPServerSocketParent.cpp
dom/network/TCPSocketParent.cpp
dom/network/interfaces/nsINetworkStatsServiceProxy.idl
dom/permission/tests/test_embed-apps.html
dom/presentation/Presentation.cpp
dom/webidl/HTMLIFrameElement.webidl
dom/webidl/MozNetworkStats.webidl
dom/xul/nsXULElement.cpp
dom/xul/nsXULElement.h
embedding/components/windowwatcher/nsWindowWatcher.cpp
extensions/cookie/nsPermissionManager.cpp
layout/base/nsPresShell.cpp
layout/generic/nsViewportFrame.cpp
layout/style/nsCSSRuleProcessor.cpp
layout/tools/reftest/reftest.jsm
mobile/android/chrome/content/browser.js
modules/libpref/init/all.js
netwerk/base/nsNetUtil.cpp
netwerk/cookie/nsCookieService.cpp
netwerk/ipc/NeckoParent.cpp
netwerk/test/unit_ipc/child_app_offline_notifications.js
testing/mochitest/manifest.webapp
uriloader/prefetch/OfflineCacheUpdateParent.cpp
widget/gonk/GonkPermission.cpp
xpfe/appshell/nsContentTreeOwner.cpp
--- a/accessible/jsat/AccessFu.jsm
+++ b/accessible/jsat/AccessFu.jsm
@@ -337,19 +337,19 @@ this.AccessFu = { // jshint ignore:line
         }
         break;
       case 'Accessibility:MoveByGranularity':
         this.Input.moveByGranularity(JSON.parse(aData));
         break;
       case 'remote-browser-shown':
       case 'inprocess-browser-shown':
       {
-        // Ignore notifications that aren't from a Browser
+        // Ignore notifications that aren't from a BrowserOrApp
         let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
-        if (!frameLoader.ownerIsMozBrowserFrame) {
+        if (!frameLoader.ownerIsMozBrowserOrAppFrame) {
           return;
         }
         this._handleMessageManager(frameLoader.messageManager);
         break;
       }
     }
   },
 
--- a/b2g/chrome/content/devtools/hud.js
+++ b/b2g/chrome/content/devtools/hud.js
@@ -31,32 +31,35 @@ XPCOMUtils.defineLazyGetter(this, 'Event
 XPCOMUtils.defineLazyGetter(this, 'PerformanceEntriesFront', function() {
   return devtools.require('devtools/server/actors/performance-entries').PerformanceEntriesFront;
 });
 
 XPCOMUtils.defineLazyGetter(this, 'MemoryFront', function() {
   return devtools.require('devtools/server/actors/memory').MemoryFront;
 });
 
+Cu.import('resource://gre/modules/Frames.jsm');
+
 var _telemetryDebug = false;
 
 function telemetryDebug(...args) {
   if (_telemetryDebug) {
     args.unshift('[AdvancedTelemetry]');
     console.log(...args);
   }
 }
 
 /**
  * The Developer HUD is an on-device developer tool that displays widgets,
  * showing visual debug information about apps. Each widget corresponds to a
  * metric as tracked by a metric watcher (e.g. consoleWatcher).
  */
 var developerHUD = {
 
+  _targets: new Map(),
   _histograms: new Set(),
   _customHistograms: new Set(),
   _client: null,
   _conn: null,
   _watchers: [],
   _logging: true,
   _telemetry: false,
 
@@ -91,16 +94,23 @@ var developerHUD = {
     this._client = new DebuggerClient(transport);
 
     for (let w of this._watchers) {
       if (w.init) {
         w.init(this._client);
       }
     }
 
+    Frames.addObserver(this);
+
+    let appFrames = Frames.list().filter(frame => frame.getAttribute('mozapp'));
+    for (let frame of appFrames) {
+      this.trackFrame(frame);
+    }
+
     SettingsListener.observe('hud.logging', this._logging, enabled => {
       this._logging = enabled;
     });
 
     SettingsListener.observe('hud.telemetry.logging', _telemetryDebug, enabled => {
       _telemetryDebug = enabled;
     });
 
@@ -109,20 +119,73 @@ var developerHUD = {
     });
   },
 
   uninit() {
     if (!this._client) {
       return;
     }
 
+    for (let frame of this._targets.keys()) {
+      this.untrackFrame(frame);
+    }
+
+    Frames.removeObserver(this);
+
     this._client.close();
     delete this._client;
   },
 
+  /**
+   * This method will ask all registered watchers to track and update metrics
+   * on an app frame.
+   */
+  trackFrame(frame) {
+    if (this._targets.has(frame)) {
+      return;
+    }
+
+    DebuggerServer.connectToChild(this._conn, frame).then(actor => {
+      let target = new Target(frame, actor);
+      this._targets.set(frame, target);
+
+      for (let w of this._watchers) {
+        w.trackTarget(target);
+      }
+    });
+  },
+
+  untrackFrame(frame) {
+    let target = this._targets.get(frame);
+    if (target) {
+      for (let w of this._watchers) {
+        w.untrackTarget(target);
+      }
+
+      target.destroy();
+      this._targets.delete(frame);
+    }
+  },
+
+  onFrameCreated(frame, isFirstAppFrame) {
+    let mozapp = frame.getAttribute('mozapp');
+    if (!mozapp) {
+      return;
+    }
+    this.trackFrame(frame);
+  },
+
+  onFrameDestroyed(frame, isLastAppFrame) {
+    let mozapp = frame.getAttribute('mozapp');
+    if (!mozapp) {
+      return;
+    }
+    this.untrackFrame(frame);
+  },
+
   log(message) {
     if (this._logging) {
       dump(DEVELOPER_HUD_LOG_PREFIX + ': ' + message + '\n');
     }
   }
 
 };
 
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -353,16 +353,17 @@ var shell = {
     // <html:iframe id="systemapp"
     //              mozbrowser="true" allowfullscreen="true"
     //              style="overflow: hidden; height: 100%; width: 100%; border: none;"
     //              src="data:text/html;charset=utf-8,%3C!DOCTYPE html>%3Cbody style='background:black;'>"/>
     let systemAppFrame =
       document.createElementNS('http://www.w3.org/1999/xhtml', 'html:iframe');
     systemAppFrame.setAttribute('id', 'systemapp');
     systemAppFrame.setAttribute('mozbrowser', 'true');
+    systemAppFrame.setAttribute('mozapp', manifestURL);
     systemAppFrame.setAttribute('allowfullscreen', 'true');
     systemAppFrame.setAttribute('src', 'blank.html');
     let container = document.getElementById('container');
 
     if (AppConstants.platform == 'macosx') {
       // See shell.html
       let hotfix = document.getElementById('placeholder');
       if (hotfix) {
--- a/b2g/chrome/content/shell_remote.js
+++ b/b2g/chrome/content/shell_remote.js
@@ -48,16 +48,17 @@ var remoteShell = {
     // <html:iframe id="this.id"
     //              mozbrowser="true"
     //              allowfullscreen="true"
     //              src="blank.html"/>
     let systemAppFrame =
       document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
     systemAppFrame.setAttribute("id", this.id);
     systemAppFrame.setAttribute("mozbrowser", "true");
+    systemAppFrame.setAttribute("mozapp", manifestURL);
     systemAppFrame.setAttribute("allowfullscreen", "true");
     systemAppFrame.setAttribute("src", "blank.html");
 
     let container = document.getElementById("container");
     this.contentBrowser = container.appendChild(systemAppFrame);
     this.contentBrowser.src = homeURL + window.location.hash;
 
     window.addEventListener("unload", this);
--- a/b2g/components/DebuggerActors.js
+++ b/b2g/components/DebuggerActors.js
@@ -25,17 +25,20 @@ XPCOMUtils.defineLazyGetter(this, "Frame
 function B2GTabList(connection) {
   BrowserTabList.call(this, connection);
   this._listening = false;
 }
 
 B2GTabList.prototype = Object.create(BrowserTabList.prototype);
 
 B2GTabList.prototype._getBrowsers = function() {
-  return Frames.list();
+  return Frames.list().filter(frame => {
+    // Ignore app frames
+    return !frame.getAttribute("mozapp");
+  });
 };
 
 B2GTabList.prototype._getSelectedBrowser = function() {
   return this._getBrowsers().find(frame => {
     // Find the one visible browser (if any)
     return !frame.classList.contains("hidden");
   });
 };
@@ -51,20 +54,30 @@ B2GTabList.prototype._listenForEventsIf 
   if (this._listening != shouldListen) {
     let op = shouldListen ? "addObserver" : "removeObserver";
     Frames[op](this);
     this._listening = shouldListen;
   }
 };
 
 B2GTabList.prototype.onFrameCreated = function(frame) {
+  let mozapp = frame.getAttribute("mozapp");
+  if (mozapp) {
+    // Ignore app frames
+    return;
+  }
   this._notifyListChanged();
   this._checkListening();
 };
 
 B2GTabList.prototype.onFrameDestroyed = function(frame) {
+  let mozapp = frame.getAttribute("mozapp");
+  if (mozapp) {
+    // Ignore app frames
+    return;
+  }
   let actor = this._actorByBrowser.get(frame);
   if (actor) {
     this._handleActorClose(actor, frame);
   }
 };
 
 exports.B2GTabList = B2GTabList;
--- a/b2g/components/ErrorPage.jsm
+++ b/b2g/components/ErrorPage.jsm
@@ -171,17 +171,17 @@ var ErrorPage = {
 
   init: function errorPageInit() {
     Services.obs.addObserver(this, 'inprocess-browser-shown', false);
     Services.obs.addObserver(this, 'remote-browser-shown', false);
   },
 
   observe: function errorPageObserve(aSubject, aTopic, aData) {
     let frameLoader = aSubject.QueryInterface(Ci.nsIFrameLoader);
-    // Ignore notifications that aren't from a Browser
-    if (!frameLoader.ownerIsMozBrowserFrame) {
+    // Ignore notifications that aren't from a BrowserOrApp
+    if (!frameLoader.ownerIsMozBrowserOrAppFrame) {
       return;
     }
     this._listenError(frameLoader);
   }
 };
 
 ErrorPage.init();
--- a/b2g/components/Frames.jsm
+++ b/b2g/components/Frames.jsm
@@ -14,32 +14,40 @@ Cu.import('resource://gre/modules/System
 
 const listeners = [];
 
 const Observer = {
   // Save a map of (MessageManager => Frame) to be able to dispatch
   // the FrameDestroyed event with a frame reference.
   _frames: new Map(),
 
+  // Also save current number of iframes opened by app
+  _apps: new Map(),
+
   start: function () {
     Services.obs.addObserver(this, 'remote-browser-shown', false);
     Services.obs.addObserver(this, 'inprocess-browser-shown', false);
     Services.obs.addObserver(this, 'message-manager-close', false);
 
     SystemAppProxy.getFrames().forEach(frame => {
       let mm = frame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
       this._frames.set(mm, frame);
+      let mozapp = frame.getAttribute('mozapp');
+      if (mozapp) {
+        this._apps.set(mozapp, (this._apps.get(mozapp) || 0) + 1);
+      }
     });
   },
 
   stop: function () {
     Services.obs.removeObserver(this, 'remote-browser-shown');
     Services.obs.removeObserver(this, 'inprocess-browser-shown');
     Services.obs.removeObserver(this, 'message-manager-close');
     this._frames.clear();
+    this._apps.clear();
   },
 
   observe: function (subject, topic, data) {
     switch(topic) {
 
       // Listen for frame creation in OOP (device) as well as in parent process (b2g desktop)
       case 'remote-browser-shown':
       case 'inprocess-browser-shown':
@@ -57,38 +65,54 @@ const Observer = {
         this.onMessageManagerDestroyed(subject);
         break;
     }
   },
 
   onMessageManagerCreated: function (mm, frame) {
     this._frames.set(mm, frame);
 
+    let isFirstAppFrame = null;
+    let mozapp = frame.getAttribute('mozapp');
+    if (mozapp) {
+      let count = (this._apps.get(mozapp) || 0) + 1;
+      this._apps.set(mozapp, count);
+      isFirstAppFrame = (count === 1);
+    }
+
     listeners.forEach(function (listener) {
       try {
-        listener.onFrameCreated(frame);
+        listener.onFrameCreated(frame, isFirstAppFrame);
       } catch(e) {
         dump('Exception while calling Frames.jsm listener:' + e + '\n' +
              e.stack + '\n');
       }
     });
   },
 
   onMessageManagerDestroyed: function (mm) {
     let frame = this._frames.get(mm);
     if (!frame) {
       // We received an event for an unknown message manager
       return;
     }
 
     this._frames.delete(mm);
 
+    let isLastAppFrame = null;
+    let mozapp = frame.getAttribute('mozapp');
+    if (mozapp) {
+      let count = (this._apps.get(mozapp) || 0) - 1;
+      this._apps.set(mozapp, count);
+      isLastAppFrame = (count === 0);
+    }
+
     listeners.forEach(function (listener) {
       try {
-        listener.onFrameDestroyed(frame);
+        listener.onFrameDestroyed(frame, isLastAppFrame);
       } catch(e) {
         dump('Exception while calling Frames.jsm listener:' + e + '\n' +
              e.stack + '\n');
       }
     });
   }
 
 };
--- a/b2g/components/SafeMode.jsm
+++ b/b2g/components/SafeMode.jsm
@@ -71,16 +71,17 @@ this.SafeMode = {
     let document = SafeMode.window.document;
     SafeMode.window.screen.mozLockOrientation("portrait");
 
     let url = Services.io.newURI(shell.homeURL, null, null)
                          .resolve(kSafeModePage);
     debug("Registry is ready, loading " + url);
     let frame = document.createElementNS("http://www.w3.org/1999/xhtml", "html:iframe");
     frame.setAttribute("mozbrowser", "true");
+    frame.setAttribute("mozapp", shell.manifestURL);
     frame.setAttribute("id", "systemapp"); // To keep screen.js happy.
     let contentBrowser = document.body.appendChild(frame);
 
     return new Promise((aResolve, aReject) => {
       let content = contentBrowser.contentWindow;
 
       // Stripped down version of the system app bootstrap.
       function handleEvent(e) {
--- a/b2g/simulator/custom-prefs.js
+++ b/b2g/simulator/custom-prefs.js
@@ -1,7 +1,8 @@
 user_pref("devtools.debugger.prompt-connection", false);
 user_pref("devtools.debugger.forbid-certified-apps", false);
+user_pref("devtools.apps.forbidden-permissions", "");
 user_pref("b2g.software-buttons", true);
 
 // Required for Mulet in order to run the debugger server from the command line
 user_pref("devtools.debugger.remote-enabled", true);
 user_pref("devtools.chrome.enabled", true);
--- a/caps/nsIPrincipal.idl
+++ b/caps/nsIPrincipal.idl
@@ -293,20 +293,20 @@ interface nsIPrincipal : nsISerializable
      * Gets the id of the private browsing state of the context containing
      * this principal. If the principal has a private browsing value of 0, it
      * is not in private browsing.
      */
     [infallible] readonly attribute unsigned long privateBrowsingId;
 
     /**
      * Returns true iff the principal is inside an isolated mozbrowser element.
-     * <xul:browser> is not considered to be a mozbrowser element.
-     * <iframe mozbrowser noisolation> does not count as isolated since
-     * isolation is disabled.  Isolation can only be disabled if the
-     * containing document is chrome.
+     * <iframe mozbrowser mozapp> and <xul:browser> are not considered to be
+     * mozbrowser elements.  <iframe mozbrowser noisolation> does not count as
+     * isolated since isolation is disabled.  Isolation can only be disabled if
+     * the containing document is chrome.
      */
     [infallible] readonly attribute boolean isInIsolatedMozBrowserElement;
 
     /**
      * Returns true if this principal has an unknown appId. This shouldn't
      * generally be used. We only expose it due to not providing the correct
      * appId everywhere where we construct principals.
      */
--- a/caps/tests/mochitest/mochitest.ini
+++ b/caps/tests/mochitest/mochitest.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 support-files =
   file_data.txt
   file_disableScript.html
   !/js/xpconnect/tests/mochitest/file_empty.html
 
+[test_app_principal_equality.html]
 [test_bug246699.html]
 [test_bug292789.html]
 [test_bug423375.html]
 [test_bug470804.html]
 [test_disallowInheritPrincipal.html]
 [test_extensionURL.html]
new file mode 100644
--- /dev/null
+++ b/caps/tests/mochitest/test_app_principal_equality.html
@@ -0,0 +1,88 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=777467
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test app principal's equality</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=777467">Mozilla Bug 777467</a>
+<p id="display"></p>
+<script>
+
+/** Test for app principal's equality **/
+
+SimpleTest.waitForExplicitFinish();
+
+var permissions = new Promise(resolve => {
+  SpecialPowers.pushPermissions(
+    [{ type: "browser", allow: true, context: document },
+     { type: "embed-apps", allow: true, context: document }],
+    resolve);
+});
+
+permissions.then(() => {
+  $('content').innerHTML =
+    '<iframe src="error404"></iframe>\n' +
+    '<iframe mozbrowser src="error404"></iframe>\n' +
+    '<iframe mozapp="http://example.org/manifest.webapp" mozbrowser src="error404"></iframe>';
+
+  var iframes = document.getElementsByTagName("iframe");
+  var promises = []
+  for (var i = 0; i < promises.length; ++i) {
+    promises.push(new Promise(resolve => {
+      iframes[i].addEventListener("load", resolve);
+    }));
+  }
+
+  return Promise.all(promises);
+});
+
+var prefs = new Promise(resolve => {
+  SpecialPowers.pushPrefEnv(
+    { set: [[ "dom.mozBrowserFramesEnabled", true ],
+            [ "dom.ipc.browser_frames.oop_by_default", false ]] },
+    resolve);
+});
+</script>
+<div id="content" style="display: none;">
+</div>
+<pre id="test">
+<script type="application/javascript">
+
+function canAccessDocument(win) {
+  var result = true;
+  try {
+    win.document;
+  } catch(e) {
+    result = false;
+  }
+  return result;
+}
+
+var loaded = new Promise(resolve => addLoadEvent(resolve));
+
+Promise.all([ permissions, prefs, loaded ]).then(runTest);
+
+function runTest() {
+  // Test the witness frame (we can access same-origin frame).
+  is(canAccessDocument(frames[0]), true,
+     "should be able to access the first frame");
+
+  // Test different app/browserElement frames.
+  for (var i=1; i<frames.length; ++i) {
+    is(canAccessDocument(frames[i]), false,
+       "should not be able to access the other frames");
+  }
+
+  SimpleTest.finish();
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/devtools/server/actors/webconsole.js
+++ b/devtools/server/actors/webconsole.js
@@ -565,19 +565,21 @@ WebConsoleActor.prototype =
   {
     // XXXworkers: Not handling the Console API yet for workers (Bug 1209353).
     if (isWorker) {
       aRequest.listeners = [];
     }
 
     let startedListeners = [];
     let window = !this.parentActor.isRootActor ? this.window : null;
+    let appId = null;
     let messageManager = null;
 
     if (this._parentIsContentActor) {
+      appId = this.parentActor.docShell.appId;
       messageManager = this.parentActor.messageManager;
     }
 
     while (aRequest.listeners.length > 0) {
       let listener = aRequest.listeners.shift();
       switch (listener) {
         case "PageError":
           if (!this.consoleServiceListener) {
@@ -598,26 +600,26 @@ WebConsoleActor.prototype =
           }
           startedListeners.push(listener);
           break;
         case "NetworkActivity":
           if (!this.networkMonitor) {
             // Create a StackTraceCollector that's going to be shared both by the
             // NetworkMonitorChild (getting messages about requests from parent) and
             // by the NetworkMonitor that directly watches service workers requests.
-            this.stackTraceCollector = new StackTraceCollector({ window });
+            this.stackTraceCollector = new StackTraceCollector({ window, appId });
             this.stackTraceCollector.init();
 
             let processBoundary = Services.appinfo.processType !=
                                   Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
-            if (messageManager && processBoundary) {
+            if ((appId || messageManager) && processBoundary) {
               // Start a network monitor in the parent process to listen to
               // most requests than happen in parent
               this.networkMonitor =
-                new NetworkMonitorChild(this.parentActor.outerWindowID,
+                new NetworkMonitorChild(appId, this.parentActor.outerWindowID,
                                         messageManager, this.conn, this);
               this.networkMonitor.init();
               // Spawn also one in the child to listen to service workers
               this.networkMonitorChild = new NetworkMonitor({ window }, this);
               this.networkMonitorChild.init();
             } else {
               this.networkMonitor = new NetworkMonitor({ window }, this);
               this.networkMonitor.init();
--- a/devtools/shared/layout/utils.js
+++ b/devtools/shared/layout/utils.js
@@ -32,22 +32,22 @@ function utilsFor(win) {
  * @param {DOMWindow} win
  * @return {DOMWindow}
  */
 function getTopWindow(win) {
   let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
                     .getInterface(Ci.nsIWebNavigation)
                     .QueryInterface(Ci.nsIDocShell);
 
-  if (!docShell.isMozBrowser) {
+  if (!docShell.isMozBrowserOrApp) {
     return win.top;
   }
 
   let topDocShell =
-    docShell.getSameTypeRootTreeItemIgnoreBrowserBoundaries();
+    docShell.getSameTypeRootTreeItemIgnoreBrowserAndAppBoundaries();
 
   return topDocShell
           ? topDocShell.contentViewer.DOMDocument.defaultView
           : null;
 }
 
 exports.getTopWindow = getTopWindow;
 
@@ -93,22 +93,22 @@ function getParentWindow(win) {
   if (isTopWindow(win)) {
     return null;
   }
 
   let docShell = win.QueryInterface(Ci.nsIInterfaceRequestor)
                  .getInterface(Ci.nsIWebNavigation)
                  .QueryInterface(Ci.nsIDocShell);
 
-  if (!docShell.isMozBrowser) {
+  if (!docShell.isMozBrowserOrApp) {
     return win.parent;
   }
 
   let parentDocShell =
-    docShell.getSameTypeParentIgnoreBrowserBoundaries();
+    docShell.getSameTypeParentIgnoreBrowserAndAppBoundaries();
 
   return parentDocShell
           ? parentDocShell.contentViewer.DOMDocument.defaultView
           : null;
 }
 
 exports.getParentWindow = getParentWindow;
 
--- a/devtools/shared/webconsole/network-monitor.js
+++ b/devtools/shared/webconsole/network-monitor.js
@@ -1496,38 +1496,42 @@ NetworkMonitor.prototype = {
  * a NetworkMonitor instance is used by the WebappsActor in the main process to
  * log the network requests for this child process.
  *
  * The main process creates NetworkEventActorProxy instances per request. These
  * send the data to this object using the nsIMessageManager. Here we proxy the
  * data to the WebConsoleActor or to a NetworkEventActor.
  *
  * @constructor
+ * @param number appId
+ *        The web appId of the child process.
  * @param number outerWindowID
  *        The outerWindowID of the TabActor's main window.
  * @param nsIMessageManager messageManager
  *        The nsIMessageManager to use to communicate with the parent process.
  * @param object DebuggerServerConnection
  *        The RDP connection to the client.
  * @param object owner
  *        The WebConsoleActor that is listening for the network requests.
  */
-function NetworkMonitorChild(outerWindowID, messageManager, conn, owner) {
+function NetworkMonitorChild(appId, outerWindowID, messageManager, conn, owner) {
+  this.appId = appId;
   this.outerWindowID = outerWindowID;
   this.conn = conn;
   this.owner = owner;
   this._messageManager = messageManager;
   this._onNewEvent = this._onNewEvent.bind(this);
   this._onUpdateEvent = this._onUpdateEvent.bind(this);
   this._netEvents = new Map();
 }
 
 exports.NetworkMonitorChild = NetworkMonitorChild;
 
 NetworkMonitorChild.prototype = {
+  appId: null,
   owner: null,
   _netEvents: null,
   _saveRequestAndResponseBodies: true,
   _throttleData: null,
 
   get saveRequestAndResponseBodies() {
     return this._saveRequestAndResponseBodies;
   },
@@ -1565,16 +1569,17 @@ NetworkMonitorChild.prototype = {
     });
 
     let mm = this._messageManager;
     mm.addMessageListener("debug:netmonitor:newEvent",
                           this._onNewEvent);
     mm.addMessageListener("debug:netmonitor:updateEvent",
                           this._onUpdateEvent);
     mm.sendAsyncMessage("debug:netmonitor", {
+      appId: this.appId,
       outerWindowID: this.outerWindowID,
       action: "start",
     });
   },
 
   _onNewEvent: DevToolsUtils.makeInfallible(function _onNewEvent(msg) {
     let {id, event} = msg.data;
 
--- a/docshell/base/LoadContext.cpp
+++ b/docshell/base/LoadContext.cpp
@@ -165,16 +165,27 @@ LoadContext::GetIsInIsolatedMozBrowserEl
 
   NS_ENSURE_ARG_POINTER(aIsInIsolatedMozBrowserElement);
 
   *aIsInIsolatedMozBrowserElement = mOriginAttributes.mInIsolatedMozBrowser;
   return NS_OK;
 }
 
 NS_IMETHODIMP
+LoadContext::GetAppId(uint32_t* aAppId)
+{
+  MOZ_ASSERT(mIsNotNull);
+
+  NS_ENSURE_ARG_POINTER(aAppId);
+
+  *aAppId = mOriginAttributes.mAppId;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 LoadContext::GetOriginAttributes(JS::MutableHandleValue aAttrs)
 {
   JSContext* cx = nsContentUtils::GetCurrentJSContext();
   MOZ_ASSERT(cx);
 
   bool ok = ToJSValue(cx, mOriginAttributes, aAttrs);
   NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
   return NS_OK;
--- a/docshell/base/nsDSURIContentListener.cpp
+++ b/docshell/base/nsDSURIContentListener.cpp
@@ -326,17 +326,17 @@ nsDSURIContentListener::CheckOneFrameOpt
 
   // Traverse up the parent chain and stop when we see a docshell whose
   // parent has a system principal, or a docshell corresponding to
   // <iframe mozbrowser>.
   while (NS_SUCCEEDED(
            curDocShellItem->GetParent(getter_AddRefs(parentDocShellItem))) &&
          parentDocShellItem) {
     nsCOMPtr<nsIDocShell> curDocShell = do_QueryInterface(curDocShellItem);
-    if (curDocShell && curDocShell->GetIsMozBrowser()) {
+    if (curDocShell && curDocShell->GetIsMozBrowserOrApp()) {
       break;
     }
 
     bool system = false;
     topDoc = parentDocShellItem->GetDocument();
     if (topDoc) {
       if (NS_SUCCEEDED(
             ssm->IsSystemPrincipal(topDoc->NodePrincipal(), &system)) &&
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -2598,17 +2598,17 @@ nsDocShell::GetFullscreenAllowed(bool* a
   // Otherwise, we have a parent, continue the checking for
   // mozFullscreenAllowed in the parent docshell's ancestors.
   return parent->GetFullscreenAllowed(aFullscreenAllowed);
 }
 
 NS_IMETHODIMP
 nsDocShell::SetFullscreenAllowed(bool aFullscreenAllowed)
 {
-  if (!nsIDocShell::GetIsMozBrowser()) {
+  if (!nsIDocShell::GetIsMozBrowserOrApp()) {
     // Only allow setting of fullscreenAllowed on content/process boundaries.
     // At non-boundaries the fullscreenAllowed attribute is calculated based on
     // whether all enclosing frames have the "mozFullscreenAllowed" attribute
     // set to "true". fullscreenAllowed is set at the process boundaries to
     // propagate the value of the parent's "mozFullscreenAllowed" attribute
     // across process boundaries.
     return NS_ERROR_UNEXPECTED;
   }
@@ -3428,34 +3428,34 @@ nsDocShell::SetDocLoaderParent(nsDocLoad
 }
 
 NS_IMETHODIMP
 nsDocShell::GetSameTypeParent(nsIDocShellTreeItem** aParent)
 {
   NS_ENSURE_ARG_POINTER(aParent);
   *aParent = nullptr;
 
-  if (nsIDocShell::GetIsMozBrowser()) {
+  if (nsIDocShell::GetIsMozBrowserOrApp()) {
     return NS_OK;
   }
 
   nsCOMPtr<nsIDocShellTreeItem> parent =
     do_QueryInterface(GetAsSupports(mParent));
   if (!parent) {
     return NS_OK;
   }
 
   if (parent->ItemType() == mItemType) {
     parent.swap(*aParent);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDocShell::GetSameTypeParentIgnoreBrowserBoundaries(nsIDocShell** aParent)
+nsDocShell::GetSameTypeParentIgnoreBrowserAndAppBoundaries(nsIDocShell** aParent)
 {
   NS_ENSURE_ARG_POINTER(aParent);
   *aParent = nullptr;
 
   nsCOMPtr<nsIDocShellTreeItem> parent =
     do_QueryInterface(GetAsSupports(mParent));
   if (!parent) {
     return NS_OK;
@@ -3499,28 +3499,28 @@ nsDocShell::GetSameTypeRootTreeItem(nsID
       (*aRootTreeItem)->GetSameTypeParent(getter_AddRefs(parent)),
       NS_ERROR_FAILURE);
   }
   NS_ADDREF(*aRootTreeItem);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDocShell::GetSameTypeRootTreeItemIgnoreBrowserBoundaries(nsIDocShell** aRootTreeItem)
+nsDocShell::GetSameTypeRootTreeItemIgnoreBrowserAndAppBoundaries(nsIDocShell ** aRootTreeItem)
 {
     NS_ENSURE_ARG_POINTER(aRootTreeItem);
     *aRootTreeItem = static_cast<nsIDocShell *>(this);
 
     nsCOMPtr<nsIDocShell> parent;
-    NS_ENSURE_SUCCESS(GetSameTypeParentIgnoreBrowserBoundaries(getter_AddRefs(parent)),
+    NS_ENSURE_SUCCESS(GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)),
                       NS_ERROR_FAILURE);
     while (parent) {
       *aRootTreeItem = parent;
       NS_ENSURE_SUCCESS((*aRootTreeItem)->
-        GetSameTypeParentIgnoreBrowserBoundaries(getter_AddRefs(parent)),
+        GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent)),
         NS_ERROR_FAILURE);
     }
     NS_ADDREF(*aRootTreeItem);
     return NS_OK;
 }
 
 /* static */
 bool
@@ -3565,17 +3565,18 @@ nsDocShell::CanAccessItem(nsIDocShellTre
   nsCOMPtr<nsIDocShell> targetDS = do_QueryInterface(aTargetItem);
   nsCOMPtr<nsIDocShell> accessingDS = do_QueryInterface(aAccessingItem);
   if (!targetDS || !accessingDS) {
     // We must be able to convert both to nsIDocShell.
     return false;
   }
 
   if (targetDS->GetIsInIsolatedMozBrowserElement() !=
-        accessingDS->GetIsInIsolatedMozBrowserElement()) {
+        accessingDS->GetIsInIsolatedMozBrowserElement() ||
+      targetDS->GetAppId() != accessingDS->GetAppId()) {
     return false;
   }
 
   nsCOMPtr<nsIDocShellTreeItem> accessingRoot;
   aAccessingItem->GetSameTypeRootTreeItem(getter_AddRefs(accessingRoot));
   nsCOMPtr<nsIDocShell> accessingRootDS = do_QueryInterface(accessingRoot);
 
   nsCOMPtr<nsIDocShellTreeItem> targetRoot;
@@ -3589,31 +3590,31 @@ nsDocShell::CanAccessItem(nsIDocShellTre
 
   // When the first party isolation is on, the top-level docShell may not have
   // the firstPartyDomain in its originAttributes, but its document will have
   // it. So we get the firstPartyDomain from the nodePrincipal of the document
   // before we compare the originAttributes.
   if (OriginAttributes::IsFirstPartyEnabled()) {
     if (accessingDS == accessingRootDS &&
         aAccessingItem->ItemType() == nsIDocShellTreeItem::typeContent &&
-        !accessingDS->GetIsMozBrowser()) {
+        !accessingDS->GetIsMozBrowserOrApp()) {
 
       nsCOMPtr<nsIDocument> accessingDoc = aAccessingItem->GetDocument();
 
       if (accessingDoc) {
         nsCOMPtr<nsIPrincipal> accessingPrincipal = accessingDoc->NodePrincipal();
 
         accessingOA.mFirstPartyDomain =
           BasePrincipal::Cast(accessingPrincipal)->OriginAttributesRef().mFirstPartyDomain;
       }
     }
 
     if (targetDS == targetRootDS &&
         aTargetItem->ItemType() == nsIDocShellTreeItem::typeContent &&
-        !targetDS->GetIsMozBrowser()) {
+        !targetDS->GetIsMozBrowserOrApp()) {
 
       nsCOMPtr<nsIDocument> targetDoc = aAccessingItem->GetDocument();
 
       if (targetDoc) {
         nsCOMPtr<nsIPrincipal> targetPrincipal = targetDoc->NodePrincipal();
 
         targetOA.mFirstPartyDomain =
           BasePrincipal::Cast(targetPrincipal)->OriginAttributesRef().mFirstPartyDomain;
@@ -3800,17 +3801,17 @@ nsDocShell::DoFindItemWithName(const nsA
     do_QueryInterface(GetAsSupports(mParent));
   if (parentAsTreeItem) {
     if (parentAsTreeItem == reqAsTreeItem) {
       return NS_OK;
     }
 
     // If we have a same-type parent, respecting browser and app boundaries.
     // NOTE: Could use GetSameTypeParent if the issues described in bug 1310344 are fixed.
-    if (!GetIsMozBrowser() && parentAsTreeItem->ItemType() == mItemType) {
+    if (!GetIsMozBrowserOrApp() && parentAsTreeItem->ItemType() == mItemType) {
       return parentAsTreeItem->FindItemWithName(
         aName,
         static_cast<nsIDocShellTreeItem*>(this),
         aOriginalRequestor,
         aResult);
     }
   }
 
@@ -5363,23 +5364,33 @@ nsDocShell::LoadErrorPage(nsIURI* aURI, 
   }
   errorPageUrl.AppendLiteral("&c=");
   errorPageUrl.AppendASCII(escapedCharset.get());
 
   nsAutoCString frameType(FrameTypeToString(mFrameType));
   errorPageUrl.AppendLiteral("&f=");
   errorPageUrl.AppendASCII(frameType.get());
 
+  // Append the manifest URL if the error comes from an app.
+  nsString manifestURL;
+  nsresult rv = GetAppManifestURL(manifestURL);
+  if (manifestURL.Length() > 0) {
+    nsCString manifestParam;
+    SAFE_ESCAPE(manifestParam, NS_ConvertUTF16toUTF8(manifestURL), url_Path);
+    errorPageUrl.AppendLiteral("&m=");
+    errorPageUrl.AppendASCII(manifestParam.get());
+  }
+
   // netError.xhtml's getDescription only handles the "d" parameter at the
   // end of the URL, so append it last.
   errorPageUrl.AppendLiteral("&d=");
   errorPageUrl.AppendASCII(escapedDescription.get());
 
   nsCOMPtr<nsIURI> errorPageURI;
-  nsresult rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl);
+  rv = NS_NewURI(getter_AddRefs(errorPageURI), errorPageUrl);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return InternalLoad(errorPageURI, nullptr, false, nullptr,
                       mozilla::net::RP_Default,
                       nsContentUtils::GetSystemPrincipal(), nullptr,
                       INTERNAL_LOAD_FLAGS_NONE, EmptyString(),
                       nullptr, NullString(), nullptr, nullptr, LOAD_ERROR_PAGE,
                       nullptr, true, NullString(), this, nullptr, nullptr,
@@ -6189,17 +6200,19 @@ nsDocShell::SetIsActive(bool aIsActive)
     pshell->SetIsActive(aIsActive);
   }
 
   // Tell the window about it
   if (mScriptGlobal) {
     mScriptGlobal->SetIsBackground(!aIsActive);
     if (nsCOMPtr<nsIDocument> doc = mScriptGlobal->GetExtantDoc()) {
       // Update orientation when the top-level browsing context becomes active.
-      if (aIsActive) {
+      // We make an exception for apps because they currently rely on
+      // orientation locks persisting across browsing contexts.
+      if (aIsActive && !GetIsApp()) {
         nsCOMPtr<nsIDocShellTreeItem> parent;
         GetSameTypeParent(getter_AddRefs(parent));
         if (!parent) {
           // We only care about the top-level browsing context.
           uint16_t orientation = OrientationLock();
           ScreenOrientation::UpdateActiveOrientationLock(orientation);
         }
       }
@@ -6226,17 +6239,17 @@ nsDocShell::SetIsActive(bool aIsActive)
   // children; they handle their state separately.
   nsTObserverArray<nsDocLoader*>::ForwardIterator iter(mChildList);
   while (iter.HasMore()) {
     nsCOMPtr<nsIDocShell> docshell = do_QueryObject(iter.GetNext());
     if (!docshell) {
       continue;
     }
 
-    if (!docshell->GetIsMozBrowser()) {
+    if (!docshell->GetIsMozBrowserOrApp()) {
       docshell->SetIsActive(aIsActive);
     }
   }
 
   // Restart or stop meta refresh timers if necessary
   if (mDisableMetaRefreshWhenInactive) {
     if (mIsActive) {
       ResumeRefreshURIs();
@@ -10538,17 +10551,19 @@ nsDocShell::InternalLoad(nsIURI* aURI,
   if (mTiming && timeBeforeUnload) {
     mTiming->NotifyUnloadAccepted(mCurrentURI);
   }
 
   // Whenever a top-level browsing context is navigated, the user agent MUST
   // lock the orientation of the document to the document's default
   // orientation. We don't explicitly check for a top-level browsing context
   // here because orientation is only set on top-level browsing contexts.
-  if (OrientationLock() != eScreenOrientation_None) {
+  // We make an exception for apps because they currently rely on
+  // orientation locks persisting across browsing contexts.
+  if (OrientationLock() != eScreenOrientation_None && !GetIsApp()) {
 #ifdef DEBUG
     nsCOMPtr<nsIDocShellTreeItem> parent;
     GetSameTypeParent(getter_AddRefs(parent));
     MOZ_ASSERT(!parent);
 #endif
     SetOrientationLock(eScreenOrientation_None);
     if (mIsActive) {
       ScreenOrientation::UpdateActiveOrientationLock(eScreenOrientation_None);
@@ -10891,17 +10906,17 @@ nsDocShell::DoURILoad(nsIURI* aURI,
   }
 
   // We have to do this in case our OriginAttributes are different from the
   // OriginAttributes of the parent document. Or in case there isn't a
   // parent document.
   NeckoOriginAttributes neckoAttrs;
   bool isTopLevelDoc = aContentPolicyType == nsIContentPolicy::TYPE_DOCUMENT &&
                        mItemType == typeContent &&
-                       !GetIsMozBrowser();
+                       !GetIsMozBrowserOrApp();
   neckoAttrs.InheritFromDocShellToNecko(GetOriginAttributes(), isTopLevelDoc, aURI);
   rv = loadInfo->SetOriginAttributes(neckoAttrs);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if (!isSrcdoc) {
     rv = NS_NewChannelInternal(getter_AddRefs(channel),
@@ -14218,19 +14233,26 @@ nsDocShell::SetFrameType(uint32_t aFrame
 /* [infallible] */ NS_IMETHODIMP
 nsDocShell::GetFrameType(uint32_t* aFrameType)
 {
   *aFrameType = mFrameType;
   return NS_OK;
 }
 
 /* [infallible] */ NS_IMETHODIMP
-nsDocShell::GetIsMozBrowser(bool* aIsMozBrowser)
-{
-  *aIsMozBrowser = (mFrameType == FRAME_TYPE_BROWSER);
+nsDocShell::GetIsApp(bool* aIsApp)
+{
+  *aIsApp = (mFrameType == FRAME_TYPE_APP);
+  return NS_OK;
+}
+
+/* [infallible] */ NS_IMETHODIMP
+nsDocShell::GetIsMozBrowserOrApp(bool* aIsMozBrowserOrApp)
+{
+  *aIsMozBrowserOrApp = (mFrameType != FRAME_TYPE_REGULAR);
   return NS_OK;
 }
 
 uint32_t
 nsDocShell::GetInheritedFrameType()
 {
   if (mFrameType != FRAME_TYPE_REGULAR) {
     return mFrameType;
@@ -14264,19 +14286,19 @@ nsDocShell::GetIsInIsolatedMozBrowserEle
              "Isolated mozbrowser should only be true inside browser frames");
   bool result = (GetInheritedFrameType() == FRAME_TYPE_BROWSER) &&
                 mOriginAttributes.mInIsolatedMozBrowser;
   *aIsInIsolatedMozBrowserElement = result;
   return NS_OK;
 }
 
 /* [infallible] */ NS_IMETHODIMP
-nsDocShell::GetIsInMozBrowser(bool* aIsInMozBrowser)
-{
-  *aIsInMozBrowser = (GetInheritedFrameType() == FRAME_TYPE_BROWSER);
+nsDocShell::GetIsInMozBrowserOrApp(bool* aIsInMozBrowserOrApp)
+{
+  *aIsInMozBrowserOrApp = (GetInheritedFrameType() != FRAME_TYPE_REGULAR);
   return NS_OK;
 }
 
 /* [infallible] */ NS_IMETHODIMP
 nsDocShell::GetIsTopLevelContentDocShell(bool* aIsTopLevelContentDocShell)
 {
   *aIsTopLevelContentDocShell = false;
 
@@ -14284,16 +14306,35 @@ nsDocShell::GetIsTopLevelContentDocShell
     nsCOMPtr<nsIDocShellTreeItem> root;
     GetSameTypeRootTreeItem(getter_AddRefs(root));
     *aIsTopLevelContentDocShell = root.get() == static_cast<nsIDocShellTreeItem*>(this);
   }
 
   return NS_OK;
 }
 
+/* [infallible] */ NS_IMETHODIMP
+nsDocShell::GetAppId(uint32_t* aAppId)
+{
+  if (mOriginAttributes.mAppId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+    *aAppId = mOriginAttributes.mAppId;
+    return NS_OK;
+  }
+
+  nsCOMPtr<nsIDocShell> parent;
+  GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent));
+
+  if (!parent) {
+    *aAppId = nsIScriptSecurityManager::NO_APP_ID;
+    return NS_OK;
+  }
+
+  return parent->GetAppId(aAppId);
+}
+
 // Implements nsILoadContext.originAttributes
 NS_IMETHODIMP
 nsDocShell::GetOriginAttributes(JS::MutableHandle<JS::Value> aVal)
 {
   JSContext* cx = nsContentUtils::GetCurrentJSContext();
   MOZ_ASSERT(cx);
 
   return GetOriginAttributes(cx, aVal);
@@ -14381,16 +14422,33 @@ nsDocShell::SetOriginAttributes(JS::Hand
   if (!aOriginAttributes.isObject() || !attrs.Init(aCx, aOriginAttributes)) {
     return NS_ERROR_INVALID_ARG;
   }
 
   return SetOriginAttributes(attrs);
 }
 
 NS_IMETHODIMP
+nsDocShell::GetAppManifestURL(nsAString& aAppManifestURL)
+{
+  uint32_t appId = nsIDocShell::GetAppId();
+  if (appId != nsIScriptSecurityManager::NO_APP_ID &&
+      appId != nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+    nsCOMPtr<nsIAppsService> appsService =
+      do_GetService(APPS_SERVICE_CONTRACTID);
+    NS_ASSERTION(appsService, "No AppsService available");
+    appsService->GetManifestURLByLocalId(appId, aAppManifestURL);
+  } else {
+    aAppManifestURL.SetLength(0);
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDocShell::GetAsyncPanZoomEnabled(bool* aOut)
 {
   if (nsIPresShell* presShell = GetPresShell()) {
     *aOut = presShell->AsyncPanZoomEnabled();
     return NS_OK;
   }
 
   // If we don't have a presShell, fall back to the default platform value of
--- a/docshell/base/nsDocShell.h
+++ b/docshell/base/nsDocShell.h
@@ -774,16 +774,18 @@ protected:
   bool JustStartedNetworkLoad();
 
   nsresult CreatePrincipalFromReferrer(nsIURI* aReferrer,
                                        nsIPrincipal** aResult);
 
   static const nsCString FrameTypeToString(uint32_t aFrameType)
   {
     switch (aFrameType) {
+      case FRAME_TYPE_APP:
+        return NS_LITERAL_CSTRING("app");
       case FRAME_TYPE_BROWSER:
         return NS_LITERAL_CSTRING("browser");
       case FRAME_TYPE_REGULAR:
         return NS_LITERAL_CSTRING("regular");
       default:
         NS_ERROR("Unknown frame type");
         return EmptyCString();
     }
--- a/docshell/base/nsIDocShell.idl
+++ b/docshell/base/nsIDocShell.idl
@@ -788,84 +788,114 @@ interface nsIDocShell : nsIDocShellTreeI
 
   /**
    * Notify all attached observers that the scroll position of some element
    * has changed.
    */
   [noscript] void notifyScrollObservers();
 
   /**
+   * Returns true iff the docshell corresponds to an <iframe mozapp>.
+   */
+  [infallible] readonly attribute boolean isApp;
+
+  /**
    * The type of iframe that this docshell lives.
    */
   const unsigned long FRAME_TYPE_REGULAR = 0;
   const unsigned long FRAME_TYPE_BROWSER = 1;
+  const unsigned long FRAME_TYPE_APP     = 2;
 
   [infallible] attribute unsigned long frameType;
 
   /**
-   * Returns true if this docshell corresponds to an <iframe mozbrowser>.
-   * <xul:browser> returns false here.
+   * Returns true if this docshell corresponds to an <iframe mozbrowser> or
+   * <iframe mozapp>.  <xul:browser> returns false here.
    */
-  [infallible] readonly attribute boolean isMozBrowser;
+  [infallible] readonly attribute boolean isMozBrowserOrApp;
 
   /**
    * Returns true if this docshell corresponds to an isolated <iframe
    * mozbrowser>.
    *
-   * <xul:browser> is not considered to be a mozbrowser element.
-   * <iframe mozbrowser noisolation> does not count as isolated since
-   * isolation is disabled.  Isolation can only be disabled if the
-   * containing document is chrome.
+   * <iframe mozbrowser mozapp> and <xul:browser> are not considered to be
+   * mozbrowser elements.  <iframe mozbrowser noisolation> does not count as
+   * isolated since isolation is disabled.  Isolation can only be disabled if
+   * the containing document is chrome.
    */
   [infallible] readonly attribute boolean isIsolatedMozBrowserElement;
 
   /**
    * Returns true if this docshell corresponds to an isolated <iframe
    * mozbrowser> or if the docshell is contained in an isolated <iframe
    * mozbrowser>.
    *
-   * <xul:browser> is not considered to be a mozbrowser element. <iframe
-   * mozbrowser noisolation> does not count as isolated since isolation is
-   * disabled.  Isolation can only be disabled if the containing document is
-   * chrome.
+   * <iframe mozbrowser mozapp> and <xul:browser> are not considered to be
+   * mozbrowser elements.  <iframe mozbrowser noisolation> does not count as
+   * isolated since isolation is disabled.  Isolation can only be disabled if
+   * the containing document is chrome.
    *
    * Our notion here of "contained in" means: Walk up the docshell hierarchy in
-   * this process until we hit an <iframe mozbrowser> (or until the hierarchy
-   * ends).  Return true iff the docshell we stopped on has
+   * this process until we hit an <iframe mozapp> or <iframe mozbrowser> (or
+   * until the hierarchy ends).  Return true iff the docshell we stopped on has
    * isIsolatedMozBrowserElement == true.
    */
   [infallible] readonly attribute boolean isInIsolatedMozBrowserElement;
 
   /**
-   * Returns true if this docshell corresponds to an <iframe mozbrowser>, or
-   * if this docshell is contained in an <iframe mozbrowser>. <xul:browser>
-   * returns false here.
+   * Returns true if this docshell corresponds to an <iframe mozbrowser> or
+   * <iframe mozapp>, or if this docshell is contained in an <iframe mozbrowser>
+   * or <iframe mozapp>.  <xul:browser> returns false here.
    *
    * To compute this value, we walk up the docshell hierarchy.  If we encounter
-   * a docshell with isMozBrowser before we hit the end of the hierarchy,
+   * a docshell with isMozBrowserOrApp before we hit the end of the hierarchy,
    * we return true.  Otherwise, we return false.
    */
-  [infallible] readonly attribute boolean isInMozBrowser;
+  [infallible] readonly attribute boolean isInMozBrowserOrApp;
 
   /**
    * Returns true if this docshell is the top level content docshell.
    */
   [infallible] readonly attribute boolean isTopLevelContentDocShell;
 
   /**
+   * Returns the id of the app associated with this docshell.  If this docshell
+   * is an <iframe mozbrowser> inside an <iframe mozapp>, we return the app's
+   * appId.
+   *
+   * We compute this value by walking up the docshell hierarchy until we find a
+   * docshell on which origin attributes was set. (ignoring those docshells
+   * where x == UNKNOWN_APP_ID).  We return the app id x.
+   *
+   * If we don't find a docshell with an associated app id in our hierarchy, we
+   * return NO_APP_ID.  We never return UNKNOWN_APP_ID.
+   *
+   * Notice that a docshell may have an associated app even if it returns true
+   * for isBrowserElement!
+   */
+  [infallible] readonly attribute unsigned long appId;
+
+  /**
+   * Return the manifest URL of the app associated with this docshell.
+   *
+   * If there is no associated app in our hierarchy, we return empty string.
+   */
+  readonly attribute DOMString appManifestURL;
+
+  /**
    * Like nsIDocShellTreeItem::GetSameTypeParent, except this ignores <iframe
-   * mozbrowser> boundaries.
+   * mozbrowser> and <iframe mozapp> boundaries.
    */
-  nsIDocShell getSameTypeParentIgnoreBrowserBoundaries();
+  nsIDocShell getSameTypeParentIgnoreBrowserAndAppBoundaries();
 
   /**
    * Like nsIDocShellTreeItem::GetSameTypeRootTreeItem, except this ignores
-   * <iframe mozbrowser> boundaries.
+   * <iframe mozbrowser> and <iframe mozapp> boundaries.
    */
-  nsIDocShell getSameTypeRootTreeItemIgnoreBrowserBoundaries();
+  nsIDocShell getSameTypeRootTreeItemIgnoreBrowserAndAppBoundaries();
 
   /**
    * True iff asynchronous panning and zooming is enabled for this
    * docshell.
    */
   readonly attribute bool asyncPanZoomEnabled;
 
   /**
--- a/docshell/base/nsIDocShellTreeItem.idl
+++ b/docshell/base/nsIDocShellTreeItem.idl
@@ -52,19 +52,20 @@ interface nsIDocShellTreeItem : nsISuppo
 
 	/*
 	Parent DocShell.
 	*/
 	readonly attribute nsIDocShellTreeItem parent;
 
 	/*
 	This getter returns the same thing parent does however if the parent
-	is of a different itemType, or if the parent is an <iframe mozbrowser>.
-	It will instead return nullptr.  This call is a convience function for
-	Ithose wishing to not cross the boundaries at which item types change.
+	is of a different itemType, or if the parent is an <iframe mozbrowser>
+	or <iframe mozapp>, it will instead return nullptr.  This call is a
+	convience function for those wishing to not cross the boundaries at
+	which item types change.
 	*/
 	readonly attribute nsIDocShellTreeItem sameTypeParent;
 
 	/*
 	Returns the root DocShellTreeItem.  This is a convience equivalent to 
 	getting the parent and its parent until there isn't a parent.
 	*/
 	readonly attribute nsIDocShellTreeItem rootTreeItem;
--- a/docshell/base/nsILoadContext.idl
+++ b/docshell/base/nsILoadContext.idl
@@ -101,24 +101,30 @@ interface nsILoadContext : nsISupports
 
   /**
    * Set the remote tabs state of the load context, meant to be used internally.
    */
   [noscript] void SetRemoteTabs(in boolean aUseRemoteTabs);
 
   /**
    * Returns true iff the load is occurring inside an isolated mozbrowser
-   * element. <xul:browser> is not considered to be a mozbrowser element.
-   * <iframe mozbrowser noisolation> does not count as isolated since
-   * isolation is disabled.  Isolation can only be disabled if the
-   * containing document is chrome.
+   * element. <iframe mozbrowser mozapp> and <xul:browser> are not considered to
+   * be mozbrowser elements.  <iframe mozbrowser noisolation> does not count as
+   * isolated since isolation is disabled.  Isolation can only be disabled if
+   * the containing document is chrome.
    */
   readonly attribute boolean isInIsolatedMozBrowserElement;
 
   /**
+   * Returns the app id of the app the load is occurring is in. Returns
+   * nsIScriptSecurityManager::NO_APP_ID if the load is not part of an app.
+   */
+  readonly attribute unsigned long appId;
+
+  /**
    * A dictionary of the non-default origin attributes associated with this
    * nsILoadContext.
    */
   readonly attribute jsval originAttributes;
 
 %{C++
 #ifdef MOZILLA_INTERNAL_API
   /**
--- a/dom/apps/PermissionsTable.jsm
+++ b/dom/apps/PermissionsTable.jsm
@@ -228,16 +228,21 @@ this.PermissionsTable =  { geolocation: 
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "time": {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
+                           "embed-apps": {
+                             app: DENY_ACTION,
+                             privileged: DENY_ACTION,
+                             certified: ALLOW_ACTION
+                           },
                            "background-sensors": {
                              app: DENY_ACTION,
                              privileged: DENY_ACTION,
                              certified: ALLOW_ACTION
                            },
                            "audio-channel-normal": {
                              app: ALLOW_ACTION,
                              privileged: ALLOW_ACTION,
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -63,16 +63,17 @@
 #include "nsComponentManagerUtils.h"
 #include "nsIStringStream.h"
 #include "nsIHttpChannel.h"
 #include "nsIHttpChannelInternal.h"
 #include "TimeManager.h"
 #include "DeviceStorage.h"
 #include "nsStreamUtils.h"
 #include "nsIAppsService.h"
+#include "mozIApplication.h"
 #include "WidgetUtils.h"
 #include "nsIPresentationService.h"
 
 #include "mozilla/dom/MediaDevices.h"
 #include "MediaManager.h"
 
 #ifdef MOZ_AUDIO_CHANNEL_MANAGER
 #include "AudioChannelManager.h"
--- a/dom/base/ThirdPartyUtil.cpp
+++ b/dom/base/ThirdPartyUtil.cpp
@@ -12,17 +12,16 @@
 #include "nsIHttpChannelInternal.h"
 #include "nsIDOMWindow.h"
 #include "nsILoadContext.h"
 #include "nsIPrincipal.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIURI.h"
 #include "nsThreadUtils.h"
 #include "mozilla/Logging.h"
-#include "nsPIDOMWindow.h"
 
 NS_IMPL_ISUPPORTS(ThirdPartyUtil, mozIThirdPartyUtil)
 
 //
 // MOZ_LOG=thirdPartyUtil:5
 //
 static mozilla::LazyLogModule gThirdPartyLog("thirdPartyUtil");
 #undef LOG
@@ -141,17 +140,17 @@ ThirdPartyUtil::IsThirdPartyWindow(mozID
       return NS_OK;
     }
   }
 
   nsCOMPtr<nsPIDOMWindowOuter> current = nsPIDOMWindowOuter::From(aWindow), parent;
   nsCOMPtr<nsIURI> parentURI;
   do {
     // We use GetScriptableParent rather than GetParent because we consider
-    // <iframe mozbrowser> to be a top-level frame.
+    // <iframe mozbrowser/mozapp> to be a top-level frame.
     parent = current->GetScriptableParent();
     if (SameCOMIdentity(parent, current)) {
       // We're at the topmost content window. We already know the answer.
       *aResult = false;
       return NS_OK;
     }
 
     rv = GetURIFromWindow(parent, getter_AddRefs(parentURI));
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -6395,17 +6395,17 @@ nsContentUtils::IsUserFocusIgnored(nsINo
     return false;
   }
 
   // Check if our mozbrowser iframe ancestors has ignoreuserfocus attribute.
   while (aNode) {
     nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aNode);
     if (browserFrame &&
         aNode->AsElement()->HasAttr(kNameSpaceID_None, nsGkAtoms::ignoreuserfocus) &&
-        browserFrame->GetReallyIsBrowser()) {
+        browserFrame->GetReallyIsBrowserOrApp()) {
       return true;
     }
     nsPIDOMWindowOuter* win = aNode->OwnerDoc()->GetWindow();
     aNode = win ? win->GetFrameElementInternal() : nullptr;
   }
 
   return false;
 }
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -8,16 +8,17 @@
  * Class for managing loading of a subframe (creation of the docshell,
  * handling of loads in it, recursion-checking).
  */
 
 #include "base/basictypes.h"
 
 #include "prenv.h"
 
+#include "mozIApplication.h"
 #include "nsDocShell.h"
 #include "nsIAppsService.h"
 #include "nsIDOMHTMLIFrameElement.h"
 #include "nsIDOMHTMLFrameElement.h"
 #include "nsIDOMMozBrowserFrame.h"
 #include "nsIDOMWindow.h"
 #include "nsIPresShell.h"
 #include "nsIContentInlines.h"
@@ -83,16 +84,17 @@
 #include "mozilla/AsyncEventDispatcher.h"
 #include "mozilla/BasePrincipal.h"
 #include "mozilla/GuardObjects.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Unused.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layout/RenderFrameParent.h"
+#include "nsIAppsService.h"
 #include "GeckoProfiler.h"
 
 #include "jsapi.h"
 #include "mozilla/dom/HTMLIFrameElement.h"
 #include "nsSandboxFlags.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
 
 #include "mozilla/dom/ipc/StructuredCloneData.h"
@@ -1059,17 +1061,18 @@ nsFrameLoader::SwapWithOtherRemoteLoader
 
   nsIPresShell* ourShell = ourDoc->GetShell();
   nsIPresShell* otherShell = otherDoc->GetShell();
   if (!ourShell || !otherShell) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   if (mRemoteBrowser->IsIsolatedMozBrowserElement() !=
-      aOther->mRemoteBrowser->IsIsolatedMozBrowserElement()) {
+      aOther->mRemoteBrowser->IsIsolatedMozBrowserElement() ||
+      mRemoteBrowser->HasOwnApp() != aOther->mRemoteBrowser->HasOwnApp()) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   // When we swap docShells, maybe we have to deal with a new page created just
   // for this operation. In this case, the browser code should already have set
   // the correct userContextId attribute value in the owning XULElement, but our
   // docShell, that has been created way before) doesn't know that that
   // happened.
@@ -1131,20 +1134,20 @@ nsFrameLoader::SwapWithOtherRemoteLoader
   nsCOMPtr<nsIBrowserDOMWindow> browserDOMWindow =
     mRemoteBrowser->GetBrowserDOMWindow();
 
   if (!!otherBrowserDOMWindow != !!browserDOMWindow) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   // Destroy browser frame scripts for content leaving a frame with browser API
-  if (OwnerIsMozBrowserFrame() && !aOther->OwnerIsMozBrowserFrame()) {
+  if (OwnerIsMozBrowserOrAppFrame() && !aOther->OwnerIsMozBrowserOrAppFrame()) {
     DestroyBrowserFrameScripts();
   }
-  if (!OwnerIsMozBrowserFrame() && aOther->OwnerIsMozBrowserFrame()) {
+  if (!OwnerIsMozBrowserOrAppFrame() && aOther->OwnerIsMozBrowserOrAppFrame()) {
     aOther->DestroyBrowserFrameScripts();
   }
 
   aOther->mRemoteBrowser->SetBrowserDOMWindow(browserDOMWindow);
   mRemoteBrowser->SetBrowserDOMWindow(otherBrowserDOMWindow);
 
   // Native plugin windows used by this remote content need to be reparented.
   if (nsPIDOMWindowOuter* newWin = ourDoc->GetWindow()) {
@@ -1306,22 +1309,22 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
                         otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::srcdoc);
   if (ourHasSrcdoc || otherHasSrcdoc) {
     // Ignore this case entirely for now, since we support XUL <-> HTML swapping
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   bool ourFullscreenAllowed =
     ourContent->IsXULElement() ||
-    (OwnerIsMozBrowserFrame() &&
+    (OwnerIsMozBrowserOrAppFrame() &&
       (ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowfullscreen) ||
        ourContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen)));
   bool otherFullscreenAllowed =
     otherContent->IsXULElement() ||
-    (aOther->OwnerIsMozBrowserFrame() &&
+    (aOther->OwnerIsMozBrowserOrAppFrame() &&
       (otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowfullscreen) ||
        otherContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen)));
   if (ourFullscreenAllowed != otherFullscreenAllowed) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   // Divert to a separate path for the remaining steps in the remote case
   if (IsRemoteFrame()) {
@@ -1457,17 +1460,18 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
 
   nsIPresShell* ourShell = ourDoc->GetShell();
   nsIPresShell* otherShell = otherDoc->GetShell();
   if (!ourShell || !otherShell) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   if (ourDocshell->GetIsIsolatedMozBrowserElement() !=
-      otherDocshell->GetIsIsolatedMozBrowserElement()) {
+      otherDocshell->GetIsIsolatedMozBrowserElement() ||
+      ourDocshell->GetIsApp() != otherDocshell->GetIsApp()) {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   // When we swap docShells, maybe we have to deal with a new page created just
   // for this operation. In this case, the browser code should already have set
   // the correct userContextId attribute value in the owning XULElement, but our
   // docShell, that has been created way before) doesn't know that that
   // happened.
@@ -1507,20 +1511,20 @@ nsFrameLoader::SwapWithOtherLoader(nsFra
 
   // OK.  First begin to swap the docshells in the two nsIFrames
   rv = ourFrameFrame->BeginSwapDocShells(otherFrame);
   if (NS_FAILED(rv)) {
     return rv;
   }
 
   // Destroy browser frame scripts for content leaving a frame with browser API
-  if (OwnerIsMozBrowserFrame() && !aOther->OwnerIsMozBrowserFrame()) {
+  if (OwnerIsMozBrowserOrAppFrame() && !aOther->OwnerIsMozBrowserOrAppFrame()) {
     DestroyBrowserFrameScripts();
   }
-  if (!OwnerIsMozBrowserFrame() && aOther->OwnerIsMozBrowserFrame()) {
+  if (!OwnerIsMozBrowserOrAppFrame() && aOther->OwnerIsMozBrowserOrAppFrame()) {
     aOther->DestroyBrowserFrameScripts();
   }
 
   // Now move the docshells to the right docshell trees.  Note that this
   // resets their treeowners to null.
   ourParentItem->RemoveChild(ourDocshell);
   otherParentItem->RemoveChild(otherDocshell);
   if (ourType == nsIDocShellTreeItem::typeContent) {
@@ -1869,28 +1873,41 @@ nsFrameLoader::SetOwnerContent(Element* 
   }
   mOwnerContent = aContent;
   if (RenderFrameParent* rfp = GetCurrentRenderFrame()) {
     rfp->OwnerContentChanged(aContent);
   }
 }
 
 bool
-nsFrameLoader::OwnerIsMozBrowserFrame()
+nsFrameLoader::OwnerIsMozBrowserOrAppFrame()
 {
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
-  return browserFrame ? browserFrame->GetReallyIsBrowser() : false;
+  return browserFrame ? browserFrame->GetReallyIsBrowserOrApp() : false;
 }
 
 // The xpcom getter version
 NS_IMETHODIMP
-nsFrameLoader::GetOwnerIsMozBrowserFrame(bool* aResult)
+nsFrameLoader::GetOwnerIsMozBrowserOrAppFrame(bool* aResult)
+{
+  *aResult = OwnerIsMozBrowserOrAppFrame();
+  return NS_OK;
+}
+
+bool
+nsFrameLoader::OwnerIsAppFrame()
 {
-  *aResult = OwnerIsMozBrowserFrame();
-  return NS_OK;
+  nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
+  return browserFrame ? browserFrame->GetReallyIsApp() : false;
+}
+
+bool
+nsFrameLoader::OwnerIsMozBrowserFrame()
+{
+  return OwnerIsMozBrowserOrAppFrame() && !OwnerIsAppFrame();
 }
 
 bool
 nsFrameLoader::OwnerIsIsolatedMozBrowserFrame()
 {
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
   if (!browserFrame) {
     return false;
@@ -1903,16 +1920,65 @@ nsFrameLoader::OwnerIsIsolatedMozBrowser
   bool isolated = browserFrame->GetIsolated();
   if (isolated) {
     return true;
   }
 
   return false;
 }
 
+void
+nsFrameLoader::GetOwnerAppManifestURL(nsAString& aOut)
+{
+  aOut.Truncate();
+  nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
+  if (browserFrame) {
+    browserFrame->GetAppManifestURL(aOut);
+  }
+}
+
+already_AddRefed<mozIApplication>
+nsFrameLoader::GetOwnApp()
+{
+  nsAutoString manifest;
+  GetOwnerAppManifestURL(manifest);
+  if (manifest.IsEmpty()) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(appsService, nullptr);
+
+  nsCOMPtr<mozIApplication> app;
+  appsService->GetAppByManifestURL(manifest, getter_AddRefs(app));
+
+  return app.forget();
+}
+
+already_AddRefed<mozIApplication>
+nsFrameLoader::GetContainingApp()
+{
+  // See if our owner content's principal has an associated app.
+  uint32_t appId = mOwnerContent->NodePrincipal()->GetAppId();
+  MOZ_ASSERT(appId != nsIScriptSecurityManager::UNKNOWN_APP_ID);
+
+  if (appId == nsIScriptSecurityManager::NO_APP_ID ||
+      appId == nsIScriptSecurityManager::UNKNOWN_APP_ID) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(appsService, nullptr);
+
+  nsCOMPtr<mozIApplication> app;
+  appsService->GetAppByLocalId(appId, getter_AddRefs(app));
+
+  return app.forget();
+}
+
 bool
 nsFrameLoader::ShouldUseRemoteProcess()
 {
   if (PR_GetEnv("MOZ_DISABLE_OOP_TABS") ||
       Preferences::GetBool("dom.ipc.tabs.disabled", false)) {
     return false;
   }
 
@@ -1926,25 +1992,25 @@ nsFrameLoader::ShouldUseRemoteProcess()
   if (XRE_IsContentProcess() &&
       !(PR_GetEnv("MOZ_NESTED_OOP_TABS") ||
         Preferences::GetBool("dom.ipc.tabs.nested.enabled", false))) {
     return false;
   }
 
   // If we're an <iframe mozbrowser> and we don't have a "remote" attribute,
   // fall back to the default.
-  if (OwnerIsMozBrowserFrame() &&
+  if (OwnerIsMozBrowserOrAppFrame() &&
       !mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::Remote)) {
 
     return Preferences::GetBool("dom.ipc.browser_frames.oop_by_default", false);
   }
 
   // Otherwise, we're remote if we have "remote=true" and we're either a
   // browser frame or a XUL element.
-  return (OwnerIsMozBrowserFrame() ||
+  return (OwnerIsMozBrowserOrAppFrame() ||
           mOwnerContent->GetNameSpaceID() == kNameSpaceID_XUL) &&
          mOwnerContent->AttrValueIs(kNameSpaceID_None,
                                     nsGkAtoms::Remote,
                                     nsGkAtoms::_true,
                                     eCaseMatters);
 }
 
 bool
@@ -2112,23 +2178,23 @@ nsFrameLoader::MaybeCreateDocShell()
   DocShellOriginAttributes attrs;
   if (docShell->ItemType() == mDocShell->ItemType()) {
     attrs = nsDocShell::Cast(docShell)->GetOriginAttributes();
   }
 
   // Inherit origin attributes from parent document if
   // 1. It's in a content docshell.
   // 2. its nodePrincipal is not a SystemPrincipal.
-  // 3. It's not a mozbrowser frame.
+  // 3. It's not a mozbrowser nor mozapp frame.
   //
   // For example, firstPartyDomain is computed from top-level document, it
   // doesn't exist in the top-level docshell.
   if (parentType == nsIDocShellTreeItem::typeContent &&
       !nsContentUtils::IsSystemPrincipal(doc->NodePrincipal()) &&
-      !OwnerIsMozBrowserFrame()) {
+      !OwnerIsMozBrowserOrAppFrame()) {
     PrincipalOriginAttributes poa = BasePrincipal::Cast(doc->NodePrincipal())->OriginAttributesRef();
 
     // Assert on the firstPartyDomain from top-level docshell should be empty
     if (mIsTopLevelContent) {
       MOZ_ASSERT(attrs.mFirstPartyDomain.IsEmpty(),
                  "top-level docshell shouldn't have firstPartyDomain attribute.");
     }
 
@@ -2141,18 +2207,43 @@ nsFrameLoader::MaybeCreateDocShell()
     MOZ_ASSERT(attrs.mInIsolatedMozBrowser == poa.mInIsolatedMozBrowser,
               "docshell and document should have the same inIsolatedMozBrowser attribute.");
     MOZ_ASSERT(attrs.mPrivateBrowsingId == poa.mPrivateBrowsingId,
               "docshell and document should have the same privateBrowsingId attribute.");
 
     attrs.InheritFromDocToChildDocShell(poa);
   }
 
+  if (OwnerIsAppFrame()) {
+    // You can't be both an app and a browser frame.
+    MOZ_ASSERT(!OwnerIsMozBrowserFrame());
+
+    nsCOMPtr<mozIApplication> ownApp = GetOwnApp();
+    MOZ_ASSERT(ownApp);
+    uint32_t ownAppId = nsIScriptSecurityManager::NO_APP_ID;
+    if (ownApp) {
+      NS_ENSURE_SUCCESS(ownApp->GetLocalId(&ownAppId), NS_ERROR_FAILURE);
+    }
+
+    attrs.mAppId = ownAppId;
+    mDocShell->SetFrameType(nsIDocShell::FRAME_TYPE_APP);
+  }
+
   if (OwnerIsMozBrowserFrame()) {
-    attrs.mAppId = nsIScriptSecurityManager::NO_APP_ID;
+    // You can't be both a browser and an app frame.
+    MOZ_ASSERT(!OwnerIsAppFrame());
+
+    nsCOMPtr<mozIApplication> containingApp = GetContainingApp();
+    uint32_t containingAppId = nsIScriptSecurityManager::NO_APP_ID;
+    if (containingApp) {
+      NS_ENSURE_SUCCESS(containingApp->GetLocalId(&containingAppId),
+                        NS_ERROR_FAILURE);
+    }
+
+    attrs.mAppId = containingAppId;
     attrs.mInIsolatedMozBrowser = OwnerIsIsolatedMozBrowserFrame();
     mDocShell->SetFrameType(nsIDocShell::FRAME_TYPE_BROWSER);
   }
 
   // Apply sandbox flags even if our owner is not an iframe, as this copies
   // flags from our owning content's owning document.
   // Note: ApplySandboxFlags should be called after mDocShell->SetFrameType
   // because we need to get the correct presentation URL in ApplySandboxFlags.
@@ -2174,17 +2265,17 @@ nsFrameLoader::MaybeCreateDocShell()
   NS_ENSURE_STATE(parentContext);
 
   rv = parentContext->GetUsePrivateBrowsing(&isPrivate);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
   attrs.SyncAttributesWithPrivateBrowsing(isPrivate);
 
-  if (OwnerIsMozBrowserFrame()) {
+  if (OwnerIsMozBrowserOrAppFrame()) {
     // For inproc frames, set the docshell properties.
     nsAutoString name;
     if (mOwnerContent->GetAttr(kNameSpaceID_None, nsGkAtoms::name, name)) {
       docShell->SetName(name);
     }
     mDocShell->SetFullscreenAllowed(
       mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::allowfullscreen) ||
       mOwnerContent->HasAttr(kNameSpaceID_None, nsGkAtoms::mozallowfullscreen));
@@ -2573,17 +2664,17 @@ nsFrameLoader::TryRemoteBrowser()
 
   if (openingTab &&
       openingTab->Manager() &&
       openingTab->Manager()->IsContentParent()) {
     openerContentParent = openingTab->Manager()->AsContentParent();
   }
 
   // <iframe mozbrowser> gets to skip these checks.
-  if (!OwnerIsMozBrowserFrame()) {
+  if (!OwnerIsMozBrowserOrAppFrame()) {
     if (parentDocShell->ItemType() != nsIDocShellTreeItem::typeChrome) {
       return false;
     }
 
     if (!mOwnerContent->IsXULElement()) {
       return false;
     }
 
@@ -2614,18 +2705,18 @@ nsFrameLoader::TryRemoteBrowser()
   PROFILER_LABEL("nsFrameLoader", "CreateRemoteBrowser",
     js::ProfileEntry::Category::OTHER);
 
   MutableTabContext context;
   nsresult rv = GetNewTabContext(&context);
   NS_ENSURE_SUCCESS(rv, false);
 
   nsCOMPtr<Element> ownerElement = mOwnerContent;
-  mRemoteBrowser = ContentParent::CreateBrowser(context, ownerElement, openerContentParent,
-                                                mFreshProcess);
+  mRemoteBrowser = ContentParent::CreateBrowserOrApp(context, ownerElement,
+                                                     openerContentParent, mFreshProcess);
   if (!mRemoteBrowser) {
     return false;
   }
 
   MaybeUpdatePrimaryTabParent(eTabParentChanged);
 
   mChildID = mRemoteBrowser->Manager()->ChildID();
 
@@ -2878,17 +2969,17 @@ nsFrameLoader::EnsureMessageManager()
 {
   NS_ENSURE_STATE(mOwnerContent);
 
   if (mMessageManager) {
     return NS_OK;
   }
 
   if (!mIsTopLevelContent &&
-      !OwnerIsMozBrowserFrame() &&
+      !OwnerIsMozBrowserOrAppFrame() &&
       !IsRemoteFrame() &&
       !(mOwnerContent->IsXULElement() &&
         mOwnerContent->AttrValueIs(kNameSpaceID_None,
                                    nsGkAtoms::forcemessagemanager,
                                    nsGkAtoms::_true, eCaseMatters))) {
     return NS_OK;
   }
 
@@ -3193,17 +3284,17 @@ nsFrameLoader::GetLoadContext(nsILoadCon
   }
   loadContext.forget(aLoadContext);
   return NS_OK;
 }
 
 void
 nsFrameLoader::InitializeBrowserAPI()
 {
-  if (!OwnerIsMozBrowserFrame()) {
+  if (!OwnerIsMozBrowserOrAppFrame()) {
     return;
   }
   if (!IsRemoteFrame()) {
     nsresult rv = EnsureMessageManager();
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return;
     }
     if (mMessageManager) {
@@ -3217,17 +3308,17 @@ nsFrameLoader::InitializeBrowserAPI()
   if (browserFrame) {
     browserFrame->InitializeBrowserAPI();
   }
 }
 
 void
 nsFrameLoader::DestroyBrowserFrameScripts()
 {
-  if (!OwnerIsMozBrowserFrame()) {
+  if (!OwnerIsMozBrowserOrAppFrame()) {
     return;
   }
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwnerContent);
   if (browserFrame) {
     browserFrame->DestroyBrowserFrameScripts();
   }
 }
 
@@ -3298,21 +3389,34 @@ nsFrameLoader::MaybeUpdatePrimaryTabPare
     }
   }
 }
 
 nsresult
 nsFrameLoader::GetNewTabContext(MutableTabContext* aTabContext,
                                 nsIURI* aURI)
 {
+  nsCOMPtr<mozIApplication> ownApp = GetOwnApp();
+  nsCOMPtr<mozIApplication> containingApp = GetContainingApp();
   DocShellOriginAttributes attrs;
   attrs.mInIsolatedMozBrowser = OwnerIsIsolatedMozBrowserFrame();
   nsresult rv;
 
-  attrs.mAppId = nsIScriptSecurityManager::NO_APP_ID;
+  // Get the AppId from ownApp
+  uint32_t appId = nsIScriptSecurityManager::NO_APP_ID;
+  if (ownApp) {
+    rv = ownApp->GetLocalId(&appId);
+    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_STATE(appId != nsIScriptSecurityManager::NO_APP_ID);
+  } else if (containingApp) {
+    rv = containingApp->GetLocalId(&appId);
+    NS_ENSURE_SUCCESS(rv, rv);
+    NS_ENSURE_STATE(appId != nsIScriptSecurityManager::NO_APP_ID);
+  }
+  attrs.mAppId = appId;
 
   // 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,
@@ -3337,16 +3441,18 @@ nsFrameLoader::GetNewTabContext(MutableT
       showFocusRings =
         root->ShowFocusRings() ? UIStateChangeType_Set : UIStateChangeType_Clear;
     }
   }
 
   bool tabContextUpdated =
     aTabContext->SetTabContext(OwnerIsMozBrowserFrame(),
                                mIsPrerendered,
+                               ownApp,
+                               containingApp,
                                showAccelerators,
                                showFocusRings,
                                attrs,
                                presentationURLStr);
   NS_ENSURE_STATE(tabContextUpdated);
 
   return NS_OK;
 }
--- a/dom/base/nsFrameLoader.h
+++ b/dom/base/nsFrameLoader.h
@@ -31,16 +31,17 @@ class nsIURI;
 class nsSubDocumentFrame;
 class nsView;
 class nsIInProcessContentFrameMessageManager;
 class AutoResetInShow;
 class AutoResetInFrameSwap;
 class nsITabParent;
 class nsIDocShellTreeItem;
 class nsIDocShellTreeOwner;
+class mozIApplication;
 
 namespace mozilla {
 
 class DocShellOriginAttributes;
 
 namespace dom {
 class ContentParent;
 class PBrowserParent;
@@ -237,16 +238,30 @@ private:
   bool ShouldUseRemoteProcess();
 
   /**
    * Return true if the frame is a remote frame. Return false otherwise
    */
   bool IsRemoteFrame();
 
   /**
+   * Is this a frameloader for a bona fide <iframe mozbrowser> or
+   * <iframe mozapp>?  (I.e., does the frame return true for
+   * nsIMozBrowserFrame::GetReallyIsBrowserOrApp()?)
+   * <xul:browser> is not a mozbrowser or app, so this is false for that case.
+   */
+  bool OwnerIsMozBrowserOrAppFrame();
+
+  /**
+   * Is this a frameloader for a bona fide <iframe mozapp>?  (I.e., does the
+   * frame return true for nsIMozBrowserFrame::GetReallyIsApp()?)
+   */
+  bool OwnerIsAppFrame();
+
+  /**
    * Is this a frame loader for a bona fide <iframe mozbrowser>?
    * <xul:browser> is not a mozbrowser, so this is false for that case.
    */
   bool OwnerIsMozBrowserFrame();
 
   /**
    * Is this a frame loader for an isolated <iframe mozbrowser>?
    *
@@ -258,16 +273,28 @@ private:
 
   /**
    * Get our owning element's app manifest URL, or return the empty string if
    * our owning element doesn't have an app manifest URL.
    */
   void GetOwnerAppManifestURL(nsAString& aOut);
 
   /**
+   * Get the app for our frame.  This is the app whose manifest is returned by
+   * GetOwnerAppManifestURL.
+   */
+  already_AddRefed<mozIApplication> GetOwnApp();
+
+  /**
+   * Get the app which contains this frame.  This is the app associated with
+   * the frame element's principal.
+   */
+  already_AddRefed<mozIApplication> GetContainingApp();
+
+  /**
    * If we are an IPC frame, set mRemoteFrame. Otherwise, create and
    * initialize mDocShell.
    */
   nsresult MaybeCreateDocShell();
   nsresult EnsureMessageManager();
   nsresult ReallyLoadFrameScripts();
 
   // Updates the subdocument position and size. This gets called only
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -96,16 +96,17 @@ GK_ATOM(always, "always")
 GK_ATOM(ancestor, "ancestor")
 GK_ATOM(ancestorOrSelf, "ancestor-or-self")
 GK_ATOM(anchor, "anchor")
 GK_ATOM(_and, "and")
 GK_ATOM(animations, "animations")
 GK_ATOM(anonid, "anonid")
 GK_ATOM(anonlocation, "anonlocation")
 GK_ATOM(any, "any")
+GK_ATOM(mozapp, "mozapp")
 GK_ATOM(applet, "applet")
 GK_ATOM(applyImports, "apply-imports")
 GK_ATOM(applyTemplates, "apply-templates")
 GK_ATOM(mozapptype, "mozapptype")
 GK_ATOM(archive, "archive")
 GK_ATOM(area, "area")
 GK_ATOM(arrow, "arrow")
 GK_ATOM(article, "article")
@@ -986,16 +987,17 @@ GK_ATOM(pageincrement, "pageincrement")
 GK_ATOM(pagex, "pagex")
 GK_ATOM(pagey, "pagey")
 GK_ATOM(paint_order, "paint-order")
 GK_ATOM(palettename, "palettename")
 GK_ATOM(panel, "panel")
 GK_ATOM(param, "param")
 GK_ATOM(parameter, "parameter")
 GK_ATOM(parent, "parent")
+GK_ATOM(parentapp, "parentapp")
 GK_ATOM(parentfocused, "parentfocused")
 GK_ATOM(parsetype, "parsetype")
 GK_ATOM(password, "password")
 GK_ATOM(pattern, "pattern")
 GK_ATOM(patternSeparator, "pattern-separator")
 GK_ATOM(perMille, "per-mille")
 GK_ATOM(percent, "percent")
 GK_ATOM(persist, "persist")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -4195,17 +4195,17 @@ nsGlobalWindow::GetParentOuter()
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   if (!mDocShell) {
     return nullptr;
   }
 
   nsCOMPtr<nsPIDOMWindowOuter> parent;
-  if (mDocShell->GetIsMozBrowser()) {
+  if (mDocShell->GetIsMozBrowserOrApp()) {
     parent = AsOuter();
   } else {
     parent = GetParent();
   }
 
   return parent.forget();
 }
 
@@ -4253,17 +4253,17 @@ nsGlobalWindow::GetParent()
 {
   MOZ_ASSERT(IsOuterWindow());
 
   if (!mDocShell) {
     return nullptr;
   }
 
   nsCOMPtr<nsIDocShell> parent;
-  mDocShell->GetSameTypeParentIgnoreBrowserBoundaries(getter_AddRefs(parent));
+  mDocShell->GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent));
 
   if (parent) {
     nsCOMPtr<nsPIDOMWindowOuter> win = parent->GetWindow();
     return win.forget();
   }
 
   nsCOMPtr<nsPIDOMWindowOuter> win(AsOuter());
   return win.forget();
@@ -4372,19 +4372,19 @@ nsGlobalWindow::GetContentInternal(Error
 
   // First check for a named frame named "content"
   nsCOMPtr<nsPIDOMWindowOuter> domWindow =
     GetChildWindow(NS_LITERAL_STRING("content"));
   if (domWindow) {
     return domWindow.forget();
   }
 
-  // If we're contained in <iframe mozbrowser>, then GetContent is the same as
-  // window.top.
-  if (mDocShell && mDocShell->GetIsInMozBrowser()) {
+  // If we're contained in <iframe mozbrowser> or <iframe mozapp>, then
+  // GetContent is the same as window.top.
+  if (mDocShell && mDocShell->GetIsInMozBrowserOrApp()) {
     return GetTopOuter();
   }
 
   nsCOMPtr<nsIDocShellTreeItem> primaryContent;
   if (aUnprivilegedCaller) {
     // If we're called by non-chrome code, make sure we don't return
     // the primary content window if the calling tab is hidden. In
     // such a case we return the same-type root in the hidden tab,
@@ -7695,17 +7695,17 @@ void
 nsGlobalWindow::ResizeToOuter(int32_t aWidth, int32_t aHeight, ErrorResult& aError, bool aCallerIsChrome)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   /*
    * If caller is a browser-element then dispatch a resize event to
    * the embedder.
    */
-  if (mDocShell && mDocShell->GetIsMozBrowser()) {
+  if (mDocShell && mDocShell->GetIsMozBrowserOrApp()) {
     CSSIntSize size(aWidth, aHeight);
     if (!DispatchResizeEvent(size)) {
       // The embedder chose to prevent the default action for this
       // event, so let's not resize this window after all...
       return;
     }
   }
 
@@ -7745,17 +7745,17 @@ nsGlobalWindow::ResizeByOuter(int32_t aW
                               ErrorResult& aError, bool aCallerIsChrome)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   /*
    * If caller is a browser-element then dispatch a resize event to
    * parent.
    */
-  if (mDocShell && mDocShell->GetIsMozBrowser()) {
+  if (mDocShell && mDocShell->GetIsMozBrowserOrApp()) {
     CSSIntSize size;
     if (NS_FAILED(GetInnerSize(size))) {
       return;
     }
 
     size.width += aWidthDif;
     size.height += aHeightDif;
 
@@ -8714,17 +8714,17 @@ nsGlobalWindow::CanClose()
 }
 
 void
 nsGlobalWindow::CloseOuter(bool aTrustedCaller)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   if (!mDocShell || IsInModalState() ||
-      (IsFrame() && !mDocShell->GetIsMozBrowser())) {
+      (IsFrame() && !mDocShell->GetIsMozBrowserOrApp())) {
     // window.close() is called on a frame in a frameset, on a window
     // that's already closed, or on a window for which there's
     // currently a modal dialog open. Ignore such calls.
     return;
   }
 
   if (mHavePendingClose) {
     // We're going to be closed anyway; do nothing since we don't want
@@ -8735,23 +8735,24 @@ nsGlobalWindow::CloseOuter(bool aTrusted
   if (mBlockScriptedClosingFlag)
   {
     // A script's popup has been blocked and we don't want
     // the window to be closed directly after this event,
     // so the user can see that there was a blocked popup.
     return;
   }
 
-  // Don't allow scripts from content to close non-neterror windows that
-  // were not opened by script.
+  // Don't allow scripts from content to close non-app or non-neterror
+  // windows that were not opened by script.
   nsAutoString url;
   nsresult rv = mDoc->GetURL(url);
   NS_ENSURE_SUCCESS_VOID(rv);
 
-  if (!StringBeginsWith(url, NS_LITERAL_STRING("about:neterror")) &&
+  if (!mDocShell->GetIsApp() &&
+      !StringBeginsWith(url, NS_LITERAL_STRING("about:neterror")) &&
       !mHadOriginalOpener && !aTrustedCaller) {
     bool allowClose = mAllowScriptsToClose ||
       Preferences::GetBool("dom.allow_scripts_to_close_windows", true);
     if (!allowClose) {
       // We're blocking the close operation
       // report localized error msg in JS console
       nsContentUtils::ReportToConsole(
           nsIScriptError::warningFlag,
@@ -9189,17 +9190,17 @@ nsGlobalWindow::CacheXBLPrototypeHandler
   mCachedXBLPrototypeHandlers->Put(aKey, aHandler);
 }
 
 Element*
 nsGlobalWindow::GetFrameElementOuter(nsIPrincipal& aSubjectPrincipal)
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
-  if (!mDocShell || mDocShell->GetIsMozBrowser()) {
+  if (!mDocShell || mDocShell->GetIsMozBrowserOrApp()) {
     return nullptr;
   }
 
   // Per HTML5, the frameElement getter returns null in cross-origin situations.
   Element* element = GetRealFrameElementOuter();
   if (!element) {
     return nullptr;
   }
@@ -9224,17 +9225,17 @@ nsGlobalWindow::GetRealFrameElementOuter
 {
   MOZ_RELEASE_ASSERT(IsOuterWindow());
 
   if (!mDocShell) {
     return nullptr;
   }
 
   nsCOMPtr<nsIDocShell> parent;
-  mDocShell->GetSameTypeParentIgnoreBrowserBoundaries(getter_AddRefs(parent));
+  mDocShell->GetSameTypeParentIgnoreBrowserAndAppBoundaries(getter_AddRefs(parent));
 
   if (!parent || parent == mDocShell) {
     // We're at a chrome boundary, don't expose the chrome iframe
     // element to content code.
     return nullptr;
   }
 
   return mFrameElement;
--- a/dom/base/nsIFrameLoader.idl
+++ b/dom/base/nsIFrameLoader.idl
@@ -1,15 +1,16 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsISupports.idl"
 
+interface mozIApplication;
 interface nsFrameLoader;
 interface nsIDocShell;
 interface nsIURI;
 interface nsIFrame;
 interface nsSubDocumentFrame;
 interface nsIMessageSender;
 interface nsIVariant;
 interface nsIDOMElement;
@@ -210,20 +211,21 @@ interface nsIFrameLoader : nsISupports
    *
    * The notion of "visibility" here is separate from the notion of a
    * window/docshell's visibility.  This field is mostly here so that we can
    * have a notion of visibility in the parent process when frames are OOP.
    */
   [infallible] attribute boolean visible;
 
   /**
-   * Find out whether the owner content really is a mozbrowser. <xul:browser>
-   * is not considered to be a mozbrowser frame.
+   * Find out whether the owner content really is a mozbrowser or app frame
+   * Especially, a widget frame is regarded as an app frame.  <xul:browser> is
+   * not considered to be a mozbrowser frame.
    */
-  readonly attribute boolean ownerIsMozBrowserFrame;
+  readonly attribute boolean ownerIsMozBrowserOrAppFrame;
 
   /**
    * The last known width of the frame. Reading this property will not trigger
    * a reflow, and therefore may not reflect the current state of things. It
    * should only be used in asynchronous APIs where values are not guaranteed
    * to be up-to-date when received.
    */
   readonly attribute unsigned long lazyWidth;
@@ -259,16 +261,22 @@ interface nsIFrameLoaderOwner : nsISuppo
 {
   /**
    * The frame loader owned by this nsIFrameLoaderOwner
    */
   [binaryname(FrameLoaderXPCOM)] readonly attribute nsIFrameLoader frameLoader;
   [noscript, notxpcom] alreadyAddRefed_nsFrameLoader GetFrameLoader();
 
   /**
+   * The principal of parent mozIApplication in case of nested mozbrowser
+   * iframes.
+   */
+  readonly attribute mozIApplication parentApplication;
+
+  /**
    * Puts the FrameLoaderOwner in prerendering mode.
    */
   void setIsPrerendered();
 
   /**
    * This method is used internally by SwapFrameLoaders to set the frame loader
    * on the target nsFrameLoader.
    *
--- a/dom/base/nsInProcessTabChildGlobal.cpp
+++ b/dom/base/nsInProcessTabChildGlobal.cpp
@@ -91,24 +91,24 @@ nsInProcessTabChildGlobal::nsInProcessTa
                                                      nsFrameMessageManager* aChrome)
 : mDocShell(aShell), mInitialized(false), mLoadingScript(false),
   mPreventEventsEscaping(false),
   mOwner(aOwner), mChromeMessageManager(aChrome)
 {
   SetIsNotDOMBinding();
   mozilla::HoldJSObjects(this);
 
-  // If owner corresponds to an <iframe mozbrowser>, we'll have to tweak our
-  // PreHandleEvent implementation.
+  // If owner corresponds to an <iframe mozbrowser> or <iframe mozapp>, we'll
+  // have to tweak our PreHandleEvent implementation.
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(mOwner);
   if (browserFrame) {
-    mIsBrowserFrame = browserFrame->GetReallyIsBrowser();
+    mIsBrowserOrAppFrame = browserFrame->GetReallyIsBrowserOrApp();
   }
   else {
-    mIsBrowserFrame = false;
+    mIsBrowserOrAppFrame = false;
   }
 }
 
 nsInProcessTabChildGlobal::~nsInProcessTabChildGlobal()
 {
   mAnonymousGlobalScopes.Clear();
   mozilla::DropJSObjects(this);
 }
@@ -269,17 +269,17 @@ nsInProcessTabChildGlobal::PreHandleEven
   }
 #endif
 
   if (mPreventEventsEscaping) {
     aVisitor.mParentTarget = nullptr;
     return NS_OK;
   }
 
-  if (mIsBrowserFrame &&
+  if (mIsBrowserOrAppFrame &&
       (!mOwner || !nsContentUtils::IsInChromeDocshell(mOwner->OwnerDoc()))) {
     if (mOwner) {
       if (nsPIDOMWindowInner* innerWindow = mOwner->OwnerDoc()->GetInnerWindow()) {
         aVisitor.mParentTarget = innerWindow->GetParentTarget();
       }
     }
   } else {
     aVisitor.mParentTarget = mOwner;
--- a/dom/base/nsInProcessTabChildGlobal.h
+++ b/dom/base/nsInProcessTabChildGlobal.h
@@ -161,19 +161,20 @@ protected:
 
   nsresult Init();
   nsresult InitTabChildGlobal();
   nsCOMPtr<nsIContentFrameMessageManager> mMessageManager;
   nsCOMPtr<nsIDocShell> mDocShell;
   bool mInitialized;
   bool mLoadingScript;
 
-  // Is this the message manager for an in-process <iframe mozbrowser>? This
-  // affects where events get sent, so it affects PreHandleEvent.
-  bool mIsBrowserFrame;
+  // Is this the message manager for an in-process <iframe mozbrowser> or
+  // <iframe mozapp>?  This affects where events get sent, so it affects
+  // PreHandleEvent.
+  bool mIsBrowserOrAppFrame;
   bool mPreventEventsEscaping;
 
   // We keep a strong reference to the frameloader after we've started
   // teardown. This allows us to dispatch message manager messages during this
   // time.
   nsCOMPtr<nsIFrameLoader> mFrameLoader;
 public:
   nsIContent* mOwner;
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -1238,16 +1238,27 @@ nsObjectLoadingContent::GetFrameLoaderXP
 
 NS_IMETHODIMP_(already_AddRefed<nsFrameLoader>)
 nsObjectLoadingContent::GetFrameLoader()
 {
   RefPtr<nsFrameLoader> loader = mFrameLoader;
   return loader.forget();
 }
 
+NS_IMETHODIMP
+nsObjectLoadingContent::GetParentApplication(mozIApplication** aApplication)
+{
+  if (!aApplication) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *aApplication = nullptr;
+  return NS_OK;
+}
+
 void
 nsObjectLoadingContent::PresetOpenerWindow(mozIDOMWindowProxy* aWindow, mozilla::ErrorResult& aRv)
 {
   aRv.Throw(NS_ERROR_FAILURE);
 }
 
 NS_IMETHODIMP
 nsObjectLoadingContent::SetIsPrerendered()
--- a/dom/base/nsXHTMLContentSerializer.cpp
+++ b/dom/base/nsXHTMLContentSerializer.cpp
@@ -301,17 +301,17 @@ nsXHTMLContentSerializer::SerializeAttri
   // XXX Unfortunately we need a namespace manager to get
   // attribute URIs.
   for (index = 0; index < count; index++) {
 
     if (aSkipAttr == index) {
         continue;
     }
 
-    mozilla::dom::BorrowedAttrInfo info = aContent->GetAttrInfoAt(index);
+    BorrowedAttrInfo info = aContent->GetAttrInfoAt(index);
     const nsAttrName* name = info.mName;
 
     int32_t namespaceID = name->NamespaceID();
     nsIAtom* attrName = name->LocalName();
     nsIAtom* attrPrefix = name->GetPrefix();
 
     // Filter out any attribute starting with [-|_]moz
     nsDependentAtomString attrNameStr(attrName);
--- a/dom/browser-element/BrowserElementChild.js
+++ b/dom/browser-element/BrowserElementChild.js
@@ -21,17 +21,17 @@ function parentDocShell(docshell) {
   }
   let treeitem = docshell.QueryInterface(Ci.nsIDocShellTreeItem);
   return treeitem.parent ? treeitem.parent.QueryInterface(Ci.nsIDocShell) : null;
 }
 
 function isTopBrowserElement(docShell) {
   while (docShell) {
     docShell = parentDocShell(docShell);
-    if (docShell && docShell.isMozBrowser) {
+    if (docShell && docShell.isMozBrowserOrApp) {
       return false;
     }
   }
   return true;
 }
 
 var BrowserElementIsReady;
 
--- a/dom/browser-element/BrowserElementCopyPaste.js
+++ b/dom/browser-element/BrowserElementCopyPaste.js
@@ -108,17 +108,17 @@ var CopyPasteAssistent = {
       detail.rect.bottom += currentRect.top;
       detail.rect.left += currentRect.left;
       detail.rect.right += currentRect.left;
       currentWindow = currentWindow.realFrameElement.ownerDocument.defaultView;
 
       let targetDocShell = currentWindow
           .QueryInterface(Ci.nsIInterfaceRequestor)
           .getInterface(Ci.nsIWebNavigation);
-      if(targetDocShell.isMozBrowser) {
+      if(targetDocShell.isMozBrowserOrApp) {
         break;
       }
     }
 
     sendAsyncMsg('caretstatechanged', detail);
   },
 };
 
--- a/dom/browser-element/BrowserElementParent.cpp
+++ b/dom/browser-element/BrowserElementParent.cpp
@@ -50,16 +50,33 @@ CreateIframe(Element* aOpenerFrameElemen
                                  nsIDOMNode::ELEMENT_NODE);
 
   RefPtr<HTMLIFrameElement> popupFrameElement =
     static_cast<HTMLIFrameElement*>(
       NS_NewHTMLIFrameElement(nodeInfo.forget(), mozilla::dom::NOT_FROM_PARSER));
 
   popupFrameElement->SetMozbrowser(true);
 
+  // Copy the opener frame's mozapp attribute to the popup frame.
+  if (aOpenerFrameElement->HasAttr(kNameSpaceID_None, nsGkAtoms::mozapp)) {
+    nsAutoString mozapp;
+    aOpenerFrameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::mozapp, mozapp);
+    popupFrameElement->SetAttr(kNameSpaceID_None, nsGkAtoms::mozapp,
+                               mozapp, /* aNotify = */ false);
+  }
+
+  // Copy the opener frame's parentApp attribute to the popup frame.
+  if (aOpenerFrameElement->HasAttr(kNameSpaceID_None, nsGkAtoms::parentapp)) {
+    nsAutoString parentApp;
+    aOpenerFrameElement->GetAttr(kNameSpaceID_None, nsGkAtoms::parentapp,
+                                 parentApp);
+    popupFrameElement->SetAttr(kNameSpaceID_None, nsGkAtoms::parentapp,
+                               parentApp, /* aNotify = */ false);
+  }
+
   // Copy the window name onto the iframe.
   popupFrameElement->SetAttr(kNameSpaceID_None, nsGkAtoms::name,
                              aName, /* aNotify = */ false);
 
   // Indicate whether the iframe is should be remote.
   popupFrameElement->SetAttr(kNameSpaceID_None, nsGkAtoms::Remote,
                              aRemote ? NS_LITERAL_STRING("true") :
                                        NS_LITERAL_STRING("false"),
--- a/dom/browser-element/mochitest/browserElement_SetInputMethodActive.js
+++ b/dom/browser-element/mochitest/browserElement_SetInputMethodActive.js
@@ -3,16 +3,37 @@
 
 // Bug 905573 - Add setInputMethodActive to browser elements to allow gaia
 // system set the active IME app.
 'use strict';
 
 SimpleTest.waitForExplicitFinish();
 browserElementTestHelpers.setEnabledPref(true);
 
+// We'll need to get the appId from the current document,
+// it's either SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID when
+// we are not running inside an app (e.g. Firefox Desktop),
+// or the appId of Mochitest app when we are running inside that app
+// (e.g. Emulator).
+var currentAppId = SpecialPowers.wrap(document).nodePrincipal.appId;
+var inApp =
+  currentAppId !== SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID;
+// We will also need the manifest URL and set it on iframes.
+var currentAppManifestURL;
+
+if (inApp) {
+  let appsService = SpecialPowers.Cc["@mozilla.org/AppsService;1"]
+                      .getService(SpecialPowers.Ci.nsIAppsService);
+
+  currentAppManifestURL = appsService.getManifestURLByLocalId(currentAppId);
+};
+
+info('appId=' + currentAppId);
+info('manifestURL=' + currentAppManifestURL);
+
 function setup() {
   let appInfo = SpecialPowers.Cc['@mozilla.org/xre/app-info;1']
                 .getService(SpecialPowers.Ci.nsIXULAppInfo);
   if (appInfo.name != 'B2G') {
     SpecialPowers.Cu.import("resource://gre/modules/Keyboard.jsm", window);
   }
 
   SpecialPowers.setBoolPref("dom.mozInputMethod.enabled", true);
@@ -47,34 +68,45 @@ function createFrames() {
    gInputFrame.setAttribute('mozbrowser', 'true');
    gInputFrame.src = 'file_browserElement_SetInputMethodActive.html';
    document.body.appendChild(gInputFrame);
    gInputFrame.addEventListener('mozbrowserloadend', countLoadend);
 
    for (let i = 0; i < 2; i++) {
      let frame = gInputMethodFrames[i] = document.createElement('iframe');
      frame.setAttribute('mozbrowser', 'true');
+     if (currentAppManifestURL) {
+       frame.setAttribute('mozapp', currentAppManifestURL);
+     }
      frame.addEventListener('mozbrowserloadend', countLoadend);
      frame.src = 'file_empty.html#' + i;
      document.body.appendChild(frame);
    }
  }
 
 function setPermissions() {
   let permissions = [{
     type: 'input',
     allow: true,
     context: {
       url: SimpleTest.getTestFileURL('/file_empty.html'),
       originAttributes: {
+        appId: currentAppId,
         inIsolatedMozBrowser: true
       }
     }
   }];
 
+  if (inApp) {
+    // The current document would also need to be given access for IPC to
+    // recognize our permission (why)?
+    permissions.push({
+      type: 'input', allow: true, context: document });
+  }
+
   SpecialPowers.pushPermissions(permissions,
     SimpleTest.waitForFocus.bind(SimpleTest, startTest));
 }
 
  function startTest() {
   // The frame script running in the frame where the input is hosted.
   let appFrameScript = function appFrameScript() {
     let input = content.document.body.firstElementChild;
--- a/dom/browser-element/mochitest/file_browserElement_AudioChannel_nested.html
+++ b/dom/browser-element/mochitest/file_browserElement_AudioChannel_nested.html
@@ -15,16 +15,17 @@
   }
 
   addEventListener('load', function(e) {
     var iframe = document.createElement('iframe');
     iframe.setAttribute('mozbrowser', 'true');
     // set 'remote' to true here will make the the iframe remote in _inproc_
     // test and in-process in _oop_  test.
     iframe.setAttribute('remote', 'true');
+    iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
 
     iframe.addEventListener('mozbrowserloadend', function(e) {
       ok("mute" in iframe, "iframe.mute exists");
       ok("unmute" in iframe, "iframe.unmute exists");
       ok("getMuted" in iframe, "iframe.getMuted exists");
       ok("getVolume" in iframe, "iframe.getVolume exists");
       ok("setVolume" in iframe, "iframe.setVolume exists");
 
--- a/dom/devicestorage/test/test_app_permissions.html
+++ b/dom/devicestorage/test/test_app_permissions.html
@@ -192,16 +192,58 @@ var gData = [
     shouldPass: true,
     fileExtension: '.txt',
 
     permissions: ["device-storage:sdcard"],
 
     test: TestGet
   },
 
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+    fileExtension: '.png',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestGet
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+    fileExtension: '.ogv',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestGet
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+    fileExtension: '.ogg',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestGet
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+    fileExtension: '.txt',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestGet
+  },
+
 
   // Add
 
 
   // Web applications with no permissions
   {
     type: 'pictures',
     mimeType: 'image/png',
@@ -268,16 +310,62 @@ var gData = [
     fileExtension: '.txt',
     shouldPass: true,
 
     permissions: ["device-storage:sdcard"],
 
     test: TestAdd
   },
 
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    mimeType: 'image/png',
+    fileExtension: '.png',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestAdd
+  },
+  {
+    type: 'videos',
+    mimeType: 'video/ogv',
+    fileExtension: '.ogv',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestAdd
+  },
+  {
+    type: 'music',
+    mimeType: 'audio/ogg',
+    fileExtension: '.ogg',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestAdd
+  },
+  {
+    type: 'sdcard',
+    mimeType: 'text/plain',
+    fileExtension: '.txt',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestAdd
+  },
+
 
 // Delete 
 
   // Web applications with no permissions
   {
     type: 'pictures',
     shouldPass: false,
     fileExtension: '.png',
@@ -335,16 +423,58 @@ var gData = [
     shouldPass: true,
     fileExtension: '.txt',
 
     permissions: ["device-storage:sdcard"],
 
     test: TestDelete
   },
 
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+    fileExtension: '.png',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestDelete
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+    fileExtension: '.ogv',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestDelete
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+    fileExtension: '.ogg',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestDelete
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+    fileExtension: '.txt',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestDelete
+  },
+
 // Enumeration 
 
   // Web applications with no permissions
   {
     type: 'pictures',
     shouldPass: false,
     fileExtension: '.png',
     test: TestEnumerate
@@ -401,16 +531,58 @@ var gData = [
     shouldPass: true,
     fileExtension: '.txt',
 
     permissions: ["device-storage:sdcard"],
 
     test: TestEnumerate
   },
 
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+    fileExtension: '.png',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestEnumerate
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+    fileExtension: '.ogv',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestEnumerate
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+    fileExtension: '.ogg',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestEnumerate
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+    fileExtension: '.txt',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestEnumerate
+  },
+
 ];
 
 function setupTest(iframe,data) {
   if (data.permissions) {
     for (var j in data.permissions) {
       SpecialPowers.addPermission(data.permissions[j], true, iframe.contentDocument);
     }
   }
@@ -433,16 +605,19 @@ function testComplete(iframe, data) {
 }
 
 function runTest() {
   while (gData.length > 0) {
     var iframe = document.createElement('iframe');
     var data = gData.pop();
 
     iframe.setAttribute('mozbrowser', '');
+    if (data.app) {
+      iframe.setAttribute('mozapp', data.app);
+    }
 
     iframe.src = gTestUri;
 
     iframe.addEventListener('load', function(e) {
       setupTest(iframe, data)
       data.test(iframe, data);
     });
 
--- a/dom/devicestorage/test/test_fs_app_permissions.html
+++ b/dom/devicestorage/test/test_fs_app_permissions.html
@@ -257,16 +257,78 @@ let gData = [
     shouldPass: true,
     fileExtension: '.txt',
 
     permissions: ["device-storage:sdcard"],
 
     test: TestGet
   },
 
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+    fileExtension: '.png',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestGet
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+    fileExtension: '.ogv',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestGet
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+    fileExtension: '.ogg',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestGet
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+    fileExtension: '.ogg',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestGet
+  },
+  {
+    type: 'music',
+    shouldPass: false,
+    fileExtension: '.txt',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestGet
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+    fileExtension: '.txt',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestGet
+  },
+
   // Directory#createDirectory
 
   // Web applications with no permissions
   {
     type: 'pictures',
     shouldPass: false,
     test: TestCreateDirectory
   },
@@ -315,16 +377,54 @@ let gData = [
     type: 'sdcard',
     shouldPass: true,
 
     permissions: ["device-storage:sdcard"],
 
     test: TestCreateDirectory
   },
 
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestCreateDirectory
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestCreateDirectory
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestCreateDirectory
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestCreateDirectory
+  },
+
   // Directory#createFile
 
   // Web applications with no permissions
   {
     type: 'pictures',
     mimeType: 'image/png',
     shouldPass: false,
     fileExtension: '.png',
@@ -423,16 +523,84 @@ let gData = [
     shouldPass: true,
     fileExtension: '.txt',
 
     permissions: ["device-storage:sdcard"],
 
     test: TestCreateFile
   },
 
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    mimeType: 'image/png',
+    shouldPass: true,
+    fileExtension: '.png',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestCreateFile
+  },
+  {
+    type: 'videos',
+    mimeType: 'video/ogv',
+    shouldPass: true,
+    fileExtension: '.ogv',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestCreateFile
+  },
+  {
+    type: 'videos',
+    mimeType: 'video/ogg',
+    shouldPass: true,
+    fileExtension: '.ogg',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestCreateFile
+  },
+  {
+    type: 'music',
+    mimeType: 'audio/ogg',
+    shouldPass: true,
+    fileExtension: '.ogg',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestCreateFile
+  },
+  {
+    type: 'music',
+    mimeType: 'audio/ogg',
+    shouldPass: false,
+    fileExtension: '.txt',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestCreateFile
+  },
+  {
+    type: 'sdcard',
+    mimeType: 'text/plain',
+    shouldPass: true,
+    fileExtension: '.txt',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestCreateFile
+  },
+
   // Directory#remove
 
   // Web applications with no permissions
   {
     type: 'pictures',
     shouldPass: false,
     fileExtension: '.png',
     test: TestRemove
@@ -519,16 +687,78 @@ let gData = [
     shouldPass: true,
     fileExtension: '.txt',
 
     permissions: ["device-storage:sdcard"],
 
     test: TestRemove
   },
 
+  // Certified application with permision granted
+  {
+    type: 'pictures',
+    shouldPass: true,
+    fileExtension: '.png',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:pictures"],
+
+    test: TestRemove
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+    fileExtension: '.ogv',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestRemove
+  },
+  {
+    type: 'videos',
+    shouldPass: true,
+    fileExtension: '.ogg',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:videos"],
+
+    test: TestRemove
+  },
+  {
+    type: 'music',
+    shouldPass: true,
+    fileExtension: '.ogg',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestRemove
+  },
+  {
+    type: 'music',
+    shouldPass: false,
+    fileExtension: '.txt',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:music"],
+
+    test: TestRemove
+  },
+  {
+    type: 'sdcard',
+    shouldPass: true,
+    fileExtension: '.txt',
+
+    app: "https://example.com/manifest_cert.webapp",
+    permissions: ["device-storage:sdcard"],
+
+    test: TestRemove
+  }
+
 ];
 
 function setupTest(iframe,data) {
   if (data.permissions) {
     for (let j in data.permissions) {
       SpecialPowers.addPermission(data.permissions[j], true, iframe.contentDocument);
     }
   }
@@ -551,16 +781,19 @@ function testComplete(iframe, data) {
 }
 
 function runTest() {
   while (gData.length > 0) {
     let iframe = document.createElement('iframe');
     let data = gData.shift();
 
     iframe.setAttribute('mozbrowser', '');
+    if (data.app) {
+      iframe.setAttribute('mozapp', data.app);
+    }
 
     iframe.src = gTestUri;
 
     iframe.addEventListener('load', function(e) {
       setupTest(iframe, data)
       data.test(iframe, data);
     });
 
--- a/dom/events/EventStateManager.cpp
+++ b/dom/events/EventStateManager.cpp
@@ -1257,19 +1257,19 @@ EventStateManager::IsRemoteTarget(nsICon
 
   // <browser/iframe remote=true> from XUL
   if (target->IsAnyOfXULElements(nsGkAtoms::browser, nsGkAtoms::iframe) &&
       target->AttrValueIs(kNameSpaceID_None, nsGkAtoms::Remote,
                           nsGkAtoms::_true, eIgnoreCase)) {
     return true;
   }
 
-  // <frame/iframe mozbrowser>
+  // <frame/iframe mozbrowser/mozapp>
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(target);
-  if (browserFrame && browserFrame->GetReallyIsBrowser()) {
+  if (browserFrame && browserFrame->GetReallyIsBrowserOrApp()) {
     return !!TabParent::GetFrom(target);
   }
 
   return false;
 }
 
 static bool
 CrossProcessSafeEvent(const WidgetEvent& aEvent)
--- a/dom/html/nsBrowserElement.cpp
+++ b/dom/html/nsBrowserElement.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/dom/BrowserElementBinding.h"
 #include "mozilla/dom/BrowserElementAudioChannel.h"
 #include "mozilla/dom/DOMRequest.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/ToJSValue.h"
 
 #include "AudioChannelService.h"
 
+#include "mozIApplication.h"
 #include "nsComponentManagerUtils.h"
 #include "nsFrameLoader.h"
 #include "nsIAppsService.h"
 #include "nsIDOMDOMRequest.h"
 #include "nsIDOMElement.h"
 #include "nsIMozBrowserFrame.h"
 #include "nsINode.h"
 #include "nsIPrincipal.h"
@@ -37,23 +38,23 @@ nsBrowserElement::IsBrowserElementOrThro
   }
   aRv.Throw(NS_ERROR_DOM_INVALID_NODE_TYPE_ERR);
   return false;
 }
 
 void
 nsBrowserElement::InitBrowserElementAPI()
 {
-  bool isMozBrowser;
+  bool isMozBrowserOrApp;
   nsCOMPtr<nsIFrameLoader> frameLoader = GetFrameLoader();
   NS_ENSURE_TRUE_VOID(frameLoader);
-  nsresult rv = frameLoader->GetOwnerIsMozBrowserFrame(&isMozBrowser);
+  nsresult rv = frameLoader->GetOwnerIsMozBrowserOrAppFrame(&isMozBrowserOrApp);
   NS_ENSURE_SUCCESS_VOID(rv);
 
-  if (!isMozBrowser) {
+  if (!isMozBrowserOrApp) {
     return;
   }
 
   if (!mBrowserElementAPI) {
     mBrowserElementAPI = do_CreateInstance("@mozilla.org/dom/browser-element-api;1");
     if (NS_WARN_IF(!mBrowserElementAPI)) {
       return;
     }
@@ -488,23 +489,23 @@ nsBrowserElement::GetAllowedAudioChannel
 
   // If empty, it means that this is the first call of this method.
   if (mBrowserElementAudioChannels.IsEmpty()) {
     nsCOMPtr<nsIFrameLoader> frameLoader = GetFrameLoader();
     if (NS_WARN_IF(!frameLoader)) {
       return;
     }
 
-    bool isMozBrowser;
-    aRv = frameLoader->GetOwnerIsMozBrowserFrame(&isMozBrowser);
+    bool isMozBrowserOrApp;
+    aRv = frameLoader->GetOwnerIsMozBrowserOrAppFrame(&isMozBrowserOrApp);
     if (NS_WARN_IF(aRv.Failed())) {
       return;
     }
 
-    if (!isMozBrowser) {
+    if (!isMozBrowserOrApp) {
       return;
     }
 
     nsCOMPtr<nsIDOMElement> frameElement;
     aRv = frameLoader->GetOwnerElement(getter_AddRefs(frameElement));
     if (NS_WARN_IF(aRv.Failed())) {
       return;
     }
--- a/dom/html/nsBrowserElement.h
+++ b/dom/html/nsBrowserElement.h
@@ -119,16 +119,17 @@ public:
                  nsPIDOMWindowInner* aWindow,
                  nsIFrameLoader* aFrameLoader,
                  nsIBrowserElementAPI* aAPI,
                  nsTArray<RefPtr<dom::BrowserElementAudioChannel>>& aAudioChannels,
                  ErrorResult& aRv);
 
 protected:
   NS_IMETHOD_(already_AddRefed<nsFrameLoader>) GetFrameLoader() = 0;
+  NS_IMETHOD GetParentApplication(mozIApplication** aApplication) = 0;
 
   void InitBrowserElementAPI();
   void DestroyBrowserElementFrameScripts();
   nsCOMPtr<nsIBrowserElementAPI> mBrowserElementAPI;
   nsTArray<RefPtr<dom::BrowserElementAudioChannel>> mBrowserElementAudioChannels;
 
 private:
   bool IsBrowserElementOrThrow(ErrorResult& aRv);
--- a/dom/html/nsGenericHTMLFrameElement.cpp
+++ b/dom/html/nsGenericHTMLFrameElement.cpp
@@ -7,18 +7,20 @@
 #include "nsGenericHTMLFrameElement.h"
 
 #include "mozilla/dom/BrowserElementAudioChannel.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/HTMLIFrameElement.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ErrorResult.h"
 #include "GeckoProfiler.h"
+#include "mozIApplication.h"
 #include "nsAttrValueInlines.h"
 #include "nsContentUtils.h"
+#include "nsIAppsService.h"
 #include "nsIDocShell.h"
 #include "nsIDOMDocument.h"
 #include "nsIFrame.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIPermissionManager.h"
 #include "nsIPresShell.h"
 #include "nsIScrollable.h"
 #include "nsPresContext.h"
@@ -184,16 +186,41 @@ nsGenericHTMLFrameElement::GetFrameLoade
 
 NS_IMETHODIMP_(already_AddRefed<nsFrameLoader>)
 nsGenericHTMLFrameElement::GetFrameLoader()
 {
   RefPtr<nsFrameLoader> loader = mFrameLoader;
   return loader.forget();
 }
 
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::GetParentApplication(mozIApplication** aApplication)
+{
+  if (!aApplication) {
+    return NS_ERROR_FAILURE;
+  }
+
+  *aApplication = nullptr;
+
+  nsIPrincipal *principal = NodePrincipal();
+  uint32_t appId = principal->GetAppId();
+
+  nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!appsService)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv = appsService->GetAppByLocalId(appId, aApplication);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
 void
 nsGenericHTMLFrameElement::PresetOpenerWindow(mozIDOMWindowProxy* aWindow, ErrorResult& aRv)
 {
   MOZ_ASSERT(!mFrameLoader);
   mOpenerWindow = nsPIDOMWindowOuter::From(aWindow);
 }
 
 void
@@ -464,22 +491,22 @@ nsGenericHTMLFrameElement::BrowserFrames
     Preferences::AddBoolVarCache(&sMozBrowserFramesEnabled,
                                  "dom.mozBrowserFramesEnabled");
   }
 
   return sMozBrowserFramesEnabled;
 }
 
 /**
- * Return true if this frame element really is a mozbrowser.  (It
+ * Return true if this frame element really is a mozbrowser or mozapp.  (It
  * needs to have the right attributes, and its creator must have the right
  * permissions.)
  */
 /* [infallible] */ nsresult
-nsGenericHTMLFrameElement::GetReallyIsBrowser(bool *aOut)
+nsGenericHTMLFrameElement::GetReallyIsBrowserOrApp(bool *aOut)
 {
   *aOut = false;
 
   // Fail if browser frames are globally disabled.
   if (!nsGenericHTMLFrameElement::BrowserFramesEnabled()) {
     return NS_OK;
   }
 
@@ -497,29 +524,142 @@ nsGenericHTMLFrameElement::GetReallyIsBr
   uint32_t permission = nsIPermissionManager::DENY_ACTION;
   nsresult rv = permMgr->TestPermissionFromPrincipal(principal, "browser", &permission);
   NS_ENSURE_SUCCESS(rv, NS_OK);
   *aOut = permission == nsIPermissionManager::ALLOW_ACTION;
   return NS_OK;
 }
 
 /* [infallible] */ NS_IMETHODIMP
+nsGenericHTMLFrameElement::GetReallyIsApp(bool *aOut)
+{
+  nsAutoString manifestURL;
+  GetAppManifestURL(manifestURL);
+
+  *aOut = !manifestURL.IsEmpty();
+  return NS_OK;
+}
+
+namespace {
+
+bool NestedEnabled()
+{
+  static bool sMozNestedEnabled = false;
+  static bool sBoolVarCacheInitialized = false;
+
+  if (!sBoolVarCacheInitialized) {
+    sBoolVarCacheInitialized = true;
+    Preferences::AddBoolVarCache(&sMozNestedEnabled,
+                                 "dom.ipc.tabs.nested.enabled");
+  }
+
+  return sMozNestedEnabled;
+}
+
+} // namespace
+
+/* [infallible] */ NS_IMETHODIMP
 nsGenericHTMLFrameElement::GetIsolated(bool *aOut)
 {
   *aOut = true;
 
   if (!nsContentUtils::IsSystemPrincipal(NodePrincipal())) {
     return NS_OK;
   }
 
   // Isolation is only disabled if the attribute is present
   *aOut = !HasAttr(kNameSpaceID_None, nsGkAtoms::noisolation);
   return NS_OK;
 }
 
+/*
+ * Get manifest url of app.
+ */
+void nsGenericHTMLFrameElement::GetManifestURL(nsAString& aManifestURL)
+{
+  aManifestURL.Truncate();
+
+  nsAutoString manifestURL;
+  GetAttr(kNameSpaceID_None, nsGkAtoms::mozapp, manifestURL);
+  if (manifestURL.IsEmpty()) {
+    return;
+  }
+
+  // Check permission.
+  nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
+  NS_ENSURE_TRUE_VOID(permMgr);
+  nsIPrincipal *principal = NodePrincipal();
+  const char* aPermissionType = "embed-apps";
+  uint32_t permission = nsIPermissionManager::DENY_ACTION;
+  nsresult rv = permMgr->TestPermissionFromPrincipal(principal,
+                                                     aPermissionType,
+                                                     &permission);
+  NS_ENSURE_SUCCESS_VOID(rv);
+  if (permission != nsIPermissionManager::ALLOW_ACTION) {
+    return;
+  }
+
+  nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE_VOID(appsService);
+
+  nsCOMPtr<mozIApplication> app;
+  appsService->GetAppByManifestURL(manifestURL, getter_AddRefs(app));
+
+  if (!app) {
+    return;
+  }
+
+  aManifestURL.Assign(manifestURL);
+}
+
+NS_IMETHODIMP
+nsGenericHTMLFrameElement::GetAppManifestURL(nsAString& aOut)
+{
+  aOut.Truncate();
+
+  // At the moment, you can't be an app without being a browser.
+  if (!nsIMozBrowserFrame::GetReallyIsBrowserOrApp()) {
+    return NS_OK;
+  }
+
+  // Only allow content process to embed an app when nested content
+  // process is enabled.
+  if (!XRE_IsParentProcess() &&
+      !(GetBoolAttr(nsGkAtoms::Remote) && NestedEnabled())){
+    NS_WARNING("Can't embed-apps. Embed-apps is restricted to in-proc apps "
+               "or content processes with nested pref enabled, see bug 1097479");
+    return NS_OK;
+  }
+
+  nsAutoString appManifestURL;
+
+  GetManifestURL(appManifestURL);
+
+  bool isApp = !appManifestURL.IsEmpty();
+
+  if (!isApp) {
+    // No valid case to get manifest
+    return NS_OK;
+  }
+
+  if (isApp) {
+    NS_WARNING("Can not simultaneously be mozapp");
+    return NS_OK;
+  }
+
+  nsAutoString manifestURL;
+  if (isApp) {
+    manifestURL.Assign(appManifestURL);
+  }
+
+  aOut.Assign(manifestURL);
+
+  return NS_OK;
+}
+
 NS_IMETHODIMP
 nsGenericHTMLFrameElement::DisallowCreateFrameLoader()
 {
   MOZ_ASSERT(!mFrameLoader);
   MOZ_ASSERT(!mFrameLoaderCreationDisallowed);
   mFrameLoaderCreationDisallowed = true;
   return NS_OK;
 }
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/file_app_isolation.html
@@ -0,0 +1,88 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    foobar!
+  </body>
+  <script>
+    var data = [
+      { id: "0", name: "foo" },
+    ];
+
+    var action = window.location.search.substring(1);
+    var finished = false;
+    var created = false; // We use that for 'read-no' action.
+
+    function finish(value) {
+      value ? alert('success') : alert('failure');
+      finished = true;
+    }
+
+    var request = window.indexedDB.open('AppIsolationTest');
+
+    request.onupgradeneeded = function(event) {
+      if (finished) {
+        finish(false);
+        return;
+      }
+
+      switch (action) {
+        case 'read-no':
+          created = true;
+          break;
+        case 'read-yes':
+          finish(false);
+          break;
+        case 'write':
+          created = true;
+
+          var db = event.target.result;
+
+          var objectStore = db.createObjectStore("test", { keyPath: "id" });
+          for (var i in data) {
+            objectStore.add(data[i]);
+          }
+          break;
+      }
+    }
+
+    request.onsuccess = function(event) {
+      if (finished) {
+        finish(false);
+        return;
+      }
+
+      var db = event.target.result;
+
+      // Think about close the db!
+      switch (action) {
+        case 'read-no':
+          db.close();
+
+          if (created) { // That means we have created it.
+            indexedDB.deleteDatabase('AppIsolationTest').onsuccess = function() {
+              finish(true);
+            };
+          } else {
+            finish(false);
+          }
+          break;
+        case 'read-yes':
+          db.transaction("test").objectStore("test").get("0").onsuccess = function(event) {
+            var name = event.target.result.name;
+            db.close();
+
+            indexedDB.deleteDatabase('AppIsolationTest').onsuccess = function() {
+              finish(name == 'foo');
+            };
+          };
+          break;
+        case 'write':
+          db.close();
+
+          // Success only if the db was actually created.
+          finish(created);
+          break;
+      }
+    };
+  </script>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/file_app_isolation.js
@@ -0,0 +1,161 @@
+SimpleTest.waitForExplicitFinish();
+
+var fileTestOnCurrentOrigin = (location.protocol + '//' + location.host + location.pathname)
+                              .replace('test_', 'file_')
+                              .replace('_inproc', '').replace('_oop', '');
+
+var previousPrefs = {
+  mozBrowserFramesEnabled: undefined,
+  oop_by_default: undefined,
+};
+
+try {
+  previousPrefs.mozBrowserFramesEnabled = SpecialPowers.getBoolPref('dom.mozBrowserFramesEnabled');
+} catch(e)
+{
+}
+
+try {
+  previousPrefs.oop_by_default = SpecialPowers.getBoolPref('dom.ipc.browser_frames.oop_by_default');
+} catch(e) {
+}
+
+SpecialPowers.setBoolPref('dom.mozBrowserFramesEnabled', true);
+SpecialPowers.setBoolPref("dom.ipc.browser_frames.oop_by_default", location.pathname.indexOf('_inproc') == -1);
+
+SpecialPowers.addPermission("browser", true, window.document);
+
+var gData = [
+  // APP 1
+  {
+    app: 'http://example.org/manifest.webapp',
+    action: 'read-no',
+    src: fileTestOnCurrentOrigin,
+  },
+  {
+    app: 'http://example.org/manifest.webapp',
+    action: 'write',
+    src: fileTestOnCurrentOrigin,
+  },
+  {
+    app: 'http://example.org/manifest.webapp',
+    action: 'read-yes',
+    src: fileTestOnCurrentOrigin,
+  },
+  // APP 2
+  {
+    app: 'https://example.com/manifest.webapp',
+    action: 'read-no',
+    src: fileTestOnCurrentOrigin,
+  },
+  {
+    app: 'https://example.com/manifest.webapp',
+    action: 'write',
+    src: fileTestOnCurrentOrigin,
+  },
+  {
+    app: 'https://example.com/manifest.webapp',
+    action: 'read-yes',
+    src: fileTestOnCurrentOrigin,
+  },
+  // Browser
+  {
+    browser: true,
+    action: 'read-no',
+    src: fileTestOnCurrentOrigin,
+  },
+  {
+    browser: true,
+    action: 'write',
+    src: fileTestOnCurrentOrigin,
+  },
+  {
+    browser: true,
+    action: 'read-yes',
+    src: fileTestOnCurrentOrigin,
+  },
+];
+
+function runTest() {
+  for (var i in gData) {
+    var iframe = document.createElement('iframe');
+    var data = gData[i];
+
+    if (data.app) {
+      iframe.setAttribute('mozbrowser', '');
+      iframe.setAttribute('mozapp', data.app);
+    } else if (data.browser) {
+      iframe.setAttribute('mozbrowser', '');
+    }
+
+    if (data.app || data.browser) {
+      iframe.addEventListener('mozbrowsershowmodalprompt', function(e) {
+        is(e.detail.message, 'success', 'test number ' + i);
+
+//        document.getElementById('content').removeChild(iframe);
+
+        i++;
+        if (i >= gData.length) {
+          if (previousPrefs.mozBrowserFramesEnabled !== undefined) {
+            SpecialPowers.setBoolPref('dom.mozBrowserFramesEnabled', previousPrefs.mozBrowserFramesEnabled);
+          }
+          if (previousPrefs.oop_by_default !== undefined) {
+            SpecialPowers.setBoolPref("dom.ipc.browser_frames.oop_by_default", previousPrefs.oop_by_default);
+          }
+
+          SpecialPowers.removePermission("browser", window.document);
+
+          indexedDB.deleteDatabase('AppIsolationTest').onsuccess = function() {
+            SimpleTest.finish();
+          };
+        } else {
+          gTestRunner.next();
+        }
+      });
+    }
+
+    iframe.src = data.src + '?' + data.action;
+
+    document.getElementById('content').appendChild(iframe);
+
+    yield undefined;
+  }
+}
+
+var gTestRunner = runTest();
+
+function startTest() {
+  var request = window.indexedDB.open('AppIsolationTest');
+  var created = false;
+
+  request.onupgradeneeded = function(event) {
+    created = true;
+    var db = event.target.result;
+    var data = [
+      { id: "0", name: "foo" },
+    ];
+    var objectStore = db.createObjectStore("test", { keyPath: "id" });
+    for (var i in data) {
+      objectStore.add(data[i]);
+    }
+  }
+
+  request.onsuccess = function(event) {
+    var db = event.target.result;
+    is(created, true, "we should have created the db");
+
+    db.transaction("test").objectStore("test").get("0").onsuccess = function(event) {
+      is(event.target.result.name, 'foo', 'data have been written');
+      db.close();
+
+      gTestRunner.next();
+    };
+  }
+}
+
+// TODO: remove unsetting network.disable.ipc.security as part of bug 820712
+SpecialPowers.pushPrefEnv({
+  "set": [
+    ["network.disable.ipc.security", true],
+  ]
+}, startTest);
--- a/dom/indexedDB/test/mochitest.ini
+++ b/dom/indexedDB/test/mochitest.ini
@@ -6,16 +6,18 @@
 support-files =
   bfcache_iframe1.html
   bfcache_iframe2.html
   blob_worker_crash_iframe.html
   error_events_abort_transactions_iframe.html
   event_propagation_iframe.html
   exceptions_in_events_iframe.html
   file.js
+  file_app_isolation.html
+  file_app_isolation.js
   helpers.js
   leaving_page_iframe.html
   service_worker.js
   service_worker_client.html
   third_party_iframe1.html
   third_party_iframe2.html
   unit/test_abort_deleted_index.js
   unit/test_abort_deleted_objectStore.js
@@ -116,16 +118,22 @@ support-files =
   unit/test_wasm_put_get_values.js
   unit/test_writer_starvation.js
 
 [test_abort_deleted_index.html]
 [test_abort_deleted_objectStore.html]
 [test_add_put.html]
 [test_add_twice_failure.html]
 [test_advance.html]
+[test_app_isolation_inproc.html]
+# The app isolation tests are only supposed to run in the main process.
+skip-if = e10s
+[test_app_isolation_oop.html]
+# The app isolation tests are only supposed to run in the main process.
+skip-if = e10s
 [test_autoIncrement.html]
 [test_autoIncrement_indexes.html]
 [test_bfcache.html]
 [test_blob_archive.html]
 [test_blob_file_backed.html]
 [test_blob_simple.html]
 [test_blob_worker_crash.html]
 [test_blob_worker_xhr_post.html]
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_app_isolation_inproc.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=756645
+-->
+<head>
+  <title>Test for IndexedDB app isolation (unique process)</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=756645">Mozilla Bug 756645</a>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7" src="file_app_isolation.js">
+</script>
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/indexedDB/test/test_app_isolation_oop.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=756645
+-->
+<head>
+  <title>Test for IndexedDB app isolation (unique process)</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=756645">Mozilla Bug 756645</a>
+<div id="content" style="display: none">
+  
+</div>
+<pre id="test">
+<script type="application/javascript;version=1.7" src="file_app_isolation.js">
+</script>
+</pre>
+</body>
+</html>
--- a/dom/inputmethod/Keyboard.jsm
+++ b/dom/inputmethod/Keyboard.jsm
@@ -185,18 +185,18 @@ this.Keyboard = {
         SystemAppProxy.dispatchEvent({
           type: 'inputmethod-contextchange',
           inputType: 'blur'
         });
 
         this.formMM = null;
       }
     } else {
-      // Ignore notifications that aren't from a Browser
-      if (!frameLoader.ownerIsMozBrowserFrame) {
+      // Ignore notifications that aren't from a BrowserOrApp
+      if (!frameLoader.ownerIsMozBrowserOrAppFrame) {
         return;
       }
       this.initFormsFrameScript(mm);
     }
   },
 
   initFormsFrameScript: function(mm) {
     mm.addMessageListener('Forms:Focus', this);
--- a/dom/interfaces/html/nsIMozBrowserFrame.idl
+++ b/dom/interfaces/html/nsIMozBrowserFrame.idl
@@ -8,39 +8,56 @@
 #include "nsIDOMMozBrowserFrame.idl"
 
 interface nsITabParent;
 
 [scriptable, builtinclass, uuid(0c0a862c-1a47-43c0-ae9e-d51835e3e1a6)]
 interface nsIMozBrowserFrame : nsIDOMMozBrowserFrame
 {
   /**
-   * Gets whether this frame really is a browser frame.
+   * Gets whether this frame really is a browser or app frame.
    *
    * In order to really be a browser frame, this frame's
    * nsIDOMMozBrowserFrame::mozbrowser attribute must be true, and the frame
    * may have to pass various security checks.
    */
-  [infallible] readonly attribute boolean reallyIsBrowser;
+  [infallible] readonly attribute boolean reallyIsBrowserOrApp;
+
+  /**
+   * Gets whether this frame really is an app frame.
+   *
+   * In order to really be an app frame, this frame must really be a browser
+   * frame (this requirement will go away eventually), and  the frame's mozapp
+   * attribute must point to the manifest of a valid app.
+   */
+  [infallible] readonly attribute boolean reallyIsApp;
 
   /**
    * Gets whether this frame is an isolated frame.
    *
    * By default, browser frames are isolated, meaning they have a principal
    * where OriginAttributes.mIsInIsolatedMozBrowser == true.  This isolates
    * storage and other origin related items from non-browser apps, xul:browsers,
    * etc.
    *
    * Isolation can be disabled by setting the frame's isolated attribute to
    * false.  Disabling isolation is only allowed if the containing document has
    * browser permission (or equivalent access).
    */
   [infallible] readonly attribute boolean isolated;
 
   /**
+   * Gets this frame's app manifest URL, if the frame really is an app frame.
+   * Otherwise, returns the empty string.
+   *
+   * This method is guaranteed not to fail.
+   */
+  readonly attribute AString appManifestURL;
+
+  /**
    * Normally, a frame tries to create its frame loader when its src is
    * modified, or its contentWindow is accessed.
    *
    * disallowCreateFrameLoader prevents the frame element from creating its
    * frame loader (in the same way that not being inside a document prevents the
    * creation of a frame loader).  allowCreateFrameLoader lifts this restriction.
    *
    * These methods are not re-entrant -- it is an error to call
--- a/dom/ipc/ContentBridgeChild.cpp
+++ b/dom/ipc/ContentBridgeChild.cpp
@@ -73,23 +73,25 @@ ContentBridgeChild::SendPBlobConstructor
 }
 
 bool
 ContentBridgeChild::SendPBrowserConstructor(PBrowserChild* aActor,
                                             const TabId& aTabId,
                                             const IPCTabContext& aContext,
                                             const uint32_t& aChromeFlags,
                                             const ContentParentId& aCpID,
+                                            const bool& aIsForApp,
                                             const bool& aIsForBrowser)
 {
   return PContentBridgeChild::SendPBrowserConstructor(aActor,
                                                       aTabId,
                                                       aContext,
                                                       aChromeFlags,
                                                       aCpID,
+                                                      aIsForApp,
                                                       aIsForBrowser);
 }
 
 PFileDescriptorSetChild*
 ContentBridgeChild::SendPFileDescriptorSetConstructor(const FileDescriptor& aFD)
 {
   return PContentBridgeChild::SendPFileDescriptorSetConstructor(aFD);
 }
@@ -124,44 +126,48 @@ ContentBridgeChild::DeallocPJavaScriptCh
   return nsIContentChild::DeallocPJavaScriptChild(child);
 }
 
 PBrowserChild*
 ContentBridgeChild::AllocPBrowserChild(const TabId& aTabId,
                                        const IPCTabContext &aContext,
                                        const uint32_t& aChromeFlags,
                                        const ContentParentId& aCpID,
+                                       const bool& aIsForApp,
                                        const bool& aIsForBrowser)
 {
   return nsIContentChild::AllocPBrowserChild(aTabId,
                                              aContext,
                                              aChromeFlags,
                                              aCpID,
+                                             aIsForApp,
                                              aIsForBrowser);
 }
 
 bool
 ContentBridgeChild::DeallocPBrowserChild(PBrowserChild* aChild)
 {
   return nsIContentChild::DeallocPBrowserChild(aChild);
 }
 
 bool
 ContentBridgeChild::RecvPBrowserConstructor(PBrowserChild* aActor,
                                             const TabId& aTabId,
                                             const IPCTabContext& aContext,
                                             const uint32_t& aChromeFlags,
                                             const ContentParentId& aCpID,
+                                            const bool& aIsForApp,
                                             const bool& aIsForBrowser)
 {
   return ContentChild::GetSingleton()->RecvPBrowserConstructor(aActor,
                                                                aTabId,
                                                                aContext,
                                                                aChromeFlags,
                                                                aCpID,
+                                                               aIsForApp,
                                                                aIsForBrowser);
 }
 
 PBlobChild*
 ContentBridgeChild::AllocPBlobChild(const BlobConstructorParams& aParams)
 {
   return nsIContentChild::AllocPBlobChild(aParams);
 }
--- a/dom/ipc/ContentBridgeChild.h
+++ b/dom/ipc/ContentBridgeChild.h
@@ -38,16 +38,17 @@ public:
 
   jsipc::CPOWManager* GetCPOWManager() override;
 
   virtual bool SendPBrowserConstructor(PBrowserChild* aActor,
                                        const TabId& aTabId,
                                        const IPCTabContext& aContext,
                                        const uint32_t& aChromeFlags,
                                        const ContentParentId& aCpID,
+                                       const bool& aIsForApp,
                                        const bool& aIsForBrowser) override;
 
   virtual mozilla::ipc::PFileDescriptorSetChild*
   SendPFileDescriptorSetConstructor(const mozilla::ipc::FileDescriptor&) override;
 
   virtual mozilla::ipc::PSendStreamChild*
   SendPSendStreamConstructor(mozilla::ipc::PSendStreamChild*) override;
 
@@ -55,23 +56,25 @@ public:
 
 protected:
   virtual ~ContentBridgeChild();
 
   virtual PBrowserChild* AllocPBrowserChild(const TabId& aTabId,
                                             const IPCTabContext& aContext,
                                             const uint32_t& aChromeFlags,
                                             const ContentParentId& aCpID,
+                                            const bool& aIsForApp,
                                             const bool& aIsForBrowser) override;
   virtual bool DeallocPBrowserChild(PBrowserChild*) override;
   virtual bool RecvPBrowserConstructor(PBrowserChild* aCctor,
                                        const TabId& aTabId,
                                        const IPCTabContext& aContext,
                                        const uint32_t& aChromeFlags,
                                        const ContentParentId& aCpID,
+                                       const bool& aIsForApp,
                                        const bool& aIsForBrowser) override;
 
   virtual mozilla::jsipc::PJavaScriptChild* AllocPJavaScriptChild() override;
   virtual bool DeallocPJavaScriptChild(mozilla::jsipc::PJavaScriptChild*) override;
 
   virtual PBlobChild* AllocPBlobChild(const BlobConstructorParams& aParams) override;
   virtual bool DeallocPBlobChild(PBlobChild*) override;
 
--- a/dom/ipc/ContentBridgeParent.cpp
+++ b/dom/ipc/ContentBridgeParent.cpp
@@ -98,23 +98,25 @@ ContentBridgeParent::SendPBlobConstructo
 }
 
 PBrowserParent*
 ContentBridgeParent::SendPBrowserConstructor(PBrowserParent* aActor,
                                              const TabId& aTabId,
                                              const IPCTabContext& aContext,
                                              const uint32_t& aChromeFlags,
                                              const ContentParentId& aCpID,
+                                             const bool& aIsForApp,
                                              const bool& aIsForBrowser)
 {
   return PContentBridgeParent::SendPBrowserConstructor(aActor,
                                                        aTabId,
                                                        aContext,
                                                        aChromeFlags,
                                                        aCpID,
+                                                       aIsForApp,
                                                        aIsForBrowser);
 }
 
 PBlobParent*
 ContentBridgeParent::AllocPBlobParent(const BlobConstructorParams& aParams)
 {
   return nsIContentParent::AllocPBlobParent(aParams);
 }
@@ -137,22 +139,24 @@ ContentBridgeParent::DeallocPJavaScriptP
   return nsIContentParent::DeallocPJavaScriptParent(parent);
 }
 
 PBrowserParent*
 ContentBridgeParent::AllocPBrowserParent(const TabId& aTabId,
                                          const IPCTabContext &aContext,
                                          const uint32_t& aChromeFlags,
                                          const ContentParentId& aCpID,
+                                         const bool& aIsForApp,
                                          const bool& aIsForBrowser)
 {
   return nsIContentParent::AllocPBrowserParent(aTabId,
                                                aContext,
                                                aChromeFlags,
                                                aCpID,
+                                               aIsForApp,
                                                aIsForBrowser);
 }
 
 bool
 ContentBridgeParent::DeallocPBrowserParent(PBrowserParent* aParent)
 {
   return nsIContentParent::DeallocPBrowserParent(aParent);
 }
--- a/dom/ipc/ContentBridgeParent.h
+++ b/dom/ipc/ContentBridgeParent.h
@@ -38,26 +38,31 @@ public:
                        const BlobConstructorParams& params) override;
 
   virtual PBrowserParent*
   SendPBrowserConstructor(PBrowserParent* aActor,
                           const TabId& aTabId,
                           const IPCTabContext& aContext,
                           const uint32_t& aChromeFlags,
                           const ContentParentId& aCpID,
+                          const bool& aIsForApp,
                           const bool& aIsForBrowser) override;
 
   FORWARD_SHMEM_ALLOCATOR_TO(PContentBridgeParent)
 
   jsipc::CPOWManager* GetCPOWManager() override;
 
   virtual ContentParentId ChildID() const override
   {
     return mChildID;
   }
+  virtual bool IsForApp() const override
+  {
+    return mIsForApp;
+  }
   virtual bool IsForBrowser() const override
   {
     return mIsForBrowser;
   }
   virtual int32_t Pid() const override
   {
     // XXX: do we need this for ContentBridgeParent?
     return -1;
@@ -66,16 +71,21 @@ public:
 protected:
   virtual ~ContentBridgeParent();
 
   void SetChildID(ContentParentId aId)
   {
     mChildID = aId;
   }
 
+  void SetIsForApp(bool aIsForApp)
+  {
+    mIsForApp = aIsForApp;
+  }
+
   void SetIsForBrowser(bool aIsForBrowser)
   {
     mIsForBrowser = aIsForBrowser;
   }
 
   void Close()
   {
     // Trick NewRunnableMethod
@@ -100,16 +110,17 @@ protected:
   virtual bool
   DeallocPJavaScriptParent(jsipc::PJavaScriptParent*) override;
 
   virtual PBrowserParent*
   AllocPBrowserParent(const TabId& aTabId,
                       const IPCTabContext &aContext,
                       const uint32_t& aChromeFlags,
                       const ContentParentId& aCpID,
+                      const bool& aIsForApp,
                       const bool& aIsForBrowser) override;
 
   virtual bool DeallocPBrowserParent(PBrowserParent*) override;
 
   virtual PBlobParent*
   AllocPBlobParent(const BlobConstructorParams& aParams) override;
 
   virtual bool DeallocPBlobParent(PBlobParent*) override;
@@ -125,16 +136,17 @@ protected:
   DeallocPFileDescriptorSetParent(PFileDescriptorSetParent*) override;
 
   DISALLOW_EVIL_CONSTRUCTORS(ContentBridgeParent);
 
 protected: // members
   RefPtr<ContentBridgeParent> mSelfRef;
   Transport* mTransport; // owned
   ContentParentId mChildID;
+  bool mIsForApp;
   bool mIsForBrowser;
 
 private:
   friend class ContentParent;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -587,36 +587,36 @@ ContentChild::Init(MessageLoop* aIOLoop,
   SendBackUpXResources(FileDescriptor(xSocketFd));
 #endif
 
 #ifdef MOZ_CRASHREPORTER
   SendPCrashReporterConstructor(CrashReporter::CurrentThreadId(),
                                 XRE_GetProcessType());
 #endif
 
-  SendGetProcessAttributes(&mID, &mIsForBrowser);
+  SendGetProcessAttributes(&mID, &mIsForApp, &mIsForBrowser);
   InitProcessAttributes();
 
 #ifdef NS_PRINTING
   // Force the creation of the nsPrintingProxy so that it's IPC counterpart,
   // PrintingParent, is always available for printing initiated from the parent.
   RefPtr<nsPrintingProxy> printingProxy = nsPrintingProxy::GetInstance();
 #endif
 
   return true;
 }
 
 void
 ContentChild::InitProcessAttributes()
 {
 #ifdef MOZ_WIDGET_GONK
-  if (mIsForBrowser) {
+  if (mIsForApp && !mIsForBrowser) {
+    SetProcessName(NS_LITERAL_STRING("(Preallocated app)"), false);
+  } else {
     SetProcessName(NS_LITERAL_STRING("Browser"), false);
-  } else {
-    SetProcessName(NS_LITERAL_STRING("(Preallocated app)"), false);
   }
 #else
   SetProcessName(NS_LITERAL_STRING("Web Content"), true);
 #endif
 }
 
 void
 ContentChild::SetProcessName(const nsAString& aName, bool aDontOverride)
@@ -719,17 +719,17 @@ ContentChild::ProvideWindowCommon(TabChi
     MOZ_ASSERT(ipcContext->type() == IPCTabContext::TPopupIPCTabContext);
     ipcContext->get_PopupIPCTabContext().opener() = aTabOpener;
   }
 
   Unused << SendPBrowserConstructor(
     // We release this ref in DeallocPBrowserChild
     RefPtr<TabChild>(newChild).forget().take(),
     tabId, *ipcContext, aChromeFlags,
-    GetID(), IsForBrowser());
+    GetID(), IsForApp(), IsForBrowser());
 
   nsString name(aName);
   nsAutoCString features(aFeatures);
   nsTArray<FrameScriptInfo> frameScripts;
   nsCString urlToLoad;
 
   PRenderFrameChild* renderFrame = newChild->SendPRenderFrameConstructor();
   TextureFactoryIdentifier textureFactoryIdentifier;
@@ -1515,47 +1515,52 @@ ContentChild::DeallocPJavaScriptChild(PJ
   return nsIContentChild::DeallocPJavaScriptChild(aChild);
 }
 
 PBrowserChild*
 ContentChild::AllocPBrowserChild(const TabId& aTabId,
                                  const IPCTabContext& aContext,
                                  const uint32_t& aChromeFlags,
                                  const ContentParentId& aCpID,
+                                 const bool& aIsForApp,
                                  const bool& aIsForBrowser)
 {
   return nsIContentChild::AllocPBrowserChild(aTabId,
                                              aContext,
                                              aChromeFlags,
                                              aCpID,
+                                             aIsForApp,
                                              aIsForBrowser);
 }
 
 bool
 ContentChild::SendPBrowserConstructor(PBrowserChild* aActor,
                                       const TabId& aTabId,
                                       const IPCTabContext& aContext,
                                       const uint32_t& aChromeFlags,
                                       const ContentParentId& aCpID,
+                                      const bool& aIsForApp,
                                       const bool& aIsForBrowser)
 {
   return PContentChild::SendPBrowserConstructor(aActor,
                                                 aTabId,
                                                 aContext,
                                                 aChromeFlags,
                                                 aCpID,
+                                                aIsForApp,
                                                 aIsForBrowser);
 }
 
 bool
 ContentChild::RecvPBrowserConstructor(PBrowserChild* aActor,
                                       const TabId& aTabId,
                                       const IPCTabContext& aContext,
                                       const uint32_t& aChromeFlags,
                                       const ContentParentId& aCpID,
+                                      const bool& aIsForApp,
                                       const bool& aIsForBrowser)
 {
   // This runs after AllocPBrowserChild() returns and the IPC machinery for this
   // PBrowserChild has been set up.
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     nsITabChild* tc =
@@ -1570,16 +1575,17 @@ ContentChild::RecvPBrowserConstructor(PB
     MOZ_ASSERT(!sFirstIdleTask);
     RefPtr<CancelableRunnable> firstIdleTask = NewCancelableRunnableFunction(FirstIdle);
     sFirstIdleTask = firstIdleTask;
     MessageLoop::current()->PostIdleTask(firstIdleTask.forget());
 
     // Redo InitProcessAttributes() when the app or browser is really
     // launching so the attributes will be correct.
     mID = aCpID;
+    mIsForApp = aIsForApp;
     mIsForBrowser = aIsForBrowser;
     InitProcessAttributes();
   }
 
   return true;
 }
 
 void
@@ -2429,17 +2435,17 @@ ContentChild::RecvAppInit()
     return true;
   }
 
   // If we're part of the mozbrowser machinery, go ahead and start
   // preloading things.  We can only do this for mozbrowser because
   // PreloadSlowThings() may set the docshell of the first TabChild
   // inactive, and we can only safely restore it to active from
   // BrowserElementChild.js.
-  if (mIsForBrowser) {
+  if (mIsForApp || mIsForBrowser) {
     PreloadSlowThings();
   }
 
   return true;
 }
 
 bool
 ContentChild::RecvInitServiceWorkers(const ServiceWorkerConfiguration& aConfig)
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -188,16 +188,17 @@ public:
   PBackgroundChild*
   AllocPBackgroundChild(Transport* aTransport, ProcessId aOtherProcess)
                         override;
 
   virtual PBrowserChild* AllocPBrowserChild(const TabId& aTabId,
                                             const IPCTabContext& aContext,
                                             const uint32_t& aChromeFlags,
                                             const ContentParentId& aCpID,
+                                            const bool& aIsForApp,
                                             const bool& aIsForBrowser) override;
 
   virtual bool DeallocPBrowserChild(PBrowserChild*) override;
 
   virtual PDeviceStorageRequestChild*
   AllocPDeviceStorageRequestChild(const DeviceStorageParams&) override;
 
   virtual bool
@@ -536,16 +537,17 @@ public:
   nsString &GetIndexedDBPath();
 
   ContentParentId GetID() const { return mID; }
 
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
   uint32_t GetMsaaID() const { return mMsaaID; }
 #endif
 
+  bool IsForApp() const { return mIsForApp; }
   bool IsForBrowser() const { return mIsForBrowser; }
 
   virtual PBlobChild*
   SendPBlobConstructor(PBlobChild* actor,
                        const BlobConstructorParams& params) override;
 
   virtual PFileDescriptorSetChild*
   SendPFileDescriptorSetConstructor(const FileDescriptor&) override;
@@ -556,23 +558,25 @@ public:
   virtual bool
   DeallocPFileDescriptorSetChild(PFileDescriptorSetChild*) override;
 
   virtual bool SendPBrowserConstructor(PBrowserChild* actor,
                                        const TabId& aTabId,
                                        const IPCTabContext& context,
                                        const uint32_t& chromeFlags,
                                        const ContentParentId& aCpID,
+                                       const bool& aIsForApp,
                                        const bool& aIsForBrowser) override;
 
   virtual bool RecvPBrowserConstructor(PBrowserChild* aCctor,
                                        const TabId& aTabId,
                                        const IPCTabContext& aContext,
                                        const uint32_t& aChromeFlags,
                                        const ContentParentId& aCpID,
+                                       const bool& aIsForApp,
                                        const bool& aIsForBrowser) override;
 
   FORWARD_SHMEM_ALLOCATOR_TO(PContentChild)
 
   void GetAvailableDictionaries(InfallibleTArray<nsString>& aDictionaries);
 
   PBrowserOrId
   GetBrowserOrId(TabChild* aTabChild);
@@ -680,16 +684,17 @@ private:
    * This is an a11y-specific unique id for the content process that is
    * generated by the chrome process.
    */
   uint32_t mMsaaID;
 #endif
 
   AppInfo mAppInfo;
 
+  bool mIsForApp;
   bool mIsForBrowser;
   bool mCanOverrideProcessName;
   bool mIsAlive;
   nsString mProcessName;
 
   static ContentChild* sSingleton;
 
   nsCOMPtr<nsIDomainPolicy> mPolicy;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -28,16 +28,17 @@
 #include "AudioChannelService.h"
 #include "BlobParent.h"
 #include "CrashReporterParent.h"
 #include "DeviceStorageStatics.h"
 #include "GMPServiceParent.h"
 #include "HandlerServiceParent.h"
 #include "IHistory.h"
 #include "imgIContainer.h"
+#include "mozIApplication.h"
 #if defined(XP_WIN) && defined(ACCESSIBILITY)
 #include "mozilla/a11y/AccessibleWrap.h"
 #include "mozilla/WindowsVersion.h"
 #endif
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/StyleSheetInlines.h"
 #include "mozilla/DataStorage.h"
 #include "mozilla/devtools/HeapSnapshotTempFileHelperParent.h"
@@ -499,17 +500,18 @@ ContentParentsMemoryReporter::CollectRep
     aHandleReport->Callback(/* process */ EmptyCString(), path,
                             KIND_OTHER, UNITS_COUNT,
                             numQueuedMessages, desc, aData);
   }
 
   return NS_OK;
 }
 
-nsTArray<ContentParent*>* ContentParent::sBrowserContentParents;
+nsDataHashtable<nsStringHashKey, ContentParent*>* ContentParent::sAppContentParents;
+nsTArray<ContentParent*>* ContentParent::sNonAppContentParents;
 nsTArray<ContentParent*>* ContentParent::sLargeAllocationContentParents;
 nsTArray<ContentParent*>* ContentParent::sPrivateContent;
 StaticAutoPtr<LinkedList<ContentParent> > ContentParent::sContentParents;
 #if defined(XP_LINUX) && defined(MOZ_CONTENT_SANDBOX)
 UniquePtr<SandboxBrokerPolicyFactory> ContentParent::sSandboxBrokerPolicyFactory;
 #endif
 
 // This is true when subprocess launching is enabled.  This is the
@@ -518,16 +520,21 @@ static bool sCanLaunchSubprocesses;
 
 // Set to true if the DISABLE_UNSAFE_CPOW_WARNINGS environment variable is
 // set.
 static bool sDisableUnsafeCPOWWarnings = false;
 
 // The first content child has ID 1, so the chrome process can have ID 0.
 static uint64_t gContentChildID = 1;
 
+// We want the prelaunched process to know that it's for apps, but not
+// actually for any app in particular.  Use a magic manifest URL.
+// Can't be a static constant.
+#define MAGIC_PREALLOCATED_APP_MANIFEST_URL NS_LITERAL_STRING("{{template}}")
+
 static const char* sObserverTopics[] = {
   "xpcom-shutdown",
   "profile-before-change",
   NS_IPC_IOSERVICE_SET_OFFLINE_TOPIC,
   NS_IPC_IOSERVICE_SET_CONNECTIVITY_TOPIC,
   "memory-pressure",
   "child-gc-request",
   "child-cc-request",
@@ -555,28 +562,80 @@ static const char* sObserverTopics[] = {
 
 // PreallocateAppProcess is called by the PreallocatedProcessManager.
 // ContentParent then takes this process back within
 // GetNewOrPreallocatedAppProcess.
 /*static*/ already_AddRefed<ContentParent>
 ContentParent::PreallocateAppProcess()
 {
   RefPtr<ContentParent> process =
-    new ContentParent(/* aOpener = */ nullptr,
+    new ContentParent(/* app = */ nullptr,
+                      /* aOpener = */ nullptr,
                       /* isForBrowserElement = */ false,
                       /* isForPreallocated = */ true);
 
   if (!process->LaunchSubprocess(PROCESS_PRIORITY_PREALLOC)) {
     return nullptr;
   }
 
   process->Init();
   return process.forget();
 }
 
+/*static*/ already_AddRefed<ContentParent>
+ContentParent::GetNewOrPreallocatedAppProcess(mozIApplication* aApp,
+                                              ProcessPriority aInitialPriority,
+                                              ContentParent* aOpener,
+                                              /*out*/ bool* aTookPreAllocated)
+{
+  MOZ_ASSERT(aApp);
+  RefPtr<ContentParent> process = PreallocatedProcessManager::Take();
+
+  if (process) {
+    if (!process->SetPriorityAndCheckIsAlive(aInitialPriority)) {
+      // Kill the process just in case it's not actually dead; we don't want
+      // to "leak" this process!
+      process->KillHard("GetNewOrPreallocatedAppProcess");
+    }
+    else {
+      nsAutoString manifestURL;
+      if (NS_FAILED(aApp->GetManifestURL(manifestURL))) {
+        NS_ERROR("Failed to get manifest URL");
+        return nullptr;
+      }
+      process->TransformPreallocatedIntoApp(aOpener, manifestURL);
+      process->ForwardKnownInfo();
+
+      if (aTookPreAllocated) {
+        *aTookPreAllocated = true;
+      }
+      return process.forget();
+    }
+  }
+
+  NS_WARNING("Unable to use pre-allocated app process");
+  process = new ContentParent(aApp,
+                              /* aOpener = */ aOpener,
+                              /* isForBrowserElement = */ false,
+                              /* isForPreallocated = */ false);
+
+  if (!process->LaunchSubprocess(aInitialPriority)) {
+    return nullptr;
+  }
+
+  process->Init();
+  process->ForwardKnownInfo();
+
+  if (aTookPreAllocated) {
+    *aTookPreAllocated = false;
+  }
+
+  return process.forget();
+}
+
 /*static*/ void
 ContentParent::StartUp()
 {
   // We could launch sub processes from content process
   // FIXME Bug 1023701 - Stop using ContentParent static methods in
   // child process
   sCanLaunchSubprocesses = true;
 
@@ -688,50 +747,51 @@ ContentParent::GetNewOrUsedBrowserProces
   if (aLargeAllocationProcess) {
     if (!sLargeAllocationContentParents) {
       sLargeAllocationContentParents = new nsTArray<ContentParent*>();
     }
     contentParents = sLargeAllocationContentParents;
 
     maxContentParents = Preferences::GetInt("dom.ipc.dedicatedProcessCount", 2);
   } else {
-    if (!sBrowserContentParents) {
-      sBrowserContentParents = new nsTArray<ContentParent*>();
+    if (!sNonAppContentParents) {
+      sNonAppContentParents = new nsTArray<ContentParent*>();
     }
-    contentParents = sBrowserContentParents;
+    contentParents = sNonAppContentParents;
 
     maxContentParents = Preferences::GetInt("dom.ipc.processCount", 1);
   }
 
   if (maxContentParents < 1) {
     maxContentParents = 1;
   }
 
   if (contentParents->Length() >= uint32_t(maxContentParents)) {
     uint32_t maxSelectable = std::min(static_cast<uint32_t>(contentParents->Length()),
                                       static_cast<uint32_t>(maxContentParents));
     uint32_t startIdx = rand() % maxSelectable;
     uint32_t currIdx = startIdx;
     do {
       RefPtr<ContentParent> p = (*contentParents)[currIdx];
-      NS_ASSERTION(p->IsAlive(), "Non-alive contentparent in sBrowserContentParents?");
+      NS_ASSERTION(p->IsAlive(), "Non-alive contentparent in sNonAppContntParents?");
       if (p->mOpener == aOpener) {
         return p.forget();
       }
       currIdx = (currIdx + 1) % maxSelectable;
     } while (currIdx != startIdx);
   }
 
   // Try to take and transform the preallocated process into browser.
   RefPtr<ContentParent> p = PreallocatedProcessManager::Take();
   if (p) {
     p->TransformPreallocatedIntoBrowser(aOpener);
   } else {
     // Failed in using the preallocated process: fork from the chrome process.
-    p = new ContentParent(aOpener,
+    p = new ContentParent(/* app = */ nullptr,
+                          aOpener,
                           aForBrowserElement,
                           /* isForPreallocated = */ false);
 
     if (!p->LaunchSubprocess(aPriority)) {
       return nullptr;
     }
 
     p->Init();
@@ -797,16 +857,17 @@ ContentParent::PreallocatedProcessReady(
   return true;
 }
 
 bool
 ContentParent::RecvCreateChildProcess(const IPCTabContext& aContext,
                                       const hal::ProcessPriority& aPriority,
                                       const TabId& aOpenerTabId,
                                       ContentParentId* aCpId,
+                                      bool* aIsForApp,
                                       bool* aIsForBrowser,
                                       TabId* aTabId)
 {
 #if 0
   if (!CanOpenBrowser(aContext)) {
       return false;
   }
 #endif
@@ -814,26 +875,34 @@ ContentParent::RecvCreateChildProcess(co
   MaybeInvalidTabContext tc(aContext);
   if (!tc.IsValid()) {
     NS_ERROR(nsPrintfCString("Received an invalid TabContext from "
                              "the child process. (%s)",
                              tc.GetInvalidReason()).get());
     return false;
   }
 
-  cp = GetNewOrUsedBrowserProcess(/* isBrowserElement = */ true,
-                                  aPriority, this);
+  nsCOMPtr<mozIApplication> ownApp = tc.GetTabContext().GetOwnApp();
+  if (ownApp) {
+    cp = GetNewOrPreallocatedAppProcess(ownApp, aPriority, this);
+  }
+  else {
+   cp = GetNewOrUsedBrowserProcess(/* isBrowserElement = */ true,
+                                   aPriority, this);
+  }
 
   if (!cp) {
     *aCpId = 0;
+    *aIsForApp = false;
     *aIsForBrowser = false;
     return true;
   }
 
   *aCpId = cp->ChildID();
+  *aIsForApp = cp->IsForApp();
   *aIsForBrowser = cp->IsForBrowser();
 
   ContentProcessManager *cpm = ContentProcessManager::GetSingleton();
   cpm->AddContentProcess(cp, this->ChildID());
 
   if (cpm->AddGrandchildProcess(this->ChildID(), cp->ChildID())) {
     // Pre-allocate a TabId here to save one time IPC call at app startup.
     *aTabId = AllocateTabId(aOpenerTabId, aContext, cp->ChildID());
@@ -954,20 +1023,20 @@ ContentParent::RecvFindPlugins(const uin
                                nsTArray<PluginTag>* aPlugins,
                                uint32_t* aNewPluginEpoch)
 {
   *aRv = mozilla::plugins::FindPluginsForContent(aPluginEpoch, aPlugins, aNewPluginEpoch);
   return true;
 }
 
 /*static*/ TabParent*
-ContentParent::CreateBrowser(const TabContext& aContext,
-                             Element* aFrameElement,
-                             ContentParent* aOpenerContentParent,
-                             bool aFreshProcess)
+ContentParent::CreateBrowserOrApp(const TabContext& aContext,
+                                  Element* aFrameElement,
+                                  ContentParent* aOpenerContentParent,
+                                  bool aFreshProcess)
 {
   PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
 
   if (!sCanLaunchSubprocesses) {
     return nullptr;
   }
 
   if (TabParent* parent = TabParent::GetNextTabParent()) {
@@ -980,114 +1049,264 @@ ContentParent::CreateBrowser(const TabCo
   TabId tabId;
 
   nsIDocShell* docShell = GetOpenerDocShellHelper(aFrameElement);
   TabId openerTabId;
   if (docShell) {
     openerTabId = TabParent::GetTabIdFrom(docShell);
   }
 
-  RefPtr<nsIContentParent> constructorSender;
-  if (isInContentProcess) {
-    MOZ_ASSERT(aContext.IsMozBrowserElement());
-    constructorSender = CreateContentBridgeParent(aContext, initialPriority,
-                                                  openerTabId, &tabId);
-  } else {
-    if (aOpenerContentParent) {
-      constructorSender = aOpenerContentParent;
+  if (aContext.IsMozBrowserElement() || !aContext.HasOwnApp()) {
+    RefPtr<nsIContentParent> constructorSender;
+    if (isInContentProcess) {
+      MOZ_ASSERT(aContext.IsMozBrowserElement());
+      constructorSender = CreateContentBridgeParent(aContext, initialPriority,
+                                                    openerTabId, &tabId);
     } else {
-      constructorSender =
-        GetNewOrUsedBrowserProcess(aContext.IsMozBrowserElement(),
-                                   initialPriority, nullptr, aFreshProcess);
-      if (!constructorSender) {
+      if (aOpenerContentParent) {
+        constructorSender = aOpenerContentParent;
+      } else {
+        constructorSender =
+          GetNewOrUsedBrowserProcess(aContext.IsMozBrowserElement(),
+                                     initialPriority,
+                                     nullptr,
+                                     aFreshProcess);
+        if (!constructorSender) {
+          return nullptr;
+        }
+      }
+      tabId = AllocateTabId(openerTabId,
+                            aContext.AsIPCTabContext(),
+                            constructorSender->ChildID());
+    }
+    if (constructorSender) {
+      nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
+      docShell->GetTreeOwner(getter_AddRefs(treeOwner));
+      if (!treeOwner) {
+        return nullptr;
+      }
+
+      nsCOMPtr<nsIWebBrowserChrome> wbc = do_GetInterface(treeOwner);
+      if (!wbc) {
+        return nullptr;
+      }
+      uint32_t chromeFlags = 0;
+      wbc->GetChromeFlags(&chromeFlags);
+
+      nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
+      if (loadContext && loadContext->UsePrivateBrowsing()) {
+        chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
+      }
+      if (docShell->GetAffectPrivateSessionLifetime()) {
+        chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME;
+      }
+
+      if (tabId == 0) {
         return nullptr;
       }
+      RefPtr<TabParent> tp(new TabParent(constructorSender, tabId,
+                                         aContext, chromeFlags));
+      tp->SetInitedByParent();
+
+      PBrowserParent* browser =
+      constructorSender->SendPBrowserConstructor(
+        // DeallocPBrowserParent() releases this ref.
+        tp.forget().take(), tabId,
+        aContext.AsIPCTabContext(),
+        chromeFlags,
+        constructorSender->ChildID(),
+        constructorSender->IsForApp(),
+        constructorSender->IsForBrowser());
+
+      if (aFreshProcess) {
+        Unused << browser->SendSetFreshProcess();
+      }
+
+      if (browser) {
+        RefPtr<TabParent> constructedTabParent = TabParent::GetFrom(browser);
+        constructedTabParent->SetOwnerElement(aFrameElement);
+        return constructedTabParent;
+      }
     }
-    tabId = AllocateTabId(openerTabId,
-                          aContext.AsIPCTabContext(),
-                          constructorSender->ChildID());
-  }
-  if (constructorSender) {
-    nsCOMPtr<nsIDocShellTreeOwner> treeOwner;
-    docShell->GetTreeOwner(getter_AddRefs(treeOwner));
-    if (!treeOwner) {
-      return nullptr;
+    return nullptr;
+  }
+
+  // If we got here, we have an app and we're not a browser element.  ownApp
+  // shouldn't be null, because we otherwise would have gone into the
+  // !HasOwnApp() branch above.
+  RefPtr<nsIContentParent> parent;
+  bool reused = false;
+  bool tookPreallocated = false;
+  nsAutoString manifestURL;
+
+  if (isInContentProcess) {
+    parent = CreateContentBridgeParent(aContext,
+                                       initialPriority,
+                                       openerTabId,
+                                       &tabId);
+  }
+  else {
+    nsCOMPtr<mozIApplication> ownApp = aContext.GetOwnApp();
+
+    if (!sAppContentParents) {
+      sAppContentParents =
+        new nsDataHashtable<nsStringHashKey, ContentParent*>();
     }
 
-    nsCOMPtr<nsIWebBrowserChrome> wbc = do_GetInterface(treeOwner);
-    if (!wbc) {
+    // Each app gets its own ContentParent instance unless it shares it with
+    // a parent app.
+    if (NS_FAILED(ownApp->GetManifestURL(manifestURL))) {
+      NS_ERROR("Failed to get manifest URL");
       return nullptr;
     }
-    uint32_t chromeFlags = 0;
-    wbc->GetChromeFlags(&chromeFlags);
-
-    nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(docShell);
-    if (loadContext && loadContext->UsePrivateBrowsing()) {
-      chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_WINDOW;
+
+    RefPtr<ContentParent> p = sAppContentParents->Get(manifestURL);
+
+    if (!p && Preferences::GetBool("dom.ipc.reuse_parent_app")) {
+      nsAutoString parentAppManifestURL;
+      aFrameElement->GetAttr(kNameSpaceID_None,
+                             nsGkAtoms::parentapp, parentAppManifestURL);
+      nsAdoptingString systemAppManifestURL =
+        Preferences::GetString("b2g.system_manifest_url");
+      nsCOMPtr<nsIAppsService> appsService =
+        do_GetService(APPS_SERVICE_CONTRACTID);
+      if (!parentAppManifestURL.IsEmpty() &&
+        !parentAppManifestURL.Equals(systemAppManifestURL) &&
+        appsService) {
+        nsCOMPtr<mozIApplication> parentApp;
+        nsCOMPtr<mozIApplication> app;
+        appsService->GetAppByManifestURL(parentAppManifestURL,
+                                         getter_AddRefs(parentApp));
+        appsService->GetAppByManifestURL(manifestURL,
+                                         getter_AddRefs(app));
+
+        // Only let certified apps re-use the same process.
+        unsigned short parentAppStatus = 0;
+        unsigned short appStatus = 0;
+        if (app &&
+          NS_SUCCEEDED(app->GetAppStatus(&appStatus)) &&
+          appStatus == nsIPrincipal::APP_STATUS_CERTIFIED &&
+          parentApp &&
+          NS_SUCCEEDED(parentApp->GetAppStatus(&parentAppStatus)) &&
+          parentAppStatus == nsIPrincipal::APP_STATUS_CERTIFIED) {
+          // Check if we can re-use the process of the parent app.
+          p = sAppContentParents->Get(parentAppManifestURL);
+        }
+      }
     }
-    if (docShell->GetAffectPrivateSessionLifetime()) {
-      chromeFlags |= nsIWebBrowserChrome::CHROME_PRIVATE_LIFETIME;
+
+    if (p) {
+      // Check that the process is still alive and set its priority.
+      // Hopefully the process won't die after this point, if this call
+      // succeeds.
+      if (!p->SetPriorityAndCheckIsAlive(initialPriority)) {
+        p = nullptr;
+      }
     }
 
-    if (tabId == 0) {
-      return nullptr;
+    reused = !!p;
+    if (!p) {
+      p = GetNewOrPreallocatedAppProcess(ownApp, initialPriority, nullptr,
+                                         &tookPreallocated);
+      MOZ_ASSERT(p);
+      sAppContentParents->Put(manifestURL, p);
     }
-    RefPtr<TabParent> tp(new TabParent(constructorSender, tabId,
-                                       aContext, chromeFlags));
-    tp->SetInitedByParent();
-
-    PBrowserParent* browser =
-    constructorSender->SendPBrowserConstructor(
-      // DeallocPBrowserParent() releases this ref.
-      tp.forget().take(), tabId,
-      aContext.AsIPCTabContext(),
-      chromeFlags,
-      constructorSender->ChildID(),
-      constructorSender->IsForBrowser());
-
-    if (aFreshProcess) {
-      Unused << browser->SendSetFreshProcess();
+    tabId = AllocateTabId(openerTabId, aContext.AsIPCTabContext(),
+                          p->ChildID());
+    parent = static_cast<nsIContentParent*>(p);
+  }
+
+  if (!parent || (tabId == 0)) {
+    return nullptr;
+  }
+
+  uint32_t chromeFlags = 0;
+
+  RefPtr<TabParent> tp = new TabParent(parent, tabId, aContext, chromeFlags);
+  tp->SetInitedByParent();
+  PBrowserParent* browser = parent->SendPBrowserConstructor(
+    // DeallocPBrowserParent() releases this ref.
+    RefPtr<TabParent>(tp).forget().take(),
+    tabId,
+    aContext.AsIPCTabContext(),
+    chromeFlags,
+    parent->ChildID(),
+    parent->IsForApp(),
+    parent->IsForBrowser());
+
+  if (aFreshProcess) {
+    Unused << browser->SendSetFreshProcess();
+  }
+
+  if (browser) {
+    RefPtr<TabParent> constructedTabParent = TabParent::GetFrom(browser);
+    constructedTabParent->SetOwnerElement(aFrameElement);
+  }
+
+  if (isInContentProcess) {
+    // Just return directly without the following check in content process.
+    return TabParent::GetFrom(browser);
+  }
+
+  if (!browser) {
+    // We failed to actually start the PBrowser.  This can happen if the
+    // other process has already died.
+    if (!reused) {
+      // Don't leave a broken ContentParent in the hashtable.
+      parent->AsContentParent()->KillHard("CreateBrowserOrApp");
+      sAppContentParents->Remove(manifestURL);
+      parent = nullptr;
     }
 
-    if (browser) {
-      RefPtr<TabParent> constructedTabParent = TabParent::GetFrom(browser);
-      constructedTabParent->SetOwnerElement(aFrameElement);
-      return constructedTabParent;
+    // If we took the preallocated process and it was already dead, try
+    // again with a non-preallocated process.  We can be sure this won't
+    // loop forever, because the next time through there will be no
+    // preallocated process to take.
+    if (tookPreallocated) {
+      return ContentParent::CreateBrowserOrApp(aContext, aFrameElement,
+                                               aOpenerContentParent);
     }
-  }
-  return nullptr;
+
+    // Otherwise just give up.
+    return nullptr;
+  }
+
+  return TabParent::GetFrom(browser);
 }
 
 /*static*/ ContentBridgeParent*
 ContentParent::CreateContentBridgeParent(const TabContext& aContext,
                                          const hal::ProcessPriority& aPriority,
                                          const TabId& aOpenerTabId,
                                          /*out*/ TabId* aTabId)
 {
   MOZ_ASSERT(aTabId);
 
   ContentChild* child = ContentChild::GetSingleton();
   ContentParentId cpId;
+  bool isForApp;
   bool isForBrowser;
   if (!child->SendCreateChildProcess(aContext.AsIPCTabContext(),
                                      aPriority,
                                      aOpenerTabId,
                                      &cpId,
+                                     &isForApp,
                                      &isForBrowser,
                                      aTabId)) {
     return nullptr;
   }
   if (cpId == 0) {
     return nullptr;
   }
   if (!child->SendBridgeToChildProcess(cpId)) {
     return nullptr;
   }
   ContentBridgeParent* parent = child->GetLastBridge();
   parent->SetChildID(cpId);
+  parent->SetIsForApp(isForApp);
   parent->SetIsForBrowser(isForBrowser);
   return parent;
 }
 
 void
 ContentParent::GetAll(nsTArray<ContentParent*>& aArray)
 {
   aArray.Clear();
@@ -1240,22 +1459,58 @@ ContentParent::SetPriorityAndCheckIsAliv
     && info.si_pid != 0) {
     return false;
   }
 #endif
 
   return true;
 }
 
+// Helper for ContentParent::TransformPreallocatedIntoApp.
+static void
+TryGetNameFromManifestURL(const nsAString& aManifestURL,
+              nsAString& aName)
+{
+  aName.Truncate();
+  if (aManifestURL.IsEmpty() ||
+    aManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL) {
+    return;
+  }
+
+  nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE_VOID(appsService);
+
+  nsCOMPtr<mozIApplication> app;
+  appsService->GetAppByManifestURL(aManifestURL, getter_AddRefs(app));
+
+  if (!app) {
+    return;
+  }
+
+  app->GetName(aName);
+}
+
+void
+ContentParent::TransformPreallocatedIntoApp(ContentParent* aOpener,
+                                            const nsAString& aAppManifestURL)
+{
+  MOZ_ASSERT(IsPreallocated());
+  mMetamorphosed = true;
+  mOpener = aOpener;
+  mAppManifestURL = aAppManifestURL;
+  TryGetNameFromManifestURL(aAppManifestURL, mAppName);
+}
+
 void
 ContentParent::TransformPreallocatedIntoBrowser(ContentParent* aOpener)
 {
-  // Reset mIsForBrowser and mOSPrivileges for browser.
+  // Reset mAppManifestURL, mIsForBrowser and mOSPrivileges for browser.
   mMetamorphosed = true;
   mOpener = aOpener;
+  mAppManifestURL.Truncate();
   mIsForBrowser = true;
 }
 
 void
 ContentParent::ShutDownProcess(ShutDownMethod aMethod)
 {
   // Shutting down by sending a shutdown message works differently than the
   // other methods. We first call Shutdown() in the child. After the child is
@@ -1332,29 +1587,39 @@ ContentParent::ShutDownMessageManager()
 
   mMessageManager->Disconnect();
   mMessageManager = nullptr;
 }
 
 void
 ContentParent::MarkAsDead()
 {
-  if (sBrowserContentParents) {
-    sBrowserContentParents->RemoveElement(this);
-    if (!sBrowserContentParents->Length()) {
-      delete sBrowserContentParents;
-      sBrowserContentParents = nullptr;
+  if (!mAppManifestURL.IsEmpty()) {
+    if (sAppContentParents) {
+      sAppContentParents->Remove(mAppManifestURL);
+      if (!sAppContentParents->Count()) {
+        delete sAppContentParents;
+        sAppContentParents = nullptr;
+      }
     }
-  }
-
-  if (sLargeAllocationContentParents) {
-    sLargeAllocationContentParents->RemoveElement(this);
-    if (!sLargeAllocationContentParents->Length()) {
-      delete sLargeAllocationContentParents;
-      sLargeAllocationContentParents = nullptr;
+  } else {
+    if (sNonAppContentParents) {
+      sNonAppContentParents->RemoveElement(this);
+      if (!sNonAppContentParents->Length()) {
+        delete sNonAppContentParents;
+        sNonAppContentParents = nullptr;
+      }
+    }
+
+    if (sLargeAllocationContentParents) {
+      sLargeAllocationContentParents->RemoveElement(this);
+      if (!sLargeAllocationContentParents->Length()) {
+        delete sLargeAllocationContentParents;
+        sLargeAllocationContentParents = nullptr;
+      }
     }
   }
 
   if (sPrivateContent) {
     sPrivateContent->RemoveElement(this);
     if (!sPrivateContent->Length()) {
       delete sPrivateContent;
       sPrivateContent = nullptr;
@@ -1579,16 +1844,27 @@ ContentParent::ActorDestroy(ActorDestroy
 #ifdef MOZ_CRASHREPORTER
       // There's a window in which child processes can crash
       // after IPC is established, but before a crash reporter
       // is created.
       if (PCrashReporterParent* p = LoneManagedOrNullAsserts(ManagedPCrashReporterParent())) {
         CrashReporterParent* crashReporter =
           static_cast<CrashReporterParent*>(p);
 
+        // If we're an app process, always stomp the latest URI
+        // loaded in the child process with our manifest URL.  We
+        // would rather associate the crashes with apps than
+        // random child windows loaded in them.
+        //
+        // XXX would be nice if we could get both ...
+        if (!mAppManifestURL.IsEmpty()) {
+          crashReporter->AnnotateCrashReport(NS_LITERAL_CSTRING("URL"),
+                                             NS_ConvertUTF16toUTF8(mAppManifestURL));
+        }
+
         // if mCreatedPairedMinidumps is true, we've already generated
         // parent/child dumps for dekstop crashes.
         if (!mCreatedPairedMinidumps) {
           crashReporter->GenerateCrashReport(this, nullptr);
         }
 
         nsAutoString dumpID(crashReporter->ChildDumpID());
         props->SetPropertyAsAString(NS_LITERAL_STRING("dumpID"), dumpID);
@@ -1670,17 +1946,17 @@ ContentParent::NotifyTabDestroying(const
         return;
     }
     ++cp->mNumDestroyingTabs;
     nsTArray<TabId> tabIds = cpm->GetTabParentsByProcessId(aCpId);
     if (static_cast<size_t>(cp->mNumDestroyingTabs) != tabIds.Length()) {
         return;
     }
 
-    uint32_t numberOfParents = sBrowserContentParents ? sBrowserContentParents->Length() : 0;
+    uint32_t numberOfParents = sNonAppContentParents ? sNonAppContentParents->Length() : 0;
     int32_t processesToKeepAlive = Preferences::GetInt("dom.ipc.keepProcessesAlive", 0);
     if (!cp->mLargeAllocationProcess && static_cast<int32_t>(numberOfParents) <= processesToKeepAlive) {
       return;
     }
 
     // We're dying now, so prevent this content process from being
     // recycled during its shutdown procedure.
     cp->MarkAsDead();
@@ -1727,17 +2003,17 @@ ContentParent::NotifyTabDestroyed(const 
   // There can be more than one PBrowser for a given app process
   // because of popup windows.  When the last one closes, shut
   // us down.
   ContentProcessManager* cpm = ContentProcessManager::GetSingleton();
   nsTArray<TabId> tabIds = cpm->GetTabParentsByProcessId(this->ChildID());
 
   // We might want to keep alive some content processes for testing, because of performance
   // reasons, but we don't want to alter behavior if the pref is not set.
-  uint32_t numberOfParents = sBrowserContentParents ? sBrowserContentParents->Length() : 0;
+  uint32_t numberOfParents = sNonAppContentParents ? sNonAppContentParents->Length() : 0;
   int32_t processesToKeepAlive = Preferences::GetInt("dom.ipc.keepProcessesAlive", 0);
   bool shouldKeepAliveAny = !mLargeAllocationProcess && processesToKeepAlive > 0;
   bool shouldKeepAliveThis = shouldKeepAliveAny && static_cast<int32_t>(numberOfParents) <= processesToKeepAlive;
 
   if (tabIds.Length() == 1 && !shouldKeepAliveThis) {
     // In the case of normal shutdown, send a shutdown message to child to
     // allow it to perform shutdown tasks.
     MessageLoop::current()->PostTask(NewRunnableMethod
@@ -1816,39 +2092,46 @@ ContentParent::LaunchSubprocess(ProcessP
   ProcessHangMonitor::AddProcess(this);
 
   // Set a reply timeout for CPOWs.
   SetReplyTimeoutMs(Preferences::GetInt("dom.ipc.cpow.timeout", 0));
 
   return true;
 }
 
-ContentParent::ContentParent(ContentParent* aOpener,
+ContentParent::ContentParent(mozIApplication* aApp,
+                             ContentParent* aOpener,
                              bool aIsForBrowser,
                              bool aIsForPreallocated)
   : nsIContentParent()
   , mOpener(aOpener)
   , mIsForBrowser(aIsForBrowser)
-  , mIsPreallocated(aIsForPreallocated)
   , mLargeAllocationProcess(false)
 {
   InitializeMembers();  // Perform common initialization.
 
-  // No more than one of aIsForBrowser and aIsForPreallocated should be
+  // No more than one of !!aApp, aIsForBrowser, aIsForPreallocated should be
   // true.
-  MOZ_ASSERT(aIsForBrowser + aIsForPreallocated <= 1);
+  MOZ_ASSERT(!!aApp + aIsForBrowser + aIsForPreallocated <= 1);
 
   mMetamorphosed = true;
 
   // Insert ourselves into the global linked list of ContentParent objects.
   if (!sContentParents) {
     sContentParents = new LinkedList<ContentParent>();
   }
   sContentParents->insertBack(this);
 
+  if (aApp) {
+    aApp->GetManifestURL(mAppManifestURL);
+    aApp->GetName(mAppName);
+  } else if (aIsForPreallocated) {
+    mAppManifestURL = MAGIC_PREALLOCATED_APP_MANIFEST_URL;
+  }
+
   // From this point on, NS_WARNING, NS_ASSERTION, etc. should print out the
   // PID along with the warning.
   nsDebugImpl::SetMultiprocessMode("Parent");
 
 #if defined(XP_WIN) && !defined(MOZ_B2G)
   // Request Windows message deferral behavior on our side of the PContent
   // channel. Generally only applies to the situation where we get caught in
   // a deadlock with the plugin process when sending CPOWs.
@@ -1865,20 +2148,29 @@ ContentParent::~ContentParent()
   if (mForceKillTimer) {
     mForceKillTimer->Cancel();
   }
 
   NS_ASSERTION(NS_IsMainThread(), "Wrong thread!");
 
   // We should be removed from all these lists in ActorDestroy.
   MOZ_ASSERT(!sPrivateContent || !sPrivateContent->Contains(this));
-  MOZ_ASSERT((!sBrowserContentParents ||
-              !sBrowserContentParents->Contains(this)) &&
-             (!sLargeAllocationContentParents ||
-              !sLargeAllocationContentParents->Contains(this)));
+  if (mAppManifestURL.IsEmpty()) {
+    MOZ_ASSERT((!sNonAppContentParents ||
+                !sNonAppContentParents->Contains(this)) &&
+               (!sLargeAllocationContentParents ||
+                !sLargeAllocationContentParents->Contains(this)));
+  } else {
+    // In general, we expect sAppContentParents->Get(mAppManifestURL) to be
+    // nullptr.  But it could be that we created another ContentParent for
+    // this app after we did this->ActorDestroy(), so the right check is
+    // that sAppContentParents->Get(mAppManifestURL) != this.
+    MOZ_ASSERT(!sAppContentParents ||
+               sAppContentParents->Get(mAppManifestURL) != this);
+  }
 }
 
 void
 ContentParent::InitInternal(ProcessPriority aInitialPriority,
                             bool aSetupOffMainThreadCompositing,
                             bool aSendRegisteredChrome)
 {
   if (aSendRegisteredChrome) {
@@ -2038,16 +2330,22 @@ ContentParent::InitInternal(ProcessPrior
 }
 
 bool
 ContentParent::IsAlive() const
 {
   return mIsAlive;
 }
 
+bool
+ContentParent::IsForApp() const
+{
+  return !mAppManifestURL.IsEmpty();
+}
+
 int32_t
 ContentParent::Pid() const
 {
   if (!mSubprocess || !mSubprocess->GetChildProcessHandle()) {
     return -1;
   }
   return base::GetProcId(mSubprocess->GetChildProcessHandle());
 }
@@ -2567,19 +2865,20 @@ ContentParent::AllocPProcessHangMonitorP
                                               ProcessId aOtherProcess)
 {
   mHangMonitorActor = CreateHangMonitorParent(this, aTransport, aOtherProcess);
   return mHangMonitorActor;
 }
 
 bool
 ContentParent::RecvGetProcessAttributes(ContentParentId* aCpId,
-                                        bool* aIsForBrowser)
+                                        bool* aIsForApp, bool* aIsForBrowser)
 {
   *aCpId = mChildID;
+  *aIsForApp = IsForApp();
   *aIsForBrowser = mIsForBrowser;
 
   return true;
 }
 
 bool
 ContentParent::RecvGetXPCOMProcessAttributes(bool* aIsOffline,
                                              bool* aIsConnected,
@@ -2665,22 +2964,24 @@ ContentParent::DeallocPJavaScriptParent(
   return nsIContentParent::DeallocPJavaScriptParent(parent);
 }
 
 PBrowserParent*
 ContentParent::AllocPBrowserParent(const TabId& aTabId,
                                    const IPCTabContext& aContext,
                                    const uint32_t& aChromeFlags,
                                    const ContentParentId& aCpId,
+                                   const bool& aIsForApp,
                                    const bool& aIsForBrowser)
 {
   return nsIContentParent::AllocPBrowserParent(aTabId,
                                                aContext,
                                                aChromeFlags,
                                                aCpId,
+                                               aIsForApp,
                                                aIsForBrowser);
 }
 
 bool
 ContentParent::DeallocPBrowserParent(PBrowserParent* frame)
 {
   return nsIContentParent::DeallocPBrowserParent(frame);
 }
@@ -2817,29 +3118,34 @@ ContentParent::KillHard(const char* aRea
   XRE_GetIOMessageLoop()->PostTask(
     NewRunnableFunction(&ProcessWatcher::EnsureProcessTerminated,
                         otherProcessHandle, /*force=*/true));
 }
 
 bool
 ContentParent::IsPreallocated() const
 {
-  return mIsPreallocated;
+  return mAppManifestURL == MAGIC_PREALLOCATED_APP_MANIFEST_URL;
 }
 
 void
 ContentParent::FriendlyName(nsAString& aName, bool aAnonymize)
 {
   aName.Truncate();
   if (IsPreallocated()) {
     aName.AssignLiteral("(Preallocated)");
   } else if (mIsForBrowser) {
     aName.AssignLiteral("Browser");
   } else if (aAnonymize) {
     aName.AssignLiteral("<anonymized-name>");
+  } else if (!mAppName.IsEmpty()) {
+    aName = mAppName;
+  } else if (!mAppManifestURL.IsEmpty()) {
+    aName.AssignLiteral("Unknown app: ");
+    aName.Append(mAppManifestURL);
   } else {
     aName.AssignLiteral("???");
   }
 }
 
 PCrashReporterParent*
 ContentParent::AllocPCrashReporterParent(const NativeThreadId& tid,
                                          const uint32_t& processType)
@@ -3769,23 +4075,25 @@ ContentParent::SendPBlobConstructor(PBlo
 }
 
 PBrowserParent*
 ContentParent::SendPBrowserConstructor(PBrowserParent* aActor,
                                        const TabId& aTabId,
                                        const IPCTabContext& aContext,
                                        const uint32_t& aChromeFlags,
                                        const ContentParentId& aCpId,
+                                       const bool& aIsForApp,
                                        const bool& aIsForBrowser)
 {
   return PContentParent::SendPBrowserConstructor(aActor,
                                                  aTabId,
                                                  aContext,
                                                  aChromeFlags,
                                                  aCpId,
+                                                 aIsForApp,
                                                  aIsForBrowser);
 }
 
 bool
 ContentParent::RecvCreateFakeVolume(const nsString& fsName,
                                     const nsString& mountPoint)
 {
 #ifdef MOZ_WIDGET_GONK
@@ -3920,19 +4228,22 @@ ContentParent::RecvRecordingDeviceEvents
                                          const bool& aIsAudio,
                                          const bool& aIsVideo)
 {
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     // recording-device-ipc-events needs to gather more information from content process
     RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
     props->SetPropertyAsUint64(NS_LITERAL_STRING("childID"), ChildID());
+    props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), IsForApp());
     props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
     props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
-    props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), aPageURL);
+
+    nsString requestURL = IsForApp() ? AppManifestURL() : aPageURL;
+    props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
 
     obs->NotifyObservers((nsIPropertyBag2*) props,
                          "recording-device-ipc-events",
                          aRecordingStatus.get());
   } else {
     NS_WARNING("Could not get the Observer service for ContentParent::RecvRecordingDeviceEvents.");
   }
   return true;
@@ -4377,17 +4688,17 @@ ContentParent::RecvCreateWindow(PBrowser
       return false;
   }
 
   TabParent* thisTabParent = nullptr;
   if (aThisTab) {
     thisTabParent = TabParent::GetFrom(aThisTab);
   }
 
-  if (NS_WARN_IF(thisTabParent && thisTabParent->IsMozBrowser())) {
+  if (NS_WARN_IF(thisTabParent && thisTabParent->IsMozBrowserOrApp())) {
     return false;
   }
 
   TabParent* newTab = TabParent::GetFrom(aNewTab);
   MOZ_ASSERT(newTab);
 
   auto destroyNewTabOnError = MakeScopeExit([&] {
     if (!*aWindowIsNew || NS_FAILED(*aResult)) {
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -27,16 +27,17 @@
 #include "nsIDOMGeoPositionCallback.h"
 #include "nsIDOMGeoPositionErrorCallback.h"
 #include "nsRefPtrHashtable.h"
 #include "PermissionMessageUtils.h"
 #include "DriverCrashGuard.h"
 
 #define CHILD_PROCESS_SHUTDOWN_MESSAGE NS_LITERAL_STRING("child-process-shutdown")
 
+class mozIApplication;
 class nsConsoleService;
 class nsICycleCollectorLogSink;
 class nsIDumpGCAndCCLogsCallback;
 class nsITimer;
 class ParentIdleListener;
 class nsIWidget;
 
 namespace mozilla {
@@ -143,20 +144,20 @@ public:
   static already_AddRefed<ContentParent> PreallocateAppProcess();
 
   /**
    * Get or create a content process for the given TabContext.  aFrameElement
    * should be the frame/iframe element with which this process will
    * associated.
    */
   static TabParent*
-  CreateBrowser(const TabContext& aContext,
-                Element* aFrameElement,
-                ContentParent* aOpenerContentParent,
-                bool aFreshProcess = false);
+  CreateBrowserOrApp(const TabContext& aContext,
+                     Element* aFrameElement,
+                     ContentParent* aOpenerContentParent,
+                     bool aFreshProcess = false);
 
   static void GetAll(nsTArray<ContentParent*>& aArray);
 
   static void GetAllEvenIfDead(nsTArray<ContentParent*>& aArray);
 
   enum CPIteratorPolicy {
     eLive,
     eAll
@@ -234,16 +235,17 @@ public:
 
   // Let managees query if it is safe to send messages.
   bool IsDestroyed() const { return !mIPCOpen; }
 
   virtual bool RecvCreateChildProcess(const IPCTabContext& aContext,
                                       const hal::ProcessPriority& aPriority,
                                       const TabId& aOpenerTabId,
                                       ContentParentId* aCpId,
+                                      bool* aIsForApp,
                                       bool* aIsForBrowser,
                                       TabId* aTabId) override;
 
   virtual bool RecvBridgeToChildProcess(const ContentParentId& aCpId) override;
 
   virtual bool RecvCreateGMPService() override;
 
   virtual bool RecvLoadPlugin(const uint32_t& aPluginId, nsresult* aRv,
@@ -322,16 +324,18 @@ public:
                   bool aMarkedDestroying);
 
   void ReportChildAlreadyBlocked();
 
   bool RequestRunToCompletion();
 
   bool IsAlive() const;
 
+  virtual bool IsForApp() const override;
+
   virtual bool IsForBrowser() const override
   {
     return mIsForBrowser;
   }
 
   GeckoChildProcessHost* Process() const
   {
     return mSubprocess;
@@ -354,16 +358,18 @@ public:
    *
    * WARNING: aReason appears in telemetry, so any new value passed in requires
    * data review.
    */
   void KillHard(const char* aWhy);
 
   ContentParentId ChildID() const override { return mChildID; }
 
+  const nsString& AppManifestURL() const { return mAppManifestURL; }
+
   bool IsPreallocated() const;
 
   /**
    * Get a user-friendly name for this ContentParent.  We make no guarantees
    * about this name: It might not be unique, apps can spoof special names,
    * etc.  So please don't use this name to make any decisions about the
    * ContentParent based on the value returned here.
    */
@@ -550,47 +556,59 @@ protected:
   virtual void ActorDestroy(ActorDestroyReason why) override;
 
   bool ShouldContinueFromReplyTimeout() override;
 
   void OnVarChanged(const GfxVarUpdate& aVar) override;
   void OnCompositorUnexpectedShutdown() override;
 
 private:
-  static nsTArray<ContentParent*>* sBrowserContentParents;
+  static nsDataHashtable<nsStringHashKey, ContentParent*> *sAppContentParents;
+  static nsTArray<ContentParent*>* sNonAppContentParents;
   static nsTArray<ContentParent*>* sLargeAllocationContentParents;
   static nsTArray<ContentParent*>* sPrivateContent;
   static StaticAutoPtr<LinkedList<ContentParent> > sContentParents;
 
   static void JoinProcessesIOThread(const nsTArray<ContentParent*>* aProcesses,
                                     Monitor* aMonitor, bool* aDone);
 
+  // Take the preallocated process and transform it into a "real" app process,
+  // for the specified manifest URL.  If there is no preallocated process (or
+  // if it's dead), create a new one and set aTookPreAllocated to false.
+  static already_AddRefed<ContentParent>
+  GetNewOrPreallocatedAppProcess(mozIApplication* aApp,
+                                 hal::ProcessPriority aInitialPriority,
+                                 ContentParent* aOpener,
+                                 /*out*/ bool* aTookPreAllocated = nullptr);
+
   static hal::ProcessPriority GetInitialProcessPriority(Element* aFrameElement);
 
   static ContentBridgeParent* CreateContentBridgeParent(const TabContext& aContext,
                                                         const hal::ProcessPriority& aPriority,
                                                         const TabId& aOpenerTabId,
                                                         /*out*/ TabId* aTabId);
 
   // Hide the raw constructor methods since we don't want client code
   // using them.
   virtual PBrowserParent* SendPBrowserConstructor(
       PBrowserParent* actor,
       const TabId& aTabId,
       const IPCTabContext& context,
       const uint32_t& chromeFlags,
       const ContentParentId& aCpId,
+      const bool& aIsForApp,
       const bool& aIsForBrowser) override;
   using PContentParent::SendPTestShellConstructor;
 
   FORWARD_SHMEM_ALLOCATOR_TO(PContentParent)
 
-  // No more than one of aIsForBrowser, and aIsForPreallocated may be
+  // No more than one of !!aApp, aIsForBrowser, and aIsForPreallocated may be
   // true.
-  ContentParent(ContentParent* aOpener,
+  ContentParent(mozIApplication* aApp,
+                ContentParent* aOpener,
                 bool aIsForBrowser,
                 bool aIsForPreallocated);
 
   // The common initialization for the constructors.
   void InitializeMembers();
 
   // Launch the subprocess and associated initialization.
   // Returns false if the process fails to start.
@@ -602,25 +620,30 @@ private:
                     bool aSendRegisteredChrome);
 
   virtual ~ContentParent();
 
   void Init();
 
   // Some information could be sent to content very early, it
   // should be send from this function. This function should only be
-  // called after the process has been transformed to browser.
+  // called after the process has been transformed to app or browser.
   void ForwardKnownInfo();
 
   // Set the child process's priority and then check whether the child is
   // still alive.  Returns true if the process is still alive, and false
   // otherwise.  If you pass a FOREGROUND* priority here, it's (hopefully)
   // unlikely that the process will be killed after this point.
   bool SetPriorityAndCheckIsAlive(hal::ProcessPriority aPriority);
 
+  // Transform a pre-allocated app process into a "real" app
+  // process, for the specified manifest URL.
+  void TransformPreallocatedIntoApp(ContentParent* aOpener,
+                                    const nsAString& aAppManifestURL);
+
   // Transform a pre-allocated app process into a browser process. If this
   // returns false, the child process has died.
   void TransformPreallocatedIntoBrowser(ContentParent* aOpener);
 
   /**
    * Mark this ContentParent as dead for the purposes of Get*().
    * This method is idempotent.
    */
@@ -673,16 +696,17 @@ private:
   AllocPBackgroundParent(Transport* aTransport, ProcessId aOtherProcess)
                          override;
 
   PProcessHangMonitorParent*
   AllocPProcessHangMonitorParent(Transport* aTransport,
                                  ProcessId aOtherProcess) override;
 
   virtual bool RecvGetProcessAttributes(ContentParentId* aCpId,
+                                        bool* aIsForApp,
                                         bool* aIsForBrowser) override;
 
   virtual bool
   RecvGetXPCOMProcessAttributes(bool* aIsOffline,
                                 bool* aIsConnected,
                                 bool* aIsLangRTL,
                                 bool* aHaveBidiKeyboards,
                                 InfallibleTArray<nsString>* dictionaries,
@@ -697,16 +721,17 @@ private:
 
   virtual bool
   DeallocPRemoteSpellcheckEngineParent(PRemoteSpellcheckEngineParent*) override;
 
   virtual PBrowserParent* AllocPBrowserParent(const TabId& aTabId,
                                               const IPCTabContext& aContext,
                                               const uint32_t& aChromeFlags,
                                               const ContentParentId& aCpId,
+                                              const bool& aIsForApp,
                                               const bool& aIsForBrowser) override;
 
   virtual bool DeallocPBrowserParent(PBrowserParent* frame) override;
 
   virtual PDeviceStorageRequestParent*
   AllocPDeviceStorageRequestParent(const DeviceStorageParams&) override;
 
   virtual bool
@@ -1074,40 +1099,48 @@ private:
   // details.
 
   GeckoChildProcessHost* mSubprocess;
   ContentParent* mOpener;
 
   ContentParentId mChildID;
   int32_t mGeolocationWatchID;
 
+  nsString mAppManifestURL;
+
   nsCString mKillHardAnnotation;
 
+  /**
+   * We cache mAppName instead of looking it up using mAppManifestURL when we
+   * need it because it turns out that getting an app from the apps service is
+   * expensive.
+   */
+  nsString mAppName;
+
   // After we initiate shutdown, we also start a timer to ensure
   // that even content processes that are 100% blocked (say from
   // SIGSTOP), are still killed eventually.  This task enforces that
   // timer.
   nsCOMPtr<nsITimer> mForceKillTimer;
   // How many tabs we're waiting to finish their destruction
   // sequence.  Precisely, how many TabParents have called
   // NotifyTabDestroying() but not called NotifyTabDestroyed().
   int32_t mNumDestroyingTabs;
   // True only while this is ready to be used to host remote tabs.
   // This must not be used for new purposes after mIsAlive goes to
   // false, but some previously scheduled IPC traffic may still pass
   // through.
   bool mIsAlive;
 
-  // True only the if process is already a browser or has
+  // True only the if process is already a browser or app or has
   // been transformed into one.
   bool mMetamorphosed;
 
   bool mSendPermissionUpdates;
   bool mIsForBrowser;
-  bool mIsPreallocated;
 
   // These variables track whether we've called Close() and KillHard() on our
   // channel.
   bool mCalledClose;
   bool mCalledKillHard;
   bool mCreatedPairedMinidumps;
   bool mShutdownPending;
   bool mIPCOpen;
--- a/dom/ipc/ContentProcessManager.cpp
+++ b/dom/ipc/ContentProcessManager.cpp
@@ -349,10 +349,24 @@ ContentProcessManager::GetTabParentsByPr
       remoteFrameIter != iter->second.mRemoteFrames.end();
       ++remoteFrameIter) {
     tabIdList.AppendElement(remoteFrameIter->first);
   }
 
   return Move(tabIdList);
 }
 
+uint32_t
+ContentProcessManager::GetAppIdByProcessAndTabId(const ContentParentId& aChildCpId,
+                                                 const TabId& aChildTabId)
+{
+  uint32_t appId = nsIScriptSecurityManager::NO_APP_ID;
+  if (aChildCpId && aChildTabId) {
+    TabContext tabContext;
+    if (GetTabContextByProcessAndTabId(aChildCpId, aChildTabId, &tabContext)) {
+      appId = tabContext.OwnOrContainingAppId();
+    }
+  }
+  return appId;
+}
+
 } // namespace dom
 } // namespace mozilla
--- a/dom/ipc/ContentProcessManager.h
+++ b/dom/ipc/ContentProcessManager.h
@@ -138,16 +138,25 @@ public:
    *  always the opener tab of the given tab in content process. This function
    *  will call GetTabParentByProcessAndTabId iteratively until the Tab returned
    *  is belong to the chrome process.
    */
   already_AddRefed<TabParent>
   GetTopLevelTabParentByProcessAndTabId(const ContentParentId& aChildCpId,
                                         const TabId& aChildTabId);
 
+  /**
+   * Return appId by given TabId and ContentParentId.
+   * It will return nsIScriptSecurityManager::NO_APP_ID
+   * if the given tab is not an app.
+   */
+  uint32_t
+  GetAppIdByProcessAndTabId(const ContentParentId& aChildCpId,
+                            const TabId& aChildTabId);
+
 private:
   static StaticAutoPtr<ContentProcessManager> sSingleton;
   TabId mUniqueId;
   std::map<ContentParentId, ContentProcessInfo> mContentParentMap;
 
   ContentProcessManager() {MOZ_COUNT_CTOR(ContentProcessManager);};
 };
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -397,30 +397,37 @@ nested(upto inside_cpow) sync protocol P
 
 both:
     // Depending on exactly how the new browser is being created, it might be
     // created from either the child or parent process!
     //
     // The child creates the PBrowser as part of
     // TabChild::BrowserFrameProvideWindow (which happens when the child's
     // content calls window.open()), and the parent creates the PBrowser as part
-    // of ContentParent::CreateBrowser.
+    // of ContentParent::CreateBrowserOrApp.
+    //
+    // When the parent constructs a PBrowser, the child trusts the app token and
+    // other attributes it receives from the parent.  In that case, the
+    // context should be FrameIPCTabContext.
     //
-    // When the parent constructs a PBrowser, the child trusts the attributes it
-    // receives from the parent.  In that case, the context should be
-    // FrameIPCTabContext.
+    // When the child constructs a PBrowser, the parent doesn't trust the app
+    // token it receives from the child.  In this case, context must have type
+    // PopupIPCTabContext.  The browser created using a PopupIPCTabContext has
+    // the opener PBrowser's app-id and containing-app-id.  The parent checks
+    // that if the opener is a browser element, the context is also for a
+    // browser element.
     //
-    // When the child constructs a PBrowser, the parent doesn't trust the
-    // attributes it receives from the child.  In this case, context must have
-    // type PopupIPCTabContext.  The parent checks that if the opener is a
-    // browser element, the context is also for a browser element.
+    // This allows the parent to prevent a malicious child from escalating its
+    // privileges by requesting a PBrowser corresponding to a highly-privileged
+    // app; the child can only request privileges for an app which the child has
+    // access to (in the form of a TabChild).
     //
     // Keep the last 3 attributes in sync with GetProcessAttributes!
     async PBrowser(TabId tabId, IPCTabContext context, uint32_t chromeFlags,
-                   ContentParentId cpId, bool isForBrowser);
+                   ContentParentId cpId, bool isForApp, bool isForBrowser);
 
     async PBlob(BlobConstructorParams params);
 
     async PFileDescriptorSet(FileDescriptor fd);
 
     // For parent->child, aBrowser must be non-null; aOuterWindowID can
     // be 0 to indicate the browser's current root document, or nonzero
     // to persist a subdocument.  For child->parent, arguments are
@@ -677,35 +684,37 @@ child:
 
 parent:
     /**
      * Tell the content process some attributes of itself.  This is
      * among the first information queried by content processes after
      * startup.  (The message is sync to allow the content process to
      * control when it receives the information.)
      *
-     * |id| is a unique ID among all subprocesses. When
-     * |isForBrowser|, we're loading <browser> or <xul:browser remote>.
+     * |id| is a unique ID among all subprocesses.  When |isForApp &&
+     * isForBrowser|, we're loading <browser> for an app.  When
+     * |isForBrowser|, we're loading <browser>.  When |!isForApp &&
+     * !isForBrowser|, we're probably loading <xul:browser remote>.
      *
      * Keep the return values in sync with PBrowser()!
      */
     sync GetProcessAttributes()
-        returns (ContentParentId cpId, bool isForBrowser);
+        returns (ContentParentId cpId, bool isForApp, bool isForBrowser);
     sync GetXPCOMProcessAttributes()
         returns (bool isOffline, bool isConnected, bool isLangRTL,
                  bool haveBidiKeyboards, nsString[] dictionaries,
                  ClipboardCapabilities clipboardCaps,
                  DomainPolicyClone domainPolicy,
                  StructuredCloneData initialData,
                  FontFamilyListEntry[] fontFamilies /* used on MacOSX only */);
 
     sync CreateChildProcess(IPCTabContext context,
                             ProcessPriority priority,
                             TabId openerTabId)
-        returns (ContentParentId cpId, bool isForBrowser, TabId tabId);
+        returns (ContentParentId cpId, bool isForApp, bool isForBrowser, TabId tabId);
     sync BridgeToChildProcess(ContentParentId cpId);
 
     async CreateGMPService();
 
     /**
      * This call connects the content process to a plugin process. While this
      * call runs, a new PluginModuleParent will be created in the ContentChild
      * via bridging. The corresponding PluginModuleChild will live in the plugin
--- a/dom/ipc/PContentBridge.ipdl
+++ b/dom/ipc/PContentBridge.ipdl
@@ -50,17 +50,17 @@ parent:
     async PJavaScript();
 
     async PSendStream();
 
 both:
     // Both the parent and the child can construct the PBrowser.
     // See the comment in PContent::PBrowser().
     async PBrowser(TabId tabId, IPCTabContext context, uint32_t chromeFlags,
-                   ContentParentId cpId, bool isForBrowser);
+                   ContentParentId cpId, bool isForApp, bool isForBrowser);
 
     async PBlob(BlobConstructorParams params);
 
     async PFileDescriptorSet(FileDescriptor fd);
 
     async AsyncMessage(nsString aMessage, CpowEntry[] aCpows,
                        Principal aPrincipal, ClonedMessageData aData);
 };
--- a/dom/ipc/PTabContext.ipdlh
+++ b/dom/ipc/PTabContext.ipdlh
@@ -34,16 +34,19 @@ struct PopupIPCTabContext
 };
 
 // An IPCTabContext which corresponds to an app, browser, or normal frame.
 struct FrameIPCTabContext
 {
   // The originAttributes dictionary.
   DocShellOriginAttributes originAttributes;
 
+  // The ID of the app containing this app/browser frame, if applicable.
+  uint32_t frameOwnerAppId;
+
   // Whether this is a mozbrowser frame.  <iframe mozbrowser mozapp> and
   // <xul:browser> are not considered to be mozbrowser frames.
   bool isMozBrowserElement;
 
   // Whether this TabContext should work in prerender mode.
   bool isPrerendered;
 
   // The requested presentation URL.
--- a/dom/ipc/ProcessPriorityManager.cpp
+++ b/dom/ipc/ProcessPriorityManager.cpp
@@ -856,20 +856,20 @@ ParticularProcessPriorityManager::OnRemo
   TabParent* tp = TabParent::GetFrom(fl);
   NS_ENSURE_TRUE_VOID(tp);
 
   MOZ_ASSERT(XRE_IsParentProcess());
   if (tp->Manager() != mContentParent) {
     return;
   }
 
-  // Ignore notifications that aren't from a Browser
-  bool isMozBrowser;
-  fl->GetOwnerIsMozBrowserFrame(&isMozBrowser);
-  if (isMozBrowser) {
+  // Ignore notifications that aren't from a BrowserOrApp
+  bool isMozBrowserOrApp;
+  fl->GetOwnerIsMozBrowserOrAppFrame(&isMozBrowserOrApp);
+  if (isMozBrowserOrApp) {
     ResetPriority();
   }
 
   nsCOMPtr<nsIObserverService> os = services::GetObserverService();
   if (os) {
     os->RemoveObserver(this, "remote-browser-shown");
   }
 }
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -412,17 +412,17 @@ TabChild::PreloadSlowThings()
 /*static*/ already_AddRefed<TabChild>
 TabChild::Create(nsIContentChild* aManager,
                  const TabId& aTabId,
                  const TabContext &aContext,
                  uint32_t aChromeFlags)
 {
     if (sPreallocatedTab &&
         sPreallocatedTab->mChromeFlags == aChromeFlags &&
-        aContext.IsMozBrowser()) {
+        aContext.IsMozBrowserOrApp()) {
 
         RefPtr<TabChild> child = sPreallocatedTab.get();
         sPreallocatedTab = nullptr;
 
         MOZ_ASSERT(!child->mTriedBrowserInit);
 
         child->mManager = aManager;
         child->SetTabId(aTabId);
@@ -787,17 +787,18 @@ TabChild::NotifyTabContextUpdated(bool a
 void
 TabChild::UpdateFrameType()
 {
   nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
   MOZ_ASSERT(docShell);
 
   // TODO: Bug 1252794 - remove frameType from nsIDocShell.idl
   docShell->SetFrameType(IsMozBrowserElement() ? nsIDocShell::FRAME_TYPE_BROWSER :
-                           nsIDocShell::FRAME_TYPE_REGULAR);
+                           HasOwnApp() ? nsIDocShell::FRAME_TYPE_APP :
+                             nsIDocShell::FRAME_TYPE_REGULAR);
 }
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(TabChild)
   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome)
   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChrome2)
   NS_INTERFACE_MAP_ENTRY(nsIEmbeddingSiteWindow)
   NS_INTERFACE_MAP_ENTRY(nsIWebBrowserChromeFocus)
   NS_INTERFACE_MAP_ENTRY(nsIInterfaceRequestor)
@@ -1097,21 +1098,21 @@ TabChild::ProvideWindow(mozIDOMWindowPro
                         bool aCalledFromJS,
                         bool aPositionSpecified, bool aSizeSpecified,
                         nsIURI* aURI, const nsAString& aName,
                         const nsACString& aFeatures, bool aForceNoOpener,
                         bool* aWindowIsNew, mozIDOMWindowProxy** aReturn)
 {
     *aReturn = nullptr;
 
-    // If aParent is inside an <iframe mozbrowser> and this isn't a request to
-    // open a modal-type window, we're going to create a new <iframe mozbrowser>
-    // and return its window here.
+    // If aParent is inside an <iframe mozbrowser> or <iframe mozapp> and this
+    // isn't a request to open a modal-type window, we're going to create a new
+    // <iframe mozbrowser/mozapp> and return its window here.
     nsCOMPtr<nsIDocShell> docshell = do_GetInterface(aParent);
-    bool iframeMoz = (docshell && docshell->GetIsInMozBrowser() &&
+    bool iframeMoz = (docshell && docshell->GetIsInMozBrowserOrApp() &&
                       !(aChromeFlags & (nsIWebBrowserChrome::CHROME_MODAL |
                                         nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |
                                         nsIWebBrowserChrome::CHROME_OPENAS_CHROME)));
 
     if (!iframeMoz) {
       int32_t openLocation =
         nsWindowWatcher::GetWindowOpenLocation(nsPIDOMWindowOuter::From(aParent),
                                                aChromeFlags, aCalledFromJS,
@@ -1208,27 +1209,47 @@ TabChild::~TabChild()
     webBrowser->SetContainerWindow(nullptr);
   }
 
   if (mHistoryListener) {
     mHistoryListener->ClearTabChild();
   }
 }
 
+void
+TabChild::SetProcessNameToAppName()
+{
+  nsCOMPtr<mozIApplication> app = GetOwnApp();
+  if (!app) {
+    return;
+  }
+
+  nsAutoString appName;
+  nsresult rv = app->GetName(appName);
+  if (NS_FAILED(rv)) {
+    NS_WARNING("Failed to retrieve app name");
+    return;
+  }
+
+  ContentChild::GetSingleton()->SetProcessName(appName, true);
+}
+
 bool
 TabChild::RecvLoadURL(const nsCString& aURI,
                       const ShowInfo& aInfo)
 {
   if (!mDidLoadURLInit) {
     mDidLoadURLInit = true;
     if (!InitTabChildGlobal()) {
       return false;
     }
 
     ApplyShowInfo(aInfo);
+
+    SetProcessNameToAppName();
   }
 
   nsresult rv =
     WebNavigation()->LoadURI(NS_ConvertUTF8toUTF16(aURI).get(),
                              nsIWebNavigation::LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP |
                              nsIWebNavigation::LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL,
                              nullptr, nullptr, nullptr);
   if (NS_FAILED(rv)) {
@@ -1263,17 +1284,17 @@ TabChild::ApplyShowInfo(const ShowInfo& 
     // Once we've got one ShowInfo from parent, no need to update the values
     // anymore.
     mDidSetRealShowInfo = true;
   }
 
   nsCOMPtr<nsIDocShell> docShell = do_GetInterface(WebNavigation());
   if (docShell) {
     nsCOMPtr<nsIDocShellTreeItem> item = do_GetInterface(docShell);
-    if (IsMozBrowser()) {
+    if (IsMozBrowserOrApp()) {
       // B2G allows window.name to be set by changing the name attribute on the
       // <iframe mozbrowser> element. window.open calls cause this attribute to
       // be set to the correct value. A normal <xul:browser> element has no such
       // attribute. The data we get here comes from reading the attribute, so we
       // shouldn't trust it for <xul:browser> elements.
       item->SetName(aInfo.name());
     }
     docShell->SetFullscreenAllowed(aInfo.fullscreenAllowed());
@@ -2165,17 +2186,17 @@ TabChild::RecvSwappedWithOtherRemoteLoad
   // Since mIsMozBrowserElement may change in UpdateTabContextAfterSwap, so we
   // call UpdateFrameType here to make sure the frameType on the docshell is
   // correct.
   UpdateFrameType();
 
   // Ignore previous value of mTriedBrowserInit since owner content has changed.
   mTriedBrowserInit = true;
   // Initialize the child side of the browser element machinery, if appropriate.
-  if (IsMozBrowser()) {
+  if (IsMozBrowserOrApp()) {
     RecvLoadRemoteScript(BROWSER_ELEMENT_CHILD_SCRIPT, true);
   }
 
   nsContentUtils::FirePageShowEvent(ourDocShell, ourEventTarget, true);
 
   docShell->SetInFrameSwap(false);
 
   return true;
@@ -2525,17 +2546,17 @@ TabChild::InitTabChildGlobal(FrameScript
     NS_ENSURE_TRUE(root, false);
     root->SetParentTarget(scope);
   }
 
   if (aScriptLoading != DONT_LOAD_SCRIPTS && !mTriedBrowserInit) {
     mTriedBrowserInit = true;
     // Initialize the child side of the browser element machinery,
     // if appropriate.
-    if (IsMozBrowser()) {
+    if (IsMozBrowserOrApp()) {
       RecvLoadRemoteScript(BROWSER_ELEMENT_CHILD_SCRIPT, true);
     }
   }
 
   return true;
 }
 
 void
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -729,16 +729,18 @@ private:
   bool InitTabChildGlobal(FrameScriptLoading aScriptLoading = DEFAULT_LOAD_SCRIPTS);
 
   void InitRenderingState(const TextureFactoryIdentifier& aTextureFactoryIdentifier,
                           const uint64_t& aLayersId,
                           PRenderFrameChild* aRenderFrame);
 
   void DestroyWindow();
 
+  void SetProcessNameToAppName();
+
   void ApplyShowInfo(const ShowInfo& aInfo);
 
   bool HasValidInnerSize();
 
   void SetTabId(const TabId& aTabId);
 
   ScreenIntRect GetOuterRect();
 
--- a/dom/ipc/TabContext.cpp
+++ b/dom/ipc/TabContext.cpp
@@ -3,32 +3,33 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/TabContext.h"
 #include "mozilla/dom/PTabContext.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/TabChild.h"
+#include "nsIAppsService.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()
   : mIsPrerendered(false)
   , mInitialized(false)
   , mIsMozBrowserElement(false)
-  , mOriginAttributes()
+  , mContainingAppId(NO_APP_ID)
   , mShowAccelerators(UIStateChangeType_NoChange)
   , mShowFocusRings(UIStateChangeType_NoChange)
 {
 }
 
 bool
 TabContext::IsMozBrowserElement() const
 {
@@ -37,19 +38,121 @@ TabContext::IsMozBrowserElement() const
 
 bool
 TabContext::IsIsolatedMozBrowserElement() const
 {
   return mOriginAttributes.mInIsolatedMozBrowser;
 }
 
 bool
-TabContext::IsMozBrowser() const
+TabContext::IsMozBrowserOrApp() const
+{
+  return HasOwnApp() || IsMozBrowserElement();
+}
+
+uint32_t
+TabContext::OwnAppId() const
+{
+  return mOriginAttributes.mAppId;
+}
+
+already_AddRefed<mozIApplication>
+TabContext::GetOwnApp() const
+{
+  nsCOMPtr<mozIApplication> ownApp = mOwnApp;
+  return ownApp.forget();
+}
+
+bool
+TabContext::HasOwnApp() const
+{
+  nsCOMPtr<mozIApplication> ownApp = GetOwnApp();
+  return !!ownApp;
+}
+
+uint32_t
+TabContext::BrowserOwnerAppId() const
+{
+  if (IsMozBrowserElement()) {
+    return mContainingAppId;
+  }
+  return NO_APP_ID;
+}
+
+already_AddRefed<mozIApplication>
+TabContext::GetBrowserOwnerApp() const
+{
+  nsCOMPtr<mozIApplication> ownerApp;
+  if (IsMozBrowserElement()) {
+    ownerApp = mContainingApp;
+  }
+  return ownerApp.forget();
+}
+
+bool
+TabContext::HasBrowserOwnerApp() const
 {
-  return IsMozBrowserElement();
+  nsCOMPtr<mozIApplication> ownerApp = GetBrowserOwnerApp();
+  return !!ownerApp;
+}
+
+uint32_t
+TabContext::AppOwnerAppId() const
+{
+  if (HasOwnApp()) {
+    return mContainingAppId;
+  }
+  return NO_APP_ID;
+}
+
+already_AddRefed<mozIApplication>
+TabContext::GetAppOwnerApp() const
+{
+  nsCOMPtr<mozIApplication> ownerApp;
+  if (HasOwnApp()) {
+    ownerApp = mContainingApp;
+  }
+  return ownerApp.forget();
+}
+
+bool
+TabContext::HasAppOwnerApp() const
+{
+  nsCOMPtr<mozIApplication> ownerApp = GetAppOwnerApp();
+  return !!ownerApp;
+}
+
+uint32_t
+TabContext::OwnOrContainingAppId() const
+{
+  if (HasOwnApp()) {
+    return mOriginAttributes.mAppId;
+  }
+
+  return mContainingAppId;
+}
+
+already_AddRefed<mozIApplication>
+TabContext::GetOwnOrContainingApp() const
+{
+  nsCOMPtr<mozIApplication> ownOrContainingApp;
+  if (HasOwnApp()) {
+    ownOrContainingApp = mOwnApp;
+  } else {
+    ownOrContainingApp = mContainingApp;
+  }
+
+  return ownOrContainingApp.forget();
+}
+
+bool
+TabContext::HasOwnOrContainingApp() const
+{
+  nsCOMPtr<mozIApplication> ownOrContainingApp = GetOwnOrContainingApp();
+  return !!ownOrContainingApp;
 }
 
 bool
 TabContext::SetTabContext(const TabContext& aContext)
 {
   NS_ENSURE_FALSE(mInitialized, false);
 
   *this = aContext;
@@ -67,17 +170,19 @@ TabContext::SetPrivateBrowsingAttributes
 bool
 TabContext::UpdateTabContextAfterSwap(const TabContext& aContext)
 {
   // This is only used after already initialized.
   MOZ_ASSERT(mInitialized);
 
   // The only permissable change is to `mIsMozBrowserElement`.  All other fields
   // must match for the change to be accepted.
-  if (aContext.mOriginAttributes != mOriginAttributes) {
+  if (aContext.OwnAppId() != OwnAppId() ||
+      aContext.mContainingAppId != mContainingAppId ||
+      aContext.mOriginAttributes != mOriginAttributes) {
     return false;
   }
 
   mIsMozBrowserElement = aContext.mIsMozBrowserElement;
   return true;
 }
 
 const DocShellOriginAttributes&
@@ -102,52 +207,89 @@ UIStateChangeType
 TabContext::ShowFocusRings() const
 {
   return mShowFocusRings;
 }
 
 bool
 TabContext::SetTabContext(bool aIsMozBrowserElement,
                           bool aIsPrerendered,
+                          mozIApplication* aOwnApp,
+                          mozIApplication* aAppFrameOwnerApp,
                           UIStateChangeType aShowAccelerators,
                           UIStateChangeType aShowFocusRings,
                           const DocShellOriginAttributes& aOriginAttributes,
                           const nsAString& aPresentationURL)
 {
   NS_ENSURE_FALSE(mInitialized, false);
 
+  // Get ids for both apps and only write to our member variables after we've
+  // verified that this worked.
+  uint32_t ownAppId = NO_APP_ID;
+  if (aOwnApp) {
+    nsresult rv = aOwnApp->GetLocalId(&ownAppId);
+    NS_ENSURE_SUCCESS(rv, false);
+    NS_ENSURE_TRUE(ownAppId != NO_APP_ID, false);
+  }
+
+  uint32_t containingAppId = NO_APP_ID;
+  if (aAppFrameOwnerApp) {
+    nsresult rv = aAppFrameOwnerApp->GetLocalId(&containingAppId);
+    NS_ENSURE_SUCCESS(rv, false);
+    NS_ENSURE_TRUE(containingAppId != NO_APP_ID, false);
+  }
+
   // Veryify that app id matches mAppId passed in originAttributes
-  MOZ_RELEASE_ASSERT(aOriginAttributes.mAppId == NO_APP_ID);
+  MOZ_RELEASE_ASSERT((aOwnApp && aOriginAttributes.mAppId == ownAppId) ||
+                     (aAppFrameOwnerApp && aOriginAttributes.mAppId == containingAppId) ||
+                     aOriginAttributes.mAppId == NO_APP_ID);
 
   mInitialized = true;
   mIsMozBrowserElement = aIsMozBrowserElement;
   mIsPrerendered = aIsPrerendered;
   mOriginAttributes = aOriginAttributes;
+  mContainingAppId = containingAppId;
+  mOwnApp = aOwnApp;
+  mContainingApp = aAppFrameOwnerApp;
   mPresentationURL = aPresentationURL;
   mShowAccelerators = aShowAccelerators;
   mShowFocusRings = aShowFocusRings;
   return true;
 }
 
 IPCTabContext
 TabContext::AsIPCTabContext() const
 {
   return IPCTabContext(FrameIPCTabContext(mOriginAttributes,
+                                          mContainingAppId,
                                           mIsMozBrowserElement,
                                           mIsPrerendered,
                                           mPresentationURL,
                                           mShowAccelerators,
                                           mShowFocusRings));
 }
 
+static already_AddRefed<mozIApplication>
+GetAppForId(uint32_t aAppId)
+{
+  nsCOMPtr<nsIAppsService> appsService = do_GetService(APPS_SERVICE_CONTRACTID);
+  NS_ENSURE_TRUE(appsService, nullptr);
+
+  nsCOMPtr<mozIApplication> app;
+  appsService->GetAppByLocalId(aAppId, getter_AddRefs(app));
+
+  return app.forget();
+}
+
 MaybeInvalidTabContext::MaybeInvalidTabContext(const IPCTabContext& aParams)
   : mInvalidReason(nullptr)
 {
   bool isMozBrowserElement = false;
   bool isPrerendered = false;
+  uint32_t containingAppId = NO_APP_ID;
   DocShellOriginAttributes originAttributes;
   nsAutoString presentationURL;
   UIStateChangeType showAccelerators = UIStateChangeType_NoChange;
   UIStateChangeType showFocusRings = UIStateChangeType_NoChange;
 
   switch(aParams.type()) {
     case IPCTabContext::TPopupIPCTabContext: {
       const PopupIPCTabContext &ipcContext = aParams.get_PopupIPCTabContext();
@@ -188,24 +330,30 @@ MaybeInvalidTabContext::MaybeInvalidTabC
       // our opener is browser element, we must be a new DOM window
       // opened by it.  In that case we inherit our containing app ID
       // (if any).
       //
       // Otherwise, we're a new app window and we inherit from our
       // opener app.
       isMozBrowserElement = ipcContext.isMozBrowserElement();
       originAttributes = context->mOriginAttributes;
+      if (isMozBrowserElement) {
+        containingAppId = context->OwnOrContainingAppId();
+      } else {
+        containingAppId = context->mContainingAppId;
+      }
       break;
     }
     case IPCTabContext::TFrameIPCTabContext: {
       const FrameIPCTabContext &ipcContext =
         aParams.get_FrameIPCTabContext();
 
       isMozBrowserElement = ipcContext.isMozBrowserElement();
       isPrerendered = ipcContext.isPrerendered();
+      containingAppId = ipcContext.frameOwnerAppId();
       presentationURL = ipcContext.presentationURL();
       showAccelerators = ipcContext.showAccelerators();
       showFocusRings = ipcContext.showFocusRings();
       originAttributes = ipcContext.originAttributes();
       break;
     }
     case IPCTabContext::TUnsafeIPCTabContext: {
       // XXXcatalinb: This used *only* by ServiceWorkerClients::OpenWindow.
@@ -216,26 +364,46 @@ MaybeInvalidTabContext::MaybeInvalidTabC
       mInvalidReason = "ServiceWorkerClients::OpenWindow is not supported.";
       return;
 #endif
       if (!Preferences::GetBool("dom.serviceWorkers.enabled", false)) {
         mInvalidReason = "ServiceWorkers should be enabled.";
         return;
       }
 
+      containingAppId = NO_APP_ID;
       break;
     }
     default: {
       MOZ_CRASH();
     }
   }
 
+  nsCOMPtr<mozIApplication> ownApp;
+  if (!isMozBrowserElement) {
+    // mAppId corresponds to OwnOrContainingAppId; if isMozBrowserElement is
+    // false then it's ownApp otherwise it's containingApp
+    ownApp = GetAppForId(originAttributes.mAppId);
+    if ((ownApp == nullptr) != (originAttributes.mAppId == NO_APP_ID)) {
+      mInvalidReason = "Got an ownAppId that didn't correspond to an app.";
+      return;
+    }
+  }
+
+  nsCOMPtr<mozIApplication> containingApp = GetAppForId(containingAppId);
+  if ((containingApp == nullptr) != (containingAppId == NO_APP_ID)) {
+    mInvalidReason = "Got a containingAppId that didn't correspond to an app.";
+    return;
+  }
+
   bool rv;
   rv = mTabContext.SetTabContext(isMozBrowserElement,
                                  isPrerendered,
+                                 ownApp,
+                                 containingApp,
                                  showAccelerators,
                                  showFocusRings,
                                  originAttributes,
                                  presentationURL);
   if (!rv) {
     mInvalidReason = "Couldn't initialize TabContext.";
   }
 }
--- a/dom/ipc/TabContext.h
+++ b/dom/ipc/TabContext.h
@@ -2,76 +2,132 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_TabContext_h
 #define mozilla_dom_TabContext_h
 
+#include "mozIApplication.h"
 #include "nsCOMPtr.h"
 #include "mozilla/BasePrincipal.h"
 #include "nsPIDOMWindow.h"
 #include "nsPIWindowRoot.h"
 
 namespace mozilla {
 namespace dom {
 
 class IPCTabContext;
 
 /**
- * TabContext encapsulates information about an iframe that may be a mozbrowser.
+ * TabContext encapsulates information about an iframe that may be a mozbrowser
+ * or mozapp.  You can ask whether a TabContext corresponds to a mozbrowser or
+ * mozapp, get the app that contains the browser, and so on.
  *
  * TabParent and TabChild both inherit from TabContext, and you can also have
  * standalone TabContext objects.
  *
  * This class is immutable except by calling one of the protected
  * SetTabContext*() methods (and those methods can only be called once).  See
  * also MutableTabContext.
  */
 class TabContext
 {
 public:
   TabContext();
 
   /* (The implicit copy-constructor and operator= are fine.) */
 
   /**
-   * Generates IPCTabContext of type BrowserFrameIPCTabContext from this
-   * TabContext's information.
+   * Generates IPCTabContext of type BrowserFrameIPCTabContext or
+   * AppFrameIPCTabContext from this TabContext's information.
    */
   IPCTabContext AsIPCTabContext() const;
 
   /**
    * Does this TabContext correspond to a mozbrowser?
    *
-   * <iframe mozbrowser> is a mozbrowser element, but <xul:browser> is not.
+   * <iframe mozbrowser mozapp> and <xul:browser> are not considered to be
+   * mozbrowser elements.
+   *
+   * If IsMozBrowserElement() is true, HasOwnApp() and HasAppOwnerApp() are
+   * guaranteed to be false.
+   *
+   * If IsMozBrowserElement() is false, HasBrowserOwnerApp() is guaranteed to be
+   * false.
    */
   bool IsMozBrowserElement() const;
 
   /**
    * Does this TabContext correspond to an isolated mozbrowser?
    *
-   * <iframe mozbrowser> is a mozbrowser element, but <xul:browser> is not.
-   * <iframe mozbrowser noisolation> does not count as isolated since isolation
-   * is disabled.  Isolation can only be disabled by chrome pages.
+   * <iframe mozbrowser mozapp> and <xul:browser> are not considered to be
+   * mozbrowser elements.  <iframe mozbrowser noisolation> does not count as
+   * isolated since isolation is disabled.  Isolation can only be disabled by
+   * chrome pages.
    */
   bool IsIsolatedMozBrowserElement() const;
 
   /**
-   * Does this TabContext correspond to a mozbrowser?  This is equivalent to
-   * IsMozBrowserElement().  Returns false for <xul:browser>, which isn't a
-   * mozbrowser.
+   * Does this TabContext correspond to a mozbrowser or mozapp?  This is
+   * equivalent to IsMozBrowserElement() || HasOwnApp().  Returns false for
+   * <xul:browser>, which is neither a mozbrowser nor a mozapp.
+   */
+  bool IsMozBrowserOrApp() const;
+
+  /**
+   * OwnAppId() returns the id of the app which directly corresponds to this
+   * context's frame.  GetOwnApp() returns the corresponding app object, and
+   * HasOwnApp() returns true iff GetOwnApp() would return a non-null value.
+   *
+   * If HasOwnApp() is true, IsMozBrowserElement() is guaranteed to be
+   * false.
    */
-  bool IsMozBrowser() const;
+  uint32_t OwnAppId() const;
+  already_AddRefed<mozIApplication> GetOwnApp() const;
+  bool HasOwnApp() const;
+
+  /**
+   * BrowserOwnerAppId() gets the ID of the app which contains this browser
+   * frame.  If this is not a mozbrowser frame (if !IsMozBrowserElement()), then
+   * BrowserOwnerAppId() is guaranteed to return NO_APP_ID.
+   *
+   * Even if we are a browser frame, BrowserOwnerAppId() may still return
+   * NO_APP_ID, if this browser frame is not contained inside an app.
+   */
+  uint32_t BrowserOwnerAppId() const;
+  already_AddRefed<mozIApplication> GetBrowserOwnerApp() const;
+  bool HasBrowserOwnerApp() const;
+
+  /**
+   * AppOwnerAppId() gets the ID of the app which contains this app frame.  If
+   * this is not an app frame (i.e., if !HasOwnApp()), then AppOwnerAppId() is
+   * guaranteed to return NO_APP_ID.
+   *
+   * Even if we are an app frame, AppOwnerAppId() may still return NO_APP_ID, if
+   * this app frame is not contained inside an app.
+   */
+  uint32_t AppOwnerAppId() const;
+  already_AddRefed<mozIApplication> GetAppOwnerApp() const;
+  bool HasAppOwnerApp() const;
+
+  /**
+   * OwnOrContainingAppId() gets the ID of this frame, if HasOwnApp().  If this
+   * frame does not have its own app, it gets the ID of the app which contains
+   * this frame (i.e., the result of {Browser,App}OwnerAppId(), as applicable).
+   */
+  uint32_t OwnOrContainingAppId() const;
+  already_AddRefed<mozIApplication> GetOwnOrContainingApp() const;
+  bool HasOwnOrContainingApp() const;
 
   /**
    * OriginAttributesRef() returns the DocShellOriginAttributes of this frame to
    * the caller. This is used to store any attribute associated with the frame's
-   * docshell.
+   * docshell, such as the AppId.
    */
   const DocShellOriginAttributes& OriginAttributesRef() const;
 
   /**
    * Returns the presentation URL associated with the tab if this tab is
    * created for presented content
    */
   const nsAString& PresentationURL() const;
@@ -96,18 +152,27 @@ protected:
    */
   bool SetTabContext(const TabContext& aContext);
 
   /**
    * Set the tab context's origin attributes to a private browsing value.
    */
   void SetPrivateBrowsingAttributes(bool aIsPrivateBrowsing);
 
+  /**
+   * Set the TabContext for this frame. This can either be:
+   *  - an app frame (with the given own app) inside the given owner app. Either
+   *    apps can be null.
+   *  - a browser frame inside the given owner app (which may be null).
+   *  - a non-browser, non-app frame. Both own app and owner app should be null.
+   */
   bool SetTabContext(bool aIsMozBrowserElement,
                      bool aIsPrerendered,
+                     mozIApplication* aOwnApp,
+                     mozIApplication* aAppFrameOwnerApp,
                      UIStateChangeType aShowAccelerators,
                      UIStateChangeType aShowFocusRings,
                      const DocShellOriginAttributes& aOriginAttributes,
                      const nsAString& aPresentationURL);
 
   /**
    * Modify this TabContext to match the given TabContext.  This is a special
    * case triggered by nsFrameLoader::SwapWithOtherRemoteLoader which may have
@@ -128,22 +193,40 @@ private:
   /**
    * Has this TabContext been initialized?  If so, mutator methods will fail.
    */
   bool mInitialized;
 
   /**
    * Whether this TabContext corresponds to a mozbrowser.
    *
-   * <iframe mozbrowser> and <xul:browser> are not considered to be
+   * <iframe mozbrowser mozapp> and <xul:browser> are not considered to be
    * mozbrowser elements.
    */
   bool mIsMozBrowserElement;
 
   /**
+   * This TabContext's own app.  If this is non-null, then this
+   * TabContext corresponds to an app, and mIsMozBrowserElement must be false.
+   */
+  nsCOMPtr<mozIApplication> mOwnApp;
+
+  /**
+   * This TabContext's containing app.  If mIsMozBrowserElement, this
+   * corresponds to the app which contains the browser frame; otherwise, this
+   * corresponds to the app which contains the app frame.
+   */
+  nsCOMPtr<mozIApplication> mContainingApp;
+
+  /*
+   * Cache of mContainingApp->GetLocalId().
+   */
+  uint32_t mContainingAppId;
+
+  /**
    * DocShellOriginAttributes of the top level tab docShell
    */
   DocShellOriginAttributes mOriginAttributes;
 
   /**
    * The requested presentation URL.
    */
   nsString mPresentationURL;
@@ -166,37 +249,42 @@ public:
   bool SetTabContext(const TabContext& aContext)
   {
     return TabContext::SetTabContext(aContext);
   }
 
   bool
   SetTabContext(bool aIsMozBrowserElement,
                 bool aIsPrerendered,
+                mozIApplication* aOwnApp,
+                mozIApplication* aAppFrameOwnerApp,
                 UIStateChangeType aShowAccelerators,
                 UIStateChangeType aShowFocusRings,
                 const DocShellOriginAttributes& aOriginAttributes,
                 const nsAString& aPresentationURL = EmptyString())
   {
     return TabContext::SetTabContext(aIsMozBrowserElement,
                                      aIsPrerendered,
+                                     aOwnApp,
+                                     aAppFrameOwnerApp,
                                      aShowAccelerators,
                                      aShowFocusRings,
                                      aOriginAttributes,
                                      aPresentationURL);
   }
 };
 
 /**
  * MaybeInvalidTabContext is a simple class that lets you transform an
  * IPCTabContext into a TabContext.
  *
- * The issue is that an IPCTabContext is not necessarily valid.  So to convert
- * an IPCTabContext into a TabContext, you construct a MaybeInvalidTabContext,
- * check whether it's valid, and, if so, read out your TabContext.
+ * The issue is that an IPCTabContext is not necessarily valid; for example, it
+ * might specify an app-id which doesn't exist.  So to convert an IPCTabContext
+ * into a TabContext, you construct a MaybeInvalidTabContext, check whether it's
+ * valid, and, if so, read out your TabContext.
  *
  * Example usage:
  *
  *   void UseTabContext(const TabContext& aTabContext);
  *
  *   void CreateTab(const IPCTabContext& aContext) {
  *     MaybeInvalidTabContext tc(aContext);
  *     if (!tc.IsValid()) {
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "base/basictypes.h"
 
 #include "TabParent.h"
 
 #include "AudioChannelService.h"
 #include "AppProcessChecker.h"
+#include "mozIApplication.h"
 #ifdef ACCESSIBILITY
 #include "mozilla/a11y/DocAccessibleParent.h"
 #include "nsAccessibilityService.h"
 #endif
 #include "mozilla/BrowserElementParent.h"
 #include "mozilla/dom/ContentBridgeParent.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/DataTransfer.h"
@@ -2967,16 +2968,17 @@ public:
     return NS_OK;
   }
   NS_IMETHOD GetNestedFrameId(uint64_t*) NO_IMPL
   NS_IMETHOD GetIsContent(bool*) NO_IMPL
   NS_IMETHOD GetUsePrivateBrowsing(bool*) NO_IMPL
   NS_IMETHOD SetUsePrivateBrowsing(bool) NO_IMPL
   NS_IMETHOD SetPrivateBrowsing(bool) NO_IMPL
   NS_IMETHOD GetIsInIsolatedMozBrowserElement(bool*) NO_IMPL
+  NS_IMETHOD GetAppId(uint32_t*) NO_IMPL
   NS_IMETHOD GetOriginAttributes(JS::MutableHandleValue) NO_IMPL
   NS_IMETHOD GetUseRemoteTabs(bool*) NO_IMPL
   NS_IMETHOD SetRemoteTabs(bool) NO_IMPL
   NS_IMETHOD IsTrackingProtectionOn(bool*) NO_IMPL
 #undef NO_IMPL
 
 protected:
   ~FakeChannel() {}
--- a/dom/ipc/nsIContentChild.cpp
+++ b/dom/ipc/nsIContentChild.cpp
@@ -39,16 +39,17 @@ nsIContentChild::DeallocPJavaScriptChild
   return true;
 }
 
 PBrowserChild*
 nsIContentChild::AllocPBrowserChild(const TabId& aTabId,
                                     const IPCTabContext& aContext,
                                     const uint32_t& aChromeFlags,
                                     const ContentParentId& aCpID,
+                                    const bool& aIsForApp,
                                     const bool& aIsForBrowser)
 {
   // We'll happily accept any kind of IPCTabContext here; we don't need to
   // check that it's of a certain type for security purposes, because we
   // believe whatever the parent process tells us.
 
   MaybeInvalidTabContext tc(aContext);
   if (!tc.IsValid()) {
--- a/dom/ipc/nsIContentChild.h
+++ b/dom/ipc/nsIContentChild.h
@@ -64,32 +64,34 @@ public:
                        const BlobConstructorParams& aParams) = 0;
 
   virtual bool
   SendPBrowserConstructor(PBrowserChild* aActor,
                           const TabId& aTabId,
                           const IPCTabContext& aContext,
                           const uint32_t& aChromeFlags,
                           const ContentParentId& aCpID,
+                          const bool& aIsForApp,
                           const bool& aIsForBrowser) = 0;
 
   virtual mozilla::ipc::PFileDescriptorSetChild*
   SendPFileDescriptorSetConstructor(const mozilla::ipc::FileDescriptor&) = 0;
 
   virtual mozilla::ipc::PSendStreamChild*
   SendPSendStreamConstructor(mozilla::ipc::PSendStreamChild*) = 0;
 
 protected:
   virtual jsipc::PJavaScriptChild* AllocPJavaScriptChild();
   virtual bool DeallocPJavaScriptChild(jsipc::PJavaScriptChild*);
 
   virtual PBrowserChild* AllocPBrowserChild(const TabId& aTabId,
                                             const IPCTabContext& aContext,
                                             const uint32_t& aChromeFlags,
                                             const ContentParentId& aCpId,
+                                            const bool& aIsForApp,
                                             const bool& aIsForBrowser);
   virtual bool DeallocPBrowserChild(PBrowserChild*);
 
   virtual PBlobChild* AllocPBlobChild(const BlobConstructorParams& aParams);
 
   virtual bool DeallocPBlobChild(PBlobChild* aActor);
 
   virtual mozilla::ipc::PSendStreamChild* AllocPSendStreamChild();
--- a/dom/ipc/nsIContentParent.cpp
+++ b/dom/ipc/nsIContentParent.cpp
@@ -117,19 +117,21 @@ nsIContentParent::CanOpenBrowser(const I
   return true;
 }
 
 PBrowserParent*
 nsIContentParent::AllocPBrowserParent(const TabId& aTabId,
                                       const IPCTabContext& aContext,
                                       const uint32_t& aChromeFlags,
                                       const ContentParentId& aCpId,
+                                      const bool& aIsForApp,
                                       const bool& aIsForBrowser)
 {
   Unused << aCpId;
+  Unused << aIsForApp;
   Unused << aIsForBrowser;
 
   if (!CanOpenBrowser(aContext)) {
     return nullptr;
   }
 
   uint32_t chromeFlags = aChromeFlags;
   if (aContext.type() == IPCTabContext::TPopupIPCTabContext) {
--- a/dom/ipc/nsIContentParent.h
+++ b/dom/ipc/nsIContentParent.h
@@ -56,28 +56,30 @@ public:
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_ICONTENTPARENT_IID)
 
   nsIContentParent();
 
   BlobParent* GetOrCreateActorForBlob(Blob* aBlob);
   BlobParent* GetOrCreateActorForBlobImpl(BlobImpl* aImpl);
 
   virtual ContentParentId ChildID() const = 0;
+  virtual bool IsForApp() const = 0;
   virtual bool IsForBrowser() const = 0;
 
   MOZ_MUST_USE virtual PBlobParent*
   SendPBlobConstructor(PBlobParent* aActor,
                        const BlobConstructorParams& aParams) = 0;
 
   MOZ_MUST_USE virtual PBrowserParent*
   SendPBrowserConstructor(PBrowserParent* actor,
                           const TabId& aTabId,
                           const IPCTabContext& context,
                           const uint32_t& chromeFlags,
                           const ContentParentId& aCpId,
+                          const bool& aIsForApp,
                           const bool& aIsForBrowser) = 0;
 
   virtual bool IsContentParent() const { return false; }
 
   ContentParent* AsContentParent();
 
   virtual bool IsContentBridgeParent() const { return false; }
 
@@ -93,16 +95,17 @@ protected: // methods
 protected: // IPDL methods
   virtual mozilla::jsipc::PJavaScriptParent* AllocPJavaScriptParent();
   virtual bool DeallocPJavaScriptParent(mozilla::jsipc::PJavaScriptParent*);
 
   virtual PBrowserParent* AllocPBrowserParent(const TabId& aTabId,
                                               const IPCTabContext& aContext,
                                               const uint32_t& aChromeFlags,
                                               const ContentParentId& aCpId,
+                                              const bool& aIsForApp,
                                               const bool& aIsForBrowser);
   virtual bool DeallocPBrowserParent(PBrowserParent* frame);
 
   virtual PBlobParent* AllocPBlobParent(const BlobConstructorParams& aParams);
 
   virtual bool DeallocPBlobParent(PBlobParent* aActor);
 
   virtual mozilla::ipc::PFileDescriptorSetParent*
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1901,25 +1901,39 @@ MediaManager::NotifyRecordingStatusChang
     NS_WARNING("Could not get the Observer service for GetUserMedia recording notification.");
     return NS_ERROR_FAILURE;
   }
 
   RefPtr<nsHashPropertyBag> props = new nsHashPropertyBag();
   props->SetPropertyAsBool(NS_LITERAL_STRING("isAudio"), aIsAudio);
   props->SetPropertyAsBool(NS_LITERAL_STRING("isVideo"), aIsVideo);
 
-  nsCString pageURL;
-  nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI();
-  NS_ENSURE_TRUE(docURI, NS_ERROR_FAILURE);
-
-  nsresult rv = docURI->GetSpec(pageURL);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  NS_ConvertUTF8toUTF16 requestURL(pageURL);
-
+  bool isApp = false;
+  nsString requestURL;
+
+  if (nsCOMPtr<nsIDocShell> docShell = aWindow->GetDocShell()) {
+    isApp = docShell->GetIsApp();
+    if (isApp) {
+      nsresult rv = docShell->GetAppManifestURL(requestURL);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
+  if (!isApp) {
+    nsCString pageURL;
+    nsCOMPtr<nsIURI> docURI = aWindow->GetDocumentURI();
+    NS_ENSURE_TRUE(docURI, NS_ERROR_FAILURE);
+
+    nsresult rv = docURI->GetSpec(pageURL);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    requestURL = NS_ConvertUTF8toUTF16(pageURL);
+  }
+
+  props->SetPropertyAsBool(NS_LITERAL_STRING("isApp"), isApp);
   props->SetPropertyAsAString(NS_LITERAL_STRING("requestURL"), requestURL);
 
   obs->NotifyObservers(static_cast<nsIPropertyBag2*>(props),
                        "recording-device-events",
                        aMsg.get());
 
   // Forward recording events to parent process.
   // The events are gathered in chrome process and used for recording indicator
--- a/dom/network/TCPServerSocketParent.cpp
+++ b/dom/network/TCPServerSocketParent.cpp
@@ -60,17 +60,23 @@ void
 TCPServerSocketParent::Init()
 {
   NS_ENSURE_SUCCESS_VOID(mServerSocket->Init());
 }
 
 uint32_t
 TCPServerSocketParent::GetAppId()
 {
-  return nsIScriptSecurityManager::UNKNOWN_APP_ID;
+  const PContentParent *content = Manager()->Manager();
+  if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) {
+    TabParent *tab = TabParent::GetFrom(browser);
+    return tab->OwnAppId();
+  } else {
+    return nsIScriptSecurityManager::UNKNOWN_APP_ID;
+  }
 }
 
 bool
 TCPServerSocketParent::GetInIsolatedMozBrowser()
 {
   const PContentParent *content = Manager()->Manager();
   if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) {
     TabParent *tab = TabParent::GetFrom(browser);
--- a/dom/network/TCPSocketParent.cpp
+++ b/dom/network/TCPSocketParent.cpp
@@ -68,17 +68,23 @@ TCPSocketParentBase::TCPSocketParentBase
 
 TCPSocketParentBase::~TCPSocketParentBase()
 {
 }
 
 uint32_t
 TCPSocketParent::GetAppId()
 {
-  return nsIScriptSecurityManager::UNKNOWN_APP_ID;
+  const PContentParent *content = Manager()->Manager();
+  if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) {
+    TabParent *tab = TabParent::GetFrom(browser);
+    return tab->OwnAppId();
+  } else {
+    return nsIScriptSecurityManager::UNKNOWN_APP_ID;
+  }
 };
 
 bool
 TCPSocketParent::GetInIsolatedMozBrowser()
 {
   const PContentParent *content = Manager()->Manager();
   if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) {
     TabParent *tab = TabParent::GetFrom(browser);
@@ -200,25 +206,30 @@ TCPSocketParent::RecvOpenBind(const nsCS
     rv = filterHandler->NewFilter(getter_AddRefs(mFilter));
     if (NS_FAILED(rv)) {
       NS_ERROR("Cannot create filter that content specified");
       FireInteralError(this, __LINE__);
       return true;
     }
   }
 
+  // Obtain App ID
+  uint32_t appId = nsIScriptSecurityManager::NO_APP_ID;
   bool     inIsolatedMozBrowser = false;
   const PContentParent *content = Manager()->Manager();
   if (PBrowserParent* browser = SingleManagedOrNull(content->ManagedPBrowserParent())) {
+    // appId's are for B2G only currently, where managees.Count() == 1
+    // This is not guaranteed currently in Desktop, so skip this there.
     TabParent *tab = TabParent::GetFrom(browser);
+    appId = tab->OwnAppId();
     inIsolatedMozBrowser = tab->IsIsolatedMozBrowserElement();
   }
 
   mSocket = new TCPSocket(nullptr, NS_ConvertUTF8toUTF16(aRemoteHost), aRemotePort, aUseSSL, aUseArrayBuffers);
-  mSocket->SetAppIdAndBrowser(nsIScriptSecurityManager::NO_APP_ID, inIsolatedMozBrowser);
+  mSocket->SetAppIdAndBrowser(appId, inIsolatedMozBrowser);
   mSocket->SetSocketBridgeParent(this);
   rv = mSocket->InitWithUnconnectedTransport(socketTransport);
   NS_ENSURE_SUCCESS(rv, true);
   return true;
 }
 
 bool
 TCPSocketParent::RecvStartTLS()
--- a/dom/network/interfaces/nsINetworkStatsServiceProxy.idl
+++ b/dom/network/interfaces/nsINetworkStatsServiceProxy.idl
@@ -18,21 +18,21 @@ interface nsINetworkStatsServiceProxyCal
 
 [scriptable, uuid(f4f3e901-e102-499d-9d37-dc9951f52df7)]
 interface nsINetworkStatsServiceProxy : nsISupports
 {
   /*
    * An interface used to record per-app traffic data.
    * @param aAppId app id
    * @param aIsInIsolatedMozBrowser
-   *        true if the frame is an isolated mozbrowser element. <xul:browser>
-   *        is not considered to be mozbrowser elements.
-   *        <iframe mozbrowser noisolation> does not count as isolated since
-   *        isolation is disabled.  Isolation can only be disabled if the
-   *        containing document is chrome.
+   *        true if the frame is an isolated mozbrowser element. <iframe
+   *        mozbrowser mozapp> and <xul:browser> are not considered to be
+   *        mozbrowser elements.  <iframe mozbrowser noisolation> does not count
+   *        as isolated since isolation is disabled.  Isolation can only be
+   *        disabled if the containing document is chrome.
    * @param aNetworkInterface network
    * @param aTimeStamp time stamp
    * @param aRxBytes received data amount
    * @param aTxBytes transmitted data amount
    * @param aIsAccumulative is stats accumulative
    * @param aCallback an optional callback
    */
   void saveAppStats(in unsigned long aAppId,
new file mode 100644
--- /dev/null
+++ b/dom/permission/tests/test_embed-apps.html
@@ -0,0 +1,61 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=815105
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 815105 </title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=815105">Mozilla Bug 815105 </a>
+<p id="display"></p>
+<div id="content" style="display: none"></div>
+<pre id="test">
+<script type="application/javascript;version=1.8" src="file_framework.js"></script>
+<script type="application/javascript;version=1.8">
+/*
+ * embed-apps allows us to create app iframes,
+ * creator must also have the browser permission
+ */
+function verifier(success, failure) {
+  var iframe = document.createElement('iframe');
+  iframe.setAttribute('mozbrowser', '');
+  iframe.setAttribute('mozapp', 'http://example.org/manifest.webapp');
+  iframe.src = "http://example.org/";
+  iframe.addEventListener('load', function() {
+    var appStatus = SpecialPowers.wrap(iframe)
+                                 .contentDocument.nodePrincipal.appStatus;
+
+    if (appStatus != SpecialPowers.Ci
+                                  .nsIPrincipal.APP_STATUS_NOT_INSTALLED) {
+      success("Got mozapp");
+    } else {
+      failure("Didn't get mozapp") ;
+    }
+  });
+
+  document.getElementById('content').appendChild(iframe);
+}
+
+var gData = [
+  {
+    perm: ["embed-apps", "browser"],
+    /*
+     * Android doesn't have working apps
+     * Mobile is for B2G which has a weird testing setup
+     * the app iframe gets embed in the test-container iframe
+     * which returns APP_STATUS_NOT_INSTALLED
+     */
+    skip: ["Android", "Mobile"],
+    settings: [["dom.mozBrowserFramesEnabled", true]],
+    verifier: verifier.toSource(),
+  }
+]
+</script>
+</pre>
+</body>
+</html>
+
--- a/dom/presentation/Presentation.cpp
+++ b/dom/presentation/Presentation.cpp
@@ -127,17 +127,17 @@ Presentation::HasReceiverSupport() const
   // Grant access to browser receiving pages and their same-origin iframes. (App
   // pages should be controlled by "presentation" permission in app manifests.)
   nsCOMPtr<nsIDocShell> docShell = mWindow->GetDocShell();
   if (!docShell) {
     return false;
   }
 
   if (!Preferences::GetBool("dom.presentation.testing.simulate-receiver") &&
-      !docShell->GetIsInMozBrowser() &&
+      !docShell->GetIsInMozBrowserOrApp() &&
       !docShell->GetIsTopLevelContentDocShell()) {
     return false;
   }
 
   nsAutoString presentationURL;
   nsContentUtils::GetPresentationURL(docShell, presentationURL);
 
   if (presentationURL.IsEmpty()) {
--- a/dom/webidl/HTMLIFrameElement.webidl
+++ b/dom/webidl/HTMLIFrameElement.webidl
@@ -55,10 +55,16 @@ partial interface HTMLIFrameElement {
 };
 
 partial interface HTMLIFrameElement {
   // nsIDOMMozBrowserFrame
   [ChromeOnly,SetterThrows]
            attribute boolean mozbrowser;
 };
 
+partial interface HTMLIFrameElement {
+  // nsIMozBrowserFrame
+  [ChromeOnly]
+  readonly attribute DOMString appManifestURL;
+};
+
 HTMLIFrameElement implements MozFrameLoaderOwner;
 HTMLIFrameElement implements BrowserElement;
--- a/dom/webidl/MozNetworkStats.webidl
+++ b/dom/webidl/MozNetworkStats.webidl
@@ -15,17 +15,17 @@ dictionary NetworkStatsGetOptions
    * others, an NS_ERROR_NOT_IMPLMENTED exception will be thrown.
    */
   DOMString? appManifestURL = null;
   DOMString serviceType = "";
   /**
    * If it is set as true, only the browsing traffic, which is generated from
    * the mozbrowser iframe element within an app, is returned in result.
    * If it is set as false or not set, the total traffic, which is generated
-   * from mozbrowser iframe elements, is returned.
+   * from both the mozapp and mozbrowser iframe elements, is returned.
    */
   boolean browsingTrafficOnly = false;
 };
 
 dictionary NetworkStatsAlarmOptions
 {
   Date startTime;
   Date data;
--- a/dom/xul/nsXULElement.cpp
+++ b/dom/xul/nsXULElement.cpp
@@ -1627,16 +1627,27 @@ nsXULElement::GetFrameLoader()
     nsXULSlots* slots = static_cast<nsXULSlots*>(GetExistingSlots());
     if (!slots)
         return nullptr;
 
     nsCOMPtr<nsIFrameLoader> loader = do_QueryInterface(slots->mFrameLoaderOrOpener);
     return already_AddRefed<nsFrameLoader>(static_cast<nsFrameLoader*>(loader.forget().take()));
 }
 
+nsresult
+nsXULElement::GetParentApplication(mozIApplication** aApplication)
+{
+    if (!aApplication) {
+        return NS_ERROR_FAILURE;
+    }
+
+    *aApplication = nullptr;
+    return NS_OK;
+}
+
 void
 nsXULElement::PresetOpenerWindow(mozIDOMWindowProxy* aWindow, ErrorResult& aRv)
 {
     nsXULSlots* slots = static_cast<nsXULSlots*>(Slots());
     MOZ_ASSERT(!slots->mFrameLoaderOrOpener, "A frameLoader or opener is present when calling PresetOpenerWindow");
 
     slots->mFrameLoaderOrOpener = aWindow;
 }
--- a/dom/xul/nsXULElement.h
+++ b/dom/xul/nsXULElement.h
@@ -406,16 +406,17 @@ public:
 
     // nsIDOMXULElement
     NS_DECL_NSIDOMXULELEMENT
 
     virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
     virtual mozilla::EventStates IntrinsicState() const override;
 
     nsresult GetFrameLoaderXPCOM(nsIFrameLoader** aFrameLoader);
+    nsresult GetParentApplication(mozIApplication** aApplication);
     void PresetOpenerWindow(mozIDOMWindowProxy* aWindow, ErrorResult& aRv);
     nsresult SetIsPrerendered();
 
     virtual void RecompileScriptEventListeners() override;
 
     // This function should ONLY be used by BindToTree implementations.
     // The function exists solely because XUL elements store the binding
     // parent as a member instead of in the slots, as Element does.
--- a/embedding/components/windowwatcher/nsWindowWatcher.cpp
+++ b/embedding/components/windowwatcher/nsWindowWatcher.cpp
@@ -1982,17 +1982,17 @@ nsWindowWatcher::CalculateChromeFlagsFor
   // Check security state for use in determing window dimensions
   if (!aHasChromeParent) {
     chromeFlags = EnsureFlagsSafeForContent(chromeFlags, aChromeURL);
   }
 
   // Disable CHROME_OPENAS_DIALOG if the window is inside <iframe mozbrowser>.
   // It's up to the embedder to interpret what dialog=1 means.
   nsCOMPtr<nsIDocShell> docshell = do_GetInterface(aParent);
-  if (docshell && docshell->GetIsInMozBrowser()) {
+  if (docshell && docshell->GetIsInMozBrowserOrApp()) {
     chromeFlags &= ~nsIWebBrowserChrome::CHROME_OPENAS_DIALOG;
   }
 
   return chromeFlags;
 }
 
 // static
 int32_t
--- a/extensions/cookie/nsPermissionManager.cpp
+++ b/extensions/cookie/nsPermissionManager.cpp
@@ -25,16 +25,17 @@
 #include "prprf.h"
 #include "mozilla/storage.h"
 #include "mozilla/Attributes.h"
 #include "nsXULAppAPI.h"
 #include "nsIPrincipal.h"
 #include "nsContentUtils.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIAppsService.h"
+#include "mozIApplication.h"
 #include "nsIEffectiveTLDService.h"
 #include "nsPIDOMWindow.h"
 #include "nsIDocument.h"
 #include "mozilla/net/NeckoMessageUtils.h"
 #include "mozilla/Preferences.h"
 #include "nsReadLine.h"
 #include "mozilla/Telemetry.h"
 #include "nsIConsoleService.h"
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -6991,16 +6991,30 @@ CheckPermissionForBeforeAfterKeyboardEve
   // able to handle before events and after events.
   nsCOMPtr<nsIPermissionManager> permMgr = services::GetPermissionManager();
   uint32_t permission = nsIPermissionManager::DENY_ACTION;
   if (permMgr) {
     permMgr->TestPermissionFromPrincipal(principal, "before-after-keyboard-event", &permission);
     if (permission == nsIPermissionManager::ALLOW_ACTION) {
       return true;
     }
+
+    // Check "embed-apps" permission for later use.
+    permission = nsIPermissionManager::DENY_ACTION;
+    permMgr->TestPermissionFromPrincipal(principal, "embed-apps", &permission);
+  }
+
+  // An element can handle before events and after events if the following
+  // conditions are met:
+  // 1) <iframe mozbrowser mozapp>
+  // 2) it has "embed-apps" permission.
+  nsCOMPtr<nsIMozBrowserFrame> browserFrame(do_QueryInterface(aElement));
+  if ((permission == nsIPermissionManager::ALLOW_ACTION) &&
+      browserFrame && browserFrame->GetReallyIsApp()) {
+    return true;
   }
 
   return false;
 }
 
 static void
 BuildTargetChainForBeforeAfterKeyboardEvent(nsINode* aTarget,
                                             nsTArray<nsCOMPtr<Element> >& aChain,
--- a/layout/generic/nsViewportFrame.cpp
+++ b/layout/generic/nsViewportFrame.cpp
@@ -81,17 +81,17 @@ ViewportFrame::BuildDisplayList(nsDispla
  */
 static bool
 ShouldInTopLayerForFullscreen(Element* aElement)
 {
   if (!aElement->GetParent()) {
     return false;
   }
   nsCOMPtr<nsIMozBrowserFrame> browserFrame = do_QueryInterface(aElement);
-  if (browserFrame && browserFrame->GetReallyIsBrowser()) {
+  if (browserFrame && browserFrame->GetReallyIsBrowserOrApp()) {
     return false;
   }
   return true;
 }
 #endif // DEBUG
 
 static void
 BuildDisplayListForTopLayerFrame(nsDisplayListBuilder* aBuilder,
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -2070,17 +2070,17 @@ static bool SelectorMatches(Element* aEl
         }
         break;
 
       case CSSPseudoClassType::mozBrowserFrame:
         {
           nsCOMPtr<nsIMozBrowserFrame>
             browserFrame = do_QueryInterface(aElement);
           if (!browserFrame ||
-              !browserFrame->GetReallyIsBrowser()) {
+              !browserFrame->GetReallyIsBrowserOrApp()) {
             return false;
           }
         }
         break;
 
       case CSSPseudoClassType::mozDir:
       case CSSPseudoClassType::dir:
         {
--- a/layout/tools/reftest/reftest.jsm
+++ b/layout/tools/reftest/reftest.jsm
@@ -304,16 +304,17 @@ this.OnRefTestLoad = function OnRefTestL
     }
     if (gContainingWindow == null && win != null) {
       gContainingWindow = win;
     }
 
     if (gBrowserIsIframe) {
       gBrowser = gContainingWindow.document.createElementNS(XHTML_NS, "iframe");
       gBrowser.setAttribute("mozbrowser", "");
+      gBrowser.setAttribute("mozapp", prefs.getCharPref("b2g.system_manifest_url"));
     } else {
       gBrowser = gContainingWindow.document.createElementNS(XUL_NS, "xul:browser");
     }
     gBrowser.setAttribute("id", "browser");
     gBrowser.setAttribute("type", "content-primary");
     gBrowser.setAttribute("remote", gBrowserIsRemote ? "true" : "false");
     // Make sure the browser element is exactly 800x1000, no matter
     // what size our window is
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -3411,16 +3411,25 @@ Tab.prototype = {
 
     // Make sure the previously selected panel remains selected. The selected panel of a deck is
     // not stable when panels are added.
     let selectedPanel = BrowserApp.deck.selectedPanel;
     BrowserApp.deck.insertBefore(this.browser, aParams.sibling || null);
     BrowserApp.deck.selectedPanel = selectedPanel;
 
     let attrs = {};
+    if (BrowserApp.manifestUrl) {
+      let appsService = Cc["@mozilla.org/AppsService;1"].getService(Ci.nsIAppsService);
+      let manifest = appsService.getAppByManifestURL(BrowserApp.manifestUrl);
+      if (manifest) {
+        let app = manifest.QueryInterface(Ci.mozIApplication);
+        this.browser.docShell.frameType = Ci.nsIDocShell.FRAME_TYPE_APP;
+        attrs['appId'] = app.localId;
+      }
+    }
 
     // Must be called after appendChild so the docShell has been created.
     this.setActive(false);
 
     let isPrivate = ("isPrivate" in aParams) && aParams.isPrivate;
     if (isPrivate) {
       attrs['privateBrowsingId'] = 1;
     }
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -958,16 +958,18 @@ pref("devtools.debugger.remote-enabled",
 pref("devtools.debugger.remote-port", 6000);
 pref("devtools.debugger.remote-websocket", false);
 // Force debugger server binding on the loopback interface
 pref("devtools.debugger.force-local", true);
 // Display a prompt when a new connection starts to accept/reject it
 pref("devtools.debugger.prompt-connection", true);
 // Block tools from seeing / interacting with certified apps
 pref("devtools.debugger.forbid-certified-apps", true);
+// List of permissions that a sideloaded app can't ask for
+pref("devtools.apps.forbidden-permissions", "embed-apps");
 
 // DevTools default color unit
 pref("devtools.defaultColorUnit", "authored");
 
 // Used for devtools debugging
 pref("devtools.dump.emit", false);
 
 // Disable device discovery logging
--- a/netwerk/base/nsNetUtil.cpp
+++ b/netwerk/base/nsNetUtil.cpp
@@ -563,22 +563,25 @@ NS_LoadGroupMatchesPrincipal(nsILoadGrou
         return false;
     }
 
     nsCOMPtr<nsILoadContext> loadContext;
     NS_QueryNotificationCallbacks(nullptr, aLoadGroup, NS_GET_IID(nsILoadContext),
                                   getter_AddRefs(loadContext));
     NS_ENSURE_TRUE(loadContext, false);
 
-    // Verify load context browser flag match the principal
+    // Verify load context appId and browser flag match the principal
+    uint32_t contextAppId;
     bool contextInIsolatedBrowser;
-    nsresult rv = loadContext->GetIsInIsolatedMozBrowserElement(&contextInIsolatedBrowser);
+    nsresult rv = loadContext->GetAppId(&contextAppId);
+    NS_ENSURE_SUCCESS(rv, false);
+    rv = loadContext->GetIsInIsolatedMozBrowserElement(&contextInIsolatedBrowser);
     NS_ENSURE_SUCCESS(rv, false);
 
-    return nsIScriptSecurityManager::NO_APP_ID == aPrincipal->GetAppId() &&
+    return contextAppId == aPrincipal->GetAppId() &&
            contextInIsolatedBrowser == aPrincipal->GetIsInIsolatedMozBrowserElement();
 }
 
 nsresult
 NS_NewDownloader(nsIStreamListener   **result,
                  nsIDownloadObserver  *observer,
                  nsIFile              *downloadLocation /* = nullptr */)
 {
@@ -2386,34 +2389,44 @@ NS_CompareLoadInfoAndLoadContext(nsIChan
   // originAttributes between loadInfo and loadContext will be different.
   // That's why we have to skip the comparison for the favicon loading.
   if (nsContentUtils::IsSystemPrincipal(loadInfo->LoadingPrincipal()) &&
       loadInfo->InternalContentPolicyType() ==
         nsIContentPolicy::TYPE_INTERNAL_IMAGE_FAVICON) {
     return NS_OK;
   }
 
+  uint32_t loadContextAppId = 0;
+  nsresult rv = loadContext->GetAppId(&loadContextAppId);
+  if (NS_FAILED(rv)) {
+    return NS_ERROR_UNEXPECTED;
+  }
+
   bool loadContextIsInBE = false;
-  nsresult rv = loadContext->GetIsInIsolatedMozBrowserElement(&loadContextIsInBE);
+  rv = loadContext->GetIsInIsolatedMozBrowserElement(&loadContextIsInBE);
   if (NS_FAILED(rv)) {
     return NS_ERROR_UNEXPECTED;
   }
 
   OriginAttributes originAttrsLoadInfo = loadInfo->GetOriginAttributes();
   DocShellOriginAttributes originAttrsLoadContext;
   loadContext->GetOriginAttributes(originAttrsLoadContext);
 
   LOG(("NS_CompareLoadInfoAndLoadContext - loadInfo: %d, %d, %d, %d; "
-       "loadContext: %d %d, %d. [channel=%p]",
+       "loadContext: %d %d, %d, %d. [channel=%p]",
        originAttrsLoadInfo.mAppId, originAttrsLoadInfo.mInIsolatedMozBrowser,
        originAttrsLoadInfo.mUserContextId, originAttrsLoadInfo.mPrivateBrowsingId,
-       loadContextIsInBE,
+       loadContextAppId, loadContextIsInBE,
        originAttrsLoadContext.mUserContextId, originAttrsLoadContext.mPrivateBrowsingId,
        aChannel));
 
+  MOZ_ASSERT(originAttrsLoadInfo.mAppId == loadContextAppId,
+             "AppId in the loadContext and in the loadInfo are not the "
+             "same!");
+
   MOZ_ASSERT(originAttrsLoadInfo.mInIsolatedMozBrowser ==
              loadContextIsInBE,
              "The value of InIsolatedMozBrowser in the loadContext and in "
              "the loadInfo are not the same!");
 
   MOZ_ASSERT(originAttrsLoadInfo.mUserContextId ==
              originAttrsLoadContext.mUserContextId,
              "The value of mUserContextId in the loadContext and in the "
--- a/netwerk/cookie/nsCookieService.cpp
+++ b/netwerk/cookie/nsCookieService.cpp
@@ -46,16 +46,17 @@
 #include "nsIInputStream.h"
 #include "nsAppDirectoryServiceDefs.h"
 #include "nsNetCID.h"
 #include "mozilla/storage.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Telemetry.h"
 #include "nsIAppsService.h"
+#include "mozIApplication.h"
 #include "mozIApplicationClearPrivateDataParams.h"
 #include "nsIConsoleService.h"
 #include "nsVariant.h"
 
 using namespace mozilla;
 using namespace mozilla::net;
 
 // Create key from baseDomain that will access the default cookie namespace.
--- a/netwerk/ipc/NeckoParent.cpp
+++ b/netwerk/ipc/NeckoParent.cpp
@@ -172,28 +172,40 @@ NeckoParent::GetValidatedAppInfo(const S
   }
 
   nsTArray<TabContext> contextArray =
     static_cast<ContentParent*>(aContent)->GetManagedTabContext();
 
   nsAutoCString debugString;
   for (uint32_t i = 0; i < contextArray.Length(); i++) {
     TabContext tabContext = contextArray[i];
+    uint32_t appId = tabContext.OwnOrContainingAppId();
     bool inBrowserElement = aSerialized.mOriginAttributes.mInIsolatedMozBrowser;
 
+    if (appId == NECKO_UNKNOWN_APP_ID) {
+      debugString.Append("u,");
+      continue;
+    }
+    // We may get appID=NO_APP if child frame is neither a browser nor an app
+    if (appId == NECKO_NO_APP_ID && tabContext.HasOwnApp()) {
+      // NECKO_NO_APP_ID but also is an app?  Weird, skip.
+      debugString.Append("h,");
+      continue;
+    }
+
     if (aSerialized.mOriginAttributes.mUserContextId != tabContext.OriginAttributesRef().mUserContextId) {
       debugString.Append("(");
       debugString.AppendInt(aSerialized.mOriginAttributes.mUserContextId);
       debugString.Append(",");
       debugString.AppendInt(tabContext.OriginAttributesRef().mUserContextId);
       debugString.Append(")");
       continue;
     }
     aAttrs = DocShellOriginAttributes();
-    aAttrs.mAppId = nsIScriptSecurityManager::NO_APP_ID;
+    aAttrs.mAppId = appId;
     aAttrs.mInIsolatedMozBrowser = inBrowserElement;
     aAttrs.mUserContextId = aSerialized.mOriginAttributes.mUserContextId;
     aAttrs.mPrivateBrowsingId = aSerialized.mOriginAttributes.mPrivateBrowsingId;
     aAttrs.mFirstPartyDomain = aSerialized.mOriginAttributes.mFirstPartyDomain;
 
     return nullptr;
   }
 
new file mode 100644
--- /dev/null
+++ b/netwerk/test/unit_ipc/child_app_offline_notifications.js
@@ -0,0 +1,43 @@
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function is_app_offline(appId) {
+  let ioservice = Cc['@mozilla.org/network/io-service;1'].
+    getService(Ci.nsIIOService);
+  return ioservice.isAppOffline(appId);
+}
+
+var events_observed_no = 0;
+
+// Holds the last observed app-offline event
+var info = null;
+function observer(aSubject, aTopic, aData) {
+  events_observed_no++;
+  info = aSubject.QueryInterface(Ci.nsIAppOfflineInfo);
+  dump("ChildObserver - subject: {" + aSubject.appId + ", " + aSubject.mode + "} ");
+}
+
+// Add observer for the app-offline notification
+function run_test() {
+  Services.obs.addObserver(observer, "network:app-offline-status-changed", false);
+}
+
+// Chech that the app has the proper offline status
+function check_status(appId, status)
+{
+  do_check_eq(is_app_offline(appId), status == Ci.nsIAppOfflineInfo.OFFLINE);
+}
+
+// Check that the app has the proper offline status
+// and that the correct notification has been received
+function check_notification_and_status(appId, status) {
+  do_check_eq(info.appId, appId);
+  do_check_eq(info.mode, status);
+  do_check_eq(is_app_offline(appId), status == Ci.nsIAppOfflineInfo.OFFLINE);
+}
+
+// Remove the observer from the child process
+function finished() {
+  Services.obs.removeObserver(observer, "network:app-offline-status-changed");
+  do_check_eq(events_observed_no, 2);
+}
--- a/testing/mochitest/manifest.webapp
+++ b/testing/mochitest/manifest.webapp
@@ -19,16 +19,17 @@
     "settings":{ "access": "readwrite" },
     "storage":{},
     "camera":{},
     "geolocation":{},
     "wifi-manage":{},
     "desktop-notification":{},
     "idle":{},
     "network-events":{},
+    "embed-apps":{},
     "audio-channel-content":{},
     "audio-channel-alarm":{},
     "before-after-keyboard-event":{}
   },
   "locales": {
     "en-US": {
       "name": "Mochitest",
       "description": "Mochitests"
--- a/uriloader/prefetch/OfflineCacheUpdateParent.cpp
+++ b/uriloader/prefetch/OfflineCacheUpdateParent.cpp
@@ -259,16 +259,23 @@ OfflineCacheUpdateParent::SetRemoteTabs(
 NS_IMETHODIMP
 OfflineCacheUpdateParent::GetIsInIsolatedMozBrowserElement(bool *aIsInIsolatedMozBrowserElement)
 {
     NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED);
     return mLoadingPrincipal->GetIsInIsolatedMozBrowserElement(aIsInIsolatedMozBrowserElement);
 }
 
 NS_IMETHODIMP
+OfflineCacheUpdateParent::GetAppId(uint32_t *aAppId)
+{
+    NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED);
+    return mLoadingPrincipal->GetAppId(aAppId);
+}
+
+NS_IMETHODIMP
 OfflineCacheUpdateParent::GetOriginAttributes(JS::MutableHandleValue aAttrs)
 {
     NS_ENSURE_TRUE(mLoadingPrincipal, NS_ERROR_UNEXPECTED);
 
     JSContext* cx = nsContentUtils::GetCurrentJSContext();
     MOZ_ASSERT(cx);
 
     nsresult rv = mLoadingPrincipal->GetOriginAttributes(cx, aAttrs);
--- a/widget/gonk/GonkPermission.cpp
+++ b/widget/gonk/GonkPermission.cpp
@@ -23,26 +23,103 @@
 #ifndef HAVE_ANDROID_OS
 #define HAVE_ANDROID_OS 1
 #endif
 #include <private/android_filesystem_config.h>
 
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/SyncRunnable.h"
+#include "nsIAppsService.h"
+#include "mozIApplication.h"
 #include "nsThreadUtils.h"
 
 #undef LOG
 #include <android/log.h>
 #undef ALOGE
 #define ALOGE(args...)  __android_log_print(ANDROID_LOG_ERROR, "gonkperm" , ## args)
 
 using namespace android;
 using namespace mozilla;
 
+// Checking permissions needs to happen on the main thread, but the
+// binder callback is called on a special binder thread, so we use
+// this runnable for that.
+class GonkPermissionChecker : public Runnable {
+  int32_t mPid;
+  bool mCanUseCamera;
+
+  explicit GonkPermissionChecker(int32_t pid)
+    : mPid(pid)
+    , mCanUseCamera(false)
+  {
+  }
+
+public:
+  static already_AddRefed<GonkPermissionChecker> Inspect(int32_t pid)
+  {
+    RefPtr<GonkPermissionChecker> that = new GonkPermissionChecker(pid);
+    nsCOMPtr<nsIThread> mainThread = do_GetMainThread();
+    MOZ_ASSERT(mainThread);
+    SyncRunnable::DispatchToThread(mainThread, that);
+    return that.forget();
+  }
+
+  bool CanUseCamera()
+  {
+    return mCanUseCamera;
+  }
+
+  NS_IMETHOD Run();
+};
+
+NS_IMETHODIMP
+GonkPermissionChecker::Run()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Find our ContentParent.
+  dom::ContentParent *contentParent = nullptr;
+  {
+    nsTArray<dom::ContentParent*> parents;
+    dom::ContentParent::GetAll(parents);
+    for (uint32_t i = 0; i < parents.Length(); ++i) {
+      if (parents[i]->Pid() == mPid) {
+	contentParent = parents[i];
+	break;
+      }
+    }
+  }
+  if (!contentParent) {
+    ALOGE("pid=%d denied: can't find ContentParent", mPid);
+    return NS_OK;
+  }
+
+  // Now iterate its apps...
+  const ManagedContainer<dom::PBrowserParent>& browsers =
+    contentParent->ManagedPBrowserParent();
+  for (auto iter = browsers.ConstIter(); !iter.Done(); iter.Next()) {
+    dom::TabParent *tabParent =
+      static_cast<dom::TabParent*>(iter.Get()->GetKey());
+    nsCOMPtr<mozIApplication> mozApp = tabParent->GetOwnOrContainingApp();
+    if (!mozApp) {
+      continue;
+    }
+
+    // ...and check if any of them has camera access.
+    bool appCanUseCamera;
+    nsresult rv = mozApp->HasPermission("camera", &appCanUseCamera);
+    if (NS_SUCCEEDED(rv) && appCanUseCamera) {
+      mCanUseCamera = true;
+      return NS_OK;
+    }
+  }
+  return NS_OK;
+}
+
 bool
 GonkPermissionService::checkPermission(const String16& permission, int32_t pid,
                                      int32_t uid)
 {
   // root can do anything.
   if (0 == uid) {
     return true;
   }
@@ -71,19 +148,29 @@ GonkPermissionService::checkPermission(c
   }
 
   // Users granted the permission through a prompt dialog.
   // Before permission managment of gUM is done, app cannot remember the
   // permission.
   PermissionGrant permGrant(perm8.string(), pid);
   if (nsTArray<PermissionGrant>::NoIndex != mGrantArray.IndexOf(permGrant)) {
     mGrantArray.RemoveElement(permGrant);
+    return true;
   }
 
-  return true;
+  // Camera/audio record permissions are allowed for apps with the
+  // "camera" permission.
+  RefPtr<GonkPermissionChecker> checker =
+    GonkPermissionChecker::Inspect(pid);
+  bool canUseCamera = checker->CanUseCamera();
+  if (!canUseCamera) {
+    ALOGE("%s for pid=%d,uid=%d denied: not granted by user or app manifest",
+      String8(permission).string(), pid, uid);
+  }
+  return canUseCamera;
 }
 
 static GonkPermissionService* gGonkPermissionService = NULL;
 
 /* static */
 void
 GonkPermissionService::instantiate()
 {
--- a/xpfe/appshell/nsContentTreeOwner.cpp
+++ b/xpfe/appshell/nsContentTreeOwner.cpp
@@ -813,17 +813,17 @@ nsContentTreeOwner::ProvideWindow(mozIDO
                                static_cast<nsIDocShellTreeOwner*>(this)),
                "Parent from wrong docshell tree?");
 #endif
 
   // If aParent is inside an <iframe mozbrowser> and this isn't a request to
   // open a modal-type window, we're going to create a new <iframe mozbrowser>
   // and return its window here.
   nsCOMPtr<nsIDocShell> docshell = do_GetInterface(aParent);
-  if (docshell && docshell->GetIsInMozBrowser() &&
+  if (docshell && docshell->GetIsInMozBrowserOrApp() &&
       !(aChromeFlags & (nsIWebBrowserChrome::CHROME_MODAL |
                         nsIWebBrowserChrome::CHROME_OPENAS_DIALOG |
                         nsIWebBrowserChrome::CHROME_OPENAS_CHROME))) {
 
     BrowserElementParent::OpenWindowResult opened =
       BrowserElementParent::OpenWindowInProcess(parent, aURI, aName,
                                                 aFeatures, aForceNoOpener, aReturn);