From dd86015766c64ed9bbb29043c52a53cbb00fb812 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 6 Aug 2015 10:35:33 -0700 Subject: [PATCH 01/18] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ======== https://hg.mozilla.org/integration/gaia-central/rev/b8d0a7e33f07 Author: Francisco Jordano Desc: Merge pull request #31234 from arcturus/bug-1188797 Bug 1188797 - [System] Implement alwaysLowered window.open() feature … ======== https://hg.mozilla.org/integration/gaia-central/rev/8dabcd5ff0ec Author: Francisco Jordano Desc: Bug 1188797 - [System] Implement alwaysLowered window.open() feature in Gaia r=etienne --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 5356567c13b2..337dbde5a969 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "7f387f859d48f9ad0761637c78447dc524747738", + "git_revision": "24f8c8b69c37f8106355d92c58039f417e81fb10", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "16423131f4a9b03659d92e8ffad7a6f80a8eae37", + "revision": "b8d0a7e33f0733b07e5686b485c7f3a9e54f4338", "repo_path": "integration/gaia-central" } From a4b7483e1f17b36308fe0a585ea952d36f6ae1a3 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 6 Aug 2015 10:37:10 -0700 Subject: [PATCH 02/18] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index d6640a9cde51..c46989cf7601 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index d4d8657f79e0..f19fe29c18df 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index e52bf5d01087..a6e2f6637188 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 6c5832c3921b..c136d53ffd4f 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 87d1f54b4560..d78d0803030d 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 1f951847fbdd..8e1ff9419283 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index e52bf5d01087..a6e2f6637188 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index b35148395ccb..1dc72cbbe06b 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 5753c9c6c0ea..4530b9b6808f 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index cba9960b7d3d..2b1e48a0e059 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From 7d8b96fdd8ea5a4217de7b5cb41147d7fe9d4cac Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 6 Aug 2015 12:19:04 -0700 Subject: [PATCH 03/18] Bumping manifests a=b2g-bump --- b2g/config/dolphin/sources.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index f19fe29c18df..43445e927132 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -138,7 +138,7 @@ - + From 43a96aab16f9068076dc3b977434af51ae2421d6 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 6 Aug 2015 12:57:09 -0700 Subject: [PATCH 04/18] Bumping gaia.json for 8 gaia revision(s) a=gaia-bump MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ======== https://hg.mozilla.org/integration/gaia-central/rev/7a5f84e60e78 Author: Ryan VanderMeulen Desc: Merge pull request #31271 from millermedeiros/1175744-disable-create-event-test Bug 1175744 - Disable intermittent calendar marionette create event test a=test-only ======== https://hg.mozilla.org/integration/gaia-central/rev/99b4e9b793b7 Author: Miller Medeiros Desc: Bug 1175744 - Disable intermittent calendar marionette create event test ======== https://hg.mozilla.org/integration/gaia-central/rev/5a053f16e492 Author: Yura Zenevich Desc: Merge pull request #31131 from yzen/bug-1184244 Bug 1184244 - ensuring that marionette-helper's waitForElementToDisap… ======== https://hg.mozilla.org/integration/gaia-central/rev/804b4eb2428e Author: Yura Zenevich Desc: Bug 1184244 - ensuring that marionette-helper's waitForElementToDisappear accounts for accessibility API. ======== https://hg.mozilla.org/integration/gaia-central/rev/b5cc98ad86b5 Author: Gareth Aye Desc: Merge pull request #31045 from gaye/bug-1181787-nodejs-app-html-util Bug 1181787 - Create nodejs util for parsing and transforming app html files r=rchien ======== https://hg.mozilla.org/integration/gaia-central/rev/31164178b8cb Author: gaye Desc: Bug 1181787 - Create nodejs util for parsing and transforming app html files ======== https://hg.mozilla.org/integration/gaia-central/rev/a7275bde1b2e Author: Gareth Aye Desc: Merge pull request #31266 from evanxd/bug-1191705 Bug 1191705 - Format the VERBOSE log r=gaye ======== https://hg.mozilla.org/integration/gaia-central/rev/f1727cdffd6c Author: Evan Tseng Desc: Bug 1191705 - Format the VERBOSE log r=gaye --- b2g/config/gaia.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index 337dbde5a969..b24cd1a77226 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -1,9 +1,9 @@ { "git": { - "git_revision": "24f8c8b69c37f8106355d92c58039f417e81fb10", + "git_revision": "682419bda6501c3d84ef116c9c51530eee99dbf6", "remote": "https://git.mozilla.org/releases/gaia.git", "branch": "" }, - "revision": "b8d0a7e33f0733b07e5686b485c7f3a9e54f4338", + "revision": "7a5f84e60e7859d0b6aa285c15c5133115341670", "repo_path": "integration/gaia-central" } From 0ee905d9fc4e27bad22f01d3fafb75626517b154 Mon Sep 17 00:00:00 2001 From: B2G Bumper Bot Date: Thu, 6 Aug 2015 12:58:50 -0700 Subject: [PATCH 05/18] Bumping manifests a=b2g-bump --- b2g/config/aries/sources.xml | 2 +- b2g/config/dolphin/sources.xml | 2 +- b2g/config/emulator-ics/sources.xml | 2 +- b2g/config/emulator-jb/sources.xml | 2 +- b2g/config/emulator-kk/sources.xml | 2 +- b2g/config/emulator-l/sources.xml | 2 +- b2g/config/emulator/sources.xml | 2 +- b2g/config/flame-kk/sources.xml | 2 +- b2g/config/nexus-4/sources.xml | 2 +- b2g/config/nexus-5-l/sources.xml | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml index c46989cf7601..67de9b66e03a 100644 --- a/b2g/config/aries/sources.xml +++ b/b2g/config/aries/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml index 43445e927132..d732fa4699fd 100644 --- a/b2g/config/dolphin/sources.xml +++ b/b2g/config/dolphin/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index a6e2f6637188..19814f8c46c3 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index c136d53ffd4f..0590f90573ae 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index d78d0803030d..59a1c8ee52c7 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml index 8e1ff9419283..e3b1b3f476de 100644 --- a/b2g/config/emulator-l/sources.xml +++ b/b2g/config/emulator-l/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index a6e2f6637188..19814f8c46c3 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml index 1dc72cbbe06b..66f66c482637 100644 --- a/b2g/config/flame-kk/sources.xml +++ b/b2g/config/flame-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml index 4530b9b6808f..de062c8adad5 100644 --- a/b2g/config/nexus-4/sources.xml +++ b/b2g/config/nexus-4/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml index 2b1e48a0e059..db0f49117ab3 100644 --- a/b2g/config/nexus-5-l/sources.xml +++ b/b2g/config/nexus-5-l/sources.xml @@ -15,7 +15,7 @@ - + From 1da14adfbdb0392b084109b86a0742ff831bd929 Mon Sep 17 00:00:00 2001 From: Sean Lin Date: Thu, 19 Mar 2015 15:48:28 +0800 Subject: [PATCH 06/18] Bug 1069230 - Presentation API implementation. Part 1 - WebIDL Bindings. r=smaug --- dom/apps/PermissionsTable.jsm | 8 +- dom/base/Navigator.cpp | 20 +++ dom/base/Navigator.h | 4 + dom/base/nsGkAtomList.h | 1 + dom/presentation/Presentation.cpp | 106 +++++++++++++++ dom/presentation/Presentation.h | 51 ++++++++ dom/presentation/PresentationSession.cpp | 128 +++++++++++++++++++ dom/presentation/PresentationSession.h | 55 ++++++++ dom/presentation/moz.build | 8 +- dom/webidl/Navigator.webidl | 5 + dom/webidl/Presentation.webidl | 64 ++++++++++ dom/webidl/PresentationAvailableEvent.webidl | 20 +++ dom/webidl/PresentationSession.webidl | 70 ++++++++++ dom/webidl/moz.build | 3 + layout/build/nsLayoutModule.cpp | 2 +- 15 files changed, 541 insertions(+), 4 deletions(-) create mode 100644 dom/presentation/Presentation.cpp create mode 100644 dom/presentation/Presentation.h create mode 100644 dom/presentation/PresentationSession.cpp create mode 100644 dom/presentation/PresentationSession.h create mode 100644 dom/webidl/Presentation.webidl create mode 100644 dom/webidl/PresentationAvailableEvent.webidl create mode 100644 dom/webidl/PresentationSession.webidl diff --git a/dom/apps/PermissionsTable.jsm b/dom/apps/PermissionsTable.jsm index effef51e699a..614943f05a82 100644 --- a/dom/apps/PermissionsTable.jsm +++ b/dom/apps/PermissionsTable.jsm @@ -566,7 +566,13 @@ this.PermissionsTable = { geolocation: { trusted: DENY_ACTION, privileged: DENY_ACTION, certified: ALLOW_ACTION - } + }, + "presentation": { + app: DENY_ACTION, + trusted: DENY_ACTION, + privileged: ALLOW_ACTION, + certified: ALLOW_ACTION + }, }; /** diff --git a/dom/base/Navigator.cpp b/dom/base/Navigator.cpp index d6f0d8027f98..de53e0a9cd7f 100644 --- a/dom/base/Navigator.cpp +++ b/dom/base/Navigator.cpp @@ -41,6 +41,7 @@ #include "mozilla/dom/InputPortManager.h" #include "mozilla/dom/MobileMessageManager.h" #include "mozilla/dom/Permissions.h" +#include "mozilla/dom/Presentation.h" #include "mozilla/dom/ServiceWorkerContainer.h" #include "mozilla/dom/Telephony.h" #include "mozilla/dom/Voicemail.h" @@ -215,6 +216,7 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Navigator) NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager) #endif NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeviceStorageAreaListener) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresentation) NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END @@ -334,6 +336,10 @@ Navigator::Invalidate() mTimeManager = nullptr; } + if (mPresentation) { + mPresentation = nullptr; + } + mServiceWorkerContainer = nullptr; #ifdef MOZ_EME @@ -2763,5 +2769,19 @@ Navigator::RequestMediaKeySystemAccess(const nsAString& aKeySystem, #endif +Presentation* +Navigator::GetPresentation(ErrorResult& aRv) +{ + if (!mPresentation) { + if (!mWindow) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + mPresentation = Presentation::Create(mWindow); + } + + return mPresentation; +} + } // namespace dom } // namespace mozilla diff --git a/dom/base/Navigator.h b/dom/base/Navigator.h index d4ef362310b7..b049572d0a95 100644 --- a/dom/base/Navigator.h +++ b/dom/base/Navigator.h @@ -97,6 +97,7 @@ class Voicemail; class TVManager; class InputPortManager; class DeviceStorageAreaListener; +class Presentation; namespace time { class TimeManager; @@ -268,6 +269,8 @@ public: system::AudioChannelManager* GetMozAudioChannelManager(ErrorResult& aRv); #endif // MOZ_AUDIO_CHANNEL_MANAGER + Presentation* GetPresentation(ErrorResult& aRv); + bool SendBeacon(const nsAString& aUrl, const Nullable& aData, ErrorResult& aRv); @@ -389,6 +392,7 @@ private: nsRefPtr mServiceWorkerContainer; nsCOMPtr mWindow; nsRefPtr mDeviceStorageAreaListener; + nsRefPtr mPresentation; // Hashtable for saving cached objects DoResolve created, so we don't create // the object twice if asked for it twice, whether due to use of "delete" or diff --git a/dom/base/nsGkAtomList.h b/dom/base/nsGkAtomList.h index 9fb241155efe..d1fcbaeb5562 100644 --- a/dom/base/nsGkAtomList.h +++ b/dom/base/nsGkAtomList.h @@ -683,6 +683,7 @@ GK_ATOM(onantennaavailablechange, "onantennaavailablechange") GK_ATOM(onAppCommand, "onAppCommand") GK_ATOM(onattributechanged, "onattributechanged") GK_ATOM(onaudioprocess, "onaudioprocess") +GK_ATOM(onavailablechange, "onavailablechange") GK_ATOM(onbeforecopy, "onbeforecopy") GK_ATOM(onbeforecut, "onbeforecut") GK_ATOM(onbeforepaste, "onbeforepaste") diff --git a/dom/presentation/Presentation.cpp b/dom/presentation/Presentation.cpp new file mode 100644 index 000000000000..cf19c8ee1dc4 --- /dev/null +++ b/dom/presentation/Presentation.cpp @@ -0,0 +1,106 @@ +/* -*- 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 "mozilla/dom/PresentationBinding.h" +#include "mozilla/dom/Promise.h" +#include "nsCycleCollectionParticipant.h" +#include "nsIPresentationDeviceManager.h" +#include "nsServiceManagerUtils.h" +#include "Presentation.h" +#include "PresentationSession.h" + +using namespace mozilla; +using namespace mozilla::dom; + +NS_IMPL_CYCLE_COLLECTION_CLASS(Presentation) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(Presentation, DOMEventTargetHelper) + NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mSession) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(Presentation, DOMEventTargetHelper) + tmp->Shutdown(); + NS_IMPL_CYCLE_COLLECTION_UNLINK(mSession) +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_ADDREF_INHERITED(Presentation, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(Presentation, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Presentation) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +/* static */ already_AddRefed +Presentation::Create(nsPIDOMWindow* aWindow) +{ + nsRefPtr presentation = new Presentation(aWindow); + return NS_WARN_IF(!presentation->Init()) ? nullptr : presentation.forget(); +} + +Presentation::Presentation(nsPIDOMWindow* aWindow) + : DOMEventTargetHelper(aWindow) + , mAvailable(false) +{ +} + +Presentation::~Presentation() +{ + Shutdown(); +} + +bool +Presentation::Init() +{ + // TODO: Register listener for |mAvailable| changes. + + return true; +} + +void Presentation::Shutdown() +{ + mSession = nullptr; + + // TODO: Unregister listener for |mAvailable| changes. +} + +/* virtual */ JSObject* +Presentation::WrapObject(JSContext* aCx, JS::Handle aGivenProto) +{ + return PresentationBinding::Wrap(aCx, this, aGivenProto); +} + +already_AddRefed +Presentation::StartSession(const nsAString& aUrl, + const Optional& aId, + ErrorResult& aRv) +{ + nsCOMPtr global = do_QueryInterface(GetOwner()); + if (NS_WARN_IF(!global)) { + aRv.Throw(NS_ERROR_UNEXPECTED); + return nullptr; + } + + nsRefPtr promise = Promise::Create(global, aRv); + if (NS_WARN_IF(aRv.Failed())) { + return nullptr; + } + + // TODO: Resolve/reject the promise. + + return promise.forget(); +} + +already_AddRefed +Presentation::GetSession() const +{ + nsRefPtr session = mSession; + return session.forget(); +} + +bool +Presentation::CachedAvailable() const +{ + return mAvailable; +} diff --git a/dom/presentation/Presentation.h b/dom/presentation/Presentation.h new file mode 100644 index 000000000000..2622aad5fbf7 --- /dev/null +++ b/dom/presentation/Presentation.h @@ -0,0 +1,51 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_Presentation_h +#define mozilla_dom_Presentation_h + +#include "mozilla/DOMEventTargetHelper.h" + +namespace mozilla { +namespace dom { + +class Promise; +class PresentationSession; + +class Presentation final : public DOMEventTargetHelper +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Presentation, + DOMEventTargetHelper) + + static already_AddRefed Create(nsPIDOMWindow* aWindow); + virtual JSObject* + WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + // WebIDL (public APIs) + already_AddRefed StartSession(const nsAString& aUrl, + const Optional& aId, + ErrorResult& aRv); + already_AddRefed GetSession() const; + bool CachedAvailable() const; + IMPL_EVENT_HANDLER(availablechange); + +private: + explicit Presentation(nsPIDOMWindow* aWindow); + ~Presentation(); + + bool Init(); + void Shutdown(); + + bool mAvailable; + nsRefPtr mSession; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_Presentation_h diff --git a/dom/presentation/PresentationSession.cpp b/dom/presentation/PresentationSession.cpp new file mode 100644 index 000000000000..a5d7934d8d3b --- /dev/null +++ b/dom/presentation/PresentationSession.cpp @@ -0,0 +1,128 @@ +/* -*- 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 "nsCycleCollectionParticipant.h" +#include "nsServiceManagerUtils.h" +#include "nsStringStream.h" +#include "PresentationSession.h" + +using namespace mozilla; +using namespace mozilla::dom; + +NS_IMPL_CYCLE_COLLECTION_CLASS(PresentationSession) + +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(PresentationSession, DOMEventTargetHelper) +NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END + +NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(PresentationSession, DOMEventTargetHelper) + tmp->Shutdown(); +NS_IMPL_CYCLE_COLLECTION_UNLINK_END + +NS_IMPL_ADDREF_INHERITED(PresentationSession, DOMEventTargetHelper) +NS_IMPL_RELEASE_INHERITED(PresentationSession, DOMEventTargetHelper) + +NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PresentationSession) +NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) + +PresentationSession::PresentationSession(nsPIDOMWindow* aWindow, + const nsAString& aId, + PresentationSessionState aState) + : DOMEventTargetHelper(aWindow) + , mId(aId) + , mState(aState) +{ +} + +/* virtual */ PresentationSession::~PresentationSession() +{ +} + +/* static */ already_AddRefed +PresentationSession::Create(nsPIDOMWindow* aWindow, + const nsAString& aId, + PresentationSessionState aState) +{ + nsRefPtr session = + new PresentationSession(aWindow, aId, aState); + return NS_WARN_IF(!session->Init()) ? nullptr : session.forget(); +} + +bool +PresentationSession::Init() +{ + if (NS_WARN_IF(mId.IsEmpty())) { + return false; + } + + // TODO: Register listener for session state changes. + + return true; +} + +void +PresentationSession::Shutdown() +{ + // TODO: Unregister listener for session state changes. +} + +/* virtual */ JSObject* +PresentationSession::WrapObject(JSContext* aCx, + JS::Handle aGivenProto) +{ + return PresentationSessionBinding::Wrap(aCx, this, aGivenProto); +} + +void +PresentationSession::GetId(nsAString& aId) const +{ + aId = mId; +} + +PresentationSessionState +PresentationSession::State() const +{ + // TODO: Dispatch event when the value of |mState| is changed. + return mState; +} + +void +PresentationSession::Send(const nsAString& aData, + ErrorResult& aRv) +{ + // Sending is not allowed if the session is not connected. + if (NS_WARN_IF(mState != PresentationSessionState::Connected)) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + nsresult rv; + nsCOMPtr stream = + do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv); + if(NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(NS_ERROR_NOT_AVAILABLE); + return; + } + + NS_ConvertUTF16toUTF8 msgString(aData); + rv = stream->SetData(msgString.BeginReading(), msgString.Length()); + if(NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(NS_ERROR_DOM_OPERATION_ERR); + return; + } + + // TODO: Send the message to the stream. +} + +void +PresentationSession::Close() +{ + // Closing does nothing if the session is already terminated. + if (NS_WARN_IF(mState == PresentationSessionState::Terminated)) { + return; + } + + // TODO: Terminate the socket. +} diff --git a/dom/presentation/PresentationSession.h b/dom/presentation/PresentationSession.h new file mode 100644 index 000000000000..e3bf54d326f2 --- /dev/null +++ b/dom/presentation/PresentationSession.h @@ -0,0 +1,55 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_PresentationSession_h +#define mozilla_dom_PresentationSession_h + +#include "mozilla/DOMEventTargetHelper.h" +#include "mozilla/dom/PresentationSessionBinding.h" + +namespace mozilla { +namespace dom { + +class PresentationSession final : public DOMEventTargetHelper +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PresentationSession, + DOMEventTargetHelper) + + static already_AddRefed + Create(nsPIDOMWindow* aWindow, + const nsAString& aId, + PresentationSessionState aState); + virtual JSObject* + WrapObject(JSContext* aCx, JS::Handle aGivenProto) override; + + // WebIDL (public APIs) + void GetId(nsAString& aId) const; + PresentationSessionState State() const; + void Send(const nsAString& aData, ErrorResult& aRv); + void Close(); + + IMPL_EVENT_HANDLER(statechange); + IMPL_EVENT_HANDLER(message); + +private: + explicit PresentationSession(nsPIDOMWindow* aWindow, + const nsAString& aId, + PresentationSessionState aState); + ~PresentationSession(); + + bool Init(); + void Shutdown(); + + nsString mId; + PresentationSessionState mState; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationSession_h diff --git a/dom/presentation/moz.build b/dom/presentation/moz.build index bf64b3d667ce..f3b7edc33612 100644 --- a/dom/presentation/moz.build +++ b/dom/presentation/moz.build @@ -9,12 +9,16 @@ DIRS += ['interfaces', 'provider'] XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini'] MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini'] -EXPORTS.mozilla.dom.presentation += [ +EXPORTS.mozilla.dom += [ + 'Presentation.h', 'PresentationDeviceManager.h', + 'PresentationSession.h', ] -SOURCES += [ +UNIFIED_SOURCES += [ + 'Presentation.cpp', 'PresentationDeviceManager.cpp', + 'PresentationSession.cpp', 'PresentationSessionRequest.cpp', ] diff --git a/dom/webidl/Navigator.webidl b/dom/webidl/Navigator.webidl index f82ad654c5e2..e3177895afc5 100644 --- a/dom/webidl/Navigator.webidl +++ b/dom/webidl/Navigator.webidl @@ -430,6 +430,11 @@ partial interface Navigator { readonly attribute InputPortManager inputPortManager; }; +partial interface Navigator { + [Throws, Pref="dom.presentation.enabled", CheckAnyPermissions="presentation", AvailableIn="PrivilegedApps"] + readonly attribute Presentation? presentation; +}; + #ifdef MOZ_EME partial interface Navigator { [Pref="media.eme.apiVisible", NewObject] diff --git a/dom/webidl/Presentation.webidl b/dom/webidl/Presentation.webidl new file mode 100644 index 000000000000..42163e48df12 --- /dev/null +++ b/dom/webidl/Presentation.webidl @@ -0,0 +1,64 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +[Pref="dom.presentation.enabled", + CheckAnyPermissions="presentation", + AvailableIn="PrivilegedApps"] +interface Presentation : EventTarget { + /* + * A requesting page use startSession() to start a new session, and the + * session will be returned with the promise. UA may show a prompt box with a + * list of available devices and ask the user to grant permission, choose a + * device, or cancel the operation. + * + * @url: The URL of presenting page. + * @sessionId: Optional. If it's not specified, a random alphanumeric value of + * at least 16 characters drawn from the character [A-Za-z0-9] is + * automatically generated as the id of the session. + * + * The promise is resolved when the presenting page is successfully loaded and + * the communication channel is established, i.e., the session state is + * "connected". + * + * The promise may be rejected duo to one of the following reasons: + * - "InternalError": Unexpected internal error occurs. + * - "NoDeviceAvailable": No available device. + * - "PermissionDenied": User dismiss the device prompt box. + * - "ControlChannelFailed": Failed to establish control channel. + * - "NoApplicationFound": app:// scheme is supported on Firefox OS, but no + * corresponding application is found on remote side. + * - "PageLoadTimeout": Presenting page takes too long to load. + * - "DataChannelFailed": Failed to establish data channel. + */ + [Throws] + Promise startSession(DOMString url, + optional DOMString sessionId); + + /* + * This attribute is only available on the presenting page. It should be + * created when loading the presenting page, and it's ready to be used after + * 'onload' event is dispatched. + */ + [Pure] + readonly attribute PresentationSession? session; + + /* + * Device availability. If there is more than one device discovered by UA, + * the value is |true|. Otherwise, its value should be |false|. + * + * UA triggers device discovery mechanism periodically and cache the latest + * result in this attribute. Thus, it may be out-of-date when we're not in + * discovery mode, however, it is still useful to give the developers an idea + * that whether there are devices nearby some time ago. + */ + readonly attribute boolean cachedAvailable; + + /* + * It is called when device availability changes. New value is dispatched with + * the event. + */ + attribute EventHandler onavailablechange; +}; diff --git a/dom/webidl/PresentationAvailableEvent.webidl b/dom/webidl/PresentationAvailableEvent.webidl new file mode 100644 index 000000000000..c992d93a207b --- /dev/null +++ b/dom/webidl/PresentationAvailableEvent.webidl @@ -0,0 +1,20 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +[Constructor(DOMString typeArg, + optional PresentationAvailableEventInit eventInitDict), + Pref="dom.presentation.enabled", + CheckAnyPermissions="presentation", + AvailableIn="PrivilegedApps"] +interface PresentationAvailableEvent : Event +{ + readonly attribute boolean available; +}; + +dictionary PresentationAvailableEventInit : EventInit +{ + boolean available = false; +}; diff --git a/dom/webidl/PresentationSession.webidl b/dom/webidl/PresentationSession.webidl new file mode 100644 index 000000000000..97d40dfce479 --- /dev/null +++ b/dom/webidl/PresentationSession.webidl @@ -0,0 +1,70 @@ +/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +enum PresentationSessionState +{ + // Existing presentation, and the communication channel is active. + "connected", + + // Existing presentation, but the communication channel is inactive. + "disconnected", + + // The presentation is nonexistent anymore. It could be terminated manually, + // or either requesting page or presenting page is no longer available. + "terminated" +}; + +[Pref="dom.presentation.enabled", + CheckAnyPermissions="presentation", + AvailableIn="PrivilegedApps"] +interface PresentationSession : EventTarget { + /* + * Unique id for all existing sessions. + */ + [Constant] + readonly attribute DOMString id; + + /* + * Please refer to PresentationSessionStateEvent.webidl for the declaration of + * PresentationSessionState. + * + * @value "connected", "disconnected", or "terminated". + */ + readonly attribute PresentationSessionState state; + + /* + * It is called when session state changes. New state is dispatched with the + * event. + */ + attribute EventHandler onstatechange; + + /* + * After a communication channel has been established between the requesting + * page and the presenting page, send() is called to send message out, and the + * event handler "onmessage" will be invoked on the remote side. + * + * This function only works when state equals "connected". + * + * @data: String literal-only for current implementation. + */ + [Throws] + void send(DOMString data); + + /* + * It is triggered when receiving messages. + */ + attribute EventHandler onmessage; + + /* + * Both the requesting page and the presenting page can close the session by + * calling terminate(). Then, the session is destroyed and its state is + * truned into "terminated". After getting into the state of "terminated", + * resumeSession() is incapable of re-establishing the connection. + * + * This function does nothing if the state has already been "terminated". + */ + void close(); +}; diff --git a/dom/webidl/moz.build b/dom/webidl/moz.build index e7ff1b390832..aefdc0b8cc78 100644 --- a/dom/webidl/moz.build +++ b/dom/webidl/moz.build @@ -365,7 +365,9 @@ WEBIDL_FILES = [ 'PopupBoxObject.webidl', 'Position.webidl', 'PositionError.webidl', + 'Presentation.webidl', 'PresentationDeviceInfoManager.webidl', + 'PresentationSession.webidl', 'ProcessingInstruction.webidl', 'ProfileTimelineMarker.webidl', 'Promise.webidl', @@ -783,6 +785,7 @@ GENERATED_EVENTS_WEBIDL_FILES = [ 'PluginCrashedEvent.webidl', 'PopStateEvent.webidl', 'PopupBlockedEvent.webidl', + 'PresentationAvailableEvent.webidl', 'ProgressEvent.webidl', 'RecordErrorEvent.webidl', 'ScrollViewChangeEvent.webidl', diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp index 4e753c27d89c..2b3da093daa2 100644 --- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -260,7 +260,7 @@ static void Shutdown(); #include "GMPService.h" -#include "mozilla/dom/presentation/PresentationDeviceManager.h" +#include "mozilla/dom/PresentationDeviceManager.h" #include "mozilla/TextInputProcessor.h" From ffb77b7af47631b20418ba512ab68ab57e04389f Mon Sep 17 00:00:00 2001 From: Sean Lin Date: Wed, 25 Mar 2015 19:47:56 +0800 Subject: [PATCH 07/18] Bug 1069230 - Presentation API implementation. Part 2 - Presentation service and listeners. r=smaug --- dom/presentation/PresentationService.cpp | 174 ++++++++++++++++++ dom/presentation/PresentationService.h | 41 +++++ dom/presentation/PresentationSessionInfo.h | 63 +++++++ dom/presentation/interfaces/moz.build | 2 + .../interfaces/nsIPresentationListener.idl | 34 ++++ .../interfaces/nsIPresentationService.idl | 112 +++++++++++ dom/presentation/moz.build | 3 + layout/build/nsLayoutModule.cpp | 9 + 8 files changed, 438 insertions(+) create mode 100644 dom/presentation/PresentationService.cpp create mode 100644 dom/presentation/PresentationService.h create mode 100644 dom/presentation/PresentationSessionInfo.h create mode 100644 dom/presentation/interfaces/nsIPresentationListener.idl create mode 100644 dom/presentation/interfaces/nsIPresentationService.idl diff --git a/dom/presentation/PresentationService.cpp b/dom/presentation/PresentationService.cpp new file mode 100644 index 000000000000..6679803ba2b7 --- /dev/null +++ b/dom/presentation/PresentationService.cpp @@ -0,0 +1,174 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/Services.h" +#include "nsIObserverService.h" +#include "nsIPresentationListener.h" +#include "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "PresentationService.h" + +using namespace mozilla; +using namespace mozilla::dom; + +NS_IMPL_ISUPPORTS(PresentationService, nsIPresentationService, nsIObserver) + +PresentationService::PresentationService() +{ +} + +PresentationService::~PresentationService() +{ +} + +bool +PresentationService::Init() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr obs = services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return false; + } + + // TODO: Add observers and get available devices. + + return true; +} + +NS_IMETHODIMP +PresentationService::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + // TODO: Handle device availability changes can call |NotifyAvailableChange|. + + return NS_OK; +} + +void +PresentationService::NotifyAvailableChange(bool aIsAvailable) +{ + nsTObserverArray >::ForwardIterator iter(mListeners); + while (iter.HasMore()) { + nsCOMPtr listener = iter.GetNext(); + NS_WARN_IF(NS_FAILED(listener->NotifyAvailableChange(aIsAvailable))); + } +} + +NS_IMETHODIMP +PresentationService::StartSession(const nsAString& aUrl, + const nsAString& aSessionId, + const nsAString& aOrigin, + nsIPresentationServiceCallback* aCallback) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aCallback); + MOZ_ASSERT(!aSessionId.IsEmpty()); + + // TODO: Reply the callback. + + return NS_OK; +} + +NS_IMETHODIMP +PresentationService::SendSessionMessage(const nsAString& aSessionId, + nsIInputStream* aStream) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aStream); + MOZ_ASSERT(!aSessionId.IsEmpty()); + + // TODO: Send input stream to the session. + + return NS_OK; +} + +NS_IMETHODIMP +PresentationService::Terminate(const nsAString& aSessionId) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!aSessionId.IsEmpty()); + + // TODO: Terminate the session. + + return NS_OK; +} + +NS_IMETHODIMP +PresentationService::RegisterListener(nsIPresentationListener* aListener) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_WARN_IF(mListeners.Contains(aListener))) { + return NS_OK; + } + + mListeners.AppendElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +PresentationService::UnregisterListener(nsIPresentationListener* aListener) +{ + MOZ_ASSERT(NS_IsMainThread()); + + mListeners.RemoveElement(aListener); + return NS_OK; +} + +NS_IMETHODIMP +PresentationService::RegisterSessionListener(const nsAString& aSessionId, + nsIPresentationSessionListener* aListener) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aListener); + + PresentationSessionInfo* info = mSessionInfo.Get(aSessionId); + if (NS_WARN_IF(!info)) { + return NS_ERROR_NOT_AVAILABLE; + } + + info->SetListener(aListener); + return NS_OK; +} + +NS_IMETHODIMP +PresentationService::UnregisterSessionListener(const nsAString& aSessionId) +{ + MOZ_ASSERT(NS_IsMainThread()); + + PresentationSessionInfo* info = mSessionInfo.Get(aSessionId); + if (info) { + info->SetListener(nullptr); + } + return NS_OK; +} + +NS_IMETHODIMP +PresentationService::GetExistentSessionIdAtLaunch(nsAString& aSessionId) +{ + // TODO: Return the value based on it's a sender or a receiver. + + return NS_OK; +} + +NS_IMETHODIMP +PresentationService::NotifyReceiverReady(const nsAString& aSessionId) +{ + // TODO: Notify the correspondent session info. + + return NS_OK; +} + +already_AddRefed +NS_CreatePresentationService() +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsCOMPtr service = new PresentationService(); + return NS_WARN_IF(!static_cast(service.get())->Init()) ? + nullptr : service.forget(); +} diff --git a/dom/presentation/PresentationService.h b/dom/presentation/PresentationService.h new file mode 100644 index 000000000000..b72eb49ce14f --- /dev/null +++ b/dom/presentation/PresentationService.h @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_PresentationService_h +#define mozilla_dom_PresentationService_h + +#include "nsClassHashtable.h" +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsTObserverArray.h" +#include "PresentationSessionInfo.h" + +namespace mozilla { +namespace dom { + +class PresentationService final : public nsIPresentationService + , public nsIObserver +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIOBSERVER + NS_DECL_NSIPRESENTATIONSERVICE + + PresentationService(); + bool Init(); + +private: + ~PresentationService(); + void NotifyAvailableChange(bool aIsAvailable); + + nsClassHashtable mSessionInfo; + nsTObserverArray > mListeners; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationService_h diff --git a/dom/presentation/PresentationSessionInfo.h b/dom/presentation/PresentationSessionInfo.h new file mode 100644 index 000000000000..c9d6f250516e --- /dev/null +++ b/dom/presentation/PresentationSessionInfo.h @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 "mozilla/nsRefPtr.h" +#include "nsCOMPtr.h" +#include "nsIPresentationListener.h" +#include "nsIPresentationService.h" +#include "nsString.h" + +namespace mozilla { +namespace dom { + +class PresentationSessionInfo +{ +public: + PresentationSessionInfo(const nsAString& aUrl, + const nsAString& aSessionId, + nsIPresentationServiceCallback* aCallback) + : mUrl(aUrl) + , mSessionId(aSessionId) + , mCallback(aCallback) + { + MOZ_ASSERT(!mUrl.IsEmpty()); + MOZ_ASSERT(!mSessionId.IsEmpty()); + } + + const nsAString& GetUrl() const + { + return mUrl; + } + + const nsAString& GetSessionId() const + { + return mSessionId; + } + + void SetCallback(nsIPresentationServiceCallback* aCallback) + { + mCallback = aCallback; + } + + void SetListener(nsIPresentationSessionListener* aListener) + { + mListener = aListener; + } + +private: + nsString mUrl; + nsString mSessionId; + nsCOMPtr mCallback; + nsCOMPtr mListener; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationSessionInfo_h diff --git a/dom/presentation/interfaces/moz.build b/dom/presentation/interfaces/moz.build index 08c75a8a8c84..6911db4782d8 100644 --- a/dom/presentation/interfaces/moz.build +++ b/dom/presentation/interfaces/moz.build @@ -10,6 +10,8 @@ XPIDL_SOURCES += [ 'nsIPresentationDeviceManager.idl', 'nsIPresentationDevicePrompt.idl', 'nsIPresentationDeviceProvider.idl', + 'nsIPresentationListener.idl', + 'nsIPresentationService.idl', 'nsIPresentationSessionRequest.idl', 'nsITCPPresentationServer.idl', ] diff --git a/dom/presentation/interfaces/nsIPresentationListener.idl b/dom/presentation/interfaces/nsIPresentationListener.idl new file mode 100644 index 000000000000..dfacdbbf9df9 --- /dev/null +++ b/dom/presentation/interfaces/nsIPresentationListener.idl @@ -0,0 +1,34 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +[scriptable, uuid(0105f837-4279-4715-9d5b-2dc3f8b65353)] +interface nsIPresentationListener : nsISupports +{ + /* + * Called when device availability changes. + */ + void notifyAvailableChange(in bool available); +}; + +[scriptable, uuid(3b9ae71f-2905-4969-9117-101627c1c2ea)] +interface nsIPresentationSessionListener : nsISupports +{ + const unsigned short STATE_CONNECTED = 0; + const unsigned short STATE_DISCONNECTED = 1; + const unsigned short STATE_TERMINATED = 2; + + /* + * Called when session state changes. + */ + void notifyStateChange(in DOMString sessionId, + in unsigned short state); + + /* + * Called when receive messages. + */ + void notifyMessage(in DOMString sessionId, + in ACString data); +}; diff --git a/dom/presentation/interfaces/nsIPresentationService.idl b/dom/presentation/interfaces/nsIPresentationService.idl new file mode 100644 index 000000000000..48a12e9b2a05 --- /dev/null +++ b/dom/presentation/interfaces/nsIPresentationService.idl @@ -0,0 +1,112 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; +interface nsIPresentationListener; +interface nsIPresentationSessionListener; + +%{C++ +#define PRESENTATION_SERVICE_CID \ + { 0x1d9bb10c, 0xc0ab, 0x4fe8, \ + { 0x9e, 0x4f, 0x40, 0x58, 0xb8, 0x51, 0x98, 0x32 } } +#define PRESENTATION_SERVICE_CONTRACTID \ + "@mozilla.org/presentation/presentationservice;1" +%} + +[scriptable, uuid(12073206-0065-4b10-9488-a6eb9b23e65b)] +interface nsIPresentationServiceCallback : nsISupports +{ + /* + * Called when the operation succeeds. + */ + void notifySuccess(); + + /* + * Called when the operation fails. + * + * @param error: error message. + */ + void notifyError(in nsresult error); +}; + +[scriptable, uuid(5801efd9-9dba-4af7-8be9-8fc97c2d54a6)] +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. + * @param origin: The url of requesting page. + * @param callback: Invoke the callback when the operation is completed. + * NotifySuccess() is called with |id| if a session is + * established successfully with the selected device. + * Otherwise, NotifyError() is called with a error message. + */ + void startSession(in DOMString url, + in DOMString sessionId, + in DOMString origin, + in nsIPresentationServiceCallback callback); + + /* + * Send the message wrapped with an input stream to the session. + * + * @param sessionId: An ID to identify presentation session. + * @param stream: The message is converted to an input stream. + */ + void sendSessionMessage(in DOMString sessionId, + in nsIInputStream stream); + + /* + * Terminate the session. + * + * @param sessionId: An ID to identify presentation session. + */ + void terminate(in DOMString sessionId); + + /* + * Register a listener. Must be called from the main thread. + * + * @param listener: The listener to register. + */ + void registerListener(in nsIPresentationListener listener); + + /* + * Unregister a listener. Must be called from the main thread. + * @param listener: The listener to unregister. + */ + void unregisterListener(in nsIPresentationListener listener); + + /* + * Register a session listener. Must be called from the main thread. + * + * @param sessionId: An ID to identify presentation session. + * @param listener: The listener to register. + */ + void registerSessionListener(in DOMString sessionId, + in nsIPresentationSessionListener listener); + + /* + * 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. + */ + DOMString getExistentSessionIdAtLaunch(); + + /* + * Notify the receiver page is ready for presentation use. + * + * @param sessionId: An ID to identify presentation session. + */ + void notifyReceiverReady(in DOMString sessionId); +}; diff --git a/dom/presentation/moz.build b/dom/presentation/moz.build index f3b7edc33612..1f68e5116bc5 100644 --- a/dom/presentation/moz.build +++ b/dom/presentation/moz.build @@ -12,12 +12,15 @@ MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini'] EXPORTS.mozilla.dom += [ 'Presentation.h', 'PresentationDeviceManager.h', + 'PresentationService.h', 'PresentationSession.h', + 'PresentationSessionInfo.h', ] UNIFIED_SOURCES += [ 'Presentation.cpp', 'PresentationDeviceManager.cpp', + 'PresentationService.cpp', 'PresentationSession.cpp', 'PresentationSessionRequest.cpp', ] diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp index 2b3da093daa2..2af3ab0f2c4e 100644 --- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -240,6 +240,7 @@ static void Shutdown(); #include "mozilla/dom/time/TimeService.h" #include "StreamingProtocolService.h" +#include "nsIPresentationService.h" #include "nsITelephonyService.h" #include "nsIVoicemailService.h" @@ -293,6 +294,8 @@ using mozilla::gmp::GeckoMediaPluginService; #define PRESENTATION_DEVICE_MANAGER_CID \ { 0xe1e79dec, 0x4085, 0x4994, { 0xac, 0x5b, 0x74, 0x4b, 0x01, 0x66, 0x97, 0xe6 } } +already_AddRefed NS_CreatePresentationService(); + // Factory Constructor NS_GENERIC_FACTORY_CONSTRUCTOR(txMozillaXSLTProcessor) NS_GENERIC_FACTORY_CONSTRUCTOR(XPathEvaluator) @@ -401,6 +404,8 @@ NS_GENERIC_FACTORY_CONSTRUCTOR(TextInputProcessor) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(FakeInputPortService, InputPortServiceFactory::CreateFakeInputPortService) NS_GENERIC_FACTORY_CONSTRUCTOR(InputPortData) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIPresentationService, + NS_CreatePresentationService) //----------------------------------------------------------------------------- static bool gInitialized = false; @@ -857,6 +862,7 @@ NS_DEFINE_NAMED_CID(INPUTPORT_DATA_CID); NS_DEFINE_NAMED_CID(GECKO_MEDIA_PLUGIN_SERVICE_CID); +NS_DEFINE_NAMED_CID(PRESENTATION_SERVICE_CID); NS_DEFINE_NAMED_CID(PRESENTATION_DEVICE_MANAGER_CID); NS_DEFINE_NAMED_CID(TEXT_INPUT_PROCESSOR_CID); @@ -1155,6 +1161,7 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = { { &kTV_TUNER_DATA_CID, false, nullptr, TVTunerDataConstructor }, { &kTV_CHANNEL_DATA_CID, false, nullptr, TVChannelDataConstructor }, { &kTV_PROGRAM_DATA_CID, false, nullptr, TVProgramDataConstructor }, + { &kPRESENTATION_SERVICE_CID, false, nullptr, nsIPresentationServiceConstructor }, { &kPRESENTATION_DEVICE_MANAGER_CID, false, nullptr, PresentationDeviceManagerConstructor }, { &kTEXT_INPUT_PROCESSOR_CID, false, nullptr, TextInputProcessorConstructor }, { &kFAKE_INPUTPORT_SERVICE_CID, false, nullptr, FakeInputPortServiceConstructor }, @@ -1324,6 +1331,7 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = { { "@mozilla.org/gecko-media-plugin-service;1", &kGECKO_MEDIA_PLUGIN_SERVICE_CID }, { NS_MOBILE_CONNECTION_SERVICE_CONTRACTID, &kNS_MOBILE_CONNECTION_SERVICE_CID }, { NS_VOICEMAIL_SERVICE_CONTRACTID, &kNS_VOICEMAIL_SERVICE_CID }, + { PRESENTATION_SERVICE_CONTRACTID, &kPRESENTATION_SERVICE_CID }, { PRESENTATION_DEVICE_MANAGER_CONTRACTID, &kPRESENTATION_DEVICE_MANAGER_CID }, { "@mozilla.org/text-input-processor;1", &kTEXT_INPUT_PROCESSOR_CID }, { FAKE_INPUTPORT_SERVICE_CONTRACTID, &kFAKE_INPUTPORT_SERVICE_CID }, @@ -1353,6 +1361,7 @@ static const mozilla::Module::CategoryEntry kLayoutCategories[] = { { "profile-after-change", "Bluetooth Service", BLUETOOTHSERVICE_CONTRACTID }, #endif { "profile-after-change", "PresentationDeviceManager", PRESENTATION_DEVICE_MANAGER_CONTRACTID }, + { "profile-after-change", "PresentationService", PRESENTATION_SERVICE_CONTRACTID }, { "idle-daily", "ServiceWorker Periodic Updater", SERVICEWORKERPERIODICUPDATER_CONTRACTID }, { nullptr } }; From 638b68e1d3407b2dd235a7a46730329153ae70cf Mon Sep 17 00:00:00 2001 From: Sean Lin Date: Thu, 26 Mar 2015 11:16:21 +0800 Subject: [PATCH 08/18] Bug 1069230 - Presentation API implementation. Part 3 - IPC. r=smaug --- dom/ipc/ContentChild.cpp | 24 ++ dom/ipc/ContentChild.h | 5 + dom/ipc/ContentParent.cpp | 23 ++ dom/ipc/ContentParent.h | 4 + dom/ipc/PContent.ipdl | 11 + dom/presentation/PresentationService.cpp | 16 +- dom/presentation/PresentationService.h | 2 +- dom/presentation/ipc/PPresentation.ipdl | 68 +++++ .../ipc/PPresentationRequest.ipdl | 21 ++ dom/presentation/ipc/PresentationChild.cpp | 130 +++++++++ dom/presentation/ipc/PresentationChild.h | 75 +++++ .../ipc/PresentationIPCService.cpp | 200 ++++++++++++++ dom/presentation/ipc/PresentationIPCService.h | 48 ++++ dom/presentation/ipc/PresentationParent.cpp | 257 ++++++++++++++++++ dom/presentation/ipc/PresentationParent.h | 95 +++++++ dom/presentation/moz.build | 11 + 16 files changed, 986 insertions(+), 4 deletions(-) create mode 100644 dom/presentation/ipc/PPresentation.ipdl create mode 100644 dom/presentation/ipc/PPresentationRequest.ipdl create mode 100644 dom/presentation/ipc/PresentationChild.cpp create mode 100644 dom/presentation/ipc/PresentationChild.h create mode 100644 dom/presentation/ipc/PresentationIPCService.cpp create mode 100644 dom/presentation/ipc/PresentationIPCService.h create mode 100644 dom/presentation/ipc/PresentationParent.cpp create mode 100644 dom/presentation/ipc/PresentationParent.h diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index 21a3cd023126..eaf0564d818a 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -171,6 +171,7 @@ #include "mozilla/dom/FileSystemTaskBase.h" #include "mozilla/dom/bluetooth/PBluetoothChild.h" #include "mozilla/dom/PFMRadioChild.h" +#include "mozilla/dom/PPresentationChild.h" #include "mozilla/ipc/InputStreamUtils.h" #ifdef MOZ_WEBSPEECH @@ -1382,6 +1383,29 @@ ContentChild::SendPBlobConstructor(PBlobChild* aActor, return PContentChild::SendPBlobConstructor(aActor, aParams); } +PPresentationChild* +ContentChild::AllocPPresentationChild() +{ + NS_NOTREACHED("We should never be manually allocating PPresentationChild actors"); + return nullptr; +} + +bool +ContentChild::DeallocPPresentationChild(PPresentationChild* aActor) +{ + delete aActor; + return true; +} + +bool +ContentChild::RecvNotifyPresentationReceiverLaunched(PBrowserChild* aIframe, + const nsString& aSessionId) +{ + // TODO Listen to |nsIWebProgressListener| state changes for this frame. + + return true; +} + PCrashReporterChild* ContentChild::AllocPCrashReporterChild(const mozilla::dom::NativeThreadId& id, const uint32_t& processType) diff --git a/dom/ipc/ContentChild.h b/dom/ipc/ContentChild.h index afd38b29c1eb..1eb1e69a8f5d 100644 --- a/dom/ipc/ContentChild.h +++ b/dom/ipc/ContentChild.h @@ -272,6 +272,11 @@ 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 PAsmJSCacheEntryChild* AllocPAsmJSCacheEntryChild( const asmjscache::OpenMode& aOpenMode, const asmjscache::WriteParams& aWriteParams, diff --git a/dom/ipc/ContentParent.cpp b/dom/ipc/ContentParent.cpp index ff2aa963ed5c..79309ad2d37e 100755 --- a/dom/ipc/ContentParent.cpp +++ b/dom/ipc/ContentParent.cpp @@ -57,6 +57,8 @@ #include "mozilla/dom/mobileconnection/MobileConnectionParent.h" #include "mozilla/dom/mobilemessage/SmsParent.h" #include "mozilla/dom/power/PowerManagerService.h" +#include "mozilla/dom/PresentationParent.h" +#include "mozilla/dom/PPresentationParent.h" #include "mozilla/dom/quota/QuotaManager.h" #include "mozilla/dom/telephony/TelephonyParent.h" #include "mozilla/dom/time/DateCacheCleaner.h" @@ -3890,6 +3892,27 @@ ContentParent::DeallocPFMRadioParent(PFMRadioParent* aActor) #endif } +PPresentationParent* +ContentParent::AllocPPresentationParent() +{ + nsRefPtr actor = new PresentationParent(); + return actor.forget().take(); +} + +bool +ContentParent::DeallocPPresentationParent(PPresentationParent* aActor) +{ + nsRefPtr actor = + dont_AddRef(static_cast(aActor)); + return true; +} + +bool +ContentParent::RecvPPresentationConstructor(PPresentationParent* aActor) +{ + return static_cast(aActor)->Init(); +} + asmjscache::PAsmJSCacheEntryParent* ContentParent::AllocPAsmJSCacheEntryParent( const asmjscache::OpenMode& aOpenMode, diff --git a/dom/ipc/ContentParent.h b/dom/ipc/ContentParent.h index f55d6131d9a5..332bfd042783 100644 --- a/dom/ipc/ContentParent.h +++ b/dom/ipc/ContentParent.h @@ -662,6 +662,10 @@ private: virtual PFMRadioParent* AllocPFMRadioParent() override; virtual bool DeallocPFMRadioParent(PFMRadioParent* aActor) override; + virtual PPresentationParent* AllocPPresentationParent() override; + virtual bool DeallocPPresentationParent(PPresentationParent* aActor) override; + virtual bool RecvPPresentationConstructor(PPresentationParent* aActor) override; + virtual PAsmJSCacheEntryParent* AllocPAsmJSCacheEntryParent( const asmjscache::OpenMode& aOpenMode, const asmjscache::WriteParams& aWriteParams, diff --git a/dom/ipc/PContent.ipdl b/dom/ipc/PContent.ipdl index 4e12c18afb8c..29ab5cedda2e 100644 --- a/dom/ipc/PContent.ipdl +++ b/dom/ipc/PContent.ipdl @@ -51,6 +51,7 @@ include protocol PVoicemail; include protocol PJavaScript; include protocol PRemoteSpellcheckEngine; include protocol PWebrtcGlobal; +include protocol PPresentation; include DOMTypes; include JavaScriptTypes; include InputStreamParams; @@ -452,6 +453,7 @@ prio(normal upto urgent) sync protocol PContent manages PJavaScript; manages PRemoteSpellcheckEngine; manages PWebrtcGlobal; + manages PPresentation; both: // Depending on exactly how the new browser is being created, it might be @@ -654,6 +656,13 @@ child: * occurred. */ async TestGraphicsDeviceReset(uint32_t aReason); + + /** + * Notify the child that presentation receiver has been launched with the + * correspondent iframe. + */ + async NotifyPresentationReceiverLaunched(PBrowser aIframe, nsString aSessionId); + parent: /** * Tell the content process some attributes of itself. This is @@ -777,6 +786,8 @@ parent: PAsmJSCacheEntry(OpenMode openMode, WriteParams write, Principal principal); PWebrtcGlobal(); + + PPresentation(); // Services remoting diff --git a/dom/presentation/PresentationService.cpp b/dom/presentation/PresentationService.cpp index 6679803ba2b7..ab915b7a4bd7 100644 --- a/dom/presentation/PresentationService.cpp +++ b/dom/presentation/PresentationService.cpp @@ -3,11 +3,13 @@ * 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 "ipc/PresentationIPCService.h" #include "mozilla/Services.h" #include "nsIObserverService.h" #include "nsIPresentationListener.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" +#include "nsXULAppAPI.h" #include "PresentationService.h" using namespace mozilla; @@ -168,7 +170,15 @@ NS_CreatePresentationService() { MOZ_ASSERT(NS_IsMainThread()); - nsCOMPtr service = new PresentationService(); - return NS_WARN_IF(!static_cast(service.get())->Init()) ? - nullptr : service.forget(); + nsCOMPtr service; + if (XRE_GetProcessType() == GeckoProcessType_Content) { + service = new mozilla::dom::PresentationIPCService(); + } else { + service = new PresentationService(); + if (NS_WARN_IF(!static_cast(service.get())->Init())) { + return nullptr; + } + } + + return service.forget(); } diff --git a/dom/presentation/PresentationService.h b/dom/presentation/PresentationService.h index b72eb49ce14f..1e8a75c3d008 100644 --- a/dom/presentation/PresentationService.h +++ b/dom/presentation/PresentationService.h @@ -32,7 +32,7 @@ private: void NotifyAvailableChange(bool aIsAvailable); nsClassHashtable mSessionInfo; - nsTObserverArray > mListeners; + nsTObserverArray> mListeners; }; } // namespace dom diff --git a/dom/presentation/ipc/PPresentation.ipdl b/dom/presentation/ipc/PPresentation.ipdl new file mode 100644 index 000000000000..8d362869c244 --- /dev/null +++ b/dom/presentation/ipc/PPresentation.ipdl @@ -0,0 +1,68 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* 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 protocol PContent; +include protocol PPresentationRequest; + +include InputStreamParams; + +namespace mozilla { +namespace dom { + +struct StartSessionRequest +{ + nsString url; + nsString sessionId; + nsString origin; +}; + +struct SendSessionMessageRequest +{ + nsString sessionId; + InputStreamParams data; +}; + +struct TerminateRequest +{ + nsString sessionId; +}; + +union PresentationRequest +{ + StartSessionRequest; + SendSessionMessageRequest; + TerminateRequest; +}; + +sync protocol PPresentation +{ + manager PContent; + manages PPresentationRequest; + +child: + NotifyAvailableChange(bool aAvailable); + NotifySessionStateChange(nsString aSessionId, uint16_t aState); + NotifyMessage(nsString aSessionId, nsCString aData); + +parent: + __delete__(); + + RegisterHandler(); + UnregisterHandler(); + + RegisterSessionHandler(nsString aSessionId); + UnregisterSessionHandler(nsString aSessionId); + + PPresentationRequest(PresentationRequest aRequest); + + sync GetExistentSessionIdAtLaunch() + returns (nsString aSessionId); + + NotifyReceiverReady(nsString aSessionId); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/presentation/ipc/PPresentationRequest.ipdl b/dom/presentation/ipc/PPresentationRequest.ipdl new file mode 100644 index 000000000000..573e6bae8df2 --- /dev/null +++ b/dom/presentation/ipc/PPresentationRequest.ipdl @@ -0,0 +1,21 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=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/. */ + +include protocol PPresentation; + +namespace mozilla { +namespace dom { + +sync protocol PPresentationRequest +{ + manager PPresentation; + +child: + __delete__(nsresult result); +}; + +} // namespace dom +} // namespace mozilla diff --git a/dom/presentation/ipc/PresentationChild.cpp b/dom/presentation/ipc/PresentationChild.cpp new file mode 100644 index 000000000000..53329dc6a954 --- /dev/null +++ b/dom/presentation/ipc/PresentationChild.cpp @@ -0,0 +1,130 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=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/. */ + +#include "mozilla/StaticPtr.h" +#include "PresentationChild.h" +#include "PresentationIPCService.h" +#include "nsThreadUtils.h" + +using namespace mozilla; +using namespace mozilla::dom; + +/* + * Implementation of PresentationChild + */ + +PresentationChild::PresentationChild(PresentationIPCService* aService) + : mActorDestroyed(false) + , mService(aService) +{ + MOZ_ASSERT(mService); + + MOZ_COUNT_CTOR(PresentationChild); +} + +PresentationChild::~PresentationChild() +{ + MOZ_COUNT_DTOR(PresentationChild); + + if (!mActorDestroyed) { + Send__delete__(this); + } + mService = nullptr; +} + +void +PresentationChild::ActorDestroy(ActorDestroyReason aWhy) +{ + mActorDestroyed = true; + mService->NotifyPresentationChildDestroyed(); + mService = nullptr; +} + +PPresentationRequestChild* +PresentationChild::AllocPPresentationRequestChild(const PresentationRequest& aRequest) +{ + NS_NOTREACHED("We should never be manually allocating PPresentationRequestChild actors"); + return nullptr; +} + +bool +PresentationChild::DeallocPPresentationRequestChild(PPresentationRequestChild* aActor) +{ + delete aActor; + return true; +} + +bool +PresentationChild::RecvNotifyAvailableChange(const bool& aAvailable) +{ + if (mService) { + NS_WARN_IF(NS_FAILED(mService->NotifyAvailableChange(aAvailable))); + } + return true; +} + +bool +PresentationChild::RecvNotifySessionStateChange(const nsString& aSessionId, + const uint16_t& aState) +{ + if (mService) { + NS_WARN_IF(NS_FAILED(mService->NotifySessionStateChange(aSessionId, aState))); + } + return true; +} + +bool +PresentationChild::RecvNotifyMessage(const nsString& aSessionId, + const nsCString& aData) +{ + if (mService) { + NS_WARN_IF(NS_FAILED(mService->NotifyMessage(aSessionId, aData))); + } + return true; +} + +/* + * Implementation of PresentationRequestChild + */ + +PresentationRequestChild::PresentationRequestChild(nsIPresentationServiceCallback* aCallback) + : mActorDestroyed(false) + , mCallback(aCallback) +{ + MOZ_COUNT_CTOR(PresentationRequestChild); +} + +PresentationRequestChild::~PresentationRequestChild() +{ + MOZ_COUNT_DTOR(PresentationRequestChild); + + mCallback = nullptr; +} + +void +PresentationRequestChild::ActorDestroy(ActorDestroyReason aWhy) +{ + mActorDestroyed = true; + mCallback = nullptr; +} + +bool +PresentationRequestChild::Recv__delete__(const nsresult& aResult) +{ + if (mActorDestroyed) { + return true; + } + + if (mCallback) { + if (NS_SUCCEEDED(aResult)) { + NS_WARN_IF(NS_FAILED(mCallback->NotifySuccess())); + } else { + NS_WARN_IF(NS_FAILED(mCallback->NotifyError(aResult))); + } + } + + return true; +} diff --git a/dom/presentation/ipc/PresentationChild.h b/dom/presentation/ipc/PresentationChild.h new file mode 100644 index 000000000000..8484fc190414 --- /dev/null +++ b/dom/presentation/ipc/PresentationChild.h @@ -0,0 +1,75 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=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_PresentationChild_h +#define mozilla_dom_PresentationChild_h + +#include "mozilla/dom/PPresentationChild.h" +#include "mozilla/dom/PPresentationRequestChild.h" + +class nsIPresentationServiceCallback; + +namespace mozilla { +namespace dom { + +class PresentationIPCService; + +class PresentationChild final : public PPresentationChild +{ +public: + explicit PresentationChild(PresentationIPCService* aService); + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual PPresentationRequestChild* + AllocPPresentationRequestChild(const PresentationRequest& aRequest) override; + + virtual bool + DeallocPPresentationRequestChild(PPresentationRequestChild* aActor) override; + + virtual bool + RecvNotifyAvailableChange(const bool& aAvailable) override; + + virtual bool + RecvNotifySessionStateChange(const nsString& aSessionId, + const uint16_t& aState) override; + + virtual bool + RecvNotifyMessage(const nsString& aSessionId, + const nsCString& aData) override; + +private: + virtual ~PresentationChild(); + + bool mActorDestroyed; + nsRefPtr mService; +}; + +class PresentationRequestChild final : public PPresentationRequestChild +{ + friend class PresentationChild; + +public: + explicit PresentationRequestChild(nsIPresentationServiceCallback* aCallback); + + virtual void + ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool + Recv__delete__(const nsresult& aResult) override; + +private: + virtual ~PresentationRequestChild(); + + bool mActorDestroyed; + nsCOMPtr mCallback; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationChild_h diff --git a/dom/presentation/ipc/PresentationIPCService.cpp b/dom/presentation/ipc/PresentationIPCService.cpp new file mode 100644 index 000000000000..a9f4571c7605 --- /dev/null +++ b/dom/presentation/ipc/PresentationIPCService.cpp @@ -0,0 +1,200 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "mozilla/dom/ContentChild.h" +#include "mozilla/dom/PPresentation.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsIPresentationListener.h" +#include "PresentationChild.h" +#include "PresentationIPCService.h" + +using namespace mozilla; +using namespace mozilla::dom; + +namespace { + +PresentationChild* sPresentationChild; + +} // anonymous + +NS_IMPL_ISUPPORTS(PresentationIPCService, nsIPresentationService) + +PresentationIPCService::PresentationIPCService() +{ + ContentChild* contentChild = ContentChild::GetSingleton(); + if (NS_WARN_IF(!contentChild)) { + return; + } + sPresentationChild = new PresentationChild(this); + NS_WARN_IF(!contentChild->SendPPresentationConstructor(sPresentationChild)); +} + +/* virtual */ +PresentationIPCService::~PresentationIPCService() +{ + mListeners.Clear(); + mSessionListeners.Clear(); + sPresentationChild = nullptr; +} + +NS_IMETHODIMP +PresentationIPCService::StartSession(const nsAString& aUrl, + const nsAString& aSessionId, + const nsAString& aOrigin, + nsIPresentationServiceCallback* aCallback) +{ + return SendRequest(aCallback, + StartSessionRequest(nsAutoString(aUrl), nsAutoString(aSessionId), nsAutoString(aOrigin))); +} + +NS_IMETHODIMP +PresentationIPCService::SendSessionMessage(const nsAString& aSessionId, + nsIInputStream* aStream) +{ + MOZ_ASSERT(!aSessionId.IsEmpty()); + MOZ_ASSERT(aStream); + + mozilla::ipc::OptionalInputStreamParams stream; + nsTArray fds; + SerializeInputStream(aStream, stream, fds); + MOZ_ASSERT(fds.IsEmpty()); + + return SendRequest(nullptr, SendSessionMessageRequest(nsAutoString(aSessionId), stream)); +} + +NS_IMETHODIMP +PresentationIPCService::Terminate(const nsAString& aSessionId) +{ + MOZ_ASSERT(!aSessionId.IsEmpty()); + + return SendRequest(nullptr, TerminateRequest(nsAutoString(aSessionId))); +} + +nsresult +PresentationIPCService::SendRequest(nsIPresentationServiceCallback* aCallback, + const PresentationRequest& aRequest) +{ + if (sPresentationChild) { + PresentationRequestChild* actor = new PresentationRequestChild(aCallback); + NS_WARN_IF(!sPresentationChild->SendPPresentationRequestConstructor(actor, aRequest)); + } + return NS_OK; +} + +NS_IMETHODIMP +PresentationIPCService::RegisterListener(nsIPresentationListener* aListener) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aListener); + + mListeners.AppendElement(aListener); + if (sPresentationChild) { + NS_WARN_IF(!sPresentationChild->SendRegisterHandler()); + } + return NS_OK; +} + +NS_IMETHODIMP +PresentationIPCService::UnregisterListener(nsIPresentationListener* aListener) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aListener); + + mListeners.RemoveElement(aListener); + if (sPresentationChild) { + NS_WARN_IF(!sPresentationChild->SendUnregisterHandler()); + } + return NS_OK; +} + +NS_IMETHODIMP +PresentationIPCService::RegisterSessionListener(const nsAString& aSessionId, + nsIPresentationSessionListener* aListener) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aListener); + + mSessionListeners.Put(aSessionId, aListener); + if (sPresentationChild) { + NS_WARN_IF(!sPresentationChild->SendRegisterSessionHandler(nsAutoString(aSessionId))); + } + return NS_OK; +} + +NS_IMETHODIMP +PresentationIPCService::UnregisterSessionListener(const nsAString& aSessionId) +{ + MOZ_ASSERT(NS_IsMainThread()); + + mSessionListeners.Remove(aSessionId); + if (sPresentationChild) { + NS_WARN_IF(!sPresentationChild->SendUnregisterSessionHandler(nsAutoString(aSessionId))); + } + return NS_OK; +} + +nsresult +PresentationIPCService::NotifySessionStateChange(const nsAString& aSessionId, + uint16_t aState) +{ + nsCOMPtr listener; + if (NS_WARN_IF(!mSessionListeners.Get(aSessionId, getter_AddRefs(listener)))) { + return NS_OK; + } + + return listener->NotifyStateChange(aSessionId, aState); +} + +nsresult +PresentationIPCService::NotifyMessage(const nsAString& aSessionId, + const nsACString& aData) +{ + nsCOMPtr listener; + if (NS_WARN_IF(!mSessionListeners.Get(aSessionId, getter_AddRefs(listener)))) { + return NS_OK; + } + + return listener->NotifyMessage(aSessionId, aData); +} + +nsresult +PresentationIPCService::NotifyAvailableChange(bool aAvailable) +{ + nsTObserverArray >::ForwardIterator iter(mListeners); + while (iter.HasMore()) { + nsIPresentationListener* listener = iter.GetNext(); + NS_WARN_IF(NS_FAILED(listener->NotifyAvailableChange(aAvailable))); + } + + return NS_OK; +} + +NS_IMETHODIMP +PresentationIPCService::GetExistentSessionIdAtLaunch(nsAString& aSessionId) +{ + MOZ_ASSERT(NS_IsMainThread()); + + nsAutoString sessionId(aSessionId); + NS_WARN_IF(!sPresentationChild->SendGetExistentSessionIdAtLaunch(&sessionId)); + aSessionId = sessionId; + + return NS_OK; +} + +nsresult +PresentationIPCService::NotifyReceiverReady(const nsAString& aSessionId) +{ + MOZ_ASSERT(NS_IsMainThread()); + + NS_WARN_IF(!sPresentationChild->SendNotifyReceiverReady(nsAutoString(aSessionId))); + return NS_OK; +} + +void +PresentationIPCService::NotifyPresentationChildDestroyed() +{ + sPresentationChild = nullptr; +} diff --git a/dom/presentation/ipc/PresentationIPCService.h b/dom/presentation/ipc/PresentationIPCService.h new file mode 100644 index 000000000000..e6641a347865 --- /dev/null +++ b/dom/presentation/ipc/PresentationIPCService.h @@ -0,0 +1,48 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et ft=cpp : */ +/* 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_PresentationIPCService_h +#define mozilla_dom_PresentationIPCService_h + +#include "nsIPresentationService.h" +#include "nsRefPtrHashtable.h" +#include "nsTObserverArray.h" + +namespace mozilla { +namespace dom { + +class PresentationRequest; + +class PresentationIPCService final : public nsIPresentationService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONSERVICE + + PresentationIPCService(); + + nsresult NotifyAvailableChange(bool aAvailable); + + nsresult NotifySessionStateChange(const nsAString& aSessionId, + uint16_t aState); + + nsresult NotifyMessage(const nsAString& aSessionId, const nsACString& aData); + + void NotifyPresentationChildDestroyed(); + +private: + virtual ~PresentationIPCService(); + nsresult SendRequest(nsIPresentationServiceCallback* aCallback, + const PresentationRequest& aRequest); + + nsTObserverArray > mListeners; + nsRefPtrHashtable mSessionListeners; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationIPCService_h diff --git a/dom/presentation/ipc/PresentationParent.cpp b/dom/presentation/ipc/PresentationParent.cpp new file mode 100644 index 000000000000..7996ce4bd9a8 --- /dev/null +++ b/dom/presentation/ipc/PresentationParent.cpp @@ -0,0 +1,257 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=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/. */ + +#include "mozilla/ipc/InputStreamUtils.h" +#include "nsIPresentationDeviceManager.h" +#include "nsServiceManagerUtils.h" +#include "PresentationParent.h" +#include "PresentationService.h" + +using namespace mozilla::dom; + +/* + * Implementation of PresentationParent + */ + +NS_IMPL_ISUPPORTS(PresentationParent, nsIPresentationListener, nsIPresentationSessionListener) + +PresentationParent::PresentationParent() + : mActorDestroyed(false) +{ + MOZ_COUNT_CTOR(PresentationParent); +} + +/* virtual */ PresentationParent::~PresentationParent() +{ + MOZ_COUNT_DTOR(PresentationParent); +} + +bool +PresentationParent::Init() +{ + MOZ_ASSERT(!mService); + mService = do_GetService(PRESENTATION_SERVICE_CONTRACTID); + return NS_WARN_IF(!mService) ? false : true; +} + +void +PresentationParent::ActorDestroy(ActorDestroyReason aWhy) +{ + mActorDestroyed = true; + mService->UnregisterListener(this); + mService = nullptr; +} + +bool +PresentationParent::RecvPPresentationRequestConstructor( + PPresentationRequestParent* aActor, + const PresentationRequest& aRequest) +{ + PresentationRequestParent* actor = static_cast(aActor); + + nsresult rv = NS_ERROR_FAILURE; + switch (aRequest.type()) { + case PresentationRequest::TStartSessionRequest: + rv = actor->DoRequest(aRequest.get_StartSessionRequest()); + break; + case PresentationRequest::TSendSessionMessageRequest: + rv = actor->DoRequest(aRequest.get_SendSessionMessageRequest()); + break; + case PresentationRequest::TTerminateRequest: + rv = actor->DoRequest(aRequest.get_TerminateRequest()); + break; + default: + MOZ_CRASH("Unknown PresentationRequest type"); + } + + return NS_WARN_IF(NS_FAILED(rv)) ? false : true; +} + +PPresentationRequestParent* +PresentationParent::AllocPPresentationRequestParent( + const PresentationRequest& aRequest) +{ + MOZ_ASSERT(mService); + nsRefPtr actor = new PresentationRequestParent(mService); + return actor.forget().take(); +} + +bool +PresentationParent::DeallocPPresentationRequestParent( + PPresentationRequestParent* aActor) +{ + nsRefPtr actor = + dont_AddRef(static_cast(aActor)); + return true; +} + +bool +PresentationParent::Recv__delete__() +{ + return true; +} + +bool +PresentationParent::RecvRegisterHandler() +{ + MOZ_ASSERT(mService); + NS_WARN_IF(NS_FAILED(mService->RegisterListener(this))); + return true; +} + +bool +PresentationParent::RecvUnregisterHandler() +{ + MOZ_ASSERT(mService); + NS_WARN_IF(NS_FAILED(mService->UnregisterListener(this))); + return true; +} + +/* virtual */ bool +PresentationParent::RecvRegisterSessionHandler(const nsString& aSessionId) +{ + MOZ_ASSERT(mService); + NS_WARN_IF(NS_FAILED(mService->RegisterSessionListener(aSessionId, this))); + return true; +} + +/* virtual */ bool +PresentationParent::RecvUnregisterSessionHandler(const nsString& aSessionId) +{ + MOZ_ASSERT(mService); + NS_WARN_IF(NS_FAILED(mService->UnregisterSessionListener(aSessionId))); + return true; +} + +NS_IMETHODIMP +PresentationParent::NotifyAvailableChange(bool aAvailable) +{ + if (NS_WARN_IF(mActorDestroyed || !SendNotifyAvailableChange(aAvailable))) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +PresentationParent::NotifyStateChange(const nsAString& aSessionId, + uint16_t aState) +{ + if (NS_WARN_IF(mActorDestroyed || + !SendNotifySessionStateChange(nsString(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)))) { + 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))); + return true; +} + +/* + * Implementation of PresentationRequestParent + */ + +NS_IMPL_ISUPPORTS(PresentationRequestParent, nsIPresentationServiceCallback) + +PresentationRequestParent::PresentationRequestParent(nsIPresentationService* aService) + : mActorDestroyed(false) + , mService(aService) +{ + MOZ_COUNT_CTOR(PresentationRequestParent); +} + +PresentationRequestParent::~PresentationRequestParent() +{ + MOZ_COUNT_DTOR(PresentationRequestParent); +} + +void +PresentationRequestParent::ActorDestroy(ActorDestroyReason aWhy) +{ + mActorDestroyed = true; + mService = nullptr; +} + +nsresult +PresentationRequestParent::DoRequest(const StartSessionRequest& aRequest) +{ + MOZ_ASSERT(mService); + return mService->StartSession(aRequest.url(), aRequest.sessionId(), + aRequest.origin(), this); +} + +nsresult +PresentationRequestParent::DoRequest(const SendSessionMessageRequest& aRequest) +{ + MOZ_ASSERT(mService); + nsTArray fds; + nsCOMPtr 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))) { + return NotifyError(rv); + } + return NotifySuccess(); +} + +nsresult +PresentationRequestParent::DoRequest(const TerminateRequest& aRequest) +{ + MOZ_ASSERT(mService); + nsresult rv = mService->Terminate(aRequest.sessionId()); + if (NS_WARN_IF(NS_FAILED(rv))) { + return NotifyError(rv); + } + return NotifySuccess(); +} + +NS_IMETHODIMP +PresentationRequestParent::NotifySuccess() +{ + return SendResponse(NS_OK); +} + +NS_IMETHODIMP +PresentationRequestParent::NotifyError(nsresult aError) +{ + return SendResponse(aError); +} + +nsresult +PresentationRequestParent::SendResponse(nsresult aResult) +{ + if (NS_WARN_IF(mActorDestroyed || !Send__delete__(this, aResult))) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} diff --git a/dom/presentation/ipc/PresentationParent.h b/dom/presentation/ipc/PresentationParent.h new file mode 100644 index 000000000000..30132b28ed20 --- /dev/null +++ b/dom/presentation/ipc/PresentationParent.h @@ -0,0 +1,95 @@ +/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */ +/* vim: set ts=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_PresentationParent_h__ +#define mozilla_dom_PresentationParent_h__ + +#include "mozilla/dom/PPresentationParent.h" +#include "mozilla/dom/PPresentationRequestParent.h" +#include "nsIPresentationListener.h" +#include "nsIPresentationService.h" + +namespace mozilla { +namespace dom { + +class PresentationParent final : public PPresentationParent + , public nsIPresentationListener + , public nsIPresentationSessionListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONLISTENER + NS_DECL_NSIPRESENTATIONSESSIONLISTENER + + PresentationParent(); + + bool Init(); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + + virtual bool + RecvPPresentationRequestConstructor(PPresentationRequestParent* aActor, + const PresentationRequest& aRequest) override; + + virtual PPresentationRequestParent* + AllocPPresentationRequestParent(const PresentationRequest& aRequest) override; + + virtual bool + DeallocPPresentationRequestParent(PPresentationRequestParent* aActor) override; + + virtual bool Recv__delete__() override; + + 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 mService; +}; + +class PresentationRequestParent final : public PPresentationRequestParent + , public nsIPresentationServiceCallback +{ + friend class PresentationParent; + +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONSERVICECALLBACK + + explicit PresentationRequestParent(nsIPresentationService* aService); + + virtual void ActorDestroy(ActorDestroyReason aWhy) override; + +private: + virtual ~PresentationRequestParent(); + + nsresult SendResponse(nsresult aResult); + + nsresult DoRequest(const StartSessionRequest& aRequest); + + nsresult DoRequest(const SendSessionMessageRequest& aRequest); + + nsresult DoRequest(const TerminateRequest& aRequest); + + bool mActorDestroyed; + nsCOMPtr mService; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationParent_h__ diff --git a/dom/presentation/moz.build b/dom/presentation/moz.build index 1f68e5116bc5..035fb70d2d9d 100644 --- a/dom/presentation/moz.build +++ b/dom/presentation/moz.build @@ -10,6 +10,9 @@ XPCSHELL_TESTS_MANIFESTS += ['tests/xpcshell/xpcshell.ini'] MOCHITEST_MANIFESTS += ['tests/mochitest/mochitest.ini'] EXPORTS.mozilla.dom += [ + 'ipc/PresentationChild.h', + 'ipc/PresentationIPCService.h', + 'ipc/PresentationParent.h', 'Presentation.h', 'PresentationDeviceManager.h', 'PresentationService.h', @@ -18,6 +21,9 @@ EXPORTS.mozilla.dom += [ ] UNIFIED_SOURCES += [ + 'ipc/PresentationChild.cpp', + 'ipc/PresentationIPCService.cpp', + 'ipc/PresentationParent.cpp', 'Presentation.cpp', 'PresentationDeviceManager.cpp', 'PresentationService.cpp', @@ -34,6 +40,11 @@ EXTRA_JS_MODULES += [ 'PresentationDeviceInfoManager.jsm', ] +IPDL_SOURCES += [ + 'ipc/PPresentation.ipdl', + 'ipc/PPresentationRequest.ipdl' +] + FAIL_ON_WARNINGS = True include('/ipc/chromium/chromium-config.mozbuild') From 0a8b5f2ba12dc9c31aa68a83c3fdeecaae3594c1 Mon Sep 17 00:00:00 2001 From: Sean Lin Date: Mon, 30 Mar 2015 14:27:27 +0800 Subject: [PATCH 09/18] Bug 1069230 - Presentation API implementation. Part 4 - Establish session (sender) & available changes. r=smaug --- dom/presentation/Presentation.cpp | 120 +++++- dom/presentation/Presentation.h | 3 + dom/presentation/PresentationCallbacks.cpp | 58 +++ dom/presentation/PresentationCallbacks.h | 44 +++ dom/presentation/PresentationService.cpp | 220 ++++++++++- dom/presentation/PresentationService.h | 21 +- dom/presentation/PresentationSessionInfo.cpp | 353 ++++++++++++++++++ dom/presentation/PresentationSessionInfo.h | 118 +++++- dom/presentation/interfaces/moz.build | 1 + .../nsIPresentationControlChannel.idl | 13 +- .../nsIPresentationSessionTransport.idl | 69 ++++ dom/presentation/moz.build | 3 + 12 files changed, 1002 insertions(+), 21 deletions(-) create mode 100644 dom/presentation/PresentationCallbacks.cpp create mode 100644 dom/presentation/PresentationCallbacks.h create mode 100644 dom/presentation/PresentationSessionInfo.cpp create mode 100644 dom/presentation/interfaces/nsIPresentationSessionTransport.idl diff --git a/dom/presentation/Presentation.cpp b/dom/presentation/Presentation.cpp index cf19c8ee1dc4..31d1172ce4a7 100644 --- a/dom/presentation/Presentation.cpp +++ b/dom/presentation/Presentation.cpp @@ -4,12 +4,17 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mozilla/AsyncEventDispatcher.h" +#include "mozilla/dom/PresentationAvailableEvent.h" #include "mozilla/dom/PresentationBinding.h" #include "mozilla/dom/Promise.h" #include "nsCycleCollectionParticipant.h" #include "nsIPresentationDeviceManager.h" +#include "nsIPresentationService.h" +#include "nsIUUIDGenerator.h" #include "nsServiceManagerUtils.h" #include "Presentation.h" +#include "PresentationCallbacks.h" #include "PresentationSession.h" using namespace mozilla; @@ -30,6 +35,7 @@ NS_IMPL_ADDREF_INHERITED(Presentation, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(Presentation, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(Presentation) + NS_INTERFACE_MAP_ENTRY(nsIPresentationListener) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) /* static */ already_AddRefed @@ -53,7 +59,39 @@ Presentation::~Presentation() bool Presentation::Init() { - // TODO: Register listener for |mAvailable| changes. + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return false; + } + + nsresult rv = service->RegisterListener(this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + nsCOMPtr deviceManager = + do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID); + if (NS_WARN_IF(!deviceManager)) { + 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); + 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; + } + } return true; } @@ -62,7 +100,14 @@ void Presentation::Shutdown() { mSession = nullptr; - // TODO: Unregister listener for |mAvailable| changes. + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return; + } + + nsresult rv = service->UnregisterListener(this); + NS_WARN_IF(NS_FAILED(rv)); } /* virtual */ JSObject* @@ -82,12 +127,63 @@ Presentation::StartSession(const nsAString& aUrl, return nullptr; } + // Get the origin. + nsAutoString origin; + nsresult rv = nsContentUtils::GetUTFOrigin(global->PrincipalOrNull(), origin); + if (NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(rv); + return nullptr; + } + nsRefPtr promise = Promise::Create(global, aRv); if (NS_WARN_IF(aRv.Failed())) { return nullptr; } - // TODO: Resolve/reject the promise. + // Ensure there's something to select. + if (NS_WARN_IF(!mAvailable)) { + promise->MaybeReject(NS_ERROR_DOM_INVALID_STATE_ERR); + return promise.forget(); + } + + // Ensure the URL is not empty. + if (NS_WARN_IF(aUrl.IsEmpty())) { + promise->MaybeReject(NS_ERROR_DOM_SYNTAX_ERR); + return promise.forget(); + } + + // Generate an ID if it's not assigned. + nsAutoString id; + if (aId.WasPassed()) { + id = aId.Value(); + } else { + nsCOMPtr uuidgen = + do_GetService("@mozilla.org/uuid-generator;1"); + if(NS_WARN_IF(!uuidgen)) { + promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + return promise.forget(); + } + + nsID uuid; + uuidgen->GenerateUUIDInPlace(&uuid); + char buffer[NSID_LENGTH]; + uuid.ToProvidedString(buffer); + CopyASCIItoUTF16(buffer, id); + } + + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if(NS_WARN_IF(!service)) { + promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + return promise.forget(); + } + + nsCOMPtr callback = + new PresentationRequesterCallback(GetOwner(), aUrl, id, promise); + rv = service->StartSession(aUrl, id, origin, callback); + if (NS_WARN_IF(NS_FAILED(rv))) { + promise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + } return promise.forget(); } @@ -104,3 +200,21 @@ Presentation::CachedAvailable() const { return mAvailable; } + +NS_IMETHODIMP +Presentation::NotifyAvailableChange(bool aAvailable) +{ + mAvailable = aAvailable; + + PresentationAvailableEventInit init; + init.mAvailable = mAvailable; + nsRefPtr event = + PresentationAvailableEvent::Constructor(this, + NS_LITERAL_STRING("availablechange"), + init); + event->SetTrusted(true); + + nsRefPtr asyncDispatcher = + new AsyncEventDispatcher(this, event); + return asyncDispatcher->PostDOMEvent(); +} diff --git a/dom/presentation/Presentation.h b/dom/presentation/Presentation.h index 2622aad5fbf7..4b2bdaa4de32 100644 --- a/dom/presentation/Presentation.h +++ b/dom/presentation/Presentation.h @@ -8,6 +8,7 @@ #define mozilla_dom_Presentation_h #include "mozilla/DOMEventTargetHelper.h" +#include "nsIPresentationListener.h" namespace mozilla { namespace dom { @@ -16,11 +17,13 @@ class Promise; class PresentationSession; class Presentation final : public DOMEventTargetHelper + , public nsIPresentationListener { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Presentation, DOMEventTargetHelper) + NS_DECL_NSIPRESENTATIONLISTENER static already_AddRefed Create(nsPIDOMWindow* aWindow); virtual JSObject* diff --git a/dom/presentation/PresentationCallbacks.cpp b/dom/presentation/PresentationCallbacks.cpp new file mode 100644 index 000000000000..20ab6d46f4f3 --- /dev/null +++ b/dom/presentation/PresentationCallbacks.cpp @@ -0,0 +1,58 @@ +/* -*- 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 "mozilla/dom/Promise.h" +#include "PresentationCallbacks.h" +#include "PresentationSession.h" + +using namespace mozilla; +using namespace mozilla::dom; + +NS_IMPL_ISUPPORTS(PresentationRequesterCallback, nsIPresentationServiceCallback) + +PresentationRequesterCallback::PresentationRequesterCallback(nsPIDOMWindow* aWindow, + const nsAString& aUrl, + const nsAString& aSessionId, + Promise* aPromise) + : mWindow(aWindow) + , mSessionId(aSessionId) + , mPromise(aPromise) +{ + MOZ_ASSERT(mWindow); + MOZ_ASSERT(mPromise); + MOZ_ASSERT(!mSessionId.IsEmpty()); +} + +PresentationRequesterCallback::~PresentationRequesterCallback() +{ +} + +NS_IMETHODIMP +PresentationRequesterCallback::NotifySuccess() +{ + MOZ_ASSERT(NS_IsMainThread()); + + // At the sender side, this function must get called after the transport + // channel is ready. So we simply set the session state as connected. + nsRefPtr session = + PresentationSession::Create(mWindow, mSessionId, PresentationSessionState::Connected); + if (!session) { + mPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR); + return NS_OK; + } + + mPromise->MaybeResolve(session); + return NS_OK; +} + +NS_IMETHODIMP +PresentationRequesterCallback::NotifyError(nsresult aError) +{ + MOZ_ASSERT(NS_IsMainThread()); + + mPromise->MaybeReject(aError); + return NS_OK; +} diff --git a/dom/presentation/PresentationCallbacks.h b/dom/presentation/PresentationCallbacks.h new file mode 100644 index 000000000000..0c72b85bd507 --- /dev/null +++ b/dom/presentation/PresentationCallbacks.h @@ -0,0 +1,44 @@ +/* -*- 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/. */ + +#ifndef mozilla_dom_PresentationCallbacks_h +#define mozilla_dom_PresentationCallbacks_h + +#include "mozilla/nsRefPtr.h" +#include "nsCOMPtr.h" +#include "nsIPresentationService.h" +#include "nsString.h" + +class nsPIDOMWindow; + +namespace mozilla { +namespace dom { + +class Promise; + +class PresentationRequesterCallback final : public nsIPresentationServiceCallback +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONSERVICECALLBACK + + PresentationRequesterCallback(nsPIDOMWindow* aWindow, + const nsAString& aUrl, + const nsAString& aSessionId, + Promise* aPromise); + +private: + ~PresentationRequesterCallback(); + + nsCOMPtr mWindow; + nsString mSessionId; + nsRefPtr mPromise; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationCallbacks_h diff --git a/dom/presentation/PresentationService.cpp b/dom/presentation/PresentationService.cpp index ab915b7a4bd7..a087828f12f2 100644 --- a/dom/presentation/PresentationService.cpp +++ b/dom/presentation/PresentationService.cpp @@ -6,6 +6,9 @@ #include "ipc/PresentationIPCService.h" #include "mozilla/Services.h" #include "nsIObserverService.h" +#include "nsIPresentationControlChannel.h" +#include "nsIPresentationDeviceManager.h" +#include "nsIPresentationDevicePrompt.h" #include "nsIPresentationListener.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" @@ -15,14 +18,135 @@ using namespace mozilla; using namespace mozilla::dom; +namespace mozilla { +namespace dom { + +/* + * Implementation of PresentationDeviceRequest + */ + +class PresentationDeviceRequest final : public nsIPresentationDeviceRequest +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONDEVICEREQUEST + + PresentationDeviceRequest(const nsAString& aRequestUrl, + const nsAString& aId, + const nsAString& aOrigin); + +private: + virtual ~PresentationDeviceRequest(); + + nsString mRequestUrl; + nsString mId; + nsString mOrigin; +}; + +} // namespace dom +} // namespace mozilla + +NS_IMPL_ISUPPORTS(PresentationDeviceRequest, nsIPresentationDeviceRequest) + +PresentationDeviceRequest::PresentationDeviceRequest(const nsAString& aRequestUrl, + const nsAString& aId, + const nsAString& aOrigin) + : mRequestUrl(aRequestUrl) + , mId(aId) + , mOrigin(aOrigin) +{ + MOZ_ASSERT(!mRequestUrl.IsEmpty()); + MOZ_ASSERT(!mId.IsEmpty()); + MOZ_ASSERT(!mOrigin.IsEmpty()); +} + +PresentationDeviceRequest::~PresentationDeviceRequest() +{ +} + +NS_IMETHODIMP +PresentationDeviceRequest::GetOrigin(nsAString& aOrigin) +{ + aOrigin = mOrigin; + return NS_OK; +} + +NS_IMETHODIMP +PresentationDeviceRequest::GetRequestURL(nsAString& aRequestUrl) +{ + aRequestUrl = mRequestUrl; + return NS_OK; +} + +NS_IMETHODIMP +PresentationDeviceRequest::Select(nsIPresentationDevice* aDevice) +{ + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(aDevice); + + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return NS_ERROR_NOT_AVAILABLE; + } + + // Update device in the session info. + nsRefPtr info = + static_cast(service.get())->GetSessionInfo(mId); + if (NS_WARN_IF(!info)) { + return NS_ERROR_NOT_AVAILABLE; + } + info->SetDevice(aDevice); + + // Establish a control channel. If we failed to do so, the callback is called + // with an error message. + nsCOMPtr ctrlChannel; + nsresult rv = aDevice->EstablishControlChannel(mRequestUrl, mId, getter_AddRefs(ctrlChannel)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return info->ReplyError(NS_ERROR_DOM_NETWORK_ERR); + } + + // Initialize the session info with the control channel. + rv = info->Init(ctrlChannel); + if (NS_WARN_IF(NS_FAILED(rv))) { + return info->ReplyError(NS_ERROR_DOM_NETWORK_ERR); + } + + return NS_OK; +} + +NS_IMETHODIMP +PresentationDeviceRequest::Cancel() +{ + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsRefPtr info = + static_cast(service.get())->GetSessionInfo(mId); + if (NS_WARN_IF(!info)) { + return NS_ERROR_NOT_AVAILABLE; + } + + return info->ReplyError(NS_ERROR_DOM_PROP_ACCESS_DENIED); +} + +/* + * Implementation of PresentationService + */ + NS_IMPL_ISUPPORTS(PresentationService, nsIPresentationService, nsIObserver) PresentationService::PresentationService() + : mIsAvailable(false) { } PresentationService::~PresentationService() { + HandleShutdown(); } bool @@ -35,9 +159,23 @@ PresentationService::Init() return false; } - // TODO: Add observers and get available devices. + nsresult rv = obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + rv = obs->AddObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } - return true; + nsCOMPtr deviceManager = + do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID); + if (NS_WARN_IF(!deviceManager)) { + return false; + } + + rv = deviceManager->GetDeviceAvailable(&mIsAvailable); + return !NS_WARN_IF(NS_FAILED(rv)); } NS_IMETHODIMP @@ -45,7 +183,55 @@ PresentationService::Observe(nsISupports* aSubject, const char* aTopic, const char16_t* aData) { - // TODO: Handle device availability changes can call |NotifyAvailableChange|. + if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + HandleShutdown(); + return NS_OK; + } else if (!strcmp(aTopic, PRESENTATION_DEVICE_CHANGE_TOPIC)) { + return HandleDeviceChange(); + } else if (!strcmp(aTopic, "profile-after-change")) { + // It's expected since we add and entry to |kLayoutCategories| in + // |nsLayoutModule.cpp| to launch this service earlier. + return NS_OK; + } + + MOZ_ASSERT(false, "Unexpected topic for PresentationService"); + return NS_ERROR_UNEXPECTED; +} + +void +PresentationService::HandleShutdown() +{ + MOZ_ASSERT(NS_IsMainThread()); + + mListeners.Clear(); + mSessionInfo.Clear(); + + nsCOMPtr obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); + obs->RemoveObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC); + } +} + +nsresult +PresentationService::HandleDeviceChange() +{ + nsCOMPtr deviceManager = + do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID); + if (NS_WARN_IF(!deviceManager)) { + return NS_ERROR_NOT_AVAILABLE; + } + + bool isAvailable; + nsresult rv = deviceManager->GetDeviceAvailable(&isAvailable); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + if (isAvailable != mIsAvailable) { + mIsAvailable = isAvailable; + NotifyAvailableChange(mIsAvailable); + } return NS_OK; } @@ -70,7 +256,24 @@ PresentationService::StartSession(const nsAString& aUrl, MOZ_ASSERT(aCallback); MOZ_ASSERT(!aSessionId.IsEmpty()); - // TODO: Reply the callback. + // Create session info and set the callback. The callback is called when the + // request is finished. + nsRefPtr info = + new PresentationRequesterInfo(aUrl, aSessionId, aCallback); + mSessionInfo.Put(aSessionId, info); + + // Pop up a prompt and ask user to select a device. + nsCOMPtr prompt = + do_GetService(PRESENTATION_DEVICE_PROMPT_CONTRACTID); + if (NS_WARN_IF(!prompt)) { + return info->ReplyError(NS_ERROR_DOM_ABORT_ERR); + } + nsCOMPtr request = + new PresentationDeviceRequest(aUrl, aSessionId, aOrigin); + nsresult rv = prompt->PromptDeviceSelection(request); + if (NS_WARN_IF(NS_FAILED(rv))) { + return info->ReplyError(NS_ERROR_DOM_ABORT_ERR); + } return NS_OK; } @@ -128,13 +331,12 @@ PresentationService::RegisterSessionListener(const nsAString& aSessionId, MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aListener); - PresentationSessionInfo* info = mSessionInfo.Get(aSessionId); + nsRefPtr info = GetSessionInfo(aSessionId); if (NS_WARN_IF(!info)) { return NS_ERROR_NOT_AVAILABLE; } - info->SetListener(aListener); - return NS_OK; + return info->SetListener(aListener); } NS_IMETHODIMP @@ -142,9 +344,9 @@ PresentationService::UnregisterSessionListener(const nsAString& aSessionId) { MOZ_ASSERT(NS_IsMainThread()); - PresentationSessionInfo* info = mSessionInfo.Get(aSessionId); + nsRefPtr info = GetSessionInfo(aSessionId); if (info) { - info->SetListener(nullptr); + return info->SetListener(nullptr); } return NS_OK; } diff --git a/dom/presentation/PresentationService.h b/dom/presentation/PresentationService.h index 1e8a75c3d008..34c5a1418c8e 100644 --- a/dom/presentation/PresentationService.h +++ b/dom/presentation/PresentationService.h @@ -7,9 +7,9 @@ #ifndef mozilla_dom_PresentationService_h #define mozilla_dom_PresentationService_h -#include "nsClassHashtable.h" #include "nsCOMPtr.h" #include "nsIObserver.h" +#include "nsRefPtrHashtable.h" #include "nsTObserverArray.h" #include "PresentationSessionInfo.h" @@ -27,11 +27,28 @@ public: PresentationService(); bool Init(); + already_AddRefed + GetSessionInfo(const nsAString& aSessionId) + { + nsRefPtr info; + return mSessionInfo.Get(aSessionId, getter_AddRefs(info)) ? + info.forget() : nullptr; + } + + void + RemoveSessionInfo(const nsAString& aSessionId) + { + mSessionInfo.Remove(aSessionId); + } + private: ~PresentationService(); + void HandleShutdown(); + nsresult HandleDeviceChange(); void NotifyAvailableChange(bool aIsAvailable); - nsClassHashtable mSessionInfo; + bool mIsAvailable; + nsRefPtrHashtable mSessionInfo; nsTObserverArray> mListeners; }; diff --git a/dom/presentation/PresentationSessionInfo.cpp b/dom/presentation/PresentationSessionInfo.cpp new file mode 100644 index 000000000000..811327b8da23 --- /dev/null +++ b/dom/presentation/PresentationSessionInfo.cpp @@ -0,0 +1,353 @@ +/* -*- 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 "nsServiceManagerUtils.h" +#include "nsThreadUtils.h" +#include "PresentationService.h" +#include "PresentationSessionInfo.h" + +using namespace mozilla; +using namespace mozilla::dom; + +/* + * Implementation of PresentationSessionInfo + */ + +NS_IMPL_ISUPPORTS(PresentationSessionInfo, + nsIPresentationSessionTransportCallback, + nsIPresentationControlChannelListener); + +/* virtual */ nsresult +PresentationSessionInfo::Init(nsIPresentationControlChannel* aControlChannel) +{ + SetControlChannel(aControlChannel); + return NS_OK; +} + +/* virtual */ void +PresentationSessionInfo::Shutdown(nsresult aReason) +{ + // Close the control channel if any. + if (mControlChannel) { + mControlChannel->SetListener(nullptr); + NS_WARN_IF(NS_FAILED(mControlChannel->Close(aReason))); + mControlChannel = nullptr; + } + + // Close the data transport channel if any. + if (mTransport) { + mTransport->SetCallback(nullptr); + NS_WARN_IF(NS_FAILED(mTransport->Close(aReason))); + mTransport = nullptr; + } + + mIsResponderReady = false; + mIsTransportReady = false; +} + +nsresult +PresentationSessionInfo::SetListener(nsIPresentationSessionListener* aListener) +{ + mListener = aListener; + + if (mListener) { + // The transport might become ready, or might become un-ready again, before + // the listener has registered. So notify the listener of the state change. + uint16_t state = IsSessionReady() ? + nsIPresentationSessionListener::STATE_CONNECTED : + nsIPresentationSessionListener::STATE_DISCONNECTED; + return mListener->NotifyStateChange(mSessionId, state); + } + + return NS_OK; +} + +nsresult +PresentationSessionInfo::Send(nsIInputStream* aData) +{ + // TODO Send data to |mTransport|. + return NS_OK; +} + +nsresult +PresentationSessionInfo::Close(nsresult aReason) +{ + // TODO Close |mTransport|. + return NS_OK; +} + +nsresult +PresentationSessionInfo::ReplySuccess() +{ + if (mListener) { + // Notify session state change. + nsresult rv = mListener->NotifyStateChange(mSessionId, + nsIPresentationSessionListener::STATE_CONNECTED); + NS_WARN_IF(NS_FAILED(rv)); + } + + if (mCallback) { + NS_WARN_IF(NS_FAILED(mCallback->NotifySuccess())); + SetCallback(nullptr); + } + + return NS_OK; +} + +nsresult +PresentationSessionInfo::ReplyError(nsresult aError) +{ + Shutdown(aError); + + if (mCallback) { + NS_WARN_IF(NS_FAILED(mCallback->NotifyError(aError))); + SetCallback(nullptr); + } + + // Remove itself since it never succeeds. + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return NS_ERROR_NOT_AVAILABLE; + } + static_cast(service.get())->RemoveSessionInfo(mSessionId); + + return NS_OK; +} + +// nsIPresentationSessionTransportCallback +NS_IMETHODIMP +PresentationSessionInfo::NotifyTransportReady() +{ + MOZ_ASSERT(NS_IsMainThread()); + + mIsTransportReady = true; + + // At sender side, session might not be ready at this point (waiting for + // receiver's answer). Yet at receiver side, session must be ready at this + // point since the data transport channel is created after the receiver page + // is ready for presentation use. + if (IsSessionReady()) { + return ReplySuccess(); + } + + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionInfo::NotifyTransportClosed(nsresult aReason) +{ + MOZ_ASSERT(NS_IsMainThread()); + + mTransport = nullptr; + + if (!IsSessionReady()) { + // It happens before the session is ready. Reply the callback. + return ReplyError(aReason); + } + + Shutdown(aReason); + + 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; + return mListener->NotifyStateChange(mSessionId, state); + } + + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionInfo::NotifyData(const nsACString& aData) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // TODO Notify the listener. + + return NS_OK; +} + +/* + * Implementation of PresentationRequesterInfo + * + * During presentation session establishment, the sender expects the following + * after trying to establish the control channel: (The order between step 2 and + * 3 is not guaranteed.) + * 1. |Init| is called to open a socket |mServerSocket| for data transport + * channel and send the offer to the receiver via the control channel. + * 2.1 |OnSocketAccepted| of |nsIServerSocketListener| is called to indicate the + * data transport channel is connected. Then initialize |mTransport|. + * 2.2 |NotifyTransportReady| of |nsIPresentationSessionTransportCallback| is + * called. + * 3. |OnAnswer| of |nsIPresentationControlChannelListener| is called to + * indicate the receiver is ready. Close the control channel since it's no + * longer needed. + * 4. Once both step 2 and 3 are done, the presentation session is ready to use. + * So notify the listener of CONNECTED state. + */ + +NS_IMPL_ISUPPORTS_INHERITED(PresentationRequesterInfo, + PresentationSessionInfo, + nsIServerSocketListener) + +nsresult +PresentationRequesterInfo::Init(nsIPresentationControlChannel* aControlChannel) +{ + PresentationSessionInfo::Init(aControlChannel); + + // TODO Initialize |mServerSocket|, use |this| as the listener, and prepare to + // send offer. + + return NS_OK; +} + +void +PresentationRequesterInfo::Shutdown(nsresult aReason) +{ + PresentationSessionInfo::Shutdown(aReason); + + // Close the server socket if any. + if (mServerSocket) { + NS_WARN_IF(NS_FAILED(mServerSocket->Close())); + mServerSocket = nullptr; + } +} + +// nsIPresentationControlChannelListener +NS_IMETHODIMP +PresentationRequesterInfo::OnOffer(nsIPresentationChannelDescription* aDescription) +{ + MOZ_ASSERT(false, "Sender side should not receive offer."); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +PresentationRequesterInfo::OnAnswer(nsIPresentationChannelDescription* aDescription) +{ + // Close the control channel since it's no longer needed. + nsresult rv = mControlChannel->Close(NS_OK); + if (NS_WARN_IF(NS_FAILED(rv))) { + return ReplyError(rv); + } + + // Session might not be ready at this moment (waiting for the establishment of + // the data transport channel). + if (IsSessionReady()){ + return ReplySuccess(); + } + + return NS_OK; +} + +NS_IMETHODIMP +PresentationRequesterInfo::NotifyOpened() +{ + // Do nothing and wait for receiver to be ready. + return NS_OK; +} + +NS_IMETHODIMP +PresentationRequesterInfo::NotifyClosed(nsresult aReason) +{ + MOZ_ASSERT(NS_IsMainThread()); + + SetControlChannel(nullptr); + + if (NS_WARN_IF(NS_FAILED(aReason))) { + // Reply error for an abnormal close. + return ReplyError(aReason); + } + + return NS_OK; +} + +// nsIServerSocketListener +NS_IMETHODIMP +PresentationRequesterInfo::OnSocketAccepted(nsIServerSocket* aServerSocket, + nsISocketTransport* aTransport) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Initialize |mTransport| and use |this| as the callback. + mTransport = do_CreateInstance(PRESENTATION_SESSION_TRANSPORT_CONTRACTID); + if (NS_WARN_IF(!mTransport)) { + return ReplyError(NS_ERROR_NOT_AVAILABLE); + } + + nsresult rv = mTransport->InitWithSocketTransport(aTransport, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +PresentationRequesterInfo::OnStopListening(nsIServerSocket* aServerSocket, + nsresult aStatus) +{ + MOZ_ASSERT(NS_IsMainThread()); + + Shutdown(aStatus); + + if (!IsSessionReady()) { + // It happens before the session is ready. Reply the callback. + return ReplyError(aStatus); + } + + // It happens after the session is ready. Notify session state change. + if (mListener) { + return mListener->NotifyStateChange(mSessionId, + nsIPresentationSessionListener::STATE_DISCONNECTED); + } + + return NS_OK; +} + +/* + * Implementation of PresentationResponderInfo + */ + +NS_IMPL_ISUPPORTS_INHERITED0(PresentationResponderInfo, + PresentationSessionInfo) + +// nsIPresentationControlChannelListener +NS_IMETHODIMP +PresentationResponderInfo::OnOffer(nsIPresentationChannelDescription* aDescription) +{ + // TODO Initialize |mTransport| and use |this| as the callback. + return NS_OK; +} + +NS_IMETHODIMP +PresentationResponderInfo::OnAnswer(nsIPresentationChannelDescription* aDescription) +{ + MOZ_ASSERT(false, "Receiver side should not receive answer."); + return NS_ERROR_FAILURE; +} + +NS_IMETHODIMP +PresentationResponderInfo::NotifyOpened() +{ + // Do nothing. + return NS_OK; +} + +NS_IMETHODIMP +PresentationResponderInfo::NotifyClosed(nsresult aReason) +{ + MOZ_ASSERT(NS_IsMainThread()); + + SetControlChannel(nullptr); + + if (NS_WARN_IF(NS_FAILED(aReason))) { + // TODO Notify session failure. + } + + return NS_OK; +} diff --git a/dom/presentation/PresentationSessionInfo.h b/dom/presentation/PresentationSessionInfo.h index c9d6f250516e..e4d6c3cf4dcf 100644 --- a/dom/presentation/PresentationSessionInfo.h +++ b/dom/presentation/PresentationSessionInfo.h @@ -9,27 +9,39 @@ #include "mozilla/nsRefPtr.h" #include "nsCOMPtr.h" +#include "nsIPresentationControlChannel.h" +#include "nsIPresentationDevice.h" #include "nsIPresentationListener.h" #include "nsIPresentationService.h" +#include "nsIPresentationSessionTransport.h" +#include "nsIServerSocket.h" #include "nsString.h" namespace mozilla { namespace dom { -class PresentationSessionInfo +class PresentationSessionInfo : public nsIPresentationSessionTransportCallback + , public nsIPresentationControlChannelListener { public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONSESSIONTRANSPORTCALLBACK + PresentationSessionInfo(const nsAString& aUrl, const nsAString& aSessionId, nsIPresentationServiceCallback* aCallback) : mUrl(aUrl) , mSessionId(aSessionId) + , mIsResponderReady(false) + , mIsTransportReady(false) , mCallback(aCallback) { MOZ_ASSERT(!mUrl.IsEmpty()); MOZ_ASSERT(!mSessionId.IsEmpty()); } + virtual nsresult Init(nsIPresentationControlChannel* aControlChannel); + const nsAString& GetUrl() const { return mUrl; @@ -45,16 +57,114 @@ public: mCallback = aCallback; } - void SetListener(nsIPresentationSessionListener* aListener) + nsresult SetListener(nsIPresentationSessionListener* aListener); + + void SetDevice(nsIPresentationDevice* aDevice) { - mListener = aListener; + mDevice = aDevice; + } + + already_AddRefed GetDevice() const + { + nsCOMPtr device = mDevice; + return device.forget(); + } + + void SetControlChannel(nsIPresentationControlChannel* aControlChannel) + { + if (mControlChannel) { + mControlChannel->SetListener(nullptr); + } + + mControlChannel = aControlChannel; + if (mControlChannel) { + mControlChannel->SetListener(this); + } + } + + nsresult Send(nsIInputStream* aData); + + nsresult Close(nsresult aReason); + + nsresult ReplyError(nsresult aReason); + +protected: + virtual ~PresentationSessionInfo() + { + Shutdown(NS_OK); + } + + virtual void Shutdown(nsresult aReason); + + nsresult ReplySuccess(); + + bool IsSessionReady() + { + return mIsResponderReady && mIsTransportReady; } -private: nsString mUrl; nsString mSessionId; + bool mIsResponderReady; + bool mIsTransportReady; nsCOMPtr mCallback; nsCOMPtr mListener; + nsCOMPtr mDevice; + nsCOMPtr mTransport; + nsCOMPtr mControlChannel; +}; + +// Session info with sender side behaviors. +class PresentationRequesterInfo final : public PresentationSessionInfo + , public nsIServerSocketListener +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER + NS_DECL_NSISERVERSOCKETLISTENER + + PresentationRequesterInfo(const nsAString& aUrl, + const nsAString& aSessionId, + nsIPresentationServiceCallback* aCallback) + : PresentationSessionInfo(aUrl, aSessionId, aCallback) + { + MOZ_ASSERT(mCallback); + } + + nsresult Init(nsIPresentationControlChannel* aControlChannel) override; + +private: + ~PresentationRequesterInfo() + { + Shutdown(NS_OK); + } + + void Shutdown(nsresult aReason) override; + + nsCOMPtr mServerSocket; +}; + +// Session info with receiver side behaviors. +class PresentationResponderInfo final : public PresentationSessionInfo +{ +public: + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER + + PresentationResponderInfo(const nsAString& aUrl, + const nsAString& aSessionId, + nsIPresentationDevice* aDevice) + : PresentationSessionInfo(aUrl, aSessionId, nullptr) + { + MOZ_ASSERT(aDevice); + + SetDevice(aDevice); + } + + // TODO May need to inherit more interfaces to handle some observed changes. + +private: + ~PresentationResponderInfo() {} }; } // namespace dom diff --git a/dom/presentation/interfaces/moz.build b/dom/presentation/interfaces/moz.build index 6911db4782d8..0e416f594f49 100644 --- a/dom/presentation/interfaces/moz.build +++ b/dom/presentation/interfaces/moz.build @@ -13,6 +13,7 @@ XPIDL_SOURCES += [ 'nsIPresentationListener.idl', 'nsIPresentationService.idl', 'nsIPresentationSessionRequest.idl', + 'nsIPresentationSessionTransport.idl', 'nsITCPPresentationServer.idl', ] diff --git a/dom/presentation/interfaces/nsIPresentationControlChannel.idl b/dom/presentation/interfaces/nsIPresentationControlChannel.idl index a0887d223dfd..faf29c0eada4 100644 --- a/dom/presentation/interfaces/nsIPresentationControlChannel.idl +++ b/dom/presentation/interfaces/nsIPresentationControlChannel.idl @@ -63,7 +63,7 @@ interface nsIPresentationControlChannelListener: nsISupports * The control channel for establishing RTCPeerConnection for a presentation * session. SDP Offer/Answer will be exchanged through this interface. */ -[scriptable, uuid(6bff04b9-8e79-466f-9446-f969de646fd3)] +[scriptable, uuid(2c8ec493-4e5b-4df7-bedc-7ab25af323f0)] interface nsIPresentationControlChannel: nsISupports { // The listener for handling events of this control channel. @@ -87,7 +87,14 @@ interface nsIPresentationControlChannel: nsISupports void sendAnswer(in nsIPresentationChannelDescription answer); /* - * Close the transport channel. + * Notify the app-to-app connection is fully established. (Only used at the + * receiver side.) */ - void close(); + void sendReceiverReady(); + + /* + * Close the transport channel. + * @param reason The reason of channel close; NS_OK represents normal. + */ + void close(in nsresult reason); }; diff --git a/dom/presentation/interfaces/nsIPresentationSessionTransport.idl b/dom/presentation/interfaces/nsIPresentationSessionTransport.idl new file mode 100644 index 000000000000..8f5668e54758 --- /dev/null +++ b/dom/presentation/interfaces/nsIPresentationSessionTransport.idl @@ -0,0 +1,69 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +interface nsIInputStream; +interface nsINetAddr; +interface nsIPresentationChannelDescription; +interface nsISocketTransport; + +%{C++ +#define PRESENTATION_SESSION_TRANSPORT_CID \ + { 0xc9d023f4, 0x6228, 0x4c07, \ + { 0x8b, 0x1d, 0x9c, 0x19, 0x57, 0x3f, 0xaa, 0x27 } } +#define PRESENTATION_SESSION_TRANSPORT_CONTRACTID \ + "@mozilla.org/presentation/presentationsessiontransport;1" +%} + +/* + * The callback for session transport events. + */ +[scriptable, uuid(9f158786-41a6-4a10-b29b-9497f25d4b67)] +interface nsIPresentationSessionTransportCallback : nsISupports +{ + void notifyTransportReady(); + void notifyTransportClosed(in nsresult reason); + void notifyData(in ACString data); +}; + +/* + * App-to-App transport channel for the presentation session. + */ +[scriptable, uuid(5a9fb9e9-b846-4c49-ad57-20ed88457295)] +interface nsIPresentationSessionTransport : nsISupports +{ + attribute nsIPresentationSessionTransportCallback callback; + readonly attribute nsINetAddr selfAddress; + + /* + * Initialize the transport channel with an existent socket transport. (This + * is primarily used at the sender side.) + * @param transport The socket transport. + * @param callback The callback for followup notifications. + */ + void initWithSocketTransport(in nsISocketTransport transport, + in nsIPresentationSessionTransportCallback callback); + + /* + * Initialize the transport channel with the channel description. (This is + * primarily used at the receiver side.) + * @param description The channel description. + * @param callback The callback for followup notifications. + */ + void initWithChannelDescription(in nsIPresentationChannelDescription description, + in nsIPresentationSessionTransportCallback callback); + + /* + * Send message to the remote endpoint. + * @param data The message to send. + */ + void send(in nsIInputStream data); + + /* + * Close this session transport. + * @param reason The reason for closing this session transport. + */ + void close(in nsresult reason); +}; diff --git a/dom/presentation/moz.build b/dom/presentation/moz.build index 035fb70d2d9d..18f37fb5bf89 100644 --- a/dom/presentation/moz.build +++ b/dom/presentation/moz.build @@ -14,6 +14,7 @@ EXPORTS.mozilla.dom += [ 'ipc/PresentationIPCService.h', 'ipc/PresentationParent.h', 'Presentation.h', + 'PresentationCallbacks.h', 'PresentationDeviceManager.h', 'PresentationService.h', 'PresentationSession.h', @@ -25,9 +26,11 @@ UNIFIED_SOURCES += [ 'ipc/PresentationIPCService.cpp', 'ipc/PresentationParent.cpp', 'Presentation.cpp', + 'PresentationCallbacks.cpp', 'PresentationDeviceManager.cpp', 'PresentationService.cpp', 'PresentationSession.cpp', + 'PresentationSessionInfo.cpp', 'PresentationSessionRequest.cpp', ] From 4eb814cda55bfbbd8ab2155ded9a79f03360bc66 Mon Sep 17 00:00:00 2001 From: Sean Lin Date: Mon, 30 Mar 2015 15:46:11 +0800 Subject: [PATCH 10/18] Bug 1069230 - Presentation API implementation. Part 5 - Establish session (receiver). r=smaug --- dom/ipc/ContentChild.cpp | 11 +- dom/presentation/PresentationCallbacks.cpp | 127 +++++++++++ dom/presentation/PresentationCallbacks.h | 24 ++ dom/presentation/PresentationService.cpp | 164 +++++++++++++- dom/presentation/PresentationService.h | 10 + dom/presentation/PresentationSessionInfo.cpp | 212 +++++++++++++++++- dom/presentation/PresentationSessionInfo.h | 24 +- .../ipc/PresentationIPCService.cpp | 9 + dom/presentation/ipc/PresentationIPCService.h | 10 +- 9 files changed, 579 insertions(+), 12 deletions(-) diff --git a/dom/ipc/ContentChild.cpp b/dom/ipc/ContentChild.cpp index eaf0564d818a..35f7f465faf1 100644 --- a/dom/ipc/ContentChild.cpp +++ b/dom/ipc/ContentChild.cpp @@ -172,6 +172,7 @@ #include "mozilla/dom/bluetooth/PBluetoothChild.h" #include "mozilla/dom/PFMRadioChild.h" #include "mozilla/dom/PPresentationChild.h" +#include "mozilla/dom/PresentationIPCService.h" #include "mozilla/ipc/InputStreamUtils.h" #ifdef MOZ_WEBSPEECH @@ -1401,7 +1402,15 @@ bool ContentChild::RecvNotifyPresentationReceiverLaunched(PBrowserChild* aIframe, const nsString& aSessionId) { - // TODO Listen to |nsIWebProgressListener| state changes for this frame. + nsCOMPtr docShell = + do_GetInterface(static_cast(aIframe)->WebNavigation()); + NS_WARN_IF(!docShell); + + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + NS_WARN_IF(!service); + + NS_WARN_IF(NS_FAILED(static_cast(service.get())->MonitorResponderLoading(aSessionId, docShell))); return true; } diff --git a/dom/presentation/PresentationCallbacks.cpp b/dom/presentation/PresentationCallbacks.cpp index 20ab6d46f4f3..763af83a216f 100644 --- a/dom/presentation/PresentationCallbacks.cpp +++ b/dom/presentation/PresentationCallbacks.cpp @@ -5,12 +5,21 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/Promise.h" +#include "nsIDocShell.h" +#include "nsIInterfaceRequestorUtils.h" +#include "nsIPresentationService.h" +#include "nsIWebProgress.h" +#include "nsServiceManagerUtils.h" #include "PresentationCallbacks.h" #include "PresentationSession.h" using namespace mozilla; using namespace mozilla::dom; +/* + * Implementation of PresentationRequesterCallback + */ + NS_IMPL_ISUPPORTS(PresentationRequesterCallback, nsIPresentationServiceCallback) PresentationRequesterCallback::PresentationRequesterCallback(nsPIDOMWindow* aWindow, @@ -30,6 +39,7 @@ PresentationRequesterCallback::~PresentationRequesterCallback() { } +// nsIPresentationServiceCallback NS_IMETHODIMP PresentationRequesterCallback::NotifySuccess() { @@ -56,3 +66,120 @@ PresentationRequesterCallback::NotifyError(nsresult aError) mPromise->MaybeReject(aError); return NS_OK; } + +/* + * Implementation of PresentationRequesterCallback + */ + +NS_IMPL_ISUPPORTS(PresentationResponderLoadingCallback, + nsIWebProgressListener, + nsISupportsWeakReference) + +PresentationResponderLoadingCallback::PresentationResponderLoadingCallback(const nsAString& aSessionId) + : mSessionId(aSessionId) +{ +} + +PresentationResponderLoadingCallback::~PresentationResponderLoadingCallback() +{ + if (mProgress) { + mProgress->RemoveProgressListener(this); + mProgress = nullptr; + } +} + +nsresult +PresentationResponderLoadingCallback::Init(nsIDocShell* aDocShell) +{ + mProgress = do_GetInterface(aDocShell); + if (NS_WARN_IF(!mProgress)) { + return NS_ERROR_NOT_AVAILABLE; + } + + uint32_t busyFlags = nsIDocShell::BUSY_FLAGS_NONE; + nsresult rv = aDocShell->GetBusyFlags(&busyFlags); + 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(); + } + + // Start to listen to document state change event |STATE_TRANSFERRING|. + return mProgress->AddProgressListener(this, nsIWebProgress::NOTIFY_STATE_DOCUMENT); +} + +nsresult +PresentationResponderLoadingCallback::NotifyReceiverReady() +{ + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return NS_ERROR_NOT_AVAILABLE; + } + + return service->NotifyReceiverReady(mSessionId); +} + +// nsIWebProgressListener +NS_IMETHODIMP +PresentationResponderLoadingCallback::OnStateChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t aStateFlags, + nsresult aStatus) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (aStateFlags & nsIWebProgressListener::STATE_TRANSFERRING) { + mProgress->RemoveProgressListener(this); + + return NotifyReceiverReady(); + } + + return NS_OK; +} + +NS_IMETHODIMP +PresentationResponderLoadingCallback::OnProgressChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + int32_t aCurSelfProgress, + int32_t aMaxSelfProgress, + int32_t aCurTotalProgress, + int32_t aMaxTotalProgress) +{ + // Do nothing. + return NS_OK; +} + +NS_IMETHODIMP +PresentationResponderLoadingCallback::OnLocationChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsIURI* aURI, + uint32_t aFlags) +{ + // Do nothing. + return NS_OK; +} + +NS_IMETHODIMP +PresentationResponderLoadingCallback::OnStatusChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + nsresult aStatus, + const char16_t* aMessage) +{ + // Do nothing. + return NS_OK; +} + +NS_IMETHODIMP +PresentationResponderLoadingCallback::OnSecurityChange(nsIWebProgress* aWebProgress, + nsIRequest* aRequest, + uint32_t state) +{ + // Do nothing. + return NS_OK; +} diff --git a/dom/presentation/PresentationCallbacks.h b/dom/presentation/PresentationCallbacks.h index 0c72b85bd507..765e8bfd6843 100644 --- a/dom/presentation/PresentationCallbacks.h +++ b/dom/presentation/PresentationCallbacks.h @@ -10,8 +10,12 @@ #include "mozilla/nsRefPtr.h" #include "nsCOMPtr.h" #include "nsIPresentationService.h" +#include "nsIWebProgressListener.h" #include "nsString.h" +#include "nsWeakReference.h" +class nsIDocShell; +class nsIWebProgress; class nsPIDOMWindow; namespace mozilla { @@ -38,6 +42,26 @@ private: nsRefPtr mPromise; }; +class PresentationResponderLoadingCallback final : public nsIWebProgressListener + , public nsSupportsWeakReference +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIWEBPROGRESSLISTENER + + explicit PresentationResponderLoadingCallback(const nsAString& aSessionId); + + nsresult Init(nsIDocShell* aDocShell); + +private: + ~PresentationResponderLoadingCallback(); + + nsresult NotifyReceiverReady(); + + nsString mSessionId; + nsCOMPtr mProgress; +}; + } // namespace dom } // namespace mozilla diff --git a/dom/presentation/PresentationService.cpp b/dom/presentation/PresentationService.cpp index a087828f12f2..a76088d80e3d 100644 --- a/dom/presentation/PresentationService.cpp +++ b/dom/presentation/PresentationService.cpp @@ -5,11 +5,15 @@ #include "ipc/PresentationIPCService.h" #include "mozilla/Services.h" +#include "mozIApplication.h" +#include "nsIAppsService.h" #include "nsIObserverService.h" #include "nsIPresentationControlChannel.h" #include "nsIPresentationDeviceManager.h" #include "nsIPresentationDevicePrompt.h" #include "nsIPresentationListener.h" +#include "nsIPresentationSessionRequest.h" +#include "nsNetUtil.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "nsXULAppAPI.h" @@ -167,6 +171,10 @@ PresentationService::Init() if (NS_WARN_IF(NS_FAILED(rv))) { return false; } + rv = obs->AddObserver(this, PRESENTATION_SESSION_REQUEST_TOPIC, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } nsCOMPtr deviceManager = do_GetService(PRESENTATION_DEVICE_MANAGER_CONTRACTID); @@ -188,6 +196,13 @@ PresentationService::Observe(nsISupports* aSubject, return NS_OK; } else if (!strcmp(aTopic, PRESENTATION_DEVICE_CHANGE_TOPIC)) { return HandleDeviceChange(); + } else if (!strcmp(aTopic, PRESENTATION_SESSION_REQUEST_TOPIC)) { + nsCOMPtr request(do_QueryInterface(aSubject)); + if (NS_WARN_IF(!request)) { + return NS_ERROR_FAILURE; + } + + return HandleSessionRequest(request); } else if (!strcmp(aTopic, "profile-after-change")) { // It's expected since we add and entry to |kLayoutCategories| in // |nsLayoutModule.cpp| to launch this service earlier. @@ -210,6 +225,7 @@ PresentationService::HandleShutdown() if (obs) { obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID); obs->RemoveObserver(this, PRESENTATION_DEVICE_CHANGE_TOPIC); + obs->RemoveObserver(this, PRESENTATION_SESSION_REQUEST_TOPIC); } } @@ -236,6 +252,104 @@ PresentationService::HandleDeviceChange() return NS_OK; } +nsresult +PresentationService::HandleSessionRequest(nsIPresentationSessionRequest* aRequest) +{ + nsCOMPtr ctrlChannel; + nsresult rv = aRequest->GetControlChannel(getter_AddRefs(ctrlChannel)); + if (NS_WARN_IF(NS_FAILED(rv) || !ctrlChannel)) { + return rv; + } + + nsAutoString url; + rv = aRequest->GetUrl(url); + if (NS_WARN_IF(NS_FAILED(rv))) { + ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR); + return rv; + } + + nsAutoString sessionId; + rv = aRequest->GetPresentationId(sessionId); + if (NS_WARN_IF(NS_FAILED(rv))) { + ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR); + return rv; + } + + nsCOMPtr device; + rv = aRequest->GetDevice(getter_AddRefs(device)); + if (NS_WARN_IF(NS_FAILED(rv))) { + ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR); + return rv; + } + +#ifdef MOZ_WIDGET_GONK + // Verify the existence of the app if necessary. + nsCOMPtr uri; + rv = NS_NewURI(getter_AddRefs(uri), url); + if (NS_WARN_IF(NS_FAILED(rv))) { + ctrlChannel->Close(NS_ERROR_DOM_BAD_URI); + return rv; + } + + bool isApp; + rv = uri->SchemeIs("app", &isApp); + if (NS_WARN_IF(NS_FAILED(rv))) { + ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR); + return rv; + } + + 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 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 obs = services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR); + return info->ReplyError(NS_ERROR_NOT_AVAILABLE); + } + rv = obs->NotifyObservers(aRequest, "presentation-launch-receiver", nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR); + return info->ReplyError(rv); + } + + return NS_OK; +} + void PresentationService::NotifyAvailableChange(bool aIsAvailable) { @@ -246,6 +360,33 @@ PresentationService::NotifyAvailableChange(bool aIsAvailable) } } +bool +PresentationService::IsAppInstalled(nsIURI* aUri) +{ + nsAutoCString prePath; + nsresult rv = aUri->GetPrePath(prePath); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + nsAutoString manifestUrl; + AppendUTF8toUTF16(prePath, manifestUrl); + manifestUrl.AppendLiteral("/manifest.webapp"); + + nsCOMPtr appsService = do_GetService(APPS_SERVICE_CONTRACTID); + if (NS_WARN_IF(!appsService)) { + return false; + } + + nsCOMPtr app; + appsService->GetAppByManifestURL(manifestUrl, getter_AddRefs(app)); + if (NS_WARN_IF(!app)) { + return false; + } + + return true; +} + NS_IMETHODIMP PresentationService::StartSession(const nsAString& aUrl, const nsAString& aSessionId, @@ -331,8 +472,21 @@ PresentationService::RegisterSessionListener(const nsAString& aSessionId, MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(aListener); + if (mRespondingSessionId.Equals(aSessionId)) { + mRespondingSessionId.Truncate(); + } + nsRefPtr 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); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } return NS_ERROR_NOT_AVAILABLE; } @@ -354,17 +508,19 @@ PresentationService::UnregisterSessionListener(const nsAString& aSessionId) NS_IMETHODIMP PresentationService::GetExistentSessionIdAtLaunch(nsAString& aSessionId) { - // TODO: Return the value based on it's a sender or a receiver. - + aSessionId = mRespondingSessionId; return NS_OK; } NS_IMETHODIMP PresentationService::NotifyReceiverReady(const nsAString& aSessionId) { - // TODO: Notify the correspondent session info. + nsRefPtr info = GetSessionInfo(aSessionId); + if (NS_WARN_IF(!info)) { + return NS_ERROR_NOT_AVAILABLE; + } - return NS_OK; + return static_cast(info.get())->NotifyResponderReady(); } already_AddRefed diff --git a/dom/presentation/PresentationService.h b/dom/presentation/PresentationService.h index 34c5a1418c8e..2e1711665c8a 100644 --- a/dom/presentation/PresentationService.h +++ b/dom/presentation/PresentationService.h @@ -13,6 +13,9 @@ #include "nsTObserverArray.h" #include "PresentationSessionInfo.h" +class nsIPresentationSessionRequest; +class nsIURI; + namespace mozilla { namespace dom { @@ -38,6 +41,10 @@ public: void RemoveSessionInfo(const nsAString& aSessionId) { + if (mRespondingSessionId.Equals(aSessionId)) { + mRespondingSessionId.Truncate(); + } + mSessionInfo.Remove(aSessionId); } @@ -45,9 +52,12 @@ private: ~PresentationService(); void HandleShutdown(); nsresult HandleDeviceChange(); + nsresult HandleSessionRequest(nsIPresentationSessionRequest* aRequest); void NotifyAvailableChange(bool aIsAvailable); + bool IsAppInstalled(nsIURI* aUri); bool mIsAvailable; + nsString mRespondingSessionId; nsRefPtrHashtable mSessionInfo; nsTObserverArray> mListeners; }; diff --git a/dom/presentation/PresentationSessionInfo.cpp b/dom/presentation/PresentationSessionInfo.cpp index 811327b8da23..79230bac7efc 100644 --- a/dom/presentation/PresentationSessionInfo.cpp +++ b/dom/presentation/PresentationSessionInfo.cpp @@ -4,6 +4,13 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/TabParent.h" +#include "mozilla/Preferences.h" +#include "mozilla/Services.h" +#include "nsIDocShell.h" +#include "nsIFrameLoader.h" +#include "nsIObserverService.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "PresentationService.h" @@ -11,6 +18,7 @@ using namespace mozilla; using namespace mozilla::dom; +using namespace mozilla::services; /* * Implementation of PresentationSessionInfo @@ -229,6 +237,8 @@ PresentationRequesterInfo::OnOffer(nsIPresentationChannelDescription* aDescripti NS_IMETHODIMP PresentationRequesterInfo::OnAnswer(nsIPresentationChannelDescription* aDescription) { + mIsResponderReady = true; + // Close the control channel since it's no longer needed. nsresult rv = mControlChannel->Close(NS_OK); if (NS_WARN_IF(NS_FAILED(rv))) { @@ -311,16 +321,138 @@ PresentationRequesterInfo::OnStopListening(nsIServerSocket* aServerSocket, /* * Implementation of PresentationResponderInfo + * + * During presentation session establishment, the receiver expects the following + * after trying to launch the app by notifying "presentation-launch-receiver": + * (The order between step 2 and 3 is not guaranteed.) + * 1. |Observe| of |nsIObserver| is called with "presentation-receiver-launched". + * Then start listen to document |STATE_TRANSFERRING| event. + * 2. |NotifyResponderReady| is called to indicate the receiver page is ready + * for presentation use. + * 3. |OnOffer| of |nsIPresentationControlChannelListener| is called. + * 4. Once both step 2 and 3 are done, establish the data transport channel and + * send the answer. (The control channel will be closed by the sender once it + * receives the answer.) + * 5. |NotifyTransportReady| of |nsIPresentationSessionTransportCallback| is + * called. The presentation session is ready to use, so notify the listener + * of CONNECTED state. */ -NS_IMPL_ISUPPORTS_INHERITED0(PresentationResponderInfo, - PresentationSessionInfo) +NS_IMPL_ISUPPORTS_INHERITED(PresentationResponderInfo, + PresentationSessionInfo, + nsIObserver, + nsITimerCallback) + +nsresult +PresentationResponderInfo::Init(nsIPresentationControlChannel* aControlChannel) +{ + PresentationSessionInfo::Init(aControlChannel); + + nsCOMPtr obs = services::GetObserverService(); + if (NS_WARN_IF(!obs)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = obs->AddObserver(this, "presentation-receiver-launched", false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Add a timer to prevent waiting indefinitely in case the receiver page fails + // to become ready. + int32_t timeout = + Preferences::GetInt("presentation.receiver.loading.timeout", 10000); + mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = mTimer->InitWithCallback(this, timeout, nsITimer::TYPE_ONE_SHOT); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +void +PresentationResponderInfo::Shutdown(nsresult aReason) +{ + PresentationSessionInfo::Shutdown(aReason); + + nsCOMPtr obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "presentation-receiver-launched"); + } + + if (mTimer) { + mTimer->Cancel(); + } + + mLoadingCallback = nullptr; + mRequesterDescription = nullptr; +} + +nsresult +PresentationResponderInfo::InitTransportAndSendAnswer() +{ + // Establish a data transport channel |mTransport| to the sender and use + // |this| as the callback. + mTransport = do_CreateInstance(PRESENTATION_SESSION_TRANSPORT_CONTRACTID); + if (NS_WARN_IF(!mTransport)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = mTransport->InitWithChannelDescription(mRequesterDescription, this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // TODO Prepare and send the answer. + + return NS_OK; + } + +nsresult +PresentationResponderInfo::NotifyResponderReady() +{ + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + mIsResponderReady = true; + + // Initialize |mTransport| and send the answer to the sender if sender's + // description is already offered. + if (mRequesterDescription) { + nsresult rv = InitTransportAndSendAnswer(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return ReplyError(rv); + } + } + + return NS_OK; +} // nsIPresentationControlChannelListener NS_IMETHODIMP PresentationResponderInfo::OnOffer(nsIPresentationChannelDescription* aDescription) { - // TODO Initialize |mTransport| and use |this| as the callback. + if (NS_WARN_IF(!aDescription)) { + return ReplyError(NS_ERROR_INVALID_ARG); + } + + mRequesterDescription = aDescription; + + // Initialize |mTransport| and send the answer to the sender if the receiver + // page is ready for presentation use. + if (mIsResponderReady) { + nsresult rv = InitTransportAndSendAnswer(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return ReplyError(rv); + } + } + return NS_OK; } @@ -346,8 +478,80 @@ PresentationResponderInfo::NotifyClosed(nsresult aReason) SetControlChannel(nullptr); if (NS_WARN_IF(NS_FAILED(aReason))) { - // TODO Notify session failure. + // Reply error for an abnormal close. + return ReplyError(aReason); } return NS_OK; } + +// nsIObserver +NS_IMETHODIMP +PresentationResponderInfo::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // The receiver has launched. + if (!strcmp(aTopic, "presentation-receiver-launched")) { + // Ignore irrelevant notifications. + if (!mSessionId.Equals(aData)) { + return NS_OK; + } + + // Remove the observer. + nsCOMPtr obs = services::GetObserverService(); + if (obs) { + obs->RemoveObserver(this, "presentation-receiver-launched"); + } + + // Start to listen to document state change event |STATE_TRANSFERRING|. + nsCOMPtr owner = do_QueryInterface(aSubject); + if (NS_WARN_IF(!owner)) { + return ReplyError(NS_ERROR_NOT_AVAILABLE); + } + + nsCOMPtr frameLoader; + nsresult rv = owner->GetFrameLoader(getter_AddRefs(frameLoader)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return ReplyError(rv); + } + + nsRefPtr tabParent = TabParent::GetFrom(frameLoader); + if (tabParent) { + // OOP frame + nsCOMPtr cp = tabParent->Manager(); + NS_WARN_IF(!static_cast(cp.get())->SendNotifyPresentationReceiverLaunched(tabParent, mSessionId)); + } else { + // In-process frame + nsCOMPtr docShell; + rv = frameLoader->GetDocShell(getter_AddRefs(docShell)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return ReplyError(rv); + } + + mLoadingCallback = new PresentationResponderLoadingCallback(mSessionId); + rv = mLoadingCallback->Init(docShell); + if (NS_WARN_IF(NS_FAILED(rv))) { + return ReplyError(rv); + } + } + + return NS_OK; + } + + MOZ_ASSERT(false, "Unexpected topic for PresentationResponderInfo."); + return NS_ERROR_UNEXPECTED; +} + +// nsITimerCallback +NS_IMETHODIMP +PresentationResponderInfo::Notify(nsITimer* aTimer) +{ + MOZ_ASSERT(NS_IsMainThread()); + NS_WARNING("The receiver page fails to become ready before timeout."); + + mTimer = nullptr; + return ReplyError(NS_ERROR_DOM_TIMEOUT_ERR); +} diff --git a/dom/presentation/PresentationSessionInfo.h b/dom/presentation/PresentationSessionInfo.h index e4d6c3cf4dcf..92c6ebf084bc 100644 --- a/dom/presentation/PresentationSessionInfo.h +++ b/dom/presentation/PresentationSessionInfo.h @@ -9,13 +9,16 @@ #include "mozilla/nsRefPtr.h" #include "nsCOMPtr.h" +#include "nsIObserver.h" #include "nsIPresentationControlChannel.h" #include "nsIPresentationDevice.h" #include "nsIPresentationListener.h" #include "nsIPresentationService.h" #include "nsIPresentationSessionTransport.h" #include "nsIServerSocket.h" +#include "nsITimer.h" #include "nsString.h" +#include "PresentationCallbacks.h" namespace mozilla { namespace dom { @@ -146,10 +149,14 @@ private: // Session info with receiver side behaviors. class PresentationResponderInfo final : public PresentationSessionInfo + , public nsIObserver + , public nsITimerCallback { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER + NS_DECL_NSIOBSERVER + NS_DECL_NSITIMERCALLBACK PresentationResponderInfo(const nsAString& aUrl, const nsAString& aSessionId, @@ -161,10 +168,23 @@ public: SetDevice(aDevice); } - // TODO May need to inherit more interfaces to handle some observed changes. + nsresult Init(nsIPresentationControlChannel* aControlChannel) override; + + nsresult NotifyResponderReady(); private: - ~PresentationResponderInfo() {} + ~PresentationResponderInfo() + { + Shutdown(NS_OK); + } + + void Shutdown(nsresult aReason) override; + + nsresult InitTransportAndSendAnswer(); + + nsRefPtr mLoadingCallback; + nsCOMPtr mTimer; + nsCOMPtr mRequesterDescription; }; } // namespace dom diff --git a/dom/presentation/ipc/PresentationIPCService.cpp b/dom/presentation/ipc/PresentationIPCService.cpp index a9f4571c7605..25e3f0dff0d3 100644 --- a/dom/presentation/ipc/PresentationIPCService.cpp +++ b/dom/presentation/ipc/PresentationIPCService.cpp @@ -8,6 +8,7 @@ #include "mozilla/dom/PPresentation.h" #include "mozilla/ipc/InputStreamUtils.h" #include "nsIPresentationListener.h" +#include "PresentationCallbacks.h" #include "PresentationChild.h" #include "PresentationIPCService.h" @@ -198,3 +199,11 @@ PresentationIPCService::NotifyPresentationChildDestroyed() { sPresentationChild = nullptr; } + +nsresult +PresentationIPCService::MonitorResponderLoading(const nsAString& aSessionId, + nsIDocShell* aDocShell) +{ + mCallback = new PresentationResponderLoadingCallback(aSessionId); + return mCallback->Init(aDocShell); +} diff --git a/dom/presentation/ipc/PresentationIPCService.h b/dom/presentation/ipc/PresentationIPCService.h index e6641a347865..b6868b50f5ab 100644 --- a/dom/presentation/ipc/PresentationIPCService.h +++ b/dom/presentation/ipc/PresentationIPCService.h @@ -11,10 +11,13 @@ #include "nsRefPtrHashtable.h" #include "nsTObserverArray.h" +class nsIDocShell; + namespace mozilla { namespace dom { class PresentationRequest; +class PresentationResponderLoadingCallback; class PresentationIPCService final : public nsIPresentationService { @@ -29,10 +32,14 @@ public: nsresult NotifySessionStateChange(const nsAString& aSessionId, uint16_t aState); - nsresult NotifyMessage(const nsAString& aSessionId, const nsACString& aData); + nsresult NotifyMessage(const nsAString& aSessionId, + const nsACString& aData); void NotifyPresentationChildDestroyed(); + nsresult MonitorResponderLoading(const nsAString& aSessionId, + nsIDocShell* aDocShell); + private: virtual ~PresentationIPCService(); nsresult SendRequest(nsIPresentationServiceCallback* aCallback, @@ -40,6 +47,7 @@ private: nsTObserverArray > mListeners; nsRefPtrHashtable mSessionListeners; + nsRefPtr mCallback; }; } // namespace dom From 4e9ad23c31b9869199b664ef87a890300a0d8978 Mon Sep 17 00:00:00 2001 From: Sean Lin Date: Mon, 30 Mar 2015 15:48:11 +0800 Subject: [PATCH 11/18] Bug 1069230 - Presentation API implementation. Part 6 - mozChromeEvent for app launch. r=fabrice r=smaug --- b2g/components/B2GComponents.manifest | 3 + b2g/components/PresentationRequestUIGlue.js | 62 +++++++ b2g/components/moz.build | 1 + b2g/components/test/mochitest/mochitest.ini | 2 + .../presentation_ui_glue_handler_chrome.js | 35 ++++ .../test_presentation_request_ui_glue.html | 78 +++++++++ b2g/installer/package-manifest.in | 1 + dom/presentation/PresentationService.cpp | 11 +- dom/presentation/PresentationSessionInfo.cpp | 157 +++++++++--------- dom/presentation/PresentationSessionInfo.h | 17 +- dom/presentation/interfaces/moz.build | 1 + .../nsIPresentationRequestUIGlue.idl | 25 +++ 12 files changed, 310 insertions(+), 83 deletions(-) create mode 100644 b2g/components/PresentationRequestUIGlue.js create mode 100644 b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js create mode 100644 b2g/components/test/mochitest/test_presentation_request_ui_glue.html create mode 100644 dom/presentation/interfaces/nsIPresentationRequestUIGlue.idl diff --git a/b2g/components/B2GComponents.manifest b/b2g/components/B2GComponents.manifest index 45935f2c7bae..9f2ca1141ab3 100644 --- a/b2g/components/B2GComponents.manifest +++ b/b2g/components/B2GComponents.manifest @@ -118,3 +118,6 @@ contract @mozilla.org/app-migrator;1 {7211ece0-b458-4635-9afc-f8d7f376ee95} component {4a300c26-e99b-4018-ab9b-c48cf9bc4de1} B2GPresentationDevicePrompt.js contract @mozilla.org/presentation-device/prompt;1 {4a300c26-e99b-4018-ab9b-c48cf9bc4de1} +# PresentationRequestUIGlue.js +component {ccc8a839-0b64-422b-8a60-fb2af0e376d0} PresentationRequestUIGlue.js +contract @mozilla.org/presentation/requestuiglue;1 {ccc8a839-0b64-422b-8a60-fb2af0e376d0} diff --git a/b2g/components/PresentationRequestUIGlue.js b/b2g/components/PresentationRequestUIGlue.js new file mode 100644 index 000000000000..45306c825d6d --- /dev/null +++ b/b2g/components/PresentationRequestUIGlue.js @@ -0,0 +1,62 @@ +/* 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/. */ + +"use strict" + +const { interfaces: Ci, utils: Cu, classes: Cc } = Components; + +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); +Cu.import("resource://gre/modules/Services.jsm"); + +XPCOMUtils.defineLazyModuleGetter(this, "SystemAppProxy", + "resource://gre/modules/SystemAppProxy.jsm"); + +function PresentationRequestUIGlue() { + // This is to store the session ID / resolver binding. + // An example of the object literal is shown below: + // + // { + // "sessionId1" : resolver1, + // ... + // } + this._resolvers = {}; + + // Listen to the result for the opened iframe from front-end. + SystemAppProxy.addEventListener("mozPresentationContentEvent", aEvent => { + let detail = aEvent.detail; + + if (detail.type != "presentation-receiver-launched") { + return; + } + + let sessionId = detail.sessionId; + let resolver = this._resolvers[sessionId]; + if (!resolver) { + return; + } + + delete this._resolvers[sessionId]; + resolver(detail.frame); + }); +} + +PresentationRequestUIGlue.prototype = { + + sendRequest: function(aUrl, aSessionId) { + SystemAppProxy._sendCustomEvent("mozPresentationChromeEvent", + { type: "presentation-launch-receiver", + url: aUrl, + id: aSessionId }); + + return new Promise(function(aResolve, aReject) { + this._resolvers[aSessionId] = aResolve; + }.bind(this)); + }, + + classID: Components.ID("{ccc8a839-0b64-422b-8a60-fb2af0e376d0}"), + + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationRequestUIGlue]) +}; + +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PresentationRequestUIGlue]); diff --git a/b2g/components/moz.build b/b2g/components/moz.build index 1fd863be7735..07a79437085c 100644 --- a/b2g/components/moz.build +++ b/b2g/components/moz.build @@ -23,6 +23,7 @@ EXTRA_COMPONENTS += [ 'OMAContentHandler.js', 'PaymentGlue.js', 'PaymentProviderStrategy.js', + 'PresentationRequestUIGlue.js', 'ProcessGlobal.js', 'SmsProtocolHandler.js', 'SystemMessageGlue.js', diff --git a/b2g/components/test/mochitest/mochitest.ini b/b2g/components/test/mochitest/mochitest.ini index 822e5b233ffc..193696729c9a 100644 --- a/b2g/components/test/mochitest/mochitest.ini +++ b/b2g/components/test/mochitest/mochitest.ini @@ -7,6 +7,7 @@ support-files = screenshot_helper.js systemapp_helper.js presentation_prompt_handler_chrome.js + presentation_ui_glue_handler_chrome.js [test_filepicker_path.html] [test_permission_deny.html] @@ -17,3 +18,4 @@ skip-if = true # Bug 1019572 - frequent timeouts [test_systemapp.html] [test_presentation_device_prompt.html] [test_permission_visibilitychange.html] +[test_presentation_request_ui_glue.html] diff --git a/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js b/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js new file mode 100644 index 000000000000..3c98cc74f767 --- /dev/null +++ b/b2g/components/test/mochitest/presentation_ui_glue_handler_chrome.js @@ -0,0 +1,35 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +const { classes: Cc, interfaces: Ci, utils: Cu } = Components; +const { XPCOMUtils } = Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +const { SystemAppProxy } = Cu.import('resource://gre/modules/SystemAppProxy.jsm'); + +const glue = Cc["@mozilla.org/presentation/requestuiglue;1"] + .createInstance(Ci.nsIPresentationRequestUIGlue); + +SystemAppProxy.addEventListener('mozPresentationChromeEvent', function(aEvent) { + if (!aEvent.detail || aEvent.detail.type !== 'presentation-launch-receiver') { + return; + } + sendAsyncMessage('presentation-launch-receiver', aEvent.detail); +}); + +addMessageListener('trigger-ui-glue', function(aData) { + var promise = glue.sendRequest(aData.url, aData.sessionId); + promise.then(function(aFrame){ + sendAsyncMessage('iframe-resolved', aFrame); + }); +}); + +addMessageListener('trigger-presentation-content-event', function(aData) { + var detail = { + type: 'presentation-receiver-launched', + sessionId: aData.sessionId, + frame: aData.frame + }; + SystemAppProxy._sendCustomEvent('mozPresentationContentEvent', detail); +}); diff --git a/b2g/components/test/mochitest/test_presentation_request_ui_glue.html b/b2g/components/test/mochitest/test_presentation_request_ui_glue.html new file mode 100644 index 000000000000..2f5608f5566e --- /dev/null +++ b/b2g/components/test/mochitest/test_presentation_request_ui_glue.html @@ -0,0 +1,78 @@ + + + + + + Test for Presentation Device Selection + + + + +Test for Presentation UI Glue + + + diff --git a/b2g/installer/package-manifest.in b/b2g/installer/package-manifest.in index 7d9b58be474d..4220342e30c7 100644 --- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -960,6 +960,7 @@ bin/libfreebl_32int64_3.so @RESPATH@/components/SystemMessageGlue.js @RESPATH@/components/B2GAppMigrator.js @RESPATH@/components/B2GPresentationDevicePrompt.js +@RESPATH@/components/PresentationRequestUIGlue.js #ifndef MOZ_WIDGET_GONK @RESPATH@/components/SimulatorScreen.js diff --git a/dom/presentation/PresentationService.cpp b/dom/presentation/PresentationService.cpp index a76088d80e3d..9b778bd8e8f8 100644 --- a/dom/presentation/PresentationService.cpp +++ b/dom/presentation/PresentationService.cpp @@ -12,6 +12,7 @@ #include "nsIPresentationDeviceManager.h" #include "nsIPresentationDevicePrompt.h" #include "nsIPresentationListener.h" +#include "nsIPresentationRequestUIGlue.h" #include "nsIPresentationSessionRequest.h" #include "nsNetUtil.h" #include "nsServiceManagerUtils.h" @@ -336,16 +337,20 @@ PresentationService::HandleSessionRequest(nsIPresentationSessionRequest* aReques mSessionInfo.Put(sessionId, info); // Notify the receiver to launch. - nsCOMPtr obs = services::GetObserverService(); - if (NS_WARN_IF(!obs)) { + nsCOMPtr glue = + do_CreateInstance(PRESENTATION_REQUEST_UI_GLUE_CONTRACTID); + if (NS_WARN_IF(!glue)) { ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR); return info->ReplyError(NS_ERROR_NOT_AVAILABLE); } - rv = obs->NotifyObservers(aRequest, "presentation-launch-receiver", nullptr); + nsCOMPtr promise; + rv = glue->SendRequest(url, sessionId, getter_AddRefs(promise)); if (NS_WARN_IF(NS_FAILED(rv))) { ctrlChannel->Close(NS_ERROR_DOM_ABORT_ERR); return info->ReplyError(rv); } + nsCOMPtr realPromise = do_QueryInterface(promise); + static_cast(info.get())->SetPromise(realPromise); return NS_OK; } diff --git a/dom/presentation/PresentationSessionInfo.cpp b/dom/presentation/PresentationSessionInfo.cpp index 79230bac7efc..96666ac7718b 100644 --- a/dom/presentation/PresentationSessionInfo.cpp +++ b/dom/presentation/PresentationSessionInfo.cpp @@ -5,12 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/HTMLIFrameElementBinding.h" #include "mozilla/dom/TabParent.h" #include "mozilla/Preferences.h" #include "mozilla/Services.h" #include "nsIDocShell.h" #include "nsIFrameLoader.h" -#include "nsIObserverService.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "PresentationService.h" @@ -340,7 +340,6 @@ PresentationRequesterInfo::OnStopListening(nsIServerSocket* aServerSocket, NS_IMPL_ISUPPORTS_INHERITED(PresentationResponderInfo, PresentationSessionInfo, - nsIObserver, nsITimerCallback) nsresult @@ -348,18 +347,9 @@ PresentationResponderInfo::Init(nsIPresentationControlChannel* aControlChannel) { PresentationSessionInfo::Init(aControlChannel); - nsCOMPtr obs = services::GetObserverService(); - if (NS_WARN_IF(!obs)) { - return NS_ERROR_NOT_AVAILABLE; - } - - nsresult rv = obs->AddObserver(this, "presentation-receiver-launched", false); - if (NS_WARN_IF(NS_FAILED(rv))) { - return rv; - } - // Add a timer to prevent waiting indefinitely in case the receiver page fails // to become ready. + nsresult rv; int32_t timeout = Preferences::GetInt("presentation.receiver.loading.timeout", 10000); mTimer = do_CreateInstance(NS_TIMER_CONTRACTID, &rv); @@ -379,17 +369,13 @@ PresentationResponderInfo::Shutdown(nsresult aReason) { PresentationSessionInfo::Shutdown(aReason); - nsCOMPtr obs = services::GetObserverService(); - if (obs) { - obs->RemoveObserver(this, "presentation-receiver-launched"); - } - if (mTimer) { mTimer->Cancel(); } mLoadingCallback = nullptr; mRequesterDescription = nullptr; + mPromise = nullptr; } nsresult @@ -485,66 +471,6 @@ PresentationResponderInfo::NotifyClosed(nsresult aReason) return NS_OK; } -// nsIObserver -NS_IMETHODIMP -PresentationResponderInfo::Observe(nsISupports* aSubject, - const char* aTopic, - const char16_t* aData) -{ - MOZ_ASSERT(NS_IsMainThread()); - - // The receiver has launched. - if (!strcmp(aTopic, "presentation-receiver-launched")) { - // Ignore irrelevant notifications. - if (!mSessionId.Equals(aData)) { - return NS_OK; - } - - // Remove the observer. - nsCOMPtr obs = services::GetObserverService(); - if (obs) { - obs->RemoveObserver(this, "presentation-receiver-launched"); - } - - // Start to listen to document state change event |STATE_TRANSFERRING|. - nsCOMPtr owner = do_QueryInterface(aSubject); - if (NS_WARN_IF(!owner)) { - return ReplyError(NS_ERROR_NOT_AVAILABLE); - } - - nsCOMPtr frameLoader; - nsresult rv = owner->GetFrameLoader(getter_AddRefs(frameLoader)); - if (NS_WARN_IF(NS_FAILED(rv))) { - return ReplyError(rv); - } - - nsRefPtr tabParent = TabParent::GetFrom(frameLoader); - if (tabParent) { - // OOP frame - nsCOMPtr cp = tabParent->Manager(); - NS_WARN_IF(!static_cast(cp.get())->SendNotifyPresentationReceiverLaunched(tabParent, mSessionId)); - } else { - // In-process frame - nsCOMPtr docShell; - rv = frameLoader->GetDocShell(getter_AddRefs(docShell)); - if (NS_WARN_IF(NS_FAILED(rv))) { - return ReplyError(rv); - } - - mLoadingCallback = new PresentationResponderLoadingCallback(mSessionId); - rv = mLoadingCallback->Init(docShell); - if (NS_WARN_IF(NS_FAILED(rv))) { - return ReplyError(rv); - } - } - - return NS_OK; - } - - MOZ_ASSERT(false, "Unexpected topic for PresentationResponderInfo."); - return NS_ERROR_UNEXPECTED; -} - // nsITimerCallback NS_IMETHODIMP PresentationResponderInfo::Notify(nsITimer* aTimer) @@ -555,3 +481,80 @@ PresentationResponderInfo::Notify(nsITimer* aTimer) mTimer = nullptr; return ReplyError(NS_ERROR_DOM_TIMEOUT_ERR); } + +// PromiseNativeHandler +void +PresentationResponderInfo::ResolvedCallback(JSContext* aCx, + JS::Handle aValue) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_WARN_IF(!aValue.isObject())) { + ReplyError(NS_ERROR_NOT_AVAILABLE); + return; + } + + JS::Rooted obj(aCx, &aValue.toObject()); + if (NS_WARN_IF(!obj)) { + ReplyError(NS_ERROR_NOT_AVAILABLE); + return; + } + + // Start to listen to document state change event |STATE_TRANSFERRING|. + HTMLIFrameElement* frame = nullptr; + nsresult rv = UNWRAP_OBJECT(HTMLIFrameElement, obj, frame); + if (NS_WARN_IF(!frame)) { + ReplyError(NS_ERROR_NOT_AVAILABLE); + return; + } + + nsCOMPtr owner = do_QueryInterface((nsIFrameLoaderOwner*) frame); + if (NS_WARN_IF(!owner)) { + ReplyError(NS_ERROR_NOT_AVAILABLE); + return; + } + + nsCOMPtr frameLoader; + rv = owner->GetFrameLoader(getter_AddRefs(frameLoader)); + if (NS_WARN_IF(NS_FAILED(rv))) { + ReplyError(rv); + return; + } + + nsRefPtr tabParent = TabParent::GetFrom(frameLoader); + if (tabParent) { + // OOP frame + nsCOMPtr cp = tabParent->Manager(); + NS_WARN_IF(!static_cast(cp.get())->SendNotifyPresentationReceiverLaunched(tabParent, mSessionId)); + } else { + // In-process frame + nsCOMPtr docShell; + rv = frameLoader->GetDocShell(getter_AddRefs(docShell)); + if (NS_WARN_IF(NS_FAILED(rv))) { + ReplyError(rv); + return; + } + + mLoadingCallback = new PresentationResponderLoadingCallback(mSessionId); + rv = mLoadingCallback->Init(docShell); + if (NS_WARN_IF(NS_FAILED(rv))) { + ReplyError(rv); + return; + } + } +} + +void +PresentationResponderInfo::RejectedCallback(JSContext* aCx, + JS::Handle aValue) +{ + MOZ_ASSERT(NS_IsMainThread()); + NS_WARNING("The receiver page fails to become ready before timeout."); + + if (mTimer) { + mTimer->Cancel(); + mTimer = nullptr; + } + + ReplyError(NS_ERROR_DOM_ABORT_ERR); +} diff --git a/dom/presentation/PresentationSessionInfo.h b/dom/presentation/PresentationSessionInfo.h index 92c6ebf084bc..e441eb221820 100644 --- a/dom/presentation/PresentationSessionInfo.h +++ b/dom/presentation/PresentationSessionInfo.h @@ -7,9 +7,10 @@ #ifndef mozilla_dom_PresentationSessionInfo_h #define mozilla_dom_PresentationSessionInfo_h +#include "mozilla/dom/Promise.h" +#include "mozilla/dom/PromiseNativeHandler.h" #include "mozilla/nsRefPtr.h" #include "nsCOMPtr.h" -#include "nsIObserver.h" #include "nsIPresentationControlChannel.h" #include "nsIPresentationDevice.h" #include "nsIPresentationListener.h" @@ -149,13 +150,12 @@ private: // Session info with receiver side behaviors. class PresentationResponderInfo final : public PresentationSessionInfo - , public nsIObserver + , public PromiseNativeHandler , public nsITimerCallback { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_NSIPRESENTATIONCONTROLCHANNELLISTENER - NS_DECL_NSIOBSERVER NS_DECL_NSITIMERCALLBACK PresentationResponderInfo(const nsAString& aUrl, @@ -172,6 +172,16 @@ public: nsresult NotifyResponderReady(); + void ResolvedCallback(JSContext* aCx, JS::Handle aValue) override; + + void RejectedCallback(JSContext* aCx, JS::Handle aValue) override; + + void SetPromise(Promise* aPromise) + { + mPromise = aPromise; + mPromise->AppendNativeHandler(this); + } + private: ~PresentationResponderInfo() { @@ -185,6 +195,7 @@ private: nsRefPtr mLoadingCallback; nsCOMPtr mTimer; nsCOMPtr mRequesterDescription; + nsRefPtr mPromise; }; } // namespace dom diff --git a/dom/presentation/interfaces/moz.build b/dom/presentation/interfaces/moz.build index 0e416f594f49..caa45be80550 100644 --- a/dom/presentation/interfaces/moz.build +++ b/dom/presentation/interfaces/moz.build @@ -11,6 +11,7 @@ XPIDL_SOURCES += [ 'nsIPresentationDevicePrompt.idl', 'nsIPresentationDeviceProvider.idl', 'nsIPresentationListener.idl', + 'nsIPresentationRequestUIGlue.idl', 'nsIPresentationService.idl', 'nsIPresentationSessionRequest.idl', 'nsIPresentationSessionTransport.idl', diff --git a/dom/presentation/interfaces/nsIPresentationRequestUIGlue.idl b/dom/presentation/interfaces/nsIPresentationRequestUIGlue.idl new file mode 100644 index 000000000000..7cd5455cf4ba --- /dev/null +++ b/dom/presentation/interfaces/nsIPresentationRequestUIGlue.idl @@ -0,0 +1,25 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "nsISupports.idl" + +%{C++ +#define PRESENTATION_REQUEST_UI_GLUE_CONTRACTID \ + "@mozilla.org/presentation/requestuiglue;1" +%} + +[scriptable, uuid(faa45119-6fb5-496c-aa4c-f740177a38b5)] +interface nsIPresentationRequestUIGlue : nsISupports +{ + /* + * This method is called to open the responding app/page when a presentation + * request comes in at receiver side. + * + * @param url The url of the request. + * @param sessionId The session ID of the request. + * + * @return A promise that resolves to the opening frame. + */ + nsISupports sendRequest(in DOMString url, in DOMString sessionId); +}; From 905d6d07e3b197715ba4859be83c476a05da0458 Mon Sep 17 00:00:00 2001 From: Sean Lin Date: Wed, 22 Apr 2015 16:01:38 +0800 Subject: [PATCH 12/18] Bug 1069230 - Presentation API implementation. Part 7 - Presentation session. r=smaug --- dom/presentation/PresentationService.cpp | 16 +- dom/presentation/PresentationSession.cpp | 170 ++++++++++++++++++- dom/presentation/PresentationSession.h | 5 + dom/presentation/PresentationSessionInfo.cpp | 61 +++++-- dom/presentation/ipc/PresentationParent.cpp | 8 + dom/presentation/ipc/PresentationParent.h | 1 + 6 files changed, 240 insertions(+), 21 deletions(-) diff --git a/dom/presentation/PresentationService.cpp b/dom/presentation/PresentationService.cpp index 9b778bd8e8f8..a2ae2e5a037f 100644 --- a/dom/presentation/PresentationService.cpp +++ b/dom/presentation/PresentationService.cpp @@ -432,9 +432,12 @@ PresentationService::SendSessionMessage(const nsAString& aSessionId, MOZ_ASSERT(aStream); MOZ_ASSERT(!aSessionId.IsEmpty()); - // TODO: Send input stream to the session. + nsRefPtr info = GetSessionInfo(aSessionId); + if (NS_WARN_IF(!info)) { + return NS_ERROR_NOT_AVAILABLE; + } - return NS_OK; + return info->Send(aStream); } NS_IMETHODIMP @@ -443,9 +446,12 @@ PresentationService::Terminate(const nsAString& aSessionId) MOZ_ASSERT(NS_IsMainThread()); MOZ_ASSERT(!aSessionId.IsEmpty()); - // TODO: Terminate the session. + nsRefPtr info = GetSessionInfo(aSessionId); + if (NS_WARN_IF(!info)) { + return NS_ERROR_NOT_AVAILABLE; + } - return NS_OK; + return info->Close(NS_OK); } NS_IMETHODIMP @@ -505,6 +511,8 @@ PresentationService::UnregisterSessionListener(const nsAString& aSessionId) nsRefPtr info = GetSessionInfo(aSessionId); if (info) { + NS_WARN_IF(NS_FAILED(info->Close(NS_OK))); + RemoveSessionInfo(aSessionId); return info->SetListener(nullptr); } return NS_OK; diff --git a/dom/presentation/PresentationSession.cpp b/dom/presentation/PresentationSession.cpp index a5d7934d8d3b..c6d5ff6d1742 100644 --- a/dom/presentation/PresentationSession.cpp +++ b/dom/presentation/PresentationSession.cpp @@ -4,7 +4,10 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +#include "mozilla/AsyncEventDispatcher.h" #include "nsCycleCollectionParticipant.h" +#include "nsIDOMMessageEvent.h" +#include "nsIPresentationService.h" #include "nsServiceManagerUtils.h" #include "nsStringStream.h" #include "PresentationSession.h" @@ -25,6 +28,7 @@ NS_IMPL_ADDREF_INHERITED(PresentationSession, DOMEventTargetHelper) NS_IMPL_RELEASE_INHERITED(PresentationSession, DOMEventTargetHelper) NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PresentationSession) + NS_INTERFACE_MAP_ENTRY(nsIPresentationSessionListener) NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper) PresentationSession::PresentationSession(nsPIDOMWindow* aWindow, @@ -57,7 +61,16 @@ PresentationSession::Init() return false; } - // TODO: Register listener for session state changes. + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if(NS_WARN_IF(!service)) { + return false; + } + + nsresult rv = service->RegisterSessionListener(mId, this); + if(NS_WARN_IF(NS_FAILED(rv))) { + return false; + } return true; } @@ -65,7 +78,14 @@ PresentationSession::Init() void PresentationSession::Shutdown() { - // TODO: Unregister listener for session state changes. + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return; + } + + nsresult rv = service->UnregisterSessionListener(mId); + NS_WARN_IF(NS_FAILED(rv)); } /* virtual */ JSObject* @@ -84,7 +104,6 @@ PresentationSession::GetId(nsAString& aId) const PresentationSessionState PresentationSession::State() const { - // TODO: Dispatch event when the value of |mState| is changed. return mState; } @@ -102,18 +121,28 @@ PresentationSession::Send(const nsAString& aData, nsCOMPtr stream = do_CreateInstance(NS_STRINGINPUTSTREAM_CONTRACTID, &rv); if(NS_WARN_IF(NS_FAILED(rv))) { - aRv.Throw(NS_ERROR_NOT_AVAILABLE); + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } NS_ConvertUTF16toUTF8 msgString(aData); rv = stream->SetData(msgString.BeginReading(), msgString.Length()); if(NS_WARN_IF(NS_FAILED(rv))) { - aRv.Throw(NS_ERROR_DOM_OPERATION_ERR); + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); return; } - // TODO: Send the message to the stream. + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if(NS_WARN_IF(!service)) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + return; + } + + rv = service->SendSessionMessage(mId, stream); + if(NS_WARN_IF(NS_FAILED(rv))) { + aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR); + } } void @@ -124,5 +153,132 @@ PresentationSession::Close() return; } - // TODO: Terminate the socket. + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if(NS_WARN_IF(!service)) { + return; + } + + NS_WARN_IF(NS_FAILED(service->Terminate(mId))); +} + +NS_IMETHODIMP +PresentationSession::NotifyStateChange(const nsAString& aSessionId, + uint16_t aState) +{ + if (!aSessionId.Equals(mId)) { + return NS_ERROR_INVALID_ARG; + } + + PresentationSessionState state; + switch (aState) { + case nsIPresentationSessionListener::STATE_CONNECTED: + state = PresentationSessionState::Connected; + break; + case nsIPresentationSessionListener::STATE_DISCONNECTED: + state = PresentationSessionState::Disconnected; + break; + case nsIPresentationSessionListener::STATE_TERMINATED: + state = PresentationSessionState::Terminated; + break; + default: + NS_WARNING("Unknown presentation session state."); + return NS_ERROR_INVALID_ARG; + } + + if (mState == state) { + return NS_OK; + } + + mState = state; + + // Unregister session listener if the session is no longer connected. + if (mState == PresentationSessionState::Terminated) { + nsCOMPtr service = + do_GetService(PRESENTATION_SERVICE_CONTRACTID); + if (NS_WARN_IF(!service)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsresult rv = service->UnregisterSessionListener(mId); + if(NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + + return DispatchStateChangeEvent(); +} + +NS_IMETHODIMP +PresentationSession::NotifyMessage(const nsAString& aSessionId, + const nsACString& aData) +{ + if (!aSessionId.Equals(mId)) { + return NS_ERROR_INVALID_ARG; + } + + // No message should be expected when the session is not connected. + if (NS_WARN_IF(mState != PresentationSessionState::Connected)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + // Transform the data. + AutoJSAPI jsapi; + if (!jsapi.Init(GetOwner())) { + return NS_ERROR_FAILURE; + } + JSContext* cx = jsapi.cx(); + JS::Rooted jsData(cx); + NS_ConvertUTF8toUTF16 utf16Data(aData); + if(NS_WARN_IF(!ToJSValue(cx, utf16Data, &jsData))) { + return NS_ERROR_FAILURE; + } + + return DispatchMessageEvent(jsData); +} + +nsresult +PresentationSession::DispatchStateChangeEvent() +{ + nsRefPtr asyncDispatcher = + new AsyncEventDispatcher(this, NS_LITERAL_STRING("statechange"), false); + return asyncDispatcher->PostDOMEvent(); +} + +nsresult +PresentationSession::DispatchMessageEvent(JS::Handle aData) +{ + nsCOMPtr global = do_QueryInterface(GetOwner()); + if (NS_WARN_IF(!global)) { + return NS_ERROR_NOT_AVAILABLE; + } + + // Get the origin. + nsAutoString origin; + nsresult rv = nsContentUtils::GetUTFOrigin(global->PrincipalOrNull(), origin); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr event; + rv = NS_NewDOMMessageEvent(getter_AddRefs(event), this, nullptr, nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr messageEvent = do_QueryInterface(event); + rv = messageEvent->InitMessageEvent(NS_LITERAL_STRING("message"), + false, false, + aData, + origin, + EmptyString(), nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + event->SetTrusted(true); + + nsRefPtr asyncDispatcher = + new AsyncEventDispatcher(this, event); + return asyncDispatcher->PostDOMEvent(); } diff --git a/dom/presentation/PresentationSession.h b/dom/presentation/PresentationSession.h index e3bf54d326f2..37477a37d0d3 100644 --- a/dom/presentation/PresentationSession.h +++ b/dom/presentation/PresentationSession.h @@ -9,16 +9,19 @@ #include "mozilla/DOMEventTargetHelper.h" #include "mozilla/dom/PresentationSessionBinding.h" +#include "nsIPresentationListener.h" namespace mozilla { namespace dom { class PresentationSession final : public DOMEventTargetHelper + , public nsIPresentationSessionListener { public: NS_DECL_ISUPPORTS_INHERITED NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PresentationSession, DOMEventTargetHelper) + NS_DECL_NSIPRESENTATIONSESSIONLISTENER static already_AddRefed Create(nsPIDOMWindow* aWindow, @@ -44,6 +47,8 @@ private: bool Init(); void Shutdown(); + nsresult DispatchStateChangeEvent(); + nsresult DispatchMessageEvent(JS::Handle aData); nsString mId; PresentationSessionState mState; diff --git a/dom/presentation/PresentationSessionInfo.cpp b/dom/presentation/PresentationSessionInfo.cpp index 96666ac7718b..7c0f3b6a9bdf 100644 --- a/dom/presentation/PresentationSessionInfo.cpp +++ b/dom/presentation/PresentationSessionInfo.cpp @@ -40,20 +40,16 @@ PresentationSessionInfo::Shutdown(nsresult aReason) { // Close the control channel if any. if (mControlChannel) { - mControlChannel->SetListener(nullptr); NS_WARN_IF(NS_FAILED(mControlChannel->Close(aReason))); - mControlChannel = nullptr; } // Close the data transport channel if any. if (mTransport) { - mTransport->SetCallback(nullptr); + // |mIsTransportReady| will be unset once |NotifyTransportClosed| is called. NS_WARN_IF(NS_FAILED(mTransport->Close(aReason))); - mTransport = nullptr; } mIsResponderReady = false; - mIsTransportReady = false; } nsresult @@ -76,14 +72,29 @@ PresentationSessionInfo::SetListener(nsIPresentationSessionListener* aListener) nsresult PresentationSessionInfo::Send(nsIInputStream* aData) { - // TODO Send data to |mTransport|. - return NS_OK; + if (NS_WARN_IF(!IsSessionReady())) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + if (NS_WARN_IF(!mTransport)) { + return NS_ERROR_NOT_AVAILABLE; + } + + return mTransport->Send(aData); } nsresult PresentationSessionInfo::Close(nsresult aReason) { - // TODO Close |mTransport|. + // 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)); + } + + Shutdown(aReason); return NS_OK; } @@ -150,6 +161,9 @@ PresentationSessionInfo::NotifyTransportClosed(nsresult aReason) { MOZ_ASSERT(NS_IsMainThread()); + // Nullify |mTransport| here so it won't try to re-close |mTransport| in + // potential subsequent |Shutdown| calls. + mTransport->SetCallback(nullptr); mTransport = nullptr; if (!IsSessionReady()) { @@ -157,6 +171,9 @@ PresentationSessionInfo::NotifyTransportClosed(nsresult aReason) return ReplyError(aReason); } + // Unset |mIsTransportReady| here so it won't affect |IsSessionReady()| above. + mIsTransportReady = false; + Shutdown(aReason); if (mListener) { @@ -175,9 +192,15 @@ PresentationSessionInfo::NotifyData(const nsACString& aData) { MOZ_ASSERT(NS_IsMainThread()); - // TODO Notify the listener. + if (NS_WARN_IF(!IsSessionReady())) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } - return NS_OK; + if (NS_WARN_IF(!mListener)) { + return NS_ERROR_NOT_AVAILABLE; + } + + return mListener->NotifyMessage(mSessionId, aData); } /* @@ -266,9 +289,18 @@ PresentationRequesterInfo::NotifyClosed(nsresult aReason) { MOZ_ASSERT(NS_IsMainThread()); + // Unset control channel here so it won't try to re-close it in potential + // subsequent |Shutdown| calls. SetControlChannel(nullptr); if (NS_WARN_IF(NS_FAILED(aReason))) { + if (mListener) { + // The presentation session instance at receiver side may already exist. + // Change the state to TERMINATED since it never succeeds. + return mListener->NotifyStateChange(mSessionId, + nsIPresentationSessionListener::STATE_TERMINATED); + } + // Reply error for an abnormal close. return ReplyError(aReason); } @@ -461,9 +493,18 @@ PresentationResponderInfo::NotifyClosed(nsresult aReason) { MOZ_ASSERT(NS_IsMainThread()); + // Unset control channel here so it won't try to re-close it in potential + // subsequent |Shutdown| calls. SetControlChannel(nullptr); if (NS_WARN_IF(NS_FAILED(aReason))) { + if (mListener) { + // The presentation session instance at receiver side may already exist. + // Change the state to TERMINATED since it never succeeds. + return mListener->NotifyStateChange(mSessionId, + nsIPresentationSessionListener::STATE_TERMINATED); + } + // Reply error for an abnormal close. return ReplyError(aReason); } diff --git a/dom/presentation/ipc/PresentationParent.cpp b/dom/presentation/ipc/PresentationParent.cpp index 7996ce4bd9a8..9814939cc84b 100644 --- a/dom/presentation/ipc/PresentationParent.cpp +++ b/dom/presentation/ipc/PresentationParent.cpp @@ -41,6 +41,12 @@ void PresentationParent::ActorDestroy(ActorDestroyReason aWhy) { mActorDestroyed = true; + + for (uint32_t i = 0; i < mSessionIds.Length(); i++) { + NS_WARN_IF(NS_FAILED(mService->UnregisterSessionListener(mSessionIds[i]))); + } + mSessionIds.Clear(); + mService->UnregisterListener(this); mService = nullptr; } @@ -114,6 +120,7 @@ PresentationParent::RecvUnregisterHandler() PresentationParent::RecvRegisterSessionHandler(const nsString& aSessionId) { MOZ_ASSERT(mService); + mSessionIds.AppendElement(aSessionId); NS_WARN_IF(NS_FAILED(mService->RegisterSessionListener(aSessionId, this))); return true; } @@ -122,6 +129,7 @@ PresentationParent::RecvRegisterSessionHandler(const nsString& aSessionId) PresentationParent::RecvUnregisterSessionHandler(const nsString& aSessionId) { MOZ_ASSERT(mService); + mSessionIds.RemoveElement(aSessionId); NS_WARN_IF(NS_FAILED(mService->UnregisterSessionListener(aSessionId))); return true; } diff --git a/dom/presentation/ipc/PresentationParent.h b/dom/presentation/ipc/PresentationParent.h index 30132b28ed20..6a4938e93e5b 100644 --- a/dom/presentation/ipc/PresentationParent.h +++ b/dom/presentation/ipc/PresentationParent.h @@ -59,6 +59,7 @@ private: bool mActorDestroyed; nsCOMPtr mService; + nsTArray mSessionIds; }; class PresentationRequestParent final : public PPresentationRequestParent From 1efa4382a3b267bef9c8744bd8e38fe5adc5f394 Mon Sep 17 00:00:00 2001 From: Sean Lin Date: Wed, 22 Apr 2015 16:01:38 +0800 Subject: [PATCH 13/18] Bug 1069230 - Presentation API implementation. Part 8 - Data transport channel. r=jdm --- dom/presentation/PresentationSessionInfo.cpp | 211 +++++++- dom/presentation/PresentationSessionInfo.h | 2 + .../PresentationSessionTransport.cpp | 484 ++++++++++++++++++ .../PresentationSessionTransport.h | 99 ++++ .../nsIPresentationSessionTransport.idl | 3 - dom/presentation/moz.build | 2 + .../test_presentation_session_transport.js | 171 +++++++ dom/presentation/tests/xpcshell/xpcshell.ini | 1 + layout/build/nsLayoutModule.cpp | 8 + 9 files changed, 975 insertions(+), 6 deletions(-) create mode 100644 dom/presentation/PresentationSessionTransport.cpp create mode 100644 dom/presentation/PresentationSessionTransport.h create mode 100644 dom/presentation/tests/xpcshell/test_presentation_session_transport.js diff --git a/dom/presentation/PresentationSessionInfo.cpp b/dom/presentation/PresentationSessionInfo.cpp index 7c0f3b6a9bdf..65c94fc5d841 100644 --- a/dom/presentation/PresentationSessionInfo.cpp +++ b/dom/presentation/PresentationSessionInfo.cpp @@ -11,15 +11,119 @@ #include "mozilla/Services.h" #include "nsIDocShell.h" #include "nsIFrameLoader.h" +#include "nsIMutableArray.h" +#include "nsINetAddr.h" +#include "nsISocketTransport.h" +#include "nsISupportsPrimitives.h" +#include "nsNetCID.h" #include "nsServiceManagerUtils.h" #include "nsThreadUtils.h" #include "PresentationService.h" #include "PresentationSessionInfo.h" +#ifdef MOZ_WIDGET_GONK +#include "nsINetworkInterface.h" +#include "nsINetworkManager.h" +#endif + using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::services; +/* + * Implementation of PresentationChannelDescription + */ + +namespace mozilla { +namespace dom { + +class PresentationChannelDescription final : public nsIPresentationChannelDescription +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONCHANNELDESCRIPTION + + PresentationChannelDescription(nsACString& aAddress, + uint16_t aPort) + : mAddress(aAddress) + , mPort(aPort) + { + } + +private: + ~PresentationChannelDescription() {} + + nsCString mAddress; + uint16_t mPort; +}; + +} // namespace dom +} // namespace mozilla + +NS_IMPL_ISUPPORTS(PresentationChannelDescription, nsIPresentationChannelDescription) + +NS_IMETHODIMP +PresentationChannelDescription::GetType(uint8_t* aRetVal) +{ + if (NS_WARN_IF(!aRetVal)) { + return NS_ERROR_INVALID_POINTER; + } + + // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + // Only support TCP socket for now. + *aRetVal = nsIPresentationChannelDescription::TYPE_TCP; + return NS_OK; +} + +NS_IMETHODIMP +PresentationChannelDescription::GetTcpAddress(nsIArray** aRetVal) +{ + if (NS_WARN_IF(!aRetVal)) { + return NS_ERROR_INVALID_POINTER; + } + + nsCOMPtr array = do_CreateInstance(NS_ARRAY_CONTRACTID); + if (NS_WARN_IF(!array)) { + return NS_ERROR_OUT_OF_MEMORY; + } + + // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + // Ultimately we may use all the available addresses. DataChannel appears + // more robust upon handling ICE. And at the first stage Presentation API is + // only exposed on Firefox OS where the first IP appears enough for most + // scenarios. + nsCOMPtr address = do_CreateInstance(NS_SUPPORTS_CSTRING_CONTRACTID); + if (NS_WARN_IF(!address)) { + return NS_ERROR_OUT_OF_MEMORY; + } + address->SetData(mAddress); + + array->AppendElement(address, false); + array.forget(aRetVal); + + return NS_OK; +} + +NS_IMETHODIMP +PresentationChannelDescription::GetTcpPort(uint16_t* aRetVal) +{ + if (NS_WARN_IF(!aRetVal)) { + return NS_ERROR_INVALID_POINTER; + } + + *aRetVal = mPort; + return NS_OK; +} + +NS_IMETHODIMP +PresentationChannelDescription::GetDataChannelSDP(nsAString& aDataChannelSDP) +{ + // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + // Only support TCP socket for now. + aDataChannelSDP.Truncate(); + return NS_OK; +} + /* * Implementation of PresentationSessionInfo */ @@ -231,8 +335,42 @@ PresentationRequesterInfo::Init(nsIPresentationControlChannel* aControlChannel) { PresentationSessionInfo::Init(aControlChannel); - // TODO Initialize |mServerSocket|, use |this| as the listener, and prepare to - // send offer. + // Initialize |mServerSocket| for bootstrapping the data transport channel and + // use |this| as the listener. + mServerSocket = do_CreateInstance(NS_SERVERSOCKET_CONTRACTID); + if (NS_WARN_IF(!mServerSocket)) { + return ReplyError(NS_ERROR_NOT_AVAILABLE); + } + + nsresult rv = mServerSocket->Init(-1, false, -1); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mServerSocket->AsyncListen(this); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Prepare and send the offer. + int32_t port; + rv = mServerSocket->GetPort(&port); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString address; + rv = GetAddress(address); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsRefPtr description = + new PresentationChannelDescription(address, static_cast(port)); + rv = mControlChannel->SendOffer(description); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } return NS_OK; } @@ -249,6 +387,51 @@ PresentationRequesterInfo::Shutdown(nsresult aReason) } } +nsresult +PresentationRequesterInfo::GetAddress(nsACString& aAddress) +{ +#ifdef MOZ_WIDGET_GONK + nsCOMPtr networkManager = + do_GetService("@mozilla.org/network/manager;1"); + if (NS_WARN_IF(!networkManager)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr activeNetworkInfo; + networkManager->GetActiveNetworkInfo(getter_AddRefs(activeNetworkInfo)); + if (NS_WARN_IF(!activeNetworkInfo)) { + return NS_ERROR_FAILURE; + } + + char16_t** ips = nullptr; + uint32_t* prefixes = nullptr; + uint32_t count = 0; + activeNetworkInfo->GetAddresses(&ips, &prefixes, &count); + if (NS_WARN_IF(!count)) { + NS_Free(prefixes); + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, ips); + return NS_ERROR_FAILURE; + } + + // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + // Ultimately we may use all the available addresses. DataChannel appears + // more robust upon handling ICE. And at the first stage Presentation API is + // only exposed on Firefox OS where the first IP appears enough for most + // scenarios. + nsAutoString ip; + ip.Assign(ips[0]); + aAddress = NS_ConvertUTF16toUTF8(ip); + + NS_Free(prefixes); + NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(count, ips); +#else + // TODO Get host IP via other platforms. + aAddress.Truncate(); +#endif + + return NS_OK; +} + // nsIPresentationControlChannelListener NS_IMETHODIMP PresentationRequesterInfo::OnOffer(nsIPresentationChannelDescription* aDescription) @@ -425,7 +608,29 @@ PresentationResponderInfo::InitTransportAndSendAnswer() return rv; } - // TODO Prepare and send the answer. + // Prepare and send the answer. + // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + // In the current implementation of |PresentationSessionTransport|, + // |GetSelfAddress| cannot return the real info when it's initialized via + // |InitWithChannelDescription|. Yet this deficiency only affects the channel + // description for the answer, which is not actually checked at requester side. + nsCOMPtr selfAddr; + rv = mTransport->GetSelfAddress(getter_AddRefs(selfAddr)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCString address; + selfAddr->GetAddress(address); + uint16_t port; + selfAddr->GetPort(&port); + nsCOMPtr description = + new PresentationChannelDescription(address, port); + + rv = mControlChannel->SendAnswer(description); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } return NS_OK; } diff --git a/dom/presentation/PresentationSessionInfo.h b/dom/presentation/PresentationSessionInfo.h index e441eb221820..5ec21cf4f5c3 100644 --- a/dom/presentation/PresentationSessionInfo.h +++ b/dom/presentation/PresentationSessionInfo.h @@ -145,6 +145,8 @@ private: void Shutdown(nsresult aReason) override; + nsresult GetAddress(nsACString& aAddress); + nsCOMPtr mServerSocket; }; diff --git a/dom/presentation/PresentationSessionTransport.cpp b/dom/presentation/PresentationSessionTransport.cpp new file mode 100644 index 000000000000..efd42621c1f2 --- /dev/null +++ b/dom/presentation/PresentationSessionTransport.cpp @@ -0,0 +1,484 @@ +/* -*- 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 "nsArrayUtils.h" +#include "nsIAsyncStreamCopier.h" +#include "nsIInputStreamPump.h" +#include "nsIMultiplexInputStream.h" +#include "nsIMutableArray.h" +#include "nsIOutputStream.h" +#include "nsIPresentationControlChannel.h" +#include "nsIScriptableInputStream.h" +#include "nsISocketTransport.h" +#include "nsISocketTransportService.h" +#include "nsISupportsPrimitives.h" +#include "nsNetUtil.h" +#include "nsServiceManagerUtils.h" +#include "nsStreamUtils.h" +#include "nsThreadUtils.h" +#include "PresentationSessionTransport.h" + +#define BUFFER_SIZE 65536 + +using namespace mozilla; +using namespace mozilla::dom; + +class CopierCallbacks final : public nsIRequestObserver +{ +public: + explicit CopierCallbacks(PresentationSessionTransport* aTransport) + : mOwner(aTransport) + {} + + NS_DECL_ISUPPORTS + NS_DECL_NSIREQUESTOBSERVER +private: + ~CopierCallbacks() {} + + nsRefPtr mOwner; +}; + +NS_IMPL_ISUPPORTS(CopierCallbacks, nsIRequestObserver) + +NS_IMETHODIMP +CopierCallbacks::OnStartRequest(nsIRequest* aRequest, nsISupports* aContext) +{ + return NS_OK; +} + +NS_IMETHODIMP +CopierCallbacks::OnStopRequest(nsIRequest* aRequest, nsISupports* aContext, nsresult aStatus) +{ + mOwner->NotifyCopyComplete(aStatus); + return NS_OK; +} + +NS_IMPL_ISUPPORTS(PresentationSessionTransport, + nsIPresentationSessionTransport, + nsITransportEventSink, + nsIInputStreamCallback, + nsIStreamListener, + nsIRequestObserver) + +PresentationSessionTransport::PresentationSessionTransport() + : mReadyState(CLOSED) + , mAsyncCopierActive(false) + , mCloseStatus(NS_OK) +{ +} + +PresentationSessionTransport::~PresentationSessionTransport() +{ +} + +NS_IMETHODIMP +PresentationSessionTransport::InitWithSocketTransport(nsISocketTransport* aTransport, + nsIPresentationSessionTransportCallback* aCallback) +{ + if (NS_WARN_IF(!aCallback)) { + return NS_ERROR_INVALID_ARG; + } + mCallback = aCallback; + + if (NS_WARN_IF(!aTransport)) { + return NS_ERROR_INVALID_ARG; + } + mTransport = aTransport; + + nsresult rv = CreateStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + SetReadyState(OPEN); + + rv = CreateInputStreamPump(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionTransport::InitWithChannelDescription(nsIPresentationChannelDescription* aDescription, + nsIPresentationSessionTransportCallback* aCallback) +{ + if (NS_WARN_IF(!aCallback)) { + return NS_ERROR_INVALID_ARG; + } + mCallback = aCallback; + + if (NS_WARN_IF(!aDescription)) { + return NS_ERROR_INVALID_ARG; + } + + uint16_t serverPort; + nsresult rv = aDescription->GetTcpPort(&serverPort); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr serverHosts; + rv = aDescription->GetTcpAddress(getter_AddRefs(serverHosts)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + // Ultimately we may use all the available addresses. DataChannel appears + // more robust upon handling ICE. And at the first stage Presentation API is + // only exposed on Firefox OS where the first IP appears enough for most + // scenarios. + nsCOMPtr supportStr = do_QueryElementAt(serverHosts, 0); + if (NS_WARN_IF(!supportStr)) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString serverHost; + supportStr->GetData(serverHost); + if (serverHost.IsEmpty()) { + return NS_ERROR_INVALID_ARG; + } + + SetReadyState(CONNECTING); + + nsCOMPtr sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); + if (NS_WARN_IF(!sts)) { + return NS_ERROR_NOT_AVAILABLE; + } + rv = sts->CreateTransport(nullptr, 0, serverHost, serverPort, nullptr, + getter_AddRefs(mTransport)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr mainThread; + NS_GetMainThread(getter_AddRefs(mainThread)); + + mTransport->SetEventSink(this, mainThread); + + rv = CreateStream(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +PresentationSessionTransport::CreateStream() +{ + nsresult rv = mTransport->OpenInputStream(0, 0, 0, getter_AddRefs(mSocketInputStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = mTransport->OpenOutputStream(nsITransport::OPEN_UNBUFFERED, 0, 0, getter_AddRefs(mSocketOutputStream)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // If the other side is not listening, we will get an |onInputStreamReady| + // callback where |available| raises to indicate the connection was refused. + nsCOMPtr asyncStream = do_QueryInterface(mSocketInputStream); + if (NS_WARN_IF(!asyncStream)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr mainThread; + NS_GetMainThread(getter_AddRefs(mainThread)); + + rv = asyncStream->AsyncWait(this, nsIAsyncInputStream::WAIT_CLOSURE_ONLY, 0, mainThread); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mInputStreamScriptable = do_CreateInstance("@mozilla.org/scriptableinputstream;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + rv = mInputStreamScriptable->Init(mSocketInputStream); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mMultiplexStream = do_CreateInstance("@mozilla.org/io/multiplex-input-stream;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mMultiplexStreamCopier = do_CreateInstance("@mozilla.org/network/async-stream-copier;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsCOMPtr sts = + do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID); + if (NS_WARN_IF(!sts)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCOMPtr target = do_QueryInterface(sts); + rv = mMultiplexStreamCopier->Init(mMultiplexStream, + mSocketOutputStream, + target, + true, /* source buffered */ + false, /* sink buffered */ + BUFFER_SIZE, + false, /* close source */ + false); /* close sink */ + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +nsresult +PresentationSessionTransport::CreateInputStreamPump() +{ + nsresult rv; + mInputStreamPump = do_CreateInstance(NS_INPUTSTREAMPUMP_CONTRACTID, &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mInputStreamPump->Init(mSocketInputStream, -1, -1, 0, 0, false); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + rv = mInputStreamPump->AsyncRead(this, nullptr); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionTransport::GetCallback(nsIPresentationSessionTransportCallback** aCallback) +{ + nsCOMPtr callback = mCallback; + callback.forget(aCallback); + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionTransport::SetCallback(nsIPresentationSessionTransportCallback* aCallback) +{ + mCallback = aCallback; + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionTransport::GetSelfAddress(nsINetAddr** aSelfAddress) +{ + if (NS_WARN_IF(mReadyState != OPEN)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + return mTransport->GetScriptableSelfAddr(aSelfAddress); +} + +void +PresentationSessionTransport::EnsureCopying() +{ + if (mAsyncCopierActive) { + return; + } + + mAsyncCopierActive = true; + nsRefPtr callbacks = new CopierCallbacks(this); + NS_WARN_IF(NS_FAILED(mMultiplexStreamCopier->AsyncCopy(callbacks, nullptr))); +} + +void +PresentationSessionTransport::NotifyCopyComplete(nsresult aStatus) +{ + mAsyncCopierActive = false; + mMultiplexStream->RemoveStream(0); + if (NS_WARN_IF(NS_FAILED(aStatus))) { + if (mReadyState != CLOSED) { + mCloseStatus = aStatus; + SetReadyState(CLOSED); + } + return; + } + + uint32_t count; + nsresult rv = mMultiplexStream->GetCount(&count); + if (NS_WARN_IF(NS_FAILED(rv))) { + return; + } + + if (count) { + EnsureCopying(); + return; + } + + if (mReadyState == CLOSING) { + mSocketOutputStream->Close(); + mCloseStatus = NS_OK; + SetReadyState(CLOSED); + } +} + +NS_IMETHODIMP +PresentationSessionTransport::Send(nsIInputStream* aData) +{ + if (NS_WARN_IF(mReadyState != OPEN)) { + return NS_ERROR_DOM_INVALID_STATE_ERR; + } + + mMultiplexStream->AppendStream(aData); + + EnsureCopying(); + + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionTransport::Close(nsresult aReason) +{ + if (mReadyState == CLOSED || mReadyState == CLOSING) { + return NS_OK; + } + + mCloseStatus = aReason; + SetReadyState(CLOSING); + + uint32_t count = 0; + mMultiplexStream->GetCount(&count); + if (!count) { + mSocketOutputStream->Close(); + } + + mSocketInputStream->Close(); + + return NS_OK; +} + +void +PresentationSessionTransport::SetReadyState(ReadyState aReadyState) +{ + mReadyState = aReadyState; + + if (mReadyState == OPEN && mCallback) { + // Notify the transport channel is ready. + NS_WARN_IF(NS_FAILED(mCallback->NotifyTransportReady())); + } else if (mReadyState == CLOSED && mCallback) { + // Notify the transport channel has been shut down. + NS_WARN_IF(NS_FAILED(mCallback->NotifyTransportClosed(mCloseStatus))); + } +} + +// nsITransportEventSink +NS_IMETHODIMP +PresentationSessionTransport::OnTransportStatus(nsITransport* aTransport, + nsresult aStatus, + int64_t aProgress, + int64_t aProgressMax) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (aStatus != NS_NET_STATUS_CONNECTED_TO) { + return NS_OK; + } + + SetReadyState(OPEN); + + nsresult rv = CreateInputStreamPump(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + return NS_OK; +} + +// nsIInputStreamCallback +NS_IMETHODIMP +PresentationSessionTransport::OnInputStreamReady(nsIAsyncInputStream* aStream) +{ + MOZ_ASSERT(NS_IsMainThread()); + + // Only used for detecting if the connection was refused. + uint64_t dummy; + nsresult rv = aStream->Available(&dummy); + if (NS_WARN_IF(NS_FAILED(rv))) { + if (mReadyState != CLOSED) { + mCloseStatus = NS_ERROR_CONNECTION_REFUSED; + SetReadyState(CLOSED); + } + } + + return NS_OK; +} + +// nsIRequestObserver +NS_IMETHODIMP +PresentationSessionTransport::OnStartRequest(nsIRequest* aRequest, + nsISupports* aContext) +{ + // Do nothing. + return NS_OK; +} + +NS_IMETHODIMP +PresentationSessionTransport::OnStopRequest(nsIRequest* aRequest, + nsISupports* aContext, + nsresult aStatusCode) +{ + MOZ_ASSERT(NS_IsMainThread()); + + uint32_t count; + nsresult rv = mMultiplexStream->GetCount(&count); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mInputStreamPump = nullptr; + + if (count != 0 && NS_SUCCEEDED(aStatusCode)) { + // If we have some buffered output still, and status is not an error, the + // other side has done a half-close, but we don't want to be in the close + // state until we are done sending everything that was buffered. We also + // don't want to call |NotifyTransportClosed| yet. + return NS_OK; + } + + // We call this even if there is no error. + if (mReadyState != CLOSED) { + mCloseStatus = aStatusCode; + SetReadyState(CLOSED); + } + return NS_OK; +} + +// nsIStreamListener +NS_IMETHODIMP +PresentationSessionTransport::OnDataAvailable(nsIRequest* aRequest, + nsISupports* aContext, + nsIInputStream* aStream, + uint64_t aOffset, + uint32_t aCount) +{ + MOZ_ASSERT(NS_IsMainThread()); + + if (NS_WARN_IF(!mCallback)) { + return NS_ERROR_NOT_AVAILABLE; + } + + nsCString data; + nsresult rv = mInputStreamScriptable->ReadBytes(aCount, data); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + // Pass the incoming data to the listener. + return mCallback->NotifyData(data); +} diff --git a/dom/presentation/PresentationSessionTransport.h b/dom/presentation/PresentationSessionTransport.h new file mode 100644 index 000000000000..10624e87dec2 --- /dev/null +++ b/dom/presentation/PresentationSessionTransport.h @@ -0,0 +1,99 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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_PresentationSessionTransport_h +#define mozilla_dom_PresentationSessionTransport_h + +#include "mozilla/nsRefPtr.h" +#include "nsCOMPtr.h" +#include "nsIAsyncInputStream.h" +#include "nsIPresentationSessionTransport.h" +#include "nsIStreamListener.h" +#include "nsISupportsImpl.h" +#include "nsITransport.h" + +class nsISocketTransport; +class nsIInputStreamPump; +class nsIScriptableInputStream; +class nsIMultiplexInputStream; +class nsIAsyncStreamCopier; +class nsIInputStream; + +namespace mozilla { +namespace dom { + +/* + * App-to-App transport channel for the presentation session. It's usually + * initialized with an |InitWithSocketTransport| call if at the presenting sender + * side; whereas it's initialized with an |InitWithChannelDescription| if at the + * presenting receiver side. The lifetime is managed in either + * |PresentationRequesterInfo| (sender side) or |PresentationResponderInfo| + * (receiver side) in PresentationSessionInfo.cpp. + * + * TODO bug 1148307 Implement PresentationSessionTransport with DataChannel. + * The implementation over the TCP channel is primarily used for the early stage + * of Presentation API (without SSL) and should be migrated to DataChannel with + * full support soon. + */ +class PresentationSessionTransport final : public nsIPresentationSessionTransport + , public nsITransportEventSink + , public nsIInputStreamCallback + , public nsIStreamListener +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPRESENTATIONSESSIONTRANSPORT + NS_DECL_NSITRANSPORTEVENTSINK + NS_DECL_NSIINPUTSTREAMCALLBACK + NS_DECL_NSIREQUESTOBSERVER + NS_DECL_NSISTREAMLISTENER + + PresentationSessionTransport(); + + void NotifyCopyComplete(nsresult aStatus); + +private: + ~PresentationSessionTransport(); + + nsresult CreateStream(); + + nsresult CreateInputStreamPump(); + + void EnsureCopying(); + + enum ReadyState { + CONNECTING, + OPEN, + CLOSING, + CLOSED + }; + + void SetReadyState(ReadyState aReadyState); + + ReadyState mReadyState; + bool mAsyncCopierActive; + nsresult mCloseStatus; + + // Raw socket streams + nsCOMPtr mTransport; + nsCOMPtr mSocketInputStream; + nsCOMPtr mSocketOutputStream; + + // Input stream machinery + nsCOMPtr mInputStreamPump; + nsCOMPtr mInputStreamScriptable; + + // Output stream machinery + nsCOMPtr mMultiplexStream; + nsCOMPtr mMultiplexStreamCopier; + + nsCOMPtr mCallback; +}; + +} // namespace dom +} // namespace mozilla + +#endif // mozilla_dom_PresentationSessionTransport_h diff --git a/dom/presentation/interfaces/nsIPresentationSessionTransport.idl b/dom/presentation/interfaces/nsIPresentationSessionTransport.idl index 8f5668e54758..73f3970449a4 100644 --- a/dom/presentation/interfaces/nsIPresentationSessionTransport.idl +++ b/dom/presentation/interfaces/nsIPresentationSessionTransport.idl @@ -10,9 +10,6 @@ interface nsIPresentationChannelDescription; interface nsISocketTransport; %{C++ -#define PRESENTATION_SESSION_TRANSPORT_CID \ - { 0xc9d023f4, 0x6228, 0x4c07, \ - { 0x8b, 0x1d, 0x9c, 0x19, 0x57, 0x3f, 0xaa, 0x27 } } #define PRESENTATION_SESSION_TRANSPORT_CONTRACTID \ "@mozilla.org/presentation/presentationsessiontransport;1" %} diff --git a/dom/presentation/moz.build b/dom/presentation/moz.build index 18f37fb5bf89..13f9582e426e 100644 --- a/dom/presentation/moz.build +++ b/dom/presentation/moz.build @@ -19,6 +19,7 @@ EXPORTS.mozilla.dom += [ 'PresentationService.h', 'PresentationSession.h', 'PresentationSessionInfo.h', + 'PresentationSessionTransport.h', ] UNIFIED_SOURCES += [ @@ -32,6 +33,7 @@ UNIFIED_SOURCES += [ 'PresentationSession.cpp', 'PresentationSessionInfo.cpp', 'PresentationSessionRequest.cpp', + 'PresentationSessionTransport.cpp', ] EXTRA_COMPONENTS += [ diff --git a/dom/presentation/tests/xpcshell/test_presentation_session_transport.js b/dom/presentation/tests/xpcshell/test_presentation_session_transport.js new file mode 100644 index 000000000000..f254e61fa052 --- /dev/null +++ b/dom/presentation/tests/xpcshell/test_presentation_session_transport.js @@ -0,0 +1,171 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +'use strict'; + +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr, Constructor: CC } = Components; +const ServerSocket = CC("@mozilla.org/network/server-socket;1", + "nsIServerSocket", + "init"); + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); +Cu.import('resource://gre/modules/Services.jsm'); + +var testServer = null; +var clientTransport = null; +var serverTransport = null; + +const clientMessage = "Client Message"; +const serverMessage = "Server Message"; + +const address = Cc["@mozilla.org/supports-cstring;1"] + .createInstance(Ci.nsISupportsCString); +address.data = "127.0.0.1"; +const addresses = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); +addresses.appendElement(address, false); + +const serverChannelDescription = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]), + type: 1, + tcpAddress: addresses, +}; + +var isClientReady = false; +var isServerReady = false; +var isClientClosed = false; +var isServerClosed = false; + +const clientCallback = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]), + notifyTransportReady: function () { + Assert.ok(true, "Client transport ready."); + + isClientReady = true; + if (isClientReady && isServerReady) { + run_next_test(); + } + }, + notifyTransportClosed: function (aReason) { + Assert.ok(true, "Client transport is closed."); + + isClientClosed = true; + if (isClientClosed && isServerClosed) { + run_next_test(); + } + }, + notifyData: function(aData) { + Assert.equal(aData, serverMessage, "Client transport receives data."); + run_next_test(); + }, +}; + +const serverCallback = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransportCallback]), + notifyTransportReady: function () { + Assert.ok(true, "Server transport ready."); + + isServerReady = true; + if (isClientReady && isServerReady) { + run_next_test(); + } + }, + notifyTransportClosed: function (aReason) { + Assert.ok(true, "Server transport is closed."); + + isServerClosed = true; + if (isClientClosed && isServerClosed) { + run_next_test(); + } + }, + notifyData: function(aData) { + Assert.equal(aData, clientMessage, "Server transport receives data."); + run_next_test(); + }, +}; + +function TestServer() { + this.serverSocket = ServerSocket(-1, true, -1); + this.serverSocket.asyncListen(this) +} + +TestServer.prototype = { + onSocketAccepted: function(aSocket, aTransport) { + print("Test server gets a client connection."); + serverTransport = Cc["@mozilla.org/presentation/presentationsessiontransport;1"] + .createInstance(Ci.nsIPresentationSessionTransport); + serverTransport.initWithSocketTransport(aTransport, serverCallback); + }, + onStopListening: function(aSocket) { + print("Test server stops listening."); + }, + close: function() { + if (this.serverSocket) { + this.serverSocket.close(); + this.serverSocket = null; + } + } +}; + +// Set up the transport connection and ensure |notifyTransportReady| triggered +// at both sides. +function setup() { + clientTransport = Cc["@mozilla.org/presentation/presentationsessiontransport;1"] + .createInstance(Ci.nsIPresentationSessionTransport); + clientTransport.initWithChannelDescription(serverChannelDescription, clientCallback); +} + +// Test |selfAddress| attribute of |nsIPresentationSessionTransport|. +function selfAddress() { + var serverSelfAddress = serverTransport.selfAddress; + Assert.equal(serverSelfAddress.address, address.data, "The self address of server transport should be set."); + Assert.equal(serverSelfAddress.port, testServer.serverSocket.port, "The port of server transport should be set."); + + var clientSelfAddress = clientTransport.selfAddress; + Assert.ok(clientSelfAddress.address, "The self address of client transport should be set."); + Assert.ok(clientSelfAddress.port, "The port of client transport should be set."); + + run_next_test(); +} + +// Test the client sends a message and then a corresponding notification gets +// triggered at the server side. +function clientSendMessage() { + var stream = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + stream.setData(clientMessage, clientMessage.length); + clientTransport.send(stream); +} + +// Test the server sends a message an then a corresponding notification gets +// triggered at the client side. +function serverSendMessage() { + var stream = Cc["@mozilla.org/io/string-input-stream;1"] + .createInstance(Ci.nsIStringInputStream); + stream.setData(serverMessage, serverMessage.length); + serverTransport.send(stream); +} + +function transportClose() { + clientTransport.close(Cr.NS_OK); +} + +function shutdown() { + testServer.close(); + run_next_test(); +} + +add_test(setup); +add_test(selfAddress); +add_test(clientSendMessage); +add_test(serverSendMessage); +add_test(transportClose); +add_test(shutdown); + +function run_test() { + testServer = new TestServer(); + // Get the port of the test server. + serverChannelDescription.tcpPort = testServer.serverSocket.port; + + run_next_test(); +} diff --git a/dom/presentation/tests/xpcshell/xpcshell.ini b/dom/presentation/tests/xpcshell/xpcshell.ini index e954805c3895..76063d1479ed 100644 --- a/dom/presentation/tests/xpcshell/xpcshell.ini +++ b/dom/presentation/tests/xpcshell/xpcshell.ini @@ -4,4 +4,5 @@ tail = [test_multicast_dns_device_provider.js] [test_presentation_device_manager.js] +[test_presentation_session_transport.js] [test_tcp_control_channel.js] diff --git a/layout/build/nsLayoutModule.cpp b/layout/build/nsLayoutModule.cpp index 2af3ab0f2c4e..193c21a8fb2d 100644 --- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -262,6 +262,7 @@ static void Shutdown(); #include "GMPService.h" #include "mozilla/dom/PresentationDeviceManager.h" +#include "mozilla/dom/PresentationSessionTransport.h" #include "mozilla/TextInputProcessor.h" @@ -294,6 +295,9 @@ using mozilla::gmp::GeckoMediaPluginService; #define PRESENTATION_DEVICE_MANAGER_CID \ { 0xe1e79dec, 0x4085, 0x4994, { 0xac, 0x5b, 0x74, 0x4b, 0x01, 0x66, 0x97, 0xe6 } } +#define PRESENTATION_SESSION_TRANSPORT_CID \ +{ 0xc9d023f4, 0x6228, 0x4c07, { 0x8b, 0x1d, 0x9c, 0x19, 0x57, 0x3f, 0xaa, 0x27 } } + already_AddRefed NS_CreatePresentationService(); // Factory Constructor @@ -406,6 +410,7 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(FakeInputPortService, NS_GENERIC_FACTORY_CONSTRUCTOR(InputPortData) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIPresentationService, NS_CreatePresentationService) +NS_GENERIC_FACTORY_CONSTRUCTOR(PresentationSessionTransport) //----------------------------------------------------------------------------- static bool gInitialized = false; @@ -864,6 +869,7 @@ NS_DEFINE_NAMED_CID(GECKO_MEDIA_PLUGIN_SERVICE_CID); NS_DEFINE_NAMED_CID(PRESENTATION_SERVICE_CID); NS_DEFINE_NAMED_CID(PRESENTATION_DEVICE_MANAGER_CID); +NS_DEFINE_NAMED_CID(PRESENTATION_SESSION_TRANSPORT_CID); NS_DEFINE_NAMED_CID(TEXT_INPUT_PROCESSOR_CID); @@ -1163,6 +1169,7 @@ static const mozilla::Module::CIDEntry kLayoutCIDs[] = { { &kTV_PROGRAM_DATA_CID, false, nullptr, TVProgramDataConstructor }, { &kPRESENTATION_SERVICE_CID, false, nullptr, nsIPresentationServiceConstructor }, { &kPRESENTATION_DEVICE_MANAGER_CID, false, nullptr, PresentationDeviceManagerConstructor }, + { &kPRESENTATION_SESSION_TRANSPORT_CID, false, nullptr, PresentationSessionTransportConstructor }, { &kTEXT_INPUT_PROCESSOR_CID, false, nullptr, TextInputProcessorConstructor }, { &kFAKE_INPUTPORT_SERVICE_CID, false, nullptr, FakeInputPortServiceConstructor }, { &kINPUTPORT_DATA_CID, false, nullptr, InputPortDataConstructor }, @@ -1333,6 +1340,7 @@ static const mozilla::Module::ContractIDEntry kLayoutContracts[] = { { NS_VOICEMAIL_SERVICE_CONTRACTID, &kNS_VOICEMAIL_SERVICE_CID }, { PRESENTATION_SERVICE_CONTRACTID, &kPRESENTATION_SERVICE_CID }, { PRESENTATION_DEVICE_MANAGER_CONTRACTID, &kPRESENTATION_DEVICE_MANAGER_CID }, + { PRESENTATION_SESSION_TRANSPORT_CONTRACTID, &kPRESENTATION_SESSION_TRANSPORT_CID }, { "@mozilla.org/text-input-processor;1", &kTEXT_INPUT_PROCESSOR_CID }, { FAKE_INPUTPORT_SERVICE_CONTRACTID, &kFAKE_INPUTPORT_SERVICE_CID }, { INPUTPORT_DATA_CONTRACTID, &kINPUTPORT_DATA_CID }, From 5eda6c5cc40ca24fe22373d3093aad7febd960da Mon Sep 17 00:00:00 2001 From: Sean Lin Date: Thu, 23 Apr 2015 11:44:01 +0800 Subject: [PATCH 14/18] Bug 1069230 - Presentation API implementation. Part 9 - Tests. r=kikuo --- dom/presentation/PresentationSessionInfo.cpp | 6 +- .../PresentationSessionChromeScript.js | 359 ++++++++++++++++++ .../mochitest/file_presentation_receiver.html | 77 ++++ .../file_presentation_receiver_oop.html | 77 ++++ ...entation_receiver_start_session_error.html | 65 ++++ .../tests/mochitest/mochitest.ini | 18 + .../mochitest/test_presentation_receiver.html | 122 ++++++ .../test_presentation_receiver_oop.html | 131 +++++++ ...entation_receiver_start_session_error.html | 110 ++++++ ...tation_receiver_start_session_timeout.html | 84 ++++ .../mochitest/test_presentation_sender.html | 167 ++++++++ .../test_presentation_sender_disconnect.html | 150 ++++++++ ...esentation_sender_start_session_error.html | 239 ++++++++++++ 13 files changed, 1604 insertions(+), 1 deletion(-) create mode 100644 dom/presentation/tests/mochitest/PresentationSessionChromeScript.js create mode 100644 dom/presentation/tests/mochitest/file_presentation_receiver.html create mode 100644 dom/presentation/tests/mochitest/file_presentation_receiver_oop.html create mode 100644 dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html create mode 100644 dom/presentation/tests/mochitest/test_presentation_receiver.html create mode 100644 dom/presentation/tests/mochitest/test_presentation_receiver_oop.html create mode 100644 dom/presentation/tests/mochitest/test_presentation_receiver_start_session_error.html create mode 100644 dom/presentation/tests/mochitest/test_presentation_receiver_start_session_timeout.html create mode 100644 dom/presentation/tests/mochitest/test_presentation_sender.html create mode 100644 dom/presentation/tests/mochitest/test_presentation_sender_disconnect.html create mode 100644 dom/presentation/tests/mochitest/test_presentation_sender_start_session_error.html diff --git a/dom/presentation/PresentationSessionInfo.cpp b/dom/presentation/PresentationSessionInfo.cpp index 65c94fc5d841..254653b77b42 100644 --- a/dom/presentation/PresentationSessionInfo.cpp +++ b/dom/presentation/PresentationSessionInfo.cpp @@ -518,6 +518,10 @@ PresentationRequesterInfo::OnStopListening(nsIServerSocket* aServerSocket, { MOZ_ASSERT(NS_IsMainThread()); + if (aStatus == NS_BINDING_ABORTED) { // The server socket was manually closed. + return NS_OK; + } + Shutdown(aStatus); if (!IsSessionReady()) { @@ -795,7 +799,7 @@ PresentationResponderInfo::RejectedCallback(JSContext* aCx, JS::Handle aValue) { MOZ_ASSERT(NS_IsMainThread()); - NS_WARNING("The receiver page fails to become ready before timeout."); + NS_WARNING("Launching the receiver page has been rejected."); if (mTimer) { mTimer->Cancel(); diff --git a/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js b/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js new file mode 100644 index 000000000000..b1913b9ba0cd --- /dev/null +++ b/dom/presentation/tests/mochitest/PresentationSessionChromeScript.js @@ -0,0 +1,359 @@ +/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ +'use strict'; + +const { classes: Cc, interfaces: Ci, manager: Cm, utils: Cu, results: Cr } = Components; + +Cu.import('resource://gre/modules/XPCOMUtils.jsm'); + +function registerMockedFactory(contractId, mockedClassId, mockedFactory) { + var originalClassId, originalFactory; + + var registrar = Cm.QueryInterface(Ci.nsIComponentRegistrar); + if (!registrar.isCIDRegistered(mockedClassId)) { + try { + originalClassId = registrar.contractIDToCID(contractId); + originalFactory = Cm.getClassObject(Cc[contractId], Ci.nsIFactory); + } catch (ex) { + originalClassId = ""; + originalFactory = null; + } + if (originalFactory) { + registrar.unregisterFactory(originalClassId, originalFactory); + } + registrar.registerFactory(mockedClassId, "", contractId, mockedFactory); + } + + return { contractId: contractId, + mockedClassId: mockedClassId, + mockedFactory: mockedFactory, + originalClassId: originalClassId, + originalFactory: originalFactory }; +} + +function registerOriginalFactory(contractId, mockedClassId, mockedFactory, originalClassId, originalFactory) { + if (originalFactory) { + registrar.unregisterFactory(mockedClassId, mockedFactory); + registrar.registerFactory(originalClassId, "", contractId, originalFactory); + } +} + +const sessionId = 'test-session-id'; + +const address = Cc["@mozilla.org/supports-cstring;1"] + .createInstance(Ci.nsISupportsCString); +address.data = "127.0.0.1"; +const addresses = Cc["@mozilla.org/array;1"].createInstance(Ci.nsIMutableArray); +addresses.appendElement(address, false); + +const mockedChannelDescription = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationChannelDescription]), + type: 1, + tcpAddress: addresses, + tcpPort: 1234, +}; + +const mockedServerSocket = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIServerSocket, + Ci.nsIFactory]), + createInstance: function(aOuter, aIID) { + if (aOuter) { + throw Components.results.NS_ERROR_NO_AGGREGATION; + } + return this.QueryInterface(aIID); + }, + get port() { + return this._port; + }, + set listener(listener) { + this._listener = listener; + }, + init: function(port, loopbackOnly, backLog) { + if (port != -1) { + this._port = port; + } else { + this._port = 5678; + } + }, + asyncListen: function(listener) { + this._listener = listener; + }, + close: function() { + this._listener.onStopListening(this, Cr.NS_BINDING_ABORTED); + }, + simulateOnSocketAccepted: function(serverSocket, socketTransport) { + this._listener.onSocketAccepted(serverSocket, socketTransport); + } +}; + +const mockedSocketTransport = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsISocketTransport]), +}; + +const mockedControlChannel = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationControlChannel]), + set listener(listener) { + this._listener = listener; + }, + get listener() { + return this._listener; + }, + sendOffer: function(offer) { + sendAsyncMessage('offer-sent'); + }, + sendAnswer: function(answer) { + this._listener.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady(); + }, + close: function(reason) { + sendAsyncMessage('control-channel-closed', reason); + this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyClosed(reason); + }, + simulateReceiverReady: function() { + this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).notifyReceiverReady(); + }, + simulateOnOffer: function() { + sendAsyncMessage('offer-received'); + this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).onOffer(mockedChannelDescription); + }, + simulateOnAnswer: function() { + sendAsyncMessage('answer-received'); + this._listener.QueryInterface(Ci.nsIPresentationControlChannelListener).onAnswer(mockedChannelDescription); + }, +}; + +const mockedDevice = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevice]), + id: 'id', + name: 'name', + type: 'type', + establishControlChannel: function(url, presentationId) { + sendAsyncMessage('control-channel-established'); + return mockedControlChannel; + }, + set listener(listener) { + this._listener = listener; + }, + get listener() { + return this._listener; + }, + simulateSessionRequest: function(url, presentationId, controlChannel) { + this._listener.onSessionRequest(this, url, presentationId, controlChannel); + }, +}; + +const mockedDevicePrompt = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationDevicePrompt, + Ci.nsIFactory]), + createInstance: function(aOuter, aIID) { + if (aOuter) { + throw Components.results.NS_ERROR_NO_AGGREGATION; + } + return this.QueryInterface(aIID); + }, + set request(request) { + this._request = request; + }, + get request() { + return this._request; + }, + promptDeviceSelection: function(request) { + this._request = request; + sendAsyncMessage('device-prompt'); + }, + simulateSelect: function() { + this._request.select(mockedDevice); + }, + simulateCancel: function() { + this._request.cancel(); + } +}; + +const mockedSessionTransport = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationSessionTransport, + Ci.nsIFactory]), + createInstance: function(aOuter, aIID) { + if (aOuter) { + throw Components.results.NS_ERROR_NO_AGGREGATION; + } + return this.QueryInterface(aIID); + }, + set callback(callback) { + this._callback = callback; + }, + get callback() { + return this._callback; + }, + get selfAddress() { + return this._selfAddress; + }, + initWithSocketTransport: function(transport, callback) { + sendAsyncMessage('data-transport-initialized'); + this._callback = callback; + this.simulateTransportReady(); + }, + initWithChannelDescription: function(description, callback) { + this._callback = callback; + + var addresses = description.QueryInterface(Ci.nsIPresentationChannelDescription).tcpAddress; + this._selfAddress = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsINetAddr]), + address: (addresses.length > 0) ? + addresses.queryElementAt(0, Ci.nsISupportsCString).data : "", + port: description.QueryInterface(Ci.nsIPresentationChannelDescription).tcpPort, + }; + }, + send: function(data) { + var binaryStream = Cc["@mozilla.org/binaryinputstream;1"]. + createInstance(Ci.nsIBinaryInputStream); + binaryStream.setInputStream(data); + var message = binaryStream.readBytes(binaryStream.available()); + sendAsyncMessage('message-sent', message); + }, + close: function(reason) { + sendAsyncMessage('data-transport-closed', reason); + this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportClosed(reason); + }, + simulateTransportReady: function() { + this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyTransportReady(); + }, + simulateIncomingMessage: function(message) { + this._callback.QueryInterface(Ci.nsIPresentationSessionTransportCallback).notifyData(message); + }, +}; + +const mockedNetworkInfo = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkInfo]), + getAddresses: function(ips, prefixLengths) { + ips.value = ["127.0.0.1"]; + prefixLengths.value = [0]; + return 1; + }, +}; + +const mockedNetworkManager = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsINetworkManager, + Ci.nsIFactory]), + createInstance: function(aOuter, aIID) { + if (aOuter) { + throw Components.results.NS_ERROR_NO_AGGREGATION; + } + return this.QueryInterface(aIID); + }, + get activeNetworkInfo() { + return mockedNetworkInfo; + }, +}; + +var requestPromise = null; + +const mockedRequestUIGlue = { + QueryInterface: XPCOMUtils.generateQI([Ci.nsIPresentationRequestUIGlue, + Ci.nsIFactory]), + createInstance: function(aOuter, aIID) { + if (aOuter) { + throw Components.results.NS_ERROR_NO_AGGREGATION; + } + return this.QueryInterface(aIID); + }, + sendRequest: function(aUrl, aSessionId) { + sendAsyncMessage('receiver-launching', aSessionId); + return requestPromise; + }, +}; + +// Register mocked factories. +const uuidGenerator = Cc["@mozilla.org/uuid-generator;1"] + .getService(Ci.nsIUUIDGenerator); +const originalFactoryData = []; +originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation-device/prompt;1", + uuidGenerator.generateUUID(), + mockedDevicePrompt)); +originalFactoryData.push(registerMockedFactory("@mozilla.org/network/server-socket;1", + uuidGenerator.generateUUID(), + mockedServerSocket)); +originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/presentationsessiontransport;1", + uuidGenerator.generateUUID(), + mockedSessionTransport)); +originalFactoryData.push(registerMockedFactory("@mozilla.org/network/manager;1", + uuidGenerator.generateUUID(), + mockedNetworkManager)); +originalFactoryData.push(registerMockedFactory("@mozilla.org/presentation/requestuiglue;1", + uuidGenerator.generateUUID(), + mockedRequestUIGlue)); + +function tearDown() { + requestPromise = null; + mockedServerSocket.listener = null; + mockedControlChannel.listener = null; + mockedDevice.listener = null; + mockedDevicePrompt.request = null; + mockedSessionTransport.callback = null; + + var deviceManager = Cc['@mozilla.org/presentation-device/manager;1'] + .getService(Ci.nsIPresentationDeviceManager); + deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener).removeDevice(mockedDevice); + + // Register original factories. + for (var data in originalFactoryData) { + registerOriginalFactory(data.contractId, data.mockedClassId, + data.mockedFactory, data.originalClassId, + data.originalFactory); + } + + sendAsyncMessage('teardown-complete'); +} + +addMessageListener('trigger-device-add', function() { + var deviceManager = Cc['@mozilla.org/presentation-device/manager;1'] + .getService(Ci.nsIPresentationDeviceManager); + deviceManager.QueryInterface(Ci.nsIPresentationDeviceListener).addDevice(mockedDevice); +}); + +addMessageListener('trigger-device-prompt-select', function() { + mockedDevicePrompt.simulateSelect(); +}); + +addMessageListener('trigger-device-prompt-cancel', function() { + mockedDevicePrompt.simulateCancel(); +}); + +addMessageListener('trigger-incoming-session-request', function(url) { + mockedDevice.simulateSessionRequest(url, sessionId, mockedControlChannel); +}); + +addMessageListener('trigger-incoming-offer', function() { + mockedControlChannel.simulateOnOffer(); +}); + +addMessageListener('trigger-incoming-answer', function() { + mockedControlChannel.simulateOnAnswer(); +}); + +addMessageListener('trigger-incoming-transport', function() { + mockedServerSocket.simulateOnSocketAccepted(mockedServerSocket, mockedSocketTransport); +}); + +addMessageListener('trigger-control-channel-close', function(reason) { + mockedControlChannel.close(reason); +}); + +addMessageListener('trigger-data-transport-close', function(reason) { + mockedSessionTransport.close(reason); +}); + +addMessageListener('trigger-incoming-message', function(message) { + mockedSessionTransport.simulateIncomingMessage(message); +}); + +addMessageListener('teardown', function() { + tearDown(); +}); + +var obs = Cc["@mozilla.org/observer-service;1"] + .getService(Ci.nsIObserverService); +obs.addObserver(function observer(aSubject, aTopic, aData) { + obs.removeObserver(observer, aTopic); + + requestPromise = aSubject; +}, 'setup-request-promise', false); diff --git a/dom/presentation/tests/mochitest/file_presentation_receiver.html b/dom/presentation/tests/mochitest/file_presentation_receiver.html new file mode 100644 index 000000000000..1d10bfbdd4dd --- /dev/null +++ b/dom/presentation/tests/mochitest/file_presentation_receiver.html @@ -0,0 +1,77 @@ + + + + + Test for B2G Presentation Session API at receiver side + + +
+ + + diff --git a/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html b/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html new file mode 100644 index 000000000000..38b307a68d18 --- /dev/null +++ b/dom/presentation/tests/mochitest/file_presentation_receiver_oop.html @@ -0,0 +1,77 @@ + + + + + Test for B2G Presentation Session API at receiver side (OOP) + + +
+ + + diff --git a/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html b/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html new file mode 100644 index 000000000000..4be01f6f1e32 --- /dev/null +++ b/dom/presentation/tests/mochitest/file_presentation_receiver_start_session_error.html @@ -0,0 +1,65 @@ + + + + + Test for startSession errors of B2G Presentation API at receiver side + + +
+ + + diff --git a/dom/presentation/tests/mochitest/mochitest.ini b/dom/presentation/tests/mochitest/mochitest.ini index c470d38e7a19..74b793aed19b 100644 --- a/dom/presentation/tests/mochitest/mochitest.ini +++ b/dom/presentation/tests/mochitest/mochitest.ini @@ -1,6 +1,24 @@ [DEFAULT] support-files = PresentationDeviceInfoChromeScript.js + PresentationSessionChromeScript.js + file_presentation_receiver.html + file_presentation_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 +[test_presentation_sender.html] +skip-if = toolkit == 'android' # Bug 1129785 +[test_presentation_receiver_start_session_error.html] +skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785 +[test_presentation_receiver_start_session_timeout.html] +skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785 +[test_presentation_receiver.html] +skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785 +[test_presentation_receiver_oop.html] +skip-if = (e10s || toolkit == 'gonk' || toolkit == 'android') # Bug 1129785 diff --git a/dom/presentation/tests/mochitest/test_presentation_receiver.html b/dom/presentation/tests/mochitest/test_presentation_receiver.html new file mode 100644 index 000000000000..29c74c7f27ac --- /dev/null +++ b/dom/presentation/tests/mochitest/test_presentation_receiver.html @@ -0,0 +1,122 @@ + + + + + + Test for B2G Presentation Session API at receiver side + + + + +Test for B2G Presentation Session API at receiver side +

