Bug 1228508 - Part 1, create new availability object for each getAvailability(). r=smaug. draft
authorShih-Chiang Chien <schien@mozilla.com>
Wed, 31 Aug 2016 10:31:15 +0800
changeset 407880 076e26f256faed64f44c035d2a1b8e317052af63
parent 407818 506facea63169a29e04eb140663da1730052db64
child 407881 004360c49a899273cc582ad4a2e886be167e9cac
push id28071
push userschien@mozilla.com
push dateWed, 31 Aug 2016 06:02:21 +0000
reviewerssmaug
bugs1228508
milestone51.0a1
Bug 1228508 - Part 1, create new availability object for each getAvailability(). r=smaug. MozReview-Commit-ID: 8DjlW3C58Tz
dom/presentation/PresentationAvailability.cpp
dom/presentation/PresentationAvailability.h
dom/presentation/PresentationRequest.cpp
dom/presentation/PresentationRequest.h
dom/presentation/PresentationService.cpp
dom/presentation/ipc/PresentationIPCService.cpp
dom/presentation/tests/mochitest/mochitest.ini
dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway.js
dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.js
dom/presentation/tests/mochitest/test_presentation_availability.html
dom/presentation/tests/mochitest/test_presentation_dc_sender.html
dom/presentation/tests/mochitest/test_presentation_reconnect.html
dom/presentation/tests/mochitest/test_presentation_sender_on_terminate_request.html
dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
dom/presentation/tests/mochitest/test_presentation_tcp_sender.html
dom/presentation/tests/mochitest/test_presentation_tcp_sender_default_request.html
dom/presentation/tests/mochitest/test_presentation_tcp_sender_disconnect.html
dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html
dom/presentation/tests/mochitest/test_presentation_terminate.js
dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error.js
dom/webidl/PresentationRequest.webidl
testing/profiles/prefs_general.js
--- a/dom/presentation/PresentationAvailability.cpp
+++ b/dom/presentation/PresentationAvailability.cpp
@@ -1,78 +1,84 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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 "PresentationAvailability.h"
+
 #include "mozilla/dom/PresentationAvailabilityBinding.h"
+#include "mozilla/dom/Promise.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIPresentationDeviceManager.h"
 #include "nsIPresentationService.h"
 #include "nsServiceManagerUtils.h"
