Bug 1192727 - Improve the way that Presentation receiver gets the ID of the incoming session. r=smaug
authorSean Lin <selin@mozilla.com>
Mon, 31 Aug 2015 13:24:35 +0800
changeset 260060 24b78ae6d1a00761e895ff0662d304126b803b38
parent 260059 f3db8054d4323dbc8b5b8a324e5d5267185a1b9e
child 260061 241885d435f96d47a44b35625283ec3ec1d6f569
push id29299
push userryanvm@gmail.com
push dateMon, 31 Aug 2015 19:14:19 +0000
treeherdermozilla-central@a7183d2b82ed [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug
bugs1192727
milestone43.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1192727 - Improve the way that Presentation receiver gets the ID of the incoming session. r=smaug
dom/ipc/ContentChild.cpp
dom/ipc/ContentChild.h
dom/ipc/PContent.ipdl
dom/presentation/Presentation.cpp
dom/presentation/PresentationCallbacks.cpp
dom/presentation/PresentationService.cpp
dom/presentation/PresentationService.h
dom/presentation/PresentationSessionInfo.cpp
dom/presentation/PresentationSessionInfo.h
dom/presentation/interfaces/nsIPresentationService.idl
dom/presentation/ipc/PPresentation.ipdl
dom/presentation/ipc/PresentationIPCService.cpp
dom/presentation/ipc/PresentationIPCService.h
dom/presentation/ipc/PresentationParent.cpp
dom/presentation/ipc/PresentationParent.h
dom/presentation/tests/mochitest/file_presentation_non_receiver_oop.html
dom/presentation/tests/mochitest/mochitest.ini
dom/presentation/tests/mochitest/test_presentation_receiver.html
dom/presentation/tests/mochitest/test_presentation_receiver_oop.html
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -1404,16 +1404,28 @@ ContentChild::RecvNotifyPresentationRece
         do_GetService(PRESENTATION_SERVICE_CONTRACTID);
     NS_WARN_IF(!service);
 
     NS_WARN_IF(NS_FAILED(static_cast<PresentationIPCService*>(service.get())->MonitorResponderLoading(aSessionId, docShell)));
 
     return true;
 }
 
+bool
+ContentChild::RecvNotifyPresentationReceiverCleanUp(const nsString& aSessionId)
+{
+  nsCOMPtr<nsIPresentationService> service =
+      do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  NS_WARN_IF(!service);
+
+  NS_WARN_IF(NS_FAILED(service->UntrackSessionInfo(aSessionId)));
+
+  return true;
+}
+
 PCrashReporterChild*
 ContentChild::AllocPCrashReporterChild(const mozilla::dom::NativeThreadId& id,
                                        const uint32_t& processType)
 {
 #ifdef MOZ_CRASHREPORTER
     return new CrashReporterChild();
 #else
     return nullptr;
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -271,16 +271,17 @@ public:
 
     virtual PFMRadioChild* AllocPFMRadioChild() override;
     virtual bool DeallocPFMRadioChild(PFMRadioChild* aActor) override;
 
     virtual PPresentationChild* AllocPPresentationChild() override;
     virtual bool DeallocPPresentationChild(PPresentationChild* aActor) override;
     virtual bool RecvNotifyPresentationReceiverLaunched(PBrowserChild* aIframe,
                                                         const nsString& aSessionId) override;
+    virtual bool RecvNotifyPresentationReceiverCleanUp(const nsString& aSessionId) override;
 
     virtual PSpeechSynthesisChild* AllocPSpeechSynthesisChild() override;
     virtual bool DeallocPSpeechSynthesisChild(PSpeechSynthesisChild* aActor) override;
 
     virtual bool RecvRegisterChrome(InfallibleTArray<ChromePackage>&& packages,
                                     InfallibleTArray<SubstitutionMapping>&& resources,
                                     InfallibleTArray<OverrideMapping>&& overrides,
                                     const nsCString& locale,
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -654,16 +654,22 @@ child:
     async TestGraphicsDeviceReset(uint32_t aReason);
 
     /**
      * Notify the child that presentation receiver has been launched with the
      * correspondent iframe.
      */
     async NotifyPresentationReceiverLaunched(PBrowser aIframe, nsString aSessionId);
 
+    /**
+     * Notify the child that the info about a presentation receiver needs to be
+     * cleaned up.
+     */
+    async NotifyPresentationReceiverCleanUp(nsString aSessionId);
+
 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 |isForApp &&
--- a/dom/presentation/Presentation.cpp
+++ b/dom/presentation/Presentation.cpp
@@ -76,17 +76,17 @@ Presentation::Init()
     return false;
   }
   deviceManager->GetDeviceAvailable(&mAvailable);
 
   // Check if a session instance is required now. The receiver requires a
   // session instance is ready at beginning because the web content may access
   // it right away; whereas the sender doesn't until |startSession| succeeds.
   nsAutoString sessionId;
-  rv = service->GetExistentSessionIdAtLaunch(sessionId);
+  rv = service->GetExistentSessionIdAtLaunch(GetOwner()->WindowID(), sessionId);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return false;
   }
   if (!sessionId.IsEmpty()) {
     mSession = PresentationSession::Create(GetOwner(), sessionId,
                                            PresentationSessionState::Disconnected);
     if (NS_WARN_IF(!mSession)) {
       return false;
--- a/dom/presentation/PresentationCallbacks.cpp
+++ b/dom/presentation/PresentationCallbacks.cpp
@@ -111,23 +111,29 @@ PresentationResponderLoadingCallback::In
 
   // Start to listen to document state change event |STATE_TRANSFERRING|.
   return mProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
 }
 
 nsresult
 PresentationResponderLoadingCallback::NotifyReceiverReady()
 {
+  nsCOMPtr<nsPIDOMWindow> window = do_GetInterface(mProgress);
+  if (NS_WARN_IF(!window || !window->GetCurrentInnerWindow())) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  uint64_t windowId = window->GetCurrentInnerWindow()->WindowID();
+
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  return service->NotifyReceiverReady(mSessionId);
+  return service->NotifyReceiverReady(mSessionId, windowId);
 }
 
 // nsIWebProgressListener
 NS_IMETHODIMP
 PresentationResponderLoadingCallback::OnStateChange(nsIWebProgress* aWebProgress,
                                                     nsIRequest* aRequest,
                                                     uint32_t aStateFlags,
                                                     nsresult aStatus)
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -216,16 +216,17 @@ PresentationService::Observe(nsISupports
 
 void
 PresentationService::HandleShutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mListeners.Clear();
   mSessionInfo.Clear();
+  mRespondingSessionIds.Clear();
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   if (obs) {
     obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID);
     obs->RemoveObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC);
     obs->RemoveObserver(this, PRESENTATION_SESSION_REQUEST_TOPIC);
   }
 }