+ +

+
+
+
diff --git a/dom/presentation/tests/mochitest/test_presentation_receiver_oop.html b/dom/presentation/tests/mochitest/test_presentation_receiver_oop.html
new file mode 100644
index 000000000000..4f0773ab23f0
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver_oop.html
@@ -0,0 +1,131 @@
+
+
+
+
+  
+  Test for B2G Presentation Session API at receiver side (OOP)
+  
+  
+
+
+Test B2G Presentation Session API at receiver side (OOP)
+

+ +

+
+
+
diff --git a/dom/presentation/tests/mochitest/test_presentation_receiver_start_session_error.html b/dom/presentation/tests/mochitest/test_presentation_receiver_start_session_error.html
new file mode 100644
index 000000000000..e192a0e14408
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver_start_session_error.html
@@ -0,0 +1,110 @@
+
+
+
+
+  
+  Test for startSession errors of B2G Presentation API at receiver side
+  
+  
+
+
+Test for startSession errors of B2G Presentation API at receiver side
+
+
+
diff --git a/dom/presentation/tests/mochitest/test_presentation_receiver_start_session_timeout.html b/dom/presentation/tests/mochitest/test_presentation_receiver_start_session_timeout.html
new file mode 100644
index 000000000000..ae8a59721e35
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_receiver_start_session_timeout.html
@@ -0,0 +1,84 @@
+
+
+
+
+  
+  Test for startSession timeout of B2G Presentation API at receiver side
+  
+  
+
+
+Test for startSession timeout of B2G Presentation API at receiver side
+
+
+
diff --git a/dom/presentation/tests/mochitest/test_presentation_sender.html b/dom/presentation/tests/mochitest/test_presentation_sender.html
new file mode 100644
index 000000000000..94ce44f1c236
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_sender.html
@@ -0,0 +1,167 @@
+
+
+
+
+  
+  Test for B2G Presentation API at sender side
+  
+  
+
+
+Test for B2G Presentation API at sender side
+
+
+
diff --git a/dom/presentation/tests/mochitest/test_presentation_sender_disconnect.html b/dom/presentation/tests/mochitest/test_presentation_sender_disconnect.html
new file mode 100644
index 000000000000..2774b569b635
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_disconnect.html
@@ -0,0 +1,150 @@
+
+
+
+
+  
+  Test for session disconnection of B2G Presentation API at sender side
+  
+  
+
+
+Test for session disconnection of B2G Presentation API at sender side
+
+
+
diff --git a/dom/presentation/tests/mochitest/test_presentation_sender_start_session_error.html b/dom/presentation/tests/mochitest/test_presentation_sender_start_session_error.html
new file mode 100644
index 000000000000..85e0cf4178f9
--- /dev/null
+++ b/dom/presentation/tests/mochitest/test_presentation_sender_start_session_error.html
@@ -0,0 +1,239 @@
+
+
+
+
+  
+  Test for startSession errors of B2G Presentation API at sender side
+  
+  
+
+
+Test for startSession errors of B2G Presentation API at sender side
+
+
+

