Bug 1287717 - Part 2, close receiver page while loading fail. r=smaug.
authorShih-Chiang Chien <schien@mozilla.com>
Thu, 04 Aug 2016 09:46:14 +0800
changeset 308264 734a7d13d2855ff89d6ee3c0c3da88170a639dc9
parent 308263 025466d995ef6bfb19b19495816c9fce10296965
child 308265 6fc40ec6d00d4d6225d6b27e74c36b9f2bf19d9b
push idunknown
push userunknown
push dateunknown
reviewerssmaug
bugs1287717
milestone51.0a1
Bug 1287717 - Part 2, close receiver page while loading fail. r=smaug. MozReview-Commit-ID: Dogham2LmHG
dom/presentation/PresentationCallbacks.cpp
dom/presentation/PresentationCallbacks.h
dom/presentation/PresentationService.cpp
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/PresentationParent.cpp
dom/presentation/ipc/PresentationParent.h
dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test
dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test^headers^
dom/presentation/tests/mochitest/mochitest.ini
dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type.js
dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_inproc.html
dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_oop.html
--- a/dom/presentation/PresentationCallbacks.cpp
+++ b/dom/presentation/PresentationCallbacks.cpp
@@ -172,54 +172,56 @@ PresentationResponderLoadingCallback::In
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
   if ((busyFlags == nsIDocShell::BUSY_FLAGS_NONE) ||
       (busyFlags & nsIDocShell::BUSY_FLAGS_PAGE_LOADING)) {
     // The docshell has finished loading or is receiving data (|STATE_TRANSFERRING|
     // has already been fired), so the page is ready for presentation use.
-    return NotifyReceiverReady();
+    return NotifyReceiverReady(/* isLoading = */ true);
   }
 
   // Start to listen to document state change event |STATE_TRANSFERRING|.
   return mProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT);
 }
 
 nsresult
-PresentationResponderLoadingCallback::NotifyReceiverReady()
+PresentationResponderLoadingCallback::NotifyReceiverReady(bool aIsLoading)
 {
   nsCOMPtr<nsPIDOMWindowOuter> 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, windowId);