@@ -300,42 +301,28 @@ PresentationService::HandleSessionReques
   }
 
   if (NS_WARN_IF(isApp && !IsAppInstalled(uri))) {
     ctrlChannel->Close(NS_ERROR_DOM_NOT_FOUND_ERR);
     return NS_OK;
   }
 #endif
 
-  // Make sure the service is not handling another session request.
-  if (NS_WARN_IF(!mRespondingSessionId.IsEmpty())) {
-    ctrlChannel->Close(NS_ERROR_DOM_INUSE_ATTRIBUTE_ERR);
-    return rv;
-  }
-
-  // Set |mRespondingSessionId| to indicate the service is handling a session
-  // request. Then a session instance will be prepared while instantiating
-  // |navigator.presentation| at receiver side. This variable will be reset when
-  // registering the session listener.
-  mRespondingSessionId = sessionId;
-
   // Create or reuse session info.
   nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(sessionId);
   if (NS_WARN_IF(info)) {
     // TODO Update here after session resumption becomes supported.
     ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
-    mRespondingSessionId.Truncate();
     return NS_ERROR_DOM_ABORT_ERR;
   }
 
   info = new PresentationResponderInfo(url, sessionId, device);
   rv = info->Init(ctrlChannel);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR);
-    mRespondingSessionId.Truncate();
     return rv;
   }
 
   mSessionInfo.Put(sessionId, info);
 
   // Notify the receiver to launch.
   nsCOMPtr<nsIPresentationRequestUIGlue> glue =
     do_CreateInstance(PRESENTATION_REQUEST_UI_GLUE_CONTRACTID);