From e821cb04d6ec994cff18479928fef78c3445d110 Mon Sep 17 00:00:00 2001
From: B2G Bumper Bot 
Date: Thu, 6 Aug 2015 13:46:54 -0700
Subject: [PATCH 15/18] Bumping gaia.json for 2 gaia revision(s) a=gaia-bump

========

https://hg.mozilla.org/integration/gaia-central/rev/0fde69e039cb
Author: Zibi Braniecki 
Desc: Merge pull request #31203 from zbraniecki/1187668-settings-l10n-downloads

Bug 1187668 - update settings/downloads l10n api uses. r=evelyn, gasolin

========

https://hg.mozilla.org/integration/gaia-central/rev/4d4ca8432b6f
Author: Zibi Braniecki 
Desc: Bug 1187668 - update settings/downloads l10n api uses.
---
 b2g/config/gaia.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json
index b24cd1a77226..10bf6cf98009 100644
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "682419bda6501c3d84ef116c9c51530eee99dbf6", 
+        "git_revision": "f33da62d93ef341a5dbd7e60bfe160495df3dc1d", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "7a5f84e60e7859d0b6aa285c15c5133115341670", 
+    "revision": "0fde69e039cb0b75f1af3fe0817deb3058fca32d", 
     "repo_path": "integration/gaia-central"
 }