+  return service->NotifyReceiverReady(mSessionId, windowId, aIsLoading);
 }
 
 // nsIWebProgressListener
 NS_IMETHODIMP
 PresentationResponderLoadingCallback::OnStateChange(nsIWebProgress* aWebProgress,
                                                     nsIRequest* aRequest,
                                                     uint32_t aStateFlags,
                                                     nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING) {
+  if (aStateFlags & (nsIWebProgressListener::STATE_TRANSFERRING |
+                     nsIWebProgressListener::STATE_STOP)) {
     mProgress->RemoveProgressListener(this);
 
-    return NotifyReceiverReady();
+    bool isLoading = aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING;
+    return NotifyReceiverReady(isLoading);
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationResponderLoadingCallback::OnProgressChange(nsIWebProgress* aWebProgress,
                                                        nsIRequest* aRequest,
--- a/dom/presentation/PresentationCallbacks.h
+++ b/dom/presentation/PresentationCallbacks.h
@@ -71,17 +71,17 @@ public:
 
   explicit PresentationResponderLoadingCallback(const nsAString& aSessionId);
 
   nsresult Init(nsIDocShell* aDocShell);
 
 private:
   ~PresentationResponderLoadingCallback();
 
-  nsresult NotifyReceiverReady();
+  nsresult NotifyReceiverReady(bool aIsLoading);
 
   nsString mSessionId;
   nsCOMPtr<nsIWebProgress> mProgress;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -884,26 +884,31 @@ NS_IMETHODIMP
 PresentationService::GetExistentSessionIdAtLaunch(uint64_t aWindowId,
                                                   nsAString& aSessionId)
 {
   return GetExistentSessionIdAtLaunchInternal(aWindowId, aSessionId);
 }
 
 NS_IMETHODIMP
 PresentationService::NotifyReceiverReady(const nsAString& aSessionId,
-                                         uint64_t aWindowId)
+                                         uint64_t aWindowId,
+                                         bool aIsLoading)
 {
   RefPtr<PresentationSessionInfo> info =
     GetSessionInfo(aSessionId, nsIPresentationService::ROLE_RECEIVER);
   if (NS_WARN_IF(!info)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
   AddRespondingSessionId(aWindowId, aSessionId);
 
+  if (!aIsLoading) {
+    return static_cast<PresentationPresentingInfo*>(info.get())->NotifyResponderFailure();
+  }
+
   nsCOMPtr<nsIPresentationRespondingListener> listener;
   if (mRespondingListeners.Get(aWindowId, getter_AddRefs(listener))) {
     nsresult rv = listener->NotifySessionConnect(aWindowId, aSessionId);
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return rv;
     }
   }
 
--- a/dom/presentation/PresentationSessionInfo.cpp
+++ b/dom/presentation/PresentationSessionInfo.cpp
@@ -1210,16 +1210,27 @@ PresentationPresentingInfo::NotifyRespon
     if (NS_WARN_IF(NS_FAILED(rv))) {
       return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
     }
   }
 
   return NS_OK;
 }
 
+nsresult
+PresentationPresentingInfo::NotifyResponderFailure()
+{
+  if (mTimer) {
+    mTimer->Cancel();
+    mTimer = nullptr;
+  }
+
+  return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
+}
+
 // nsIPresentationControlChannelListener
 NS_IMETHODIMP
 PresentationPresentingInfo::OnOffer(nsIPresentationChannelDescription* aDescription)
 {
   if (NS_WARN_IF(mHasFlushPendingEvents)) {
     return ReplyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
--- a/dom/presentation/PresentationSessionInfo.h
+++ b/dom/presentation/PresentationSessionInfo.h
@@ -225,16 +225,17 @@ public:
   {
     MOZ_ASSERT(aDevice);
     SetDevice(aDevice);
   }
 
   nsresult Init(nsIPresentationControlChannel* aControlChannel) override;
 
   nsresult NotifyResponderReady();
+  nsresult NotifyResponderFailure();
 
   NS_IMETHODIMP OnSessionTransport(nsIPresentationSessionTransport* transport) override;
 
   void ResolvedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   void RejectedCallback(JSContext* aCx, JS::Handle<JS::Value> aValue) override;
 
   void SetPromise(Promise* aPromise)
--- a/dom/presentation/interfaces/nsIPresentationService.idl
+++ b/dom/presentation/interfaces/nsIPresentationService.idl
@@ -169,24 +169,24 @@ interface nsIPresentationService : nsISu
    *
    * @param windowId: The inner window ID used to look up the session ID.
    */
   DOMString getExistentSessionIdAtLaunch(in unsigned long long 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. Generally it's the case the window is
-   *                  located in different process from this service)
+   * @param sessionId An ID to identify presentation session.
+   * @param windowId  The inner window ID associated with the presentation
+   *                  session.
+   * @param isLoading true if receiver page is loading successfully.
    */
   void notifyReceiverReady(in DOMString sessionId,
-                           [optional] in unsigned long long windowId);
+                           in unsigned long long windowId,
+                           in boolean isLoading);
 
   /*
    * Notify the transport is closed
    *
    * @param sessionId: An ID to identify presentation session.
    * @param role: Identify the function called by controller or receiver.
    * @param reason: the error message. NS_OK indicates it is closed normally.
    */
--- a/dom/presentation/ipc/PPresentation.ipdl
+++ b/dom/presentation/ipc/PPresentation.ipdl
@@ -90,14 +90,14 @@ parent:
   async RegisterSessionHandler(nsString aSessionId, uint8_t aRole);
   async UnregisterSessionHandler(nsString aSessionId, uint8_t aRole);
 
   async RegisterRespondingHandler(uint64_t aWindowId);
   async UnregisterRespondingHandler(uint64_t aWindowId);
 
   async PPresentationRequest(PresentationIPCRequest aRequest);
 
-  async NotifyReceiverReady(nsString aSessionId, uint64_t aWindowId);
+  async NotifyReceiverReady(nsString aSessionId, uint64_t aWindowId, bool aIsLoading);
   async NotifyTransportClosed(nsString aSessionId, uint8_t aRole, nsresult aReason);
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -359,30 +359,32 @@ NS_IMETHODIMP
 PresentationIPCService::GetExistentSessionIdAtLaunch(uint64_t aWindowId,
                                                      nsAString& aSessionId)
 {
   return GetExistentSessionIdAtLaunchInternal(aWindowId, aSessionId);;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::NotifyReceiverReady(const nsAString& aSessionId,
-                                            uint64_t aWindowId)
+                                            uint64_t aWindowId,
+                                            bool aIsLoading)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   // 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.
   AddRespondingSessionId(aWindowId, aSessionId);
 
   NS_WARN_IF(!sPresentationChild->SendNotifyReceiverReady(nsString(aSessionId),
-                                                          aWindowId));
+                                                          aWindowId,
+                                                          aIsLoading));
 
   // Release mCallback after using aSessionId
   // because aSessionId is held by mCallback.
   mCallback = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