@@ -478,20 +465,16 @@ PresentationService::UnregisterListener(
 
 NS_IMETHODIMP
 PresentationService::RegisterSessionListener(const nsAString& aSessionId,
                                              nsIPresentationSessionListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aListener);
 
-  if (mRespondingSessionId.Equals(aSessionId)) {
-    mRespondingSessionId.Truncate();
-  }
-
   nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
   if (NS_WARN_IF(!info)) {
     // Notify the listener of TERMINATED since no correspondent session info is
     // available possibly due to establishment failure. This would be useful at
     // the receiver side, since a presentation session is created at beginning
     // and here is the place to realize the underlying establishment fails.
     nsresult rv = aListener->NotifyStateChange(aSessionId,
                                                nsIPresentationSessionListener::STATE_TERMINATED);
@@ -507,40 +490,83 @@ PresentationService::RegisterSessionList
 NS_IMETHODIMP
 PresentationService::UnregisterSessionListener(const nsAString& aSessionId)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
   if (info) {
     NS_WARN_IF(NS_FAILED(info->Close(NS_OK)));
-    RemoveSessionInfo(aSessionId);
+    UntrackSessionInfo(aSessionId);
     return info->SetListener(nullptr);
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationService::GetExistentSessionIdAtLaunch(nsAString& aSessionId)
+PresentationService::GetExistentSessionIdAtLaunch(uint64_t aWindowId,
+                                                  nsAString& aSessionId)
 {
-  aSessionId = mRespondingSessionId;
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsString* sessionId = mRespondingSessionIds.Get(aWindowId);
+  if (sessionId) {
+    aSessionId.Assign(*sessionId);
+  } else {
+    aSessionId.Truncate();
+  }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationService::NotifyReceiverReady(const nsAString& aSessionId)
+PresentationService::NotifyReceiverReady(const nsAString& aSessionId,
+                                         uint64_t aWindowId)
 {
   nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
+  // Only track the responding info when an actual window ID, which would never
+  // be 0, is provided (for an in-process receiver page).
+  if (aWindowId != 0) {
+    mRespondingSessionIds.Put(aWindowId, new nsAutoString(aSessionId));
+    mRespondingWindowIds.Put(aSessionId, aWindowId);
+  }
+
   return static_cast<PresentationResponderInfo*>(info.get())->NotifyResponderReady();
 }
 
+NS_IMETHODIMP
+PresentationService::UntrackSessionInfo(const nsAString& aSessionId)
+{
+  // Remove the session info.
+  mSessionInfo.Remove(aSessionId);
+
+  // Remove the in-process responding info if there's still any.
+  uint64_t windowId = 0;
+  if(mRespondingWindowIds.Get(aSessionId, &windowId)) {
+    mRespondingWindowIds.Remove(aSessionId);
+    mRespondingSessionIds.Remove(windowId);
+  }
+
+  return NS_OK;
+}
+
+bool
+PresentationService::IsSessionAccessible(const nsAString& aSessionId,
+                                         base::ProcessId aProcessId)
+{
+  nsRefPtr<PresentationSessionInfo> info = GetSessionInfo(aSessionId);
+  if (NS_WARN_IF(!info)) {
+    return false;
+  }
+  return info->IsAccessible(aProcessId);
+}
+
 already_AddRefed<nsIPresentationService>
 NS_CreatePresentationService()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   nsCOMPtr<nsIPresentationService> service;
   if (XRE_GetProcessType() == GeckoProcessType_Content) {
     service = new mozilla::dom::PresentationIPCService();
--- a/dom/presentation/PresentationService.h
+++ b/dom/presentation/PresentationService.h
@@ -33,36 +33,36 @@ public:
   already_AddRefed<PresentationSessionInfo>
   GetSessionInfo(const nsAString& aSessionId)
   {
     nsRefPtr<PresentationSessionInfo> info;
     return mSessionInfo.Get(aSessionId, getter_AddRefs(info)) ?
            info.forget() : nullptr;
   }
 
-  void
-  RemoveSessionInfo(const nsAString& aSessionId)
-  {
-    if (mRespondingSessionId.Equals(aSessionId)) {
-      mRespondingSessionId.Truncate();
-    }
-
-    mSessionInfo.Remove(aSessionId);
-  }
+  bool IsSessionAccessible(const nsAString& aSessionId,
+                           base::ProcessId aProcessId);
 
 private:
   ~PresentationService();
   void HandleShutdown();
   nsresult HandleDeviceChange();
   nsresult HandleSessionRequest(nsIPresentationSessionRequest* aRequest);
   void NotifyAvailableChange(bool aIsAvailable);
   bool IsAppInstalled(nsIURI* aUri);
 
   bool mIsAvailable;
-  nsString mRespondingSessionId;
   nsRefPtrHashtable<nsStringHashKey, PresentationSessionInfo> mSessionInfo;
   nsTObserverArray<nsCOMPtr<nsIPresentationListener>> mListeners;
+
+  // Store the mapping between the window ID of the in-process page and the ID
+  // of the responding session. It's used for an in-process receiver page to
+  // retrieve the correspondent session ID. Besides, also keep the mapping
+  // between the responding session ID and the window ID to help look up the
+  // window ID.
+  nsClassHashtable<nsUint64HashKey, nsString> mRespondingSessionIds;
+  nsDataHashtable<nsStringHashKey, uint64_t> mRespondingWindowIds;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationService_h
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -187,20 +187,27 @@ PresentationSessionInfo::Send(nsIInputSt
   return mTransport->Send(aData);
 }
 
 nsresult
 PresentationSessionInfo::Close(nsresult aReason)
 {
   // The session is disconnected and it's a normal close. Simply change the
   // state to TERMINATED.
-  if (!IsSessionReady() && NS_SUCCEEDED(aReason) && mListener) {
-    nsresult rv = mListener->NotifyStateChange(mSessionId,
-                                               nsIPresentationSessionListener::STATE_TERMINATED);
-    NS_WARN_IF(NS_FAILED(rv));
+  if (!IsSessionReady() && NS_SUCCEEDED(aReason)) {
+    if (mListener) {
+      // Notify the listener and the service will untrack the session info after
+      // the listener calls |UnregisterSessionListener|.
+      nsresult rv = mListener->NotifyStateChange(mSessionId,
+                                                 nsIPresentationSessionListener::STATE_TERMINATED);
+      NS_WARN_IF(NS_FAILED(rv));
+    } else {
+      // Directly untrack the session info from the service.
+      NS_WARN_IF(NS_FAILED(UntrackFromService()));
+    }
   }
 
   Shutdown(aReason);
   return NS_OK;
 }
 
 nsresult
 PresentationSessionInfo::ReplySuccess()
@@ -226,26 +233,39 @@ PresentationSessionInfo::ReplyError(nsre
   Shutdown(aError);
 
   if (mCallback) {
     NS_WARN_IF(NS_FAILED(mCallback->NotifyError(aError)));
     SetCallback(nullptr);
   }
 
   // Remove itself since it never succeeds.
+  return UntrackFromService();
+}
+
+/* virtual */ nsresult
+PresentationSessionInfo::UntrackFromService()
+{
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
-  static_cast<PresentationService*>(service.get())->RemoveSessionInfo(mSessionId);
+  static_cast<PresentationService*>(service.get())->UntrackSessionInfo(mSessionId);
 
   return NS_OK;
 }
 
+/* virtual */ bool
+PresentationSessionInfo::IsAccessible(base::ProcessId aProcessId)
+{
+  // No restriction by default.
+  return true;
+}
+
 // nsIPresentationSessionTransportCallback
 NS_IMETHODIMP
 PresentationSessionInfo::NotifyTransportReady()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   mIsTransportReady = true;
 
@@ -275,22 +295,27 @@ PresentationSessionInfo::NotifyTransport
     return ReplyError(aReason);
   }
 
   // Unset |mIsTransportReady| here so it won't affect |IsSessionReady()| above.
   mIsTransportReady = false;
 
   Shutdown(aReason);
 
+  uint16_t state = (NS_WARN_IF(NS_FAILED(aReason))) ?
+                   nsIPresentationSessionListener::STATE_DISCONNECTED :
+                   nsIPresentationSessionListener::STATE_TERMINATED;
   if (mListener) {
     // It happens after the session is ready. Notify session state change.
-    uint16_t state = (NS_WARN_IF(NS_FAILED(aReason))) ?
-                     nsIPresentationSessionListener::STATE_DISCONNECTED :
-                     nsIPresentationSessionListener::STATE_TERMINATED;
+    // If the new state is TERMINATED. the service will untrack the session info
+    // after the listener calls |UnregisterSessionListener|.
     return mListener->NotifyStateChange(mSessionId, state);
+  } else if (state == nsIPresentationSessionListener::STATE_TERMINATED) {
+    // Directly untrack the session info from the service.
+    return UntrackFromService();
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationSessionInfo::NotifyData(const nsACString& aData)
 {
@@ -635,16 +660,44 @@ PresentationResponderInfo::InitTransport
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   return NS_OK;
  }
 
 nsresult
+PresentationResponderInfo::UntrackFromService()
+{
+  // Remove the OOP responding info (if it has never been used).
+  if (mContentParent) {
+    NS_WARN_IF(!static_cast<ContentParent*>(mContentParent.get())->SendNotifyPresentationReceiverCleanUp(mSessionId));
+  }
+
+  // Remove the session info (and the in-process responding info if there's any).
+  nsCOMPtr<nsIPresentationService> service =
+    do_GetService(PRESENTATION_SERVICE_CONTRACTID);
+  if (NS_WARN_IF(!service)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
+  static_cast<PresentationService*>(service.get())->UntrackSessionInfo(mSessionId);
+
+  return NS_OK;
+}
+
+bool
+PresentationResponderInfo::IsAccessible(base::ProcessId aProcessId)
+{
+  // Only the specific content process should access the responder info.
+  return (mContentParent) ?
+          aProcessId == static_cast<ContentParent*>(mContentParent.get())->OtherPid() :
+          false;
+}
+
+nsresult
 PresentationResponderInfo::NotifyResponderReady()
 {
   if (mTimer) {
     mTimer->Cancel();
     mTimer = nullptr;
   }
 
   mIsResponderReady = true;
@@ -769,27 +822,30 @@ PresentationResponderInfo::ResolvedCallb
   if (NS_WARN_IF(NS_FAILED(rv))) {
     ReplyError(rv);
     return;
   }
 
   nsRefPtr<TabParent> tabParent = TabParent::GetFrom(frameLoader);
   if (tabParent) {
     // OOP frame
-    nsCOMPtr<nsIContentParent> cp = tabParent->Manager();
-    NS_WARN_IF(!static_cast<ContentParent*>(cp.get())->SendNotifyPresentationReceiverLaunched(tabParent, mSessionId));
+    // Notify the content process that a receiver page has launched, so it can
+    // start monitoring the loading progress.
+    mContentParent = tabParent->Manager();
+    NS_WARN_IF(!static_cast<ContentParent*>(mContentParent.get())->SendNotifyPresentationReceiverLaunched(tabParent, mSessionId));
   } else {
     // In-process frame
     nsCOMPtr<nsIDocShell> docShell;
     rv = frameLoader->GetDocShell(getter_AddRefs(docShell));
     if (NS_WARN_IF(NS_FAILED(rv))) {
       ReplyError(rv);
       return;
     }
 
+    // Keep an eye on the loading progress of the receiver page.
     mLoadingCallback = new PresentationResponderLoadingCallback(mSessionId);
     rv = mLoadingCallback->Init(docShell);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       ReplyError(rv);
       return;
     }
   }
 }
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -2,16 +2,17 @@
 /* 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_PresentationSessionInfo_h
 #define mozilla_dom_PresentationSessionInfo_h
 
+#include "base/process.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/PromiseNativeHandler.h"
 #include "mozilla/nsRefPtr.h"
 #include "nsCOMPtr.h"
 #include "nsIPresentationControlChannel.h"
 #include "nsIPresentationDevice.h"
 #include "nsIPresentationListener.h"
 #include "nsIPresentationService.h"
@@ -87,31 +88,35 @@ public:
   }
 
   nsresult Send(nsIInputStream* aData);
 
   nsresult Close(nsresult aReason);
 
   nsresult ReplyError(nsresult aReason);
 
+  virtual bool IsAccessible(base::ProcessId aProcessId);
+
 protected:
   virtual ~PresentationSessionInfo()
   {
     Shutdown(NS_OK);
   }
 
   virtual void Shutdown(nsresult aReason);
 
   nsresult ReplySuccess();
 
   bool IsSessionReady()
   {
     return mIsResponderReady && mIsTransportReady;
   }
 
+  virtual nsresult UntrackFromService();
+
   nsString mUrl;
   nsString mSessionId;
   bool mIsResponderReady;
   bool mIsTransportReady;
   nsCOMPtr<nsIPresentationServiceCallback> mCallback;
   nsCOMPtr<nsIPresentationSessionListener> mListener;
   nsCOMPtr<nsIPresentationDevice> mDevice;
   nsCOMPtr<nsIPresentationSessionTransport> mTransport;
@@ -179,28 +184,36 @@ public:
   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   void SetPromise(Promise* aPromise)
   {
     mPromise = aPromise;
     mPromise->AppendNativeHandler(this);
   }
 
+  bool IsAccessible(base::ProcessId aProcessId) override;
+
 private:
   ~PresentationResponderInfo()
   {
     Shutdown(NS_OK);
   }
 
   void Shutdown(nsresult aReason) override;
 
   nsresult InitTransportAndSendAnswer();
 
+  nsresult UntrackFromService() override;
+
   nsRefPtr<PresentationResponderLoadingCallback> mLoadingCallback;
   nsCOMPtr<nsITimer> mTimer;
   nsCOMPtr<nsIPresentationChannelDescription> mRequesterDescription;
   nsRefPtr<Promise> mPromise;
+
+  // The content parent communicating with the content process which the OOP
+  // receiver page belongs to.
+  nsCOMPtr<nsIContentParent> mContentParent;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationSessionInfo_h
--- a/dom/presentation/interfaces/nsIPresentationService.idl
+++ b/dom/presentation/interfaces/nsIPresentationService.idl
@@ -27,17 +27,17 @@ interface nsIPresentationServiceCallback
   /*
    * Called when the operation fails.
    *
    * @param error: error message.
    */
   void notifyError(in nsresult error);
 };
 
-[scriptable, uuid(5801efd9-9dba-4af7-8be9-8fc97c2d54a6)]
+[scriptable, uuid(731af98a-2760-4e7f-bb59-c7cf6665f26f)]
 interface nsIPresentationService : nsISupports
 {
   /*
    * Start a new presentation session and display a prompt box which asks users
    * to select a device.
    *
    * @param url: The url of presenting page.
    * @param sessionId: An ID to identify presentation session.
@@ -94,19 +94,34 @@ interface nsIPresentationService : nsISu
    * Unregister a session listener. Must be called from the main thread.
    *
    * @param sessionId: An ID to identify presentation session.
    */
   void unregisterSessionListener(in DOMString sessionId);
 
   /*
    * Check if the presentation instance has an existent session ID at launch.
-   * An empty string is returned at sender side; non-empty at receiver side.
+   * An empty string is always returned at sender side. Whereas at receiver side
+   * the associated session ID is returned if the window ID and URI are matched;
+   * otherwise an empty string is returned.
+   *
+   * @param windowId: The inner window ID used to look up the session ID.
    */
-  DOMString getExistentSessionIdAtLaunch();
+  DOMString getExistentSessionIdAtLaunch(in uint64_t windowId);
 
   /*
    * Notify the receiver page is ready for presentation use.
    *
    * @param sessionId: An ID to identify presentation session.
+   * @param windowId: The inner window ID associated with the presentation
+   *                  session. (0 implies no window ID since no actual window
+   *                  uses 0 as its ID.)
    */
-  void notifyReceiverReady(in DOMString sessionId);
+  void notifyReceiverReady(in DOMString sessionId,
+                           [optional] in uint64_t windowId);
+
+  /*
+   * Untrack the relevant info about the presentation session if there's any.
+   *
+   * @param sessionId: An ID to identify presentation session.
+   */
+  void untrackSessionInfo(in DOMString sessionId);
 };
--- a/dom/presentation/ipc/PPresentation.ipdl
+++ b/dom/presentation/ipc/PPresentation.ipdl
@@ -53,16 +53,13 @@ parent:
   RegisterHandler();
   UnregisterHandler();
 
   RegisterSessionHandler(nsString aSessionId);
   UnregisterSessionHandler(nsString aSessionId);
 
   PPresentationRequest(PresentationRequest aRequest);
 
-  sync GetExistentSessionIdAtLaunch()
-    returns (nsString aSessionId);
-
   NotifyReceiverReady(nsString aSessionId);
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -33,16 +33,18 @@ PresentationIPCService::PresentationIPCS
   NS_WARN_IF(!contentChild->SendPPresentationConstructor(sPresentationChild));
 }
 
 /* virtual */
 PresentationIPCService::~PresentationIPCService()
 {
   mListeners.Clear();
   mSessionListeners.Clear();
+  mRespondingSessionIds.Clear();
+  mRespondingWindowIds.Clear();
   sPresentationChild = nullptr;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::StartSession(const nsAString& aUrl,
                                      const nsAString& aSessionId,
                                      const nsAString& aOrigin,
                                      nsIPresentationServiceCallback* aCallback)
@@ -125,16 +127,18 @@ PresentationIPCService::RegisterSessionL
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::UnregisterSessionListener(const nsAString& aSessionId)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
+  UntrackSessionInfo(aSessionId);
+
   mSessionListeners.Remove(aSessionId);
   if (sPresentationChild) {
     NS_WARN_IF(!sPresentationChild->SendUnregisterSessionHandler(nsAutoString(aSessionId)));
   }
   return NS_OK;
 }
 
 nsresult
@@ -169,41 +173,70 @@ PresentationIPCService::NotifyAvailableC
     nsIPresentationListener* listener = iter.GetNext();
     NS_WARN_IF(NS_FAILED(listener->NotifyAvailableChange(aAvailable)));
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-PresentationIPCService::GetExistentSessionIdAtLaunch(nsAString& aSessionId)
+PresentationIPCService::GetExistentSessionIdAtLaunch(uint64_t aWindowId,
+                                                     nsAString& aSessionId)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsString* sessionId = mRespondingSessionIds.Get(aWindowId);
+  if (sessionId) {
+    aSessionId.Assign(*sessionId);
+  } else {
+    aSessionId.Truncate();
+  }
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+PresentationIPCService::NotifyReceiverReady(const nsAString& aSessionId,
+                                            uint64_t aWindowId)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  nsAutoString sessionId(aSessionId);
-  NS_WARN_IF(!sPresentationChild->SendGetExistentSessionIdAtLaunch(&sessionId));
-  aSessionId = sessionId;
+  // No actual window uses 0 as its ID.
+  if (NS_WARN_IF(aWindowId == 0)) {
+    return NS_ERROR_NOT_AVAILABLE;
+  }
 
+  // Track the responding info for an OOP receiver page.
+  mRespondingSessionIds.Put(aWindowId, new nsAutoString(aSessionId));
+  mRespondingWindowIds.Put(aSessionId, aWindowId);
+
+  mCallback = nullptr;
+  NS_WARN_IF(!sPresentationChild->SendNotifyReceiverReady(nsAutoString(aSessionId)));
   return NS_OK;
 }
 
-nsresult
-PresentationIPCService::NotifyReceiverReady(const nsAString& aSessionId)
+NS_IMETHODIMP
+PresentationIPCService::UntrackSessionInfo(const nsAString& aSessionId)
 {
-  MOZ_ASSERT(NS_IsMainThread());
+  // Remove the OOP responding info (if it has never been used).
+  uint64_t windowId = 0;
+  if(mRespondingWindowIds.Get(aSessionId, &windowId)) {
+    mRespondingWindowIds.Remove(aSessionId);
+    mRespondingSessionIds.Remove(windowId);
+  }
 
-  NS_WARN_IF(!sPresentationChild->SendNotifyReceiverReady(nsAutoString(aSessionId)));
   return NS_OK;
 }
 
 void
 PresentationIPCService::NotifyPresentationChildDestroyed()
 {
   sPresentationChild = nullptr;
 }
 
 nsresult
 PresentationIPCService::MonitorResponderLoading(const nsAString& aSessionId,
                                                 nsIDocShell* aDocShell)
 {
+  MOZ_ASSERT(NS_IsMainThread());
+
   mCallback = new PresentationResponderLoadingCallback(aSessionId);
   return mCallback->Init(aDocShell);
 }
--- a/dom/presentation/ipc/PresentationIPCService.h
+++ b/dom/presentation/ipc/PresentationIPCService.h
@@ -43,14 +43,22 @@ public:
 private:
   virtual ~PresentationIPCService();
   nsresult SendRequest(nsIPresentationServiceCallback* aCallback,
                        const PresentationRequest& aRequest);
 
   nsTObserverArray<nsCOMPtr<nsIPresentationListener> > mListeners;
   nsRefPtrHashtable<nsStringHashKey, nsIPresentationSessionListener> mSessionListeners;
   nsRefPtr<PresentationResponderLoadingCallback> mCallback;
+
+  // Store the mapping between the window ID of the OOP page (in this process)
+  // and the ID of the responding session. It's used for an OOP receiver page
+  // to retrieve the correspondent session ID. Besides, also keep the mapping
+  // between the responding session ID and the window ID to help look up the
+  // window ID.
+  nsClassHashtable<nsUint64HashKey, nsString> mRespondingSessionIds;
+  nsDataHashtable<nsStringHashKey, uint64_t> mRespondingWindowIds;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationIPCService_h
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -115,16 +115,24 @@ PresentationParent::RecvUnregisterHandle
   NS_WARN_IF(NS_FAILED(mService->UnregisterListener(this)));
   return true;
 }
 
 /* virtual */ bool
 PresentationParent::RecvRegisterSessionHandler(const nsString& aSessionId)
 {
   MOZ_ASSERT(mService);
+
+  // Validate the accessibility (primarily for receiver side) so that a
+  // compromised child process can't fake the ID.
+  if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
+                  IsSessionAccessible(aSessionId, OtherPid()))) {
+    return true;
+  }
+
   mSessionIds.AppendElement(aSessionId);
   NS_WARN_IF(NS_FAILED(mService->RegisterSessionListener(aSessionId, this)));
   return true;
 }
 
 /* virtual */ bool
 PresentationParent::RecvUnregisterSessionHandler(const nsString& aSessionId)
 {
@@ -143,46 +151,38 @@ PresentationParent::NotifyAvailableChang
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationParent::NotifyStateChange(const nsAString& aSessionId,
                                       uint16_t aState)
 {
   if (NS_WARN_IF(mActorDestroyed ||
-                 !SendNotifySessionStateChange(nsString(aSessionId), aState))) {
+                 !SendNotifySessionStateChange(nsAutoString(aSessionId), aState))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationParent::NotifyMessage(const nsAString& aSessionId,
                                   const nsACString& aData)
 {
   if (NS_WARN_IF(mActorDestroyed ||
-                 !SendNotifyMessage(nsString(aSessionId), nsCString(aData)))) {
+                 !SendNotifyMessage(nsAutoString(aSessionId), nsAutoCString(aData)))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 bool
-PresentationParent::RecvGetExistentSessionIdAtLaunch(nsString* aSessionId)
-{
-  MOZ_ASSERT(mService);
-  NS_WARN_IF(NS_FAILED(mService->GetExistentSessionIdAtLaunch(*aSessionId)));
-  return true;
-}
-
-bool
 PresentationParent::RecvNotifyReceiverReady(const nsString& aSessionId)
 {
   MOZ_ASSERT(mService);
-  NS_WARN_IF(NS_FAILED(mService->NotifyReceiverReady(aSessionId)));
+  NS_WARN_IF(NS_FAILED(mService->NotifyReceiverReady(aSessionId, 0)));
   return true;
 }
 
 /*
  * Implementation of PresentationRequestParent
  */
 
 NS_IMPL_ISUPPORTS(PresentationRequestParent, nsIPresentationServiceCallback)
@@ -213,16 +213,24 @@ PresentationRequestParent::DoRequest(con
   return mService->StartSession(aRequest.url(), aRequest.sessionId(),
                                 aRequest.origin(), this);
 }
 
 nsresult
 PresentationRequestParent::DoRequest(const SendSessionMessageRequest& aRequest)
 {
   MOZ_ASSERT(mService);
+
+  // Validate the accessibility (primarily for receiver side) so that a
+  // compromised child process can't fake the ID.
+  if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
+                  IsSessionAccessible(aRequest.sessionId(), OtherPid()))) {
+    return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
+  }
+
   nsTArray<mozilla::ipc::FileDescriptor> fds;
   nsCOMPtr<nsIInputStream> stream = DeserializeInputStream(aRequest.data(), fds);
   if(NS_WARN_IF(!stream)) {
     return NotifyError(NS_ERROR_NOT_AVAILABLE);
   }
 
   nsresult rv = mService->SendSessionMessage(aRequest.sessionId(), stream);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -230,16 +238,24 @@ PresentationRequestParent::DoRequest(con
   }
   return NotifySuccess();
 }
 
 nsresult
 PresentationRequestParent::DoRequest(const TerminateRequest& aRequest)
 {
   MOZ_ASSERT(mService);
+
+  // Validate the accessibility (primarily for receiver side) so that a
+  // compromised child process can't fake the ID.
+  if (NS_WARN_IF(!static_cast<PresentationService*>(mService.get())->
+                  IsSessionAccessible(aRequest.sessionId(), OtherPid()))) {
+    return NotifyError(NS_ERROR_DOM_SECURITY_ERR);
+  }
+
   nsresult rv = mService->Terminate(aRequest.sessionId());
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NotifyError(rv);
   }
   return NotifySuccess();
 }
 
 NS_IMETHODIMP