From d10e90d711205fb1ba1516d34287ec7cc0523646 Mon Sep 17 00:00:00 2001
From: B2G Bumper Bot 
Date: Thu, 6 Aug 2015 13:47:51 -0700
Subject: [PATCH 16/18] Bumping manifests a=b2g-bump

---
 b2g/config/aries/sources.xml        | 2 +-
 b2g/config/dolphin/sources.xml      | 2 +-
 b2g/config/emulator-ics/sources.xml | 2 +-
 b2g/config/emulator-jb/sources.xml  | 2 +-
 b2g/config/emulator-kk/sources.xml  | 2 +-
 b2g/config/emulator-l/sources.xml   | 2 +-
 b2g/config/emulator/sources.xml     | 2 +-
 b2g/config/flame-kk/sources.xml     | 2 +-
 b2g/config/nexus-4/sources.xml      | 2 +-
 b2g/config/nexus-5-l/sources.xml    | 2 +-
 10 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml
index 67de9b66e03a..1361b985f1d2 100644
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -15,7 +15,7 @@
   
     
   
-  
+  
   
   
   
diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml
index d732fa4699fd..4a05f7b8052d 100644
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -15,7 +15,7 @@
   
     
   
-  
+  
   
   
   
diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml
index 19814f8c46c3..3dd9ee04673d 100644
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -19,7 +19,7 @@
     
   
   