--- a/dom/presentation/ipc/PresentationParent.cpp
+++ b/dom/presentation/ipc/PresentationParent.cpp
@@ -275,22 +275,25 @@ PresentationParent::NotifySessionConnect
                  !SendNotifySessionConnect(aWindowId, nsString(aSessionId)))) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 bool
 PresentationParent::RecvNotifyReceiverReady(const nsString& aSessionId,
-                                            const uint64_t& aWindowId)
+                                            const uint64_t& aWindowId,
+                                            const bool& aIsLoading)
 {
   MOZ_ASSERT(mService);
 
   RegisterTransportBuilder(aSessionId, nsIPresentationService::ROLE_RECEIVER);
-  NS_WARN_IF(NS_FAILED(mService->NotifyReceiverReady(aSessionId, aWindowId)));
+  NS_WARN_IF(NS_FAILED(mService->NotifyReceiverReady(aSessionId,
+                                                     aWindowId,
+                                                     aIsLoading)));
   return true;
 }
 
 bool
 PresentationParent::RecvNotifyTransportClosed(const nsString& aSessionId,
                                               const uint8_t& aRole,
                                               const nsresult& aReason)
 {
--- a/dom/presentation/ipc/PresentationParent.h
+++ b/dom/presentation/ipc/PresentationParent.h
@@ -66,17 +66,18 @@ public:
   virtual bool RecvUnregisterSessionHandler(const nsString& aSessionId,
                                             const uint8_t& aRole) override;
 
   virtual bool RecvRegisterRespondingHandler(const uint64_t& aWindowId) override;
 
   virtual bool RecvUnregisterRespondingHandler(const uint64_t& aWindowId) override;
 
   virtual bool RecvNotifyReceiverReady(const nsString& aSessionId,
-                                       const uint64_t& aWindowId) override;
+                                       const uint64_t& aWindowId,
+                                       const bool& aIsLoading) override;
 
   virtual bool RecvNotifyTransportClosed(const nsString& aSessionId,
                                          const uint8_t& aRole,
                                          const nsresult& aReason) override;
 
 private:
   virtual ~PresentationParent();
 
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test
@@ -0,0 +1,1 @@
+
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/file_presentation_unknown_content_type.test^headers^
@@ -0,0 +1,1 @@
+Content-Type: application/unknown
--- a/dom/presentation/tests/mochitest/mochitest.ini
+++ b/dom/presentation/tests/mochitest/mochitest.ini
@@ -15,16 +15,19 @@ support-files =
   file_presentation_receiver_auxiliary_navigation.html
   test_presentation_receiver_auxiliary_navigation.js
   file_presentation_sandboxed_presentation.html
   file_presentation_terminate.html
   test_presentation_terminate.js
   file_presentation_terminate_establish_connection_error.html
   test_presentation_terminate_establish_connection_error.js
   file_presentation_reconnect.html