--- a/dom/presentation/ipc/PresentationParent.h
+++ b/dom/presentation/ipc/PresentationParent.h
@@ -45,18 +45,16 @@ public:
   virtual bool RecvRegisterHandler() override;
 
   virtual bool RecvUnregisterHandler() override;
 
   virtual bool RecvRegisterSessionHandler(const nsString& aSessionId) override;
 
   virtual bool RecvUnregisterSessionHandler(const nsString& aSessionId) override;
 
-  virtual bool RecvGetExistentSessionIdAtLaunch(nsString* aSessionId) override;
-
   virtual bool RecvNotifyReceiverReady(const nsString& aSessionId) override;
 
 private:
   virtual ~PresentationParent();
 
   bool mActorDestroyed;
   nsCOMPtr<nsIPresentationService> mService;
   nsTArray<nsString> mSessionIds;
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_non_receiver_oop.html
@@ -0,0 +1,42 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Test for B2G Presentation Session API on a non-receiver page at receiver side (OOP)</title>
+</head>
+<body>
+<div id="content"></div>
+<script type="application/javascript;version=1.7">
+
+"use strict";
+
+function is(a, b, msg) {
+  alert((a === b ? 'OK ' : 'KO ') + msg);
+}
+
+function ok(a, msg) {
+  alert((a ? 'OK ' : 'KO ') + msg);
+}
+
+function info(msg) {
+  alert('INFO ' + msg);
+}
+
+function finish() {
+  alert('DONE');
+}
+
+function testSessionAvailable() {
+  return new Promise(function(aResolve, aReject) {
+    ok(navigator.presentation, "navigator.presentation should be available in OOP pages.");
+    ok(!navigator.presentation.session, "Non-receiving OOP pages shouldn't get a predefined presentation session instance.");
+    aResolve();
+  });
+}
+
+testSessionAvailable().
+then(finish);
+
+</script>
+</body>
+</html>
--- a/dom/presentation/tests/mochitest/mochitest.ini
+++ b/dom/presentation/tests/mochitest/mochitest.ini
@@ -1,14 +1,15 @@
 [DEFAULT]
 support-files =
   PresentationDeviceInfoChromeScript.js
   PresentationSessionChromeScript.js
   file_presentation_receiver.html
   file_presentation_receiver_oop.html