-  
+  
   
   
   
diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml
index 0590f90573ae..6a13cf0ed4b5 100644
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -17,7 +17,7 @@
   
   
   
-  
+  
   
   
   
diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml
index 59a1c8ee52c7..289a86cc68b8 100644
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -15,7 +15,7 @@
   
     
   
-  
+  
   
   
   
diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml
index e3b1b3f476de..12d47ad93bf8 100644
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -15,7 +15,7 @@
   
     
   
-  
+  
   
   
   
diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml
index 19814f8c46c3..3dd9ee04673d 100644
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -19,7 +19,7 @@
     
   
   
-  
+  
   
   
   
diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml
index 66f66c482637..7d80b981ec2a 100644
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -15,7 +15,7 @@
   
     
   
-  
+  
   
   
   
diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml
index de062c8adad5..7cb81b32da9a 100644
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -17,7 +17,7 @@
   
   
   
-  
+  
   
   
   
diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml
index db0f49117ab3..a2c8cb45bb9f 100644
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -15,7 +15,7 @@
   
     
   
-  
+  
   
   
   

From 8e7f92e19a0742c90cd0339b07603a94a18ef36e Mon Sep 17 00:00:00 2001
From: B2G Bumper Bot 
Date: Thu, 6 Aug 2015 15:25:25 -0700
Subject: [PATCH 17/18] Bumping gaia.json for 1 gaia revision(s) a=gaia-bump