+  file_presentation_unknown_content_type.test
+  file_presentation_unknown_content_type.test^headers^
+  test_presentation_tcp_receiver_establish_connection_unknown_content_type.js
 
 [test_presentation_dc_sender.html]
 [test_presentation_dc_receiver.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_dc_receiver_oop.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_1ua_sender_and_receiver.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
@@ -38,16 +41,20 @@ skip-if = (e10s || toolkit == 'gonk' || 
 [test_presentation_tcp_sender_disconnect.html]
 skip-if = toolkit == 'android' # Bug 1129785
 [test_presentation_tcp_sender_establish_connection_error.html]
 skip-if = toolkit == 'android' # Bug 1129785
 [test_presentation_tcp_receiver_establish_connection_error.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android' || os == 'mac' || os == 'win' || buildapp == 'mulet') # Bug 1129785, Bug 1204709
 [test_presentation_tcp_receiver_establish_connection_timeout.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
+[test_presentation_tcp_receiver_establish_connection_unknown_content_type_inproc.html]
+skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android')
+[test_presentation_tcp_receiver_establish_connection_unknown_content_type_oop.html]
+skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android')
 [test_presentation_tcp_receiver.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_tcp_receiver_oop.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785
 [test_presentation_receiver_auxiliary_navigation_inproc.html]
 skip-if = (e10s || toolkit == 'gonk')
 [test_presentation_receiver_auxiliary_navigation_oop.html]
 skip-if = (e10s || toolkit == 'gonk')
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type.js
@@ -0,0 +1,86 @@
+'use strict';
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationSessionChromeScript.js'));
+var receiverUrl = SimpleTest.getTestFileURL('file_presentation_unknown_content_type.test');
+
+var obs = SpecialPowers.Cc['@mozilla.org/observer-service;1']
+          .getService(SpecialPowers.Ci.nsIObserverService);
+
+var receiverIframe;
+
+function setup() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.sendAsyncMessage('trigger-device-add');
+
+    receiverIframe = document.createElement('iframe');
+    receiverIframe.setAttribute('mozbrowser', 'true');
+    receiverIframe.setAttribute('mozpresentation', receiverUrl);
+    receiverIframe.setAttribute('src', receiverUrl);
+    var oop = location.pathname.indexOf('_inproc') == -1;
+    receiverIframe.setAttribute("remote", oop);
+
+    var promise = new Promise(function(aResolve, aReject) {
+      document.body.appendChild(receiverIframe);
+
+      aResolve(receiverIframe);
+    });
+    obs.notifyObservers(promise, 'setup-request-promise', null);
+
+    aResolve();
+  });
+}
+
+function testIncomingSessionRequestReceiverLaunchUnknownContentType() {
+  let promise = Promise.all([
+    new Promise(function(aResolve, aReject) {
+      gScript.addMessageListener('receiver-launching', function launchReceiverHandler(aSessionId) {
+        gScript.removeMessageListener('receiver-launching', launchReceiverHandler);
+        info('Trying to launch receiver page.');
+
+        receiverIframe.addEventListener('mozbrowserclose', function() {
+          ok(true, 'observe receiver window closed');
+          aResolve();
+        });
+      });
+    }),
+    new Promise(function(aResolve, aReject) {
+      gScript.addMessageListener('control-channel-closed', function controlChannelClosedHandler(aReason) {
+        gScript.removeMessageListener('control-channel-closed', controlChannelClosedHandler);
+        is(aReason, 0x80530020 /* NS_ERROR_DOM_OPERATION_ERR */, 'The control channel is closed due to load failure.');
+        aResolve();
+      });
+    })
+  ]);
+
+  gScript.sendAsyncMessage('trigger-incoming-session-request', receiverUrl);
+  return promise;
+}
+
+function teardown() {
+  gScript.addMessageListener('teardown-complete', function teardownCompleteHandler() {
+    gScript.removeMessageListener('teardown-complete', teardownCompleteHandler);
+    gScript.destroy();
+    SimpleTest.finish();
+  });
+
+  gScript.sendAsyncMessage('teardown');
+}
+
+function runTests() {
+  setup().
+  then(testIncomingSessionRequestReceiverLaunchUnknownContentType).
+  then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+  {type: 'presentation-device-manage', allow: false, context: document},
+  {type: 'presentation', allow: true, context: document},
+  {type: 'browser', allow: true, context: document},
+], function() {
+  SpecialPowers.pushPrefEnv({ 'set': [['dom.presentation.enabled', true],
+                                      ['dom.presentation.session_transport.data_channel.enable', false],
+                                      ['dom.mozBrowserFramesEnabled', true],
+                                      ['dom.ipc.tabs.disabled', false]]},
+                            runTests);
+});
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_inproc.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+  <meta charset="utf-8">
+  <title>Test for unknown content type of B2G Presentation API at receiver side</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1287717">Test for unknown content type of B2G Presentation API at receiver side</a>
+    <script type="application/javascript;version=1.8" src="test_presentation_tcp_receiver_establish_connection_unknown_content_type.js">
+    </script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_receiver_establish_connection_unknown_content_type_oop.html
@@ -0,0 +1,16 @@
+<!DOCTYPE HTML>
+<html>
+<!-- Any copyright is dedicated to the Public Domain.
+   - http://creativecommons.org/publicdomain/zero/1.0/ -->
+<head>
+  <meta charset="utf-8">
+  <title>Test for unknown content type of B2G Presentation API at receiver side (OOP)</title>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1287717">Test for unknown content type of B2G Presentation API at receiver side (OOP)</a>
+    <script type="application/javascript;version=1.8" src="test_presentation_tcp_receiver_establish_connection_unknown_content_type.js">
+    </script>
+</body>
+</html>