+  file_presentation_non_receiver_oop.html
   file_presentation_receiver_start_session_error.html
 
 [test_presentation_device_info.html]
 [test_presentation_device_info_permission.html]
 [test_presentation_sender_disconnect.html]
 skip-if = toolkit == 'android' # Bug 1129785
 [test_presentation_sender_start_session_error.html]
 skip-if = toolkit == 'android' # Bug 1129785
--- a/dom/presentation/tests/mochitest/test_presentation_receiver.html
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver.html
@@ -77,16 +77,19 @@ function setup() {
 }
 
 function testIncomingSessionRequest() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
       gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
       info("Trying to launch receiver page.");
 
+      ok(navigator.presentation, "navigator.presentation should be available in in-process pages.");
+      ok(!navigator.presentation.session, "Non-receiving in-process pages shouldn't get a predefined presentation session instance.");
+
       aResolve();
     });
 
     gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
   });
 }
 
 function teardown() {
--- a/dom/presentation/tests/mochitest/test_presentation_receiver_oop.html
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver_oop.html
@@ -14,60 +14,103 @@
 <div id="content" style="display: none"></div>
 <pre id="test"></pre>
 <script type="application/javascript">
 
 'use strict';
 
 var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
 var receiverUrl = SimpleTest.getTestFileURL('file_presentation_receiver_oop.html');
+var nonReceiverUrl = SimpleTest.getTestFileURL('file_presentation_non_receiver_oop.html');
+
+var isReceiverFinished = false;
+var isNonReceiverFinished = false;
 
 var obs = SpecialPowers.Cc["@mozilla.org/observer-service;1"]
           .getService(SpecialPowers.Ci.nsIObserverService);
 
 function setup() {
   return new Promise(function(aResolve, aReject) {
     gScript.sendAsyncMessage('trigger-device-add');
 
+    // Create a receiver OOP iframe.
     SpecialPowers.addPermission('presentation', true, { url: receiverUrl,
                                                         appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
                                                         isInBrowserElement: true });
-
-    var iframe = document.createElement('iframe');
-    iframe.setAttribute('remote', 'true');
-    iframe.setAttribute('mozbrowser', 'true');
-    iframe.setAttribute('src', receiverUrl);
+    var receiverIframe = document.createElement('iframe');
+    receiverIframe.setAttribute('remote', 'true');
+    receiverIframe.setAttribute('mozbrowser', 'true');
+    receiverIframe.setAttribute('src', receiverUrl);
 
     // This event is triggered when the iframe calls "alert".
-    iframe.addEventListener('mozbrowsershowmodalprompt', function listener(aEvent) {
+    receiverIframe.addEventListener('mozbrowsershowmodalprompt', function receiverListener(aEvent) {
       var message = aEvent.detail.message;
       if (/^OK /.exec(message)) {
         ok(true, "Message from iframe: " + message);
       } else if (/^KO /.exec(message)) {
         ok(false, "Message from iframe: " + message);
       } else if (/^INFO /.exec(message)) {
         info("Message from iframe: " + message);
       } else if (/^COMMAND /.exec(message)) {
         var command = JSON.parse(message.replace(/^COMMAND /, ''));
         gScript.sendAsyncMessage(command.name, command.data);
       } else if (/^DONE$/.exec(message)) {
         ok(true, "Messaging from iframe complete.");
-        iframe.removeEventListener('mozbrowsershowmodalprompt', listener);
+        receiverIframe.removeEventListener('mozbrowsershowmodalprompt', receiverListener);
+
+        isReceiverFinished = true;
 
-        teardown();
+        if (isNonReceiverFinished) {
+          teardown();
+        }
       }
     }, false);
 
     var promise = new Promise(function(aResolve, aReject) {
-      document.body.appendChild(iframe);
+      document.body.appendChild(receiverIframe);
 
-      aResolve(iframe);
+      aResolve(receiverIframe);
     });
     obs.notifyObservers(promise, 'setup-request-promise', null);
 
+    // Create a non-receiver OOP iframe.
+    SpecialPowers.addPermission('presentation', true, { url: nonReceiverUrl,
+                                                        appId: SpecialPowers.Ci.nsIScriptSecurityManager.NO_APP_ID,
+                                                        isInBrowserElement: true });
+    var nonReceiverIframe = document.createElement('iframe');
+    nonReceiverIframe.setAttribute('remote', 'true');
+    nonReceiverIframe.setAttribute('mozbrowser', 'true');
+    nonReceiverIframe.setAttribute('src', nonReceiverUrl);
+
+    // This event is triggered when the iframe calls "alert".
+    nonReceiverIframe.addEventListener('mozbrowsershowmodalprompt', function nonReceiverListener(aEvent) {
+      var message = aEvent.detail.message;
+      if (/^OK /.exec(message)) {
+        ok(true, "Message from iframe: " + message);
+      } else if (/^KO /.exec(message)) {
+        ok(false, "Message from iframe: " + message);
+      } else if (/^INFO /.exec(message)) {
+        info("Message from iframe: " + message);
+      } else if (/^COMMAND /.exec(message)) {
+        var command = JSON.parse(message.replace(/^COMMAND /, ''));
+        gScript.sendAsyncMessage(command.name, command.data);
+      } else if (/^DONE$/.exec(message)) {
+        ok(true, "Messaging from iframe complete.");
+        nonReceiverIframe.removeEventListener('mozbrowsershowmodalprompt', nonReceiverListener);
+
+        isNonReceiverFinished = true;
+
+        if (isReceiverFinished) {
+          teardown();
+        }
+      }
+    }, false);
+
+    document.body.appendChild(nonReceiverIframe);
+
     gScript.addMessageListener('offer-received', function offerReceivedHandler() {
       gScript.removeMessageListener('offer-received', offerReceivedHandler);
       info("An offer is received.");
     });
 
     gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
       gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
       is(aReason, SpecialPowers.Cr.NS_OK, "The control channel is closed normally.");