========

https://hg.mozilla.org/integration/gaia-central/rev/ab36b7edaadc
Author: Wes Kocher 
Desc: Revert "Merge pull request #31203 from zbraniecki/1187668-settings-l10n-downloads" for download_formatter_test.js failures

This reverts commit f33da62d93ef341a5dbd7e60bfe160495df3dc1d, reversing
changes made to 682419bda6501c3d84ef116c9c51530eee99dbf6.
---
 b2g/config/gaia.json | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json
index 10bf6cf98009..8a522641d72f 100644
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
-        "git_revision": "f33da62d93ef341a5dbd7e60bfe160495df3dc1d", 
+        "git_revision": "91068221506ff05692aa187ac314e15443db68fd", 
         "remote": "https://git.mozilla.org/releases/gaia.git", 
         "branch": ""
     }, 
-    "revision": "0fde69e039cb0b75f1af3fe0817deb3058fca32d", 
+    "revision": "ab36b7edaadc88ece94cc213db139d1615dadf64", 
     "repo_path": "integration/gaia-central"
 }

From a7579e616393aea02658edc34a874792f4412293 Mon Sep 17 00:00:00 2001
From: B2G Bumper Bot 
Date: Thu, 6 Aug 2015 15:27:04 -0700
Subject: [PATCH 18/18] Bumping manifests a=b2g-bump