-#include "PresentationAvailability.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(PresentationAvailability)
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PresentationAvailability, DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPromise)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PresentationAvailability, DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mPromise);
   tmp->Shutdown();
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_ADDREF_INHERITED(PresentationAvailability, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(PresentationAvailability, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PresentationAvailability)
   NS_INTERFACE_MAP_ENTRY(nsIPresentationAvailabilityListener)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 /* static */ already_AddRefed<PresentationAvailability>
-PresentationAvailability::Create(nsPIDOMWindowInner* aWindow)
+PresentationAvailability::Create(nsPIDOMWindowInner* aWindow,
+                                 RefPtr<Promise>& aPromise)
 {
-  RefPtr<PresentationAvailability> availability = new PresentationAvailability(aWindow);
-  return NS_WARN_IF(!availability->Init()) ? nullptr : availability.forget();
+  RefPtr<PresentationAvailability> availability =
+    new PresentationAvailability(aWindow);
+  return NS_WARN_IF(!availability->Init(aPromise)) ? nullptr
+                                                   : availability.forget();
 }
 
 PresentationAvailability::PresentationAvailability(nsPIDOMWindowInner* aWindow)
   : DOMEventTargetHelper(aWindow)
   , mIsAvailable(false)
 {
 }
 
 PresentationAvailability::~PresentationAvailability()
 {
   Shutdown();
 }
 
 bool
-PresentationAvailability::Init()
+PresentationAvailability::Init(RefPtr<Promise>& aPromise)
 {
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
   if (NS_WARN_IF(!service)) {
     return false;
   }
 
   nsresult rv = service->RegisterAvailabilityListener(this);
   if (NS_WARN_IF(NS_FAILED(rv))) {
-    return false;
+    // If the user agent is unable to monitor available device,
+    // Resolve promise with |value| set to false.
+    mIsAvailable = false;
+    aPromise->MaybeResolve(this);
+    return true;
   }
 
-  nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
-    do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
-  if (NS_WARN_IF(!deviceManager)) {
-    return false;
-  }
-  deviceManager->GetDeviceAvailable(&mIsAvailable);
+  mPromise = aPromise;
 
   return true;
 }
 
 void PresentationAvailability::Shutdown()
 {
   nsCOMPtr<nsIPresentationService> service =
     do_GetService(PRESENTATION_SERVICE_CONTRACTID);
@@ -109,13 +115,24 @@ PresentationAvailability::NotifyAvailabl
 {
   return NS_DispatchToCurrentThread(NewRunnableMethod
                                     <bool>(this,
                                            &PresentationAvailability::UpdateAvailabilityAndDispatchEvent,
                                            aIsAvailable));
 }
 
 void
-PresentationAvailability::UpdateAvailabilityAndDispatchEvent(bool aIsAvailable) {
+PresentationAvailability::UpdateAvailabilityAndDispatchEvent(bool aIsAvailable)
+{
+  bool isChanged = (aIsAvailable != mIsAvailable);
+
   mIsAvailable = aIsAvailable;
 
-  NS_WARN_IF(NS_FAILED(DispatchTrustedEvent(NS_LITERAL_STRING("change"))));
+  if (mPromise) {
+    mPromise->MaybeResolve(this);
+    mPromise = nullptr;
+    return;
+  }
+
+  if (isChanged) {
+    NS_WARN_IF(NS_FAILED(DispatchTrustedEvent(NS_LITERAL_STRING("change"))));
+  }
 }
--- a/dom/presentation/PresentationAvailability.h
+++ b/dom/presentation/PresentationAvailability.h
@@ -4,52 +4,58 @@
  * 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_PresentationAvailability_h
 #define mozilla_dom_PresentationAvailability_h
 
 #include "mozilla/DOMEventTargetHelper.h"
 #include "nsIPresentationListener.h"
+#include "nsTArray.h"
 
 namespace mozilla {
 namespace dom {
 
+class Promise;
+
 class PresentationAvailability final : public DOMEventTargetHelper
                                      , public nsIPresentationAvailabilityListener
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PresentationAvailability,
                                            DOMEventTargetHelper)
   NS_DECL_NSIPRESENTATIONAVAILABILITYLISTENER
 
   static already_AddRefed<PresentationAvailability>
-  Create(nsPIDOMWindowInner* aWindow);
+  Create(nsPIDOMWindowInner* aWindow,
+         RefPtr<Promise>& aPromise);
 
   virtual void DisconnectFromOwner() override;
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
   // WebIDL (public APIs)
   bool Value() const;
 
   IMPL_EVENT_HANDLER(change);
 
 private:
   explicit PresentationAvailability(nsPIDOMWindowInner* aWindow);
 
-  ~PresentationAvailability();
+  virtual ~PresentationAvailability();
 
-  bool Init();
+  bool Init(RefPtr<Promise>& aPromise);
 
   void Shutdown();
 
   void UpdateAvailabilityAndDispatchEvent(bool aIsAvailable);
 
   bool mIsAvailable;
+
+  RefPtr<Promise> mPromise;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationAvailability_h
--- a/dom/presentation/PresentationRequest.cpp
+++ b/dom/presentation/PresentationRequest.cpp
@@ -18,23 +18,21 @@
 #include "nsIPresentationService.h"
 #include "nsIURI.h"
 #include "nsIUUIDGenerator.h"
 #include "nsNetUtil.h"
 #include "nsSandboxFlags.h"
 #include "nsServiceManagerUtils.h"
 #include "PresentationAvailability.h"
 #include "PresentationCallbacks.h"
+#include "PresentationLog.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 
-NS_IMPL_CYCLE_COLLECTION_INHERITED(PresentationRequest, DOMEventTargetHelper,
-                                   mAvailability)
-
 NS_IMPL_ADDREF_INHERITED(PresentationRequest, DOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(PresentationRequest, DOMEventTargetHelper)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PresentationRequest)
 NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
 
 static nsresult
 GetAbsoluteURL(const nsAString& aUrl,
@@ -102,21 +100,16 @@ PresentationRequest::PresentationRequest
 
 PresentationRequest::~PresentationRequest()
 {
 }
 
 bool
 PresentationRequest::Init()
 {
-  mAvailability = PresentationAvailability::Create(GetOwner());
-  if (NS_WARN_IF(!mAvailability)) {
-    return false;
-  }
-
   return true;
 }
 
 /* virtual */ JSObject*
 PresentationRequest::WrapObject(JSContext* aCx,
                                 JS::Handle<JSObject*> aGivenProto)
 {
   return PresentationRequestBinding::Wrap(aCx, this, aGivenProto);
@@ -312,16 +305,18 @@ PresentationRequest::FindOrCreatePresent
   if (NS_WARN_IF(NS_FAILED(rv))) {
     aPromise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
   }
 }
 
 already_AddRefed<Promise>
 PresentationRequest::GetAvailability(ErrorResult& aRv)
 {
+  PRES_DEBUG("%s:id[%s]\n", __func__,
+             NS_ConvertUTF16toUTF8(mUrl).get());
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(GetOwner());
   if (NS_WARN_IF(!global)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   nsCOMPtr<nsIDocument> doc = GetOwner()->GetExtantDoc();
   if (NS_WARN_IF(!doc)) {
@@ -340,17 +335,28 @@ PresentationRequest::GetAvailability(Err
     return promise.forget();
   }
 
   if (doc->GetSandboxFlags() & SANDBOXED_PRESENTATION) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
-  promise->MaybeResolve(mAvailability);
+  // TODO
+  // Search the set of availability object and resolve
+  // promise with the one had same presentation URLs.
+
+  RefPtr<PresentationAvailability> availability =
+    PresentationAvailability::Create(GetOwner(), promise);
+
+  if (!availability) {
+    promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
+    return promise.forget();
+  }
+
   return promise.forget();
 }
 
 nsresult
 PresentationRequest::DispatchConnectionAvailableEvent(PresentationConnection* aConnection)
 {
   PresentationConnectionAvailableEventInit init;
   init.mConnection = aConnection;
--- a/dom/presentation/PresentationRequest.h
+++ b/dom/presentation/PresentationRequest.h
@@ -17,18 +17,16 @@ namespace dom {
 class Promise;
 class PresentationAvailability;
 class PresentationConnection;
 
 class PresentationRequest final : public DOMEventTargetHelper
 {
 public:
   NS_DECL_ISUPPORTS_INHERITED
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PresentationRequest,
-                                           DOMEventTargetHelper)
 
   static already_AddRefed<PresentationRequest> Constructor(const GlobalObject& aGlobal,
                                                            const nsAString& aUrl,
                                                            ErrorResult& aRv);
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
@@ -60,15 +58,14 @@ private:
 
   // Implement https://w3c.github.io/webappsec-mixed-content/#categorize-settings-object
   bool IsProhibitMixedSecurityContexts(nsIDocument* aDocument);
 
   // Implement https://w3c.github.io/webappsec-mixed-content/#a-priori-authenticated-url
   bool IsPrioriAuthenticatedURL(const nsAString& aUrl);
 
   nsString mUrl;
-  RefPtr<PresentationAvailability> mAvailability;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_PresentationRequest_h
--- a/dom/presentation/PresentationService.cpp
+++ b/dom/presentation/PresentationService.cpp
@@ -612,18 +612,18 @@ PresentationService::StartSession(const 
 
   // Find the designated device from available device list.
   nsCOMPtr<nsIPresentationDeviceManager> deviceManager =
     do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID);
   if (NS_WARN_IF(!deviceManager)) {
     return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
   }
 
-  nsCOMPtr<nsIMutableArray> presentationUrls
-    = do_CreateInstance(NS_ARRAY_CONTRACTID);
+  nsCOMPtr<nsIMutableArray> presentationUrls =
+    do_CreateInstance(NS_ARRAY_CONTRACTID);
   if (!presentationUrls) {
     return aCallback->NotifyError(NS_ERROR_DOM_OPERATION_ERR);
   }
   nsCOMPtr<nsISupportsString> supportsStr =
     do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
   supportsStr->SetData(aUrl);
   presentationUrls->AppendElement(supportsStr, false);
 
@@ -801,21 +801,24 @@ PresentationService::BuildTransport(cons
   return static_cast<PresentationControllingInfo*>(info.get())->BuildTransport();
 }
 
 NS_IMETHODIMP
 PresentationService::RegisterAvailabilityListener(nsIPresentationAvailabilityListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  if (NS_WARN_IF(mAvailabilityListeners.Contains(aListener))) {
-    return NS_OK;
+  if (!mAvailabilityListeners.Contains(aListener)) {
+    mAvailabilityListeners.AppendElement(aListener);
   }
 
-  mAvailabilityListeners.AppendElement(aListener);
+  // Leverage availablility change notification to assign
+  // the initial value of availability object.
+  NS_WARN_IF(NS_FAILED(aListener->NotifyAvailableChange(mIsAvailable)));
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationService::UnregisterAvailabilityListener(nsIPresentationAvailabilityListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
--- a/dom/presentation/ipc/PresentationIPCService.cpp
+++ b/dom/presentation/ipc/PresentationIPCService.cpp
@@ -193,17 +193,17 @@ PresentationIPCService::RegisterAvailabi
 
 NS_IMETHODIMP
 PresentationIPCService::UnregisterAvailabilityListener(nsIPresentationAvailabilityListener* aListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aListener);
 
   mAvailabilityListeners.RemoveElement(aListener);
-  if (sPresentationChild) {
+  if (mAvailabilityListeners.IsEmpty() && sPresentationChild) {
     NS_WARN_IF(!sPresentationChild->SendUnregisterAvailabilityHandler());
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
 PresentationIPCService::RegisterSessionListener(const nsAString& aSessionId,
                                                 uint8_t aRole,
--- a/dom/presentation/tests/mochitest/mochitest.ini
+++ b/dom/presentation/tests/mochitest/mochitest.ini
@@ -68,8 +68,9 @@ skip-if = (e10s || toolkit == 'gonk' || 
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android')
 [test_presentation_terminate_establish_connection_error_oop.html]
 skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android')
 [test_presentation_sender_on_terminate_request.html]
 skip-if = toolkit == 'android'
 [test_presentation_sandboxed_presentation.html]
 [test_presentation_reconnect.html]
 [test_presentation_mixed_security_contexts.html]
+[test_presentation_availability.html]
--- a/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway.js
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_connection_wentaway.js
@@ -81,28 +81,29 @@ function setup() {
   return Promise.resolve();
 }
 
 function testCreateRequest() {
   return new Promise(function(aResolve, aReject) {
     info('Sender: --- testCreateRequest ---');
     request = new PresentationRequest(receiverUrl);
     request.getAvailability().then((aAvailability) => {
+      is(aAvailability.value, false, "Sender: should have no available device after setup");
       aAvailability.onchange = function() {
         aAvailability.onchange = null;
         ok(aAvailability.value, "Sender: Device should be available.");
         aResolve();
       }
+
+      gScript.sendAsyncMessage('trigger-device-add');
     }).catch((aError) => {
       ok(false, "Sender: Error occurred when getting availability: " + aError);
       teardown();
       aReject();
     });
-
-    gScript.sendAsyncMessage('trigger-device-add');
   });
 }
 
 function testStartConnection() {
   return new Promise(function(aResolve, aReject) {
     request.start().then((aConnection) => {
       connection = aConnection;
       ok(connection, "Sender: Connection should be available.");
--- a/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.js
+++ b/dom/presentation/tests/mochitest/test_presentation_1ua_sender_and_receiver.js
@@ -84,28 +84,29 @@ function setup() {
   return Promise.resolve();
 }
 
 function testCreateRequest() {
   return new Promise(function(aResolve, aReject) {
     info('Sender: --- testCreateRequest ---');
     request = new PresentationRequest("file_presentation_1ua_receiver.html");
     request.getAvailability().then((aAvailability) => {
+      is(aAvailability.value, false, "Sender: should have no available device after setup");
       aAvailability.onchange = function() {
         aAvailability.onchange = null;
         ok(aAvailability.value, "Sender: Device should be available.");
         aResolve();
       }
+
+      gScript.sendAsyncMessage('trigger-device-add');
     }).catch((aError) => {
       ok(false, "Sender: Error occurred when getting availability: " + aError);
       teardown();
       aReject();
     });
-
-    gScript.sendAsyncMessage('trigger-device-add');
   });
 }
 
 function testStartConnection() {
   return new Promise(function(aResolve, aReject) {
     request.start().then((aConnection) => {
       connection = aConnection;
       ok(connection, "Sender: Connection should be available.");
new file mode 100644
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_availability.html
@@ -0,0 +1,120 @@
+<!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 PresentationAvailability</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=1228508">Test PresentationAvailability</a>
+<script type="application/javascript;version=1.8">
+
+"use strict";
+
+var testDevice = {
+  id: 'id',
+  name: 'name',
+  type: 'type',
+};
+
+var gScript = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('PresentationDeviceInfoChromeScript.js'));
+var request;
+var availability;
+
+function testSetup() {
+  return new Promise(function(aResolve, aReject) {
+    gScript.addMessageListener('setup-complete', function() {
+      aResolve();
+    });
+    gScript.sendAsyncMessage('setup');
+  });
+}
+
+function testInitialUnavailable() {
+  request = new PresentationRequest("https://example.com");
+
+  return request.getAvailability().then(function(aAvailability) {
+    is(aAvailability.value, false, "Should have no available device after setup");
+    aAvailability.onchange = function() {
+      aAvailability.onchange = null;
+      ok(aAvailability.value, "Device should be available.");
+    }
+    availability = aAvailability;
+    gScript.sendAsyncMessage('trigger-device-add', testDevice);
+  }).catch(function(aError) {
+    ok(false, "Error occurred when getting availability: " + aError);
+    teardown();
+  });
+}
+
+function testInitialAvailable() {
+  let anotherRequest = new PresentationRequest("https://example.net");
+  return anotherRequest.getAvailability().then(function(aAvailability) {
+    is(aAvailability.value, true, "Should have available device initially");
+    isnot(aAvailability, availability, "Should get different availability object for different request URL");
+  }).catch(function(aError) {
+    ok(false, "Error occurred when getting availability: " + aError);
+    teardown();
+  });
+}
+
+function testOnChangeEvent() {
+  return new Promise(function(aResolve, aReject) {
+    availability.onchange = function() {
+      availability.onchange = null;
+      is(availability.value, false, "Should have no available device after device removed");
+      aResolve();
+    }
+    gScript.sendAsyncMessage('trigger-device-remove');
+  });
+}
+
+function testConsecutiveGetAvailability() {
+  let request = new PresentationRequest("https://example.org");
+  let firstAvailabilityResolved = false;
+  return Promise.all([
+    request.getAvailability().then(function() {
+      firstAvailabilityResolved = true;
+    }),
+    request.getAvailability().then(function() {
+      ok(firstAvailabilityResolved, "getAvailability() should be resolved in sequence");
+    })
+  ]).catch(function(aError) {
+    ok(false, "Error occurred when getting availability: " + aError);
+    teardown();
+  });
+}
+
+function teardown() {
+  request = null;
+  availability = null;
+  gScript.sendAsyncMessage('teardown');
+  gScript.destroy();
+  SimpleTest.finish();
+}
+
+function runTests() {
+  ok(navigator.presentation, "navigator.presentation should be available.");
+  testSetup().then(testInitialUnavailable)
+             .then(testInitialAvailable)
+             .then(testOnChangeEvent)
+             .then(testConsecutiveGetAvailability)
+             .then(teardown);
+}
+
+SimpleTest.waitForExplicitFinish();
+SpecialPowers.pushPermissions([
+  {type: "presentation-device-manage", allow: false, context: document},
+], function() {
+  SpecialPowers.pushPrefEnv({ "set": [["dom.presentation.enabled", true],
+                                      ["dom.presentation.controller.enabled", true],
+                                      ["dom.presentation.session_transport.data_channel.enable", false]]},
+                            runTests);
+});
+
+</script>
+</body>
+</html>
--- a/dom/presentation/tests/mochitest/test_presentation_dc_sender.html
+++ b/dom/presentation/tests/mochitest/test_presentation_dc_sender.html
@@ -21,30 +21,31 @@ var request;
 var connection;
 
 function testSetup() {
   return new Promise(function(aResolve, aReject) {
     request = new PresentationRequest("http://example.com/");
 
     request.getAvailability().then(
       function(aAvailability) {
+        is(aAvailability.value, false, "Sender: should have no available device after setup");
         aAvailability.onchange = function() {
           aAvailability.onchange = null;
           ok(aAvailability.value, "Device should be available.");
           aResolve();
         }
+
+        gScript.sendAsyncMessage('trigger-device-add');
       },
       function(aError) {
         ok(false, "Error occurred when getting availability: " + aError);
         teardown();
         aReject();
       }
     );
-
-    gScript.sendAsyncMessage('trigger-device-add');
   });
 }
 
 function testStartConnection() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('device-prompt', function devicePromptHandler() {
       gScript.removeMessageListener('device-prompt', devicePromptHandler);
       info("Device prompt is triggered.");
--- a/dom/presentation/tests/mochitest/test_presentation_reconnect.html
+++ b/dom/presentation/tests/mochitest/test_presentation_reconnect.html
@@ -43,30 +43,31 @@ function testSetup() {
         SimpleTest.finish();
       }
     }, false);
 
     request = new PresentationRequest("http://example.com/");
 
     request.getAvailability().then(
       function(aAvailability) {
+        is(aAvailability.value, false, "Sender: should have no available device after setup");
         aAvailability.onchange = function() {
           aAvailability.onchange = null;
           ok(aAvailability.value, "Device should be available.");
           aResolve();
         }
+
+        gScript.sendAsyncMessage('trigger-device-add');
       },
       function(aError) {
         ok(false, "Error occurred when getting availability: " + aError);
         teardown();
         aReject();
       }
     );
-
-    gScript.sendAsyncMessage('trigger-device-add');
   });
 }
 
 function testStartConnection() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('device-prompt', function devicePromptHandler() {
       info("Device prompt is triggered.");
       gScript.sendAsyncMessage('trigger-device-prompt-select');
--- a/dom/presentation/tests/mochitest/test_presentation_sender_on_terminate_request.html
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_on_terminate_request.html
@@ -19,30 +19,31 @@ var request;
 var connection;
 
 function testSetup() {
   return new Promise(function(aResolve, aReject) {
     request = new PresentationRequest("http://example.com");
 
     request.getAvailability().then(
       function(aAvailability) {
+        is(aAvailability.value, false, "Sender: should have no available device after setup");
         aAvailability.onchange = function() {
           aAvailability.onchange = null;
           ok(aAvailability.value, "Device should be available.");
           aResolve();
         }
+
+        gScript.sendAsyncMessage('trigger-device-add');
       },
       function(aError) {
         ok(false, "Error occurred when getting availability: " + aError);
         teardown();
         aReject();
       }
     );
-
-    gScript.sendAsyncMessage('trigger-device-add');
   });
 }
 
 function testStartConnection() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('device-prompt', function devicePromptHandler() {
       gScript.removeMessageListener('device-prompt', devicePromptHandler);
       info("Device prompt is triggered.");
--- a/dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_startWithDevice.html
@@ -19,30 +19,31 @@ var request;
 var connection;
 
 function testSetup() {
   return new Promise(function(aResolve, aReject) {
     request = new PresentationRequest("https://example.com");
 
     request.getAvailability().then(
       function(aAvailability) {
+        is(aAvailability.value, false, "Sender: should have no available device after setup");
         aAvailability.onchange = function() {
           aAvailability.onchange = null;
           ok(aAvailability.value, "Device should be available.");
           aResolve();
         }
+
+        gScript.sendAsyncMessage('trigger-device-add');
       },
       function(aError) {
         ok(false, "Error occurred when getting availability: " + aError);
         teardown();
         aReject();
       }
     );
-
-    gScript.sendAsyncMessage('trigger-device-add');
   });
 }
 
 function testStartConnectionWithDevice() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('device-prompt', function devicePromptHandler() {
       gScript.removeMessageListener('device-prompt', devicePromptHandler);
       ok(false, "Device prompt should not be triggered.");
--- a/dom/presentation/tests/mochitest/test_presentation_tcp_sender.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender.html
@@ -19,30 +19,31 @@ var request;
 var connection;
 
 function testSetup() {
   return new Promise(function(aResolve, aReject) {
     request = new PresentationRequest("https://example.com");
 
     request.getAvailability().then(
       function(aAvailability) {
+        is(aAvailability.value, false, "Sender: should have no available device after setup");
         aAvailability.onchange = function() {
           aAvailability.onchange = null;
           ok(aAvailability.value, "Device should be available.");
           aResolve();
         }
+        gScript.sendAsyncMessage('trigger-device-add');
       },
       function(aError) {
         ok(false, "Error occurred when getting availability: " + aError);
         teardown();
         aReject();
       }
     );
 
-    gScript.sendAsyncMessage('trigger-device-add');
   });
 }
 
 function testStartConnection() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('device-prompt', function devicePromptHandler() {
       gScript.removeMessageListener('device-prompt', devicePromptHandler);
       info("Device prompt is triggered.");
--- a/dom/presentation/tests/mochitest/test_presentation_tcp_sender_default_request.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_default_request.html
@@ -18,30 +18,31 @@ var gScript = SpecialPowers.loadChromeSc
 var connection;
 
 function testSetup() {
   return new Promise(function(aResolve, aReject) {
     navigator.presentation.defaultRequest = new PresentationRequest("https://example.com");
 
     navigator.presentation.defaultRequest.getAvailability().then(
       function(aAvailability) {
+        is(aAvailability.value, false, "Sender: should have no available device after setup");
         aAvailability.onchange = function() {
           aAvailability.onchange = null;
           ok(aAvailability.value, "Device should be available.");
           aResolve();
         }
+
+        gScript.sendAsyncMessage('trigger-device-add');
       },
       function(aError) {
         ok(false, "Error occurred when getting availability: " + aError);
         teardown();
         aReject();
       }
     );
-
-    gScript.sendAsyncMessage('trigger-device-add');
   });
 }
 
 function testStartConnection() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('device-prompt', function devicePromptHandler() {
       gScript.removeMessageListener('device-prompt', devicePromptHandler);
       info("Device prompt is triggered.");
--- a/dom/presentation/tests/mochitest/test_presentation_tcp_sender_disconnect.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_disconnect.html
@@ -19,30 +19,31 @@ var request;
 var connection;
 
 function testSetup() {
   return new Promise(function(aResolve, aReject) {
     request = new PresentationRequest("http://example.com");
 
     request.getAvailability().then(
       function(aAvailability) {
+        is(aAvailability.value, false, "Sender: should have no available device after setup");
         aAvailability.onchange = function() {
           aAvailability.onchange = null;
           ok(aAvailability.value, "Device should be available.");
           aResolve();
         }
+
+        gScript.sendAsyncMessage('trigger-device-add');
       },
       function(aError) {
         ok(false, "Error occurred when getting availability: " + aError);
         teardown();
         aReject();
       }
     );
-
-    gScript.sendAsyncMessage('trigger-device-add');
   });
 }
 
 function testStartConnection() {
   return new Promise(function(aResolve, aReject) {
     gScript.addMessageListener('device-prompt', function devicePromptHandler() {
       gScript.removeMessageListener('device-prompt', devicePromptHandler);
       info("Device prompt is triggered.");
--- a/dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html
+++ b/dom/presentation/tests/mochitest/test_presentation_tcp_sender_establish_connection_error.html
@@ -18,30 +18,31 @@ var gScript = SpecialPowers.loadChromeSc
 var request;
 
 function setup() {
   return new Promise(function(aResolve, aReject) {
     request = new PresentationRequest("http://example.com");
 
     request.getAvailability().then(
       function(aAvailability) {
+        is(aAvailability.value, false, "Sender: should have no available device after setup");
         aAvailability.onchange = function() {
           aAvailability.onchange = null;
           ok(aAvailability.value, "Device should be available.");
           aResolve();
         }
+
+        gScript.sendAsyncMessage('trigger-device-add');
       },
       function(aError) {
         ok(false, "Error occurred when getting availability: " + aError);
         teardown();
         aReject();
       }
     );
-
-    gScript.sendAsyncMessage('trigger-device-add');
   });
 }
 
 function testCreateRequestWithEmptyURL() {
   return new Promise(function(aResolve, aReject) {
     try {
       request = new PresentationRequest("");
     } catch (aError) {
--- a/dom/presentation/tests/mochitest/test_presentation_terminate.js
+++ b/dom/presentation/tests/mochitest/test_presentation_terminate.js
@@ -81,28 +81,29 @@ function setup() {
   return Promise.resolve();
 }
 
 function testCreateRequest() {
   return new Promise(function(aResolve, aReject) {
     info('Sender: --- testCreateRequest ---');
     request = new PresentationRequest(receiverUrl);
     request.getAvailability().then((aAvailability) => {
+      is(aAvailability.value, false, "Sender: should have no available device after setup");
       aAvailability.onchange = function() {
         aAvailability.onchange = null;
         ok(aAvailability.value, 'Sender: Device should be available.');
         aResolve();
       }
+
+      gScript.sendAsyncMessage('trigger-device-add');
     }).catch((aError) => {
       ok(false, 'Sender: Error occurred when getting availability: ' + aError);
       teardown();
       aReject();
     });
-
-    gScript.sendAsyncMessage('trigger-device-add');
   });
 }
 
 function testStartConnection() {
   return new Promise(function(aResolve, aReject) {
     request.start().then((aConnection) => {
       connection = aConnection;
       ok(connection, 'Sender: Connection should be available.');
--- a/dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error.js
+++ b/dom/presentation/tests/mochitest/test_presentation_terminate_establish_connection_error.js
@@ -86,28 +86,29 @@ function setup() {
   return Promise.resolve();
 }
 
 function testCreateRequest() {
   return new Promise(function(aResolve, aReject) {
     info('Sender: --- testCreateRequest ---');
     request = new PresentationRequest(receiverUrl);
     request.getAvailability().then((aAvailability) => {
+      is(aAvailability.value, false, "Sender: should have no available device after setup");
       aAvailability.onchange = function() {
         aAvailability.onchange = null;
         ok(aAvailability.value, 'Sender: Device should be available.');
         aResolve();
       }
+
+      gScript.sendAsyncMessage('trigger-device-add');
     }).catch((aError) => {
       ok(false, 'Sender: Error occurred when getting availability: ' + aError);
       teardown();
       aReject();
     });
-
-    gScript.sendAsyncMessage('trigger-device-add');
   });
 }
 
 function testStartConnection() {
   return new Promise(function(aResolve, aReject) {
     request.start().then((aConnection) => {
       connection = aConnection;
       ok(connection, 'Sender: Connection should be available.');
--- a/dom/webidl/PresentationRequest.webidl
+++ b/dom/webidl/PresentationRequest.webidl
@@ -44,16 +44,17 @@ interface PresentationRequest : EventTar
   Promise<PresentationConnection> reconnect(DOMString presentationId);
 
  /*
   * UA triggers device discovery mechanism periodically and monitor device
   * availability.
   *
   * The promise may be rejected duo to one of the following reasons:
   * - "NotSupportedError": Unable to continuously monitor the availability.
+  * - "SecurityError":  This operation is insecure.
   */
   [Throws]
   Promise<PresentationAvailability> getAvailability();
 
   /*
    * It is called when a connection associated with a PresentationRequest is created.
    * The event is fired for all connections that are created for the controller.
    */
--- a/testing/profiles/prefs_general.js
+++ b/testing/profiles/prefs_general.js
@@ -227,17 +227,17 @@ user_pref("browser.snippets.firstrunHome
 user_pref("general.useragent.updates.enabled", false);
 
 // Disable webapp updates.  Yes, it is supposed to be an integer.
 user_pref("browser.webapps.checkForUpdates", 0);
 
 // Enable debug logging in the tcp presentation server.
 user_pref("dom.presentation.tcp_server.debug", true);
 // Enable debug logging in the presentation core service.
-pref("logging.Presentation", "debug");
+user_pref("logging.Presentation", "debug");
 
 // Don't connect to Yahoo! for RSS feed tests.
 // en-US only uses .types.0.uri, but set all of them just to be sure.
 user_pref('browser.contentHandlers.types.0.uri', 'http://test1.example.org/rss?url=%%s')
 user_pref('browser.contentHandlers.types.1.uri', 'http://test1.example.org/rss?url=%%s')
 user_pref('browser.contentHandlers.types.2.uri', 'http://test1.example.org/rss?url=%%s')
 user_pref('browser.contentHandlers.types.3.uri', 'http://test1.example.org/rss?url=%%s')
 user_pref('browser.contentHandlers.types.4.uri', 'http://test1.example.org/rss?url=%%s')