---
 b2g/config/aries/sources.xml        | 2 +-
 b2g/config/dolphin/sources.xml      | 2 +-
 b2g/config/emulator-ics/sources.xml | 2 +-
 b2g/config/emulator-jb/sources.xml  | 2 +-
 b2g/config/emulator-kk/sources.xml  | 2 +-
 b2g/config/emulator-l/sources.xml   | 2 +-
 b2g/config/emulator/sources.xml     | 2 +-
 b2g/config/flame-kk/sources.xml     | 2 +-
 b2g/config/nexus-4/sources.xml      | 2 +-
 b2g/config/nexus-5-l/sources.xml    | 2 +-
 10 files changed, 10 insertions(+), 10 deletions(-)

diff --git a/b2g/config/aries/sources.xml b/b2g/config/aries/sources.xml
index 1361b985f1d2..c1c745b76d78 100644
--- a/b2g/config/aries/sources.xml
+++ b/b2g/config/aries/sources.xml
@@ -15,7 +15,7 @@
   
     
   
-  
+  
   
   
   
diff --git a/b2g/config/dolphin/sources.xml b/b2g/config/dolphin/sources.xml
index 4a05f7b8052d..ab448096cb22 100644
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -15,7 +15,7 @@
   
     
   
-  
+  
   
   
   
diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml
index 3dd9ee04673d..c02a4fc1d677 100644
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -19,7 +19,7 @@
     
   
   
-  
+  
   
   
   
diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml
index 6a13cf0ed4b5..74cfbd27a209 100644
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -17,7 +17,7 @@
   
   
   
-  
+  
   
   
   
diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml
index 289a86cc68b8..2936cba74d1f 100644
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -15,7 +15,7 @@
   
     
   
-  
+  
   
   
   
diff --git a/b2g/config/emulator-l/sources.xml b/b2g/config/emulator-l/sources.xml
index 12d47ad93bf8..d9e7a2f3c94a 100644
--- a/b2g/config/emulator-l/sources.xml
+++ b/b2g/config/emulator-l/sources.xml
@@ -15,7 +15,7 @@
   
     
   
-  
+  
   
   
   
diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml
index 3dd9ee04673d..c02a4fc1d677 100644
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -19,7 +19,7 @@
     
   
   
-  
+  
   
   
   
diff --git a/b2g/config/flame-kk/sources.xml b/b2g/config/flame-kk/sources.xml
index 7d80b981ec2a..2f4a5cad2e9b 100644
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -15,7 +15,7 @@
   
     
   
-  
+  
   
   
   
diff --git a/b2g/config/nexus-4/sources.xml b/b2g/config/nexus-4/sources.xml
index 7cb81b32da9a..3e9fb6381cd2 100644
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -17,7 +17,7 @@
   
   
   
-  
+  
   
   
   
diff --git a/b2g/config/nexus-5-l/sources.xml b/b2g/config/nexus-5-l/sources.xml
index a2c8cb45bb9f..890a831947fb 100644
--- a/b2g/config/nexus-5-l/sources.xml
+++ b/b2g/config/nexus-5-l/sources.xml
@@ -15,7 +15,7 @@
   
     
   
-  
+